use archlens_domain::{ CodeElement, CodeElementKind, CodeGraph, FilePath, ModuleName, Relationship, RelationshipKind, }; fn make_element(name: &str, module: Option<&str>) -> CodeElement { let mut element = CodeElement::new( name, CodeElementKind::Class, FilePath::new(&format!("src/{name}.rs")).unwrap(), 1, ) .unwrap(); if let Some(m) = module { element = element.with_module(ModuleName::new(m).unwrap()); } element } #[test] fn empty_graph_has_no_elements() { let graph = CodeGraph::new(); assert!(graph.elements().is_empty()); assert!(graph.relationships().is_empty()); } #[test] fn graph_stores_added_elements() { let mut graph = CodeGraph::new(); graph.add_element(make_element("OrderService", None)); graph.add_element(make_element("Order", None)); assert_eq!(graph.elements().len(), 2); } #[test] fn graph_stores_relationships() { let mut graph = CodeGraph::new(); let service = make_element("OrderService", None); let repo = make_element("OrderRepository", None); graph.add_element(service); graph.add_element(repo); graph.add_relationship( Relationship::new( "OrderService", "OrderRepository", RelationshipKind::Composition, ) .unwrap(), ); assert_eq!(graph.relationships().len(), 1); let rel = &graph.relationships()[0]; assert_eq!(rel.source(), "OrderService"); assert_eq!(rel.target(), "OrderRepository"); assert_eq!(rel.kind(), RelationshipKind::Composition); } #[test] fn subgraph_by_module_filters_elements() { let mut graph = CodeGraph::new(); graph.add_element(make_element("OrderService", Some("Orders"))); graph.add_element(make_element("Order", Some("Orders"))); graph.add_element(make_element("BillingService", Some("Billing"))); let module = ModuleName::new("Orders").unwrap(); let subgraph = graph.subgraph_by_module(&module); assert_eq!(subgraph.elements().len(), 2); assert!( subgraph .elements() .iter() .all(|e| e.module().unwrap().as_str() == "Orders") ); } #[test] fn subgraph_includes_relationships_within_module() { let mut graph = CodeGraph::new(); graph.add_element(make_element("OrderService", Some("Orders"))); graph.add_element(make_element("Order", Some("Orders"))); graph.add_element(make_element("BillingService", Some("Billing"))); graph.add_relationship( Relationship::new("OrderService", "Order", RelationshipKind::Composition).unwrap(), ); graph.add_relationship( Relationship::new( "OrderService", "BillingService", RelationshipKind::Composition, ) .unwrap(), ); let module = ModuleName::new("Orders").unwrap(); let subgraph = graph.subgraph_by_module(&module); assert_eq!(subgraph.relationships().len(), 1); assert_eq!(subgraph.relationships()[0].target(), "Order"); } #[test] fn subgraph_of_nonexistent_module_is_empty() { let mut graph = CodeGraph::new(); graph.add_element(make_element("OrderService", Some("Orders"))); let module = ModuleName::new("Unknown").unwrap(); let subgraph = graph.subgraph_by_module(&module); assert!(subgraph.elements().is_empty()); assert!(subgraph.relationships().is_empty()); } #[test] fn qualify_sets_qualified_name_on_elements_with_modules() { let mut graph = CodeGraph::new(); graph.add_element(make_element("DtoBaseModel", Some("Commons"))); graph.add_element(make_element("DtoBaseModel", Some("Api"))); graph.add_element(make_element("Orphan", None)); let graph = graph.qualify(); let commons_dto = graph .elements() .iter() .find(|e| e.module().map(|m| m.as_str()) == Some("Commons")) .unwrap(); assert_eq!(commons_dto.qualified_name(), "Commons::DtoBaseModel"); let api_dto = graph .elements() .iter() .find(|e| e.module().map(|m| m.as_str()) == Some("Api")) .unwrap(); assert_eq!(api_dto.qualified_name(), "Api::DtoBaseModel"); let orphan = graph .elements() .iter() .find(|e| e.name() == "Orphan") .unwrap(); assert_eq!(orphan.qualified_name(), "Orphan"); } #[test] fn qualify_rewrites_unambiguous_relationship_target() { let mut graph = CodeGraph::new(); graph.add_element(make_element("OrderService", Some("App"))); graph.add_element(make_element("Order", Some("Domain"))); graph.add_relationship( Relationship::new("OrderService", "Order", RelationshipKind::Composition).unwrap(), ); let graph = graph.qualify(); let rel = &graph.relationships()[0]; assert_eq!(rel.source(), "App::OrderService"); assert_eq!(rel.target(), "Domain::Order"); } #[test] fn qualify_disambiguates_target_by_source_module() { let mut graph = CodeGraph::new(); // DtoBaseModel exists in both Commons and Api graph.add_element(make_element("DtoBaseModel", Some("Commons"))); graph.add_element(make_element("DtoBaseModel", Some("Api"))); // GlobalAudienceDefinition inherits DtoBaseModel, and is in Commons graph.add_element(make_element("GlobalAudienceDefinition", Some("Commons"))); let mut rel = Relationship::new( "GlobalAudienceDefinition", "DtoBaseModel", RelationshipKind::Inheritance, ) .unwrap(); // source_file is in the Commons module path rel = rel.with_source_file( archlens_domain::FilePath::new("src/commons/global_audience.rs").unwrap(), ); let gad = CodeElement::new( "GlobalAudienceDefinition", archlens_domain::CodeElementKind::Class, archlens_domain::FilePath::new("src/commons/global_audience.rs").unwrap(), 1, ) .unwrap() .with_module(archlens_domain::ModuleName::new("Commons").unwrap()); let mut graph = CodeGraph::new(); graph.add_element(make_element("DtoBaseModel", Some("Commons"))); graph.add_element(make_element("DtoBaseModel", Some("Api"))); graph.add_element(gad); graph.add_relationship(rel); let graph = graph.qualify(); let rel = &graph.relationships()[0]; assert_eq!(rel.source(), "Commons::GlobalAudienceDefinition"); assert_eq!(rel.target(), "Commons::DtoBaseModel"); } #[test] fn resolve_preserves_relationship_when_both_qualified_names_exist() { let mut graph = CodeGraph::new(); graph.add_element(make_element("GlobalAudienceDefinition", Some("Commons"))); graph.add_element(make_element("DtoBaseModel", Some("Commons"))); graph.add_element(make_element("DtoBaseModel", Some("Api"))); graph.add_relationship( Relationship::new( "GlobalAudienceDefinition", "DtoBaseModel", RelationshipKind::Inheritance, ) .unwrap(), ); let graph = graph.qualify().resolve_relationships(); // The relationship should survive — Commons::GlobalAudienceDefinition --> Commons::DtoBaseModel assert_eq!(graph.relationships().len(), 1); assert_eq!( graph.relationships()[0].source(), "Commons::GlobalAudienceDefinition" ); assert_eq!(graph.relationships()[0].target(), "Commons::DtoBaseModel"); } #[test] fn cross_module_deps_for_returns_target_module_with_count() { let mut graph = CodeGraph::new(); graph.add_element(make_element("WidgetJobData", Some("aiss_worker"))); graph.add_element(make_element("WidgetType", Some("commons"))); graph.add_relationship( Relationship::new("WidgetJobData", "WidgetType", RelationshipKind::Composition).unwrap(), ); let module = ModuleName::new("aiss_worker").unwrap(); let deps = graph.cross_module_deps_for(&module); assert_eq!(deps.len(), 1); assert_eq!(deps[0].0.as_str(), "commons"); assert_eq!(deps[0].1, 1); } #[test] fn cross_module_deps_for_returns_empty_for_intra_module_only() { let mut graph = CodeGraph::new(); graph.add_element(make_element("OrderService", Some("Orders"))); graph.add_element(make_element("Order", Some("Orders"))); graph.add_relationship( Relationship::new("OrderService", "Order", RelationshipKind::Composition).unwrap(), ); let module = ModuleName::new("Orders").unwrap(); let deps = graph.cross_module_deps_for(&module); assert!(deps.is_empty()); } #[test] fn cross_module_deps_for_aggregates_multiple_relationships_to_same_module() { let mut graph = CodeGraph::new(); graph.add_element(make_element("ServiceA", Some("app"))); graph.add_element(make_element("ServiceB", Some("app"))); graph.add_element(make_element("DomainType1", Some("domain"))); graph.add_element(make_element("DomainType2", Some("domain"))); graph.add_relationship( Relationship::new("ServiceA", "DomainType1", RelationshipKind::Composition).unwrap(), ); graph.add_relationship( Relationship::new("ServiceB", "DomainType2", RelationshipKind::Composition).unwrap(), ); let module = ModuleName::new("app").unwrap(); let deps = graph.cross_module_deps_for(&module); assert_eq!(deps.len(), 1); assert_eq!(deps[0].0.as_str(), "domain"); assert_eq!(deps[0].1, 2); } #[test] fn graph_lists_unique_modules() { let mut graph = CodeGraph::new(); graph.add_element(make_element("OrderService", Some("Orders"))); graph.add_element(make_element("Order", Some("Orders"))); graph.add_element(make_element("BillingService", Some("Billing"))); graph.add_element(make_element("Orphan", None)); let modules = graph.modules(); assert_eq!(modules.len(), 2); assert!(modules.iter().any(|m| m.as_str() == "Orders")); assert!(modules.iter().any(|m| m.as_str() == "Billing")); }