feat(up): capture follower snapshots at ingest and backfill history
This commit is contained in:
1 parent
4942d1d161
commit
519fc39fca
8 files changed
+401
-14
No files matched your search
@@ -0,0 +1,132 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""UP 粉丝快照采集测试(不联网): uv run python tests/test_up_profile.py"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
sys.stdout.reconfigure(encoding="utf-8")
|
||||
|
||||
from reaction_board.bilibili.models import FavResource # noqa: E402
|
||||
from reaction_board.config import FolderConfig # noqa: E402
|
||||
from reaction_board.database import Database # noqa: E402
|
||||
from reaction_board.repository import Repository # noqa: E402
|
||||
from reaction_board.services.up_profile import UpProfileService # noqa: E402
|
||||
|
||||
FOLDER = FolderConfig(year=2026, media_id=3700023351, title="2026")
|
||||
NOW = 1_800_000_000
|
||||
|
||||
|
||||
def resource(aid, up_mid):
|
||||
return FavResource(
|
||||
aid=aid,
|
||||
bvid=f"BV{aid}",
|
||||
title=f"视频{aid}",
|
||||
up_mid=up_mid,
|
||||
up_name=f"UP{up_mid}",
|
||||
pubdate=NOW - 86400,
|
||||
duration=7200,
|
||||
cover=None,
|
||||
attr=0,
|
||||
fav_time=None,
|
||||
pages=2,
|
||||
)
|
||||
|
||||
|
||||
class FakeClient:
|
||||
def __init__(self, *, failing=()):
|
||||
self.calls = []
|
||||
self.failing = set(failing)
|
||||
|
||||
def relation_stat(self, mid):
|
||||
self.calls.append(int(mid))
|
||||
if int(mid) in self.failing:
|
||||
raise RuntimeError("simulated network error")
|
||||
return SimpleNamespace(mid=int(mid), follower=1000 + int(mid), following=5)
|
||||
|
||||
|
||||
def setup(tmp):
|
||||
db = Database(Path(tmp) / "app.db")
|
||||
db.initialize()
|
||||
repo = Repository(db, clock=lambda: NOW)
|
||||
repo.ensure_folder(FOLDER)
|
||||
for aid, mid in ((1, 100), (2, 100), (3, 200)):
|
||||
repo.upsert_from_fav(resource(aid, mid), FOLDER.media_id, was_added=True)
|
||||
return db, repo
|
||||
|
||||
|
||||
def test_capture_dedupes_and_continues_after_failure():
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
db, repo = setup(tmp)
|
||||
try:
|
||||
client = FakeClient(failing={200})
|
||||
service = UpProfileService(repo, lambda: client, clock=lambda: NOW)
|
||||
result = service.capture(
|
||||
[100, 100, 200, None, 0], live_since=NOW - 3600
|
||||
)
|
||||
assert result.requested == 2
|
||||
assert client.calls == [100, 200]
|
||||
assert result.updated_videos == 2
|
||||
assert result.failed == 1
|
||||
assert "200" in result.errors[0]
|
||||
|
||||
assert repo.get_video(1)["up_follower"] == 1100
|
||||
assert repo.get_video(2)["up_follower"] == 1100
|
||||
assert repo.get_video(3)["up_follower"] is None
|
||||
assert repo.get_up_profile(100)["follower_first"] == 1100
|
||||
assert repo.get_up_profile(200) is None
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
def test_capture_marks_old_videos_as_backfilled():
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
db, repo = setup(tmp)
|
||||
try:
|
||||
client = FakeClient()
|
||||
service = UpProfileService(repo, lambda: client, clock=lambda: NOW)
|
||||
service.capture([100], live_since=NOW + 1)
|
||||
assert repo.get_video(1)["up_follower_backfilled"] == 1
|
||||
assert repo.get_video(2)["up_follower_backfilled"] == 1
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
def test_backfill_dry_run_and_limit():
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
db, repo = setup(tmp)
|
||||
try:
|
||||
client = FakeClient()
|
||||
service = UpProfileService(repo, lambda: client, clock=lambda: NOW)
|
||||
|
||||
dry = service.backfill(dry_run=True)
|
||||
assert dry.requested == 2
|
||||
assert client.calls == []
|
||||
assert repo.up_follower_coverage()["missing"] == 3
|
||||
|
||||
limited = service.backfill(limit=1)
|
||||
assert limited.requested == 1
|
||||
assert client.calls == [100]
|
||||
assert repo.get_video(1)["up_follower_backfilled"] == 1
|
||||
assert repo.get_video(3)["up_follower"] is None
|
||||
|
||||
rest = service.backfill()
|
||||
assert rest.requested == 1
|
||||
assert client.calls == [100, 200]
|
||||
assert repo.up_follower_coverage()["missing"] == 0
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
def main():
|
||||
test_capture_dedupes_and_continues_after_failure()
|
||||
test_capture_marks_old_videos_as_backfilled()
|
||||
test_backfill_dry_run_and_limit()
|
||||
print("OK: UP 粉丝快照采集与回填测试全部通过")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in new issue
Block a user