diff --git a/cyclonedx/model/bom.py b/cyclonedx/model/bom.py index 65c578fb..055bbf3c 100644 --- a/cyclonedx/model/bom.py +++ b/cyclonedx/model/bom.py @@ -694,8 +694,9 @@ def validate(self) -> bool: 'One or more Components have Dependency references to Components/Services that are not known in this ' f'BOM. They are: {dependency_diff}') - # 2. if root component is set: dependencies should exist for the Component this BOM is describing - if self.metadata.component and not any(map( + # 2. if root component is set and there are other components: dependencies should exist for the Component + # this BOM is describing + if self.metadata.component and len(self.components) > 0 and not any(map( lambda d: d.ref == self.metadata.component.bom_ref and len(d.dependencies) > 0, # type: ignore[union-attr] self.dependencies )): diff --git a/tests/test_model_bom.py b/tests/test_model_bom.py index 74046a09..1c490a8a 100644 --- a/tests/test_model_bom.py +++ b/tests/test_model_bom.py @@ -14,8 +14,7 @@ # # SPDX-License-Identifier: Apache-2.0 # Copyright (c) OWASP Foundation. All Rights Reserved. - - +import warnings from typing import Callable, Tuple from unittest import TestCase from uuid import uuid4 @@ -31,6 +30,7 @@ from cyclonedx.model.license import DisjunctiveLicense from cyclonedx.model.lifecycle import LifecyclePhase, NamedLifecycle, PredefinedLifecycle from cyclonedx.model.tool import Tool +from cyclonedx.output.json import JsonV1Dot6 from tests._data.models import ( get_bom_component_licenses_invalid, get_bom_component_nested_licenses_invalid, @@ -139,6 +139,21 @@ def test_empty_bom(self) -> None: self.assertFalse(bom.services) self.assertFalse(bom.external_references) + def test_root_component_only_bom(self) -> None: + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('always') + bom = Bom(metadata=BomMetaData(component=Component(name='test', version='1.2'))) + _ = JsonV1Dot6(bom).output_as_string() + self.assertEqual(len(w), 0) + + def test_warning_missing_dependency(self) -> None: + with self.assertWarns(expected_warning=UserWarning) as w: + bom = Bom(metadata=BomMetaData(component=Component(name='root_component', version='1.2'))) + bom.components.add(Component(name='test2', version='4.2')) + _ = JsonV1Dot6(bom).output_as_string() + self.assertEqual(len(w.warnings), 1) + self.assertIn('has no defined dependencies ', str(w.warnings[0])) + def test_empty_bom_defined_serial(self) -> None: serial_number = uuid4() bom = Bom(serial_number=serial_number)