Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions cyclonedx/model/bom.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
)):
Expand Down
19 changes: 17 additions & 2 deletions tests/test_model_bom.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
Expand Down Expand Up @@ -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)
Expand Down