verified_admission_conformance.rs (6009B)
1 #![cfg(feature = "json")] 2 3 use std::{borrow::Cow, fs, path::Path}; 4 5 use radroots_event::{envelope::EventEnvelope, wire::Nip01EventWire}; 6 use radroots_event_codec::{ 7 admission::{RadrootsAdmittedEvent, RadrootsEventAdmissionError, admit_verified_event}, 8 verify::verify_nip01_event, 9 }; 10 use serde::Deserialize; 11 use serde_json::Value; 12 13 const PACKAGED_VECTORS: &str = include_str!("fixtures/verified_admission.v1.json"); 14 const WORKSPACE_VECTOR_PATH: &str = 15 "../../contracts/conformance/vectors/event/verified_admission.v1.json"; 16 const WORKSPACE_CONTRACT_MARKER_PATH: &str = "../../contracts/manifest.toml"; 17 18 #[derive(Debug, Deserialize)] 19 #[serde(deny_unknown_fields)] 20 struct Suite { 21 suite: String, 22 contract_version: String, 23 vectors: Vec<Vector>, 24 } 25 26 #[derive(Debug, Deserialize)] 27 #[serde(deny_unknown_fields)] 28 struct Vector { 29 id: String, 30 kind: String, 31 input: Value, 32 expected: Value, 33 } 34 35 #[test] 36 fn fixed_signed_vectors_execute_the_complete_verified_admission_boundary() { 37 let vectors = conformance_vectors(); 38 let suite: Suite = 39 serde_json::from_str(&vectors).expect("verified admission vectors must parse"); 40 assert_eq!(suite.suite, "verified_event_admission"); 41 assert_eq!(suite.contract_version, "1.0.0"); 42 assert_eq!(suite.vectors.len(), 13); 43 44 for vector in &suite.vectors { 45 execute(vector); 46 } 47 } 48 49 fn conformance_vectors() -> Cow<'static, str> { 50 let workspace_path = Path::new(env!("CARGO_MANIFEST_DIR")).join(WORKSPACE_VECTOR_PATH); 51 match fs::read_to_string(&workspace_path) { 52 Ok(canonical) => { 53 assert_eq!( 54 canonical, 55 PACKAGED_VECTORS, 56 "packaged verified admission vectors must match {}", 57 workspace_path.display() 58 ); 59 Cow::Owned(canonical) 60 } 61 Err(error) 62 if error.kind() == std::io::ErrorKind::NotFound 63 && !Path::new(env!("CARGO_MANIFEST_DIR")) 64 .join(WORKSPACE_CONTRACT_MARKER_PATH) 65 .is_file() => 66 { 67 Cow::Borrowed(PACKAGED_VECTORS) 68 } 69 Err(error) => panic!("failed to read {}: {error}", workspace_path.display()), 70 } 71 } 72 73 fn execute(vector: &Vector) { 74 let envelope = event_envelope(&vector.input["event"]); 75 let event_id = envelope.id_hex().to_owned(); 76 let verified = verify_nip01_event(envelope) 77 .unwrap_or_else(|error| panic!("{} fixture is not NIP-01 verified: {error}", vector.id)); 78 let result = admit_verified_event(verified); 79 80 match vector.kind.as_str() { 81 "event.admit_verified.valid" => { 82 let admitted = result.unwrap_or_else(|error| panic!("{} failed: {error}", vector.id)); 83 assert_eq!( 84 admitted_variant(&admitted), 85 expected_str(vector, "variant"), 86 "{}", 87 vector.id 88 ); 89 assert_eq!( 90 admitted.contract_id(), 91 expected_str(vector, "contract_id"), 92 "{}", 93 vector.id 94 ); 95 assert_eq!( 96 admitted.event().id_hex(), 97 expected_str(vector, "event_id"), 98 "{}", 99 vector.id 100 ); 101 assert_eq!(admitted.into_verified_event().event().id_hex(), event_id); 102 } 103 "event.admit_verified.invalid" => { 104 let error = match result { 105 Err(error) => error, 106 Ok(_) => panic!("{} unexpectedly admitted", vector.id), 107 }; 108 assert_eq!( 109 admission_error_variant(&error), 110 expected_str(vector, "error_variant"), 111 "{}", 112 vector.id 113 ); 114 assert_eq!( 115 error.code(), 116 expected_str(vector, "error_code"), 117 "{}", 118 vector.id 119 ); 120 assert_eq!(event_id, expected_str(vector, "event_id"), "{}", vector.id); 121 } 122 kind => panic!("{} uses unsupported vector kind {kind}", vector.id), 123 } 124 } 125 126 fn admitted_variant(admitted: &RadrootsAdmittedEvent) -> &'static str { 127 match admitted { 128 RadrootsAdmittedEvent::Profile(_) => "profile", 129 RadrootsAdmittedEvent::RootPost(_) => "root_post", 130 RadrootsAdmittedEvent::Reply(_) => "reply", 131 RadrootsAdmittedEvent::Comment(_) => "comment", 132 RadrootsAdmittedEvent::DeletionRequest(_) => "deletion_request", 133 RadrootsAdmittedEvent::FoodAvailability(_) => "food_availability", 134 RadrootsAdmittedEvent::ContractValidated(_) => "contract_validated", 135 _ => panic!("conformance runner must be updated for a new admission variant"), 136 } 137 } 138 139 fn admission_error_variant(error: &RadrootsEventAdmissionError) -> &'static str { 140 match error { 141 RadrootsEventAdmissionError::ContractMatch(_) => "contract_match", 142 RadrootsEventAdmissionError::ContractValidation(_) => "contract_validation", 143 RadrootsEventAdmissionError::Profile(_) => "profile", 144 RadrootsEventAdmissionError::Post(_) => "post", 145 RadrootsEventAdmissionError::Reply(_) => "reply", 146 RadrootsEventAdmissionError::Comment(_) => "comment", 147 RadrootsEventAdmissionError::DeletionRequest(_) => "deletion_request", 148 RadrootsEventAdmissionError::FoodAvailability(_) => "food_availability", 149 _ => panic!("conformance runner must be updated for a new admission error variant"), 150 } 151 } 152 153 fn event_envelope(value: &Value) -> EventEnvelope { 154 let raw_json = serde_json::to_string(value).expect("serialize fixed event"); 155 Nip01EventWire::parse_json(&raw_json) 156 .expect("fixed signed event wire") 157 .into_envelope() 158 .expect("fixed signed event envelope") 159 } 160 161 fn expected_str<'a>(vector: &'a Vector, field: &str) -> &'a str { 162 vector.expected[field] 163 .as_str() 164 .unwrap_or_else(|| panic!("{}.expected.{field} must be a string", vector.id)) 165 }