diff --git a/cterasdk/core/admins.py b/cterasdk/core/admins.py index dc5a955b..693cca9e 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 @@ -126,8 +127,19 @@ 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(f'Could not modify user: {ref}') 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): + 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/docs/source/UserGuides/Miscellaneous/Changelog.rst b/docs/source/UserGuides/Miscellaneous/Changelog.rst index e4f709c6..293d8b6f 100644 --- a/docs/source/UserGuides/Miscellaneous/Changelog.rst +++ b/docs/source/UserGuides/Miscellaneous/Changelog.rst @@ -1,6 +1,18 @@ 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 ------- diff --git a/tests/ut/core/admin/test_users.py b/tests/ut/core/admin/test_users.py index 1caace86..235a2cbf 100644 --- a/tests/ut/core/admin/test_users.py +++ b/tests/ut/core/admin/test_users.py @@ -319,6 +319,87 @@ 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) + @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, + 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) + 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' self._init_global_admin(execute_response=execute_response)