diff --git a/cyclonedx/exception/serialization.py b/cyclonedx/exception/serialization.py index 565b36c8b..9c947344b 100644 --- a/cyclonedx/exception/serialization.py +++ b/cyclonedx/exception/serialization.py @@ -44,6 +44,15 @@ class SerializationOfUnsupportedComponentTypeException(CycloneDxSerializationExc """ +class SerializationOfUnsupportedComponentIdentityEvidenceFieldException(CycloneDxSerializationException): + """ + Raised when attempting serializing/normalizing a :py:class:`cyclonedx.model.component.Component` + to a :py:class:`cyclonedx.schema.schema.BaseSchemaVersion` + which does not support that :py:class:`cyclonedx.model.component.ComponentIdentityEvidenceField` + . + """ + + class SerializationOfUnexpectedValueException(CycloneDxSerializationException, ValueError): """ Raised when attempting serializing/normalizing a type that is not expected there. diff --git a/cyclonedx/model/component.py b/cyclonedx/model/component.py index 6b106c312..682ad4c53 100644 --- a/cyclonedx/model/component.py +++ b/cyclonedx/model/component.py @@ -38,6 +38,7 @@ from ..exception.serialization import ( CycloneDxDeserializationException, SerializationOfUnexpectedValueException, + SerializationOfUnsupportedComponentIdentityEvidenceFieldException, SerializationOfUnsupportedComponentTypeException, ) from ..schema.schema import ( @@ -237,10 +238,11 @@ class _ComponentIdentityEvidenceFieldSerializationHelper(serializable.helpers.Ba } @classmethod - def __normalize(cls, cs: ComponentIdentityEvidenceField, view: Type[serializable.ViewType]) -> Optional[str]: - return cs.value \ - if cs in cls.__CASES.get(view, ()) \ - else None + def __normalize(cls, cief: ComponentIdentityEvidenceField, view: Type[serializable.ViewType]) -> Optional[str]: + if cief in cls.__CASES.get(view, ()): + return cief.value + raise SerializationOfUnsupportedComponentIdentityEvidenceFieldException( + f'unsupported {cief!r} for view {view!r}') @classmethod def json_normalize(cls, o: Any, *, @@ -492,7 +494,7 @@ def confidence(self, confidence: Optional[float]) -> None: self._confidence = confidence @property - @serializable.type_mapping(ComponentIdentityEvidenceMethod) + @serializable.xml_array(serializable.XmlArraySerializationType.NESTED, 'method') @serializable.xml_sequence(3) def methods(self) -> 'SortedSet[ComponentIdentityEvidenceMethod]': """ @@ -572,7 +574,7 @@ def __init__( @property @serializable.view(SchemaVersion1Dot5) @serializable.view(SchemaVersion1Dot6) - @serializable.xml_array(serializable.XmlArraySerializationType.NESTED, 'identity') + @serializable.xml_array(serializable.XmlArraySerializationType.FLAT, 'identity') @serializable.xml_sequence(1) def identity(self) -> 'SortedSet[ComponentIdentityEvidence]': """ diff --git a/tests/_data/snapshots/enum_ComponentIdentityEvidenceField-1.0.xml.bin b/tests/_data/snapshots/enum_ComponentIdentityEvidenceField-1.0.xml.bin index acb066124..068b881e8 100644 --- a/tests/_data/snapshots/enum_ComponentIdentityEvidenceField-1.0.xml.bin +++ b/tests/_data/snapshots/enum_ComponentIdentityEvidenceField-1.0.xml.bin @@ -1,4 +1,10 @@ - + + + dummy + + false + + diff --git a/tests/_data/snapshots/enum_ComponentIdentityEvidenceField-1.1.xml.bin b/tests/_data/snapshots/enum_ComponentIdentityEvidenceField-1.1.xml.bin index 55ef5cda2..6212e7a1e 100644 --- a/tests/_data/snapshots/enum_ComponentIdentityEvidenceField-1.1.xml.bin +++ b/tests/_data/snapshots/enum_ComponentIdentityEvidenceField-1.1.xml.bin @@ -1,4 +1,9 @@ - + + + dummy + + + diff --git a/tests/_data/snapshots/enum_ComponentIdentityEvidenceField-1.2.json.bin b/tests/_data/snapshots/enum_ComponentIdentityEvidenceField-1.2.json.bin index fafe615c9..2161f0540 100644 --- a/tests/_data/snapshots/enum_ComponentIdentityEvidenceField-1.2.json.bin +++ b/tests/_data/snapshots/enum_ComponentIdentityEvidenceField-1.2.json.bin @@ -1,4 +1,17 @@ { + "components": [ + { + "bom-ref": "dummy", + "name": "dummy", + "type": "library", + "version": "" + } + ], + "dependencies": [ + { + "ref": "dummy" + } + ], "metadata": { "timestamp": "2023-01-07T13:44:32.312678+00:00", "tools": [ @@ -14,4 +27,4 @@ "$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/enum_ComponentIdentityEvidenceField-1.2.xml.bin b/tests/_data/snapshots/enum_ComponentIdentityEvidenceField-1.2.xml.bin index bc36ede08..9be884da4 100644 --- a/tests/_data/snapshots/enum_ComponentIdentityEvidenceField-1.2.xml.bin +++ b/tests/_data/snapshots/enum_ComponentIdentityEvidenceField-1.2.xml.bin @@ -10,4 +10,13 @@ + + + dummy + + + + + + diff --git a/tests/_data/snapshots/enum_ComponentIdentityEvidenceField-1.3.json.bin b/tests/_data/snapshots/enum_ComponentIdentityEvidenceField-1.3.json.bin index d2e65ac15..05a843a59 100644 --- a/tests/_data/snapshots/enum_ComponentIdentityEvidenceField-1.3.json.bin +++ b/tests/_data/snapshots/enum_ComponentIdentityEvidenceField-1.3.json.bin @@ -1,4 +1,24 @@ { + "components": [ + { + "bom-ref": "dummy", + "evidence": { + "copyright": [ + { + "text": "Dummy" + } + ] + }, + "name": "dummy", + "type": "library", + "version": "" + } + ], + "dependencies": [ + { + "ref": "dummy" + } + ], "metadata": { "timestamp": "2023-01-07T13:44:32.312678+00:00", "tools": [ @@ -14,4 +34,4 @@ "$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/enum_ComponentIdentityEvidenceField-1.3.xml.bin b/tests/_data/snapshots/enum_ComponentIdentityEvidenceField-1.3.xml.bin index 1ebd391f9..d8a52a530 100644 --- a/tests/_data/snapshots/enum_ComponentIdentityEvidenceField-1.3.xml.bin +++ b/tests/_data/snapshots/enum_ComponentIdentityEvidenceField-1.3.xml.bin @@ -10,4 +10,18 @@ + + + dummy + + + + Dummy + + + + + + + diff --git a/tests/_data/snapshots/enum_ComponentIdentityEvidenceField-1.4.json.bin b/tests/_data/snapshots/enum_ComponentIdentityEvidenceField-1.4.json.bin index 513c4f0f5..a3baa3997 100644 --- a/tests/_data/snapshots/enum_ComponentIdentityEvidenceField-1.4.json.bin +++ b/tests/_data/snapshots/enum_ComponentIdentityEvidenceField-1.4.json.bin @@ -1,8 +1,61 @@ { + "components": [ + { + "bom-ref": "dummy", + "evidence": { + "copyright": [ + { + "text": "Dummy" + } + ] + }, + "name": "dummy", + "type": "library" + } + ], + "dependencies": [ + { + "ref": "dummy" + } + ], "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" @@ -14,4 +67,4 @@ "$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/enum_ComponentIdentityEvidenceField-1.4.xml.bin b/tests/_data/snapshots/enum_ComponentIdentityEvidenceField-1.4.xml.bin index 21093ed67..e5530a752 100644 --- a/tests/_data/snapshots/enum_ComponentIdentityEvidenceField-1.4.xml.bin +++ b/tests/_data/snapshots/enum_ComponentIdentityEvidenceField-1.4.xml.bin @@ -1,4 +1,3 @@ - @@ -8,7 +7,46 @@ 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 + + + + + dummy + + + Dummy + + + + + + + diff --git a/tests/_data/snapshots/enum_ComponentIdentityEvidenceField-1.5.json.bin b/tests/_data/snapshots/enum_ComponentIdentityEvidenceField-1.5.json.bin index b533e71c3..457968196 100644 --- a/tests/_data/snapshots/enum_ComponentIdentityEvidenceField-1.5.json.bin +++ b/tests/_data/snapshots/enum_ComponentIdentityEvidenceField-1.5.json.bin @@ -2,13 +2,20 @@ "components": [ { "bom-ref": "dummy", - "name": "dummy", - "type": "application", "evidence": { - "identity": { - "field": "group" - } - } + "copyright": [ + { + "text": "Dummy" + } + ] + }, + "name": "dummy", + "type": "library" + } + ], + "dependencies": [ + { + "ref": "dummy" } ], "metadata": { @@ -70,4 +77,4 @@ "$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/enum_ComponentIdentityEvidenceField-1.5.xml.bin b/tests/_data/snapshots/enum_ComponentIdentityEvidenceField-1.5.xml.bin index 67d8e9bdb..aedaed06f 100644 --- a/tests/_data/snapshots/enum_ComponentIdentityEvidenceField-1.5.xml.bin +++ b/tests/_data/snapshots/enum_ComponentIdentityEvidenceField-1.5.xml.bin @@ -37,15 +37,18 @@ - + dummy - - group - + + Dummy + + + + val1 val2 diff --git a/tests/_data/snapshots/enum_ComponentIdentityEvidenceField-1.6.json.bin b/tests/_data/snapshots/enum_ComponentIdentityEvidenceField-1.6.json.bin index 62c96b5da..d79dc1d42 100644 --- a/tests/_data/snapshots/enum_ComponentIdentityEvidenceField-1.6.json.bin +++ b/tests/_data/snapshots/enum_ComponentIdentityEvidenceField-1.6.json.bin @@ -2,27 +2,30 @@ "components": [ { "bom-ref": "dummy", - "name": "dummy", - "type": "application", "evidence": { + "copyright": [ + { + "text": "Dummy" + } + ], "identity": [ { - "field": "group" + "field": "cpe" }, { - "field": "name" + "field": "group" }, { - "field": "version" + "field": "hash" }, { - "field": "purl" + "field": "name" }, { - "field": "cpe" + "field": "omniborId" }, { - "field": "omniborId" + "field": "purl" }, { "field": "swhid" @@ -31,10 +34,17 @@ "field": "swid" }, { - "field": "hash" + "field": "version" } ] - } + }, + "name": "dummy", + "type": "library" + } + ], + "dependencies": [ + { + "ref": "dummy" } ], "metadata": { @@ -96,4 +106,4 @@ "$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/enum_ComponentIdentityEvidenceField-1.6.xml.bin b/tests/_data/snapshots/enum_ComponentIdentityEvidenceField-1.6.xml.bin index bc7e7b68d..7bdd5e990 100644 --- a/tests/_data/snapshots/enum_ComponentIdentityEvidenceField-1.6.xml.bin +++ b/tests/_data/snapshots/enum_ComponentIdentityEvidenceField-1.6.xml.bin @@ -37,15 +37,45 @@ - + dummy + + cpe + group + + hash + + + name + + + omniborId + + + purl + + + swhid + + + swid + + + version + + + Dummy + + + + val1 val2 diff --git a/tests/test_enums.py b/tests/test_enums.py index fec8fa9de..ab551c774 100644 --- a/tests/test_enums.py +++ b/tests/test_enums.py @@ -26,7 +26,10 @@ from ddt import ddt, idata, named_data from cyclonedx.exception import MissingOptionalDependencyException -from cyclonedx.exception.serialization import SerializationOfUnsupportedComponentTypeException +from cyclonedx.exception.serialization import ( + SerializationOfUnsupportedComponentIdentityEvidenceFieldException, + SerializationOfUnsupportedComponentTypeException, +) from cyclonedx.model import AttachedText, ExternalReference, HashType, XsUri from cyclonedx.model.bom import Bom from cyclonedx.model.component import ( @@ -34,8 +37,7 @@ ComponentEvidence, ComponentIdentityEvidence, ComponentIdentityEvidenceField, - ComponentIdentityEvidenceMethod, - ComponentIdentityEvidenceMethodTechnique, + Copyright, Patch, Pedigree, ) @@ -364,12 +366,32 @@ def test_cases_render_valid(self, of: OutputFormat, sv: SchemaVersion, *_: Any, super()._test_cases_render(bom, of, sv) +class _DP_ComponentIdentityEvidenceField(): # noqa: N801 + XML_SCHEMA_XPATH = f"./{SCHEMA_NS}simpleType[@name='identityFieldType']" + JSON_SCHEMA_POINTER = ('definitions', 'componentIdentityEvidence', 'properties', 'field') + + @classmethod + def unsupported_cases(cls) -> Generator[ + Tuple[str, OutputFormat, SchemaVersion, ComponentIdentityEvidenceField], None, None + ]: + for name, of, sv in NAMED_OF_SV: + if OutputFormat.XML is of: + schema_cases = set(dp_cases_from_xml_schema(SCHEMA_XML[sv], cls.XML_SCHEMA_XPATH)) + elif OutputFormat.JSON is of: + schema_cases = set(dp_cases_from_json_schema(SCHEMA_JSON[sv], cls.JSON_SCHEMA_POINTER)) + else: + raise ValueError(f'unexpected of: {of!r}') + for cief in ComponentIdentityEvidenceField: + if cief.value not in schema_cases: + yield f'{name}-{cief.name}', of, sv, cief + + @ddt class TestEnumComponentIdentityEvidenceField(_EnumTestCase): @idata(set(chain( - # dp_cases_from_xml_schemas(f"./{SCHEMA_NS}simpleType[@name='componentIdentityEvidence']"), - dp_cases_from_json_schemas('definitions', 'componentIdentityEvidence', 'properties', 'field'), + dp_cases_from_xml_schemas(_DP_ComponentIdentityEvidenceField.XML_SCHEMA_XPATH), + dp_cases_from_json_schemas(*_DP_ComponentIdentityEvidenceField.JSON_SCHEMA_POINTER), ))) def test_knows_value(self, value: str) -> None: super()._test_knows_value(ComponentIdentityEvidenceField, value) @@ -377,23 +399,53 @@ def test_knows_value(self, value: str) -> None: @named_data(*NAMED_OF_SV) @patch('cyclonedx.model.ThisTool._version', 'TESTING') def test_cases_render_valid(self, of: OutputFormat, sv: SchemaVersion, *_: Any, **__: Any) -> None: + if OutputFormat.XML is of: + schema_cases = set(dp_cases_from_xml_schema(SCHEMA_XML[sv], _DP_ComponentIdentityEvidenceField.XML_SCHEMA_XPATH)) + elif OutputFormat.JSON is of: + schema_cases = set(dp_cases_from_json_schema(SCHEMA_JSON[sv], _DP_ComponentIdentityEvidenceField.JSON_SCHEMA_POINTER)) + else: + raise ValueError(f'unexpected of: {of!r}') + bom = _make_bom(components=[ - Component(name='dummy', type=ComponentType.LIBRARY, bom_ref='dummy', evidence=ComponentEvidence( + Component(bom_ref='dummy', name='dummy', evidence=ComponentEvidence( + copyright=[ + Copyright(text="Dummy") + ], identity=[ ComponentIdentityEvidence( - field=cief, - confidence=0.5, - methods=[ - ComponentIdentityEvidenceMethod(technique=ciemt, confidence=0.5) - for ciemt in ComponentIdentityEvidenceMethodTechnique - ] + field=ComponentIdentityEvidenceField(cief), ) + for cief in schema_cases + if cief in schema_cases ] )) - for cief in ComponentIdentityEvidenceField ]) super()._test_cases_render(bom, of, sv) + @named_data(*_DP_ComponentIdentityEvidenceField.unsupported_cases()) + @patch('cyclonedx.model.ThisTool._version', 'TESTING') + def test_cases_render_raises_on_unsupported(self, of: OutputFormat, sv: SchemaVersion, + cief: ComponentIdentityEvidenceField, + *_: Any, **__: Any) -> None: + # componentIdentityEvidence type was not added until 1.5 + if sv.value < (1, 5): + return True + + bom = _make_bom(components=[ + Component(bom_ref='dummy', name='dummy', evidence=ComponentEvidence( + copyright=[ + Copyright(text="Dummy") + ], + identity=[ + ComponentIdentityEvidence( + field=cief, + ) + ] + )) + ]) + with self.assertRaises(SerializationOfUnsupportedComponentIdentityEvidenceFieldException): + super()._test_cases_render(bom, of, sv) + @ddt class TestEnumImpactAnalysisJustification(_EnumTestCase):