use super::*;
use crate::priority::unified_scorer::Location;
use crate::priority::{
file_metrics::{FileDebtItem, FileDebtMetrics, FileImpact},
semantic_classifier::FunctionRole,
tiers::RecommendationTier,
view::{SortCriteria, ViewConfig},
ActionableRecommendation, DebtType, ImpactMetrics, UnifiedScore,
};
use std::path::PathBuf;
fn create_test_function_item(score: f64) -> UnifiedDebtItem {
create_test_function_item_at("test.rs", "test_fn", 10, score)
}
fn create_test_function_item_at(
file: &str,
func: &str,
line: usize,
score: f64,
) -> UnifiedDebtItem {
UnifiedDebtItem {
location: Location {
file: file.into(),
function: func.into(),
line,
},
debt_type: DebtType::TestingGap {
coverage: 0.0,
cyclomatic: 10,
cognitive: 15,
},
unified_score: UnifiedScore {
complexity_factor: 5.0,
coverage_factor: 5.0,
dependency_factor: 2.0,
role_multiplier: 1.0,
final_score: score.max(0.0),
base_score: None,
exponential_factor: None,
risk_boost: None,
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: "Add tests".into(),
rationale: "Improve coverage".into(),
implementation_steps: vec![],
related_items: vec![],
steps: None,
estimated_effort_hours: None,
},
expected_impact: ImpactMetrics {
risk_reduction: 0.0,
complexity_reduction: 0.0,
coverage_improvement: 20.0,
lines_reduction: 0,
},
transitive_coverage: None,
file_context: None,
upstream_dependencies: 3,
downstream_dependencies: 2,
upstream_callers: vec![],
downstream_callees: vec![],
upstream_production_callers: vec![],
upstream_test_callers: vec![],
production_blast_radius: 0,
nesting_depth: 2,
function_length: 50,
cyclomatic_complexity: 10,
cognitive_complexity: 15,
is_pure: Some(false),
purity_confidence: Some(0.8),
purity_level: None,
god_object_indicators: None,
tier: None,
function_context: None,
context_confidence: None,
contextual_recommendation: None,
pattern_analysis: 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_test_file_item(score: f64) -> FileDebtItem {
FileDebtItem {
metrics: FileDebtMetrics {
path: "test_file.rs".into(),
total_lines: 500,
function_count: 20,
class_count: 1,
avg_complexity: 8.0,
max_complexity: 25,
total_complexity: 160,
coverage_percent: 0.3,
uncovered_lines: 350,
god_object_analysis: None,
function_scores: vec![5.0; 20],
god_object_type: None,
file_type: None,
..Default::default()
},
score,
priority_rank: 1,
recommendation: "Split into multiple modules".into(),
impact: FileImpact {
complexity_reduction: 50.0,
maintainability_improvement: 30.0,
test_effort: 20.0,
},
}
}
fn create_view_item(score: f64) -> ViewItem {
ViewItem::Function(Box::new(create_test_function_item(score)))
}
fn create_view_item_with_tier(score: f64, tier: RecommendationTier) -> ViewItem {
let mut item = create_test_function_item(score);
item.tier = Some(tier);
ViewItem::Function(Box::new(item))
}
fn create_view_item_at(file: &str, func: &str, line: usize, score: f64) -> ViewItem {
ViewItem::Function(Box::new(create_test_function_item_at(
file, func, line, score,
)))
}
#[test]
fn test_combine_items_preserves_all() {
let functions: im::Vector<UnifiedDebtItem> = (0..3)
.map(|i| create_test_function_item(50.0 + i as f64))
.collect();
let files: im::Vector<FileDebtItem> = (0..2)
.map(|i| create_test_file_item(60.0 + i as f64))
.collect();
let combined = combine_items(&functions, &files);
assert_eq!(combined.len(), 5);
}
#[test]
fn test_combine_items_empty() {
let combined = combine_items(&im::Vector::new(), &im::Vector::new());
assert!(combined.is_empty());
}
#[test]
fn test_combine_items_only_functions() {
let functions: im::Vector<UnifiedDebtItem> = (0..3)
.map(|i| create_test_function_item(50.0 + i as f64))
.collect();
let files: im::Vector<FileDebtItem> = im::Vector::new();
let combined = combine_items(&functions, &files);
assert_eq!(combined.len(), 3);
assert!(combined
.iter()
.all(|item| matches!(item, ViewItem::Function(_))));
}
#[test]
fn test_combine_items_only_files() {
let functions: im::Vector<UnifiedDebtItem> = im::Vector::new();
let files: im::Vector<FileDebtItem> = (0..2)
.map(|i| create_test_file_item(60.0 + i as f64))
.collect();
let combined = combine_items(&functions, &files);
assert_eq!(combined.len(), 2);
assert!(combined
.iter()
.all(|item| matches!(item, ViewItem::File(_))));
}
#[test]
fn test_filter_by_score_threshold() {
let items = vec![
create_view_item(10.0), create_view_item(2.0), create_view_item(5.0), ];
let config = ViewConfig {
min_score_threshold: 3.0,
..Default::default()
};
let (filtered, stats) = filter::filter_items(items, &config);
assert_eq!(filtered.len(), 2);
assert_eq!(stats.filtered_by_score, 1);
}
#[test]
fn test_filter_by_tier() {
let items = vec![
create_view_item_with_tier(50.0, RecommendationTier::T1CriticalArchitecture),
create_view_item_with_tier(30.0, RecommendationTier::T4Maintenance),
create_view_item_with_tier(40.0, RecommendationTier::T2ComplexUntested),
];
let config = ViewConfig {
min_score_threshold: 0.0,
exclude_t4_maintenance: true,
..Default::default()
};
let (filtered, stats) = filter::filter_items(items, &config);
assert_eq!(filtered.len(), 2);
assert_eq!(stats.filtered_by_tier, 1);
}
#[test]
fn test_filter_no_exclusions() {
let items = vec![
create_view_item_with_tier(50.0, RecommendationTier::T1CriticalArchitecture),
create_view_item_with_tier(30.0, RecommendationTier::T4Maintenance),
];
let config = ViewConfig {
min_score_threshold: 0.0,
exclude_t4_maintenance: false,
..Default::default()
};
let (filtered, stats) = filter::filter_items(items, &config);
assert_eq!(filtered.len(), 2);
assert_eq!(stats.filtered_by_tier, 0);
assert_eq!(stats.filtered_by_score, 0);
}
#[test]
fn test_sort_by_score_descending() {
let items = vec![
create_view_item(30.0),
create_view_item(50.0),
create_view_item(10.0),
];
let sorted = sort::sort_items(items, SortCriteria::Score);
assert_eq!(sorted[0].score(), 50.0);
assert_eq!(sorted[1].score(), 30.0);
assert_eq!(sorted[2].score(), 10.0);
}
#[test]
fn test_sort_by_file_path() {
let items = vec![
create_view_item_at("z_file.rs", "fn1", 10, 50.0),
create_view_item_at("a_file.rs", "fn2", 10, 30.0),
create_view_item_at("m_file.rs", "fn3", 10, 40.0),
];
let sorted = sort::sort_items(items, SortCriteria::FilePath);
assert_eq!(sorted[0].location().file, PathBuf::from("a_file.rs"));
assert_eq!(sorted[1].location().file, PathBuf::from("m_file.rs"));
assert_eq!(sorted[2].location().file, PathBuf::from("z_file.rs"));
}
#[test]
fn test_sort_by_function_name() {
let items = vec![
create_view_item_at("file.rs", "zebra", 10, 50.0),
create_view_item_at("file.rs", "alpha", 20, 30.0),
create_view_item_at("file.rs", "monkey", 30, 40.0),
];
let sorted = sort::sort_items(items, SortCriteria::FunctionName);
assert_eq!(sorted[0].location().function, Some("alpha".to_string()));
assert_eq!(sorted[1].location().function, Some("monkey".to_string()));
assert_eq!(sorted[2].location().function, Some("zebra".to_string()));
}
#[test]
fn test_sort_preserves_count() {
let items = vec![
create_view_item(30.0),
create_view_item(50.0),
create_view_item(10.0),
];
let original_count = items.len();
let sorted = sort::sort_items(items, SortCriteria::Score);
assert_eq!(sorted.len(), original_count);
}
#[test]
fn test_limit_items() {
let items = vec![
create_view_item(50.0),
create_view_item(40.0),
create_view_item(30.0),
create_view_item(20.0),
];
let limited = limit_items(items, Some(2));
assert_eq!(limited.len(), 2);
}
#[test]
fn test_limit_none_returns_all() {
let items = vec![create_view_item(50.0), create_view_item(40.0)];
let limited = limit_items(items, None);
assert_eq!(limited.len(), 2);
}
#[test]
fn test_limit_greater_than_count() {
let items = vec![create_view_item(50.0), create_view_item(40.0)];
let limited = limit_items(items, Some(10));
assert_eq!(limited.len(), 2);
}
#[test]
fn test_limit_zero() {
let items = vec![create_view_item(50.0), create_view_item(40.0)];
let limited = limit_items(items, Some(0));
assert!(limited.is_empty());
}
#[test]
fn test_compute_groups_combines_same_location() {
let items = vec![
create_view_item_at("file.rs", "func", 10, 30.0),
create_view_item_at("file.rs", "func", 10, 20.0),
create_view_item_at("other.rs", "func", 10, 50.0),
];
let groups = group::compute_groups(&items, SortCriteria::Score);
assert_eq!(groups.len(), 2);
let total_combined: f64 = groups.iter().map(|g| g.combined_score).sum();
assert_eq!(total_combined, 100.0);
}
#[test]
fn test_compute_groups_empty() {
let groups = group::compute_groups(&[], SortCriteria::Score);
assert!(groups.is_empty());
}
#[test]
fn test_compute_groups_single_item() {
let items = vec![create_view_item_at("file.rs", "func", 10, 50.0)];
let groups = group::compute_groups(&items, SortCriteria::Score);
assert_eq!(groups.len(), 1);
assert_eq!(groups[0].combined_score, 50.0);
assert_eq!(groups[0].item_count, 1);
}
#[test]
fn test_calculate_summary_scores() {
let items = vec![
create_view_item(85.0), create_view_item(60.0), create_view_item(40.0), create_view_item(20.0), ];
let filter_stats = filter::FilterStats::default();
let sum = summary::calculate_summary(&items, 10, filter_stats, 1000, Some(0.5));
assert_eq!(sum.total_items_before_filter, 10);
assert_eq!(sum.total_items_after_filter, 4);
assert_eq!(sum.total_debt_score, 205.0);
assert_eq!(sum.total_lines_of_code, 1000);
assert_eq!(sum.overall_coverage, Some(0.5));
}
#[test]
fn test_debt_density_calculation() {
let items = vec![create_view_item(100.0)];
let filter_stats = filter::FilterStats::default();
let sum = summary::calculate_summary(&items, 1, filter_stats, 1000, None);
assert_eq!(sum.debt_density, 100.0);
}
#[test]
fn test_debt_density_zero_loc() {
let items = vec![create_view_item(100.0)];
let filter_stats = filter::FilterStats::default();
let sum = summary::calculate_summary(&items, 1, filter_stats, 0, None);
assert_eq!(sum.debt_density, 0.0);
}
#[test]
fn test_prepare_view_deterministic() {
use crate::priority::call_graph::CallGraph;
let call_graph = CallGraph::new();
let mut analysis = UnifiedAnalysis::new(call_graph);
analysis.items = (0..5)
.map(|i| create_test_function_item(50.0 + i as f64 * 10.0))
.collect();
let config = ViewConfig::default();
let tier_config = TierConfig::default();
let view1 = prepare_view(&analysis, &config, &tier_config);
let view2 = prepare_view(&analysis, &config, &tier_config);
assert_eq!(view1.items.len(), view2.items.len());
for (a, b) in view1.items.iter().zip(view2.items.iter()) {
assert_eq!(a.score(), b.score());
}
}
#[test]
fn test_prepare_view_for_tui() {
use crate::priority::call_graph::CallGraph;
let call_graph = CallGraph::new();
let mut analysis = UnifiedAnalysis::new(call_graph);
analysis.items = (0..3)
.map(|i| create_test_function_item(50.0 + i as f64 * 10.0))
.collect();
let view = prepare_view_for_tui(&analysis);
assert!(view.summary.filtered_by_score == 0);
assert!(view.config.compute_groups);
}
#[test]
fn test_prepare_view_for_terminal() {
use crate::priority::call_graph::CallGraph;
let call_graph = CallGraph::new();
let mut analysis = UnifiedAnalysis::new(call_graph);
analysis.items = (0..10)
.map(|i| create_test_function_item(1.0 + i as f64 * 2.0))
.collect();
let view = prepare_view_for_terminal(&analysis, Some(5));
assert!(view.items.len() <= 5);
assert!(!view.config.compute_groups);
assert!(view.groups.is_empty());
}
#[test]
fn test_prepare_view_empty_analysis() {
use crate::priority::call_graph::CallGraph;
let call_graph = CallGraph::new();
let analysis = UnifiedAnalysis::new(call_graph);
let view = prepare_view_default(&analysis);
assert!(view.is_empty());
assert_eq!(view.summary.total_items_before_filter, 0);
assert_eq!(view.summary.total_items_after_filter, 0);
}
#[test]
fn test_passes_score_threshold() {
let item = create_view_item(5.0);
assert!(filter::passes_score_threshold(&item, 3.0));
assert!(filter::passes_score_threshold(&item, 5.0));
assert!(!filter::passes_score_threshold(&item, 6.0));
}
#[test]
fn test_passes_tier_filter() {
let t1_item = create_view_item_with_tier(50.0, RecommendationTier::T1CriticalArchitecture);
let t4_item = create_view_item_with_tier(50.0, RecommendationTier::T4Maintenance);
assert!(filter::passes_tier_filter(&t1_item, false));
assert!(filter::passes_tier_filter(&t4_item, false));
assert!(filter::passes_tier_filter(&t1_item, true));
assert!(!filter::passes_tier_filter(&t4_item, true));
}
#[test]
fn test_get_coverage_function() {
let item = create_view_item(50.0);
assert!(sort::get_coverage(&item).is_none());
}
#[test]
fn test_get_coverage_file() {
let file_item = ViewItem::File(Box::new(create_test_file_item(50.0)));
let coverage = sort::get_coverage(&file_item);
assert!(coverage.is_some());
assert_eq!(coverage.unwrap(), 0.3); }
#[test]
fn test_get_complexity() {
let func_item = create_view_item(50.0);
let file_item = ViewItem::File(Box::new(create_test_file_item(50.0)));
assert_eq!(sort::get_complexity(&func_item), 15); assert_eq!(sort::get_complexity(&file_item), 25); }