import ipaddress from unittest.mock import MagicMock from starlette.requests import Request from app.auth.rate_limiter import LoginRateLimiter, get_client_ip # --------------------------------------------------------------------------- # LoginRateLimiter tests # --------------------------------------------------------------------------- def make_limiter(): return LoginRateLimiter(max_failures=3, window_seconds=60, cooldown_seconds=300) def test_not_blocked_initially(): assert make_limiter().is_blocked("1.2.3.4") is False def test_blocked_after_threshold(): limiter = make_limiter() for _ in range(3): limiter.record_failure("1.2.3.4") assert limiter.is_blocked("1.2.3.4") is True def test_success_clears_failures(): limiter = make_limiter() limiter.record_failure("1.2.3.4") limiter.record_failure("1.2.3.4") limiter.record_success("1.2.3.4") assert limiter.is_blocked("1.2.3.4") is False def test_ips_are_isolated(): limiter = make_limiter() for _ in range(3): limiter.record_failure("1.1.1.1") assert limiter.is_blocked("2.2.2.2") is False def test_window_resets_after_expiry(): import time limiter = LoginRateLimiter(max_failures=3, window_seconds=0, cooldown_seconds=300) limiter.record_failure("1.2.3.4") limiter.record_failure("1.2.3.4") time.sleep(0.01) limiter.record_failure("1.2.3.4") # window expired — counter reset on third call, so failures = 1, not 3 assert limiter.is_blocked("1.2.3.4") is False def test_log_warning_on_lockout(caplog): import logging limiter = make_limiter() with caplog.at_level(logging.WARNING, logger="app.auth.rate_limiter"): for _ in range(3): limiter.record_failure("5.6.7.8") assert "Login blocked" in caplog.text assert "5.6.7.8" in caplog.text # --------------------------------------------------------------------------- # get_client_ip tests # --------------------------------------------------------------------------- def make_request(peer: str, headers: dict) -> MagicMock: req = MagicMock(spec=Request) req.client.host = peer req.headers = headers return req def test_get_client_ip_no_trusted_networks_returns_peer(): req = make_request("203.0.113.1", {"X-Forwarded-For": "10.0.0.1"}) assert get_client_ip(req, []) == "203.0.113.1" def test_get_client_ip_trusted_peer_uses_xff(): req = make_request("10.0.0.1", {"X-Forwarded-For": "203.0.113.5"}) nets = [ipaddress.ip_network("10.0.0.0/8")] assert get_client_ip(req, nets) == "203.0.113.5" def test_get_client_ip_untrusted_peer_ignores_xff(): req = make_request("8.8.8.8", {"X-Forwarded-For": "203.0.113.5"}) nets = [ipaddress.ip_network("10.0.0.0/8")] assert get_client_ip(req, nets) == "8.8.8.8" def test_get_client_ip_trusted_peer_falls_back_to_real_ip(): req = make_request("10.0.0.1", {"X-Real-IP": "203.0.113.9"}) nets = [ipaddress.ip_network("10.0.0.0/8")] assert get_client_ip(req, nets) == "203.0.113.9"