use super::*;
#[test]
fn every_golden_line_passes_the_exchange_once() -> TestResult {
let topology: serde_json::Value = serde_json::from_slice(TOPOLOGY_GOLDEN)?;
let this = topology["this_device"]
.as_str()
.ok_or("the golden names no device")?;
let lines = topology["lines"].as_array().ok_or("no lines")?;
assert!(!lines.is_empty());
for line in lines {
let stops = line["stops"].as_array().ok_or("a line with no stops")?;
let exchanges = stops
.iter()
.filter(|stop| stop.as_str() == Some(this))
.count();
assert_eq!(exchanges, 1, "{line}");
}
let exchange = topology["stations"]
.as_array()
.and_then(|stations| stations.iter().find(|station| station["id"] == this))
.ok_or("the exchange is not a listed station")?;
assert_eq!(
(&exchange["kind"], &exchange["device"]),
(&serde_json::json!("exchange"), &topology["this_device"])
);
Ok(())
}
#[test]
fn every_golden_hop_names_a_station_in_order_without_dwell() -> TestResult {
let topology: serde_json::Value = serde_json::from_slice(TOPOLOGY_GOLDEN)?;
let batch: serde_json::Value = serde_json::from_slice(TAP_BATCH_GOLDEN)?;
let stations: Vec<&str> = topology["stations"]
.as_array()
.ok_or("no stations")?
.iter()
.filter_map(|station| station["id"].as_str())
.collect();
let this = topology["this_device"].as_str().ok_or("no device")?;
let depot = format!("{this}.depot");
let messages = batch["messages"].as_array().ok_or("no messages")?;
let mut durable = 0;
let mut unmeasured = 0;
for message in messages {
let hops = message["hops"].as_array().ok_or("no hops")?;
let nodes: Vec<&str> = hops.iter().filter_map(|hop| hop["node"].as_str()).collect();
assert_eq!(nodes.len(), hops.len(), "{message}");
for node in &nodes {
assert!(stations.contains(node), "{node} is not a listed station");
}
let times: Vec<u64> = hops
.iter()
.filter_map(|hop| hop["at_ms"].as_u64())
.collect();
assert_eq!(times.len(), hops.len(), "{message}");
assert!(
times.is_sorted(),
"{message}: a hop before the one ahead of it"
);
assert!(
hops.iter().all(|hop| hop.get("dwell_ms").is_none()),
"{message}"
);
let exchange = nodes.iter().position(|node| *node == this);
assert!(nodes.iter().filter(|node| **node == this).count() <= 1);
unmeasured += usize::from(exchange.is_none());
if let Some(at) = nodes.iter().position(|node| *node == depot) {
durable += 1;
assert_eq!(
Some(at + 1),
exchange,
"{message}: the depot, then the exchange"
);
}
let receivers = message["to_stations"].as_array().map_or(0, Vec::len);
let publisher = usize::from(message.get("from_station").is_some());
assert_eq!(
exchange.map_or(nodes.len() - publisher, |at| nodes.len() - at - 1),
receivers,
"{message}: each receiver after the exchange, or after the publisher"
);
}
assert!(durable > 0, "a durable golden message passes the depot");
assert!(messages.len() > durable, "and another does not");
assert!(
unmeasured > 0,
"a golden message whose exchange nothing measured"
);
Ok(())
}
#[test]
fn existing_canonical_bytes_are_unchanged_without_links() -> TestResult {
let mut record = topology();
record.links.clear();
let mut golden: serde_json::Value = serde_json::from_slice(TOPOLOGY_GOLDEN)?;
golden["links"] = serde_json::json!([]);
let encoded = canonical_bytes(&record, "topology")?;
assert_eq!(encoded, serde_json::to_vec(&golden)?);
assert_eq!(encoded, normalized(&encoded)?);
assert_eq!(canonical_bytes(&topology(), "topology")?, TOPOLOGY_GOLDEN);
Ok(())
}
#[test]
fn a_federated_link_serialises_its_carrier_relay_and_reason() -> TestResult {
for (carrier, word, relayed) in [
(Carrier::Wire, "wire", None),
(Carrier::Direct, "direct", None),
(Carrier::Relay("hub".to_owned()), "relay", Some("hub")),
] {
let link = Link::federated(
"local",
"edge",
Some(carrier),
LinkState::Down,
Some("peer gone".to_owned()),
LinkCounters {
crossed: Some(7),
refused: Some(0),
..LinkCounters::default()
},
);
let encoded = canonical_bytes(&link, "link")?;
let expected = serde_json::json!({
"id": "local~edge", "name": "edge", "kind": "federated", "state": "down",
"rtt_ms": null, "ends": ["edge", "local"], "carrier": word,
"reason": "peer gone", "crossed": 7, "refused": 0,
"carrier_refusals": ["peer gone"],
});
let mut expected = expected;
if let Some(relayed) = relayed {
expected["relayed"] = serde_json::json!(relayed);
expected["carried_by"] = serde_json::json!(relayed);
}
assert_eq!(encoded, serde_json::to_vec(&expected)?);
assert_eq!(encoded, normalized(&encoded)?);
}
Ok(())
}
#[test]
fn a_down_link_carries_its_reason_and_an_up_one_none() -> TestResult {
for (state, reason) in [(LinkState::Down, Some("revoked")), (LinkState::Up, None)] {
let link = Link::federated(
"edge",
"local",
None,
state,
Some("revoked".to_owned()),
LinkCounters::default(),
);
let value = serde_json::to_value(&link)?;
assert_eq!(
value["state"],
if state == LinkState::Down {
"down"
} else {
"up"
}
);
assert_eq!(
value.get("reason").and_then(serde_json::Value::as_str),
reason
);
for field in ["carrier", "relayed", "crossed", "refused"] {
assert!(value.get(field).is_none(), "{field}: unknown is omitted");
}
}
Ok(())
}