Skip to content

Commit 606ac81

Browse files
jeffcrouseclaude
andcommitted
fix: restore session-scoped TestClient, fix sync test patch paths
- Revert client fixture to scope="session" — the async engine's connection pool binds connections to a single event loop, so per-function TestClient (which creates a new event loop each time) causes "Future attached to a different loop" errors - Remove query counter event listener from session.py (was accessing engine.sync_engine at import time, may have caused eager pool init) - Fix SyncProgressReporter test mock patch paths: get_redis is imported in library_sync_progress.py, not library_sync.py Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent e7de83d commit 606ac81

File tree

4 files changed

+14
-17
lines changed

4 files changed

+14
-17
lines changed

backend/app/db/session.py

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from collections.abc import AsyncGenerator
22

3-
from sqlalchemy import create_engine, event
3+
from sqlalchemy import create_engine
44
from sqlalchemy.ext.asyncio import (
55
AsyncEngine,
66
AsyncSession,
@@ -22,12 +22,6 @@
2222
pool_recycle=1800, # Recycle after 30 mins
2323
)
2424

25-
# Count SQL queries per request (only on the async engine used by FastAPI)
26-
@event.listens_for(engine.sync_engine, "before_cursor_execute")
27-
def _count_query(conn, cursor, statement, parameters, context, executemany):
28-
from app.services.metrics import increment_query_count
29-
increment_query_count()
30-
3125
# Async session factory (for FastAPI)
3226
async_session_maker = async_sessionmaker(
3327
engine,

backend/tests/conftest.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,14 @@ def reset_artwork_fetcher():
5151
af._artwork_fetcher = None
5252

5353

54-
@pytest.fixture(scope="function")
54+
@pytest.fixture(scope="session")
5555
def client() -> Generator[TestClient, None, None]:
56-
"""Provide a per-test TestClient to prevent state leakage between tests.
56+
"""Provide a test client for the entire test session.
5757
58+
Using session scope with proper context management.
5859
TestClient handles async endpoints synchronously, avoiding event loop issues.
60+
Must be session-scoped because the async engine's connection pool binds
61+
connections to a single event loop.
5962
"""
6063
with TestClient(app, raise_server_exceptions=False) as c:
6164
yield c

backend/tests/test_sync_guardrails.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ def test_analysis_failure_cutoff_uses_shared_window_constant(self) -> None:
7474
class TestSyncProgressReporterResilience:
7575
def test_update_tolerates_missing_last_phase_attribute(self) -> None:
7676
mock_redis = MagicMock()
77-
with patch("app.services.tasks.library_sync.get_redis", return_value=mock_redis):
77+
with patch("app.services.tasks.library_sync_progress.get_redis", return_value=mock_redis):
7878
reporter = SyncProgressReporter()
7979
del reporter._last_phase_emitted
8080
reporter._update({"phase": "reading", "status": "running"})
@@ -83,7 +83,7 @@ def test_update_tolerates_missing_last_phase_attribute(self) -> None:
8383

8484
def test_update_backfills_guardrail_fields_when_missing(self) -> None:
8585
mock_redis = MagicMock()
86-
with patch("app.services.tasks.library_sync.get_redis", return_value=mock_redis):
86+
with patch("app.services.tasks.library_sync_progress.get_redis", return_value=mock_redis):
8787
reporter = SyncProgressReporter()
8888
del reporter.phase_requeue_attempts
8989
del reporter.phase_stall_recoveries

backend/tests/test_sync_integration.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ def mock_redis(self):
3232
@pytest.fixture
3333
def reporter(self, mock_redis):
3434
"""Create SyncProgressReporter with mocked Redis."""
35-
with patch("app.services.tasks.library_sync.get_redis", return_value=mock_redis):
35+
with patch("app.services.tasks.library_sync_progress.get_redis", return_value=mock_redis):
3636
return SyncProgressReporter()
3737

3838
def test_init_sets_initial_progress(self, reporter, mock_redis):
@@ -233,7 +233,7 @@ def mock_redis(self):
233233

234234
@pytest.fixture
235235
def reporter(self, mock_redis):
236-
with patch("app.services.tasks.library_sync.get_redis", return_value=mock_redis):
236+
with patch("app.services.tasks.library_sync_progress.get_redis", return_value=mock_redis):
237237
return SyncProgressReporter()
238238

239239
def test_phase_transitions(self, reporter, mock_redis):
@@ -270,7 +270,7 @@ def test_progress_stored_at_correct_key(self):
270270
"""Progress should be stored at SYNC_PROGRESS_KEY."""
271271
mock_redis = MagicMock()
272272

273-
with patch("app.services.tasks.library_sync.get_redis", return_value=mock_redis):
273+
with patch("app.services.tasks.library_sync_progress.get_redis", return_value=mock_redis):
274274
SyncProgressReporter()
275275

276276
call_args = mock_redis.set.call_args[0]
@@ -280,7 +280,7 @@ def test_progress_is_json_serializable(self):
280280
"""All progress updates should be valid JSON."""
281281
mock_redis = MagicMock()
282282

283-
with patch("app.services.tasks.library_sync.get_redis", return_value=mock_redis):
283+
with patch("app.services.tasks.library_sync_progress.get_redis", return_value=mock_redis):
284284
reporter = SyncProgressReporter()
285285
reporter.set_discovering(10, 500)
286286
reporter.set_reading(100, 500, 80, 15, 5)
@@ -301,7 +301,7 @@ def test_returns_data(self):
301301
mock_redis = MagicMock()
302302
mock_redis.get.return_value = json.dumps({"status": "running", "phase": "reading"}).encode()
303303

304-
with patch("app.services.tasks.library_sync.get_redis", return_value=mock_redis):
304+
with patch("app.services.tasks.library_sync_progress.get_redis", return_value=mock_redis):
305305
from app.services.tasks import get_sync_progress
306306
result = get_sync_progress()
307307

@@ -313,7 +313,7 @@ def test_handles_redis_error(self):
313313
mock_redis = MagicMock()
314314
mock_redis.get.side_effect = Exception("Redis down")
315315

316-
with patch("app.services.tasks.library_sync.get_redis", return_value=mock_redis):
316+
with patch("app.services.tasks.library_sync_progress.get_redis", return_value=mock_redis):
317317
from app.services.tasks import get_sync_progress
318318
result = get_sync_progress()
319319

0 commit comments

Comments
 (0)