use std::sync::Arc;
use std::time::{Duration, Instant};
use tokio::time::sleep;
use crate::brain::provider::rate_limiter::{
GlobalRateLimiter, OPENROUTER_FREE_LIMITERS, RateLimiter,
};
#[tokio::test]
async fn first_request_instant() {
let limiter = RateLimiter::new(Duration::from_millis(50));
let start = Instant::now();
let slept = limiter.wait().await;
let elapsed = start.elapsed();
assert_eq!(
slept,
Duration::ZERO,
"first call on fresh limiter should return immediately"
);
assert!(
elapsed < Duration::from_millis(50),
"wall-clock should also be near-zero (was <10ms, loosened for CI)"
);
}
#[tokio::test]
async fn second_request_paces() {
let gap = Duration::from_millis(500);
let limiter = RateLimiter::new(gap);
limiter.wait().await;
let start = Instant::now();
let returned = limiter.wait().await; let elapsed = start.elapsed();
assert!(
returned >= Duration::from_millis(300),
"limiter should have computed ≥300 ms sleep, got {:?}",
returned
);
assert!(
elapsed >= Duration::from_millis(200),
"wall-clock should be ≥200 ms, got {:?}",
elapsed
);
assert!(
elapsed < Duration::from_millis(1500),
"should not have overslept, got {:?}",
elapsed
);
}
#[tokio::test]
async fn multiple_arcs_share_state() {
let limiter = Arc::new(RateLimiter::new(Duration::from_millis(80)));
let a = Arc::clone(&limiter);
let b = Arc::clone(&limiter);
a.wait().await;
let start = Instant::now();
b.wait().await;
let elapsed = start.elapsed();
assert!(
elapsed >= Duration::from_millis(70),
"shared Arc should enforce the gap, got {:?}",
elapsed
);
}
#[tokio::test]
async fn concurrent_callers_serialise() {
let limiter = Arc::new(RateLimiter::new(Duration::from_millis(100)));
let mut handles = Vec::new();
let start = Instant::now();
for _ in 0..3 {
let lim = limiter.clone();
handles.push(tokio::spawn(async move { lim.wait().await }));
}
let results: Vec<_> = futures::future::join_all(handles)
.await
.into_iter()
.map(|h| h.unwrap())
.collect();
let total_sleep: Duration = results.iter().sum();
assert!(
total_sleep >= Duration::from_millis(90),
"at least one gap's worth of sleep across all callers, got {:?}",
total_sleep
);
assert!(
start.elapsed() >= Duration::from_millis(90),
"wall-clock >= 90 ms, got {:?}",
start.elapsed()
);
}
#[tokio::test]
async fn instant_after_idle_gap() {
let limiter = RateLimiter::new(Duration::from_millis(20));
limiter.wait().await;
sleep(Duration::from_millis(40)).await;
let start = Instant::now();
let slept = limiter.wait().await;
assert_eq!(slept, Duration::ZERO, "after 2× idle gap, should be free");
assert!(
start.elapsed() < Duration::from_millis(50),
"after 2× idle gap, should be near-instant"
);
}
#[tokio::test]
async fn openrouter_free_static_exists() {
let limiter = OPENROUTER_FREE_LIMITERS.get("qwen/qwen3.6-plus:free");
assert!(
limiter.min_interval >= Duration::from_secs(2),
"openrouter_free should be ~4 s, got {:?}",
limiter.min_interval
);
}
#[tokio::test]
async fn global_limiter_returns_same_limiter_for_same_model() {
let global = GlobalRateLimiter::new();
let a = global.get("qwen/qwen3.6-plus:free");
let b = global.get("qwen/qwen3.6-plus:free");
assert!(Arc::ptr_eq(&a, &b));
}
#[tokio::test]
async fn global_limiter_returns_different_limiter_for_different_model() {
let global = GlobalRateLimiter::new();
let a = global.get("qwen/qwen3.6-plus:free");
let b = global.get("google/gemma-3-27b-it:free");
assert!(!Arc::ptr_eq(&a, &b));
}