49 lines
1.4 KiB
Python
49 lines
1.4 KiB
Python
"""登录 IP 限速(auth_api._login_rate_limited):窗口内超限拦截、过期滑出、独立 IP 隔离。"""
|
||
from __future__ import annotations
|
||
|
||
import pytest
|
||
|
||
import app.auth_api as auth_api
|
||
from app.auth_api import _LOGIN_MAX_PER_WINDOW, _LOGIN_WINDOW, _login_attempts, _login_rate_limited
|
||
|
||
|
||
class _FakeClock:
|
||
"""替换 auth_api 命名空间里的 time(不影响全局 time 模块)。"""
|
||
|
||
def __init__(self):
|
||
self.now = 1000.0
|
||
|
||
def monotonic(self) -> float:
|
||
return self.now
|
||
|
||
|
||
@pytest.fixture()
|
||
def clock(monkeypatch):
|
||
c = _FakeClock()
|
||
monkeypatch.setattr(auth_api, "time", c)
|
||
_login_attempts.clear()
|
||
yield c
|
||
_login_attempts.clear()
|
||
|
||
|
||
def test_under_limit_passes(clock):
|
||
for _ in range(_LOGIN_MAX_PER_WINDOW):
|
||
assert _login_rate_limited("1.2.3.4") is False
|
||
assert _login_rate_limited("1.2.3.4") is True # 第 16 次被拦
|
||
|
||
|
||
def test_window_slides(clock):
|
||
for _ in range(_LOGIN_MAX_PER_WINDOW):
|
||
_login_rate_limited("1.2.3.4")
|
||
assert _login_rate_limited("1.2.3.4") is True
|
||
# 窗口滑过:最早的尝试过期出窗,重新放行
|
||
clock.now += _LOGIN_WINDOW + 0.1
|
||
assert _login_rate_limited("1.2.3.4") is False
|
||
|
||
|
||
def test_ips_isolated(clock):
|
||
for _ in range(_LOGIN_MAX_PER_WINDOW):
|
||
_login_rate_limited("1.1.1.1")
|
||
assert _login_rate_limited("1.1.1.1") is True
|
||
assert _login_rate_limited("2.2.2.2") is False # 另一 IP 不受牵连
|