"""Tests for the documents page routes.""" import asyncio from datetime import UTC from datetime import date from datetime import datetime from uuid import uuid4 import pytest from transcription.db import session_scope from transcription.db.models import Document from transcription.db.models import DocumentPerson from transcription.db.models import DocumentPersonRole from transcription.db.models import Job from transcription.db.models import Person from transcription.db.models import Source @pytest.mark.integration class TestDocumentsPageRendering: """Verify document list/detail routes render expected read states.""" def test_documents_page_renders_empty_state(self, app_client): """GET /ui/documents renders empty-state text when no records exist.""" _, client = app_client response = client.get("/ui/documents") assert response.status_code == 200 assert "Documents" in response.text assert "Create new document" in response.text assert "No documents yet." in response.text assert "Create your first document" in response.text def test_document_create_page_renders_fields(self, app_client): """GET /ui/documents/new renders document-create form fields.""" _, client = app_client response = client.get("/ui/documents/new") assert response.status_code == 200 assert "Create document" in response.text assert "Document name is required." in response.text assert "Document name" in response.text assert "Document type" in response.text assert "Author (Person)" in response.text assert "Exact date (YYYY-MM-DD)" in response.text assert "Approximate date" in response.text assert "Document location" in response.text assert "Archive identifier" in response.text assert "Notes" in response.text assert "Save document" in response.text def test_documents_page_lists_seeded_documents(self, app_client): """GET /ui/documents lists seeded document cards.""" _, client = app_client async def _seed_document() -> None: async with session_scope() as session: session.add(Document(name="Seeded Document", document_type="letter")) await session.commit() asyncio.run(_seed_document()) response = client.get("/ui/documents") assert response.status_code == 200 assert "Seeded Document" in response.text assert "Type: letter" in response.text def test_document_detail_page_renders_metadata_and_empty_related_sections(self, app_client): """GET /ui/documents/{document_id} shows metadata and related empty states.""" _, client = app_client async def _seed_document() -> str: async with session_scope() as session: document = Document( name="Zenna Letter", document_type="letter", document_date=date(1885, 7, 13), document_date_raw="c. 1885", location_created="Ohio", notes="Family archive", archive_identifier="BOX-1-FOLDER-2", ) session.add(document) await session.commit() await session.refresh(document) return str(document.id) document_id = asyncio.run(_seed_document()) response = client.get(f"/ui/documents/{document_id}") assert response.status_code == 200 assert "Zenna Letter" in response.text assert "Document type: letter" in response.text assert "Author: not set" in response.text assert "Exact date: 1885-07-13" in response.text assert "Approximate date: c. 1885" in response.text assert "Location created: Ohio" in response.text assert "Archive identifier: BOX-1-FOLDER-2" in response.text assert "Notes: Family archive" in response.text assert "Created at (read-only):" in response.text assert "Updated at (read-only):" in response.text assert "No linked people yet." in response.text assert "0 source(s) linked" in response.text assert "0 job(s) linked" in response.text assert "Add sources" in response.text assert "Create job" in response.text assert "Sources" in response.text assert "Jobs" in response.text assert "Edit document" in response.text assert "Delete document" in response.text def test_document_detail_page_renders_related_people_sources_and_jobs(self, app_client): """GET /ui/documents/{document_id} shows related records when present.""" _, client = app_client async def _seed_related() -> str: async with session_scope() as session: document = Document(name="Roster", document_type="record") person = Person(full_name="Jane Doe") session.add(document) session.add(person) await session.flush() session.add( DocumentPerson( document_id=document.id, person_id=person.id, role=DocumentPersonRole.AUTHOR, ) ) session.add( Source( document_id=document.id, page_number=1, upload_name="001_page.png", filename="stored_001_page.png", file_path="/tmp/stored_001_page.png", ) ) session.add( Job( document_id=document.id, ) ) await session.commit() await session.refresh(document) return str(document.id) document_id = asyncio.run(_seed_related()) response = client.get(f"/ui/documents/{document_id}") assert response.status_code == 200 assert "Jane Doe (author)" in response.text assert "Author: Jane Doe" in response.text assert "1 source(s) linked" in response.text assert "1 job(s) linked" in response.text def test_document_jobs_page_filters_to_document_context(self, app_client): _, client = app_client async def _seed() -> str: async with session_scope() as session: target = Document(name="Target", document_type="letter") other = Document(name="Other", document_type="record") session.add(target) session.add(other) await session.flush() session.add(Job(document_id=target.id)) session.add(Job(document_id=other.id)) await session.commit() await session.refresh(target) return str(target.id) document_id = asyncio.run(_seed()) response = client.get(f"/ui/documents/{document_id}/jobs") assert response.status_code == 200 assert "Jobs for Target" in response.text assert "Jobs for Other" not in response.text def test_document_sources_page_filters_to_document_context(self, app_client): _, client = app_client async def _seed() -> str: async with session_scope() as session: target = Document(name="Target", document_type="letter") other = Document(name="Other", document_type="record") session.add(target) session.add(other) await session.flush() session.add( Source( document_id=target.id, page_number=1, upload_name="target_page.png", filename="target_stored.png", file_path="/tmp/target_stored.png", ) ) session.add( Source( document_id=other.id, page_number=1, upload_name="other_page.png", filename="other_stored.png", file_path="/tmp/other_stored.png", ) ) await session.commit() await session.refresh(target) return str(target.id) document_id = asyncio.run(_seed()) response = client.get(f"/ui/documents/{document_id}/sources") assert response.status_code == 200 assert "Sources for Target" in response.text assert "target_page.png" in response.text assert "other_page.png" not in response.text def test_document_detail_page_rejects_invalid_id(self, app_client): """GET /ui/documents/{document_id} shows validation feedback for malformed IDs.""" _, client = app_client response = client.get("/ui/documents/not-a-uuid") assert response.status_code == 200 assert "Invalid document id" in response.text def test_document_detail_page_handles_missing_document(self, app_client): """GET /ui/documents/{document_id} shows not-found state for unknown IDs.""" _, client = app_client response = client.get(f"/ui/documents/{uuid4()}") assert response.status_code == 200 assert "Document not found" in response.text def test_document_edit_page_renders_expected_fields(self, app_client): """GET /ui/documents/{document_id}/edit renders editable fields and save controls.""" _, client = app_client async def _seed_document() -> str: async with session_scope() as session: document = Document( name="Editable Document", document_type="memo", document_date_raw="c. 1900", ) session.add(document) await session.commit() await session.refresh(document) return str(document.id) document_id = asyncio.run(_seed_document()) response = client.get(f"/ui/documents/{document_id}/edit") assert response.status_code == 200 assert "Edit document" in response.text assert "Document name and document type are required." in response.text assert "Document name" in response.text assert "Document type" in response.text assert "Author (Person)" in response.text assert "Exact date (YYYY-MM-DD)" in response.text assert "Approximate date" in response.text assert "Document location" in response.text assert "Archive identifier" in response.text assert "Notes" in response.text assert "Save changes" in response.text def test_document_delete_page_shows_confirmation_when_unlinked(self, app_client): """GET /ui/documents/{document_id}/delete renders permanent-action confirmation if unlinked.""" _, client = app_client async def _seed_document() -> str: async with session_scope() as session: document = Document(name="Safe Delete", document_type="letter") session.add(document) await session.commit() await session.refresh(document) return str(document.id) document_id = asyncio.run(_seed_document()) response = client.get(f"/ui/documents/{document_id}/delete") assert response.status_code == 200 assert "Delete document" in response.text assert "This action permanently deletes the document." in response.text assert "Delete document permanently" in response.text def test_document_delete_page_shows_blocked_state_when_dependencies_exist(self, app_client): """GET /ui/documents/{document_id}/delete explains blocked deletion with dependency categories.""" _, client = app_client async def _seed_related() -> str: async with session_scope() as session: document = Document(name="Blocked Delete", document_type="record") session.add(document) await session.flush() session.add( Source( document_id=document.id, page_number=1, upload_name="001_page.png", filename="stored_001_page.png", file_path="/tmp/stored_001_page.png", ) ) session.add(Job(document_id=document.id)) await session.commit() await session.refresh(document) return str(document.id) document_id = asyncio.run(_seed_related()) response = client.get(f"/ui/documents/{document_id}/delete") assert response.status_code == 200 assert "Delete is blocked because related records exist." in response.text assert "Dependencies present: Sources, Jobs" in response.text assert "Go to Jobs" in response.text def test_job_create_page_preselects_document_query_param(self, app_client): """GET /ui/jobs/new?document_id=... includes the selected document in rendered state.""" _, client = app_client async def _seed_document() -> str: async with session_scope() as session: document = Document( name="Preselected Document", document_type="letter", created_at=datetime.now(UTC), updated_at=datetime.now(UTC), ) session.add(document) await session.commit() await session.refresh(document) return str(document.id) document_id = asyncio.run(_seed_document()) response = client.get(f"/ui/jobs/new?document_id={document_id}") assert response.status_code == 200 assert "Preselected Document" in response.text