Files
consentos/apps/api/tests/test_middleware_rate_limit.py
James Cottrill fbf26453f2 feat: initial public release
ConsentOS — a privacy-first cookie consent management platform.

Self-hosted, source-available alternative to OneTrust, Cookiebot, and
CookieYes. Full standards coverage (IAB TCF v2.2, GPP v1, Google
Consent Mode v2, GPC, Shopify Customer Privacy API), multi-tenant
architecture with role-based access, configuration cascade
(system → org → group → site → region), dark-pattern detection in
the scanner, and a tamper-evident consent record audit trail.

This is the initial public release. Prior development history is
retained internally.

See README.md for the feature list, architecture overview, and
quick-start instructions. Licensed under the Elastic Licence 2.0 —
self-host freely; do not resell as a managed service.
2026-04-14 09:18:18 +00:00

138 lines
4.6 KiB
Python

"""Tests for the rate limiting middleware."""
from unittest.mock import AsyncMock, MagicMock
import pytest
from httpx import ASGITransport, AsyncClient
from src.middleware.rate_limit import RateLimitMiddleware
class TestRateLimitMiddleware:
@pytest.mark.asyncio
async def test_get_client_ip_from_forwarded_for(self):
from starlette.applications import Starlette
app = Starlette()
middleware = RateLimitMiddleware(app)
request = MagicMock()
request.headers = {"x-forwarded-for": "1.2.3.4, 5.6.7.8"}
assert middleware._get_client_ip(request) == "1.2.3.4"
@pytest.mark.asyncio
async def test_get_client_ip_from_real_ip(self):
from starlette.applications import Starlette
app = Starlette()
middleware = RateLimitMiddleware(app)
request = MagicMock()
request.headers = {"x-real-ip": "9.8.7.6"}
assert middleware._get_client_ip(request) == "9.8.7.6"
@pytest.mark.asyncio
async def test_get_client_ip_from_client(self):
from starlette.applications import Starlette
app = Starlette()
middleware = RateLimitMiddleware(app)
request = MagicMock()
request.headers = {}
request.client = MagicMock()
request.client.host = "10.0.0.1"
assert middleware._get_client_ip(request) == "10.0.0.1"
@pytest.mark.asyncio
async def test_get_client_ip_no_client(self):
from starlette.applications import Starlette
app = Starlette()
middleware = RateLimitMiddleware(app)
request = MagicMock()
request.headers = {}
request.client = None
assert middleware._get_client_ip(request) == "unknown"
@pytest.mark.asyncio
async def test_health_bypasses_rate_limit(self):
"""Health checks should never be rate limited."""
from src.main import create_app
app = create_app()
transport = ASGITransport(app=app)
async with AsyncClient(transport=transport, base_url="http://test") as client:
resp = await client.get("/health")
assert resp.status_code == 200
@pytest.mark.asyncio
async def test_passes_through_when_redis_unavailable(self):
"""When Redis is down, requests should still be served."""
from src.main import create_app
# Rate limiting disabled by default in test settings
app = create_app()
transport = ASGITransport(app=app)
async with AsyncClient(transport=transport, base_url="http://test") as client:
resp = await client.get("/health")
assert resp.status_code == 200
@pytest.mark.asyncio
async def test_rate_limit_headers_present(self):
"""Rate limit headers should be added when middleware is active."""
from fastapi import FastAPI
app = FastAPI()
@app.get("/test")
async def test_endpoint():
return {"ok": True}
# Mock Redis
mock_redis = AsyncMock()
mock_redis.incr = AsyncMock(return_value=1)
mock_redis.expire = AsyncMock()
middleware = RateLimitMiddleware(app, requests_per_minute=100)
middleware._redis = mock_redis
# Since we can't easily inject the mock Redis into the ASGI middleware,
# test the logic unit separately
assert middleware.requests_per_minute == 100
@pytest.mark.asyncio
async def test_middleware_creation(self):
"""Middleware should initialise with provided parameters."""
from starlette.applications import Starlette
app = Starlette()
middleware = RateLimitMiddleware(app, redis_url="redis://fake:6379", requests_per_minute=30)
assert middleware.requests_per_minute == 30
assert middleware.redis_url == "redis://fake:6379"
assert middleware._redis is None # Lazy initialisation
class TestRateLimitConfiguration:
def test_default_settings_enabled(self, monkeypatch):
"""Rate limiting is on by default — public endpoints must not be DoS-able.
Note: the suite-wide conftest sets ``RATE_LIMIT_ENABLED=false``
so other tests aren't rate-limited by Redis; we unset it here
to verify the baked-in default.
"""
monkeypatch.delenv("RATE_LIMIT_ENABLED", raising=False)
from src.config.settings import Settings
settings = Settings()
assert settings.rate_limit_enabled is True
def test_configurable_limit(self):
"""Rate limit per minute should be configurable."""
from src.config.settings import Settings
settings = Settings(rate_limit_per_minute=120)
assert settings.rate_limit_per_minute == 120