"""Gemeinsame Test-Fixtures. Nutzt eine separate Testdatenbank.""" import os # Muss vor dem ersten Import der Anwendung gesetzt sein, da die Settings gecacht werden. os.environ.setdefault( "DATABASE_URL", os.environ.get( "TEST_DATABASE_URL", "postgresql+asyncpg://moneyfy:moneyfy@127.0.0.1:5432/moneyfy_test", ), ) os.environ.setdefault("SECRET_KEY", "test-secret-key") os.environ.setdefault("ENVIRONMENT", "test") from collections.abc import AsyncGenerator import pytest from httpx import ASGITransport, AsyncClient from sqlalchemy.ext.asyncio import ( AsyncSession, async_sessionmaker, create_async_engine, ) from app.core.config import settings from app.db.session import get_session from app.main import app as fastapi_app from app.models import Base @pytest.fixture(scope="session") def anyio_backend() -> str: return "asyncio" @pytest.fixture(scope="session") async def engine(): """Legt das Schema einmal pro Testlauf frisch an.""" test_engine = create_async_engine(str(settings.database_url), poolclass=None) async with test_engine.begin() as conn: await conn.run_sync(Base.metadata.drop_all) await conn.run_sync(Base.metadata.create_all) yield test_engine await test_engine.dispose() @pytest.fixture async def session(engine) -> AsyncGenerator[AsyncSession, None]: """Eine Session je Test, am Ende wird zurückgerollt.""" connection = await engine.connect() transaction = await connection.begin() # `create_savepoint` sorgt dafür, dass session.commit() nur den Savepoint freigibt # und der abschließende Rollback wirklich alle Testdaten entfernt. maker = async_sessionmaker( bind=connection, expire_on_commit=False, autoflush=False, join_transaction_mode="create_savepoint", ) async with maker() as db_session: yield db_session await transaction.rollback() await connection.close() @pytest.fixture async def client(session: AsyncSession) -> AsyncGenerator[AsyncClient, None]: """HTTP-Client gegen die App, alle Requests laufen in der Test-Transaktion.""" async def _override() -> AsyncGenerator[AsyncSession, None]: yield session fastapi_app.dependency_overrides[get_session] = _override transport = ASGITransport(app=fastapi_app) async with AsyncClient(transport=transport, base_url="http://test") as http_client: yield http_client fastapi_app.dependency_overrides.clear()