"""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