- Rate-limit failed logins (8 per 15 min per IP or username, 429 + Retry-After) - Add ruff/mypy config and GitHub Actions workflow (ruff, pytest, tsc, tests, build) - Apply ruff autofixes (import order, pyupgrade) - Extract rpy_helpers.py, engine-util.ts, TranslationParts.tsx and translation-types.ts from oversized modules - Add dependency-free React UI tests - Refresh README and ARCHITECTURE (Naninovel/Tomie, status and plans) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
26 lines
841 B
Python
26 lines
841 B
Python
from app.ratelimit import FailureLimiter
|
|
|
|
|
|
def test_blocks_after_max_failures_and_resets():
|
|
limiter = FailureLimiter(max_failures=3, window_seconds=60)
|
|
for _ in range(2):
|
|
limiter.fail("ip:1", "user:a")
|
|
assert limiter.retry_after("ip:1", "user:a") == 0
|
|
limiter.fail("ip:1", "user:a")
|
|
assert limiter.retry_after("ip:1", "user:a") > 0
|
|
assert limiter.retry_after("ip:2", "user:b") == 0
|
|
limiter.reset("ip:1", "user:a")
|
|
assert limiter.retry_after("ip:1", "user:a") == 0
|
|
|
|
|
|
def test_window_expires(monkeypatch):
|
|
import app.ratelimit as rl
|
|
|
|
now = [1000.0]
|
|
monkeypatch.setattr(rl.time, "monotonic", lambda: now[0])
|
|
limiter = FailureLimiter(max_failures=1, window_seconds=10)
|
|
limiter.fail("k")
|
|
assert limiter.retry_after("k") > 0
|
|
now[0] += 11
|
|
assert limiter.retry_after("k") == 0
|