#[tokio::test]
async fn test_analyze_directory_empty() {
let temp_dir = TempDir::new().expect("internal error");
let detector = SATDDetector::new();
let result = detector.analyze_directory(temp_dir.path()).await;
assert!(result.is_ok());
let debts = result.expect("internal error");
assert_eq!(debts.len(), 0);
}
#[tokio::test]
async fn test_analyze_directory_with_rust_files() {
let temp_dir = TempDir::new().expect("internal error");
let detector = SATDDetector::new();
let file1 = temp_dir.path().join("file1.rs");
fs::write(&file1, "// TODO: Test debt in file 1\nfn main() {}").expect("internal error");
let file2 = temp_dir.path().join("file2.rs");
fs::write(&file2, "// FIXME: Test debt in file 2\nfn helper() {}").expect("internal error");
let result = detector.analyze_directory(temp_dir.path()).await;
assert!(result.is_ok());
let debts = result.expect("internal error");
assert_eq!(debts.len(), 2);
}
#[tokio::test]
async fn test_analyze_directory_ignores_non_source_files() {
let temp_dir = TempDir::new().expect("internal error");
let detector = SATDDetector::new();
let rust_file = temp_dir.path().join("source.rs");
fs::write(&rust_file, "// TODO: This should be found").expect("internal error");
let text_file = temp_dir.path().join("readme.txt");
fs::write(&text_file, "TODO: This should be ignored").expect("internal error");
let result = detector.analyze_directory(temp_dir.path()).await;
assert!(result.is_ok());
let debts = result.expect("internal error");
assert_eq!(debts.len(), 1); assert!(debts[0].file.ends_with("source.rs"));
}
#[test]
fn test_generate_metrics_edge_cases() {
let detector = SATDDetector::new();
let empty_debts = vec![];
let metrics = detector.generate_metrics(&empty_debts, 1000);
assert_eq!(metrics.total_debts, 0);
assert_eq!(metrics.critical_debts.len(), 0);
assert_eq!(metrics.debt_density_per_kloc, 0.0);
assert_eq!(metrics.by_category.len(), 0);
assert_eq!(metrics.debt_age_distribution.len(), 0);
}
#[test]
fn test_generate_metrics_with_mixed_severities() {
let detector = SATDDetector::new();
let debts = vec![
create_test_debt(DebtCategory::Design, Severity::Low),
create_test_debt(DebtCategory::Design, Severity::Medium),
create_test_debt(DebtCategory::Defect, Severity::High),
create_test_debt(DebtCategory::Defect, Severity::Critical),
];
let metrics = detector.generate_metrics(&debts, 2000);
assert_eq!(metrics.total_debts, 4);
assert_eq!(metrics.critical_debts.len(), 1); assert_eq!(metrics.debt_density_per_kloc, 2.0); assert_eq!(metrics.by_category.len(), 2);
let design_metrics = metrics.by_category.get("Design").expect("internal error");
assert_eq!(design_metrics.count, 2);
assert!((design_metrics.avg_severity - 1.5).abs() < 0.1);
let defect_metrics = metrics.by_category.get("Defect").expect("internal error");
assert_eq!(defect_metrics.count, 2);
assert!((defect_metrics.avg_severity - 3.5).abs() < 0.1); }
#[tokio::test]
async fn test_calculate_average_debt_age_empty_debts() {
let detector = SATDDetector::new();
let temp_dir = tempfile::tempdir().expect("internal error");
let project_root = temp_dir.path();
let result = detector
.calculate_average_debt_age(&[], project_root)
.await
.expect("internal error");
assert_eq!(result, 0.0);
}
#[tokio::test]
async fn test_calculate_average_debt_age_no_git() {
let detector = SATDDetector::new();
let temp_dir = tempfile::tempdir().expect("internal error");
let project_root = temp_dir.path();
let test_file = project_root.join("test.rs");
std::fs::write(&test_file, "// TODO: test debt").expect("internal error");
let debts = vec![create_test_debt_with_file(
DebtCategory::Design,
Severity::Medium,
test_file.clone(),
1,
)];
let result = detector
.calculate_average_debt_age(&debts, project_root)
.await
.expect("internal error");
assert_eq!(result, 0.0); }
#[tokio::test]
async fn test_calculate_average_debt_age_invalid_file_path() {
let detector = SATDDetector::new();
let temp_dir = tempfile::tempdir().expect("internal error");
let project_root = temp_dir.path();
let external_file = PathBuf::from("/external/file.rs");
let debts = vec![create_test_debt_with_file(
DebtCategory::Design,
Severity::Medium,
external_file,
1,
)];
let result = detector
.calculate_average_debt_age(&debts, project_root)
.await
.expect("internal error");
assert_eq!(result, 0.0); }
fn create_test_debt_with_file(
category: DebtCategory,
severity: Severity,
file: PathBuf,
line: u32,
) -> TechnicalDebt {
TechnicalDebt {
text: "test debt".to_string(),
category,
severity,
file,
line,
column: 1,
context_hash: [0; 16], }
}
#[test]
fn test_extract_from_content_complex_test_blocks() {
let detector = SATDDetector::new();
let content = r#"
// TODO: regular debt
fn main() {
#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod nested_tests {
// TODO: should be ignored
#[test]
fn test_with_nested_blocks() {
if true {
// FIXME: nested ignored
let x = {
// TODO: deeply nested ignored
42
};
}
}
}
// TODO: after test block
}
"#;
let debts = detector
.extract_from_content(content, Path::new("test.rs"))
.expect("internal error");
assert_eq!(debts.len(), 2);
assert!(debts.iter().any(|d| d.text.contains("regular debt")));
assert!(debts.iter().any(|d| d.text.contains("after test block")));
assert!(!debts.iter().any(|d| d.text.contains("should be ignored")));
assert!(!debts.iter().any(|d| d.text.contains("nested ignored")));
assert!(!debts
.iter()
.any(|d| d.text.contains("deeply nested ignored")));
}
#[test]
fn test_extract_from_content_non_rust_files() {
let detector = SATDDetector::new();
let content = r#"
// TODO: python debt
#[cfg(test)] // This should not be treated as test block in Python
def test_something():
# TODO: python test debt should be found
pass
"#;
let debts = detector
.extract_from_content(content, Path::new("test.py"))
.expect("internal error");
assert_eq!(debts.len(), 2);
assert!(debts.iter().any(|d| d.text.contains("python debt")));
assert!(debts.iter().any(|d| d.text.contains("python test debt")));
}