"""Phase 3 stubs: job models and queue gating.""" from __future__ import annotations import json from app.jobs.models import GenerationJob, JobType def test_generation_job_roundtrip() -> None: job = GenerationJob( job_type=JobType.generate, report_id="r1", tenant_id="t1", payload={"template_id": "E1", "bullets": ["note"]}, ) raw = job.model_dump_json() back = GenerationJob.model_validate_json(raw) assert back.job_type == JobType.generate assert json.loads(raw)["report_id"] == "r1" async def test_dispatch_or_enqueue_falls_back_on_redis_error(monkeypatch) -> None: import asyncio from app.jobs.models import GenerationJob, JobType from app.jobs import queue as jq monkeypatch.setattr("app.jobs.queue.settings.enable_job_queue", True) monkeypatch.setattr("app.jobs.queue.settings.redis_url", "redis://localhost:6379/0") async def _boom(_job: GenerationJob) -> None: raise ConnectionError("redis down") monkeypatch.setattr(jq, "enqueue_generation_job", _boom) ran: list[str] = [] spawned: list[asyncio.Task] = [] async def _inline() -> None: ran.append("yes") def _capture_task(coro) -> asyncio.Task: # type: ignore[no-untyped-def] t = asyncio.create_task(coro) spawned.append(t) return t monkeypatch.setattr("app.api.background_tasks.spawn_background_task", _capture_task) mode = await jq.dispatch_or_enqueue( job=GenerationJob( job_type=JobType.generate, report_id="r1", tenant_id="t1", payload={}, ), inline_factory=_inline, ) if spawned: await asyncio.gather(*spawned) assert mode == "inline" assert ran == ["yes"] def test_job_queue_active_requires_redis(monkeypatch) -> None: from app.jobs import queue as jq monkeypatch.setattr("app.jobs.queue.settings.enable_job_queue", True) monkeypatch.setattr("app.jobs.queue.settings.redis_url", "") assert jq.job_queue_active() is False monkeypatch.setattr("app.jobs.queue.settings.redis_url", "redis://localhost:6379/0") assert jq.job_queue_active() is True