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

nordisk/holger

server/lib/tests/mirror_mtls_e2e.rs

//! **Two holgers, real TLS, real loopback: an on-prem mirror authenticating to
//! a cloud upstream with a client certificate.**
//!
//! `mirror_declaration.rs` proves the CYCLE against an in-memory fake upstream.
//! It cannot prove any of this, because everything here happens below the
//! `MirrorUpstream` trait: a TLS handshake, a client certificate, a CN turning
//! into a principal, and a read door refusing one repository while serving
//! another on the same connection. So this file stands a REAL upstream holger up
//! on a real port and points a REAL mirror at it.
//!
//! # The shape being proven
//!
//! An on-prem holger mirroring from a cloud holger must be able to authenticate.
//! Before this lane it could not: the mirror read anonymously, which worked only
//! because `holger.rs` happens to serve reads open. A customer's mirror against
//! a real cloud does not get that.
//!
//! The upstream here is configured the way the owner ruled it could be — mTLS
//! required **per repository**:
//!
//! * `gated` declares `ron_require_auth: true`,
//! * `open` declares nothing and stays open,
//! * the endpoint sets `client_ca` (so a certificate is verified) AND
//!   `client_auth_optional` (so a certless client still reaches a repository
//!   NAME and can be refused by name rather than by a dropped handshake).
//!
//! # The four proofs
//!
//! 1. [`the_mirror_with_a_certificate_pulls_the_gated_repository`] — the bytes
//!    arrive and are byte-identical, digest compared.
//! 2. [`the_mirror_without_a_certificate_is_refused_on_the_gated_repository`] —
//!    refused, and the pair comes back as an `Err`, NOT as a clean cycle that
//!    silently moved nothing.
//! 3. [`an_open_repository_on_the_same_upstream_mirrors_either_way`] — the
//!    ungated repository is served to both mirrors.
//! 4. [`a_declared_credential_that_cannot_be_loaded_refuses_at_boot`] — the
//!    refusal is at `wire_holger`, before anything serves, not on the first
//!    cycle.

// A server's test, never a gateway build's (lane HOL-CLEANUP): a gateway build is a
// downstream, and several Holgers in one process, and a downstream's egress pin is process-wide.
#![cfg(not(feature = "gateway"))]

use std::io::Write;
use std::sync::Arc;
use traits::test_support::hold_port;

use holger_server_lib::{mirror_cycle, read_ron_config, wire_holger, Holger};
use traits::{ArtifactId, RepositoryBackendTrait};

// ───────────────────────────── a throwaway PKI ─────────────────────────────

/// One CA, and the certificates it signs. `rcgen` is this crate's existing
/// dev-dependency for exactly this (`integration_oci_tls.rs`), and it is the
/// same material `holger-mannequin`'s PKI door issues — the mannequin is the
/// standalone version of this for a human driving it by hand, and pulling that
/// whole daemon in as a dev-dependency to get two certificates would be the
/// wrong trade for a `cargo test`.
struct Pki {
    ca_pem: String,
    /// rcgen 0.14 (2026-10-02, W3): the CA's params + key sign the leaves.
    issuer: rcgen::Issuer<'static, rcgen::KeyPair>,
}

struct Issued {
    cert_pem: String,
    key_pem: String,
}

impl Pki {
    fn new() -> Self {
        let mut params = rcgen::CertificateParams::new(Vec::<String>::new()).unwrap();
        params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Unconstrained);
        params
            .distinguished_name
            .push(rcgen::DnType::CommonName, "holger-lane-mtls-ca");
        let ca_key = rcgen::KeyPair::generate().unwrap();
        let ca_cert = params.self_signed(&ca_key).unwrap();
        Self {
            ca_pem: ca_cert.pem(),
            issuer: rcgen::Issuer::new(params, ca_key),
        }
    }

    /// A SERVER certificate valid for `names` alone (loopback: `localhost`, `127.0.0.1`).
    fn server_for(&self, cn: &str, names: &[&str]) -> Issued {
        let mut params =
            rcgen::CertificateParams::new(names.iter().map(|n| n.to_string()).collect::<Vec<_>>())
                .unwrap();
        params
            .distinguished_name
            .push(rcgen::DnType::CommonName, cn);
        params.use_authority_key_identifier_extension = true;
        let key = rcgen::KeyPair::generate().unwrap();
        let cert = params.signed_by(&key, &self.issuer).unwrap();
        Issued {
            cert_pem: cert.pem(),
            key_pem: key.serialize_pem(),
        }
    }

    /// A CLIENT certificate. **`cn` is the principal**: holger turns the leaf's
    /// Common Name into the subject every role map keys on.
    fn client(&self, cn: &str) -> Issued {
        let mut params = rcgen::CertificateParams::new(Vec::<String>::new()).unwrap();
        params
            .distinguished_name
            .push(rcgen::DnType::CommonName, cn);
        params.use_authority_key_identifier_extension = true;
        params.extended_key_usages = vec![rcgen::ExtendedKeyUsagePurpose::ClientAuth];
        let key = rcgen::KeyPair::generate().unwrap();
        let cert = params.signed_by(&key, &self.issuer).unwrap();
        Issued {
            cert_pem: cert.pem(),
            key_pem: key.serialize_pem(),
        }
    }
}

fn write_to(dir: &std::path::Path, name: &str, body: &str) -> String {
    let p = dir.join(name);
    std::fs::write(&p, body).unwrap();
    std::fs::set_permissions(
        &p,
        <std::fs::Permissions as std::os::unix::fs::PermissionsExt>::from_mode(0o600),
    )
    .unwrap();
    p.to_string_lossy().into_owned()
}

fn sha256_hex(data: &[u8]) -> String {
    use sha2::{Digest, Sha256};
    let mut h = Sha256::new();
    h.update(data);
    hex::encode(h.finalize())
}

// ───────────────────────────── the upstream ─────────────────────────────

struct Upstream {
    _data: tempfile::TempDir,
    _certs: tempfile::TempDir,
    _cfg: tempfile::NamedTempFile,
    /// Kept ALIVE, not read: dropping the config drops the served backends.
    _holger: Holger,
    endpoint: String,
    ca_path: String,
    /// `(repo, coord) -> bytes`, so a proof can compare what arrived against
    /// what was published.
    published: Vec<(String, ArtifactId, Vec<u8>)>,
}

/// Stand up an upstream holger with TWO repositories — one gated behind
/// `ron_require_auth`, one open — on a real TLS gRPC port, and publish one
/// artifact into each.
///
/// `mirror-node` is granted `Reader`. The role map keys on the CLIENT
/// CERTIFICATE'S CN; nothing else in this config mentions it.
fn start_upstream(pki: &Pki) -> Upstream {
    start_upstream_for(pki, &["localhost", "127.0.0.1"])
}

/// [`start_upstream`] whose certificate names `names` alone.
fn start_upstream_for(pki: &Pki, names: &[&str]) -> Upstream {
    let data = tempfile::tempdir().unwrap();
    let certs = tempfile::tempdir().unwrap();

    let srv = pki.server_for("upstream.holger.test", names);
    let cert_path = write_to(certs.path(), "server.crt", &srv.cert_pem);
    let key_path = write_to(certs.path(), "server.key", &srv.key_pem);
    let ca_path = write_to(certs.path(), "ca.crt", &pki.ca_pem);

    let gated_dir = data.path().join("gated");
    let open_dir = data.path().join("open");
    std::fs::create_dir_all(&gated_dir).unwrap();
    std::fs::create_dir_all(&open_dir).unwrap();

    let grpc = hold_port();
    let config = format!(
        r#"(
    repositories: [
        (
            ron_name: "gated",
            ron_repo_type: "rust",
            ron_upstreams: [],
            ron_in: None,
            ron_out: Some((ron_storage_endpoint: "storage", ron_exposed_endpoint: "default")),
            ron_data_dir: Some("{gated}"),
            ron_require_auth: true,
        ),
        (
            ron_name: "open",
            ron_repo_type: "rust",
            ron_upstreams: [],
            ron_in: None,
            ron_out: Some((ron_storage_endpoint: "storage", ron_exposed_endpoint: "default")),
            ron_data_dir: Some("{open}"),
        ),
    ],
    // Several boxes share this test process, and the egress guard is per process: the box
    // that names no upstream says `network: Online` so its neighbours may dial (B2).
    network: Online,
    exposed_endpoints: [
        (
            ron_name: "default",
            ron_url: "127.0.0.1:{grpc}",
            ron_tls: Some((
                cert: "{cert}",
                key: "{key}",
                client_ca: Some("{ca}"),
                client_auth_optional: true,
            )),
        ),
    ],
    storage_endpoints: [
        (ron_name: "storage", ron_storage_type: "file", ron_path: "{root}"),
    ],
    access: (reads: Open, front: Open), auth: Some((
        methods: [ Mtls(ca_cert: "{ca}") ],
        roles: {{ "mirror-node": Reader }},
    )),
)"#,
        gated = gated_dir.display(),
        open = open_dir.display(),
        root = data.path().display(),
        grpc = grpc,
        cert = cert_path,
        key = key_path,
        ca = ca_path,
    );

    let mut cfg = tempfile::NamedTempFile::new().unwrap();
    cfg.write_all(config.as_bytes()).unwrap();

    let mut holger: Holger = read_ron_config(cfg.path()).unwrap();
    holger.instantiate_backends().unwrap();
    wire_holger(&mut holger).unwrap();

    // Publish through the BACKEND, before the door exists. The proofs are about
    // reads; putting bytes in over the wire would need a write credential and
    // would prove nothing extra.
    let mut published = Vec::new();
    for (repo, name, body) in [
        (
            "gated",
            "secret-crate",
            b"GATED BYTES -- only with a client certificate".to_vec(),
        ),
        (
            "open",
            "public-crate",
            b"OPEN BYTES -- anyone may take these".to_vec(),
        ),
    ] {
        let id = ArtifactId {
            namespace: None,
            name: name.to_string(),
            version: "1.0.0".to_string(),
        };
        let backend: Arc<dyn RepositoryBackendTrait> = holger.backend_named(repo).unwrap();
        backend.put(&id, &body).unwrap();
        published.push((repo.to_string(), id, body));
    }

    holger.start().unwrap();

    Upstream {
        _data: data,
        _certs: certs,
        _cfg: cfg,
        _holger: holger,
        endpoint: format!("https://localhost:{grpc}"),
        ca_path,
        published,
    }
}

// ───────────────────────────── the mirror ─────────────────────────────

struct Mirror {
    _data: tempfile::TempDir,
    _certs: tempfile::TempDir,
    _cfg: tempfile::NamedTempFile,
    holger: Holger,
    data_root: std::path::PathBuf,
}

/// A downstream holger whose `mirror:` block points at `upstream`, with or
/// without a client certificate.
///
/// `identity: None` is the mirror a customer has TODAY — `ron_credential:
/// Anonymous`, the only thing that ever ran.
fn build_mirror(up: &Upstream, identity: Option<&Issued>) -> Mirror {
    build_mirror_at(up, &up.endpoint, identity, None)
}

/// [`build_mirror`] dialling `endpoint`, its certless `Tls` credential naming `server_name`.
fn build_mirror_at(
    up: &Upstream,
    endpoint: &str,
    identity: Option<&Issued>,
    server_name: Option<&str>,
) -> Mirror {
    let data = tempfile::tempdir().unwrap();
    let certs = tempfile::tempdir().unwrap();

    let credential = match identity {
        // NOT `Anonymous`. The upstream is an HTTPS server, and a mirror that
        // only wants its OPEN repositories still has to speak TLS to ask for
        // them — `Tls` is "verify the server, present nothing", which is exactly
        // the certless customer this proof is about. (`Anonymous` against an
        // `https://` endpoint is refused at boot, which is its own proof below.)
        None => match server_name {
            None => format!("Tls( ron_ca_cert: Some(\"{}\") )", up.ca_path),
            Some(n) => format!(
                "Tls( ron_ca_cert: Some(\"{}\"), ron_server_name: Some(\"{n}\") )",
                up.ca_path
            ),
        },
        Some(id) => {
            let c = write_to(certs.path(), "mirror.crt", &id.cert_pem);
            let k = write_to(certs.path(), "mirror.key", &id.key_pem);
            format!(
                "Mtls( ron_client_cert: \"{c}\", ron_client_key: \"{k}\", \
                 ron_ca_cert: Some(\"{ca}\") )",
                ca = up.ca_path
            )
        }
    };

    let gated_dir = data.path().join("gated");
    let open_dir = data.path().join("open");
    std::fs::create_dir_all(&gated_dir).unwrap();
    std::fs::create_dir_all(&open_dir).unwrap();

    let config = format!(
        r#"(
    repositories: [
        (
            ron_name: "gated",
            ron_repo_type: "rust",
            ron_upstreams: [],
            ron_in: None,
            ron_out: Some((ron_storage_endpoint: "storage", ron_exposed_endpoint: "default")),
            ron_data_dir: Some("{gated}"),
        ),
        (
            ron_name: "open",
            ron_repo_type: "rust",
            ron_upstreams: [],
            ron_in: None,
            ron_out: Some((ron_storage_endpoint: "storage", ron_exposed_endpoint: "default")),
            ron_data_dir: Some("{open}"),
        ),
    ],
    // Several boxes share this test process, and the egress guard is per process: the box
    // that names no upstream says `network: Online` so its neighbours may dial (B2).
    network: Online,
    exposed_endpoints: [
        (ron_name: "default", ron_url: "127.0.0.1:{grpc}"),
    ],
    storage_endpoints: [
        (ron_name: "storage", ron_storage_type: "file", ron_path: "{root}"),
    ],
    mirror: Some((
        ron_state_dir: "holger-mirror",
        ron_sources: [
            (
                ron_name: "cloud",
                ron_kind: Holger,
                ron_endpoint: "{endpoint}",
                ron_credential: {credential},
                ron_every_secs: 300,
                ron_repositories: [
                    ( ron_from: "gated", ron_to: "gated" ),
                    ( ron_from: "open",  ron_to: "open"  ),
                ],
            ),
        ],
    )),
)"#,
        gated = gated_dir.display(),
        open = open_dir.display(),
        root = data.path().display(),
        grpc = hold_port(),
        endpoint = endpoint,
        credential = credential,
    );

    let mut cfg = tempfile::NamedTempFile::new().unwrap();
    cfg.write_all(config.as_bytes()).unwrap();

    let mut holger: Holger = read_ron_config(cfg.path()).unwrap();
    holger.instantiate_backends().unwrap();
    // THE BOOT. A declared credential that will not load stops here.
    wire_holger(&mut holger).unwrap();

    let data_root = data.path().to_path_buf();
    Mirror {
        _data: data,
        _certs: certs,
        _cfg: cfg,
        holger,
        data_root,
    }
}

/// One cycle over every declared pair, as `holger-server mirror --once` runs it.
async fn cycle(m: &Mirror) -> Vec<anyhow::Result<mirror_cycle::PairReport>> {
    mirror_cycle::run_once(&m.holger, Some(&m.data_root), None, None)
        .await
        .expect("the mirror block is declared, so a cycle has pairs to run")
}

/// Wait for the upstream's gRPC door to answer. `start()` spawns; nothing
/// promises the listener is up when it returns.
async fn wait_ready(up: &Upstream) {
    for _ in 0..200 {
        if tokio::net::TcpStream::connect(up.endpoint.trim_start_matches("https://"))
            .await
            .is_ok()
        {
            return;
        }
        tokio::time::sleep(std::time::Duration::from_millis(25)).await;
    }
    panic!("upstream never started listening on {}", up.endpoint);
}

fn report_for<'a>(
    out: &'a [anyhow::Result<mirror_cycle::PairReport>],
    from: &str,
) -> &'a anyhow::Result<mirror_cycle::PairReport> {
    // The pairs come back in declaration order; find by name rather than index
    // so a reordering of the RON cannot make a proof lie.
    out.iter()
        .find(|r| match r {
            Ok(rep) => rep.from == from,
            Err(e) => format!("{e:#}").contains(from),
        })
        .unwrap_or_else(|| panic!("no slot for pair '{from}' in {} results", out.len()))
}

// ───────────────────────────── PROOF 1 + 3 (with cert) ─────────────────────

/// **Proof 1.** The mirror that presents `CN=mirror-node` pulls the GATED
/// repository, and the bytes are byte-identical.
///
/// **Proof 3, first half.** The same cycle also takes the OPEN repository.
#[tokio::test]
async fn the_mirror_with_a_certificate_pulls_the_gated_repository() {
    holger_server_lib::exposed::tls::ensure_crypto_provider();
    let pki = Pki::new();
    let up = start_upstream(&pki);
    wait_ready(&up).await;

    let id = pki.client("mirror-node");
    let mirror = build_mirror(&up, Some(&id));
    let out = cycle(&mirror).await;

    for repo in ["gated", "open"] {
        let rep = report_for(&out, repo)
            .as_ref()
            .unwrap_or_else(|e| panic!("pair '{repo}' must succeed with a certificate: {e:#}"));
        assert_eq!(
            rep.listed, 1,
            "{repo}: the upstream must list its one artifact"
        );
        assert_eq!(rep.fetched, 1, "{repo}: it must be fetched");
        assert_eq!(rep.failed, 0, "{repo}: nothing may fail");
    }

    // BYTE-IDENTICAL, both repositories, compared by digest.
    for (repo, id, body) in &up.published {
        let local = mirror.holger.backend_named(repo).unwrap();
        let got = local
            .fetch(id)
            .unwrap()
            .unwrap_or_else(|| panic!("'{repo}' did not hold the mirrored artifact"));
        assert_eq!(
            sha256_hex(&got),
            sha256_hex(body),
            "'{repo}': mirrored bytes must be byte-identical to the upstream's"
        );
        assert_eq!(&got[..], &body[..]);
    }
}

// ───────────────────────────── PROOF 2 + 3 (no cert) ───────────────────────

/// **Proof 2.** The mirror with NO certificate is refused on the gated
/// repository — and refused LOUDLY. The pair comes back as an `Err`, not as a
/// clean report that happens to have moved nothing.
///
/// This is the requirement that matters most. A mirror that silently skipped
/// the repository it could not authenticate for would report a green cycle and
/// hold nothing, and nobody would look again for months.
///
/// **Proof 3, second half.** The OPEN repository on the SAME upstream, over the
/// SAME certless connection, still mirrors.
#[tokio::test]
async fn the_mirror_without_a_certificate_is_refused_on_the_gated_repository() {
    holger_server_lib::exposed::tls::ensure_crypto_provider();
    let pki = Pki::new();
    let up = start_upstream(&pki);
    wait_ready(&up).await;

    let mirror = build_mirror(&up, None); // ron_credential: Anonymous
    let out = cycle(&mirror).await;

    // The gated pair FAILED, by name, and said why.
    let gated = report_for(&out, "gated");
    let err = match gated {
        Err(e) => format!("{e:#}"),
        Ok(rep) => panic!(
            "a certless mirror must be REFUSED on a gated repository, not handed a clean \
             cycle: {}",
            rep.line()
        ),
    };
    eprintln!("PROOF 2 refusal: {err}");
    assert!(
        err.contains("gated"),
        "the refusal must name the repository it is about: {err}"
    );
    assert!(
        err.contains("Unauthenticated") || err.contains("credentials"),
        "the refusal must be an AUTH refusal, not an incidental transport failure — \
         a proof that passed because the port was shut would prove nothing: {err}"
    );

    // …and it is not a silent skip: nothing landed locally.
    let local = mirror.holger.backend_named("gated").unwrap();
    let gated_id = &up.published.iter().find(|(r, ..)| r == "gated").unwrap().1;
    assert!(
        local.fetch(gated_id).unwrap().is_none(),
        "the gated artifact must NOT be present on a mirror that was refused"
    );

    // The OPEN repository, same upstream, same certless connection: mirrored.
    let open = report_for(&out, "open")
        .as_ref()
        .expect("an ungated repository must still mirror without a certificate");
    assert_eq!(open.fetched, 1, "{}", open.line());
    let (_, open_id, open_body) = up.published.iter().find(|(r, ..)| r == "open").unwrap();
    let got = mirror
        .holger
        .backend_named("open")
        .unwrap()
        .fetch(open_id)
        .unwrap()
        .expect("the open artifact must be here");
    assert_eq!(sha256_hex(&got), sha256_hex(open_body));
}

/// **Proof 3, stated on its own**: ONE upstream, one repository gated and one
/// not, and the open one behaves identically for both mirrors.
///
/// Proofs 1 and 2 each cover half of this; this one puts the two halves beside
/// each other so a regression that made the open repository depend on the
/// certificate fails HERE, with that sentence in the name, rather than as a
/// confusing assertion inside one of the others.
#[tokio::test]
async fn an_open_repository_on_the_same_upstream_mirrors_either_way() {
    holger_server_lib::exposed::tls::ensure_crypto_provider();
    let pki = Pki::new();
    let up = start_upstream(&pki);
    wait_ready(&up).await;
    let (_, open_id, open_body) = up.published.iter().find(|(r, ..)| r == "open").unwrap();

    let id = pki.client("mirror-node");
    for (label, identity) in [("with a certificate", Some(&id)), ("without one", None)] {
        let mirror = build_mirror(&up, identity);
        let out = cycle(&mirror).await;
        let open = report_for(&out, "open")
            .as_ref()
            .unwrap_or_else(|e| panic!("the open repository must mirror {label}: {e:#}"));
        assert_eq!(open.fetched, 1, "{label}: {}", open.line());
        let got = mirror
            .holger
            .backend_named("open")
            .unwrap()
            .fetch(open_id)
            .unwrap()
            .unwrap_or_else(|| panic!("{label}: the open artifact must be here"));
        assert_eq!(
            sha256_hex(&got),
            sha256_hex(open_body),
            "{label}: byte-identical either way"
        );
    }
}

// ───────────────────────────── PROOF 4 ─────────────────────────────────────

/// **Proof 4.** A declared-but-unloadable credential refuses at BOOT — in
/// `wire_holger`, before a port is opened — not on the first cycle.
///
/// The distinction is the whole security posture. A mirror that started, served,
/// and only discovered five minutes later that it had no credential is a mirror
/// that was running with an unknown posture for five minutes; and the tempting
/// alternative, carrying on anonymously, is the one thing a credential
/// declaration must never mean.
#[tokio::test]
async fn a_declared_credential_that_cannot_be_loaded_refuses_at_boot() {
    holger_server_lib::exposed::tls::ensure_crypto_provider();
    let pki = Pki::new();
    let up = start_upstream(&pki);

    let data = tempfile::tempdir().unwrap();
    let repo_dir = data.path().join("open");
    std::fs::create_dir_all(&repo_dir).unwrap();

    let config = format!(
        r#"(
    repositories: [
        (
            ron_name: "open",
            ron_repo_type: "rust",
            ron_upstreams: [],
            ron_in: None,
            ron_out: Some((ron_storage_endpoint: "storage", ron_exposed_endpoint: "default")),
            ron_data_dir: Some("{repo}"),
        ),
    ],
    // Several boxes share this test process, and the egress guard is per process: the box
    // that names no upstream says `network: Online` so its neighbours may dial (B2).
    network: Online,
    exposed_endpoints: [ (ron_name: "default", ron_url: "127.0.0.1:{grpc}") ],
    storage_endpoints: [ (ron_name: "storage", ron_storage_type: "file", ron_path: "{root}") ],
    mirror: Some((
        ron_sources: [
            (
                ron_name: "cloud",
                ron_kind: Holger,
                ron_endpoint: "{endpoint}",
                ron_credential: Mtls(
                    ron_client_cert: "{root}/not-here.crt",
                    ron_client_key:  "{root}/not-here.key",
                ),
                ron_repositories: [ ( ron_from: "open", ron_to: "open" ) ],
            ),
        ],
    )),
)"#,
        repo = repo_dir.display(),
        root = data.path().display(),
        grpc = hold_port(),
        endpoint = up.endpoint,
    );

    let mut cfg = tempfile::NamedTempFile::new().unwrap();
    cfg.write_all(config.as_bytes()).unwrap();

    let mut holger: Holger =
        read_ron_config(cfg.path()).expect("the shape parses — the operator must be told WHY");
    holger.instantiate_backends().unwrap();
    let e = wire_holger(&mut holger).expect_err("a credential that cannot load must stop the boot");
    let msg = format!("{e:#}");
    assert!(msg.contains("cloud"), "names the source: {msg}");
    assert!(msg.contains("not-here.crt"), "names the path: {msg}");
    assert!(msg.contains("REFUSES"), "says it refuses: {msg}");
    assert!(
        !msg.to_lowercase().contains("anonymous") || msg.contains("declare Anonymous"),
        "the only mention of anonymous may be the instruction to declare it on purpose: {msg}"
    );
}

// ───────────────────────────── the TLS name (lane OX-WGDS3) ─────────────────

/// ★ **An upstream reached at an ADDRESS, its certificate checked against a NAME** (lane
/// OX-WGDS3): a downstream reaches its estate's front at `https://10.0.2.2:<port>` while the
/// front's certificate is `repo.<estate>`'s. `Tls(ron_server_name: Some(..))` is the name the
/// certificate is checked against; without it the address is, and the handshake is refused.
#[tokio::test]
async fn a_mirror_reaching_its_upstream_by_address_checks_the_certificates_name() {
    holger_server_lib::exposed::tls::ensure_crypto_provider();
    let pki = Pki::new();
    let up = start_upstream_for(&pki, &["repo.estate.test"]);
    wait_ready(&up).await;
    let by_address = up.endpoint.replace("localhost", "127.0.0.1");
    let (_, open_id, open_body) = up.published.iter().find(|(r, ..)| r == "open").unwrap();

    let named = build_mirror_at(&up, &by_address, None, Some("repo.estate.test"));
    let out = cycle(&named).await;
    let said = |out: &[anyhow::Result<mirror_cycle::PairReport>]| {
        out.iter()
            .map(|r| match r {
                Ok(p) => p.line(),
                Err(e) => format!("{e:#}"),
            })
            .collect::<Vec<_>>()
            .join(" | ")
    };
    let open = out
        .iter()
        .find_map(|r| r.as_ref().ok().filter(|p| p.from == "open"))
        .unwrap_or_else(|| {
            panic!(
                "the name the certificate carries lets the mirror in: {}",
                said(&out)
            )
        });
    assert_eq!(open.fetched, 1, "{}", open.line());
    let got = named
        .holger
        .backend_named("open")
        .unwrap()
        .fetch(open_id)
        .unwrap()
        .expect("the open artifact must be here");
    assert_eq!(sha256_hex(&got), sha256_hex(open_body));

    let unnamed = build_mirror_at(&up, &by_address, None, None);
    let out = cycle(&unnamed).await;
    assert!(
        out.iter().all(|r| r.is_err()),
        "the address is not the certificate's name: the handshake is refused: {}",
        said(&out)
    );
    assert!(
        unnamed
            .holger
            .backend_named("open")
            .unwrap()
            .fetch(open_id)
            .unwrap()
            .is_none(),
        "nothing arrived without the name"
    );
}