V4.4 Complete

This commit is contained in:
Jim Lancaster
2026-08-15 14:30:33 -05:00
parent 63373bf24d
commit 7db4df1729
32 changed files with 1529 additions and 716 deletions
+27 -15
View File
@@ -24,6 +24,7 @@ from transcription.db.engine import get_engine
from transcription.db.models import Document
from transcription.db.models import DocumentType
from transcription.db.models import Person
from transcription.db.models import PersonRole
from transcription.db.session import dispose_session_factory
from transcription.db.session import session_scope
from transcription.services.documents import DocumentService
@@ -53,6 +54,12 @@ async def _document_type_id(*, db_url: str, label: str) -> UUID:
return document_type.id
async def _person_role_id(*, db_url: str, semantic_key: str) -> UUID:
async with session_scope(database_url=db_url) as session:
role = (await session.exec(select(PersonRole).where(PersonRole.semantic_key == semantic_key))).one()
return role.id
@contextmanager
def _v4_api_client(tmp_path: Path, *, db_filename: str) -> Generator[tuple[TestClient, str]]:
settings = Settings(
@@ -97,7 +104,7 @@ def test_list_document_types_returns_seeded_registry(tmp_path):
assert response.status_code == 200
payload = response.json()
labels = {item["label"] for item in payload}
assert {"Letter", "Record", "Memo"}.issubset(labels)
assert {"Book", "Letter", "Postcard", "Photo", "Journal", "Form"}.issubset(labels)
def test_list_person_roles_returns_seeded_registry(tmp_path):
@@ -106,14 +113,15 @@ def test_list_person_roles_returns_seeded_registry(tmp_path):
assert response.status_code == 200
payload = response.json()
codes = {item["code"] for item in payload}
assert {"author", "recipient", "mentioned"}.issubset(codes)
labels = {item["label"] for item in payload}
assert {"Author", "Recipient", "Mentioned"}.issubset(labels)
assert all("code" not in item and "semantic_key" not in item for item in payload)
def test_set_document_type_by_id_updates_canonical_field(tmp_path):
with _v4_api_client(tmp_path, db_filename="api-doc-type.db") as (client, db_url):
document_id, _ = _seed_document_and_person(db_url=db_url)
type_id = asyncio.run(_document_type_id(db_url=db_url, label="Record"))
type_id = asyncio.run(_document_type_id(db_url=db_url, label="Form"))
response = client.put(
f"/api/v4/documents/{document_id}/type",
json={"document_type_id": str(type_id)},
@@ -147,32 +155,35 @@ def test_document_type_payload_requires_uuid_only(tmp_path):
def test_document_people_role_aware_write_read_and_delete(tmp_path):
with _v4_api_client(tmp_path, db_filename="api-links.db") as (client, db_url):
document_id, person_id = _seed_document_and_person(db_url=db_url)
author_id = asyncio.run(_person_role_id(db_url=db_url, semantic_key="author"))
recipient_id = asyncio.run(_person_role_id(db_url=db_url, semantic_key="recipient"))
create_response = client.post(
f"/api/v4/documents/{document_id}/people",
json={"person_id": str(person_id), "role_code": "author"},
json={"person_id": str(person_id), "role_id": str(author_id)},
)
assert create_response.status_code == 200
created = create_response.json()
assert created["document_id"] == str(document_id)
assert created["person_id"] == str(person_id)
assert created["role_code"] == "author"
assert created["role_id"] is not None
assert created["role_id"] == str(author_id)
assert created["role_label"] == "Author"
link_id = created["id"]
update_response = client.patch(
f"/api/v4/document-people/{link_id}",
json={"role_code": "recipient"},
json={"role_id": str(recipient_id)},
)
assert update_response.status_code == 200
updated = update_response.json()
assert updated["role_code"] == "recipient"
assert updated["role_id"] == str(recipient_id)
assert updated["role_label"] == "Recipient"
list_response = client.get(f"/api/v4/documents/{document_id}/people")
assert list_response.status_code == 200
links = list_response.json()["links"]
assert len(links) == 1
assert links[0]["role_code"] == "recipient"
assert links[0]["role_id"] == str(recipient_id)
delete_response = client.delete(f"/api/v4/document-people/{link_id}")
assert delete_response.status_code == 204
@@ -182,7 +193,7 @@ def test_document_people_role_aware_write_read_and_delete(tmp_path):
assert list_after_delete.json()["links"] == []
def test_document_person_link_defaults_to_author_when_role_is_omitted(tmp_path):
def test_document_person_link_requires_role_id(tmp_path):
with _v4_api_client(tmp_path, db_filename="api-default-role.db") as (client, db_url):
document_id, person_id = _seed_document_and_person(db_url=db_url)
@@ -191,23 +202,24 @@ def test_document_person_link_defaults_to_author_when_role_is_omitted(tmp_path):
json={"person_id": str(person_id)},
)
assert response.status_code == 200
assert response.json()["role_code"] == "author"
assert response.status_code == 422
def test_duplicate_document_person_link_returns_conflict_envelope(tmp_path):
with _v4_api_client(tmp_path, db_filename="api-dup.db") as (client, db_url):
document_id, person_id = _seed_document_and_person(db_url=db_url)
author_id = asyncio.run(_person_role_id(db_url=db_url, semantic_key="author"))
recipient_id = asyncio.run(_person_role_id(db_url=db_url, semantic_key="recipient"))
first = client.post(
f"/api/v4/documents/{document_id}/people",
json={"person_id": str(person_id), "role_code": "author"},
json={"person_id": str(person_id), "role_id": str(author_id)},
)
assert first.status_code == 200
second = client.post(
f"/api/v4/documents/{document_id}/people",
json={"person_id": str(person_id), "role_code": "author"},
json={"person_id": str(person_id), "role_id": str(recipient_id)},
)
assert second.status_code == 409