import pytest from src.middlewares.rate_limiter import RateLimiter class TestRateLimiterPathNormalization: def test_get_rate_limit_matches_versioned_api_prefix(self): limiter = RateLimiter() limit, window = limiter.get_rate_limit("/api/v1/generations/image") assert (limit, window) == (10, 60) def test_normalize_path_reduces_dynamic_segments(self): limiter = RateLimiter() assert limiter._normalize_path("/api/v1/projects/123") == "/projects/{id}" assert ( limiter._normalize_path("/api/v1/tasks/550e8400-e29b-41d4-a716-446655440000") == "/tasks/{id}" ) @pytest.mark.asyncio class TestRateLimiterLocalFallback: async def test_local_fallback_limits_critical_endpoint_when_redis_unavailable(self): limiter = RateLimiter() limiter._connected = False limiter.rate_limits["/generations/image"] = (2, 60) limited_1, _, _, _ = await limiter.is_rate_limited("ip:test", "/api/v1/generations/image") limited_2, _, _, _ = await limiter.is_rate_limited("ip:test", "/api/v1/generations/image") limited_3, _, limit, _ = await limiter.is_rate_limited("ip:test", "/api/v1/generations/image") assert limited_1 is False assert limited_2 is False assert limited_3 is True assert limit == 2 async def test_local_fallback_not_used_for_non_critical_endpoint(self): limiter = RateLimiter() limiter._connected = False limited, count, limit, reset = await limiter.is_rate_limited("ip:test", "/api/v1/health") assert limited is False assert count == 0 assert limit == 0 assert reset == 0