message.rs (13175B)
1 #[path = "../src/test_fixtures.rs"] 2 mod test_fixtures; 3 4 use radroots_events::{ 5 RadrootsNostrEventPtr, 6 kinds::{KIND_MESSAGE, KIND_POST}, 7 message::{RadrootsMessage, RadrootsMessageRecipient}, 8 }; 9 use radroots_events_codec::error::{EventEncodeError, EventParseError}; 10 use radroots_events_codec::message::decode::{ 11 data_from_event, message_from_tags, parsed_from_event, 12 }; 13 use radroots_events_codec::message::encode::{message_build_tags, to_wire_parts}; 14 use test_fixtures::{RELAY_PRIMARY_WSS, RELAY_SECONDARY_WSS}; 15 16 #[test] 17 fn message_build_tags_requires_recipients() { 18 let message = RadrootsMessage { 19 recipients: Vec::new(), 20 content: "hello".to_string(), 21 reply_to: None, 22 subject: None, 23 }; 24 25 let err = message_build_tags(&message).unwrap_err(); 26 assert!(matches!( 27 err, 28 EventEncodeError::EmptyRequiredField("recipients") 29 )); 30 } 31 32 #[test] 33 fn message_build_tags_requires_recipient_pubkey() { 34 let message = RadrootsMessage { 35 recipients: vec![RadrootsMessageRecipient { 36 public_key: " ".to_string(), 37 relay_url: None, 38 }], 39 content: "hello".to_string(), 40 reply_to: None, 41 subject: None, 42 }; 43 44 let err = message_build_tags(&message).unwrap_err(); 45 assert!(matches!( 46 err, 47 EventEncodeError::EmptyRequiredField("recipients.public_key") 48 )); 49 } 50 51 #[test] 52 fn message_to_wire_parts_requires_content() { 53 let message = RadrootsMessage { 54 recipients: vec![RadrootsMessageRecipient { 55 public_key: "pub".to_string(), 56 relay_url: None, 57 }], 58 content: " ".to_string(), 59 reply_to: None, 60 subject: None, 61 }; 62 63 let err = to_wire_parts(&message).unwrap_err(); 64 assert!(matches!( 65 err, 66 EventEncodeError::EmptyRequiredField("content") 67 )); 68 69 let message = RadrootsMessage { 70 recipients: vec![RadrootsMessageRecipient { 71 public_key: " ".to_string(), 72 relay_url: None, 73 }], 74 content: "hello".to_string(), 75 reply_to: None, 76 subject: None, 77 }; 78 let err = to_wire_parts(&message).unwrap_err(); 79 assert!(matches!( 80 err, 81 EventEncodeError::EmptyRequiredField("recipients.public_key") 82 )); 83 } 84 85 #[test] 86 fn message_to_wire_parts_sets_tags() { 87 let message = RadrootsMessage { 88 recipients: vec![ 89 RadrootsMessageRecipient { 90 public_key: "pub1".to_string(), 91 relay_url: None, 92 }, 93 RadrootsMessageRecipient { 94 public_key: "pub2".to_string(), 95 relay_url: Some(RELAY_PRIMARY_WSS.to_string()), 96 }, 97 ], 98 content: "hello".to_string(), 99 reply_to: Some(RadrootsNostrEventPtr { 100 id: "reply".to_string(), 101 relays: Some(RELAY_SECONDARY_WSS.to_string()), 102 }), 103 subject: Some("topic".to_string()), 104 }; 105 106 let parts = to_wire_parts(&message).unwrap(); 107 assert_eq!(parts.kind, KIND_MESSAGE); 108 assert_eq!(parts.content, "hello"); 109 assert_eq!( 110 parts.tags, 111 vec![ 112 vec!["p".to_string(), "pub1".to_string()], 113 vec![ 114 "p".to_string(), 115 "pub2".to_string(), 116 RELAY_PRIMARY_WSS.to_string() 117 ], 118 vec![ 119 "e".to_string(), 120 "reply".to_string(), 121 RELAY_SECONDARY_WSS.to_string() 122 ], 123 vec!["subject".to_string(), "topic".to_string()], 124 ] 125 ); 126 } 127 128 #[test] 129 fn message_to_wire_parts_handles_absent_optional_fields() { 130 let message = RadrootsMessage { 131 recipients: vec![RadrootsMessageRecipient { 132 public_key: "pub1".to_string(), 133 relay_url: None, 134 }], 135 content: "hello".to_string(), 136 reply_to: None, 137 subject: None, 138 }; 139 140 let parts = to_wire_parts(&message).unwrap(); 141 assert_eq!(parts.tags, vec![vec!["p".to_string(), "pub1".to_string()]]); 142 } 143 144 #[test] 145 fn message_to_wire_parts_supports_reply_without_relay() { 146 let message = RadrootsMessage { 147 recipients: vec![RadrootsMessageRecipient { 148 public_key: "pub1".to_string(), 149 relay_url: None, 150 }], 151 content: "hello".to_string(), 152 reply_to: Some(RadrootsNostrEventPtr { 153 id: "reply".to_string(), 154 relays: None, 155 }), 156 subject: None, 157 }; 158 159 let parts = to_wire_parts(&message).unwrap(); 160 assert_eq!( 161 parts.tags, 162 vec![ 163 vec!["p".to_string(), "pub1".to_string()], 164 vec!["e".to_string(), "reply".to_string()], 165 ] 166 ); 167 } 168 169 #[test] 170 fn message_from_tags_requires_kind_content_and_recipients() { 171 let tags = vec![vec!["p".to_string(), "pub".to_string()]]; 172 let err = message_from_tags(KIND_POST, &tags, "hello").unwrap_err(); 173 assert!(matches!( 174 err, 175 EventParseError::InvalidKind { 176 expected: "14", 177 got: KIND_POST 178 } 179 )); 180 181 let err = message_from_tags(KIND_MESSAGE, &tags, " ").unwrap_err(); 182 assert!(matches!(err, EventParseError::InvalidTag("content"))); 183 184 let err = message_from_tags(KIND_MESSAGE, &[], "hello").unwrap_err(); 185 assert!(matches!(err, EventParseError::MissingTag("p"))); 186 } 187 188 #[test] 189 fn message_roundtrip_from_tags() { 190 let tags = vec![ 191 vec!["p".to_string(), "pub1".to_string()], 192 vec![ 193 "p".to_string(), 194 "pub2".to_string(), 195 RELAY_PRIMARY_WSS.to_string(), 196 ], 197 vec![ 198 "e".to_string(), 199 "reply".to_string(), 200 RELAY_SECONDARY_WSS.to_string(), 201 ], 202 vec!["subject".to_string(), "topic".to_string()], 203 ]; 204 205 let message = message_from_tags(KIND_MESSAGE, &tags, "hello").unwrap(); 206 207 assert_eq!(message.recipients.len(), 2); 208 assert_eq!(message.recipients[0].public_key, "pub1"); 209 assert_eq!(message.recipients[0].relay_url, None); 210 assert_eq!(message.recipients[1].public_key, "pub2"); 211 assert_eq!( 212 message.recipients[1].relay_url, 213 Some(RELAY_PRIMARY_WSS.to_string()) 214 ); 215 assert_eq!(message.content, "hello"); 216 assert_eq!( 217 message.reply_to.as_ref().map(|r| r.id.as_str()), 218 Some("reply") 219 ); 220 assert_eq!( 221 message.reply_to.as_ref().and_then(|r| r.relays.as_deref()), 222 Some(RELAY_SECONDARY_WSS) 223 ); 224 assert_eq!(message.subject.as_deref(), Some("topic")); 225 226 let tags_without_reply_relay = vec![ 227 vec!["p".to_string(), "pub1".to_string()], 228 vec!["e".to_string(), "reply".to_string()], 229 ]; 230 let no_relay_message = message_from_tags(KIND_MESSAGE, &tags_without_reply_relay, "hello") 231 .expect("message without reply relay"); 232 assert_eq!( 233 no_relay_message 234 .reply_to 235 .as_ref() 236 .and_then(|reply| reply.relays.as_deref()), 237 None 238 ); 239 } 240 241 #[test] 242 fn message_metadata_and_index_from_event_roundtrip() { 243 let tags = vec![ 244 vec!["p".to_string(), "pub1".to_string()], 245 vec![ 246 "p".to_string(), 247 "pub2".to_string(), 248 RELAY_PRIMARY_WSS.to_string(), 249 ], 250 vec![ 251 "e".to_string(), 252 "reply".to_string(), 253 RELAY_SECONDARY_WSS.to_string(), 254 ], 255 vec!["subject".to_string(), "topic".to_string()], 256 ]; 257 let metadata = data_from_event( 258 "id".to_string(), 259 "author".to_string(), 260 77, 261 KIND_MESSAGE, 262 "hello".to_string(), 263 tags.clone(), 264 ) 265 .unwrap(); 266 assert_eq!(metadata.id, "id"); 267 assert_eq!(metadata.author, "author"); 268 assert_eq!(metadata.published_at, 77); 269 assert_eq!(metadata.kind, KIND_MESSAGE); 270 assert_eq!(metadata.data.recipients.len(), 2); 271 assert_eq!(metadata.data.content, "hello"); 272 assert_eq!(metadata.data.subject.as_deref(), Some("topic")); 273 274 let index = parsed_from_event( 275 "id".to_string(), 276 "author".to_string(), 277 77, 278 KIND_MESSAGE, 279 "hello".to_string(), 280 tags, 281 "sig".to_string(), 282 ) 283 .unwrap(); 284 assert_eq!(index.event.kind, KIND_MESSAGE); 285 assert_eq!(index.event.sig, "sig"); 286 assert_eq!(index.data.data.recipients.len(), 2); 287 } 288 289 #[test] 290 fn message_index_from_event_propagates_parse_errors() { 291 let err = parsed_from_event( 292 "id".to_string(), 293 "author".to_string(), 294 77, 295 KIND_POST, 296 "hello".to_string(), 297 Vec::new(), 298 "sig".to_string(), 299 ) 300 .unwrap_err(); 301 assert!(matches!( 302 err, 303 EventParseError::InvalidKind { 304 expected: "14", 305 got: KIND_POST 306 } 307 )); 308 } 309 310 #[test] 311 fn message_build_tags_rejects_invalid_optional_fields() { 312 let message = RadrootsMessage { 313 recipients: vec![RadrootsMessageRecipient { 314 public_key: "pub".to_string(), 315 relay_url: Some(" ".to_string()), 316 }], 317 content: "hello".to_string(), 318 reply_to: None, 319 subject: None, 320 }; 321 let err = message_build_tags(&message).unwrap_err(); 322 assert!(matches!( 323 err, 324 EventEncodeError::EmptyRequiredField("recipients.relay_url") 325 )); 326 327 let message = RadrootsMessage { 328 recipients: vec![RadrootsMessageRecipient { 329 public_key: "pub".to_string(), 330 relay_url: None, 331 }], 332 content: "hello".to_string(), 333 reply_to: Some(RadrootsNostrEventPtr { 334 id: " ".to_string(), 335 relays: None, 336 }), 337 subject: None, 338 }; 339 let err = message_build_tags(&message).unwrap_err(); 340 assert!(matches!( 341 err, 342 EventEncodeError::EmptyRequiredField("reply_to.id") 343 )); 344 345 let message = RadrootsMessage { 346 recipients: vec![RadrootsMessageRecipient { 347 public_key: "pub".to_string(), 348 relay_url: None, 349 }], 350 content: "hello".to_string(), 351 reply_to: Some(RadrootsNostrEventPtr { 352 id: "reply".to_string(), 353 relays: Some(" ".to_string()), 354 }), 355 subject: None, 356 }; 357 let err = message_build_tags(&message).unwrap_err(); 358 assert!(matches!( 359 err, 360 EventEncodeError::EmptyRequiredField("reply_to.relays") 361 )); 362 363 let message = RadrootsMessage { 364 recipients: vec![RadrootsMessageRecipient { 365 public_key: "pub".to_string(), 366 relay_url: None, 367 }], 368 content: "hello".to_string(), 369 reply_to: None, 370 subject: Some(" ".to_string()), 371 }; 372 let err = message_build_tags(&message).unwrap_err(); 373 assert!(matches!( 374 err, 375 EventEncodeError::EmptyRequiredField("subject") 376 )); 377 } 378 379 #[test] 380 fn message_from_tags_rejects_invalid_optional_tags() { 381 let err = message_from_tags( 382 KIND_MESSAGE, 383 &[ 384 vec!["p".to_string()], 385 vec!["e".to_string(), "reply".to_string()], 386 ], 387 "hello", 388 ) 389 .unwrap_err(); 390 assert!(matches!(err, EventParseError::InvalidTag("p"))); 391 392 let err = message_from_tags( 393 KIND_MESSAGE, 394 &[ 395 vec!["p".to_string(), "pub".to_string(), " ".to_string()], 396 vec!["e".to_string(), "reply".to_string()], 397 ], 398 "hello", 399 ) 400 .unwrap_err(); 401 assert!(matches!(err, EventParseError::InvalidTag("p"))); 402 403 let err = message_from_tags( 404 KIND_MESSAGE, 405 &[ 406 vec!["p".to_string(), "pub".to_string()], 407 vec!["e".to_string()], 408 ], 409 "hello", 410 ) 411 .unwrap_err(); 412 assert!(matches!(err, EventParseError::InvalidTag("e"))); 413 414 let err = message_from_tags( 415 KIND_MESSAGE, 416 &[ 417 vec!["p".to_string(), "pub".to_string()], 418 vec!["e".to_string(), " ".to_string()], 419 ], 420 "hello", 421 ) 422 .unwrap_err(); 423 assert!(matches!(err, EventParseError::InvalidTag("e"))); 424 425 let err = message_from_tags( 426 KIND_MESSAGE, 427 &[ 428 vec!["p".to_string(), "pub".to_string()], 429 vec!["e".to_string(), "reply".to_string(), " ".to_string()], 430 ], 431 "hello", 432 ) 433 .unwrap_err(); 434 assert!(matches!(err, EventParseError::InvalidTag("e"))); 435 436 let err = message_from_tags( 437 KIND_MESSAGE, 438 &[ 439 vec!["p".to_string(), "pub".to_string()], 440 vec!["subject".to_string(), " ".to_string()], 441 ], 442 "hello", 443 ) 444 .unwrap_err(); 445 assert!(matches!(err, EventParseError::InvalidTag("subject"))); 446 447 let err = message_from_tags( 448 KIND_MESSAGE, 449 &[ 450 vec!["p".to_string(), "".to_string()], 451 vec!["subject".to_string(), "topic".to_string()], 452 ], 453 "hello", 454 ) 455 .unwrap_err(); 456 assert!(matches!(err, EventParseError::InvalidTag("p"))); 457 458 let err = message_from_tags( 459 KIND_MESSAGE, 460 &[ 461 vec!["p".to_string(), "pub".to_string()], 462 vec!["subject".to_string()], 463 ], 464 "hello", 465 ) 466 .unwrap_err(); 467 assert!(matches!(err, EventParseError::InvalidTag("subject"))); 468 }