From 865c9311a658e12e2fdb18a48a29b134d95043b2 Mon Sep 17 00:00:00 2001 From: yarona Date: Wed, 16 Sep 2026 15:39:23 +0300 Subject: [PATCH 1/4] SDK-369-bwi-issue-with-error-handling --- cterasdk/core/admins.py | 13 +++++++++++- tests/ut/core/admin/test_users.py | 35 +++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/cterasdk/core/admins.py b/cterasdk/core/admins.py index dc5a955b..ab55a5e5 100644 --- a/cterasdk/core/admins.py +++ b/cterasdk/core/admins.py @@ -2,6 +2,7 @@ from .base_command import BaseCommand from ..exceptions import CTERAException, ObjectNotFoundException +from ..exceptions.transport import HTTPError from ..common import Object, DateTimeUtils from ..common import union from . import query @@ -127,7 +128,17 @@ def modify(self, current_username, new_username=None, email=None, first_name=Non return response except CTERAException as error: logger.error('Could not modify user: %s', ref) - raise CTERAException(f'Could not modify user: {ref}') from error + raise CTERAException(Administrators._modify_failure_message(ref, error)) from error + + @staticmethod + def _modify_failure_message(ref, error): + message = f'Could not modify user: {ref}' + if not isinstance(error, HTTPError): + return message + err_detail = error.error.response.error + if hasattr(err_detail, 'msg') and err_detail.msg: + return f'{message}. {err_detail.msg}' + return message def delete(self, name): """ diff --git a/tests/ut/core/admin/test_users.py b/tests/ut/core/admin/test_users.py index 1caace86..99a3e7a0 100644 --- a/tests/ut/core/admin/test_users.py +++ b/tests/ut/core/admin/test_users.py @@ -319,6 +319,41 @@ def test_modify_admin_user(self): actual_param = self._global_admin.api.put.call_args[0][1] self._assert_equal_objects(actual_param, new_user_object) + def test_modify_admin_user_same_password_includes_portal_msg(self): + current_user_object = self._get_admin_object( + name=self._username, + email=self._email, + firstName=self._first_name, + lastName=self._last_name, + password=self._password, + role=self._role, + company=None, + comment=None + ) + portal_msg = 'Object validation failed (field: password error: Old and new passwords cannot be the same)' + password_value = 'Str0ngP@ssword!1' + portal_error = Object() + portal_error.msg = portal_msg + field = Object() + field.name = 'password' + field.desc = 'Old and new passwords cannot be the same' + field.val = password_value + portal_error.fields = [field] + http_error = Object() + http_error.request = Object(url='/administrators/' + self._username) + http_error.response = Object(status=500, error=portal_error) + self._init_global_admin(get_response=current_user_object) + self._global_admin.api.put = mock.MagicMock(side_effect=exceptions.transport.InternalServerError(http_error)) + with self.assertRaises(exceptions.CTERAException) as error: + admins.Administrators(self._global_admin).modify(self._username, password=self._password) + ref = f'/administrators/{self._username}' + self._global_admin.api.get.assert_called_once_with(ref) + self._global_admin.api.put.assert_called_once_with(ref, mock.ANY) + exception_text = str(error.exception) + self.assertIn(f'Could not modify user: {ref}', exception_text) + self.assertIn('Old and new passwords cannot be the same', exception_text) + self.assertNotIn(password_value, exception_text) + def test_delete_admin_user(self): execute_response = 'Success' self._init_global_admin(execute_response=execute_response) From e883b88dbf82681519c6fd4c435c3fda976a3cf9 Mon Sep 17 00:00:00 2001 From: yarona Date: Wed, 16 Sep 2026 15:48:04 +0300 Subject: [PATCH 2/4] Update Changelog for version 2.20.48 --- docs/source/UserGuides/Miscellaneous/Changelog.rst | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/source/UserGuides/Miscellaneous/Changelog.rst b/docs/source/UserGuides/Miscellaneous/Changelog.rst index e4f709c6..7f587e1b 100644 --- a/docs/source/UserGuides/Miscellaneous/Changelog.rst +++ b/docs/source/UserGuides/Miscellaneous/Changelog.rst @@ -1,6 +1,16 @@ Changelog ========= +2.20.48 +------- + +Bug Fixes +^^^^^^^^^ + +* Restore portal validation details in ``admins.modify`` failure messages when the API returns an HTTP error with a portal ``msg`` attribute (for example, when the new password matches the current password). + +Related issue: `SDK-369 `_ + 2.20.47 ------- From f492b329b81159d5d7281292802a08a17692b0d2 Mon Sep 17 00:00:00 2001 From: yarona Date: Wed, 16 Sep 2026 17:50:54 +0300 Subject: [PATCH 3/4] Restore portal validation details in admins.modify failure messages --- docs/source/UserGuides/Miscellaneous/Changelog.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/source/UserGuides/Miscellaneous/Changelog.rst b/docs/source/UserGuides/Miscellaneous/Changelog.rst index 7f587e1b..293d8b6f 100644 --- a/docs/source/UserGuides/Miscellaneous/Changelog.rst +++ b/docs/source/UserGuides/Miscellaneous/Changelog.rst @@ -7,7 +7,9 @@ Changelog Bug Fixes ^^^^^^^^^ -* Restore portal validation details in ``admins.modify`` failure messages when the API returns an HTTP error with a portal ``msg`` attribute (for example, when the new password matches the current password). +* Restore portal validation details in ``admins.modify`` failure messages when the API returns an + HTTP error with a portal ``msg`` attribute (for example, when the new password matches the current + password). Related issue: `SDK-369 `_ From 607aa8062c93515144fcd65be3f55e39a7c73189 Mon Sep 17 00:00:00 2001 From: yarona Date: Thu, 17 Sep 2026 12:02:21 +0300 Subject: [PATCH 4/4] Improve error handling in user modification by logging detailed failure messages --- cterasdk/core/admins.py | 5 ++-- tests/ut/core/admin/test_users.py | 48 ++++++++++++++++++++++++++++++- 2 files changed, 50 insertions(+), 3 deletions(-) diff --git a/cterasdk/core/admins.py b/cterasdk/core/admins.py index ab55a5e5..693cca9e 100644 --- a/cterasdk/core/admins.py +++ b/cterasdk/core/admins.py @@ -127,8 +127,9 @@ def modify(self, current_username, new_username=None, email=None, first_name=Non logger.info("User modified. %s", {'username': user.name}) return response except CTERAException as error: - logger.error('Could not modify user: %s', ref) - raise CTERAException(Administrators._modify_failure_message(ref, error)) from error + failure_message = Administrators._modify_failure_message(ref, error) + logger.error('%s', failure_message) + raise CTERAException(failure_message) from error @staticmethod def _modify_failure_message(ref, error): diff --git a/tests/ut/core/admin/test_users.py b/tests/ut/core/admin/test_users.py index 99a3e7a0..235a2cbf 100644 --- a/tests/ut/core/admin/test_users.py +++ b/tests/ut/core/admin/test_users.py @@ -319,7 +319,8 @@ def test_modify_admin_user(self): actual_param = self._global_admin.api.put.call_args[0][1] self._assert_equal_objects(actual_param, new_user_object) - def test_modify_admin_user_same_password_includes_portal_msg(self): + @mock.patch('cterasdk.core.admins.logger') + def test_modify_admin_user_same_password_includes_portal_msg(self, logger_mock): current_user_object = self._get_admin_object( name=self._username, email=self._email, @@ -353,6 +354,51 @@ def test_modify_admin_user_same_password_includes_portal_msg(self): self.assertIn(f'Could not modify user: {ref}', exception_text) self.assertIn('Old and new passwords cannot be the same', exception_text) self.assertNotIn(password_value, exception_text) + logger_mock.error.assert_called_once_with('%s', exception_text) + + def test_modify_admin_user_http_error_without_portal_msg_uses_generic_message(self): + current_user_object = self._get_admin_object( + name=self._username, + email=self._email, + firstName=self._first_name, + lastName=self._last_name, + password=self._password, + role=self._role, + company=None, + comment=None + ) + portal_error = Object() + http_error = Object() + http_error.request = Object(url='/administrators/' + self._username) + http_error.response = Object(status=500, error=portal_error) + self._init_global_admin(get_response=current_user_object) + self._global_admin.api.put = mock.MagicMock(side_effect=exceptions.transport.InternalServerError(http_error)) + ref = f'/administrators/{self._username}' + expected_message = f'Could not modify user: {ref}' + with self.assertRaises(exceptions.CTERAException) as error: + admins.Administrators(self._global_admin).modify(self._username, password=self._password) + self.assertEqual(expected_message, str(error.exception)) + + def test_modify_admin_user_non_http_error_uses_generic_message(self): + current_user_object = self._get_admin_object( + name=self._username, + email=self._email, + firstName=self._first_name, + lastName=self._last_name, + password=self._password, + role=self._role, + company=None, + comment=None + ) + self._init_global_admin(get_response=current_user_object) + self._global_admin.api.put = mock.MagicMock( + side_effect=exceptions.ObjectNotFoundException('Object not found') + ) + ref = f'/administrators/{self._username}' + expected_message = f'Could not modify user: {ref}' + with self.assertRaises(exceptions.CTERAException) as error: + admins.Administrators(self._global_admin).modify(self._username, password=self._password) + self.assertEqual(expected_message, str(error.exception)) def test_delete_admin_user(self): execute_response = 'Success'