mod fakes; use std::path::Path; use archlens_application::queries::AnalyzeCodebase; use archlens_domain::{ AnalysisConfig, AnalysisResult, AnalysisWarning, CodeElement, CodeElementKind, DomainError, FilePath, Language, Relationship, RelationshipKind, SourceFile, }; use fakes::{FakeFileDiscovery, FakeSourceAnalyzer}; #[test] fn analyzes_discovered_files_and_builds_code_graph() { let files = vec![ SourceFile::new(FilePath::new("src/order.rs").unwrap(), Language::Rust), SourceFile::new(FilePath::new("src/service.rs").unwrap(), Language::Rust), ]; let discovery = FakeFileDiscovery::new(files); let analyzer = FakeSourceAnalyzer::new() .with_result( "src/order.rs", AnalysisResult::new( vec![ CodeElement::new( "Order", CodeElementKind::Struct, FilePath::new("src/order.rs").unwrap(), 1, ) .unwrap(), ], vec![], vec![], ), ) .with_result( "src/service.rs", AnalysisResult::new( vec![ CodeElement::new( "OrderService", CodeElementKind::Class, FilePath::new("src/service.rs").unwrap(), 1, ) .unwrap(), ], vec![ Relationship::new("OrderService", "Order", RelationshipKind::Composition) .unwrap(), ], vec![], ), ); let use_case = AnalyzeCodebase::new(discovery, analyzer); let result = use_case .execute(Path::new("."), &AnalysisConfig::default()) .unwrap(); assert_eq!(result.graph().elements().len(), 2); assert_eq!(result.graph().relationships().len(), 1); assert!(result.warnings().is_empty()); } #[test] fn empty_file_list_returns_empty_graph() { let discovery = FakeFileDiscovery::empty(); let analyzer = FakeSourceAnalyzer::new(); let use_case = AnalyzeCodebase::new(discovery, analyzer); let result = use_case .execute(Path::new("."), &AnalysisConfig::default()) .unwrap(); assert!(result.graph().elements().is_empty()); assert!(result.graph().relationships().is_empty()); assert!(result.warnings().is_empty()); } #[test] fn aggregates_warnings_from_multiple_files() { let files = vec![ SourceFile::new(FilePath::new("src/a.rs").unwrap(), Language::Rust), SourceFile::new(FilePath::new("src/b.rs").unwrap(), Language::Rust), ]; let discovery = FakeFileDiscovery::new(files); let analyzer = FakeSourceAnalyzer::new() .with_result( "src/a.rs", AnalysisResult::new( vec![], vec![], vec![ AnalysisWarning::new(FilePath::new("src/a.rs").unwrap(), 10, "unknown macro") .unwrap(), ], ), ) .with_result( "src/b.rs", AnalysisResult::new( vec![], vec![], vec![ AnalysisWarning::new( FilePath::new("src/b.rs").unwrap(), 5, "unparseable block", ) .unwrap(), ], ), ); let use_case = AnalyzeCodebase::new(discovery, analyzer); let result = use_case .execute(Path::new("."), &AnalysisConfig::default()) .unwrap(); assert_eq!(result.warnings().len(), 2); } #[test] fn analysis_error_on_file_collects_warning_and_continues() { let files = vec![ SourceFile::new(FilePath::new("src/good.rs").unwrap(), Language::Rust), SourceFile::new(FilePath::new("src/broken.rs").unwrap(), Language::Rust), ]; let discovery = FakeFileDiscovery::new(files); let analyzer = FakeSourceAnalyzer::new() .with_result( "src/good.rs", AnalysisResult::new( vec![ CodeElement::new( "Good", CodeElementKind::Struct, FilePath::new("src/good.rs").unwrap(), 1, ) .unwrap(), ], vec![], vec![], ), ) .with_error( "src/broken.rs", DomainError::AnalysisError("parse failed".to_string()), ); let use_case = AnalyzeCodebase::new(discovery, analyzer); let result = use_case .execute(Path::new("."), &AnalysisConfig::default()) .unwrap(); assert_eq!(result.graph().elements().len(), 1); assert_eq!(result.graph().elements()[0].name(), "Good"); assert_eq!(result.warnings().len(), 1); } #[test] fn infers_module_from_directory_structure() { let files = vec![ SourceFile::new( FilePath::new("/project/src/orders/service.rs").unwrap(), Language::Rust, ), SourceFile::new( FilePath::new("/project/src/billing/invoice.rs").unwrap(), Language::Rust, ), SourceFile::new( FilePath::new("/project/src/lib.rs").unwrap(), Language::Rust, ), ]; let discovery = FakeFileDiscovery::new(files); let analyzer = FakeSourceAnalyzer::new() .with_result( "/project/src/orders/service.rs", AnalysisResult::new( vec![ CodeElement::new( "OrderService", CodeElementKind::Class, FilePath::new("/project/src/orders/service.rs").unwrap(), 1, ) .unwrap(), ], vec![], vec![], ), ) .with_result( "/project/src/billing/invoice.rs", AnalysisResult::new( vec![ CodeElement::new( "Invoice", CodeElementKind::Struct, FilePath::new("/project/src/billing/invoice.rs").unwrap(), 1, ) .unwrap(), ], vec![], vec![], ), ) .with_result( "/project/src/lib.rs", AnalysisResult::new( vec![ CodeElement::new( "App", CodeElementKind::Struct, FilePath::new("/project/src/lib.rs").unwrap(), 1, ) .unwrap(), ], vec![], vec![], ), ); let use_case = AnalyzeCodebase::new(discovery, analyzer); let result = use_case .execute(Path::new("/project"), &AnalysisConfig::default()) .unwrap(); let order_svc = result .graph() .elements() .iter() .find(|e| e.name() == "OrderService") .unwrap(); assert_eq!(order_svc.module().unwrap().as_str(), "Orders"); let invoice = result .graph() .elements() .iter() .find(|e| e.name() == "Invoice") .unwrap(); assert_eq!(invoice.module().unwrap().as_str(), "Billing"); let app = result .graph() .elements() .iter() .find(|e| e.name() == "App") .unwrap(); assert!(app.module().is_none()); } #[test] fn infers_nested_module_from_deep_directories() { let files = vec![SourceFile::new( FilePath::new("/project/src/orders/models/order.rs").unwrap(), Language::Rust, )]; let discovery = FakeFileDiscovery::new(files); let analyzer = FakeSourceAnalyzer::new().with_result( "/project/src/orders/models/order.rs", AnalysisResult::new( vec![ CodeElement::new( "Order", CodeElementKind::Struct, FilePath::new("/project/src/orders/models/order.rs").unwrap(), 1, ) .unwrap(), ], vec![], vec![], ), ); let use_case = AnalyzeCodebase::new(discovery, analyzer); let result = use_case .execute(Path::new("/project"), &AnalysisConfig::default()) .unwrap(); let order = result .graph() .elements() .iter() .find(|e| e.name() == "Order") .unwrap(); assert_eq!(order.module().unwrap().as_str(), "Orders"); } #[test] fn respects_config_module_mappings() { let files = vec![SourceFile::new( FilePath::new("/project/src/infra/db.rs").unwrap(), Language::Rust, )]; let discovery = FakeFileDiscovery::new(files); let analyzer = FakeSourceAnalyzer::new().with_result( "/project/src/infra/db.rs", AnalysisResult::new( vec![ CodeElement::new( "DbPool", CodeElementKind::Struct, FilePath::new("/project/src/infra/db.rs").unwrap(), 1, ) .unwrap(), ], vec![], vec![], ), ); let mut mappings = std::collections::HashMap::new(); mappings.insert("src/infra".to_string(), "Infrastructure".to_string()); let config = AnalysisConfig::default().with_module_mappings(mappings); let use_case = AnalyzeCodebase::new(discovery, analyzer); let result = use_case.execute(Path::new("/project"), &config).unwrap(); let db = result .graph() .elements() .iter() .find(|e| e.name() == "DbPool") .unwrap(); assert_eq!(db.module().unwrap().as_str(), "Infrastructure"); }