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