From 4f31890bdec8d2fd31076c8d67f8e79d464bc7aa Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Sun, 14 Jun 2026 19:24:09 -0400 Subject: [PATCH] test(ci): dispose engine between integration tests (per-loop pool) First integration run proved the lane works (run_maintenance test passed against real Postgres), but the health test failed with 'Future attached to a different loop': pytest-asyncio uses a fresh loop per test while the app's module-level engine pools a connection from the prior test's loop. Dispose the engine in each test's teardown so the next test starts with an empty pool on its own loop. Co-Authored-By: Claude Opus 4.8 --- tests/test_integration_db_maintenance.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/test_integration_db_maintenance.py b/tests/test_integration_db_maintenance.py index cff8cf3..7cba7b1 100644 --- a/tests/test_integration_db_maintenance.py +++ b/tests/test_integration_db_maintenance.py @@ -7,7 +7,9 @@ connection path that unit mocks cannot: the un-awaited AttributeError, reporting 0/6) passes the unit suite but fails here. """ import pytest +import pytest_asyncio +from scribe.models import engine from scribe.services.db_maintenance import ( MAINTENANCE_TABLES, get_table_health, @@ -17,6 +19,19 @@ from scribe.services.db_maintenance import ( pytestmark = pytest.mark.integration +@pytest_asyncio.fixture(autouse=True) +async def _dispose_engine(): + """Dispose the app's module-level engine after each test. + + The engine pools asyncpg connections per event loop, but pytest-asyncio runs + each test on a fresh loop — so without this, test 2 gets handed test 1's + connection bound to a now-dead loop ("Future attached to a different loop"). + Disposing in the test's own loop teardown clears the pool cleanly. + """ + yield + await engine.dispose() + + @pytest.mark.asyncio async def test_run_maintenance_vacuums_real_tables(): summary = await run_maintenance()