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

mikronova/modgunn

tests/cli_framework.rs

//! End-to-end CLI test for the COMMON FRAMEWORK surfaces in `src/main.rs` — the
//! `about` / `tools` / `demo` / `service` verbs the library-level unit tests never
//! drive as one flow through the compiled binary.
//!
//! DRIVES `CARGO_BIN_EXE_modgunn`: `about --json` (the identity oracle), `tools
//! --json` (the jera demo roster), `demo advisory-feed` (the jera ContainerSpec plan),
//! and `service --unit` (the skidbladnir-rendered systemd unit with modgunn's
//! dedicated user). Fully offline, zero-shell — the demo prints the plan (no live
//! container without `framework-oci`).
//!
//! Gated on `warehouse` (the binary's `required-features`) AND `framework` (the
//! subcommands). Run:
//! `cargo test --features "framework warehouse manifests test-support" --test cli_framework`.
#![cfg(all(feature = "warehouse", feature = "framework"))]

use std::process::Command;

/// Run the compiled `modgunn` binary with `args`; return `(stdout, stderr, code)`.
fn run(args: &[&str]) -> (String, String, i32) {
    let out = Command::new(env!("CARGO_BIN_EXE_modgunn"))
        .args(args)
        .output()
        .expect("spawn modgunn binary");
    (
        String::from_utf8_lossy(&out.stdout).into_owned(),
        String::from_utf8_lossy(&out.stderr).into_owned(),
        out.status.code().unwrap_or(-1),
    )
}

/// **RED-when-broken: `modgunn about --json` prints modgunn's identity** — name +
/// version + both engines. The About CLI surface a robot asserts. If the verb or the
/// identity broke, the JSON would be wrong/absent.
#[test]
fn about_json_prints_identity() {
    let (out, _err, code) = run(&["about", "--json"]);
    assert_eq!(code, 0, "about exits 0");
    let js: serde_json::Value = serde_json::from_str(&out).expect("about --json is JSON");
    assert_eq!(js["name"], "modgunn");
    // The identity is `<semver> (<git hash>)` — `about::identity()` builds it from
    // `version_with_hash()` (src/about.rs:50) per the build-version-githash-law, and
    // the in-crate unit test at src/about.rs:84 already asserts it that way. This
    // assertion had drifted to a bare `==` against CARGO_PKG_VERSION and was RED on
    // every checkout with git metadata (measured 2026-08-01: left `"0.0.5 (a3f10a5)"`,
    // right `"0.0.5"`). Assert the version PREFIX plus the hash, which is the claim.
    let version = js["version"].as_str().expect("version is a string");
    assert!(
        version.starts_with(env!("CARGO_PKG_VERSION")),
        "identity carries the crate semver: {version}"
    );
    assert!(
        version.contains(env!("MODGUNN_GIT_HASH")),
        "identity carries the git hash (build-version-githash-law): {version}"
    );
    assert_eq!(
        js["engines"].as_array().unwrap().len(),
        2,
        "both engines listed"
    );
}

/// **RED-when-broken: `modgunn tools --json` prints the jera-backed demo roster** —
/// the demo advisory-feed entry naming the container it starts + the verb that views
/// it. The Tools-menu CLI surface a robot asserts (open Tools → the jera dispatch).
#[test]
fn tools_json_prints_jera_demo_roster() {
    let (out, _err, code) = run(&["tools", "--json"]);
    assert_eq!(code, 0, "tools exits 0");
    let js: serde_json::Value = serde_json::from_str(&out).expect("tools --json is JSON");
    let entries = js["entries"].as_array().expect("entries array");
    assert_eq!(entries.len(), 1, "one demo action");
    assert_eq!(entries[0]["effect"], "demo_advisory_feed");
    assert_eq!(
        entries[0]["needs_container"], "modgunn-demo-feed",
        "names the jera container it starts"
    );
    assert_eq!(entries[0]["view_verb"], "warehouse scan");
    assert_eq!(js["workspace"], "modgunn");
}

/// **RED-when-broken: `modgunn demo advisory-feed` dispatches a jera ContainerSpec** —
/// the printed plan names the pinned image + the demo container + the `/out` bind, and
/// the `state_json` records the pick. Click the Tools demo entry → the jera dispatch.
#[test]
fn demo_prints_jera_container_plan() {
    let out_dir = std::env::temp_dir().join("modgunn-cli-demo-out");
    let out_s = out_dir.to_string_lossy().to_string();
    let (out, _err, code) = run(&["demo", "advisory-feed", "--out", &out_s, "--json"]);
    assert_eq!(code, 0, "demo exits 0");
    assert!(
        out.contains("jera ContainerSpec"),
        "names the jera dispatch:\n{out}"
    );
    assert!(
        out.contains("modgunn-demo-feed"),
        "names the demo container:\n{out}"
    );
    assert!(out.contains("alpine"), "names the pinned image:\n{out}");
    assert!(
        out.contains(r#""last_picked": "demo_advisory_feed""#),
        "records the pick:\n{out}"
    );
}

/// **RED-when-broken: `modgunn service --unit` renders the skidbladnir systemd unit**
/// with modgunn's DEDICATED `modgunn` user + the world-delta ExecStart. The lifecycle
/// CLI surface — proof modgunn declares its service through the shared ship.
#[test]
fn service_unit_renders_dedicated_user() {
    let (out, _err, code) = run(&["service", "--unit"]);
    assert_eq!(code, 0, "service --unit exits 0");
    assert!(
        out.contains("ExecStart=/usr/local/bin/modgunn world-delta watch"),
        "world-delta ExecStart:\n{out}"
    );
    assert!(
        out.contains("User=modgunn"),
        "dedicated modgunn user:\n{out}"
    );
    assert!(
        out.contains("Restart=on-failure"),
        "on-failure restart:\n{out}"
    );
}