From 0bca0bd31d7ff857cc0166c4a8aebb77866339cf Mon Sep 17 00:00:00 2001 From: Gaurav Mishra Date: Sun, 16 Aug 2026 22:18:51 +0530 Subject: [PATCH 1/3] feat(api): add API version check for features Signed-off-by: Gaurav Mishra --- sw360/base.py | 14 +++++++- sw360/components.py | 13 ++++++-- sw360/license.py | 7 +++- sw360/moderationrequests.py | 9 ++++-- sw360/packages.py | 23 ++++++++++--- sw360/project.py | 33 +++++++++++++++---- sw360/releases.py | 13 ++++++-- sw360/reports.py | 9 ++++++ sw360/sw360_api.py | 36 +++++++++++++++++---- sw360/sw360error.py | 12 +++++++ sw360/vendor.py | 9 ++++-- sw360/vulnerabilities.py | 2 +- tests/test_sw360_clearingrequests.py | 14 ++++++++ tests/test_sw360_components.py | 45 ++++++++++++++++++++++++++ tests/test_sw360_health.py | 22 ++++++++----- tests/test_sw360_licenses.py | 18 +++++++++++ tests/test_sw360_moderationrequests.py | 15 +++++++++ tests/test_sw360_packages.py | 27 ++++++++++++++++ tests/test_sw360_projects.py | 7 ++++ tests/test_sw360_releases.py | 38 ++++++++++++++++++++++ 20 files changed, 325 insertions(+), 41 deletions(-) diff --git a/sw360/base.py b/sw360/base.py index b2d965d..13fb4d5 100644 --- a/sw360/base.py +++ b/sw360/base.py @@ -59,6 +59,7 @@ def __init__(self, url: str, token: str, oauth2: bool = False, self.force_no_session = False self.default_batch_size = default_batch_size + self.api_version = (18, 0) def api_get(self, url: str = "") -> Optional[Dict[str, Any]]: """Request `url` from REST API and return json answer. @@ -294,7 +295,8 @@ def _update_external_ids(self, current_data: Dict[str, Any], ext_id_name: str, e return (old_value, ext_id_data, update) - def _add_params(self, url: str, params: Dict[str, str]) -> str: + @staticmethod + def _add_params(url: str, params: Dict[str, str]) -> str: """Add the given parameter to the given url""" query_string = urlencode(params) @@ -426,3 +428,13 @@ def _get_attachments( if '_links' in attachment: del attachment['_links'] return attachments + + def is_above_version_18(self) -> bool: + """Check if API version is above version 18. + Used to check version >= 19 features.""" + return self.api_version >= (19, 0) + + def is_above_version_19(self) -> bool: + """Check if API version is above version 19. + Used to check version >= 20 features.""" + return self.api_version >= (20, 0) diff --git a/sw360/components.py b/sw360/components.py index 794faa4..0f4ff06 100644 --- a/sw360/components.py +++ b/sw360/components.py @@ -42,11 +42,15 @@ def __get_components_filtered( :raises SW360Error: if there is a negative HTTP response """ - full_url = self._add_params(url, {"luceneSearch": "true"}) + if self.is_above_version_18(): + full_url = self._add_params(url, {"luceneSearch": "true"}) + else: + full_url = url + if page > -1 and page_size > -1: full_url = self._add_pagination(full_url, page, page_size, sort) - if page_size == -1: + if self.is_above_version_18() and page_size == -1: resp = self.api_get_all(full_url, sort) else: resp = self.api_get(full_url) @@ -197,7 +201,10 @@ def get_component_by_name( url_with_param = self._add_params(fullbase_url, params) if sort is None: - sort = ComponentSortColumn.SCORE.asc() + if self.is_above_version_18(): + sort = ComponentSortColumn.SCORE.asc() + else: + sort = ComponentSortColumn.NAME.asc() return self.__get_components_filtered(url_with_param, page, page_size, sort) diff --git a/sw360/license.py b/sw360/license.py index 12cac92..6ac2465 100644 --- a/sw360/license.py +++ b/sw360/license.py @@ -133,7 +133,12 @@ def get_all_licenses(self) -> List[Dict[str, Any]]: """ fullbase_url = self.url + "resource/api/licenses" - resp = self.api_get_all(fullbase_url) + sort = LicenseSortColumn.SHORT_NAME.asc() + + if self.is_above_version_18(): + resp = self.api_get_all(fullbase_url) + else: + resp = self.api_get(fullbase_url) if resp and "_embedded" in resp and "sw360:licenses" in resp["_embedded"]: return resp["_embedded"]["sw360:licenses"] diff --git a/sw360/moderationrequests.py b/sw360/moderationrequests.py index 79c2aa7..b2f902a 100644 --- a/sw360/moderationrequests.py +++ b/sw360/moderationrequests.py @@ -37,7 +37,10 @@ def get_all_moderation_requests( """ fullbase_url = self.url + "resource/api/moderationrequest" - params = {"luceneSearch": "true"} + if self.is_above_version_18(): + params = {"luceneSearch": "true"} + else: + params = {} if sort is None: sort = ModerationSortColumn.REQUEST_DATE.desc() @@ -46,7 +49,7 @@ def get_all_moderation_requests( if page > -1 and page_size > -1: full_url = self._add_pagination(full_url, page, page_size, sort) - if page_size == -1: + if self.is_above_version_18() and page_size == -1: resp = self.api_get_all(full_url, sort) else: resp = self.api_get(full_url) @@ -92,7 +95,7 @@ def get_moderation_requests_by_state( if page > -1 and page_size > -1: full_url = self._add_pagination(full_url, page, page_size, sort) - if page_size == -1: + if self.is_above_version_18() and page_size == -1: resp = self.api_get_all(full_url, sort) else: resp = self.api_get(full_url) diff --git a/sw360/packages.py b/sw360/packages.py index 02f7eee..11d112a 100644 --- a/sw360/packages.py +++ b/sw360/packages.py @@ -38,11 +38,15 @@ def __get_packages_filtered( :raises SW360Error: if there is a negative HTTP response """ - full_url = self._add_params(url, {"luceneSearch": "true"}) + if self.is_above_version_18(): + full_url = self._add_params(url, {"luceneSearch": "true"}) + else: + full_url = url + if page > -1 and page_size > -1: full_url = self._add_pagination(full_url, page, page_size, sort) - if page_size == -1: + if self.is_above_version_18() and page_size == -1: resp = self.api_get_all(full_url, sort) else: resp = self.api_get(full_url) @@ -100,7 +104,10 @@ def get_packages_by_name( url_with_param = self._add_params(fullbase_url, params) if sort is None: - sort = PackageSortColumn.SCORE.asc() + if self.is_above_version_18(): + sort = PackageSortColumn.SCORE.asc() + else: + sort = PackageSortColumn.NAME.asc() return self.__get_packages_filtered(url_with_param, page, page_size, sort) @@ -156,7 +163,10 @@ def get_all_packages( if sort is None: sort = PackageSortColumn.NAME.asc() if name != "" or version != "" or purl != "": - sort = PackageSortColumn.SCORE.asc() + if self.is_above_version_18(): + sort = PackageSortColumn.SCORE.asc() + else: + sort = PackageSortColumn.NAME.asc() return self.__get_packages_filtered(url_with_param, page, page_size, sort) @@ -190,7 +200,10 @@ def get_packages_by_packagemanager( url_with_param = self._add_params(fullbase_url, params) if sort is None: - sort = PackageSortColumn.SCORE.asc() + if self.is_above_version_18(): + sort = PackageSortColumn.SCORE.asc() + else: + sort = PackageSortColumn.NAME.asc() return self.__get_packages_filtered(url_with_param, page, page_size, sort) diff --git a/sw360/project.py b/sw360/project.py index 130c77c..fac0ac8 100644 --- a/sw360/project.py +++ b/sw360/project.py @@ -55,7 +55,10 @@ def get_project_releases(self, project_id: str, transitive: bool = False) -> Any trans = "true" url = self.url + "resource/api/projects/" + project_id + "/releases" url = self._add_params(url, {"transitive": trans}) - resp = self.api_get_all(url, ReleaseSortColumn.CREATED_ON.desc()) + if self.is_above_version_18(): + resp = self.api_get_all(url, ReleaseSortColumn.CREATED_ON.desc()) + else: + resp = self.api_get(url) return resp def get_project_by_url(self, url: str) -> Optional[Dict[str, Any]]: @@ -97,11 +100,15 @@ def __get_projects_filtered( :raises SW360Error: if there is a negative HTTP response """ - full_url = self._add_params(url, {"luceneSearch": "true"}) + if self.is_above_version_18(): + full_url = self._add_params(url, {"luceneSearch": "true"}) + else: + full_url = url + if page > -1 and page_size > -1: full_url = self._add_pagination(full_url, page, page_size, sort) - if page_size == -1: + if self.is_above_version_18() and page_size == -1: resp = self.api_get_all(full_url, sort) else: resp = self.api_get(full_url) @@ -237,7 +244,10 @@ def get_projects_by_name( url_with_param = self._add_params(fullbase_url, params) if sort is None: - sort = ProjectSortColumn.SCORE.asc() + if self.is_above_version_18(): + sort = ProjectSortColumn.SCORE.asc() + else: + sort = ProjectSortColumn.NAME.asc() return self.__get_projects_filtered(url_with_param, page, page_size, sort) @@ -311,7 +321,10 @@ def get_projects_by_group( url_with_param = self._add_params(fullbase_url, params) if sort is None: - sort = ProjectSortColumn.SCORE.asc() + if self.is_above_version_18(): + sort = ProjectSortColumn.SCORE.asc() + else: + sort = ProjectSortColumn.NAME.asc() return self.__get_projects_filtered(url_with_param, page, page_size, sort) @@ -345,7 +358,10 @@ def get_projects_by_tag( url_with_param = self._add_params(fullbase_url, params) if sort is None: - sort = ProjectSortColumn.SCORE.asc() + if self.is_above_version_18(): + sort = ProjectSortColumn.SCORE.asc() + else: + sort = ProjectSortColumn.NAME.asc() return self.__get_projects_filtered(url_with_param, page, page_size, sort) @@ -365,7 +381,10 @@ def get_project_vulnerabilities(self, project_id: str) -> Optional[Dict[str, Any raise SW360Error(message="No project id provided!") full_url = self.url + "resource/api/projects/" + project_id + "/vulnerabilities" - resp = self.api_get_all(full_url) + if self.is_above_version_18(): + resp = self.api_get_all(full_url) + else: + resp = self.api_get(full_url) if not resp: return None diff --git a/sw360/releases.py b/sw360/releases.py index 5bed6cd..eff1f43 100644 --- a/sw360/releases.py +++ b/sw360/releases.py @@ -40,11 +40,15 @@ def __get_releases_filtered( :raises SW360Error: if there is a negative HTTP response """ - full_url = self._add_params(url, {"luceneSearch": "true"}) + if self.is_above_version_18(): + full_url = self._add_params(url, {"luceneSearch": "true"}) + else: + full_url = url + if page > -1 and page_size > -1: full_url = self._add_pagination(full_url, page, page_size, sort) - if page_size == -1: + if self.is_above_version_18() and page_size == -1: resp = self.api_get_all(full_url, sort) else: resp = self.api_get(full_url) @@ -119,7 +123,10 @@ def get_releases_by_name( url_with_param = self._add_params(fullbase_url, params) if sort is None: - sort = ReleaseSortColumn.SCORE.asc() + if self.is_above_version_18(): + sort = ReleaseSortColumn.SCORE.asc() + else: + sort = ReleaseSortColumn.VERSION.desc() return self.__get_releases_filtered(url_with_param, page, page_size, sort) diff --git a/sw360/reports.py b/sw360/reports.py index 163ae7d..3e13f24 100644 --- a/sw360/reports.py +++ b/sw360/reports.py @@ -37,6 +37,9 @@ def generate_project_license_info( :rtype: int :raises SW360Error: if there is a negative HTTP response """ + if not self.is_above_version_18(): + raise SW360Error(message="SW360 version < 19 is not supported") + if not project_id: raise SW360Error(message="No project id provided!") @@ -97,6 +100,9 @@ def generate_project_clearing_report( :rtype: int :raises SW360Error: if there is a negative HTTP response """ + if not self.is_above_version_18(): + raise SW360Error(message="SW360 version < 19 is not supported") + if not project_id: raise SW360Error(message="No project id provided!") @@ -148,6 +154,9 @@ def generate_project_source_code_bundle( :type with_subprojects: bool :raises SW360Error: if there is a negative HTTP response """ + if not self.is_above_version_18(): + raise SW360Error(message="SW360 version < 19 is not supported") + if not project_id: raise SW360Error(message="No project id provided!") diff --git a/sw360/sw360_api.py b/sw360/sw360_api.py index 8283e54..a068d8b 100644 --- a/sw360/sw360_api.py +++ b/sw360/sw360_api.py @@ -9,7 +9,7 @@ """Python interface to the Siemens SW360 platform""" -from typing import Any, Dict, Optional +from typing import Any, Dict, Optional, Tuple import requests from requests.adapters import HTTPAdapter @@ -72,6 +72,7 @@ class SW360( :type oauth2: boolean :ivar default_batch_size: Default size of batch to use while fetching all items from API :type default_batch_size: int + :ivar api_version: SW360 API Version, defaults to 18.0 """ def __init__( @@ -97,6 +98,7 @@ def __init__( self.force_no_session = False self.default_batch_size = default_batch_size + self.api_version = self.parse_version("18.0") def login_api(self, token: str = "") -> bool: """Login to SW360 REST API. This used to have a `token` parameter @@ -120,6 +122,24 @@ def login_api(self, token: str = "") -> bool: except Exception as ex: raise SW360Error(None, url, message="Unable to login: " + repr(ex)) + version_url = self.url + "resource/api/version" + try: + if self.force_no_session: + versp = requests.get(version_url, + headers={"Accept": "application/json"}) + else: + if self.session: + versp = self.session.get( + version_url, + headers={"Accept": "application/json"}) + version_response = versp.json() + if "apiVersion" not in version_response: + raise SW360Error(versp, version_url, + "Unable to get API version") + self.api_version = self.parse_version(version_response["apiVersion"]) + except Exception: + self.api_version = self.parse_version("18.0") + if resp.ok: return True else: @@ -168,9 +188,13 @@ def get_health_status(self) -> Optional[Dict[str, Any]]: :rtype: JSON health status object :raises SW360Error: if there is a negative HTTP response """ - try: - # SW360 >= 19.0 has changed the endpoint path - return self.api_get(self.url + "resource/api/health/") - except SW360Error: - # try to fallback to old endpoint for SW360 <= 18.x + if self.is_above_version_18(): + return self.api_get(self.url + "resource/api/health") + else: return self.api_get(self.url + "resource/health/") + + @staticmethod + def parse_version(version_string: str) -> Tuple[int, int]: + """Extract major.minor as comparable tuple""" + parts = version_string.split('.') + return (int(parts[0]), int(parts[1])) diff --git a/sw360/sw360error.py b/sw360/sw360error.py index 350ecf7..1bed794 100644 --- a/sw360/sw360error.py +++ b/sw360/sw360error.py @@ -42,3 +42,15 @@ def __init__(self, response: Optional[Response] = None, url: str = "", message: super().__init__(message) else: super().__init__(str(response)) + + def __str__(self) -> str: + return_str = "Operation failed " + if self.message != "": + return_str = f"{self.message} " + if self.url != "": + return_str += f"for '{self.url}' " + if self.response is not None: + return_str += f"with status code {self.response.status_code}. " + if self.details is not None: + return_str += f"Response body: {self.details}" + return return_str.strip() diff --git a/sw360/vendor.py b/sw360/vendor.py index 8906cc8..b685a62 100644 --- a/sw360/vendor.py +++ b/sw360/vendor.py @@ -44,7 +44,7 @@ def get_all_vendors( if page > -1 and page_size > -1: full_url = self._add_pagination(full_url, page, page_size, sort) - if page_size == -1: + if self.is_above_version_18() and page_size == -1: resp = self.api_get_all(full_url, sort) else: resp = self.api_get(full_url) @@ -81,13 +81,16 @@ def search_vendors( params = {"searchText": search_text} if sort is None: - sort = VendorSortColumn.SCORE.asc() + if self.is_above_version_18(): + sort = VendorSortColumn.SCORE.asc() + else: + sort = VendorSortColumn.SHORT_NAME.asc() full_url = self._add_params(fullbase_url, params) if page > -1 and page_size > -1: full_url = self._add_pagination(full_url, page, page_size, sort) - if page_size == -1: + if self.is_above_version_18() and page_size == -1: resp = self.api_get_all(full_url, sort) else: resp = self.api_get(full_url) diff --git a/sw360/vulnerabilities.py b/sw360/vulnerabilities.py index 616ba98..b998fd9 100644 --- a/sw360/vulnerabilities.py +++ b/sw360/vulnerabilities.py @@ -57,7 +57,7 @@ def get_all_vulnerabilities( if page > -1 and page_size > -1: full_url = self._add_pagination(full_url, page, page_size, sort) - if page_size == -1: + if self.is_above_version_18() and page_size == -1: resp = self.api_get_all(full_url, sort) else: resp = self.api_get(full_url) diff --git a/tests/test_sw360_clearingrequests.py b/tests/test_sw360_clearingrequests.py index 8d5552f..aa4d4aa 100644 --- a/tests/test_sw360_clearingrequests.py +++ b/tests/test_sw360_clearingrequests.py @@ -41,10 +41,23 @@ def _add_login_response(self) -> None: adding_headers={"Authorization": "Token " + self.MYTOKEN}, ) + def _add_version_response(self) -> None: + """ + Add the response for a successful login. + """ + responses.add( + method=responses.GET, + url=self.MYURL + "resource/api/version", + body='{"apiVersion":"20.1.76","buildTime":"2026-08-14T13:17:47Z","buildNumber":"a8a0997","sw360Version":"20.1.0","gitBranch":"main"}', + status=200, + content_type="application/json", + ) + @responses.activate def test_get_clearing_request(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) self._add_login_response() + self._add_version_response() actual = lib.login_api() self.assertTrue(actual) @@ -69,6 +82,7 @@ def test_get_clearing_request(self) -> None: def test_get_clearing_request_for_project(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) self._add_login_response() + self._add_version_response() actual = lib.login_api() self.assertTrue(actual) diff --git a/tests/test_sw360_components.py b/tests/test_sw360_components.py index 029a1cf..4bac620 100644 --- a/tests/test_sw360_components.py +++ b/tests/test_sw360_components.py @@ -44,6 +44,18 @@ def _add_login_response(self) -> None: adding_headers={"Authorization": "Token " + self.MYTOKEN}, ) + def _add_version_response(self) -> None: + """ + Add the response for a successful login. + """ + responses.add( + method=responses.GET, + url=self.MYURL + "resource/api/version", + body='{"apiVersion":"20.1.76","buildTime":"2026-08-14T13:17:47Z","buildNumber":"a8a0997","sw360Version":"20.1.0","gitBranch":"main"}', + status=200, + content_type="application/json", + ) + def _my_matcher(self) -> Any: """ Helper method to display the JSON parameters of a REST call. @@ -59,6 +71,7 @@ def test_get_all_components(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) lib.force_no_session = True self._add_login_response() + self._add_version_response() actual = lib.login_api() self.assertTrue(actual) @@ -81,6 +94,7 @@ def test_get_all_components_no_result(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) lib.force_no_session = True self._add_login_response() + self._add_version_response() actual = lib.login_api() self.assertTrue(actual) @@ -101,6 +115,7 @@ def test_get_all_components_with_fields(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) lib.force_no_session = True self._add_login_response() + self._add_version_response() actual = lib.login_api() self.assertTrue(actual) @@ -124,6 +139,7 @@ def test_get_all_components_with_fields_and_paging(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) lib.force_no_session = True self._add_login_response() + self._add_version_response() actual = lib.login_api() self.assertTrue(actual) responses.add( @@ -146,6 +162,7 @@ def test_get_all_components_with_all_details(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) lib.force_no_session = True self._add_login_response() + self._add_version_response() actual = lib.login_api() self.assertTrue(actual) @@ -169,6 +186,7 @@ def test_get_all_components_with_all_details_and_sorting(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) lib.force_no_session = True self._add_login_response() + self._add_version_response() actual = lib.login_api() self.assertTrue(actual) @@ -192,6 +210,7 @@ def test_get_all_components_invalid_reply(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) lib.force_no_session = True self._add_login_response() + self._add_version_response() actual = lib.login_api() self.assertTrue(actual) @@ -213,6 +232,7 @@ def test_get_all_components_invalid_reply2(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) lib.force_no_session = True self._add_login_response() + self._add_version_response() actual = lib.login_api() self.assertTrue(actual) @@ -234,6 +254,7 @@ def test_get_all_components_by_type(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) lib.force_no_session = True self._add_login_response() + self._add_version_response() actual = lib.login_api() self.assertTrue(actual) @@ -257,6 +278,7 @@ def test_get_all_components_by_type_no_result(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) lib.force_no_session = True self._add_login_response() + self._add_version_response() actual = lib.login_api() self.assertTrue(actual) @@ -277,6 +299,7 @@ def test_get_all_components_by_type_invalid_reply(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) lib.force_no_session = True self._add_login_response() + self._add_version_response() actual = lib.login_api() self.assertTrue(actual) @@ -298,6 +321,7 @@ def test_get_all_components_by_type_invalid_reply2(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) lib.force_no_session = True self._add_login_response() + self._add_version_response() actual = lib.login_api() self.assertTrue(actual) @@ -319,6 +343,7 @@ def test_get_component(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) lib.force_no_session = True self._add_login_response() + self._add_version_response() actual = lib.login_api() self.assertTrue(actual) @@ -340,6 +365,7 @@ def test_get_component_by_url(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) lib.force_no_session = True self._add_login_response() + self._add_version_response() actual = lib.login_api() self.assertTrue(actual) @@ -361,6 +387,7 @@ def test_get_component_by_name(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) lib.force_no_session = True self._add_login_response() + self._add_version_response() actual = lib.login_api() self.assertTrue(actual) @@ -382,6 +409,7 @@ def test_get_components_by_external_id(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) lib.force_no_session = True self._add_login_response() + self._add_version_response() actual = lib.login_api() self.assertTrue(actual) @@ -404,6 +432,7 @@ def test_get_components_by_external_id_full_answer(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) lib.force_no_session = True self._add_login_response() + self._add_version_response() actual = lib.login_api() self.assertTrue(actual) @@ -426,6 +455,7 @@ def test_get_components_by_external_id_invalid_answer(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) lib.force_no_session = True self._add_login_response() + self._add_version_response() actual = lib.login_api() self.assertTrue(actual) @@ -447,6 +477,7 @@ def test_update_component_external_id_add_fresh_id(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) lib.force_no_session = True self._add_login_response() + self._add_version_response() actual = lib.login_api() self.assertTrue(actual) @@ -479,6 +510,7 @@ def test_update_component_external_id_no_overwrite(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) lib.force_no_session = True self._add_login_response() + self._add_version_response() actual = lib.login_api() self.assertTrue(actual) @@ -502,6 +534,7 @@ def test_update_component_external_id_overwrite(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) lib.force_no_session = True self._add_login_response() + self._add_version_response() actual = lib.login_api() self.assertTrue(actual) @@ -535,6 +568,7 @@ def test_update_component_external_id_delete(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) lib.force_no_session = True self._add_login_response() + self._add_version_response() actual = lib.login_api() self.assertTrue(actual) @@ -568,6 +602,7 @@ def test_update_component_external_id_no_exist(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) lib.force_no_session = True self._add_login_response() + self._add_version_response() actual = lib.login_api() self.assertTrue(actual) @@ -605,6 +640,7 @@ def test_update_component_external_id_no_extids_yet(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) lib.force_no_session = True self._add_login_response() + self._add_version_response() actual = lib.login_api() self.assertTrue(actual) @@ -637,6 +673,7 @@ def test_create_new_component(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) lib.force_no_session = True self._add_login_response() + self._add_version_response() actual = lib.login_api() self.assertTrue(actual) @@ -673,6 +710,7 @@ def test_create_new_component_fail(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) lib.force_no_session = True self._add_login_response() + self._add_version_response() actual = lib.login_api() self.assertTrue(actual) @@ -716,6 +754,7 @@ def test_update_component_no_id(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) lib.force_no_session = True self._add_login_response() + self._add_version_response() actual = lib.login_api() self.assertTrue(actual) @@ -732,6 +771,7 @@ def test_update_component_failed(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) lib.force_no_session = True self._add_login_response() + self._add_version_response() actual = lib.login_api() self.assertTrue(actual) @@ -761,6 +801,7 @@ def test_delete_component(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) lib.force_no_session = True self._add_login_response() + self._add_version_response() actual = lib.login_api() self.assertTrue(actual) @@ -778,6 +819,7 @@ def test_delete_component_no_id(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) lib.force_no_session = True self._add_login_response() + self._add_version_response() actual = lib.login_api() self.assertTrue(actual) @@ -791,6 +833,7 @@ def test_delete_component_failed(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) lib.force_no_session = True self._add_login_response() + self._add_version_response() actual = lib.login_api() self.assertTrue(actual) @@ -817,6 +860,7 @@ def test_get_users_of_component(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) lib.force_no_session = True self._add_login_response() + self._add_version_response() actual = lib.login_api() self.assertTrue(actual) @@ -847,6 +891,7 @@ def test_get_recent_components(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) lib.force_no_session = True self._add_login_response() + self._add_version_response() actual = lib.login_api() self.assertTrue(actual) diff --git a/tests/test_sw360_health.py b/tests/test_sw360_health.py index 6e6723d..d105858 100644 --- a/tests/test_sw360_health.py +++ b/tests/test_sw360_health.py @@ -41,16 +41,29 @@ def _add_login_response(self) -> None: adding_headers={"Authorization": "Token " + self.MYTOKEN}, ) + def _add_version_response(self) -> None: + """ + Add the response for a successful login. + """ + responses.add( + method=responses.GET, + url=self.MYURL + "resource/api/version", + body='{"apiVersion":"20.1.76","buildTime":"2026-08-14T13:17:47Z","buildNumber":"a8a0997","sw360Version":"20.1.0","gitBranch":"main"}', + status=200, + content_type="application/json", + ) + @responses.activate def test_get_health_status(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) self._add_login_response() + self._add_version_response() actual = lib.login_api() self.assertTrue(actual) responses.add( method=responses.GET, - url=self.MYURL + "resource/api/health/", + url=self.MYURL + "resource/api/health", body='{"status": "UP"}', status=200, content_type="application/json", @@ -69,13 +82,6 @@ def test_get_health_status_pre_v19_fallback(self) -> None: actual = lib.login_api() self.assertTrue(actual) - responses.add( - method=responses.GET, - url=self.MYURL + "resource/api/health/", - body='{"status":404,"error":"Not Found","path":"/resource/api/health/"}', - status=404, - ) - responses.add( method=responses.GET, url=self.MYURL + "resource/health/", diff --git a/tests/test_sw360_licenses.py b/tests/test_sw360_licenses.py index 992ce64..58b5183 100644 --- a/tests/test_sw360_licenses.py +++ b/tests/test_sw360_licenses.py @@ -41,10 +41,23 @@ def _add_login_response(self) -> None: adding_headers={"Authorization": "Token " + self.MYTOKEN}, ) + def _add_version_response(self) -> None: + """ + Add the response for a successful login. + """ + responses.add( + method=responses.GET, + url=self.MYURL + "resource/api/version", + body='{"apiVersion":"20.1.76","buildTime":"2026-08-14T13:17:47Z","buildNumber":"a8a0997","sw360Version":"20.1.0","gitBranch":"main"}', + status=200, + content_type="application/json", + ) + @responses.activate def test_get_all_licenses(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) self._add_login_response() + self._add_version_response() actual = lib.login_api() self.assertTrue(actual) @@ -80,6 +93,7 @@ def test_get_all_licenses(self) -> None: def test_get_all_licenses_none(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) self._add_login_response() + self._add_version_response() actual = lib.login_api() self.assertTrue(actual) @@ -99,6 +113,7 @@ def test_get_all_licenses_none(self) -> None: def test_get_license(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) self._add_login_response() + self._add_version_response() actual = lib.login_api() self.assertTrue(actual) @@ -128,6 +143,7 @@ def test_get_license(self) -> None: def test_create_new_license(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) self._add_login_response() + self._add_version_response() actual = lib.login_api() self.assertTrue(actual) @@ -163,6 +179,7 @@ def test_create_new_license(self) -> None: def test_create_new_license_fail(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) self._add_login_response() + self._add_version_response() actual = lib.login_api() self.assertTrue(actual) @@ -208,6 +225,7 @@ def test_create_new_license_fail(self) -> None: def test_delete_license(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) self._add_login_response() + self._add_version_response() actual = lib.login_api() self.assertTrue(actual) diff --git a/tests/test_sw360_moderationrequests.py b/tests/test_sw360_moderationrequests.py index 44b4cbd..b315589 100644 --- a/tests/test_sw360_moderationrequests.py +++ b/tests/test_sw360_moderationrequests.py @@ -42,10 +42,23 @@ def _add_login_response(self) -> None: adding_headers={"Authorization": "Token " + self.MYTOKEN}, ) + def _add_version_response(self) -> None: + """ + Add the response for a successful login. + """ + responses.add( + method=responses.GET, + url=self.MYURL + "resource/api/version", + body='{"apiVersion":"20.1.76","buildTime":"2026-08-14T13:17:47Z","buildNumber":"a8a0997","sw360Version":"20.1.0","gitBranch":"main"}', + status=200, + content_type="application/json", + ) + @responses.activate def test_get_all_moderation_requests(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) self._add_login_response() + self._add_version_response() actual = lib.login_api() self.assertTrue(actual) @@ -116,6 +129,7 @@ def test_get_all_moderation_requests(self) -> None: def test_get_moderation_requests_by_state(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) self._add_login_response() + self._add_version_response() actual = lib.login_api() self.assertTrue(actual) @@ -186,6 +200,7 @@ def test_get_moderation_requests_by_state(self) -> None: def test_get_license(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) self._add_login_response() + self._add_version_response() actual = lib.login_api() self.assertTrue(actual) diff --git a/tests/test_sw360_packages.py b/tests/test_sw360_packages.py index f692a72..c7eb130 100644 --- a/tests/test_sw360_packages.py +++ b/tests/test_sw360_packages.py @@ -43,6 +43,18 @@ def _add_login_response(self) -> None: adding_headers={"Authorization": "Token " + self.MYTOKEN}, ) + def _add_version_response(self) -> None: + """ + Add the response for a successful login. + """ + responses.add( + method=responses.GET, + url=self.MYURL + "resource/api/version", + body='{"apiVersion":"20.1.76","buildTime":"2026-08-14T13:17:47Z","buildNumber":"a8a0997","sw360Version":"20.1.0","gitBranch":"main"}', + status=200, + content_type="application/json", + ) + def _my_matcher(self) -> Any: """ Helper method to display the JSON parameters of a REST call. @@ -57,6 +69,7 @@ def display_json_params(request_body: Any) -> bool: def test_get_get_packages(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) self._add_login_response() + self._add_version_response() actual = lib.login_api() self.assertTrue(actual) @@ -79,6 +92,7 @@ def test_get_get_packages(self) -> None: def test_get_all_packages(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) self._add_login_response() + self._add_version_response() actual = lib.login_api() self.assertTrue(actual) @@ -102,6 +116,7 @@ def test_get_all_packages(self) -> None: def test_get_all_packages_with_fields_and_all_details(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) self._add_login_response() + self._add_version_response() actual = lib.login_api() self.assertTrue(actual) @@ -124,6 +139,7 @@ def test_get_all_packages_with_fields_and_all_details(self) -> None: def test_get_all_packages_with_fields_and_all_details2(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) self._add_login_response() + self._add_version_response() actual = lib.login_api() self.assertTrue(actual) @@ -146,6 +162,7 @@ def test_get_all_packages_with_fields_and_all_details2(self) -> None: def test_get_packages_by_name(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) self._add_login_response() + self._add_version_response() actual = lib.login_api() self.assertTrue(actual) @@ -168,6 +185,7 @@ def test_get_packages_by_name(self) -> None: def test_get_packages_by_packagemanager(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) self._add_login_response() + self._add_version_response() actual = lib.login_api() self.assertTrue(actual) @@ -190,6 +208,7 @@ def test_get_packages_by_packagemanager(self) -> None: def test_get_packages_by_packagemanager_with_details(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) self._add_login_response() + self._add_version_response() actual = lib.login_api() self.assertTrue(actual) @@ -213,6 +232,7 @@ def test_create_new_package(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) lib.force_no_session = True self._add_login_response() + self._add_version_response() actual = lib.login_api() self.assertTrue(actual) @@ -244,6 +264,7 @@ def test_create_new_package_already_exists(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) lib.force_no_session = True self._add_login_response() + self._add_version_response() actual = lib.login_api() self.assertTrue(actual) @@ -284,6 +305,7 @@ def test_update_package(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) lib.force_no_session = True self._add_login_response() + self._add_version_response() actual = lib.login_api() self.assertTrue(actual) @@ -305,6 +327,7 @@ def test_update_package_no_id(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) lib.force_no_session = True self._add_login_response() + self._add_version_response() actual = lib.login_api() self.assertTrue(actual) @@ -328,6 +351,7 @@ def test_update_package_failed(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) lib.force_no_session = True self._add_login_response() + self._add_version_response() actual = lib.login_api() self.assertTrue(actual) @@ -357,6 +381,7 @@ def test_delete_package(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) lib.force_no_session = True self._add_login_response() + self._add_version_response() actual = lib.login_api() self.assertTrue(actual) @@ -373,6 +398,7 @@ def test_delete_package(self) -> None: def test_delete_package_no_id(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) self._add_login_response() + self._add_version_response() actual = lib.login_api() self.assertTrue(actual) @@ -385,6 +411,7 @@ def test_delete_package_no_id(self) -> None: def test_delete_package_failed(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) self._add_login_response() + self._add_version_response() actual = lib.login_api() self.assertTrue(actual) diff --git a/tests/test_sw360_projects.py b/tests/test_sw360_projects.py index 7d4e3ca..4691514 100644 --- a/tests/test_sw360_projects.py +++ b/tests/test_sw360_projects.py @@ -42,6 +42,13 @@ def get_logged_in_lib(self) -> SW360: content_type="application/json", adding_headers={"Authorization": "Token " + self.MYTOKEN}, ) + responses.add( + method=responses.GET, + url=self.MYURL + "resource/api/version", + body='{"apiVersion":"20.1.76","buildTime":"2026-08-14T13:17:47Z","buildNumber":"a8a0997","sw360Version":"20.1.0","gitBranch":"main"}', + status=200, + content_type="application/json", + ) actual = lib.login_api() self.assertTrue(actual) diff --git a/tests/test_sw360_releases.py b/tests/test_sw360_releases.py index d6974de..f8839fc 100644 --- a/tests/test_sw360_releases.py +++ b/tests/test_sw360_releases.py @@ -43,6 +43,18 @@ def _add_login_response(self) -> None: adding_headers={"Authorization": "Token " + self.MYTOKEN}, ) + def _add_version_response(self) -> None: + """ + Add the response for a successful login. + """ + responses.add( + method=responses.GET, + url=self.MYURL + "resource/api/version", + body='{"apiVersion":"20.1.76","buildTime":"2026-08-14T13:17:47Z","buildNumber":"a8a0997","sw360Version":"20.1.0","gitBranch":"main"}', + status=200, + content_type="application/json", + ) + def _my_matcher(self) -> Any: """ Helper method to display the JSON parameters of a REST call. @@ -57,6 +69,7 @@ def display_json_params(request_body: Any) -> bool: def test_get_get_release(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) self._add_login_response() + self._add_version_response() actual = lib.login_api() self.assertTrue(actual) @@ -79,6 +92,7 @@ def test_get_get_release(self) -> None: def test_get_get_release_internal_server_error(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) self._add_login_response() + self._add_version_response() actual = lib.login_api() self.assertTrue(actual) @@ -109,6 +123,7 @@ def test_get_get_release_internal_server_error(self) -> None: def test_get_release_by_url(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) self._add_login_response() + self._add_version_response() actual = lib.login_api() self.assertTrue(actual) @@ -131,6 +146,7 @@ def test_get_release_by_url(self) -> None: def test_get_all_releases(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) self._add_login_response() + self._add_version_response() actual = lib.login_api() self.assertTrue(actual) @@ -157,6 +173,7 @@ def test_get_all_releases_isnewclearing_with_source_available(self) -> None: """ lib = SW360(self.MYURL, self.MYTOKEN, False) self._add_login_response() + self._add_version_response() actual = lib.login_api() self.assertTrue(actual) @@ -181,6 +198,7 @@ def test_get_all_releases_isnewclearing_with_source_available(self) -> None: def test_get_all_releases_all_details(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) self._add_login_response() + self._add_version_response() actual = lib.login_api() self.assertTrue(actual) @@ -203,6 +221,7 @@ def test_get_all_releases_all_details(self) -> None: def test_get_all_releases_with_fields(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) self._add_login_response() + self._add_version_response() actual = lib.login_api() self.assertTrue(actual) @@ -225,6 +244,7 @@ def test_get_all_releases_with_fields(self) -> None: def test_get_all_releases_with_fields_and_all_details(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) self._add_login_response() + self._add_version_response() actual = lib.login_api() self.assertTrue(actual) @@ -247,6 +267,7 @@ def test_get_all_releases_with_fields_and_all_details(self) -> None: def test_get_all_releases_with_paging_and_sorting(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) self._add_login_response() + self._add_version_response() actual = lib.login_api() self.assertTrue(actual) @@ -269,6 +290,7 @@ def test_get_all_releases_with_paging_and_sorting(self) -> None: def test_get_releases_by_external_id(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) self._add_login_response() + self._add_version_response() actual = lib.login_api() self.assertTrue(actual) @@ -291,6 +313,7 @@ def test_get_releases_by_external_id(self) -> None: def test_get_releases_by_external_id_invalid_reply(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) self._add_login_response() + self._add_version_response() actual = lib.login_api() self.assertTrue(actual) @@ -311,6 +334,7 @@ def test_get_releases_by_external_id_invalid_reply(self) -> None: def test_get_releases_by_name(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) self._add_login_response() + self._add_version_response() actual = lib.login_api() self.assertTrue(actual) @@ -333,6 +357,7 @@ def test_get_releases_by_name(self) -> None: def test_get_releases_by_name_invalid_answer(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) self._add_login_response() + self._add_version_response() actual = lib.login_api() self.assertTrue(actual) @@ -354,6 +379,7 @@ def test_create_new_release(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) lib.force_no_session = True self._add_login_response() + self._add_version_response() actual = lib.login_api() self.assertTrue(actual) @@ -387,6 +413,7 @@ def test_create_new_release_already_exists(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) lib.force_no_session = True self._add_login_response() + self._add_version_response() actual = lib.login_api() self.assertTrue(actual) @@ -429,6 +456,7 @@ def test_update_release(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) lib.force_no_session = True self._add_login_response() + self._add_version_response() actual = lib.login_api() self.assertTrue(actual) @@ -450,6 +478,7 @@ def test_update_release_no_id(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) lib.force_no_session = True self._add_login_response() + self._add_version_response() actual = lib.login_api() self.assertTrue(actual) @@ -473,6 +502,7 @@ def test_update_release_failed(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) lib.force_no_session = True self._add_login_response() + self._add_version_response() actual = lib.login_api() self.assertTrue(actual) @@ -501,6 +531,7 @@ def test_update_release_failed(self) -> None: def test_update_release_external_id_add_fresh_id(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) self._add_login_response() + self._add_version_response() actual = lib.login_api() self.assertTrue(actual) @@ -533,6 +564,7 @@ def test_delete_release(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) lib.force_no_session = True self._add_login_response() + self._add_version_response() actual = lib.login_api() self.assertTrue(actual) @@ -549,6 +581,7 @@ def test_delete_release(self) -> None: def test_delete_release_no_id(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) self._add_login_response() + self._add_version_response() actual = lib.login_api() self.assertTrue(actual) @@ -561,6 +594,7 @@ def test_delete_release_no_id(self) -> None: def test_delete_release_failed(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) self._add_login_response() + self._add_version_response() actual = lib.login_api() self.assertTrue(actual) @@ -586,6 +620,7 @@ def test_delete_release_failed(self) -> None: def test_get_users_of_release(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) self._add_login_response() + self._add_version_response() actual = lib.login_api() self.assertTrue(actual) @@ -605,6 +640,7 @@ def test_link_packages_to_release(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) lib.force_no_session = True self._add_login_response() + self._add_version_response() actual = lib.login_api() self.assertTrue(actual) @@ -631,6 +667,7 @@ def test_unlink_packages_from_release(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) lib.force_no_session = True self._add_login_response() + self._add_version_response() actual = lib.login_api() self.assertTrue(actual) @@ -656,6 +693,7 @@ def test_unlink_packages_from_release_no_id(self) -> None: def test_get_recent_releases(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) self._add_login_response() + self._add_version_response() actual = lib.login_api() self.assertTrue(actual) From 6d2a09a404c932d6199bd1e5096a3d5a6dbe259e Mon Sep 17 00:00:00 2001 From: Gaurav Mishra Date: Mon, 17 Aug 2026 01:26:57 +0530 Subject: [PATCH 2/3] feat(test): add test cases for v18 Signed-off-by: Gaurav Mishra --- tests/test_sw360_components.py | 23 +++++++++++++++++++++++ tests/test_sw360_projects.py | 19 +++++++++++++++++++ tests/test_sw360_releases.py | 22 ++++++++++++++++++++++ 3 files changed, 64 insertions(+) diff --git a/tests/test_sw360_components.py b/tests/test_sw360_components.py index 4bac620..ccea730 100644 --- a/tests/test_sw360_components.py +++ b/tests/test_sw360_components.py @@ -181,6 +181,29 @@ def test_get_all_components_with_all_details(self) -> None: self.assertEqual("Tethys.Logging", components[0]["name"]) self.assertEqual("DE", components[0]["ownerCountry"]) + @responses.activate + def test_get_all_components_with_all_details_v18_style(self) -> None: + lib = SW360(self.MYURL, self.MYTOKEN, False) + lib.force_no_session = True + self._add_login_response() + actual = lib.login_api() + self.assertTrue(actual) + + responses.add( + method=responses.GET, + url=self.MYURL + "resource/api/components?allDetails=true", # noqa + body='{"_embedded": {"sw360:components": [{"name": "Tethys.Logging", "ownerCountry": "DE", "componentType": "OSS", "externalIds": {"package-url": "pkg:nuget/Tethys.Logging"}}]}}', # noqa + status=200, + content_type="application/json", + adding_headers={"Authorization": "Token " + self.MYTOKEN}, + ) + + components = lib.get_all_components(all_details=True) + self.assertIsNotNone(components) + self.assertTrue(len(components) > 0) + self.assertEqual("Tethys.Logging", components[0]["name"]) + self.assertEqual("DE", components[0]["ownerCountry"]) + @responses.activate def test_get_all_components_with_all_details_and_sorting(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) diff --git a/tests/test_sw360_projects.py b/tests/test_sw360_projects.py index 4691514..b6802f6 100644 --- a/tests/test_sw360_projects.py +++ b/tests/test_sw360_projects.py @@ -205,6 +205,25 @@ def test_get_projects(self) -> None: if projects: # only for mypy self.assertEqual("My Testproject", projects[0]["name"]) + @responses.activate + def test_get_projects_v18_style(self) -> None: + lib = self.get_logged_in_lib() + lib.api_version = (18, 0) + + responses.add( + responses.GET, + url=self.MYURL + "resource/api/projects", + body='{"_embedded": {"sw360:projects": [{"name": "My Testproject"}]}}', + status=200, + content_type="application/json", + adding_headers={"Authorization": "Token " + self.MYTOKEN}, + ) + + projects = lib.get_projects() + self.assertIsNotNone(projects) + if projects: # only for mypy + self.assertEqual("My Testproject", projects[0]["name"]) + @responses.activate def test_get_projects_with_details(self) -> None: lib = self.get_logged_in_lib() diff --git a/tests/test_sw360_releases.py b/tests/test_sw360_releases.py index f8839fc..55bb0b1 100644 --- a/tests/test_sw360_releases.py +++ b/tests/test_sw360_releases.py @@ -217,6 +217,28 @@ def test_get_all_releases_all_details(self) -> None: self.assertEqual("Tethys.Logging", releases[0]["name"]) self.assertEqual("1.3.0", releases[0]["version"]) + @responses.activate + def test_get_all_releases_all_details_v18_style(self) -> None: + lib = SW360(self.MYURL, self.MYTOKEN, False) + self._add_login_response() + actual = lib.login_api() + self.assertTrue(actual) + + responses.add( + method=responses.GET, + url=self.MYURL + "resource/api/releases?allDetails=true", + body='{"_embedded": {"sw360:releases": [{"name": "Tethys.Logging", "version": "1.3.0", "releaseDate": "2018-03-04"}]}}', # noqa + status=200, + content_type="application/json", + adding_headers={"Authorization": "Token " + self.MYTOKEN}, + ) + + releases = lib.get_all_releases("", True) + self.assertIsNotNone(releases) + self.assertTrue(len(releases) > 0) + self.assertEqual("Tethys.Logging", releases[0]["name"]) + self.assertEqual("1.3.0", releases[0]["version"]) + @responses.activate def test_get_all_releases_with_fields(self) -> None: lib = SW360(self.MYURL, self.MYTOKEN, False) From 953ee00dbb65973a275e046f695a87f9064d23c8 Mon Sep 17 00:00:00 2001 From: Thomas Graf Date: Tue, 18 Aug 2026 18:57:33 +0200 Subject: [PATCH 3/3] style: fix issues --- sw360/__init__.py | 2 +- sw360/base.py | 1 + sw360/license.py | 4 +--- sw360/releases.py | 8 ++++---- sw360/reports.py | 2 -- sw360/sorting.py | 2 ++ sw360/sw360_api.py | 2 +- tests/test_sw360_clearingrequests.py | 3 ++- tests/test_sw360_components.py | 3 ++- tests/test_sw360_health.py | 3 ++- tests/test_sw360_licenses.py | 3 ++- tests/test_sw360_moderationrequests.py | 3 ++- tests/test_sw360_packages.py | 3 ++- tests/test_sw360_projects.py | 3 ++- tests/test_sw360_releases.py | 20 ++++++++++++++------ 15 files changed, 38 insertions(+), 24 deletions(-) diff --git a/sw360/__init__.py b/sw360/__init__.py index a1399e2..8c4cdc6 100644 --- a/sw360/__init__.py +++ b/sw360/__init__.py @@ -9,9 +9,9 @@ __version__ = (1, 11, 2) +from .base import SW360Response from .sorting import (BaseSortMixin, ProjectSortColumn, ReleaseSortColumn, SortParam) -from .base import SW360Response from .sw360_api import SW360 from .sw360error import SW360Error from .sw360keycloak import SW360Keycloak diff --git a/sw360/base.py b/sw360/base.py index 814703d..abcec49 100644 --- a/sw360/base.py +++ b/sw360/base.py @@ -517,6 +517,7 @@ def is_above_version_19(self) -> bool: """Check if API version is above version 19. Used to check version >= 20 features.""" return self.api_version >= (20, 0) + @classmethod def get_linked_id(cls, data: Dict[str, Any], link_key: str = "self") -> Optional[str]: """Extract the resource ID from a HAL ``_links`` entry. diff --git a/sw360/license.py b/sw360/license.py index 6ac2465..8a9f914 100644 --- a/sw360/license.py +++ b/sw360/license.py @@ -3,7 +3,7 @@ # Copyright (c) 2022 BMW CarIT GmbH # All Rights Reserved. # Authors: thomas.graf@siemens.com, gernot.hillier@siemens.com -# Authors: helio.chissini-de-castro@bmw.de +# Authors: helio.chissini-de-castro@bmw.de, mishra.gaurav@siemens.com # # Licensed under the MIT license. # SPDX-License-Identifier: MIT @@ -133,8 +133,6 @@ def get_all_licenses(self) -> List[Dict[str, Any]]: """ fullbase_url = self.url + "resource/api/licenses" - sort = LicenseSortColumn.SHORT_NAME.asc() - if self.is_above_version_18(): resp = self.api_get_all(fullbase_url) else: diff --git a/sw360/releases.py b/sw360/releases.py index eff1f43..e5aec8c 100644 --- a/sw360/releases.py +++ b/sw360/releases.py @@ -3,7 +3,7 @@ # Copyright (c) 2022 BMW CarIT GmbH # All Rights Reserved. # Authors: thomas.graf@siemens.com, gernot.hillier@siemens.com -# Authors: helio.chissini-de-castro@bmw.de +# Authors: helio.chissini-de-castro@bmw.de, mishra.gaurav@siemens.com # # Licensed under the MIT license. # SPDX-License-Identifier: MIT @@ -55,7 +55,7 @@ def __get_releases_filtered( if (resp and "_embedded" in resp and - "sw360:releases" in resp["_embedded"]): + "sw360:releases" in resp["_embedded"]): return resp["_embedded"]["sw360:releases"] return [] @@ -427,8 +427,8 @@ def upload_attachment_to_release( raise SW360Error(message="No release id provided!") attachment_content = self._upload_resource_file(upload_file, upload_type, upload_comment) - attachment_content['attachmentType'] = upload_type # Make sure the type is correct - attachment_content['createdComment'] = upload_comment # Override + attachment_content['attachmentType'] = upload_type # Make sure the type is correct + attachment_content['createdComment'] = upload_comment # Override current_release = self.get_release(release_id) attachments = self._get_attachments(current_release) diff --git a/sw360/reports.py b/sw360/reports.py index 3e13f24..d3848e5 100644 --- a/sw360/reports.py +++ b/sw360/reports.py @@ -7,8 +7,6 @@ # SPDX-License-Identifier: MIT # ------------------------------------------------------------------------------- -from typing import Any, Dict, List, Optional - import requests from .base import BaseMixin diff --git a/sw360/sorting.py b/sw360/sorting.py index c9a36a0..02a63dd 100644 --- a/sw360/sorting.py +++ b/sw360/sorting.py @@ -79,11 +79,13 @@ class PackageSortColumn(BaseSortMixin, Enum): VERSION = "version" PACKAGE_MANAGER = "packageManager" + class VendorSortColumn(BaseSortMixin, Enum): SCORE = "score" FULL_NAME = "fullName" SHORT_NAME = "shortName" + class VulnerabilitySortColumn(BaseSortMixin, Enum): LAST_UPDATE_DATE = "lastUpdateDate" EXTERNAL_ID = "externalId" diff --git a/sw360/sw360_api.py b/sw360/sw360_api.py index d2863b5..f81a4f8 100644 --- a/sw360/sw360_api.py +++ b/sw360/sw360_api.py @@ -101,7 +101,7 @@ def __init__( self.default_batch_size = default_batch_size self.api_version = self.parse_version("18.0") - def login_api(self, token: str = "") -> bool: + def login_api(self, token: str = "") -> bool: # noqa """Login to SW360 REST API. This used to have a `token` parameter due to historic reasons which is ignored. diff --git a/tests/test_sw360_clearingrequests.py b/tests/test_sw360_clearingrequests.py index aa4d4aa..7d22a31 100644 --- a/tests/test_sw360_clearingrequests.py +++ b/tests/test_sw360_clearingrequests.py @@ -48,7 +48,8 @@ def _add_version_response(self) -> None: responses.add( method=responses.GET, url=self.MYURL + "resource/api/version", - body='{"apiVersion":"20.1.76","buildTime":"2026-08-14T13:17:47Z","buildNumber":"a8a0997","sw360Version":"20.1.0","gitBranch":"main"}', + body='{"apiVersion":"20.1.76","buildTime":"2026-08-14T13:17:47Z","buildNumber":"a8a0997"' + + ',"sw360Version":"20.1.0","gitBranch":"main"}', status=200, content_type="application/json", ) diff --git a/tests/test_sw360_components.py b/tests/test_sw360_components.py index 702130e..8205f68 100644 --- a/tests/test_sw360_components.py +++ b/tests/test_sw360_components.py @@ -51,7 +51,8 @@ def _add_version_response(self) -> None: responses.add( method=responses.GET, url=self.MYURL + "resource/api/version", - body='{"apiVersion":"20.1.76","buildTime":"2026-08-14T13:17:47Z","buildNumber":"a8a0997","sw360Version":"20.1.0","gitBranch":"main"}', + body='{"apiVersion":"20.1.76","buildTime":"2026-08-14T13:17:47Z","buildNumber":"a8a0997",' + + '"sw360Version":"20.1.0","gitBranch":"main"}', status=200, content_type="application/json", ) diff --git a/tests/test_sw360_health.py b/tests/test_sw360_health.py index d105858..6f4dc32 100644 --- a/tests/test_sw360_health.py +++ b/tests/test_sw360_health.py @@ -48,7 +48,8 @@ def _add_version_response(self) -> None: responses.add( method=responses.GET, url=self.MYURL + "resource/api/version", - body='{"apiVersion":"20.1.76","buildTime":"2026-08-14T13:17:47Z","buildNumber":"a8a0997","sw360Version":"20.1.0","gitBranch":"main"}', + body='{"apiVersion":"20.1.76","buildTime":"2026-08-14T13:17:47Z","buildNumber":"a8a0997",' + + '"sw360Version":"20.1.0","gitBranch":"main"}', status=200, content_type="application/json", ) diff --git a/tests/test_sw360_licenses.py b/tests/test_sw360_licenses.py index 58b5183..ab27e22 100644 --- a/tests/test_sw360_licenses.py +++ b/tests/test_sw360_licenses.py @@ -48,7 +48,8 @@ def _add_version_response(self) -> None: responses.add( method=responses.GET, url=self.MYURL + "resource/api/version", - body='{"apiVersion":"20.1.76","buildTime":"2026-08-14T13:17:47Z","buildNumber":"a8a0997","sw360Version":"20.1.0","gitBranch":"main"}', + body='{"apiVersion":"20.1.76","buildTime":"2026-08-14T13:17:47Z","buildNumber":"a8a0997",' + + '"sw360Version":"20.1.0","gitBranch":"main"}', status=200, content_type="application/json", ) diff --git a/tests/test_sw360_moderationrequests.py b/tests/test_sw360_moderationrequests.py index b5a335d..2799a7d 100644 --- a/tests/test_sw360_moderationrequests.py +++ b/tests/test_sw360_moderationrequests.py @@ -49,7 +49,8 @@ def _add_version_response(self) -> None: responses.add( method=responses.GET, url=self.MYURL + "resource/api/version", - body='{"apiVersion":"20.1.76","buildTime":"2026-08-14T13:17:47Z","buildNumber":"a8a0997","sw360Version":"20.1.0","gitBranch":"main"}', + body='{"apiVersion":"20.1.76","buildTime":"2026-08-14T13:17:47Z","buildNumber":"a8a0997",' + + '"sw360Version":"20.1.0","gitBranch":"main"}', status=200, content_type="application/json", ) diff --git a/tests/test_sw360_packages.py b/tests/test_sw360_packages.py index 9d7c6f3..c12b5a2 100644 --- a/tests/test_sw360_packages.py +++ b/tests/test_sw360_packages.py @@ -50,7 +50,8 @@ def _add_version_response(self) -> None: responses.add( method=responses.GET, url=self.MYURL + "resource/api/version", - body='{"apiVersion":"20.1.76","buildTime":"2026-08-14T13:17:47Z","buildNumber":"a8a0997","sw360Version":"20.1.0","gitBranch":"main"}', + body='{"apiVersion":"20.1.76","buildTime":"2026-08-14T13:17:47Z","buildNumber":"a8a0997",' + + '"sw360Version":"20.1.0","gitBranch":"main"}', status=200, content_type="application/json", ) diff --git a/tests/test_sw360_projects.py b/tests/test_sw360_projects.py index cff2477..61a7379 100644 --- a/tests/test_sw360_projects.py +++ b/tests/test_sw360_projects.py @@ -45,7 +45,8 @@ def get_logged_in_lib(self) -> SW360: responses.add( method=responses.GET, url=self.MYURL + "resource/api/version", - body='{"apiVersion":"20.1.76","buildTime":"2026-08-14T13:17:47Z","buildNumber":"a8a0997","sw360Version":"20.1.0","gitBranch":"main"}', + body='{"apiVersion":"20.1.76","buildTime":"2026-08-14T13:17:47Z","buildNumber":"a8a0997",' + + '"sw360Version":"20.1.0","gitBranch":"main"}', status=200, content_type="application/json", ) diff --git a/tests/test_sw360_releases.py b/tests/test_sw360_releases.py index 55bb0b1..183e8ca 100644 --- a/tests/test_sw360_releases.py +++ b/tests/test_sw360_releases.py @@ -50,7 +50,8 @@ def _add_version_response(self) -> None: responses.add( method=responses.GET, url=self.MYURL + "resource/api/version", - body='{"apiVersion":"20.1.76","buildTime":"2026-08-14T13:17:47Z","buildNumber":"a8a0997","sw360Version":"20.1.0","gitBranch":"main"}', + body='{"apiVersion":"20.1.76","buildTime":"2026-08-14T13:17:47Z","buildNumber":"a8a0997",' + + '"sw360Version":"20.1.0","gitBranch":"main"}', status=200, content_type="application/json", ) @@ -99,7 +100,8 @@ def test_get_get_release_internal_server_error(self) -> None: responses.add( method=responses.GET, url=self.MYURL + "resource/api/releases/123", - body='{"timestamp": "2020-12-10T07:22:06.1685Z", "status": "500", "error": "Internal Server Error", "message": "Handler dispatch failed; nested exception is java.lang.OutOfMemoryError: Metaspace"}', # noqa + body='{"timestamp": "2020-12-10T07:22:06.1685Z", "status": "500", "error": "Internal Server Error",' + + ' "message": "Handler dispatch failed; nested exception is java.lang.OutOfMemoryError: Metaspace"}', # noqa status=500, content_type="application/json", adding_headers={"Authorization": "Token " + self.MYTOKEN}, @@ -179,7 +181,9 @@ def test_get_all_releases_isnewclearing_with_source_available(self) -> None: responses.add( method=responses.GET, - url=self.MYURL + "resource/api/releases?isNewClearingWithSourceAvailable=true&luceneSearch=true&page=0&page_entries=50&sort=name,asc", + url=self.MYURL + + "resource/api/releases?isNewClearingWithSourceAvailable=true&luceneSearch=true" + + "&page=0&page_entries=50&sort=name,asc", body='{"_embedded": {"sw360:releases": [{"name": "Tethys.Logging", "version": "1.3.0"}]}}', status=200, content_type="application/json", @@ -204,7 +208,8 @@ def test_get_all_releases_all_details(self) -> None: responses.add( method=responses.GET, - url=self.MYURL + "resource/api/releases?allDetails=true&luceneSearch=true&page=0&page_entries=50&sort=name,asc", + url=self.MYURL + + "resource/api/releases?allDetails=true&luceneSearch=true&page=0&page_entries=50&sort=name,asc", body='{"_embedded": {"sw360:releases": [{"name": "Tethys.Logging", "version": "1.3.0", "releaseDate": "2018-03-04"}]}}', # noqa status=200, content_type="application/json", @@ -249,7 +254,8 @@ def test_get_all_releases_with_fields(self) -> None: responses.add( method=responses.GET, - url=self.MYURL + "resource/api/releases?fields=releaseDate&luceneSearch=true&page=0&page_entries=50&sort=name,asc", + url=self.MYURL + + "resource/api/releases?fields=releaseDate&luceneSearch=true&page=0&page_entries=50&sort=name,asc", body='{"_embedded": {"sw360:releases": [{"name": "Tethys.Logging", "version": "1.3.0", "releaseDate": "2018-03-04"}]}}', # noqa status=200, content_type="application/json", @@ -272,7 +278,9 @@ def test_get_all_releases_with_fields_and_all_details(self) -> None: responses.add( method=responses.GET, - url=self.MYURL + "resource/api/releases?allDetails=true&fields=releaseDate&luceneSearch=true&page=0&page_entries=50&sort=name,asc", + url=self.MYURL + + "resource/api/releases?allDetails=true&fields=releaseDate&luceneSearch=true" + + "&page=0&page_entries=50&sort=name,asc", body='{"_embedded": {"sw360:releases": [{"name": "Tethys.Logging", "version": "1.3.0", "releaseDate": "2018-03-04"}]}}', # noqa status=200, content_type="application/json",