fix(scheduler): address !7 REQUEST_CHANGES
- seed: check completed_key before no_source so same-day last_completed is already_completed (not 10m retry) - scheduler: resolve TaskControl ok/error before logging; ok=False logs task_fail, not task_ok - candidate_cleanup: catch failures, record task, retry ~5m like discover
This commit is contained in:
1 parent
bb18d232f6
commit
1697573d95
7 files changed
+264
-17
No files matched your search
@@ -479,6 +479,115 @@ def test_defer_snapshot_seed_schedules_ten_minutes():
|
||||
db.close()
|
||||
|
||||
|
||||
|
||||
|
||||
def test_snapshot_seed_same_day_completed_succeeds():
|
||||
"""当日已完成时手动 snapshot-seed 应成功 already_completed,不走 no_source。"""
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
path = write_config(tmp, cookie="", csrf="")
|
||||
code, _ = run_cli(["init", "--config", str(path)])
|
||||
assert code == 0
|
||||
today = "2026-10-31"
|
||||
db = Database(Path(tmp) / "app.db")
|
||||
db.initialize()
|
||||
try:
|
||||
repo = Repository(db)
|
||||
repo.kv_set("snapshot:last_completed", today)
|
||||
repo.kv_set("snapshot:completed:" + today, "1")
|
||||
finally:
|
||||
db.close()
|
||||
code, output = run_cli([
|
||||
"snapshot-seed", "--config", str(path), "--date", today,
|
||||
])
|
||||
assert code == 0, output
|
||||
assert '"reason": "already_completed"' in output
|
||||
assert "no_source" not 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 True
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
def test_candidate_cleanup_failure_retries_soon():
|
||||
"""candidate_cleanup 异常应记失败任务并约 5 分钟补跑,而非落到 86400 间隔。"""
|
||||
class FakeClient:
|
||||
def check_login(self):
|
||||
return LoginState(True, 1, "tester", 0, "OK")
|
||||
|
||||
class FakeDiscovery:
|
||||
def __init__(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def run(self):
|
||||
from reaction_board.services.discovery import DiscoverySummary
|
||||
return DiscoverySummary(started_at=1, finished_at=1)
|
||||
|
||||
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_client = cli._client
|
||||
original_discovery = cli.DiscoveryService
|
||||
original_delete = Repository.delete_expired_candidates
|
||||
captured = {}
|
||||
|
||||
def boom(self, *args, **kwargs):
|
||||
raise RuntimeError("cleanup db boom")
|
||||
|
||||
def fake_client(credentials, *args, **kwargs):
|
||||
return FakeClient()
|
||||
|
||||
# 只跑 cleanup:把其它任务 next_run 推远;通过 Scheduler 包装拦截。
|
||||
original_scheduler = cli.Scheduler
|
||||
|
||||
class CaptureScheduler(original_scheduler):
|
||||
def __init__(self, tasks, **kwargs):
|
||||
far = 1_000_000_000_000.0
|
||||
for task in tasks:
|
||||
if task.name != "candidate_cleanup":
|
||||
task.next_run = far
|
||||
else:
|
||||
task.next_run = 0.0
|
||||
super().__init__(tasks, **kwargs)
|
||||
captured["scheduler"] = self
|
||||
|
||||
cli._client = fake_client
|
||||
cli.DiscoveryService = FakeDiscovery
|
||||
cli.Scheduler = CaptureScheduler
|
||||
Repository.delete_expired_candidates = boom
|
||||
try:
|
||||
code, output = run_cli(["run", "--once", "--config", str(path)])
|
||||
finally:
|
||||
cli._client = original_client
|
||||
cli.DiscoveryService = original_discovery
|
||||
cli.Scheduler = original_scheduler
|
||||
Repository.delete_expired_candidates = original_delete
|
||||
|
||||
assert code == 0, output
|
||||
db = Database(Path(tmp) / "app.db")
|
||||
db.initialize()
|
||||
try:
|
||||
repo = Repository(db)
|
||||
last = json.loads(repo.kv_get("task:last:candidate_cleanup"))
|
||||
assert last["ok"] is False
|
||||
assert "cleanup db boom" in last["error"]
|
||||
finally:
|
||||
db.close()
|
||||
scheduler = captured["scheduler"]
|
||||
cleanup_task = next(t for t in scheduler.tasks if t.name == "candidate_cleanup")
|
||||
# next_run ≈ finished + 300;允许几秒时钟误差
|
||||
assert cleanup_task.next_run > 0
|
||||
delta = cleanup_task.next_run - __import__("time").time()
|
||||
assert 250 <= delta <= 360, delta
|
||||
|
||||
|
||||
def main_test():
|
||||
test_init_and_status()
|
||||
test_missing_config()
|
||||
@@ -496,7 +605,9 @@ def main_test():
|
||||
test_snapshot_seed_cli_clones_offline()
|
||||
test_snapshot_seed_defers_when_snapshot_running()
|
||||
test_snapshot_seed_no_source_is_non_success()
|
||||
test_snapshot_seed_same_day_completed_succeeds()
|
||||
test_defer_snapshot_seed_schedules_ten_minutes()
|
||||
test_candidate_cleanup_failure_retries_soon()
|
||||
print("OK: CLI init/sync/snapshot/snapshot-seed/auth-check/run/status/backfill/错误路径测试全部通过")
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user