Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/holger

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