"""Shared pytest fixtures. Integration/API tests need a real PostgreSQL instance (JSONB, trigram/FTS indexes, and native enums are Postgres-specific — see docs/database.md) and Redis. Point `TEST_DATABASE_URL` / `TEST_REDIS_URL` at throwaway instances, e.g. via `docker compose up -d postgres redis` and a separate test database: createdb -h localhost -U aurora aurora_core_test Unit tests (tests/unit/) never touch either and always run offline. """ from __future__ import annotations import os from collections.abc import AsyncGenerator import pytest import pytest_asyncio from httpx import ASGITransport, AsyncClient from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine TEST_DATABASE_URL = os.environ.get( "TEST_DATABASE_URL", "postgresql+asyncpg://aurora:aurora@localhost:5432/aurora_core_test" ) TEST_REDIS_URL = os.environ.get("TEST_REDIS_URL", "redis://localhost:6379/15") os.environ.setdefault("DATABASE_URL", TEST_DATABASE_URL) os.environ.setdefault("REDIS_URL", TEST_REDIS_URL) os.environ.setdefault("AURORA_ENV", "test") os.environ.setdefault("AURORA_SECRET_KEY", "test-secret") os.environ.setdefault("AURORA_ADMIN_API_KEYS", "test-admin-key") def _requires_postgres() -> bool: return "--no-db" not in os.environ.get("PYTEST_ADDOPTS", "") @pytest_asyncio.fixture(scope="session") async def engine(): from sqlalchemy import text from app.models import Base eng = create_async_engine(TEST_DATABASE_URL) try: async with eng.begin() as conn: # Tests build schema straight from ORM metadata for speed and # to catch model/migration drift; docs/database.md notes this # is deliberately *not* how the running application migrates. await conn.execute(text("CREATE EXTENSION IF NOT EXISTS pg_trgm")) await conn.run_sync(Base.metadata.create_all) except Exception as exc: # pragma: no cover - environment guard pytest.skip(f"PostgreSQL not reachable at {TEST_DATABASE_URL}: {exc}") yield eng async with eng.begin() as conn: await conn.run_sync(Base.metadata.drop_all) await eng.dispose() @pytest_asyncio.fixture async def db_session(engine) -> AsyncGenerator[AsyncSession, None]: connection = await engine.connect() transaction = await connection.begin() session_factory = async_sessionmaker(bind=connection, expire_on_commit=False) session = session_factory() yield session await session.close() await transaction.rollback() await connection.close() @pytest_asyncio.fixture async def client(db_session: AsyncSession) -> AsyncGenerator[AsyncClient, None]: from app.core.database import get_db from app.main import app async def _override_get_db(): yield db_session app.dependency_overrides[get_db] = _override_get_db transport = ASGITransport(app=app) async with AsyncClient(transport=transport, base_url="http://testserver") as ac: yield ac app.dependency_overrides.clear()