diff --git a/.test_durations b/.test_durations index b7bdee05de234..caea8a23da46d 100644 --- a/.test_durations +++ b/.test_durations @@ -843,21 +843,21 @@ "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_put_events_with_input_transformer_input_template_string[\"{[/Check with special starting characters for event of type\"]": 0.11936591799985763, "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_put_events_with_input_transformer_missing_keys": 0.0006529859997499443, "tests/aws/services/events/test_events_inputs.py::test_put_event_input_path_and_input_transfomer": 0.0006545790001837304, - "tests/aws/services/events/test_events_integrations.py::test_put_events_with_target_firehose": 0.07406386900083817, - "tests/aws/services/events/test_events_integrations.py::test_put_events_with_target_kinesis": 0.9582597469998291, - "tests/aws/services/events/test_events_integrations.py::test_put_events_with_target_lambda": 4.074284712000008, - "tests/aws/services/events/test_events_integrations.py::test_put_events_with_target_lambda_list_entries_partial_match": 4.080728459999591, - "tests/aws/services/events/test_events_integrations.py::test_put_events_with_target_lambda_list_entry": 4.072307003000333, - "tests/aws/services/events/test_events_integrations.py::test_put_events_with_target_sns[domain]": 0.05766030499989938, - "tests/aws/services/events/test_events_integrations.py::test_put_events_with_target_sns[path]": 0.058427959999335144, - "tests/aws/services/events/test_events_integrations.py::test_put_events_with_target_sns[standard]": 0.06193175200041878, - "tests/aws/services/events/test_events_integrations.py::test_put_events_with_target_sqs": 0.05069734699964101, - "tests/aws/services/events/test_events_integrations.py::test_put_events_with_target_sqs_event_detail_match": 5.064694446999965, - "tests/aws/services/events/test_events_integrations.py::test_put_events_with_target_sqs_new_region": 0.021846191999884468, - "tests/aws/services/events/test_events_integrations.py::test_should_ignore_schedules_for_put_event": 11.069308752000325, - "tests/aws/services/events/test_events_integrations.py::test_trigger_event_on_ssm_change[domain]": 0.038843789999646106, - "tests/aws/services/events/test_events_integrations.py::test_trigger_event_on_ssm_change[path]": 0.040668131999609614, - "tests/aws/services/events/test_events_integrations.py::test_trigger_event_on_ssm_change[standard]": 0.041024010999535676, + "tests/aws/services/events/test_events_targets.py::test_put_events_with_target_firehose": 0.07406386900083817, + "tests/aws/services/events/test_events_targets.py::test_put_events_with_target_kinesis": 0.9582597469998291, + "tests/aws/services/events/test_events_targets.py::test_put_events_with_target_lambda": 4.074284712000008, + "tests/aws/services/events/test_events_targets.py::test_put_events_with_target_lambda_list_entries_partial_match": 4.080728459999591, + "tests/aws/services/events/test_events_targets.py::test_put_events_with_target_lambda_list_entry": 4.072307003000333, + "tests/aws/services/events/test_events_targets.py::test_put_events_with_target_sns[domain]": 0.05766030499989938, + "tests/aws/services/events/test_events_targets.py::test_put_events_with_target_sns[path]": 0.058427959999335144, + "tests/aws/services/events/test_events_targets.py::test_put_events_with_target_sns[standard]": 0.06193175200041878, + "tests/aws/services/events/test_events_targets.py::test_put_events_with_target_sqs": 0.05069734699964101, + "tests/aws/services/events/test_events_targets.py::test_put_events_with_target_sqs_event_detail_match": 5.064694446999965, + "tests/aws/services/events/test_events_targets.py::test_put_events_with_target_sqs_new_region": 0.021846191999884468, + "tests/aws/services/events/test_events_targets.py::test_should_ignore_schedules_for_put_event": 11.069308752000325, + "tests/aws/services/events/test_events_targets.py::test_trigger_event_on_ssm_change[domain]": 0.038843789999646106, + "tests/aws/services/events/test_events_targets.py::test_trigger_event_on_ssm_change[path]": 0.040668131999609614, + "tests/aws/services/events/test_events_targets.py::test_trigger_event_on_ssm_change[standard]": 0.041024010999535676, "tests/aws/services/events/test_events_rules.py::test_put_event_with_content_base_rule_in_pattern": 3.05817666799976, "tests/aws/services/events/test_events_rules.py::test_put_events_with_rule_anything_but_to_sqs": 5.094898402000126, "tests/aws/services/events/test_events_rules.py::test_put_events_with_rule_exists_false_to_sqs": 5.073232123999787, diff --git a/tests/aws/services/events/conftest.py b/tests/aws/services/events/conftest.py index 9c2692b8e3d9d..aaa34f563683b 100644 --- a/tests/aws/services/events/conftest.py +++ b/tests/aws/services/events/conftest.py @@ -416,3 +416,21 @@ def _add_resource_policy_logs_events_access(log_group_arn: str): for policy_name in policies: aws_client.logs.delete_resource_policy(policyName=policy_name) + + +@pytest.fixture +def events_create_connection(aws_client): + connections = [] + + def _create_connection(**kwargs): + response = aws_client.events.create_connection(**kwargs) + connections.append(kwargs["Name"]) + return response + + yield _create_connection + + for connection in connections: + try: + aws_client.events.delete_connection(Name=connection) + except Exception as e: + LOG.warning(f"Failed to delete connection {connection}: {e}") diff --git a/tests/aws/services/events/helper_functions.py b/tests/aws/services/events/helper_functions.py index 68856d4d636f1..048b6ea031b02 100644 --- a/tests/aws/services/events/helper_functions.py +++ b/tests/aws/services/events/helper_functions.py @@ -70,3 +70,14 @@ def collect_events() -> None: retry(collect_events, retries=retries, sleep=0.01) return events + + +def events_connection_wait_for_deleted(aws_client, connection_name: str) -> None: + def _wait_for_deleted(): + try: + aws_client.events.describe_connection(Name=connection_name) + except aws_client.events.exceptions.ResourceNotFoundException: + return + raise AssertionError(f"Connection {connection_name} was not deleted") + + retry(_wait_for_deleted, retries=3, sleep=1) diff --git a/tests/aws/services/events/test_events.py b/tests/aws/services/events/test_events.py index e8d2d8512fe84..4262706d43e21 100644 --- a/tests/aws/services/events/test_events.py +++ b/tests/aws/services/events/test_events.py @@ -24,6 +24,7 @@ from localstack.utils.sync import poll_condition, retry from tests.aws.services.events.conftest import assert_valid_event from tests.aws.services.events.helper_functions import ( + events_connection_wait_for_deleted, is_old_provider, is_v2_provider, sqs_collect_messages, @@ -83,6 +84,14 @@ } } +API_CONNECTION_TEST_PASSWORD = "test_pw_1984%&" +API_CONNECTION_TEST_USERNAME = "test_user_1984" + +API_CONNECTION_TEST_KEY_NAME = "test_key_1984" +API_CONNECTION_TEST_KEY_VALUE = "test_value_1984" + +OAUTH_PLAYGROUND_URL = "https://oauth.pstmn.io/v1/callback" + class TestEvents: @markers.aws.validated @@ -592,7 +601,7 @@ def test_list_event_buses_with_prefix(self, create_event_bus, aws_client, snapsh bus_name = f"unique-prefix-1234567890-{short_uid()}" snapshot.add_transformer(snapshot.transform.regex(bus_name, "")) - bus_name_not_match = "no-prefix-match" + bus_name_not_match = f"no-prefix-match-{short_uid()}" snapshot.add_transformer(snapshot.transform.regex(bus_name_not_match, "")) create_event_bus(Name=bus_name) @@ -1347,3 +1356,242 @@ def test_put_target_id_validation( {"Id": target_id, "Arn": queue_arn, "InputPath": "$.detail"}, ], ) + + +class TestConnection: + @markers.aws.validated + @pytest.mark.parametrize("auth_type", ["BASIC", "OAUTH_CLIENT_CREDENTIALS", "API_KEY"]) + @pytest.mark.parametrize("invocation_parameters", [True, False]) + def test_create_list_describe_update_delete_connection( + self, + auth_type, + invocation_parameters, + events_create_connection, + aws_client, + snapshot, + ): + # Specify auth parameters + if auth_type == "BASIC": + auth_parameters = { + "BasicAuthParameters": { + "Username": API_CONNECTION_TEST_USERNAME, + "Password": API_CONNECTION_TEST_PASSWORD, + } + } + if auth_type == "OAUTH_CLIENT_CREDENTIALS": + auth_parameters = { + "OAuthParameters": { + "ClientParameters": {"ClientID": "string", "ClientSecret": "string"}, + "AuthorizationEndpoint": OAUTH_PLAYGROUND_URL, + "HttpMethod": "PUT", # TODO test "GET" | "POST" + "OAuthHttpParameters": { + "HeaderParameters": [ + { + "Key": "Content_Type", + "Value": "'application/x-www-form-urlencoded'", + "IsValueSecret": False, + }, + ], + "QueryStringParameters": [ + { + "Key": "some_test_key", + "Value": "some_test_value", + "IsValueSecret": False, + }, + ], + "BodyParameters": [ + {"Key": "grant_type", "Value": "string", "IsValueSecret": True}, + { + "Key": "user", + "Value": API_CONNECTION_TEST_USERNAME, + "IsValueSecret": True, + }, + { + "Key": "password", + "Value": API_CONNECTION_TEST_PASSWORD, + "IsValueSecret": True, + }, + ], + }, + } + } + if auth_type == "API_KEY": + auth_parameters = { + "ApiKeyAuthParameters": { + "ApiKeyName": API_CONNECTION_TEST_KEY_NAME, + "ApiKeyValue": API_CONNECTION_TEST_KEY_VALUE, + } + } + + # Specify invocation parameters + if invocation_parameters: + invocation_http_parameters = { + "HeaderParameters": [ + {"Key": "string", "Value": "string", "IsValueSecret": True}, + ], + "QueryStringParameters": [ + {"Key": "string", "Value": "string", "IsValueSecret": False}, + ], + "BodyParameters": [ + {"Key": "string", "Value": "string", "IsValueSecret": True}, + ], + } + auth_parameters["InvocationHttpParameters"] = invocation_http_parameters + + # Test create connection + connection_name = f"test-connection-{short_uid()}" + response = events_create_connection( + Name=connection_name, + Description="test description", + AuthorizationType=auth_type, + AuthParameters=auth_parameters, + ) + connection_arn = response["ConnectionArn"] + connection_arn_id = connection_arn.split("/")[-1] + + snapshot.add_transformers_list( + [ + snapshot.transform.regex(connection_name, ""), + snapshot.transform.regex(connection_arn_id, ""), + ] + ) + snapshot.match("create-connection", response) + + response = aws_client.events.list_connections(NamePrefix=connection_name) + snapshot.match("list-connections", response) + + response = aws_client.events.describe_connection( + Name=connection_name, + ) + secret_arn = response["SecretArn"] + secret_arn_id = secret_arn.split("/")[-1] + + snapshot.add_transformer(snapshot.transform.regex(secret_arn_id, "")) + snapshot.match("describe-connection", response) + + response = aws_client.events.delete_connection(Name=connection_name) + snapshot.match("delete-connection", response) + + events_connection_wait_for_deleted(aws_client, connection_name) + + response = aws_client.events.list_connections(NamePrefix=connection_name) + snapshot.match("list-connections-after-delete", response) + + @markers.aws.validated + def test_list_connections_with_prefix(self, events_create_connection, aws_client, snapshot): + events = aws_client.events + connection_name = f"unique-prefix-1234567890-{short_uid()}" + snapshot.add_transformer(snapshot.transform.regex(connection_name, "")) + + connection_name_not_match = f"no-prefix-match-{short_uid()}" + snapshot.add_transformer( + snapshot.transform.regex(connection_name_not_match, "") + ) + + response = events_create_connection( + Name=connection_name, + Description="test description", + AuthorizationType="BASIC", + AuthParameters={ + "BasicAuthParameters": { + "Username": API_CONNECTION_TEST_USERNAME, + "Password": API_CONNECTION_TEST_PASSWORD, + } + }, + ) + connection_arn = response["ConnectionArn"] + connection_arn_id = connection_arn.split("/")[-1] + snapshot.add_transformer(snapshot.transform.regex(connection_arn_id, "")) + + events_create_connection( + Name=connection_name_not_match, + Description="test description", + AuthorizationType="BASIC", + AuthParameters={ + "BasicAuthParameters": { + "Username": API_CONNECTION_TEST_USERNAME, + "Password": API_CONNECTION_TEST_PASSWORD, + } + }, + ) + + response = events.list_connections(NamePrefix=connection_name) + snapshot.match("list-connections-prefix-complete-name", response) + + response = events.list_connections(NamePrefix=connection_name.split("-")[0]) + snapshot.match("list-connections-prefix", response) + + @markers.aws.validated + def test_list_connections_with_limit(self, events_create_connection, aws_client, snapshot): + snapshot.add_transformer(snapshot.transform.jsonpath("$..NextToken", "next_token")) + connection_name_prefix = f"test-connection-{short_uid()}" + snapshot.add_transformer( + snapshot.transform.regex(connection_name_prefix, "") + ) + count = 6 + + for i in range(count): + connection_name = f"{connection_name_prefix}-{i}" + response = events_create_connection( + Name=connection_name, + Description="test description", + AuthorizationType="BASIC", + AuthParameters={ + "BasicAuthParameters": { + "Username": API_CONNECTION_TEST_USERNAME, + "Password": API_CONNECTION_TEST_PASSWORD, + } + }, + ) + connection_arn = response["ConnectionArn"] + connection_arn_id = connection_arn.split("/")[-1] + snapshot.add_transformer( + snapshot.transform.regex(connection_arn_id, "") + ) + + response = aws_client.events.list_connections( + Limit=int(count / 2), NamePrefix=connection_name_prefix + ) + snapshot.match("list-connections-limit", response) + + response = aws_client.events.list_connections( + Limit=int(count / 2) + 2, + NextToken=response["NextToken"], + NamePrefix=connection_name_prefix, + ) + snapshot.match("list-connections-limit-next-token", response) + + @markers.aws.validated + def test_list_connection_with_state(self, events_create_connection, aws_client, snapshot): + connection_name = f"test-connection-{short_uid()}" + response = events_create_connection( + Name=connection_name, + Description="test description", + AuthorizationType="BASIC", + AuthParameters={ + "BasicAuthParameters": { + "Username": API_CONNECTION_TEST_USERNAME, + "Password": API_CONNECTION_TEST_PASSWORD, + } + }, + ) + connection_arn = response["ConnectionArn"] + connection_arn_id = connection_arn.split("/")[-1] + snapshot.add_transformers_list( + [ + snapshot.transform.regex(connection_arn_id, ""), + snapshot.transform.regex(connection_name, ""), + ] + ) + + def _wait_for_active(): + response = aws_client.events.describe_connection(Name=connection_name) + return response["ConnectionState"] == "AUTHORIZED" + + retry(_wait_for_active, retries=5, sleep=1) + + response = aws_client.events.list_connections(ConnectionState="AUTHORIZED") + snapshot.match("list-connections-state", response) + + response = aws_client.events.list_connections(ConnectionState="CREATING") + snapshot.match("list-connections-state-empty", response) diff --git a/tests/aws/services/events/test_events.snapshot.json b/tests/aws/services/events/test_events.snapshot.json index 424fa08196021..bbd421083d545 100644 --- a/tests/aws/services/events/test_events.snapshot.json +++ b/tests/aws/services/events/test_events.snapshot.json @@ -1208,5 +1208,715 @@ } } } + }, + "tests/aws/services/events/test_events.py::TestConnection::test_create_list_describe_update_delete_connection": { + "recorded-date": "18-06-2024, 15:29:18", + "recorded-content": { + "create-connection": { + "ConnectionArn": "arn:aws:events::111111111111:connection//ebe99901-153c-495a-b8c5-7f789a0bb127", + "ConnectionState": "AUTHORIZED", + "CreationTime": "datetime", + "LastModifiedTime": "datetime", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/events/test_events.py::TestConnection::test_create_list_describe_update_delete_connection[True-BASIC]": { + "recorded-date": "19-06-2024, 14:09:43", + "recorded-content": { + "create-connection": { + "ConnectionArn": "arn:aws:events::111111111111:connection//", + "ConnectionState": "AUTHORIZED", + "CreationTime": "datetime", + "LastModifiedTime": "datetime", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "list-connections": { + "Connections": [ + { + "AuthorizationType": "BASIC", + "ConnectionArn": "arn:aws:events::111111111111:connection//", + "ConnectionState": "AUTHORIZED", + "CreationTime": "datetime", + "LastAuthorizedTime": "datetime", + "LastModifiedTime": "datetime", + "Name": "" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-connection": { + "AuthParameters": { + "BasicAuthParameters": { + "Username": "test_user_1984" + }, + "InvocationHttpParameters": { + "BodyParameters": [ + { + "IsValueSecret": true, + "Key": "string" + } + ], + "HeaderParameters": [ + { + "IsValueSecret": true, + "Key": "string" + } + ], + "QueryStringParameters": [ + { + "IsValueSecret": false, + "Key": "string", + "Value": "string" + } + ] + } + }, + "AuthorizationType": "BASIC", + "ConnectionArn": "arn:aws:events::111111111111:connection//", + "ConnectionState": "AUTHORIZED", + "CreationTime": "datetime", + "Description": "test description", + "LastAuthorizedTime": "datetime", + "LastModifiedTime": "datetime", + "Name": "", + "SecretArn": "arn:aws:secretsmanager::111111111111:secret:events!connection//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "delete-connection": { + "ConnectionArn": "arn:aws:events::111111111111:connection//", + "ConnectionState": "DELETING", + "CreationTime": "datetime", + "LastAuthorizedTime": "datetime", + "LastModifiedTime": "datetime", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "list-connections-after-delete": { + "Connections": [], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/events/test_events.py::TestConnection::test_create_list_describe_update_delete_connection[True-OAUTH_CLIENT_CREDENTIALS]": { + "recorded-date": "19-06-2024, 14:09:47", + "recorded-content": { + "create-connection": { + "ConnectionArn": "arn:aws:events::111111111111:connection//", + "ConnectionState": "AUTHORIZING", + "CreationTime": "datetime", + "LastModifiedTime": "datetime", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "list-connections": { + "Connections": [ + { + "AuthorizationType": "OAUTH_CLIENT_CREDENTIALS", + "ConnectionArn": "arn:aws:events::111111111111:connection//", + "ConnectionState": "AUTHORIZING", + "CreationTime": "datetime", + "LastAuthorizedTime": "datetime", + "LastModifiedTime": "datetime", + "Name": "" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-connection": { + "AuthParameters": { + "InvocationHttpParameters": { + "BodyParameters": [ + { + "IsValueSecret": true, + "Key": "string" + } + ], + "HeaderParameters": [ + { + "IsValueSecret": true, + "Key": "string" + } + ], + "QueryStringParameters": [ + { + "IsValueSecret": false, + "Key": "string", + "Value": "string" + } + ] + }, + "OAuthParameters": { + "AuthorizationEndpoint": "https://oauth.pstmn.io/v1/callback", + "ClientParameters": { + "ClientID": "string" + }, + "HttpMethod": "PUT", + "OAuthHttpParameters": { + "BodyParameters": [ + { + "IsValueSecret": true, + "Key": "grant_type" + }, + { + "IsValueSecret": true, + "Key": "user" + }, + { + "IsValueSecret": true, + "Key": "password" + } + ], + "HeaderParameters": [ + { + "IsValueSecret": false, + "Key": "Content_Type", + "Value": "'application/x-www-form-urlencoded'" + } + ], + "QueryStringParameters": [ + { + "IsValueSecret": false, + "Key": "some_test_key", + "Value": "some_test_value" + } + ] + } + } + }, + "AuthorizationType": "OAUTH_CLIENT_CREDENTIALS", + "ConnectionArn": "arn:aws:events::111111111111:connection//", + "ConnectionState": "AUTHORIZING", + "CreationTime": "datetime", + "Description": "test description", + "LastAuthorizedTime": "datetime", + "LastModifiedTime": "datetime", + "Name": "", + "SecretArn": "arn:aws:secretsmanager::111111111111:secret:events!connection//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "delete-connection": { + "ConnectionArn": "arn:aws:events::111111111111:connection//", + "ConnectionState": "DELETING", + "CreationTime": "datetime", + "LastAuthorizedTime": "datetime", + "LastModifiedTime": "datetime", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "list-connections-after-delete": { + "Connections": [], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/events/test_events.py::TestConnection::test_create_list_describe_update_delete_connection[True-API_KEY]": { + "recorded-date": "19-06-2024, 14:09:50", + "recorded-content": { + "create-connection": { + "ConnectionArn": "arn:aws:events::111111111111:connection//", + "ConnectionState": "AUTHORIZED", + "CreationTime": "datetime", + "LastModifiedTime": "datetime", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "list-connections": { + "Connections": [ + { + "AuthorizationType": "API_KEY", + "ConnectionArn": "arn:aws:events::111111111111:connection//", + "ConnectionState": "AUTHORIZED", + "CreationTime": "datetime", + "LastAuthorizedTime": "datetime", + "LastModifiedTime": "datetime", + "Name": "" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-connection": { + "AuthParameters": { + "ApiKeyAuthParameters": { + "ApiKeyName": "test_key_1984" + }, + "InvocationHttpParameters": { + "BodyParameters": [ + { + "IsValueSecret": true, + "Key": "string" + } + ], + "HeaderParameters": [ + { + "IsValueSecret": true, + "Key": "string" + } + ], + "QueryStringParameters": [ + { + "IsValueSecret": false, + "Key": "string", + "Value": "string" + } + ] + } + }, + "AuthorizationType": "API_KEY", + "ConnectionArn": "arn:aws:events::111111111111:connection//", + "ConnectionState": "AUTHORIZED", + "CreationTime": "datetime", + "Description": "test description", + "LastAuthorizedTime": "datetime", + "LastModifiedTime": "datetime", + "Name": "", + "SecretArn": "arn:aws:secretsmanager::111111111111:secret:events!connection//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "delete-connection": { + "ConnectionArn": "arn:aws:events::111111111111:connection//", + "ConnectionState": "DELETING", + "CreationTime": "datetime", + "LastAuthorizedTime": "datetime", + "LastModifiedTime": "datetime", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "list-connections-after-delete": { + "Connections": [], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/events/test_events.py::TestConnection::test_create_list_describe_update_delete_connection[False-BASIC]": { + "recorded-date": "19-06-2024, 14:09:53", + "recorded-content": { + "create-connection": { + "ConnectionArn": "arn:aws:events::111111111111:connection//", + "ConnectionState": "AUTHORIZED", + "CreationTime": "datetime", + "LastModifiedTime": "datetime", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "list-connections": { + "Connections": [ + { + "AuthorizationType": "BASIC", + "ConnectionArn": "arn:aws:events::111111111111:connection//", + "ConnectionState": "AUTHORIZED", + "CreationTime": "datetime", + "LastAuthorizedTime": "datetime", + "LastModifiedTime": "datetime", + "Name": "" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-connection": { + "AuthParameters": { + "BasicAuthParameters": { + "Username": "test_user_1984" + } + }, + "AuthorizationType": "BASIC", + "ConnectionArn": "arn:aws:events::111111111111:connection//", + "ConnectionState": "AUTHORIZED", + "CreationTime": "datetime", + "Description": "test description", + "LastAuthorizedTime": "datetime", + "LastModifiedTime": "datetime", + "Name": "", + "SecretArn": "arn:aws:secretsmanager::111111111111:secret:events!connection//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "delete-connection": { + "ConnectionArn": "arn:aws:events::111111111111:connection//", + "ConnectionState": "DELETING", + "CreationTime": "datetime", + "LastAuthorizedTime": "datetime", + "LastModifiedTime": "datetime", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "list-connections-after-delete": { + "Connections": [], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/events/test_events.py::TestConnection::test_create_list_describe_update_delete_connection[False-OAUTH_CLIENT_CREDENTIALS]": { + "recorded-date": "19-06-2024, 14:09:55", + "recorded-content": { + "create-connection": { + "ConnectionArn": "arn:aws:events::111111111111:connection//", + "ConnectionState": "AUTHORIZING", + "CreationTime": "datetime", + "LastModifiedTime": "datetime", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "list-connections": { + "Connections": [ + { + "AuthorizationType": "OAUTH_CLIENT_CREDENTIALS", + "ConnectionArn": "arn:aws:events::111111111111:connection//", + "ConnectionState": "AUTHORIZING", + "CreationTime": "datetime", + "LastAuthorizedTime": "datetime", + "LastModifiedTime": "datetime", + "Name": "" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-connection": { + "AuthParameters": { + "OAuthParameters": { + "AuthorizationEndpoint": "https://oauth.pstmn.io/v1/callback", + "ClientParameters": { + "ClientID": "string" + }, + "HttpMethod": "PUT", + "OAuthHttpParameters": { + "BodyParameters": [ + { + "IsValueSecret": true, + "Key": "grant_type" + }, + { + "IsValueSecret": true, + "Key": "user" + }, + { + "IsValueSecret": true, + "Key": "password" + } + ], + "HeaderParameters": [ + { + "IsValueSecret": false, + "Key": "Content_Type", + "Value": "'application/x-www-form-urlencoded'" + } + ], + "QueryStringParameters": [ + { + "IsValueSecret": false, + "Key": "some_test_key", + "Value": "some_test_value" + } + ] + } + } + }, + "AuthorizationType": "OAUTH_CLIENT_CREDENTIALS", + "ConnectionArn": "arn:aws:events::111111111111:connection//", + "ConnectionState": "AUTHORIZING", + "CreationTime": "datetime", + "Description": "test description", + "LastAuthorizedTime": "datetime", + "LastModifiedTime": "datetime", + "Name": "", + "SecretArn": "arn:aws:secretsmanager::111111111111:secret:events!connection//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "delete-connection": { + "ConnectionArn": "arn:aws:events::111111111111:connection//", + "ConnectionState": "DELETING", + "CreationTime": "datetime", + "LastAuthorizedTime": "datetime", + "LastModifiedTime": "datetime", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "list-connections-after-delete": { + "Connections": [], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/events/test_events.py::TestConnection::test_create_list_describe_update_delete_connection[False-API_KEY]": { + "recorded-date": "19-06-2024, 14:09:58", + "recorded-content": { + "create-connection": { + "ConnectionArn": "arn:aws:events::111111111111:connection//", + "ConnectionState": "AUTHORIZED", + "CreationTime": "datetime", + "LastModifiedTime": "datetime", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "list-connections": { + "Connections": [ + { + "AuthorizationType": "API_KEY", + "ConnectionArn": "arn:aws:events::111111111111:connection//", + "ConnectionState": "AUTHORIZED", + "CreationTime": "datetime", + "LastAuthorizedTime": "datetime", + "LastModifiedTime": "datetime", + "Name": "" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-connection": { + "AuthParameters": { + "ApiKeyAuthParameters": { + "ApiKeyName": "test_key_1984" + } + }, + "AuthorizationType": "API_KEY", + "ConnectionArn": "arn:aws:events::111111111111:connection//", + "ConnectionState": "AUTHORIZED", + "CreationTime": "datetime", + "Description": "test description", + "LastAuthorizedTime": "datetime", + "LastModifiedTime": "datetime", + "Name": "", + "SecretArn": "arn:aws:secretsmanager::111111111111:secret:events!connection//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "delete-connection": { + "ConnectionArn": "arn:aws:events::111111111111:connection//", + "ConnectionState": "DELETING", + "CreationTime": "datetime", + "LastAuthorizedTime": "datetime", + "LastModifiedTime": "datetime", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "list-connections-after-delete": { + "Connections": [], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/events/test_events.py::TestConnection::test_list_connections_with_prefix": { + "recorded-date": "19-06-2024, 14:22:55", + "recorded-content": { + "list-connections-prefix-complete-name": { + "Connections": [ + { + "AuthorizationType": "BASIC", + "ConnectionArn": "arn:aws:events::111111111111:connection//", + "ConnectionState": "AUTHORIZED", + "CreationTime": "datetime", + "LastAuthorizedTime": "datetime", + "LastModifiedTime": "datetime", + "Name": "" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "list-connections-prefix": { + "Connections": [ + { + "AuthorizationType": "BASIC", + "ConnectionArn": "arn:aws:events::111111111111:connection//", + "ConnectionState": "AUTHORIZED", + "CreationTime": "datetime", + "LastAuthorizedTime": "datetime", + "LastModifiedTime": "datetime", + "Name": "" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/events/test_events.py::TestConnection::test_list_connections_with_limit": { + "recorded-date": "19-06-2024, 14:31:49", + "recorded-content": { + "list-connections-limit": { + "Connections": [ + { + "AuthorizationType": "BASIC", + "ConnectionArn": "arn:aws:events::111111111111:connection/-0/", + "ConnectionState": "AUTHORIZED", + "CreationTime": "datetime", + "LastAuthorizedTime": "datetime", + "LastModifiedTime": "datetime", + "Name": "-0" + }, + { + "AuthorizationType": "BASIC", + "ConnectionArn": "arn:aws:events::111111111111:connection/-1/", + "ConnectionState": "AUTHORIZED", + "CreationTime": "datetime", + "LastAuthorizedTime": "datetime", + "LastModifiedTime": "datetime", + "Name": "-1" + }, + { + "AuthorizationType": "BASIC", + "ConnectionArn": "arn:aws:events::111111111111:connection/-2/", + "ConnectionState": "AUTHORIZED", + "CreationTime": "datetime", + "LastAuthorizedTime": "datetime", + "LastModifiedTime": "datetime", + "Name": "-2" + } + ], + "NextToken": "", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "list-connections-limit-next-token": { + "Connections": [ + { + "AuthorizationType": "BASIC", + "ConnectionArn": "arn:aws:events::111111111111:connection/-3/", + "ConnectionState": "AUTHORIZED", + "CreationTime": "datetime", + "LastAuthorizedTime": "datetime", + "LastModifiedTime": "datetime", + "Name": "-3" + }, + { + "AuthorizationType": "BASIC", + "ConnectionArn": "arn:aws:events::111111111111:connection/-4/", + "ConnectionState": "AUTHORIZED", + "CreationTime": "datetime", + "LastAuthorizedTime": "datetime", + "LastModifiedTime": "datetime", + "Name": "-4" + }, + { + "AuthorizationType": "BASIC", + "ConnectionArn": "arn:aws:events::111111111111:connection/-5/", + "ConnectionState": "AUTHORIZED", + "CreationTime": "datetime", + "LastAuthorizedTime": "datetime", + "LastModifiedTime": "datetime", + "Name": "-5" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/events/test_events.py::TestConnection::test_list_connection_with_state": { + "recorded-date": "19-06-2024, 14:38:29", + "recorded-content": { + "list-connections-state": { + "Connections": [ + { + "AuthorizationType": "BASIC", + "ConnectionArn": "arn:aws:events::111111111111:connection//", + "ConnectionState": "AUTHORIZED", + "CreationTime": "datetime", + "LastAuthorizedTime": "datetime", + "LastModifiedTime": "datetime", + "Name": "" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "list-connections-state-empty": { + "Connections": [], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } } } diff --git a/tests/aws/services/events/test_events.validation.json b/tests/aws/services/events/test_events.validation.json index dd22b7d3333c7..abc660072fd2c 100644 --- a/tests/aws/services/events/test_events.validation.json +++ b/tests/aws/services/events/test_events.validation.json @@ -1,4 +1,34 @@ { + "tests/aws/services/events/test_events.py::TestConnection::test_create_list_describe_update_delete_connection": { + "last_validated_date": "2024-06-18T15:29:18+00:00" + }, + "tests/aws/services/events/test_events.py::TestConnection::test_create_list_describe_update_delete_connection[False-API_KEY]": { + "last_validated_date": "2024-06-19T14:09:58+00:00" + }, + "tests/aws/services/events/test_events.py::TestConnection::test_create_list_describe_update_delete_connection[False-BASIC]": { + "last_validated_date": "2024-06-19T14:09:53+00:00" + }, + "tests/aws/services/events/test_events.py::TestConnection::test_create_list_describe_update_delete_connection[False-OAUTH_CLIENT_CREDENTIALS]": { + "last_validated_date": "2024-06-19T14:09:55+00:00" + }, + "tests/aws/services/events/test_events.py::TestConnection::test_create_list_describe_update_delete_connection[True-API_KEY]": { + "last_validated_date": "2024-06-19T14:09:50+00:00" + }, + "tests/aws/services/events/test_events.py::TestConnection::test_create_list_describe_update_delete_connection[True-BASIC]": { + "last_validated_date": "2024-06-19T14:09:43+00:00" + }, + "tests/aws/services/events/test_events.py::TestConnection::test_create_list_describe_update_delete_connection[True-OAUTH_CLIENT_CREDENTIALS]": { + "last_validated_date": "2024-06-19T14:09:47+00:00" + }, + "tests/aws/services/events/test_events.py::TestConnection::test_list_connection_with_state": { + "last_validated_date": "2024-06-19T14:38:29+00:00" + }, + "tests/aws/services/events/test_events.py::TestConnection::test_list_connections_with_limit": { + "last_validated_date": "2024-06-19T14:31:49+00:00" + }, + "tests/aws/services/events/test_events.py::TestConnection::test_list_connections_with_prefix": { + "last_validated_date": "2024-06-19T14:22:55+00:00" + }, "tests/aws/services/events/test_events.py::TestEventBus::test_create_list_describe_delete_custom_event_buses[regions0]": { "last_validated_date": "2024-04-29T13:15:44+00:00" }, diff --git a/tests/aws/services/events/test_events_integrations.py b/tests/aws/services/events/test_events_integrations.py deleted file mode 100644 index 2a492729ddc6b..0000000000000 --- a/tests/aws/services/events/test_events_integrations.py +++ /dev/null @@ -1,699 +0,0 @@ -"""Tests for integrations between AWS EventBridge and other AWS services.""" - -import json -from datetime import datetime - -import pytest - -from localstack import config -from localstack.aws.api.lambda_ import Runtime -from localstack.testing.config import TEST_AWS_ACCOUNT_ID, TEST_AWS_REGION_NAME -from localstack.testing.pytest import markers -from localstack.utils.aws import arns, resources -from localstack.utils.strings import short_uid -from localstack.utils.sync import retry -from localstack.utils.testutil import check_expected_lambda_log_events_length -from tests.aws.services.events.conftest import assert_valid_event -from tests.aws.services.events.helper_functions import is_v2_provider, sqs_collect_messages -from tests.aws.services.events.test_events import EVENT_DETAIL, TEST_EVENT_PATTERN -from tests.aws.services.lambda_.test_lambda import TEST_LAMBDA_PYTHON_ECHO - - -@markers.aws.validated -def test_put_events_with_target_sqs(put_events_with_filter_to_sqs, snapshot): - entries = [ - { - "Source": TEST_EVENT_PATTERN["source"][0], - "DetailType": TEST_EVENT_PATTERN["detail-type"][0], - "Detail": json.dumps(EVENT_DETAIL), - } - ] - message = put_events_with_filter_to_sqs( - pattern=TEST_EVENT_PATTERN, - entries_asserts=[(entries, True)], - ) - snapshot.add_transformers_list( - [ - snapshot.transform.key_value("ReceiptHandle", reference_replacement=False), - snapshot.transform.key_value("MD5OfBody", reference_replacement=False), - ], - ) - snapshot.match("message", message) - - -@markers.aws.unknown -@pytest.mark.skipif(is_v2_provider(), reason="V2 provider does not support this feature yet") -def test_put_events_with_target_sqs_new_region(aws_client_factory): - events_client = aws_client_factory(region_name="eu-west-1").events - queue_name = "queue-{}".format(short_uid()) - rule_name = "rule-{}".format(short_uid()) - target_id = "target-{}".format(short_uid()) - bus_name = "bus-{}".format(short_uid()) - - sqs_client = aws_client_factory(region_name="eu-west-1").sqs - sqs_client.create_queue(QueueName=queue_name) - queue_arn = arns.sqs_queue_arn(queue_name, TEST_AWS_ACCOUNT_ID, TEST_AWS_REGION_NAME) - - events_client.create_event_bus(Name=bus_name) - - events_client.put_rule( - Name=rule_name, - EventBusName=bus_name, - EventPattern=json.dumps(TEST_EVENT_PATTERN), - ) - - events_client.put_targets( - Rule=rule_name, - EventBusName=bus_name, - Targets=[{"Id": target_id, "Arn": queue_arn}], - ) - - response = events_client.put_events( - Entries=[ - { - "Source": "com.mycompany.myapp", - "Detail": '{ "key1": "value1", "key": "value2" }', - "Resources": [], - "DetailType": "myDetailType", - } - ] - ) - assert "Entries" in response - assert len(response.get("Entries")) == 1 - assert "EventId" in response.get("Entries")[0] - - -@markers.aws.validated -@pytest.mark.skipif(is_v2_provider(), reason="V2 provider does not support this feature yet") -def test_put_events_with_target_sqs_event_detail_match(put_events_with_filter_to_sqs, snapshot): - entries1 = [ - { - "Source": TEST_EVENT_PATTERN["source"][0], - "DetailType": TEST_EVENT_PATTERN["detail-type"][0], - "Detail": json.dumps({"EventType": "1"}), - } - ] - entries2 = [ - { - "Source": TEST_EVENT_PATTERN["source"][0], - "DetailType": TEST_EVENT_PATTERN["detail-type"][0], - "Detail": json.dumps({"EventType": "2"}), - } - ] - entries_asserts = [(entries1, True), (entries2, False)] - messages = put_events_with_filter_to_sqs( - pattern={"detail": {"EventType": ["0", "1"]}}, - entries_asserts=entries_asserts, - input_path="$.detail", - ) - - snapshot.add_transformers_list( - [ - snapshot.transform.key_value("ReceiptHandle", reference_replacement=False), - snapshot.transform.key_value("MD5OfBody", reference_replacement=False), - ], - ) - snapshot.match("messages", messages) - - -# TODO: further unify/parameterize the tests for the different target types below - - -@markers.aws.needs_fixing -@pytest.mark.parametrize("strategy", ["standard", "domain", "path"]) -def test_put_events_with_target_sns( - monkeypatch, - sns_subscription, - aws_client, - clean_up, - strategy, -): - monkeypatch.setattr(config, "SQS_ENDPOINT_STRATEGY", strategy) - - queue_name = "test-%s" % short_uid() - rule_name = "rule-{}".format(short_uid()) - target_id = "target-{}".format(short_uid()) - bus_name = "bus-{}".format(short_uid()) - - topic_name = "topic-{}".format(short_uid()) - topic_arn = aws_client.sns.create_topic(Name=topic_name)["TopicArn"] - - queue_url = aws_client.sqs.create_queue(QueueName=queue_name)["QueueUrl"] - queue_arn = arns.sqs_queue_arn(queue_name, TEST_AWS_ACCOUNT_ID, TEST_AWS_REGION_NAME) - - sns_subscription(TopicArn=topic_arn, Protocol="sqs", Endpoint=queue_arn) - - aws_client.events.create_event_bus(Name=bus_name) - aws_client.events.put_rule( - Name=rule_name, - EventBusName=bus_name, - EventPattern=json.dumps(TEST_EVENT_PATTERN), - ) - rs = aws_client.events.put_targets( - Rule=rule_name, - EventBusName=bus_name, - Targets=[{"Id": target_id, "Arn": topic_arn}], - ) - - assert "FailedEntryCount" in rs - assert "FailedEntries" in rs - assert rs["FailedEntryCount"] == 0 - assert rs["FailedEntries"] == [] - - aws_client.events.put_events( - Entries=[ - { - "EventBusName": bus_name, - "Source": TEST_EVENT_PATTERN["source"][0], - "DetailType": TEST_EVENT_PATTERN["detail-type"][0], - "Detail": json.dumps(EVENT_DETAIL), - } - ] - ) - - messages = sqs_collect_messages(aws_client, queue_url, min_events=1, retries=3) - assert len(messages) == 1 - - actual_event = json.loads(messages[0]["Body"]).get("Message") - assert_valid_event(actual_event) - assert json.loads(actual_event).get("detail") == EVENT_DETAIL - - # clean up - aws_client.sns.delete_topic(TopicArn=topic_arn) - clean_up( - bus_name=bus_name, - rule_name=rule_name, - target_ids=target_id, - queue_url=queue_url, - ) - - -@markers.aws.needs_fixing -def test_put_events_with_target_lambda(create_lambda_function, cleanups, aws_client, clean_up): - rule_name = f"rule-{short_uid()}" - function_name = f"lambda-func-{short_uid()}" - target_id = f"target-{short_uid()}" - bus_name = f"bus-{short_uid()}" - - # clean up - cleanups.append(lambda: aws_client.lambda_.delete_function(FunctionName=function_name)) - cleanups.append(lambda: clean_up(bus_name=bus_name, rule_name=rule_name, target_ids=target_id)) - - rs = create_lambda_function( - handler_file=TEST_LAMBDA_PYTHON_ECHO, - func_name=function_name, - runtime=Runtime.python3_9, - ) - - func_arn = rs["CreateFunctionResponse"]["FunctionArn"] - - aws_client.events.create_event_bus(Name=bus_name) - aws_client.events.put_rule( - Name=rule_name, - EventBusName=bus_name, - EventPattern=json.dumps(TEST_EVENT_PATTERN), - ) - rs = aws_client.events.put_targets( - Rule=rule_name, - EventBusName=bus_name, - Targets=[{"Id": target_id, "Arn": func_arn}], - ) - - assert "FailedEntryCount" in rs - assert "FailedEntries" in rs - assert rs["FailedEntryCount"] == 0 - assert rs["FailedEntries"] == [] - - aws_client.events.put_events( - Entries=[ - { - "EventBusName": bus_name, - "Source": TEST_EVENT_PATTERN["source"][0], - "DetailType": TEST_EVENT_PATTERN["detail-type"][0], - "Detail": json.dumps(EVENT_DETAIL), - } - ] - ) - - # Get lambda's log events - events = retry( - check_expected_lambda_log_events_length, - retries=3, - sleep=1, - function_name=function_name, - expected_length=1, - logs_client=aws_client.logs, - ) - actual_event = events[0] - assert_valid_event(actual_event) - assert actual_event["detail"] == EVENT_DETAIL - - -@markers.aws.validated -def test_put_events_with_target_lambda_list_entry( - create_lambda_function, cleanups, aws_client, clean_up, snapshot -): - rule_name = f"rule-{short_uid()}" - function_name = f"lambda-func-{short_uid()}" - target_id = f"target-{short_uid()}" - bus_name = f"bus-{short_uid()}" - - # clean up - cleanups.append(lambda: clean_up(bus_name=bus_name, rule_name=rule_name, target_ids=target_id)) - - create_lambda_response = create_lambda_function( - handler_file=TEST_LAMBDA_PYTHON_ECHO, - func_name=function_name, - runtime=Runtime.python3_12, - ) - - func_arn = create_lambda_response["CreateFunctionResponse"]["FunctionArn"] - - event_pattern = {"detail": {"payload": {"automations": {"id": [{"exists": True}]}}}} - - aws_client.events.create_event_bus(Name=bus_name) - put_rule_response = aws_client.events.put_rule( - Name=rule_name, - EventBusName=bus_name, - EventPattern=json.dumps(event_pattern), - ) - aws_client.lambda_.add_permission( - FunctionName=function_name, - StatementId=f"{rule_name}-Event", - Action="lambda:InvokeFunction", - Principal="events.amazonaws.com", - SourceArn=put_rule_response["RuleArn"], - ) - put_target_response = aws_client.events.put_targets( - Rule=rule_name, - EventBusName=bus_name, - Targets=[{"Id": target_id, "Arn": func_arn}], - ) - - assert "FailedEntryCount" in put_target_response - assert "FailedEntries" in put_target_response - assert put_target_response["FailedEntryCount"] == 0 - assert put_target_response["FailedEntries"] == [] - - event_detail = { - "payload": { - "userId": 10, - "businessId": 3, - "channelId": 6, - "card": {"foo": "bar"}, - "targetEntity": True, - "entityAuditTrailEvent": {"foo": "bar"}, - "automations": [ - { - "id": "123", - "actions": [ - { - "id": "321", - "type": "SEND_NOTIFICATION", - "settings": { - "message": "", - "recipientEmails": [], - "subject": "", - "type": "SEND_NOTIFICATION", - }, - } - ], - } - ], - } - } - aws_client.events.put_events( - Entries=[ - { - "EventBusName": bus_name, - "Source": TEST_EVENT_PATTERN["source"][0], - "DetailType": TEST_EVENT_PATTERN["detail-type"][0], - "Detail": json.dumps(event_detail), - } - ] - ) - - # Get lambda's log events - events = retry( - check_expected_lambda_log_events_length, - retries=15, - sleep=1, - function_name=function_name, - expected_length=1, - logs_client=aws_client.logs, - ) - snapshot.match("events", events) - - -@markers.aws.validated -def test_put_events_with_target_lambda_list_entries_partial_match( - create_lambda_function, cleanups, aws_client, clean_up, snapshot -): - rule_name = f"rule-{short_uid()}" - function_name = f"lambda-func-{short_uid()}" - target_id = f"target-{short_uid()}" - bus_name = f"bus-{short_uid()}" - - # clean up - cleanups.append(lambda: clean_up(bus_name=bus_name, rule_name=rule_name, target_ids=target_id)) - - rs = create_lambda_function( - handler_file=TEST_LAMBDA_PYTHON_ECHO, - func_name=function_name, - runtime=Runtime.python3_12, - ) - - func_arn = rs["CreateFunctionResponse"]["FunctionArn"] - - event_pattern = {"detail": {"payload": {"automations": {"id": [{"exists": True}]}}}} - - aws_client.events.create_event_bus(Name=bus_name) - rs = aws_client.events.put_rule( - Name=rule_name, - EventBusName=bus_name, - EventPattern=json.dumps(event_pattern), - ) - aws_client.lambda_.add_permission( - FunctionName=function_name, - StatementId=f"{rule_name}-Event", - Action="lambda:InvokeFunction", - Principal="events.amazonaws.com", - SourceArn=rs["RuleArn"], - ) - rs = aws_client.events.put_targets( - Rule=rule_name, - EventBusName=bus_name, - Targets=[{"Id": target_id, "Arn": func_arn}], - ) - - assert "FailedEntryCount" in rs - assert "FailedEntries" in rs - assert rs["FailedEntryCount"] == 0 - assert rs["FailedEntries"] == [] - - event_detail_partial_match = { - "payload": { - "userId": 10, - "businessId": 3, - "channelId": 6, - "card": {"foo": "bar"}, - "targetEntity": True, - "entityAuditTrailEvent": {"foo": "bar"}, - "automations": [ - {"foo": "bar"}, - { - "id": "123", - "actions": [ - { - "id": "321", - "type": "SEND_NOTIFICATION", - "settings": { - "message": "", - "recipientEmails": [], - "subject": "", - "type": "SEND_NOTIFICATION", - }, - } - ], - }, - {"bar": "foo"}, - ], - } - } - aws_client.events.put_events( - Entries=[ - { - "EventBusName": bus_name, - "Source": TEST_EVENT_PATTERN["source"][0], - "DetailType": TEST_EVENT_PATTERN["detail-type"][0], - "Detail": json.dumps(event_detail_partial_match), - }, - ] - ) - - # Get lambda's log events - events = retry( - check_expected_lambda_log_events_length, - retries=15, - sleep=1, - function_name=function_name, - expected_length=1, - logs_client=aws_client.logs, - ) - snapshot.match("events", events) - - -@markers.aws.validated -@pytest.mark.skipif(is_v2_provider(), reason="V2 provider does not support this feature yet") -def test_should_ignore_schedules_for_put_event(create_lambda_function, cleanups, aws_client): - """Regression test for https://github.com/localstack/localstack/issues/7847""" - fn_name = f"test-event-fn-{short_uid()}" - create_lambda_function( - func_name=fn_name, - handler_file=TEST_LAMBDA_PYTHON_ECHO, - runtime=Runtime.python3_9, - client=aws_client.lambda_, - ) - - aws_client.lambda_.add_permission( - FunctionName=fn_name, - StatementId="AllowFnInvokeStatement", - Action="lambda:InvokeFunction", - Principal="events.amazonaws.com", - ) - - fn_arn = aws_client.lambda_.get_function(FunctionName=fn_name)["Configuration"]["FunctionArn"] - aws_client.events.put_rule( - Name="ScheduledLambda", ScheduleExpression="rate(1 minute)" - ) # every minute, can't go lower than that - cleanups.append(lambda: aws_client.events.delete_rule(Name="ScheduledLambda")) - aws_client.events.put_targets( - Rule="ScheduledLambda", Targets=[{"Id": "calllambda1", "Arn": fn_arn}] - ) - cleanups.append( - lambda: aws_client.events.remove_targets(Rule="ScheduledLambda", Ids=["calllambda1"]) - ) - - aws_client.events.put_events( - Entries=[ - { - "Source": "MySource", - "DetailType": "CustomType", - "Detail": json.dumps({"message": "manually invoked"}), - } - ] - ) - - def check_invocation(): - events_after = aws_client.logs.filter_log_events(logGroupName=f"/aws/lambda/{fn_name}") - # the custom sent event should NOT trigger the lambda (!) - assert len([e for e in events_after["events"] if "START" in e["message"]]) >= 1 - assert len([e for e in events_after["events"] if "manually invoked" in e["message"]]) == 0 - - retry(check_invocation, sleep=5, retries=15) - - -@markers.aws.needs_fixing -def test_put_events_with_target_firehose(aws_client, clean_up): - s3_bucket = "s3-{}".format(short_uid()) - s3_prefix = "testeventdata" - stream_name = "firehose-{}".format(short_uid()) - rule_name = "rule-{}".format(short_uid()) - target_id = "target-{}".format(short_uid()) - bus_name = "bus-{}".format(short_uid()) - - # create firehose target bucket - resources.get_or_create_bucket(s3_bucket, s3_client=aws_client.s3) - - # create firehose delivery stream to s3 - stream = aws_client.firehose.create_delivery_stream( - DeliveryStreamName=stream_name, - S3DestinationConfiguration={ - "RoleARN": arns.iam_resource_arn("firehose", TEST_AWS_ACCOUNT_ID), - "BucketARN": arns.s3_bucket_arn(s3_bucket), - "Prefix": s3_prefix, - }, - ) - stream_arn = stream["DeliveryStreamARN"] - - aws_client.events.create_event_bus(Name=bus_name) - aws_client.events.put_rule( - Name=rule_name, - EventBusName=bus_name, - EventPattern=json.dumps(TEST_EVENT_PATTERN), - ) - rs = aws_client.events.put_targets( - Rule=rule_name, - EventBusName=bus_name, - Targets=[{"Id": target_id, "Arn": stream_arn}], - ) - - assert "FailedEntryCount" in rs - assert "FailedEntries" in rs - assert rs["FailedEntryCount"] == 0 - assert rs["FailedEntries"] == [] - - aws_client.events.put_events( - Entries=[ - { - "EventBusName": bus_name, - "Source": TEST_EVENT_PATTERN["source"][0], - "DetailType": TEST_EVENT_PATTERN["detail-type"][0], - "Detail": json.dumps(EVENT_DETAIL), - } - ] - ) - - # run tests - bucket_contents = aws_client.s3.list_objects(Bucket=s3_bucket)["Contents"] - assert len(bucket_contents) == 1 - key = bucket_contents[0]["Key"] - s3_object = aws_client.s3.get_object(Bucket=s3_bucket, Key=key) - actual_event = json.loads(s3_object["Body"].read().decode()) - assert_valid_event(actual_event) - assert actual_event["detail"] == EVENT_DETAIL - - # clean up - aws_client.firehose.delete_delivery_stream(DeliveryStreamName=stream_name) - # empty and delete bucket - aws_client.s3.delete_object(Bucket=s3_bucket, Key=key) - aws_client.s3.delete_bucket(Bucket=s3_bucket) - clean_up(bus_name=bus_name, rule_name=rule_name, target_ids=target_id) - - -@markers.aws.unknown -@pytest.mark.skipif(is_v2_provider(), reason="V2 provider does not support this feature yet") -def test_put_events_with_target_kinesis(aws_client): - rule_name = "rule-{}".format(short_uid()) - target_id = "target-{}".format(short_uid()) - bus_name = "bus-{}".format(short_uid()) - stream_name = "stream-{}".format(short_uid()) - stream_arn = arns.kinesis_stream_arn(stream_name, TEST_AWS_ACCOUNT_ID, TEST_AWS_REGION_NAME) - - aws_client.kinesis.create_stream(StreamName=stream_name, ShardCount=1) - - aws_client.events.create_event_bus(Name=bus_name) - - aws_client.events.put_rule( - Name=rule_name, - EventBusName=bus_name, - EventPattern=json.dumps(TEST_EVENT_PATTERN), - ) - - put_response = aws_client.events.put_targets( - Rule=rule_name, - EventBusName=bus_name, - Targets=[ - { - "Id": target_id, - "Arn": stream_arn, - "KinesisParameters": {"PartitionKeyPath": "$.detail-type"}, - } - ], - ) - - assert "FailedEntryCount" in put_response - assert "FailedEntries" in put_response - assert put_response["FailedEntryCount"] == 0 - assert put_response["FailedEntries"] == [] - - def check_stream_status(): - _stream = aws_client.kinesis.describe_stream(StreamName=stream_name) - assert _stream["StreamDescription"]["StreamStatus"] == "ACTIVE" - - # wait until stream becomes available - retry(check_stream_status, retries=7, sleep=0.8) - - aws_client.events.put_events( - Entries=[ - { - "EventBusName": bus_name, - "Source": TEST_EVENT_PATTERN["source"][0], - "DetailType": TEST_EVENT_PATTERN["detail-type"][0], - "Detail": json.dumps(EVENT_DETAIL), - } - ] - ) - - stream = aws_client.kinesis.describe_stream(StreamName=stream_name) - shard_id = stream["StreamDescription"]["Shards"][0]["ShardId"] - shard_iterator = aws_client.kinesis.get_shard_iterator( - StreamName=stream_name, - ShardId=shard_id, - ShardIteratorType="AT_TIMESTAMP", - Timestamp=datetime(2020, 1, 1), - )["ShardIterator"] - - record = aws_client.kinesis.get_records(ShardIterator=shard_iterator)["Records"][0] - - partition_key = record["PartitionKey"] - data = json.loads(record["Data"].decode()) - - assert partition_key == TEST_EVENT_PATTERN["detail-type"][0] - assert data["detail"] == EVENT_DETAIL - assert_valid_event(data) - - -@markers.aws.needs_fixing # TODO: Reason add permission and correct policies -@pytest.mark.parametrize("strategy", ["standard", "domain", "path"]) -def test_trigger_event_on_ssm_change(monkeypatch, aws_client, clean_up, strategy): - monkeypatch.setattr(config, "SQS_ENDPOINT_STRATEGY", strategy) - - rule_name = "rule-{}".format(short_uid()) - target_id = "target-{}".format(short_uid()) - - # create queue - queue_name = "queue-{}".format(short_uid()) - queue_url = aws_client.sqs.create_queue(QueueName=queue_name)["QueueUrl"] - queue_arn = arns.sqs_queue_arn(queue_name, TEST_AWS_ACCOUNT_ID, TEST_AWS_REGION_NAME) - - # put rule listening on SSM changes - ssm_prefix = "/test/local/" - aws_client.events.put_rule( - Name=rule_name, - EventPattern=json.dumps( - { - "detail": { - "name": [{"prefix": ssm_prefix}], - "operation": [ - "Create", - "Update", - "Delete", - "LabelParameterVersion", - ], - }, - "detail-type": ["Parameter Store Change"], - "source": ["aws.ssm"], - } - ), - State="ENABLED", - Description="Trigger on SSM parameter changes", - ) - - # put target - aws_client.events.put_targets( - Rule=rule_name, - Targets=[{"Id": target_id, "Arn": queue_arn, "InputPath": "$.detail"}], - ) - - param_suffix = short_uid() - - # change SSM param to trigger event - aws_client.ssm.put_parameter( - Name=f"{ssm_prefix}/test-{param_suffix}", Value="value1", Type="String" - ) - - def assert_message(): - resp = aws_client.sqs.receive_message(QueueUrl=queue_url) - result = resp.get("Messages") - body = json.loads(result[0]["Body"]) - assert body == { - "name": f"/test/local/test-{param_suffix}", - "operation": "Create", - } - - # assert that message has been received - retry(assert_message, retries=7, sleep=0.3) - - # clean up - clean_up(rule_name=rule_name, target_ids=target_id) diff --git a/tests/aws/services/events/test_events_integrations.validation.json b/tests/aws/services/events/test_events_integrations.validation.json deleted file mode 100644 index bf2860707799f..0000000000000 --- a/tests/aws/services/events/test_events_integrations.validation.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "tests/aws/services/events/test_events_integrations.py::test_put_events_with_target_lambda_list_entries_partial_match": { - "last_validated_date": "2024-04-08T17:36:24+00:00" - }, - "tests/aws/services/events/test_events_integrations.py::test_put_events_with_target_lambda_list_entry": { - "last_validated_date": "2024-04-08T17:33:44+00:00" - }, - "tests/aws/services/events/test_events_integrations.py::test_put_events_with_target_sqs": { - "last_validated_date": "2024-04-26T08:43:27+00:00" - }, - "tests/aws/services/events/test_events_integrations.py::test_put_events_with_target_sqs_event_detail_match": { - "last_validated_date": "2024-05-07T10:40:38+00:00" - }, - "tests/aws/services/events/test_events_integrations.py::test_should_ignore_schedules_for_put_event": { - "last_validated_date": "2024-03-26T15:51:47+00:00" - } -} diff --git a/tests/aws/services/events/test_events_schedule.py b/tests/aws/services/events/test_events_schedule.py index 3594ac38f76ae..2e872df82ebbd 100644 --- a/tests/aws/services/events/test_events_schedule.py +++ b/tests/aws/services/events/test_events_schedule.py @@ -5,6 +5,7 @@ import pytest from botocore.exceptions import ClientError +from localstack.aws.api.lambda_ import Runtime from localstack.testing.aws.eventbus_utils import trigger_scheduled_rule from localstack.testing.pytest import markers from localstack.testing.snapshots.transformer_utility import TransformerUtility @@ -13,8 +14,10 @@ from tests.aws.services.events.helper_functions import ( events_time_string_to_timestamp, get_cron_expression, + is_v2_provider, sqs_collect_messages, ) +from tests.aws.services.lambda_.test_lambda import TEST_LAMBDA_PYTHON_ECHO class TestScheduleRate: @@ -267,6 +270,61 @@ def _get_events(): snapshot.match("log-events", events) + @markers.aws.validated + @pytest.mark.skipif(is_v2_provider(), reason="V2 provider does not support this feature yet") + def test_should_ignore_schedules_for_put_event( + self, create_lambda_function, cleanups, aws_client + ): + """Regression test for https://github.com/localstack/localstack/issues/7847""" + fn_name = f"test-event-fn-{short_uid()}" + create_lambda_function( + func_name=fn_name, + handler_file=TEST_LAMBDA_PYTHON_ECHO, + runtime=Runtime.python3_9, + client=aws_client.lambda_, + ) + + aws_client.lambda_.add_permission( + FunctionName=fn_name, + StatementId="AllowFnInvokeStatement", + Action="lambda:InvokeFunction", + Principal="events.amazonaws.com", + ) + + fn_arn = aws_client.lambda_.get_function(FunctionName=fn_name)["Configuration"][ + "FunctionArn" + ] + aws_client.events.put_rule( + Name="ScheduledLambda", ScheduleExpression="rate(1 minute)" + ) # every minute, can't go lower than that + cleanups.append(lambda: aws_client.events.delete_rule(Name="ScheduledLambda")) + aws_client.events.put_targets( + Rule="ScheduledLambda", Targets=[{"Id": "calllambda1", "Arn": fn_arn}] + ) + cleanups.append( + lambda: aws_client.events.remove_targets(Rule="ScheduledLambda", Ids=["calllambda1"]) + ) + + aws_client.events.put_events( + Entries=[ + { + "Source": "MySource", + "DetailType": "CustomType", + "Detail": json.dumps({"message": "manually invoked"}), + } + ] + ) + + def check_invocation(): + events_after = aws_client.logs.filter_log_events(logGroupName=f"/aws/lambda/{fn_name}") + # the custom sent event should NOT trigger the lambda (!) + assert len([e for e in events_after["events"] if "START" in e["message"]]) >= 1 + assert ( + len([e for e in events_after["events"] if "manually invoked" in e["message"]]) == 0 + ) + + retry(check_invocation, sleep=5, retries=15) + class TestScheduleCron: @markers.aws.validated diff --git a/tests/aws/services/events/test_events_schedule.validation.json b/tests/aws/services/events/test_events_schedule.validation.json index a21ca78f556dd..36ce7e905bad9 100644 --- a/tests/aws/services/events/test_events_schedule.validation.json +++ b/tests/aws/services/events/test_events_schedule.validation.json @@ -86,6 +86,9 @@ "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::test_put_rule_with_schedule_rate": { "last_validated_date": "2024-05-14T11:23:22+00:00" }, + "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::test_should_ignore_schedules_for_put_event": { + "last_validated_date": "2024-06-18T14:50:01+00:00" + }, "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::tests_put_rule_with_schedule_custom_event_bus": { "last_validated_date": "2024-05-14T11:38:21+00:00" }, diff --git a/tests/aws/services/events/test_events_targets.py b/tests/aws/services/events/test_events_targets.py new file mode 100644 index 0000000000000..440f8dc502a15 --- /dev/null +++ b/tests/aws/services/events/test_events_targets.py @@ -0,0 +1,657 @@ +"""Tests for integrations between AWS EventBridge and other AWS services.""" + +import json +from datetime import datetime + +import pytest + +from localstack import config +from localstack.aws.api.lambda_ import Runtime +from localstack.testing.config import TEST_AWS_ACCOUNT_ID, TEST_AWS_REGION_NAME +from localstack.testing.pytest import markers +from localstack.utils.aws import arns, resources +from localstack.utils.strings import short_uid +from localstack.utils.sync import retry +from localstack.utils.testutil import check_expected_lambda_log_events_length +from tests.aws.services.events.conftest import assert_valid_event +from tests.aws.services.events.helper_functions import is_v2_provider, sqs_collect_messages +from tests.aws.services.events.test_events import EVENT_DETAIL, TEST_EVENT_PATTERN +from tests.aws.services.lambda_.test_lambda import TEST_LAMBDA_PYTHON_ECHO + +# TODO: further unify/parameterize the tests for the different target types below + + +@markers.aws.needs_fixing +@pytest.mark.parametrize("strategy", ["standard", "domain", "path"]) +def test_put_events_with_target_sns( + monkeypatch, + sns_subscription, + aws_client, + clean_up, + strategy, +): + monkeypatch.setattr(config, "SQS_ENDPOINT_STRATEGY", strategy) + + queue_name = "test-%s" % short_uid() + rule_name = "rule-{}".format(short_uid()) + target_id = "target-{}".format(short_uid()) + bus_name = "bus-{}".format(short_uid()) + + topic_name = "topic-{}".format(short_uid()) + topic_arn = aws_client.sns.create_topic(Name=topic_name)["TopicArn"] + + queue_url = aws_client.sqs.create_queue(QueueName=queue_name)["QueueUrl"] + queue_arn = arns.sqs_queue_arn(queue_name, TEST_AWS_ACCOUNT_ID, TEST_AWS_REGION_NAME) + + sns_subscription(TopicArn=topic_arn, Protocol="sqs", Endpoint=queue_arn) + + aws_client.events.create_event_bus(Name=bus_name) + aws_client.events.put_rule( + Name=rule_name, + EventBusName=bus_name, + EventPattern=json.dumps(TEST_EVENT_PATTERN), + ) + rs = aws_client.events.put_targets( + Rule=rule_name, + EventBusName=bus_name, + Targets=[{"Id": target_id, "Arn": topic_arn}], + ) + + assert "FailedEntryCount" in rs + assert "FailedEntries" in rs + assert rs["FailedEntryCount"] == 0 + assert rs["FailedEntries"] == [] + + aws_client.events.put_events( + Entries=[ + { + "EventBusName": bus_name, + "Source": TEST_EVENT_PATTERN["source"][0], + "DetailType": TEST_EVENT_PATTERN["detail-type"][0], + "Detail": json.dumps(EVENT_DETAIL), + } + ] + ) + + messages = sqs_collect_messages(aws_client, queue_url, min_events=1, retries=3) + assert len(messages) == 1 + + actual_event = json.loads(messages[0]["Body"]).get("Message") + assert_valid_event(actual_event) + assert json.loads(actual_event).get("detail") == EVENT_DETAIL + + # clean up + aws_client.sns.delete_topic(TopicArn=topic_arn) + clean_up( + bus_name=bus_name, + rule_name=rule_name, + target_ids=target_id, + queue_url=queue_url, + ) + + +@markers.aws.needs_fixing +def test_put_events_with_target_firehose(aws_client, clean_up): + s3_bucket = "s3-{}".format(short_uid()) + s3_prefix = "testeventdata" + stream_name = "firehose-{}".format(short_uid()) + rule_name = "rule-{}".format(short_uid()) + target_id = "target-{}".format(short_uid()) + bus_name = "bus-{}".format(short_uid()) + + # create firehose target bucket + resources.get_or_create_bucket(s3_bucket, s3_client=aws_client.s3) + + # create firehose delivery stream to s3 + stream = aws_client.firehose.create_delivery_stream( + DeliveryStreamName=stream_name, + S3DestinationConfiguration={ + "RoleARN": arns.iam_resource_arn("firehose", TEST_AWS_ACCOUNT_ID), + "BucketARN": arns.s3_bucket_arn(s3_bucket), + "Prefix": s3_prefix, + }, + ) + stream_arn = stream["DeliveryStreamARN"] + + aws_client.events.create_event_bus(Name=bus_name) + aws_client.events.put_rule( + Name=rule_name, + EventBusName=bus_name, + EventPattern=json.dumps(TEST_EVENT_PATTERN), + ) + rs = aws_client.events.put_targets( + Rule=rule_name, + EventBusName=bus_name, + Targets=[{"Id": target_id, "Arn": stream_arn}], + ) + + assert "FailedEntryCount" in rs + assert "FailedEntries" in rs + assert rs["FailedEntryCount"] == 0 + assert rs["FailedEntries"] == [] + + aws_client.events.put_events( + Entries=[ + { + "EventBusName": bus_name, + "Source": TEST_EVENT_PATTERN["source"][0], + "DetailType": TEST_EVENT_PATTERN["detail-type"][0], + "Detail": json.dumps(EVENT_DETAIL), + } + ] + ) + + # run tests + bucket_contents = aws_client.s3.list_objects(Bucket=s3_bucket)["Contents"] + assert len(bucket_contents) == 1 + key = bucket_contents[0]["Key"] + s3_object = aws_client.s3.get_object(Bucket=s3_bucket, Key=key) + actual_event = json.loads(s3_object["Body"].read().decode()) + assert_valid_event(actual_event) + assert actual_event["detail"] == EVENT_DETAIL + + # clean up + aws_client.firehose.delete_delivery_stream(DeliveryStreamName=stream_name) + # empty and delete bucket + aws_client.s3.delete_object(Bucket=s3_bucket, Key=key) + aws_client.s3.delete_bucket(Bucket=s3_bucket) + clean_up(bus_name=bus_name, rule_name=rule_name, target_ids=target_id) + + +@markers.aws.unknown +@pytest.mark.skipif(is_v2_provider(), reason="V2 provider does not support this feature yet") +def test_put_events_with_target_kinesis(aws_client): + rule_name = "rule-{}".format(short_uid()) + target_id = "target-{}".format(short_uid()) + bus_name = "bus-{}".format(short_uid()) + stream_name = "stream-{}".format(short_uid()) + stream_arn = arns.kinesis_stream_arn(stream_name, TEST_AWS_ACCOUNT_ID, TEST_AWS_REGION_NAME) + + aws_client.kinesis.create_stream(StreamName=stream_name, ShardCount=1) + + aws_client.events.create_event_bus(Name=bus_name) + + aws_client.events.put_rule( + Name=rule_name, + EventBusName=bus_name, + EventPattern=json.dumps(TEST_EVENT_PATTERN), + ) + + put_response = aws_client.events.put_targets( + Rule=rule_name, + EventBusName=bus_name, + Targets=[ + { + "Id": target_id, + "Arn": stream_arn, + "KinesisParameters": {"PartitionKeyPath": "$.detail-type"}, + } + ], + ) + + assert "FailedEntryCount" in put_response + assert "FailedEntries" in put_response + assert put_response["FailedEntryCount"] == 0 + assert put_response["FailedEntries"] == [] + + def check_stream_status(): + _stream = aws_client.kinesis.describe_stream(StreamName=stream_name) + assert _stream["StreamDescription"]["StreamStatus"] == "ACTIVE" + + # wait until stream becomes available + retry(check_stream_status, retries=7, sleep=0.8) + + aws_client.events.put_events( + Entries=[ + { + "EventBusName": bus_name, + "Source": TEST_EVENT_PATTERN["source"][0], + "DetailType": TEST_EVENT_PATTERN["detail-type"][0], + "Detail": json.dumps(EVENT_DETAIL), + } + ] + ) + + stream = aws_client.kinesis.describe_stream(StreamName=stream_name) + shard_id = stream["StreamDescription"]["Shards"][0]["ShardId"] + shard_iterator = aws_client.kinesis.get_shard_iterator( + StreamName=stream_name, + ShardId=shard_id, + ShardIteratorType="AT_TIMESTAMP", + Timestamp=datetime(2020, 1, 1), + )["ShardIterator"] + + record = aws_client.kinesis.get_records(ShardIterator=shard_iterator)["Records"][0] + + partition_key = record["PartitionKey"] + data = json.loads(record["Data"].decode()) + + assert partition_key == TEST_EVENT_PATTERN["detail-type"][0] + assert data["detail"] == EVENT_DETAIL + assert_valid_event(data) + + +@markers.aws.needs_fixing # TODO: Reason add permission and correct policies +@pytest.mark.parametrize("strategy", ["standard", "domain", "path"]) +def test_trigger_event_on_ssm_change(monkeypatch, aws_client, clean_up, strategy): + monkeypatch.setattr(config, "SQS_ENDPOINT_STRATEGY", strategy) + + rule_name = "rule-{}".format(short_uid()) + target_id = "target-{}".format(short_uid()) + + # create queue + queue_name = "queue-{}".format(short_uid()) + queue_url = aws_client.sqs.create_queue(QueueName=queue_name)["QueueUrl"] + queue_arn = arns.sqs_queue_arn(queue_name, TEST_AWS_ACCOUNT_ID, TEST_AWS_REGION_NAME) + + # put rule listening on SSM changes + ssm_prefix = "/test/local/" + aws_client.events.put_rule( + Name=rule_name, + EventPattern=json.dumps( + { + "detail": { + "name": [{"prefix": ssm_prefix}], + "operation": [ + "Create", + "Update", + "Delete", + "LabelParameterVersion", + ], + }, + "detail-type": ["Parameter Store Change"], + "source": ["aws.ssm"], + } + ), + State="ENABLED", + Description="Trigger on SSM parameter changes", + ) + + # put target + aws_client.events.put_targets( + Rule=rule_name, + Targets=[{"Id": target_id, "Arn": queue_arn, "InputPath": "$.detail"}], + ) + + param_suffix = short_uid() + + # change SSM param to trigger event + aws_client.ssm.put_parameter( + Name=f"{ssm_prefix}/test-{param_suffix}", Value="value1", Type="String" + ) + + def assert_message(): + resp = aws_client.sqs.receive_message(QueueUrl=queue_url) + result = resp.get("Messages") + body = json.loads(result[0]["Body"]) + assert body == { + "name": f"/test/local/test-{param_suffix}", + "operation": "Create", + } + + # assert that message has been received + retry(assert_message, retries=7, sleep=0.3) + + # clean up + clean_up(rule_name=rule_name, target_ids=target_id) + + +class TestEventTargetLambda: + @markers.aws.needs_fixing + def test_put_events_with_target_lambda( + self, + create_lambda_function, + cleanups, + events_create_event_bus, + events_put_rule, + aws_client, + ): + rule_name = f"rule-{short_uid()}" + function_name = f"lambda-func-{short_uid()}" + target_id = f"target-{short_uid()}" + bus_name = f"bus-{short_uid()}" + + # clean up + cleanups.append(lambda: aws_client.lambda_.delete_function(FunctionName=function_name)) + + rs = create_lambda_function( + handler_file=TEST_LAMBDA_PYTHON_ECHO, + func_name=function_name, + runtime=Runtime.python3_9, + ) + + func_arn = rs["CreateFunctionResponse"]["FunctionArn"] + + events_create_event_bus(Name=bus_name) + events_put_rule( + Name=rule_name, + EventBusName=bus_name, + EventPattern=json.dumps(TEST_EVENT_PATTERN), + ) + rs = aws_client.events.put_targets( + Rule=rule_name, + EventBusName=bus_name, + Targets=[{"Id": target_id, "Arn": func_arn}], + ) + + assert "FailedEntryCount" in rs + assert "FailedEntries" in rs + assert rs["FailedEntryCount"] == 0 + assert rs["FailedEntries"] == [] + + aws_client.events.put_events( + Entries=[ + { + "EventBusName": bus_name, + "Source": TEST_EVENT_PATTERN["source"][0], + "DetailType": TEST_EVENT_PATTERN["detail-type"][0], + "Detail": json.dumps(EVENT_DETAIL), + } + ] + ) + + # Get lambda's log events + events = retry( + check_expected_lambda_log_events_length, + retries=3, + sleep=1, + function_name=function_name, + expected_length=1, + logs_client=aws_client.logs, + ) + actual_event = events[0] + assert_valid_event(actual_event) + assert actual_event["detail"] == EVENT_DETAIL + + @markers.aws.validated + def test_put_events_with_target_lambda_list_entry( + self, create_lambda_function, cleanups, aws_client, clean_up, snapshot + ): + rule_name = f"rule-{short_uid()}" + function_name = f"lambda-func-{short_uid()}" + target_id = f"target-{short_uid()}" + bus_name = f"bus-{short_uid()}" + + # clean up + cleanups.append( + lambda: clean_up(bus_name=bus_name, rule_name=rule_name, target_ids=target_id) + ) + + create_lambda_response = create_lambda_function( + handler_file=TEST_LAMBDA_PYTHON_ECHO, + func_name=function_name, + runtime=Runtime.python3_12, + ) + + func_arn = create_lambda_response["CreateFunctionResponse"]["FunctionArn"] + + event_pattern = {"detail": {"payload": {"automations": {"id": [{"exists": True}]}}}} + + aws_client.events.create_event_bus(Name=bus_name) + put_rule_response = aws_client.events.put_rule( + Name=rule_name, + EventBusName=bus_name, + EventPattern=json.dumps(event_pattern), + ) + aws_client.lambda_.add_permission( + FunctionName=function_name, + StatementId=f"{rule_name}-Event", + Action="lambda:InvokeFunction", + Principal="events.amazonaws.com", + SourceArn=put_rule_response["RuleArn"], + ) + put_target_response = aws_client.events.put_targets( + Rule=rule_name, + EventBusName=bus_name, + Targets=[{"Id": target_id, "Arn": func_arn}], + ) + + assert "FailedEntryCount" in put_target_response + assert "FailedEntries" in put_target_response + assert put_target_response["FailedEntryCount"] == 0 + assert put_target_response["FailedEntries"] == [] + + event_detail = { + "payload": { + "userId": 10, + "businessId": 3, + "channelId": 6, + "card": {"foo": "bar"}, + "targetEntity": True, + "entityAuditTrailEvent": {"foo": "bar"}, + "automations": [ + { + "id": "123", + "actions": [ + { + "id": "321", + "type": "SEND_NOTIFICATION", + "settings": { + "message": "", + "recipientEmails": [], + "subject": "", + "type": "SEND_NOTIFICATION", + }, + } + ], + } + ], + } + } + aws_client.events.put_events( + Entries=[ + { + "EventBusName": bus_name, + "Source": TEST_EVENT_PATTERN["source"][0], + "DetailType": TEST_EVENT_PATTERN["detail-type"][0], + "Detail": json.dumps(event_detail), + } + ] + ) + + # Get lambda's log events + events = retry( + check_expected_lambda_log_events_length, + retries=15, + sleep=1, + function_name=function_name, + expected_length=1, + logs_client=aws_client.logs, + ) + snapshot.match("events", events) + + @markers.aws.validated + def test_put_events_with_target_lambda_list_entries_partial_match( + self, create_lambda_function, cleanups, aws_client, clean_up, snapshot + ): + rule_name = f"rule-{short_uid()}" + function_name = f"lambda-func-{short_uid()}" + target_id = f"target-{short_uid()}" + bus_name = f"bus-{short_uid()}" + + # clean up + cleanups.append( + lambda: clean_up(bus_name=bus_name, rule_name=rule_name, target_ids=target_id) + ) + + rs = create_lambda_function( + handler_file=TEST_LAMBDA_PYTHON_ECHO, + func_name=function_name, + runtime=Runtime.python3_12, + ) + + func_arn = rs["CreateFunctionResponse"]["FunctionArn"] + + event_pattern = {"detail": {"payload": {"automations": {"id": [{"exists": True}]}}}} + + aws_client.events.create_event_bus(Name=bus_name) + rs = aws_client.events.put_rule( + Name=rule_name, + EventBusName=bus_name, + EventPattern=json.dumps(event_pattern), + ) + aws_client.lambda_.add_permission( + FunctionName=function_name, + StatementId=f"{rule_name}-Event", + Action="lambda:InvokeFunction", + Principal="events.amazonaws.com", + SourceArn=rs["RuleArn"], + ) + rs = aws_client.events.put_targets( + Rule=rule_name, + EventBusName=bus_name, + Targets=[{"Id": target_id, "Arn": func_arn}], + ) + + assert "FailedEntryCount" in rs + assert "FailedEntries" in rs + assert rs["FailedEntryCount"] == 0 + assert rs["FailedEntries"] == [] + + event_detail_partial_match = { + "payload": { + "userId": 10, + "businessId": 3, + "channelId": 6, + "card": {"foo": "bar"}, + "targetEntity": True, + "entityAuditTrailEvent": {"foo": "bar"}, + "automations": [ + {"foo": "bar"}, + { + "id": "123", + "actions": [ + { + "id": "321", + "type": "SEND_NOTIFICATION", + "settings": { + "message": "", + "recipientEmails": [], + "subject": "", + "type": "SEND_NOTIFICATION", + }, + } + ], + }, + {"bar": "foo"}, + ], + } + } + aws_client.events.put_events( + Entries=[ + { + "EventBusName": bus_name, + "Source": TEST_EVENT_PATTERN["source"][0], + "DetailType": TEST_EVENT_PATTERN["detail-type"][0], + "Detail": json.dumps(event_detail_partial_match), + }, + ] + ) + + # Get lambda's log events + events = retry( + check_expected_lambda_log_events_length, + retries=15, + sleep=1, + function_name=function_name, + expected_length=1, + logs_client=aws_client.logs, + ) + snapshot.match("events", events) + + +class TestEventTargetSqs: + @markers.aws.validated + def test_put_events_with_target_sqs(self, put_events_with_filter_to_sqs, snapshot): + entries = [ + { + "Source": TEST_EVENT_PATTERN["source"][0], + "DetailType": TEST_EVENT_PATTERN["detail-type"][0], + "Detail": json.dumps(EVENT_DETAIL), + } + ] + message = put_events_with_filter_to_sqs( + pattern=TEST_EVENT_PATTERN, + entries_asserts=[(entries, True)], + ) + snapshot.add_transformers_list( + [ + snapshot.transform.key_value("ReceiptHandle", reference_replacement=False), + snapshot.transform.key_value("MD5OfBody", reference_replacement=False), + ], + ) + snapshot.match("message", message) + + @markers.aws.unknown + @pytest.mark.skipif(is_v2_provider(), reason="V2 provider does not support this feature yet") + def test_put_events_with_target_sqs_new_region(self, aws_client_factory): + events_client = aws_client_factory(region_name="eu-west-1").events + queue_name = "queue-{}".format(short_uid()) + rule_name = "rule-{}".format(short_uid()) + target_id = "target-{}".format(short_uid()) + bus_name = "bus-{}".format(short_uid()) + + sqs_client = aws_client_factory(region_name="eu-west-1").sqs + sqs_client.create_queue(QueueName=queue_name) + queue_arn = arns.sqs_queue_arn(queue_name, TEST_AWS_ACCOUNT_ID, TEST_AWS_REGION_NAME) + + events_client.create_event_bus(Name=bus_name) + + events_client.put_rule( + Name=rule_name, + EventBusName=bus_name, + EventPattern=json.dumps(TEST_EVENT_PATTERN), + ) + + events_client.put_targets( + Rule=rule_name, + EventBusName=bus_name, + Targets=[{"Id": target_id, "Arn": queue_arn}], + ) + + response = events_client.put_events( + Entries=[ + { + "Source": "com.mycompany.myapp", + "Detail": '{ "key1": "value1", "key": "value2" }', + "Resources": [], + "DetailType": "myDetailType", + } + ] + ) + assert "Entries" in response + assert len(response.get("Entries")) == 1 + assert "EventId" in response.get("Entries")[0] + + @markers.aws.validated + def test_put_events_with_target_sqs_event_detail_match( + self, put_events_with_filter_to_sqs, snapshot + ): + entries1 = [ + { + "Source": TEST_EVENT_PATTERN["source"][0], + "DetailType": TEST_EVENT_PATTERN["detail-type"][0], + "Detail": json.dumps({"EventType": "1"}), + } + ] + entries2 = [ + { + "Source": TEST_EVENT_PATTERN["source"][0], + "DetailType": TEST_EVENT_PATTERN["detail-type"][0], + "Detail": json.dumps({"EventType": "2"}), + } + ] + entries_asserts = [(entries1, True), (entries2, False)] + messages = put_events_with_filter_to_sqs( + pattern={"detail": {"EventType": ["0", "1"]}}, + entries_asserts=entries_asserts, + input_path="$.detail", + ) + + snapshot.add_transformers_list( + [ + snapshot.transform.key_value("ReceiptHandle", reference_replacement=False), + snapshot.transform.key_value("MD5OfBody", reference_replacement=False), + ], + ) + snapshot.match("messages", messages) diff --git a/tests/aws/services/events/test_events_integrations.snapshot.json b/tests/aws/services/events/test_events_targets.snapshot.json similarity index 87% rename from tests/aws/services/events/test_events_integrations.snapshot.json rename to tests/aws/services/events/test_events_targets.snapshot.json index e27cdb2a10e9e..ff08ce08a91c6 100644 --- a/tests/aws/services/events/test_events_integrations.snapshot.json +++ b/tests/aws/services/events/test_events_targets.snapshot.json @@ -1,5 +1,5 @@ { - "tests/aws/services/events/test_events_integrations.py::test_put_events_with_target_lambda_list_entry": { + "tests/aws/services/events/test_events_targets.py::TestEventTargetLambda::test_put_events_with_target_lambda_list_entry": { "recorded-date": "08-04-2024, 17:32:58", "recorded-content": { "events": [ @@ -47,7 +47,7 @@ ] } }, - "tests/aws/services/events/test_events_integrations.py::test_put_events_with_target_lambda_list_entries_partial_match": { + "tests/aws/services/events/test_events_targets.py::TestEventTargetLambda::test_put_events_with_target_lambda_list_entries_partial_match": { "recorded-date": "03-04-2024, 20:00:13", "recorded-content": { "events": [ @@ -101,8 +101,23 @@ ] } }, - "tests/aws/services/events/test_events_integrations.py::test_put_events_with_target_sqs": { - "recorded-date": "26-04-2024, 08:43:27", + "tests/aws/services/events/test_events_targets.py::TestEventTargetSqs::test_put_events_with_target_sqs_event_detail_match": { + "recorded-date": "07-05-2024, 10:40:38", + "recorded-content": { + "messages": [ + { + "MessageId": "", + "ReceiptHandle": "receipt-handle", + "MD5OfBody": "m-d5-of-body", + "Body": { + "EventType": "1" + } + } + ] + } + }, + "tests/aws/services/events/test_events_targets.py::TestEventTargetSqs::test_put_events_with_target_sqs": { + "recorded-date": "18-06-2024, 14:34:29", "recorded-content": { "message": [ { @@ -129,20 +144,5 @@ } ] } - }, - "tests/aws/services/events/test_events_integrations.py::test_put_events_with_target_sqs_event_detail_match": { - "recorded-date": "07-05-2024, 10:40:38", - "recorded-content": { - "messages": [ - { - "MessageId": "", - "ReceiptHandle": "receipt-handle", - "MD5OfBody": "m-d5-of-body", - "Body": { - "EventType": "1" - } - } - ] - } } } diff --git a/tests/aws/services/events/test_events_targets.validation.json b/tests/aws/services/events/test_events_targets.validation.json new file mode 100644 index 0000000000000..3efe7cdd43b6f --- /dev/null +++ b/tests/aws/services/events/test_events_targets.validation.json @@ -0,0 +1,14 @@ +{ + "tests/aws/services/events/test_events_targets.py::TestEventTargetSqs::test_put_events_with_target_sqs": { + "last_validated_date": "2024-06-18T14:34:29+00:00" + }, + "tests/aws/services/events/test_events_targets.py::TestEventTargetSqs::test_put_events_with_target_sqs_event_detail_match": { + "last_validated_date": "2024-05-07T10:40:38+00:00" + }, + "tests/aws/services/events/test_events_targets.py::TestEventTargetLambda::test_put_events_with_target_lambda_list_entries_partial_match": { + "last_validated_date": "2024-04-08T17:36:24+00:00" + }, + "tests/aws/services/events/test_events_targets.py::TestEventTargetLambda::test_put_events_with_target_lambda_list_entry": { + "last_validated_date": "2024-04-08T17:33:44+00:00" + } +}