Revamped the Documents, People, & Jobs too.

This commit is contained in:
Jim Lancaster
2026-08-05 13:05:41 -05:00
parent 72bc96ab3a
commit fd3ca60008
15 changed files with 1719 additions and 1338 deletions
+78 -87
View File
@@ -2,39 +2,35 @@
from __future__ import annotations
import asyncio
from collections.abc import Callable
from collections.abc import Generator
from datetime import UTC
from datetime import datetime
from collections.abc import AsyncGenerator, Callable
from datetime import UTC, datetime
from pathlib import Path
from typing import Any
from uuid import UUID
import pytest
import pytest_asyncio
from fastapi import FastAPI
from fastapi.testclient import TestClient
from sqlmodel import delete
from transcription.app import create_app
from transcription.config import Settings
from transcription.config import SqliteSettings
from transcription.db import create_all
from transcription.db import initialize_database_runtime
from transcription.db import session_scope
from transcription.db.models import Document
from transcription.db.models import DocumentPerson
from transcription.db.models import Job
from transcription.db.models import JobSource
from transcription.db.models import JobSourceStatus
from transcription.db.models import JobStatus
from transcription.db.models import Person
from transcription.db.models import Source
RevisionSeed = str
from transcription.config import Settings, SqliteSettings
from transcription.db import create_all, initialize_database_runtime, session_scope
from transcription.db.models import (
Document,
DocumentPerson,
Job,
JobSource,
JobSourceStatus,
JobStatus,
Person,
Source,
)
@pytest.fixture(scope="session")
def app_client(tmp_path_factory: pytest.TempPathFactory) -> Generator[tuple[FastAPI, TestClient]]:
def app_client(tmp_path_factory: pytest.TempPathFactory) -> AsyncGenerator[tuple[FastAPI, TestClient], None]:
"""Provide a real application and test client backed by in-memory SQLite."""
tmp_path = tmp_path_factory.mktemp("ui")
settings = Settings(
@@ -48,94 +44,89 @@ def app_client(tmp_path_factory: pytest.TempPathFactory) -> Generator[tuple[Fast
app = create_app()
app.state.runtime = initialize_database_runtime(settings=settings)
import asyncio
asyncio.run(create_all(engine=app.state.runtime.engine))
with TestClient(app) as client:
yield app, client
@pytest.fixture(autouse=True)
def clear_ui_database(app_client: tuple[FastAPI, TestClient]) -> None:
"""Reset UI-facing tables before each test for isolation."""
app, _ = app_client
async def _clear() -> None:
async with session_scope() as session:
await session.exec(delete(JobSource))
await session.exec(delete(DocumentPerson))
await session.exec(delete(Source))
await session.exec(delete(Job))
await session.exec(delete(Document))
await session.exec(delete(Person))
await session.commit()
asyncio.run(_clear())
@pytest_asyncio.fixture(autouse=True)
async def clear_ui_database(app_client: tuple[FastAPI, TestClient]) -> None:
"""Reset UI-facing tables asynchronously before each test for isolation."""
async with session_scope() as session:
await session.exec(delete(JobSource))
await session.exec(delete(DocumentPerson))
await session.exec(delete(Source))
await session.exec(delete(Job))
await session.exec(delete(Document))
await session.exec(delete(Person))
await session.commit()
@pytest.fixture
def seed_job(app_client: tuple[FastAPI, TestClient]) -> Callable[..., UUID]:
"""Return a helper for inserting a document/job/source/(optional revision) tuple."""
@pytest_asyncio.fixture
async def seed_job(app_client: tuple[FastAPI, TestClient]) -> Callable[..., AsyncGenerator[UUID, None]]:
"""Return an async factory helper for seeding a Document -> Job -> Source tuple."""
app, _ = app_client
fixtures_dir = Path(__file__).resolve().parents[1] / "fixtures" / "images" / "valid"
def _seed(
async def _seed(
*,
filename: str = "sample.pdf",
status: JobStatus = JobStatus.TRANSCRIBED,
transcription_text: str | None = "Sample transcript text",
error_detail: str | None = None,
revision_text: RevisionSeed | None = None,
revision_text: str | None = None,
source_file: Path | None = None,
) -> UUID:
async def _insert() -> UUID:
async with session_scope() as session:
stored_path = app.state.settings.upload_dir / filename
stored_path.parent.mkdir(parents=True, exist_ok=True)
source_path = source_file or fixtures_dir / "small_png.png"
stored_path.write_bytes(source_path.read_bytes())
async with session_scope() as session:
stored_path = app.state.settings.upload_dir / filename
stored_path.parent.mkdir(parents=True, exist_ok=True)
source_path = source_file or fixtures_dir / "small_png.png"
stored_path.write_bytes(source_path.read_bytes())
document = Document(name=filename)
session.add(document)
await session.flush()
document = Document(name=filename)
session.add(document)
await session.flush()
job = Job(
document_id=document.id,
status=status,
retry_count=0,
provider="openrouter",
model="google/gemini-2.5-flash",
prompt_name="transcribe_document.md",
)
session.add(job)
await session.flush()
job = Job(
document_id=document.id,
status=status,
retry_count=0,
provider="openrouter",
model="google/gemini-2.5-flash",
prompt_name="transcribe_document.md",
)
session.add(job)
await session.flush()
source = Source(
document_id=document.id,
upload_name=filename,
filename=filename,
file_path=str(stored_path),
)
session.add(source)
await session.flush()
source = Source(
document_id=document.id,
upload_name=filename,
filename=filename,
file_path=str(stored_path),
)
session.add(source)
await session.flush()
if transcription_text is not None or error_detail is not None:
session.add(
JobSource(
job_id=job.id,
source_id=source.id,
status=JobSourceStatus.TRANSCRIBED if transcription_text is not None else JobSourceStatus.FAILED,
raw_transcription=transcription_text,
error_detail=error_detail,
)
if transcription_text is not None or error_detail is not None:
session.add(
JobSource(
job_id=job.id,
source_id=source.id,
status=JobSourceStatus.TRANSCRIBED if transcription_text is not None else JobSourceStatus.FAILED,
raw_transcription=transcription_text,
error_detail=error_detail,
)
)
if revision_text is not None:
source.revised_text = revision_text
source.date_revised = datetime.now(UTC)
session.add(source)
if revision_text is not None:
source.revised_text = revision_text
source.date_revised = datetime.now(UTC)
session.add(source)
await session.commit()
return job.id
await session.commit()
return job.id
return asyncio.run(_insert())
return _seed
return _seed