server/cli/tests/tools_robot.rs
//! Non-UI **tool robot** — the data twin of holger-server-ui's egui robot.
//!
//! `server/ui/tests/robot_ui_app.rs::robot_drives_tabs_tools_and_static_mode`
//! drives the **Tools tab** in the egui app and asserts `state_json`. That proves
//! the UI is wired, but not that the tools themselves *do their job*. This test
//! drives each **working tool verb's SAFE path** on the real `holger-server`
//! binary (inject args → assert the observable verdict in stdout/exit), exactly
//! the robot LAW ("see what the operator sees, as data, not pixels") applied to a
//! non-UI surface. Each tool becomes one `functional` nornir-testmatrix row, so
//! nornir's Test pane can SEE holger's tool surface the same way it sees the UI
//! robot rows.
//!
//! SAFE paths only — never the invasive limbs:
//! * Tool#3 Móðguðr `scan` — real scan of a fixture artifact → `VERDICT: PASS`
//! * Tool#4 deploy `install render` — render the systemd unit (no root, no side effects)
//! * Tool#1 `holger-ds` — **proved ABSENT from this binary**. It was a verb here
//! until 2026-09-20, when the owner ruled that `holger-bench-scenarios` is a dev
//! dependency and never reaches the shipped artifact (`7a618e0`); it is now that
//! crate's own `holger-ds` binary. The row guards the ruling. Tool#2
//! migrate/suck has no verb yet.
use std::process::Command;
use nornir_testmatrix::{functional_row, status, TestResultRow};
/// The package's own binary, provided by cargo to integration tests.
const BIN: &str = env!("CARGO_BIN_EXE_holger");
/// What a step's output has to show.
enum Expect {
/// The verb runs (exit 0) and its output carries this substring.
Works(&'static str),
/// The verb is NOT in this binary: the call FAILS, and neither its own
/// output nor `--help` mentions the word. A tool that moved out of the
/// shipped artifact on purpose is proved absent, not quietly dropped from
/// the robot — see `holger_ds_verb_is_not_in_the_shipped_binary`.
Absent(&'static str),
}
/// One robot step: a tool's SAFE invocation + what proves the verdict.
struct ToolStep {
/// Matrix `test_name` for this tool's row.
tool: &'static str,
args: Vec<String>,
expect: Expect,
}
/// Run one step: spawn the verb and fold the outcome into a pass/fail + a human
/// detail string.
fn drive(step: &ToolStep) -> (bool, String) {
let output = match Command::new(BIN).args(&step.args).output() {
Ok(o) => o,
Err(e) => return (false, format!("spawn {BIN} failed: {e}")),
};
let merged = format!(
"{}{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
match step.expect {
Expect::Works(needle) => {
let ok = output.status.success() && merged.contains(needle);
(
ok,
format!(
"exit={:?} expect={:?} matched={}",
output.status.code(),
needle,
merged.contains(needle)
),
)
}
Expect::Absent(word) => {
// The binary's own help is asked too: a verb can be missing from one
// spelling and present under another, and the help is the list a
// reader trusts.
let help = Command::new(BIN)
.arg("--help")
.output()
.map(|o| {
format!(
"{}{}",
String::from_utf8_lossy(&o.stdout),
String::from_utf8_lossy(&o.stderr)
)
})
.unwrap_or_default();
let ok = !output.status.success() && !help.contains(word);
(
ok,
format!(
"exit={:?} absent={:?} in_help={}",
output.status.code(),
word,
help.contains(word)
),
)
}
}
}
#[test]
fn tool_robot_drives_every_working_tool_verb() {
// Fixture for the Móðguðr scan: one named artifact in an otherwise-empty dir
// (the filename carries the `name-version.ext` coords the verdict keys on).
let scan_dir =
std::env::temp_dir().join(format!("holger-tool-robot-scan-{}", std::process::id()));
std::fs::create_dir_all(&scan_dir).expect("create scan fixture dir");
std::fs::write(scan_dir.join("serde-1.0.0.crate"), b"").expect("write fixture artifact");
#[allow(unused_mut)]
let mut steps = vec![
ToolStep {
tool: "deploy_install_render_unit",
args: vec!["install".into(), "render".into()],
expect: Expect::Works("ExecStart="),
},
// ★ Tool#1 `holger-ds` is NOT a verb of this binary any more, and that is
// the point of the row. The owner ruled on 2026-09-20 that
// `holger-bench-scenarios` is a DEV DEPENDENCY and never reaches the
// shipped artifact (`7a618e0`), so the verb moved out of
// `server/cli/src/main.rs` and is now that crate's own binary
// (`bench-scenarios/src/bin/holger_ds.rs`, `required-features =
// ["served"]`, ?20). This robot kept driving `holger-server holger-ds
// --help` and had been RED ever since — invisible while the package
// could not build at all.
//
// Rather than delete the row, it now proves the RULING: a bench verb
// that crept back into the shipped binary would turn this red again.
// The verb itself is driven where it lives:
// `cargo run -p holger-bench-scenarios --features served --bin holger-ds -- --help`.
ToolStep {
tool: "holger_ds_verb_is_not_in_the_shipped_binary",
args: vec!["holger-ds".into(), "--help".into()],
expect: Expect::Absent("holger-ds"),
},
];
// The Móðguðr scan verb is part of the `supply-chain` plane.
#[cfg(feature = "supply-chain")]
steps.insert(
0,
ToolStep {
tool: "modgudr_scan_verdict_pass",
args: vec![
"scan".into(),
"--path".into(),
scan_dir.display().to_string(),
"--ecosystem".into(),
"rust".into(),
],
expect: Expect::Works("VERDICT: PASS"),
},
);
let mut rows: Vec<TestResultRow> = Vec::new();
let mut failed: Vec<&'static str> = Vec::new();
for step in &steps {
let (ok, detail) = drive(step);
// One matrix row per tool — the data nornir's Test pane reads back.
let row = functional_row("holger-tools", step.tool, ok, &detail);
eprintln!(
"tool-robot row: {}/{} -> {} ({})",
row.suite, row.test_name, row.status, detail
);
if !ok {
failed.push(step.tool);
}
rows.push(row);
}
// Best-effort cleanup of the fixture.
let _ = std::fs::remove_dir_all(&scan_dir);
// Shape contract: exactly one row per working tool, all PASS.
assert_eq!(
rows.len(),
steps.len(),
"one matrix row per working tool verb"
);
assert!(
rows.iter().all(|r| r.status == status::PASS),
"every tool row should be PASS; failed tools: {failed:?}"
);
}