v4.10 revision to remove "legacy compatibility" code
Quality Gate / gate (push) Failing after 11s

This commit is contained in:
Jim Lancaster
2026-08-22 11:21:18 -05:00
parent cf49c3c127
commit 63c21d4a14
25 changed files with 840 additions and 301 deletions
+55 -5
View File
@@ -99,8 +99,20 @@ class TestPipelineSuccessFlow:
top_p=None,
settings=None,
provider=None,
source_reference=None,
requested_model=None,
) -> TranscriptionResult:
_ = (image_path, prompt_name, prompt_text, temperature, top_p, settings, provider)
_ = (
image_path,
prompt_name,
prompt_text,
temperature,
top_p,
settings,
provider,
source_reference,
requested_model,
)
return TranscriptionResult(
text="Pipeline transcript",
provider="openrouter",
@@ -181,9 +193,11 @@ class TestPipelineSuccessFlow:
top_p=None,
settings=None,
provider=None,
source_reference=None,
requested_model=None,
) -> TranscriptionResult:
page_name = Path(image_path).name
_ = (prompt_name, prompt_text, temperature, top_p, settings, provider)
_ = (prompt_name, prompt_text, temperature, top_p, settings, provider, source_reference, requested_model)
return TranscriptionResult(
text=f"Transcript for {page_name}",
provider="openrouter",
@@ -248,10 +262,22 @@ class TestPipelineSuccessFlow:
top_p=None,
settings=None,
provider=None,
source_reference=None,
requested_model=None,
) -> TranscriptionResult:
nonlocal call_count
call_count += 1
_ = (image_path, prompt_name, prompt_text, temperature, top_p, settings, provider)
_ = (
image_path,
prompt_name,
prompt_text,
temperature,
top_p,
settings,
provider,
source_reference,
requested_model,
)
if call_count == 2:
raise RuntimeError("simulated page failure")
return TranscriptionResult(
@@ -327,9 +353,21 @@ class TestPipelineSuccessFlow:
top_p=None,
settings=None,
provider=None,
source_reference=None,
requested_model=None,
) -> TranscriptionResult:
nonlocal call_count
_ = (image_path, prompt_name, prompt_text, temperature, top_p, settings, provider)
_ = (
image_path,
prompt_name,
prompt_text,
temperature,
top_p,
settings,
provider,
source_reference,
requested_model,
)
call_count += 1
return TranscriptionResult(
text="new transcript",
@@ -383,8 +421,20 @@ class TestPipelineFailureFlow:
top_p=None,
settings=None,
provider=None,
source_reference=None,
requested_model=None,
) -> TranscriptionResult:
_ = (image_path, prompt_name, prompt_text, temperature, top_p, settings, provider)
_ = (
image_path,
prompt_name,
prompt_text,
temperature,
top_p,
settings,
provider,
source_reference,
requested_model,
)
raise RuntimeError("pipeline provider failure")
monkeypatch.setattr(
+3 -3
View File
@@ -34,7 +34,7 @@ async def test_document_type_maintenance_uses_alphabetical_labels(default_sessio
summaries = await service.list_document_type_summaries()
assert [item.label for item in summaries] == ["Archive", "Letter"]
assert [item.document_count for item in summaries] == [0, 0]
assert [item.reference_count for item in summaries] == [0, 0]
@pytest.mark.asyncio
@@ -56,7 +56,7 @@ async def test_document_type_delete_allows_unreferenced_and_blocks_referenced(de
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].document_count == 1
assert summaries[referenced.id].reference_count == 1
await service.delete_document_type(unused.id)
with pytest.raises(DocumentTypeError) as caught:
@@ -84,7 +84,7 @@ async def test_person_role_maintenance_orders_by_normalized_label(default_sessio
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].link_count == 0
assert summaries[second.id].reference_count == 0
assert summaries[second.id].is_built_in is False
+5 -2
View File
@@ -73,7 +73,10 @@ async def test_create_job_for_document_sorts_sources_and_creates_links(async_ses
assert all(source.filename.endswith(".pdf") for source in sources)
assert all("A_page" not in source.filename and "b_page" not in source.filename for source in sources)
assert all(Path(source.filename).stem == str(source.id) for source in sources)
assert all(Path(source.file_path).parent == (tmp_path / "documents" / str(document.id)) for source in sources)
assert all(
source.file_path == f"documents/{document.id}/{source.filename}"
for source in sources
)
assert [source.file_hash for source in sources] == [
"ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb",
"3e23e8160039594a33894f6564e1b1348bbd7a0088d42c4acb73eeaed59c009d",
@@ -108,7 +111,7 @@ async def test_create_document_job_stores_source_under_document_id_directory(asy
assert source is not None
assert Path(source.filename).stem == str(source.id)
assert result.stored_path.name == source.filename
assert Path(source.file_path).parent == expected_parent
assert source.file_path == f"documents/{result.document_id}/{source.filename}"
assert source.file_hash == "2c8648d103e3dd7ad87660da0f126a1443b6d21ac1bd3ec000c5e24e2373a90c"
assert source.file_size_bytes == len(b"image-bytes")
+15 -3
View File
@@ -81,8 +81,20 @@ class TestWorkflowReliability:
top_p=None,
settings=None,
provider=None,
source_reference=None,
requested_model=None,
):
_ = (image_path, prompt_name, prompt_text, temperature, top_p, settings, provider)
_ = (
image_path,
prompt_name,
prompt_text,
temperature,
top_p,
settings,
provider,
source_reference,
requested_model,
)
raise TimeoutError("simulated provider timeout")
monkeypatch.setattr("transcription.services.workflows.transcribe_document_image", _never_returns)
@@ -146,9 +158,9 @@ class TestWorkflowReliability:
budget_seconds = 0.20
real_build = workflows_module.build_provider_input
def _slow_build(source_arg):
def _slow_build(source_arg, **kwargs):
time.sleep(setup_seconds)
return real_build(source_arg)
return real_build(source_arg, **kwargs)
async def _never_returns(*args, **kwargs):
_ = (args, kwargs)
+59 -59
View File
@@ -17,14 +17,10 @@ from transcription.config import SqliteSettings
from transcription.db import create_all
from transcription.db import dispose_database_runtime
from transcription.db import initialize_database_runtime
from transcription.db import normalize_legacy_status_spellings
from transcription.db import reconcile_canonical_media_paths
from transcription.db import reconcile_legacy_job_source_columns
from transcription.db import session_scope
from transcription.db.models import Document
from transcription.db.models import DocumentType
from transcription.db.models import Job
from transcription.db.models import JobSource
from transcription.db.models import JobSourceStatus
from transcription.db.models import PersonRole
from transcription.db.models import Source
@@ -158,60 +154,6 @@ async def test_create_all_declares_hot_path_indexes(tmp_path):
await dispose_database_runtime()
@pytest.mark.asyncio
async def test_normalize_legacy_status_spellings_repairs_job_source_status_rows(tmp_path):
settings = Settings(
openrouter_api_key="test-key",
database=SqliteSettings(path=str(tmp_path / "legacy-status.db")),
environment="test",
)
runtime = initialize_database_runtime(settings=settings)
try:
await create_all(engine=runtime.engine)
async with AsyncSession(runtime.engine, expire_on_commit=False) as session:
document = Document(name="legacy-status-doc")
session.add(document)
await session.flush()
job = Job(document_id=document.id)
session.add(job)
await session.flush()
source = Source(
document_id=document.id,
page_number=1,
upload_name="legacy.jpg",
filename="legacy.jpg",
file_path="uploads/legacy.jpg",
file_hash="a" * 64,
file_size_bytes=1,
)
session.add(source)
await session.flush()
job_source = JobSource(job_id=job.id, source_id=source.id, status=JobSourceStatus.PENDING)
session.add(job_source)
await session.commit()
await session.refresh(job_source)
async with runtime.engine.begin() as connection:
await connection.execute(
text('update "job_source" set status = :status where status = :expected'),
{"status": "TRANSCRIBED", "expected": JobSourceStatus.PENDING.value},
)
fixed_rows = await normalize_legacy_status_spellings(engine=runtime.engine)
assert fixed_rows == 1
async with runtime.engine.connect() as connection:
status = (
await connection.execute(
text('select status from "job_source"'),
)
).scalar_one()
assert status == "transcribed"
finally:
await dispose_database_runtime()
@pytest.mark.asyncio
async def test_reconcile_legacy_job_source_columns_drops_executed_at(tmp_path):
settings = Settings(
@@ -253,6 +195,64 @@ async def test_reconcile_legacy_job_source_columns_drops_executed_at(tmp_path):
await dispose_database_runtime()
@pytest.mark.asyncio
async def test_reconcile_canonical_media_paths_normalizes_source_and_person_paths(tmp_path):
settings = Settings(
openrouter_api_key="test-key",
database=SqliteSettings(path=str(tmp_path / "canonical-paths.db")),
environment="test",
)
runtime = initialize_database_runtime(settings=settings)
try:
await create_all(engine=runtime.engine)
async with runtime.engine.begin() as connection:
await connection.execute(
text(
'insert into "person" (id, full_name, portrait_path, created_at, updated_at) '
'values (:id, :full_name, :portrait_path, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)'
),
{"id": "11" * 16, "full_name": "Portrait", "portrait_path": "portraits/person/seeded.png"},
)
await connection.execute(
text(
'insert into "document" (id, name, created_at, updated_at) '
'values (:id, :name, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)'
),
{"id": "22" * 16, "name": "Doc"},
)
await connection.execute(
text(
'insert into "source" (id, document_id, page_number, upload_name, filename, file_path, file_hash, file_size_bytes, date_uploaded) '
'values (:id, :document_id, 1, :upload_name, :filename, :file_path, :file_hash, :file_size_bytes, CURRENT_TIMESTAMP)'
),
{
"id": "33" * 16,
"document_id": "22" * 16,
"upload_name": "page.png",
"filename": "page.png",
"file_path": "data\\documents\\doc-1\\page.png",
"file_hash": "a" * 64,
"file_size_bytes": 1,
},
)
changed = await reconcile_canonical_media_paths(engine=runtime.engine)
assert changed == 2
async with runtime.engine.connect() as connection:
source_path = (
await connection.execute(text('select file_path from "source" where id = :id'), {"id": "33" * 16})
).scalar_one()
portrait_path = (
await connection.execute(text('select portrait_path from "person" where id = :id'), {"id": "11" * 16})
).scalar_one()
assert source_path == "documents/doc-1/page.png"
assert portrait_path == "persons/person/seeded.png"
finally:
await dispose_database_runtime()
def test_metadata_has_no_unresolvable_table_cycle():
"""create_all must be able to order every table, including on PostgreSQL."""
with warnings.catch_warnings():
+116
View File
@@ -0,0 +1,116 @@
from __future__ import annotations
from datetime import UTC
from datetime import datetime
from pathlib import Path
from uuid import uuid4
from sqlalchemy import create_engine
from sqlalchemy import select
from sqlmodel import SQLModel
from transcription.db.migration import export_bundle
from transcription.db.migration import import_bundle
from transcription.db.migration import sqlite_url_from_path
# Register table metadata.
from transcription.db import models as _models # noqa: F401
def test_export_import_migration_round_trips_db_and_uploads(tmp_path):
source_db_path = tmp_path / "source.db"
target_db_path = tmp_path / "target.db"
source_upload_dir = tmp_path / "source_uploads"
target_upload_dir = tmp_path / "target_uploads"
bundle_dir = tmp_path / "bundle"
source_db_url = sqlite_url_from_path(source_db_path)
target_db_url = sqlite_url_from_path(target_db_path)
document_id = uuid4()
job_id = uuid4()
source_id = uuid4()
job_source_id = uuid4()
attempt_id = uuid4()
filename = f"{source_id}.jpg"
media_path = source_upload_dir / "documents" / str(document_id) / filename
media_path.parent.mkdir(parents=True, exist_ok=True)
media_path.write_bytes(b"sample-image")
engine = create_engine(source_db_url)
try:
SQLModel.metadata.create_all(engine)
with engine.begin() as connection:
connection.execute(
SQLModel.metadata.tables["document"].insert(),
[{"id": document_id, "name": "Export doc"}],
)
connection.execute(
SQLModel.metadata.tables["job"].insert(),
[{"id": job_id, "document_id": document_id, "status": "queued"}],
)
connection.execute(
SQLModel.metadata.tables["source"].insert(),
[
{
"id": source_id,
"document_id": document_id,
"page_number": 1,
"upload_name": "upload.jpg",
"filename": filename,
"file_path": str(media_path),
"file_hash": "a" * 64,
"file_size_bytes": len(b"sample-image"),
}
],
)
connection.execute(
SQLModel.metadata.tables["job_source"].insert(),
[{"id": job_source_id, "job_id": job_id, "source_id": source_id, "status": "pending"}],
)
connection.execute(
SQLModel.metadata.tables["execution_attempt"].insert(),
[
{
"id": attempt_id,
"job_source_id": job_source_id,
"job_id": job_id,
"source_id": source_id,
"attempt_number": 1,
"status": "transcribed",
"provider": "fixture",
"model": "fixture-model",
"transport_body": b"body",
"raw_transcription": "hello",
"started_at": datetime.now(UTC),
"finished_at": datetime.now(UTC),
"duration_ms": 10,
}
],
)
finally:
engine.dispose()
export_bundle(source_db_url=source_db_url, source_upload_dir=source_upload_dir, bundle_dir=bundle_dir)
import_bundle(target_db_url=target_db_url, target_upload_dir=target_upload_dir, bundle_dir=bundle_dir)
target_engine = create_engine(target_db_url)
try:
with target_engine.connect() as connection:
source_row = connection.execute(
select(SQLModel.metadata.tables["source"].c.file_path).where(
SQLModel.metadata.tables["source"].c.id == source_id
)
).one()
attempt_row = connection.execute(
select(SQLModel.metadata.tables["execution_attempt"].c.transport_body).where(
SQLModel.metadata.tables["execution_attempt"].c.id == attempt_id
)
).one()
assert source_row[0] == f"documents/{document_id}/{filename}"
assert attempt_row[0] == b"body"
finally:
target_engine.dispose()
copied_media_path = target_upload_dir / "documents" / str(document_id) / filename
assert copied_media_path.read_bytes() == b"sample-image"
+1 -1
View File
@@ -124,7 +124,7 @@ async def seed_job(app_client: tuple[FastAPI, TestClient]) -> Callable[..., Awai
page_number=1,
upload_name=filename,
filename=filename,
file_path=str(stored_path),
file_path=stored_path.resolve().relative_to(app.state.settings.upload_dir.resolve()).as_posix(),
file_hash="b" * 64,
file_size_bytes=len(stored_path.read_bytes()),
)
+15 -40
View File
@@ -4,24 +4,21 @@ from transcription.ui.components.media_urls import resolve_media_url
def test_resolve_media_url_maps_managed_absolute_path_to_upload_route(tmp_path):
upload_dir = tmp_path / "uploads"
managed_path = upload_dir / "documents" / "abc" / "page.jpg"
managed_path.parent.mkdir(parents=True, exist_ok=True)
managed_path.write_bytes(b"x")
canonical_path = upload_dir / "documents" / "abc" / "page.jpg"
canonical_path.parent.mkdir(parents=True, exist_ok=True)
canonical_path.write_bytes(b"x")
resolved = resolve_media_url(str(managed_path), upload_dir=upload_dir, base_url="http://localhost:8000")
resolved = resolve_media_url("documents/abc/page.jpg", upload_dir=upload_dir, base_url="http://localhost:8000")
assert resolved == "http://localhost:8000/uploads/documents/abc/page.jpg"
def test_resolve_media_url_rejects_unmanaged_absolute_path(tmp_path):
def test_resolve_media_url_rejects_absolute_filesystem_path(tmp_path):
upload_dir = tmp_path / "uploads"
managed_path = upload_dir / "documents" / "abc" / "page.jpg"
managed_path.parent.mkdir(parents=True, exist_ok=True)
managed_path.write_bytes(b"x")
unmanaged_path = tmp_path / "other-root" / "secret" / "page.jpg"
unmanaged_path.parent.mkdir(parents=True, exist_ok=True)
unmanaged_path.write_bytes(b"x")
absolute_path = upload_dir / "documents" / "abc" / "page.jpg"
absolute_path.parent.mkdir(parents=True, exist_ok=True)
absolute_path.write_bytes(b"x")
resolved = resolve_media_url(str(unmanaged_path), upload_dir=upload_dir, base_url="http://localhost:8000")
resolved = resolve_media_url(str(absolute_path), upload_dir=upload_dir, base_url="http://localhost:8000")
assert resolved is None
@@ -36,21 +33,9 @@ def test_resolve_media_url_rejects_stale_relative_path(tmp_path):
assert resolved is None
def test_resolve_media_url_rejects_basename_collision_from_unmanaged_path(tmp_path):
def test_resolve_media_url_rejects_non_canonical_relative_path(tmp_path):
upload_dir = tmp_path / "uploads"
managed_path = upload_dir / "documents" / "abc" / "shared-name.jpg"
managed_path.parent.mkdir(parents=True, exist_ok=True)
managed_path.write_bytes(b"managed")
unmanaged_path = tmp_path / "scratch" / "shared-name.jpg"
unmanaged_path.parent.mkdir(parents=True, exist_ok=True)
unmanaged_path.write_bytes(b"unmanaged")
resolved = resolve_media_url(
str(unmanaged_path),
upload_dir=upload_dir,
base_url="http://localhost:8000",
)
resolved = resolve_media_url("shared-name.jpg", upload_dir=upload_dir, base_url="http://localhost:8000")
assert resolved is None
@@ -68,28 +53,18 @@ def test_resolve_media_url_accepts_existing_upload_relative_path(tmp_path):
assert resolved == "http://localhost:8000/uploads/documents/abc/page.jpg"
def test_resolve_media_url_accepts_existing_portraits_relative_path(tmp_path):
def test_resolve_media_url_accepts_existing_persons_relative_path(tmp_path):
upload_dir = tmp_path / "uploads"
managed_path = upload_dir / "portraits" / "person" / "seeded.png"
managed_path = upload_dir / "persons" / "person" / "seeded.png"
managed_path.parent.mkdir(parents=True, exist_ok=True)
managed_path.write_bytes(b"x")
resolved = resolve_media_url(
"portraits/person/seeded.png",
"persons/person/seeded.png",
upload_dir=upload_dir,
base_url="http://localhost:8000",
)
assert resolved == "http://localhost:8000/uploads/portraits/person/seeded.png"
def test_public_media_path_label_maps_managed_absolute_path_to_upload_route(tmp_path):
upload_dir = tmp_path / "uploads"
managed_path = upload_dir / "documents" / "abc" / "page.jpg"
managed_path.parent.mkdir(parents=True, exist_ok=True)
managed_path.write_bytes(b"x")
label = public_media_path_label(str(managed_path), upload_dir=upload_dir)
assert label == "/uploads/documents/abc/page.jpg"
assert resolved == "http://localhost:8000/uploads/persons/person/seeded.png"
def test_public_media_path_label_hides_unmanaged_absolute_path(tmp_path):
+2 -2
View File
@@ -115,14 +115,14 @@ class TestPeoplePageRendering:
app, client = app_client
upload_dirs = {app.state.settings.upload_dir, get_settings().upload_dir}
for upload_dir in upload_dirs:
portrait_file = upload_dir / "portraits" / "person" / "seeded.png"
portrait_file = upload_dir / "persons" / "person" / "seeded.png"
portrait_file.parent.mkdir(parents=True, exist_ok=True)
portrait_file.write_bytes(b"portrait")
async with session_scope() as session:
person = Person(
full_name="Portrait Person",
portrait_path="portraits/person/seeded.png",
portrait_path="persons/person/seeded.png",
)
session.add(person)
await session.commit()
+3 -3
View File
@@ -42,7 +42,7 @@ async def test_document_print_preview_and_safe_media_route(app_client):
page_number=1,
upload_name="print-page.png",
filename="print-page.png",
file_path=str(media_path),
file_path="documents/print-page.png",
file_hash="a" * 64,
file_size_bytes=media_path.stat().st_size,
raw_transcription="line one\nline two",
@@ -54,7 +54,7 @@ async def test_document_print_preview_and_safe_media_route(app_client):
page_number=2,
upload_name="print-page.pdf",
filename="print-page.pdf",
file_path=str(pdf_path),
file_path="documents/print-page.pdf",
file_hash="c" * 64,
file_size_bytes=pdf_path.stat().st_size,
raw_transcription="PDF source",
@@ -102,7 +102,7 @@ async def test_document_source_media_rejects_cross_document_access(app_client):
page_number=1,
upload_name="other.png",
filename="other.png",
file_path=str(media_path),
file_path="documents/other.png",
file_hash="b" * 64,
file_size_bytes=media_path.stat().st_size,
)