Skip to main content

shape_vm/feature_tests/
window_tests.rs

1//! Tests for window and time-related features
2//!
3//! This module covers:
4//! - Window function calls (row_number, rank, etc.)
5//! - OVER clauses with PARTITION BY and ORDER BY
6//! - Time windows (last, between)
7//! - Timeframe specifications
8//! - Datetime arithmetic
9//! - Compound durations
10//! - Window frame clauses
11//! - Session windows
12
13use super::{FeatureCategory, FeatureTest};
14
15pub const TESTS: &[FeatureTest] = &[
16    // === Window Functions ===
17    FeatureTest {
18        name: "window_function_row_number",
19        covers: &["window_function_call", "over_clause"],
20        code: "function test() { return row_number() OVER (ORDER BY x); }",
21        function: "test",
22        category: FeatureCategory::Domain,
23        requires_data: false,
24    },
25    FeatureTest {
26        name: "window_function_partition_by",
27        covers: &["window_function_call", "over_clause", "partition_clause"],
28        code: "function test() { return sum(value) OVER (PARTITION BY category ORDER BY date); }",
29        function: "test",
30        category: FeatureCategory::Domain,
31        requires_data: false,
32    },
33    FeatureTest {
34        name: "window_function_rank",
35        covers: &["window_function_call", "over_clause"],
36        code: "function test() { return rank() OVER (PARTITION BY group ORDER BY score DESC); }",
37        function: "test",
38        category: FeatureCategory::Domain,
39        requires_data: false,
40    },
41    // === Time Windows ===
42    FeatureTest {
43        name: "time_window_last_days",
44        covers: &["time_window", "last_window"],
45        code: r#"function test() { return last(5, "days"); }"#,
46        function: "test",
47        category: FeatureCategory::Domain,
48        requires_data: false,
49    },
50    FeatureTest {
51        name: "time_window_last_keyword",
52        covers: &["last_window", "duration"],
53        code: "function test() { return last 1 year; }",
54        function: "test",
55        category: FeatureCategory::Domain,
56        requires_data: false,
57    },
58    FeatureTest {
59        name: "time_window_last_bars",
60        covers: &["time_window", "last_window"],
61        code: r#"function test() { return last(100, "bars"); }"#,
62        function: "test",
63        category: FeatureCategory::Domain,
64        requires_data: false,
65    },
66    // === Between Windows ===
67    FeatureTest {
68        name: "between_window_dates",
69        covers: &["between_window", "datetime_literal"],
70        code: r#"function test() { return between @"2020-01-01" and @"2021-01-01"; }"#,
71        function: "test",
72        category: FeatureCategory::Domain,
73        requires_data: false,
74    },
75    FeatureTest {
76        name: "between_window_time_refs",
77        covers: &["between_window", "time_ref"],
78        code: "function test() { return between @today and @tomorrow; }",
79        function: "test",
80        category: FeatureCategory::Domain,
81        requires_data: false,
82    },
83    // === Timeframe Specifications ===
84    FeatureTest {
85        name: "timeframe_spec_5m",
86        covers: &["timeframe_spec", "timeframe", "data_ref"],
87        code: "function test() { return data(5m)[0]; }",
88        function: "test",
89        category: FeatureCategory::Domain,
90        requires_data: true,
91    },
92    FeatureTest {
93        name: "timeframe_spec_1h",
94        covers: &["timeframe_spec", "timeframe"],
95        code: "function test() { return data(1h)[0].close; }",
96        function: "test",
97        category: FeatureCategory::Domain,
98        requires_data: true,
99    },
100    FeatureTest {
101        name: "timeframe_spec_daily",
102        covers: &["timeframe_spec", "timeframe"],
103        code: "function test() { return data(1d)[-1]; }",
104        function: "test",
105        category: FeatureCategory::Domain,
106        requires_data: true,
107    },
108    // === Datetime Arithmetic ===
109    FeatureTest {
110        name: "datetime_arithmetic_add_days",
111        covers: &["datetime_arithmetic", "datetime_expr", "duration"],
112        code: "function test() { return @today + 5d; }",
113        function: "test",
114        category: FeatureCategory::Domain,
115        requires_data: false,
116    },
117    FeatureTest {
118        name: "datetime_arithmetic_sub_hours",
119        covers: &["datetime_arithmetic", "datetime_expr", "duration"],
120        code: "function test() { return @now - 2h; }",
121        function: "test",
122        category: FeatureCategory::Domain,
123        requires_data: false,
124    },
125    FeatureTest {
126        name: "datetime_arithmetic_complex",
127        covers: &["datetime_arithmetic", "datetime_literal", "duration"],
128        code: r#"function test() { return @"2020-06-15" + 30d - 1w; }"#,
129        function: "test",
130        category: FeatureCategory::Domain,
131        requires_data: false,
132    },
133    // === Compound Durations ===
134    FeatureTest {
135        name: "compound_duration_hm",
136        covers: &["compound_duration", "duration_unit"],
137        code: "function test() { return 5h30m; }",
138        function: "test",
139        category: FeatureCategory::Literal,
140        requires_data: false,
141    },
142    FeatureTest {
143        name: "compound_duration_dh",
144        covers: &["compound_duration", "duration_unit"],
145        code: "function test() { return 1d12h; }",
146        function: "test",
147        category: FeatureCategory::Literal,
148        requires_data: false,
149    },
150    FeatureTest {
151        name: "compound_duration_hms",
152        covers: &["compound_duration", "duration_unit"],
153        code: "function test() { return 2h30m45s; }",
154        function: "test",
155        category: FeatureCategory::Literal,
156        requires_data: false,
157    },
158    // === Window Frame Clauses ===
159    FeatureTest {
160        name: "window_frame_rows_between",
161        covers: &["window_frame_clause", "frame_bound"],
162        code: "function test() { return sum(x) OVER (ORDER BY y ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING); }",
163        function: "test",
164        category: FeatureCategory::Domain,
165        requires_data: false,
166    },
167    FeatureTest {
168        name: "window_frame_rows_unbounded",
169        covers: &["window_frame_clause", "frame_bound"],
170        code: "function test() { return sum(x) OVER (ORDER BY y ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW); }",
171        function: "test",
172        category: FeatureCategory::Domain,
173        requires_data: false,
174    },
175    FeatureTest {
176        name: "window_frame_range",
177        covers: &["window_frame_clause", "frame_bound"],
178        code: "function test() { return avg(price) OVER (ORDER BY date RANGE BETWEEN 7d PRECEDING AND CURRENT ROW); }",
179        function: "test",
180        category: FeatureCategory::Domain,
181        requires_data: false,
182    },
183    // === Session Windows ===
184    FeatureTest {
185        name: "session_window_30m",
186        covers: &["session_window", "duration"],
187        code: "function test() { return session(30m); }",
188        function: "test",
189        category: FeatureCategory::Domain,
190        requires_data: false,
191    },
192    FeatureTest {
193        name: "session_window_with_gap",
194        covers: &["session_window", "duration"],
195        code: "function test() { return session(5m, gap: 1m); }",
196        function: "test",
197        category: FeatureCategory::Domain,
198        requires_data: false,
199    },
200    FeatureTest {
201        name: "session_window_1h",
202        covers: &["session_window", "duration"],
203        code: "function test() { return session(1h); }",
204        function: "test",
205        category: FeatureCategory::Domain,
206        requires_data: false,
207    },
208];
209
210#[cfg(test)]
211mod tests {
212    use super::*;
213    use std::collections::BTreeSet;
214
215    #[test]
216    fn test_window_tests_defined() {
217        assert!(!TESTS.is_empty());
218        // Should have at least 10 tests
219        assert!(
220            TESTS.len() >= 10,
221            "Expected at least 10 window tests, got {}",
222            TESTS.len()
223        );
224    }
225
226    #[test]
227    fn test_window_tests_cover_main_rules() {
228        let all_rules: BTreeSet<_> = TESTS
229            .iter()
230            .flat_map(|t| t.covers.iter().copied())
231            .collect();
232
233        // Check that key window rules are covered
234        assert!(
235            all_rules.contains(&"window_function_call"),
236            "Missing window_function_call coverage"
237        );
238        assert!(
239            all_rules.contains(&"over_clause"),
240            "Missing over_clause coverage"
241        );
242        assert!(
243            all_rules.contains(&"time_window") || all_rules.contains(&"last_window"),
244            "Missing time window coverage"
245        );
246        assert!(
247            all_rules.contains(&"between_window"),
248            "Missing between_window coverage"
249        );
250        assert!(
251            all_rules.contains(&"datetime_arithmetic"),
252            "Missing datetime_arithmetic coverage"
253        );
254        assert!(
255            all_rules.contains(&"compound_duration"),
256            "Missing compound_duration coverage"
257        );
258        assert!(
259            all_rules.contains(&"window_frame_clause"),
260            "Missing window_frame_clause coverage"
261        );
262        assert!(
263            all_rules.contains(&"session_window"),
264            "Missing session_window coverage"
265        );
266    }
267}