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());
}