use super::{EstateView, Step};
pub const HEARD_WITHIN_MS: u64 = 30 * 60_000;
pub const PAIRED_WITHIN_MS: u64 = 10 * 60_000;
pub const RECHARGED_WITHIN_MS: u64 = 6 * 3_600_000;
pub fn order(to: &str, kind: &str, body: &[(&str, &str)]) -> Step {
Step::Order { role: to.into(), kind: kind.into(), body: body.iter().map(|(k, v)| (k.to_string(), v.to_string())).collect() }
}
pub fn wait(r: &str, what: &str, within_ms: u64) -> Step {
Step::Await { role: r.into(), what: what.into(), within_ms }
}
pub fn recharge() -> Step {
order(super::role::MAIN, "recharge_from_twin", &[("main", "$addr:main"), ("twin", "$addr:twin"), ("accept_lost", "false"), ("epoch", "$epoch")])
}
pub fn restate() -> Step {
order(super::role::MAIN, "restate_entitlements", &[("snapshot", "$entitlements")])
}
pub fn heard(v: &EstateView, r: &str) -> String {
match v.get(r) {
None => format!("the {r}: no box registered"),
Some(b) if b.heard_unix_ms == 0 => format!("the {r}: never heard since its key was registered"),
Some(b) => {
let words: Vec<String> = b.health.iter().map(|(k, v)| format!("{k}={v}")).collect();
format!("the {r}: last report {} s ago ({})", v.now_unix_ms.saturating_sub(b.heard_unix_ms) / 1000, words.join(" "))
}
}
}
pub fn word<'a>(v: &'a EstateView, r: &str, key: &str) -> &'a str {
v.get(r).map(|b| b.word(key)).unwrap_or("")
}
#[cfg(test)]
mod tests {
use super::super::{role, BoxView};
use super::*;
use std::collections::BTreeMap;
fn view() -> EstateView {
let mut boxes = BTreeMap::new();
boxes.insert("main".to_string(), BoxView { role: "main".into(), heard_unix_ms: 40_000, health: BTreeMap::from([("data".into(), "ok".into()), ("serving".into(), "yes".into())]), ..BoxView::default() });
boxes.insert("twin".to_string(), BoxView { role: "twin".into(), ..BoxView::default() });
EstateView { product: "p".into(), instance: "acme".into(), now_unix_ms: 100_000, silent_after_ms: 90_000, boxes }
}
#[test]
fn an_order_and_a_wait_are_the_steps_the_runner_reads() {
assert_eq!(order(role::TWIN, "become_twin", &[("primary", "$addr:main")]), Step::Order { role: "twin".into(), kind: "become_twin".into(), body: BTreeMap::from([("primary".to_string(), "$addr:main".to_string())]) });
assert_eq!(wait(role::MAIN, "paired", PAIRED_WITHIN_MS), Step::Await { role: "main".into(), what: "paired".into(), within_ms: 600_000 });
assert_eq!((HEARD_WITHIN_MS, RECHARGED_WITHIN_MS), (1_800_000, 21_600_000));
}
#[test]
fn the_recharge_and_the_restate_name_the_main_and_the_runners_fills() {
let Step::Order { role, kind, body } = recharge() else { panic!("an order") };
assert_eq!((role.as_str(), kind.as_str()), ("main", "recharge_from_twin"));
assert_eq!(body.iter().map(|(k, v)| format!("{k}={v}")).collect::<Vec<_>>(), ["accept_lost=false", "epoch=$epoch", "main=$addr:main", "twin=$addr:twin"]);
assert_eq!(restate(), order("main", "restate_entitlements", &[("snapshot", "$entitlements")]));
}
#[test]
fn the_evidence_says_when_each_box_was_heard_and_what_it_said() {
let v = view();
assert_eq!(heard(&v, "main"), "the main: last report 60 s ago (data=ok serving=yes)");
assert_eq!(heard(&v, "twin"), "the twin: never heard since its key was registered");
assert_eq!(heard(&v, "front"), "the front: no box registered");
assert_eq!((word(&v, "main", "serving"), word(&v, "main", "twin"), word(&v, "front", "data")), ("yes", "", ""));
}
}