1pub mod protocol;
2
3pub use protocol::{
4 Command,
5 Response,
6 format_command,
7 format_response,
8 parse_command,
9 parse_response,
10 connect_with_auth,
11 connect_with_retry,
12 start_long_lived_tcp,
13 Client,
14 send_command,
15};
16
17#[cfg(test)]
18mod tests {
19 use super::*;
20 use serde_json::json;
21
22 #[test]
23 fn parse_and_format_room_create() {
24 let cmd = parse_command("ROOM.CREATE").unwrap();
25 assert_eq!(cmd, Command::RoomCreate);
26 let line = format_command(&cmd);
27 assert_eq!(line, "ROOM.CREATE");
28 }
29
30 #[test]
31 fn parse_set_with_json() {
32 let line = "SET 1 public foo {\"a\": 123, \"s\": \"x\"}";
33 let cmd = parse_command(line).unwrap();
34 match cmd {
35 Command::Set { room_id, container, key, value } => {
36 assert_eq!(room_id, 1);
37 assert_eq!(container, "public");
38 assert_eq!(key, "foo");
39 assert_eq!(value, json!({"a": 123, "s": "x"}));
40 }
41 _ => panic!("unexpected command variant"),
42 }
43 }
44
45 #[test]
46 fn parse_and_format_persist_commands() {
47 let s = "PERSIST.SET 1 public foo";
48 let cmd = parse_command(s).unwrap();
49 assert_eq!(cmd, Command::PersistSet { room_id: 1, container: "public".into(), key: "foo".into() });
50 assert_eq!(format_command(&cmd), "PERSIST.SET 1 public foo");
51
52 let s2 = "PERSIST.UNSET 1 public foo";
53 let cmd2 = parse_command(s2).unwrap();
54 assert_eq!(cmd2, Command::PersistUnset { room_id: 1, container: "public".into(), key: "foo".into() });
55 assert_eq!(format_command(&cmd2), "PERSIST.UNSET 1 public foo");
56
57 let s3 = "PERSIST.GET 1 public foo";
58 let cmd3 = parse_command(s3).unwrap();
59 assert_eq!(cmd3, Command::PersistGet { room_id: 1, container: "public".into(), key: "foo".into() });
60 assert_eq!(format_command(&cmd3), "PERSIST.GET 1 public foo");
61 }
62
63 #[test]
64 fn parse_and_format_server_commands() {
65 use serde_json::json;
66
67 let s = "SERVER.SET 1 mykey {\"a\":1}";
68 let cmd = parse_command(s).unwrap();
69 assert_eq!(cmd, Command::ServerSet { room_id: 1, key: "mykey".into(), value: json!({"a":1}) });
70 assert_eq!(format_command(&cmd), "SERVER.SET 1 mykey {\"a\":1}");
71
72 let s2 = "SERVER.DEL 1 mykey";
73 let cmd2 = parse_command(s2).unwrap();
74 assert_eq!(cmd2, Command::ServerDel { room_id: 1, key: "mykey".into() });
75 assert_eq!(format_command(&cmd2), "SERVER.DEL 1 mykey");
76
77 let s3 = "SERVER.GET 1 mykey";
78 let cmd3 = parse_command(s3).unwrap();
79 assert_eq!(cmd3, Command::ServerGet { room_id: 1, key: "mykey".into() });
80 assert_eq!(format_command(&cmd3), "SERVER.GET 1 mykey");
81 }
82
83 #[test]
84 fn parse_and_format_save_load_commands() {
85 let s = "SAVE 1";
86 let cmd = parse_command(s).unwrap();
87 assert_eq!(cmd, Command::Save { room_id: 1 });
88 assert_eq!(format_command(&cmd), "SAVE 1");
89
90 let s2 = "LOAD 1";
91 let cmd2 = parse_command(s2).unwrap();
92 assert_eq!(cmd2, Command::Load { room_id: 1 });
93 assert_eq!(format_command(&cmd2), "LOAD 1");
94 }
95
96 #[test]
97 fn response_roundtrip() {
98 let r = Response::Ok(Some("{\"a\":1}".to_string()));
99 let s = format_response(&r);
100 assert_eq!(s, "OK {\"a\":1}");
101 let parsed = parse_response(&s).unwrap();
102 assert_eq!(parsed, r);
103 }
104
105 #[tokio::test]
106 async fn connect_and_auth_and_receive() {
107 use tokio::net::TcpListener;
108 use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
109 use tokio::time::Duration;
110
111 let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
112 let addr = listener.local_addr().unwrap().to_string();
113 let api_key = "test-api-key-xyz";
114
115 let (mut rx, _tx) = start_long_lived_tcp(addr.clone(), api_key.to_string());
116
117 let accept = tokio::spawn(async move {
119 let (mut socket, _) = listener.accept().await.unwrap();
120 let mut reader = BufReader::new(&mut socket);
121 let mut line = String::new();
122 reader.read_line(&mut line).await.unwrap();
123 assert_eq!(line.trim_end(), api_key);
124 socket.write_all(b"HELLO\n").await.unwrap();
125 socket.flush().await.unwrap();
126 });
127
128 let got = tokio::time::timeout(Duration::from_secs(2), rx.recv())
129 .await
130 .expect("timeout waiting for line")
131 .expect("channel closed");
132 assert_eq!(got, "HELLO");
133
134 accept.await.unwrap();
135 }
136
137 #[tokio::test]
138 async fn client_wrapper_send_and_shutdown() {
139 use tokio::net::TcpListener;
140 use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
141 use tokio::time::Duration;
142
143 let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
144 let addr = listener.local_addr().unwrap().to_string();
145 let api_key = "test-api-key-xyz".to_string();
146
147 let client = Client::start(addr.clone(), api_key.clone());
149
150 let accept = tokio::spawn(async move {
152 let (mut socket, _) = listener.accept().await.unwrap();
153 let mut reader = BufReader::new(&mut socket);
154 let mut line = String::new();
155 reader.read_line(&mut line).await.unwrap();
157 assert_eq!(line.trim_end(), api_key);
158
159 let mut cmd_line = String::new();
161 reader.read_line(&mut cmd_line).await.unwrap();
162 assert_eq!(cmd_line.trim_end(), "ROOM.CREATE");
163 });
164
165 client.send(Command::RoomCreate).await.unwrap();
167
168 accept.await.unwrap();
171 client.shutdown().await.unwrap();
172 }
173}