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.
This commit is contained in:
1 parent
178ab64b18
commit
6a21b5d905
21 files changed
+895
-82
No files matched your search
@@ -0,0 +1,100 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""favsort run 参数校验与资源类型测试(不联网): uv run python tests/test_favsort_run.py"""
|
||||
|
||||
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")
|
||||
|
||||
import favsort # noqa: E402
|
||||
|
||||
|
||||
class Args:
|
||||
media_id = 1
|
||||
verify_every = 1
|
||||
|
||||
|
||||
def config():
|
||||
return {
|
||||
"mid": 1,
|
||||
"csrf": "csrf",
|
||||
"cookie": "cookie",
|
||||
"overrides": {},
|
||||
"sleep": {
|
||||
"min": 0, "max": 0,
|
||||
"long_every_min": 1000, "long_every_max": 1000,
|
||||
"long_min": 0, "long_max": 0,
|
||||
"retry_wait": 0,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def test_zero_verify_every_rejected_before_writes():
|
||||
calls = []
|
||||
originals = (favsort.load_plan, favsort.fetch_ids, favsort.post_form)
|
||||
favsort.load_plan = lambda media_id: {"target_order": [], "titles": {}}
|
||||
favsort.fetch_ids = lambda cfg, media_id: calls.append("fetch") or []
|
||||
favsort.post_form = lambda *args, **kwargs: calls.append("post") or {}
|
||||
args = Args()
|
||||
args.verify_every = 0
|
||||
try:
|
||||
code = favsort.cmd_run(config(), args)
|
||||
finally:
|
||||
favsort.load_plan, favsort.fetch_ids, favsort.post_form = originals
|
||||
assert code == 2
|
||||
assert calls == []
|
||||
|
||||
|
||||
def test_sort_preserves_resource_types():
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
plan = {
|
||||
"media_id": 1,
|
||||
"target_order": [1, 2],
|
||||
"titles": {"1": {"title": "音频"}, "2": {"title": "视频"}},
|
||||
"types": {1: 2, 2: 12},
|
||||
}
|
||||
bodies = []
|
||||
orders = [[2, 1]]
|
||||
originals = (
|
||||
favsort.load_plan,
|
||||
favsort.fetch_ids,
|
||||
favsort.post_form,
|
||||
favsort.plan_path,
|
||||
favsort.journal_path,
|
||||
)
|
||||
favsort.load_plan = lambda media_id: plan
|
||||
favsort.plan_path = lambda media_id: str(Path(tmp) / "plan.json")
|
||||
favsort.journal_path = lambda media_id: str(Path(tmp) / "journal.jsonl")
|
||||
|
||||
def fetch_ids(cfg, media_id):
|
||||
order = orders.pop(0) if orders else [1, 2]
|
||||
return [{"id": aid} for aid in order]
|
||||
|
||||
favsort.fetch_ids = fetch_ids
|
||||
favsort.post_form = lambda cfg, url, body: bodies.append(body) or {"code": 0}
|
||||
args = Args()
|
||||
try:
|
||||
code = favsort.cmd_run(config(), args)
|
||||
finally:
|
||||
(
|
||||
favsort.load_plan,
|
||||
favsort.fetch_ids,
|
||||
favsort.post_form,
|
||||
favsort.plan_path,
|
||||
favsort.journal_path,
|
||||
) = originals
|
||||
assert code == 0
|
||||
assert [body["sort"] for body in bodies] == ["1:2:2:12"]
|
||||
|
||||
|
||||
def main():
|
||||
test_zero_verify_every_rejected_before_writes()
|
||||
test_sort_preserves_resource_types()
|
||||
print("OK: favsort run 参数校验与资源类型测试全部通过")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in new issue
Block a user