From 2d9334744c2f5c3b106b8e40153f31ba4af6f02c Mon Sep 17 00:00:00 2001 From: Melody Ma Date: Fri, 17 Jul 2026 11:16:07 +0800 Subject: [PATCH 1/2] fix(validation): check only component internal interfaces --- .../core/src/models/class_diagram_models.rs | 10 +-- .../core/src/profiles/architectural_design.rs | 2 +- validation/core/src/report.rs | 37 +++++++++- .../component_internal_api_validator.rs | 6 +- .../component_internal_api_validator_test.rs | 69 ++++++++++++++----- .../core/src/validators/test/fixtures.rs | 19 ++--- 6 files changed, 100 insertions(+), 43 deletions(-) diff --git a/validation/core/src/models/class_diagram_models.rs b/validation/core/src/models/class_diagram_models.rs index 4957d8f2..7609f599 100644 --- a/validation/core/src/models/class_diagram_models.rs +++ b/validation/core/src/models/class_diagram_models.rs @@ -76,7 +76,7 @@ pub struct InternalApiIndex { impl InternalApiIndex { /// Build an [`InternalApiIndex`] from internal-API diagram inputs. - pub fn build_index(diagrams: &[ClassDiagramInput], _result: &mut ValidationResult) -> Self { + pub fn build_index(diagrams: &[ClassDiagramInput]) -> Self { let mut interfaces = Vec::new(); for diagram in diagrams { @@ -211,10 +211,8 @@ mod tests { ], }]; - let mut result = ValidationResult::default(); - let index = InternalApiIndex::build_index(&diagrams, &mut result); + let index = InternalApiIndex::build_index(&diagrams); - assert!(result.is_empty()); assert!(index .interfaces() .find(|interface| interface.id == "InternalAPI.InternalInterface") @@ -260,10 +258,8 @@ mod tests { ], }]; - let mut result = ValidationResult::default(); - let index = InternalApiIndex::build_index(&diagrams, &mut result); + let index = InternalApiIndex::build_index(&diagrams); - assert!(result.is_empty()); let interface_ids: BTreeSet<&str> = index .interfaces() .map(|interface| interface.id.as_str()) diff --git a/validation/core/src/profiles/architectural_design.rs b/validation/core/src/profiles/architectural_design.rs index 3275e677..7affe74c 100644 --- a/validation/core/src/profiles/architectural_design.rs +++ b/validation/core/src/profiles/architectural_design.rs @@ -75,7 +75,7 @@ pub fn run(inputs: &ArchitecturalDesignInputs) -> Result { let internal_api = read_and_convert::( inputs.internal_api_diagrams.as_slice(), &mut result, - |raw: ClassDiagramInputs, errs| InternalApiIndex::build_index(&raw, errs), + |raw: ClassDiagramInputs, _result| InternalApiIndex::build_index(&raw), )?; let validators = registered_validators(&component, &sequence, &internal_api); diff --git a/validation/core/src/report.rs b/validation/core/src/report.rs index 2852f97e..320370a7 100644 --- a/validation/core/src/report.rs +++ b/validation/core/src/report.rs @@ -21,7 +21,7 @@ pub fn finish_profile_validation( profile: Profile, profile_run: &ProfileRun, ) -> Result<(), String> { - if !profile_run.ran_validator { + if !profile_run.ran_validator && profile_run.result.is_empty() { log::info!( "Skipping validation profile {}: no selected validators have their required inputs.", profile.as_str() @@ -107,3 +107,38 @@ fn format_error_details(profile_run: &ProfileRun, prefix: &str) -> String { .collect::>() .join("\n\n") } + +#[cfg(test)] +mod tests { + use super::*; + use validation::ValidationResult; + + #[test] + fn skips_when_no_validator_ran_and_result_is_empty() { + let profile_run = ProfileRun { + ran_validator: false, + result: ValidationResult::default(), + }; + + let result = + finish_profile_validation(None, false, Profile::ArchitecturalDesign, &profile_run); + + assert!(result.is_ok()); + } + + #[test] + fn reports_failure_when_no_validator_ran_but_read_model_failed() { + let mut validation_result = ValidationResult::default(); + validation_result.add_failure("read model failed".to_string()); + let profile_run = ProfileRun { + ran_validator: false, + result: validation_result, + }; + + let result = + finish_profile_validation(None, false, Profile::ArchitecturalDesign, &profile_run); + + assert!(result.is_err()); + assert!(result.unwrap_err().contains("read model failed")); + } +} diff --git a/validation/core/src/validators/component_internal_api_validator.rs b/validation/core/src/validators/component_internal_api_validator.rs index 5e10ef55..22bfa6e1 100644 --- a/validation/core/src/validators/component_internal_api_validator.rs +++ b/validation/core/src/validators/component_internal_api_validator.rs @@ -40,7 +40,7 @@ impl ComponentInternalApiValidator { internal_api_diagram: &InternalApiIndex, ) -> Self { Self { - component_interface_ids: collect_component_interface_ids(component_diagram), + component_interface_ids: collect_component_internal_interface_ids(component_diagram), internal_api_interface_ids: collect_internal_api_interface_ids(internal_api_diagram), result: ValidationResult::default(), } @@ -88,13 +88,13 @@ fn append_debug_log( } } -fn collect_component_interface_ids( +fn collect_component_internal_interface_ids( component_diagram: &ComponentDiagramArchitecture, ) -> BTreeSet { component_diagram .entities .iter() - .filter(|entity| entity.is_interface()) + .filter(|entity| entity.is_interface() && entity.parent_id.is_some()) .map(|entity| entity.id.clone()) .collect() } diff --git a/validation/core/src/validators/test/component_internal_api_validator_test.rs b/validation/core/src/validators/test/component_internal_api_validator_test.rs index 5f84e7a1..19f441c4 100644 --- a/validation/core/src/validators/test/component_internal_api_validator_test.rs +++ b/validation/core/src/validators/test/component_internal_api_validator_test.rs @@ -21,7 +21,11 @@ fn validate( ) -> ValidationResult { let mut setup_result = ValidationResult::default(); let component_arch = component_diagrams.to_diagram_architecture(&mut setup_result); - + assert!( + setup_result.is_empty(), + "test fixture setup failed: {:?}", + setup_result.failures + ); validate_component_internal_api(&component_arch, internal_api) } @@ -30,7 +34,7 @@ fn reports_missing_component_interface_declared_by_internal_api() { let component_diagrams = component_diagrams_with_entities(vec![ unit("u1", &["InternalInterface"]), unit("u2", &["InternalInterface"]), - interface("InternalInterface"), + interface_with_parent("InternalInterface", Some("component_example")), ]); let internal_api = internal_api_index(vec![("OtherInterface", vec!["GetData"])]); @@ -38,7 +42,8 @@ fn reports_missing_component_interface_declared_by_internal_api() { assert_eq!(validation_result.failures.len(), 1); assert!(validation_result.failures[0].contains("Missing internal API interface")); - assert!(validation_result.failures[0].contains("Missing interfaces : \"InternalInterface\"")); + assert!(validation_result.failures[0] + .contains("Missing interfaces : \"component_example.InternalInterface\"")); assert!(!validation_result.failures[0].contains("Unit :")); } @@ -47,29 +52,36 @@ fn reports_each_missing_component_interface_once() { let component_diagrams = component_diagrams_with_entities(vec![ unit("u1", &["InternalInterface", "InternalInterface1"]), unit("u2", &["InternalInterface"]), - interface("InternalInterface"), - interface("InternalInterface1"), + interface_with_parent("InternalInterface", Some("component_example")), + interface_with_parent("InternalInterface1", Some("component_example")), ]); - let internal_api = internal_api_index(vec![("InternalInterface", vec!["GetData"])]); + let internal_api = internal_api_index(vec![( + "component_example.InternalInterface", + vec!["GetData"], + )]); let validation_result = validate(component_diagrams, &internal_api); assert_eq!(validation_result.failures.len(), 1); assert!(validation_result.failures[0].contains("Missing internal API interface")); - assert!(validation_result.failures[0].contains("Missing interfaces : \"InternalInterface1\"")); + assert!(validation_result.failures[0] + .contains("Missing interfaces : \"component_example.InternalInterface1\"")); } #[test] fn reports_missing_component_interface_even_without_unit_relation() { - let component_diagrams = - component_diagrams_with_entities(vec![unit("u1", &[]), interface("UnusedInterface")]); + let component_diagrams = component_diagrams_with_entities(vec![ + unit("u1", &[]), + interface_with_parent("UnusedInterface", Some("component_example")), + ]); let internal_api = internal_api_index(vec![]); let validation_result = validate(component_diagrams, &internal_api); assert_eq!(validation_result.failures.len(), 1); assert!(validation_result.failures[0].contains("Missing internal API interface")); - assert!(validation_result.failures[0].contains("Missing interfaces : \"UnusedInterface\"")); + assert!(validation_result.failures[0] + .contains("Missing interfaces : \"component_example.UnusedInterface\"")); } #[test] @@ -78,23 +90,27 @@ fn reports_all_missing_component_interfaces_in_one_message() { unit("u1", &["InternalInterface", "InternalInterface1"]), unit("u2", &["InternalInterface"]), unit("u3", &["InternalInterface"]), - interface("InternalInterface"), - interface("InternalInterface1"), + interface_with_parent("InternalInterface", Some("component_example")), + interface_with_parent("InternalInterface1", Some("component_example")), ]); - let internal_api = internal_api_index(vec![("InternalInterface", vec!["GetData"])]); + let internal_api = internal_api_index(vec![( + "component_example.InternalInterface", + vec!["GetData"], + )]); let validation_result = validate(component_diagrams, &internal_api); assert_eq!(validation_result.failures.len(), 1); assert!(validation_result.failures[0].contains("Missing internal API interface")); - assert!(validation_result.failures[0].contains("Missing interfaces : \"InternalInterface1\"")); + assert!(validation_result.failures[0] + .contains("Missing interfaces : \"component_example.InternalInterface1\"")); } #[test] fn reports_missing_component_interface_without_sequence_method_call() { let component_diagrams = component_diagrams_with_entities(vec![ unit("u1", &["InternalInterface"]), - interface("InternalInterface"), + interface_with_parent("InternalInterface", Some("component_example")), ]); let internal_api = internal_api_index(vec![("OtherInterface", vec!["GetData"])]); @@ -102,7 +118,8 @@ fn reports_missing_component_interface_without_sequence_method_call() { assert_eq!(validation_result.failures.len(), 1); assert!(validation_result.failures[0].contains("Missing internal API interface")); - assert!(validation_result.failures[0].contains("Missing interfaces : \"InternalInterface\"")); + assert!(validation_result.failures[0] + .contains("Missing interfaces : \"component_example.InternalInterface\"")); } #[test] @@ -110,7 +127,7 @@ fn reports_case_mismatch_between_component_and_internal_api_interface_names() { let component_diagrams = component_diagrams_with_entities(vec![ unit("u1", &["InternalInterface"]), unit("u2", &["InternalInterface"]), - interface("InternalInterface"), + interface_with_parent("InternalInterface", Some("component_example")), ]); let internal_api = internal_api_index(vec![("internalinterface", vec!["GetData"])]); @@ -118,7 +135,8 @@ fn reports_case_mismatch_between_component_and_internal_api_interface_names() { assert_eq!(validation_result.failures.len(), 1); assert!(validation_result.failures[0].contains("Missing internal API interface")); - assert!(validation_result.failures[0].contains("Missing interfaces : \"InternalInterface\"")); + assert!(validation_result.failures[0] + .contains("Missing interfaces : \"component_example.InternalInterface\"")); } #[test] @@ -126,7 +144,7 @@ fn matches_internal_api_by_component_interface_id_when_alias_differs() { let component_diagrams = component_diagrams_with_entities(vec![ unit("u1", &["pkg.InternalInterface"]), unit("u2", &["pkg.InternalInterface"]), - interface_with_id("pkg.InternalInterface", "InternalInterface"), + interface_with_parent("InternalInterface", Some("pkg")), ]); let internal_api = internal_api_index(vec![("pkg.InternalInterface", vec!["GetData"])]); @@ -134,3 +152,16 @@ fn matches_internal_api_by_component_interface_id_when_alias_differs() { assert!(validation_result.failures.is_empty()); } + +#[test] +fn ignores_component_interface_without_parent_id() { + let component_diagrams = component_diagrams_with_entities(vec![ + unit("u1", &["InternalInterface"]), + interface("InternalInterface"), // no parent_id + ]); + let internal_api = internal_api_index(vec![]); + + let validation_result = validate(component_diagrams, &internal_api); + + assert!(validation_result.failures.is_empty()); +} diff --git a/validation/core/src/validators/test/fixtures.rs b/validation/core/src/validators/test/fixtures.rs index 7f0c00c4..30f26fcf 100644 --- a/validation/core/src/validators/test/fixtures.rs +++ b/validation/core/src/validators/test/fixtures.rs @@ -72,15 +72,17 @@ pub(super) fn unit_with_interface_roles( } pub(super) fn interface(alias: &str) -> LogicComponent { - interface_with_id(alias, alias) + interface_with_parent(alias, None) } -pub(super) fn interface_with_id(id: &str, alias: &str) -> LogicComponent { +pub(super) fn interface_with_parent(alias: &str, parent_id: Option<&str>) -> LogicComponent { LogicComponent { - id: id.to_string(), + id: parent_id + .map(|parent_id| format!("{parent_id}.{alias}")) + .unwrap_or_else(|| alias.to_string()), name: Some(alias.to_string()), alias: Some(alias.to_string()), - parent_id: None, + parent_id: parent_id.map(str::to_string), element_type: ComponentType::Interface, stereotype: None, relations: Vec::new(), @@ -151,14 +153,7 @@ pub(super) fn internal_api_index(interfaces: Vec<(&str, Vec<&str>)>) -> Internal .collect(), }]; - let mut setup_result = ValidationResult::default(); - let index = InternalApiIndex::build_index(&diagrams, &mut setup_result); - assert!( - setup_result.is_empty(), - "test fixture construction failed: {:?}", - setup_result.failures - ); - index + InternalApiIndex::build_index(&diagrams) } fn method(name: &str) -> Method { From 77da7dcf0e6714391a12a3fb98ce66fe3217300c Mon Sep 17 00:00:00 2001 From: Melody Ma Date: Fri, 17 Jul 2026 16:37:17 +0800 Subject: [PATCH 2/2] test(validation): clarify validator fixture builders --- .../component_internal_api_validator_test.rs | 62 ++++---- .../test/component_sequence_validator_test.rs | 46 +++--- .../core/src/validators/test/fixtures.rs | 150 +++++++++++------- .../sequence_internal_api_validator_test.rs | 103 ++++++------ 4 files changed, 202 insertions(+), 159 deletions(-) diff --git a/validation/core/src/validators/test/component_internal_api_validator_test.rs b/validation/core/src/validators/test/component_internal_api_validator_test.rs index 19f441c4..64c410ad 100644 --- a/validation/core/src/validators/test/component_internal_api_validator_test.rs +++ b/validation/core/src/validators/test/component_internal_api_validator_test.rs @@ -31,10 +31,10 @@ fn validate( #[test] fn reports_missing_component_interface_declared_by_internal_api() { - let component_diagrams = component_diagrams_with_entities(vec![ - unit("u1", &["InternalInterface"]), - unit("u2", &["InternalInterface"]), - interface_with_parent("InternalInterface", Some("component_example")), + let component_diagrams = component_diagram(vec![ + unit("u1", &["InternalInterface"], &[]), + unit("u2", &["InternalInterface"], &[]), + interface_with_parent_id("InternalInterface", "component_example"), ]); let internal_api = internal_api_index(vec![("OtherInterface", vec!["GetData"])]); @@ -49,11 +49,11 @@ fn reports_missing_component_interface_declared_by_internal_api() { #[test] fn reports_each_missing_component_interface_once() { - let component_diagrams = component_diagrams_with_entities(vec![ - unit("u1", &["InternalInterface", "InternalInterface1"]), - unit("u2", &["InternalInterface"]), - interface_with_parent("InternalInterface", Some("component_example")), - interface_with_parent("InternalInterface1", Some("component_example")), + let component_diagrams = component_diagram(vec![ + unit("u1", &["InternalInterface", "InternalInterface1"], &[]), + unit("u2", &["InternalInterface"], &[]), + interface_with_parent_id("InternalInterface", "component_example"), + interface_with_parent_id("InternalInterface1", "component_example"), ]); let internal_api = internal_api_index(vec![( "component_example.InternalInterface", @@ -70,9 +70,9 @@ fn reports_each_missing_component_interface_once() { #[test] fn reports_missing_component_interface_even_without_unit_relation() { - let component_diagrams = component_diagrams_with_entities(vec![ - unit("u1", &[]), - interface_with_parent("UnusedInterface", Some("component_example")), + let component_diagrams = component_diagram(vec![ + unit_without_interfaces("u1"), + interface_with_parent_id("UnusedInterface", "component_example"), ]); let internal_api = internal_api_index(vec![]); @@ -86,12 +86,12 @@ fn reports_missing_component_interface_even_without_unit_relation() { #[test] fn reports_all_missing_component_interfaces_in_one_message() { - let component_diagrams = component_diagrams_with_entities(vec![ - unit("u1", &["InternalInterface", "InternalInterface1"]), - unit("u2", &["InternalInterface"]), - unit("u3", &["InternalInterface"]), - interface_with_parent("InternalInterface", Some("component_example")), - interface_with_parent("InternalInterface1", Some("component_example")), + let component_diagrams = component_diagram(vec![ + unit("u1", &["InternalInterface", "InternalInterface1"], &[]), + unit("u2", &["InternalInterface"], &[]), + unit("u3", &["InternalInterface"], &[]), + interface_with_parent_id("InternalInterface", "component_example"), + interface_with_parent_id("InternalInterface1", "component_example"), ]); let internal_api = internal_api_index(vec![( "component_example.InternalInterface", @@ -108,9 +108,9 @@ fn reports_all_missing_component_interfaces_in_one_message() { #[test] fn reports_missing_component_interface_without_sequence_method_call() { - let component_diagrams = component_diagrams_with_entities(vec![ - unit("u1", &["InternalInterface"]), - interface_with_parent("InternalInterface", Some("component_example")), + let component_diagrams = component_diagram(vec![ + unit("u1", &["InternalInterface"], &[]), + interface_with_parent_id("InternalInterface", "component_example"), ]); let internal_api = internal_api_index(vec![("OtherInterface", vec!["GetData"])]); @@ -124,10 +124,10 @@ fn reports_missing_component_interface_without_sequence_method_call() { #[test] fn reports_case_mismatch_between_component_and_internal_api_interface_names() { - let component_diagrams = component_diagrams_with_entities(vec![ - unit("u1", &["InternalInterface"]), - unit("u2", &["InternalInterface"]), - interface_with_parent("InternalInterface", Some("component_example")), + let component_diagrams = component_diagram(vec![ + unit("u1", &["InternalInterface"], &[]), + unit("u2", &["InternalInterface"], &[]), + interface_with_parent_id("InternalInterface", "component_example"), ]); let internal_api = internal_api_index(vec![("internalinterface", vec!["GetData"])]); @@ -141,10 +141,10 @@ fn reports_case_mismatch_between_component_and_internal_api_interface_names() { #[test] fn matches_internal_api_by_component_interface_id_when_alias_differs() { - let component_diagrams = component_diagrams_with_entities(vec![ - unit("u1", &["pkg.InternalInterface"]), - unit("u2", &["pkg.InternalInterface"]), - interface_with_parent("InternalInterface", Some("pkg")), + let component_diagrams = component_diagram(vec![ + unit("u1", &["pkg.InternalInterface"], &[]), + unit("u2", &["pkg.InternalInterface"], &[]), + interface_with_parent_id("InternalInterface", "pkg"), ]); let internal_api = internal_api_index(vec![("pkg.InternalInterface", vec!["GetData"])]); @@ -155,8 +155,8 @@ fn matches_internal_api_by_component_interface_id_when_alias_differs() { #[test] fn ignores_component_interface_without_parent_id() { - let component_diagrams = component_diagrams_with_entities(vec![ - unit("u1", &["InternalInterface"]), + let component_diagrams = component_diagram(vec![ + unit("u1", &["InternalInterface"], &[]), interface("InternalInterface"), // no parent_id ]); let internal_api = internal_api_index(vec![]); diff --git a/validation/core/src/validators/test/component_sequence_validator_test.rs b/validation/core/src/validators/test/component_sequence_validator_test.rs index f0dda80e..64986ef5 100644 --- a/validation/core/src/validators/test/component_sequence_validator_test.rs +++ b/validation/core/src/validators/test/component_sequence_validator_test.rs @@ -29,7 +29,10 @@ fn validate( #[test] fn passes_when_aliases_and_participants_are_identical() { - let component_diagrams = component_diagrams(&["unit_1", "unit_2"]); + let component_diagrams = component_diagram(vec![ + unit_without_interfaces("unit_1"), + unit_without_interfaces("unit_2"), + ]); let sequence_diagrams = sequence_diagrams(&["unit_1", "unit_2"]); let validation_result = validate(component_diagrams, sequence_diagrams); @@ -38,7 +41,11 @@ fn passes_when_aliases_and_participants_are_identical() { #[test] fn reports_missing_and_extra() { - let component_diagrams = component_diagrams(&["unit_1", "unit_2", "unit_3"]); + let component_diagrams = component_diagram(vec![ + unit_without_interfaces("unit_1"), + unit_without_interfaces("unit_2"), + unit_without_interfaces("unit_3"), + ]); let sequence_diagrams = sequence_diagrams(&["unit_2", "unit_4"]); let validation_result = validate(component_diagrams, sequence_diagrams); @@ -83,7 +90,10 @@ fn units_without_alias_are_ignored() { #[test] fn reports_alias_missing_from_participants() { - let component_diagrams = component_diagrams(&["u1", "u2"]); + let component_diagrams = component_diagram(vec![ + unit_without_interfaces("u1"), + unit_without_interfaces("u2"), + ]); let sequence_diagrams = sequence_diagrams(&["u1"]); let validation_result = validate(component_diagrams, sequence_diagrams); @@ -93,7 +103,7 @@ fn reports_alias_missing_from_participants() { #[test] fn reports_participant_not_in_aliases() { - let component_diagrams = component_diagrams(&["u1"]); + let component_diagrams = component_diagram(vec![unit_without_interfaces("u1")]); let sequence_diagrams = sequence_diagrams(&["u1", "orphan"]); let validation_result = validate(component_diagrams, sequence_diagrams); @@ -103,8 +113,8 @@ fn reports_participant_not_in_aliases() { #[test] fn reports_missing_component_alias_and_interface_connection_for_sequence_call() { - let component_diagrams = component_diagrams_with_entities(vec![ - unit("u1", &["InternalInterface"]), + let component_diagrams = component_diagram(vec![ + unit("u1", &["InternalInterface"], &[]), interface("InternalInterface"), ]); let sequence_diagrams = sequence_calls(&[("u1", "orphan", "GetData()")]); @@ -127,9 +137,9 @@ fn reports_missing_component_alias_and_interface_connection_for_sequence_call() #[test] fn reports_missing_sequence_call_for_interface_connected_units() { - let component_diagrams = component_diagrams_with_entities(vec![ - unit("u1", &["InternalInterface"]), - unit("u2", &["InternalInterface"]), + let component_diagrams = component_diagram(vec![ + unit("u1", &["InternalInterface"], &[]), + unit("u2", &[], &["InternalInterface"]), interface("InternalInterface"), ]); let sequence_diagrams = sequence_diagrams(&["u1", "u2"]); @@ -144,9 +154,9 @@ fn reports_missing_sequence_call_for_interface_connected_units() { #[test] fn reports_missing_participant_and_missing_sequence_call_for_interface_connected_units() { - let component_diagrams = component_diagrams_with_entities(vec![ - unit("u1", &["InternalInterface"]), - unit("u2", &["InternalInterface"]), + let component_diagrams = component_diagram(vec![ + unit("u1", &["InternalInterface"], &[]), + unit("u2", &[], &["InternalInterface"]), interface("InternalInterface"), ]); let sequence_diagrams = sequence_diagrams(&["u1"]); @@ -167,9 +177,9 @@ fn reports_missing_participant_and_missing_sequence_call_for_interface_connected #[test] fn reports_sequence_call_without_corresponding_shared_interface_connection() { - let component_diagrams = component_diagrams_with_entities(vec![ - unit("u1", &["CallerInterface"]), - unit("u2", &["CalleeInterface"]), + let component_diagrams = component_diagram(vec![ + unit("u1", &["CallerInterface"], &[]), + unit("u2", &[], &["CalleeInterface"]), interface("CallerInterface"), interface("CalleeInterface"), ]); @@ -186,9 +196,9 @@ fn reports_sequence_call_without_corresponding_shared_interface_connection() { #[test] fn passes_when_interface_connected_units_have_sequence_call() { - let component_diagrams = component_diagrams_with_entities(vec![ - unit("u1", &["InternalInterface"]), - unit("u2", &["InternalInterface"]), + let component_diagrams = component_diagram(vec![ + unit("u1", &["InternalInterface"], &[]), + unit("u2", &[], &["InternalInterface"]), interface("InternalInterface"), ]); let sequence_diagrams = sequence_calls(&[("u1", "u2", "GetData()")]); diff --git a/validation/core/src/validators/test/fixtures.rs b/validation/core/src/validators/test/fixtures.rs index 30f26fcf..f9941fd0 100644 --- a/validation/core/src/validators/test/fixtures.rs +++ b/validation/core/src/validators/test/fixtures.rs @@ -15,92 +15,108 @@ use crate::models::{ ComponentDiagramInputs, ComponentRelationType, ComponentType, EndpointRole, InternalApiIndex, LogicComponent, LogicRelation, SequenceDiagramInputs, }; -use crate::ValidationResult; use class_diagram::{ClassDiagram, EntityType, Method, SimpleEntity, Visibility}; use component_diagram::SourceLocation; use sequence_logic::{Event, Interaction, SequenceNode, SequenceTree}; +// Common fixtures + pub(crate) fn dummy_source_location() -> SourceLocation { SourceLocation::new("test.puml", 1) } -pub(super) fn relation_with_role(target: &str, source_role: EndpointRole) -> LogicRelation { - relation_with_type_and_role(target, ComponentRelationType::InterfaceBinding, source_role) +fn entity_id(alias: &str, parent_id: Option<&str>) -> String { + parent_id + .map(|parent_id| format!("{parent_id}.{alias}")) + .unwrap_or_else(|| alias.to_string()) } -pub(super) fn relation_with_type_and_role( - target: &str, - relation_type: ComponentRelationType, - source_role: EndpointRole, -) -> LogicRelation { - LogicRelation { - target: target.to_string(), - annotation: None, - relation_type, - source_role, - source_location: dummy_source_location(), - } -} +// Component diagram fixtures -pub(super) fn unit(alias: &str, interface_targets: &[&str]) -> LogicComponent { - unit_with_interface_roles(alias, interface_targets, interface_targets) +pub(super) fn component_diagram(entities: Vec) -> ComponentDiagramInputs { + ComponentDiagramInputs { entities } } -pub(super) fn unit_with_interface_roles( +pub(super) fn unit( alias: &str, required_interfaces: &[&str], provided_interfaces: &[&str], ) -> LogicComponent { let mut relations = Vec::new(); for target in required_interfaces { - relations.push(relation_with_role(target, EndpointRole::Required)); + relations.push(relation( + target, + ComponentRelationType::InterfaceBinding, + EndpointRole::Required, + )); } for target in provided_interfaces { - relations.push(relation_with_role(target, EndpointRole::Provided)); + relations.push(relation( + target, + ComponentRelationType::InterfaceBinding, + EndpointRole::Provided, + )); } - LogicComponent { - id: format!("some_id.{alias}"), - name: Some(alias.to_string()), - alias: Some(alias.to_string()), - parent_id: None, - element_type: ComponentType::Component, - stereotype: Some("unit".to_string()), + logic_component( + alias, + None, + ComponentType::Component, + Some("unit"), relations, + ) +} + +pub(super) fn unit_without_interfaces(alias: &str) -> LogicComponent { + unit(alias, &[], &[]) +} + +pub(super) fn interface(alias: &str) -> LogicComponent { + interface_entity(alias, None) +} + +pub(super) fn interface_with_parent_id(alias: &str, parent_id: &str) -> LogicComponent { + interface_entity(alias, Some(parent_id)) +} + +pub(super) fn relation( + target: &str, + relation_type: ComponentRelationType, + source_role: EndpointRole, +) -> LogicRelation { + LogicRelation { + target: target.to_string(), + annotation: None, + relation_type, + source_role, source_location: dummy_source_location(), } } -pub(super) fn interface(alias: &str) -> LogicComponent { - interface_with_parent(alias, None) +fn interface_entity(alias: &str, parent_id: Option<&str>) -> LogicComponent { + logic_component(alias, parent_id, ComponentType::Interface, None, Vec::new()) } -pub(super) fn interface_with_parent(alias: &str, parent_id: Option<&str>) -> LogicComponent { +fn logic_component( + alias: &str, + parent_id: Option<&str>, + element_type: ComponentType, + stereotype: Option<&str>, + relations: Vec, +) -> LogicComponent { LogicComponent { - id: parent_id - .map(|parent_id| format!("{parent_id}.{alias}")) - .unwrap_or_else(|| alias.to_string()), + id: entity_id(alias, parent_id), name: Some(alias.to_string()), alias: Some(alias.to_string()), parent_id: parent_id.map(str::to_string), - element_type: ComponentType::Interface, - stereotype: None, - relations: Vec::new(), + element_type, + stereotype: stereotype.map(str::to_string), + relations, source_location: dummy_source_location(), } } -pub(super) fn component_diagrams(aliases: &[&str]) -> ComponentDiagramInputs { - ComponentDiagramInputs { - entities: aliases.iter().map(|alias| unit(alias, &[])).collect(), - } -} - -pub(super) fn component_diagrams_with_entities( - entities: Vec, -) -> ComponentDiagramInputs { - ComponentDiagramInputs { entities } -} +// Sequence diagram fixtures. pub(super) fn sequence_diagrams(participants: &[&str]) -> SequenceDiagramInputs { sequence_calls( @@ -132,23 +148,17 @@ pub(super) fn sequence_calls(calls: &[(&str, &str, &str)]) -> SequenceDiagramInp } } +// Class diagram API fixtures. + pub(super) fn internal_api_index(interfaces: Vec<(&str, Vec<&str>)>) -> InternalApiIndex { let diagrams = vec![ClassDiagram { name: "internal_api".to_string(), entities: interfaces .into_iter() - .map(|(interface_name, methods)| SimpleEntity { - id: interface_name.to_string(), - name: interface_name.to_string(), - enclosing_namespace_id: None, - entity_type: EntityType::Interface, - type_aliases: Vec::new(), - variables: Vec::new(), - methods: methods.into_iter().map(method).collect(), - template_parameters: None, - enum_literals: Vec::new(), - relationships: Vec::new(), - source_location: dummy_source_location(), + .map(|(interface_name, methods)| { + let mut interface = class_interface(interface_name, None); + interface.methods = methods.into_iter().map(method).collect(); + interface }) .collect(), }]; @@ -156,6 +166,26 @@ pub(super) fn internal_api_index(interfaces: Vec<(&str, Vec<&str>)>) -> Internal InternalApiIndex::build_index(&diagrams) } +pub(super) fn class_interface(name: &str, namespace: Option<&str>) -> SimpleEntity { + simple_entity(name, EntityType::Interface, namespace) +} + +fn simple_entity(name: &str, entity_type: EntityType, namespace: Option<&str>) -> SimpleEntity { + SimpleEntity { + id: entity_id(name, namespace), + name: name.to_string(), + enclosing_namespace_id: namespace.map(str::to_string), + entity_type, + type_aliases: Vec::new(), + variables: Vec::new(), + methods: Vec::new(), + template_parameters: None, + enum_literals: Vec::new(), + relationships: Vec::new(), + source_location: dummy_source_location(), + } +} + fn method(name: &str) -> Method { Method { name: name.to_string(), diff --git a/validation/core/src/validators/test/sequence_internal_api_validator_test.rs b/validation/core/src/validators/test/sequence_internal_api_validator_test.rs index 89409689..b7a63bc5 100644 --- a/validation/core/src/validators/test/sequence_internal_api_validator_test.rs +++ b/validation/core/src/validators/test/sequence_internal_api_validator_test.rs @@ -14,7 +14,7 @@ use super::super::fixtures::*; use super::*; use crate::models::{ - ComponentDiagramInputs, ComponentRelationType, ComponentType, EndpointRole, LogicComponent, + ComponentDiagramInputs, ComponentRelationType, EndpointRole, LogicComponent, SequenceDiagramInputs, }; use crate::ValidationResult; @@ -25,6 +25,11 @@ fn validate( ) -> ValidationResult { let mut setup_result = ValidationResult::default(); let sequence_index = sequence_diagrams.to_sequence_diagram_index(&mut setup_result); + assert!( + setup_result.is_empty(), + "test fixture setup failed: {:?}", + setup_result.failures + ); validate_sequence_internal_api(&sequence_index, internal_api, None) } @@ -37,25 +42,23 @@ fn validate_with_component_context( let mut setup_result = ValidationResult::default(); let component_arch = component_diagrams.to_diagram_architecture(&mut setup_result); let sequence_index = sequence_diagrams.to_sequence_diagram_index(&mut setup_result); + assert!( + setup_result.is_empty(), + "test fixture setup failed: {:?}", + setup_result.failures + ); validate_sequence_internal_api(&sequence_index, internal_api, Some(&component_arch)) } fn unit_with_non_binding_interface(alias: &str, interface_id: &str) -> LogicComponent { - LogicComponent { - id: format!("some_id.{alias}"), - name: Some(alias.to_string()), - alias: Some(alias.to_string()), - parent_id: None, - element_type: ComponentType::Component, - stereotype: Some("unit".to_string()), - relations: vec![relation_with_type_and_role( - interface_id, - ComponentRelationType::Dependency, - EndpointRole::None, - )], - source_location: dummy_source_location(), - } + let mut unit = unit_without_interfaces(alias); + unit.relations = vec![relation( + interface_id, + ComponentRelationType::Dependency, + EndpointRole::None, + )]; + unit } #[test] @@ -112,9 +115,9 @@ fn self_calls_count_as_internal_api_method_usage() { #[test] fn reports_sequence_function_missing_from_available_interfaces_with_component_context() { - let component_diagrams = component_diagrams_with_entities(vec![ - unit("u1", &["InternalInterface"]), - unit("u2", &["InternalInterface"]), + let component_diagrams = component_diagram(vec![ + unit("u1", &["InternalInterface"], &[]), + unit("u2", &[], &["InternalInterface"]), interface("InternalInterface"), ]); let sequence_diagrams = sequence_calls(&[("u1", "u2", "GetData()")]); @@ -137,7 +140,7 @@ fn reports_sequence_function_missing_from_available_interfaces_with_component_co #[test] fn reports_sequence_function_missing_when_shared_interface_has_no_direction_roles() { - let component_diagrams = component_diagrams_with_entities(vec![ + let component_diagrams = component_diagram(vec![ unit_with_non_binding_interface("u1", "InternalInterface"), unit_with_non_binding_interface("u2", "InternalInterface"), interface("InternalInterface"), @@ -164,9 +167,9 @@ fn reports_sequence_function_missing_when_shared_interface_has_no_direction_role #[test] fn reports_interface_function_not_exercised_in_sequence_diagrams_with_component_context() { - let component_diagrams = component_diagrams_with_entities(vec![ - unit("u1", &["InternalInterface"]), - unit("u2", &["InternalInterface"]), + let component_diagrams = component_diagram(vec![ + unit("u1", &["InternalInterface"], &[]), + unit("u2", &[], &["InternalInterface"]), interface("InternalInterface"), ]); let sequence_diagrams = sequence_calls(&[("u1", "u2", "GetData()")]); @@ -184,9 +187,9 @@ fn reports_interface_function_not_exercised_in_sequence_diagrams_with_component_ #[test] fn reports_unreferenced_internal_api_interface_function_not_exercised_without_self_calls() { - let component_diagrams = component_diagrams_with_entities(vec![ - unit("u1", &["InternalInterface"]), - unit("u2", &["InternalInterface"]), + let component_diagrams = component_diagram(vec![ + unit("u1", &["InternalInterface"], &[]), + unit("u2", &[], &["InternalInterface"]), interface("InternalInterface"), ]); let sequence_diagrams = sequence_calls(&[("u1", "u2", "GetData()")]); @@ -207,8 +210,8 @@ fn reports_unreferenced_internal_api_interface_function_not_exercised_without_se #[test] fn reports_self_call_method_mismatch_when_unit_has_missing_internal_api_interface() { - let component_diagrams = component_diagrams_with_entities(vec![ - unit("u1", &["MissingInterface"]), + let component_diagrams = component_diagram(vec![ + unit("u1", &["MissingInterface"], &[]), interface("MissingInterface"), ]); let sequence_diagrams = sequence_calls(&[("u1", "u1", "GetData()")]); @@ -226,9 +229,9 @@ fn reports_self_call_method_mismatch_when_unit_has_missing_internal_api_interfac #[test] fn passes_when_sequence_function_exists_on_related_interface_with_component_context() { - let component_diagrams = component_diagrams_with_entities(vec![ - unit("u1", &["InternalInterface"]), - unit("u2", &["InternalInterface"]), + let component_diagrams = component_diagram(vec![ + unit("u1", &["InternalInterface"], &[]), + unit("u2", &[], &["InternalInterface"]), interface("InternalInterface"), ]); let sequence_diagrams = sequence_calls(&[("u1", "u2", "GetData()")]); @@ -242,7 +245,7 @@ fn passes_when_sequence_function_exists_on_related_interface_with_component_cont #[test] fn reports_self_call_function_missing_from_available_interfaces() { - let component_diagrams = component_diagrams(&["u1"]); + let component_diagrams = component_diagram(vec![unit_without_interfaces("u1")]); let sequence_diagrams = sequence_calls(&[("u1", "u1", "GetData()")]); let internal_api = internal_api_index(vec![("InternalInterface", vec!["OtherMethod"])]); @@ -263,7 +266,7 @@ fn reports_self_call_function_missing_from_available_interfaces() { #[test] fn passes_when_self_call_uses_internal_api_interface_without_component_interfaces() { - let component_diagrams = component_diagrams(&["u1"]); + let component_diagrams = component_diagram(vec![unit_without_interfaces("u1")]); let sequence_diagrams = sequence_calls(&[("u1", "u1", "GetData()")]); let internal_api = internal_api_index(vec![("InternalInterface", vec!["GetData"])]); @@ -275,8 +278,8 @@ fn passes_when_self_call_uses_internal_api_interface_without_component_interface #[test] fn passes_when_all_interface_functions_are_exercised_by_self_calls() { - let component_diagrams = component_diagrams_with_entities(vec![ - unit("u1", &["InternalInterface"]), + let component_diagrams = component_diagram(vec![ + unit("u1", &["InternalInterface"], &[]), interface("InternalInterface"), ]); let sequence_diagrams = sequence_calls(&[("u1", "u1", "GetData()"), ("u1", "u1", "SetData()")]); @@ -290,7 +293,7 @@ fn passes_when_all_interface_functions_are_exercised_by_self_calls() { #[test] fn reports_self_call_without_any_available_interfaces() { - let component_diagrams = component_diagrams(&["u1"]); + let component_diagrams = component_diagram(vec![unit_without_interfaces("u1")]); let sequence_diagrams = sequence_calls(&[("u1", "u1", "GetData()")]); let internal_api = internal_api_index(vec![]); @@ -305,9 +308,9 @@ fn reports_self_call_without_any_available_interfaces() { #[test] fn reports_method_declared_only_on_caller_side_interfaces() { - let component_diagrams = component_diagrams_with_entities(vec![ - unit("u1", &["SharedInterface", "CallerOnlyInterface"]), - unit("u2", &["SharedInterface"]), + let component_diagrams = component_diagram(vec![ + unit("u1", &["SharedInterface", "CallerOnlyInterface"], &[]), + unit("u2", &[], &["SharedInterface"]), interface("SharedInterface"), interface("CallerOnlyInterface"), ]); @@ -338,9 +341,9 @@ fn reports_method_declared_only_on_caller_side_interfaces() { #[test] fn reports_method_declared_only_on_callee_side_interfaces() { - let component_diagrams = component_diagrams_with_entities(vec![ - unit("u1", &["SharedInterface"]), - unit("u2", &["SharedInterface", "CalleeOnlyInterface"]), + let component_diagrams = component_diagram(vec![ + unit("u1", &["SharedInterface"], &[]), + unit("u2", &[], &["SharedInterface", "CalleeOnlyInterface"]), interface("SharedInterface"), interface("CalleeOnlyInterface"), ]); @@ -371,9 +374,9 @@ fn reports_method_declared_only_on_callee_side_interfaces() { #[test] fn reports_method_declared_on_both_sides_but_not_on_shared_interface() { - let component_diagrams = component_diagrams_with_entities(vec![ - unit("u1", &["SharedInterface", "CallerOnlyInterface"]), - unit("u2", &["SharedInterface", "CalleeOnlyInterface"]), + let component_diagrams = component_diagram(vec![ + unit("u1", &["SharedInterface", "CallerOnlyInterface"], &[]), + unit("u2", &[], &["SharedInterface", "CalleeOnlyInterface"]), interface("SharedInterface"), interface("CallerOnlyInterface"), interface("CalleeOnlyInterface"), @@ -406,9 +409,9 @@ fn reports_method_declared_on_both_sides_but_not_on_shared_interface() { #[test] fn reports_role_violation_when_method_exists_only_on_reverse_direction_interface() { - let component_diagrams = component_diagrams_with_entities(vec![ - unit_with_interface_roles("u1", &[], &["InternalInterface"]), - unit_with_interface_roles("u2", &["InternalInterface"], &[]), + let component_diagrams = component_diagram(vec![ + unit("u1", &[], &["InternalInterface"]), + unit("u2", &["InternalInterface"], &[]), interface("InternalInterface"), ]); let sequence_diagrams = sequence_calls(&[("u1", "u2", "GetData()")]); @@ -433,9 +436,9 @@ fn reports_role_violation_when_method_exists_only_on_reverse_direction_interface #[test] fn passes_when_method_interface_matches_call_direction_roles() { - let component_diagrams = component_diagrams_with_entities(vec![ - unit_with_interface_roles("u1", &["InternalInterface"], &[]), - unit_with_interface_roles("u2", &[], &["InternalInterface"]), + let component_diagrams = component_diagram(vec![ + unit("u1", &["InternalInterface"], &[]), + unit("u2", &[], &["InternalInterface"]), interface("InternalInterface"), ]); let sequence_diagrams = sequence_calls(&[("u1", "u2", "GetData()")]);