scholarr/tests/unit/test_auth_primitives.py

53 lines
1.6 KiB
Python

from app.auth.rate_limit import SlidingWindowRateLimiter
from app.auth.security import PasswordService
def test_password_service_hash_and_verify_round_trip() -> None:
password_service = PasswordService()
password_hash = password_service.hash_password("example-password")
assert password_hash.startswith("$argon2id$")
assert password_service.verify_password(password_hash, "example-password")
assert not password_service.verify_password(password_hash, "wrong-password")
def test_rate_limiter_enforces_windowed_attempt_budget() -> None:
clock = {"now": 0.0}
limiter = SlidingWindowRateLimiter(
max_attempts=2,
window_seconds=10,
now=lambda: clock["now"],
)
key = "127.0.0.1:test@example.com"
assert limiter.check(key).allowed
limiter.record_failure(key)
assert limiter.check(key).allowed
limiter.record_failure(key)
decision = limiter.check(key)
assert not decision.allowed
assert decision.retry_after_seconds == 10
clock["now"] = 11.0
assert limiter.check(key).allowed
def test_rate_limiter_check_does_not_create_or_retain_empty_keys() -> None:
clock = {"now": 0.0}
limiter = SlidingWindowRateLimiter(
max_attempts=2,
window_seconds=10,
now=lambda: clock["now"],
)
key = "127.0.0.1:never-failed@example.com"
assert limiter.check(key).allowed
assert key not in limiter._attempts
limiter.record_failure(key)
assert key in limiter._attempts
clock["now"] = 11.0
assert limiter.check(key).allowed
assert key not in limiter._attempts