From 3b14098fc7a5ec5a117028d10a1798431510baf1 Mon Sep 17 00:00:00 2001 From: NGHTBOY Date: Fri, 26 Jun 2026 14:53:32 +0200 Subject: [PATCH] fix(tests): dispose the global app engine at session teardown The green CI run still surfaced two non-fatal `PytestUnhandledThreadExceptionWarning: RuntimeError: Event loop is closed` annotations. They come from aiosqlite's `_connection_worker_thread` calling `call_soon_threadsafe` on the session event loop after it has closed: tests that exercise the real `app.models.base.async_session_factory` open connections on the module-level `engine` that nothing tears down, so with a session-scoped loop those connections' daemon threads outlive the loop. Add a session-scoped autouse fixture that disposes the global engine before the loop closes, eliminating the thread-exception noise. Verified: full backend suite 4494 passed locally (warnings 16 -> 15). Co-Authored-By: Claude Opus 4.8 (1M context) --- backend/tests/conftest.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/backend/tests/conftest.py b/backend/tests/conftest.py index 2003eb5..491cd3c 100644 --- a/backend/tests/conftest.py +++ b/backend/tests/conftest.py @@ -2,6 +2,7 @@ from pathlib import Path import pytest +import pytest_asyncio from cryptography.fernet import Fernet os.environ.setdefault("DATABASE_URL", "sqlite+aiosqlite:///:memory:") @@ -12,6 +13,24 @@ os.environ.setdefault("AUTH_COOKIE_ENABLED", "false") +@pytest_asyncio.fixture(scope="session", autouse=True) +async def _dispose_app_engine(): + """Dispose the global app engine before the session event loop closes. + + Tests that exercise the real ``app.models.base.async_session_factory`` open + aiosqlite connections on the module-level ``engine`` that nothing else tears + down. With a session-scoped loop those connections' worker threads outlive + individual tests; when the loop finally closes, the daemon thread calls + ``call_soon_threadsafe`` on it and pytest surfaces a + ``PytestUnhandledThreadExceptionWarning: RuntimeError: Event loop is closed``. + Disposing here closes those connections while the loop is still running. + """ + yield + from app.models import base as base_mod + + await base_mod.engine.dispose() + + def pytest_collection_modifyitems(items: list[pytest.Item]) -> None: """Auto-apply 'unit' or 'integration' markers based on test file location.""" for item in items: