"""Shared fixtures for UI integration tests.""" from __future__ import annotations 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, 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) import asyncio 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[..., 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" 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