1mod codec;
2mod types;
3
4pub use codec::IpcCodec;
5pub use types::*;
6
7pub const IPC_PROTOCOL_VERSION: u32 = 1;
8
9#[cfg(test)]
10mod tests {
11 use super::*;
12 use bytes::BytesMut;
13 use mxr_core::id::*;
14 use tokio_util::codec::{Decoder, Encoder};
15
16 #[test]
17 fn request_serde_roundtrip() {
18 let variants: Vec<Request> = vec![
19 Request::Ping,
20 Request::Shutdown,
21 Request::ListEnvelopes {
22 label_id: None,
23 account_id: None,
24 limit: 50,
25 offset: 0,
26 },
27 Request::GetEnvelope {
28 message_id: MessageId::new(),
29 },
30 Request::Search {
31 query: "test".to_string(),
32 limit: 10,
33 offset: 0,
34 mode: None,
35 sort: None,
36 explain: false,
37 },
38 ];
39
40 for req in variants {
41 let msg = IpcMessage {
42 id: 1,
43 payload: IpcPayload::Request(req),
44 };
45 let json = serde_json::to_string(&msg).unwrap();
46 let parsed: IpcMessage = serde_json::from_str(&json).unwrap();
47 assert_eq!(parsed.id, 1);
48 }
49 }
50
51 #[test]
52 fn response_serde_roundtrip() {
53 let ok = Response::Ok {
54 data: ResponseData::Pong,
55 };
56 let err = Response::Error {
57 message: "something failed".to_string(),
58 };
59
60 for resp in [ok, err] {
61 let msg = IpcMessage {
62 id: 2,
63 payload: IpcPayload::Response(resp),
64 };
65 let json = serde_json::to_string(&msg).unwrap();
66 let parsed: IpcMessage = serde_json::from_str(&json).unwrap();
67 assert_eq!(parsed.id, 2);
68 }
69 }
70
71 #[test]
72 fn daemon_event_roundtrip() {
73 let events: Vec<DaemonEvent> = vec![
74 DaemonEvent::SyncCompleted {
75 account_id: AccountId::new(),
76 messages_synced: 10,
77 },
78 DaemonEvent::SyncError {
79 account_id: AccountId::new(),
80 error: "timeout".to_string(),
81 },
82 DaemonEvent::MessageUnsnoozed {
83 message_id: MessageId::new(),
84 },
85 ];
86
87 for event in events {
88 let msg = IpcMessage {
89 id: 0,
90 payload: IpcPayload::Event(event),
91 };
92 let json = serde_json::to_string(&msg).unwrap();
93 let _parsed: IpcMessage = serde_json::from_str(&json).unwrap();
94 }
95 }
96
97 #[test]
98 fn codec_encode_decode() {
99 let mut codec = IpcCodec::new();
100 let msg = IpcMessage {
101 id: 42,
102 payload: IpcPayload::Request(Request::Ping),
103 };
104
105 let mut buf = BytesMut::new();
106 codec.encode(msg, &mut buf).unwrap();
107
108 let decoded = codec.decode(&mut buf).unwrap().unwrap();
109 assert_eq!(decoded.id, 42);
110 }
111
112 #[test]
113 fn codec_multiple_messages() {
114 let mut codec = IpcCodec::new();
115 let mut buf = BytesMut::new();
116
117 for i in 0..3 {
118 let msg = IpcMessage {
119 id: i,
120 payload: IpcPayload::Request(Request::Ping),
121 };
122 codec.encode(msg, &mut buf).unwrap();
123 }
124
125 for i in 0..3 {
126 let decoded = codec.decode(&mut buf).unwrap().unwrap();
127 assert_eq!(decoded.id, i);
128 }
129
130 assert!(codec.decode(&mut buf).unwrap().is_none());
132 }
133
134 #[test]
135 fn legacy_status_response_defaults_new_fields() {
136 let json = serde_json::json!({
137 "id": 7,
138 "payload": {
139 "type": "Response",
140 "status": "Ok",
141 "data": {
142 "kind": "Status",
143 "uptime_secs": 42,
144 "accounts": ["personal"],
145 "total_messages": 123,
146 "daemon_pid": 999
147 }
148 }
149 });
150
151 let parsed: IpcMessage = serde_json::from_value(json).unwrap();
152 match parsed.payload {
153 IpcPayload::Response(Response::Ok {
154 data:
155 ResponseData::Status {
156 sync_statuses,
157 protocol_version,
158 daemon_version,
159 daemon_build_id,
160 repair_required,
161 ..
162 },
163 }) => {
164 assert!(sync_statuses.is_empty());
165 assert_eq!(protocol_version, 0);
166 assert!(daemon_version.is_none());
167 assert!(daemon_build_id.is_none());
168 assert!(!repair_required);
169 }
170 other => panic!("unexpected payload: {other:?}"),
171 }
172 }
173}