Files
re-xxm/tests/test_cli.py
T
kairee 6a21b5d905 fix: close data integrity and hot-reload review findings
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.
2026-10-31 20:58:36 +08:00

226 lines
7.6 KiB
Python

# -*- coding: utf-8 -*-
"""CLI 入口测试(离线): uv run python tests/test_cli.py"""
import contextlib
import io
import json
import os
import sys
import tempfile
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.models import LoginState # noqa: E402
from reaction_board.cli import main # noqa: E402
from reaction_board.database import Database # noqa: E402
from reaction_board.repository import Repository # noqa: E402
import reaction_board.cli as cli # noqa: E402
def write_config(tmp, *, cookie="SESSDATA=x; buvid3=y", csrf="csrf"):
path = Path(tmp) / "config.json"
path.write_text(json.dumps({
"bilibili": {
"read": {"mid": 1, "csrf": csrf, "cookie": cookie},
"write": {"mid": 2, "csrf": "write-csrf", "cookie": "write-cookie"},
},
"storage": {
"database_path": str(Path(tmp) / "app.db"),
"backup_dir": str(Path(tmp) / "backup"),
},
"folders": [
{"year": 2025, "media_id": 11, "title": "2025"},
{"year": 2026, "media_id": 22, "title": "2026"},
],
"up_blacklist_seed": [101, 202],
}, ensure_ascii=False), encoding="utf-8")
return path
def run_cli(argv):
out = io.StringIO()
with contextlib.redirect_stdout(out):
code = main(argv)
return code, out.getvalue()
def test_init_and_status():
with tempfile.TemporaryDirectory() as tmp:
path = write_config(tmp)
code, output = run_cli(["init", "--config", str(path)])
assert code == 0
assert '"schema_version": 2' in output
db = Database(Path(tmp) / "app.db")
db.initialize()
try:
repo = Repository(db)
assert len(repo.list_folders()) == 2
assert [r["mid"] for r in repo.blacklist()] == [101, 202]
assert repo.counts()["video_total"] == 0
finally:
db.close()
code, output = run_cli(["status", "--config", str(path)])
assert code == 0
assert '"configured_folders"' in output
assert '"media_count": null' in output
assert '"video_total": 0' in output
def test_missing_config():
with tempfile.TemporaryDirectory() as tmp:
code, output = run_cli(["init", "--config", str(Path(tmp) / "nope.json")])
assert code == 2
payload = json.loads(output.strip().splitlines()[-1])
assert payload["level"] == "error"
assert "缺少配置文件" in payload["error"]
def test_sync_requires_auth_without_network():
with tempfile.TemporaryDirectory() as tmp:
path = write_config(tmp, cookie="", csrf="")
code, output = run_cli(["sync", "--config", str(path)])
assert code == 2
payload = json.loads(output.strip().splitlines()[-1])
assert "cookie" in payload["error"]
assert not (Path(tmp) / "app.db").exists()
def test_snapshot_offline_empty_db():
with tempfile.TemporaryDirectory() as tmp:
path = write_config(tmp, cookie="", csrf="")
data = json.loads(path.read_text(encoding="utf-8"))
data["folders"] = []
data["track_fans"] = []
path.write_text(json.dumps(data, ensure_ascii=False), encoding="utf-8")
code, output = run_cli(["snapshot", "--config", str(path)])
assert code == 0
assert '"completed": true' in output
assert '"total": 0' in output
assert len(list((Path(tmp) / "backup").glob("app-*.db"))) == 1
def test_auth_check_without_cookie():
with tempfile.TemporaryDirectory() as tmp:
path = write_config(tmp, cookie="", csrf="")
code, output = run_cli(["auth-check", "--config", str(path)])
assert code == 1
payload = json.loads(output)
assert payload["is_login"] is False
assert "未配置 cookie" in payload["message"]
def test_auth_check_all_and_auth_dispatch():
with tempfile.TemporaryDirectory() as tmp:
path = write_config(tmp, cookie="", csrf="")
code, output = run_cli(["auth-check", "--account", "all", "--config", str(path)])
assert code == 1
payload = json.loads(output)
assert payload["ok"] is False
assert [item["account"] for item in payload["accounts"]] == ["read", "write"]
original = cli.AuthService
calls = []
class FakeAuthService:
def __init__(self, path, *, account, logger):
calls.append((str(path), account))
def run(self, *, timeout, no_browser):
assert timeout == 9 and no_browser is True
return 7
cli.AuthService = FakeAuthService
try:
code, _ = run_cli([
"auth", "--account", "write", "--timeout", "9", "--no-browser", "--config", str(path),
])
finally:
cli.AuthService = original
assert code == 7
assert calls == [(str(path), "write")]
def test_run_once_offline():
class FakeClient:
def check_login(self):
return LoginState(True, 1, "tester", 0, "OK")
with tempfile.TemporaryDirectory() as tmp:
path = write_config(tmp)
data = json.loads(path.read_text(encoding="utf-8"))
data["folders"] = []
data["track_fans"] = []
path.write_text(json.dumps(data, ensure_ascii=False), encoding="utf-8")
original = cli._client
selected = []
def fake_client(credentials, *args, **kwargs):
selected.append(credentials)
return FakeClient()
cli._client = fake_client
try:
code, output = run_cli(["run", "--once", "--config", str(path)])
finally:
cli._client = original
assert code == 0
assert '"name": "folder_sync"' in output
assert '"name": "snapshot"' in output
assert selected and all(credentials.mid == 1 for credentials in selected)
def test_snapshot_backup_failure_returns_error_and_retries():
import reaction_board.services.snapshot as snapshot_module
with tempfile.TemporaryDirectory() as tmp:
path = write_config(tmp, cookie="", csrf="")
data = json.loads(path.read_text(encoding="utf-8"))
data["folders"] = []
data["track_fans"] = []
path.write_text(json.dumps(data, ensure_ascii=False), encoding="utf-8")
original = snapshot_module.backup_daily
def fail_backup(*args, **kwargs):
raise OSError("disk full")
snapshot_module.backup_daily = fail_backup
try:
code, output = run_cli(["snapshot", "--config", str(path)])
finally:
snapshot_module.backup_daily = original
assert code == 1
marker = output.find("{\n")
assert marker >= 0, output
payload = json.loads(output[marker:])
assert payload["completed"] is True
assert "disk full" in payload["backup_error"]
code, output = run_cli(["snapshot", "--config", str(path)])
assert code == 0
marker = output.find("{\n")
payload = json.loads(output[marker:])
assert payload["backup_error"] is None
assert Path(payload["backup_path"]).exists()
def main_test():
test_init_and_status()
test_missing_config()
test_sync_requires_auth_without_network()
test_snapshot_offline_empty_db()
test_auth_check_without_cookie()
test_auth_check_all_and_auth_dispatch()
test_run_once_offline()
test_snapshot_backup_failure_returns_error_and_retries()
print("OK: CLI init/sync/snapshot/auth-check/run/status/错误路径测试全部通过")
if __name__ == "__main__":
main_test()