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

nordisk/edda

crates/estate-front/src/web/tests.rs

//! **The whole certificate road, driven end to end on loopback** — a small
//! ACME server (RFC 8555, the parts `instant-acme` speaks) written for these
//! tests, because no pure-Rust ACME test server exists to reuse (pebble is Go;
//! the one Rust crate, `coyote`, links openssl and postgres). It is honest
//! where it matters: it VALIDATES the http-01 challenge by fetching it from
//! the front's own :80 listener and checking the key authorization against
//! the account key's JWK thumbprint (RFC 7638), and it signs the CSR the front
//! sends with a CA of its own.
#![cfg_attr(not(feature = "acme"), allow(dead_code, unused_imports))] // A32-11: the fake ACME directory serves only the `acme` tests.

use std::collections::BTreeMap;
use std::net::SocketAddr;
use std::sync::{Arc, Mutex};
use std::time::{Duration, SystemTime};

use axum::extract::{Path as UrlPath, State};
use axum::http::{header, StatusCode};
use axum::response::{IntoResponse, Response};
use axum::routing::{get, post};
use axum::Router;
use base64::Engine as _;

use super::*;

const B64: base64::engine::GeneralPurpose = base64::engine::general_purpose::URL_SAFE_NO_PAD;

struct Fake {
    base: String,
    /// Where the front's :80 is — the validator dials it with the name as Host.
    front_http: SocketAddr,
    ca_cert: rcgen::Certificate,
    ca_key: rcgen::KeyPair,
    thumbprint: Mutex<Option<String>>,
    names: Mutex<Vec<String>>,
    valid: Mutex<Vec<bool>>,
    cert: Mutex<Option<String>>,
    /// How many certificates were issued — the rate-limit tests count it.
    issued: Mutex<u32>,
    /// How many times the directory was read, and orders placed: zero while DNS is wrong.
    asked: Mutex<u32>,
    orders: Mutex<u32>,
    /// Per authorization: whether its challenge was tried (a tried, failed one is `invalid`,
    /// as Let's Encrypt answers a failed validation).
    tried: Mutex<Vec<bool>>,
    /// Refuse every new order (a rate limit, say): an ACME failure that is not a validation.
    refuse_orders: Mutex<bool>,
}

type F = Arc<Fake>;

fn nonce() -> [(header::HeaderName, String); 1] {
    [(header::HeaderName::from_static("replay-nonce"), format!("n{}", rand_core::RngCore::next_u64(&mut rand_core::OsRng)))]
}

/// `(protected header, payload)` of a flattened JWS.
fn jws(body: &str) -> (serde_json::Value, serde_json::Value) {
    let v: serde_json::Value = serde_json::from_str(body).unwrap();
    let dec = |k: &str| -> serde_json::Value {
        let s = v[k].as_str().unwrap_or("");
        if s.is_empty() {
            serde_json::Value::Null
        } else {
            serde_json::from_slice(&B64.decode(s).unwrap()).unwrap()
        }
    };
    (dec("protected"), dec("payload"))
}

fn json(status: StatusCode, location: Option<String>, body: serde_json::Value) -> Response {
    let mut r = (status, nonce(), [(header::CONTENT_TYPE, "application/json".to_string())], body.to_string()).into_response();
    if let Some(l) = location {
        r.headers_mut().insert(header::LOCATION, l.parse().unwrap());
    }
    r
}

async fn directory(State(f): State<F>) -> Response {
    *f.asked.lock().unwrap() += 1;
    let b = &f.base;
    json(StatusCode::OK, None, serde_json::json!({"newNonce": format!("{b}/nonce"), "newAccount": format!("{b}/acct"), "newOrder": format!("{b}/order")}))
}

async fn new_nonce() -> Response {
    (StatusCode::OK, nonce()).into_response()
}

async fn new_account(State(f): State<F>, body: String) -> Response {
    let (protected, _) = jws(&body);
    let jwk = &protected["jwk"];
    // RFC 7638: the required members, lexicographic, no whitespace.
    let canon = format!(r#"{{"crv":"{}","kty":"{}","x":"{}","y":"{}"}}"#, jwk["crv"].as_str().unwrap(), jwk["kty"].as_str().unwrap(), jwk["x"].as_str().unwrap(), jwk["y"].as_str().unwrap());
    *f.thumbprint.lock().unwrap() = Some(B64.encode(<sha2::Sha256 as sha2::Digest>::digest(canon.as_bytes())));
    json(StatusCode::CREATED, Some(format!("{}/acct/1", f.base)), serde_json::json!({"status": "valid"}))
}

fn order_body(f: &Fake) -> serde_json::Value {
    let names = f.names.lock().unwrap().clone();
    let valid = f.valid.lock().unwrap().clone();
    let cert = f.cert.lock().unwrap().is_some();
    let failed = f.tried.lock().unwrap().iter().zip(valid.iter()).any(|(t, v)| *t && !*v);
    let status = if cert {
        "valid"
    } else if failed {
        "invalid"
    } else if !valid.is_empty() && valid.iter().all(|v| *v) {
        "ready"
    } else {
        "pending"
    };
    let mut o = serde_json::json!({
        "status": status,
        "identifiers": names.iter().map(|n| serde_json::json!({"type": "dns", "value": n})).collect::<Vec<_>>(),
        "authorizations": (0..names.len()).map(|i| format!("{}/authz/{i}", f.base)).collect::<Vec<_>>(),
        "finalize": format!("{}/finalize", f.base),
    });
    if cert {
        o["certificate"] = serde_json::Value::String(format!("{}/cert", f.base));
    }
    o
}

async fn new_order(State(f): State<F>, body: String) -> Response {
    let (_, payload) = jws(&body);
    if *f.refuse_orders.lock().unwrap() {
        *f.orders.lock().unwrap() += 1;
        return json(StatusCode::TOO_MANY_REQUESTS, None, serde_json::json!({"type": "urn:ietf:params:acme:error:rateLimited", "detail": "too many orders (fake)", "status": 429}));
    }
    let names: Vec<String> = payload["identifiers"].as_array().unwrap().iter().map(|i| i["value"].as_str().unwrap().to_string()).collect();
    *f.valid.lock().unwrap() = vec![false; names.len()];
    *f.tried.lock().unwrap() = vec![false; names.len()];
    *f.orders.lock().unwrap() += 1;
    *f.names.lock().unwrap() = names;
    *f.cert.lock().unwrap() = None;
    json(StatusCode::CREATED, Some(format!("{}/order/1", f.base)), order_body(&f))
}

async fn order(State(f): State<F>) -> Response {
    json(StatusCode::OK, None, order_body(&f))
}

fn authz_body(f: &Fake, i: usize) -> serde_json::Value {
    let name = f.names.lock().unwrap()[i].clone();
    let valid = f.valid.lock().unwrap()[i];
    let tried = f.tried.lock().unwrap()[i];
    let s = if valid { "valid" } else if tried { "invalid" } else { "pending" };
    serde_json::json!({"status": s, "identifier": {"type": "dns", "value": name},
        "challenges": [{"type": "http-01", "url": format!("{}/chall/{i}", f.base), "token": format!("tok{i}"), "status": s}]})
}

async fn authz(State(f): State<F>, UrlPath(i): UrlPath<usize>) -> Response {
    json(StatusCode::OK, None, authz_body(&f, i))
}

/// **The validation**: GET the token from the front's :80 with the name as
/// Host, and compare with `token.thumbprint`.
async fn challenge(State(f): State<F>, UrlPath(i): UrlPath<usize>) -> Response {
    let name = f.names.lock().unwrap()[i].clone();
    let token = format!("tok{i}");
    let expected = format!("{token}.{}", f.thumbprint.lock().unwrap().clone().unwrap());
    use tokio::io::{AsyncReadExt as _, AsyncWriteExt as _};
    // The name may point elsewhere (a dead address): that is a failed validation.
    let mut got = String::new();
    if let Ok(mut tcp) = tokio::net::TcpStream::connect(f.front_http).await {
        tcp.write_all(format!("GET /.well-known/acme-challenge/{token} HTTP/1.1\r\nHost: {name}\r\nConnection: close\r\n\r\n").as_bytes()).await.unwrap();
        let _ = tcp.read_to_string(&mut got).await;
    }
    let ok = got.starts_with("HTTP/1.1 200") && got.ends_with(&expected);
    f.valid.lock().unwrap()[i] = ok;
    f.tried.lock().unwrap()[i] = true;
    json(StatusCode::OK, None, serde_json::json!({"type": "http-01", "url": format!("{}/chall/{i}", f.base), "token": token, "status": if ok { "valid" } else { "invalid" }}))
}

async fn finalize(State(f): State<F>, body: String) -> Response {
    let (_, payload) = jws(&body);
    let der = B64.decode(payload["csr"].as_str().unwrap()).unwrap();
    let csr = rcgen::CertificateSigningRequestParams::from_der(&der.into()).unwrap();
    let leaf = csr.signed_by(&f.ca_cert, &f.ca_key).unwrap();
    *f.cert.lock().unwrap() = Some(format!("{}{}", leaf.pem(), f.ca_cert.pem()));
    *f.issued.lock().unwrap() += 1;
    json(StatusCode::OK, Some(format!("{}/order/1", f.base)), order_body(&f))
}

async fn certificate(State(f): State<F>) -> Response {
    (StatusCode::OK, nonce(), [(header::CONTENT_TYPE, "application/pem-certificate-chain".to_string())], f.cert.lock().unwrap().clone().unwrap()).into_response()
}

/// Start the fake ACME server; answers its directory URL and the server.
async fn fake_acme(front_http: SocketAddr) -> (String, F) {
    let l = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
    let base = format!("http://{}", l.local_addr().unwrap());
    let ca_key = rcgen::KeyPair::generate().unwrap();
    let mut p = rcgen::CertificateParams::new(Vec::<String>::new()).unwrap();
    p.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Unconstrained);
    p.distinguished_name.push(rcgen::DnType::CommonName, "fake acme CA");
    let ca_cert = p.self_signed(&ca_key).unwrap();
    let f = Arc::new(Fake { base: base.clone(), front_http, ca_cert, ca_key, thumbprint: Mutex::new(None), names: Mutex::new(vec![]), valid: Mutex::new(vec![]), cert: Mutex::new(None), issued: Mutex::new(0), asked: Mutex::new(0), orders: Mutex::new(0), tried: Mutex::new(vec![]), refuse_orders: Mutex::new(false) });
    let app = Router::new()
        .route("/dir", get(directory))
        .route("/nonce", get(new_nonce).head(new_nonce))
        .route("/acct", post(new_account))
        .route("/order", post(new_order))
        .route("/order/1", post(order))
        .route("/authz/{i}", post(authz))
        .route("/chall/{i}", post(challenge))
        .route("/finalize", post(finalize))
        .route("/cert", post(certificate))
        .with_state(Arc::clone(&f));
    tokio::spawn(async move { axum::serve(l, app).await.unwrap() });
    (format!("{base}/dir"), f)
}

/// GET `path` over TLS from `addr` presenting SNI `name`, trusting `ca_pem`.
async fn https_get(addr: SocketAddr, name: &str, ca_pem: &str, path: &str) -> Result<String, String> {
    use rustls::pki_types::pem::PemObject as _;
    use tokio::io::{AsyncReadExt as _, AsyncWriteExt as _};
    let mut roots = rustls::RootCertStore::empty();
    for c in rustls::pki_types::CertificateDer::pem_slice_iter(ca_pem.as_bytes()) {
        roots.add(c.unwrap()).unwrap();
    }
    let cfg = rustls::ClientConfig::builder_with_provider(Arc::new(rustls::crypto::ring::default_provider())).with_safe_default_protocol_versions().unwrap().with_root_certificates(roots).with_no_client_auth();
    let tcp = tokio::net::TcpStream::connect(addr).await.map_err(|e| e.to_string())?;
    let sni = rustls::pki_types::ServerName::try_from(name.to_string()).unwrap();
    let mut tls = tokio_rustls::TlsConnector::from(Arc::new(cfg)).connect(sni, tcp).await.map_err(|e| e.to_string())?;
    tls.write_all(format!("GET {path} HTTP/1.1\r\nHost: {name}\r\nConnection: close\r\n\r\n").as_bytes()).await.map_err(|e| e.to_string())?;
    let mut out = String::new();
    let _ = tls.read_to_string(&mut out).await;
    Ok(out)
}

async fn listeners() -> (tokio::net::TcpListener, tokio::net::TcpListener, SocketAddr, SocketAddr) {
    let a = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
    let b = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
    let (x, y) = (a.local_addr().unwrap(), b.local_addr().unwrap());
    (a, b, x, y)
}

async fn until<T>(what: &str, mut f: impl FnMut() -> Option<T>) -> T {
    for _ in 0..200 {
        if let Some(t) = f() {
            return t;
        }
        tokio::time::sleep(Duration::from_millis(100)).await;
    }
    panic!("timed out waiting for {what}");
}

/// **Nothing fed, nothing kept**: the front asks ACME, answers the http-01
/// challenge on its own :80, keeps the certificate on its data disk and
/// serves it on :443 — no restart anywhere. A second front on the same data
/// disk (a reboot) serves the kept one and does NOT ask again.
#[cfg(feature = "acme")]
#[tokio::test(flavor = "multi_thread")]
async fn with_nothing_fed_the_front_gets_its_certificate_by_acme_and_serves_it_live() {
    let dir = tempfile::tempdir().unwrap();
    let (l80, l443, a80, a443) = listeners().await;
    let (directory, acme) = fake_acme(a80).await;
    let feed: BTreeMap<String, String> = BTreeMap::from([(FEED_ACME_DIRECTORY.to_string(), directory.clone()), (FEED_WEB_HOSTS.to_string(), "example.test, www.example.test".to_string())]);
    let f2 = feed.clone();
    let web = start_on_probed(dir.path(), "ignored.estate".into(), Arc::new(move || f2.clone()), l80, l443, here());
    until("the ACME certificate", || web.resolver.any().then_some(())).await;
    let ca = acme.ca_cert.pem();
    let page = https_get(a443, "www.example.test", &ca, "/health").await.unwrap();
    assert!(page.starts_with("HTTP/1.1 200") && page.ends_with("ok\n"), "{page}");
    // Both names are in it: the bare site's handshake is valid, and its answer the 301 to www (WWW-CANON).
    let bare = https_get(a443, "example.test", &ca, "/health").await.unwrap();
    assert_eq!(status_and_location(&bare), (301, Some("https://www.example.test/health".into())), "{bare}");
    let kept = certs::kept(dir.path(), "example.test", SystemTime::now()).unwrap().unwrap();
    assert_eq!(kept.names, vec!["example.test".to_string(), "www.example.test".to_string()]);
    assert_eq!(*acme.issued.lock().unwrap(), 1);

    // The site is swapped live: the next connection gets the new router.
    web.app.set(Router::new().route("/health", get(|| async { "swapped\n" })));
    assert!(https_get(a443, "www.example.test", &ca, "/health").await.unwrap().ends_with("swapped\n"));

    // :80 redirects everything that is not a challenge; the bare site straight to www (WWW-CANON).
    let mut tcp = tokio::net::TcpStream::connect(a80).await.unwrap();
    use tokio::io::{AsyncReadExt as _, AsyncWriteExt as _};
    tcp.write_all(b"GET /some/page?x=1 HTTP/1.1\r\nHost: example.test\r\nConnection: close\r\n\r\n").await.unwrap();
    let mut got = String::new();
    tcp.read_to_string(&mut got).await.unwrap();
    assert!(got.starts_with("HTTP/1.1 301") && got.to_ascii_lowercase().contains("location: https://www.example.test/some/page?x=1"), "{got}");

    // A reboot on the same disk: served from the disk, ACME not asked.
    let again = Web::new(dir.path());
    let cfg = WebConfig::from("x", &feed);
    let (due, _) = again.look(&cfg, &feed, SystemTime::now());
    assert!(!due && again.resolver.any(), "the kept certificate serves; no second order");
}

/// **A rebuild carries the certificate over**: fed with more than thirty days
/// left, it is served and kept, and ACME is never asked — Let's Encrypt's
/// weekly five are not spent on a rebuild. Near expiry, ACME is asked and its
/// certificate replaces the fed one live.
#[cfg(feature = "acme")]
#[tokio::test(flavor = "multi_thread")]
async fn a_fed_certificate_is_served_first_and_acme_is_asked_only_near_its_expiry() {
    let dir = tempfile::tempdir().unwrap();
    let (l80, l443, a80, a443) = listeners().await;
    let (directory, acme) = fake_acme(a80).await;
    let (chain, key) = certs::tests::minted(&["example.test"], 80);
    let feed = Arc::new(Mutex::new(BTreeMap::from([
        (FEED_ACME_DIRECTORY.to_string(), directory.clone()),
        (FEED_WEB_HOSTS.to_string(), "example.test".to_string()),
        (certs::fed_cert_key("example.test"), chain.clone()),
        (certs::fed_key_key("example.test"), key.clone()),
    ])));
    let f2 = Arc::clone(&feed);
    let web = start_on_probed(dir.path(), "x".into(), Arc::new(move || f2.lock().unwrap().clone()), l80, l443, here());
    until("the fed certificate", || web.resolver.any().then_some(())).await;
    // The self-signed fed certificate is its own root.
    assert!(https_get(a443, "example.test", &chain, "/health").await.unwrap().starts_with("HTTP/1.1 200"));
    assert_eq!(certs::kept(dir.path(), "example.test", SystemTime::now()).unwrap().unwrap().chain_pem, chain, "kept on the disk");
    tokio::time::sleep(Duration::from_millis(500)).await;
    assert_eq!(*acme.issued.lock().unwrap(), 0, "a fed certificate with 80 days left: ACME is not asked");

    // Near expiry (10 days left) — asked, and served live.
    let (c10, k10) = certs::tests::minted(&["example.test"], 10);
    let web2 = Web::new(tempfile::tempdir().unwrap().path());
    let near = BTreeMap::from([
        (FEED_ACME_DIRECTORY.to_string(), directory),
        (FEED_WEB_HOSTS.to_string(), "example.test".to_string()),
        (certs::fed_cert_key("example.test"), c10),
        (certs::fed_key_key("example.test"), k10),
    ]);
    let cfg = WebConfig::from("x", &near);
    let (due, _) = web2.look(&cfg, &near, SystemTime::now());
    assert!(due, "ten days left: renew");
    assert!(web2.resolver.any(), "the fed one serves meanwhile");
}

#[test]
fn the_web_config_defaults_to_the_estate_and_lets_encrypt() {
    let c = WebConfig::from("Example.test", &BTreeMap::new());
    assert_eq!(c, WebConfig { hosts: vec!["example.test".into()], directory: acme::LETS_ENCRYPT.into(), contact: None, callback_only: vec![], control_host: None });
    let c = WebConfig::from("example.test", &BTreeMap::from([(FEED_WEB_HOSTS.to_string(), " example.test ,www.example.test".to_string()), (FEED_ACME_CONTACT.to_string(), "ops@example.test".to_string())]));
    assert_eq!(c.hosts, vec!["example.test", "www.example.test"]);
    assert_eq!(c.name(), "example.test");
    assert_eq!(c.contact.as_deref(), Some("ops@example.test"));
}

/// A malformed fed certificate is refused by name and the kept one serves.
#[test]
fn a_bad_fed_certificate_is_refused_by_name_and_the_kept_one_serves() {
    let dir = tempfile::tempdir().unwrap();
    let (c, k) = certs::tests::minted(&["example.test"], 80);
    certs::keep(dir.path(), "example.test", &certs::Cert::parse("example.test", &c, &k, SystemTime::now()).unwrap()).unwrap();
    let (other_c, _) = certs::tests::minted(&["example.test"], 80);
    let feed = BTreeMap::from([(certs::fed_cert_key("example.test"), other_c), (certs::fed_key_key("example.test"), k)]);
    let web = Web::new(dir.path());
    let cfg = WebConfig::from("example.test", &feed);
    let (due, lines) = web.look(&cfg, &feed, SystemTime::now());
    assert!(!due && web.resolver.any());
    assert!(lines.iter().any(|l| l.contains("[tls-key-mismatch]")), "{lines:?}");
}

/// **No ACME before the first feed; a new feed is looked at at once.**
/// MEASURED 2026-09-26 (loki, mock bench run 8): the one look a minute ran at
/// boot, asked Let's Encrypt production for the bare estate name before the
/// installer had fed anything, and the certificate fed two seconds later was
/// not kept before the install closed — the hand-over was empty.
#[test]
fn acme_waits_for_the_first_feed_and_a_changed_feed_is_looked_at_now() {
    let empty = BTreeMap::new();
    let fed = BTreeMap::from([(FEED_WEB_HOSTS.to_string(), "example.test".to_string())]);
    assert!(!acme_may_ask(true, &empty, true), "nothing fed: ACME is not asked");
    assert!(acme_may_ask(true, &fed, true));
    assert!(!acme_may_ask(false, &fed, true), "not due: not asked");
    assert!(!acme_may_ask(true, &fed, false), "the switch off: not asked (AIRGAP B1)");
    assert!(look_now(None, &empty), "the first look is at once");
    assert!(!look_now(Some((&empty, Duration::from_secs(3))), &empty), "unchanged and recent: no look");
    assert!(look_now(Some((&empty, Duration::from_secs(3))), &fed), "the feed changed: look now");
    assert!(look_now(Some((&fed, LOOK_EVERY)), &fed), "a minute passed: look again");
}

/// End to end: a front that boots unfed asks nobody, and the certificate the
/// installer feeds a moment later is served and KEPT within seconds — what the
/// hand-over at the close reads.
#[tokio::test(flavor = "multi_thread")]
async fn a_certificate_fed_after_boot_is_kept_within_seconds_and_acme_was_never_asked() {
    let dir = tempfile::tempdir().unwrap();
    let (l80, l443, a80, _a443) = listeners().await;
    let (directory, acme) = fake_acme(a80).await;
    let feed = Arc::new(Mutex::new(BTreeMap::new()));
    let f2 = Arc::clone(&feed);
    let web = start_on_probed(dir.path(), "example.test".into(), Arc::new(move || f2.lock().unwrap().clone()), l80, l443, here());
    tokio::time::sleep(Duration::from_millis(500)).await;
    assert!(!web.resolver.any());
    let (chain, key) = certs::tests::minted(&["example.test"], 80);
    *feed.lock().unwrap() = BTreeMap::from([
        (FEED_ACME_DIRECTORY.to_string(), directory),
        (FEED_WEB_HOSTS.to_string(), "example.test".to_string()),
        (certs::fed_cert_key("example.test"), chain.clone()),
        (certs::fed_key_key("example.test"), key),
    ]);
    let t0 = std::time::Instant::now();
    until("the fed certificate", || web.resolver.any().then_some(())).await;
    assert!(t0.elapsed() < LOOK_EVERY, "looked at when the feed changed, not a minute later");
    assert_eq!(certs::kept(dir.path(), "example.test", SystemTime::now()).unwrap().unwrap().chain_pem, chain, "kept for the hand-over");
    assert_eq!(*acme.issued.lock().unwrap(), 0, "ACME was never asked");
}

/// Lane E 3: `POST /callback/<vendor>` reaches monetize on loopback with the
/// body byte for byte and `Stripe-Signature` intact, monetize's status comes
/// back, and every other path is still the web face.
#[tokio::test(flavor = "multi_thread")]
async fn a_vendor_webhook_reaches_monetize_with_its_signature_and_its_answer_comes_back() {
    use tower::ServiceExt as _;
    // A stand-in for monetize's h1 callback: 401 unless the signature is the one sent.
    async fn monetize(UrlPath(vendor): UrlPath<String>, headers: axum::http::HeaderMap, body: axum::body::Bytes) -> Response {
        let sig = headers.get("stripe-signature").and_then(|v| v.to_str().ok()).unwrap_or("");
        let status = if sig == "t=1,v1=abc" { StatusCode::OK } else { StatusCode::UNAUTHORIZED };
        (status, format!("{vendor}|{}|{}", sig, String::from_utf8_lossy(&body))).into_response()
    }
    let l = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
    let to = l.local_addr().unwrap();
    tokio::spawn(async move { axum::serve(l, Router::new().route("/callback/{vendor}", post(monetize))).await.unwrap() });
    let face = Router::new().fallback(|| async { "the console" });
    let app = serve::with_callbacks(face, to);
    let body = r#"{"id":"evt_1","type":"checkout.session.completed"}"#;
    let req = |sig: &str, path: &str| {
        axum::http::Request::post(path).header("stripe-signature", sig).header(header::CONTENT_TYPE, "application/json").body(axum::body::Body::from(body)).unwrap()
    };
    let ok = app.clone().oneshot(req("t=1,v1=abc", "/callback/stripe")).await.unwrap();
    assert_eq!(ok.status(), StatusCode::OK);
    let text = String::from_utf8(axum::body::to_bytes(ok.into_body(), 1 << 20).await.unwrap().to_vec()).unwrap();
    assert_eq!(text, format!("stripe|t=1,v1=abc|{body}"), "the body and the signature arrive untouched");
    let forged = app.clone().oneshot(req("t=1,v1=forged", "/callback/stripe")).await.unwrap();
    assert_eq!(forged.status(), StatusCode::UNAUTHORIZED, "monetize's refusal is handed back");
    let bad = app.clone().oneshot(req("x", "/callback/..%2Fadmin")).await.unwrap();
    assert_eq!(bad.status(), StatusCode::NOT_FOUND);
    let other = app.clone().oneshot(axum::http::Request::get("/").body(axum::body::Body::empty()).unwrap()).await.unwrap();
    assert_eq!(String::from_utf8(axum::body::to_bytes(other.into_body(), 1 << 20).await.unwrap().to_vec()).unwrap(), "the console");
}

/// OM-7: a Stripe webhook is per product instance (`[vendors.stripe.<instance>]`,
/// `POST /callback/stripe/<instance>`): the front carries BOTH segments to
/// monetize — vendor and instance — with the signature intact, the
/// one-segment `/callback/<vendor>` still passes, and a third segment, an
/// empty one or a smuggled `..` is refused at the front, never forwarded.
#[tokio::test(flavor = "multi_thread")]
async fn a_per_instance_stripe_webhook_reaches_monetize_with_its_vendor_and_instance() {
    use tower::ServiceExt as _;
    // A stand-in for monetize's door: `/callback/{*vendor}`, the vendor id is `stripe/<instance>`.
    async fn monetize(UrlPath(vendor): UrlPath<String>, headers: axum::http::HeaderMap, body: axum::body::Bytes) -> Response {
        let sig = headers.get("stripe-signature").and_then(|v| v.to_str().ok()).unwrap_or("");
        (StatusCode::OK, format!("{vendor}|{}|{}", sig, String::from_utf8_lossy(&body))).into_response()
    }
    let l = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
    let to = l.local_addr().unwrap();
    tokio::spawn(async move { axum::serve(l, Router::new().route("/callback/{*vendor}", post(monetize))).await.unwrap() });
    let face = Router::new().fallback(|| async { (StatusCode::NOT_FOUND, "the console") });
    let app = serve::with_callbacks(face, to);
    let body = r#"{"id":"evt_1","type":"checkout.session.completed"}"#;
    let send = |path: &str| {
        let req = axum::http::Request::post(path).header("stripe-signature", "t=1,v1=abc").header(header::CONTENT_TYPE, "application/json").body(axum::body::Body::from(body)).unwrap();
        app.clone().oneshot(req)
    };
    let text = |r: Response| async move { String::from_utf8(axum::body::to_bytes(r.into_body(), 1 << 20).await.unwrap().to_vec()).unwrap() };
    for instance in ["gunnar", "holger-test2"] {
        let hook = send(&format!("/callback/stripe/{instance}")).await.unwrap();
        assert_eq!(hook.status(), StatusCode::OK, "/callback/stripe/{instance} reaches monetize, not a 404");
        assert_eq!(text(hook).await, format!("stripe/{instance}|t=1,v1=abc|{body}"), "the instance arrives with the vendor, the body and signature untouched");
    }
    let solo = send("/callback/stripe").await.unwrap();
    assert_eq!(solo.status(), StatusCode::OK, "the one-segment path still passes");
    assert_eq!(text(solo).await, format!("stripe|t=1,v1=abc|{body}"));
    for bad in ["/callback/stripe/gunnar/extra", "/callback/stripe/..%2Fadmin", "/callback/stripe/gun%2Fnar", "/callback/stripe/", "/callback//gunnar"] {
        let r = send(bad).await.unwrap();
        assert_eq!(r.status(), StatusCode::NOT_FOUND, "{bad} is refused");
        assert_ne!(text(r).await.split('|').count(), 3, "{bad} never reached monetize");
    }
}

/// **A callback-only host** (`web_callback_only`, the Stripe account's webhook
/// name): `/callback/<vendor>` passes, every other path is a 308 to the same
/// path on the first host; the other hosts are untouched; the first host and
/// a name that is not served are never narrowed.
#[tokio::test(flavor = "multi_thread")]
async fn a_callback_only_host_answers_callbacks_and_sends_the_rest_to_the_apex() {
    use tower::ServiceExt as _;
    let feed = BTreeMap::from([
        (FEED_WEB_HOSTS.to_string(), "example.test,www.example.test,pay.example.dk".to_string()),
        (FEED_WEB_CALLBACK_ONLY.to_string(), " Pay.Example.dk ,example.test,nothere.example".to_string()),
    ]);
    let cfg = WebConfig::from("example.test", &feed);
    assert_eq!(cfg.callback_only, vec!["pay.example.dk"], "served names only, never the first");
    let app = serve::App::new(Router::new().route("/callback/{vendor}", post(|| async { "callback" })).route("/callback/{vendor}/{instance}", post(|| async { "callback" })).fallback(|| async { "the console" }));
    app.set_callback_only(&cfg.callback_only, cfg.name());
    let router = serve::narrowed(app.current(), app.callback_only());
    let send = |host: &str, method: &str, path: &str| {
        let req = axum::http::Request::builder().method(method).uri(path).header(header::HOST, host).body(axum::body::Body::empty()).unwrap();
        router.clone().oneshot(req)
    };
    let hook = send("pay.example.dk:443", "POST", "/callback/stripe").await.unwrap();
    assert_eq!(hook.status(), StatusCode::OK);
    assert_eq!(axum::body::to_bytes(hook.into_body(), 1 << 20).await.unwrap(), "callback");
    let per_instance = send("pay.example.dk", "POST", "/callback/stripe/gunnar").await.unwrap();
    assert_eq!(axum::body::to_bytes(per_instance.into_body(), 1 << 20).await.unwrap(), "callback", "a per-instance webhook passes too (OM-7)");
    let page = send("pay.example.dk", "GET", "/login?next=%2F").await.unwrap();
    assert_eq!(page.status(), StatusCode::PERMANENT_REDIRECT);
    assert_eq!(page.headers()[header::LOCATION], "https://example.test/login?next=%2F");
    let apex = send("example.test", "GET", "/login").await.unwrap();
    assert_eq!(axum::body::to_bytes(apex.into_body(), 1 << 20).await.unwrap(), "the console", "the apex is untouched");
    let www = send("www.example.test", "GET", "/").await.unwrap();
    assert_eq!(www.status(), StatusCode::OK);
    // Nothing fed: nothing narrowed.
    app.set_callback_only(&[], "example.test");
    assert!(app.callback_only().is_none());
    let open = serve::narrowed(app.current(), app.callback_only());
    let req = axum::http::Request::get("/").header(header::HOST, "pay.example.dk").body(axum::body::Body::empty()).unwrap();
    assert_eq!(open.oneshot(req).await.unwrap().status(), StatusCode::OK);
}

/// **The repository route, through the real :443 router**: a request to the
/// routed name reaches the primary's HTTP (HTTP/1.1, the name it asked for as
/// `Host`, `x-forwarded-proto: https`), a gRPC call to the same name its gRPC
/// (HTTP/2 cleartext), and any other name is the face, untouched. (The test of
/// gunnar's `front-product-key` lane `repo_route.rs`, moved with the route.)
#[tokio::test(flavor = "multi_thread")]
async fn a_routed_name_reaches_the_primarys_http_and_a_grpc_call_its_grpc() {
    use tower::ServiceExt as _;
    async fn upstream(tag: &'static str) -> std::net::SocketAddrV4 {
        let l = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
        let SocketAddr::V4(at) = l.local_addr().unwrap() else { unreachable!() };
        let app = Router::new().fallback(move |req: axum::extract::Request| async move {
            let host = req.headers().get(header::HOST).and_then(|h| h.to_str().ok()).unwrap_or("-").to_string();
            let proto = req.headers().get("x-forwarded-proto").and_then(|h| h.to_str().ok()).unwrap_or("-").to_string();
            format!("{tag} {:?} host={host} proto={proto} path={}", req.version(), req.uri().path())
        });
        tokio::spawn(async move { axum::serve(l, app).await.unwrap() });
        at
    }
    let (h, g) = (upstream("http").await, upstream("grpc").await);
    let app = serve::App::new(Router::new().fallback(|| async { "console" }));
    assert!(app.set_routed(Some(serve::Routed { hosts: vec!["repo.example.test".into()], http: h, grpc: g, grpc_services: vec!["x.v1.Repo".into()] })));
    assert!(!app.set_routed(app.routed()), "the same route again is no change");
    let r = app.router_now();
    let ask = |host: &str, grpc: bool| {
        let mut b = axum::http::Request::post("/api/v1/crates/new").header("host", host);
        if grpc {
            b = b.header("content-type", "application/grpc").header("te", "trailers");
        }
        b.body(axum::body::Body::from("x")).unwrap()
    };
    let text = |r: Response| async move { String::from_utf8(axum::body::to_bytes(r.into_body(), 1 << 16).await.unwrap().to_vec()).unwrap() };
    let a = text(r.clone().oneshot(ask("repo.example.test", false)).await.unwrap()).await;
    assert_eq!(a, "http HTTP/1.1 host=repo.example.test proto=https path=/api/v1/crates/new");
    let mut call = ask("repo.example.test", true);
    *call.uri_mut() = "/x.v1.Repo/Get".parse().unwrap();
    let b = text(r.clone().oneshot(call).await.unwrap()).await;
    assert!(b.starts_with("grpc HTTP/2.0 ") && b.ends_with("path=/x.v1.Repo/Get"), "{b}");
    assert_eq!(text(r.oneshot(ask("example.test", false)).await.unwrap()).await, "console");
    // Nothing routed: the face alone.
    app.set_routed(None);
    assert_eq!(text(app.router_now().oneshot(ask("repo.example.test", false)).await.unwrap()).await, "console");
}

/// ★ **The front names the public face to the primary, and only the front does** (HOL-DOCFIND,
/// Holger 3.1.1): every routed request reaches the primary with `x-forwarded-proto: https` and
/// `x-forwarded-host` the name the client asked for, written by the front. What the client
/// sent in `x-forwarded-proto`, `x-forwarded-host`, `x-forwarded-port`, `x-forwarded-prefix`,
/// `x-forwarded-for` and `forwarded` is never passed through: the primary trusts these
/// headers from the front (Holger's `ron_trusted_proxies`), so a passed-through value would be
/// a client writing the links the primary hands everybody.
#[tokio::test(flavor = "multi_thread")]
async fn the_front_overwrites_the_forwarded_headers_and_never_passes_the_clients() {
    use tower::ServiceExt as _;
    let l = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
    let SocketAddr::V4(up) = l.local_addr().unwrap() else { unreachable!() };
    let echo = Router::new().fallback(|req: axum::extract::Request| async move {
        let h = |k: &str| {
            req.headers()
                .get_all(k)
                .iter()
                .map(|v| v.to_str().unwrap_or("?").to_string())
                .collect::<Vec<_>>()
                .join(",")
        };
        format!(
            "proto={} host={} xhost={} port={} prefix={} for={} forwarded={}",
            h("x-forwarded-proto"),
            h("host"),
            h("x-forwarded-host"),
            h("x-forwarded-port"),
            h("x-forwarded-prefix"),
            h("x-forwarded-for"),
            h("forwarded")
        )
    });
    tokio::spawn(async move { axum::serve(l, echo).await.unwrap() });
    let app = serve::App::new(Router::new().fallback(|| async { "console" }));
    app.set_routed(Some(serve::Routed { hosts: vec!["repo.example.test".into()], http: up, grpc: up, grpc_services: Vec::new() }));
    let req = axum::http::Request::get("/production/npmjs/is-number")
        .header("host", "repo.example.test")
        .header("x-forwarded-proto", "http")
        .header("x-forwarded-host", "evil.example")
        .header("x-forwarded-port", "80")
        .header("x-forwarded-prefix", "/evil")
        .header("x-forwarded-for", "203.0.113.66")
        .header("forwarded", "for=203.0.113.66;proto=http;host=evil.example")
        .body(axum::body::Body::empty())
        .unwrap();
    let got = app.router_now().oneshot(req).await.unwrap();
    let got = String::from_utf8(axum::body::to_bytes(got.into_body(), 1 << 16).await.unwrap().to_vec()).unwrap();
    // No connection address in a oneshot: the front names no client, and passes none through.
    assert_eq!(got, "proto=https host=repo.example.test xhost=repo.example.test port= prefix= for= forwarded=");
}

/// ★ **RA5-1 (the edda half): the front carries only the gRPC services the product declares.**
/// The repository route carried EVERY gRPC call on a routed name to the primary, so whatever the
/// primary serves on its gRPC door was reachable from the internet: a stranger's
/// `nornir.identity.v1.IdentityService/SignUp` on `repo.<customer>` reached a private box's
/// identity core. A call to a service the route does not carry is refused HERE (`UNIMPLEMENTED`,
/// audited) and never reaches the primary.
#[tokio::test(flavor = "multi_thread")]
async fn a_grpc_call_to_a_service_the_product_did_not_declare_is_refused_at_the_front() {
    use tower::ServiceExt as _;
    let l = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
    let SocketAddr::V4(up) = l.local_addr().unwrap() else { unreachable!() };
    tokio::spawn(async move { axum::serve(l, Router::new().fallback(|| async { "the primary" })).await.unwrap() });
    let app = serve::App::new(Router::new().fallback(|| async { "console" }));
    let carried = vec!["holger.".to_string(), "x.v1.Repo/Get".to_string()];
    app.set_routed(Some(serve::Routed { hosts: vec!["repo.example.test".into()], http: up, grpc: up, grpc_services: carried.clone() }));
    let r = app.router_now();
    let call = |path: &str| axum::http::Request::post(path).header("host", "repo.example.test").header("content-type", "application/grpc").header("te", "trailers").body(axum::body::Body::empty()).unwrap();
    let text = |r: Response| async move { String::from_utf8(axum::body::to_bytes(r.into_body(), 1 << 16).await.unwrap().to_vec()).unwrap() };
    let sign_up = r.clone().oneshot(call("/nornir.identity.v1.IdentityService/SignUp")).await.unwrap();
    assert_eq!(sign_up.headers().get("grpc-status").map(|v| v.to_str().unwrap().to_owned()).as_deref(), Some("12"), "refused at the front as UNIMPLEMENTED");
    assert_ne!(text(sign_up).await, "the primary", "an undeclared service never reaches the primary");
    for path in ["/holger.v1.GrowService/Status", "/holger.room.v1.RoomService/Rooms", "/x.v1.Repo/Get"] {
        let a = r.clone().oneshot(call(path)).await.unwrap();
        assert!(a.headers().get("grpc-status").is_none(), "{path} is carried");
        assert_eq!(text(a).await, "the primary", "{path}");
    }
    for path in ["/x.v1.Repo/Put", "/holgerx.v1.S/M", "/holger.v1.GrowService/Status/more", "/api/v1/crates/new", "/"] {
        assert!(!serve::carries(&carried, path), "{path} is not carried");
    }
    // Plain HTTP on the same name is the repository's, as before.
    let page = r.oneshot(axum::http::Request::get("/nornir.identity.v1.IdentityService/SignUp").header("host", "repo.example.test").body(axum::body::Body::empty()).unwrap()).await.unwrap();
    assert_eq!(text(page).await, "the primary");
    // A route that declares no service carries no gRPC call at all.
    app.set_routed(Some(serve::Routed { hosts: vec!["repo.example.test".into()], http: up, grpc: up, grpc_services: Vec::new() }));
    let none = app.router_now().oneshot(call("/holger.v1.GrowService/Status")).await.unwrap();
    assert_eq!(none.headers().get("grpc-status").map(|v| v.to_str().unwrap().to_owned()).as_deref(), Some("12"));
}

/// **RA1-16: a callback-only name is never carried to the primary**, even when
/// the route names it (a feed, or a caller of [`serve::App::set_routed`], that
/// got it wrong): on that name a gRPC call and a page are the narrowing's 308
/// to the first host, `/callback/<vendor>` is the face's, and the other routed
/// names still reach the primary.
#[tokio::test(flavor = "multi_thread")]
async fn a_callback_only_name_is_never_carried_to_the_primary_even_when_routed() {
    use tower::ServiceExt as _;
    let l = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
    let SocketAddr::V4(up) = l.local_addr().unwrap() else { unreachable!() };
    tokio::spawn(async move { axum::serve(l, Router::new().fallback(|| async { "the primary" })).await.unwrap() });
    let app = serve::App::new(Router::new().route("/callback/{vendor}", post(|| async { "callback" })).fallback(|| async { "console" }));
    app.set_callback_only(&["pay.example.test".to_string()], "example.test");
    app.set_routed(Some(serve::Routed { hosts: vec!["pay.example.test".into(), "repo.example.test".into()], http: up, grpc: up, grpc_services: vec!["holger.".into()] }));
    let r = app.router_now();
    let ask = |host: &str, path: &str, grpc: bool| {
        let mut b = axum::http::Request::post(path).header("host", host);
        if grpc {
            b = b.header("content-type", "application/grpc").header("te", "trailers");
        }
        b.body(axum::body::Body::empty()).unwrap()
    };
    let text = |r: Response| async move { String::from_utf8(axum::body::to_bytes(r.into_body(), 1 << 16).await.unwrap().to_vec()).unwrap() };
    let grpc = r.clone().oneshot(ask("pay.example.test", "/holger.v1.GrowService/Status", true)).await.unwrap();
    assert_eq!(grpc.status(), StatusCode::PERMANENT_REDIRECT, "a gRPC call on a callback-only name is not carried");
    assert_eq!(grpc.headers()[header::LOCATION], "https://example.test/holger.v1.GrowService/Status");
    let page = r.clone().oneshot(ask("pay.example.test", "/api/v1/crates/new", false)).await.unwrap();
    assert_eq!(page.status(), StatusCode::PERMANENT_REDIRECT);
    assert_eq!(text(r.clone().oneshot(ask("pay.example.test", "/callback/stripe", false)).await.unwrap()).await, "callback");
    assert_eq!(text(r.oneshot(ask("repo.example.test", "/api/v1/crates/new", false)).await.unwrap()).await, "the primary", "the other routed name still routes");
}

/// ★ **An early refusal is carried back while the body is still owed.** The
/// primary answers a PUT that DECLARES more than it will take (holger's
/// `[holger-metered-6]`, 507, before a byte of the body is read); the route must
/// carry that answer back rather than wait for a body the client will never
/// send (MEASURED on holger's mock 2026-09-28: a declared 10 TiB PUT through vm1
/// got no answer at all).
#[tokio::test(flavor = "multi_thread")]
async fn an_early_refusal_is_carried_back_before_the_body() {
    use tower::ServiceExt as _;
    let l = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
    let SocketAddr::V4(h) = l.local_addr().unwrap() else { unreachable!() };
    let upstream = Router::new().fallback(|req: axum::extract::Request| async move {
        let declared: u64 = req.headers().get(header::CONTENT_LENGTH).and_then(|v| v.to_str().ok()).and_then(|v| v.parse().ok()).unwrap_or(0);
        if declared > 1 << 20 {
            return (StatusCode::INSUFFICIENT_STORAGE, "[holger-metered-6] over the paid cap").into_response();
        }
        "taken".into_response()
    });
    tokio::spawn(async move { axum::serve(l, upstream).await.unwrap() });
    let app = serve::App::new(Router::new().fallback(|| async { "console" }));
    app.set_routed(Some(serve::Routed { hosts: vec!["repo.example.test".into()], http: h, grpc: h, grpc_services: vec!["holger.".into()] }));
    // A body that never comes.
    struct Never;
    impl hyper::body::Body for Never {
        type Data = axum::body::Bytes;
        type Error = std::io::Error;
        fn poll_frame(self: std::pin::Pin<&mut Self>, _: &mut std::task::Context<'_>) -> std::task::Poll<Option<Result<hyper::body::Frame<Self::Data>, Self::Error>>> {
            std::task::Poll::Pending
        }
    }
    let req = axum::http::Request::put("/bundles/over-cap.bin")
        .header("host", "repo.example.test")
        .header(header::CONTENT_LENGTH, (10u64 << 40).to_string())
        .body(axum::body::Body::new(Never))
        .unwrap();
    let answer = tokio::time::timeout(std::time::Duration::from_secs(10), app.router_now().oneshot(req)).await;
    let r = answer.expect("the refusal came back while the body was still owed").unwrap();
    assert_eq!(r.status(), StatusCode::INSUFFICIENT_STORAGE);
    // And over a real connection, the way a client meets vm1: the head alone.
    let front = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
    let at = front.local_addr().unwrap();
    let face = app.router_now();
    tokio::spawn(async move { axum::serve(front, face).await.unwrap() });
    use tokio::io::{AsyncReadExt as _, AsyncWriteExt as _};
    let mut c = tokio::net::TcpStream::connect(at).await.unwrap();
    c.write_all(format!("PUT /bundles/over-cap.bin HTTP/1.1\r\nhost: repo.example.test\r\ncontent-length: {}\r\n\r\n", 10u64 << 40).as_bytes()).await.unwrap();
    let mut buf = vec![0u8; 256];
    let n = tokio::time::timeout(std::time::Duration::from_secs(10), c.read(&mut buf)).await.expect("an answer on the head alone").unwrap();
    let head = String::from_utf8_lossy(&buf[..n]).to_string();
    assert!(head.starts_with("HTTP/1.1 507"), "{head}");
}

#[tokio::test(flavor = "multi_thread")]
async fn monetizes_own_host_carries_its_control_api_only_with_a_signature_and_every_other_host_is_unchanged() {
    use tower::ServiceExt as _;
    let feed = BTreeMap::from([
        (FEED_WEB_HOSTS.to_string(), "holger.rs,www.holger.rs,pay.example.dk,monetize.holger.rs".to_string()),
        (FEED_WEB_CALLBACK_ONLY.to_string(), "pay.example.dk,monetize.holger.rs".to_string()),
        (FEED_MONETIZE_CONTROL_HOST.to_string(), " Monetize.Holger.rs ".to_string()),
    ]);
    let cfg = WebConfig::from("holger.rs", &feed);
    assert_eq!(cfg.control_host.as_deref(), Some("monetize.holger.rs"));
    assert_eq!(cfg.callback_only, vec!["pay.example.dk"], "monetize's host is never narrowed to callbacks");
    assert_eq!(WebConfig::from("holger.rs", &BTreeMap::from([(FEED_MONETIZE_CONTROL_HOST.to_string(), "monetize.holger.rs".to_string())])).control_host, None, "a name the front does not serve is not taken");
    let first = BTreeMap::from([(FEED_WEB_HOSTS.to_string(), "holger.rs".to_string()), (FEED_MONETIZE_CONTROL_HOST.to_string(), "holger.rs".to_string())]);
    assert_eq!(WebConfig::from("holger.rs", &first).control_host, None, "the first host is the console's, never monetize's");

    // A stand-in for monetize on loopback: protocol, path, bearer, signature.
    let l = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
    let to = l.local_addr().unwrap();
    tokio::spawn(async move {
        axum::serve(
            l,
            Router::new().fallback(|req: axum::extract::Request| async move {
                let h = |n: &str| req.headers().get(n).and_then(|v| v.to_str().ok()).unwrap_or("-").to_string();
                format!("{:?}|{}|{}|{}", req.version(), req.uri().path(), h("authorization"), h("monetize-authorization"))
            }),
        )
        .await
        .unwrap()
    });
    let app = serve::App::new(Router::new().fallback(|| async { "console" }));
    app.set_callback_only(&cfg.callback_only, cfg.name());
    app.set_control_host(cfg.control_host.as_deref());
    let ask = |app: &serve::App, host: &str, path: &str, grpc: bool| {
        let mut b = axum::http::Request::post(path).header("host", host).header("authorization", "Bearer the-callers-own").header("monetize-authorization", "monetize-sshsig-v1 sig");
        if grpc {
            b = b.header("content-type", "application/grpc").header("te", "trailers");
        }
        app.router_now().oneshot(b.body(axum::body::Body::empty()).unwrap())
    };
    let text = |r: Response| async move { String::from_utf8(axum::body::to_bytes(r.into_body(), 1 << 16).await.unwrap().to_vec()).unwrap() };
    // No monetize target (an open front): nothing is carried.
    assert_eq!(text(ask(&app, "monetize.holger.rs", "/monetize.v1.Products/List", true).await.unwrap()).await, "console");
    app.set_callbacks(Some(to));
    assert_eq!(text(ask(&app, "monetize.holger.rs", "/monetize.v1.Products/List", true).await.unwrap()).await, "HTTP/2.0|/monetize.v1.Products/List|-|monetize-sshsig-v1 sig", "gRPC over h2c, the signature kept, NO bearer: only SSHSIG from outside");
    assert_eq!(text(ask(&app, "monetize.holger.rs:443", "/callback/stripe", false).await.unwrap()).await, "HTTP/1.1|/callback/stripe|-|monetize-sshsig-v1 sig", "a callback on monetize's host is monetize's");
    assert_eq!(text(ask(&app, "monetize.holger.rs", "/", false).await.unwrap()).await, "HTTP/1.1|/|-|monetize-sshsig-v1 sig", "nothing of the console on monetize's host");
    // Every other host: as before.
    assert_eq!(text(ask(&app, "holger.rs", "/monetize.v1.Products/List", true).await.unwrap()).await, "console", "the apex carries no control API");
    let hook = text(ask(&app, "holger.rs", "/callback/stripe", false).await.unwrap()).await;
    assert!(hook.ends_with("|/callback/stripe|Bearer the-callers-own|monetize-sshsig-v1 sig"), "/callback/<vendor> on the apex is unchanged: {hook}");
    let narrowed = ask(&app, "pay.example.dk", "/login", false).await.unwrap();
    assert_eq!(narrowed.status(), StatusCode::PERMANENT_REDIRECT, "a callback-only host still 308s");
    app.set_control_host(None);
    assert_eq!(text(ask(&app, "monetize.holger.rs", "/", false).await.unwrap()).await, "console", "nothing fed: nothing routed");
}

// ── DNS first (owner 2026-10-25: certificates must fix themselves) ─────────────────────

/// **An injected resolver**: every name resolves to `to`; this front's own public
/// address is `own`. No live DNS is ever asked from a test.
struct TestDns {
    own: Vec<std::net::IpAddr>,
    to: Mutex<Vec<std::net::IpAddr>>,
    resolved: Mutex<u32>,
}

impl dns::Dns for TestDns {
    fn resolve(&self, _host: &str) -> Result<Vec<std::net::IpAddr>, String> {
        *self.resolved.lock().unwrap() += 1;
        let to = self.to.lock().unwrap().clone();
        if to.is_empty() {
            Err("Name does not resolve".into())
        } else {
            Ok(to)
        }
    }
    fn own(&self) -> Vec<std::net::IpAddr> {
        self.own.clone()
    }
}

const OURS: &str = "198.51.100.7";
const THEIRS: &str = "203.0.113.9";

fn test_dns(to: &[&str]) -> Arc<TestDns> {
    Arc::new(TestDns { own: vec![OURS.parse().unwrap()], to: Mutex::new(to.iter().map(|s| s.parse().unwrap()).collect()), resolved: Mutex::new(0) })
}

/// A fast probe: a look every 200 ms (the DNS re-check stands for the minute), the feed
/// read every 50 ms.
fn probe_with(d: Arc<TestDns>) -> Probe {
    Probe { dns: d, clock: Arc::new(SystemTime::now), look_every: Duration::from_millis(200), tick: Duration::from_millis(50) }
}

/// Every name points here.
fn here() -> Probe {
    probe_with(test_dns(&[OURS]))
}

/// An address nothing listens on: an ACME validator dialling it fails, as Let's Encrypt
/// does for a name that points at another box.
async fn dead_addr() -> SocketAddr {
    let l = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
    l.local_addr().unwrap()
}

fn acme_feed(directory: &str) -> BTreeMap<String, String> {
    BTreeMap::from([(FEED_ACME_DIRECTORY.to_string(), directory.to_string()), (FEED_WEB_HOSTS.to_string(), "example.test".to_string())])
}

/// ★ **A name that does not point here is not an ACME failure.** test2's ONE box had no A
/// record: every look asked Let's Encrypt, failed its validation and doubled the persisted
/// backoff, toward six hours. With DNS checked first: no order, no backoff, the reason said.
#[cfg(feature = "acme")]
#[tokio::test(flavor = "multi_thread")]
async fn a_name_that_does_not_point_here_asks_no_acme_and_grows_no_backoff() {
    let dir = tempfile::tempdir().unwrap();
    let (l80, l443, _a80, _a443) = listeners().await;
    let (directory, acme) = fake_acme(dead_addr().await).await;
    let d = test_dns(&[THEIRS]);
    let feed = acme_feed(&directory);
    let web = start_on_probed(dir.path(), "x".into(), Arc::new(move || feed.clone()), l80, l443, probe_with(Arc::clone(&d)));
    tokio::time::sleep(Duration::from_millis(1500)).await;
    assert!(!web.resolver.any());
    assert_eq!(*acme.orders.lock().unwrap(), 0, "Let's Encrypt was asked for a name that points elsewhere");
    assert!(!dir.path().join("tls/example.test/acme-next").exists(), "the backoff grew: {:?}", acme::next_attempt(dir.path(), "example.test"));
    assert!(*d.resolved.lock().unwrap() >= 3, "the name is resolved again on every look, not once");
}

/// ★ **The moment the name points here, ACME is asked** — within one DNS check, not after a
/// backoff earned while the record was missing.
#[cfg(feature = "acme")]
#[tokio::test(flavor = "multi_thread")]
async fn the_moment_the_name_points_here_acme_is_asked_within_one_dns_check() {
    let dir = tempfile::tempdir().unwrap();
    let (l80, l443, a80, _a443) = listeners().await;
    let (directory, acme) = fake_acme(a80).await;
    let d = test_dns(&[]);
    let feed = acme_feed(&directory);
    let web = start_on_probed(dir.path(), "x".into(), Arc::new(move || feed.clone()), l80, l443, probe_with(Arc::clone(&d)));
    tokio::time::sleep(Duration::from_millis(1000)).await;
    assert_eq!(*acme.orders.lock().unwrap(), 0, "no A record yet: not asked");
    *d.to.lock().unwrap() = vec![OURS.parse().unwrap()];
    let t0 = std::time::Instant::now();
    until("the ACME certificate", || web.resolver.any().then_some(())).await;
    assert!(t0.elapsed() < Duration::from_millis(200 * 4 + 2000), "asked within a DNS check of the record appearing ({:?})", t0.elapsed());
    assert_eq!(*acme.issued.lock().unwrap(), 1);
}

/// ★ **A backoff an older front wrote is not held against a name that points here.** Before
/// this change every failure (a missing A record too) wrote `acme-next`, with no word of
/// what failed; a box updated while inside a six-hour wait must not sit it out.
#[cfg(feature = "acme")]
#[tokio::test(flavor = "multi_thread")]
async fn a_backoff_an_older_front_wrote_does_not_hold_a_name_that_now_points_here() {
    let dir = tempfile::tempdir().unwrap();
    let in_6h = SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs() + 6 * 3600;
    crate::write_secret(dir.path(), "tls/example.test/acme-next", &format!("{in_6h} 6\n")).unwrap();
    let (l80, l443, a80, _a443) = listeners().await;
    let (directory, acme) = fake_acme(a80).await;
    let feed = acme_feed(&directory);
    let web = start_on_probed(dir.path(), "x".into(), Arc::new(move || feed.clone()), l80, l443, here());
    until("the ACME certificate", || web.resolver.any().then_some(())).await;
    assert_eq!(*acme.issued.lock().unwrap(), 1);
}

/// **A real ACME failure still backs off**: the name points here, yet the validation fails
/// (tcp/80 is not reached). The failure is recorded as a validation failure, waits fifteen
/// minutes, and is not repeated inside them.
#[cfg(feature = "acme")]
#[tokio::test(flavor = "multi_thread")]
async fn a_real_acme_validation_failure_still_backs_off() {
    let dir = tempfile::tempdir().unwrap();
    let (l80, l443, _a80, _a443) = listeners().await;
    let (directory, acme) = fake_acme(dead_addr().await).await;
    let feed = acme_feed(&directory);
    let _web = start_on_probed(dir.path(), "x".into(), Arc::new(move || feed.clone()), l80, l443, here());
    until("the failed attempt", || dir.path().join("tls/example.test/acme-next").exists().then_some(())).await;
    tokio::time::sleep(Duration::from_millis(1000)).await;
    assert_eq!(*acme.orders.lock().unwrap(), 1, "asked once, then waits");
    let w = acme::wait(dir.path(), "example.test").expect("a wait is kept");
    assert_eq!((w.failures, w.kind), (1, Some(acme::FailKind::Validation)));
    let left = w.at.duration_since(SystemTime::now()).unwrap();
    assert!(left > Duration::from_secs(14 * 60) && left <= Duration::from_secs(15 * 60), "{left:?}");
}

// ── the status report, "try now", and the one-hour alert ───────────────────────────────

/// A clock the test moves: the real time plus `ahead`.
fn moved_clock(ahead: Arc<Mutex<Duration>>) -> Arc<dyn Fn() -> SystemTime + Send + Sync> {
    Arc::new(move || SystemTime::now() + *ahead.lock().unwrap())
}

fn status_of(root: &std::path::Path, host: &str) -> Option<status::HostStatus> {
    status::report(root, SystemTime::now()).into_iter().find(|s| s.host == host)
}

/// ★ **The report**: a name that does not point here says so, with no next attempt (DNS is
/// re-checked, nothing waits), "try now" allowed; once it points here and the certificate
/// is issued: valid until its notAfter, no reason left.
#[cfg(feature = "acme")]
#[tokio::test(flavor = "multi_thread")]
async fn the_status_says_valid_until_next_attempt_and_the_last_reason() {
    let dir = tempfile::tempdir().unwrap();
    let (l80, l443, a80, _a443) = listeners().await;
    let (directory, _acme) = fake_acme(a80).await;
    let d = test_dns(&[THEIRS]);
    let feed = acme_feed(&directory);
    let web = start_on_probed(dir.path(), "x".into(), Arc::new(move || feed.clone()), l80, l443, probe_with(Arc::clone(&d)));
    let s = until("a status for example.test", || status_of(dir.path(), "example.test").filter(|s| s.last_reason.is_some())).await;
    assert_eq!(s.last_kind.as_deref(), Some("dns"));
    assert_eq!(s.last_reason.as_deref(), Some("dns: example.test does not point at this box (it resolves to 203.0.113.9)"));
    assert_eq!((s.valid_until, s.next_attempt, s.failures, s.try_now), (None, None, 0, true));
    assert!(s.no_certificate_since.is_some() && s.alert.is_none());
    assert_eq!(s.hosts, vec!["example.test".to_string()]);
    *d.to.lock().unwrap() = vec![OURS.parse().unwrap()];
    until("the ACME certificate", || web.resolver.any().then_some(())).await;
    let s = until("the issued status", || status_of(dir.path(), "example.test").filter(|s| s.valid_until.is_some())).await;
    let kept = certs::kept(dir.path(), "example.test", SystemTime::now()).unwrap().unwrap();
    assert_eq!(s.valid_until, Some(kept.not_after.duration_since(std::time::UNIX_EPOCH).unwrap().as_secs()));
    assert_eq!((s.last_kind, s.last_reason, s.no_certificate_since, s.next_attempt), (None, None, None, None));
}

/// ★ **"Try now" skips the wait only when the last failure was not a validation failure**:
/// Let's Encrypt counts failed validations (five per name per hour); any other failure (the
/// order refused, the account, the network) may be tried again at once.
#[cfg(feature = "acme")]
#[tokio::test(flavor = "multi_thread")]
async fn try_now_skips_the_wait_only_after_a_failure_that_was_not_a_validation() {
    let now = SystemTime::now();
    let dir = tempfile::tempdir().unwrap();
    acme::failed_as(dir.path(), "v.test", now, acme::FailKind::Validation);
    let refused = status::try_now(dir.path(), "v.test", now).unwrap_err();
    assert!(refused.contains("validation"), "{refused}");
    assert!(acme::wait(dir.path(), "v.test").unwrap().at > now, "the wait is kept");
    assert!(!status_of(dir.path(), "v.test").unwrap().try_now);
    assert!(status::try_now(dir.path(), "v.test", now + Duration::from_secs(16 * 60)).is_ok(), "once the wait ran out there is nothing to skip");

    acme::failed_as(dir.path(), "a.test", now, acme::FailKind::Acme);
    assert!(status_of(dir.path(), "a.test").unwrap().try_now);
    status::try_now(dir.path(), "a.test", now).unwrap();
    let w = acme::wait(dir.path(), "a.test").unwrap();
    assert!(w.at <= now && w.failures == 1, "the wait is skipped, the count kept: {w:?}");

    // End to end: the order is refused (not a validation), so the front waits fifteen
    // minutes; "try now" has it ask again at once.
    let dir = tempfile::tempdir().unwrap();
    let (l80, l443, a80, _a443) = listeners().await;
    let (directory, acme) = fake_acme(a80).await;
    *acme.refuse_orders.lock().unwrap() = true;
    let feed = acme_feed(&directory);
    let _web = start_on_probed(dir.path(), "x".into(), Arc::new(move || feed.clone()), l80, l443, here());
    until("the refused order", || (*acme.orders.lock().unwrap() == 1 && acme::wait(dir.path(), "example.test").is_some()).then_some(())).await;
    tokio::time::sleep(Duration::from_millis(600)).await;
    assert_eq!(*acme.orders.lock().unwrap(), 1, "waits");
    assert_eq!(acme::wait(dir.path(), "example.test").unwrap().kind, Some(acme::FailKind::Acme));
    *acme.refuse_orders.lock().unwrap() = false;
    status::try_now(dir.path(), "example.test", SystemTime::now()).unwrap();
    until("the ACME certificate after try now", || (*acme.issued.lock().unwrap() == 1).then_some(())).await;
}

/// ★ **The one-hour alert**: no valid certificate for more than an hour is an `ALERT
/// tls.no-certificate` with its reason (the ONE box mails every `ALERT <kind>` line).
#[cfg(feature = "acme")]
#[tokio::test(flavor = "multi_thread")]
async fn a_host_without_a_valid_certificate_for_an_hour_is_an_alert_with_its_reason() {
    let t = SystemTime::now();
    assert_eq!(status::alert_line("a.test", t, t + Duration::from_secs(59 * 60), "dns: x"), None);
    assert_eq!(
        status::alert_line("a.test", t, t + Duration::from_secs(61 * 60), "dns: a.test does not point at this box (it resolves to nothing)").as_deref(),
        Some("ALERT tls.no-certificate: a.test has had no valid certificate for 1 h 1 min: dns: a.test does not point at this box (it resolves to nothing)")
    );
    let dir = tempfile::tempdir().unwrap();
    let (l80, l443, _a80, _a443) = listeners().await;
    let (directory, _acme) = fake_acme(dead_addr().await).await;
    let ahead = Arc::new(Mutex::new(Duration::ZERO));
    let probe = Probe { clock: moved_clock(Arc::clone(&ahead)), ..probe_with(test_dns(&[])) };
    let feed = acme_feed(&directory);
    let _web = start_on_probed(dir.path(), "x".into(), Arc::new(move || feed.clone()), l80, l443, probe);
    let s = until("the first status", || status_of(dir.path(), "example.test").filter(|s| s.no_certificate_since.is_some())).await;
    assert!(s.alert.is_none(), "not yet an hour");
    *ahead.lock().unwrap() = Duration::from_secs(61 * 60);
    let later = || status::report(dir.path(), SystemTime::now() + Duration::from_secs(61 * 60)).into_iter().find(|s| s.host == "example.test");
    let s = until("the alert", || later().filter(|s| s.alert.is_some())).await;
    until("the loop saying it", || status::Record::load(dir.path(), "example.test").alerted.map(|_| ())).await;
    let a = s.alert.unwrap();
    assert!(a.starts_with("ALERT tls.no-certificate: example.test has had no valid certificate for 1 h"), "{a}");
    assert!(a.ends_with("dns: example.test does not point at this box (it resolves to nothing: Name does not resolve)"), "{a}");
}

/// **The DNS check is part of ACME**: a front with a valid certificate, or one not fed yet,
/// resolves nothing (no new outbound where ACME is not asked).
#[tokio::test(flavor = "multi_thread")]
async fn a_front_that_does_not_ask_acme_resolves_nothing() {
    let dir = tempfile::tempdir().unwrap();
    let (l80, l443, a80, _a443) = listeners().await;
    let (directory, acme) = fake_acme(a80).await;
    let (chain, key) = certs::tests::minted(&["example.test"], 80);
    let mut feed = acme_feed(&directory);
    feed.insert(certs::fed_cert_key("example.test"), chain);
    feed.insert(certs::fed_key_key("example.test"), key);
    let d = test_dns(&[OURS]);
    let web = start_on_probed(dir.path(), "x".into(), Arc::new(move || feed.clone()), l80, l443, probe_with(Arc::clone(&d)));
    until("the fed certificate", || web.resolver.any().then_some(())).await;
    let (l80, l443, _, _) = listeners().await;
    let unfed = test_dns(&[OURS]);
    let _quiet = start_on_probed(tempfile::tempdir().unwrap().path(), "x".into(), Arc::new(BTreeMap::new), l80, l443, probe_with(Arc::clone(&unfed)));
    tokio::time::sleep(Duration::from_millis(800)).await;
    assert_eq!((*d.resolved.lock().unwrap(), *unfed.resolved.lock().unwrap(), *acme.asked.lock().unwrap()), (0, 0, 0));
    let s = status_of(dir.path(), "example.test").unwrap();
    assert!(s.valid_until.is_some() && s.last_reason.is_none() && s.alert.is_none(), "{s:?}");
}

/// ★ **The routed gRPC door is rate-limited per caller and audited** (holger lane B3, the
/// pattern of Gunnar's A6: the front forwards `application/grpc` to the main's control plane
/// with no logic of its own). One address may make [`serve::ROUTE_GRPC_BURST`] calls at once
/// and [`serve::ROUTE_GRPC_PER_SEC`] a second after that; the next is answered HERE as gRPC's
/// `RESOURCE_EXHAUSTED` with a `retry-after`, never carried. Another address is not held back.
/// Plain HTTP to the repository host is not counted (package clients fetch in bursts).
#[tokio::test(flavor = "multi_thread")]
async fn the_routed_grpc_door_is_rate_limited_per_caller() {
    use tower::ServiceExt as _;
    let l = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
    let SocketAddr::V4(g) = l.local_addr().unwrap() else { unreachable!() };
    tokio::spawn(async move { axum::serve(l, Router::new().fallback(|| async { "carried" })).await.unwrap() });
    let app = serve::App::new(Router::new().fallback(|| async { "console" }));
    app.set_routed(Some(serve::Routed { hosts: vec!["repo.example.test".into()], http: g, grpc: g, grpc_services: vec!["holger.".into()] }));
    let r = app.router_now();
    let ask = |ip: [u8; 4], grpc: bool| {
        let mut b = axum::http::Request::post("/holger.v1.AdminService/Health").header("host", "repo.example.test");
        if grpc {
            b = b.header("content-type", "application/grpc").header("te", "trailers");
        }
        let mut req = b.body(axum::body::Body::from("x")).unwrap();
        req.extensions_mut().insert(axum::extract::ConnectInfo(SocketAddr::from((ip, 40000))));
        req
    };
    let busy = [192, 0, 2, 77];
    // Twice the burst at once: the burst is carried, the rest is refused at the front
    // (the bucket refills at ROUTE_GRPC_PER_SEC, so a slow host may carry a few more).
    let mut calls = tokio::task::JoinSet::new();
    for _ in 0..2 * serve::ROUTE_GRPC_BURST {
        let (r, req) = (r.clone(), ask(busy, true));
        calls.spawn(async move { r.oneshot(req).await.unwrap() });
    }
    let (mut carried, mut refused) = (0u32, Vec::new());
    while let Some(a) = calls.join_next().await {
        let a = a.unwrap();
        match a.headers().get("grpc-status").and_then(|v| v.to_str().ok()) {
            None => carried += 1,
            Some(_) => refused.push(a),
        }
    }
    assert!(carried >= serve::ROUTE_GRPC_BURST, "the burst is carried: {carried}");
    assert!(!refused.is_empty(), "twice the burst at once is over the rate: {carried} carried");
    let over = refused.pop().unwrap();
    assert_eq!(over.status(), StatusCode::OK, "a gRPC refusal is an HTTP 200 with a grpc-status");
    assert_eq!(over.headers().get("grpc-status").and_then(|v| v.to_str().ok()), Some("8"), "RESOURCE_EXHAUSTED");
    assert!(over.headers().get("retry-after").is_some(), "it says when to come back");
    let calm = r.clone().oneshot(ask([192, 0, 2, 78], true)).await.unwrap();
    assert!(calm.headers().get("grpc-status").is_none(), "another caller is not held back");
    let http = r.clone().oneshot(ask(busy, false)).await.unwrap();
    assert_eq!(http.status(), StatusCode::OK, "plain HTTP to the repository is not counted");
    assert!(http.headers().get("grpc-status").is_none());
}

#[test]
fn a_callers_bucket_refills_at_its_rate_up_to_its_burst() {
    let mut b = serve::Bucket::full(3, 2);
    let t0 = std::time::Instant::now();
    assert!(b.take(t0) && b.take(t0) && b.take(t0));
    assert!(!b.take(t0), "the burst is spent");
    assert!(b.take(t0 + std::time::Duration::from_millis(600)), "one token back after half a second at 2/s");
    assert!(!b.take(t0 + std::time::Duration::from_millis(600)));
    let mut later = b.clone();
    for _ in 0..3 {
        assert!(later.take(t0 + std::time::Duration::from_secs(60)), "refilled to the burst, never past it");
    }
    assert!(!later.take(t0 + std::time::Duration::from_secs(60)));
    assert!(!later.idle(t0 + std::time::Duration::from_secs(60)) && later.idle(t0 + std::time::Duration::from_secs(62)), "a quiet caller's bucket is full again");
    assert!(serve::Bucket::full(1, 1).idle(t0), "a fresh bucket is idle");
}

/// ★ RA2-3: **a caller is an IPv6 /64 (an IPv4 address)**, so one /64 is not 2^64 callers.
#[test]
fn a_caller_is_one_ipv6_slash_64_or_one_ipv4_address() {
    let k = |s: &str| serve::caller_key(s.parse().unwrap());
    assert_eq!(k("2001:db8:1:2::1"), k("2001:db8:1:2:ffff:ffff:ffff:ffff"), "one /64, one caller");
    assert_ne!(k("2001:db8:1:2::1"), k("2001:db8:1:3::1"), "another /64, another caller");
    assert_ne!(k("192.0.2.1"), k("192.0.2.2"), "an IPv4 address is its own caller");
    assert_eq!(k("::ffff:192.0.2.1"), k("192.0.2.1"), "an IPv4-mapped address is the IPv4 caller");
}

/// ★ RA2-3: **a full map evicts its oldest callers, never every bucket.** A flooder whose
/// bucket is empty stays refused when fresh callers fill the map.
#[test]
fn a_full_map_drops_the_oldest_caller_and_keeps_the_flooders_bucket() {
    let t0 = std::time::Instant::now();
    let cap = 4;
    let mut all = serve::Callers::new(cap);
    let (burst, rate) = (serve::ROUTE_GRPC_BURST, serve::ROUTE_GRPC_PER_SEC);
    for (i, key) in ["a", "b", "c"].iter().enumerate() {
        // Busy callers: each spends its whole burst, so none is idle.
        for _ in 0..burst {
            all.take(key, t0 + std::time::Duration::from_millis(i as u64), burst, rate);
        }
    }
    let flood = t0 + std::time::Duration::from_millis(10);
    while all.take("flooder", flood, burst, rate) {}
    assert!(!all.take("flooder", flood, burst, rate), "the flooder is over its rate");
    for (i, fresh) in ["d", "e", "f"].iter().enumerate() {
        assert!(all.take(fresh, flood + std::time::Duration::from_micros(i as u64 + 1), burst, rate), "a fresh caller is admitted");
    }
    assert!(all.len() <= cap, "the map is bounded: {}", all.len());
    assert!(!all.take("flooder", flood + std::time::Duration::from_micros(5), burst, rate), "a full map did not reset the flooder's bucket");
    assert!(!all.contains("a"), "the oldest caller is the one dropped");
}

/// ★ RA4-23: **a /48 that rotates its /64s shares one site bucket.** Each fresh /64 still has
/// its own full burst, but every call also spends its /48's, so 65,536 /64s are not 65,536
/// bursts. Another /48 and an IPv4 caller are not held back.
#[test]
fn a_slash_48_rotating_its_slash_64s_shares_one_site_bucket() {
    let t0 = std::time::Instant::now();
    let mut lim = serve::Limiter::new(serve::ROUTE_GRPC_CALLERS);
    let ip = |s: String| s.parse::<std::net::IpAddr>().unwrap();
    let admitted = (0..4000u32).filter(|i| lim.admit(ip(format!("2001:db8:1:{:x}::1", i)), t0)).count();
    assert_eq!(admitted, serve::ROUTE_GRPC_SITE_BURST as usize, "one /48's fresh /64s share the site's burst");
    assert!(lim.admit(ip("2001:db8:2::1".into()), t0), "another /48 is its own site");
    assert!(lim.admit(ip("192.0.2.7".into()), t0), "an IPv4 caller has no site tier");
    assert!(lim.admit(ip("2001:db8:1:7::1".into()), t0 + std::time::Duration::from_secs(1)), "the site refills at its rate");
    assert_eq!(serve::site_key(ip("2001:db8:1:2::1".into())), serve::site_key(ip("2001:db8:1:ffff::9".into())), "one /48, one site");
    assert_ne!(serve::site_key(ip("2001:db8:1::1".into())), serve::site_key(ip("2001:db8:2::1".into())));
    assert_eq!(serve::site_key(ip("::ffff:192.0.2.1".into())), None, "an IPv4-mapped caller is IPv4");
    assert!(lim.callers().len() <= serve::ROUTE_GRPC_CALLERS, "the map is bounded");
}

/// ★ RA4-23: **a full map drops its least recently seen caller without a scan.** 200,000 fresh
/// callers through a full 4096-bucket map (each one evicts): an O(4096) scan per eviction is
/// over a billion steps under the mutex; the LRU is a log step each.
#[test]
fn a_full_map_evicts_in_log_time_not_by_a_scan() {
    let t0 = std::time::Instant::now();
    let mut all = serve::Callers::new(serve::ROUTE_GRPC_CALLERS);
    let (burst, rate) = (serve::ROUTE_GRPC_BURST, serve::ROUTE_GRPC_PER_SEC);
    for i in 0..serve::ROUTE_GRPC_CALLERS {
        // Busy: none is idle, so nothing is forgotten for free.
        for _ in 0..2 {
            all.take(&format!("busy-{i}"), t0, burst, rate);
        }
    }
    let begun = std::time::Instant::now();
    for i in 0..200_000u32 {
        assert!(all.take(&format!("fresh-{i}"), t0, burst, rate));
    }
    assert!(begun.elapsed() < std::time::Duration::from_secs(5), "200,000 evictions took {:?}", begun.elapsed());
    assert_eq!(all.len(), serve::ROUTE_GRPC_CALLERS);
    assert!(all.contains("fresh-199999") && !all.contains("fresh-0") && !all.contains("busy-0"), "the least recently seen go first");
    // Seen again, a caller is recent: the next eviction takes the one after it.
    let mut lru = serve::Callers::new(3);
    for k in ["a", "b", "c"] {
        lru.take(k, t0, burst, rate);
    }
    lru.take("a", t0, burst, rate);
    lru.take("d", t0, burst, rate);
    assert!(lru.contains("a") && !lru.contains("b") && lru.contains("c") && lru.contains("d"), "{lru:?}");
}

/// ★ RA2-3: **plain HTTP to a routed host's credential doors is counted too** (holger's
/// `/-/settings/` and `/-/auth/` doors travel as plain HTTP on the repository host), and
/// answered 429 with a retry-after past the rate; package paths are not counted.
#[tokio::test(flavor = "multi_thread")]
async fn plain_http_to_the_credential_doors_is_rate_limited_per_caller() {
    use tower::ServiceExt as _;
    assert!(serve::counted_http("/-/settings/console.public-site") && serve::counted_http("/-/auth/passkey/login/start"));
    assert!(!serve::counted_http("/crates/serde/1.0.0/download") && !serve::counted_http("/-/twin"));
    let l = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
    let SocketAddr::V4(h) = l.local_addr().unwrap() else { unreachable!() };
    tokio::spawn(async move { axum::serve(l, Router::new().fallback(|| async { "carried" })).await.unwrap() });
    let app = serve::App::new(Router::new().fallback(|| async { "console" }));
    app.set_routed(Some(serve::Routed { hosts: vec!["repo.example.test".into()], http: h, grpc: h, grpc_services: vec!["holger.".into()] }));
    let r = app.router_now();
    let ask = |path: &str| {
        let mut req = axum::http::Request::get(path).header("host", "repo.example.test").body(axum::body::Body::empty()).unwrap();
        req.extensions_mut().insert(axum::extract::ConnectInfo(SocketAddr::from(([198, 51, 100, 9], 40000))));
        req
    };
    let mut refused = None;
    for _ in 0..2 * serve::ROUTE_GRPC_BURST {
        let a = r.clone().oneshot(ask("/-/settings/console.public-site")).await.unwrap();
        if a.status() == StatusCode::TOO_MANY_REQUESTS {
            refused = Some(a);
            break;
        }
    }
    let over = refused.expect("twice the burst to a credential door is over the rate");
    assert!(over.headers().get("retry-after").is_some(), "it says when to come back");
    let pkg = r.clone().oneshot(ask("/crates/serde/1.0.0/download")).await.unwrap();
    assert_eq!(pkg.status(), StatusCode::OK, "a package path is not counted");
}

// ── A32-11 (step 2): what a product's front needs from the shared front ─────────────────

/// ★ **AIRGAP B1: with the switch off, ACME is never asked** (moved from gunnar-front's fork).
/// The feed names a live (fake) ACME directory and nothing valid is fed or kept; the front serves
/// nothing, says ACME is switched off (`status::KIND_OFF`), and the directory saw no request.
#[tokio::test(flavor = "multi_thread")]
async fn with_the_acme_switch_off_no_acme_directory_is_asked() {
    assert!(Hooks::default().acme_on == cfg!(feature = "acme"), "on wherever the client is built, unless the product says otherwise");
    let dir = tempfile::tempdir().unwrap();
    let (l80, l443, a80, _a443) = listeners().await;
    let (directory, acme) = fake_acme(a80).await;
    let feed = BTreeMap::from([(FEED_ACME_DIRECTORY.to_string(), directory), (FEED_WEB_HOSTS.to_string(), "front.test".to_string())]);
    let web = start_on_hooked(dir.path(), "x".into(), Arc::new(move || feed.clone()), l80, l443, Hooks { acme_on: false, ..Hooks::default() }, here());
    tokio::time::sleep(Duration::from_millis(1500)).await;
    assert!(!web.resolver.any(), "nothing fed or kept: nothing to serve");
    assert_eq!(*acme.asked.lock().unwrap(), 0, "the ACME directory was asked with the switch off");
    let rec = status::Record::load(dir.path(), "front.test");
    assert_eq!(rec.kind.as_deref(), Some(status::KIND_OFF), "{rec:?}");
}

/// Stinky row 25 (Gunnar 2026-10-10, now the shared front's): the certificate loop's said lines
/// are a bounded memory; a line with a changing number is a new line each look.
#[test]
fn the_lines_said_once_are_a_bounded_memory() {
    let mut said = SaidOnce::default();
    assert!(said.first_time("tls: serving front.test, 80 more days"));
    assert!(!said.first_time("tls: serving front.test, 80 more days"), "said once");
    for day in 0..10_000 {
        said.first_time(&format!("tls: serving front.test, {day} more days"));
    }
    assert!(said.len() <= SAID_CAP, "{} lines remembered, the bound is {SAID_CAP}", said.len());
    assert!(!said.first_time("tls: serving front.test, 9999 more days"), "the latest line is still remembered");
}

/// ★ **A product's outer layer wraps every answer on :443** ([`Hooks::outer`], installed through the crate-only `App::set_outer`): the
/// face, the callback narrowing and the repository route inside it, the product's own layers
/// (Gunnar: the CLI hole, its login names, git's paths, its access log) outside.
#[tokio::test]
async fn a_products_outer_layer_wraps_every_answer() {
    use tower::ServiceExt as _;
    let app = serve::App::new(Router::new().route("/", get(|| async { "face" })));
    app.set_outer(Some(Arc::new(|r: Router| r.layer(axum::middleware::map_response(|mut res: Response| async move {
        res.headers_mut().insert("x-outer", "1".parse().unwrap());
        res
    })))));
    for path in ["/", "/nothing-here"] {
        let res = app.router_now().oneshot(axum::http::Request::builder().uri(path).body(axum::body::Body::empty()).unwrap()).await.unwrap();
        assert_eq!(res.headers().get("x-outer").map(|v| v.to_str().unwrap()), Some("1"), "{path}");
    }
    app.set_outer(None);
    let res = app.router_now().oneshot(axum::http::Request::builder().uri("/").body(axum::body::Body::empty()).unwrap()).await.unwrap();
    assert!(res.headers().get("x-outer").is_none());
}

/// ★ **Every look tells the product what it read** ([`Hooks::on_look`]): Gunnar sets its login
/// names and its hole's names from the same config the certificate loop runs with.
#[tokio::test(flavor = "multi_thread")]
async fn every_look_hands_the_product_its_config() {
    let dir = tempfile::tempdir().unwrap();
    let (l80, l443, _a80, _a443) = listeners().await;
    let seen = Arc::new(Mutex::new(Vec::<Vec<String>>::new()));
    let s2 = Arc::clone(&seen);
    let feed = BTreeMap::from([(FEED_WEB_HOSTS.to_string(), "a.test,b.test".to_string())]);
    let hooks = Hooks { acme_on: false, on_look: Some(Arc::new(move |cfg: &WebConfig| s2.lock().unwrap().push(cfg.hosts.clone()))), ..Hooks::default() };
    let _web = start_on_hooked(dir.path(), "x".into(), Arc::new(move || feed.clone()), l80, l443, hooks, here());
    tokio::time::sleep(Duration::from_millis(800)).await;
    assert_eq!(seen.lock().unwrap().first(), Some(&vec!["a.test".to_string(), "b.test".to_string()]));
}

/// ★ **The product chooses its TLS provider** ([`Hooks::tls`]; Gunnar: X25519MLKEM768 first):
/// the :443 config is built with it.
#[test]
fn the_tls_config_takes_the_products_provider() {
    let dir = tempfile::tempdir().unwrap();
    let resolver = Arc::new(certs::Resolver::default());
    let _ = dir;
    let provider = Arc::new(rustls::crypto::ring::default_provider());
    let cfg = serve::tls_config_with(resolver, Some(Arc::clone(&provider))).expect("a config");
    assert_eq!(cfg.alpn_protocols, vec![b"h2".to_vec(), b"http/1.1".to_vec()]);
    assert_eq!(cfg.crypto_provider().kx_groups.len(), provider.kx_groups.len());
}

/// ★ RA4-22: **a product installs its outer layer one way, through its [`Hooks`]**: a web half
/// made with hooks serves through the outer layer from the first router on; one made without
/// has none; and `App::set_outer` is not public API (two ways let Gunnar set it twice).
#[tokio::test]
async fn a_products_outer_layer_is_installed_one_way_through_its_hooks() {
    use tower::ServiceExt as _;
    let outer: serve::Outer = Arc::new(|face: Router| Router::new().route("/outer", get(|| async { "the product's outer layer" })).fallback_service(face));
    let hooks = Hooks { outer: Some(outer), ..Hooks::default() };
    let ask = |p: &str| axum::http::Request::get(p).body(axum::body::Body::empty()).unwrap();
    let web = Web::with_hooks(tempfile::tempdir().unwrap().path(), &hooks);
    let r = web.app.router_now().oneshot(ask("/outer")).await.unwrap();
    assert_eq!(r.status(), StatusCode::OK, "the hooks' outer layer is on before anything is served");
    let r = web.app.router_now().oneshot(ask("/health")).await.unwrap();
    assert_eq!(r.status(), StatusCode::OK, "the face is inside it");
    let plain = Web::new(tempfile::tempdir().unwrap().path());
    let r = plain.app.router_now().oneshot(ask("/outer")).await.unwrap();
    assert_ne!(r.status(), StatusCode::OK, "no hooks, no outer layer");
    let serve_rs = include_str!("serve.rs");
    assert!(!serve_rs.contains(&["pub fn ", "set_outer"].concat()), "App::set_outer is crate-only: Hooks::outer is the one way");
}

// ─── RA5-22: the :443 door's bounds ─────────────────────────────────────────

/// A :443 door on loopback with `limits`, serving `/health` with a certificate for `door.test`;
/// its address and the certificate to trust.
async fn bounded_door(limits: serve::ConnLimits) -> (SocketAddr, String) {
    let (chain, key) = certs::tests::minted(&["door.test"], 30);
    let cert = certs::Cert::parse("door.test", &chain, &key, SystemTime::now()).unwrap();
    let resolver = Arc::new(certs::Resolver::default());
    resolver.set("door.test", Arc::clone(&cert.key));
    let acceptor = tokio_rustls::TlsAcceptor::from(Arc::new(serve::tls_config(resolver).unwrap()));
    let app = serve::App::new(Router::new().route("/health", get(|| async { "ok\n" })));
    let l = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
    let addr = l.local_addr().unwrap();
    // A32-14 test-only: the door under test, for the test's life.
    tokio::spawn(serve::serve_https_limited(l, acceptor, app, limits));
    (addr, chain)
}

/// A TLS client to `addr` from the local address `from` (another loopback address is another peer).
async fn tls_from(addr: SocketAddr, from: std::net::IpAddr, ca_pem: &str) -> tokio_rustls::client::TlsStream<tokio::net::TcpStream> {
    use rustls::pki_types::pem::PemObject as _;
    let mut roots = rustls::RootCertStore::empty();
    for c in rustls::pki_types::CertificateDer::pem_slice_iter(ca_pem.as_bytes()) {
        roots.add(c.unwrap()).unwrap();
    }
    let cfg = rustls::ClientConfig::builder_with_provider(Arc::new(rustls::crypto::ring::default_provider())).with_safe_default_protocol_versions().unwrap().with_root_certificates(roots).with_no_client_auth();
    let tcp = raw_from(addr, from).await;
    let sni = rustls::pki_types::ServerName::try_from("door.test".to_string()).unwrap();
    tokio_rustls::TlsConnector::from(Arc::new(cfg)).connect(sni, tcp).await.expect("the TLS handshake")
}

async fn raw_from(addr: SocketAddr, from: std::net::IpAddr) -> tokio::net::TcpStream {
    let sock = tokio::net::TcpSocket::new_v4().unwrap();
    sock.bind(SocketAddr::new(from, 0)).unwrap();
    sock.connect(addr).await.unwrap()
}

/// Whether the door closed `io` within `within` (end of stream or a reset), reading what it sent.
async fn closed_within<S: tokio::io::AsyncRead + Unpin>(io: &mut S, within: Duration) -> bool {
    use tokio::io::AsyncReadExt as _;
    let mut buf = [0u8; 4096];
    let until = tokio::time::Instant::now() + within;
    loop {
        match tokio::time::timeout_at(until, io.read(&mut buf)).await {
            Err(_) => return false,
            Ok(Ok(0)) | Ok(Err(_)) => return true,
            Ok(Ok(_)) => continue,
        }
    }
}

const SLOW: Duration = Duration::from_secs(30);

/// ★ RA5-22: **one peer holds at most its share of :443.** Past the per-peer cap its next
/// connection is closed at once; another peer is served meanwhile.
#[tokio::test(flavor = "multi_thread")]
async fn one_peer_holds_at_most_its_share_of_the_door() {
    let limits = serve::ConnLimits { total: 1024, per_peer: 2, handshake: SLOW, header_read: SLOW, idle: SLOW };
    let (addr, ca) = bounded_door(limits).await;
    let one: std::net::IpAddr = "127.0.0.1".parse().unwrap();
    let mut held = Vec::new();
    for _ in 0..2 {
        held.push(raw_from(addr, one).await);
    }
    tokio::time::sleep(Duration::from_millis(200)).await;
    let mut third = raw_from(addr, one).await;
    assert!(closed_within(&mut third, Duration::from_secs(3)).await, "past its share a peer's connection is closed at once");
    let mut other = tls_from(addr, "127.0.0.2".parse().unwrap(), &ca).await;
    use tokio::io::AsyncWriteExt as _;
    other.write_all(b"GET /health HTTP/1.1\r\nHost: door.test\r\nConnection: close\r\n\r\n").await.unwrap();
    let mut page = String::new();
    let _ = tokio::io::AsyncReadExt::read_to_string(&mut other, &mut page).await;
    assert!(page.starts_with("HTTP/1.1 200") && page.ends_with("ok\n"), "another peer is served: {page}");
    drop(held);
    tokio::time::sleep(Duration::from_millis(300)).await;
    let mut again = tls_from(addr, one, &ca).await;
    again.write_all(b"GET /health HTTP/1.1\r\nHost: door.test\r\nConnection: close\r\n\r\n").await.unwrap();
    let mut page = String::new();
    let _ = tokio::io::AsyncReadExt::read_to_string(&mut again, &mut page).await;
    assert!(page.starts_with("HTTP/1.1 200"), "a peer that let go is served again: {page}");
}

/// ★ RA5-22: **a silent connection is closed**: after its handshake, no request and no byte
/// for the idle time.
#[tokio::test(flavor = "multi_thread")]
async fn a_silent_connection_is_closed() {
    let limits = serve::ConnLimits { total: 1024, per_peer: 64, handshake: SLOW, header_read: SLOW, idle: Duration::from_secs(1) };
    let (addr, ca) = bounded_door(limits).await;
    let mut tls = tls_from(addr, "127.0.0.1".parse().unwrap(), &ca).await;
    assert!(closed_within(&mut tls, Duration::from_secs(5)).await, "a connection that says nothing is closed");
}

/// ★ RA5-22: **a request's head has a deadline**: a client that trickles header lines (each
/// within the idle time) is closed when the head is not in after the header-read time.
#[tokio::test(flavor = "multi_thread")]
async fn a_trickled_request_head_is_cut_off() {
    use tokio::io::AsyncWriteExt as _;
    let limits = serve::ConnLimits { total: 1024, per_peer: 64, handshake: SLOW, header_read: Duration::from_secs(1), idle: Duration::from_secs(5) };
    let (addr, ca) = bounded_door(limits).await;
    let tls = tls_from(addr, "127.0.0.1".parse().unwrap(), &ca).await;
    let (mut rd, mut wr) = tokio::io::split(tls);
    // A32-14 test-only: the slow client, until the door hangs up.
    let trickle = tokio::spawn(async move {
        let _ = wr.write_all(b"GET /health HTTP/1.1\r\nHost: door.test\r\n").await;
        for n in 0..40 {
            tokio::time::sleep(Duration::from_millis(250)).await;
            if wr.write_all(format!("X-Slow-{n}: x\r\n").as_bytes()).await.is_err() {
                return;
            }
        }
    });
    assert!(closed_within(&mut rd, Duration::from_secs(6)).await, "the head did not arrive in time: the door hangs up");
    trickle.abort();
}

// ─── WWW-CANON: the bare `<site>` is a 301 to `www.<site>`, the one origin ──────

/// One request over TLS to `addr` with SNI and `Host` `name`, trusting `ca_pem`: the whole raw answer.
async fn https_ask(addr: SocketAddr, name: &str, ca_pem: &str, method: &str, path: &str) -> String {
    https_ask_as(addr, name, ca_pem, method, path, "application/json").await
}

/// [`https_ask`] with the request's `Content-Type`.
async fn https_ask_as(addr: SocketAddr, name: &str, ca_pem: &str, method: &str, path: &str, content_type: &str) -> String {
    use rustls::pki_types::pem::PemObject as _;
    use tokio::io::{AsyncReadExt as _, AsyncWriteExt as _};
    let mut roots = rustls::RootCertStore::empty();
    for c in rustls::pki_types::CertificateDer::pem_slice_iter(ca_pem.as_bytes()) {
        roots.add(c.unwrap()).unwrap();
    }
    let cfg = rustls::ClientConfig::builder_with_provider(Arc::new(rustls::crypto::ring::default_provider())).with_safe_default_protocol_versions().unwrap().with_root_certificates(roots).with_no_client_auth();
    let tcp = tokio::net::TcpStream::connect(addr).await.unwrap();
    let sni = rustls::pki_types::ServerName::try_from(name.to_string()).unwrap();
    let mut tls = tokio_rustls::TlsConnector::from(Arc::new(cfg)).connect(sni, tcp).await.expect("a valid TLS handshake for the name");
    tls.write_all(format!("{method} {path} HTTP/1.1\r\nHost: {name}\r\nContent-Length: 2\r\nContent-Type: {content_type}\r\nConnection: close\r\n\r\n{{}}").as_bytes()).await.unwrap();
    let mut out = String::new();
    let _ = tls.read_to_string(&mut out).await;
    out
}

/// One plain request to :80 at `addr` with `Host` `host`: the whole raw answer.
async fn http_ask(addr: SocketAddr, host: &str, path: &str) -> String {
    use tokio::io::{AsyncReadExt as _, AsyncWriteExt as _};
    let mut tcp = tokio::net::TcpStream::connect(addr).await.unwrap();
    tcp.write_all(format!("GET {path} HTTP/1.1\r\nHost: {host}\r\nConnection: close\r\n\r\n").as_bytes()).await.unwrap();
    let mut out = String::new();
    let _ = tcp.read_to_string(&mut out).await;
    out
}

/// The status line's code and the `Location` header of a raw answer.
fn status_and_location(raw: &str) -> (u16, Option<String>) {
    let code = raw.split_whitespace().nth(1).and_then(|c| c.parse().ok()).unwrap_or(0);
    let location = raw.lines().take_while(|l| !l.is_empty()).find_map(|l| l.split_once(':').filter(|(k, _)| k.eq_ignore_ascii_case("location")).map(|(_, v)| v.trim().to_string()));
    (code, location)
}

/// A started front for `example.test`, `www.example.test` and `git.example.test` (one fed
/// certificate for all three, ACME off), its face counting every passkey ceremony it starts;
/// the :80 and :443 addresses, the certificate to trust, the counter.
async fn www_front(dir: &std::path::Path) -> (SocketAddr, SocketAddr, String, Arc<Mutex<u32>>) {
    let (l80, l443, a80, a443) = listeners().await;
    let (chain, key) = certs::tests::minted(&["example.test", "www.example.test", "git.example.test"], 80);
    let feed = BTreeMap::from([
        (FEED_WEB_HOSTS.to_string(), "example.test,www.example.test,git.example.test".to_string()),
        (certs::fed_cert_key("example.test"), chain.clone()),
        (certs::fed_key_key("example.test"), key),
    ]);
    let hooks = Hooks { acme_on: false, ..Hooks::default() };
    let web = start_on_hooked(dir, "example.test".into(), Arc::new(move || feed.clone()), l80, l443, hooks, here());
    let ceremonies = Arc::new(Mutex::new(0u32));
    let started = |c: Arc<Mutex<u32>>| {
        move || async move {
            *c.lock().unwrap() += 1;
            "a passkey ceremony started"
        }
    };
    web.app.set(
        Router::new()
            .route("/auth/passkey/login/start", post(started(Arc::clone(&ceremonies))))
            .route("/auth/passkey/register/start", post(started(Arc::clone(&ceremonies))))
            .route("/-/auth/passkey/login/start", post(started(Arc::clone(&ceremonies))))
            .route("/callback/{vendor}", post(|| async { "a vendor's webhook" }))
            .fallback(|| async { "the console" }),
    );
    until("the fed certificate", || web.resolver.any().then_some(())).await;
    // The look that served the certificate set the names on the same pass; give it its tick.
    tokio::time::sleep(Duration::from_millis(300)).await;
    (a80, a443, chain, ceremonies)
}

/// ★ WWW-CANON (owner 2026-10-30: the sites are www.gunnar.rs and www.holger.rs): **every page
/// request to the bare `<site>` is a 301 to `https://www.<site>` with the same path and query**,
/// over the bare name's own certificate (the redirect is valid HTTPS), and the face serves
/// nothing there.
#[tokio::test(flavor = "multi_thread")]
async fn the_bare_site_is_a_301_to_www_with_its_path_and_query() {
    let dir = tempfile::tempdir().unwrap();
    let (_a80, a443, ca, _) = www_front(dir.path()).await;
    for path in ["/", "/nordisk/gunnar/issues?q=open%20bug&page=2", "/login?next=%2Fadmin", "/health", "/llms.txt"] {
        for method in ["GET", "HEAD"] {
            let raw = https_ask(a443, "example.test", &ca, method, path).await;
            assert_eq!(status_and_location(&raw), (301, Some(format!("https://www.example.test{path}"))), "{method} {path} on the bare site: {raw}");
            assert!(!raw.contains("the console"), "the face serves nothing on the bare site: {raw}");
        }
    }
}

/// ★ WWW-CANON: **www serves the site, and the other names are unchanged** — the face, no
/// redirect; a bare name whose `www.` is not served is not redirected either.
#[tokio::test(flavor = "multi_thread")]
async fn www_and_the_other_names_serve_the_site() {
    let dir = tempfile::tempdir().unwrap();
    let (_a80, a443, ca, _) = www_front(dir.path()).await;
    for name in ["www.example.test", "git.example.test"] {
        let raw = https_ask(a443, name, &ca, "GET", "/nordisk/gunnar/issues?q=x").await;
        assert_eq!(status_and_location(&raw), (200, None), "{name}: {raw}");
        assert!(raw.ends_with("the console"), "{name}: {raw}");
    }
}

/// ★ WWW-CANON: **a passkey ceremony is never started on the bare host** — the console's origin
/// is www: every passkey door there is a 308 to the same door on www (the method and body
/// kept, so a client that re-sends lands on the canonical origin) and the face's handler is
/// never reached on the bare host. The same doors on www start it.
#[tokio::test(flavor = "multi_thread")]
async fn a_passkey_ceremony_is_never_started_on_the_bare_host() {
    let dir = tempfile::tempdir().unwrap();
    let (_a80, a443, ca, ceremonies) = www_front(dir.path()).await;
    for path in ["/auth/passkey/login/start", "/auth/passkey/register/start", "/-/auth/passkey/login/start"] {
        let raw = https_ask(a443, "example.test", &ca, "POST", path).await;
        assert_eq!(status_and_location(&raw), (308, Some(format!("https://www.example.test{path}"))), "POST {path} on the bare site: {raw}");
    }
    assert_eq!(*ceremonies.lock().unwrap(), 0, "no ceremony was started on the bare host");
    let raw = https_ask(a443, "www.example.test", &ca, "POST", "/auth/passkey/login/start").await;
    assert_eq!(status_and_location(&raw).0, 200, "{raw}");
    assert_eq!(*ceremonies.lock().unwrap(), 1, "www starts it");
}

/// ★ WWW-CANON: **machines on the bare host are not redirected** — a gRPC call (a CLI dialing
/// `https://<site>`) and a payment vendor's webhook (`/callback/<vendor>`, a vendor that does
/// not follow redirects) reach what they reached before.
#[tokio::test(flavor = "multi_thread")]
async fn grpc_and_vendor_webhooks_on_the_bare_host_are_not_redirected() {
    let dir = tempfile::tempdir().unwrap();
    let (_a80, a443, ca, _) = www_front(dir.path()).await;
    let raw = https_ask_as(a443, "example.test", &ca, "POST", "/gunnar.v1.Repos/List", "application/grpc").await;
    assert_eq!(status_and_location(&raw), (200, None), "a gRPC call: {raw}");
    let raw = https_ask(a443, "example.test", &ca, "POST", "/callback/stripe").await;
    assert_eq!(status_and_location(&raw), (200, None), "a webhook: {raw}");
    assert!(raw.ends_with("a vendor's webhook"), "{raw}");
}

/// ★ WWW-CANON on :80: `http://<site>/…` goes straight to `https://www.<site>/…` (301, path
/// and query kept); www's :80 redirect is unchanged (308 to its own https).
#[tokio::test(flavor = "multi_thread")]
async fn port_80_sends_the_bare_site_straight_to_www() {
    let dir = tempfile::tempdir().unwrap();
    let (a80, _a443, _ca, _) = www_front(dir.path()).await;
    let raw = http_ask(a80, "example.test", "/a/b?c=d").await;
    assert_eq!(status_and_location(&raw), (301, Some("https://www.example.test/a/b?c=d".into())), "{raw}");
    let raw = http_ask(a80, "www.example.test", "/a/b?c=d").await;
    assert_eq!(status_and_location(&raw), (308, Some("https://www.example.test/a/b?c=d".into())), "{raw}");
}

/// ★ WWW-CANON: **the canonical name** a product builds its URLs and its console's origin
/// from: `www.<first>` when the feed serves it, else the first host.
#[test]
fn the_canonical_name_is_www_of_the_first_host_when_served() {
    let cfg = |hosts: &str| WebConfig::from("x", &BTreeMap::from([(FEED_WEB_HOSTS.to_string(), hosts.to_string())]));
    assert_eq!(cfg("gunnar.rs,www.gunnar.rs,git.gunnar.rs").name(), "gunnar.rs", "the certificate's name and the relying party stay the bare site");
    assert_eq!(cfg("gunnar.rs,www.gunnar.rs,git.gunnar.rs").canonical(), "www.gunnar.rs");
    assert_eq!(cfg("acme.gunnar.rs,www.acme.gunnar.rs,git.acme.example").canonical(), "www.acme.gunnar.rs");
    assert_eq!(cfg("holger.mock.invalid").canonical(), "holger.mock.invalid", "no www served: the first host");
    assert_eq!(cfg("www.example.com").canonical(), "www.example.com");
}

/// ★ WWW-CANON, pure: which names are sent to their `www.` — any case, a port, a trailing dot;
/// never `www.` itself, never a name whose `www.` is not served, never a name not served.
#[test]
fn which_names_are_sent_to_www() {
    let hosts: Vec<String> = ["example.test", "www.example.test", "git.example.test"].map(String::from).to_vec();
    assert_eq!(serve::www_of("example.test", &hosts).as_deref(), Some("www.example.test"));
    assert_eq!(serve::www_of("Example.Test:443", &hosts).as_deref(), Some("www.example.test"));
    assert_eq!(serve::www_of("example.test.", &hosts).as_deref(), Some("www.example.test"));
    assert_eq!(serve::www_of("www.example.test", &hosts), None, "www is the site");
    assert_eq!(serve::www_of("git.example.test", &hosts), None, "git.<site> stays as it is");
    assert_eq!(serve::www_of("other.test", &hosts), None, "not served");
    assert_eq!(serve::www_of("holger.mock.invalid", &["holger.mock.invalid".to_string()]), None, "no www served: served as itself");
    let mut grpc = axum::http::HeaderMap::new();
    grpc.insert(header::CONTENT_TYPE, "application/grpc+proto".parse().unwrap());
    assert!(serve::stays_on_bare(&grpc, "/gunnar.v1.Repos/List"));
    assert!(serve::stays_on_bare(&axum::http::HeaderMap::new(), "/callback/stripe/gunnar"));
    assert!(!serve::stays_on_bare(&axum::http::HeaderMap::new(), "/auth/passkey/login/start"));
}