From 69d7fe150801a1f95bb88e15df4115cb5a3ce0c7 Mon Sep 17 00:00:00 2001 From: Barcode Betty Date: Tue, 2 Jun 2026 13:42:48 +0000 Subject: [PATCH] Swap Redis limiters for in-memory in test fixture The conftest was setting rate_limit_redis_enabled=False but the rate_limit module's _redis_client and the RedisSlidingWindow limiters are constructed at module import. Flipping the setting inside the fixture doesn't undo that, so the Redis client was still being constructed and torn down at the end of the test event loop, raising RuntimeError('Event loop is closed'). This swaps the limiters directly on the module in the fixture setup and restores the originals in teardown. Local: 164 passed, 7 skipped. Co-Authored-By: Claude Opus 4.8 --- tests/conftest.py | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/tests/conftest.py b/tests/conftest.py index 9920564..133f726 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -18,6 +18,7 @@ from sqlalchemy.types import CHAR from cartsnitch_api.config import settings as cartsnitch_settings from cartsnitch_api.database import get_db from cartsnitch_api.main import create_app +from cartsnitch_api.middleware import rate_limit as _rate_limit_module from cartsnitch_api.models import Base @@ -140,12 +141,39 @@ TEST_DATABASE_URL = "sqlite+aiosqlite:///:memory:" @pytest.fixture(autouse=True) def disable_rate_limiting(): - """Disable rate limiting for all tests to prevent 429 interference.""" + """Disable rate limiting for all tests to prevent 429 interference. + + The rate_limit module creates its Redis client at import time when + ``settings.rate_limit_redis_enabled`` is true. We can't undo that by + flipping the setting inside the fixture — the client and the + Redis-backed limiters are already constructed. So we swap them out + for the in-memory limiters directly on the module, which also + prevents "Event loop is closed" errors when the redis client tries + to disconnect after the test event loop ends. + """ cartsnitch_settings.rate_limit_enabled = False cartsnitch_settings.rate_limit_redis_enabled = False + original_public = _rate_limit_module._public_limiter + original_auth = _rate_limit_module._auth_limiter + original_auth_strict = _rate_limit_module._auth_strict_limiter + _rate_limit_module._redis_client = None + _rate_limit_module._use_redis = False + _rate_limit_module._public_limiter = _rate_limit_module.InMemorySlidingWindow( + cartsnitch_settings.rate_limit_requests, cartsnitch_settings.rate_limit_window_seconds + ) + _rate_limit_module._auth_limiter = _rate_limit_module.InMemorySlidingWindow( + cartsnitch_settings.rate_limit_requests * 5, cartsnitch_settings.rate_limit_window_seconds + ) + _rate_limit_module._auth_strict_limiter = _rate_limit_module.InMemorySlidingWindow( + cartsnitch_settings.rate_limit_auth_requests, + cartsnitch_settings.rate_limit_auth_window_seconds, + ) yield cartsnitch_settings.rate_limit_enabled = True cartsnitch_settings.rate_limit_redis_enabled = True + _rate_limit_module._public_limiter = original_public + _rate_limit_module._auth_limiter = original_auth + _rate_limit_module._auth_strict_limiter = original_auth_strict @pytest.fixture