use std::path::{Path, PathBuf};
use std::process::Command;
const BIN: &str = env!("CARGO_BIN_EXE_roteiro");
fn roteiro(args: &[&str]) -> std::process::Output {
Command::new(BIN).args(args).output().expect("run roteiro")
}
fn bundle(tag: &str, files: &[(&str, &str)]) -> PathBuf {
static SEQ: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
let seq = SEQ.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
let root = std::env::temp_dir().join(format!(
"roteiro-okf-inspect-{}-{seq}-{tag}",
std::process::id()
));
let _ = std::fs::remove_dir_all(&root);
for (rel, content) in files {
let path = root.join(rel);
std::fs::create_dir_all(path.parent().expect("a parent")).expect("create bundle dir");
std::fs::write(&path, content).expect("write concept");
}
root
}
fn two_tier_bundle(tag: &str) -> PathBuf {
bundle(
tag,
&[
("index.md", "---\nokf_version: \"0.2\"\n---\n\n# Bundle\n"),
(
"metrics/revenue.md",
"---\ntype: Metric\ntitle: Revenue\nverified: { by: human:alice, at: 2026-08-01T10:00:00Z }\n---\n\n# Definition\n",
),
(
"metrics/cost.md",
"---\ntype: Metric\ntitle: Cost\ngenerated: { by: agent/1.0, at: 2026-08-01T10:00:00Z }\n---\n\n# Definition\n",
),
],
)
}
#[test]
fn trust_reports_the_tier_of_every_concept() {
let root = two_tier_bundle("trust");
let out = roteiro(&["okf", "trust", &root.to_string_lossy()]);
assert!(out.status.success(), "okf trust should succeed");
let stdout = String::from_utf8(out.stdout).expect("utf-8");
assert!(
stdout.contains(" human-reviewed 1, machine-confirmed 0, unverified 1\n"),
"the aggregate line must state all three tiers, so a bundle with none of \
one is legible as zero rather than absent; got:\n{stdout}"
);
assert!(
stdout.contains("human-reviewed metrics/revenue — verified by human:alice\n"),
"a human-verified concept must name its verifier: who signed off is the \
load-bearing half of the claim; got:\n{stdout}"
);
assert!(
stdout.contains("unverified metrics/cost\n"),
"a concept with `generated` and no `verified` is unverified, not unknown; \
got:\n{stdout}"
);
let _ = std::fs::remove_dir_all(&root);
}
#[test]
fn trust_emits_json_on_request() {
let root = two_tier_bundle("trust-json");
let out = roteiro(&["okf", "trust", &root.to_string_lossy(), "--json"]);
assert!(out.status.success());
let v: serde_json::Value =
serde_json::from_slice(&out.stdout).expect("--json must emit parseable JSON");
assert_eq!(v["total"], 2);
assert_eq!(v["human_reviewed"], 1);
assert_eq!(v["unverified"], 1);
assert_eq!(
v["okf_version"], "0.2",
"the root index's declared version (§10) belongs in the summary"
);
let _ = std::fs::remove_dir_all(&root);
}
#[test]
fn a_broken_link_is_reported_and_gates_only_under_check() {
let root = bundle(
"links",
&[
(
"a.md",
"---\ntype: Metric\n---\n\n# A\n\nSee [B](./b.md) and [gone](./gone.md).\n",
),
("b.md", "---\ntype: Metric\n---\n\n# B\n"),
],
);
let path = root.to_string_lossy().into_owned();
let plain = roteiro(&["okf", "links", &path]);
let stdout = String::from_utf8(plain.stdout).expect("utf-8");
assert!(
plain.status.success(),
"without --check a broken link is reported, not gated: inspecting a \
peer's bundle must not fail the command"
);
assert!(
stdout.contains(" broken: a -> ./gone.md\n"),
"the broken link must name both the concept and the target as written; \
got:\n{stdout}"
);
let gated = roteiro(&["okf", "links", &path, "--check"]);
assert_eq!(
gated.status.code(),
Some(1),
"--check must exit 1 on a broken link, or it is not a gate"
);
let _ = std::fs::remove_dir_all(&root);
}
#[test]
fn diff_separates_an_unchanged_bundle_from_a_changed_one() {
let a = two_tier_bundle("diff-a");
let b = bundle(
"diff-b",
&[(
"metrics/revenue.md",
"---\ntype: Metric\ntitle: Revenue\nverified: { by: human:alice, at: 2026-08-01T10:00:00Z }\n---\n\n# Definition\n",
)],
);
let same = roteiro(&["okf", "diff", &a.to_string_lossy(), &a.to_string_lossy()]);
let stdout = String::from_utf8(same.stdout).expect("utf-8");
assert!(
stdout.contains(" no semantic change\n"),
"a bundle must be semantically identical to itself; got:\n{stdout}"
);
let changed = roteiro(&["okf", "diff", &a.to_string_lossy(), &b.to_string_lossy()]);
let stdout = String::from_utf8(changed.stdout).expect("utf-8");
assert!(
stdout.contains(" removed metrics/cost\n"),
"a concept present in `before` and absent from `after` is a removal; \
got:\n{stdout}"
);
let _ = std::fs::remove_dir_all(&a);
let _ = std::fs::remove_dir_all(&b);
}
#[test]
fn a_path_that_is_not_a_bundle_is_refused_by_name() {
let out = roteiro(&["okf", "trust", "/no/such/bundle"]);
let stderr = String::from_utf8(out.stderr).expect("utf-8");
assert_eq!(
stderr,
"Error: OKF bundle not found: /no/such/bundle (expected a directory of concept documents)\n",
"the refusal must name the path and say what was expected"
);
}
#[test]
fn the_upstream_fixtures_stay_in_one_crate() {
let here = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/okf-upstream");
assert!(
!here.exists(),
"the specification's published bundles are vendored once, under \
crates/rto-render/tests/fixtures/okf-upstream, with their provenance and \
licence recorded beside them. A second copy would drift from the first"
);
}