"""Action handler tests for Job CRUD mutations.""" from pathlib import Path import pytest from sqlmodel import select from transcription.db import session_scope from transcription.db.models import Document, Job, JobSource, JobSourceStatus, JobStatus, Source @pytest.mark.integration class TestJobsActionHandlers: """Verify POST/mutation routes for Job creation, status changes, and deletions.""" @pytest.mark.asyncio async def test_create_job_success(self, app_client): _, client = app_client async with session_scope() as session: doc = Document(name="Postcard Batch", document_type="postcard") session.add(doc) await session.commit() doc_id = str(doc.id) fixture_path = ( Path(__file__).resolve().parents[1] / "fixtures" / "images" / "valid" / "small_png.png" ) with open(fixture_path, "rb") as file_bytes: files = [("files", ("001_postcard.png", file_bytes, "image/png"))] data = { "document_id": doc_id, "provider": "openai", "model": "gpt-4o", "prompt_name": "default_transcription", } assert response.status_code == 200 assert "Job Record:" in response.text or "Execution Logistics" in response.text async with session_scope() as session: job = ( await session.exec(select(Job).where(Job.document_id == doc_id)) ).first() assert job is not None assert job.status == JobStatus.QUEUED assert job.provider == "openai" assert job.model == "gpt-4o" @pytest.mark.asyncio async def test_cancel_queued_job_success(self, app_client, seed_job): _, client = app_client job_id = await seed_job(status=JobStatus.QUEUED, filename="queued-job.png") assert response.status_code == 200 assert "Job cancelled" in response.text or "CANCELLED" in response.text or "FAILED" in response.text async with session_scope() as session: cancelled_job = await session.get(Job, job_id) assert cancelled_job is not None assert cancelled_job.status in {JobStatus.FAILED, JobStatus.COMPLETED} @pytest.mark.asyncio async def test_resubmit_failed_sources_success(self, app_client, seed_job): _, client = app_client job_id = await seed_job( filename="failed-page.png", status=JobStatus.FAILED, transcription_text=None, error_detail="Provider API timeout", ) assert response.status_code == 200 assert "Resubmitted" in response.text or "QUEUED" in response.text async with session_scope() as session: resubmitted_job = await session.get(Job, job_id) assert resubmitted_job is not None assert resubmitted_job.status == JobStatus.QUEUED job_source = ( await session.exec(select(JobSource).where(JobSource.job_id == job_id)) ).first() assert job_source is not None assert job_source.status == JobSourceStatus.PENDING @pytest.mark.asyncio async def test_delete_queued_or_completed_job_success(self, app_client, seed_job): _, client = app_client job_id = await seed_job(status=JobStatus.COMPLETED, filename="completed-job.png") assert response.status_code == 200 assert "Job deleted" in response.text or "Transcription Pipeline Jobs" in response.text async with session_scope() as session: deleted_job = await session.get(Job, job_id) assert deleted_job is None @pytest.mark.asyncio async def test_delete_job_blocked_when_processing(self, app_client): _, client = app_client async with session_scope() as session: doc = Document(name="Active Doc", document_type="letter") session.add(doc) await session.flush() job = Job(document_id=doc.id, status=JobStatus.PROCESSING) session.add(job) await session.commit() job_id = str(job.id) assert response.status_code == 200 assert "Delete is blocked while the job is processing." in response.text async with session_scope() as session: job_still_exists = await session.get(Job, job_id) assert job_still_exists is not None