nornir 0.4.39

Companion to cargo: dependency tracking, release gating, deploy, benchmarks, and documentation assembly. Project-agnostic.
//! CLI-parity smoke tests for the dependency-graph (`mimir`) and viz-diagram
//! (`diagram`) subcommands added to bring the `nornir` CLI to parity with the
//! server's `Mimir`/`Viz` RPCs and the egui viz's `diagram.rs` renderers.
//!
//! These drive the real `nornir` binary's argument parser (`--help`), so a
//! regression that drops a subcommand or mis-wires clap fails here without
//! needing a warehouse fixture. `clap` also debug-asserts the whole command
//! tree while building `--help`, catching duplicate/ambiguous args.

use std::process::Command;

fn nornir() -> Command {
    Command::new(env!("CARGO_BIN_EXE_nornir"))
}

fn stdout_of(args: &[&str]) -> String {
    let out = nornir().args(args).output().expect("run nornir");
    assert!(
        out.status.success(),
        "`nornir {}` failed: {}",
        args.join(" "),
        String::from_utf8_lossy(&out.stderr)
    );
    String::from_utf8_lossy(&out.stdout).into_owned()
}

#[test]
fn mimir_is_registered_with_every_op() {
    let help = stdout_of(&["mimir", "--help"]);
    for sub in [
        "deps-of",
        "dependents-of",
        "affected",
        "build-order",
        "dep-path",
        "external-users",
        "svg",
        "overview",
        "changed",
    ] {
        assert!(help.contains(sub), "`mimir --help` missing subcommand `{sub}`:\n{help}");
    }
}

#[test]
fn diagram_is_registered_with_every_renderer() {
    let help = stdout_of(&["diagram", "--help"]);
    for sub in [
        "timeline",
        "lane-summary",
        "depgraph",
        "snapshot-edges",
        "gate-matrix",
        "release-versions",
        "bench-history",
        "bench-compare",
    ] {
        assert!(help.contains(sub), "`diagram --help` missing renderer `{sub}`:\n{help}");
    }
}

#[test]
fn top_level_lists_new_commands() {
    let help = stdout_of(&["--help"]);
    assert!(help.contains("mimir"), "top-level help missing `mimir`:\n{help}");
    assert!(help.contains("diagram"), "top-level help missing `diagram`:\n{help}");
}

#[test]
fn mimir_deps_of_accepts_transitive_flag() {
    // Parse-only: `--help` on the leaf op asserts the flag exists.
    let help = stdout_of(&["mimir", "deps-of", "--help"]);
    assert!(help.contains("--transitive"), "deps-of missing --transitive:\n{help}");
}

#[test]
fn server_and_token_are_global_thin_client_flags() {
    // Advertised at the top level...
    let top = stdout_of(&["--help"]);
    assert!(top.contains("--server"), "top-level --help must advertise --server:\n{top}");
    assert!(top.contains("--token"), "top-level --help must advertise --token:\n{top}");
    // ...and reachable on a subcommand (global), so `nornir test run … --server …`
    // routes that command to the server (the thin-client UX).
    let sub = stdout_of(&["test", "run", "--help"]);
    assert!(sub.contains("--server"), "`test run --help` must show the global --server:\n{sub}");
    assert!(sub.contains("--token"), "`test run --help` must show the global --token:\n{sub}");
}

#[test]
fn mirror_to_holger_exposes_renamed_holger_token() {
    // Renamed off `--token` so it no longer collides with the global bearer flag.
    let help = stdout_of(&["release", "mirror-to-holger", "--help"]);
    assert!(help.contains("--holger-token"), "mirror-to-holger must expose --holger-token:\n{help}");
}

#[test]
fn workspace_populate_verb_is_registered() {
    // The `populate` verb (force fetch + republish) must be on `workspace`.
    let help = stdout_of(&["workspace", "--help"]);
    assert!(help.contains("populate"), "`workspace --help` must list `populate`:\n{help}");
    // And it parses with a name (clap debug-asserts the leaf command tree).
    let leaf = stdout_of(&["workspace", "populate", "--help"]);
    assert!(leaf.contains("name") || leaf.contains("NAME"), "`workspace populate` takes a name:\n{leaf}");
}

/// INSTALL-3 — the generic component verbs (adopting `nornir-airgap`) are
/// registered alongside the bespoke `systemd`/`uninstall`. clap debug-asserts the
/// whole tree while rendering `--help`.
#[test]
fn install_exposes_generic_component_verbs() {
    let help = stdout_of(&["install", "--help"]);
    for sub in ["systemd", "uninstall", "add", "upgrade", "remove", "status"] {
        assert!(
            help.contains(sub),
            "`install --help` missing subcommand `{sub}`:\n{help}"
        );
    }
}

/// INSTALL-3/4/5 — `install status` lists the airgap component registry with the
/// exact spec facts the CLI resolves from `nornir_airgap::product::registry()`.
/// Inject-assert: real command → real asserted output (not just "runs").
#[test]
fn install_status_lists_registry_with_real_facts() {
    let out = stdout_of(&["install", "status"]);
    // Every registered component appears with its user/ports.
    assert!(out.contains("holger") && out.contains("_holger") && out.contains("7070"), "{out}");
    assert!(out.contains("tunnr") && out.contains("_tunnr") && out.contains("8443"), "{out}");
    assert!(out.contains("nornir-server") && out.contains("7878"), "{out}");
}

/// INSTALL-4 — `install add holger --dry-run` renders the EXACT holger unit the
/// installer would write (the "render unit → would-install" demo), with no root
/// and no side effects. Asserts the load-bearing lines, not just non-empty.
#[test]
fn install_add_holger_dry_run_renders_exact_unit() {
    let out = stdout_of(&[
        "install",
        "add",
        "holger",
        "--bundle",
        "/opt/bundles/lake.tar.zst",
        "--software-dir",
        "/opt/lake",
        "--data-dir",
        "/var/lib",
        "--dry-run",
    ]);
    assert!(out.contains("Description=holger service"), "{out}");
    assert!(out.contains("User=_holger"), "{out}");
    assert!(
        out.contains("ExecStart=/opt/lake/current/holger/holger serve --config /etc/holger/holger.toml"),
        "{out}"
    );
    assert!(out.contains("After=network-online.target nornir-server.service"), "{out}");
    assert!(out.contains("# ports : [7070]"), "{out}");
}

/// INSTALL-5 — `install upgrade --delta` honestly notes the znippy delta seam is
/// not built in and falls back to a full re-install (no silent pretend).
#[test]
fn install_upgrade_delta_falls_back_with_note() {
    let out = stdout_of(&[
        "install",
        "upgrade",
        "tunnr",
        "--bundle",
        "/opt/bundles/lake.tar.zst",
        "--delta",
        "--dry-run",
    ]);
    assert!(out.contains("--delta requested"), "{out}");
    assert!(out.contains("falling back to a full re-install"), "{out}");
    // Still renders the tunnr appliance unit it would install.
    assert!(out.contains("ExecStart=/opt/lake/current/tunnr/tunnr run --appliance"), "{out}");
}

/// Unknown component → a clear error listing the registered names (no panic).
#[test]
fn install_add_unknown_component_errors_with_registry() {
    let out = nornir()
        .args(["install", "add", "bogus", "--bundle", "/x", "--dry-run"])
        .output()
        .expect("run nornir");
    assert!(!out.status.success(), "unknown component must fail");
    let err = String::from_utf8_lossy(&out.stderr);
    assert!(err.contains("unknown component `bogus`"), "{err}");
    assert!(err.contains("holger") && err.contains("tunnr"), "{err}");
}

#[test]
fn workspace_is_a_global_flag_on_subcommands() {
    // Global `--workspace` reaches subcommands (incl. ones that used to own a
    // local `--workspace`), so a thin client can target a workspace by name.
    for cmd in [
        &["test", "run", "--help"][..],
        &["release", "run", "--help"][..],
        &["diagram", "timeline", "--help"][..],
    ] {
        let help = stdout_of(cmd);
        assert!(
            help.contains("--workspace"),
            "`{}` must show the global --workspace:\n{help}",
            cmd.join(" ")
        );
    }
}