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/the_four_products.rs

//! **Holger is four products, one crate each** (lane HOL-PRODUCTS, owner 2026-10-30).
//!
//! * `cargo install holger` is the server, `cargo install holger-downstream` the airgapped
//!   downstream, `cargo install holger-ui` the web console and `cargo install holger-cli`
//!   the command-line tool (with the bundle, verify and agent verbs). Each is a thin crate
//!   over the shared libraries.
//! * Every product's default is the twelve MVP formats where formats apply, and no egress.
//! * The extras are named features on the same crates: `all-formats`, `online`,
//!   `federation`, and `full` = all three.
//! * The Build Thing has one product list, the MVP suite: the four products plus what only
//!   an estate needs (the appliance and metered builds, the front, the signed wasm plugins
//!   and the installer media). The ORIGINAL and OPEN suites are gone.
//!
//! Held over the manifests and the declared rows; no cargo process.

use std::collections::BTreeSet;
use std::path::{Path, PathBuf};

use holger_recipe::variants::variants;

fn repo_root() -> PathBuf {
    Path::new(env!("CARGO_MANIFEST_DIR"))
        .join("../..")
        .canonicalize()
        .unwrap()
}

fn manifest(dir: &str) -> toml::Value {
    let p = repo_root().join(dir).join("Cargo.toml");
    let text = std::fs::read_to_string(&p).unwrap_or_else(|e| panic!("read {}: {e}", p.display()));
    toml::from_str(&text).unwrap_or_else(|e| panic!("parse {}: {e}", p.display()))
}

fn bins(m: &toml::Value) -> Vec<String> {
    m.get("bin")
        .and_then(|b| b.as_array())
        .map(|a| {
            a.iter()
                .map(|b| b["name"].as_str().unwrap().to_string())
                .collect()
        })
        .unwrap_or_default()
}

fn features(m: &toml::Value) -> toml::value::Table {
    m.get("features")
        .and_then(|f| f.as_table())
        .cloned()
        .unwrap_or_default()
}

fn list(t: &toml::value::Table, f: &str) -> Vec<String> {
    t.get(f)
        .and_then(|v| v.as_array())
        .map(|a| {
            a.iter()
                .filter_map(|v| v.as_str())
                .map(String::from)
                .collect()
        })
        .unwrap_or_default()
}

/// The twelve MVP formats (lane HOL-MVP-FORMATS), as server/cli's feature names.
const MVP_FORMATS: [&str; 12] = [
    "fmt-raw",
    "fmt-rust",
    "fmt-npm",
    "fmt-maven",
    "fmt-python",
    "fmt-docker",
    "fmt-go",
    "fmt-helm",
    "fmt-deb",
    "fmt-rpm",
    "fmt-znippy",
    "fmt-build-thing",
];

/// The four product crates of this repository (holger-ui's lives in holger-ui) and the
/// directory each one is.
const PRODUCTS: [(&str, &str); 3] = [
    ("holger", "server/cli"),
    ("holger-downstream", "holger-downstream"),
    ("holger-cli", "holger-cli"),
];

#[test]
fn each_product_is_its_own_crate_with_its_own_bin() {
    for (name, dir) in PRODUCTS {
        let m = manifest(dir);
        assert_eq!(
            m["package"]["name"].as_str(),
            Some(name),
            "{dir} is the crate `{name}`"
        );
        let default_bins: Vec<String> = m["bin"]
            .as_array()
            .unwrap_or_else(|| panic!("{dir}: no [[bin]]"))
            .iter()
            .filter(|b| b.get("required-features").is_none())
            .map(|b| b["name"].as_str().unwrap().to_string())
            .collect();
        assert_eq!(
            default_bins,
            [name],
            "{dir}: `cargo install {name}` installs `{name}` and nothing else"
        );
        assert!(
            m["package"].get("publish").is_none(),
            "{dir}: publish-ready (the owner publishes)"
        );
    }
}

#[test]
fn the_server_crate_has_no_retired_tier_bins() {
    let b = bins(&manifest("server/cli"));
    for gone in [
        "holger-downstream",
        "holger-embedded",
        "holger-mikro",
        "holger-full",
    ] {
        assert!(
            !b.iter().any(|x| x == gone),
            "server/cli still builds `{gone}`: {b:?}"
        );
    }
    let f = features(&manifest("server/cli"));
    for gone in ["embedded", "mini"] {
        assert!(
            !f.contains_key(gone),
            "server/cli keeps the retired tier feature `{gone}`"
        );
    }
}

#[test]
fn the_downstream_rules_crate_left_the_product_name() {
    assert_eq!(
        manifest("server/downstream")["package"]["name"].as_str(),
        Some("holger-downstream-rules")
    );
}

#[test]
fn the_bundle_verify_and_agent_tools_are_folded_into_holger_cli() {
    assert!(
        bins(&manifest("update-bundle")).is_empty(),
        "holger-bundle and holger-verify are `holger-cli bundle` and `holger-cli verify`"
    );
    assert!(
        bins(&manifest("agent/cli")).is_empty(),
        "holger-agent is `holger-cli agent`"
    );
}

#[test]
fn every_product_names_the_four_extras() {
    for (name, dir) in PRODUCTS {
        let f = features(&manifest(dir));
        for extra in ["all-formats", "online", "federation", "full"] {
            assert!(f.contains_key(extra), "{name}: no `{extra}` feature");
        }
        let full: BTreeSet<String> = list(&f, "full").into_iter().collect();
        for extra in ["all-formats", "online", "federation"] {
            assert!(
                full.contains(extra),
                "{name}: `full` does not carry `{extra}`: {full:?}"
            );
        }
        assert!(
            !list(&f, "default")
                .iter()
                .any(|d| d == "online" || d == "federation" || d == "full"),
            "{name}: the default dials nothing"
        );
    }
}

#[test]
fn the_server_default_serves_the_twelve_formats() {
    let f = features(&manifest("server/cli"));
    let default: BTreeSet<String> = list(&f, "default")
        .into_iter()
        .filter(|d| d.starts_with("fmt-"))
        .collect();
    let want: BTreeSet<String> = MVP_FORMATS.iter().map(|s| s.to_string()).collect();
    assert_eq!(
        default, want,
        "`cargo install holger` serves the twelve MVP formats"
    );
    let all: BTreeSet<String> = list(&f, "all-formats").into_iter().collect();
    assert!(
        all.contains("server-lib/fmt-all"),
        "`all-formats` adds NuGet, gems, conda and Composer: {all:?}"
    );
    assert_eq!(
        list(&f, "fmt-all"),
        ["all-formats"],
        "`fmt-all` is the old name of `all-formats`"
    );
}

/// The server is an upstream out of the box: it signs its scan evidence (an airgap export
/// needs it) and speaks the sync protocol (a downstream enrols at it). The guide "Three
/// products by hand" ran RED on both before they joined the default.
#[test]
fn the_server_default_signs_and_syncs() {
    let f = features(&manifest("server/cli"));
    let default = list(&f, "default");
    for must in ["sign", "sync", "secure_update"] {
        assert!(
            default.iter().any(|d| d == must),
            "`cargo install holger` lacks `{must}`: {default:?}"
        );
    }
}

/// The one product list: every row is in the MVP suite, and the rows are exactly the four
/// products and the estate's own builds.
#[test]
fn the_build_thing_declares_one_product_list() {
    let all = variants();
    let suites: BTreeSet<String> = all.iter().flat_map(|v| v.suites.clone()).collect();
    assert_eq!(suites, BTreeSet::from(["MVP".to_string()]), "one suite");
    let names: BTreeSet<String> = all.iter().map(|v| v.name.clone()).collect();
    for gone in [
        "embedded",
        "mini",
        "full",
        "server-local",
        "agent-online",
        "agent",
        "upstream",
        "downstream",
        "cli",
        "mvp-server",
        "mvp-bundle",
        "mvp-verify",
    ] {
        assert!(
            !names.contains(gone),
            "the retired row `{gone}` is still declared"
        );
    }
    for (name, _) in PRODUCTS {
        let v = all
            .iter()
            .find(|v| v.name == name)
            .unwrap_or_else(|| panic!("no product row `{name}`"));
        assert_eq!(
            v.package.as_deref(),
            Some(name),
            "row `{name}` builds the crate `{name}`"
        );
        assert!(
            v.features.is_empty() && !v.no_default_features,
            "row `{name}` is `cargo install {name}`: its defaults"
        );
    }
    let ui = all
        .iter()
        .find(|v| v.name == "holger-ui")
        .expect("the console's row `holger-ui`");
    assert_eq!(ui.package.as_deref(), Some("holger-ui"));
}