use std::collections::HashMap;
use std::sync::OnceLock;
use crate::core::types::{ExchangeId, ValidationStamp};
const SNAPSHOT_JSON: &str = include_str!("../../../data/validation_snapshot.json");
static SNAPSHOT: OnceLock<HashMap<ExchangeId, ValidationStamp>> = OnceLock::new();
fn load() -> HashMap<ExchangeId, ValidationStamp> {
let raw: HashMap<String, ValidationStamp> = serde_json::from_str(SNAPSHOT_JSON)
.expect("data/validation_snapshot.json is malformed — fix the JSON or regenerate");
raw.into_iter()
.filter_map(|(k, v)| ExchangeId::from_str(&k).map(|id| (id, v)))
.collect()
}
pub fn validation_for(id: ExchangeId) -> Option<&'static ValidationStamp> {
SNAPSHOT.get_or_init(load).get(&id)
}
pub fn all_entries() -> Vec<(ExchangeId, &'static ValidationStamp)> {
let map = SNAPSHOT.get_or_init(load);
let mut entries: Vec<_> = map.iter().map(|(&id, v)| (id, v)).collect();
entries.sort_by_key(|(id, _)| id.as_str());
entries
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn snapshot_loads_without_panic() {
let _ = load();
}
#[test]
fn at_least_five_known_exchanges_present() {
let map = load();
for id in [
ExchangeId::Binance,
ExchangeId::Bybit,
ExchangeId::OKX,
ExchangeId::KuCoin,
ExchangeId::Kraken,
] {
assert!(
map.contains_key(&id),
"Expected {:?} in validation snapshot",
id
);
}
}
#[test]
fn binance_is_validated() {
let map = load();
let stamp = map.get(&ExchangeId::Binance).expect("Binance missing from snapshot");
assert!(
!stamp.harness_version.is_empty(),
"harness_version is empty for Binance"
);
assert!(stamp.rest.contains_key("get_ticker"));
let ticker = &stamp.rest["get_ticker"];
assert!(ticker.is_validated(), "Binance get_ticker should be Validated");
}
#[test]
fn bitstamp_ws_is_failed() {
let map = load();
let stamp = map.get(&ExchangeId::Bitstamp).expect("Bitstamp missing from snapshot");
let ws_ticker = &stamp.ws["Ticker"];
assert!(
!ws_ticker.is_validated(),
"Bitstamp WS Ticker was silent — should NOT be Validated"
);
}
#[test]
fn upbit_ws_is_populated_but_empty() {
use crate::core::types::FieldValidation;
let map = load();
let stamp = map.get(&ExchangeId::Upbit).expect("Upbit missing from snapshot");
assert!(
matches!(stamp.ws["Ticker"], FieldValidation::PopulatedButEmpty { .. }),
"Upbit WS Ticker had parser bug — should be PopulatedButEmpty"
);
}
#[test]
fn validation_for_static_lookup_consistent() {
let from_static = validation_for(ExchangeId::Binance);
let from_fresh = load();
assert!(from_static.is_some());
assert_eq!(from_static.unwrap(), from_fresh.get(&ExchangeId::Binance).unwrap());
}
#[test]
fn entry_count_at_least_20() {
let map = load();
assert!(
map.len() >= 20,
"Expected at least 20 entries in snapshot, got {}",
map.len()
);
}
}