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