Backfill same-day snapshots for newly added or restored videos, preserve successful fan snapshots on resume, persist incomplete state before forced re-runs, and split collection completion from backup completion so failed backups can retry without API calls. Reject missing data payloads instead of treating them as empty folders, guard against mass-unfav on suspicious empty remote folders, rebuild Bilibili clients when credentials change, recompute primary folder ownership from active relations with per-folder fav_time, preserve original titles for dropped invalid videos, poll config every 60 seconds, and fix favsort verify interval and mixed resource types.
107 lines
3.3 KiB
Python
107 lines
3.3 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""凭据变化时客户端重建测试(不联网): uv run python tests/test_provider.py"""
|
|
|
|
import json
|
|
import os
|
|
import sys
|
|
import tempfile
|
|
import time
|
|
from pathlib import Path
|
|
|
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
sys.stdout.reconfigure(encoding="utf-8")
|
|
|
|
from reaction_board.bilibili.client import BilibiliClient, Pacer # noqa: E402
|
|
from reaction_board.bilibili.provider import ClientProvider # noqa: E402
|
|
from reaction_board.config import ConfigStore # noqa: E402
|
|
|
|
|
|
def write_config(path: Path, *, cookie: str, mid: int) -> None:
|
|
path.write_text(json.dumps({
|
|
"bilibili": {
|
|
"read": {"mid": mid, "csrf": "read-csrf", "cookie": cookie},
|
|
"write": {"mid": 2, "csrf": "write-csrf", "cookie": "write-cookie"},
|
|
},
|
|
"folders": [],
|
|
}, ensure_ascii=False), encoding="utf-8")
|
|
|
|
|
|
def bump(path: Path) -> None:
|
|
target = max(path.stat().st_mtime_ns, time.time_ns()) + 1_000_000
|
|
os.utime(path, ns=(target, target))
|
|
|
|
|
|
def test_rebuild_only_when_credentials_change():
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
path = Path(tmp) / "config.json"
|
|
write_config(path, cookie="cookie-a", mid=1)
|
|
store = ConfigStore(path, env={})
|
|
created = []
|
|
|
|
def factory(credentials):
|
|
created.append((credentials.mid, credentials.cookie))
|
|
return BilibiliClient(
|
|
cookie=credentials.cookie,
|
|
csrf=credentials.csrf,
|
|
mid=credentials.mid,
|
|
pacer=Pacer(1.0, 1.6),
|
|
)
|
|
|
|
provider = ClientProvider(store, "read", factory)
|
|
first = provider.get()
|
|
assert provider.get() is first
|
|
assert created == [(1, "cookie-a")]
|
|
|
|
write_config(path, cookie="cookie-b", mid=1)
|
|
bump(path)
|
|
second = provider.get()
|
|
assert second is not first
|
|
assert created == [(1, "cookie-a"), (1, "cookie-b")]
|
|
assert second.pacer.min_delay == 1.0
|
|
assert second.pacer.max_delay == 1.6
|
|
|
|
# 仅 mtime 变化但凭据相同:不重建
|
|
bump(path)
|
|
assert provider.get() is second
|
|
assert len(created) == 2
|
|
|
|
|
|
def test_mid_change_rebuilds():
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
path = Path(tmp) / "config.json"
|
|
write_config(path, cookie="cookie-a", mid=1)
|
|
store = ConfigStore(path, env={})
|
|
provider = ClientProvider(
|
|
store, "read",
|
|
lambda credentials: BilibiliClient(mid=credentials.mid),
|
|
)
|
|
assert provider.get().mid == 1
|
|
write_config(path, cookie="cookie-a", mid=9)
|
|
bump(path)
|
|
assert provider.get().mid == 9
|
|
|
|
|
|
def test_config_revision_increments():
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
path = Path(tmp) / "config.json"
|
|
write_config(path, cookie="cookie-a", mid=1)
|
|
store = ConfigStore(path, env={})
|
|
assert store.revision == 0
|
|
write_config(path, cookie="cookie-b", mid=1)
|
|
bump(path)
|
|
store.get()
|
|
assert store.revision == 1
|
|
store.get()
|
|
assert store.revision == 1
|
|
|
|
|
|
def main():
|
|
test_rebuild_only_when_credentials_change()
|
|
test_mid_change_rebuilds()
|
|
test_config_revision_increments()
|
|
print("OK: 凭据变化重建客户端与配置 revision 测试全部通过")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|