Files
transcription/tests/ui/test_people_page.py
T
Jim Lancaster 0f30d902b9
Quality Gate / gate (push) Failing after 11s
V5.0 fixes and revisions
2026-08-23 10:32:20 -05:00

317 lines
12 KiB
Python

"""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(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",
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_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(full_name="Portrait 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(full_name="Gallery 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(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