pmat 3.11.0

PMAT - Zero-config AI context generation and code quality toolkit (CLI, MCP, HTTP)
// Unit tests for auto_clippy_fix: property-based tests, parsing, confidence
// level matching, and diagnostic filtering.

#[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);
        }
    }
}

#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod coverage_tests {
    use super::*;
    use crate::services::clippy_fix::{ClippyDiagnostic, DiagnosticLevel};
    use std::path::PathBuf;

    // ========================================================================
    // Tests for parse_confidence_level
    // ========================================================================

    #[test]
    fn test_parse_confidence_level_high() {
        let result = parse_confidence_level(&Some("high".to_string()));
        assert!(result.is_ok());
        assert_eq!(result.unwrap(), ConfidenceLevel::High);
    }

    #[test]
    fn test_parse_confidence_level_medium() {
        let result = parse_confidence_level(&Some("medium".to_string()));
        assert!(result.is_ok());
        assert_eq!(result.unwrap(), ConfidenceLevel::Medium);
    }

    #[test]
    fn test_parse_confidence_level_low() {
        let result = parse_confidence_level(&Some("low".to_string()));
        assert!(result.is_ok());
        assert_eq!(result.unwrap(), ConfidenceLevel::Low);
    }

    #[test]
    fn test_parse_confidence_level_none_defaults_to_high() {
        let result = parse_confidence_level(&None);
        assert!(result.is_ok());
        assert_eq!(result.unwrap(), ConfidenceLevel::High);
    }

    #[test]
    fn test_parse_confidence_level_invalid() {
        let result = parse_confidence_level(&Some("invalid".to_string()));
        assert!(result.is_err());
        let err = result.unwrap_err();
        assert!(err.to_string().contains("Invalid confidence level"));
        assert!(err.to_string().contains("invalid"));
    }

    #[test]
    fn test_parse_confidence_level_empty_string() {
        let result = parse_confidence_level(&Some("".to_string()));
        assert!(result.is_err());
    }

    // ========================================================================
    // Tests for parse_clippy_output
    // ========================================================================

    #[test]
    fn test_parse_clippy_output_empty() {
        let result = parse_clippy_output("");
        assert!(result.is_ok());
        assert!(result.unwrap().is_empty());
    }

    #[test]
    fn test_parse_clippy_output_empty_lines() {
        let result = parse_clippy_output("\n\n   \n\n");
        assert!(result.is_ok());
        assert!(result.unwrap().is_empty());
    }

    #[test]
    fn test_parse_clippy_output_valid_json() {
        let json = r#"{"message":{"code":{"code":"clippy::test"},"level":"warning","message":"test","spans":[{"file_name":"test.rs","line_start":1,"line_end":1,"column_start":1,"column_end":10}]}}"#;
        let result = parse_clippy_output(json);
        assert!(result.is_ok());
        let diagnostics = result.unwrap();
        assert_eq!(diagnostics.len(), 1);
        assert_eq!(diagnostics[0].code, "clippy::test");
    }

    #[test]
    fn test_parse_clippy_output_multiple_lines_mixed() {
        // Mix of valid JSON and invalid lines
        let output = r#"
{"message":{"code":{"code":"lint1"},"level":"warning","message":"msg1","spans":[{"file_name":"a.rs","line_start":1,"line_end":1,"column_start":1,"column_end":5}]}}
Not valid JSON line
{"message":{"code":{"code":"lint2"},"level":"error","message":"msg2","spans":[{"file_name":"b.rs","line_start":10,"line_end":10,"column_start":1,"column_end":5}]}}
"#;
        let result = parse_clippy_output(output);
        assert!(result.is_ok());
        let diagnostics = result.unwrap();
        // Should parse 2 valid diagnostics, skipping invalid line
        assert_eq!(diagnostics.len(), 2);
        assert_eq!(diagnostics[0].code, "lint1");
        assert_eq!(diagnostics[1].code, "lint2");
    }

    #[test]
    fn test_parse_clippy_output_invalid_json_lines() {
        let output = "not json\nalso not json\n{malformed";
        let result = parse_clippy_output(output);
        assert!(result.is_ok());
        assert!(result.unwrap().is_empty());
    }

    // ========================================================================
    // Tests for confidence_meets_minimum
    // ========================================================================

    #[test]
    fn test_confidence_meets_minimum_high_always_passes() {
        assert!(confidence_meets_minimum(
            ConfidenceLevel::High,
            ConfidenceLevel::High
        ));
        assert!(confidence_meets_minimum(
            ConfidenceLevel::High,
            ConfidenceLevel::Medium
        ));
        assert!(confidence_meets_minimum(
            ConfidenceLevel::High,
            ConfidenceLevel::Low
        ));
    }

    #[test]
    fn test_confidence_meets_minimum_medium_passes_medium_and_low() {
        assert!(confidence_meets_minimum(
            ConfidenceLevel::Medium,
            ConfidenceLevel::Medium
        ));
        assert!(confidence_meets_minimum(
            ConfidenceLevel::Medium,
            ConfidenceLevel::Low
        ));
        assert!(!confidence_meets_minimum(
            ConfidenceLevel::Medium,
            ConfidenceLevel::High
        ));
    }

    #[test]
    fn test_confidence_meets_minimum_low_only_passes_low() {
        assert!(confidence_meets_minimum(
            ConfidenceLevel::Low,
            ConfidenceLevel::Low
        ));
        assert!(!confidence_meets_minimum(
            ConfidenceLevel::Low,
            ConfidenceLevel::Medium
        ));
        assert!(!confidence_meets_minimum(
            ConfidenceLevel::Low,
            ConfidenceLevel::High
        ));
    }

    // ========================================================================
    // Tests for filter_diagnostics
    // ========================================================================

    fn create_test_diagnostic(code: &str) -> ClippyDiagnostic {
        ClippyDiagnostic {
            code: code.to_string(),
            level: DiagnosticLevel::Warning,
            message: "test message".to_string(),
            file: PathBuf::from("test.rs"),
            line_start: 1,
            line_end: 1,
            column_start: 1,
            column_end: 10,
            suggestion: None,
        }
    }

    #[test]
    fn test_filter_diagnostics_empty() {
        let engine = ClippyFixEngine::new();
        let filtered = filter_diagnostics(&engine, vec![], ConfidenceLevel::High, &None);
        assert!(filtered.is_empty());
    }

    #[test]
    fn test_filter_diagnostics_by_confidence() {
        let engine = ClippyFixEngine::new();
        let diagnostics = vec![
            create_test_diagnostic("clippy::needless_return"), // High confidence
            create_test_diagnostic("clippy::manual_map"),      // Medium confidence
            create_test_diagnostic("clippy::needless_lifetimes"), // Low confidence
        ];

        // Filter for High only
        let high_only =
            filter_diagnostics(&engine, diagnostics.clone(), ConfidenceLevel::High, &None);
        assert_eq!(high_only.len(), 1);
        assert_eq!(high_only[0].code, "clippy::needless_return");

        // Filter for Medium and above
        let medium_plus =
            filter_diagnostics(&engine, diagnostics.clone(), ConfidenceLevel::Medium, &None);
        assert_eq!(medium_plus.len(), 2);

        // Filter for Low (all pass)
        let all = filter_diagnostics(&engine, diagnostics, ConfidenceLevel::Low, &None);
        assert_eq!(all.len(), 3);
    }

    #[test]
    fn test_filter_diagnostics_by_specific_codes() {
        let engine = ClippyFixEngine::new();
        let diagnostics = vec![
            create_test_diagnostic("clippy::needless_return"),
            create_test_diagnostic("clippy::manual_map"),
            create_test_diagnostic("clippy::redundant_clone"),
        ];

        let specific_codes = Some(vec![
            "clippy::needless_return".to_string(),
            "clippy::redundant_clone".to_string(),
        ]);

        let filtered =
            filter_diagnostics(&engine, diagnostics, ConfidenceLevel::Low, &specific_codes);
        assert_eq!(filtered.len(), 2);
        assert!(filtered.iter().any(|d| d.code == "clippy::needless_return"));
        assert!(filtered.iter().any(|d| d.code == "clippy::redundant_clone"));
        assert!(!filtered.iter().any(|d| d.code == "clippy::manual_map"));
    }

    #[test]
    fn test_filter_diagnostics_combined_filters() {
        let engine = ClippyFixEngine::new();
        let diagnostics = vec![
            create_test_diagnostic("clippy::needless_return"), // High - in list
            create_test_diagnostic("clippy::manual_map"),      // Medium - in list
            create_test_diagnostic("clippy::needless_lifetimes"), // Low - NOT in list
        ];

        let specific_codes = Some(vec![
            "clippy::needless_return".to_string(),
            "clippy::manual_map".to_string(),
        ]);

        // High confidence + specific codes: only needless_return
        let filtered = filter_diagnostics(
            &engine,
            diagnostics.clone(),
            ConfidenceLevel::High,
            &specific_codes,
        );
        assert_eq!(filtered.len(), 1);
        assert_eq!(filtered[0].code, "clippy::needless_return");
    }
}