diff --git a/src/spdx/validation/checksum_validator.py b/src/spdx/validation/checksum_validator.py index b5f3f8078..b684ec772 100644 --- a/src/spdx/validation/checksum_validator.py +++ b/src/spdx/validation/checksum_validator.py @@ -60,7 +60,7 @@ def validate_checksum(checksum: Checksum, parent_id: str) -> List[ValidationMess length = algorithm_length[algorithm] validation_messages.append( ValidationMessage( - f"value of {algorithm} must consist of {length} hexadecimal digits, but is: {checksum.value} (length: {len(checksum.value)} digits)", + f"value of {algorithm} must consist of {length} lowercase hexadecimal digits, but is: {checksum.value} (length: {len(checksum.value)} digits)", context) ) diff --git a/src/spdx/validation/package_validator.py b/src/spdx/validation/package_validator.py index 5131d39e4..f73f42eff 100644 --- a/src/spdx/validation/package_validator.py +++ b/src/spdx/validation/package_validator.py @@ -13,7 +13,8 @@ from spdx.model.document import Document from spdx.model.package import Package -from spdx.model.relationship import RelationshipType +from spdx.model.relationship import RelationshipType, Relationship +from spdx.model.relationship_filters import filter_by_type_and_origin, filter_by_type_and_target from spdx.validation.checksum_validator import validate_checksums from spdx.validation.external_package_ref_validator import validate_external_package_refs from spdx.validation.license_expression_validator import validate_license_expression, validate_license_expressions @@ -43,23 +44,18 @@ def validate_package_within_document(package: Package, document: Document) -> Li for message in validate_spdx_id(package.spdx_id, document): validation_messages.append(ValidationMessage(message, context)) - # TODO: make test for this (https://github.com/spdx/tools-python/issues/386) if not package.files_analyzed: - package_contains_relationships = [relationship for relationship in document.relationships if - relationship.relationship_type == RelationshipType.CONTAINS and relationship.spdx_element_id == package.spdx_id] - if package_contains_relationships: - validation_messages.append( - ValidationMessage( - f"package must contain no elements if files_analyzed is False, but found {package_contains_relationships}", - context) - ) + package_contains_relationships = filter_by_type_and_origin(document.relationships, RelationshipType.CONTAINS, + package.spdx_id) + contained_in_package_relationships = filter_by_type_and_target(document.relationships, + RelationshipType.CONTAINED_BY, package.spdx_id) + + combined_relationships: List[Relationship] = package_contains_relationships + contained_in_package_relationships - contained_in_package_relationships = [relationship for relationship in document.relationships if - relationship.relationship_type == RelationshipType.CONTAINED_BY and relationship.related_spdx_element_id == package.spdx_id] - if contained_in_package_relationships: + if combined_relationships: validation_messages.append( ValidationMessage( - f"package must contain no elements if files_analyzed is False, but found {contained_in_package_relationships}", + f"package must contain no elements if files_analyzed is False, but found {combined_relationships}", context) ) @@ -83,7 +79,6 @@ def validate_package(package: Package, context: Optional[ValidationContext] = No for message in validate_url(homepage): validation_messages.append(ValidationMessage("homepage " + message, context)) - # TODO: is verification_code required if files_analyzed=True? (https://github.com/spdx/tools-python/issues/386) verification_code = package.verification_code if verification_code: if not package.files_analyzed: diff --git a/src/spdx/validation/package_verification_code_validator.py b/src/spdx/validation/package_verification_code_validator.py index 2b86520cc..de6abbec2 100644 --- a/src/spdx/validation/package_verification_code_validator.py +++ b/src/spdx/validation/package_verification_code_validator.py @@ -16,24 +16,23 @@ from spdx.validation.validation_message import ValidationMessage, ValidationContext, SpdxElementType -# TODO: make test for this (https://github.com/spdx/tools-python/issues/386) def validate_verification_code(verification_code: PackageVerificationCode, parent_id: str) -> List[ValidationMessage]: validation_messages: List[ValidationMessage] = [] context = ValidationContext(parent_id=parent_id, element_type=SpdxElementType.PACKAGE_VERIFICATION_CODE, full_element=verification_code) for file in verification_code.excluded_files: - if not file.startswith("./"): + if file.startswith("/"): validation_messages.append( ValidationMessage( - f'file name must be a relative path to the file, starting with "./", but is: {file}', context) + f'file name must not be an absolute path starting with "/", but is: {file}', context) ) value: str = verification_code.value if not re.match("^[0-9a-f]{40}$", value): validation_messages.append( ValidationMessage( - f"value of verification_code must consist of 40 hexadecimal digits, but is: {value} (length: {len(value)} digits)", + f"value of verification_code must consist of 40 lowercase hexadecimal digits, but is: {value} (length: {len(value)} digits)", context) ) diff --git a/tests/spdx/validation/test_checksum_validator.py b/tests/spdx/validation/test_checksum_validator.py index 99a127db5..7fd5f56d2 100644 --- a/tests/spdx/validation/test_checksum_validator.py +++ b/tests/spdx/validation/test_checksum_validator.py @@ -58,40 +58,42 @@ def test_valid_checksum(checksum): @pytest.mark.parametrize("checksum, expected_message", [(Checksum(ChecksumAlgorithm.SHA1, "af1eec2a1b18886c3f3cc244349d91d8"), - "value of ChecksumAlgorithm.SHA1 must consist of 40 hexadecimal digits, but is: af1eec2a1b18886c3f3cc244349d91d8 (length: 32 digits)"), + "value of ChecksumAlgorithm.SHA1 must consist of 40 lowercase hexadecimal digits, but is: af1eec2a1b18886c3f3cc244349d91d8 (length: 32 digits)"), (Checksum(ChecksumAlgorithm.SHA224, "af1eec2a1b18886c3f3cc244349d91d8"), - "value of ChecksumAlgorithm.SHA224 must consist of 56 hexadecimal digits, but is: af1eec2a1b18886c3f3cc244349d91d8 (length: 32 digits)"), + "value of ChecksumAlgorithm.SHA224 must consist of 56 lowercase hexadecimal digits, but is: af1eec2a1b18886c3f3cc244349d91d8 (length: 32 digits)"), (Checksum(ChecksumAlgorithm.SHA256, "af1eec2a1b18886c3f3cc244349d91d8"), - "value of ChecksumAlgorithm.SHA256 must consist of 64 hexadecimal digits, but is: af1eec2a1b18886c3f3cc244349d91d8 (length: 32 digits)"), + "value of ChecksumAlgorithm.SHA256 must consist of 64 lowercase hexadecimal digits, but is: af1eec2a1b18886c3f3cc244349d91d8 (length: 32 digits)"), (Checksum(ChecksumAlgorithm.SHA384, "af1eec2a1b18886c3f3cc244349d91d8"), - "value of ChecksumAlgorithm.SHA384 must consist of 96 hexadecimal digits, but is: af1eec2a1b18886c3f3cc244349d91d8 (length: 32 digits)"), + "value of ChecksumAlgorithm.SHA384 must consist of 96 lowercase hexadecimal digits, but is: af1eec2a1b18886c3f3cc244349d91d8 (length: 32 digits)"), (Checksum(ChecksumAlgorithm.SHA512, "af1eec2a1b18886c3f3cc244349d91d8"), - "value of ChecksumAlgorithm.SHA512 must consist of 128 hexadecimal digits, but is: af1eec2a1b18886c3f3cc244349d91d8 (length: 32 digits)"), + "value of ChecksumAlgorithm.SHA512 must consist of 128 lowercase hexadecimal digits, but is: af1eec2a1b18886c3f3cc244349d91d8 (length: 32 digits)"), (Checksum(ChecksumAlgorithm.SHA3_256, "af1eec2a1b18886c3f3cc244349d91d8"), - "value of ChecksumAlgorithm.SHA3_256 must consist of 64 hexadecimal digits, but is: af1eec2a1b18886c3f3cc244349d91d8 (length: 32 digits)"), + "value of ChecksumAlgorithm.SHA3_256 must consist of 64 lowercase hexadecimal digits, but is: af1eec2a1b18886c3f3cc244349d91d8 (length: 32 digits)"), (Checksum(ChecksumAlgorithm.SHA3_384, "af1eec2a1b18886c3f3cc244349d91d8"), - "value of ChecksumAlgorithm.SHA3_384 must consist of 96 hexadecimal digits, but is: af1eec2a1b18886c3f3cc244349d91d8 (length: 32 digits)"), + "value of ChecksumAlgorithm.SHA3_384 must consist of 96 lowercase hexadecimal digits, but is: af1eec2a1b18886c3f3cc244349d91d8 (length: 32 digits)"), (Checksum(ChecksumAlgorithm.SHA3_512, "af1eec2a1b18886c3f3cc244349d91d8"), - "value of ChecksumAlgorithm.SHA3_512 must consist of 128 hexadecimal digits, but is: af1eec2a1b18886c3f3cc244349d91d8 (length: 32 digits)"), + "value of ChecksumAlgorithm.SHA3_512 must consist of 128 lowercase hexadecimal digits, but is: af1eec2a1b18886c3f3cc244349d91d8 (length: 32 digits)"), (Checksum(ChecksumAlgorithm.BLAKE2B_256, "af1eec2a1b18886c3f3cc244349d91d8"), - "value of ChecksumAlgorithm.BLAKE2B_256 must consist of 64 hexadecimal digits, but is: af1eec2a1b18886c3f3cc244349d91d8 (length: 32 digits)"), + "value of ChecksumAlgorithm.BLAKE2B_256 must consist of 64 lowercase hexadecimal digits, but is: af1eec2a1b18886c3f3cc244349d91d8 (length: 32 digits)"), (Checksum(ChecksumAlgorithm.BLAKE2B_384, "af1eec2a1b18886c3f3cc244349d91d8"), - "value of ChecksumAlgorithm.BLAKE2B_384 must consist of 96 hexadecimal digits, but is: af1eec2a1b18886c3f3cc244349d91d8 (length: 32 digits)"), + "value of ChecksumAlgorithm.BLAKE2B_384 must consist of 96 lowercase hexadecimal digits, but is: af1eec2a1b18886c3f3cc244349d91d8 (length: 32 digits)"), (Checksum(ChecksumAlgorithm.BLAKE2B_512, "af1eec2a1b18886c3f3cc244349d91d8"), - "value of ChecksumAlgorithm.BLAKE2B_512 must consist of 128 hexadecimal digits, but is: af1eec2a1b18886c3f3cc244349d91d8 (length: 32 digits)"), + "value of ChecksumAlgorithm.BLAKE2B_512 must consist of 128 lowercase hexadecimal digits, but is: af1eec2a1b18886c3f3cc244349d91d8 (length: 32 digits)"), (Checksum(ChecksumAlgorithm.BLAKE3, "af1eec2a1b18886c3f3cc244349d91d8"), - "value of ChecksumAlgorithm.BLAKE3 must consist of at least 256 hexadecimal digits, but is: af1eec2a1b18886c3f3cc244349d91d8 (length: 32 digits)"), + "value of ChecksumAlgorithm.BLAKE3 must consist of at least 256 lowercase hexadecimal digits, but is: af1eec2a1b18886c3f3cc244349d91d8 (length: 32 digits)"), (Checksum(ChecksumAlgorithm.MD2, "71c4025dd9897b364f3ebbb42c484ff43d00791c"), - "value of ChecksumAlgorithm.MD2 must consist of 32 hexadecimal digits, but is: 71c4025dd9897b364f3ebbb42c484ff43d00791c (length: 40 digits)"), + "value of ChecksumAlgorithm.MD2 must consist of 32 lowercase hexadecimal digits, but is: 71c4025dd9897b364f3ebbb42c484ff43d00791c (length: 40 digits)"), (Checksum(ChecksumAlgorithm.MD4, "71c4025dd9897b364f3ebbb42c484ff43d00791c"), - "value of ChecksumAlgorithm.MD4 must consist of 32 hexadecimal digits, but is: 71c4025dd9897b364f3ebbb42c484ff43d00791c (length: 40 digits)"), + "value of ChecksumAlgorithm.MD4 must consist of 32 lowercase hexadecimal digits, but is: 71c4025dd9897b364f3ebbb42c484ff43d00791c (length: 40 digits)"), (Checksum(ChecksumAlgorithm.MD5, "71c4025dd9897b364f3ebbb42c484ff43d00791c"), - "value of ChecksumAlgorithm.MD5 must consist of 32 hexadecimal digits, but is: 71c4025dd9897b364f3ebbb42c484ff43d00791c (length: 40 digits)"), + "value of ChecksumAlgorithm.MD5 must consist of 32 lowercase hexadecimal digits, but is: 71c4025dd9897b364f3ebbb42c484ff43d00791c (length: 40 digits)"), (Checksum(ChecksumAlgorithm.MD6, "a872cac2efd29ed2ad8b5faa79b63f983341bea41183582b8863d952f6ac3e1cdfe0189967a13006857d3b9985174bf67239874dcec4cbbc9839496179feafeda872cac2efd29ed2ad8b5faa79b63f983341bea41183582b8863d952f6ac3e1cdfe0189967a13006857d3b9985174bf67239874dcec4cbbc9839496179feafeda872cac2efd29ed2ad8b5faa79b63f983341bea41183582b8863d952f6ac3e1cdfe0189967a13006857d3b9985174bf67239874dcec4cbbc9839496179feafeda872cac2efd29ed2ad8b5faa79b63f983341bea41183582b8863d952f6ac3e1cdfe0189967a13006857d3b9985174bf67239874dcec4cbbc9839496179feafed5"), - "value of ChecksumAlgorithm.MD6 must consist of between 0 and 512 hexadecimal digits, but is: a872cac2efd29ed2ad8b5faa79b63f983341bea41183582b8863d952f6ac3e1cdfe0189967a13006857d3b9985174bf67239874dcec4cbbc9839496179feafeda872cac2efd29ed2ad8b5faa79b63f983341bea41183582b8863d952f6ac3e1cdfe0189967a13006857d3b9985174bf67239874dcec4cbbc9839496179feafeda872cac2efd29ed2ad8b5faa79b63f983341bea41183582b8863d952f6ac3e1cdfe0189967a13006857d3b9985174bf67239874dcec4cbbc9839496179feafeda872cac2efd29ed2ad8b5faa79b63f983341bea41183582b8863d952f6ac3e1cdfe0189967a13006857d3b9985174bf67239874dcec4cbbc9839496179feafed5 (length: 513 digits)"), + "value of ChecksumAlgorithm.MD6 must consist of between 0 and 512 lowercase hexadecimal digits, but is: a872cac2efd29ed2ad8b5faa79b63f983341bea41183582b8863d952f6ac3e1cdfe0189967a13006857d3b9985174bf67239874dcec4cbbc9839496179feafeda872cac2efd29ed2ad8b5faa79b63f983341bea41183582b8863d952f6ac3e1cdfe0189967a13006857d3b9985174bf67239874dcec4cbbc9839496179feafeda872cac2efd29ed2ad8b5faa79b63f983341bea41183582b8863d952f6ac3e1cdfe0189967a13006857d3b9985174bf67239874dcec4cbbc9839496179feafeda872cac2efd29ed2ad8b5faa79b63f983341bea41183582b8863d952f6ac3e1cdfe0189967a13006857d3b9985174bf67239874dcec4cbbc9839496179feafed5 (length: 513 digits)"), (Checksum(ChecksumAlgorithm.ADLER32, "af1eec2a1b18886c3f3cc244349d91d8"), - "value of ChecksumAlgorithm.ADLER32 must consist of 8 hexadecimal digits, but is: af1eec2a1b18886c3f3cc244349d91d8 (length: 32 digits)"), + "value of ChecksumAlgorithm.ADLER32 must consist of 8 lowercase hexadecimal digits, but is: af1eec2a1b18886c3f3cc244349d91d8 (length: 32 digits)"), + (Checksum(ChecksumAlgorithm.SHA1, "CE9F343C4BA371746FD7EAD9B59031AE34D8AFC4"), + "value of ChecksumAlgorithm.SHA1 must consist of 40 lowercase hexadecimal digits, but is: CE9F343C4BA371746FD7EAD9B59031AE34D8AFC4 (length: 40 digits)"), ]) def test_invalid_checksum(checksum, expected_message): parent_id = "parent_id" diff --git a/tests/spdx/validation/test_package_validator.py b/tests/spdx/validation/test_package_validator.py index 3a4133872..fa7923982 100644 --- a/tests/spdx/validation/test_package_validator.py +++ b/tests/spdx/validation/test_package_validator.py @@ -14,11 +14,12 @@ import pytest from license_expression import Licensing +from spdx.model.relationship import Relationship, RelationshipType from spdx.model.spdx_no_assertion import SpdxNoAssertion from spdx.model.spdx_none import SpdxNone from spdx.validation.package_validator import validate_package_within_document from spdx.validation.validation_message import ValidationMessage, ValidationContext, SpdxElementType -from tests.spdx.fixtures import package_fixture, package_verification_code_fixture, document_fixture +from tests.spdx.fixtures import package_fixture, package_verification_code_fixture, document_fixture, file_fixture def test_valid_package(): @@ -42,7 +43,7 @@ def test_valid_package(): license_info_from_files=[Licensing().parse("some_license")], verification_code=None), "license_info_from_files must be None if files_analyzed is False, but is: [LicenseSymbol('some_license', " - "is_exception=False)]") + "is_exception=False)]") ]) def test_invalid_package(package_input, expected_message): validation_messages: List[ValidationMessage] = validate_package_within_document(package_input, @@ -54,3 +55,25 @@ def test_invalid_package(package_input, expected_message): full_element=package_input)) assert validation_messages == [expected] + + +@pytest.mark.parametrize("relationships", + [[Relationship("SPDXRef-Package", RelationshipType.CONTAINS, "SPDXRef-File1")], + [Relationship("SPDXRef-Package", RelationshipType.CONTAINS, "DocumentRef-external:SPDXRef-File")], + [Relationship("SPDXRef-File2", RelationshipType.CONTAINED_BY, "SPDXRef-Package")], + [Relationship("DocumentRef-external:SPDXRef-File", RelationshipType.CONTAINED_BY, "SPDXRef-Package")], + [Relationship("SPDXRef-Package", RelationshipType.CONTAINS, "SPDXRef-File2"), + Relationship("SPDXRef-File1", RelationshipType.CONTAINED_BY, "SPDXRef-Package")]]) +def test_invalid_package_with_contains(relationships): + document = document_fixture(relationships=relationships, + files=[file_fixture(spdx_id="SPDXRef-File1"), file_fixture(spdx_id="SPDXRef-File2")]) + package = package_fixture(files_analyzed=False, verification_code=None, license_info_from_files=[]) + context = ValidationContext(spdx_id=package.spdx_id, parent_id=document.creation_info.spdx_id, + element_type=SpdxElementType.PACKAGE, + full_element=package) + + validation_messages: List[ValidationMessage] = validate_package_within_document(package, document) + + assert validation_messages == [ + ValidationMessage(f"package must contain no elements if files_analyzed is False, but found {relationships}", + context)] diff --git a/tests/spdx/validation/test_package_verification_code_validator.py b/tests/spdx/validation/test_package_verification_code_validator.py new file mode 100644 index 000000000..5ed3ae4ce --- /dev/null +++ b/tests/spdx/validation/test_package_verification_code_validator.py @@ -0,0 +1,40 @@ +# Copyright (c) 2022 spdx contributors +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest + +from spdx.model.package import PackageVerificationCode +from spdx.validation.package_verification_code_validator import validate_verification_code +from spdx.validation.validation_message import ValidationContext, SpdxElementType, ValidationMessage + + +def test_valid_package_verification_code(): + code = PackageVerificationCode("71c4025dd9897b364f3ebbb42c484ff43d00791c", ["./excluded_file", "another.file"]) + validation_messages = validate_verification_code(code, "SPDXRef-Package") + + assert validation_messages == [] + + +@pytest.mark.parametrize("code, expected_message", + [(PackageVerificationCode("71c4025dd9897b364f3ebbb42c484ff43d00791cab", []), + "value of verification_code must consist of 40 lowercase hexadecimal digits, but is: 71c4025dd9897b364f3ebbb42c484ff43d00791cab (length: 42 digits)"), + (PackageVerificationCode("CE9F343C4BA371746FD7EAD9B59031AE34D8AFC4", []), + "value of verification_code must consist of 40 lowercase hexadecimal digits, but is: CE9F343C4BA371746FD7EAD9B59031AE34D8AFC4 (length: 40 digits)"), + (PackageVerificationCode("71c4025dd9897b364f3ebbb42c484ff43d00791c", ["/invalid/excluded/file"]), + 'file name must not be an absolute path starting with "/", but is: /invalid/excluded/file') + ]) +def test_invalid_package_verification_code(code, expected_message): + parent_id = "SPDXRef-Package" + context = ValidationContext(parent_id=parent_id, element_type=SpdxElementType.PACKAGE_VERIFICATION_CODE, + full_element=code) + validation_messages = validate_verification_code(code, parent_id) + + assert validation_messages == [ValidationMessage(expected_message, context)]