diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..17d087db --- /dev/null +++ b/.editorconfig @@ -0,0 +1,37 @@ +# EditorConfig is awesome: https://EditorConfig.org + +root = true + +[*] +insert_final_newline = true +charset = utf-8 +trim_trailing_whitespace = true +end_of_line = lf + +[*.py] +indent_style = space +indent_size = 4 + +[*.{yml,yaml}] +indent_style = space +indent_size = 2 + +[*.toml] +indent_style = space +indent_size = 2 + +[*.md] +charset = latin1 +indent_style = space +indent_size = 2 +# 2 trailing spaces indicate line breaks. +trim_trailing_whitespace = false + +[*.{rst,txt}] +indent_style = space +indent_size = 4 + +[*.ini] +charset = latin1 +indent_style = space +indent_size = 4 diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 501e0eef..2054df4f 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -2,9 +2,12 @@ name: Deploy to PyPI on: push: - branches: - - main + branches: [ 'main' ] workflow_dispatch: + +env: + PYTHON_VERSION_DEFAULT: "3.10" + POETRY_VERSION: "1.1.12" jobs: release: @@ -19,22 +22,33 @@ jobs: uses: actions/checkout@v3 with: fetch-depth: 0 + - name: Setup python # see https://github.com/actions/setup-python uses: actions/setup-python@v4 with: - python-version: 3.9 + python-version: ${{ env.PYTHON_VERSION_DEFAULT }} + architecture: 'x64' + + - name: Install and configure Poetry + # See https://github.com/marketplace/actions/install-poetry-action + uses: snok/install-poetry@v1 + with: + version: ${{ env.POETRY_VERSION }} + virtualenvs-create: true + virtualenvs-in-project: true + installer-parallel: true + - name: Install dependencies - run: | - python -m pip install poetry --upgrade pip - poetry config virtualenvs.create false - poetry install + run: poetry install --no-root + - name: View poetry version run: poetry --version + - name: Python Semantic Release # see https://python-semantic-release.readthedocs.io/en/latest/automatic-releases/github-actions.html # see https://github.com/relekang/python-semantic-release - uses: relekang/python-semantic-release@v7.29.5 + uses: relekang/python-semantic-release@v7.31.2 with: github_token: ${{ secrets.GITHUB_TOKEN }} repository_username: __token__ diff --git a/.github/workflows/manual-release-candidate.yml b/.github/workflows/manual-release-candidate.yml index 47376642..c56aaab9 100644 --- a/.github/workflows/manual-release-candidate.yml +++ b/.github/workflows/manual-release-candidate.yml @@ -25,7 +25,7 @@ jobs: python -m pip install poetry --upgrade pip poetry config virtualenvs.create false poetry install - python -m pip install python-semantic-release + python -m pip install python-semantic-release==7.28.1 - name: Apply Pre Release Version run: | RC_VERSION="$(semantic-release --noop --major print-version)-${{ github.event.inputs.release_candidate_suffix }}" diff --git a/.github/workflows/poetry.yml b/.github/workflows/poetry.yml index 9674dd7f..e9dc8771 100644 --- a/.github/workflows/poetry.yml +++ b/.github/workflows/poetry.yml @@ -80,7 +80,7 @@ jobs: build-and-test: name: Test (${{ matrix.os }} py${{ matrix.python-version }} ${{ matrix.toxenv-factor }}) runs-on: ${{ matrix.os }} - timeout-minutes: 10 + timeout-minutes: 15 env: REPORTS_ARTIFACT: tests-reports strategy: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b92ff6d9..5cab2e61 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,7 +4,7 @@ repos: hooks: - id: system name: mypy - entry: poetry run tox -e mypy + entry: poetry run tox -e mypy-locked pass_filenames: false language: system - repo: local diff --git a/README.md b/README.md index 48fea4dc..17bd905d 100644 --- a/README.md +++ b/README.md @@ -13,19 +13,21 @@ ---- This CycloneDX module for Python can generate valid CycloneDX bill-of-material document containing an aggregate of all -project dependencies. +project dependencies. CycloneDX is a lightweight BOM specification that is easily created, human-readable, and simple +to parse. -This module is not designed for standalone use. +**This module is not designed for standalone use.** -If you're looking for a CycloneDX tool to run to generate (SBOM) software bill-of-materials documents, why not checkout: [CycloneDX Python][cyclonedx-python] +As of version `3.0.0`, the internal data model was adjusted to allow CycloneDX VEX documents to be produced as per +[official examples](https://cyclonedx.org/capabilities/bomlink/#linking-external-vex-to-bom-inventory) linking a VEX +documents to a separate BOM document. -Additionally, the following tool can be used as well (and this library was written to help improve it) [Jake][jake]. +If you're looking for a CycloneDX tool to run to generate (SBOM) software bill-of-materials documents, why not checkout +[CycloneDX Python][cyclonedx-python] or [Jake][jake]. -Additionally, you can use this module yourself in your application to programmatically generate SBOMs. +Alternatively, you can use this module yourself in your application to programmatically generate CycloneDX BOMs. -CycloneDX is a lightweight BOM specification that is easily created, human-readable, and simple to parse. - -View our documentation [here](https://cyclonedx-python-library.readthedocs.io/). +View the documentation [here](https://cyclonedx-python-library.readthedocs.io/). ## Python Support diff --git a/cyclonedx/model/bom.py b/cyclonedx/model/bom.py index 9cbf8363..5e3b3ecc 100644 --- a/cyclonedx/model/bom.py +++ b/cyclonedx/model/bom.py @@ -16,6 +16,7 @@ # # SPDX-License-Identifier: Apache-2.0 # Copyright (c) OWASP Foundation. All Rights Reserved. + import warnings from datetime import datetime, timezone from typing import Iterable, Optional, Set @@ -26,8 +27,10 @@ from ..exception.model import UnknownComponentDependencyException from ..parser import BaseParser from . import ExternalReference, LicenseChoice, OrganizationalContact, OrganizationalEntity, Property, ThisTool, Tool +from .bom_ref import BomRef from .component import Component from .service import Service +from .vulnerability import Vulnerability class BomMetaData: @@ -230,18 +233,21 @@ def from_parser(parser: BaseParser) -> 'Bom': def __init__(self, *, components: Optional[Iterable[Component]] = None, services: Optional[Iterable[Service]] = None, - external_references: Optional[Iterable[ExternalReference]] = None) -> None: + external_references: Optional[Iterable[ExternalReference]] = None, + serial_number: Optional[UUID] = None, version: int = 1) -> None: """ Create a new Bom that you can manually/programmatically add data to later. Returns: New, empty `cyclonedx.model.bom.Bom` instance. """ - self.uuid = uuid4() + self.uuid = serial_number or uuid4() self.metadata = BomMetaData() self.components = components or [] # type: ignore self.services = services or [] # type: ignore self.external_references = external_references or [] # type: ignore + self.vulnerabilities = SortedSet() + self.version = version @property def uuid(self) -> UUID: @@ -313,7 +319,7 @@ def get_urn_uuid(self) -> str: Returns: URN formatted UUID that uniquely identified this Bom instance. """ - return 'urn:uuid:{}'.format(self.__uuid) + return self.__uuid.urn def has_component(self, component: Component) -> bool: """ @@ -366,15 +372,57 @@ def _get_all_components(self) -> Set[Component]: return components + def get_vulnerabilities_for_bom_ref(self, bom_ref: BomRef) -> "SortedSet[Vulnerability]": + """ + Get all known Vulnerabilities that affect the supplied bom_ref. + + Args: + bom_ref: `BomRef` + + Returns: + `SortedSet` of `Vulnerability` + """ + + vulnerabilities: SortedSet[Vulnerability] = SortedSet() + for v in self.vulnerabilities: + for target in v.affects: + if target.ref == bom_ref.value: + vulnerabilities.add(v) + return vulnerabilities + def has_vulnerabilities(self) -> bool: """ Check whether this Bom has any declared vulnerabilities. Returns: - `bool` - `True` if at least one `cyclonedx.model.component.Component` has at least one Vulnerability, - `False` otherwise. + `bool` - `True` if this Bom has at least one Vulnerability, `False` otherwise. + """ + return bool(self.vulnerabilities) + + @property + def vulnerabilities(self) -> "SortedSet[Vulnerability]": + """ + Get all the Vulnerabilities in this BOM. + + Returns: + Set of `Vulnerability` """ - return any(c.has_vulnerabilities() for c in self.components) + return self._vulnerabilities + + @vulnerabilities.setter + def vulnerabilities(self, vulnerabilities: Iterable[Vulnerability]) -> None: + self._vulnerabilities = SortedSet(vulnerabilities) + + @property + def version(self) -> int: + return self._version + + @version.setter + def version(self, version: int) -> None: + self._version = version + + def urn(self) -> str: + return f'urn:cdx:{self.uuid}/{self.version}' def validate(self) -> bool: """ @@ -399,9 +447,9 @@ def validate(self) -> bool: # 2. Dependencies should exist for the Component this BOM is describing, if one is set if self.metadata.component and not self.metadata.component.dependencies: warnings.warn( - f'The Component this BOM is describing (PURL={self.metadata.component.purl}) has no defined ' - f'dependencies which means the Dependency Graph is incomplete - you should add direct dependencies to ' - f'this Component to complete the Dependency Graph data.', + f'The Component this BOM is describing {self.metadata.component.purl} has no defined dependencies ' + f'which means the Dependency Graph is incomplete - you should add direct dependencies to this Component' + f'to complete the Dependency Graph data.', UserWarning ) diff --git a/cyclonedx/model/bom_ref.py b/cyclonedx/model/bom_ref.py index 1d48e6a3..79dad0a3 100644 --- a/cyclonedx/model/bom_ref.py +++ b/cyclonedx/model/bom_ref.py @@ -44,7 +44,7 @@ def value(self, value: str) -> None: def __eq__(self, other: object) -> bool: if isinstance(other, BomRef): - return hash(other) == hash(self) + return other.value == self.value return False def __lt__(self, other: Any) -> bool: diff --git a/cyclonedx/model/component.py b/cyclonedx/model/component.py index f3074f67..34ff9dc4 100644 --- a/cyclonedx/model/component.py +++ b/cyclonedx/model/component.py @@ -44,7 +44,6 @@ from .bom_ref import BomRef from .issue import IssueType from .release_note import ReleaseNotes -from .vulnerability import Vulnerability class Commit: @@ -763,7 +762,6 @@ def __init__(self, *, name: str, component_type: ComponentType = ComponentType.L self.licenses = [LicenseChoice(license_expression=license_str)] # type: ignore self.__dependencies: "SortedSet[BomRef]" = SortedSet() - self.__vulnerabilites: "SortedSet[Vulnerability]" = SortedSet() @property def type(self) -> ComponentType: @@ -1128,37 +1126,6 @@ def dependencies(self) -> "SortedSet[BomRef]": def dependencies(self, dependencies: Iterable[BomRef]) -> None: self.__dependencies = SortedSet(dependencies) - def add_vulnerability(self, vulnerability: Vulnerability) -> None: - """ - Add a Vulnerability to this Component. - - Args: - vulnerability: - `cyclonedx.model.vulnerability.Vulnerability` instance to add to this Component. - - Returns: - None - """ - self.__vulnerabilites.add(vulnerability) - - def get_vulnerabilities(self) -> "SortedSet[Vulnerability]": - """ - Get all the Vulnerabilities for this Component. - - Returns: - Set of `Vulnerability` - """ - return self.__vulnerabilites - - def has_vulnerabilities(self) -> bool: - """ - Does this Component have any vulnerabilities? - - Returns: - `True` if this Component has 1 or more vulnerabilities, `False` otherwise. - """ - return bool(self.get_vulnerabilities()) - def get_all_nested_components(self, include_self: bool = False) -> Set["Component"]: components = set() if include_self: diff --git a/cyclonedx/output/json.py b/cyclonedx/output/json.py index 4556f3d2..d71d3370 100644 --- a/cyclonedx/output/json.py +++ b/cyclonedx/output/json.py @@ -79,17 +79,6 @@ def generate(self, force_regeneration: bool = False) -> None: extras["dependencies"] = dependencies del dep_components - if self.bom_supports_vulnerabilities(): - vulnerabilities: List[Dict[Any, Any]] = [] - if bom.components: - for component in bom.components: - for vulnerability in component.get_vulnerabilities(): - vulnerabilities.append( - json.loads(json.dumps(vulnerability, cls=CycloneDxJSONEncoder)) - ) - if vulnerabilities: - extras["vulnerabilities"] = vulnerabilities - bom_json = json.loads(json.dumps(bom, cls=CycloneDxJSONEncoder)) bom_json = json.loads(self._specialise_output_for_schema_version(bom_json=bom_json)) self._json_output = json.dumps({**self._create_bom_element(), **bom_json, **extras}) @@ -136,6 +125,10 @@ def _specialise_output_for_schema_version(self, bom_json: Dict[Any, Any]) -> str and 'hashes' in bom_json['externalReferences'][i].keys(): del bom_json['externalReferences'][i]['hashes'] + # Remove Vulnerabilities if not supported + if not self.bom_supports_vulnerabilities() and 'vulnerabilities' in bom_json.keys(): + del bom_json['vulnerabilities'] + return json.dumps(bom_json) def output_as_string(self) -> str: diff --git a/cyclonedx/output/xml.py b/cyclonedx/output/xml.py index d297b143..af3c42c9 100644 --- a/cyclonedx/output/xml.py +++ b/cyclonedx/output/xml.py @@ -75,29 +75,28 @@ def generate(self, force_regeneration: bool = False) -> None: if self.bom_supports_metadata(): self._add_metadata_element() - has_vulnerabilities: bool = False - components_element = ElementTree.SubElement(self._root_bom_element, 'components') if bom.components: for component in bom.components: component_element = self._add_component_element(component=component) components_element.append(component_element) - if self.bom_supports_vulnerabilities_via_extension() and component.has_vulnerabilities(): - # Vulnerabilities are only possible when bom-ref is supported by the main CycloneDX schema version - vulnerabilities = ElementTree.SubElement(component_element, 'v:vulnerabilities') - for vulnerability in component.get_vulnerabilities(): - if component.bom_ref: - vulnerabilities.append( - Xml._get_vulnerability_as_xml_element_pre_1_3(bom_ref=component.bom_ref, - vulnerability=vulnerability) - ) - else: - warnings.warn( - f'Unable to include Vulnerability {str(vulnerability)} in generated BOM as the ' - f'Component it relates to ({str(component)}) but it has no bom-ref.' - ) - elif component.has_vulnerabilities(): - has_vulnerabilities = True + if self.bom_supports_vulnerabilities_via_extension(): + component_vulnerabilities = bom.get_vulnerabilities_for_bom_ref(bom_ref=component.bom_ref) + if component_vulnerabilities: + # Vulnerabilities are only possible when bom-ref is supported by the main CycloneDX schema + # version + vulnerabilities = ElementTree.SubElement(component_element, 'v:vulnerabilities') + for vulnerability in component_vulnerabilities: + if component.bom_ref: + vulnerabilities.append( + Xml._get_vulnerability_as_xml_element_pre_1_3(bom_ref=component.bom_ref, + vulnerability=vulnerability) + ) + else: + warnings.warn( + f'Unable to include Vulnerability {str(vulnerability)} in generated BOM as the ' + f'Component it relates to ({str(component)}) but it has no bom-ref.' + ) if self.bom_supports_services() and bom.services: services_element = ElementTree.SubElement(self._root_bom_element, 'services') @@ -125,13 +124,12 @@ def generate(self, force_regeneration: bool = False) -> None: }) del dep_components - if self.bom_supports_vulnerabilities() and has_vulnerabilities: + if self.bom_supports_vulnerabilities() and bom.vulnerabilities: vulnerabilities_element = ElementTree.SubElement(self._root_bom_element, 'vulnerabilities') - for component in bom.components: - for vulnerability in component.get_vulnerabilities(): - vulnerabilities_element.append( - self._get_vulnerability_as_xml_element_post_1_4(vulnerability=vulnerability) - ) + for vulnerability in bom.vulnerabilities: + vulnerabilities_element.append( + self._get_vulnerability_as_xml_element_post_1_4(vulnerability=vulnerability) + ) self.generated = True diff --git a/docs/index.rst b/docs/index.rst index 6f2b0201..5d8b3986 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -20,6 +20,9 @@ CycloneDX is a lightweight BOM specification that is easily created, human-reada This CycloneDX module for Python can generate valid CycloneDX bill-of-material document containing an aggregate of all project dependencies. +As of version ``3.0.0``, the internal data model was adjusted to allow CycloneDX VEX documents to be produced as per +`official examples`_ linking VEX to a separate BOM. + This module is not designed for standalone use (i.e. it is not executable on it’s own). If you’re looking for a CycloneDX tool to run to generate (SBOM) software bill-of-materials documents, why not checkout: @@ -44,4 +47,5 @@ programmatically generate SBOMs. .. _CycloneDX Python: https://pypi.org/project/cyclonedx-bom/ .. _Jake: https://pypi.org/project/jake -.. _CycloneDX Tool Center: https://cyclonedx.org/tool-center/ \ No newline at end of file +.. _CycloneDX Tool Center: https://cyclonedx.org/tool-center/ +.. _official examples: https://cyclonedx.org/capabilities/bomlink/#linking-external-vex-to-bom-inventory \ No newline at end of file diff --git a/docs/schema-support.rst b/docs/schema-support.rst index 78553bc2..bbd7e7a3 100644 --- a/docs/schema-support.rst +++ b/docs/schema-support.rst @@ -43,6 +43,7 @@ supported in prior versions of the CycloneDX schema. | ``bom.properties`` | No | See `schema specification bug 130`_ | +----------------------------+---------------+---------------------------------------------------------------------------------------------------+ | ``bom.vulnerabilities`` | Yes | Note: Prior to CycloneDX 1.4, these were present under ``bom.components`` via a schema extension. | +| | | Note: As of ``cyclonedx-python-lib`` ``>3.0.0``, Vulnerability are modelled differently | +----------------------------+---------------+---------------------------------------------------------------------------------------------------+ | ``bom.signature`` | No | | +----------------------------+---------------+---------------------------------------------------------------------------------------------------+ diff --git a/tests/base.py b/tests/base.py index be5a1314..1bacc0c3 100644 --- a/tests/base.py +++ b/tests/base.py @@ -38,12 +38,16 @@ from jsonschema import ValidationError, validate as json_validate if sys.version_info >= (3, 8): - from importlib.metadata import version + from importlib.metadata import PackageNotFoundError, version else: from importlib_metadata import version cyclonedx_lib_name: str = 'cyclonedx-python-lib' -cyclonedx_lib_version: str = version(cyclonedx_lib_name) +cyclonedx_lib_version: str = 'DEV' +try: + cyclonedx_lib_version: str = version(cyclonedx_lib_name) +except PackageNotFoundError: + pass single_uuid: str = 'urn:uuid:{}'.format(uuid4()) schema_directory = os.path.join(os.path.dirname(__file__), '../cyclonedx/schema') diff --git a/tests/data.py b/tests/data.py index 4fa73737..d1ff4338 100644 --- a/tests/data.py +++ b/tests/data.py @@ -22,7 +22,8 @@ from decimal import Decimal from typing import List, Optional, TypeVar -from packageurl import PackageURL +# See https://github.com/package-url/packageurl-python/issues/65 +from packageurl import PackageURL # type: ignore from cyclonedx.model import ( AttachedText, @@ -144,7 +145,8 @@ def get_bom_with_component_setuptools_complete() -> Bom: def get_bom_with_component_setuptools_with_vulnerability() -> Bom: bom = Bom() component = get_component_setuptools_simple() - vulnerability = Vulnerability( + bom.components.add(component) + bom.vulnerabilities.add(Vulnerability( bom_ref='my-vuln-ref-1', id='CVE-2018-7489', source=get_vulnerability_source_nvd(), references=[ VulnerabilityReference(id='SOME-OTHER-ID', source=VulnerabilitySource( @@ -197,9 +199,7 @@ def get_bom_with_component_setuptools_with_vulnerability() -> Bom: ) ], properties=get_properties_1() - ) - component.add_vulnerability(vulnerability=vulnerability) - bom.components.add(component) + )) return bom diff --git a/tests/test_model_bom.py b/tests/test_model_bom.py index 6636d863..83896ccc 100644 --- a/tests/test_model_bom.py +++ b/tests/test_model_bom.py @@ -18,9 +18,11 @@ # Copyright (c) OWASP Foundation. All Rights Reserved. from unittest import TestCase +from uuid import uuid4 from cyclonedx.model import License, LicenseChoice, OrganizationalContact, OrganizationalEntity, Property from cyclonedx.model.bom import Bom, BomMetaData, ThisTool, Tool +from cyclonedx.model.bom_ref import BomRef from cyclonedx.model.component import Component, ComponentType from data import get_bom_for_issue_275_components, get_bom_with_component_setuptools_with_vulnerability @@ -94,6 +96,7 @@ def test_bom_metadata_tool_multiple_tools(self) -> None: bom.metadata.tools.add( Tool(vendor='TestVendor', name='TestTool', version='0.0.0') ) + self.assertEqual(bom.version, 1) self.assertEqual(len(bom.metadata.tools), 2) def test_metadata_component(self) -> None: @@ -106,16 +109,43 @@ def test_metadata_component(self) -> None: def test_empty_bom(self) -> None: bom = Bom() + self.assertEqual(bom.version, 1) self.assertIsNotNone(bom.uuid) self.assertIsNotNone(bom.metadata) self.assertFalse(bom.components) self.assertFalse(bom.services) self.assertFalse(bom.external_references) + def test_empty_bom_defined_serial(self) -> None: + serial_number = uuid4() + bom = Bom(serial_number=serial_number) + self.assertEqual(bom.uuid, serial_number) + self.assertEqual(bom.get_urn_uuid(), serial_number.urn) + self.assertEqual(bom.version, 1) + self.assertEqual(bom.urn(), f'urn:cdx:{serial_number}/1') + + def test_empty_bom_defined_serial_and_version(self) -> None: + serial_number = uuid4() + bom = Bom(serial_number=serial_number, version=2) + self.assertEqual(bom.uuid, serial_number) + self.assertEqual(bom.get_urn_uuid(), serial_number.urn) + self.assertEqual(bom.version, 2) + self.assertEqual(bom.urn(), f'urn:cdx:{serial_number}/2') + def test_bom_with_vulnerabilities(self) -> None: bom = get_bom_with_component_setuptools_with_vulnerability() self.assertTrue(bom.has_vulnerabilities()) + def test_bom_get_vulnerabilities_by_bom_ref(self) -> None: + bom = get_bom_with_component_setuptools_with_vulnerability() + vulns = bom.get_vulnerabilities_for_bom_ref(bom_ref=BomRef(value='pkg:pypi/setuptools@50.3.2?extension=tar.gz')) + self.assertEqual(len(vulns), 1) + + def test_bom_get_vulnerabilities_by_bom_ref_negative(self) -> None: + bom = get_bom_with_component_setuptools_with_vulnerability() + vulns = bom.get_vulnerabilities_for_bom_ref(bom_ref=BomRef(value='pkg:pypi/setuptools@50.3.1?extension=tar.gz')) + self.assertEqual(len(vulns), 0) + def test_bom_nested_components_issue_275(self) -> None: bom = get_bom_for_issue_275_components() self.assertIsInstance(bom.metadata.component, Component) diff --git a/tests/test_model_component.py b/tests/test_model_component.py index 78e6c4c0..541c0ba8 100644 --- a/tests/test_model_component.py +++ b/tests/test_model_component.py @@ -130,9 +130,8 @@ def test_empty_basic_component(self, mock_uuid: Mock) -> None: self.assertSetEqual(c.external_references, set()) self.assertFalse(c.properties) self.assertIsNone(c.release_notes) - self.assertEqual(0, len(c.components)) - self.assertEqual(0, len(c.get_all_nested_components())) - self.assertEqual(len(c.get_vulnerabilities()), 0) + self.assertEqual(len(c.components), 0) + self.assertEqual(len(c.get_all_nested_components(include_self=True)), 1) @patch('cyclonedx.model.bom_ref.uuid4', return_value='6f266d1c-760f-4552-ae3b-41a9b74232fa') def test_multiple_basic_components(self, mock_uuid: Mock) -> None: @@ -144,7 +143,6 @@ def test_multiple_basic_components(self, mock_uuid: Mock) -> None: self.assertEqual(c1.type, ComponentType.LIBRARY) self.assertEqual(len(c1.external_references), 0) self.assertEqual(len(c1.hashes), 0) - self.assertEqual(len(c1.get_vulnerabilities()), 0) c2 = Component( name='test2-component', version='3.2.1' @@ -154,7 +152,6 @@ def test_multiple_basic_components(self, mock_uuid: Mock) -> None: self.assertEqual(c2.type, ComponentType.LIBRARY) self.assertEqual(len(c2.external_references), 0) self.assertEqual(len(c2.hashes), 0) - self.assertEqual(len(c2.get_vulnerabilities()), 0) self.assertNotEqual(c1, c2) @@ -174,7 +171,6 @@ def test_external_references(self) -> None: self.assertEqual(c.type, ComponentType.LIBRARY) self.assertEqual(len(c.external_references), 1) self.assertEqual(len(c.hashes), 0) - self.assertEqual(len(c.get_vulnerabilities()), 0) c2 = Component( name='test2-component', version='3.2.1' @@ -184,7 +180,6 @@ def test_external_references(self) -> None: self.assertEqual(c2.type, ComponentType.LIBRARY) self.assertEqual(len(c2.external_references), 0) self.assertEqual(len(c2.hashes), 0) - self.assertEqual(len(c2.get_vulnerabilities()), 0) def test_empty_basic_component_no_version(self) -> None: c = Component( @@ -195,7 +190,6 @@ def test_empty_basic_component_no_version(self) -> None: self.assertEqual(c.type, ComponentType.LIBRARY) self.assertEqual(len(c.external_references), 0) self.assertEqual(len(c.hashes), 0) - self.assertEqual(len(c.get_vulnerabilities()), 0) def test_component_equal_1(self) -> None: c = Component(