use debtmap::analysis::patterns::{
config::PatternConfig, PatternDetector, PatternInstance, PatternType,
};
use debtmap::core::{
ast::{ClassDef, MethodDef},
ComplexityMetrics, FileMetrics, Language,
};
use std::fs;
use std::path::PathBuf;
use tempfile::TempDir;
fn create_test_file_metrics() -> FileMetrics {
FileMetrics {
path: PathBuf::from("test.py"),
language: Language::Python,
complexity: ComplexityMetrics::default(),
debt_items: vec![],
dependencies: vec![],
duplications: vec![],
total_lines: 0,
module_scope: None,
classes: Some(vec![
ClassDef {
name: "EventHandler".to_string(),
base_classes: vec!["ABC".to_string()],
methods: vec![MethodDef {
name: "handle_event".to_string(),
is_abstract: true,
decorators: vec!["abstractmethod".to_string()],
overrides_base: false,
line: 5,
}],
is_abstract: true,
decorators: vec![],
line: 3,
},
ClassDef {
name: "WidgetFactory".to_string(),
base_classes: vec![],
methods: vec![MethodDef {
name: "create_widget".to_string(),
is_abstract: false,
decorators: vec![],
overrides_base: false,
line: 15,
}],
is_abstract: false,
decorators: vec![],
line: 12,
},
ClassDef {
name: "UserService".to_string(),
base_classes: vec![],
methods: vec![MethodDef {
name: "__init__".to_string(),
is_abstract: false,
decorators: vec!["inject".to_string()],
overrides_base: false,
line: 25,
}],
is_abstract: false,
decorators: vec!["injectable".to_string()],
line: 23,
},
]),
}
}
#[test]
fn test_pattern_detector_finds_multiple_patterns() {
let detector = PatternDetector::new();
let file_metrics = create_test_file_metrics();
let patterns = detector.detect_all_patterns(&file_metrics);
assert!(!patterns.is_empty(), "Expected to find patterns");
let pattern_types: Vec<PatternType> = patterns.iter().map(|p| p.pattern_type.clone()).collect();
assert!(
pattern_types.contains(&PatternType::Factory)
|| pattern_types.contains(&PatternType::DependencyInjection),
"Expected to find Factory or DependencyInjection patterns, found: {:?}",
pattern_types
);
}
#[test]
fn test_confidence_threshold_filtering() {
let detector = PatternDetector::new();
let file_metrics = create_test_file_metrics();
let all_patterns = detector.detect_all_patterns(&file_metrics);
let high_confidence: Vec<&PatternInstance> = all_patterns
.iter()
.filter(|p| p.confidence >= 0.8)
.collect();
let medium_confidence: Vec<&PatternInstance> = all_patterns
.iter()
.filter(|p| p.confidence >= 0.5)
.collect();
assert!(
high_confidence.len() <= medium_confidence.len(),
"High confidence count should be <= medium confidence count"
);
for pattern in high_confidence {
assert!(
pattern.confidence >= 0.8,
"Pattern confidence {} should be >= 0.8",
pattern.confidence
);
}
}
#[test]
fn test_load_debtmap_toml_config() {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let config_path = temp_dir.path().join(".debtmap.toml");
let config_content = r#"
enabled = true
confidence_threshold = 0.85
[observer]
interface_markers = ["ABC", "Protocol", "EventHandler"]
method_prefixes = ["on_", "handle_", "process_"]
[factory]
name_patterns = ["create_", "make_", "build_", "new_"]
[[custom_rules]]
name = "event_handler"
method_pattern = "^handle_.*_event$"
confidence = 0.9
"#;
fs::write(&config_path, config_content).expect("Failed to write config");
let config = PatternConfig::load(&config_path).expect("Failed to load config");
assert!(config.enabled);
assert_eq!(config.confidence_threshold, 0.85);
assert_eq!(config.observer.interface_markers.len(), 3);
assert!(config
.observer
.interface_markers
.contains(&"EventHandler".to_string()));
assert_eq!(config.factory.name_patterns.len(), 4);
assert_eq!(config.custom_rules.len(), 1);
assert_eq!(config.custom_rules[0].name, "event_handler");
assert_eq!(config.custom_rules[0].confidence, 0.9);
}
#[test]
fn test_load_or_default_with_no_config() {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let config = PatternConfig::load_or_default(temp_dir.path());
assert!(config.enabled);
assert_eq!(config.confidence_threshold, 0.7);
assert!(config.custom_rules.is_empty());
}
#[test]
fn test_load_or_default_with_config() {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let config_path = temp_dir.path().join(".debtmap.toml");
let config_content = r#"
enabled = false
confidence_threshold = 0.9
"#;
fs::write(&config_path, config_content).expect("Failed to write config");
let config = PatternConfig::load_or_default(temp_dir.path());
assert!(!config.enabled);
assert_eq!(config.confidence_threshold, 0.9);
}
#[test]
fn test_custom_pattern_rules_validation() {
let mut config = PatternConfig::default();
config
.custom_rules
.push(debtmap::analysis::patterns::config::CustomPatternRule {
name: "test_pattern".to_string(),
method_pattern: Some("^test_.*".to_string()),
class_pattern: None,
decorator_pattern: None,
confidence: 0.8,
});
assert!(config.validate().is_ok());
config
.custom_rules
.push(debtmap::analysis::patterns::config::CustomPatternRule {
name: "invalid_pattern".to_string(),
method_pattern: None,
class_pattern: None,
decorator_pattern: None,
confidence: 1.5,
});
assert!(config.validate().is_err());
}
#[test]
fn test_pattern_detection_respects_confidence_threshold() {
let detector = PatternDetector::new();
let file_metrics = create_test_file_metrics();
let patterns = detector.detect_all_patterns(&file_metrics);
for pattern in &patterns {
assert!(
pattern.confidence > 0.0 && pattern.confidence <= 1.0,
"Pattern confidence {} should be between 0 and 1",
pattern.confidence
);
}
}
#[test]
fn test_dependency_injection_pattern_detection() {
let detector = PatternDetector::new();
let file_metrics = FileMetrics {
path: PathBuf::from("service.py"),
language: Language::Python,
complexity: ComplexityMetrics::default(),
debt_items: vec![],
dependencies: vec![],
duplications: vec![],
total_lines: 0,
module_scope: None,
classes: Some(vec![ClassDef {
name: "DatabaseService".to_string(),
base_classes: vec![],
methods: vec![MethodDef {
name: "__init__".to_string(),
is_abstract: false,
decorators: vec!["inject".to_string()],
overrides_base: false,
line: 10,
}],
is_abstract: false,
decorators: vec![],
line: 8,
}]),
};
let patterns = detector.detect_all_patterns(&file_metrics);
let di_patterns: Vec<&PatternInstance> = patterns
.iter()
.filter(|p| p.pattern_type == PatternType::DependencyInjection)
.collect();
assert_eq!(di_patterns.len(), 1, "Expected to find one DI pattern");
assert!(
di_patterns[0].confidence >= 0.5,
"DI pattern should have reasonable confidence"
);
}
#[test]
fn test_pattern_instance_contains_reasoning() {
let detector = PatternDetector::new();
let file_metrics = create_test_file_metrics();
let patterns = detector.detect_all_patterns(&file_metrics);
for pattern in &patterns {
assert!(
!pattern.reasoning.is_empty(),
"Pattern should have reasoning explanation"
);
}
}