1#[cfg(test)]
10mod tests {
11 use proptest::prelude::*;
12 use crate::{Rule, RuleScope, RuleSource, RuleValidator};
13
14 fn valid_pattern_strategy() -> impl Strategy<Value = String> {
16 r"[a-zA-Z0-9_\-\.]+"
17 .prop_map(|s| s.to_string())
18 .prop_filter("pattern must not be empty", |s| !s.is_empty())
19 }
20
21 fn valid_action_strategy() -> impl Strategy<Value = String> {
23 r"[a-zA-Z0-9_\-\.\s]+"
24 .prop_map(|s| s.to_string())
25 .prop_filter("action must not be empty", |s| !s.is_empty())
26 }
27
28 fn valid_confidence_strategy() -> impl Strategy<Value = f32> {
30 0.0f32..=1.0f32
31 }
32
33 fn valid_success_rate_strategy() -> impl Strategy<Value = f32> {
35 0.0f32..=1.0f32
36 }
37
38 fn valid_rule_strategy() -> impl Strategy<Value = Rule> {
40 (
41 valid_pattern_strategy(),
42 valid_action_strategy(),
43 valid_confidence_strategy(),
44 valid_success_rate_strategy(),
45 )
46 .prop_map(|(pattern, action, confidence, success_rate)| {
47 let mut rule = Rule::new(
48 RuleScope::Global,
49 pattern,
50 action,
51 RuleSource::Learned,
52 );
53 rule.confidence = confidence;
54 rule.success_rate = success_rate;
55 rule
56 })
57 }
58
59 fn invalid_confidence_strategy() -> impl Strategy<Value = f32> {
61 prop_oneof![
62 Just(1.5f32),
63 Just(-0.5f32),
64 Just(2.0f32),
65 Just(-1.0f32),
66 Just(f32::NAN),
67 Just(f32::INFINITY),
68 Just(f32::NEG_INFINITY),
69 ]
70 }
71
72 fn invalid_success_rate_strategy() -> impl Strategy<Value = f32> {
74 prop_oneof![
75 Just(1.5f32),
76 Just(-0.5f32),
77 Just(2.0f32),
78 Just(-1.0f32),
79 Just(f32::NAN),
80 Just(f32::INFINITY),
81 Just(f32::NEG_INFINITY),
82 ]
83 }
84
85 proptest! {
89 #[test]
90 fn prop_valid_rules_accepted(rule in valid_rule_strategy()) {
91 let validator = RuleValidator::new();
92 assert!(
93 validator.validate(&rule).is_ok(),
94 "Valid rule should be accepted: {:?}",
95 rule
96 );
97 }
98
99 #[test]
103 fn prop_invalid_confidence_rejected(confidence in invalid_confidence_strategy()) {
104 let validator = RuleValidator::new();
105 let mut rule = Rule::new(
106 RuleScope::Global,
107 "pattern".to_string(),
108 "action".to_string(),
109 RuleSource::Learned,
110 );
111 rule.confidence = confidence;
112
113 assert!(
114 validator.validate(&rule).is_err(),
115 "Rule with invalid confidence {} should be rejected",
116 confidence
117 );
118 }
119
120 #[test]
124 fn prop_invalid_success_rate_rejected(success_rate in invalid_success_rate_strategy()) {
125 let validator = RuleValidator::new();
126 let mut rule = Rule::new(
127 RuleScope::Global,
128 "pattern".to_string(),
129 "action".to_string(),
130 RuleSource::Learned,
131 );
132 rule.success_rate = success_rate;
133
134 assert!(
135 validator.validate(&rule).is_err(),
136 "Rule with invalid success rate {} should be rejected",
137 success_rate
138 );
139 }
140 }
141
142 #[test]
146 fn prop_empty_pattern_rejected() {
147 let validator = RuleValidator::new();
148 let mut rule = Rule::new(
149 RuleScope::Global,
150 "pattern".to_string(),
151 "action".to_string(),
152 RuleSource::Learned,
153 );
154 rule.pattern = String::new();
155
156 assert!(
157 validator.validate(&rule).is_err(),
158 "Rule with empty pattern should be rejected"
159 );
160 }
161
162 #[test]
166 fn prop_empty_action_rejected() {
167 let validator = RuleValidator::new();
168 let mut rule = Rule::new(
169 RuleScope::Global,
170 "pattern".to_string(),
171 "action".to_string(),
172 RuleSource::Learned,
173 );
174 rule.action = String::new();
175
176 assert!(
177 validator.validate(&rule).is_err(),
178 "Rule with empty action should be rejected"
179 );
180 }
181
182 #[test]
186 fn prop_empty_id_rejected() {
187 let validator = RuleValidator::new();
188 let mut rule = Rule::new(
189 RuleScope::Global,
190 "pattern".to_string(),
191 "action".to_string(),
192 RuleSource::Learned,
193 );
194 rule.id = String::new();
195
196 assert!(
197 validator.validate(&rule).is_err(),
198 "Rule with empty ID should be rejected"
199 );
200 }
201
202 #[test]
206 fn prop_zero_version_rejected() {
207 let validator = RuleValidator::new();
208 let mut rule = Rule::new(
209 RuleScope::Global,
210 "pattern".to_string(),
211 "action".to_string(),
212 RuleSource::Learned,
213 );
214 rule.version = 0;
215
216 assert!(
217 validator.validate(&rule).is_err(),
218 "Rule with version 0 should be rejected"
219 );
220 }
221
222 proptest! {
223 #[test]
227 fn prop_conflict_detection(pattern in valid_pattern_strategy()) {
228 let validator = RuleValidator::new();
229
230 let rule1 = Rule::new(
231 RuleScope::Global,
232 pattern.clone(),
233 "action1".to_string(),
234 RuleSource::Learned,
235 );
236
237 let rule2 = Rule::new(
238 RuleScope::Global,
239 pattern,
240 "action2".to_string(),
241 RuleSource::Learned,
242 );
243
244 assert!(
245 validator.check_conflicts(&rule2, &[rule1]).is_err(),
246 "Conflicting rules should be detected"
247 );
248 }
249
250 #[test]
254 fn prop_no_conflict_different_scope(pattern in valid_pattern_strategy()) {
255 let validator = RuleValidator::new();
256
257 let rule1 = Rule::new(
258 RuleScope::Global,
259 pattern.clone(),
260 "action1".to_string(),
261 RuleSource::Learned,
262 );
263
264 let rule2 = Rule::new(
265 RuleScope::Project,
266 pattern,
267 "action2".to_string(),
268 RuleSource::Learned,
269 );
270
271 assert!(
272 validator.check_conflicts(&rule2, &[rule1]).is_ok(),
273 "Rules with different scopes should not conflict"
274 );
275 }
276
277 #[test]
281 fn prop_no_conflict_different_pattern(
282 pattern1 in valid_pattern_strategy(),
283 pattern2 in valid_pattern_strategy(),
284 ) {
285 prop_assume!(pattern1 != pattern2);
286
287 let validator = RuleValidator::new();
288
289 let rule1 = Rule::new(
290 RuleScope::Global,
291 pattern1,
292 "action1".to_string(),
293 RuleSource::Learned,
294 );
295
296 let rule2 = Rule::new(
297 RuleScope::Global,
298 pattern2,
299 "action2".to_string(),
300 RuleSource::Learned,
301 );
302
303 assert!(
304 validator.check_conflicts(&rule2, &[rule1]).is_ok(),
305 "Rules with different patterns should not conflict"
306 );
307 }
308 }
309
310 #[test]
314 fn prop_validation_report_accuracy() {
315 let validator = RuleValidator::new();
316 let mut rule = Rule::new(
317 RuleScope::Global,
318 "pattern".to_string(),
319 "action".to_string(),
320 RuleSource::Learned,
321 );
322 rule.confidence = 1.5; let report = validator.validate_with_report(&rule);
325 assert!(
326 report.has_errors(),
327 "Validation report should contain errors for invalid rule"
328 );
329 }
330
331 proptest! {
332 #[test]
336 fn prop_valid_rules_no_errors(rule in valid_rule_strategy()) {
337 let validator = RuleValidator::new();
338 let report = validator.validate_with_report(&rule);
339
340 assert!(
341 !report.has_errors(),
342 "Valid rule should have no errors in report: {:?}",
343 rule
344 );
345 }
346 }
347
348 #[test]
352 fn prop_json_action_accepted() {
353 let validator = RuleValidator::new();
354 let rule = Rule::new(
355 RuleScope::Global,
356 "pattern".to_string(),
357 r#"{"key": "value", "nested": {"inner": 42}}"#.to_string(),
358 RuleSource::Learned,
359 );
360
361 assert!(
362 validator.validate(&rule).is_ok(),
363 "Valid JSON action should be accepted"
364 );
365 }
366
367 #[test]
371 fn prop_invalid_json_action_rejected() {
372 let validator = RuleValidator::new();
373 let rule = Rule::new(
374 RuleScope::Global,
375 "pattern".to_string(),
376 r#"{"key": invalid, "nested": {inner: 42}}"#.to_string(),
377 RuleSource::Learned,
378 );
379
380 assert!(
381 validator.validate(&rule).is_err(),
382 "Invalid JSON action should be rejected"
383 );
384 }
385
386 #[test]
390 fn prop_non_object_metadata_rejected() {
391 let validator = RuleValidator::new();
392 let mut rule = Rule::new(
393 RuleScope::Global,
394 "pattern".to_string(),
395 "action".to_string(),
396 RuleSource::Learned,
397 );
398 rule.metadata = serde_json::json!([1, 2, 3]); assert!(
401 validator.validate(&rule).is_err(),
402 "Non-object metadata should be rejected"
403 );
404 }
405
406 proptest! {
407 #[test]
411 fn prop_multiple_valid_rules_accepted(rules in prop::collection::vec(valid_rule_strategy(), 1..10)) {
412 let validator = RuleValidator::new();
413
414 for rule in rules {
415 assert!(
416 validator.validate(&rule).is_ok(),
417 "All valid rules should be accepted"
418 );
419 }
420 }
421
422 #[test]
426 fn prop_boundary_confidence_accepted(confidence in prop_oneof![Just(0.0f32), Just(1.0f32)]) {
427 let validator = RuleValidator::new();
428 let mut rule = Rule::new(
429 RuleScope::Global,
430 "pattern".to_string(),
431 "action".to_string(),
432 RuleSource::Learned,
433 );
434 rule.confidence = confidence;
435
436 assert!(
437 validator.validate(&rule).is_ok(),
438 "Boundary confidence values should be accepted"
439 );
440 }
441
442 #[test]
446 fn prop_boundary_success_rate_accepted(success_rate in prop_oneof![Just(0.0f32), Just(1.0f32)]) {
447 let validator = RuleValidator::new();
448 let mut rule = Rule::new(
449 RuleScope::Global,
450 "pattern".to_string(),
451 "action".to_string(),
452 RuleSource::Learned,
453 );
454 rule.success_rate = success_rate;
455
456 assert!(
457 validator.validate(&rule).is_ok(),
458 "Boundary success rate values should be accepted"
459 );
460 }
461 }
462}