diff --git a/changelogs/fragments/1163-map_complex_type.yml b/changelogs/fragments/1163-map_complex_type.yml new file mode 100644 index 00000000000..6315b24128f --- /dev/null +++ b/changelogs/fragments/1163-map_complex_type.yml @@ -0,0 +1,2 @@ +bugfixes: +- module_utils.transformations - ensure that ``map_complex_type`` still returns transformed items if items exists that are not in the type_map (https://github.com/ansible-collections/amazon.aws/pull/1163). diff --git a/plugins/module_utils/transformation.py b/plugins/module_utils/transformation.py index e768844fa6f..70d38cd8abb 100644 --- a/plugins/module_utils/transformation.py +++ b/plugins/module_utils/transformation.py @@ -104,7 +104,7 @@ def map_complex_type(complex_type, type_map): complex_type[key], type_map[key]) else: - return complex_type + new_type[key] = complex_type[key] elif isinstance(complex_type, list): for i in range(len(complex_type)): new_type.append(map_complex_type( diff --git a/tests/unit/module_utils/cloud/test_cloud_retry.py b/tests/unit/module_utils/cloud/test_cloud_retry.py index 71b4e655ce3..ce5f03f1196 100644 --- a/tests/unit/module_utils/cloud/test_cloud_retry.py +++ b/tests/unit/module_utils/cloud/test_cloud_retry.py @@ -18,7 +18,7 @@ class TestCloudRetry(): error_codes = [400, 500, 600] custom_error_codes = [100, 200, 300] - class TestException(Exception): + class OurTestException(Exception): """ custom exception class for testing """ @@ -81,7 +81,7 @@ def test_retry_backoff(self): def test_retry_func(): if test_retry_func.counter < 2: test_retry_func.counter += 1 - raise self.TestException(status=random.choice(TestCloudRetry.error_codes)) + raise self.OurTestException(status=random.choice(TestCloudRetry.error_codes)) else: return True @@ -99,7 +99,7 @@ def test_retry_exponential_backoff(self): def test_retry_func(): if test_retry_func.counter < 2: test_retry_func.counter += 1 - raise self.TestException(status=random.choice(TestCloudRetry.error_codes)) + raise self.OurTestException(status=random.choice(TestCloudRetry.error_codes)) else: return True @@ -108,19 +108,19 @@ def test_retry_func(): assert ret is True def test_retry_exponential_backoff_with_unexpected_exception(self): - unexpected_except = self.TestException(status=100) + unexpected_except = self.OurTestException(status=100) @TestCloudRetry.UnitTestsRetry.exponential_backoff(retries=3, delay=1, backoff=1.1, max_delay=3, catch_extra_error_codes=TestCloudRetry.error_codes) def test_retry_func(): if test_retry_func.counter == 0: test_retry_func.counter += 1 - raise self.TestException(status=random.choice(TestCloudRetry.error_codes)) + raise self.OurTestException(status=random.choice(TestCloudRetry.error_codes)) else: raise unexpected_except test_retry_func.counter = 0 - with pytest.raises(self.TestException) as context: + with pytest.raises(self.OurTestException) as context: test_retry_func() assert context.value.status == unexpected_except.status @@ -134,7 +134,7 @@ def test_retry_jitter_backoff(self): def test_retry_func(): if test_retry_func.counter < 2: test_retry_func.counter += 1 - raise self.TestException(status=random.choice(TestCloudRetry.error_codes)) + raise self.OurTestException(status=random.choice(TestCloudRetry.error_codes)) else: return True @@ -143,19 +143,19 @@ def test_retry_func(): assert ret is True def test_retry_jittered_backoff_with_unexpected_exception(self): - unexpected_except = self.TestException(status=100) + unexpected_except = self.OurTestException(status=100) @TestCloudRetry.UnitTestsRetry.jittered_backoff(retries=3, delay=1, max_delay=3, catch_extra_error_codes=TestCloudRetry.error_codes) def test_retry_func(): if test_retry_func.counter == 0: test_retry_func.counter += 1 - raise self.TestException(status=random.choice(TestCloudRetry.error_codes)) + raise self.OurTestException(status=random.choice(TestCloudRetry.error_codes)) else: raise unexpected_except test_retry_func.counter = 0 - with pytest.raises(self.TestException) as context: + with pytest.raises(self.OurTestException) as context: test_retry_func() assert context.value.status == unexpected_except.status @@ -172,7 +172,7 @@ def build_response(): def test_retry_func(): if test_retry_func.counter < 2: test_retry_func.counter += 1 - raise self.TestException(build_response()) + raise self.OurTestException(build_response()) else: return True @@ -188,13 +188,13 @@ def test_wrapped_function_called_several_times(self): @TestCloudRetry.UnitTestsRetry.exponential_backoff(retries=2, delay=2, backoff=4, max_delay=100, catch_extra_error_codes=TestCloudRetry.error_codes) def _fail(): - raise self.TestException(status=random.choice(TestCloudRetry.error_codes)) + raise self.OurTestException(status=random.choice(TestCloudRetry.error_codes)) # run the method 3 times and assert that each it is retrying after 2secs # the elapsed execution time should be closed to 2sec for _i in range(3): start = datetime.now() - with pytest.raises(self.TestException): + with pytest.raises(self.OurTestException): _fail() duration = (datetime.now() - start).seconds assert duration == 2 diff --git a/tests/unit/module_utils/transformation/test_ansible_dict_to_boto3_filter_list.py b/tests/unit/module_utils/transformation/test_ansible_dict_to_boto3_filter_list.py index 3c19089231f..23c82b173b2 100644 --- a/tests/unit/module_utils/transformation/test_ansible_dict_to_boto3_filter_list.py +++ b/tests/unit/module_utils/transformation/test_ansible_dict_to_boto3_filter_list.py @@ -10,7 +10,7 @@ from ansible_collections.amazon.aws.plugins.module_utils.transformation import ansible_dict_to_boto3_filter_list -class AnsibleDictToBoto3FilterListTestSuite(): +class TestAnsibleDictToBoto3FilterList(): # ======================================================== # ec2.ansible_dict_to_boto3_filter_list @@ -28,7 +28,7 @@ def test_ansible_dict_with_string_to_boto3_filter_list(self): ] converted_filters_list = ansible_dict_to_boto3_filter_list(filters) - self.assertEqual(converted_filters_list, filter_list_string) + assert converted_filters_list == filter_list_string def test_ansible_dict_with_boolean_to_boto3_filter_list(self): filters = {'enabled': True} @@ -42,7 +42,7 @@ def test_ansible_dict_with_boolean_to_boto3_filter_list(self): ] converted_filters_bool = ansible_dict_to_boto3_filter_list(filters) - self.assertEqual(converted_filters_bool, filter_list_boolean) + assert converted_filters_bool == filter_list_boolean def test_ansible_dict_with_integer_to_boto3_filter_list(self): filters = {'version': 1} @@ -56,4 +56,18 @@ def test_ansible_dict_with_integer_to_boto3_filter_list(self): ] converted_filters_int = ansible_dict_to_boto3_filter_list(filters) - self.assertEqual(converted_filters_int, filter_list_integer) + assert converted_filters_int == filter_list_integer + + def test_ansible_dict_with_list_to_boto3_filter_list(self): + filters = {'version': ['1', '2', '3']} + filter_list_integer = [ + { + 'Name': 'version', + 'Values': [ + '1', '2', '3' + ] + } + ] + + converted_filters_int = ansible_dict_to_boto3_filter_list(filters) + assert converted_filters_int == filter_list_integer diff --git a/tests/unit/module_utils/transformation/test_map_complex_type.py b/tests/unit/module_utils/transformation/test_map_complex_type.py index 32635d47acd..2300e2351dd 100644 --- a/tests/unit/module_utils/transformation/test_map_complex_type.py +++ b/tests/unit/module_utils/transformation/test_map_complex_type.py @@ -8,14 +8,93 @@ from ansible_collections.amazon.aws.plugins.module_utils.transformation import map_complex_type +from ansible_collections.amazon.aws.tests.unit.compat.mock import sentinel -class TestMapComplexTypeTestSuite(): - def test_map_complex_type_over_dict(self): - type_map = {'minimum_healthy_percent': 'int', 'maximum_percent': 'int'} - complex_type_dict = {'minimum_healthy_percent': "75", 'maximum_percent': "150"} - complex_type_expected = {'minimum_healthy_percent': 75, 'maximum_percent': 150} +def test_map_complex_type_over_dict(): + type_map = {'minimum_healthy_percent': 'int', 'maximum_percent': 'int'} + complex_type_dict = {'minimum_healthy_percent': "75", 'maximum_percent': "150"} + complex_type_expected = {'minimum_healthy_percent': 75, 'maximum_percent': 150} - complex_type_mapped = map_complex_type(complex_type_dict, type_map) + complex_type_mapped = map_complex_type(complex_type_dict, type_map) - assert complex_type_mapped == complex_type_expected + assert complex_type_mapped == complex_type_expected + + +def test_map_complex_type_empty(): + type_map = {'minimum_healthy_percent': 'int', 'maximum_percent': 'int'} + assert map_complex_type({}, type_map) == {} + assert map_complex_type([], type_map) == [] + assert map_complex_type(None, type_map) is None + + +def test_map_complex_type_no_type(): + type_map = {'some_entry': 'int'} + complex_dict = {'another_entry': sentinel.UNSPECIFIED_MAPPING} + mapped_dict = map_complex_type(complex_dict, type_map) + assert mapped_dict == complex_dict + # we should have the original sentinel object, even if it's a new dictionary + assert mapped_dict['another_entry'] is sentinel.UNSPECIFIED_MAPPING + + +def test_map_complex_type_list(): + type_map = {'some_entry': 'int'} + complex_dict = {'some_entry': ["1", "2", "3"]} + expected_dict = {'some_entry': [1, 2, 3]} + mapped_dict = map_complex_type(complex_dict, type_map) + assert mapped_dict == expected_dict + + +def test_map_complex_type_list_type(): + type_map = {'some_entry': ['int']} + complex_dict = {'some_entry': ["1", "2", "3"]} + expected_dict = {'some_entry': [1, 2, 3]} + mapped_dict = map_complex_type(complex_dict, type_map) + assert mapped_dict == expected_dict + + type_map = {'some_entry': ['int']} + complex_dict = {'some_entry': "1"} + expected_dict = {'some_entry': 1} + mapped_dict = map_complex_type(complex_dict, type_map) + assert mapped_dict == expected_dict + + +def test_map_complex_type_complex(): + type_map = { + 'my_integer': 'int', + 'my_bool': 'bool', + 'my_string': 'str', + 'my_typelist_of_int': ['int'], + 'my_maplist_of_int': 'int', + 'my_unused': 'bool', + } + complex_dict = { + 'my_integer': '-24', + 'my_bool': 'true', + 'my_string': 43, + 'my_typelist_of_int': '5', + 'my_maplist_of_int': ['-26', '47'], + 'my_unconverted': sentinel.UNSPECIFIED_MAPPING, + } + expected_dict = { + 'my_integer': -24, + 'my_bool': True, + 'my_string': '43', + 'my_typelist_of_int': 5, + 'my_maplist_of_int': [-26, 47], + 'my_unconverted': sentinel.UNSPECIFIED_MAPPING, + } + + mapped_dict = map_complex_type(complex_dict, type_map) + + assert mapped_dict == expected_dict + assert mapped_dict['my_unconverted'] is sentinel.UNSPECIFIED_MAPPING + assert mapped_dict['my_bool'] is True + + +def test_map_complex_type_nested_list(): + type_map = {'my_integer': 'int'} + complex_dict = [{'my_integer': '5'}, {'my_integer': '-24'}] + expected_dict = [{'my_integer': 5}, {'my_integer': -24}] + mapped_dict = map_complex_type(complex_dict, type_map) + assert mapped_dict == expected_dict