ricecoder_learning/
scope_isolation_property.rs1#[cfg(test)]
6mod tests {
7 use crate::models::{Rule, RuleScope, RuleSource};
8 use crate::scope_config::ScopeFilter;
9 use proptest::prelude::*;
10
11 fn arb_rule_scope() -> impl Strategy<Value = RuleScope> {
13 prop_oneof![
14 Just(RuleScope::Global),
15 Just(RuleScope::Project),
16 Just(RuleScope::Session),
17 ]
18 }
19
20 fn arb_rule(scope: RuleScope) -> impl Strategy<Value = Rule> {
22 (
23 "[a-z]{1,10}",
24 "[a-z]{1,10}",
25 )
26 .prop_map(move |(pattern, action)| {
27 Rule::new(
28 scope,
29 pattern,
30 action,
31 RuleSource::Learned,
32 )
33 })
34 }
35
36 fn arb_rules_for_scope(scope: RuleScope) -> impl Strategy<Value = Vec<Rule>> {
38 prop::collection::vec(arb_rule(scope), 0..20)
39 }
40
41 proptest! {
47 #[test]
48 fn prop_scope_isolation_no_cross_scope_interference(
49 global_rules in arb_rules_for_scope(RuleScope::Global),
50 project_rules in arb_rules_for_scope(RuleScope::Project),
51 session_rules in arb_rules_for_scope(RuleScope::Session),
52 ) {
53 let mut all_rules = global_rules.clone();
55 all_rules.extend(project_rules.clone());
56 all_rules.extend(session_rules.clone());
57
58 let filtered_global = ScopeFilter::filter_by_scope(&all_rules, RuleScope::Global);
60 let filtered_project = ScopeFilter::filter_by_scope(&all_rules, RuleScope::Project);
61 let filtered_session = ScopeFilter::filter_by_scope(&all_rules, RuleScope::Session);
62
63 prop_assert_eq!(filtered_global.len(), global_rules.len());
65 prop_assert_eq!(filtered_project.len(), project_rules.len());
66 prop_assert_eq!(filtered_session.len(), session_rules.len());
67
68 for rule in &filtered_global {
70 prop_assert_eq!(rule.scope, RuleScope::Global);
71 }
72 for rule in &filtered_project {
73 prop_assert_eq!(rule.scope, RuleScope::Project);
74 }
75 for rule in &filtered_session {
76 prop_assert_eq!(rule.scope, RuleScope::Session);
77 }
78
79 for global_rule in &filtered_global {
81 for project_rule in &filtered_project {
82 prop_assert_ne!(&global_rule.id, &project_rule.id);
83 }
84 for session_rule in &filtered_session {
85 prop_assert_ne!(&global_rule.id, &session_rule.id);
86 }
87 }
88 for project_rule in &filtered_project {
89 for session_rule in &filtered_session {
90 prop_assert_ne!(&project_rule.id, &session_rule.id);
91 }
92 }
93 }
94
95 #[test]
96 fn prop_scope_isolation_filtering_is_idempotent(
97 global_rules in arb_rules_for_scope(RuleScope::Global),
98 project_rules in arb_rules_for_scope(RuleScope::Project),
99 ) {
100 let mut all_rules = global_rules.clone();
102 all_rules.extend(project_rules.clone());
103
104 let filtered_once = ScopeFilter::filter_by_scope(&all_rules, RuleScope::Global);
106 let filtered_twice = ScopeFilter::filter_by_scope(&filtered_once, RuleScope::Global);
107
108 prop_assert_eq!(filtered_once.len(), filtered_twice.len());
110 for (rule1, rule2) in filtered_once.iter().zip(filtered_twice.iter()) {
111 prop_assert_eq!(&rule1.id, &rule2.id);
112 }
113 }
114
115 #[test]
116 fn prop_scope_isolation_union_covers_all_scopes(
117 global_rules in arb_rules_for_scope(RuleScope::Global),
118 project_rules in arb_rules_for_scope(RuleScope::Project),
119 session_rules in arb_rules_for_scope(RuleScope::Session),
120 ) {
121 let mut all_rules = global_rules.clone();
123 all_rules.extend(project_rules.clone());
124 all_rules.extend(session_rules.clone());
125
126 let filtered_global = ScopeFilter::filter_by_scope(&all_rules, RuleScope::Global);
128 let filtered_project = ScopeFilter::filter_by_scope(&all_rules, RuleScope::Project);
129 let filtered_session = ScopeFilter::filter_by_scope(&all_rules, RuleScope::Session);
130
131 let mut union = filtered_global.clone();
133 union.extend(filtered_project.clone());
134 union.extend(filtered_session.clone());
135
136 prop_assert_eq!(union.len(), all_rules.len());
137 }
138
139 #[test]
140 fn prop_scope_isolation_no_interference_different_patterns(
141 global_rules in arb_rules_for_scope(RuleScope::Global),
142 project_rules in arb_rules_for_scope(RuleScope::Project),
143 ) {
144 let interference = ScopeFilter::check_scope_interference(&global_rules, &project_rules);
146
147 let mut expected_interference = false;
150 for global_rule in &global_rules {
151 for project_rule in &project_rules {
152 if global_rule.pattern == project_rule.pattern && global_rule.action != project_rule.action {
153 expected_interference = true;
154 break;
155 }
156 }
157 if expected_interference {
158 break;
159 }
160 }
161
162 prop_assert_eq!(interference, expected_interference);
163 }
164
165 #[test]
166 fn prop_scope_isolation_filter_by_multiple_scopes(
167 global_rules in arb_rules_for_scope(RuleScope::Global),
168 project_rules in arb_rules_for_scope(RuleScope::Project),
169 session_rules in arb_rules_for_scope(RuleScope::Session),
170 ) {
171 let mut all_rules = global_rules.clone();
173 all_rules.extend(project_rules.clone());
174 all_rules.extend(session_rules.clone());
175
176 let filtered = ScopeFilter::filter_by_scopes(
178 &all_rules,
179 &[RuleScope::Project, RuleScope::Session],
180 );
181
182 let expected_count = project_rules.len() + session_rules.len();
184 prop_assert_eq!(filtered.len(), expected_count);
185
186 for rule in &filtered {
188 prop_assert!(
189 rule.scope == RuleScope::Project || rule.scope == RuleScope::Session,
190 "Rule scope should be Project or Session, got {:?}",
191 rule.scope
192 );
193 }
194 }
195
196 #[test]
197 fn prop_scope_isolation_precedence_respects_scopes(
198 global_rules in arb_rules_for_scope(RuleScope::Global),
199 project_rules in arb_rules_for_scope(RuleScope::Project),
200 session_rules in arb_rules_for_scope(RuleScope::Session),
201 ) {
202 let mut all_rules = global_rules.clone();
204 all_rules.extend(project_rules.clone());
205 all_rules.extend(session_rules.clone());
206
207 let project_precedence = ScopeFilter::get_rules_with_precedence(&all_rules, RuleScope::Project);
209
210 for rule in &project_precedence {
212 prop_assert!(
213 rule.scope == RuleScope::Project || rule.scope == RuleScope::Session,
214 "Project precedence should only include Project and Session rules"
215 );
216 }
217
218 let global_precedence = ScopeFilter::get_rules_with_precedence(&all_rules, RuleScope::Global);
220
221 for rule in &global_precedence {
223 prop_assert_eq!(
224 rule.scope,
225 RuleScope::Global,
226 "Global precedence should only include Global rules"
227 );
228 }
229 }
230
231 #[test]
232 fn prop_scope_isolation_empty_scopes(
233 global_rules in arb_rules_for_scope(RuleScope::Global),
234 ) {
235 let project_rules: Vec<Rule> = Vec::new();
237 let session_rules: Vec<Rule> = Vec::new();
238
239 let mut all_rules = global_rules.clone();
240 all_rules.extend(project_rules.clone());
241 all_rules.extend(session_rules.clone());
242
243 let filtered_global = ScopeFilter::filter_by_scope(&all_rules, RuleScope::Global);
245 let filtered_project = ScopeFilter::filter_by_scope(&all_rules, RuleScope::Project);
246 let filtered_session = ScopeFilter::filter_by_scope(&all_rules, RuleScope::Session);
247
248 prop_assert_eq!(filtered_global.len(), global_rules.len());
250 prop_assert_eq!(filtered_project.len(), 0);
251 prop_assert_eq!(filtered_session.len(), 0);
252 }
253 }
254}