mod analysis;
mod categorization;
mod clustering;
mod types;
pub use types::{BehaviorCategory, FieldAccessStats, MethodCluster};
pub use categorization::{cluster_methods_by_behavior, is_test_method, BehavioralCategorizer};
pub use clustering::{
apply_community_detection, apply_hybrid_clustering, apply_production_ready_clustering,
build_method_call_adjacency_matrix, build_method_call_adjacency_matrix_with_functions,
ClusteringResult, ClusteringWarning,
};
pub use analysis::{
detect_service_candidates, recommend_service_extraction, suggest_trait_extraction,
FieldAccessTracker,
};
#[cfg(test)]
mod tests {
use super::*;
use std::collections::{HashMap, HashSet};
use syn::ItemImpl;
#[test]
fn test_categorize_lifecycle_methods() {
assert_eq!(
BehavioralCategorizer::categorize_method("new"),
BehaviorCategory::Construction
);
assert_eq!(
BehavioralCategorizer::categorize_method("initialize_system"),
BehaviorCategory::Lifecycle
);
assert_eq!(
BehavioralCategorizer::categorize_method("cleanup"),
BehaviorCategory::Lifecycle
);
}
#[test]
fn test_categorize_parsing_methods() {
assert_eq!(
BehavioralCategorizer::categorize_method("parse_json"),
BehaviorCategory::Parsing
);
assert_eq!(
BehavioralCategorizer::categorize_method("read_config"),
BehaviorCategory::Parsing
);
assert_eq!(
BehavioralCategorizer::categorize_method("extract_data"),
BehaviorCategory::Parsing
);
assert_eq!(
BehavioralCategorizer::categorize_method("decode_message"),
BehaviorCategory::Parsing
);
}
#[test]
fn test_categorize_construction_methods() {
assert_eq!(
BehavioralCategorizer::categorize_method("create_instance"),
BehaviorCategory::Construction
);
assert_eq!(
BehavioralCategorizer::categorize_method("build_object"),
BehaviorCategory::Construction
);
assert_eq!(
BehavioralCategorizer::categorize_method("make_widget"),
BehaviorCategory::Construction
);
}
#[test]
fn test_categorize_filtering_methods() {
assert_eq!(
BehavioralCategorizer::categorize_method("filter_results"),
BehaviorCategory::Filtering
);
assert_eq!(
BehavioralCategorizer::categorize_method("select_items"),
BehaviorCategory::Filtering
);
assert_eq!(
BehavioralCategorizer::categorize_method("find_matches"),
BehaviorCategory::Filtering
);
assert_eq!(
BehavioralCategorizer::categorize_method("search_database"),
BehaviorCategory::Filtering
);
}
#[test]
fn test_categorize_transformation_methods() {
assert_eq!(
BehavioralCategorizer::categorize_method("transform_data"),
BehaviorCategory::Transformation
);
assert_eq!(
BehavioralCategorizer::categorize_method("convert_to_json"),
BehaviorCategory::Transformation
);
assert_eq!(
BehavioralCategorizer::categorize_method("map_values"),
BehaviorCategory::Transformation
);
assert_eq!(
BehavioralCategorizer::categorize_method("apply_transformation"),
BehaviorCategory::Transformation
);
}
#[test]
fn test_categorize_data_access_methods() {
assert_eq!(
BehavioralCategorizer::categorize_method("get_value"),
BehaviorCategory::DataAccess
);
assert_eq!(
BehavioralCategorizer::categorize_method("set_property"),
BehaviorCategory::DataAccess
);
assert_eq!(
BehavioralCategorizer::categorize_method("fetch_record"),
BehaviorCategory::DataAccess
);
assert_eq!(
BehavioralCategorizer::categorize_method("retrieve_data"),
BehaviorCategory::DataAccess
);
}
#[test]
fn test_categorize_processing_methods() {
assert_eq!(
BehavioralCategorizer::categorize_method("process_request"),
BehaviorCategory::Processing
);
assert_eq!(
BehavioralCategorizer::categorize_method("process_message"),
BehaviorCategory::Processing
);
assert_eq!(
BehavioralCategorizer::categorize_method("execute_task"),
BehaviorCategory::Processing
);
assert_eq!(
BehavioralCategorizer::categorize_method("run_pipeline"),
BehaviorCategory::Processing
);
}
#[test]
fn test_categorize_communication_methods() {
assert_eq!(
BehavioralCategorizer::categorize_method("send_message"),
BehaviorCategory::Communication
);
assert_eq!(
BehavioralCategorizer::categorize_method("receive_data"),
BehaviorCategory::Communication
);
assert_eq!(
BehavioralCategorizer::categorize_method("transmit_packet"),
BehaviorCategory::Communication
);
assert_eq!(
BehavioralCategorizer::categorize_method("broadcast_update"),
BehaviorCategory::Communication
);
}
#[test]
fn test_categorize_rendering_methods() {
assert_eq!(
BehavioralCategorizer::categorize_method("render"),
BehaviorCategory::Rendering
);
assert_eq!(
BehavioralCategorizer::categorize_method("draw_cursor"),
BehaviorCategory::Rendering
);
assert_eq!(
BehavioralCategorizer::categorize_method("paint_background"),
BehaviorCategory::Rendering
);
}
#[test]
fn test_categorize_event_handling() {
assert_eq!(
BehavioralCategorizer::categorize_method("handle_keypress"),
BehaviorCategory::EventHandling
);
assert_eq!(
BehavioralCategorizer::categorize_method("on_mouse_down"),
BehaviorCategory::EventHandling
);
assert_eq!(
BehavioralCategorizer::categorize_method("dispatch_event"),
BehaviorCategory::EventHandling
);
}
#[test]
fn test_categorize_persistence() {
assert_eq!(
BehavioralCategorizer::categorize_method("save_state"),
BehaviorCategory::Persistence
);
assert_eq!(
BehavioralCategorizer::categorize_method("load_config"),
BehaviorCategory::Persistence
);
assert_eq!(
BehavioralCategorizer::categorize_method("serialize"),
BehaviorCategory::Persistence
);
}
#[test]
fn test_categorize_validation() {
assert_eq!(
BehavioralCategorizer::categorize_method("validate_input"),
BehaviorCategory::Validation
);
assert_eq!(
BehavioralCategorizer::categorize_method("check_bounds"),
BehaviorCategory::Validation
);
assert_eq!(
BehavioralCategorizer::categorize_method("verify_signature"),
BehaviorCategory::Validation
);
}
#[test]
fn test_categorize_state_management() {
assert_eq!(
BehavioralCategorizer::categorize_method("get_value"),
BehaviorCategory::DataAccess
);
assert_eq!(
BehavioralCategorizer::categorize_method("set_name"),
BehaviorCategory::DataAccess
);
assert_eq!(
BehavioralCategorizer::categorize_method("update_state"),
BehaviorCategory::StateManagement
);
}
#[test]
fn test_cluster_methods_by_behavior() {
let methods = vec![
"render".to_string(),
"draw_cursor".to_string(),
"handle_keypress".to_string(),
"on_mouse_down".to_string(),
"validate_input".to_string(),
"get_value".to_string(),
"set_name".to_string(),
];
let clusters = cluster_methods_by_behavior(&methods);
assert!(clusters.contains_key(&BehaviorCategory::Rendering));
assert!(clusters.contains_key(&BehaviorCategory::EventHandling));
assert!(clusters.contains_key(&BehaviorCategory::DataAccess));
assert_eq!(clusters.get(&BehaviorCategory::Rendering).unwrap().len(), 2);
assert_eq!(
clusters
.get(&BehaviorCategory::EventHandling)
.unwrap()
.len(),
2
);
assert_eq!(
clusters.get(&BehaviorCategory::DataAccess).unwrap().len(),
2
);
}
#[test]
fn test_method_cluster_cohesion() {
let mut cluster = MethodCluster {
category: BehaviorCategory::Rendering,
methods: vec!["render".to_string(), "draw".to_string()],
fields_accessed: vec!["display_map".to_string()],
internal_calls: 8,
external_calls: 2,
cohesion_score: 0.0,
};
cluster.calculate_cohesion();
assert_eq!(cluster.cohesion_score, 0.8); }
#[test]
fn test_good_extraction_candidate() {
let cluster = MethodCluster {
category: BehaviorCategory::Rendering,
methods: (0..15).map(|i| format!("method{}", i)).collect(),
fields_accessed: vec!["field1".to_string(), "field2".to_string()],
internal_calls: 20,
external_calls: 5,
cohesion_score: 0.8,
};
assert!(cluster.is_good_extraction_candidate(10)); assert!(!cluster.is_good_extraction_candidate(5)); }
#[test]
fn test_suggest_trait_extraction() {
let cluster = MethodCluster {
category: BehaviorCategory::Rendering,
methods: vec![
"render".to_string(),
"draw_cursor".to_string(),
"paint_background".to_string(),
],
fields_accessed: vec![],
internal_calls: 0,
external_calls: 0,
cohesion_score: 0.0,
};
let suggestion = suggest_trait_extraction(&cluster, "Editor");
assert!(suggestion.contains("trait Renderable"));
assert!(suggestion.contains("fn render(&self);"));
assert!(suggestion.contains("3 methods total"));
}
#[test]
fn test_field_access_tracking() {
let code = quote::quote! {
impl Editor {
fn render(&self) {
let x = self.display_map;
let y = self.cursor_position;
}
fn handle_input(&mut self) {
self.input_buffer.clear();
}
fn save(&self) {
let path = self.file_path;
}
}
};
let impl_block: ItemImpl = syn::parse2(code).unwrap();
let mut tracker = FieldAccessTracker::new();
tracker.analyze_impl(&impl_block);
let render_fields = tracker.get_method_fields("render");
assert_eq!(render_fields, vec!["cursor_position", "display_map"]);
let input_fields = tracker.get_method_fields("handle_input");
assert_eq!(input_fields, vec!["input_buffer"]);
let save_fields = tracker.get_method_fields("save");
assert_eq!(save_fields, vec!["file_path"]);
}
#[test]
fn test_minimal_field_set() {
let code = quote::quote! {
impl Editor {
fn render(&self) {
let x = self.display_map;
let y = self.cursor_position;
}
fn draw(&self) {
let z = self.display_map;
}
}
};
let impl_block: ItemImpl = syn::parse2(code).unwrap();
let mut tracker = FieldAccessTracker::new();
tracker.analyze_impl(&impl_block);
let methods = vec!["render".to_string(), "draw".to_string()];
let minimal_fields = tracker.get_minimal_field_set(&methods);
assert_eq!(minimal_fields, vec!["cursor_position", "display_map"]);
}
#[test]
fn test_hybrid_clustering_lcov_like_example() {
let code = quote::quote! {
pub struct LcovData {
file_index: HashMap<String, Vec<String>>,
function_cache: HashMap<String, CoverageData>,
loc_counter: Option<LocCounter>,
}
impl LcovData {
pub fn new() -> Self {
Self {
file_index: HashMap::new(),
function_cache: HashMap::new(),
loc_counter: None,
}
}
pub fn create_empty() -> Self {
Self::new()
}
pub fn initialize(&mut self) {
self.build_index();
}
pub fn build_index(&mut self) {
}
pub fn with_loc_counter(mut self, counter: LocCounter) -> Self {
self.loc_counter = Some(counter);
self
}
pub fn get_function_coverage(&self, file: &str, function: &str) -> Option<f64> {
let funcs = self.find_functions_by_path(file)?;
self.find_function_by_name(funcs, function)
}
pub fn get_file_coverage(&self, file: &str) -> Option<f64> {
let funcs = self.find_functions_by_path(file)?;
Some(self.calculate_average(funcs))
}
pub fn get_overall_coverage(&self) -> f64 {
let all_files = self.get_all_files();
self.calculate_weighted_average(&all_files)
}
pub fn batch_get_function_coverage(&self, queries: Vec<Query>) -> Vec<f64> {
queries.iter().map(|q| {
self.get_function_coverage(&q.file, &q.func).unwrap_or(0.0)
}).collect()
}
fn find_functions_by_path(&self, path: &str) -> Option<Vec<String>> {
if self.matches_suffix_strategy(path) {
Some(vec![])
} else {
self.apply_strategies_parallel(path)
}
}
fn matches_suffix_strategy(&self, path: &str) -> bool {
let normalized = normalize_path(path);
self.file_index.contains_key(&normalized)
}
fn apply_strategies_parallel(&self, path: &str) -> Option<Vec<String>> {
let results = self.apply_strategies_sequential(path);
results
}
fn apply_strategies_sequential(&self, path: &str) -> Option<Vec<String>> {
if self.matches_reverse_suffix(path) {
Some(vec![])
} else {
None
}
}
fn matches_reverse_suffix(&self, path: &str) -> bool {
false
}
fn find_function_by_name(&self, funcs: Vec<String>, name: &str) -> Option<f64> {
let normalized = normalize_function_name(name);
Some(0.5)
}
fn calculate_average(&self, funcs: Vec<String>) -> f64 {
0.75
}
fn calculate_weighted_average(&self, files: &[String]) -> f64 {
0.85
}
fn get_all_files(&self) -> Vec<String> {
vec![]
}
}
fn normalize_path(path: &str) -> String {
demangle_path_components(path)
}
fn demangle_path_components(path: &str) -> String {
path.to_lowercase()
}
fn normalize_function_name(name: &str) -> String {
demangle_function_name(name)
}
fn demangle_function_name(name: &str) -> String {
strip_trailing_generics(name)
}
fn strip_trailing_generics(name: &str) -> String {
name.trim_end_matches(">").to_string()
}
pub fn parse_lcov_file(path: &str) -> Result<LcovData, String> {
parse_lcov_file_with_progress(path, &ProgressBar::new())
}
pub fn parse_lcov_file_with_progress(path: &str, progress: &ProgressBar) -> Result<LcovData, String> {
let data = parse_coverage_data(path)?;
calculate_function_coverage_data(data)
}
fn parse_coverage_data(path: &str) -> Result<Vec<String>, String> {
Ok(vec![])
}
fn process_function_coverage_parallel(path: &str) -> Result<Vec<String>, String> {
Ok(vec![])
}
fn calculate_function_coverage_data(data: Vec<String>) -> Result<LcovData, String> {
Ok(LcovData::new())
}
};
let ast: syn::File = syn::parse2(code).unwrap();
let impl_blocks: Vec<&syn::ItemImpl> = ast
.items
.iter()
.filter_map(|item| {
if let syn::Item::Impl(impl_block) = item {
Some(impl_block)
} else {
None
}
})
.collect();
let standalone_functions: Vec<&syn::ItemFn> = ast
.items
.iter()
.filter_map(|item| {
if let syn::Item::Fn(func) = item {
Some(func)
} else {
None
}
})
.collect();
let mut all_methods = Vec::new();
for impl_block in &impl_blocks {
for item in &impl_block.items {
if let syn::ImplItem::Fn(method) = item {
all_methods.push(method.sig.ident.to_string());
}
}
}
for func in &standalone_functions {
all_methods.push(func.sig.ident.to_string());
}
let adjacency =
build_method_call_adjacency_matrix_with_functions(&impl_blocks, &standalone_functions);
let clusters = apply_hybrid_clustering(&all_methods, &adjacency);
assert!(
clusters.len() >= 3,
"Expected at least 3 behavioral clusters, but found {}. Clusters: {:?}",
clusters.len(),
clusters
.iter()
.map(|c| (c.category.display_name(), c.methods.len()))
.collect::<Vec<_>>()
);
let categories: HashSet<String> =
clusters.iter().map(|c| c.category.display_name()).collect();
assert!(
categories.len() >= 2,
"Expected diverse behavioral categories, but found only: {:?}",
categories
);
let construction_cluster = clusters
.iter()
.find(|c| matches!(c.category, BehaviorCategory::Construction));
assert!(
construction_cluster.is_some(),
"Expected to find Construction cluster for methods like 'new', 'create_empty', 'build_index'"
);
let data_access_cluster = clusters
.iter()
.find(|c| matches!(c.category, BehaviorCategory::DataAccess));
assert!(
data_access_cluster.is_some(),
"Expected to find DataAccess cluster for get_* methods"
);
let parsing_cluster = clusters
.iter()
.find(|c| matches!(c.category, BehaviorCategory::Parsing));
assert!(
parsing_cluster.is_some(),
"Expected to find Parsing cluster for parse_* methods"
);
let normalize_cluster = clusters
.iter()
.find(|c| c.methods.contains(&"normalize_path".to_string()));
if let Some(cluster) = normalize_cluster {
let has_related_demangle = cluster.methods.iter().any(|m| m.contains("demangle"));
assert!(
has_related_demangle || cluster.methods.len() >= 3,
"Expected normalize functions to be clustered together or in a reasonable cluster"
);
}
println!("\n=== Hybrid Clustering Results ===");
for (i, cluster) in clusters.iter().enumerate() {
println!(
"Cluster {}: {} ({} methods, cohesion: {:.2})",
i + 1,
cluster.category.display_name(),
cluster.methods.len(),
cluster.cohesion_score
);
println!(" Methods: {:?}", cluster.methods);
}
println!("=================================\n");
}
#[test]
fn test_production_ready_clustering_filters_tests() {
let methods = vec![
"parse_lcov_file".to_string(),
"parse_lcov_file_with_progress".to_string(),
"parse_coverage_data".to_string(),
"read_file_contents".to_string(),
"get_function_coverage".to_string(),
"get_file_coverage".to_string(),
"get_overall_coverage".to_string(),
"get_all_files".to_string(),
"fetch_coverage_data".to_string(),
"normalize_path".to_string(),
"normalize_function_name".to_string(),
"normalize_demangled_name".to_string(),
"find_function_by_name".to_string(),
"find_functions_by_path".to_string(),
"find_function_by_line".to_string(),
"test_parse_lcov_file".to_string(),
"test_function_name_normalization".to_string(),
"test_coverage_calculation".to_string(),
"test_empty_data".to_string(),
"mock_coverage_data".to_string(),
"fixture_test_file".to_string(),
];
let adjacency = HashMap::new();
let result = apply_production_ready_clustering(&methods, &adjacency);
let clusters = result.clusters;
let all_cluster_methods: Vec<&String> = clusters.iter().flat_map(|c| &c.methods).collect();
assert!(
!all_cluster_methods.contains(&&"test_parse_lcov_file".to_string()),
"Test methods should be filtered out"
);
assert!(
!all_cluster_methods.contains(&&"mock_coverage_data".to_string()),
"Test helper methods should be filtered out"
);
assert!(
all_cluster_methods.contains(&&"parse_lcov_file".to_string()),
"Production methods should be included"
);
assert!(
all_cluster_methods.contains(&&"get_function_coverage".to_string()),
"Production methods should be included"
);
assert!(
clusters.len() >= 3,
"Should have multiple clusters, found {}",
clusters.len()
);
let has_good_categories = clusters.iter().all(|c| {
!matches!(
c.category,
BehaviorCategory::Domain(ref name) if name == "Utilities" || name == "Operations"
)
});
assert!(
has_good_categories,
"All clusters should have meaningful categories (not Utilities/Operations)"
);
println!("\n=== Production-Ready Clustering Results ===");
println!("Total clusters: {}", clusters.len());
println!(
"Production methods: {} / {} total",
all_cluster_methods.len(),
methods.len()
);
for (i, cluster) in clusters.iter().enumerate() {
println!(
"Cluster {}: {} ({} methods)",
i + 1,
cluster.category.display_name(),
cluster.methods.len()
);
println!(" Methods: {:?}", cluster.methods);
}
println!("==========================================\n");
}
#[test]
fn test_no_method_loss_and_minimum_cluster_size() {
let methods = vec![
"render_text".to_string(),
"render_cursor".to_string(),
"paint_highlights".to_string(),
"draw_gutter".to_string(),
"format_timestamp".to_string(),
"clamp_value".to_string(),
"validate_config".to_string(),
"get_state".to_string(),
"set_state".to_string(),
];
let adjacency = HashMap::from([
(
("render_text".to_string(), "paint_highlights".to_string()),
1,
),
(("render_cursor".to_string(), "draw_gutter".to_string()), 1),
(("set_state".to_string(), "get_state".to_string()), 1),
]);
let result = apply_production_ready_clustering(&methods, &adjacency);
let clusters = result.clusters;
let clustered_methods: std::collections::HashSet<String> =
clusters.iter().flat_map(|c| &c.methods).cloned().collect();
for method in &methods {
assert!(
clustered_methods.contains(method),
"Method '{}' was lost during clustering!",
method
);
}
assert_eq!(
clustered_methods.len(),
methods.len(),
"Total methods mismatch: {} clustered vs {} input",
clustered_methods.len(),
methods.len()
);
for cluster in &clusters {
assert!(
cluster.methods.len() >= 3,
"Cluster '{}' has only {} methods (minimum is 3)",
cluster.category.display_name(),
cluster.methods.len()
);
}
assert!(
clustered_methods.contains("format_timestamp"),
"Low-cohesion method 'format_timestamp' should be kept"
);
assert!(
clustered_methods.contains("clamp_value"),
"Low-cohesion method 'clamp_value' should be kept"
);
println!("\n=== No Method Loss Test Results ===");
println!("Total input methods: {}", methods.len());
println!("Total clustered methods: {}", clustered_methods.len());
println!("Clusters created: {}", clusters.len());
for (i, cluster) in clusters.iter().enumerate() {
println!(
"Cluster {}: {} ({} methods, cohesion: {:.2})",
i + 1,
cluster.category.display_name(),
cluster.methods.len(),
cluster.cohesion_score
);
println!(" Methods: {:?}", cluster.methods);
}
println!("=====================================\n");
}
#[test]
fn test_is_parsing_predicate() {
assert!(BehavioralCategorizer::is_parsing("parse_json"));
assert!(BehavioralCategorizer::is_parsing("read_file"));
assert!(BehavioralCategorizer::is_parsing("extract_data"));
assert!(BehavioralCategorizer::is_parsing("decode_base64"));
assert!(BehavioralCategorizer::is_parsing("deserialize_xml"));
assert!(BehavioralCategorizer::is_parsing("unmarshal_proto"));
assert!(BehavioralCategorizer::is_parsing("scan_tokens"));
assert!(!BehavioralCategorizer::is_parsing("render_view"));
assert!(!BehavioralCategorizer::is_parsing("calculate_sum"));
assert!(!BehavioralCategorizer::is_parsing("validate_input"));
}
#[test]
fn test_is_rendering_predicate() {
assert!(BehavioralCategorizer::is_rendering("render_template"));
assert!(BehavioralCategorizer::is_rendering("draw_rectangle"));
assert!(BehavioralCategorizer::is_rendering("paint_canvas"));
assert!(BehavioralCategorizer::is_rendering("display_message"));
assert!(BehavioralCategorizer::is_rendering("show_dialog"));
assert!(BehavioralCategorizer::is_rendering("present_view"));
assert!(BehavioralCategorizer::is_rendering("format_output"));
assert!(BehavioralCategorizer::is_rendering("to_string"));
assert!(BehavioralCategorizer::is_rendering("print_report"));
assert!(!BehavioralCategorizer::is_rendering("parse_json"));
assert!(!BehavioralCategorizer::is_rendering("calculate_sum"));
assert!(!BehavioralCategorizer::is_rendering("validate_input"));
}
#[test]
fn test_is_filtering_predicate() {
assert!(BehavioralCategorizer::is_filtering("filter_results"));
assert!(BehavioralCategorizer::is_filtering("select_items"));
assert!(BehavioralCategorizer::is_filtering("find_matches"));
assert!(BehavioralCategorizer::is_filtering("search_database"));
assert!(BehavioralCategorizer::is_filtering("query_records"));
assert!(BehavioralCategorizer::is_filtering("lookup_value"));
assert!(BehavioralCategorizer::is_filtering("match_pattern"));
assert!(!BehavioralCategorizer::is_filtering("parse_json"));
assert!(!BehavioralCategorizer::is_filtering("render_view"));
assert!(!BehavioralCategorizer::is_filtering("calculate_sum"));
}
#[test]
fn test_is_transformation_predicate() {
assert!(BehavioralCategorizer::is_transformation("transform_data"));
assert!(BehavioralCategorizer::is_transformation("convert_format"));
assert!(BehavioralCategorizer::is_transformation("map_values"));
assert!(BehavioralCategorizer::is_transformation("apply_rules"));
assert!(BehavioralCategorizer::is_transformation("adapt_schema"));
assert!(!BehavioralCategorizer::is_transformation("parse_json"));
assert!(!BehavioralCategorizer::is_transformation("filter_results"));
assert!(!BehavioralCategorizer::is_transformation("validate_input"));
}
#[test]
fn test_is_construction_predicate() {
assert!(BehavioralCategorizer::is_construction("create_instance"));
assert!(BehavioralCategorizer::is_construction("build_object"));
assert!(BehavioralCategorizer::is_construction("new_connection"));
assert!(BehavioralCategorizer::is_construction("make_widget"));
assert!(BehavioralCategorizer::is_construction("construct_tree"));
assert!(!BehavioralCategorizer::is_construction("parse_json"));
assert!(!BehavioralCategorizer::is_construction("render_view"));
assert!(!BehavioralCategorizer::is_construction("validate_input"));
}
#[test]
fn test_is_data_access_predicate() {
assert!(BehavioralCategorizer::is_data_access("get_value"));
assert!(BehavioralCategorizer::is_data_access("set_property"));
assert!(BehavioralCategorizer::is_data_access("fetch_record"));
assert!(BehavioralCategorizer::is_data_access("retrieve_data"));
assert!(BehavioralCategorizer::is_data_access("access_field"));
assert!(!BehavioralCategorizer::is_data_access("parse_json"));
assert!(!BehavioralCategorizer::is_data_access("render_view"));
assert!(!BehavioralCategorizer::is_data_access("validate_input"));
}
#[test]
fn test_is_communication_predicate() {
assert!(BehavioralCategorizer::is_communication("send_message"));
assert!(BehavioralCategorizer::is_communication("receive_data"));
assert!(BehavioralCategorizer::is_communication("transmit_packet"));
assert!(BehavioralCategorizer::is_communication("broadcast_event"));
assert!(BehavioralCategorizer::is_communication("notify_observers"));
assert!(!BehavioralCategorizer::is_communication("parse_json"));
assert!(!BehavioralCategorizer::is_communication("render_view"));
assert!(!BehavioralCategorizer::is_communication("validate_input"));
}
#[test]
fn test_no_duplicate_responsibilities_integration() {
let methods = vec![
"format_output".to_string(),
"format_json".to_string(),
"render_view".to_string(),
"draw_chart".to_string(),
"parse_json".to_string(),
"parse_xml".to_string(),
"read_config".to_string(),
"get_value".to_string(),
"set_property".to_string(),
"fetch_record".to_string(),
"validate_input".to_string(),
"check_bounds".to_string(),
"is_valid".to_string(),
];
let clusters = cluster_methods_by_behavior(&methods);
let category_names: Vec<String> = clusters.keys().map(|cat| cat.display_name()).collect();
let mut seen_lower = std::collections::HashSet::new();
let mut duplicates = Vec::new();
for name in &category_names {
let lower = name.to_lowercase();
if seen_lower.contains(&lower) {
duplicates.push(name.clone());
}
seen_lower.insert(lower);
}
assert!(
duplicates.is_empty(),
"Found duplicate responsibilities with different capitalizations: {:?}\nAll categories: {:?}",
duplicates,
category_names
);
for name in &category_names {
let first_char = name.chars().next().unwrap();
assert!(
first_char.is_uppercase(),
"Category '{}' should start with uppercase letter (Title Case)",
name
);
}
let has_rendering = category_names.iter().any(|n| n == "Rendering");
let has_parsing = category_names.iter().any(|n| n == "Parsing");
let has_data_access = category_names.iter().any(|n| n == "Data Access");
let has_validation = category_names.iter().any(|n| n == "Validation");
assert!(has_rendering, "Expected 'Rendering' category (Title Case)");
assert!(has_parsing, "Expected 'Parsing' category (Title Case)");
assert!(
has_data_access,
"Expected 'Data Access' category (Title Case)"
);
assert!(
has_validation,
"Expected 'Validation' category (Title Case)"
);
assert!(
!category_names.iter().any(|n| n == "output"),
"Should not have lowercase 'output' category"
);
assert!(
!category_names.iter().any(|n| n == "parsing"),
"Should not have lowercase 'parsing' category"
);
assert!(
!category_names.iter().any(|n| n == "data_access"),
"Should not have snake_case 'data_access' category"
);
assert!(
!category_names.iter().any(|n| n == "validation"),
"Should not have lowercase 'validation' category"
);
}
}