From b750cdfb1827b570aaf0958edca0172f0d3a04e0 Mon Sep 17 00:00:00 2001 From: Nicolaus Weidner Date: Wed, 21 Dec 2022 15:21:42 +0100 Subject: [PATCH 01/55] [bugfix] Fix snippet copyright text type Signed-off-by: Nicolaus Weidner --- src/model/snippet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/model/snippet.py b/src/model/snippet.py index 329de5c25..690647fad 100644 --- a/src/model/snippet.py +++ b/src/model/snippet.py @@ -27,7 +27,7 @@ class Snippet: concluded_license: Optional[Union[LicenseExpression, SpdxNoAssertion, SpdxNone]] = None license_info_in_snippet: Optional[Union[List[LicenseExpression], SpdxNoAssertion, SpdxNone]] = None license_comment: Optional[str] = None - copyright_text: Optional[str] = None + copyright_text: Optional[Union[str, SpdxNoAssertion, SpdxNone]] = None comment: Optional[str] = None name: Optional[str] = None attribution_texts: List[str] = field(default_factory=list) From 72145b0592e1f2530a76943aabc2d17b72126ca6 Mon Sep 17 00:00:00 2001 From: Nicolaus Weidner Date: Tue, 13 Dec 2022 17:32:54 +0100 Subject: [PATCH 02/55] [issue-359] Recover datetime utils from git history, add tests Signed-off-by: Nicolaus Weidner --- tests/test_datetime_conversions.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/test_datetime_conversions.py b/tests/test_datetime_conversions.py index 6ec233cda..e70e1dc86 100644 --- a/tests/test_datetime_conversions.py +++ b/tests/test_datetime_conversions.py @@ -12,7 +12,11 @@ import pytest -from src.datetime_conversions import datetime_from_str +from src.datetime_conversions import datetime_from_str, datetime_to_iso_string + + +def test_datetime_to_iso_string(): + assert datetime_to_iso_string(datetime(2022, 12, 13, 1, 2, 3)) == "2022-12-13T01:02:03Z" def test_datetime_from_str(): From 6b892fd979cfcf2ce0c9c699d06c2b8391d50976 Mon Sep 17 00:00:00 2001 From: Nicolaus Weidner Date: Fri, 30 Dec 2022 17:19:49 +0100 Subject: [PATCH 03/55] [issue-359][squash] add license header Signed-off-by: Nicolaus Weidner --- src/datetime_conversions.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/datetime_conversions.py b/src/datetime_conversions.py index 7829693cf..62fafe866 100644 --- a/src/datetime_conversions.py +++ b/src/datetime_conversions.py @@ -10,8 +10,6 @@ # limitations under the License. from datetime import datetime -from src.parser.error import SPDXParsingError - def datetime_from_str(date_str: str) -> datetime: if not isinstance(date_str, str): From e2b06ddf377a439be6428a26cf8dec9de5b9485d Mon Sep 17 00:00:00 2001 From: Nicolaus Weidner Date: Wed, 14 Dec 2022 23:42:06 +0100 Subject: [PATCH 04/55] [issue-359] Add Json property conversion setup for CreationInfo properties Signed-off-by: Nicolaus Weidner --- src/jsonschema/__init__.py | 0 src/jsonschema/checksum_properties.py | 37 ++++++++++++++ src/jsonschema/common_conversions.py | 20 ++++++++ src/jsonschema/creation_info_properties.py | 37 ++++++++++++++ src/jsonschema/document_properties.py | 48 +++++++++++++++++++ .../external_document_ref_properties.py | 35 ++++++++++++++ src/jsonschema/json_property.py | 20 ++++++++ src/writer/__init__.py | 0 src/writer/casing_tools.py | 16 +++++++ tests/model/test_actor.py | 13 +++++ 10 files changed, 226 insertions(+) create mode 100644 src/jsonschema/__init__.py create mode 100644 src/jsonschema/checksum_properties.py create mode 100644 src/jsonschema/common_conversions.py create mode 100644 src/jsonschema/creation_info_properties.py create mode 100644 src/jsonschema/document_properties.py create mode 100644 src/jsonschema/external_document_ref_properties.py create mode 100644 src/jsonschema/json_property.py create mode 100644 src/writer/__init__.py create mode 100644 src/writer/casing_tools.py diff --git a/src/jsonschema/__init__.py b/src/jsonschema/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/jsonschema/checksum_properties.py b/src/jsonschema/checksum_properties.py new file mode 100644 index 000000000..98a28166a --- /dev/null +++ b/src/jsonschema/checksum_properties.py @@ -0,0 +1,37 @@ +# 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. +from enum import auto +from typing import Any + +from src.jsonschema.json_property import JsonProperty +from src.model.checksum import Checksum, ChecksumAlgorithm +from src.writer.casing_tools import snake_case_to_camel_case + + +class ChecksumProperty(JsonProperty): + ALGORITHM = auto() + CHECKSUM_VALUE = auto() + + def json_property_name(self) -> str: + return snake_case_to_camel_case(self.name) + + def get_property_value(self, checksum: Checksum) -> Any: + if self == ChecksumProperty.ALGORITHM: + return algorithm_to_json_string(checksum.algorithm) + elif self == ChecksumProperty.CHECKSUM_VALUE: + return checksum.value + + +def algorithm_to_json_string(algorithm: ChecksumAlgorithm) -> str: + name_with_dash: str = algorithm.name.replace("_", "-") + if "BLAKE2B" in name_with_dash: + return name_with_dash.replace("BLAKE2B", "BLAKE2b") + return name_with_dash diff --git a/src/jsonschema/common_conversions.py b/src/jsonschema/common_conversions.py new file mode 100644 index 000000000..a9102dd06 --- /dev/null +++ b/src/jsonschema/common_conversions.py @@ -0,0 +1,20 @@ +# 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. +from typing import Any, Dict, Type + +from src.jsonschema.json_property import JsonProperty + + +def convert_complex_type(instance: Any, property_type: Type[JsonProperty]) -> Dict: + result = {} + for property_name in property_type: + result[property_name.json_property_name()] = property_name.get_property_value(instance) + return result diff --git a/src/jsonschema/creation_info_properties.py b/src/jsonschema/creation_info_properties.py new file mode 100644 index 000000000..6451f1fb7 --- /dev/null +++ b/src/jsonschema/creation_info_properties.py @@ -0,0 +1,37 @@ +# 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. +from enum import auto +from typing import Any + +from src.datetime_conversions import datetime_to_iso_string +from src.jsonschema.json_property import JsonProperty +from src.model.document import CreationInfo +from src.writer.casing_tools import snake_case_to_camel_case + + +class CreationInfoProperty(JsonProperty): + CREATED = auto() + CREATORS = auto() + LICENSE_LIST_VERSION = auto() + COMMENT = auto() + + def json_property_name(self) -> str: + return snake_case_to_camel_case(self.name) + + def get_property_value(self, creation_info: CreationInfo) -> Any: + if self == CreationInfoProperty.CREATED: + return datetime_to_iso_string(creation_info.created) + elif self == CreationInfoProperty.CREATORS: + return [creator.to_serialized_string() for creator in creation_info.creators] + elif self == CreationInfoProperty.LICENSE_LIST_VERSION: + return str(creation_info.license_list_version) + elif self == CreationInfoProperty.COMMENT: + return creation_info.creator_comment diff --git a/src/jsonschema/document_properties.py b/src/jsonschema/document_properties.py new file mode 100644 index 000000000..06938b1f5 --- /dev/null +++ b/src/jsonschema/document_properties.py @@ -0,0 +1,48 @@ +# 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. +from enum import auto +from typing import Any + +from src.jsonschema.common_conversions import convert_complex_type +from src.jsonschema.external_document_ref_properties import ExternalDocumentRefProperty +from src.jsonschema.json_property import JsonProperty +from src.model.document import CreationInfo +from src.writer.casing_tools import snake_case_to_camel_case + + +class DocumentProperty(JsonProperty): + SPDX_VERSION = auto() + SPDX_ID = auto() + NAME = auto() + DOCUMENT_NAMESPACE = auto() + DATA_LICENSE = auto() + EXTERNAL_DOCUMENT_REFS = auto() + COMMENT = auto() + + def json_property_name(self) -> str: + if self == DocumentProperty.SPDX_ID: + return "SPDXID" + return snake_case_to_camel_case(self.name) + + def get_property_value(self, creation_info: CreationInfo) -> Any: + if self == DocumentProperty.SPDX_VERSION: + return creation_info.spdx_version + elif self == DocumentProperty.SPDX_ID: + return creation_info.spdx_id + elif self == DocumentProperty.NAME: + return creation_info.name + elif self == DocumentProperty.DATA_LICENSE: + return creation_info.data_license + elif self == DocumentProperty.EXTERNAL_DOCUMENT_REFS: + return [convert_complex_type(external_document_ref, ExternalDocumentRefProperty) for external_document_ref + in creation_info.external_document_refs] + elif self == DocumentProperty.COMMENT: + return creation_info.document_comment diff --git a/src/jsonschema/external_document_ref_properties.py b/src/jsonschema/external_document_ref_properties.py new file mode 100644 index 000000000..2c22afc3d --- /dev/null +++ b/src/jsonschema/external_document_ref_properties.py @@ -0,0 +1,35 @@ +# 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. +from enum import auto +from typing import Any + +from src.jsonschema.checksum_properties import ChecksumProperty +from src.jsonschema.common_conversions import convert_complex_type +from src.jsonschema.json_property import JsonProperty +from src.model.external_document_ref import ExternalDocumentRef +from src.writer.casing_tools import snake_case_to_camel_case + + +class ExternalDocumentRefProperty(JsonProperty): + EXTERNAL_DOCUMENT_ID = auto() + SPDX_DOCUMENT = auto() + CHECKSUM = auto() + + def json_property_name(self) -> str: + return snake_case_to_camel_case(self.name) + + def get_property_value(self, external_document_ref: ExternalDocumentRef) -> Any: + if self == ExternalDocumentRefProperty.EXTERNAL_DOCUMENT_ID: + return external_document_ref.document_ref_id + elif self == ExternalDocumentRefProperty.SPDX_DOCUMENT: + return external_document_ref.document_uri + elif self == ExternalDocumentRefProperty.CHECKSUM: + return convert_complex_type(external_document_ref.checksum, ChecksumProperty) diff --git a/src/jsonschema/json_property.py b/src/jsonschema/json_property.py new file mode 100644 index 000000000..28c7ef29e --- /dev/null +++ b/src/jsonschema/json_property.py @@ -0,0 +1,20 @@ +# 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. +from enum import Enum +from typing import Any + + +class JsonProperty(Enum): + def json_property_name(self) -> str: + raise NotImplementedError("Must be implemented") + + def get_property_value(self, instance: Any) -> Any: + raise NotImplementedError("Must be implemented") diff --git a/src/writer/__init__.py b/src/writer/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/writer/casing_tools.py b/src/writer/casing_tools.py new file mode 100644 index 000000000..b14543093 --- /dev/null +++ b/src/writer/casing_tools.py @@ -0,0 +1,16 @@ +# 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. +from re import sub + + +def snake_case_to_camel_case(snake_case_string: str) -> str: + each_word_capitalized = sub(r"[_\-]+", " ", snake_case_string).title().replace(" ", "") + return each_word_capitalized[0].lower() + each_word_capitalized[1:] diff --git a/tests/model/test_actor.py b/tests/model/test_actor.py index f1eacf5b1..c39ac744a 100644 --- a/tests/model/test_actor.py +++ b/tests/model/test_actor.py @@ -36,3 +36,16 @@ def test_wrong_type_in_email_after_initializing(): with pytest.raises(TypeError): actor = Actor(ActorType.PERSON, "name") actor.email = [] + + +@pytest.mark.parametrize("actor,expected_string", [(Actor(ActorType.PERSON, "personName"), "Person: personName"), + (Actor(ActorType.PERSON, "personName", "personEmail"), + "Person: personName (personEmail)"), + (Actor(ActorType.ORGANIZATION, "orgName"), "Organization: orgName"), + (Actor(ActorType.ORGANIZATION, "orgName", "orgEmail"), + "Organization: orgName (orgEmail)"), + (Actor(ActorType.TOOL, "toolName"), "Tool: toolName"), + (Actor(ActorType.TOOL, "toolName", "toolEmail"), + "Tool: toolName (toolEmail)")]) +def test_serialization(actor: Actor, expected_string: str): + assert actor.to_serialized_string() == expected_string From f84ef3fc538a8e4955033bcabf99bf2a359d2ced Mon Sep 17 00:00:00 2001 From: Nicolaus Weidner Date: Thu, 15 Dec 2022 11:10:57 +0100 Subject: [PATCH 05/55] [issue-359] Remove logic from json property enums (will be included in separate converters), refactor document_properties.py to include all document properties instead of just top-level ones Signed-off-by: Nicolaus Weidner --- src/jsonschema/checksum_properties.py | 19 ------- src/jsonschema/creation_info_properties.py | 17 ------ src/jsonschema/document_properties.py | 52 ++++++------------- .../external_document_ref_properties.py | 16 ------ src/jsonschema/json_property.py | 11 ++-- 5 files changed, 22 insertions(+), 93 deletions(-) diff --git a/src/jsonschema/checksum_properties.py b/src/jsonschema/checksum_properties.py index 98a28166a..6b974d5bb 100644 --- a/src/jsonschema/checksum_properties.py +++ b/src/jsonschema/checksum_properties.py @@ -9,29 +9,10 @@ # See the License for the specific language governing permissions and # limitations under the License. from enum import auto -from typing import Any from src.jsonschema.json_property import JsonProperty -from src.model.checksum import Checksum, ChecksumAlgorithm -from src.writer.casing_tools import snake_case_to_camel_case class ChecksumProperty(JsonProperty): ALGORITHM = auto() CHECKSUM_VALUE = auto() - - def json_property_name(self) -> str: - return snake_case_to_camel_case(self.name) - - def get_property_value(self, checksum: Checksum) -> Any: - if self == ChecksumProperty.ALGORITHM: - return algorithm_to_json_string(checksum.algorithm) - elif self == ChecksumProperty.CHECKSUM_VALUE: - return checksum.value - - -def algorithm_to_json_string(algorithm: ChecksumAlgorithm) -> str: - name_with_dash: str = algorithm.name.replace("_", "-") - if "BLAKE2B" in name_with_dash: - return name_with_dash.replace("BLAKE2B", "BLAKE2b") - return name_with_dash diff --git a/src/jsonschema/creation_info_properties.py b/src/jsonschema/creation_info_properties.py index 6451f1fb7..ccd52f58c 100644 --- a/src/jsonschema/creation_info_properties.py +++ b/src/jsonschema/creation_info_properties.py @@ -9,12 +9,8 @@ # See the License for the specific language governing permissions and # limitations under the License. from enum import auto -from typing import Any -from src.datetime_conversions import datetime_to_iso_string from src.jsonschema.json_property import JsonProperty -from src.model.document import CreationInfo -from src.writer.casing_tools import snake_case_to_camel_case class CreationInfoProperty(JsonProperty): @@ -22,16 +18,3 @@ class CreationInfoProperty(JsonProperty): CREATORS = auto() LICENSE_LIST_VERSION = auto() COMMENT = auto() - - def json_property_name(self) -> str: - return snake_case_to_camel_case(self.name) - - def get_property_value(self, creation_info: CreationInfo) -> Any: - if self == CreationInfoProperty.CREATED: - return datetime_to_iso_string(creation_info.created) - elif self == CreationInfoProperty.CREATORS: - return [creator.to_serialized_string() for creator in creation_info.creators] - elif self == CreationInfoProperty.LICENSE_LIST_VERSION: - return str(creation_info.license_list_version) - elif self == CreationInfoProperty.COMMENT: - return creation_info.creator_comment diff --git a/src/jsonschema/document_properties.py b/src/jsonschema/document_properties.py index 06938b1f5..444a966bd 100644 --- a/src/jsonschema/document_properties.py +++ b/src/jsonschema/document_properties.py @@ -1,21 +1,16 @@ -# 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. +# 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. from enum import auto -from typing import Any -from src.jsonschema.common_conversions import convert_complex_type -from src.jsonschema.external_document_ref_properties import ExternalDocumentRefProperty from src.jsonschema.json_property import JsonProperty -from src.model.document import CreationInfo -from src.writer.casing_tools import snake_case_to_camel_case class DocumentProperty(JsonProperty): @@ -26,23 +21,10 @@ class DocumentProperty(JsonProperty): DATA_LICENSE = auto() EXTERNAL_DOCUMENT_REFS = auto() COMMENT = auto() - - def json_property_name(self) -> str: - if self == DocumentProperty.SPDX_ID: - return "SPDXID" - return snake_case_to_camel_case(self.name) - - def get_property_value(self, creation_info: CreationInfo) -> Any: - if self == DocumentProperty.SPDX_VERSION: - return creation_info.spdx_version - elif self == DocumentProperty.SPDX_ID: - return creation_info.spdx_id - elif self == DocumentProperty.NAME: - return creation_info.name - elif self == DocumentProperty.DATA_LICENSE: - return creation_info.data_license - elif self == DocumentProperty.EXTERNAL_DOCUMENT_REFS: - return [convert_complex_type(external_document_ref, ExternalDocumentRefProperty) for external_document_ref - in creation_info.external_document_refs] - elif self == DocumentProperty.COMMENT: - return creation_info.document_comment + CREATION_INFO = auto() + PACKAGES = auto() + FILES = auto() + SNIPPETS = auto() + ANNOTATIONS = auto() + RELATIONSHIPS = auto() + HAS_EXTRACTED_LICENSING_INFO = auto() diff --git a/src/jsonschema/external_document_ref_properties.py b/src/jsonschema/external_document_ref_properties.py index 2c22afc3d..fd2c1eb3a 100644 --- a/src/jsonschema/external_document_ref_properties.py +++ b/src/jsonschema/external_document_ref_properties.py @@ -9,27 +9,11 @@ # See the License for the specific language governing permissions and # limitations under the License. from enum import auto -from typing import Any -from src.jsonschema.checksum_properties import ChecksumProperty -from src.jsonschema.common_conversions import convert_complex_type from src.jsonschema.json_property import JsonProperty -from src.model.external_document_ref import ExternalDocumentRef -from src.writer.casing_tools import snake_case_to_camel_case class ExternalDocumentRefProperty(JsonProperty): EXTERNAL_DOCUMENT_ID = auto() SPDX_DOCUMENT = auto() CHECKSUM = auto() - - def json_property_name(self) -> str: - return snake_case_to_camel_case(self.name) - - def get_property_value(self, external_document_ref: ExternalDocumentRef) -> Any: - if self == ExternalDocumentRefProperty.EXTERNAL_DOCUMENT_ID: - return external_document_ref.document_ref_id - elif self == ExternalDocumentRefProperty.SPDX_DOCUMENT: - return external_document_ref.document_uri - elif self == ExternalDocumentRefProperty.CHECKSUM: - return convert_complex_type(external_document_ref.checksum, ChecksumProperty) diff --git a/src/jsonschema/json_property.py b/src/jsonschema/json_property.py index 28c7ef29e..fcdcd7167 100644 --- a/src/jsonschema/json_property.py +++ b/src/jsonschema/json_property.py @@ -9,12 +9,11 @@ # See the License for the specific language governing permissions and # limitations under the License. from enum import Enum -from typing import Any class JsonProperty(Enum): - def json_property_name(self) -> str: - raise NotImplementedError("Must be implemented") - - def get_property_value(self, instance: Any) -> Any: - raise NotImplementedError("Must be implemented") + """ + Parent class for all json property classes. Not meant to be instantiated directly, only to have a common parent + type that can be used in type hints. + """ + pass From cb395c596ccfcfdbe4bfeb40a8281fda8faa3483 Mon Sep 17 00:00:00 2001 From: Nicolaus Weidner Date: Thu, 15 Dec 2022 11:57:10 +0100 Subject: [PATCH 06/55] [issue-359] Add abstract base converter class and checksum converter Signed-off-by: Nicolaus Weidner --- src/jsonschema/checksum_converter.py | 42 ++++++++++ src/jsonschema/converter.py | 45 +++++++++++ .../jsonschema/__init__.py | 10 --- tests/jsonschema/test_checksum_converter.py | 43 ++++++++++ tests/jsonschema/test_converter.py | 78 +++++++++++++++++++ 5 files changed, 208 insertions(+), 10 deletions(-) create mode 100644 src/jsonschema/checksum_converter.py create mode 100644 src/jsonschema/converter.py rename src/jsonschema/common_conversions.py => tests/jsonschema/__init__.py (62%) create mode 100644 tests/jsonschema/test_checksum_converter.py create mode 100644 tests/jsonschema/test_converter.py diff --git a/src/jsonschema/checksum_converter.py b/src/jsonschema/checksum_converter.py new file mode 100644 index 000000000..9a2ec945f --- /dev/null +++ b/src/jsonschema/checksum_converter.py @@ -0,0 +1,42 @@ +# 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. +from typing import Type + +from src.jsonschema.checksum_properties import ChecksumProperty +from src.jsonschema.converter import TypedConverter +from src.jsonschema.json_property import JsonProperty +from src.model.checksum import Checksum, ChecksumAlgorithm +from src.writer.casing_tools import snake_case_to_camel_case + + +class ChecksumConverter(TypedConverter): + + def get_data_model_type(self) -> Type[Checksum]: + return Checksum + + def get_json_type(self) -> Type[JsonProperty]: + return ChecksumProperty + + def json_property_name(self, checksum_property: ChecksumProperty) -> str: + return snake_case_to_camel_case(checksum_property.name) + + def get_property_value(self, checksum: Checksum, checksum_property: ChecksumProperty) -> str: + if checksum_property == ChecksumProperty.ALGORITHM: + return algorithm_to_json_string(checksum.algorithm) + elif checksum_property == ChecksumProperty.CHECKSUM_VALUE: + return checksum.value + + +def algorithm_to_json_string(algorithm: ChecksumAlgorithm) -> str: + name_with_dash: str = algorithm.name.replace("_", "-") + if "BLAKE2B" in name_with_dash: + return name_with_dash.replace("BLAKE2B", "BLAKE2b") + return name_with_dash diff --git a/src/jsonschema/converter.py b/src/jsonschema/converter.py new file mode 100644 index 000000000..cb703c3bb --- /dev/null +++ b/src/jsonschema/converter.py @@ -0,0 +1,45 @@ +# 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. +from abc import ABC, abstractmethod +from typing import Any, Type, Dict + +from src.jsonschema.json_property import JsonProperty + +MISSING_IMPLEMENTATION_MESSAGE = "Must be implemented" + + +class TypedConverter(ABC): + @abstractmethod + def json_property_name(self, property_thing: JsonProperty) -> str: + raise NotImplementedError(MISSING_IMPLEMENTATION_MESSAGE) + + @abstractmethod + def get_property_value(self, instance: Any, property_thing: JsonProperty) -> Any: + raise NotImplementedError(MISSING_IMPLEMENTATION_MESSAGE) + + @abstractmethod + def get_json_type(self) -> Type[JsonProperty]: + raise NotImplementedError(MISSING_IMPLEMENTATION_MESSAGE) + + @abstractmethod + def get_data_model_type(self) -> Type: + raise NotImplementedError(MISSING_IMPLEMENTATION_MESSAGE) + + def convert(self, instance: Any) -> Dict: + if not isinstance(instance, self.get_data_model_type()): + raise TypeError( + f"Converter of type {self.__class__} can only convert objects of type " + f"{self.get_data_model_type()}. Received {type(instance)} instead.") + + result = {} + for property_name in self.get_json_type(): + result[self.json_property_name(property_name)] = self.get_property_value(instance, property_name) + return result diff --git a/src/jsonschema/common_conversions.py b/tests/jsonschema/__init__.py similarity index 62% rename from src/jsonschema/common_conversions.py rename to tests/jsonschema/__init__.py index a9102dd06..cbc5c4070 100644 --- a/src/jsonschema/common_conversions.py +++ b/tests/jsonschema/__init__.py @@ -8,13 +8,3 @@ # 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. -from typing import Any, Dict, Type - -from src.jsonschema.json_property import JsonProperty - - -def convert_complex_type(instance: Any, property_type: Type[JsonProperty]) -> Dict: - result = {} - for property_name in property_type: - result[property_name.json_property_name()] = property_name.get_property_value(instance) - return result diff --git a/tests/jsonschema/test_checksum_converter.py b/tests/jsonschema/test_checksum_converter.py new file mode 100644 index 000000000..088f853f1 --- /dev/null +++ b/tests/jsonschema/test_checksum_converter.py @@ -0,0 +1,43 @@ +# 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 src.jsonschema.checksum_converter import ChecksumConverter +from src.jsonschema.checksum_properties import ChecksumProperty +from src.model.checksum import Checksum, ChecksumAlgorithm + + +@pytest.fixture +def converter() -> ChecksumConverter: + return ChecksumConverter() + + +@pytest.mark.parametrize("checksum_property,expected", [(ChecksumProperty.ALGORITHM, "algorithm"), + (ChecksumProperty.CHECKSUM_VALUE, "checksumValue")]) +def test_json_property_names(converter: ChecksumConverter, checksum_property: ChecksumProperty, expected: str): + assert converter.json_property_name(checksum_property) == expected + + +def test_successful_conversion(converter: ChecksumConverter): + checksum = Checksum(ChecksumAlgorithm.SHA1, "123") + + converted_dict = converter.convert(checksum) + + assert converted_dict[converter.json_property_name(ChecksumProperty.ALGORITHM)] == "SHA1" + assert converted_dict[converter.json_property_name(ChecksumProperty.CHECKSUM_VALUE)] == "123" + + +def test_json_type(converter: ChecksumConverter): + assert converter.get_json_type() == ChecksumProperty + + +def test_data_model_type(converter: ChecksumConverter): + assert converter.get_data_model_type() == Checksum diff --git a/tests/jsonschema/test_converter.py b/tests/jsonschema/test_converter.py new file mode 100644 index 000000000..433037c4b --- /dev/null +++ b/tests/jsonschema/test_converter.py @@ -0,0 +1,78 @@ +# 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. +from enum import auto +from typing import Type, Any + +import pytest + +from src.jsonschema.converter import TypedConverter +from src.jsonschema.json_property import JsonProperty +from src.model.checksum import Checksum, ChecksumAlgorithm +from src.model.typing.dataclass_with_properties import dataclass_with_properties +from src.model.typing.type_checks import check_types_and_set_values + + +class TestPropertyType(JsonProperty): + FIRST_NAME = auto() + SECOND_NAME = auto() + + +@dataclass_with_properties +class TestDataModelType: + first_property: str + second_property: int + third_property: int + + def __init__(self, first_property: str, second_property: int, third_property: int): + check_types_and_set_values(self, locals()) + + +class TestConverter(TypedConverter): + def json_property_name(self, test_property: TestPropertyType) -> str: + if test_property == TestPropertyType.FIRST_NAME: + return "jsonFirstName" + else: + return "jsonSecondName" + + def get_property_value(self, instance: TestDataModelType, test_property: TestPropertyType) -> Any: + if test_property == TestPropertyType.FIRST_NAME: + return instance.first_property + elif test_property == TestPropertyType.SECOND_NAME: + return instance.second_property + instance.third_property + + def get_json_type(self) -> Type[JsonProperty]: + return TestPropertyType + + def get_data_model_type(self) -> Type: + return TestDataModelType + + +def test_conversion(): + converter = TestConverter() + test_instance = TestDataModelType("firstPropertyValue", 1, 2) + + converted_dict = converter.convert(test_instance) + + assert converted_dict.get("jsonFirstName") == "firstPropertyValue" + assert converted_dict.get("jsonSecondName") == 3 + + +def test_wrong_type(): + converter = TestConverter() + checksum = Checksum(ChecksumAlgorithm.SHA1, "123") + + with pytest.raises(TypeError) as error: + converter.convert(checksum) + + error_message: str = error.value.args[0] + assert TestConverter.__name__ in error_message + assert TestDataModelType.__name__ in error_message + assert Checksum.__name__ in error_message From 92c183ea05cfa12eb248e5e47ede32d9c9b9b3dc Mon Sep 17 00:00:00 2001 From: Nicolaus Weidner Date: Mon, 2 Jan 2023 23:39:47 +0100 Subject: [PATCH 07/55] [issue-359][squash] Remove license header from empty __init__.py Signed-off-by: Nicolaus Weidner --- tests/jsonschema/__init__.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/tests/jsonschema/__init__.py b/tests/jsonschema/__init__.py index cbc5c4070..e69de29bb 100644 --- a/tests/jsonschema/__init__.py +++ b/tests/jsonschema/__init__.py @@ -1,10 +0,0 @@ -# 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. From aca25d18dafcad4ef27d3a4fec612dac8019a56b Mon Sep 17 00:00:00 2001 From: Nicolaus Weidner Date: Thu, 15 Dec 2022 13:36:19 +0100 Subject: [PATCH 08/55] [issue-359] Add CreationInfo converter Signed-off-by: Nicolaus Weidner --- src/jsonschema/creation_info_converter.py | 39 +++++++++++++ .../test_creation_info_converter.py | 57 +++++++++++++++++++ 2 files changed, 96 insertions(+) create mode 100644 src/jsonschema/creation_info_converter.py create mode 100644 tests/jsonschema/test_creation_info_converter.py diff --git a/src/jsonschema/creation_info_converter.py b/src/jsonschema/creation_info_converter.py new file mode 100644 index 000000000..8483fb2a8 --- /dev/null +++ b/src/jsonschema/creation_info_converter.py @@ -0,0 +1,39 @@ +# 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. +from typing import Type, Any + +from src.datetime_conversions import datetime_to_iso_string +from src.jsonschema.converter import TypedConverter +from src.jsonschema.creation_info_properties import CreationInfoProperty +from src.jsonschema.json_property import JsonProperty +from src.model.document import CreationInfo +from src.writer.casing_tools import snake_case_to_camel_case + + +class CreationInfoConverter(TypedConverter): + def get_data_model_type(self) -> Type[CreationInfo]: + return CreationInfo + + def get_json_type(self) -> Type[JsonProperty]: + return CreationInfoProperty + + def json_property_name(self, creation_info_property: CreationInfoProperty) -> str: + return snake_case_to_camel_case(creation_info_property.name) + + def get_property_value(self, creation_info: CreationInfo, creation_info_property: CreationInfoProperty) -> Any: + if creation_info_property == CreationInfoProperty.CREATED: + return datetime_to_iso_string(creation_info.created) + elif creation_info_property == CreationInfoProperty.CREATORS: + return [creator.to_serialized_string() for creator in creation_info.creators] + elif creation_info_property == CreationInfoProperty.LICENSE_LIST_VERSION: + return str(creation_info.license_list_version) + elif creation_info_property == CreationInfoProperty.COMMENT: + return creation_info.creator_comment diff --git a/tests/jsonschema/test_creation_info_converter.py b/tests/jsonschema/test_creation_info_converter.py new file mode 100644 index 000000000..8b1cb4b2d --- /dev/null +++ b/tests/jsonschema/test_creation_info_converter.py @@ -0,0 +1,57 @@ +# 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. +from datetime import datetime + +import pytest + +from src.datetime_conversions import datetime_to_iso_string +from src.jsonschema.creation_info_converter import CreationInfoConverter +from src.jsonschema.creation_info_properties import CreationInfoProperty +from src.model.actor import Actor, ActorType +from src.model.document import CreationInfo +from src.model.version import Version + + +@pytest.fixture +def converter() -> CreationInfoConverter: + return CreationInfoConverter() + + +@pytest.mark.parametrize("creation_info_property,expected", + [(CreationInfoProperty.CREATED, "created"), (CreationInfoProperty.CREATORS, "creators"), + (CreationInfoProperty.LICENSE_LIST_VERSION, "licenseListVersion"), + (CreationInfoProperty.COMMENT, "comment")]) +def test_json_property_names(converter: CreationInfoConverter, creation_info_property: CreationInfoProperty, + expected: str): + assert converter.json_property_name(creation_info_property) == expected + + +def test_successful_conversion(converter: CreationInfoConverter): + creators = [Actor(ActorType.PERSON, "personName"), Actor(ActorType.TOOL, "toolName")] + created = datetime(2022, 12, 1) + creation_info = CreationInfo("irrelevant", "irrelevant", "irrelevant", "irrelevant", creators=creators, + created=created, creator_comment="comment", license_list_version=Version(1, 2)) + + converted_dict = converter.convert(creation_info) + + assert converted_dict[converter.json_property_name(CreationInfoProperty.CREATED)] == datetime_to_iso_string(created) + assert converted_dict[converter.json_property_name(CreationInfoProperty.CREATORS)] == ["Person: personName", + "Tool: toolName"] + assert converted_dict[converter.json_property_name(CreationInfoProperty.LICENSE_LIST_VERSION)] == "1.2" + assert converted_dict[converter.json_property_name(CreationInfoProperty.COMMENT)] == "comment" + + +def test_json_type(converter: CreationInfoConverter): + assert converter.get_json_type() == CreationInfoProperty + + +def test_data_model_type(converter: CreationInfoConverter): + assert converter.get_data_model_type() == CreationInfo From dad3227c5641357b9f5ead258c68414b1b6a0802 Mon Sep 17 00:00:00 2001 From: Nicolaus Weidner Date: Thu, 15 Dec 2022 14:14:17 +0100 Subject: [PATCH 09/55] [issue-359] Add external document ref converter Signed-off-by: Nicolaus Weidner --- .../external_document_ref_converter.py | 43 ++++++++++++++ .../test_external_document_ref_converter.py | 59 +++++++++++++++++++ 2 files changed, 102 insertions(+) create mode 100644 src/jsonschema/external_document_ref_converter.py create mode 100644 tests/jsonschema/test_external_document_ref_converter.py diff --git a/src/jsonschema/external_document_ref_converter.py b/src/jsonschema/external_document_ref_converter.py new file mode 100644 index 000000000..41eb939da --- /dev/null +++ b/src/jsonschema/external_document_ref_converter.py @@ -0,0 +1,43 @@ +# 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. +from typing import Type, Any + +from src.jsonschema.checksum_converter import ChecksumConverter +from src.jsonschema.converter import TypedConverter +from src.jsonschema.external_document_ref_properties import ExternalDocumentRefProperty +from src.jsonschema.json_property import JsonProperty +from src.model.external_document_ref import ExternalDocumentRef +from src.writer.casing_tools import snake_case_to_camel_case + + +class ExternalDocumentRefConverter(TypedConverter): + checksum_converter: ChecksumConverter + + def __init__(self): + self.checksum_converter = ChecksumConverter() + + def json_property_name(self, property_thing: ExternalDocumentRefProperty) -> str: + return snake_case_to_camel_case(property_thing.name) + + def get_property_value(self, external_document_ref: ExternalDocumentRef, + property_thing: ExternalDocumentRefProperty) -> Any: + if property_thing == ExternalDocumentRefProperty.EXTERNAL_DOCUMENT_ID: + return external_document_ref.document_ref_id + elif property_thing == ExternalDocumentRefProperty.SPDX_DOCUMENT: + return external_document_ref.document_uri + elif property_thing == ExternalDocumentRefProperty.CHECKSUM: + return self.checksum_converter.convert(external_document_ref.checksum) + + def get_json_type(self) -> Type[JsonProperty]: + return ExternalDocumentRefProperty + + def get_data_model_type(self) -> Type[ExternalDocumentRef]: + return ExternalDocumentRef diff --git a/tests/jsonschema/test_external_document_ref_converter.py b/tests/jsonschema/test_external_document_ref_converter.py new file mode 100644 index 000000000..72f87712e --- /dev/null +++ b/tests/jsonschema/test_external_document_ref_converter.py @@ -0,0 +1,59 @@ +# 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. +from unittest import mock +from unittest.mock import MagicMock + +import pytest + +from src.jsonschema.external_document_ref_converter import ExternalDocumentRefConverter +from src.jsonschema.external_document_ref_properties import ExternalDocumentRefProperty +from src.model.checksum import Checksum, ChecksumAlgorithm +from src.model.external_document_ref import ExternalDocumentRef + + +@pytest.fixture +@mock.patch('src.jsonschema.checksum_converter.ChecksumConverter', autospec=True) +def converter(checksum_converter_magic_mock: MagicMock) -> ExternalDocumentRefConverter: + mocked_checksum_converter = checksum_converter_magic_mock() + converter = ExternalDocumentRefConverter() + converter.checksum_converter = mocked_checksum_converter + return converter + + +@pytest.mark.parametrize("external_document_ref_property,expected", + [(ExternalDocumentRefProperty.EXTERNAL_DOCUMENT_ID, "externalDocumentId"), + (ExternalDocumentRefProperty.SPDX_DOCUMENT, "spdxDocument"), + (ExternalDocumentRefProperty.CHECKSUM, "checksum")]) +def test_json_property_names(converter: ExternalDocumentRefConverter, + external_document_ref_property: ExternalDocumentRefProperty, expected: str): + assert converter.json_property_name(external_document_ref_property) == expected + + +def test_successful_conversion(converter: ExternalDocumentRefConverter): + converter.checksum_converter.convert.return_value = "dummy_converted_checksum" + checksum = Checksum(ChecksumAlgorithm.SHA1, "123") + external_document_ref = ExternalDocumentRef("document_ref_id", "document_uri", checksum) + + converted_dict = converter.convert(external_document_ref) + + assert converted_dict[ + converter.json_property_name(ExternalDocumentRefProperty.EXTERNAL_DOCUMENT_ID)] == "document_ref_id" + assert converted_dict[converter.json_property_name(ExternalDocumentRefProperty.SPDX_DOCUMENT)] == "document_uri" + assert converted_dict[ + converter.json_property_name(ExternalDocumentRefProperty.CHECKSUM)] == "dummy_converted_checksum" + + +def test_json_type(converter: ExternalDocumentRefConverter): + assert converter.get_json_type() == ExternalDocumentRefProperty + + +def test_data_model_type(converter: ExternalDocumentRefConverter): + assert converter.get_data_model_type() == ExternalDocumentRef From 5f625b913632d3e8b1567f183a528d79043757b3 Mon Sep 17 00:00:00 2001 From: Nicolaus Weidner Date: Fri, 30 Dec 2022 18:17:31 +0100 Subject: [PATCH 10/55] [issue-359][squash] Rename variable Signed-off-by: Nicolaus Weidner --- tests/jsonschema/test_external_document_ref_converter.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/jsonschema/test_external_document_ref_converter.py b/tests/jsonschema/test_external_document_ref_converter.py index 72f87712e..0694b95ea 100644 --- a/tests/jsonschema/test_external_document_ref_converter.py +++ b/tests/jsonschema/test_external_document_ref_converter.py @@ -38,7 +38,7 @@ def test_json_property_names(converter: ExternalDocumentRefConverter, def test_successful_conversion(converter: ExternalDocumentRefConverter): - converter.checksum_converter.convert.return_value = "dummy_converted_checksum" + converter.checksum_converter.convert.return_value = "mock_converted_checksum" checksum = Checksum(ChecksumAlgorithm.SHA1, "123") external_document_ref = ExternalDocumentRef("document_ref_id", "document_uri", checksum) @@ -48,7 +48,7 @@ def test_successful_conversion(converter: ExternalDocumentRefConverter): converter.json_property_name(ExternalDocumentRefProperty.EXTERNAL_DOCUMENT_ID)] == "document_ref_id" assert converted_dict[converter.json_property_name(ExternalDocumentRefProperty.SPDX_DOCUMENT)] == "document_uri" assert converted_dict[ - converter.json_property_name(ExternalDocumentRefProperty.CHECKSUM)] == "dummy_converted_checksum" + converter.json_property_name(ExternalDocumentRefProperty.CHECKSUM)] == "mock_converted_checksum" def test_json_type(converter: ExternalDocumentRefConverter): From 1804c21970d79378d8f0d81347b18444f2ad0683 Mon Sep 17 00:00:00 2001 From: Nicolaus Weidner Date: Thu, 15 Dec 2022 16:37:04 +0100 Subject: [PATCH 11/55] [issue-359] Enable passing the full document to converters to account for (upcoming) more complex conversion cases Signed-off-by: Nicolaus Weidner --- src/jsonschema/checksum_converter.py | 4 +++- src/jsonschema/converter.py | 12 +++++++++--- src/jsonschema/creation_info_converter.py | 5 +++-- src/jsonschema/external_document_ref_converter.py | 5 +++-- tests/jsonschema/test_converter.py | 4 +++- 5 files changed, 21 insertions(+), 9 deletions(-) diff --git a/src/jsonschema/checksum_converter.py b/src/jsonschema/checksum_converter.py index 9a2ec945f..695f4df7b 100644 --- a/src/jsonschema/checksum_converter.py +++ b/src/jsonschema/checksum_converter.py @@ -14,6 +14,7 @@ from src.jsonschema.converter import TypedConverter from src.jsonschema.json_property import JsonProperty from src.model.checksum import Checksum, ChecksumAlgorithm +from src.model.document import Document from src.writer.casing_tools import snake_case_to_camel_case @@ -28,7 +29,8 @@ def get_json_type(self) -> Type[JsonProperty]: def json_property_name(self, checksum_property: ChecksumProperty) -> str: return snake_case_to_camel_case(checksum_property.name) - def get_property_value(self, checksum: Checksum, checksum_property: ChecksumProperty) -> str: + def _get_property_value(self, checksum: Checksum, checksum_property: ChecksumProperty, + _document: Document = None) -> str: if checksum_property == ChecksumProperty.ALGORITHM: return algorithm_to_json_string(checksum.algorithm) elif checksum_property == ChecksumProperty.CHECKSUM_VALUE: diff --git a/src/jsonschema/converter.py b/src/jsonschema/converter.py index cb703c3bb..a4e03f3c3 100644 --- a/src/jsonschema/converter.py +++ b/src/jsonschema/converter.py @@ -12,6 +12,7 @@ from typing import Any, Type, Dict from src.jsonschema.json_property import JsonProperty +from src.model.document import Document MISSING_IMPLEMENTATION_MESSAGE = "Must be implemented" @@ -22,7 +23,7 @@ def json_property_name(self, property_thing: JsonProperty) -> str: raise NotImplementedError(MISSING_IMPLEMENTATION_MESSAGE) @abstractmethod - def get_property_value(self, instance: Any, property_thing: JsonProperty) -> Any: + def _get_property_value(self, instance: Any, property_thing: JsonProperty, document: Document = None) -> Any: raise NotImplementedError(MISSING_IMPLEMENTATION_MESSAGE) @abstractmethod @@ -33,13 +34,18 @@ def get_json_type(self) -> Type[JsonProperty]: def get_data_model_type(self) -> Type: raise NotImplementedError(MISSING_IMPLEMENTATION_MESSAGE) - def convert(self, instance: Any) -> Dict: + def requires_full_document(self) -> bool: + return False + + def convert(self, instance: Any, document: Document = None) -> Dict: if not isinstance(instance, self.get_data_model_type()): raise TypeError( f"Converter of type {self.__class__} can only convert objects of type " f"{self.get_data_model_type()}. Received {type(instance)} instead.") + if self.requires_full_document() and not document: + raise ValueError(f"Converter of type {self.__class__} requires the full document") result = {} for property_name in self.get_json_type(): - result[self.json_property_name(property_name)] = self.get_property_value(instance, property_name) + result[self.json_property_name(property_name)] = self._get_property_value(instance, property_name, document) return result diff --git a/src/jsonschema/creation_info_converter.py b/src/jsonschema/creation_info_converter.py index 8483fb2a8..656126203 100644 --- a/src/jsonschema/creation_info_converter.py +++ b/src/jsonschema/creation_info_converter.py @@ -14,7 +14,7 @@ from src.jsonschema.converter import TypedConverter from src.jsonschema.creation_info_properties import CreationInfoProperty from src.jsonschema.json_property import JsonProperty -from src.model.document import CreationInfo +from src.model.document import CreationInfo, Document from src.writer.casing_tools import snake_case_to_camel_case @@ -28,7 +28,8 @@ def get_json_type(self) -> Type[JsonProperty]: def json_property_name(self, creation_info_property: CreationInfoProperty) -> str: return snake_case_to_camel_case(creation_info_property.name) - def get_property_value(self, creation_info: CreationInfo, creation_info_property: CreationInfoProperty) -> Any: + def _get_property_value(self, creation_info: CreationInfo, creation_info_property: CreationInfoProperty, + _document: Document = None) -> Any: if creation_info_property == CreationInfoProperty.CREATED: return datetime_to_iso_string(creation_info.created) elif creation_info_property == CreationInfoProperty.CREATORS: diff --git a/src/jsonschema/external_document_ref_converter.py b/src/jsonschema/external_document_ref_converter.py index 41eb939da..ccb5e0be3 100644 --- a/src/jsonschema/external_document_ref_converter.py +++ b/src/jsonschema/external_document_ref_converter.py @@ -14,6 +14,7 @@ from src.jsonschema.converter import TypedConverter from src.jsonschema.external_document_ref_properties import ExternalDocumentRefProperty from src.jsonschema.json_property import JsonProperty +from src.model.document import Document from src.model.external_document_ref import ExternalDocumentRef from src.writer.casing_tools import snake_case_to_camel_case @@ -27,8 +28,8 @@ def __init__(self): def json_property_name(self, property_thing: ExternalDocumentRefProperty) -> str: return snake_case_to_camel_case(property_thing.name) - def get_property_value(self, external_document_ref: ExternalDocumentRef, - property_thing: ExternalDocumentRefProperty) -> Any: + def _get_property_value(self, external_document_ref: ExternalDocumentRef, + property_thing: ExternalDocumentRefProperty, _document: Document = None) -> Any: if property_thing == ExternalDocumentRefProperty.EXTERNAL_DOCUMENT_ID: return external_document_ref.document_ref_id elif property_thing == ExternalDocumentRefProperty.SPDX_DOCUMENT: diff --git a/tests/jsonschema/test_converter.py b/tests/jsonschema/test_converter.py index 433037c4b..add64a706 100644 --- a/tests/jsonschema/test_converter.py +++ b/tests/jsonschema/test_converter.py @@ -16,6 +16,7 @@ from src.jsonschema.converter import TypedConverter from src.jsonschema.json_property import JsonProperty from src.model.checksum import Checksum, ChecksumAlgorithm +from src.model.document import Document from src.model.typing.dataclass_with_properties import dataclass_with_properties from src.model.typing.type_checks import check_types_and_set_values @@ -42,7 +43,8 @@ def json_property_name(self, test_property: TestPropertyType) -> str: else: return "jsonSecondName" - def get_property_value(self, instance: TestDataModelType, test_property: TestPropertyType) -> Any: + def _get_property_value(self, instance: TestDataModelType, test_property: TestPropertyType, + _document: Document = None) -> Any: if test_property == TestPropertyType.FIRST_NAME: return instance.first_property elif test_property == TestPropertyType.SECOND_NAME: From 70a99bca420dedc38bf6ed698bc0bad593fd6b39 Mon Sep 17 00:00:00 2001 From: Nicolaus Weidner Date: Thu, 15 Dec 2022 22:02:11 +0100 Subject: [PATCH 12/55] [issue-359] Add annotation properties and converter Signed-off-by: Nicolaus Weidner --- src/jsonschema/annotation_converter.py | 41 ++++++++++++++ src/jsonschema/annotation_properties.py | 20 +++++++ tests/jsonschema/test_annotation_converter.py | 56 +++++++++++++++++++ 3 files changed, 117 insertions(+) create mode 100644 src/jsonschema/annotation_converter.py create mode 100644 src/jsonschema/annotation_properties.py create mode 100644 tests/jsonschema/test_annotation_converter.py diff --git a/src/jsonschema/annotation_converter.py b/src/jsonschema/annotation_converter.py new file mode 100644 index 000000000..a8f30d5a1 --- /dev/null +++ b/src/jsonschema/annotation_converter.py @@ -0,0 +1,41 @@ +# 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. +from typing import Type, Any + +from src.datetime_conversions import datetime_to_iso_string +from src.jsonschema.annotation_properties import AnnotationProperty +from src.jsonschema.converter import TypedConverter +from src.jsonschema.json_property import JsonProperty +from src.model.annotation import Annotation +from src.model.document import Document +from src.writer.casing_tools import snake_case_to_camel_case + + +class AnnotationConverter(TypedConverter): + def json_property_name(self, annotation_property: AnnotationProperty) -> str: + return snake_case_to_camel_case(annotation_property.name) + + def _get_property_value(self, annotation: Annotation, annotation_property: AnnotationProperty, + document: Document = None) -> Any: + if annotation_property == AnnotationProperty.ANNOTATION_DATE: + return datetime_to_iso_string(annotation.annotation_date) + elif annotation_property == AnnotationProperty.ANNOTATION_TYPE: + return annotation.annotation_type.name + elif annotation_property == AnnotationProperty.ANNOTATOR: + return annotation.annotator.to_serialized_string() + elif annotation_property == AnnotationProperty.COMMENT: + return annotation.annotation_comment + + def get_json_type(self) -> Type[JsonProperty]: + return AnnotationProperty + + def get_data_model_type(self) -> Type[Annotation]: + return Annotation diff --git a/src/jsonschema/annotation_properties.py b/src/jsonschema/annotation_properties.py new file mode 100644 index 000000000..5215a2819 --- /dev/null +++ b/src/jsonschema/annotation_properties.py @@ -0,0 +1,20 @@ +# 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. +from enum import auto + +from src.jsonschema.json_property import JsonProperty + + +class AnnotationProperty(JsonProperty): + ANNOTATION_DATE = auto() + ANNOTATION_TYPE = auto() + ANNOTATOR = auto() + COMMENT = auto() diff --git a/tests/jsonschema/test_annotation_converter.py b/tests/jsonschema/test_annotation_converter.py new file mode 100644 index 000000000..1f64ba336 --- /dev/null +++ b/tests/jsonschema/test_annotation_converter.py @@ -0,0 +1,56 @@ +# 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. +from datetime import datetime + +import pytest + +from src.datetime_conversions import datetime_to_iso_string +from src.jsonschema.annotation_converter import AnnotationConverter +from src.jsonschema.annotation_properties import AnnotationProperty +from src.model.actor import Actor, ActorType +from src.model.annotation import Annotation, AnnotationType + + +@pytest.fixture +def converter() -> AnnotationConverter: + return AnnotationConverter() + + +@pytest.mark.parametrize("annotation_property,expected", [(AnnotationProperty.ANNOTATION_DATE, "annotationDate"), + (AnnotationProperty.ANNOTATION_TYPE, "annotationType"), + (AnnotationProperty.ANNOTATOR, "annotator"), + (AnnotationProperty.COMMENT, "comment")]) +def test_json_property_names(converter: AnnotationConverter, annotation_property: AnnotationProperty, expected: str): + assert converter.json_property_name(annotation_property) == expected + + +def test_json_type(converter: AnnotationConverter): + assert converter.get_json_type() == AnnotationProperty + + +def test_data_model_type(converter: AnnotationConverter): + assert converter.get_data_model_type() == Annotation + + +def test_successful_conversion(converter: AnnotationConverter): + date = datetime(2022, 12, 1) + annotator = Actor(ActorType.PERSON, "actorName") + annotation = Annotation("spdxId", AnnotationType.REVIEW, annotator, + date, "comment") + + converted_dict = converter.convert(annotation) + + assert converted_dict[converter.json_property_name(AnnotationProperty.ANNOTATION_DATE)] == datetime_to_iso_string( + date) + assert converted_dict[converter.json_property_name(AnnotationProperty.ANNOTATION_TYPE)] == "REVIEW" + assert converted_dict[ + converter.json_property_name(AnnotationProperty.ANNOTATOR)] == annotator.to_serialized_string() + assert converted_dict[converter.json_property_name(AnnotationProperty.COMMENT)] == "comment" From a33766b9b07c5aaec47d19a556bd4bbf4c4ec786 Mon Sep 17 00:00:00 2001 From: Nicolaus Weidner Date: Thu, 15 Dec 2022 22:27:44 +0100 Subject: [PATCH 13/55] [issue-359] Add external package ref properties and converter Signed-off-by: Nicolaus Weidner --- .../external_package_ref_converter.py | 40 +++++++++++++++ .../external_package_ref_properties.py | 20 ++++++++ .../test_external_package_ref_converter.py | 50 +++++++++++++++++++ 3 files changed, 110 insertions(+) create mode 100644 src/jsonschema/external_package_ref_converter.py create mode 100644 src/jsonschema/external_package_ref_properties.py create mode 100644 tests/jsonschema/test_external_package_ref_converter.py diff --git a/src/jsonschema/external_package_ref_converter.py b/src/jsonschema/external_package_ref_converter.py new file mode 100644 index 000000000..531861105 --- /dev/null +++ b/src/jsonschema/external_package_ref_converter.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. +from typing import Type, Any + +from src.jsonschema.converter import TypedConverter +from src.jsonschema.external_package_ref_properties import ExternalPackageRefProperty +from src.jsonschema.json_property import JsonProperty +from src.model.document import Document +from src.model.package import ExternalPackageRef +from src.writer.casing_tools import snake_case_to_camel_case + + +class ExternalPackageRefConverter(TypedConverter): + def json_property_name(self, external_ref_property: ExternalPackageRefProperty) -> str: + return snake_case_to_camel_case(external_ref_property.name) + + def _get_property_value(self, external_ref: ExternalPackageRef, external_ref_property: ExternalPackageRefProperty, + document: Document = None) -> Any: + if external_ref_property == ExternalPackageRefProperty.COMMENT: + return external_ref.comment + elif external_ref_property == ExternalPackageRefProperty.REFERENCE_CATEGORY: + return external_ref.category.name + elif external_ref_property == ExternalPackageRefProperty.REFERENCE_LOCATOR: + return external_ref.locator + elif external_ref_property == ExternalPackageRefProperty.REFERENCE_TYPE: + return external_ref.reference_type + + def get_json_type(self) -> Type[JsonProperty]: + return ExternalPackageRefProperty + + def get_data_model_type(self) -> Type[ExternalPackageRef]: + return ExternalPackageRef diff --git a/src/jsonschema/external_package_ref_properties.py b/src/jsonschema/external_package_ref_properties.py new file mode 100644 index 000000000..d59348821 --- /dev/null +++ b/src/jsonschema/external_package_ref_properties.py @@ -0,0 +1,20 @@ +# 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. +from enum import auto + +from src.jsonschema.json_property import JsonProperty + + +class ExternalPackageRefProperty(JsonProperty): + COMMENT = auto() + REFERENCE_CATEGORY = auto() + REFERENCE_LOCATOR = auto() + REFERENCE_TYPE = auto() diff --git a/tests/jsonschema/test_external_package_ref_converter.py b/tests/jsonschema/test_external_package_ref_converter.py new file mode 100644 index 000000000..44094c00d --- /dev/null +++ b/tests/jsonschema/test_external_package_ref_converter.py @@ -0,0 +1,50 @@ +# 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 src.jsonschema.external_package_ref_converter import ExternalPackageRefConverter +from src.jsonschema.external_package_ref_properties import ExternalPackageRefProperty +from src.model.package import ExternalPackageRef, ExternalPackageRefCategory + + +@pytest.fixture +def converter() -> ExternalPackageRefConverter: + return ExternalPackageRefConverter() + + +@pytest.mark.parametrize("external_package_ref_property,expected", + [(ExternalPackageRefProperty.COMMENT, "comment"), + (ExternalPackageRefProperty.REFERENCE_CATEGORY, "referenceCategory"), + (ExternalPackageRefProperty.REFERENCE_LOCATOR, "referenceLocator"), + (ExternalPackageRefProperty.REFERENCE_TYPE, "referenceType")]) +def test_json_property_names(converter: ExternalPackageRefConverter, + external_package_ref_property: ExternalPackageRefProperty, expected: str): + assert converter.json_property_name(external_package_ref_property) == expected + + +def test_json_type(converter: ExternalPackageRefConverter): + assert converter.get_json_type() == ExternalPackageRefProperty + + +def test_data_model_type(converter: ExternalPackageRefConverter): + assert converter.get_data_model_type() == ExternalPackageRef + + +def test_successful_conversion(converter: ExternalPackageRefConverter): + external_package_ref = ExternalPackageRef(ExternalPackageRefCategory.PACKAGE_MANAGER, "type", "locator", "comment") + + converted_dict = converter.convert(external_package_ref) + + assert converted_dict[converter.json_property_name(ExternalPackageRefProperty.COMMENT)] == "comment" + assert converted_dict[ + converter.json_property_name(ExternalPackageRefProperty.REFERENCE_CATEGORY)] == "PACKAGE_MANAGER" + assert converted_dict[converter.json_property_name(ExternalPackageRefProperty.REFERENCE_LOCATOR)] == "locator" + assert converted_dict[converter.json_property_name(ExternalPackageRefProperty.REFERENCE_TYPE)] == "type" From 525d451d39ed8d9a6296b8f844a0439f69c152f9 Mon Sep 17 00:00:00 2001 From: Nicolaus Weidner Date: Thu, 15 Dec 2022 22:44:39 +0100 Subject: [PATCH 14/55] [issue-359] Add package verification code properties and converter Signed-off-by: Nicolaus Weidner --- .../package_verification_code_converter.py | 37 ++++++++++++++ .../package_verification_code_properties.py | 18 +++++++ ...est_package_verification_code_converter.py | 49 +++++++++++++++++++ 3 files changed, 104 insertions(+) create mode 100644 src/jsonschema/package_verification_code_converter.py create mode 100644 src/jsonschema/package_verification_code_properties.py create mode 100644 tests/jsonschema/test_package_verification_code_converter.py diff --git a/src/jsonschema/package_verification_code_converter.py b/src/jsonschema/package_verification_code_converter.py new file mode 100644 index 000000000..61a5b7400 --- /dev/null +++ b/src/jsonschema/package_verification_code_converter.py @@ -0,0 +1,37 @@ +# 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. +from typing import Type, Any + +from src.jsonschema.converter import TypedConverter +from src.jsonschema.json_property import JsonProperty +from src.jsonschema.package_verification_code_properties import PackageVerificationCodeProperty +from src.model.document import Document +from src.model.package import PackageVerificationCode +from src.writer.casing_tools import snake_case_to_camel_case + + +class PackageVerificationCodeConverter(TypedConverter): + def json_property_name(self, verification_code_property: PackageVerificationCodeProperty) -> str: + return snake_case_to_camel_case(verification_code_property.name) + + def _get_property_value(self, verification_code: PackageVerificationCode, + verification_code_property: PackageVerificationCodeProperty, + document: Document = None) -> Any: + if verification_code_property == PackageVerificationCodeProperty.PACKAGE_VERIFICATION_CODE_EXCLUDED_FILES: + return verification_code.excluded_files + elif verification_code_property == PackageVerificationCodeProperty.PACKAGE_VERIFICATION_CODE_VALUE: + return verification_code.value + + def get_json_type(self) -> Type[JsonProperty]: + return PackageVerificationCodeProperty + + def get_data_model_type(self) -> Type[PackageVerificationCode]: + return PackageVerificationCode diff --git a/src/jsonschema/package_verification_code_properties.py b/src/jsonschema/package_verification_code_properties.py new file mode 100644 index 000000000..ee202f51a --- /dev/null +++ b/src/jsonschema/package_verification_code_properties.py @@ -0,0 +1,18 @@ +# 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. +from enum import auto + +from src.jsonschema.json_property import JsonProperty + + +class PackageVerificationCodeProperty(JsonProperty): + PACKAGE_VERIFICATION_CODE_EXCLUDED_FILES = auto() + PACKAGE_VERIFICATION_CODE_VALUE = auto() diff --git a/tests/jsonschema/test_package_verification_code_converter.py b/tests/jsonschema/test_package_verification_code_converter.py new file mode 100644 index 000000000..926c6e428 --- /dev/null +++ b/tests/jsonschema/test_package_verification_code_converter.py @@ -0,0 +1,49 @@ +# 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 src.jsonschema.package_verification_code_converter import PackageVerificationCodeConverter +from src.jsonschema.package_verification_code_properties import PackageVerificationCodeProperty +from src.model.package import PackageVerificationCode + + +@pytest.fixture +def converter() -> PackageVerificationCodeConverter: + return PackageVerificationCodeConverter() + + +@pytest.mark.parametrize("package_verification_code_property,expected", + [(PackageVerificationCodeProperty.PACKAGE_VERIFICATION_CODE_EXCLUDED_FILES, + "packageVerificationCodeExcludedFiles"), + (PackageVerificationCodeProperty.PACKAGE_VERIFICATION_CODE_VALUE, + "packageVerificationCodeValue")]) +def test_json_property_names(converter: PackageVerificationCodeConverter, + package_verification_code_property: PackageVerificationCodeProperty, expected: str): + assert converter.json_property_name(package_verification_code_property) == expected + + +def test_json_type(converter: PackageVerificationCodeConverter): + assert converter.get_json_type() == PackageVerificationCodeProperty + + +def test_data_model_type(converter: PackageVerificationCodeConverter): + assert converter.get_data_model_type() == PackageVerificationCode + + +def test_successful_conversion(converter: PackageVerificationCodeConverter): + package_verification_code = PackageVerificationCode("value", ["file1", "file2"]) + + converted_dict = converter.convert(package_verification_code) + + assert converted_dict[converter.json_property_name( + PackageVerificationCodeProperty.PACKAGE_VERIFICATION_CODE_EXCLUDED_FILES)] == ["file1", "file2"] + assert converted_dict[ + converter.json_property_name(PackageVerificationCodeProperty.PACKAGE_VERIFICATION_CODE_VALUE)] == "value" From 0ae5968ed5f210048f4340dbd08bbc3fa4684d0b Mon Sep 17 00:00:00 2001 From: Nicolaus Weidner Date: Wed, 21 Dec 2022 13:52:35 +0100 Subject: [PATCH 15/55] [issue-359] Add __str__ method to LicenseExpression for easy serialization Signed-off-by: Nicolaus Weidner --- src/model/license_expression.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/model/license_expression.py b/src/model/license_expression.py index 1f73ffc33..226b5e438 100644 --- a/src/model/license_expression.py +++ b/src/model/license_expression.py @@ -21,3 +21,6 @@ class LicenseExpression: def __init__(self, expression_string: str): check_types_and_set_values(self, locals()) + + def __str__(self): + return self.expression_string From e5058b627759821618f8912c0332078259b786ab Mon Sep 17 00:00:00 2001 From: Nicolaus Weidner Date: Mon, 19 Dec 2022 17:49:29 +0100 Subject: [PATCH 16/55] [issue-359] add package converter Signed-off-by: Nicolaus Weidner --- src/jsonschema/package_converter.py | 124 +++++++++++++++++ src/jsonschema/package_properties.py | 44 ++++++ src/jsonschema/relationship_filters.py | 43 ++++++ tests/jsonschema/test_package_converter.py | 148 +++++++++++++++++++++ 4 files changed, 359 insertions(+) create mode 100644 src/jsonschema/package_converter.py create mode 100644 src/jsonschema/package_properties.py create mode 100644 src/jsonschema/relationship_filters.py create mode 100644 tests/jsonschema/test_package_converter.py diff --git a/src/jsonschema/package_converter.py b/src/jsonschema/package_converter.py new file mode 100644 index 000000000..ff140251d --- /dev/null +++ b/src/jsonschema/package_converter.py @@ -0,0 +1,124 @@ +# 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. +from typing import Type, Any + +from src.datetime_conversions import datetime_to_iso_string +from src.jsonschema.annotation_converter import AnnotationConverter +from src.jsonschema.checksum_converter import ChecksumConverter +from src.jsonschema.converter import TypedConverter +from src.jsonschema.external_package_ref_converter import ExternalPackageRefConverter +from src.jsonschema.json_property import JsonProperty +from src.jsonschema.package_properties import PackageProperty +from src.jsonschema.package_verification_code_converter import PackageVerificationCodeConverter +from src.jsonschema.relationship_filters import find_package_contains_file_relationships, \ + find_file_contained_by_package_relationships +from src.model.actor import Actor +from src.model.document import Document +from src.model.package import Package +from src.writer.casing_tools import snake_case_to_camel_case + + +class PackageConverter(TypedConverter): + annotation_converter: AnnotationConverter + checksum_converter: ChecksumConverter + external_package_ref_converter: ExternalPackageRefConverter + package_verification_code_converter: PackageVerificationCodeConverter + + def __init__(self): + self.annotation_converter = AnnotationConverter() + self.checksum_converter = ChecksumConverter() + self.external_package_ref_converter = ExternalPackageRefConverter() + self.package_verification_code_converter = PackageVerificationCodeConverter() + + def json_property_name(self, package_property: PackageProperty) -> str: + if package_property == PackageProperty.SPDX_ID: + return "SPDXID" + return snake_case_to_camel_case(package_property.name) + + def _get_property_value(self, package: Package, package_property: PackageProperty, + document: Document = None) -> Any: + if package_property == PackageProperty.SPDX_ID: + return package.spdx_id + elif package_property == PackageProperty.ANNOTATIONS: + package_annotations = filter(lambda annotation: annotation.spdx_id == package.spdx_id, document.annotations) + return [self.annotation_converter.convert(annotation, document) for annotation in package_annotations] + elif package_property == PackageProperty.ATTRIBUTION_TEXTS: + return package.attribution_texts + elif package_property == PackageProperty.BUILT_DATE: + return datetime_to_iso_string(package.built_date) + elif package_property == PackageProperty.CHECKSUMS: + return [self.checksum_converter.convert(checksum, document) for checksum in package.checksums] + elif package_property == PackageProperty.COMMENT: + return package.comment + elif package_property == PackageProperty.COPYRIGHT_TEXT: + return str(package.copyright_text) + elif package_property == PackageProperty.DESCRIPTION: + return package.description + elif package_property == PackageProperty.DOWNLOAD_LOCATION: + return str(package.download_location) + elif package_property == PackageProperty.EXTERNAL_REFS: + return [self.external_package_ref_converter.convert(external_ref) for external_ref in + package.external_references] + elif package_property == PackageProperty.FILES_ANALYZED: + return package.files_analyzed + elif package_property == PackageProperty.HAS_FILES: + package_contains_file_ids = [relationship.related_spdx_element_id for relationship in + find_package_contains_file_relationships(document, package)] + file_contained_in_package_ids = [relationship.spdx_element_id for relationship in + find_file_contained_by_package_relationships(document, package)] + return package_contains_file_ids + file_contained_in_package_ids + elif package_property == PackageProperty.HOMEPAGE: + return str(package.homepage) + elif package_property == PackageProperty.LICENSE_COMMENTS: + return package.license_comment + elif package_property == PackageProperty.LICENSE_CONCLUDED: + return str(package.license_concluded) + elif package_property == PackageProperty.LICENSE_DECLARED: + return str(package.license_declared) + elif package_property == PackageProperty.LICENSE_INFO_FROM_FILES: + if isinstance(package.license_info_from_files, list): + return [str(license_expression) for license_expression in package.license_info_from_files] + return str(package.license_info_from_files) + elif package_property == PackageProperty.NAME: + return package.name + elif package_property == PackageProperty.ORIGINATOR: + if isinstance(package.originator, Actor): + return package.originator.to_serialized_string() + return str(package.originator) + elif package_property == PackageProperty.PACKAGE_FILE_NAME: + return package.file_name + elif package_property == PackageProperty.PACKAGE_VERIFICATION_CODE: + return self.package_verification_code_converter.convert(package.verification_code) + elif package_property == PackageProperty.PRIMARY_PACKAGE_PURPOSE: + return package.primary_package_purpose.name + elif package_property == PackageProperty.RELEASE_DATE: + return datetime_to_iso_string(package.release_date) + elif package_property == PackageProperty.SOURCE_INFO: + return package.source_info + elif package_property == PackageProperty.SUMMARY: + return package.summary + elif package_property == PackageProperty.SUPPLIER: + if isinstance(package.supplier, Actor): + return package.supplier.to_serialized_string() + return str(package.supplier) + elif package_property == PackageProperty.VALID_UNTIL_DATE: + return datetime_to_iso_string(package.valid_until_date) + elif package_property == PackageProperty.VERSION_INFO: + return package.version + + def get_json_type(self) -> Type[JsonProperty]: + return PackageProperty + + def get_data_model_type(self) -> Type[Package]: + return Package + + def requires_full_document(self) -> bool: + return True diff --git a/src/jsonschema/package_properties.py b/src/jsonschema/package_properties.py new file mode 100644 index 000000000..467ef5fc1 --- /dev/null +++ b/src/jsonschema/package_properties.py @@ -0,0 +1,44 @@ +# 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. +from enum import auto + +from src.jsonschema.json_property import JsonProperty + + +class PackageProperty(JsonProperty): + SPDX_ID = auto() + ANNOTATIONS = auto() + ATTRIBUTION_TEXTS = auto() + BUILT_DATE = auto() + CHECKSUMS = auto() + COMMENT = auto() + COPYRIGHT_TEXT = auto() + DESCRIPTION = auto() + DOWNLOAD_LOCATION = auto() + EXTERNAL_REFS = auto() + FILES_ANALYZED = auto() + HAS_FILES = auto() + HOMEPAGE = auto() + LICENSE_COMMENTS = auto() + LICENSE_CONCLUDED = auto() + LICENSE_DECLARED = auto() + LICENSE_INFO_FROM_FILES = auto() + NAME = auto() + ORIGINATOR = auto() + PACKAGE_FILE_NAME = auto() + PACKAGE_VERIFICATION_CODE = auto() + PRIMARY_PACKAGE_PURPOSE = auto() + RELEASE_DATE = auto() + SOURCE_INFO = auto() + SUMMARY = auto() + SUPPLIER = auto() + VALID_UNTIL_DATE = auto() + VERSION_INFO = auto() diff --git a/src/jsonschema/relationship_filters.py b/src/jsonschema/relationship_filters.py new file mode 100644 index 000000000..4d388b5f5 --- /dev/null +++ b/src/jsonschema/relationship_filters.py @@ -0,0 +1,43 @@ +# 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. +from typing import List + +from src.model.document import Document +from src.model.package import Package +from src.model.relationship import Relationship, RelationshipType + + +def find_package_contains_file_relationships(document: Document, package: Package) -> List[Relationship]: + file_ids_in_document = [file.spdx_id for file in document.files] + package_contains_relationships = filter_by_type_and_origin(document.relationships, RelationshipType.CONTAINS, + package.spdx_id) + return [relationship for relationship in package_contains_relationships if + relationship.related_spdx_element_id in file_ids_in_document] + + +def find_file_contained_by_package_relationships(document: Document, package: Package) -> List[Relationship]: + file_ids_in_document = [file.spdx_id for file in document.files] + contained_by_package_relationships = filter_by_type_and_target(document.relationships, + RelationshipType.CONTAINED_BY, package.spdx_id) + return [relationship for relationship in contained_by_package_relationships if + relationship.spdx_element_id in file_ids_in_document] + + +def filter_by_type_and_target(relationships: List[Relationship], relationship_type: RelationshipType, + target_id: str) -> List[Relationship]: + return [relationship for relationship in relationships if + relationship.relationship_type == relationship_type and relationship.related_spdx_element_id == target_id] + + +def filter_by_type_and_origin(relationships: List[Relationship], relationship_type: RelationshipType, + origin_id: str) -> List[Relationship]: + return [relationship for relationship in relationships if + relationship.relationship_type == relationship_type and relationship.spdx_element_id == origin_id] diff --git a/tests/jsonschema/test_package_converter.py b/tests/jsonschema/test_package_converter.py new file mode 100644 index 000000000..a1aeb3abe --- /dev/null +++ b/tests/jsonschema/test_package_converter.py @@ -0,0 +1,148 @@ +# 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. +from datetime import datetime +from unittest import mock +from unittest.mock import MagicMock + +import pytest + +from src.jsonschema.package_converter import PackageConverter +from src.jsonschema.package_properties import PackageProperty +from src.model.actor import Actor, ActorType +from src.model.annotation import Annotation, AnnotationType +from src.model.checksum import Checksum, ChecksumAlgorithm +from src.model.document import Document, CreationInfo +from src.model.license_expression import LicenseExpression +from src.model.package import Package, PackageVerificationCode, ExternalPackageRef, ExternalPackageRefCategory, \ + PackagePurpose + + +@pytest.fixture +@mock.patch('src.jsonschema.checksum_converter.ChecksumConverter', autospec=True) +@mock.patch('src.jsonschema.annotation_converter.AnnotationConverter', autospec=True) +@mock.patch('src.jsonschema.package_verification_code_converter.PackageVerificationCodeConverter', autospec=True) +@mock.patch('src.jsonschema.external_package_ref_converter.ExternalPackageRefConverter', autospec=True) +def converter(checksum_converter_mock: MagicMock, annotation_converter_mock: MagicMock, + verification_code_converter_mock: MagicMock, + package_ref_converter_mock: MagicMock) -> PackageConverter: + converter = PackageConverter() + converter.checksum_converter = checksum_converter_mock() + converter.annotation_converter = annotation_converter_mock() + converter.package_verification_code_converter = verification_code_converter_mock() + converter.external_package_ref_converter = package_ref_converter_mock() + return converter + + +@pytest.mark.parametrize("external_package_ref_property,expected", + [(PackageProperty.SPDX_ID, "SPDXID"), + (PackageProperty.ANNOTATIONS, "annotations"), + (PackageProperty.ATTRIBUTION_TEXTS, "attributionTexts"), + (PackageProperty.BUILT_DATE, "builtDate"), + (PackageProperty.CHECKSUMS, "checksums"), + (PackageProperty.COMMENT, "comment"), + (PackageProperty.COPYRIGHT_TEXT, "copyrightText"), + (PackageProperty.DESCRIPTION, "description"), + (PackageProperty.DOWNLOAD_LOCATION, "downloadLocation"), + (PackageProperty.EXTERNAL_REFS, "externalRefs"), + (PackageProperty.FILES_ANALYZED, "filesAnalyzed"), + (PackageProperty.HAS_FILES, "hasFiles"), + (PackageProperty.HOMEPAGE, "homepage"), + (PackageProperty.LICENSE_COMMENTS, "licenseComments"), + (PackageProperty.LICENSE_CONCLUDED, "licenseConcluded"), + (PackageProperty.LICENSE_DECLARED, "licenseDeclared"), + (PackageProperty.LICENSE_INFO_FROM_FILES, "licenseInfoFromFiles"), + (PackageProperty.NAME, "name"), + (PackageProperty.ORIGINATOR, "originator"), + (PackageProperty.PACKAGE_FILE_NAME, "packageFileName"), + (PackageProperty.PACKAGE_VERIFICATION_CODE, "packageVerificationCode"), + (PackageProperty.PRIMARY_PACKAGE_PURPOSE, "primaryPackagePurpose"), + (PackageProperty.RELEASE_DATE, "releaseDate"), + (PackageProperty.SOURCE_INFO, "sourceInfo"), + (PackageProperty.SUMMARY, "summary"), + (PackageProperty.SUPPLIER, "supplier"), + (PackageProperty.VALID_UNTIL_DATE, "validUntilDate"), + (PackageProperty.VERSION_INFO, "versionInfo")]) +def test_json_property_names(converter: PackageConverter, + external_package_ref_property: PackageProperty, expected: str): + assert converter.json_property_name(external_package_ref_property) == expected + + +def test_json_type(converter: PackageConverter): + assert converter.get_json_type() == PackageProperty + + +def test_data_model_type(converter: PackageConverter): + assert converter.get_data_model_type() == Package + + +def test_successful_conversion(converter: PackageConverter): + converter.checksum_converter.convert.return_value = "mock_converted_checksum" + converter.annotation_converter.convert.return_value = "mock_converted_annotation" + converter.package_verification_code_converter.convert.return_value = "mock_converted_verification_code" + converter.external_package_ref_converter.convert.return_value = "mock_package_ref" + package = Package(spdx_id="packageId", name="name", download_location="downloadLocation", version="version", + file_name="fileName", supplier=Actor(ActorType.PERSON, "supplierName"), + originator=Actor(ActorType.PERSON, "originatorName"), files_analyzed=True, + verification_code=PackageVerificationCode("value"), + checksums=[Checksum(ChecksumAlgorithm.SHA1, "sha1"), + Checksum(ChecksumAlgorithm.BLAKE2B_256, "blake")], homepage="homepage", + source_info="sourceInfo", license_concluded=LicenseExpression("licenseExpression1"), + license_info_from_files=[LicenseExpression("licenseExpression2"), + LicenseExpression("licenseExpression3")], + license_declared=LicenseExpression("licenseExpression4"), license_comment="licenseComment", + copyright_text="copyrightText", summary="summary", description="description", comment="comment", + external_references=[ + ExternalPackageRef(ExternalPackageRefCategory.PACKAGE_MANAGER, "referenceType", + "referenceLocator")], + attribution_texts=["attributionText1", "attributionText2"], + primary_package_purpose=PackagePurpose.APPLICATION, release_date=datetime(2022, 12, 1), + built_date=datetime(2022, 12, 2), valid_until_date=datetime(2022, 12, 3)) + + creation_info = CreationInfo("spdxVersion", "documentID", "documentName", "documentNamespace", [], + datetime(2022, 12, 4)) + annotation = Annotation(package.spdx_id, AnnotationType.REVIEW, Actor(ActorType.TOOL, "toolName"), + datetime(2022, 12, 5), + "review comment") + document = Document(creation_info, packages=[package], annotations=[annotation]) + + converted_dict = converter.convert(package, document) + + assert converted_dict[converter.json_property_name(PackageProperty.SPDX_ID)] == "packageId" + assert converted_dict[converter.json_property_name(PackageProperty.ANNOTATIONS)] == ["mock_converted_annotation"] + assert converted_dict[converter.json_property_name(PackageProperty.ATTRIBUTION_TEXTS)] == ["attributionText1", + "attributionText2"] + assert converted_dict[converter.json_property_name(PackageProperty.NAME)] == "name" + assert converted_dict[converter.json_property_name(PackageProperty.DOWNLOAD_LOCATION)] == "downloadLocation" + assert converted_dict[converter.json_property_name(PackageProperty.VERSION_INFO)] == "version" + assert converted_dict[converter.json_property_name(PackageProperty.PACKAGE_FILE_NAME)] == "fileName" + assert converted_dict[converter.json_property_name(PackageProperty.SUPPLIER)] == "Person: supplierName" + assert converted_dict[converter.json_property_name(PackageProperty.ORIGINATOR)] == "Person: originatorName" + assert converted_dict[converter.json_property_name(PackageProperty.FILES_ANALYZED)] + assert converted_dict[converter.json_property_name( + PackageProperty.PACKAGE_VERIFICATION_CODE)] == "mock_converted_verification_code" + assert converted_dict[converter.json_property_name(PackageProperty.CHECKSUMS)] == ["mock_converted_checksum", + "mock_converted_checksum"] + assert converted_dict[converter.json_property_name(PackageProperty.HOMEPAGE)] == "homepage" + assert converted_dict[converter.json_property_name(PackageProperty.SOURCE_INFO)] == "sourceInfo" + assert converted_dict[converter.json_property_name(PackageProperty.LICENSE_CONCLUDED)] == "licenseExpression1" + assert converted_dict[converter.json_property_name(PackageProperty.LICENSE_INFO_FROM_FILES)] == [ + "licenseExpression2", "licenseExpression3"] + assert converted_dict[converter.json_property_name(PackageProperty.LICENSE_DECLARED)] == "licenseExpression4" + assert converted_dict[converter.json_property_name(PackageProperty.LICENSE_COMMENTS)] == "licenseComment" + assert converted_dict[converter.json_property_name(PackageProperty.COPYRIGHT_TEXT)] == "copyrightText" + assert converted_dict[converter.json_property_name(PackageProperty.SUMMARY)] == "summary" + assert converted_dict[converter.json_property_name(PackageProperty.DESCRIPTION)] == "description" + assert converted_dict[converter.json_property_name(PackageProperty.COMMENT)] == "comment" + assert converted_dict[converter.json_property_name(PackageProperty.EXTERNAL_REFS)] == ["mock_package_ref"] + assert converted_dict[converter.json_property_name(PackageProperty.PRIMARY_PACKAGE_PURPOSE)] == "APPLICATION" + assert converted_dict[converter.json_property_name(PackageProperty.RELEASE_DATE)] == "2022-12-01T00:00:00Z" + assert converted_dict[converter.json_property_name(PackageProperty.BUILT_DATE)] == "2022-12-02T00:00:00Z" + assert converted_dict[converter.json_property_name(PackageProperty.VALID_UNTIL_DATE)] == "2022-12-03T00:00:00Z" From d0baa7fdfd8bcfa0e57a75f63f9b0ba88801247c Mon Sep 17 00:00:00 2001 From: Nicolaus Weidner Date: Mon, 19 Dec 2022 17:59:27 +0100 Subject: [PATCH 17/55] [issue-359] add relationship converter Signed-off-by: Nicolaus Weidner --- src/jsonschema/relationship_converter.py | 40 +++++++++++++++ src/jsonschema/relationship_properties.py | 20 ++++++++ .../jsonschema/test_relationship_converter.py | 49 +++++++++++++++++++ 3 files changed, 109 insertions(+) create mode 100644 src/jsonschema/relationship_converter.py create mode 100644 src/jsonschema/relationship_properties.py create mode 100644 tests/jsonschema/test_relationship_converter.py diff --git a/src/jsonschema/relationship_converter.py b/src/jsonschema/relationship_converter.py new file mode 100644 index 000000000..1860b6a06 --- /dev/null +++ b/src/jsonschema/relationship_converter.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. +from typing import Type, Any + +from src.jsonschema.converter import TypedConverter +from src.jsonschema.json_property import JsonProperty +from src.jsonschema.relationship_properties import RelationshipProperty +from src.model.document import Document +from src.model.relationship import Relationship +from src.writer.casing_tools import snake_case_to_camel_case + + +class RelationshipConverter(TypedConverter): + def json_property_name(self, relationship_property: RelationshipProperty) -> str: + return snake_case_to_camel_case(relationship_property.name) + + def _get_property_value(self, relationship: Relationship, relationship_property: RelationshipProperty, + document: Document = None) -> Any: + if relationship_property == RelationshipProperty.SPDX_ELEMENT_ID: + return relationship.spdx_element_id + elif relationship_property == RelationshipProperty.COMMENT: + return relationship.comment + elif relationship_property == RelationshipProperty.RELATED_SPDX_ELEMENT: + return relationship.related_spdx_element_id + elif relationship_property == RelationshipProperty.RELATIONSHIP_TYPE: + return relationship.relationship_type.name + + def get_json_type(self) -> Type[JsonProperty]: + return RelationshipProperty + + def get_data_model_type(self) -> Type[Relationship]: + return Relationship diff --git a/src/jsonschema/relationship_properties.py b/src/jsonschema/relationship_properties.py new file mode 100644 index 000000000..bd6b787d6 --- /dev/null +++ b/src/jsonschema/relationship_properties.py @@ -0,0 +1,20 @@ +# 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. +from enum import auto + +from src.jsonschema.json_property import JsonProperty + + +class RelationshipProperty(JsonProperty): + SPDX_ELEMENT_ID = auto() + COMMENT = auto() + RELATED_SPDX_ELEMENT = auto() + RELATIONSHIP_TYPE = auto() diff --git a/tests/jsonschema/test_relationship_converter.py b/tests/jsonschema/test_relationship_converter.py new file mode 100644 index 000000000..6c6a51a57 --- /dev/null +++ b/tests/jsonschema/test_relationship_converter.py @@ -0,0 +1,49 @@ +# 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 src.jsonschema.relationship_converter import RelationshipConverter +from src.jsonschema.relationship_properties import RelationshipProperty +from src.model.relationship import Relationship, RelationshipType + + +@pytest.fixture +def converter() -> RelationshipConverter: + return RelationshipConverter() + + +@pytest.mark.parametrize("relationship_property,expected", + [(RelationshipProperty.SPDX_ELEMENT_ID, "spdxElementId"), + (RelationshipProperty.COMMENT, "comment"), + (RelationshipProperty.RELATED_SPDX_ELEMENT, "relatedSpdxElement"), + (RelationshipProperty.RELATIONSHIP_TYPE, "relationshipType")]) +def test_json_property_names(converter: RelationshipConverter, relationship_property: RelationshipProperty, + expected: str): + assert converter.json_property_name(relationship_property) == expected + + +def test_json_type(converter: RelationshipConverter): + assert converter.get_json_type() == RelationshipProperty + + +def test_data_model_type(converter: RelationshipConverter): + assert converter.get_data_model_type() == Relationship + + +def test_successful_conversion(converter: RelationshipConverter): + relationship = Relationship("spdxElementId", RelationshipType.COPY_OF, "relatedElementId", "comment") + + converted_dict = converter.convert(relationship) + + assert converted_dict[converter.json_property_name(RelationshipProperty.SPDX_ELEMENT_ID)] == "spdxElementId" + assert converted_dict[converter.json_property_name(RelationshipProperty.COMMENT)] == "comment" + assert converted_dict[converter.json_property_name(RelationshipProperty.RELATED_SPDX_ELEMENT)] == "relatedElementId" + assert converted_dict[converter.json_property_name(RelationshipProperty.RELATIONSHIP_TYPE)] == "COPY_OF" From 92e2dc3d858d688cb5bc2813627fce26aa7de23e Mon Sep 17 00:00:00 2001 From: Nicolaus Weidner Date: Mon, 19 Dec 2022 23:00:57 +0100 Subject: [PATCH 18/55] [issue-359] add extracted licensing info converter Signed-off-by: Nicolaus Weidner --- .../extracted_licensing_info_converter.py | 43 +++++++++++++++ .../extracted_licensing_info_properties.py | 21 +++++++ ...test_extracted_licensing_info_converter.py | 55 +++++++++++++++++++ 3 files changed, 119 insertions(+) create mode 100644 src/jsonschema/extracted_licensing_info_converter.py create mode 100644 src/jsonschema/extracted_licensing_info_properties.py create mode 100644 tests/jsonschema/test_extracted_licensing_info_converter.py diff --git a/src/jsonschema/extracted_licensing_info_converter.py b/src/jsonschema/extracted_licensing_info_converter.py new file mode 100644 index 000000000..f15629c5a --- /dev/null +++ b/src/jsonschema/extracted_licensing_info_converter.py @@ -0,0 +1,43 @@ +# 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. +from typing import Type, Any + +from src.jsonschema.converter import TypedConverter +from src.jsonschema.extracted_licensing_info_properties import ExtractedLicensingInfoProperty +from src.jsonschema.json_property import JsonProperty +from src.model.document import Document +from src.model.extracted_licensing_info import ExtractedLicensingInfo +from src.writer.casing_tools import snake_case_to_camel_case + + +class ExtractedLicensingInfoConverter(TypedConverter): + def json_property_name(self, extracted_licensing_info_property: ExtractedLicensingInfoProperty) -> str: + return snake_case_to_camel_case(extracted_licensing_info_property.name) + + def _get_property_value(self, extracted_licensing_info: ExtractedLicensingInfo, + extracted_licensing_info_property: ExtractedLicensingInfoProperty, + document: Document = None) -> Any: + if extracted_licensing_info_property == ExtractedLicensingInfoProperty.COMMENT: + return extracted_licensing_info.comment + elif extracted_licensing_info_property == ExtractedLicensingInfoProperty.EXTRACTED_TEXT: + return extracted_licensing_info.extracted_text + elif extracted_licensing_info_property == ExtractedLicensingInfoProperty.LICENSE_ID: + return extracted_licensing_info.license_id + elif extracted_licensing_info_property == ExtractedLicensingInfoProperty.NAME: + return extracted_licensing_info.license_name + elif extracted_licensing_info_property == ExtractedLicensingInfoProperty.SEE_ALSOS: + return extracted_licensing_info.cross_references + + def get_json_type(self) -> Type[JsonProperty]: + return ExtractedLicensingInfoProperty + + def get_data_model_type(self) -> Type[ExtractedLicensingInfo]: + return ExtractedLicensingInfo diff --git a/src/jsonschema/extracted_licensing_info_properties.py b/src/jsonschema/extracted_licensing_info_properties.py new file mode 100644 index 000000000..0bfcda02a --- /dev/null +++ b/src/jsonschema/extracted_licensing_info_properties.py @@ -0,0 +1,21 @@ +# 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. +from enum import auto + +from src.jsonschema.json_property import JsonProperty + + +class ExtractedLicensingInfoProperty(JsonProperty): + COMMENT = auto() + EXTRACTED_TEXT = auto() + LICENSE_ID = auto() + NAME = auto() + SEE_ALSOS = auto() diff --git a/tests/jsonschema/test_extracted_licensing_info_converter.py b/tests/jsonschema/test_extracted_licensing_info_converter.py new file mode 100644 index 000000000..518a6f2b3 --- /dev/null +++ b/tests/jsonschema/test_extracted_licensing_info_converter.py @@ -0,0 +1,55 @@ +# 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 src.jsonschema.extracted_licensing_info_converter import ExtractedLicensingInfoConverter +from src.jsonschema.extracted_licensing_info_properties import ExtractedLicensingInfoProperty +from src.model.extracted_licensing_info import ExtractedLicensingInfo + + +@pytest.fixture +def converter() -> ExtractedLicensingInfoConverter: + return ExtractedLicensingInfoConverter() + + +@pytest.mark.parametrize("extracted_licensing_info_property,expected", + [(ExtractedLicensingInfoProperty.LICENSE_ID, "licenseId"), + (ExtractedLicensingInfoProperty.EXTRACTED_TEXT, "extractedText"), + (ExtractedLicensingInfoProperty.NAME, "name"), + (ExtractedLicensingInfoProperty.COMMENT, "comment"), + (ExtractedLicensingInfoProperty.SEE_ALSOS, "seeAlsos")]) +def test_json_property_names(converter: ExtractedLicensingInfoConverter, + extracted_licensing_info_property: ExtractedLicensingInfoProperty, expected: str): + assert converter.json_property_name(extracted_licensing_info_property) == expected + + +def test_json_type(converter: ExtractedLicensingInfoConverter): + assert converter.get_json_type() == ExtractedLicensingInfoProperty + + +def test_data_model_type(converter: ExtractedLicensingInfoConverter): + assert converter.get_data_model_type() == ExtractedLicensingInfo + + +def test_successful_conversion(converter: ExtractedLicensingInfoConverter): + extracted_licensing_info = ExtractedLicensingInfo(license_id="licenseId", extracted_text="Extracted text", + license_name="license name", + cross_references=["reference1", "reference2"], comment="comment") + + converted_dict = converter.convert(extracted_licensing_info) + + assert converted_dict[converter.json_property_name(ExtractedLicensingInfoProperty.LICENSE_ID)] == "licenseId" + assert converted_dict[ + converter.json_property_name(ExtractedLicensingInfoProperty.EXTRACTED_TEXT)] == "Extracted text" + assert converted_dict[converter.json_property_name(ExtractedLicensingInfoProperty.NAME)] == "license name" + assert converted_dict[converter.json_property_name(ExtractedLicensingInfoProperty.SEE_ALSOS)] == ["reference1", + "reference2"] + assert converted_dict[converter.json_property_name(ExtractedLicensingInfoProperty.COMMENT)] == "comment" From dbca30a150e612f731d7034cd94ace98c538ad0d Mon Sep 17 00:00:00 2001 From: Nicolaus Weidner Date: Tue, 20 Dec 2022 10:41:14 +0100 Subject: [PATCH 19/55] [issue-359] add file converter Signed-off-by: Nicolaus Weidner --- src/jsonschema/file_converter.py | 80 +++++++++++++++++++ src/jsonschema/file_properties.py | 31 +++++++ tests/jsonschema/test_file_converter.py | 102 ++++++++++++++++++++++++ 3 files changed, 213 insertions(+) create mode 100644 src/jsonschema/file_converter.py create mode 100644 src/jsonschema/file_properties.py create mode 100644 tests/jsonschema/test_file_converter.py diff --git a/src/jsonschema/file_converter.py b/src/jsonschema/file_converter.py new file mode 100644 index 000000000..19d96b180 --- /dev/null +++ b/src/jsonschema/file_converter.py @@ -0,0 +1,80 @@ +# 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. +from typing import Type, Any + +from src.jsonschema.annotation_converter import AnnotationConverter +from src.jsonschema.checksum_converter import ChecksumConverter +from src.jsonschema.converter import TypedConverter +from src.jsonschema.file_properties import FileProperty +from src.jsonschema.json_property import JsonProperty +from src.model.document import Document +from src.model.file import File +from src.writer.casing_tools import snake_case_to_camel_case + + +class FileConverter(TypedConverter): + annotation_converter: AnnotationConverter + checksum_converter: ChecksumConverter + + def __init__(self): + self.annotation_converter = AnnotationConverter() + self.checksum_converter = ChecksumConverter() + + def json_property_name(self, file_property: FileProperty) -> str: + if file_property == FileProperty.SPDX_ID: + return "SPDXID" + return snake_case_to_camel_case(file_property.name) + + def _get_property_value(self, file: Any, file_property: FileProperty, document: Document = None) -> Any: + if file_property == FileProperty.SPDX_ID: + return file.spdx_id + elif file_property == FileProperty.ANNOTATIONS: + file_annotations = filter(lambda annotation: annotation.spdx_id == file.spdx_id, document.annotations) + return [self.annotation_converter.convert(annotation) for annotation in file_annotations] + elif file_property == FileProperty.ARTIFACT_OFS: + # Deprecated property, automatically converted during parsing + pass + elif file_property == FileProperty.ATTRIBUTION_TEXTS: + return file.attribution_texts + elif file_property == FileProperty.CHECKSUMS: + return [self.checksum_converter.convert(checksum) for checksum in file.checksums] + elif file_property == FileProperty.COMMENT: + return file.comment + elif file_property == FileProperty.COPYRIGHT_TEXT: + return file.copyright_text + elif file_property == FileProperty.FILE_CONTRIBUTORS: + return file.contributors + elif file_property == FileProperty.FILE_DEPENDENCIES: + # Deprecated property, automatically converted during parsing + pass + elif file_property == FileProperty.FILE_NAME: + return file.name + elif file_property == FileProperty.FILE_TYPES: + return [file_type.name for file_type in file.file_type] + elif file_property == FileProperty.LICENSE_COMMENTS: + return file.license_comment + elif file_property == FileProperty.LICENSE_CONCLUDED: + return str(file.concluded_license) + elif file_property == FileProperty.LICENSE_INFO_IN_FILES: + if isinstance(file.license_info_in_file, list): + return [str(license_expression) for license_expression in file.license_info_in_file] + return str(file.license_info_in_file) + elif file_property == FileProperty.NOTICE_TEXT: + return file.notice + + def get_json_type(self) -> Type[JsonProperty]: + return FileProperty + + def get_data_model_type(self) -> Type[File]: + return File + + def requires_full_document(self) -> bool: + return True diff --git a/src/jsonschema/file_properties.py b/src/jsonschema/file_properties.py new file mode 100644 index 000000000..02cc8a25b --- /dev/null +++ b/src/jsonschema/file_properties.py @@ -0,0 +1,31 @@ +# 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. +from enum import auto + +from src.jsonschema.json_property import JsonProperty + + +class FileProperty(JsonProperty): + SPDX_ID = auto() + ANNOTATIONS = auto() + ARTIFACT_OFS = auto() + ATTRIBUTION_TEXTS = auto() + CHECKSUMS = auto() + COMMENT = auto() + COPYRIGHT_TEXT = auto() + FILE_CONTRIBUTORS = auto() + FILE_DEPENDENCIES = auto() + FILE_NAME = auto() + FILE_TYPES = auto() + LICENSE_COMMENTS = auto() + LICENSE_CONCLUDED = auto() + LICENSE_INFO_IN_FILES = auto() + NOTICE_TEXT = auto() diff --git a/tests/jsonschema/test_file_converter.py b/tests/jsonschema/test_file_converter.py new file mode 100644 index 000000000..a7846b756 --- /dev/null +++ b/tests/jsonschema/test_file_converter.py @@ -0,0 +1,102 @@ +# 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. +from datetime import datetime +from unittest import mock +from unittest.mock import MagicMock + +import pytest + +from src.jsonschema.file_converter import FileConverter +from src.jsonschema.file_properties import FileProperty +from src.model.actor import Actor, ActorType +from src.model.annotation import Annotation, AnnotationType +from src.model.checksum import Checksum, ChecksumAlgorithm +from src.model.document import CreationInfo, Document +from src.model.file import File, FileType +from src.model.license_expression import LicenseExpression + + +@pytest.fixture +@mock.patch('src.jsonschema.checksum_converter.ChecksumConverter', autospec=True) +@mock.patch('src.jsonschema.annotation_converter.AnnotationConverter', autospec=True) +def converter(checksum_converter_mock: MagicMock, annotation_converter_mock: MagicMock) -> FileConverter: + converter = FileConverter() + converter.checksum_converter = checksum_converter_mock() + converter.annotation_converter = annotation_converter_mock() + return converter + + +@pytest.mark.parametrize("file_property,expected", + [(FileProperty.SPDX_ID, "SPDXID"), + (FileProperty.ANNOTATIONS, "annotations"), + (FileProperty.ARTIFACT_OFS, "artifactOfs"), + (FileProperty.ATTRIBUTION_TEXTS, "attributionTexts"), + (FileProperty.CHECKSUMS, "checksums"), + (FileProperty.COMMENT, "comment"), + (FileProperty.COPYRIGHT_TEXT, "copyrightText"), + (FileProperty.FILE_CONTRIBUTORS, "fileContributors"), + (FileProperty.FILE_DEPENDENCIES, "fileDependencies"), + (FileProperty.FILE_NAME, "fileName"), + (FileProperty.FILE_TYPES, "fileTypes"), + (FileProperty.LICENSE_COMMENTS, "licenseComments"), + (FileProperty.LICENSE_CONCLUDED, "licenseConcluded"), + (FileProperty.LICENSE_INFO_IN_FILES, "licenseInfoInFiles"), + (FileProperty.NOTICE_TEXT, "noticeText")]) +def test_json_property_names(converter: FileConverter, file_property: FileProperty, expected: str): + assert converter.json_property_name(file_property) == expected + + +def test_json_type(converter: FileConverter): + assert converter.get_json_type() == FileProperty + + +def test_data_model_type(converter: FileConverter): + assert converter.get_data_model_type() == File + + +def test_successful_conversion(converter: FileConverter): + converter.checksum_converter.convert.return_value = "mock_converted_checksum" + converter.annotation_converter.convert.return_value = "mock_converted_annotation" + file = File(name="name", spdx_id="spdxId", + checksums=[Checksum(ChecksumAlgorithm.SHA224, "sha224"), Checksum(ChecksumAlgorithm.MD2, "md2")], + file_type=[FileType.SPDX, FileType.OTHER], concluded_license=LicenseExpression("licenseExpression1"), + license_info_in_file=[LicenseExpression("licenseExpression2"), LicenseExpression("licenseExpression3")], + license_comment="licenseComment", copyright_text="copyrightText", comment="comment", notice="notice", + contributors=["contributor1", "contributor2"], + attribution_texts=["attributionText1", "attributionText2"]) + + creation_info = CreationInfo("spdxVersion", "documentID", "documentName", "documentNamespace", [], + datetime(2022, 12, 4)) + + annotations = [Annotation(file.spdx_id, AnnotationType.REVIEW, Actor(ActorType.PERSON, "annotatorName"), + datetime(2022, 12, 5), "review comment")] + document = Document(creation_info, files=[file], annotations=annotations) + + converted_dict = converter.convert(file, document) + + assert converted_dict[converter.json_property_name(FileProperty.SPDX_ID)] == "spdxId" + assert converted_dict[converter.json_property_name(FileProperty.ANNOTATIONS)] == ["mock_converted_annotation"] + assert converted_dict[converter.json_property_name(FileProperty.ATTRIBUTION_TEXTS)] == ["attributionText1", + "attributionText2"] + assert converted_dict[converter.json_property_name(FileProperty.CHECKSUMS)] == ["mock_converted_checksum", + "mock_converted_checksum"] + assert converted_dict[converter.json_property_name(FileProperty.COMMENT)] == "comment" + assert converted_dict[ + converter.json_property_name(FileProperty.COPYRIGHT_TEXT)] == "copyrightText" + assert converted_dict[converter.json_property_name(FileProperty.FILE_CONTRIBUTORS)] == ["contributor1", + "contributor2"] + assert converted_dict[converter.json_property_name(FileProperty.FILE_NAME)] == "name" + assert converted_dict[converter.json_property_name(FileProperty.FILE_TYPES)] == ["SPDX", "OTHER"] + assert converted_dict[converter.json_property_name(FileProperty.LICENSE_COMMENTS)] == "licenseComment" + assert converted_dict[converter.json_property_name(FileProperty.LICENSE_CONCLUDED)] == "licenseExpression1" + assert converted_dict[converter.json_property_name(FileProperty.LICENSE_INFO_IN_FILES)] == ["licenseExpression2", + "licenseExpression3"] + assert converted_dict[converter.json_property_name(FileProperty.NOTICE_TEXT)] == "notice" From 8f97552a6c7ec21a122a1ea1e376fac5dc676148 Mon Sep 17 00:00:00 2001 From: Nicolaus Weidner Date: Tue, 20 Dec 2022 12:11:23 +0100 Subject: [PATCH 20/55] [issue-359] add snippet converter Signed-off-by: Nicolaus Weidner --- src/jsonschema/snippet_converter.py | 88 +++++++++++++++++++++ src/jsonschema/snippet_properties.py | 27 +++++++ tests/jsonschema/test_snippet_converter.py | 92 ++++++++++++++++++++++ 3 files changed, 207 insertions(+) create mode 100644 src/jsonschema/snippet_converter.py create mode 100644 src/jsonschema/snippet_properties.py create mode 100644 tests/jsonschema/test_snippet_converter.py diff --git a/src/jsonschema/snippet_converter.py b/src/jsonschema/snippet_converter.py new file mode 100644 index 000000000..b0581e2c6 --- /dev/null +++ b/src/jsonschema/snippet_converter.py @@ -0,0 +1,88 @@ +# 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. +from typing import Type, Any, Tuple, Dict + +from src.jsonschema.annotation_converter import AnnotationConverter +from src.jsonschema.converter import TypedConverter +from src.jsonschema.json_property import JsonProperty +from src.jsonschema.snippet_properties import SnippetProperty +from src.model.document import Document +from src.model.snippet import Snippet +from src.writer.casing_tools import snake_case_to_camel_case + + +class SnippetConverter(TypedConverter): + annotation_converter: AnnotationConverter + + def __init__(self): + self.annotation_converter = AnnotationConverter() + + def json_property_name(self, snippet_property: SnippetProperty) -> str: + if snippet_property == SnippetProperty.SPDX_ID: + return "SPDXID" + return snake_case_to_camel_case(snippet_property.name) + + def _get_property_value(self, snippet: Snippet, snippet_property: SnippetProperty, + document: Document = None) -> Any: + if snippet_property == SnippetProperty.SPDX_ID: + return snippet.spdx_id + elif snippet_property == SnippetProperty.ANNOTATIONS: + snippet_annotations = filter(lambda annotation: annotation.spdx_id == snippet.spdx_id, document.annotations) + return [self.annotation_converter.convert(annotation) for annotation in snippet_annotations] + elif snippet_property == SnippetProperty.ATTRIBUTION_TEXTS: + return snippet.attribution_texts + elif snippet_property == SnippetProperty.COMMENT: + return snippet.comment + elif snippet_property == SnippetProperty.COPYRIGHT_TEXT: + return snippet.copyright_text + elif snippet_property == SnippetProperty.LICENSE_COMMENTS: + return snippet.license_comment + elif snippet_property == SnippetProperty.LICENSE_CONCLUDED: + return str(snippet.concluded_license) + elif snippet_property == SnippetProperty.LICENSE_INFO_IN_SNIPPETS: + if isinstance(snippet.license_info_in_snippet, list): + return [str(license_expression) for license_expression in snippet.license_info_in_snippet] + return str(snippet.license_info_in_snippet) + elif snippet_property == SnippetProperty.NAME: + return snippet.name + elif snippet_property == SnippetProperty.RANGES: + ranges = [convert_byte_range_to_dict(snippet.byte_range, snippet.file_spdx_id)] + if snippet.line_range: + ranges.append(convert_line_range_to_dict(snippet.line_range, snippet.file_spdx_id)) + return ranges + elif snippet_property == SnippetProperty.SNIPPET_FROM_FILE: + return snippet.file_spdx_id + + def get_json_type(self) -> Type[JsonProperty]: + return SnippetProperty + + def get_data_model_type(self) -> Type[Snippet]: + return Snippet + + def requires_full_document(self) -> bool: + return True + + +def convert_line_range_to_dict(line_range: Tuple[int, int], file_id: str) -> Dict: + return _convert_range_to_dict(line_range, file_id, "lineNumber") + + +def convert_byte_range_to_dict(byte_range: Tuple[int, int], file_id: str) -> Dict: + return _convert_range_to_dict(byte_range, file_id, "offset") + + +def _convert_range_to_dict(int_range: Tuple[int, int], file_id: str, pointer_property: str) -> Dict: + return {"startPointer": _pointer(file_id, int_range[0], pointer_property), + "endPointer": _pointer(file_id, int_range[1], pointer_property)} + + +def _pointer(reference: str, target: int, pointer_property: str) -> Dict: + return {"reference": reference, pointer_property: target} diff --git a/src/jsonschema/snippet_properties.py b/src/jsonschema/snippet_properties.py new file mode 100644 index 000000000..679326b26 --- /dev/null +++ b/src/jsonschema/snippet_properties.py @@ -0,0 +1,27 @@ +# 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. +from enum import auto + +from src.jsonschema.json_property import JsonProperty + + +class SnippetProperty(JsonProperty): + SPDX_ID = auto() + ANNOTATIONS = auto() + ATTRIBUTION_TEXTS = auto() + COMMENT = auto() + COPYRIGHT_TEXT = auto() + LICENSE_COMMENTS = auto() + LICENSE_CONCLUDED = auto() + LICENSE_INFO_IN_SNIPPETS = auto() + NAME = auto() + RANGES = auto() + SNIPPET_FROM_FILE = auto() diff --git a/tests/jsonschema/test_snippet_converter.py b/tests/jsonschema/test_snippet_converter.py new file mode 100644 index 000000000..693b008ac --- /dev/null +++ b/tests/jsonschema/test_snippet_converter.py @@ -0,0 +1,92 @@ +# 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. +from datetime import datetime +from unittest import mock +from unittest.mock import MagicMock + +import pytest + +from src.jsonschema.snippet_converter import SnippetConverter +from src.jsonschema.snippet_properties import SnippetProperty +from src.model.actor import Actor, ActorType +from src.model.annotation import Annotation, AnnotationType +from src.model.document import Document, CreationInfo +from src.model.license_expression import LicenseExpression +from src.model.snippet import Snippet + + +@pytest.fixture +@mock.patch('src.jsonschema.annotation_converter.AnnotationConverter', autospec=True) +def converter(annotation_converter_mock: MagicMock) -> SnippetConverter: + converter = SnippetConverter() + converter.annotation_converter = annotation_converter_mock() + return converter + + +@pytest.mark.parametrize("snippet_property,expected", + [(SnippetProperty.SPDX_ID, "SPDXID"), + (SnippetProperty.ANNOTATIONS, "annotations"), + (SnippetProperty.ATTRIBUTION_TEXTS, "attributionTexts"), + (SnippetProperty.COMMENT, "comment"), + (SnippetProperty.COPYRIGHT_TEXT, "copyrightText"), + (SnippetProperty.LICENSE_COMMENTS, "licenseComments"), + (SnippetProperty.LICENSE_CONCLUDED, "licenseConcluded"), + (SnippetProperty.LICENSE_INFO_IN_SNIPPETS, "licenseInfoInSnippets"), + (SnippetProperty.NAME, "name"), + (SnippetProperty.RANGES, "ranges"), + (SnippetProperty.SNIPPET_FROM_FILE, "snippetFromFile")]) +def test_json_property_names(converter: SnippetConverter, snippet_property: SnippetProperty, + expected: str): + assert converter.json_property_name(snippet_property) == expected + + +def test_json_type(converter: SnippetConverter): + assert converter.get_json_type() == SnippetProperty + + +def test_data_model_type(converter: SnippetConverter): + assert converter.get_data_model_type() == Snippet + + +def test_successful_conversion(converter: SnippetConverter): + converter.annotation_converter.convert.return_value = "mock_converted_annotation" + file_spdx_id = "fileSpdxId" + snippet = Snippet("spdxId", file_spdx_id=file_spdx_id, byte_range=(1, 2), line_range=(3, 4), + concluded_license=LicenseExpression("licenseExpression1"), + license_info_in_snippet=[LicenseExpression("licenseExpression2"), + LicenseExpression("licenseExpression3")], + license_comment="licenseComment", copyright_text="copyrightText", comment="comment", name="name", + attribution_texts=["attributionText1", "attributionText2"]) + + annotation = Annotation(snippet.spdx_id, AnnotationType.OTHER, Actor(ActorType.PERSON, "annotatorName"), + datetime(2022, 12, 5), "other comment") + creation_info = CreationInfo("spdxVersion", "documentId", "documentName", "documentNamespace", [], + datetime(2022, 12, 4)) + document = Document(creation_info, snippets=[snippet], annotations=[annotation]) + converted_dict = converter.convert(snippet, document) + + assert converted_dict[converter.json_property_name(SnippetProperty.SPDX_ID)] == "spdxId" + assert converted_dict[converter.json_property_name(SnippetProperty.ANNOTATIONS)] == ["mock_converted_annotation"] + assert converted_dict[converter.json_property_name(SnippetProperty.ATTRIBUTION_TEXTS)] == ["attributionText1", + "attributionText2"] + assert converted_dict[converter.json_property_name(SnippetProperty.COMMENT)] == "comment" + assert converted_dict[converter.json_property_name(SnippetProperty.COPYRIGHT_TEXT)] == "copyrightText" + assert converted_dict[converter.json_property_name(SnippetProperty.LICENSE_COMMENTS)] == "licenseComment" + assert converted_dict[converter.json_property_name(SnippetProperty.LICENSE_CONCLUDED)] == "licenseExpression1" + assert converted_dict[converter.json_property_name(SnippetProperty.LICENSE_INFO_IN_SNIPPETS)] == [ + "licenseExpression2", "licenseExpression3"] + assert converted_dict[converter.json_property_name(SnippetProperty.NAME)] == "name" + assert converted_dict[converter.json_property_name(SnippetProperty.RANGES)] == [ + {"startPointer": {"reference": file_spdx_id, "offset": 1}, + "endPointer": {"reference": file_spdx_id, "offset": 2}}, + {"startPointer": {"reference": file_spdx_id, "lineNumber": 3}, + "endPointer": {"reference": file_spdx_id, "lineNumber": 4}}] + assert converted_dict[converter.json_property_name(SnippetProperty.SNIPPET_FROM_FILE)] == file_spdx_id From dd355d452e34b5f85096dcae82482b47533d3dac Mon Sep 17 00:00:00 2001 From: Nicolaus Weidner Date: Tue, 20 Dec 2022 17:34:11 +0100 Subject: [PATCH 21/55] [issue-359] add document converter Signed-off-by: Nicolaus Weidner --- src/jsonschema/document_converter.py | 123 +++++++++++++++++++ src/jsonschema/document_properties.py | 16 +-- tests/jsonschema/test_document_converter.py | 125 ++++++++++++++++++++ 3 files changed, 257 insertions(+), 7 deletions(-) create mode 100644 src/jsonschema/document_converter.py create mode 100644 tests/jsonschema/test_document_converter.py diff --git a/src/jsonschema/document_converter.py b/src/jsonschema/document_converter.py new file mode 100644 index 000000000..87ee98fd3 --- /dev/null +++ b/src/jsonschema/document_converter.py @@ -0,0 +1,123 @@ +# 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. +from typing import Type, Any + +from src.jsonschema.annotation_converter import AnnotationConverter +from src.jsonschema.converter import TypedConverter +from src.jsonschema.creation_info_converter import CreationInfoConverter +from src.jsonschema.document_properties import DocumentProperty +from src.jsonschema.external_document_ref_converter import ExternalDocumentRefConverter +from src.jsonschema.extracted_licensing_info_converter import ExtractedLicensingInfoConverter +from src.jsonschema.file_converter import FileConverter +from src.jsonschema.json_property import JsonProperty +from src.jsonschema.package_converter import PackageConverter +from src.jsonschema.relationship_converter import RelationshipConverter +from src.jsonschema.relationship_filters import filter_by_type_and_origin, filter_by_type_and_target, \ + find_package_contains_file_relationships, \ + find_file_contained_by_package_relationships +from src.jsonschema.snippet_converter import SnippetConverter +from src.model.document import Document +from src.model.relationship import RelationshipType +from src.writer.casing_tools import snake_case_to_camel_case + + +class DocumentConverter(TypedConverter): + creation_info_converter: CreationInfoConverter + external_document_ref_converter: ExternalDocumentRefConverter + package_converter: PackageConverter + file_converter: FileConverter + snippet_converter: SnippetConverter + annotation_converter: AnnotationConverter + relationship_converter: RelationshipConverter + extracted_licensing_info_converter: ExtractedLicensingInfoConverter + + def __init__(self): + self.external_document_ref_converter = ExternalDocumentRefConverter() + self.creation_info_converter = CreationInfoConverter() + self.package_converter = PackageConverter() + self.file_converter = FileConverter() + self.snippet_converter = SnippetConverter() + self.annotation_converter = AnnotationConverter() + self.relationship_converter = RelationshipConverter() + self.extracted_licensing_info_converter = ExtractedLicensingInfoConverter() + + def get_json_type(self) -> Type[JsonProperty]: + return DocumentProperty + + def get_data_model_type(self) -> Type[Document]: + return Document + + def json_property_name(self, document_property: DocumentProperty) -> str: + if document_property == DocumentProperty.SPDX_ID: + return "SPDXID" + return snake_case_to_camel_case(document_property.name) + + def _get_property_value(self, document: Document, document_property: DocumentProperty, + _document: Document = None) -> Any: + if document_property == DocumentProperty.SPDX_ID: + return document.creation_info.spdx_id + elif document_property == DocumentProperty.ANNOTATIONS: + # annotations referencing files, packages or snippets will be added to those elements directly + element_ids = [file.spdx_id for file in document.files] + element_ids.extend([package.spdx_id for package in document.packages]) + element_ids.extend([snippet.spdx_id for snippet in document.snippets]) + document_annotations = filter(lambda annotation: annotation.spdx_id not in element_ids, + document.annotations) + return [self.annotation_converter.convert(annotation) for annotation in document_annotations] + elif document_property == DocumentProperty.COMMENT: + return document.creation_info.document_comment + elif document_property == DocumentProperty.CREATION_INFO: + return self.creation_info_converter.convert(document.creation_info) + elif document_property == DocumentProperty.DATA_LICENSE: + return document.creation_info.data_license + elif document_property == DocumentProperty.EXTERNAL_DOCUMENT_REFS: + return [self.external_document_ref_converter.convert(external_document_ref) for + external_document_ref in document.creation_info.external_document_refs] + elif document_property == DocumentProperty.HAS_EXTRACTED_LICENSING_INFO: + return [self.extracted_licensing_info_converter.convert(licensing_info) for licensing_info in + document.extracted_licensing_info] + elif document_property == DocumentProperty.NAME: + return document.creation_info.name + elif document_property == DocumentProperty.REVIEWEDS: + # Deprecated, converted to relationship on parsing + pass + elif document_property == DocumentProperty.SPDX_VERSION: + return document.creation_info.spdx_version + elif document_property == DocumentProperty.DOCUMENT_NAMESPACE: + return document.creation_info.document_namespace + elif document_property == DocumentProperty.DOCUMENT_DESCRIBES: + describes_ids = [relationship.related_spdx_element_id for relationship in + filter_by_type_and_origin(document.relationships, RelationshipType.DESCRIBES, + document.creation_info.spdx_id)] + described_by_ids = [relationship.spdx_element_id for relationship in + filter_by_type_and_target(document.relationships, RelationshipType.DESCRIBED_BY, + document.creation_info.spdx_id)] + return describes_ids + described_by_ids + elif document_property == DocumentProperty.PACKAGES: + return [self.package_converter.convert(package, document) for package in document.packages] + elif document_property == DocumentProperty.FILES: + return [self.file_converter.convert(file, document) for file in document.files] + elif document_property == DocumentProperty.SNIPPETS: + return [self.snippet_converter.convert(snippet, document) for snippet in document.snippets] + elif document_property == DocumentProperty.RELATIONSHIPS: + already_covered_relationships = filter_by_type_and_origin(document.relationships, + RelationshipType.DESCRIBES, + document.creation_info.spdx_id) + already_covered_relationships.extend( + filter_by_type_and_target(document.relationships, RelationshipType.DESCRIBED_BY, + document.creation_info.spdx_id)) + for package in document.packages: + already_covered_relationships.extend(find_package_contains_file_relationships(document, package)) + already_covered_relationships.extend(find_file_contained_by_package_relationships(document, package)) + relationships_to_ignore = [relationship for relationship in already_covered_relationships if + relationship.comment is None] + return [self.relationship_converter.convert(relationship) for relationship in document.relationships if + relationship not in relationships_to_ignore] diff --git a/src/jsonschema/document_properties.py b/src/jsonschema/document_properties.py index 444a966bd..136634e6c 100644 --- a/src/jsonschema/document_properties.py +++ b/src/jsonschema/document_properties.py @@ -14,17 +14,19 @@ class DocumentProperty(JsonProperty): - SPDX_VERSION = auto() SPDX_ID = auto() - NAME = auto() - DOCUMENT_NAMESPACE = auto() - DATA_LICENSE = auto() - EXTERNAL_DOCUMENT_REFS = auto() + ANNOTATIONS = auto() COMMENT = auto() CREATION_INFO = auto() + DATA_LICENSE = auto() + EXTERNAL_DOCUMENT_REFS = auto() + HAS_EXTRACTED_LICENSING_INFO = auto() + NAME = auto() + REVIEWEDS = auto() + SPDX_VERSION = auto() + DOCUMENT_NAMESPACE = auto() + DOCUMENT_DESCRIBES = auto() PACKAGES = auto() FILES = auto() SNIPPETS = auto() - ANNOTATIONS = auto() RELATIONSHIPS = auto() - HAS_EXTRACTED_LICENSING_INFO = auto() diff --git a/tests/jsonschema/test_document_converter.py b/tests/jsonschema/test_document_converter.py new file mode 100644 index 000000000..73088366e --- /dev/null +++ b/tests/jsonschema/test_document_converter.py @@ -0,0 +1,125 @@ +# 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. +from datetime import datetime +from unittest import mock +from unittest.mock import MagicMock + +import pytest + +from src.jsonschema.document_converter import DocumentConverter +from src.jsonschema.document_properties import DocumentProperty +from src.model.actor import Actor, ActorType +from src.model.annotation import Annotation, AnnotationType +from src.model.checksum import Checksum, ChecksumAlgorithm +from src.model.document import Document, CreationInfo +from src.model.external_document_ref import ExternalDocumentRef +from src.model.extracted_licensing_info import ExtractedLicensingInfo +from src.model.file import File +from src.model.package import Package +from src.model.relationship import Relationship, RelationshipType +from src.model.snippet import Snippet +from src.model.spdx_none import SpdxNone + + +@pytest.fixture +@mock.patch('src.jsonschema.creation_info_converter.CreationInfoConverter', autospec=True) +@mock.patch('src.jsonschema.external_document_ref_converter.ExternalDocumentRefConverter', autospec=True) +@mock.patch('src.jsonschema.package_converter.PackageConverter', autospec=True) +@mock.patch('src.jsonschema.annotation_converter.AnnotationConverter', autospec=True) +@mock.patch('src.jsonschema.extracted_licensing_info_converter.ExtractedLicensingInfoConverter', autospec=True) +@mock.patch('src.jsonschema.file_converter.FileConverter', autospec=True) +@mock.patch('src.jsonschema.snippet_converter.SnippetConverter', autospec=True) +@mock.patch('src.jsonschema.relationship_converter.RelationshipConverter', autospec=True) +def converter(external_ref_converter_mock: MagicMock, creation_info_converter_mock: MagicMock, + package_converter_mock: MagicMock, annotation_converter_mock: MagicMock, + extracted_licensing_info_converter_mock: MagicMock, file_converter_mock: MagicMock, + snippet_converter_mock: MagicMock, relationship_converter_mock: MagicMock) -> DocumentConverter: + converter = DocumentConverter() + converter.creation_info_converter = creation_info_converter_mock() + converter.external_document_ref_converter = external_ref_converter_mock() + converter.package_converter = package_converter_mock() + converter.annotation_converter = annotation_converter_mock() + converter.extracted_licensing_info_converter = extracted_licensing_info_converter_mock() + converter.file_converter = file_converter_mock() + converter.snippet_converter = snippet_converter_mock() + converter.relationship_converter = relationship_converter_mock() + return converter + + +@pytest.mark.parametrize("document_property,expected", + [(DocumentProperty.SPDX_VERSION, "spdxVersion"), (DocumentProperty.SPDX_ID, "SPDXID"), + (DocumentProperty.NAME, "name"), (DocumentProperty.DOCUMENT_NAMESPACE, "documentNamespace"), + (DocumentProperty.DATA_LICENSE, "dataLicense"), + (DocumentProperty.EXTERNAL_DOCUMENT_REFS, "externalDocumentRefs"), + (DocumentProperty.COMMENT, "comment"), (DocumentProperty.CREATION_INFO, "creationInfo"), + (DocumentProperty.PACKAGES, "packages"), (DocumentProperty.FILES, "files"), + (DocumentProperty.SNIPPETS, "snippets"), (DocumentProperty.ANNOTATIONS, "annotations"), + (DocumentProperty.RELATIONSHIPS, "relationships"), + (DocumentProperty.HAS_EXTRACTED_LICENSING_INFO, "hasExtractedLicensingInfo")]) +def test_json_property_names(converter: DocumentConverter, document_property: DocumentProperty, + expected: str): + assert converter.json_property_name(document_property) == expected + + +def test_successful_conversion(converter: DocumentConverter): + creation_info = CreationInfo("spdxVersion", "spdxId", "name", "namespace", [], datetime.today(), + document_comment="comment", data_license="dataLicense", external_document_refs=[ + ExternalDocumentRef("docRefId", "externalDocumentUri", Checksum(ChecksumAlgorithm.SHA1, "sha1"))]) + package = Package("packageID", "packageName", SpdxNone()) + file = File("fileName", "fileId", []) + snippet = Snippet("snippetId", "snippetFileId", (1, 2)) + document = Document(creation_info, annotations=[ + Annotation("annotationId", AnnotationType.REVIEW, Actor(ActorType.PERSON, "reviewerName"), + datetime(2022, 12, 1), "reviewComment")], + extracted_licensing_info=[ExtractedLicensingInfo("licenseId", "licenseText")], relationships=[ + Relationship(creation_info.spdx_id, RelationshipType.DESCRIBES, "describedElementId"), + Relationship("relationshipOriginId", RelationshipType.AMENDS, "relationShipTargetId")], packages=[package], + files=[file], snippets=[snippet]) + converter.external_document_ref_converter.convert.return_value = "mock_converted_external_ref" + converter.creation_info_converter.convert.return_value = "mock_converted_creation_info" + converter.package_converter.convert.return_value = "mock_converted_package" + converter.annotation_converter.convert.return_value = "mock_converted_annotation" + converter.extracted_licensing_info_converter.convert.return_value = "mock_converted_extracted_licensing_info" + converter.package_converter.convert.return_value = "mock_converted_package" + converter.file_converter.convert.return_value = "mock_converted_file" + converter.snippet_converter.convert.return_value = "mock_converted_snippet" + converter.relationship_converter.convert.return_value = "mock_converted_relationship" + + converted_dict = converter.convert(document) + + assert converted_dict[converter.json_property_name(DocumentProperty.SPDX_ID)] == "spdxId" + assert converted_dict[converter.json_property_name(DocumentProperty.ANNOTATIONS)] == ["mock_converted_annotation"] + assert converted_dict[converter.json_property_name(DocumentProperty.COMMENT)] == "comment" + assert converted_dict[ + converter.json_property_name(DocumentProperty.CREATION_INFO)] == "mock_converted_creation_info" + assert converted_dict[converter.json_property_name(DocumentProperty.DATA_LICENSE)] == "dataLicense" + assert converted_dict[ + converter.json_property_name( + DocumentProperty.EXTERNAL_DOCUMENT_REFS)] == ["mock_converted_external_ref"] + assert converted_dict[converter.json_property_name(DocumentProperty.HAS_EXTRACTED_LICENSING_INFO)] == [ + "mock_converted_extracted_licensing_info"] + assert converted_dict[converter.json_property_name(DocumentProperty.NAME)] == "name" + assert converted_dict[converter.json_property_name(DocumentProperty.SPDX_VERSION)] == "spdxVersion" + assert converted_dict[converter.json_property_name(DocumentProperty.DOCUMENT_NAMESPACE)] == "namespace" + assert converted_dict[converter.json_property_name(DocumentProperty.DOCUMENT_DESCRIBES)] == ["describedElementId"] + assert converted_dict[converter.json_property_name(DocumentProperty.PACKAGES)] == ["mock_converted_package"] + assert converted_dict[converter.json_property_name(DocumentProperty.FILES)] == ["mock_converted_file"] + assert converted_dict[converter.json_property_name(DocumentProperty.SNIPPETS)] == ["mock_converted_snippet"] + assert converted_dict[converter.json_property_name(DocumentProperty.RELATIONSHIPS)] == [ + "mock_converted_relationship"] + + +def test_json_type(converter: DocumentConverter): + assert converter.get_json_type() == DocumentProperty + + +def test_data_model_type(converter: DocumentConverter): + assert converter.get_data_model_type() == Document From e92d3988d30854898ddd9fe6fa0a3b369edb0d27 Mon Sep 17 00:00:00 2001 From: Nicolaus Weidner Date: Fri, 30 Dec 2022 17:29:27 +0100 Subject: [PATCH 22/55] [issue-359][squash] Remove deprecated REVIEWEDS property Signed-off-by: Nicolaus Weidner --- src/jsonschema/document_converter.py | 3 --- src/jsonschema/document_properties.py | 1 - 2 files changed, 4 deletions(-) diff --git a/src/jsonschema/document_converter.py b/src/jsonschema/document_converter.py index 87ee98fd3..3e0ad398f 100644 --- a/src/jsonschema/document_converter.py +++ b/src/jsonschema/document_converter.py @@ -86,9 +86,6 @@ def _get_property_value(self, document: Document, document_property: DocumentPro document.extracted_licensing_info] elif document_property == DocumentProperty.NAME: return document.creation_info.name - elif document_property == DocumentProperty.REVIEWEDS: - # Deprecated, converted to relationship on parsing - pass elif document_property == DocumentProperty.SPDX_VERSION: return document.creation_info.spdx_version elif document_property == DocumentProperty.DOCUMENT_NAMESPACE: diff --git a/src/jsonschema/document_properties.py b/src/jsonschema/document_properties.py index 136634e6c..e684fe80f 100644 --- a/src/jsonschema/document_properties.py +++ b/src/jsonschema/document_properties.py @@ -22,7 +22,6 @@ class DocumentProperty(JsonProperty): EXTERNAL_DOCUMENT_REFS = auto() HAS_EXTRACTED_LICENSING_INFO = auto() NAME = auto() - REVIEWEDS = auto() SPDX_VERSION = auto() DOCUMENT_NAMESPACE = auto() DOCUMENT_DESCRIBES = auto() From f3bc25b4d80b0bf27974dcb772c6a251ec6e8d33 Mon Sep 17 00:00:00 2001 From: Nicolaus Weidner Date: Tue, 20 Dec 2022 23:33:03 +0100 Subject: [PATCH 23/55] [issue-359] json converters no longer set dictionary properties if the value is None Signed-off-by: Nicolaus Weidner --- src/jsonschema/converter.py | 5 +++- src/jsonschema/creation_info_converter.py | 3 +- src/jsonschema/file_converter.py | 7 +++-- src/jsonschema/optional_utils.py | 18 ++++++++++++ src/jsonschema/package_converter.py | 25 ++++++++-------- src/jsonschema/snippet_converter.py | 7 +++-- .../test_creation_info_converter.py | 9 ++++++ tests/jsonschema/test_file_converter.py | 18 ++++++++++++ tests/jsonschema/test_package_converter.py | 29 +++++++++++++++++++ tests/jsonschema/test_snippet_converter.py | 15 ++++++++++ 10 files changed, 116 insertions(+), 20 deletions(-) create mode 100644 src/jsonschema/optional_utils.py diff --git a/src/jsonschema/converter.py b/src/jsonschema/converter.py index a4e03f3c3..edecf3be5 100644 --- a/src/jsonschema/converter.py +++ b/src/jsonschema/converter.py @@ -47,5 +47,8 @@ def convert(self, instance: Any, document: Document = None) -> Dict: result = {} for property_name in self.get_json_type(): - result[self.json_property_name(property_name)] = self._get_property_value(instance, property_name, document) + property_value = self._get_property_value(instance, property_name, document) + if property_value is None: + continue + result[self.json_property_name(property_name)] = property_value return result diff --git a/src/jsonschema/creation_info_converter.py b/src/jsonschema/creation_info_converter.py index 656126203..36dec0215 100644 --- a/src/jsonschema/creation_info_converter.py +++ b/src/jsonschema/creation_info_converter.py @@ -14,6 +14,7 @@ from src.jsonschema.converter import TypedConverter from src.jsonschema.creation_info_properties import CreationInfoProperty from src.jsonschema.json_property import JsonProperty +from src.jsonschema.optional_utils import apply_if_present from src.model.document import CreationInfo, Document from src.writer.casing_tools import snake_case_to_camel_case @@ -35,6 +36,6 @@ def _get_property_value(self, creation_info: CreationInfo, creation_info_propert elif creation_info_property == CreationInfoProperty.CREATORS: return [creator.to_serialized_string() for creator in creation_info.creators] elif creation_info_property == CreationInfoProperty.LICENSE_LIST_VERSION: - return str(creation_info.license_list_version) + return apply_if_present(str, creation_info.license_list_version) elif creation_info_property == CreationInfoProperty.COMMENT: return creation_info.creator_comment diff --git a/src/jsonschema/file_converter.py b/src/jsonschema/file_converter.py index 19d96b180..e2309ee8b 100644 --- a/src/jsonschema/file_converter.py +++ b/src/jsonschema/file_converter.py @@ -15,6 +15,7 @@ from src.jsonschema.converter import TypedConverter from src.jsonschema.file_properties import FileProperty from src.jsonschema.json_property import JsonProperty +from src.jsonschema.optional_utils import apply_if_present from src.model.document import Document from src.model.file import File from src.writer.casing_tools import snake_case_to_camel_case @@ -49,7 +50,7 @@ def _get_property_value(self, file: Any, file_property: FileProperty, document: elif file_property == FileProperty.COMMENT: return file.comment elif file_property == FileProperty.COPYRIGHT_TEXT: - return file.copyright_text + return apply_if_present(str, file.copyright_text) elif file_property == FileProperty.FILE_CONTRIBUTORS: return file.contributors elif file_property == FileProperty.FILE_DEPENDENCIES: @@ -62,11 +63,11 @@ def _get_property_value(self, file: Any, file_property: FileProperty, document: elif file_property == FileProperty.LICENSE_COMMENTS: return file.license_comment elif file_property == FileProperty.LICENSE_CONCLUDED: - return str(file.concluded_license) + return apply_if_present(str, file.concluded_license) elif file_property == FileProperty.LICENSE_INFO_IN_FILES: if isinstance(file.license_info_in_file, list): return [str(license_expression) for license_expression in file.license_info_in_file] - return str(file.license_info_in_file) + return apply_if_present(str, file.license_info_in_file) elif file_property == FileProperty.NOTICE_TEXT: return file.notice diff --git a/src/jsonschema/optional_utils.py b/src/jsonschema/optional_utils.py new file mode 100644 index 000000000..b560f83fc --- /dev/null +++ b/src/jsonschema/optional_utils.py @@ -0,0 +1,18 @@ +# 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. +from typing import Callable, TypeVar, Optional + +T = TypeVar("T") +S = TypeVar("S") + + +def apply_if_present(function: Callable[[T], S], optional_value: Optional[T]) -> S: + return function(optional_value) if optional_value else None diff --git a/src/jsonschema/package_converter.py b/src/jsonschema/package_converter.py index ff140251d..3e016330b 100644 --- a/src/jsonschema/package_converter.py +++ b/src/jsonschema/package_converter.py @@ -16,6 +16,7 @@ from src.jsonschema.converter import TypedConverter from src.jsonschema.external_package_ref_converter import ExternalPackageRefConverter from src.jsonschema.json_property import JsonProperty +from src.jsonschema.optional_utils import apply_if_present from src.jsonschema.package_properties import PackageProperty from src.jsonschema.package_verification_code_converter import PackageVerificationCodeConverter from src.jsonschema.relationship_filters import find_package_contains_file_relationships, \ @@ -53,13 +54,13 @@ def _get_property_value(self, package: Package, package_property: PackagePropert elif package_property == PackageProperty.ATTRIBUTION_TEXTS: return package.attribution_texts elif package_property == PackageProperty.BUILT_DATE: - return datetime_to_iso_string(package.built_date) + return apply_if_present(datetime_to_iso_string, package.built_date) elif package_property == PackageProperty.CHECKSUMS: return [self.checksum_converter.convert(checksum, document) for checksum in package.checksums] elif package_property == PackageProperty.COMMENT: return package.comment elif package_property == PackageProperty.COPYRIGHT_TEXT: - return str(package.copyright_text) + return apply_if_present(str, package.copyright_text) elif package_property == PackageProperty.DESCRIPTION: return package.description elif package_property == PackageProperty.DOWNLOAD_LOCATION: @@ -76,31 +77,31 @@ def _get_property_value(self, package: Package, package_property: PackagePropert find_file_contained_by_package_relationships(document, package)] return package_contains_file_ids + file_contained_in_package_ids elif package_property == PackageProperty.HOMEPAGE: - return str(package.homepage) + return apply_if_present(str, package.homepage) elif package_property == PackageProperty.LICENSE_COMMENTS: return package.license_comment elif package_property == PackageProperty.LICENSE_CONCLUDED: - return str(package.license_concluded) + return apply_if_present(str, package.license_concluded) elif package_property == PackageProperty.LICENSE_DECLARED: - return str(package.license_declared) + return apply_if_present(str, package.license_declared) elif package_property == PackageProperty.LICENSE_INFO_FROM_FILES: if isinstance(package.license_info_from_files, list): return [str(license_expression) for license_expression in package.license_info_from_files] - return str(package.license_info_from_files) + return apply_if_present(str, package.license_info_from_files) elif package_property == PackageProperty.NAME: return package.name elif package_property == PackageProperty.ORIGINATOR: if isinstance(package.originator, Actor): return package.originator.to_serialized_string() - return str(package.originator) + return apply_if_present(str, package.originator) elif package_property == PackageProperty.PACKAGE_FILE_NAME: return package.file_name elif package_property == PackageProperty.PACKAGE_VERIFICATION_CODE: - return self.package_verification_code_converter.convert(package.verification_code) + return apply_if_present(self.package_verification_code_converter.convert, package.verification_code) elif package_property == PackageProperty.PRIMARY_PACKAGE_PURPOSE: - return package.primary_package_purpose.name + return package.primary_package_purpose.name if package.primary_package_purpose is not None else None elif package_property == PackageProperty.RELEASE_DATE: - return datetime_to_iso_string(package.release_date) + return apply_if_present(datetime_to_iso_string, package.release_date) elif package_property == PackageProperty.SOURCE_INFO: return package.source_info elif package_property == PackageProperty.SUMMARY: @@ -108,9 +109,9 @@ def _get_property_value(self, package: Package, package_property: PackagePropert elif package_property == PackageProperty.SUPPLIER: if isinstance(package.supplier, Actor): return package.supplier.to_serialized_string() - return str(package.supplier) + return apply_if_present(str, package.supplier) elif package_property == PackageProperty.VALID_UNTIL_DATE: - return datetime_to_iso_string(package.valid_until_date) + return apply_if_present(datetime_to_iso_string, package.valid_until_date) elif package_property == PackageProperty.VERSION_INFO: return package.version diff --git a/src/jsonschema/snippet_converter.py b/src/jsonschema/snippet_converter.py index b0581e2c6..d726d63b9 100644 --- a/src/jsonschema/snippet_converter.py +++ b/src/jsonschema/snippet_converter.py @@ -13,6 +13,7 @@ from src.jsonschema.annotation_converter import AnnotationConverter from src.jsonschema.converter import TypedConverter from src.jsonschema.json_property import JsonProperty +from src.jsonschema.optional_utils import apply_if_present from src.jsonschema.snippet_properties import SnippetProperty from src.model.document import Document from src.model.snippet import Snippet @@ -42,15 +43,15 @@ def _get_property_value(self, snippet: Snippet, snippet_property: SnippetPropert elif snippet_property == SnippetProperty.COMMENT: return snippet.comment elif snippet_property == SnippetProperty.COPYRIGHT_TEXT: - return snippet.copyright_text + return apply_if_present(str, snippet.copyright_text) elif snippet_property == SnippetProperty.LICENSE_COMMENTS: return snippet.license_comment elif snippet_property == SnippetProperty.LICENSE_CONCLUDED: - return str(snippet.concluded_license) + return apply_if_present(str, snippet.concluded_license) elif snippet_property == SnippetProperty.LICENSE_INFO_IN_SNIPPETS: if isinstance(snippet.license_info_in_snippet, list): return [str(license_expression) for license_expression in snippet.license_info_in_snippet] - return str(snippet.license_info_in_snippet) + return apply_if_present(str, snippet.license_info_in_snippet) elif snippet_property == SnippetProperty.NAME: return snippet.name elif snippet_property == SnippetProperty.RANGES: diff --git a/tests/jsonschema/test_creation_info_converter.py b/tests/jsonschema/test_creation_info_converter.py index 8b1cb4b2d..f27081dca 100644 --- a/tests/jsonschema/test_creation_info_converter.py +++ b/tests/jsonschema/test_creation_info_converter.py @@ -49,6 +49,15 @@ def test_successful_conversion(converter: CreationInfoConverter): assert converted_dict[converter.json_property_name(CreationInfoProperty.COMMENT)] == "comment" +def test_null_values(converter: CreationInfoConverter): + creation_info = CreationInfo("irrelevant", "irrelevant", "irrelevant", "irrelevant", [], datetime(2022, 12, 1)) + + converted_dict = converter.convert(creation_info) + + assert converter.json_property_name(CreationInfoProperty.LICENSE_LIST_VERSION) not in converted_dict + assert converter.json_property_name(CreationInfoProperty.COMMENT) not in converted_dict + + def test_json_type(converter: CreationInfoConverter): assert converter.get_json_type() == CreationInfoProperty diff --git a/tests/jsonschema/test_file_converter.py b/tests/jsonschema/test_file_converter.py index a7846b756..1acd74918 100644 --- a/tests/jsonschema/test_file_converter.py +++ b/tests/jsonschema/test_file_converter.py @@ -100,3 +100,21 @@ def test_successful_conversion(converter: FileConverter): assert converted_dict[converter.json_property_name(FileProperty.LICENSE_INFO_IN_FILES)] == ["licenseExpression2", "licenseExpression3"] assert converted_dict[converter.json_property_name(FileProperty.NOTICE_TEXT)] == "notice" + + +def test_null_values(converter: FileConverter): + file = File(name="name", spdx_id="spdxId", + checksums=[Checksum(ChecksumAlgorithm.SHA224, "sha224"), Checksum(ChecksumAlgorithm.MD2, "md2")]) + + creation_info = CreationInfo("spdxVersion", "documentID", "documentName", "documentNamespace", [], + datetime(2022, 12, 4)) + + document = Document(creation_info, files=[file]) + + converted_dict = converter.convert(file, document) + + assert converter.json_property_name(FileProperty.COPYRIGHT_TEXT) not in converted_dict + assert converter.json_property_name(FileProperty.LICENSE_CONCLUDED) not in converted_dict + assert converter.json_property_name(FileProperty.LICENSE_COMMENTS) not in converted_dict + assert converter.json_property_name(FileProperty.COMMENT) not in converted_dict + assert converter.json_property_name(FileProperty.NOTICE_TEXT) not in converted_dict diff --git a/tests/jsonschema/test_package_converter.py b/tests/jsonschema/test_package_converter.py index a1aeb3abe..324b64ccb 100644 --- a/tests/jsonschema/test_package_converter.py +++ b/tests/jsonschema/test_package_converter.py @@ -146,3 +146,32 @@ def test_successful_conversion(converter: PackageConverter): assert converted_dict[converter.json_property_name(PackageProperty.RELEASE_DATE)] == "2022-12-01T00:00:00Z" assert converted_dict[converter.json_property_name(PackageProperty.BUILT_DATE)] == "2022-12-02T00:00:00Z" assert converted_dict[converter.json_property_name(PackageProperty.VALID_UNTIL_DATE)] == "2022-12-03T00:00:00Z" + + +def test_null_values(converter: PackageConverter): + package = Package(spdx_id="packageId", name="name", download_location="downloadLocation") + + creation_info = CreationInfo("spdxVersion", "documentID", "documentName", "documentNamespace", [], + datetime(2022, 12, 4)) + document = Document(creation_info, packages=[package]) + + converted_dict = converter.convert(package, document) + + assert converter.json_property_name(PackageProperty.VERSION_INFO) not in converted_dict + assert converter.json_property_name(PackageProperty.PACKAGE_FILE_NAME) not in converted_dict + assert converter.json_property_name(PackageProperty.SUPPLIER) not in converted_dict + assert converter.json_property_name(PackageProperty.ORIGINATOR) not in converted_dict + assert converter.json_property_name(PackageProperty.PACKAGE_VERIFICATION_CODE) not in converted_dict + assert converter.json_property_name(PackageProperty.HOMEPAGE) not in converted_dict + assert converter.json_property_name(PackageProperty.SOURCE_INFO) not in converted_dict + assert converter.json_property_name(PackageProperty.LICENSE_CONCLUDED) not in converted_dict + assert converter.json_property_name(PackageProperty.LICENSE_DECLARED) not in converted_dict + assert converter.json_property_name(PackageProperty.LICENSE_COMMENTS) not in converted_dict + assert converter.json_property_name(PackageProperty.COPYRIGHT_TEXT) not in converted_dict + assert converter.json_property_name(PackageProperty.SUMMARY) not in converted_dict + assert converter.json_property_name(PackageProperty.DESCRIPTION) not in converted_dict + assert converter.json_property_name(PackageProperty.COMMENT) not in converted_dict + assert converter.json_property_name(PackageProperty.PRIMARY_PACKAGE_PURPOSE) not in converted_dict + assert converter.json_property_name(PackageProperty.BUILT_DATE) not in converted_dict + assert converter.json_property_name(PackageProperty.RELEASE_DATE) not in converted_dict + assert converter.json_property_name(PackageProperty.VALID_UNTIL_DATE) not in converted_dict diff --git a/tests/jsonschema/test_snippet_converter.py b/tests/jsonschema/test_snippet_converter.py index 693b008ac..6008d63b0 100644 --- a/tests/jsonschema/test_snippet_converter.py +++ b/tests/jsonschema/test_snippet_converter.py @@ -90,3 +90,18 @@ def test_successful_conversion(converter: SnippetConverter): {"startPointer": {"reference": file_spdx_id, "lineNumber": 3}, "endPointer": {"reference": file_spdx_id, "lineNumber": 4}}] assert converted_dict[converter.json_property_name(SnippetProperty.SNIPPET_FROM_FILE)] == file_spdx_id + + +def test_null_values(converter: SnippetConverter): + snippet = Snippet("spdxId", file_spdx_id="fileId", byte_range=(1, 2)) + + creation_info = CreationInfo("spdxVersion", "documentId", "documentName", "documentNamespace", [], + datetime(2022, 12, 4)) + document = Document(creation_info, snippets=[snippet]) + converted_dict = converter.convert(snippet, document) + + assert converter.json_property_name(SnippetProperty.LICENSE_CONCLUDED) not in converted_dict + assert converter.json_property_name(SnippetProperty.LICENSE_COMMENTS) not in converted_dict + assert converter.json_property_name(SnippetProperty.COPYRIGHT_TEXT) not in converted_dict + assert converter.json_property_name(SnippetProperty.COMMENT) not in converted_dict + assert converter.json_property_name(SnippetProperty.NAME) not in converted_dict From f4c313fb7a59f1283f27efc33027e7acc5d4ba9d Mon Sep 17 00:00:00 2001 From: Nicolaus Weidner Date: Fri, 30 Dec 2022 17:54:00 +0100 Subject: [PATCH 24/55] [issue-359][squash] Fix optional utils return value Signed-off-by: Nicolaus Weidner --- src/jsonschema/optional_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jsonschema/optional_utils.py b/src/jsonschema/optional_utils.py index b560f83fc..d30fd7607 100644 --- a/src/jsonschema/optional_utils.py +++ b/src/jsonschema/optional_utils.py @@ -14,5 +14,5 @@ S = TypeVar("S") -def apply_if_present(function: Callable[[T], S], optional_value: Optional[T]) -> S: +def apply_if_present(function: Callable[[T], S], optional_value: Optional[T]) -> Optional[S]: return function(optional_value) if optional_value else None From dc29f1d81db71d14ec0371c530c395b2865eec84 Mon Sep 17 00:00:00 2001 From: Nicolaus Weidner Date: Wed, 21 Dec 2022 09:26:19 +0100 Subject: [PATCH 25/55] [issue-359] Improve converter typing and some parameter names Signed-off-by: Nicolaus Weidner --- src/jsonschema/annotation_converter.py | 2 +- src/jsonschema/checksum_converter.py | 2 +- src/jsonschema/converter.py | 14 ++++++++------ src/jsonschema/creation_info_converter.py | 2 +- src/jsonschema/document_converter.py | 2 +- src/jsonschema/external_document_ref_converter.py | 15 ++++++++------- src/jsonschema/external_package_ref_converter.py | 2 +- .../extracted_licensing_info_converter.py | 2 +- src/jsonschema/file_converter.py | 2 +- src/jsonschema/package_converter.py | 2 +- .../package_verification_code_converter.py | 2 +- src/jsonschema/relationship_converter.py | 2 +- src/jsonschema/snippet_converter.py | 2 +- 13 files changed, 27 insertions(+), 24 deletions(-) diff --git a/src/jsonschema/annotation_converter.py b/src/jsonschema/annotation_converter.py index a8f30d5a1..503da69e9 100644 --- a/src/jsonschema/annotation_converter.py +++ b/src/jsonschema/annotation_converter.py @@ -19,7 +19,7 @@ from src.writer.casing_tools import snake_case_to_camel_case -class AnnotationConverter(TypedConverter): +class AnnotationConverter(TypedConverter[Annotation]): def json_property_name(self, annotation_property: AnnotationProperty) -> str: return snake_case_to_camel_case(annotation_property.name) diff --git a/src/jsonschema/checksum_converter.py b/src/jsonschema/checksum_converter.py index 695f4df7b..d68710234 100644 --- a/src/jsonschema/checksum_converter.py +++ b/src/jsonschema/checksum_converter.py @@ -18,7 +18,7 @@ from src.writer.casing_tools import snake_case_to_camel_case -class ChecksumConverter(TypedConverter): +class ChecksumConverter(TypedConverter[Checksum]): def get_data_model_type(self) -> Type[Checksum]: return Checksum diff --git a/src/jsonschema/converter.py b/src/jsonschema/converter.py index edecf3be5..1d6d348fa 100644 --- a/src/jsonschema/converter.py +++ b/src/jsonschema/converter.py @@ -9,21 +9,23 @@ # See the License for the specific language governing permissions and # limitations under the License. from abc import ABC, abstractmethod -from typing import Any, Type, Dict +from typing import Any, Type, Dict, TypeVar, Generic from src.jsonschema.json_property import JsonProperty from src.model.document import Document MISSING_IMPLEMENTATION_MESSAGE = "Must be implemented" +T = TypeVar("T") -class TypedConverter(ABC): + +class TypedConverter(ABC, Generic[T]): @abstractmethod - def json_property_name(self, property_thing: JsonProperty) -> str: + def json_property_name(self, json_property: JsonProperty) -> str: raise NotImplementedError(MISSING_IMPLEMENTATION_MESSAGE) @abstractmethod - def _get_property_value(self, instance: Any, property_thing: JsonProperty, document: Document = None) -> Any: + def _get_property_value(self, instance: T, json_property: JsonProperty, document: Document = None) -> Any: raise NotImplementedError(MISSING_IMPLEMENTATION_MESSAGE) @abstractmethod @@ -31,13 +33,13 @@ def get_json_type(self) -> Type[JsonProperty]: raise NotImplementedError(MISSING_IMPLEMENTATION_MESSAGE) @abstractmethod - def get_data_model_type(self) -> Type: + def get_data_model_type(self) -> Type[T]: raise NotImplementedError(MISSING_IMPLEMENTATION_MESSAGE) def requires_full_document(self) -> bool: return False - def convert(self, instance: Any, document: Document = None) -> Dict: + def convert(self, instance: T, document: Document = None) -> Dict: if not isinstance(instance, self.get_data_model_type()): raise TypeError( f"Converter of type {self.__class__} can only convert objects of type " diff --git a/src/jsonschema/creation_info_converter.py b/src/jsonschema/creation_info_converter.py index 36dec0215..04734ea8c 100644 --- a/src/jsonschema/creation_info_converter.py +++ b/src/jsonschema/creation_info_converter.py @@ -19,7 +19,7 @@ from src.writer.casing_tools import snake_case_to_camel_case -class CreationInfoConverter(TypedConverter): +class CreationInfoConverter(TypedConverter[CreationInfo]): def get_data_model_type(self) -> Type[CreationInfo]: return CreationInfo diff --git a/src/jsonschema/document_converter.py b/src/jsonschema/document_converter.py index 3e0ad398f..98f5b784a 100644 --- a/src/jsonschema/document_converter.py +++ b/src/jsonschema/document_converter.py @@ -29,7 +29,7 @@ from src.writer.casing_tools import snake_case_to_camel_case -class DocumentConverter(TypedConverter): +class DocumentConverter(TypedConverter[Document]): creation_info_converter: CreationInfoConverter external_document_ref_converter: ExternalDocumentRefConverter package_converter: PackageConverter diff --git a/src/jsonschema/external_document_ref_converter.py b/src/jsonschema/external_document_ref_converter.py index ccb5e0be3..6ac16d56e 100644 --- a/src/jsonschema/external_document_ref_converter.py +++ b/src/jsonschema/external_document_ref_converter.py @@ -19,22 +19,23 @@ from src.writer.casing_tools import snake_case_to_camel_case -class ExternalDocumentRefConverter(TypedConverter): +class ExternalDocumentRefConverter(TypedConverter[ExternalDocumentRef]): checksum_converter: ChecksumConverter def __init__(self): self.checksum_converter = ChecksumConverter() - def json_property_name(self, property_thing: ExternalDocumentRefProperty) -> str: - return snake_case_to_camel_case(property_thing.name) + def json_property_name(self, external_document_ref_property: ExternalDocumentRefProperty) -> str: + return snake_case_to_camel_case(external_document_ref_property.name) def _get_property_value(self, external_document_ref: ExternalDocumentRef, - property_thing: ExternalDocumentRefProperty, _document: Document = None) -> Any: - if property_thing == ExternalDocumentRefProperty.EXTERNAL_DOCUMENT_ID: + external_document_ref_property: ExternalDocumentRefProperty, + _document: Document = None) -> Any: + if external_document_ref_property == ExternalDocumentRefProperty.EXTERNAL_DOCUMENT_ID: return external_document_ref.document_ref_id - elif property_thing == ExternalDocumentRefProperty.SPDX_DOCUMENT: + elif external_document_ref_property == ExternalDocumentRefProperty.SPDX_DOCUMENT: return external_document_ref.document_uri - elif property_thing == ExternalDocumentRefProperty.CHECKSUM: + elif external_document_ref_property == ExternalDocumentRefProperty.CHECKSUM: return self.checksum_converter.convert(external_document_ref.checksum) def get_json_type(self) -> Type[JsonProperty]: diff --git a/src/jsonschema/external_package_ref_converter.py b/src/jsonschema/external_package_ref_converter.py index 531861105..55e1ccabf 100644 --- a/src/jsonschema/external_package_ref_converter.py +++ b/src/jsonschema/external_package_ref_converter.py @@ -18,7 +18,7 @@ from src.writer.casing_tools import snake_case_to_camel_case -class ExternalPackageRefConverter(TypedConverter): +class ExternalPackageRefConverter(TypedConverter[ExternalPackageRef]): def json_property_name(self, external_ref_property: ExternalPackageRefProperty) -> str: return snake_case_to_camel_case(external_ref_property.name) diff --git a/src/jsonschema/extracted_licensing_info_converter.py b/src/jsonschema/extracted_licensing_info_converter.py index f15629c5a..25643ec3d 100644 --- a/src/jsonschema/extracted_licensing_info_converter.py +++ b/src/jsonschema/extracted_licensing_info_converter.py @@ -18,7 +18,7 @@ from src.writer.casing_tools import snake_case_to_camel_case -class ExtractedLicensingInfoConverter(TypedConverter): +class ExtractedLicensingInfoConverter(TypedConverter[ExtractedLicensingInfo]): def json_property_name(self, extracted_licensing_info_property: ExtractedLicensingInfoProperty) -> str: return snake_case_to_camel_case(extracted_licensing_info_property.name) diff --git a/src/jsonschema/file_converter.py b/src/jsonschema/file_converter.py index e2309ee8b..305239637 100644 --- a/src/jsonschema/file_converter.py +++ b/src/jsonschema/file_converter.py @@ -21,7 +21,7 @@ from src.writer.casing_tools import snake_case_to_camel_case -class FileConverter(TypedConverter): +class FileConverter(TypedConverter[File]): annotation_converter: AnnotationConverter checksum_converter: ChecksumConverter diff --git a/src/jsonschema/package_converter.py b/src/jsonschema/package_converter.py index 3e016330b..3253ec801 100644 --- a/src/jsonschema/package_converter.py +++ b/src/jsonschema/package_converter.py @@ -27,7 +27,7 @@ from src.writer.casing_tools import snake_case_to_camel_case -class PackageConverter(TypedConverter): +class PackageConverter(TypedConverter[Package]): annotation_converter: AnnotationConverter checksum_converter: ChecksumConverter external_package_ref_converter: ExternalPackageRefConverter diff --git a/src/jsonschema/package_verification_code_converter.py b/src/jsonschema/package_verification_code_converter.py index 61a5b7400..f8bdc68df 100644 --- a/src/jsonschema/package_verification_code_converter.py +++ b/src/jsonschema/package_verification_code_converter.py @@ -18,7 +18,7 @@ from src.writer.casing_tools import snake_case_to_camel_case -class PackageVerificationCodeConverter(TypedConverter): +class PackageVerificationCodeConverter(TypedConverter[PackageVerificationCode]): def json_property_name(self, verification_code_property: PackageVerificationCodeProperty) -> str: return snake_case_to_camel_case(verification_code_property.name) diff --git a/src/jsonschema/relationship_converter.py b/src/jsonschema/relationship_converter.py index 1860b6a06..ea2806765 100644 --- a/src/jsonschema/relationship_converter.py +++ b/src/jsonschema/relationship_converter.py @@ -18,7 +18,7 @@ from src.writer.casing_tools import snake_case_to_camel_case -class RelationshipConverter(TypedConverter): +class RelationshipConverter(TypedConverter[Relationship]): def json_property_name(self, relationship_property: RelationshipProperty) -> str: return snake_case_to_camel_case(relationship_property.name) diff --git a/src/jsonschema/snippet_converter.py b/src/jsonschema/snippet_converter.py index d726d63b9..f907072fa 100644 --- a/src/jsonschema/snippet_converter.py +++ b/src/jsonschema/snippet_converter.py @@ -20,7 +20,7 @@ from src.writer.casing_tools import snake_case_to_camel_case -class SnippetConverter(TypedConverter): +class SnippetConverter(TypedConverter[Snippet]): annotation_converter: AnnotationConverter def __init__(self): From 0cf085f30671db957875cb2f9fdf174bbe580663 Mon Sep 17 00:00:00 2001 From: Nicolaus Weidner Date: Wed, 21 Dec 2022 11:07:50 +0100 Subject: [PATCH 26/55] [issue-359] Extract creation info fixture for reuse in tests Signed-off-by: Nicolaus Weidner --- tests/fixtures.py | 28 +++++++++++++++++++ .../test_creation_info_converter.py | 9 +++--- tests/jsonschema/test_document_converter.py | 11 +++++--- tests/jsonschema/test_file_converter.py | 15 ++++------ tests/jsonschema/test_package_converter.py | 11 +++----- tests/jsonschema/test_snippet_converter.py | 11 +++----- 6 files changed, 54 insertions(+), 31 deletions(-) create mode 100644 tests/fixtures.py diff --git a/tests/fixtures.py b/tests/fixtures.py new file mode 100644 index 000000000..c00af0e1a --- /dev/null +++ b/tests/fixtures.py @@ -0,0 +1,28 @@ +# 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. +from datetime import datetime + +from src.model.actor import Actor, ActorType +from src.model.document import CreationInfo +from src.model.version import Version + +"""Utility methods to create data model instances. All properties have valid defaults, so they don't need to be +specified unless relevant for the test.""" + + +def creation_info_fixture(spdx_version="spdxVersion", spdx_id="documentId", name="documentName", + namespace="documentNamespace", creators=None, created=datetime(2022, 12, 1), + creator_comment="creatorComment", data_license="CC0-1.0", external_document_refs=None, + license_list_version=Version(3, 19), document_comment="documentComment") -> CreationInfo: + creators = [Actor(ActorType.PERSON, "creatorName")] if creators is None else creators + external_document_refs = [] if external_document_refs is None else external_document_refs + return CreationInfo(spdx_version, spdx_id, name, namespace, creators, created, creator_comment, data_license, + external_document_refs, license_list_version, document_comment) diff --git a/tests/jsonschema/test_creation_info_converter.py b/tests/jsonschema/test_creation_info_converter.py index f27081dca..0fb500ebc 100644 --- a/tests/jsonschema/test_creation_info_converter.py +++ b/tests/jsonschema/test_creation_info_converter.py @@ -18,6 +18,7 @@ from src.model.actor import Actor, ActorType from src.model.document import CreationInfo from src.model.version import Version +from tests.fixtures import creation_info_fixture @pytest.fixture @@ -37,10 +38,10 @@ def test_json_property_names(converter: CreationInfoConverter, creation_info_pro def test_successful_conversion(converter: CreationInfoConverter): creators = [Actor(ActorType.PERSON, "personName"), Actor(ActorType.TOOL, "toolName")] created = datetime(2022, 12, 1) - creation_info = CreationInfo("irrelevant", "irrelevant", "irrelevant", "irrelevant", creators=creators, - created=created, creator_comment="comment", license_list_version=Version(1, 2)) - converted_dict = converter.convert(creation_info) + converted_dict = converter.convert( + creation_info_fixture(creators=creators, created=created, creator_comment="comment", + license_list_version=Version(1, 2))) assert converted_dict[converter.json_property_name(CreationInfoProperty.CREATED)] == datetime_to_iso_string(created) assert converted_dict[converter.json_property_name(CreationInfoProperty.CREATORS)] == ["Person: personName", @@ -50,7 +51,7 @@ def test_successful_conversion(converter: CreationInfoConverter): def test_null_values(converter: CreationInfoConverter): - creation_info = CreationInfo("irrelevant", "irrelevant", "irrelevant", "irrelevant", [], datetime(2022, 12, 1)) + creation_info = creation_info_fixture(license_list_version=None, creator_comment=None) converted_dict = converter.convert(creation_info) diff --git a/tests/jsonschema/test_document_converter.py b/tests/jsonschema/test_document_converter.py index 73088366e..d531ebc18 100644 --- a/tests/jsonschema/test_document_converter.py +++ b/tests/jsonschema/test_document_converter.py @@ -19,7 +19,7 @@ from src.model.actor import Actor, ActorType from src.model.annotation import Annotation, AnnotationType from src.model.checksum import Checksum, ChecksumAlgorithm -from src.model.document import Document, CreationInfo +from src.model.document import Document from src.model.external_document_ref import ExternalDocumentRef from src.model.extracted_licensing_info import ExtractedLicensingInfo from src.model.file import File @@ -27,6 +27,7 @@ from src.model.relationship import Relationship, RelationshipType from src.model.snippet import Snippet from src.model.spdx_none import SpdxNone +from tests.fixtures import creation_info_fixture @pytest.fixture @@ -70,9 +71,11 @@ def test_json_property_names(converter: DocumentConverter, document_property: Do def test_successful_conversion(converter: DocumentConverter): - creation_info = CreationInfo("spdxVersion", "spdxId", "name", "namespace", [], datetime.today(), - document_comment="comment", data_license="dataLicense", external_document_refs=[ - ExternalDocumentRef("docRefId", "externalDocumentUri", Checksum(ChecksumAlgorithm.SHA1, "sha1"))]) + creation_info = creation_info_fixture(spdx_version="spdxVersion", spdx_id="spdxId", name="name", + namespace="namespace", document_comment="comment", data_license="dataLicense", + external_document_refs=[ExternalDocumentRef("docRefId", "externalDocumentUri", + Checksum(ChecksumAlgorithm.SHA1, + "sha1"))]) package = Package("packageID", "packageName", SpdxNone()) file = File("fileName", "fileId", []) snippet = Snippet("snippetId", "snippetFileId", (1, 2)) diff --git a/tests/jsonschema/test_file_converter.py b/tests/jsonschema/test_file_converter.py index 1acd74918..5e971a328 100644 --- a/tests/jsonschema/test_file_converter.py +++ b/tests/jsonschema/test_file_converter.py @@ -19,9 +19,12 @@ from src.model.actor import Actor, ActorType from src.model.annotation import Annotation, AnnotationType from src.model.checksum import Checksum, ChecksumAlgorithm -from src.model.document import CreationInfo, Document +from src.model.document import Document from src.model.file import File, FileType from src.model.license_expression import LicenseExpression +from src.model.spdx_no_assertion import SpdxNoAssertion +from src.model.spdx_none import SpdxNone +from tests.fixtures import creation_info_fixture @pytest.fixture @@ -73,12 +76,9 @@ def test_successful_conversion(converter: FileConverter): contributors=["contributor1", "contributor2"], attribution_texts=["attributionText1", "attributionText2"]) - creation_info = CreationInfo("spdxVersion", "documentID", "documentName", "documentNamespace", [], - datetime(2022, 12, 4)) - annotations = [Annotation(file.spdx_id, AnnotationType.REVIEW, Actor(ActorType.PERSON, "annotatorName"), datetime(2022, 12, 5), "review comment")] - document = Document(creation_info, files=[file], annotations=annotations) + document = Document(creation_info_fixture(), files=[file], annotations=annotations) converted_dict = converter.convert(file, document) @@ -106,10 +106,7 @@ def test_null_values(converter: FileConverter): file = File(name="name", spdx_id="spdxId", checksums=[Checksum(ChecksumAlgorithm.SHA224, "sha224"), Checksum(ChecksumAlgorithm.MD2, "md2")]) - creation_info = CreationInfo("spdxVersion", "documentID", "documentName", "documentNamespace", [], - datetime(2022, 12, 4)) - - document = Document(creation_info, files=[file]) + document = Document(creation_info_fixture(), files=[file]) converted_dict = converter.convert(file, document) diff --git a/tests/jsonschema/test_package_converter.py b/tests/jsonschema/test_package_converter.py index 324b64ccb..6a4e5a2b3 100644 --- a/tests/jsonschema/test_package_converter.py +++ b/tests/jsonschema/test_package_converter.py @@ -19,10 +19,11 @@ from src.model.actor import Actor, ActorType from src.model.annotation import Annotation, AnnotationType from src.model.checksum import Checksum, ChecksumAlgorithm -from src.model.document import Document, CreationInfo +from src.model.document import Document from src.model.license_expression import LicenseExpression from src.model.package import Package, PackageVerificationCode, ExternalPackageRef, ExternalPackageRefCategory, \ PackagePurpose +from tests.fixtures import creation_info_fixture @pytest.fixture @@ -106,12 +107,10 @@ def test_successful_conversion(converter: PackageConverter): primary_package_purpose=PackagePurpose.APPLICATION, release_date=datetime(2022, 12, 1), built_date=datetime(2022, 12, 2), valid_until_date=datetime(2022, 12, 3)) - creation_info = CreationInfo("spdxVersion", "documentID", "documentName", "documentNamespace", [], - datetime(2022, 12, 4)) annotation = Annotation(package.spdx_id, AnnotationType.REVIEW, Actor(ActorType.TOOL, "toolName"), datetime(2022, 12, 5), "review comment") - document = Document(creation_info, packages=[package], annotations=[annotation]) + document = Document(creation_info_fixture(), packages=[package], annotations=[annotation]) converted_dict = converter.convert(package, document) @@ -151,9 +150,7 @@ def test_successful_conversion(converter: PackageConverter): def test_null_values(converter: PackageConverter): package = Package(spdx_id="packageId", name="name", download_location="downloadLocation") - creation_info = CreationInfo("spdxVersion", "documentID", "documentName", "documentNamespace", [], - datetime(2022, 12, 4)) - document = Document(creation_info, packages=[package]) + document = Document(creation_info_fixture(), packages=[package]) converted_dict = converter.convert(package, document) diff --git a/tests/jsonschema/test_snippet_converter.py b/tests/jsonschema/test_snippet_converter.py index 6008d63b0..e15d27a7e 100644 --- a/tests/jsonschema/test_snippet_converter.py +++ b/tests/jsonschema/test_snippet_converter.py @@ -18,9 +18,10 @@ from src.jsonschema.snippet_properties import SnippetProperty from src.model.actor import Actor, ActorType from src.model.annotation import Annotation, AnnotationType -from src.model.document import Document, CreationInfo +from src.model.document import Document from src.model.license_expression import LicenseExpression from src.model.snippet import Snippet +from tests.fixtures import creation_info_fixture @pytest.fixture @@ -68,9 +69,7 @@ def test_successful_conversion(converter: SnippetConverter): annotation = Annotation(snippet.spdx_id, AnnotationType.OTHER, Actor(ActorType.PERSON, "annotatorName"), datetime(2022, 12, 5), "other comment") - creation_info = CreationInfo("spdxVersion", "documentId", "documentName", "documentNamespace", [], - datetime(2022, 12, 4)) - document = Document(creation_info, snippets=[snippet], annotations=[annotation]) + document = Document(creation_info_fixture(), snippets=[snippet], annotations=[annotation]) converted_dict = converter.convert(snippet, document) assert converted_dict[converter.json_property_name(SnippetProperty.SPDX_ID)] == "spdxId" @@ -95,9 +94,7 @@ def test_successful_conversion(converter: SnippetConverter): def test_null_values(converter: SnippetConverter): snippet = Snippet("spdxId", file_spdx_id="fileId", byte_range=(1, 2)) - creation_info = CreationInfo("spdxVersion", "documentId", "documentName", "documentNamespace", [], - datetime(2022, 12, 4)) - document = Document(creation_info, snippets=[snippet]) + document = Document(creation_info_fixture(), snippets=[snippet]) converted_dict = converter.convert(snippet, document) assert converter.json_property_name(SnippetProperty.LICENSE_CONCLUDED) not in converted_dict From 50e0a548bff546859243d323d1f2163a02e06b92 Mon Sep 17 00:00:00 2001 From: Nicolaus Weidner Date: Wed, 21 Dec 2022 11:34:43 +0100 Subject: [PATCH 27/55] [issue-359] Extract file fixture for reuse in tests Signed-off-by: Nicolaus Weidner --- tests/fixtures.py | 19 +++++++++++++++++++ tests/jsonschema/test_document_converter.py | 6 ++---- tests/jsonschema/test_file_converter.py | 7 ++----- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/tests/fixtures.py b/tests/fixtures.py index c00af0e1a..71e092fb0 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -11,7 +11,10 @@ from datetime import datetime from src.model.actor import Actor, ActorType +from src.model.checksum import Checksum, ChecksumAlgorithm from src.model.document import CreationInfo +from src.model.file import File, FileType +from src.model.license_expression import LicenseExpression from src.model.version import Version """Utility methods to create data model instances. All properties have valid defaults, so they don't need to be @@ -26,3 +29,19 @@ def creation_info_fixture(spdx_version="spdxVersion", spdx_id="documentId", name external_document_refs = [] if external_document_refs is None else external_document_refs return CreationInfo(spdx_version, spdx_id, name, namespace, creators, created, creator_comment, data_license, external_document_refs, license_list_version, document_comment) + + +def file_fixture(name="fileName", spdx_id="fileId", checksums=None, file_type=None, + concluded_license=LicenseExpression("concludedLicenseExpression"), license_info_in_file=None, + license_comment="licenseComment", copyright_text="copyrightText", comment="fileComment", + notice="fileNotice", contributors=None, attribution_texts=None) -> File: + checksums = [Checksum(ChecksumAlgorithm.SHA1, "sha1")] if checksums is None else checksums + file_type = [FileType.TEXT] if file_type is None else file_type + license_info_in_file = [ + LicenseExpression("licenseInfoInFileExpression")] if license_info_in_file is None else license_info_in_file + contributors = ["fileContributor"] if contributors is None else contributors + attribution_texts = ["fileAttributionText"] if attribution_texts is None else attribution_texts + return File(name=name, spdx_id=spdx_id, checksums=checksums, file_type=file_type, + concluded_license=concluded_license, license_info_in_file=license_info_in_file, + license_comment=license_comment, copyright_text=copyright_text, comment=comment, notice=notice, + contributors=contributors, attribution_texts=attribution_texts) diff --git a/tests/jsonschema/test_document_converter.py b/tests/jsonschema/test_document_converter.py index d531ebc18..2f4433105 100644 --- a/tests/jsonschema/test_document_converter.py +++ b/tests/jsonschema/test_document_converter.py @@ -22,12 +22,11 @@ from src.model.document import Document from src.model.external_document_ref import ExternalDocumentRef from src.model.extracted_licensing_info import ExtractedLicensingInfo -from src.model.file import File from src.model.package import Package from src.model.relationship import Relationship, RelationshipType from src.model.snippet import Snippet from src.model.spdx_none import SpdxNone -from tests.fixtures import creation_info_fixture +from tests.fixtures import creation_info_fixture, file_fixture @pytest.fixture @@ -77,7 +76,6 @@ def test_successful_conversion(converter: DocumentConverter): Checksum(ChecksumAlgorithm.SHA1, "sha1"))]) package = Package("packageID", "packageName", SpdxNone()) - file = File("fileName", "fileId", []) snippet = Snippet("snippetId", "snippetFileId", (1, 2)) document = Document(creation_info, annotations=[ Annotation("annotationId", AnnotationType.REVIEW, Actor(ActorType.PERSON, "reviewerName"), @@ -85,7 +83,7 @@ def test_successful_conversion(converter: DocumentConverter): extracted_licensing_info=[ExtractedLicensingInfo("licenseId", "licenseText")], relationships=[ Relationship(creation_info.spdx_id, RelationshipType.DESCRIBES, "describedElementId"), Relationship("relationshipOriginId", RelationshipType.AMENDS, "relationShipTargetId")], packages=[package], - files=[file], snippets=[snippet]) + files=[file_fixture()], snippets=[snippet]) converter.external_document_ref_converter.convert.return_value = "mock_converted_external_ref" converter.creation_info_converter.convert.return_value = "mock_converted_creation_info" converter.package_converter.convert.return_value = "mock_converted_package" diff --git a/tests/jsonschema/test_file_converter.py b/tests/jsonschema/test_file_converter.py index 5e971a328..f20159c83 100644 --- a/tests/jsonschema/test_file_converter.py +++ b/tests/jsonschema/test_file_converter.py @@ -22,9 +22,7 @@ from src.model.document import Document from src.model.file import File, FileType from src.model.license_expression import LicenseExpression -from src.model.spdx_no_assertion import SpdxNoAssertion -from src.model.spdx_none import SpdxNone -from tests.fixtures import creation_info_fixture +from tests.fixtures import creation_info_fixture, file_fixture @pytest.fixture @@ -103,8 +101,7 @@ def test_successful_conversion(converter: FileConverter): def test_null_values(converter: FileConverter): - file = File(name="name", spdx_id="spdxId", - checksums=[Checksum(ChecksumAlgorithm.SHA224, "sha224"), Checksum(ChecksumAlgorithm.MD2, "md2")]) + file = file_fixture(copyright_text=None, concluded_license=None, license_comment=None, comment=None, notice=None) document = Document(creation_info_fixture(), files=[file]) From 09a4af5f9940d2e13249591739fd4d4a3b9b3b91 Mon Sep 17 00:00:00 2001 From: Nicolaus Weidner Date: Wed, 21 Dec 2022 11:50:16 +0100 Subject: [PATCH 28/55] [issue-359] Extract package fixture for reuse in tests Signed-off-by: Nicolaus Weidner --- tests/fixtures.py | 29 +++++++++++++++++++++ tests/jsonschema/test_document_converter.py | 9 +++---- tests/jsonschema/test_package_converter.py | 8 ++++-- 3 files changed, 38 insertions(+), 8 deletions(-) diff --git a/tests/fixtures.py b/tests/fixtures.py index 71e092fb0..407e79875 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -15,6 +15,7 @@ from src.model.document import CreationInfo from src.model.file import File, FileType from src.model.license_expression import LicenseExpression +from src.model.package import Package, PackageVerificationCode, PackagePurpose from src.model.version import Version """Utility methods to create data model instances. All properties have valid defaults, so they don't need to be @@ -45,3 +46,31 @@ def file_fixture(name="fileName", spdx_id="fileId", checksums=None, file_type=No concluded_license=concluded_license, license_info_in_file=license_info_in_file, license_comment=license_comment, copyright_text=copyright_text, comment=comment, notice=notice, contributors=contributors, attribution_texts=attribution_texts) + + +def package_fixture(spdx_id="packageId", name="packageName", download_location="downloadLocation", + version="packageVersion", file_name="packageFileName", + supplier=Actor(ActorType.PERSON, "supplierName"), + originator=Actor(ActorType.PERSON, "originatorName"), files_analyzed=True, + verification_code=PackageVerificationCode("verificationCode"), checksums=None, + homepage="packageHomepage", source_info="sourceInfo", + license_concluded=LicenseExpression("packageLicenseConcluded"), license_info_from_files=None, + license_declared=LicenseExpression("packageLicenseDeclared"), + license_comment="packageLicenseComment", copyright_text="packageCopyrightText", + summary="packageSummary", description="packageDescription", comment="packageComment", + external_references=None, attribution_texts=None, primary_package_purpose=PackagePurpose.SOURCE, + release_date=datetime(2022, 12, 1), built_date=datetime(2022, 12, 2), + valid_until_date=datetime(2022, 12, 3)) -> Package: + checksums = [Checksum(ChecksumAlgorithm.SHA1, "packageSha1")] if checksums is None else checksums + license_info_from_files = [ + LicenseExpression("licenseInfoFromFile")] if license_info_from_files is None else license_info_from_files + external_references = [] if external_references is None else external_references + attribution_texts = ["packageAttributionText"] if attribution_texts is None else attribution_texts + return Package(spdx_id=spdx_id, name=name, download_location=download_location, version=version, + file_name=file_name, supplier=supplier, originator=originator, files_analyzed=files_analyzed, + verification_code=verification_code, checksums=checksums, homepage=homepage, source_info=source_info, + license_concluded=license_concluded, license_info_from_files=license_info_from_files, + license_declared=license_declared, license_comment=license_comment, copyright_text=copyright_text, + summary=summary, description=description, comment=comment, external_references=external_references, + attribution_texts=attribution_texts, primary_package_purpose=primary_package_purpose, + release_date=release_date, built_date=built_date, valid_until_date=valid_until_date) diff --git a/tests/jsonschema/test_document_converter.py b/tests/jsonschema/test_document_converter.py index 2f4433105..7622e1865 100644 --- a/tests/jsonschema/test_document_converter.py +++ b/tests/jsonschema/test_document_converter.py @@ -22,11 +22,9 @@ from src.model.document import Document from src.model.external_document_ref import ExternalDocumentRef from src.model.extracted_licensing_info import ExtractedLicensingInfo -from src.model.package import Package from src.model.relationship import Relationship, RelationshipType from src.model.snippet import Snippet -from src.model.spdx_none import SpdxNone -from tests.fixtures import creation_info_fixture, file_fixture +from tests.fixtures import creation_info_fixture, file_fixture, package_fixture @pytest.fixture @@ -75,15 +73,14 @@ def test_successful_conversion(converter: DocumentConverter): external_document_refs=[ExternalDocumentRef("docRefId", "externalDocumentUri", Checksum(ChecksumAlgorithm.SHA1, "sha1"))]) - package = Package("packageID", "packageName", SpdxNone()) snippet = Snippet("snippetId", "snippetFileId", (1, 2)) document = Document(creation_info, annotations=[ Annotation("annotationId", AnnotationType.REVIEW, Actor(ActorType.PERSON, "reviewerName"), datetime(2022, 12, 1), "reviewComment")], extracted_licensing_info=[ExtractedLicensingInfo("licenseId", "licenseText")], relationships=[ Relationship(creation_info.spdx_id, RelationshipType.DESCRIBES, "describedElementId"), - Relationship("relationshipOriginId", RelationshipType.AMENDS, "relationShipTargetId")], packages=[package], - files=[file_fixture()], snippets=[snippet]) + Relationship("relationshipOriginId", RelationshipType.AMENDS, "relationShipTargetId")], + packages=[package_fixture()], files=[file_fixture()], snippets=[snippet]) converter.external_document_ref_converter.convert.return_value = "mock_converted_external_ref" converter.creation_info_converter.convert.return_value = "mock_converted_creation_info" converter.package_converter.convert.return_value = "mock_converted_package" diff --git a/tests/jsonschema/test_package_converter.py b/tests/jsonschema/test_package_converter.py index 6a4e5a2b3..d7f633558 100644 --- a/tests/jsonschema/test_package_converter.py +++ b/tests/jsonschema/test_package_converter.py @@ -23,7 +23,7 @@ from src.model.license_expression import LicenseExpression from src.model.package import Package, PackageVerificationCode, ExternalPackageRef, ExternalPackageRefCategory, \ PackagePurpose -from tests.fixtures import creation_info_fixture +from tests.fixtures import creation_info_fixture, package_fixture @pytest.fixture @@ -148,7 +148,11 @@ def test_successful_conversion(converter: PackageConverter): def test_null_values(converter: PackageConverter): - package = Package(spdx_id="packageId", name="name", download_location="downloadLocation") + package = package_fixture(built_date=None, release_date=None, valid_until_date=None, homepage=None, + license_concluded=None, license_declared=None, originator=None, verification_code=None, + primary_package_purpose=None, supplier=None, version=None, file_name=None, + source_info=None, license_comment=None, copyright_text=None, summary=None, + description=None, comment=None) document = Document(creation_info_fixture(), packages=[package]) From f669f6bb0e33a069537896b1edcaaf2b6369f2f2 Mon Sep 17 00:00:00 2001 From: Nicolaus Weidner Date: Wed, 21 Dec 2022 11:57:38 +0100 Subject: [PATCH 29/55] [issue-359] Extract external document ref fixture for reuse in tests Signed-off-by: Nicolaus Weidner --- tests/fixtures.py | 10 +++++++++- tests/jsonschema/test_document_converter.py | 8 ++------ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/tests/fixtures.py b/tests/fixtures.py index 407e79875..df7b5f96c 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -13,6 +13,7 @@ from src.model.actor import Actor, ActorType from src.model.checksum import Checksum, ChecksumAlgorithm from src.model.document import CreationInfo +from src.model.external_document_ref import ExternalDocumentRef from src.model.file import File, FileType from src.model.license_expression import LicenseExpression from src.model.package import Package, PackageVerificationCode, PackagePurpose @@ -27,7 +28,8 @@ def creation_info_fixture(spdx_version="spdxVersion", spdx_id="documentId", name creator_comment="creatorComment", data_license="CC0-1.0", external_document_refs=None, license_list_version=Version(3, 19), document_comment="documentComment") -> CreationInfo: creators = [Actor(ActorType.PERSON, "creatorName")] if creators is None else creators - external_document_refs = [] if external_document_refs is None else external_document_refs + external_document_refs = [ + external_document_ref_fixture()] if external_document_refs is None else external_document_refs return CreationInfo(spdx_version, spdx_id, name, namespace, creators, created, creator_comment, data_license, external_document_refs, license_list_version, document_comment) @@ -74,3 +76,9 @@ def package_fixture(spdx_id="packageId", name="packageName", download_location=" summary=summary, description=description, comment=comment, external_references=external_references, attribution_texts=attribution_texts, primary_package_purpose=primary_package_purpose, release_date=release_date, built_date=built_date, valid_until_date=valid_until_date) + + +def external_document_ref_fixture(document_ref_id="externalDocumentRefId", document_uri="externalDocumentUri", + checksum=Checksum(ChecksumAlgorithm.MD5, + "externalDocumentRefMd5")) -> ExternalDocumentRef: + return ExternalDocumentRef(document_ref_id=document_ref_id, document_uri=document_uri, checksum=checksum) diff --git a/tests/jsonschema/test_document_converter.py b/tests/jsonschema/test_document_converter.py index 7622e1865..42270be06 100644 --- a/tests/jsonschema/test_document_converter.py +++ b/tests/jsonschema/test_document_converter.py @@ -18,13 +18,11 @@ from src.jsonschema.document_properties import DocumentProperty from src.model.actor import Actor, ActorType from src.model.annotation import Annotation, AnnotationType -from src.model.checksum import Checksum, ChecksumAlgorithm from src.model.document import Document -from src.model.external_document_ref import ExternalDocumentRef from src.model.extracted_licensing_info import ExtractedLicensingInfo from src.model.relationship import Relationship, RelationshipType from src.model.snippet import Snippet -from tests.fixtures import creation_info_fixture, file_fixture, package_fixture +from tests.fixtures import creation_info_fixture, file_fixture, package_fixture, external_document_ref_fixture @pytest.fixture @@ -70,9 +68,7 @@ def test_json_property_names(converter: DocumentConverter, document_property: Do def test_successful_conversion(converter: DocumentConverter): creation_info = creation_info_fixture(spdx_version="spdxVersion", spdx_id="spdxId", name="name", namespace="namespace", document_comment="comment", data_license="dataLicense", - external_document_refs=[ExternalDocumentRef("docRefId", "externalDocumentUri", - Checksum(ChecksumAlgorithm.SHA1, - "sha1"))]) + external_document_refs=[external_document_ref_fixture()]) snippet = Snippet("snippetId", "snippetFileId", (1, 2)) document = Document(creation_info, annotations=[ Annotation("annotationId", AnnotationType.REVIEW, Actor(ActorType.PERSON, "reviewerName"), From d640c58ad893d9ccb9844eb4d1483ca3a1e074e9 Mon Sep 17 00:00:00 2001 From: Nicolaus Weidner Date: Wed, 21 Dec 2022 12:01:22 +0100 Subject: [PATCH 30/55] [issue-359] Extract external package ref fixture for reuse in tests Signed-off-by: Nicolaus Weidner --- tests/fixtures.py | 12 ++++++++++-- tests/jsonschema/test_package_converter.py | 12 ++++-------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/tests/fixtures.py b/tests/fixtures.py index df7b5f96c..6d6d529b4 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -16,7 +16,8 @@ from src.model.external_document_ref import ExternalDocumentRef from src.model.file import File, FileType from src.model.license_expression import LicenseExpression -from src.model.package import Package, PackageVerificationCode, PackagePurpose +from src.model.package import Package, PackageVerificationCode, PackagePurpose, ExternalPackageRef, \ + ExternalPackageRefCategory from src.model.version import Version """Utility methods to create data model instances. All properties have valid defaults, so they don't need to be @@ -66,7 +67,7 @@ def package_fixture(spdx_id="packageId", name="packageName", download_location=" checksums = [Checksum(ChecksumAlgorithm.SHA1, "packageSha1")] if checksums is None else checksums license_info_from_files = [ LicenseExpression("licenseInfoFromFile")] if license_info_from_files is None else license_info_from_files - external_references = [] if external_references is None else external_references + external_references = [external_package_ref_fixture()] if external_references is None else external_references attribution_texts = ["packageAttributionText"] if attribution_texts is None else attribution_texts return Package(spdx_id=spdx_id, name=name, download_location=download_location, version=version, file_name=file_name, supplier=supplier, originator=originator, files_analyzed=files_analyzed, @@ -82,3 +83,10 @@ def external_document_ref_fixture(document_ref_id="externalDocumentRefId", docum checksum=Checksum(ChecksumAlgorithm.MD5, "externalDocumentRefMd5")) -> ExternalDocumentRef: return ExternalDocumentRef(document_ref_id=document_ref_id, document_uri=document_uri, checksum=checksum) + + +def external_package_ref_fixture(category=ExternalPackageRefCategory.PACKAGE_MANAGER, + reference_type="externalPackageRefType", + locator="externalPackageRefLocator", + comment="externalPackageRefComment") -> ExternalPackageRef: + return ExternalPackageRef(category=category, reference_type=reference_type, locator=locator, comment=comment) diff --git a/tests/jsonschema/test_package_converter.py b/tests/jsonschema/test_package_converter.py index d7f633558..6da05428d 100644 --- a/tests/jsonschema/test_package_converter.py +++ b/tests/jsonschema/test_package_converter.py @@ -21,9 +21,8 @@ from src.model.checksum import Checksum, ChecksumAlgorithm from src.model.document import Document from src.model.license_expression import LicenseExpression -from src.model.package import Package, PackageVerificationCode, ExternalPackageRef, ExternalPackageRefCategory, \ - PackagePurpose -from tests.fixtures import creation_info_fixture, package_fixture +from src.model.package import Package, PackageVerificationCode, PackagePurpose +from tests.fixtures import creation_info_fixture, package_fixture, external_package_ref_fixture @pytest.fixture @@ -100,16 +99,13 @@ def test_successful_conversion(converter: PackageConverter): LicenseExpression("licenseExpression3")], license_declared=LicenseExpression("licenseExpression4"), license_comment="licenseComment", copyright_text="copyrightText", summary="summary", description="description", comment="comment", - external_references=[ - ExternalPackageRef(ExternalPackageRefCategory.PACKAGE_MANAGER, "referenceType", - "referenceLocator")], + external_references=[external_package_ref_fixture()], attribution_texts=["attributionText1", "attributionText2"], primary_package_purpose=PackagePurpose.APPLICATION, release_date=datetime(2022, 12, 1), built_date=datetime(2022, 12, 2), valid_until_date=datetime(2022, 12, 3)) annotation = Annotation(package.spdx_id, AnnotationType.REVIEW, Actor(ActorType.TOOL, "toolName"), - datetime(2022, 12, 5), - "review comment") + datetime(2022, 12, 5), "review comment") document = Document(creation_info_fixture(), packages=[package], annotations=[annotation]) converted_dict = converter.convert(package, document) From 1aabe998e482dfa7b63f9601183d091c86c8e2db Mon Sep 17 00:00:00 2001 From: Nicolaus Weidner Date: Wed, 21 Dec 2022 12:26:11 +0100 Subject: [PATCH 31/55] [issue-359] Extract snippet fixture for reuse in tests Signed-off-by: Nicolaus Weidner --- tests/fixtures.py | 15 +++++++++++++++ tests/jsonschema/test_document_converter.py | 7 +++---- tests/jsonschema/test_snippet_converter.py | 5 +++-- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/tests/fixtures.py b/tests/fixtures.py index 6d6d529b4..ae9d4b3d8 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -18,6 +18,7 @@ from src.model.license_expression import LicenseExpression from src.model.package import Package, PackageVerificationCode, PackagePurpose, ExternalPackageRef, \ ExternalPackageRefCategory +from src.model.snippet import Snippet from src.model.version import Version """Utility methods to create data model instances. All properties have valid defaults, so they don't need to be @@ -90,3 +91,17 @@ def external_package_ref_fixture(category=ExternalPackageRefCategory.PACKAGE_MAN locator="externalPackageRefLocator", comment="externalPackageRefComment") -> ExternalPackageRef: return ExternalPackageRef(category=category, reference_type=reference_type, locator=locator, comment=comment) + + +def snippet_fixture(spdx_id="snippetId", file_spdx_id="snippetFromFileId", byte_range=(1, 2), + line_range=(3, 4), concluded_license=LicenseExpression("snippetLicenseConcluded"), + license_info_in_snippet=None, license_comment="snippetLicenseComment", + copyright_text="licenseCopyrightText", comment="snippetComment", name="snippetName", + attribution_texts=None) -> Snippet: + license_info_in_snippet = [ + LicenseExpression("licenseInfoInSnippet")] if license_info_in_snippet is None else license_info_in_snippet + attribution_texts = ["snippetAttributionText"] if attribution_texts is None else attribution_texts + return Snippet(spdx_id=spdx_id, file_spdx_id=file_spdx_id, byte_range=byte_range, line_range=line_range, + concluded_license=concluded_license, license_info_in_snippet=license_info_in_snippet, + license_comment=license_comment, copyright_text=copyright_text, comment=comment, name=name, + attribution_texts=attribution_texts) diff --git a/tests/jsonschema/test_document_converter.py b/tests/jsonschema/test_document_converter.py index 42270be06..cac563dd5 100644 --- a/tests/jsonschema/test_document_converter.py +++ b/tests/jsonschema/test_document_converter.py @@ -21,8 +21,8 @@ from src.model.document import Document from src.model.extracted_licensing_info import ExtractedLicensingInfo from src.model.relationship import Relationship, RelationshipType -from src.model.snippet import Snippet -from tests.fixtures import creation_info_fixture, file_fixture, package_fixture, external_document_ref_fixture +from tests.fixtures import creation_info_fixture, file_fixture, package_fixture, external_document_ref_fixture, \ + snippet_fixture @pytest.fixture @@ -69,14 +69,13 @@ def test_successful_conversion(converter: DocumentConverter): creation_info = creation_info_fixture(spdx_version="spdxVersion", spdx_id="spdxId", name="name", namespace="namespace", document_comment="comment", data_license="dataLicense", external_document_refs=[external_document_ref_fixture()]) - snippet = Snippet("snippetId", "snippetFileId", (1, 2)) document = Document(creation_info, annotations=[ Annotation("annotationId", AnnotationType.REVIEW, Actor(ActorType.PERSON, "reviewerName"), datetime(2022, 12, 1), "reviewComment")], extracted_licensing_info=[ExtractedLicensingInfo("licenseId", "licenseText")], relationships=[ Relationship(creation_info.spdx_id, RelationshipType.DESCRIBES, "describedElementId"), Relationship("relationshipOriginId", RelationshipType.AMENDS, "relationShipTargetId")], - packages=[package_fixture()], files=[file_fixture()], snippets=[snippet]) + packages=[package_fixture()], files=[file_fixture()], snippets=[snippet_fixture()]) converter.external_document_ref_converter.convert.return_value = "mock_converted_external_ref" converter.creation_info_converter.convert.return_value = "mock_converted_creation_info" converter.package_converter.convert.return_value = "mock_converted_package" diff --git a/tests/jsonschema/test_snippet_converter.py b/tests/jsonschema/test_snippet_converter.py index e15d27a7e..564d0f118 100644 --- a/tests/jsonschema/test_snippet_converter.py +++ b/tests/jsonschema/test_snippet_converter.py @@ -21,7 +21,7 @@ from src.model.document import Document from src.model.license_expression import LicenseExpression from src.model.snippet import Snippet -from tests.fixtures import creation_info_fixture +from tests.fixtures import creation_info_fixture, snippet_fixture @pytest.fixture @@ -92,7 +92,8 @@ def test_successful_conversion(converter: SnippetConverter): def test_null_values(converter: SnippetConverter): - snippet = Snippet("spdxId", file_spdx_id="fileId", byte_range=(1, 2)) + snippet = snippet_fixture(concluded_license=None, license_comment=None, copyright_text=None, comment=None, + name=None) document = Document(creation_info_fixture(), snippets=[snippet]) converted_dict = converter.convert(snippet, document) From 0adbb75316bfa579e9da886dcfef6f43c2d41c7f Mon Sep 17 00:00:00 2001 From: Nicolaus Weidner Date: Wed, 21 Dec 2022 13:33:55 +0100 Subject: [PATCH 32/55] [issue-359] Add SpdxNoAssertion and SpdxNone tests Signed-off-by: Nicolaus Weidner --- src/model/spdx_no_assertion.py | 8 ++--- src/model/spdx_none.py | 8 ++--- tests/jsonschema/test_file_converter.py | 28 ++++++++++++++- tests/jsonschema/test_package_converter.py | 40 ++++++++++++++++++++++ tests/jsonschema/test_snippet_converter.py | 24 +++++++++++++ 5 files changed, 99 insertions(+), 9 deletions(-) diff --git a/src/model/spdx_no_assertion.py b/src/model/spdx_no_assertion.py index 60bec6fd6..3a12ed36c 100644 --- a/src/model/spdx_no_assertion.py +++ b/src/model/spdx_no_assertion.py @@ -9,19 +9,19 @@ # See the License for the specific language governing permissions and # limitations under the License. +SPDX_NO_ASSERTION_STRING = "NOASSERTION" + class SpdxNoAssertion: """ Represents the SPDX NOASSERTION value. """ - _string_value: str = "NOASSERTION" - def __str__(self): - return self._string_value + return SPDX_NO_ASSERTION_STRING def __repr__(self): - return self._string_value + return SPDX_NO_ASSERTION_STRING def __eq__(self, other): return isinstance(other, SpdxNoAssertion) diff --git a/src/model/spdx_none.py b/src/model/spdx_none.py index 8e170ae4c..e4e97c534 100644 --- a/src/model/spdx_none.py +++ b/src/model/spdx_none.py @@ -9,19 +9,19 @@ # See the License for the specific language governing permissions and # limitations under the License. +SPDX_NONE_STRING = "NONE" + class SpdxNone: """ Represents the SPDX NONE value. """ - _string_value = "NONE" - def __str__(self): - return self._string_value + return SPDX_NONE_STRING def __repr__(self): - return self._string_value + return SPDX_NONE_STRING def __eq__(self, other): return isinstance(other, SpdxNone) diff --git a/tests/jsonschema/test_file_converter.py b/tests/jsonschema/test_file_converter.py index f20159c83..7cc6f0462 100644 --- a/tests/jsonschema/test_file_converter.py +++ b/tests/jsonschema/test_file_converter.py @@ -22,6 +22,8 @@ from src.model.document import Document from src.model.file import File, FileType from src.model.license_expression import LicenseExpression +from src.model.spdx_no_assertion import SpdxNoAssertion, SPDX_NO_ASSERTION_STRING +from src.model.spdx_none import SpdxNone, SPDX_NONE_STRING from tests.fixtures import creation_info_fixture, file_fixture @@ -102,7 +104,6 @@ def test_successful_conversion(converter: FileConverter): def test_null_values(converter: FileConverter): file = file_fixture(copyright_text=None, concluded_license=None, license_comment=None, comment=None, notice=None) - document = Document(creation_info_fixture(), files=[file]) converted_dict = converter.convert(file, document) @@ -112,3 +113,28 @@ def test_null_values(converter: FileConverter): assert converter.json_property_name(FileProperty.LICENSE_COMMENTS) not in converted_dict assert converter.json_property_name(FileProperty.COMMENT) not in converted_dict assert converter.json_property_name(FileProperty.NOTICE_TEXT) not in converted_dict + + +def test_spdx_no_assertion(converter: FileConverter): + file = file_fixture(concluded_license=SpdxNoAssertion(), license_info_in_file=SpdxNoAssertion(), + copyright_text=SpdxNoAssertion()) + document = Document(creation_info_fixture(), files=[file]) + + converted_dict = converter.convert(file, document) + + assert converted_dict[ + converter.json_property_name(FileProperty.COPYRIGHT_TEXT)] == SPDX_NO_ASSERTION_STRING + assert converted_dict[converter.json_property_name(FileProperty.LICENSE_CONCLUDED)] == SPDX_NO_ASSERTION_STRING + assert converted_dict[converter.json_property_name(FileProperty.LICENSE_INFO_IN_FILES)] == SPDX_NO_ASSERTION_STRING + + +def test_spdx_none(converter: FileConverter): + file = file_fixture(concluded_license=SpdxNone(), license_info_in_file=SpdxNone(), copyright_text=SpdxNone()) + document = Document(creation_info_fixture(), files=[file]) + + converted_dict = converter.convert(file, document) + + assert converted_dict[ + converter.json_property_name(FileProperty.COPYRIGHT_TEXT)] == SPDX_NONE_STRING + assert converted_dict[converter.json_property_name(FileProperty.LICENSE_CONCLUDED)] == SPDX_NONE_STRING + assert converted_dict[converter.json_property_name(FileProperty.LICENSE_INFO_IN_FILES)] == SPDX_NONE_STRING diff --git a/tests/jsonschema/test_package_converter.py b/tests/jsonschema/test_package_converter.py index 6da05428d..ba769a616 100644 --- a/tests/jsonschema/test_package_converter.py +++ b/tests/jsonschema/test_package_converter.py @@ -22,6 +22,8 @@ from src.model.document import Document from src.model.license_expression import LicenseExpression from src.model.package import Package, PackageVerificationCode, PackagePurpose +from src.model.spdx_no_assertion import SpdxNoAssertion, SPDX_NO_ASSERTION_STRING +from src.model.spdx_none import SpdxNone, SPDX_NONE_STRING from tests.fixtures import creation_info_fixture, package_fixture, external_package_ref_fixture @@ -172,3 +174,41 @@ def test_null_values(converter: PackageConverter): assert converter.json_property_name(PackageProperty.BUILT_DATE) not in converted_dict assert converter.json_property_name(PackageProperty.RELEASE_DATE) not in converted_dict assert converter.json_property_name(PackageProperty.VALID_UNTIL_DATE) not in converted_dict + + +def test_spdx_no_assertion(converter: PackageConverter): + package = package_fixture(download_location=SpdxNoAssertion(), supplier=SpdxNoAssertion(), + originator=SpdxNoAssertion(), homepage=SpdxNoAssertion(), + license_concluded=SpdxNoAssertion(), license_info_from_files=SpdxNoAssertion(), + license_declared=SpdxNoAssertion(), copyright_text=SpdxNoAssertion()) + + document = Document(creation_info_fixture(), packages=[package]) + + converted_dict = converter.convert(package, document) + + assert converted_dict[converter.json_property_name(PackageProperty.DOWNLOAD_LOCATION)] == SPDX_NO_ASSERTION_STRING + assert converted_dict[converter.json_property_name(PackageProperty.SUPPLIER)] == SPDX_NO_ASSERTION_STRING + assert converted_dict[converter.json_property_name(PackageProperty.ORIGINATOR)] == SPDX_NO_ASSERTION_STRING + assert converted_dict[converter.json_property_name(PackageProperty.HOMEPAGE)] == SPDX_NO_ASSERTION_STRING + assert converted_dict[converter.json_property_name(PackageProperty.LICENSE_CONCLUDED)] == SPDX_NO_ASSERTION_STRING + assert converted_dict[ + converter.json_property_name(PackageProperty.LICENSE_INFO_FROM_FILES)] == SPDX_NO_ASSERTION_STRING + assert converted_dict[converter.json_property_name(PackageProperty.LICENSE_DECLARED)] == SPDX_NO_ASSERTION_STRING + assert converted_dict[converter.json_property_name(PackageProperty.COPYRIGHT_TEXT)] == SPDX_NO_ASSERTION_STRING + + +def test_spdx_none(converter: PackageConverter): + package = package_fixture(download_location=SpdxNone(), homepage=SpdxNone(), + license_concluded=SpdxNone(), license_info_from_files=SpdxNone(), + license_declared=SpdxNone(), copyright_text=SpdxNone()) + + document = Document(creation_info_fixture(), packages=[package]) + + converted_dict = converter.convert(package, document) + + assert converted_dict[converter.json_property_name(PackageProperty.DOWNLOAD_LOCATION)] == SPDX_NONE_STRING + assert converted_dict[converter.json_property_name(PackageProperty.HOMEPAGE)] == SPDX_NONE_STRING + assert converted_dict[converter.json_property_name(PackageProperty.LICENSE_CONCLUDED)] == SPDX_NONE_STRING + assert converted_dict[converter.json_property_name(PackageProperty.LICENSE_INFO_FROM_FILES)] == SPDX_NONE_STRING + assert converted_dict[converter.json_property_name(PackageProperty.LICENSE_DECLARED)] == SPDX_NONE_STRING + assert converted_dict[converter.json_property_name(PackageProperty.COPYRIGHT_TEXT)] == SPDX_NONE_STRING diff --git a/tests/jsonschema/test_snippet_converter.py b/tests/jsonschema/test_snippet_converter.py index 564d0f118..746476d3a 100644 --- a/tests/jsonschema/test_snippet_converter.py +++ b/tests/jsonschema/test_snippet_converter.py @@ -21,6 +21,8 @@ from src.model.document import Document from src.model.license_expression import LicenseExpression from src.model.snippet import Snippet +from src.model.spdx_no_assertion import SpdxNoAssertion, SPDX_NO_ASSERTION_STRING +from src.model.spdx_none import SpdxNone, SPDX_NONE_STRING from tests.fixtures import creation_info_fixture, snippet_fixture @@ -103,3 +105,25 @@ def test_null_values(converter: SnippetConverter): assert converter.json_property_name(SnippetProperty.COPYRIGHT_TEXT) not in converted_dict assert converter.json_property_name(SnippetProperty.COMMENT) not in converted_dict assert converter.json_property_name(SnippetProperty.NAME) not in converted_dict + + +def test_spdx_no_assertion(converter: SnippetConverter): + snippet = snippet_fixture(concluded_license=SpdxNoAssertion(), license_info_in_snippet=SpdxNoAssertion()) + + document = Document(creation_info_fixture(), snippets=[snippet]) + converted_dict = converter.convert(snippet, document) + + assert converted_dict[converter.json_property_name(SnippetProperty.LICENSE_CONCLUDED)] == SPDX_NO_ASSERTION_STRING + assert converted_dict[ + converter.json_property_name(SnippetProperty.LICENSE_INFO_IN_SNIPPETS)] == SPDX_NO_ASSERTION_STRING + + +def test_spdx_none(converter: SnippetConverter): + snippet = snippet_fixture(concluded_license=SpdxNone(), license_info_in_snippet=SpdxNone()) + + document = Document(creation_info_fixture(), snippets=[snippet]) + converted_dict = converter.convert(snippet, document) + + assert converted_dict[converter.json_property_name(SnippetProperty.LICENSE_CONCLUDED)] == SPDX_NONE_STRING + assert converted_dict[ + converter.json_property_name(SnippetProperty.LICENSE_INFO_IN_SNIPPETS)] == SPDX_NONE_STRING From c430a0dbe7e2c98d04ffd3b514775087bec6998d Mon Sep 17 00:00:00 2001 From: Nicolaus Weidner Date: Wed, 21 Dec 2022 16:02:35 +0100 Subject: [PATCH 33/55] [issue-359] Don't write empty lists into the converted dictionary Signed-off-by: Nicolaus Weidner --- src/jsonschema/creation_info_converter.py | 2 +- src/jsonschema/document_converter.py | 16 ++++++++-------- .../extracted_licensing_info_converter.py | 2 +- src/jsonschema/file_converter.py | 12 ++++++------ src/jsonschema/package_converter.py | 13 +++++++------ .../package_verification_code_converter.py | 2 +- src/jsonschema/snippet_converter.py | 6 +++--- tests/jsonschema/test_creation_info_converter.py | 3 ++- tests/jsonschema/test_document_converter.py | 15 +++++++++++++++ .../test_extracted_licensing_info_converter.py | 12 ++++++++++++ tests/jsonschema/test_file_converter.py | 9 ++++++++- tests/jsonschema/test_package_converter.py | 9 ++++++++- .../test_package_verification_code_converter.py | 9 +++++++++ tests/jsonschema/test_snippet_converter.py | 5 ++++- 14 files changed, 85 insertions(+), 30 deletions(-) diff --git a/src/jsonschema/creation_info_converter.py b/src/jsonschema/creation_info_converter.py index 04734ea8c..656f56ee7 100644 --- a/src/jsonschema/creation_info_converter.py +++ b/src/jsonschema/creation_info_converter.py @@ -34,7 +34,7 @@ def _get_property_value(self, creation_info: CreationInfo, creation_info_propert if creation_info_property == CreationInfoProperty.CREATED: return datetime_to_iso_string(creation_info.created) elif creation_info_property == CreationInfoProperty.CREATORS: - return [creator.to_serialized_string() for creator in creation_info.creators] + return [creator.to_serialized_string() for creator in creation_info.creators] or None elif creation_info_property == CreationInfoProperty.LICENSE_LIST_VERSION: return apply_if_present(str, creation_info.license_list_version) elif creation_info_property == CreationInfoProperty.COMMENT: diff --git a/src/jsonschema/document_converter.py b/src/jsonschema/document_converter.py index 98f5b784a..d04e4e8e2 100644 --- a/src/jsonschema/document_converter.py +++ b/src/jsonschema/document_converter.py @@ -71,7 +71,7 @@ def _get_property_value(self, document: Document, document_property: DocumentPro element_ids.extend([snippet.spdx_id for snippet in document.snippets]) document_annotations = filter(lambda annotation: annotation.spdx_id not in element_ids, document.annotations) - return [self.annotation_converter.convert(annotation) for annotation in document_annotations] + return [self.annotation_converter.convert(annotation) for annotation in document_annotations] or None elif document_property == DocumentProperty.COMMENT: return document.creation_info.document_comment elif document_property == DocumentProperty.CREATION_INFO: @@ -80,10 +80,10 @@ def _get_property_value(self, document: Document, document_property: DocumentPro return document.creation_info.data_license elif document_property == DocumentProperty.EXTERNAL_DOCUMENT_REFS: return [self.external_document_ref_converter.convert(external_document_ref) for - external_document_ref in document.creation_info.external_document_refs] + external_document_ref in document.creation_info.external_document_refs] or None elif document_property == DocumentProperty.HAS_EXTRACTED_LICENSING_INFO: return [self.extracted_licensing_info_converter.convert(licensing_info) for licensing_info in - document.extracted_licensing_info] + document.extracted_licensing_info] or None elif document_property == DocumentProperty.NAME: return document.creation_info.name elif document_property == DocumentProperty.SPDX_VERSION: @@ -97,13 +97,13 @@ def _get_property_value(self, document: Document, document_property: DocumentPro described_by_ids = [relationship.spdx_element_id for relationship in filter_by_type_and_target(document.relationships, RelationshipType.DESCRIBED_BY, document.creation_info.spdx_id)] - return describes_ids + described_by_ids + return describes_ids + described_by_ids or None elif document_property == DocumentProperty.PACKAGES: - return [self.package_converter.convert(package, document) for package in document.packages] + return [self.package_converter.convert(package, document) for package in document.packages] or None elif document_property == DocumentProperty.FILES: - return [self.file_converter.convert(file, document) for file in document.files] + return [self.file_converter.convert(file, document) for file in document.files] or None elif document_property == DocumentProperty.SNIPPETS: - return [self.snippet_converter.convert(snippet, document) for snippet in document.snippets] + return [self.snippet_converter.convert(snippet, document) for snippet in document.snippets] or None elif document_property == DocumentProperty.RELATIONSHIPS: already_covered_relationships = filter_by_type_and_origin(document.relationships, RelationshipType.DESCRIBES, @@ -117,4 +117,4 @@ def _get_property_value(self, document: Document, document_property: DocumentPro relationships_to_ignore = [relationship for relationship in already_covered_relationships if relationship.comment is None] return [self.relationship_converter.convert(relationship) for relationship in document.relationships if - relationship not in relationships_to_ignore] + relationship not in relationships_to_ignore] or None diff --git a/src/jsonschema/extracted_licensing_info_converter.py b/src/jsonschema/extracted_licensing_info_converter.py index 25643ec3d..87f1312fa 100644 --- a/src/jsonschema/extracted_licensing_info_converter.py +++ b/src/jsonschema/extracted_licensing_info_converter.py @@ -34,7 +34,7 @@ def _get_property_value(self, extracted_licensing_info: ExtractedLicensingInfo, elif extracted_licensing_info_property == ExtractedLicensingInfoProperty.NAME: return extracted_licensing_info.license_name elif extracted_licensing_info_property == ExtractedLicensingInfoProperty.SEE_ALSOS: - return extracted_licensing_info.cross_references + return extracted_licensing_info.cross_references or None def get_json_type(self) -> Type[JsonProperty]: return ExtractedLicensingInfoProperty diff --git a/src/jsonschema/file_converter.py b/src/jsonschema/file_converter.py index 305239637..1b66a72d8 100644 --- a/src/jsonschema/file_converter.py +++ b/src/jsonschema/file_converter.py @@ -39,34 +39,34 @@ def _get_property_value(self, file: Any, file_property: FileProperty, document: return file.spdx_id elif file_property == FileProperty.ANNOTATIONS: file_annotations = filter(lambda annotation: annotation.spdx_id == file.spdx_id, document.annotations) - return [self.annotation_converter.convert(annotation) for annotation in file_annotations] + return [self.annotation_converter.convert(annotation) for annotation in file_annotations] or None elif file_property == FileProperty.ARTIFACT_OFS: # Deprecated property, automatically converted during parsing pass elif file_property == FileProperty.ATTRIBUTION_TEXTS: - return file.attribution_texts + return file.attribution_texts or None elif file_property == FileProperty.CHECKSUMS: - return [self.checksum_converter.convert(checksum) for checksum in file.checksums] + return [self.checksum_converter.convert(checksum) for checksum in file.checksums] or None elif file_property == FileProperty.COMMENT: return file.comment elif file_property == FileProperty.COPYRIGHT_TEXT: return apply_if_present(str, file.copyright_text) elif file_property == FileProperty.FILE_CONTRIBUTORS: - return file.contributors + return file.contributors or None elif file_property == FileProperty.FILE_DEPENDENCIES: # Deprecated property, automatically converted during parsing pass elif file_property == FileProperty.FILE_NAME: return file.name elif file_property == FileProperty.FILE_TYPES: - return [file_type.name for file_type in file.file_type] + return [file_type.name for file_type in file.file_type] or None elif file_property == FileProperty.LICENSE_COMMENTS: return file.license_comment elif file_property == FileProperty.LICENSE_CONCLUDED: return apply_if_present(str, file.concluded_license) elif file_property == FileProperty.LICENSE_INFO_IN_FILES: if isinstance(file.license_info_in_file, list): - return [str(license_expression) for license_expression in file.license_info_in_file] + return [str(license_expression) for license_expression in file.license_info_in_file] or None return apply_if_present(str, file.license_info_in_file) elif file_property == FileProperty.NOTICE_TEXT: return file.notice diff --git a/src/jsonschema/package_converter.py b/src/jsonschema/package_converter.py index 3253ec801..a15503e71 100644 --- a/src/jsonschema/package_converter.py +++ b/src/jsonschema/package_converter.py @@ -50,13 +50,14 @@ def _get_property_value(self, package: Package, package_property: PackagePropert return package.spdx_id elif package_property == PackageProperty.ANNOTATIONS: package_annotations = filter(lambda annotation: annotation.spdx_id == package.spdx_id, document.annotations) - return [self.annotation_converter.convert(annotation, document) for annotation in package_annotations] + return [self.annotation_converter.convert(annotation, document) for annotation in + package_annotations] or None elif package_property == PackageProperty.ATTRIBUTION_TEXTS: - return package.attribution_texts + return package.attribution_texts or None elif package_property == PackageProperty.BUILT_DATE: return apply_if_present(datetime_to_iso_string, package.built_date) elif package_property == PackageProperty.CHECKSUMS: - return [self.checksum_converter.convert(checksum, document) for checksum in package.checksums] + return [self.checksum_converter.convert(checksum, document) for checksum in package.checksums] or None elif package_property == PackageProperty.COMMENT: return package.comment elif package_property == PackageProperty.COPYRIGHT_TEXT: @@ -67,7 +68,7 @@ def _get_property_value(self, package: Package, package_property: PackagePropert return str(package.download_location) elif package_property == PackageProperty.EXTERNAL_REFS: return [self.external_package_ref_converter.convert(external_ref) for external_ref in - package.external_references] + package.external_references] or None elif package_property == PackageProperty.FILES_ANALYZED: return package.files_analyzed elif package_property == PackageProperty.HAS_FILES: @@ -75,7 +76,7 @@ def _get_property_value(self, package: Package, package_property: PackagePropert find_package_contains_file_relationships(document, package)] file_contained_in_package_ids = [relationship.spdx_element_id for relationship in find_file_contained_by_package_relationships(document, package)] - return package_contains_file_ids + file_contained_in_package_ids + return package_contains_file_ids + file_contained_in_package_ids or None elif package_property == PackageProperty.HOMEPAGE: return apply_if_present(str, package.homepage) elif package_property == PackageProperty.LICENSE_COMMENTS: @@ -86,7 +87,7 @@ def _get_property_value(self, package: Package, package_property: PackagePropert return apply_if_present(str, package.license_declared) elif package_property == PackageProperty.LICENSE_INFO_FROM_FILES: if isinstance(package.license_info_from_files, list): - return [str(license_expression) for license_expression in package.license_info_from_files] + return [str(license_expression) for license_expression in package.license_info_from_files] or None return apply_if_present(str, package.license_info_from_files) elif package_property == PackageProperty.NAME: return package.name diff --git a/src/jsonschema/package_verification_code_converter.py b/src/jsonschema/package_verification_code_converter.py index f8bdc68df..c312ef4b5 100644 --- a/src/jsonschema/package_verification_code_converter.py +++ b/src/jsonschema/package_verification_code_converter.py @@ -26,7 +26,7 @@ def _get_property_value(self, verification_code: PackageVerificationCode, verification_code_property: PackageVerificationCodeProperty, document: Document = None) -> Any: if verification_code_property == PackageVerificationCodeProperty.PACKAGE_VERIFICATION_CODE_EXCLUDED_FILES: - return verification_code.excluded_files + return verification_code.excluded_files or None elif verification_code_property == PackageVerificationCodeProperty.PACKAGE_VERIFICATION_CODE_VALUE: return verification_code.value diff --git a/src/jsonschema/snippet_converter.py b/src/jsonschema/snippet_converter.py index f907072fa..6e73f0849 100644 --- a/src/jsonschema/snippet_converter.py +++ b/src/jsonschema/snippet_converter.py @@ -37,9 +37,9 @@ def _get_property_value(self, snippet: Snippet, snippet_property: SnippetPropert return snippet.spdx_id elif snippet_property == SnippetProperty.ANNOTATIONS: snippet_annotations = filter(lambda annotation: annotation.spdx_id == snippet.spdx_id, document.annotations) - return [self.annotation_converter.convert(annotation) for annotation in snippet_annotations] + return [self.annotation_converter.convert(annotation) for annotation in snippet_annotations] or None elif snippet_property == SnippetProperty.ATTRIBUTION_TEXTS: - return snippet.attribution_texts + return snippet.attribution_texts or None elif snippet_property == SnippetProperty.COMMENT: return snippet.comment elif snippet_property == SnippetProperty.COPYRIGHT_TEXT: @@ -50,7 +50,7 @@ def _get_property_value(self, snippet: Snippet, snippet_property: SnippetPropert return apply_if_present(str, snippet.concluded_license) elif snippet_property == SnippetProperty.LICENSE_INFO_IN_SNIPPETS: if isinstance(snippet.license_info_in_snippet, list): - return [str(license_expression) for license_expression in snippet.license_info_in_snippet] + return [str(license_expression) for license_expression in snippet.license_info_in_snippet] or None return apply_if_present(str, snippet.license_info_in_snippet) elif snippet_property == SnippetProperty.NAME: return snippet.name diff --git a/tests/jsonschema/test_creation_info_converter.py b/tests/jsonschema/test_creation_info_converter.py index 0fb500ebc..6e236d798 100644 --- a/tests/jsonschema/test_creation_info_converter.py +++ b/tests/jsonschema/test_creation_info_converter.py @@ -51,12 +51,13 @@ def test_successful_conversion(converter: CreationInfoConverter): def test_null_values(converter: CreationInfoConverter): - creation_info = creation_info_fixture(license_list_version=None, creator_comment=None) + creation_info = creation_info_fixture(license_list_version=None, creator_comment=None, creators=[]) converted_dict = converter.convert(creation_info) assert converter.json_property_name(CreationInfoProperty.LICENSE_LIST_VERSION) not in converted_dict assert converter.json_property_name(CreationInfoProperty.COMMENT) not in converted_dict + assert converter.json_property_name(CreationInfoProperty.CREATORS) not in converted_dict def test_json_type(converter: CreationInfoConverter): diff --git a/tests/jsonschema/test_document_converter.py b/tests/jsonschema/test_document_converter.py index cac563dd5..b16ad4bbc 100644 --- a/tests/jsonschema/test_document_converter.py +++ b/tests/jsonschema/test_document_converter.py @@ -116,3 +116,18 @@ def test_json_type(converter: DocumentConverter): def test_data_model_type(converter: DocumentConverter): assert converter.get_data_model_type() == Document + + +def test_null_values(converter: DocumentConverter): + document = Document(creation_info_fixture(external_document_refs=[])) + + converted_dict = converter.convert(document) + + assert converter.json_property_name(DocumentProperty.ANNOTATIONS) not in converted_dict + assert converter.json_property_name(DocumentProperty.EXTERNAL_DOCUMENT_REFS) not in converted_dict + assert converter.json_property_name(DocumentProperty.HAS_EXTRACTED_LICENSING_INFO) not in converted_dict + assert converter.json_property_name(DocumentProperty.DOCUMENT_DESCRIBES) not in converted_dict + assert converter.json_property_name(DocumentProperty.PACKAGES) not in converted_dict + assert converter.json_property_name(DocumentProperty.FILES) not in converted_dict + assert converter.json_property_name(DocumentProperty.SNIPPETS) not in converted_dict + assert converter.json_property_name(DocumentProperty.RELATIONSHIPS) not in converted_dict diff --git a/tests/jsonschema/test_extracted_licensing_info_converter.py b/tests/jsonschema/test_extracted_licensing_info_converter.py index 518a6f2b3..d5085d889 100644 --- a/tests/jsonschema/test_extracted_licensing_info_converter.py +++ b/tests/jsonschema/test_extracted_licensing_info_converter.py @@ -53,3 +53,15 @@ def test_successful_conversion(converter: ExtractedLicensingInfoConverter): assert converted_dict[converter.json_property_name(ExtractedLicensingInfoProperty.SEE_ALSOS)] == ["reference1", "reference2"] assert converted_dict[converter.json_property_name(ExtractedLicensingInfoProperty.COMMENT)] == "comment" + + +def test_null_values(converter: ExtractedLicensingInfoConverter): + extracted_licensing_info = ExtractedLicensingInfo(cross_references=[]) + + converted_dict = converter.convert(extracted_licensing_info) + + assert converter.json_property_name(ExtractedLicensingInfoProperty.LICENSE_ID) not in converted_dict + assert converter.json_property_name(ExtractedLicensingInfoProperty.EXTRACTED_TEXT) not in converted_dict + assert converter.json_property_name(ExtractedLicensingInfoProperty.NAME) not in converted_dict + assert converter.json_property_name(ExtractedLicensingInfoProperty.SEE_ALSOS) not in converted_dict + assert converter.json_property_name(ExtractedLicensingInfoProperty.COMMENT) not in converted_dict diff --git a/tests/jsonschema/test_file_converter.py b/tests/jsonschema/test_file_converter.py index 7cc6f0462..d90bf6883 100644 --- a/tests/jsonschema/test_file_converter.py +++ b/tests/jsonschema/test_file_converter.py @@ -103,7 +103,8 @@ def test_successful_conversion(converter: FileConverter): def test_null_values(converter: FileConverter): - file = file_fixture(copyright_text=None, concluded_license=None, license_comment=None, comment=None, notice=None) + file = file_fixture(copyright_text=None, concluded_license=None, license_comment=None, comment=None, notice=None, + attribution_texts=[], checksums=[], contributors=[], file_type=[], license_info_in_file=[]) document = Document(creation_info_fixture(), files=[file]) converted_dict = converter.convert(file, document) @@ -113,6 +114,12 @@ def test_null_values(converter: FileConverter): assert converter.json_property_name(FileProperty.LICENSE_COMMENTS) not in converted_dict assert converter.json_property_name(FileProperty.COMMENT) not in converted_dict assert converter.json_property_name(FileProperty.NOTICE_TEXT) not in converted_dict + assert converter.json_property_name(FileProperty.ANNOTATIONS) not in converted_dict + assert converter.json_property_name(FileProperty.ATTRIBUTION_TEXTS) not in converted_dict + assert converter.json_property_name(FileProperty.CHECKSUMS) not in converted_dict + assert converter.json_property_name(FileProperty.FILE_CONTRIBUTORS) not in converted_dict + assert converter.json_property_name(FileProperty.FILE_TYPES) not in converted_dict + assert converter.json_property_name(FileProperty.LICENSE_INFO_IN_FILES) not in converted_dict def test_spdx_no_assertion(converter: FileConverter): diff --git a/tests/jsonschema/test_package_converter.py b/tests/jsonschema/test_package_converter.py index ba769a616..71e8c1ae7 100644 --- a/tests/jsonschema/test_package_converter.py +++ b/tests/jsonschema/test_package_converter.py @@ -150,7 +150,8 @@ def test_null_values(converter: PackageConverter): license_concluded=None, license_declared=None, originator=None, verification_code=None, primary_package_purpose=None, supplier=None, version=None, file_name=None, source_info=None, license_comment=None, copyright_text=None, summary=None, - description=None, comment=None) + description=None, comment=None, attribution_texts=[], checksums=[], + external_references=[], license_info_from_files=[]) document = Document(creation_info_fixture(), packages=[package]) @@ -174,6 +175,12 @@ def test_null_values(converter: PackageConverter): assert converter.json_property_name(PackageProperty.BUILT_DATE) not in converted_dict assert converter.json_property_name(PackageProperty.RELEASE_DATE) not in converted_dict assert converter.json_property_name(PackageProperty.VALID_UNTIL_DATE) not in converted_dict + assert converter.json_property_name(PackageProperty.ANNOTATIONS) not in converted_dict + assert converter.json_property_name(PackageProperty.ATTRIBUTION_TEXTS) not in converted_dict + assert converter.json_property_name(PackageProperty.CHECKSUMS) not in converted_dict + assert converter.json_property_name(PackageProperty.EXTERNAL_REFS) not in converted_dict + assert converter.json_property_name(PackageProperty.HAS_FILES) not in converted_dict + assert converter.json_property_name(PackageProperty.LICENSE_INFO_FROM_FILES) not in converted_dict def test_spdx_no_assertion(converter: PackageConverter): diff --git a/tests/jsonschema/test_package_verification_code_converter.py b/tests/jsonschema/test_package_verification_code_converter.py index 926c6e428..48d7d649b 100644 --- a/tests/jsonschema/test_package_verification_code_converter.py +++ b/tests/jsonschema/test_package_verification_code_converter.py @@ -47,3 +47,12 @@ def test_successful_conversion(converter: PackageVerificationCodeConverter): PackageVerificationCodeProperty.PACKAGE_VERIFICATION_CODE_EXCLUDED_FILES)] == ["file1", "file2"] assert converted_dict[ converter.json_property_name(PackageVerificationCodeProperty.PACKAGE_VERIFICATION_CODE_VALUE)] == "value" + + +def test_null_values(converter: PackageVerificationCodeConverter): + package_verification_code = PackageVerificationCode("value") + + converted_dict = converter.convert(package_verification_code) + + assert converter.json_property_name( + PackageVerificationCodeProperty.PACKAGE_VERIFICATION_CODE_EXCLUDED_FILES) not in converted_dict diff --git a/tests/jsonschema/test_snippet_converter.py b/tests/jsonschema/test_snippet_converter.py index 746476d3a..61262ce5d 100644 --- a/tests/jsonschema/test_snippet_converter.py +++ b/tests/jsonschema/test_snippet_converter.py @@ -95,7 +95,7 @@ def test_successful_conversion(converter: SnippetConverter): def test_null_values(converter: SnippetConverter): snippet = snippet_fixture(concluded_license=None, license_comment=None, copyright_text=None, comment=None, - name=None) + name=None, attribution_texts=[], license_info_in_snippet=[]) document = Document(creation_info_fixture(), snippets=[snippet]) converted_dict = converter.convert(snippet, document) @@ -105,6 +105,9 @@ def test_null_values(converter: SnippetConverter): assert converter.json_property_name(SnippetProperty.COPYRIGHT_TEXT) not in converted_dict assert converter.json_property_name(SnippetProperty.COMMENT) not in converted_dict assert converter.json_property_name(SnippetProperty.NAME) not in converted_dict + assert converter.json_property_name(SnippetProperty.ANNOTATIONS) not in converted_dict + assert converter.json_property_name(SnippetProperty.ATTRIBUTION_TEXTS) not in converted_dict + assert converter.json_property_name(SnippetProperty.LICENSE_INFO_IN_SNIPPETS) not in converted_dict def test_spdx_no_assertion(converter: SnippetConverter): From 976287d1eaeb449b1b9feeba77afc7e8ae93f468 Mon Sep 17 00:00:00 2001 From: Nicolaus Weidner Date: Wed, 21 Dec 2022 13:43:05 +0100 Subject: [PATCH 34/55] [issue-359] Add json writer Signed-off-by: Nicolaus Weidner --- src/writer/json/__init__.py | 11 ++ src/writer/json/json_writer.py | 26 +++++ tests/writer/__init__.py | 10 ++ tests/writer/json/__init__.py | 10 ++ .../writer/json/expected_results/__init__.py | 10 ++ .../json/expected_results/expected.json | 101 ++++++++++++++++++ tests/writer/json/test_json_writer.py | 67 ++++++++++++ 7 files changed, 235 insertions(+) create mode 100644 src/writer/json/__init__.py create mode 100644 src/writer/json/json_writer.py create mode 100644 tests/writer/__init__.py create mode 100644 tests/writer/json/__init__.py create mode 100644 tests/writer/json/expected_results/__init__.py create mode 100644 tests/writer/json/expected_results/expected.json create mode 100644 tests/writer/json/test_json_writer.py diff --git a/src/writer/json/__init__.py b/src/writer/json/__init__.py new file mode 100644 index 000000000..7b5337962 --- /dev/null +++ b/src/writer/json/__init__.py @@ -0,0 +1,11 @@ +# 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. + diff --git a/src/writer/json/json_writer.py b/src/writer/json/json_writer.py new file mode 100644 index 000000000..f6c85f5a4 --- /dev/null +++ b/src/writer/json/json_writer.py @@ -0,0 +1,26 @@ +# 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 json + +from src.jsonschema.document_converter import DocumentConverter +from src.model.document import Document + + +class JsonWriter: + converter: DocumentConverter + + def __init__(self): + self.converter = DocumentConverter() + + def write_document(self, document: Document, file_name: str) -> None: + document_dict = self.converter.convert(document) + with open(file_name, "w") as out: + json.dump(document_dict, out, indent=4) diff --git a/tests/writer/__init__.py b/tests/writer/__init__.py new file mode 100644 index 000000000..cbc5c4070 --- /dev/null +++ b/tests/writer/__init__.py @@ -0,0 +1,10 @@ +# 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. diff --git a/tests/writer/json/__init__.py b/tests/writer/json/__init__.py new file mode 100644 index 000000000..cbc5c4070 --- /dev/null +++ b/tests/writer/json/__init__.py @@ -0,0 +1,10 @@ +# 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. diff --git a/tests/writer/json/expected_results/__init__.py b/tests/writer/json/expected_results/__init__.py new file mode 100644 index 000000000..cbc5c4070 --- /dev/null +++ b/tests/writer/json/expected_results/__init__.py @@ -0,0 +1,10 @@ +# 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. diff --git a/tests/writer/json/expected_results/expected.json b/tests/writer/json/expected_results/expected.json new file mode 100644 index 000000000..3335e12eb --- /dev/null +++ b/tests/writer/json/expected_results/expected.json @@ -0,0 +1,101 @@ +{ + "SPDXID": "documentId", + "annotations": [ + { + "annotationDate": "2022-12-02T00:00:00Z", + "annotationType": "REVIEW", + "annotator": "Person: reviewerName", + "comment": "reviewComment" + } + ], + "comment": "comment", + "creationInfo": { + "created": "2022-12-01T00:00:00Z", + "creators": [ + "Tool: tools-python (tools-python@github.com)" + ] + }, + "dataLicense": "dataLicense", + "externalDocumentRefs": [ + { + "externalDocumentId": "docRefId", + "spdxDocument": "externalDocumentUri", + "checksum": { + "algorithm": "SHA1", + "checksumValue": "externalRefSha1" + } + } + ], + "hasExtractedLicensingInfo": [ + { + "extractedText": "licenseText", + "licenseId": "licenseId" + } + ], + "name": "documentName", + "spdxVersion": "spdxVersion", + "documentNamespace": "documentNamespace", + "documentDescribes": [ + "packageId", + "fileId" + ], + "packages": [ + { + "SPDXID": "packageId", + "downloadLocation": "NONE", + "filesAnalyzed": true, + "name": "packageName" + } + ], + "files": [ + { + "SPDXID": "fileId", + "annotations": [ + { + "annotationDate": "2022-12-03T00:00:00Z", + "annotationType": "OTHER", + "annotator": "Tool: toolName", + "comment": "otherComment" + } + ], + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "fileSha1" + } + ], + "fileName": "fileName" + } + ], + "snippets": [ + { + "SPDXID": "snippetId", + "ranges": [ + { + "startPointer": { + "reference": "snippetFileId", + "offset": 1 + }, + "endPointer": { + "reference": "snippetFileId", + "offset": 2 + } + } + ], + "snippetFromFile": "snippetFileId" + } + ], + "relationships": [ + { + "spdxElementId": "documentId", + "comment": "relationshipComment", + "relatedSpdxElement": "fileId", + "relationshipType": "DESCRIBES" + }, + { + "spdxElementId": "relationshipOriginId", + "relatedSpdxElement": "relationShipTargetId", + "relationshipType": "AMENDS" + } + ] +} diff --git a/tests/writer/json/test_json_writer.py b/tests/writer/json/test_json_writer.py new file mode 100644 index 000000000..8b11bab16 --- /dev/null +++ b/tests/writer/json/test_json_writer.py @@ -0,0 +1,67 @@ +# 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 json +import os +from datetime import datetime + +import pytest + +from src.model.actor import Actor, ActorType +from src.model.annotation import Annotation, AnnotationType +from src.model.checksum import ChecksumAlgorithm, Checksum +from src.model.document import CreationInfo, Document +from src.model.external_document_ref import ExternalDocumentRef +from src.model.extracted_licensing_info import ExtractedLicensingInfo +from src.model.file import File +from src.model.package import Package +from src.model.relationship import RelationshipType, Relationship +from src.model.snippet import Snippet +from src.model.spdx_none import SpdxNone +from src.writer.json.json_writer import JsonWriter + + +@pytest.fixture +def temporary_file_path() -> str: + temporary_file_path = "temp_test_json_writer_output.json" + yield temporary_file_path + os.remove(temporary_file_path) + + +def test_write_json(temporary_file_path: str): + writer = JsonWriter() + creation_info = CreationInfo("spdxVersion", "documentId", "documentName", "documentNamespace", + [Actor(ActorType.TOOL, "tools-python", "tools-python@github.com")], + datetime(2022, 12, 1), document_comment="comment", data_license="dataLicense", + external_document_refs=[ExternalDocumentRef("docRefId", "externalDocumentUri", + Checksum(ChecksumAlgorithm.SHA1, + "externalRefSha1"))]) + package = Package("packageId", "packageName", SpdxNone()) + file = File("fileName", "fileId", [Checksum(ChecksumAlgorithm.SHA1, "fileSha1")]) + snippet = Snippet("snippetId", "snippetFileId", (1, 2)) + document = Document(creation_info, annotations=[ + Annotation("documentId", AnnotationType.REVIEW, Actor(ActorType.PERSON, "reviewerName"), + datetime(2022, 12, 2), "reviewComment"), + Annotation("fileId", AnnotationType.OTHER, Actor(ActorType.TOOL, "toolName"), datetime(2022, 12, 3), + "otherComment")], + extracted_licensing_info=[ExtractedLicensingInfo("licenseId", "licenseText")], relationships=[ + Relationship(creation_info.spdx_id, RelationshipType.DESCRIBES, "packageId"), + Relationship(creation_info.spdx_id, RelationshipType.DESCRIBES, "fileId", "relationshipComment"), + Relationship("relationshipOriginId", RelationshipType.AMENDS, "relationShipTargetId")], packages=[package], + files=[file], snippets=[snippet]) + writer.write_document(document, temporary_file_path) + + with open(temporary_file_path) as written_file: + written_json = json.load(written_file) + + with open(os.path.join(os.path.dirname(__file__), 'expected_results', 'expected.json')) as expected_file: + expected_json = json.load(expected_file) + + assert written_json == expected_json From 23894d7224983f63d35b259d753c0f8829b1c8f1 Mon Sep 17 00:00:00 2001 From: Nicolaus Weidner Date: Fri, 30 Dec 2022 17:22:15 +0100 Subject: [PATCH 35/55] [issue-359][squash] extract variables in test_json_writer.py Signed-off-by: Nicolaus Weidner --- tests/writer/json/test_json_writer.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/tests/writer/json/test_json_writer.py b/tests/writer/json/test_json_writer.py index 8b11bab16..ea699dad4 100644 --- a/tests/writer/json/test_json_writer.py +++ b/tests/writer/json/test_json_writer.py @@ -46,16 +46,18 @@ def test_write_json(temporary_file_path: str): package = Package("packageId", "packageName", SpdxNone()) file = File("fileName", "fileId", [Checksum(ChecksumAlgorithm.SHA1, "fileSha1")]) snippet = Snippet("snippetId", "snippetFileId", (1, 2)) - document = Document(creation_info, annotations=[ + relationships = [ + Relationship(creation_info.spdx_id, RelationshipType.DESCRIBES, "packageId"), + Relationship(creation_info.spdx_id, RelationshipType.DESCRIBES, "fileId", "relationshipComment"), + Relationship("relationshipOriginId", RelationshipType.AMENDS, "relationShipTargetId")] + annotations = [ Annotation("documentId", AnnotationType.REVIEW, Actor(ActorType.PERSON, "reviewerName"), datetime(2022, 12, 2), "reviewComment"), Annotation("fileId", AnnotationType.OTHER, Actor(ActorType.TOOL, "toolName"), datetime(2022, 12, 3), - "otherComment")], - extracted_licensing_info=[ExtractedLicensingInfo("licenseId", "licenseText")], relationships=[ - Relationship(creation_info.spdx_id, RelationshipType.DESCRIBES, "packageId"), - Relationship(creation_info.spdx_id, RelationshipType.DESCRIBES, "fileId", "relationshipComment"), - Relationship("relationshipOriginId", RelationshipType.AMENDS, "relationShipTargetId")], packages=[package], - files=[file], snippets=[snippet]) + "otherComment")] + extracted_licensing_info = [ExtractedLicensingInfo("licenseId", "licenseText")] + document = Document(creation_info, annotations=annotations, extracted_licensing_info=extracted_licensing_info, + relationships=relationships, packages=[package], files=[file], snippets=[snippet]) writer.write_document(document, temporary_file_path) with open(temporary_file_path) as written_file: From 35d235e4e16bf176ef986347faf946988e8cda33 Mon Sep 17 00:00:00 2001 From: Nicolaus Weidner Date: Wed, 21 Dec 2022 23:46:26 +0100 Subject: [PATCH 36/55] [issue-359] Make conversion test assertions more precise by excluding any additional properties Signed-off-by: Nicolaus Weidner --- tests/jsonschema/test_annotation_converter.py | 12 ++-- tests/jsonschema/test_checksum_converter.py | 6 +- tests/jsonschema/test_converter.py | 6 +- .../test_creation_info_converter.py | 11 ++-- tests/jsonschema/test_document_converter.py | 38 ++++++------ .../test_external_document_ref_converter.py | 10 +-- .../test_external_package_ref_converter.py | 11 ++-- ...test_extracted_licensing_info_converter.py | 14 ++--- tests/jsonschema/test_file_converter.py | 33 +++++----- tests/jsonschema/test_package_converter.py | 61 +++++++++---------- ...est_package_verification_code_converter.py | 9 +-- .../jsonschema/test_relationship_converter.py | 10 +-- tests/jsonschema/test_snippet_converter.py | 35 +++++------ 13 files changed, 130 insertions(+), 126 deletions(-) diff --git a/tests/jsonschema/test_annotation_converter.py b/tests/jsonschema/test_annotation_converter.py index 1f64ba336..714b7bb4a 100644 --- a/tests/jsonschema/test_annotation_converter.py +++ b/tests/jsonschema/test_annotation_converter.py @@ -48,9 +48,9 @@ def test_successful_conversion(converter: AnnotationConverter): converted_dict = converter.convert(annotation) - assert converted_dict[converter.json_property_name(AnnotationProperty.ANNOTATION_DATE)] == datetime_to_iso_string( - date) - assert converted_dict[converter.json_property_name(AnnotationProperty.ANNOTATION_TYPE)] == "REVIEW" - assert converted_dict[ - converter.json_property_name(AnnotationProperty.ANNOTATOR)] == annotator.to_serialized_string() - assert converted_dict[converter.json_property_name(AnnotationProperty.COMMENT)] == "comment" + assert converted_dict == { + converter.json_property_name(AnnotationProperty.ANNOTATION_DATE): datetime_to_iso_string(date), + converter.json_property_name(AnnotationProperty.ANNOTATION_TYPE): "REVIEW", + converter.json_property_name(AnnotationProperty.ANNOTATOR): annotator.to_serialized_string(), + converter.json_property_name(AnnotationProperty.COMMENT): "comment" + } diff --git a/tests/jsonschema/test_checksum_converter.py b/tests/jsonschema/test_checksum_converter.py index 088f853f1..9412761de 100644 --- a/tests/jsonschema/test_checksum_converter.py +++ b/tests/jsonschema/test_checksum_converter.py @@ -31,8 +31,10 @@ def test_successful_conversion(converter: ChecksumConverter): converted_dict = converter.convert(checksum) - assert converted_dict[converter.json_property_name(ChecksumProperty.ALGORITHM)] == "SHA1" - assert converted_dict[converter.json_property_name(ChecksumProperty.CHECKSUM_VALUE)] == "123" + assert converted_dict == { + converter.json_property_name(ChecksumProperty.ALGORITHM): "SHA1", + converter.json_property_name(ChecksumProperty.CHECKSUM_VALUE): "123" + } def test_json_type(converter: ChecksumConverter): diff --git a/tests/jsonschema/test_converter.py b/tests/jsonschema/test_converter.py index add64a706..0123c3b19 100644 --- a/tests/jsonschema/test_converter.py +++ b/tests/jsonschema/test_converter.py @@ -63,8 +63,10 @@ def test_conversion(): converted_dict = converter.convert(test_instance) - assert converted_dict.get("jsonFirstName") == "firstPropertyValue" - assert converted_dict.get("jsonSecondName") == 3 + assert converted_dict == { + "jsonFirstName": "firstPropertyValue", + "jsonSecondName": 3 + } def test_wrong_type(): diff --git a/tests/jsonschema/test_creation_info_converter.py b/tests/jsonschema/test_creation_info_converter.py index 6e236d798..87cd0a0a1 100644 --- a/tests/jsonschema/test_creation_info_converter.py +++ b/tests/jsonschema/test_creation_info_converter.py @@ -43,11 +43,12 @@ def test_successful_conversion(converter: CreationInfoConverter): creation_info_fixture(creators=creators, created=created, creator_comment="comment", license_list_version=Version(1, 2))) - assert converted_dict[converter.json_property_name(CreationInfoProperty.CREATED)] == datetime_to_iso_string(created) - assert converted_dict[converter.json_property_name(CreationInfoProperty.CREATORS)] == ["Person: personName", - "Tool: toolName"] - assert converted_dict[converter.json_property_name(CreationInfoProperty.LICENSE_LIST_VERSION)] == "1.2" - assert converted_dict[converter.json_property_name(CreationInfoProperty.COMMENT)] == "comment" + assert converted_dict == { + converter.json_property_name(CreationInfoProperty.CREATED): datetime_to_iso_string(created), + converter.json_property_name(CreationInfoProperty.CREATORS): ["Person: personName", "Tool: toolName"], + converter.json_property_name(CreationInfoProperty.LICENSE_LIST_VERSION): "1.2", + converter.json_property_name(CreationInfoProperty.COMMENT): "comment" + } def test_null_values(converter: CreationInfoConverter): diff --git a/tests/jsonschema/test_document_converter.py b/tests/jsonschema/test_document_converter.py index b16ad4bbc..4a7e29754 100644 --- a/tests/jsonschema/test_document_converter.py +++ b/tests/jsonschema/test_document_converter.py @@ -88,26 +88,24 @@ def test_successful_conversion(converter: DocumentConverter): converted_dict = converter.convert(document) - assert converted_dict[converter.json_property_name(DocumentProperty.SPDX_ID)] == "spdxId" - assert converted_dict[converter.json_property_name(DocumentProperty.ANNOTATIONS)] == ["mock_converted_annotation"] - assert converted_dict[converter.json_property_name(DocumentProperty.COMMENT)] == "comment" - assert converted_dict[ - converter.json_property_name(DocumentProperty.CREATION_INFO)] == "mock_converted_creation_info" - assert converted_dict[converter.json_property_name(DocumentProperty.DATA_LICENSE)] == "dataLicense" - assert converted_dict[ - converter.json_property_name( - DocumentProperty.EXTERNAL_DOCUMENT_REFS)] == ["mock_converted_external_ref"] - assert converted_dict[converter.json_property_name(DocumentProperty.HAS_EXTRACTED_LICENSING_INFO)] == [ - "mock_converted_extracted_licensing_info"] - assert converted_dict[converter.json_property_name(DocumentProperty.NAME)] == "name" - assert converted_dict[converter.json_property_name(DocumentProperty.SPDX_VERSION)] == "spdxVersion" - assert converted_dict[converter.json_property_name(DocumentProperty.DOCUMENT_NAMESPACE)] == "namespace" - assert converted_dict[converter.json_property_name(DocumentProperty.DOCUMENT_DESCRIBES)] == ["describedElementId"] - assert converted_dict[converter.json_property_name(DocumentProperty.PACKAGES)] == ["mock_converted_package"] - assert converted_dict[converter.json_property_name(DocumentProperty.FILES)] == ["mock_converted_file"] - assert converted_dict[converter.json_property_name(DocumentProperty.SNIPPETS)] == ["mock_converted_snippet"] - assert converted_dict[converter.json_property_name(DocumentProperty.RELATIONSHIPS)] == [ - "mock_converted_relationship"] + assert converted_dict == { + converter.json_property_name(DocumentProperty.SPDX_ID): "spdxId", + converter.json_property_name(DocumentProperty.ANNOTATIONS): ["mock_converted_annotation"], + converter.json_property_name(DocumentProperty.COMMENT): "comment", + converter.json_property_name(DocumentProperty.CREATION_INFO): "mock_converted_creation_info", + converter.json_property_name(DocumentProperty.DATA_LICENSE): "dataLicense", + converter.json_property_name(DocumentProperty.EXTERNAL_DOCUMENT_REFS): ["mock_converted_external_ref"], + converter.json_property_name(DocumentProperty.HAS_EXTRACTED_LICENSING_INFO): [ + "mock_converted_extracted_licensing_info"], + converter.json_property_name(DocumentProperty.NAME): "name", + converter.json_property_name(DocumentProperty.SPDX_VERSION): "spdxVersion", + converter.json_property_name(DocumentProperty.DOCUMENT_NAMESPACE): "namespace", + converter.json_property_name(DocumentProperty.DOCUMENT_DESCRIBES): ["describedElementId"], + converter.json_property_name(DocumentProperty.PACKAGES): ["mock_converted_package"], + converter.json_property_name(DocumentProperty.FILES): ["mock_converted_file"], + converter.json_property_name(DocumentProperty.SNIPPETS): ["mock_converted_snippet"], + converter.json_property_name(DocumentProperty.RELATIONSHIPS): ["mock_converted_relationship"] + } def test_json_type(converter: DocumentConverter): diff --git a/tests/jsonschema/test_external_document_ref_converter.py b/tests/jsonschema/test_external_document_ref_converter.py index 0694b95ea..51932c125 100644 --- a/tests/jsonschema/test_external_document_ref_converter.py +++ b/tests/jsonschema/test_external_document_ref_converter.py @@ -44,11 +44,11 @@ def test_successful_conversion(converter: ExternalDocumentRefConverter): converted_dict = converter.convert(external_document_ref) - assert converted_dict[ - converter.json_property_name(ExternalDocumentRefProperty.EXTERNAL_DOCUMENT_ID)] == "document_ref_id" - assert converted_dict[converter.json_property_name(ExternalDocumentRefProperty.SPDX_DOCUMENT)] == "document_uri" - assert converted_dict[ - converter.json_property_name(ExternalDocumentRefProperty.CHECKSUM)] == "mock_converted_checksum" + assert converted_dict == { + converter.json_property_name(ExternalDocumentRefProperty.EXTERNAL_DOCUMENT_ID): "document_ref_id", + converter.json_property_name(ExternalDocumentRefProperty.SPDX_DOCUMENT): "document_uri", + converter.json_property_name(ExternalDocumentRefProperty.CHECKSUM): "mock_converted_checksum" + } def test_json_type(converter: ExternalDocumentRefConverter): diff --git a/tests/jsonschema/test_external_package_ref_converter.py b/tests/jsonschema/test_external_package_ref_converter.py index 44094c00d..7af0752e1 100644 --- a/tests/jsonschema/test_external_package_ref_converter.py +++ b/tests/jsonschema/test_external_package_ref_converter.py @@ -43,8 +43,9 @@ def test_successful_conversion(converter: ExternalPackageRefConverter): converted_dict = converter.convert(external_package_ref) - assert converted_dict[converter.json_property_name(ExternalPackageRefProperty.COMMENT)] == "comment" - assert converted_dict[ - converter.json_property_name(ExternalPackageRefProperty.REFERENCE_CATEGORY)] == "PACKAGE_MANAGER" - assert converted_dict[converter.json_property_name(ExternalPackageRefProperty.REFERENCE_LOCATOR)] == "locator" - assert converted_dict[converter.json_property_name(ExternalPackageRefProperty.REFERENCE_TYPE)] == "type" + assert converted_dict == { + converter.json_property_name(ExternalPackageRefProperty.COMMENT): "comment", + converter.json_property_name(ExternalPackageRefProperty.REFERENCE_CATEGORY): "PACKAGE_MANAGER", + converter.json_property_name(ExternalPackageRefProperty.REFERENCE_LOCATOR): "locator", + converter.json_property_name(ExternalPackageRefProperty.REFERENCE_TYPE): "type" + } diff --git a/tests/jsonschema/test_extracted_licensing_info_converter.py b/tests/jsonschema/test_extracted_licensing_info_converter.py index d5085d889..39c146e84 100644 --- a/tests/jsonschema/test_extracted_licensing_info_converter.py +++ b/tests/jsonschema/test_extracted_licensing_info_converter.py @@ -46,13 +46,13 @@ def test_successful_conversion(converter: ExtractedLicensingInfoConverter): converted_dict = converter.convert(extracted_licensing_info) - assert converted_dict[converter.json_property_name(ExtractedLicensingInfoProperty.LICENSE_ID)] == "licenseId" - assert converted_dict[ - converter.json_property_name(ExtractedLicensingInfoProperty.EXTRACTED_TEXT)] == "Extracted text" - assert converted_dict[converter.json_property_name(ExtractedLicensingInfoProperty.NAME)] == "license name" - assert converted_dict[converter.json_property_name(ExtractedLicensingInfoProperty.SEE_ALSOS)] == ["reference1", - "reference2"] - assert converted_dict[converter.json_property_name(ExtractedLicensingInfoProperty.COMMENT)] == "comment" + assert converted_dict == { + converter.json_property_name(ExtractedLicensingInfoProperty.LICENSE_ID): "licenseId", + converter.json_property_name(ExtractedLicensingInfoProperty.EXTRACTED_TEXT): "Extracted text", + converter.json_property_name(ExtractedLicensingInfoProperty.NAME): "license name", + converter.json_property_name(ExtractedLicensingInfoProperty.SEE_ALSOS): ["reference1", "reference2"], + converter.json_property_name(ExtractedLicensingInfoProperty.COMMENT): "comment" + } def test_null_values(converter: ExtractedLicensingInfoConverter): diff --git a/tests/jsonschema/test_file_converter.py b/tests/jsonschema/test_file_converter.py index d90bf6883..faca0a954 100644 --- a/tests/jsonschema/test_file_converter.py +++ b/tests/jsonschema/test_file_converter.py @@ -82,24 +82,21 @@ def test_successful_conversion(converter: FileConverter): converted_dict = converter.convert(file, document) - assert converted_dict[converter.json_property_name(FileProperty.SPDX_ID)] == "spdxId" - assert converted_dict[converter.json_property_name(FileProperty.ANNOTATIONS)] == ["mock_converted_annotation"] - assert converted_dict[converter.json_property_name(FileProperty.ATTRIBUTION_TEXTS)] == ["attributionText1", - "attributionText2"] - assert converted_dict[converter.json_property_name(FileProperty.CHECKSUMS)] == ["mock_converted_checksum", - "mock_converted_checksum"] - assert converted_dict[converter.json_property_name(FileProperty.COMMENT)] == "comment" - assert converted_dict[ - converter.json_property_name(FileProperty.COPYRIGHT_TEXT)] == "copyrightText" - assert converted_dict[converter.json_property_name(FileProperty.FILE_CONTRIBUTORS)] == ["contributor1", - "contributor2"] - assert converted_dict[converter.json_property_name(FileProperty.FILE_NAME)] == "name" - assert converted_dict[converter.json_property_name(FileProperty.FILE_TYPES)] == ["SPDX", "OTHER"] - assert converted_dict[converter.json_property_name(FileProperty.LICENSE_COMMENTS)] == "licenseComment" - assert converted_dict[converter.json_property_name(FileProperty.LICENSE_CONCLUDED)] == "licenseExpression1" - assert converted_dict[converter.json_property_name(FileProperty.LICENSE_INFO_IN_FILES)] == ["licenseExpression2", - "licenseExpression3"] - assert converted_dict[converter.json_property_name(FileProperty.NOTICE_TEXT)] == "notice" + assert converted_dict == { + converter.json_property_name(FileProperty.SPDX_ID): "spdxId", + converter.json_property_name(FileProperty.ANNOTATIONS): ["mock_converted_annotation"], + converter.json_property_name(FileProperty.ATTRIBUTION_TEXTS): ["attributionText1", "attributionText2"], + converter.json_property_name(FileProperty.CHECKSUMS): ["mock_converted_checksum", "mock_converted_checksum"], + converter.json_property_name(FileProperty.COMMENT): "comment", + converter.json_property_name(FileProperty.COPYRIGHT_TEXT): "copyrightText", + converter.json_property_name(FileProperty.FILE_CONTRIBUTORS): ["contributor1", "contributor2"], + converter.json_property_name(FileProperty.FILE_NAME): "name", + converter.json_property_name(FileProperty.FILE_TYPES): ["SPDX", "OTHER"], + converter.json_property_name(FileProperty.LICENSE_COMMENTS): "licenseComment", + converter.json_property_name(FileProperty.LICENSE_CONCLUDED): "licenseExpression1", + converter.json_property_name(FileProperty.LICENSE_INFO_IN_FILES): ["licenseExpression2", "licenseExpression3"], + converter.json_property_name(FileProperty.NOTICE_TEXT): "notice" + } def test_null_values(converter: FileConverter): diff --git a/tests/jsonschema/test_package_converter.py b/tests/jsonschema/test_package_converter.py index 71e8c1ae7..3592a9e46 100644 --- a/tests/jsonschema/test_package_converter.py +++ b/tests/jsonschema/test_package_converter.py @@ -112,37 +112,36 @@ def test_successful_conversion(converter: PackageConverter): converted_dict = converter.convert(package, document) - assert converted_dict[converter.json_property_name(PackageProperty.SPDX_ID)] == "packageId" - assert converted_dict[converter.json_property_name(PackageProperty.ANNOTATIONS)] == ["mock_converted_annotation"] - assert converted_dict[converter.json_property_name(PackageProperty.ATTRIBUTION_TEXTS)] == ["attributionText1", - "attributionText2"] - assert converted_dict[converter.json_property_name(PackageProperty.NAME)] == "name" - assert converted_dict[converter.json_property_name(PackageProperty.DOWNLOAD_LOCATION)] == "downloadLocation" - assert converted_dict[converter.json_property_name(PackageProperty.VERSION_INFO)] == "version" - assert converted_dict[converter.json_property_name(PackageProperty.PACKAGE_FILE_NAME)] == "fileName" - assert converted_dict[converter.json_property_name(PackageProperty.SUPPLIER)] == "Person: supplierName" - assert converted_dict[converter.json_property_name(PackageProperty.ORIGINATOR)] == "Person: originatorName" - assert converted_dict[converter.json_property_name(PackageProperty.FILES_ANALYZED)] - assert converted_dict[converter.json_property_name( - PackageProperty.PACKAGE_VERIFICATION_CODE)] == "mock_converted_verification_code" - assert converted_dict[converter.json_property_name(PackageProperty.CHECKSUMS)] == ["mock_converted_checksum", - "mock_converted_checksum"] - assert converted_dict[converter.json_property_name(PackageProperty.HOMEPAGE)] == "homepage" - assert converted_dict[converter.json_property_name(PackageProperty.SOURCE_INFO)] == "sourceInfo" - assert converted_dict[converter.json_property_name(PackageProperty.LICENSE_CONCLUDED)] == "licenseExpression1" - assert converted_dict[converter.json_property_name(PackageProperty.LICENSE_INFO_FROM_FILES)] == [ - "licenseExpression2", "licenseExpression3"] - assert converted_dict[converter.json_property_name(PackageProperty.LICENSE_DECLARED)] == "licenseExpression4" - assert converted_dict[converter.json_property_name(PackageProperty.LICENSE_COMMENTS)] == "licenseComment" - assert converted_dict[converter.json_property_name(PackageProperty.COPYRIGHT_TEXT)] == "copyrightText" - assert converted_dict[converter.json_property_name(PackageProperty.SUMMARY)] == "summary" - assert converted_dict[converter.json_property_name(PackageProperty.DESCRIPTION)] == "description" - assert converted_dict[converter.json_property_name(PackageProperty.COMMENT)] == "comment" - assert converted_dict[converter.json_property_name(PackageProperty.EXTERNAL_REFS)] == ["mock_package_ref"] - assert converted_dict[converter.json_property_name(PackageProperty.PRIMARY_PACKAGE_PURPOSE)] == "APPLICATION" - assert converted_dict[converter.json_property_name(PackageProperty.RELEASE_DATE)] == "2022-12-01T00:00:00Z" - assert converted_dict[converter.json_property_name(PackageProperty.BUILT_DATE)] == "2022-12-02T00:00:00Z" - assert converted_dict[converter.json_property_name(PackageProperty.VALID_UNTIL_DATE)] == "2022-12-03T00:00:00Z" + assert converted_dict == { + converter.json_property_name(PackageProperty.SPDX_ID): "packageId", + converter.json_property_name(PackageProperty.ANNOTATIONS): ["mock_converted_annotation"], + converter.json_property_name(PackageProperty.ATTRIBUTION_TEXTS): ["attributionText1", "attributionText2"], + converter.json_property_name(PackageProperty.NAME): "name", + converter.json_property_name(PackageProperty.DOWNLOAD_LOCATION): "downloadLocation", + converter.json_property_name(PackageProperty.VERSION_INFO): "version", + converter.json_property_name(PackageProperty.PACKAGE_FILE_NAME): "fileName", + converter.json_property_name(PackageProperty.SUPPLIER): "Person: supplierName", + converter.json_property_name(PackageProperty.ORIGINATOR): "Person: originatorName", + converter.json_property_name(PackageProperty.FILES_ANALYZED): True, + converter.json_property_name(PackageProperty.PACKAGE_VERIFICATION_CODE): "mock_converted_verification_code", + converter.json_property_name(PackageProperty.CHECKSUMS): ["mock_converted_checksum", "mock_converted_checksum"], + converter.json_property_name(PackageProperty.HOMEPAGE): "homepage", + converter.json_property_name(PackageProperty.SOURCE_INFO): "sourceInfo", + converter.json_property_name(PackageProperty.LICENSE_CONCLUDED): "licenseExpression1", + converter.json_property_name(PackageProperty.LICENSE_INFO_FROM_FILES): ["licenseExpression2", + "licenseExpression3"], + converter.json_property_name(PackageProperty.LICENSE_DECLARED): "licenseExpression4", + converter.json_property_name(PackageProperty.LICENSE_COMMENTS): "licenseComment", + converter.json_property_name(PackageProperty.COPYRIGHT_TEXT): "copyrightText", + converter.json_property_name(PackageProperty.SUMMARY): "summary", + converter.json_property_name(PackageProperty.DESCRIPTION): "description", + converter.json_property_name(PackageProperty.COMMENT): "comment", + converter.json_property_name(PackageProperty.EXTERNAL_REFS): ["mock_package_ref"], + converter.json_property_name(PackageProperty.PRIMARY_PACKAGE_PURPOSE): "APPLICATION", + converter.json_property_name(PackageProperty.RELEASE_DATE): "2022-12-01T00:00:00Z", + converter.json_property_name(PackageProperty.BUILT_DATE): "2022-12-02T00:00:00Z", + converter.json_property_name(PackageProperty.VALID_UNTIL_DATE): "2022-12-03T00:00:00Z" + } def test_null_values(converter: PackageConverter): diff --git a/tests/jsonschema/test_package_verification_code_converter.py b/tests/jsonschema/test_package_verification_code_converter.py index 48d7d649b..a7050c492 100644 --- a/tests/jsonschema/test_package_verification_code_converter.py +++ b/tests/jsonschema/test_package_verification_code_converter.py @@ -43,10 +43,11 @@ def test_successful_conversion(converter: PackageVerificationCodeConverter): converted_dict = converter.convert(package_verification_code) - assert converted_dict[converter.json_property_name( - PackageVerificationCodeProperty.PACKAGE_VERIFICATION_CODE_EXCLUDED_FILES)] == ["file1", "file2"] - assert converted_dict[ - converter.json_property_name(PackageVerificationCodeProperty.PACKAGE_VERIFICATION_CODE_VALUE)] == "value" + assert converted_dict == { + converter.json_property_name(PackageVerificationCodeProperty.PACKAGE_VERIFICATION_CODE_EXCLUDED_FILES): [ + "file1", "file2"], + converter.json_property_name(PackageVerificationCodeProperty.PACKAGE_VERIFICATION_CODE_VALUE): "value" + } def test_null_values(converter: PackageVerificationCodeConverter): diff --git a/tests/jsonschema/test_relationship_converter.py b/tests/jsonschema/test_relationship_converter.py index 6c6a51a57..a4b372935 100644 --- a/tests/jsonschema/test_relationship_converter.py +++ b/tests/jsonschema/test_relationship_converter.py @@ -43,7 +43,9 @@ def test_successful_conversion(converter: RelationshipConverter): converted_dict = converter.convert(relationship) - assert converted_dict[converter.json_property_name(RelationshipProperty.SPDX_ELEMENT_ID)] == "spdxElementId" - assert converted_dict[converter.json_property_name(RelationshipProperty.COMMENT)] == "comment" - assert converted_dict[converter.json_property_name(RelationshipProperty.RELATED_SPDX_ELEMENT)] == "relatedElementId" - assert converted_dict[converter.json_property_name(RelationshipProperty.RELATIONSHIP_TYPE)] == "COPY_OF" + assert converted_dict == { + converter.json_property_name(RelationshipProperty.SPDX_ELEMENT_ID): "spdxElementId", + converter.json_property_name(RelationshipProperty.COMMENT): "comment", + converter.json_property_name(RelationshipProperty.RELATED_SPDX_ELEMENT): "relatedElementId", + converter.json_property_name(RelationshipProperty.RELATIONSHIP_TYPE): "COPY_OF" + } diff --git a/tests/jsonschema/test_snippet_converter.py b/tests/jsonschema/test_snippet_converter.py index 61262ce5d..d75705de4 100644 --- a/tests/jsonschema/test_snippet_converter.py +++ b/tests/jsonschema/test_snippet_converter.py @@ -74,23 +74,24 @@ def test_successful_conversion(converter: SnippetConverter): document = Document(creation_info_fixture(), snippets=[snippet], annotations=[annotation]) converted_dict = converter.convert(snippet, document) - assert converted_dict[converter.json_property_name(SnippetProperty.SPDX_ID)] == "spdxId" - assert converted_dict[converter.json_property_name(SnippetProperty.ANNOTATIONS)] == ["mock_converted_annotation"] - assert converted_dict[converter.json_property_name(SnippetProperty.ATTRIBUTION_TEXTS)] == ["attributionText1", - "attributionText2"] - assert converted_dict[converter.json_property_name(SnippetProperty.COMMENT)] == "comment" - assert converted_dict[converter.json_property_name(SnippetProperty.COPYRIGHT_TEXT)] == "copyrightText" - assert converted_dict[converter.json_property_name(SnippetProperty.LICENSE_COMMENTS)] == "licenseComment" - assert converted_dict[converter.json_property_name(SnippetProperty.LICENSE_CONCLUDED)] == "licenseExpression1" - assert converted_dict[converter.json_property_name(SnippetProperty.LICENSE_INFO_IN_SNIPPETS)] == [ - "licenseExpression2", "licenseExpression3"] - assert converted_dict[converter.json_property_name(SnippetProperty.NAME)] == "name" - assert converted_dict[converter.json_property_name(SnippetProperty.RANGES)] == [ - {"startPointer": {"reference": file_spdx_id, "offset": 1}, - "endPointer": {"reference": file_spdx_id, "offset": 2}}, - {"startPointer": {"reference": file_spdx_id, "lineNumber": 3}, - "endPointer": {"reference": file_spdx_id, "lineNumber": 4}}] - assert converted_dict[converter.json_property_name(SnippetProperty.SNIPPET_FROM_FILE)] == file_spdx_id + assert converted_dict == { + converter.json_property_name(SnippetProperty.SPDX_ID): "spdxId", + converter.json_property_name(SnippetProperty.ANNOTATIONS): ["mock_converted_annotation"], + converter.json_property_name(SnippetProperty.ATTRIBUTION_TEXTS): ["attributionText1", "attributionText2"], + converter.json_property_name(SnippetProperty.COMMENT): "comment", + converter.json_property_name(SnippetProperty.COPYRIGHT_TEXT): "copyrightText", + converter.json_property_name(SnippetProperty.LICENSE_COMMENTS): "licenseComment", + converter.json_property_name(SnippetProperty.LICENSE_CONCLUDED): "licenseExpression1", + converter.json_property_name(SnippetProperty.LICENSE_INFO_IN_SNIPPETS): ["licenseExpression2", + "licenseExpression3"], + converter.json_property_name(SnippetProperty.NAME): "name", + converter.json_property_name(SnippetProperty.RANGES): [ + {"startPointer": {"reference": file_spdx_id, "offset": 1}, + "endPointer": {"reference": file_spdx_id, "offset": 2}}, + {"startPointer": {"reference": file_spdx_id, "lineNumber": 3}, + "endPointer": {"reference": file_spdx_id, "lineNumber": 4}}], + converter.json_property_name(SnippetProperty.SNIPPET_FROM_FILE): file_spdx_id + } def test_null_values(converter: SnippetConverter): From 000cd803fca39d84468c6f13572da5d9c196ea84 Mon Sep 17 00:00:00 2001 From: Nicolaus Weidner Date: Thu, 22 Dec 2022 11:30:25 +0100 Subject: [PATCH 37/55] [issue-359] Add more test fixtures Signed-off-by: Nicolaus Weidner --- tests/fixtures.py | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/tests/fixtures.py b/tests/fixtures.py index ae9d4b3d8..16c757ee0 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -11,13 +11,16 @@ from datetime import datetime from src.model.actor import Actor, ActorType +from src.model.annotation import Annotation, AnnotationType from src.model.checksum import Checksum, ChecksumAlgorithm -from src.model.document import CreationInfo +from src.model.document import CreationInfo, Document from src.model.external_document_ref import ExternalDocumentRef +from src.model.extracted_licensing_info import ExtractedLicensingInfo from src.model.file import File, FileType from src.model.license_expression import LicenseExpression from src.model.package import Package, PackageVerificationCode, PackagePurpose, ExternalPackageRef, \ ExternalPackageRefCategory +from src.model.relationship import Relationship, RelationshipType from src.model.snippet import Snippet from src.model.version import Version @@ -105,3 +108,33 @@ def snippet_fixture(spdx_id="snippetId", file_spdx_id="snippetFromFileId", byte_ concluded_license=concluded_license, license_info_in_snippet=license_info_in_snippet, license_comment=license_comment, copyright_text=copyright_text, comment=comment, name=name, attribution_texts=attribution_texts) + + +def annotation_fixture(spdx_id="annotatedElementId", annotation_type=AnnotationType.REVIEW, + annotator=Actor(ActorType.PERSON, "annotatorName"), annotation_date=datetime(2022, 12, 1), + annotation_comment="annotationComment") -> Annotation: + return Annotation(spdx_id=spdx_id, annotation_type=annotation_type, annotator=annotator, + annotation_date=annotation_date, annotation_comment=annotation_comment) + + +def extracted_licensing_info_fixture(license_id="licenseId", extracted_text="extractedText", license_name="licenseName", + cross_references=None, comment="licenseComment") -> ExtractedLicensingInfo: + cross_references = ["crossReference"] if cross_references is None else cross_references + return ExtractedLicensingInfo(license_id=license_id, extracted_text=extracted_text, license_name=license_name, + cross_references=cross_references, comment=comment) + + +def document_fixture(creation_info=None, packages=None, files=None, snippets=None, annotations=None, relationships=None, + extracted_licensing_info=None) -> Document: + creation_info = creation_info_fixture() if creation_info is None else creation_info + packages = [package_fixture()] if packages is None else packages + files = [file_fixture()] if files is None else files + snippets = [snippet_fixture()] if snippets is None else snippets + annotations = [annotation_fixture()] if annotations is None else annotations + relationships = [Relationship("relationshipOriginId", RelationshipType.DESCRIBES, "relationshipTargetId", + "relationshipComment")] if relationships is None else relationships + extracted_licensing_info = [ + extracted_licensing_info_fixture()] if extracted_licensing_info is None else extracted_licensing_info + return Document(creation_info=creation_info, packages=packages, files=files, snippets=snippets, + annotations=annotations, relationships=relationships, + extracted_licensing_info=extracted_licensing_info) From fba6e21980ea8c0f677067e1c11d5c59a96038a6 Mon Sep 17 00:00:00 2001 From: Nicolaus Weidner Date: Thu, 22 Dec 2022 11:30:52 +0100 Subject: [PATCH 38/55] [issue-359] Add utility assertion function for mocks Signed-off-by: Nicolaus Weidner --- tests/mock_utils.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 tests/mock_utils.py diff --git a/tests/mock_utils.py b/tests/mock_utils.py new file mode 100644 index 000000000..9564ae88b --- /dev/null +++ b/tests/mock_utils.py @@ -0,0 +1,19 @@ +# 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. +from unittest.mock import NonCallableMagicMock + + +def assert_mock_method_called_with_arguments(mock_object: NonCallableMagicMock, method_name: str, *args): + assert len(mock_object.method_calls) == len(args) + for running_index in range(len(args)): + call = mock_object.method_calls[running_index] + assert call[0] == method_name + assert call.args[0] == args[running_index] From 410dd3454a3b8c5d3013870f23d76da459df9ec5 Mon Sep 17 00:00:00 2001 From: Nicolaus Weidner Date: Thu, 22 Dec 2022 11:31:12 +0100 Subject: [PATCH 39/55] [issue-359] Add tests for annotation writing logic Signed-off-by: Nicolaus Weidner --- tests/jsonschema/test_document_converter.py | 35 +++++++++++++++++++-- tests/jsonschema/test_file_converter.py | 32 +++++++++++++++++-- tests/jsonschema/test_package_converter.py | 33 +++++++++++++++++-- tests/jsonschema/test_snippet_converter.py | 32 +++++++++++++++++-- 4 files changed, 124 insertions(+), 8 deletions(-) diff --git a/tests/jsonschema/test_document_converter.py b/tests/jsonschema/test_document_converter.py index 4a7e29754..1041ba83d 100644 --- a/tests/jsonschema/test_document_converter.py +++ b/tests/jsonschema/test_document_converter.py @@ -9,11 +9,13 @@ # See the License for the specific language governing permissions and # limitations under the License. from datetime import datetime +from typing import Union from unittest import mock -from unittest.mock import MagicMock +from unittest.mock import MagicMock, NonCallableMagicMock import pytest +from src.jsonschema.annotation_converter import AnnotationConverter from src.jsonschema.document_converter import DocumentConverter from src.jsonschema.document_properties import DocumentProperty from src.model.actor import Actor, ActorType @@ -22,7 +24,8 @@ from src.model.extracted_licensing_info import ExtractedLicensingInfo from src.model.relationship import Relationship, RelationshipType from tests.fixtures import creation_info_fixture, file_fixture, package_fixture, external_document_ref_fixture, \ - snippet_fixture + snippet_fixture, annotation_fixture +from tests.mock_utils import assert_mock_method_called_with_arguments @pytest.fixture @@ -129,3 +132,31 @@ def test_null_values(converter: DocumentConverter): assert converter.json_property_name(DocumentProperty.FILES) not in converted_dict assert converter.json_property_name(DocumentProperty.SNIPPETS) not in converted_dict assert converter.json_property_name(DocumentProperty.RELATIONSHIPS) not in converted_dict + + +def test_document_annotations(converter: DocumentConverter): + file = file_fixture(spdx_id="fileId") + package = package_fixture(spdx_id="packageId") + snippet = snippet_fixture(spdx_id="snippetId") + document_id = "documentId" + + # There are 5 annotations: one each referencing the document, package, file and snippet, and one with an id + # matching none of the Spdx elements. The writer is expected to add the package, file and snippet annotations to + # those elements, so the document should receive the other two. + document_annotation = annotation_fixture(spdx_id=document_id) + other_annotation = annotation_fixture(spdx_id="otherId") + annotations = [annotation_fixture(spdx_id=file.spdx_id), annotation_fixture(spdx_id=package.spdx_id), + annotation_fixture(spdx_id=snippet.spdx_id), document_annotation, + other_annotation] + document = Document(creation_info_fixture(spdx_id=document_id), files=[file], packages=[package], + snippets=[snippet], annotations=annotations) + + # Weird type hint to make warnings about unresolved references from the mock class disappear + annotation_converter: Union[AnnotationConverter, NonCallableMagicMock] = converter.annotation_converter + annotation_converter.convert.return_value = "mock_converted_annotation" + + converted_dict = converter.convert(document) + + assert_mock_method_called_with_arguments(annotation_converter, "convert", document_annotation, other_annotation) + converted_document_annotations = converted_dict.get(converter.json_property_name(DocumentProperty.ANNOTATIONS)) + assert converted_document_annotations == ["mock_converted_annotation", "mock_converted_annotation"] diff --git a/tests/jsonschema/test_file_converter.py b/tests/jsonschema/test_file_converter.py index faca0a954..7dd802544 100644 --- a/tests/jsonschema/test_file_converter.py +++ b/tests/jsonschema/test_file_converter.py @@ -9,11 +9,13 @@ # See the License for the specific language governing permissions and # limitations under the License. from datetime import datetime +from typing import Union from unittest import mock -from unittest.mock import MagicMock +from unittest.mock import MagicMock, NonCallableMagicMock import pytest +from src.jsonschema.annotation_converter import AnnotationConverter from src.jsonschema.file_converter import FileConverter from src.jsonschema.file_properties import FileProperty from src.model.actor import Actor, ActorType @@ -24,7 +26,8 @@ from src.model.license_expression import LicenseExpression from src.model.spdx_no_assertion import SpdxNoAssertion, SPDX_NO_ASSERTION_STRING from src.model.spdx_none import SpdxNone, SPDX_NONE_STRING -from tests.fixtures import creation_info_fixture, file_fixture +from tests.fixtures import creation_info_fixture, file_fixture, annotation_fixture, document_fixture +from tests.mock_utils import assert_mock_method_called_with_arguments @pytest.fixture @@ -142,3 +145,28 @@ def test_spdx_none(converter: FileConverter): converter.json_property_name(FileProperty.COPYRIGHT_TEXT)] == SPDX_NONE_STRING assert converted_dict[converter.json_property_name(FileProperty.LICENSE_CONCLUDED)] == SPDX_NONE_STRING assert converted_dict[converter.json_property_name(FileProperty.LICENSE_INFO_IN_FILES)] == SPDX_NONE_STRING + + +def test_file_annotations(converter: FileConverter): + file = file_fixture(spdx_id="fileId") + document = document_fixture(files=[file]) + first_file_annotation = annotation_fixture(spdx_id=file.spdx_id) + second_file_annotation = annotation_fixture(spdx_id=file.spdx_id) + document_annotation = annotation_fixture(spdx_id=document.creation_info.spdx_id) + package_annotation = annotation_fixture(spdx_id=document.packages[0].spdx_id) + snippet_annotation = annotation_fixture(spdx_id=document.snippets[0].spdx_id) + other_annotation = annotation_fixture(spdx_id="otherId") + annotations = [first_file_annotation, second_file_annotation, document_annotation, package_annotation, + snippet_annotation, other_annotation] + document.annotations = annotations + + # Weird type hint to make warnings about unresolved references from the mock class disappear + annotation_converter: Union[AnnotationConverter, NonCallableMagicMock] = converter.annotation_converter + annotation_converter.convert.return_value = "mock_converted_annotation" + + converted_dict = converter.convert(file, document) + + assert_mock_method_called_with_arguments(annotation_converter, "convert", first_file_annotation, + second_file_annotation) + converted_file_annotations = converted_dict.get(converter.json_property_name(FileProperty.ANNOTATIONS)) + assert converted_file_annotations == ["mock_converted_annotation", "mock_converted_annotation"] diff --git a/tests/jsonschema/test_package_converter.py b/tests/jsonschema/test_package_converter.py index 3592a9e46..cd49333cf 100644 --- a/tests/jsonschema/test_package_converter.py +++ b/tests/jsonschema/test_package_converter.py @@ -9,11 +9,13 @@ # See the License for the specific language governing permissions and # limitations under the License. from datetime import datetime +from typing import Union from unittest import mock -from unittest.mock import MagicMock +from unittest.mock import MagicMock, NonCallableMagicMock import pytest +from src.jsonschema.annotation_converter import AnnotationConverter from src.jsonschema.package_converter import PackageConverter from src.jsonschema.package_properties import PackageProperty from src.model.actor import Actor, ActorType @@ -24,7 +26,9 @@ from src.model.package import Package, PackageVerificationCode, PackagePurpose from src.model.spdx_no_assertion import SpdxNoAssertion, SPDX_NO_ASSERTION_STRING from src.model.spdx_none import SpdxNone, SPDX_NONE_STRING -from tests.fixtures import creation_info_fixture, package_fixture, external_package_ref_fixture +from tests.fixtures import creation_info_fixture, package_fixture, external_package_ref_fixture, document_fixture, \ + annotation_fixture +from tests.mock_utils import assert_mock_method_called_with_arguments @pytest.fixture @@ -218,3 +222,28 @@ def test_spdx_none(converter: PackageConverter): assert converted_dict[converter.json_property_name(PackageProperty.LICENSE_INFO_FROM_FILES)] == SPDX_NONE_STRING assert converted_dict[converter.json_property_name(PackageProperty.LICENSE_DECLARED)] == SPDX_NONE_STRING assert converted_dict[converter.json_property_name(PackageProperty.COPYRIGHT_TEXT)] == SPDX_NONE_STRING + + +def test_package_annotations(converter: PackageConverter): + package = package_fixture(spdx_id="packageId") + document = document_fixture(packages=[package]) + first_package_annotation = annotation_fixture(spdx_id=package.spdx_id) + second_package_annotation = annotation_fixture(spdx_id=package.spdx_id) + document_annotation = annotation_fixture(spdx_id=document.creation_info.spdx_id) + file_annotation = annotation_fixture(spdx_id=document.files[0].spdx_id) + snippet_annotation = annotation_fixture(spdx_id=document.snippets[0].spdx_id) + other_annotation = annotation_fixture(spdx_id="otherId") + annotations = [first_package_annotation, second_package_annotation, document_annotation, file_annotation, + snippet_annotation, other_annotation] + document.annotations = annotations + + # Weird type hint to make warnings about unresolved references from the mock class disappear + annotation_converter: Union[AnnotationConverter, NonCallableMagicMock] = converter.annotation_converter + annotation_converter.convert.return_value = "mock_converted_annotation" + + converted_dict = converter.convert(package, document) + + assert_mock_method_called_with_arguments(annotation_converter, "convert", first_package_annotation, + second_package_annotation) + converted_file_annotations = converted_dict.get(converter.json_property_name(PackageProperty.ANNOTATIONS)) + assert converted_file_annotations == ["mock_converted_annotation", "mock_converted_annotation"] diff --git a/tests/jsonschema/test_snippet_converter.py b/tests/jsonschema/test_snippet_converter.py index d75705de4..75432235c 100644 --- a/tests/jsonschema/test_snippet_converter.py +++ b/tests/jsonschema/test_snippet_converter.py @@ -9,11 +9,13 @@ # See the License for the specific language governing permissions and # limitations under the License. from datetime import datetime +from typing import Union from unittest import mock -from unittest.mock import MagicMock +from unittest.mock import MagicMock, NonCallableMagicMock import pytest +from src.jsonschema.annotation_converter import AnnotationConverter from src.jsonschema.snippet_converter import SnippetConverter from src.jsonschema.snippet_properties import SnippetProperty from src.model.actor import Actor, ActorType @@ -23,7 +25,8 @@ from src.model.snippet import Snippet from src.model.spdx_no_assertion import SpdxNoAssertion, SPDX_NO_ASSERTION_STRING from src.model.spdx_none import SpdxNone, SPDX_NONE_STRING -from tests.fixtures import creation_info_fixture, snippet_fixture +from tests.fixtures import creation_info_fixture, snippet_fixture, document_fixture, annotation_fixture +from tests.mock_utils import assert_mock_method_called_with_arguments @pytest.fixture @@ -131,3 +134,28 @@ def test_spdx_none(converter: SnippetConverter): assert converted_dict[converter.json_property_name(SnippetProperty.LICENSE_CONCLUDED)] == SPDX_NONE_STRING assert converted_dict[ converter.json_property_name(SnippetProperty.LICENSE_INFO_IN_SNIPPETS)] == SPDX_NONE_STRING + + +def test_snippet_annotations(converter: SnippetConverter): + snippet = snippet_fixture(spdx_id="snippetId") + document = document_fixture(snippets=[snippet]) + first_snippet_annotation = annotation_fixture(spdx_id=snippet.spdx_id) + second_snippet_annotation = annotation_fixture(spdx_id=snippet.spdx_id) + document_annotation = annotation_fixture(spdx_id=document.creation_info.spdx_id) + package_annotation = annotation_fixture(spdx_id=document.packages[0].spdx_id) + file_annotation = annotation_fixture(spdx_id=document.files[0].spdx_id) + other_annotation = annotation_fixture(spdx_id="otherId") + annotations = [first_snippet_annotation, second_snippet_annotation, document_annotation, package_annotation, + file_annotation, other_annotation] + document.annotations = annotations + + # Weird type hint to make warnings about unresolved references from the mock class disappear + annotation_converter: Union[AnnotationConverter, NonCallableMagicMock] = converter.annotation_converter + annotation_converter.convert.return_value = "mock_converted_annotation" + + converted_dict = converter.convert(snippet, document) + + assert_mock_method_called_with_arguments(annotation_converter, "convert", first_snippet_annotation, + second_snippet_annotation) + converted_file_annotations = converted_dict.get(converter.json_property_name(SnippetProperty.ANNOTATIONS)) + assert converted_file_annotations == ["mock_converted_annotation", "mock_converted_annotation"] From fbf6e57bf121d8a63cea79ce999512adcd3b2382 Mon Sep 17 00:00:00 2001 From: Nicolaus Weidner Date: Thu, 22 Dec 2022 11:38:13 +0100 Subject: [PATCH 40/55] [issue-359] Add relationship fixture to test fixtures Signed-off-by: Nicolaus Weidner --- tests/fixtures.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/fixtures.py b/tests/fixtures.py index 16c757ee0..12da10c32 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -131,10 +131,15 @@ def document_fixture(creation_info=None, packages=None, files=None, snippets=Non files = [file_fixture()] if files is None else files snippets = [snippet_fixture()] if snippets is None else snippets annotations = [annotation_fixture()] if annotations is None else annotations - relationships = [Relationship("relationshipOriginId", RelationshipType.DESCRIBES, "relationshipTargetId", - "relationshipComment")] if relationships is None else relationships + relationships = [relationship_fixture()] if relationships is None else relationships extracted_licensing_info = [ extracted_licensing_info_fixture()] if extracted_licensing_info is None else extracted_licensing_info return Document(creation_info=creation_info, packages=packages, files=files, snippets=snippets, annotations=annotations, relationships=relationships, extracted_licensing_info=extracted_licensing_info) + + +def relationship_fixture(spdx_element_id="relationshipOriginId", relationship_type=RelationshipType.DESCRIBES, + related_spdx_element_id="relationshipTargetId", comment="relationshipComment") -> Relationship: + return Relationship(spdx_element_id=spdx_element_id, relationship_type=relationship_type, + related_spdx_element_id=related_spdx_element_id, comment=comment) From f9997da20cee484ce1086265bc08fff6a5f58215 Mon Sep 17 00:00:00 2001 From: Nicolaus Weidner Date: Thu, 22 Dec 2022 12:05:33 +0100 Subject: [PATCH 41/55] [issue-359] Add document_describes and has_files tests Signed-off-by: Nicolaus Weidner --- tests/jsonschema/test_document_converter.py | 23 +++++++++++++- tests/jsonschema/test_package_converter.py | 33 ++++++++++++++++++++- 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/tests/jsonschema/test_document_converter.py b/tests/jsonschema/test_document_converter.py index 1041ba83d..923f2fc20 100644 --- a/tests/jsonschema/test_document_converter.py +++ b/tests/jsonschema/test_document_converter.py @@ -24,7 +24,7 @@ from src.model.extracted_licensing_info import ExtractedLicensingInfo from src.model.relationship import Relationship, RelationshipType from tests.fixtures import creation_info_fixture, file_fixture, package_fixture, external_document_ref_fixture, \ - snippet_fixture, annotation_fixture + snippet_fixture, annotation_fixture, document_fixture, relationship_fixture from tests.mock_utils import assert_mock_method_called_with_arguments @@ -160,3 +160,24 @@ def test_document_annotations(converter: DocumentConverter): assert_mock_method_called_with_arguments(annotation_converter, "convert", document_annotation, other_annotation) converted_document_annotations = converted_dict.get(converter.json_property_name(DocumentProperty.ANNOTATIONS)) assert converted_document_annotations == ["mock_converted_annotation", "mock_converted_annotation"] + + +def test_document_describes(converter: DocumentConverter): + document = document_fixture() + document_id = document.creation_info.spdx_id + document_describes_relationship = relationship_fixture(spdx_element_id=document_id, + relationship_type=RelationshipType.DESCRIBES, + related_spdx_element_id="describesId") + described_by_document_relationship = relationship_fixture(related_spdx_element_id=document_id, + relationship_type=RelationshipType.DESCRIBED_BY, + spdx_element_id="describedById") + other_describes_relationship = relationship_fixture(relationship_type=RelationshipType.DESCRIBES) + other_relationship = relationship_fixture(spdx_element_id=document_id, relationship_type=RelationshipType.CONTAINS) + document.relationships = [document_describes_relationship, described_by_document_relationship, + other_describes_relationship, other_relationship] + + converted_dict = converter.convert(document) + + document_describes = converted_dict.get(converter.json_property_name(DocumentProperty.DOCUMENT_DESCRIBES)) + assert document_describes == [document_describes_relationship.related_spdx_element_id, + described_by_document_relationship.spdx_element_id] diff --git a/tests/jsonschema/test_package_converter.py b/tests/jsonschema/test_package_converter.py index cd49333cf..c6e6735be 100644 --- a/tests/jsonschema/test_package_converter.py +++ b/tests/jsonschema/test_package_converter.py @@ -24,10 +24,11 @@ from src.model.document import Document from src.model.license_expression import LicenseExpression from src.model.package import Package, PackageVerificationCode, PackagePurpose +from src.model.relationship import RelationshipType from src.model.spdx_no_assertion import SpdxNoAssertion, SPDX_NO_ASSERTION_STRING from src.model.spdx_none import SpdxNone, SPDX_NONE_STRING from tests.fixtures import creation_info_fixture, package_fixture, external_package_ref_fixture, document_fixture, \ - annotation_fixture + annotation_fixture, file_fixture, relationship_fixture, snippet_fixture from tests.mock_utils import assert_mock_method_called_with_arguments @@ -247,3 +248,33 @@ def test_package_annotations(converter: PackageConverter): second_package_annotation) converted_file_annotations = converted_dict.get(converter.json_property_name(PackageProperty.ANNOTATIONS)) assert converted_file_annotations == ["mock_converted_annotation", "mock_converted_annotation"] + + +def test_has_files(converter: PackageConverter): + package = package_fixture() + first_contained_file = file_fixture(spdx_id="firstFileId") + second_contained_file = file_fixture(spdx_id="secondFileId") + non_contained_file = file_fixture(spdx_id="otherFileId") + snippet = snippet_fixture() + document = document_fixture(packages=[package], + files=[first_contained_file, second_contained_file, non_contained_file], + snippets=[snippet]) + package_contains_file_relationship = relationship_fixture(spdx_element_id=package.spdx_id, + relationship_type=RelationshipType.CONTAINS, + related_spdx_element_id=first_contained_file.spdx_id) + file_contained_in_package_relationship = relationship_fixture(spdx_element_id=second_contained_file.spdx_id, + relationship_type=RelationshipType.CONTAINED_BY, + related_spdx_element_id=package.spdx_id) + package_contains_snippet_relationship = relationship_fixture(spdx_element_id=package.spdx_id, + relationship_type=RelationshipType.CONTAINS, + related_spdx_element_id=snippet.spdx_id) + package_describes_file_relationship = relationship_fixture(spdx_element_id=package.spdx_id, + relationship_type=RelationshipType.DESCRIBES, + related_spdx_element_id=non_contained_file.spdx_id) + document.relationships = [package_contains_file_relationship, file_contained_in_package_relationship, + package_contains_snippet_relationship, package_describes_file_relationship] + + converted_dict = converter.convert(package, document) + + has_files = converted_dict.get(converter.json_property_name(PackageProperty.HAS_FILES)) + assert has_files == [first_contained_file.spdx_id, second_contained_file.spdx_id] From fe7b0bd8991b9a75fb3762ff5e492259d5a41ef0 Mon Sep 17 00:00:00 2001 From: Nicolaus Weidner Date: Thu, 22 Dec 2022 13:45:19 +0100 Subject: [PATCH 42/55] [issue-359] Add tests for relationship conversion logic in DocumentConverter Signed-off-by: Nicolaus Weidner --- tests/jsonschema/test_document_converter.py | 45 ++++++++++++++++++++- tests/mock_utils.py | 4 ++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/tests/jsonschema/test_document_converter.py b/tests/jsonschema/test_document_converter.py index 923f2fc20..934c3c5c6 100644 --- a/tests/jsonschema/test_document_converter.py +++ b/tests/jsonschema/test_document_converter.py @@ -18,6 +18,7 @@ from src.jsonschema.annotation_converter import AnnotationConverter from src.jsonschema.document_converter import DocumentConverter from src.jsonschema.document_properties import DocumentProperty +from src.jsonschema.relationship_converter import RelationshipConverter from src.model.actor import Actor, ActorType from src.model.annotation import Annotation, AnnotationType from src.model.document import Document @@ -25,7 +26,7 @@ from src.model.relationship import Relationship, RelationshipType from tests.fixtures import creation_info_fixture, file_fixture, package_fixture, external_document_ref_fixture, \ snippet_fixture, annotation_fixture, document_fixture, relationship_fixture -from tests.mock_utils import assert_mock_method_called_with_arguments +from tests.mock_utils import assert_mock_method_called_with_arguments, assert_no_mock_methods_called @pytest.fixture @@ -181,3 +182,45 @@ def test_document_describes(converter: DocumentConverter): document_describes = converted_dict.get(converter.json_property_name(DocumentProperty.DOCUMENT_DESCRIBES)) assert document_describes == [document_describes_relationship.related_spdx_element_id, described_by_document_relationship.spdx_element_id] + + +DOCUMENT_ID = "docConverterTestDocumentId" +PACKAGE_ID = "docConverterTestPackageId" +FILE_ID = "docConverterTestFileId" + + +@pytest.mark.parametrize("relationship,should_be_written", + [(relationship_fixture(DOCUMENT_ID, RelationshipType.DESCRIBES), True), + (relationship_fixture(DOCUMENT_ID, RelationshipType.DESCRIBES, comment=None), False), + (relationship_fixture(relationship_type=RelationshipType.DESCRIBED_BY, + related_spdx_element_id=DOCUMENT_ID), True), + (relationship_fixture(relationship_type=RelationshipType.DESCRIBED_BY, + related_spdx_element_id=DOCUMENT_ID, comment=None), False), + (relationship_fixture(DOCUMENT_ID, RelationshipType.AMENDS, comment=None), True), + (relationship_fixture(PACKAGE_ID, RelationshipType.CONTAINS, FILE_ID), True), + (relationship_fixture(PACKAGE_ID, RelationshipType.CONTAINS, FILE_ID, comment=None), False), + (relationship_fixture(FILE_ID, RelationshipType.CONTAINED_BY, PACKAGE_ID), True), + (relationship_fixture(FILE_ID, RelationshipType.CONTAINED_BY, PACKAGE_ID, comment=None), + False), + (relationship_fixture(PACKAGE_ID, RelationshipType.CONTAINS, comment=None), True), + (relationship_fixture(PACKAGE_ID, RelationshipType.COPY_OF, FILE_ID, comment=None), True)]) +def test_document_relationships(converter: DocumentConverter, relationship: Relationship, should_be_written: bool): + package = package_fixture(spdx_id=PACKAGE_ID) + file = file_fixture(spdx_id=FILE_ID) + document = document_fixture(creation_info_fixture(spdx_id=DOCUMENT_ID), packages=[package], files=[file], + relationships=[relationship]) + + # Weird type hint to make warnings about unresolved references from the mock class disappear + relationship_converter: Union[RelationshipConverter, NonCallableMagicMock] = converter.relationship_converter + relationship_converter.convert.return_value = "mock_converted_relationship" + + converted_dict = converter.convert(document) + + relationships = converted_dict.get(converter.json_property_name(DocumentProperty.RELATIONSHIPS)) + + if should_be_written: + assert_mock_method_called_with_arguments(relationship_converter, "convert", relationship) + assert relationships == ["mock_converted_relationship"] + else: + assert_no_mock_methods_called(relationship_converter) + assert relationships is None diff --git a/tests/mock_utils.py b/tests/mock_utils.py index 9564ae88b..06bbc836f 100644 --- a/tests/mock_utils.py +++ b/tests/mock_utils.py @@ -17,3 +17,7 @@ def assert_mock_method_called_with_arguments(mock_object: NonCallableMagicMock, call = mock_object.method_calls[running_index] assert call[0] == method_name assert call.args[0] == args[running_index] + + +def assert_no_mock_methods_called(mock_object: NonCallableMagicMock): + assert len(mock_object.method_calls) == 0 From c2b9ac08a9e73275955575d29479bb61eb402782 Mon Sep 17 00:00:00 2001 From: Nicolaus Weidner Date: Thu, 22 Dec 2022 13:49:20 +0100 Subject: [PATCH 43/55] [issue-359] Fix mock ordering in converter tests Signed-off-by: Nicolaus Weidner --- tests/jsonschema/test_document_converter.py | 8 ++++---- tests/jsonschema/test_file_converter.py | 2 +- tests/jsonschema/test_package_converter.py | 5 ++--- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/tests/jsonschema/test_document_converter.py b/tests/jsonschema/test_document_converter.py index 934c3c5c6..a39b1c64b 100644 --- a/tests/jsonschema/test_document_converter.py +++ b/tests/jsonschema/test_document_converter.py @@ -38,10 +38,10 @@ @mock.patch('src.jsonschema.file_converter.FileConverter', autospec=True) @mock.patch('src.jsonschema.snippet_converter.SnippetConverter', autospec=True) @mock.patch('src.jsonschema.relationship_converter.RelationshipConverter', autospec=True) -def converter(external_ref_converter_mock: MagicMock, creation_info_converter_mock: MagicMock, - package_converter_mock: MagicMock, annotation_converter_mock: MagicMock, - extracted_licensing_info_converter_mock: MagicMock, file_converter_mock: MagicMock, - snippet_converter_mock: MagicMock, relationship_converter_mock: MagicMock) -> DocumentConverter: +def converter(relationship_converter_mock: MagicMock, snippet_converter_mock: MagicMock, file_converter_mock: MagicMock, + extracted_licensing_info_converter_mock: MagicMock, annotation_converter_mock: MagicMock, + package_converter_mock: MagicMock, external_ref_converter_mock: MagicMock, + creation_info_converter_mock: MagicMock) -> DocumentConverter: converter = DocumentConverter() converter.creation_info_converter = creation_info_converter_mock() converter.external_document_ref_converter = external_ref_converter_mock() diff --git a/tests/jsonschema/test_file_converter.py b/tests/jsonschema/test_file_converter.py index 7dd802544..5d291b012 100644 --- a/tests/jsonschema/test_file_converter.py +++ b/tests/jsonschema/test_file_converter.py @@ -33,7 +33,7 @@ @pytest.fixture @mock.patch('src.jsonschema.checksum_converter.ChecksumConverter', autospec=True) @mock.patch('src.jsonschema.annotation_converter.AnnotationConverter', autospec=True) -def converter(checksum_converter_mock: MagicMock, annotation_converter_mock: MagicMock) -> FileConverter: +def converter(annotation_converter_mock: MagicMock, checksum_converter_mock: MagicMock) -> FileConverter: converter = FileConverter() converter.checksum_converter = checksum_converter_mock() converter.annotation_converter = annotation_converter_mock() diff --git a/tests/jsonschema/test_package_converter.py b/tests/jsonschema/test_package_converter.py index c6e6735be..446cac7e5 100644 --- a/tests/jsonschema/test_package_converter.py +++ b/tests/jsonschema/test_package_converter.py @@ -37,9 +37,8 @@ @mock.patch('src.jsonschema.annotation_converter.AnnotationConverter', autospec=True) @mock.patch('src.jsonschema.package_verification_code_converter.PackageVerificationCodeConverter', autospec=True) @mock.patch('src.jsonschema.external_package_ref_converter.ExternalPackageRefConverter', autospec=True) -def converter(checksum_converter_mock: MagicMock, annotation_converter_mock: MagicMock, - verification_code_converter_mock: MagicMock, - package_ref_converter_mock: MagicMock) -> PackageConverter: +def converter(package_ref_converter_mock: MagicMock, verification_code_converter_mock: MagicMock, + annotation_converter_mock: MagicMock, checksum_converter_mock: MagicMock) -> PackageConverter: converter = PackageConverter() converter.checksum_converter = checksum_converter_mock() converter.annotation_converter = annotation_converter_mock() From 7aa1b74f613cfa0f83fceebf2ae8db969c4dfbc3 Mon Sep 17 00:00:00 2001 From: Nicolaus Weidner Date: Thu, 22 Dec 2022 23:50:55 +0100 Subject: [PATCH 44/55] [issue-359] Make mock utilities compatible with Python 3.7 Signed-off-by: Nicolaus Weidner --- tests/mock_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/mock_utils.py b/tests/mock_utils.py index 06bbc836f..09af9f19e 100644 --- a/tests/mock_utils.py +++ b/tests/mock_utils.py @@ -16,7 +16,7 @@ def assert_mock_method_called_with_arguments(mock_object: NonCallableMagicMock, for running_index in range(len(args)): call = mock_object.method_calls[running_index] assert call[0] == method_name - assert call.args[0] == args[running_index] + assert call[1][0] == args[running_index] def assert_no_mock_methods_called(mock_object: NonCallableMagicMock): From 4aac32cada0e5a294d0fca0e28890fef6780566f Mon Sep 17 00:00:00 2001 From: Nicolaus Weidner Date: Fri, 30 Dec 2022 17:24:25 +0100 Subject: [PATCH 45/55] [issue-359] Remove license headers from empty init files Signed-off-by: Nicolaus Weidner --- src/writer/json/__init__.py | 11 ----------- tests/writer/__init__.py | 10 ---------- tests/writer/json/__init__.py | 10 ---------- tests/writer/json/expected_results/__init__.py | 10 ---------- 4 files changed, 41 deletions(-) diff --git a/src/writer/json/__init__.py b/src/writer/json/__init__.py index 7b5337962..e69de29bb 100644 --- a/src/writer/json/__init__.py +++ b/src/writer/json/__init__.py @@ -1,11 +0,0 @@ -# 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. - diff --git a/tests/writer/__init__.py b/tests/writer/__init__.py index cbc5c4070..e69de29bb 100644 --- a/tests/writer/__init__.py +++ b/tests/writer/__init__.py @@ -1,10 +0,0 @@ -# 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. diff --git a/tests/writer/json/__init__.py b/tests/writer/json/__init__.py index cbc5c4070..e69de29bb 100644 --- a/tests/writer/json/__init__.py +++ b/tests/writer/json/__init__.py @@ -1,10 +0,0 @@ -# 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. diff --git a/tests/writer/json/expected_results/__init__.py b/tests/writer/json/expected_results/__init__.py index cbc5c4070..e69de29bb 100644 --- a/tests/writer/json/expected_results/__init__.py +++ b/tests/writer/json/expected_results/__init__.py @@ -1,10 +0,0 @@ -# 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. From 030b1fccfec0d783ebabb3ed5a30811018ef5ac6 Mon Sep 17 00:00:00 2001 From: Nicolaus Weidner Date: Fri, 30 Dec 2022 17:52:06 +0100 Subject: [PATCH 46/55] [issue-359] Move relationship_filters to model package Signed-off-by: Nicolaus Weidner --- src/jsonschema/document_converter.py | 2 +- src/jsonschema/package_converter.py | 2 +- src/{jsonschema => model}/relationship_filters.py | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename src/{jsonschema => model}/relationship_filters.py (100%) diff --git a/src/jsonschema/document_converter.py b/src/jsonschema/document_converter.py index d04e4e8e2..983f36754 100644 --- a/src/jsonschema/document_converter.py +++ b/src/jsonschema/document_converter.py @@ -20,7 +20,7 @@ from src.jsonschema.json_property import JsonProperty from src.jsonschema.package_converter import PackageConverter from src.jsonschema.relationship_converter import RelationshipConverter -from src.jsonschema.relationship_filters import filter_by_type_and_origin, filter_by_type_and_target, \ +from src.model.relationship_filters import filter_by_type_and_origin, filter_by_type_and_target, \ find_package_contains_file_relationships, \ find_file_contained_by_package_relationships from src.jsonschema.snippet_converter import SnippetConverter diff --git a/src/jsonschema/package_converter.py b/src/jsonschema/package_converter.py index a15503e71..45beebd41 100644 --- a/src/jsonschema/package_converter.py +++ b/src/jsonschema/package_converter.py @@ -19,7 +19,7 @@ from src.jsonschema.optional_utils import apply_if_present from src.jsonschema.package_properties import PackageProperty from src.jsonschema.package_verification_code_converter import PackageVerificationCodeConverter -from src.jsonschema.relationship_filters import find_package_contains_file_relationships, \ +from src.model.relationship_filters import find_package_contains_file_relationships, \ find_file_contained_by_package_relationships from src.model.actor import Actor from src.model.document import Document diff --git a/src/jsonschema/relationship_filters.py b/src/model/relationship_filters.py similarity index 100% rename from src/jsonschema/relationship_filters.py rename to src/model/relationship_filters.py From b371f4c6b9948f0913cd0992aecb3f0fce7a4759 Mon Sep 17 00:00:00 2001 From: Nicolaus Weidner Date: Fri, 30 Dec 2022 18:02:14 +0100 Subject: [PATCH 47/55] [issue-359] Add NOASSERTION case to extracted license name Signed-off-by: Nicolaus Weidner --- src/jsonschema/extracted_licensing_info_converter.py | 3 ++- .../test_extracted_licensing_info_converter.py | 10 ++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/jsonschema/extracted_licensing_info_converter.py b/src/jsonschema/extracted_licensing_info_converter.py index 87f1312fa..5888247dd 100644 --- a/src/jsonschema/extracted_licensing_info_converter.py +++ b/src/jsonschema/extracted_licensing_info_converter.py @@ -13,6 +13,7 @@ from src.jsonschema.converter import TypedConverter from src.jsonschema.extracted_licensing_info_properties import ExtractedLicensingInfoProperty from src.jsonschema.json_property import JsonProperty +from src.jsonschema.optional_utils import apply_if_present from src.model.document import Document from src.model.extracted_licensing_info import ExtractedLicensingInfo from src.writer.casing_tools import snake_case_to_camel_case @@ -32,7 +33,7 @@ def _get_property_value(self, extracted_licensing_info: ExtractedLicensingInfo, elif extracted_licensing_info_property == ExtractedLicensingInfoProperty.LICENSE_ID: return extracted_licensing_info.license_id elif extracted_licensing_info_property == ExtractedLicensingInfoProperty.NAME: - return extracted_licensing_info.license_name + return apply_if_present(str, extracted_licensing_info.license_name) elif extracted_licensing_info_property == ExtractedLicensingInfoProperty.SEE_ALSOS: return extracted_licensing_info.cross_references or None diff --git a/tests/jsonschema/test_extracted_licensing_info_converter.py b/tests/jsonschema/test_extracted_licensing_info_converter.py index 39c146e84..c90d5c311 100644 --- a/tests/jsonschema/test_extracted_licensing_info_converter.py +++ b/tests/jsonschema/test_extracted_licensing_info_converter.py @@ -13,6 +13,8 @@ from src.jsonschema.extracted_licensing_info_converter import ExtractedLicensingInfoConverter from src.jsonschema.extracted_licensing_info_properties import ExtractedLicensingInfoProperty from src.model.extracted_licensing_info import ExtractedLicensingInfo +from src.model.spdx_no_assertion import SpdxNoAssertion, SPDX_NO_ASSERTION_STRING +from tests.fixtures import extracted_licensing_info_fixture @pytest.fixture @@ -65,3 +67,11 @@ def test_null_values(converter: ExtractedLicensingInfoConverter): assert converter.json_property_name(ExtractedLicensingInfoProperty.NAME) not in converted_dict assert converter.json_property_name(ExtractedLicensingInfoProperty.SEE_ALSOS) not in converted_dict assert converter.json_property_name(ExtractedLicensingInfoProperty.COMMENT) not in converted_dict + + +def test_spdx_no_assertion(converter: ExtractedLicensingInfoConverter): + extracted_licensing_info = extracted_licensing_info_fixture(license_name=SpdxNoAssertion()) + + converted_dict = converter.convert(extracted_licensing_info) + + assert converted_dict[converter.json_property_name(ExtractedLicensingInfoProperty.NAME)] == SPDX_NO_ASSERTION_STRING From f511d0263ab854c46754fdcb74b1587230209e43 Mon Sep 17 00:00:00 2001 From: Nicolaus Weidner Date: Fri, 30 Dec 2022 18:06:55 +0100 Subject: [PATCH 48/55] [issue-359] Add default implementation of json_property_name to converter Signed-off-by: Nicolaus Weidner --- src/jsonschema/annotation_converter.py | 4 ---- src/jsonschema/checksum_converter.py | 4 ---- src/jsonschema/converter.py | 7 ++++--- src/jsonschema/creation_info_converter.py | 4 ---- src/jsonschema/document_converter.py | 9 ++++----- src/jsonschema/external_document_ref_converter.py | 4 ---- src/jsonschema/external_package_ref_converter.py | 4 ---- src/jsonschema/extracted_licensing_info_converter.py | 4 ---- src/jsonschema/file_converter.py | 3 +-- src/jsonschema/package_converter.py | 7 +++---- src/jsonschema/package_verification_code_converter.py | 4 ---- src/jsonschema/relationship_converter.py | 4 ---- src/jsonschema/snippet_converter.py | 3 +-- 13 files changed, 13 insertions(+), 48 deletions(-) diff --git a/src/jsonschema/annotation_converter.py b/src/jsonschema/annotation_converter.py index 503da69e9..9b8905053 100644 --- a/src/jsonschema/annotation_converter.py +++ b/src/jsonschema/annotation_converter.py @@ -16,13 +16,9 @@ from src.jsonschema.json_property import JsonProperty from src.model.annotation import Annotation from src.model.document import Document -from src.writer.casing_tools import snake_case_to_camel_case class AnnotationConverter(TypedConverter[Annotation]): - def json_property_name(self, annotation_property: AnnotationProperty) -> str: - return snake_case_to_camel_case(annotation_property.name) - def _get_property_value(self, annotation: Annotation, annotation_property: AnnotationProperty, document: Document = None) -> Any: if annotation_property == AnnotationProperty.ANNOTATION_DATE: diff --git a/src/jsonschema/checksum_converter.py b/src/jsonschema/checksum_converter.py index d68710234..162a4f174 100644 --- a/src/jsonschema/checksum_converter.py +++ b/src/jsonschema/checksum_converter.py @@ -15,7 +15,6 @@ from src.jsonschema.json_property import JsonProperty from src.model.checksum import Checksum, ChecksumAlgorithm from src.model.document import Document -from src.writer.casing_tools import snake_case_to_camel_case class ChecksumConverter(TypedConverter[Checksum]): @@ -26,9 +25,6 @@ def get_data_model_type(self) -> Type[Checksum]: def get_json_type(self) -> Type[JsonProperty]: return ChecksumProperty - def json_property_name(self, checksum_property: ChecksumProperty) -> str: - return snake_case_to_camel_case(checksum_property.name) - def _get_property_value(self, checksum: Checksum, checksum_property: ChecksumProperty, _document: Document = None) -> str: if checksum_property == ChecksumProperty.ALGORITHM: diff --git a/src/jsonschema/converter.py b/src/jsonschema/converter.py index 1d6d348fa..76972af76 100644 --- a/src/jsonschema/converter.py +++ b/src/jsonschema/converter.py @@ -13,6 +13,7 @@ from src.jsonschema.json_property import JsonProperty from src.model.document import Document +from src.writer.casing_tools import snake_case_to_camel_case MISSING_IMPLEMENTATION_MESSAGE = "Must be implemented" @@ -20,9 +21,6 @@ class TypedConverter(ABC, Generic[T]): - @abstractmethod - def json_property_name(self, json_property: JsonProperty) -> str: - raise NotImplementedError(MISSING_IMPLEMENTATION_MESSAGE) @abstractmethod def _get_property_value(self, instance: T, json_property: JsonProperty, document: Document = None) -> Any: @@ -36,6 +34,9 @@ def get_json_type(self) -> Type[JsonProperty]: def get_data_model_type(self) -> Type[T]: raise NotImplementedError(MISSING_IMPLEMENTATION_MESSAGE) + def json_property_name(self, json_property: JsonProperty) -> str: + return snake_case_to_camel_case(json_property.name) + def requires_full_document(self) -> bool: return False diff --git a/src/jsonschema/creation_info_converter.py b/src/jsonschema/creation_info_converter.py index 656f56ee7..0111a8cab 100644 --- a/src/jsonschema/creation_info_converter.py +++ b/src/jsonschema/creation_info_converter.py @@ -16,7 +16,6 @@ from src.jsonschema.json_property import JsonProperty from src.jsonschema.optional_utils import apply_if_present from src.model.document import CreationInfo, Document -from src.writer.casing_tools import snake_case_to_camel_case class CreationInfoConverter(TypedConverter[CreationInfo]): @@ -26,9 +25,6 @@ def get_data_model_type(self) -> Type[CreationInfo]: def get_json_type(self) -> Type[JsonProperty]: return CreationInfoProperty - def json_property_name(self, creation_info_property: CreationInfoProperty) -> str: - return snake_case_to_camel_case(creation_info_property.name) - def _get_property_value(self, creation_info: CreationInfo, creation_info_property: CreationInfoProperty, _document: Document = None) -> Any: if creation_info_property == CreationInfoProperty.CREATED: diff --git a/src/jsonschema/document_converter.py b/src/jsonschema/document_converter.py index 983f36754..d9f6abae1 100644 --- a/src/jsonschema/document_converter.py +++ b/src/jsonschema/document_converter.py @@ -20,13 +20,12 @@ from src.jsonschema.json_property import JsonProperty from src.jsonschema.package_converter import PackageConverter from src.jsonschema.relationship_converter import RelationshipConverter -from src.model.relationship_filters import filter_by_type_and_origin, filter_by_type_and_target, \ - find_package_contains_file_relationships, \ - find_file_contained_by_package_relationships from src.jsonschema.snippet_converter import SnippetConverter from src.model.document import Document from src.model.relationship import RelationshipType -from src.writer.casing_tools import snake_case_to_camel_case +from src.model.relationship_filters import filter_by_type_and_origin, filter_by_type_and_target, \ + find_package_contains_file_relationships, \ + find_file_contained_by_package_relationships class DocumentConverter(TypedConverter[Document]): @@ -58,7 +57,7 @@ def get_data_model_type(self) -> Type[Document]: def json_property_name(self, document_property: DocumentProperty) -> str: if document_property == DocumentProperty.SPDX_ID: return "SPDXID" - return snake_case_to_camel_case(document_property.name) + return super().json_property_name(document_property) def _get_property_value(self, document: Document, document_property: DocumentProperty, _document: Document = None) -> Any: diff --git a/src/jsonschema/external_document_ref_converter.py b/src/jsonschema/external_document_ref_converter.py index 6ac16d56e..893a24014 100644 --- a/src/jsonschema/external_document_ref_converter.py +++ b/src/jsonschema/external_document_ref_converter.py @@ -16,7 +16,6 @@ from src.jsonschema.json_property import JsonProperty from src.model.document import Document from src.model.external_document_ref import ExternalDocumentRef -from src.writer.casing_tools import snake_case_to_camel_case class ExternalDocumentRefConverter(TypedConverter[ExternalDocumentRef]): @@ -25,9 +24,6 @@ class ExternalDocumentRefConverter(TypedConverter[ExternalDocumentRef]): def __init__(self): self.checksum_converter = ChecksumConverter() - def json_property_name(self, external_document_ref_property: ExternalDocumentRefProperty) -> str: - return snake_case_to_camel_case(external_document_ref_property.name) - def _get_property_value(self, external_document_ref: ExternalDocumentRef, external_document_ref_property: ExternalDocumentRefProperty, _document: Document = None) -> Any: diff --git a/src/jsonschema/external_package_ref_converter.py b/src/jsonschema/external_package_ref_converter.py index 55e1ccabf..3993b7c88 100644 --- a/src/jsonschema/external_package_ref_converter.py +++ b/src/jsonschema/external_package_ref_converter.py @@ -15,13 +15,9 @@ from src.jsonschema.json_property import JsonProperty from src.model.document import Document from src.model.package import ExternalPackageRef -from src.writer.casing_tools import snake_case_to_camel_case class ExternalPackageRefConverter(TypedConverter[ExternalPackageRef]): - def json_property_name(self, external_ref_property: ExternalPackageRefProperty) -> str: - return snake_case_to_camel_case(external_ref_property.name) - def _get_property_value(self, external_ref: ExternalPackageRef, external_ref_property: ExternalPackageRefProperty, document: Document = None) -> Any: if external_ref_property == ExternalPackageRefProperty.COMMENT: diff --git a/src/jsonschema/extracted_licensing_info_converter.py b/src/jsonschema/extracted_licensing_info_converter.py index 5888247dd..0aa4630d9 100644 --- a/src/jsonschema/extracted_licensing_info_converter.py +++ b/src/jsonschema/extracted_licensing_info_converter.py @@ -16,13 +16,9 @@ from src.jsonschema.optional_utils import apply_if_present from src.model.document import Document from src.model.extracted_licensing_info import ExtractedLicensingInfo -from src.writer.casing_tools import snake_case_to_camel_case class ExtractedLicensingInfoConverter(TypedConverter[ExtractedLicensingInfo]): - def json_property_name(self, extracted_licensing_info_property: ExtractedLicensingInfoProperty) -> str: - return snake_case_to_camel_case(extracted_licensing_info_property.name) - def _get_property_value(self, extracted_licensing_info: ExtractedLicensingInfo, extracted_licensing_info_property: ExtractedLicensingInfoProperty, document: Document = None) -> Any: diff --git a/src/jsonschema/file_converter.py b/src/jsonschema/file_converter.py index 1b66a72d8..fd76eefa9 100644 --- a/src/jsonschema/file_converter.py +++ b/src/jsonschema/file_converter.py @@ -18,7 +18,6 @@ from src.jsonschema.optional_utils import apply_if_present from src.model.document import Document from src.model.file import File -from src.writer.casing_tools import snake_case_to_camel_case class FileConverter(TypedConverter[File]): @@ -32,7 +31,7 @@ def __init__(self): def json_property_name(self, file_property: FileProperty) -> str: if file_property == FileProperty.SPDX_ID: return "SPDXID" - return snake_case_to_camel_case(file_property.name) + return super().json_property_name(file_property) def _get_property_value(self, file: Any, file_property: FileProperty, document: Document = None) -> Any: if file_property == FileProperty.SPDX_ID: diff --git a/src/jsonschema/package_converter.py b/src/jsonschema/package_converter.py index 45beebd41..fe8a26d71 100644 --- a/src/jsonschema/package_converter.py +++ b/src/jsonschema/package_converter.py @@ -19,12 +19,11 @@ from src.jsonschema.optional_utils import apply_if_present from src.jsonschema.package_properties import PackageProperty from src.jsonschema.package_verification_code_converter import PackageVerificationCodeConverter -from src.model.relationship_filters import find_package_contains_file_relationships, \ - find_file_contained_by_package_relationships from src.model.actor import Actor from src.model.document import Document from src.model.package import Package -from src.writer.casing_tools import snake_case_to_camel_case +from src.model.relationship_filters import find_package_contains_file_relationships, \ + find_file_contained_by_package_relationships class PackageConverter(TypedConverter[Package]): @@ -42,7 +41,7 @@ def __init__(self): def json_property_name(self, package_property: PackageProperty) -> str: if package_property == PackageProperty.SPDX_ID: return "SPDXID" - return snake_case_to_camel_case(package_property.name) + return super().json_property_name(package_property) def _get_property_value(self, package: Package, package_property: PackageProperty, document: Document = None) -> Any: diff --git a/src/jsonschema/package_verification_code_converter.py b/src/jsonschema/package_verification_code_converter.py index c312ef4b5..bb6cd7a6e 100644 --- a/src/jsonschema/package_verification_code_converter.py +++ b/src/jsonschema/package_verification_code_converter.py @@ -15,13 +15,9 @@ from src.jsonschema.package_verification_code_properties import PackageVerificationCodeProperty from src.model.document import Document from src.model.package import PackageVerificationCode -from src.writer.casing_tools import snake_case_to_camel_case class PackageVerificationCodeConverter(TypedConverter[PackageVerificationCode]): - def json_property_name(self, verification_code_property: PackageVerificationCodeProperty) -> str: - return snake_case_to_camel_case(verification_code_property.name) - def _get_property_value(self, verification_code: PackageVerificationCode, verification_code_property: PackageVerificationCodeProperty, document: Document = None) -> Any: diff --git a/src/jsonschema/relationship_converter.py b/src/jsonschema/relationship_converter.py index ea2806765..ea89083b2 100644 --- a/src/jsonschema/relationship_converter.py +++ b/src/jsonschema/relationship_converter.py @@ -15,13 +15,9 @@ from src.jsonschema.relationship_properties import RelationshipProperty from src.model.document import Document from src.model.relationship import Relationship -from src.writer.casing_tools import snake_case_to_camel_case class RelationshipConverter(TypedConverter[Relationship]): - def json_property_name(self, relationship_property: RelationshipProperty) -> str: - return snake_case_to_camel_case(relationship_property.name) - def _get_property_value(self, relationship: Relationship, relationship_property: RelationshipProperty, document: Document = None) -> Any: if relationship_property == RelationshipProperty.SPDX_ELEMENT_ID: diff --git a/src/jsonschema/snippet_converter.py b/src/jsonschema/snippet_converter.py index 6e73f0849..9abb7992e 100644 --- a/src/jsonschema/snippet_converter.py +++ b/src/jsonschema/snippet_converter.py @@ -17,7 +17,6 @@ from src.jsonschema.snippet_properties import SnippetProperty from src.model.document import Document from src.model.snippet import Snippet -from src.writer.casing_tools import snake_case_to_camel_case class SnippetConverter(TypedConverter[Snippet]): @@ -29,7 +28,7 @@ def __init__(self): def json_property_name(self, snippet_property: SnippetProperty) -> str: if snippet_property == SnippetProperty.SPDX_ID: return "SPDXID" - return snake_case_to_camel_case(snippet_property.name) + return super().json_property_name(snippet_property) def _get_property_value(self, snippet: Snippet, snippet_property: SnippetProperty, document: Document = None) -> Any: From 57b70afe557064d846d26464d577abdb2d04b585 Mon Sep 17 00:00:00 2001 From: Nicolaus Weidner Date: Fri, 30 Dec 2022 18:11:36 +0100 Subject: [PATCH 49/55] [issue-359] Allow NOASSERTION and NONE for related element id in converter Signed-off-by: Nicolaus Weidner --- src/jsonschema/relationship_converter.py | 2 +- .../jsonschema/test_relationship_converter.py | 20 +++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/jsonschema/relationship_converter.py b/src/jsonschema/relationship_converter.py index ea89083b2..e5b6a36a1 100644 --- a/src/jsonschema/relationship_converter.py +++ b/src/jsonschema/relationship_converter.py @@ -25,7 +25,7 @@ def _get_property_value(self, relationship: Relationship, relationship_property: elif relationship_property == RelationshipProperty.COMMENT: return relationship.comment elif relationship_property == RelationshipProperty.RELATED_SPDX_ELEMENT: - return relationship.related_spdx_element_id + return str(relationship.related_spdx_element_id) elif relationship_property == RelationshipProperty.RELATIONSHIP_TYPE: return relationship.relationship_type.name diff --git a/tests/jsonschema/test_relationship_converter.py b/tests/jsonschema/test_relationship_converter.py index a4b372935..8646e4dcc 100644 --- a/tests/jsonschema/test_relationship_converter.py +++ b/tests/jsonschema/test_relationship_converter.py @@ -13,6 +13,9 @@ from src.jsonschema.relationship_converter import RelationshipConverter from src.jsonschema.relationship_properties import RelationshipProperty from src.model.relationship import Relationship, RelationshipType +from src.model.spdx_no_assertion import SpdxNoAssertion, SPDX_NO_ASSERTION_STRING +from src.model.spdx_none import SpdxNone, SPDX_NONE_STRING +from tests.fixtures import relationship_fixture @pytest.fixture @@ -49,3 +52,20 @@ def test_successful_conversion(converter: RelationshipConverter): converter.json_property_name(RelationshipProperty.RELATED_SPDX_ELEMENT): "relatedElementId", converter.json_property_name(RelationshipProperty.RELATIONSHIP_TYPE): "COPY_OF" } + + +def test_spdx_no_assertion(converter: RelationshipConverter): + relationship = relationship_fixture(related_spdx_element_id=SpdxNoAssertion()) + + converted_dict = converter.convert(relationship) + + assert converted_dict[ + converter.json_property_name(RelationshipProperty.RELATED_SPDX_ELEMENT)] == SPDX_NO_ASSERTION_STRING + + +def test_spdx_none(converter: RelationshipConverter): + relationship = relationship_fixture(related_spdx_element_id=SpdxNone()) + + converted_dict = converter.convert(relationship) + + assert converted_dict[converter.json_property_name(RelationshipProperty.RELATED_SPDX_ELEMENT)] == SPDX_NONE_STRING From 4727af7e34aac6946ee20c161425cb55695be883 Mon Sep 17 00:00:00 2001 From: Nicolaus Weidner Date: Fri, 30 Dec 2022 18:29:16 +0100 Subject: [PATCH 50/55] [issue-359] Remove JsonWriter class, convert to plain function Signed-off-by: Nicolaus Weidner --- src/writer/json/json_writer.py | 16 ++++++---------- tests/writer/json/test_json_writer.py | 5 ++--- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/src/writer/json/json_writer.py b/src/writer/json/json_writer.py index f6c85f5a4..1d8eb3ad6 100644 --- a/src/writer/json/json_writer.py +++ b/src/writer/json/json_writer.py @@ -14,13 +14,9 @@ from src.model.document import Document -class JsonWriter: - converter: DocumentConverter - - def __init__(self): - self.converter = DocumentConverter() - - def write_document(self, document: Document, file_name: str) -> None: - document_dict = self.converter.convert(document) - with open(file_name, "w") as out: - json.dump(document_dict, out, indent=4) +def write_document(document: Document, file_name: str, converter: DocumentConverter = None): + if converter is None: + converter = DocumentConverter() + document_dict = converter.convert(document) + with open(file_name, "w") as out: + json.dump(document_dict, out, indent=4) diff --git a/tests/writer/json/test_json_writer.py b/tests/writer/json/test_json_writer.py index ea699dad4..dc3a460af 100644 --- a/tests/writer/json/test_json_writer.py +++ b/tests/writer/json/test_json_writer.py @@ -25,7 +25,7 @@ from src.model.relationship import RelationshipType, Relationship from src.model.snippet import Snippet from src.model.spdx_none import SpdxNone -from src.writer.json.json_writer import JsonWriter +from src.writer.json.json_writer import write_document @pytest.fixture @@ -36,7 +36,6 @@ def temporary_file_path() -> str: def test_write_json(temporary_file_path: str): - writer = JsonWriter() creation_info = CreationInfo("spdxVersion", "documentId", "documentName", "documentNamespace", [Actor(ActorType.TOOL, "tools-python", "tools-python@github.com")], datetime(2022, 12, 1), document_comment="comment", data_license="dataLicense", @@ -58,7 +57,7 @@ def test_write_json(temporary_file_path: str): extracted_licensing_info = [ExtractedLicensingInfo("licenseId", "licenseText")] document = Document(creation_info, annotations=annotations, extracted_licensing_info=extracted_licensing_info, relationships=relationships, packages=[package], files=[file], snippets=[snippet]) - writer.write_document(document, temporary_file_path) + write_document(document, temporary_file_path) with open(temporary_file_path) as written_file: written_json = json.load(written_file) From 6c2d577c097355b4575f101b533238611b962b1f Mon Sep 17 00:00:00 2001 From: Nicolaus Weidner Date: Fri, 30 Dec 2022 18:37:20 +0100 Subject: [PATCH 51/55] [issue-359] Add validation to json writer, with the possibility of disabling it Signed-off-by: Nicolaus Weidner --- src/writer/json/json_writer.py | 10 +++++++++- tests/writer/json/test_json_writer.py | 20 +++++++++++++++++++- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/src/writer/json/json_writer.py b/src/writer/json/json_writer.py index 1d8eb3ad6..cc0c9bf74 100644 --- a/src/writer/json/json_writer.py +++ b/src/writer/json/json_writer.py @@ -9,12 +9,20 @@ # See the License for the specific language governing permissions and # limitations under the License. import json +from typing import List from src.jsonschema.document_converter import DocumentConverter from src.model.document import Document +from src.validation.document_validator import validate_full_spdx_document +from src.validation.validation_message import ValidationMessage -def write_document(document: Document, file_name: str, converter: DocumentConverter = None): +def write_document(document: Document, file_name: str, validate: bool = True, converter: DocumentConverter = None): + if validate: + validation_messages: List[ValidationMessage] = validate_full_spdx_document(document, + document.creation_info.spdx_version) + if validation_messages: + raise ValueError(f"Document is not valid. The following errors were detected: {validation_messages}") if converter is None: converter = DocumentConverter() document_dict = converter.convert(document) diff --git a/tests/writer/json/test_json_writer.py b/tests/writer/json/test_json_writer.py index dc3a460af..6e48cf5da 100644 --- a/tests/writer/json/test_json_writer.py +++ b/tests/writer/json/test_json_writer.py @@ -26,6 +26,7 @@ from src.model.snippet import Snippet from src.model.spdx_none import SpdxNone from src.writer.json.json_writer import write_document +from tests.fixtures import document_fixture @pytest.fixture @@ -57,7 +58,8 @@ def test_write_json(temporary_file_path: str): extracted_licensing_info = [ExtractedLicensingInfo("licenseId", "licenseText")] document = Document(creation_info, annotations=annotations, extracted_licensing_info=extracted_licensing_info, relationships=relationships, packages=[package], files=[file], snippets=[snippet]) - write_document(document, temporary_file_path) + # TODO: Enable validation once test data is valid, https://github.com/spdx/tools-python/issues/397 + write_document(document, temporary_file_path, validate=False) with open(temporary_file_path) as written_file: written_json = json.load(written_file) @@ -66,3 +68,19 @@ def test_write_json(temporary_file_path: str): expected_json = json.load(expected_file) assert written_json == expected_json + + +def test_document_is_validated(): + document = document_fixture() + document.creation_info.spdx_id = "InvalidId" + + with pytest.raises(ValueError) as error: + write_document(document, "dummy_path") + assert "Document is not valid" in error.value.args[0] + + +def test_document_validation_can_be_overridden(temporary_file_path: str): + document = document_fixture() + document.creation_info.spdx_id = "InvalidId" + + write_document(document, temporary_file_path, validate=False) From 03fca1d21b49423f6cee7779463998cdb5c65dd2 Mon Sep 17 00:00:00 2001 From: Nicolaus Weidner Date: Mon, 2 Jan 2023 15:33:47 +0100 Subject: [PATCH 52/55] [issue-359] Add docstrings to some core files Signed-off-by: Nicolaus Weidner --- src/jsonschema/converter.py | 16 ++++++++++++++++ src/jsonschema/json_property.py | 1 + src/jsonschema/optional_utils.py | 3 +++ src/writer/json/json_writer.py | 5 +++++ 4 files changed, 25 insertions(+) diff --git a/src/jsonschema/converter.py b/src/jsonschema/converter.py index 76972af76..b46c93dcb 100644 --- a/src/jsonschema/converter.py +++ b/src/jsonschema/converter.py @@ -21,6 +21,22 @@ class TypedConverter(ABC, Generic[T]): + """ + Base class for all converters between an instance of the tools-python data model and the corresponding dictionary + representation, following the json schema. The generic type T is the type according to the data model. + Each converter has several methods: + - get_json_type and get_data_model_type: return the data model type and the corresponding JsonProperty subclass. + These methods are abstract in the base class and need to be implemented in subclasses. + - json_property_name: converts an enum value of a JsonProperty subclass to the corresponding property name in the + json schema. The default implementation simply converts to snake case. Can be overridden in case of exceptions + like "SPDXID". + - convert: converts an instance of type T (one of the data model types) to a dictionary representation. In some + cases, the full document is required (see below). The logic should be generic for all types. + - requires_full_document: indicates whether the full document is required for conversion. Returns False by + default, can be overridden as needed for specific types. + - _get_property_value: Retrieves the value of a specific json property from the data model instance. In some + cases, the full document is required. + """ @abstractmethod def _get_property_value(self, instance: T, json_property: JsonProperty, document: Document = None) -> Any: diff --git a/src/jsonschema/json_property.py b/src/jsonschema/json_property.py index fcdcd7167..0f65a77f2 100644 --- a/src/jsonschema/json_property.py +++ b/src/jsonschema/json_property.py @@ -15,5 +15,6 @@ class JsonProperty(Enum): """ Parent class for all json property classes. Not meant to be instantiated directly, only to have a common parent type that can be used in type hints. + In general, all the child enums list the properties of the corresponding objects from the json schema. """ pass diff --git a/src/jsonschema/optional_utils.py b/src/jsonschema/optional_utils.py index d30fd7607..d5039d3e3 100644 --- a/src/jsonschema/optional_utils.py +++ b/src/jsonschema/optional_utils.py @@ -15,4 +15,7 @@ def apply_if_present(function: Callable[[T], S], optional_value: Optional[T]) -> Optional[S]: + """ + Apply the passed function to the optional value if it is not None. Else returns None. + """ return function(optional_value) if optional_value else None diff --git a/src/writer/json/json_writer.py b/src/writer/json/json_writer.py index cc0c9bf74..5083c8caa 100644 --- a/src/writer/json/json_writer.py +++ b/src/writer/json/json_writer.py @@ -18,6 +18,11 @@ def write_document(document: Document, file_name: str, validate: bool = True, converter: DocumentConverter = None): + """ + Serializes the provided document to json and writes it to a file with the provided name. Unless validate is set + to False, validates the document before serialization. Unless a DocumentConverter instance is provided, + a new one is created. + """ if validate: validation_messages: List[ValidationMessage] = validate_full_spdx_document(document, document.creation_info.spdx_version) From 3f0f111fa1e60fa6a1ce72bc34aaf87c9b72bbb8 Mon Sep 17 00:00:00 2001 From: Nicolaus Weidner Date: Mon, 2 Jan 2023 23:41:08 +0100 Subject: [PATCH 53/55] [issue-359][squash] Fix docstring Signed-off-by: Nicolaus Weidner --- src/jsonschema/converter.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/jsonschema/converter.py b/src/jsonschema/converter.py index b46c93dcb..b5dc2a3c3 100644 --- a/src/jsonschema/converter.py +++ b/src/jsonschema/converter.py @@ -28,8 +28,8 @@ class TypedConverter(ABC, Generic[T]): - get_json_type and get_data_model_type: return the data model type and the corresponding JsonProperty subclass. These methods are abstract in the base class and need to be implemented in subclasses. - json_property_name: converts an enum value of a JsonProperty subclass to the corresponding property name in the - json schema. The default implementation simply converts to snake case. Can be overridden in case of exceptions - like "SPDXID". + json schema. The default implementation simply converts from snake case to camel case. Can be overridden in case + of exceptions like "SPDXID". - convert: converts an instance of type T (one of the data model types) to a dictionary representation. In some cases, the full document is required (see below). The logic should be generic for all types. - requires_full_document: indicates whether the full document is required for conversion. Returns False by From ebae51aa700f940f2387eab74e6c2fe6add07ee9 Mon Sep 17 00:00:00 2001 From: Nicolaus Weidner Date: Mon, 2 Jan 2023 15:46:06 +0100 Subject: [PATCH 54/55] [issue-359] Extract common method to get all spdx element ids inside a document Signed-off-by: Nicolaus Weidner --- src/document_utils.py | 20 ++++++++++++++++++++ src/jsonschema/document_converter.py | 5 ++--- src/validation/spdx_id_validators.py | 6 ++---- 3 files changed, 24 insertions(+), 7 deletions(-) create mode 100644 src/document_utils.py diff --git a/src/document_utils.py b/src/document_utils.py new file mode 100644 index 000000000..f0fe18552 --- /dev/null +++ b/src/document_utils.py @@ -0,0 +1,20 @@ +# 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. +from typing import List + +from src.model.document import Document + + +def get_contained_spdx_element_ids(document: Document) -> List[str]: + element_ids = [file.spdx_id for file in document.files] + element_ids.extend([package.spdx_id for package in document.packages]) + element_ids.extend([snippet.spdx_id for snippet in document.snippets]) + return element_ids diff --git a/src/jsonschema/document_converter.py b/src/jsonschema/document_converter.py index d9f6abae1..4d669962b 100644 --- a/src/jsonschema/document_converter.py +++ b/src/jsonschema/document_converter.py @@ -10,6 +10,7 @@ # limitations under the License. from typing import Type, Any +from src.document_utils import get_contained_spdx_element_ids from src.jsonschema.annotation_converter import AnnotationConverter from src.jsonschema.converter import TypedConverter from src.jsonschema.creation_info_converter import CreationInfoConverter @@ -65,9 +66,7 @@ def _get_property_value(self, document: Document, document_property: DocumentPro return document.creation_info.spdx_id elif document_property == DocumentProperty.ANNOTATIONS: # annotations referencing files, packages or snippets will be added to those elements directly - element_ids = [file.spdx_id for file in document.files] - element_ids.extend([package.spdx_id for package in document.packages]) - element_ids.extend([snippet.spdx_id for snippet in document.snippets]) + element_ids = get_contained_spdx_element_ids(document) document_annotations = filter(lambda annotation: annotation.spdx_id not in element_ids, document.annotations) return [self.annotation_converter.convert(annotation) for annotation in document_annotations] or None diff --git a/src/validation/spdx_id_validators.py b/src/validation/spdx_id_validators.py index 30e90be78..77bc91025 100644 --- a/src/validation/spdx_id_validators.py +++ b/src/validation/spdx_id_validators.py @@ -12,6 +12,7 @@ import re from typing import List +from src.document_utils import get_contained_spdx_element_ids from src.model.document import Document from src.model.file import File @@ -36,10 +37,7 @@ def is_spdx_id_present_in_document(spdx_id: str, document: Document) -> bool: def get_list_of_all_spdx_ids(document: Document) -> List[str]: all_spdx_ids_in_document: List[str] = [document.creation_info.spdx_id] - - all_spdx_ids_in_document.extend([package.spdx_id for package in document.packages]) - all_spdx_ids_in_document.extend([file.spdx_id for file in document.files]) - all_spdx_ids_in_document.extend([snippet.spdx_id for snippet in document.snippets]) + all_spdx_ids_in_document.extend(get_contained_spdx_element_ids(document)) return all_spdx_ids_in_document From a0a804b0054c1cf3f458ef6f8c4272cdd908af6d Mon Sep 17 00:00:00 2001 From: Nicolaus Weidner Date: Mon, 2 Jan 2023 23:54:43 +0100 Subject: [PATCH 55/55] [issue-359] Use extracted relationship filtering methods in document_validator.py Signed-off-by: Nicolaus Weidner --- src/validation/document_validator.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/validation/document_validator.py b/src/validation/document_validator.py index a0a151e21..bf2c11450 100644 --- a/src/validation/document_validator.py +++ b/src/validation/document_validator.py @@ -13,6 +13,7 @@ from src.model.document import Document from src.model.relationship import RelationshipType +from src.model.relationship_filters import filter_by_type_and_origin, filter_by_type_and_target from src.validation.annotation_validator import validate_annotations from src.validation.creation_info_validator import validate_creation_info from src.validation.extracted_licensing_info_validator import validate_extracted_licensing_infos @@ -35,15 +36,16 @@ def validate_full_spdx_document(document: Document, spdx_version: str) -> List[V validation_messages.extend(validate_extracted_licensing_infos(document.extracted_licensing_info)) document_id = document.creation_info.spdx_id - document_describes_relationships = [relationship for relationship in document.relationships if - relationship.relationship_type == RelationshipType.DESCRIBES and relationship.spdx_element_id == document_id] - described_by_document_relationships = [relationship for relationship in document.relationships if - relationship.relationship_type == RelationshipType.DESCRIBED_BY and relationship.related_spdx_element_id == document_id] + document_describes_relationships = filter_by_type_and_origin(document.relationships, RelationshipType.DESCRIBES, + document_id) + described_by_document_relationships = filter_by_type_and_target(document.relationships, + RelationshipType.DESCRIBED_BY, document_id) if not document_describes_relationships + described_by_document_relationships: validation_messages.append( ValidationMessage( - f'there must be at least one relationship "{document_id} DESCRIBES ..." or "... DESCRIBED_BY {document_id}"', + f'there must be at least one relationship "{document_id} DESCRIBES ..." or "... DESCRIBED_BY ' + f'{document_id}"', ValidationContext(spdx_id=document_id, element_type=SpdxElementType.DOCUMENT)))