1use crate::core::FunctionMetrics;
52use crate::effects::validation::ValidationRuleSet;
53use stillwater::predicate::Predicate;
54
55#[derive(Debug, Clone, Copy)]
64pub struct HighCyclomatic {
65 threshold: u32,
66}
67
68impl HighCyclomatic {
69 pub fn new(threshold: u32) -> Self {
71 Self { threshold }
72 }
73
74 pub fn from_rules(rules: &ValidationRuleSet) -> Self {
76 Self::new(rules.complexity_warning)
77 }
78
79 pub fn threshold(&self) -> u32 {
81 self.threshold
82 }
83
84 pub fn description(&self) -> String {
86 format!("cyclomatic complexity > {}", self.threshold)
87 }
88}
89
90impl Predicate<FunctionMetrics> for HighCyclomatic {
91 fn check(&self, metrics: &FunctionMetrics) -> bool {
92 metrics.cyclomatic > self.threshold
93 }
94}
95
96impl Predicate<u32> for HighCyclomatic {
97 fn check(&self, value: &u32) -> bool {
98 *value > self.threshold
99 }
100}
101
102#[derive(Debug, Clone, Copy)]
107pub struct HighCognitive {
108 threshold: u32,
109}
110
111impl HighCognitive {
112 pub fn new(threshold: u32) -> Self {
114 Self { threshold }
115 }
116
117 pub fn from_rules(rules: &ValidationRuleSet) -> Self {
119 Self::new(rules.complexity_warning)
120 }
121
122 pub fn threshold(&self) -> u32 {
124 self.threshold
125 }
126
127 pub fn description(&self) -> String {
129 format!("cognitive complexity > {}", self.threshold)
130 }
131}
132
133impl Predicate<FunctionMetrics> for HighCognitive {
134 fn check(&self, metrics: &FunctionMetrics) -> bool {
135 metrics.cognitive > self.threshold
136 }
137}
138
139impl Predicate<u32> for HighCognitive {
140 fn check(&self, value: &u32) -> bool {
141 *value > self.threshold
142 }
143}
144
145#[derive(Debug, Clone, Copy)]
150pub struct CriticalComplexity {
151 cyclomatic_threshold: u32,
152 cognitive_threshold: u32,
153}
154
155impl CriticalComplexity {
156 pub fn new(cyclomatic_threshold: u32, cognitive_threshold: u32) -> Self {
158 Self {
159 cyclomatic_threshold,
160 cognitive_threshold,
161 }
162 }
163
164 pub fn from_rules(rules: &ValidationRuleSet) -> Self {
166 Self::new(rules.complexity_critical, rules.complexity_critical)
167 }
168
169 pub fn description(&self) -> String {
171 format!(
172 "cyclomatic > {} AND cognitive > {}",
173 self.cyclomatic_threshold, self.cognitive_threshold
174 )
175 }
176}
177
178impl Predicate<FunctionMetrics> for CriticalComplexity {
179 fn check(&self, metrics: &FunctionMetrics) -> bool {
180 metrics.cyclomatic > self.cyclomatic_threshold
181 && metrics.cognitive > self.cognitive_threshold
182 }
183}
184
185#[derive(Debug, Clone, Copy)]
194pub struct DeepNesting {
195 threshold: u32,
196}
197
198impl DeepNesting {
199 pub fn new(threshold: u32) -> Self {
201 Self { threshold }
202 }
203
204 pub fn from_rules(rules: &ValidationRuleSet) -> Self {
206 Self::new(rules.max_nesting_depth)
207 }
208
209 pub fn threshold(&self) -> u32 {
211 self.threshold
212 }
213
214 pub fn description(&self) -> String {
216 format!("nesting depth > {}", self.threshold)
217 }
218}
219
220impl Predicate<FunctionMetrics> for DeepNesting {
221 fn check(&self, metrics: &FunctionMetrics) -> bool {
222 metrics.nesting > self.threshold
223 }
224}
225
226impl Predicate<u32> for DeepNesting {
227 fn check(&self, value: &u32) -> bool {
228 *value > self.threshold
229 }
230}
231
232#[derive(Debug, Clone, Copy)]
237pub struct LongMethod {
238 threshold: usize,
239}
240
241impl LongMethod {
242 pub fn new(threshold: usize) -> Self {
244 Self { threshold }
245 }
246
247 pub fn from_rules(rules: &ValidationRuleSet) -> Self {
249 Self::new(rules.max_function_length)
250 }
251
252 pub fn threshold(&self) -> usize {
254 self.threshold
255 }
256
257 pub fn description(&self) -> String {
259 format!("line count > {}", self.threshold)
260 }
261}
262
263impl Predicate<FunctionMetrics> for LongMethod {
264 fn check(&self, metrics: &FunctionMetrics) -> bool {
265 metrics.length > self.threshold
266 }
267}
268
269impl Predicate<usize> for LongMethod {
270 fn check(&self, value: &usize) -> bool {
271 *value > self.threshold
272 }
273}
274
275#[derive(Debug, Clone, Copy)]
280pub struct TooManyParameters {
281 threshold: usize,
282}
283
284impl TooManyParameters {
285 pub fn new(threshold: usize) -> Self {
287 Self { threshold }
288 }
289
290 pub fn threshold(&self) -> usize {
292 self.threshold
293 }
294
295 pub fn description(&self) -> String {
297 format!("parameter count > {}", self.threshold)
298 }
299}
300
301impl Predicate<usize> for TooManyParameters {
302 fn check(&self, value: &usize) -> bool {
303 *value > self.threshold
304 }
305}
306
307#[derive(Debug, Clone, Copy)]
315pub struct LowCoverage {
316 threshold: f64,
318}
319
320impl LowCoverage {
321 pub fn new(threshold: f64) -> Self {
323 Self {
324 threshold: threshold.clamp(0.0, 1.0),
325 }
326 }
327
328 pub fn from_percentage(percentage: u32) -> Self {
330 Self::new(f64::from(percentage) / 100.0)
331 }
332
333 pub fn threshold(&self) -> f64 {
335 self.threshold
336 }
337
338 pub fn description(&self) -> String {
340 format!("coverage < {}%", (self.threshold * 100.0) as u32)
341 }
342}
343
344impl Predicate<f64> for LowCoverage {
345 fn check(&self, coverage: &f64) -> bool {
346 *coverage < self.threshold
347 }
348}
349
350impl Predicate<Option<f64>> for LowCoverage {
351 fn check(&self, coverage: &Option<f64>) -> bool {
352 coverage.is_none_or(|c| c < self.threshold)
353 }
354}
355
356#[derive(Debug, Clone, Copy, Default)]
360pub struct NoCoverage;
361
362impl NoCoverage {
363 pub fn new() -> Self {
365 Self
366 }
367
368 pub fn description(&self) -> String {
370 "coverage = 0% or absent".to_string()
371 }
372}
373
374impl Predicate<f64> for NoCoverage {
375 fn check(&self, coverage: &f64) -> bool {
376 *coverage == 0.0
377 }
378}
379
380impl Predicate<Option<f64>> for NoCoverage {
381 fn check(&self, coverage: &Option<f64>) -> bool {
382 coverage.is_none_or(|c| c == 0.0)
383 }
384}
385
386#[derive(Debug, Clone, Copy)]
390pub struct PartialCoverage {
391 threshold: f64,
392}
393
394impl PartialCoverage {
395 pub fn new(threshold: f64) -> Self {
397 Self {
398 threshold: threshold.clamp(0.0, 1.0),
399 }
400 }
401
402 pub fn threshold(&self) -> f64 {
404 self.threshold
405 }
406
407 pub fn description(&self) -> String {
409 format!("0% < coverage < {}%", (self.threshold * 100.0) as u32)
410 }
411}
412
413impl Predicate<f64> for PartialCoverage {
414 fn check(&self, coverage: &f64) -> bool {
415 *coverage > 0.0 && *coverage < self.threshold
416 }
417}
418
419impl Predicate<Option<f64>> for PartialCoverage {
420 fn check(&self, coverage: &Option<f64>) -> bool {
421 coverage.is_some_and(|c| c > 0.0 && c < self.threshold)
422 }
423}
424
425#[derive(Debug, Clone)]
434pub struct HighRisk {
435 high_complexity: HighCyclomatic,
436 low_coverage: LowCoverage,
437}
438
439impl HighRisk {
440 pub fn new(complexity_threshold: u32, coverage_threshold: f64) -> Self {
442 Self {
443 high_complexity: HighCyclomatic::new(complexity_threshold),
444 low_coverage: LowCoverage::new(coverage_threshold),
445 }
446 }
447
448 pub fn from_rules(rules: &ValidationRuleSet) -> Self {
450 Self::new(rules.complexity_warning, 0.5) }
452
453 pub fn description(&self) -> String {
455 format!(
456 "{} AND {}",
457 self.high_complexity.description(),
458 self.low_coverage.description()
459 )
460 }
461}
462
463pub struct FunctionWithCoverage<'a> {
465 pub metrics: &'a FunctionMetrics,
466 pub coverage: Option<f64>,
467}
468
469impl Predicate<FunctionWithCoverage<'_>> for HighRisk {
470 fn check(&self, value: &FunctionWithCoverage<'_>) -> bool {
471 self.high_complexity.check(value.metrics) && self.low_coverage.check(&value.coverage)
472 }
473}
474
475#[derive(Debug, Clone)]
479pub struct CriticalRisk {
480 critical_complexity: CriticalComplexity,
481 no_coverage: NoCoverage,
482}
483
484impl CriticalRisk {
485 pub fn new(cyclomatic_threshold: u32, cognitive_threshold: u32) -> Self {
487 Self {
488 critical_complexity: CriticalComplexity::new(cyclomatic_threshold, cognitive_threshold),
489 no_coverage: NoCoverage::new(),
490 }
491 }
492
493 pub fn from_rules(rules: &ValidationRuleSet) -> Self {
495 Self::new(rules.complexity_critical, rules.complexity_critical)
496 }
497
498 pub fn description(&self) -> String {
500 format!(
501 "{} AND {}",
502 self.critical_complexity.description(),
503 self.no_coverage.description()
504 )
505 }
506}
507
508impl Predicate<FunctionWithCoverage<'_>> for CriticalRisk {
509 fn check(&self, value: &FunctionWithCoverage<'_>) -> bool {
510 self.critical_complexity.check(value.metrics) && self.no_coverage.check(&value.coverage)
511 }
512}
513
514#[derive(Debug, Clone)]
518pub struct ModerateRisk {
519 high_complexity: HighCyclomatic,
520 low_coverage: LowCoverage,
521}
522
523impl ModerateRisk {
524 pub fn new(complexity_threshold: u32, coverage_threshold: f64) -> Self {
526 Self {
527 high_complexity: HighCyclomatic::new(complexity_threshold),
528 low_coverage: LowCoverage::new(coverage_threshold),
529 }
530 }
531
532 pub fn from_rules(rules: &ValidationRuleSet) -> Self {
534 Self::new(rules.complexity_warning, 0.5)
535 }
536
537 pub fn description(&self) -> String {
539 format!(
540 "{} OR {}",
541 self.high_complexity.description(),
542 self.low_coverage.description()
543 )
544 }
545}
546
547impl Predicate<FunctionWithCoverage<'_>> for ModerateRisk {
548 fn check(&self, value: &FunctionWithCoverage<'_>) -> bool {
549 self.high_complexity.check(value.metrics) || self.low_coverage.check(&value.coverage)
550 }
551}
552
553#[derive(Debug, Clone)]
562pub struct DebtPredicates {
563 pub high_cyclomatic: HighCyclomatic,
565 pub high_cognitive: HighCognitive,
567 pub critical_complexity: CriticalComplexity,
569 pub deep_nesting: DeepNesting,
571 pub long_method: LongMethod,
573 pub low_coverage: LowCoverage,
575 pub no_coverage: NoCoverage,
577}
578
579impl DebtPredicates {
580 pub fn from_rules(rules: &ValidationRuleSet) -> Self {
582 Self {
583 high_cyclomatic: HighCyclomatic::from_rules(rules),
584 high_cognitive: HighCognitive::from_rules(rules),
585 critical_complexity: CriticalComplexity::from_rules(rules),
586 deep_nesting: DeepNesting::from_rules(rules),
587 long_method: LongMethod::from_rules(rules),
588 low_coverage: LowCoverage::new(0.5), no_coverage: NoCoverage::new(),
590 }
591 }
592
593 pub fn with_coverage_threshold(mut self, threshold: f64) -> Self {
595 self.low_coverage = LowCoverage::new(threshold);
596 self
597 }
598
599 pub fn high_risk(&self) -> HighRisk {
601 HighRisk {
602 high_complexity: self.high_cyclomatic,
603 low_coverage: self.low_coverage,
604 }
605 }
606
607 pub fn critical_risk(&self) -> CriticalRisk {
609 CriticalRisk {
610 critical_complexity: self.critical_complexity,
611 no_coverage: self.no_coverage,
612 }
613 }
614
615 pub fn moderate_risk(&self) -> ModerateRisk {
617 ModerateRisk {
618 high_complexity: self.high_cyclomatic,
619 low_coverage: self.low_coverage,
620 }
621 }
622
623 pub fn has_complexity_issues(&self, metrics: &FunctionMetrics) -> bool {
625 self.high_cyclomatic.check(metrics)
626 || self.high_cognitive.check(metrics)
627 || self.critical_complexity.check(metrics)
628 }
629
630 pub fn has_structural_issues(&self, metrics: &FunctionMetrics) -> bool {
632 self.deep_nesting.check(metrics) || self.long_method.check(metrics)
633 }
634
635 pub fn has_any_issues(&self, metrics: &FunctionMetrics) -> bool {
637 self.has_complexity_issues(metrics) || self.has_structural_issues(metrics)
638 }
639}
640
641impl Default for DebtPredicates {
642 fn default() -> Self {
643 Self::from_rules(&ValidationRuleSet::default())
644 }
645}
646
647#[derive(Debug, Clone)]
653pub struct PredicateResult {
654 pub matched: bool,
656 pub predicate_name: String,
658 pub description: String,
660 pub details: Option<String>,
662}
663
664impl PredicateResult {
665 pub fn matched(name: impl Into<String>, description: impl Into<String>) -> Self {
667 Self {
668 matched: true,
669 predicate_name: name.into(),
670 description: description.into(),
671 details: None,
672 }
673 }
674
675 pub fn not_matched(name: impl Into<String>, description: impl Into<String>) -> Self {
677 Self {
678 matched: false,
679 predicate_name: name.into(),
680 description: description.into(),
681 details: None,
682 }
683 }
684
685 pub fn with_details(mut self, details: impl Into<String>) -> Self {
687 self.details = Some(details.into());
688 self
689 }
690}
691
692#[derive(Debug, Clone)]
694pub struct DebtFindings {
695 pub function_name: String,
697 pub results: Vec<PredicateResult>,
699}
700
701impl DebtFindings {
702 pub fn new(function_name: impl Into<String>) -> Self {
704 Self {
705 function_name: function_name.into(),
706 results: Vec::new(),
707 }
708 }
709
710 pub fn add_result(&mut self, result: PredicateResult) {
712 self.results.push(result);
713 }
714
715 pub fn matched_predicates(&self) -> impl Iterator<Item = &PredicateResult> {
717 self.results.iter().filter(|r| r.matched)
718 }
719
720 pub fn has_issues(&self) -> bool {
722 self.results.iter().any(|r| r.matched)
723 }
724
725 pub fn issue_count(&self) -> usize {
727 self.results.iter().filter(|r| r.matched).count()
728 }
729}
730
731#[cfg(test)]
736mod tests {
737 use super::*;
738 use std::path::PathBuf;
739
740 fn create_test_metrics(
741 name: &str,
742 cyclomatic: u32,
743 cognitive: u32,
744 nesting: u32,
745 length: usize,
746 ) -> FunctionMetrics {
747 FunctionMetrics {
748 name: name.to_string(),
749 file: PathBuf::from("test.rs"),
750 line: 1,
751 cyclomatic,
752 cognitive,
753 nesting,
754 length,
755 is_test: false,
756 visibility: None,
757 is_trait_method: false,
758 in_test_module: false,
759 entropy_score: None,
760 is_pure: None,
761 purity_confidence: None,
762 purity_reason: None,
763 call_dependencies: None,
764 detected_patterns: None,
765 upstream_callers: None,
766 downstream_callees: None,
767 mapping_pattern_result: None,
768 adjusted_complexity: None,
769 composition_metrics: None,
770 language_specific: None,
771 purity_level: None,
772 error_swallowing_count: None,
773 error_swallowing_patterns: None,
774 entropy_analysis: None,
775 }
776 }
777
778 mod complexity_predicates {
783 use super::*;
784
785 #[test]
786 fn high_cyclomatic_above_threshold() {
787 let pred = HighCyclomatic::new(20);
788 let metrics = create_test_metrics("complex_fn", 25, 10, 2, 30);
789 assert!(pred.check(&metrics));
790 }
791
792 #[test]
793 fn high_cyclomatic_at_threshold() {
794 let pred = HighCyclomatic::new(20);
795 let metrics = create_test_metrics("boundary_fn", 20, 10, 2, 30);
796 assert!(!pred.check(&metrics)); }
798
799 #[test]
800 fn high_cyclomatic_below_threshold() {
801 let pred = HighCyclomatic::new(20);
802 let metrics = create_test_metrics("simple_fn", 10, 5, 1, 15);
803 assert!(!pred.check(&metrics));
804 }
805
806 #[test]
807 fn high_cyclomatic_direct_value() {
808 let pred = HighCyclomatic::new(20);
809 assert!(pred.check(&25_u32));
810 assert!(!pred.check(&20_u32));
811 assert!(!pred.check(&15_u32));
812 }
813
814 #[test]
815 fn high_cognitive_above_threshold() {
816 let pred = HighCognitive::new(15);
817 let metrics = create_test_metrics("confusing_fn", 10, 20, 2, 30);
818 assert!(pred.check(&metrics));
819 }
820
821 #[test]
822 fn high_cognitive_below_threshold() {
823 let pred = HighCognitive::new(15);
824 let metrics = create_test_metrics("clear_fn", 10, 10, 2, 30);
825 assert!(!pred.check(&metrics));
826 }
827
828 #[test]
829 fn critical_complexity_both_exceeded() {
830 let pred = CriticalComplexity::new(50, 40);
831 let metrics = create_test_metrics("nightmare_fn", 60, 50, 5, 100);
832 assert!(pred.check(&metrics));
833 }
834
835 #[test]
836 fn critical_complexity_only_one_exceeded() {
837 let pred = CriticalComplexity::new(50, 40);
838
839 let metrics1 = create_test_metrics("high_cyclomatic", 60, 30, 2, 50);
841 assert!(!pred.check(&metrics1));
842
843 let metrics2 = create_test_metrics("high_cognitive", 30, 50, 2, 50);
845 assert!(!pred.check(&metrics2));
846 }
847
848 #[test]
849 fn critical_complexity_neither_exceeded() {
850 let pred = CriticalComplexity::new(50, 40);
851 let metrics = create_test_metrics("good_fn", 30, 25, 2, 30);
852 assert!(!pred.check(&metrics));
853 }
854 }
855
856 mod structure_predicates {
861 use super::*;
862
863 #[test]
864 fn deep_nesting_above_threshold() {
865 let pred = DeepNesting::new(4);
866 let metrics = create_test_metrics("nested_fn", 10, 8, 6, 30);
867 assert!(pred.check(&metrics));
868 }
869
870 #[test]
871 fn deep_nesting_at_threshold() {
872 let pred = DeepNesting::new(4);
873 let metrics = create_test_metrics("boundary_fn", 10, 8, 4, 30);
874 assert!(!pred.check(&metrics));
875 }
876
877 #[test]
878 fn deep_nesting_below_threshold() {
879 let pred = DeepNesting::new(4);
880 let metrics = create_test_metrics("flat_fn", 10, 8, 2, 30);
881 assert!(!pred.check(&metrics));
882 }
883
884 #[test]
885 fn long_method_above_threshold() {
886 let pred = LongMethod::new(50);
887 let metrics = create_test_metrics("huge_fn", 10, 8, 2, 100);
888 assert!(pred.check(&metrics));
889 }
890
891 #[test]
892 fn long_method_at_threshold() {
893 let pred = LongMethod::new(50);
894 let metrics = create_test_metrics("boundary_fn", 10, 8, 2, 50);
895 assert!(!pred.check(&metrics));
896 }
897
898 #[test]
899 fn long_method_below_threshold() {
900 let pred = LongMethod::new(50);
901 let metrics = create_test_metrics("short_fn", 10, 8, 2, 25);
902 assert!(!pred.check(&metrics));
903 }
904
905 #[test]
906 fn too_many_parameters_direct_value() {
907 let pred = TooManyParameters::new(4);
908 assert!(pred.check(&6_usize));
909 assert!(!pred.check(&4_usize));
910 assert!(!pred.check(&2_usize));
911 }
912 }
913
914 mod coverage_predicates {
919 use super::*;
920
921 #[test]
922 fn low_coverage_below_threshold() {
923 let pred = LowCoverage::new(0.5);
924 assert!(pred.check(&0.3_f64));
925 assert!(pred.check(&Some(0.3_f64)));
926 }
927
928 #[test]
929 fn low_coverage_at_threshold() {
930 let pred = LowCoverage::new(0.5);
931 assert!(!pred.check(&0.5_f64));
932 assert!(!pred.check(&Some(0.5_f64)));
933 }
934
935 #[test]
936 fn low_coverage_above_threshold() {
937 let pred = LowCoverage::new(0.5);
938 assert!(!pred.check(&0.7_f64));
939 assert!(!pred.check(&Some(0.7_f64)));
940 }
941
942 #[test]
943 fn low_coverage_missing() {
944 let pred = LowCoverage::new(0.5);
945 assert!(pred.check(&None::<f64>)); }
947
948 #[test]
949 fn low_coverage_from_percentage() {
950 let pred = LowCoverage::from_percentage(50);
951 assert!(pred.check(&0.3_f64));
952 assert!(!pred.check(&0.5_f64));
953 assert!(!pred.check(&0.7_f64));
954 }
955
956 #[test]
957 fn no_coverage_zero() {
958 let pred = NoCoverage::new();
959 assert!(pred.check(&0.0_f64));
960 assert!(pred.check(&Some(0.0_f64)));
961 }
962
963 #[test]
964 fn no_coverage_missing() {
965 let pred = NoCoverage::new();
966 assert!(pred.check(&None::<f64>));
967 }
968
969 #[test]
970 fn no_coverage_present() {
971 let pred = NoCoverage::new();
972 assert!(!pred.check(&0.5_f64));
973 assert!(!pred.check(&Some(0.5_f64)));
974 }
975
976 #[test]
977 fn partial_coverage_in_range() {
978 let pred = PartialCoverage::new(0.5);
979 assert!(pred.check(&0.25_f64));
980 assert!(pred.check(&Some(0.25_f64)));
981 }
982
983 #[test]
984 fn partial_coverage_at_zero() {
985 let pred = PartialCoverage::new(0.5);
986 assert!(!pred.check(&0.0_f64)); assert!(!pred.check(&Some(0.0_f64)));
988 }
989
990 #[test]
991 fn partial_coverage_at_threshold() {
992 let pred = PartialCoverage::new(0.5);
993 assert!(!pred.check(&0.5_f64)); assert!(!pred.check(&Some(0.5_f64)));
995 }
996
997 #[test]
998 fn partial_coverage_missing() {
999 let pred = PartialCoverage::new(0.5);
1000 assert!(!pred.check(&None::<f64>)); }
1002 }
1003
1004 mod composed_predicates {
1009 use super::*;
1010
1011 #[test]
1012 fn high_risk_both_conditions() {
1013 let pred = HighRisk::new(20, 0.5);
1014 let metrics = create_test_metrics("risky_fn", 30, 10, 2, 30);
1015 let with_coverage = FunctionWithCoverage {
1016 metrics: &metrics,
1017 coverage: Some(0.3),
1018 };
1019 assert!(pred.check(&with_coverage));
1020 }
1021
1022 #[test]
1023 fn high_risk_only_complexity() {
1024 let pred = HighRisk::new(20, 0.5);
1025 let metrics = create_test_metrics("complex_but_tested", 30, 10, 2, 30);
1026 let with_coverage = FunctionWithCoverage {
1027 metrics: &metrics,
1028 coverage: Some(0.8),
1029 };
1030 assert!(!pred.check(&with_coverage));
1031 }
1032
1033 #[test]
1034 fn high_risk_only_low_coverage() {
1035 let pred = HighRisk::new(20, 0.5);
1036 let metrics = create_test_metrics("simple_untested", 10, 5, 1, 15);
1037 let with_coverage = FunctionWithCoverage {
1038 metrics: &metrics,
1039 coverage: Some(0.2),
1040 };
1041 assert!(!pred.check(&with_coverage));
1042 }
1043
1044 #[test]
1045 fn critical_risk_critical_and_zero() {
1046 let pred = CriticalRisk::new(50, 50);
1047 let metrics = create_test_metrics("disaster_fn", 100, 80, 8, 200);
1048 let with_coverage = FunctionWithCoverage {
1049 metrics: &metrics,
1050 coverage: Some(0.0),
1051 };
1052 assert!(pred.check(&with_coverage));
1053 }
1054
1055 #[test]
1056 fn critical_risk_critical_but_some_coverage() {
1057 let pred = CriticalRisk::new(50, 50);
1058 let metrics = create_test_metrics("complex_tested", 100, 80, 8, 200);
1059 let with_coverage = FunctionWithCoverage {
1060 metrics: &metrics,
1061 coverage: Some(0.5),
1062 };
1063 assert!(!pred.check(&with_coverage));
1064 }
1065
1066 #[test]
1067 fn moderate_risk_either_condition() {
1068 let pred = ModerateRisk::new(20, 0.5);
1069 let metrics = create_test_metrics("complex_fn", 30, 10, 2, 30);
1070
1071 let with_good_coverage = FunctionWithCoverage {
1073 metrics: &metrics,
1074 coverage: Some(0.8),
1075 };
1076 assert!(pred.check(&with_good_coverage));
1077
1078 let simple_metrics = create_test_metrics("simple_fn", 10, 5, 1, 15);
1080 let with_low_coverage = FunctionWithCoverage {
1081 metrics: &simple_metrics,
1082 coverage: Some(0.2),
1083 };
1084 assert!(pred.check(&with_low_coverage));
1085 }
1086
1087 #[test]
1088 fn moderate_risk_neither_condition() {
1089 let pred = ModerateRisk::new(20, 0.5);
1090 let metrics = create_test_metrics("good_fn", 10, 5, 1, 15);
1091 let with_coverage = FunctionWithCoverage {
1092 metrics: &metrics,
1093 coverage: Some(0.8),
1094 };
1095 assert!(!pred.check(&with_coverage));
1096 }
1097 }
1098
1099 mod factory_tests {
1104 use super::*;
1105
1106 #[test]
1107 fn debt_predicates_default() {
1108 let predicates = DebtPredicates::default();
1109
1110 assert_eq!(predicates.high_cyclomatic.threshold(), 21);
1112 assert_eq!(predicates.deep_nesting.threshold(), 4);
1113 assert_eq!(predicates.long_method.threshold(), 50);
1114 }
1115
1116 #[test]
1117 fn debt_predicates_from_strict_rules() {
1118 let rules = ValidationRuleSet::strict();
1119 let predicates = DebtPredicates::from_rules(&rules);
1120
1121 assert_eq!(predicates.high_cyclomatic.threshold(), 10);
1122 assert_eq!(predicates.deep_nesting.threshold(), 2);
1123 assert_eq!(predicates.long_method.threshold(), 20);
1124 }
1125
1126 #[test]
1127 fn debt_predicates_from_lenient_rules() {
1128 let rules = ValidationRuleSet::lenient();
1129 let predicates = DebtPredicates::from_rules(&rules);
1130
1131 assert_eq!(predicates.high_cyclomatic.threshold(), 30);
1132 assert_eq!(predicates.deep_nesting.threshold(), 6);
1133 assert_eq!(predicates.long_method.threshold(), 100);
1134 }
1135
1136 #[test]
1137 fn debt_predicates_with_custom_coverage() {
1138 let predicates = DebtPredicates::default().with_coverage_threshold(0.8);
1139 assert!((predicates.low_coverage.threshold() - 0.8).abs() < 0.001);
1140 }
1141
1142 #[test]
1143 fn debt_predicates_has_complexity_issues() {
1144 let predicates = DebtPredicates::default();
1145
1146 let complex = create_test_metrics("complex_fn", 50, 10, 2, 30);
1147 assert!(predicates.has_complexity_issues(&complex));
1148
1149 let simple = create_test_metrics("simple_fn", 5, 3, 1, 10);
1150 assert!(!predicates.has_complexity_issues(&simple));
1151 }
1152
1153 #[test]
1154 fn debt_predicates_has_structural_issues() {
1155 let predicates = DebtPredicates::default();
1156
1157 let deeply_nested = create_test_metrics("nested_fn", 10, 8, 10, 30);
1158 assert!(predicates.has_structural_issues(&deeply_nested));
1159
1160 let long_fn = create_test_metrics("long_fn", 10, 8, 2, 100);
1161 assert!(predicates.has_structural_issues(&long_fn));
1162
1163 let good_fn = create_test_metrics("good_fn", 10, 8, 2, 30);
1164 assert!(!predicates.has_structural_issues(&good_fn));
1165 }
1166
1167 #[test]
1168 fn debt_predicates_has_any_issues() {
1169 let predicates = DebtPredicates::default();
1170
1171 let problematic = create_test_metrics("bad_fn", 50, 40, 8, 100);
1172 assert!(predicates.has_any_issues(&problematic));
1173
1174 let good = create_test_metrics("good_fn", 5, 3, 1, 10);
1175 assert!(!predicates.has_any_issues(&good));
1176 }
1177 }
1178
1179 mod description_tests {
1184 use super::*;
1185
1186 #[test]
1187 fn high_cyclomatic_description() {
1188 let pred = HighCyclomatic::new(25);
1189 assert_eq!(pred.description(), "cyclomatic complexity > 25");
1190 }
1191
1192 #[test]
1193 fn high_cognitive_description() {
1194 let pred = HighCognitive::new(15);
1195 assert_eq!(pred.description(), "cognitive complexity > 15");
1196 }
1197
1198 #[test]
1199 fn critical_complexity_description() {
1200 let pred = CriticalComplexity::new(50, 40);
1201 assert_eq!(pred.description(), "cyclomatic > 50 AND cognitive > 40");
1202 }
1203
1204 #[test]
1205 fn deep_nesting_description() {
1206 let pred = DeepNesting::new(4);
1207 assert_eq!(pred.description(), "nesting depth > 4");
1208 }
1209
1210 #[test]
1211 fn long_method_description() {
1212 let pred = LongMethod::new(50);
1213 assert_eq!(pred.description(), "line count > 50");
1214 }
1215
1216 #[test]
1217 fn low_coverage_description() {
1218 let pred = LowCoverage::new(0.5);
1219 assert_eq!(pred.description(), "coverage < 50%");
1220 }
1221
1222 #[test]
1223 fn no_coverage_description() {
1224 let pred = NoCoverage::new();
1225 assert_eq!(pred.description(), "coverage = 0% or absent");
1226 }
1227
1228 #[test]
1229 fn high_risk_description() {
1230 let pred = HighRisk::new(20, 0.5);
1231 assert_eq!(
1232 pred.description(),
1233 "cyclomatic complexity > 20 AND coverage < 50%"
1234 );
1235 }
1236 }
1237
1238 mod findings_tests {
1243 use super::*;
1244
1245 #[test]
1246 fn debt_findings_creation() {
1247 let findings = DebtFindings::new("test_function");
1248 assert_eq!(findings.function_name, "test_function");
1249 assert!(findings.results.is_empty());
1250 assert!(!findings.has_issues());
1251 assert_eq!(findings.issue_count(), 0);
1252 }
1253
1254 #[test]
1255 fn debt_findings_with_results() {
1256 let mut findings = DebtFindings::new("problematic_fn");
1257 findings.add_result(PredicateResult::matched(
1258 "high_complexity",
1259 "cyclomatic > 20",
1260 ));
1261 findings.add_result(PredicateResult::not_matched("deep_nesting", "nesting > 4"));
1262 findings.add_result(PredicateResult::matched("long_method", "lines > 50"));
1263
1264 assert!(findings.has_issues());
1265 assert_eq!(findings.issue_count(), 2);
1266 assert_eq!(findings.matched_predicates().count(), 2);
1267 }
1268
1269 #[test]
1270 fn predicate_result_with_details() {
1271 let result = PredicateResult::matched("high_complexity", "cyclomatic > 20")
1272 .with_details("Actual value: 45");
1273
1274 assert!(result.matched);
1275 assert_eq!(result.details, Some("Actual value: 45".to_string()));
1276 }
1277 }
1278}