use super::*;
use std::fs;
fn fixture(name: &str) -> String {
let p = Path::new(env!("CARGO_MANIFEST_DIR"))
.join("tests/fixtures/ont4g")
.join(name);
fs::read_to_string(&p).unwrap_or_else(|e| panic!("{}: {e}", p.display()))
}
fn write(root: &Path, rel: &str, text: &str) {
let p = root.join(rel);
fs::create_dir_all(p.parent().unwrap()).unwrap();
fs::write(p, text).unwrap();
}
fn planted(snapshot: &str) -> tempfile::TempDir {
let d = tempfile::tempdir().unwrap();
let r = d.path();
write(
r,
"Cargo.toml",
"[package]\nname = \"aprender\"\n\n[[bin]]\nname = \"apr\"\npath = \"src/bin/apr.rs\"\n\n\
[workspace]\nmembers = [\"crates/*\"]\n",
);
write(r, "src/lib.rs", "");
write(r, "src/bin/apr.rs", "fn main() {}\n");
write(
r,
"crates/apr-cli/Cargo.toml",
"[package]\nname = \"apr-cli\"\n\n[[bin]]\nname = \"apr\"\npath = \"src/main.rs\"\n",
);
write(r, "crates/apr-cli/src/main.rs", "fn main() {}\n");
write(
r,
"crates/aprender-contracts-cli/Cargo.toml",
"[package]\nname = \"aprender-contracts-cli\"\n\n[[bin]]\nname = \"pv\"\npath = \"src/main.rs\"\n",
);
write(
r,
"crates/aprender-contracts-cli/src/main.rs",
"fn main() {}\n",
);
write(r, SNAPSHOT, snapshot);
write(r, LEDGER, &fixture("surface_audit.csv"));
d
}
fn run(snapshot: &str) -> (Graph, BinaryStats) {
let d = planted(snapshot);
let mut g = Graph::default();
let stats = extract_at(d.path(), &mut g);
(g, stats)
}
fn has(g: &Graph, n: &str, pred: &str) -> bool {
!g.objects(n, &bin(pred)).is_empty()
}
fn mutate(package: &str, f: impl Fn(&mut serde_json::Value)) -> String {
fixture("snapshot.jsonl")
.lines()
.map(|l| {
let mut v: serde_json::Value = serde_json::from_str(l).unwrap();
if v["package"] == package {
f(&mut v);
}
v.to_string()
})
.collect::<Vec<_>>()
.join("\n")
}
#[test]
fn the_fixture_is_green_three_nodes_two_names() {
let (g, s) = run(&fixture("snapshot.jsonl"));
assert!(s.errors.is_empty(), "{:?}", s.errors);
assert_eq!((s.targets, s.names, s.census), (3, 2, Some(3)));
assert_eq!(g.instances_of(&ont("Binary")).len(), 3);
assert_eq!(
(s.version_lacks_sha, s.name_pair_mismatch, s.help_failed),
(0, 0, 0)
);
assert_eq!((s.unledgered, s.orphans, s.no_audit_row), (0, 0, 0));
let pv = node("aprender-contracts-cli", "pv");
assert_eq!(
g.instances_of(&bin("target/aprender-contracts-cli/pv")),
[pv.as_str()]
);
assert_eq!(g.objects(&pv, &bin("auditRow")).len(), 2);
assert!(!has(&g, &node("apr-cli", "apr"), "ledgerOrphan"));
}
#[test]
fn two_extractions_are_byte_identical() {
let a = run(&fixture("snapshot.jsonl")).0.to_ntriples();
let b = run(&fixture("snapshot.jsonl")).0.to_ntriples();
assert_eq!(a, b);
assert!(!a.contains("_:"), "no blank nodes (R-15)");
}
#[test]
fn falsify_bin_001_a_dropped_line_is_red_naming_the_target() {
let snap: String = fixture("snapshot.jsonl")
.lines()
.filter(|l| !l.contains("\"package\":\"aprender-contracts-cli\""))
.collect::<Vec<_>>()
.join("\n");
let (_, s) = run(&snap);
assert_eq!((s.targets, s.census), (2, Some(3)));
assert_eq!(s.errors.len(), 1, "{:?}", s.errors);
assert!(s.errors[0].what.contains("aprender-contracts-cli/pv"));
}
#[test]
fn falsify_bin_002_the_apr_pair_is_two_nodes() {
let (g, s) = run(&fixture("snapshot.jsonl"));
let a = node("apr-cli", "apr");
let b = node("aprender", "apr");
assert_ne!(a, b);
assert!(g.instances_of(&ont("Binary")).contains(&a.as_str()));
assert!(g.instances_of(&ont("Binary")).contains(&b.as_str()));
assert_eq!(s.names + 1, s.census.unwrap());
}
#[test]
fn falsify_bin_003_a_version_without_the_commit_fires() {
let snap = mutate("aprender-contracts-cli", |v| {
v["version"] = "pv 0.70.0".into();
});
let (g, s) = run(&snap);
assert_eq!(s.version_lacks_sha, 1);
assert!(has(
&g,
&node("aprender-contracts-cli", "pv"),
"versionLacksSha"
));
assert!(!has(&g, &node("apr-cli", "apr"), "versionLacksSha"));
}
#[test]
fn falsify_bin_004_the_apr_pair_diverging_fires_on_both() {
let snap = mutate("apr-cli", |v| {
v["help_sha256"] = "f".repeat(64).into();
});
let (g, s) = run(&snap);
assert_eq!(s.name_pair_mismatch, 2);
let a = node("apr-cli", "apr");
let b = node("aprender", "apr");
assert_eq!(
g.objects(&a, &bin("namePairMismatch")),
[&Term::iri(b.clone())]
);
assert_eq!(g.objects(&b, &bin("namePairMismatch")), [&Term::iri(a)]);
assert!(!has(
&g,
&node("aprender-contracts-cli", "pv"),
"namePairMismatch"
));
}
#[test]
fn a_foreign_schema_or_an_omitted_array_is_refused_by_line() {
let (_, s) = run(&mutate("aprender", |v| {
v["schema"] = "binary-snapshot/v0".into()
}));
assert!(s
.errors
.iter()
.any(|e| e.what.contains("binary-snapshot/v0")));
let (_, s) = run(&mutate("aprender", |v| {
v.as_object_mut().unwrap().remove("tools");
}));
assert!(s.errors.iter().any(|e| e.what.contains("`tools`")));
assert!(s.errors.iter().any(|e| e.what.contains("aprender/apr")));
}
#[test]
fn a_workspace_root_with_no_snapshot_is_red_not_vacuous() {
let d = planted("");
fs::remove_file(d.path().join(SNAPSHOT)).unwrap();
let s = extract_at(d.path(), &mut Graph::default());
assert_eq!(s.targets, 0);
assert!(
s.errors.iter().any(|e| e.what.contains("absent")),
"{:?}",
s.errors
);
let s = extract_at(Path::new("/nonexistent-ont4g"), &mut Graph::default());
assert!(s.errors.is_empty() && !s.at_workspace_root);
}
#[test]
fn a_workspace_root_with_no_bin_target_needs_no_snapshot() {
let d = tempfile::tempdir().unwrap();
write(
d.path(),
"Cargo.toml",
"[workspace]\nmembers = [\"crates/kern\"]\n",
);
write(
d.path(),
"crates/kern/Cargo.toml",
"[package]\nname = \"kern\"\n",
);
write(d.path(), "crates/kern/src/lib.rs", "");
let s = extract_at(d.path(), &mut Graph::default());
assert_eq!((s.targets, s.census), (0, Some(0)));
assert!(s.errors.is_empty(), "{:?}", s.errors);
}
#[test]
fn the_ledger_join_finds_unledgered_and_orphans_both_ways() {
let snap = mutate("apr-cli", |v| {
v["commands"] = serde_json::json!(["run", "serve", "serve plan", "tune"]);
v["routes"] = serde_json::json!([]);
});
let (g, _) = run(&snap);
let a = node("apr-cli", "apr");
assert_eq!(
g.objects(&a, &bin("unledgeredCommand")),
[&Term::string("tune")]
);
assert_eq!(
g.objects(&a, &bin("ledgerOrphanRoute")),
[&Term::string("GET /api/tags")]
);
assert!(!has(&g, &node("aprender", "apr"), "unledgeredCommand"));
}
#[test]
fn feature_forms_case_table() {
for (name, feature, want) in [
("apr", "apr run", Some("run")),
("apr", "apr serve plan <MODEL>", Some("serve plan")),
("apr", "apr eval --task humaneval", Some("eval")),
("apr", "apr --version", Some("")),
("apr", "apr", Some("")),
("apr", "aprender-orchestrate run", None),
("pv", "apr run", None),
] {
assert_eq!(command_path(name, feature).as_deref(), want, "{feature}");
}
assert_eq!(
route_key("POST /api/chat (stream:true, x)").as_deref(),
Some("POST /api/chat")
);
assert_eq!(route_key("HTTP 404 fallback route index"), None);
assert_eq!(
tool_key("mcp:calculator (banco POST /api/v1/mcp)").as_deref(),
Some("calculator")
);
assert_eq!(tool_key("mcp: nothing"), None);
}
#[test]
fn a_quoted_ledger_field_keeps_its_comma() {
let l = parse_ledger(&fixture("surface_audit.csv")).unwrap();
assert!(l["apr"].rows.contains("GET /api/tags (list, local)"));
assert!(l["apr"].routes.contains("GET /api/tags"));
assert!(parse_ledger("binary,feature\napr,apr run\n").is_err());
}
#[test]
fn the_positive_control_fires() {
assert!(positive_control(&control_sample()));
}
#[test]
fn the_positive_control_refuses_a_third_apr() {
let mut targets = control_sample();
let extra = Target {
package: "apr-extra".into(),
..targets[1].clone()
};
targets.push(extra);
assert!(!positive_control(&targets));
let mut bare = control_sample();
for t in &mut bare {
t.commands.clear();
}
assert!(!positive_control(&bare));
}
#[test]
fn the_control_sample_looks_like_a_snapshot_row() {
for t in control_sample() {
for d in [&t.help_sha256, &t.version_sha256] {
assert!(
d.len() == 64 && d.bytes().all(|c| c.is_ascii_hexdigit()),
"{}: {d:?}",
t.package
);
}
assert!(t.commands.contains("run"), "{}", t.package);
}
}
#[test]
fn the_repo_census_has_both_aprs_and_pv() {
let root = Path::new(env!("CARGO_MANIFEST_DIR")).join("../..");
let c = census(&root).expect("the repo root is a workspace");
for key in [
("apr-cli", "apr"),
("aprender", "apr"),
("aprender-contracts-cli", "pv"),
] {
assert!(
c.contains(&(key.0.to_string(), key.1.to_string())),
"{key:?} in {c:?}"
);
}
}
fn set(items: &[&str]) -> BTreeSet<String> {
items.iter().map(|s| (*s).to_string()).collect()
}
#[test]
fn emit_all_counts_are_exact() {
let sha = "0123456789abcdef0123456789abcdef01234567";
let snapshot_sets = |t: &mut Target| {
t.commands = set(&["run", "x"]);
t.routes = set(&["GET /a", "GET /b"]);
t.tools = set(&["t1", "t2", "t3", "t4"]);
};
let mut t1 = Target {
package: "p1".into(),
target: "apr".into(),
name: "apr".into(),
git_sha: sha.into(),
version: "apr 1.0".into(),
help_sha256: "h1".into(),
version_sha256: "v".into(),
help_failed: true,
..Target::default()
};
snapshot_sets(&mut t1);
let mut t2 = Target {
package: "p2".into(),
target: "apr".into(),
name: "apr".into(),
git_sha: sha.into(),
version: "apr 1.0 (012345678)".into(),
help_sha256: "h2".into(),
version_sha256: "v".into(),
..Target::default()
};
snapshot_sets(&mut t2);
let t3 = Target {
package: "p3".into(),
target: "pv".into(),
name: "pv".into(),
git_sha: sha.into(),
version: "pv (012345678)".into(),
help_sha256: EMPTY_SHA256.into(),
commands: set(&["a", "b"]),
..Target::default()
};
let mut ledger = BTreeMap::new();
ledger.insert(
"apr".to_string(),
LedgerEntry {
rows: set(&["r"]),
commands: set(&["run", "g1", "g2"]),
routes: set(&["GET /c", "GET /d", "GET /e"]),
tools: BTreeSet::new(),
},
);
let mut stats = BinaryStats::default();
emit_all(&mut Graph::default(), &[t1, t2, t3], &ledger, &mut stats);
assert_eq!(stats.version_lacks_sha, 1);
assert_eq!(stats.name_pair_mismatch, 2);
assert_eq!(stats.help_failed, 2);
assert_eq!(stats.no_audit_row, 1);
assert_eq!(stats.unledgered, 7 + 7 + 2);
assert_eq!(stats.orphans, 5 + 5);
assert_eq!((stats.targets, stats.names), (3, 2));
}
#[test]
fn pair_mismatches_only_pairs_same_named_targets() {
let mk = |package: &str, name: &str, help: &str| Target {
package: package.into(),
target: name.into(),
name: name.into(),
help_sha256: help.into(),
..Target::default()
};
let ts = [mk("p1", "a", "h1"), mk("p2", "b", "h2")];
assert!(pair_mismatches(&ts).iter().all(Vec::is_empty));
let ts = [mk("p1", "a", "h1"), mk("p2", "a", "h2")];
let m = pair_mismatches(&ts);
assert_eq!((m[0].len(), m[1].len()), (1, 1));
assert_eq!(m[0][0].package, "p2");
}
#[test]
fn pair_mismatches_pairs_same_package_different_targets() {
let mk = |target: &str, help: &str| Target {
package: "p1".into(),
target: target.into(),
name: "a".into(),
help_sha256: help.into(),
..Target::default()
};
let ts = [mk("t1", "h1"), mk("t2", "h2")];
let m = pair_mismatches(&ts);
assert_eq!((m[0].len(), m[1].len()), (1, 1));
assert_eq!(m[0][0].target, "t2");
}
#[test]
fn extract_reports_the_workspace_root_and_the_targets() {
let d = planted(&fixture("snapshot.jsonl"));
let mut g = Graph::default();
let s = extract_at(d.path(), &mut g);
assert!(s.at_workspace_root);
let mut g2 = Graph::default();
let s2 = extract(&d.path().join("contracts"), &mut g2);
assert!(s2.at_workspace_root);
assert_eq!((s2.targets, s2.names, s2.census), (3, 2, Some(3)));
assert_eq!(g2.instances_of(&ont("Binary")).len(), 3);
let bare = tempfile::tempdir().unwrap();
let s3 = extract_at(bare.path(), &mut Graph::default());
assert!(!s3.at_workspace_root);
assert_eq!(s3.census, None);
}
fn good_line() -> serde_json::Value {
let l = fixture("snapshot.jsonl");
serde_json::from_str(l.lines().next().expect("fixture has a line"))
.expect("fixture line is JSON")
}
#[test]
fn parse_line_refuses_a_git_sha_that_is_not_40_lowercase_hex() {
assert!(parse_line(&good_line().to_string()).is_ok());
for bad in [
"a".repeat(39),
"a".repeat(41),
"g".repeat(40),
"A".repeat(40),
String::new(),
] {
let mut v = good_line();
v["git_sha"] = bad.clone().into();
assert!(parse_line(&v.to_string()).is_err(), "{bad}");
}
assert!(hex_of("0123456789abcdef", 16));
assert!(!hex_of("0123456789abcdeg", 16));
assert!(!hex_of("0123456789abcde", 16));
}
#[test]
fn parse_line_refuses_each_empty_identity_field_alone() {
for k in ["package", "target", "name"] {
let mut v = good_line();
v[k] = "".into();
assert!(parse_line(&v.to_string()).is_err(), "{k}");
}
}
#[test]
fn csv_records_case_table() {
let s = |r: &[&[&str]]| -> Vec<Vec<String>> {
r.iter()
.map(|x| x.iter().map(|f| (*f).to_string()).collect())
.collect()
};
assert_eq!(csv_records("\"a\"\"b\",c\n"), s(&[&["a\"b", "c"]]));
assert_eq!(csv_records("a"), s(&[&["a"]]));
assert_eq!(csv_records("x,"), s(&[&["x", ""]]));
assert_eq!(csv_records("a\n"), s(&[&["a"]]));
assert!(csv_records("").is_empty());
}
#[test]
fn route_key_needs_a_verb_and_a_rooted_path() {
assert_eq!(route_key("GET nope"), None);
assert_eq!(route_key("FOO /x"), None);
assert_eq!(route_key("GET /x").as_deref(), Some("GET /x"));
}
#[test]
fn declared_bins_ignores_a_value_line_ending_in_a_bracket() {
let (names, paths, _) =
declared_bins("[[bin]]\nname = \"x\"\nfeatures = [\"a\"]\npath = \"src/x.rs\"\n");
assert!(names.contains("x"));
assert!(paths.contains("src/x.rs"));
let (names, _, _) = declared_bins("[[bin]]\n[odd\nname = \"y\"\n");
assert!(names.contains("y"));
}
#[test]
fn emit_help_failed_fires_on_either_cause_alone() {
let mk = |failed: bool, help: &str| Target {
package: "p".into(),
target: "t".into(),
name: "t".into(),
git_sha: "0123456789abcdef0123456789abcdef01234567".into(),
help_sha256: help.into(),
help_failed: failed,
..Target::default()
};
let fires = |t: &Target| {
let mut g = Graph::default();
emit(&mut g, t, None, &[]);
has(&g, &node("p", "t"), "helpFailed")
};
assert!(fires(&mk(true, "real")));
assert!(fires(&mk(false, EMPTY_SHA256)));
assert!(!fires(&mk(false, "real")));
}