Rust and WASM did-method-plc tools and structures
at main 5.7 kB view raw
1//! Tests for operation chain validation 2 3use atproto_plc::{DidBuilder, Operation, OperationChainValidator, SigningKey}; 4use std::collections::HashMap; 5 6#[test] 7fn test_genesis_operation_validation() { 8 let key = SigningKey::generate_p256(); 9 let did_key = key.to_did_key(); 10 11 let unsigned = Operation::new_genesis(vec![did_key], HashMap::new(), vec![], HashMap::new()); 12 13 let signed = unsigned.sign(&key).unwrap(); 14 15 // Validate single genesis operation 16 let state = OperationChainValidator::validate_chain(&[signed]).unwrap(); 17 assert_eq!(state.rotation_keys.len(), 1); 18} 19 20#[test] 21fn test_empty_chain_validation() { 22 let result = OperationChainValidator::validate_chain(&[]); 23 assert!(result.is_err()); 24} 25 26#[test] 27fn test_non_genesis_first_operation() { 28 let key = SigningKey::generate_p256(); 29 let did_key = key.to_did_key(); 30 31 // Create an update operation (non-genesis) 32 let unsigned = Operation::new_update( 33 vec![did_key], 34 HashMap::new(), 35 vec![], 36 HashMap::new(), 37 "bafyreib2rxk3rybk3aobmv5msrxhgt7h4b4kfzxx4wxltyqu7e7vgq".to_string(), 38 ); 39 40 let signed = unsigned.sign(&key).unwrap(); 41 42 // Should fail because first operation must be genesis 43 let result = OperationChainValidator::validate_chain(&[signed]); 44 assert!(result.is_err()); 45} 46 47#[test] 48fn test_genesis_plus_update_chain() { 49 let key1 = SigningKey::generate_p256(); 50 let key2 = SigningKey::generate_k256(); 51 52 let did_key1 = key1.to_did_key(); 53 let did_key2 = key2.to_did_key(); 54 55 // Create genesis operation 56 let genesis_unsigned = 57 Operation::new_genesis(vec![did_key1.clone()], HashMap::new(), vec![], HashMap::new()); 58 59 let genesis = genesis_unsigned.sign(&key1).unwrap(); 60 let genesis_cid = genesis.cid().unwrap(); 61 62 // Create update operation 63 let update_unsigned = Operation::new_update( 64 vec![did_key2], 65 HashMap::new(), 66 vec![], 67 HashMap::new(), 68 genesis_cid, 69 ); 70 71 let update = update_unsigned.sign(&key1).unwrap(); // Sign with original key 72 73 // Validate chain 74 let state = OperationChainValidator::validate_chain(&[genesis, update]).unwrap(); 75 assert_eq!(state.rotation_keys.len(), 1); 76} 77 78#[test] 79fn test_operation_cid_computation() { 80 let key = SigningKey::generate_p256(); 81 let did_key = key.to_did_key(); 82 83 let unsigned = Operation::new_genesis(vec![did_key], HashMap::new(), vec![], HashMap::new()); 84 85 let signed = unsigned.sign(&key).unwrap(); 86 87 // Compute CID 88 let cid = signed.cid().unwrap(); 89 assert!(cid.starts_with('b')); // CIDv1 in base32 90 91 // CID should be deterministic for same operation 92 let cid2 = signed.cid().unwrap(); 93 assert_eq!(cid, cid2); 94} 95 96#[test] 97fn test_operation_signature_verification() { 98 let key = SigningKey::generate_p256(); 99 let did_key = key.to_did_key(); 100 101 let unsigned = Operation::new_genesis(vec![did_key.clone()], HashMap::new(), vec![], HashMap::new()); 102 103 let signed = unsigned.sign(&key).unwrap(); 104 105 // Verify with correct key 106 let verifying_key = key.verifying_key(); 107 assert!(signed.verify(&[verifying_key]).is_ok()); 108 109 // Verify with wrong key should fail 110 let wrong_key = SigningKey::generate_p256(); 111 let wrong_verifying_key = wrong_key.verifying_key(); 112 assert!(signed.verify(&[wrong_verifying_key]).is_err()); 113} 114 115#[test] 116fn test_tombstone_operation() { 117 let key = SigningKey::generate_p256(); 118 let did_key = key.to_did_key(); 119 120 // Create genesis 121 let genesis_unsigned = 122 Operation::new_genesis(vec![did_key], HashMap::new(), vec![], HashMap::new()); 123 let genesis = genesis_unsigned.sign(&key).unwrap(); 124 let genesis_cid = genesis.cid().unwrap(); 125 126 // Create tombstone 127 let tombstone_unsigned = Operation::new_tombstone(genesis_cid); 128 let tombstone = tombstone_unsigned.sign(&key).unwrap(); 129 130 // Validate chain with tombstone 131 let state = OperationChainValidator::validate_chain(&[genesis, tombstone]).unwrap(); 132 133 // After tombstone, state should be empty 134 assert!(state.rotation_keys.is_empty()); 135 assert!(state.verification_methods.is_empty()); 136} 137 138#[test] 139fn test_full_workflow_with_builder() { 140 // Create DID with builder 141 let rotation_key = SigningKey::generate_p256(); 142 let signing_key = SigningKey::generate_k256(); 143 144 let (did, operation, keys) = DidBuilder::new() 145 .add_rotation_key(rotation_key) 146 .add_verification_method("atproto".into(), signing_key) 147 .build() 148 .unwrap(); 149 150 // Validate the operation chain 151 let state = OperationChainValidator::validate_chain(&[operation.clone()]).unwrap(); 152 153 assert_eq!(state.rotation_keys.len(), 1); 154 assert_eq!(state.verification_methods.len(), 1); 155 156 // Create DID document 157 let doc = state.to_did_document(&did); 158 assert_eq!(doc.id, did); 159} 160 161#[test] 162fn test_recovery_window_check() { 163 use chrono::{Duration, Utc}; 164 165 let base = Utc::now(); 166 167 // Within window (24 hours) 168 let within = base + Duration::hours(24); 169 assert!(OperationChainValidator::is_within_recovery_window( 170 base, within 171 )); 172 173 // Outside window (100 hours) 174 let outside = base + Duration::hours(100); 175 assert!(!OperationChainValidator::is_within_recovery_window( 176 base, outside 177 )); 178 179 // Exactly at window boundary (72 hours) 180 let boundary = base + Duration::hours(72); 181 assert!(!OperationChainValidator::is_within_recovery_window( 182 base, boundary 183 )); 184 185 // Negative time (before fork) should be invalid 186 let before = base - Duration::hours(1); 187 assert!(!OperationChainValidator::is_within_recovery_window( 188 base, before 189 )); 190}