from __future__ import annotations from uuid import uuid4 import pytest 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.errors import ErrorCategory from transcription.services.documents import DocumentService from transcription.services.documents import DocumentTypeError from transcription.services.people import PeopleService from transcription.services.people import PersonRoleError @pytest.mark.asyncio async def test_document_type_maintenance_uses_alphabetical_labels(default_session_factory): service = DocumentService(session_factory=default_session_factory) second = await service.create_document_type(label="Court Record") first = await service.create_document_type(label="Letter") updated = await service.update_document_type( second.id, label="Archive", is_active=False, ) assert updated.label == "Archive" assert updated.is_active is False assert [item.id for item in await service.list_document_types(active_only=False)] == [second.id, first.id] assert [item.id for item in await service.list_document_types()] == [first.id] summaries = await service.list_document_type_summaries() assert [item.label for item in summaries] == ["Archive", "Letter"] assert [item.reference_count for item in summaries] == [0, 0] @pytest.mark.asyncio async def test_document_type_duplicate_normalized_label_is_conflict(default_session_factory): service = DocumentService(session_factory=default_session_factory) await service.create_document_type(label="Letter") with pytest.raises(DocumentTypeError) as caught: await service.create_document_type(label=" letter ") assert caught.value.category == ErrorCategory.CONFLICT @pytest.mark.asyncio async def test_document_type_delete_allows_unreferenced_and_blocks_referenced(default_session_factory): service = DocumentService(session_factory=default_session_factory) unused = await service.create_document_type(label="Unused") referenced = await service.create_document_type(label="Record") await service.create_document(Document(id=uuid4(), name="Typed document", document_type_id=referenced.id)) summaries = {item.id: item for item in await service.list_document_type_summaries()} assert summaries[referenced.id].reference_count == 1 await service.delete_document_type(unused.id) with pytest.raises(DocumentTypeError) as caught: await service.delete_document_type(referenced.id) assert caught.value.category == ErrorCategory.CONFLICT relabeled = await service.update_document_type( referenced.id, label="Referenced Record", is_active=False, ) assert relabeled.label == "Referenced Record" assert relabeled.is_active is False @pytest.mark.asyncio async def test_person_role_maintenance_orders_by_normalized_label(default_session_factory): service = PeopleService(session_factory=default_session_factory) second = await service.create_person_role(label="Witness") first = await service.create_person_role(label="Archivist") updated = await service.update_person_role(second.id, label="Attestor", is_active=False) assert updated.semantic_key is None assert [item.id for item in await service.list_person_roles(active_only=False)] == [first.id, second.id] assert [item.id for item in await service.list_person_roles()] == [first.id] summaries = {item.id: item for item in await service.list_person_role_summaries()} assert summaries[second.id].reference_count == 0 assert summaries[second.id].is_built_in is False @pytest.mark.asyncio async def test_person_role_delete_allows_unreferenced_and_blocks_referenced(default_session_factory): documents = DocumentService(session_factory=default_session_factory) people = PeopleService(session_factory=default_session_factory) unused = await people.create_person_role(label="Witness") referenced = await people.create_person_role(label="Creator") document = await documents.create_document(Document(name="Role document")) person = await people.create_person(Person(full_name="Role Person")) await people.create_document_person( DocumentPerson( document_id=document.id, person_id=person.id, role_id=referenced.id, ) ) await people.delete_person_role(unused.id) with pytest.raises(PersonRoleError) as caught: await people.delete_person_role(referenced.id) assert caught.value.category == ErrorCategory.CONFLICT relabeled = await people.update_person_role(referenced.id, label="Creator", is_active=False) assert relabeled.label == "Creator" assert relabeled.is_active is False @pytest.mark.asyncio async def test_person_role_duplicate_normalized_label_is_conflict(default_session_factory): service = PeopleService(session_factory=default_session_factory) await service.create_person_role(label="Witness") with pytest.raises(PersonRoleError) as caught: await service.create_person_role(label=" witness ") assert caught.value.category == ErrorCategory.CONFLICT @pytest.mark.asyncio async def test_custom_person_role_can_be_used_for_document_link(default_session_factory): documents = DocumentService(session_factory=default_session_factory) people = PeopleService(session_factory=default_session_factory) role = await people.create_person_role(label="Witness") document = await documents.create_document(Document(name="Witnessed document")) person = await people.create_person(Person(full_name="Archive Witness")) link = await people.add_document_person_link( document_id=document.id, person_id=person.id, role_id=role.id, ) loaded = await people.list_document_people(document_id=document.id) assert link.role_id == role.id assert loaded[0].role_ref is not None assert loaded[0].role_ref.label == "Witness" @pytest.mark.asyncio async def test_built_in_person_role_cannot_be_deleted(default_session_factory): people = PeopleService(session_factory=default_session_factory) async with people._session_scope() as session: role = PersonRole( semantic_key="author", label="Author", normalized_label="author", ) session.add(role) await session.commit() await session.refresh(role) with pytest.raises(PersonRoleError) as caught: await people.delete_person_role(role.id) assert caught.value.category == ErrorCategory.CONFLICT