repopilot 0.13.0

Local-first CLI for repository audit, architecture risk detection, baseline tracking, and CI-friendly code review.
Documentation
use super::*;
use crate::findings::types::{Evidence, Finding};

#[test]
fn parses_minimal_feedback_suppressions() {
    let suppressions = parse_suppressions(
        r#"
suppressions:
  - rule_id: architecture.large-file
    path: src/generated/schema.rs
    reason: generated schema boundary
"#,
    );

    assert_eq!(suppressions.len(), 1);
    assert_eq!(suppressions[0].index, 1);
    assert_eq!(suppressions[0].rule_id, "architecture.large-file");
    assert_eq!(suppressions[0].path, "src/generated/schema.rs");
    assert_eq!(
        suppressions[0].reason.as_deref(),
        Some("generated schema boundary")
    );
}

#[test]
fn reports_malformed_yaml_as_warning() {
    let validation = validate_feedback_content(
        "suppressions:\n  - rule_id: [\n",
        PathBuf::from(".repopilot/feedback.yml"),
    );

    assert!(validation.parse_error.is_some());
    assert_eq!(validation.diagnostics.len(), 1);
    assert_eq!(validation.diagnostics[0].code, "feedback.parse-failed");
}

#[test]
fn rejects_incomplete_suppressions() {
    let validation = validate_feedback_content(
        r#"
suppressions:
  - rule_id: security.secret-candidate
  - path: src/main.rs
"#,
        PathBuf::from(".repopilot/feedback.yml"),
    );

    assert_eq!(validation.suppressions.len(), 0);
    assert_eq!(validation.invalid_suppressions_count, 2);
    assert_eq!(validation.diagnostics.len(), 2);
}

#[test]
fn indexed_matching_considers_all_evidence_paths_and_suppression_order() {
    let suppressions = vec![
        LocalSuppression {
            index: 1,
            rule_id: "security.secret-candidate".to_string(),
            path: "src/second.rs".to_string(),
            reason: None,
        },
        LocalSuppression {
            index: 2,
            rule_id: "security.secret-candidate".to_string(),
            path: "src/first.rs".to_string(),
            reason: None,
        },
        LocalSuppression {
            index: 3,
            rule_id: "architecture.large-file".to_string(),
            path: "src/first.rs".to_string(),
            reason: None,
        },
    ];
    let index = build_suppression_index(&suppressions);
    let finding = Finding {
        rule_id: "security.secret-candidate".to_string(),
        evidence: vec![
            Evidence {
                path: PathBuf::from("./src/first.rs"),
                line_start: 1,
                line_end: None,
                snippet: String::new(),
            },
            Evidence {
                path: PathBuf::from("src/second.rs"),
                line_start: 2,
                line_end: None,
                snippet: String::new(),
            },
        ],
        ..Finding::default()
    };

    assert_eq!(matching_suppression_index(&finding, &index), Some(1));
}