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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015jtxAiu5dAW4WxCNdGkrkJ
This commit is contained in:
@@ -75,6 +75,10 @@ class Settings(BaseSettings):
|
|||||||
# CORS
|
# CORS
|
||||||
CORS_ORIGINS: str = "http://localhost:8080,http://localhost:3000"
|
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
|
# File uploads
|
||||||
UPLOAD_DIR: str = "uploads"
|
UPLOAD_DIR: str = "uploads"
|
||||||
MAX_UPLOAD_SIZE_MB: int = 100
|
MAX_UPLOAD_SIZE_MB: int = 100
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ from collections import defaultdict, deque
|
|||||||
|
|
||||||
from fastapi import HTTPException, Request, status
|
from fastapi import HTTPException, Request, status
|
||||||
|
|
||||||
|
from app.config import settings
|
||||||
|
|
||||||
|
|
||||||
class SlidingWindowLimiter:
|
class SlidingWindowLimiter:
|
||||||
def __init__(self, max_hits: int, window_seconds: float) -> None:
|
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:
|
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)}")
|
_auth_limiter.check(f"auth:{_client_key(request)}")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -4,16 +4,21 @@ import pytest
|
|||||||
from fastapi.testclient import TestClient
|
from fastapi.testclient import TestClient
|
||||||
|
|
||||||
from app import rate_limit
|
from app import rate_limit
|
||||||
|
from app.config import settings
|
||||||
from app.main import app
|
from app.main import app
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
def _clean_limiter():
|
def _clean_limiter():
|
||||||
# This module tests the limiter itself, so start each test from empty
|
# This module tests the limiter itself: force it on (the checkpoint
|
||||||
# and don't let conftest's between-test reset interfere mid-test.
|
# 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()
|
rate_limit.reset_all()
|
||||||
yield
|
yield
|
||||||
rate_limit.reset_all()
|
rate_limit.reset_all()
|
||||||
|
settings.RATE_LIMIT_ENABLED = prev
|
||||||
|
|
||||||
|
|
||||||
def test_login_is_limited_after_the_threshold():
|
def test_login_is_limited_after_the_threshold():
|
||||||
|
|||||||
@@ -90,6 +90,10 @@ def main():
|
|||||||
db_url = f"postgresql+asyncpg://agp_user:agp_password@localhost:{args.postgres_port}/agp_db"
|
db_url = f"postgresql+asyncpg://agp_user:agp_password@localhost:{args.postgres_port}/agp_db"
|
||||||
backend_env = os.environ.copy()
|
backend_env = os.environ.copy()
|
||||||
backend_env["DATABASE_URL"] = db_url
|
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 ===")
|
print("\n=== Backend: alembic upgrade head ===")
|
||||||
r = run([sys.executable, "-m", "alembic", "upgrade", "head"], cwd=BACKEND, env=backend_env)
|
r = run([sys.executable, "-m", "alembic", "upgrade", "head"], cwd=BACKEND, env=backend_env)
|
||||||
|
|||||||
Reference in New Issue
Block a user