lib

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

tests.rs (18500B)


      1 use super::*;
      2 use serde_json::json;
      3 
      4 fn hex_64(character: char) -> String {
      5     crate::test_valid_hex_64(character)
      6 }
      7 
      8 fn hex_128(character: char) -> String {
      9     core::iter::repeat_n(character, 128).collect()
     10 }
     11 
     12 fn valid_event_value(content: &str, tags: Vec<Vec<String>>) -> Value {
     13     let pubkey = hex_64('a');
     14     let id = compute_canonical_nip01_event_id(pubkey.as_str(), 1_700_000_000, 1, &tags, content)
     15         .expect("event id")
     16         .into_string();
     17     json!({
     18         "id": id,
     19         "pubkey": pubkey,
     20         "created_at": 1_700_000_000u64,
     21         "kind": 1u32,
     22         "tags": tags,
     23         "content": content,
     24         "sig": hex_128('b')
     25     })
     26 }
     27 
     28 fn raw_json(value: &Value) -> String {
     29     serde_json::to_string(value).expect("event json")
     30 }
     31 
     32 fn valid_event_json(content: &str, tags: Vec<Vec<String>>) -> String {
     33     raw_json(&valid_event_value(content, tags))
     34 }
     35 
     36 fn default_tags() -> Vec<Vec<String>> {
     37     vec![vec!["t".to_owned(), "soil".to_owned()]]
     38 }
     39 
     40 fn tag_with_total_elements(element_count: usize) -> Vec<Vec<String>> {
     41     assert!(element_count > 0);
     42     let mut tag = Vec::with_capacity(element_count);
     43     tag.push("t".to_owned());
     44     tag.resize(element_count, String::new());
     45     vec![tag]
     46 }
     47 
     48 fn valid_envelope_parts(content: &str, tags: Vec<Vec<String>>) -> EventEnvelopeParts {
     49     let pubkey = hex_64('a');
     50     let id = compute_canonical_nip01_event_id(pubkey.as_str(), 1_700_000_000, 1, &tags, content)
     51         .expect("event id")
     52         .into_string();
     53     EventEnvelopeParts {
     54         id,
     55         author: pubkey,
     56         created_at: 1_700_000_000,
     57         kind: 1,
     58         tags,
     59         content: content.to_owned(),
     60         sig: hex_128('b'),
     61     }
     62 }
     63 
     64 #[test]
     65 fn canonical_preimage_escapes_required_json_characters() {
     66     let preimage = canonical_nip01_event_id_preimage(
     67         hex_64('a').as_str(),
     68         10,
     69         1,
     70         &[vec!["t".to_owned(), "line\nsoil".to_owned()]],
     71         "\"\\\n\r\t\u{08}\u{0c}\u{01}",
     72     )
     73     .expect("preimage");
     74 
     75     assert_eq!(
     76         preimage,
     77         r#"[0,"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",10,1,[["t","line\nsoil"]],"\"\\\n\r\t\b\f\u0001"]"#
     78     );
     79 }
     80 
     81 #[test]
     82 fn parses_wire_json_preserves_extra_and_verifies_id() {
     83     let mut value = valid_event_value("hello", default_tags());
     84     value
     85         .as_object_mut()
     86         .expect("object")
     87         .insert("client".to_owned(), json!({"name":"radroots-test"}));
     88     let wire = Nip01EventWire::parse_json(raw_json(&value).as_str()).expect("wire");
     89 
     90     assert_eq!(wire.pubkey, hex_64('a'));
     91     assert_eq!(wire.created_at, 1_700_000_000);
     92     assert_eq!(wire.kind, 1);
     93     assert_eq!(wire.tags, default_tags());
     94     assert_eq!(wire.content, "hello");
     95     assert_eq!(
     96         wire.extra.get("client").expect("client extra"),
     97         &json!({"name":"radroots-test"})
     98     );
     99     wire.verify_id().expect("verified id");
    100     assert_eq!(
    101         wire.canonical_id_preimage().expect("preimage"),
    102         r#"[0,"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",1700000000,1,[["t","soil"]],"hello"]"#
    103     );
    104 }
    105 
    106 #[test]
    107 fn into_envelope_verifies_id_before_domain_conversion() {
    108     let wire = Nip01EventWire::parse_json(valid_event_json("hello", default_tags()).as_str())
    109         .expect("wire");
    110 
    111     let envelope = wire.clone().into_envelope().expect("envelope");
    112     assert_eq!(envelope.id_hex(), wire.id);
    113     assert_eq!(envelope.content(), "hello");
    114 
    115     let mut tampered_id = wire.clone();
    116     tampered_id.id = hex_64('f');
    117     assert!(matches!(
    118         tampered_id.into_envelope(),
    119         Err(EventWireError::EventIdMismatch { .. })
    120     ));
    121 
    122     let mut tampered_content = wire;
    123     tampered_content.content = "tampered".to_owned();
    124     assert!(matches!(
    125         tampered_content.into_envelope(),
    126         Err(EventWireError::EventIdMismatch { .. })
    127     ));
    128 }
    129 
    130 #[test]
    131 fn unverified_parse_preserves_explicit_id_verification_stage() {
    132     let mut value = valid_event_value("hello", default_tags());
    133     value
    134         .as_object_mut()
    135         .expect("object")
    136         .insert("id".to_owned(), json!(hex_64('f')));
    137     let raw = raw_json(&value);
    138 
    139     let wire = Nip01EventWire::parse_json_unverified(raw.as_str()).expect("unverified wire");
    140     assert!(matches!(
    141         wire.verify_id(),
    142         Err(EventWireError::EventIdMismatch { .. })
    143     ));
    144     wire.into_unverified_envelope()
    145         .expect("structurally valid unverified envelope");
    146 
    147     assert!(matches!(
    148         Nip01EventWire::parse_json(raw.as_str()),
    149         Err(EventWireError::EventIdMismatch { .. })
    150     ));
    151 }
    152 
    153 #[test]
    154 fn into_envelope_ignores_extra_for_id_and_propagates_domain_limits() {
    155     let mut value = valid_event_value("hello", default_tags());
    156     value
    157         .as_object_mut()
    158         .expect("object")
    159         .insert("client".to_owned(), json!("radroots-test"));
    160     let wire = Nip01EventWire::parse_json(raw_json(&value).as_str()).expect("wire");
    161     let envelope = wire.into_envelope().expect("envelope");
    162     assert_eq!(envelope.content(), "hello");
    163 
    164     let content = core::iter::repeat_n('x', DEFAULT_CONTENT_MAX_BYTES + 1).collect::<String>();
    165     let tags = default_tags();
    166     let pubkey = hex_64('a');
    167     let id = compute_canonical_nip01_event_id(
    168         pubkey.as_str(),
    169         1_700_000_000,
    170         1,
    171         &tags,
    172         content.as_str(),
    173     )
    174     .expect("event id")
    175     .into_string();
    176     let wire = Nip01EventWire {
    177         id,
    178         pubkey,
    179         created_at: 1_700_000_000,
    180         kind: 1,
    181         tags,
    182         content,
    183         sig: hex_128('b'),
    184         extra: Default::default(),
    185     };
    186 
    187     assert!(matches!(
    188         wire.into_envelope(),
    189         Err(EventWireError::Envelope(
    190             EventEnvelopeError::ContentTooLarge { .. }
    191         ))
    192     ));
    193 }
    194 
    195 #[cfg(feature = "serde")]
    196 #[test]
    197 fn serde_flatten_preserves_extra_fields() {
    198     let mut value = valid_event_value("hello", default_tags());
    199     value
    200         .as_object_mut()
    201         .expect("object")
    202         .insert("client".to_owned(), json!("radroots-test"));
    203     let wire = Nip01EventWire::parse_json(raw_json(&value).as_str()).expect("wire");
    204     let encoded = serde_json::to_value(&wire).expect("encoded");
    205 
    206     assert_eq!(encoded.get("client"), Some(&json!("radroots-test")));
    207     assert_eq!(encoded.get("id"), Some(&Value::String(wire.id)));
    208 }
    209 
    210 #[test]
    211 fn parse_json_rejects_required_field_errors_and_id_mismatch() {
    212     let mut value = valid_event_value("hello", default_tags());
    213     value.as_object_mut().expect("object").remove("id");
    214     assert!(matches!(
    215         Nip01EventWire::parse_json(raw_json(&value).as_str()),
    216         Err(EventWireError::MissingField("id"))
    217     ));
    218 
    219     let mut value = valid_event_value("hello", default_tags());
    220     value
    221         .as_object_mut()
    222         .expect("object")
    223         .insert("pubkey".to_owned(), json!("not-hex"));
    224     assert!(matches!(
    225         Nip01EventWire::parse_json(raw_json(&value).as_str()),
    226         Err(EventWireError::InvalidIdentifier {
    227             field: "pubkey",
    228             ..
    229         })
    230     ));
    231 
    232     let mut value = valid_event_value("hello", default_tags());
    233     value
    234         .as_object_mut()
    235         .expect("object")
    236         .insert("sig".to_owned(), json!(hex_64('b')));
    237     assert!(matches!(
    238         Nip01EventWire::parse_json(raw_json(&value).as_str()),
    239         Err(EventWireError::InvalidIdentifier { field: "sig", .. })
    240     ));
    241 
    242     let mut value = valid_event_value("hello", default_tags());
    243     value
    244         .as_object_mut()
    245         .expect("object")
    246         .insert("id".to_owned(), json!(hex_64('f')));
    247     assert!(matches!(
    248         Nip01EventWire::parse_json(raw_json(&value).as_str()),
    249         Err(EventWireError::EventIdMismatch { .. })
    250     ));
    251 
    252     let mut value = valid_event_value("hello", default_tags());
    253     value
    254         .as_object_mut()
    255         .expect("object")
    256         .insert("id".to_owned(), json!(hex_64('A')));
    257     assert!(matches!(
    258         Nip01EventWire::parse_json(raw_json(&value).as_str()),
    259         Err(EventWireError::NonCanonicalIdentifier { field: "id" })
    260     ));
    261 }
    262 
    263 #[test]
    264 fn parse_json_rejects_tag_shape_errors() {
    265     let mut value = valid_event_value("hello", default_tags());
    266     value
    267         .as_object_mut()
    268         .expect("object")
    269         .insert("tags".to_owned(), json!([[]]));
    270     assert!(matches!(
    271         Nip01EventWire::parse_json(raw_json(&value).as_str()),
    272         Err(EventWireError::EmptyTag { index: 0 })
    273     ));
    274 
    275     let mut value = valid_event_value("hello", default_tags());
    276     value
    277         .as_object_mut()
    278         .expect("object")
    279         .insert("tags".to_owned(), json!([["", "soil"]]));
    280     assert!(matches!(
    281         Nip01EventWire::parse_json(raw_json(&value).as_str()),
    282         Err(EventWireError::EmptyTagKey { index: 0 })
    283     ));
    284 
    285     let mut value = valid_event_value("hello", default_tags());
    286     value
    287         .as_object_mut()
    288         .expect("object")
    289         .insert("tags".to_owned(), json!([["t\n", "soil"]]));
    290     assert!(matches!(
    291         Nip01EventWire::parse_json(raw_json(&value).as_str()),
    292         Err(EventWireError::ControlCharacterTagKey { index: 0 })
    293     ));
    294 }
    295 
    296 #[test]
    297 fn parse_json_rejects_resource_budget_violations() {
    298     let raw = valid_event_json("hello", default_tags());
    299     assert!(matches!(
    300         Nip01EventWire::parse_json_with_limits(
    301             raw.as_str(),
    302             EventWireLimits {
    303                 max_raw_json_bytes: 1,
    304                 ..EventWireLimits::default()
    305             }
    306         ),
    307         Err(EventWireError::RawJsonTooLarge { .. })
    308     ));
    309 
    310     let raw = valid_event_json("hello", default_tags());
    311     assert!(matches!(
    312         Nip01EventWire::parse_json_with_limits(
    313             raw.as_str(),
    314             EventWireLimits {
    315                 max_content_bytes: 1,
    316                 ..EventWireLimits::default()
    317             }
    318         ),
    319         Err(EventWireError::ContentTooLarge { .. })
    320     ));
    321 
    322     let raw = valid_event_json("hello", default_tags());
    323     assert!(matches!(
    324         Nip01EventWire::parse_json_with_limits(
    325             raw.as_str(),
    326             EventWireLimits {
    327                 max_tag_count: 0,
    328                 ..EventWireLimits::default()
    329             }
    330         ),
    331         Err(EventWireError::TooManyTags { .. })
    332     ));
    333 
    334     let raw = valid_event_json("hello", default_tags());
    335     assert_eq!(
    336         Nip01EventWire::parse_json_with_limits(
    337             raw.as_str(),
    338             EventWireLimits {
    339                 max_total_tag_elements: 1,
    340                 ..EventWireLimits::default()
    341             }
    342         ),
    343         Err(EventWireError::TooManyTagElements { max: 1, actual: 2 })
    344     );
    345 
    346     let raw = valid_event_json("hello", vec![vec!["t".to_owned(), "soil".to_owned()]]);
    347     assert!(matches!(
    348         Nip01EventWire::parse_json_with_limits(
    349             raw.as_str(),
    350             EventWireLimits {
    351                 max_tag_element_bytes: 1,
    352                 ..EventWireLimits::default()
    353             }
    354         ),
    355         Err(EventWireError::TagElementTooLarge { .. })
    356     ));
    357 
    358     let raw = valid_event_json("hello", default_tags());
    359     assert!(matches!(
    360         Nip01EventWire::parse_json_with_limits(
    361             raw.as_str(),
    362             EventWireLimits {
    363                 max_total_tag_bytes: 1,
    364                 ..EventWireLimits::default()
    365             }
    366         ),
    367         Err(EventWireError::TagsTooLarge { .. })
    368     ));
    369 
    370     let mut value = valid_event_value("hello", default_tags());
    371     value
    372         .as_object_mut()
    373         .expect("object")
    374         .insert("client".to_owned(), json!("radroots-test"));
    375     let raw = raw_json(&value);
    376     assert!(matches!(
    377         Nip01EventWire::parse_json_with_limits(
    378             raw.as_str(),
    379             EventWireLimits {
    380                 max_extra_fields: 0,
    381                 ..EventWireLimits::default()
    382             }
    383         ),
    384         Err(EventWireError::TooManyExtraFields { .. })
    385     ));
    386 
    387     assert!(matches!(
    388         Nip01EventWire::parse_json_with_limits(
    389             raw.as_str(),
    390             EventWireLimits {
    391                 max_total_extra_json_bytes: 1,
    392                 ..EventWireLimits::default()
    393             }
    394         ),
    395         Err(EventWireError::ExtraJsonTooLarge { .. })
    396     ));
    397 }
    398 
    399 #[test]
    400 fn wire_accepts_exact_total_tag_element_boundary() {
    401     let raw = valid_event_json("hello", default_tags());
    402     let wire = Nip01EventWire::parse_json_with_limits(
    403         raw.as_str(),
    404         EventWireLimits {
    405             max_total_tag_elements: 2,
    406             ..EventWireLimits::default()
    407         },
    408     )
    409     .expect("wire at exact tag element boundary");
    410 
    411     assert_eq!(wire.tags, default_tags());
    412 }
    413 
    414 #[test]
    415 fn wire_and_envelope_share_default_total_tag_element_boundary() {
    416     let exact_tags = tag_with_total_elements(DEFAULT_TAG_TOTAL_ELEMENT_MAX_COUNT);
    417     Nip01EventWire::parse_json(valid_event_json("", exact_tags.clone()).as_str())
    418         .expect("wire at default tag element boundary");
    419     EventEnvelope::new(valid_envelope_parts("", exact_tags))
    420         .expect("envelope at default tag element boundary");
    421 
    422     let overflow_elements = DEFAULT_TAG_TOTAL_ELEMENT_MAX_COUNT + 1;
    423     let overflow_tags = tag_with_total_elements(overflow_elements);
    424     assert_eq!(
    425         Nip01EventWire::parse_json(valid_event_json("", overflow_tags.clone()).as_str()),
    426         Err(EventWireError::TooManyTagElements {
    427             max: DEFAULT_TAG_TOTAL_ELEMENT_MAX_COUNT,
    428             actual: overflow_elements,
    429         })
    430     );
    431     assert_eq!(
    432         EventEnvelope::new(valid_envelope_parts("", overflow_tags)),
    433         Err(EventEnvelopeError::TooManyTagElements {
    434             max: DEFAULT_TAG_TOTAL_ELEMENT_MAX_COUNT,
    435             actual: overflow_elements,
    436         })
    437     );
    438 }
    439 
    440 #[test]
    441 #[cfg_attr(coverage_nightly, coverage(off))]
    442 fn wire_parser_and_error_contracts_cover_all_typed_failures() {
    443     let parse_error = EventId::parse("bad").expect_err("invalid id");
    444     for error in [
    445         EventWireError::Json("bad json".to_owned()),
    446         EventWireError::RootNotObject,
    447         EventWireError::MissingField("id"),
    448         EventWireError::InvalidField("kind"),
    449         EventWireError::InvalidIdentifier {
    450             field: "id",
    451             error: parse_error.clone(),
    452         },
    453         EventWireError::NonCanonicalIdentifier { field: "id" },
    454         EventWireError::RawJsonTooLarge { max: 1, actual: 2 },
    455         EventWireError::ContentTooLarge { max: 1, actual: 2 },
    456         EventWireError::TooManyTags { max: 1, actual: 2 },
    457         EventWireError::TooManyTagElements { max: 1, actual: 2 },
    458         EventWireError::EmptyTag { index: 1 },
    459         EventWireError::EmptyTagKey { index: 1 },
    460         EventWireError::ControlCharacterTagKey { index: 1 },
    461         EventWireError::TagElementTooLarge {
    462             tag_index: 1,
    463             element_index: 2,
    464             max: 3,
    465             actual: 4,
    466         },
    467         EventWireError::TagsTooLarge { max: 1, actual: 2 },
    468         EventWireError::TooManyExtraFields { max: 1, actual: 2 },
    469         EventWireError::ExtraJsonTooLarge { max: 1, actual: 2 },
    470         EventWireError::from(CanonicalEventIdError::InvalidPubkey(parse_error.clone())),
    471         EventWireError::from(EventEnvelopeError::NonCanonicalId),
    472         EventWireError::EventIdMismatch {
    473             declared: "a".to_owned(),
    474             computed: "b".to_owned(),
    475         },
    476     ] {
    477         assert!(!error.to_string().is_empty());
    478     }
    479 
    480     for raw in ["{", "[]", "null"] {
    481         assert!(Nip01EventWire::parse_json(raw).is_err());
    482     }
    483 
    484     for (field, replacement) in [
    485         ("id", json!(7)),
    486         ("id", json!("bad")),
    487         ("pubkey", json!(7)),
    488         ("pubkey", json!(hex_64('A'))),
    489         ("created_at", json!("bad")),
    490         ("created_at", json!(-1)),
    491         ("kind", json!("bad")),
    492         ("kind", json!(u64::from(u32::MAX) + 1)),
    493         ("tags", json!("bad")),
    494         ("tags", json!(["bad"])),
    495         ("tags", json!([["t", 7]])),
    496         ("content", json!(7)),
    497         ("sig", json!(7)),
    498         ("sig", json!("bad")),
    499         ("sig", json!(hex_128('B'))),
    500     ] {
    501         let mut value = valid_event_value("hello", default_tags());
    502         value
    503             .as_object_mut()
    504             .expect("object")
    505             .insert(field.to_owned(), replacement);
    506         assert!(Nip01EventWire::parse_json(raw_json(&value).as_str()).is_err());
    507     }
    508 
    509     for field in ["pubkey", "created_at", "kind", "tags", "content", "sig"] {
    510         let mut value = valid_event_value("hello", default_tags());
    511         value.as_object_mut().expect("object").remove(field);
    512         assert!(Nip01EventWire::parse_json(raw_json(&value).as_str()).is_err());
    513     }
    514 }
    515 
    516 #[test]
    517 #[cfg_attr(coverage_nightly, coverage(off))]
    518 fn checked_in_conformance_vectors_match_wire_behavior() {
    519     let vectors =
    520         include_str!("../../../../../contracts/conformance/vectors/event/nip01_wire.v1.json");
    521     let document: Value = serde_json::from_str(vectors).expect("vectors json");
    522     let entries = document
    523         .get("vectors")
    524         .and_then(Value::as_array)
    525         .expect("vector entries");
    526 
    527     for entry in entries {
    528         match entry.get("kind").and_then(Value::as_str).expect("kind") {
    529             "event.nip01_wire.valid" => {
    530                 let raw = entry
    531                     .get("input")
    532                     .and_then(|input| input.get("raw_json"))
    533                     .and_then(Value::as_str)
    534                     .expect("raw json");
    535                 let expected = entry.get("expected").expect("expected");
    536                 let wire = Nip01EventWire::parse_json(raw).expect("wire");
    537                 assert_eq!(
    538                     wire.canonical_id_preimage().expect("preimage"),
    539                     expected
    540                         .get("canonical_id_preimage")
    541                         .and_then(Value::as_str)
    542                         .expect("expected preimage")
    543                 );
    544                 assert_eq!(
    545                     wire.computed_event_id().expect("event id").to_hex(),
    546                     expected
    547                         .get("computed_event_id")
    548                         .and_then(Value::as_str)
    549                         .expect("expected event id")
    550                 );
    551             }
    552             "event.nip01_wire.invalid" => {
    553                 let raw = entry
    554                     .get("input")
    555                     .and_then(|input| input.get("raw_json"))
    556                     .and_then(Value::as_str)
    557                     .expect("raw json");
    558                 assert!(Nip01EventWire::parse_json(raw).is_err());
    559             }
    560             other => panic!("unknown event wire vector kind {other}"),
    561         }
    562     }
    563 }