generated from john/python-template
Revamped the Documents, People, & Jobs too.
This commit is contained in:
+106
-104
@@ -1,30 +1,62 @@
|
||||
"""Tests for the jobs page route."""
|
||||
|
||||
import asyncio
|
||||
from uuid import uuid4
|
||||
"""Tests for the jobs page routes and action handlers."""
|
||||
|
||||
import pytest
|
||||
from sqlmodel import select
|
||||
|
||||
from transcription.db import session_scope
|
||||
from transcription.db.models import Document
|
||||
from transcription.db.models import JobStatus
|
||||
from transcription.db.models import Document, Job, JobSourceStatus, JobStatus
|
||||
|
||||
|
||||
# --- Helper Fixtures ---
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def seed_document_with_unlinked_job():
|
||||
"""Seed a document and a queued job for testing route actions."""
|
||||
async with session_scope() as session:
|
||||
document = Document(name="Test Archival Letter", document_type="letter")
|
||||
session.add(document)
|
||||
await session.flush()
|
||||
|
||||
job = Job(
|
||||
document_id=document.id,
|
||||
status=JobStatus.QUEUED,
|
||||
provider="openai",
|
||||
model="gpt-4o",
|
||||
)
|
||||
session.add(job)
|
||||
await session.commit()
|
||||
return str(document.id), str(job.id)
|
||||
|
||||
|
||||
# --- Integration Tests for Jobs Route Handlers ---
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
class TestPageRendering:
|
||||
"""Verify jobs routes render correctly with real app wiring."""
|
||||
class TestJobsPageRendering:
|
||||
"""Verify jobs list, creation, detail, and lifecycle action routes."""
|
||||
|
||||
def test_jobs_page_renders_empty_state(self, app_client):
|
||||
"""GET /ui/jobs renders the page and empty-state text when no jobs exist."""
|
||||
_, client = app_client
|
||||
|
||||
response = client.get("/ui/jobs")
|
||||
|
||||
assert response.status_code == 200
|
||||
assert "Create job" in response.text
|
||||
assert "Transcription Pipeline Jobs" in response.text
|
||||
assert "No active or historical processing jobs found." in response.text
|
||||
|
||||
def test_job_create_page_requires_existing_documents(self, app_client):
|
||||
"""GET /ui/jobs/new shows guidance when no Documents exist."""
|
||||
def test_jobs_page_lists_seeded_jobs(self, app_client, seed_job):
|
||||
_, client = app_client
|
||||
job_id = seed_job(filename="seeded-document-page.png")
|
||||
|
||||
response = client.get("/ui/jobs")
|
||||
|
||||
assert response.status_code == 200
|
||||
assert str(job_id) in response.text
|
||||
assert "seeded-document-page.png" in response.text
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_job_create_page_shows_empty_document_warning_when_no_docs(self, app_client):
|
||||
_, client = app_client
|
||||
|
||||
response = client.get("/ui/jobs/new")
|
||||
@@ -32,132 +64,102 @@ class TestPageRendering:
|
||||
assert response.status_code == 200
|
||||
assert "Create Processing Job" in response.text
|
||||
assert "No documents available. Create a Document before creating a Job." in response.text
|
||||
assert "Create document" in response.text
|
||||
|
||||
def test_job_create_page_lists_available_documents(self, app_client):
|
||||
"""GET /ui/jobs/new renders document choices when Documents exist."""
|
||||
@pytest.mark.asyncio
|
||||
async def test_job_create_page_preselects_document_from_query_param(self, app_client):
|
||||
_, client = app_client
|
||||
|
||||
async def _seed_document() -> None:
|
||||
async with session_scope() as session:
|
||||
session.add(Document(name="Seeded Document"))
|
||||
await session.commit()
|
||||
async with session_scope() as session:
|
||||
doc = Document(name="Preselected Journal Entry", document_type="journal")
|
||||
session.add(doc)
|
||||
await session.commit()
|
||||
doc_id = str(doc.id)
|
||||
|
||||
asyncio.run(_seed_document())
|
||||
|
||||
response = client.get("/ui/jobs/new")
|
||||
response = client.get(f"/ui/jobs/new?document_id={doc_id}")
|
||||
|
||||
assert response.status_code == 200
|
||||
assert "Create Processing Job" in response.text
|
||||
assert "Seeded Document" in response.text
|
||||
assert "Files are processed alphabetically by original filename." in response.text
|
||||
assert "No files uploaded yet." in response.text
|
||||
assert "Select source files or a folder" in response.text
|
||||
assert "Preselected Journal Entry" in response.text
|
||||
|
||||
def test_jobs_page_lists_seeded_jobs(self, app_client, seed_job):
|
||||
"""GET /ui/jobs lists seeded jobs from the in-memory database."""
|
||||
@pytest.mark.asyncio
|
||||
async def test_job_detail_page_renders_logistics_and_links(
|
||||
self, app_client, seed_document_with_unlinked_job
|
||||
):
|
||||
_, client = app_client
|
||||
seed_job(filename="sample.pdf", status=JobStatus.TRANSCRIBED, transcription_text="done")
|
||||
|
||||
response = client.get("/ui/jobs")
|
||||
|
||||
assert response.status_code == 200
|
||||
assert "sample.pdf" in response.text
|
||||
assert "transcribed" in response.text
|
||||
|
||||
def test_job_detail_page_renders_document_links(self, app_client, seed_job):
|
||||
"""GET /ui/jobs/{job_id} renders document-scoped navigation links."""
|
||||
_, client = app_client
|
||||
job_id = seed_job(
|
||||
filename="detail.pdf",
|
||||
status=JobStatus.TRANSCRIBED,
|
||||
transcription_text="original text",
|
||||
)
|
||||
_, job_id = seed_document_with_unlinked_job
|
||||
|
||||
response = client.get(f"/ui/jobs/{job_id}")
|
||||
|
||||
assert response.status_code == 200
|
||||
assert "Job" in response.text
|
||||
assert "Provider:" in response.text
|
||||
assert "Model:" in response.text
|
||||
assert "Prompt:" in response.text
|
||||
assert "Retry Count:" in response.text
|
||||
assert "Last Updated:" in response.text
|
||||
assert "document links" in response.text.lower()
|
||||
assert "Sources" in response.text
|
||||
assert "Jobs" in response.text
|
||||
assert "Delete Job" in response.text
|
||||
assert f"Job Record: {job_id}" in response.text
|
||||
assert "Execution Logistics" in response.text
|
||||
assert "openai" in response.text
|
||||
assert "gpt-4o" in response.text
|
||||
assert "View Linked Document" in response.text
|
||||
assert "View Linked Sources" in response.text
|
||||
|
||||
def test_job_detail_page_rejects_invalid_id(self, app_client):
|
||||
"""GET /ui/jobs/{job_id} shows validation feedback for malformed IDs."""
|
||||
@pytest.mark.asyncio
|
||||
async def test_job_cancel_page_renders_confirmation(
|
||||
self, app_client, seed_document_with_unlinked_job
|
||||
):
|
||||
_, client = app_client
|
||||
response = client.get("/ui/jobs/not-a-uuid")
|
||||
|
||||
assert response.status_code == 200
|
||||
assert "Invalid job id" in response.text
|
||||
|
||||
def test_job_detail_page_handles_missing_job(self, app_client):
|
||||
"""GET /ui/jobs/{job_id} shows not-found state for unknown IDs."""
|
||||
_, client = app_client
|
||||
missing_id = uuid4()
|
||||
response = client.get(f"/ui/jobs/{missing_id}")
|
||||
|
||||
assert response.status_code == 200
|
||||
assert "Job not found" in response.text
|
||||
|
||||
def test_job_detail_page_shows_cancel_and_resubmit_when_queued(self, app_client, seed_job):
|
||||
"""GET /ui/jobs/{job_id} exposes cancel/resubmit controls for queued jobs."""
|
||||
_, client = app_client
|
||||
job_id = seed_job(
|
||||
filename="no-revision.pdf",
|
||||
status=JobStatus.QUEUED,
|
||||
transcription_text=None,
|
||||
)
|
||||
|
||||
response = client.get(f"/ui/jobs/{job_id}")
|
||||
|
||||
assert response.status_code == 200
|
||||
assert "Cancel" in response.text
|
||||
assert "Resubmit" in response.text
|
||||
assert "Delete Job" in response.text
|
||||
|
||||
def test_job_cancel_page_renders_confirmation(self, app_client, seed_job):
|
||||
_, client = app_client
|
||||
job_id = seed_job(filename="cancel-ready.pdf", status=JobStatus.PROCESSING)
|
||||
_, job_id = seed_document_with_unlinked_job
|
||||
|
||||
response = client.get(f"/ui/jobs/{job_id}/cancel")
|
||||
|
||||
assert response.status_code == 200
|
||||
assert "Cancel Processing Job" in response.text
|
||||
assert "Cancel stops processing" in response.text
|
||||
assert "Cancel job" in response.text
|
||||
|
||||
def test_job_resubmit_page_renders_confirmation(self, app_client, seed_job):
|
||||
@pytest.mark.asyncio
|
||||
async def test_job_resubmit_page_renders_counts(
|
||||
self, app_client, seed_job
|
||||
):
|
||||
_, client = app_client
|
||||
job_id = seed_job(filename="resubmit-ready.pdf", status=JobStatus.FAILED, transcription_text=None)
|
||||
job_id = seed_job(
|
||||
filename="failed-resubmit.png",
|
||||
status=JobStatus.FAILED,
|
||||
transcription_text=None,
|
||||
error_detail="Timeout",
|
||||
)
|
||||
|
||||
response = client.get(f"/ui/jobs/{job_id}/resubmit")
|
||||
|
||||
assert response.status_code == 200
|
||||
assert "Resubmit Job" in response.text
|
||||
assert "Non-Transcribed Sources:" in response.text
|
||||
assert "Resubmit now" in response.text
|
||||
|
||||
def test_job_delete_page_shows_confirmation_when_not_processing(self, app_client, seed_job):
|
||||
@pytest.mark.asyncio
|
||||
async def test_job_delete_page_blocks_deletion_when_processing(self, app_client):
|
||||
_, client = app_client
|
||||
job_id = seed_job(filename="delete-ready.pdf", status=JobStatus.TRANSCRIBED)
|
||||
|
||||
response = client.get(f"/ui/jobs/{job_id}/delete")
|
||||
|
||||
assert response.status_code == 200
|
||||
assert "Delete job" in response.text
|
||||
assert "This action permanently deletes the job." in response.text
|
||||
assert "Delete job permanently" in response.text
|
||||
|
||||
def test_job_delete_page_shows_blocked_state_when_processing(self, app_client, seed_job):
|
||||
_, client = app_client
|
||||
job_id = seed_job(filename="delete-blocked.pdf", status=JobStatus.PROCESSING)
|
||||
async with session_scope() as session:
|
||||
doc = Document(name="Processing 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)
|
||||
|
||||
response = client.get(f"/ui/jobs/{job_id}/delete")
|
||||
|
||||
assert response.status_code == 200
|
||||
assert "Delete Processing Job" in response.text
|
||||
assert "Delete is blocked while the job is processing." in response.text
|
||||
assert "Wait for processing to complete, then retry delete." in response.text
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_job_delete_page_allows_deletion_for_queued_or_completed_job(
|
||||
self, app_client, seed_document_with_unlinked_job
|
||||
):
|
||||
_, client = app_client
|
||||
_, job_id = seed_document_with_unlinked_job
|
||||
|
||||
response = client.get(f"/ui/jobs/{job_id}/delete")
|
||||
|
||||
assert response.status_code == 200
|
||||
assert "Delete Processing Job" in response.text
|
||||
assert "Delete job permanently" in response.text
|
||||
assert "Delete is blocked" not in response.text
|
||||
Reference in New Issue
Block a user