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