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