Skip to main content

shape_vm/feature_tests/
query_tests.rs

1//! Tests for query/SQL-related features
2//!
3//! This module covers query grammar rules:
4//! - where_clause - Filtering with .where()
5//! - group_by_clause - Grouping with .group()
6//! - having_clause - Post-aggregation filtering with .having()
7//! - order_by_clause - Sorting with .orderBy()
8//! - limit_clause - Limiting results with .limit()
9//! - join_clause - Joining data sources
10//! - on_clause - Timeframe context blocks
11//! - pipe_expr - Pipe operator for data transformation
12//! - named_arg - Named function arguments
13//! - try_operator - Error propagation with ?
14
15use super::{FeatureCategory, FeatureTest};
16
17pub const TESTS: &[FeatureTest] = &[
18    // === Where Clause ===
19    FeatureTest {
20        name: "where_clause_basic",
21        covers: &["where_clause"],
22        code: r#"
23function test() {
24    let data = [1, 2, 3, 4, 5];
25    let filtered = data.filter(x => x > 2);
26    return filtered;
27}
28"#,
29        function: "test",
30        category: FeatureCategory::Collection,
31        requires_data: false,
32    },
33    FeatureTest {
34        name: "where_clause_complex",
35        covers: &["where_clause", "and_expr", "comparison_expr"],
36        code: r#"
37function test() {
38    let items = [{ v: 1 }, { v: 5 }, { v: 10 }];
39    let filtered = items.filter(x => x.v > 2 && x.v < 8);
40    return filtered[0].v;
41}
42"#,
43        function: "test",
44        category: FeatureCategory::Collection,
45        requires_data: false,
46    },
47    // === Group By Clause ===
48    FeatureTest {
49        name: "group_by_clause_basic",
50        covers: &["group_by_clause", "group_by_list", "group_by_expr"],
51        code: r#"
52function test() {
53    let items = [
54        { category: "a", value: 1 },
55        { category: "a", value: 2 },
56        { category: "b", value: 3 }
57    ];
58    let grouped = items.group(x => x.category);
59    return true;
60}
61"#,
62        function: "test",
63        category: FeatureCategory::Collection,
64        requires_data: false,
65    },
66    // === Having Clause ===
67    FeatureTest {
68        name: "having_clause_basic",
69        covers: &["having_clause"],
70        code: r#"
71function test() {
72    let counts = [{ name: "a", count: 5 }, { name: "b", count: 15 }];
73    let filtered = counts.filter(x => x.count > 10);
74    return filtered[0].name;
75}
76"#,
77        function: "test",
78        category: FeatureCategory::Collection,
79        requires_data: false,
80    },
81    // === Order By Clause ===
82    FeatureTest {
83        name: "order_by_clause_asc",
84        covers: &[
85            "order_by_clause",
86            "order_by_list",
87            "order_by_item",
88            "sort_direction",
89        ],
90        code: r#"
91function test() {
92    let items = [3, 1, 4, 1, 5];
93    let sorted = items.sort((a, b) => a - b);
94    return sorted[0];
95}
96"#,
97        function: "test",
98        category: FeatureCategory::Collection,
99        requires_data: false,
100    },
101    FeatureTest {
102        name: "order_by_clause_desc",
103        covers: &["order_by_clause", "sort_direction"],
104        code: r#"
105function test() {
106    let items = [3, 1, 4, 1, 5];
107    let sorted = items.sort((a, b) => b - a);
108    return sorted[0];
109}
110"#,
111        function: "test",
112        category: FeatureCategory::Collection,
113        requires_data: false,
114    },
115    // === Limit Clause ===
116    FeatureTest {
117        name: "limit_clause_basic",
118        covers: &["limit_clause"],
119        code: r#"
120function test() {
121    let items = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
122    let limited = items[0:5];
123    return length(limited);
124}
125"#,
126        function: "test",
127        category: FeatureCategory::Collection,
128        requires_data: false,
129    },
130    // === Join Clause ===
131    FeatureTest {
132        name: "join_clause_basic",
133        covers: &["join_clause", "join_type", "join_source", "join_condition"],
134        code: r#"
135function test() {
136    let left = [{ id: 1, name: "a" }, { id: 2, name: "b" }];
137    let right = [{ id: 1, value: 100 }, { id: 2, value: 200 }];
138    let joined = [
139        for l in left {
140            for r in right {
141                if l.id == r.id { { id: l.id, name: l.name, value: r.value } }
142            }
143        }
144    ];
145    return true;
146}
147"#,
148        function: "test",
149        category: FeatureCategory::Collection,
150        requires_data: false,
151    },
152    // === On Clause (Timeframe Context) ===
153    FeatureTest {
154        name: "on_clause_timeframe",
155        covers: &["on_clause", "timeframe_expr", "timeframe"],
156        code: r#"
157function test() {
158    let result = on(1h) { 2 + 2 };
159    return result;
160}
161"#,
162        function: "test",
163        category: FeatureCategory::Domain,
164        requires_data: false,
165    },
166    FeatureTest {
167        name: "on_clause_nested",
168        covers: &["on_clause", "timeframe_expr"],
169        code: r#"
170function test() {
171    let outer = on(1d) {
172        let inner = on(1h) { 3 };
173        inner * 2
174    };
175    return outer;
176}
177"#,
178        function: "test",
179        category: FeatureCategory::Domain,
180        requires_data: false,
181    },
182    // === Pipe Expression ===
183    FeatureTest {
184        name: "pipe_expr_basic",
185        covers: &["pipe_expr"],
186        code: r#"
187function double(x) { return x * 2; }
188function add_one(x) { return x + 1; }
189
190function test() {
191    let result = 5 |> double |> add_one;
192    return result;
193}
194"#,
195        function: "test",
196        category: FeatureCategory::Operator,
197        requires_data: false,
198    },
199    FeatureTest {
200        name: "pipe_expr_chain",
201        covers: &["pipe_expr"],
202        code: r#"
203function square(x) { return x * x; }
204function negate(x) { return -x; }
205function abs_val(x) { return if x < 0 then -x else x; }
206
207function test() {
208    let result = 3 |> square |> negate |> abs_val;
209    return result;
210}
211"#,
212        function: "test",
213        category: FeatureCategory::Operator,
214        requires_data: false,
215    },
216    FeatureTest {
217        name: "pipe_expr_with_lambda",
218        covers: &["pipe_expr", "arrow_function"],
219        code: r#"
220function test() {
221    let data = [1, 2, 3, 4, 5];
222    let result = data
223        |> (arr => arr.filter(x => x > 2))
224        |> (arr => arr.map(x => x * 2));
225    return result[0];
226}
227"#,
228        function: "test",
229        category: FeatureCategory::Operator,
230        requires_data: false,
231    },
232    // === Named Arguments ===
233    FeatureTest {
234        name: "named_arg_basic",
235        covers: &["named_arg", "arg_list", "argument"],
236        code: r#"
237function greet(name, greeting) {
238    return greeting;
239}
240
241function test() {
242    return greet(name: "World", greeting: "Hello");
243}
244"#,
245        function: "test",
246        category: FeatureCategory::Function,
247        requires_data: false,
248    },
249    FeatureTest {
250        name: "named_arg_mixed",
251        covers: &["named_arg", "arg_list"],
252        code: r#"
253function calc(a, b, c) {
254    return a + b + c;
255}
256
257function test() {
258    return calc(1, c: 3, b: 2);
259}
260"#,
261        function: "test",
262        category: FeatureCategory::Function,
263        requires_data: false,
264    },
265    FeatureTest {
266        name: "named_arg_with_defaults",
267        covers: &["named_arg", "function_param"],
268        code: r#"
269function make_config(width = 100, height = 50) {
270    return width * height;
271}
272
273function test() {
274    return make_config(width: 200);
275}
276"#,
277        function: "test",
278        category: FeatureCategory::Function,
279        requires_data: false,
280    },
281    // === Try Operator ===
282    FeatureTest {
283        name: "try_operator_basic",
284        covers: &["try_operator", "postfix_expr"],
285        code: r#"
286function may_fail(x) {
287    if x < 0 {
288        return { error: "negative value" };
289    }
290    return { value: x * 2 };
291}
292
293function test() {
294    let result = may_fail(5);
295    return result.value;
296}
297"#,
298        function: "test",
299        category: FeatureCategory::Exception,
300        requires_data: false,
301    },
302    FeatureTest {
303        name: "try_operator_chain",
304        covers: &["try_operator"],
305        code: r#"
306function step1(x) { return x + 1; }
307function step2(x) { return x * 2; }
308
309function test() {
310    let a = step1(5);
311    let b = step2(a);
312    return b;
313}
314"#,
315        function: "test",
316        category: FeatureCategory::Exception,
317        requires_data: false,
318    },
319    // try_expr_with_catch removed: throw() builtin removed
320];
321
322#[cfg(test)]
323mod tests {
324    use super::*;
325
326    #[test]
327    fn test_query_tests_defined() {
328        assert!(!TESTS.is_empty());
329        // Should have tests for multiple categories
330        let has_collection = TESTS
331            .iter()
332            .any(|t| t.category == FeatureCategory::Collection);
333        let has_domain = TESTS.iter().any(|t| t.category == FeatureCategory::Domain);
334        let has_operator = TESTS
335            .iter()
336            .any(|t| t.category == FeatureCategory::Operator);
337        let has_function = TESTS
338            .iter()
339            .any(|t| t.category == FeatureCategory::Function);
340        let has_exception = TESTS
341            .iter()
342            .any(|t| t.category == FeatureCategory::Exception);
343
344        assert!(has_collection, "Should have Collection tests");
345        assert!(has_domain, "Should have Domain tests");
346        assert!(has_operator, "Should have Operator tests");
347        assert!(has_function, "Should have Function tests");
348        assert!(has_exception, "Should have Exception tests");
349    }
350}