use super::*;
use std::time::{Duration, Instant};
#[test]
fn the_channel_table_covers_both_sub_bands_including_forty_and_forty_four() {
assert_eq!(get_channel(36).unwrap().frequency_mhz, 5180);
assert_eq!(get_channel(40).unwrap().frequency_mhz, 5200);
assert_eq!(get_channel(44).unwrap().frequency_mhz, 5220);
assert_eq!(get_channel(48).unwrap().frequency_mhz, 5240);
assert_eq!(get_channel(165).unwrap().frequency_mhz, 5825);
assert!(get_channel(1).is_none());
for ch in STANDARD_CHANNELS {
assert_eq!(ch.bandwidth_mhz, 20);
}
}
#[test]
fn the_radio_block_resolves_a_channel_40_frequency() {
let mut status = Map::new();
status.insert("actual_channel".to_string(), json!(40));
let block = build_radio_block(Some(&status));
assert_eq!(block["freq_mhz"], json!(5200));
let mut status44 = Map::new();
status44.insert("actual_channel".to_string(), json!(44));
assert_eq!(build_radio_block(Some(&status44))["freq_mhz"], json!(5220));
}
#[test]
fn the_absent_block_omits_exactly_the_three_skip_serializing_keys() {
let absent = radio_absent_block();
let populated = build_radio_block(Some(&Map::new()));
let a = absent.as_object().unwrap();
let p = populated.as_object().unwrap();
assert_eq!(p.len(), 40, "the populated block is the 40-field set");
assert_eq!(a.len(), 37, "the absent skeleton is the 37-field set");
let mut only_populated: Vec<&str> = p
.keys()
.filter(|k| !a.contains_key(*k))
.map(|k| k.as_str())
.collect();
only_populated.sort_unstable();
assert_eq!(
only_populated,
vec!["restart_count", "tx_bytes_per_s", "tx_zombie_kills"]
);
assert!(
a.keys().all(|k| p.contains_key(k)),
"the absent skeleton must not carry a key the populated block lacks"
);
}
#[test]
fn the_verdict_fields_are_null_rather_than_a_fabricated_false() {
let block = build_radio_block(Some(&Map::new()));
for key in [
"adapter_injection_ok",
"adapter_usb_degraded",
"phy_muted",
"rf_unverified",
] {
assert_eq!(block[key], Value::Null, "{key} must not fabricate a false");
}
let mut status = Map::new();
status.insert("adapter_injection_ok".to_string(), json!(false));
status.insert("phy_muted".to_string(), json!(true));
let block = build_radio_block(Some(&status));
assert_eq!(block["adapter_injection_ok"], json!(false));
assert_eq!(block["phy_muted"], json!(true));
}
#[test]
fn a_stale_view_reports_no_rf_verdict() {
let mut status = Map::new();
status.insert("state".to_string(), json!("stale"));
status.insert("rf_unverified".to_string(), json!(false));
assert_eq!(
build_radio_block(Some(&status))["rf_unverified"],
Value::Null
);
}
#[test]
fn the_rssi_sentinel_reads_as_no_reading() {
let mut status = Map::new();
status.insert("rssi_dbm".to_string(), json!(RSSI_SENTINEL));
assert_eq!(build_radio_block(Some(&status))["rssi_dbm"], Value::Null);
status.insert("rssi_dbm".to_string(), json!(-61.5));
assert_eq!(build_radio_block(Some(&status))["rssi_dbm"], json!(-61.5));
}
#[test]
fn an_absent_stats_file_degrades_to_the_bare_base() {
let dir = tempfile::tempdir().unwrap();
let cfg = WfbStatusConfig {
channel: 149,
..Default::default()
};
let body = build_status_from_stats_file_at(&cfg, &dir.path().join("wfb-stats.json"));
let obj = body.as_object().unwrap();
assert_eq!(obj["state"], json!("disabled"));
assert_eq!(obj["channel"], json!(149));
assert_eq!(
obj["actual_channel"],
Value::Null,
"no producer, no live channel"
);
assert!(!obj.contains_key("bitrate_mbps"));
assert_eq!(obj["frequency_mhz"], json!(0));
}
#[test]
fn a_fresh_stats_file_merges_and_finalizes() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("wfb-stats.json");
std::fs::write(
&path,
r#"{"state":"active","interface":"wlan1","actual_channel":40,"bitrate_kbps":4057,"rssi_dbm":-40.0}"#,
)
.unwrap();
let body = build_status_from_stats_file_at(&WfbStatusConfig::default(), &path);
let obj = body.as_object().unwrap();
assert_eq!(obj["state"], json!("active"));
assert_eq!(obj["frequency_mhz"], json!(5200));
assert_eq!(obj["bandwidth_mhz"], json!(20));
assert_eq!(obj["bitrate_mbps"], json!(4.057));
}
#[test]
fn a_stale_stats_file_flips_state_and_drops_the_rf_verdict() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("wfb-stats.json");
std::fs::write(
&path,
r#"{"state":"active","rf_unverified":false,"actual_channel":149}"#,
)
.unwrap();
let old =
std::time::SystemTime::now() - std::time::Duration::from_secs(WFB_STALE_AGE_S as u64 + 5);
std::fs::File::options()
.write(true)
.open(&path)
.unwrap()
.set_modified(old)
.unwrap();
let body = build_status_from_stats_file_at(&WfbStatusConfig::default(), &path);
let obj = body.as_object().unwrap().clone();
assert_eq!(obj["state"], json!("stale"));
assert_eq!(build_radio_block(Some(&obj))["rf_unverified"], Value::Null);
}
#[test]
fn a_stored_event_older_than_the_ceiling_reads_stale() {
let mut detail = Map::new();
detail.insert("state".to_string(), json!("active"));
let fresh = derive_wfb_status(&detail, now_unix_micros(), &WfbStatusConfig::default());
assert_eq!(fresh["state"], json!("active"));
let old_ts = now_unix_micros() - WFB_STALE_AGE_US - 1;
let stale = derive_wfb_status(&detail, old_ts, &WfbStatusConfig::default());
assert_eq!(stale["state"], json!("stale"));
let unknown = derive_wfb_status(&detail, 0, &WfbStatusConfig::default());
assert_eq!(unknown["state"], json!("active"));
}
#[test]
fn the_config_slice_defaults_when_the_file_or_section_is_missing() {
let dir = tempfile::tempdir().unwrap();
let missing = dir.path().join("nope.yaml");
assert_eq!(WfbStatusConfig::load(&missing).channel, 149);
let cfg_path = dir.path().join("config.yaml");
std::fs::write(&cfg_path, "agent:\n name: x\n").unwrap();
assert_eq!(WfbStatusConfig::load(&cfg_path).channel, 149);
std::fs::write(
&cfg_path,
"video:\n wfb:\n channel: 157\n tx_power_dbm: 10\n",
)
.unwrap();
let cfg = WfbStatusConfig::load(&cfg_path);
assert_eq!(cfg.channel, 157);
assert_eq!(cfg.tx_power_dbm, json!(10));
assert_eq!(cfg.topology, json!("host_vbus"));
assert_eq!(cfg.tx_power_max_dbm, json!(15));
}
#[test]
fn a_regulatory_reading_is_served_inside_its_window_and_falls_due_after_it() {
let cache = RegDomainCache::new();
let t0 = Instant::now();
assert_eq!(
cache.peek_at(t0, REG_DOMAIN_TTL),
(REG_DOMAIN_UNKNOWN.to_string(), true)
);
cache.store_at(t0, "US".to_string());
let t1 = t0 + Duration::from_secs(1);
assert_eq!(cache.peek_at(t1, REG_DOMAIN_TTL), ("US".to_string(), false));
assert_eq!(cache.peek_at(t1, REG_DOMAIN_TTL), ("US".to_string(), false));
let t2 = t0 + REG_DOMAIN_TTL;
assert_eq!(cache.peek_at(t2, REG_DOMAIN_TTL), ("US".to_string(), true));
cache.store_at(t2, "IN".to_string());
assert_eq!(cache.peek_at(t2, REG_DOMAIN_TTL), ("IN".to_string(), false));
cache.store_at(t2, REG_DOMAIN_UNKNOWN.to_string());
assert_eq!(
cache.peek_at(t2, REG_DOMAIN_TTL),
(REG_DOMAIN_UNKNOWN.to_string(), false)
);
}
#[test]
fn only_one_refresh_is_in_flight_at_a_time() {
let cache = RegDomainCache::new();
assert!(cache.claim_refresh());
assert!(!cache.claim_refresh());
assert!(!cache.claim_refresh());
cache.release_refresh();
assert!(cache.claim_refresh());
}
#[test]
fn regulatory_domain_never_spawns_on_the_callers_thread() {
assert_eq!(regulatory_domain(), REG_DOMAIN_UNKNOWN);
}
#[test]
fn the_reg_get_parse_reads_a_country_a_global_domain_and_nothing_else() {
assert_eq!(parse_reg_get("global\ncountry US: DFS-FCC\n"), "global");
assert_eq!(parse_reg_get("country US: DFS-FCC\n"), "US");
assert_eq!(parse_reg_get(" country IN: DFS-ETSI\n"), "IN");
assert_eq!(parse_reg_get("country : DFS-FCC\n"), REG_DOMAIN_UNKNOWN);
assert_eq!(parse_reg_get(""), REG_DOMAIN_UNKNOWN);
assert_eq!(parse_reg_get("phy#0\n\tband 1:\n"), REG_DOMAIN_UNKNOWN);
}
#[test]
fn a_node_with_no_radio_reports_no_signal_measurement_at_all() {
let base = wfb_base_block(&WfbStatusConfig::default());
for key in ["rssi_dbm", "noise_dbm", "snr_db"] {
assert_eq!(
base.get(key),
Some(&Value::Null),
"{key} must not fabricate a reading on a node with no radio"
);
}
let block = build_radio_block(Some(&base));
for key in ["rssi_dbm", "noise_dbm", "snr_db"] {
assert_eq!(block[key], Value::Null, "{key} reached the heartbeat");
}
let mut merged = wfb_base_block(&WfbStatusConfig::default());
merged.insert("rssi_dbm".to_string(), json!(-62.0));
merged.insert("noise_dbm".to_string(), json!(-94.0));
merged.insert("snr_db".to_string(), json!(0.0));
let live = build_radio_block(Some(&merged));
assert_eq!(live["rssi_dbm"], json!(-62.0));
assert_eq!(live["noise_dbm"], json!(-94.0));
assert_eq!(live["snr_db"], json!(0.0));
}