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_default_build_dials_nothing.rs

//! **The default build dials nothing** (Holger v2.0 B2, the airgap defaults; HCUT 2026-10-26).
//!
//! The real `holger` binary of `cargo install holger` (the default features), started
//! under H3's connect trap (`tests/support/connect_trap.rs`) with a config that names no
//! upstream and says no `network:`: it boots as `network: Offline` (the default), takes an
//! upload, serves it, and in all that time makes no outbound connection and no DNS
//! lookup. The only sockets it may connect are its own doors on loopback.
// An estate build runs none of this file's airgap proof (HCON-6): its helpers are unused there.
#![cfg_attr(feature = "estate", allow(unused_imports, dead_code))]

use std::collections::BTreeSet;
use std::io::{Read, Write};
use std::net::TcpStream;
use std::path::Path;
use std::process::{Command, Stdio};
use std::time::{Duration, Instant};

#[path = "support/connect_trap.rs"]
mod connect_trap;
use connect_trap::{dialled, trapped};

const BIN: &str = env!("CARGO_BIN_EXE_holger");
const TOKEN: &str = "airgap-default-token-0000000000000000";

fn free_port() -> u16 {
    std::net::TcpListener::bind("127.0.0.1:0")
        .unwrap()
        .local_addr()
        .unwrap()
        .port()
}

fn config(d: &Path, grpc: u16, http: u16) -> String {
    let d = d.display();
    format!(
        r#"(
    exposed_endpoints: [ (ron_name: "door", ron_url: "127.0.0.1:{grpc}", ron_http_url: Some("127.0.0.1:{http}")) ],
    storage_endpoints: [ (ron_name: "disk", ron_storage_type: "znippy", ron_path: "{d}/store") ],
    repositories: [
        (ron_name: "files", ron_repo_type: "raw", ron_upstreams: [], ron_in: None,
         ron_out: Some((ron_storage_endpoint: "disk", ron_exposed_endpoint: "door")), ron_data_dir: Some("{d}/files")),
    ],
    audit: (dir: "{d}/audit", enabled: true),
    auth: Some(( methods: [ StaticToken(subject: "ops", token: Some("{TOKEN}")) ], roles: {{ "ops": Admin }} )),
)"#
    )
}

fn request(port: u16, method: &str, path: &str, body: &[u8]) -> (u16, String) {
    let mut c = TcpStream::connect(("127.0.0.1", port)).unwrap();
    c.set_read_timeout(Some(Duration::from_secs(20))).unwrap();
    write!(
        c,
        "{method} {path} HTTP/1.1\r\nHost: 127.0.0.1\r\nAuthorization: Bearer {TOKEN}\r\nContent-Length: {}\r\nConnection: close\r\n\r\n",
        body.len()
    )
    .unwrap();
    c.write_all(body).unwrap();
    let mut raw = Vec::new();
    let _ = c.read_to_end(&mut raw);
    let text = String::from_utf8_lossy(&raw).to_string();
    let status = text
        .split(' ')
        .nth(1)
        .and_then(|s| s.parse().ok())
        .unwrap_or(0);
    (
        status,
        text.split_once("\r\n\r\n")
            .map(|(_, b)| b.to_string())
            .unwrap_or_default(),
    )
}

/// The default and the downstream builds only. An ESTATE build (`estate`, which `metered`
/// implies: the sold, online product) is online by design: its box dials its front, its twin
/// and the ONE box, so it never boots Offline (HCON-6; server-lib
/// `tests/the_airgap_is_the_default.rs` holds the estate's reason by name).
#[cfg(not(feature = "estate"))]
#[test]
fn the_default_build_with_no_upstream_boots_offline_and_dials_nothing() {
    let dir = tempfile::tempdir().unwrap();
    let (grpc, http) = (free_port(), free_port());
    let cfg = dir.path().join("holger.ron");
    std::fs::write(&cfg, config(dir.path(), grpc, http)).unwrap();
    let (connects, log) = (
        dir.path().join("connects.log"),
        dir.path().join("server.log"),
    );
    let out = std::fs::File::create(&log).unwrap();
    let mut child = trapped(&mut Command::new(BIN), &connects)
        .args(["start", "--config"])
        .arg(&cfg)
        .stdout(Stdio::from(out.try_clone().unwrap()))
        .stderr(Stdio::from(out))
        .spawn()
        .expect("spawn holger");
    let server_log = || std::fs::read_to_string(&log).unwrap_or_default();
    let t = Instant::now();
    while TcpStream::connect(("127.0.0.1", http)).is_err() {
        assert!(
            t.elapsed() < Duration::from_secs(60),
            "holger never listened:\n{}",
            server_log()
        );
        std::thread::sleep(Duration::from_millis(100));
    }
    let (healthz, _) = request(http, "GET", "/healthz", b"");
    let (put, put_body) = request(
        http,
        "PUT",
        "/files/notes/readme.txt",
        b"the airgap is the default\n",
    );
    let (get, body) = request(http, "GET", "/files/notes/readme.txt", b"");
    std::thread::sleep(Duration::from_secs(2));
    let _ = child.kill();
    let _ = child.wait();

    assert_eq!(healthz, 200, "{}", server_log());
    assert!(
        (200..300).contains(&put),
        "the upload: {put} {put_body}\n{}",
        server_log()
    );
    assert_eq!(
        (get, body.as_str()),
        (200, "the airgap is the default\n"),
        "{}",
        server_log()
    );
    assert!(
        server_log().contains("network: Offline (the default"),
        "the boot says it is offline by default:\n{}",
        server_log()
    );

    let own: BTreeSet<String> = [grpc, http]
        .iter()
        .map(|p| format!("inet 127.0.0.1 {p}"))
        .collect();
    let out: Vec<String> = dialled(&connects)
        .into_iter()
        .filter(|l| !own.contains(l))
        .collect();
    assert!(
        out.is_empty(),
        "the default build dialled out with no upstream configured:\n  {}\n{}",
        out.join("\n  "),
        server_log()
    );
}