server/lib/tests/the_default_server_serves_no_page.rs
//! ★ **The default server serves no page** (A28 M14; owner 2026-10-22: "the server has no
//! UI"; A18: tier 1 is the server and its RON, nothing else).
//!
//! `front` (holger.rs's public page, with its "Sign up account" link) was in this crate's
//! `default`, so a plain build of the server answered `/` with HTML. It is a Build Thing
//! choice now: the variant rows that face a public name it (`full`, `mvp-server-metered`),
//! and nothing else carries it. Two halves:
//!
//! * the manifest's `default` names no `front`;
//! * a real HTTP door answers `/` and the page's own paths with no document unless this build
//! has `front` (then it does, so the switch is proven both ways).
use std::io::{Read as _, Write as _};
use holger_server_lib::{read_ron_config, wire_holger, Holger};
use traits::test_support::hold_port;
/// Pure: the features `[features] default = [...]` names in a Cargo manifest.
fn default_features(manifest: &str) -> Vec<String> {
let mut in_features = false;
for line in manifest.lines() {
let t = line.trim();
if t.starts_with('[') {
in_features = t == "[features]";
continue;
}
if in_features {
if let Some(list) = t
.strip_prefix("default")
.map(str::trim_start)
.and_then(|r| r.strip_prefix('='))
{
return list
.trim()
.trim_start_matches('[')
.trim_end_matches(']')
.split(',')
.map(|f| f.trim().trim_matches('"').to_owned())
.filter(|f| !f.is_empty())
.collect();
}
}
}
Vec::new()
}
#[test]
fn the_manifest_reader_finds_the_default_list() {
let toml = "[package]\nname = \"x\"\n[features]\n# a comment\ndefault = [\"a\", \"front\"]\nfront = []\n";
assert_eq!(default_features(toml), vec!["a", "front"]);
assert!(
default_features("[package]\ndefault = [\"x\"]\n").is_empty(),
"only [features] counts"
);
}
#[test]
fn the_default_features_name_no_front() {
let manifest = std::fs::read_to_string(concat!(env!("CARGO_MANIFEST_DIR"), "/Cargo.toml"))
.expect("server/lib/Cargo.toml");
let default = default_features(&manifest);
// H2 #3: the small core is named `downstream` (`embedded` and `mini` are its aliases).
assert!(
default.iter().any(|f| f == "downstream" || f == "embedded"),
"the reader found the real list: {default:?}"
);
assert!(
!default.iter().any(|f| f == "front"),
"server/lib's default build carries `front`, the public page with its sign-up link: \
the server has no UI (owner 2026-10-22). Name `front` in the Build Thing rows that \
face a public instead. default = {default:?}"
);
}
fn start(
grpc: u16,
http: u16,
) -> Result<(tempfile::TempDir, tempfile::NamedTempFile, Holger), String> {
let data = tempfile::tempdir().map_err(|e| e.to_string())?;
let ron = format!(
r#"(
repositories: [
(ron_name: "raw", ron_repo_type: "raw", ron_upstreams: [], ron_in: None,
ron_out: Some((ron_storage_endpoint: "storage", ron_exposed_endpoint: "default")),
ron_data_dir: Some("{data}")),
],
exposed_endpoints: [
(ron_name: "default", ron_url: "127.0.0.1:{grpc}", ron_http_url: Some("127.0.0.1:{http}")),
],
storage_endpoints: [ (ron_name: "storage", ron_storage_type: "znippy", ron_path: "{data}") ],
)"#,
data = data.path().display(),
);
let mut cfg = tempfile::NamedTempFile::new().map_err(|e| e.to_string())?;
cfg.write_all(ron.as_bytes()).map_err(|e| e.to_string())?;
let mut holger: Holger = read_ron_config(cfg.path()).map_err(|e| e.to_string())?;
holger.instantiate_backends().map_err(|e| e.to_string())?;
wire_holger(&mut holger).map_err(|e| e.to_string())?;
holger.start().map_err(|e| e.to_string())?;
Ok((data, cfg, holger))
}
/// One plain HTTP/1.1 GET: `(status, head and body as text)`.
fn get(http: u16, path: &str) -> std::io::Result<(u16, String)> {
let mut s = std::net::TcpStream::connect(("127.0.0.1", http))?;
s.set_read_timeout(Some(std::time::Duration::from_secs(10)))?;
write!(
s,
"GET {path} HTTP/1.1\r\nHost: 127.0.0.1\r\nConnection: close\r\n\r\n"
)?;
let mut raw = Vec::new();
s.read_to_end(&mut raw)?;
let text = String::from_utf8_lossy(&raw).into_owned();
let status = text
.split_whitespace()
.nth(1)
.and_then(|c| c.parse().ok())
.unwrap_or(0);
Ok((status, text))
}
#[test]
fn the_http_door_serves_the_page_only_where_front_is_built() {
// The doors are tasks on this runtime; the GETs below are plain blocking sockets.
let rt = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.expect("a runtime");
let _inside = rt.enter();
let mut last = String::new();
let mut running = None;
for _ in 0..8 {
let (grpc, http) = (hold_port(), hold_port());
match start(grpc, http) {
Ok(r) => {
running = Some((r, http));
break;
}
Err(e) => last = e,
}
}
let Some((_running, http)) = running else {
panic!("holger did not start: {last}")
};
let mut healthy = false;
for _ in 0..200 {
if matches!(get(http, "/healthz"), Ok((200, _))) {
healthy = true;
break;
}
std::thread::sleep(std::time::Duration::from_millis(25));
}
assert!(healthy, "the HTTP door never answered /healthz");
for path in ["/", "/-/front", "/holger.webp"] {
let (status, text) = get(http, path).expect("the door answers");
let lower = text.to_ascii_lowercase();
let page = status == 200
&& (lower.contains("content-type: text/html")
|| lower.contains("content-type: image/"));
if cfg!(feature = "front") {
if path == "/" {
assert!(
page && text.contains("Sign up"),
"a `front` build serves its page at /: {status}\n{text}"
);
}
} else {
assert!(
!page,
"a build without `front` served {path} ({status}): the server has no UI\n{text}"
);
assert!(
!text.contains("<html") && !text.contains("<!doctype") && !text.contains("Sign up"),
"{path} carried a document: {text}"
);
}
}
}