vantage_expressions/mocks/
mock_builder.rs1use crate::Expression;
50use crate::expression::flatten::{ExpressionFlattener, Flatten};
51use crate::mocks::select::MockSelect;
52use crate::traits::datasource::{DataSource, ExprDataSource, SelectableDataSource};
53use crate::traits::expressive::{DeferredFn, ExpressiveEnum};
54use serde_json::Value;
55use std::collections::HashMap;
56use vantage_core::Result;
57
58#[derive(Debug, Clone)]
60pub struct MockBuilder {
61 patterns: HashMap<String, Value>,
62 flatten_expressions: bool,
63 flattener: ExpressionFlattener,
64}
65
66impl MockBuilder {
67 pub fn new() -> Self {
69 Self {
70 patterns: HashMap::new(),
71 flatten_expressions: false,
72 flattener: ExpressionFlattener::new(),
73 }
74 }
75
76 pub fn with_flattening(mut self) -> Self {
78 self.flatten_expressions = true;
79 self
80 }
81
82 pub fn on_exact_select(mut self, pattern: impl Into<String>, response: Value) -> Self {
84 self.patterns.insert(pattern.into(), response);
85 self
86 }
87
88 fn process_expression(&self, expr: &Expression<Value>) -> Expression<Value> {
89 if self.flatten_expressions {
90 self.flattener.flatten(expr)
91 } else {
92 expr.clone()
93 }
94 }
95
96 async fn resolve_deferred_expression(
97 &self,
98 expr: &Expression<Value>,
99 ) -> Result<Expression<Value>> {
100 let mut resolved_params = Vec::new();
101
102 for param in &expr.parameters {
103 match param {
104 ExpressiveEnum::Deferred(deferred_fn) => {
105 let result = deferred_fn.call().await?;
106 resolved_params.push(result);
107 }
108 other => {
109 resolved_params.push(other.clone());
110 }
111 }
112 }
113
114 Ok(Expression::new(expr.template.clone(), resolved_params))
115 }
116
117 fn find_matching_response(&self, query: &str) -> Option<Value> {
118 self.patterns.get(query).cloned()
119 }
120}
121
122impl Default for MockBuilder {
123 fn default() -> Self {
124 Self::new()
125 }
126}
127
128impl DataSource for MockBuilder {}
129
130impl ExprDataSource<Value> for MockBuilder {
131 async fn execute(&self, expr: &Expression<Value>) -> Result<Value> {
132 let resolved_expr = self.resolve_deferred_expression(expr).await?;
134
135 let processed_expr = self.process_expression(&resolved_expr);
137 let query_str = processed_expr.preview();
138
139 match self.find_matching_response(&query_str) {
140 Some(response) => Ok(response),
141 None => Err(vantage_core::error!(
142 "No matching pattern found for query",
143 query = query_str
144 )),
145 }
146 }
147
148 fn defer(&self, expr: Expression<Value>) -> DeferredFn<Value>
149 where
150 Value: Clone + Send + Sync + 'static,
151 {
152 let processed_expr = self.process_expression(&expr);
153 let query_str = processed_expr.preview();
154 let response = self.find_matching_response(&query_str);
155
156 DeferredFn::new(move || {
157 let response = response.clone();
158 let query_str = query_str.clone();
159 Box::pin(async move {
160 match response {
161 Some(value) => Ok(ExpressiveEnum::Scalar(value)),
162 None => Err(vantage_core::error!(
163 "No matching pattern found for deferred query",
164 query = query_str
165 )),
166 }
167 })
168 })
169 }
170}
171
172impl SelectableDataSource<Value> for MockBuilder {
173 type Select = MockSelect;
174
175 fn select(&self) -> Self::Select {
176 MockSelect::new()
177 }
178
179 async fn execute_select(&self, select: &Self::Select) -> Result<Vec<Value>> {
180 use crate::traits::expressive::Expressive;
181 let expr = select.expr();
182 let result = self.execute(&expr).await?;
183
184 match result {
185 Value::Array(arr) => Ok(arr),
186 single_value => Ok(vec![single_value]),
187 }
188 }
189}
190
191pub fn new() -> MockBuilder {
193 MockBuilder::new()
194}
195
196#[cfg(test)]
197mod tests {
198 use super::*;
199 use crate::expr;
200 use serde_json::json;
201
202 #[tokio::test]
203 async fn test_exact_pattern_matching() {
204 let mock = new().on_exact_select(
205 "SELECT * FROM users",
206 json!([
207 {"id": 1, "name": "Alice"},
208 {"id": 2, "name": "Bob"}
209 ]),
210 );
211
212 let query = expr!("SELECT * FROM users");
213 let result = mock.execute(&query).await.unwrap();
214 assert_eq!(result.as_array().unwrap().len(), 2);
215 }
216
217 #[tokio::test]
218 async fn test_multiple_patterns() {
219 let mock = new()
220 .on_exact_select("SELECT COUNT(*) FROM orders", json!(42))
221 .on_exact_select(
222 "SELECT * FROM products",
223 json!([
224 {"id": "prod1", "name": "Widget"}
225 ]),
226 );
227
228 let count_query = expr!("SELECT COUNT(*) FROM orders");
229 let count_result = mock.execute(&count_query).await.unwrap();
230 assert_eq!(count_result, json!(42));
231
232 let products_query = expr!("SELECT * FROM products");
233 let products_result = mock.execute(&products_query).await.unwrap();
234 assert_eq!(products_result.as_array().unwrap().len(), 1);
235 }
236
237 #[tokio::test]
238 async fn test_flattening() {
239 let mock = new().with_flattening().on_exact_select(
240 "SELECT * FROM orders WHERE user_id = 123",
241 json!([
242 {"id": 1, "user_id": 123, "amount": 99.99}
243 ]),
244 );
245
246 let nested_query = expr!("SELECT * FROM orders WHERE user_id = {}", 123);
247 let result = mock.execute(&nested_query).await.unwrap();
248 assert_eq!(result.as_array().unwrap().len(), 1);
249 }
250
251 #[tokio::test]
252 async fn test_no_matching_pattern() {
253 let mock = new().on_exact_select("SELECT * FROM users", json!([]));
254
255 let query = expr!("SELECT * FROM products");
256 let result = mock.execute(&query).await;
257 assert!(result.is_err());
258 }
259
260 #[test]
261 fn test_defer() {
262 let mock = new().on_exact_select("SELECT COUNT(*)", json!(5));
263
264 let query = expr!("SELECT COUNT(*)");
265 let _deferred = mock.defer(query);
266 }
267
268 #[tokio::test]
269 async fn test_select_source() {
270 use crate::traits::selectable::Selectable;
271
272 let mock = new().on_exact_select(
273 "SELECT * FROM products",
274 json!([
275 {"id": "prod1", "name": "Widget"}
276 ]),
277 );
278
279 let mut select = mock.select();
280 select.add_source("products", None);
281
282 let results = mock.execute_select(&select).await.unwrap();
283 assert_eq!(results.len(), 1);
284 assert_eq!(results[0]["name"], "Widget");
285 }
286}