Rust and WASM did-method-plc tools and structures
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}