From fb52c4fd080e35e6f6faf7d26a424caf20119148 Mon Sep 17 00:00:00 2001 From: daddy Date: Sat, 5 Sep 2026 22:22:52 +0000 Subject: [PATCH] PC3 Phase 3.3 fix: RATE_LIMIT_ENABLED flag so the smoke suite isn't throttled Every checkpoint smoke test registers users against one backend from one client IP, so the per-IP auth limit tripped mid-suite (main_menu_full_flow -> 2/12). Add RATE_LIMIT_ENABLED (default true); run_checkpoint_tests.py sets it false for its backend. test_rate_limit.py forces it on in-fixture so the limiter is still really tested. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_015jtxAiu5dAW4WxCNdGkrkJ --- backend/app/config.py | 4 ++++ backend/app/rate_limit.py | 12 +++++++++++- backend/tests/test_rate_limit.py | 9 +++++++-- scripts/run_checkpoint_tests.py | 4 ++++ 4 files changed, 26 insertions(+), 3 deletions(-) diff --git a/backend/app/config.py b/backend/app/config.py index 89712b2..c5960e3 100644 --- a/backend/app/config.py +++ b/backend/app/config.py @@ -75,6 +75,10 @@ class Settings(BaseSettings): # CORS CORS_ORIGINS: str = "http://localhost:8080,http://localhost:3000" + # Per-IP auth rate limiting (app/rate_limit.py). Disabled by the + # checkpoint smoke suite, which runs every test from one client IP. + RATE_LIMIT_ENABLED: bool = True + # File uploads UPLOAD_DIR: str = "uploads" MAX_UPLOAD_SIZE_MB: int = 100 diff --git a/backend/app/rate_limit.py b/backend/app/rate_limit.py index 66c4fdb..28f69dc 100644 --- a/backend/app/rate_limit.py +++ b/backend/app/rate_limit.py @@ -14,6 +14,8 @@ from collections import defaultdict, deque from fastapi import HTTPException, Request, status +from app.config import settings + class SlidingWindowLimiter: def __init__(self, max_hits: int, window_seconds: float) -> None: @@ -54,7 +56,15 @@ _auth_limiter = SlidingWindowLimiter(max_hits=10, window_seconds=60) async def auth_rate_limit(request: Request) -> None: - """FastAPI dependency: rate-limit auth attempts per client IP.""" + """FastAPI dependency: rate-limit auth attempts per client IP. + + A no-op when ``RATE_LIMIT_ENABLED`` is false -- the checkpoint smoke + suite runs every test against one backend from one client IP, so the + per-IP cap would trip mid-suite. The dedicated ``test_rate_limit`` + module exercises the real behaviour regardless of this flag. + """ + if not settings.RATE_LIMIT_ENABLED: + return _auth_limiter.check(f"auth:{_client_key(request)}") diff --git a/backend/tests/test_rate_limit.py b/backend/tests/test_rate_limit.py index 15b06f6..4080548 100644 --- a/backend/tests/test_rate_limit.py +++ b/backend/tests/test_rate_limit.py @@ -4,16 +4,21 @@ import pytest from fastapi.testclient import TestClient from app import rate_limit +from app.config import settings from app.main import app @pytest.fixture(autouse=True) def _clean_limiter(): - # This module tests the limiter itself, so start each test from empty - # and don't let conftest's between-test reset interfere mid-test. + # This module tests the limiter itself: force it on (the checkpoint + # runner disables it suite-wide via RATE_LIMIT_ENABLED) and start each + # test from an empty window. + prev = settings.RATE_LIMIT_ENABLED + settings.RATE_LIMIT_ENABLED = True rate_limit.reset_all() yield rate_limit.reset_all() + settings.RATE_LIMIT_ENABLED = prev def test_login_is_limited_after_the_threshold(): diff --git a/scripts/run_checkpoint_tests.py b/scripts/run_checkpoint_tests.py index 6040a09..a8f8d9e 100755 --- a/scripts/run_checkpoint_tests.py +++ b/scripts/run_checkpoint_tests.py @@ -90,6 +90,10 @@ def main(): db_url = f"postgresql+asyncpg://agp_user:agp_password@localhost:{args.postgres_port}/agp_db" backend_env = os.environ.copy() backend_env["DATABASE_URL"] = db_url + # Every smoke test registers users against this one backend from one + # client IP; the per-IP auth rate limit would trip mid-suite. + # test_rate_limit.py exercises the limiter itself regardless. + backend_env["RATE_LIMIT_ENABLED"] = "false" print("\n=== Backend: alembic upgrade head ===") r = run([sys.executable, "-m", "alembic", "upgrade", "head"], cwd=BACKEND, env=backend_env)