repopilot 0.17.0

Local-first CLI for reviewing Git changes, security boundaries, and blast radius before merge.
Documentation
//! Tests for hidden-suggestion summaries and applying a visibility profile to a
//! whole `ScanSummary`. Per-finding `classify_visibility` cases live in `tests`.

use super::*;
use crate::findings::types::{Confidence, FindingCategory, Severity};
use crate::scan::types::{ScanArtifacts, ScanMetadata, ScanMetrics};

fn finding(rule_id: &str, category: FindingCategory, severity: Severity) -> Finding {
    Finding {
        rule_id: rule_id.to_string(),
        category,
        severity,
        confidence: Confidence::High,
        ..Default::default()
    }
}

#[test]
fn hidden_suggestion_summary_groups_by_intent_rule_and_reason() {
    let findings = vec![
        finding(
            "architecture.large-file",
            FindingCategory::Architecture,
            Severity::High,
        ),
        finding(
            "architecture.large-file",
            FindingCategory::Architecture,
            Severity::High,
        ),
        finding(
            "testing.source-without-test",
            FindingCategory::Testing,
            Severity::Low,
        ),
    ];

    let summaries = build_hidden_suggestion_summaries(&findings);

    assert_eq!(summaries.len(), 2);
    assert_eq!(summaries[0].rule_id, "architecture.large-file");
    assert_eq!(summaries[0].intent, "maintainability");
    assert_eq!(summaries[0].count, 2);
    assert_eq!(summaries[1].rule_id, "testing.source-without-test");
    assert_eq!(summaries[1].intent, "testing-gap");
    assert_eq!(summaries[1].count, 1);
}

#[test]
fn apply_default_profile_recomputes_visible_counts_and_hidden_breakdown() {
    let visible = finding(
        "security.secret-candidate",
        FindingCategory::Security,
        Severity::High,
    );
    let hidden = finding(
        "architecture.large-file",
        FindingCategory::Architecture,
        Severity::High,
    );

    let mut summary = ScanSummary {
        metadata: ScanMetadata {
            ..Default::default()
        },
        metrics: ScanMetrics {
            non_empty_lines: 1_000,
            ..Default::default()
        },
        artifacts: ScanArtifacts {
            hidden_suggestions: Vec::new(),
            findings: vec![visible, hidden],
            ..Default::default()
        },
    };

    apply_visibility_profile(&mut summary, FindingVisibilityProfile::Default);

    assert_eq!(summary.visibility_profile.as_deref(), Some("default"));
    assert_eq!(summary.metrics.raw_findings_count, 2);
    assert_eq!(summary.metrics.visible_findings_count, 1);
    assert_eq!(summary.metrics.hidden_suggestions_count, 1);
    assert_eq!(summary.artifacts.raw_signal_quality.findings_total, 2);
    assert_eq!(summary.artifacts.visible_signal_quality.findings_total, 1);
    assert_eq!(summary.artifacts.signal_quality.findings_total, 1);
    assert_eq!(summary.artifacts.hidden_suggestions.len(), 1);
    assert_eq!(
        summary.artifacts.hidden_suggestions[0].intent,
        "maintainability"
    );
    assert_eq!(
        summary.artifacts.hidden_suggestions[0].rule_id,
        "architecture.large-file"
    );
    assert_eq!(summary.artifacts.findings.len(), 1);
    assert_eq!(
        summary.artifacts.findings[0].rule_id,
        "security.secret-candidate"
    );
}

#[test]
fn strict_profile_keeps_all_findings_and_clears_hidden_breakdown() {
    let mut summary = ScanSummary {
        metadata: ScanMetadata {
            ..Default::default()
        },
        metrics: ScanMetrics {
            non_empty_lines: 1_000,
            ..Default::default()
        },
        artifacts: ScanArtifacts {
            hidden_suggestions: vec![HiddenSuggestionSummary {
                intent: "maintainability".to_string(),
                rule_id: "architecture.large-file".to_string(),
                category: "architecture".to_string(),
                reason: "example".to_string(),
                count: 1,
            }],
            findings: vec![
                finding(
                    "security.secret-candidate",
                    FindingCategory::Security,
                    Severity::High,
                ),
                finding(
                    "architecture.large-file",
                    FindingCategory::Architecture,
                    Severity::High,
                ),
            ],
            ..Default::default()
        },
    };

    apply_visibility_profile(&mut summary, FindingVisibilityProfile::Strict);

    assert_eq!(summary.visibility_profile.as_deref(), Some("strict"));
    assert_eq!(summary.metrics.raw_findings_count, 2);
    assert_eq!(summary.metrics.visible_findings_count, 2);
    assert_eq!(summary.metrics.hidden_suggestions_count, 0);
    assert!(summary.artifacts.hidden_suggestions.is_empty());
    assert_eq!(summary.artifacts.findings.len(), 2);
}