Skip to main content

vantage_expressions/mocks/
mock_builder.rs

1//! MockBuilder for pattern-based query testing with flattening support.
2//!
3//! Provides a builder pattern for creating mock data sources that can match
4//! specific query patterns and return configured responses. Useful for testing
5//! complex query scenarios without actual database connections.
6//!
7//! # Examples
8//!
9//! Basic pattern matching:
10//! ```rust
11//! use vantage_expressions::prelude::*;
12//! use vantage_expressions::mocks::mockbuilder;
13//! use serde_json::json;
14//!
15//! # tokio_test::block_on(async {
16//! let mock = mockbuilder::new()
17//!     .on_exact_select("SELECT * FROM users", json!([
18//!         {"id": 1, "name": "Alice"},
19//!         {"id": 2, "name": "Bob"}
20//!     ]));
21//!
22//! let query = expr!("SELECT * FROM users");
23//! let result = mock.execute(&query).await.unwrap();
24//! assert_eq!(result.as_array().unwrap().len(), 2);
25//! # });
26//! ```
27//!
28//! Multiple patterns with flattening:
29//! ```rust
30//! use vantage_expressions::prelude::*;
31//! use vantage_expressions::mocks::mockbuilder;
32//! use serde_json::json;
33//!
34//! # tokio_test::block_on(async {
35//! let mock = mockbuilder::new()
36//!     .with_flattening()
37//!     .on_exact_select("SELECT COUNT(*) FROM orders WHERE user_id IN (SELECT id FROM users WHERE active = true)", json!(42))
38//!     .on_exact_select("SELECT * FROM products", json!([
39//!         {"id": "prod1", "name": "Widget"}
40//!     ]));
41//!
42//! let user_subquery = expr!("SELECT id FROM users WHERE active = {}", true);
43//! let nested_query = expr!("SELECT COUNT(*) FROM orders WHERE user_id IN ({})", (user_subquery));
44//! let result = mock.execute(&nested_query).await.unwrap();
45//! assert_eq!(result, json!(42));
46//! # });
47//! ```
48
49use 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/// Mock builder for creating pattern-based mock data sources
59#[derive(Debug, Clone)]
60pub struct MockBuilder {
61    patterns: HashMap<String, Value>,
62    flatten_expressions: bool,
63    flattener: ExpressionFlattener,
64}
65
66impl MockBuilder {
67    /// Create a new mock builder
68    pub fn new() -> Self {
69        Self {
70            patterns: HashMap::new(),
71            flatten_expressions: false,
72            flattener: ExpressionFlattener::new(),
73        }
74    }
75
76    /// Enable expression flattening before pattern matching
77    pub fn with_flattening(mut self) -> Self {
78        self.flatten_expressions = true;
79        self
80    }
81
82    /// Add an exact pattern match for select queries
83    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        // First resolve any deferred functions
133        let resolved_expr = self.resolve_deferred_expression(expr).await?;
134
135        // Then process (flatten if enabled)
136        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
191/// Create a new mock builder
192pub 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}