"""Unit tests for the worker dispatch table and job status transitions.""" from worker import HANDLERS, poll_jobs class FakeDoc: def __init__(self, **fields): self.__dict__.update(fields) def model_dump(self, **kwargs): return dict(self.__dict__) class FakeJobSvc: def __init__(self, jobs): self.jobs = {j.id: j for j in jobs} self.patches: list[tuple[int, dict]] = [] async def search(self, filter=None): filter = filter or {} return [j for j in self.jobs.values() if all(j.__dict__.get(k) == v for k, v in filter.items())] async def patch(self, id, body): self.patches.append((int(id), body)) job = self.jobs[int(id)] for k, v in body.items(): setattr(job, k, v) class FakeAppointmentSvc: def __init__(self, data): self._data = data async def read(self, id): return FakeDoc(**self._data) if self._data else None def _job(id=1, kind="appointment_reminder", payload=None): return FakeDoc( id=id, name="j", kind=kind, status="pending", payload=payload or {"appointment_id": 1}, retry_count=0, ) def test_dispatch_table_has_appointment_reminder(): assert "appointment_reminder" in HANDLERS async def test_poll_jobs_runs_pending_and_marks_done(monkeypatch): monkeypatch.setattr("worker.settings.worker_poll_seconds", 0) appointment = { "id": 1, "date": "2026-11-02T10:00:00", "pet": {"name": "Rex", "parents": [{"name": "Kim", "email": "kim@example.com"}]}, } job_svc = FakeJobSvc([_job()]) resources = {"job_svc": job_svc, "appointment_svc": FakeAppointmentSvc(appointment)} import asyncio task = asyncio.create_task(poll_jobs(resources)) for _ in range(50): await asyncio.sleep(0.01) if job_svc.patches and job_svc.patches[-1][1].get("status") == "done": break task.cancel() try: await task except asyncio.CancelledError: pass statuses = [body["status"] for _, body in job_svc.patches] assert statuses == ["processing", "done"] async def test_poll_jobs_marks_unknown_kind_failed(monkeypatch): monkeypatch.setattr("worker.settings.worker_poll_seconds", 0) job_svc = FakeJobSvc([_job(kind="nope")]) resources = {"job_svc": job_svc, "appointment_svc": FakeAppointmentSvc(None)} import asyncio task = asyncio.create_task(poll_jobs(resources)) for _ in range(50): await asyncio.sleep(0.01) if job_svc.patches and job_svc.patches[-1][1].get("status") == "failed": break task.cancel() try: await task except asyncio.CancelledError: pass last = job_svc.patches[-1][1] assert last["status"] == "failed" assert "no handler" in last["error_message"] assert last["retry_count"] == 1 async def test_poll_jobs_skips_retries_exhausted(monkeypatch): monkeypatch.setattr("worker.settings.worker_poll_seconds", 0) monkeypatch.setattr("worker.settings.worker_max_retries", 1) job = _job() job.retry_count = 1 job_svc = FakeJobSvc([job]) resources = {"job_svc": job_svc, "appointment_svc": FakeAppointmentSvc(None)} import asyncio task = asyncio.create_task(poll_jobs(resources)) await asyncio.sleep(0.05) task.cancel() try: await task except asyncio.CancelledError: pass assert job_svc.patches == []