Files
transcription/tests/ui/conftest.py
T

135 lines
4.5 KiB
Python

"""Shared fixtures for UI integration tests."""
from __future__ import annotations
import asyncio
from collections.abc import AsyncGenerator, Callable
from datetime import UTC, datetime
from pathlib import Path
from typing import Awaitable
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, 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) -> 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(
openrouter_api_key="test-key",
database=SqliteSettings(path=":memory:"),
environment="test",
bootstrap_schema_on_startup=True,
upload_dir=tmp_path / "uploads",
prompt_dir=tmp_path / "prompts",
)
app = create_app()
app.state.runtime = initialize_database_runtime(settings=settings)
asyncio.run(create_all(engine=app.state.runtime.engine))
with TestClient(app) as client:
yield app, client
@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_asyncio.fixture
async def seed_job(app_client: tuple[FastAPI, TestClient]) -> Callable[..., Awaitable[UUID]]:
"""Return an async helper for seeding a Document -> Job -> Source tuple."""
app, _ = app_client
fixtures_dir = Path(__file__).resolve().parents[1] / "fixtures" / "images" / "valid"
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: str | None = None,
source_file: Path | None = None,
) -> 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())
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()
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 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
return _seed