#![cfg_attr(coverage_nightly, coverage(off))]
use crate::models::defect_report::{
Defect, DefectCategory, DefectReport, ReportMetadata, Severity,
};
use crate::services::defect_report_service::{DefectReportService, ReportFormat};
use chrono::Utc;
use std::collections::{BTreeMap, HashMap};
use std::path::PathBuf;
fn make_defect(id: &str, sev: Severity, cat: DefectCategory, file: &str, line: u32) -> Defect {
Defect {
id: id.to_string(),
severity: sev,
category: cat,
file_path: PathBuf::from(file),
line_start: line,
line_end: Some(line + 5),
column_start: Some(1),
column_end: Some(80),
message: format!("Test defect {id}"),
rule_id: format!("RULE-{id}"),
fix_suggestion: Some(format!("Fix for {id}")),
metrics: {
let mut m = HashMap::new();
m.insert("cyclomatic".to_string(), 15.0);
m.insert("cognitive".to_string(), 12.0);
m
},
}
}
fn make_report(defects: Vec<Defect>) -> DefectReport {
let svc = DefectReportService::new();
let summary = svc.compute_summary(&defects);
let mut file_index = BTreeMap::new();
for d in &defects {
file_index
.entry(d.file_path.clone())
.or_insert_with(Vec::new)
.push(d.id.clone());
}
DefectReport {
metadata: ReportMetadata {
tool: "pmat".to_string(),
version: "2.0.0".to_string(),
generated_at: Utc::now(),
project_root: PathBuf::from("/test/project"),
total_files_analyzed: file_index.len(),
analysis_duration_ms: 1234,
},
defects,
summary,
file_index,
}
}
fn sample_defects() -> Vec<Defect> {
vec![
make_defect(
"D001",
Severity::Critical,
DefectCategory::Complexity,
"src/main.rs",
10,
),
make_defect(
"D002",
Severity::High,
DefectCategory::TechnicalDebt,
"src/main.rs",
50,
),
make_defect(
"D003",
Severity::Medium,
DefectCategory::DeadCode,
"src/lib.rs",
20,
),
make_defect(
"D004",
Severity::Low,
DefectCategory::Duplication,
"src/utils.rs",
30,
),
make_defect(
"D005",
Severity::High,
DefectCategory::Performance,
"src/main.rs",
100,
),
make_defect(
"D006",
Severity::Medium,
DefectCategory::Architecture,
"src/mod.rs",
5,
),
make_defect(
"D007",
Severity::Critical,
DefectCategory::Complexity,
"tests/test.rs",
1,
),
make_defect(
"D008",
Severity::Low,
DefectCategory::TestCoverage,
"src/lib.rs",
200,
),
]
}
#[test]
fn test_defect_report_service_new() {
let svc = DefectReportService::new();
let _ = svc;
}
#[test]
fn test_defect_report_service_default() {
let svc = DefectReportService::default();
let _ = svc;
}
#[test]
fn test_compute_summary_empty() {
let svc = DefectReportService::new();
let summary = svc.compute_summary(&[]);
assert_eq!(summary.total_defects, 0);
assert!(summary.by_severity.is_empty());
assert!(summary.by_category.is_empty());
assert!(summary.hotspot_files.is_empty());
}
#[test]
fn test_compute_summary_single_defect() {
let svc = DefectReportService::new();
let defects = vec![make_defect(
"D001",
Severity::High,
DefectCategory::Complexity,
"src/main.rs",
10,
)];
let summary = svc.compute_summary(&defects);
assert_eq!(summary.total_defects, 1);
assert_eq!(summary.hotspot_files.len(), 1);
assert_eq!(summary.hotspot_files[0].defect_count, 1);
}
#[test]
fn test_compute_summary_multiple_defects() {
let svc = DefectReportService::new();
let defects = sample_defects();
let summary = svc.compute_summary(&defects);
assert_eq!(summary.total_defects, 8);
assert!(!summary.by_severity.is_empty());
assert!(!summary.by_category.is_empty());
if summary.hotspot_files.len() >= 2 {
assert!(summary.hotspot_files[0].severity_score >= summary.hotspot_files[1].severity_score);
}
}
#[test]
fn test_compute_summary_hotspot_limit() {
let svc = DefectReportService::new();
let defects: Vec<Defect> = (0..15)
.map(|i| {
make_defect(
&format!("D{:03}", i),
Severity::Medium,
DefectCategory::Complexity,
&format!("src/file{i}.rs"),
10,
)
})
.collect();
let summary = svc.compute_summary(&defects);
assert!(summary.hotspot_files.len() <= 10);
}
#[test]
fn test_compute_summary_severity_counting() {
let svc = DefectReportService::new();
let defects = vec![
make_defect(
"D001",
Severity::Critical,
DefectCategory::Complexity,
"a.rs",
1,
),
make_defect(
"D002",
Severity::Critical,
DefectCategory::Complexity,
"b.rs",
1,
),
make_defect("D003", Severity::Low, DefectCategory::DeadCode, "c.rs", 1),
];
let summary = svc.compute_summary(&defects);
assert_eq!(summary.total_defects, 3);
assert_eq!(*summary.by_severity.get("critical").unwrap_or(&0), 2);
assert_eq!(*summary.by_severity.get("low").unwrap_or(&0), 1);
}
#[test]
fn test_format_json_valid() {
let svc = DefectReportService::new();
let report = make_report(sample_defects());
let json_str = svc.format_json(&report).unwrap();
assert!(json_str.contains("D001"));
assert!(json_str.contains("pmat"));
let parsed: serde_json::Value = serde_json::from_str(&json_str).unwrap();
assert!(parsed.is_object());
}
#[test]
fn test_format_json_empty_report() {
let svc = DefectReportService::new();
let report = make_report(vec![]);
let json_str = svc.format_json(&report).unwrap();
let parsed: serde_json::Value = serde_json::from_str(&json_str).unwrap();
assert_eq!(parsed["defects"].as_array().unwrap().len(), 0);
}
#[cfg(feature = "reporting")]
#[test]
fn test_format_csv_valid() {
let svc = DefectReportService::new();
let report = make_report(sample_defects());
let csv_str = svc.format_csv(&report).unwrap();
assert!(csv_str.starts_with("id,severity,category,file_path,line_start"));
assert!(csv_str.contains("D001"));
assert!(csv_str.contains("src/main.rs"));
}
#[cfg(feature = "reporting")]
#[test]
fn test_format_csv_empty() {
let svc = DefectReportService::new();
let report = make_report(vec![]);
let csv_str = svc.format_csv(&report).unwrap();
assert!(csv_str.contains("id,severity"));
}
#[cfg(not(feature = "reporting"))]
#[test]
fn test_format_csv_requires_feature() {
let svc = DefectReportService::new();
let report = make_report(sample_defects());
assert!(svc.format_csv(&report).is_err());
}
#[test]
fn test_format_markdown_structure() {
let svc = DefectReportService::new();
let report = make_report(sample_defects());
let md = svc.format_markdown(&report).unwrap();
assert!(md.contains("# Code Quality Report"));
assert!(md.contains("## Executive Summary"));
assert!(md.contains("**Total Defects**"));
assert!(md.contains("### Severity Distribution"));
assert!(md.contains("## Detailed Findings"));
}
#[test]
fn test_format_markdown_hotspots() {
let svc = DefectReportService::new();
let report = make_report(sample_defects());
let md = svc.format_markdown(&report).unwrap();
assert!(md.contains("Top 10 Hotspot Files"));
assert!(md.contains("| Rank |"));
}
#[test]
fn test_format_markdown_fix_suggestions() {
let svc = DefectReportService::new();
let report = make_report(sample_defects());
let md = svc.format_markdown(&report).unwrap();
assert!(md.contains("Suggestion"));
}
#[test]
fn test_format_markdown_empty() {
let svc = DefectReportService::new();
let report = make_report(vec![]);
let md = svc.format_markdown(&report).unwrap();
assert!(md.contains("# Code Quality Report"));
assert!(md.contains("**Total Defects**: 0"));
}
#[test]
fn test_format_text_structure() {
let svc = DefectReportService::new();
let report = make_report(sample_defects());
let txt = svc.format_text(&report).unwrap();
assert!(txt.contains("CODE QUALITY REPORT"));
assert!(txt.contains("SEVERITY BREAKDOWN"));
assert!(txt.contains("CATEGORY BREAKDOWN"));
assert!(txt.contains("DEFECTS"));
}
#[test]
fn test_format_text_hotspots() {
let svc = DefectReportService::new();
let report = make_report(sample_defects());
let txt = svc.format_text(&report).unwrap();
assert!(txt.contains("TOP HOTSPOT FILES"));
}
#[test]
fn test_format_text_defect_lines() {
let svc = DefectReportService::new();
let report = make_report(sample_defects());
let txt = svc.format_text(&report).unwrap();
assert!(txt.contains("src/main.rs"));
assert!(txt.contains("Fix:"));
}
#[test]
fn test_format_text_empty() {
let svc = DefectReportService::new();
let report = make_report(vec![]);
let txt = svc.format_text(&report).unwrap();
assert!(txt.contains("Total Defects: 0"));
}
#[test]
fn test_generate_filename_json() {
let svc = DefectReportService::new();
let name = svc.generate_filename(ReportFormat::Json);
assert!(name.starts_with("defect-report-"));
assert!(name.ends_with(".json"));
}
#[test]
fn test_generate_filename_csv() {
let svc = DefectReportService::new();
let name = svc.generate_filename(ReportFormat::Csv);
assert!(name.ends_with(".csv"));
}
#[test]
fn test_generate_filename_markdown() {
let svc = DefectReportService::new();
let name = svc.generate_filename(ReportFormat::Markdown);
assert!(name.ends_with(".md"));
}
#[test]
fn test_generate_filename_text() {
let svc = DefectReportService::new();
let name = svc.generate_filename(ReportFormat::Text);
assert!(name.ends_with(".txt"));
}
#[test]
fn test_filter_by_pattern_include() {
let report = make_report(sample_defects());
let filtered =
DefectReportService::filter_by_pattern(&report, Some("src/main.rs".to_string()), None, 0);
for d in &filtered.defects {
assert_eq!(d.file_path, PathBuf::from("src/main.rs"));
}
assert!(filtered.defects.len() < report.defects.len());
}
#[test]
fn test_filter_by_pattern_exclude() {
let report = make_report(sample_defects());
let filtered =
DefectReportService::filter_by_pattern(&report, None, Some("tests/*".to_string()), 0);
for d in &filtered.defects {
assert!(!d.file_path.starts_with("tests/"));
}
}
#[test]
fn test_filter_by_pattern_both() {
let report = make_report(sample_defects());
let filtered = DefectReportService::filter_by_pattern(
&report,
Some("src/**".to_string()),
Some("src/utils.rs".to_string()),
0,
);
for d in &filtered.defects {
assert!(d.file_path.starts_with("src/"));
assert_ne!(d.file_path, PathBuf::from("src/utils.rs"));
}
}
#[test]
fn test_filter_by_pattern_none() {
let report = make_report(sample_defects());
let filtered = DefectReportService::filter_by_pattern(&report, None, None, 0);
assert_eq!(filtered.defects.len(), report.defects.len());
}
#[test]
fn test_filter_by_pattern_rebuilds_index() {
let report = make_report(sample_defects());
let filtered =
DefectReportService::filter_by_pattern(&report, Some("src/main.rs".to_string()), None, 0);
assert!(filtered
.file_index
.contains_key(&PathBuf::from("src/main.rs")));
assert_eq!(
filtered.metadata.total_files_analyzed,
filtered.file_index.len()
);
}
#[test]
fn test_report_format_debug() {
let fmt = ReportFormat::Json;
let debug_str = format!("{:?}", fmt);
assert_eq!(debug_str, "Json");
}
#[test]
fn test_report_format_clone() {
let fmt = ReportFormat::Markdown;
let cloned = fmt;
assert!(matches!(cloned, ReportFormat::Markdown));
}
#[test]
fn test_all_formats_contain_defect_data() {
let svc = DefectReportService::new();
let report = make_report(sample_defects());
let json = svc.format_json(&report).unwrap();
let md = svc.format_markdown(&report).unwrap();
let txt = svc.format_text(&report).unwrap();
assert!(json.contains("D001"));
assert!(md.contains("src/main.rs"));
assert!(txt.contains("src/main.rs"));
#[cfg(feature = "reporting")]
{
let csv = svc.format_csv(&report).unwrap();
assert!(csv.contains("D001"));
}
#[cfg(not(feature = "reporting"))]
{
assert!(svc.format_csv(&report).is_err());
}
}
#[test]
fn test_defect_without_fix_suggestion() {
let mut defect = make_defect("D100", Severity::Low, DefectCategory::DeadCode, "a.rs", 1);
defect.fix_suggestion = None;
let svc = DefectReportService::new();
let report = make_report(vec![defect]);
let _ = svc.format_json(&report).unwrap();
let _ = svc.format_markdown(&report).unwrap();
let _ = svc.format_text(&report).unwrap();
#[cfg(feature = "reporting")]
{
let _ = svc.format_csv(&report).unwrap();
}
}
#[test]
fn test_defect_without_line_end() {
let mut defect = make_defect(
"D101",
Severity::Medium,
DefectCategory::Complexity,
"b.rs",
42,
);
defect.line_end = None;
let svc = DefectReportService::new();
let report = make_report(vec![defect]);
let txt = svc.format_text(&report).unwrap();
assert!(txt.contains("b.rs:42"));
let md = svc.format_markdown(&report).unwrap();
assert!(md.contains("b.rs:42"));
}
#[test]
fn test_many_defects_in_same_file() {
let defects: Vec<Defect> = (0..50)
.map(|i| {
make_defect(
&format!("D{:03}", i),
Severity::Medium,
DefectCategory::Complexity,
"src/big_file.rs",
i * 10,
)
})
.collect();
let svc = DefectReportService::new();
let summary = svc.compute_summary(&defects);
assert_eq!(summary.total_defects, 50);
assert_eq!(summary.hotspot_files.len(), 1);
assert_eq!(summary.hotspot_files[0].defect_count, 50);
}