"""Tests for rate limiting (TTLCache-based).""" import pytest from auth import check_rate_limit, record_login_attempt, MAX_LOGIN_ATTEMPTS, LOGIN_BLOCK_MINUTES class TestRateLimit: """Test rate limiting functions.""" def test_no_attempts_allowed(self): """Fresh IP should be allowed.""" allowed, wait = check_rate_limit("fresh-ip") assert allowed is True assert wait is None def test_single_attempt_allowed(self): """One failed attempt should still be allowed.""" ip = "single-attempt-ip" record_login_attempt(ip, False) allowed, wait = check_rate_limit(ip) assert allowed is True def test_max_attempts_blocks(self): """MAX_LOGIN_ATTEMPTS failed attempts should block.""" ip = "blocked-ip" for _ in range(MAX_LOGIN_ATTEMPTS): record_login_attempt(ip, False) allowed, wait = check_rate_limit(ip) assert allowed is False assert wait is not None assert wait > 0 # Wait should be approximately LOGIN_BLOCK_MINUTES * 60 assert wait <= LOGIN_BLOCK_MINUTES * 60 def test_success_resets_attempts(self): """Successful login should reset rate limit.""" ip = "reset-ip" for _ in range(MAX_LOGIN_ATTEMPTS - 1): record_login_attempt(ip, False) # One success should reset record_login_attempt(ip, True) allowed, wait = check_rate_limit(ip) assert allowed is True assert wait is None def test_success_then_fail_starts_fresh(self): """After success reset, failing again should start from 1.""" ip = "fresh-start-ip" record_login_attempt(ip, False) record_login_attempt(ip, True) record_login_attempt(ip, False) allowed, wait = check_rate_limit(ip) assert allowed is True # Only 1 attempt after reset def test_separate_ips_independent(self): """Rate limit should be per-IP.""" ip1 = "ip-one" ip2 = "ip-two" for _ in range(MAX_LOGIN_ATTEMPTS): record_login_attempt(ip1, False) allowed1, _ = check_rate_limit(ip1) allowed2, _ = check_rate_limit(ip2) assert allowed1 is False assert allowed2 is True