diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-06-05 16:18:37 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-06-05 16:18:37 +0000 |
commit | 5d7eda1e172f8e396536a8fbd6f85b4b991290e8 (patch) | |
tree | b18be36b43a1abdab0d40ecc8e4c8de2dbcd65c0 /ansible_collections/amazon/aws/tests/unit | |
parent | Adding debian version 9.5.1+dfsg-1. (diff) | |
download | ansible-5d7eda1e172f8e396536a8fbd6f85b4b991290e8.tar.xz ansible-5d7eda1e172f8e396536a8fbd6f85b4b991290e8.zip |
Merging upstream version 10.0.0+dfsg.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ansible_collections/amazon/aws/tests/unit')
6 files changed, 758 insertions, 70 deletions
diff --git a/ansible_collections/amazon/aws/tests/unit/module_utils/botocore/test_is_boto3_error_code.py b/ansible_collections/amazon/aws/tests/unit/module_utils/botocore/test_is_boto3_error_code.py index 9f3e4194b..a5ce452fc 100644 --- a/ansible_collections/amazon/aws/tests/unit/module_utils/botocore/test_is_boto3_error_code.py +++ b/ansible_collections/amazon/aws/tests/unit/module_utils/botocore/test_is_boto3_error_code.py @@ -209,3 +209,71 @@ class TestIsBoto3ErrorCode: assert not issubclass(returned_exception, botocore.exceptions.BotoCoreError) assert issubclass(returned_exception, Exception) assert returned_exception.__name__ == "NeverEverRaisedException" + + def test_is_boto3_error_code_tuple__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_tuple__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_tuple__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" + + def test_is_boto3_error_code_set__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_set__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_set__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/ansible_collections/amazon/aws/tests/unit/module_utils/iam/test_iam_resource_transforms.py b/ansible_collections/amazon/aws/tests/unit/module_utils/iam/test_iam_resource_transforms.py index 28090f993..0a6830311 100644 --- a/ansible_collections/amazon/aws/tests/unit/module_utils/iam/test_iam_resource_transforms.py +++ b/ansible_collections/amazon/aws/tests/unit/module_utils/iam/test_iam_resource_transforms.py @@ -451,10 +451,10 @@ class TestIamResourceToAnsibleDict: OUTPUT = { "arn": "arn:aws:iam::123456789012:role/ansible-test-76640355", "assume_role_policy_document": { - "statement": [ - {"action": "sts:AssumeRole", "effect": "Deny", "principal": {"service": "ec2.amazonaws.com"}} + "Statement": [ + {"Action": "sts:AssumeRole", "Effect": "Deny", "Principal": {"Service": "ec2.amazonaws.com"}} ], - "version": "2012-10-17", + "Version": "2012-10-17", }, "assume_role_policy_document_raw": { "Statement": [ diff --git a/ansible_collections/amazon/aws/tests/unit/module_utils/modules/ansible_aws_module/test_passthrough.py b/ansible_collections/amazon/aws/tests/unit/module_utils/modules/ansible_aws_module/test_passthrough.py index c61de1391..688514f59 100644 --- a/ansible_collections/amazon/aws/tests/unit/module_utils/modules/ansible_aws_module/test_passthrough.py +++ b/ansible_collections/amazon/aws/tests/unit/module_utils/modules/ansible_aws_module/test_passthrough.py @@ -70,7 +70,7 @@ def test_region(monkeypatch, stdin): aws_module = utils_module.AnsibleAWSModule(argument_spec=dict()) assert aws_module.region is sentinel.RETURNED_REGION - assert get_aws_region.call_args == call(aws_module, True) + assert get_aws_region.call_args == call(aws_module) @pytest.mark.parametrize("stdin", [{}], indirect=["stdin"]) @@ -129,7 +129,7 @@ def test_client_no_wrapper(monkeypatch, stdin): aws_module = utils_module.AnsibleAWSModule(argument_spec=dict()) assert aws_module.client(sentinel.PARAM_SERVICE) is sentinel.BOTO3_CONN - assert get_aws_connection_info.call_args == call(aws_module, boto3=True) + assert get_aws_connection_info.call_args == call(aws_module) assert boto3_conn.call_args == call( aws_module, conn_type="client", @@ -153,7 +153,7 @@ def test_client_wrapper(monkeypatch, stdin): wrapped_conn = aws_module.client(sentinel.PARAM_SERVICE, sentinel.PARAM_WRAPPER) assert wrapped_conn.client is sentinel.BOTO3_CONN assert wrapped_conn.retry is sentinel.PARAM_WRAPPER - assert get_aws_connection_info.call_args == call(aws_module, boto3=True) + assert get_aws_connection_info.call_args == call(aws_module) assert boto3_conn.call_args == call( aws_module, conn_type="client", @@ -166,7 +166,7 @@ def test_client_wrapper(monkeypatch, stdin): wrapped_conn = aws_module.client(sentinel.PARAM_SERVICE, sentinel.PARAM_WRAPPER, region=sentinel.PARAM_REGION) assert wrapped_conn.client is sentinel.BOTO3_CONN assert wrapped_conn.retry is sentinel.PARAM_WRAPPER - assert get_aws_connection_info.call_args == call(aws_module, boto3=True) + assert get_aws_connection_info.call_args == call(aws_module) assert boto3_conn.call_args == call( aws_module, conn_type="client", @@ -188,7 +188,7 @@ def test_resource(monkeypatch, stdin): aws_module = utils_module.AnsibleAWSModule(argument_spec=dict()) assert aws_module.resource(sentinel.PARAM_SERVICE) is sentinel.BOTO3_CONN - assert get_aws_connection_info.call_args == call(aws_module, boto3=True) + assert get_aws_connection_info.call_args == call(aws_module) assert boto3_conn.call_args == call( aws_module, conn_type="resource", @@ -199,7 +199,7 @@ def test_resource(monkeypatch, stdin): # Check that we can override parameters assert aws_module.resource(sentinel.PARAM_SERVICE, region=sentinel.PARAM_REGION) is sentinel.BOTO3_CONN - assert get_aws_connection_info.call_args == call(aws_module, boto3=True) + assert get_aws_connection_info.call_args == call(aws_module) assert boto3_conn.call_args == call( aws_module, conn_type="resource", diff --git a/ansible_collections/amazon/aws/tests/unit/module_utils/policy/test_sort_json_policy_dict.py b/ansible_collections/amazon/aws/tests/unit/module_utils/policy/test_sort_json_policy_dict.py deleted file mode 100644 index 8829f332c..000000000 --- a/ansible_collections/amazon/aws/tests/unit/module_utils/policy/test_sort_json_policy_dict.py +++ /dev/null @@ -1,61 +0,0 @@ -# (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 ansible_collections.amazon.aws.plugins.module_utils.policy import sort_json_policy_dict - - -def test_nothing_to_sort(): - simple_dict = {"key1": "a"} - nested_dict = {"key1": {"key2": "a"}} - very_nested_dict = {"key1": {"key2": {"key3": "a"}}} - assert sort_json_policy_dict(simple_dict) == simple_dict - assert sort_json_policy_dict(nested_dict) == nested_dict - assert sort_json_policy_dict(very_nested_dict) == very_nested_dict - - -def test_basic_sort(): - simple_dict = {"key1": [1, 2, 3, 4], "key2": [9, 8, 7, 6]} - sorted_dict = {"key1": [1, 2, 3, 4], "key2": [6, 7, 8, 9]} - assert sort_json_policy_dict(simple_dict) == sorted_dict - assert sort_json_policy_dict(sorted_dict) == sorted_dict - simple_dict = {"key1": ["a", "b", "c", "d"], "key2": ["z", "y", "x", "w"]} - sorted_dict = {"key1": ["a", "b", "c", "d"], "key2": ["w", "x", "y", "z"]} - assert sort_json_policy_dict(sorted_dict) == sorted_dict - - -def test_nested_list_sort(): - nested_dict = {"key1": {"key2": [9, 8, 7, 6]}} - sorted_dict = {"key1": {"key2": [6, 7, 8, 9]}} - assert sort_json_policy_dict(nested_dict) == sorted_dict - assert sort_json_policy_dict(sorted_dict) == sorted_dict - nested_dict = {"key1": {"key2": ["z", "y", "x", "w"]}} - sorted_dict = {"key1": {"key2": ["w", "x", "y", "z"]}} - assert sort_json_policy_dict(nested_dict) == sorted_dict - assert sort_json_policy_dict(sorted_dict) == sorted_dict - - -def test_nested_dict_list_sort(): - nested_dict = {"key1": {"key2": {"key3": [9, 8, 7, 6]}}} - sorted_dict = {"key1": {"key2": {"key3": [6, 7, 8, 9]}}} - assert sort_json_policy_dict(nested_dict) == sorted_dict - assert sort_json_policy_dict(sorted_dict) == sorted_dict - nested_dict = {"key1": {"key2": {"key3": ["z", "y", "x", "w"]}}} - sorted_dict = {"key1": {"key2": {"key3": ["w", "x", "y", "z"]}}} - assert sort_json_policy_dict(nested_dict) == sorted_dict - assert sort_json_policy_dict(sorted_dict) == sorted_dict - - -def test_list_of_dict_sort(): - nested_dict = {"key1": [{"key2": [4, 3, 2, 1]}, {"key3": [9, 8, 7, 6]}]} - sorted_dict = {"key1": [{"key2": [1, 2, 3, 4]}, {"key3": [6, 7, 8, 9]}]} - assert sort_json_policy_dict(nested_dict) == sorted_dict - assert sort_json_policy_dict(sorted_dict) == sorted_dict - - -def test_list_of_list_sort(): - nested_dict = {"key1": [[4, 3, 2, 1], [9, 8, 7, 6]]} - sorted_dict = {"key1": [[1, 2, 3, 4], [6, 7, 8, 9]]} - assert sort_json_policy_dict(nested_dict) == sorted_dict - assert sort_json_policy_dict(sorted_dict) == sorted_dict diff --git a/ansible_collections/amazon/aws/tests/unit/module_utils/test_elbv2.py b/ansible_collections/amazon/aws/tests/unit/module_utils/test_elbv2.py index d7293f0ce..0d2f3c153 100644 --- a/ansible_collections/amazon/aws/tests/unit/module_utils/test_elbv2.py +++ b/ansible_collections/amazon/aws/tests/unit/module_utils/test_elbv2.py @@ -6,6 +6,8 @@ from unittest.mock import MagicMock +import pytest + from ansible_collections.amazon.aws.plugins.module_utils import elbv2 one_action = [ @@ -159,3 +161,138 @@ class TestElBV2Utils: actual_elb_attributes = self.elbv2obj.get_elb_attributes() # Assert we got the expected result assert actual_elb_attributes == expected_elb_attributes + + +class TestELBListeners: + DEFAULT_PORT = 80 + DEFAULT_PROTOCOL = "TCP" + + def createListener(self, **kwargs): + result = {"Port": self.DEFAULT_PORT, "Protocol": self.DEFAULT_PROTOCOL} + if kwargs.get("port"): + result["Port"] = kwargs.get("port") + if kwargs.get("protocol"): + result["Protocol"] = kwargs.get("protocol") + if kwargs.get("certificate_arn") and kwargs.get("protocol") in ("TLS", "HTTPS"): + result["Certificates"] = [{"CertificateArn": kwargs.get("certificate_arn")}] + if kwargs.get("sslPolicy") and kwargs.get("protocol") in ("TLS", "HTTPS"): + result["SslPolicy"] = kwargs.get("sslPolicy") + if kwargs.get("alpnPolicy") and kwargs.get("protocol") == "TLS": + result["AlpnPolicy"] = kwargs.get("alpnPolicy") + return result + + @pytest.mark.parametrize("current_protocol", ["TCP", "TLS", "UDP"]) + @pytest.mark.parametrize( + "current_alpn,new_alpn", + [ + (None, "None"), + (None, "HTTP1Only"), + ("HTTP1Only", "HTTP2Only"), + ("HTTP1Only", "HTTP1Only"), + ], + ) + def test__compare_listener_alpn_policy(self, current_protocol, current_alpn, new_alpn): + current_listener = self.createListener(protocol=current_protocol, alpnPolicy=[current_alpn]) + new_listener = self.createListener(protocol="TLS", alpnPolicy=[new_alpn]) + result = None + if current_protocol != "TLS": + result = {"Protocol": "TLS"} + if new_alpn and any((current_protocol != "TLS", not current_alpn, current_alpn and current_alpn != new_alpn)): + result = result or {} + result["AlpnPolicy"] = [new_alpn] + + assert result == elbv2.ELBListeners._compare_listener(current_listener, new_listener) + + @pytest.mark.parametrize( + "current_protocol,new_protocol", + [ + ("TCP", "TCP"), + ("TLS", "HTTPS"), + ("HTTPS", "HTTPS"), + ("TLS", "TLS"), + ("HTTPS", "TLS"), + ("HTTPS", "TCP"), + ("TLS", "TCP"), + ], + ) + @pytest.mark.parametrize( + "current_ssl,new_ssl", + [ + (None, "ELBSecurityPolicy-TLS-1-0-2015-04"), + ("ELBSecurityPolicy-TLS13-1-2-Ext2-2021-06", "ELBSecurityPolicy-TLS-1-0-2015-04"), + ("ELBSecurityPolicy-TLS-1-0-2015-04", None), + ("ELBSecurityPolicy-TLS-1-0-2015-04", "ELBSecurityPolicy-TLS-1-0-2015-04"), + ], + ) + def test__compare_listener_sslpolicy(self, current_protocol, new_protocol, current_ssl, new_ssl): + current_listener = self.createListener(protocol=current_protocol, sslPolicy=current_ssl) + + new_listener = self.createListener(protocol=new_protocol, sslPolicy=new_ssl) + + expected = None + if new_protocol != current_protocol: + expected = {"Protocol": new_protocol} + if new_protocol in ("HTTPS", "TLS") and new_ssl and new_ssl != current_ssl: + expected = expected or {} + expected["SslPolicy"] = new_ssl + assert expected == elbv2.ELBListeners._compare_listener(current_listener, new_listener) + + @pytest.mark.parametrize( + "current_protocol,new_protocol", + [ + ("TCP", "TCP"), + ("TLS", "HTTPS"), + ("HTTPS", "HTTPS"), + ("TLS", "TLS"), + ("HTTPS", "TLS"), + ("HTTPS", "TCP"), + ("TLS", "TCP"), + ], + ) + @pytest.mark.parametrize( + "current_certificate,new_certificate", + [ + (None, "arn:aws:iam::012345678901:server-certificate/ansible-test-1"), + ( + "arn:aws:iam::012345678901:server-certificate/ansible-test-1", + "arn:aws:iam::012345678901:server-certificate/ansible-test-2", + ), + ("arn:aws:iam::012345678901:server-certificate/ansible-test-1", None), + ( + "arn:aws:iam::012345678901:server-certificate/ansible-test-1", + "arn:aws:iam::012345678901:server-certificate/ansible-test-1", + ), + ], + ) + def test__compare_listener_certificates(self, current_protocol, new_protocol, current_certificate, new_certificate): + current_listener = self.createListener(protocol=current_protocol, certificate_arn=current_certificate) + + new_listener = self.createListener(protocol=new_protocol, certificate_arn=new_certificate) + + expected = None + if new_protocol != current_protocol: + expected = {"Protocol": new_protocol} + if new_protocol in ("HTTPS", "TLS") and new_certificate and new_certificate != current_certificate: + expected = expected or {} + expected["Certificates"] = [{"CertificateArn": new_certificate}] + assert expected == elbv2.ELBListeners._compare_listener(current_listener, new_listener) + + @pytest.mark.parametrize( + "are_equals", + [True, False], + ) + def test__compare_listener_port(self, are_equals): + current_listener = self.createListener() + new_port = MagicMock() if not are_equals else None + new_listener = self.createListener(port=new_port) + + result = elbv2.ELBListeners._compare_listener(current_listener, new_listener) + expected = None + if not are_equals: + expected = {"Port": new_port} + assert result == expected + + def test_ensure_listeners_alpn_policy(self): + listeners = [{"Port": self.DEFAULT_PORT, "AlpnPolicy": "HTTP2Optional"}] + expected = [{"Port": self.DEFAULT_PORT, "AlpnPolicy": ["HTTP2Optional"]}] + assert expected == elbv2.ELBListeners._ensure_listeners_alpn_policy(listeners) diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/test_lambda_event.py b/ansible_collections/amazon/aws/tests/unit/plugins/modules/test_lambda_event.py new file mode 100644 index 000000000..c292329b4 --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/test_lambda_event.py @@ -0,0 +1,544 @@ +# +# (c) 2024 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 contextlib import nullcontext as does_not_raise +from copy import deepcopy +from unittest.mock import MagicMock +from unittest.mock import patch + +import pytest + +from ansible_collections.amazon.aws.plugins.modules.lambda_event import get_qualifier +from ansible_collections.amazon.aws.plugins.modules.lambda_event import lambda_event_stream +from ansible_collections.amazon.aws.plugins.modules.lambda_event import set_default_values +from ansible_collections.amazon.aws.plugins.modules.lambda_event import validate_params + +mock_get_qualifier = "ansible_collections.amazon.aws.plugins.modules.lambda_event.get_qualifier" +mock_camel_dict_to_snake_dict = "ansible_collections.amazon.aws.plugins.modules.lambda_event.camel_dict_to_snake_dict" + + +@pytest.fixture +def ansible_aws_module(): + module = MagicMock() + module.check_mode = False + module.params = { + "state": "present", + "lambda_function_arn": None, + "event_source": "sqs", + "source_params": { + "source_arn": "arn:aws:sqs:us-east-2:123456789012:ansible-test-sqs", + "batch_size": 200, + "starting_position": "LATEST", + }, + "alias": None, + "version": 0, + } + module.exit_json = MagicMock() + module.exit_json.side_effect = SystemExit(1) + module.fail_json_aws = MagicMock() + module.fail_json_aws.side_effect = SystemExit(2) + module.fail_json = MagicMock() + module.fail_json.side_effect = SystemExit(2) + module.client = MagicMock() + module.client.return_value = MagicMock() + module.boolean = MagicMock() + module.boolean.side_effect = lambda x: x.lower() in ["true", "1", "t", "y", "yes"] + return module + + +@pytest.mark.parametrize( + "module_params,expected", + [ + ({"version": 1}, "1"), + ({"alias": "ansible-test"}, "ansible-test"), + ({"version": 1, "alias": "ansible-test"}, "1"), + ({}, None), + ], +) +def test_get_qualifier(ansible_aws_module, module_params, expected): + ansible_aws_module.params.update(module_params) + assert get_qualifier(ansible_aws_module) == expected + + +@pytest.mark.parametrize( + "function_name,error_msg", + [ + ( + "invalid+function+name", + "Function name invalid+function+name is invalid. Names must contain only alphanumeric characters and hyphens.", + ), + ( + "this_invalid_function_name_has_more_than_64_character_limit_this_is_not_allowed_by_the_module", + 'Function name "this_invalid_function_name_has_more_than_64_character_limit_this_is_not_allowed_by_the_module" exceeds 64 character limit', + ), + ( + "arn:aws:lambda:us-east-2:123456789012:function:ansible-test-ansible-test-ansible-test-sqs-lambda-function:" + "ansible-test-ansible-test-ansible-test-sqs-lambda-function-alias", + 'ARN "arn:aws:lambda:us-east-2:123456789012:function:ansible-test-ansible-test-ansible-test-sqs-lambda-function:' + 'ansible-test-ansible-test-ansible-test-sqs-lambda-function-alias" exceeds 140 character limit', + ), + ], +) +def test_validate_params_function_name_errors(ansible_aws_module, function_name, error_msg): + ansible_aws_module.params.update({"lambda_function_arn": function_name}) + client = MagicMock() + client.get_function = MagicMock() + client.get_function.return_value = {} + with pytest.raises(SystemExit): + validate_params(ansible_aws_module, client) + + ansible_aws_module.fail_json.assert_called_once_with(msg=error_msg) + + +@pytest.mark.parametrize( + "qualifier", + [None, "ansible-test"], +) +@patch(mock_get_qualifier) +def test_validate_params_with_function_arn(m_get_qualifier, ansible_aws_module, qualifier): + function_name = "arn:aws:lambda:us-east-2:123456789012:function:sqs_consumer" + ansible_aws_module.params.update({"lambda_function_arn": function_name}) + m_get_qualifier.return_value = qualifier + + client = MagicMock() + client.get_function = MagicMock() + client.get_function.return_value = {} + + params = deepcopy(ansible_aws_module.params) + params["lambda_function_arn"] = f"{function_name}:{qualifier}" if qualifier else function_name + + validate_params(ansible_aws_module, client) + assert params == ansible_aws_module.params + m_get_qualifier.assert_called_once() + + +@pytest.mark.parametrize( + "qualifier", + [None, "ansible-test"], +) +@patch(mock_get_qualifier) +def test_validate_params_with_function_name(m_get_qualifier, ansible_aws_module, qualifier): + function_arn = "arn:aws:lambda:us-east-2:123456789012:function:sqs_consumer" + function_name = "sqs_consumer" + ansible_aws_module.params.update({"lambda_function_arn": function_name}) + m_get_qualifier.return_value = qualifier + + client = MagicMock() + client.get_function = MagicMock() + client.get_function.return_value = { + "Configuration": {"FunctionArn": function_arn}, + } + + params = deepcopy(ansible_aws_module.params) + params["lambda_function_arn"] = function_arn + + validate_params(ansible_aws_module, client) + + assert params == ansible_aws_module.params + m_get_qualifier.assert_called_once() + api_params = {"FunctionName": function_name} + if qualifier: + api_params.update({"Qualifier": qualifier}) + client.get_function.assert_called_once_with(**api_params) + + +EventSourceMappings = [ + { + "BatchSize": 10, + "EventSourceArn": "arn:aws:sqs:us-east-2:123456789012:sqs_consumer", + "FunctionArn": "arn:aws:lambda:us-east-2:123456789012:function:sqs_consumer", + "LastModified": "2024-02-08T15:24:57.014000+01:00", + "MaximumBatchingWindowInSeconds": 0, + "State": "Enabled", + "StateTransitionReason": "USER_INITIATED", + "UUID": "3ab96d4c-b0c4-4885-87d0-f58cb9c0a4cc", + } +] + + +@pytest.mark.parametrize( + "check_mode", + [True, False], +) +@pytest.mark.parametrize( + "existing_event_source", + [True, False], +) +@patch(mock_camel_dict_to_snake_dict) +def test_lambda_event_stream_with_state_absent( + mock_camel_dict_to_snake_dict, ansible_aws_module, check_mode, existing_event_source +): + function_name = "sqs_consumer" + ansible_aws_module.params.update({"lambda_function_arn": function_name, "state": "absent"}) + ansible_aws_module.check_mode = check_mode + + client = MagicMock() + client.list_event_source_mappings = MagicMock() + + client.list_event_source_mappings.return_value = { + "EventSourceMappings": EventSourceMappings if existing_event_source else [] + } + client.delete_event_source_mapping = MagicMock() + event_source_deleted = {"msg": "event source successfully deleted."} + client.delete_event_source_mapping.return_value = event_source_deleted + mock_camel_dict_to_snake_dict.side_effect = lambda x: x + + events = [] + changed = False + result = lambda_event_stream(ansible_aws_module, client) + changed = existing_event_source + if existing_event_source: + events = EventSourceMappings + if not check_mode: + events = event_source_deleted + client.delete_event_source_mapping.assert_called_once_with(UUID=EventSourceMappings[0]["UUID"]) + else: + client.delete_event_source_mapping.assert_not_called() + assert dict(changed=changed, events=events) == result + + +def test_lambda_event_stream_create_event_missing_starting_position(ansible_aws_module): + ansible_aws_module.params = { + "state": "present", + "lambda_function_arn": "sqs_consumer", + "event_source": "stream", + "source_params": { + "source_arn": "arn:aws:sqs:us-east-2:123456789012:ansible-test-sqs", + "maximum_batching_window_in_seconds": 1, + "batch_size": 200, + }, + "alias": None, + "version": 0, + } + + client = MagicMock() + client.list_event_source_mappings = MagicMock() + client.list_event_source_mappings.return_value = {"EventSourceMappings": []} + + error_message = "Source parameter 'starting_position' is required for stream event notification." + with pytest.raises(SystemExit): + lambda_event_stream(ansible_aws_module, client) + ansible_aws_module.fail_json.assert_called_once_with(msg=error_message) + + +@pytest.mark.parametrize( + "check_mode", + [True, False], +) +@pytest.mark.parametrize( + "module_params,api_params", + [ + ( + { + "state": "present", + "lambda_function_arn": "sqs_consumer", + "event_source": "stream", + "source_params": { + "source_arn": "arn:aws:sqs:us-east-2:123456789012:ansible-test-sqs", + "maximum_batching_window_in_seconds": 1, + "batch_size": 250, + "starting_position": "END", + "function_response_types": ["ReportBatchItemFailures"], + }, + "alias": None, + "version": 0, + }, + { + "FunctionName": "sqs_consumer", + "EventSourceArn": "arn:aws:sqs:us-east-2:123456789012:ansible-test-sqs", + "StartingPosition": "END", + "Enabled": True, + "MaximumBatchingWindowInSeconds": 1, + "BatchSize": 250, + "FunctionResponseTypes": ["ReportBatchItemFailures"], + }, + ), + ( + { + "state": "present", + "lambda_function_arn": "sqs_consumer", + "event_source": "stream", + "source_params": { + "source_arn": "arn:aws:sqs:us-east-2:123456789012:ansible-test-sqs", + "maximum_batching_window_in_seconds": 1, + "batch_size": 250, + "starting_position": "END", + "function_response_types": ["ReportBatchItemFailures"], + "enabled": "no", + }, + "alias": None, + "version": 0, + }, + { + "FunctionName": "sqs_consumer", + "EventSourceArn": "arn:aws:sqs:us-east-2:123456789012:ansible-test-sqs", + "StartingPosition": "END", + "Enabled": False, + "MaximumBatchingWindowInSeconds": 1, + "BatchSize": 250, + "FunctionResponseTypes": ["ReportBatchItemFailures"], + }, + ), + ( + { + "state": "present", + "lambda_function_arn": "sqs_consumer", + "event_source": "sqs", + "source_params": { + "source_arn": "arn:aws:sqs:us-east-2:123456789012:ansible-test-sqs", + "maximum_batching_window_in_seconds": 1, + "batch_size": 101, + }, + "alias": None, + "version": 0, + }, + { + "FunctionName": "sqs_consumer", + "EventSourceArn": "arn:aws:sqs:us-east-2:123456789012:ansible-test-sqs", + "Enabled": True, + "MaximumBatchingWindowInSeconds": 1, + "BatchSize": 101, + }, + ), + ], +) +@patch(mock_camel_dict_to_snake_dict) +def test_lambda_event_stream_create_event( + mock_camel_dict_to_snake_dict, ansible_aws_module, check_mode, module_params, api_params +): + ansible_aws_module.params = module_params + ansible_aws_module.check_mode = check_mode + + client = MagicMock() + client.list_event_source_mappings = MagicMock() + client.list_event_source_mappings.return_value = {"EventSourceMappings": []} + + client.create_event_source_mapping = MagicMock() + event_source_created = {"msg": "event source successfully created."} + client.create_event_source_mapping.return_value = event_source_created + mock_camel_dict_to_snake_dict.side_effect = lambda x: x + + result = lambda_event_stream(ansible_aws_module, client) + + events = [] + + if not check_mode: + events = event_source_created + client.create_event_source_mapping.assert_called_once_with(**api_params) + else: + client.create_event_source_mapping.assert_not_called() + + assert dict(changed=True, events=events) == result + + +@pytest.mark.parametrize( + "check_mode", + [True, False], +) +@pytest.mark.parametrize( + "module_source_params,current_mapping,api_params", + [ + ( + {"batch_size": 100, "enabled": "false"}, + {"BatchSize": 120, "State": "Enabled"}, + {"BatchSize": 100, "Enabled": False}, + ), + ( + {"batch_size": 100, "enabled": "true"}, + {"BatchSize": 100, "State": "Enabled"}, + {}, + ), + ], +) +@patch(mock_camel_dict_to_snake_dict) +def test_lambda_event_stream_update_event( + mock_camel_dict_to_snake_dict, ansible_aws_module, check_mode, module_source_params, current_mapping, api_params +): + function_name = "ansible-test-update-event-function" + ansible_aws_module.params.update({"lambda_function_arn": function_name}) + ansible_aws_module.params["source_params"].update(module_source_params) + ansible_aws_module.check_mode = check_mode + + client = MagicMock() + client.list_event_source_mappings = MagicMock() + existing_event_source = deepcopy(EventSourceMappings) + existing_event_source[0].update(current_mapping) + client.list_event_source_mappings.return_value = {"EventSourceMappings": existing_event_source} + + client.update_event_source_mapping = MagicMock() + event_source_updated = {"msg": "event source successfully updated."} + client.update_event_source_mapping.return_value = event_source_updated + mock_camel_dict_to_snake_dict.side_effect = lambda x: x + + result = lambda_event_stream(ansible_aws_module, client) + if not api_params: + assert dict(changed=False, events=existing_event_source) == result + client.update_event_source_mapping.assert_not_called() + elif check_mode: + assert dict(changed=True, events=existing_event_source) == result + client.update_event_source_mapping.assert_not_called() + else: + api_params.update({"FunctionName": function_name, "UUID": existing_event_source[0]["UUID"]}) + assert dict(changed=True, events=event_source_updated) == result + client.update_event_source_mapping.assert_called_once_with(**api_params) + + +@pytest.mark.parametrize( + "params, expected, exception, message, source_type", + [ + ( + { + "source_arn": "arn:aws:sqs:us-east-1:123456789012:ansible-test-28277052.fifo", + "enabled": True, + "batch_size": 100, + "starting_position": None, + "function_response_types": None, + "maximum_batching_window_in_seconds": None, + }, + None, + pytest.raises(SystemExit), + "For FIFO queues the maximum batch_size is 10.", + "sqs", + ), + ( + { + "source_arn": "arn:aws:sqs:us-east-1:123456789012:ansible-test-28277052.fifo", + "enabled": True, + "batch_size": 10, + "starting_position": None, + "function_response_types": None, + "maximum_batching_window_in_seconds": 1, + }, + None, + pytest.raises(SystemExit), + "maximum_batching_window_in_seconds is not supported by Amazon SQS FIFO event sources.", + "sqs", + ), + ( + { + "source_arn": "arn:aws:sqs:us-east-1:123456789012:ansible-test-28277052.fifo", + "enabled": True, + "batch_size": 10, + "starting_position": None, + "function_response_types": None, + "maximum_batching_window_in_seconds": None, + }, + { + "source_arn": "arn:aws:sqs:us-east-1:123456789012:ansible-test-28277052.fifo", + "enabled": True, + "batch_size": 10, + "starting_position": None, + "function_response_types": None, + "maximum_batching_window_in_seconds": None, + }, + does_not_raise(), + None, + "sqs", + ), + ( + { + "source_arn": "arn:aws:sqs:us-east-1:123456789012:ansible-test-28277052", + "enabled": True, + "batch_size": 11000, + "starting_position": None, + "function_response_types": None, + "maximum_batching_window_in_seconds": None, + }, + None, + pytest.raises(SystemExit), + "For standard queue batch_size must be lower than 10000.", + "sqs", + ), + ( + { + "source_arn": "arn:aws:sqs:us-east-1:123456789012:ansible-test-28277052", + "enabled": True, + "batch_size": 100, + "starting_position": None, + "function_response_types": None, + "maximum_batching_window_in_seconds": None, + }, + { + "source_arn": "arn:aws:sqs:us-east-1:123456789012:ansible-test-28277052", + "enabled": True, + "batch_size": 100, + "starting_position": None, + "function_response_types": None, + "maximum_batching_window_in_seconds": 1, + }, + does_not_raise(), + None, + "sqs", + ), + ( + { + "source_arn": "arn:aws:sqs:us-east-1:123456789012:ansible-test-28277052", + "enabled": True, + "starting_position": None, + "function_response_types": None, + "maximum_batching_window_in_seconds": None, + }, + { + "source_arn": "arn:aws:sqs:us-east-1:123456789012:ansible-test-28277052", + "enabled": True, + "batch_size": 100, + "starting_position": None, + "function_response_types": None, + "maximum_batching_window_in_seconds": 1, + }, + does_not_raise(), + None, + "stream", + ), + ( + { + "source_arn": "arn:aws:sqs:us-east-1:123456789012:ansible-test-28277052", + "enabled": True, + "starting_position": None, + "function_response_types": None, + }, + { + "source_arn": "arn:aws:sqs:us-east-1:123456789012:ansible-test-28277052", + "enabled": True, + "batch_size": 10, + "starting_position": None, + "function_response_types": None, + }, + does_not_raise(), + None, + "sqs", + ), + ( + { + "source_arn": "arn:aws:sqs:us-east-1:123456789012:ansible-test-28277052", + "enabled": True, + "batch_size": 10, + "starting_position": None, + "function_response_types": None, + "maximum_batching_window_in_seconds": None, + }, + None, + pytest.raises(SystemExit), + "batch_size for streams must be between 100 and 10000", + "stream", + ), + ], +) +def test__set_default_values(params, expected, exception, message, source_type): + result = None + module = MagicMock() + module.check_mode = False + module.params = { + "event_source": source_type, + "source_params": params, + } + module.fail_json = MagicMock() + module.fail_json.side_effect = SystemExit(message) + with exception as e: + result = set_default_values(module, params) + assert message is None or message in str(e) + if expected is not None: + assert result == expected |