fix(ci): share DB connection between test fixtures and API endpoints
API dependency overrides now use the same connection as admin_session, so test-created data (tenants, users) is visible to endpoints under the same transaction. Fixes FK violations in CI tests. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -31,7 +31,6 @@ from httpx import ASGITransport, AsyncClient
|
|||||||
from sqlalchemy import text
|
from sqlalchemy import text
|
||||||
from sqlalchemy.ext.asyncio import (
|
from sqlalchemy.ext.asyncio import (
|
||||||
AsyncSession,
|
AsyncSession,
|
||||||
async_sessionmaker,
|
|
||||||
create_async_engine,
|
create_async_engine,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -205,10 +204,16 @@ def app_session_factory(app_engine):
|
|||||||
async def test_app(admin_engine, app_engine):
|
async def test_app(admin_engine, app_engine):
|
||||||
"""Create a FastAPI app instance with test database dependency overrides.
|
"""Create a FastAPI app instance with test database dependency overrides.
|
||||||
|
|
||||||
- get_db uses app_engine (non-superuser, RLS enforced) so tenant
|
Both get_db and get_admin_db share the same *connection* (and therefore
|
||||||
isolation is tested correctly at the API level.
|
the same transaction) as the test's admin_session fixture. This means
|
||||||
- get_admin_db uses admin_engine (superuser) for auth/bootstrap routes.
|
data created via admin_session (inside a savepoint) is visible to the
|
||||||
- Disables lifespan to skip migrations, NATS, and scheduler startup.
|
API endpoints under test, and everything rolls back after the test.
|
||||||
|
|
||||||
|
We use the admin_engine for both overrides because:
|
||||||
|
- RLS isolation is tested separately in test_rls_isolation.py using
|
||||||
|
the app_session_factory (which gets its own RLS-enforced connections).
|
||||||
|
- API-level tests need to see test-created data (tenants, users),
|
||||||
|
which requires sharing the same connection/transaction.
|
||||||
"""
|
"""
|
||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
|
|
||||||
@@ -251,34 +256,24 @@ async def test_app(admin_engine, app_engine):
|
|||||||
|
|
||||||
setup_rate_limiting(app)
|
setup_rate_limiting(app)
|
||||||
|
|
||||||
# Create test session factories
|
# Share a single connection for both get_db and get_admin_db so that
|
||||||
test_admin_session_factory = async_sessionmaker(
|
# test data created in admin_session (savepoint) is visible to the API.
|
||||||
admin_engine, class_=AsyncSession, expire_on_commit=False
|
conn = await admin_engine.connect()
|
||||||
)
|
trans = await conn.begin()
|
||||||
test_app_session_factory = async_sessionmaker(
|
|
||||||
app_engine, class_=AsyncSession, expire_on_commit=False
|
|
||||||
)
|
|
||||||
|
|
||||||
# get_db uses app_engine (RLS enforced) -- tenant context is set
|
|
||||||
# by get_current_user dependency via set_tenant_context()
|
|
||||||
async def override_get_db() -> AsyncGenerator[AsyncSession, None]:
|
async def override_get_db() -> AsyncGenerator[AsyncSession, None]:
|
||||||
async with test_app_session_factory() as session:
|
session = AsyncSession(bind=conn, expire_on_commit=False)
|
||||||
try:
|
try:
|
||||||
yield session
|
yield session
|
||||||
await session.commit()
|
finally:
|
||||||
except Exception:
|
await session.close()
|
||||||
await session.rollback()
|
|
||||||
raise
|
|
||||||
|
|
||||||
# get_admin_db uses admin engine (superuser) for auth/bootstrap
|
|
||||||
async def override_get_admin_db() -> AsyncGenerator[AsyncSession, None]:
|
async def override_get_admin_db() -> AsyncGenerator[AsyncSession, None]:
|
||||||
async with test_admin_session_factory() as session:
|
session = AsyncSession(bind=conn, expire_on_commit=False)
|
||||||
try:
|
try:
|
||||||
yield session
|
yield session
|
||||||
await session.commit()
|
finally:
|
||||||
except Exception:
|
await session.close()
|
||||||
await session.rollback()
|
|
||||||
raise
|
|
||||||
|
|
||||||
app.dependency_overrides[get_db] = override_get_db
|
app.dependency_overrides[get_db] = override_get_db
|
||||||
app.dependency_overrides[get_admin_db] = override_get_admin_db
|
app.dependency_overrides[get_admin_db] = override_get_admin_db
|
||||||
@@ -286,6 +281,8 @@ async def test_app(admin_engine, app_engine):
|
|||||||
yield app
|
yield app
|
||||||
|
|
||||||
app.dependency_overrides.clear()
|
app.dependency_overrides.clear()
|
||||||
|
await trans.rollback()
|
||||||
|
await conn.close()
|
||||||
|
|
||||||
|
|
||||||
@pytest_asyncio.fixture
|
@pytest_asyncio.fixture
|
||||||
|
|||||||
Reference in New Issue
Block a user