event_ref.rs (12826B)
1 mod common; 2 #[path = "../src/test_fixtures.rs"] 3 mod test_fixtures; 4 5 use radroots_event::envelope::kind::KIND_POST; 6 use radroots_event_codec::decode::EventParseError; 7 use radroots_event_codec::decode::event_ref::{ 8 build_event_ref_tag, find_event_ref_tag, parse_event_ref_tag, parse_nip10_ref_tags, 9 push_nip10_ref_tags, 10 }; 11 use test_fixtures::{RELAY_PRIMARY_WSS, RELAY_SECONDARY_WSS, RELAY_TERTIARY_WSS}; 12 13 fn hex_64(character: char) -> String { 14 core::iter::repeat_n(character, 64).collect() 15 } 16 17 #[test] 18 fn build_and_parse_roundtrip_with_d_tag_and_relays() { 19 let event = common::event_ref_with_d( 20 &hex_64('1'), 21 &hex_64('2'), 22 42, 23 "d-tag", 24 Some(vec!["wss://relay".to_string()]), 25 ); 26 27 let tag = build_event_ref_tag("e", &event); 28 let parsed = parse_event_ref_tag(&tag, "e").unwrap(); 29 30 assert_eq!(parsed.id, event.id); 31 assert_eq!(parsed.author, event.author); 32 assert_eq!(parsed.kind, event.kind); 33 assert_eq!(parsed.d_tag, event.d_tag); 34 assert_eq!(parsed.relays, event.relays); 35 } 36 37 #[test] 38 fn build_and_parse_roundtrip_without_d_tag_or_relays() { 39 let event = common::event_ref(&hex_64('3'), &hex_64('4'), KIND_POST); 40 let tag = build_event_ref_tag("e", &event); 41 42 assert_eq!(tag.len(), 5); 43 assert_eq!(tag[4], ""); 44 45 let parsed = parse_event_ref_tag(&tag, "e").unwrap(); 46 assert_eq!(parsed.id, event.id); 47 assert_eq!(parsed.author, event.author); 48 assert_eq!(parsed.kind, event.kind); 49 assert!(parsed.d_tag.is_none()); 50 assert!(parsed.relays.is_none()); 51 } 52 53 #[test] 54 fn parse_event_ref_tag_rejects_noncanonical_event_pointer_shapes() { 55 let tag = vec![ 56 "e".to_string(), 57 hex_64('5'), 58 hex_64('6'), 59 KIND_POST.to_string(), 60 "wss://relay".to_string(), 61 ]; 62 let err = parse_event_ref_tag(&tag, "e").unwrap_err(); 63 assert!(matches!(err, EventParseError::InvalidTag("e"))); 64 65 let tag = vec![ 66 "e".to_string(), 67 hex_64('5'), 68 common::AUTHOR.to_string(), 69 KIND_POST.to_string(), 70 RELAY_PRIMARY_WSS.to_string(), 71 ]; 72 let err = parse_event_ref_tag(&tag, "e").unwrap_err(); 73 assert!(matches!(err, EventParseError::InvalidTag("e"))); 74 75 let tag = vec![ 76 "e".to_string(), 77 hex_64('5'), 78 hex_64('6'), 79 KIND_POST.to_string(), 80 ]; 81 let err = parse_event_ref_tag(&tag, "e").unwrap_err(); 82 assert!(matches!(err, EventParseError::InvalidTag("e"))); 83 84 let tag = vec![ 85 "e".to_string(), 86 "not-hex".to_string(), 87 hex_64('6'), 88 KIND_POST.to_string(), 89 String::new(), 90 ]; 91 let err = parse_event_ref_tag(&tag, "e").unwrap_err(); 92 assert!(matches!(err, EventParseError::InvalidTag("e"))); 93 94 let tag = vec![ 95 "e".to_string(), 96 hex_64('5'), 97 "not-hex".to_string(), 98 KIND_POST.to_string(), 99 String::new(), 100 ]; 101 let err = parse_event_ref_tag(&tag, "e").unwrap_err(); 102 assert!(matches!(err, EventParseError::InvalidTag("e"))); 103 104 let tag = vec![ 105 "e".to_string(), 106 hex_64('5'), 107 hex_64('6'), 108 KIND_POST.to_string(), 109 "bad d-tag".to_string(), 110 ]; 111 let err = parse_event_ref_tag(&tag, "e").unwrap_err(); 112 assert!(matches!(err, EventParseError::InvalidTag("e"))); 113 114 let tag = vec![ 115 "e".to_string(), 116 hex_64('5'), 117 hex_64('6'), 118 KIND_POST.to_string(), 119 String::new(), 120 "https://relay".to_string(), 121 ]; 122 let err = parse_event_ref_tag(&tag, "e").unwrap_err(); 123 assert!(matches!(err, EventParseError::InvalidTag("e"))); 124 } 125 126 #[test] 127 fn parse_event_ref_tag_rejects_invalid_kind() { 128 let tag = vec![ 129 "e".to_string(), 130 hex_64('7'), 131 hex_64('8'), 132 "bad".to_string(), 133 String::new(), 134 ]; 135 136 let err = parse_event_ref_tag(&tag, "e").unwrap_err(); 137 assert!(matches!(err, EventParseError::InvalidNumber("e", _))); 138 } 139 140 #[test] 141 fn parse_event_ref_tag_rejects_wrong_tag_name_and_missing_fields() { 142 let tag = vec!["p".to_string(), "id".to_string()]; 143 let err = parse_event_ref_tag(&tag, "e").unwrap_err(); 144 assert!(matches!(err, EventParseError::InvalidTag("e"))); 145 146 let tag = vec![ 147 "e".to_string(), 148 hex_64('9'), 149 hex_64('a'), 150 KIND_POST.to_string(), 151 ]; 152 let err = parse_event_ref_tag(&tag, "e").unwrap_err(); 153 assert!(matches!(err, EventParseError::InvalidTag("e"))); 154 } 155 156 #[test] 157 fn parse_event_ref_tag_rejects_missing_required_values() { 158 let err = parse_event_ref_tag(&["e".to_string()], "e").unwrap_err(); 159 assert!(matches!(err, EventParseError::InvalidTag("e"))); 160 161 let err = parse_event_ref_tag(&["e".to_string(), "id".to_string()], "e").unwrap_err(); 162 assert!(matches!(err, EventParseError::InvalidTag("e"))); 163 164 let err = parse_event_ref_tag( 165 &["e".to_string(), "id".to_string(), "author".to_string()], 166 "e", 167 ) 168 .unwrap_err(); 169 assert!(matches!(err, EventParseError::InvalidTag("e"))); 170 } 171 172 #[test] 173 fn find_event_ref_tag_locates_first_match() { 174 let id = hex_64('b'); 175 let event = common::event_ref(&id, common::AUTHOR, KIND_POST); 176 let tags = vec![ 177 vec!["p".to_string(), "pubkey".to_string()], 178 build_event_ref_tag("e", &event), 179 ]; 180 181 let found = find_event_ref_tag(&tags, "e").unwrap(); 182 assert_eq!(found[0], "e"); 183 assert_eq!(found[1], id); 184 } 185 186 #[test] 187 fn push_and_parse_nip10_ref_tags_roundtrip_with_and_without_a_tag() { 188 let event = common::event_ref_with_d( 189 "id", 190 common::AUTHOR, 191 KIND_POST, 192 "AAAAAAAAAAAAAAAAAAAAAA", 193 Some(vec![RELAY_PRIMARY_WSS.to_string()]), 194 ); 195 let mut tags = Vec::new(); 196 push_nip10_ref_tags(&mut tags, &event, "e", "p", "k", "a"); 197 let parsed = parse_nip10_ref_tags(&tags, "e", "p", "k", "a").unwrap(); 198 assert_eq!(parsed.id, event.id); 199 assert_eq!(parsed.author, event.author); 200 assert_eq!(parsed.kind, event.kind); 201 assert_eq!(parsed.d_tag, event.d_tag); 202 assert_eq!(parsed.relays, event.relays); 203 204 let event = common::event_ref("id2", common::AUTHOR, KIND_POST); 205 let mut tags = Vec::new(); 206 push_nip10_ref_tags(&mut tags, &event, "e", "p", "k", "a"); 207 let parsed = parse_nip10_ref_tags(&tags, "e", "p", "k", "a").unwrap(); 208 assert_eq!(parsed.id, event.id); 209 assert_eq!(parsed.author, event.author); 210 assert_eq!(parsed.kind, event.kind); 211 assert!(parsed.d_tag.is_none()); 212 213 let event = common::event_ref_with_d( 214 "id3", 215 common::AUTHOR, 216 KIND_POST, 217 "AAAAAAAAAAAAAAAAAAAAAA", 218 None, 219 ); 220 let mut tags = Vec::new(); 221 push_nip10_ref_tags(&mut tags, &event, "e", "p", "k", "a"); 222 let a_tag = tags 223 .iter() 224 .find(|tag| tag.first().map(|v| v.as_str()) == Some("a")) 225 .expect("a tag"); 226 assert_eq!(a_tag.len(), 2); 227 let parsed = parse_nip10_ref_tags(&tags, "e", "p", "k", "a").unwrap(); 228 assert_eq!(parsed.d_tag, event.d_tag); 229 assert!(parsed.relays.is_none()); 230 } 231 232 #[test] 233 fn parse_nip10_ref_tags_rejects_missing_or_invalid_required_tags() { 234 let err = parse_nip10_ref_tags(&[], "e", "p", "k", "a").unwrap_err(); 235 assert!(matches!(err, EventParseError::MissingTag("e"))); 236 237 let tags = vec![ 238 vec!["e".to_string(), "".to_string()], 239 vec!["p".to_string(), "author".to_string()], 240 vec!["k".to_string(), KIND_POST.to_string()], 241 ]; 242 let err = parse_nip10_ref_tags(&tags, "e", "p", "k", "a").unwrap_err(); 243 assert!(matches!(err, EventParseError::InvalidTag("e"))); 244 245 let tags = vec![ 246 vec!["e".to_string(), "id".to_string()], 247 vec!["p".to_string(), "".to_string()], 248 vec!["k".to_string(), KIND_POST.to_string()], 249 ]; 250 let err = parse_nip10_ref_tags(&tags, "e", "p", "k", "a").unwrap_err(); 251 assert!(matches!(err, EventParseError::InvalidTag("p"))); 252 253 let tags = vec![ 254 vec!["e".to_string(), "id".to_string()], 255 vec!["p".to_string(), common::AUTHOR.to_string()], 256 vec!["k".to_string(), "bad-kind".to_string()], 257 ]; 258 let err = parse_nip10_ref_tags(&tags, "e", "p", "k", "a").unwrap_err(); 259 assert!(matches!(err, EventParseError::InvalidNumber("k", _))); 260 } 261 262 #[test] 263 fn parse_nip10_ref_tags_rejects_missing_required_values() { 264 let tags = vec![ 265 vec!["e".to_string()], 266 vec!["p".to_string(), "author".to_string()], 267 vec!["k".to_string(), KIND_POST.to_string()], 268 ]; 269 let err = parse_nip10_ref_tags(&tags, "e", "p", "k", "a").unwrap_err(); 270 assert!(matches!(err, EventParseError::InvalidTag("e"))); 271 272 let tags = vec![ 273 vec!["e".to_string(), "id".to_string()], 274 vec!["p".to_string()], 275 vec!["k".to_string(), KIND_POST.to_string()], 276 ]; 277 let err = parse_nip10_ref_tags(&tags, "e", "p", "k", "a").unwrap_err(); 278 assert!(matches!(err, EventParseError::InvalidTag("p"))); 279 280 let tags = vec![ 281 vec!["e".to_string(), "id".to_string()], 282 vec!["p".to_string(), common::AUTHOR.to_string()], 283 vec!["k".to_string()], 284 ]; 285 let err = parse_nip10_ref_tags(&tags, "e", "p", "k", "a").unwrap_err(); 286 assert!(matches!(err, EventParseError::InvalidTag("k"))); 287 } 288 289 #[test] 290 fn parse_nip10_ref_tags_rejects_missing_p_and_k_tags() { 291 let missing_p = vec![ 292 vec!["e".to_string(), "id".to_string()], 293 vec!["k".to_string(), KIND_POST.to_string()], 294 ]; 295 let err = parse_nip10_ref_tags(&missing_p, "e", "p", "k", "a").unwrap_err(); 296 assert!(matches!(err, EventParseError::MissingTag("p"))); 297 298 let missing_k = vec![ 299 vec!["e".to_string(), "id".to_string()], 300 vec!["p".to_string(), common::AUTHOR.to_string()], 301 ]; 302 let err = parse_nip10_ref_tags(&missing_k, "e", "p", "k", "a").unwrap_err(); 303 assert!(matches!(err, EventParseError::MissingTag("k"))); 304 } 305 306 #[test] 307 fn parse_nip10_ref_tags_prefers_e_relays_and_can_fall_back_to_a_relays() { 308 let tags = vec![ 309 vec!["e".to_string(), "id".to_string()], 310 vec!["p".to_string(), common::AUTHOR.to_string()], 311 vec!["k".to_string(), KIND_POST.to_string()], 312 vec![ 313 "a".to_string(), 314 format!( 315 "{}:{}:{}", 316 KIND_POST, 317 common::AUTHOR, 318 "AAAAAAAAAAAAAAAAAAAAAA" 319 ), 320 RELAY_SECONDARY_WSS.to_string(), 321 ], 322 ]; 323 let parsed = parse_nip10_ref_tags(&tags, "e", "p", "k", "a").unwrap(); 324 assert_eq!(parsed.d_tag.as_deref(), Some("AAAAAAAAAAAAAAAAAAAAAA")); 325 assert_eq!(parsed.relays, Some(vec![RELAY_SECONDARY_WSS.to_string()])); 326 327 let tags = vec![ 328 vec![ 329 "e".to_string(), 330 "id".to_string(), 331 RELAY_PRIMARY_WSS.to_string(), 332 ], 333 vec!["p".to_string(), common::AUTHOR.to_string()], 334 vec!["k".to_string(), KIND_POST.to_string()], 335 vec![ 336 "a".to_string(), 337 format!( 338 "{}:{}:{}", 339 KIND_POST, 340 common::AUTHOR, 341 "AAAAAAAAAAAAAAAAAAAAAA" 342 ), 343 RELAY_SECONDARY_WSS.to_string(), 344 ], 345 ]; 346 let parsed = parse_nip10_ref_tags(&tags, "e", "p", "k", "a").unwrap(); 347 assert_eq!(parsed.relays, Some(vec![RELAY_PRIMARY_WSS.to_string()])); 348 } 349 350 #[test] 351 fn parse_nip10_ref_tags_skips_invalid_a_tags_until_match() { 352 let tags = vec![ 353 vec!["e".to_string(), "id".to_string()], 354 vec!["p".to_string(), common::AUTHOR.to_string()], 355 vec!["k".to_string(), KIND_POST.to_string()], 356 vec!["a".to_string()], 357 vec![ 358 "a".to_string(), 359 format!( 360 "{}:{}:{}", 361 KIND_POST + 1, 362 common::AUTHOR, 363 "AAAAAAAAAAAAAAAAAAAAAA" 364 ), 365 RELAY_PRIMARY_WSS.to_string(), 366 ], 367 vec![ 368 "a".to_string(), 369 format!( 370 "{}:{}:{}", 371 KIND_POST, "other-author", "AAAAAAAAAAAAAAAAAAAAAA" 372 ), 373 RELAY_SECONDARY_WSS.to_string(), 374 ], 375 vec![ 376 "a".to_string(), 377 format!("{}:{}:", KIND_POST, common::AUTHOR), 378 RELAY_TERTIARY_WSS.to_string(), 379 ], 380 ]; 381 382 let parsed = parse_nip10_ref_tags(&tags, "e", "p", "k", "a").unwrap(); 383 assert!(parsed.d_tag.is_none()); 384 assert_eq!(parsed.relays, Some(vec![RELAY_TERTIARY_WSS.to_string()])); 385 386 let tags = vec![ 387 vec!["e".to_string(), "id".to_string()], 388 vec!["p".to_string(), common::AUTHOR.to_string()], 389 vec!["k".to_string(), KIND_POST.to_string()], 390 vec!["a".to_string(), format!("{}:{}", KIND_POST, common::AUTHOR)], 391 ]; 392 let parsed = parse_nip10_ref_tags(&tags, "e", "p", "k", "a").unwrap(); 393 assert!(parsed.d_tag.is_none()); 394 assert!(parsed.relays.is_none()); 395 }