FastAPI + Celery + Next.js + Postgres/Redis app with company monitoring, source collection, LLM-based change analysis, enrichment, and account security (Turnstile, escalating lockout, email verification).
90 lines
3.2 KiB
Python
90 lines
3.2 KiB
Python
"""The suite disables the rate limiter globally (see conftest.py) so the many
|
|
auth calls other tests make don't trip real limits. This test re-enables it
|
|
temporarily to verify the limiter itself actually works."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import uuid
|
|
|
|
from fastapi import Request
|
|
|
|
from app.core.config import get_settings
|
|
from app.core.rate_limit import _client_ip_key, limiter
|
|
|
|
|
|
def _build_request(headers: dict[str, str], client_ip: str) -> Request:
|
|
scope = {
|
|
"type": "http",
|
|
"headers": [(k.lower().encode(), v.encode()) for k, v in headers.items()],
|
|
"client": (client_ip, 12345),
|
|
}
|
|
return Request(scope)
|
|
|
|
|
|
def test_client_ip_key_uses_direct_peer_when_no_proxy_header_configured():
|
|
# Default test settings have trusted_proxy_ip_header="" - the header
|
|
# must be ignored even if a client sends it, or a spoofed header could
|
|
# forge any rate-limit identity with no proxy actually in front.
|
|
request = _build_request({"CF-Connecting-IP": "203.0.113.5"}, client_ip="10.0.0.9")
|
|
assert _client_ip_key(request) == "10.0.0.9"
|
|
|
|
|
|
def test_client_ip_key_honors_configured_proxy_header(monkeypatch):
|
|
# Once deployed behind Cloudflare, trusted_proxy_ip_header="CF-Connecting-IP"
|
|
# must make the limiter key off the real visitor, not Nginx's own address -
|
|
# this is the exact bug slowapi's default get_remote_address had.
|
|
settings = get_settings().model_copy(update={"trusted_proxy_ip_header": "CF-Connecting-IP"})
|
|
monkeypatch.setattr("app.core.rate_limit.get_settings", lambda: settings)
|
|
|
|
request = _build_request({"CF-Connecting-IP": "203.0.113.5"}, client_ip="10.0.0.9")
|
|
assert _client_ip_key(request) == "203.0.113.5"
|
|
|
|
|
|
def test_register_endpoint_enforces_rate_limit(client):
|
|
limiter.enabled = True
|
|
try:
|
|
responses = [
|
|
client.post(
|
|
"/api/v1/auth/register",
|
|
json={
|
|
"email": f"rl-{uuid.uuid4().hex[:10]}@example.com",
|
|
"password": "correct-horse-1",
|
|
"display_name": "Rate Limit Test",
|
|
},
|
|
)
|
|
for _ in range(6)
|
|
]
|
|
finally:
|
|
limiter.enabled = False
|
|
|
|
statuses = [r.status_code for r in responses]
|
|
assert 429 in statuses, f"Expected a 429 among {statuses} after 6 rapid registrations"
|
|
|
|
|
|
def test_create_company_enforces_rate_limit(client):
|
|
email = f"rl-company-{uuid.uuid4().hex[:10]}@example.com"
|
|
client.post(
|
|
"/api/v1/auth/register",
|
|
json={"email": email, "password": "correct-horse-1", "display_name": "RL Test"},
|
|
)
|
|
tokens = client.post(
|
|
"/api/v1/auth/login", json={"email": email, "password": "correct-horse-1"}
|
|
).json()
|
|
headers = {"Authorization": f"Bearer {tokens['access_token']}"}
|
|
|
|
limiter.enabled = True
|
|
try:
|
|
responses = [
|
|
client.post(
|
|
"/api/v1/companies",
|
|
json={"name": f"RL Co {i}", "frequency_type": "weekly"},
|
|
headers=headers,
|
|
)
|
|
for i in range(22)
|
|
]
|
|
finally:
|
|
limiter.enabled = False
|
|
|
|
statuses = [r.status_code for r in responses]
|
|
assert 429 in statuses, f"Expected a 429 among {statuses} after 22 rapid company creations"
|