use std::net::{IpAddr, Ipv4Addr};
use std::time::{Duration, Instant};
use super::AuthRateLimiter;
fn ip(last: u8) -> IpAddr {
IpAddr::V4(Ipv4Addr::new(10, 0, 0, last))
}
#[test]
fn first_request_for_a_fresh_ip_is_allowed() {
let limiter = AuthRateLimiter::default();
assert!(limiter.check_at(ip(1), 60, Instant::now()));
}
#[test]
fn allows_exactly_limit_requests_then_rejects() {
let limiter = AuthRateLimiter::default();
let t0 = Instant::now();
for i in 0..5 {
assert!(
limiter.check_at(ip(1), 5, t0),
"request {} unexpectedly rejected within the limit",
i + 1
);
}
assert!(!limiter.check_at(ip(1), 5, t0));
assert!(!limiter.check_at(ip(1), 5, t0 + Duration::from_secs(30)));
}
#[test]
fn different_ips_have_independent_buckets() {
let limiter = AuthRateLimiter::default();
let t0 = Instant::now();
for _ in 0..3 {
assert!(limiter.check_at(ip(1), 3, t0));
}
assert!(!limiter.check_at(ip(1), 3, t0), "IP A should be over limit");
for _ in 0..3 {
assert!(
limiter.check_at(ip(2), 3, t0),
"IP B's bucket was contaminated by IP A's"
);
}
}
#[test]
fn bucket_resets_after_60_seconds() {
let limiter = AuthRateLimiter::default();
let t0 = Instant::now();
for _ in 0..2 {
assert!(limiter.check_at(ip(1), 2, t0));
}
assert!(!limiter.check_at(ip(1), 2, t0));
assert!(!limiter.check_at(ip(1), 2, t0 + Duration::from_secs(59)));
let t1 = t0 + Duration::from_secs(60);
assert!(limiter.check_at(ip(1), 2, t1));
assert!(limiter.check_at(ip(1), 2, t1));
assert!(!limiter.check_at(ip(1), 2, t1));
}
#[test]
fn stale_entries_are_pruned_after_two_windows() {
let limiter = AuthRateLimiter::default();
let t0 = Instant::now();
assert!(limiter.check_at(ip(1), 1, t0));
assert_eq!(limiter.windows.lock().unwrap().len(), 1);
let t_stale = t0 + Duration::from_secs(120);
assert!(limiter.check_at(ip(2), 1, t_stale));
let remaining: Vec<IpAddr> =
limiter.windows.lock().unwrap().keys().copied().collect();
assert_eq!(
remaining,
vec![ip(2)],
"stale entry for IP A should be pruned"
);
}
#[test]
fn limit_of_zero_rejects_everything() {
let limiter = AuthRateLimiter::default();
assert!(!limiter.check_at(ip(1), 0, Instant::now()));
}