Revamped the Documents, People, & Jobs too.

This commit is contained in:
Jim Lancaster
2026-08-05 13:05:41 -05:00
parent 72bc96ab3a
commit fd3ca60008
15 changed files with 1719 additions and 1338 deletions
+106 -104
View File
@@ -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