"""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 Photo 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(given_names="Ada", last_name="Lovelace")) await session.commit() response = client.get("/ui/people") assert response.status_code == 200 assert "Lovelace" in response.text assert "Ada" in response.text assert "Last Name" in response.text assert "First & Middle" in response.text or "First & Middle" in response.text assert "FamilySearch ID" in response.text assert "# Documents" 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(given_names="Counted", last_name="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 "Last name and first/middle names are 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( given_names="Grace", last_name="Hopper", 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", 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 "Last Name:" in response.text assert "First & Middle:" in response.text or "First & Middle:" 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_photo_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: photo_file = upload_dir / "photos" / "seeded.png" photo_file.parent.mkdir(parents=True, exist_ok=True) photo_file.write_bytes(b"portrait") async with session_scope() as session: person = Person(given_names="Portrait", last_name="Person") session.add(person) await session.flush() session.add( Photo( person_id=person.id, path="photos/seeded.png", is_primary=True, ) ) 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 assert "Edit Photo(s)" in response.text assert response.text.count("Edit Photo(s)") == 1 assert "Upload photo(s)" not in response.text @pytest.mark.asyncio async def test_person_photos_page_renders_photo_management_controls(self, app_client): app, client = app_client upload_dirs = {app.state.settings.upload_dir, get_settings().upload_dir} for upload_dir in upload_dirs: photo_file = upload_dir / "photos" / "seeded.png" photo_file.parent.mkdir(parents=True, exist_ok=True) photo_file.write_bytes(b"portrait") async with session_scope() as session: person = Person(given_names="Gallery", last_name="Person") session.add(person) await session.flush() session.add( Photo( person_id=person.id, path="photos/seeded.png", is_primary=True, description="Seeded description", ) ) await session.commit() person_id = str(person.id) response = client.get(f"/ui/people/{person_id}/photos") assert response.status_code == 200 assert "Edit Photos" in response.text assert "Upload Photo(s)" in response.text assert "Back to Person" in response.text assert "Save Description" in response.text assert "Delete Photo" in response.text assert "Set Primary" 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(given_names="Linked", last_name="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_removed_maiden_name_field(self, app_client): _, client = app_client async with session_scope() as session: person = Person(given_names="No Maiden", last_name="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(given_names="Editable", last_name="Person") 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 "Last name and first/middle names are required." in response.text assert "Editable" in response.text assert "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(given_names="Safe", last_name="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