use cellos_core::events::lifecycle_started_data_v1;
use cellos_core::types::{AuthorityBundle, ExecutionCellSpec, Lifetime};
fn minimal_spec(id: &str) -> ExecutionCellSpec {
ExecutionCellSpec {
id: id.to_string(),
correlation: None,
ingress: None,
environment: None,
placement: None,
policy: None,
identity: None,
run: None,
authority: AuthorityBundle::default(),
lifetime: Lifetime { ttl_seconds: 60 },
export: None,
telemetry: None,
}
}
const KERNEL_HEX: &str = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
const ROOTFS_HEX: &str = "fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210";
const FIRECRACKER_HEX: &str = "1111222233334444555566667777888899990000aaaabbbbccccddddeeeeffff";
#[test]
fn lifecycle_started_emits_all_three_digest_fields() {
let spec = minimal_spec("cell-fc-08-all");
let data = lifecycle_started_data_v1(
&spec,
"cell-fc-08-all",
Some("run-fc-08-001"),
None,
None,
None,
None,
Some(KERNEL_HEX),
Some(ROOTFS_HEX),
Some(FIRECRACKER_HEX),
)
.expect("build started data");
assert_eq!(data["kernelDigestSha256"], KERNEL_HEX);
assert_eq!(data["rootfsDigestSha256"], ROOTFS_HEX);
assert_eq!(data["firecrackerDigestSha256"], FIRECRACKER_HEX);
for key in [
"kernelDigestSha256",
"rootfsDigestSha256",
"firecrackerDigestSha256",
] {
let s = data[key]
.as_str()
.unwrap_or_else(|| panic!("{key} is not a string"));
assert_eq!(s.len(), 64, "{key} must be 64 hex chars, got: {s}");
assert!(
s.chars()
.all(|c| c.is_ascii_hexdigit()
&& (!c.is_ascii_alphabetic() || c.is_ascii_lowercase())),
"{key} must be lowercase hex, got: {s}"
);
}
}
#[test]
fn lifecycle_started_emits_kernel_and_rootfs_when_firecracker_omitted() {
let spec = minimal_spec("cell-fc-08-no-fc");
let data = lifecycle_started_data_v1(
&spec,
"cell-fc-08-no-fc",
None,
None,
None,
None,
None,
Some(KERNEL_HEX),
Some(ROOTFS_HEX),
None,
)
.expect("build started data");
assert_eq!(data["kernelDigestSha256"], KERNEL_HEX);
assert_eq!(data["rootfsDigestSha256"], ROOTFS_HEX);
assert!(
!data
.as_object()
.expect("data is object")
.contains_key("firecrackerDigestSha256"),
"firecrackerDigestSha256 must be omitted when None (not stamped as null)"
);
}
#[test]
fn lifecycle_started_omits_all_three_when_none() {
let spec = minimal_spec("cell-fc-08-none");
let data = lifecycle_started_data_v1(
&spec,
"cell-fc-08-none",
None,
None,
None,
None,
None,
None,
None,
None,
)
.expect("build started data");
let obj = data.as_object().expect("data is object");
assert!(!obj.contains_key("kernelDigestSha256"));
assert!(!obj.contains_key("rootfsDigestSha256"));
assert!(!obj.contains_key("firecrackerDigestSha256"));
}
#[test]
fn lifecycle_started_digest_fields_round_trip_through_serde_json() {
let spec = minimal_spec("cell-fc-08-rt");
let original = lifecycle_started_data_v1(
&spec,
"cell-fc-08-rt",
Some("run-fc-08-rt"),
None,
None,
None,
None,
Some(KERNEL_HEX),
Some(ROOTFS_HEX),
Some(FIRECRACKER_HEX),
)
.expect("build started data");
let wire = serde_json::to_string(&original).expect("serialize");
let reparsed: serde_json::Value = serde_json::from_str(&wire).expect("reparse");
assert_eq!(reparsed, original, "round-trip must be lossless");
assert_eq!(reparsed["kernelDigestSha256"], KERNEL_HEX);
assert_eq!(reparsed["rootfsDigestSha256"], ROOTFS_HEX);
assert_eq!(reparsed["firecrackerDigestSha256"], FIRECRACKER_HEX);
assert!(
wire.contains("\"kernelDigestSha256\""),
"wire must use camelCase key kernelDigestSha256, got: {wire}"
);
assert!(
wire.contains("\"rootfsDigestSha256\""),
"wire must use camelCase key rootfsDigestSha256, got: {wire}"
);
assert!(
wire.contains("\"firecrackerDigestSha256\""),
"wire must use camelCase key firecrackerDigestSha256, got: {wire}"
);
}