diff --git a/plugins/module_utils/identity/keycloak/keycloak.py b/plugins/module_utils/identity/keycloak/keycloak.py index f35064600c..194893dd52 100644 --- a/plugins/module_utils/identity/keycloak/keycloak.py +++ b/plugins/module_utils/identity/keycloak/keycloak.py @@ -389,7 +389,7 @@ class KeycloakAPI: self.restheaders = connection_header self.http_agent = self.module.params.get("http_agent") - def _request(self, url: str, method: str, data: str | bytes | None = None, headers = None): + def _request(self, url: str, method: str, data: str | bytes | None = None, headers=None): """Makes a request to Keycloak and returns the raw response. If a 401 is returned, attempts to re-authenticate using first the module's refresh_token (if provided) @@ -597,77 +597,79 @@ class KeycloakAPI: except Exception as e: self.fail_request(e, msg=f"Could not delete realm {realm}: {e}", exception=traceback.format_exc()) -def get_localization_values(self, locale, realm="master"): - """ - Get all localization overrides for a given realm and locale. + def get_localization_values(self, locale: str, realm: str = "master") -> dict[str, str]: + """ + Get all localization overrides for a given realm and locale. - Parameters: - locale (str): Locale code (for example, 'en', 'fi', 'de'). - realm (str): Realm name. Defaults to 'master'. + :param locale: Locale code (for example, 'en', 'fi', 'de'). + :param realm: Realm name. Defaults to 'master'. - Returns: - dict[str, str]: Mapping of localization keys to override values. + :return: Mapping of localization keys to override values. - Raises: - KeycloakError: Wrapped HTTP/JSON error with context + :raise KeycloakError: Wrapped HTTP/JSON error with context """ realm_url = URL_LOCALIZATIONS.format(url=self.baseurl, realm=realm, locale=locale) try: - return self._request_and_deserialize(realm_url, method='GET') + return self._request_and_deserialize(realm_url, method="GET") except Exception as e: - self.fail_request(e, msg='Could not read localization overrides for realm %s, locale %s: %s' % (realm, locale, str(e)), - exception=traceback.format_exc()) + self.fail_request( + e, + msg="Could not read localization overrides for realm %s, locale %s: %s" % (realm, locale, str(e)), + exception=traceback.format_exc(), + ) -def set_localization_value(self, locale, key, value, realm="master"): - """ - Create or update a single localization override for the given key. + def set_localization_value(self, locale: str, key: str, value: str, realm: str = "master"): + """ + Create or update a single localization override for the given key. - Parameters: - locale (str): Locale code (for example, 'en'). - key (str): Localization message key to set. - value (str): Override value to set. - realm (str): Realm name. Defaults to 'master'. + :param locale: Locale code (for example, 'en'). + :param key: Localization message key to set. + :param value: Override value to set. + :param realm: Realm name. Defaults to 'master'. - Returns: - HTTPResponse: Response object on success. + :return: HTTPResponse: Response object on success. - Raises: - KeycloakError: Wrapped HTTP error with context + :raise KeycloakError: Wrapped HTTP error with context """ realm_url = URL_LOCALIZATION.format(url=self.baseurl, realm=realm, locale=locale, key=key) - headers = self.restheaders.copy() - headers['Content-Type'] = 'text/plain; charset=utf-8' + headers = self.restheaders.copy() + headers["Content-Type"] = "text/plain; charset=utf-8" try: - return self._request(realm_url, method='PUT', data=to_native(value), headers=headers) + return self._request(realm_url, method="PUT", data=to_native(value), headers=headers) except Exception as e: - self.fail_request(e, msg='Could not set localization value in realm %s, locale %s: %s=%s: %s' % (realm, locale, key, value, str(e)), - exception=traceback.format_exc()) + self.fail_request( + e, + msg="Could not set localization value in realm %s, locale %s: %s=%s: %s" + % (realm, locale, key, value, str(e)), + exception=traceback.format_exc(), + ) -def delete_localization_value(self, locale, key, realm="master"): - """ - Delete a single localization override key for the given locale. + def delete_localization_value(self, locale: str, key: str, realm: str = "master"): + """ + Delete a single localization override key for the given locale. - Parameters: - locale (str): Locale code (for example, 'en'). - key (str): Localization message key to delete. - realm (str): Realm name. Defaults to 'master'. + :param locale: Locale code (for example, 'en'). + :param key: Localization message key to delete. + :param realm: Realm name. Defaults to 'master'. - Returns: - HTTPResponse: Response object on success. + :return: HTTPResponse: Response object on success. - Raises: - KeycloakError: Wrapped HTTP error with context + :raise KeycloakError: Wrapped HTTP error with context """ realm_url = URL_LOCALIZATION.format(url=self.baseurl, realm=realm, locale=locale, key=key) try: - return self._request(realm_url, method='DELETE') + return self._request(realm_url, method="DELETE") except Exception as e: - self.fail_request(e, msg='Could not delete localization value in realm %s, locale %s, key %s: %s' % (realm, locale, key, str(e)), - exception=traceback.format_exc()) + self.fail_request( + e, + msg="Could not delete localization value in realm %s, locale %s, key %s: %s" + % (realm, locale, key, str(e)), + exception=traceback.format_exc(), + ) def get_clients(self, realm: str = "master", filter=None): """Obtains client representations for clients in a realm diff --git a/plugins/modules/keycloak_realm_localization.py b/plugins/modules/keycloak_realm_localization.py index db3beac55e..14aec450c7 100644 --- a/plugins/modules/keycloak_realm_localization.py +++ b/plugins/modules/keycloak_realm_localization.py @@ -7,6 +7,7 @@ # https://www.gnu.org/licenses/gpl-3.0.txt) # SPDX-License-Identifier: GPL-3.0-or-later from __future__ import absolute_import, division, print_function + __metaclass__ = type DOCUMENTATION = r""" @@ -162,8 +163,12 @@ end_state: value: Bye """ -from ansible_collections.community.general.plugins.module_utils.identity.keycloak.keycloak import KeycloakAPI, \ - keycloak_argument_spec, get_token, KeycloakError +from ansible_collections.community.general.plugins.module_utils.identity.keycloak.keycloak import ( + KeycloakAPI, + keycloak_argument_spec, + get_token, + KeycloakError, +) from ansible.module_utils.basic import AnsibleModule from copy import deepcopy @@ -180,7 +185,7 @@ def _normalize_overrides_from_api(current): return [] # Convert mapping to list of key/value dicts - items = [{'key': k, 'value': v} for k, v in sorted(current.items())] + items = [{"key": k, "value": v} for k, v in sorted(current.items())] # Sort for stable comparisons and diff output return items @@ -197,28 +202,30 @@ def main(): # Describe a single override record overrides_spec = dict( - key=dict(type='str', no_log=False, required=True), - value=dict(type='str', required=True), + key=dict(type="str", no_log=False, required=True), + value=dict(type="str", required=True), ) # Module-specific arguments meta_args = dict( - locale=dict(type='str', required=True), - parent_id=dict(type='str', required=True), - state=dict(type='str', default='present', choices=['present', 'absent']), - overrides=dict(type='list', elements='dict', options=overrides_spec, default=[]), + locale=dict(type="str", required=True), + parent_id=dict(type="str", required=True), + state=dict(type="str", default="present", choices=["present", "absent"]), + overrides=dict(type="list", elements="dict", options=overrides_spec, default=[]), ) argument_spec.update(meta_args) - module = AnsibleModule(argument_spec=argument_spec, - supports_check_mode=True, - # Require token OR full credential set. This mirrors other Keycloak modules. - required_one_of=([['token', 'auth_realm', 'auth_username', 'auth_password']]), - required_together=([['auth_realm', 'auth_username', 'auth_password']])) + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + # Require token OR full credential set. This mirrors other Keycloak modules. + required_one_of=([["token", "auth_realm", "auth_username", "auth_password"]]), + required_together=([["auth_realm", "auth_username", "auth_password"]]), + ) # Initialize the result object used by Ansible - result = dict(changed=False, msg='', end_state={}, diff=dict(before={}, after={})) + result = dict(changed=False, msg="", end_state={}, diff=dict(before={}, after={})) # Obtain access token, initialize API try: @@ -229,33 +236,32 @@ def main(): kc = KeycloakAPI(module, connection_header) # Convenience locals for frequently used parameters - locale = module.params.get('locale') - state = module.params.get('state') - parent_id = module.params.get('parent_id') + locale = module.params.get("locale") + state = module.params.get("state") + parent_id = module.params.get("parent_id") - desired_raw = module.params.get('overrides') or [] - desired_map = {r['key']: r.get('value') for r in desired_raw} - desired_overrides = [{'key': k, 'value': v} for k, v in sorted(desired_map.items())] + desired_raw = module.params.get("overrides") or [] + desired_map = {r["key"]: r.get("value") for r in desired_raw} + desired_overrides = [{"key": k, "value": v} for k, v in sorted(desired_map.items())] # Fetch current overrides and normalize to comparable structure old_overrides = _normalize_overrides_from_api(kc.get_localization_values(locale, parent_id) or {}) before = { - 'locale': locale, - 'overrides': deepcopy(old_overrides), + "locale": locale, + "overrides": deepcopy(old_overrides), } # Proposed state used for diff reporting changeset = { - 'locale': locale, - 'overrides': [], + "locale": locale, + "overrides": [], } # Default to no change; flip to True when updates/deletes are needed - result['changed'] = False + result["changed"] = False - if state == 'present': - - changeset['overrides'] = deepcopy(desired_overrides) + if state == "present": + changeset["overrides"] = deepcopy(desired_overrides) # Compute two sets: # - to_update: keys missing or with different values @@ -268,13 +274,12 @@ def main(): override_found = False for override in to_remove: - - if override['key'] == record['key']: + if override["key"] == record["key"]: override_found = True # Value differs -> update needed - if override['value'] != record['value']: - result['changed'] = True + if override["value"] != record["value"]: + result["changed"] = True to_update.append(record) # Remove processed item so what's left in to_remove are deletions @@ -284,68 +289,65 @@ def main(): if not override_found: # New key, must be created to_update.append(record) - result['changed'] = True + result["changed"] = True # Any leftovers in to_remove must be deleted if to_remove: - result['changed'] = True + result["changed"] = True - if result['changed']: + if result["changed"]: if module._diff: - result['diff'] = dict(before=before, after=changeset) + result["diff"] = dict(before=before, after=changeset) if module.check_mode: # Dry-run: report intent without side effects - result['msg'] = "Locale %s overrides would be updated." % (locale) + result["msg"] = "Locale %s overrides would be updated." % (locale) else: - for override in to_remove: - kc.delete_localization_value(locale, override['key'], parent_id) + kc.delete_localization_value(locale, override["key"], parent_id) for override in to_update: - kc.set_localization_value(locale, override['key'], override['value'], parent_id) + kc.set_localization_value(locale, override["key"], override["value"], parent_id) - result['msg'] = "Locale %s overrides have been updated." % (locale) + result["msg"] = "Locale %s overrides have been updated." % (locale) else: - result['msg'] = "Locale %s overrides are in sync." % (locale) + result["msg"] = "Locale %s overrides are in sync." % (locale) # For accurate end_state, read back from API unless we are in check_mode if not module.check_mode: final_overrides = _normalize_overrides_from_api(kc.get_localization_values(locale, parent_id) or {}) else: - final_overrides = ['overrides'] + final_overrides = ["overrides"] - result['end_state'] = {'locale': locale, 'overrides': final_overrides} + result["end_state"] = {"locale": locale, "overrides": final_overrides} - elif state == 'absent': + elif state == "absent": # Full removal of locale overrides if module._diff: - result['diff'] = dict(before=before, after=changeset) + result["diff"] = dict(before=before, after=changeset) if module.check_mode: - if old_overrides: - result['changed'] = True - result['msg'] = "All overrides for locale %s would be deleted." % (locale) + result["changed"] = True + result["msg"] = "All overrides for locale %s would be deleted." % (locale) else: - result['msg'] = "No overrides for locale %s to be deleted." % (locale) + result["msg"] = "No overrides for locale %s to be deleted." % (locale) else: - for override in old_overrides: - kc.delete_localization_value(locale, override['key'], parent_id) - result['changed'] = True + kc.delete_localization_value(locale, override["key"], parent_id) + result["changed"] = True - result['msg'] = "Locale %s has no overrides." % (locale) + result["msg"] = "Locale %s has no overrides." % (locale) - result['end_state'] = changeset + result["end_state"] = changeset module.exit_json(**result) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/tests/unit/plugins/modules/test_keycloak_realm_localization.py b/tests/unit/plugins/modules/test_keycloak_realm_localization.py index e93f7c6e3c..fcd3811e14 100644 --- a/tests/unit/plugins/modules/test_keycloak_realm_localization.py +++ b/tests/unit/plugins/modules/test_keycloak_realm_localization.py @@ -7,9 +7,12 @@ # SPDX-License-Identifier: GPL-3.0-or-later from __future__ import absolute_import, division, print_function + __metaclass__ = type from contextlib import contextmanager +from io import StringIO +from itertools import count from ansible_collections.community.internal_test_tools.tests.unit.compat import unittest from ansible_collections.community.internal_test_tools.tests.unit.compat.mock import patch @@ -22,10 +25,6 @@ from ansible_collections.community.internal_test_tools.tests.unit.plugins.module from ansible_collections.community.general.plugins.modules import keycloak_realm_localization -from itertools import count - -from io import StringIO - @contextmanager def patch_keycloak_api(get_localization_values=None, set_localization_value=None, delete_localization_value=None): @@ -33,9 +32,11 @@ def patch_keycloak_api(get_localization_values=None, set_localization_value=None Patch KeycloakAPI methods used by the module under test. """ obj = keycloak_realm_localization.KeycloakAPI - with patch.object(obj, 'get_localization_values', side_effect=get_localization_values) as mock_get_values: - with patch.object(obj, 'set_localization_value', side_effect=set_localization_value) as mock_set_value: - with patch.object(obj, 'delete_localization_value', side_effect=delete_localization_value) as mock_del_value: + with patch.object(obj, "get_localization_values", side_effect=get_localization_values) as mock_get_values: + with patch.object(obj, "set_localization_value", side_effect=set_localization_value) as mock_set_value: + with patch.object( + obj, "delete_localization_value", side_effect=delete_localization_value + ) as mock_del_value: yield mock_get_values, mock_set_value, mock_del_value @@ -43,21 +44,20 @@ def get_response(object_with_future_response, method, get_id_call_count): if callable(object_with_future_response): return object_with_future_response() if isinstance(object_with_future_response, dict): - return get_response( - object_with_future_response[method], method, get_id_call_count) + return get_response(object_with_future_response[method], method, get_id_call_count) if isinstance(object_with_future_response, list): call_number = next(get_id_call_count) - return get_response( - object_with_future_response[call_number], method, get_id_call_count) + return get_response(object_with_future_response[call_number], method, get_id_call_count) return object_with_future_response def build_mocked_request(get_id_user_count, response_dict): def _mocked_requests(*args, **kwargs): url = args[0] - method = kwargs['method'] + method = kwargs["method"] future_response = response_dict.get(url, None) return get_response(future_response, method, get_id_user_count) + return _mocked_requests @@ -65,19 +65,23 @@ def create_wrapper(text_as_string): """Allow to mock many times a call to one address. Without this function, the StringIO is empty for the second call. """ + def _create_wrapper(): return StringIO(text_as_string) + return _create_wrapper def mock_good_connection(): token_response = { - 'http://keycloak.url/auth/realms/master/protocol/openid-connect/token': create_wrapper('{"access_token": "alongtoken"}'), + "http://keycloak.url/auth/realms/master/protocol/openid-connect/token": create_wrapper( + '{"access_token": "alongtoken"}' + ), } return patch( - 'ansible_collections.community.general.plugins.module_utils.identity.keycloak.keycloak.open_url', + "ansible_collections.community.general.plugins.module_utils.identity.keycloak.keycloak.open_url", side_effect=build_mocked_request(count(), token_response), - autospec=True + autospec=True, ) @@ -89,51 +93,54 @@ class TestKeycloakRealmLocalization(ModuleTestCase): def test_present_no_change_in_sync(self): """Desired overrides already match, no change.""" module_args = { - 'auth_keycloak_url': 'http://keycloak.url/auth', - 'token': '{{ access_token }}', - 'parent_id': 'my-realm', - 'locale': 'en', - 'state': 'present', - 'overrides': [ - {'key': 'greeting', 'value': 'Hello'}, - {'key': 'farewell', 'value': 'Bye'}, + "auth_keycloak_url": "http://keycloak.url/auth", + "token": "{{ access_token }}", + "parent_id": "my-realm", + "locale": "en", + "state": "present", + "overrides": [ + {"key": "greeting", "value": "Hello"}, + {"key": "farewell", "value": "Bye"}, ], } # get_localization_values is called twice: before and after return_value_get_localization_values = [ - {'greeting': 'Hello', 'farewell': 'Bye'}, - {'greeting': 'Hello', 'farewell': 'Bye'}, + {"greeting": "Hello", "farewell": "Bye"}, + {"greeting": "Hello", "farewell": "Bye"}, ] with set_module_args(module_args): with mock_good_connection(): - with patch_keycloak_api(get_localization_values=return_value_get_localization_values) \ - as (mock_get_values, mock_set_value, mock_del_value): + with patch_keycloak_api(get_localization_values=return_value_get_localization_values) as ( + mock_get_values, + mock_set_value, + mock_del_value, + ): with self.assertRaises(AnsibleExitJson) as exec_info: self.module.main() self.assertEqual(mock_get_values.call_count, 2) self.assertEqual(mock_set_value.call_count, 0) self.assertEqual(mock_del_value.call_count, 0) - self.assertIs(exec_info.exception.args[0]['changed'], False) + self.assertIs(exec_info.exception.args[0]["changed"], False) def test_present_creates_updates_and_deletes(self): """Create missing, update differing, and delete extra overrides.""" module_args = { - 'auth_keycloak_url': 'http://keycloak.url/auth', - 'token': '{{ access_token }}', - 'parent_id': 'my-realm', - 'locale': 'en', - 'state': 'present', - 'overrides': [ - {'key': 'a', 'value': '1-new'}, # update - {'key': 'c', 'value': '3'}, # create + "auth_keycloak_url": "http://keycloak.url/auth", + "token": "{{ access_token }}", + "parent_id": "my-realm", + "locale": "en", + "state": "present", + "overrides": [ + {"key": "a", "value": "1-new"}, # update + {"key": "c", "value": "3"}, # create ], } # Before: a=1, b=2; After: a=1-new, c=3 return_value_get_localization_values = [ - {'a': '1', 'b': '2'}, - {'a': '1-new', 'c': '3'}, + {"a": "1", "b": "2"}, + {"a": "1-new", "c": "3"}, ] return_value_set = [None, None] return_value_delete = [None] @@ -141,9 +148,9 @@ class TestKeycloakRealmLocalization(ModuleTestCase): with set_module_args(module_args): with mock_good_connection(): with patch_keycloak_api( - get_localization_values=return_value_get_localization_values, - set_localization_value=return_value_set, - delete_localization_value=return_value_delete, + get_localization_values=return_value_get_localization_values, + set_localization_value=return_value_set, + delete_localization_value=return_value_delete, ) as (mock_get_values, mock_set_value, mock_del_value): with self.assertRaises(AnsibleExitJson) as exec_info: self.module.main() @@ -153,30 +160,33 @@ class TestKeycloakRealmLocalization(ModuleTestCase): self.assertEqual(mock_del_value.call_count, 1) # Two set calls: update 'a', create 'c' self.assertEqual(mock_set_value.call_count, 2) - self.assertIs(exec_info.exception.args[0]['changed'], True) + self.assertIs(exec_info.exception.args[0]["changed"], True) def test_present_check_mode_only_reports(self): """Check mode: report changes, do not call API mutators.""" module_args = { - 'auth_keycloak_url': 'http://keycloak.url/auth', - 'token': '{{ access_token }}', - 'parent_id': 'my-realm', - 'locale': 'en', - 'state': 'present', - 'overrides': [ - {'key': 'x', 'value': '1'}, # change - {'key': 'y', 'value': '2'}, # create + "auth_keycloak_url": "http://keycloak.url/auth", + "token": "{{ access_token }}", + "parent_id": "my-realm", + "locale": "en", + "state": "present", + "overrides": [ + {"key": "x", "value": "1"}, # change + {"key": "y", "value": "2"}, # create ], - '_ansible_check_mode': True, # signal for readers; set_module_args is what matters + "_ansible_check_mode": True, # signal for readers; set_module_args is what matters } return_value_get_localization_values = [ - {'x': '0'}, + {"x": "0"}, ] with set_module_args(module_args): with mock_good_connection(): - with patch_keycloak_api(get_localization_values=return_value_get_localization_values) \ - as (mock_get_values, mock_set_value, mock_del_value): + with patch_keycloak_api(get_localization_values=return_value_get_localization_values) as ( + mock_get_values, + mock_set_value, + mock_del_value, + ): with self.assertRaises(AnsibleExitJson) as exec_info: self.module.main() @@ -184,28 +194,28 @@ class TestKeycloakRealmLocalization(ModuleTestCase): self.assertEqual(mock_get_values.call_count, 1) self.assertEqual(mock_set_value.call_count, 0) self.assertEqual(mock_del_value.call_count, 0) - self.assertIs(exec_info.exception.args[0]['changed'], True) - self.assertIn('would be updated', exec_info.exception.args[0]['msg']) + self.assertIs(exec_info.exception.args[0]["changed"], True) + self.assertIn("would be updated", exec_info.exception.args[0]["msg"]) def test_absent_deletes_all(self): """Remove all overrides when present.""" module_args = { - 'auth_keycloak_url': 'http://keycloak.url/auth', - 'token': '{{ access_token }}', - 'parent_id': 'my-realm', - 'locale': 'en', - 'state': 'absent', + "auth_keycloak_url": "http://keycloak.url/auth", + "token": "{{ access_token }}", + "parent_id": "my-realm", + "locale": "en", + "state": "absent", } return_value_get_localization_values = [ - {'k1': 'v1', 'k2': 'v2'}, + {"k1": "v1", "k2": "v2"}, ] return_value_delete = [None, None] with set_module_args(module_args): with mock_good_connection(): with patch_keycloak_api( - get_localization_values=return_value_get_localization_values, - delete_localization_value=return_value_delete, + get_localization_values=return_value_get_localization_values, + delete_localization_value=return_value_delete, ) as (mock_get_values, mock_set_value, mock_del_value): with self.assertRaises(AnsibleExitJson) as exec_info: self.module.main() @@ -213,16 +223,16 @@ class TestKeycloakRealmLocalization(ModuleTestCase): self.assertEqual(mock_get_values.call_count, 1) self.assertEqual(mock_del_value.call_count, 2) self.assertEqual(mock_set_value.call_count, 0) - self.assertIs(exec_info.exception.args[0]['changed'], True) + self.assertIs(exec_info.exception.args[0]["changed"], True) def test_absent_idempotent_when_nothing_to_delete(self): """No change when locale has no overrides.""" module_args = { - 'auth_keycloak_url': 'http://keycloak.url/auth', - 'token': '{{ access_token }}', - 'parent_id': 'my-realm', - 'locale': 'en', - 'state': 'absent', + "auth_keycloak_url": "http://keycloak.url/auth", + "token": "{{ access_token }}", + "parent_id": "my-realm", + "locale": "en", + "state": "absent", } return_value_get_localization_values = [ {}, @@ -230,38 +240,40 @@ class TestKeycloakRealmLocalization(ModuleTestCase): with set_module_args(module_args): with mock_good_connection(): - with patch_keycloak_api(get_localization_values=return_value_get_localization_values) \ - as (mock_get_values, mock_set_value, mock_del_value): + with patch_keycloak_api(get_localization_values=return_value_get_localization_values) as ( + mock_get_values, + mock_set_value, + mock_del_value, + ): with self.assertRaises(AnsibleExitJson) as exec_info: self.module.main() self.assertEqual(mock_get_values.call_count, 1) self.assertEqual(mock_del_value.call_count, 0) self.assertEqual(mock_set_value.call_count, 0) - self.assertIs(exec_info.exception.args[0]['changed'], False) + self.assertIs(exec_info.exception.args[0]["changed"], False) def test_present_missing_value_validation(self): """Validation error when state=present and value is missing.""" module_args = { - 'auth_keycloak_url': 'http://keycloak.url/auth', - 'token': '{{ access_token }}', - 'parent_id': 'my-realm', - 'locale': 'en', - 'state': 'present', - 'overrides': [ - {'key': 'greeting'}, + "auth_keycloak_url": "http://keycloak.url/auth", + "token": "{{ access_token }}", + "parent_id": "my-realm", + "locale": "en", + "state": "present", + "overrides": [ + {"key": "greeting"}, ], } with set_module_args(module_args): with mock_good_connection(): - with patch_keycloak_api() \ - as (_mock_get_values, _mock_set_value, _mock_del_value): + with patch_keycloak_api() as (_mock_get_values, _mock_set_value, _mock_del_value): with self.assertRaises(AnsibleFailJson) as exec_info: self.module.main() - self.assertIn("missing required arguments: value", exec_info.exception.args[0]['msg']) + self.assertIn("missing required arguments: value", exec_info.exception.args[0]["msg"]) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main()