From d30c4ab5225eae9167914f9e101c20a3f45fc73d Mon Sep 17 00:00:00 2001 From: Jason Staack Date: Sat, 14 Mar 2026 23:14:46 -0500 Subject: [PATCH] fix(ci): use shared admin_conn fixture for test transaction visibility Both admin_session and test_app now bind to the same connection (admin_conn), ensuring test-created data is visible to API endpoints. Co-Authored-By: Claude Opus 4.6 (1M context) --- backend/tests/integration/conftest.py | 62 +++++++++++++-------------- 1 file changed, 30 insertions(+), 32 deletions(-) diff --git a/backend/tests/integration/conftest.py b/backend/tests/integration/conftest.py index 11d64a7..4a3ce56 100644 --- a/backend/tests/integration/conftest.py +++ b/backend/tests/integration/conftest.py @@ -130,20 +130,30 @@ async def app_engine(): @pytest_asyncio.fixture -async def admin_session(admin_engine) -> AsyncGenerator[AsyncSession, None]: - """Per-test admin session with transaction rollback. +async def admin_conn(admin_engine): + """Shared admin connection with transaction rollback. - Each test gets a clean transaction that is rolled back after the test, - ensuring no state leakage between tests. + Both admin_session and test_app bind to the same connection so that + data created in the test (via admin_session) is visible to API + endpoints (via get_db / get_admin_db overrides). """ - async with admin_engine.connect() as conn: - trans = await conn.begin() - session = AsyncSession(bind=conn, expire_on_commit=False) - try: - yield session - finally: - await trans.rollback() - await session.close() + conn = await admin_engine.connect() + trans = await conn.begin() + try: + yield conn + finally: + await trans.rollback() + await conn.close() + + +@pytest_asyncio.fixture +async def admin_session(admin_conn) -> AsyncGenerator[AsyncSession, None]: + """Per-test admin session sharing the admin_conn transaction.""" + session = AsyncSession(bind=admin_conn, expire_on_commit=False) + try: + yield session + finally: + await session.close() @pytest_asyncio.fixture @@ -201,19 +211,12 @@ def app_session_factory(app_engine): @pytest_asyncio.fixture -async def test_app(admin_engine, app_engine): +async def test_app(admin_conn, app_engine): """Create a FastAPI app instance with test database dependency overrides. - Both get_db and get_admin_db share the same *connection* (and therefore - the same transaction) as the test's admin_session fixture. This means - data created via admin_session (inside a savepoint) is visible to the - 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. + Both get_db and get_admin_db bind to admin_conn (the shared connection + from admin_conn fixture). This means data created via admin_session + is visible to API endpoints, and everything rolls back after the test. """ from fastapi import FastAPI @@ -256,20 +259,17 @@ async def test_app(admin_engine, app_engine): setup_rate_limiting(app) - # Share a single connection for both get_db and get_admin_db so that - # test data created in admin_session (savepoint) is visible to the API. - conn = await admin_engine.connect() - trans = await conn.begin() - + # API endpoints bind to the same shared connection as admin_session + # so test-created data is visible across the transaction. async def override_get_db() -> AsyncGenerator[AsyncSession, None]: - session = AsyncSession(bind=conn, expire_on_commit=False) + session = AsyncSession(bind=admin_conn, expire_on_commit=False) try: yield session finally: await session.close() async def override_get_admin_db() -> AsyncGenerator[AsyncSession, None]: - session = AsyncSession(bind=conn, expire_on_commit=False) + session = AsyncSession(bind=admin_conn, expire_on_commit=False) try: yield session finally: @@ -281,8 +281,6 @@ async def test_app(admin_engine, app_engine): yield app app.dependency_overrides.clear() - await trans.rollback() - await conn.close() @pytest_asyncio.fixture