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