lib

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

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 }