1use crate::{Engine, error::Result};
7use async_trait::async_trait;
8use ciborium::Value as CborValue;
9use serde_json::{Value, json};
10use std::collections::HashMap;
11
12#[derive(Debug, Clone)]
14pub struct MockSurrealEngine {
15 exact_matches: HashMap<(String, Value), Value>,
17 debug: bool,
19}
20
21impl MockSurrealEngine {
22 pub fn new() -> Self {
24 Self {
25 exact_matches: HashMap::new(),
26 debug: false,
27 }
28 }
29
30 pub fn with_debug(mut self, debug: bool) -> Self {
32 self.debug = debug;
33 self
34 }
35
36 pub fn with_exact_response(
38 mut self,
39 method: impl Into<String>,
40 params: Value,
41 response: Value,
42 ) -> Self {
43 self.exact_matches.insert((method.into(), params), response);
44 self
45 }
46
47 pub fn with_method_response(mut self, method: impl Into<String>, response: Value) -> Self {
49 self.exact_matches
50 .insert((method.into(), json!({})), response);
51 self
52 }
53
54 pub fn with_query_response(mut self, query: impl Into<String>, response: Value) -> Self {
56 let params = json!([query.into()]);
57 self.exact_matches
58 .insert(("query".to_string(), params), response);
59 self
60 }
61
62 fn find_response(&self, method: &str, params: &Value) -> Value {
64 if self.debug {
65 println!("MockSurrealEngine: method='{}', params={}", method, params);
66 }
67
68 let key = (method.to_string(), params.clone());
69
70 if let Some(response) = self.exact_matches.get(&key) {
71 if self.debug {
72 println!(
73 "MockSurrealEngine: exact match found, returning {:?}",
74 response
75 );
76 }
77 return response.clone();
78 }
79
80 let allowed_patterns: Vec<String> = self
82 .exact_matches
83 .keys()
84 .map(|(method, params)| format!("{}({})", method, params))
85 .collect();
86
87 panic!(
88 "MockSurrealEngine: executed method {}({}), but allowed patterns are: {}",
89 method,
90 params,
91 if allowed_patterns.is_empty() {
92 "NONE - no patterns configured!".to_string()
93 } else {
94 allowed_patterns.join(", ")
95 }
96 );
97 }
98}
99
100impl Default for MockSurrealEngine {
101 fn default() -> Self {
102 Self::new()
103 }
104}
105
106#[async_trait]
107impl Engine for MockSurrealEngine {
108 async fn send_message(&mut self, method: &str, params: Value) -> Result<Value> {
109 Ok(self.find_response(method, ¶ms))
110 }
111
112 async fn send_message_cbor(&mut self, method: &str, params: CborValue) -> Result<CborValue> {
113 let json_params = crate::cbor_convert::cbor_to_json(params);
114 let response = self.find_response(method, &json_params);
115 Ok(crate::cbor_convert::json_to_cbor(response))
116 }
117}
118
119pub struct SurrealMockBuilder {
121 engine: MockSurrealEngine,
122 namespace: Option<String>,
123 database: Option<String>,
124}
125
126impl SurrealMockBuilder {
127 pub fn new() -> Self {
129 Self {
130 engine: MockSurrealEngine::new(),
131 namespace: Some("test".to_string()),
132 database: Some("test".to_string()),
133 }
134 }
135
136 pub fn with_namespace(mut self, namespace: impl Into<String>) -> Self {
138 self.namespace = Some(namespace.into());
139 self
140 }
141
142 pub fn with_database(mut self, database: impl Into<String>) -> Self {
144 self.database = Some(database.into());
145 self
146 }
147
148 pub fn with_debug(mut self, debug: bool) -> Self {
150 self.engine = self.engine.with_debug(debug);
151 self
152 }
153
154 pub fn with_exact_response(
156 mut self,
157 method: impl Into<String>,
158 params: Value,
159 response: Value,
160 ) -> Self {
161 self.engine = self.engine.with_exact_response(method, params, response);
162 self
163 }
164
165 pub fn with_method_response(mut self, method: impl Into<String>, response: Value) -> Self {
167 self.engine = self.engine.with_method_response(method, response);
168 self
169 }
170
171 pub fn with_query_response(mut self, query: impl Into<String>, response: Value) -> Self {
173 self.engine = self.engine.with_query_response(query, response);
174 self
175 }
176
177 pub fn build(self) -> crate::SurrealClient {
179 crate::SurrealClient::new(Box::new(self.engine), self.namespace, self.database)
180 }
181}
182
183impl Default for SurrealMockBuilder {
184 fn default() -> Self {
185 Self::new()
186 }
187}
188
189#[cfg(test)]
190mod tests {
191 use super::*;
192 use serde_json::json;
193
194 #[tokio::test]
195 #[should_panic(
196 expected = "executed method any_method({}), but allowed patterns are: NONE - no patterns configured!"
197 )]
198 async fn test_mock_engine_no_patterns_panics() {
199 let mut engine = MockSurrealEngine::new();
200 let _result = engine.send_message("any_method", json!({})).await.unwrap();
201 }
202
203 #[tokio::test]
204 async fn test_mock_engine_method_response() {
205 let mut engine =
206 MockSurrealEngine::new().with_method_response("query", json!([{"name": "John"}]));
207
208 let result = engine.send_message("query", json!({})).await.unwrap();
209 assert_eq!(result, json!([{"name": "John"}]));
210 }
211
212 #[tokio::test]
213 #[should_panic(expected = "executed method select({}), but allowed patterns are: query({})")]
214 async fn test_mock_engine_method_response_panics_on_unmatch() {
215 let mut engine =
216 MockSurrealEngine::new().with_method_response("query", json!([{"name": "John"}]));
217
218 let _result = engine.send_message("select", json!({})).await.unwrap();
219 }
220
221 #[tokio::test]
222 async fn test_mock_engine_exact_query_response() {
223 let mut engine = MockSurrealEngine::new()
224 .with_query_response("SELECT * FROM users", json!([{"type": "user"}]));
225
226 let result = engine
227 .send_message("query", json!(["SELECT * FROM users"]))
228 .await
229 .unwrap();
230 assert_eq!(result, json!([{"type": "user"}]));
231 }
232
233 #[tokio::test]
234 async fn test_mock_engine_exact_response() {
235 let mut engine = MockSurrealEngine::new().with_exact_response(
236 "custom",
237 json!({"param": "value"}),
238 json!({"result": "success"}),
239 );
240
241 let result = engine
242 .send_message("custom", json!({"param": "value"}))
243 .await
244 .unwrap();
245 assert_eq!(result, json!({"result": "success"}));
246
247 let should_panic = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
249 tokio::runtime::Runtime::new().unwrap().block_on(async {
250 engine
251 .send_message("custom", json!({"param": "different"}))
252 .await
253 })
254 }));
255 assert!(should_panic.is_err());
256 }
257
258 #[tokio::test]
259 async fn test_surreal_mock_builder() {
260 let db = SurrealMockBuilder::new()
261 .with_query_response("SELECT * FROM users", json!([{"name": "Alice"}]))
262 .build();
263
264 let result = db.query("SELECT * FROM users", None).await.unwrap();
266 assert_eq!(result, json!([{"name": "Alice"}]));
267 }
268
269 #[tokio::test]
270 #[should_panic(
271 expected = "executed method query([\"SELECT * FROM posts\"]), but allowed patterns are"
272 )]
273 async fn test_surreal_mock_builder_panics_on_unmatch() {
274 let db = SurrealMockBuilder::new()
275 .with_query_response("SELECT * FROM users", json!([{"name": "Alice"}]))
276 .build();
277
278 let _result = db.query("SELECT * FROM posts", None).await.unwrap();
280 }
281
282 #[test]
283 fn test_exact_matching_only() {
284 let engine = MockSurrealEngine::new()
285 .with_query_response("SELECT name FROM users", json!([{"name": "Alice"}]))
286 .with_query_response(
287 "SELECT * FROM users",
288 json!([{"name": "Alice", "email": "alice@example.com"}]),
289 );
290
291 let key1 = ("query".to_string(), json!(["SELECT name FROM users"]));
293 let key2 = ("query".to_string(), json!(["SELECT * FROM users"]));
294 let key3 = ("query".to_string(), json!(["SELECT name FROM posts"]));
295
296 assert!(engine.exact_matches.contains_key(&key1));
297 assert!(engine.exact_matches.contains_key(&key2));
298 assert!(!engine.exact_matches.contains_key(&key3));
299 }
300
301 #[test]
302 fn test_different_parameter_types() {
303 let engine = MockSurrealEngine::new()
304 .with_exact_response("method1", json!({}), json!("empty"))
305 .with_exact_response("method1", json!([]), json!("array"))
306 .with_exact_response("method1", json!({"key": "value"}), json!("object"));
307
308 assert_eq!(engine.exact_matches.len(), 3);
310
311 let key1 = ("method1".to_string(), json!({}));
312 let key2 = ("method1".to_string(), json!([]));
313 let key3 = ("method1".to_string(), json!({"key": "value"}));
314
315 assert_eq!(engine.exact_matches.get(&key1), Some(&json!("empty")));
316 assert_eq!(engine.exact_matches.get(&key2), Some(&json!("array")));
317 assert_eq!(engine.exact_matches.get(&key3), Some(&json!("object")));
318 }
319}