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

nordisk/dwarves

crates/dwarves-slsa/tests/statement.rs

//! The statement's shape: in-toto Statement v1, SLSA Provenance v1, where Holger's
//! scanner and cosign look.

mod common;

use dwarves_slsa::statement::{
    self, Subject, BUILDER_ID_DWARVES_SERVER, BUILD_TYPE_CARGO_RELEASE, SLSA_PROVENANCE_V1,
    STATEMENT_V1,
};
use serde_json::Value;

#[test]
fn the_statement_is_in_toto_v1_with_slsa_provenance_v1() {
    let st = statement::statement(&common::subjects(), &common::provenance()).unwrap();
    assert_eq!(st["_type"], STATEMENT_V1);
    assert_eq!(st["predicateType"], SLSA_PROVENANCE_V1);
    let p = &st["predicate"];
    assert_eq!(p["buildDefinition"]["buildType"], BUILD_TYPE_CARGO_RELEASE);
    assert_eq!(
        p["buildDefinition"]["externalParameters"]["release"],
        "holger-2.0.1"
    );
    assert_eq!(
        p["buildDefinition"]["internalParameters"]["image"],
        "localhost/nordisk-cargo-toolchain@sha256:875a"
    );
    let deps = p["buildDefinition"]["resolvedDependencies"]
        .as_array()
        .unwrap();
    assert_eq!(
        deps[0]["uri"],
        "git+https://git.gunnar.rs/nordisk/holger.git"
    );
    assert_eq!(
        deps[0]["digest"]["gitCommit"],
        "87e5ada50ab5745464e4b46305bfd62d0460f52c"
    );
    assert_eq!(p["runDetails"]["builder"]["id"], BUILDER_ID_DWARVES_SERVER);
    assert_eq!(
        p["runDetails"]["builder"]["version"]["dwarves-slsa"],
        "0.1.0"
    );
    assert_eq!(
        p["runDetails"]["metadata"]["invocationId"],
        "holger-2.0.1/20261029T120000Z-0011223344556677"
    );
    assert_eq!(
        p["runDetails"]["metadata"]["startedOn"],
        "2026-10-29T12:00:00Z"
    );
    assert_eq!(
        p["runDetails"]["metadata"]["finishedOn"],
        "2026-10-29T12:03:00Z"
    );
    assert_eq!(p["runDetails"]["byproducts"][0]["name"], "logs/build.log");
    // Only the four top-level keys of a statement.
    let keys: Vec<&String> = st.as_object().unwrap().keys().collect();
    assert_eq!(keys.len(), 4, "{keys:?}");
}

#[test]
fn holger_and_cosign_find_the_fields_where_they_look() {
    let st = statement::statement(&common::subjects(), &common::provenance()).unwrap();
    // holger-provenance statement.rs reads these pointers for SLSA v1.
    for ptr in [
        "/predicate/runDetails/builder/id",
        "/predicate/buildDefinition/buildType",
        "/predicate/runDetails/metadata/invocationId",
    ] {
        assert!(
            st.pointer(ptr)
                .and_then(Value::as_str)
                .is_some_and(|s| !s.is_empty()),
            "{ptr}"
        );
    }
    assert!(st
        .pointer("/predicate/buildDefinition/externalParameters")
        .is_some_and(Value::is_object));
    assert!(st
        .pointer("/predicate/buildDefinition/resolvedDependencies")
        .is_some_and(Value::is_array));
    // cosign matches a blob by subject[].digest.sha256.
    for s in st["subject"].as_array().unwrap() {
        assert_eq!(s["digest"]["sha256"].as_str().unwrap().len(), 64);
    }
}

#[test]
fn subjects_are_sorted_by_name() {
    let st = statement::statement(&common::subjects(), &common::provenance()).unwrap();
    let names: Vec<&str> = st["subject"]
        .as_array()
        .unwrap()
        .iter()
        .map(|s| s["name"].as_str().unwrap())
        .collect();
    assert_eq!(
        names,
        vec!["bin/holger", "crates/holger-errcode-2.0.1.crate"]
    );
}

#[test]
fn a_statement_without_subjects_is_refused() {
    let e = statement::statement(&[], &common::provenance()).unwrap_err();
    assert!(e.0.contains("no subject"), "{e}");
}

#[test]
fn a_name_twice_is_refused() {
    let s = vec![
        Subject::sha256("a", common::hex64('a')),
        Subject::sha256("a", common::hex64('b')),
    ];
    let e = statement::statement(&s, &common::provenance()).unwrap_err();
    assert!(e.0.contains("twice"), "{e}");
}

#[test]
fn a_subject_without_a_proper_sha256_is_refused() {
    for bad in ["abc".to_string(), common::hex64('A'), common::hex64('g')] {
        let s = vec![Subject::sha256("a", bad.clone())];
        assert!(
            statement::statement(&s, &common::provenance()).is_err(),
            "{bad}"
        );
    }
    let mut s = Subject::sha256("a", common::hex64('a'));
    s.digest.clear();
    s.digest.insert("sha512".into(), "00".into());
    assert!(statement::statement(&[s], &common::provenance()).is_err());
}

#[test]
fn an_empty_builder_build_type_or_invocation_is_refused() {
    let mut p = common::provenance();
    p.builder_id.clear();
    assert!(statement::statement(&common::subjects(), &p).is_err());
    let mut p = common::provenance();
    p.build_type.clear();
    assert!(statement::statement(&common::subjects(), &p).is_err());
    let mut p = common::provenance();
    p.invocation_id.clear();
    assert!(statement::statement(&common::subjects(), &p).is_err());
}

#[test]
fn a_finish_before_the_start_or_a_bad_timestamp_is_refused() {
    let mut p = common::provenance();
    p.finished_on = "2026-10-29T11:59:59Z".into();
    assert!(statement::statement(&common::subjects(), &p).is_err());
    let mut p = common::provenance();
    p.started_on = "2026-10-29 12:00:00".into();
    assert!(statement::statement(&common::subjects(), &p).is_err());
}

#[test]
fn the_payload_is_pretty_json_with_a_newline_and_reads_back() {
    let st = statement::statement(&common::subjects(), &common::provenance()).unwrap();
    let bytes = statement::payload(&st);
    assert_eq!(*bytes.last().unwrap(), b'\n');
    assert!(bytes.windows(2).any(|w| w == b"\n "), "pretty");
    let back: Value = serde_json::from_slice(&bytes).unwrap();
    assert_eq!(back, st);
}

#[test]
fn the_summary_reads_what_a_reviewer_checks() {
    let st = statement::statement(&common::subjects(), &common::provenance()).unwrap();
    let s = statement::summary(&st).unwrap();
    assert_eq!(s.release, "holger-2.0.1");
    assert_eq!(s.builder_id, BUILDER_ID_DWARVES_SERVER);
    assert_eq!(s.build_type, BUILD_TYPE_CARGO_RELEASE);
    assert_eq!(s.subjects.len(), 2);
    assert_eq!(
        s.repositories,
        vec![(
            "holger".to_string(),
            "https://git.gunnar.rs/nordisk/holger".to_string(),
            "87e5ada50ab5745464e4b46305bfd62d0460f52c".to_string()
        )]
    );
}

#[test]
fn a_summary_of_something_else_is_refused() {
    let mut st = statement::statement(&common::subjects(), &common::provenance()).unwrap();
    st["predicateType"] = "https://slsa.dev/provenance/v0.2".into();
    assert!(statement::summary(&st).is_err());
    let mut st = statement::statement(&common::subjects(), &common::provenance()).unwrap();
    st["_type"] = "https://in-toto.io/Statement/v0.1".into();
    assert!(statement::summary(&st).is_err());
}