generated from john/python-template
This commit is contained in:
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
|
||||
@@ -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
@@ -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():
|
||||
|
||||
@@ -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"
|
||||
@@ -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
@@ -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):
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user