Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/holger

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:?}"
    );
}