fb52c4fd08
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
57 lines
1.8 KiB
Python
57 lines
1.8 KiB
Python
"""The auth endpoints are rate-limited per client IP."""
|
|
|
|
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: 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():
|
|
client = TestClient(app)
|
|
codes = []
|
|
for i in range(15):
|
|
r = client.post("/api/auth/login", data={"username": f"nobody{i}", "password": "x"})
|
|
codes.append(r.status_code)
|
|
# First 10 get through to the (failing) auth check; the rest are 429.
|
|
assert codes[:10] == [401] * 10
|
|
assert 429 in codes[10:]
|
|
assert codes[-1] == 429
|
|
|
|
|
|
def test_register_shares_the_same_bucket():
|
|
client = TestClient(app)
|
|
# Burn the budget on login...
|
|
for i in range(10):
|
|
client.post("/api/auth/login", data={"username": f"x{i}", "password": "y"})
|
|
# ...register from the same IP is now blocked too.
|
|
r = client.post("/api/auth/register", json={
|
|
"username": "rl_user", "email": "rl@e.com",
|
|
"password": "secret123", "date_of_birth": "1990-01-01",
|
|
})
|
|
assert r.status_code == 429
|
|
assert "Retry-After" in r.headers
|
|
|
|
|
|
def test_limiter_window_is_per_key():
|
|
limiter = rate_limit.SlidingWindowLimiter(max_hits=2, window_seconds=60)
|
|
limiter.check("a")
|
|
limiter.check("a")
|
|
with pytest.raises(Exception):
|
|
limiter.check("a")
|
|
limiter.check("b") # different key, unaffected
|