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

//! **`cargo install holger` serves the twelve MVP formats**
//! (lane HOL-MVP-FORMATS, owner 2026-10-30).
//!
//! The MVP format set is raw, Rust, npm, Maven, Python, Docker/OCI, Go, Helm, deb, rpm, the
//! znippy package and the Build Thing's history. server/cli's `default` (this test's
//! `holger`) and every MVP server row carry exactly
//! those (holger-recipe `tests/mvp_formats.rs` holds the declarations). Here the real binary
//! is started:
//!
//! * a RON with one repository of each of the twelve boots, answers `/healthz`, and no
//!   repository was disabled on the way (the boot log names none `DISABLED`);
//!
//! The other half, a format outside the twelve refusing the boot by name (`holger-mvp-1`), is
//! server-lib's `tests/the_mvp_build_refuses_the_formats_outside_the_mvp_set.rs`: this crate's
//! test builds link `holger-bench-scenarios` as a dev-dependency, which pins server-lib with
//! `fmt-all`, so a test binary here serves every format whatever `default` says.

use std::path::Path;
use std::process::{Command, Stdio};
use std::time::{Duration, Instant};

const BIN: &str = env!("CARGO_BIN_EXE_holger");

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

/// A znippy archive at `out` holding `files` (path, bytes): the read-only shape every
/// archive-backed format (Maven, Python, npm, Go, Helm, deb, rpm, znippy, Build Thing)
/// opens at boot.
fn archive(out: &Path, files: &[(&str, &[u8])]) {
    let src = tempfile::tempdir().unwrap();
    for (p, b) in files {
        let at = src.path().join(p);
        std::fs::create_dir_all(at.parent().unwrap()).unwrap();
        std::fs::write(at, b).unwrap();
    }
    znippy_compress::compress_dir(
        &src.path().to_path_buf(),
        &out.to_path_buf(),
        false,
        None,
        None,
        None,
    )
    .expect("compress_dir");
}

/// One repository line of the RON.
fn repo(name: &str, ty: &str, extra: &str) -> String {
    format!(
        "        (ron_name: \"{name}\", ron_repo_type: \"{ty}\", ron_upstreams: [], ron_in: None,\n\
         \x20         ron_out: Some((ron_storage_endpoint: \"store\", ron_exposed_endpoint: \"door\")){extra}),\n"
    )
}

fn ron(d: &Path, grpc: u16, http: u16, repos: &str) -> String {
    let d = d.display();
    format!(
        "(\n    exposed_endpoints: [ (ron_name: \"door\", ron_url: \"127.0.0.1:{grpc}\", ron_http_url: Some(\"127.0.0.1:{http}\")) ],\n\
         \x20   storage_endpoints: [ (ron_name: \"store\", ron_storage_type: \"znippy\", ron_path: \"{d}/store\") ],\n\
         \x20   repositories: [\n{repos}    ],\n\
         \x20   audit: (dir: \"{d}/audit\", enabled: true),\n\
         )\n"
    )
}

/// The twelve, one repository each.
fn the_twelve(d: &Path) -> String {
    let a = |name: &str, files: &[(&str, &[u8])]| {
        let p = d.join(format!("{name}.znippy"));
        archive(&p, files);
        format!(", ron_archive_path: Some(\"{}\")", p.display())
    };
    let dir = |name: &str| format!(", ron_data_dir: Some(\"{}\")", d.join(name).display());
    let mut s = String::new();
    s += &repo("raw", "raw", &dir("raw"));
    s += &repo(
        "crates",
        "rust",
        &format!(
            ", ron_writable_archive: Some(\"{}\")",
            d.join("crates.znippy").display()
        ),
    );
    s += &repo(
        "npm",
        "npm",
        &a("npm", &[("demo/-/demo-1.0.0.tgz", b"npm")]),
    );
    s += &repo(
        "maven",
        "maven3",
        &a(
            "maven",
            &[("org/demo/demo-lib/1.0.0/demo-lib-1.0.0.pom", b"<project/>")],
        ),
    );
    s += &repo(
        "pypi",
        "pip",
        &a("pypi", &[("demo_pkg-1.0.0-py3-none-any.whl", b"wheel")]),
    );
    s += &repo("oci", "oci", &dir("oci"));
    s += &repo(
        "go",
        "go",
        &a(
            "go",
            &[(
                "example.com/hello/@v/v1.0.0.mod",
                b"module example.com/hello\n",
            )],
        ),
    );
    s += &repo(
        "charts",
        "helm",
        &a("charts", &[("demo-1.0.0.tgz", b"chart")]),
    );
    s += &repo(
        "apt",
        "deb",
        &a("apt", &[("pool/main/d/demo/demo_1.0.0_amd64.deb", b"deb")]),
    );
    s += &repo(
        "yum",
        "rpm",
        &a("yum", &[("demo-1.0.0-1.x86_64.rpm", b"rpm")]),
    );
    s += &repo(
        "packages",
        "znippy",
        &a("packages", &[("demo/1.0.0/demo.txt", b"znippy")]),
    );
    s += &repo(
        "history",
        "znippy-build-thing",
        &a("history", &[("demo/1.0.0/attestation.json", b"{}")]),
    );
    s
}

/// Start `holger start --config` on `repos`; `Ok(log)` once `/healthz` answers, `Err(log)`
/// when the process exits first.
fn boot(make: impl FnOnce(&Path) -> String) -> Result<String, String> {
    let dir = tempfile::tempdir().unwrap();
    let (grpc, http) = (free_port(), free_port());
    let repos = make(dir.path());
    let cfg = dir.path().join("holger.ron");
    std::fs::write(&cfg, ron(dir.path(), grpc, http, &repos)).unwrap();
    let log = dir.path().join("boot.log");
    let mut child = Command::new(BIN)
        .args(["start", "--config"])
        .arg(&cfg)
        .env("RUST_LOG", "info")
        .stdout(Stdio::from(std::fs::File::create(&log).unwrap()))
        .stderr(Stdio::from(
            std::fs::File::create(dir.path().join("boot.err")).unwrap(),
        ))
        .spawn()
        .expect("spawn holger");
    let read = |d: &Path| {
        format!(
            "{}{}",
            std::fs::read_to_string(d.join("boot.log")).unwrap_or_default(),
            std::fs::read_to_string(d.join("boot.err")).unwrap_or_default()
        )
    };
    let deadline = Instant::now() + Duration::from_secs(60);
    while Instant::now() < deadline {
        if let Ok(Some(_)) = child.try_wait() {
            return Err(read(dir.path()));
        }
        if healthz(http) {
            let _ = child.kill();
            let _ = child.wait();
            return Ok(read(dir.path()));
        }
        std::thread::sleep(Duration::from_millis(100));
    }
    let _ = child.kill();
    let _ = child.wait();
    panic!(
        "holger neither answered /healthz nor exited in 60 s:\n{}",
        read(dir.path())
    );
}

fn healthz(port: u16) -> bool {
    use std::io::{Read, Write};
    let Ok(mut c) = std::net::TcpStream::connect(("127.0.0.1", port)) else {
        return false;
    };
    let _ = c.set_read_timeout(Some(Duration::from_secs(5)));
    if c.write_all(b"GET /healthz HTTP/1.1\r\nHost: 127.0.0.1\r\nConnection: close\r\n\r\n")
        .is_err()
    {
        return false;
    }
    let mut raw = Vec::new();
    let _ = c.read_to_end(&mut raw);
    String::from_utf8_lossy(&raw).starts_with("HTTP/1.1 200")
}

#[test]
fn a_ron_with_one_repository_of_each_mvp_format_boots() {
    match boot(the_twelve) {
        Err(log) => panic!("the twelve MVP formats (raw, rust, npm, maven3, pip, oci, go, helm, deb, rpm, znippy, znippy-build-thing) did not boot:\n{log}"),
        Ok(log) => assert!(
            !log.contains("DISABLED") && !log.contains("repositories disabled") && !log.contains("holger-mvp-1"),
            "every one of the twelve repositories comes up, none disabled:\n{log}"
        ),
    }
}