#![cfg_attr(coverage_nightly, coverage(off))]
use crate::services::enhanced_reporting::*;
use std::collections::HashMap;
use std::path::PathBuf;
use std::time::Duration;
fn make_analysis_results() -> AnalysisResults {
AnalysisResults {
total_duration: Duration::from_secs(5),
analyzed_files: 100,
total_lines: 50000,
complexity_analysis: Some(ComplexityAnalysis {
total_cyclomatic: 500,
total_cognitive: 300,
functions: 200,
max_cyclomatic: 25,
high_complexity_functions: 5,
distribution: vec![100, 50, 30, 15, 5],
}),
dead_code_analysis: Some(DeadCodeAnalysis {
dead_lines: 500,
dead_functions: 15,
dead_code_percentage: 1.0,
}),
duplication_analysis: Some(DuplicationAnalysis {
duplicated_lines: 200,
duplicate_blocks: 25,
duplication_percentage: 0.4,
}),
tdg_analysis: Some(TdgAnalysis {
average_tdg: 2.5,
max_tdg: 5.0,
high_tdg_files: 3,
}),
big_o_analysis: Some(BigOAnalysis {
analyzed_functions: 200,
high_complexity_count: 8,
complexity_distribution: {
let mut m = HashMap::new();
m.insert("O(1)".to_string(), 100);
m.insert("O(n)".to_string(), 80);
m.insert("O(n^2)".to_string(), 15);
m.insert("O(n^3)".to_string(), 5);
m
},
}),
}
}
fn make_minimal_analysis_results() -> AnalysisResults {
AnalysisResults {
total_duration: Duration::from_millis(100),
analyzed_files: 1,
total_lines: 100,
complexity_analysis: None,
dead_code_analysis: None,
duplication_analysis: None,
tdg_analysis: None,
big_o_analysis: None,
}
}
fn make_report_config() -> ReportConfig {
ReportConfig {
project_path: PathBuf::from("/test/project"),
output_format: ReportFormat::Json,
include_visualizations: true,
include_executive_summary: true,
include_recommendations: true,
confidence_threshold: 80,
output_path: None,
}
}
#[test]
fn test_enhanced_reporting_service_new() {
let svc = EnhancedReportingService::new();
assert!(svc.is_ok());
}
#[test]
fn test_enhanced_reporting_service_default() {
let svc = EnhancedReportingService::default();
let _ = svc;
}
#[tokio::test]
async fn test_generate_report_full() {
let svc = EnhancedReportingService::new().unwrap();
let config = make_report_config();
let results = make_analysis_results();
let report = svc.generate_report(config, results).await.unwrap();
assert!(!report.metadata.project_name.is_empty());
assert!(report.executive_summary.overall_health_score <= 100.0);
assert!(!report.sections.is_empty());
assert!(!report.visualizations.is_empty());
}
#[tokio::test]
async fn test_generate_report_minimal() {
let svc = EnhancedReportingService::new().unwrap();
let mut config = make_report_config();
config.include_visualizations = false;
let results = make_minimal_analysis_results();
let report = svc.generate_report(config, results).await.unwrap();
assert!(report.sections.is_empty());
assert!(report.visualizations.is_empty());
assert!(report.recommendations.is_empty());
}
#[tokio::test]
async fn test_generate_report_without_visualizations() {
let svc = EnhancedReportingService::new().unwrap();
let mut config = make_report_config();
config.include_visualizations = false;
let results = make_analysis_results();
let report = svc.generate_report(config, results).await.unwrap();
assert!(report.visualizations.is_empty());
}
#[tokio::test]
async fn test_format_report_json() {
let svc = EnhancedReportingService::new().unwrap();
let config = make_report_config();
let results = make_analysis_results();
let report = svc.generate_report(config, results).await.unwrap();
let json = svc
.format_report(&report, ReportFormat::Json)
.await
.unwrap();
let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
assert!(parsed.is_object());
}
#[tokio::test]
async fn test_format_report_markdown() {
let svc = EnhancedReportingService::new().unwrap();
let config = make_report_config();
let results = make_analysis_results();
let report = svc.generate_report(config, results).await.unwrap();
let md = svc
.format_report(&report, ReportFormat::Markdown)
.await
.unwrap();
assert!(md.contains("# "));
assert!(md.contains("## Executive Summary"));
assert!(md.contains("## Metadata"));
assert!(md.contains("## Recommendations"));
}
#[tokio::test]
async fn test_format_report_html() {
let svc = EnhancedReportingService::new().unwrap();
let config = make_report_config();
let results = make_analysis_results();
let report = svc.generate_report(config, results).await.unwrap();
let html = svc
.format_report(&report, ReportFormat::Html)
.await
.unwrap();
assert!(html.contains("<!DOCTYPE html>"));
assert!(html.contains("<html>"));
assert!(html.contains("</html>"));
assert!(html.contains("Executive Summary"));
}
#[tokio::test]
async fn test_format_report_pdf() {
let svc = EnhancedReportingService::new().unwrap();
let config = make_report_config();
let results = make_minimal_analysis_results();
let report = svc.generate_report(config, results).await.unwrap();
let pdf = svc.format_report(&report, ReportFormat::Pdf).await.unwrap();
assert!(!pdf.is_empty());
}
#[tokio::test]
async fn test_format_report_dashboard() {
let svc = EnhancedReportingService::new().unwrap();
let config = make_report_config();
let results = make_minimal_analysis_results();
let report = svc.generate_report(config, results).await.unwrap();
let dash = svc
.format_report(&report, ReportFormat::Dashboard)
.await
.unwrap();
assert!(dash.contains("Dashboard"));
}
#[tokio::test]
async fn test_markdown_has_metrics_table() {
let svc = EnhancedReportingService::new().unwrap();
let config = make_report_config();
let results = make_analysis_results();
let report = svc.generate_report(config, results).await.unwrap();
let md = svc
.format_report(&report, ReportFormat::Markdown)
.await
.unwrap();
assert!(md.contains("| Metric |"));
}
#[tokio::test]
async fn test_markdown_has_key_findings() {
let svc = EnhancedReportingService::new().unwrap();
let config = make_report_config();
let results = make_analysis_results();
let report = svc.generate_report(config, results).await.unwrap();
let md = svc
.format_report(&report, ReportFormat::Markdown)
.await
.unwrap();
assert!(md.contains("Key Findings"));
}
#[tokio::test]
async fn test_markdown_recommendation_priorities() {
let svc = EnhancedReportingService::new().unwrap();
let config = make_report_config();
let results = make_analysis_results();
let report = svc.generate_report(config, results).await.unwrap();
let md = svc
.format_report(&report, ReportFormat::Markdown)
.await
.unwrap();
assert!(
md.contains("HIGH")
|| md.contains("MEDIUM")
|| md.contains("LOW")
|| md.contains("CRITICAL")
);
}
#[test]
fn test_complexity_analysis_serde() {
let ca = ComplexityAnalysis {
total_cyclomatic: 100,
total_cognitive: 80,
functions: 50,
max_cyclomatic: 30,
high_complexity_functions: 3,
distribution: vec![20, 15, 10, 3, 2],
};
let json = serde_json::to_string(&ca).unwrap();
let back: ComplexityAnalysis = serde_json::from_str(&json).unwrap();
assert_eq!(back.total_cyclomatic, 100);
assert_eq!(back.distribution.len(), 5);
}
#[test]
fn test_dead_code_analysis_serde() {
let dca = DeadCodeAnalysis {
dead_lines: 200,
dead_functions: 10,
dead_code_percentage: 2.5,
};
let json = serde_json::to_string(&dca).unwrap();
let back: DeadCodeAnalysis = serde_json::from_str(&json).unwrap();
assert_eq!(back.dead_lines, 200);
}
#[test]
fn test_duplication_analysis_serde() {
let da = DuplicationAnalysis {
duplicated_lines: 150,
duplicate_blocks: 8,
duplication_percentage: 1.5,
};
let json = serde_json::to_string(&da).unwrap();
let back: DuplicationAnalysis = serde_json::from_str(&json).unwrap();
assert_eq!(back.duplicate_blocks, 8);
}
#[test]
fn test_tdg_analysis_serde() {
let ta = TdgAnalysis {
average_tdg: 2.1,
max_tdg: 4.5,
high_tdg_files: 2,
};
let json = serde_json::to_string(&ta).unwrap();
let back: TdgAnalysis = serde_json::from_str(&json).unwrap();
assert!((back.average_tdg - 2.1).abs() < f64::EPSILON);
}
#[test]
fn test_big_o_analysis_serde() {
let boa = BigOAnalysis {
analyzed_functions: 100,
high_complexity_count: 5,
complexity_distribution: HashMap::new(),
};
let json = serde_json::to_string(&boa).unwrap();
let back: BigOAnalysis = serde_json::from_str(&json).unwrap();
assert_eq!(back.analyzed_functions, 100);
}
#[test]
fn test_report_format_variants() {
assert_eq!(ReportFormat::Html, ReportFormat::Html);
assert_ne!(ReportFormat::Json, ReportFormat::Markdown);
assert_ne!(ReportFormat::Pdf, ReportFormat::Dashboard);
}
#[test]
fn test_risk_level_serde() {
for level in [
RiskLevel::Low,
RiskLevel::Medium,
RiskLevel::High,
RiskLevel::Critical,
] {
let json = serde_json::to_string(&level).unwrap();
let back: RiskLevel = serde_json::from_str(&json).unwrap();
assert_eq!(format!("{:?}", level), format!("{:?}", back));
}
}
#[test]
fn test_severity_serde() {
for sev in [
Severity::Info,
Severity::Low,
Severity::Medium,
Severity::High,
Severity::Critical,
] {
let json = serde_json::to_string(&sev).unwrap();
let back: Severity = serde_json::from_str(&json).unwrap();
assert_eq!(format!("{:?}", sev), format!("{:?}", back));
}
}
#[test]
fn test_effort_level_serde() {
for eff in [
EffortLevel::Trivial,
EffortLevel::Easy,
EffortLevel::Medium,
EffortLevel::Hard,
EffortLevel::VeryHard,
] {
let json = serde_json::to_string(&eff).unwrap();
let back: EffortLevel = serde_json::from_str(&json).unwrap();
assert_eq!(format!("{:?}", eff), format!("{:?}", back));
}
}
#[test]
fn test_priority_serde() {
for pri in [
Priority::Low,
Priority::Medium,
Priority::High,
Priority::Critical,
] {
let json = serde_json::to_string(&pri).unwrap();
let back: Priority = serde_json::from_str(&json).unwrap();
assert_eq!(format!("{:?}", pri), format!("{:?}", back));
}
}
#[test]
fn test_trend_serde() {
for trend in [
Trend::Improving,
Trend::Stable,
Trend::Degrading,
Trend::Unknown,
] {
let json = serde_json::to_string(&trend).unwrap();
let back: Trend = serde_json::from_str(&json).unwrap();
assert_eq!(format!("{:?}", trend), format!("{:?}", back));
}
}
#[test]
fn test_section_type_serde() {
for st in [
SectionType::Complexity,
SectionType::DeadCode,
SectionType::Duplication,
SectionType::TechnicalDebt,
SectionType::Security,
SectionType::Performance,
SectionType::BigOAnalysis,
SectionType::Dependencies,
SectionType::TestCoverage,
SectionType::CodeSmells,
] {
let json = serde_json::to_string(&st).unwrap();
let back: SectionType = serde_json::from_str(&json).unwrap();
assert_eq!(format!("{:?}", st), format!("{:?}", back));
}
}
#[test]
fn test_visualization_type_serde() {
for vt in [
VisualizationType::LineChart,
VisualizationType::BarChart,
VisualizationType::PieChart,
VisualizationType::HeatMap,
VisualizationType::TreeMap,
VisualizationType::NetworkGraph,
VisualizationType::Table,
] {
let json = serde_json::to_string(&vt).unwrap();
let back: VisualizationType = serde_json::from_str(&json).unwrap();
assert_eq!(format!("{:?}", vt), format!("{:?}", back));
}
}
#[test]
fn test_metric_value_construction() {
let mv = MetricValue {
value: 42.0,
unit: "CC".to_string(),
trend: Trend::Improving,
threshold: Some(10.0),
};
let json = serde_json::to_string(&mv).unwrap();
let back: MetricValue = serde_json::from_str(&json).unwrap();
assert!((back.value - 42.0).abs() < f64::EPSILON);
}
#[test]
fn test_finding_construction() {
let f = Finding {
severity: Severity::High,
category: "test".to_string(),
description: "A test finding".to_string(),
location: Some(Location {
file: "src/main.rs".to_string(),
line: Some(42),
column: Some(10),
}),
impact: "High".to_string(),
effort: EffortLevel::Medium,
};
let json = serde_json::to_string(&f).unwrap();
assert!(json.contains("src/main.rs"));
}
#[test]
fn test_finding_without_location() {
let f = Finding {
severity: Severity::Low,
category: "misc".to_string(),
description: "No location".to_string(),
location: None,
impact: "Low".to_string(),
effort: EffortLevel::Trivial,
};
let json = serde_json::to_string(&f).unwrap();
assert!(json.contains("null") || json.contains("location"));
}
#[test]
fn test_recommendation_construction() {
let rec = Recommendation {
priority: Priority::Critical,
category: "security".to_string(),
title: "Fix vulnerability".to_string(),
description: "Critical security issue found".to_string(),
expected_impact: "Prevents data breach".to_string(),
effort: EffortLevel::Hard,
related_findings: vec!["CVE-1234".to_string()],
};
let json = serde_json::to_string(&rec).unwrap();
assert!(json.contains("CVE-1234"));
}
#[test]
fn test_visualization_construction() {
let viz = Visualization {
title: "Test Chart".to_string(),
viz_type: VisualizationType::BarChart,
data: serde_json::json!({"labels": ["a", "b"], "values": [1, 2]}),
config: HashMap::new(),
};
let json = serde_json::to_string(&viz).unwrap();
assert!(json.contains("Test Chart"));
}
#[test]
fn test_report_section_construction() {
let section = ReportSection {
title: "Test Section".to_string(),
section_type: SectionType::Complexity,
content: serde_json::json!({}),
metrics: HashMap::new(),
findings: vec![],
};
let json = serde_json::to_string(§ion).unwrap();
assert!(json.contains("Test Section"));
}
#[test]
fn test_report_metadata_construction() {
let meta = ReportMetadata {
project_name: "test".to_string(),
project_path: "/test".to_string(),
report_date: "2025-01-01".to_string(),
tool_version: "1.0.0".to_string(),
analysis_duration: 5.0,
analyzed_files: 100,
total_lines: 50000,
};
let json = serde_json::to_string(&meta).unwrap();
let back: ReportMetadata = serde_json::from_str(&json).unwrap();
assert_eq!(back.project_name, "test");
}
#[test]
fn test_executive_summary_construction() {
let es = ExecutiveSummary {
overall_health_score: 85.0,
critical_issues: 2,
high_priority_issues: 5,
key_findings: vec!["Finding 1".to_string()],
risk_assessment: RiskLevel::Medium,
};
let json = serde_json::to_string(&es).unwrap();
assert!(json.contains("85"));
}
#[test]
fn test_unified_analysis_report_construction() {
let report = UnifiedAnalysisReport {
metadata: ReportMetadata {
project_name: "test".to_string(),
project_path: "/test".to_string(),
report_date: "2025-01-01".to_string(),
tool_version: "1.0.0".to_string(),
analysis_duration: 1.0,
analyzed_files: 10,
total_lines: 1000,
},
executive_summary: ExecutiveSummary {
overall_health_score: 90.0,
critical_issues: 0,
high_priority_issues: 1,
key_findings: vec![],
risk_assessment: RiskLevel::Low,
},
sections: vec![],
recommendations: vec![],
visualizations: vec![],
};
let json = serde_json::to_string(&report).unwrap();
let back: UnifiedAnalysisReport = serde_json::from_str(&json).unwrap();
assert_eq!(back.metadata.project_name, "test");
}
#[tokio::test]
async fn test_health_score_perfect() {
let svc = EnhancedReportingService::new().unwrap();
let config = make_report_config();
let results = AnalysisResults {
total_duration: Duration::from_secs(1),
analyzed_files: 10,
total_lines: 1000,
complexity_analysis: Some(ComplexityAnalysis {
total_cyclomatic: 50,
total_cognitive: 30,
functions: 50,
max_cyclomatic: 5,
high_complexity_functions: 0,
distribution: vec![50, 0, 0, 0, 0],
}),
dead_code_analysis: Some(DeadCodeAnalysis {
dead_lines: 0,
dead_functions: 0,
dead_code_percentage: 0.0,
}),
duplication_analysis: Some(DuplicationAnalysis {
duplicated_lines: 0,
duplicate_blocks: 0,
duplication_percentage: 0.0,
}),
tdg_analysis: Some(TdgAnalysis {
average_tdg: 1.0,
max_tdg: 2.0,
high_tdg_files: 0,
}),
big_o_analysis: None,
};
let report = svc.generate_report(config, results).await.unwrap();
assert!(report.executive_summary.overall_health_score >= 90.0);
assert!(matches!(
report.executive_summary.risk_assessment,
RiskLevel::Low
));
}
#[tokio::test]
async fn test_health_score_terrible() {
let svc = EnhancedReportingService::new().unwrap();
let config = make_report_config();
let results = AnalysisResults {
total_duration: Duration::from_secs(1),
analyzed_files: 10,
total_lines: 1000,
complexity_analysis: Some(ComplexityAnalysis {
total_cyclomatic: 3000,
total_cognitive: 2000,
functions: 100,
max_cyclomatic: 50,
high_complexity_functions: 30,
distribution: vec![5, 10, 20, 30, 35],
}),
dead_code_analysis: Some(DeadCodeAnalysis {
dead_lines: 300,
dead_functions: 50,
dead_code_percentage: 30.0,
}),
duplication_analysis: Some(DuplicationAnalysis {
duplicated_lines: 300,
duplicate_blocks: 50,
duplication_percentage: 30.0,
}),
tdg_analysis: Some(TdgAnalysis {
average_tdg: 8.0,
max_tdg: 10.0,
high_tdg_files: 20,
}),
big_o_analysis: None,
};
let report = svc.generate_report(config, results).await.unwrap();
assert!(report.executive_summary.overall_health_score < 50.0);
}
#[test]
fn test_report_config_construction() {
let config = ReportConfig {
project_path: PathBuf::from("/test"),
output_format: ReportFormat::Markdown,
include_visualizations: false,
include_executive_summary: true,
include_recommendations: false,
confidence_threshold: 90,
output_path: Some(PathBuf::from("/output/report.md")),
};
assert!(!config.include_visualizations);
assert_eq!(config.confidence_threshold, 90);
}