"""Integration test fixtures using testcontainers for a real Postgres.""" import pytest import pytest_asyncio from httpx import ASGITransport, AsyncClient from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine from rehearsalhub.db.models import Base from rehearsalhub.main import create_app @pytest.fixture(scope="session") def pg_container(): """Start a real Postgres container for the test session.""" from testcontainers.postgres import PostgresContainer with PostgresContainer("postgres:16-alpine") as pg: yield pg @pytest.fixture(scope="session") def pg_url(pg_container) -> str: url = pg_container.get_connection_url() return url.replace("postgresql+psycopg2://", "postgresql+asyncpg://").replace( "postgresql://", "postgresql+asyncpg://" ) @pytest_asyncio.fixture(scope="session") async def pg_engine(pg_url): engine = create_async_engine(pg_url, echo=False) async with engine.begin() as conn: await conn.run_sync(Base.metadata.create_all) yield engine await engine.dispose() @pytest_asyncio.fixture(scope="function") async def db_session(pg_engine) -> AsyncSession: """Each test gets its own connection with a rolled-back transaction.""" async with pg_engine.connect() as conn: trans = await conn.begin() session = AsyncSession(bind=conn, expire_on_commit=False) yield session await session.close() await trans.rollback() @pytest_asyncio.fixture(scope="function") async def client(db_session): """httpx AsyncClient with DB session dependency overridden.""" from rehearsalhub.db.engine import get_session app = create_app() app.dependency_overrides[get_session] = lambda: db_session async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as c: yield c app.dependency_overrides.clear() @pytest_asyncio.fixture(scope="function") async def auth_headers(client, db_session): """Register + login a test user, return Authorization headers.""" from tests.factories import create_member member = await create_member(db_session, email="auth@test.com") await db_session.commit() resp = await client.post( "/api/v1/auth/login", json={"email": "auth@test.com", "password": "testpassword123"} ) assert resp.status_code == 200, resp.text token = resp.json()["access_token"] return {"Authorization": f"Bearer {token}"} @pytest_asyncio.fixture(scope="function") async def current_member(db_session): from tests.factories import create_member member = await create_member(db_session, email=f"member_{__import__('uuid').uuid4().hex[:6]}@test.com") await db_session.commit() return member @pytest_asyncio.fixture(scope="function") async def auth_headers_for(client, db_session): """Factory fixture: given a member, return auth headers for them.""" async def _make(member): from rehearsalhub.services.auth import create_access_token token = create_access_token(str(member.id), member.email) return {"Authorization": f"Bearer {token}"} return _make