"""Pytest-Fixtures. Nutzt eine echte PostgreSQL-Testdatenbank (Plan §5.2: kein SQLite, wegen JSONB / ARRAY / echten Constraints). URL ueber ``OAMC_DATABASE_URL`` oder Default ``postgresql+psycopg://linus:linus@localhost:5432/oamc_test``. Jeder Test laeuft in einer aussen zurueckgerollten Transaktion — die DB bleibt sauber, Migrationen laufen einmalig. """ from __future__ import annotations import os import subprocess import sys from pathlib import Path import pytest from sqlalchemy import create_engine, text from sqlalchemy.orm import Session os.environ.setdefault( "OAMC_DATABASE_URL", "postgresql+psycopg://linus:linus@localhost:5432/oamc_test" ) os.environ.setdefault("OAMC_SECRET_KEY", "test-secret-key-0123456789abcdef0123") os.environ.setdefault("OAMC_INGEST_API_KEYS", "test-key") PROJEKT_WURZEL = Path(__file__).resolve().parent.parent @pytest.fixture(scope="session") def _migriert() -> None: """Schema einmal pro Testlauf frisch aufbauen.""" url = os.environ["OAMC_DATABASE_URL"] admin = create_engine(url, isolation_level="AUTOCOMMIT") with admin.connect() as conn: conn.execute(text("DROP SCHEMA public CASCADE")) conn.execute(text("CREATE SCHEMA public")) admin.dispose() subprocess.run( [sys.executable, "-m", "alembic", "upgrade", "head"], cwd=str(PROJEKT_WURZEL), check=True, env={**os.environ}, ) @pytest.fixture(scope="session") def engine(_migriert): from oamc.config import get_settings eng = create_engine(get_settings().database_url, future=True) yield eng eng.dispose() @pytest.fixture def db(engine) -> Session: conn = engine.connect() trans = conn.begin() session = Session(bind=conn, join_transaction_mode="create_savepoint") try: yield session finally: session.close() trans.rollback() conn.close() @pytest.fixture def seed_2026(db): from oamc.seeds import seed_saison_2026 seed_saison_2026(db) db.flush() return db @pytest.fixture def client(engine): """TestClient mit auf die Testtransaktion gebundener DB-Session.""" from fastapi.testclient import TestClient from oamc.db import get_db from oamc.main import app conn = engine.connect() trans = conn.begin() session = Session(bind=conn, join_transaction_mode="create_savepoint") def _get_db_override(): # Kein Rollback bei HTTPException: die aeussere Testtransaktion raeumt auf. # Ein Rollback hier wuerde den Savepoint mitsamt Fixture-Daten verwerfen. yield session session.flush() app.dependency_overrides[get_db] = _get_db_override with TestClient(app) as c: c.db = session # type: ignore[attr-defined] yield c app.dependency_overrides.clear() session.close() trans.rollback() conn.close()