Set up backend test infrastructure

Add pytest fixtures (engine, db_session, client) with session-scoped
event loop to avoid asyncpg loop mismatch errors. Smoke tests verify
all three main API endpoints return empty results on a clean DB.
Test DB provided by docker-compose.test.yml on port 5433.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-21 12:35:22 +01:00
parent 16f9e68821
commit b0ac3714a9
6 changed files with 782 additions and 19 deletions

61
backend/tests/conftest.py Normal file
View File

@@ -0,0 +1,61 @@
import os
import pytest
from httpx import ASGITransport, AsyncClient
from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine
import app.models # noqa: F401 — ensures all models register with Base.metadata
from app.core.database import Base, get_session
from app.main import app
TEST_DATABASE_URL = os.getenv(
"TEST_DATABASE_URL",
"postgresql+asyncpg://postgres:postgres@localhost:5433/nuzlocke_test",
)
@pytest.fixture(scope="session")
async def engine():
"""Create the test engine and schema once for the entire session."""
eng = create_async_engine(TEST_DATABASE_URL, echo=False)
async with eng.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
yield eng
async with eng.begin() as conn:
await conn.run_sync(Base.metadata.drop_all)
await eng.dispose()
@pytest.fixture
async def db_session(engine):
"""
Provide a database session for a single test.
Overrides the FastAPI get_session dependency so endpoint handlers use the
same session. Truncates all tables after the test to isolate state.
"""
session_factory = async_sessionmaker(engine, expire_on_commit=False)
session = session_factory()
async def override_get_session():
yield session
app.dependency_overrides[get_session] = override_get_session
yield session
await session.close()
app.dependency_overrides.clear()
async with engine.begin() as conn:
for table in reversed(Base.metadata.sorted_tables):
await conn.execute(table.delete())
@pytest.fixture
async def client(db_session):
"""Async HTTP client wired to the FastAPI app with the test database session."""
async with AsyncClient(
transport=ASGITransport(app=app), base_url="http://test"
) as ac:
yield ac

View File

@@ -0,0 +1,31 @@
"""Smoke tests that verify the test infrastructure is working correctly."""
async def test_games_endpoint_returns_empty_list(client):
"""Games endpoint returns an empty list on a clean database."""
response = await client.get("/api/v1/games")
assert response.status_code == 200
assert response.json() == []
async def test_runs_endpoint_returns_empty_list(client):
"""Runs endpoint returns an empty list on a clean database."""
response = await client.get("/api/v1/runs")
assert response.status_code == 200
assert response.json() == []
async def test_pokemon_endpoint_returns_empty_list(client):
"""Pokemon endpoint returns paginated empty result on a clean database."""
response = await client.get("/api/v1/pokemon")
assert response.status_code == 200
data = response.json()
assert data["items"] == []
assert data["total"] == 0
async def test_database_isolation_between_tests(client):
"""Confirm state from previous tests does not leak into this one."""
response = await client.get("/api/v1/games")
assert response.status_code == 200
assert response.json() == []