"""Tests for the people page routes and action handlers.""" from datetime import date from uuid import uuid4 import pytest from sqlmodel import select from transcription.config import get_settings from transcription.db import session_scope from transcription.db.models import Document from transcription.db.models import DocumentPerson from transcription.db.models import Person from transcription.db.models import PersonRole from transcription.db.models import Source @pytest.mark.integration class TestPeoplePageRendering: """Verify people routes render expected CRUD read states.""" def test_people_page_renders_empty_state(self, app_client): _, client = app_client response = client.get("/ui/people") assert response.status_code == 200 assert "People" in response.text assert "Create new person" in response.text assert "No person records found in repository." in response.text @pytest.mark.asyncio async def test_people_page_lists_seeded_people(self, app_client): _, client = app_client async with session_scope() as session: session.add(Person(full_name="Ada Lovelace", display_name="Ada")) await session.commit() response = client.get("/ui/people") assert response.status_code == 200 assert "Ada Lovelace" in response.text assert "FamilySearch ID" in response.text assert "# Documents" in response.text assert "Display Name" not in response.text assert "Maiden Name" not in response.text @pytest.mark.asyncio async def test_people_page_shows_document_counts(self, app_client): _, client = app_client async with session_scope() as session: role = (await session.exec(select(PersonRole).where(PersonRole.semantic_key == "author"))).one() person = Person(full_name="Counted Person") document = Document(name="Linked For Count") session.add_all([person, document]) await session.flush() session.add(DocumentPerson(document_id=document.id, person_id=person.id, role_id=role.id)) await session.commit() response = client.get("/ui/people") assert response.status_code == 200 assert '"document_count":1' in response.text def test_person_create_page_renders_fields(self, app_client): _, client = app_client response = client.get("/ui/people/new") assert response.status_code == 200 assert "Create Person Record" in response.text assert "Full name is required." in response.text assert "Birth date" in response.text assert "Death date" in response.text assert "Birth date (YYYY-MM-DD)" not in response.text assert "Death date (YYYY-MM-DD)" not in response.text assert "FamilySearch ID" in response.text assert "Auto-fill from FamilySearch" not in response.text assert "Marriage date (FamilySearch)" not in response.text assert "Spouse (FamilySearch)" not in response.text assert "Biography" in response.text assert "Save person" in response.text @pytest.mark.asyncio async def test_person_detail_page_renders_metadata_and_empty_links(self, app_client): _, client = app_client async with session_scope() as session: person = Person( full_name="Grace Hopper", display_name="Grace", maiden_name="Murray", birth_date=date(1906, 12, 9), birth_date_raw="1906", birth_place="New York", death_date=date(1992, 1, 1), death_date_raw="1992", death_place="Arlington", biography="Computer pioneer", portrait_path="/images/grace.jpg", family_search_id="G8T4-MDQ", ) session.add(person) await session.commit() person_id = str(person.id) response = client.get(f"/ui/people/{person_id}") assert response.status_code == 200 assert "Grace Hopper" in response.text assert "Full Name:" in response.text assert "Display Name:" in response.text assert "Maiden Name:" in response.text assert "Birth Date:" in response.text assert "1906-12-09" in response.text assert "Death Date:" in response.text assert "1992-01-01" in response.text assert "Open Birth Place in Google Maps" not in response.text assert "Open Death Place in Google Maps" not in response.text assert "google.com/maps/search/?api=1&query=New+York" in response.text assert "google.com/maps/search/?api=1&query=Arlington" in response.text assert "biography" in response.text.lower() assert "Computer pioneer" in response.text assert "Created:" in response.text assert "Updated:" in response.text assert "Open in FamilySearch" not in response.text assert "FamilySearch ID:" in response.text assert "familysearch.org/tree/person/details/G8T4-MDQ" in response.text assert "New Document" in response.text assert "No linked documents yet." in response.text @pytest.mark.asyncio async def test_person_detail_page_resolves_relative_portrait_path(self, app_client): app, client = app_client upload_dirs = {app.state.settings.upload_dir, get_settings().upload_dir} for upload_dir in upload_dirs: portrait_file = upload_dir / "persons" / "person" / "seeded.png" portrait_file.parent.mkdir(parents=True, exist_ok=True) portrait_file.write_bytes(b"portrait") async with session_scope() as session: person = Person( full_name="Portrait Person", portrait_path="persons/person/seeded.png", ) session.add(person) await session.commit() person_id = str(person.id) response = client.get(f"/ui/people/{person_id}") assert response.status_code == 200 assert "No source media available for inspection." not in response.text @pytest.mark.asyncio async def test_person_detail_page_renders_linked_documents(self, app_client): _, client = app_client async with session_scope() as session: author_role = (await session.exec(select(PersonRole).where(PersonRole.semantic_key == "author"))).one() person = Person(full_name="Linked Person") document = Document(name="Linked Document") session.add_all([person, document]) await session.flush() session.add_all( [ Source( document_id=document.id, page_number=1, upload_name="linked-page-1.png", filename="linked-page-1.png", file_path="/tmp/linked-page-1.png", file_hash="1" * 64, file_size_bytes=1, ), Source( document_id=document.id, page_number=2, upload_name="linked-page-2.png", filename="linked-page-2.png", file_path="/tmp/linked-page-2.png", file_hash="2" * 64, file_size_bytes=1, ), ] ) session.add( DocumentPerson( document_id=document.id, person_id=person.id, role_id=author_role.id, ) ) await session.commit() person_id = str(person.id) response = client.get(f"/ui/people/{person_id}") assert response.status_code == 200 assert "Document Name" in response.text assert "Number of Pages" in response.text assert "Linked Document" in response.text assert "Author" in response.text assert '"page_count":2' in response.text assert "Open" not in response.text @pytest.mark.asyncio async def test_person_detail_page_hides_empty_maiden_name(self, app_client): _, client = app_client async with session_scope() as session: person = Person(full_name="No Maiden Name") session.add(person) await session.commit() person_id = str(person.id) response = client.get(f"/ui/people/{person_id}") assert response.status_code == 200 assert "Maiden Name:" not in response.text def test_person_detail_page_handles_invalid_id(self, app_client): _, client = app_client response = client.get("/ui/people/not-a-uuid") assert response.status_code == 200 assert "Invalid person id" in response.text def test_person_detail_page_handles_missing_person(self, app_client): _, client = app_client response = client.get(f"/ui/people/{uuid4()}") assert response.status_code == 200 assert "Person not found" in response.text @pytest.mark.asyncio async def test_person_edit_page_renders_expected_fields(self, app_client): _, client = app_client async with session_scope() as session: person = Person(full_name="Editable Person", display_name="EP") session.add(person) await session.commit() person_id = str(person.id) response = client.get(f"/ui/people/{person_id}/edit") assert response.status_code == 200 assert "Edit Person Record" in response.text assert "Full name is required." in response.text assert "Editable Person" in response.text assert "Save changes" in response.text @pytest.mark.asyncio async def test_person_delete_page_shows_confirmation_when_unlinked(self, app_client): _, client = app_client async with session_scope() as session: person = Person(full_name="Safe Delete") session.add(person) await session.commit() person_id = str(person.id) response = client.get(f"/ui/people/{person_id}/delete") assert response.status_code == 200 assert "Delete Person Record" in response.text assert "This action permanently deletes the person record." in response.text assert "Delete person permanently" in response.text