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 }