From f4c4a50ebdd1c872ba92586a75d4f69296788a87 Mon Sep 17 00:00:00 2001 From: Agent Zero Date: Tue, 25 Aug 2026 22:19:41 +0200 Subject: [PATCH] =?UTF-8?q?fix(i-e):=20BUG-097=20geschlossen=20=E2=80=94?= =?UTF-8?q?=20auth-Suite=2010/10=20gruen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Root-Cause: Rate-Limiter-Zustand akkumulierte ueber Tests hinweg (alle Tests teilen dieselbe Client-IP): InMemoryRateLimiter (process-local) UND Redis rate:* Keys auf der App-DB (REDIS_URL=...db1). Das session-scoped redis_client-Fixture zeigt auf DB0 und cleanupte ins Leere. Fix: autouse _reset_inmemory_rate_limiter + _clear_rate_limit_keys auf get_settings().redis_url. Beweis: test_auth 10/10 in Kette (vorher 3 PasswordReset-Failures mit 429). --- tests/conftest.py | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/tests/conftest.py b/tests/conftest.py index ca9e569..fa83983 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -469,6 +469,48 @@ def mock_imap_connections(monkeypatch): yield fake_client +@pytest.fixture(autouse=True) +def _reset_inmemory_rate_limiter(): + """Clear the process-local rate limiter around every test (BUG-097). + + The InMemoryRateLimiter is process-local and would otherwise accumulate + password-reset/login attempts across tests, making later tests fail with + 429 depending purely on execution order. + """ + from app.core.resilience import get_inmemory_limiter + + get_inmemory_limiter().clear_all() + yield + get_inmemory_limiter().clear_all() + + +@pytest_asyncio.fixture(autouse=True) +async def _clear_rate_limit_keys(): + """Delete Redis rate-limit keys after every test (BUG-097). + + check_rate_limit uses Redis INCR+EXPIRE keyed by client IP; all tests share + the same test-client IP, so password-reset/login counters accumulate across + tests and later tests hit 429 depending purely on execution order. + + Uses the SAME Redis connection the app uses (app settings REDIS_URL, DB 1 in + .env.test) — the session-scoped redis_client fixture points at a different + logical DB than get_redis() and would clean up nothing. + """ + import redis.asyncio as aioredis_mod + + from app.config import get_settings + + url = get_settings().redis_url + r = aioredis_mod.from_url(url, decode_responses=True) + try: + yield + keys = await r.keys("rate:*") + if keys: + await r.delete(*keys) + finally: + await r.aclose() + + @pytest_asyncio.fixture(scope="session") async def engine() -> AsyncGenerator[AsyncEngine, None]: """Async engine for the test database (session-scoped for speed)."""