import threading from dashboard.jobs import JobRegistry def test_job_registry_atomically_reserves_only_one_job_per_kind(tmp_path): registry = JobRegistry(tmp_path / "jobs.json") barrier = threading.Barrier(10) results = [] def reserve(): barrier.wait() results.append(registry.reserve("refresh", details={"full": False})) threads = [threading.Thread(target=reserve) for _ in range(10)] for thread in threads: thread.start() for thread in threads: thread.join() reserved = [job for job in results if job is not None] assert len(reserved) == 1 assert reserved[0]["id"] assert reserved[0]["status"] == "queued" assert registry.active("refresh")["id"] == reserved[0]["id"] def test_job_registry_tracks_completion_and_result(tmp_path): registry = JobRegistry(tmp_path / "jobs.json") job = registry.reserve("history") result = registry.run(job["id"], lambda: {"records": 42}) assert result == {"records": 42} saved = registry.get(job["id"]) assert saved["status"] == "complete" assert saved["result"] == {"records": 42} assert saved["started_at"] assert saved["finished_at"] assert registry.active("history") is None def test_job_registry_marks_abandoned_active_jobs_interrupted_on_restart(tmp_path): path = tmp_path / "jobs.json" first = JobRegistry(path) job = first.reserve("refresh") restarted = JobRegistry(path) recovered = restarted.get(job["id"]) assert recovered["status"] == "interrupted" assert recovered["finished_at"] assert restarted.active("refresh") is None