Extend the SPDX v2 model to support referencing extracted licensing information, which is either not in the SPDX license list or can't be expressed by the SPDX-compliant license expression. Cover the new functionality by unit tests. Signed-off-by: Tomáš Hozza <thozza@redhat.com>
744 lines
26 KiB
Python
744 lines
26 KiB
Python
# pylint: disable=protected-access
|
|
import hashlib
|
|
import json
|
|
from datetime import datetime
|
|
|
|
import jsonschema
|
|
import pytest
|
|
|
|
from osbuild.util.sbom.spdx2.model import (
|
|
CATEGORY_TO_REPOSITORY_TYPE,
|
|
Checksum,
|
|
ChecksumAlgorithm,
|
|
CreationInfo,
|
|
Creator,
|
|
CreatorType,
|
|
Document,
|
|
EntityWithSpdxId,
|
|
ExternalPackageRef,
|
|
ExternalPackageRefCategory,
|
|
ExtractedLicensingInfo,
|
|
NoAssertionValue,
|
|
NoneValue,
|
|
Package,
|
|
Relationship,
|
|
RelationshipType,
|
|
datetime_to_iso8601,
|
|
generate_license_id,
|
|
normalize_name_for_license_id,
|
|
)
|
|
|
|
zoneinfo = pytest.importorskip("zoneinfo")
|
|
|
|
|
|
def test_creator_type_str():
|
|
assert str(CreatorType.PERSON) == "Person"
|
|
assert str(CreatorType.ORGANIZATION) == "Organization"
|
|
assert str(CreatorType.TOOL) == "Tool"
|
|
|
|
|
|
@pytest.mark.parametrize("test_object,expected_str", (
|
|
(
|
|
Creator(CreatorType.TOOL, "Sample-Tool-123"),
|
|
"Tool: Sample-Tool-123"
|
|
),
|
|
(
|
|
Creator(CreatorType.ORGANIZATION, "Sample Organization"),
|
|
"Organization: Sample Organization"
|
|
),
|
|
(
|
|
Creator(CreatorType.ORGANIZATION, "Sample Organization", "email@example.com"),
|
|
"Organization: Sample Organization (email@example.com)"
|
|
),
|
|
(
|
|
Creator(CreatorType.PERSON, "John Foo"),
|
|
"Person: John Foo"
|
|
),
|
|
(
|
|
Creator(CreatorType.PERSON, "John Foo", "email@example.com"),
|
|
"Person: John Foo (email@example.com)"
|
|
)
|
|
))
|
|
def test_creator_str(test_object, expected_str):
|
|
assert str(test_object) == expected_str
|
|
|
|
|
|
@pytest.mark.parametrize("test_spdx_id,error", (
|
|
("SPDXRef-DOCUMENT", False),
|
|
("SPDXRef-package-1.2.3", False),
|
|
("SPDXRef-package-1.2.3-0ec6114d-8d46-4553-a310-4df502c29082", False),
|
|
("", True),
|
|
("SPDXRef-", True),
|
|
("SPDxRef-DOCUMENT", True),
|
|
("SPDXRef-createrepo_c-1.2.3-1", True)
|
|
))
|
|
def test_entity_with_spdx_id(test_spdx_id, error):
|
|
if error:
|
|
with pytest.raises(ValueError):
|
|
_ = EntityWithSpdxId(test_spdx_id)
|
|
else:
|
|
_ = EntityWithSpdxId(test_spdx_id)
|
|
|
|
|
|
@pytest.mark.parametrize("test_date,expected_str", (
|
|
(datetime(2024, 11, 15, 14, 33, tzinfo=zoneinfo.ZoneInfo("UTC")), "2024-11-15T14:33:00Z"),
|
|
(datetime(2024, 11, 15, 14, 33, 59, tzinfo=zoneinfo.ZoneInfo("UTC")), "2024-11-15T14:33:59Z"),
|
|
(datetime(2024, 11, 15, 14, 33, 59, 123456, tzinfo=zoneinfo.ZoneInfo("UTC")), "2024-11-15T14:33:59Z"),
|
|
(datetime(2024, 11, 15, 14, 33, tzinfo=zoneinfo.ZoneInfo("Europe/Prague")), "2024-11-15T13:33:00Z"),
|
|
(datetime(2024, 11, 15, 14, 33, 59, tzinfo=zoneinfo.ZoneInfo("Europe/Prague")), "2024-11-15T13:33:59Z")
|
|
))
|
|
def test_datetime_to_iso8601(test_date, expected_str):
|
|
assert datetime_to_iso8601(test_date) == expected_str
|
|
|
|
|
|
@pytest.mark.parametrize("test_case", (
|
|
{
|
|
"instance_args": {
|
|
"spdx_version": "SPDX-2.3",
|
|
"spdx_id": "SPDXRef-DOCUMENT",
|
|
"name": "Sample-Document",
|
|
"document_namespace": "https://example.com",
|
|
"creators": [Creator(CreatorType.TOOL, "Sample-Tool-123")],
|
|
"created": datetime(2024, 11, 15, 14, 33, 59, tzinfo=zoneinfo.ZoneInfo("Europe/Prague")),
|
|
"data_license": "Public Domain"
|
|
},
|
|
"expected": {
|
|
"spdxVersion": "SPDX-2.3",
|
|
"SPDXID": "SPDXRef-DOCUMENT",
|
|
"name": "Sample-Document",
|
|
"dataLicense": "Public Domain",
|
|
"documentNamespace": "https://example.com",
|
|
"creationInfo": {
|
|
"created": "2024-11-15T13:33:59Z",
|
|
"creators": [
|
|
"Tool: Sample-Tool-123"
|
|
]
|
|
}
|
|
},
|
|
},
|
|
{
|
|
"instance_args": {
|
|
"spdx_version": "SPDX-2.3",
|
|
"spdx_id": "SPDXRef-DOCUMENT",
|
|
"name": "Sample-Document",
|
|
"document_namespace": "https://example.com",
|
|
"creators": [Creator(CreatorType.TOOL, "Sample-Tool-123")],
|
|
"created": datetime(2024, 11, 15, 14, 33, 59, tzinfo=zoneinfo.ZoneInfo("Europe/Prague")),
|
|
},
|
|
"expected": {
|
|
"spdxVersion": "SPDX-2.3",
|
|
"SPDXID": "SPDXRef-DOCUMENT",
|
|
"name": "Sample-Document",
|
|
"dataLicense": "CC0-1.0",
|
|
"documentNamespace": "https://example.com",
|
|
"creationInfo": {
|
|
"created": "2024-11-15T13:33:59Z",
|
|
"creators": [
|
|
"Tool: Sample-Tool-123"
|
|
]
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"instance_args": {
|
|
"spdx_version": "SPDX-2.3",
|
|
"spdx_id": "DOCUMENT",
|
|
"name": "Sample-Document",
|
|
"document_namespace": "https://example.com",
|
|
"creators": [Creator(CreatorType.TOOL, "Sample-Tool-123")],
|
|
"created": datetime(2024, 11, 15, 14, 33, 59, tzinfo=zoneinfo.ZoneInfo("Europe/Prague")),
|
|
},
|
|
"error": True
|
|
},
|
|
{
|
|
"instance_args": {
|
|
"spdx_version": "SPDX-2.3",
|
|
"spdx_id": "SPDXRef-YOLO",
|
|
"name": "Sample-Document",
|
|
"document_namespace": "https://example.com",
|
|
"creators": [Creator(CreatorType.TOOL, "Sample-Tool-123")],
|
|
"created": datetime(2024, 11, 15, 14, 33, 59, tzinfo=zoneinfo.ZoneInfo("Europe/Prague")),
|
|
},
|
|
"error": True
|
|
}
|
|
))
|
|
def test_creation_info_to_dict(test_case):
|
|
if test_case.get("error", False):
|
|
with pytest.raises(ValueError):
|
|
CreationInfo(**test_case["instance_args"])
|
|
else:
|
|
ci = CreationInfo(**test_case["instance_args"])
|
|
assert ci.to_dict() == test_case["expected"]
|
|
|
|
|
|
def test_no_assertion_value_str():
|
|
assert str(NoAssertionValue()) == "NOASSERTION"
|
|
|
|
|
|
def test_none_value_str():
|
|
assert str(NoneValue()) == "NONE"
|
|
|
|
|
|
def test_external_package_ref_category_str():
|
|
assert str(ExternalPackageRefCategory.SECURITY) == "SECURITY"
|
|
assert str(ExternalPackageRefCategory.PACKAGE_MANAGER) == "PACKAGE-MANAGER"
|
|
assert str(ExternalPackageRefCategory.PERSISTENT_ID) == "PERSISTENT-ID"
|
|
assert str(ExternalPackageRefCategory.OTHER) == "OTHER"
|
|
|
|
|
|
def test_external_package_ref_cat_type_combinations():
|
|
for category, types in CATEGORY_TO_REPOSITORY_TYPE.items():
|
|
if category == ExternalPackageRefCategory.OTHER:
|
|
_ = ExternalPackageRef(category, "made-up", "https://example.com")
|
|
_ = ExternalPackageRef(category, "yolo-type", "https://example.com")
|
|
continue
|
|
|
|
for ref_type in types:
|
|
_ = ExternalPackageRef(category, ref_type, "https://example.com")
|
|
|
|
with pytest.raises(ValueError):
|
|
_ = ExternalPackageRef(category, "made-up", "https://example.com")
|
|
|
|
|
|
def test_external_package_ref_to_dict():
|
|
ref = ExternalPackageRef(ExternalPackageRefCategory.PACKAGE_MANAGER, "purl", "https://example.com")
|
|
assert ref.to_dict() == {
|
|
"referenceCategory": "PACKAGE-MANAGER",
|
|
"referenceType": "purl",
|
|
"referenceLocator": "https://example.com"
|
|
}
|
|
|
|
|
|
def test_checksum_algorithm_str():
|
|
assert str(ChecksumAlgorithm.SHA1) == "SHA1"
|
|
assert str(ChecksumAlgorithm.SHA224) == "SHA224"
|
|
assert str(ChecksumAlgorithm.SHA256) == "SHA256"
|
|
assert str(ChecksumAlgorithm.SHA384) == "SHA384"
|
|
assert str(ChecksumAlgorithm.SHA512) == "SHA512"
|
|
assert str(ChecksumAlgorithm.SHA3_256) == "SHA3-256"
|
|
assert str(ChecksumAlgorithm.SHA3_384) == "SHA3-384"
|
|
assert str(ChecksumAlgorithm.SHA3_512) == "SHA3-512"
|
|
assert str(ChecksumAlgorithm.BLAKE2b_256) == "BLAKE2b-256"
|
|
assert str(ChecksumAlgorithm.BLAKE2b_384) == "BLAKE2b-384"
|
|
assert str(ChecksumAlgorithm.BLAKE2b_512) == "BLAKE2b-512"
|
|
assert str(ChecksumAlgorithm.BLAKE3) == "BLAKE3"
|
|
assert str(ChecksumAlgorithm.MD2) == "MD2"
|
|
assert str(ChecksumAlgorithm.MD4) == "MD4"
|
|
assert str(ChecksumAlgorithm.MD5) == "MD5"
|
|
assert str(ChecksumAlgorithm.MD6) == "MD6"
|
|
assert str(ChecksumAlgorithm.ADLER32) == "ADLER32"
|
|
|
|
|
|
def test_checksum_to_dict():
|
|
assert Checksum(ChecksumAlgorithm.SHA1, "123456").to_dict() == {
|
|
"algorithm": "SHA1",
|
|
"checksumValue": "123456"
|
|
}
|
|
|
|
|
|
@pytest.mark.parametrize("name,expected_str", (
|
|
("MIT", "MIT"),
|
|
("MIT-2.0", "MIT-2.0"),
|
|
("My License", "My-License"),
|
|
("%$MIT)) ((@\",))", "-MIT-"),
|
|
("MIT.and more-12345", "MIT.and-more-12345"),
|
|
("......-----------.......---------", ".-.-"),
|
|
("", ""))
|
|
)
|
|
def test_normalize_name_for_license_id(name, expected_str):
|
|
assert normalize_name_for_license_id(name) == expected_str
|
|
|
|
|
|
@pytest.mark.parametrize("extracted_text,name", (
|
|
("MIT", "test"),
|
|
("MIT", None),
|
|
("MIT and GPLv2", "MIT and GPLv2"))
|
|
)
|
|
def test_generate_license_id(extracted_text, name):
|
|
extracted_text_hash = hashlib.sha256(extracted_text.encode()).hexdigest()
|
|
expect = f"LicenseRef-{normalize_name_for_license_id(name)}-{extracted_text_hash}" \
|
|
if name else f"LicenseRef-{extracted_text_hash}"
|
|
assert generate_license_id(extracted_text, name) == expect
|
|
|
|
|
|
# The __str__ just returns the license ID, which is generated by the _generate_license_id method
|
|
# tested above. So, the test case looks the same for now.
|
|
@pytest.mark.parametrize("extracted_text,name", (
|
|
("MIT", "test"),
|
|
("MIT", None),
|
|
("MIT and GPLv2", "MIT and GPLv2"))
|
|
)
|
|
def test_extracted_licensing_info___str__(extracted_text, name):
|
|
extracted_text_hash = hashlib.sha256(extracted_text.encode()).hexdigest()
|
|
expect = f"LicenseRef-{normalize_name_for_license_id(name)}-{extracted_text_hash}" \
|
|
if name else f"LicenseRef-{extracted_text_hash}"
|
|
assert str(ExtractedLicensingInfo(extracted_text, name)) == expect
|
|
|
|
|
|
@pytest.mark.parametrize("test_case", (
|
|
{
|
|
"instance_args": {
|
|
"extracted_text": "MIT and GPLv2",
|
|
},
|
|
"expected": {
|
|
"licenseId": "LicenseRef-7805d4303e817ddd5f86dcf6541af84daac5c5b4a8ad1fb4cd14def8a4ca3d13",
|
|
"extractedText": "MIT and GPLv2"
|
|
}
|
|
},
|
|
{
|
|
"instance_args": {
|
|
"extracted_text": "MIT and GPLv2",
|
|
"name": "MIT and GPLv2",
|
|
},
|
|
"expected": {
|
|
"licenseId": "LicenseRef-MIT-and-GPLv2-7805d4303e817ddd5f86dcf6541af84daac5c5b4a8ad1fb4cd14def8a4ca3d13",
|
|
"extractedText": "MIT and GPLv2",
|
|
"name": "MIT and GPLv2"
|
|
}
|
|
}
|
|
))
|
|
def test_extracted_licensing_info_to_dict(test_case):
|
|
p = ExtractedLicensingInfo(**test_case["instance_args"])
|
|
assert p.to_dict() == test_case["expected"]
|
|
|
|
|
|
@pytest.mark.parametrize("test_case", (
|
|
{
|
|
"instance_args": {
|
|
"spdx_id": "SPDXRef-package-1.2.3",
|
|
"name": "package",
|
|
"download_location": "https://example.org/package-1.2.3.rpm"
|
|
},
|
|
"expected": {
|
|
"SPDXID": "SPDXRef-package-1.2.3",
|
|
"name": "package",
|
|
"downloadLocation": "https://example.org/package-1.2.3.rpm"
|
|
}
|
|
},
|
|
{
|
|
"instance_args": {
|
|
"spdx_id": "SPDXRef-package-1.2.3",
|
|
"name": "package",
|
|
"download_location": NoAssertionValue(),
|
|
"files_analyzed": True
|
|
},
|
|
"expected": {
|
|
"SPDXID": "SPDXRef-package-1.2.3",
|
|
"name": "package",
|
|
"downloadLocation": "NOASSERTION",
|
|
"filesAnalyzed": True
|
|
}
|
|
},
|
|
{
|
|
"instance_args": {
|
|
"spdx_id": "SPDXRef-package-1.2.3",
|
|
"name": "package",
|
|
"download_location": NoneValue(),
|
|
"files_analyzed": False,
|
|
"checksums": [
|
|
Checksum(ChecksumAlgorithm.SHA256, "123456")
|
|
],
|
|
"version": "1.2.3",
|
|
"homepage": "https://example.org/package",
|
|
"source_info": "https://example.org/package-1.2.3.src.rpm",
|
|
"license_declared": "MIT",
|
|
"summary": "A sample package",
|
|
"description": "A sample package description",
|
|
"external_references": [
|
|
ExternalPackageRef(
|
|
ExternalPackageRefCategory.PACKAGE_MANAGER,
|
|
"purl",
|
|
"pkg:rpm:/example/package@1.2.3-1?arch=x86_64"
|
|
)
|
|
],
|
|
"built_date": datetime(2024, 11, 15, 14, 33, 59, tzinfo=zoneinfo.ZoneInfo("Europe/Prague"))
|
|
},
|
|
"expected": {
|
|
"SPDXID": "SPDXRef-package-1.2.3",
|
|
"name": "package",
|
|
"downloadLocation": "NONE",
|
|
"filesAnalyzed": False,
|
|
"checksums": [
|
|
{
|
|
"algorithm": "SHA256",
|
|
"checksumValue": "123456"
|
|
}
|
|
],
|
|
"versionInfo": "1.2.3",
|
|
"homepage": "https://example.org/package",
|
|
"sourceInfo": "https://example.org/package-1.2.3.src.rpm",
|
|
"licenseDeclared": "MIT",
|
|
"summary": "A sample package",
|
|
"description": "A sample package description",
|
|
"externalRefs": [
|
|
{
|
|
"referenceCategory": "PACKAGE-MANAGER",
|
|
"referenceType": "purl",
|
|
"referenceLocator": "pkg:rpm:/example/package@1.2.3-1?arch=x86_64"
|
|
}
|
|
],
|
|
"builtDate": "2024-11-15T13:33:59Z"
|
|
}
|
|
},
|
|
# Test with ExtractedLicensingInfo
|
|
{
|
|
"instance_args": {
|
|
"spdx_id": "SPDXRef-package-1.2.3",
|
|
"name": "package",
|
|
"download_location": NoneValue(),
|
|
"files_analyzed": False,
|
|
"checksums": [
|
|
Checksum(ChecksumAlgorithm.SHA256, "123456")
|
|
],
|
|
"version": "1.2.3",
|
|
"homepage": "https://example.org/package",
|
|
"source_info": "https://example.org/package-1.2.3.src.rpm",
|
|
"license_declared": ExtractedLicensingInfo("MIT and GPLv2"),
|
|
"summary": "A sample package",
|
|
"description": "A sample package description",
|
|
"external_references": [
|
|
ExternalPackageRef(
|
|
ExternalPackageRefCategory.PACKAGE_MANAGER,
|
|
"purl",
|
|
"pkg:rpm:/example/package@1.2.3-1?arch=x86_64"
|
|
)
|
|
],
|
|
"built_date": datetime(2024, 11, 15, 14, 33, 59, tzinfo=zoneinfo.ZoneInfo("Europe/Prague"))
|
|
},
|
|
"expected": {
|
|
"SPDXID": "SPDXRef-package-1.2.3",
|
|
"name": "package",
|
|
"downloadLocation": "NONE",
|
|
"filesAnalyzed": False,
|
|
"checksums": [
|
|
{
|
|
"algorithm": "SHA256",
|
|
"checksumValue": "123456"
|
|
}
|
|
],
|
|
"versionInfo": "1.2.3",
|
|
"homepage": "https://example.org/package",
|
|
"sourceInfo": "https://example.org/package-1.2.3.src.rpm",
|
|
"licenseDeclared": "LicenseRef-7805d4303e817ddd5f86dcf6541af84daac5c5b4a8ad1fb4cd14def8a4ca3d13",
|
|
"summary": "A sample package",
|
|
"description": "A sample package description",
|
|
"externalRefs": [
|
|
{
|
|
"referenceCategory": "PACKAGE-MANAGER",
|
|
"referenceType": "purl",
|
|
"referenceLocator": "pkg:rpm:/example/package@1.2.3-1?arch=x86_64"
|
|
}
|
|
],
|
|
"builtDate": "2024-11-15T13:33:59Z"
|
|
}
|
|
}
|
|
))
|
|
def test_package_to_dict(test_case):
|
|
p = Package(**test_case["instance_args"])
|
|
assert p.to_dict() == test_case["expected"]
|
|
|
|
|
|
def test_relationship_type_str():
|
|
assert str(RelationshipType.DESCRIBES) == "DESCRIBES"
|
|
assert str(RelationshipType.DEPENDS_ON) == "DEPENDS_ON"
|
|
assert str(RelationshipType.OPTIONAL_DEPENDENCY_OF) == "OPTIONAL_DEPENDENCY_OF"
|
|
|
|
|
|
@pytest.mark.parametrize("test_case", (
|
|
{
|
|
"instance_args": {
|
|
"spdx_element_id": "SPDXRef-packageA-1.2.3",
|
|
"relationship_type": RelationshipType.DEPENDS_ON,
|
|
"related_spdx_element_id": "SPDXRef-packageB-3.2.1"
|
|
},
|
|
"expected": {
|
|
"spdxElementId": "SPDXRef-packageA-1.2.3",
|
|
"relationshipType": "DEPENDS_ON",
|
|
"relatedSpdxElement": "SPDXRef-packageB-3.2.1"
|
|
}
|
|
},
|
|
{
|
|
"instance_args": {
|
|
"spdx_element_id": "SPDXRef-DOCUMENT",
|
|
"relationship_type": RelationshipType.DESCRIBES,
|
|
"related_spdx_element_id": "SPDXRef-packageB-3.2.1",
|
|
"comment": "This document describes package B"
|
|
},
|
|
"expected": {
|
|
"spdxElementId": "SPDXRef-DOCUMENT",
|
|
"relationshipType": "DESCRIBES",
|
|
"relatedSpdxElement": "SPDXRef-packageB-3.2.1",
|
|
"comment": "This document describes package B"
|
|
}
|
|
},
|
|
))
|
|
def test_relationship_to_dict(test_case):
|
|
r = Relationship(**test_case["instance_args"])
|
|
assert r.to_dict() == test_case["expected"]
|
|
|
|
|
|
@pytest.mark.parametrize("test_case", (
|
|
{
|
|
"instance_args": {
|
|
"creation_info": CreationInfo(
|
|
"SPDX-2.3",
|
|
"SPDXRef-DOCUMENT",
|
|
"Sample-Document",
|
|
"https://example.com",
|
|
[Creator(CreatorType.TOOL, "Sample-Tool-123")],
|
|
datetime(2024, 11, 15, 14, 33, 59, tzinfo=zoneinfo.ZoneInfo("Europe/Prague")),
|
|
"Public Domain"
|
|
)
|
|
},
|
|
"expected": {
|
|
"spdxVersion": "SPDX-2.3",
|
|
"SPDXID": "SPDXRef-DOCUMENT",
|
|
"name": "Sample-Document",
|
|
"dataLicense": "Public Domain",
|
|
"documentNamespace": "https://example.com",
|
|
"creationInfo": {
|
|
"created": "2024-11-15T13:33:59Z",
|
|
"creators": [
|
|
"Tool: Sample-Tool-123"
|
|
]
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"instance_args": {
|
|
"creation_info": CreationInfo(
|
|
"SPDX-2.3",
|
|
"SPDXRef-DOCUMENT",
|
|
"Sample-Document",
|
|
"https://example.com",
|
|
[Creator(CreatorType.TOOL, "Sample-Tool-123")],
|
|
datetime(2024, 11, 15, 14, 33, 59, tzinfo=zoneinfo.ZoneInfo("Europe/Prague")),
|
|
"Public Domain"
|
|
),
|
|
"packages": [
|
|
Package(
|
|
"SPDXRef-packageA-1.2.3",
|
|
"package",
|
|
"https://example.org/packageA-1.2.3.rpm"
|
|
),
|
|
Package(
|
|
"SPDXRef-packageB-3.2.1",
|
|
"package",
|
|
"https://example.org/packageB-3.2.1.rpm"
|
|
),
|
|
],
|
|
"relationships": [
|
|
Relationship(
|
|
"SPDXRef-DOCUMENT",
|
|
RelationshipType.DESCRIBES,
|
|
"SPDXRef-packageA-1.2.3"
|
|
),
|
|
Relationship(
|
|
"SPDXRef-DOCUMENT",
|
|
RelationshipType.DESCRIBES,
|
|
"SPDXRef-packageB-3.2.1"
|
|
),
|
|
Relationship(
|
|
"SPDXRef-packageA-1.2.3",
|
|
RelationshipType.DEPENDS_ON,
|
|
"SPDXRef-packageB-3.2.1"
|
|
)
|
|
]
|
|
},
|
|
"expected": {
|
|
"spdxVersion": "SPDX-2.3",
|
|
"SPDXID": "SPDXRef-DOCUMENT",
|
|
"name": "Sample-Document",
|
|
"dataLicense": "Public Domain",
|
|
"documentNamespace": "https://example.com",
|
|
"creationInfo": {
|
|
"created": "2024-11-15T13:33:59Z",
|
|
"creators": [
|
|
"Tool: Sample-Tool-123"
|
|
]
|
|
},
|
|
"packages": [
|
|
{
|
|
"SPDXID": "SPDXRef-packageA-1.2.3",
|
|
"name": "package",
|
|
"downloadLocation": "https://example.org/packageA-1.2.3.rpm"
|
|
},
|
|
{
|
|
"SPDXID": "SPDXRef-packageB-3.2.1",
|
|
"name": "package",
|
|
"downloadLocation": "https://example.org/packageB-3.2.1.rpm"
|
|
}
|
|
],
|
|
"relationships": [
|
|
{
|
|
"spdxElementId": "SPDXRef-DOCUMENT",
|
|
"relationshipType": "DESCRIBES",
|
|
"relatedSpdxElement": "SPDXRef-packageA-1.2.3"
|
|
},
|
|
{
|
|
"spdxElementId": "SPDXRef-DOCUMENT",
|
|
"relationshipType": "DESCRIBES",
|
|
"relatedSpdxElement": "SPDXRef-packageB-3.2.1"
|
|
},
|
|
{
|
|
"spdxElementId": "SPDXRef-packageA-1.2.3",
|
|
"relationshipType": "DEPENDS_ON",
|
|
"relatedSpdxElement": "SPDXRef-packageB-3.2.1"
|
|
}
|
|
]
|
|
}
|
|
},
|
|
# Test with ExtractedLicensingInfo
|
|
{
|
|
"instance_args": {
|
|
"creation_info": CreationInfo(
|
|
"SPDX-2.3",
|
|
"SPDXRef-DOCUMENT",
|
|
"Sample-Document",
|
|
"https://example.com",
|
|
[Creator(CreatorType.TOOL, "Sample-Tool-123")],
|
|
datetime(2024, 11, 15, 14, 33, 59, tzinfo=zoneinfo.ZoneInfo("Europe/Prague")),
|
|
"Public Domain"
|
|
),
|
|
"packages": [
|
|
Package(
|
|
"SPDXRef-packageA-1.2.3",
|
|
"package",
|
|
"https://example.org/packageA-1.2.3.rpm"
|
|
),
|
|
Package(
|
|
"SPDXRef-packageB-3.2.1",
|
|
"package",
|
|
"https://example.org/packageB-3.2.1.rpm"
|
|
),
|
|
Package(
|
|
"SPDXRef-package-1.2.3",
|
|
"package",
|
|
NoneValue(),
|
|
"1.2.3",
|
|
False,
|
|
[Checksum(ChecksumAlgorithm.SHA256, "123456")],
|
|
"https://example.org/package",
|
|
"https://example.org/package-1.2.3.src.rpm",
|
|
ExtractedLicensingInfo("MIT and GPLv2"),
|
|
"A sample package",
|
|
"A sample package description",
|
|
[
|
|
ExternalPackageRef(
|
|
ExternalPackageRefCategory.PACKAGE_MANAGER,
|
|
"purl",
|
|
"pkg:rpm:/example/package@1.2.3-1?arch=x86_64"
|
|
)
|
|
],
|
|
datetime(2024, 11, 15, 14, 33, 59, tzinfo=zoneinfo.ZoneInfo("Europe/Prague"))
|
|
),
|
|
],
|
|
"relationships": [
|
|
Relationship(
|
|
"SPDXRef-DOCUMENT",
|
|
RelationshipType.DESCRIBES,
|
|
"SPDXRef-packageA-1.2.3"
|
|
),
|
|
Relationship(
|
|
"SPDXRef-DOCUMENT",
|
|
RelationshipType.DESCRIBES,
|
|
"SPDXRef-packageB-3.2.1"
|
|
),
|
|
Relationship(
|
|
"SPDXRef-packageA-1.2.3",
|
|
RelationshipType.DEPENDS_ON,
|
|
"SPDXRef-packageB-3.2.1"
|
|
)
|
|
],
|
|
"extracted_licensing_infos": [
|
|
ExtractedLicensingInfo("MIT and GPLv2")
|
|
]
|
|
},
|
|
"expected": {
|
|
"spdxVersion": "SPDX-2.3",
|
|
"SPDXID": "SPDXRef-DOCUMENT",
|
|
"name": "Sample-Document",
|
|
"dataLicense": "Public Domain",
|
|
"documentNamespace": "https://example.com",
|
|
"creationInfo": {
|
|
"created": "2024-11-15T13:33:59Z",
|
|
"creators": [
|
|
"Tool: Sample-Tool-123"
|
|
]
|
|
},
|
|
"packages": [
|
|
{
|
|
"SPDXID": "SPDXRef-packageA-1.2.3",
|
|
"name": "package",
|
|
"downloadLocation": "https://example.org/packageA-1.2.3.rpm"
|
|
},
|
|
{
|
|
"SPDXID": "SPDXRef-packageB-3.2.1",
|
|
"name": "package",
|
|
"downloadLocation": "https://example.org/packageB-3.2.1.rpm"
|
|
},
|
|
{
|
|
"SPDXID": "SPDXRef-package-1.2.3",
|
|
"name": "package",
|
|
"downloadLocation": "NONE",
|
|
"filesAnalyzed": False,
|
|
"checksums": [
|
|
{
|
|
"algorithm": "SHA256",
|
|
"checksumValue": "123456"
|
|
}
|
|
],
|
|
"versionInfo": "1.2.3",
|
|
"homepage": "https://example.org/package",
|
|
"sourceInfo": "https://example.org/package-1.2.3.src.rpm",
|
|
"licenseDeclared": "LicenseRef-7805d4303e817ddd5f86dcf6541af84daac5c5b4a8ad1fb4cd14def8a4ca3d13",
|
|
"summary": "A sample package",
|
|
"description": "A sample package description",
|
|
"externalRefs": [
|
|
{
|
|
"referenceCategory": "PACKAGE-MANAGER",
|
|
"referenceType": "purl",
|
|
"referenceLocator": "pkg:rpm:/example/package@1.2.3-1?arch=x86_64"
|
|
}
|
|
],
|
|
"builtDate": "2024-11-15T13:33:59Z"
|
|
}
|
|
],
|
|
"relationships": [
|
|
{
|
|
"spdxElementId": "SPDXRef-DOCUMENT",
|
|
"relationshipType": "DESCRIBES",
|
|
"relatedSpdxElement": "SPDXRef-packageA-1.2.3"
|
|
},
|
|
{
|
|
"spdxElementId": "SPDXRef-DOCUMENT",
|
|
"relationshipType": "DESCRIBES",
|
|
"relatedSpdxElement": "SPDXRef-packageB-3.2.1"
|
|
},
|
|
{
|
|
"spdxElementId": "SPDXRef-packageA-1.2.3",
|
|
"relationshipType": "DEPENDS_ON",
|
|
"relatedSpdxElement": "SPDXRef-packageB-3.2.1"
|
|
}
|
|
],
|
|
"hasExtractedLicensingInfos": [
|
|
{
|
|
'extractedText': 'MIT and GPLv2',
|
|
'licenseId': 'LicenseRef-7805d4303e817ddd5f86dcf6541af84daac5c5b4a8ad1fb4cd14def8a4ca3d13',
|
|
}
|
|
]
|
|
}
|
|
}
|
|
))
|
|
def test_document_to_dict(test_case):
|
|
d = Document(**test_case["instance_args"])
|
|
assert d.to_dict() == test_case["expected"]
|
|
|
|
spdx_2_3_1_schema_file = './test/data/spdx/spdx-schema-v2.3.1.json'
|
|
with open(spdx_2_3_1_schema_file, encoding="utf-8") as f:
|
|
spdx_schema = json.load(f)
|
|
|
|
validator = jsonschema.Draft4Validator
|
|
validator.check_schema(spdx_schema)
|
|
spdx_validator = validator(spdx_schema)
|
|
spdx_validator.validate(d.to_dict())
|