agent_tui/ipc/
mock_client.rs1use std::collections::HashMap;
2use std::sync::Arc;
3use std::sync::Mutex;
4
5use serde_json::Value;
6
7use crate::ipc::client::{DaemonClient, DaemonClientConfig};
8use crate::ipc::error::ClientError;
9
10type CallRecord = Vec<(String, Option<Value>)>;
11
12#[derive(Clone)]
33pub struct MockClient {
34 responses: Arc<Mutex<HashMap<String, Value>>>,
35 calls: Arc<Mutex<CallRecord>>,
36 default_response: Value,
37 error_on_missing: bool,
38}
39
40impl Default for MockClient {
41 fn default() -> Self {
42 Self::new()
43 }
44}
45
46impl MockClient {
47 pub fn new() -> Self {
49 Self {
50 responses: Arc::new(Mutex::new(HashMap::new())),
51 calls: Arc::new(Mutex::new(Vec::new())),
52 default_response: serde_json::json!({ "success": true }),
53 error_on_missing: false,
54 }
55 }
56
57 pub fn new_strict() -> Self {
59 Self {
60 responses: Arc::new(Mutex::new(HashMap::new())),
61 calls: Arc::new(Mutex::new(Vec::new())),
62 default_response: serde_json::json!(null),
63 error_on_missing: true,
64 }
65 }
66
67 pub fn set_response(&mut self, method: &str, response: Value) {
69 self.responses
70 .lock()
71 .unwrap()
72 .insert(method.to_string(), response);
73 }
74
75 pub fn set_default_response(&mut self, response: Value) {
77 self.default_response = response;
78 }
79
80 pub fn get_calls(&self) -> Vec<(String, Option<Value>)> {
82 self.calls.lock().unwrap().clone()
83 }
84
85 pub fn call_count(&self, method: &str) -> usize {
87 self.calls
88 .lock()
89 .unwrap()
90 .iter()
91 .filter(|(m, _)| m == method)
92 .count()
93 }
94
95 pub fn last_call(&self, method: &str) -> Option<(String, Option<Value>)> {
97 self.calls
98 .lock()
99 .unwrap()
100 .iter()
101 .rev()
102 .find(|(m, _)| m == method)
103 .cloned()
104 }
105
106 pub fn params_for(&self, method: &str) -> Vec<Option<Value>> {
108 self.calls
109 .lock()
110 .unwrap()
111 .iter()
112 .filter(|(m, _)| m == method)
113 .map(|(_, p)| p.clone())
114 .collect()
115 }
116
117 pub fn clear_calls(&mut self) {
119 self.calls.lock().unwrap().clear();
120 }
121
122 pub fn clear_responses(&mut self) {
124 self.responses.lock().unwrap().clear();
125 }
126
127 pub fn reset(&mut self) {
129 self.clear_calls();
130 self.clear_responses();
131 }
132}
133
134impl DaemonClient for MockClient {
135 fn call(&mut self, method: &str, params: Option<Value>) -> Result<Value, ClientError> {
136 self.calls
137 .lock()
138 .unwrap()
139 .push((method.to_string(), params.clone()));
140
141 let responses = self.responses.lock().unwrap();
142 if let Some(response) = responses.get(method) {
143 Ok(response.clone())
144 } else if self.error_on_missing {
145 Err(ClientError::RpcError {
146 code: -32601,
147 message: format!("Method not found: {}", method),
148 category: None,
149 retryable: false,
150 context: None,
151 suggestion: None,
152 })
153 } else {
154 Ok(self.default_response.clone())
155 }
156 }
157
158 fn call_with_config(
159 &mut self,
160 method: &str,
161 params: Option<Value>,
162 _config: &DaemonClientConfig,
163 ) -> Result<Value, ClientError> {
164 self.call(method, params)
165 }
166
167 fn call_with_retry(
168 &mut self,
169 method: &str,
170 params: Option<Value>,
171 _max_retries: u32,
172 ) -> Result<Value, ClientError> {
173 self.call(method, params)
174 }
175}
176
177#[cfg(test)]
178mod tests {
179 use super::*;
180 use serde_json::json;
181
182 #[test]
183 fn test_mock_client_returns_configured_response() {
184 let mut mock = MockClient::new();
185 mock.set_response("health", json!({ "status": "ok" }));
186
187 let result = mock.call("health", None).unwrap();
188 assert_eq!(result, json!({ "status": "ok" }));
189 }
190
191 #[test]
192 fn test_mock_client_returns_default_for_unconfigured() {
193 let mut mock = MockClient::new();
194
195 let result = mock.call("unknown", None).unwrap();
196 assert_eq!(result, json!({ "success": true }));
197 }
198
199 #[test]
200 fn test_mock_client_strict_errors_on_unknown() {
201 let mut mock = MockClient::new_strict();
202
203 let result = mock.call("unknown", None);
204 assert!(result.is_err());
205 }
206
207 #[test]
208 fn test_mock_client_tracks_calls() {
209 let mut mock = MockClient::new();
210
211 mock.call("method1", Some(json!({ "key": "value" })))
212 .unwrap();
213 mock.call("method2", None).unwrap();
214 mock.call("method1", Some(json!({ "key2": "value2" })))
215 .unwrap();
216
217 assert_eq!(mock.call_count("method1"), 2);
218 assert_eq!(mock.call_count("method2"), 1);
219 assert_eq!(mock.get_calls().len(), 3);
220 }
221
222 #[test]
223 fn test_mock_client_last_call() {
224 let mut mock = MockClient::new();
225
226 mock.call("test", Some(json!({ "attempt": 1 }))).unwrap();
227 mock.call("test", Some(json!({ "attempt": 2 }))).unwrap();
228
229 let last = mock.last_call("test").unwrap();
230 assert_eq!(last.1, Some(json!({ "attempt": 2 })));
231 }
232
233 #[test]
234 fn test_mock_client_params_for() {
235 let mut mock = MockClient::new();
236
237 mock.call("test", Some(json!({ "a": 1 }))).unwrap();
238 mock.call("other", Some(json!({ "b": 2 }))).unwrap();
239 mock.call("test", Some(json!({ "c": 3 }))).unwrap();
240
241 let params = mock.params_for("test");
242 assert_eq!(params.len(), 2);
243 assert_eq!(params[0], Some(json!({ "a": 1 })));
244 assert_eq!(params[1], Some(json!({ "c": 3 })));
245 }
246
247 #[test]
248 fn test_mock_client_reset() {
249 let mut mock = MockClient::new();
250 mock.set_response("test", json!({ "data": "value" }));
251 mock.call("test", None).unwrap();
252
253 mock.reset();
254
255 assert_eq!(mock.call_count("test"), 0);
256 let result = mock.call("test", None).unwrap();
257 assert_eq!(result, json!({ "success": true })); }
259
260 #[test]
261 fn test_mock_client_custom_default() {
262 let mut mock = MockClient::new();
263 mock.set_default_response(json!({ "custom": "default" }));
264
265 let result = mock.call("any_method", None).unwrap();
266 assert_eq!(result, json!({ "custom": "default" }));
267 }
268}