Skip to content

Commit cbb7370

Browse files
committed
refactor: compoennt evidence
Signed-off-by: Jan Kowalleck <jan.kowalleck@gmail.com>
1 parent 392e61d commit cbb7370

3 files changed

Lines changed: 13 additions & 678 deletions

File tree

tests/_data/models.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -45,26 +45,28 @@
4545
from cyclonedx.model.bom import Bom, BomMetaData
4646
from cyclonedx.model.bom_ref import BomRef
4747
from cyclonedx.model.component import (
48-
AnalysisTechnique,
49-
CallStack,
5048
Commit,
5149
Component,
52-
ComponentEvidence,
5350
ComponentScope,
5451
ComponentType,
5552
Diff,
56-
Identity,
57-
IdentityFieldType,
58-
Method,
59-
Occurrence,
6053
OmniborId,
6154
Patch,
6255
PatchClassification,
6356
Pedigree,
64-
StackFrame,
6557
Swhid,
6658
Swid,
6759
)
60+
from cyclonedx.model.component_evidence import (
61+
AnalysisTechnique,
62+
CallStack,
63+
ComponentEvidence,
64+
Identity,
65+
IdentityFieldType,
66+
Method,
67+
Occurrence,
68+
StackFrame,
69+
)
6870
from cyclonedx.model.contact import OrganizationalContact, OrganizationalEntity, PostalAddress
6971
from cyclonedx.model.crypto import (
7072
AlgorithmProperties,

tests/test_model_component.py

Lines changed: 1 addition & 237 deletions
Original file line numberDiff line numberDiff line change
@@ -16,36 +16,18 @@
1616
# Copyright (c) OWASP Foundation. All Rights Reserved.
1717

1818
import datetime
19-
from decimal import Decimal
2019
from unittest import TestCase
2120

2221
from cyclonedx.model import (
2322
AttachedText,
24-
Copyright,
2523
Encoding,
2624
ExternalReference,
2725
ExternalReferenceType,
2826
IdentifiableAction,
2927
Property,
3028
XsUri,
3129
)
32-
from cyclonedx.model.component import (
33-
AnalysisTechnique,
34-
CallStack,
35-
Commit,
36-
Component,
37-
ComponentEvidence,
38-
ComponentType,
39-
Diff,
40-
Identity,
41-
IdentityFieldType,
42-
Method,
43-
Occurrence,
44-
Patch,
45-
PatchClassification,
46-
Pedigree,
47-
StackFrame,
48-
)
30+
from cyclonedx.model.component import Commit, Component, ComponentType, Diff, Patch, PatchClassification, Pedigree
4931
from cyclonedx.model.issue import IssueClassification, IssueType
5032
from tests import reorder
5133
from tests._data.models import (
@@ -293,224 +275,6 @@ def test_nested_components_2(self) -> None:
293275
self.assertEqual(2, len(comp_b.get_all_nested_components(include_self=False)))
294276

295277

296-
class TestModelComponentEvidence(TestCase):
297-
298-
def test_no_params(self) -> None:
299-
ComponentEvidence() # Does not raise `NoPropertiesProvidedException`
300-
301-
def test_identity(self) -> None:
302-
identity = Identity(field=IdentityFieldType.NAME, confidence=Decimal('1'), concluded_value='test')
303-
ce = ComponentEvidence(identity=[identity])
304-
self.assertEqual(len(ce.identity), 1)
305-
self.assertEqual(ce.identity.pop().field, 'name')
306-
307-
def test_identity_multiple(self) -> None:
308-
identities = [
309-
Identity(field=IdentityFieldType.NAME, confidence=Decimal('1'), concluded_value='test'),
310-
Identity(field=IdentityFieldType.VERSION, confidence=Decimal('0.8'), concluded_value='1.0.0')
311-
]
312-
ce = ComponentEvidence(identity=identities)
313-
self.assertEqual(len(ce.identity), 2)
314-
315-
def test_identity_with_methods(self) -> None:
316-
"""Test identity with analysis methods"""
317-
methods = [
318-
Method(
319-
technique=AnalysisTechnique.BINARY_ANALYSIS, # Changed order to test sorting
320-
confidence=Decimal('0.9'),
321-
value='Found in binary'
322-
),
323-
Method(
324-
technique=AnalysisTechnique.SOURCE_CODE_ANALYSIS,
325-
confidence=Decimal('0.8'),
326-
value='Found in source'
327-
)
328-
]
329-
identity = Identity(field='name', confidence=Decimal('1'), methods=methods)
330-
self.assertEqual(len(identity.methods), 2)
331-
sorted_methods = sorted(methods) # Methods should be sorted by technique name
332-
self.assertEqual(list(identity.methods), sorted_methods)
333-
334-
# Verify first method
335-
method = sorted_methods[0]
336-
self.assertEqual(method.technique, AnalysisTechnique.BINARY_ANALYSIS)
337-
self.assertEqual(method.confidence, Decimal('0.9'))
338-
self.assertEqual(method.value, 'Found in binary')
339-
340-
def test_method_sorting(self) -> None:
341-
"""Test that methods are properly sorted by technique value"""
342-
methods = [
343-
Method(technique=AnalysisTechnique.SOURCE_CODE_ANALYSIS, confidence=Decimal('0.8')),
344-
Method(technique=AnalysisTechnique.BINARY_ANALYSIS, confidence=Decimal('0.9')),
345-
Method(technique=AnalysisTechnique.ATTESTATION, confidence=Decimal('1.0'))
346-
]
347-
348-
sorted_methods = sorted(methods)
349-
self.assertEqual(sorted_methods[0].technique, AnalysisTechnique.ATTESTATION)
350-
self.assertEqual(sorted_methods[1].technique, AnalysisTechnique.BINARY_ANALYSIS)
351-
self.assertEqual(sorted_methods[2].technique, AnalysisTechnique.SOURCE_CODE_ANALYSIS)
352-
353-
def test_invalid_method_technique(self) -> None:
354-
"""Test that invalid technique raises ValueError"""
355-
with self.assertRaises(ValueError):
356-
Method(technique='invalid', confidence=Decimal('0.5'))
357-
358-
def test_invalid_method_confidence(self) -> None:
359-
"""Test that invalid confidence raises ValueError"""
360-
with self.assertRaises(ValueError):
361-
Method(technique=AnalysisTechnique.FILENAME, confidence=Decimal('1.5'))
362-
363-
def test_occurrences(self) -> None:
364-
occurrence = Occurrence(location='/path/to/file', line=42)
365-
ce = ComponentEvidence(occurrences=[occurrence])
366-
self.assertEqual(len(ce.occurrences), 1)
367-
self.assertEqual(ce.occurrences.pop().line, 42)
368-
369-
def test_stackframe(self) -> None:
370-
# Test StackFrame with required fields
371-
frame = StackFrame(
372-
package='com.example',
373-
module='app',
374-
function='main',
375-
parameters=['arg1', 'arg2'],
376-
line=1,
377-
column=10,
378-
full_filename='/path/to/file.py'
379-
)
380-
self.assertEqual(frame.package, 'com.example')
381-
self.assertEqual(frame.module, 'app')
382-
self.assertEqual(frame.function, 'main')
383-
self.assertEqual(len(frame.parameters), 2)
384-
self.assertEqual(frame.line, 1)
385-
self.assertEqual(frame.column, 10)
386-
self.assertEqual(frame.full_filename, '/path/to/file.py')
387-
388-
def test_stackframe_module_required(self) -> None:
389-
"""Test that module is the only required field"""
390-
frame = StackFrame(module='app') # Only mandatory field
391-
self.assertEqual(frame.module, 'app')
392-
self.assertIsNone(frame.package)
393-
self.assertIsNone(frame.function)
394-
self.assertEqual(len(frame.parameters), 0)
395-
self.assertIsNone(frame.line)
396-
self.assertIsNone(frame.column)
397-
self.assertIsNone(frame.full_filename)
398-
399-
def test_stackframe_without_module(self) -> None:
400-
"""Test that omitting module raises TypeError"""
401-
with self.assertRaises(TypeError):
402-
StackFrame() # Should raise TypeError for missing module
403-
404-
with self.assertRaises(TypeError):
405-
StackFrame(package='com.example') # Should raise TypeError for missing module
406-
407-
def test_stackframe_with_none_module(self) -> None:
408-
"""Test that setting module as None raises TypeError"""
409-
with self.assertRaises(TypeError):
410-
StackFrame(module=None) # Should raise TypeError for None module
411-
412-
def test_callstack(self) -> None:
413-
frame = StackFrame(
414-
package='com.example',
415-
module='app',
416-
function='main'
417-
)
418-
stack = CallStack(frames=[frame])
419-
ce = ComponentEvidence(callstack=stack)
420-
self.assertIsNotNone(ce.callstack)
421-
self.assertEqual(len(ce.callstack.frames), 1)
422-
423-
def test_licenses(self) -> None:
424-
from cyclonedx.model.license import DisjunctiveLicense
425-
license = DisjunctiveLicense(id='MIT')
426-
ce = ComponentEvidence(licenses=[license])
427-
self.assertEqual(len(ce.licenses), 1)
428-
429-
def test_copyright(self) -> None:
430-
copyright = Copyright(text='(c) 2023')
431-
ce = ComponentEvidence(copyright=[copyright])
432-
self.assertEqual(len(ce.copyright), 1)
433-
self.assertEqual(ce.copyright.pop().text, '(c) 2023')
434-
435-
def test_full_evidence(self) -> None:
436-
# Test with all fields populated
437-
identity = Identity(field=IdentityFieldType.NAME, confidence=Decimal('1'), concluded_value='test')
438-
occurrence = Occurrence(location='/path/to/file', line=42)
439-
frame = StackFrame(module='app', function='main', line=1)
440-
stack = CallStack(frames=[frame])
441-
from cyclonedx.model.license import DisjunctiveLicense
442-
license = DisjunctiveLicense(id='MIT')
443-
copyright = Copyright(text='(c) 2023')
444-
445-
ce = ComponentEvidence(
446-
identity=[identity],
447-
occurrences=[occurrence],
448-
callstack=stack,
449-
licenses=[license],
450-
copyright=[copyright]
451-
)
452-
453-
self.assertEqual(len(ce.identity), 1)
454-
self.assertEqual(len(ce.occurrences), 1)
455-
self.assertIsNotNone(ce.callstack)
456-
self.assertEqual(len(ce.callstack.frames), 1)
457-
self.assertEqual(len(ce.licenses), 1)
458-
self.assertEqual(len(ce.copyright), 1)
459-
460-
def test_full_evidence_with_complete_stack(self) -> None:
461-
identity = Identity(field=IdentityFieldType.NAME, confidence=Decimal('1'), concluded_value='test')
462-
occurrence = Occurrence(location='/path/to/file', line=42)
463-
464-
frame = StackFrame(
465-
package='com.example',
466-
module='app',
467-
function='main',
468-
parameters=['arg1', 'arg2'],
469-
line=1,
470-
column=10,
471-
full_filename='/path/to/file.py'
472-
)
473-
stack = CallStack(frames=[frame])
474-
475-
from cyclonedx.model.license import DisjunctiveLicense
476-
license = DisjunctiveLicense(id='MIT')
477-
copyright = Copyright(text='(c) 2023')
478-
479-
ce = ComponentEvidence(
480-
identity=[identity],
481-
occurrences=[occurrence],
482-
callstack=stack,
483-
licenses=[license],
484-
copyright=[copyright]
485-
)
486-
487-
self.assertEqual(len(ce.identity), 1)
488-
self.assertEqual(len(ce.occurrences), 1)
489-
self.assertIsNotNone(ce.callstack)
490-
self.assertEqual(len(ce.callstack.frames), 1)
491-
self.assertEqual(ce.callstack.frames.pop().package, 'com.example')
492-
self.assertEqual(len(ce.licenses), 1)
493-
self.assertEqual(len(ce.copyright), 1)
494-
495-
def test_same_1(self) -> None:
496-
ce_1 = ComponentEvidence(copyright=[Copyright(text='Commercial')])
497-
ce_2 = ComponentEvidence(copyright=[Copyright(text='Commercial')])
498-
self.assertEqual(hash(ce_1), hash(ce_2))
499-
self.assertTrue(ce_1 == ce_2)
500-
501-
def test_same_2(self) -> None:
502-
ce_1 = ComponentEvidence(copyright=[Copyright(text='Commercial'), Copyright(text='Commercial 2')])
503-
ce_2 = ComponentEvidence(copyright=[Copyright(text='Commercial 2'), Copyright(text='Commercial')])
504-
self.assertEqual(hash(ce_1), hash(ce_2))
505-
self.assertTrue(ce_1 == ce_2)
506-
507-
def test_not_same_1(self) -> None:
508-
ce_1 = ComponentEvidence(copyright=[Copyright(text='Commercial')])
509-
ce_2 = ComponentEvidence(copyright=[Copyright(text='Commercial 2')])
510-
self.assertNotEqual(hash(ce_1), hash(ce_2))
511-
self.assertFalse(ce_1 == ce_2)
512-
513-
514278
class TestModelDiff(TestCase):
515279

516280
def test_no_params(self) -> None:

0 commit comments

Comments
 (0)