lib

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

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 }