Skip to content

Commit e9652cc

Browse files
committed
Adding tests for new functionality
I don't know if this is the best way, but the tests transit the the new functionality; both serialize and deserialize.
1 parent ea90034 commit e9652cc

File tree

4 files changed

+149
-37
lines changed

4 files changed

+149
-37
lines changed

cyclonedx/model/bom.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ class BomMetaData:
6060
See the CycloneDX Schema for Bom metadata: https://cyclonedx.org/docs/1.5/#type_metadata
6161
"""
6262

63-
def __init__(self, *, tools: Optional[Union[Iterable[Tool], Dict[AnyStr, Any]]] = None,
63+
def __init__(self, *, tools: Optional[Union[Iterable[Tool], ToolsRepository]] = None,
6464
authors: Optional[Iterable[OrganizationalContact]] = None, component: Optional[Component] = None,
6565
supplier: Optional[OrganizationalEntity] = None,
6666
licenses: Optional[Iterable[License]] = None,

cyclonedx/model/tool.py

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ def __bool__(self) -> bool:
178178
return any([self._tools, self._components, self._services])
179179

180180
@property
181-
def components(self) -> Iterable[Component]:
181+
def components(self) -> SortedSet[Component]:
182182
"""
183183
Returns:
184184
A SortedSet of Components
@@ -196,7 +196,7 @@ def components(self, components: Iterable[Component]) -> None:
196196
self._components = SortedSet(components)
197197

198198
@property
199-
def services(self) -> Iterable[Service]:
199+
def services(self) -> SortedSet[Service]:
200200
"""
201201
Returns:
202202
A SortedSet of Services
@@ -240,9 +240,6 @@ def json_normalize(cls, o: ToolsRepository, *,
240240
if not any([o._tools, o.components, o.services]): # pylint: disable=protected-access
241241
return None
242242

243-
if o._tools: # pylint: disable=protected-access
244-
return [json_loads(Tool.as_json(t, view_=view)) for t in o] # type: ignore[attr-defined]
245-
246243
result = {}
247244

248245
if o.components:
@@ -253,7 +250,12 @@ def json_normalize(cls, o: ToolsRepository, *,
253250
result['services'] = [json_loads(Service.as_json(s, view_=view)) # type: ignore[attr-defined]
254251
for s in o.services]
255252

256-
return result
253+
if result:
254+
return result
255+
256+
return [json_loads(Tool.as_json(t, view_=view)) for t in o] # type: ignore[attr-defined]
257+
258+
257259

258260
@classmethod
259261
def json_denormalize(cls, o: Union[List[Dict[str, Any]], Dict[str, Any]],
@@ -291,31 +293,37 @@ def xml_normalize(cls, o: ToolsRepository, *,
291293

292294
elem = Element(element_name)
293295

294-
if o._tools: # pylint: disable=protected-access
295-
elem.extend(
296-
t.as_xml( # type: ignore[attr-defined]
297-
view_=view, as_string=False, element_name='tool', xmlns=xmlns)
298-
for t in o
299-
)
300-
301296
if o.components:
302-
c_elem = Element('components')
297+
c_elem = Element('{' + xmlns + '}' + 'components') # type: ignore[operator]
303298

304299
c_elem.extend(
305300
c.as_xml( # type: ignore[attr-defined]
306301
view_=view, as_string=False, element_name='component', xmlns=xmlns)
307302
for c in o.components
308303
)
309304

305+
elem.append(c_elem)
306+
310307
if o.services:
311-
s_elem = Element('services')
308+
s_elem = Element('{' + xmlns + '}' + 'services') # type: ignore[operator]
312309

313310
s_elem.extend(
314311
s.as_xml( # type: ignore[attr-defined]
315-
view_=view, as_string=False, element_name='services', xmlns=xmlns)
312+
view_=view, as_string=False, element_name='service', xmlns=xmlns)
316313
for s in o.services
317314
)
318315

316+
elem.append(s_elem)
317+
318+
if len(elem) > 0:
319+
return elem
320+
321+
elem.extend(
322+
t.as_xml( # type: ignore[attr-defined]
323+
view_=view, as_string=False, element_name='tool', xmlns=xmlns)
324+
for t in o
325+
)
326+
319327
return elem
320328

321329
@classmethod

tests/test_model.py

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@
4040
Note,
4141
NoteText,
4242
Property,
43-
Tool,
4443
XsUri,
4544
)
4645
from cyclonedx.model.contact import OrganizationalContact
@@ -546,22 +545,3 @@ def test_sort(self) -> None:
546545
sorted_props = sorted(props)
547546
expected_props = reorder(props, expected_order)
548547
self.assertListEqual(sorted_props, expected_props)
549-
550-
551-
class TestModelTool(TestCase):
552-
553-
def test_sort(self) -> None:
554-
# expected sort order: (vendor, name, version)
555-
expected_order = [0, 1, 2, 3, 4, 5, 6]
556-
tools = [
557-
Tool(vendor='a', name='a', version='1.0.0'),
558-
Tool(vendor='a', name='a', version='2.0.0'),
559-
Tool(vendor='a', name='b', version='1.0.0'),
560-
Tool(vendor='a', name='b'),
561-
Tool(vendor='b', name='a'),
562-
Tool(vendor='b', name='b', version='1.0.0'),
563-
Tool(name='b'),
564-
]
565-
sorted_tools = sorted(tools)
566-
expected_tools = reorder(tools, expected_order)
567-
self.assertListEqual(sorted_tools, expected_tools)

tests/test_model_tool.py

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import io
2+
from json import loads as json_loads
3+
from unittest import TestCase
4+
5+
from cyclonedx.model.bom import Bom, BomMetaData
6+
from cyclonedx.model.component import Component, ComponentType
7+
from cyclonedx.model.service import Service
8+
from cyclonedx.model.tool import Tool, ToolsRepository
9+
from cyclonedx.output.json import JsonV1Dot5
10+
from cyclonedx.output.xml import XmlV1Dot5
11+
from tests import reorder
12+
13+
14+
class TestModelTool(TestCase):
15+
16+
def test_sort(self) -> None:
17+
# expected sort order: (vendor, name, version)
18+
expected_order = [0, 1, 2, 3, 4, 5, 6]
19+
tools = [
20+
Tool(vendor='a', name='a', version='1.0.0'),
21+
Tool(vendor='a', name='a', version='2.0.0'),
22+
Tool(vendor='a', name='b', version='1.0.0'),
23+
Tool(vendor='a', name='b'),
24+
Tool(vendor='b', name='a'),
25+
Tool(vendor='b', name='b', version='1.0.0'),
26+
Tool(name='b'),
27+
]
28+
sorted_tools = sorted(tools)
29+
expected_tools = reorder(tools, expected_order)
30+
self.assertListEqual(sorted_tools, expected_tools)
31+
32+
def test_tool_with_component_and_service_load_json(self) -> None:
33+
# test_file = join(OWN_DATA_DIRECTORY, 'json', '1.5', 'tools_with_components_and_services.json')
34+
bom_json = """
35+
{
36+
"$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json",
37+
"bomFormat": "CycloneDX",
38+
"specVersion": "1.5",
39+
"serialNumber": "urn:uuid:60bd7113-edac-4277-a518-88d84aef2399",
40+
"version": 1337,
41+
"metadata": {
42+
"tools": {
43+
"components": [
44+
{
45+
"type": "application",
46+
"author": "anchore",
47+
"name": "syft",
48+
"version": "1.4.1"
49+
}
50+
],
51+
"services": [
52+
{
53+
"name": "testing-service"
54+
}
55+
]
56+
},
57+
"component": {
58+
"type": "file",
59+
"name": "Testing tools with components and services"
60+
}
61+
}
62+
}
63+
"""
64+
bom = Bom.from_json(json_loads(bom_json)) # type: ignore[attr-defined]
65+
self.assertEqual(bom.metadata.tools.components[0].type, 'application')
66+
self.assertEqual(bom.metadata.tools.components[0].name, 'syft')
67+
self.assertEqual(bom.metadata.tools.services[0].name, 'testing-service')
68+
69+
def test_tool_with_component_and_service_render_json(self) -> None:
70+
bom = Bom()
71+
bom.metadata.tools.components.add(Component(type=ComponentType.APPLICATION, author='adobe',
72+
name='test-component', version='1.2.3'))
73+
bom.metadata.tools.services.add(Service(name='test-service'))
74+
out = json_loads(JsonV1Dot5(bom).output_as_string())
75+
self.assertEqual(out['metadata']['tools']['components'][0]['name'], 'test-component')
76+
self.assertEqual(out['metadata']['tools']['services'][0]['name'], 'test-service')
77+
78+
def test_tool_with_component_and_service_load_xml(self) -> None:
79+
bom_xml = io.StringIO("""<?xml version="1.0" ?>
80+
<bom xmlns="http://cyclonedx.org/schema/bom/1.5" serialNumber="urn:uuid:f9dbe3d0-53ee-485d-96ae-1d4dce7deea2" version="1">
81+
<metadata>
82+
<timestamp>2024-06-20T22:49:51.530453+00:00</timestamp>
83+
<tools>
84+
<components>
85+
<component type="application" bom-ref="None">
86+
<author>adobe</author>
87+
<name>test-component</name>
88+
<version>1.2.3</version>
89+
</component>
90+
</components>
91+
<services>
92+
<service bom-ref="None">
93+
<name>test-service</name>
94+
</service>
95+
</services>
96+
</tools>
97+
</metadata>
98+
</bom>
99+
""")
100+
bom = Bom.from_xml(bom_xml) # type: ignore[attr-defined]
101+
self.assertEqual(bom.metadata.tools.components[0].type, 'application')
102+
self.assertEqual(bom.metadata.tools.components[0].name, 'test-component')
103+
self.assertEqual(bom.metadata.tools.services[0].name, 'test-service')
104+
105+
def test_tool_with_componet_and_service_render_xml(self) -> None:
106+
bom = Bom()
107+
bom.metadata.tools.components.add(Component(type=ComponentType.APPLICATION, author='adobe',
108+
name='test-component', version='1.2.3'))
109+
bom.metadata.tools.services.add(Service(name='test-service'))
110+
out = XmlV1Dot5(bom).output_as_string(indent=2)
111+
self.assertIn(""" <tools>
112+
<components>
113+
<component type="application" bom-ref="None">
114+
<author>adobe</author>
115+
<name>test-component</name>
116+
<version>1.2.3</version>
117+
</component>
118+
</components>
119+
<services>
120+
<service bom-ref="None">
121+
<name>test-service</name>
122+
</service>
123+
</services>
124+
</tools>""", out)

0 commit comments

Comments
 (0)