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 }