Skip to main content

shape_vm/feature_tests/
stream_tests.rs

1//! Tests for stream grammar rules
2//!
3//! This module covers:
4//! - Stream definitions
5//! - Stream state declarations
6//! - Stream event handlers (on_tick, on_bar)
7//! - Stream lifecycle
8
9use super::{FeatureCategory, FeatureTest};
10
11pub const TESTS: &[FeatureTest] = &[
12    // === Stream Definitions ===
13    FeatureTest {
14        name: "stream_def_basic",
15        covers: &["stream_def"],
16        code: r#"
17            stream Counter {
18                state { count: 0 }
19                on_tick { state.count = state.count + 1; }
20            }
21            function test() { return 1; }
22        "#,
23        function: "test",
24        category: FeatureCategory::Domain,
25        requires_data: false,
26    },
27    FeatureTest {
28        name: "stream_def_with_params",
29        covers: &["stream_def", "function_params"],
30        code: r#"
31            stream MovingAverage(period) {
32                state { sum: 0, count: 0 }
33                on_tick {
34                    state.sum = state.sum + data[0].close;
35                    state.count = state.count + 1;
36                }
37            }
38            function test() { return 1; }
39        "#,
40        function: "test",
41        category: FeatureCategory::Domain,
42        requires_data: false,
43    },
44    // === Stream State ===
45    FeatureTest {
46        name: "stream_state_simple",
47        covers: &["stream_state", "object_literal"],
48        code: r#"
49            stream TestStream {
50                state { value: 0 }
51                on_tick { }
52            }
53            function test() { return 1; }
54        "#,
55        function: "test",
56        category: FeatureCategory::Domain,
57        requires_data: false,
58    },
59    FeatureTest {
60        name: "stream_state_multiple_fields",
61        covers: &["stream_state", "object_literal", "object_field"],
62        code: r#"
63            stream TestStream {
64                state {
65                    count: 0,
66                    sum: 0.0,
67                    high: -999999,
68                    low: 999999,
69                    active: false
70                }
71                on_tick { }
72            }
73            function test() { return 1; }
74        "#,
75        function: "test",
76        category: FeatureCategory::Domain,
77        requires_data: false,
78    },
79    FeatureTest {
80        name: "stream_state_nested",
81        covers: &["stream_state", "object_literal"],
82        code: r#"
83            stream TestStream {
84                state {
85                    position: { size: 0, entry_price: 0 },
86                    stats: { wins: 0, losses: 0 }
87                }
88                on_tick { }
89            }
90            function test() { return 1; }
91        "#,
92        function: "test",
93        category: FeatureCategory::Domain,
94        requires_data: false,
95    },
96    // === Stream on_tick Handler ===
97    FeatureTest {
98        name: "stream_on_tick_basic",
99        covers: &["stream_on_tick", "block_expr"],
100        code: r#"
101            stream TickCounter {
102                state { ticks: 0 }
103                on_tick {
104                    state.ticks = state.ticks + 1;
105                }
106            }
107            function test() { return 1; }
108        "#,
109        function: "test",
110        category: FeatureCategory::Domain,
111        requires_data: false,
112    },
113    FeatureTest {
114        name: "stream_on_tick_with_logic",
115        covers: &["stream_on_tick", "if_stmt", "block_expr"],
116        code: r#"
117            stream HighTracker {
118                state { high: 0 }
119                on_tick {
120                    if (data[0].high > state.high) {
121                        state.high = data[0].high;
122                    }
123                }
124            }
125            function test() { return 1; }
126        "#,
127        function: "test",
128        category: FeatureCategory::Domain,
129        requires_data: false,
130    },
131    FeatureTest {
132        name: "stream_on_tick_emit",
133        covers: &["stream_on_tick", "function_call"],
134        code: r#"
135            stream SignalEmitter {
136                state { triggered: false }
137                on_tick {
138                    if (data[0].close > data[1].close && !state.triggered) {
139                        emit("buy_signal", { price: data[0].close });
140                        state.triggered = true;
141                    }
142                }
143            }
144            function test() { return 1; }
145        "#,
146        function: "test",
147        category: FeatureCategory::Domain,
148        requires_data: false,
149    },
150    // === Stream on_bar Handler ===
151    FeatureTest {
152        name: "stream_on_bar_basic",
153        covers: &["stream_on_bar", "block_expr"],
154        code: r#"
155            stream BarCounter {
156                state { bars: 0 }
157                on_bar {
158                    state.bars = state.bars + 1;
159                }
160            }
161            function test() { return 1; }
162        "#,
163        function: "test",
164        category: FeatureCategory::Domain,
165        requires_data: false,
166    },
167    FeatureTest {
168        name: "stream_on_bar_aggregation",
169        covers: &["stream_on_bar", "block_expr", "assignment"],
170        code: r#"
171            stream VolumeAccumulator {
172                state { total_volume: 0, bar_count: 0 }
173                on_bar {
174                    state.total_volume = state.total_volume + bar.volume;
175                    state.bar_count = state.bar_count + 1;
176                }
177            }
178            function test() { return 1; }
179        "#,
180        function: "test",
181        category: FeatureCategory::Domain,
182        requires_data: false,
183    },
184    FeatureTest {
185        name: "stream_on_bar_with_condition",
186        covers: &["stream_on_bar", "if_stmt"],
187        code: r#"
188            stream GreenBarCounter {
189                state { green_bars: 0, red_bars: 0 }
190                on_bar {
191                    if (bar.close > bar.open) {
192                        state.green_bars = state.green_bars + 1;
193                    } else {
194                        state.red_bars = state.red_bars + 1;
195                    }
196                }
197            }
198            function test() { return 1; }
199        "#,
200        function: "test",
201        category: FeatureCategory::Domain,
202        requires_data: false,
203    },
204    // === Combined Stream Patterns ===
205    FeatureTest {
206        name: "stream_combined_handlers",
207        covers: &[
208            "stream_def",
209            "stream_state",
210            "stream_on_tick",
211            "stream_on_bar",
212        ],
213        code: r#"
214            stream CompleteStream {
215                state {
216                    tick_count: 0,
217                    bar_count: 0,
218                    last_price: 0
219                }
220
221                on_tick {
222                    state.tick_count = state.tick_count + 1;
223                    state.last_price = data[0].close;
224                }
225
226                on_bar {
227                    state.bar_count = state.bar_count + 1;
228                }
229            }
230            function test() { return 1; }
231        "#,
232        function: "test",
233        category: FeatureCategory::Domain,
234        requires_data: false,
235    },
236];
237
238#[cfg(test)]
239mod tests {
240    use super::*;
241    use std::collections::BTreeSet;
242
243    #[test]
244    fn test_stream_tests_defined() {
245        assert!(!TESTS.is_empty());
246        // Should have at least 8 tests
247        assert!(
248            TESTS.len() >= 8,
249            "Expected at least 8 stream tests, got {}",
250            TESTS.len()
251        );
252    }
253
254    #[test]
255    fn test_stream_tests_cover_main_rules() {
256        let all_rules: BTreeSet<_> = TESTS
257            .iter()
258            .flat_map(|t| t.covers.iter().copied())
259            .collect();
260
261        // Check that key stream rules are covered
262        assert!(
263            all_rules.contains(&"stream_def"),
264            "Missing stream_def coverage"
265        );
266        assert!(
267            all_rules.contains(&"stream_state"),
268            "Missing stream_state coverage"
269        );
270        assert!(
271            all_rules.contains(&"stream_on_tick"),
272            "Missing stream_on_tick coverage"
273        );
274        assert!(
275            all_rules.contains(&"stream_on_bar"),
276            "Missing stream_on_bar coverage"
277        );
278    }
279}