#[tokio::test]
async fn test_extract_from_content() {
let detector = SATDDetector::new();
let content = r#"
// TODO: implement error handling
fn main() {
// FIXME: this is broken
let x = 42;
# HACK: python style comment
/* BUG: memory leak */
<!-- SECURITY: XSS vulnerability -->
}
// Regular comment
fn helper() {
// Another regular comment
}
"#;
let debts = detector
.extract_from_content(content, Path::new("test.rs"))
.expect("internal error");
assert_eq!(debts.len(), 5);
for i in 1..debts.len() {
assert!(debts[i].line >= debts[i - 1].line);
}
assert!(debts
.iter()
.any(|d| d.text.contains("implement error handling")));
assert!(debts.iter().any(|d| d.text.contains("this is broken")));
assert!(debts
.iter()
.any(|d| d.text.contains("python style comment")));
assert!(debts.iter().any(|d| d.text.contains("memory leak")));
assert!(debts.iter().any(|d| d.text.contains("XSS vulnerability")));
}
#[tokio::test]
async fn test_extract_from_content_skips_test_blocks() {
let detector = SATDDetector::new();
let content = format!(
r#"
// {}: implement feature
fn main() {{
// {}: production bug
}}
#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod tests {{
// {}: this should be ignored
#[test]
fn test_something() {{
// {}: test debt should be ignored
}}
}}
// {}: this should be found
"#,
"TODO", "FIXME", "TODO", "FIXME", "TODO"
);
let debts = detector
.extract_from_content(&content, Path::new("test.rs"))
.expect("internal error");
assert_eq!(debts.len(), 3);
assert!(!debts.iter().any(|d| d.text.contains("should be ignored")));
assert!(!debts.iter().any(|d| d.text.contains("test debt")));
assert!(debts.iter().any(|d| d.text.contains("implement feature")));
assert!(debts.iter().any(|d| d.text.contains("production bug")));
assert!(debts
.iter()
.any(|d| d.text.contains("this should be found")));
}
#[test]
fn test_technical_debt_equality() {
let debt1 = create_test_debt(DebtCategory::Design, Severity::Medium);
let debt2 = create_test_debt(DebtCategory::Design, Severity::Medium);
assert_eq!(debt1, debt2);
let debt3 = create_test_debt(DebtCategory::Defect, Severity::High);
assert_ne!(debt1, debt3);
}
#[test]
fn test_satd_summary_creation() {
let summary = SATDSummary {
total_items: 10,
by_severity: {
let mut map = std::collections::HashMap::new();
map.insert("High".to_string(), 5);
map.insert("Low".to_string(), 5);
map
},
by_category: {
let mut map = std::collections::HashMap::new();
map.insert("Design".to_string(), 6);
map.insert("Defect".to_string(), 4);
map
},
files_with_satd: 3,
avg_age_days: 30.5,
};
assert_eq!(summary.total_items, 10);
assert_eq!(summary.by_severity.get("High"), Some(&5));
assert_eq!(summary.by_category.get("Design"), Some(&6));
assert_eq!(summary.files_with_satd, 3);
assert_eq!(summary.avg_age_days, 30.5);
}
#[test]
fn test_satd_analysis_result_creation() {
let debts = vec![
create_test_debt(DebtCategory::Design, Severity::Medium),
create_test_debt(DebtCategory::Defect, Severity::High),
];
let result = SATDAnalysisResult {
items: debts.clone(),
summary: SATDSummary {
total_items: 2,
by_severity: std::collections::HashMap::new(),
by_category: std::collections::HashMap::new(),
files_with_satd: 1,
avg_age_days: 0.0,
},
total_files_analyzed: 10,
files_with_debt: 1,
analysis_timestamp: Utc::now(),
};
assert_eq!(result.items.len(), 2);
assert_eq!(result.total_files_analyzed, 10);
assert_eq!(result.files_with_debt, 1);
}
#[test]
fn test_category_metrics() {
let metrics = CategoryMetrics {
count: 5,
files: {
let mut set = BTreeSet::new();
set.insert("file1.rs".to_string());
set.insert("file2.rs".to_string());
set
},
avg_severity: 2.5,
};
assert_eq!(metrics.count, 5);
assert_eq!(metrics.files.len(), 2);
assert!(metrics.files.contains("file1.rs"));
assert_eq!(metrics.avg_severity, 2.5);
}
#[test]
fn test_satd_metrics() {
let metrics = SATDMetrics {
total_debts: 20,
debt_density_per_kloc: 5.5,
by_category: BTreeMap::new(),
critical_debts: vec![],
debt_age_distribution: vec![1.0, 5.0, 10.0, 30.0],
};
assert_eq!(metrics.total_debts, 20);
assert_eq!(metrics.debt_density_per_kloc, 5.5);
assert_eq!(metrics.debt_age_distribution.len(), 4);
}
#[test]
fn test_debt_evolution() {
let evolution = DebtEvolution {
total_introduced: 15,
total_resolved: 10,
current_debt_age_p50: 25.5,
debt_velocity: 0.5,
};
assert_eq!(evolution.total_introduced, 15);
assert_eq!(evolution.total_resolved, 10);
assert_eq!(evolution.current_debt_age_p50, 25.5);
assert_eq!(evolution.debt_velocity, 0.5);
}
#[test]
fn test_ast_node_type_equality() {
assert_eq!(AstNodeType::SecurityFunction, AstNodeType::SecurityFunction);
assert_ne!(AstNodeType::SecurityFunction, AstNodeType::TestFunction);
}
#[tokio::test]
async fn test_is_test_file() {
let detector = SATDDetector::new();
assert!(detector.is_test_file(&PathBuf::from("test_module.rs")));
assert!(detector.is_test_file(&PathBuf::from("module_test.rs")));
assert!(!detector.is_test_file(&PathBuf::from("tests/integration.rs"))); assert!(detector.is_test_file(&PathBuf::from("src/tests.rs")));
assert!(!detector.is_test_file(&PathBuf::from("__tests__/app.js"))); assert!(detector.is_test_file(&PathBuf::from("spec/feature_spec.rb")));
assert!(!detector.is_test_file(&PathBuf::from("main.rs")));
assert!(!detector.is_test_file(&PathBuf::from("lib.rs")));
assert!(!detector.is_test_file(&PathBuf::from("module.rs")));
}
#[tokio::test]
async fn test_find_source_files_excludes_common_dirs() {
let temp_dir = TempDir::new().expect("internal error");
let root = temp_dir.path();
fs::write(root.join("main.rs"), "// TODO: test").expect("internal error");
fs::create_dir(root.join("target")).expect("internal error");
fs::write(root.join("target").join("debug.rs"), "// TODO: ignore").expect("internal error");
fs::create_dir(root.join("node_modules")).expect("internal error");
fs::write(root.join("node_modules").join("lib.js"), "// TODO: ignore")
.expect("internal error");
fs::create_dir(root.join(".git")).expect("internal error");
fs::write(root.join(".git").join("config"), "// TODO: ignore").expect("internal error");
let detector = SATDDetector::new();
let files = detector
.find_source_files(root)
.await
.expect("internal error");
assert_eq!(files.len(), 1);
assert!(files[0].ends_with("main.rs"));
}
#[tokio::test]
async fn test_is_source_file() {
let detector = SATDDetector::new();
assert!(detector.is_source_file(&PathBuf::from("main.rs")));
assert!(detector.is_source_file(&PathBuf::from("app.js")));
assert!(detector.is_source_file(&PathBuf::from("script.ts")));
assert!(detector.is_source_file(&PathBuf::from("module.py")));
assert!(detector.is_source_file(&PathBuf::from("main.cpp")));
assert!(detector.is_source_file(&PathBuf::from("header.h")));
assert!(detector.is_source_file(&PathBuf::from("Main.java")));
assert!(detector.is_source_file(&PathBuf::from("app.go")));
assert!(detector.is_source_file(&PathBuf::from("script.php")));
assert!(detector.is_source_file(&PathBuf::from("app.rb")));
assert!(detector.is_source_file(&PathBuf::from("Main.cs")));
assert!(detector.is_source_file(&PathBuf::from("main.swift")));
assert!(detector.is_source_file(&PathBuf::from("app.kt")));
assert!(!detector.is_source_file(&PathBuf::from("main.m"))); assert!(!detector.is_source_file(&PathBuf::from("script.sh"))); assert!(!detector.is_source_file(&PathBuf::from("script.bash"))); assert!(!detector.is_source_file(&PathBuf::from("style.css"))); assert!(!detector.is_source_file(&PathBuf::from("index.html"))); assert!(detector.is_source_file(&PathBuf::from("app.jsx")));
assert!(detector.is_source_file(&PathBuf::from("app.tsx")));
assert!(!detector.is_source_file(&PathBuf::from("app.vue")));
assert!(!detector.is_source_file(&PathBuf::from("image.png")));
assert!(!detector.is_source_file(&PathBuf::from("data.json")));
assert!(!detector.is_source_file(&PathBuf::from("config.yml")));
assert!(!detector.is_source_file(&PathBuf::from("README.md")));
assert!(!detector.is_source_file(&PathBuf::from("binary.exe")));
}
#[tokio::test]
async fn test_analyze_directory() {
let temp_dir = TempDir::new().expect("internal error");
let root = temp_dir.path();
let main_content = format!(
r#"
// {}: implement feature
fn main() {{
// {}: bug here
}}
"#,
"TODO", "FIXME"
);
fs::write(root.join("main.rs"), main_content).expect("internal error");
let helper_content = format!(
r#"
// {}: test helper function needed
fn helper_test() {{
// Regular test helper function
}}
"#,
"TODO"
);
fs::write(root.join("helper_test.rs"), helper_content).expect("internal error");
let detector = SATDDetector::new();
let debts = detector
.analyze_directory(root)
.await
.expect("internal error");
assert_eq!(debts.len(), 2);
let debts_with_tests = detector
.analyze_directory_with_tests(root, true)
.await
.expect("internal error");
assert_eq!(debts_with_tests.len(), 2); }
#[tokio::test]
async fn test_analyze_project() {
let temp_dir = TempDir::new().expect("internal error");
let root = temp_dir.path();
let file1_content = format!(
r#"
// {}: task 1
// {}: bug 1
"#,
"TODO", "FIXME"
);
fs::write(root.join("file1.rs"), file1_content).expect("internal error");
let file2_content = format!(
r#"
// {}: workaround
// {}: vulnerability
"#,
"HACK", "SECURITY"
);
fs::write(root.join("file2.rs"), file2_content).expect("internal error");
fs::write(
root.join("empty.rs"),
"// Just a normal comment\nfn main() {}\n",
)
.expect("internal error");
let detector = SATDDetector::new();
let result = detector
.analyze_project(root, false)
.await
.expect("internal error");
assert_eq!(result.total_files_analyzed, 3);
assert_eq!(result.files_with_debt, 2); assert_eq!(result.items.len(), 4);
assert_eq!(result.summary.total_items, 4);
assert!(result.summary.by_severity.contains_key("Low"));
assert!(result.summary.by_severity.contains_key("High"));
assert!(result.summary.by_severity.contains_key("Medium"));
assert!(result.summary.by_severity.contains_key("Critical"));
assert!(result.summary.by_category.contains_key("Requirement"));
assert!(result.summary.by_category.contains_key("Defect"));
assert!(result.summary.by_category.contains_key("Design"));
assert!(result.summary.by_category.contains_key("Security"));
}
#[tokio::test]
async fn test_large_file_handling() {
let temp_dir = TempDir::new().expect("internal error");
let root = temp_dir.path();
let large_content = format!("// {}", "a".repeat(11_000_000));
fs::write(root.join("large.rs"), large_content).expect("internal error");
let detector = SATDDetector::new();
let debts = detector
.analyze_directory(root)
.await
.expect("internal error");
assert_eq!(debts.len(), 0);
}
#[test]
fn test_extract_from_line_error_handling() {
let detector = SATDDetector::new();
let result = detector
.extract_from_line("// TODO: fix", Path::new("test.rs"), 1)
.expect("internal error");
assert!(result.is_some());
let result = detector
.extract_from_line("", Path::new("test.rs"), 1)
.expect("internal error");
assert!(result.is_none());
}