1#[cfg(test)]
6mod tests {
7 use proptest::prelude::*;
8 use crate::{ConflictResolver, Rule, RuleScope, RuleSource};
9
10 proptest! {
11 #[test]
15 fn prop_project_rules_override_global(
16 project_pattern in "[a-z0-9]{1,20}",
17 project_action in "[a-z0-9]{1,20}",
18 global_action in "[a-z0-9]{1,20}",
19 ) {
20 let mut project_rule = Rule::new(
22 RuleScope::Project,
23 project_pattern.clone(),
24 project_action,
25 RuleSource::Learned,
26 );
27 project_rule.id = "project_rule".to_string();
28
29 let mut global_rule = Rule::new(
30 RuleScope::Global,
31 project_pattern.clone(),
32 global_action,
33 RuleSource::Learned,
34 );
35 global_rule.id = "global_rule".to_string();
36
37 let rules = vec![global_rule.clone(), project_rule.clone()];
38
39 let selected = ConflictResolver::apply_precedence(&rules);
41
42 prop_assert!(selected.is_some());
44 prop_assert_eq!(selected.unwrap().id, "project_rule");
45 }
46
47 #[test]
51 fn prop_global_rules_override_session(
52 pattern in "[a-z0-9]{1,20}",
53 global_action in "[a-z0-9]{1,20}",
54 session_action in "[a-z0-9]{1,20}",
55 ) {
56 let mut global_rule = Rule::new(
57 RuleScope::Global,
58 pattern.clone(),
59 global_action,
60 RuleSource::Learned,
61 );
62 global_rule.id = "global_rule".to_string();
63
64 let mut session_rule = Rule::new(
65 RuleScope::Session,
66 pattern,
67 session_action,
68 RuleSource::Learned,
69 );
70 session_rule.id = "session_rule".to_string();
71
72 let rules = vec![session_rule.clone(), global_rule.clone()];
73
74 let selected = ConflictResolver::apply_precedence(&rules);
76
77 prop_assert!(selected.is_some());
79 prop_assert_eq!(selected.unwrap().id, "global_rule");
80 }
81
82 #[test]
86 fn prop_project_rules_override_session(
87 pattern in "[a-z0-9]{1,20}",
88 project_action in "[a-z0-9]{1,20}",
89 session_action in "[a-z0-9]{1,20}",
90 ) {
91 let mut project_rule = Rule::new(
92 RuleScope::Project,
93 pattern.clone(),
94 project_action,
95 RuleSource::Learned,
96 );
97 project_rule.id = "project_rule".to_string();
98
99 let mut session_rule = Rule::new(
100 RuleScope::Session,
101 pattern,
102 session_action,
103 RuleSource::Learned,
104 );
105 session_rule.id = "session_rule".to_string();
106
107 let rules = vec![session_rule.clone(), project_rule.clone()];
108
109 let selected = ConflictResolver::apply_precedence(&rules);
111
112 prop_assert!(selected.is_some());
114 prop_assert_eq!(selected.unwrap().id, "project_rule");
115 }
116
117 #[test]
121 fn prop_full_precedence_order(
122 pattern in "[a-z0-9]{1,20}",
123 project_action in "[a-z0-9]{1,20}",
124 global_action in "[a-z0-9]{1,20}",
125 session_action in "[a-z0-9]{1,20}",
126 ) {
127 let mut project_rule = Rule::new(
128 RuleScope::Project,
129 pattern.clone(),
130 project_action,
131 RuleSource::Learned,
132 );
133 project_rule.id = "project_rule".to_string();
134
135 let mut global_rule = Rule::new(
136 RuleScope::Global,
137 pattern.clone(),
138 global_action,
139 RuleSource::Learned,
140 );
141 global_rule.id = "global_rule".to_string();
142
143 let mut session_rule = Rule::new(
144 RuleScope::Session,
145 pattern,
146 session_action,
147 RuleSource::Learned,
148 );
149 session_rule.id = "session_rule".to_string();
150
151 let permutations = vec![
153 vec![project_rule.clone(), global_rule.clone(), session_rule.clone()],
154 vec![project_rule.clone(), session_rule.clone(), global_rule.clone()],
155 vec![global_rule.clone(), project_rule.clone(), session_rule.clone()],
156 vec![global_rule.clone(), session_rule.clone(), project_rule.clone()],
157 vec![session_rule.clone(), project_rule.clone(), global_rule.clone()],
158 vec![session_rule.clone(), global_rule.clone(), project_rule.clone()],
159 ];
160
161 for rules in permutations {
162 let selected = ConflictResolver::apply_precedence(&rules);
163 prop_assert!(selected.is_some());
164 prop_assert_eq!(selected.unwrap().id, "project_rule");
166 }
167 }
168
169 #[test]
173 fn prop_resolve_conflicts_maintains_precedence(
174 pattern1 in "[a-z0-9]{1,20}",
175 pattern2 in "[a-z0-9]{1,20}",
176 ) {
177 prop_assume!(pattern1 != pattern2);
178
179 let mut project_rule1 = Rule::new(
180 RuleScope::Project,
181 pattern1.clone(),
182 "project_action1".to_string(),
183 RuleSource::Learned,
184 );
185 project_rule1.id = "project_rule1".to_string();
186
187 let mut global_rule1 = Rule::new(
188 RuleScope::Global,
189 pattern1.clone(),
190 "global_action1".to_string(),
191 RuleSource::Learned,
192 );
193 global_rule1.id = "global_rule1".to_string();
194
195 let mut global_rule2 = Rule::new(
196 RuleScope::Global,
197 pattern2.clone(),
198 "global_action2".to_string(),
199 RuleSource::Learned,
200 );
201 global_rule2.id = "global_rule2".to_string();
202
203 let mut session_rule2 = Rule::new(
204 RuleScope::Session,
205 pattern2.clone(),
206 "session_action2".to_string(),
207 RuleSource::Learned,
208 );
209 session_rule2.id = "session_rule2".to_string();
210
211 let rules = vec![
212 project_rule1.clone(),
213 global_rule1.clone(),
214 global_rule2.clone(),
215 session_rule2.clone(),
216 ];
217
218 let resolved = ConflictResolver::resolve_conflicts(&rules).unwrap();
219
220 prop_assert_eq!(resolved.len(), 2);
222
223 let pattern1_rule = resolved.iter().find(|r| r.pattern == pattern1);
225 prop_assert!(pattern1_rule.is_some());
226 prop_assert_eq!(pattern1_rule.unwrap().id.as_str(), "project_rule1");
227
228 let pattern2_rule = resolved.iter().find(|r| r.pattern == pattern2);
230 prop_assert!(pattern2_rule.is_some());
231 prop_assert_eq!(pattern2_rule.unwrap().id.as_str(), "global_rule2");
232 }
233
234 #[test]
238 fn prop_get_highest_priority_returns_project(
239 pattern in "[a-z0-9]{1,20}",
240 ) {
241 let mut project_rule = Rule::new(
242 RuleScope::Project,
243 pattern.clone(),
244 "project_action".to_string(),
245 RuleSource::Learned,
246 );
247 project_rule.id = "project_rule".to_string();
248
249 let mut global_rule = Rule::new(
250 RuleScope::Global,
251 pattern.clone(),
252 "global_action".to_string(),
253 RuleSource::Learned,
254 );
255 global_rule.id = "global_rule".to_string();
256
257 let mut session_rule = Rule::new(
258 RuleScope::Session,
259 pattern.clone(),
260 "session_action".to_string(),
261 RuleSource::Learned,
262 );
263 session_rule.id = "session_rule".to_string();
264
265 let rules = vec![global_rule, session_rule, project_rule.clone()];
266
267 let highest = ConflictResolver::get_highest_priority_rule(&rules, &pattern);
268
269 prop_assert!(highest.is_some());
270 prop_assert_eq!(highest.unwrap().id, "project_rule");
271 }
272
273 #[test]
277 fn prop_get_highest_priority_returns_global_when_no_project(
278 pattern in "[a-z0-9]{1,20}",
279 ) {
280 let mut global_rule = Rule::new(
281 RuleScope::Global,
282 pattern.clone(),
283 "global_action".to_string(),
284 RuleSource::Learned,
285 );
286 global_rule.id = "global_rule".to_string();
287
288 let mut session_rule = Rule::new(
289 RuleScope::Session,
290 pattern.clone(),
291 "session_action".to_string(),
292 RuleSource::Learned,
293 );
294 session_rule.id = "session_rule".to_string();
295
296 let rules = vec![session_rule, global_rule.clone()];
297
298 let highest = ConflictResolver::get_highest_priority_rule(&rules, &pattern);
299
300 prop_assert!(highest.is_some());
301 prop_assert_eq!(highest.unwrap().id, "global_rule");
302 }
303
304 #[test]
308 fn prop_precedence_independent_of_order(
309 pattern in "[a-z0-9]{1,20}",
310 ) {
311 let mut project_rule = Rule::new(
312 RuleScope::Project,
313 pattern.clone(),
314 "project_action".to_string(),
315 RuleSource::Learned,
316 );
317 project_rule.id = "project_rule".to_string();
318
319 let mut global_rule = Rule::new(
320 RuleScope::Global,
321 pattern.clone(),
322 "global_action".to_string(),
323 RuleSource::Learned,
324 );
325 global_rule.id = "global_rule".to_string();
326
327 let mut session_rule = Rule::new(
328 RuleScope::Session,
329 pattern.clone(),
330 "session_action".to_string(),
331 RuleSource::Learned,
332 );
333 session_rule.id = "session_rule".to_string();
334
335 let orderings = vec![
337 vec![project_rule.clone(), global_rule.clone(), session_rule.clone()],
338 vec![session_rule.clone(), project_rule.clone(), global_rule.clone()],
339 vec![global_rule.clone(), session_rule.clone(), project_rule.clone()],
340 ];
341
342 let mut results = Vec::new();
343 for rules in orderings {
344 let selected = ConflictResolver::apply_precedence(&rules);
345 results.push(selected.map(|r| r.id));
346 }
347
348 prop_assert!(results.iter().all(|r| r == &Some("project_rule".to_string())));
350 }
351
352 #[test]
356 fn prop_cross_scope_conflict_detection(
357 pattern in "[a-z0-9]{1,20}",
358 project_action in "[a-z0-9]{1,20}",
359 global_action in "[a-z0-9]{1,20}",
360 ) {
361 prop_assume!(project_action != global_action);
362
363 let mut project_rule = Rule::new(
364 RuleScope::Project,
365 pattern.clone(),
366 project_action,
367 RuleSource::Learned,
368 );
369 project_rule.id = "project_rule".to_string();
370
371 let mut global_rule = Rule::new(
372 RuleScope::Global,
373 pattern,
374 global_action,
375 RuleSource::Learned,
376 );
377 global_rule.id = "global_rule".to_string();
378
379 let conflicts = ConflictResolver::check_cross_scope_conflicts(
380 &[project_rule.clone()],
381 &[global_rule.clone()],
382 );
383
384 prop_assert_eq!(conflicts.len(), 1);
386 prop_assert_eq!(conflicts[0].0.id.as_str(), "project_rule");
387 prop_assert_eq!(conflicts[0].1.id.as_str(), "global_rule");
388 }
389
390 #[test]
394 fn prop_no_conflict_different_patterns(
395 pattern1 in "[a-z0-9]{1,20}",
396 pattern2 in "[a-z0-9]{1,20}",
397 ) {
398 prop_assume!(pattern1 != pattern2);
399
400 let mut project_rule = Rule::new(
401 RuleScope::Project,
402 pattern1,
403 "action1".to_string(),
404 RuleSource::Learned,
405 );
406 project_rule.id = "project_rule".to_string();
407
408 let mut global_rule = Rule::new(
409 RuleScope::Global,
410 pattern2,
411 "action2".to_string(),
412 RuleSource::Learned,
413 );
414 global_rule.id = "global_rule".to_string();
415
416 let conflicts = ConflictResolver::check_cross_scope_conflicts(
417 &[project_rule],
418 &[global_rule],
419 );
420
421 prop_assert_eq!(conflicts.len(), 0);
423 }
424 }
425}