diff --git a/cdxev/__main__.py b/cdxev/__main__.py index 64dd04a4..a82475fd 100644 --- a/cdxev/__main__.py +++ b/cdxev/__main__.py @@ -441,9 +441,14 @@ def create_merge_parser( parser.add_argument( "--from-folder", metavar="", - help="Path to a folder with sboms to be merged", + help="Path to a folder with SBOMs to be merged.", type=Path, ) + parser.add_argument( + "--hierarchical", + help="Flag to determine if the components should be merged hierarchical.", + action="store_true", + ) add_output_argument(parser) parser.set_defaults(cmd_handler=invoke_merge, parser=parser) @@ -879,7 +884,7 @@ def invoke_merge(args: argparse.Namespace) -> int: ) inputs = [sbom for (sbom, _) in (read_sbom(input) for input in inputs)] - output = merge(inputs) + output = merge(inputs, hierarchical=args.hierarchical) write_sbom(output, args.output) return Status.OK diff --git a/cdxev/auxiliary/sbomFunctions.py b/cdxev/auxiliary/sbomFunctions.py index 3bba35c1..3508aeba 100644 --- a/cdxev/auxiliary/sbomFunctions.py +++ b/cdxev/auxiliary/sbomFunctions.py @@ -377,38 +377,6 @@ def _recurse( _recurse(sbom["components"], func, *args, **kwargs) -def get_corresponding_reference_to_component( - component: dict, list_of_components: list -) -> tuple[bool, str]: - """ - Function that checks if a given component is contained - in a list of components and returns the bom-ref from - the corresponding component in the list. - - Parameters - ---------- - component: dict - A component dict - list_of_components: str - A list of component dicts - - Returns - ------- - is_in_list: bool - A boolean describing if the component is in the list - bomref_from_list: - The bom-ref from the corresponding component in the list - """ - is_in_list = False - bomref_from_list = "" - for component_from_list in list_of_components: - if compare_components(component, component_from_list): - is_in_list = True - bomref_from_list = component_from_list.get("bom-ref", "") - break - return is_in_list, bomref_from_list - - # Function for the usage of the python cyclonedx model diff --git a/cdxev/merge.py b/cdxev/merge.py index 3429f5c0..14e333c5 100644 --- a/cdxev/merge.py +++ b/cdxev/merge.py @@ -8,8 +8,8 @@ compare_time_flag_from_vulnerabilities, compare_vulnerabilities, copy_ratings, + extract_components, get_bom_refs_from_dependencies, - get_corresponding_reference_to_component, get_dependency_by_ref, get_ref_from_components, ) @@ -18,7 +18,76 @@ logger = logging.getLogger(__name__) -def merge_components(governing_sbom: dict, sbom_to_be_merged: dict) -> t.List[dict]: +def filter_component( + present_components: list[ComponentIdentity], + components_to_add: list, + kept_components: list, + dropped_components: list, + add_to_existing: dict, +) -> list[dict]: + """ + Function that goes through a list of components and their nested sub components + and determine if they are present in a provided list with component identities. + + The function operates directly on the lists and dictionary provided and returns + a list of filtered top level components that were not found in present_components. + Filtered means, that the nested components are also not already present. + + param present_components: a list of component identities that are already present in the SBOM. + param components_to_add: a list of components that shall be compared against the list of + already present components. + param kept_components: list of components not present in the list of provided components, + including nested components. + param dropped_components: list of added components that are already present. + param add_to_existing: list of nested components that have to be added to present_components. + + returns: filtered_components: list of top level components not present in present_components + """ + filtered_components: list[dict] = [] + for component in components_to_add: + component_id = ComponentIdentity.create(component, allow_unsafe=True) + # component is new + if component_id not in present_components: + nested_components = filter_component( + present_components, + component.get("components", []), + kept_components, + dropped_components, + add_to_existing, + ) + if component.get("components", []): + component["components"] = nested_components + filtered_components.append(component) + kept_components.append(component) + + # component already present + # contained components get filtered and added to the component in the main sbom + else: + logger.warning( + LogMessage( + "Potential loss of information", + f"Dropping a duplicate component ({component_id}) from the merge result.", + ) + ) + dropped_components.append(component) + nested_components = filter_component( + present_components, + component.get("components", []), + kept_components, + dropped_components, + add_to_existing, + ) + if nested_components: + add_to_existing[component_id] = ( + add_to_existing.get(component_id, []) + nested_components + ) + + return filtered_components + + +def merge_components( + governing_sbom: dict, sbom_to_be_merged: dict, hierarchical: bool = False +) -> t.List[dict]: """ Function that gets two lists of components and merges them unique into one. @@ -34,67 +103,93 @@ def merge_components(governing_sbom: dict, sbom_to_be_merged: dict) -> t.List[di Output: list_of_merged_components: List with the uniquely merged components of the submitted sboms """ - list_of_merged_components = governing_sbom.get("components", []) + list_of_merged_components: t.List[dict] = governing_sbom.get("components", []) list_of_added_components = sbom_to_be_merged.get("components", []) list_of_merged_bom_refs = get_ref_from_components(list_of_merged_components) - for component in list_of_added_components: - is_in_list, bom_ref_from_list = get_corresponding_reference_to_component( - component, list_of_merged_components - ) - if is_in_list: - component_id = ComponentIdentity.create(component, allow_unsafe=True) - logger.warning( - LogMessage( - "Potential loss of information", - f"Dropping a duplicate component ({component_id}) from the merge result.", - ) + + present_component_identities: dict[ComponentIdentity, dict] = {} + for component in extract_components(governing_sbom.get("components", [])): + present_component_identities[ + ComponentIdentity.create(component, allow_unsafe=True) + ] = component + + kept_components: list[dict] = [] + dropped_components: list[dict] = [] + add_to_existing: dict[ComponentIdentity, dict] = {} + list_present_component_identities = list(present_component_identities.keys()) + list_of_filtered_components = filter_component( + list_present_component_identities, + list_of_added_components, + kept_components, + dropped_components, + add_to_existing, + ) + + list_of_merged_components += list_of_filtered_components + + if hierarchical: + for key in add_to_existing.keys(): + list_of_subcomponents = ( + present_component_identities[key].get("components", []) + + add_to_existing[key] ) - # if the component in the sbom_to_be_merged has a different - # bom-ref than the governing_sbom, then the bom-ref will be - # replaced through the one from the governing_sbom. - # while doing so, the algorithm checks, that the sbom does not - # already contain a different component with that ref, if so - # that component's bom-ref will be renamed - if bom_ref_from_list != component.get("bom-ref", 1): - counter = 0 - new_reference = bom_ref_from_list - while not replace_ref_in_sbom( - new_reference, component.get("bom-ref", ""), sbom_to_be_merged - ): - counter += 1 - new_reference = bom_ref_from_list + "_" + str(counter) + present_component_identities[key]["components"] = list_of_subcomponents + else: + for key in add_to_existing.keys(): + for new_component in add_to_existing[key]: + list_of_merged_components.append(new_component) + + for component in dropped_components: + # if the component in the sbom_to_be_merged has a different + # bom-ref than the governing_sbom, then the bom-ref will be + # replaced through the one from the governing_sbom. + # While doing so, the algorithm checks, that the SBOM does not + # already contain a different component with that ref, if so + # that component's bom-ref will be renamed. + component_id = ComponentIdentity.create(component, allow_unsafe=True) + bom_ref_from_list = present_component_identities[component_id].get( + "bom-ref", "" + ) + if bom_ref_from_list != component.get("bom-ref", 1): + counter = 0 + new_reference = bom_ref_from_list + while not replace_ref_in_sbom( + new_reference, component.get("bom-ref", ""), sbom_to_be_merged + ): + counter += 1 + new_reference = bom_ref_from_list + "_" + str(counter) + + for component in kept_components: + if not (component.get("bom-ref", 1) in list_of_merged_bom_refs): + list_of_merged_bom_refs.append(component.get("bom-ref", "")) else: - if not (component.get("bom-ref", 1) in list_of_merged_bom_refs): - list_of_merged_components.append(component) - list_of_merged_bom_refs.append(component.get("bom-ref")) - else: - # if the bom-ref already exists in the components, add a incrementing number to - # the bom-ref - list_of_bom_refs_to_be_added = get_ref_from_components( - sbom_to_be_merged.get("components", []) - ) - list_of_bom_refs_to_be_added.append( - sbom_to_be_merged.get("metadata", {}) - .get("component", {}) - .get("bom-ref", "") - ) - bom_ref_is_not_unique = False - new_bom_ref = component.get("bom-ref") - n = 0 - while new_bom_ref in list_of_merged_bom_refs or bom_ref_is_not_unique: - n += 1 - new_bom_ref = component.get("bom-ref") + "_" + str(n) - # The new bom-ref must not appear in either of the sboms - if new_bom_ref in list_of_bom_refs_to_be_added: - bom_ref_is_not_unique = True - else: - bom_ref_is_not_unique = False - replace_ref_in_sbom( - new_bom_ref, component.get("bom-ref", ""), sbom_to_be_merged - ) - list_of_merged_components.append(component) - list_of_merged_bom_refs.append(new_bom_ref) - return list_of_merged_components # type:ignore [no-any-return] + # if the bom-ref already exists in the components, add a incrementing number to + # the bom-ref + list_of_bom_refs_to_be_added = get_ref_from_components( + sbom_to_be_merged.get("components", []) + ) + list_of_bom_refs_to_be_added.append( + sbom_to_be_merged.get("metadata", {}) + .get("component", {}) + .get("bom-ref", "") + ) + bom_ref_is_not_unique = False + new_bom_ref = component.get("bom-ref", "") + n = 0 + while new_bom_ref in list_of_merged_bom_refs or bom_ref_is_not_unique: + n += 1 + new_bom_ref = component.get("bom-ref", "") + "_" + str(n) + # The new bom-ref must not appear in either of the SBOMs + if new_bom_ref in list_of_bom_refs_to_be_added: + bom_ref_is_not_unique = True + else: + bom_ref_is_not_unique = False + replace_ref_in_sbom( + new_bom_ref, component.get("bom-ref", ""), sbom_to_be_merged + ) + list_of_merged_bom_refs.append(new_bom_ref) + + return list_of_merged_components def merge_dependency( @@ -163,7 +258,9 @@ def merge_dependency_lists( return list_of_merged_dependencies -def merge_2_sboms(original_sbom: dict, sbom_to_be_merged: dict) -> dict: +def merge_2_sboms( + original_sbom: dict, sbom_to_be_merged: dict, hierarchical: bool = False +) -> dict: """ Function that merges two sboms. @@ -181,7 +278,9 @@ def merge_2_sboms(original_sbom: dict, sbom_to_be_merged: dict) -> dict: components_of_sbom_to_be_merged.append(component_from_metadata) list_of_original_dependencies = original_sbom.get("dependencies", []) list_of_new_dependencies = sbom_to_be_merged.get("dependencies", []) - list_of_merged_components = merge_components(original_sbom, sbom_to_be_merged) + list_of_merged_components = merge_components( + original_sbom, sbom_to_be_merged, hierarchical=hierarchical + ) merged_dependencies = merge_dependency_lists( list_of_original_dependencies, list_of_new_dependencies, @@ -208,7 +307,7 @@ def merge_2_sboms(original_sbom: dict, sbom_to_be_merged: dict) -> dict: return merged_sbom -def merge(sboms: t.Sequence[dict]) -> dict: +def merge(sboms: t.Sequence[dict], hierarchical: bool = False) -> dict: """ Function that merges a list of sboms successively in to the first one and creates an JSON file. for the result @@ -222,7 +321,7 @@ def merge(sboms: t.Sequence[dict]) -> dict: """ merged_sbom = sboms[0] for k in range(1, len(sboms)): - merged_sbom = merge_2_sboms(merged_sbom, sboms[k]) + merged_sbom = merge_2_sboms(merged_sbom, sboms[k], hierarchical=hierarchical) return merged_sbom diff --git a/docs/source/img/merge_hierarchical_structure.svg b/docs/source/img/merge_hierarchical_structure.svg new file mode 100644 index 00000000..b5a2b377 --- /dev/null +++ b/docs/source/img/merge_hierarchical_structure.svg @@ -0,0 +1 @@ +Component 2Component 1Component 4Component 3SBOM 1SBOM 2Default ResultMetadata.ComponentComponent 3Component 2Component 4Component 1Hierarchical ResultMetadata.ComponentComponent 2Component 1Component 4Component 3Component 5Component 5Component 5Component 1(Metadata.Component)Component 0(Metadata.Component)Component 0(Metadata.Component)Component 0(Metadata.Component) diff --git a/docs/source/usage/merge.rst b/docs/source/usage/merge.rst index 3d456e0a..cc53b37e 100644 --- a/docs/source/usage/merge.rst +++ b/docs/source/usage/merge.rst @@ -23,6 +23,12 @@ The process runs iteratively, merging two SBOMs in each iteration. In the first In mathematical terms: :math:`output = (((input_1 * input_2) * input_3) * input_4 ...)` +The merge is per default not hierarchical for the ``components`` field of a ``component`` (`CycloneDX documentation `_). This means that components that were contained in the ``components`` of an already present component will just be added as new components under the SBOMs' ``components`` sections. +The ``--hierarchical`` flag allows for hierarchical merges. This affects only the top level components of the merged SBOM. The structured of nested components is preserved in both cases (except the removal of already present components), as shown for "component 4" in the image below. + +.. image:: /img/merge_hierarchical_structure.svg + :alt: Merge components structure default and hierarchical. + A few notes on the merge algorithm: - The ``metadata`` field is always retained from the first input and never changed through a merge with the exception of the ``timestamp``. diff --git a/tests/auxiliary/test_merge_sboms/sections_for_test_sbom.json b/tests/auxiliary/test_merge_sboms/sections_for_test_sbom.json index 2dcd9e6a..4c7a8851 100644 --- a/tests/auxiliary/test_merge_sboms/sections_for_test_sbom.json +++ b/tests/auxiliary/test_merge_sboms/sections_for_test_sbom.json @@ -1361,5 +1361,1636 @@ } ] } - ] + ], + "hierarchical_components": { + "component_1": { + "name": "component_1", + "version": "1.0.0", + "bom-ref": "component_1", + "components": [ + { + "name": "component_1_sub_1", + "version": "1.0.0", + "bom-ref": "component_1_sub_1", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + } + ], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + }, + "component_2": { + "name": "component_2", + "version": "1.0.0", + "bom-ref": "component_2", + "components": [ + { + "name": "component_2_sub_1", + "version": "1.0.0", + "bom-ref": "component_2_sub_1", + "components": [ + { + "name": "component_2_sub_1_sub_1", + "version": "1.0.0", + "bom-ref": "component_2_sub_1_sub_1", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + } + ], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + }, + { + "name": "component_2_sub_2", + "version": "1.0.0", + "bom-ref": "component_2_sub_2", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + } + ], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + }, + "component_3": { + "name": "component_3", + "version": "1.0.0", + "bom-ref": "component_3", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + }, + "component_4": { + "name": "component_4", + "version": "1.0.0", + "bom-ref": "component_4", + "components": [ + { + "name": "component_4_sub_1", + "version": "1.0.0", + "bom-ref": "component_4_sub_1", + "components": [ + { + "name": "component_4_sub_1_sub_1", + "version": "1.0.0", + "bom-ref": "component_4_sub_1_sub_1", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + }, + { + "name": "component_4_sub_1_sub_2", + "version": "1.0.0", + "bom-ref": "component_4_sub_1_sub_2", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + } + ], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + } + ], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + }, + "component_1_sub_1": { + "name": "component_1_sub_1", + "version": "1.0.0", + "bom-ref": "component_1_sub_1", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + }, + "component_2_sub_1": { + "name": "component_2_sub_1", + "version": "1.0.0", + "bom-ref": "component_2_sub_1", + "components": [ + { + "name": "component_2_sub_1_sub_1", + "version": "1.0.0", + "bom-ref": "component_2_sub_1_sub_1", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + } + ], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + }, + "component_2_sub_2": { + "name": "component_2_sub_2", + "version": "1.0.0", + "bom-ref": "component_2_sub_2", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + }, + "component_2_sub_1_sub_1": { + "name": "component_2_sub_1_sub_1", + "version": "1.0.0", + "bom-ref": "component_2_sub_1_sub_1", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + }, + "component_4_sub_1": { + "name": "component_4_sub_1", + "version": "1.0.0", + "bom-ref": "component_4_sub_1", + "components": [ + { + "name": "component_4_sub_1_sub_1", + "version": "1.0.0", + "bom-ref": "component_4_sub_1_sub_1", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + }, + { + "name": "component_4_sub_1_sub_2", + "version": "1.0.0", + "bom-ref": "component_4_sub_1_sub_2", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + } + ], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + }, + "component_4_sub_1_sub_1": { + "name": "component_4_sub_1_sub_1", + "version": "1.0.0", + "bom-ref": "component_4_sub_1_sub_1", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + }, + "component_4_sub_1_sub_2": { + "name": "component_4_sub_1_sub_2", + "version": "1.0.0", + "bom-ref": "component_4_sub_1_sub_2", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + } + }, + "test_filter_component_new_components": [ + { + "name": "component_1", + "version": "1.0.0", + "bom-ref": "component_1", + "components": [ + { + "name": "component_1_sub_1", + "version": "1.0.0", + "bom-ref": "component_1_sub_1", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + } + ], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + }, + { + "name": "component_2", + "version": "1.0.0", + "bom-ref": "component_2", + "components": [ + { + "name": "component_2_sub_1", + "version": "1.0.0", + "bom-ref": "component_2_sub_1", + "components": [ + { + "name": "component_2_sub_1_sub_1", + "version": "1.0.0", + "bom-ref": "component_2_sub_1_sub_1", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + } + ], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + }, + { + "name": "component_2_sub_2", + "version": "1.0.0", + "bom-ref": "component_2_sub_2", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + } + ], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + }, + { + "name": "component_4", + "version": "1.0.0", + "bom-ref": "component_4", + "components": [ + { + "name": "component_4_sub_1", + "version": "1.0.0", + "bom-ref": "component_4_sub_1", + "components": [ + { + "name": "component_4_sub_1_sub_1", + "version": "1.0.0", + "bom-ref": "component_4_sub_1_sub_1", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + }, + { + "name": "component_4_sub_1_sub_2", + "version": "1.0.0", + "bom-ref": "component_4_sub_1_sub_2", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + } + ], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + } + ], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + } + ], + "test_filter_component_kept_components_expected": [ + { + "name": "component_2", + "version": "1.0.0", + "bom-ref": "component_2", + "components": [ + { + "name": "component_2_sub_2", + "version": "1.0.0", + "bom-ref": "component_2_sub_2", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + } + ], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + }, + { + "name": "component_4_sub_1_sub_1", + "version": "1.0.0", + "bom-ref": "component_4_sub_1_sub_1", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + }, + { + "name": "component_4_sub_1", + "version": "1.0.0", + "bom-ref": "component_4_sub_1", + "components": [ + { + "name": "component_4_sub_1_sub_1", + "version": "1.0.0", + "bom-ref": "component_4_sub_1_sub_1", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + } + ], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + }, + { + "name": "component_2_sub_1_sub_1", + "version": "1.0.0", + "bom-ref": "component_2_sub_1_sub_1", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + }, + { + "name": "component_2_sub_2", + "version": "1.0.0", + "bom-ref": "component_2_sub_2", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + }, + { + "name": "component_1_sub_1", + "version": "1.0.0", + "bom-ref": "component_1_sub_1", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + }, + { + "name": "component_4", + "version": "1.0.0", + "bom-ref": "component_4", + "components": [ + { + "name": "component_4_sub_1", + "version": "1.0.0", + "bom-ref": "component_4_sub_1", + "components": [ + { + "name": "component_4_sub_1_sub_1", + "version": "1.0.0", + "bom-ref": "component_4_sub_1_sub_1", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + } + ], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + } + ], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + } + ], + "test_merge_hierarchical_present_components": [ + { + "name": "component_1", + "version": "1.0.0", + "bom-ref": "component_1", + "components": [ + { + "name": "component_1_sub_1", + "version": "1.0.0", + "bom-ref": "component_1_sub_1", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + } + ], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + }, + { + "name": "component_3", + "version": "1.0.0", + "bom-ref": "component_3", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + }, + { + "name": "component_2_sub_1", + "version": "1.0.0", + "bom-ref": "component_2_sub_1", + "components": [ + { + "name": "component_2_sub_1_sub_1", + "version": "1.0.0", + "bom-ref": "component_2_sub_1_sub_1", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + } + ], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + }, + { + "name": "component_4_sub_1_sub_2", + "version": "1.0.0", + "bom-ref": "component_4_sub_1_sub_2", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + } + ], + "test_merge_hierarchical_new_components": [ + { + "name": "component_1", + "version": "1.0.0", + "bom-ref": "component_1", + "components": [ + { + "name": "component_1_sub_1", + "version": "1.0.0", + "bom-ref": "component_1_sub_1", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + } + ], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + }, + { + "name": "component_2", + "version": "1.0.0", + "bom-ref": "component_2", + "components": [ + { + "name": "component_2_sub_1", + "version": "1.0.0", + "bom-ref": "component_2_sub_1", + "components": [ + { + "name": "component_2_sub_1_sub_1", + "version": "1.0.0", + "bom-ref": "component_2_sub_1_sub_1", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + } + ], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + }, + { + "name": "component_2_sub_2", + "version": "1.0.0", + "bom-ref": "component_2_sub_2", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + } + ], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + }, + { + "name": "component_4", + "version": "1.0.0", + "bom-ref": "component_4", + "components": [ + { + "name": "component_4_sub_1", + "version": "1.0.0", + "bom-ref": "component_4_sub_1", + "components": [ + { + "name": "component_4_sub_1_sub_1", + "version": "1.0.0", + "bom-ref": "component_4_sub_1_sub_1", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + }, + { + "name": "component_4_sub_1_sub_2", + "version": "1.0.0", + "bom-ref": "component_4_sub_1_sub_2", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + } + ], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + } + ], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + } + ], + "hierarchical_expected": [ + { + "name": "component_1", + "version": "1.0.0", + "bom-ref": "component_1", + "components": [ + { + "name": "component_1_sub_1", + "version": "1.0.0", + "bom-ref": "component_1_sub_1", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + } + ], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + }, + { + "name": "component_3", + "version": "1.0.0", + "bom-ref": "component_3", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + }, + { + "name": "component_2_sub_1", + "version": "1.0.0", + "bom-ref": "component_2_sub_1", + "components": [ + { + "name": "component_2_sub_1_sub_1", + "version": "1.0.0", + "bom-ref": "component_2_sub_1_sub_1", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + } + ], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + }, + { + "name": "component_4_sub_1_sub_2", + "version": "1.0.0", + "bom-ref": "component_4_sub_1_sub_2", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + }, + { + "name": "component_2", + "version": "1.0.0", + "bom-ref": "component_2", + "components": [ + { + "name": "component_2_sub_2", + "version": "1.0.0", + "bom-ref": "component_2_sub_2", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + } + ], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + }, + { + "name": "component_4", + "version": "1.0.0", + "bom-ref": "component_4", + "components": [ + { + "name": "component_4_sub_1", + "version": "1.0.0", + "bom-ref": "component_4_sub_1", + "components": [ + { + "name": "component_4_sub_1_sub_1", + "version": "1.0.0", + "bom-ref": "component_4_sub_1_sub_1", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + } + ], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + } + ], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + } + ], + "singled_out_test_cases": { + "test_case_merge_two_sub_components_to_existing_component": { + "original": [ + { + "name": "comp_1", + "version": "1.0.0", + "bom-ref": "comp_1", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + }, + { + "name": "comp_2", + "version": "1.0.0", + "bom-ref": "comp_2", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + } + ], + "new": [ + { + "name": "comp_1", + "version": "1.0.0", + "bom-ref": "comp_1", + "components": [ + { + "name": "comp_3", + "version": "1.0.0", + "bom-ref": "comp_3", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + }, + { + "name": "comp_4", + "version": "1.0.0", + "bom-ref": "comp_4", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + } + ], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + } + ], + "merged_hr": [ + { + "name": "comp_1", + "version": "1.0.0", + "bom-ref": "comp_1", + "components": [ + { + "name": "comp_3", + "version": "1.0.0", + "bom-ref": "comp_3", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + }, + { + "name": "comp_4", + "version": "1.0.0", + "bom-ref": "comp_4", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + } + ], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + }, + { + "name": "comp_2", + "version": "1.0.0", + "bom-ref": "comp_2", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + } + ], + "merged_normal": [ + { + "name": "comp_1", + "version": "1.0.0", + "bom-ref": "comp_1", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + }, + { + "name": "comp_2", + "version": "1.0.0", + "bom-ref": "comp_2", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + }, + { + "name": "comp_3", + "version": "1.0.0", + "bom-ref": "comp_3", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + }, + { + "name": "comp_4", + "version": "1.0.0", + "bom-ref": "comp_4", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + } + ] + }, + "test_case_merge_two_sub_sub_components_to_existing_component": { + "original": [ + { + "name": "comp_1", + "version": "1.0.0", + "bom-ref": "comp_1", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + }, + { + "name": "comp_2", + "version": "1.0.0", + "bom-ref": "comp_2", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + } + ], + "new": [ + { + "name": "comp_3", + "version": "1.0.0", + "bom-ref": "comp_3", + "components": [ + { + "name": "comp_1", + "version": "1.0.0", + "bom-ref": "comp_1", + "components": [ + { + "name": "comp_4", + "version": "1.0.0", + "bom-ref": "comp_4", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + }, + { + "name": "comp_5", + "version": "1.0.0", + "bom-ref": "comp_5", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + } + ], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + } + ], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + } + ], + "merged_hr": [ + { + "name": "comp_1", + "version": "1.0.0", + "bom-ref": "comp_1", + "components": [ + { + "name": "comp_4", + "version": "1.0.0", + "bom-ref": "comp_4", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + }, + { + "name": "comp_5", + "version": "1.0.0", + "bom-ref": "comp_5", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + } + ], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + }, + { + "name": "comp_2", + "version": "1.0.0", + "bom-ref": "comp_2", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + }, + { + "name": "comp_3", + "version": "1.0.0", + "bom-ref": "comp_3", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + } + ], + "merged_normal": [ + { + "name": "comp_1", + "version": "1.0.0", + "bom-ref": "comp_1", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + }, + { + "name": "comp_2", + "version": "1.0.0", + "bom-ref": "comp_2", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + }, + { + "name": "comp_3", + "version": "1.0.0", + "bom-ref": "comp_3", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + }, + { + "name": "comp_4", + "version": "1.0.0", + "bom-ref": "comp_4", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + }, + { + "name": "comp_5", + "version": "1.0.0", + "bom-ref": "comp_5", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + } + ] + }, + "test_case_remove_sub_sub_components_to_existing_component": { + "original": [ + { + "name": "comp_1", + "version": "1.0.0", + "bom-ref": "comp_1", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + }, + { + "name": "comp_2", + "version": "1.0.0", + "bom-ref": "comp_2", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + } + ], + "new": [ + { + "name": "comp_3", + "version": "1.0.0", + "bom-ref": "comp_3", + "components": [ + { + "name": "comp_1", + "version": "1.0.0", + "bom-ref": "comp_1", + "components": [ + { + "name": "comp_4", + "version": "1.0.0", + "bom-ref": "comp_4", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + } + ], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + } + ], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + } + ], + "merged_hr": [ + { + "name": "comp_1", + "version": "1.0.0", + "bom-ref": "comp_1", + "components": [ + { + "name": "comp_4", + "version": "1.0.0", + "bom-ref": "comp_4", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + } + ], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + }, + { + "name": "comp_2", + "version": "1.0.0", + "bom-ref": "comp_2", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + }, + { + "name": "comp_3", + "version": "1.0.0", + "bom-ref": "comp_3", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + } + ], + "merged_normal": [ + { + "name": "comp_1", + "version": "1.0.0", + "bom-ref": "comp_1", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + }, + { + "name": "comp_2", + "version": "1.0.0", + "bom-ref": "comp_2", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + }, + { + "name": "comp_3", + "version": "1.0.0", + "bom-ref": "comp_3", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + }, + { + "name": "comp_4", + "version": "1.0.0", + "bom-ref": "comp_4", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + } + ] + }, + "test_case_remove_sub_sub_components_to_existing_sub_component": { + "original": [ + { + "name": "comp_1", + "version": "1.0.0", + "bom-ref": "comp_1", + "components": [ + { + "name": "comp_5", + "version": "1.0.0", + "bom-ref": "comp_5", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + } + ], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + }, + { + "name": "comp_2", + "version": "1.0.0", + "bom-ref": "comp_2", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + } + ], + "new": [ + { + "name": "comp_3", + "version": "1.0.0", + "bom-ref": "comp_3", + "components": [ + { + "name": "comp_1", + "version": "1.0.0", + "bom-ref": "comp_1", + "components": [ + { + "name": "comp_5", + "version": "1.0.0", + "bom-ref": "comp_5", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + }, + { + "name": "comp_4", + "version": "1.0.0", + "bom-ref": "comp_4", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + } + ], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + } + ], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + } + ], + "merged_hr": [ + { + "name": "comp_1", + "version": "1.0.0", + "bom-ref": "comp_1", + "components": [ + { + "name": "comp_5", + "version": "1.0.0", + "bom-ref": "comp_5", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + }, + { + "name": "comp_4", + "version": "1.0.0", + "bom-ref": "comp_4", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + } + ], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + }, + { + "name": "comp_2", + "version": "1.0.0", + "bom-ref": "comp_2", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + }, + { + "name": "comp_3", + "version": "1.0.0", + "bom-ref": "comp_3", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + } + ], + "merged_normal": [ + { + "name": "comp_1", + "version": "1.0.0", + "bom-ref": "comp_1", + "components": [ + { + "name": "comp_5", + "version": "1.0.0", + "bom-ref": "comp_5", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + } + ], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + }, + { + "name": "comp_2", + "version": "1.0.0", + "bom-ref": "comp_2", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + }, + { + "name": "comp_3", + "version": "1.0.0", + "bom-ref": "comp_3", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + }, + { + "name": "comp_4", + "version": "1.0.0", + "bom-ref": "comp_4", + "components": [], + "type": "library", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved" + } + ] + } + } } diff --git a/tests/integration/data/merge.expected.cdx.json b/tests/integration/data/merge.expected.cdx.json index 4ab7d125..b1f6f7f0 100644 --- a/tests/integration/data/merge.expected.cdx.json +++ b/tests/integration/data/merge.expected.cdx.json @@ -3,7 +3,7 @@ "specVersion": "1.5", "version": 2, "metadata": { - "timestamp": "2024-04-10T11:17:06+00:00", + "timestamp": "2024-12-17T21:30:33+00:00", "authors": [ { "name": "automated" @@ -33,7 +33,7 @@ "type": "application", "name": "cyclonedx-editor-validator", "publisher": "Festo SE & Co. KG", - "version": "0" + "version": "0.0.0" } ] } @@ -66,6 +66,54 @@ } ] }, + { + "type": "library", + "bom-ref": "gov_comp_1", + "name": "gov_comp_1", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved", + "version": "5.0.3.96", + "components": [ + { + "type": "library", + "bom-ref": "gov_comp_1_sub_1", + "name": "gov_comp_1_sub_1", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved", + "version": "5.0.3.96", + "components": [ + { + "type": "library", + "bom-ref": "gov_comp_1_sub_1_sub_1", + "name": "gov_comp_1_sub_1_sub_1", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved", + "version": "5.0.3.96" + } + ] + } + ] + }, + { + "type": "library", + "bom-ref": "gov_comp_2", + "name": "gov_comp_2", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved", + "version": "5.0.3.96" + }, { "type": "library", "bom-ref": "sp_first_component", @@ -445,6 +493,119 @@ } } ] + }, + { + "type": "library", + "bom-ref": "merged_comp_1", + "name": "merged_comp_1", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved", + "version": "5.0.3.96", + "components": [ + { + "type": "library", + "bom-ref": "merged_comp_1_sub_1", + "name": "merged_comp_1_sub_1", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved", + "version": "5.0.3.96" + }, + { + "type": "library", + "bom-ref": "merged_comp_1_sub_2", + "name": "merged_comp_1_sub_2", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved", + "version": "5.0.3.96" + }, + { + "type": "library", + "bom-ref": "merged_comp_1_sub_3", + "name": "merged_comp_1_sub_3", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved", + "version": "5.0.3.96" + } + ] + }, + { + "type": "library", + "bom-ref": "gov_comp_1_sub_1_sub_2", + "name": "gov_comp_1_sub_1_sub_2", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved", + "version": "5.0.3.96" + }, + { + "type": "library", + "bom-ref": "gov_comp_1_sub_2", + "name": "gov_comp_1_sub_2", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved", + "version": "5.0.3.96" + }, + { + "type": "library", + "bom-ref": "Should not disappear!", + "name": "some deep sub component", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved", + "version": "5.0.3.96" + }, + { + "type": "library", + "bom-ref": "gov_comp_2_sub_1", + "name": "gov_comp_2_sub_1", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved", + "version": "5.0.3.96" + }, + { + "type": "library", + "bom-ref": "gov_comp_2_sub_2", + "name": "gov_comp_2_sub_2", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved", + "version": "5.0.3.96", + "components": [] + }, + { + "type": "library", + "bom-ref": "gov_comp_2_sub_3", + "name": "gov_comp_2_sub_3", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved", + "version": "5.0.3.96" } ], "dependencies": [ @@ -647,5 +808,5 @@ ] } ], - "serialNumber": "urn:uuid:700a0c02-31b0-4401-a358-30c19733d028" + "serialNumber": "urn:uuid:a3b00100-4130-4d19-89b7-222c25ab7811" } diff --git a/tests/integration/data/merge.expected_hierarchical.cdx.json b/tests/integration/data/merge.expected_hierarchical.cdx.json new file mode 100644 index 00000000..71146de2 --- /dev/null +++ b/tests/integration/data/merge.expected_hierarchical.cdx.json @@ -0,0 +1,814 @@ +{ + "bomFormat": "CycloneDX", + "specVersion": "1.5", + "version": 2, + "metadata": { + "timestamp": "2024-12-17T21:55:36+00:00", + "authors": [ + { + "name": "automated" + } + ], + "component": { + "type": "application", + "bom-ref": "governing_program", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "name": "governing_program", + "version": "T5.0.3.96", + "licenses": [ + { + "license": { + "name": "company internal" + } + } + ], + "copyright": "Company Legal 2022, all rights reserved" + }, + "tools": { + "components": [ + { + "type": "application", + "name": "cyclonedx-editor-validator", + "publisher": "Festo SE & Co. KG", + "version": "0.0.0" + } + ] + } + }, + "components": [ + { + "type": "library", + "bom-ref": "sub_program", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "name": "sub_program", + "copyright": "Company Legal 2022, all rights reserved", + "version": "T5.0.3.96" + }, + { + "type": "library", + "bom-ref": "gp_first_component-copy", + "supplier": { + "name": "The first component Contributors" + }, + "name": "gp_first_component", + "version": "2.24.0", + "licenses": [ + { + "license": { + "id": "Apache-2.0" + } + } + ] + }, + { + "type": "library", + "bom-ref": "gov_comp_1", + "name": "gov_comp_1", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved", + "version": "5.0.3.96", + "components": [ + { + "type": "library", + "bom-ref": "gov_comp_1_sub_1", + "name": "gov_comp_1_sub_1", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved", + "version": "5.0.3.96", + "components": [ + { + "type": "library", + "bom-ref": "gov_comp_1_sub_1_sub_1", + "name": "gov_comp_1_sub_1_sub_1", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved", + "version": "5.0.3.96" + }, + { + "type": "library", + "bom-ref": "gov_comp_1_sub_1_sub_2", + "name": "gov_comp_1_sub_1_sub_2", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved", + "version": "5.0.3.96" + } + ] + }, + { + "type": "library", + "bom-ref": "gov_comp_1_sub_2", + "name": "gov_comp_1_sub_2", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved", + "version": "5.0.3.96" + }, + { + "type": "library", + "bom-ref": "Should not disappear!", + "name": "some deep sub component", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved", + "version": "5.0.3.96" + } + ] + }, + { + "type": "library", + "bom-ref": "gov_comp_2", + "name": "gov_comp_2", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved", + "version": "5.0.3.96", + "components": [ + { + "type": "library", + "bom-ref": "gov_comp_2_sub_1", + "name": "gov_comp_2_sub_1", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved", + "version": "5.0.3.96" + }, + { + "type": "library", + "bom-ref": "gov_comp_2_sub_2", + "name": "gov_comp_2_sub_2", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved", + "version": "5.0.3.96", + "components": [] + }, + { + "type": "library", + "bom-ref": "gov_comp_2_sub_3", + "name": "gov_comp_2_sub_3", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved", + "version": "5.0.3.96" + } + ] + }, + { + "type": "library", + "bom-ref": "sp_first_component", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "name": "sp_first_component", + "copyright": "Company Legal 2022, all rights reserved", + "version": "e84ec5567d56d2dcf963ed2e454c106d9a9f323b" + }, + { + "type": "library", + "bom-ref": "sp_second_component", + "supplier": { + "name": "some name Microcontroller GmbH" + }, + "name": "sp_second_component", + "version": "5.16.1.0", + "licenses": [ + { + "license": { + "name": "some name OS-01264" + } + } + ] + }, + { + "type": "library", + "bom-ref": "sp_fifteenth_component", + "supplier": { + "name": "The first component Contributors" + }, + "name": "gp_first_component", + "version": "3.1.0", + "licenses": [ + { + "license": { + "id": "Apache-2.0" + } + } + ] + }, + { + "type": "library", + "bom-ref": "sp_fourth_component", + "supplier": { + "name": "some name Microcontroller GmbH" + }, + "name": "sp_fourth_component", + "version": "5.16.1.0", + "licenses": [ + { + "license": { + "name": "some name OS-01267" + } + } + ] + }, + { + "type": "library", + "bom-ref": "sp_fifth_component", + "supplier": { + "name": "some name Microcontroller GmbH" + }, + "name": "sp_fifth_component", + "version": "3.42.6", + "licenses": [ + { + "license": { + "name": "some name IP-00443" + } + } + ] + }, + { + "type": "library", + "bom-ref": "sp_sixth_component", + "supplier": { + "name": "some name Microcontroller GmbH" + }, + "name": "sp_sixth_component", + "version": "5.14.0", + "licenses": [ + { + "license": { + "name": "some name FS-00766" + } + } + ] + }, + { + "type": "library", + "bom-ref": "sp_seventh_component", + "supplier": { + "name": "some name Microcontroller GmbH" + }, + "name": "sp_seventh_component", + "version": "1.02h", + "licenses": [ + { + "license": { + "name": "some name MODB-00197" + } + } + ] + }, + { + "type": "library", + "bom-ref": "sp_eight_component", + "supplier": { + "name": "some electronics" + }, + "name": "sp_eight_component", + "version": "1.14.0", + "licenses": [ + { + "license": { + "id": "Apache-2.0" + } + }, + { + "license": { + "id": "BSD-3-Clause" + } + }, + { + "license": { + "id": "MIT" + } + }, + { + "license": { + "id": "IJG" + } + }, + { + "license": { + "name": "ST SLA0044" + } + }, + { + "license": { + "name": "ST Proprietary" + } + } + ] + }, + { + "type": "library", + "bom-ref": "sp_ninth_component", + "supplier": { + "name": "some electronics" + }, + "name": "sp_ninth_component", + "version": "1.8.0", + "licenses": [ + { + "license": { + "id": "Apache-2.0" + } + }, + { + "license": { + "id": "BSD-3-Clause" + } + }, + { + "license": { + "id": "MIT" + } + }, + { + "license": { + "id": "IJG" + } + }, + { + "license": { + "name": "ST SLA0044" + } + }, + { + "license": { + "name": "ST Proprietary" + } + } + ] + }, + { + "type": "library", + "bom-ref": "sp_tenth_component", + "supplier": { + "name": "some electronics" + }, + "name": "sp_tenth_component", + "version": "1.4.0-1", + "licenses": [ + { + "license": { + "id": "Apache-2.0" + } + }, + { + "license": { + "id": "BSD-3-Clause" + } + }, + { + "license": { + "id": "MIT" + } + }, + { + "license": { + "id": "IJG" + } + }, + { + "license": { + "name": "ST SLA0044" + } + }, + { + "license": { + "name": "ST Proprietary" + } + } + ] + }, + { + "type": "library", + "bom-ref": "sp_eleventh_component", + "supplier": { + "name": "some stuff" + }, + "name": "sp_eleventh_component", + "version": "6.15.2", + "licenses": [ + { + "license": { + "id": "MIT" + } + } + ] + }, + { + "type": "library", + "bom-ref": "sp_twelfth_component", + "supplier": { + "name": " a supplier" + }, + "name": "sp_twelfth_component", + "version": "1.12", + "licenses": [ + { + "license": { + "id": "MIT" + } + }, + { + "license": { + "id": "BSD-3-Clause" + } + }, + { + "license": { + "id": "Apache-2.0" + } + }, + { + "license": { + "id": "GPL-2.0-or-later" + } + }, + { + "license": { + "id": "GPL-2.0" + } + }, + { + "license": { + "id": "BSD-4-Clause" + } + }, + { + "license": { + "id": "ISC" + } + }, + { + "license": { + "id": "BSD-1-Clause" + } + }, + { + "license": { + "id": "Zlib" + } + }, + { + "license": { + "id": "OFL-1.1" + } + }, + { + "license": { + "name": "MCD-ST Liberty SW License Agreement V2" + } + }, + { + "license": { + "name": "PJRC.COM" + } + }, + { + "license": { + "id": "LGPL-3.0-only" + } + } + ] + }, + { + "type": "library", + "bom-ref": "sp_thirteenth_component", + "supplier": { + "name": " another supplier" + }, + "name": "sp_thirteenth_component", + "version": "3.8.0", + "licenses": [ + { + "license": { + "id": "MIT" + } + } + ] + }, + { + "type": "library", + "bom-ref": "sp_fourteenth_component", + "supplier": { + "name": "leg" + }, + "name": "GNU Tools for Arm Embedded Processors", + "version": "9-2019-q4-major", + "copyright": "Arm Limited" + }, + { + "type": "library", + "bom-ref": "sp_sixteenth_component", + "supplier": { + "name": "Free Software Foundation, Inc." + }, + "name": "sp_sixteenth_component", + "version": "9.2.1", + "licenses": [ + { + "license": { + "id": "GPL-3.0-only" + } + } + ] + }, + { + "type": "library", + "bom-ref": "sp_seventeenth_component", + "supplier": { + "name": "some website" + }, + "name": "sp_seventeenth_component", + "version": "3.1.0", + "licenses": [ + { + "license": { + "id": "GPL-3.0-only" + } + } + ] + }, + { + "type": "library", + "bom-ref": "merged_comp_1", + "name": "merged_comp_1", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved", + "version": "5.0.3.96", + "components": [ + { + "type": "library", + "bom-ref": "merged_comp_1_sub_1", + "name": "merged_comp_1_sub_1", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved", + "version": "5.0.3.96" + }, + { + "type": "library", + "bom-ref": "merged_comp_1_sub_2", + "name": "merged_comp_1_sub_2", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved", + "version": "5.0.3.96" + }, + { + "type": "library", + "bom-ref": "merged_comp_1_sub_3", + "name": "merged_comp_1_sub_3", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved", + "version": "5.0.3.96" + } + ] + } + ], + "dependencies": [ + { + "ref": "sub_program", + "dependsOn": [ + "sp_first_component", + "sp_second_component", + "sp_fourth_component", + "sp_fifth_component", + "sp_sixth_component", + "sp_seventh_component", + "sp_eight_component", + "sp_ninth_component", + "sp_tenth_component", + "sp_eleventh_component", + "sp_twelfth_component", + "sp_thirteenth_component", + "sp_fourteenth_component", + "sp_fifteenth_component" + ] + }, + { + "ref": "sp_first_component", + "dependsOn": [] + }, + { + "ref": "sp_second_component", + "dependsOn": [] + }, + { + "ref": "sp_fourth_component", + "dependsOn": [] + }, + { + "ref": "sp_fifth_component", + "dependsOn": [] + }, + { + "ref": "sp_sixth_component", + "dependsOn": [] + }, + { + "ref": "sp_seventh_component", + "dependsOn": [] + }, + { + "ref": "sp_eight_component", + "dependsOn": [] + }, + { + "ref": "sp_ninth_component", + "dependsOn": [] + }, + { + "ref": "sp_tenth_component", + "dependsOn": [] + }, + { + "ref": "sp_eleventh_component", + "dependsOn": [] + }, + { + "ref": "sp_twelfth_component", + "dependsOn": [] + }, + { + "ref": "sp_thirteenth_component", + "dependsOn": [] + }, + { + "ref": "sp_fourteenth_component", + "dependsOn": [ + "sp_sixteenth_component", + "sp_seventeenth_component" + ] + }, + { + "ref": "sp_sixteenth_component", + "dependsOn": [] + }, + { + "ref": "sp_seventeenth_component", + "dependsOn": [] + }, + { + "ref": "sp_fifteenth_component", + "dependsOn": [] + }, + { + "ref": "governing_program", + "dependsOn": [ + "sub_program", + "gp_first_component-copy" + ] + }, + { + "ref": "gp_first_component-copy", + "dependsOn": [] + } + ], + "compositions": [ + { + "aggregate": "incomplete", + "assemblies": [ + "sub_program", + "gp_first_component-copy", + "sp_fifth_component", + "sp_sixth_component", + "sp_seventh_component", + "sp_eight_component", + "sp_ninth_component", + "sp_tenth_component", + "sp_eleventh_component", + "sp_twelfth_component", + "sp_thirteenth_component", + "sp_fourteenth_component", + "sp_sixteenth_component", + "sp_seventeenth_component", + "sp_fifteenth_component" + ] + }, + { + "aggregate": "complete", + "assemblies": [ + "sp_first_component", + "sp_second_component", + "sp_fourth_component" + ] + } + ], + "vulnerabilities": [ + { + "description": "The application is vulnerable to remote SQL injection and shell upload", + "id": "Vul 1", + "ratings": [ + { + "score": 9.8, + "severity": "critical", + "method": "CVSSv31", + "vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H" + }, + { + "score": 7.5, + "severity": "high", + "method": "CVSSv2", + "vector": "AV:N/AC:L/Au:N/C:P/I:P/A:P" + } + ], + "affects": [ + { + "ref": "sub_program" + } + ] + }, + { + "description": "The application is vulnerable to remote SQL injection and shell upload", + "id": "Vul 2", + "ratings": [ + { + "score": 9.8, + "severity": "critical", + "method": "CVSSv31", + "vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H" + }, + { + "score": 7.5, + "severity": "high", + "method": "CVSSv2", + "vector": "AV:N/AC:L/Au:N/C:P/I:P/A:P" + } + ], + "affects": [ + { + "ref": "sp_fifteenth_component" + } + ] + }, + { + "description": "The application is vulnerable to remote SQL injection and shell upload", + "id": "Vul 3", + "ratings": [ + { + "score": 9.8, + "severity": "critical", + "method": "CVSSv31", + "vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H" + }, + { + "score": 7.5, + "severity": "high", + "method": "CVSSv2", + "vector": "AV:N/AC:L/Au:N/C:P/I:P/A:P" + } + ], + "affects": [ + { + "ref": "sp_first_component" + } + ] + } + ], + "serialNumber": "urn:uuid:d1feafb2-b64a-46ac-9231-2576172cd354" +} diff --git a/tests/integration/data/merge.input_1.cdx.json b/tests/integration/data/merge.input_1.cdx.json index a835c1b8..a0ef716e 100644 --- a/tests/integration/data/merge.input_1.cdx.json +++ b/tests/integration/data/merge.input_1.cdx.json @@ -55,6 +55,54 @@ } } ] + }, + { + "type": "library", + "bom-ref": "gov_comp_1", + "name": "gov_comp_1", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved", + "version": "5.0.3.96", + "components": [ + { + "type": "library", + "bom-ref": "gov_comp_1_sub_1", + "name": "gov_comp_1_sub_1", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved", + "version": "5.0.3.96", + "components": [ + { + "type": "library", + "bom-ref": "gov_comp_1_sub_1_sub_1", + "name": "gov_comp_1_sub_1_sub_1", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved", + "version": "5.0.3.96" + } + ] + } + ] + }, + { + "type": "library", + "bom-ref": "gov_comp_2", + "name": "gov_comp_2", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved", + "version": "5.0.3.96" } ], "dependencies": [ diff --git a/tests/integration/data/merge.input_2.cdx.json b/tests/integration/data/merge.input_2.cdx.json index 7d9e76b7..021a7384 100644 --- a/tests/integration/data/merge.input_2.cdx.json +++ b/tests/integration/data/merge.input_2.cdx.json @@ -408,6 +408,183 @@ } } ] + }, + { + "type": "library", + "bom-ref": "gov_comp_1", + "name": "gov_comp_1", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved", + "version": "5.0.3.96", + "components": [ + { + "type": "library", + "bom-ref": "gov_comp_1_sub_1", + "name": "gov_comp_1_sub_1", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved", + "version": "5.0.3.96", + "components": [ + { + "type": "library", + "bom-ref": "gov_comp_1_sub_1_sub_2", + "name": "gov_comp_1_sub_1_sub_2", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved", + "version": "5.0.3.96" + } + ] + }, + { + "type": "library", + "bom-ref": "gov_comp_1_sub_2", + "name": "gov_comp_1_sub_2", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved", + "version": "5.0.3.96" + } + ] + }, + { + "type": "library", + "bom-ref": "merged_comp_1", + "name": "merged_comp_1", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved", + "version": "5.0.3.96", + "components": [ + { + "type": "library", + "bom-ref": "merged_comp_1_sub_1", + "name": "merged_comp_1_sub_1", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved", + "version": "5.0.3.96" + }, + { + "type": "library", + "bom-ref": "merged_comp_1_sub_2", + "name": "merged_comp_1_sub_2", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved", + "version": "5.0.3.96" + }, + { + "type": "library", + "bom-ref": "merged_comp_1_sub_3", + "name": "merged_comp_1_sub_3", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved", + "version": "5.0.3.96" + } + ] + }, + { + "type": "library", + "bom-ref": "gov_comp_2", + "name": "gov_comp_2", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved", + "version": "5.0.3.96", + "components": [ + { + "type": "library", + "bom-ref": "gov_comp_2_sub_1", + "name": "gov_comp_2_sub_1", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved", + "version": "5.0.3.96" + }, + { + "type": "library", + "bom-ref": "gov_comp_2_sub_2", + "name": "gov_comp_2_sub_2", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved", + "version": "5.0.3.96", + "components": [ + { + "type": "library", + "bom-ref": "This should disappear", + "name": "gov_comp_1", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved", + "version": "5.0.3.96", + "components": [ + { + "type": "library", + "bom-ref": "Should not disappear!", + "name": "some deep sub component", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved", + "version": "5.0.3.96" + }, + { + "type": "library", + "bom-ref": "gov_comp_1_sub_1_sub_1", + "name": "gov_comp_1_sub_1_sub_1", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved", + "version": "5.0.3.96" + } + ] + } + ] + }, + { + "type": "library", + "bom-ref": "gov_comp_2_sub_3", + "name": "gov_comp_2_sub_3", + "supplier": { + "name": "Company Legal" + }, + "group": "com.company.governing", + "copyright": "Company Legal 2022, all rights reserved", + "version": "5.0.3.96" + } + ] } ], "dependencies": [ diff --git a/tests/integration/test_integration.py b/tests/integration/test_integration.py index 8143ddc8..2fcc06c5 100644 --- a/tests/integration/test_integration.py +++ b/tests/integration/test_integration.py @@ -501,6 +501,40 @@ def test_direct( expected = load_sbom(data_dir / "merge.expected_from-folder.cdx.json") assert expected == actual + def test_hierarchical( + self, + argv: Callable[..., None], + data_dir: Path, + capsys: pytest.CaptureFixture[str], + ) -> None: + input_folder = data_dir + + input_1 = input_folder / "merge.input_1.cdx.json" + input_2 = input_folder / "merge.input_2.cdx.json" + + argv("merge", str(input_1), str(input_2), "--hierarchical") + exit_code, actual, _ = run_main(capsys=capsys, parse_output="json") + + assert exit_code == Status.OK + + expected = load_sbom(data_dir / "merge.expected_hierarchical.cdx.json") + + assert expected == actual + + def test_same_sbom_warning_duplicate( + self, + argv: Callable[..., None], + data_dir: Path, + capsys: pytest.CaptureFixture[str], + ) -> None: + + input_1 = data_dir / "merge.input_1.cdx.json" + + argv("merge", str(input_1), str(input_1), "--hierarchical") + _, _, warnings = run_main(capsys=capsys, parse_output="json") + + assert "Dropping a duplicate component" in warnings + def test_order( self, argv: Callable[..., None], diff --git a/tests/test_merge.py b/tests/test_merge.py index 02d25d14..d7656706 100644 --- a/tests/test_merge.py +++ b/tests/test_merge.py @@ -1,10 +1,12 @@ # SPDX-License-Identifier: GPL-3.0-or-later +import copy import json import unittest from cdxev import merge from cdxev.auxiliary import sbomFunctions as sbF +from cdxev.auxiliary.identity import ComponentIdentity from tests.auxiliary import helper as helper path_to_folder_with_test_sboms = "tests/auxiliary/test_merge_sboms/" @@ -722,6 +724,110 @@ def test_renaming_same_component_in_other_sbom(self) -> None: ) ) + def test_filter_component(self) -> None: + # considered test cases: + # - top level component present, sub component not + # - top level component not present, sublevel component present + # sub_sub component not present + # - top level not present, sub_sub present + components = load_sections_for_test_sbom()["hierarchical_components"] + present_components = [ + ComponentIdentity.create(components["component_1"], allow_unsafe=True), + ComponentIdentity.create(components["component_3"], allow_unsafe=True), + ComponentIdentity.create( + components["component_2_sub_1"], allow_unsafe=True + ), + ComponentIdentity.create( + components["component_4_sub_1_sub_2"], allow_unsafe=True + ), + ] + + kept_components_expected = load_sections_for_test_sbom()[ + "test_filter_component_kept_components_expected" + ] + new_components = load_sections_for_test_sbom()[ + "test_filter_component_new_components" + ] + components["component_1"]["components"] = [components["component_1_sub_1"]] + + kept_components: list[dict] = [] + dropped_components: list[dict] = [] + add_to_existing: dict[ComponentIdentity, dict] = {} + + merge.filter_component( + present_components, + new_components, + kept_components, + dropped_components, + add_to_existing, + ) + + add_to_existing_expected = { + ComponentIdentity.create(components["component_1"], allow_unsafe=True): [ + components["component_1_sub_1"] + ], + ComponentIdentity.create( + components["component_2_sub_1"], allow_unsafe=True + ): [components["component_2_sub_1_sub_1"]], + } + + add_to_existing_identical = True + for key in add_to_existing_expected.keys(): + if add_to_existing_expected[key] != add_to_existing[key]: + add_to_existing_identical = False + + kept_components_identical = True + for comp in kept_components: + if comp not in kept_components_expected: + kept_components_identical = False + + self.assertTrue(len(dropped_components) == 3) + self.assertTrue( + len(add_to_existing.keys()) == len(add_to_existing_expected.keys()) + ) + self.assertTrue(len(kept_components) == len(kept_components_expected)) + self.assertTrue(add_to_existing_identical) + self.assertTrue(kept_components_identical) + + def test_individual_merge_cases(self) -> None: + test_cases = load_sections_for_test_sbom()["singled_out_test_cases"] + + for key in test_cases.keys(): + original = test_cases[key]["original"] + new = test_cases[key]["new"] + merged_hr = test_cases[key]["merged_hr"] + merged_nm = test_cases[key]["merged_normal"] + merged_hierarchical = merge.merge_components( + copy.deepcopy({"components": original}), + copy.deepcopy({"components": new}), + hierarchical=True, + ) + merged_normal = merge.merge_components( + copy.deepcopy({"components": original}), + copy.deepcopy({"components": new}), + ) + + self.assertCountEqual(merged_hierarchical, merged_hr) + self.assertCountEqual(merged_normal, merged_nm) + + def test_merge_hierarchical(self) -> None: + new_components = load_sections_for_test_sbom()[ + "test_merge_hierarchical_new_components" + ] + present_components = load_sections_for_test_sbom()[ + "test_merge_hierarchical_present_components" + ] + + merged_components = merge.merge_components( + {"components": present_components}, + {"components": new_components}, + hierarchical=True, + ) + + expected_components = load_sections_for_test_sbom()["hierarchical_expected"] + + self.assertEqual(merged_components, expected_components) + class TestMergeCompositions(unittest.TestCase): def test_only_first_sbom_contains_compositions(self) -> None: