use northroot_proof_engine::{
canonicalize_span_shape, compute_data_shape_hash, compute_method_shape_root,
compute_span_commitment_hash, compute_span_shape_hash, compute_trace_set_root,
normalize_timestamp, ArtifactRef, MethodRef, SpanCommitment, SpanShape, SpanStatus,
};
#[test]
fn test_timestamp_normalization_determinism() {
let t1 = normalize_timestamp("2025-11-04T05:00:00-05:00").unwrap();
let t2 = normalize_timestamp("2025-11-04T10:00:00Z").unwrap();
assert_eq!(t1, t2);
assert_eq!(t1, "2025-11-04T10:00:00.000Z");
let t3 = normalize_timestamp("2025-11-04T10:00:00.123456Z").unwrap();
let t4 = normalize_timestamp("2025-11-04T10:00:00.123456Z").unwrap();
assert_eq!(t3, t4);
assert_eq!(t3, "2025-11-04T10:00:00.123Z");
}
#[test]
fn test_span_shape_hash_determinism() {
let shape = SpanShape {
tool_id: "io.read_csv".to_string(),
tool_version: "1".to_string(),
param_schema: serde_json::json!({"delimiter": {"type": "string", "default": ","}}),
checker_ref: Some("chk:csv-valid".to_string()),
};
let hash1 = compute_span_shape_hash(&shape).unwrap();
let hash2 = compute_span_shape_hash(&shape).unwrap();
assert_eq!(hash1, hash2, "Span shape hash must be deterministic");
}
#[test]
fn test_method_shape_root_sorted_determinism() {
let a = "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".to_string();
let b = "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb".to_string();
let c = "sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc".to_string();
let root1 = compute_method_shape_root(&[b.clone(), a.clone()]).unwrap();
let root2 = compute_method_shape_root(&[a.clone(), b.clone()]).unwrap();
assert_eq!(root1, root2, "Order must not matter");
let root3 = compute_method_shape_root(&[a.clone(), b.clone(), c.clone()]).unwrap();
let root4 = compute_method_shape_root(&[a.clone(), b.clone(), c.clone()]).unwrap();
assert_eq!(root3, root4, "Must be deterministic");
}
#[test]
fn test_span_commitment_hash_determinism() {
let commitment = SpanCommitment {
schema_version: "proof.v2".to_string(),
trace_id: "tr_01H1234567890".to_string(),
span_id: "sp-0001".to_string(),
method_ref: MethodRef {
method_id: "com.test/method".to_string(),
version: "1.0.0".to_string(),
method_shape_root:
"sha256:0000000000000000000000000000000000000000000000000000000000000000"
.to_string(),
image_digest: "sha256:0000000000000000000000000000000000000000000000000000000000000000"
.to_string(),
method_fingerprint: "git:deadbeef".to_string(),
},
data_shape_hash: "sha256:1111111111111111111111111111111111111111111111111111111111111111"
.to_string(),
span_shape_hash: "sha256:2222222222222222222222222222222222222222222222222222222222222222"
.to_string(),
inputs: vec![ArtifactRef {
name: "input.csv".to_string(),
content_hash: "sha256:3333333333333333333333333333333333333333333333333333333333333333"
.to_string(),
uri: None,
}],
outputs: vec![ArtifactRef {
name: "output.json".to_string(),
content_hash: "sha256:4444444444444444444444444444444444444444444444444444444444444444"
.to_string(),
uri: None,
}],
start_time: "2025-11-06T12:00:00.000Z".to_string(),
end_time: "2025-11-06T12:00:00.042Z".to_string(),
status: SpanStatus::OK,
meta: None,
};
let hash1 = compute_span_commitment_hash(&commitment).unwrap();
let hash2 = compute_span_commitment_hash(&commitment).unwrap();
assert_eq!(hash1, hash2, "Span commitment hash must be deterministic");
}
#[test]
fn test_trace_set_root_sorted_determinism() {
let a = "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".to_string();
let b = "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb".to_string();
let root1 = compute_trace_set_root(&[b.clone(), a.clone()]).unwrap();
let root2 = compute_trace_set_root(&[a.clone(), b.clone()]).unwrap();
assert_eq!(root1, root2, "Order must not matter for trace_set_root");
}
#[test]
fn test_canonicalization_byte_for_byte() {
let shape = SpanShape {
tool_id: "io.read_csv".to_string(),
tool_version: "1".to_string(),
param_schema: serde_json::json!({"delimiter": {"type": "string"}}),
checker_ref: None,
};
let canonical1 = canonicalize_span_shape(&shape).unwrap();
let canonical2 = canonicalize_span_shape(&shape).unwrap();
assert_eq!(
canonical1, canonical2,
"Canonicalization must be byte-for-byte identical"
);
}
#[test]
fn test_data_shape_hash_determinism() {
let schema = serde_json::json!({
"type": "object",
"properties": {
"name": {"type": "string"}
}
});
let hash1 = compute_data_shape_hash(&schema, None).unwrap();
let hash2 = compute_data_shape_hash(&schema, None).unwrap();
assert_eq!(hash1, hash2, "Data shape hash must be deterministic");
}