use super::{
CategorizedDebt, CategorySummary, CrossCategoryDependency, DebtCategory, DebtItem, DebtType,
DisplayGroup, ImpactLevel, Tier, TieredDisplay, UnifiedAnalysis, UnifiedDebtItem,
};
use crate::priority::filtering::{ClassifiedItem, FilterConfig, FilterResult};
use crate::priority::tiers::{classify_tier, TierConfig};
use im::Vector;
use std::collections::{BTreeMap, HashMap};
pub trait UnifiedAnalysisQueries {
fn get_top_priorities(&self, n: usize) -> Vector<UnifiedDebtItem>;
fn get_top_mixed_priorities(&self, n: usize) -> Vector<DebtItem>;
fn get_top_mixed_priorities_tiered(
&self,
n: usize,
tier_config: &TierConfig,
) -> Vector<DebtItem>;
fn get_top_mixed_priorities_with_metrics(
&self,
n: usize,
tier_config: &TierConfig,
) -> crate::priority::filtering::FilterResult;
fn get_bottom_priorities(&self, n: usize) -> Vector<UnifiedDebtItem>;
fn get_tiered_display(&self, limit: usize) -> TieredDisplay;
fn get_categorized_debt(&self, limit: usize) -> CategorizedDebt;
}
impl UnifiedAnalysisQueries for UnifiedAnalysis {
fn get_top_priorities(&self, n: usize) -> Vector<UnifiedDebtItem> {
self.items.iter().take(n).cloned().collect()
}
fn get_top_mixed_priorities(&self, n: usize) -> Vector<DebtItem> {
self.get_top_mixed_priorities_tiered(n, &TierConfig::default())
}
fn get_top_mixed_priorities_tiered(
&self,
n: usize,
tier_config: &TierConfig,
) -> Vector<DebtItem> {
use crate::priority::tiers::RecommendationTier;
let mut all_items: Vec<DebtItem> = Vec::new();
let min_score = crate::config::get_minimum_score_threshold();
for item in &self.items {
let mut item_with_tier = item.clone();
let tier = classify_tier(item, tier_config);
item_with_tier.tier = Some(tier);
if tier == RecommendationTier::T4Maintenance && !tier_config.show_t4_in_main_report {
continue;
}
if item.unified_score.final_score <= 0.0 || item.unified_score.final_score < min_score {
continue;
}
all_items.push(DebtItem::Function(Box::new(item_with_tier)));
}
for item in &self.file_items {
if item.score <= 0.0 || item.score < min_score {
continue;
}
all_items.push(DebtItem::File(Box::new(item.clone())));
}
all_items.sort_by(|a, b| {
b.score()
.partial_cmp(&a.score())
.unwrap_or(std::cmp::Ordering::Equal)
});
all_items.into_iter().take(n).collect()
}
fn get_top_mixed_priorities_with_metrics(
&self,
limit: usize,
tier_config: &TierConfig,
) -> FilterResult {
use crate::priority::pipeline::filter_sort_limit;
let min_score = crate::config::get_minimum_score_threshold();
let function_items: Vec<ClassifiedItem> = self
.items
.iter()
.map(|item| {
let tier = classify_tier(item, tier_config);
let score = item.unified_score.final_score;
let debt_item = DebtItem::Function(Box::new(item.clone()));
ClassifiedItem {
item: debt_item,
tier,
score,
}
})
.collect();
let file_items: Vec<ClassifiedItem> = self
.file_items
.iter()
.map(|item| {
let tier = crate::priority::tiers::RecommendationTier::T1CriticalArchitecture;
let score = item.score;
let debt_item = DebtItem::File(Box::new(item.clone()));
ClassifiedItem {
item: debt_item,
tier,
score,
}
})
.collect();
let classified: Vec<ClassifiedItem> =
function_items.into_iter().chain(file_items).collect();
let config = FilterConfig {
min_score,
show_t4: tier_config.show_t4_in_main_report,
};
filter_sort_limit(classified, &config, limit)
}
fn get_bottom_priorities(&self, n: usize) -> Vector<UnifiedDebtItem> {
let total_items = self.items.len();
if total_items <= n {
self.items.clone()
} else {
self.items.iter().skip(total_items - n).cloned().collect()
}
}
fn get_tiered_display(&self, limit: usize) -> TieredDisplay {
let all_items = self.get_top_mixed_priorities(limit);
let (critical_items, groupable_items) = partition_by_criticality(all_items);
let mut tier_groups = TierGroups::default();
for item in critical_items {
tier_groups.add_group(create_critical_item_group(item));
}
let grouped = group_items_by_tier_and_type(groupable_items);
for ((tier, debt_type), items) in grouped {
tier_groups.add_group(create_grouped_display_group(tier, debt_type, items));
}
tier_groups.into_tiered_display()
}
fn get_categorized_debt(&self, limit: usize) -> CategorizedDebt {
let all_items = self.get_top_mixed_priorities(limit);
let categories = collect_categorized_items(all_items);
let category_summaries: BTreeMap<DebtCategory, CategorySummary> = categories
.into_iter()
.filter(|(_, items)| !items.is_empty())
.map(|(category, items)| {
let summary = build_category_summary(category.clone(), items);
(category, summary)
})
.collect();
let cross_dependencies = identify_cross_category_dependencies(&category_summaries);
CategorizedDebt {
categories: category_summaries,
cross_category_dependencies: cross_dependencies,
}
}
}
fn categorize_debt_item(item: &DebtItem) -> DebtCategory {
match item {
DebtItem::Function(func) => DebtCategory::from_debt_type(&func.debt_type),
DebtItem::File(file) => {
if file
.metrics
.god_object_analysis
.as_ref()
.is_some_and(|a| a.is_god_object)
{
DebtCategory::Architecture
} else if file.metrics.coverage_percent < 0.5 {
DebtCategory::Testing
} else {
DebtCategory::CodeQuality
}
}
}
}
fn estimate_effort_per_item(category: &DebtCategory, average_severity: f64) -> u32 {
match category {
DebtCategory::Architecture => {
if average_severity >= 90.0 {
16 } else if average_severity >= 70.0 {
8 } else {
4 }
}
DebtCategory::Testing => {
if average_severity >= 70.0 {
4
} else {
2
}
}
DebtCategory::Performance => {
if average_severity >= 70.0 {
8
} else {
4
}
}
DebtCategory::CodeQuality => {
if average_severity >= 70.0 {
4
} else {
2
}
}
}
}
fn collect_categorized_items(items: Vector<DebtItem>) -> BTreeMap<DebtCategory, Vec<DebtItem>> {
items
.into_iter()
.fold(BTreeMap::new(), |mut categories, item| {
let category = categorize_debt_item(&item);
categories.entry(category).or_default().push(item);
categories
})
}
fn build_category_summary(category: DebtCategory, items: Vec<DebtItem>) -> CategorySummary {
let total_score: f64 = items.iter().map(|item| item.score()).sum();
let item_count = items.len();
let average_severity = total_score / item_count as f64;
let effort_per_item = estimate_effort_per_item(&category, average_severity);
let estimated_effort_hours = (item_count as u32) * effort_per_item;
let top_items = items.into_iter().take(5).collect();
CategorySummary {
category,
total_score,
item_count,
estimated_effort_hours,
average_severity,
top_items,
}
}
#[derive(Default)]
struct TierGroups {
critical: Vec<DisplayGroup>,
high: Vec<DisplayGroup>,
moderate: Vec<DisplayGroup>,
low: Vec<DisplayGroup>,
}
impl TierGroups {
fn add_group(&mut self, group: DisplayGroup) {
match group.tier {
Tier::Critical => self.critical.push(group),
Tier::High => self.high.push(group),
Tier::Moderate => self.moderate.push(group),
Tier::Low => self.low.push(group),
}
}
fn into_tiered_display(mut self) -> TieredDisplay {
sort_groups_by_score(&mut self.critical);
sort_groups_by_score(&mut self.high);
sort_groups_by_score(&mut self.moderate);
sort_groups_by_score(&mut self.low);
TieredDisplay {
critical: self.critical,
high: self.high,
moderate: self.moderate,
low: self.low,
}
}
}
fn sort_groups_by_score(groups: &mut [DisplayGroup]) {
groups.sort_by(|a, b| {
let a_score: f64 = a.items.iter().map(|i| i.score()).sum();
let b_score: f64 = b.items.iter().map(|i| i.score()).sum();
b_score
.partial_cmp(&a_score)
.unwrap_or(std::cmp::Ordering::Equal)
});
}
fn create_critical_item_group(item: DebtItem) -> DisplayGroup {
let tier = Tier::from_score(item.score());
let debt_type = get_debt_type_key(&item);
DisplayGroup {
tier,
debt_type,
items: vec![item],
batch_action: None,
}
}
fn create_grouped_display_group(
tier: Tier,
debt_type: String,
items: Vec<DebtItem>,
) -> DisplayGroup {
let batch_action = if items.len() > 1 {
Some(generate_batch_action(&debt_type, items.len()))
} else {
None
};
DisplayGroup {
tier,
debt_type,
items,
batch_action,
}
}
fn partition_by_criticality(items: im::Vector<DebtItem>) -> (Vec<DebtItem>, Vec<DebtItem>) {
items.into_iter().partition(is_critical_item)
}
fn group_items_by_tier_and_type(items: Vec<DebtItem>) -> HashMap<(Tier, String), Vec<DebtItem>> {
let mut groups: HashMap<(Tier, String), Vec<DebtItem>> = HashMap::new();
for item in items {
let tier = Tier::from_score(item.score());
let debt_type = get_debt_type_key(&item);
groups.entry((tier, debt_type)).or_default().push(item);
}
groups
}
fn get_debt_type_key(item: &DebtItem) -> String {
match item {
DebtItem::Function(func) => match &func.debt_type {
DebtType::TestingGap { .. } => "Untested Complex Functions".to_string(),
DebtType::ComplexityHotspot { .. } => "High Complexity Functions".to_string(),
DebtType::DeadCode { .. } => "Dead Code".to_string(),
DebtType::Duplication { .. } => "Code Duplication".to_string(),
DebtType::Risk { .. } => "High Risk Functions".to_string(),
DebtType::GodObject { .. } => "God Object".to_string(),
DebtType::FeatureEnvy { .. } => "Feature Envy".to_string(),
DebtType::TestComplexityHotspot { .. } => "Complex Test Functions".to_string(),
_ => "Technical Debt".to_string(),
},
DebtItem::File(file) => {
if file
.metrics
.god_object_analysis
.as_ref()
.is_some_and(|a| a.is_god_object)
{
"God Object File".to_string()
} else if file.metrics.total_lines > 1000 {
"Large File".to_string()
} else if file.metrics.avg_complexity > 10.0 {
"Complex File".to_string()
} else {
"File-Level Debt".to_string()
}
}
}
}
fn is_critical_item(item: &DebtItem) -> bool {
match item {
DebtItem::Function(func) => {
matches!(func.debt_type, DebtType::GodObject { .. })
|| func.unified_score.final_score >= 95.0
}
DebtItem::File(file) => {
file.metrics
.god_object_analysis
.as_ref()
.is_some_and(|a| a.is_god_object)
|| file.metrics.total_lines > 2000
|| file.score >= 95.0
}
}
}
fn generate_batch_action(debt_type: &str, count: usize) -> String {
match debt_type {
"Untested Complex Functions" => {
format!("Add test coverage for {} complex functions", count)
}
"High Complexity Functions" => {
format!("Refactor {} complex functions into smaller units", count)
}
"Dead Code" => format!("Remove {} unused functions", count),
"Code Duplication" => format!(
"Extract {} duplicated code blocks into shared utilities",
count
),
"Complex Test Functions" => format!("Simplify {} complex test functions", count),
_ => format!("Address {} {} items", count, debt_type.to_lowercase()),
}
}
fn identify_cross_category_dependencies(
categories: &BTreeMap<DebtCategory, CategorySummary>,
) -> Vec<CrossCategoryDependency> {
let mut dependencies = Vec::new();
if categories.contains_key(&DebtCategory::Architecture)
&& categories.contains_key(&DebtCategory::Testing)
{
if let Some(arch) = categories.get(&DebtCategory::Architecture) {
let has_god_objects = arch.top_items.iter().any(|item| match item {
DebtItem::Function(func) => {
matches!(func.debt_type, DebtType::GodObject { .. })
}
DebtItem::File(file) => file
.metrics
.god_object_analysis
.as_ref()
.is_some_and(|a| a.is_god_object),
});
if has_god_objects {
dependencies.push(CrossCategoryDependency {
source_category: DebtCategory::Architecture,
target_category: DebtCategory::Testing,
description: "God objects and complex architectures make testing difficult. Refactor architecture first to enable effective testing.".to_string(),
impact_level: ImpactLevel::High,
});
}
}
}
if categories.contains_key(&DebtCategory::Performance)
&& categories.contains_key(&DebtCategory::Architecture)
{
if let Some(perf) = categories.get(&DebtCategory::Performance) {
let has_async_issues = perf.top_items.iter().any(|item| match item {
DebtItem::Function(func) => {
matches!(func.debt_type, DebtType::AsyncMisuse { .. })
}
_ => false,
});
if has_async_issues {
dependencies.push(CrossCategoryDependency {
source_category: DebtCategory::Performance,
target_category: DebtCategory::Architecture,
description: "Async performance issues may require architectural refactoring for proper async/await patterns.".to_string(),
impact_level: ImpactLevel::Medium,
});
}
}
}
if categories.contains_key(&DebtCategory::CodeQuality)
&& categories.contains_key(&DebtCategory::Testing)
{
if let Some(quality) = categories.get(&DebtCategory::CodeQuality) {
if quality.average_severity >= 70.0 {
dependencies.push(CrossCategoryDependency {
source_category: DebtCategory::CodeQuality,
target_category: DebtCategory::Testing,
description: "High complexity code is harder to test effectively. Simplify code first for better test coverage.".to_string(),
impact_level: ImpactLevel::Medium,
});
}
}
}
dependencies
}
#[cfg(test)]
mod tests {
use super::*;
use crate::priority::unified_scorer::{Location, UnifiedScore};
use crate::priority::{ActionableRecommendation, ImpactMetrics};
use std::path::PathBuf;
#[test]
fn test_score_based_sorting() {
use crate::priority::semantic_classifier::FunctionRole;
let item1 = DebtItem::Function(Box::new(UnifiedDebtItem {
location: Location {
file: PathBuf::from("test.rs"),
function: "high_score".to_string(),
line: 1,
},
debt_type: DebtType::ComplexityHotspot {
cyclomatic: 40,
cognitive: 50,
},
unified_score: UnifiedScore {
complexity_factor: 5.0,
coverage_factor: 5.0,
dependency_factor: 5.0,
role_multiplier: 1.0,
final_score: 50.0,
base_score: Some(50.0),
exponential_factor: Some(1.0),
risk_boost: Some(1.0),
pre_adjustment_score: None,
adjustment_applied: None,
purity_factor: None,
refactorability_factor: None,
pattern_factor: None,
debt_adjustment: None,
pre_normalization_score: None,
structural_multiplier: Some(1.0),
has_coverage_data: false,
contextual_risk_multiplier: None,
pre_contextual_score: None,
debt_type_multiplier: None,
},
function_role: FunctionRole::PureLogic,
recommendation: ActionableRecommendation {
primary_action: "Test".to_string(),
rationale: "Test".to_string(),
implementation_steps: vec![],
related_items: vec![],
steps: None,
estimated_effort_hours: None,
},
expected_impact: ImpactMetrics {
coverage_improvement: 0.0,
lines_reduction: 0,
complexity_reduction: 0.0,
risk_reduction: 0.0,
},
transitive_coverage: None,
upstream_dependencies: 0,
downstream_dependencies: 0,
upstream_callers: vec![],
downstream_callees: vec![],
upstream_production_callers: vec![],
upstream_test_callers: vec![],
production_blast_radius: 0,
nesting_depth: 1,
function_length: 10,
cyclomatic_complexity: 10,
cognitive_complexity: 10,
is_pure: None,
purity_confidence: None,
purity_level: None,
god_object_indicators: None,
tier: None,
function_context: None,
context_confidence: None,
contextual_recommendation: None,
pattern_analysis: None,
file_context: None,
context_multiplier: None,
context_type: None,
language_specific: None, detected_pattern: None,
contextual_risk: None, file_line_count: None,
responsibility_category: None,
error_swallowing_count: None,
error_swallowing_patterns: None,
entropy_analysis: None,
context_suggestion: None,
}));
let item2 = DebtItem::Function(Box::new(UnifiedDebtItem {
location: Location {
file: PathBuf::from("test.rs"),
function: "low_score".to_string(),
line: 10,
},
debt_type: DebtType::TestingGap {
coverage: 0.0,
cyclomatic: 5,
cognitive: 5,
},
unified_score: UnifiedScore {
complexity_factor: 2.0,
coverage_factor: 2.0,
dependency_factor: 2.0,
role_multiplier: 1.0,
final_score: 10.0,
base_score: Some(10.0),
exponential_factor: Some(1.0),
risk_boost: Some(1.0),
pre_adjustment_score: None,
adjustment_applied: None,
purity_factor: None,
refactorability_factor: None,
pattern_factor: None,
debt_adjustment: None,
pre_normalization_score: None,
structural_multiplier: Some(1.0),
has_coverage_data: false,
contextual_risk_multiplier: None,
pre_contextual_score: None,
debt_type_multiplier: None,
},
function_role: FunctionRole::PureLogic,
recommendation: ActionableRecommendation {
primary_action: "Test".to_string(),
rationale: "Test".to_string(),
implementation_steps: vec![],
related_items: vec![],
steps: None,
estimated_effort_hours: None,
},
expected_impact: ImpactMetrics {
coverage_improvement: 0.0,
lines_reduction: 0,
complexity_reduction: 0.0,
risk_reduction: 0.0,
},
transitive_coverage: None,
upstream_dependencies: 0,
downstream_dependencies: 0,
upstream_callers: vec![],
downstream_callees: vec![],
upstream_production_callers: vec![],
upstream_test_callers: vec![],
production_blast_radius: 0,
nesting_depth: 1,
function_length: 5,
cyclomatic_complexity: 5,
cognitive_complexity: 5,
is_pure: None,
purity_confidence: None,
purity_level: None,
god_object_indicators: None,
tier: None,
function_context: None,
context_confidence: None,
contextual_recommendation: None,
pattern_analysis: None,
file_context: None,
context_multiplier: None,
context_type: None,
language_specific: None, detected_pattern: None,
contextual_risk: None, file_line_count: None,
responsibility_category: None,
error_swallowing_count: None,
error_swallowing_patterns: None,
entropy_analysis: None,
context_suggestion: None,
}));
let mut items = [item2.clone(), item1.clone()];
items.sort_by(|a, b| {
b.score()
.partial_cmp(&a.score())
.unwrap_or(std::cmp::Ordering::Equal)
});
assert!(items[0].score() > items[1].score());
assert_eq!(items[0].score(), 50.0);
assert_eq!(items[1].score(), 10.0);
}
#[test]
fn test_get_minimum_score_threshold_with_env_override() {
std::env::remove_var("DEBTMAP_MIN_SCORE_THRESHOLD");
let default_threshold = crate::config::get_minimum_score_threshold();
assert_eq!(default_threshold, 3.0);
std::env::set_var("DEBTMAP_MIN_SCORE_THRESHOLD", "5.0");
let env_threshold = crate::config::get_minimum_score_threshold();
assert_eq!(env_threshold, 5.0);
std::env::set_var("DEBTMAP_MIN_SCORE_THRESHOLD", "0.0");
let zero_threshold = crate::config::get_minimum_score_threshold();
assert_eq!(zero_threshold, 0.0);
std::env::remove_var("DEBTMAP_MIN_SCORE_THRESHOLD");
}
use crate::priority::debt_types::FunctionVisibility;
use crate::priority::semantic_classifier::FunctionRole;
use crate::priority::CallGraph;
fn create_test_unified_item(name: &str, score: f64, debt_type: DebtType) -> UnifiedDebtItem {
UnifiedDebtItem {
location: Location {
file: PathBuf::from("test.rs"),
function: name.to_string(),
line: 1,
},
debt_type,
unified_score: UnifiedScore {
complexity_factor: 5.0,
coverage_factor: 5.0,
dependency_factor: 5.0,
role_multiplier: 1.0,
final_score: score,
base_score: Some(score),
exponential_factor: Some(1.0),
risk_boost: Some(1.0),
pre_adjustment_score: None,
adjustment_applied: None,
purity_factor: None,
refactorability_factor: None,
pattern_factor: None,
debt_adjustment: None,
pre_normalization_score: None,
structural_multiplier: Some(1.0),
has_coverage_data: false,
contextual_risk_multiplier: None,
pre_contextual_score: None,
debt_type_multiplier: None,
},
function_role: FunctionRole::PureLogic,
recommendation: ActionableRecommendation {
primary_action: "Test".to_string(),
rationale: "Test".to_string(),
implementation_steps: vec![],
related_items: vec![],
steps: None,
estimated_effort_hours: None,
},
expected_impact: ImpactMetrics {
coverage_improvement: 0.0,
lines_reduction: 0,
complexity_reduction: 0.0,
risk_reduction: 0.0,
},
transitive_coverage: None,
upstream_dependencies: 0,
downstream_dependencies: 0,
upstream_callers: vec![],
downstream_callees: vec![],
upstream_production_callers: vec![],
upstream_test_callers: vec![],
production_blast_radius: 0,
nesting_depth: 1,
function_length: 10,
cyclomatic_complexity: 10,
cognitive_complexity: 10,
is_pure: None,
purity_confidence: None,
purity_level: None,
god_object_indicators: None,
tier: None,
function_context: None,
context_confidence: None,
contextual_recommendation: None,
pattern_analysis: None,
file_context: None,
context_multiplier: None,
context_type: None,
language_specific: None,
detected_pattern: None,
contextual_risk: None,
file_line_count: None,
responsibility_category: None,
error_swallowing_count: None,
error_swallowing_patterns: None,
entropy_analysis: None,
context_suggestion: None,
}
}
fn create_empty_analysis() -> UnifiedAnalysis {
UnifiedAnalysis::new(CallGraph::new())
}
fn create_analysis_with_items(items: Vec<UnifiedDebtItem>) -> UnifiedAnalysis {
let mut analysis = create_empty_analysis();
for item in items {
analysis.items.push_back(item);
}
analysis
}
#[test]
fn test_get_tiered_display_empty_analysis() {
let analysis = create_empty_analysis();
let display = analysis.get_tiered_display(10);
assert!(display.critical.is_empty());
assert!(display.high.is_empty());
assert!(display.moderate.is_empty());
assert!(display.low.is_empty());
}
#[test]
fn test_get_tiered_display_single_critical_item() {
let item = create_test_unified_item(
"critical_func",
92.0,
DebtType::ComplexityHotspot {
cyclomatic: 50,
cognitive: 60,
},
);
let analysis = create_analysis_with_items(vec![item]);
let display = analysis.get_tiered_display(10);
assert_eq!(display.critical.len(), 1);
assert!(display.high.is_empty());
assert!(display.moderate.is_empty());
assert!(display.low.is_empty());
assert_eq!(display.critical[0].items.len(), 1);
assert_eq!(display.critical[0].debt_type, "High Complexity Functions");
}
#[test]
fn test_get_tiered_display_single_high_item() {
let item = create_test_unified_item(
"high_func",
75.0,
DebtType::TestingGap {
coverage: 0.0,
cyclomatic: 20,
cognitive: 25,
},
);
let analysis = create_analysis_with_items(vec![item]);
let display = analysis.get_tiered_display(10);
assert!(display.critical.is_empty());
assert_eq!(display.high.len(), 1);
assert!(display.moderate.is_empty());
assert!(display.low.is_empty());
assert_eq!(display.high[0].debt_type, "Untested Complex Functions");
}
#[test]
fn test_get_tiered_display_single_moderate_item() {
let item = create_test_unified_item(
"moderate_func",
55.0,
DebtType::Duplication {
instances: 2,
total_lines: 50,
},
);
let analysis = create_analysis_with_items(vec![item]);
let display = analysis.get_tiered_display(10);
assert!(display.critical.is_empty());
assert!(display.high.is_empty());
assert_eq!(display.moderate.len(), 1);
assert!(display.low.is_empty());
assert_eq!(display.moderate[0].debt_type, "Code Duplication");
}
#[test]
fn test_get_tiered_display_single_low_item() {
let item = create_test_unified_item(
"low_func",
30.0,
DebtType::DeadCode {
visibility: FunctionVisibility::Public,
cyclomatic: 5,
cognitive: 5,
usage_hints: vec![],
},
);
let analysis = create_analysis_with_items(vec![item]);
let display = analysis.get_tiered_display(10);
assert!(display.critical.is_empty());
assert!(display.high.is_empty());
assert!(display.moderate.is_empty());
assert_eq!(display.low.len(), 1);
assert_eq!(display.low[0].debt_type, "Dead Code");
}
#[test]
fn test_get_tiered_display_groups_similar_items() {
let item1 = create_test_unified_item(
"func1",
55.0,
DebtType::ComplexityHotspot {
cyclomatic: 20,
cognitive: 25,
},
);
let item2 = create_test_unified_item(
"func2",
52.0,
DebtType::ComplexityHotspot {
cyclomatic: 18,
cognitive: 22,
},
);
let analysis = create_analysis_with_items(vec![item1, item2]);
let display = analysis.get_tiered_display(10);
assert_eq!(display.moderate.len(), 1); assert_eq!(display.moderate[0].items.len(), 2);
assert_eq!(display.moderate[0].debt_type, "High Complexity Functions");
assert!(display.moderate[0].batch_action.is_some());
assert!(display.moderate[0]
.batch_action
.as_ref()
.unwrap()
.contains("2"));
}
#[test]
fn test_get_tiered_display_critical_items_not_grouped() {
let item1 = create_test_unified_item(
"critical1",
96.0,
DebtType::ComplexityHotspot {
cyclomatic: 100,
cognitive: 120,
},
);
let item2 = create_test_unified_item(
"critical2",
97.0,
DebtType::ComplexityHotspot {
cyclomatic: 110,
cognitive: 130,
},
);
let analysis = create_analysis_with_items(vec![item1, item2]);
let display = analysis.get_tiered_display(10);
assert_eq!(display.critical.len(), 2);
assert_eq!(display.critical[0].items.len(), 1);
assert_eq!(display.critical[1].items.len(), 1);
}
#[test]
fn test_get_tiered_display_god_objects_not_grouped() {
let item1 = create_test_unified_item(
"god1",
75.0,
DebtType::GodObject {
methods: 50,
fields: Some(30),
responsibilities: 5,
god_object_score: 85.0,
lines: 2000,
},
);
let item2 = create_test_unified_item(
"god2",
72.0,
DebtType::GodObject {
methods: 45,
fields: Some(25),
responsibilities: 4,
god_object_score: 80.0,
lines: 1800,
},
);
let analysis = create_analysis_with_items(vec![item1, item2]);
let display = analysis.get_tiered_display(10);
assert_eq!(display.high.len(), 2);
assert_eq!(display.high[0].items.len(), 1);
assert_eq!(display.high[1].items.len(), 1);
}
#[test]
fn test_get_tiered_display_sorting_within_tier() {
let item1 = create_test_unified_item(
"low_score",
52.0,
DebtType::DeadCode {
visibility: FunctionVisibility::Public,
cyclomatic: 5,
cognitive: 5,
usage_hints: vec![],
},
);
let item2 = create_test_unified_item(
"high_score",
68.0,
DebtType::ComplexityHotspot {
cyclomatic: 30,
cognitive: 35,
},
);
let analysis = create_analysis_with_items(vec![item1, item2]);
let display = analysis.get_tiered_display(10);
assert_eq!(display.moderate.len(), 2);
let first_total: f64 = display.moderate[0].items.iter().map(|i| i.score()).sum();
let second_total: f64 = display.moderate[1].items.iter().map(|i| i.score()).sum();
assert!(first_total >= second_total);
}
#[test]
fn test_get_tiered_display_items_across_all_tiers() {
let critical = create_test_unified_item(
"critical",
92.0,
DebtType::ComplexityHotspot {
cyclomatic: 50,
cognitive: 60,
},
);
let high = create_test_unified_item(
"high",
75.0,
DebtType::TestingGap {
coverage: 0.0,
cyclomatic: 20,
cognitive: 25,
},
);
let moderate = create_test_unified_item(
"moderate",
55.0,
DebtType::Duplication {
instances: 2,
total_lines: 50,
},
);
let low = create_test_unified_item(
"low",
30.0,
DebtType::DeadCode {
visibility: FunctionVisibility::Public,
cyclomatic: 5,
cognitive: 5,
usage_hints: vec![],
},
);
let analysis = create_analysis_with_items(vec![critical, high, moderate, low]);
let display = analysis.get_tiered_display(10);
assert_eq!(display.critical.len(), 1);
assert_eq!(display.high.len(), 1);
assert_eq!(display.moderate.len(), 1);
assert_eq!(display.low.len(), 1);
}
#[test]
fn test_get_tiered_display_respects_limit() {
let items: Vec<UnifiedDebtItem> = (0..10)
.map(|i| {
create_test_unified_item(
&format!("func_{}", i),
90.0 + (i as f64 * 0.5), DebtType::ComplexityHotspot {
cyclomatic: 50 + i,
cognitive: 60 + i,
},
)
})
.collect();
let analysis = create_analysis_with_items(items);
let display = analysis.get_tiered_display(5);
let total_items: usize = display
.critical
.iter()
.chain(display.high.iter())
.chain(display.moderate.iter())
.chain(display.low.iter())
.map(|g| g.items.len())
.sum();
assert_eq!(total_items, 5);
}
#[test]
fn test_get_tiered_display_batch_actions() {
let items: Vec<UnifiedDebtItem> = (0..3)
.map(|i| {
create_test_unified_item(
&format!("untested_{}", i),
55.0 + (i as f64),
DebtType::TestingGap {
coverage: 0.0,
cyclomatic: 10 + i,
cognitive: 15 + i,
},
)
})
.collect();
let analysis = create_analysis_with_items(items);
let display = analysis.get_tiered_display(10);
let untested_group = display
.moderate
.iter()
.find(|g| g.debt_type == "Untested Complex Functions");
assert!(untested_group.is_some());
let group = untested_group.unwrap();
assert_eq!(group.items.len(), 3);
assert!(group.batch_action.is_some());
assert!(group.batch_action.as_ref().unwrap().contains("3"));
assert!(group
.batch_action
.as_ref()
.unwrap()
.contains("test coverage"));
}
#[test]
fn test_get_tiered_display_no_batch_action_for_single_item() {
let item = create_test_unified_item(
"single",
55.0,
DebtType::Duplication {
instances: 2,
total_lines: 50,
},
);
let analysis = create_analysis_with_items(vec![item]);
let display = analysis.get_tiered_display(10);
assert_eq!(display.moderate.len(), 1);
assert!(display.moderate[0].batch_action.is_none());
}
#[test]
fn test_partition_by_criticality_separates_critical_items() {
let critical = create_test_unified_item(
"critical",
96.0, DebtType::ComplexityHotspot {
cyclomatic: 50,
cognitive: 60,
},
);
let normal = create_test_unified_item(
"normal",
60.0,
DebtType::ComplexityHotspot {
cyclomatic: 20,
cognitive: 25,
},
);
let items: im::Vector<DebtItem> = vec![
DebtItem::Function(Box::new(critical)),
DebtItem::Function(Box::new(normal)),
]
.into_iter()
.collect();
let (critical_items, groupable_items) = super::partition_by_criticality(items);
assert_eq!(critical_items.len(), 1);
assert_eq!(groupable_items.len(), 1);
assert_eq!(critical_items[0].score(), 96.0);
assert_eq!(groupable_items[0].score(), 60.0);
}
#[test]
fn test_partition_by_criticality_god_objects_always_critical() {
let god_object = create_test_unified_item(
"god_object",
50.0, DebtType::GodObject {
methods: 100,
fields: Some(50),
responsibilities: 10,
god_object_score: 85.0,
lines: 2000,
},
);
let items: im::Vector<DebtItem> = vec![DebtItem::Function(Box::new(god_object))]
.into_iter()
.collect();
let (critical_items, groupable_items) = super::partition_by_criticality(items);
assert_eq!(critical_items.len(), 1);
assert_eq!(groupable_items.len(), 0);
}
#[test]
fn test_group_items_by_tier_and_type_groups_correctly() {
let item1 = create_test_unified_item(
"func1",
75.0, DebtType::ComplexityHotspot {
cyclomatic: 30,
cognitive: 35,
},
);
let item2 = create_test_unified_item(
"func2",
78.0, DebtType::ComplexityHotspot {
cyclomatic: 32,
cognitive: 38,
},
);
let item3 = create_test_unified_item(
"func3",
55.0, DebtType::TestingGap {
coverage: 0.2,
cyclomatic: 15,
cognitive: 18,
},
);
let items = vec![
DebtItem::Function(Box::new(item1)),
DebtItem::Function(Box::new(item2)),
DebtItem::Function(Box::new(item3)),
];
let groups = super::group_items_by_tier_and_type(items);
assert_eq!(groups.len(), 2);
let high_complexity_key = (Tier::High, "High Complexity Functions".to_string());
assert!(groups.contains_key(&high_complexity_key));
assert_eq!(groups[&high_complexity_key].len(), 2);
let moderate_testing_key = (Tier::Moderate, "Untested Complex Functions".to_string());
assert!(groups.contains_key(&moderate_testing_key));
assert_eq!(groups[&moderate_testing_key].len(), 1);
}
#[test]
fn test_sort_groups_by_score_descending() {
let item1 = create_test_unified_item(
"low",
30.0,
DebtType::ComplexityHotspot {
cyclomatic: 10,
cognitive: 12,
},
);
let item2 = create_test_unified_item(
"high",
80.0,
DebtType::ComplexityHotspot {
cyclomatic: 40,
cognitive: 50,
},
);
let mut groups = vec![
DisplayGroup {
tier: Tier::Low,
debt_type: "Low Score".to_string(),
items: vec![DebtItem::Function(Box::new(item1))],
batch_action: None,
},
DisplayGroup {
tier: Tier::High,
debt_type: "High Score".to_string(),
items: vec![DebtItem::Function(Box::new(item2))],
batch_action: None,
},
];
super::sort_groups_by_score(&mut groups);
assert_eq!(groups[0].debt_type, "High Score");
assert_eq!(groups[1].debt_type, "Low Score");
}
#[test]
fn test_create_critical_item_group_single_item_no_batch() {
let item = create_test_unified_item(
"critical",
96.0,
DebtType::ComplexityHotspot {
cyclomatic: 50,
cognitive: 60,
},
);
let group = super::create_critical_item_group(DebtItem::Function(Box::new(item)));
assert_eq!(group.items.len(), 1);
assert!(group.batch_action.is_none());
assert_eq!(group.tier, Tier::Critical);
}
#[test]
fn test_create_grouped_display_group_with_batch_action() {
let item1 = create_test_unified_item(
"func1",
75.0,
DebtType::TestingGap {
coverage: 0.1,
cyclomatic: 20,
cognitive: 25,
},
);
let item2 = create_test_unified_item(
"func2",
72.0,
DebtType::TestingGap {
coverage: 0.2,
cyclomatic: 18,
cognitive: 22,
},
);
let items = vec![
DebtItem::Function(Box::new(item1)),
DebtItem::Function(Box::new(item2)),
];
let group = super::create_grouped_display_group(
Tier::High,
"Untested Complex Functions".to_string(),
items,
);
assert_eq!(group.items.len(), 2);
assert!(group.batch_action.is_some());
assert_eq!(
group.batch_action.unwrap(),
"Add test coverage for 2 complex functions"
);
}
#[test]
fn test_create_grouped_display_group_single_item_no_batch() {
let item = create_test_unified_item(
"func1",
75.0,
DebtType::TestingGap {
coverage: 0.1,
cyclomatic: 20,
cognitive: 25,
},
);
let items = vec![DebtItem::Function(Box::new(item))];
let group = super::create_grouped_display_group(
Tier::High,
"Untested Complex Functions".to_string(),
items,
);
assert_eq!(group.items.len(), 1);
assert!(group.batch_action.is_none());
}
#[test]
fn test_tier_groups_into_tiered_display_sorts_all_tiers() {
let critical_item = create_test_unified_item(
"critical",
92.0,
DebtType::ComplexityHotspot {
cyclomatic: 45,
cognitive: 55,
},
);
let high_item = create_test_unified_item(
"high",
75.0,
DebtType::ComplexityHotspot {
cyclomatic: 30,
cognitive: 35,
},
);
let mut tier_groups = super::TierGroups::default();
tier_groups.add_group(DisplayGroup {
tier: Tier::Critical,
debt_type: "Test".to_string(),
items: vec![DebtItem::Function(Box::new(critical_item))],
batch_action: None,
});
tier_groups.add_group(DisplayGroup {
tier: Tier::High,
debt_type: "Test".to_string(),
items: vec![DebtItem::Function(Box::new(high_item))],
batch_action: None,
});
let display = tier_groups.into_tiered_display();
assert_eq!(display.critical.len(), 1);
assert_eq!(display.high.len(), 1);
assert_eq!(display.moderate.len(), 0);
assert_eq!(display.low.len(), 0);
}
}