use debtmap::{
progress::{ProgressConfig, ProgressManager},
tui::app::{App, StageStatus},
};
#[test]
fn test_tui_app_initialization() {
let app = App::new();
assert_eq!(app.stages.len(), 6);
assert_eq!(app.stages[0].name, "files parse");
assert_eq!(app.stages[1].name, "call graph");
assert_eq!(app.stages[2].name, "coverage");
assert_eq!(app.stages[3].name, "purity analysis");
assert_eq!(app.stages[4].name, "context");
assert_eq!(app.stages[5].name, "debt scoring");
for stage in &app.stages {
assert_eq!(stage.status, StageStatus::Pending);
assert!(stage.metric.is_none());
assert!(stage.elapsed.is_none());
}
assert_eq!(app.overall_progress, 0.0);
assert_eq!(app.current_stage, 0);
}
#[test]
fn test_tui_stage_lifecycle() {
let mut app = App::new();
app.start_stage(0);
assert_eq!(app.stages[0].status, StageStatus::Active);
assert_eq!(app.current_stage, 0);
app.complete_stage(0, "469 files");
assert_eq!(app.stages[0].status, StageStatus::Completed);
assert_eq!(app.stages[0].metric, Some("469 files".to_string()));
assert!(app.stages[0].elapsed.is_some());
app.start_stage(1);
assert_eq!(app.stages[1].status, StageStatus::Active);
assert_eq!(app.current_stage, 1);
assert_eq!(app.stages[0].status, StageStatus::Completed);
}
#[test]
fn test_tui_progress_updates() {
let mut app = App::new();
app.set_overall_progress(0.5);
assert_eq!(app.overall_progress, 0.5);
app.set_overall_progress(1.5);
assert_eq!(app.overall_progress, 1.0);
app.set_overall_progress(-0.5);
assert_eq!(app.overall_progress, 0.0);
}
#[test]
fn test_tui_subtask_updates() {
let mut app = App::new();
assert!(!app.stages[3].sub_tasks.is_empty());
app.update_subtask(3, 0, StageStatus::Completed, None);
assert_eq!(app.stages[3].sub_tasks[0].status, StageStatus::Completed);
app.update_subtask(3, 1, StageStatus::Active, Some((50, 100)));
assert_eq!(app.stages[3].sub_tasks[1].status, StageStatus::Active);
assert_eq!(app.stages[3].sub_tasks[1].progress, Some((50, 100)));
}
#[test]
fn test_tui_animation_tick() {
let mut app = App::new();
let initial_frame = app.animation_frame;
app.tick();
assert_eq!(app.animation_frame, (initial_frame + 1) % 60);
assert!(app.elapsed_time.as_nanos() > 0);
for _ in 0..59 {
app.tick();
}
assert_eq!(app.animation_frame, 0);
}
#[test]
fn test_tui_statistics_updates() {
let mut app = App::new();
assert_eq!(app.functions_count, 0);
assert_eq!(app.debt_count, 0);
assert_eq!(app.coverage_percent, 0.0);
app.update_stats(1234, 56, 78.5);
assert_eq!(app.functions_count, 1234);
assert_eq!(app.debt_count, 56);
assert_eq!(app.coverage_percent, 78.5);
}
#[test]
fn test_progress_manager_tty_detection() {
let config = ProgressConfig {
quiet_mode: true,
verbosity: 0,
};
let manager = ProgressManager::new(config);
let bar = manager.create_bar(100, "{msg}");
assert!(bar.is_hidden());
let spinner = manager.create_spinner("Test");
assert!(spinner.is_hidden());
}
#[test]
fn test_full_pipeline_simulation() {
let mut app = App::new();
app.start_stage(0);
app.set_overall_progress(0.0);
app.tick();
app.complete_stage(0, "469 files parsed");
app.set_overall_progress(0.22);
app.start_stage(1);
app.tick();
app.complete_stage(1, "5432 functions");
app.set_overall_progress(0.33);
app.start_stage(2);
app.tick();
app.complete_stage(2, "loaded");
app.set_overall_progress(0.55);
app.start_stage(3);
app.update_subtask(3, 0, StageStatus::Completed, None);
app.tick();
app.update_subtask(3, 1, StageStatus::Active, Some((50, 100)));
app.tick();
app.update_subtask(3, 1, StageStatus::Completed, None);
app.tick();
app.complete_stage(3, "5432 functions analyzed");
app.set_overall_progress(0.66);
app.start_stage(4);
app.tick();
app.complete_stage(4, "loaded");
app.set_overall_progress(0.77);
app.start_stage(5);
app.tick();
app.complete_stage(5, "123 items scored and prioritized");
app.set_overall_progress(1.0);
assert_eq!(app.overall_progress, 1.0);
assert_eq!(app.current_stage, 5);
for stage in &app.stages {
assert_eq!(stage.status, StageStatus::Completed);
assert!(stage.metric.is_some());
}
}
#[test]
fn test_stage_metric_updates() {
let mut app = App::new();
app.start_stage(0);
assert!(app.stages[0].metric.is_none());
app.update_stage_metric(0, "100 files");
assert_eq!(app.stages[0].metric, Some("100 files".to_string()));
app.update_stage_metric(0, "200 files");
assert_eq!(app.stages[0].metric, Some("200 files".to_string()));
}
#[test]
fn test_purity_analysis_subtasks() {
let app = App::new();
let purity_stage = &app.stages[3];
assert_eq!(purity_stage.name, "purity analysis");
assert_eq!(purity_stage.sub_tasks.len(), 4);
assert_eq!(purity_stage.sub_tasks[0].name, "data flow graph");
assert_eq!(purity_stage.sub_tasks[1].name, "initial detection");
assert_eq!(purity_stage.sub_tasks[2].name, "propagation");
assert_eq!(purity_stage.sub_tasks[3].name, "side effects");
for subtask in &purity_stage.sub_tasks {
assert_eq!(subtask.status, StageStatus::Pending);
assert!(subtask.progress.is_none());
}
}
#[test]
fn test_context_analysis_subtasks() {
let app = App::new();
let context_stage = &app.stages[4];
assert_eq!(context_stage.name, "context");
assert_eq!(context_stage.sub_tasks.len(), 3);
assert_eq!(context_stage.sub_tasks[0].name, "critical path");
assert_eq!(context_stage.sub_tasks[1].name, "dependencies");
assert_eq!(context_stage.sub_tasks[2].name, "git history");
for subtask in &context_stage.sub_tasks {
assert_eq!(subtask.status, StageStatus::Pending);
assert!(subtask.progress.is_none());
}
}
#[test]
fn test_context_subsections_when_disabled() {
let mut app = App::new();
assert_eq!(app.stages[4].sub_tasks.len(), 3);
app.start_stage(4);
assert_eq!(app.stages[4].status, StageStatus::Active);
app.complete_stage(4, "skipped");
assert_eq!(app.stages[4].status, StageStatus::Completed);
assert_eq!(app.stages[4].metric, Some("skipped".to_string()));
for subtask in &app.stages[4].sub_tasks {
assert_eq!(subtask.status, StageStatus::Pending);
assert!(subtask.progress.is_none());
}
}
#[test]
fn test_context_subsection_lifecycle() {
let mut app = App::new();
app.start_stage(4);
assert_eq!(app.stages[4].status, StageStatus::Active);
for subtask in &app.stages[4].sub_tasks {
assert_eq!(subtask.status, StageStatus::Pending);
}
app.update_subtask(4, 0, StageStatus::Active, None);
assert_eq!(app.stages[4].sub_tasks[0].status, StageStatus::Active);
assert_eq!(app.stages[4].sub_tasks[0].name, "critical path");
app.tick();
app.update_subtask(4, 0, StageStatus::Completed, None);
assert_eq!(app.stages[4].sub_tasks[0].status, StageStatus::Completed);
app.update_subtask(4, 1, StageStatus::Active, None);
assert_eq!(app.stages[4].sub_tasks[1].status, StageStatus::Active);
assert_eq!(app.stages[4].sub_tasks[1].name, "dependencies");
app.tick();
app.update_subtask(4, 1, StageStatus::Completed, None);
assert_eq!(app.stages[4].sub_tasks[1].status, StageStatus::Completed);
app.update_subtask(4, 2, StageStatus::Active, None);
assert_eq!(app.stages[4].sub_tasks[2].status, StageStatus::Active);
assert_eq!(app.stages[4].sub_tasks[2].name, "git history");
app.tick();
app.update_subtask(4, 2, StageStatus::Completed, None);
assert_eq!(app.stages[4].sub_tasks[2].status, StageStatus::Completed);
app.complete_stage(4, "loaded");
assert_eq!(app.stages[4].status, StageStatus::Completed);
assert_eq!(app.stages[4].metric, Some("loaded".to_string()));
for subtask in &app.stages[4].sub_tasks {
assert_eq!(subtask.status, StageStatus::Completed);
}
}
#[test]
fn test_debt_scoring_subtasks() {
let app = App::new();
let debt_stage = &app.stages[5];
assert_eq!(debt_stage.name, "debt scoring");
assert_eq!(debt_stage.sub_tasks.len(), 4);
assert_eq!(debt_stage.sub_tasks[0].name, "aggregate debt");
assert_eq!(debt_stage.sub_tasks[1].name, "score functions");
assert_eq!(debt_stage.sub_tasks[2].name, "filter results");
assert_eq!(debt_stage.sub_tasks[3].name, "finalize results");
for subtask in &debt_stage.sub_tasks {
assert_eq!(subtask.status, StageStatus::Pending);
assert!(subtask.progress.is_none());
}
}
fn create_test_unified_debt_item(
location: debtmap::priority::Location,
debt_type: debtmap::priority::DebtType,
) -> debtmap::priority::UnifiedDebtItem {
use debtmap::priority::{
ActionableRecommendation, FunctionRole, ImpactMetrics, UnifiedDebtItem, UnifiedScore,
};
UnifiedDebtItem {
location,
debt_type,
unified_score: UnifiedScore {
complexity_factor: 5.0,
coverage_factor: 5.0,
dependency_factor: 3.0,
role_multiplier: 1.0,
final_score: 5.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::Unknown,
recommendation: ActionableRecommendation {
primary_action: "Test action".to_string(),
rationale: "Test rationale".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: 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,
}
}
#[test]
fn test_data_flow_page_rendering_with_mutations() {
use debtmap::data_flow::{DataFlowGraph, MutationInfo};
use debtmap::priority::{call_graph::FunctionId, DebtType, Location};
use std::path::PathBuf;
let mut data_flow = DataFlowGraph::new();
let location = Location {
file: PathBuf::from("src/test.rs"),
line: 42,
function: "test_function".to_string(),
};
let func_id = FunctionId::new(
location.file.clone(),
location.function.clone(),
location.line,
);
let mutation_info = MutationInfo {
has_mutations: true,
detected_mutations: vec!["x".to_string(), "y".to_string()],
};
data_flow.set_mutation_info(func_id.clone(), mutation_info);
let _item = create_test_unified_debt_item(
location,
DebtType::ComplexityHotspot {
cyclomatic: 10,
cognitive: 15,
},
);
let retrieved_mutation = data_flow.get_mutation_info(&func_id);
assert!(retrieved_mutation.is_some());
let mutation = retrieved_mutation.unwrap();
assert!(mutation.has_mutations);
assert_eq!(mutation.detected_mutations.len(), 2);
}
#[test]
fn test_data_flow_page_rendering_with_io_operations() {
use debtmap::data_flow::{DataFlowGraph, IoOperation};
use debtmap::priority::{call_graph::FunctionId, DebtType, Location};
use std::path::PathBuf;
let mut data_flow = DataFlowGraph::new();
let location = Location {
file: PathBuf::from("src/test.rs"),
line: 100,
function: "io_function".to_string(),
};
let func_id = FunctionId::new(
location.file.clone(),
location.function.clone(),
location.line,
);
data_flow.add_io_operation(
func_id.clone(),
IoOperation {
operation_type: "File Read".to_string(),
line: 105,
variables: vec!["file".to_string()],
},
);
data_flow.add_io_operation(
func_id.clone(),
IoOperation {
operation_type: "Network Call".to_string(),
line: 110,
variables: vec!["socket".to_string()],
},
);
let _item = create_test_unified_debt_item(
location,
DebtType::ComplexityHotspot {
cyclomatic: 8,
cognitive: 12,
},
);
let io_operations = data_flow.get_io_operations(&func_id);
assert!(io_operations.is_some());
let ops = io_operations.unwrap();
assert_eq!(ops.len(), 2);
assert_eq!(ops[0].operation_type, "File Read");
assert_eq!(ops[1].operation_type, "Network Call");
}
#[test]
fn test_data_flow_markdown_formatting() {
use debtmap::data_flow::{DataFlowGraph, IoOperation, MutationInfo};
use debtmap::priority::{call_graph::FunctionId, Location};
use std::path::PathBuf;
let mut data_flow = DataFlowGraph::new();
let location = Location {
file: PathBuf::from("src/example.rs"),
line: 50,
function: "complex_function".to_string(),
};
let func_id = FunctionId::new(
location.file.clone(),
location.function.clone(),
location.line,
);
data_flow.set_mutation_info(
func_id.clone(),
MutationInfo {
has_mutations: true,
detected_mutations: vec!["counter".to_string(), "state".to_string()],
},
);
data_flow.add_io_operation(
func_id.clone(),
IoOperation {
operation_type: "Database Query".to_string(),
line: 55,
variables: vec!["db".to_string()],
},
);
assert!(data_flow.get_mutation_info(&func_id).is_some());
assert!(data_flow.get_io_operations(&func_id).is_some());
let mutation = data_flow.get_mutation_info(&func_id).unwrap();
assert!(mutation.has_mutations);
assert_eq!(mutation.detected_mutations.len(), 2);
let io_ops = data_flow.get_io_operations(&func_id).unwrap();
assert_eq!(io_ops.len(), 1);
assert_eq!(io_ops[0].operation_type, "Database Query");
}
#[test]
fn test_god_object_displays_git_context() {
use debtmap::priority::{DebtType, Location};
use debtmap::risk;
use debtmap::risk::context::git_history::GitHistoryProvider;
use debtmap::risk::context::{ContextAggregator, ContextDetails};
use std::fs;
use std::process::Command;
let temp_dir = tempfile::tempdir().expect("Failed to create temp dir");
let project_root = temp_dir.path().to_path_buf();
Command::new("git")
.args(["init"])
.current_dir(&project_root)
.output()
.expect("Failed to init git repo");
Command::new("git")
.args(["config", "user.name", "Test User"])
.current_dir(&project_root)
.output()
.expect("Failed to configure git user");
Command::new("git")
.args(["config", "user.email", "test@example.com"])
.current_dir(&project_root)
.output()
.expect("Failed to configure git email");
let src_dir = project_root.join("src");
fs::create_dir_all(&src_dir).expect("Failed to create src dir");
let god_object_file = src_dir.join("god_object.rs");
fs::write(
&god_object_file,
"pub struct GodObject { field1: i32 }\nimpl GodObject { fn method1(&self) {} }",
)
.expect("Failed to write initial file");
Command::new("git")
.args(["add", "."])
.current_dir(&project_root)
.output()
.expect("Failed to git add");
Command::new("git")
.args(["commit", "-m", "Initial commit"])
.current_dir(&project_root)
.output()
.expect("Failed to git commit");
fs::write(
&god_object_file,
"pub struct GodObject { field1: i32, field2: String }\n\
impl GodObject {\n\
fn method1(&self) {}\n\
fn method2(&self) {}\n\
fn method3(&self) {}\n\
}",
)
.expect("Failed to write second version");
Command::new("git")
.args(["add", "."])
.current_dir(&project_root)
.output()
.expect("Failed to git add");
Command::new("git")
.args(["commit", "-m", "Add more methods"])
.current_dir(&project_root)
.output()
.expect("Failed to git commit");
let git_provider =
GitHistoryProvider::new(project_root.clone()).expect("Failed to create git provider");
let context_aggregator = ContextAggregator::new().with_provider(Box::new(git_provider));
let risk_analyzer = risk::RiskAnalyzer::default().with_context_aggregator(context_aggregator);
let contextual_risk = debtmap::builders::unified_analysis::analyze_file_git_context(
&god_object_file,
&risk_analyzer,
&project_root,
);
assert!(
contextual_risk.is_some(),
"God object should have git context when analyzed with git context enabled"
);
let context = contextual_risk.unwrap();
let git_context = context
.contexts
.iter()
.find(|ctx| ctx.provider == "git_history");
assert!(
git_context.is_some(),
"Contextual risk should include git_history context"
);
let git_ctx = git_context.unwrap();
match &git_ctx.details {
ContextDetails::Historical {
change_frequency,
author_count,
age_days,
..
} => {
assert!(
*change_frequency >= 0.0,
"Should have valid (non-negative) change frequency"
);
assert!(*author_count >= 1, "Should have at least one author");
let _ = age_days;
}
_ => panic!("Expected Historical context details"),
}
let location = Location {
file: god_object_file.clone(),
line: 1,
function: "GodObject".to_string(),
};
let god_object_item = create_test_unified_debt_item(
location,
DebtType::GodObject {
methods: 3,
fields: Some(2),
responsibilities: 2,
god_object_score: 60.0,
lines: 150,
},
);
assert_eq!(format!("{}", god_object_item.debt_type), "God Object");
let mut enriched_item = god_object_item;
enriched_item.contextual_risk = Some(context);
assert!(
enriched_item.contextual_risk.is_some(),
"God object item should have contextual risk attached"
);
let final_context = enriched_item.contextual_risk.unwrap();
let final_git_context = final_context
.contexts
.iter()
.find(|ctx| ctx.provider == "git_history");
assert!(
final_git_context.is_some(),
"Final item should preserve git history context"
);
if let Some(git_ctx) = final_git_context {
match &git_ctx.details {
ContextDetails::Historical {
change_frequency, ..
} => {
assert!(
*change_frequency >= 0.0,
"Git metrics should be preserved in final item (valid non-negative value)"
);
}
_ => panic!("Expected Historical context details"),
}
}
}