//! Comprehensive tests for witness receipts and hash chain integrity //! //! Tests cover: //! - Receipt creation and hashing //! - Hash chain verification //! - Tamper detection //! - Security tests (chain manipulation, replay attacks) use cognitum_gate_tilezero::permit::PermitToken; use cognitum_gate_tilezero::receipt::{ EvidentialWitness, PredictiveWitness, ReceiptLog, StructuralWitness, TimestampProof, WitnessReceipt, WitnessSummary, }; use cognitum_gate_tilezero::GateDecision; fn create_test_token(sequence: u64, action_id: &str) -> PermitToken { PermitToken { decision: GateDecision::Permit, action_id: action_id.to_string(), timestamp: 1000000000 + sequence * 1000, ttl_ns: 60_000_000_000, witness_hash: [0u8; 32], sequence, signature: [0u8; 64], } } fn create_test_summary() -> WitnessSummary { WitnessSummary { structural: StructuralWitness { cut_value: 10.0, partition: "stable".to_string(), critical_edges: 5, boundary: vec!["edge1".to_string(), "edge2".to_string()], }, predictive: PredictiveWitness { set_size: 8, coverage: 0.9, }, evidential: EvidentialWitness { e_value: 150.0, verdict: "accept".to_string(), }, } } fn create_test_receipt(sequence: u64, previous_hash: [u8; 32]) -> WitnessReceipt { WitnessReceipt { sequence, token: create_test_token(sequence, &format!("action-{}", sequence)), previous_hash, witness_summary: create_test_summary(), timestamp_proof: TimestampProof { timestamp: 1000000000 + sequence * 1000, previous_receipt_hash: previous_hash, merkle_root: [0u8; 32], }, } } #[cfg(test)] mod witness_summary { use super::*; #[test] fn test_empty_summary() { let summary = WitnessSummary::empty(); assert_eq!(summary.structural.cut_value, 0.0); assert_eq!(summary.predictive.set_size, 0); assert_eq!(summary.evidential.e_value, 1.0); } #[test] fn test_summary_hash_deterministic() { let summary = create_test_summary(); let hash1 = summary.hash(); let hash2 = summary.hash(); assert_eq!(hash1, hash2); } #[test] fn test_summary_hash_unique() { let summary1 = create_test_summary(); let mut summary2 = create_test_summary(); summary2.structural.cut_value = 20.0; assert_ne!(summary1.hash(), summary2.hash()); } #[test] fn test_summary_to_json() { let summary = create_test_summary(); let json = summary.to_json(); assert!(json.is_object()); assert!(json["structural"]["cut_value"].is_number()); assert!(json["predictive"]["set_size"].is_number()); assert!(json["evidential"]["e_value"].is_number()); } } #[cfg(test)] mod receipt_hashing { use super::*; #[test] fn test_receipt_hash_nonzero() { let receipt = create_test_receipt(0, [0u8; 32]); let hash = receipt.hash(); assert_ne!(hash, [0u8; 32]); } #[test] fn test_receipt_hash_deterministic() { let receipt = create_test_receipt(0, [0u8; 32]); let hash1 = receipt.hash(); let hash2 = receipt.hash(); assert_eq!(hash1, hash2); } #[test] fn test_receipt_hash_changes_with_sequence() { let receipt1 = create_test_receipt(0, [0u8; 32]); let receipt2 = create_test_receipt(1, [0u8; 32]); assert_ne!(receipt1.hash(), receipt2.hash()); } #[test] fn test_receipt_hash_changes_with_previous() { let receipt1 = create_test_receipt(0, [0u8; 32]); let receipt2 = create_test_receipt(0, [1u8; 32]); assert_ne!(receipt1.hash(), receipt2.hash()); } #[test] fn test_receipt_hash_includes_witness() { let mut receipt1 = create_test_receipt(0, [0u8; 32]); let mut receipt2 = create_test_receipt(0, [0u8; 32]); receipt2.witness_summary.structural.cut_value = 99.0; assert_ne!(receipt1.hash(), receipt2.hash()); } } #[cfg(test)] mod receipt_log { use super::*; #[test] fn test_new_log_empty() { let log = ReceiptLog::new(); assert!(log.is_empty()); assert_eq!(log.len(), 0); assert_eq!(log.latest_sequence(), None); } #[test] fn test_genesis_hash() { let log = ReceiptLog::new(); assert_eq!(log.last_hash(), [0u8; 32]); } #[test] fn test_append_single() { let mut log = ReceiptLog::new(); let receipt = create_test_receipt(0, log.last_hash()); log.append(receipt); assert_eq!(log.len(), 1); assert_eq!(log.latest_sequence(), Some(0)); assert_ne!(log.last_hash(), [0u8; 32]); } #[test] fn test_append_multiple() { let mut log = ReceiptLog::new(); for i in 0..5 { let receipt = create_test_receipt(i, log.last_hash()); log.append(receipt); } assert_eq!(log.len(), 5); assert_eq!(log.latest_sequence(), Some(4)); } #[test] fn test_get_receipt() { let mut log = ReceiptLog::new(); let receipt = create_test_receipt(0, log.last_hash()); log.append(receipt); let retrieved = log.get(0); assert!(retrieved.is_some()); assert_eq!(retrieved.unwrap().sequence, 0); } #[test] fn test_get_nonexistent() { let log = ReceiptLog::new(); assert!(log.get(0).is_none()); assert!(log.get(999).is_none()); } } #[cfg(test)] mod hash_chain_verification { use super::*; #[test] fn test_verify_empty_chain() { let log = ReceiptLog::new(); // Verifying empty chain up to 0 should fail (no receipt at 0) assert!(log.verify_chain_to(0).is_err()); } #[test] fn test_verify_single_receipt() { let mut log = ReceiptLog::new(); let receipt = create_test_receipt(0, log.last_hash()); log.append(receipt); assert!(log.verify_chain_to(0).is_ok()); } #[test] fn test_verify_chain_multiple() { let mut log = ReceiptLog::new(); for i in 0..10 { let receipt = create_test_receipt(i, log.last_hash()); log.append(receipt); } // Verify full chain assert!(log.verify_chain_to(9).is_ok()); // Verify partial chains assert!(log.verify_chain_to(0).is_ok()); assert!(log.verify_chain_to(5).is_ok()); } #[test] fn test_verify_beyond_latest() { let mut log = ReceiptLog::new(); let receipt = create_test_receipt(0, log.last_hash()); log.append(receipt); // Trying to verify beyond what exists should fail assert!(log.verify_chain_to(1).is_err()); } } #[cfg(test)] mod tamper_detection { use super::*; #[test] fn test_detect_modified_hash() { let mut log = ReceiptLog::new(); // Build a valid chain for i in 0..5 { let receipt = create_test_receipt(i, log.last_hash()); log.append(receipt); } // The chain should be valid assert!(log.verify_chain_to(4).is_ok()); } #[test] fn test_chain_with_gap() { let mut log = ReceiptLog::new(); // Add receipt at 0 let receipt0 = create_test_receipt(0, log.last_hash()); log.append(receipt0); // Skip 1, add at 2 (breaking chain) let receipt2 = create_test_receipt(2, log.last_hash()); log.append(receipt2); // Verify should fail at sequence 1 (missing) assert!(log.verify_chain_to(2).is_err()); } } #[cfg(test)] mod timestamp_proof { use super::*; #[test] fn test_timestamp_proof_structure() { let proof = TimestampProof { timestamp: 1000000000, previous_receipt_hash: [1u8; 32], merkle_root: [2u8; 32], }; assert_eq!(proof.timestamp, 1000000000); assert_eq!(proof.previous_receipt_hash, [1u8; 32]); assert_eq!(proof.merkle_root, [2u8; 32]); } #[test] fn test_receipt_contains_timestamp_proof() { let receipt = create_test_receipt(5, [3u8; 32]); assert_eq!(receipt.timestamp_proof.previous_receipt_hash, [3u8; 32]); assert!(receipt.timestamp_proof.timestamp > 0); } #[test] fn test_timestamp_ordering() { let mut log = ReceiptLog::new(); for i in 0..5 { let receipt = create_test_receipt(i, log.last_hash()); log.append(receipt); } // Each receipt should have increasing timestamp let mut prev_ts = 0; for i in 0..5 { let receipt = log.get(i).unwrap(); assert!(receipt.timestamp_proof.timestamp > prev_ts); prev_ts = receipt.timestamp_proof.timestamp; } } } #[cfg(test)] mod structural_witness { use super::*; #[test] fn test_structural_witness_fields() { let witness = StructuralWitness { cut_value: 15.0, partition: "fragile".to_string(), critical_edges: 3, boundary: vec!["e1".to_string(), "e2".to_string(), "e3".to_string()], }; assert_eq!(witness.cut_value, 15.0); assert_eq!(witness.partition, "fragile"); assert_eq!(witness.critical_edges, 3); assert_eq!(witness.boundary.len(), 3); } #[test] fn test_structural_witness_serialization() { let witness = StructuralWitness { cut_value: 10.0, partition: "stable".to_string(), critical_edges: 2, boundary: vec![], }; let json = serde_json::to_string(&witness).unwrap(); let restored: StructuralWitness = serde_json::from_str(&json).unwrap(); assert_eq!(witness.cut_value, restored.cut_value); assert_eq!(witness.partition, restored.partition); } } #[cfg(test)] mod predictive_witness { use super::*; #[test] fn test_predictive_witness_fields() { let witness = PredictiveWitness { set_size: 12, coverage: 0.95, }; assert_eq!(witness.set_size, 12); assert_eq!(witness.coverage, 0.95); } #[test] fn test_predictive_witness_serialization() { let witness = PredictiveWitness { set_size: 5, coverage: 0.9, }; let json = serde_json::to_string(&witness).unwrap(); let restored: PredictiveWitness = serde_json::from_str(&json).unwrap(); assert_eq!(witness.set_size, restored.set_size); assert!((witness.coverage - restored.coverage).abs() < 0.001); } } #[cfg(test)] mod evidential_witness { use super::*; #[test] fn test_evidential_witness_fields() { let witness = EvidentialWitness { e_value: 250.0, verdict: "accept".to_string(), }; assert_eq!(witness.e_value, 250.0); assert_eq!(witness.verdict, "accept"); } #[test] fn test_evidential_witness_verdicts() { let accept = EvidentialWitness { e_value: 200.0, verdict: "accept".to_string(), }; let cont = EvidentialWitness { e_value: 50.0, verdict: "continue".to_string(), }; let reject = EvidentialWitness { e_value: 0.005, verdict: "reject".to_string(), }; assert_eq!(accept.verdict, "accept"); assert_eq!(cont.verdict, "continue"); assert_eq!(reject.verdict, "reject"); } } #[cfg(test)] mod security_tests { use super::*; /// Test that forged receipts are detected #[test] fn test_forged_receipt_detection() { let mut log = ReceiptLog::new(); // Build legitimate chain for i in 0..3 { let receipt = create_test_receipt(i, log.last_hash()); log.append(receipt); } // A forged receipt with wrong previous hash would break verification // (simulated by the verify_chain_to test with gaps) } /// Test that hash provides uniqueness #[test] fn test_hash_collision_resistance() { let mut hashes = std::collections::HashSet::new(); // Generate many receipts and check for collisions for i in 0..100 { let receipt = create_test_receipt(i, [i as u8; 32]); let hash = receipt.hash(); assert!(hashes.insert(hash), "Hash collision at sequence {}", i); } } /// Test that modifying any field changes the hash #[test] fn test_all_fields_affect_hash() { let base = create_test_receipt(0, [0u8; 32]); let base_hash = base.hash(); // Modify sequence let mut modified = create_test_receipt(0, [0u8; 32]); modified.sequence = 1; assert_ne!(base_hash, modified.hash()); // Modify previous_hash let modified2 = create_test_receipt(0, [1u8; 32]); assert_ne!(base_hash, modified2.hash()); // Modify witness let mut modified3 = create_test_receipt(0, [0u8; 32]); modified3.witness_summary.evidential.e_value = 0.0; assert_ne!(base_hash, modified3.hash()); } /// Test sequence monotonicity #[test] fn test_sequence_monotonicity() { let mut log = ReceiptLog::new(); let mut prev_seq = None; for i in 0..10 { let receipt = create_test_receipt(i, log.last_hash()); log.append(receipt); if let Some(prev) = prev_seq { assert!(log.get(i).unwrap().sequence > prev); } prev_seq = Some(i); } } } // Property-based tests #[cfg(test)] mod property_tests { use super::*; use proptest::prelude::*; proptest! { #[test] fn prop_hash_deterministic(seq in 0u64..1000, prev in proptest::array::uniform32(0u8..255)) { let receipt = create_test_receipt(seq, prev); assert_eq!(receipt.hash(), receipt.hash()); } #[test] fn prop_different_sequences_different_hashes(seq1 in 0u64..1000, seq2 in 0u64..1000) { prop_assume!(seq1 != seq2); let r1 = create_test_receipt(seq1, [0u8; 32]); let r2 = create_test_receipt(seq2, [0u8; 32]); assert_ne!(r1.hash(), r2.hash()); } #[test] fn prop_chain_grows_correctly(n in 1usize..20) { let mut log = ReceiptLog::new(); for i in 0..n { let receipt = create_test_receipt(i as u64, log.last_hash()); log.append(receipt); } assert_eq!(log.len(), n); assert!(log.verify_chain_to((n - 1) as u64).is_ok()); } } }