server/lib/tests/what_is_open_is_the_rons.rs
//! ★ HCON-5 (owner 2026-10-27): **what is open on a Holger is configured in its RON.** One
//! `access` block (`reads: Gated | Open`, `front: Open | Gated`) and each repository's
//! `visibility: Public`; one gate for every read door.
//!
//! * Gated, a public repository is read with no sign-in; a private one is refused by name
//! (`[read-gated]` and the repository), and read with a token.
//! * The catalog an anonymous caller is shown lists the public repository only.
//! * The front page stays open on a gated box (`front: Open`).
//! * A bad `access` block is refused at load, by name.
use std::io::Write;
use holger_server_lib::{read_ron_config, wire_holger, Holger};
use traits::test_support::hold_port;
const TOKEN: &str = "a-token-of-sufficient-length-0123456789";
fn ron(data: &std::path::Path, grpc: u16, http: u16, access: &str) -> String {
let d = data.display();
format!(
r#"(
repositories: [
(ron_name: "crates-nordisk", ron_repo_type: "raw", ron_upstreams: [], ron_in: None,
ron_out: Some((ron_storage_endpoint: "storage", ron_exposed_endpoint: "default")),
ron_data_dir: Some("{d}/crates-nordisk"), visibility: Public),
(ron_name: "pypi-internal", ron_repo_type: "raw", ron_upstreams: [], ron_in: None,
ron_out: Some((ron_storage_endpoint: "storage", ron_exposed_endpoint: "default")),
ron_data_dir: Some("{d}/pypi-internal")),
],
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: "{d}") ],
auth: Some(( methods: [ StaticToken(subject: "ci", token: Some("{TOKEN}")) ], roles: {{"ci": Admin}}, require_auth_for_reads: false )),
{access}
)"#
)
}
fn load(text: &str) -> anyhow::Result<(Holger, tempfile::NamedTempFile)> {
let mut cfg = tempfile::NamedTempFile::new().unwrap();
cfg.write_all(text.as_bytes()).unwrap();
let holger = read_ron_config(cfg.path())?;
Ok((holger, cfg))
}
async fn wait_healthy(c: &reqwest::Client, base: &str) {
for _ in 0..200 {
if let Ok(r) = c.get(format!("{base}/healthz")).send().await {
if r.status() == 200 {
return;
}
}
tokio::time::sleep(std::time::Duration::from_millis(20)).await;
}
panic!("holger never became healthy at {base}");
}
#[tokio::test(flavor = "multi_thread")]
async fn a_gated_holger_serves_its_public_repository_and_refuses_the_rest_by_name() {
let data = tempfile::tempdir().unwrap();
let (grpc, http) = (hold_port(), hold_port());
let (mut holger, _cfg) = load(&ron(
data.path(),
grpc,
http,
"access: (reads: Gated, front: Open),",
))
.expect("the declaration loads");
holger.instantiate_backends().unwrap();
wire_holger(&mut holger).unwrap();
holger.start().unwrap();
let base = format!("http://127.0.0.1:{http}");
let _ = rustls::crypto::ring::default_provider().install_default();
let c = reqwest::Client::new();
wait_healthy(&c, &base).await;
for repo in ["crates-nordisk", "pypi-internal"] {
let put = c
.put(format!("{base}/{repo}/pkg-1.0.tar"))
.bearer_auth(TOKEN)
.body(format!("{repo} bytes"))
.send()
.await
.unwrap();
assert!(
put.status().is_success(),
"{repo}: the admin writes: {}",
put.status()
);
}
let public = c
.get(format!("{base}/crates-nordisk/pkg-1.0.tar"))
.send()
.await
.unwrap();
assert_eq!(
public.status(),
200,
"a public repository is read with no sign-in on a gated box"
);
assert_eq!(public.text().await.unwrap(), "crates-nordisk bytes");
let private = c
.get(format!("{base}/pypi-internal/pkg-1.0.tar"))
.send()
.await
.unwrap();
assert_eq!(
private.status(),
401,
"a private repository needs a sign-in"
);
let words = private.text().await.unwrap();
assert!(
words.starts_with("[read-gated]") && words.contains("pypi-internal"),
"refused by name, with the repository: {words}"
);
let signed_in = c
.get(format!("{base}/pypi-internal/pkg-1.0.tar"))
.bearer_auth(TOKEN)
.send()
.await
.unwrap();
assert_eq!(
signed_in.status(),
200,
"a token reads the private repository"
);
// The page itself always carries the words it prints on its own (`page_refusals`, among
// them `holger-front-3`), so the gate is read where it answers: the `/-/front` door.
#[cfg(feature = "front")]
{
let page = c.get(format!("{base}/")).send().await.unwrap();
assert_eq!(
page.status(),
200,
"the front page is served on a gated box (front: Open)"
);
let door = c.get(format!("{base}/-/front")).send().await.unwrap();
assert_eq!(
door.status(),
200,
"the front door stays open on a gated box (front: Open)"
);
let words = door.text().await.unwrap();
assert!(
!words.contains("holger-front-3"),
"not refused as gated: {words}"
);
}
}
/// ★ HCON-5: `front: Gated` gates the front door too: an anonymous caller is refused by name
/// (`holger-front-3`), a signed-in one reads it.
#[cfg(feature = "front")]
#[tokio::test(flavor = "multi_thread")]
async fn a_gated_front_is_refused_by_name_and_read_signed_in() {
let data = tempfile::tempdir().unwrap();
let (grpc, http) = (hold_port(), hold_port());
let (mut holger, _cfg) = load(&ron(
data.path(),
grpc,
http,
"access: (reads: Gated, front: Gated),",
))
.expect("the declaration loads");
holger.instantiate_backends().unwrap();
wire_holger(&mut holger).unwrap();
holger.start().unwrap();
let base = format!("http://127.0.0.1:{http}");
let _ = rustls::crypto::ring::default_provider().install_default();
let c = reqwest::Client::new();
wait_healthy(&c, &base).await;
let anon = c.get(format!("{base}/-/front")).send().await.unwrap();
let words = anon.text().await.unwrap();
assert!(
words.contains("holger-front-3"),
"an anonymous caller is refused by name on a gated front: {words}"
);
let signed_in = c
.get(format!("{base}/-/front"))
.bearer_auth(TOKEN)
.send()
.await
.unwrap();
assert_eq!(signed_in.status(), 200, "a token reads the gated front");
assert!(!signed_in.text().await.unwrap().contains("holger-front-3"));
}
#[test]
fn the_access_block_and_the_build_say_what_is_open() {
let data = tempfile::tempdir().unwrap();
let (holger, _cfg) = load(&ron(
data.path(),
1,
2,
"access: (reads: Gated, front: Open),",
))
.unwrap();
assert!(
holger.auth.as_ref().unwrap().require_auth_for_reads,
"Gated gates every read door"
);
assert_eq!(
holger.access_words(),
"Reading packages needs a sign-in. Public, readable with no sign-in: crates-nordisk. The front page and the sign-up are open."
);
let (open, _cfg) = load(&ron(
data.path(),
1,
2,
"access: (reads: Open, front: Open),",
))
.unwrap();
assert!(
!open.auth.as_ref().unwrap().require_auth_for_reads,
"Open reads openly, on any build"
);
let (default, _cfg) = load(&ron(data.path(), 1, 2, "")).unwrap();
assert_eq!(
default.auth.as_ref().unwrap().require_auth_for_reads,
cfg!(feature = "estate"),
"no access block: the build decides (an estate gates, a downstream reads openly)"
);
}
#[test]
fn a_bad_access_block_is_refused_at_load_by_name() {
let data = tempfile::tempdir().unwrap();
let contradiction = ron(data.path(), 1, 2, "access: (reads: Open, front: Open),").replace(
"require_auth_for_reads: false",
"require_auth_for_reads: true",
);
let e = load(&contradiction).err().expect("refused").to_string();
assert!(e.contains("[access-refused]"), "{e}");
let e = load(&ron(data.path(), 1, 2, "access: (reads: Closed),"))
.err()
.expect("refused")
.to_string();
assert!(e.contains("Closed"), "an unknown word is named: {e}");
let e = load(&ron(
data.path(),
1,
2,
"access: (reads: Gated, frontpage: Open),",
))
.err()
.expect("refused")
.to_string();
assert!(e.contains("frontpage"), "an unknown key is named: {e}");
let gated_public = ron(data.path(), 1, 2, "").replace(
"visibility: Public)",
"visibility: Public, ron_require_auth: true)",
);
let e = load(&gated_public).err().expect("refused").to_string();
assert!(
e.contains("[access-refused]") && e.contains("crates-nordisk"),
"{e}"
);
}