diff --git a/cyclonedx_py/_internal/utils/pep621.py b/cyclonedx_py/_internal/utils/pep621.py index a128dff9f..cfb3ac979 100644 --- a/cyclonedx_py/_internal/utils/pep621.py +++ b/cyclonedx_py/_internal/utils/pep621.py @@ -60,7 +60,7 @@ def project2licenses(project: dict[str, Any], lfac: 'LicenseFactory', *, # https://peps.python.org/pep-0621/#classifiers # https://packaging.python.org/en/latest/specifications/core-metadata/#classifier-multiple-use yield from classifiers2licenses(classifiers, lfac, lack) - if plicense := project.get('license'): + if isinstance(plicense := project.get('license'), dict): # https://packaging.python.org/en/latest/specifications/pyproject-toml/#license # https://peps.python.org/pep-0621/#license # https://packaging.python.org/en/latest/specifications/core-metadata/#license @@ -87,6 +87,7 @@ def project2licenses(project: dict[str, Any], lfac: 'LicenseFactory', *, text=AttachedText(content=plicense_text)) else: yield license + # Silently skip any other types (including string/PEP 639) def project2extrefs(project: dict[str, Any]) -> Generator['ExternalReference', None, None]: diff --git a/tests/integration/test_utils_pep621.py b/tests/integration/test_utils_pep621.py new file mode 100644 index 000000000..463cddd48 --- /dev/null +++ b/tests/integration/test_utils_pep621.py @@ -0,0 +1,79 @@ +# This file is part of CycloneDX Python +# +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +# Copyright (c) OWASP Foundation. All Rights Reserved. + +from os.path import join +from tempfile import TemporaryDirectory +from unittest import TestCase + +from cyclonedx.factory.license import LicenseFactory +from cyclonedx.model import Encoding +from cyclonedx.model.license import DisjunctiveLicense, LicenseAcknowledgement +from ddt import ddt, named_data + +from cyclonedx_py._internal.utils.pep621 import project2licenses + + +@ddt() +class TestUtilsPEP621(TestCase): + + def test_project2licenses_license_dict_text(self) -> None: + project = { + 'name': 'testpkg', + 'license': {'text': 'This is the license text.'}, + } + lfac = LicenseFactory() + with TemporaryDirectory() as tmpdir: + licenses = list(project2licenses(project, lfac, fpath=join(tmpdir, 'pyproject.toml'))) + self.assertEqual(len(licenses), 1) + lic = licenses[0] + self.assertIsInstance(lic, DisjunctiveLicense) + self.assertIsNone(lic.id) + self.assertIsNone(lic.text.encoding) + self.assertEqual(lic.text.content, 'This is the license text.') + self.assertEqual(lic.acknowledgement, LicenseAcknowledgement.DECLARED) + + def test_project2licenses_license_dict_file(self) -> None: + project = { + 'name': 'testpkg', + 'license': {'file': 'license.txt'}, + } + lfac = LicenseFactory() + with TemporaryDirectory() as tmpdir: + with open(join(tmpdir, project['license']['file']), 'w') as tf: + tf.write('File license text') + licenses = list(project2licenses(project, lfac, fpath=join(tmpdir, 'pyproject.toml'))) + self.assertEqual(len(licenses), 1) + lic = licenses[0] + self.assertIsInstance(lic, DisjunctiveLicense) + self.assertIs(lic.text.encoding, Encoding.BASE_64) + self.assertEqual(lic.text.content, 'RmlsZSBsaWNlbnNlIHRleHQ=') + self.assertEqual(lic.acknowledgement, LicenseAcknowledgement.DECLARED) + + @named_data( + ('none', None), + ('string', 'MIT'), + ('list', ['MIT', 'Apache-2.0']) + ) + def test_project2licenses_license_non_dict(self, license: any) -> None: + project = { + 'name': 'testpkg', + 'license': license, + } + lfac = LicenseFactory() + with TemporaryDirectory() as tmpdir: + licenses = list(project2licenses(project, lfac, fpath=join(tmpdir, 'pyproject.toml'))) + self.assertEqual(len(licenses), 0)