a73x

tests/release_server_test.rs

Ref:   Size: 33.2 KiB   History

mod common;

use std::process::Output;

use common::ServerHarness;

#[test]
fn ssh_client_interop_rejects_unknown_command() {
    let harness = ServerHarness::new("release-canary");
    harness.push_head();

    let output = harness.ssh_exec("frobnicate");
    assert!(
        !output.status.success(),
        "unknown exec command must fail, got: {}",
        String::from_utf8_lossy(&output.stdout)
    );

    // A bogus exec command failing isn't proof the interop worked - ssh itself
    // failing to connect/authenticate would also produce a non-zero exit. Rule
    // that out explicitly so this canary actually proves client<->server auth
    // and exec dispatch succeeded.
    let stderr = String::from_utf8_lossy(&output.stderr);
    assert!(
        !stderr.contains("Permission denied"),
        "ssh authentication failed, interop is broken: {}",
        stderr
    );
    assert!(
        !stderr.contains("Connection refused"),
        "ssh failed to connect, interop is broken: {}",
        stderr
    );
}

fn stdout(output: &Output) -> String {
    String::from_utf8_lossy(&output.stdout).to_string()
}

/// Pull a single header value out of a raw response head block, e.g.
/// `extract_header(&head, "etag")`. Case-insensitive on the header name.
fn extract_header(head: &str, name: &str) -> String {
    let prefix = format!("{}:", name.to_lowercase());
    head.lines()
        .find(|l| l.to_lowercase().starts_with(&prefix))
        .unwrap_or_else(|| panic!("missing header {} in:\n{}", name, head))
        .split_once(':')
        .unwrap()
        .1
        .trim()
        .to_string()
}

/// Every header line except `Date` (which legitimately varies request to
/// request), for comparing that two responses are otherwise byte-identical.
fn strip_date_header(head: &str) -> String {
    head.lines()
        .filter(|l| !l.to_lowercase().starts_with("date:"))
        .collect::<Vec<_>>()
        .join("\n")
}

/// The exact HTTP status line (e.g. `"HTTP/1.1 200 OK"`), so a caller can't
/// be fooled by another header happening to contain the same digits as the
/// status code it's checking for.
fn status_line(head: &str) -> &str {
    head.lines().next().unwrap_or("")
}

fn assert_ssh_error(output: &Output, needle: &str) {
    assert!(!output.status.success(), "expected failure, got success");
    let all = format!(
        "{}{}",
        String::from_utf8_lossy(&output.stdout),
        String::from_utf8_lossy(&output.stderr)
    );
    assert!(
        all.contains(needle),
        "expected '{}' in output: {}",
        needle,
        all
    );
}

#[test]
fn upload_stores_file_with_checksum() {
    let harness = ServerHarness::new("release-upload");
    harness.push_head();

    let content = b"fake tarball bytes";
    let output = harness.ssh_exec_with_stdin(
        "collab-release upload 'release-upload.git' 'v1.0.0' 'app.tar.gz'",
        content,
    );
    assert!(output.status.success(), "upload failed: {:?}", output);

    // reply is "ok <sha256>"
    let reply = stdout(&output);
    let sha = reply
        .trim()
        .strip_prefix("ok ")
        .expect("reply not 'ok <sha>'");

    use sha2::Digest;
    let expected: String = sha2::Sha256::digest(content)
        .iter()
        .map(|b| format!("{:02x}", b))
        .collect();
    assert_eq!(sha, expected);

    let stored = harness
        .repos_dir()
        .join("release-upload.git/collab/releases/v1.0.0/app.tar.gz");
    assert_eq!(std::fs::read(&stored).unwrap(), content);
    assert!(stored.with_file_name("app.tar.gz.sha256").exists());
}

#[test]
fn duplicate_upload_needs_force() {
    let harness = ServerHarness::new("release-dup");
    harness.push_head();
    let cmd = "collab-release upload 'release-dup.git' 'v1' 'a.tar.gz'";

    assert!(harness.ssh_exec_with_stdin(cmd, b"one").status.success());
    let dup = harness.ssh_exec_with_stdin(cmd, b"two");
    assert_ssh_error(&dup, "already exists");

    let forced = harness.ssh_exec_with_stdin(
        "collab-release upload 'release-dup.git' 'v1' 'a.tar.gz' --force",
        b"two",
    );
    assert!(
        forced.status.success(),
        "forced upload failed: {:?}",
        forced
    );
    let stored = harness
        .repos_dir()
        .join("release-dup.git/collab/releases/v1/a.tar.gz");
    assert_eq!(std::fs::read(&stored).unwrap(), b"two");
}

#[test]
fn list_returns_json_index() {
    let harness = ServerHarness::new("release-list");
    harness.push_head();

    harness.ssh_exec_with_stdin(
        "collab-release upload 'release-list.git' 'v1.0.0' 'a.tar.gz'",
        b"aaa",
    );
    let output = harness.ssh_exec("collab-release list 'release-list.git'");
    assert!(output.status.success());

    let index: serde_json::Value = serde_json::from_str(&stdout(&output)).unwrap();
    let versions = index["versions"].as_array().unwrap();
    assert_eq!(versions.len(), 1);
    assert_eq!(versions[0]["version"], "v1.0.0");
    assert_eq!(versions[0]["files"][0]["name"], "a.tar.gz");
    assert_eq!(versions[0]["files"][0]["size"], 3);
    assert_eq!(
        versions[0]["files"][0]["sha256"].as_str().unwrap().len(),
        64
    );
}

#[test]
fn delete_removes_file_then_version() {
    let harness = ServerHarness::new("release-del");
    harness.push_head();

    harness.ssh_exec_with_stdin(
        "collab-release upload 'release-del.git' 'v1' 'a.tar.gz'",
        b"a",
    );
    harness.ssh_exec_with_stdin(
        "collab-release upload 'release-del.git' 'v1' 'b.tar.gz'",
        b"b",
    );

    let releases = harness.repos_dir().join("release-del.git/collab/releases");

    let del_file = harness.ssh_exec("collab-release delete 'release-del.git' 'v1' 'a.tar.gz'");
    assert!(del_file.status.success());
    assert!(!releases.join("v1/a.tar.gz").exists());
    assert!(releases.join("v1/b.tar.gz").exists());

    let del_version = harness.ssh_exec("collab-release delete 'release-del.git' 'v1'");
    assert!(del_version.status.success());
    assert!(!releases.join("v1").exists());

    let missing = harness.ssh_exec("collab-release delete 'release-del.git' 'v1'");
    assert_ssh_error(&missing, "not found");
}

#[test]
fn write_policy_gates_upload_and_delete_but_not_list() {
    let harness = ServerHarness::new("release-policy");
    harness.push_head();

    // Seed a release while the policy is still permissive, so the read-only
    // assertions below have something real to observe. Without this, "list
    // succeeds" and "delete was denied" would both be vacuously true against
    // an empty releases dir.
    let seeded = harness.ssh_exec_with_stdin(
        "collab-release upload 'release-policy.git' 'v1' 'a.tar.gz'",
        b"seed",
    );
    assert!(seeded.status.success(), "seed upload failed: {:?}", seeded);

    harness.write_repo_server_policy(
        "visibility = \"public\"\n[access]\nread = [\"*\"]\nwrite = []\n",
    );

    // Denied writes report "repository not found" - unknown and unauthorized
    // deliberately share a reply so the error can't be used to probe which
    // repos exist.
    let upload = harness.ssh_exec_with_stdin(
        "collab-release upload 'release-policy.git' 'v2' 'b.tar.gz'",
        b"x",
    );
    assert_ssh_error(&upload, "repository not found");

    let delete = harness.ssh_exec("collab-release delete 'release-policy.git' 'v1'");
    assert_ssh_error(&delete, "repository not found");

    let list = harness.ssh_exec("collab-release list 'release-policy.git'");
    assert!(list.status.success(), "list must be allowed for readers");

    // The seeded release is still listed: proof the denied delete didn't run.
    let index: serde_json::Value = serde_json::from_str(&stdout(&list)).unwrap();
    let versions = index["versions"].as_array().unwrap();
    assert_eq!(versions.len(), 1, "seeded release must survive: {}", index);
    assert_eq!(versions[0]["version"], "v1");

    // And on disk: the seed is intact, the denied upload left nothing behind.
    let releases = harness
        .repos_dir()
        .join("release-policy.git/collab/releases");
    assert_eq!(
        std::fs::read(releases.join("v1/a.tar.gz")).unwrap(),
        b"seed"
    );
    assert!(!releases.join("v2/b.tar.gz").exists());
    assert!(!releases.join("v2").exists());
}

#[test]
fn git_push_and_clone_over_ssh() {
    // Exercises exec_request's git branch, stdin forwarding through data(),
    // and channel_eof against a real git client - the path the release
    // dispatch had to be threaded through without regressing.
    let harness = ServerHarness::new("release-git");
    let ssh_command = harness.ssh_command_string();

    harness.work_repo().git(&[
        "-c",
        &format!("core.sshCommand={}", ssh_command),
        "push",
        &harness.repo_ssh_url(),
        "main",
    ]);

    let expected = harness.work_repo().git(&["rev-parse", "HEAD"]);
    let expected = expected.trim();

    let clone_dir = tempfile::TempDir::new().unwrap();
    let dest = clone_dir.path().join("clone");
    // Reuse the work repo's isolated HOME so the developer's real git config
    // can't influence the clone.
    let mut clone_cmd = std::process::Command::new("git");
    harness.work_repo().apply_env(&mut clone_cmd);
    let output = clone_cmd
        .args([
            "-c",
            &format!("core.sshCommand={}", ssh_command),
            "clone",
            &harness.repo_ssh_url(),
            dest.to_str().unwrap(),
        ])
        .output()
        .expect("failed to run git clone");
    assert!(
        output.status.success(),
        "clone over ssh failed: {}",
        String::from_utf8_lossy(&output.stderr)
    );

    let mut head_cmd = std::process::Command::new("git");
    harness.work_repo().apply_env(&mut head_cmd);
    let cloned_head = head_cmd
        .args(["rev-parse", "HEAD"])
        .current_dir(&dest)
        .output()
        .expect("failed to run git rev-parse in clone");
    assert_eq!(
        String::from_utf8_lossy(&cloned_head.stdout).trim(),
        expected,
        "cloned repo is not at the pushed commit"
    );
}

#[test]
fn oversize_upload_rejected_without_partial_file() {
    let harness = ServerHarness::new_with_extra_config("release-size", "max_release_size = 16\n");
    harness.push_head();

    let output = harness.ssh_exec_with_stdin(
        "collab-release upload 'release-size.git' 'v1' 'big.tar.gz'",
        &[0u8; 64],
    );
    assert_ssh_error(&output, "maximum release size");

    let version_dir = harness
        .repos_dir()
        .join("release-size.git/collab/releases/v1");
    assert!(!version_dir.join("big.tar.gz").exists());
    if version_dir.exists() {
        assert_eq!(std::fs::read_dir(&version_dir).unwrap().count(), 0);
    }
}

#[test]
fn invalid_names_and_unknown_repo_rejected() {
    let harness = ServerHarness::new("release-invalid");
    harness.push_head();

    let traversal = harness.ssh_exec_with_stdin(
        "collab-release upload 'release-invalid.git' '../evil' 'a.tar.gz'",
        b"x",
    );
    assert!(!traversal.status.success());

    let unknown =
        harness.ssh_exec_with_stdin("collab-release upload 'nope.git' 'v1' 'a.tar.gz'", b"x");
    assert!(!unknown.status.success());
}

#[test]
fn http_releases_page_and_download() {
    let harness = ServerHarness::new("release-http");
    harness.push_head();

    let content: Vec<u8> = (0u32..600).flat_map(|i| i.to_le_bytes()).collect(); // binary body
    harness.ssh_exec_with_stdin(
        "collab-release upload 'release-http.git' 'v2.0.0' 'app.tar.gz'",
        &content,
    );

    let page = harness.get_ok("/release-http/releases");
    assert!(page.body.contains("v2.0.0"));
    assert!(page.body.contains("app.tar.gz"));
    assert!(page
        .body
        .contains("/release-http/releases/v2.0.0/app.tar.gz"));

    let (head, body) = harness.get_bytes("/release-http/releases/v2.0.0/app.tar.gz");
    assert!(head.contains("200"), "download failed: {}", head);
    assert!(head.to_lowercase().contains("application/octet-stream"));
    assert!(head
        .to_lowercase()
        .contains(&format!("content-length: {}", content.len())));
    assert_eq!(body, content);

    // checksum companion is downloadable as text
    let (sha_head, sha_body) = harness.get_bytes("/release-http/releases/v2.0.0/app.tar.gz.sha256");
    assert!(sha_head.contains("200"));
    assert!(String::from_utf8(sha_body).unwrap().contains("app.tar.gz"));
}

#[test]
fn http_release_download_404s() {
    let harness = ServerHarness::new("release-http-404");
    harness.push_head();

    let missing = harness.get("/release-http-404/releases/v9/none.tar.gz");
    assert!(missing.status_line.contains("404"));

    let traversal = harness.get("/release-http-404/releases/v9/..%2f..%2fconfig");
    assert!(!traversal.status_line.contains("200"));

    let page = harness.get_ok("/release-http-404/releases");
    assert!(page.body.contains("No releases"));
}

#[test]
fn http_releases_respect_repo_policy() {
    let harness = ServerHarness::new("release-http-private");
    harness.push_head();
    harness.ssh_exec_with_stdin(
        "collab-release upload 'release-http-private.git' 'v1' 'a.tar.gz'",
        b"secret",
    );
    harness.write_repo_server_policy(
        "visibility = \"private\"\n[ui]\nanonymous = false\n[http]\nanonymous_clone = false\n[access]\nread = [\"*\"]\nwrite = [\"*\"]\n",
    );

    let page = harness.get("/release-http-private/releases");
    assert!(page.status_line.contains("404"));

    let (head, _) = harness.get_bytes("/release-http-private/releases/v1/a.tar.gz");
    assert!(
        head.contains("404"),
        "private artifact must not be served: {}",
        head
    );
}

/// The releases page is gated by allows_anonymous_ui; artifact downloads are
/// gated by allows_anonymous_http. These are separate gates: a policy that
/// allows the UI page but disables anonymous clone/download must still let
/// the page render (listing versions) while 404ing the actual download. This
/// pins the distinction so `release_download` can't accidentally be changed
/// to use `allows_anonymous_ui` instead of `allows_anonymous_http`.
#[test]
fn http_release_page_and_download_gates_are_independent() {
    let harness = ServerHarness::new("release-http-gates");
    harness.push_head();
    harness.ssh_exec_with_stdin(
        "collab-release upload 'release-http-gates.git' 'v1' 'a.tar.gz'",
        b"gated",
    );
    harness.write_repo_server_policy(
        "visibility = \"public\"\n[ui]\nanonymous = true\n[http]\nanonymous_clone = false\n[access]\nread = [\"*\"]\nwrite = [\"*\"]\n",
    );

    let page = harness.get_ok("/release-http-gates/releases");
    assert!(page.body.contains("v1"));
    assert!(page.body.contains("a.tar.gz"));

    let (head, _) = harness.get_bytes("/release-http-gates/releases/v1/a.tar.gz");
    assert!(
        head.contains("404"),
        "download must stay gated by allows_anonymous_http: {}",
        head
    );
}

#[test]
fn http_release_range_request_returns_partial_content() {
    let harness = ServerHarness::new("release-range");
    harness.push_head();
    let content: Vec<u8> = (0u16..=255).map(|b| b as u8).collect(); // 256 deterministic bytes
    harness.ssh_exec_with_stdin(
        "collab-release upload 'release-range.git' 'v1' 'app.bin'",
        &content,
    );

    let (head, body) = harness.get_with_headers(
        "/release-range/releases/v1/app.bin",
        &[("Range", "bytes=10-19")],
    );
    assert!(head.contains("206"), "expected 206, got: {}", head);
    assert!(
        head.to_lowercase()
            .contains("content-range: bytes 10-19/256"),
        "missing/wrong content-range: {}",
        head
    );
    assert!(
        head.to_lowercase().contains("content-length: 10"),
        "wrong content-length: {}",
        head
    );
    assert_eq!(body, content[10..20]);
}

#[test]
fn http_release_range_suffix_request() {
    let harness = ServerHarness::new("release-range-suffix");
    harness.push_head();
    let content: Vec<u8> = (0u16..=255).map(|b| b as u8).collect();
    harness.ssh_exec_with_stdin(
        "collab-release upload 'release-range-suffix.git' 'v1' 'app.bin'",
        &content,
    );

    let (head, body) = harness.get_with_headers(
        "/release-range-suffix/releases/v1/app.bin",
        &[("Range", "bytes=-10")],
    );
    assert!(head.contains("206"), "expected 206, got: {}", head);
    assert!(
        head.to_lowercase()
            .contains("content-range: bytes 246-255/256"),
        "missing/wrong content-range: {}",
        head
    );
    assert_eq!(body, content[246..256]);
}

#[test]
fn http_release_range_open_ended_request() {
    let harness = ServerHarness::new("release-range-open");
    harness.push_head();
    let content: Vec<u8> = (0u16..=255).map(|b| b as u8).collect();
    harness.ssh_exec_with_stdin(
        "collab-release upload 'release-range-open.git' 'v1' 'app.bin'",
        &content,
    );

    let (head, body) = harness.get_with_headers(
        "/release-range-open/releases/v1/app.bin",
        &[("Range", "bytes=250-")],
    );
    assert!(head.contains("206"), "expected 206, got: {}", head);
    assert!(
        head.to_lowercase()
            .contains("content-range: bytes 250-255/256"),
        "missing/wrong content-range: {}",
        head
    );
    assert_eq!(body, content[250..256]);
}

#[test]
fn http_release_range_out_of_range_returns_416() {
    let harness = ServerHarness::new("release-range-416");
    harness.push_head();
    let content: Vec<u8> = (0u16..=255).map(|b| b as u8).collect();
    harness.ssh_exec_with_stdin(
        "collab-release upload 'release-range-416.git' 'v1' 'app.bin'",
        &content,
    );

    let (head, _) = harness.get_with_headers(
        "/release-range-416/releases/v1/app.bin",
        &[("Range", "bytes=1000-2000")],
    );
    assert!(head.contains("416"), "expected 416, got: {}", head);
    assert!(
        head.to_lowercase().contains("content-range: bytes */256"),
        "missing/wrong content-range: {}",
        head
    );
}

#[test]
fn http_release_range_malformed_or_multi_falls_back_to_full_response() {
    let harness = ServerHarness::new("release-range-bad");
    harness.push_head();
    let content: Vec<u8> = (0u16..=255).map(|b| b as u8).collect();
    harness.ssh_exec_with_stdin(
        "collab-release upload 'release-range-bad.git' 'v1' 'app.bin'",
        &content,
    );

    // Multi-range: ignore the header, serve the full 200 (always spec-legal).
    let (head, body) = harness.get_with_headers(
        "/release-range-bad/releases/v1/app.bin",
        &[("Range", "bytes=0-9,20-29")],
    );
    assert!(
        head.contains("200"),
        "expected full 200 for multi-range: {}",
        head
    );
    assert_eq!(body, content);

    // Malformed: same fallback.
    let (head2, body2) = harness.get_with_headers(
        "/release-range-bad/releases/v1/app.bin",
        &[("Range", "not-a-range")],
    );
    assert!(
        head2.contains("200"),
        "expected full 200 for malformed range: {}",
        head2
    );
    assert_eq!(body2, content);
}

#[test]
fn http_release_download_advertises_accept_ranges() {
    let harness = ServerHarness::new("release-accept-ranges");
    harness.push_head();
    harness.ssh_exec_with_stdin(
        "collab-release upload 'release-accept-ranges.git' 'v1' 'a.tar.gz'",
        b"hello world",
    );

    let (head, _) = harness.get_bytes("/release-accept-ranges/releases/v1/a.tar.gz");
    assert!(head.contains("200"));
    assert!(
        head.to_lowercase().contains("accept-ranges: bytes"),
        "missing accept-ranges: {}",
        head
    );
}

#[test]
fn http_release_conditional_get_returns_304() {
    let harness = ServerHarness::new("release-etag");
    harness.push_head();
    harness.ssh_exec_with_stdin(
        "collab-release upload 'release-etag.git' 'v1' 'a.tar.gz'",
        b"hello world",
    );

    let (head, _) = harness.get_bytes("/release-etag/releases/v1/a.tar.gz");
    assert!(head.contains("200"));
    assert!(
        head.to_lowercase().contains("etag:"),
        "missing etag: {}",
        head
    );
    assert!(
        head.to_lowercase().contains("last-modified:"),
        "missing last-modified: {}",
        head
    );
    assert!(
        head.to_lowercase().contains("cache-control:"),
        "missing cache-control: {}",
        head
    );
    let etag = extract_header(&head, "etag");

    let (head2, body2) = harness.get_with_headers(
        "/release-etag/releases/v1/a.tar.gz",
        &[("If-None-Match", &etag)],
    );
    assert!(head2.contains("304"), "expected 304, got: {}", head2);
    assert!(body2.is_empty(), "304 must not have a body: {:?}", body2);
}

#[test]
fn http_release_if_none_match_weak_comparison_matches() {
    // RFC 7232 §2.3.2: If-None-Match uses weak comparison, so a client that
    // stored our strong ETag but replays it with a "W/" prefix (or a client
    // that legitimately received a weak tag from an intermediary) must still
    // get a 304, not a spurious full re-download.
    let harness = ServerHarness::new("release-weak-inm");
    harness.push_head();
    harness.ssh_exec_with_stdin(
        "collab-release upload 'release-weak-inm.git' 'v1' 'a.tar.gz'",
        b"hello world",
    );

    let (head, _) = harness.get_bytes("/release-weak-inm/releases/v1/a.tar.gz");
    let etag = extract_header(&head, "etag");
    let weak_etag = format!("W/{}", etag);

    let (head2, body2) = harness.get_with_headers(
        "/release-weak-inm/releases/v1/a.tar.gz",
        &[("If-None-Match", &weak_etag)],
    );
    assert!(
        head2.contains("304"),
        "expected 304 for weak-compared matching etag, got: {}",
        head2
    );
    assert!(body2.is_empty());
}

#[test]
fn http_release_if_modified_since_round_trip() {
    let harness = ServerHarness::new("release-ims");
    harness.push_head();
    harness.ssh_exec_with_stdin(
        "collab-release upload 'release-ims.git' 'v1' 'a.tar.gz'",
        b"hello world",
    );

    let (head, _) = harness.get_bytes("/release-ims/releases/v1/a.tar.gz");
    assert!(head.contains("200"));
    let last_modified = extract_header(&head, "last-modified");

    let (head2, body2) = harness.get_with_headers(
        "/release-ims/releases/v1/a.tar.gz",
        &[("If-Modified-Since", &last_modified)],
    );
    assert!(head2.contains("304"), "expected 304, got: {}", head2);
    assert!(body2.is_empty(), "304 must not have a body: {:?}", body2);
}

#[test]
fn http_release_range_single_byte_at_start() {
    let harness = ServerHarness::new("release-range-single");
    harness.push_head();
    let content: Vec<u8> = (0u16..=255).map(|b| b as u8).collect();
    harness.ssh_exec_with_stdin(
        "collab-release upload 'release-range-single.git' 'v1' 'app.bin'",
        &content,
    );

    let (head, body) = harness.get_with_headers(
        "/release-range-single/releases/v1/app.bin",
        &[("Range", "bytes=0-0")],
    );
    assert!(head.contains("206"), "expected 206, got: {}", head);
    assert!(
        head.to_lowercase().contains("content-range: bytes 0-0/256"),
        "missing/wrong content-range: {}",
        head
    );
    assert_eq!(body, vec![content[0]]);
}

#[test]
fn http_release_zero_length_artifact() {
    let harness = ServerHarness::new("release-zero-len");
    harness.push_head();
    harness.ssh_exec_with_stdin(
        "collab-release upload 'release-zero-len.git' 'v1' 'empty.bin'",
        b"",
    );

    // A normal GET on a zero-length file is a 200 with an empty body.
    let (head, body) = harness.get_bytes("/release-zero-len/releases/v1/empty.bin");
    assert!(head.contains("200"), "expected 200, got: {}", head);
    assert!(body.is_empty());

    // Any byte range on a zero-length file is unsatisfiable.
    let (range_head, _) = harness.get_with_headers(
        "/release-zero-len/releases/v1/empty.bin",
        &[("Range", "bytes=0-0")],
    );
    assert!(
        range_head.contains("416"),
        "expected 416, got: {}",
        range_head
    );
    assert!(
        range_head
            .to_lowercase()
            .contains("content-range: bytes */0"),
        "missing/wrong content-range: {}",
        range_head
    );
}

/// Reproduces the review finding directly: a client fetches the first half
/// of an artifact, the artifact is `--force`-replaced with different bytes
/// of the same length, and the client resumes presenting the validator it
/// still holds via `If-Range`. The server must detect the mismatch and
/// serve the full, current artifact rather than splicing old and new bytes
/// into a single response.
#[test]
fn http_release_if_range_etag_falls_back_to_full_after_force_replace() {
    let harness = ServerHarness::new("release-if-range-stale");
    harness.push_head();
    let original: Vec<u8> = (0u8..20).collect();
    harness.ssh_exec_with_stdin(
        "collab-release upload 'release-if-range-stale.git' 'v1' 'app.bin'",
        &original,
    );

    let (head1, body1) = harness.get_with_headers(
        "/release-if-range-stale/releases/v1/app.bin",
        &[("Range", "bytes=0-9")],
    );
    assert!(head1.contains("206"), "expected 206, got: {}", head1);
    assert_eq!(body1, original[0..10]);
    let stale_etag = extract_header(&head1, "etag");

    let replaced: Vec<u8> = (100u8..120).collect();
    let force = harness.ssh_exec_with_stdin(
        "collab-release upload 'release-if-range-stale.git' 'v1' 'app.bin' --force",
        &replaced,
    );
    assert!(force.status.success(), "force upload failed: {:?}", force);

    let (head2, body2) = harness.get_with_headers(
        "/release-if-range-stale/releases/v1/app.bin",
        &[("Range", "bytes=10-19"), ("If-Range", &stale_etag)],
    );
    assert_eq!(
        status_line(&head2),
        "HTTP/1.1 200 OK",
        "expected a full 200 when If-Range is stale (never splice), got: {}",
        head2
    );
    assert_eq!(
        body2, replaced,
        "must serve the full current artifact, not a splice of old+new bytes"
    );

    // Sanity: the "assembled from a stale resume" bytes really would not
    // have matched anything real, proving this isn't a vacuous check.
    let mut spliced = original[0..10].to_vec();
    spliced.extend_from_slice(&replaced[10..20]);
    assert_ne!(body2, spliced);
}

#[test]
fn http_release_if_range_etag_matching_honors_range() {
    let harness = ServerHarness::new("release-if-range-match");
    harness.push_head();
    let content: Vec<u8> = (0u8..20).collect();
    harness.ssh_exec_with_stdin(
        "collab-release upload 'release-if-range-match.git' 'v1' 'app.bin'",
        &content,
    );

    let (head1, _) = harness.get_bytes("/release-if-range-match/releases/v1/app.bin");
    let etag = extract_header(&head1, "etag");

    let (head2, body2) = harness.get_with_headers(
        "/release-if-range-match/releases/v1/app.bin",
        &[("Range", "bytes=10-19"), ("If-Range", &etag)],
    );
    assert!(
        head2.contains("206"),
        "expected 206 when If-Range matches, got: {}",
        head2
    );
    assert_eq!(body2, content[10..20]);
}

#[test]
fn http_release_if_range_date_form_match_honors_range() {
    let harness = ServerHarness::new("release-if-range-date-match");
    harness.push_head();
    let content: Vec<u8> = (0u8..20).collect();
    harness.ssh_exec_with_stdin(
        "collab-release upload 'release-if-range-date-match.git' 'v1' 'app.bin'",
        &content,
    );

    let (head1, _) = harness.get_bytes("/release-if-range-date-match/releases/v1/app.bin");
    let last_modified = extract_header(&head1, "last-modified");

    let (head2, body2) = harness.get_with_headers(
        "/release-if-range-date-match/releases/v1/app.bin",
        &[("Range", "bytes=10-19"), ("If-Range", &last_modified)],
    );
    assert!(
        head2.contains("206"),
        "expected 206 when If-Range date matches, got: {}",
        head2
    );
    assert_eq!(body2, content[10..20]);
}

#[test]
fn http_release_if_range_date_form_mismatch_falls_back_to_full() {
    let harness = ServerHarness::new("release-if-range-date-bad");
    harness.push_head();
    let content: Vec<u8> = (0u8..20).collect();
    harness.ssh_exec_with_stdin(
        "collab-release upload 'release-if-range-date-bad.git' 'v1' 'app.bin'",
        &content,
    );

    let (head, body) = harness.get_with_headers(
        "/release-if-range-date-bad/releases/v1/app.bin",
        &[
            ("Range", "bytes=10-19"),
            ("If-Range", "Sun, 06 Nov 1994 08:49:37 GMT"),
        ],
    );
    assert_eq!(
        status_line(&head),
        "HTTP/1.1 200 OK",
        "expected full 200 for a non-matching If-Range date, got: {}",
        head
    );
    assert_eq!(body, content);
}

/// Byte-identical 404s for policy-denied repos is a deliberate security
/// property (private repos must not be distinguishable from missing ones).
/// This pins that the property survives regardless of which conditional or
/// range headers a probing client sends — currently guarded only by the
/// early-return ordering in `release_download`, which a refactor could move.
///
/// The comparison baseline is a repo that was never created at all (`None`
/// out of `crate::repos::resolve`), not another response from the denied
/// repo itself — the property under test is "denied is indistinguishable
/// from absent," and only an independently-absent repo proves that.
///
/// The conditional-header variants are chosen to include the two that can
/// actually produce a *different* status code (304) if the policy gate ever
/// moved below the conditional-GET check: `If-None-Match: *` unconditionally
/// matches any existing resource, and a future `If-Modified-Since` is always
/// satisfied. `"whatever"`/a fixed past date can never yield anything but
/// a plain miss, so on their own they can't detect that particular
/// regression — both kinds are included so this test still means something
/// if either check is later changed.
#[test]
fn http_release_download_404_is_identical_regardless_of_conditional_headers() {
    let harness = ServerHarness::new("release-404-headers");
    harness.push_head();
    harness.ssh_exec_with_stdin(
        "collab-release upload 'release-404-headers.git' 'v1' 'a.tar.gz'",
        b"secret",
    );
    harness.write_repo_server_policy(
        "visibility = \"private\"\n[ui]\nanonymous = false\n[http]\nanonymous_clone = false\n[access]\nread = [\"*\"]\nwrite = [\"*\"]\n",
    );

    let denied_path = "/release-404-headers/releases/v1/a.tar.gz";
    // This repo was never created; resolve() returns None for it regardless
    // of any conditional header, which is exactly the ground truth "absent"
    // response the denied repo must stay indistinguishable from.
    let nonexistent_path = "/release-404-headers-does-not-exist/releases/v1/a.tar.gz";

    let (absent_head, absent_body) = harness.get_bytes(nonexistent_path);
    assert_eq!(status_line(&absent_head), "HTTP/1.1 404 Not Found");

    let variants: &[&[(&str, &str)]] = &[
        &[("Range", "bytes=0-9")],
        &[("If-None-Match", "\"whatever\"")],
        &[("If-None-Match", "*")],
        &[("If-Modified-Since", "Sun, 06 Nov 1994 08:49:37 GMT")],
        // A real Monday: chrono's `%a` validates the weekday name, so a
        // wrong day would silently fail to parse and make this variant
        // vacuous (indistinguishable from the past-date case above).
        &[("If-Modified-Since", "Mon, 01 Jan 2035 00:00:00 GMT")],
        &[("If-Range", "\"whatever\"")],
    ];
    for headers in variants {
        let (head, body) = harness.get_with_headers(denied_path, headers);
        assert_eq!(
            status_line(&head),
            "HTTP/1.1 404 Not Found",
            "denied repo status differs from absent for {:?}: {}",
            headers,
            head
        );
        assert_eq!(
            strip_date_header(&head),
            strip_date_header(&absent_head),
            "denied-repo response headers differ from a nonexistent repo's for {:?}",
            headers
        );
        assert_eq!(
            body, absent_body,
            "denied-repo body differs from a nonexistent repo's for {:?}",
            headers
        );
    }
}

#[test]
fn http_release_download_sets_nosniff() {
    let harness = ServerHarness::new("release-nosniff");
    harness.push_head();
    harness.ssh_exec_with_stdin(
        "collab-release upload 'release-nosniff.git' 'v1' 'a.tar.gz'",
        b"hello",
    );

    let (head, _) = harness.get_bytes("/release-nosniff/releases/v1/a.tar.gz");
    assert!(
        head.to_lowercase()
            .contains("x-content-type-options: nosniff"),
        "missing nosniff header: {}",
        head
    );
}

#[test]
fn http_releases_page_hides_download_links_when_downloads_disabled() {
    let harness = ServerHarness::new("release-deadlink");
    harness.push_head();
    harness.ssh_exec_with_stdin(
        "collab-release upload 'release-deadlink.git' 'v1' 'a.tar.gz'",
        b"content",
    );
    harness.write_repo_server_policy(
        "visibility = \"public\"\n[ui]\nanonymous = true\n[http]\nanonymous_clone = false\n[access]\nread = [\"*\"]\nwrite = [\"*\"]\n",
    );

    let page = harness.get_ok("/release-deadlink/releases");
    assert!(page.body.contains("v1"));
    assert!(page.body.contains("a.tar.gz"));
    assert!(
        !page
            .body
            .contains("href=\"/release-deadlink/releases/v1/a.tar.gz\""),
        "download link must not be rendered when downloads are disabled:\n{}",
        page.body
    );
    let lower = page.body.to_lowercase();
    assert!(
        lower.contains("not") && lower.contains("available"),
        "expected a note that downloads are unavailable:\n{}",
        page.body
    );
}