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

nordisk/holger

server/index-rows/src/tests.rs

use super::*;

fn rpm() -> IndexRow {
    IndexRow::Rpm {
        epoch: Some("1".into()),
        summary: Some("a demo".into()),
        license: Some("MIT".into()),
        url: None,
        vendor: None,
        sourcerpm: None,
        provides: vec!["hello = 1.0".into()],
        requires: vec![],
    }
}

fn honest() -> Vec<IndexRow> {
    vec![
        IndexRow::Deb {
            control: "Package: hello\nVersion: 1.0\nArchitecture: amd64\nDescription: hi\n there\n"
                .into(),
        },
        rpm(),
        IndexRow::Helm {
            name: "demo".into(),
            version: "0.1.0".into(),
            app_version: Some("1.16.0".into()),
            description: None,
        },
        IndexRow::Wheel {
            metadata: Some(b"Metadata-Version: 2.1\nName: demo\n\xff".to_vec()),
            sha256: Some("ab".repeat(32)),
        },
        IndexRow::Wheel {
            metadata: None,
            sha256: None,
        },
        IndexRow::Npm {
            manifest: serde_json::json!({"name": "left-pad", "version": "1.3.0"}),
            integrity: format!("sha512-{}", "A".repeat(86) + "=="),
        },
        IndexRow::Maven {
            group: "org.demo".into(),
            artifact: "demo".into(),
            version: "1.0".into(),
            file: "demo-1.0.jar".into(),
        },
        IndexRow::Go {
            module: "example.com/demo".into(),
            version: "v1.0.0".into(),
        },
    ]
}

#[test]
fn every_honest_row_passes_and_reads_back_the_same() {
    for row in honest() {
        row.check()
            .unwrap_or_else(|e| panic!("{}: {e}", row.kind()));
        let s = StoredRow::new(&"cd".repeat(32), 42, row.clone());
        let back = StoredRow::parse(&serde_json::to_vec(&s).unwrap(), &"cd".repeat(32)).unwrap();
        assert_eq!(back, s, "{}", row.kind());
    }
}

#[test]
fn a_row_out_of_its_bounds_is_refused_by_the_field_it_breaks() {
    let big = "x".repeat(MAX_ROW + 1);
    let cases: Vec<(&str, IndexRow)> = vec![
        (
            "control",
            IndexRow::Deb {
                control: big.clone(),
            },
        ),
        (
            "control",
            IndexRow::Deb {
                control: "Package: a\u{1b}[2J\n".into(),
            },
        ),
        (
            "summary",
            rpm_with(|s, _| *s = Some("x".repeat(MAX_TEXT + 1))),
        ),
        (
            "requires",
            rpm_with(|_, r| *r = vec!["x".into(); MAX_DEPS + 1]),
        ),
        (
            "epoch",
            match rpm() {
                IndexRow::Rpm {
                    summary,
                    license,
                    url,
                    vendor,
                    sourcerpm,
                    provides,
                    requires,
                    ..
                } => IndexRow::Rpm {
                    epoch: Some("1\n".into()),
                    summary,
                    license,
                    url,
                    vendor,
                    sourcerpm,
                    provides,
                    requires,
                },
                _ => unreachable!(),
            },
        ),
        (
            "name",
            IndexRow::Helm {
                name: String::new(),
                version: "1".into(),
                app_version: None,
                description: None,
            },
        ),
        (
            "version",
            IndexRow::Helm {
                name: "a".into(),
                version: "1\r".into(),
                app_version: None,
                description: None,
            },
        ),
        (
            "metadata",
            IndexRow::Wheel {
                metadata: Some(vec![b'a'; MAX_WHEEL_METADATA + 1]),
                sha256: Some("ab".repeat(32)),
            },
        ),
        (
            "sha256",
            IndexRow::Wheel {
                metadata: Some(b"Name: a\n".to_vec()),
                sha256: Some("not hex".into()),
            },
        ),
        (
            "sha256",
            IndexRow::Wheel {
                metadata: Some(b"Name: a\n".to_vec()),
                sha256: None,
            },
        ),
        (
            "manifest",
            IndexRow::Npm {
                manifest: serde_json::json!("a string"),
                integrity: "sha512-AA==".into(),
            },
        ),
        (
            "manifest",
            IndexRow::Npm {
                manifest: serde_json::json!({"x": "y".repeat(MAX_NPM_MANIFEST)}),
                integrity: "sha512-AA==".into(),
            },
        ),
        (
            "integrity",
            IndexRow::Npm {
                manifest: serde_json::json!({}),
                integrity: "sha1-AA==".into(),
            },
        ),
        (
            "group",
            IndexRow::Maven {
                group: "a/b".into(),
                artifact: "c".into(),
                version: "1".into(),
                file: "c-1.jar".into(),
            },
        ),
        (
            "file",
            IndexRow::Maven {
                group: "a".into(),
                artifact: "c".into(),
                version: "1".into(),
                file: "../c-1.jar".into(),
            },
        ),
        (
            "module",
            IndexRow::Go {
                module: "x".repeat(MAX_ID + 1),
                version: "v1".into(),
            },
        ),
    ];
    for (field, row) in cases {
        let e = row
            .check()
            .expect_err(&format!("{} {field} must be refused", row.kind()));
        assert!(
            e.contains(field),
            "{} {field}: the refusal names the field: {e}",
            row.kind()
        );
    }
}

#[test]
fn a_stored_row_about_other_bytes_or_of_another_schema_is_refused() {
    let s = StoredRow::new(&"cd".repeat(32), 1, rpm());
    let bytes = serde_json::to_vec(&s).unwrap();
    assert!(StoredRow::parse(&bytes, &"ef".repeat(32))
        .unwrap_err()
        .contains("not"));
    let mut v: serde_json::Value = serde_json::from_slice(&bytes).unwrap();
    v["schema"] = "holger.index-row/v0".into();
    assert!(
        StoredRow::parse(&serde_json::to_vec(&v).unwrap(), &"cd".repeat(32))
            .unwrap_err()
            .contains("schema")
    );
    v["schema"] = SCHEMA.into();
    v["row"]["path_to_open"] = "/etc/shadow".into();
    assert!(
        StoredRow::parse(&serde_json::to_vec(&v).unwrap(), &"cd".repeat(32)).is_err(),
        "an unknown field"
    );
}

/// The honest rpm row with its summary and requires changed by `f`.
fn rpm_with(f: impl FnOnce(&mut Option<String>, &mut Vec<String>)) -> IndexRow {
    let mut r = rpm();
    if let IndexRow::Rpm {
        summary, requires, ..
    } = &mut r
    {
        f(summary, requires);
    }
    r
}

/// A gzipped tar of `files`, as `npm pack` and `helm package` lay one.
fn tgz(files: &[(&str, &[u8])]) -> Vec<u8> {
    let mut b = tar::Builder::new(flate2::write::GzEncoder::new(
        Vec::new(),
        flate2::Compression::default(),
    ));
    for (path, body) in files {
        let mut h = tar::Header::new_gnu();
        h.set_size(body.len() as u64);
        h.set_mode(0o644);
        h.set_cksum();
        b.append_data(&mut h, path, *body).unwrap();
    }
    b.into_inner().unwrap().finish().unwrap()
}

/// **Each format's row is read out of its own bytes** (lane HOL-PERF-2), by the very parse
/// the format backends ran in the server before: the deb stanza, the rpm header, the
/// Chart.yaml, the npm package.json with its integrity, the wheel METADATA with its sha256.
#[test]
fn every_format_reads_its_row_out_of_its_bytes() {
    let deb = deb::tests_support::sample_deb();
    let want = deb::control_from_deb(&deb).unwrap();
    assert!(want.contains("Depends: libc6 (>= 2.14)"));
    assert_eq!(
        row_of("deb", "hello_2.10-2_amd64.deb", &deb),
        Some(IndexRow::Deb { control: want })
    );

    use rpm::tests_support::{make_rpm, Tag};
    let r = make_rpm(&[
        Tag::Int32(1003, 2),
        Tag::Str(1004, "A greeter"),
        Tag::Str(1014, "MIT"),
        Tag::StrArray(1049, &["libc.so.6"]),
    ]);
    let h = rpm::header(&r).unwrap();
    assert_eq!(h.epoch.as_deref(), Some("2"));
    assert_eq!(
        row_of("raw", "hello-2.10-1.x86_64.rpm", &r),
        Some(IndexRow::Rpm {
            epoch: h.epoch,
            summary: h.summary,
            license: h.license,
            url: None,
            vendor: None,
            sourcerpm: None,
            provides: vec![],
            requires: vec!["libc.so.6".into()],
        })
    );

    let chart = tgz(&[("demo/Chart.yaml", b"apiVersion: v2\nname: demo\nversion: 0.1.0\nappVersion: \"1.16.0\"\ndescription: A chart\n")]);
    assert_eq!(
        row_of("raw", "demo-0.1.0.tgz", &chart),
        Some(IndexRow::Helm {
            name: "demo".into(),
            version: "0.1.0".into(),
            app_version: Some("1.16.0".into()),
            description: Some("A chart".into())
        })
    );

    let pkg = tgz(&[(
        "package/package.json",
        br#"{"name":"left-pad","version":"1.3.0","main":"index.js"}"#,
    )]);
    assert_eq!(
        row_of("npm", "left-pad-1.3.0.tgz", &pkg),
        Some(IndexRow::Npm {
            manifest: serde_json::json!({"name": "left-pad", "version": "1.3.0", "main": "index.js"}),
            integrity: npm::integrity_of(&pkg)
        })
    );
    assert_eq!(
        row_of("raw", "left-pad-1.3.0.tgz", &pkg),
        row_of("npm", "left-pad-1.3.0.tgz", &pkg),
        "an npm tarball a pull-through names `raw` is still npm's, never a chart"
    );

    let meta = b"Metadata-Version: 2.1\nName: demo\nVersion: 1.0\n";
    let whl = zip_entry::fixture::zip(&[
        ("demo/__init__.py", b""),
        ("demo-1.0.dist-info/METADATA", meta),
    ]);
    let sha = {
        use sha2::Digest as _;
        sha2::Sha256::digest(meta)
            .iter()
            .map(|b| format!("{b:02x}"))
            .collect::<String>()
    };
    assert_eq!(
        row_of("pip", "demo-1.0-py3-none-any.whl", &whl),
        Some(IndexRow::Wheel {
            metadata: Some(meta.to_vec()),
            sha256: Some(sha)
        })
    );
    let bare = zip_entry::fixture::zip(&[("demo/__init__.py", b"")]);
    assert_eq!(
        row_of("pip", "demo-1.0-py3-none-any.whl", &bare),
        Some(IndexRow::Wheel {
            metadata: None,
            sha256: None
        })
    );

    assert_eq!(
        row_of("raw", "hello_1.0_amd64.deb", b"not a deb"),
        None,
        "bytes that are not the package give no row"
    );
    assert_eq!(
        row_of("raw", "notes.txt", b"hello"),
        None,
        "a format without an index has no row"
    );
}

/// A package whose fields are out of a row's bounds gives no row, never a row the server
/// would refuse: the worker checks what it answers.
#[test]
fn a_package_whose_fields_break_the_bounds_gives_no_row() {
    let chart = tgz(&[(
        "demo/Chart.yaml",
        b"name: demo\nversion: \"0.1.0\x1b[2J\"\n",
    )]);
    assert!(
        helm::chart_meta_from_tgz(&chart).is_ok(),
        "the chart itself reads"
    );
    assert_eq!(row_of("raw", "demo-0.1.0.tgz", &chart), None);
}

/// **A crate's row** (lane HOL-CARGO-PUBLISH-DOOR): its index line from its own normalised
/// `Cargo.toml`, and the dependency versions it pins: exact requirements of normal and build
/// dependencies (never a dev-dependency), and a binary crate's `Cargo.lock` registry packages.
#[test]
fn a_crate_row_carries_its_index_line_and_its_pinned_dependencies() {
    let manifest = br#"
[package]
name = "demo-bin"
version = "0.3.0"

[[bin]]
name = "demo-bin"
path = "src/main.rs"

[dependencies]
smallvec = { version = "=1.6.0", registry-index = "https://github.com/rust-lang/crates.io-index" }
log = "0.4"

[dependencies.myrand]
version = "=0.8.5"
package = "rand"

[dev-dependencies]
tempfile = "=3.0.0"
"#;
    let lock = br#"
version = 4

[[package]]
name = "demo-bin"
version = "0.3.0"

[[package]]
name = "time"
version = "0.1.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
"#;
    let bytes = tgz(&[
        ("demo-bin-0.3.0/Cargo.toml", manifest),
        ("demo-bin-0.3.0/Cargo.lock", lock),
        ("demo-bin-0.3.0/src/main.rs", b"fn main() {}\n"),
    ]);
    let row = row_of("cargo", "demo-bin-0.3.0.crate", &bytes).expect("a crate row");
    let IndexRow::Crate {
        name,
        version,
        line,
        pinned,
    } = &row
    else {
        panic!("{row:?}");
    };
    assert_eq!((name.as_str(), version.as_str()), ("demo-bin", "0.3.0"));
    assert_eq!(line["vers"], "0.3.0");
    assert!(
        line.get("cksum").is_none(),
        "the sealed bytes give the cksum"
    );
    let deps = line["deps"].as_array().unwrap();
    assert!(deps
        .iter()
        .any(|d| d["name"] == "smallvec" && d["registry"].as_str().is_some()));
    let got: Vec<(&str, &str, &str)> = pinned
        .iter()
        .map(|p| (p.name.as_str(), p.version.as_str(), p.from.as_str()))
        .collect();
    assert_eq!(
        got,
        vec![
            ("rand", "0.8.5", "Cargo.toml"),
            ("smallvec", "1.6.0", "Cargo.toml"),
            ("time", "0.1.43", "Cargo.lock"),
        ],
        "a renamed dependency is pinned by its crate name; a dev-dependency never"
    );
    row.check().unwrap();
    // A library's lock file is never what its users build with.
    let lib_manifest = String::from_utf8_lossy(manifest)
        .replace("[[bin]]\nname = \"demo-bin\"\npath = \"src/main.rs\"\n", "");
    let lib = tgz(&[
        ("demo-bin-0.3.0/Cargo.toml", lib_manifest.as_bytes()),
        ("demo-bin-0.3.0/Cargo.lock", lock),
    ]);
    let Some(IndexRow::Crate { pinned, .. }) = row_of("cargo", "demo-bin-0.3.0.crate", &lib) else {
        panic!("a library crate row");
    };
    assert!(pinned.iter().all(|p| p.from == "Cargo.toml"), "{pinned:?}");
    // A crate whose directory is not what its manifest names gives no row.
    let liar = tgz(&[("other-0.3.0/Cargo.toml", manifest)]);
    assert_eq!(row_of("cargo", "demo-bin-0.3.0.crate", &liar), None);
    assert_eq!(crates::exact_version("=1.6.0"), Some("1.6.0"));
    assert_eq!(crates::exact_version("= 1.6.0-rc.1"), Some("1.6.0-rc.1"));
    for loose in ["1.6.0", "^1.6", "=1.6", ">=1, <2", "=1.*.0"] {
        assert_eq!(crates::exact_version(loose), None, "{loose}");
    }
}