lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README

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 }