diff --git a/localstack-core/localstack/services/events/provider.py b/localstack-core/localstack/services/events/provider.py index c38f3b3eb85f0..1319fe9cb6c09 100644 --- a/localstack-core/localstack/services/events/provider.py +++ b/localstack-core/localstack/services/events/provider.py @@ -1772,7 +1772,7 @@ def create_target_sender( target_sender = TargetSenderFactory( target, rule_arn, rule_name, region, account_id ).get_target_sender() - self._target_sender_store[target_sender.arn] = target_sender + self._target_sender_store[target_sender.unique_id] = target_sender return target_sender def create_archive_service( @@ -1835,11 +1835,11 @@ def _delete_rule_services(self, rules: RuleDict | Rule) -> None: def _delete_target_sender(self, ids: TargetIdList, rule) -> None: for target_id in ids: if target := rule.targets.get(target_id): - target_arn = target["Arn"] + target_unique_id = f"{rule.arn}-{target_id}" try: - del self._target_sender_store[target_arn] + del self._target_sender_store[target_unique_id] except KeyError: - LOG.error("Error deleting target service %s.", target_arn) + LOG.error("Error deleting target service %s.", target["Arn"]) def _get_limited_dict_and_next_token( self, input_dict: dict, next_token: NextToken | None, limit: LimitMax100 | None @@ -1889,8 +1889,8 @@ def func(*args, **kwargs): "resources": [rule.arn], "detail": {}, } - - target_sender = self._target_sender_store[target["Arn"]] + target_unique_id = f"{rule.arn}-{target['Id']}" + target_sender = self._target_sender_store[target_unique_id] try: target_sender.process_event(event.copy()) except Exception as e: @@ -2178,16 +2178,17 @@ def _process_rules( return for target in rule.targets.values(): - target_arn = target["Arn"] - if is_archive_arn(target_arn): + target_id = target["Id"] + if is_archive_arn(target["Arn"]): self._put_to_archive( region, account_id, - archive_target_id=target["Id"], + archive_target_id=target_id, event=event_formatted, ) else: - target_sender = self._target_sender_store[target_arn] + target_unique_id = f"{rule.arn}-{target_id}" + target_sender = self._target_sender_store[target_unique_id] try: target_sender.process_event(event_formatted.copy()) rule_invocation.record(target_sender.service) @@ -2198,7 +2199,7 @@ def _process_rules( json.dumps( { "ErrorCode": "TargetDeliveryFailure", - "ErrorMessage": f"Failed to deliver to target {target['Id']}: {str(error)}", + "ErrorMessage": f"Failed to deliver to target {target_id}: {str(error)}", } ) ) diff --git a/localstack-core/localstack/services/events/target.py b/localstack-core/localstack/services/events/target.py index d82a774be8e37..a0a214549553e 100644 --- a/localstack-core/localstack/services/events/target.py +++ b/localstack-core/localstack/services/events/target.py @@ -160,6 +160,18 @@ def __init__( def arn(self): return self.target["Arn"] + @property + def target_id(self): + return self.target["Id"] + + @property + def unique_id(self): + """Necessary to distinguish between targets with the same ARN but for different rules. + The unique_id is a combination of the rule ARN and the Target Id. + This is necessary since input path and input transformer can be different for the same target ARN, + attached to different rules.""" + return f"{self.rule_arn}-{self.target_id}" + @property def client(self): """Lazy initialization of internal botoclient factory.""" @@ -263,7 +275,7 @@ def _get_predefined_template_replacements(self, event: FormattedEvent) -> dict[s return predefined_template_replacements -TargetSenderDict = dict[Arn, TargetSender] +TargetSenderDict = dict[str, TargetSender] # rule_arn-target_id as global unique id # Target Senders are ordered alphabetically by service name diff --git a/tests/aws/services/events/test_archive_and_replay.py b/tests/aws/services/events/test_archive_and_replay.py index ff8c468c05f2a..9fb0ce8a7535d 100644 --- a/tests/aws/services/events/test_archive_and_replay.py +++ b/tests/aws/services/events/test_archive_and_replay.py @@ -13,7 +13,7 @@ wait_for_replay_in_state, ) from tests.aws.services.events.test_events import ( - EVENT_DETAIL, + TEST_EVENT_DETAIL, TEST_EVENT_PATTERN, TEST_EVENT_PATTERN_NO_DETAIL, ) @@ -219,7 +219,7 @@ def test_list_archive_with_events( entry = { "Source": TEST_EVENT_PATTERN["source"][0], "DetailType": TEST_EVENT_PATTERN["detail-type"][0], - "Detail": json.dumps(EVENT_DETAIL), + "Detail": json.dumps(TEST_EVENT_DETAIL), } entries.append(entry) @@ -412,7 +412,7 @@ def test_start_list_describe_canceled_replay( entry = { "Source": TEST_EVENT_PATTERN["source"][0], "DetailType": TEST_EVENT_PATTERN["detail-type"][0], - "Detail": json.dumps(EVENT_DETAIL), + "Detail": json.dumps(TEST_EVENT_DETAIL), "EventBusName": event_bus_name, } entries.append(entry) diff --git a/tests/aws/services/events/test_events.py b/tests/aws/services/events/test_events.py index 588c8c6f645aa..d63ec6c41d2ea 100644 --- a/tests/aws/services/events/test_events.py +++ b/tests/aws/services/events/test_events.py @@ -14,6 +14,7 @@ from localstack_snapshot.snapshots.transformer import SortingTransformer from localstack import config +from localstack.aws.api.lambda_ import Runtime from localstack.services.events.v1.provider import _get_events_tmp_dir from localstack.testing.aws.eventbus_utils import allow_event_rule_to_sqs_queue from localstack.testing.aws.util import is_aws_cloud @@ -21,14 +22,19 @@ from localstack.utils.files import load_file from localstack.utils.strings import long_uid, short_uid from localstack.utils.sync import retry +from localstack.utils.testutil import check_expected_lambda_log_events_length from tests.aws.services.events.helper_functions import ( assert_valid_event, is_old_provider, is_v2_provider, sqs_collect_messages, ) +from tests.aws.services.lambda_.test_lambda import ( + TEST_LAMBDA_PYTHON_ECHO, +) EVENT_DETAIL = {"command": "update-account", "payload": {"acc_id": "0a787ecb-4015", "sf_id": "baz"}} + SPECIAL_EVENT_DETAIL = { "command": "update-account", "payload": {"acc_id": "0a787ecb-4015", "sf_id": "baz"}, @@ -36,6 +42,11 @@ "listmulti": ["ACTIVE", "INACTIVE"], } +TEST_EVENT_DETAIL = { + "command": "update-account", + "payload": {"acc_id": "0a787ecb-4015", "sf_id": "baz"}, +} + TEST_EVENT_PATTERN = { "source": ["core.update-account-command"], "detail-type": ["core.update-account-command"], @@ -73,7 +84,7 @@ def test_put_events_without_source(self, snapshot, aws_client): entries = [ { "DetailType": TEST_EVENT_PATTERN_NO_SOURCE["detail-type"][0], - "Detail": json.dumps(EVENT_DETAIL), + "Detail": json.dumps(TEST_EVENT_DETAIL), }, ] response = aws_client.events.put_events(Entries=entries) @@ -121,7 +132,7 @@ def test_put_event_without_detail_type(self, snapshot, aws_client): entries = [ { "Source": "some.source", - "Detail": json.dumps(EVENT_DETAIL), + "Detail": json.dumps(TEST_EVENT_DETAIL), "DetailType": "", }, ] @@ -212,7 +223,7 @@ def test_put_events_exceed_limit_ten_entries( { "Source": TEST_EVENT_PATTERN["source"][0], "DetailType": TEST_EVENT_PATTERN["detail-type"][0], - "Detail": json.dumps(EVENT_DETAIL), + "Detail": json.dumps(TEST_EVENT_DETAIL), "EventBusName": bus_name, } ) @@ -328,7 +339,7 @@ def check_rule_active(): test_event = { "Source": TEST_EVENT_PATTERN_NO_DETAIL["source"][0], "DetailType": TEST_EVENT_PATTERN_NO_DETAIL["detail-type"][0], - "Detail": json.dumps(EVENT_DETAIL), + "Detail": json.dumps(TEST_EVENT_DETAIL), } event_response = aws_client.events.put_events(Entries=[test_event]) @@ -352,7 +363,7 @@ def verify_message_content(message, original_event_id): detail = body["detail"] # detail is already parsed as dict assert isinstance(detail, dict), f"Detail should be a dict, got {type(detail)}" - assert detail == EVENT_DETAIL, f"Unexpected detail content: {detail}" + assert detail == TEST_EVENT_DETAIL, f"Unexpected detail content: {detail}" assert ( body["id"] == original_event_id @@ -419,7 +430,7 @@ def test_put_events_with_target_delivery_failure( test_event = { "Source": TEST_EVENT_PATTERN_NO_DETAIL["source"][0], "DetailType": TEST_EVENT_PATTERN_NO_DETAIL["detail-type"][0], - "Detail": json.dumps(EVENT_DETAIL), + "Detail": json.dumps(TEST_EVENT_DETAIL), } response = aws_client.events.put_events(Entries=[test_event]) @@ -962,7 +973,7 @@ def test_put_events_bus_to_bus( "EventBusName": bus_name_one, "Source": TEST_EVENT_PATTERN["source"][0], "DetailType": TEST_EVENT_PATTERN["detail-type"][0], - "Detail": json.dumps(EVENT_DETAIL), + "Detail": json.dumps(TEST_EVENT_DETAIL), } ] ) @@ -1394,6 +1405,338 @@ def test_update_rule_with_targets( response = aws_client.events.list_targets_by_rule(Rule=rule_name) snapshot.match("list-targets-after-update", response) + @markers.aws.validated + def test_process_to_multiple_matching_rules_different_targets( + self, + events_create_event_bus, + sqs_create_queue, + sqs_get_queue_arn, + events_put_rule, + aws_client, + ): + """two rules with each two sqs targets, all 4 queues should receive the event""" + + custom_bus_name = f"test-bus-{short_uid()}" + events_create_event_bus(Name=custom_bus_name) + + # create sqs queues targets + targets = {} + for i in range(4): + queue_url = sqs_create_queue() + queue_arn = sqs_get_queue_arn(queue_url) + targets[f"sqs_target_{i}"] = {"queue_url": queue_url, "queue_arn": queue_arn} + + # create rules + rules = {} + for i in range(2): + rule_name = f"test-rule-{i}-{short_uid()}" + rule = events_put_rule( + Name=rule_name, + EventBusName=custom_bus_name, + EventPattern=json.dumps(TEST_EVENT_PATTERN_NO_DETAIL), + State="ENABLED", + ) + rule_arn = rule["RuleArn"] + rules[f"rule_{i}"] = {"rule_name": rule_name, "rule_arn": rule_arn} + + # attach targets to rule + combinations = [("0", ["0", "1"]), ("1", ["2", "3"])] + for rule_idx, targets_idxs in combinations: + rule_arn = rules[f"rule_{rule_idx}"]["rule_arn"] + for target_idx in targets_idxs: + queue_url = targets[f"sqs_target_{target_idx}"]["queue_url"] + queue_arn = targets[f"sqs_target_{target_idx}"]["queue_arn"] + allow_event_rule_to_sqs_queue( + aws_client=aws_client, + sqs_queue_url=queue_url, + sqs_queue_arn=queue_arn, + event_rule_arn=rule_arn, + ) + + aws_client.events.put_targets( + Rule=rules[f"rule_{rule_idx}"]["rule_name"], + EventBusName=custom_bus_name, + Targets=[ + {"Id": f"test-target-{target_idx}-{short_uid()}", "Arn": queue_arn}, + ], + ) + + # put event + aws_client.events.put_events( + Entries=[ + { + "EventBusName": custom_bus_name, + "Source": TEST_EVENT_PATTERN_NO_DETAIL["source"][0], + "DetailType": TEST_EVENT_PATTERN_NO_DETAIL["detail-type"][0], + "Detail": json.dumps(TEST_EVENT_DETAIL), + } + ], + ) + + sqs_collect_messages( + aws_client, targets["sqs_target_0"]["queue_url"], expected_events_count=1 + ) + sqs_collect_messages( + aws_client, targets["sqs_target_1"]["queue_url"], expected_events_count=1 + ) + sqs_collect_messages( + aws_client, targets["sqs_target_2"]["queue_url"], expected_events_count=1 + ) + sqs_collect_messages( + aws_client, targets["sqs_target_3"]["queue_url"], expected_events_count=1 + ) + + @markers.aws.validated + def test_process_to_multiple_matching_rules_single_target( + self, + create_lambda_function, + events_create_event_bus, + events_put_rule, + aws_client, + snapshot, + ): + """two rules with both the same lambda target, the lambda target should be invoked twice. + This will only work for certain targets, since e.g. sqs has message deduplication""" + + bus_name = f"test-bus-{short_uid()}" + events_create_event_bus(Name=bus_name) + + # create lambda target + function_name = f"lambda-func-{short_uid()}" + create_lambda_response = create_lambda_function( + handler_file=TEST_LAMBDA_PYTHON_ECHO, + func_name=function_name, + runtime=Runtime.python3_12, + ) + lambda_function_arn = create_lambda_response["CreateFunctionResponse"]["FunctionArn"] + + # create rules + for i in range(2): + rule_name = f"test-rule-{i}-{short_uid()}" + rule = events_put_rule( + Name=rule_name, + EventBusName=bus_name, + EventPattern=json.dumps(TEST_EVENT_PATTERN_NO_DETAIL), + State="ENABLED", + ) + rule_arn = rule["RuleArn"] + + aws_client.lambda_.add_permission( + FunctionName=function_name, + StatementId=f"{rule_name}-Event", + Action="lambda:InvokeFunction", + Principal="events.amazonaws.com", + SourceArn=rule_arn, + ) + + target_id = f"test-target-{i}-{short_uid()}" + aws_client.events.put_targets( + Rule=rule_name, + EventBusName=bus_name, + Targets=[{"Id": target_id, "Arn": lambda_function_arn}], + ) + + # put event + aws_client.events.put_events( + Entries=[ + { + "EventBusName": bus_name, + "Source": TEST_EVENT_PATTERN_NO_DETAIL["source"][0], + "DetailType": TEST_EVENT_PATTERN_NO_DETAIL["detail-type"][0], + "Detail": json.dumps(TEST_EVENT_DETAIL), + } + ], + ) + + # check lambda invocation + events = retry( + check_expected_lambda_log_events_length, + retries=3, + sleep=1, + function_name=function_name, + expected_length=2, + logs_client=aws_client.logs, + ) + snapshot.match("events", events) + + @markers.aws.validated + def test_process_to_single_matching_rules_single_target( + self, + create_lambda_function, + events_create_event_bus, + events_put_rule, + aws_client, + snapshot, + ): + """Three rules with all the same lambda target, but different patterns as condition. + The lambda should onl be invoked by the rule matching the event pattern.""" + + bus_name = f"test-bus-{short_uid()}" + events_create_event_bus(Name=bus_name) + + # create lambda target + function_name = f"lambda-func-{short_uid()}" + create_lambda_response = create_lambda_function( + handler_file=TEST_LAMBDA_PYTHON_ECHO, + func_name=function_name, + runtime=Runtime.python3_12, + ) + lambda_function_arn = create_lambda_response["CreateFunctionResponse"]["FunctionArn"] + + # create rules + sources = ["source-one", "source-two", "source-three"] + for i, source in zip(range(3), sources): + rule_name = f"test-rule-{i}-{short_uid()}" + rule = events_put_rule( + Name=rule_name, + EventBusName=bus_name, + EventPattern=json.dumps({"source": [source]}), + State="ENABLED", + ) + rule_arn = rule["RuleArn"] + + aws_client.lambda_.add_permission( + FunctionName=function_name, + StatementId=f"{rule_name}-Event", + Action="lambda:InvokeFunction", + Principal="events.amazonaws.com", + SourceArn=rule_arn, + ) + + target_id = f"test-target-{i}-{short_uid()}" + aws_client.events.put_targets( + Rule=rule_name, + EventBusName=bus_name, + Targets=[{"Id": target_id, "Arn": lambda_function_arn}], + ) + + for i, source in zip(range(3), sources): + num_events = i + 1 + aws_client.events.put_events( + Entries=[ + { + "EventBusName": bus_name, + "Source": source, + "DetailType": TEST_EVENT_PATTERN_NO_DETAIL["detail-type"][0], + "Detail": json.dumps(TEST_EVENT_DETAIL), + } + ], + ) + + # check lambda invocation + events = retry( + check_expected_lambda_log_events_length, + retries=3, + sleep=1, + function_name=function_name, + expected_length=num_events, + logs_client=aws_client.logs, + ) + snapshot.match(f"events-{source}", events) + + @markers.aws.validated + @pytest.mark.skipif( + is_old_provider(), + reason="V1 provider does not support this feature", + ) + def test_process_pattern_to_single_matching_rules_single_target( + self, + create_lambda_function, + events_create_event_bus, + events_put_rule, + aws_client, + snapshot, + ): + """Three rules with all the same lambda target, but different patterns as condition. + The lambda should onl be invoked by the rule matching the event pattern.""" + + bus_name = f"test-bus-{short_uid()}" + events_create_event_bus(Name=bus_name) + + # create lambda target + function_name = f"lambda-func-{short_uid()}" + create_lambda_response = create_lambda_function( + handler_file=TEST_LAMBDA_PYTHON_ECHO, + func_name=function_name, + runtime=Runtime.python3_12, + ) + lambda_function_arn = create_lambda_response["CreateFunctionResponse"]["FunctionArn"] + + # create rules + input_path_map = {"detail": "$.detail"} + patterns = [ + {"detail": {"payload": {"id": [{"exists": True}]}}}, + {"detail": {"id": [{"exists": True}]}}, + ] + input_transformers = [ + { + "InputPathsMap": input_path_map, + "InputTemplate": '{"detail-payload-with-id": }', + }, + { + "InputPathsMap": input_path_map, + "InputTemplate": '{"detail-with-id": }', + }, + ] + for i, pattern, input_transformer in zip(range(2), patterns, input_transformers): + rule_name = f"test-rule-{i}-{short_uid()}" + rule = events_put_rule( + Name=rule_name, + EventBusName=bus_name, + EventPattern=json.dumps(pattern), + State="ENABLED", + ) + rule_arn = rule["RuleArn"] + + aws_client.lambda_.add_permission( + FunctionName=function_name, + StatementId=f"{rule_name}-Event", + Action="lambda:InvokeFunction", + Principal="events.amazonaws.com", + SourceArn=rule_arn, + ) + + target_id = f"test-target-{i}-{short_uid()}" + aws_client.events.put_targets( + Rule=rule_name, + EventBusName=bus_name, + Targets=[ + { + "Id": target_id, + "Arn": lambda_function_arn, + "InputTransformer": input_transformer, + } + ], + ) + + details = [ + {"payload": {"id": "123"}}, + {"id": "123"}, + ] + for i, detail in zip(range(2), details): + num_events = i + 1 + aws_client.events.put_events( + Entries=[ + { + "EventBusName": bus_name, + "Source": TEST_EVENT_PATTERN_NO_DETAIL["source"][0], + "DetailType": TEST_EVENT_PATTERN_NO_DETAIL["detail-type"][0], + "Detail": json.dumps(detail), + } + ], + ) + + # check lambda invocation + events = retry( + check_expected_lambda_log_events_length, + retries=3, + sleep=1, + function_name=function_name, + expected_length=num_events, + logs_client=aws_client.logs, + ) + snapshot.match(f"events-{num_events}", events) + class TestEventPattern: @markers.aws.validated @@ -1626,3 +1969,173 @@ def test_put_target_id_validation( {"Id": target_id, "Arn": queue_arn, "InputPath": "$.detail"}, ], ) + + @markers.aws.validated + def test_put_multiple_targets_with_same_id_single_rule( + self, sqs_create_queue, sqs_get_queue_arn, events_put_rule, snapshot, aws_client + ): + """Targets attached to a rule must have unique IDs, but there is no validation for this. + The last target with the same ID will overwrite the previous one.""" + rule_name = f"rule-{short_uid()}" + queue_url = sqs_create_queue() + queue_arn = sqs_get_queue_arn(queue_url) + + events_put_rule( + Name=rule_name, EventPattern=json.dumps(TEST_EVENT_PATTERN), State="ENABLED" + ) + + target_id = f"test-With_valid.Characters-{short_uid()}" + aws_client.events.put_targets( + Rule=rule_name, + Targets=[ + {"Id": target_id, "Arn": queue_arn, "InputPath": "$.detail"}, + ], + ) + + aws_client.events.put_targets( + Rule=rule_name, + Targets=[ + { + "Id": target_id, + "Arn": queue_arn, + "InputPath": "$.notexisting", + }, + ], + ) + + snapshot.add_transformers_list( + [ + snapshot.transform.regex(target_id, "target-id"), + snapshot.transform.regex(queue_arn, "target-arn"), + ] + ) + response = aws_client.events.list_targets_by_rule(Rule=rule_name) + snapshot.match("list-targets", response) + + @markers.aws.validated + def test_put_multiple_targets_with_same_id_across_different_rules( + self, sqs_create_queue, sqs_get_queue_arn, events_put_rule, snapshot, aws_client + ): + """Targets attached to different rules can have the same ID""" + rule_one_name = f"test-rule-one-{short_uid()}" + rule_two_name = f"test-rule-two-{short_uid()}" + queue_url = sqs_create_queue() + queue_arn = sqs_get_queue_arn(queue_url) + + events_put_rule( + Name=rule_one_name, EventPattern=json.dumps(TEST_EVENT_PATTERN), State="ENABLED" + ) + events_put_rule( + Name=rule_two_name, EventPattern=json.dumps(TEST_EVENT_PATTERN), State="ENABLED" + ) + + target_id = f"test-With_valid.Characters-{short_uid()}" + aws_client.events.put_targets( + Rule=rule_one_name, + Targets=[ + {"Id": target_id, "Arn": queue_arn, "InputPath": "$.detail"}, + ], + ) + + aws_client.events.put_targets( + Rule=rule_two_name, + Targets=[ + { + "Id": target_id, + "Arn": queue_arn, + "InputPath": "$.notexisting", + }, + ], + ) + + snapshot.add_transformers_list( + [ + snapshot.transform.regex(target_id, "target-id"), + snapshot.transform.regex(queue_arn, "target-arn"), + ] + ) + + response = aws_client.events.list_targets_by_rule(Rule=rule_one_name) + snapshot.match("list-targets-rule-one", response) + + response = aws_client.events.list_targets_by_rule(Rule=rule_two_name) + snapshot.match("list-targets-rule-two", response) + + @markers.aws.validated + def test_put_multiple_targets_with_same_arn_single_rule( + self, sqs_create_queue, sqs_get_queue_arn, events_put_rule, snapshot, aws_client + ): + """Targets attached to a rule can have the same ARN, but different IDs""" + rule_name = f"rule-{short_uid()}" + queue_url = sqs_create_queue() + queue_arn = sqs_get_queue_arn(queue_url) + + events_put_rule( + Name=rule_name, EventPattern=json.dumps(TEST_EVENT_PATTERN), State="ENABLED" + ) + + target_id_one = f"test-With_valid.Characters-{short_uid()}" + target_id_two = f"test-With_valid.Characters-{short_uid()}" + aws_client.events.put_targets( + Rule=rule_name, + Targets=[ + {"Id": target_id_one, "Arn": queue_arn, "InputPath": "$.detail"}, + {"Id": target_id_two, "Arn": queue_arn, "InputPath": "$.doesnotexist"}, + ], + ) + + snapshot.add_transformers_list( + [ + snapshot.transform.regex(target_id_one, "target-id-one"), + snapshot.transform.regex(target_id_two, "target-id-two"), + snapshot.transform.regex(queue_arn, "target-arn"), + ] + ) + + response = aws_client.events.list_targets_by_rule(Rule=rule_name) + snapshot.match("list-targets", response) + + @markers.aws.validated + def test_put_multiple_targets_with_same_arn_across_different_rules( + self, sqs_create_queue, sqs_get_queue_arn, events_put_rule, snapshot, aws_client + ): + """Targets attached to different rules can have the same ARN""" + rule_one_name = f"test-rule-one-{short_uid()}" + rule_two_name = f"test-rule-two-{short_uid()}" + queue_url = sqs_create_queue() + queue_arn = sqs_get_queue_arn(queue_url) + + events_put_rule( + Name=rule_one_name, EventPattern=json.dumps(TEST_EVENT_PATTERN), State="ENABLED" + ) + events_put_rule( + Name=rule_two_name, EventPattern=json.dumps(TEST_EVENT_PATTERN), State="ENABLED" + ) + + target_id = f"test-With_valid.Characters-{short_uid()}" + aws_client.events.put_targets( + Rule=rule_one_name, + Targets=[ + {"Id": target_id, "Arn": queue_arn, "InputPath": "$.detail"}, + ], + ) + + aws_client.events.put_targets( + Rule=rule_two_name, + Targets=[ + {"Id": target_id, "Arn": queue_arn, "InputPath": "$.doesnotexist"}, + ], + ) + + snapshot.add_transformers_list( + [ + snapshot.transform.regex(target_id, "target-id"), + snapshot.transform.regex(queue_arn, "target-arn"), + ] + ) + + response = aws_client.events.list_targets_by_rule(Rule=rule_one_name) + snapshot.match("list-targets-rule-one", response) + + response = aws_client.events.list_targets_by_rule(Rule=rule_two_name) + snapshot.match("list-targets-rule-two", response) diff --git a/tests/aws/services/events/test_events.snapshot.json b/tests/aws/services/events/test_events.snapshot.json index 33ae5bc1c260f..8766362af3472 100644 --- a/tests/aws/services/events/test_events.snapshot.json +++ b/tests/aws/services/events/test_events.snapshot.json @@ -1982,5 +1982,294 @@ } } } + }, + "tests/aws/services/events/test_events.py::TestEventRule::test_process_to_multiple_matching_rules_different_targets": { + "recorded-date": "27-12-2024, 19:02:05", + "recorded-content": {} + }, + "tests/aws/services/events/test_events.py::TestEventRule::test_process_to_multiple_matching_rules_single_target": { + "recorded-date": "02-01-2025, 11:37:47", + "recorded-content": { + "events": [ + { + "version": "0", + "id": "", + "detail-type": "core.update-account-command", + "source": "core.update-account-command", + "account": "111111111111", + "time": "date", + "region": "", + "resources": [], + "detail": { + "command": "update-account", + "payload": { + "acc_id": "0a787ecb-4015", + "sf_id": "baz" + } + } + }, + { + "version": "0", + "id": "", + "detail-type": "core.update-account-command", + "source": "core.update-account-command", + "account": "111111111111", + "time": "date", + "region": "", + "resources": [], + "detail": { + "command": "update-account", + "payload": { + "acc_id": "0a787ecb-4015", + "sf_id": "baz" + } + } + } + ] + } + }, + "tests/aws/services/events/test_events.py::TestEventRule::test_process_to_single_matching_rules_single_target": { + "recorded-date": "02-01-2025, 12:09:21", + "recorded-content": { + "events-source-one": [ + { + "version": "0", + "id": "", + "detail-type": "core.update-account-command", + "source": "source-one", + "account": "111111111111", + "time": "date", + "region": "", + "resources": [], + "detail": { + "command": "update-account", + "payload": { + "acc_id": "0a787ecb-4015", + "sf_id": "baz" + } + } + } + ], + "events-source-two": [ + { + "version": "0", + "id": "", + "detail-type": "core.update-account-command", + "source": "source-one", + "account": "111111111111", + "time": "date", + "region": "", + "resources": [], + "detail": { + "command": "update-account", + "payload": { + "acc_id": "0a787ecb-4015", + "sf_id": "baz" + } + } + }, + { + "version": "0", + "id": "", + "detail-type": "core.update-account-command", + "source": "source-two", + "account": "111111111111", + "time": "date", + "region": "", + "resources": [], + "detail": { + "command": "update-account", + "payload": { + "acc_id": "0a787ecb-4015", + "sf_id": "baz" + } + } + } + ], + "events-source-three": [ + { + "version": "0", + "id": "", + "detail-type": "core.update-account-command", + "source": "source-one", + "account": "111111111111", + "time": "date", + "region": "", + "resources": [], + "detail": { + "command": "update-account", + "payload": { + "acc_id": "0a787ecb-4015", + "sf_id": "baz" + } + } + }, + { + "version": "0", + "id": "", + "detail-type": "core.update-account-command", + "source": "source-two", + "account": "111111111111", + "time": "date", + "region": "", + "resources": [], + "detail": { + "command": "update-account", + "payload": { + "acc_id": "0a787ecb-4015", + "sf_id": "baz" + } + } + }, + { + "version": "0", + "id": "", + "detail-type": "core.update-account-command", + "source": "source-three", + "account": "111111111111", + "time": "date", + "region": "", + "resources": [], + "detail": { + "command": "update-account", + "payload": { + "acc_id": "0a787ecb-4015", + "sf_id": "baz" + } + } + } + ] + } + }, + "tests/aws/services/events/test_events.py::TestEventRule::test_process_pattern_to_single_matching_rules_single_target": { + "recorded-date": "02-01-2025, 12:47:30", + "recorded-content": { + "events-1": [ + { + "detail-payload-with-id": { + "payload": { + "id": "123" + } + } + } + ], + "events-2": [ + { + "detail-payload-with-id": { + "payload": { + "id": "123" + } + } + }, + { + "detail-with-id": { + "id": "123" + } + } + ] + } + }, + "tests/aws/services/events/test_events.py::TestEventTarget::test_put_multiple_targets_with_same_id_single_rule": { + "recorded-date": "03-01-2025, 12:29:52", + "recorded-content": { + "list-targets": { + "Targets": [ + { + "Arn": "target-arn", + "Id": "target-id", + "InputPath": "$.notexisting" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/events/test_events.py::TestEventTarget::test_put_multiple_targets_with_same_id_across_different_rules": { + "recorded-date": "03-01-2025, 12:30:57", + "recorded-content": { + "list-targets-rule-one": { + "Targets": [ + { + "Arn": "target-arn", + "Id": "target-id", + "InputPath": "$.detail" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "list-targets-rule-two": { + "Targets": [ + { + "Arn": "target-arn", + "Id": "target-id", + "InputPath": "$.notexisting" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/events/test_events.py::TestEventTarget::test_put_multiple_targets_with_same_arn_single_rule": { + "recorded-date": "03-01-2025, 12:32:45", + "recorded-content": { + "list-targets": { + "Targets": [ + { + "Arn": "target-arn", + "Id": "target-id-one", + "InputPath": "$.detail" + }, + { + "Arn": "target-arn", + "Id": "target-id-two", + "InputPath": "$.doesnotexist" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/events/test_events.py::TestEventTarget::test_put_multiple_targets_with_same_arn_across_different_rules": { + "recorded-date": "03-01-2025, 12:33:23", + "recorded-content": { + "list-targets-rule-one": { + "Targets": [ + { + "Arn": "target-arn", + "Id": "target-id", + "InputPath": "$.detail" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "list-targets-rule-two": { + "Targets": [ + { + "Arn": "target-arn", + "Id": "target-id", + "InputPath": "$.doesnotexist" + } + ], + "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 c7a4c7efa0a3b..335927aba3e7e 100644 --- a/tests/aws/services/events/test_events.validation.json +++ b/tests/aws/services/events/test_events.validation.json @@ -1,15 +1,9 @@ { - "tests/aws/services/events/test_events.py::TestEvents::test_create_connection_validations": { - "last_validated_date": "2024-12-13T10:54:30+00:00" + "tests/aws/services/events/test_events.py::TestEventTarget::test_put_multiple_targets_with_same_arn_across_different_rules": { + "last_validated_date": "2025-01-03T12:33:23+00:00" } } - } -} -} -" - } -} " } } diff --git a/tests/aws/services/events/test_events_cross_account_region.py b/tests/aws/services/events/test_events_cross_account_region.py index 572f52780a8f4..5a7e8adb1837a 100644 --- a/tests/aws/services/events/test_events_cross_account_region.py +++ b/tests/aws/services/events/test_events_cross_account_region.py @@ -11,7 +11,7 @@ sqs_collect_messages, ) from tests.aws.services.events.test_events import ( - EVENT_DETAIL, + TEST_EVENT_DETAIL, TEST_EVENT_PATTERN_NO_SOURCE, ) @@ -231,7 +231,7 @@ def test_event_bus_to_event_bus_cross_account_region( { "Source": SOURCE_PRIMARY, "DetailType": TEST_EVENT_PATTERN_NO_SOURCE["detail-type"][0], - "Detail": json.dumps(EVENT_DETAIL), + "Detail": json.dumps(TEST_EVENT_DETAIL), "EventBusName": event_bus_name_primary, } ], @@ -265,7 +265,7 @@ def test_event_bus_to_event_bus_cross_account_region( { "Source": SOURCE_SECONDARY, "DetailType": TEST_EVENT_PATTERN_NO_SOURCE["detail-type"][0], - "Detail": json.dumps(EVENT_DETAIL), + "Detail": json.dumps(TEST_EVENT_DETAIL), "EventBusName": event_bus_name_secondary, } ], @@ -424,7 +424,7 @@ def test_put_events( { "Source": SOURCE_PRIMARY, "DetailType": TEST_EVENT_PATTERN_NO_SOURCE["detail-type"][0], - "Detail": json.dumps(EVENT_DETAIL), + "Detail": json.dumps(TEST_EVENT_DETAIL), "EventBusName": event_bus_arn, # using arn for cross region / cross account } ], diff --git a/tests/aws/services/events/test_events_inputs.py b/tests/aws/services/events/test_events_inputs.py index bf531a2a95413..30efe56e432fe 100644 --- a/tests/aws/services/events/test_events_inputs.py +++ b/tests/aws/services/events/test_events_inputs.py @@ -12,8 +12,8 @@ sqs_collect_messages, ) from tests.aws.services.events.test_events import ( - EVENT_DETAIL, SPECIAL_EVENT_DETAIL, + TEST_EVENT_DETAIL, TEST_EVENT_PATTERN, ) @@ -82,7 +82,7 @@ def test_put_events_with_input_path(self, put_events_with_filter_to_sqs, snapsho { "Source": TEST_EVENT_PATTERN["source"][0], "DetailType": TEST_EVENT_PATTERN["detail-type"][0], - "Detail": json.dumps(EVENT_DETAIL), + "Detail": json.dumps(TEST_EVENT_DETAIL), } ] entries_asserts = [(entries1, True)] @@ -101,7 +101,7 @@ def test_put_events_with_input_path(self, put_events_with_filter_to_sqs, snapsho snapshot.match("message", messages) @markers.aws.validated - @pytest.mark.parametrize("event_detail", [EVENT_DETAIL, EVENT_DETAIL_DUPLICATED_KEY]) + @pytest.mark.parametrize("event_detail", [TEST_EVENT_DETAIL, EVENT_DETAIL_DUPLICATED_KEY]) def test_put_events_with_input_path_nested( self, event_detail, put_events_with_filter_to_sqs, snapshot ): @@ -135,7 +135,7 @@ def test_put_events_with_input_path_max_level_depth( { "Source": TEST_EVENT_PATTERN["source"][0], "DetailType": TEST_EVENT_PATTERN["detail-type"][0], - "Detail": json.dumps(EVENT_DETAIL), + "Detail": json.dumps(TEST_EVENT_DETAIL), } ] entries_asserts = [(entries1, True)] @@ -193,7 +193,7 @@ def test_put_events_with_input_path_multiple_targets( "EventBusName": bus_name, "Source": TEST_EVENT_PATTERN["source"][0], "DetailType": TEST_EVENT_PATTERN["detail-type"][0], - "Detail": json.dumps(EVENT_DETAIL), + "Detail": json.dumps(TEST_EVENT_DETAIL), } ] ) @@ -235,7 +235,7 @@ def test_put_events_with_input_transformer_input_template_string( { "Source": TEST_EVENT_PATTERN["source"][0], "DetailType": TEST_EVENT_PATTERN["detail-type"][0], - "Detail": json.dumps(EVENT_DETAIL), + "Detail": json.dumps(TEST_EVENT_DETAIL), } ] entries_asserts = [(entries, True)] @@ -294,7 +294,7 @@ def test_put_events_with_input_transformer_input_template_json( { "Source": TEST_EVENT_PATTERN["source"][0], "DetailType": TEST_EVENT_PATTERN["detail-type"][0], - "Detail": json.dumps(EVENT_DETAIL), + "Detail": json.dumps(TEST_EVENT_DETAIL), } ] entries_asserts = [(entries, True)] @@ -455,7 +455,7 @@ def test_input_transformer_predefined_variables( "EventBusName": bus_name, "Source": TEST_EVENT_PATTERN["source"][0], "DetailType": TEST_EVENT_PATTERN["detail-type"][0], - "Detail": json.dumps(EVENT_DETAIL), + "Detail": json.dumps(TEST_EVENT_DETAIL), } ] ) diff --git a/tests/aws/services/events/test_events_targets.py b/tests/aws/services/events/test_events_targets.py index bfa433591fb33..a0ebdc0285d54 100644 --- a/tests/aws/services/events/test_events_targets.py +++ b/tests/aws/services/events/test_events_targets.py @@ -22,7 +22,7 @@ from tests.aws.scenario.kinesis_firehose.conftest import get_all_expected_messages_from_s3 from tests.aws.services.events.helper_functions import is_old_provider, sqs_collect_messages from tests.aws.services.events.test_api_destinations_and_connection import API_DESTINATION_AUTHS -from tests.aws.services.events.test_events import EVENT_DETAIL, TEST_EVENT_PATTERN +from tests.aws.services.events.test_events import TEST_EVENT_DETAIL, TEST_EVENT_PATTERN from tests.aws.services.firehose.helper_functions import get_firehose_iam_documents from tests.aws.services.kinesis.helper_functions import get_shard_iterator from tests.aws.services.lambda_.test_lambda import ( @@ -533,7 +533,7 @@ def test_put_events_with_target_cloudwatch_logs( "EventBusName": event_bus_name, "Source": TEST_EVENT_PATTERN["source"][0], "DetailType": TEST_EVENT_PATTERN["detail-type"][0], - "Detail": json.dumps(EVENT_DETAIL), + "Detail": json.dumps(TEST_EVENT_DETAIL), } put_events_response = aws_client.events.put_events(Entries=[event_entry]) snapshot.match("put_events_response", put_events_response) @@ -657,7 +657,7 @@ def test_put_events_with_target_events( { "Source": TEST_EVENT_PATTERN["source"][0], "DetailType": TEST_EVENT_PATTERN["detail-type"][0], - "Detail": json.dumps(EVENT_DETAIL), + "Detail": json.dumps(TEST_EVENT_DETAIL), "EventBusName": event_bus_name_source, } ], @@ -782,7 +782,7 @@ def test_put_events_with_target_firehose( "EventBusName": event_bus_name, "Source": TEST_EVENT_PATTERN["source"][0], "DetailType": TEST_EVENT_PATTERN["detail-type"][0], - "Detail": json.dumps(EVENT_DETAIL), + "Detail": json.dumps(TEST_EVENT_DETAIL), } ] ) @@ -895,7 +895,7 @@ def test_put_events_with_target_kinesis( "EventBusName": event_bus_name, "Source": TEST_EVENT_PATTERN["source"][0], "DetailType": TEST_EVENT_PATTERN["detail-type"][0], - "Detail": json.dumps(EVENT_DETAIL), + "Detail": json.dumps(TEST_EVENT_DETAIL), } ] ) @@ -959,7 +959,7 @@ def test_put_events_with_target_lambda( "EventBusName": bus_name, "Source": TEST_EVENT_PATTERN["source"][0], "DetailType": TEST_EVENT_PATTERN["detail-type"][0], - "Detail": json.dumps(EVENT_DETAIL), + "Detail": json.dumps(TEST_EVENT_DETAIL), } ] ) @@ -1247,7 +1247,7 @@ def test_put_events_with_target_sns( "EventBusName": event_bus_name, "Source": TEST_EVENT_PATTERN["source"][0], "DetailType": TEST_EVENT_PATTERN["detail-type"][0], - "Detail": json.dumps(EVENT_DETAIL), + "Detail": json.dumps(TEST_EVENT_DETAIL), } ] ) @@ -1277,7 +1277,7 @@ def test_put_events_with_target_sqs(self, put_events_with_filter_to_sqs, snapsho { "Source": TEST_EVENT_PATTERN["source"][0], "DetailType": TEST_EVENT_PATTERN["detail-type"][0], - "Detail": json.dumps(EVENT_DETAIL), + "Detail": json.dumps(TEST_EVENT_DETAIL), } ] message = put_events_with_filter_to_sqs(