pmat 3.16.0

PMAT - Zero-config AI context generation and code quality toolkit (CLI, MCP, HTTP)
#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod tests {
    use super::*;
    use crate::services::facades::satd_facade::{SatdSeverity as FacadeSeverity, SatdViolation};

    /// Strip ANSI escape codes from a string for assertion comparisons.
    fn strip_ansi(s: &str) -> String {
        let re = regex::Regex::new(r"\x1b\[[0-9;]*m").unwrap();
        re.replace_all(s, "").to_string()
    }

    #[test]
    fn test_format_summary() {
        let result = SatdAnalysisResult {
            total_files: 10,
            violations: vec![SatdViolation {
                file_path: "test.rs".to_string(),
                line_number: 42,
                violation_type: "TODO".to_string(),
                message: "Implement feature".to_string(),
                severity: FacadeSeverity::Medium,
            }],
            summary: "Test summary".to_string(),
        };

        let output = strip_ansi(&format_summary(&result));
        assert!(output.contains("Test summary"));
        assert!(output.contains("Total violations:"));
        assert!(output.contains("1"));
    }

    fn make_violation(severity: FacadeSeverity, vtype: &str) -> SatdViolation {
        SatdViolation {
            file_path: format!("src/{vtype}.rs"),
            line_number: 1,
            violation_type: vtype.to_string(),
            message: format!("{vtype} message"),
            severity,
        }
    }

    fn result_with_all_severities() -> SatdAnalysisResult {
        SatdAnalysisResult {
            total_files: 4,
            violations: vec![
                make_violation(FacadeSeverity::Critical, "FIXME"),
                make_violation(FacadeSeverity::High, "TODO"),
                make_violation(FacadeSeverity::Medium, "HACK"),
                make_violation(FacadeSeverity::Low, "XXX"),
            ],
            summary: "All severities".to_string(),
        }
    }

    fn empty_result() -> SatdAnalysisResult {
        SatdAnalysisResult {
            total_files: 0,
            violations: vec![],
            summary: "Empty".to_string(),
        }
    }

    // ── format_output dispatcher ──

    #[test]
    fn test_format_output_summary_arm() {
        let r = format_output(
            &result_with_all_severities(),
            SatdOutputFormat::Summary,
            false,
            0,
            false,
        );
        let s = strip_ansi(&r);
        assert!(s.contains("SATD Analysis Summary"));
    }

    #[test]
    fn test_format_output_json_arm() {
        let r = format_output(
            &result_with_all_severities(),
            SatdOutputFormat::Json,
            false,
            0,
            false,
        );
        assert!(r.contains("\"total_violations\": 4"));
    }

    #[test]
    fn test_format_output_sarif_arm() {
        let r = format_output(
            &result_with_all_severities(),
            SatdOutputFormat::Sarif,
            false,
            0,
            false,
        );
        assert!(r.contains("\"version\":\"2.1.0\""));
        assert!(r.contains("satd-violation"));
    }

    #[test]
    fn test_format_output_markdown_arm() {
        let r = format_output(
            &result_with_all_severities(),
            SatdOutputFormat::Markdown,
            false,
            0,
            false,
        );
        assert!(r.contains("# SATD Analysis Report"));
    }

    // ── format_summary: severity-loop and Top-Violations gating ──

    #[test]
    fn test_format_summary_with_all_severities_lists_each_count() {
        let r = strip_ansi(&format_summary(&result_with_all_severities()));
        assert!(r.contains("Critical:"));
        assert!(r.contains("High:"));
        assert!(r.contains("Medium:"));
        assert!(r.contains("Low:"));
        assert!(r.contains("Top Violations"));
    }

    #[test]
    fn test_format_summary_empty_skips_top_violations() {
        let r = strip_ansi(&format_summary(&empty_result()));
        assert!(!r.contains("Top Violations"));
    }

    #[test]
    fn test_format_summary_top_violations_caps_at_10() {
        let mut res = result_with_all_severities();
        // Inject 12 more → total 16; format cap is 10.
        for i in 0..12 {
            res.violations
                .push(make_violation(FacadeSeverity::Low, &format!("V{i}")));
        }
        let r = strip_ansi(&format_summary(&res));
        // V0..V5 (first 6 of the 12 extras) appear after the 4 baseline,
        // so V0..V5 in (4 + 0..6 = positions 5..10), V6+ should NOT appear.
        // Conservative check: V11 (last extra) must not appear.
        assert!(!r.contains("V11"));
    }

    // ── format_json: metrics + evolution flag toggles ──

    #[test]
    fn test_format_json_metrics_flag_adds_metrics_block() {
        let r = format_json(&result_with_all_severities(), true, false);
        assert!(r.contains("\"metrics\""));
        assert!(r.contains("\"critical_count\": 1"));
        assert!(r.contains("\"high_count\": 1"));
    }

    #[test]
    fn test_format_json_no_metrics_flag_omits_metrics() {
        let r = format_json(&result_with_all_severities(), false, false);
        assert!(!r.contains("\"metrics\""));
    }

    #[test]
    fn test_format_json_evolution_flag_adds_evolution_block() {
        let r = format_json(&result_with_all_severities(), false, true);
        assert!(r.contains("\"evolution\""));
        assert!(r.contains("Evolution tracking"));
    }

    // ── format_sarif: severity → level mapping ──

    #[test]
    fn test_format_sarif_maps_each_severity_to_correct_level() {
        let r = format_sarif(&result_with_all_severities());
        // Critical + High → "error", Medium → "warning", Low → "note".
        assert!(r.contains("\"error\""));
        assert!(r.contains("\"warning\""));
        assert!(r.contains("\"note\""));
    }

    #[test]
    fn test_format_sarif_empty_violations_emits_empty_results() {
        let r = format_sarif(&empty_result());
        assert!(r.contains("\"results\":[]"));
    }

    // ── format_markdown: evolution + violations table gating ──

    #[test]
    fn test_format_markdown_with_evolution_emits_section() {
        let r = format_markdown(&result_with_all_severities(), true, 30);
        assert!(r.contains("## Evolution (Last 30 Days)"));
    }

    #[test]
    fn test_format_markdown_without_evolution_skips_section() {
        let r = format_markdown(&result_with_all_severities(), false, 0);
        assert!(!r.contains("## Evolution"));
    }

    #[test]
    fn test_format_markdown_violations_table_emitted_only_when_non_empty() {
        let with = format_markdown(&result_with_all_severities(), false, 0);
        assert!(with.contains("## Violations"));
        let without = format_markdown(&empty_result(), false, 0);
        assert!(!without.contains("## Violations"));
    }

    #[test]
    fn test_format_markdown_metrics_table_includes_critical_high_counts() {
        let r = format_markdown(&result_with_all_severities(), false, 0);
        assert!(r.contains("Critical Violations | 1"));
        assert!(r.contains("High Violations | 1"));
    }

    // ── print_metrics smoke (writes to stderr; verify no panic) ──

    #[test]
    fn test_print_metrics_with_violations_no_panic() {
        print_metrics(&result_with_all_severities());
    }

    #[test]
    fn test_print_metrics_empty_no_panic() {
        print_metrics(&empty_result());
    }
}

#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod property_tests {
    use proptest::prelude::*;

    proptest! {
        #[test]
        fn basic_property_stability(_input in ".*") {
            // Basic property test for coverage
            prop_assert!(true);
        }

        #[test]
        fn module_consistency_check(_x in 0u32..1000) {
            // Module consistency verification
            prop_assert!(_x < 1001);
        }
    }
}