use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use std::time::Duration;
use gunnar_ssh::governor::OpClass;
use gunnar_ssh::gunnar_core::ANONYMOUS_PRINCIPAL_ID;
use gunnar_ssh::{Admission, Refusal};
fn gate(limit: usize, keyed_wait: Option<Duration>) -> Arc<Admission> {
Arc::new(
Admission::new(Some(limit), Some(8))
.with_anonymous_ceiling(Some(5))
.with_wait(Duration::from_millis(50))
.with_keyed_wait(keyed_wait),
)
}
async fn burst(gate: &Arc<Admission>, principal: &'static str, n: usize, hold: Duration) -> (usize, Vec<Refusal>) {
let admitted = Arc::new(AtomicUsize::new(0));
let mut tasks = tokio::task::JoinSet::new();
for _ in 0..n {
let gate = Arc::clone(gate);
let admitted = Arc::clone(&admitted);
tasks.spawn(async move {
match gate.admit_within(principal, "nordisk", OpClass::Fetch).await {
Ok((guard, _waited)) => {
admitted.fetch_add(1, Ordering::SeqCst);
tokio::time::sleep(hold).await;
drop(guard);
None
}
Err(refusal) => Some(refusal),
}
});
}
let mut refusals = Vec::new();
while let Some(done) = tasks.join_next().await {
if let Some(r) = done.expect("a transfer task") {
refusals.push(r);
}
}
(admitted.load(Ordering::SeqCst), refusals)
}
#[tokio::test(flavor = "multi_thread", worker_threads = 8)]
async fn forty_keyed_transfers_are_all_served_and_forty_strangers_get_their_ceiling() {
let keyed = gate(8, Some(Duration::from_secs(20)));
let (admitted, refusals) = burst(&keyed, "admin", 40, Duration::from_millis(150)).await;
assert!(
refusals.is_empty(),
"a keyed CI burst was refused although it may queue for 20 s and every slot frees in \
150 ms: {} refused, first {:?}",
refusals.len(),
refusals.first()
);
assert_eq!(admitted, 40);
assert_eq!(keyed.stats().refused(), 0);
let open = gate(64, Some(Duration::from_secs(20)));
let started = std::time::Instant::now();
let (admitted, refusals) = burst(&open, ANONYMOUS_PRINCIPAL_ID, 40, Duration::from_millis(1500)).await;
assert_eq!(admitted, 5, "a stranger's ceiling is 5 however idle the box is");
assert_eq!(refusals.len(), 35);
for r in &refusals {
assert!(matches!(r, Refusal::AnonymousAtCapacity { limit: 5 }), "{r:?}");
assert!(r.client_message().contains("try again"), "transient words: {}", r.client_message());
}
assert_eq!(open.stats().refused_anonymous, 35, "counted under its own reason");
assert!(started.elapsed() < Duration::from_secs(5), "{:?}", started.elapsed());
let old = gate(8, None);
let (admitted, refusals) = burst(&old, "admin", 40, Duration::from_millis(150)).await;
assert!(
!refusals.is_empty() && admitted < 40,
"the control arm (2026-10-25 behaviour: the 50 ms wait for keys too) must refuse some of \
the burst, or this test proves nothing: admitted {admitted}"
);
}