Skip to main content

mxr_protocol/
lib.rs

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        // No more messages
131        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}