holger-znippy-python-repository 0.1.1

Holger guards your artifacts at rest. May Allfather Odin watch over every bit.
//! Holger python backend serve test through a REAL compressed archive built with
//! the python plugin. Proves `fetch` resolves `(name, version)` coords via the
//! typed view — a wheel and an sdist resolve by their authoritative coords, where
//! the old `.tar.gz` filename guess / substring-match would mis-resolve.

use std::fs;

use holger_znippy_python_repository::PipRepoZnippy;
use traits::{ArtifactId, RepositoryBackendTrait};
use znippy_common::plugin::PluginRegistry;
use znippy_compress::compress_dir;
use znippy_plugin_python::NativePythonPlugin;

fn crc32(data: &[u8]) -> u32 {
    let mut crc = 0xFFFF_FFFFu32;
    for &b in data {
        crc ^= b as u32;
        for _ in 0..8 {
            crc = if crc & 1 != 0 { (crc >> 1) ^ 0xEDB8_8320 } else { crc >> 1 };
        }
    }
    !crc
}

fn build_zip_stored(name: &str, data: &[u8]) -> Vec<u8> {
    let nb = name.as_bytes();
    let crc = crc32(data);
    let size = data.len() as u32;
    let mut out = Vec::new();
    let local = out.len() as u32;
    out.extend_from_slice(b"PK\x03\x04");
    out.extend_from_slice(&20u16.to_le_bytes());
    for _ in 0..3 { out.extend_from_slice(&0u16.to_le_bytes()); }
    out.extend_from_slice(&0u16.to_le_bytes());
    out.extend_from_slice(&crc.to_le_bytes());
    out.extend_from_slice(&size.to_le_bytes());
    out.extend_from_slice(&size.to_le_bytes());
    out.extend_from_slice(&(nb.len() as u16).to_le_bytes());
    out.extend_from_slice(&0u16.to_le_bytes());
    out.extend_from_slice(nb);
    out.extend_from_slice(data);
    let cd = out.len() as u32;
    out.extend_from_slice(b"PK\x01\x02");
    out.extend_from_slice(&20u16.to_le_bytes());
    out.extend_from_slice(&20u16.to_le_bytes());
    for _ in 0..4 { out.extend_from_slice(&0u16.to_le_bytes()); }
    out.extend_from_slice(&crc.to_le_bytes());
    out.extend_from_slice(&size.to_le_bytes());
    out.extend_from_slice(&size.to_le_bytes());
    out.extend_from_slice(&(nb.len() as u16).to_le_bytes());
    for _ in 0..3 { out.extend_from_slice(&0u16.to_le_bytes()); }
    out.extend_from_slice(&0u16.to_le_bytes());
    out.extend_from_slice(&0u32.to_le_bytes());
    out.extend_from_slice(&local.to_le_bytes());
    out.extend_from_slice(nb);
    let cd_size = out.len() as u32 - cd;
    out.extend_from_slice(b"PK\x05\x06");
    out.extend_from_slice(&0u16.to_le_bytes());
    out.extend_from_slice(&0u16.to_le_bytes());
    out.extend_from_slice(&1u16.to_le_bytes());
    out.extend_from_slice(&1u16.to_le_bytes());
    out.extend_from_slice(&cd_size.to_le_bytes());
    out.extend_from_slice(&cd.to_le_bytes());
    out.extend_from_slice(&0u16.to_le_bytes());
    out
}

#[test]
fn python_backend_serves_wheel_and_sdist_via_view() {
    let src = tempfile::tempdir().unwrap();
    let out = tempfile::tempdir().unwrap();

    // A real-zip wheel (so the plugin parses tags) + an sdist tarball.
    let wheel = build_zip_stored("numpy-1.26.0.dist-info/METADATA", b"Name: numpy\n");
    fs::create_dir_all(src.path().join("packages/numpy")).unwrap();
    fs::write(
        src.path().join("packages/numpy/numpy-1.26.0-cp311-cp311-manylinux_x86_64.whl"),
        &wheel,
    )
    .unwrap();

    let sdist = b"requests sdist tarball bytes".to_vec();
    fs::create_dir_all(src.path().join("packages/requests")).unwrap();
    fs::write(src.path().join("packages/requests/requests-2.31.0.tar.gz"), &sdist).unwrap();

    let archive = out.path().join("py.znippy");
    compress_dir(
        &src.path().to_path_buf(),
        &archive,
        false,
        Some(&PluginRegistry::with_plugin(Box::new(NativePythonPlugin))),
        None,
        None,
    )
    .expect("compress_dir with python plugin");

    let repo = PipRepoZnippy::with_archive("pip".into(), archive).unwrap();

    // fetch via the view → exact wheel bytes by (name, version).
    let wid = ArtifactId { namespace: None, name: "numpy".into(), version: "1.26.0".into() };
    assert_eq!(repo.fetch(&wid).unwrap().as_deref(), Some(wheel.as_slice()));

    // sdist resolves too — the old canonical `{name}-{version}.tar.gz` guess would
    // have worked here, but the view path is coord-authoritative regardless.
    let sid = ArtifactId { namespace: None, name: "requests".into(), version: "2.31.0".into() };
    assert_eq!(repo.fetch(&sid).unwrap().as_deref(), Some(sdist.as_slice()));

    // Absent → None.
    let absent = ArtifactId { namespace: None, name: "numpy".into(), version: "9.9.9".into() };
    assert_eq!(repo.fetch(&absent).unwrap(), None);
}