From 530ce553b250b4a505619ce2b7fee06e752b172e Mon Sep 17 00:00:00 2001 From: NGHTBOY Date: Fri, 26 Jun 2026 12:26:30 +0200 Subject: [PATCH] fix(tests): pin pytest-asyncio to a session-scoped event loop MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pytest-asyncio 1.x ignores the old session-scoped `event_loop` fixture and defaults both fixtures and tests to function-scoped loops (`asyncio_default_fixture_loop_scope=None`, `asyncio_default_test_loop_scope= function`). The integration suite shares a session-scoped engine whose in-memory SQLite (aiosqlite) connection is bound to the loop it was created on; with per-test function loops that connection is used from a different, already-closed loop. On Linux CI this surfaced as a cascade of `RuntimeError: Event loop is closed` / `sqlite3.OperationalError: no such table: users` across ~400 integration tests (201 failed + 202 errors), while macOS happened to tolerate it — so the suite passed locally but red-gated the Heroku auto-deploy, which only fires on a green CI run on main. - Set `asyncio_default_fixture_loop_scope = "session"` and `asyncio_default_test_loop_scope = "session"` so the shared engine and every test run on one session loop (restores the pre-1.x behaviour). - Drop the now-ignored custom `event_loop` fixture (and its now-unused `asyncio` / `pytest` imports). Verified: full backend suite 4494 passed locally, 0 "no such table", 0 "Event loop is closed". Co-Authored-By: Claude Opus 4.8 (1M context) --- backend/pyproject.toml | 10 ++++++++++ backend/tests/integration/conftest.py | 9 --------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/backend/pyproject.toml b/backend/pyproject.toml index 0dabe12..98a8aa2 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -95,6 +95,16 @@ no_implicit_optional = true [tool.pytest.ini_options] asyncio_mode = "auto" +# pytest-asyncio 1.x ignores the old session-scoped ``event_loop`` fixture and +# defaults fixtures + tests to function-scoped loops. The integration suite +# shares a session-scoped engine whose in-memory SQLite (aiosqlite) connection +# is bound to the loop it was created on; with function-scoped test loops that +# connection is used from a different/closed loop → "Event loop is closed" / +# "no such table: users" cascades (Linux CI; macOS happened to tolerate it). +# Pin both fixtures and tests to one session loop so the shared engine stays +# valid for every test — restoring the pre-1.x behaviour. +asyncio_default_fixture_loop_scope = "session" +asyncio_default_test_loop_scope = "session" testpaths = ["tests"] markers = [ "unit: unit tests (deselect with '-m \"not unit\"')", diff --git a/backend/tests/integration/conftest.py b/backend/tests/integration/conftest.py index c9c3c03..a871bce 100644 --- a/backend/tests/integration/conftest.py +++ b/backend/tests/integration/conftest.py @@ -4,11 +4,9 @@ dependency so every endpoint hits an actual DB instead of mocks. """ -import asyncio import uuid from collections.abc import AsyncGenerator -import pytest import pytest_asyncio from httpx import ASGITransport, AsyncClient from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine @@ -51,13 +49,6 @@ from app.models.base import Base -@pytest.fixture(scope="session") -def event_loop(): - loop = asyncio.new_event_loop() - yield loop - loop.close() - - @pytest_asyncio.fixture(scope="session") async def engine(): from app.models.base import enable_sqlite_fk