update-bundle/src/tests/roundtrip.rs
//! Real znippy container round-trip: pack → load → verify → extract, plus a
//! tampered-archive check.
use std::fs;
use super::testkit::*;
use crate::api::{extract_bundle_file, verify_bundle_file};
use crate::container::{load_bundle, pack_bundle};
use crate::verify::TargetSpec;
#[test]
fn pack_load_verify_roundtrip() {
let b = valid_bundle();
let dir = tempfile::tempdir().unwrap();
let out = dir
.path()
.join("ExampleCorp-ExampleDrive-1.2.3.holger.znippy");
pack_bundle(&b.members, &out).unwrap();
assert!(out.exists());
let loaded = load_bundle(&out).unwrap();
assert_eq!(loaded.len(), b.members.len());
for (k, v) in &b.members {
assert_eq!(
loaded.get(k).map(|x| x.as_slice()),
Some(v.as_slice()),
"member {k}"
);
}
let rep = verify_bundle_file(&out, &b.policy, &TargetSpec::default(), None).unwrap();
assert_eq!(rep.approvals.len(), 2);
assert_eq!(rep.bundle_id, b.bundle_id);
}
#[test]
fn extract_writes_verified_tree_into_empty_dir() {
let b = valid_bundle();
let dir = tempfile::tempdir().unwrap();
let out = dir.path().join("bundle.holger.znippy");
pack_bundle(&b.members, &out).unwrap();
let dest = dir.path().join("extracted");
extract_bundle_file(&out, &b.policy, &TargetSpec::default(), &dest).unwrap();
assert_eq!(
fs::read(dest.join("payload/firmware.bin")).unwrap(),
b"firmware-v1"
);
assert!(dest.join("core.json").exists());
assert!(dest.join("signatures/release-manifest.json").exists());
}
#[test]
fn extract_refuses_non_empty_destination() {
let b = valid_bundle();
let dir = tempfile::tempdir().unwrap();
let out = dir.path().join("bundle.holger.znippy");
pack_bundle(&b.members, &out).unwrap();
let dest = dir.path().join("dest");
fs::create_dir_all(&dest).unwrap();
fs::write(dest.join("occupied"), b"x").unwrap();
assert!(extract_bundle_file(&out, &b.policy, &TargetSpec::default(), &dest).is_err());
}
#[test]
fn tampered_archive_fails_verification() {
let b = valid_bundle();
let dir = tempfile::tempdir().unwrap();
let out = dir.path().join("bundle.holger.znippy");
pack_bundle(&b.members, &out).unwrap();
let mut bytes = fs::read(&out).unwrap();
// Prefer to tamper a known payload's bytes (proves the manifest-digest layer
// catches a member change end-to-end through the real container). Small members
// are stored with little/no compression, so the literal is usually present. If
// not, corrupt the archive header instead (proves structural rejection). Either
// way a tampered archive must not verify.
if let Some(pos) = bytes.windows(11).position(|w| w == b"firmware-v1") {
bytes[pos + 3] ^= 0x01;
} else {
for b in bytes.iter_mut().take(64) {
*b ^= 0xff;
}
}
fs::write(&out, &bytes).unwrap();
assert!(
verify_bundle_file(&out, &b.policy, &TargetSpec::default(), None).is_err(),
"a tampered archive must not verify"
);
}