use allsource_core::rate_limit::{RateLimitConfig, RateLimiter};
use std::{sync::Arc, thread, time::Duration};
#[test]
fn test_token_refill() {
let config = RateLimitConfig {
requests_per_minute: 60, burst_size: 10,
};
let limiter = RateLimiter::new(config);
for _ in 0..10 {
let result = limiter.check_rate_limit("test-tenant");
assert!(result.allowed);
}
let result = limiter.check_rate_limit("test-tenant");
assert!(!result.allowed);
let mut allowed = false;
for _ in 0..5 {
thread::sleep(Duration::from_millis(500));
let result = limiter.check_rate_limit("test-tenant");
if result.allowed {
allowed = true;
break;
}
}
assert!(allowed, "Token should have refilled after waiting");
}
#[test]
fn test_tenant_isolation() {
let config = RateLimitConfig {
requests_per_minute: 5,
burst_size: 5,
};
let limiter = RateLimiter::new(config);
for _ in 0..5 {
limiter.check_rate_limit("tenant1");
}
let result1 = limiter.check_rate_limit("tenant1");
assert!(!result1.allowed);
let result2 = limiter.check_rate_limit("tenant2");
assert!(result2.allowed);
}
#[test]
fn test_rate_limit_headers() {
let config = RateLimitConfig {
requests_per_minute: 60,
burst_size: 10,
};
let limiter = RateLimiter::new(config);
let result = limiter.check_rate_limit("test-tenant");
assert_eq!(result.limit, 60);
assert_eq!(result.remaining, 9); assert!(result.retry_after.is_none()); }
#[test]
fn test_concurrent_rate_limiting() {
let config = RateLimitConfig {
requests_per_minute: 100,
burst_size: 50,
};
let limiter = Arc::new(RateLimiter::new(config));
let mut handles = vec![];
for i in 0..10 {
let limiter_clone = Arc::clone(&limiter);
let handle = thread::spawn(move || {
let tenant = format!("tenant-{i}");
for _ in 0..5 {
limiter_clone.check_rate_limit(&tenant);
}
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
for i in 0..10 {
let tenant = format!("tenant-{i}");
let result = limiter.check_rate_limit(&tenant);
assert!(result.allowed);
}
}
#[test]
fn test_custom_tier_config() {
let limiter = RateLimiter::new(RateLimitConfig {
requests_per_minute: 10,
burst_size: 5,
});
limiter.set_config(
"premium-tenant",
RateLimitConfig {
requests_per_minute: 1000,
burst_size: 100,
},
);
let premium_result = limiter.check_rate_limit("premium-tenant");
assert_eq!(premium_result.limit, 1000);
assert_eq!(premium_result.remaining, 99);
let regular_result = limiter.check_rate_limit("regular-tenant");
assert_eq!(regular_result.limit, 10);
assert_eq!(regular_result.remaining, 4);
}
#[test]
fn test_zero_burst_behavior() {
let config = RateLimitConfig {
requests_per_minute: 60,
burst_size: 0, };
let limiter = RateLimiter::new(config);
let result = limiter.check_rate_limit("test-tenant");
assert!(!result.allowed);
}
#[test]
fn test_very_high_rate() {
let config = RateLimitConfig {
requests_per_minute: 60000, burst_size: 100, };
let limiter = RateLimiter::new(config);
for _ in 0..100 {
let result = limiter.check_rate_limit("high-rate-tenant");
assert!(result.allowed);
}
let result = limiter.check_rate_limit("high-rate-tenant");
assert!(result.remaining <= 100);
}
#[test]
fn test_rate_limit_recovery() {
let config = RateLimitConfig {
requests_per_minute: 60,
burst_size: 10,
};
let limiter = RateLimiter::new(config);
for _ in 0..10 {
limiter.check_rate_limit("test-tenant");
}
assert!(!limiter.check_rate_limit("test-tenant").allowed);
let mut recovered_count = 0;
for attempt in 0..10 {
thread::sleep(Duration::from_millis(500));
let result = limiter.check_rate_limit("test-tenant");
if result.allowed {
recovered_count += 1;
if recovered_count >= 2 {
break;
}
}
if attempt >= 5 && recovered_count == 0 {
thread::sleep(Duration::from_millis(1000));
}
}
assert!(
recovered_count >= 1,
"Should have recovered at least 1 token, got {recovered_count}"
);
}
#[test]
fn test_retry_after() {
let config = RateLimitConfig {
requests_per_minute: 60,
burst_size: 10,
};
let limiter = RateLimiter::new(config);
for _ in 0..10 {
limiter.check_rate_limit("test-tenant");
}
let result = limiter.check_rate_limit("test-tenant");
assert!(!result.allowed);
assert!(result.retry_after.is_some());
let retry_after = result.retry_after.unwrap();
assert!(!retry_after.is_zero());
}