diff --git a/tests/unit/module_utils/arn/test_is_outpost_arn.py b/tests/unit/module_utils/arn/test_is_outpost_arn.py new file mode 100644 index 00000000000..aab13ebd6ae --- /dev/null +++ b/tests/unit/module_utils/arn/test_is_outpost_arn.py @@ -0,0 +1,24 @@ +# (c) 2022 Red Hat Inc. +# +# This file is part of Ansible +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import pytest + +from ansible_collections.amazon.aws.plugins.module_utils.arn import is_outpost_arn + +outpost_arn_test_inputs = [ + ("arn:aws:outposts:us-east-1:123456789012:outpost/op-1234567890abcdef0", True), + ("arn:aws:outposts:us-east-1:123456789012:outpost/op-1234567890abcdef0123", False), + ("arn:aws:outpost:us-east-1: 123456789012:outpost/ op-1234567890abcdef0", False), + ("ars:aws:outposts:us-east-1: 123456789012:outpost/ op-1234567890abcdef0", False), + ("arn:was:outposts:us-east-1: 123456789012:outpost/ op-1234567890abcdef0", False), +] + + +@pytest.mark.parametrize("outpost_arn, result", outpost_arn_test_inputs) +def test_is_outpost_arn(outpost_arn, result): + assert is_outpost_arn(outpost_arn) == result diff --git a/tests/unit/module_utils/test_arn.py b/tests/unit/module_utils/arn/test_parse_aws_arn.py similarity index 78% rename from tests/unit/module_utils/test_arn.py rename to tests/unit/module_utils/arn/test_parse_aws_arn.py index 0f5bec7ec04..87dada4a916 100644 --- a/tests/unit/module_utils/test_arn.py +++ b/tests/unit/module_utils/arn/test_parse_aws_arn.py @@ -4,23 +4,12 @@ # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import (absolute_import, division, print_function) - __metaclass__ = type -# import unittest import pytest -from ansible_collections.amazon.aws.plugins.module_utils.arn import is_outpost_arn from ansible_collections.amazon.aws.plugins.module_utils.arn import parse_aws_arn -outpost_arn_test_inputs = [ - ("arn:aws:outposts:us-east-1:123456789012:outpost/op-1234567890abcdef0", True), - ("arn:aws:outposts:us-east-1:123456789012:outpost/op-1234567890abcdef0123", False), - ("arn:aws:outpost:us-east-1: 123456789012:outpost/ op-1234567890abcdef0", False), - ("ars:aws:outposts:us-east-1: 123456789012:outpost/ op-1234567890abcdef0", False), - ("arn:was:outposts:us-east-1: 123456789012:outpost/ op-1234567890abcdef0", False), -] - arn_bad_values = [ ("arn:aws:outpost:us-east-1: 123456789012:outpost/op-1234567890abcdef0"), ("arn:aws:out post:us-east-1:123456789012:outpost/op-1234567890abcdef0"), @@ -71,14 +60,28 @@ resource='stateful-rulegroup/BotNetCommandAndControlDomainsActionOrder'), dict(partition='aws', service='iam', region='', account_id='aws', resource='policy/AWSDirectConnectReadOnlyAccess'), + # Examples merged in from test_arn.py + dict(partition="aws-us-gov", service="iam", region="", account_id="0123456789", + resource="role/foo-role"), + dict(partition="aws", service='iam', region="", account_id="123456789012", + resource="user/dev/*"), + dict(partition="aws", service="iam", region="", account_id="123456789012", + resource="user:test"), + dict(partition="aws-cn", service="iam", region="", account_id="123456789012", + resource="user:test"), + dict(partition="aws", service="iam", region="", account_id="123456789012", + resource="user"), + dict(partition="aws", service="s3", region="", account_id="", + resource="my_corporate_bucket/*"), + dict(partition="aws", service="s3", region="", account_id="", + resource="my_corporate_bucket/Development/*"), + dict(partition="aws", service="rds", region="es-east-1", account_id="000000000000", + resource="snapshot:rds:my-db-snapshot"), + dict(partition="aws", service="cloudformation", region="us-east-1", account_id="012345678901", + resource="changeSet/Ansible-StackName-c6884247ede41eb0"), ] -@pytest.mark.parametrize("outpost_arn, result", outpost_arn_test_inputs) -def test_is_outpost_arn(outpost_arn, result): - assert is_outpost_arn(outpost_arn) == result - - @pytest.mark.parametrize("arn", arn_bad_values) def test_parse_aws_arn_bad_values(arn): # Make sure we get the expected 'None' for various 'bad' ARNs. diff --git a/tests/unit/module_utils/botocore/test_is_boto3_error_code.py b/tests/unit/module_utils/botocore/test_is_boto3_error_code.py new file mode 100644 index 00000000000..14fc5d05c80 --- /dev/null +++ b/tests/unit/module_utils/botocore/test_is_boto3_error_code.py @@ -0,0 +1,214 @@ +# -*- coding: utf-8 -*- +# (c) 2020 Red Hat Inc. +# +# This file is part of Ansible +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import pytest + +try: + import botocore +except ImportError: + # Handled by HAS_BOTO3 + pass + +from ansible_collections.amazon.aws.plugins.module_utils.botocore import is_boto3_error_code +from ansible_collections.amazon.aws.plugins.module_utils.botocore import HAS_BOTO3 + +if not HAS_BOTO3: + pytestmark = pytest.mark.skip("test_is_boto3_error_code.py requires the python modules 'boto3' and 'botocore'") + + +class Boto3ErrorCodeTestSuite(): + + def _make_denied_exception(self): + return botocore.exceptions.ClientError( + { + "Error": { + "Code": "AccessDenied", + "Message": "User: arn:aws:iam::123456789012:user/ExampleUser " + + "is not authorized to perform: iam:GetUser on resource: user ExampleUser" + }, + "ResponseMetadata": { + "RequestId": "01234567-89ab-cdef-0123-456789abcdef" + } + }, 'getUser') + + def _make_unexpected_exception(self): + return botocore.exceptions.ClientError( + { + "Error": { + "Code": "SomeThingWentWrong", + "Message": "Boom!" + }, + "ResponseMetadata": { + "RequestId": "01234567-89ab-cdef-0123-456789abcdef" + } + }, 'someCall') + + def _make_encoded_exception(self): + return botocore.exceptions.ClientError( + { + "Error": { + "Code": "PermissionDenied", + "Message": "You are not authorized to perform this operation. Encoded authorization failure message: " + + "fEwXX6llx3cClm9J4pURgz1XPnJPrYexEbrJcLhFkwygMdOgx_-aEsj0LqRM6Kxt2HVI6prUhDwbJqBo9U2V7iRKZ" + + "T6ZdJvHH02cXmD0Jwl5vrTsf0PhBcWYlH5wl2qME7xTfdolEUr4CzumCiti7ETiO-RDdHqWlasBOW5bWsZ4GSpPdU" + + "06YAX0TfwVBs48uU5RpCHfz1uhSzez-3elbtp9CmTOHLt5pzJodiovccO55BQKYLPtmJcs6S9YLEEogmpI4Cb1D26" + + "fYahDh51jEmaohPnW5pb1nQe2yPEtuIhtRzNjhFCOOMwY5DBzNsymK-Gj6eJLm7FSGHee4AHLU_XmZMe_6bcLAiOx" + + "6Zdl65Kdd0hLcpwVxyZMi27HnYjAdqRlV3wuCW2PkhAW14qZQLfiuHZDEwnPe2PBGSlFcCmkQvJvX-YLoA7Uyc2wf" + + "NX5RJm38STwfiJSkQaNDhHKTWKiLOsgY4Gze6uZoG7zOcFXFRyaA4cbMmI76uyBO7j-9uQUCtBYqYto8x_9CUJcxI" + + "VC5SPG_C1mk-WoDMew01f0qy-bNaCgmJ9TOQGd08FyuT1SaMpCC0gX6mHuOnEgkFw3veBIowMpp9XcM-yc42fmIOp" + + "FOdvQO6uE9p55Qc-uXvsDTTvT3A7EeFU8a_YoAIt9UgNYM6VTvoprLz7dBI_P6C-bdPPZCY2amm-dJNVZelT6TbJB" + + "H_Vxh0fzeiSUBersy_QzB0moc-vPWgnB-IkgnYLV-4L3K0L2" + }, + "ResponseMetadata": { + "RequestId": "01234567-89ab-cdef-0123-456789abcdef" + } + }, 'someCall') + + def _make_botocore_exception(self): + return botocore.exceptions.EndpointConnectionError(endpoint_url='junk.endpoint') + + ### + # Test that is_boto3_error_code does what's expected when used in a try/except block + # (where we don't explicitly pass an exception to the function) + ### + + def _do_try_code(self, exception, codes): + try: + raise exception + except is_boto3_error_code(codes) as e: + return e + + def test_is_boto3_error_code_single__raise__client(self): + # 'AccessDenied' error, should be caught in our try/except in _do_try_code + thrown_exception = self._make_denied_exception() + codes_to_catch = 'AccessDenied' + + caught_exception = self._do_try_code(thrown_exception, codes_to_catch) + assert caught_exception == thrown_exception + + def test_is_boto3_error_code_single__raise__unexpected(self): + # 'SomeThingWentWrong' error, shouldn't be caught because the Code doesn't match + thrown_exception = self._make_unexpected_exception() + codes_to_catch = 'AccessDenied' + + with pytest.raises(botocore.exceptions.ClientError) as context: + self._do_try_code(thrown_exception, codes_to_catch) + assert context.value == thrown_exception + + def test_is_boto3_error_code_single__raise__botocore(self): + # BotoCoreExceptions don't have an error code, so shouldn't be caught (and shouldn't throw + # some other error due to the missing 'Code' data on the exception) + thrown_exception = self._make_botocore_exception() + codes_to_catch = 'AccessDenied' + + with pytest.raises(botocore.exceptions.BotoCoreError) as context: + self._do_try_code(thrown_exception, codes_to_catch) + + assert context.value == thrown_exception + + def test_is_boto3_error_code_multiple__raise__client(self): + # 'AccessDenied' error, should be caught in our try/except in _do_try_code + # test with multiple possible codes to catch + thrown_exception = self._make_denied_exception() + codes_to_catch = ['AccessDenied', 'NotAccessDenied'] + + caught_exception = self._do_try_code(thrown_exception, codes_to_catch) + assert caught_exception == thrown_exception + + thrown_exception = self._make_denied_exception() + codes_to_catch = ['NotAccessDenied', 'AccessDenied'] + + caught_exception = self._do_try_code(thrown_exception, codes_to_catch) + assert caught_exception == thrown_exception + + def test_is_boto3_error_code_multiple__raise__unexpected(self): + # 'SomeThingWentWrong' error, shouldn't be caught because the Code doesn't match + # test with multiple possible codes to catch + thrown_exception = self._make_unexpected_exception() + codes_to_catch = ['NotAccessDenied', 'AccessDenied'] + + with pytest.raises(botocore.exceptions.ClientError) as context: + self._do_try_code(thrown_exception, codes_to_catch) + assert context.value == thrown_exception + + def test_is_boto3_error_code_multiple__raise__botocore(self): + # BotoCoreErrors don't have an error code, so shouldn't be caught (and shouldn't throw + # some other error due to the missing 'Code' data on the exception) + # test with multiple possible codes to catch + thrown_exception = self._make_botocore_exception() + codes_to_catch = ['NotAccessDenied', 'AccessDenied'] + + with pytest.raises(botocore.exceptions.BotoCoreError) as context: + self._do_try_code(thrown_exception, codes_to_catch) + assert context.value == thrown_exception + + ### + # Test that is_boto3_error_code returns what we expect when explicitly passed an exception + ### + + def test_is_boto3_error_code_single__pass__client(self): + passed_exception = self._make_denied_exception() + returned_exception = is_boto3_error_code('AccessDenied', e=passed_exception) + assert isinstance(passed_exception, returned_exception) + assert issubclass(returned_exception, botocore.exceptions.ClientError) + assert not issubclass(returned_exception, botocore.exceptions.BotoCoreError) + assert issubclass(returned_exception, Exception) + assert returned_exception.__name__ != "NeverEverRaisedException" + + def test_is_boto3_error_code_single__pass__unexpected(self): + passed_exception = self._make_unexpected_exception() + returned_exception = is_boto3_error_code('AccessDenied', e=passed_exception) + assert not isinstance(passed_exception, returned_exception) + assert not issubclass(returned_exception, botocore.exceptions.ClientError) + assert not issubclass(returned_exception, botocore.exceptions.BotoCoreError) + assert issubclass(returned_exception, Exception) + assert returned_exception.__name__ == "NeverEverRaisedException" + + def test_is_boto3_error_code_single__pass__botocore(self): + passed_exception = self._make_botocore_exception() + returned_exception = is_boto3_error_code('AccessDenied', e=passed_exception) + assert not isinstance(passed_exception, returned_exception) + assert not issubclass(returned_exception, botocore.exceptions.ClientError) + assert not issubclass(returned_exception, botocore.exceptions.BotoCoreError) + assert issubclass(returned_exception, Exception) + assert returned_exception.__name__ == "NeverEverRaisedException" + + def test_is_boto3_error_code_multiple__pass__client(self): + passed_exception = self._make_denied_exception() + returned_exception = is_boto3_error_code(['NotAccessDenied', 'AccessDenied'], e=passed_exception) + assert isinstance(passed_exception, returned_exception) + assert issubclass(returned_exception, botocore.exceptions.ClientError) + assert not issubclass(returned_exception, botocore.exceptions.BotoCoreError) + assert issubclass(returned_exception, Exception) + assert returned_exception.__name__ != "NeverEverRaisedException" + + returned_exception = is_boto3_error_code(['AccessDenied', 'NotAccessDenied'], e=passed_exception) + assert isinstance(passed_exception, returned_exception) + assert issubclass(returned_exception, botocore.exceptions.ClientError) + assert not issubclass(returned_exception, botocore.exceptions.BotoCoreError) + assert issubclass(returned_exception, Exception) + assert returned_exception.__name__ != "NeverEverRaisedException" + + def test_is_boto3_error_code_multiple__pass__unexpected(self): + passed_exception = self._make_unexpected_exception() + returned_exception = is_boto3_error_code(['NotAccessDenied', 'AccessDenied'], e=passed_exception) + assert not isinstance(passed_exception, returned_exception) + assert not issubclass(returned_exception, botocore.exceptions.ClientError) + assert not issubclass(returned_exception, botocore.exceptions.BotoCoreError) + assert issubclass(returned_exception, Exception) + assert returned_exception.__name__ == "NeverEverRaisedException" + + def test_is_boto3_error_code_multiple__pass__botocore(self): + passed_exception = self._make_botocore_exception() + returned_exception = is_boto3_error_code(['NotAccessDenied', 'AccessDenied'], e=passed_exception) + assert not isinstance(passed_exception, returned_exception) + assert not issubclass(returned_exception, botocore.exceptions.ClientError) + assert not issubclass(returned_exception, botocore.exceptions.BotoCoreError) + assert issubclass(returned_exception, Exception) + assert returned_exception.__name__ == "NeverEverRaisedException" diff --git a/tests/unit/module_utils/core/test_is_boto3_error_message.py b/tests/unit/module_utils/botocore/test_is_boto3_error_message.py similarity index 51% rename from tests/unit/module_utils/core/test_is_boto3_error_message.py rename to tests/unit/module_utils/botocore/test_is_boto3_error_message.py index 550e89efe72..e80676d3fc4 100644 --- a/tests/unit/module_utils/core/test_is_boto3_error_message.py +++ b/tests/unit/module_utils/botocore/test_is_boto3_error_message.py @@ -8,18 +8,21 @@ __metaclass__ = type import pytest -import botocore -from ansible_collections.amazon.aws.tests.unit.compat import unittest +try: + import botocore +except ImportError: + # Handled by HAS_BOTO3 + pass -from ansible_collections.amazon.aws.plugins.module_utils.core import is_boto3_error_message -from ansible_collections.amazon.aws.plugins.module_utils.ec2 import HAS_BOTO3 +from ansible_collections.amazon.aws.plugins.module_utils.botocore import is_boto3_error_message +from ansible_collections.amazon.aws.plugins.module_utils.botocore import HAS_BOTO3 if not HAS_BOTO3: - pytestmark = pytest.mark.skip("test_iam.py requires the python modules 'boto3' and 'botocore'") + pytestmark = pytest.mark.skip("test_is_boto3_error_message.py requires the python modules 'boto3' and 'botocore'") -class Boto3ErrorTestSuite(unittest.TestCase): +class Boto3ErrorMessageTestSuite(): def _make_denied_exception(self): return botocore.exceptions.ClientError( @@ -70,95 +73,73 @@ def _make_encoded_exception(self): def _make_botocore_exception(self): return botocore.exceptions.EndpointConnectionError(endpoint_url='junk.endpoint') - def setUp(self): - pass + def _do_try_message(self, exception, messages): + try: + raise exception + except is_boto3_error_message(messages) as e: + return e + + ### + # Test that is_boto3_error_message does what's expected when used in a try/except block + # (where we don't explicitly pass an exception to the function) + ### def test_is_boto3_error_message_single__raise__client(self): - caught_exception = None + # error with 'is not authorized to perform' in the message, should be caught in our try/except in _do_try_code thrown_exception = self._make_denied_exception() - # Test that we don't catch BotoCoreError - try: - raise thrown_exception - except is_boto3_error_message('is not authorized to perform') as e: - caught_exception = e - caught = 'Message' - except botocore.exceptions.ClientError as e: # pylint: disable=duplicate-except - caught_exception = e - caught = 'ClientError' - except botocore.exceptions.BotoCoreError as e: - caught_exception = e - caught = 'BotoCoreError' - except Exception as e: - caught_exception = e - caught = 'Exception' + messages_to_catch = 'is not authorized to perform' + + caught_exception = self._do_try_message(thrown_exception, messages_to_catch) + self.assertEqual(caught_exception, thrown_exception) - self.assertEqual(caught, 'Message') def test_is_boto3_error_message_single__raise__unexpected(self): - caught_exception = None + # error with 'Boom!' as the message, shouldn't match and should fall through thrown_exception = self._make_unexpected_exception() - # Test that we don't catch BotoCoreError - try: - raise thrown_exception - except is_boto3_error_message('is not authorized to perform') as e: - caught_exception = e - caught = 'Message' - except botocore.exceptions.ClientError as e: # pylint: disable=duplicate-except - caught_exception = e - caught = 'ClientError' - except botocore.exceptions.BotoCoreError as e: - caught_exception = e - caught = 'BotoCoreError' - except Exception as e: - caught_exception = e - caught = 'Exception' - self.assertEqual(caught_exception, thrown_exception) - self.assertEqual(caught, 'ClientError') + messages_to_catch = 'is not authorized to perform' + + with pytest.raises(botocore.exceptions.ClientError) as context: + self._do_try_message(thrown_exception, messages_to_catch) + + assert context.value == thrown_exception def test_is_boto3_error_message_single__raise__botocore(self): - caught_exception = None - thrown_exception = self._make_botocore_exception() # Test that we don't catch BotoCoreError - try: - raise thrown_exception - except is_boto3_error_message('is not authorized to perform') as e: - caught_exception = e - caught = 'Message' - except botocore.exceptions.ClientError as e: # pylint: disable=duplicate-except - caught_exception = e - caught = 'ClientError' - except botocore.exceptions.BotoCoreError as e: - caught_exception = e - caught = 'BotoCoreError' - except Exception as e: - caught_exception = e - caught = 'Exception' - self.assertEqual(caught_exception, thrown_exception) - self.assertEqual(caught, 'BotoCoreError') + thrown_exception = self._make_botocore_exception() + messages_to_catch = 'is not authorized to perform' + + with pytest.raises(botocore.exceptions.BotoCoreError) as context: + self._do_try_message(thrown_exception, messages_to_catch) + + assert context.value == thrown_exception + + ### + # Test that is_boto3_error_message returns what we expect when explicitly passed an exception + ### def test_is_boto3_error_message_single__pass__client(self): passed_exception = self._make_denied_exception() returned_exception = is_boto3_error_message('is not authorized to perform', e=passed_exception) - self.assertTrue(isinstance(passed_exception, returned_exception)) - self.assertTrue(issubclass(returned_exception, botocore.exceptions.ClientError)) - self.assertFalse(issubclass(returned_exception, botocore.exceptions.BotoCoreError)) - self.assertTrue(issubclass(returned_exception, Exception)) - self.assertNotEqual(returned_exception.__name__, "NeverEverRaisedException") + assert isinstance(passed_exception, returned_exception) + assert issubclass(returned_exception, botocore.exceptions.ClientError) + assert not issubclass(returned_exception, botocore.exceptions.BotoCoreError) + assert issubclass(returned_exception, Exception) + assert returned_exception.__name__ != "NeverEverRaisedException" def test_is_boto3_error_message_single__pass__unexpected(self): passed_exception = self._make_unexpected_exception() returned_exception = is_boto3_error_message('is not authorized to perform', e=passed_exception) - self.assertFalse(isinstance(passed_exception, returned_exception)) - self.assertFalse(issubclass(returned_exception, botocore.exceptions.ClientError)) - self.assertFalse(issubclass(returned_exception, botocore.exceptions.BotoCoreError)) - self.assertTrue(issubclass(returned_exception, Exception)) - self.assertEqual(returned_exception.__name__, "NeverEverRaisedException") + assert not isinstance(passed_exception, returned_exception) + assert not issubclass(returned_exception, botocore.exceptions.ClientError) + assert not issubclass(returned_exception, botocore.exceptions.BotoCoreError) + assert issubclass(returned_exception, Exception) + assert returned_exception.__name__ == "NeverEverRaisedException" def test_is_boto3_error_message_single__pass__botocore(self): passed_exception = self._make_botocore_exception() returned_exception = is_boto3_error_message('is not authorized to perform', e=passed_exception) - self.assertFalse(isinstance(passed_exception, returned_exception)) - self.assertFalse(issubclass(returned_exception, botocore.exceptions.ClientError)) - self.assertFalse(issubclass(returned_exception, botocore.exceptions.BotoCoreError)) - self.assertTrue(issubclass(returned_exception, Exception)) - self.assertEqual(returned_exception.__name__, "NeverEverRaisedException") + assert not isinstance(passed_exception, returned_exception) + assert not issubclass(returned_exception, botocore.exceptions.ClientError) + assert not issubclass(returned_exception, botocore.exceptions.BotoCoreError) + assert issubclass(returned_exception, Exception) + assert returned_exception.__name__ == "NeverEverRaisedException" diff --git a/tests/unit/module_utils/core/test_normalize_boto3_result.py b/tests/unit/module_utils/botocore/test_normalize_boto3_result.py similarity index 75% rename from tests/unit/module_utils/core/test_normalize_boto3_result.py rename to tests/unit/module_utils/botocore/test_normalize_boto3_result.py index e9f71a2add8..71da9d66d8a 100644 --- a/tests/unit/module_utils/core/test_normalize_boto3_result.py +++ b/tests/unit/module_utils/botocore/test_normalize_boto3_result.py @@ -2,13 +2,18 @@ __metaclass__ = type import pytest -from dateutil import parser as date_parser -from ansible_collections.amazon.aws.plugins.module_utils.core import normalize_boto3_result +from ansible_collections.amazon.aws.plugins.module_utils.botocore import normalize_boto3_result example_date_txt = '2020-12-30T00:00:00.000Z' example_date_iso = '2020-12-30T00:00:00+00:00' -example_date = date_parser.parse(example_date_txt) + +try: + from dateutil import parser as date_parser + example_date = date_parser.parse(example_date_txt) +except ImportError: + example_date = None + pytestmark = pytest.mark.skip("test_normalize_boto3_result.py requires the python module dateutil (python-dateutil)") normalize_boto3_result_data = [ diff --git a/tests/unit/module_utils/cloud/test_backoff_iterator.py b/tests/unit/module_utils/cloud/test_backoff_iterator.py new file mode 100644 index 00000000000..5fee115c230 --- /dev/null +++ b/tests/unit/module_utils/cloud/test_backoff_iterator.py @@ -0,0 +1,45 @@ +# (c) 2021 Red Hat Inc. +# +# This file is part of Ansible +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from ansible_collections.amazon.aws.plugins.module_utils.cloud import BackoffIterator + + +def test_backoff_value_generator(): + max_delay = 60 + initial = 3 + backoff = 2 + + min_sleep = initial + counter = 0 + for sleep in BackoffIterator(delay=initial, backoff=backoff, max_delay=max_delay): + if counter > 4: + assert sleep == max_delay + else: + assert sleep == min_sleep + min_sleep *= backoff + counter += 1 + if counter == 10: + break + + +def test_backoff_value_generator_with_jitter(): + max_delay = 60 + initial = 3 + backoff = 2 + + min_sleep = initial + counter = 0 + for sleep in BackoffIterator(delay=initial, backoff=backoff, max_delay=max_delay, jitter=True): + if counter > 4: + assert sleep <= max_delay + else: + assert sleep <= min_sleep + min_sleep *= backoff + counter += 1 + if counter == 10: + break diff --git a/tests/unit/module_utils/test_cloud.py b/tests/unit/module_utils/cloud/test_cloud_retry.py similarity index 64% rename from tests/unit/module_utils/test_cloud.py rename to tests/unit/module_utils/cloud/test_cloud_retry.py index 1486da01f5f..146733ff601 100644 --- a/tests/unit/module_utils/test_cloud.py +++ b/tests/unit/module_utils/cloud/test_cloud_retry.py @@ -6,49 +6,13 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type -from ansible_collections.amazon.aws.plugins.module_utils.cloud import CloudRetry, BackoffIterator -import unittest +from ansible_collections.amazon.aws.plugins.module_utils.cloud import CloudRetry +import pytest import random from datetime import datetime -def test_backoff_value_generator(): - max_delay = 60 - initial = 3 - backoff = 2 - - min_sleep = initial - counter = 0 - for sleep in BackoffIterator(delay=initial, backoff=backoff, max_delay=max_delay): - if counter > 4: - assert sleep == max_delay - else: - assert sleep == min_sleep - min_sleep *= backoff - counter += 1 - if counter == 10: - break - - -def test_backoff_value_generator_with_jitter(): - max_delay = 60 - initial = 3 - backoff = 2 - - min_sleep = initial - counter = 0 - for sleep in BackoffIterator(delay=initial, backoff=backoff, max_delay=max_delay, jitter=True): - if counter > 4: - assert sleep <= max_delay - else: - assert sleep <= min_sleep - min_sleep *= backoff - counter += 1 - if counter == 10: - break - - -class CloudRetryUtils(unittest.TestCase): +class CloudRetryTestSuite(): error_codes = [400, 500, 600] custom_error_codes = [100, 200, 300] @@ -80,9 +44,9 @@ def status_code_from_exception(error): @staticmethod def found(response_code, catch_extra_error_codes=None): if catch_extra_error_codes: - return response_code in catch_extra_error_codes + CloudRetryUtils.custom_error_codes + return response_code in catch_extra_error_codes + CloudRetryTestSuite.custom_error_codes else: - return response_code in CloudRetryUtils.custom_error_codes + return response_code in CloudRetryTestSuite.custom_error_codes class KeyRetry(CloudRetry): base_class = KeyError @@ -106,23 +70,17 @@ def status_code_from_exception(error): def found(response_code, catch_extra_error_codes=None): return True - # ======================================================== - # Setup some initial data that we can use within our tests - # ======================================================== - def setUp(self): - # nothing to do on setup stage - pass - # ======================================================== # retry original backoff # ======================================================== def test_retry_backoff(self): - @CloudRetryUtils.UnitTestsRetry.backoff(tries=3, delay=1, backoff=1.1, catch_extra_error_codes=CloudRetryUtils.error_codes) + @CloudRetryTestSuite.UnitTestsRetry.backoff(tries=3, delay=1, backoff=1.1, + catch_extra_error_codes=CloudRetryTestSuite.error_codes) def test_retry_func(): if test_retry_func.counter < 2: test_retry_func.counter += 1 - raise self.TestException(status=random.choice(CloudRetryUtils.error_codes)) + raise self.TestException(status=random.choice(CloudRetryTestSuite.error_codes)) else: return True @@ -135,11 +93,12 @@ def test_retry_func(): # ======================================================== def test_retry_exponential_backoff(self): - @CloudRetryUtils.UnitTestsRetry.exponential_backoff(retries=3, delay=1, backoff=1.1, max_delay=3, catch_extra_error_codes=CloudRetryUtils.error_codes) + @CloudRetryTestSuite.UnitTestsRetry.exponential_backoff(retries=3, delay=1, backoff=1.1, max_delay=3, + catch_extra_error_codes=CloudRetryTestSuite.error_codes) def test_retry_func(): if test_retry_func.counter < 2: test_retry_func.counter += 1 - raise self.TestException(status=random.choice(CloudRetryUtils.error_codes)) + raise self.TestException(status=random.choice(CloudRetryTestSuite.error_codes)) else: return True @@ -150,29 +109,31 @@ def test_retry_func(): def test_retry_exponential_backoff_with_unexpected_exception(self): unexpected_except = self.TestException(status=100) - @CloudRetryUtils.UnitTestsRetry.exponential_backoff(retries=3, delay=1, backoff=1.1, max_delay=3, catch_extra_error_codes=CloudRetryUtils.error_codes) + @CloudRetryTestSuite.UnitTestsRetry.exponential_backoff(retries=3, delay=1, backoff=1.1, max_delay=3, + catch_extra_error_codes=CloudRetryTestSuite.error_codes) def test_retry_func(): if test_retry_func.counter == 0: test_retry_func.counter += 1 - raise self.TestException(status=random.choice(CloudRetryUtils.error_codes)) + raise self.TestException(status=random.choice(CloudRetryTestSuite.error_codes)) else: raise unexpected_except test_retry_func.counter = 0 - with self.assertRaises(self.TestException) as exc: + with pytest.raises(self.TestException) as context: test_retry_func() - assert exc.exception.status == unexpected_except.status + assert context.value.status == unexpected_except.status # ======================================================== # retry jittered backoff # ======================================================== def test_retry_jitter_backoff(self): - @CloudRetryUtils.UnitTestsRetry.jittered_backoff(retries=3, delay=1, max_delay=3, catch_extra_error_codes=CloudRetryUtils.error_codes) + @CloudRetryTestSuite.UnitTestsRetry.jittered_backoff(retries=3, delay=1, max_delay=3, + catch_extra_error_codes=CloudRetryTestSuite.error_codes) def test_retry_func(): if test_retry_func.counter < 2: test_retry_func.counter += 1 - raise self.TestException(status=random.choice(CloudRetryUtils.error_codes)) + raise self.TestException(status=random.choice(CloudRetryTestSuite.error_codes)) else: return True @@ -183,28 +144,30 @@ def test_retry_func(): def test_retry_jittered_backoff_with_unexpected_exception(self): unexpected_except = self.TestException(status=100) - @CloudRetryUtils.UnitTestsRetry.jittered_backoff(retries=3, delay=1, max_delay=3, catch_extra_error_codes=CloudRetryUtils.error_codes) + @CloudRetryTestSuite.UnitTestsRetry.jittered_backoff(retries=3, delay=1, max_delay=3, + catch_extra_error_codes=CloudRetryTestSuite.error_codes) def test_retry_func(): if test_retry_func.counter == 0: test_retry_func.counter += 1 - raise self.TestException(status=random.choice(CloudRetryUtils.error_codes)) + raise self.TestException(status=random.choice(CloudRetryTestSuite.error_codes)) else: raise unexpected_except test_retry_func.counter = 0 - with self.assertRaises(self.TestException) as exc: + with pytest.raises(self.TestException) as context: test_retry_func() - assert exc.exception.status == unexpected_except.status + assert context.value.status == unexpected_except.status # ======================================================== # retry with custom class # ======================================================== def test_retry_exponential_backoff_custom_class(self): def build_response(): - return dict(response=dict(status=random.choice(CloudRetryUtils.custom_error_codes))) + return dict(response=dict(status=random.choice(CloudRetryTestSuite.custom_error_codes))) - @self.CustomRetry.exponential_backoff(retries=3, delay=1, backoff=1.1, max_delay=3, catch_extra_error_codes=CloudRetryUtils.error_codes) + @self.CustomRetry.exponential_backoff(retries=3, delay=1, backoff=1.1, max_delay=3, + catch_extra_error_codes=CloudRetryTestSuite.error_codes) def test_retry_func(): if test_retry_func.counter < 2: test_retry_func.counter += 1 @@ -221,16 +184,16 @@ def test_retry_func(): # Test wrapped function multiple times will restart the sleep # ============================================================= def test_wrapped_function_called_several_times(self): - @CloudRetryUtils.UnitTestsRetry.exponential_backoff(retries=2, delay=2, backoff=4, max_delay=100, catch_extra_error_codes=CloudRetryUtils.error_codes) + @CloudRetryTestSuite.UnitTestsRetry.exponential_backoff(retries=2, delay=2, backoff=4, max_delay=100, + catch_extra_error_codes=CloudRetryTestSuite.error_codes) def _fail(): - raise self.TestException(status=random.choice(CloudRetryUtils.error_codes)) + raise self.TestException(status=random.choice(CloudRetryTestSuite.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() - raised = False - with self.assertRaises(self.TestException): + with pytest.raises(self.TestException): _fail() duration = (datetime.now() - start).seconds assert duration == 2 @@ -247,26 +210,26 @@ def _fail_key(): def _fail_exception(): raise Exception('bang') - key_retry_decorator = CloudRetryUtils.KeyRetry.exponential_backoff(retries=2, delay=2, backoff=4, max_delay=100) - key_and_index_retry_decorator = CloudRetryUtils.KeyAndIndexRetry.exponential_backoff(retries=2, delay=2, backoff=4, max_delay=100) + key_retry_decorator = CloudRetryTestSuite.KeyRetry.exponential_backoff(retries=2, delay=2, backoff=4, max_delay=100) + key_and_index_retry_decorator = CloudRetryTestSuite.KeyAndIndexRetry.exponential_backoff(retries=2, delay=2, backoff=4, max_delay=100) expectations = [ - [key_retry_decorator, _fail_exception, 0], - [key_retry_decorator, _fail_index, 0], - [key_retry_decorator, _fail_key, 2], - [key_and_index_retry_decorator, _fail_exception, 0], - [key_and_index_retry_decorator, _fail_index, 2], - [key_and_index_retry_decorator, _fail_key, 2], + [key_retry_decorator, _fail_exception, 0, Exception], + [key_retry_decorator, _fail_index, 0, IndexError], + [key_retry_decorator, _fail_key, 2, KeyError], + [key_and_index_retry_decorator, _fail_exception, 0, Exception], + [key_and_index_retry_decorator, _fail_index, 2, IndexError], + [key_and_index_retry_decorator, _fail_key, 2, KeyError], ] for expection in expectations: decorator = expection[0] function = expection[1] duration = expection[2] + exception = exception[3] start = datetime.now() - raised = False - with self.assertRaises(Exception): + with pytest.raises(exception): decorator(function)() _duration = (datetime.now() - start).seconds assert duration == _duration diff --git a/tests/unit/module_utils/core/test_is_boto3_error_code.py b/tests/unit/module_utils/core/test_is_boto3_error_code.py deleted file mode 100644 index 1b1a70e4e5b..00000000000 --- a/tests/unit/module_utils/core/test_is_boto3_error_code.py +++ /dev/null @@ -1,271 +0,0 @@ -# -*- coding: utf-8 -*- -# (c) 2020 Red Hat Inc. -# -# This file is part of Ansible -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type - -import pytest -import botocore - -from ansible_collections.amazon.aws.tests.unit.compat import unittest - -from ansible_collections.amazon.aws.plugins.module_utils.core import is_boto3_error_code -from ansible_collections.amazon.aws.plugins.module_utils.ec2 import HAS_BOTO3 - -if not HAS_BOTO3: - pytestmark = pytest.mark.skip("test_iam.py requires the python modules 'boto3' and 'botocore'") - - -class Boto3ErrorCodeTestSuite(unittest.TestCase): - - def _make_denied_exception(self): - return botocore.exceptions.ClientError( - { - "Error": { - "Code": "AccessDenied", - "Message": "User: arn:aws:iam::123456789012:user/ExampleUser " - + "is not authorized to perform: iam:GetUser on resource: user ExampleUser" - }, - "ResponseMetadata": { - "RequestId": "01234567-89ab-cdef-0123-456789abcdef" - } - }, 'getUser') - - def _make_unexpected_exception(self): - return botocore.exceptions.ClientError( - { - "Error": { - "Code": "SomeThingWentWrong", - "Message": "Boom!" - }, - "ResponseMetadata": { - "RequestId": "01234567-89ab-cdef-0123-456789abcdef" - } - }, 'someCall') - - def _make_encoded_exception(self): - return botocore.exceptions.ClientError( - { - "Error": { - "Code": "PermissionDenied", - "Message": "You are not authorized to perform this operation. Encoded authorization failure message: " + - "fEwXX6llx3cClm9J4pURgz1XPnJPrYexEbrJcLhFkwygMdOgx_-aEsj0LqRM6Kxt2HVI6prUhDwbJqBo9U2V7iRKZ" + - "T6ZdJvHH02cXmD0Jwl5vrTsf0PhBcWYlH5wl2qME7xTfdolEUr4CzumCiti7ETiO-RDdHqWlasBOW5bWsZ4GSpPdU" + - "06YAX0TfwVBs48uU5RpCHfz1uhSzez-3elbtp9CmTOHLt5pzJodiovccO55BQKYLPtmJcs6S9YLEEogmpI4Cb1D26" + - "fYahDh51jEmaohPnW5pb1nQe2yPEtuIhtRzNjhFCOOMwY5DBzNsymK-Gj6eJLm7FSGHee4AHLU_XmZMe_6bcLAiOx" + - "6Zdl65Kdd0hLcpwVxyZMi27HnYjAdqRlV3wuCW2PkhAW14qZQLfiuHZDEwnPe2PBGSlFcCmkQvJvX-YLoA7Uyc2wf" + - "NX5RJm38STwfiJSkQaNDhHKTWKiLOsgY4Gze6uZoG7zOcFXFRyaA4cbMmI76uyBO7j-9uQUCtBYqYto8x_9CUJcxI" + - "VC5SPG_C1mk-WoDMew01f0qy-bNaCgmJ9TOQGd08FyuT1SaMpCC0gX6mHuOnEgkFw3veBIowMpp9XcM-yc42fmIOp" + - "FOdvQO6uE9p55Qc-uXvsDTTvT3A7EeFU8a_YoAIt9UgNYM6VTvoprLz7dBI_P6C-bdPPZCY2amm-dJNVZelT6TbJB" + - "H_Vxh0fzeiSUBersy_QzB0moc-vPWgnB-IkgnYLV-4L3K0L2" - }, - "ResponseMetadata": { - "RequestId": "01234567-89ab-cdef-0123-456789abcdef" - } - }, 'someCall') - - def _make_botocore_exception(self): - return botocore.exceptions.EndpointConnectionError(endpoint_url='junk.endpoint') - - def setUp(self): - pass - - def test_is_boto3_error_code_single__raise__client(self): - thrown_exception = self._make_denied_exception() - caught_exception = None - try: - raise thrown_exception - except is_boto3_error_code('AccessDenied') as e: - caught_exception = e - caught = 'Code' - except botocore.exceptions.ClientError as e: # pylint: disable=duplicate-except - caught_exception = e - caught = 'ClientError' - except botocore.exceptions.BotoCoreError as e: - caught_exception = e - caught = 'BotoCoreError' - except Exception as e: - caught_exception = e - caught = 'Exception' - self.assertEqual(caught_exception, thrown_exception) - self.assertEqual(caught, 'Code') - - def test_is_boto3_error_code_single__raise__unexpected(self): - thrown_exception = self._make_unexpected_exception() - caught_exception = None - try: - raise thrown_exception - except is_boto3_error_code('AccessDenied') as e: - caught_exception = e - caught = 'Code' - except botocore.exceptions.ClientError as e: # pylint: disable=duplicate-except - caught_exception = e - caught = 'ClientError' - except botocore.exceptions.BotoCoreError as e: - caught_exception = e - caught = 'BotoCoreError' - except Exception as e: - caught_exception = e - caught = 'Exception' - self.assertEqual(caught_exception, thrown_exception) - self.assertEqual(caught, 'ClientError') - - def test_is_boto3_error_code_single__raise__botocore(self): - thrown_exception = self._make_botocore_exception() - caught_exception = None - try: - raise thrown_exception - except is_boto3_error_code('AccessDenied') as e: - caught_exception = e - caught = 'Code' - except botocore.exceptions.ClientError as e: # pylint: disable=duplicate-except - caught_exception = e - caught = 'ClientError' - except botocore.exceptions.BotoCoreError as e: - caught_exception = e - caught = 'BotoCoreError' - except Exception as e: - caught_exception = e - caught = 'Exception' - self.assertEqual(caught_exception, thrown_exception) - self.assertEqual(caught, 'BotoCoreError') - - def test_is_boto3_error_code_multiple__raise__client(self): - thrown_exception = self._make_denied_exception() - caught_exception = None - try: - raise thrown_exception - except is_boto3_error_code(['NotAccessDenied', 'AccessDenied']) as e: - caught_exception = e - caught = 'Code' - except botocore.exceptions.ClientError as e: # pylint: disable=duplicate-except - caught_exception = e - caught = 'ClientError' - except botocore.exceptions.BotoCoreError as e: - caught_exception = e - caught = 'BotoCoreError' - except Exception as e: - caught_exception = e - caught = 'Exception' - self.assertEqual(caught_exception, thrown_exception) - self.assertEqual(caught, 'Code') - caught_exception = None - try: - raise thrown_exception - except is_boto3_error_code(['AccessDenied', 'NotAccessDenied']) as e: - caught_exception = e - caught = 'Code' - except botocore.exceptions.ClientError as e: # pylint: disable=duplicate-except - caught_exception = e - caught = 'ClientError' - except botocore.exceptions.BotoCoreError as e: - caught_exception = e - caught = 'BotoCoreError' - except Exception as e: - caught_exception = e - caught = 'Exception' - self.assertEqual(caught_exception, thrown_exception) - self.assertEqual(caught, 'Code') - - def test_is_boto3_error_code_multiple__raise__unexpected(self): - thrown_exception = self._make_unexpected_exception() - caught_exception = None - try: - raise thrown_exception - except is_boto3_error_code(['NotAccessDenied', 'AccessDenied']) as e: - caught_exception = e - caught = 'Code' - except botocore.exceptions.ClientError as e: # pylint: disable=duplicate-except - caught_exception = e - caught = 'ClientError' - except botocore.exceptions.BotoCoreError as e: - caught_exception = e - caught = 'BotoCoreError' - except Exception as e: - caught_exception = e - caught = 'Exception' - self.assertEqual(caught_exception, thrown_exception) - self.assertEqual(caught, 'ClientError') - - def test_is_boto3_error_code_multiple__raise__botocore(self): - thrown_exception = self._make_botocore_exception() - caught_exception = None - try: - raise thrown_exception - except is_boto3_error_code(['NotAccessDenied', 'AccessDenied']) as e: - caught_exception = e - caught = 'Code' - except botocore.exceptions.ClientError as e: # pylint: disable=duplicate-except - caught_exception = e - caught = 'ClientError' - except botocore.exceptions.BotoCoreError as e: - caught_exception = e - caught = 'BotoCoreError' - except Exception as e: - caught_exception = e - caught = 'Exception' - self.assertEqual(caught_exception, thrown_exception) - self.assertEqual(caught, 'BotoCoreError') - - def test_is_boto3_error_code_single__pass__client(self): - passed_exception = self._make_denied_exception() - returned_exception = is_boto3_error_code('AccessDenied', e=passed_exception) - self.assertTrue(isinstance(passed_exception, returned_exception)) - self.assertTrue(issubclass(returned_exception, botocore.exceptions.ClientError)) - self.assertFalse(issubclass(returned_exception, botocore.exceptions.BotoCoreError)) - self.assertTrue(issubclass(returned_exception, Exception)) - self.assertNotEqual(returned_exception.__name__, "NeverEverRaisedException") - - def test_is_boto3_error_code_single__pass__unexpected(self): - passed_exception = self._make_unexpected_exception() - returned_exception = is_boto3_error_code('AccessDenied', e=passed_exception) - self.assertFalse(isinstance(passed_exception, returned_exception)) - self.assertFalse(issubclass(returned_exception, botocore.exceptions.ClientError)) - self.assertFalse(issubclass(returned_exception, botocore.exceptions.BotoCoreError)) - self.assertTrue(issubclass(returned_exception, Exception)) - self.assertEqual(returned_exception.__name__, "NeverEverRaisedException") - - def test_is_boto3_error_code_single__pass__botocore(self): - passed_exception = self._make_botocore_exception() - returned_exception = is_boto3_error_code('AccessDenied', e=passed_exception) - self.assertFalse(isinstance(passed_exception, returned_exception)) - self.assertFalse(issubclass(returned_exception, botocore.exceptions.ClientError)) - self.assertFalse(issubclass(returned_exception, botocore.exceptions.BotoCoreError)) - self.assertTrue(issubclass(returned_exception, Exception)) - self.assertEqual(returned_exception.__name__, "NeverEverRaisedException") - - def test_is_boto3_error_code_multiple__pass__client(self): - passed_exception = self._make_denied_exception() - returned_exception = is_boto3_error_code(['NotAccessDenied', 'AccessDenied'], e=passed_exception) - self.assertTrue(isinstance(passed_exception, returned_exception)) - self.assertTrue(issubclass(returned_exception, botocore.exceptions.ClientError)) - self.assertFalse(issubclass(returned_exception, botocore.exceptions.BotoCoreError)) - self.assertTrue(issubclass(returned_exception, Exception)) - self.assertNotEqual(returned_exception.__name__, "NeverEverRaisedException") - returned_exception = is_boto3_error_code(['AccessDenied', 'NotAccessDenied'], e=passed_exception) - self.assertTrue(isinstance(passed_exception, returned_exception)) - self.assertTrue(issubclass(returned_exception, botocore.exceptions.ClientError)) - self.assertFalse(issubclass(returned_exception, botocore.exceptions.BotoCoreError)) - self.assertTrue(issubclass(returned_exception, Exception)) - self.assertNotEqual(returned_exception.__name__, "NeverEverRaisedException") - - def test_is_boto3_error_code_multiple__pass__unexpected(self): - passed_exception = self._make_unexpected_exception() - returned_exception = is_boto3_error_code(['NotAccessDenied', 'AccessDenied'], e=passed_exception) - self.assertFalse(isinstance(passed_exception, returned_exception)) - self.assertFalse(issubclass(returned_exception, botocore.exceptions.ClientError)) - self.assertFalse(issubclass(returned_exception, botocore.exceptions.BotoCoreError)) - self.assertTrue(issubclass(returned_exception, Exception)) - self.assertEqual(returned_exception.__name__, "NeverEverRaisedException") - - def test_is_boto3_error_code_multiple__pass__botocore(self): - passed_exception = self._make_botocore_exception() - returned_exception = is_boto3_error_code(['NotAccessDenied', 'AccessDenied'], e=passed_exception) - self.assertFalse(isinstance(passed_exception, returned_exception)) - self.assertFalse(issubclass(returned_exception, botocore.exceptions.ClientError)) - self.assertFalse(issubclass(returned_exception, botocore.exceptions.BotoCoreError)) - self.assertTrue(issubclass(returned_exception, Exception)) - self.assertEqual(returned_exception.__name__, "NeverEverRaisedException") diff --git a/tests/unit/module_utils/core/test_parse_aws_arn.py b/tests/unit/module_utils/core/test_parse_aws_arn.py deleted file mode 100644 index 48ba0a43caf..00000000000 --- a/tests/unit/module_utils/core/test_parse_aws_arn.py +++ /dev/null @@ -1,45 +0,0 @@ -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type - -import pytest - -from ansible_collections.amazon.aws.plugins.module_utils.core import parse_aws_arn - - -arn_data = [ - ("arn:aws-us-gov:iam::0123456789:role/foo-role", - dict(partition="aws-us-gov", account_id="0123456789", resource="role/foo-role", service="iam", region="") - ), - ("arn:aws:iam::123456789012:user/dev/*", - dict(partition="aws", account_id="123456789012", resource="user/dev/*", service='iam', region="") - ), - ("arn:aws:iam::123456789012:user:test", - dict(partition="aws", account_id="123456789012", resource="user:test", service="iam", region="") - ), - ("arn:aws-cn:iam::123456789012:user:test", - dict(partition="aws-cn", account_id="123456789012", resource="user:test", service="iam", region="") - ), - ("arn:aws:iam::123456789012:user", - dict(partition="aws", account_id="123456789012", resource="user", service="iam", region="") - ), - ("arn:aws:s3:::my_corporate_bucket/*", - dict(partition="aws", account_id="", resource="my_corporate_bucket/*", service="s3", region="") - ), - ("arn:aws:s3:::my_corporate_bucket/Development/*", - dict(partition="aws", account_id="", resource="my_corporate_bucket/Development/*", service="s3", region="") - ), - ("arn:aws:rds:es-east-1:000000000000:snapshot:rds:my-db-snapshot", - dict(partition="aws", account_id="000000000000", resource="snapshot:rds:my-db-snapshot", service="rds", region="es-east-1") - ), - ("arn:aws:cloudformation:us-east-1:012345678901:changeSet/Ansible-StackName-c6884247ede41eb0", - dict(partition="aws", account_id="012345678901", resource="changeSet/Ansible-StackName-c6884247ede41eb0", service="cloudformation", region="us-east-1") - ), - ("test:aws:iam::123456789012:user", None), - ("arn:aws::us-east-1:123456789012:user", None), -] - - -@pytest.mark.parametrize("input_params, output_params", arn_data) -def test_parse_aws_iam_arn(input_params, output_params): - - assert parse_aws_arn(input_params) == output_params diff --git a/tests/unit/module_utils/ec2/__init__.py b/tests/unit/module_utils/ec2/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/tests/unit/module_utils/core/ansible_aws_module/test_fail_json_aws.py b/tests/unit/module_utils/modules/ansible_aws_module/test_fail_json_aws.py similarity index 94% rename from tests/unit/module_utils/core/ansible_aws_module/test_fail_json_aws.py rename to tests/unit/module_utils/modules/ansible_aws_module/test_fail_json_aws.py index c7e53afca02..51e64490f9a 100644 --- a/tests/unit/module_utils/core/ansible_aws_module/test_fail_json_aws.py +++ b/tests/unit/module_utils/modules/ansible_aws_module/test_fail_json_aws.py @@ -6,16 +6,25 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type -import pytest -import botocore -import boto3 import json +import pytest + +try: + import botocore + import boto3 +except ImportError: + pass from ansible.module_utils.common.dict_transformations import camel_dict_to_snake_dict -from ansible_collections.amazon.aws.plugins.module_utils.core import AnsibleAWSModule + +from ansible_collections.amazon.aws.plugins.module_utils.botocore import HAS_BOTO3 +from ansible_collections.amazon.aws.plugins.module_utils.modules import AnsibleAWSModule + +if not HAS_BOTO3: + pytestmark = pytest.mark.skip("test_fail_json_aws.py requires the python modules 'boto3' and 'botocore'") -class TestFailJsonAws(object): +class TestFailJsonAwsTestSuite(object): # ======================================================== # Prepare some data for use in our testing # ======================================================== @@ -61,7 +70,7 @@ def test_fail_client_minimal(self, monkeypatch, stdin, capfd): with pytest.raises(SystemExit) as ctx: module.fail_json_aws(e) assert ctx.value.code == 1 - out, err = capfd.readouterr() + out, _err = capfd.readouterr() return_val = json.loads(out) assert return_val.get("msg") == self.EXAMPLE_MSG @@ -88,7 +97,7 @@ def test_fail_client_msg(self, monkeypatch, stdin, capfd): with pytest.raises(SystemExit) as ctx: module.fail_json_aws(e, msg=self.FAIL_MSG) assert ctx.value.code == 1 - out, err = capfd.readouterr() + out, _err = capfd.readouterr() return_val = json.loads(out) assert return_val.get("msg") == self.FAIL_MSG + ": " + self.EXAMPLE_MSG @@ -115,7 +124,7 @@ def test_fail_client_positional_msg(self, monkeypatch, stdin, capfd): with pytest.raises(SystemExit) as ctx: module.fail_json_aws(e, self.FAIL_MSG) assert ctx.value.code == 1 - out, err = capfd.readouterr() + out, _err = capfd.readouterr() return_val = json.loads(out) assert return_val.get("msg") == self.FAIL_MSG + ": " + self.EXAMPLE_MSG @@ -142,7 +151,7 @@ def test_fail_client_key(self, monkeypatch, stdin, capfd): with pytest.raises(SystemExit) as ctx: module.fail_json_aws(e, extra_key="Some Value") assert ctx.value.code == 1 - out, err = capfd.readouterr() + out, _err = capfd.readouterr() return_val = json.loads(out) assert return_val.get("msg") == self.EXAMPLE_MSG @@ -170,7 +179,7 @@ def test_fail_client_msg_and_key(self, monkeypatch, stdin, capfd): with pytest.raises(SystemExit) as ctx: module.fail_json_aws(e, extra_key="Some Value", msg=self.FAIL_MSG) assert ctx.value.code == 1 - out, err = capfd.readouterr() + out, _err = capfd.readouterr() return_val = json.loads(out) assert return_val.get("msg") == self.FAIL_MSG + ": " + self.EXAMPLE_MSG @@ -198,7 +207,7 @@ def test_fail_botocore_minimal(self, monkeypatch, stdin, capfd): with pytest.raises(SystemExit) as ctx: module.fail_json_aws(e) assert ctx.value.code == 1 - out, err = capfd.readouterr() + out, _err = capfd.readouterr() return_val = json.loads(out) assert return_val.get("msg") == self.DEFAULT_CORE_MSG @@ -225,7 +234,7 @@ def test_fail_botocore_msg(self, monkeypatch, stdin, capfd): with pytest.raises(SystemExit) as ctx: module.fail_json_aws(e, msg=self.FAIL_MSG) assert ctx.value.code == 1 - out, err = capfd.readouterr() + out, _err = capfd.readouterr() return_val = json.loads(out) assert return_val.get("msg") == self.FAIL_MSG + ": " + self.DEFAULT_CORE_MSG @@ -253,7 +262,7 @@ def test_fail_botocore_positional_msg(self, monkeypatch, stdin, capfd): with pytest.raises(SystemExit) as ctx: module.fail_json_aws(e, self.FAIL_MSG) assert ctx.value.code == 1 - out, err = capfd.readouterr() + out, _err = capfd.readouterr() return_val = json.loads(out) assert return_val.get("msg") == self.FAIL_MSG + ": " + self.DEFAULT_CORE_MSG @@ -280,7 +289,7 @@ def test_fail_botocore_key(self, monkeypatch, stdin, capfd): with pytest.raises(SystemExit) as ctx: module.fail_json_aws(e, extra_key="Some Value") assert ctx.value.code == 1 - out, err = capfd.readouterr() + out, _err = capfd.readouterr() return_val = json.loads(out) assert return_val.get("msg") == self.DEFAULT_CORE_MSG @@ -308,7 +317,7 @@ def test_fail_botocore_msg_and_key(self, monkeypatch, stdin, capfd): with pytest.raises(SystemExit) as ctx: module.fail_json_aws(e, extra_key="Some Value", msg=self.FAIL_MSG) assert ctx.value.code == 1 - out, err = capfd.readouterr() + out, _err = capfd.readouterr() return_val = json.loads(out) assert return_val.get("msg") == self.FAIL_MSG + ": " + self.DEFAULT_CORE_MSG diff --git a/tests/unit/module_utils/core/ansible_aws_module/test_minimal_versions.py b/tests/unit/module_utils/modules/ansible_aws_module/test_minimal_versions.py similarity index 90% rename from tests/unit/module_utils/core/ansible_aws_module/test_minimal_versions.py rename to tests/unit/module_utils/modules/ansible_aws_module/test_minimal_versions.py index c367a18181f..ba2f563e30b 100644 --- a/tests/unit/module_utils/core/ansible_aws_module/test_minimal_versions.py +++ b/tests/unit/module_utils/modules/ansible_aws_module/test_minimal_versions.py @@ -6,16 +6,24 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type +from pprint import pprint import pytest -import botocore -import boto3 import json -from ansible_collections.amazon.aws.plugins.module_utils.core import AnsibleAWSModule -from pprint import pprint +try: + import botocore + import boto3 +except ImportError: + pass + +from ansible_collections.amazon.aws.plugins.module_utils.botocore import HAS_BOTO3 +from ansible_collections.amazon.aws.plugins.module_utils.modules import AnsibleAWSModule +if not HAS_BOTO3: + pytestmark = pytest.mark.skip("test_minimal_versions.py requires the python modules 'boto3' and 'botocore'") -class TestMinimalVersions(object): + +class TestMinimalVersionTestSuite(object): # ======================================================== # Prepare some data for use in our testing # ======================================================== @@ -36,10 +44,10 @@ def test_no_warn(self, monkeypatch, stdin, capfd): # Create a minimal module that we can call module = AnsibleAWSModule(argument_spec=dict()) - with pytest.raises(SystemExit) as e: + with pytest.raises(SystemExit): module.exit_json() - out, err = capfd.readouterr() + out, _err = capfd.readouterr() return_val = json.loads(out) assert return_val.get("exception") is None @@ -59,10 +67,10 @@ def test_no_check(self, monkeypatch, stdin, capfd): # Create a minimal module that we can call module = AnsibleAWSModule(argument_spec=dict(), check_boto3=False) - with pytest.raises(SystemExit) as e: + with pytest.raises(SystemExit): module.exit_json() - out, err = capfd.readouterr() + out, _err = capfd.readouterr() return_val = json.loads(out) assert return_val.get("exception") is None @@ -82,7 +90,7 @@ def test_warn_boto3(self, monkeypatch, stdin, capfd): # Create a minimal module that we can call module = AnsibleAWSModule(argument_spec=dict()) - with pytest.raises(SystemExit) as e: + with pytest.raises(SystemExit): module.exit_json() out, err = capfd.readouterr() @@ -115,7 +123,7 @@ def test_warn_botocore(self, monkeypatch, stdin, capfd): # Create a minimal module that we can call module = AnsibleAWSModule(argument_spec=dict()) - with pytest.raises(SystemExit) as e: + with pytest.raises(SystemExit): module.exit_json() out, err = capfd.readouterr() @@ -148,7 +156,7 @@ def test_warn_boto3_and_botocore(self, monkeypatch, stdin, capfd): # Create a minimal module that we can call module = AnsibleAWSModule(argument_spec=dict()) - with pytest.raises(SystemExit) as e: + with pytest.raises(SystemExit): module.exit_json() out, err = capfd.readouterr() diff --git a/tests/unit/module_utils/core/ansible_aws_module/test_require_at_least.py b/tests/unit/module_utils/modules/ansible_aws_module/test_require_at_least.py similarity index 92% rename from tests/unit/module_utils/core/ansible_aws_module/test_require_at_least.py rename to tests/unit/module_utils/modules/ansible_aws_module/test_require_at_least.py index c0e0e5b6c00..adf2bf558e5 100644 --- a/tests/unit/module_utils/core/ansible_aws_module/test_require_at_least.py +++ b/tests/unit/module_utils/modules/ansible_aws_module/test_require_at_least.py @@ -6,12 +6,18 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type -import pytest -import botocore -import boto3 import json +import pytest -from ansible_collections.amazon.aws.plugins.module_utils.core import AnsibleAWSModule +try: + import botocore + import boto3 +except ImportError: + # Handled by HAS_BOTO3 + pass + +from ansible_collections.amazon.aws.plugins.module_utils.botocore import HAS_BOTO3 +from ansible_collections.amazon.aws.plugins.module_utils.modules import AnsibleAWSModule DUMMY_VERSION = '5.5.5.5' @@ -34,8 +40,11 @@ ['10.10.10', '9.19.10', False], ] +if not HAS_BOTO3: + pytestmark = pytest.mark.skip("test_require_at_least.py requires the python modules 'boto3' and 'botocore'") + -class TestRequireAtLeast(object): +class TestRequireAtLeastTestSuite(object): # ======================================================== # Prepare some data for use in our testing # ======================================================== @@ -85,11 +94,11 @@ def test_require_botocore_at_least(self, monkeypatch, stdin, desired_version, co # Create a minimal module that we can call module = AnsibleAWSModule(argument_spec=dict()) - with pytest.raises(SystemExit) as e: + with pytest.raises(SystemExit): module.require_botocore_at_least(desired_version) module.exit_json() - out, err = capfd.readouterr() + out, _err = capfd.readouterr() return_val = json.loads(out) assert return_val.get("exception") is None @@ -118,11 +127,11 @@ def test_require_boto3_at_least(self, monkeypatch, stdin, desired_version, compa # Create a minimal module that we can call module = AnsibleAWSModule(argument_spec=dict()) - with pytest.raises(SystemExit) as e: + with pytest.raises(SystemExit): module.require_boto3_at_least(desired_version) module.exit_json() - out, err = capfd.readouterr() + out, _err = capfd.readouterr() return_val = json.loads(out) assert return_val.get("exception") is None @@ -153,11 +162,11 @@ def test_require_botocore_at_least_with_reason(self, monkeypatch, stdin, desired # Create a minimal module that we can call module = AnsibleAWSModule(argument_spec=dict()) - with pytest.raises(SystemExit) as e: + with pytest.raises(SystemExit): module.require_botocore_at_least(desired_version, reason=reason) module.exit_json() - out, err = capfd.readouterr() + out, _err = capfd.readouterr() return_val = json.loads(out) assert return_val.get("exception") is None @@ -189,11 +198,11 @@ def test_require_boto3_at_least_with_reason(self, monkeypatch, stdin, desired_ve # Create a minimal module that we can call module = AnsibleAWSModule(argument_spec=dict()) - with pytest.raises(SystemExit) as e: + with pytest.raises(SystemExit): module.require_boto3_at_least(desired_version, reason=reason) module.exit_json() - out, err = capfd.readouterr() + out, _err = capfd.readouterr() return_val = json.loads(out) assert return_val.get("exception") is None diff --git a/tests/unit/module_utils/policy/test_compare_policies.py b/tests/unit/module_utils/policy/test_compare_policies.py index 06cd180a27f..82d347cfa06 100644 --- a/tests/unit/module_utils/policy/test_compare_policies.py +++ b/tests/unit/module_utils/policy/test_compare_policies.py @@ -6,17 +6,15 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type -import unittest - from ansible_collections.amazon.aws.plugins.module_utils.policy import compare_policies -class PolicyUtils(unittest.TestCase): +class CompartPolicyTestSuite(): # ======================================================== # Setup some initial data that we can use within our tests # ======================================================== - def setUp(self): + def setup_method(self): # A pair of simple IAM Trust relationships using bools, the first a # native bool the second a quoted string self.bool_policy_bool = { @@ -275,7 +273,7 @@ def test_compare_small_policies_without_differences(self): * The contents of the statement are in different orders * The second policy contains a list of length one whereas in the first it is a string """ - self.assertFalse(compare_policies(self.small_policy_one, self.small_policy_two)) + assert compare_policies(self.small_policy_one, self.small_policy_two) is False def test_compare_large_policies_without_differences(self): """ Testing two larger policies which are identical except for: @@ -283,59 +281,59 @@ def test_compare_large_policies_without_differences(self): * The contents of the statements are also in different orders * The second contains a list of length one for the Principal whereas in the first it is a string """ - self.assertFalse(compare_policies(self.larger_policy_one, self.larger_policy_two)) + assert compare_policies(self.larger_policy_one, self.larger_policy_two) is False def test_compare_larger_policies_with_difference(self): """ Testing two larger policies which are identical except for: * one different principal """ - self.assertTrue(compare_policies(self.larger_policy_two, self.larger_policy_three)) + assert compare_policies(self.larger_policy_two, self.larger_policy_three) is True def test_compare_smaller_policy_with_larger(self): """ Testing two policies of different sizes """ - self.assertTrue(compare_policies(self.larger_policy_one, self.small_policy_one)) + assert compare_policies(self.larger_policy_one, self.small_policy_one) is True def test_compare_boolean_policy_bool_and_string_are_equal(self): """ Testing two policies one using a quoted boolean, the other a bool """ - self.assertFalse(compare_policies(self.bool_policy_string, self.bool_policy_bool)) + assert compare_policies(self.bool_policy_string, self.bool_policy_bool) is False def test_compare_numeric_policy_number_and_string_are_equal(self): """ Testing two policies one using a quoted number, the other an int """ - self.assertFalse(compare_policies(self.numeric_policy_string, self.numeric_policy_number)) + assert compare_policies(self.numeric_policy_string, self.numeric_policy_number) is False def test_compare_version_policies_defaults_old(self): """ Testing that a policy without Version is considered identical to one with the 'old' Version (by default) """ - self.assertFalse(compare_policies(self.version_policy_old, self.version_policy_missing)) - self.assertTrue(compare_policies(self.version_policy_new, self.version_policy_missing)) + assert compare_policies(self.version_policy_old, self.version_policy_missing) is False + assert compare_policies(self.version_policy_new, self.version_policy_missing) is True def test_compare_version_policies_default_disabled(self): """ Testing that a policy without Version not considered identical when default_version=None """ - self.assertFalse(compare_policies(self.version_policy_missing, self.version_policy_missing, default_version=None)) - self.assertTrue(compare_policies(self.version_policy_old, self.version_policy_missing, default_version=None)) - self.assertTrue(compare_policies(self.version_policy_new, self.version_policy_missing, default_version=None)) + assert compare_policies(self.version_policy_missing, self.version_policy_missing, default_version=None) is False + assert compare_policies(self.version_policy_old, self.version_policy_missing, default_version=None) is True + assert compare_policies(self.version_policy_new, self.version_policy_missing, default_version=None) is True def test_compare_version_policies_default_set(self): """ Testing that a policy without Version is only considered identical when default_version="2008-10-17" """ - self.assertFalse(compare_policies(self.version_policy_missing, self.version_policy_missing, default_version="2012-10-17")) - self.assertTrue(compare_policies(self.version_policy_old, self.version_policy_missing, default_version="2012-10-17")) - self.assertFalse(compare_policies(self.version_policy_old, self.version_policy_missing, default_version="2008-10-17")) - self.assertFalse(compare_policies(self.version_policy_new, self.version_policy_missing, default_version="2012-10-17")) - self.assertTrue(compare_policies(self.version_policy_new, self.version_policy_missing, default_version="2008-10-17")) + assert compare_policies(self.version_policy_missing, self.version_policy_missing, default_version="2012-10-17") is False + assert compare_policies(self.version_policy_old, self.version_policy_missing, default_version="2012-10-17") is True + assert compare_policies(self.version_policy_old, self.version_policy_missing, default_version="2008-10-17") is False + assert compare_policies(self.version_policy_new, self.version_policy_missing, default_version="2012-10-17") is False + assert compare_policies(self.version_policy_new, self.version_policy_missing, default_version="2008-10-17") is True def test_compare_version_policies_with_none(self): """ Testing that comparing with no policy works """ - self.assertTrue(compare_policies(self.small_policy_one, None)) - self.assertTrue(compare_policies(None, self.small_policy_one)) - self.assertFalse(compare_policies(None, None)) + assert compare_policies(self.small_policy_one, None) is True + assert compare_policies(None, self.small_policy_one) is True + assert compare_policies(None, None) is False def test_compare_wildcard_policies_without_differences(self): """ Testing two small wildcard policies which are identical except for: * Principal: "*" vs Principal: ["AWS": "*"] """ - self.assertFalse(compare_policies(self.wildcard_policy_one, self.wildcard_policy_two)) + assert compare_policies(self.wildcard_policy_one, self.wildcard_policy_two) is False diff --git a/tests/unit/module_utils/retries/test_awsretry.py b/tests/unit/module_utils/retries/test_awsretry.py new file mode 100644 index 00000000000..ddb4dc3aa4b --- /dev/null +++ b/tests/unit/module_utils/retries/test_awsretry.py @@ -0,0 +1,96 @@ +# -*- coding: utf-8 -*- +# (c) 2015, Allen Sanabria +# +# This file is part of Ansible +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +try: + import botocore +except ImportError: + pass + +import pytest + +from ansible_collections.amazon.aws.plugins.module_utils.retries import AWSRetry +from ansible_collections.amazon.aws.plugins.module_utils.botocore import HAS_BOTO3 + +if not HAS_BOTO3: + pytestmark = pytest.mark.skip("test_awsretry.py requires the python modules 'boto3' and 'botocore'") + + +class AWSRetryTestSuite(): + + def test_no_failures(self): + self.counter = 0 + + @AWSRetry.backoff(tries=2, delay=0.1) + def no_failures(): + self.counter += 1 + + no_failures() + assert self.counter == 1 + + def test_extend_boto3_failures(self): + self.counter = 0 + err_response = {'Error': {'Code': 'MalformedPolicyDocument'}} + + @AWSRetry.backoff(tries=2, delay=0.1, catch_extra_error_codes=['MalformedPolicyDocument']) + def extend_failures(): + self.counter += 1 + if self.counter < 2: + raise botocore.exceptions.ClientError(err_response, 'You did something wrong.') + else: + return 'success' + + result = extend_failures() + assert result == 'success' + assert self.counter == 2 + + def test_retry_once(self): + self.counter = 0 + err_response = {'Error': {'Code': 'InternalFailure'}} + + @AWSRetry.backoff(tries=2, delay=0.1) + def retry_once(): + self.counter += 1 + if self.counter < 2: + raise botocore.exceptions.ClientError(err_response, 'Something went wrong!') + else: + return 'success' + + result = retry_once() + assert result == 'success' + assert self.counter == 2 + + def test_reached_limit(self): + self.counter = 0 + err_response = {'Error': {'Code': 'RequestLimitExceeded'}} + + @AWSRetry.backoff(tries=4, delay=0.1) + def fail(): + self.counter += 1 + raise botocore.exceptions.ClientError(err_response, 'toooo fast!!') + + with pytest.raises(botocore.exceptions.ClientError) as context: + fail() + response = context.value.response + assert response['Error']['Code'] == 'RequestLimitExceeded' + assert self.counter == 4 + + def test_unexpected_exception_does_not_retry(self): + self.counter = 0 + err_response = {'Error': {'Code': 'AuthFailure'}} + + @AWSRetry.backoff(tries=4, delay=0.1) + def raise_unexpected_error(): + self.counter += 1 + raise botocore.exceptions.ClientError(err_response, 'unexpected error') + + with pytest.raises(botocore.exceptions.ClientError) as context: + raise_unexpected_error() + response = context.value.response + assert response['Error']['Code'] == 'AuthFailure' + assert self.counter == 1 diff --git a/tests/unit/module_utils/test_elbv2.py b/tests/unit/module_utils/test_elbv2.py index c827bf41542..c07d89a102a 100644 --- a/tests/unit/module_utils/test_elbv2.py +++ b/tests/unit/module_utils/test_elbv2.py @@ -8,7 +8,6 @@ __metaclass__ = type from ansible_collections.amazon.aws.plugins.module_utils import elbv2 -from ansible_collections.amazon.aws.tests.unit.compat import unittest from ansible_collections.amazon.aws.tests.unit.compat.mock import MagicMock one_action = [ @@ -65,9 +64,9 @@ def _sort_actions_one_entry(): assert elbv2._sort_actions(one_action) == one_action -class ElBV2UtilsTestSuite(unittest.TestCase): +class ElBV2UtilsTestSuite(): - def setUp(self): + def setup_method(self): self.connection = MagicMock(name="connection") self.module = MagicMock(name="module") @@ -187,25 +186,25 @@ def test_get_elb_ip_address_type(self): self.connection.describe_tags.assert_called_once() self.conn_paginator.paginate.assert_called_once() # assert we got the expected value - self.assertEqual(return_value, 'ipv4') + assert return_value == 'ipv4' # Test modify_ip_address_type idempotency def test_modify_ip_address_type_idempotency(self): # Run module - return_value = self.elbv2obj.modify_ip_address_type("ipv4") + self.elbv2obj.modify_ip_address_type("ipv4") # check that no method was called and this has been retrieved from elb attributes self.connection.set_ip_address_type.assert_not_called() # assert we got the expected value - self.assertEqual(self.elbv2obj.changed, False) + assert self.elbv2obj.changed is False # Test modify_ip_address_type def test_modify_ip_address_type_update(self): # Run module - return_value = self.elbv2obj.modify_ip_address_type("dualstack") + self.elbv2obj.modify_ip_address_type("dualstack") # check that no method was called and this has been retrieved from elb attributes self.connection.set_ip_address_type.assert_called_once() # assert we got the expected value - self.assertEqual(self.elbv2obj.changed, True) + assert self.elbv2obj.changed is True # Test get_elb_attributes def test_get_elb_attributes(self): @@ -226,4 +225,4 @@ def test_get_elb_attributes(self): # Run module actual_elb_attributes = self.elbv2obj.get_elb_attributes() # Assert we got the expected result - self.assertEqual(actual_elb_attributes, expected_elb_attributes) + assert actual_elb_attributes == expected_elb_attributes diff --git a/tests/unit/module_utils/test_iam.py b/tests/unit/module_utils/test_iam.py index 0bfa7484040..56071da7c32 100644 --- a/tests/unit/module_utils/test_iam.py +++ b/tests/unit/module_utils/test_iam.py @@ -8,10 +8,14 @@ __metaclass__ = type import pytest -import botocore + +try: + import botocore +except ImportError: + # Handled by HAS_BOTO3 + pass from ansible_collections.amazon.aws.tests.unit.compat.mock import MagicMock -from ansible_collections.amazon.aws.tests.unit.compat import unittest import ansible_collections.amazon.aws.plugins.module_utils.iam as utils_iam from ansible_collections.amazon.aws.plugins.module_utils.ec2 import HAS_BOTO3 @@ -20,7 +24,7 @@ pytestmark = pytest.mark.skip("test_iam.py requires the python modules 'boto3' and 'botocore'") -class IamUtilsTestSuite(unittest.TestCase): +class IamUtilsTestSuite(): def _make_denied_exception(self, partition): return botocore.exceptions.ClientError( diff --git a/tests/unit/module_utils/test_rds.py b/tests/unit/module_utils/test_rds.py index 9e2d2664494..c133eefcff5 100644 --- a/tests/unit/module_utils/test_rds.py +++ b/tests/unit/module_utils/test_rds.py @@ -4,21 +4,25 @@ # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import absolute_import, division, print_function - __metaclass__ = type -from ansible_collections.amazon.aws.plugins.module_utils import rds -from ansible_collections.amazon.aws.tests.unit.compat import unittest -from ansible_collections.amazon.aws.tests.unit.compat.mock import MagicMock - from contextlib import nullcontext import pytest try: - from botocore.exceptions import ClientError, WaiterError + import botocore except ImportError: + # Handled by HAS_BOTO3 pass +from ansible_collections.amazon.aws.tests.unit.compat.mock import MagicMock + +from ansible_collections.amazon.aws.plugins.module_utils import rds +from ansible_collections.amazon.aws.plugins.module_utils.botocore import HAS_BOTO3 + +if not HAS_BOTO3: + pytestmark = pytest.mark.skip("test_rds.py requires the python modules 'boto3' and 'botocore'") + def expected(x): return x, nullcontext() @@ -31,6 +35,10 @@ def error(*args, **kwargs): def build_exception( operation_name, code=None, message=None, http_status_code=None, error=True ): + # Support skipping the test is botocore isn't installed + # (called by parametrize before skip is evaluated) + if not HAS_BOTO3: + return Exception('MissingBotoCore') response = {} if error or code or message: response["Error"] = {} @@ -40,7 +48,8 @@ def build_exception( response["Error"]["Message"] = message if http_status_code: response["ResponseMetadata"] = {"HTTPStatusCode": http_status_code} - return ClientError(response, operation_name) + + return botocore.exceptions.ClientError(response, operation_name) @pytest.mark.parametrize("waiter_name", ["", "db_snapshot_available"]) @@ -66,7 +75,7 @@ def test__wait_for_cluster_snapshot_status(waiter_name): ], ) def test__wait_for_instance_snapshot_status_failed(input, expected): - spec = {"get_waiter.side_effect": [WaiterError(None, None, None)]} + spec = {"get_waiter.side_effect": [botocore.exceptions.WaiterError(None, None, None)]} client = MagicMock(**spec) module = MagicMock() @@ -89,7 +98,7 @@ def test__wait_for_instance_snapshot_status_failed(input, expected): ], ) def test__wait_for_cluster_snapshot_status_failed(input, expected): - spec = {"get_waiter.side_effect": [WaiterError(None, None, None)]} + spec = {"get_waiter.side_effect": [botocore.exceptions.WaiterError(None, None, None)]} client = MagicMock(**spec) module = MagicMock() @@ -713,7 +722,7 @@ def test__handle_errors_failed(method_name, exception, expected, error): module.fail_json_aws.call_args[1]["msg"] == expected -class RdsUtils(unittest.TestCase): +class RdsUtilsTestSuite(): # ======================================================== # Setup some initial data that we can use within our tests @@ -741,28 +750,28 @@ def setUp(self): def test_compare_iam_roles_equal(self): existing_list = self.target_role_list roles_to_add, roles_to_delete = rds.compare_iam_roles(existing_list, self.target_role_list, purge_roles=False) - self.assertEqual([], roles_to_add) - self.assertEqual([], roles_to_delete) + assert [] == roles_to_add + assert [] == roles_to_delete roles_to_add, roles_to_delete = rds.compare_iam_roles(existing_list, self.target_role_list, purge_roles=True) - self.assertEqual([], roles_to_add) - self.assertEqual([], roles_to_delete) + assert [] == roles_to_add + assert [] == roles_to_delete def test_compare_iam_roles_empty_arr_existing(self): roles_to_add, roles_to_delete = rds.compare_iam_roles([], self.target_role_list, purge_roles=False) - self.assertEqual(self.target_role_list, roles_to_add) - self.assertEqual([], roles_to_delete) + assert self.target_role_list == roles_to_add + assert [] == roles_to_delete roles_to_add, roles_to_delete = rds.compare_iam_roles([], self.target_role_list, purge_roles=True) - self.assertEqual(self.target_role_list, roles_to_add) - self.assertEqual([], roles_to_delete) + assert self.target_role_list, roles_to_add + assert [] == roles_to_delete def test_compare_iam_roles_empty_arr_target(self): existing_list = self.target_role_list roles_to_add, roles_to_delete = rds.compare_iam_roles(existing_list, [], purge_roles=False) - self.assertEqual([], roles_to_add) - self.assertEqual([], roles_to_delete) + assert [] == roles_to_add + assert [] == roles_to_delete roles_to_add, roles_to_delete = rds.compare_iam_roles(existing_list, [], purge_roles=True) - self.assertEqual([], roles_to_add) - self.assertEqual(self.target_role_list, roles_to_delete) + assert [] == roles_to_add + assert self.target_role_list == roles_to_delete def test_compare_iam_roles_different(self): existing_list = [ @@ -771,11 +780,11 @@ def test_compare_iam_roles_different(self): 'feature_name': 's3Export' }] roles_to_add, roles_to_delete = rds.compare_iam_roles(existing_list, self.target_role_list, purge_roles=False) - self.assertEqual(self.target_role_list, roles_to_add) - self.assertEqual([], roles_to_delete) + assert self.target_role_list == roles_to_add + assert [] == roles_to_delete roles_to_add, roles_to_delete = rds.compare_iam_roles(existing_list, self.target_role_list, purge_roles=True) - self.assertEqual(self.target_role_list, roles_to_add) - self.assertEqual(existing_list, roles_to_delete) + assert self.target_role_list == roles_to_add + assert existing_list == roles_to_delete existing_list = self.target_role_list.copy() self.target_role_list = [ @@ -784,8 +793,8 @@ def test_compare_iam_roles_different(self): 'feature_name': 's3Export' }] roles_to_add, roles_to_delete = rds.compare_iam_roles(existing_list, self.target_role_list, purge_roles=False) - self.assertEqual(self.target_role_list, roles_to_add) - self.assertEqual([], roles_to_delete) + assert self.target_role_list == roles_to_add + assert [] == roles_to_delete roles_to_add, roles_to_delete = rds.compare_iam_roles(existing_list, self.target_role_list, purge_roles=True) - self.assertEqual(self.target_role_list, roles_to_add) - self.assertEqual(existing_list, roles_to_delete) + assert self.target_role_list == roles_to_add + assert existing_list == roles_to_delete diff --git a/tests/unit/module_utils/test_retries.py b/tests/unit/module_utils/test_retries.py deleted file mode 100644 index 2655bcd0692..00000000000 --- a/tests/unit/module_utils/test_retries.py +++ /dev/null @@ -1,103 +0,0 @@ -# -*- coding: utf-8 -*- -# (c) 2015, Allen Sanabria -# -# This file is part of Ansible -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type - -try: - # We explicitly want to know if boto3/botocore are available, they're used - # by the code we're testing even if we don't directly use them. - import boto3 # pylint: disable=unused-import - import botocore - HAS_BOTO3 = True -except Exception: - HAS_BOTO3 = False - -import pytest - -from ansible_collections.amazon.aws.tests.unit.compat import unittest -from ansible_collections.amazon.aws.plugins.module_utils.retries import AWSRetry - -if not HAS_BOTO3: - pytestmark = pytest.mark.skip("test_aws.py requires the python modules 'boto3' and 'botocore'") - - -class RetryTestCase(unittest.TestCase): - - def test_no_failures(self): - self.counter = 0 - - @AWSRetry.backoff(tries=2, delay=0.1) - def no_failures(): - self.counter += 1 - - r = no_failures() - self.assertEqual(self.counter, 1) - - def test_extend_boto3_failures(self): - self.counter = 0 - err_msg = {'Error': {'Code': 'MalformedPolicyDocument'}} - - @AWSRetry.backoff(tries=2, delay=0.1, catch_extra_error_codes=['MalformedPolicyDocument']) - def extend_failures(): - self.counter += 1 - if self.counter < 2: - raise botocore.exceptions.ClientError(err_msg, 'You did something wrong.') - else: - return 'success' - - r = extend_failures() - self.assertEqual(r, 'success') - self.assertEqual(self.counter, 2) - - def test_retry_once(self): - self.counter = 0 - err_msg = {'Error': {'Code': 'InternalFailure'}} - - @AWSRetry.backoff(tries=2, delay=0.1) - def retry_once(): - self.counter += 1 - if self.counter < 2: - raise botocore.exceptions.ClientError(err_msg, 'Something went wrong!') - else: - return 'success' - - r = retry_once() - self.assertEqual(r, 'success') - self.assertEqual(self.counter, 2) - - def test_reached_limit(self): - self.counter = 0 - err_msg = {'Error': {'Code': 'RequestLimitExceeded'}} - - @AWSRetry.backoff(tries=4, delay=0.1) - def fail(): - self.counter += 1 - raise botocore.exceptions.ClientError(err_msg, 'toooo fast!!') - - # with self.assertRaises(botocore.exceptions.ClientError): - try: - fail() - except Exception as e: - self.assertEqual(e.response['Error']['Code'], 'RequestLimitExceeded') - self.assertEqual(self.counter, 4) - - def test_unexpected_exception_does_not_retry(self): - self.counter = 0 - err_msg = {'Error': {'Code': 'AuthFailure'}} - - @AWSRetry.backoff(tries=4, delay=0.1) - def raise_unexpected_error(): - self.counter += 1 - raise botocore.exceptions.ClientError(err_msg, 'unexpected error') - - # with self.assertRaises(botocore.exceptions.ClientError): - try: - raise_unexpected_error() - except Exception as e: - self.assertEqual(e.response['Error']['Code'], 'AuthFailure') - - self.assertEqual(self.counter, 1) diff --git a/tests/unit/module_utils/test_tagging.py b/tests/unit/module_utils/test_tagging.py index 5f7e81236a9..8880b82966c 100644 --- a/tests/unit/module_utils/test_tagging.py +++ b/tests/unit/module_utils/test_tagging.py @@ -6,20 +6,18 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type -import unittest - from ansible_collections.amazon.aws.plugins.module_utils.tagging import ansible_dict_to_boto3_tag_list from ansible_collections.amazon.aws.plugins.module_utils.tagging import boto3_tag_list_to_ansible_dict from ansible_collections.amazon.aws.plugins.module_utils.tagging import boto3_tag_specifications from ansible_collections.amazon.aws.plugins.module_utils.tagging import compare_aws_tags -class Ec2Utils(unittest.TestCase): +class TaggingTestSuite(): # ======================================================== # Setup some initial data that we can use within our tests # ======================================================== - def setUp(self): + def setup_method(self): self.tag_example_boto3_list = [ {'Key': 'lowerCamel', 'Value': 'lowerCamelValue'}, @@ -52,7 +50,7 @@ def test_ansible_dict_to_boto3_tag_list(self): converted_list = ansible_dict_to_boto3_tag_list(self.tag_example_dict) sorted_converted_list = sorted(converted_list, key=lambda i: (i['Key'])) sorted_list = sorted(self.tag_example_boto3_list, key=lambda i: (i['Key'])) - self.assertEqual(sorted_converted_list, sorted_list) + assert sorted_converted_list != sorted_list # ======================================================== # tagging.boto3_tag_list_to_ansible_dict @@ -60,13 +58,13 @@ def test_ansible_dict_to_boto3_tag_list(self): def test_boto3_tag_list_to_ansible_dict(self): converted_dict = boto3_tag_list_to_ansible_dict(self.tag_example_boto3_list) - self.assertEqual(converted_dict, self.tag_example_dict) + assert converted_dict == self.tag_example_dict def test_boto3_tag_list_to_ansible_dict_empty(self): # AWS returns [] when there are no tags - self.assertEqual(boto3_tag_list_to_ansible_dict([]), {}) + assert boto3_tag_list_to_ansible_dict([]) == {} # Minio returns [{}] when there are no tags - self.assertEqual(boto3_tag_list_to_ansible_dict([{}]), {}) + assert boto3_tag_list_to_ansible_dict([{}]) == {} # ======================================================== # tagging.compare_aws_tags @@ -75,56 +73,56 @@ def test_boto3_tag_list_to_ansible_dict_empty(self): def test_compare_aws_tags_equal(self): new_dict = dict(self.tag_example_dict) keys_to_set, keys_to_unset = compare_aws_tags(self.tag_example_dict, new_dict) - self.assertEqual({}, keys_to_set) - self.assertEqual([], keys_to_unset) + assert {} == keys_to_set + assert [] == keys_to_unset keys_to_set, keys_to_unset = compare_aws_tags(self.tag_example_dict, new_dict, purge_tags=False) - self.assertEqual({}, keys_to_set) - self.assertEqual([], keys_to_unset) + assert {} == keys_to_set + assert [] == keys_to_unset keys_to_set, keys_to_unset = compare_aws_tags(self.tag_example_dict, new_dict, purge_tags=True) - self.assertEqual({}, keys_to_set) - self.assertEqual([], keys_to_unset) + assert {} == keys_to_set + assert [] == keys_to_unset def test_compare_aws_tags_removed(self): new_dict = dict(self.tag_example_dict) del new_dict['lowerCamel'] del new_dict['Normal case'] keys_to_set, keys_to_unset = compare_aws_tags(self.tag_example_dict, new_dict) - self.assertEqual({}, keys_to_set) - self.assertEqual(set(['lowerCamel', 'Normal case']), set(keys_to_unset)) + assert {} == keys_to_set + assert set(['lowerCamel', 'Normal case']) == set(keys_to_unset) keys_to_set, keys_to_unset = compare_aws_tags(self.tag_example_dict, new_dict, purge_tags=False) - self.assertEqual({}, keys_to_set) - self.assertEqual([], keys_to_unset) + assert {} == keys_to_set + assert [] == keys_to_unset keys_to_set, keys_to_unset = compare_aws_tags(self.tag_example_dict, new_dict, purge_tags=True) - self.assertEqual({}, keys_to_set) - self.assertEqual(set(['lowerCamel', 'Normal case']), set(keys_to_unset)) + assert {} == keys_to_set + assert set(['lowerCamel', 'Normal case']) == set(keys_to_unset) def test_compare_aws_tags_added(self): new_dict = dict(self.tag_example_dict) new_keys = {'add_me': 'lower case', 'Me too!': 'Contributing'} new_dict.update(new_keys) keys_to_set, keys_to_unset = compare_aws_tags(self.tag_example_dict, new_dict) - self.assertEqual(new_keys, keys_to_set) - self.assertEqual([], keys_to_unset) + assert new_keys == keys_to_set + assert [] == keys_to_unset keys_to_set, keys_to_unset = compare_aws_tags(self.tag_example_dict, new_dict, purge_tags=False) - self.assertEqual(new_keys, keys_to_set) - self.assertEqual([], keys_to_unset) + assert new_keys == keys_to_set + assert [] == keys_to_unset keys_to_set, keys_to_unset = compare_aws_tags(self.tag_example_dict, new_dict, purge_tags=True) - self.assertEqual(new_keys, keys_to_set) - self.assertEqual([], keys_to_unset) + assert new_keys == keys_to_set + assert [] == keys_to_unset def test_compare_aws_tags_changed(self): new_dict = dict(self.tag_example_dict) new_keys = {'UpperCamel': 'anotherCamelValue', 'Normal case': 'normal value'} new_dict.update(new_keys) keys_to_set, keys_to_unset = compare_aws_tags(self.tag_example_dict, new_dict) - self.assertEqual(new_keys, keys_to_set) - self.assertEqual([], keys_to_unset) + assert new_keys == keys_to_set + assert [] == keys_to_unset keys_to_set, keys_to_unset = compare_aws_tags(self.tag_example_dict, new_dict, purge_tags=False) - self.assertEqual(new_keys, keys_to_set) - self.assertEqual([], keys_to_unset) + assert new_keys == keys_to_set + assert [] == keys_to_unset keys_to_set, keys_to_unset = compare_aws_tags(self.tag_example_dict, new_dict, purge_tags=True) - self.assertEqual(new_keys, keys_to_set) - self.assertEqual([], keys_to_unset) + assert new_keys == keys_to_set + assert [] == keys_to_unset def test_compare_aws_tags_complex_update(self): # Adds 'Me too!', Changes 'UpperCamel' and removes 'Normal case' @@ -133,27 +131,27 @@ def test_compare_aws_tags_complex_update(self): new_dict.update(new_keys) del new_dict['Normal case'] keys_to_set, keys_to_unset = compare_aws_tags(self.tag_example_dict, new_dict) - self.assertEqual(new_keys, keys_to_set) - self.assertEqual(['Normal case'], keys_to_unset) + assert new_keys == keys_to_set + assert ['Normal case'] == keys_to_unset keys_to_set, keys_to_unset = compare_aws_tags(self.tag_example_dict, new_dict, purge_tags=False) - self.assertEqual(new_keys, keys_to_set) - self.assertEqual([], keys_to_unset) + assert new_keys == keys_to_set + assert [] == keys_to_unset keys_to_set, keys_to_unset = compare_aws_tags(self.tag_example_dict, new_dict, purge_tags=True) - self.assertEqual(new_keys, keys_to_set) - self.assertEqual(['Normal case'], keys_to_unset) + assert new_keys == keys_to_set + assert ['Normal case'] == keys_to_unset def test_compare_aws_tags_aws(self): starting_tags = dict(self.tag_aws_dict) desired_tags = dict(self.tag_minimal_dict) tags_to_set, tags_to_unset = compare_aws_tags(starting_tags, desired_tags, purge_tags=True) - self.assertEqual(desired_tags, tags_to_set) - self.assertEqual([], tags_to_unset) + assert desired_tags == tags_to_set + assert [] == tags_to_unset # If someone explicitly passes a changed 'aws:' key the APIs will probably # throw an error, but this is their responsibility. desired_tags.update(self.tag_aws_changed) tags_to_set, tags_to_unset = compare_aws_tags(starting_tags, desired_tags, purge_tags=True) - self.assertEqual(desired_tags, tags_to_set) - self.assertEqual([], tags_to_unset) + assert desired_tags == tags_to_set + assert [] == tags_to_unset def test_compare_aws_tags_aws_complex(self): old_dict = dict(self.tag_example_dict) @@ -164,14 +162,14 @@ def test_compare_aws_tags_aws_complex(self): new_dict.update(new_keys) del new_dict['Normal case'] keys_to_set, keys_to_unset = compare_aws_tags(old_dict, new_dict) - self.assertEqual(new_keys, keys_to_set) - self.assertEqual(['Normal case'], keys_to_unset) + assert new_keys == keys_to_set + assert ['Normal case'] == keys_to_unset keys_to_set, keys_to_unset = compare_aws_tags(old_dict, new_dict, purge_tags=False) - self.assertEqual(new_keys, keys_to_set) - self.assertEqual([], keys_to_unset) + assert new_keys == keys_to_set + assert [] == keys_to_unset keys_to_set, keys_to_unset = compare_aws_tags(old_dict, new_dict, purge_tags=True) - self.assertEqual(new_keys, keys_to_set) - self.assertEqual(['Normal case'], keys_to_unset) + assert new_keys == keys_to_set + assert ['Normal case'] == keys_to_unset # ======================================================== # tagging.boto3_tag_specifications @@ -182,17 +180,17 @@ def test_compare_aws_tags_aws_complex(self): def test_boto3_tag_specifications_no_type(self): tag_specification = boto3_tag_specifications(self.tag_minimal_dict) expected_specification = [{'Tags': self.tag_minimal_boto3_list}] - self.assertEqual(tag_specification, expected_specification) + assert tag_specification == expected_specification def test_boto3_tag_specifications_string_type(self): tag_specification = boto3_tag_specifications(self.tag_minimal_dict, 'instance') expected_specification = [{'ResourceType': 'instance', 'Tags': self.tag_minimal_boto3_list}] - self.assertEqual(tag_specification, expected_specification) + assert tag_specification == expected_specification def test_boto3_tag_specifications_single_type(self): tag_specification = boto3_tag_specifications(self.tag_minimal_dict, ['instance']) expected_specification = [{'ResourceType': 'instance', 'Tags': self.tag_minimal_boto3_list}] - self.assertEqual(tag_specification, expected_specification) + assert tag_specification == expected_specification def test_boto3_tag_specifications_multipe_types(self): tag_specification = boto3_tag_specifications(self.tag_minimal_dict, ['instance', 'volume']) @@ -202,4 +200,4 @@ def test_boto3_tag_specifications_multipe_types(self): ] sorted_tag_spec = sorted(tag_specification, key=lambda i: (i['ResourceType'])) sorted_expected = sorted(expected_specification, key=lambda i: (i['ResourceType'])) - self.assertEqual(sorted_tag_spec, sorted_expected) + assert sorted_tag_spec == sorted_expected diff --git a/tests/unit/module_utils/test_ec2.py b/tests/unit/module_utils/transformation/test_ansible_dict_to_boto3_filter_list.py similarity index 61% rename from tests/unit/module_utils/test_ec2.py rename to tests/unit/module_utils/transformation/test_ansible_dict_to_boto3_filter_list.py index 6b439f48f13..3c19089231f 100644 --- a/tests/unit/module_utils/test_ec2.py +++ b/tests/unit/module_utils/transformation/test_ansible_dict_to_boto3_filter_list.py @@ -7,30 +7,10 @@ __metaclass__ = type -import unittest +from ansible_collections.amazon.aws.plugins.module_utils.transformation import ansible_dict_to_boto3_filter_list -from ansible_collections.amazon.aws.plugins.module_utils.ec2 import ansible_dict_to_boto3_filter_list -from ansible_collections.amazon.aws.plugins.module_utils.ec2 import map_complex_type - -class Ec2Utils(unittest.TestCase): - - # ======================================================== - # Setup some initial data that we can use within our tests - # ======================================================== - def setUp(self): - pass - - # ======================================================== - # ec2.map_complex_type - # ======================================================== - - def test_map_complex_type_over_dict(self): - complex_type = {'minimum_healthy_percent': "75", 'maximum_percent': "150"} - type_map = {'minimum_healthy_percent': 'int', 'maximum_percent': 'int'} - complex_type_mapped = map_complex_type(complex_type, type_map) - complex_type_expected = {'minimum_healthy_percent': 75, 'maximum_percent': 150} - self.assertEqual(complex_type_mapped, complex_type_expected) +class AnsibleDictToBoto3FilterListTestSuite(): # ======================================================== # ec2.ansible_dict_to_boto3_filter_list diff --git a/tests/unit/module_utils/transformation/test_map_complex_type.py b/tests/unit/module_utils/transformation/test_map_complex_type.py new file mode 100644 index 00000000000..32635d47acd --- /dev/null +++ b/tests/unit/module_utils/transformation/test_map_complex_type.py @@ -0,0 +1,21 @@ +# (c) 2017 Red Hat Inc. +# +# This file is part of Ansible +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from ansible_collections.amazon.aws.plugins.module_utils.transformation import map_complex_type + + +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} + + complex_type_mapped = map_complex_type(complex_type_dict, type_map) + + assert complex_type_mapped == complex_type_expected diff --git a/tests/unit/module_utils/core/test_scrub_none_parameters.py b/tests/unit/module_utils/transformation/test_scrub_none_parameters.py similarity index 97% rename from tests/unit/module_utils/core/test_scrub_none_parameters.py rename to tests/unit/module_utils/transformation/test_scrub_none_parameters.py index 8c1faf42832..82fd41ed364 100644 --- a/tests/unit/module_utils/core/test_scrub_none_parameters.py +++ b/tests/unit/module_utils/transformation/test_scrub_none_parameters.py @@ -3,7 +3,7 @@ import pytest -from ansible_collections.amazon.aws.plugins.module_utils.core import scrub_none_parameters +from ansible_collections.amazon.aws.plugins.module_utils.transformation import scrub_none_parameters scrub_none_test_data = [ (dict(), # Input diff --git a/tests/unit/plugins/inventory/test_aws_ec2.py b/tests/unit/plugins/inventory/test_aws_ec2.py index b69b5e3d802..028d4a37fde 100644 --- a/tests/unit/plugins/inventory/test_aws_ec2.py +++ b/tests/unit/plugins/inventory/test_aws_ec2.py @@ -24,10 +24,6 @@ import pytest import datetime -# Just to test that we have the prerequisite for InventoryModule and instance_data_filter_to_boto_attr -boto3 = pytest.importorskip('boto3') -botocore = pytest.importorskip('botocore') - from ansible.errors import AnsibleError from ansible.parsing.dataloader import DataLoader from ansible_collections.amazon.aws.plugins.inventory.aws_ec2 import InventoryModule, instance_data_filter_to_boto_attr diff --git a/tests/unit/plugins/modules/test_aws_s3.py b/tests/unit/plugins/modules/test_aws_s3.py deleted file mode 100644 index 7d34a84fdac..00000000000 --- a/tests/unit/plugins/modules/test_aws_s3.py +++ /dev/null @@ -1,38 +0,0 @@ -# Make coding more python3-ish -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type - -import pytest - -import unittest - -try: - import ansible_collections.amazon.aws.plugins.modules.aws_s3 as s3 -except ImportError: - pytestmark = pytest.mark.skip("This test requires the s3 Python libraries") - -from ansible.module_utils.six.moves.urllib.parse import urlparse - -boto3 = pytest.importorskip("boto3") - - -class TestUrlparse(unittest.TestCase): - - def test_urlparse(self): - actual = urlparse("http://test.com/here") - self.assertEqual("http", actual.scheme) - self.assertEqual("test.com", actual.netloc) - self.assertEqual("/here", actual.path) - - def test_is_fakes3(self): - actual = s3.is_fakes3("fakes3://bla.blubb") - self.assertEqual(True, actual) - - def test_get_s3_connection(self): - aws_connect_kwargs = dict(aws_access_key_id="access_key", - aws_secret_access_key="secret_key") - location = None - rgw = True - s3_url = "http://bla.blubb" - actual = s3.get_s3_connection(None, aws_connect_kwargs, location, rgw, s3_url) - self.assertEqual(bool("bla.blubb" in str(actual._endpoint)), True) diff --git a/tests/unit/plugins/modules/test_cloudformation.py b/tests/unit/plugins/modules/test_cloudformation.py index 67029181201..f46bc1113d6 100644 --- a/tests/unit/plugins/modules/test_cloudformation.py +++ b/tests/unit/plugins/modules/test_cloudformation.py @@ -12,10 +12,10 @@ # Magic... from ansible_collections.amazon.aws.tests.unit.utils.amazon_placebo_fixtures import maybe_sleep, placeboify # pylint: disable=unused-import +from ansible_collections.amazon.aws.plugins.module_utils.botocore import boto_exception from ansible_collections.amazon.aws.plugins.module_utils.modules import _RetryingBotoClientWrapper -import ansible_collections.amazon.aws.plugins.module_utils.ec2 as aws_ec2 +from ansible_collections.amazon.aws.plugins.module_utils.retries import AWSRetry -from ansible_collections.amazon.aws.plugins.module_utils.ec2 import boto_exception from ansible_collections.amazon.aws.plugins.modules import cloudformation as cfn_module basic_yaml_tpl = """ @@ -83,7 +83,7 @@ def exit_json(self, *args, **kwargs): def _create_wrapped_client(placeboify): connection = placeboify.client('cloudformation') - retry_decorator = aws_ec2.AWSRetry.jittered_backoff() + retry_decorator = AWSRetry.jittered_backoff() wrapped_conn = _RetryingBotoClientWrapper(connection, retry_decorator) return wrapped_conn diff --git a/tests/unit/plugins/modules/test_s3_object.py b/tests/unit/plugins/modules/test_s3_object.py new file mode 100644 index 00000000000..b0251307229 --- /dev/null +++ b/tests/unit/plugins/modules/test_s3_object.py @@ -0,0 +1,29 @@ +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from ansible.module_utils.six.moves.urllib.parse import urlparse + +from ansible_collections.amazon.aws.plugins.modules import s3_object + + +class TestUrlparse(): + + def test_urlparse(self): + actual = urlparse("http://test.com/here") + assert actual.scheme == "http" + assert actual.netloc == "test.com" + assert actual.path == "/here" + + def test_is_fakes3(self): + actual = s3_object.is_fakes3("fakes3://bla.blubb") + assert actual is True + + def test_get_s3_connection(self): + aws_connect_kwargs = dict(aws_access_key_id="access_key", + aws_secret_access_key="secret_key") + location = None + rgw = True + s3_url = "http://bla.blubb" + actual = s3_object.get_s3_connection(None, aws_connect_kwargs, location, rgw, s3_url) + assert "bla.blubb" in str(actual._endpoint)