use kshana::verification::{
to_ledger_json, to_modelled_rationale_md, to_verification_matrix_md, verification_matrix,
};
use std::path::Path;
fn root() -> &'static Path {
Path::new(env!("CARGO_MANIFEST_DIR"))
}
fn assert_in_sync(rel: &str, regenerated: String) {
let path = root().join(rel);
let committed = std::fs::read_to_string(&path).unwrap_or_else(|e| {
panic!("read committed {rel}: {e} — run `cargo run --bin gen_validation_artifacts`")
});
assert_eq!(
regenerated, committed,
"{rel} is out of sync with verification_matrix(); regenerate with \
`cargo run --bin gen_validation_artifacts` and commit the result"
);
}
#[test]
fn ledger_json_matches_the_matrix() {
let m = verification_matrix();
assert_in_sync(
"web/data/verification-matrix.json",
to_ledger_json(&m, root()),
);
}
#[test]
fn verification_matrix_md_matches_the_matrix() {
let m = verification_matrix();
assert_in_sync("docs/VERIFICATION-MATRIX.md", to_verification_matrix_md(&m));
}
#[test]
fn modelled_rationale_md_matches_the_matrix() {
let m = verification_matrix();
assert_in_sync("docs/MODELLED-RATIONALE.md", to_modelled_rationale_md(&m));
}
#[test]
fn card_matrix_map_references_real_rows_and_covers_every_card() {
let reqs: std::collections::HashSet<String> = verification_matrix()
.iter()
.map(|i| i.requirement.to_string())
.collect();
let map_raw = std::fs::read_to_string(root().join("web/data/card-matrix-map.json"))
.expect("read card-matrix-map.json");
let map: serde_json::Map<String, serde_json::Value> =
serde_json::from_str(&map_raw).expect("parse card-matrix-map.json");
for (card, v) in &map {
let arr = v
.as_array()
.unwrap_or_else(|| panic!("card {card} value must be an array"));
assert!(!arr.is_empty(), "card {card} maps to no matrix rows");
for r in arr {
let req = r.as_str().expect("requirement must be a string");
assert!(
reqs.contains(req),
"card {card:?} maps to {req:?}, which is not a verification_matrix() requirement"
);
}
}
let caps_raw = std::fs::read_to_string(root().join("web/capabilities.json"))
.expect("read capabilities.json");
let caps: serde_json::Value = serde_json::from_str(&caps_raw).expect("parse capabilities.json");
let cards = caps["capabilities"]
.as_array()
.or_else(|| caps.as_array())
.expect("capabilities array");
for c in cards {
let name = c["name"].as_str().expect("card name");
assert!(
map.contains_key(name),
"capability card {name:?} has no entry in card-matrix-map.json"
);
}
}
#[test]
fn oracle_references_have_no_dead_entries() {
let oracles: Vec<&'static str> = verification_matrix().iter().map(|i| i.oracle).collect();
let appears = |needle: &str| oracles.iter().any(|o| o.contains(needle));
let raw = std::fs::read_to_string(root().join("web/data/oracle-references.json"))
.expect("read oracle-references.json");
let refs: Vec<serde_json::Value> =
serde_json::from_str(&raw).expect("parse oracle-references.json (expected an array)");
assert!(!refs.is_empty(), "oracle-references.json is empty");
let mut seen_urls = std::collections::HashSet::new();
for e in &refs {
let label = e["label"].as_str().expect("entry needs a string label");
assert!(!label.trim().is_empty(), "entry {e:?} has an empty label");
let url = e["url"].as_str().expect("entry needs a string url");
assert!(
url.starts_with("https://") || url.starts_with("http://"),
"entry {label:?} has a non-http(s) url: {url:?}"
);
assert!(
seen_urls.insert(url.to_string()),
"duplicate url {url:?} (entry {label:?}) — collapse into one entry, \
oracleSources() de-dupes by url and would drop the second silently"
);
let matches = e["match"]
.as_array()
.unwrap_or_else(|| panic!("entry {label:?} needs a `match` array"));
assert!(
!matches.is_empty(),
"entry {label:?} has an empty `match` array"
);
for m in matches {
let needle = m.as_str().expect("each `match` token must be a string");
assert!(
appears(needle),
"oracle-references entry {label:?} matches {needle:?}, which appears in no \
verification_matrix() oracle — prune it or fix the spelling"
);
}
}
}
#[test]
fn standards_matrix_map_references_real_rows_and_standards() {
let reqs: std::collections::HashSet<String> = verification_matrix()
.iter()
.map(|i| i.requirement.to_string())
.collect();
let caps_raw = std::fs::read_to_string(root().join("web/capabilities.json"))
.expect("read capabilities.json");
let caps: serde_json::Value = serde_json::from_str(&caps_raw).expect("parse capabilities.json");
let std_names: std::collections::HashSet<String> = caps["standards"]
.as_array()
.expect("capabilities.json has a standards array")
.iter()
.map(|s| s["name"].as_str().expect("standard name").to_string())
.collect();
let map_raw = std::fs::read_to_string(root().join("web/data/standards-matrix-map.json"))
.expect("read standards-matrix-map.json");
let map: serde_json::Map<String, serde_json::Value> =
serde_json::from_str(&map_raw).expect("parse standards-matrix-map.json");
assert!(!map.is_empty(), "standards-matrix-map.json is empty");
for (standard, v) in &map {
assert!(
std_names.contains(standard),
"standards-matrix-map key {standard:?} is not a standard card in capabilities.json"
);
let req = v
.as_str()
.unwrap_or_else(|| panic!("value for {standard:?} must be a string"));
assert!(
reqs.contains(req),
"standard {standard:?} maps to {req:?}, which is not a verification_matrix() requirement"
);
}
}
#[test]
fn ledger_links_resolve_to_committed_files() {
let json = std::fs::read_to_string(root().join("web/data/verification-matrix.json"))
.expect("read ledger json");
let v: serde_json::Value = serde_json::from_str(&json).expect("parse ledger json");
let mut checked = 0usize;
for row in v["rows"].as_array().expect("rows array") {
for group in ["module_links", "test_links"] {
for link in row[group].as_array().into_iter().flatten() {
let p = link["path"].as_str().expect("link path");
assert!(
root().join(p).is_file(),
"ledger links to a non-existent file: {p}"
);
checked += 1;
}
}
if let Some(fx) = row["fixture"].as_object() {
let p = fx["path"].as_str().expect("fixture path");
assert!(
root().join(p).is_dir(),
"ledger fixture path is not a directory: {p}"
);
checked += 1;
}
}
assert!(
checked > 50,
"expected the ledger to carry many links, found {checked}"
);
}