diff --git a/changelogs/fragments/6688-is-struct-included-bug-in-keycloak-py.yml b/changelogs/fragments/6688-is-struct-included-bug-in-keycloak-py.yml new file mode 100644 index 0000000000..92546d8eca --- /dev/null +++ b/changelogs/fragments/6688-is-struct-included-bug-in-keycloak-py.yml @@ -0,0 +1,2 @@ +bugfixes: + - keycloak module utils - fix ``is_struct_included`` handling of lists of lists/dictionaries (https://github.com/ansible-collections/community.general/pull/6688). \ No newline at end of file diff --git a/plugins/module_utils/identity/keycloak/keycloak.py b/plugins/module_utils/identity/keycloak/keycloak.py index 20314ede36..a08e8613e4 100644 --- a/plugins/module_utils/identity/keycloak/keycloak.py +++ b/plugins/module_utils/identity/keycloak/keycloak.py @@ -216,24 +216,30 @@ def is_struct_included(struct1, struct2, exclude=None): Return True if all element of dict 1 are present in dict 2, return false otherwise. """ if isinstance(struct1, list) and isinstance(struct2, list): + if not struct1 and not struct2: + return True for item1 in struct1: if isinstance(item1, (list, dict)): for item2 in struct2: - if not is_struct_included(item1, item2, exclude): - return False + if is_struct_included(item1, item2, exclude): + break + else: + return False else: if item1 not in struct2: return False return True elif isinstance(struct1, dict) and isinstance(struct2, dict): + if not struct1 and not struct2: + return True try: for key in struct1: if not (exclude and key in exclude): if not is_struct_included(struct1[key], struct2[key], exclude): return False - return True except KeyError: return False + return True elif isinstance(struct1, bool) and isinstance(struct2, bool): return struct1 == struct2 else: diff --git a/tests/unit/plugins/module_utils/identity/keycloak/test_keycloak_module_utils.py b/tests/unit/plugins/module_utils/identity/keycloak/test_keycloak_module_utils.py new file mode 100644 index 0000000000..dc0f8d3f9a --- /dev/null +++ b/tests/unit/plugins/module_utils/identity/keycloak/test_keycloak_module_utils.py @@ -0,0 +1,103 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or 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 + +import unittest + +from ansible_collections.community.general.plugins.module_utils.identity.keycloak.keycloak import is_struct_included + + +class KeycloakIsStructIncludedTestCase(unittest.TestCase): + dict1 = dict( + test1='test1', + test2=dict( + test1='test1', + test2='test2' + ), + test3=['test1', dict(test='test1', test2='test2')] + ) + dict2 = dict( + test1='test1', + test2=dict( + test1='test1', + test2='test2', + test3='test3' + ), + test3=['test1', dict(test='test1', test2='test2'), 'test3'], + test4='test4' + ) + dict3 = dict( + test1='test1', + test2=dict( + test1='test1', + test2='test23', + test3='test3' + ), + test3=['test1', dict(test='test1', test2='test23'), 'test3'], + test4='test4' + ) + + dict5 = dict( + test1='test1', + test2=dict( + test1=True, + test2='test23', + test3='test3' + ), + test3=['test1', dict(test='test1', test2='test23'), 'test3'], + test4='test4' + ) + + dict6 = dict( + test1='test1', + test2=dict( + test1='true', + test2='test23', + test3='test3' + ), + test3=['test1', dict(test='test1', test2='test23'), 'test3'], + test4='test4' + ) + dict7 = [ + { + 'roles': ['view-clients', 'view-identity-providers', 'view-users', 'query-realms', 'manage-users'], + 'clientid': 'master-realm' + }, + { + 'roles': ['manage-account', 'view-profile', 'manage-account-links'], + 'clientid': 'account' + } + ] + dict8 = [ + { + 'roles': ['view-clients', 'query-realms', 'view-users'], + 'clientid': 'master-realm' + }, + { + 'roles': ['manage-account-links', 'view-profile', 'manage-account'], + 'clientid': 'account' + } + ] + + def test_trivial(self): + self.assertTrue(is_struct_included(self.dict1, self.dict1)) + + def test_equals_with_dict2_bigger_than_dict1(self): + self.assertTrue(is_struct_included(self.dict1, self.dict2)) + + def test_not_equals_with_dict2_bigger_than_dict1(self): + self.assertFalse(is_struct_included(self.dict2, self.dict1)) + + def test_not_equals_with_dict1_different_than_dict3(self): + self.assertFalse(is_struct_included(self.dict1, self.dict3)) + + def test_equals_with_dict5_contain_bool_and_dict6_contain_true_string(self): + self.assertFalse(is_struct_included(self.dict5, self.dict6)) + self.assertFalse(is_struct_included(self.dict6, self.dict5)) + + def test_not_equals_dict7_dict8_compare_dict7_with_list_bigger_than_dict8_but_reverse_equals(self): + self.assertFalse(is_struct_included(self.dict7, self.dict8)) + self.assertTrue(is_struct_included(self.dict8, self.dict7))