"""Test bootstrap. CI provides DATABASE_URL (a Postgres service container). Locally, when it is unset, a throwaway Postgres is started with `pgserver` so `pytest` needs no Docker. Set REQUIRE_DB=1 to turn a missing database into a failure instead of a skip (CI does this). DATABASE_URL must be settled before anything imports `app`, because `app.config.settings` and the engine in `app.db` are created at import time. """ import os import tempfile from collections.abc import AsyncIterator, Iterator from pathlib import Path from typing import TYPE_CHECKING import pytest from httpx import ASGITransport, AsyncClient if TYPE_CHECKING: from alembic.config import Config API_DIR = Path(__file__).resolve().parents[1] _pg_server = None # keeps the pgserver handle alive for the whole session def _resolve_database_url() -> str | None: global _pg_server if url := os.environ.get("DATABASE_URL"): return url try: import pgserver except ImportError: return None _pg_server = pgserver.get_server(tempfile.mkdtemp(prefix="rarelens-pg-"), cleanup_mode="delete") socket_dir = _pg_server.get_uri().split("host=", 1)[1] return f"postgresql+asyncpg://postgres@/postgres?host={socket_dir}" DATABASE_URL = _resolve_database_url() if DATABASE_URL: os.environ["DATABASE_URL"] = DATABASE_URL @pytest.fixture(scope="session") def alembic_cfg() -> "Config": if not DATABASE_URL: if os.environ.get("REQUIRE_DB"): pytest.fail("REQUIRE_DB is set but no database is available") pytest.skip("no DATABASE_URL and pgserver is not installed") from alembic.config import Config cfg = Config(str(API_DIR / "alembic.ini")) cfg.set_main_option("script_location", str(API_DIR / "alembic")) return cfg @pytest.fixture(scope="session") def migrated_db(alembic_cfg: "Config") -> Iterator[None]: from alembic import command command.upgrade(alembic_cfg, "head") yield @pytest.fixture async def db(migrated_db: None) -> AsyncIterator[None]: from sqlalchemy import text from app.db import engine async with engine.begin() as conn: await conn.execute( text("TRUNCATE samples, jobs, variants, predictions RESTART IDENTITY CASCADE") ) yield @pytest.fixture async def client() -> AsyncIterator[AsyncClient]: from app.main import app async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as c: yield c