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
+7 -6
View File
@@ -9,10 +9,10 @@ from sqlmodel import select
from transcription.db import session_scope
from transcription.db.models import Document
from transcription.db.models import DocumentPerson
from transcription.db.models import DocumentPersonRole
from transcription.db.models import DocumentType
from transcription.db.models import Job
from transcription.db.models import Person
from transcription.db.models import PersonRole
from transcription.db.models import Source
# --- Helper Fixtures ---
@@ -23,6 +23,7 @@ async def seed_person_and_document():
"""Seed a Person and Document linked by DocumentPerson role."""
async with session_scope() as session:
letter_type = (await session.exec(select(DocumentType).where(DocumentType.label == "Letter"))).one()
author_role = (await session.exec(select(PersonRole).where(PersonRole.semantic_key == "author"))).one()
person = Person(full_name="Zenna Cochran")
session.add(person)
await session.flush()
@@ -38,7 +39,7 @@ async def seed_person_and_document():
link = DocumentPerson(
document_id=doc.id,
person_id=person.id,
role=DocumentPersonRole.AUTHOR,
role_id=author_role.id,
)
session.add(link)
await session.commit()
@@ -92,7 +93,7 @@ class TestDocumentsPageRendering:
assert response.status_code == 200
assert "Create Document" in response.text
assert "Document name" in response.text
assert "Linked People by Role" in response.text
assert "Linked People" in response.text
assert "Document type" in response.text
@pytest.mark.asyncio
@@ -132,7 +133,7 @@ class TestDocumentsPageRendering:
_, client = app_client
async with session_scope() as session:
doc = Document(name="Doc With Job", document_type="letter")
doc = Document(name="Doc With Job")
session.add(doc)
await session.flush()
@@ -165,7 +166,7 @@ class TestDocumentsPageRendering:
_, client = app_client
async with session_scope() as session:
doc = Document(name="Doc With Source", document_type="letter")
doc = Document(name="Doc With Source")
session.add(doc)
await session.flush()
@@ -194,7 +195,7 @@ class TestDocumentsPageRendering:
_, client = app_client
async with session_scope() as session:
doc = Document(name="Orphan Document", document_type="note")
doc = Document(name="Orphan Document")
session.add(doc)
await session.commit()
doc_id = str(doc.id)
+3 -3
View File
@@ -15,7 +15,7 @@ from transcription.db.models import JobStatus
async def seed_document_with_unlinked_job():
"""Seed a document and a queued job for testing route actions."""
async with session_scope() as session:
document = Document(name="Test Archival Letter", document_type="letter")
document = Document(name="Test Archival Letter")
session.add(document)
await session.flush()
@@ -72,7 +72,7 @@ class TestJobsPageRendering:
_, client = app_client
async with session_scope() as session:
doc = Document(name="Preselected Journal Entry", document_type="journal")
doc = Document(name="Preselected Journal Entry")
session.add(doc)
await session.commit()
doc_id = str(doc.id)
@@ -133,7 +133,7 @@ class TestJobsPageRendering:
_, client = app_client
async with session_scope() as session:
doc = Document(name="Processing Doc", document_type="letter")
doc = Document(name="Processing Doc")
session.add(doc)
await session.flush()
job = Job(document_id=doc.id, status=JobStatus.PROCESSING)
+6 -4
View File
@@ -4,12 +4,13 @@ from datetime import date
from uuid import uuid4
import pytest
from sqlmodel import select
from transcription.db import session_scope
from transcription.db.models import Document
from transcription.db.models import DocumentPerson
from transcription.db.models import DocumentPersonRole
from transcription.db.models import Person
from transcription.db.models import PersonRole
@pytest.mark.integration
@@ -122,8 +123,9 @@ class TestPeoplePageRendering:
_, 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", document_type="letter")
document = Document(name="Linked Document")
session.add_all([person, document])
await session.flush()
@@ -131,7 +133,7 @@ class TestPeoplePageRendering:
DocumentPerson(
document_id=document.id,
person_id=person.id,
role=DocumentPersonRole.AUTHOR,
role_id=author_role.id,
)
)
await session.commit()
@@ -141,7 +143,7 @@ class TestPeoplePageRendering:
assert response.status_code == 200
assert "Linked Document" in response.text
assert "Role: author" in response.text
assert "Role: Author" in response.text
def test_person_detail_page_handles_invalid_id(self, app_client):
_, client = app_client
+103
View File
@@ -0,0 +1,103 @@
from pathlib import Path
import pytest
from transcription.db import session_scope
from transcription.db.models import Document
from transcription.db.models import Source
from transcription.ui.pages.print_preview_page import reflow_transcription
def test_reflow_transcription_preserves_paragraph_boundaries():
assert reflow_transcription("first line\nsecond line\n\nnext paragraph") == [
"first line second line",
"next paragraph",
]
@pytest.mark.integration
@pytest.mark.asyncio
async def test_document_print_preview_and_safe_media_route(app_client):
app, client = app_client
media_path = app.state.settings.upload_dir / "documents" / "print-page.png"
pdf_path = app.state.settings.upload_dir / "documents" / "print-page.pdf"
media_path.parent.mkdir(parents=True, exist_ok=True)
media_path.write_bytes(b"\x89PNG\r\n\x1a\n")
pdf_path.write_bytes(b"%PDF-1.4\n%%EOF")
async with session_scope() as session:
document = Document(name="<Print & Preserve>", notes="<script>unsafe()</script>")
session.add(document)
await session.flush()
source = Source(
document_id=document.id,
page_number=1,
upload_name="print-page.png",
filename="print-page.png",
file_path=str(media_path),
file_hash="a" * 64,
file_size_bytes=media_path.stat().st_size,
raw_transcription="line one\nline two",
)
session.add(source)
session.add(
Source(
document_id=document.id,
page_number=2,
upload_name="print-page.pdf",
filename="print-page.pdf",
file_path=str(pdf_path),
file_hash="c" * 64,
file_size_bytes=pdf_path.stat().st_size,
raw_transcription="PDF source",
)
)
await session.commit()
document_id = document.id
source_id = source.id
response = client.get(f"/ui/documents/{document_id}/print")
assert response.status_code == 200
assert "Print &amp; Preserve" in response.text
assert "&lt;script&gt;unsafe()&lt;/script&gt;" in response.text
assert "Facsimile" in response.text
assert "Text only" in response.text
assert "print-page-break" in response.text
assert "print-source-pdf" in response.text
assert str(media_path) not in response.text
assert str(pdf_path) not in response.text
media_response = client.get(f"/api/v4/documents/{document_id}/sources/{source_id}/media")
assert media_response.status_code == 200
assert media_response.headers["content-type"] == "image/png"
@pytest.mark.integration
@pytest.mark.asyncio
async def test_document_source_media_rejects_cross_document_access(app_client):
app, client = app_client
media_path = Path(app.state.settings.upload_dir) / "documents" / "other.png"
media_path.parent.mkdir(parents=True, exist_ok=True)
media_path.write_bytes(b"\x89PNG\r\n\x1a\n")
async with session_scope() as session:
owner = Document(name="Owner")
other = Document(name="Other")
session.add_all([owner, other])
await session.flush()
source = Source(
document_id=owner.id,
page_number=1,
upload_name="other.png",
filename="other.png",
file_path=str(media_path),
file_hash="b" * 64,
file_size_bytes=media_path.stat().st_size,
)
session.add(source)
await session.commit()
other_id = other.id
source_id = source.id
response = client.get(f"/api/v4/documents/{other_id}/sources/{source_id}/media")
assert response.status_code == 404
+4 -4
View File
@@ -80,7 +80,7 @@ class TestSourcesPageRendering:
_, client = app_client
async with session_scope() as session:
document = Document(name="Source Document", document_type="letter")
document = Document(name="Source Document")
session.add(document)
await session.flush()
session.add(
@@ -108,8 +108,8 @@ class TestSourcesPageRendering:
_, client = app_client
async with session_scope() as session:
target = Document(name="Target", document_type="letter")
other = Document(name="Other", document_type="record")
target = Document(name="Target")
other = Document(name="Other")
session.add_all([target, other])
await session.flush()
@@ -274,7 +274,7 @@ class TestSourcesPageRendering:
_, client = app_client
async with session_scope() as session:
document = Document(name="Unlinked Source Doc", document_type="memo")
document = Document(name="Unlinked Source Doc")
session.add(document)
await session.flush()
source = Source(