# -*- coding: utf-8 -*- """RateLimiter 单元测试。 被测代码:apps/backend/app/ai/rate_limiter.py 纯内存测试,不涉及 DB/网络。 """ from __future__ import annotations import time from unittest.mock import patch from app.ai.rate_limiter import RateLimiter class TestCheckUserRate: """App1 每用户每分钟限流。""" def test_allows_under_limit(self): rl = RateLimiter() for _ in range(10): assert rl.check_user_rate("u1", limit=10) is True def test_rejects_at_limit(self): rl = RateLimiter() for _ in range(10): rl.check_user_rate("u1", limit=10) assert rl.check_user_rate("u1", limit=10) is False def test_different_users_independent(self): rl = RateLimiter() for _ in range(10): rl.check_user_rate("u1", limit=10) # u1 已满,u2 不受影响 assert rl.check_user_rate("u1", limit=10) is False assert rl.check_user_rate("u2", limit=10) is True def test_window_expiry_allows_again(self): """窗口过期后,历史请求不影响当前判断。""" rl = RateLimiter() base = time.monotonic() with patch("app.ai.rate_limiter.time.monotonic", return_value=base): for _ in range(10): rl.check_user_rate("u1", limit=10, window_seconds=60) # 61 秒后,窗口内无请求 with patch("app.ai.rate_limiter.time.monotonic", return_value=base + 61): assert rl.check_user_rate("u1", limit=10, window_seconds=60) is True class TestCheckStoreRate: """App2~8 每门店每小时限流。""" def test_allows_under_limit(self): rl = RateLimiter() for _ in range(5): assert rl.check_store_rate(123, limit=5) is True def test_rejects_at_limit(self): rl = RateLimiter() for _ in range(5): rl.check_store_rate(123, limit=5) assert rl.check_store_rate(123, limit=5) is False def test_different_stores_independent(self): rl = RateLimiter() for _ in range(5): rl.check_store_rate(100, limit=5) assert rl.check_store_rate(100, limit=5) is False assert rl.check_store_rate(200, limit=5) is True def test_site_id_int_works(self): """site_id 为 int,内部转 str 存储。""" rl = RateLimiter() assert rl.check_store_rate(2790685415443269, limit=100) is True def test_window_expiry_allows_again(self): rl = RateLimiter() base = time.monotonic() with patch("app.ai.rate_limiter.time.monotonic", return_value=base): for _ in range(100): rl.check_store_rate(123, limit=100, window_seconds=3600) # 3601 秒后 with patch("app.ai.rate_limiter.time.monotonic", return_value=base + 3601): assert rl.check_store_rate(123, limit=100, window_seconds=3600) is True class TestRejectedRequestNotRecorded: """被拒绝的请求不应记录时间戳(不占用窗口配额)。""" def test_rejected_user_request_not_counted(self): rl = RateLimiter() for _ in range(3): rl.check_user_rate("u1", limit=3) # 连续拒绝不应增加窗口内计数 rl.check_user_rate("u1", limit=3) rl.check_user_rate("u1", limit=3) assert len(rl._user_windows["u1"]) == 3 def test_rejected_store_request_not_counted(self): rl = RateLimiter() for _ in range(3): rl.check_store_rate(1, limit=3) rl.check_store_rate(1, limit=3) rl.check_store_rate(1, limit=3) assert len(rl._store_windows["1"]) == 3