use super::*;
fn nid(byte: u8) -> NodeId {
NodeId::from_bytes([byte; 32])
}
fn fresh_state() -> (Arc<RwLock<HashSet<NodeId>>>, Arc<Metrics>) {
(
Arc::new(RwLock::new(HashSet::new())),
Arc::new(Metrics::new()),
)
}
#[test]
fn apply_change_active_idempotent() {
let (active, metrics) = fresh_state();
assert!(
apply_change(&active, &metrics, StakerChange::Active(nid(1))),
"first insert of an absent id is a real change"
);
assert!(active.read().unwrap().contains(&nid(1)));
assert!(
!apply_change(&active, &metrics, StakerChange::Active(nid(1))),
"re-inserting a present id must report no change"
);
assert_eq!(active.read().unwrap().len(), 1);
}
#[test]
fn apply_change_inactive_idempotent() {
let (active, metrics) = fresh_state();
active.write().unwrap().insert(nid(1));
assert!(
apply_change(&active, &metrics, StakerChange::Inactive(nid(1))),
"removing a present id is a real change"
);
assert!(!active.read().unwrap().contains(&nid(1)));
assert!(
!apply_change(&active, &metrics, StakerChange::Inactive(nid(2))),
"removing an id that was never present must report no change"
);
}
#[test]
fn apply_change_updates_active_count_gauge_only_on_real_change() {
let (active, metrics) = fresh_state();
apply_change(&active, &metrics, StakerChange::Active(nid(1)));
apply_change(&active, &metrics, StakerChange::Active(nid(2)));
let text = metrics.encode().unwrap();
assert!(
text.lines().any(|l| l == "decdn_staker_set_active_count 2"),
"active-count gauge should report 2 after two distinct inserts:\n{text}"
);
apply_change(&active, &metrics, StakerChange::Active(nid(1)));
let text = metrics.encode().unwrap();
assert!(
text.lines().any(|l| l == "decdn_staker_set_active_count 2"),
"active-count gauge should stay at 2 after an idempotent re-insert:\n{text}"
);
apply_change(&active, &metrics, StakerChange::Inactive(nid(1)));
let text = metrics.encode().unwrap();
assert!(
text.lines().any(|l| l == "decdn_staker_set_active_count 1"),
"active-count gauge should report 1 after a removal:\n{text}"
);
}
#[test]
fn watcher_error_restart_counts_one_per_drift_window() {
let metrics = Arc::new(Metrics::new());
let text = metrics.encode().unwrap();
assert!(
text.lines()
.any(|l| l == "decdn_staker_set_watcher_restarts_total 0"),
"restart counter should start at zero:\n{text}"
);
metrics.staker_set_watcher_backoff_started();
metrics.staker_set_watcher_backoff_started();
metrics.staker_set_watcher_backoff_started();
let text = metrics.encode().unwrap();
assert!(
text.lines()
.any(|l| l == "decdn_staker_set_watcher_restarts_total 1"),
"one continuous outage should count exactly one restart:\n{text}"
);
metrics.staker_set_watcher_cycle_established();
metrics.staker_set_watcher_backoff_started();
let text = metrics.encode().unwrap();
assert!(
text.lines()
.any(|l| l == "decdn_staker_set_watcher_restarts_total 2"),
"a second distinct outage should count a second restart:\n{text}"
);
}
#[test]
fn watcher_resolve_failure_bumps_counter() {
let metrics = Arc::new(Metrics::new());
let text = metrics.encode().unwrap();
assert!(
text.lines()
.any(|l| l == "decdn_staker_set_watcher_resolve_failures_total 0"),
"resolve-failure counter should start at zero:\n{text}"
);
metrics.staker_set_watcher_resolve_failure();
metrics.staker_set_watcher_resolve_failure();
let text = metrics.encode().unwrap();
assert!(
text.lines()
.any(|l| l == "decdn_staker_set_watcher_resolve_failures_total 2"),
"expected 2 resolve failures:\n{text}"
);
}