From 7a0dc93270c5f8227c15c98eec4fc1a97f8c9add Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Sat, 29 Aug 2026 18:41:18 -0400 Subject: [PATCH] fix(tests): the rule-version round trip must not touch Postgres after dispose (#3240) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `_dispose_engine` is a usefixtures entry, so it sets up AFTER an autouse fixture and tears down BEFORE it. The purge running after this file's `yield` therefore opened a fresh pooled connection that the closing loop immediately orphaned, and the next test to touch Postgres died on "Future attached to a different loop" — two of this file's own tests and test_run_maintenance_vacuums_real_tables, which shares nothing with it but the engine. The autouse fixture is setup-only now, matching its sibling in test_integration_backup_note_roundtrip.py, and the cleanup moved into `restored`, whose teardown runs while the engine is still live. Co-Authored-By: Claude Opus 5 --- ...tegration_backup_rule_version_roundtrip.py | 26 +++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/tests/test_integration_backup_rule_version_roundtrip.py b/tests/test_integration_backup_rule_version_roundtrip.py index 7e5d3c1..4a4b905 100644 --- a/tests/test_integration_backup_rule_version_roundtrip.py +++ b/tests/test_integration_backup_rule_version_roundtrip.py @@ -67,10 +67,16 @@ async def _purge_restored() -> None: @pytest_asyncio.fixture(autouse=True) async def _no_leftovers(): """The usernames are fixed, so a previous failed run would leave rows that - make the fixtures below pick the wrong user — or hit `.one()` with two.""" - await _purge_restored() - await _purge_books(OWNER_USERNAME) - yield + make the fixtures below pick the wrong user — or hit `.one()` with two. + + SETUP ONLY, and that is not a stylistic choice. `_dispose_engine` is a + usefixtures entry, so it sets up AFTER this autouse one and therefore + tears down BEFORE it. Any database call here after a `yield` would open a + fresh pooled connection that the closing loop then orphans, and the next + test to touch Postgres dies on "Future attached to a different loop" — + including tests in other files. Cleanup belongs in the fixtures below, + whose teardowns run while the engine is still live. + """ await _purge_restored() await _purge_books(OWNER_USERNAME) @@ -173,6 +179,12 @@ async def source(): @pytest_asyncio.fixture async def restored(source): + """Runs the real restore, then hands back the new rows. + + The restore mints a NEW user from the payload, so the restored corpus is + entirely separate from the source one — which is what makes the id + assertions below able to fail. + """ await backup.restore_full_backup(source["payload"]) async with async_session() as s: user = (await s.execute( @@ -192,7 +204,11 @@ async def restored(source): select(RuleVersion).where(RuleVersion.rule_id == rule.id) .order_by(RuleVersion.id) )).scalars().all() - return {"user": user, "rule": rule, "versions": versions, "source": source} + yield {"user": user, "rule": rule, "versions": versions, "source": source} + + # Here rather than in the autouse fixture: this teardown still runs while + # the engine is live. See _no_leftovers. + await _purge_restored() async def test_both_snapshots_come_back(restored):