use crate::domain::findings::{
ArchitectureFinding, ComplexityFinding, ComplexityFindingKind, CouplingFinding,
CouplingFindingDetails, CouplingFindingKind, DryFinding, DryFindingDetails, DryFindingKind,
DuplicateParticipant, IospFinding, TqFinding, TqFindingKind,
};
use crate::domain::Finding;
use crate::report::findings_list::*;
use crate::report::{AnalysisResult, Summary};
fn empty_analysis() -> AnalysisResult {
AnalysisResult {
results: vec![],
summary: Summary::default(),
findings: crate::domain::AnalysisFindings::default(),
data: crate::domain::AnalysisData::default(),
}
}
fn common(file: &str, line: usize, dim: crate::findings::Dimension) -> Finding {
Finding {
file: file.into(),
line,
column: 0,
dimension: dim,
rule_id: "test".into(),
message: "test".into(),
severity: crate::domain::Severity::Medium,
suppressed: false,
}
}
#[test]
fn test_collect_empty_analysis() {
let analysis = empty_analysis();
let findings = collect_all_findings(&analysis);
assert!(findings.is_empty());
}
#[test]
fn test_collect_iosp_violation() {
let mut analysis = empty_analysis();
analysis.findings.iosp = vec![IospFinding {
common: common("src/lib.rs", 5, crate::findings::Dimension::Iosp),
logic_locations: vec![],
call_locations: vec![],
effort_score: None,
}];
let findings = collect_all_findings(&analysis);
assert_eq!(findings.len(), 1);
assert_eq!(findings[0].category, "VIOLATION");
}
#[test]
fn test_collect_magic_number_per_occurrence() {
let mut analysis = empty_analysis();
analysis.findings.complexity = vec![
ComplexityFinding {
common: common("src/lib.rs", 12, crate::findings::Dimension::Complexity),
kind: ComplexityFindingKind::MagicNumber,
metric_value: 1,
threshold: 0,
hotspot: None,
},
ComplexityFinding {
common: common("src/lib.rs", 15, crate::findings::Dimension::Complexity),
kind: ComplexityFindingKind::MagicNumber,
metric_value: 1,
threshold: 0,
hotspot: None,
},
];
let findings = collect_all_findings(&analysis);
assert_eq!(findings.len(), 2);
assert_eq!(findings[0].category, "MAGIC_NUMBER");
assert_eq!(findings[0].line, 12);
assert_eq!(findings[1].line, 15);
}
#[test]
fn test_sorted_by_file_and_line() {
let mut analysis = empty_analysis();
analysis.findings.complexity = vec![
ComplexityFinding {
common: common("src/b.rs", 20, crate::findings::Dimension::Complexity),
kind: ComplexityFindingKind::ErrorHandling,
metric_value: 1,
threshold: 0,
hotspot: None,
},
ComplexityFinding {
common: common("src/a.rs", 10, crate::findings::Dimension::Complexity),
kind: ComplexityFindingKind::ErrorHandling,
metric_value: 1,
threshold: 0,
hotspot: None,
},
];
let findings = collect_all_findings(&analysis);
assert_eq!(findings[0].file, "src/a.rs");
assert_eq!(findings[1].file, "src/b.rs");
}
#[test]
fn test_suppressed_not_collected() {
let mut analysis = empty_analysis();
let mut c = common("src/lib.rs", 5, crate::findings::Dimension::Iosp);
c.suppressed = true;
analysis.findings.iosp = vec![IospFinding {
common: c,
logic_locations: vec![],
call_locations: vec![],
effort_score: None,
}];
let findings = collect_all_findings(&analysis);
assert!(findings.is_empty());
}
#[test]
fn test_total_findings_dry_duplicate_per_participant() {
let mut analysis = empty_analysis();
let participants = vec![
DuplicateParticipant {
function_name: "fn_a".into(),
file: "src/a.rs".into(),
line: 10,
},
DuplicateParticipant {
function_name: "fn_b".into(),
file: "src/b.rs".into(),
line: 20,
},
];
analysis.findings.dry = vec![
DryFinding {
common: common("src/a.rs", 10, crate::findings::Dimension::Dry),
kind: DryFindingKind::DuplicateExact,
details: DryFindingDetails::Duplicate {
participants: participants.clone(),
similarity: None,
},
},
DryFinding {
common: common("src/b.rs", 20, crate::findings::Dimension::Dry),
kind: DryFindingKind::DuplicateExact,
details: DryFindingDetails::Duplicate {
participants: participants.clone(),
similarity: None,
},
},
];
let findings = collect_all_findings(&analysis);
assert_eq!(findings.len(), 2);
assert!(findings.iter().all(|f| f.category == "DUPLICATE"));
}
#[test]
fn test_total_findings_coupling_cycle_no_file() {
let mut analysis = empty_analysis();
let mut c = common("", 0, crate::findings::Dimension::Coupling);
c.file = "".into();
c.line = 0;
analysis.findings.coupling = vec![CouplingFinding {
common: c,
kind: CouplingFindingKind::Cycle,
details: CouplingFindingDetails::Cycle {
modules: vec!["a".into(), "b".into(), "a".into()],
},
}];
let findings = collect_all_findings(&analysis);
assert_eq!(findings.len(), 1);
assert_eq!(findings[0].category, "CYCLE");
assert!(findings[0].file.is_empty());
}
#[test]
fn test_collect_architecture_finding() {
let mut analysis = empty_analysis();
analysis.findings.architecture = vec![ArchitectureFinding {
common: common(
"src/cli/handlers.rs",
17,
crate::findings::Dimension::Architecture,
),
}];
let findings = collect_all_findings(&analysis);
assert_eq!(findings.len(), 1);
assert_eq!(findings[0].category, "ARCHITECTURE");
assert_eq!(findings[0].file, "src/cli/handlers.rs");
assert_eq!(findings[0].line, 17);
}
#[test]
fn test_collect_test_quality_uncovered() {
let mut analysis = empty_analysis();
analysis.findings.test_quality = vec![TqFinding {
common: common("src/lib.rs", 30, crate::findings::Dimension::TestQuality),
kind: TqFindingKind::Uncovered,
function_name: "uncovered_fn".into(),
uncovered_lines: None,
}];
let findings = collect_all_findings(&analysis);
assert_eq!(findings.len(), 1);
assert_eq!(findings[0].category, "TQ_UNCOVERED");
}
#[test]
fn findings_list_includes_orphan_suppressions_via_snapshot_view() {
use crate::domain::findings::OrphanSuppression;
let mut analysis = empty_analysis();
analysis.findings.orphan_suppressions = vec![OrphanSuppression {
file: "src/foo.rs".into(),
line: 42,
dimensions: vec![crate::findings::Dimension::Srp],
reason: Some("legacy marker".into()),
}];
let findings = collect_all_findings(&analysis);
assert_eq!(findings.len(), 1);
assert_eq!(findings[0].category, "ORPHAN_SUPPRESSION");
assert!(findings[0].detail.contains("srp"));
assert!(findings[0].detail.contains("legacy marker"));
}
#[test]
fn test_format_findings_empty_yields_empty_string() {
let s = crate::report::findings_list::format_findings(&[]);
assert!(
s.is_empty(),
"empty input must produce empty output; got `{s}`"
);
}
#[test]
fn test_format_findings_emits_file_line_category_and_function_name() {
let entries = vec![FindingEntry::new(
"src/foo.rs",
10,
"VIOLATION",
"logic + calls".into(),
"fn_x".into(),
)];
let s = crate::report::findings_list::format_findings(&entries);
assert!(
s.contains("src/foo.rs:10"),
"file:line preserved; got `{s}`"
);
assert!(s.contains("VIOLATION"), "category preserved; got `{s}`");
assert!(s.contains("logic + calls"), "detail preserved; got `{s}`");
assert!(s.contains("fn_x"), "function_name preserved; got `{s}`");
assert!(s.contains("1 Finding"), "heading shows count; got `{s}`");
}