145 lines
4.8 KiB
Python
145 lines
4.8 KiB
Python
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")
|