Files
moneyfy/backend/tests/conftest.py
T
moneyfyandClaude Opus 5 0b06775be3 feat: Projektfundament mit Datenmodell, Migrationen und Seed
- Repo-Struktur für Backend, Frontend, Dokumentation und Gitea-Workflows
- FastAPI-Skeleton mit pydantic-settings und RFC-7807-artigem Fehlerformat
- Vollständiges Datenmodell (15 Tabellen) als SQLAlchemy-2.0-Modelle
- Alembic gegen die Async-Engine inklusive Erstmigration
- Idempotenter Seed für den deutschen Kategoriebaum und Standardregeln
- Endpunkte /api/health und /api/version
- pytest-Infrastruktur mit transaktionsisolierten Fixtures

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014e7t8UpmoVNMtWivY5LiSH
2026-09-09 13:12:08 +02:00

79 lines
2.4 KiB
Python

"""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()