use debtmap::core::FunctionMetrics;
use debtmap::priority::external_api_detector::{
generate_enhanced_dead_code_hints_with_config, is_likely_external_api_with_config,
ExternalApiConfig,
};
use debtmap::priority::FunctionVisibility;
use std::path::PathBuf;
fn create_function(name: &str, path: &str, visibility: Option<String>) -> FunctionMetrics {
FunctionMetrics {
name: name.to_string(),
file: PathBuf::from(path),
line: 1,
cyclomatic: 5,
cognitive: 8,
nesting: 2,
length: 20,
is_test: false,
visibility,
is_trait_method: false,
in_test_module: false,
entropy_score: None,
is_pure: None,
purity_confidence: None,
detected_patterns: None,
upstream_callers: None,
downstream_callees: None,
mapping_pattern_result: None,
adjusted_complexity: None,
composition_metrics: None,
language_specific: None,
purity_reason: None,
call_dependencies: None,
purity_level: None,
error_swallowing_count: None,
error_swallowing_patterns: None,
entropy_analysis: None,
}
}
fn test_config() -> ExternalApiConfig {
ExternalApiConfig {
detect_external_api: true,
api_functions: vec![],
api_files: vec![],
}
}
#[test]
fn test_lib_rs_functions_are_apis() {
let test_cases = vec![
("new", "src/lib.rs"),
("from_string", "src/lib.rs"),
("process", "crate/lib.rs"),
("analyze", "my_crate/src/lib.rs"),
];
for (name, path) in test_cases {
let func = create_function(name, path, Some("pub".to_string()));
let visibility = FunctionVisibility::Public;
let (is_api, indicators) =
is_likely_external_api_with_config(&func, &visibility, &test_config());
assert!(
is_api,
"Function '{name}' in '{path}' should be detected as API"
);
assert!(
indicators.iter().any(|i| i.contains("lib.rs")),
"Should have lib.rs indicator for '{name}' in '{path}'"
);
}
}
#[test]
fn test_mod_rs_functions_are_likely_apis() {
let test_cases = vec![
("init", "src/mod.rs"),
("create_instance", "src/parser/mod.rs"),
("configure", "src/config/mod.rs"),
];
for (name, path) in test_cases {
let func = create_function(name, path, Some("pub".to_string()));
let visibility = FunctionVisibility::Public;
let (_is_api, indicators) =
is_likely_external_api_with_config(&func, &visibility, &test_config());
assert!(
indicators.iter().any(|i| i.contains("mod.rs")),
"Should have mod.rs indicator for '{name}' in '{path}'"
);
}
}
#[test]
fn test_common_constructors_are_apis() {
let constructors = vec![
"new",
"new_with_config",
"default",
"from_str",
"from_path",
"from_config",
"init",
"init_with_options",
"builder",
"build",
];
for name in constructors {
let func = create_function(name, "src/any/path.rs", Some("pub".to_string()));
let visibility = FunctionVisibility::Public;
let (is_api, indicators) =
is_likely_external_api_with_config(&func, &visibility, &test_config());
assert!(is_api, "Constructor '{name}' should be detected as API");
assert!(
indicators.iter().any(|i| i.contains("Constructor")
|| i.contains("Builder")
|| i.contains("trait method")),
"Should have constructor/builder indicator for '{name}'"
);
}
}
#[test]
fn test_api_pattern_prefixes() {
let api_functions = vec![
("get_value", "getter"),
("set_value", "setter"),
("with_option", "builder pattern"),
("try_parse", "fallible operation"),
("is_valid", "predicate"),
("has_feature", "predicate"),
("create_instance", "factory"),
("parse_input", "parser"),
("to_string", "conversion"),
("into_iter", "conversion"),
("as_ref", "conversion"),
];
for (name, expected_pattern) in api_functions {
let func = create_function(name, "src/module.rs", Some("pub".to_string()));
let visibility = FunctionVisibility::Public;
let (_is_api, indicators) =
is_likely_external_api_with_config(&func, &visibility, &test_config());
assert!(
indicators.iter().any(|i| i.contains("API pattern")),
"Function '{name}' should have API pattern indicator ({expected_pattern})"
);
}
}
#[test]
fn test_internal_paths_not_apis() {
let internal_paths = vec![
"src/internal/helper.rs",
"src/private/utils.rs",
"src/impl/detail.rs",
"src/detail/internal.rs",
"src/tests/fixtures.rs",
"src/benches/utils.rs",
"src/examples/demo.rs",
];
for path in internal_paths {
let func = create_function("public_func", path, Some("pub".to_string()));
let visibility = FunctionVisibility::Public;
let (is_api, indicators) =
is_likely_external_api_with_config(&func, &visibility, &test_config());
assert!(
!is_api,
"Function in '{path}' should NOT be detected as API"
);
assert!(
!indicators.is_empty() || path.contains("internal") || path.contains("private"),
"Should have some analysis for '{path}'"
);
}
}
#[test]
fn test_deep_vs_shallow_paths() {
let shallow_api = create_function("process", "src/processor.rs", Some("pub".to_string()));
let deep_internal = create_function(
"process",
"src/core/internal/impl/detail/utils/processor.rs",
Some("pub".to_string()),
);
let visibility = FunctionVisibility::Public;
let (shallow_is_api, shallow_indicators) =
is_likely_external_api_with_config(&shallow_api, &visibility, &test_config());
let (deep_is_api, deep_indicators) =
is_likely_external_api_with_config(&deep_internal, &visibility, &test_config());
assert!(
shallow_indicators
.iter()
.any(|i| i.contains("Shallow module path")),
"Shallow path should be noted"
);
assert!(
deep_indicators
.iter()
.any(|i| i.contains("Deep module path")),
"Deep path should be noted"
);
assert!(
shallow_is_api || !deep_is_api,
"Shallow paths should be more likely APIs than deep paths"
);
}
#[test]
fn test_non_public_functions_never_apis() {
let visibilities = vec![
(FunctionVisibility::Private, None),
(FunctionVisibility::Crate, Some("pub(crate)".to_string())),
(FunctionVisibility::Private, Some("pub(super)".to_string())),
];
for (vis_enum, vis_string) in visibilities {
let func = create_function("new", "src/lib.rs", vis_string);
let (is_api, indicators) =
is_likely_external_api_with_config(&func, &vis_enum, &test_config());
assert!(
!is_api,
"Non-public functions should never be external APIs"
);
assert!(
indicators.is_empty(),
"Non-public functions should have no API indicators"
);
}
}
#[test]
fn test_specific_api_prefixes() {
let api_prefixed = vec!["public_interface", "api_endpoint", "export_data"];
for name in api_prefixed {
let func = create_function(name, "src/some/path.rs", Some("pub".to_string()));
let visibility = FunctionVisibility::Public;
let (_is_api, indicators) =
is_likely_external_api_with_config(&func, &visibility, &test_config());
assert!(
indicators.iter().any(|i| i.contains("Public API prefix")),
"Function '{name}' should have public API prefix indicator"
);
}
}
#[test]
fn test_enhanced_hints_generation() {
let api_func = create_function("new", "src/lib.rs", Some("pub".to_string()));
let visibility = FunctionVisibility::Public;
let hints =
generate_enhanced_dead_code_hints_with_config(&api_func, &visibility, &test_config());
assert!(
hints.iter().any(|h| h.contains("Likely external API")),
"Hints should indicate likely external API"
);
assert!(
hints.iter().any(|h| h.contains("lib.rs")),
"Hints should mention specific indicators"
);
}
#[test]
fn test_test_helper_detection() {
let test_helpers = vec![
"test_helper",
"create_test_fixture",
"mock_util",
"setup_test_fixture",
];
for name in test_helpers {
let func = create_function(name, "src/utils.rs", Some("pub".to_string()));
let visibility = FunctionVisibility::Public;
let hints =
generate_enhanced_dead_code_hints_with_config(&func, &visibility, &test_config());
assert!(
hints.iter().any(|h| h.contains("test helper")),
"Function '{name}' should be identified as potential test helper"
);
}
}
#[test]
fn test_complexity_impact_hints() {
let mut simple_func = create_function("simple", "src/utils.rs", Some("pub".to_string()));
simple_func.cyclomatic = 2;
simple_func.cognitive = 3;
let visibility = FunctionVisibility::Public;
let hints =
generate_enhanced_dead_code_hints_with_config(&simple_func, &visibility, &test_config());
assert!(
hints.iter().any(|h| h.contains("Low complexity")),
"Low complexity functions should be noted"
);
let mut complex_func = create_function("complex", "src/utils.rs", Some("pub".to_string()));
complex_func.cyclomatic = 15;
complex_func.cognitive = 20;
let hints_complex =
generate_enhanced_dead_code_hints_with_config(&complex_func, &visibility, &test_config());
assert!(
hints_complex.iter().any(|h| h.contains("High complexity")),
"High complexity functions should be noted"
);
}
#[test]
fn test_score_threshold_for_api_detection() {
let func1 = create_function(
"random_name",
"src/deep/nested/path.rs",
Some("pub".to_string()),
);
let visibility = FunctionVisibility::Public;
let (is_api1, _) = is_likely_external_api_with_config(&func1, &visibility, &test_config());
assert!(
!is_api1,
"Single indicator shouldn't be enough for API classification"
);
let func2 = create_function("new", "src/lib.rs", Some("pub".to_string()));
let (is_api2, indicators2) =
is_likely_external_api_with_config(&func2, &visibility, &test_config());
assert!(is_api2, "Multiple strong indicators should classify as API");
assert!(indicators2.len() >= 3, "Should have multiple indicators");
let func3 = create_function("get_config", "src/mod.rs", Some("pub".to_string()));
let (is_api3, indicators3) =
is_likely_external_api_with_config(&func3, &visibility, &test_config());
assert!(is_api3, "Medium strength indicators should be enough");
assert!(indicators3.len() >= 2, "Should have at least 2 indicators");
}
#[test]
fn test_real_world_patterns() {
let deserialize = create_function("deserialize", "src/de/mod.rs", Some("pub".to_string()));
let visibility = FunctionVisibility::Public;
let (is_api, _) = is_likely_external_api_with_config(&deserialize, &visibility, &test_config());
assert!(is_api, "Common trait methods in mod.rs should be APIs");
let spawn = create_function("spawn", "src/runtime/mod.rs", Some("pub".to_string()));
let (is_api2, _) = is_likely_external_api_with_config(&spawn, &visibility, &test_config());
assert!(is_api2, "Runtime spawn functions should be APIs");
let internal = create_function(
"poll_next",
"src/runtime/task/core/detail.rs",
Some("pub".to_string()),
);
let (is_api3, _) = is_likely_external_api_with_config(&internal, &visibility, &test_config());
assert!(!is_api3, "Deep internal implementation should not be API");
}