diff --git a/cyclonedx/model/__init__.py b/cyclonedx/model/__init__.py index 61ba9beb..c074a701 100644 --- a/cyclonedx/model/__init__.py +++ b/cyclonedx/model/__init__.py @@ -1127,139 +1127,6 @@ def __repr__(self) -> str: return f'' -@serializable.serializable_class -class Tool: - """ - This is our internal representation of the `toolType` complex type within the CycloneDX standard. - - Tool(s) are the things used in the creation of the CycloneDX document. - - Tool might be deprecated since CycloneDX 1.5, but it is not deprecated in this library. - In fact, this library will try to provide a compatibility layer if needed. - - .. note:: - See the CycloneDX Schema for toolType: https://cyclonedx.org/docs/1.3/#type_toolType - """ - - def __init__( - self, *, - vendor: Optional[str] = None, - name: Optional[str] = None, - version: Optional[str] = None, - hashes: Optional[Iterable[HashType]] = None, - external_references: Optional[Iterable[ExternalReference]] = None, - ) -> None: - self.vendor = vendor - self.name = name - self.version = version - self.hashes = hashes or [] # type:ignore[assignment] - self.external_references = external_references or [] # type:ignore[assignment] - - @property - @serializable.xml_sequence(1) - @serializable.xml_string(serializable.XmlStringSerializationType.NORMALIZED_STRING) - def vendor(self) -> Optional[str]: - """ - The name of the vendor who created the tool. - - Returns: - `str` if set else `None` - """ - return self._vendor - - @vendor.setter - def vendor(self, vendor: Optional[str]) -> None: - self._vendor = vendor - - @property - @serializable.xml_sequence(2) - @serializable.xml_string(serializable.XmlStringSerializationType.NORMALIZED_STRING) - def name(self) -> Optional[str]: - """ - The name of the tool. - - Returns: - `str` if set else `None` - """ - return self._name - - @name.setter - def name(self, name: Optional[str]) -> None: - self._name = name - - @property - @serializable.xml_sequence(3) - @serializable.xml_string(serializable.XmlStringSerializationType.NORMALIZED_STRING) - def version(self) -> Optional[str]: - """ - The version of the tool. - - Returns: - `str` if set else `None` - """ - return self._version - - @version.setter - def version(self, version: Optional[str]) -> None: - self._version = version - - @property - @serializable.type_mapping(_HashTypeRepositorySerializationHelper) - @serializable.xml_sequence(4) - def hashes(self) -> 'SortedSet[HashType]': - """ - The hashes of the tool (if applicable). - - Returns: - Set of `HashType` - """ - return self._hashes - - @hashes.setter - def hashes(self, hashes: Iterable[HashType]) -> None: - self._hashes = SortedSet(hashes) - - @property - @serializable.view(SchemaVersion1Dot4) - @serializable.view(SchemaVersion1Dot5) - @serializable.view(SchemaVersion1Dot6) - @serializable.xml_array(serializable.XmlArraySerializationType.NESTED, 'reference') - @serializable.xml_sequence(5) - def external_references(self) -> 'SortedSet[ExternalReference]': - """ - External References provides a way to document systems, sites, and information that may be relevant but which - are not included with the BOM. - - Returns: - Set of `ExternalReference` - """ - return self._external_references - - @external_references.setter - def external_references(self, external_references: Iterable[ExternalReference]) -> None: - self._external_references = SortedSet(external_references) - - def __eq__(self, other: object) -> bool: - if isinstance(other, Tool): - return hash(other) == hash(self) - return False - - def __lt__(self, other: Any) -> bool: - if isinstance(other, Tool): - return _ComparableTuple(( - self.vendor, self.name, self.version - )) < _ComparableTuple(( - other.vendor, other.name, other.version - )) - return NotImplemented - - def __hash__(self) -> int: - return hash((self.vendor, self.name, self.version, tuple(self.hashes), tuple(self.external_references))) - - def __repr__(self) -> str: - return f'' - - @serializable.serializable_class class IdentifiableAction: """ @@ -1397,6 +1264,9 @@ def __repr__(self) -> str: return f'' +# Importing here to avoid a circular import +from .tool import Tool # pylint: disable=wrong-import-position # noqa: E402 + ThisTool = Tool( vendor='CycloneDX', name='cyclonedx-python-lib', @@ -1434,4 +1304,5 @@ def __repr__(self) -> str: type=ExternalReferenceType.WEBSITE, url=XsUri('https://github.com/CycloneDX/cyclonedx-python-lib/#readme') ) - ]) + ] +) diff --git a/cyclonedx/model/bom.py b/cyclonedx/model/bom.py index a36a5e10..a9c38888 100644 --- a/cyclonedx/model/bom.py +++ b/cyclonedx/model/bom.py @@ -37,13 +37,14 @@ SchemaVersion1Dot6, ) from ..serialization import LicenseRepositoryHelper, UrnUuidHelper -from . import ExternalReference, Property, ThisTool, Tool +from . import ExternalReference, Property, ThisTool from .bom_ref import BomRef from .component import Component from .contact import OrganizationalContact, OrganizationalEntity from .dependency import Dependable, Dependency from .license import License, LicenseExpression, LicenseRepository from .service import Service +from .tool import Tool, ToolsRepository, _ToolsRepositoryHelper from .vulnerability import Vulnerability if TYPE_CHECKING: # pragma: no cover @@ -61,7 +62,7 @@ class BomMetaData: def __init__( self, *, - tools: Optional[Iterable[Tool]] = None, + tools: Optional[Union[Iterable[Tool], ToolsRepository]] = None, authors: Optional[Iterable[OrganizationalContact]] = None, component: Optional[Component] = None, supplier: Optional[OrganizationalEntity] = None, @@ -89,7 +90,7 @@ def __init__( DeprecationWarning) if not tools: - self.tools.add(ThisTool) + self.tools.tools.add(ThisTool) @property @serializable.type_mapping(serializable.helpers.XsdDateTime) @@ -119,22 +120,22 @@ def timestamp(self, timestamp: datetime) -> None: # ... # TODO since CDX1.5 @property - @serializable.xml_array(serializable.XmlArraySerializationType.NESTED, 'tool') + @serializable.type_mapping(_ToolsRepositoryHelper) @serializable.xml_sequence(3) - def tools(self) -> 'SortedSet[Tool]': + def tools(self) -> ToolsRepository: """ Tools used to create this BOM. Returns: - `Set` of `Tool` objects. + `ToolsRepository` objects. """ - # TODO since CDX1.5 also supports `Component` and `Services`, not only `Tool` return self._tools @tools.setter - def tools(self, tools: Iterable[Tool]) -> None: - # TODO since CDX1.5 also supports `Component` and `Services`, not only `Tool` - self._tools = SortedSet(tools) + def tools(self, tools: Union[Iterable[Tool], ToolsRepository]) -> None: + self._tools = tools \ + if isinstance(tools, ToolsRepository) \ + else ToolsRepository(tools=tools) @property @serializable.xml_array(serializable.XmlArraySerializationType.NESTED, 'author') @@ -292,7 +293,7 @@ def __eq__(self, other: object) -> bool: def __hash__(self) -> int: return hash(( tuple(self.authors), self.component, tuple(self.licenses), self.manufacture, tuple(self.properties), - self.supplier, self.timestamp, tuple(self.tools), self.manufacturer, + self.supplier, self.timestamp, self.tools, self.manufacturer, )) def __repr__(self) -> str: diff --git a/cyclonedx/model/tool.py b/cyclonedx/model/tool.py new file mode 100644 index 00000000..e9749e8e --- /dev/null +++ b/cyclonedx/model/tool.py @@ -0,0 +1,365 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# Copyright (c) OWASP Foundation. All Rights Reserved. + + +from typing import TYPE_CHECKING, Any, Dict, Iterable, List, Optional, Tuple, Type, Union +from warnings import warn +from xml.etree.ElementTree import Element # nosec B405 + +import serializable +from serializable.helpers import BaseHelper +from sortedcontainers import SortedSet + +from .._internal.compare import ComparableTuple as _ComparableTuple +from ..exception.serialization import CycloneDxDeserializationException +from ..schema import SchemaVersion +from ..schema.schema import SchemaVersion1Dot4, SchemaVersion1Dot5, SchemaVersion1Dot6 +from . import ExternalReference, HashType, _HashTypeRepositorySerializationHelper +from .component import Component +from .service import Service + +if TYPE_CHECKING: # pragma: no cover + from serializable import ObjectMetadataLibrary, ViewType + + +@serializable.serializable_class +class Tool: + """ + This is our internal representation of the `toolType` complex type within the CycloneDX standard. + + Tool(s) are the things used in the creation of the CycloneDX document. + + Tool might be deprecated since CycloneDX 1.5, but it is not deprecated in this library. + In fact, this library will try to provide a compatibility layer if needed. + + .. note:: + See the CycloneDX Schema for toolType: https://cyclonedx.org/docs/1.3/#type_toolType + """ + + def __init__( + self, *, + vendor: Optional[str] = None, + name: Optional[str] = None, + version: Optional[str] = None, + hashes: Optional[Iterable[HashType]] = None, + external_references: Optional[Iterable[ExternalReference]] = None, + ) -> None: + self.vendor = vendor + self.name = name + self.version = version + self.hashes = hashes or () # type:ignore[assignment] + self.external_references = external_references or () # type:ignore[assignment] + + @property + @serializable.xml_sequence(1) + @serializable.xml_string(serializable.XmlStringSerializationType.NORMALIZED_STRING) + def vendor(self) -> Optional[str]: + """ + The name of the vendor who created the tool. + + Returns: + `str` if set else `None` + """ + return self._vendor + + @vendor.setter + def vendor(self, vendor: Optional[str]) -> None: + self._vendor = vendor + + @property + @serializable.xml_sequence(2) + @serializable.xml_string(serializable.XmlStringSerializationType.NORMALIZED_STRING) + def name(self) -> Optional[str]: + """ + The name of the tool. + + Returns: + `str` if set else `None` + """ + return self._name + + @name.setter + def name(self, name: Optional[str]) -> None: + self._name = name + + @property + @serializable.xml_sequence(3) + @serializable.xml_string(serializable.XmlStringSerializationType.NORMALIZED_STRING) + def version(self) -> Optional[str]: + """ + The version of the tool. + + Returns: + `str` if set else `None` + """ + return self._version + + @version.setter + def version(self, version: Optional[str]) -> None: + self._version = version + + @property + @serializable.type_mapping(_HashTypeRepositorySerializationHelper) + @serializable.xml_sequence(4) + def hashes(self) -> 'SortedSet[HashType]': + """ + The hashes of the tool (if applicable). + + Returns: + Set of `HashType` + """ + return self._hashes + + @hashes.setter + def hashes(self, hashes: Iterable[HashType]) -> None: + self._hashes = SortedSet(hashes) + + @property + @serializable.view(SchemaVersion1Dot4) + @serializable.view(SchemaVersion1Dot5) + @serializable.view(SchemaVersion1Dot6) + @serializable.xml_array(serializable.XmlArraySerializationType.NESTED, 'reference') + @serializable.xml_sequence(5) + def external_references(self) -> 'SortedSet[ExternalReference]': + """ + External References provides a way to document systems, sites, and information that may be relevant but which + are not included with the BOM. + + Returns: + Set of `ExternalReference` + """ + return self._external_references + + @external_references.setter + def external_references(self, external_references: Iterable[ExternalReference]) -> None: + self._external_references = SortedSet(external_references) + + def __eq__(self, other: object) -> bool: + if isinstance(other, Tool): + return hash(other) == hash(self) + return False + + def __lt__(self, other: Any) -> bool: + if isinstance(other, Tool): + return _ComparableTuple(( + self.vendor, self.name, self.version + )) < _ComparableTuple(( + other.vendor, other.name, other.version + )) + return NotImplemented + + def __hash__(self) -> int: + return hash((self.vendor, self.name, self.version, tuple(self.hashes), tuple(self.external_references))) + + def __repr__(self) -> str: + return f'' + + @classmethod + def from_component(cls: Type['Tool'], component: 'Component') -> 'Tool': + return cls( + vendor=component.group, + name=component.name, + version=component.version, + hashes=component.hashes, + external_references=component.external_references, + ) + + @classmethod + def from_service(cls: Type['Tool'], service: 'Service') -> 'Tool': + return cls( + vendor=service.group, + name=service.name, + version=service.version, + external_references=service.external_references, + ) + + +class ToolsRepository: + """ + The repository of tool formats + """ + + def __init__( + self, *, + components: Optional[Iterable[Component]] = None, + services: Optional[Iterable[Service]] = None, + # Deprecated since v1.5 + tools: Optional[Iterable[Tool]] = None + ) -> None: + if tools: + warn('`@.tools` is deprecated from CycloneDX v1.5 onwards. ' + 'Please use `@.components` and `@.services` instead.', + DeprecationWarning) + self.components = components or () # type:ignore[assignment] + self.services = services or () # type:ignore[assignment] + self.tools = tools or () # type:ignore[assignment] + + @property + def components(self) -> 'SortedSet[Component]': + """ + Returns: + A SortedSet of Components + """ + return self._components + + @components.setter + def components(self, components: Iterable[Component]) -> None: + self._components = SortedSet(components) + + @property + def services(self) -> 'SortedSet[Service]': + """ + Returns: + A SortedSet of Services + """ + return self._services + + @services.setter + def services(self, services: Iterable[Service]) -> None: + self._services = SortedSet(services) + + @property + def tools(self) -> 'SortedSet[Tool]': + return self._tools + + @tools.setter + def tools(self, tools: Iterable[Tool]) -> None: + self._tools = SortedSet(tools) + + def __len__(self) -> int: + return len(self._tools) \ + + len(self._components) \ + + len(self._services) + + def __bool__(self) -> bool: + return len(self._tools) > 0 \ + or len(self._components) > 0 \ + or len(self._services) > 0 + + def __eq__(self, other: object) -> bool: + if not isinstance(other, ToolsRepository): + return False + + return self._tools == other._tools \ + and self._components == other._components \ + and self._services == other._services + + def __hash__(self) -> int: + return hash((tuple(self._tools), tuple(self._components), tuple(self._services))) + + +class _ToolsRepositoryHelper(BaseHelper): + + @staticmethod + def __all_as_tools(o: ToolsRepository) -> Tuple[Tool, ...]: + return ( + *o.tools, + *map(Tool.from_component, o.components), + *map(Tool.from_service, o.services), + ) + + @staticmethod + def __supports_components_and_services(view: Any) -> bool: + try: + return view is not None and view().schema_version_enum >= SchemaVersion.V1_5 + except Exception: # pragma: no cover + return False + + @classmethod + def json_normalize(cls, o: ToolsRepository, *, + view: Optional[Type['ViewType']], + **__: Any) -> Any: + if len(o.tools) > 0 or not cls.__supports_components_and_services(view): + return cls.__all_as_tools(o) or None + elem: Dict[str, Any] = {} + if o.components: + elem['components'] = tuple(o.components) + if o.services: + elem['services'] = tuple(o.services) + return elem or None + + @classmethod + def json_denormalize(cls, o: Union[List[Dict[str, Any]], Dict[str, Any]], + **__: Any) -> ToolsRepository: + tools = None + components = None + services = None + if isinstance(o, Dict): + components = map(lambda c: Component.from_json( # type:ignore[attr-defined] + c), o.get('components', ())) + services = map(lambda s: Service.from_json( # type:ignore[attr-defined] + s), o.get('services', ())) + elif isinstance(o, Iterable): + tools = map(lambda t: Tool.from_json(t), o) # type:ignore[attr-defined] + return ToolsRepository(components=components, services=services, tools=tools) + + @classmethod + def xml_normalize(cls, o: ToolsRepository, *, + element_name: str, + view: Optional[Type['ViewType']], + xmlns: Optional[str], + **__: Any) -> Optional[Element]: + elem = Element(element_name) + if len(o.tools) > 0 or not cls.__supports_components_and_services(view): + elem.extend( + ti.as_xml( # type:ignore[attr-defined] + view_=view, as_string=False, element_name='tool', xmlns=xmlns) + for ti in cls.__all_as_tools(o) + ) + else: + if o.components: + elem_c = Element(f'{{{xmlns}}}components' if xmlns else 'components') + elem_c.extend( + ci.as_xml( # type:ignore[attr-defined] + view_=view, as_string=False, element_name='component', xmlns=xmlns) + for ci in o.components) + elem.append(elem_c) + if o.services: + elem_s = Element(f'{{{xmlns}}}services' if xmlns else 'services') + elem_s.extend( + si.as_xml( # type:ignore[attr-defined] + view_=view, as_string=False, element_name='service', xmlns=xmlns) + for si in o.services) + elem.append(elem_s) + return elem \ + if len(elem) > 0 \ + else None + + @classmethod + def xml_denormalize(cls, o: Element, *, + default_ns: Optional[str], + prop_info: 'ObjectMetadataLibrary.SerializableProperty', + ctx: Type[Any], + **kwargs: Any) -> ToolsRepository: + tools = [] + components = None + services = None + for e in o: + tag = e.tag if default_ns is None else e.tag.replace(f'{{{default_ns}}}', '') + if tag == 'tool': + tools.append(Tool.from_xml( # type:ignore[attr-defined] + e, default_ns)) + elif tag == 'components': + components = map(lambda s: Component.from_xml( # type:ignore[attr-defined] + s, default_ns), e) + elif tag == 'services': + services = map(lambda s: Service.from_xml( # type:ignore[attr-defined] + s, default_ns), e) + else: + raise CycloneDxDeserializationException(f'unexpected: {e!r}') + return ToolsRepository( + tools=tools, + components=components, + services=services) diff --git a/cyclonedx/model/vulnerability.py b/cyclonedx/model/vulnerability.py index 8c9528f4..2bce491c 100644 --- a/cyclonedx/model/vulnerability.py +++ b/cyclonedx/model/vulnerability.py @@ -42,7 +42,7 @@ from ..exception.model import MutuallyExclusivePropertiesException, NoPropertiesProvidedException from ..schema.schema import SchemaVersion1Dot4, SchemaVersion1Dot5, SchemaVersion1Dot6 from ..serialization import BomRefHelper -from . import Property, Tool, XsUri +from . import Property, XsUri from .bom_ref import BomRef from .contact import OrganizationalContact, OrganizationalEntity from .impact_analysis import ( @@ -51,6 +51,7 @@ ImpactAnalysisResponse, ImpactAnalysisState, ) +from .tool import Tool, ToolsRepository, _ToolsRepositoryHelper @serializable.serializable_class @@ -953,13 +954,13 @@ def __init__( published: Optional[datetime] = None, updated: Optional[datetime] = None, credits: Optional[VulnerabilityCredits] = None, - tools: Optional[Iterable[Tool]] = None, + tools: Optional[Union[Iterable[Tool], ToolsRepository]] = None, analysis: Optional[VulnerabilityAnalysis] = None, affects: Optional[Iterable[BomTarget]] = None, properties: Optional[Iterable[Property]] = None, ) -> None: if isinstance(bom_ref, BomRef): - self._bom_ref = bom_ref + self._bom_ref: BomRef = bom_ref else: self._bom_ref = BomRef(value=str(bom_ref) if bom_ref else None) self.id = id @@ -1246,20 +1247,22 @@ def credits(self, credits: Optional[VulnerabilityCredits]) -> None: self._credits = credits @property - @serializable.xml_array(serializable.XmlArraySerializationType.NESTED, 'tool') + @serializable.type_mapping(_ToolsRepositoryHelper) @serializable.xml_sequence(17) - def tools(self) -> 'SortedSet[Tool]': + def tools(self) -> ToolsRepository: """ - The tool(s) used to identify, confirm, or score the vulnerability. + Tools used to create this BOM. Returns: - Set of `Tool` + `ToolsRepository` objects. """ return self._tools @tools.setter - def tools(self, tools: Iterable[Tool]) -> None: - self._tools = SortedSet(tools) + def tools(self, tools: Union[Iterable[Tool], ToolsRepository]) -> None: + self._tools = tools \ + if isinstance(tools, ToolsRepository) \ + else ToolsRepository(tools=tools) @property @serializable.xml_sequence(18) @@ -1327,7 +1330,7 @@ def __hash__(self) -> int: return hash(( self.id, self.source, tuple(self.references), tuple(self.ratings), tuple(self.cwes), self.description, self.detail, self.recommendation, self.workaround, tuple(self.advisories), self.created, self.published, - self.updated, self.credits, tuple(self.tools), self.analysis, tuple(self.affects), tuple(self.properties) + self.updated, self.credits, self.tools, self.analysis, tuple(self.affects), tuple(self.properties) )) def __repr__(self) -> str: diff --git a/tests/_data/models.py b/tests/_data/models.py index 150739a0..0939f1ee 100644 --- a/tests/_data/models.py +++ b/tests/_data/models.py @@ -38,7 +38,7 @@ Note, NoteText, Property, - Tool, + ThisTool, XsUri, ) from cyclonedx.model.bom import Bom, BomMetaData @@ -89,6 +89,7 @@ from cyclonedx.model.license import DisjunctiveLicense, License, LicenseAcknowledgement, LicenseExpression from cyclonedx.model.release_note import ReleaseNotes from cyclonedx.model.service import Service +from cyclonedx.model.tool import Tool, ToolsRepository from cyclonedx.model.vulnerability import ( BomTarget, BomTargetVersionRange, @@ -494,9 +495,9 @@ def get_bom_with_component_setuptools_with_vulnerability() -> Bom: ], individuals=[get_org_contact_2()] ), - tools=[ - Tool(vendor='CycloneDX', name='cyclonedx-python-lib') - ], + tools=ToolsRepository(tools=( + Tool(vendor='CycloneDX', name='cyclonedx-python-lib'), + )), analysis=VulnerabilityAnalysis( state=ImpactAnalysisState.EXPLOITABLE, justification=ImpactAnalysisJustification.REQUIRES_ENVIRONMENT, responses=[ImpactAnalysisResponse.CAN_NOT_FIX], detail='Some extra detail' @@ -1047,6 +1048,127 @@ def get_bom_with_multiple_licenses() -> Bom: ) +def get_bom_with_tools() -> Bom: + return _make_bom( + metadata=BomMetaData( + tools=( + ThisTool, + Tool(name='test-tool-b'), + Tool(vendor='example', + name='test-tool-a', + version='1.33.7', + hashes=[HashType.from_composite_str( + 'sha256:adbbbe72c8f023b4a2d96a3978f69d94873ab2fef424e0298287c3368519c1a6')], + external_references=[get_external_reference_1()], + ), + ) + ) + ) + + +def get_bom_with_tools_with_component_migrate() -> Bom: + return _make_bom( + metadata=BomMetaData( + tools=ToolsRepository( + components=( + Component(name='test-component', bom_ref='test-component'), + Component(type=ComponentType.APPLICATION, + bom_ref='other-component', + group='acme', + name='other-component', + hashes=[HashType.from_composite_str( + 'sha256:49b420bd8d8182542a76d4422e0c7890dcc88a3d8ddad04da06366d8c40ac8ca')], + external_references=[get_external_reference_1()], + ), + ) + ) + ) + ) + + +def get_bom_with_tools_with_service_migrate() -> Bom: + return _make_bom( + metadata=BomMetaData( + tools=ToolsRepository( + services=( + Service(name='test-service', bom_ref='test-service'), + Service(group='acme', + name='other-service', + bom_ref='other-service', + external_references=[get_external_reference_1()], + ), + ) + ) + ) + ) + + +def get_bom_with_tools_with_component_and_service_migrate() -> Bom: + return _make_bom( + metadata=BomMetaData( + tools=ToolsRepository( + components=( + Component(name='test-component', bom_ref='test-component'), + Component(type=ComponentType.APPLICATION, + bom_ref='other-component', + group='acme', + name='other-component', + hashes=[HashType.from_composite_str( + 'sha256:49b420bd8d8182542a76d4422e0c7890dcc88a3d8ddad04da06366d8c40ac8ca')], + external_references=[get_external_reference_1()], + ), + ), + services=( + Service(name='test-service', bom_ref='test-service'), + Service(group='acme', + name='other-service', + bom_ref='other-service', + external_references=[get_external_reference_1()], + ), + ) + ) + ) + ) + + +def get_bom_with_tools_with_component_and_service_and_tools_irreversible_migrate() -> Bom: + tools = ToolsRepository() + tcomp = tools.components + tserv = tools.services + ttools = tools.tools + tcomp.update(( + Component(name='test-component', bom_ref='test-component'), + Component(type=ComponentType.APPLICATION, + bom_ref='other-component', + group='acme', + name='other-component', + hashes=[HashType.from_composite_str( + 'sha256:49b420bd8d8182542a76d4422e0c7890dcc88a3d8ddad04da06366d8c40ac8ca')], + external_references=[get_external_reference_1()], + ), + )) + tserv.update(( + Service(name='test-service', bom_ref='test-service'), + Service(group='acme', + name='other-service', + bom_ref='other-service', + external_references=[get_external_reference_1()], + ), + )) + ttools.update(( + ThisTool, + Tool(name='test-tool-b'), + Tool(vendor='example', + name='test-tool-a', + version='1.33.7', + hashes=[HashType.from_composite_str( + 'sha256:adbbbe72c8f023b4a2d96a3978f69d94873ab2fef424e0298287c3368519c1a6')], + external_references=[get_external_reference_1()], + ), + )) + return _make_bom(metadata=BomMetaData(tools=tools)) + + def get_bom_for_issue_497_urls() -> Bom: """regression test for issue #497 see https://github.com/CycloneDX/cyclonedx-python-lib/issues/497 @@ -1122,6 +1244,7 @@ def get_bom_for_issue_630_empty_property() -> Bom: ) }) + # --- @@ -1135,6 +1258,11 @@ def get_bom_for_issue_630_empty_property() -> Bom: if n.startswith('get_bom_') and not n.endswith('_invalid') and not n.endswith('_migrate') ) +all_get_bom_funct_valid_reversible_migrate = tuple( + (n, f) for n, f in getmembers(sys.modules[__name__], isfunction) + if n.startswith('get_bom_') and n.endswith('_migrate') and not n.endswith('_irreversible_migrate') +) + all_get_bom_funct_invalid = tuple( (n, f) for n, f in getmembers(sys.modules[__name__], isfunction) if n.startswith('get_bom_') and n.endswith('_invalid') diff --git a/tests/_data/own/xml/1.5/invalid-tool.xml b/tests/_data/own/xml/1.5/invalid-tool.xml new file mode 100644 index 00000000..f57259b9 --- /dev/null +++ b/tests/_data/own/xml/1.5/invalid-tool.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/tests/_data/snapshots/get_bom_with_tools-1.0.xml.bin b/tests/_data/snapshots/get_bom_with_tools-1.0.xml.bin new file mode 100644 index 00000000..acb06612 --- /dev/null +++ b/tests/_data/snapshots/get_bom_with_tools-1.0.xml.bin @@ -0,0 +1,4 @@ + + + + diff --git a/tests/_data/snapshots/get_bom_with_tools-1.1.xml.bin b/tests/_data/snapshots/get_bom_with_tools-1.1.xml.bin new file mode 100644 index 00000000..55ef5cda --- /dev/null +++ b/tests/_data/snapshots/get_bom_with_tools-1.1.xml.bin @@ -0,0 +1,4 @@ + + + + diff --git a/tests/_data/snapshots/get_bom_with_tools-1.2.json.bin b/tests/_data/snapshots/get_bom_with_tools-1.2.json.bin new file mode 100644 index 00000000..ca38e5f0 --- /dev/null +++ b/tests/_data/snapshots/get_bom_with_tools-1.2.json.bin @@ -0,0 +1,31 @@ +{ + "metadata": { + "timestamp": "2023-01-07T13:44:32.312678+00:00", + "tools": [ + { + "name": "cyclonedx-python-lib", + "vendor": "CycloneDX", + "version": "TESTING" + }, + { + "hashes": [ + { + "alg": "SHA-256", + "content": "adbbbe72c8f023b4a2d96a3978f69d94873ab2fef424e0298287c3368519c1a6" + } + ], + "name": "test-tool-a", + "vendor": "example", + "version": "1.33.7" + }, + { + "name": "test-tool-b" + } + ] + }, + "serialNumber": "urn:uuid:1441d33a-e0fc-45b5-af3b-61ee52a88bac", + "version": 1, + "$schema": "http://cyclonedx.org/schema/bom-1.2b.schema.json", + "bomFormat": "CycloneDX", + "specVersion": "1.2" +} \ No newline at end of file diff --git a/tests/_data/snapshots/get_bom_with_tools-1.2.xml.bin b/tests/_data/snapshots/get_bom_with_tools-1.2.xml.bin new file mode 100644 index 00000000..32920edd --- /dev/null +++ b/tests/_data/snapshots/get_bom_with_tools-1.2.xml.bin @@ -0,0 +1,24 @@ + + + + 2023-01-07T13:44:32.312678+00:00 + + + CycloneDX + cyclonedx-python-lib + TESTING + + + example + test-tool-a + 1.33.7 + + adbbbe72c8f023b4a2d96a3978f69d94873ab2fef424e0298287c3368519c1a6 + + + + test-tool-b + + + + diff --git a/tests/_data/snapshots/get_bom_with_tools-1.3.json.bin b/tests/_data/snapshots/get_bom_with_tools-1.3.json.bin new file mode 100644 index 00000000..1eb342c2 --- /dev/null +++ b/tests/_data/snapshots/get_bom_with_tools-1.3.json.bin @@ -0,0 +1,31 @@ +{ + "metadata": { + "timestamp": "2023-01-07T13:44:32.312678+00:00", + "tools": [ + { + "name": "cyclonedx-python-lib", + "vendor": "CycloneDX", + "version": "TESTING" + }, + { + "hashes": [ + { + "alg": "SHA-256", + "content": "adbbbe72c8f023b4a2d96a3978f69d94873ab2fef424e0298287c3368519c1a6" + } + ], + "name": "test-tool-a", + "vendor": "example", + "version": "1.33.7" + }, + { + "name": "test-tool-b" + } + ] + }, + "serialNumber": "urn:uuid:1441d33a-e0fc-45b5-af3b-61ee52a88bac", + "version": 1, + "$schema": "http://cyclonedx.org/schema/bom-1.3a.schema.json", + "bomFormat": "CycloneDX", + "specVersion": "1.3" +} \ No newline at end of file diff --git a/tests/_data/snapshots/get_bom_with_tools-1.3.xml.bin b/tests/_data/snapshots/get_bom_with_tools-1.3.xml.bin new file mode 100644 index 00000000..aee9da63 --- /dev/null +++ b/tests/_data/snapshots/get_bom_with_tools-1.3.xml.bin @@ -0,0 +1,24 @@ + + + + 2023-01-07T13:44:32.312678+00:00 + + + CycloneDX + cyclonedx-python-lib + TESTING + + + example + test-tool-a + 1.33.7 + + adbbbe72c8f023b4a2d96a3978f69d94873ab2fef424e0298287c3368519c1a6 + + + + test-tool-b + + + + diff --git a/tests/_data/snapshots/get_bom_with_tools-1.4.json.bin b/tests/_data/snapshots/get_bom_with_tools-1.4.json.bin new file mode 100644 index 00000000..3a26a1c6 --- /dev/null +++ b/tests/_data/snapshots/get_bom_with_tools-1.4.json.bin @@ -0,0 +1,78 @@ +{ + "metadata": { + "timestamp": "2023-01-07T13:44:32.312678+00:00", + "tools": [ + { + "externalReferences": [ + { + "type": "build-system", + "url": "https://github.com/CycloneDX/cyclonedx-python-lib/actions" + }, + { + "type": "distribution", + "url": "https://pypi.org/project/cyclonedx-python-lib/" + }, + { + "type": "documentation", + "url": "https://cyclonedx-python-library.readthedocs.io/" + }, + { + "type": "issue-tracker", + "url": "https://github.com/CycloneDX/cyclonedx-python-lib/issues" + }, + { + "type": "license", + "url": "https://github.com/CycloneDX/cyclonedx-python-lib/blob/main/LICENSE" + }, + { + "type": "release-notes", + "url": "https://github.com/CycloneDX/cyclonedx-python-lib/blob/main/CHANGELOG.md" + }, + { + "type": "vcs", + "url": "https://github.com/CycloneDX/cyclonedx-python-lib" + }, + { + "type": "website", + "url": "https://github.com/CycloneDX/cyclonedx-python-lib/#readme" + } + ], + "name": "cyclonedx-python-lib", + "vendor": "CycloneDX", + "version": "TESTING" + }, + { + "externalReferences": [ + { + "comment": "No comment", + "hashes": [ + { + "alg": "SHA-256", + "content": "806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b" + } + ], + "type": "distribution", + "url": "https://cyclonedx.org" + } + ], + "hashes": [ + { + "alg": "SHA-256", + "content": "adbbbe72c8f023b4a2d96a3978f69d94873ab2fef424e0298287c3368519c1a6" + } + ], + "name": "test-tool-a", + "vendor": "example", + "version": "1.33.7" + }, + { + "name": "test-tool-b" + } + ] + }, + "serialNumber": "urn:uuid:1441d33a-e0fc-45b5-af3b-61ee52a88bac", + "version": 1, + "$schema": "http://cyclonedx.org/schema/bom-1.4.schema.json", + "bomFormat": "CycloneDX", + "specVersion": "1.4" +} \ No newline at end of file diff --git a/tests/_data/snapshots/get_bom_with_tools-1.4.xml.bin b/tests/_data/snapshots/get_bom_with_tools-1.4.xml.bin new file mode 100644 index 00000000..aae5b797 --- /dev/null +++ b/tests/_data/snapshots/get_bom_with_tools-1.4.xml.bin @@ -0,0 +1,59 @@ + + + + 2023-01-07T13:44:32.312678+00:00 + + + CycloneDX + cyclonedx-python-lib + TESTING + + + https://github.com/CycloneDX/cyclonedx-python-lib/actions + + + https://pypi.org/project/cyclonedx-python-lib/ + + + https://cyclonedx-python-library.readthedocs.io/ + + + https://github.com/CycloneDX/cyclonedx-python-lib/issues + + + https://github.com/CycloneDX/cyclonedx-python-lib/blob/main/LICENSE + + + https://github.com/CycloneDX/cyclonedx-python-lib/blob/main/CHANGELOG.md + + + https://github.com/CycloneDX/cyclonedx-python-lib + + + https://github.com/CycloneDX/cyclonedx-python-lib/#readme + + + + + example + test-tool-a + 1.33.7 + + adbbbe72c8f023b4a2d96a3978f69d94873ab2fef424e0298287c3368519c1a6 + + + + https://cyclonedx.org + No comment + + 806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b + + + + + + test-tool-b + + + + diff --git a/tests/_data/snapshots/get_bom_with_crypto-1.6.json.bin b/tests/_data/snapshots/get_bom_with_tools-1.5.json.bin similarity index 50% rename from tests/_data/snapshots/get_bom_with_crypto-1.6.json.bin rename to tests/_data/snapshots/get_bom_with_tools-1.5.json.bin index 46c4d952..173988c2 100644 --- a/tests/_data/snapshots/get_bom_with_crypto-1.6.json.bin +++ b/tests/_data/snapshots/get_bom_with_tools-1.5.json.bin @@ -1,61 +1,4 @@ { - "components": [ - { - "bom-ref": "26b1ce0f-bec6-4bfe-9db1-03b75a4ed1ec", - "cryptoProperties": { - "assetType": "protocol", - "oid": "an-oid-here", - "protocolProperties": { - "cipherSuites": [ - { - "identifiers": [ - "TLS_AES_128_CCM_8_SHA256" - ], - "name": "TLS_AES_128_CCM_8_SHA256" - }, - { - "identifiers": [ - "TLS_AES_128_CCM_SHA256" - ], - "name": "TLS_AES_128_CCM_SHA256" - }, - { - "identifiers": [ - "TLS_AES_128_GCM_SHA256" - ], - "name": "TLS_AES_128_GCM_SHA256" - }, - { - "identifiers": [ - "TLS_AES_256_GCM_SHA384" - ], - "name": "TLS_AES_256_GCM_SHA384" - }, - { - "identifiers": [ - "TLS_CHACHA20_POLY1305_SHA256" - ], - "name": "TLS_CHACHA20_POLY1305_SHA256" - } - ], - "type": "tls", - "version": "1.3" - } - }, - "name": "TLS", - "tags": [ - "protocl", - "tls" - ], - "type": "cryptographic-asset", - "version": "v1.3" - } - ], - "dependencies": [ - { - "ref": "26b1ce0f-bec6-4bfe-9db1-03b75a4ed1ec" - } - ], "metadata": { "timestamp": "2023-01-07T13:44:32.312678+00:00", "tools": [ @@ -97,12 +40,49 @@ "name": "cyclonedx-python-lib", "vendor": "CycloneDX", "version": "TESTING" + }, + { + "externalReferences": [ + { + "comment": "No comment", + "hashes": [ + { + "alg": "SHA-256", + "content": "806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b" + } + ], + "type": "distribution", + "url": "https://cyclonedx.org" + } + ], + "hashes": [ + { + "alg": "SHA-256", + "content": "adbbbe72c8f023b4a2d96a3978f69d94873ab2fef424e0298287c3368519c1a6" + } + ], + "name": "test-tool-a", + "vendor": "example", + "version": "1.33.7" + }, + { + "name": "test-tool-b" } ] }, + "properties": [ + { + "name": "key1", + "value": "val1" + }, + { + "name": "key2", + "value": "val2" + } + ], "serialNumber": "urn:uuid:1441d33a-e0fc-45b5-af3b-61ee52a88bac", "version": 1, - "$schema": "http://cyclonedx.org/schema/bom-1.6.schema.json", + "$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json", "bomFormat": "CycloneDX", - "specVersion": "1.6" + "specVersion": "1.5" } \ No newline at end of file diff --git a/tests/_data/snapshots/get_bom_with_tools-1.5.xml.bin b/tests/_data/snapshots/get_bom_with_tools-1.5.xml.bin new file mode 100644 index 00000000..4800c7ba --- /dev/null +++ b/tests/_data/snapshots/get_bom_with_tools-1.5.xml.bin @@ -0,0 +1,63 @@ + + + + 2023-01-07T13:44:32.312678+00:00 + + + CycloneDX + cyclonedx-python-lib + TESTING + + + https://github.com/CycloneDX/cyclonedx-python-lib/actions + + + https://pypi.org/project/cyclonedx-python-lib/ + + + https://cyclonedx-python-library.readthedocs.io/ + + + https://github.com/CycloneDX/cyclonedx-python-lib/issues + + + https://github.com/CycloneDX/cyclonedx-python-lib/blob/main/LICENSE + + + https://github.com/CycloneDX/cyclonedx-python-lib/blob/main/CHANGELOG.md + + + https://github.com/CycloneDX/cyclonedx-python-lib + + + https://github.com/CycloneDX/cyclonedx-python-lib/#readme + + + + + example + test-tool-a + 1.33.7 + + adbbbe72c8f023b4a2d96a3978f69d94873ab2fef424e0298287c3368519c1a6 + + + + https://cyclonedx.org + No comment + + 806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b + + + + + + test-tool-b + + + + + val1 + val2 + + diff --git a/tests/_data/snapshots/get_bom_v1_6_with_crypto-1.6.json.bin b/tests/_data/snapshots/get_bom_with_tools-1.6.json.bin similarity index 53% rename from tests/_data/snapshots/get_bom_v1_6_with_crypto-1.6.json.bin rename to tests/_data/snapshots/get_bom_with_tools-1.6.json.bin index 46c4d952..5eb714cb 100644 --- a/tests/_data/snapshots/get_bom_v1_6_with_crypto-1.6.json.bin +++ b/tests/_data/snapshots/get_bom_with_tools-1.6.json.bin @@ -1,61 +1,4 @@ { - "components": [ - { - "bom-ref": "26b1ce0f-bec6-4bfe-9db1-03b75a4ed1ec", - "cryptoProperties": { - "assetType": "protocol", - "oid": "an-oid-here", - "protocolProperties": { - "cipherSuites": [ - { - "identifiers": [ - "TLS_AES_128_CCM_8_SHA256" - ], - "name": "TLS_AES_128_CCM_8_SHA256" - }, - { - "identifiers": [ - "TLS_AES_128_CCM_SHA256" - ], - "name": "TLS_AES_128_CCM_SHA256" - }, - { - "identifiers": [ - "TLS_AES_128_GCM_SHA256" - ], - "name": "TLS_AES_128_GCM_SHA256" - }, - { - "identifiers": [ - "TLS_AES_256_GCM_SHA384" - ], - "name": "TLS_AES_256_GCM_SHA384" - }, - { - "identifiers": [ - "TLS_CHACHA20_POLY1305_SHA256" - ], - "name": "TLS_CHACHA20_POLY1305_SHA256" - } - ], - "type": "tls", - "version": "1.3" - } - }, - "name": "TLS", - "tags": [ - "protocl", - "tls" - ], - "type": "cryptographic-asset", - "version": "v1.3" - } - ], - "dependencies": [ - { - "ref": "26b1ce0f-bec6-4bfe-9db1-03b75a4ed1ec" - } - ], "metadata": { "timestamp": "2023-01-07T13:44:32.312678+00:00", "tools": [ @@ -97,9 +40,46 @@ "name": "cyclonedx-python-lib", "vendor": "CycloneDX", "version": "TESTING" + }, + { + "externalReferences": [ + { + "comment": "No comment", + "hashes": [ + { + "alg": "SHA-256", + "content": "806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b" + } + ], + "type": "distribution", + "url": "https://cyclonedx.org" + } + ], + "hashes": [ + { + "alg": "SHA-256", + "content": "adbbbe72c8f023b4a2d96a3978f69d94873ab2fef424e0298287c3368519c1a6" + } + ], + "name": "test-tool-a", + "vendor": "example", + "version": "1.33.7" + }, + { + "name": "test-tool-b" } ] }, + "properties": [ + { + "name": "key1", + "value": "val1" + }, + { + "name": "key2", + "value": "val2" + } + ], "serialNumber": "urn:uuid:1441d33a-e0fc-45b5-af3b-61ee52a88bac", "version": 1, "$schema": "http://cyclonedx.org/schema/bom-1.6.schema.json", diff --git a/tests/_data/snapshots/get_bom_with_tools-1.6.xml.bin b/tests/_data/snapshots/get_bom_with_tools-1.6.xml.bin new file mode 100644 index 00000000..fc5013d8 --- /dev/null +++ b/tests/_data/snapshots/get_bom_with_tools-1.6.xml.bin @@ -0,0 +1,63 @@ + + + + 2023-01-07T13:44:32.312678+00:00 + + + CycloneDX + cyclonedx-python-lib + TESTING + + + https://github.com/CycloneDX/cyclonedx-python-lib/actions + + + https://pypi.org/project/cyclonedx-python-lib/ + + + https://cyclonedx-python-library.readthedocs.io/ + + + https://github.com/CycloneDX/cyclonedx-python-lib/issues + + + https://github.com/CycloneDX/cyclonedx-python-lib/blob/main/LICENSE + + + https://github.com/CycloneDX/cyclonedx-python-lib/blob/main/CHANGELOG.md + + + https://github.com/CycloneDX/cyclonedx-python-lib + + + https://github.com/CycloneDX/cyclonedx-python-lib/#readme + + + + + example + test-tool-a + 1.33.7 + + adbbbe72c8f023b4a2d96a3978f69d94873ab2fef424e0298287c3368519c1a6 + + + + https://cyclonedx.org + No comment + + 806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b + + + + + + test-tool-b + + + + + val1 + val2 + + diff --git a/tests/_data/snapshots/get_bom_with_tools_with_component_and_service_and_tools_irreversible_migrate-1.0.xml.bin b/tests/_data/snapshots/get_bom_with_tools_with_component_and_service_and_tools_irreversible_migrate-1.0.xml.bin new file mode 100644 index 00000000..acb06612 --- /dev/null +++ b/tests/_data/snapshots/get_bom_with_tools_with_component_and_service_and_tools_irreversible_migrate-1.0.xml.bin @@ -0,0 +1,4 @@ + + + + diff --git a/tests/_data/snapshots/get_bom_with_tools_with_component_and_service_and_tools_irreversible_migrate-1.1.xml.bin b/tests/_data/snapshots/get_bom_with_tools_with_component_and_service_and_tools_irreversible_migrate-1.1.xml.bin new file mode 100644 index 00000000..55ef5cda --- /dev/null +++ b/tests/_data/snapshots/get_bom_with_tools_with_component_and_service_and_tools_irreversible_migrate-1.1.xml.bin @@ -0,0 +1,4 @@ + + + + diff --git a/tests/_data/snapshots/get_bom_with_tools_with_component_and_service_and_tools_irreversible_migrate-1.2.json.bin b/tests/_data/snapshots/get_bom_with_tools_with_component_and_service_and_tools_irreversible_migrate-1.2.json.bin new file mode 100644 index 00000000..1fd2b7d6 --- /dev/null +++ b/tests/_data/snapshots/get_bom_with_tools_with_component_and_service_and_tools_irreversible_migrate-1.2.json.bin @@ -0,0 +1,51 @@ +{ + "metadata": { + "timestamp": "2023-01-07T13:44:32.312678+00:00", + "tools": [ + { + "name": "cyclonedx-python-lib", + "vendor": "CycloneDX", + "version": "TESTING" + }, + { + "hashes": [ + { + "alg": "SHA-256", + "content": "adbbbe72c8f023b4a2d96a3978f69d94873ab2fef424e0298287c3368519c1a6" + } + ], + "name": "test-tool-a", + "vendor": "example", + "version": "1.33.7" + }, + { + "name": "test-tool-b" + }, + { + "hashes": [ + { + "alg": "SHA-256", + "content": "49b420bd8d8182542a76d4422e0c7890dcc88a3d8ddad04da06366d8c40ac8ca" + } + ], + "name": "other-component", + "vendor": "acme" + }, + { + "name": "test-component" + }, + { + "name": "other-service", + "vendor": "acme" + }, + { + "name": "test-service" + } + ] + }, + "serialNumber": "urn:uuid:1441d33a-e0fc-45b5-af3b-61ee52a88bac", + "version": 1, + "$schema": "http://cyclonedx.org/schema/bom-1.2b.schema.json", + "bomFormat": "CycloneDX", + "specVersion": "1.2" +} \ No newline at end of file diff --git a/tests/_data/snapshots/get_bom_with_tools_with_component_and_service_and_tools_irreversible_migrate-1.2.xml.bin b/tests/_data/snapshots/get_bom_with_tools_with_component_and_service_and_tools_irreversible_migrate-1.2.xml.bin new file mode 100644 index 00000000..d8b2a4c1 --- /dev/null +++ b/tests/_data/snapshots/get_bom_with_tools_with_component_and_service_and_tools_irreversible_migrate-1.2.xml.bin @@ -0,0 +1,41 @@ + + + + 2023-01-07T13:44:32.312678+00:00 + + + CycloneDX + cyclonedx-python-lib + TESTING + + + example + test-tool-a + 1.33.7 + + adbbbe72c8f023b4a2d96a3978f69d94873ab2fef424e0298287c3368519c1a6 + + + + test-tool-b + + + acme + other-component + + 49b420bd8d8182542a76d4422e0c7890dcc88a3d8ddad04da06366d8c40ac8ca + + + + test-component + + + acme + other-service + + + test-service + + + + diff --git a/tests/_data/snapshots/get_bom_with_tools_with_component_and_service_and_tools_irreversible_migrate-1.3.json.bin b/tests/_data/snapshots/get_bom_with_tools_with_component_and_service_and_tools_irreversible_migrate-1.3.json.bin new file mode 100644 index 00000000..01886ae2 --- /dev/null +++ b/tests/_data/snapshots/get_bom_with_tools_with_component_and_service_and_tools_irreversible_migrate-1.3.json.bin @@ -0,0 +1,51 @@ +{ + "metadata": { + "timestamp": "2023-01-07T13:44:32.312678+00:00", + "tools": [ + { + "name": "cyclonedx-python-lib", + "vendor": "CycloneDX", + "version": "TESTING" + }, + { + "hashes": [ + { + "alg": "SHA-256", + "content": "adbbbe72c8f023b4a2d96a3978f69d94873ab2fef424e0298287c3368519c1a6" + } + ], + "name": "test-tool-a", + "vendor": "example", + "version": "1.33.7" + }, + { + "name": "test-tool-b" + }, + { + "hashes": [ + { + "alg": "SHA-256", + "content": "49b420bd8d8182542a76d4422e0c7890dcc88a3d8ddad04da06366d8c40ac8ca" + } + ], + "name": "other-component", + "vendor": "acme" + }, + { + "name": "test-component" + }, + { + "name": "other-service", + "vendor": "acme" + }, + { + "name": "test-service" + } + ] + }, + "serialNumber": "urn:uuid:1441d33a-e0fc-45b5-af3b-61ee52a88bac", + "version": 1, + "$schema": "http://cyclonedx.org/schema/bom-1.3a.schema.json", + "bomFormat": "CycloneDX", + "specVersion": "1.3" +} \ No newline at end of file diff --git a/tests/_data/snapshots/get_bom_with_tools_with_component_and_service_and_tools_irreversible_migrate-1.3.xml.bin b/tests/_data/snapshots/get_bom_with_tools_with_component_and_service_and_tools_irreversible_migrate-1.3.xml.bin new file mode 100644 index 00000000..b0d0956c --- /dev/null +++ b/tests/_data/snapshots/get_bom_with_tools_with_component_and_service_and_tools_irreversible_migrate-1.3.xml.bin @@ -0,0 +1,41 @@ + + + + 2023-01-07T13:44:32.312678+00:00 + + + CycloneDX + cyclonedx-python-lib + TESTING + + + example + test-tool-a + 1.33.7 + + adbbbe72c8f023b4a2d96a3978f69d94873ab2fef424e0298287c3368519c1a6 + + + + test-tool-b + + + acme + other-component + + 49b420bd8d8182542a76d4422e0c7890dcc88a3d8ddad04da06366d8c40ac8ca + + + + test-component + + + acme + other-service + + + test-service + + + + diff --git a/tests/_data/snapshots/get_bom_with_tools_with_component_and_service_and_tools_irreversible_migrate-1.4.json.bin b/tests/_data/snapshots/get_bom_with_tools_with_component_and_service_and_tools_irreversible_migrate-1.4.json.bin new file mode 100644 index 00000000..5dd3c4d1 --- /dev/null +++ b/tests/_data/snapshots/get_bom_with_tools_with_component_and_service_and_tools_irreversible_migrate-1.4.json.bin @@ -0,0 +1,124 @@ +{ + "metadata": { + "timestamp": "2023-01-07T13:44:32.312678+00:00", + "tools": [ + { + "externalReferences": [ + { + "type": "build-system", + "url": "https://github.com/CycloneDX/cyclonedx-python-lib/actions" + }, + { + "type": "distribution", + "url": "https://pypi.org/project/cyclonedx-python-lib/" + }, + { + "type": "documentation", + "url": "https://cyclonedx-python-library.readthedocs.io/" + }, + { + "type": "issue-tracker", + "url": "https://github.com/CycloneDX/cyclonedx-python-lib/issues" + }, + { + "type": "license", + "url": "https://github.com/CycloneDX/cyclonedx-python-lib/blob/main/LICENSE" + }, + { + "type": "release-notes", + "url": "https://github.com/CycloneDX/cyclonedx-python-lib/blob/main/CHANGELOG.md" + }, + { + "type": "vcs", + "url": "https://github.com/CycloneDX/cyclonedx-python-lib" + }, + { + "type": "website", + "url": "https://github.com/CycloneDX/cyclonedx-python-lib/#readme" + } + ], + "name": "cyclonedx-python-lib", + "vendor": "CycloneDX", + "version": "TESTING" + }, + { + "externalReferences": [ + { + "comment": "No comment", + "hashes": [ + { + "alg": "SHA-256", + "content": "806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b" + } + ], + "type": "distribution", + "url": "https://cyclonedx.org" + } + ], + "hashes": [ + { + "alg": "SHA-256", + "content": "adbbbe72c8f023b4a2d96a3978f69d94873ab2fef424e0298287c3368519c1a6" + } + ], + "name": "test-tool-a", + "vendor": "example", + "version": "1.33.7" + }, + { + "name": "test-tool-b" + }, + { + "externalReferences": [ + { + "comment": "No comment", + "hashes": [ + { + "alg": "SHA-256", + "content": "806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b" + } + ], + "type": "distribution", + "url": "https://cyclonedx.org" + } + ], + "hashes": [ + { + "alg": "SHA-256", + "content": "49b420bd8d8182542a76d4422e0c7890dcc88a3d8ddad04da06366d8c40ac8ca" + } + ], + "name": "other-component", + "vendor": "acme" + }, + { + "name": "test-component" + }, + { + "externalReferences": [ + { + "comment": "No comment", + "hashes": [ + { + "alg": "SHA-256", + "content": "806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b" + } + ], + "type": "distribution", + "url": "https://cyclonedx.org" + } + ], + "name": "other-service", + "vendor": "acme" + }, + { + "name": "test-service" + } + ] + }, + "serialNumber": "urn:uuid:1441d33a-e0fc-45b5-af3b-61ee52a88bac", + "version": 1, + "$schema": "http://cyclonedx.org/schema/bom-1.4.schema.json", + "bomFormat": "CycloneDX", + "specVersion": "1.4" +} \ No newline at end of file diff --git a/tests/_data/snapshots/get_bom_with_tools_with_component_and_service_and_tools_irreversible_migrate-1.4.xml.bin b/tests/_data/snapshots/get_bom_with_tools_with_component_and_service_and_tools_irreversible_migrate-1.4.xml.bin new file mode 100644 index 00000000..4144e524 --- /dev/null +++ b/tests/_data/snapshots/get_bom_with_tools_with_component_and_service_and_tools_irreversible_migrate-1.4.xml.bin @@ -0,0 +1,94 @@ + + + + 2023-01-07T13:44:32.312678+00:00 + + + CycloneDX + cyclonedx-python-lib + TESTING + + + https://github.com/CycloneDX/cyclonedx-python-lib/actions + + + https://pypi.org/project/cyclonedx-python-lib/ + + + https://cyclonedx-python-library.readthedocs.io/ + + + https://github.com/CycloneDX/cyclonedx-python-lib/issues + + + https://github.com/CycloneDX/cyclonedx-python-lib/blob/main/LICENSE + + + https://github.com/CycloneDX/cyclonedx-python-lib/blob/main/CHANGELOG.md + + + https://github.com/CycloneDX/cyclonedx-python-lib + + + https://github.com/CycloneDX/cyclonedx-python-lib/#readme + + + + + example + test-tool-a + 1.33.7 + + adbbbe72c8f023b4a2d96a3978f69d94873ab2fef424e0298287c3368519c1a6 + + + + https://cyclonedx.org + No comment + + 806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b + + + + + + test-tool-b + + + acme + other-component + + 49b420bd8d8182542a76d4422e0c7890dcc88a3d8ddad04da06366d8c40ac8ca + + + + https://cyclonedx.org + No comment + + 806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b + + + + + + test-component + + + acme + other-service + + + https://cyclonedx.org + No comment + + 806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b + + + + + + test-service + + + + diff --git a/tests/_data/snapshots/get_bom_with_tools_with_component_and_service_and_tools_irreversible_migrate-1.5.json.bin b/tests/_data/snapshots/get_bom_with_tools_with_component_and_service_and_tools_irreversible_migrate-1.5.json.bin new file mode 100644 index 00000000..4c467bf6 --- /dev/null +++ b/tests/_data/snapshots/get_bom_with_tools_with_component_and_service_and_tools_irreversible_migrate-1.5.json.bin @@ -0,0 +1,134 @@ +{ + "metadata": { + "timestamp": "2023-01-07T13:44:32.312678+00:00", + "tools": [ + { + "externalReferences": [ + { + "type": "build-system", + "url": "https://github.com/CycloneDX/cyclonedx-python-lib/actions" + }, + { + "type": "distribution", + "url": "https://pypi.org/project/cyclonedx-python-lib/" + }, + { + "type": "documentation", + "url": "https://cyclonedx-python-library.readthedocs.io/" + }, + { + "type": "issue-tracker", + "url": "https://github.com/CycloneDX/cyclonedx-python-lib/issues" + }, + { + "type": "license", + "url": "https://github.com/CycloneDX/cyclonedx-python-lib/blob/main/LICENSE" + }, + { + "type": "release-notes", + "url": "https://github.com/CycloneDX/cyclonedx-python-lib/blob/main/CHANGELOG.md" + }, + { + "type": "vcs", + "url": "https://github.com/CycloneDX/cyclonedx-python-lib" + }, + { + "type": "website", + "url": "https://github.com/CycloneDX/cyclonedx-python-lib/#readme" + } + ], + "name": "cyclonedx-python-lib", + "vendor": "CycloneDX", + "version": "TESTING" + }, + { + "externalReferences": [ + { + "comment": "No comment", + "hashes": [ + { + "alg": "SHA-256", + "content": "806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b" + } + ], + "type": "distribution", + "url": "https://cyclonedx.org" + } + ], + "hashes": [ + { + "alg": "SHA-256", + "content": "adbbbe72c8f023b4a2d96a3978f69d94873ab2fef424e0298287c3368519c1a6" + } + ], + "name": "test-tool-a", + "vendor": "example", + "version": "1.33.7" + }, + { + "name": "test-tool-b" + }, + { + "externalReferences": [ + { + "comment": "No comment", + "hashes": [ + { + "alg": "SHA-256", + "content": "806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b" + } + ], + "type": "distribution", + "url": "https://cyclonedx.org" + } + ], + "hashes": [ + { + "alg": "SHA-256", + "content": "49b420bd8d8182542a76d4422e0c7890dcc88a3d8ddad04da06366d8c40ac8ca" + } + ], + "name": "other-component", + "vendor": "acme" + }, + { + "name": "test-component" + }, + { + "externalReferences": [ + { + "comment": "No comment", + "hashes": [ + { + "alg": "SHA-256", + "content": "806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b" + } + ], + "type": "distribution", + "url": "https://cyclonedx.org" + } + ], + "name": "other-service", + "vendor": "acme" + }, + { + "name": "test-service" + } + ] + }, + "properties": [ + { + "name": "key1", + "value": "val1" + }, + { + "name": "key2", + "value": "val2" + } + ], + "serialNumber": "urn:uuid:1441d33a-e0fc-45b5-af3b-61ee52a88bac", + "version": 1, + "$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json", + "bomFormat": "CycloneDX", + "specVersion": "1.5" +} \ No newline at end of file diff --git a/tests/_data/snapshots/get_bom_with_tools_with_component_and_service_and_tools_irreversible_migrate-1.5.xml.bin b/tests/_data/snapshots/get_bom_with_tools_with_component_and_service_and_tools_irreversible_migrate-1.5.xml.bin new file mode 100644 index 00000000..d16609a5 --- /dev/null +++ b/tests/_data/snapshots/get_bom_with_tools_with_component_and_service_and_tools_irreversible_migrate-1.5.xml.bin @@ -0,0 +1,98 @@ + + + + 2023-01-07T13:44:32.312678+00:00 + + + CycloneDX + cyclonedx-python-lib + TESTING + + + https://github.com/CycloneDX/cyclonedx-python-lib/actions + + + https://pypi.org/project/cyclonedx-python-lib/ + + + https://cyclonedx-python-library.readthedocs.io/ + + + https://github.com/CycloneDX/cyclonedx-python-lib/issues + + + https://github.com/CycloneDX/cyclonedx-python-lib/blob/main/LICENSE + + + https://github.com/CycloneDX/cyclonedx-python-lib/blob/main/CHANGELOG.md + + + https://github.com/CycloneDX/cyclonedx-python-lib + + + https://github.com/CycloneDX/cyclonedx-python-lib/#readme + + + + + example + test-tool-a + 1.33.7 + + adbbbe72c8f023b4a2d96a3978f69d94873ab2fef424e0298287c3368519c1a6 + + + + https://cyclonedx.org + No comment + + 806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b + + + + + + test-tool-b + + + acme + other-component + + 49b420bd8d8182542a76d4422e0c7890dcc88a3d8ddad04da06366d8c40ac8ca + + + + https://cyclonedx.org + No comment + + 806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b + + + + + + test-component + + + acme + other-service + + + https://cyclonedx.org + No comment + + 806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b + + + + + + test-service + + + + + val1 + val2 + + diff --git a/tests/_data/snapshots/get_bom_with_tools_with_component_and_service_and_tools_irreversible_migrate-1.6.json.bin b/tests/_data/snapshots/get_bom_with_tools_with_component_and_service_and_tools_irreversible_migrate-1.6.json.bin new file mode 100644 index 00000000..1d4d653e --- /dev/null +++ b/tests/_data/snapshots/get_bom_with_tools_with_component_and_service_and_tools_irreversible_migrate-1.6.json.bin @@ -0,0 +1,134 @@ +{ + "metadata": { + "timestamp": "2023-01-07T13:44:32.312678+00:00", + "tools": [ + { + "externalReferences": [ + { + "type": "build-system", + "url": "https://github.com/CycloneDX/cyclonedx-python-lib/actions" + }, + { + "type": "distribution", + "url": "https://pypi.org/project/cyclonedx-python-lib/" + }, + { + "type": "documentation", + "url": "https://cyclonedx-python-library.readthedocs.io/" + }, + { + "type": "issue-tracker", + "url": "https://github.com/CycloneDX/cyclonedx-python-lib/issues" + }, + { + "type": "license", + "url": "https://github.com/CycloneDX/cyclonedx-python-lib/blob/main/LICENSE" + }, + { + "type": "release-notes", + "url": "https://github.com/CycloneDX/cyclonedx-python-lib/blob/main/CHANGELOG.md" + }, + { + "type": "vcs", + "url": "https://github.com/CycloneDX/cyclonedx-python-lib" + }, + { + "type": "website", + "url": "https://github.com/CycloneDX/cyclonedx-python-lib/#readme" + } + ], + "name": "cyclonedx-python-lib", + "vendor": "CycloneDX", + "version": "TESTING" + }, + { + "externalReferences": [ + { + "comment": "No comment", + "hashes": [ + { + "alg": "SHA-256", + "content": "806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b" + } + ], + "type": "distribution", + "url": "https://cyclonedx.org" + } + ], + "hashes": [ + { + "alg": "SHA-256", + "content": "adbbbe72c8f023b4a2d96a3978f69d94873ab2fef424e0298287c3368519c1a6" + } + ], + "name": "test-tool-a", + "vendor": "example", + "version": "1.33.7" + }, + { + "name": "test-tool-b" + }, + { + "externalReferences": [ + { + "comment": "No comment", + "hashes": [ + { + "alg": "SHA-256", + "content": "806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b" + } + ], + "type": "distribution", + "url": "https://cyclonedx.org" + } + ], + "hashes": [ + { + "alg": "SHA-256", + "content": "49b420bd8d8182542a76d4422e0c7890dcc88a3d8ddad04da06366d8c40ac8ca" + } + ], + "name": "other-component", + "vendor": "acme" + }, + { + "name": "test-component" + }, + { + "externalReferences": [ + { + "comment": "No comment", + "hashes": [ + { + "alg": "SHA-256", + "content": "806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b" + } + ], + "type": "distribution", + "url": "https://cyclonedx.org" + } + ], + "name": "other-service", + "vendor": "acme" + }, + { + "name": "test-service" + } + ] + }, + "properties": [ + { + "name": "key1", + "value": "val1" + }, + { + "name": "key2", + "value": "val2" + } + ], + "serialNumber": "urn:uuid:1441d33a-e0fc-45b5-af3b-61ee52a88bac", + "version": 1, + "$schema": "http://cyclonedx.org/schema/bom-1.6.schema.json", + "bomFormat": "CycloneDX", + "specVersion": "1.6" +} \ No newline at end of file diff --git a/tests/_data/snapshots/get_bom_with_tools_with_component_and_service_and_tools_irreversible_migrate-1.6.xml.bin b/tests/_data/snapshots/get_bom_with_tools_with_component_and_service_and_tools_irreversible_migrate-1.6.xml.bin new file mode 100644 index 00000000..f9b4eb19 --- /dev/null +++ b/tests/_data/snapshots/get_bom_with_tools_with_component_and_service_and_tools_irreversible_migrate-1.6.xml.bin @@ -0,0 +1,98 @@ + + + + 2023-01-07T13:44:32.312678+00:00 + + + CycloneDX + cyclonedx-python-lib + TESTING + + + https://github.com/CycloneDX/cyclonedx-python-lib/actions + + + https://pypi.org/project/cyclonedx-python-lib/ + + + https://cyclonedx-python-library.readthedocs.io/ + + + https://github.com/CycloneDX/cyclonedx-python-lib/issues + + + https://github.com/CycloneDX/cyclonedx-python-lib/blob/main/LICENSE + + + https://github.com/CycloneDX/cyclonedx-python-lib/blob/main/CHANGELOG.md + + + https://github.com/CycloneDX/cyclonedx-python-lib + + + https://github.com/CycloneDX/cyclonedx-python-lib/#readme + + + + + example + test-tool-a + 1.33.7 + + adbbbe72c8f023b4a2d96a3978f69d94873ab2fef424e0298287c3368519c1a6 + + + + https://cyclonedx.org + No comment + + 806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b + + + + + + test-tool-b + + + acme + other-component + + 49b420bd8d8182542a76d4422e0c7890dcc88a3d8ddad04da06366d8c40ac8ca + + + + https://cyclonedx.org + No comment + + 806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b + + + + + + test-component + + + acme + other-service + + + https://cyclonedx.org + No comment + + 806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b + + + + + + test-service + + + + + val1 + val2 + + diff --git a/tests/_data/snapshots/get_bom_with_tools_with_component_and_service_migrate-1.0.xml.bin b/tests/_data/snapshots/get_bom_with_tools_with_component_and_service_migrate-1.0.xml.bin new file mode 100644 index 00000000..acb06612 --- /dev/null +++ b/tests/_data/snapshots/get_bom_with_tools_with_component_and_service_migrate-1.0.xml.bin @@ -0,0 +1,4 @@ + + + + diff --git a/tests/_data/snapshots/get_bom_with_tools_with_component_and_service_migrate-1.1.xml.bin b/tests/_data/snapshots/get_bom_with_tools_with_component_and_service_migrate-1.1.xml.bin new file mode 100644 index 00000000..55ef5cda --- /dev/null +++ b/tests/_data/snapshots/get_bom_with_tools_with_component_and_service_migrate-1.1.xml.bin @@ -0,0 +1,4 @@ + + + + diff --git a/tests/_data/snapshots/get_bom_with_tools_with_component_and_service_migrate-1.2.json.bin b/tests/_data/snapshots/get_bom_with_tools_with_component_and_service_migrate-1.2.json.bin new file mode 100644 index 00000000..ce417066 --- /dev/null +++ b/tests/_data/snapshots/get_bom_with_tools_with_component_and_service_migrate-1.2.json.bin @@ -0,0 +1,32 @@ +{ + "metadata": { + "timestamp": "2023-01-07T13:44:32.312678+00:00", + "tools": [ + { + "hashes": [ + { + "alg": "SHA-256", + "content": "49b420bd8d8182542a76d4422e0c7890dcc88a3d8ddad04da06366d8c40ac8ca" + } + ], + "name": "other-component", + "vendor": "acme" + }, + { + "name": "test-component" + }, + { + "name": "other-service", + "vendor": "acme" + }, + { + "name": "test-service" + } + ] + }, + "serialNumber": "urn:uuid:1441d33a-e0fc-45b5-af3b-61ee52a88bac", + "version": 1, + "$schema": "http://cyclonedx.org/schema/bom-1.2b.schema.json", + "bomFormat": "CycloneDX", + "specVersion": "1.2" +} \ No newline at end of file diff --git a/tests/_data/snapshots/get_bom_with_tools_with_component_and_service_migrate-1.2.xml.bin b/tests/_data/snapshots/get_bom_with_tools_with_component_and_service_migrate-1.2.xml.bin new file mode 100644 index 00000000..2fa064f6 --- /dev/null +++ b/tests/_data/snapshots/get_bom_with_tools_with_component_and_service_migrate-1.2.xml.bin @@ -0,0 +1,25 @@ + + + + 2023-01-07T13:44:32.312678+00:00 + + + acme + other-component + + 49b420bd8d8182542a76d4422e0c7890dcc88a3d8ddad04da06366d8c40ac8ca + + + + test-component + + + acme + other-service + + + test-service + + + + diff --git a/tests/_data/snapshots/get_bom_with_tools_with_component_and_service_migrate-1.3.json.bin b/tests/_data/snapshots/get_bom_with_tools_with_component_and_service_migrate-1.3.json.bin new file mode 100644 index 00000000..6990fc8a --- /dev/null +++ b/tests/_data/snapshots/get_bom_with_tools_with_component_and_service_migrate-1.3.json.bin @@ -0,0 +1,32 @@ +{ + "metadata": { + "timestamp": "2023-01-07T13:44:32.312678+00:00", + "tools": [ + { + "hashes": [ + { + "alg": "SHA-256", + "content": "49b420bd8d8182542a76d4422e0c7890dcc88a3d8ddad04da06366d8c40ac8ca" + } + ], + "name": "other-component", + "vendor": "acme" + }, + { + "name": "test-component" + }, + { + "name": "other-service", + "vendor": "acme" + }, + { + "name": "test-service" + } + ] + }, + "serialNumber": "urn:uuid:1441d33a-e0fc-45b5-af3b-61ee52a88bac", + "version": 1, + "$schema": "http://cyclonedx.org/schema/bom-1.3a.schema.json", + "bomFormat": "CycloneDX", + "specVersion": "1.3" +} \ No newline at end of file diff --git a/tests/_data/snapshots/get_bom_with_tools_with_component_and_service_migrate-1.3.xml.bin b/tests/_data/snapshots/get_bom_with_tools_with_component_and_service_migrate-1.3.xml.bin new file mode 100644 index 00000000..4e71b908 --- /dev/null +++ b/tests/_data/snapshots/get_bom_with_tools_with_component_and_service_migrate-1.3.xml.bin @@ -0,0 +1,25 @@ + + + + 2023-01-07T13:44:32.312678+00:00 + + + acme + other-component + + 49b420bd8d8182542a76d4422e0c7890dcc88a3d8ddad04da06366d8c40ac8ca + + + + test-component + + + acme + other-service + + + test-service + + + + diff --git a/tests/_data/snapshots/get_bom_with_tools_with_component_and_service_migrate-1.4.json.bin b/tests/_data/snapshots/get_bom_with_tools_with_component_and_service_migrate-1.4.json.bin new file mode 100644 index 00000000..3851eb48 --- /dev/null +++ b/tests/_data/snapshots/get_bom_with_tools_with_component_and_service_migrate-1.4.json.bin @@ -0,0 +1,58 @@ +{ + "metadata": { + "timestamp": "2023-01-07T13:44:32.312678+00:00", + "tools": [ + { + "externalReferences": [ + { + "comment": "No comment", + "hashes": [ + { + "alg": "SHA-256", + "content": "806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b" + } + ], + "type": "distribution", + "url": "https://cyclonedx.org" + } + ], + "hashes": [ + { + "alg": "SHA-256", + "content": "49b420bd8d8182542a76d4422e0c7890dcc88a3d8ddad04da06366d8c40ac8ca" + } + ], + "name": "other-component", + "vendor": "acme" + }, + { + "name": "test-component" + }, + { + "externalReferences": [ + { + "comment": "No comment", + "hashes": [ + { + "alg": "SHA-256", + "content": "806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b" + } + ], + "type": "distribution", + "url": "https://cyclonedx.org" + } + ], + "name": "other-service", + "vendor": "acme" + }, + { + "name": "test-service" + } + ] + }, + "serialNumber": "urn:uuid:1441d33a-e0fc-45b5-af3b-61ee52a88bac", + "version": 1, + "$schema": "http://cyclonedx.org/schema/bom-1.4.schema.json", + "bomFormat": "CycloneDX", + "specVersion": "1.4" +} \ No newline at end of file diff --git a/tests/_data/snapshots/get_bom_with_tools_with_component_and_service_migrate-1.4.xml.bin b/tests/_data/snapshots/get_bom_with_tools_with_component_and_service_migrate-1.4.xml.bin new file mode 100644 index 00000000..426cdf54 --- /dev/null +++ b/tests/_data/snapshots/get_bom_with_tools_with_component_and_service_migrate-1.4.xml.bin @@ -0,0 +1,43 @@ + + + + 2023-01-07T13:44:32.312678+00:00 + + + acme + other-component + + 49b420bd8d8182542a76d4422e0c7890dcc88a3d8ddad04da06366d8c40ac8ca + + + + https://cyclonedx.org + No comment + + 806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b + + + + + + test-component + + + acme + other-service + + + https://cyclonedx.org + No comment + + 806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b + + + + + + test-service + + + + diff --git a/tests/_data/snapshots/get_bom_with_tools_with_component_and_service_migrate-1.5.json.bin b/tests/_data/snapshots/get_bom_with_tools_with_component_and_service_migrate-1.5.json.bin new file mode 100644 index 00000000..9b205056 --- /dev/null +++ b/tests/_data/snapshots/get_bom_with_tools_with_component_and_service_migrate-1.5.json.bin @@ -0,0 +1,78 @@ +{ + "metadata": { + "timestamp": "2023-01-07T13:44:32.312678+00:00", + "tools": { + "components": [ + { + "bom-ref": "other-component", + "externalReferences": [ + { + "comment": "No comment", + "hashes": [ + { + "alg": "SHA-256", + "content": "806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b" + } + ], + "type": "distribution", + "url": "https://cyclonedx.org" + } + ], + "group": "acme", + "hashes": [ + { + "alg": "SHA-256", + "content": "49b420bd8d8182542a76d4422e0c7890dcc88a3d8ddad04da06366d8c40ac8ca" + } + ], + "name": "other-component", + "type": "application" + }, + { + "bom-ref": "test-component", + "name": "test-component", + "type": "library" + } + ], + "services": [ + { + "bom-ref": "other-service", + "externalReferences": [ + { + "comment": "No comment", + "hashes": [ + { + "alg": "SHA-256", + "content": "806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b" + } + ], + "type": "distribution", + "url": "https://cyclonedx.org" + } + ], + "group": "acme", + "name": "other-service" + }, + { + "bom-ref": "test-service", + "name": "test-service" + } + ] + } + }, + "properties": [ + { + "name": "key1", + "value": "val1" + }, + { + "name": "key2", + "value": "val2" + } + ], + "serialNumber": "urn:uuid:1441d33a-e0fc-45b5-af3b-61ee52a88bac", + "version": 1, + "$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json", + "bomFormat": "CycloneDX", + "specVersion": "1.5" +} \ No newline at end of file diff --git a/tests/_data/snapshots/get_bom_with_tools_with_component_and_service_migrate-1.5.xml.bin b/tests/_data/snapshots/get_bom_with_tools_with_component_and_service_migrate-1.5.xml.bin new file mode 100644 index 00000000..66d11d3b --- /dev/null +++ b/tests/_data/snapshots/get_bom_with_tools_with_component_and_service_migrate-1.5.xml.bin @@ -0,0 +1,51 @@ + + + + 2023-01-07T13:44:32.312678+00:00 + + + + acme + other-component + + 49b420bd8d8182542a76d4422e0c7890dcc88a3d8ddad04da06366d8c40ac8ca + + + + https://cyclonedx.org + No comment + + 806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b + + + + + + test-component + + + + + acme + other-service + + + https://cyclonedx.org + No comment + + 806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b + + + + + + test-service + + + + + + val1 + val2 + + diff --git a/tests/_data/snapshots/get_bom_with_tools_with_component_and_service_migrate-1.6.json.bin b/tests/_data/snapshots/get_bom_with_tools_with_component_and_service_migrate-1.6.json.bin new file mode 100644 index 00000000..7ddfb8fa --- /dev/null +++ b/tests/_data/snapshots/get_bom_with_tools_with_component_and_service_migrate-1.6.json.bin @@ -0,0 +1,78 @@ +{ + "metadata": { + "timestamp": "2023-01-07T13:44:32.312678+00:00", + "tools": { + "components": [ + { + "bom-ref": "other-component", + "externalReferences": [ + { + "comment": "No comment", + "hashes": [ + { + "alg": "SHA-256", + "content": "806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b" + } + ], + "type": "distribution", + "url": "https://cyclonedx.org" + } + ], + "group": "acme", + "hashes": [ + { + "alg": "SHA-256", + "content": "49b420bd8d8182542a76d4422e0c7890dcc88a3d8ddad04da06366d8c40ac8ca" + } + ], + "name": "other-component", + "type": "application" + }, + { + "bom-ref": "test-component", + "name": "test-component", + "type": "library" + } + ], + "services": [ + { + "bom-ref": "other-service", + "externalReferences": [ + { + "comment": "No comment", + "hashes": [ + { + "alg": "SHA-256", + "content": "806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b" + } + ], + "type": "distribution", + "url": "https://cyclonedx.org" + } + ], + "group": "acme", + "name": "other-service" + }, + { + "bom-ref": "test-service", + "name": "test-service" + } + ] + } + }, + "properties": [ + { + "name": "key1", + "value": "val1" + }, + { + "name": "key2", + "value": "val2" + } + ], + "serialNumber": "urn:uuid:1441d33a-e0fc-45b5-af3b-61ee52a88bac", + "version": 1, + "$schema": "http://cyclonedx.org/schema/bom-1.6.schema.json", + "bomFormat": "CycloneDX", + "specVersion": "1.6" +} \ No newline at end of file diff --git a/tests/_data/snapshots/get_bom_with_tools_with_component_and_service_migrate-1.6.xml.bin b/tests/_data/snapshots/get_bom_with_tools_with_component_and_service_migrate-1.6.xml.bin new file mode 100644 index 00000000..89519324 --- /dev/null +++ b/tests/_data/snapshots/get_bom_with_tools_with_component_and_service_migrate-1.6.xml.bin @@ -0,0 +1,51 @@ + + + + 2023-01-07T13:44:32.312678+00:00 + + + + acme + other-component + + 49b420bd8d8182542a76d4422e0c7890dcc88a3d8ddad04da06366d8c40ac8ca + + + + https://cyclonedx.org + No comment + + 806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b + + + + + + test-component + + + + + acme + other-service + + + https://cyclonedx.org + No comment + + 806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b + + + + + + test-service + + + + + + val1 + val2 + + diff --git a/tests/_data/snapshots/get_bom_with_tools_with_component_migrate-1.0.xml.bin b/tests/_data/snapshots/get_bom_with_tools_with_component_migrate-1.0.xml.bin new file mode 100644 index 00000000..acb06612 --- /dev/null +++ b/tests/_data/snapshots/get_bom_with_tools_with_component_migrate-1.0.xml.bin @@ -0,0 +1,4 @@ + + + + diff --git a/tests/_data/snapshots/get_bom_with_tools_with_component_migrate-1.1.xml.bin b/tests/_data/snapshots/get_bom_with_tools_with_component_migrate-1.1.xml.bin new file mode 100644 index 00000000..55ef5cda --- /dev/null +++ b/tests/_data/snapshots/get_bom_with_tools_with_component_migrate-1.1.xml.bin @@ -0,0 +1,4 @@ + + + + diff --git a/tests/_data/snapshots/get_bom_with_tools_with_component_migrate-1.2.json.bin b/tests/_data/snapshots/get_bom_with_tools_with_component_migrate-1.2.json.bin new file mode 100644 index 00000000..254aa82a --- /dev/null +++ b/tests/_data/snapshots/get_bom_with_tools_with_component_migrate-1.2.json.bin @@ -0,0 +1,25 @@ +{ + "metadata": { + "timestamp": "2023-01-07T13:44:32.312678+00:00", + "tools": [ + { + "hashes": [ + { + "alg": "SHA-256", + "content": "49b420bd8d8182542a76d4422e0c7890dcc88a3d8ddad04da06366d8c40ac8ca" + } + ], + "name": "other-component", + "vendor": "acme" + }, + { + "name": "test-component" + } + ] + }, + "serialNumber": "urn:uuid:1441d33a-e0fc-45b5-af3b-61ee52a88bac", + "version": 1, + "$schema": "http://cyclonedx.org/schema/bom-1.2b.schema.json", + "bomFormat": "CycloneDX", + "specVersion": "1.2" +} \ No newline at end of file diff --git a/tests/_data/snapshots/get_bom_with_tools_with_component_migrate-1.2.xml.bin b/tests/_data/snapshots/get_bom_with_tools_with_component_migrate-1.2.xml.bin new file mode 100644 index 00000000..e8e74ab2 --- /dev/null +++ b/tests/_data/snapshots/get_bom_with_tools_with_component_migrate-1.2.xml.bin @@ -0,0 +1,18 @@ + + + + 2023-01-07T13:44:32.312678+00:00 + + + acme + other-component + + 49b420bd8d8182542a76d4422e0c7890dcc88a3d8ddad04da06366d8c40ac8ca + + + + test-component + + + + diff --git a/tests/_data/snapshots/get_bom_with_tools_with_component_migrate-1.3.json.bin b/tests/_data/snapshots/get_bom_with_tools_with_component_migrate-1.3.json.bin new file mode 100644 index 00000000..7477a298 --- /dev/null +++ b/tests/_data/snapshots/get_bom_with_tools_with_component_migrate-1.3.json.bin @@ -0,0 +1,25 @@ +{ + "metadata": { + "timestamp": "2023-01-07T13:44:32.312678+00:00", + "tools": [ + { + "hashes": [ + { + "alg": "SHA-256", + "content": "49b420bd8d8182542a76d4422e0c7890dcc88a3d8ddad04da06366d8c40ac8ca" + } + ], + "name": "other-component", + "vendor": "acme" + }, + { + "name": "test-component" + } + ] + }, + "serialNumber": "urn:uuid:1441d33a-e0fc-45b5-af3b-61ee52a88bac", + "version": 1, + "$schema": "http://cyclonedx.org/schema/bom-1.3a.schema.json", + "bomFormat": "CycloneDX", + "specVersion": "1.3" +} \ No newline at end of file diff --git a/tests/_data/snapshots/get_bom_with_tools_with_component_migrate-1.3.xml.bin b/tests/_data/snapshots/get_bom_with_tools_with_component_migrate-1.3.xml.bin new file mode 100644 index 00000000..fadc64c7 --- /dev/null +++ b/tests/_data/snapshots/get_bom_with_tools_with_component_migrate-1.3.xml.bin @@ -0,0 +1,18 @@ + + + + 2023-01-07T13:44:32.312678+00:00 + + + acme + other-component + + 49b420bd8d8182542a76d4422e0c7890dcc88a3d8ddad04da06366d8c40ac8ca + + + + test-component + + + + diff --git a/tests/_data/snapshots/get_bom_with_tools_with_component_migrate-1.4.json.bin b/tests/_data/snapshots/get_bom_with_tools_with_component_migrate-1.4.json.bin new file mode 100644 index 00000000..84229368 --- /dev/null +++ b/tests/_data/snapshots/get_bom_with_tools_with_component_migrate-1.4.json.bin @@ -0,0 +1,38 @@ +{ + "metadata": { + "timestamp": "2023-01-07T13:44:32.312678+00:00", + "tools": [ + { + "externalReferences": [ + { + "comment": "No comment", + "hashes": [ + { + "alg": "SHA-256", + "content": "806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b" + } + ], + "type": "distribution", + "url": "https://cyclonedx.org" + } + ], + "hashes": [ + { + "alg": "SHA-256", + "content": "49b420bd8d8182542a76d4422e0c7890dcc88a3d8ddad04da06366d8c40ac8ca" + } + ], + "name": "other-component", + "vendor": "acme" + }, + { + "name": "test-component" + } + ] + }, + "serialNumber": "urn:uuid:1441d33a-e0fc-45b5-af3b-61ee52a88bac", + "version": 1, + "$schema": "http://cyclonedx.org/schema/bom-1.4.schema.json", + "bomFormat": "CycloneDX", + "specVersion": "1.4" +} \ No newline at end of file diff --git a/tests/_data/snapshots/get_bom_with_tools_with_component_migrate-1.4.xml.bin b/tests/_data/snapshots/get_bom_with_tools_with_component_migrate-1.4.xml.bin new file mode 100644 index 00000000..15c7faa4 --- /dev/null +++ b/tests/_data/snapshots/get_bom_with_tools_with_component_migrate-1.4.xml.bin @@ -0,0 +1,27 @@ + + + + 2023-01-07T13:44:32.312678+00:00 + + + acme + other-component + + 49b420bd8d8182542a76d4422e0c7890dcc88a3d8ddad04da06366d8c40ac8ca + + + + https://cyclonedx.org + No comment + + 806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b + + + + + + test-component + + + + diff --git a/tests/_data/snapshots/get_bom_with_tools_with_component_migrate-1.5.json.bin b/tests/_data/snapshots/get_bom_with_tools_with_component_migrate-1.5.json.bin new file mode 100644 index 00000000..3eb88d3e --- /dev/null +++ b/tests/_data/snapshots/get_bom_with_tools_with_component_migrate-1.5.json.bin @@ -0,0 +1,54 @@ +{ + "metadata": { + "timestamp": "2023-01-07T13:44:32.312678+00:00", + "tools": { + "components": [ + { + "bom-ref": "other-component", + "externalReferences": [ + { + "comment": "No comment", + "hashes": [ + { + "alg": "SHA-256", + "content": "806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b" + } + ], + "type": "distribution", + "url": "https://cyclonedx.org" + } + ], + "group": "acme", + "hashes": [ + { + "alg": "SHA-256", + "content": "49b420bd8d8182542a76d4422e0c7890dcc88a3d8ddad04da06366d8c40ac8ca" + } + ], + "name": "other-component", + "type": "application" + }, + { + "bom-ref": "test-component", + "name": "test-component", + "type": "library" + } + ] + } + }, + "properties": [ + { + "name": "key1", + "value": "val1" + }, + { + "name": "key2", + "value": "val2" + } + ], + "serialNumber": "urn:uuid:1441d33a-e0fc-45b5-af3b-61ee52a88bac", + "version": 1, + "$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json", + "bomFormat": "CycloneDX", + "specVersion": "1.5" +} \ No newline at end of file diff --git a/tests/_data/snapshots/get_bom_with_tools_with_component_migrate-1.5.xml.bin b/tests/_data/snapshots/get_bom_with_tools_with_component_migrate-1.5.xml.bin new file mode 100644 index 00000000..5a6ba111 --- /dev/null +++ b/tests/_data/snapshots/get_bom_with_tools_with_component_migrate-1.5.xml.bin @@ -0,0 +1,33 @@ + + + + 2023-01-07T13:44:32.312678+00:00 + + + + acme + other-component + + 49b420bd8d8182542a76d4422e0c7890dcc88a3d8ddad04da06366d8c40ac8ca + + + + https://cyclonedx.org + No comment + + 806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b + + + + + + test-component + + + + + + val1 + val2 + + diff --git a/tests/_data/snapshots/get_bom_with_tools_with_component_migrate-1.6.json.bin b/tests/_data/snapshots/get_bom_with_tools_with_component_migrate-1.6.json.bin new file mode 100644 index 00000000..1348f4f3 --- /dev/null +++ b/tests/_data/snapshots/get_bom_with_tools_with_component_migrate-1.6.json.bin @@ -0,0 +1,54 @@ +{ + "metadata": { + "timestamp": "2023-01-07T13:44:32.312678+00:00", + "tools": { + "components": [ + { + "bom-ref": "other-component", + "externalReferences": [ + { + "comment": "No comment", + "hashes": [ + { + "alg": "SHA-256", + "content": "806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b" + } + ], + "type": "distribution", + "url": "https://cyclonedx.org" + } + ], + "group": "acme", + "hashes": [ + { + "alg": "SHA-256", + "content": "49b420bd8d8182542a76d4422e0c7890dcc88a3d8ddad04da06366d8c40ac8ca" + } + ], + "name": "other-component", + "type": "application" + }, + { + "bom-ref": "test-component", + "name": "test-component", + "type": "library" + } + ] + } + }, + "properties": [ + { + "name": "key1", + "value": "val1" + }, + { + "name": "key2", + "value": "val2" + } + ], + "serialNumber": "urn:uuid:1441d33a-e0fc-45b5-af3b-61ee52a88bac", + "version": 1, + "$schema": "http://cyclonedx.org/schema/bom-1.6.schema.json", + "bomFormat": "CycloneDX", + "specVersion": "1.6" +} \ No newline at end of file diff --git a/tests/_data/snapshots/get_bom_with_tools_with_component_migrate-1.6.xml.bin b/tests/_data/snapshots/get_bom_with_tools_with_component_migrate-1.6.xml.bin new file mode 100644 index 00000000..0963cb16 --- /dev/null +++ b/tests/_data/snapshots/get_bom_with_tools_with_component_migrate-1.6.xml.bin @@ -0,0 +1,33 @@ + + + + 2023-01-07T13:44:32.312678+00:00 + + + + acme + other-component + + 49b420bd8d8182542a76d4422e0c7890dcc88a3d8ddad04da06366d8c40ac8ca + + + + https://cyclonedx.org + No comment + + 806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b + + + + + + test-component + + + + + + val1 + val2 + + diff --git a/tests/_data/snapshots/get_bom_with_tools_with_service_migrate-1.0.xml.bin b/tests/_data/snapshots/get_bom_with_tools_with_service_migrate-1.0.xml.bin new file mode 100644 index 00000000..acb06612 --- /dev/null +++ b/tests/_data/snapshots/get_bom_with_tools_with_service_migrate-1.0.xml.bin @@ -0,0 +1,4 @@ + + + + diff --git a/tests/_data/snapshots/get_bom_with_tools_with_service_migrate-1.1.xml.bin b/tests/_data/snapshots/get_bom_with_tools_with_service_migrate-1.1.xml.bin new file mode 100644 index 00000000..55ef5cda --- /dev/null +++ b/tests/_data/snapshots/get_bom_with_tools_with_service_migrate-1.1.xml.bin @@ -0,0 +1,4 @@ + + + + diff --git a/tests/_data/snapshots/get_bom_with_tools_with_service_migrate-1.2.json.bin b/tests/_data/snapshots/get_bom_with_tools_with_service_migrate-1.2.json.bin new file mode 100644 index 00000000..02094998 --- /dev/null +++ b/tests/_data/snapshots/get_bom_with_tools_with_service_migrate-1.2.json.bin @@ -0,0 +1,19 @@ +{ + "metadata": { + "timestamp": "2023-01-07T13:44:32.312678+00:00", + "tools": [ + { + "name": "other-service", + "vendor": "acme" + }, + { + "name": "test-service" + } + ] + }, + "serialNumber": "urn:uuid:1441d33a-e0fc-45b5-af3b-61ee52a88bac", + "version": 1, + "$schema": "http://cyclonedx.org/schema/bom-1.2b.schema.json", + "bomFormat": "CycloneDX", + "specVersion": "1.2" +} \ No newline at end of file diff --git a/tests/_data/snapshots/get_bom_with_tools_with_service_migrate-1.2.xml.bin b/tests/_data/snapshots/get_bom_with_tools_with_service_migrate-1.2.xml.bin new file mode 100644 index 00000000..266aa5b6 --- /dev/null +++ b/tests/_data/snapshots/get_bom_with_tools_with_service_migrate-1.2.xml.bin @@ -0,0 +1,15 @@ + + + + 2023-01-07T13:44:32.312678+00:00 + + + acme + other-service + + + test-service + + + + diff --git a/tests/_data/snapshots/get_bom_with_tools_with_service_migrate-1.3.json.bin b/tests/_data/snapshots/get_bom_with_tools_with_service_migrate-1.3.json.bin new file mode 100644 index 00000000..62a2e4c3 --- /dev/null +++ b/tests/_data/snapshots/get_bom_with_tools_with_service_migrate-1.3.json.bin @@ -0,0 +1,19 @@ +{ + "metadata": { + "timestamp": "2023-01-07T13:44:32.312678+00:00", + "tools": [ + { + "name": "other-service", + "vendor": "acme" + }, + { + "name": "test-service" + } + ] + }, + "serialNumber": "urn:uuid:1441d33a-e0fc-45b5-af3b-61ee52a88bac", + "version": 1, + "$schema": "http://cyclonedx.org/schema/bom-1.3a.schema.json", + "bomFormat": "CycloneDX", + "specVersion": "1.3" +} \ No newline at end of file diff --git a/tests/_data/snapshots/get_bom_with_tools_with_service_migrate-1.3.xml.bin b/tests/_data/snapshots/get_bom_with_tools_with_service_migrate-1.3.xml.bin new file mode 100644 index 00000000..97729c4e --- /dev/null +++ b/tests/_data/snapshots/get_bom_with_tools_with_service_migrate-1.3.xml.bin @@ -0,0 +1,15 @@ + + + + 2023-01-07T13:44:32.312678+00:00 + + + acme + other-service + + + test-service + + + + diff --git a/tests/_data/snapshots/get_bom_with_tools_with_service_migrate-1.4.json.bin b/tests/_data/snapshots/get_bom_with_tools_with_service_migrate-1.4.json.bin new file mode 100644 index 00000000..bcf21a66 --- /dev/null +++ b/tests/_data/snapshots/get_bom_with_tools_with_service_migrate-1.4.json.bin @@ -0,0 +1,32 @@ +{ + "metadata": { + "timestamp": "2023-01-07T13:44:32.312678+00:00", + "tools": [ + { + "externalReferences": [ + { + "comment": "No comment", + "hashes": [ + { + "alg": "SHA-256", + "content": "806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b" + } + ], + "type": "distribution", + "url": "https://cyclonedx.org" + } + ], + "name": "other-service", + "vendor": "acme" + }, + { + "name": "test-service" + } + ] + }, + "serialNumber": "urn:uuid:1441d33a-e0fc-45b5-af3b-61ee52a88bac", + "version": 1, + "$schema": "http://cyclonedx.org/schema/bom-1.4.schema.json", + "bomFormat": "CycloneDX", + "specVersion": "1.4" +} \ No newline at end of file diff --git a/tests/_data/snapshots/get_bom_with_tools_with_service_migrate-1.4.xml.bin b/tests/_data/snapshots/get_bom_with_tools_with_service_migrate-1.4.xml.bin new file mode 100644 index 00000000..af666d2c --- /dev/null +++ b/tests/_data/snapshots/get_bom_with_tools_with_service_migrate-1.4.xml.bin @@ -0,0 +1,24 @@ + + + + 2023-01-07T13:44:32.312678+00:00 + + + acme + other-service + + + https://cyclonedx.org + No comment + + 806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b + + + + + + test-service + + + + diff --git a/tests/_data/snapshots/get_bom_with_tools_with_service_migrate-1.5.json.bin b/tests/_data/snapshots/get_bom_with_tools_with_service_migrate-1.5.json.bin new file mode 100644 index 00000000..c5e9c6e8 --- /dev/null +++ b/tests/_data/snapshots/get_bom_with_tools_with_service_migrate-1.5.json.bin @@ -0,0 +1,46 @@ +{ + "metadata": { + "timestamp": "2023-01-07T13:44:32.312678+00:00", + "tools": { + "services": [ + { + "bom-ref": "other-service", + "externalReferences": [ + { + "comment": "No comment", + "hashes": [ + { + "alg": "SHA-256", + "content": "806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b" + } + ], + "type": "distribution", + "url": "https://cyclonedx.org" + } + ], + "group": "acme", + "name": "other-service" + }, + { + "bom-ref": "test-service", + "name": "test-service" + } + ] + } + }, + "properties": [ + { + "name": "key1", + "value": "val1" + }, + { + "name": "key2", + "value": "val2" + } + ], + "serialNumber": "urn:uuid:1441d33a-e0fc-45b5-af3b-61ee52a88bac", + "version": 1, + "$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json", + "bomFormat": "CycloneDX", + "specVersion": "1.5" +} \ No newline at end of file diff --git a/tests/_data/snapshots/get_bom_with_tools_with_service_migrate-1.5.xml.bin b/tests/_data/snapshots/get_bom_with_tools_with_service_migrate-1.5.xml.bin new file mode 100644 index 00000000..3a8ba335 --- /dev/null +++ b/tests/_data/snapshots/get_bom_with_tools_with_service_migrate-1.5.xml.bin @@ -0,0 +1,30 @@ + + + + 2023-01-07T13:44:32.312678+00:00 + + + + acme + other-service + + + https://cyclonedx.org + No comment + + 806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b + + + + + + test-service + + + + + + val1 + val2 + + diff --git a/tests/_data/snapshots/get_bom_with_tools_with_service_migrate-1.6.json.bin b/tests/_data/snapshots/get_bom_with_tools_with_service_migrate-1.6.json.bin new file mode 100644 index 00000000..a9293a54 --- /dev/null +++ b/tests/_data/snapshots/get_bom_with_tools_with_service_migrate-1.6.json.bin @@ -0,0 +1,46 @@ +{ + "metadata": { + "timestamp": "2023-01-07T13:44:32.312678+00:00", + "tools": { + "services": [ + { + "bom-ref": "other-service", + "externalReferences": [ + { + "comment": "No comment", + "hashes": [ + { + "alg": "SHA-256", + "content": "806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b" + } + ], + "type": "distribution", + "url": "https://cyclonedx.org" + } + ], + "group": "acme", + "name": "other-service" + }, + { + "bom-ref": "test-service", + "name": "test-service" + } + ] + } + }, + "properties": [ + { + "name": "key1", + "value": "val1" + }, + { + "name": "key2", + "value": "val2" + } + ], + "serialNumber": "urn:uuid:1441d33a-e0fc-45b5-af3b-61ee52a88bac", + "version": 1, + "$schema": "http://cyclonedx.org/schema/bom-1.6.schema.json", + "bomFormat": "CycloneDX", + "specVersion": "1.6" +} \ No newline at end of file diff --git a/tests/_data/snapshots/get_bom_with_tools_with_service_migrate-1.6.xml.bin b/tests/_data/snapshots/get_bom_with_tools_with_service_migrate-1.6.xml.bin new file mode 100644 index 00000000..e33e4dc1 --- /dev/null +++ b/tests/_data/snapshots/get_bom_with_tools_with_service_migrate-1.6.xml.bin @@ -0,0 +1,30 @@ + + + + 2023-01-07T13:44:32.312678+00:00 + + + + acme + other-service + + + https://cyclonedx.org + No comment + + 806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b + + + + + + test-service + + + + + + val1 + val2 + + diff --git a/tests/test_deserialize_json.py b/tests/test_deserialize_json.py index 9c5ad40e..03306571 100644 --- a/tests/test_deserialize_json.py +++ b/tests/test_deserialize_json.py @@ -28,13 +28,18 @@ from cyclonedx.model.license import DisjunctiveLicense, LicenseExpression, LicenseRepository from cyclonedx.schema import OutputFormat, SchemaVersion from tests import OWN_DATA_DIRECTORY, DeepCompareMixin, SnapshotMixin, mksname -from tests._data.models import all_get_bom_funct_valid_immut, all_get_bom_funct_with_incomplete_deps +from tests._data.models import ( + all_get_bom_funct_valid_immut, + all_get_bom_funct_valid_reversible_migrate, + all_get_bom_funct_with_incomplete_deps, +) @ddt class TestDeserializeJson(TestCase, SnapshotMixin, DeepCompareMixin): - @named_data(*all_get_bom_funct_valid_immut) + @named_data(*all_get_bom_funct_valid_immut, + *all_get_bom_funct_valid_reversible_migrate) @patch('cyclonedx.model.ThisTool._version', 'TESTING') def test_prepared(self, get_bom: Callable[[], Bom], *_: Any, **__: Any) -> None: # only latest schema will have all data populated in serialized form diff --git a/tests/test_deserialize_xml.py b/tests/test_deserialize_xml.py index f2a3ad9c..3b02fe77 100644 --- a/tests/test_deserialize_xml.py +++ b/tests/test_deserialize_xml.py @@ -14,24 +14,29 @@ # # SPDX-License-Identifier: Apache-2.0 # Copyright (c) OWASP Foundation. All Rights Reserved. - - +from os.path import join from typing import Any, Callable from unittest import TestCase from unittest.mock import patch from ddt import ddt, named_data +from cyclonedx.exception.serialization import CycloneDxDeserializationException from cyclonedx.model.bom import Bom from cyclonedx.schema import OutputFormat, SchemaVersion -from tests import DeepCompareMixin, SnapshotMixin, mksname -from tests._data.models import all_get_bom_funct_valid_immut, all_get_bom_funct_with_incomplete_deps +from tests import OWN_DATA_DIRECTORY, DeepCompareMixin, SnapshotMixin, mksname +from tests._data.models import ( + all_get_bom_funct_valid_immut, + all_get_bom_funct_valid_reversible_migrate, + all_get_bom_funct_with_incomplete_deps, +) @ddt class TestDeserializeXml(TestCase, SnapshotMixin, DeepCompareMixin): - @named_data(*all_get_bom_funct_valid_immut) + @named_data(*all_get_bom_funct_valid_immut, + *all_get_bom_funct_valid_reversible_migrate) @patch('cyclonedx.model.ThisTool._version', 'TESTING') def test_prepared(self, get_bom: Callable[[], Bom], *_: Any, **__: Any) -> None: # only latest schema will have all data populated in serialized form @@ -41,3 +46,9 @@ def test_prepared(self, get_bom: Callable[[], Bom], *_: Any, **__: Any) -> None: bom = Bom.from_xml(s) self.assertBomDeepEqual(expected, bom, fuzzy_deps=get_bom in all_get_bom_funct_with_incomplete_deps) + + def test_unexpected_toolsrepository_item(self) -> None: + with open(join(OWN_DATA_DIRECTORY, 'xml', '1.5', 'invalid-tool.xml')) as input_xml: + with self.assertRaisesRegex(CycloneDxDeserializationException, + r"^unexpected: $"): + Bom.from_xml(input_xml) diff --git a/tests/test_model.py b/tests/test_model.py index 76959f33..5c6bb9ac 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -40,7 +40,6 @@ Note, NoteText, Property, - Tool, XsUri, ) from cyclonedx.model.contact import OrganizationalContact @@ -563,22 +562,3 @@ def test_sort(self) -> None: sorted_props = sorted(props) expected_props = reorder(props, expected_order) self.assertListEqual(sorted_props, expected_props) - - -class TestModelTool(TestCase): - - def test_sort(self) -> None: - # expected sort order: (vendor, name, version) - expected_order = [0, 1, 2, 3, 4, 5, 6] - tools = [ - Tool(vendor='a', name='a', version='1.0.0'), - Tool(vendor='a', name='a', version='2.0.0'), - Tool(vendor='a', name='b', version='1.0.0'), - Tool(vendor='a', name='b'), - Tool(vendor='b', name='a'), - Tool(vendor='b', name='b', version='1.0.0'), - Tool(name='b'), - ] - sorted_tools = sorted(tools) - expected_tools = reorder(tools, expected_order) - self.assertListEqual(sorted_tools, expected_tools) diff --git a/tests/test_model_bom.py b/tests/test_model_bom.py index 925846c7..7c993036 100644 --- a/tests/test_model_bom.py +++ b/tests/test_model_bom.py @@ -55,7 +55,7 @@ def test_empty_bom_metadata(self) -> None: self.assertIsNotNone(metadata.licenses) self.assertIsNotNone(metadata.properties) self.assertIsNotNone(metadata.tools) - self.assertTrue(ThisTool in metadata.tools) + self.assertTrue(ThisTool in metadata.tools.tools) def test_basic_bom_metadata(self) -> None: tools = [ @@ -94,9 +94,9 @@ def test_basic_bom_metadata(self) -> None: self.assertTrue(properties[0] in metadata.properties) self.assertTrue(properties[1] in metadata.properties) self.assertIsNotNone(metadata.tools) - self.assertTrue(ThisTool not in metadata.tools) - self.assertTrue(tools[0] in metadata.tools) - self.assertTrue(tools[1] in metadata.tools) + self.assertTrue(ThisTool not in metadata.tools.tools) + self.assertTrue(tools[0] in metadata.tools.tools) + self.assertTrue(tools[1] in metadata.tools.tools) @ddt @@ -110,7 +110,7 @@ def test_bom_metadata_tool_this_tool(self) -> None: def test_bom_metadata_tool_multiple_tools(self) -> None: bom = Bom() self.assertEqual(len(bom.metadata.tools), 1) - bom.metadata.tools.add( + bom.metadata.tools.tools.add( Tool(vendor='TestVendor', name='TestTool', version='0.0.0') ) self.assertEqual(bom.version, 1) diff --git a/tests/test_model_tool.py b/tests/test_model_tool.py new file mode 100644 index 00000000..b0b87c0b --- /dev/null +++ b/tests/test_model_tool.py @@ -0,0 +1,58 @@ +# This file is part of CycloneDX Python Lib +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# Copyright (c) OWASP Foundation. All Rights Reserved. + + +from unittest import TestCase + +from cyclonedx.model.tool import Tool +from tests import reorder + + +class TestModelTool(TestCase): + + def test_sort(self) -> None: + # expected sort order: (vendor, name, version) + expected_order = [0, 1, 2, 3, 4, 5, 6] + tools = [ + Tool(vendor='a', name='a', version='1.0.0'), + Tool(vendor='a', name='a', version='2.0.0'), + Tool(vendor='a', name='b', version='1.0.0'), + Tool(vendor='a', name='b'), + Tool(vendor='b', name='a'), + Tool(vendor='b', name='b', version='1.0.0'), + Tool(name='b'), + ] + sorted_tools = sorted(tools) + expected_tools = reorder(tools, expected_order) + self.assertListEqual(sorted_tools, expected_tools) + + def test_non_equal_tool_and_invalid(self) -> None: + t = Tool(vendor='VendorA') + self.assertFalse(t == 'INVALID') + + def test_invalid_tool_compare(self) -> None: + t = Tool(vendor='VendorA') + with self.assertRaises(TypeError): + r = t < 'INVALID' # pylint: disable=unused-variable # noqa: disable=E841 + + def test_tool_repr(self) -> None: + t = Tool(name='test-tool', version='1.2.3', vendor='test-vendor') + self.assertEqual(repr(t), '') + + def test_tool_equals(self) -> None: + t = Tool() + self.assertEqual(t, t) diff --git a/tests/test_model_tool_repository.py b/tests/test_model_tool_repository.py new file mode 100644 index 00000000..4588fbe4 --- /dev/null +++ b/tests/test_model_tool_repository.py @@ -0,0 +1,81 @@ +# This file is part of CycloneDX Python Lib +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# Copyright (c) OWASP Foundation. All Rights Reserved. + + +from unittest import TestCase + +from cyclonedx.model.component import Component +from cyclonedx.model.service import Service +from cyclonedx.model.tool import Tool, ToolsRepository + + +class TestModelToolRepository(TestCase): + + def test_init(self) -> ToolsRepository: + c = Component(name='test-component') + s = Service(name='test-service') + t = Tool(name='test-tool') + tr = ToolsRepository( + components=(c,), + services=(s,), + tools=(t,) + ) + self.assertIs(c, tuple(tr.components)[0]) + self.assertIs(s, tuple(tr.services)[0]) + self.assertIs(t, tuple(tr.tools)[0]) + return tr + + def test_filled(self) -> None: + tr = self.test_init() + self.assertEqual(3, len(tr)) + self.assertTrue(tr) + + def test_empty(self) -> None: + tr = ToolsRepository() + self.assertEqual(0, len(tr)) + self.assertFalse(tr) + + def test_unequal_different_type(self) -> None: + tr = ToolsRepository() + self.assertFalse(tr == 'other') + + def test_equal_self(self) -> None: + tr = ToolsRepository() + tr.tools.add(Tool(name='my-tool')) + self.assertTrue(tr == tr) + + def test_unequal(self) -> None: + tr1 = ToolsRepository() + tr1.components.add(Component(name='my-component')) + tr1.services.add(Service(name='my-service')) + tr1.tools.add(Tool(name='my-tool')) + tr2 = ToolsRepository() + self.assertFalse(tr1 == tr2) + + def test_equal(self) -> None: + c = Component(name='my-component') + s = Service(name='my-service') + t = Tool(name='my-tool') + tr1 = ToolsRepository() + tr1.components.add(c) + tr1.services.add(s) + tr1.tools.add(t) + tr2 = ToolsRepository() + tr2.components.add(c) + tr2.services.add(s) + tr2.tools.add(t) + self.assertTrue(tr1 == tr2) diff --git a/tests/test_validation_json.py b/tests/test_validation_json.py index 7a297189..08b53f18 100644 --- a/tests/test_validation_json.py +++ b/tests/test_validation_json.py @@ -31,17 +31,20 @@ UNSUPPORTED_SCHEMA_VERSIONS = {SchemaVersion.V1_0, SchemaVersion.V1_1, } -def _dp_sv_tf(prefix: str) -> Generator: +def _dp_sv_tf(valid: bool) -> Generator: + prefix = 'valid-' if valid else 'invalid-' return ( - DpTuple((sv, tf)) for sv in SchemaVersion if sv not in UNSUPPORTED_SCHEMA_VERSIONS - for tf in iglob(join(SCHEMA_TESTDATA_DIRECTORY, sv.to_version(), f'{prefix}-*.json')) + DpTuple((sv, tf)) + for sv in SchemaVersion if sv not in UNSUPPORTED_SCHEMA_VERSIONS + for tf in iglob(join(SCHEMA_TESTDATA_DIRECTORY, sv.to_version(), f'{prefix}*.json')) ) -def _dp_sv_own() -> Generator: +def _dp_sv_own(valid: bool) -> Generator: return ( - DpTuple((sv, tf)) for sv in SchemaVersion if sv not in UNSUPPORTED_SCHEMA_VERSIONS - for tf in iglob(join(OWN_DATA_DIRECTORY, 'json', sv.to_version(), '*.json')) + DpTuple((sv, tf)) + for sv in SchemaVersion if sv not in UNSUPPORTED_SCHEMA_VERSIONS + for tf in iglob(join(OWN_DATA_DIRECTORY, 'json', sv.to_version(), '*.json')) if ('invalid-' in tf) != valid ) @@ -60,8 +63,8 @@ def test_throws_with_unsupported_schema_version(self, schema_version: SchemaVers JsonValidator(schema_version) @idata(chain( - _dp_sv_tf('valid'), - _dp_sv_own() + _dp_sv_tf(True), + _dp_sv_own(True) )) @unpack def test_validate_no_none(self, schema_version: SchemaVersion, test_data_file: str) -> None: @@ -74,7 +77,10 @@ def test_validate_no_none(self, schema_version: SchemaVersion, test_data_file: s self.skipTest('MissingOptionalDependencyException') self.assertIsNone(validation_error) - @idata(_dp_sv_tf('invalid')) + @idata(chain( + _dp_sv_tf(False), + _dp_sv_own(False) + )) @unpack def test_validate_expected_error(self, schema_version: SchemaVersion, test_data_file: str) -> None: validator = JsonValidator(schema_version) @@ -97,8 +103,8 @@ def test_throws_with_unsupported_schema_version(self, schema_version: SchemaVers JsonStrictValidator(schema_version) @idata(chain( - _dp_sv_tf('valid'), - _dp_sv_own() + _dp_sv_tf(True), + _dp_sv_own(True) )) @unpack def test_validate_no_none(self, schema_version: SchemaVersion, test_data_file: str) -> None: @@ -111,7 +117,10 @@ def test_validate_no_none(self, schema_version: SchemaVersion, test_data_file: s self.skipTest('MissingOptionalDependencyException') self.assertIsNone(validation_error) - @idata(_dp_sv_tf('invalid')) + @idata(chain( + _dp_sv_tf(False), + _dp_sv_own(False) + )) @unpack def test_validate_expected_error(self, schema_version: SchemaVersion, test_data_file: str) -> None: validator = JsonStrictValidator(schema_version) diff --git a/tests/test_validation_xml.py b/tests/test_validation_xml.py index e5d91479..b4228b33 100644 --- a/tests/test_validation_xml.py +++ b/tests/test_validation_xml.py @@ -31,17 +31,20 @@ UNSUPPORTED_SCHEMA_VERSIONS = set() -def _dp_sv_tf(prefix: str) -> Generator: +def _dp_sv_tf(valid: bool) -> Generator: + prefix = 'valid-' if valid else 'invalid-' return ( - DpTuple((sv, tf)) for sv in SchemaVersion if sv not in UNSUPPORTED_SCHEMA_VERSIONS - for tf in iglob(join(SCHEMA_TESTDATA_DIRECTORY, sv.to_version(), f'{prefix}-*.xml')) + DpTuple((sv, tf)) + for sv in SchemaVersion if sv not in UNSUPPORTED_SCHEMA_VERSIONS + for tf in iglob(join(SCHEMA_TESTDATA_DIRECTORY, sv.to_version(), f'{prefix}*.xml')) ) -def _dp_sv_own() -> Generator: +def _dp_sv_own(valid: bool) -> Generator: return ( - DpTuple((sv, tf)) for sv in SchemaVersion if sv not in UNSUPPORTED_SCHEMA_VERSIONS - for tf in iglob(join(OWN_DATA_DIRECTORY, 'xml', sv.to_version(), '*.xml')) + DpTuple((sv, tf)) + for sv in SchemaVersion if sv not in UNSUPPORTED_SCHEMA_VERSIONS + for tf in iglob(join(OWN_DATA_DIRECTORY, 'xml', sv.to_version(), '*.xml')) if ('invalid-' in tf) != valid ) @@ -60,8 +63,8 @@ def test_throws_with_unsupported_schema_version(self, schema_version: SchemaVers XmlValidator(schema_version) @idata(chain( - _dp_sv_tf('valid'), - _dp_sv_own() + _dp_sv_tf(True), + _dp_sv_own(True) )) @unpack def test_validate_no_none(self, schema_version: SchemaVersion, test_data_file: str) -> None: @@ -74,7 +77,10 @@ def test_validate_no_none(self, schema_version: SchemaVersion, test_data_file: s self.skipTest('MissingOptionalDependencyException') self.assertIsNone(validation_error) - @idata(_dp_sv_tf('invalid')) + @idata(chain( + _dp_sv_tf(False), + _dp_sv_own(False) + )) @unpack def test_validate_expected_error(self, schema_version: SchemaVersion, test_data_file: str) -> None: validator = XmlValidator(schema_version)