lib

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

chat_proto.rs (7478B)


      1 use radroots_simplex_chat_proto::prelude::{
      2     RadrootsSimplexChatContainerKind, RadrootsSimplexChatContent, RadrootsSimplexChatEvent,
      3     RadrootsSimplexChatForwardMarker, RadrootsSimplexChatMessage, RadrootsSimplexChatScope,
      4     RadrootsSimplexChatVersionRange, decode_messages, encode_compressed_batch, encode_message,
      5 };
      6 use serde_json::{Value, json};
      7 
      8 fn decode_one(value: Value) -> RadrootsSimplexChatMessage {
      9     let bytes = serde_json::to_vec(&value).expect("serialize synthetic test value");
     10     let mut messages = decode_messages(&bytes).expect("decode synthetic test value");
     11     assert_eq!(messages.len(), 1, "expected exactly one decoded message");
     12     messages.pop().expect("single decoded message")
     13 }
     14 
     15 fn encode_value(message: &RadrootsSimplexChatMessage) -> Value {
     16     serde_json::from_slice(&encode_message(message).expect("encode message"))
     17         .expect("parse encoded message json")
     18 }
     19 
     20 #[test]
     21 fn roundtrips_ok_message_with_top_level_version() {
     22     let expected = json!({
     23         "v": "1-16",
     24         "msgId": "AQ",
     25         "event": "x.ok",
     26         "params": {},
     27     });
     28 
     29     let message = decode_one(expected.clone());
     30     assert_eq!(
     31         message.version,
     32         Some(RadrootsSimplexChatVersionRange::new(1, 16).unwrap())
     33     );
     34     assert!(matches!(message.event, RadrootsSimplexChatEvent::Ok(_)));
     35     assert_eq!(encode_value(&message), expected);
     36 }
     37 
     38 #[test]
     39 fn roundtrips_contact_event_with_request_message_fields() {
     40     let expected = json!({
     41         "v": "1-16",
     42         "event": "x.contact",
     43         "params": {
     44             "profile": {
     45                 "displayName": "rr",
     46                 "fullName": "Rad Roots",
     47                 "peerType": "human"
     48             },
     49             "contactReqId": "AQ",
     50             "welcomeMsgId": "Ag",
     51             "msgId": "Aw",
     52             "content": {
     53                 "type": "text",
     54                 "text": "hello from rr"
     55             },
     56             "nickname": "roots"
     57         }
     58     });
     59 
     60     let message = decode_one(expected.clone());
     61     let RadrootsSimplexChatEvent::Contact(event) = &message.event else {
     62         panic!("expected contact event");
     63     };
     64     assert_eq!(event.profile.display_name, "rr");
     65     assert!(event.request_message.is_some());
     66     assert_eq!(
     67         event.extra.get("nickname"),
     68         Some(&Value::String("roots".into()))
     69     );
     70     assert_eq!(encode_value(&message), expected);
     71 }
     72 
     73 #[test]
     74 fn roundtrips_msg_new_with_object_forward_marker() {
     75     let expected = json!({
     76         "event": "x.msg.new",
     77         "params": {
     78             "forward": {
     79                 "groupLinkId": "AQ",
     80                 "mode": "public"
     81             },
     82             "content": {
     83                 "type": "text",
     84                 "text": "forwarded text"
     85             },
     86             "ttl": 60,
     87             "live": true
     88         }
     89     });
     90 
     91     let message = decode_one(expected.clone());
     92     let RadrootsSimplexChatEvent::MsgNew(event) = &message.event else {
     93         panic!("expected x.msg.new");
     94     };
     95     assert!(matches!(
     96         event.container.kind,
     97         RadrootsSimplexChatContainerKind::Forward(RadrootsSimplexChatForwardMarker::Object(_))
     98     ));
     99     assert_eq!(event.container.ttl, Some(60));
    100     assert_eq!(event.container.live, Some(true));
    101     assert_eq!(encode_value(&message), expected);
    102 }
    103 
    104 #[test]
    105 fn roundtrips_msg_update_with_mentions_and_scope() {
    106     let expected = json!({
    107         "event": "x.msg.update",
    108         "params": {
    109             "msgId": "AQ",
    110             "content": {
    111                 "type": "text",
    112                 "text": "edited"
    113             },
    114             "mentions": {
    115                 "lead": {
    116                     "memberId": "Ag",
    117                     "label": "Lead"
    118                 }
    119             },
    120             "scope": {
    121                 "type": "member",
    122                 "data": {
    123                     "memberId": "Aw",
    124                     "role": "writer"
    125                 }
    126             },
    127             "ttl": 90,
    128             "live": false
    129         }
    130     });
    131 
    132     let message = decode_one(expected.clone());
    133     let RadrootsSimplexChatEvent::MsgUpdate(event) = &message.event else {
    134         panic!("expected x.msg.update");
    135     };
    136     assert_eq!(event.mentions.len(), 1);
    137     assert!(matches!(
    138         event.scope,
    139         Some(RadrootsSimplexChatScope::Member { .. })
    140     ));
    141     assert_eq!(encode_value(&message), expected);
    142 }
    143 
    144 #[test]
    145 fn roundtrips_file_description_and_accept_invitation_events() {
    146     let file_descr = json!({
    147         "event": "x.msg.file.descr",
    148         "params": {
    149             "msgId": "AQ",
    150             "fileDescr": {
    151                 "fileDescrText": "part 1",
    152                 "fileDescrPartNo": 1,
    153                 "fileDescrComplete": true
    154             },
    155             "label": "intro"
    156         }
    157     });
    158 
    159     let file_accept_inv = json!({
    160         "event": "x.file.acpt.inv",
    161         "params": {
    162             "msgId": "Ag",
    163             "fileConnReq": "smp://example",
    164             "fileName": "hello.txt",
    165             "label": "doc"
    166         }
    167     });
    168 
    169     let descr_message = decode_one(file_descr.clone());
    170     let acpt_inv_message = decode_one(file_accept_inv.clone());
    171     assert_eq!(encode_value(&descr_message), file_descr);
    172     assert_eq!(encode_value(&acpt_inv_message), file_accept_inv);
    173 }
    174 
    175 #[test]
    176 fn preserves_unknown_event_params() {
    177     let expected = json!({
    178         "v": "8",
    179         "event": "x.future.dm",
    180         "params": {
    181             "flag": true,
    182             "nested": {
    183                 "kind": "preview"
    184             }
    185         }
    186     });
    187 
    188     let message = decode_one(expected.clone());
    189     let RadrootsSimplexChatEvent::Unknown { event, params } = &message.event else {
    190         panic!("expected unknown event");
    191     };
    192     assert_eq!(event, "x.future.dm");
    193     assert_eq!(params.get("flag"), Some(&Value::Bool(true)));
    194     assert_eq!(encode_value(&message), expected);
    195 }
    196 
    197 #[test]
    198 fn preserves_unknown_content_types_inside_direct_messages() {
    199     let expected = json!({
    200         "event": "x.msg.new",
    201         "params": {
    202             "content": {
    203                 "type": "chat",
    204                 "text": "join us",
    205                 "chatLink": "https://radroots.example/simplex"
    206             }
    207         }
    208     });
    209 
    210     let message = decode_one(expected.clone());
    211     let RadrootsSimplexChatEvent::MsgNew(event) = &message.event else {
    212         panic!("expected x.msg.new");
    213     };
    214     match &event.container.content {
    215         RadrootsSimplexChatContent::Unknown {
    216             content_type,
    217             text,
    218             raw,
    219         } => {
    220             assert_eq!(content_type, "chat");
    221             assert_eq!(text.as_deref(), Some("join us"));
    222             assert_eq!(
    223                 raw.get("chatLink"),
    224                 Some(&Value::String("https://radroots.example/simplex".into()))
    225             );
    226         }
    227         other => panic!("expected unknown content, got {other:?}"),
    228     }
    229     assert_eq!(encode_value(&message), expected);
    230 }
    231 
    232 #[test]
    233 fn roundtrips_official_compressed_envelope_batches() {
    234     let message = decode_one(json!({
    235         "v": "1-16",
    236         "msgId": "AQ",
    237         "event": "x.msg.new",
    238         "params": {
    239             "content": {
    240                 "type": "text",
    241                 "text": "x".repeat(256)
    242             }
    243         }
    244     }));
    245 
    246     let encoded = encode_compressed_batch(&[message.clone()]).expect("encode compressed batch");
    247     assert_eq!(encoded.first(), Some(&b'X'));
    248 
    249     let decoded = decode_messages(&encoded).expect("decode compressed envelope");
    250     assert_eq!(decoded, vec![message]);
    251 }