# VAULTMESH-TESTING-FRAMEWORK.md **Property-Based Testing for the Civilization Ledger** > *What is not tested cannot be trusted.* --- ## 1. Testing Philosophy VaultMesh uses a layered testing approach: | Layer | What It Tests | Framework | |-------|---------------|-----------| | Unit | Individual functions | Rust: `#[test]`, Python: `pytest` | | Property | Invariants that must always hold | `proptest`, `hypothesis` | | Integration | Component interactions | `testcontainers` | | Contract | API compatibility | OpenAPI validation | | Chaos | Resilience under failure | `chaos-mesh`, custom | | Acceptance | End-to-end scenarios | `cucumber-rs` | --- ## 2. Core Invariants These properties must ALWAYS hold: ```rust // vaultmesh-core/src/invariants.rs /// Core invariants that must never be violated pub trait Invariant { fn check(&self) -> Result<(), InvariantViolation>; } /// Receipts are append-only (AXIOM-001) pub struct AppendOnlyReceipts; impl Invariant for AppendOnlyReceipts { fn check(&self) -> Result<(), InvariantViolation> { // Verify no receipts have been modified or deleted // by comparing sequential hashes Ok(()) } } /// Merkle roots are consistent with receipts (AXIOM-002) pub struct ConsistentMerkleRoots; impl Invariant for ConsistentMerkleRoots { fn check(&self) -> Result<(), InvariantViolation> { // Recompute Merkle root from receipts // Compare with stored root Ok(()) } } /// All significant operations produce receipts (AXIOM-003) pub struct UniversalReceipting; impl Invariant for UniversalReceipting { fn check(&self) -> Result<(), InvariantViolation> { // Check that tracked operations have corresponding receipts Ok(()) } } /// Hash chains are unbroken pub struct UnbrokenHashChains; impl Invariant for UnbrokenHashChains { fn check(&self) -> Result<(), InvariantViolation> { // Verify each receipt's previous_hash matches the prior receipt Ok(()) } } ``` --- ## 3. Property-Based Tests ### 3.1 Receipt Properties ```rust // vaultmesh-core/tests/receipt_properties.rs use proptest::prelude::*; use vaultmesh_core::{Receipt, Scroll, VmHash}; proptest! { /// Any valid receipt can be serialized and deserialized without loss #[test] fn receipt_roundtrip(receipt in arb_receipt()) { let json = serde_json::to_string(&receipt)?; let restored: Receipt = serde_json::from_str(&json)?; prop_assert_eq!(receipt, restored); } /// Receipt hash is deterministic #[test] fn receipt_hash_deterministic(receipt in arb_receipt()) { let hash1 = VmHash::from_json(&receipt)?; let hash2 = VmHash::from_json(&receipt)?; prop_assert_eq!(hash1, hash2); } /// Different receipts produce different hashes #[test] fn different_receipts_different_hashes( receipt1 in arb_receipt(), receipt2 in arb_receipt() ) { prop_assume!(receipt1 != receipt2); let hash1 = VmHash::from_json(&receipt1)?; let hash2 = VmHash::from_json(&receipt2)?; prop_assert_ne!(hash1, hash2); } /// Merkle root of N receipts is consistent regardless of computation order #[test] fn merkle_root_order_independent(receipts in prop::collection::vec(arb_receipt(), 1..100)) { let hashes: Vec = receipts.iter() .map(|r| VmHash::from_json(r).unwrap()) .collect(); let root1 = merkle_root(&hashes); // Shuffle but keep same hashes let mut shuffled = hashes.clone(); shuffled.sort_by(|a, b| a.hex().cmp(b.hex())); // Root should be same because merkle_root sorts internally let root2 = merkle_root(&shuffled); prop_assert_eq!(root1, root2); } } fn arb_receipt() -> impl Strategy> { ( arb_scroll(), arb_receipt_type(), any::(), prop::collection::vec(any::(), 0..5), ).prop_map(|(scroll, receipt_type, timestamp, tags)| { Receipt { header: ReceiptHeader { receipt_type, timestamp: DateTime::from_timestamp(timestamp as i64, 0).unwrap(), root_hash: "blake3:placeholder".to_string(), tags, }, meta: ReceiptMeta { scroll, sequence: 0, anchor_epoch: None, proof_path: None, }, body: serde_json::json!({"test": true}), } }) } fn arb_scroll() -> impl Strategy { prop_oneof![ Just(Scroll::Drills), Just(Scroll::Compliance), Just(Scroll::Guardian), Just(Scroll::Treasury), Just(Scroll::Mesh), Just(Scroll::OffSec), Just(Scroll::Identity), Just(Scroll::Observability), Just(Scroll::Automation), Just(Scroll::PsiField), ] } fn arb_receipt_type() -> impl Strategy { prop_oneof![ Just("security_drill_run".to_string()), Just("oracle_answer".to_string()), Just("anchor_success".to_string()), Just("treasury_credit".to_string()), Just("mesh_node_join".to_string()), ] } ``` ### 3.2 Guardian Properties ```rust // vaultmesh-guardian/tests/guardian_properties.rs use proptest::prelude::*; use vaultmesh_guardian::{ProofChain, AnchorCycle}; proptest! { /// Anchor cycle produces valid proof for all included receipts #[test] fn anchor_cycle_valid_proofs( receipts in prop::collection::vec(arb_receipt(), 1..50) ) { let mut proofchain = ProofChain::new(); for receipt in &receipts { proofchain.append(receipt)?; } let cycle = AnchorCycle::new(&proofchain); let anchor_result = cycle.execute_mock()?; // Every receipt should have a valid Merkle proof for receipt in &receipts { let proof = anchor_result.get_proof(&receipt.header.root_hash)?; prop_assert!(proof.verify(&anchor_result.root_hash)); } } /// Anchor root changes when any receipt changes #[test] fn anchor_root_sensitive( receipts in prop::collection::vec(arb_receipt(), 2..20), index in any::() ) { let mut proofchain1 = ProofChain::new(); let mut proofchain2 = ProofChain::new(); for receipt in &receipts { proofchain1.append(receipt)?; proofchain2.append(receipt)?; } let root1 = proofchain1.current_root(); // Modify one receipt in proofchain2 let idx = index.index(receipts.len()); let mut modified = receipts[idx].clone(); modified.body = serde_json::json!({"modified": true}); proofchain2.replace(idx, &modified)?; let root2 = proofchain2.current_root(); prop_assert_ne!(root1, root2); } /// Sequential anchors form valid chain #[test] fn sequential_anchors_chain( receipt_batches in prop::collection::vec( prop::collection::vec(arb_receipt(), 1..20), 2..10 ) ) { let mut proofchain = ProofChain::new(); let mut previous_anchor: Option = None; for batch in receipt_batches { for receipt in batch { proofchain.append(&receipt)?; } let cycle = AnchorCycle::new(&proofchain); let anchor_result = cycle.execute_mock()?; if let Some(prev) = &previous_anchor { // Current anchor should reference previous prop_assert_eq!(anchor_result.previous_root, Some(prev.root_hash.clone())); } previous_anchor = Some(anchor_result); } } } ``` ### 3.3 Treasury Properties ```rust // vaultmesh-treasury/tests/treasury_properties.rs use proptest::prelude::*; use rust_decimal::Decimal; use vaultmesh_treasury::{TreasuryEngine, Entry, EntryType, Settlement}; proptest! { /// Sum of all entries is always zero (double-entry invariant) #[test] fn double_entry_balance( entries in prop::collection::vec(arb_entry_pair(), 1..50) ) { let mut engine = TreasuryEngine::new(); engine.create_account(test_account("account-a"))?; engine.create_account(test_account("account-b"))?; let mut total = Decimal::ZERO; for (debit, credit) in entries { engine.record_entry(debit.clone())?; engine.record_entry(credit.clone())?; total += credit.amount; total -= debit.amount; } // Total should always be zero prop_assert_eq!(total, Decimal::ZERO); } /// Settlement balances match pre/post snapshots #[test] fn settlement_balance_consistency( settlement in arb_settlement() ) { let mut engine = TreasuryEngine::new(); // Create accounts from settlement for entry in &settlement.entries { engine.create_account_if_not_exists(&entry.account)?; } // Fund accounts for entry in &settlement.entries { if entry.entry_type == EntryType::Debit { engine.fund_account(&entry.account, entry.amount * 2)?; } } // Snapshot before let before = engine.snapshot_balances(&settlement.affected_accounts())?; // Execute settlement let result = engine.execute_settlement(settlement.clone())?; // Snapshot after let after = engine.snapshot_balances(&settlement.affected_accounts())?; // Verify net flows match difference for (account, net_flow) in &result.net_flow { let expected_after = before.get(account).unwrap() + net_flow; prop_assert_eq!(*after.get(account).unwrap(), expected_after); } } } fn arb_entry_pair() -> impl Strategy { (1u64..1000000).prop_map(|cents| { let amount = Decimal::new(cents as i64, 2); let debit = Entry { entry_id: format!("debit-{}", uuid::Uuid::new_v4()), entry_type: EntryType::Debit, account: "account-a".to_string(), amount, currency: Currency::EUR, memo: "Test debit".to_string(), timestamp: Utc::now(), tags: vec![], }; let credit = Entry { entry_id: format!("credit-{}", uuid::Uuid::new_v4()), entry_type: EntryType::Credit, account: "account-b".to_string(), amount, currency: Currency::EUR, memo: "Test credit".to_string(), timestamp: Utc::now(), tags: vec![], }; (debit, credit) }) } ``` --- ## 4. Integration Tests ```rust // tests/integration/full_cycle.rs use testcontainers::{clients, images::postgres::Postgres, Container}; use vaultmesh_portal::Portal; use vaultmesh_guardian::Guardian; use vaultmesh_oracle::Oracle; #[tokio::test] async fn full_receipt_lifecycle() { // Start containers let docker = clients::Cli::default(); let postgres = docker.run(Postgres::default()); let db_url = format!( "postgresql://postgres:postgres@localhost:{}/postgres", postgres.get_host_port_ipv4(5432) ); // Initialize services let portal = Portal::new(&db_url).await?; let guardian = Guardian::new(&db_url).await?; // Create and emit receipt let receipt = portal.emit_receipt( Scroll::Drills, "security_drill_run", json!({ "drill_id": "test-drill-001", "status": "completed" }), vec!["test".to_string()], ).await?; // Verify receipt exists let stored = portal.get_receipt(&receipt.header.root_hash).await?; assert_eq!(stored.header.root_hash, receipt.header.root_hash); // Trigger anchor let anchor_result = guardian.anchor_now(None).await?; assert!(anchor_result.success); // Verify receipt has proof let proof = guardian.get_proof(&receipt.header.root_hash).await?; assert!(proof.is_some()); assert!(proof.unwrap().verify(&anchor_result.root_hash)); } #[tokio::test] async fn oracle_answer_receipted() { let docker = clients::Cli::default(); let postgres = docker.run(Postgres::default()); let db_url = format!( "postgresql://postgres:postgres@localhost:{}/postgres", postgres.get_host_port_ipv4(5432) ); let portal = Portal::new(&db_url).await?; let oracle = Oracle::new(&db_url).await?; // Load test corpus oracle.load_corpus("tests/fixtures/corpus").await?; // Ask question let answer = oracle.answer( "What are the requirements for technical documentation under Article 11?", vec!["AI_Act".to_string()], vec![], ).await?; // Verify answer was receipted let receipts = portal.query_receipts( Some(Scroll::Compliance), Some("oracle_answer".to_string()), None, None, 10, ).await?; assert!(!receipts.is_empty()); assert_eq!(receipts[0].body["answer_hash"], answer.answer_hash); } ``` --- ## 5. Chaos Tests ```yaml # chaos/anchor-failure.yaml apiVersion: chaos-mesh.org/v1alpha1 kind: NetworkChaos metadata: name: anchor-network-partition namespace: vaultmesh spec: action: partition mode: all selector: namespaces: - vaultmesh labelSelectors: app.kubernetes.io/name: guardian direction: to target: selector: namespaces: - default labelSelectors: app: ethereum-node mode: all duration: "5m" scheduler: cron: "@every 6h" --- apiVersion: chaos-mesh.org/v1alpha1 kind: PodChaos metadata: name: guardian-pod-kill namespace: vaultmesh spec: action: pod-kill mode: one selector: namespaces: - vaultmesh labelSelectors: app.kubernetes.io/name: guardian scheduler: cron: "@every 4h" ``` ```rust // tests/chaos/anchor_resilience.rs #[tokio::test] #[ignore] // Run manually with chaos-mesh async fn guardian_recovers_from_network_partition() { let guardian = connect_to_guardian().await?; let portal = connect_to_portal().await?; // Generate receipts for i in 0..100 { portal.emit_receipt( Scroll::Drills, "test_receipt", json!({"index": i}), vec![], ).await?; } // Wait for chaos to potentially occur tokio::time::sleep(Duration::from_secs(60)).await; // Verify guardian state is consistent let status = guardian.get_status().await?; // Should either be anchoring or have recovered assert!( status.state == "idle" || status.state == "anchoring", "Guardian in unexpected state: {}", status.state ); // If idle, verify all receipts are anchored if status.state == "idle" { let receipts = portal.query_receipts(None, None, None, None, 200).await?; for receipt in receipts { let proof = guardian.get_proof(&receipt.header.root_hash).await?; assert!(proof.is_some(), "Receipt not anchored: {}", receipt.header.root_hash); } } } ``` --- ## 6. Test Fixtures ```rust // tests/fixtures/mod.rs use vaultmesh_core::*; pub fn test_drill_receipt() -> Receipt { Receipt { header: ReceiptHeader { receipt_type: "security_drill_run".to_string(), timestamp: Utc::now(), root_hash: "blake3:placeholder".to_string(), tags: vec!["test".to_string()], }, meta: ReceiptMeta { scroll: Scroll::Drills, sequence: 1, anchor_epoch: None, proof_path: None, }, body: json!({ "drill_id": "drill-test-001", "prompt": "Test security scenario", "status": "completed", "stages_total": 3, "stages_completed": 3 }), } } pub fn test_oracle_receipt() -> Receipt { Receipt { header: ReceiptHeader { receipt_type: "oracle_answer".to_string(), timestamp: Utc::now(), root_hash: "blake3:placeholder".to_string(), tags: vec!["test".to_string(), "compliance".to_string()], }, meta: ReceiptMeta { scroll: Scroll::Compliance, sequence: 1, anchor_epoch: None, proof_path: None, }, body: json!({ "question": "Test compliance question?", "answer_hash": "blake3:test...", "confidence": 0.95, "frameworks": ["AI_Act"] }), } } pub fn test_corpus() -> Vec { vec![ CorpusDocument { id: "doc-001".to_string(), title: "AI Act Article 11 - Technical Documentation".to_string(), content: "Providers shall draw up technical documentation...".to_string(), framework: "AI_Act".to_string(), section: "Article 11".to_string(), }, // ... more test documents ] } ```