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

nordisk/facett

facett-ops/tests/archive_round_trip.rs

//! **RED FIRST for the publish's own verification.**
//!
//! `pack_index` returning `Ok` means the compressor did not error and wrote a
//! non-zero file. That is a state round-trip. The failure it cannot see is the one
//! that matters: an archive with a correct index and a short payload still has a
//! size, holger still serves it, and `pip` downloads a truncated wheel and fails its
//! own hash check with a message about the network — pointing at the client for a
//! defect in the publish.
//!
//! So `verify_archive_round_trip` extracts the finished archive and compares bytes.
//! These tests prove it actually catches the two ways that can go wrong, using REAL
//! znippy archives rather than a mock, because a mock of the compressor would be
//! testing the mock.

use std::path::PathBuf;
use facett_ops::holger::{pack_index, stage_index, verify_archive_round_trip};

fn scratch(tag: &str) -> PathBuf {
    let base = std::env::var("CARGO_TARGET_TMPDIR")
        .map(PathBuf::from)
        .unwrap_or_else(|_| std::env::temp_dir());
    let d = base.join(format!("roundtrip-{tag}"));
    let _ = std::fs::remove_dir_all(&d);
    std::fs::create_dir_all(&d).unwrap();
    d
}

/// A wheel-shaped payload big enough that the packer takes a real path through
/// chunking rather than a degenerate one.
fn payload(seed: u8) -> Vec<u8> {
    let mut v = b"PK\x03\x04".to_vec();
    v.extend((0..200_000u32).map(|i| (i as u8).wrapping_add(seed)));
    v
}

/// Stage + pack one artifact, returning `(archive, original)`.
fn packed(tag: &str, bytes: &[u8]) -> (PathBuf, PathBuf, PathBuf) {
    let dir = scratch(tag);
    let src = dir.join("in");
    std::fs::create_dir_all(&src).unwrap();
    let whl = src.join("facet_wrapped_web-0.1.19-py3-none-any.whl");
    std::fs::write(&whl, bytes).unwrap();

    let stage = dir.join("stage");
    stage_index(std::slice::from_ref(&whl), &stage).unwrap();
    let archive = dir.join("out").join("facett-pip.znippy");
    pack_index(&stage, &archive).unwrap();
    (archive, whl, dir)
}

// ── GREEN: a real archive round-trips byte for byte ────────────────────────

#[test]
fn a_healthy_archive_returns_the_exact_bytes_that_went_in() {
    let bytes = payload(0);
    let (archive, whl, dir) = packed("healthy", &bytes);

    let checked =
        verify_archive_round_trip(&archive, &[whl], &dir.join("extract")).expect("must verify");
    assert_eq!(checked.len(), 1);
    assert_eq!(checked[0].0, "packages/facet-wrapped-web/facet_wrapped_web-0.1.19-py3-none-any.whl");
    // The LENGTH is asserted against the input, not against itself.
    assert_eq!(checked[0].1, bytes.len() as u64);
}

// ── RED: the bytes came out different ──────────────────────────────────────

#[test]
fn a_single_flipped_byte_is_caught() {
    // Same length, one byte different — a size check alone passes this, and that is
    // precisely the corruption a "the file is there and non-empty" check cannot see.
    let bytes = payload(0);
    let (archive, _whl, dir) = packed("flipped", &bytes);

    let mut evil = bytes.clone();
    evil[123_456] ^= 0xff;
    let decoy = dir.join("decoy");
    std::fs::create_dir_all(&decoy).unwrap();
    let decoy_whl = decoy.join("facet_wrapped_web-0.1.19-py3-none-any.whl");
    std::fs::write(&decoy_whl, &evil).unwrap();

    let err = verify_archive_round_trip(&archive, &[decoy_whl], &dir.join("extract2"))
        .expect_err("differing bytes must be refused");
    assert!(err.contains("DIFFERENT bytes"), "{err}");
}

#[test]
fn a_length_mismatch_names_both_lengths() {
    let bytes = payload(0);
    let (archive, _whl, dir) = packed("short", &bytes);

    let short = dir.join("short-copy");
    std::fs::create_dir_all(&short).unwrap();
    let short_whl = short.join("facet_wrapped_web-0.1.19-py3-none-any.whl");
    std::fs::write(&short_whl, &bytes[..bytes.len() - 1000]).unwrap();

    let err = verify_archive_round_trip(&archive, &[short_whl], &dir.join("extract3"))
        .expect_err("a length mismatch must be refused");
    // Both numbers, or the reader cannot tell which side is short.
    assert!(err.contains(&bytes.len().to_string()), "must name the length that went in: {err}");
    assert!(err.contains(&(bytes.len() - 1000).to_string()), "must name what came out: {err}");
    assert!(err.contains("truncated"), "must say what this looks like downstream: {err}");
}

// ── RED: the artifact is not in the archive at all ─────────────────────────

#[test]
fn an_artifact_missing_from_the_archive_is_caught_by_name() {
    let (archive, _whl, dir) = packed("missing", &payload(0));

    // An artifact that was never packed. The check must not shrug and return the
    // one it did find — the count would still look plausible to a careless caller.
    let other = dir.join("other");
    std::fs::create_dir_all(&other).unwrap();
    let ghost = other.join("facet_wrapped_web-0.1.20-py3-none-any.whl");
    std::fs::write(&ghost, payload(1)).unwrap();

    let err = verify_archive_round_trip(&archive, &[ghost], &dir.join("extract4"))
        .expect_err("an absent artifact must be refused");
    assert!(err.contains("0.1.20"), "must name the artifact it could not find: {err}");
    assert!(err.contains("not in the extracted archive"), "{err}");
}

// ── RED: the archive itself is damaged ─────────────────────────────────────

#[test]
fn a_truncated_archive_does_not_verify() {
    let bytes = payload(0);
    let (archive, whl, dir) = packed("truncated", &bytes);

    // Chop the tail. This is the shape of an interrupted publish: a file with a
    // size, an index, and a payload that stops early.
    let full = std::fs::read(&archive).unwrap();
    let damaged = dir.join("out").join("damaged.znippy");
    std::fs::write(&damaged, &full[..full.len() / 2]).unwrap();

    let r = verify_archive_round_trip(&damaged, &[whl], &dir.join("extract5"));
    assert!(r.is_err(), "a half-written archive must not verify, got {r:?}");
}

#[test]
fn an_empty_pack_is_refused_rather_than_producing_an_empty_archive() {
    let dir = scratch("emptypack");
    let stage = dir.join("stage");
    std::fs::create_dir_all(&stage).unwrap();
    // Nothing staged. Whatever the compressor does with an empty tree, the publish
    // must not hand holger an index of no packages and call it done.
    let archive = dir.join("out").join("empty.znippy");
    let r = pack_index(&stage, &archive);
    if let Ok(n) = r {
        // If the compressor happily seals an empty tree, the round-trip check is the
        // backstop: verifying it against a real artifact must fail.
        let fake = dir.join("f.whl");
        std::fs::write(&fake, payload(0)).unwrap();
        let src = dir.join("named");
        std::fs::create_dir_all(&src).unwrap();
        let named = src.join("facet_wrapped_web-0.1.19-py3-none-any.whl");
        std::fs::rename(&fake, &named).unwrap();
        assert!(
            verify_archive_round_trip(&archive, &[named], &dir.join("ex")).is_err(),
            "an empty {n}-byte archive must not verify against a real artifact"
        );
    }
}