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

nordisk/gunnar

crates/gunnar-cli/tests/http_transport.rs

//! **Smart HTTP, judged by stock `git`.**
//!
//! > **Runs only under `--features http`.** The transport is default-off (the
//! > shape the systemd verb set), so this file is gated to the feature it tests. That is
//! > a silently-skipping suite, which is a shape worth naming rather than
//! > leaving to be discovered: with the feature off, `cargo test` reports
//! > *nothing here*, not a pass. `xtask ci` therefore runs the workspace with
//! > `--features gunnar-cli/http`, and `tests/feature_split.rs` asserts from a
//! > resolved graph that the appliance build does **not** carry it — so the
//! > default-off claim is measured rather than assumed.
//!
//! Nothing in this file talks to gunnar's own client. The only honest proof
//! that a git server works is that the `git` binary talks to it, and the only
//! honest proof that a *clone* worked is that the clone holds the same objects
//! the server does — not that `git` exited zero. `git clone` of an empty
//! repository exits zero. So does a clone that fetches nothing because
//! negotiation went wrong.
//!
//! Every assertion here therefore ends at one of two places:
//!
//! * [`object_graph`], which is `git cat-file --batch-all-objects` plus every
//!   ref tip, compared **oid by oid** between the two sides; or
//! * the stored refs, read back out of the server's own `RefStore` after a
//!   push, so "the client said ok" is never the evidence.
//!
//! And each of them carries a **non-vacuity floor**: a fixture with fewer than
//! [`MIN_OBJECTS`] objects fails the comparison outright. Without it, an empty
//! repository compared against an empty clone is a green test for a server
//! that serves nothing at all, which is precisely the shape a measured
//! assertion exists to catch.
//!
//! # Both protocol versions, deliberately forced
//!
//! `git` negotiates v2 by default over HTTP, so a suite that did not say
//! otherwise would never exercise the v0 path — and v0 over HTTP is where
//! stateless-rpc is hardest, because v0 has no `command=` boundary to end a
//! round on. Every test that can run under both runs under both, with
//! `-c protocol.version=…` on the command line, and asserts *which one was
//! actually spoken* rather than trusting the flag.

#![cfg(feature = "http")]

mod common;

use std::path::Path;

use common::{Harness, HarnessOptions};
use gunnar_core::{Grant, HashKind, Level, Principal, PrincipalKind, RepoRef, Visibility};

/// The floor a fixture must clear for a graph comparison to mean anything.
///
/// Seven commits with a merge and a tag produce comfortably more than this. The
/// number is a floor and not an equality on purpose: it must fail an empty or
/// truncated fetch, not track the fixture.
const MIN_OBJECTS: usize = 12;

/// The two protocol versions, and what `git` has to be told to speak each.
const VERSIONS: &[(&str, u8)] = &[("0", 0), ("2", 2)];

fn harness(hash: HashKind, visibility: Visibility) -> Harness {
    Harness::start_with_options(
        "team",
        "project",
        hash,
        HarnessOptions {
            http: true,
            visibility,
            ..HarnessOptions::default()
        },
    )
}

/// A working tree with real history: a branch that diverges and merges, a tag,
/// and enough objects to clear [`MIN_OBJECTS`].
///
/// A merge and not a chain. A straight line makes negotiation look easy,
/// because the client's first `have` is always the answer; the second parent is
/// what catches a traversal that follows `parent[0]` only.
fn seed(harness: &Harness, work: &Path) {
    std::fs::create_dir_all(work).expect("the working tree");
    harness
        .git(work, &["init", "-q", "-b", "main"])
        .expect_ok("a git command in the http suite");
    for index in 0..4 {
        commit(harness, work, &format!("main-{index}"));
    }
    harness
        .git(work, &["checkout", "-q", "-b", "side", "HEAD~2"])
        .expect_ok("a git command in the http suite");
    commit(harness, work, "side-0");
    harness
        .git(work, &["checkout", "-q", "main"])
        .expect_ok("a git command in the http suite");
    harness
        .git(
            work,
            &["merge", "-q", "--no-ff", "-m", "merge side", "side"],
        )
        .expect_ok("a git command in the http suite");
    commit(harness, work, "after-merge");
    harness
        .git(work, &["tag", "-a", "v1", "-m", "release one"])
        .expect_ok("a git command in the http suite");
}

fn commit(harness: &Harness, work: &Path, name: &str) {
    std::fs::write(work.join(format!("{name}.txt")), format!("{name}\n")).expect("write a file");
    harness
        .git(work, &["add", "-A"])
        .expect_ok("a git command in the http suite");
    harness
        .git(work, &["commit", "-q", "-m", name])
        .expect_ok("a git command in the http suite");
}

/// Every object a repository holds, and every ref tip, as one sorted list.
///
/// `cat-file --batch-all-objects` is the whole object database — not what is
/// reachable, not what a log walk finds — so a pack that arrived missing a tree
/// shows up here and nowhere else. The ref tips are appended because two
/// repositories can hold identical objects and disagree about where `main`
/// points, which is a push that did nothing.
///
/// # Refs are compared in ONE namespace
///
/// A clone stores what a bare repository calls `refs/heads/main` under
/// `refs/remotes/origin/main`, so a raw comparison fails on every clone that
/// worked perfectly. [`normalise_ref`] folds the remote-tracking namespace onto
/// the local one and drops the two `HEAD` symrefs, which differ by design.
///
/// This is a narrowing and it is worth being explicit about what survives it:
/// the **oid** of every branch and every tag still has to match on both sides,
/// so a fetch that moved no ref, or moved one to the wrong commit, still fails.
/// What is given up is only *where the client filed it*, which is git's
/// business and not gunnar's.
fn object_graph(harness: &Harness, repo: &Path) -> Vec<String> {
    let objects = harness.git(
        repo,
        &[
            "cat-file",
            "--batch-all-objects",
            "--batch-check=%(objectname) %(objecttype)",
        ],
    );
    objects.expect_ok("cat-file --batch-all-objects");
    let refs = harness.git(repo, &["show-ref", "-d"]).stdout.clone();

    // Remote-tracking first, local second, so that when a working repository
    // holds both — `git push <url>` does NOT update a remote-tracking ref, so
    // origin/main goes stale the moment you push by URL — the LOCAL one wins.
    // That is the right way round: the local ref is what was pushed, and it is
    // what the server must now agree with.
    let mut tips: std::collections::BTreeMap<String, String> = Default::default();
    for pass in [Namespace::RemoteTracking, Namespace::Local] {
        for line in refs.lines() {
            let Some((oid, name)) = line.split_once(' ') else {
                continue;
            };
            // `refs/remotes/origin/HEAD` is the clone's note of where the
            // server's HEAD pointed; the server has `HEAD`, which `show-ref -d`
            // does not list. Neither has a counterpart on the other side.
            if name == "refs/remotes/origin/HEAD" {
                continue;
            }
            match (pass, name.strip_prefix("refs/remotes/origin/")) {
                (Namespace::RemoteTracking, Some(branch)) => {
                    tips.insert(format!("refs/heads/{branch}"), oid.to_string());
                }
                (Namespace::Local, None) => {
                    tips.insert(name.to_string(), oid.to_string());
                }
                _ => {}
            }
        }
    }

    let mut lines: Vec<String> = objects
        .stdout
        .lines()
        .map(str::to_string)
        .chain(
            tips.into_iter()
                .map(|(name, oid)| format!("ref {oid} {name}")),
        )
        .collect();
    lines.sort();
    lines
}

/// Which half of [`object_graph`]'s two-pass ref fold is running.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Namespace {
    RemoteTracking,
    Local,
}

/// Compare two graphs, with the floor that makes the comparison mean something.
fn assert_same_graph(what: &str, server_side: &[String], client_side: &[String]) {
    let objects = server_side
        .iter()
        .filter(|line| !line.starts_with("ref "))
        .count();
    assert!(
        objects >= MIN_OBJECTS,
        "{what}: the SERVER side holds {objects} objects, under the {MIN_OBJECTS}-object \
         floor. An empty repository compared against an empty clone is a green test for a \
         server that serves nothing, so this fails rather than passes."
    );
    assert_eq!(server_side, client_side, "{what}: the object graphs differ");
}

/// **The server's whole graph, read the only way the SHIPPED engine
/// answers:** a fresh `--mirror` clone by stock `git`, then [`object_graph`]
/// on the clone.
///
/// # 🔴 This was `git` pointed INSIDE the server's git directory until 2026-10-11
///
/// Which `tests/replication.rs` found and wrote down on 2026-08-30: on the
/// znippy engine that directory holds no `refs/`, no `packed-refs`, no
/// `objects/` and no loose object — a push is appended verbatim into
/// `gunnar-git.znippy/objects.pack` and the refs are rows in a log — so
/// `cat-file`, `show-ref` and `fsck` all died there with "not a git
/// repository", on every call, whatever the transport had done. The mirror
/// clone is the *stronger* read: the pack has to be emitted, sent and indexed
/// by a client nobody here wrote. What is narrowed is only that objects
/// unreachable on the server are not carried — and nothing in this suite
/// creates one.
fn server_graph(harness: &Harness, url: &str, probe: &str) -> Vec<String> {
    let probe = harness.dir.path().join(probe);
    harness
        .git_http(
            harness.dir.path(),
            &[
                "clone",
                "-q",
                "--mirror",
                url,
                probe.to_str().expect("utf-8 path"),
            ],
        )
        .expect_ok("mirror-clone the server to read its graph");
    object_graph(harness, &probe)
}

// ── clone ───────────────────────────────────────────────────────────────────

/// **`git clone http://…` of a public repository, with no credentials at all,
/// on both protocol versions.**
///
/// This is the headline: a stranger, over HTTP, cloning a repository they were
/// never given a key for, and ending up with byte-identical objects. It is also
/// the anonymous path arriving on a second transport, which is the thing
/// that had to keep obeying one switch rather than growing a second.
#[test]
fn a_stranger_clones_a_public_repository_over_http_on_both_protocol_versions() {
    let harness = harness(HashKind::Sha1, Visibility::Public);
    let work = harness.dir.path().join("work");
    seed(&harness, &work);
    harness
        .git(
            &work,
            &["push", "-q", &harness.url("team", "project"), "--mirror"],
        )
        .expect_ok("a git command in the http suite");
    let expected = server_graph(
        &harness,
        &harness.http_url("team", "project"),
        "server-probe",
    );

    for (flag, number) in VERSIONS {
        let target = harness.dir.path().join(format!("clone-v{number}"));
        harness
            .git_http(
                harness.dir.path(),
                &[
                    "-c",
                    &format!("protocol.version={flag}"),
                    "clone",
                    "-q",
                    &harness.http_url("team", "project"),
                    target.to_str().expect("utf-8 path"),
                ],
            )
            .expect_ok("a git command in the http suite");
        assert_same_graph(
            &format!("anonymous http clone, protocol v{number}"),
            &expected,
            &object_graph(&harness, &target),
        );
    }
}

/// The same over **sha256**, which is where `object-format` echoing goes wrong
/// silently: answering `sha1` to a sha256 repository yields an *empty*
/// response, not an error (object-format negotiation, and the bug that cost a session in
/// `nornir-git`).
#[test]
fn a_sha256_repository_clones_over_http_on_both_protocol_versions() {
    let harness = harness(HashKind::Sha256, Visibility::Public);
    let work = harness.dir.path().join("work");
    std::fs::create_dir_all(&work).expect("the working tree");
    harness
        .git(
            &work,
            &["init", "-q", "-b", "main", "--object-format=sha256"],
        )
        .expect_ok("a git command in the http suite");
    for index in 0..8 {
        commit(&harness, &work, &format!("c{index}"));
    }
    harness
        .git(&work, &["tag", "-a", "v1", "-m", "one"])
        .expect_ok("a git command in the http suite");
    harness
        .git(
            &work,
            &["push", "-q", &harness.url("team", "project"), "--mirror"],
        )
        .expect_ok("a git command in the http suite");
    let expected = server_graph(
        &harness,
        &harness.http_url("team", "project"),
        "server-probe",
    );

    for (flag, number) in VERSIONS {
        let target = harness.dir.path().join(format!("sha256-clone-v{number}"));
        harness
            .git_http(
                harness.dir.path(),
                &[
                    "-c",
                    &format!("protocol.version={flag}"),
                    "clone",
                    "-q",
                    &harness.http_url("team", "project"),
                    target.to_str().expect("utf-8 path"),
                ],
            )
            .expect_ok("a git command in the http suite");
        // The oids themselves are the assertion: a sha256 repository's object
        // names are 64 hex characters, so a clone that somehow landed sha1
        // objects could not match.
        let actual = object_graph(&harness, &target);
        assert!(
            actual
                .iter()
                .filter(|line| !line.starts_with("ref "))
                .all(|line| line.split(' ').next().is_some_and(|oid| oid.len() == 64)),
            "protocol v{number}: a sha256 clone must hold 64-character object names: {actual:?}"
        );
        assert_same_graph(
            &format!("sha256 http clone, protocol v{number}"),
            &expected,
            &actual,
        );
    }
}

// ── ls-remote ───────────────────────────────────────────────────────────────

/// `git ls-remote http://…` on both versions, against the server's own refs.
///
/// Asserted against what the **server** stores rather than against a literal,
/// so the test cannot drift from the fixture, and including the peeled tag,
/// which is the entry a `peel`-less advertisement drops silently.
#[test]
fn ls_remote_over_http_lists_exactly_the_stored_refs_on_both_versions() {
    let harness = harness(HashKind::Sha1, Visibility::Public);
    let work = harness.dir.path().join("work");
    seed(&harness, &work);
    harness
        .git(
            &work,
            &["push", "-q", &harness.url("team", "project"), "--mirror"],
        )
        .expect_ok("a git command in the http suite");

    // The server's refs are exactly what `--mirror` just pushed, and `work` is
    // a directory stock `git` can read — the server's own is not (the znippy
    // engine keeps its refs as rows in a log; see [`server_graph`]).
    let show_ref = harness.git(&work, &["show-ref", "-d"]);
    let mut expected: Vec<String> = show_ref
        .expect_ok("show-ref on the mirror-pushed source")
        .stdout
        .lines()
        .map(|line| line.replace(' ', "\t"))
        .collect();
    expected.sort();
    assert!(
        expected.len() >= 4,
        "the fixture must have main, side, v1 and v1^{{}} at least: {expected:?}"
    );

    for (flag, number) in VERSIONS {
        let listing = harness.git_http(
            harness.dir.path(),
            &[
                "-c",
                &format!("protocol.version={flag}"),
                "ls-remote",
                &harness.http_url("team", "project"),
            ],
        );
        let mut listed: Vec<String> = listing
            .expect_ok("ls-remote over http")
            .stdout
            .lines()
            .filter(|line| !line.ends_with("HEAD"))
            .map(str::to_string)
            .collect();
        listed.sort();
        assert_eq!(
            listed, expected,
            "protocol v{number}: ls-remote over HTTP disagrees with the stored refs"
        );
    }
}

// ── fetch, with a real negotiation ──────────────────────────────────────────

/// **An incremental fetch over HTTP**, on both versions.
///
/// This is the one stateless-rpc is really about. The client already holds most
/// of the history, so it sends `have`s, and over HTTP each round is a separate
/// `POST` with the whole request repeated. A server that carried state between
/// rounds, or that kept reading after the body ended, hangs here rather than
/// failing — so the test also asserts the fetch **moved the ref**, which a
/// no-op fetch that exited zero would not.
#[test]
fn an_incremental_fetch_over_http_gets_the_new_commits_on_both_versions() {
    let harness = harness(HashKind::Sha1, Visibility::Public);
    let work = harness.dir.path().join("work");
    seed(&harness, &work);
    let url = harness.url("team", "project");
    harness
        .git(&work, &["push", "-q", &url, "--mirror"])
        .expect_ok("a git command in the http suite");

    for (flag, number) in VERSIONS {
        let clone = harness.dir.path().join(format!("fetch-v{number}"));
        harness
            .git_http(
                harness.dir.path(),
                &[
                    "-c",
                    &format!("protocol.version={flag}"),
                    "clone",
                    "-q",
                    &harness.http_url("team", "project"),
                    clone.to_str().expect("utf-8"),
                ],
            )
            .expect_ok("a git command in the http suite");
        let before = rev_parse(&harness, &clone, "origin/main");

        // Three more commits on the server, pushed over SSH so the fetch below
        // is the only HTTP operation being measured.
        for index in 0..3 {
            commit(&harness, &work, &format!("later-v{number}-{index}"));
        }
        harness
            .git(&work, &["push", "-q", &url, "main"])
            .expect_ok("a git command in the http suite");
        let server_tip = rev_parse(&harness, &work, "main");

        harness
            .git_http(
                &clone,
                &[
                    "-c",
                    &format!("protocol.version={flag}"),
                    "fetch",
                    "-q",
                    "origin",
                ],
            )
            .expect_ok("a git command in the http suite");
        let after = rev_parse(&harness, &clone, "origin/main");

        assert_ne!(
            before, after,
            "protocol v{number}: the fetch left origin/main where it was, so it fetched \
             nothing; an exit code of zero says nothing here"
        );
        assert_eq!(
            after, server_tip,
            "protocol v{number}: origin/main did not reach the server's tip"
        );
        // …and the new objects really arrived, not just the ref.
        harness
            .git(
                &clone,
                &["cat-file", "-e", &format!("{server_tip}^{{tree}}")],
            )
            .expect_ok("a git command in the http suite");
    }
}

// ── push ────────────────────────────────────────────────────────────────────

/// **`git push http://…`**, authenticated with an access token, asserted
/// against the refs the server actually stored.
///
/// Push is v0 on every transport — protocol v2 has no push side — so there is
/// one version here and that is not an omission.
#[test]
fn a_token_holder_pushes_over_http_and_the_server_stores_it() {
    let harness = harness(HashKind::Sha1, Visibility::Private);
    let work = harness.dir.path().join("work");
    seed(&harness, &work);

    let token = harness.mint_token("tester");
    let url = harness.http_url_with_token("team", "project", &token);
    harness
        .git(&work, &["push", "-q", &url, "--mirror"])
        .expect_ok("a git command in the http suite");

    // Read back from the SERVER's own ref store, not from what git reported.
    let path = RepoRef::new("team", "project").expect("legal");
    let repo = harness
        .stores
        .catalog()
        .open(&path)
        .expect("the repository");
    let store = harness.stores.repo_store(&repo).expect("the store");
    // `RefStore::resolve` → `GitOps::refs` + `RefRow::oid` (2026-08-10, same
    // migration as the ssh and anonymous suites). A symbolic row carries
    // `oid: None`, so a symref where a push landed still fails, by name.
    let rows = gunnar_store::git_storage_trait::GitOps::refs(store.serve()).expect("read the refs");
    let main = rows
        .iter()
        .find(|row| row.name == "refs/heads/main")
        .expect("refs/heads/main was pushed");
    let stored = main
        .oid
        .as_ref()
        .unwrap_or_else(|| panic!("refs/heads/main is symbolic after a push: {main:?}"));
    let expected = rev_parse(&harness, &work, "main");
    assert_eq!(
        gix_hash::ObjectId::try_from(stored.as_slice())
            .expect("a well-sized oid")
            .to_string(),
        expected,
        "the server stored a different tip than the client pushed"
    );

    // And the whole graph, so a push that landed the ref without the objects
    // fails here.
    assert_same_graph(
        "http push",
        &server_graph(&harness, &url, "stored-probe"),
        &object_graph(&harness, &work),
    );

    // A second push with nothing to do must also succeed: `git push` with no
    // work takes the advertisement and hangs up, which over HTTP is a GET with
    // no POST after it, and a server that expected one would wait.
    harness
        .git(&work, &["push", &url, "main"])
        .expect_ok("a git command in the http suite");
}

/// The clone-then-push round trip over HTTP alone, ending in `git fsck`.
///
/// `fsck` is the assertion an object-graph comparison cannot make: it walks
/// every object it has and complains about missing links. A thin pack whose
/// bases never resolved passes a graph comparison against the pusher (both
/// sides lack the same object) and fails here.
#[test]
fn clone_push_and_fsck_over_http_only() {
    let harness = harness(HashKind::Sha1, Visibility::Public);
    let work = harness.dir.path().join("work");
    seed(&harness, &work);
    let token = harness.mint_token("tester");
    let authed = harness.http_url_with_token("team", "project", &token);
    harness
        .git(&work, &["push", "-q", &authed, "--mirror"])
        .expect_ok("a git command in the http suite");

    let clone = harness.dir.path().join("roundtrip");
    harness
        .git_http(
            harness.dir.path(),
            &["clone", "-q", &authed, clone.to_str().expect("utf-8")],
        )
        .expect_ok("a git command in the http suite");
    commit(&harness, &clone, "from-the-clone");
    harness
        .git(&clone, &["push", "-q", &authed, "main"])
        .expect_ok("a git command in the http suite");

    // The round trip, read back through a mirror clone ([`server_graph`]) —
    // and `fsck` walks THAT clone, because the server's own directory is not
    // one stock `git` can read. The pack had to be emitted, sent and indexed
    // to get here, which is the integrity question this test asks.
    let server_side = server_graph(&harness, &authed, "fsck-probe");
    harness
        .git(
            &harness.dir.path().join("fsck-probe"),
            &["fsck", "--strict", "--no-progress"],
        )
        .expect_ok("a git command in the http suite");
    assert_same_graph(
        "http round trip",
        &server_side,
        &object_graph(&harness, &clone),
    );
}

// ── the refusals ────────────────────────────────────────────────────────────

/// **`allow_anonymous: false` shuts the HTTP door too.**
///
/// The switch this test was written for: it was found stubbed to `false` on
/// one path today and served strangers a full clone of every public repository.
/// One field in `ServeConfig` feeds both listeners, and this asserts the HTTP
/// end of it — with the *live* half beside it, because a test that only ever
/// saw the door shut would pass against a transport that never worked at all.
#[test]
fn the_anonymous_switch_governs_http_exactly_as_it_governs_ssh() {
    // Open: a stranger clones.
    let open = harness(HashKind::Sha1, Visibility::Public);
    let work = open.dir.path().join("work");
    seed(&open, &work);
    open.git(
        &work,
        &["push", "-q", &open.url("team", "project"), "--mirror"],
    )
    .expect_ok("a git command in the http suite");
    open.git_http(
        open.dir.path(),
        &[
            "clone",
            "-q",
            &open.http_url("team", "project"),
            open.dir.path().join("open").to_str().expect("utf-8"),
        ],
    )
    .expect_ok("a git command in the http suite");

    // Shut: the same repository, the same request, refused.
    let shut = Harness::start_with_options(
        "team",
        "project",
        HashKind::Sha1,
        HarnessOptions {
            http: true,
            visibility: Visibility::Public,
            allow_anonymous: false,
            ..HarnessOptions::default()
        },
    );
    let shut_work = shut.dir.path().join("work");
    seed(&shut, &shut_work);
    shut.git(
        &shut_work,
        &["push", "-q", &shut.url("team", "project"), "--mirror"],
    )
    .expect_ok("a git command in the http suite");
    let refused = shut.git_http(
        shut.dir.path(),
        &[
            "clone",
            "-q",
            &shut.http_url("team", "project"),
            shut.dir.path().join("shut").to_str().expect("utf-8"),
        ],
    );
    refused.expect_failure("--no-anonymous must refuse a stranger over HTTP as it does over SSH");
    assert!(
        !shut.dir.path().join("shut/.git").exists()
            || object_graph(&shut, &shut.dir.path().join("shut"))
                .iter()
                .all(|line| line.starts_with("ref ")),
        "the refused clone left objects behind, so something was served"
    );
}

/// **Anonymous may never push**, on HTTP as on SSH: rule 0 gives `Read` only.
#[test]
fn anonymous_cannot_push_over_http() {
    let harness = harness(HashKind::Sha1, Visibility::Public);
    let work = harness.dir.path().join("work");
    seed(&harness, &work);
    harness
        .git(
            &work,
            &["push", "-q", &harness.url("team", "project"), "--mirror"],
        )
        .expect_ok("a git command in the http suite");

    let tip_before = stored_tip(&harness);
    commit(&harness, &work, "anonymous-attempt");
    harness
        .git_http(
            &work,
            &["push", &harness.http_url("team", "project"), "main"],
        )
        .expect_failure("an ANONYMOUS PUSH to a public repository");
    assert_eq!(
        stored_tip(&harness),
        tip_before,
        "the refused push moved the stored ref anyway"
    );
}

/// A token whose principal holds only `Read` may fetch and may **not** push.
///
/// Both directions on one credential, which is the assertion that a token
/// carries no authority of its own: the same secret that succeeds at reading
/// fails at writing, so what decided the outcome was the grant.
#[test]
fn a_read_only_tokens_holder_fetches_and_cannot_push() {
    let harness = harness(HashKind::Sha1, Visibility::Private);
    let work = harness.dir.path().join("work");
    seed(&harness, &work);
    harness
        .git(
            &work,
            &["push", "-q", &harness.url("team", "project"), "--mirror"],
        )
        .expect_ok("a git command in the http suite");

    let path = RepoRef::new("team", "project").expect("legal");
    harness
        .stores
        .auth()
        .add_principal(Principal::new("reader", "reader", PrincipalKind::User).expect("legal"))
        .expect("add");
    harness
        .stores
        .auth()
        .grant(Grant::repo("reader", path, Level::Read))
        .expect("grant read");
    let token = harness.mint_token("reader");
    let url = harness.http_url_with_token("team", "project", &token);

    let clone = harness.dir.path().join("reader-clone");
    harness
        .git_http(
            harness.dir.path(),
            &["clone", "-q", &url, clone.to_str().expect("utf-8")],
        )
        .expect_ok("a git command in the http suite");
    assert_same_graph(
        "read-only token clone",
        &server_graph(&harness, &url, "reader-probe"),
        &object_graph(&harness, &clone),
    );

    let tip_before = stored_tip(&harness);
    commit(&harness, &clone, "reader-attempt");
    harness
        .git_http(&clone, &["push", &url, "main"])
        .expect_failure("a READ-ONLY token must not push");
    assert_eq!(stored_tip(&harness), tip_before, "the refused push landed");
}

/// A revoked token stops working immediately, and the live half beside it.
#[test]
fn a_revoked_token_stops_working() {
    let harness = harness(HashKind::Sha1, Visibility::Private);
    let work = harness.dir.path().join("work");
    seed(&harness, &work);
    let token = harness.mint_token("tester");
    let url = harness.http_url_with_token("team", "project", &token);
    harness
        .git(&work, &["push", "-q", &url, "--mirror"])
        .expect_ok("a git command in the http suite");

    harness
        .git_http(
            harness.dir.path(),
            &[
                "clone",
                "-q",
                &url,
                harness.dir.path().join("before").to_str().expect("utf-8"),
            ],
        )
        .expect_ok("a git command in the http suite");

    let hash = gunnar_core::token_hash(&token);
    harness
        .stores
        .auth()
        .revoke_token(&hash)
        .expect("revoke")
        .expect("it was registered");

    harness
        .git_http(
            harness.dir.path(),
            &[
                "clone",
                "-q",
                &url,
                harness.dir.path().join("after").to_str().expect("utf-8"),
            ],
        )
        .expect_failure("a REVOKED token must not clone");
}

/// **A revoked token cannot push either.**
///
/// `a_revoked_token_stops_working` revokes and then re-*clones*. Read and write
/// are different verbs down different code paths — upload-pack against
/// receive-pack — and a credential check that was dropped on the write side
/// would leave that test green while a revoked token kept its ability to
/// rewrite history. The destructive verb is the one worth asserting.
///
/// The tip is read out of the store before and after, because "the push
/// command failed" and "the push did not land" are not the same claim: a push
/// can be reported as failed after the reference has already moved.
#[test]
fn a_revoked_token_cannot_push_either() {
    let harness = harness(HashKind::Sha1, Visibility::Private);
    let work = harness.dir.path().join("work");
    seed(&harness, &work);
    let token = harness.mint_token("tester");
    let url = harness.http_url_with_token("team", "project", &token);

    // The acceptance half: this token pushes, while it is live.
    harness
        .git(&work, &["push", "-q", &url, "--mirror"])
        .expect_ok("a git command in the http suite");
    let tip_before = stored_tip(&harness);
    assert!(
        tip_before.is_some(),
        "the seeded push left no tip, so the comparison below would be vacuous"
    );

    // A second commit, so the revoked push has something real to carry.
    std::fs::write(work.join("after-revocation.txt"), "new work\n").expect("write");
    harness
        .git(&work, &["add", "after-revocation.txt"])
        .expect_ok("a git command in the http suite");
    harness
        .git(
            &work,
            &["commit", "-q", "-m", "work done after the token died"],
        )
        .expect_ok("a git command in the http suite");
    let local_tip = harness
        .git(&work, &["rev-parse", "HEAD"])
        .expect_ok("a git command in the http suite")
        .stdout
        .clone();
    assert_ne!(
        Some(local_tip.trim().to_owned()),
        tip_before,
        "the new commit did not move HEAD, so the push would be a no-op"
    );

    let hash = gunnar_core::token_hash(&token);
    harness
        .stores
        .auth()
        .revoke_token(&hash)
        .expect("revoke")
        .expect("it was registered");

    harness
        .git_http(
            &work,
            &["push", "-q", &url, "refs/heads/main:refs/heads/main"],
        )
        .expect_failure("a REVOKED token must not push");
    assert_eq!(
        stored_tip(&harness),
        tip_before,
        "the push by a revoked token was reported as failed but moved the reference anyway"
    );
}

/// A token that was never registered must reach nothing, on the write path as
/// well as the read one.
///
/// The credential is well formed — it carries gunnar's own token prefix — so
/// this is the ACL refusing an unknown secret and not a parser refusing a
/// malformed header.
#[test]
fn an_unregistered_token_cannot_clone_or_push() {
    let harness = harness(HashKind::Sha1, Visibility::Private);
    let work = harness.dir.path().join("work");
    seed(&harness, &work);
    let good = harness.mint_token("tester");
    harness
        .git(
            &work,
            &["push", "-q", &harness.url("team", "project"), "--mirror"],
        )
        .expect_ok("a git command in the http suite");
    let tip_before = stored_tip(&harness);

    // The acceptance half, so this is not a server refusing every token.
    let good_url = harness.http_url_with_token("team", "project", &good);
    harness
        .git_http(
            harness.dir.path(),
            &[
                "clone",
                "-q",
                &good_url,
                harness.dir.path().join("good").to_str().expect("utf-8"),
            ],
        )
        .expect_ok("a git command in the http suite");

    // Never minted by this server, and shaped exactly like one that was.
    let forged = format!("{}{}", "gunnar_pat_", "A".repeat(43));
    let forged_url = harness.http_url_with_token("team", "project", &forged);
    harness
        .git_http(
            harness.dir.path(),
            &[
                "clone",
                "-q",
                &forged_url,
                harness.dir.path().join("forged").to_str().expect("utf-8"),
            ],
        )
        .expect_failure("an UNREGISTERED token must not clone");
    harness
        .git_http(
            &work,
            &["push", "-q", &forged_url, "refs/heads/main:refs/heads/main"],
        )
        .expect_failure("an UNREGISTERED token must not push");
    assert_eq!(
        stored_tip(&harness),
        tip_before,
        "the push by an unregistered token moved the reference"
    );
}

/// A private repository and one that does not exist look the same to a
/// stranger, and both invite authentication rather than reporting absence.
///
/// The anonymous-read property, stated for SSH, on HTTP. Measured with `curl`-shaped
/// requests through `git ls-remote`'s own reporting, comparing the two
/// *statuses*: a 404 on one and a 401 on the other would be an enumeration
/// oracle.
#[test]
fn a_private_repository_and_a_missing_one_are_indistinguishable_to_a_stranger() {
    let harness = harness(HashKind::Sha1, Visibility::Private);
    let work = harness.dir.path().join("work");
    seed(&harness, &work);
    harness
        .git(
            &work,
            &["push", "-q", &harness.url("team", "project"), "--mirror"],
        )
        .expect_ok("a git command in the http suite");

    let private = status_of(&harness.http_url("team", "project"));
    let missing = status_of(&harness.http_url("team", "no-such-repo"));
    let bad_namespace = status_of(&harness.http_url("nobody", "project"));
    assert_eq!(
        private, missing,
        "a private repository and a missing one must answer identically; got {private} and \
         {missing}, which is an enumeration oracle"
    );
    assert_eq!(private, bad_namespace);
    assert_eq!(
        private, 401,
        "an unauthenticated caller must be invited to authenticate, whatever the truth is"
    );

    // The live half: with a credential, the private one is served. Without
    // this, a server that answered 401 to everything would pass above.
    let token = harness.mint_token("tester");
    assert_eq!(
        status_of(&harness.http_url_with_token("team", "project", &token)),
        200
    );
    // …and a *missing* repository, authenticated, is a 404 rather than a 401:
    // the caller already knows they are somebody.
    assert_eq!(
        status_of(&harness.http_url_with_token("team", "no-such-repo", &token)),
        404
    );
}

/// **The advertisement's opening bytes, per protocol version.**
///
/// `git http-backend` 2.53.0 opens a v0 advertisement with
/// `001e# service=git-upload-pack` + flush and a v2 one with `000eversion 2`
/// and no preamble at all. gunnar matches that byte for byte.
///
/// # This test exists because the obvious one was blind
///
/// The first attempt asserted the difference only through `git clone`, on the
/// reasoning that a v2 client would parse the preamble as a capability line.
/// **That reasoning was wrong.** Measured 2026-08-03 by sending the preamble on
/// a v2 response deliberately: `git 2.53.0` clones perfectly, because
/// `remote-curl.c`'s `discover_refs` consumes a leading `# service=` line
/// whatever version follows it. So no clone test can ever fail on this, and a
/// suite that relied on one would be recording a byte-level property it could
/// not measure.
///
/// The property is still worth holding — thirty bytes of divergence from the
/// reference server is thirty bytes for some other client to disagree about —
/// so it is asserted where it can actually fail: on the bytes.
#[test]
fn the_advertisement_opens_with_the_bytes_git_http_backend_opens_with() {
    let harness = harness(HashKind::Sha1, Visibility::Public);
    let work = harness.dir.path().join("work");
    seed(&harness, &work);
    harness
        .git(
            &work,
            &["push", "-q", &harness.url("team", "project"), "--mirror"],
        )
        .expect_ok("seeding the server over ssh");

    let addr = harness.http_addr.expect("the http listener").to_string();
    let path = "/team/project.git/info/refs?service=git-upload-pack";

    let (status, v0) = request_with(&addr, path, None, None);
    assert_eq!(status, 200);
    let v0_body = body_of(&v0);
    assert!(
        v0_body.starts_with("001e# service=git-upload-pack\n0000"),
        "the v0 advertisement must open with the service preamble and a flush; \
         it opened {:?}",
        &v0_body[..v0_body.len().min(64)]
    );

    let (status, v2) = request_with(&addr, path, None, Some("version=2"));
    assert_eq!(status, 200);
    let v2_body = body_of(&v2);
    assert!(
        v2_body.starts_with("000eversion 2\n"),
        "the v2 advertisement must open with `version 2` and NO preamble; it \
         opened {:?}",
        &v2_body[..v2_body.len().min(64)]
    );
    assert!(
        !v2_body.starts_with("001e# service="),
        "a v2 advertisement must not carry the v0 preamble"
    );

    // Both carry the advertisement content type, which is the same string for
    // both versions — asserted so a future split of the two paths cannot drop
    // it on one of them.
    for (version, response) in [("v0", &v0), ("v2", &v2)] {
        assert!(
            response
                .to_ascii_lowercase()
                .contains("content-type: application/x-git-upload-pack-advertisement"),
            "{version}: wrong content type in {response:?}"
        );
    }
}

/// **The v0 advertisement offers `multi_ack_detailed` and `no-done`.**
///
/// Not a style assertion. A v0 client picks its ACK mode from this list, and
/// offered neither it falls back to single-ACK, in which a bare `ACK <oid>` is
/// terminal. Over a connected session that is right and SSH has always worked.
/// Over stateless-rpc it cannot be: the round ends with the HTTP response, the
/// client opens a fresh `POST` carrying `want` and `done` and no `have`s, and
/// then reads that response as a raw sideband packfile — while the server,
/// seeing no commons, correctly opens it with `NAK`. git demuxes the `N` as a
/// band number and dies with `bad band #78`.
///
/// MEASURED 2026-08-05: `git fetch http://…` exited **128** against a
/// gunnar whose advertisement lacked these two, and **0** against `git
/// http-backend` serving the identical repository. See
/// [`a_multi_round_fetch_over_http_completes_on_both_versions`], which is the
/// end-to-end guard; this one names the cause, so a regression reports which
/// capability went missing rather than only that a fetch broke.
#[test]
fn the_v0_advertisement_offers_the_ack_mode_stateless_rpc_needs() {
    let harness = harness(HashKind::Sha1, Visibility::Public);
    let work = harness.dir.path().join("work");
    seed(&harness, &work);
    harness
        .git(
            &work,
            &["push", "-q", &harness.url("team", "project"), "--mirror"],
        )
        .expect_ok("seeding the server over ssh");

    let addr = harness.http_addr.expect("the http listener").to_string();
    let (status, response) = request_with(
        &addr,
        "/team/project.git/info/refs?service=git-upload-pack",
        None,
        None,
    );
    assert_eq!(status, 200);
    let body = body_of(&response);
    // The capability list rides on the FIRST ref line, after a NUL. Reading it
    // off the whole body would let a capability advertised anywhere at all pass
    // — including inside a ref name — so the line is isolated first.
    let caps = body
        .split('\0')
        .nth(1)
        .expect("the v0 capability list rides after a NUL on the first ref line")
        .lines()
        .next()
        .expect("a capability line");
    for capability in ["multi_ack_detailed", "no-done"] {
        assert!(
            caps.split_whitespace().any(|c| c == capability),
            "the v0 advertisement must offer `{capability}`, or stock git \
             negotiates in single-ACK mode and cannot complete a multi-round \
             fetch over stateless-rpc; it offered: {caps:?}"
        );
    }
    // Anti-vacuity: if the split above ever stopped finding the capability
    // list, `caps` would be some other string and the loop would fail loudly
    // rather than pass — but an EMPTY one would also fail, and this says which.
    assert!(
        caps.split_whitespace().count() >= 5,
        "the capability list parsed out to {caps:?}, which is too short to be one"
    );
}

/// **A fetch that really negotiates, over HTTP, on both protocol versions.**
///
/// The difference from
/// [`an_incremental_fetch_over_http_gets_the_new_commits_on_both_versions`] is
/// the fixture, and the fixture is the whole test. That one adds three commits
/// to a fresh clone: the client's first `have` is already the answer, the
/// server is `ready` on the first round, and the exchange is over before
/// stateless-rpc has to carry any state at all. It passed for the entire time
/// v0 multi-round fetch over HTTP was broken.
///
/// This one resets the client **fifty commits back** and deletes its
/// reflog and remote-tracking refs, so `git` must send `have` batches and the
/// server must answer them across a boundary that, over HTTP, is a whole
/// separate request. That is the only shape in which the ACK mode matters.
///
/// # The depth is load-bearing, and 30 was not enough
///
/// Written first at 40 commits and `HEAD~30`, this test **passed against the
/// broken server**. `git` sends its first `have` batch and then, if it has run
/// out of history to offer, appends `done` to the *same* request — so the
/// whole negotiation fits in one `POST`, the terminal ACK and the packfile
/// come back together, and the second request that stateless-rpc actually
/// breaks on never happens. Only a client left holding **more history than it
/// will offer in one batch** flushes without `done` and forces the round trip.
/// At 100 commits and `HEAD~50` it fails on the unfixed server with the
/// production symptom, `fetch-pack: protocol error: bad band #78`. Do not trim
/// the fixture: shortening it does not make this test faster, it makes it
/// blind, and it was blind once already.
///
/// The assertion is the ref tip and the object graph, never the exit code
/// alone — though the exit code is checked too, because the failure this
/// guards against was a hard `fatal:` and a silent one would be worse.
#[test]
fn a_multi_round_fetch_over_http_completes_on_both_versions() {
    let harness = harness(HashKind::Sha1, Visibility::Public);
    let work = harness.dir.path().join("work");
    seed(&harness, &work);
    // Deep enough that git cannot settle it inside its first `have` batch,
    // which is 16. A fixture shorter than that is the hollow one above.
    for index in 0..100 {
        commit(&harness, &work, &format!("deep-{index}"));
    }
    let url = harness.url("team", "project");
    harness
        .git(&work, &["push", "-q", &url, "--mirror"])
        .expect_ok("seeding the server over ssh");
    let server_tip = rev_parse(&harness, &work, "main");

    for (flag, number) in VERSIONS {
        let clone = harness.dir.path().join(format!("negotiate-v{number}"));
        harness
            .git_http(
                harness.dir.path(),
                &[
                    "-c",
                    &format!("protocol.version={flag}"),
                    "clone",
                    "-q",
                    &harness.http_url("team", "project"),
                    clone.to_str().expect("utf-8"),
                ],
            )
            .expect_ok("a git command in the http suite");

        // Wind the client back and take away every other way it could know the
        // history it just cloned. Without the reflog expiry and the `gc`, the
        // objects are still there and `git` short-circuits the negotiation.
        harness
            .git(&clone, &["reset", "-q", "--hard", "HEAD~50"])
            .expect_ok("winding the client back");
        // `remote remove`, not `update-ref -d` over the tracking refs one by
        // one: that leaves `refs/remotes/origin/HEAD` as a symref pointing at a
        // ref that no longer exists, which `git fsck --strict` reports as an
        // invalid pointer — a failure of the cleanup, in a test whose whole
        // point is that `fsck` is clean afterwards.
        harness
            .git(&clone, &["remote", "remove", "origin"])
            .expect_ok("dropping the remote-tracking refs");
        harness
            .git(&clone, &["reflog", "expire", "--all", "--expire=now"])
            .expect_ok("expiring the reflog");
        harness
            .git(&clone, &["gc", "-q", "--prune=now"])
            .expect_ok("pruning the wound-back history");

        let before = rev_parse(&harness, &clone, "HEAD");
        assert_ne!(
            before, server_tip,
            "protocol v{number}: the client was not actually wound back, so the \
             fetch below has nothing to negotiate and this test proves nothing"
        );

        harness
            .git_http(
                &clone,
                &[
                    "-c",
                    &format!("protocol.version={flag}"),
                    "-c",
                    "transfer.fsckObjects=true",
                    "fetch",
                    "-q",
                    &harness.http_url("team", "project"),
                    "+refs/heads/main:refs/heads/fetched",
                ],
            )
            .expect_ok("a multi-round fetch over http");

        assert_eq!(
            rev_parse(&harness, &clone, "refs/heads/fetched"),
            server_tip,
            "protocol v{number}: the negotiated fetch did not land the server's tip"
        );
        // The ref is not the objects. `fsck` walks what actually arrived.
        harness
            .git(&clone, &["fsck", "--full", "--strict", "--no-progress"])
            .expect_ok("fsck after a negotiated http fetch");
    }
}

/// The dumb protocol is refused with a sentence, not a 404.
#[test]
fn the_dumb_protocol_is_refused_by_name() {
    let harness = harness(HashKind::Sha1, Visibility::Public);
    let (status, body) = get(&harness, "/team/project.git/objects/info/packs");
    assert_eq!(status, 403, "body was {body:?}");
    assert!(
        body.contains("smart HTTP"),
        "the refusal must say what gunnar does serve: {body:?}"
    );
}

// ── helpers that speak raw HTTP ─────────────────────────────────────────────

/// `git rev-parse <rev>`, trimmed.
fn rev_parse(harness: &Harness, repo: &Path, rev: &str) -> String {
    let out = harness.git(repo, &["rev-parse", rev]);
    out.expect_ok("rev-parse").stdout.trim().to_string()
}

/// The stored tip of `refs/heads/main`, read from the server's own ref store.
fn stored_tip(harness: &Harness) -> Option<String> {
    let path = RepoRef::new("team", "project").expect("legal");
    let repo = harness
        .stores
        .catalog()
        .open(&path)
        .expect("the repository");
    let store = harness.stores.repo_store(&repo).expect("the store");
    // Same `GitOps::refs` + `RefRow::oid` reading as above: a symbolic row is
    // `oid: None`, which maps to `None` here — same answer `resolve` gave.
    let rows = gunnar_store::git_storage_trait::GitOps::refs(store.serve()).expect("read the refs");
    rows.iter()
        .find(|row| row.name == "refs/heads/main")
        .and_then(|row| row.oid.as_ref())
        .map(|raw| {
            gix_hash::ObjectId::try_from(raw.as_slice())
                .expect("a well-sized oid")
                .to_string()
        })
}

/// The HTTP status of the advertisement for a clone URL.
fn status_of(url: &str) -> u16 {
    // `<scheme>://[user:pass@]host/path` → the path, and the credentials.
    let rest = url.strip_prefix("http://").expect("an http url");
    let (authority, path) = rest.split_once('/').expect("a path");
    let (credentials, host) = match authority.split_once('@') {
        Some((credentials, host)) => (Some(credentials.to_string()), host),
        None => (None, authority),
    };
    request(
        host,
        &format!("/{path}/info/refs?service=git-upload-pack"),
        credentials.as_deref(),
    )
    .0
}

fn get(harness: &Harness, path: &str) -> (u16, String) {
    let addr = harness.http_addr.expect("the http listener");
    request(&addr.to_string(), path, None)
}

/// Everything after the header/body separator, de-chunked.
///
/// The advertisement arrives with `Transfer-Encoding: chunked`, which is itself
/// worth noticing: gunnar streams the response rather than buffering it and
/// sending a `Content-Length`, so a clone of a large repository starts moving
/// before the last object is written. A test that read the raw bytes would be
/// reading chunk headers.
fn body_of(response: &str) -> String {
    let (head, body) = response
        .split_once("\r\n\r\n")
        .expect("a response with a body");
    if !head
        .to_ascii_lowercase()
        .contains("transfer-encoding: chunked")
    {
        return body.to_string();
    }
    let mut out = String::new();
    let mut rest = body;
    while let Some((size, tail)) = rest.split_once("\r\n") {
        let Ok(size) = usize::from_str_radix(size.trim(), 16) else {
            break;
        };
        if size == 0 || tail.len() < size {
            break;
        }
        out.push_str(&tail[..size]);
        rest = tail[size..].strip_prefix("\r\n").unwrap_or(&tail[size..]);
    }
    out
}

/// A minimal HTTP/1.1 GET.
///
/// The writer is `common::http_request` — shared with the scrape endpoint's
/// suite rather than copied into each (one implementation); see it for why
/// there is no HTTP client crate in the dev-dependencies.
fn request(host: &str, path: &str, credentials: Option<&str>) -> (u16, String) {
    request_with(host, path, credentials, None)
}

/// The same, with an optional `Git-Protocol` header — the one thing that
/// selects protocol v2 over HTTP.
fn request_with(
    host: &str,
    path: &str,
    credentials: Option<&str>,
    git_protocol: Option<&str>,
) -> (u16, String) {
    common::http_request("GET", host, path, credentials, git_protocol)
}

// ───────────────────────────────────────────────────────────────────────────
// The request id (the half HTTP never had)
// ───────────────────────────────────────────────────────────────────────────

/// The value of `x-gunnar-request-id` in a raw HTTP/1.1 response, if any.
fn request_id_header(response: &str) -> Option<String> {
    common::http_header(response, "x-gunnar-request-id")
}

/// **Every HTTP response names the log line that produced it — including every
/// refusal.**
///
/// SSH has had a connection id on a span since the observability work. HTTP had
/// nothing: its
/// lines named a principal here, a path there, a repository somewhere else, and
/// the `git.upload_pack.served` line `gunnar-cli` emits for an HTTP transfer
/// named none of them. An operator holding a client's `403` had no way to find
/// the line that produced it.
///
/// Asserted on **four** responses that take four different paths out of
/// `call` — a served advertisement, a route refusal, an auth refusal and an
/// authorisation refusal — because the header is stamped once on the way out
/// and the claim is that no arm can escape it. And the four ids must all
/// DIFFER: one span per request, not per connection, or a keep-alive client's
/// clone and the `401` before it would file under one id.
///
/// Seen red: with the stamp moved from `call` into `streaming` (where it would
/// naturally have gone), the three refusals come back with no header at all and
/// this fails naming which one.
#[test]
fn every_http_response_carries_a_request_id_and_no_two_requests_share_one() {
    let harness = harness(HashKind::Sha1, Visibility::Public);
    let work = harness.dir.path().join("work");
    seed(&harness, &work);
    harness
        .git(
            &work,
            &["push", "-q", &harness.url("team", "project"), "--mirror"],
        )
        .expect_ok("seeding the server over ssh");
    let addr = harness.http_addr.expect("the http listener").to_string();

    let cases: [(&str, &str, Option<&str>, u16); 4] = [
        (
            "a served advertisement",
            "/team/project.git/info/refs?service=git-upload-pack",
            None,
            200,
        ),
        ("a route refusal", "/not/a/git/route", None, 404),
        (
            "an auth refusal",
            "/team/project.git/info/refs?service=git-upload-pack",
            Some("gunnar:not-a-token"),
            401,
        ),
        (
            "an authorisation refusal",
            "/team/private.git/info/refs?service=git-receive-pack",
            None,
            401,
        ),
    ];

    let mut seen: Vec<String> = Vec::new();
    for (what, path, credentials, expected) in cases {
        let (status, response) = request_with(&addr, path, credentials, None);
        assert_eq!(
            status,
            expected,
            "{what} came back {status}, so this case is not exercising the path it names:\n{}",
            response.chars().take(400).collect::<String>()
        );
        let id = request_id_header(&response).unwrap_or_else(|| {
            panic!(
                "{what} carried NO x-gunnar-request-id. An operator holding this response \
                 cannot name the server line that produced it, which is the whole point:\n{}",
                response.chars().take(400).collect::<String>()
            )
        });
        // The same rendering `conn=` uses on the SSH side — `RequestId`'s
        // own `Display`, so an operator greps one shape and not two.
        assert!(
            !id.is_empty() && id.chars().all(|c| c.is_ascii_hexdigit()),
            "{what} carried a request id of {id:?}, which is not a RequestId"
        );
        assert!(
            !seen.contains(&id),
            "{what} reused request id {id}; ids are per REQUEST, and a keep-alive connection \
             serves many. Already seen: {seen:?}"
        );
        seen.push(id);
    }
    assert_eq!(seen.len(), 4, "four requests, four ids: {seen:?}");
}