diff --git a/aws_lambda_powertools/event_handler/async_execution/__init__.py b/aws_lambda_powertools/event_handler/async_execution/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/aws_lambda_powertools/event_handler/async_execution/exceptions.py b/aws_lambda_powertools/event_handler/async_execution/exceptions.py new file mode 100644 index 00000000000..3e4c091eb04 --- /dev/null +++ b/aws_lambda_powertools/event_handler/async_execution/exceptions.py @@ -0,0 +1,2 @@ +class RouteNotFoundError(Exception): + pass diff --git a/aws_lambda_powertools/event_handler/async_execution/router.py b/aws_lambda_powertools/event_handler/async_execution/router.py new file mode 100644 index 00000000000..f6cfc1bbaca --- /dev/null +++ b/aws_lambda_powertools/event_handler/async_execution/router.py @@ -0,0 +1,169 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Callable + +from .routes.aws_config_rule import AwsConfigRuleRoute +from .routes.cloud_watch_alarm import CloudWatchAlarmRoute +from .routes.cloud_watch_logs import CloudWatchLogsRoute +from .routes.code_deploy_lifecycle_hook import CodeDeployLifecycleHookRoute +from .routes.event_bridge import EventBridgeRoute +from .routes.s3 import S3Route +from .routes.secrets_manager import SecretsManagerRoute +from .routes.ses import SESRoute +from .routes.sns import SNSRoute + +if TYPE_CHECKING: + from .routes.base import BaseRoute + + +class Router: + _routes: list[BaseRoute] + + def __init__(self): + self._routes = [] + + def aws_config_rule( + self, + arn: str | None = None, + rule_name: str | None = None, + rule_name_prefix: str | None = None, + rule_id: str | None = None, + ) -> Callable: + def wrapper_aws_config_rule(func: Callable): + self._routes.append( + AwsConfigRuleRoute( + func=func, + arn=arn, + rule_name=rule_name, + rule_name_prefix=rule_name_prefix, + rule_id=rule_id, + ), + ) + + return wrapper_aws_config_rule + + def cloud_watch_alarm( + self, + arn: str | None = None, + alarm_name: str | None = None, + alarm_name_prefix: str | None = None, + ) -> Callable: + def wrapper_cloud_watch_alarm(func: Callable): + self._routes.append( + CloudWatchAlarmRoute(func=func, arn=arn, alarm_name=alarm_name, alarm_name_prefix=alarm_name_prefix), + ) + + return wrapper_cloud_watch_alarm + + def cloud_watch_logs( + self, + log_group: str | None = None, + log_group_prefix: str | None = None, + log_stream: str | None = None, + log_stream_prefix: str | None = None, + subscription_filters: str | list[str] | None = None, + ) -> Callable: + def wrapper_cloud_watch_logs(func: Callable): + self._routes.append( + CloudWatchLogsRoute( + func=func, + log_group=log_group, + log_group_prefix=log_group_prefix, + log_stream=log_stream, + log_stream_prefix=log_stream_prefix, + subscription_filters=subscription_filters, + ), + ) + + return wrapper_cloud_watch_logs + + def code_deploy_lifecycle_hook(self) -> Callable: + def wrapper_code_deploy_lifecycle_hook(func: Callable): + self._routes.append(CodeDeployLifecycleHookRoute(func=func)) + + return wrapper_code_deploy_lifecycle_hook + + def event_bridge( + self, + detail_type: str | None = None, + source: str | None = None, + resources: str | list[str] | None = None, + ) -> Callable: + def wrap_event_bridge(func: Callable): + self._routes.append( + EventBridgeRoute(func=func, detail_type=detail_type, source=source, resources=resources), + ) + + return wrap_event_bridge + + def s3( + self, + bucket: str | None = None, + bucket_prefix: str | None = None, + key: str | None = None, + key_prefix: str | None = None, + key_suffix: str | None = None, + event_name: str | None = None, + ) -> Callable: + def wrap_s3(func: Callable): + self._routes.append( + S3Route( + func=func, + bucket=bucket, + bucket_prefix=bucket_prefix, + key=key, + key_prefix=key_prefix, + key_suffix=key_suffix, + event_name=event_name, + ), + ) + + return wrap_s3 + + def secrets_manager(self, secret_id: str | None = None, secret_name_prefix: str | None = None): + def wrap_secrets_manager(func: Callable): + self._routes.append( + SecretsManagerRoute(func=func, secret_id=secret_id, secret_name_prefix=secret_name_prefix), + ) + + return wrap_secrets_manager + + def ses( + self, + mail_to: str | list[str] | None = None, + mail_from: str | list[str] | None = None, + mail_subject: str | None = None, + ) -> Callable: + def wrap_ses(func: Callable): + self._routes.append(SESRoute(func=func, mail_to=mail_to, mail_from=mail_from, mail_subject=mail_subject)) + + return wrap_ses + + def sns( + self, + arn: str | None = None, + name: str | None = None, + name_prefix: str | None = None, + subject: str | None = None, + subject_prefix: str | None = None, + ) -> Callable: + def wrap_sns(func: Callable): + self._routes.append( + SNSRoute( + func=func, + arn=arn, + name=name, + name_prefix=name_prefix, + subject=subject, + subject_prefix=subject_prefix, + ), + ) + + return wrap_sns + + def resolve_route(self, event: dict[str, Any]) -> tuple[Callable, Any] | None: + for route in self._routes: + data = route.match(event=event) + if data is not None: + return data + return None diff --git a/aws_lambda_powertools/event_handler/async_execution/routes/__init__.py b/aws_lambda_powertools/event_handler/async_execution/routes/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/aws_lambda_powertools/event_handler/async_execution/routes/aws_config_rule.py b/aws_lambda_powertools/event_handler/async_execution/routes/aws_config_rule.py new file mode 100644 index 00000000000..e96959f26da --- /dev/null +++ b/aws_lambda_powertools/event_handler/async_execution/routes/aws_config_rule.py @@ -0,0 +1,107 @@ +from __future__ import annotations + +from typing import Any, Callable + +from aws_lambda_powertools.utilities.data_classes.aws_config_rule_event import ( + AWSConfigRuleEvent, +) + +from .base import BaseRoute + + +class AwsConfigRuleRoute(BaseRoute): + arn: str | None + rule_name: str | None + rule_name_prefix: str | None + rule_id: str | None + + def __init__( + self, + func: Callable, + arn: str | None = None, + rule_name: str | None = None, + rule_name_prefix: str | None = None, + rule_id: str | None = None, + ): + self.func = func + self.arn = arn + self.rule_name = rule_name + self.rule_name_prefix = rule_name_prefix + self.rule_id = rule_id + + if not self.arn and not self.rule_name and not self.rule_name_prefix and not self.rule_id: + raise ValueError("arn, rule_name, rule_name_prefix, or rule_id must be not null") + + def is_target_with_arn(self, arn: str | None) -> bool: + if not arn: + return False + elif self.arn: + return self.arn == arn + else: + return False + + def is_target_with_rule_name(self, rule_name: str | None) -> bool: + if not rule_name: + return False + elif self.rule_name: + return self.rule_name == rule_name + elif self.rule_name_prefix: + return rule_name.find(self.rule_name_prefix) == 0 + else: + return False + + def is_target_with_rule_id(self, rule_id: str | None) -> bool: + if not rule_id: + return False + elif self.rule_id: + return self.rule_id == rule_id + else: + return False + + def match(self, event: dict[str, Any]) -> tuple[Callable, AWSConfigRuleEvent] | None: + if not isinstance(event, dict): + return None + + arn = event.get("configRuleArn") + rule_name = event.get("configRuleName") + rule_id = event.get("configRuleId") + + if not arn and not rule_name and not rule_id: + return None + + if not self.arn: + arn = None + + if not self.rule_name and not self.rule_name_prefix: + rule_name = None + + if not self.rule_id: + rule_id = None + + flag_arn = self.is_target_with_arn(arn=arn) + flag_rule_name = self.is_target_with_rule_name(rule_name=rule_name) + flag_rule_id = self.is_target_with_rule_id(rule_id=rule_id) + + text = ", ".join( + [ + "arn: x" if arn is None else "arn: o", + "rule_name: x" if rule_name is None else "rule_name: o", + "rule_id: x" if rule_id is None else "rule_id: o", + ], + ) + + mapping = { + "arn: o, rule_name: o, rule_id: o": flag_arn and flag_rule_name and flag_rule_id, + "arn: o, rule_name: o, rule_id: x": flag_arn and flag_rule_name, + "arn: o, rule_name: x, rule_id: o": flag_arn and flag_rule_id, + "arn: x, rule_name: o, rule_id: o": flag_rule_name and flag_rule_id, + "arn: o, rule_name: x, rule_id: x": flag_arn, + "arn: x, rule_name: o, rule_id: x": flag_rule_name, + "arn: x, rule_name: x, rule_id: o": flag_rule_id, + "arn: x, rule_name: x, rule_id: x": False, + } + + if mapping[text]: + return self.func, AWSConfigRuleEvent(event) + else: + return None diff --git a/aws_lambda_powertools/event_handler/async_execution/routes/base.py b/aws_lambda_powertools/event_handler/async_execution/routes/base.py new file mode 100644 index 00000000000..8d71faada26 --- /dev/null +++ b/aws_lambda_powertools/event_handler/async_execution/routes/base.py @@ -0,0 +1,12 @@ +from __future__ import annotations + +from abc import ABC, abstractmethod +from typing import Any, Callable + + +class BaseRoute(ABC): + func: Callable + + @abstractmethod + def match(self, event: dict[str, Any]) -> tuple[Callable, Any] | None: + raise NotImplementedError() diff --git a/aws_lambda_powertools/event_handler/async_execution/routes/cloud_watch_alarm.py b/aws_lambda_powertools/event_handler/async_execution/routes/cloud_watch_alarm.py new file mode 100644 index 00000000000..a6735bd201e --- /dev/null +++ b/aws_lambda_powertools/event_handler/async_execution/routes/cloud_watch_alarm.py @@ -0,0 +1,83 @@ +from __future__ import annotations + +from typing import Any, Callable + +from aws_lambda_powertools.utilities.data_classes.cloud_watch_alarm_event import ( + CloudWatchAlarmEvent, +) + +from .base import BaseRoute + + +class CloudWatchAlarmRoute(BaseRoute): + arn: str | None + alarm_name: str | None + alarm_name_prefix: str | None + + def __init__( + self, + func: Callable, + arn: str | None = None, + alarm_name: str | None = None, + alarm_name_prefix: str | None = None, + ): + self.func = func + self.arn = arn + self.alarm_name = alarm_name + self.alarm_name_prefix = alarm_name_prefix + + if not self.arn and not self.alarm_name and not self.alarm_name_prefix: + raise ValueError("arn, alarm_name, or alarm_name_prefix must be not null") + + def is_target_with_arn(self, arn: str | None) -> bool: + if not arn: + return False + elif self.arn: + return self.arn == arn + else: + return False + + def is_target_with_alarm_name(self, alarm_name: str | None) -> bool: + if not alarm_name: + return False + elif self.alarm_name: + return self.alarm_name == alarm_name + elif self.alarm_name_prefix: + return alarm_name.find(self.alarm_name_prefix) == 0 + else: + return False + + def match(self, event: dict[str, Any]) -> tuple[Callable, CloudWatchAlarmEvent] | None: + if not isinstance(event, dict): + return None + + arn: str | None = event.get("alarmArn") + alarm_name: str | None = event.get("alarmData", {}).get("alarmName") + + if not arn and not alarm_name: + return None + + if not self.arn: + arn = None + + if not self.alarm_name and not self.alarm_name_prefix: + alarm_name = None + + flag_arn = self.is_target_with_arn(arn=arn) + flag_alarm_name = self.is_target_with_alarm_name(alarm_name=alarm_name) + + text = ", ".join( + ["arn: x" if arn is None else "arn: o", "alarm_name: x" if alarm_name is None else "alarm_name: o"], + ) + + mapping = { + "arn: o, alarm_name: o": flag_arn and flag_alarm_name, + "arn: o, alarm_name: x": flag_arn, + "arn: x, alarm_name: o": flag_alarm_name, + "arn: x, alarm_name: x": False, + } + + if mapping[text]: + return self.func, CloudWatchAlarmEvent(event) + else: + return None diff --git a/aws_lambda_powertools/event_handler/async_execution/routes/cloud_watch_logs.py b/aws_lambda_powertools/event_handler/async_execution/routes/cloud_watch_logs.py new file mode 100644 index 00000000000..c8d598c7512 --- /dev/null +++ b/aws_lambda_powertools/event_handler/async_execution/routes/cloud_watch_logs.py @@ -0,0 +1,137 @@ +from __future__ import annotations + +from typing import Any, Callable + +from aws_lambda_powertools.utilities.data_classes.cloud_watch_logs_event import ( + CloudWatchLogsEvent, +) + +from .base import BaseRoute + + +class CloudWatchLogsRoute(BaseRoute): + log_group: str | None + log_group_prefix: str | None + log_stream: str | None + log_stream_prefix: str | None + subscription_filters: list[str] | None + + def __init__( + self, + func: Callable, + log_group: str | None = None, + log_group_prefix: str | None = None, + log_stream: str | None = None, + log_stream_prefix: str | None = None, + subscription_filters: str | list[str] | None = None, + ): + self.func = func + self.log_group = log_group + self.log_group_prefix = log_group_prefix + self.log_stream = log_stream + self.log_stream_prefix = log_stream_prefix + + if isinstance(subscription_filters, str): + self.subscription_filters = [subscription_filters] + else: + self.subscription_filters = subscription_filters + + if ( + not self.log_group + and not self.log_group_prefix + and not self.log_stream + and not self.log_stream_prefix + and not self.subscription_filters + ): + raise ValueError( + "log_group, log_group_prefix, log_stream, log_stream_prefix, or subscription_filters must be not null", + ) + + def is_target_with_log_group(self, log_group: str | None) -> bool: + if not log_group: + return False + elif self.log_group: + return self.log_group == log_group + elif self.log_group_prefix: + return log_group.find(self.log_group_prefix) == 0 + else: + return False + + def is_target_with_log_stream(self, log_stream: str | None) -> bool: + if not log_stream: + return False + elif self.log_stream: + return self.log_stream == log_stream + elif self.log_stream_prefix: + return log_stream.find(self.log_stream_prefix) == 0 + else: + return False + + def is_target_with_subscription_filters(self, subscription_filters: list[str] | None) -> bool: + if not subscription_filters: + return False + elif not self.subscription_filters: + return False + + for name in self.subscription_filters: + if name in subscription_filters: + return True + + return False + + def match(self, event: dict[str, Any]) -> tuple[Callable, CloudWatchLogsEvent] | None: + if not isinstance(event, dict): + return None + + raw_text = event.get("awslogs", {}).get("data") + + if not isinstance(raw_text, str): + return None + + data = CloudWatchLogsEvent(event) + decoded = data.parse_logs_data() + + if self.log_group or self.log_group_prefix: + log_group = decoded.log_group + else: + log_group = None + + if self.log_stream or self.log_stream_prefix: + log_stream = decoded.log_stream + else: + log_stream = None + + if self.subscription_filters: + subscription_filters = decoded.subscription_filters + else: + subscription_filters = None + + flag_log_group = self.is_target_with_log_group(log_group=log_group) + flag_log_stream = self.is_target_with_log_stream(log_stream=log_stream) + flag_subscription_filters = self.is_target_with_subscription_filters(subscription_filters=subscription_filters) + + text = ", ".join( + [ + "log_group: x" if log_group is None else "log_group: o", + "log_stream: x" if log_stream is None else "log_stream: o", + "subscription_filters: x" if subscription_filters is None else "subscription_filters: o", + ], + ) + + mapping = { + "log_group: o, log_stream: o, subscription_filters: o": flag_log_group + and flag_log_stream + and flag_subscription_filters, + "log_group: o, log_stream: o, subscription_filters: x": flag_log_group and flag_log_stream, + "log_group: o, log_stream: x, subscription_filters: o": flag_log_group and flag_subscription_filters, + "log_group: x, log_stream: o, subscription_filters: o": flag_log_stream and flag_subscription_filters, + "log_group: o, log_stream: x, subscription_filters: x": flag_log_group, + "log_group: x, log_stream: o, subscription_filters: x": flag_log_stream, + "log_group: x, log_stream: x, subscription_filters: o": flag_subscription_filters, + "log_group: x, log_stream: x, subscription_filters: x": False, + } + + if mapping[text]: + return self.func, data + else: + return None diff --git a/aws_lambda_powertools/event_handler/async_execution/routes/code_deploy_lifecycle_hook.py b/aws_lambda_powertools/event_handler/async_execution/routes/code_deploy_lifecycle_hook.py new file mode 100644 index 00000000000..1148b601f89 --- /dev/null +++ b/aws_lambda_powertools/event_handler/async_execution/routes/code_deploy_lifecycle_hook.py @@ -0,0 +1,22 @@ +from __future__ import annotations + +from typing import Any, Callable + +from aws_lambda_powertools.utilities.data_classes.code_deploy_lifecycle_hook_event import ( + CodeDeployLifecycleHookEvent, +) + +from .base import BaseRoute + + +class CodeDeployLifecycleHookRoute(BaseRoute): + def __init__(self, func: Callable): + self.func = func + + def match(self, event: dict[str, Any]) -> tuple[Callable, CodeDeployLifecycleHookEvent] | None: + if not isinstance(event, dict): + return None + elif "DeploymentId" in event and "LifecycleEventHookExecutionId" in event: + return self.func, CodeDeployLifecycleHookEvent(event) + else: + return None diff --git a/aws_lambda_powertools/event_handler/async_execution/routes/event_bridge.py b/aws_lambda_powertools/event_handler/async_execution/routes/event_bridge.py new file mode 100644 index 00000000000..9c82c1780c5 --- /dev/null +++ b/aws_lambda_powertools/event_handler/async_execution/routes/event_bridge.py @@ -0,0 +1,102 @@ +from __future__ import annotations + +from typing import Any, Callable + +from aws_lambda_powertools.utilities.data_classes.event_bridge_event import ( + EventBridgeEvent, +) + +from .base import BaseRoute + + +class EventBridgeRoute(BaseRoute): + detail_type: str | None + source: str | None + resources: list[str] | None + + def __init__( + self, + func: Callable, + detail_type: str | None = None, + source: str | None = None, + resources: str | list[str] | None = None, + ): + self.func = func + self.detail_type = detail_type + self.source = source + + if isinstance(resources, str): + self.resources = [resources] + else: + self.resources = resources + + if not self.detail_type and not self.source and not self.resources: + raise ValueError("detail_type, source, or resources must be not null") + + def is_target_with_detail_type(self, detail_type: str | None) -> bool: + if detail_type and self.detail_type: + return self.detail_type == detail_type + else: + return False + + def is_target_with_source(self, source: str | None) -> bool: + if source and self.source: + return self.source == source + else: + return False + + def is_target_with_resources(self, resources: list[str] | None) -> bool: + if resources and self.resources: + for item in self.resources: + if item in resources: + return True + + return False + + def match(self, event: dict[str, Any]) -> tuple[Callable, EventBridgeEvent] | None: + if not isinstance(event, dict): + return None + + detail_type: str | None = event.get("detail-type") + source: str | None = event.get("source") + resources: list[str] | None = event.get("resources") + + if not detail_type and not source and not resources: + return None + + if not self.detail_type: + detail_type = None + + if not self.source: + source = None + + if not self.resources: + resources = None + + flag_detail_type = self.is_target_with_detail_type(detail_type=detail_type) + flag_source = self.is_target_with_source(source=source) + flag_resources = self.is_target_with_resources(resources=resources) + + text = ", ".join( + [ + "detail_type: x" if detail_type is None else "detail_type: o", + "source: x" if source is None else "source: o", + "resources: x" if resources is None else "resources: o", + ], + ) + + mapping = { + "detail_type: o, source: o, resources: o": flag_detail_type and flag_source and flag_resources, + "detail_type: o, source: o, resources: x": flag_detail_type and flag_source, + "detail_type: o, source: x, resources: o": flag_detail_type and flag_resources, + "detail_type: x, source: o, resources: o": flag_source and flag_resources, + "detail_type: o, source: x, resources: x": flag_detail_type, + "detail_type: x, source: o, resources: x": flag_source, + "detail_type: x, source: x, resources: o": flag_resources, + "detail_type: x, source: x, resources: x": False, + } + + if mapping[text]: + return self.func, EventBridgeEvent(event) + else: + return None diff --git a/aws_lambda_powertools/event_handler/async_execution/routes/s3.py b/aws_lambda_powertools/event_handler/async_execution/routes/s3.py new file mode 100644 index 00000000000..61fed9e3bad --- /dev/null +++ b/aws_lambda_powertools/event_handler/async_execution/routes/s3.py @@ -0,0 +1,197 @@ +from __future__ import annotations + +from typing import Any, Callable + +from aws_lambda_powertools.utilities.data_classes.s3_event import S3Event + +from .base import BaseRoute + + +class S3Route(BaseRoute): + bucket: str | None + bucket_prefix: str | None + key: str | None + key_prefix: str | None + key_suffix: str | None + event_name: str | None + event_name_prefix: str | None + configuration_id: str | None + configuration_id_prefix: str | None + + def __init__( + self, + func: Callable, + bucket: str | None = None, + bucket_prefix: str | None = None, + key: str | None = None, + key_prefix: str | None = None, + key_suffix: str | None = None, + event_name: str | None = None, + event_name_prefix: str | None = None, + configuration_id: str | None = None, + configuration_id_prefix: str | None = None, + ): + self.func = func + self.bucket = bucket + self.bucket_prefix = bucket_prefix + self.key = key + self.key_prefix = key_prefix + self.key_suffix = key_suffix + self.event_name = event_name + self.event_name_prefix = event_name_prefix + self.configuration_id = configuration_id + self.configuration_id_prefix = configuration_id_prefix + + if ( + not self.bucket + and not self.bucket_prefix + and not self.key + and not self.key_prefix + and not self.key_suffix + and not self.event_name + and not self.event_name_prefix + and not self.configuration_id + and not self.configuration_id_prefix + ): + raise ValueError( + ( + "bucket, bucket_prefix, key, key_prefix, key_suffix, event_name, event_name_prefix, " + "configuration_id, or configuration_id_prefix must be not null" + ), + ) + + def is_target_with_bucket(self, bucket: str | None) -> bool: + if not bucket: + return False + elif self.bucket and self.bucket == bucket: + return True + elif self.bucket_prefix and bucket.find(self.bucket_prefix) == 0: + return True + else: + return False + + def is_target_with_key(self, key: str | None) -> bool: + if not key: + return False + elif self.key: + return self.key == key + elif self.key_prefix and not self.key_suffix: + return key.find(self.key_prefix) == 0 + elif not self.key_prefix and self.key_suffix: + length_suffix = len(self.key_suffix) + suffix = key[-length_suffix:] + return self.key_suffix == suffix + elif self.key_prefix and self.key_suffix: + length_suffix = len(self.key_suffix) + suffix = key[-length_suffix:] + return key.find(self.key_prefix) == 0 and self.key_suffix == suffix + else: + return False + + def is_target_with_event_name(self, event_name: str | None) -> bool: + if not event_name: + return False + elif self.event_name: + return self.event_name == event_name + elif self.event_name_prefix: + return event_name.find(self.event_name_prefix) == 0 + else: + return False + + def is_target_with_configuration_id(self, configuration_id: str | None) -> bool: + if not configuration_id: + return False + elif self.configuration_id: + return self.configuration_id == configuration_id + elif self.configuration_id_prefix: + return configuration_id.find(self.configuration_id_prefix) == 0 + else: + return False + + def is_target( + self, + bucket: str | None, + key: str | None, + event_name: str | None, + configuration_id: str | None, + ) -> bool: + flag_bucket = self.is_target_with_bucket(bucket=bucket) + flag_key = self.is_target_with_key(key=key) + flag_event_name = self.is_target_with_event_name(event_name=event_name) + flag_configuration_id = self.is_target_with_configuration_id(configuration_id=configuration_id) + + text = ", ".join( + [ + "bucket: x" if bucket is None else "bucket: o", + "key: x" if key is None else "key: o", + "event_name: x" if event_name is None else "event_name: o", + "configuration_id: x" if configuration_id is None else "configuration_id: o", + ], + ) + + mapping = { + "bucket: o, key: o, event_name: o, configuration_id: o": flag_bucket + and flag_key + and flag_event_name + and flag_configuration_id, + "bucket: o, key: o, event_name: o, configuration_id: x": flag_bucket and flag_key and flag_event_name, + "bucket: o, key: o, event_name: x, configuration_id: o": flag_bucket and flag_key and flag_configuration_id, + "bucket: o, key: x, event_name: o, configuration_id: o": flag_bucket + and flag_event_name + and flag_configuration_id, + "bucket: x, key: o, event_name: o, configuration_id: o": flag_key + and flag_event_name + and flag_configuration_id, + "bucket: o, key: o, event_name: x, configuration_id: x": flag_bucket and flag_key, + "bucket: o, key: x, event_name: o, configuration_id: x": flag_bucket and flag_event_name, + "bucket: x, key: o, event_name: o, configuration_id: x": flag_key and flag_event_name, + "bucket: o, key: x, event_name: x, configuration_id: o": flag_bucket and flag_configuration_id, + "bucket: x, key: o, event_name: x, configuration_id: o": flag_key and flag_configuration_id, + "bucket: x, key: x, event_name: o, configuration_id: o": flag_event_name and flag_configuration_id, + "bucket: o, key: x, event_name: x, configuration_id: x": flag_bucket, + "bucket: x, key: o, event_name: x, configuration_id: x": flag_key, + "bucket: x, key: x, event_name: o, configuration_id: x": flag_event_name, + "bucket: x, key: x, event_name: x, configuration_id: o": flag_configuration_id, + "bucket: x, key: x, event_name: x, configuration_id: x": False, + } + + return mapping[text] + + def match(self, event: dict[str, Any]) -> tuple[Callable, S3Event] | None: + if not isinstance(event, dict): + return None + + all_records: list[dict[str, Any]] = event.get("Records", []) + + if len(all_records) == 0: + return None + + record = all_records[0] + event_name = record.get("eventName") + s3_data = record.get("s3") + if not event_name or not s3_data: + return None + + bucket: str | None = s3_data.get("bucket", {}).get("name") + key: str | None = s3_data.get("object", {}).get("key") + configuration_id: str | None = s3_data.get("configurationId") + + if not bucket and not key and not configuration_id: + return None + + if not self.bucket and not self.bucket_prefix: + bucket = None + + if not self.key and not self.key_prefix and not self.key_suffix: + key = None + + if not self.event_name and not self.event_name_prefix: + event_name = None + + if not self.configuration_id and not self.configuration_id_prefix: + configuration_id = None + + if self.is_target(bucket, key, event_name, configuration_id): + return self.func, S3Event(event) + else: + return None diff --git a/aws_lambda_powertools/event_handler/async_execution/routes/secrets_manager.py b/aws_lambda_powertools/event_handler/async_execution/routes/secrets_manager.py new file mode 100644 index 00000000000..d8eaa06e261 --- /dev/null +++ b/aws_lambda_powertools/event_handler/async_execution/routes/secrets_manager.py @@ -0,0 +1,40 @@ +from __future__ import annotations + +from typing import Any, Callable + +from aws_lambda_powertools.utilities.data_classes.secrets_manager_event import ( + SecretsManagerEvent, +) + +from .base import BaseRoute + + +class SecretsManagerRoute(BaseRoute): + secret_id: str | None + secret_name_prefix: str | None + + def __init__(self, func: Callable, secret_id: str | None = None, secret_name_prefix: str | None = None): + self.func = func + self.secret_id = secret_id + self.secret_name_prefix = secret_name_prefix + + if not self.secret_id and not self.secret_name_prefix: + raise ValueError("secret_id, or secret_name_prefix must be not null") + + def match(self, event: dict[str, Any]) -> tuple[Callable, SecretsManagerEvent] | None: + if not isinstance(event, dict): + return None + + secret_id: str | None = event.get("SecretId") + + if not secret_id: + return None + elif self.secret_id and self.secret_id == secret_id: + return self.func, SecretsManagerEvent(event) + elif self.secret_name_prefix: + part = secret_id.split(":") + secret_name = part[-1] + if secret_name.find(self.secret_name_prefix) == 0: + return self.func, SecretsManagerEvent(event) + + return None diff --git a/aws_lambda_powertools/event_handler/async_execution/routes/ses.py b/aws_lambda_powertools/event_handler/async_execution/routes/ses.py new file mode 100644 index 00000000000..ba367ff8723 --- /dev/null +++ b/aws_lambda_powertools/event_handler/async_execution/routes/ses.py @@ -0,0 +1,207 @@ +from __future__ import annotations + +from typing import Any, Callable + +from aws_lambda_powertools.utilities.data_classes.ses_event import SESEvent + +from .base import BaseRoute + + +def match_prefix(all_address: list[str], all_prefix: list[str]) -> bool: + for address in all_address: + for prefix in all_prefix: + if address.find(prefix) == 0: + return True + return False + + +def match_suffix(all_address: list[str], all_suffix: list[str]) -> bool: + for address in all_address: + for suffix in all_suffix: + length_suffix = len(suffix) + address_suffix = address[-length_suffix:] + if address_suffix == suffix: + return True + return False + + +def match_prefix_and_suffix(all_address: list[str], all_prefix: list[str], all_suffix: list[str]) -> bool: + for address in all_address: + for prefix in all_prefix: + for suffix in all_suffix: + length_suffix = len(suffix) + address_suffix = address[-length_suffix:] + if address.find(prefix) == 0 and address_suffix == suffix: + return True + return False + + +class SESRoute(BaseRoute): + mail_to: list[str] | None + mail_to_prefix: list[str] | None + mail_to_suffix: list[str] | None + mail_from: list[str] | None + mail_from_prefix: list[str] | None + mail_from_suffix: list[str] | None + mail_subject: str | None + mail_subject_prefix: str | None + + def __init__( + self, + func: Callable, + mail_to: str | list[str] | None = None, + mail_to_prefix: str | list[str] | None = None, + mail_to_suffix: str | list[str] | None = None, + mail_from: str | list[str] | None = None, + mail_from_prefix: str | list[str] | None = None, + mail_from_suffix: str | list[str] | None = None, + mail_subject: str | None = None, + mail_subject_prefix: str | None = None, + ): + self.func = func + + if isinstance(mail_to, str): + self.mail_to = [mail_to] + else: + self.mail_to = mail_to + + if isinstance(mail_to_prefix, str): + self.mail_to_prefix = [mail_to_prefix] + else: + self.mail_to_prefix = mail_to_prefix + + if isinstance(mail_to_suffix, str): + self.mail_to_suffix = [mail_to_suffix] + else: + self.mail_to_suffix = mail_to_suffix + + if isinstance(mail_from, str): + self.mail_from = [mail_from] + else: + self.mail_from = mail_from + + if isinstance(mail_from_prefix, str): + self.mail_from_prefix = [mail_from_prefix] + else: + self.mail_from_prefix = mail_from_prefix + + if isinstance(mail_from_suffix, str): + self.mail_from_suffix = [mail_from_suffix] + else: + self.mail_from_suffix = mail_from_suffix + + self.mail_subject = mail_subject + self.mail_subject_prefix = mail_subject_prefix + + if not self.mail_to and not self.mail_from and not self.mail_subject: + raise ValueError( + ( + "mail_to, mail_to_prefix, mail_to_suffix, mail_from, mail_from_prefix, mail_from_suffix, " + "mail_subject, or mail_subject_prefix must be not null" + ), + ) + + def is_target_with_mail_to(self, mail_to: list[str] | None) -> bool: + if not mail_to: + return False + elif self.mail_to: + for address in mail_to: + if address in self.mail_to: + return True + elif self.mail_to_prefix and self.mail_to_suffix: + return match_prefix_and_suffix( + all_address=mail_to, + all_prefix=self.mail_to_prefix, + all_suffix=self.mail_to_suffix, + ) + elif self.mail_to_prefix: + return match_prefix(all_address=mail_to, all_prefix=self.mail_to_prefix) + elif self.mail_to_suffix: + return match_suffix(all_address=mail_to, all_suffix=self.mail_to_suffix) + + return False + + def is_target_with_mail_from(self, mail_from: list[str] | None) -> bool: + if not mail_from: + return False + elif self.mail_from: + for address in mail_from: + if address in self.mail_from: + return True + elif self.mail_from_prefix and self.mail_from_suffix: + return match_prefix_and_suffix( + all_address=mail_from, + all_prefix=self.mail_from_prefix, + all_suffix=self.mail_from_suffix, + ) + elif self.mail_from_prefix: + return match_prefix(all_address=mail_from, all_prefix=self.mail_from_prefix) + elif self.mail_from_suffix: + return match_suffix(all_address=mail_from, all_suffix=self.mail_from_suffix) + + return False + + def is_target_with_mail_subject(self, mail_subject: str | None) -> bool: + if not mail_subject: + return False + elif self.mail_subject: + return self.mail_subject == mail_subject + elif self.mail_subject_prefix: + return mail_subject.find(self.mail_subject_prefix) == 0 + else: + return False + + def match(self, event: dict[str, Any]) -> tuple[Callable, SESEvent] | None: + if not isinstance(event, dict): + return None + + all_records: list[dict[str, Any]] = event.get("Records", []) + + if len(all_records) == 0: + return None + + common_header: dict[str, Any] | None = all_records[0].get("ses", {}).get("mail", {}).get("commonHeaders") + + if common_header is None: + return None + + mail_to = common_header.get("to") + mail_from = common_header.get("from") + mail_subject = common_header.get("subject") + + if not self.mail_to and not self.mail_to_prefix and not self.mail_to_suffix: + mail_to = None + + if not self.mail_from and not self.mail_from_prefix and not self.mail_from_suffix: + mail_from = None + + if not self.mail_subject and not self.mail_subject_prefix: + mail_subject = None + + flag_mail_to = self.is_target_with_mail_to(mail_to=mail_to) + flag_mail_from = self.is_target_with_mail_from(mail_from=mail_from) + flag_mail_subject = self.is_target_with_mail_subject(mail_subject=mail_subject) + + text = ", ".join( + [ + "mail_to: x" if mail_to is None else "mail_to: o", + "mail_from: x" if mail_from is None else "mail_from: o", + "mail_subject: x" if mail_subject is None else "mail_subject: o", + ], + ) + + mapping = { + "mail_to: o, mail_from: o, mail_subject: o": flag_mail_to and flag_mail_from and flag_mail_subject, + "mail_to: o, mail_from: o, mail_subject: x": flag_mail_to and flag_mail_from, + "mail_to: o, mail_from: x, mail_subject: o": flag_mail_to and flag_mail_subject, + "mail_to: x, mail_from: o, mail_subject: o": flag_mail_from and flag_mail_subject, + "mail_to: o, mail_from: x, mail_subject: x": flag_mail_to, + "mail_to: x, mail_from: o, mail_subject: x": flag_mail_from, + "mail_to: x, mail_from: x, mail_subject: o": flag_mail_subject, + "mail_to: x, mail_from: x, mail_subject: x": False, + } + + if mapping[text]: + return self.func, SESEvent(event) + else: + return None diff --git a/aws_lambda_powertools/event_handler/async_execution/routes/sns.py b/aws_lambda_powertools/event_handler/async_execution/routes/sns.py new file mode 100644 index 00000000000..f0c15baed0a --- /dev/null +++ b/aws_lambda_powertools/event_handler/async_execution/routes/sns.py @@ -0,0 +1,109 @@ +from __future__ import annotations + +from typing import Any, Callable + +from aws_lambda_powertools.utilities.data_classes.sns_event import SNSEvent + +from .base import BaseRoute + + +class SNSRoute(BaseRoute): + arn: str | None + name: str | None + name_prefix: str | None + subject: str | None + subject_prefix: str | None + + def __init__( + self, + func: Callable, + arn: str | None = None, + name: str | None = None, + name_prefix: str | None = None, + subject: str | None = None, + subject_prefix: str | None = None, + ): + self.func = func + self.arn = arn + self.name = name + self.name_prefix = name_prefix + self.subject = subject + self.subject_prefix = subject_prefix + + if not self.arn and not self.name and not self.name_prefix and not self.subject and not self.subject_prefix: + raise ValueError("arn, name, name_prefix, subject, or subject_prefix must be not null") + + def is_target_with_arn(self, arn: str | None) -> bool: + if not arn: + return False + elif self.arn: + return self.arn == arn + + part = arn.split(":") + name = part[-1] + if self.name: + return self.name == name + elif self.name_prefix: + return name.find(self.name_prefix) == 0 + else: + return False + + def is_target_with_subject(self, subject: str | None) -> bool: + if not subject: + return False + elif self.subject: + return self.subject == subject + elif self.subject_prefix: + return subject.find(self.subject_prefix) == 0 + else: + return False + + def is_target(self, arn: str | None, subject: str | None) -> bool: + if arn and subject: + return self.is_target_with_arn(arn) and self.is_target_with_subject(subject) + elif arn and not subject: + return self.is_target_with_arn(arn) + elif not arn and subject: + return self.is_target_with_subject(subject) + else: + return False + + def match(self, event: dict[str, Any]) -> tuple[Callable, SNSEvent] | None: + if not isinstance(event, dict): + return None + + all_records: list[dict[str, Any]] = event.get("Records", []) + + if len(all_records) == 0: + return None + + sns_data: dict[str, Any] | None = all_records[0].get("Sns") + if not sns_data: + return None + + if self.arn or self.name or self.name_prefix: + arn = sns_data.get("TopicArn") + else: + arn = None + + if self.subject or self.subject_prefix: + subject = sns_data.get("Subject") + else: + subject = None + + flag_arn = self.is_target_with_arn(arn=arn) + flag_subject = self.is_target_with_subject(subject=subject) + + text = ", ".join(["arn: x" if arn is None else "arn: o", "subject: x" if subject is None else "subject: o"]) + + mapping = { + "arn: o, subject: o": flag_arn and flag_subject, + "arn: o, subject: x": flag_arn, + "arn: x, subject: o": flag_subject, + "arn: x, subject: x": False, + } + + if mapping[text]: + return self.func, SNSEvent(event) + else: + return None diff --git a/aws_lambda_powertools/event_handler/async_trigger.py b/aws_lambda_powertools/event_handler/async_trigger.py new file mode 100644 index 00000000000..60fd00f59e8 --- /dev/null +++ b/aws_lambda_powertools/event_handler/async_trigger.py @@ -0,0 +1,37 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Any + +from .async_execution.exceptions import RouteNotFoundError +from .async_execution.router import Router + +if TYPE_CHECKING: + from aws_lambda_powertools.utilities.typing import LambdaContext + + +class AsyncTriggerResolver(Router): + current_event: Any + lambda_context: LambdaContext | None + raise_when_not_found: bool + + def __init__(self, raise_when_not_found: bool = True): + super().__init__() + self.current_event = None + self.lambda_context = None + self.raise_when_not_found = raise_when_not_found + + def resolve(self, event: dict[str, Any], context: LambdaContext): + data = self.resolve_route(event=event) + if data is None: + if self.raise_when_not_found: + raise RouteNotFoundError() + else: + return + func, current_event = data + try: + self.current_event = current_event + self.lambda_context = context + return func() + finally: + self.current_event = None + self.lambda_context = None diff --git a/tests/unit/event_handler/_async_execution/__init__.py b/tests/unit/event_handler/_async_execution/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/unit/event_handler/_async_execution/_routes/__init__.py b/tests/unit/event_handler/_async_execution/_routes/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/unit/event_handler/_async_execution/_routes/test_aws_config_rule.py b/tests/unit/event_handler/_async_execution/_routes/test_aws_config_rule.py new file mode 100644 index 00000000000..2f3a1875152 --- /dev/null +++ b/tests/unit/event_handler/_async_execution/_routes/test_aws_config_rule.py @@ -0,0 +1,1471 @@ +import pytest + +from aws_lambda_powertools.event_handler.async_execution.router import ( + AwsConfigRuleRoute, +) +from aws_lambda_powertools.utilities.data_classes.aws_config_rule_event import ( + AWSConfigRuleEvent, +) +from tests.functional.utils import load_event + + +class TestAwsConfigRuleRoute: + def test_constructor_error(self): + with pytest.raises(ValueError): + AwsConfigRuleRoute(func=lambda _: None) + + @pytest.mark.parametrize( + "option_constructor, option_func, expected", + [ + ({"func": None, "arn": "test-arn"}, {"arn": None}, False), + ({"func": None, "arn": "test-arn"}, {"arn": "test-arn"}, True), + ({"func": None, "arn": "test-arn-v2"}, {"arn": "test-arn"}, False), + ({"func": None, "rule_name": "test-rule"}, {"arn": "test-arn"}, False), + ], + ) + def test_is_target_with_arn(self, option_constructor, option_func, expected): + route = AwsConfigRuleRoute(**option_constructor) + actual = route.is_target_with_arn(**option_func) + assert actual == expected + + @pytest.mark.parametrize( + "option_constructor, option_func, expected", + [ + ({"func": None, "rule_name": "test-rule"}, {"rule_name": None}, False), + ({"func": None, "rule_name": "test-rule"}, {"rule_name": "test-rule"}, True), + ({"func": None, "rule_name": "test-rule-v2"}, {"rule_name": "test-rule"}, False), + ({"func": None, "rule_name_prefix": "test-r"}, {"rule_name": "test-rule"}, True), + ({"func": None, "rule_name_prefix": "test-rr"}, {"rule_name": "test-rule"}, False), + ({"func": None, "rule_name": "test-rule", "rule_name_prefix": "test-r"}, {"rule_name": "test-rule"}, True), + ({"func": None, "rule_name": "test-rule", "rule_name_prefix": "test-rr"}, {"rule_name": "test-rule"}, True), + ( + {"func": None, "rule_name": "test-rule-v2", "rule_name_prefix": "test-r"}, + {"rule_name": "test-rule"}, + False, + ), + ( + {"func": None, "rule_name": "test-rule-v2", "rule_name_prefix": "test-r"}, + {"rule_name": "test-rule"}, + False, + ), + ({"func": None, "arn": "test-arn"}, {"rule_name": "test-rule"}, False), + ], + ) + def test_is_target_with_rule_name(self, option_constructor, option_func, expected): + route = AwsConfigRuleRoute(**option_constructor) + actual = route.is_target_with_rule_name(**option_func) + assert actual == expected + + @pytest.mark.parametrize( + "option_constructor, option_func, expected", + [ + ({"func": None, "rule_id": "test-id"}, {"rule_id": None}, False), + ({"func": None, "rule_id": "test-id"}, {"rule_id": "test-id"}, True), + ({"func": None, "rule_id": "test-id-v2"}, {"rule_id": "test-id"}, False), + ({"func": None, "arn": "test-arn"}, {"rule_id": "test-id"}, False), + ], + ) + def test_is_target_with_rule_id(self, option_constructor, option_func, expected): + route = AwsConfigRuleRoute(**option_constructor) + actual = route.is_target_with_rule_id(**option_func) + assert actual == expected + + @pytest.mark.parametrize( + "event_name, option_constructor, expected_true", + [ + # awsConfigRuleConfigurationChanged.json + # with arn, rule_name, rule_name_prefix, and rule_id + # match all + ( + "awsConfigRuleConfigurationChanged.json", + { + "func": lambda *_: None, + "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9", + "rule_name": "MyRule", + "rule_name_prefix": "MyR", + "rule_id": "config-rule-i9y8j9", + }, + True, + ), + # awsConfigRuleConfigurationChanged.json + # with arn, rule_name, rule_name_prefix, and rule_id + # match 3, unmatch 1 + ( + "awsConfigRuleConfigurationChanged.json", + { + "func": lambda *_: None, + "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9", + "rule_name": "MyRule", + "rule_name_prefix": "MyR", + "rule_id": "config-rule-000000", + }, + False, + ), + ( + "awsConfigRuleConfigurationChanged.json", + { + "func": lambda *_: None, + "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9", + "rule_name": "MyRule", + "rule_name_prefix": "MyRR", + "rule_id": "config-rule-i9y8j9", + }, + True, + ), + ( + "awsConfigRuleConfigurationChanged.json", + { + "func": lambda *_: None, + "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9", + "rule_name": "MyRuleV2", + "rule_name_prefix": "MyR", + "rule_id": "config-rule-i9y8j9", + }, + False, + ), + ( + "awsConfigRuleConfigurationChanged.json", + { + "func": lambda *_: None, + "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-000000", + "rule_name": "MyRule", + "rule_name_prefix": "MyR", + "rule_id": "config-rule-i9y8j9", + }, + False, + ), + # awsConfigRuleConfigurationChanged.json + # with arn, rule_name, rule_name_prefix, and rule_id + # match 2, unmatch 2 + ( + "awsConfigRuleConfigurationChanged.json", + { + "func": lambda *_: None, + "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9", + "rule_name": "MyRule", + "rule_name_prefix": "MyRR", + "rule_id": "config-rule-000000", + }, + False, + ), + ( + "awsConfigRuleConfigurationChanged.json", + { + "func": lambda *_: None, + "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9", + "rule_name": "MyRuleV2", + "rule_name_prefix": "MyR", + "rule_id": "config-rule-000000", + }, + False, + ), + ( + "awsConfigRuleConfigurationChanged.json", + { + "func": lambda *_: None, + "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-000000", + "rule_name": "MyRule", + "rule_name_prefix": "MyR", + "rule_id": "config-rule-000000", + }, + False, + ), + ( + "awsConfigRuleConfigurationChanged.json", + { + "func": lambda *_: None, + "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9", + "rule_name": "MyRuleV2", + "rule_name_prefix": "MyRR", + "rule_id": "config-rule-i9y8j9", + }, + False, + ), + ( + "awsConfigRuleConfigurationChanged.json", + { + "func": lambda *_: None, + "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-000000", + "rule_name": "MyRule", + "rule_name_prefix": "MyRR", + "rule_id": "config-rule-i9y8j9", + }, + False, + ), + ( + "awsConfigRuleConfigurationChanged.json", + { + "func": lambda *_: None, + "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-000000", + "rule_name": "MyRuleV2", + "rule_name_prefix": "MyR", + "rule_id": "config-rule-i9y8j9", + }, + False, + ), + # awsConfigRuleConfigurationChanged.json + # with arn, rule_name, rule_name_prefix, and rule_id + # match 1, unmatch 3 + ( + "awsConfigRuleConfigurationChanged.json", + { + "func": lambda *_: None, + "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9", + "rule_name": "MyRuleV2", + "rule_name_prefix": "MyRR", + "rule_id": "config-rule-000000", + }, + False, + ), + ( + "awsConfigRuleConfigurationChanged.json", + { + "func": lambda *_: None, + "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-000000", + "rule_name": "MyRule", + "rule_name_prefix": "MyRR", + "rule_id": "config-rule-000000", + }, + False, + ), + ( + "awsConfigRuleConfigurationChanged.json", + { + "func": lambda *_: None, + "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-000000", + "rule_name": "MyRuleV2", + "rule_name_prefix": "MyR", + "rule_id": "config-rule-000000", + }, + False, + ), + ( + "awsConfigRuleConfigurationChanged.json", + { + "func": lambda *_: None, + "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-000000", + "rule_name": "MyRuleV2", + "rule_name_prefix": "MyRR", + "rule_id": "config-rule-i9y8j9", + }, + False, + ), + # awsConfigRuleConfigurationChanged.json + # with arn, rule_name, rule_name_prefix, and rule_id + # unmatch all + ( + "awsConfigRuleConfigurationChanged.json", + { + "func": lambda *_: None, + "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-000000", + "rule_name": "MyRuleV2", + "rule_name_prefix": "MyRR", + "rule_id": "config-rule-000000", + }, + False, + ), + # awsConfigRuleConfigurationChanged.json + # with arn, rule_name, and rule_name_prefix + # match all + ( + "awsConfigRuleConfigurationChanged.json", + { + "func": lambda *_: None, + "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9", + "rule_name": "MyRule", + "rule_name_prefix": "MyR", + }, + True, + ), + # awsConfigRuleConfigurationChanged.json + # with arn, rule_name, and rule_name_prefix + # match 2, unmatch 1 + ( + "awsConfigRuleConfigurationChanged.json", + { + "func": lambda *_: None, + "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9", + "rule_name": "MyRule", + "rule_name_prefix": "MyRR", + }, + True, + ), + ( + "awsConfigRuleConfigurationChanged.json", + { + "func": lambda *_: None, + "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9", + "rule_name": "MyRuleV2", + "rule_name_prefix": "MyR", + }, + False, + ), + ( + "awsConfigRuleConfigurationChanged.json", + { + "func": lambda *_: None, + "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-000000", + "rule_name": "MyRule", + "rule_name_prefix": "MyR", + }, + False, + ), + # awsConfigRuleConfigurationChanged.json + # with arn, rule_name, and rule_name_prefix + # match 1, unmatch 2 + ( + "awsConfigRuleConfigurationChanged.json", + { + "func": lambda *_: None, + "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9", + "rule_name": "MyRuleV2", + "rule_name_prefix": "MyRR", + }, + False, + ), + ( + "awsConfigRuleConfigurationChanged.json", + { + "func": lambda *_: None, + "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-000000", + "rule_name": "MyRule", + "rule_name_prefix": "MyRR", + }, + False, + ), + ( + "awsConfigRuleConfigurationChanged.json", + { + "func": lambda *_: None, + "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-000000", + "rule_name": "MyRuleV2", + "rule_name_prefix": "MyR", + }, + False, + ), + # awsConfigRuleConfigurationChanged.json + # with arn, rule_name, and rule_name_prefix + # unmatch all + ( + "awsConfigRuleConfigurationChanged.json", + { + "func": lambda *_: None, + "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-000000", + "rule_name": "MyRuleV2", + "rule_name_prefix": "MyRR", + }, + False, + ), + # awsConfigRuleConfigurationChanged.json + # with arn, rule_name, and rule_id + # match all + ( + "awsConfigRuleConfigurationChanged.json", + { + "func": lambda *_: None, + "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9", + "rule_name": "MyRule", + "rule_id": "config-rule-i9y8j9", + }, + True, + ), + # awsConfigRuleConfigurationChanged.json + # with arn, rule_name, and rule_id + # match 2, unmatch 1 + ( + "awsConfigRuleConfigurationChanged.json", + { + "func": lambda *_: None, + "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9", + "rule_name": "MyRule", + "rule_id": "config-rule-000000", + }, + False, + ), + ( + "awsConfigRuleConfigurationChanged.json", + { + "func": lambda *_: None, + "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9", + "rule_name": "MyRuleV2", + "rule_id": "config-rule-i9y8j9", + }, + False, + ), + ( + "awsConfigRuleConfigurationChanged.json", + { + "func": lambda *_: None, + "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-000000", + "rule_name": "MyRule", + "rule_id": "config-rule-i9y8j9", + }, + False, + ), + # awsConfigRuleConfigurationChanged.json + # with arn, rule_name, and rule_id + # match 1, unmatch 2 + ( + "awsConfigRuleConfigurationChanged.json", + { + "func": lambda *_: None, + "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9", + "rule_name": "MyRuleV2", + "rule_id": "config-rule-000000", + }, + False, + ), + ( + "awsConfigRuleConfigurationChanged.json", + { + "func": lambda *_: None, + "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-000000", + "rule_name": "MyRule", + "rule_id": "config-rule-000000", + }, + False, + ), + ( + "awsConfigRuleConfigurationChanged.json", + { + "func": lambda *_: None, + "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-000000", + "rule_name": "MyRuleV2", + "rule_id": "config-rule-i9y8j9", + }, + False, + ), + # awsConfigRuleConfigurationChanged.json + # with arn, rule_name, and rule_id + # unmatch all + ( + "awsConfigRuleConfigurationChanged.json", + { + "func": lambda *_: None, + "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-000000", + "rule_name": "MyRuleV2", + "rule_id": "config-rule-000000", + }, + False, + ), + # awsConfigRuleConfigurationChanged.json + # with arn, rule_name_prefix, and rule_id + # match all + ( + "awsConfigRuleConfigurationChanged.json", + { + "func": lambda *_: None, + "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9", + "rule_name_prefix": "MyR", + "rule_id": "config-rule-i9y8j9", + }, + True, + ), + # awsConfigRuleConfigurationChanged.json + # with arn, rule_name_prefix, and rule_id + # match 2, unmatch 1 + ( + "awsConfigRuleConfigurationChanged.json", + { + "func": lambda *_: None, + "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9", + "rule_name_prefix": "MyR", + "rule_id": "config-rule-000000", + }, + False, + ), + ( + "awsConfigRuleConfigurationChanged.json", + { + "func": lambda *_: None, + "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9", + "rule_name_prefix": "MyRR", + "rule_id": "config-rule-i9y8j9", + }, + False, + ), + ( + "awsConfigRuleConfigurationChanged.json", + { + "func": lambda *_: None, + "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9", + "rule_name_prefix": "MyR", + "rule_id": "config-rule-000000", + }, + False, + ), + # awsConfigRuleConfigurationChanged.json + # with arn, rule_name_prefix, and rule_id + # match 1, unmatch 2 + ( + "awsConfigRuleConfigurationChanged.json", + { + "func": lambda *_: None, + "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9", + "rule_name_prefix": "MyRR", + "rule_id": "config-rule-000000", + }, + False, + ), + ( + "awsConfigRuleConfigurationChanged.json", + { + "func": lambda *_: None, + "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-000000", + "rule_name_prefix": "MyR", + "rule_id": "config-rule-000000", + }, + False, + ), + ( + "awsConfigRuleConfigurationChanged.json", + { + "func": lambda *_: None, + "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-000000", + "rule_name_prefix": "MyRR", + "rule_id": "config-rule-i9y8j9", + }, + False, + ), + # awsConfigRuleConfigurationChanged.json + # with arn, rule_name_prefix, and rule_id + # unmatch all + ( + "awsConfigRuleConfigurationChanged.json", + { + "func": lambda *_: None, + "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-000000", + "rule_name_prefix": "MyRR", + "rule_id": "config-rule-000000", + }, + False, + ), + # awsConfigRuleConfigurationChanged.json + # with rule_name, rule_name_prefix, and rule_id + # match all + ( + "awsConfigRuleConfigurationChanged.json", + { + "func": lambda *_: None, + "rule_name": "MyRule", + "rule_name_prefix": "MyR", + "rule_id": "config-rule-i9y8j9", + }, + True, + ), + # awsConfigRuleConfigurationChanged.json + # with rule_name, rule_name_prefix, and rule_id + # match 2, unmatch 1 + ( + "awsConfigRuleConfigurationChanged.json", + { + "func": lambda *_: None, + "rule_name": "MyRule", + "rule_name_prefix": "MyR", + "rule_id": "config-rule-000000", + }, + False, + ), + ( + "awsConfigRuleConfigurationChanged.json", + { + "func": lambda *_: None, + "rule_name": "MyRule", + "rule_name_prefix": "MyRR", + "rule_id": "config-rule-i9y8j9", + }, + True, + ), + ( + "awsConfigRuleConfigurationChanged.json", + { + "func": lambda *_: None, + "rule_name": "MyRuleV2", + "rule_name_prefix": "MyR", + "rule_id": "config-rule-i9y8j9", + }, + False, + ), + # awsConfigRuleConfigurationChanged.json + # with rule_name, rule_name_prefix, and rule_id + # match 1, unmatch 2 + ( + "awsConfigRuleConfigurationChanged.json", + { + "func": lambda *_: None, + "rule_name": "MyRule", + "rule_name_prefix": "MyRR", + "rule_id": "config-rule-000000", + }, + False, + ), + ( + "awsConfigRuleConfigurationChanged.json", + { + "func": lambda *_: None, + "rule_name": "MyRuleV2", + "rule_name_prefix": "MyR", + "rule_id": "config-rule-000000", + }, + False, + ), + ( + "awsConfigRuleConfigurationChanged.json", + { + "func": lambda *_: None, + "rule_name": "MyRuleV2", + "rule_name_prefix": "MyRR", + "rule_id": "config-rule-i9y8j9", + }, + False, + ), + # awsConfigRuleConfigurationChanged.json + # with rule_name, rule_name_prefix, and rule_id + # unmatch all + ( + "awsConfigRuleConfigurationChanged.json", + { + "func": lambda *_: None, + "rule_name": "MyRuleV2", + "rule_name_prefix": "MyRR", + "rule_id": "config-rule-000000", + }, + False, + ), + # awsConfigRuleConfigurationChanged.json + # with arn and rule_name + # match all + ( + "awsConfigRuleConfigurationChanged.json", + { + "func": lambda *_: None, + "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9", + "rule_name": "MyRule", + }, + True, + ), + # awsConfigRuleConfigurationChanged.json + # with arn and rule_name + # match 1, unmatch 1 + ( + "awsConfigRuleConfigurationChanged.json", + { + "func": lambda *_: None, + "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9", + "rule_name": "MyRuleV2", + }, + False, + ), + ( + "awsConfigRuleConfigurationChanged.json", + { + "func": lambda *_: None, + "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-000000", + "rule_name": "MyRule", + }, + False, + ), + # awsConfigRuleConfigurationChanged.json + # with arn and rule_name + # unmatch all + ( + "awsConfigRuleConfigurationChanged.json", + { + "func": lambda *_: None, + "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-000000", + "rule_name": "MyRuleV2", + }, + False, + ), + # awsConfigRuleConfigurationChanged.json + # with arn and rule_name_prefix + # match all + ( + "awsConfigRuleConfigurationChanged.json", + { + "func": lambda *_: None, + "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9", + "rule_name_prefix": "MyR", + }, + True, + ), + # awsConfigRuleConfigurationChanged.json + # with arn and rule_name_prefix + # match 1, unmatch 1 + ( + "awsConfigRuleConfigurationChanged.json", + { + "func": lambda *_: None, + "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9", + "rule_name_prefix": "MyRR", + }, + False, + ), + ( + "awsConfigRuleConfigurationChanged.json", + { + "func": lambda *_: None, + "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-000000", + "rule_name_prefix": "MyR", + }, + False, + ), + # awsConfigRuleConfigurationChanged.json + # with arn and rule_name_prefix + # unmatch all + ( + "awsConfigRuleConfigurationChanged.json", + { + "func": lambda *_: None, + "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-000000", + "rule_name_prefix": "MyRR", + }, + False, + ), + # awsConfigRuleConfigurationChanged.json + # with arn and rule_id + # match all + ( + "awsConfigRuleConfigurationChanged.json", + { + "func": lambda *_: None, + "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9", + "rule_id": "config-rule-i9y8j9", + }, + True, + ), + # awsConfigRuleConfigurationChanged.json + # with arn and rule_id + # match 1, unmatch 1 + ( + "awsConfigRuleConfigurationChanged.json", + { + "func": lambda *_: None, + "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9", + "rule_id": "config-rule-000000", + }, + False, + ), + ( + "awsConfigRuleConfigurationChanged.json", + { + "func": lambda *_: None, + "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-000000", + "rule_id": "config-rule-i9y8j9", + }, + False, + ), + # awsConfigRuleConfigurationChanged.json + # with arn and rule_id + # unmatch all + ( + "awsConfigRuleConfigurationChanged.json", + { + "func": lambda *_: None, + "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-000000", + "rule_id": "config-rule-000000", + }, + False, + ), + # awsConfigRuleConfigurationChanged.json + # with rule_name and rule_name_prefix + # match all + ( + "awsConfigRuleConfigurationChanged.json", + { + "func": lambda *_: None, + "rule_name": "MyRule", + "rule_name_prefix": "MyR", + }, + True, + ), + # awsConfigRuleConfigurationChanged.json + # with rule_name and rule_name_prefix + # match 1, unmatch 1 + ( + "awsConfigRuleConfigurationChanged.json", + { + "func": lambda *_: None, + "rule_name": "MyRule", + "rule_name_prefix": "MyRR", + }, + True, + ), + ( + "awsConfigRuleConfigurationChanged.json", + { + "func": lambda *_: None, + "rule_name": "MyRuleV2", + "rule_name_prefix": "MyR", + }, + False, + ), + # awsConfigRuleConfigurationChanged.json + # with rule_name and rule_name_prefix + # unmatch all + ( + "awsConfigRuleConfigurationChanged.json", + { + "func": lambda *_: None, + "rule_name": "MyRuleV2", + "rule_name_prefix": "MyRR", + }, + False, + ), + # awsConfigRuleConfigurationChanged.json + # with rule_name and rule_id + # match all + ( + "awsConfigRuleConfigurationChanged.json", + {"func": lambda *_: None, "rule_name": "MyRule", "rule_id": "config-rule-i9y8j9"}, + True, + ), + # awsConfigRuleConfigurationChanged.json + # with rule_name and rule_id + # match 1, unmatch 1 + ( + "awsConfigRuleConfigurationChanged.json", + {"func": lambda *_: None, "rule_name": "MyRule", "rule_id": "config-rule-000000"}, + False, + ), + ( + "awsConfigRuleConfigurationChanged.json", + {"func": lambda *_: None, "rule_name": "MyRuleV2", "rule_id": "config-rule-i9y8j9"}, + False, + ), + # awsConfigRuleConfigurationChanged.json + # with rule_name and rule_id + # unmatch all + ( + "awsConfigRuleConfigurationChanged.json", + {"func": lambda *_: None, "rule_name": "MyRuleV2", "rule_id": "config-rule-000000"}, + False, + ), + # awsConfigRuleConfigurationChanged.json + # with rule_name_prefix and rule_id + # match all + ( + "awsConfigRuleConfigurationChanged.json", + {"func": lambda *_: None, "rule_name_prefix": "MyR", "rule_id": "config-rule-i9y8j9"}, + True, + ), + # awsConfigRuleConfigurationChanged.json + # with rule_name_prefix and rule_id + # match 1, unmatch 1 + ( + "awsConfigRuleConfigurationChanged.json", + {"func": lambda *_: None, "rule_name_prefix": "MyR", "rule_id": "config-rule-000000"}, + False, + ), + ( + "awsConfigRuleConfigurationChanged.json", + {"func": lambda *_: None, "rule_name_prefix": "MyRR", "rule_id": "config-rule-i9y8j9"}, + False, + ), + # awsConfigRuleConfigurationChanged.json + # with rule_name_prefix and rule_id + # unmatch all + ( + "awsConfigRuleConfigurationChanged.json", + {"func": lambda *_: None, "rule_name_prefix": "MyRR", "rule_id": "config-rule-000000"}, + False, + ), + # awsConfigRuleConfigurationChanged.json + # with arn + # match all + ( + "awsConfigRuleConfigurationChanged.json", + { + "func": lambda *_: None, + "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9", + }, + True, + ), + # awsConfigRuleConfigurationChanged.json + # with arn + # unmatch all + ( + "awsConfigRuleConfigurationChanged.json", + { + "func": lambda *_: None, + "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-000000", + }, + False, + ), + # awsConfigRuleConfigurationChanged.json + # with rule_name + # match all + ( + "awsConfigRuleConfigurationChanged.json", + { + "func": lambda *_: None, + "rule_name": "MyRule", + }, + True, + ), + # awsConfigRuleConfigurationChanged.json + # with rule_name + # unmatch all + ( + "awsConfigRuleConfigurationChanged.json", + { + "func": lambda *_: None, + "rule_name": "MyRuleV2", + }, + False, + ), + # awsConfigRuleConfigurationChanged.json + # with rule_name_prefix + # match all + ( + "awsConfigRuleConfigurationChanged.json", + { + "func": lambda *_: None, + "rule_name_prefix": "MyR", + }, + True, + ), + # awsConfigRuleConfigurationChanged.json + # with rule_name_prefix + # unmatch all + ( + "awsConfigRuleConfigurationChanged.json", + { + "func": lambda *_: None, + "rule_name_prefix": "MyRR", + }, + False, + ), + # awsConfigRuleConfigurationChanged.json + # with rule_id + # match all + ( + "awsConfigRuleConfigurationChanged.json", + {"func": lambda *_: None, "rule_id": "config-rule-i9y8j9"}, + True, + ), + # awsConfigRuleConfigurationChanged.json + # with rule_id + # unmatch all + ( + "awsConfigRuleConfigurationChanged.json", + {"func": lambda *_: None, "rule_id": "config-rule-000000"}, + False, + ), + # Other events + ( + "activeMQEvent.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "albEvent.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "albEventPathTrailingSlash.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "albMultiValueHeadersEvent.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "albMultiValueQueryStringEvent.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "apiGatewayAuthorizerRequestEvent.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "apiGatewayAuthorizerTokenEvent.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "apiGatewayAuthorizerV2Event.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "apiGatewayProxyEvent.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "apiGatewayProxyEventAnotherPath.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "apiGatewayProxyEventNoOrigin.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "apiGatewayProxyEventPathTrailingSlash.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "apiGatewayProxyEventPrincipalId.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "apiGatewayProxyEvent_noVersionAuth.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "apiGatewayProxyOtherEvent.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "apiGatewayProxyV2Event.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "apiGatewayProxyV2EventPathTrailingSlash.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "apiGatewayProxyV2Event_GET.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "apiGatewayProxyV2IamEvent.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "apiGatewayProxyV2LambdaAuthorizerEvent.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "apiGatewayProxyV2OtherGetEvent.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "apiGatewayProxyV2SchemaMiddlwareInvalidEvent.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "apiGatewayProxyV2SchemaMiddlwareValidEvent.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "apigatewayeSchemaMiddlwareInvalidEvent.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "apigatewayeSchemaMiddlwareValidEvent.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "appSyncAuthorizerEvent.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "appSyncAuthorizerResponse.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "appSyncBatchEvent.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "appSyncDirectResolver.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "appSyncResolverEvent.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "bedrockAgentEvent.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "bedrockAgentEventWithPathParams.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "bedrockAgentPostEvent.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "cloudWatchAlarmEventCompositeMetric.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "cloudWatchAlarmEventSingleMetric.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "cloudWatchDashboardEvent.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "cloudWatchLogEvent.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "cloudWatchLogEventWithPolicyLevel.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "cloudformationCustomResourceCreate.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "cloudformationCustomResourceDelete.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "cloudformationCustomResourceUpdate.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "codeDeployLifecycleHookEvent.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "codePipelineEvent.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "codePipelineEventData.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "codePipelineEventEmptyUserParameters.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "codePipelineEventWithEncryptionKey.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "cognitoCreateAuthChallengeEvent.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "cognitoCustomEmailSenderEvent.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "cognitoCustomMessageEvent.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "cognitoCustomSMSSenderEvent.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "cognitoDefineAuthChallengeEvent.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "cognitoPostAuthenticationEvent.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "cognitoPostConfirmationEvent.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "cognitoPreAuthenticationEvent.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "cognitoPreSignUpEvent.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "cognitoPreTokenGenerationEvent.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "cognitoPreTokenV2GenerationEvent.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "cognitoUserMigrationEvent.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "cognitoVerifyAuthChallengeResponseEvent.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "connectContactFlowEventAll.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "connectContactFlowEventMin.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "dynamoStreamEvent.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "eventBridgeEvent.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "kafkaEventMsk.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "kafkaEventSelfManaged.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "kinesisFirehoseKinesisEvent.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "kinesisFirehosePutEvent.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "kinesisFirehoseSQSEvent.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "kinesisStreamCloudWatchLogsEvent.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "kinesisStreamEvent.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "kinesisStreamEventOneRecord.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "lambdaFunctionUrlEvent.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "lambdaFunctionUrlEventPathTrailingSlash.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "lambdaFunctionUrlEventWithHeaders.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "lambdaFunctionUrlIAMEvent.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "rabbitMQEvent.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "s3BatchOperationEventSchemaV1.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "s3BatchOperationEventSchemaV2.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "s3Event.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "s3EventBridgeNotificationObjectCreatedEvent.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "s3EventBridgeNotificationObjectDeletedEvent.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "s3EventBridgeNotificationObjectExpiredEvent.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "s3EventBridgeNotificationObjectRestoreCompletedEvent.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "s3EventDecodedKey.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "s3EventDeleteObject.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "s3EventGlacier.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "s3ObjectEventIAMUser.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "s3ObjectEventTempCredentials.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "s3SqsEvent.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "secretsManagerEvent.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "sesEvent.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "snsEvent.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "snsSqsEvent.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "snsSqsFifoEvent.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "sqsDlqTriggerEvent.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "sqsEvent.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "vpcLatticeEvent.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "vpcLatticeEventPathTrailingSlash.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "vpcLatticeEventV2PathTrailingSlash.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "vpcLatticeV2Event.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ( + "vpcLatticeV2EventWithHeaders.json", + {"func": None, "arn": "arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9"}, + False, + ), + ], + ) + def test_match(self, event_name, option_constructor, expected_true): + event = load_event(file_name=event_name) + route = AwsConfigRuleRoute(**option_constructor) + expected_return = (route.func, AWSConfigRuleEvent(event)) + actual = route.match(event=event) + if expected_true: + assert actual == expected_return + else: + assert actual is None diff --git a/tests/unit/event_handler/_async_execution/_routes/test_cloud_watch_alarm.py b/tests/unit/event_handler/_async_execution/_routes/test_cloud_watch_alarm.py new file mode 100644 index 00000000000..2865c9d154c --- /dev/null +++ b/tests/unit/event_handler/_async_execution/_routes/test_cloud_watch_alarm.py @@ -0,0 +1,346 @@ +import pytest + +from aws_lambda_powertools.event_handler.async_execution.routes.cloud_watch_alarm import ( + CloudWatchAlarmRoute, +) +from aws_lambda_powertools.utilities.data_classes.cloud_watch_alarm_event import ( + CloudWatchAlarmEvent, +) +from tests.functional.utils import load_event + + +class TestCloudWatchAlarmRoute: + def test_constructor_error(self): + with pytest.raises(ValueError): + CloudWatchAlarmRoute(func=None) + + @pytest.mark.parametrize( + "option_constructor, option_func, expected", + [ + ( + {"func": None, "arn": "arn:aws:cloudwatch:us-east-1:111122223333:alarm:SuppressionDemo.Main"}, + {"arn": None}, + False, + ), + ( + {"func": None, "arn": "arn:aws:cloudwatch:us-east-1:111122223333:alarm:SuppressionDemo.Main"}, + {"arn": "arn:aws:cloudwatch:us-east-1:111122223333:alarm:SuppressionDemo.Main"}, + True, + ), + ( + {"func": None, "arn": "arn:aws:cloudwatch:us-east-1:111122223333:alarm:SuppressionDemo.MainV2"}, + {"arn": "arn:aws:cloudwatch:us-east-1:111122223333:alarm:SuppressionDemo.Main"}, + False, + ), + ( + {"func": None, "alarm_name": "SuppressionDemo.Main"}, + {"arn": "arn:aws:cloudwatch:us-east-1:111122223333:alarm:SuppressionDemo.Main"}, + False, + ), + ], + ) + def test_is_target_with_arn(self, option_constructor, option_func, expected): + route = CloudWatchAlarmRoute(**option_constructor) + actual = route.is_target_with_arn(**option_func) + assert actual == expected + + @pytest.mark.parametrize( + "option_constructor, option_func, expected", + [ + ({"func": None, "alarm_name": "CompositeDemo.Main"}, {"alarm_name": None}, False), + ({"func": None, "alarm_name": "CompositeDemo.Main"}, {"alarm_name": "CompositeDemo.Main"}, True), + ({"func": None, "alarm_name": "CompositeDemo.MainV2"}, {"alarm_name": "CompositeDemo.Main"}, False), + ({"func": None, "alarm_name_prefix": "CompositeDemo.M"}, {"alarm_name": "CompositeDemo.Main"}, True), + ({"func": None, "alarm_name_prefix": "Main"}, {"alarm_name": "CompositeDemo.Main"}, False), + ( + {"func": None, "arn": "arn:aws:cloudwatch:us-east-1:111122223333:alarm:SuppressionDemo.Main"}, + {"alarm_name": "CompositeDemo.Main"}, + False, + ), + ], + ) + def test_is_target_with_alarm_name(self, option_constructor, option_func, expected): + route = CloudWatchAlarmRoute(**option_constructor) + actual = route.is_target_with_alarm_name(**option_func) + assert actual == expected + + @pytest.mark.parametrize( + "event_name, option_constructor", + [ + # cloudWatchAlarmEventCompositeMetric.json, match arn, without alarm_name and alarm_name_prefix + ( + "cloudWatchAlarmEventCompositeMetric.json", + {"func": lambda _: None, "arn": "arn:aws:cloudwatch:us-east-1:111122223333:alarm:SuppressionDemo.Main"}, + ), + # cloudWatchAlarmEventCompositeMetric.json, match arn, with alarm_name or alarm_name_prefix + ( + "cloudWatchAlarmEventCompositeMetric.json", + { + "func": lambda _: None, + "arn": "arn:aws:cloudwatch:us-east-1:111122223333:alarm:SuppressionDemo.Main", + "alarm_name": "CompositeDemo.Main", + }, + ), + ( + "cloudWatchAlarmEventCompositeMetric.json", + { + "func": lambda _: None, + "arn": "arn:aws:cloudwatch:us-east-1:111122223333:alarm:SuppressionDemo.Main", + "alarm_name_prefix": "CompositeD", + }, + ), + ( + "cloudWatchAlarmEventCompositeMetric.json", + { + "func": lambda _: None, + "arn": "arn:aws:cloudwatch:us-east-1:111122223333:alarm:SuppressionDemo.Main", + "alarm_name": "CompositeDemo.Main", + "alarm_name_prefix": "CompositeD", + }, + ), + # cloudWatchAlarmEventCompositeMetric.json, match alarm_name, without alarm_name_prefix + ( + "cloudWatchAlarmEventCompositeMetric.json", + { + "func": lambda _: None, + "alarm_name": "CompositeDemo.Main", + }, + ), + # cloudWatchAlarmEventCompositeMetric.json, match alarm_name, with alarm_name_prefix + ( + "cloudWatchAlarmEventCompositeMetric.json", + { + "func": lambda _: None, + "alarm_name": "CompositeDemo.Main", + "alarm_name_prefix": "CompositeDD", + }, + ), + # cloudWatchAlarmEventCompositeMetric.json, match alarm_name_prefix + ( + "cloudWatchAlarmEventCompositeMetric.json", + { + "func": lambda _: None, + "alarm_name_prefix": "CompositeD", + }, + ), + # cloudWatchAlarmEventSingleMetric.json, match arn, without alarm_name and alarm_name_prefix + ( + "cloudWatchAlarmEventSingleMetric.json", + { + "func": lambda _: None, + "arn": "arn:aws:cloudwatch:eu-west-1:912397435824:alarm:test_alarm", + }, + ), + # cloudWatchAlarmEventSingleMetric.json, match arn, with alarm_name or alarm_name_prefix + ( + "cloudWatchAlarmEventSingleMetric.json", + { + "func": lambda _: None, + "arn": "arn:aws:cloudwatch:eu-west-1:912397435824:alarm:test_alarm", + "alarm_name": "Test alert", + }, + ), + ( + "cloudWatchAlarmEventSingleMetric.json", + { + "func": lambda _: None, + "arn": "arn:aws:cloudwatch:eu-west-1:912397435824:alarm:test_alarm", + "alarm_name_prefix": "Test a", + }, + ), + ( + "cloudWatchAlarmEventSingleMetric.json", + { + "func": lambda _: None, + "arn": "arn:aws:cloudwatch:eu-west-1:912397435824:alarm:test_alarm", + "alarm_name": "Test alert", + "alarm_name_prefix": "Test a", + }, + ), + # cloudWatchAlarmEventSingleMetric.json, match alarm_name, without alarm_name_prefix + ("cloudWatchAlarmEventSingleMetric.json", {"func": lambda _: None, "alarm_name": "Test alert"}), + # cloudWatchAlarmEventSingleMetric.json, match alarm_name, with alarm_name_prefix + ( + "cloudWatchAlarmEventSingleMetric.json", + {"func": lambda _: None, "alarm_name": "Test alert", "alarm_name_prefix": "Test-a"}, + ), + # cloudWatchAlarmEventSingleMetric.json, match alarm_name_prefix + ("cloudWatchAlarmEventSingleMetric.json", {"func": lambda _: None, "alarm_name_prefix": "Test a"}), + ], + ) + def test_match_true(self, event_name, option_constructor): + event = load_event(file_name=event_name) + route = CloudWatchAlarmRoute(**option_constructor) + expected = (route.func, CloudWatchAlarmEvent(event)) + actual = route.match(event=event) + assert actual == expected + + @pytest.mark.parametrize( + "event_name, option_constructor", + [ + # other events + ("activeMQEvent.json", {"func": None, "arn": "test"}), + ("albEvent.json", {"func": None, "arn": "test"}), + ("albEventPathTrailingSlash.json", {"func": None, "arn": "test"}), + ("albMultiValueHeadersEvent.json", {"func": None, "arn": "test"}), + ("albMultiValueQueryStringEvent.json", {"func": None, "arn": "test"}), + ("apiGatewayAuthorizerRequestEvent.json", {"func": None, "arn": "test"}), + ("apiGatewayAuthorizerTokenEvent.json", {"func": None, "arn": "test"}), + ("apiGatewayAuthorizerV2Event.json", {"func": None, "arn": "test"}), + ("apiGatewayProxyEvent.json", {"func": None, "arn": "test"}), + ("apiGatewayProxyEventAnotherPath.json", {"func": None, "arn": "test"}), + ("apiGatewayProxyEventNoOrigin.json", {"func": None, "arn": "test"}), + ("apiGatewayProxyEventPathTrailingSlash.json", {"func": None, "arn": "test"}), + ("apiGatewayProxyEventPrincipalId.json", {"func": None, "arn": "test"}), + ("apiGatewayProxyEvent_noVersionAuth.json", {"func": None, "arn": "test"}), + ("apiGatewayProxyOtherEvent.json", {"func": None, "arn": "test"}), + ("apiGatewayProxyV2Event.json", {"func": None, "arn": "test"}), + ("apiGatewayProxyV2EventPathTrailingSlash.json", {"func": None, "arn": "test"}), + ("apiGatewayProxyV2Event_GET.json", {"func": None, "arn": "test"}), + ("apiGatewayProxyV2IamEvent.json", {"func": None, "arn": "test"}), + ("apiGatewayProxyV2LambdaAuthorizerEvent.json", {"func": None, "arn": "test"}), + ("apiGatewayProxyV2OtherGetEvent.json", {"func": None, "arn": "test"}), + ("apiGatewayProxyV2SchemaMiddlwareInvalidEvent.json", {"func": None, "arn": "test"}), + ("apiGatewayProxyV2SchemaMiddlwareValidEvent.json", {"func": None, "arn": "test"}), + ("apigatewayeSchemaMiddlwareInvalidEvent.json", {"func": None, "arn": "test"}), + ("apigatewayeSchemaMiddlwareValidEvent.json", {"func": None, "arn": "test"}), + ("appSyncAuthorizerEvent.json", {"func": None, "arn": "test"}), + ("appSyncAuthorizerResponse.json", {"func": None, "arn": "test"}), + ("appSyncBatchEvent.json", {"func": None, "arn": "test"}), + ("appSyncDirectResolver.json", {"func": None, "arn": "test"}), + ("appSyncResolverEvent.json", {"func": None, "arn": "test"}), + ("awsConfigRuleConfigurationChanged.json", {"func": None, "arn": "test"}), + ("awsConfigRuleOversizedConfiguration.json", {"func": None, "arn": "test"}), + ("awsConfigRuleScheduled.json", {"func": None, "arn": "test"}), + ("bedrockAgentEvent.json", {"func": None, "arn": "test"}), + ("bedrockAgentEventWithPathParams.json", {"func": None, "arn": "test"}), + ("bedrockAgentPostEvent.json", {"func": None, "arn": "test"}), + ("cloudWatchDashboardEvent.json", {"func": None, "arn": "test"}), + ("cloudWatchLogEvent.json", {"func": None, "arn": "test"}), + ("cloudWatchLogEventWithPolicyLevel.json", {"func": None, "arn": "test"}), + ("cloudformationCustomResourceCreate.json", {"func": None, "arn": "test"}), + ("cloudformationCustomResourceDelete.json", {"func": None, "arn": "test"}), + ("cloudformationCustomResourceUpdate.json", {"func": None, "arn": "test"}), + ("codeDeployLifecycleHookEvent.json", {"func": None, "arn": "test"}), + ("codePipelineEvent.json", {"func": None, "arn": "test"}), + ("codePipelineEventData.json", {"func": None, "arn": "test"}), + ("codePipelineEventEmptyUserParameters.json", {"func": None, "arn": "test"}), + ("codePipelineEventWithEncryptionKey.json", {"func": None, "arn": "test"}), + ("cognitoCreateAuthChallengeEvent.json", {"func": None, "arn": "test"}), + ("cognitoCustomEmailSenderEvent.json", {"func": None, "arn": "test"}), + ("cognitoCustomMessageEvent.json", {"func": None, "arn": "test"}), + ("cognitoCustomSMSSenderEvent.json", {"func": None, "arn": "test"}), + ("cognitoDefineAuthChallengeEvent.json", {"func": None, "arn": "test"}), + ("cognitoPostAuthenticationEvent.json", {"func": None, "arn": "test"}), + ("cognitoPostConfirmationEvent.json", {"func": None, "arn": "test"}), + ("cognitoPreAuthenticationEvent.json", {"func": None, "arn": "test"}), + ("cognitoPreSignUpEvent.json", {"func": None, "arn": "test"}), + ("cognitoPreTokenGenerationEvent.json", {"func": None, "arn": "test"}), + ("cognitoPreTokenV2GenerationEvent.json", {"func": None, "arn": "test"}), + ("cognitoUserMigrationEvent.json", {"func": None, "arn": "test"}), + ("cognitoVerifyAuthChallengeResponseEvent.json", {"func": None, "arn": "test"}), + ("connectContactFlowEventAll.json", {"func": None, "arn": "test"}), + ("connectContactFlowEventMin.json", {"func": None, "arn": "test"}), + ("dynamoStreamEvent.json", {"func": None, "arn": "test"}), + ("eventBridgeEvent.json", {"func": None, "arn": "test"}), + ("kafkaEventMsk.json", {"func": None, "arn": "test"}), + ("kafkaEventSelfManaged.json", {"func": None, "arn": "test"}), + ("kinesisFirehoseKinesisEvent.json", {"func": None, "arn": "test"}), + ("kinesisFirehosePutEvent.json", {"func": None, "arn": "test"}), + ("kinesisFirehoseSQSEvent.json", {"func": None, "arn": "test"}), + ("kinesisStreamCloudWatchLogsEvent.json", {"func": None, "arn": "test"}), + ("kinesisStreamEvent.json", {"func": None, "arn": "test"}), + ("kinesisStreamEventOneRecord.json", {"func": None, "arn": "test"}), + ("lambdaFunctionUrlEvent.json", {"func": None, "arn": "test"}), + ("lambdaFunctionUrlEventPathTrailingSlash.json", {"func": None, "arn": "test"}), + ("lambdaFunctionUrlEventWithHeaders.json", {"func": None, "arn": "test"}), + ("lambdaFunctionUrlIAMEvent.json", {"func": None, "arn": "test"}), + ("rabbitMQEvent.json", {"func": None, "arn": "test"}), + ("s3BatchOperationEventSchemaV1.json", {"func": None, "arn": "test"}), + ("s3BatchOperationEventSchemaV2.json", {"func": None, "arn": "test"}), + ("s3Event.json", {"func": None, "arn": "test"}), + ("s3EventBridgeNotificationObjectCreatedEvent.json", {"func": None, "arn": "test"}), + ("s3EventBridgeNotificationObjectDeletedEvent.json", {"func": None, "arn": "test"}), + ("s3EventBridgeNotificationObjectExpiredEvent.json", {"func": None, "arn": "test"}), + ("s3EventBridgeNotificationObjectRestoreCompletedEvent.json", {"func": None, "arn": "test"}), + ("s3EventDecodedKey.json", {"func": None, "arn": "test"}), + ("s3EventDeleteObject.json", {"func": None, "arn": "test"}), + ("s3EventGlacier.json", {"func": None, "arn": "test"}), + ("s3ObjectEventIAMUser.json", {"func": None, "arn": "test"}), + ("s3ObjectEventTempCredentials.json", {"func": None, "arn": "test"}), + ("s3SqsEvent.json", {"func": None, "arn": "test"}), + ("secretsManagerEvent.json", {"func": None, "arn": "test"}), + ("sesEvent.json", {"func": None, "arn": "test"}), + ("snsEvent.json", {"func": None, "arn": "test"}), + ("snsSqsEvent.json", {"func": None, "arn": "test"}), + ("snsSqsFifoEvent.json", {"func": None, "arn": "test"}), + ("sqsDlqTriggerEvent.json", {"func": None, "arn": "test"}), + ("sqsEvent.json", {"func": None, "arn": "test"}), + ("vpcLatticeEvent.json", {"func": None, "arn": "test"}), + ("vpcLatticeEventPathTrailingSlash.json", {"func": None, "arn": "test"}), + ("vpcLatticeEventV2PathTrailingSlash.json", {"func": None, "arn": "test"}), + ("vpcLatticeV2Event.json", {"func": None, "arn": "test"}), + ("vpcLatticeV2EventWithHeaders.json", {"func": None, "arn": "test"}), + # cloudWatchAlarmEventCompositeMetric.json, not match arn, without alarm_name and alarm_name_prefix + ( + "cloudWatchAlarmEventCompositeMetric.json", + {"func": None, "arn": "arn:aws:cloudwatch:us-east-1:111122223333:alarm:SuppressionDemo.MainV2"}, + ), + # cloudWatchAlarmEventCompositeMetric.json, not match arn, with alarm_name or alarm_name_prefix + ( + "cloudWatchAlarmEventCompositeMetric.json", + { + "func": None, + "arn": "arn:aws:cloudwatch:us-east-1:111122223333:alarm:SuppressionDemo.MainV2", + "alarm_name": "CompositeDemo.Main", + }, + ), + ( + "cloudWatchAlarmEventCompositeMetric.json", + { + "func": None, + "arn": "arn:aws:cloudwatch:us-east-1:111122223333:alarm:SuppressionDemo.MainV2", + "alarm_name_prefix": "CompositeD", + }, + ), + ( + "cloudWatchAlarmEventCompositeMetric.json", + { + "func": None, + "arn": "arn:aws:cloudwatch:us-east-1:111122223333:alarm:SuppressionDemo.MainV2", + "alarm_name": "CompositeDemo.Main", + "alarm_name_prefix": "CompositeD", + }, + ), + # cloudWatchAlarmEventCompositeMetric.json, not match alarm_name, without alarm_name_prefix + ( + "cloudWatchAlarmEventCompositeMetric.json", + { + "func": None, + "alarm_name": "CompositeDemo.MainV2", + }, + ), + # cloudWatchAlarmEventCompositeMetric.json, not match alarm_name, with alarm_name_prefix + ( + "cloudWatchAlarmEventCompositeMetric.json", + { + "func": None, + "alarm_name": "CompositeDemo.MainV2", + "alarm_name_prefix": "CompositeD", + }, + ), + # cloudWatchAlarmEventCompositeMetric.json, not match alarm_name_prefix + ( + "cloudWatchAlarmEventCompositeMetric.json", + { + "func": None, + "alarm_name_prefix": "CompositeDD", + }, + ), + ], + ) + def test_match_false(self, event_name, option_constructor): + event = load_event(file_name=event_name) + route = CloudWatchAlarmRoute(**option_constructor) + actual = route.match(event=event) + assert actual is None diff --git a/tests/unit/event_handler/_async_execution/_routes/test_cloud_watch_logs.py b/tests/unit/event_handler/_async_execution/_routes/test_cloud_watch_logs.py new file mode 100644 index 00000000000..cbe3a5189ee --- /dev/null +++ b/tests/unit/event_handler/_async_execution/_routes/test_cloud_watch_logs.py @@ -0,0 +1,713 @@ +import pytest + +from aws_lambda_powertools.event_handler.async_execution.routes.cloud_watch_logs import ( + CloudWatchLogsRoute, +) +from aws_lambda_powertools.utilities.data_classes.cloud_watch_logs_event import ( + CloudWatchLogsEvent, +) +from tests.functional.utils import load_event + + +class TestCloudWatchLogsRoute: + def test_constructor_error(self): + with pytest.raises(ValueError): + CloudWatchLogsRoute(func=None) + + @pytest.mark.parametrize( + "option, expected", + [ + ( + {"func": None, "log_group": "test"}, + { + "func": None, + "log_group": "test", + "log_group_prefix": None, + "log_stream": None, + "log_stream_prefix": None, + "subscription_filters": None, + }, + ), + ( + {"func": None, "log_group_prefix": "test"}, + { + "func": None, + "log_group": None, + "log_group_prefix": "test", + "log_stream": None, + "log_stream_prefix": None, + "subscription_filters": None, + }, + ), + ( + {"func": None, "log_stream": "test"}, + { + "func": None, + "log_group": None, + "log_group_prefix": None, + "log_stream": "test", + "log_stream_prefix": None, + "subscription_filters": None, + }, + ), + ( + {"func": None, "log_stream_prefix": "test"}, + { + "func": None, + "log_group": None, + "log_group_prefix": None, + "log_stream": None, + "log_stream_prefix": "test", + "subscription_filters": None, + }, + ), + ( + {"func": None, "subscription_filters": "test"}, + { + "func": None, + "log_group": None, + "log_group_prefix": None, + "log_stream": None, + "log_stream_prefix": None, + "subscription_filters": ["test"], + }, + ), + ( + {"func": None, "subscription_filters": ["test", "name"]}, + { + "func": None, + "log_group": None, + "log_group_prefix": None, + "log_stream": None, + "log_stream_prefix": None, + "subscription_filters": ["test", "name"], + }, + ), + ], + ) + def test_constructor_normal(self, option, expected): + route = CloudWatchLogsRoute(**option) + assert route.__dict__ == expected + + @pytest.mark.parametrize( + "option_constructor, option, expected", + [ + ({"func": None, "log_group": "test-log-group"}, {"log_group": None}, False), + ({"func": None, "log_group": "test-log-group"}, {"log_group": "test-log-group-v2"}, False), + ({"func": None, "log_group": "test-log-group"}, {"log_group": "test-log-group"}, True), + ({"func": None, "log_group_prefix": "test-ll"}, {"log_group": "test-log-group"}, False), + ({"func": None, "log_group_prefix": "est-l"}, {"log_group": "test-log-group"}, False), + ({"func": None, "log_group_prefix": "test-l"}, {"log_group": "test-log-group"}, True), + ({"func": None, "log_stream": "test-log-group"}, {"log_group": None}, False), + ], + ) + def test_is_target_with_log_group(self, option_constructor, option, expected): + route = CloudWatchLogsRoute(**option_constructor) + actual = route.is_target_with_log_group(**option) + assert actual == expected + + @pytest.mark.parametrize( + "option_constructor, option, expected", + [ + ({"func": None, "log_stream": "test-log-stream"}, {"log_stream": None}, False), + ({"func": None, "log_stream": "test-log-stream-v2"}, {"log_stream": "test-log-stream"}, False), + ({"func": None, "log_stream": "test-log-stream"}, {"log_stream": "test-log-stream"}, True), + ({"func": None, "log_stream_prefix": "test-ll"}, {"log_stream": "test-log-stream"}, False), + ({"func": None, "log_stream_prefix": "est-l"}, {"log_stream": "test-log-stream"}, False), + ({"func": None, "log_stream_prefix": "test-l"}, {"log_stream": "test-log-stream"}, True), + ({"func": None, "log_group": "test-l"}, {"log_stream": "test-log-stream"}, False), + ], + ) + def test_is_target_with_log_stream(self, option_constructor, option, expected): + route = CloudWatchLogsRoute(**option_constructor) + actual = route.is_target_with_log_stream(**option) + assert actual == expected + + @pytest.mark.parametrize( + "option_constructor, option, expected", + [ + ({"func": None, "subscription_filters": ["test"]}, {"subscription_filters": None}, False), + ({"func": None, "log_group": "test"}, {"subscription_filters": ["test"]}, False), + ({"func": None, "subscription_filters": ["test"]}, {"subscription_filters": ["test"]}, True), + ({"func": None, "subscription_filters": ["test", "name"]}, {"subscription_filters": ["test"]}, True), + ({"func": None, "subscription_filters": ["test"]}, {"subscription_filters": ["test", "name"]}, True), + ({"func": None, "subscription_filters": ["test"]}, {"subscription_filters": ["name"]}, False), + ], + ) + def test_is_target_with_subscription_filters(self, option_constructor, option, expected): + route = CloudWatchLogsRoute(**option_constructor) + actual = route.is_target_with_subscription_filters(**option) + assert actual == expected + + @pytest.mark.parametrize( + "event_name, option_constructor", + [ + ( + "cloudWatchLogEvent.json", + { + "func": lambda *_: None, + "log_group": "testLogGroup", + "log_stream": "testLogStream", + "subscription_filters": ["testFilter"], + }, + ), + ( + "cloudWatchLogEvent.json", + { + "func": lambda *_: None, + "log_group_prefix": "testLogG", + "log_stream": "testLogStream", + "subscription_filters": ["testFilter"], + }, + ), + ( + "cloudWatchLogEvent.json", + { + "func": lambda *_: None, + "log_group": "testLogGroup", + "log_stream_prefix": "testLogS", + "subscription_filters": ["testFilter"], + }, + ), + ( + "cloudWatchLogEvent.json", + { + "func": lambda *_: None, + "log_group_prefix": "testLogG", + "log_stream_prefix": "testLogS", + "subscription_filters": ["testFilter"], + }, + ), + ( + "cloudWatchLogEvent.json", + { + "func": lambda *_: None, + "log_group": "testLogGroup", + "log_stream": "testLogStream", + }, + ), + ( + "cloudWatchLogEvent.json", + { + "func": lambda *_: None, + "log_group_prefix": "testLogG", + "log_stream": "testLogStream", + }, + ), + ( + "cloudWatchLogEvent.json", + { + "func": lambda *_: None, + "log_group": "testLogGroup", + "log_stream_prefix": "testLogS", + }, + ), + ( + "cloudWatchLogEvent.json", + { + "func": lambda *_: None, + "log_group_prefix": "testLogG", + "log_stream_prefix": "testLogS", + }, + ), + ( + "cloudWatchLogEvent.json", + {"func": lambda *_: None, "log_group": "testLogGroup", "subscription_filters": ["testFilter"]}, + ), + ( + "cloudWatchLogEvent.json", + {"func": lambda *_: None, "log_group_prefix": "testLogG", "subscription_filters": ["testFilter"]}, + ), + ( + "cloudWatchLogEvent.json", + {"func": lambda *_: None, "log_stream": "testLogStream", "subscription_filters": ["testFilter"]}, + ), + ( + "cloudWatchLogEvent.json", + {"func": lambda *_: None, "log_stream_prefix": "testLogS", "subscription_filters": ["testFilter"]}, + ), + ( + "cloudWatchLogEvent.json", + { + "func": lambda *_: None, + "log_group": "testLogGroup", + }, + ), + ( + "cloudWatchLogEvent.json", + { + "func": lambda *_: None, + "log_group_prefix": "testLogG", + }, + ), + ( + "cloudWatchLogEvent.json", + { + "func": lambda *_: None, + "log_stream": "testLogStream", + }, + ), + ("cloudWatchLogEvent.json", {"func": lambda *_: None, "subscription_filters": ["testFilter"]}), + ( + "cloudWatchLogEventWithPolicyLevel.json", + { + "func": lambda *_: None, + "log_group": "testLogGroup", + "log_stream": "testLogStream", + "subscription_filters": ["testFilter"], + }, + ), + ( + "cloudWatchLogEventWithPolicyLevel.json", + { + "func": lambda *_: None, + "log_group_prefix": "testLogG", + "log_stream": "testLogStream", + "subscription_filters": ["testFilter"], + }, + ), + ( + "cloudWatchLogEventWithPolicyLevel.json", + { + "func": lambda *_: None, + "log_group": "testLogGroup", + "log_stream_prefix": "testLogS", + "subscription_filters": ["testFilter"], + }, + ), + ( + "cloudWatchLogEventWithPolicyLevel.json", + { + "func": lambda *_: None, + "log_group_prefix": "testLogG", + "log_stream_prefix": "testLogS", + "subscription_filters": ["testFilter"], + }, + ), + ( + "cloudWatchLogEventWithPolicyLevel.json", + { + "func": lambda *_: None, + "log_group": "testLogGroup", + "log_stream": "testLogStream", + }, + ), + ( + "cloudWatchLogEventWithPolicyLevel.json", + { + "func": lambda *_: None, + "log_group_prefix": "testLogG", + "log_stream": "testLogStream", + }, + ), + ( + "cloudWatchLogEventWithPolicyLevel.json", + { + "func": lambda *_: None, + "log_group": "testLogGroup", + "log_stream_prefix": "testLogS", + }, + ), + ( + "cloudWatchLogEventWithPolicyLevel.json", + { + "func": lambda *_: None, + "log_group_prefix": "testLogG", + "log_stream_prefix": "testLogS", + }, + ), + ( + "cloudWatchLogEventWithPolicyLevel.json", + { + "func": lambda *_: None, + "log_group": "testLogGroup", + "subscription_filters": ["testFilter"], + }, + ), + ( + "cloudWatchLogEventWithPolicyLevel.json", + { + "func": lambda *_: None, + "log_group_prefix": "testLogG", + "subscription_filters": ["testFilter"], + }, + ), + ( + "cloudWatchLogEventWithPolicyLevel.json", + { + "func": lambda *_: None, + "log_stream": "testLogStream", + "subscription_filters": ["testFilter"], + }, + ), + ( + "cloudWatchLogEventWithPolicyLevel.json", + { + "func": lambda *_: None, + "log_stream_prefix": "testLogS", + "subscription_filters": ["testFilter"], + }, + ), + ( + "cloudWatchLogEventWithPolicyLevel.json", + {"func": lambda *_: None, "log_group": "testLogGroup"}, + ), + ( + "cloudWatchLogEventWithPolicyLevel.json", + {"func": lambda *_: None, "log_group_prefix": "testLogG"}, + ), + ( + "cloudWatchLogEventWithPolicyLevel.json", + {"func": lambda *_: None, "log_stream": "testLogStream"}, + ), + ( + "cloudWatchLogEventWithPolicyLevel.json", + {"func": lambda *_: None, "log_stream_prefix": "testLogS"}, + ), + ( + "cloudWatchLogEventWithPolicyLevel.json", + {"func": lambda *_: None, "subscription_filters": ["testFilter"]}, + ), + ], + ) + def test_match_true(self, event_name, option_constructor): + event = load_event(file_name=event_name) + route = CloudWatchLogsRoute(**option_constructor) + expected = (route.func, CloudWatchLogsEvent(event)) + actual = route.match(event=event) + assert actual == expected + + @pytest.mark.parametrize( + "event_name, option_constructor", + [ + ("activeMQEvent.json", {"func": None, "log_group": "test"}), + ("albEvent.json", {"func": None, "log_group": "test"}), + ("albEventPathTrailingSlash.json", {"func": None, "log_group": "test"}), + ("albMultiValueHeadersEvent.json", {"func": None, "log_group": "test"}), + ("albMultiValueQueryStringEvent.json", {"func": None, "log_group": "test"}), + ("apiGatewayAuthorizerRequestEvent.json", {"func": None, "log_group": "test"}), + ("apiGatewayAuthorizerTokenEvent.json", {"func": None, "log_group": "test"}), + ("apiGatewayAuthorizerV2Event.json", {"func": None, "log_group": "test"}), + ("apiGatewayProxyEvent.json", {"func": None, "log_group": "test"}), + ("apiGatewayProxyEventAnotherPath.json", {"func": None, "log_group": "test"}), + ("apiGatewayProxyEventNoOrigin.json", {"func": None, "log_group": "test"}), + ("apiGatewayProxyEventPathTrailingSlash.json", {"func": None, "log_group": "test"}), + ("apiGatewayProxyEventPrincipalId.json", {"func": None, "log_group": "test"}), + ("apiGatewayProxyEvent_noVersionAuth.json", {"func": None, "log_group": "test"}), + ("apiGatewayProxyOtherEvent.json", {"func": None, "log_group": "test"}), + ("apiGatewayProxyV2Event.json", {"func": None, "log_group": "test"}), + ("apiGatewayProxyV2EventPathTrailingSlash.json", {"func": None, "log_group": "test"}), + ("apiGatewayProxyV2Event_GET.json", {"func": None, "log_group": "test"}), + ("apiGatewayProxyV2IamEvent.json", {"func": None, "log_group": "test"}), + ("apiGatewayProxyV2LambdaAuthorizerEvent.json", {"func": None, "log_group": "test"}), + ("apiGatewayProxyV2OtherGetEvent.json", {"func": None, "log_group": "test"}), + ("apiGatewayProxyV2SchemaMiddlwareInvalidEvent.json", {"func": None, "log_group": "test"}), + ("apiGatewayProxyV2SchemaMiddlwareValidEvent.json", {"func": None, "log_group": "test"}), + ("apigatewayeSchemaMiddlwareInvalidEvent.json", {"func": None, "log_group": "test"}), + ("apigatewayeSchemaMiddlwareValidEvent.json", {"func": None, "log_group": "test"}), + ("appSyncAuthorizerEvent.json", {"func": None, "log_group": "test"}), + ("appSyncAuthorizerResponse.json", {"func": None, "log_group": "test"}), + ("appSyncBatchEvent.json", {"func": None, "log_group": "test"}), + ("appSyncDirectResolver.json", {"func": None, "log_group": "test"}), + ("appSyncResolverEvent.json", {"func": None, "log_group": "test"}), + ("awsConfigRuleConfigurationChanged.json", {"func": None, "log_group": "test"}), + ("awsConfigRuleOversizedConfiguration.json", {"func": None, "log_group": "test"}), + ("awsConfigRuleScheduled.json", {"func": None, "log_group": "test"}), + ("bedrockAgentEvent.json", {"func": None, "log_group": "test"}), + ("bedrockAgentEventWithPathParams.json", {"func": None, "log_group": "test"}), + ("bedrockAgentPostEvent.json", {"func": None, "log_group": "test"}), + ("cloudWatchAlarmEventCompositeMetric.json", {"func": None, "log_group": "test"}), + ("cloudWatchAlarmEventSingleMetric.json", {"func": None, "log_group": "test"}), + ("cloudWatchDashboardEvent.json", {"func": None, "log_group": "test"}), + ("cloudformationCustomResourceCreate.json", {"func": None, "log_group": "test"}), + ("cloudformationCustomResourceDelete.json", {"func": None, "log_group": "test"}), + ("cloudformationCustomResourceUpdate.json", {"func": None, "log_group": "test"}), + ("codeDeployLifecycleHookEvent.json", {"func": None, "log_group": "test"}), + ("codePipelineEvent.json", {"func": None, "log_group": "test"}), + ("codePipelineEventData.json", {"func": None, "log_group": "test"}), + ("codePipelineEventEmptyUserParameters.json", {"func": None, "log_group": "test"}), + ("codePipelineEventWithEncryptionKey.json", {"func": None, "log_group": "test"}), + ("cognitoCreateAuthChallengeEvent.json", {"func": None, "log_group": "test"}), + ("cognitoCustomEmailSenderEvent.json", {"func": None, "log_group": "test"}), + ("cognitoCustomMessageEvent.json", {"func": None, "log_group": "test"}), + ("cognitoCustomSMSSenderEvent.json", {"func": None, "log_group": "test"}), + ("cognitoDefineAuthChallengeEvent.json", {"func": None, "log_group": "test"}), + ("cognitoPostAuthenticationEvent.json", {"func": None, "log_group": "test"}), + ("cognitoPostConfirmationEvent.json", {"func": None, "log_group": "test"}), + ("cognitoPreAuthenticationEvent.json", {"func": None, "log_group": "test"}), + ("cognitoPreSignUpEvent.json", {"func": None, "log_group": "test"}), + ("cognitoPreTokenGenerationEvent.json", {"func": None, "log_group": "test"}), + ("cognitoPreTokenV2GenerationEvent.json", {"func": None, "log_group": "test"}), + ("cognitoUserMigrationEvent.json", {"func": None, "log_group": "test"}), + ("cognitoVerifyAuthChallengeResponseEvent.json", {"func": None, "log_group": "test"}), + ("connectContactFlowEventAll.json", {"func": None, "log_group": "test"}), + ("connectContactFlowEventMin.json", {"func": None, "log_group": "test"}), + ("dynamoStreamEvent.json", {"func": None, "log_group": "test"}), + ("eventBridgeEvent.json", {"func": None, "log_group": "test"}), + ("kafkaEventMsk.json", {"func": None, "log_group": "test"}), + ("kafkaEventSelfManaged.json", {"func": None, "log_group": "test"}), + ("kinesisFirehoseKinesisEvent.json", {"func": None, "log_group": "test"}), + ("kinesisFirehosePutEvent.json", {"func": None, "log_group": "test"}), + ("kinesisFirehoseSQSEvent.json", {"func": None, "log_group": "test"}), + ("kinesisStreamCloudWatchLogsEvent.json", {"func": None, "log_group": "test"}), + ("kinesisStreamEvent.json", {"func": None, "log_group": "test"}), + ("kinesisStreamEventOneRecord.json", {"func": None, "log_group": "test"}), + ("lambdaFunctionUrlEvent.json", {"func": None, "log_group": "test"}), + ("lambdaFunctionUrlEventPathTrailingSlash.json", {"func": None, "log_group": "test"}), + ("lambdaFunctionUrlEventWithHeaders.json", {"func": None, "log_group": "test"}), + ("lambdaFunctionUrlIAMEvent.json", {"func": None, "log_group": "test"}), + ("rabbitMQEvent.json", {"func": None, "log_group": "test"}), + ("s3BatchOperationEventSchemaV1.json", {"func": None, "log_group": "test"}), + ("s3BatchOperationEventSchemaV2.json", {"func": None, "log_group": "test"}), + ("s3Event.json", {"func": None, "log_group": "test"}), + ("s3EventBridgeNotificationObjectCreatedEvent.json", {"func": None, "log_group": "test"}), + ("s3EventBridgeNotificationObjectDeletedEvent.json", {"func": None, "log_group": "test"}), + ("s3EventBridgeNotificationObjectExpiredEvent.json", {"func": None, "log_group": "test"}), + ("s3EventBridgeNotificationObjectRestoreCompletedEvent.json", {"func": None, "log_group": "test"}), + ("s3EventDecodedKey.json", {"func": None, "log_group": "test"}), + ("s3EventDeleteObject.json", {"func": None, "log_group": "test"}), + ("s3EventGlacier.json", {"func": None, "log_group": "test"}), + ("s3ObjectEventIAMUser.json", {"func": None, "log_group": "test"}), + ("s3ObjectEventTempCredentials.json", {"func": None, "log_group": "test"}), + ("s3SqsEvent.json", {"func": None, "log_group": "test"}), + ("secretsManagerEvent.json", {"func": None, "log_group": "test"}), + ("sesEvent.json", {"func": None, "log_group": "test"}), + ("snsEvent.json", {"func": None, "log_group": "test"}), + ("snsSqsEvent.json", {"func": None, "log_group": "test"}), + ("snsSqsFifoEvent.json", {"func": None, "log_group": "test"}), + ("sqsDlqTriggerEvent.json", {"func": None, "log_group": "test"}), + ("sqsEvent.json", {"func": None, "log_group": "test"}), + ("vpcLatticeEvent.json", {"func": None, "log_group": "test"}), + ("vpcLatticeEventPathTrailingSlash.json", {"func": None, "log_group": "test"}), + ("vpcLatticeEventV2PathTrailingSlash.json", {"func": None, "log_group": "test"}), + ("vpcLatticeV2Event.json", {"func": None, "log_group": "test"}), + ("vpcLatticeV2EventWithHeaders.json", {"func": None, "log_group": "test"}), + # cloudWatchLogEvent.json, not match (log group, log stream and subscription filters) + ( + "cloudWatchLogEvent.json", + { + "func": None, + "log_group": "testLogGroupV2", + "log_stream": "testLogStreamV2", + "subscription_filters": ["testFilterV2"], + }, + ), + ( + "cloudWatchLogEvent.json", + { + "func": None, + "log_group_prefix": "testLogGG", + "log_stream": "testLogStreamV2", + "subscription_filters": ["testFilterV2"], + }, + ), + ( + "cloudWatchLogEvent.json", + { + "func": None, + "log_group": "testLogGroupV2", + "log_stream_prefix": "testLogSS", + "subscription_filters": ["testFilterV2"], + }, + ), + ( + "cloudWatchLogEvent.json", + { + "func": None, + "log_group_prefix": "testLogGG", + "log_stream_prefix": "testLogSS", + "subscription_filters": ["testFilterV2"], + }, + ), + ( + "cloudWatchLogEvent.json", + { + "func": None, + "log_group": "testLogGroupV2", + "log_stream": "testLogStreamV2", + "subscription_filters": ["testFilter"], + }, + ), + ( + "cloudWatchLogEvent.json", + { + "func": None, + "log_group_prefix": "testLogGG", + "log_stream": "testLogStreamV2", + "subscription_filters": ["testFilter"], + }, + ), + ( + "cloudWatchLogEvent.json", + { + "func": None, + "log_group": "testLogGroupV2", + "log_stream_prefix": "testLogSS", + "subscription_filters": ["testFilter"], + }, + ), + ( + "cloudWatchLogEvent.json", + { + "func": None, + "log_group_prefix": "testLogGG", + "log_stream_prefix": "testLogSS", + "subscription_filters": ["testFilter"], + }, + ), + ( + "cloudWatchLogEvent.json", + { + "func": None, + "log_group": "testLogGroupV2", + "log_stream": "testLogStream", + "subscription_filters": ["testFilterV2"], + }, + ), + ( + "cloudWatchLogEvent.json", + { + "func": None, + "log_group_prefix": "testLogGG", + "log_stream": "testLogStream", + "subscription_filters": ["testFilterV2"], + }, + ), + ( + "cloudWatchLogEvent.json", + { + "func": None, + "log_group": "testLogGroup", + "log_stream": "testLogStreamV2", + "subscription_filters": ["testFilterV2"], + }, + ), + ( + "cloudWatchLogEvent.json", + { + "func": None, + "log_group": "testLogGroup", + "log_stream_prefix": "testLogSS", + "subscription_filters": ["testFilterV2"], + }, + ), + ( + "cloudWatchLogEvent.json", + { + "func": None, + "log_group": "testLogGroupV2", + "log_stream": "testLogStream", + "subscription_filters": ["testFilter"], + }, + ), + ( + "cloudWatchLogEvent.json", + { + "func": None, + "log_group_prefix": "testLogGG", + "log_stream": "testLogStream", + "subscription_filters": ["testFilter"], + }, + ), + ( + "cloudWatchLogEvent.json", + { + "func": None, + "log_group": "testLogGroup", + "log_stream": "testLogStreamV2", + "subscription_filters": ["testFilter"], + }, + ), + ( + "cloudWatchLogEvent.json", + { + "func": None, + "log_group": "testLogGroup", + "log_stream_prefix": "testLogSS", + "subscription_filters": ["testFilter"], + }, + ), + ( + "cloudWatchLogEvent.json", + { + "func": None, + "log_group": "testLogGroup", + "log_stream": "testLogStream", + "subscription_filters": ["testFilterV2"], + }, + ), + # cloudWatchLogEvent.json, not match (log group and log stream) + ("cloudWatchLogEvent.json", {"func": None, "log_group": "testLogGroupV2", "log_stream": "testLogStreamV2"}), + ( + "cloudWatchLogEvent.json", + {"func": None, "log_group_prefix": "testLogGG", "log_stream": "testLogStreamV2"}, + ), + ( + "cloudWatchLogEvent.json", + {"func": None, "log_group": "testLogGroupV2", "log_stream_prefix": "testLogSS"}, + ), + ( + "cloudWatchLogEvent.json", + {"func": None, "log_group_prefix": "testLogGG", "log_stream_prefix": "testLogSS"}, + ), + ("cloudWatchLogEvent.json", {"func": None, "log_group": "testLogGroupV2", "log_stream": "testLogStream"}), + ("cloudWatchLogEvent.json", {"func": None, "log_group_prefix": "testLogGG", "log_stream": "testLogStream"}), + ("cloudWatchLogEvent.json", {"func": None, "log_group": "testLogGroup", "log_stream": "testLogStreamV2"}), + ("cloudWatchLogEvent.json", {"func": None, "log_group": "testLogGroup", "log_stream_prefix": "testLogSS"}), + # cloudWatchLogEvent.json, not match (log group and subscription filters) + ( + "cloudWatchLogEvent.json", + {"func": None, "log_group": "testLogGroupV2", "subscription_filters": ["testFilterV2"]}, + ), + ( + "cloudWatchLogEvent.json", + {"func": None, "log_group_prefix": "testLogGG", "subscription_filters": ["testFilterV2"]}, + ), + ( + "cloudWatchLogEvent.json", + {"func": None, "log_group": "testLogGroupV2", "subscription_filters": ["testFilter"]}, + ), + ( + "cloudWatchLogEvent.json", + {"func": None, "log_group_prefix": "testLogGG", "subscription_filters": ["testFilter"]}, + ), + ( + "cloudWatchLogEvent.json", + {"func": None, "log_group": "testLogGroup", "subscription_filters": ["testFilterV2"]}, + ), + # cloudWatchLogEvent.json, not match (log stream and subscription filters) + ( + "cloudWatchLogEvent.json", + {"func": None, "log_stream": "testLogStreamV2", "subscription_filters": ["testFilterV2"]}, + ), + ( + "cloudWatchLogEvent.json", + {"func": None, "log_stream_prefix": "testLogSS", "subscription_filters": ["testFilterV2"]}, + ), + ( + "cloudWatchLogEvent.json", + {"func": None, "log_stream": "testLogStreamV2", "subscription_filters": ["testFilter"]}, + ), + ( + "cloudWatchLogEvent.json", + {"func": None, "log_stream_prefix": "testLogSS", "subscription_filters": ["testFilter"]}, + ), + ( + "cloudWatchLogEvent.json", + {"func": None, "log_stream": "testLogStream", "subscription_filters": ["testFilterV2"]}, + ), + # cloudWatchLogEvent.json, not match log_group + ("cloudWatchLogEvent.json", {"func": None, "log_group": "testLogGroupV2"}), + ("cloudWatchLogEvent.json", {"func": None, "log_group_prefix": "testLogGG"}), + # cloudWatchLogEvent.json, not match log_stream + ("cloudWatchLogEvent.json", {"func": None, "log_stream": "testLogStreamV2"}), + ("cloudWatchLogEvent.json", {"func": None, "log_stream_prefix": "testLogSS"}), + # cloudWatchLogEvent.json, not match subscription_filters + ("cloudWatchLogEvent.json", {"func": None, "subscription_filters": ["testFilterV2"]}), + ], + ) + def test_match_false(self, event_name, option_constructor): + event = load_event(file_name=event_name) + route = CloudWatchLogsRoute(**option_constructor) + actual = route.match(event=event) + assert actual is None diff --git a/tests/unit/event_handler/_async_execution/_routes/test_code_deploy_lifecycle_hook.py b/tests/unit/event_handler/_async_execution/_routes/test_code_deploy_lifecycle_hook.py new file mode 100644 index 00000000000..ff994a64e66 --- /dev/null +++ b/tests/unit/event_handler/_async_execution/_routes/test_code_deploy_lifecycle_hook.py @@ -0,0 +1,138 @@ +import pytest + +from aws_lambda_powertools.event_handler.async_execution.routes.code_deploy_lifecycle_hook import ( + CodeDeployLifecycleHookRoute, +) +from aws_lambda_powertools.utilities.data_classes.code_deploy_lifecycle_hook_event import ( + CodeDeployLifecycleHookEvent, +) +from tests.functional.utils import load_event + + +class TestCodeDeployLifecycleHookRoute: + @pytest.mark.parametrize( + "event_name, option_constructor", + [ + ("codeDeployLifecycleHookEvent.json", {"func": None}), + ], + ) + def test_match_true(self, event_name, option_constructor): + event = load_event(file_name=event_name) + route = CodeDeployLifecycleHookRoute(**option_constructor) + expected = (route.func, CodeDeployLifecycleHookEvent(event)) + actual = route.match(event=event) + assert actual == expected + + @pytest.mark.parametrize( + "event_name, option_constructor", + [ + ("activeMQEvent.json", {"func": None}), + ("albEvent.json", {"func": None}), + ("albEventPathTrailingSlash.json", {"func": None}), + ("albMultiValueHeadersEvent.json", {"func": None}), + ("albMultiValueQueryStringEvent.json", {"func": None}), + ("apiGatewayAuthorizerRequestEvent.json", {"func": None}), + ("apiGatewayAuthorizerTokenEvent.json", {"func": None}), + ("apiGatewayAuthorizerV2Event.json", {"func": None}), + ("apiGatewayProxyEvent.json", {"func": None}), + ("apiGatewayProxyEventAnotherPath.json", {"func": None}), + ("apiGatewayProxyEventNoOrigin.json", {"func": None}), + ("apiGatewayProxyEventPathTrailingSlash.json", {"func": None}), + ("apiGatewayProxyEventPrincipalId.json", {"func": None}), + ("apiGatewayProxyEvent_noVersionAuth.json", {"func": None}), + ("apiGatewayProxyOtherEvent.json", {"func": None}), + ("apiGatewayProxyV2Event.json", {"func": None}), + ("apiGatewayProxyV2EventPathTrailingSlash.json", {"func": None}), + ("apiGatewayProxyV2Event_GET.json", {"func": None}), + ("apiGatewayProxyV2IamEvent.json", {"func": None}), + ("apiGatewayProxyV2LambdaAuthorizerEvent.json", {"func": None}), + ("apiGatewayProxyV2OtherGetEvent.json", {"func": None}), + ("apiGatewayProxyV2SchemaMiddlwareInvalidEvent.json", {"func": None}), + ("apiGatewayProxyV2SchemaMiddlwareValidEvent.json", {"func": None}), + ("apigatewayeSchemaMiddlwareInvalidEvent.json", {"func": None}), + ("apigatewayeSchemaMiddlwareValidEvent.json", {"func": None}), + ("appSyncAuthorizerEvent.json", {"func": None}), + ("appSyncAuthorizerResponse.json", {"func": None}), + ("appSyncBatchEvent.json", {"func": None}), + ("appSyncDirectResolver.json", {"func": None}), + ("appSyncResolverEvent.json", {"func": None}), + ("awsConfigRuleConfigurationChanged.json", {"func": None}), + ("awsConfigRuleOversizedConfiguration.json", {"func": None}), + ("awsConfigRuleScheduled.json", {"func": None}), + ("bedrockAgentEvent.json", {"func": None}), + ("bedrockAgentEventWithPathParams.json", {"func": None}), + ("bedrockAgentPostEvent.json", {"func": None}), + ("cloudWatchAlarmEventCompositeMetric.json", {"func": None}), + ("cloudWatchAlarmEventSingleMetric.json", {"func": None}), + ("cloudWatchDashboardEvent.json", {"func": None}), + ("cloudWatchLogEvent.json", {"func": None}), + ("cloudWatchLogEventWithPolicyLevel.json", {"func": None}), + ("cloudformationCustomResourceCreate.json", {"func": None}), + ("cloudformationCustomResourceDelete.json", {"func": None}), + ("cloudformationCustomResourceUpdate.json", {"func": None}), + ("codePipelineEvent.json", {"func": None}), + ("codePipelineEventData.json", {"func": None}), + ("codePipelineEventEmptyUserParameters.json", {"func": None}), + ("codePipelineEventWithEncryptionKey.json", {"func": None}), + ("cognitoCreateAuthChallengeEvent.json", {"func": None}), + ("cognitoCustomEmailSenderEvent.json", {"func": None}), + ("cognitoCustomMessageEvent.json", {"func": None}), + ("cognitoCustomSMSSenderEvent.json", {"func": None}), + ("cognitoDefineAuthChallengeEvent.json", {"func": None}), + ("cognitoPostAuthenticationEvent.json", {"func": None}), + ("cognitoPostConfirmationEvent.json", {"func": None}), + ("cognitoPreAuthenticationEvent.json", {"func": None}), + ("cognitoPreSignUpEvent.json", {"func": None}), + ("cognitoPreTokenGenerationEvent.json", {"func": None}), + ("cognitoPreTokenV2GenerationEvent.json", {"func": None}), + ("cognitoUserMigrationEvent.json", {"func": None}), + ("cognitoVerifyAuthChallengeResponseEvent.json", {"func": None}), + ("connectContactFlowEventAll.json", {"func": None}), + ("connectContactFlowEventMin.json", {"func": None}), + ("dynamoStreamEvent.json", {"func": None}), + ("eventBridgeEvent.json", {"func": None}), + ("kafkaEventMsk.json", {"func": None}), + ("kafkaEventSelfManaged.json", {"func": None}), + ("kinesisFirehoseKinesisEvent.json", {"func": None}), + ("kinesisFirehosePutEvent.json", {"func": None}), + ("kinesisFirehoseSQSEvent.json", {"func": None}), + ("kinesisStreamCloudWatchLogsEvent.json", {"func": None}), + ("kinesisStreamEvent.json", {"func": None}), + ("kinesisStreamEventOneRecord.json", {"func": None}), + ("lambdaFunctionUrlEvent.json", {"func": None}), + ("lambdaFunctionUrlEventPathTrailingSlash.json", {"func": None}), + ("lambdaFunctionUrlEventWithHeaders.json", {"func": None}), + ("lambdaFunctionUrlIAMEvent.json", {"func": None}), + ("rabbitMQEvent.json", {"func": None}), + ("s3BatchOperationEventSchemaV1.json", {"func": None}), + ("s3BatchOperationEventSchemaV2.json", {"func": None}), + ("s3Event.json", {"func": None}), + ("s3EventBridgeNotificationObjectCreatedEvent.json", {"func": None}), + ("s3EventBridgeNotificationObjectDeletedEvent.json", {"func": None}), + ("s3EventBridgeNotificationObjectExpiredEvent.json", {"func": None}), + ("s3EventBridgeNotificationObjectRestoreCompletedEvent.json", {"func": None}), + ("s3EventDecodedKey.json", {"func": None}), + ("s3EventDeleteObject.json", {"func": None}), + ("s3EventGlacier.json", {"func": None}), + ("s3ObjectEventIAMUser.json", {"func": None}), + ("s3ObjectEventTempCredentials.json", {"func": None}), + ("s3SqsEvent.json", {"func": None}), + ("secretsManagerEvent.json", {"func": None}), + ("sesEvent.json", {"func": None}), + ("snsEvent.json", {"func": None}), + ("snsSqsEvent.json", {"func": None}), + ("snsSqsFifoEvent.json", {"func": None}), + ("sqsDlqTriggerEvent.json", {"func": None}), + ("sqsEvent.json", {"func": None}), + ("vpcLatticeEvent.json", {"func": None}), + ("vpcLatticeEventPathTrailingSlash.json", {"func": None}), + ("vpcLatticeEventV2PathTrailingSlash.json", {"func": None}), + ("vpcLatticeV2Event.json", {"func": None}), + ("vpcLatticeV2EventWithHeaders.json", {"func": None}), + ], + ) + def test_match_false(self, event_name, option_constructor): + event = load_event(file_name=event_name) + route = CodeDeployLifecycleHookRoute(**option_constructor) + actual = route.match(event=event) + assert actual is None diff --git a/tests/unit/event_handler/_async_execution/_routes/test_event_bridge.py b/tests/unit/event_handler/_async_execution/_routes/test_event_bridge.py new file mode 100644 index 00000000000..42eb30ece21 --- /dev/null +++ b/tests/unit/event_handler/_async_execution/_routes/test_event_bridge.py @@ -0,0 +1,460 @@ +import pytest + +from aws_lambda_powertools.event_handler.async_execution.routes.event_bridge import ( + EventBridgeRoute, +) +from aws_lambda_powertools.utilities.data_classes.event_bridge_event import ( + EventBridgeEvent, +) +from tests.functional.utils import load_event + + +class TestEventBridgeRoute: + def test_constructor_error(self): + with pytest.raises(ValueError): + EventBridgeRoute(func=lambda *_: None) + + @pytest.mark.parametrize( + "option_constructor, expected", + [ + ( + {"func": None, "resources": "test"}, + {"func": None, "detail_type": None, "source": None, "resources": ["test"]}, + ), + ( + {"func": None, "resources": ["test"]}, + {"func": None, "detail_type": None, "source": None, "resources": ["test"]}, + ), + ( + {"func": None, "resources": ["test", "name"]}, + {"func": None, "detail_type": None, "source": None, "resources": ["test", "name"]}, + ), + ], + ) + def test_constructor_normal(self, option_constructor, expected): + route = EventBridgeRoute(**option_constructor) + assert route.__dict__ == expected + + @pytest.mark.parametrize( + "option_constructor, option, expected", + [ + ({"func": None, "detail_type": "test type"}, {"detail_type": None}, False), + ({"func": None, "detail_type": "test type"}, {"detail_type": "test type 2"}, False), + ({"func": None, "detail_type": "test type"}, {"detail_type": "test type"}, True), + ], + ) + def test_is_target_with_detail_type(self, option_constructor, option, expected): + route = EventBridgeRoute(**option_constructor) + actual = route.is_target_with_detail_type(**option) + assert actual == expected + + @pytest.mark.parametrize( + "option_constructor, option, expected", + [ + ({"func": None, "source": "aws.ec2"}, {"source": None}, False), + ({"func": None, "source": "aws.ec2"}, {"source": "aws.lambda"}, False), + ({"func": None, "source": "aws.ec2"}, {"source": "aws.ec2"}, True), + ], + ) + def test_is_target_with_source(self, option_constructor, option, expected): + route = EventBridgeRoute(**option_constructor) + actual = route.is_target_with_source(**option) + assert actual == expected + + @pytest.mark.parametrize( + "option_constructor, option, expected", + [ + ( + {"func": None, "resources": ["arn:aws:ec2:us-west-1:123456789012:instance/i-1234567890abcdef0"]}, + {"resources": None}, + False, + ), + ( + {"func": None, "resources": ["arn:aws:ec2:us-west-1:123456789012:instance/i-1234567890abcdef0"]}, + {"resources": ["arn:aws:ec2:us-west-1:123456789012:instance/i-9999999999abcdef9"]}, + False, + ), + ( + {"func": None, "resources": ["arn:aws:ec2:us-west-1:123456789012:instance/i-1234567890abcdef0"]}, + {"resources": ["arn:aws:ec2:us-west-1:123456789012:instance/i-1234567890abcdef0"]}, + True, + ), + ( + { + "func": None, + "resources": [ + "arn:aws:ec2:us-west-1:123456789012:instance/i-1234567890abcdef0", + "arn:aws:ec2:us-west-1:123456789012:instance/i-2222222222abcdef2", + ], + }, + {"resources": ["arn:aws:ec2:us-west-1:123456789012:instance/i-1234567890abcdef0"]}, + True, + ), + ( + { + "func": None, + "resources": [ + "arn:aws:ec2:us-west-1:123456789012:instance/i-1234567890abcdef0", + ], + }, + { + "resources": [ + "arn:aws:ec2:us-west-1:123456789012:instance/i-2222222222abcdef2", + "arn:aws:ec2:us-west-1:123456789012:instance/i-1234567890abcdef0", + ], + }, + True, + ), + ( + { + "func": None, + "resources": [ + "arn:aws:ec2:us-west-1:123456789012:instance/i-1234567890abcdef0", + "arn:aws:ec2:us-west-1:123456789012:instance/i-3333333333abcdef3", + ], + }, + { + "resources": [ + "arn:aws:ec2:us-west-1:123456789012:instance/i-2222222222abcdef2", + "arn:aws:ec2:us-west-1:123456789012:instance/i-1234567890abcdef0", + ], + }, + True, + ), + ], + ) + def test_is_target_with_resources(self, option_constructor, option, expected): + route = EventBridgeRoute(**option_constructor) + actual = route.is_target_with_resources(**option) + assert actual == expected + + @pytest.mark.parametrize( + "event_name, option_constructor", + [ + ( + "eventBridgeEvent.json", + { + "func": lambda *_: None, + "detail_type": "EC2 Instance State-change Notification", + "source": "aws.ec2", + "resources": ["arn:aws:ec2:us-west-1:123456789012:instance/i-1234567890abcdef0"], + }, + ), + ( + "eventBridgeEvent.json", + { + "func": lambda *_: None, + "detail_type": "EC2 Instance State-change Notification", + "source": "aws.ec2", + }, + ), + ( + "eventBridgeEvent.json", + { + "func": lambda *_: None, + "detail_type": "EC2 Instance State-change Notification", + "resources": ["arn:aws:ec2:us-west-1:123456789012:instance/i-1234567890abcdef0"], + }, + ), + ( + "eventBridgeEvent.json", + { + "func": lambda *_: None, + "source": "aws.ec2", + "resources": ["arn:aws:ec2:us-west-1:123456789012:instance/i-1234567890abcdef0"], + }, + ), + ( + "eventBridgeEvent.json", + { + "func": lambda *_: None, + "detail_type": "EC2 Instance State-change Notification", + }, + ), + ( + "eventBridgeEvent.json", + { + "func": lambda *_: None, + "source": "aws.ec2", + }, + ), + ( + "eventBridgeEvent.json", + { + "func": lambda *_: None, + "resources": ["arn:aws:ec2:us-west-1:123456789012:instance/i-1234567890abcdef0"], + }, + ), + ], + ) + def test_match_true(self, event_name, option_constructor): + event = load_event(file_name=event_name) + route = EventBridgeRoute(**option_constructor) + expected = (route.func, EventBridgeEvent(event)) + actual = route.match(event=event) + assert actual == expected + + @pytest.mark.parametrize( + "event_name, option_constructor", + [ + ("activeMQEvent.json", {"func": None, "detail_type": "test"}), + ("albEvent.json", {"func": None, "detail_type": "test"}), + ("albEventPathTrailingSlash.json", {"func": None, "detail_type": "test"}), + ("albMultiValueHeadersEvent.json", {"func": None, "detail_type": "test"}), + ("albMultiValueQueryStringEvent.json", {"func": None, "detail_type": "test"}), + ("apiGatewayAuthorizerRequestEvent.json", {"func": None, "detail_type": "test"}), + ("apiGatewayAuthorizerTokenEvent.json", {"func": None, "detail_type": "test"}), + ("apiGatewayAuthorizerV2Event.json", {"func": None, "detail_type": "test"}), + ("apiGatewayProxyEvent.json", {"func": None, "detail_type": "test"}), + ("apiGatewayProxyEventAnotherPath.json", {"func": None, "detail_type": "test"}), + ("apiGatewayProxyEventNoOrigin.json", {"func": None, "detail_type": "test"}), + ("apiGatewayProxyEventPathTrailingSlash.json", {"func": None, "detail_type": "test"}), + ("apiGatewayProxyEventPrincipalId.json", {"func": None, "detail_type": "test"}), + ("apiGatewayProxyEvent_noVersionAuth.json", {"func": None, "detail_type": "test"}), + ("apiGatewayProxyOtherEvent.json", {"func": None, "detail_type": "test"}), + ("apiGatewayProxyV2Event.json", {"func": None, "detail_type": "test"}), + ("apiGatewayProxyV2EventPathTrailingSlash.json", {"func": None, "detail_type": "test"}), + ("apiGatewayProxyV2Event_GET.json", {"func": None, "detail_type": "test"}), + ("apiGatewayProxyV2IamEvent.json", {"func": None, "detail_type": "test"}), + ("apiGatewayProxyV2LambdaAuthorizerEvent.json", {"func": None, "detail_type": "test"}), + ("apiGatewayProxyV2OtherGetEvent.json", {"func": None, "detail_type": "test"}), + ("apiGatewayProxyV2SchemaMiddlwareInvalidEvent.json", {"func": None, "detail_type": "test"}), + ("apiGatewayProxyV2SchemaMiddlwareValidEvent.json", {"func": None, "detail_type": "test"}), + ("apigatewayeSchemaMiddlwareInvalidEvent.json", {"func": None, "detail_type": "test"}), + ("apigatewayeSchemaMiddlwareValidEvent.json", {"func": None, "detail_type": "test"}), + ("appSyncAuthorizerEvent.json", {"func": None, "detail_type": "test"}), + ("appSyncAuthorizerResponse.json", {"func": None, "detail_type": "test"}), + ("appSyncBatchEvent.json", {"func": None, "detail_type": "test"}), + ("appSyncDirectResolver.json", {"func": None, "detail_type": "test"}), + ("appSyncResolverEvent.json", {"func": None, "detail_type": "test"}), + ("awsConfigRuleConfigurationChanged.json", {"func": None, "detail_type": "test"}), + ("awsConfigRuleOversizedConfiguration.json", {"func": None, "detail_type": "test"}), + ("awsConfigRuleScheduled.json", {"func": None, "detail_type": "test"}), + ("bedrockAgentEvent.json", {"func": None, "detail_type": "test"}), + ("bedrockAgentEventWithPathParams.json", {"func": None, "detail_type": "test"}), + ("bedrockAgentPostEvent.json", {"func": None, "detail_type": "test"}), + ("cloudWatchAlarmEventCompositeMetric.json", {"func": None, "detail_type": "test"}), + ("cloudWatchAlarmEventSingleMetric.json", {"func": None, "detail_type": "test"}), + ("cloudWatchDashboardEvent.json", {"func": None, "detail_type": "test"}), + ("cloudWatchLogEvent.json", {"func": None, "detail_type": "test"}), + ("cloudWatchLogEventWithPolicyLevel.json", {"func": None, "detail_type": "test"}), + ("cloudformationCustomResourceCreate.json", {"func": None, "detail_type": "test"}), + ("cloudformationCustomResourceDelete.json", {"func": None, "detail_type": "test"}), + ("cloudformationCustomResourceUpdate.json", {"func": None, "detail_type": "test"}), + ("codeDeployLifecycleHookEvent.json", {"func": None, "detail_type": "test"}), + ("codePipelineEvent.json", {"func": None, "detail_type": "test"}), + ("codePipelineEventData.json", {"func": None, "detail_type": "test"}), + ("codePipelineEventEmptyUserParameters.json", {"func": None, "detail_type": "test"}), + ("codePipelineEventWithEncryptionKey.json", {"func": None, "detail_type": "test"}), + ("cognitoCreateAuthChallengeEvent.json", {"func": None, "detail_type": "test"}), + ("cognitoCustomEmailSenderEvent.json", {"func": None, "detail_type": "test"}), + ("cognitoCustomMessageEvent.json", {"func": None, "detail_type": "test"}), + ("cognitoCustomSMSSenderEvent.json", {"func": None, "detail_type": "test"}), + ("cognitoDefineAuthChallengeEvent.json", {"func": None, "detail_type": "test"}), + ("cognitoPostAuthenticationEvent.json", {"func": None, "detail_type": "test"}), + ("cognitoPostConfirmationEvent.json", {"func": None, "detail_type": "test"}), + ("cognitoPreAuthenticationEvent.json", {"func": None, "detail_type": "test"}), + ("cognitoPreSignUpEvent.json", {"func": None, "detail_type": "test"}), + ("cognitoPreTokenGenerationEvent.json", {"func": None, "detail_type": "test"}), + ("cognitoPreTokenV2GenerationEvent.json", {"func": None, "detail_type": "test"}), + ("cognitoUserMigrationEvent.json", {"func": None, "detail_type": "test"}), + ("cognitoVerifyAuthChallengeResponseEvent.json", {"func": None, "detail_type": "test"}), + ("connectContactFlowEventAll.json", {"func": None, "detail_type": "test"}), + ("connectContactFlowEventMin.json", {"func": None, "detail_type": "test"}), + ("dynamoStreamEvent.json", {"func": None, "detail_type": "test"}), + ("kafkaEventMsk.json", {"func": None, "detail_type": "test"}), + ("kafkaEventSelfManaged.json", {"func": None, "detail_type": "test"}), + ("kinesisFirehoseKinesisEvent.json", {"func": None, "detail_type": "test"}), + ("kinesisFirehosePutEvent.json", {"func": None, "detail_type": "test"}), + ("kinesisFirehoseSQSEvent.json", {"func": None, "detail_type": "test"}), + ("kinesisStreamCloudWatchLogsEvent.json", {"func": None, "detail_type": "test"}), + ("kinesisStreamEvent.json", {"func": None, "detail_type": "test"}), + ("kinesisStreamEventOneRecord.json", {"func": None, "detail_type": "test"}), + ("lambdaFunctionUrlEvent.json", {"func": None, "detail_type": "test"}), + ("lambdaFunctionUrlEventPathTrailingSlash.json", {"func": None, "detail_type": "test"}), + ("lambdaFunctionUrlEventWithHeaders.json", {"func": None, "detail_type": "test"}), + ("lambdaFunctionUrlIAMEvent.json", {"func": None, "detail_type": "test"}), + ("rabbitMQEvent.json", {"func": None, "detail_type": "test"}), + ("s3BatchOperationEventSchemaV1.json", {"func": None, "detail_type": "test"}), + ("s3BatchOperationEventSchemaV2.json", {"func": None, "detail_type": "test"}), + ("s3Event.json", {"func": None, "detail_type": "test"}), + ("s3EventBridgeNotificationObjectCreatedEvent.json", {"func": None, "detail_type": "test"}), + ("s3EventBridgeNotificationObjectDeletedEvent.json", {"func": None, "detail_type": "test"}), + ("s3EventBridgeNotificationObjectExpiredEvent.json", {"func": None, "detail_type": "test"}), + ("s3EventBridgeNotificationObjectRestoreCompletedEvent.json", {"func": None, "detail_type": "test"}), + ("s3EventDecodedKey.json", {"func": None, "detail_type": "test"}), + ("s3EventDeleteObject.json", {"func": None, "detail_type": "test"}), + ("s3EventGlacier.json", {"func": None, "detail_type": "test"}), + ("s3ObjectEventIAMUser.json", {"func": None, "detail_type": "test"}), + ("s3ObjectEventTempCredentials.json", {"func": None, "detail_type": "test"}), + ("s3SqsEvent.json", {"func": None, "detail_type": "test"}), + ("secretsManagerEvent.json", {"func": None, "detail_type": "test"}), + ("sesEvent.json", {"func": None, "detail_type": "test"}), + ("snsEvent.json", {"func": None, "detail_type": "test"}), + ("snsSqsEvent.json", {"func": None, "detail_type": "test"}), + ("snsSqsFifoEvent.json", {"func": None, "detail_type": "test"}), + ("sqsDlqTriggerEvent.json", {"func": None, "detail_type": "test"}), + ("sqsEvent.json", {"func": None, "detail_type": "test"}), + ("vpcLatticeEvent.json", {"func": None, "detail_type": "test"}), + ("vpcLatticeEventPathTrailingSlash.json", {"func": None, "detail_type": "test"}), + ("vpcLatticeEventV2PathTrailingSlash.json", {"func": None, "detail_type": "test"}), + ("vpcLatticeV2Event.json", {"func": None, "detail_type": "test"}), + ("vpcLatticeV2EventWithHeaders.json", {"func": None, "detail_type": "test"}), + # eventBridgeEvent.json, not match (detail type, source, and resources) + ( + "eventBridgeEvent.json", + { + "func": None, + "detail_type": "EC2 Instance State-change Notification V2", + "source": "aws.lambda", + "resources": ["arn:aws:ec2:us-west-1:123456789012:instance/i-9999999999abcdef9"], + }, + ), + ( + "eventBridgeEvent.json", + { + "func": None, + "detail_type": "EC2 Instance State-change Notification V2", + "source": "aws.lambda", + "resources": ["arn:aws:ec2:us-west-1:123456789012:instance/i-1234567890abcdef0"], + }, + ), + ( + "eventBridgeEvent.json", + { + "func": None, + "detail_type": "EC2 Instance State-change Notification V2", + "source": "aws.ec2", + "resources": ["arn:aws:ec2:us-west-1:123456789012:instance/i-9999999999abcdef9"], + }, + ), + ( + "eventBridgeEvent.json", + { + "func": None, + "detail_type": "EC2 Instance State-change Notification", + "source": "aws.lambda", + "resources": ["arn:aws:ec2:us-west-1:123456789012:instance/i-9999999999abcdef9"], + }, + ), + ( + "eventBridgeEvent.json", + { + "func": None, + "detail_type": "EC2 Instance State-change Notification V2", + "source": "aws.ec2", + "resources": ["arn:aws:ec2:us-west-1:123456789012:instance/i-1234567890abcdef0"], + }, + ), + ( + "eventBridgeEvent.json", + { + "func": None, + "detail_type": "EC2 Instance State-change Notification", + "source": "aws.lambda", + "resources": ["arn:aws:ec2:us-west-1:123456789012:instance/i-1234567890abcdef0"], + }, + ), + ( + "eventBridgeEvent.json", + { + "func": None, + "detail_type": "EC2 Instance State-change Notification", + "source": "aws.ec2", + "resources": ["arn:aws:ec2:us-west-1:123456789012:instance/i-9999999999abcdef9"], + }, + ), + # eventBridgeEvent.json, not match (detail type and source) + ( + "eventBridgeEvent.json", + { + "func": None, + "detail_type": "EC2 Instance State-change Notification V2", + "source": "aws.lambda", + }, + ), + ( + "eventBridgeEvent.json", + { + "func": None, + "detail_type": "EC2 Instance State-change Notification V2", + "source": "aws.ec2", + }, + ), + ( + "eventBridgeEvent.json", + { + "func": None, + "detail_type": "EC2 Instance State-change Notification", + "source": "aws.lambda", + }, + ), + # eventBridgeEvent.json, not match (detail type and resources) + ( + "eventBridgeEvent.json", + { + "func": None, + "detail_type": "EC2 Instance State-change Notification V2", + "resources": ["arn:aws:ec2:us-west-1:123456789012:instance/i-9999999999abcdef9"], + }, + ), + ( + "eventBridgeEvent.json", + { + "func": None, + "detail_type": "EC2 Instance State-change Notification V2", + "resources": ["arn:aws:ec2:us-west-1:123456789012:instance/i-1234567890abcdef0"], + }, + ), + ( + "eventBridgeEvent.json", + { + "func": None, + "detail_type": "EC2 Instance State-change Notification", + "resources": ["arn:aws:ec2:us-west-1:123456789012:instance/i-9999999999abcdef9"], + }, + ), + # eventBridgeEvent.json, not match (source and resources arguments) + ( + "eventBridgeEvent.json", + { + "func": None, + "source": "aws.lambda", + "resources": ["arn:aws:ec2:us-west-1:123456789012:instance/i-9999999999abcdef9"], + }, + ), + ( + "eventBridgeEvent.json", + { + "func": None, + "source": "aws.lambda", + "resources": ["arn:aws:ec2:us-west-1:123456789012:instance/i-1234567890abcdef0"], + }, + ), + ( + "eventBridgeEvent.json", + { + "func": None, + "source": "aws.ec2", + "resources": ["arn:aws:ec2:us-west-1:123456789012:instance/i-9999999999abcdef9"], + }, + ), + # eventBridgeEvent.json, not match detail_type + ("eventBridgeEvent.json", {"func": None, "detail_type": "EC2 Instance State-change Notification V2"}), + # eventBridgeEvent.json, not match source + ("eventBridgeEvent.json", {"func": None, "source": "aws.lambda"}), + # eventBridgeEvent.json, not match resources + ( + "eventBridgeEvent.json", + { + "func": None, + "resources": ["arn:aws:ec2:us-west-1:123456789012:instance/i-9999999999abcdef9"], + }, + ), + ], + ) + def test_match_false(self, event_name, option_constructor): + event = load_event(file_name=event_name) + route = EventBridgeRoute(**option_constructor) + actual = route.match(event=event) + assert actual is None