use std::time::{Duration, Instant};
use mcpmesh::config::LimitsCfg;
use mcpmesh::limits::{MeshLimiters, RateLimiter};
use mcpmesh_net::identity::EndpointId;
#[test]
fn scaled_ac_limiter_engages_and_memory_is_bounded() {
let ml = MeshLimiters::from_config(&LimitsCfg {
rate_limit_per_min: 120,
max_inflight: 16,
max_sessions: 4,
});
let t0 = Instant::now();
for peer in 0u8..20 {
let eid = EndpointId::from_bytes([peer; 32]);
let mut admitted = 0;
let mut throttled = 0;
for _ in 0..300 {
match ml.requests.check(&eid, t0) {
Ok(()) => admitted += 1,
Err(_) => throttled += 1,
}
}
assert!(
admitted <= 120,
"burst is capped at the config rate: {admitted}"
);
assert!(
throttled >= 180,
"the limiter ENGAGES on the over-limit remainder: {throttled}"
);
}
assert_eq!(
ml.requests.tracked(),
20,
"the bucket map is bounded to the live endpoint set"
);
let later = t0 + Duration::from_secs(601);
assert!(
ml.requests
.check(&EndpointId::from_bytes([99u8; 32]), later)
.is_ok()
);
assert_eq!(
ml.requests.tracked(),
1,
"idle buckets evicted; the map does not grow without bound"
);
}
#[test]
fn cheap_rejection_allocates_no_buckets_for_strangers() {
let limiter = RateLimiter::unlimited_shared();
assert_eq!(
limiter.tracked(),
0,
"no bucket exists before any authorized request"
);
limiter
.check(&EndpointId::from_bytes([1u8; 32]), Instant::now())
.ok(); assert_eq!(
limiter.tracked(),
1,
"only authorized peers ever allocate a bucket"
);
}
#[test]
#[ignore = "literal 10-minute soak; run manually"]
fn literal_20x120_10min() {
let ml = MeshLimiters::from_config(&LimitsCfg {
rate_limit_per_min: 120,
max_inflight: 16,
max_sessions: 4,
});
let start = Instant::now();
let mut served = 0u64;
let mut throttled = 0u64;
while start.elapsed() < Duration::from_secs(600) {
let now = Instant::now();
for peer in 0u8..20 {
match ml.requests.check(&EndpointId::from_bytes([peer; 32]), now) {
Ok(()) => served += 1,
Err(_) => throttled += 1,
}
}
std::thread::sleep(Duration::from_millis(500));
}
assert!(served > 0);
assert_eq!(
ml.requests.tracked(),
20,
"no unbounded memory across the full soak"
);
eprintln!(
"literal soak: served={served} throttled={throttled} tracked={}",
ml.requests.tracked()
);
}