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