use super::*;
use std::thread;
fn nid(byte: u8) -> NodeId {
NodeId::from_bytes([byte; 32])
}
fn h(byte: u8) -> Hash {
Hash::from_bytes([byte; 32])
}
#[test]
fn absent_key_returns_false() {
let c = NegativeProbeCache::new();
assert!(!c.contains_active(&nid(1), &h(1)));
assert!(c.is_empty());
}
#[test]
fn record_then_contains_returns_true() {
let c = NegativeProbeCache::new();
c.record_failure(nid(1), h(1));
assert!(c.contains_active(&nid(1), &h(1)));
assert!(!c.contains_active(&nid(2), &h(1)));
assert!(!c.contains_active(&nid(1), &h(2)));
assert_eq!(c.len(), 1);
}
#[test]
fn a_short_ttl_entry_expires_while_a_default_one_is_still_active() {
let c = NegativeProbeCache::new(); c.record_failure(nid(1), h(1)); c.record_failure_with_ttl(nid(2), h(1), Duration::from_millis(30));
assert!(c.contains_active(&nid(1), &h(1)));
assert!(c.contains_active(&nid(2), &h(1)));
thread::sleep(Duration::from_millis(60));
assert!(
c.contains_active(&nid(1), &h(1)),
"the default-TTL entry must outlive the short one"
);
assert!(
!c.contains_active(&nid(2), &h(1)),
"a short-TTL entry must expire on its OWN clock — if it inherited the cache \
default, a healthy peer stays blackholed long after the transient cause passed"
);
}
#[test]
fn expired_entry_returns_false_and_is_evicted() {
let c = NegativeProbeCache::with_capacity_and_ttl(8, Duration::from_millis(500));
c.record_failure(nid(1), h(1));
assert!(c.contains_active(&nid(1), &h(1)));
thread::sleep(Duration::from_millis(750));
assert!(!c.contains_active(&nid(1), &h(1)));
assert!(
c.is_empty(),
"expired entry should have been evicted on read"
);
}
#[test]
fn lru_eviction_at_cap_drops_oldest() {
let c = NegativeProbeCache::with_capacity(2);
c.record_failure(nid(1), h(1));
c.record_failure(nid(2), h(2));
c.record_failure(nid(3), h(3));
assert!(!c.contains_active(&nid(1), &h(1)));
assert!(c.contains_active(&nid(2), &h(2)));
assert!(c.contains_active(&nid(3), &h(3)));
assert_eq!(c.len(), 2);
}
#[test]
fn read_hit_bumps_lru_so_oldest_eviction_changes() {
let c = NegativeProbeCache::with_capacity(2);
c.record_failure(nid(1), h(1));
c.record_failure(nid(2), h(2));
assert!(c.contains_active(&nid(1), &h(1)));
c.record_failure(nid(3), h(3));
assert!(c.contains_active(&nid(1), &h(1)));
assert!(!c.contains_active(&nid(2), &h(2)));
assert!(c.contains_active(&nid(3), &h(3)));
}
#[test]
fn shift_insert_on_existing_key_moves_to_front_of_lru() {
let c = NegativeProbeCache::with_capacity(4);
c.record_failure(nid(1), h(1));
c.record_failure(nid(2), h(2));
c.record_failure(nid(3), h(3));
c.record_failure(nid(1), h(1));
let guard = c.lock();
assert_eq!(
guard.entries.get_index_of(&(nid(1), h(1))),
Some(0),
"shift_insert should have moved nid(1) to index 0"
);
}
#[test]
fn read_hit_does_not_refresh_ttl() {
let c = NegativeProbeCache::with_capacity_and_ttl(8, Duration::from_millis(500));
c.record_failure(nid(1), h(1));
thread::sleep(Duration::from_millis(250));
assert!(c.contains_active(&nid(1), &h(1)));
thread::sleep(Duration::from_millis(500));
assert!(
!c.contains_active(&nid(1), &h(1)),
"read-hit illegally extended the TTL window"
);
}
#[test]
fn re_recording_same_key_refreshes_ttl_does_not_grow_len() {
let c = NegativeProbeCache::with_capacity_and_ttl(8, Duration::from_secs(1));
c.record_failure(nid(1), h(1));
thread::sleep(Duration::from_millis(500));
c.record_failure(nid(1), h(1));
thread::sleep(Duration::from_millis(750));
assert!(c.contains_active(&nid(1), &h(1)));
assert_eq!(c.len(), 1);
}