//! Tests for operation chain validation use atproto_plc::{DidBuilder, Operation, OperationChainValidator, SigningKey}; use std::collections::HashMap; #[test] fn test_genesis_operation_validation() { let key = SigningKey::generate_p256(); let did_key = key.to_did_key(); let unsigned = Operation::new_genesis(vec![did_key], HashMap::new(), vec![], HashMap::new()); let signed = unsigned.sign(&key).unwrap(); // Validate single genesis operation let state = OperationChainValidator::validate_chain(&[signed]).unwrap(); assert_eq!(state.rotation_keys.len(), 1); } #[test] fn test_empty_chain_validation() { let result = OperationChainValidator::validate_chain(&[]); assert!(result.is_err()); } #[test] fn test_non_genesis_first_operation() { let key = SigningKey::generate_p256(); let did_key = key.to_did_key(); // Create an update operation (non-genesis) let unsigned = Operation::new_update( vec![did_key], HashMap::new(), vec![], HashMap::new(), "bafyreib2rxk3rybk3aobmv5msrxhgt7h4b4kfzxx4wxltyqu7e7vgq".to_string(), ); let signed = unsigned.sign(&key).unwrap(); // Should fail because first operation must be genesis let result = OperationChainValidator::validate_chain(&[signed]); assert!(result.is_err()); } #[test] fn test_genesis_plus_update_chain() { let key1 = SigningKey::generate_p256(); let key2 = SigningKey::generate_k256(); let did_key1 = key1.to_did_key(); let did_key2 = key2.to_did_key(); // Create genesis operation let genesis_unsigned = Operation::new_genesis(vec![did_key1.clone()], HashMap::new(), vec![], HashMap::new()); let genesis = genesis_unsigned.sign(&key1).unwrap(); let genesis_cid = genesis.cid().unwrap(); // Create update operation let update_unsigned = Operation::new_update( vec![did_key2], HashMap::new(), vec![], HashMap::new(), genesis_cid, ); let update = update_unsigned.sign(&key1).unwrap(); // Sign with original key // Validate chain let state = OperationChainValidator::validate_chain(&[genesis, update]).unwrap(); assert_eq!(state.rotation_keys.len(), 1); } #[test] fn test_operation_cid_computation() { let key = SigningKey::generate_p256(); let did_key = key.to_did_key(); let unsigned = Operation::new_genesis(vec![did_key], HashMap::new(), vec![], HashMap::new()); let signed = unsigned.sign(&key).unwrap(); // Compute CID let cid = signed.cid().unwrap(); assert!(cid.starts_with('b')); // CIDv1 in base32 // CID should be deterministic for same operation let cid2 = signed.cid().unwrap(); assert_eq!(cid, cid2); } #[test] fn test_operation_signature_verification() { let key = SigningKey::generate_p256(); let did_key = key.to_did_key(); let unsigned = Operation::new_genesis(vec![did_key.clone()], HashMap::new(), vec![], HashMap::new()); let signed = unsigned.sign(&key).unwrap(); // Verify with correct key let verifying_key = key.verifying_key(); assert!(signed.verify(&[verifying_key]).is_ok()); // Verify with wrong key should fail let wrong_key = SigningKey::generate_p256(); let wrong_verifying_key = wrong_key.verifying_key(); assert!(signed.verify(&[wrong_verifying_key]).is_err()); } #[test] fn test_tombstone_operation() { let key = SigningKey::generate_p256(); let did_key = key.to_did_key(); // Create genesis let genesis_unsigned = Operation::new_genesis(vec![did_key], HashMap::new(), vec![], HashMap::new()); let genesis = genesis_unsigned.sign(&key).unwrap(); let genesis_cid = genesis.cid().unwrap(); // Create tombstone let tombstone_unsigned = Operation::new_tombstone(genesis_cid); let tombstone = tombstone_unsigned.sign(&key).unwrap(); // Validate chain with tombstone let state = OperationChainValidator::validate_chain(&[genesis, tombstone]).unwrap(); // After tombstone, state should be empty assert!(state.rotation_keys.is_empty()); assert!(state.verification_methods.is_empty()); } #[test] fn test_full_workflow_with_builder() { // Create DID with builder let rotation_key = SigningKey::generate_p256(); let signing_key = SigningKey::generate_k256(); let (did, operation, keys) = DidBuilder::new() .add_rotation_key(rotation_key) .add_verification_method("atproto".into(), signing_key) .build() .unwrap(); // Validate the operation chain let state = OperationChainValidator::validate_chain(&[operation.clone()]).unwrap(); assert_eq!(state.rotation_keys.len(), 1); assert_eq!(state.verification_methods.len(), 1); // Create DID document let doc = state.to_did_document(&did); assert_eq!(doc.id, did); } #[test] fn test_recovery_window_check() { use chrono::{Duration, Utc}; let base = Utc::now(); // Within window (24 hours) let within = base + Duration::hours(24); assert!(OperationChainValidator::is_within_recovery_window( base, within )); // Outside window (100 hours) let outside = base + Duration::hours(100); assert!(!OperationChainValidator::is_within_recovery_window( base, outside )); // Exactly at window boundary (72 hours) let boundary = base + Duration::hours(72); assert!(!OperationChainValidator::is_within_recovery_window( base, boundary )); // Negative time (before fork) should be invalid let before = base - Duration::hours(1); assert!(!OperationChainValidator::is_within_recovery_window( base, before )); }