"""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