feat(p0): add favorite folder sync and sqlite data foundation

Implement P0 of the reaction dashboard: config loading with hot reload, SQLite migrations with backups, single-writer queue, read-only Bilibili fav client, folder import/diff sync, invalid-resource handling, blacklist seed, scheduler, and JSON redacted logging. Includes offline unit and transport-level contract tests; all existing and new tests pass.
This commit is contained in:
kairee committed 2026-10-31 18:08:33 +08:00
commit e2cdf997af
39 files changed
+8575

No files matched your search

+57
View File
@@ -0,0 +1,57 @@
# -*- coding: utf-8 -*-
"""build_moves / apply_move 的正确性自测(无需 pytest): python tests/test_moves.py"""
import os
import random
import sys
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
sys.stdout.reconfigure(encoding="utf-8")
from favsort import apply_move, build_moves, lcs_keep # noqa: E402
def run_case(cur, tgt, label):
moves = build_moves(cur, tgt)
work = list(cur)
for m in moves:
assert m["pred"] is not None, f"{label}: pred 为空"
work = apply_move(work, m)
assert work == tgt, f"{label}: 结果不符\n cur={cur}\n tgt={tgt}\n got={work}"
lower = len(lcs_keep(cur, tgt))
# 不追求严格最优(多轮 LCS 会略多于下界),但不能出现病态膨胀
assert len(moves) <= len(cur) + 8, (
f"{label}: 步数异常 {len(moves)} (n={len(cur)}, 下界={len(cur) - lower})"
)
return len(moves)
def main():
random.seed(20261031)
total = 0
for n in range(1, 13):
ids = [1000 + i for i in range(n)]
for _ in range(200):
cur = ids[:]
tgt = ids[:]
random.shuffle(cur)
random.shuffle(tgt)
run_case(cur, tgt, f"n={n}")
total += 1
# 头部必须换人的用例
cases = [
([3, 2, 1], [1, 2, 3]),
([5, 4, 3, 2, 1], [1, 2, 3, 4, 5]),
([2, 3, 1], [1, 2, 3]),
([4, 1, 2, 3], [1, 2, 3, 4]),
]
for cur, tgt in cases:
run_case(cur, tgt, f"head {cur}->{tgt}")
# 已排序不应产生移动
assert build_moves([1, 2, 3], [1, 2, 3]) == []
print(f"OK: {total} 个随机用例 + {len(cases)} 个头部用例全部通过")
if __name__ == "__main__":
main()