server/cli/tests/one_way_ships_no_client.rs
//! **The one-way downstream ships no way out** (lane HOL-CDS, owner 2026-10-30): the
//! default `holger-downstream` build (`--no-default-features --features downstream`) is
//! resolved with `cargo tree` and must link
//!
//! * no outbound HTTP client (reqwest, ureq and the rest of [`HTTP_CLIENTS`]),
//! * no WireGuard dialer (boringtun, the `downstream-wg` tunnel's wg-appliance-core),
//! * and server-lib WITHOUT its `fetch` feature: the mirror timer and its connector, the
//! gRPC client of another Holger (`RemoteHolger`), the sync pull and its receipt are all
//! `#[cfg(feature = "fetch")]`, so with the feature off they are not compiled at all.
//!
//! Non-vacuous: `downstream,fetch` DOES carry server-lib `fetch`, and `downstream,fetch,
//! downstream-wg` the WireGuard dialer.
//!
//! Known and named, not hidden: the tonic `channel` feature (with hyper's client half) is
//! still in the graph, switched on by edda's published `nornir-service` (`tonic/transport`).
//! No holger code in the one-way build uses it (no `RemoteHolger`, no generated client is
//! constructed); dropping it needs a published nornir-service on tonic `server` only (plan row
//! HOL-CDS-3).
use std::path::Path;
use std::process::Command;
fn repo_root() -> &'static Path {
Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/../.."))
}
/// `cargo tree -p holger --no-default-features --features <f> -e <edges> [-i <invert>]`, one
/// line per node. The features a package is built with show in the INVERTED tree.
fn tree_of(features: &str, edges: &str, invert: Option<&str>) -> String {
let mut args = vec![
"tree",
"-p",
"holger",
"--no-default-features",
"--features",
features,
"-e",
edges,
"--prefix",
"none",
];
if let Some(i) = invert {
args.extend(["-i", i]);
}
let out = Command::new(env!("CARGO"))
.current_dir(repo_root())
.args(&args)
.output()
.expect("cargo tree runs");
assert!(
out.status.success(),
"cargo tree --features {features}: {}",
String::from_utf8_lossy(&out.stderr)
);
String::from_utf8_lossy(&out.stdout).into_owned()
}
fn tree(features: &str, edges: &str) -> String {
tree_of(features, edges, None)
}
/// The features server-lib is built with, as `holger-server-lib feature "<f>"` lines.
fn server_lib_features(features: &str) -> String {
tree_of(features, "normal,features", Some("holger-server-lib"))
}
/// Outbound clients a one-way downstream must never link.
const HTTP_CLIENTS: &[&str] = &[
"reqwest",
"ureq",
"isahc",
"curl",
"surf",
"attohttpc",
"awc",
"http-client",
];
const DIALERS: &[&str] = &["boringtun", "wg-appliance-core"];
/// The crates that depend on `krate` DIRECTLY in the `--features <features>` build (the
/// inverted tree's first level).
fn direct_dependents(features: &str, krate: &str) -> Vec<String> {
let out = Command::new(env!("CARGO"))
.current_dir(repo_root())
.args([
"tree",
"-p",
"holger",
"--no-default-features",
"--features",
features,
"-e",
"normal",
"--prefix",
"depth",
"-i",
krate,
])
.output()
.expect("cargo tree runs");
assert!(
out.status.success(),
"cargo tree -i {krate}: {}",
String::from_utf8_lossy(&out.stderr)
);
let mut by: Vec<String> = String::from_utf8_lossy(&out.stdout)
.lines()
.filter_map(|l| l.strip_prefix('1'))
.filter(|l| !l.starts_with(char::is_numeric))
.filter_map(|l| l.split_whitespace().next())
.map(String::from)
.collect();
by.sort();
by.dedup();
by
}
fn crates(tree: &str) -> Vec<String> {
tree.lines()
.filter_map(|l| l.split_whitespace().next())
.map(String::from)
.collect()
}
#[test]
fn the_one_way_downstream_links_no_client_and_no_fetch_code() {
let one_way = crates(&tree("downstream", "normal"));
for c in HTTP_CLIENTS.iter().chain(DIALERS) {
assert!(
!one_way.iter().any(|x| x == c),
"the one-way downstream links `{c}`"
);
}
let features = server_lib_features("downstream");
assert!(
features.contains("holger-server-lib feature \"gateway\""),
"the tree is the downstream's:\n{features}"
);
assert!(
!features.contains("holger-server-lib feature \"fetch\""),
"the one-way downstream compiles server-lib's fetch code (mirror, RemoteHolger, sync pull, receipt)"
);
assert!(
!features.contains("holger feature \"fetch\""),
"the one-way downstream turns `fetch` on"
);
assert!(
!features.contains("holger-server-lib feature \"export-push\""),
"the one-way downstream carries the upstream's push"
);
}
#[test]
fn fetch_is_what_adds_the_fetch_code_and_the_tunnel() {
let features = server_lib_features("downstream,fetch");
assert!(
features.contains("holger-server-lib feature \"fetch\""),
"`--features fetch` must carry server-lib `fetch`:\n{features}"
);
let wg = crates(&tree("downstream,fetch,downstream-wg", "normal"));
assert!(
wg.iter().any(|x| x == "boringtun"),
"the fetching WireGuard downstream carries its dialer"
);
}
/// ★ **The one-way appliance** (lane HOL-CDS-5, row `downstream-musl` = `downstream,pid1`): its
/// own init adds no way out: no client, no WireGuard dialer, no pairing, no server-lib `fetch`.
#[test]
fn the_one_way_appliance_links_no_client_no_dialer_and_no_pairing() {
let appliance = crates(&tree("downstream,pid1", "normal"));
assert!(
appliance.iter().any(|x| x == "appliance-pid1"),
"the init is linked"
);
for c in HTTP_CLIENTS
.iter()
.chain(["nordisk-pairing", "holger-live"].iter())
{
assert!(
!appliance.iter().any(|x| x == c),
"the one-way appliance links `{c}`"
);
}
// The init's medium reads (the command line, the data partition, the slot's archive)
// are tunnr's korp-installer on `default-features = false`. From 0.2.11 its WireGuard
// identity ceremony is the feature `wg-identity` (plan row HOL-CDS-6), so the one-way
// appliance links korp-installer and NO dialer at all: neither wg-appliance-core nor
// boringtun is in its graph, by any path.
assert!(
appliance.iter().any(|x| x == "korp-installer"),
"the init reads the medium through korp-installer"
);
for dialer in DIALERS {
assert!(
!appliance.iter().any(|x| x == dialer),
"the one-way appliance links the WireGuard dialer `{dialer}` (by {:?})",
direct_dependents("downstream,pid1", dialer)
);
}
let features = server_lib_features("downstream,pid1");
assert!(
!features.contains("holger-server-lib feature \"fetch\""),
"the one-way appliance compiles the fetch code"
);
}
/// **The fetching build adds no client crate** (lane HOL-SANDBOX-TRAIT): `downstream,fetch`
/// resolves to exactly the one-way build's crates, so its only way out is server-lib's
/// `fetch` code over the tonic channel already named above (the mirror's `RemoteHolger`, the
/// sync pull). No HTTP client, no dialer, no new crate may ride in with it. Both builds
/// verify every bundle in a job (holger-job-runner). Moving that fetch code itself into a
/// fetch job (network to its one upstream only, its output into the inbox) waits for a
/// runner that gives a job exactly one peer: the interim container runner gives a job no
/// network at all (plan row HOL-SANDBOX-TRAIT-FETCH).
#[test]
fn the_fetching_build_links_no_client_crate_beyond_the_one_way_build() {
use std::collections::BTreeSet;
let one_way: BTreeSet<String> = crates(&tree("downstream", "normal")).into_iter().collect();
let fetch: BTreeSet<String> = crates(&tree("downstream,fetch", "normal"))
.into_iter()
.collect();
let added: Vec<&String> = fetch.difference(&one_way).collect();
assert!(
added.is_empty(),
"`--features fetch` links crates the one-way build does not: {added:?}"
);
for c in HTTP_CLIENTS.iter().chain(DIALERS) {
assert!(!fetch.contains(*c), "the fetching downstream links `{c}`");
}
for b in [&one_way, &fetch] {
assert!(
b.contains("holger-job-runner"),
"every downstream verifies its bundles in a job"
);
}
}