Skip to content

Commit 58ef8d7

Browse files
committed
test scope fixes
1 parent 2797283 commit 58ef8d7

File tree

6 files changed

+28
-31
lines changed

6 files changed

+28
-31
lines changed

.github/workflows/backend-ci.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ jobs:
4949
run: |
5050
cd backend
5151
uv run pytest tests/unit -v -rs \
52-
--cov=app --cov-branch \
52+
--cov=app \
5353
--cov-report=xml --cov-report=term
5454
5555
- name: Upload coverage to Codecov
@@ -146,7 +146,7 @@ jobs:
146146
cd backend
147147
uv run pytest tests/integration -v -rs \
148148
--ignore=tests/integration/k8s \
149-
--cov=app --cov-branch \
149+
--cov=app \
150150
--cov-report=xml --cov-report=term
151151
152152
- name: Upload coverage to Codecov
@@ -267,7 +267,7 @@ jobs:
267267
run: |
268268
cd backend
269269
uv run pytest tests/integration/k8s -v -rs \
270-
--cov=app --cov-branch \
270+
--cov=app \
271271
--cov-report=xml --cov-report=term
272272
273273
- name: Upload coverage to Codecov

backend/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ markers = [
202202
"performance: marks tests as performance tests"
203203
]
204204
asyncio_mode = "auto"
205-
asyncio_default_fixture_loop_scope = "function"
205+
asyncio_default_fixture_loop_scope = "session"
206206
log_cli = false
207207
log_cli_level = "ERROR"
208208
log_level = "ERROR"

backend/tests/conftest.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ def create_test_app():
126126

127127

128128
# ===== App without lifespan for tests =====
129-
@pytest_asyncio.fixture(scope="session", loop_scope="session")
129+
@pytest_asyncio.fixture(scope="session")
130130
async def app():
131131
"""Create FastAPI app once per session/worker to avoid Pydantic schema crashes."""
132132
application = create_test_app()
@@ -138,15 +138,15 @@ async def app():
138138
await container.close()
139139

140140

141-
@pytest_asyncio.fixture(scope="session", loop_scope="session")
141+
@pytest_asyncio.fixture(scope="session")
142142
async def app_container(app): # type: ignore[valid-type]
143143
"""Expose the Dishka container attached to the app."""
144144
container: AsyncContainer = app.state.dishka_container # type: ignore[attr-defined]
145145
return container
146146

147147

148148
# ===== Client (function-scoped for clean cookies per test) =====
149-
@pytest_asyncio.fixture(scope="function", loop_scope="session")
149+
@pytest_asyncio.fixture
150150
async def client(app) -> AsyncGenerator[httpx.AsyncClient, None]: # type: ignore[valid-type]
151151
# Use httpx with ASGI app directly
152152
# The app fixture already handles lifespan via LifespanManager
@@ -168,19 +168,19 @@ async def _container_scope(container: AsyncContainer):
168168
yield scope
169169

170170

171-
@pytest_asyncio.fixture(scope="function", loop_scope="session")
171+
@pytest_asyncio.fixture
172172
async def scope(app_container: AsyncContainer): # type: ignore[valid-type]
173173
async with _container_scope(app_container) as s:
174174
yield s
175175

176176

177-
@pytest_asyncio.fixture(scope="function", loop_scope="session")
177+
@pytest_asyncio.fixture
178178
async def db(scope) -> AsyncGenerator[Database, None]: # type: ignore[valid-type]
179179
database: Database = await scope.get(Database)
180180
yield database
181181

182182

183-
@pytest_asyncio.fixture(scope="function", loop_scope="session")
183+
@pytest_asyncio.fixture
184184
async def redis_client(scope) -> AsyncGenerator[redis.Redis, None]: # type: ignore[valid-type]
185185
client: redis.Redis = await scope.get(redis.Redis)
186186
yield client
@@ -222,7 +222,7 @@ def shared_admin_credentials():
222222
}
223223

224224

225-
@pytest_asyncio.fixture(scope="function", loop_scope="session")
225+
@pytest_asyncio.fixture
226226
async def shared_user(client: httpx.AsyncClient, shared_user_credentials):
227227
creds = shared_user_credentials
228228
# Always attempt to register; DB is wiped after each test
@@ -233,7 +233,7 @@ async def shared_user(client: httpx.AsyncClient, shared_user_credentials):
233233
return {**creds, "csrf_token": csrf, "headers": {"X-CSRF-Token": csrf}}
234234

235235

236-
@pytest_asyncio.fixture(scope="function", loop_scope="session")
236+
@pytest_asyncio.fixture
237237
async def shared_admin(client: httpx.AsyncClient, shared_admin_credentials):
238238
creds = shared_admin_credentials
239239
r = await client.post("/api/v1/auth/register", json=creds)
@@ -243,7 +243,7 @@ async def shared_admin(client: httpx.AsyncClient, shared_admin_credentials):
243243
return {**creds, "csrf_token": csrf, "headers": {"X-CSRF-Token": csrf}}
244244

245245

246-
@pytest_asyncio.fixture(scope="function", loop_scope="session")
246+
@pytest_asyncio.fixture
247247
async def another_user(client: httpx.AsyncClient):
248248
username = f"test_user_{uuid.uuid4().hex[:8]}"
249249
email = f"{username}@example.com"

backend/tests/fixtures/real_services.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ async def cleanup(self):
112112
await self.kafka_consumer.stop()
113113

114114

115-
@pytest_asyncio.fixture(loop_scope="session")
115+
@pytest_asyncio.fixture
116116
async def real_services(request) -> AsyncGenerator[TestServiceConnections, None]:
117117
"""
118118
Provides real service connections for testing.
@@ -130,7 +130,7 @@ async def real_services(request) -> AsyncGenerator[TestServiceConnections, None]
130130
await connections.cleanup()
131131

132132

133-
@pytest_asyncio.fixture(loop_scope="session")
133+
@pytest_asyncio.fixture
134134
async def real_mongodb(real_services: TestServiceConnections) -> Database:
135135
"""Get real MongoDB database for testing."""
136136
# Use MongoDB from docker-compose with auth
@@ -139,19 +139,19 @@ async def real_mongodb(real_services: TestServiceConnections) -> Database:
139139
)
140140

141141

142-
@pytest_asyncio.fixture(loop_scope="session")
142+
@pytest_asyncio.fixture
143143
async def real_redis(real_services: TestServiceConnections) -> redis.Redis:
144144
"""Get real Redis client for testing."""
145145
return await real_services.connect_redis()
146146

147147

148-
@pytest_asyncio.fixture(loop_scope="session")
148+
@pytest_asyncio.fixture
149149
async def real_kafka_producer(real_services: TestServiceConnections) -> Optional[AIOKafkaProducer]:
150150
"""Get real Kafka producer if available."""
151151
return await real_services.connect_kafka_producer("localhost:9092")
152152

153153

154-
@pytest_asyncio.fixture(loop_scope="session")
154+
@pytest_asyncio.fixture
155155
async def real_kafka_consumer(real_services: TestServiceConnections) -> Optional[AIOKafkaConsumer]:
156156
"""Get real Kafka consumer if available."""
157157
test_group = f"test_group_{real_services.test_id}"
@@ -305,7 +305,7 @@ async def wait_for_service(check_func, timeout: int = 30, service_name: str = "s
305305
raise TimeoutError(f"{service_name} not ready after {timeout}s: {last_error}")
306306

307307

308-
@pytest_asyncio.fixture(scope="session", loop_scope="session")
308+
@pytest_asyncio.fixture(scope="session")
309309
async def ensure_services_running():
310310
"""Ensure required Docker services are running."""
311311
import subprocess

backend/tests/integration/conftest.py

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,18 @@
55
from app.core.database_context import Database
66

77

8-
@pytest_asyncio.fixture(scope="function", loop_scope="session", autouse=True)
8+
@pytest_asyncio.fixture(autouse=True)
99
async def _cleanup(db: Database, redis_client: redis.Redis):
10-
"""Clean DB and Redis before/after each integration test."""
11-
# Pre-test cleanup
10+
"""Clean DB and Redis before each integration test.
11+
12+
Only pre-test cleanup - post-test cleanup causes event loop issues
13+
when SSE/streaming tests hold connections across loop boundaries.
14+
"""
1215
collections = await db.list_collection_names()
1316
for name in collections:
1417
if not name.startswith("system."):
1518
await db.drop_collection(name)
1619
await redis_client.flushdb()
1720

1821
yield
19-
20-
# Post-test cleanup
21-
collections = await db.list_collection_names()
22-
for name in collections:
23-
if not name.startswith("system."):
24-
await db.drop_collection(name)
25-
await redis_client.flushdb()
22+
# No post-test cleanup to avoid "Event loop is closed" errors

backend/tests/integration/test_user_settings_routes.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
pytestmark = pytest.mark.xdist_group(name="user_settings")
1818

1919

20-
@pytest_asyncio.fixture(loop_scope="session")
20+
@pytest_asyncio.fixture
2121
async def test_user(client: AsyncClient) -> Dict[str, str]:
2222
"""Create a fresh user for each test."""
2323
uid = uuid4().hex[:8]
@@ -49,7 +49,7 @@ async def test_user(client: AsyncClient) -> Dict[str, str]:
4949
}
5050

5151

52-
@pytest_asyncio.fixture(loop_scope="session")
52+
@pytest_asyncio.fixture
5353
async def test_user2(client: AsyncClient) -> Dict[str, str]:
5454
"""Create a second fresh user for isolation tests."""
5555
uid = uuid4().hex[:8]

0 commit comments

Comments
 (0)