import time from server import jobs def _wait(job_id, status, timeout=2.0): end = time.time() + timeout while time.time() < end: j = jobs.get_job(job_id) if j and j.status == status: return j time.sleep(0.01) raise AssertionError(f"job {job_id} never reached {status}") def test_create_job_is_queued(): job = jobs.create_job(hit={"artist": "A"}, message="queued msg") assert job.status == "queued" assert job.hit == {"artist": "A"} assert jobs.get_job(job.id) is job def test_run_job_success_sets_done(): job = jobs.create_job(hit={}, message="m") jobs.run_job(job.id, lambda: {"path": "/x", "lidarr_album_id": None}, done_message="done!") j = _wait(job.id, "done") assert j.result == {"path": "/x", "lidarr_album_id": None} assert j.message == "done!" assert j.error is None def test_run_job_failure_sets_failed(): job = jobs.create_job(hit={}, message="m") def boom(): raise RuntimeError("kaboom") jobs.run_job(job.id, boom, done_message="done!", fail_message="it broke") j = _wait(job.id, "failed") assert j.error and "kaboom" in j.error assert j.message == "it broke" def test_get_unknown_job_returns_none(): assert jobs.get_job("nope") is None def test_eviction_keeps_within_cap(): jobs.JOBS.clear() for i in range(jobs._MAX_JOBS + 25): jobs.create_job(hit={"i": i}, message="m") assert len(jobs.JOBS) <= jobs._MAX_JOBS jobs.JOBS.clear() def test_run_job_callable_done_message(): job = jobs.create_job(hit={}, message="m") jobs.run_job(job.id, lambda: {"ok": 2, "total": 3}, done_message=lambda res: f"{res['ok']}/{res['total']} done") j = _wait(job.id, "done") assert j.message == "2/3 done" def teardown_module(): jobs.JOBS.clear() # --- choice jobs ----------------------------------------------------------- def _choices_result(): return {"artist": "Overgrown", "album": "Lay Cold", "choices": [{"n": 1, "label": "FLAC"}, {"n": 2, "label": "MP3"}], "pending": [{"guid": "a"}, {"guid": "b"}]} def test_run_choice_job_parks_the_job(): job = jobs.create_job(hit=None, message="m") jobs.run_choice_job(job.id, _choices_result, choice_message=lambda res: "Which one?", done_message="done!") j = _wait(job.id, "awaiting_choice") assert j.message == "Which one?" assert j.choices == [{"n": 1, "label": "FLAC"}, {"n": 2, "label": "MP3"}] assert j.expires_at is not None # The raw indexer data stays server-side only. assert j.pending == [{"guid": "a"}, {"guid": "b"}] assert "pending" not in j.result assert "choices" not in j.result def test_run_choice_job_finishes_when_there_is_nothing_to_choose(): job = jobs.create_job(hit=None, message="m") jobs.run_choice_job(job.id, lambda: {"outcome": "no_release", "choices": [], "pending": []}, choice_message=lambda res: "never asked", done_message=lambda res: f"nothing: {res['outcome']}") j = _wait(job.id, "done") assert j.message == "nothing: no_release" assert j.choices is None def test_run_choice_job_records_a_failure_with_the_real_reason(): def boom(): raise RuntimeError("Lidarr said no") job = jobs.create_job(hit=None, message="m") jobs.run_choice_job(job.id, boom, choice_message=lambda res: "x", done_message="done!", fail_message=lambda e: str(e)) j = _wait(job.id, "failed") assert j.message == "Lidarr said no" assert "RuntimeError" in j.error def test_take_choice_can_only_be_used_once(): job = jobs.create_job(hit=None, message="m") jobs.run_choice_job(job.id, _choices_result, choice_message=lambda res: "?", done_message="done!") _wait(job.id, "awaiting_choice") _, item = jobs.take_choice(job.id, 2) assert item == {"guid": "b"} assert jobs.get_job(job.id).status == "queued" try: jobs.take_choice(job.id, 1) except jobs.ChoiceError as e: assert "not waiting for a choice" in str(e) else: raise AssertionError("a second take_choice must be refused") def test_take_choice_rejects_a_number_out_of_range(): job = jobs.create_job(hit=None, message="m") jobs.run_choice_job(job.id, _choices_result, choice_message=lambda res: "?", done_message="done!") _wait(job.id, "awaiting_choice") try: jobs.take_choice(job.id, 3) except jobs.ChoiceError as e: assert "between 1 and 2" in str(e) else: raise AssertionError("out-of-range must be refused") def test_take_choice_on_an_unknown_job_raises_key_error(): try: jobs.take_choice("nope", 1) except KeyError: pass else: raise AssertionError("unknown job must raise KeyError")