feat(scheduler): add snapshot-seed CLI and 10m seed deferral
Add `python app.py snapshot-seed [--date]` (offline clone, Debian / docker compose exec friendly). When seed fires while full-site snapshot is running, or last_completed is missing (no_source), record skip as non-success and retry in 10 minutes until a real seed succeeds; fans and quiet-window rules unchanged.
This commit is contained in:
1 parent
9969e65202
commit
bb18d232f6
7 files changed
+374
-8
No files matched your search
+153
-1
@@ -332,6 +332,153 @@ def test_backfill_followers_dry_run():
|
||||
assert '"total": 0' in output
|
||||
|
||||
|
||||
|
||||
def test_help_lists_snapshot_seed():
|
||||
from reaction_board.cli import _build_parser
|
||||
|
||||
parser = _build_parser()
|
||||
help_text = parser.format_help()
|
||||
assert "snapshot-seed" in help_text
|
||||
args = parser.parse_args(["snapshot-seed", "--date", "2026-10-31"])
|
||||
assert args.command == "snapshot-seed"
|
||||
assert args.date == "2026-10-31"
|
||||
|
||||
|
||||
def test_snapshot_seed_cli_clones_offline():
|
||||
from reaction_board.bilibili.models import FavResource
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
path = write_config(tmp, cookie="", csrf="")
|
||||
code, _ = run_cli(["init", "--config", str(path)])
|
||||
assert code == 0
|
||||
db = Database(Path(tmp) / "app.db")
|
||||
db.initialize()
|
||||
try:
|
||||
repo = Repository(db)
|
||||
media_id = 11
|
||||
repo.upsert_from_fav(
|
||||
FavResource(
|
||||
aid=1, bvid="BV1xx411c7mD", title="t", up_mid=9,
|
||||
up_name="u", pubdate=1, duration=10, cover=None,
|
||||
attr=0, fav_time=None,
|
||||
),
|
||||
media_id,
|
||||
was_added=True,
|
||||
)
|
||||
yday, today = "2026-10-30", "2026-10-31"
|
||||
repo.upsert_snapshot(
|
||||
yday, 1,
|
||||
metrics={"view": 10, "like": 1, "coin": 0, "favorite": 0,
|
||||
"share": 0, "reply": 0, "danmaku": 0},
|
||||
ok=True, err=None, captured_at=1_800_000_000,
|
||||
)
|
||||
repo.kv_set("snapshot:last_completed", yday)
|
||||
repo.kv_set("snapshot:completed:" + yday, "1")
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
code, output = run_cli([
|
||||
"snapshot-seed", "--config", str(path), "--date", today,
|
||||
])
|
||||
assert code == 0, output
|
||||
assert '"reason": "seeded"' in output
|
||||
db = Database(Path(tmp) / "app.db")
|
||||
db.initialize()
|
||||
try:
|
||||
repo = Repository(db)
|
||||
assert repo.kv_get("snapshot:seeded:" + today) == "1"
|
||||
row = repo.get_snapshot(today, 1)
|
||||
assert row is not None and int(row["view"]) == 10
|
||||
assert row["src"] == "seed"
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
def test_snapshot_seed_defers_when_snapshot_running():
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
path = write_config(tmp, cookie="", csrf="")
|
||||
code, _ = run_cli(["init", "--config", str(path)])
|
||||
assert code == 0
|
||||
db = Database(Path(tmp) / "app.db")
|
||||
db.initialize()
|
||||
try:
|
||||
repo = Repository(db)
|
||||
repo.kv_set(
|
||||
"task:running:snapshot",
|
||||
json.dumps({"name": "snapshot", "started_at": 1}),
|
||||
)
|
||||
repo.kv_set("snapshot:last_completed", "2026-10-30")
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
code, output = run_cli([
|
||||
"snapshot-seed", "--config", str(path), "--date", "2026-10-31",
|
||||
])
|
||||
assert code == 1, output
|
||||
assert "snapshot_running" in output
|
||||
db = Database(Path(tmp) / "app.db")
|
||||
db.initialize()
|
||||
try:
|
||||
repo = Repository(db)
|
||||
last = json.loads(repo.kv_get("task:last:snapshot_seed"))
|
||||
assert last["ok"] is False
|
||||
assert last["skipped"] is True
|
||||
assert last["error"] == "snapshot_running"
|
||||
assert repo.kv_get("task:running:snapshot_seed") is None
|
||||
assert repo.kv_get("task:running:snapshot") is not None
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
def test_snapshot_seed_no_source_is_non_success():
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
path = write_config(tmp, cookie="", csrf="")
|
||||
code, _ = run_cli(["init", "--config", str(path)])
|
||||
assert code == 0
|
||||
code, output = run_cli([
|
||||
"snapshot-seed", "--config", str(path), "--date", "2026-10-31",
|
||||
])
|
||||
assert code == 1, output
|
||||
assert "no_source" in output
|
||||
db = Database(Path(tmp) / "app.db")
|
||||
db.initialize()
|
||||
try:
|
||||
repo = Repository(db)
|
||||
last = json.loads(repo.kv_get("task:last:snapshot_seed"))
|
||||
assert last["ok"] is False and last["skipped"] is True
|
||||
assert last["error"] == "no_source"
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
def test_defer_snapshot_seed_schedules_ten_minutes():
|
||||
"""调度辅助:skip 记非成功,next_run = finished + 600。"""
|
||||
from reaction_board.logging_json import JsonLogger
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
path = write_config(tmp, cookie="", csrf="")
|
||||
code, _ = run_cli(["init", "--config", str(path)])
|
||||
assert code == 0
|
||||
db = Database(Path(tmp) / "app.db")
|
||||
db.initialize()
|
||||
try:
|
||||
repo = Repository(db)
|
||||
logger = JsonLogger()
|
||||
started = 1_800_000_000
|
||||
finished = started + 5
|
||||
ctrl = cli._defer_snapshot_seed(
|
||||
repo, logger, started_at=started, reason="snapshot_running",
|
||||
finished_at=finished,
|
||||
)
|
||||
assert ctrl.ok is False
|
||||
assert ctrl.error == "snapshot_running"
|
||||
assert abs(ctrl.next_run - (finished + cli._SEED_RETRY_SECONDS)) < 0.01
|
||||
last = json.loads(repo.kv_get("task:last:snapshot_seed"))
|
||||
assert last["skipped"] is True and last["ok"] is False
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
def main_test():
|
||||
test_init_and_status()
|
||||
test_missing_config()
|
||||
@@ -345,7 +492,12 @@ def main_test():
|
||||
test_snapshot_backup_failure_returns_error_and_retries()
|
||||
test_candidates_and_discover_commands()
|
||||
test_backfill_followers_dry_run()
|
||||
print("OK: CLI init/sync/snapshot/auth-check/run/status/backfill/错误路径测试全部通过")
|
||||
test_help_lists_snapshot_seed()
|
||||
test_snapshot_seed_cli_clones_offline()
|
||||
test_snapshot_seed_defers_when_snapshot_running()
|
||||
test_snapshot_seed_no_source_is_non_success()
|
||||
test_defer_snapshot_seed_schedules_ten_minutes()
|
||||
print("OK: CLI init/sync/snapshot/snapshot-seed/auth-check/run/status/backfill/错误路径测试全部通过")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in new issue
Block a user