|
16 | 16 | # Copyright (c) OWASP Foundation. All Rights Reserved. |
17 | 17 |
|
18 | 18 | import datetime |
19 | | -from decimal import Decimal |
20 | 19 | from unittest import TestCase |
21 | 20 |
|
22 | 21 | from cyclonedx.model import ( |
23 | 22 | AttachedText, |
24 | | - Copyright, |
25 | 23 | Encoding, |
26 | 24 | ExternalReference, |
27 | 25 | ExternalReferenceType, |
28 | 26 | IdentifiableAction, |
29 | 27 | Property, |
30 | 28 | XsUri, |
31 | 29 | ) |
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 |
49 | 31 | from cyclonedx.model.issue import IssueClassification, IssueType |
50 | 32 | from tests import reorder |
51 | 33 | from tests._data.models import ( |
@@ -293,224 +275,6 @@ def test_nested_components_2(self) -> None: |
293 | 275 | self.assertEqual(2, len(comp_b.get_all_nested_components(include_self=False))) |
294 | 276 |
|
295 | 277 |
|
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 | | - |
514 | 278 | class TestModelDiff(TestCase): |
515 | 279 |
|
516 | 280 | def test_no_params(self) -> None: |
|
0 commit comments