diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4379b43b3bb..9db2d5a0675 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -119,6 +119,128 @@
* **docs:** enable privacy plugin in docs ([#6036](https://github.com/aws-powertools/powertools-lambda-python/issues/6036))
+
+## [v3.5.0] - 2025-01-28
+## Bug Fixes
+
+* **security:** fix encryption_context handling in data masking operations ([#6074](https://github.com/aws-powertools/powertools-lambda-python/issues/6074))
+
+## Documentation
+
+* **roadmap:** update roadmap ([#6077](https://github.com/aws-powertools/powertools-lambda-python/issues/6077))
+
+## Maintenance
+
+* **ci:** new pre-release 3.6.1a0 ([#6084](https://github.com/aws-powertools/powertools-lambda-python/issues/6084))
+* **deps:** bump aws-actions/configure-aws-credentials from 4.0.3 to 4.1.0 ([#6082](https://github.com/aws-powertools/powertools-lambda-python/issues/6082))
+* **deps-dev:** bump coverage from 7.6.11 to 7.6.12 ([#6080](https://github.com/aws-powertools/powertools-lambda-python/issues/6080))
+* **deps-dev:** bump mkdocstrings-python from 1.14.6 to 1.15.0 ([#6079](https://github.com/aws-powertools/powertools-lambda-python/issues/6079))
+* **deps-dev:** bump boto3-stubs from 1.36.16 to 1.36.17 ([#6078](https://github.com/aws-powertools/powertools-lambda-python/issues/6078))
+
+
+
+## [v3.6.0] - 2025-02-11
+## Bug Fixes
+
+* **docs:** typo in a service name in Event Handler ([#5944](https://github.com/aws-powertools/powertools-lambda-python/issues/5944))
+* **logger:** child logger must respect log level ([#5950](https://github.com/aws-powertools/powertools-lambda-python/issues/5950))
+
+## Code Refactoring
+
+* **metrics:** Improve type annotations for metrics decorator ([#6000](https://github.com/aws-powertools/powertools-lambda-python/issues/6000))
+
+## Documentation
+
+* **api:** migrating the event handler utility to mkdocstrings ([#6023](https://github.com/aws-powertools/powertools-lambda-python/issues/6023))
+* **api:** migrating the metrics utility to mkdocstrings ([#6022](https://github.com/aws-powertools/powertools-lambda-python/issues/6022))
+* **api:** migrating the logger utility to mkdocstrings ([#6021](https://github.com/aws-powertools/powertools-lambda-python/issues/6021))
+* **api:** migrating the Middleware Factory utility to mkdocstrings ([#6019](https://github.com/aws-powertools/powertools-lambda-python/issues/6019))
+* **api:** migrating the tracer utility to mkdocstrings ([#6017](https://github.com/aws-powertools/powertools-lambda-python/issues/6017))
+* **api:** migrating the batch utility to mkdocstrings ([#6016](https://github.com/aws-powertools/powertools-lambda-python/issues/6016))
+* **api:** migrating the event source data classes utility to mkdocstrings ([#6015](https://github.com/aws-powertools/powertools-lambda-python/issues/6015))
+* **api:** migrating the data masking utility to mkdocstrings ([#6013](https://github.com/aws-powertools/powertools-lambda-python/issues/6013))
+* **api:** migrating the AppConfig utility to mkdocstrings ([#6008](https://github.com/aws-powertools/powertools-lambda-python/issues/6008))
+* **api:** migrating the idempotency utility to mkdocstrings ([#6007](https://github.com/aws-powertools/powertools-lambda-python/issues/6007))
+* **api:** migrating the jmespath utility to mkdocstrings ([#6006](https://github.com/aws-powertools/powertools-lambda-python/issues/6006))
+* **api:** migrating the parameters utility to mkdocstrings ([#6005](https://github.com/aws-powertools/powertools-lambda-python/issues/6005))
+* **api:** migrating the parser utility to mkdocstrings ([#6004](https://github.com/aws-powertools/powertools-lambda-python/issues/6004))
+* **api:** migrating the streaming utility to mkdocstrings ([#6003](https://github.com/aws-powertools/powertools-lambda-python/issues/6003))
+* **api:** migrating the typing utility to mkdocstrings ([#5996](https://github.com/aws-powertools/powertools-lambda-python/issues/5996))
+* **api:** migrating the validation utility to mkdocstrings ([#5972](https://github.com/aws-powertools/powertools-lambda-python/issues/5972))
+* **layer:** update layer version number - v3.5.0 ([#5952](https://github.com/aws-powertools/powertools-lambda-python/issues/5952))
+
+## Features
+
+* **data-masking:** add custom mask functionalities ([#5837](https://github.com/aws-powertools/powertools-lambda-python/issues/5837))
+* **event_source:** add class APIGatewayAuthorizerResponseWebSocket ([#6058](https://github.com/aws-powertools/powertools-lambda-python/issues/6058))
+* **logger:** add clear_state method ([#5956](https://github.com/aws-powertools/powertools-lambda-python/issues/5956))
+* **metrics:** disable metrics flush via environment variables ([#6046](https://github.com/aws-powertools/powertools-lambda-python/issues/6046))
+* **openapi:** enhance support for tuple return type validation ([#5997](https://github.com/aws-powertools/powertools-lambda-python/issues/5997))
+
+## Maintenance
+
+* version bump
+* **ci:** new pre-release 3.5.1a9 ([#6069](https://github.com/aws-powertools/powertools-lambda-python/issues/6069))
+* **ci:** new pre-release 3.5.1a0 ([#5945](https://github.com/aws-powertools/powertools-lambda-python/issues/5945))
+* **ci:** new pre-release 3.5.1a1 ([#5954](https://github.com/aws-powertools/powertools-lambda-python/issues/5954))
+* **ci:** new pre-release 3.5.1a8 ([#6061](https://github.com/aws-powertools/powertools-lambda-python/issues/6061))
+* **ci:** install & configure mkdocstrings plugin ([#5959](https://github.com/aws-powertools/powertools-lambda-python/issues/5959))
+* **ci:** new pre-release 3.5.1a2 ([#5970](https://github.com/aws-powertools/powertools-lambda-python/issues/5970))
+* **ci:** new pre-release 3.5.1a3 ([#5998](https://github.com/aws-powertools/powertools-lambda-python/issues/5998))
+* **ci:** new pre-release 3.5.1a7 ([#6044](https://github.com/aws-powertools/powertools-lambda-python/issues/6044))
+* **ci:** new pre-release 3.5.1a4 ([#6018](https://github.com/aws-powertools/powertools-lambda-python/issues/6018))
+* **ci:** remove pdoc3 library ([#6024](https://github.com/aws-powertools/powertools-lambda-python/issues/6024))
+* **ci:** new pre-release 3.5.1a5 ([#6026](https://github.com/aws-powertools/powertools-lambda-python/issues/6026))
+* **ci:** add new script to bump Lambda layer version ([#6001](https://github.com/aws-powertools/powertools-lambda-python/issues/6001))
+* **ci:** new pre-release 3.5.1a6 ([#6033](https://github.com/aws-powertools/powertools-lambda-python/issues/6033))
+* **deps:** bump squidfunk/mkdocs-material from `471695f` to `7e841df` in /docs ([#6012](https://github.com/aws-powertools/powertools-lambda-python/issues/6012))
+* **deps:** bump zgosalvez/github-actions-ensure-sha-pinned-actions from 3.0.20 to 3.0.21 ([#6064](https://github.com/aws-powertools/powertools-lambda-python/issues/6064))
+* **deps:** bump actions/setup-python from 5.3.0 to 5.4.0 ([#5960](https://github.com/aws-powertools/powertools-lambda-python/issues/5960))
+* **deps:** bump docker/setup-qemu-action from 3.2.0 to 3.3.0 ([#5961](https://github.com/aws-powertools/powertools-lambda-python/issues/5961))
+* **deps:** bump codecov/codecov-action from 5.1.2 to 5.3.1 ([#5964](https://github.com/aws-powertools/powertools-lambda-python/issues/5964))
+* **deps:** bump squidfunk/mkdocs-material from `7e841df` to `c62453b` in /docs ([#6052](https://github.com/aws-powertools/powertools-lambda-python/issues/6052))
+* **deps:** bump actions/setup-node from 4.1.0 to 4.2.0 ([#5963](https://github.com/aws-powertools/powertools-lambda-python/issues/5963))
+* **deps:** bump actions/upload-artifact from 4.5.0 to 4.6.0 ([#5962](https://github.com/aws-powertools/powertools-lambda-python/issues/5962))
+* **deps:** bump release-drafter/release-drafter from 6.0.0 to 6.1.0 ([#5976](https://github.com/aws-powertools/powertools-lambda-python/issues/5976))
+* **deps:** bump zgosalvez/github-actions-ensure-sha-pinned-actions from 3.0.18 to 3.0.20 ([#5977](https://github.com/aws-powertools/powertools-lambda-python/issues/5977))
+* **deps:** bump pypa/gh-action-pypi-publish from 1.12.3 to 1.12.4 ([#5980](https://github.com/aws-powertools/powertools-lambda-python/issues/5980))
+* **deps:** bump docker/setup-buildx-action from 3.8.0 to 3.9.0 ([#6042](https://github.com/aws-powertools/powertools-lambda-python/issues/6042))
+* **deps:** bump docker/setup-qemu-action from 3.3.0 to 3.4.0 ([#6043](https://github.com/aws-powertools/powertools-lambda-python/issues/6043))
+* **deps:** bump aws-actions/configure-aws-credentials from 4.0.2 to 4.0.3 ([#5975](https://github.com/aws-powertools/powertools-lambda-python/issues/5975))
+* **deps:** bump squidfunk/mkdocs-material from `41942f7` to `471695f` in /docs ([#5979](https://github.com/aws-powertools/powertools-lambda-python/issues/5979))
+* **deps:** bump actions/setup-go from 5.2.0 to 5.3.0 ([#5978](https://github.com/aws-powertools/powertools-lambda-python/issues/5978))
+* **deps-dev:** bump aws-cdk from 2.178.0 to 2.178.1 ([#6053](https://github.com/aws-powertools/powertools-lambda-python/issues/6053))
+* **deps-dev:** bump mkdocstrings-python from 1.13.0 to 1.14.2 ([#6011](https://github.com/aws-powertools/powertools-lambda-python/issues/6011))
+* **deps-dev:** bump mkdocs-material from 9.6.1 to 9.6.2 ([#6009](https://github.com/aws-powertools/powertools-lambda-python/issues/6009))
+* **deps-dev:** bump aws-cdk-lib from 2.178.0 to 2.178.1 ([#6047](https://github.com/aws-powertools/powertools-lambda-python/issues/6047))
+* **deps-dev:** bump aws-cdk-aws-lambda-python-alpha from 2.178.0a0 to 2.178.1a0 ([#6048](https://github.com/aws-powertools/powertools-lambda-python/issues/6048))
+* **deps-dev:** bump boto3-stubs from 1.36.14 to 1.36.15 ([#6049](https://github.com/aws-powertools/powertools-lambda-python/issues/6049))
+* **deps-dev:** bump boto3-stubs from 1.36.10 to 1.36.11 ([#6010](https://github.com/aws-powertools/powertools-lambda-python/issues/6010))
+* **deps-dev:** bump boto3-stubs from 1.36.10 to 1.36.12 ([#6014](https://github.com/aws-powertools/powertools-lambda-python/issues/6014))
+* **deps-dev:** bump ruff from 0.9.5 to 0.9.6 ([#6066](https://github.com/aws-powertools/powertools-lambda-python/issues/6066))
+* **deps-dev:** bump mkdocstrings-python from 1.14.2 to 1.14.4 ([#6025](https://github.com/aws-powertools/powertools-lambda-python/issues/6025))
+* **deps-dev:** bump aws-cdk-aws-lambda-python-alpha from 2.177.0a0 to 2.178.0a0 ([#6041](https://github.com/aws-powertools/powertools-lambda-python/issues/6041))
+* **deps-dev:** bump mkdocs-material from 9.5.50 to 9.6.1 ([#5966](https://github.com/aws-powertools/powertools-lambda-python/issues/5966))
+* **deps-dev:** bump black from 24.10.0 to 25.1.0 ([#5968](https://github.com/aws-powertools/powertools-lambda-python/issues/5968))
+* **deps-dev:** bump ruff from 0.9.3 to 0.9.4 ([#5969](https://github.com/aws-powertools/powertools-lambda-python/issues/5969))
+* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.291 to 0.1.292 ([#6051](https://github.com/aws-powertools/powertools-lambda-python/issues/6051))
+* **deps-dev:** bump cfn-lint from 1.22.7 to 1.23.1 ([#5967](https://github.com/aws-powertools/powertools-lambda-python/issues/5967))
+* **deps-dev:** bump mkdocstrings-python from 1.14.5 to 1.14.6 ([#6050](https://github.com/aws-powertools/powertools-lambda-python/issues/6050))
+* **deps-dev:** bump isort from 5.13.2 to 6.0.0 ([#5965](https://github.com/aws-powertools/powertools-lambda-python/issues/5965))
+* **deps-dev:** bump ruff from 0.9.4 to 0.9.5 ([#6039](https://github.com/aws-powertools/powertools-lambda-python/issues/6039))
+* **deps-dev:** bump aws-cdk-lib from 2.177.0 to 2.178.0 ([#6038](https://github.com/aws-powertools/powertools-lambda-python/issues/6038))
+* **deps-dev:** bump mypy from 1.14.1 to 1.15.0 ([#6028](https://github.com/aws-powertools/powertools-lambda-python/issues/6028))
+* **deps-dev:** bump mkdocstrings-python from 1.14.4 to 1.14.5 ([#6032](https://github.com/aws-powertools/powertools-lambda-python/issues/6032))
+* **deps-dev:** bump cfn-lint from 1.23.1 to 1.24.0 ([#6030](https://github.com/aws-powertools/powertools-lambda-python/issues/6030))
+* **deps-dev:** bump boto3-stubs from 1.36.14 to 1.36.16 ([#6057](https://github.com/aws-powertools/powertools-lambda-python/issues/6057))
+* **deps-dev:** bump cdklabs-generative-ai-cdk-constructs from 0.1.290 to 0.1.291 ([#6031](https://github.com/aws-powertools/powertools-lambda-python/issues/6031))
+* **deps-dev:** bump boto3-stubs from 1.36.12 to 1.36.14 ([#6029](https://github.com/aws-powertools/powertools-lambda-python/issues/6029))
+* **deps-dev:** bump mkdocs-material from 9.6.2 to 9.6.3 ([#6065](https://github.com/aws-powertools/powertools-lambda-python/issues/6065))
+* **deps-dev:** bump coverage from 7.6.10 to 7.6.11 ([#6067](https://github.com/aws-powertools/powertools-lambda-python/issues/6067))
+* **deps-dev:** bump aws-cdk from 2.177.0 to 2.178.0 ([#6040](https://github.com/aws-powertools/powertools-lambda-python/issues/6040))
+* **docs:** enable privacy plugin in docs ([#6036](https://github.com/aws-powertools/powertools-lambda-python/issues/6036))
+
+
## [v3.5.0] - 2025-01-28
## Bug Fixes
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..836fd6bdb13
--- /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:
+ return self.bucket == bucket
+ elif self.bucket_prefix:
+ return bucket.find(self.bucket_prefix) == 0
+ 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..88fc059bb93
--- /dev/null
+++ b/aws_lambda_powertools/event_handler/async_execution/routes/secrets_manager.py
@@ -0,0 +1,46 @@
+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_name_prefix:
+ part = secret_id.split(":")
+ secret_name = part[-1]
+ if self.secret_id == secret_id and secret_name.find(self.secret_name_prefix) == 0:
+ return self.func, SecretsManagerEvent(event)
+ elif self.secret_id:
+ if 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..b17065b8d59
--- /dev/null
+++ b/tests/unit/event_handler/_async_execution/_routes/test_aws_config_rule.py
@@ -0,0 +1,1080 @@
+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,
+ ),
+ ],
+ )
+ def test_match_with_aws_config_rule_event(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
+
+ @pytest.mark.parametrize(
+ "event_name",
+ [
+ "activeMQEvent.json",
+ "albEvent.json",
+ "albEventPathTrailingSlash.json",
+ "albMultiValueHeadersEvent.json",
+ "albMultiValueQueryStringEvent.json",
+ "apiGatewayAuthorizerRequestEvent.json",
+ "apiGatewayAuthorizerTokenEvent.json",
+ "apiGatewayAuthorizerV2Event.json",
+ "apiGatewayProxyEvent.json",
+ "apiGatewayProxyEventAnotherPath.json",
+ "apiGatewayProxyEventNoOrigin.json",
+ "apiGatewayProxyEventPathTrailingSlash.json",
+ "apiGatewayProxyEventPrincipalId.json",
+ "apiGatewayProxyEvent_noVersionAuth.json",
+ "apiGatewayProxyOtherEvent.json",
+ "apiGatewayProxyV2Event.json",
+ "apiGatewayProxyV2EventPathTrailingSlash.json",
+ "apiGatewayProxyV2Event_GET.json",
+ "apiGatewayProxyV2IamEvent.json",
+ "apiGatewayProxyV2LambdaAuthorizerEvent.json",
+ "apiGatewayProxyV2OtherGetEvent.json",
+ "apiGatewayProxyV2SchemaMiddlwareInvalidEvent.json",
+ "apiGatewayProxyV2SchemaMiddlwareValidEvent.json",
+ "apigatewayeSchemaMiddlwareInvalidEvent.json",
+ "apigatewayeSchemaMiddlwareValidEvent.json",
+ "appSyncAuthorizerEvent.json",
+ "appSyncAuthorizerResponse.json",
+ "appSyncBatchEvent.json",
+ "appSyncDirectResolver.json",
+ "appSyncResolverEvent.json",
+ "bedrockAgentEvent.json",
+ "bedrockAgentEventWithPathParams.json",
+ "bedrockAgentPostEvent.json",
+ "cloudWatchAlarmEventCompositeMetric.json",
+ "cloudWatchAlarmEventSingleMetric.json",
+ "cloudWatchDashboardEvent.json",
+ "cloudWatchLogEvent.json",
+ "cloudWatchLogEventWithPolicyLevel.json",
+ "cloudformationCustomResourceCreate.json",
+ "cloudformationCustomResourceDelete.json",
+ "cloudformationCustomResourceUpdate.json",
+ "codeDeployLifecycleHookEvent.json",
+ "codePipelineEvent.json",
+ "codePipelineEventData.json",
+ "codePipelineEventEmptyUserParameters.json",
+ "codePipelineEventWithEncryptionKey.json",
+ "cognitoCreateAuthChallengeEvent.json",
+ "cognitoCustomEmailSenderEvent.json",
+ "cognitoCustomMessageEvent.json",
+ "cognitoCustomSMSSenderEvent.json",
+ "cognitoDefineAuthChallengeEvent.json",
+ "cognitoPostAuthenticationEvent.json",
+ "cognitoPostConfirmationEvent.json",
+ "cognitoPreAuthenticationEvent.json",
+ "cognitoPreSignUpEvent.json",
+ "cognitoPreTokenGenerationEvent.json",
+ "cognitoPreTokenV2GenerationEvent.json",
+ "cognitoUserMigrationEvent.json",
+ "cognitoVerifyAuthChallengeResponseEvent.json",
+ "connectContactFlowEventAll.json",
+ "connectContactFlowEventMin.json",
+ "dynamoStreamEvent.json",
+ "eventBridgeEvent.json",
+ "kafkaEventMsk.json",
+ "kafkaEventSelfManaged.json",
+ "kinesisFirehoseKinesisEvent.json",
+ "kinesisFirehosePutEvent.json",
+ "kinesisFirehoseSQSEvent.json",
+ "kinesisStreamCloudWatchLogsEvent.json",
+ "kinesisStreamEvent.json",
+ "kinesisStreamEventOneRecord.json",
+ "lambdaFunctionUrlEvent.json",
+ "lambdaFunctionUrlEventPathTrailingSlash.json",
+ "lambdaFunctionUrlEventWithHeaders.json",
+ "lambdaFunctionUrlIAMEvent.json",
+ "rabbitMQEvent.json",
+ "s3BatchOperationEventSchemaV1.json",
+ "s3BatchOperationEventSchemaV2.json",
+ "s3Event.json",
+ "s3EventBridgeNotificationObjectCreatedEvent.json",
+ "s3EventBridgeNotificationObjectDeletedEvent.json",
+ "s3EventBridgeNotificationObjectExpiredEvent.json",
+ "s3EventBridgeNotificationObjectRestoreCompletedEvent.json",
+ "s3EventDecodedKey.json",
+ "s3EventDeleteObject.json",
+ "s3EventGlacier.json",
+ "s3ObjectEventIAMUser.json",
+ "s3ObjectEventTempCredentials.json",
+ "s3SqsEvent.json",
+ "secretsManagerEvent.json",
+ "sesEvent.json",
+ "snsEvent.json",
+ "snsSqsEvent.json",
+ "snsSqsFifoEvent.json",
+ "sqsDlqTriggerEvent.json",
+ "sqsEvent.json",
+ "vpcLatticeEvent.json",
+ "vpcLatticeEventPathTrailingSlash.json",
+ "vpcLatticeEventV2PathTrailingSlash.json",
+ "vpcLatticeV2Event.json",
+ "vpcLatticeV2EventWithHeaders.json",
+ ],
+ )
+ def test_match_for_not_aws_config_rule_event(self, event_name):
+ event = load_event(file_name=event_name)
+ route = AwsConfigRuleRoute(
+ func=None,
+ arn="arn:aws:config:us-east-1:0123456789012:config-rule/config-rule-i9y8j9",
+ )
+ actual = route.match(event=event)
+ 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..af6e44a9032
--- /dev/null
+++ b/tests/unit/event_handler/_async_execution/_routes/test_cloud_watch_alarm.py
@@ -0,0 +1,310 @@
+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",
+ [
+ # with alarm_name and alarm_name_prefix
+ # match all
+ (
+ {"func": None, "alarm_name": "CompositeDemo.Main", "alarm_name_prefix": "CompositeDemo.M"},
+ {"alarm_name": "CompositeDemo.Main"},
+ True,
+ ),
+ # with alarm_name and alarm_name_prefix
+ # match 1, unmatch 1
+ (
+ {"func": None, "alarm_name": "CompositeDemo.Main", "alarm_name_prefix": "ompositeDemo.M"},
+ {"alarm_name": "CompositeDemo.Main"},
+ True,
+ ),
+ (
+ {"func": None, "alarm_name": "CompositeDemo.MainV2", "alarm_name_prefix": "CompositeDemo.M"},
+ {"alarm_name": "CompositeDemo.Main"},
+ False,
+ ),
+ # with alarm_name and alarm_name_prefix
+ # unmatch all
+ (
+ {"func": None, "alarm_name": "CompositeDemo.MainV2", "alarm_name_prefix": "ompositeDemo.M"},
+ {"alarm_name": "CompositeDemo.Main"},
+ False,
+ ),
+ # with alarm_name
+ (
+ {
+ "func": None,
+ "alarm_name": "CompositeDemo.Main",
+ },
+ {"alarm_name": "CompositeDemo.Main"},
+ True,
+ ),
+ (
+ {
+ "func": None,
+ "alarm_name": "CompositeDemo.MainV2",
+ },
+ {"alarm_name": "CompositeDemo.Main"},
+ False,
+ ),
+ # with alarm_name_prefix
+ ({"func": None, "alarm_name_prefix": "CompositeDemo.M"}, {"alarm_name": "CompositeDemo.Main"}, True),
+ ({"func": None, "alarm_name_prefix": "ompositeDemo.M"}, {"alarm_name": "CompositeDemo.Main"}, False),
+ # without alarm_name and alarm_name_prefix
+ (
+ {"func": None, "arn": "arn:aws:cloudwatch:us-east-1:111122223333:alarm:SuppressionDemo.Main"},
+ {"alarm_name": "CompositeDemo.Main"},
+ False,
+ ),
+ # without alarm_name at option_func
+ ({"func": None, "alarm_name_prefix": "ompositeDemo.M"}, {"alarm_name": None}, 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, is_match",
+ [
+ # with arn and alarm_name
+ # match all
+ (
+ "cloudWatchAlarmEventCompositeMetric.json",
+ {
+ "func": lambda *_: None,
+ "arn": "arn:aws:cloudwatch:us-east-1:111122223333:alarm:SuppressionDemo.Main",
+ "alarm_name": "CompositeDemo.Main",
+ },
+ True,
+ ),
+ # with arn and alarm_name
+ # match 1, unmatch 1
+ (
+ "cloudWatchAlarmEventCompositeMetric.json",
+ {
+ "func": lambda *_: None,
+ "arn": "arn:aws:cloudwatch:us-east-1:111122223333:alarm:SuppressionDemo.MainV2",
+ "alarm_name": "CompositeDemo.Main",
+ },
+ False,
+ ),
+ (
+ "cloudWatchAlarmEventCompositeMetric.json",
+ {
+ "func": lambda *_: None,
+ "arn": "arn:aws:cloudwatch:us-east-1:111122223333:alarm:SuppressionDemo.Main",
+ "alarm_name": "CompositeDemo.MainV2",
+ },
+ False,
+ ),
+ # with arn and alarm_name
+ # unmatch all
+ (
+ "cloudWatchAlarmEventCompositeMetric.json",
+ {
+ "func": lambda *_: None,
+ "arn": "arn:aws:cloudwatch:us-east-1:111122223333:alarm:SuppressionDemo.MainV2",
+ "alarm_name": "CompositeDemo.MainV2",
+ },
+ False,
+ ),
+ # with arn
+ (
+ "cloudWatchAlarmEventCompositeMetric.json",
+ {
+ "func": lambda *_: None,
+ "arn": "arn:aws:cloudwatch:us-east-1:111122223333:alarm:SuppressionDemo.Main",
+ },
+ True,
+ ),
+ (
+ "cloudWatchAlarmEventCompositeMetric.json",
+ {
+ "func": lambda *_: None,
+ "arn": "arn:aws:cloudwatch:us-east-1:111122223333:alarm:SuppressionDemo.MainV2",
+ },
+ False,
+ ),
+ # with alarm_name
+ (
+ "cloudWatchAlarmEventCompositeMetric.json",
+ {"func": lambda *_: None, "alarm_name": "CompositeDemo.Main"},
+ True,
+ ),
+ (
+ "cloudWatchAlarmEventCompositeMetric.json",
+ {"func": lambda *_: None, "alarm_name": "CompositeDemo.MainV2"},
+ False,
+ ),
+ ],
+ )
+ def test_match_for_cloud_watch_alarm_event(self, event_name, option_constructor, is_match):
+ event = load_event(file_name=event_name)
+ route = CloudWatchAlarmRoute(**option_constructor)
+ actual = route.match(event=event)
+ if is_match:
+ expected = route.func, CloudWatchAlarmEvent(event)
+ assert actual == expected
+ else:
+ assert actual is None
+
+ @pytest.mark.parametrize(
+ "event_name",
+ [
+ "activeMQEvent.json",
+ "albEvent.json",
+ "albEventPathTrailingSlash.json",
+ "albMultiValueHeadersEvent.json",
+ "albMultiValueQueryStringEvent.json",
+ "apiGatewayAuthorizerRequestEvent.json",
+ "apiGatewayAuthorizerTokenEvent.json",
+ "apiGatewayAuthorizerV2Event.json",
+ "apiGatewayProxyEvent.json",
+ "apiGatewayProxyEventAnotherPath.json",
+ "apiGatewayProxyEventNoOrigin.json",
+ "apiGatewayProxyEventPathTrailingSlash.json",
+ "apiGatewayProxyEventPrincipalId.json",
+ "apiGatewayProxyEvent_noVersionAuth.json",
+ "apiGatewayProxyOtherEvent.json",
+ "apiGatewayProxyV2Event.json",
+ "apiGatewayProxyV2EventPathTrailingSlash.json",
+ "apiGatewayProxyV2Event_GET.json",
+ "apiGatewayProxyV2IamEvent.json",
+ "apiGatewayProxyV2LambdaAuthorizerEvent.json",
+ "apiGatewayProxyV2OtherGetEvent.json",
+ "apiGatewayProxyV2SchemaMiddlwareInvalidEvent.json",
+ "apiGatewayProxyV2SchemaMiddlwareValidEvent.json",
+ "apigatewayeSchemaMiddlwareInvalidEvent.json",
+ "apigatewayeSchemaMiddlwareValidEvent.json",
+ "appSyncAuthorizerEvent.json",
+ "appSyncAuthorizerResponse.json",
+ "appSyncBatchEvent.json",
+ "appSyncDirectResolver.json",
+ "appSyncResolverEvent.json",
+ "awsConfigRuleConfigurationChanged.json",
+ "awsConfigRuleOversizedConfiguration.json",
+ "awsConfigRuleScheduled.json",
+ "bedrockAgentEvent.json",
+ "bedrockAgentEventWithPathParams.json",
+ "bedrockAgentPostEvent.json",
+ "cloudWatchDashboardEvent.json",
+ "cloudWatchLogEvent.json",
+ "cloudWatchLogEventWithPolicyLevel.json",
+ "cloudformationCustomResourceCreate.json",
+ "cloudformationCustomResourceDelete.json",
+ "cloudformationCustomResourceUpdate.json",
+ "codeDeployLifecycleHookEvent.json",
+ "codePipelineEvent.json",
+ "codePipelineEventData.json",
+ "codePipelineEventEmptyUserParameters.json",
+ "codePipelineEventWithEncryptionKey.json",
+ "cognitoCreateAuthChallengeEvent.json",
+ "cognitoCustomEmailSenderEvent.json",
+ "cognitoCustomMessageEvent.json",
+ "cognitoCustomSMSSenderEvent.json",
+ "cognitoDefineAuthChallengeEvent.json",
+ "cognitoPostAuthenticationEvent.json",
+ "cognitoPostConfirmationEvent.json",
+ "cognitoPreAuthenticationEvent.json",
+ "cognitoPreSignUpEvent.json",
+ "cognitoPreTokenGenerationEvent.json",
+ "cognitoPreTokenV2GenerationEvent.json",
+ "cognitoUserMigrationEvent.json",
+ "cognitoVerifyAuthChallengeResponseEvent.json",
+ "connectContactFlowEventAll.json",
+ "connectContactFlowEventMin.json",
+ "dynamoStreamEvent.json",
+ "eventBridgeEvent.json",
+ "kafkaEventMsk.json",
+ "kafkaEventSelfManaged.json",
+ "kinesisFirehoseKinesisEvent.json",
+ "kinesisFirehosePutEvent.json",
+ "kinesisFirehoseSQSEvent.json",
+ "kinesisStreamCloudWatchLogsEvent.json",
+ "kinesisStreamEvent.json",
+ "kinesisStreamEventOneRecord.json",
+ "lambdaFunctionUrlEvent.json",
+ "lambdaFunctionUrlEventPathTrailingSlash.json",
+ "lambdaFunctionUrlEventWithHeaders.json",
+ "lambdaFunctionUrlIAMEvent.json",
+ "rabbitMQEvent.json",
+ "s3BatchOperationEventSchemaV1.json",
+ "s3BatchOperationEventSchemaV2.json",
+ "s3Event.json",
+ "s3EventBridgeNotificationObjectCreatedEvent.json",
+ "s3EventBridgeNotificationObjectDeletedEvent.json",
+ "s3EventBridgeNotificationObjectExpiredEvent.json",
+ "s3EventBridgeNotificationObjectRestoreCompletedEvent.json",
+ "s3EventDecodedKey.json",
+ "s3EventDeleteObject.json",
+ "s3EventGlacier.json",
+ "s3ObjectEventIAMUser.json",
+ "s3ObjectEventTempCredentials.json",
+ "s3SqsEvent.json",
+ "secretsManagerEvent.json",
+ "sesEvent.json",
+ "snsEvent.json",
+ "snsSqsEvent.json",
+ "snsSqsFifoEvent.json",
+ "sqsDlqTriggerEvent.json",
+ "sqsEvent.json",
+ "vpcLatticeEvent.json",
+ "vpcLatticeEventPathTrailingSlash.json",
+ "vpcLatticeEventV2PathTrailingSlash.json",
+ "vpcLatticeV2Event.json",
+ "vpcLatticeV2EventWithHeaders.json",
+ ],
+ )
+ def test_match_for_not_cloud_watch_alarm_event(self, event_name):
+ event = load_event(file_name=event_name)
+ route = CloudWatchAlarmRoute(
+ func=None,
+ arn="arn:aws:cloudwatch:us-east-1:111122223333:alarm:SuppressionDemo.Main",
+ )
+ 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..54a7cecf07a
--- /dev/null
+++ b/tests/unit/event_handler/_async_execution/_routes/test_cloud_watch_logs.py
@@ -0,0 +1,551 @@
+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",
+ [
+ # with log_group and log_group_prefix
+ # match all
+ (
+ {"func": None, "log_group": "testLogGroup", "log_group_prefix": "testL"},
+ {"log_group": "testLogGroup"},
+ True,
+ ),
+ # with log_group and log_group_prefix
+ # match 1, unmatch 1
+ (
+ {"func": None, "log_group": "testLogGroup", "log_group_prefix": "estL"},
+ {"log_group": "testLogGroup"},
+ True,
+ ),
+ (
+ {"func": None, "log_group": "testLogGroupV2", "log_group_prefix": "testL"},
+ {"log_group": "testLogGroup"},
+ False,
+ ),
+ # with log_group and log_group_prefix
+ # unmatch all
+ (
+ {"func": None, "log_group": "testLogGroupV2", "log_group_prefix": "estL"},
+ {"log_group": "testLogGroup"},
+ False,
+ ),
+ # with log_group
+ (
+ {
+ "func": None,
+ "log_group": "testLogGroup",
+ },
+ {"log_group": "testLogGroup"},
+ True,
+ ),
+ (
+ {
+ "func": None,
+ "log_group": "testLogGroupV2",
+ },
+ {"log_group": "testLogGroup"},
+ False,
+ ),
+ # with log_group_prefix
+ ({"func": None, "log_group_prefix": "testL"}, {"log_group": "testLogGroup"}, True),
+ ({"func": None, "log_group_prefix": "estL"}, {"log_group": "testLogGroup"}, False),
+ # without log_group and log_group_prefix
+ ({"func": None, "log_stream": "testLogStream"}, {"log_group": "testLogGroup"}, False),
+ # without log_group at option_func
+ ({"func": None, "log_group": "testLogGroup"}, {"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",
+ [
+ # with log_stream and log_stream_prefix
+ # match all
+ (
+ {"func": None, "log_stream": "testLogStream", "log_stream_prefix": "testL"},
+ {"log_stream": "testLogStream"},
+ True,
+ ),
+ # with log_stream and log_stream_prefix
+ # match 1, unmatch 1
+ (
+ {"func": None, "log_stream": "testLogStream", "log_stream_prefix": "estL"},
+ {"log_stream": "testLogStream"},
+ True,
+ ),
+ (
+ {"func": None, "log_stream": "testLogStreamV2", "log_stream_prefix": "testL"},
+ {"log_stream": "testLogStream"},
+ False,
+ ),
+ # with log_stream and log_stream_prefix
+ # unmatch all
+ (
+ {"func": None, "log_stream": "testLogStreamV2", "log_stream_prefix": "estL"},
+ {"log_stream": "testLogStream"},
+ False,
+ ),
+ # with log_stream
+ (
+ {
+ "func": None,
+ "log_stream": "testLogStream",
+ },
+ {"log_stream": "testLogStream"},
+ True,
+ ),
+ (
+ {
+ "func": None,
+ "log_stream": "testLogStreamV2",
+ },
+ {"log_stream": "testLogStream"},
+ False,
+ ),
+ # with log_stream_prefix
+ ({"func": None, "log_stream_prefix": "testL"}, {"log_stream": "testLogStream"}, True),
+ ({"func": None, "log_stream_prefix": "estL"}, {"log_stream": "testLogStream"}, False),
+ # without log_stream and log_stream_prefix
+ ({"func": None, "log_group": "testLogGroup"}, {"log_stream": "testLogStream"}, False),
+ # without log_stream at option_func
+ ({"func": None, "log_stream_prefix": "testL"}, {"log_stream": None}, 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",
+ [
+ # without subscription_filters at option_func
+ ({"func": None, "subscription_filters": ["test"]}, {"subscription_filters": None}, False),
+ # without subscription_filters
+ ({"func": None, "log_group": "test"}, {"subscription_filters": ["test"]}, False),
+ # with subscription_filters
+ # match
+ ({"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),
+ # with subscription_filters
+ # unmatch
+ ({"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, is_match",
+ [
+ # with log_group, log_stream, and subscription_filters
+ # match all
+ (
+ "cloudWatchLogEvent.json",
+ {
+ "func": lambda *_: None,
+ "log_group": "testLogGroup",
+ "log_stream": "testLogStream",
+ "subscription_filters": ["testFilter"],
+ },
+ True,
+ ),
+ # with log_group, log_stream, and subscription_filters
+ # match 2, unmatch 1
+ (
+ "cloudWatchLogEvent.json",
+ {
+ "func": lambda *_: None,
+ "log_group": "testLogGroup",
+ "log_stream": "testLogStream",
+ "subscription_filters": ["testFilterV2"],
+ },
+ False,
+ ),
+ (
+ "cloudWatchLogEvent.json",
+ {
+ "func": lambda *_: None,
+ "log_group": "testLogGroup",
+ "log_stream": "testLogStreamV2",
+ "subscription_filters": ["testFilter"],
+ },
+ False,
+ ),
+ (
+ "cloudWatchLogEvent.json",
+ {
+ "func": lambda *_: None,
+ "log_group": "testLogGroupV2",
+ "log_stream": "testLogStream",
+ "subscription_filters": ["testFilter"],
+ },
+ False,
+ ),
+ # with log_group, log_stream, and subscription_filters
+ # match 1, unmatch 2
+ (
+ "cloudWatchLogEvent.json",
+ {
+ "func": lambda *_: None,
+ "log_group": "testLogGroup",
+ "log_stream": "testLogStreamV2",
+ "subscription_filters": ["testFilterV2"],
+ },
+ False,
+ ),
+ (
+ "cloudWatchLogEvent.json",
+ {
+ "func": lambda *_: None,
+ "log_group": "testLogGroupV2",
+ "log_stream": "testLogStream",
+ "subscription_filters": ["testFilterV2"],
+ },
+ False,
+ ),
+ (
+ "cloudWatchLogEvent.json",
+ {
+ "func": lambda *_: None,
+ "log_group": "testLogGroupV2",
+ "log_stream": "testLogStreamV2",
+ "subscription_filters": ["testFilter"],
+ },
+ False,
+ ),
+ # with log_group, log_stream, and subscription_filters
+ # unmatch all
+ (
+ "cloudWatchLogEvent.json",
+ {
+ "func": lambda *_: None,
+ "log_group": "testLogGroupV2",
+ "log_stream": "testLogStreamV2",
+ "subscription_filters": ["testFilterV2"],
+ },
+ False,
+ ),
+ # with log_group and log_stream
+ # match all
+ (
+ "cloudWatchLogEvent.json",
+ {
+ "func": lambda *_: None,
+ "log_group": "testLogGroup",
+ "log_stream": "testLogStream",
+ },
+ True,
+ ),
+ # with log_group and log_stream
+ # match 1, unmatch 1
+ (
+ "cloudWatchLogEvent.json",
+ {
+ "func": lambda *_: None,
+ "log_group": "testLogGroup",
+ "log_stream": "testLogStreamV2",
+ },
+ False,
+ ),
+ (
+ "cloudWatchLogEvent.json",
+ {
+ "func": lambda *_: None,
+ "log_group": "testLogGroupV2",
+ "log_stream": "testLogStream",
+ },
+ False,
+ ),
+ # with log_group and log_stream
+ # unmatch all
+ (
+ "cloudWatchLogEvent.json",
+ {
+ "func": lambda *_: None,
+ "log_group": "testLogGroupV2",
+ "log_stream": "testLogStreamV2",
+ },
+ False,
+ ),
+ # with log_group and subscription_filters
+ # match all
+ (
+ "cloudWatchLogEvent.json",
+ {"func": lambda *_: None, "log_group": "testLogGroup", "subscription_filters": ["testFilter"]},
+ True,
+ ),
+ # with log_group and subscription_filters
+ # match 1, unmatch 1
+ (
+ "cloudWatchLogEvent.json",
+ {"func": lambda *_: None, "log_group": "testLogGroupV2", "subscription_filters": ["testFilter"]},
+ False,
+ ),
+ (
+ "cloudWatchLogEvent.json",
+ {"func": lambda *_: None, "log_group": "testLogGroup", "subscription_filters": ["testFilterV2"]},
+ False,
+ ),
+ # with log_group and subscription_filters
+ # unmatch all
+ (
+ "cloudWatchLogEvent.json",
+ {"func": lambda *_: None, "log_group": "testLogGroupV2", "subscription_filters": ["testFilterV2"]},
+ False,
+ ),
+ # with log_stream and subscription_filters
+ # match all
+ (
+ "cloudWatchLogEvent.json",
+ {"func": lambda *_: None, "log_stream": "testLogStream", "subscription_filters": ["testFilter"]},
+ True,
+ ),
+ # with log_stream and subscription_filters
+ # match 1, unmatch 1
+ (
+ "cloudWatchLogEvent.json",
+ {"func": lambda *_: None, "log_stream": "testLogStream", "subscription_filters": ["testFilterV2"]},
+ False,
+ ),
+ (
+ "cloudWatchLogEvent.json",
+ {"func": lambda *_: None, "log_stream": "testLogStreamV2", "subscription_filters": ["testFilter"]},
+ False,
+ ),
+ # with log_stream and subscription_filters
+ # unmatch all
+ (
+ "cloudWatchLogEvent.json",
+ {"func": lambda *_: None, "log_stream": "testLogStreamV2", "subscription_filters": ["testFilter"]},
+ False,
+ ),
+ # with log_group
+ ("cloudWatchLogEvent.json", {"func": lambda *_: None, "log_group": "testLogGroup"}, True),
+ ("cloudWatchLogEvent.json", {"func": lambda *_: None, "log_group": "testLogGroupV2"}, False),
+ # with log_stream
+ ("cloudWatchLogEvent.json", {"func": lambda *_: None, "log_stream": "testLogStream"}, True),
+ ("cloudWatchLogEvent.json", {"func": lambda *_: None, "log_stream": "testLogStreamV2"}, False),
+ # with subscription_filters
+ ("cloudWatchLogEvent.json", {"func": lambda *_: None, "subscription_filters": ["testFilter"]}, True),
+ ("cloudWatchLogEvent.json", {"func": lambda *_: None, "subscription_filters": ["testFilterV2"]}, False),
+ ],
+ )
+ def test_match_for_cloud_watch_logs_event(self, event_name, option_constructor, is_match):
+ event = load_event(file_name=event_name)
+ route = CloudWatchLogsRoute(**option_constructor)
+ actual = route.match(event=event)
+ if is_match:
+ expected = route.func, CloudWatchLogsEvent(event)
+ assert actual == expected
+ else:
+ assert actual is None
+
+ @pytest.mark.parametrize(
+ "event_name",
+ [
+ "activeMQEvent.json",
+ "albEvent.json",
+ "albEventPathTrailingSlash.json",
+ "albMultiValueHeadersEvent.json",
+ "albMultiValueQueryStringEvent.json",
+ "apiGatewayAuthorizerRequestEvent.json",
+ "apiGatewayAuthorizerTokenEvent.json",
+ "apiGatewayAuthorizerV2Event.json",
+ "apiGatewayProxyEvent.json",
+ "apiGatewayProxyEventAnotherPath.json",
+ "apiGatewayProxyEventNoOrigin.json",
+ "apiGatewayProxyEventPathTrailingSlash.json",
+ "apiGatewayProxyEventPrincipalId.json",
+ "apiGatewayProxyEvent_noVersionAuth.json",
+ "apiGatewayProxyOtherEvent.json",
+ "apiGatewayProxyV2Event.json",
+ "apiGatewayProxyV2EventPathTrailingSlash.json",
+ "apiGatewayProxyV2Event_GET.json",
+ "apiGatewayProxyV2IamEvent.json",
+ "apiGatewayProxyV2LambdaAuthorizerEvent.json",
+ "apiGatewayProxyV2OtherGetEvent.json",
+ "apiGatewayProxyV2SchemaMiddlwareInvalidEvent.json",
+ "apiGatewayProxyV2SchemaMiddlwareValidEvent.json",
+ "apigatewayeSchemaMiddlwareInvalidEvent.json",
+ "apigatewayeSchemaMiddlwareValidEvent.json",
+ "appSyncAuthorizerEvent.json",
+ "appSyncAuthorizerResponse.json",
+ "appSyncBatchEvent.json",
+ "appSyncDirectResolver.json",
+ "appSyncResolverEvent.json",
+ "awsConfigRuleConfigurationChanged.json",
+ "awsConfigRuleOversizedConfiguration.json",
+ "awsConfigRuleScheduled.json",
+ "bedrockAgentEvent.json",
+ "bedrockAgentEventWithPathParams.json",
+ "bedrockAgentPostEvent.json",
+ "cloudWatchAlarmEventCompositeMetric.json",
+ "cloudWatchAlarmEventSingleMetric.json",
+ "cloudWatchDashboardEvent.json",
+ "cloudformationCustomResourceCreate.json",
+ "cloudformationCustomResourceDelete.json",
+ "cloudformationCustomResourceUpdate.json",
+ "codeDeployLifecycleHookEvent.json",
+ "codePipelineEvent.json",
+ "codePipelineEventData.json",
+ "codePipelineEventEmptyUserParameters.json",
+ "codePipelineEventWithEncryptionKey.json",
+ "cognitoCreateAuthChallengeEvent.json",
+ "cognitoCustomEmailSenderEvent.json",
+ "cognitoCustomMessageEvent.json",
+ "cognitoCustomSMSSenderEvent.json",
+ "cognitoDefineAuthChallengeEvent.json",
+ "cognitoPostAuthenticationEvent.json",
+ "cognitoPostConfirmationEvent.json",
+ "cognitoPreAuthenticationEvent.json",
+ "cognitoPreSignUpEvent.json",
+ "cognitoPreTokenGenerationEvent.json",
+ "cognitoPreTokenV2GenerationEvent.json",
+ "cognitoUserMigrationEvent.json",
+ "cognitoVerifyAuthChallengeResponseEvent.json",
+ "connectContactFlowEventAll.json",
+ "connectContactFlowEventMin.json",
+ "dynamoStreamEvent.json",
+ "eventBridgeEvent.json",
+ "kafkaEventMsk.json",
+ "kafkaEventSelfManaged.json",
+ "kinesisFirehoseKinesisEvent.json",
+ "kinesisFirehosePutEvent.json",
+ "kinesisFirehoseSQSEvent.json",
+ "kinesisStreamCloudWatchLogsEvent.json",
+ "kinesisStreamEvent.json",
+ "kinesisStreamEventOneRecord.json",
+ "lambdaFunctionUrlEvent.json",
+ "lambdaFunctionUrlEventPathTrailingSlash.json",
+ "lambdaFunctionUrlEventWithHeaders.json",
+ "lambdaFunctionUrlIAMEvent.json",
+ "rabbitMQEvent.json",
+ "s3BatchOperationEventSchemaV1.json",
+ "s3BatchOperationEventSchemaV2.json",
+ "s3Event.json",
+ "s3EventBridgeNotificationObjectCreatedEvent.json",
+ "s3EventBridgeNotificationObjectDeletedEvent.json",
+ "s3EventBridgeNotificationObjectExpiredEvent.json",
+ "s3EventBridgeNotificationObjectRestoreCompletedEvent.json",
+ "s3EventDecodedKey.json",
+ "s3EventDeleteObject.json",
+ "s3EventGlacier.json",
+ "s3ObjectEventIAMUser.json",
+ "s3ObjectEventTempCredentials.json",
+ "s3SqsEvent.json",
+ "secretsManagerEvent.json",
+ "sesEvent.json",
+ "snsEvent.json",
+ "snsSqsEvent.json",
+ "snsSqsFifoEvent.json",
+ "sqsDlqTriggerEvent.json",
+ "sqsEvent.json",
+ "vpcLatticeEvent.json",
+ "vpcLatticeEventPathTrailingSlash.json",
+ "vpcLatticeEventV2PathTrailingSlash.json",
+ "vpcLatticeV2Event.json",
+ "vpcLatticeV2EventWithHeaders.json",
+ ],
+ )
+ def test_match_for_not_cloud_watch_logs_event(self, event_name):
+ event = load_event(file_name=event_name)
+ route = CloudWatchLogsRoute(func=None, log_group="testLogGroup")
+ 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..9b561e4bd4f
--- /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",
+ [
+ "activeMQEvent.json",
+ "albEvent.json",
+ "albEventPathTrailingSlash.json",
+ "albMultiValueHeadersEvent.json",
+ "albMultiValueQueryStringEvent.json",
+ "apiGatewayAuthorizerRequestEvent.json",
+ "apiGatewayAuthorizerTokenEvent.json",
+ "apiGatewayAuthorizerV2Event.json",
+ "apiGatewayProxyEvent.json",
+ "apiGatewayProxyEventAnotherPath.json",
+ "apiGatewayProxyEventNoOrigin.json",
+ "apiGatewayProxyEventPathTrailingSlash.json",
+ "apiGatewayProxyEventPrincipalId.json",
+ "apiGatewayProxyEvent_noVersionAuth.json",
+ "apiGatewayProxyOtherEvent.json",
+ "apiGatewayProxyV2Event.json",
+ "apiGatewayProxyV2EventPathTrailingSlash.json",
+ "apiGatewayProxyV2Event_GET.json",
+ "apiGatewayProxyV2IamEvent.json",
+ "apiGatewayProxyV2LambdaAuthorizerEvent.json",
+ "apiGatewayProxyV2OtherGetEvent.json",
+ "apiGatewayProxyV2SchemaMiddlwareInvalidEvent.json",
+ "apiGatewayProxyV2SchemaMiddlwareValidEvent.json",
+ "apigatewayeSchemaMiddlwareInvalidEvent.json",
+ "apigatewayeSchemaMiddlwareValidEvent.json",
+ "appSyncAuthorizerEvent.json",
+ "appSyncAuthorizerResponse.json",
+ "appSyncBatchEvent.json",
+ "appSyncDirectResolver.json",
+ "appSyncResolverEvent.json",
+ "awsConfigRuleConfigurationChanged.json",
+ "awsConfigRuleOversizedConfiguration.json",
+ "awsConfigRuleScheduled.json",
+ "bedrockAgentEvent.json",
+ "bedrockAgentEventWithPathParams.json",
+ "bedrockAgentPostEvent.json",
+ "cloudWatchAlarmEventCompositeMetric.json",
+ "cloudWatchAlarmEventSingleMetric.json",
+ "cloudWatchDashboardEvent.json",
+ "cloudWatchLogEvent.json",
+ "cloudWatchLogEventWithPolicyLevel.json",
+ "cloudformationCustomResourceCreate.json",
+ "cloudformationCustomResourceDelete.json",
+ "cloudformationCustomResourceUpdate.json",
+ "codePipelineEvent.json",
+ "codePipelineEventData.json",
+ "codePipelineEventEmptyUserParameters.json",
+ "codePipelineEventWithEncryptionKey.json",
+ "cognitoCreateAuthChallengeEvent.json",
+ "cognitoCustomEmailSenderEvent.json",
+ "cognitoCustomMessageEvent.json",
+ "cognitoCustomSMSSenderEvent.json",
+ "cognitoDefineAuthChallengeEvent.json",
+ "cognitoPostAuthenticationEvent.json",
+ "cognitoPostConfirmationEvent.json",
+ "cognitoPreAuthenticationEvent.json",
+ "cognitoPreSignUpEvent.json",
+ "cognitoPreTokenGenerationEvent.json",
+ "cognitoPreTokenV2GenerationEvent.json",
+ "cognitoUserMigrationEvent.json",
+ "cognitoVerifyAuthChallengeResponseEvent.json",
+ "connectContactFlowEventAll.json",
+ "connectContactFlowEventMin.json",
+ "dynamoStreamEvent.json",
+ "eventBridgeEvent.json",
+ "kafkaEventMsk.json",
+ "kafkaEventSelfManaged.json",
+ "kinesisFirehoseKinesisEvent.json",
+ "kinesisFirehosePutEvent.json",
+ "kinesisFirehoseSQSEvent.json",
+ "kinesisStreamCloudWatchLogsEvent.json",
+ "kinesisStreamEvent.json",
+ "kinesisStreamEventOneRecord.json",
+ "lambdaFunctionUrlEvent.json",
+ "lambdaFunctionUrlEventPathTrailingSlash.json",
+ "lambdaFunctionUrlEventWithHeaders.json",
+ "lambdaFunctionUrlIAMEvent.json",
+ "rabbitMQEvent.json",
+ "s3BatchOperationEventSchemaV1.json",
+ "s3BatchOperationEventSchemaV2.json",
+ "s3Event.json",
+ "s3EventBridgeNotificationObjectCreatedEvent.json",
+ "s3EventBridgeNotificationObjectDeletedEvent.json",
+ "s3EventBridgeNotificationObjectExpiredEvent.json",
+ "s3EventBridgeNotificationObjectRestoreCompletedEvent.json",
+ "s3EventDecodedKey.json",
+ "s3EventDeleteObject.json",
+ "s3EventGlacier.json",
+ "s3ObjectEventIAMUser.json",
+ "s3ObjectEventTempCredentials.json",
+ "s3SqsEvent.json",
+ "secretsManagerEvent.json",
+ "sesEvent.json",
+ "snsEvent.json",
+ "snsSqsEvent.json",
+ "snsSqsFifoEvent.json",
+ "sqsDlqTriggerEvent.json",
+ "sqsEvent.json",
+ "vpcLatticeEvent.json",
+ "vpcLatticeEventPathTrailingSlash.json",
+ "vpcLatticeEventV2PathTrailingSlash.json",
+ "vpcLatticeV2Event.json",
+ "vpcLatticeV2EventWithHeaders.json",
+ ],
+ )
+ def test_match_for_not_code_deploy_lifecycle_hook_event(self, event_name):
+ event = load_event(file_name=event_name)
+ route = CodeDeployLifecycleHookRoute(func=None)
+ 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..a396f174d82
--- /dev/null
+++ b/tests/unit/event_handler/_async_execution/_routes/test_event_bridge.py
@@ -0,0 +1,539 @@
+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_func, expected",
+ [
+ # without detail_type at option_func
+ ({"func": None, "detail_type": "test type"}, {"detail_type": None}, False),
+ # without detail_type
+ ({"func": None, "source": "aws.ec2"}, {"detail_type": "test type"}, False),
+ # with detail_type
+ ({"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_func, expected):
+ route = EventBridgeRoute(**option_constructor)
+ actual = route.is_target_with_detail_type(**option_func)
+ assert actual == expected
+
+ @pytest.mark.parametrize(
+ "option_constructor, option_func, expected",
+ [
+ # without source at option_func
+ ({"func": None, "source": "aws.ec2"}, {"source": None}, False),
+ # without source
+ ({"func": None, "detail_type": "test type"}, {"source": "aws.ec2"}, False),
+ # with source
+ ({"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_func, expected):
+ route = EventBridgeRoute(**option_constructor)
+ actual = route.is_target_with_source(**option_func)
+ assert actual == expected
+
+ @pytest.mark.parametrize(
+ "option_constructor, option_func, expected",
+ [
+ # without resources at option_func
+ (
+ {"func": None, "resources": ["arn:aws:ec2:us-west-1:123456789012:instance/i-1234567890abcdef0"]},
+ {"resources": None},
+ False,
+ ),
+ # without resources
+ (
+ {"func": None, "source": "aws.ec2"},
+ {"resources": ["arn:aws:ec2:us-west-1:123456789012:instance/i-9999999999abcdef9"]},
+ False,
+ ),
+ # with resources
+ (
+ {"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_func, expected):
+ route = EventBridgeRoute(**option_constructor)
+ actual = route.is_target_with_resources(**option_func)
+ assert actual == expected
+
+ @pytest.mark.parametrize(
+ "event_name, option_constructor, is_match",
+ [
+ # with detail_type, source, resources
+ # match all
+ (
+ "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"],
+ },
+ True,
+ ),
+ # with detail_type, source, resources
+ # match 2, unmatch 1
+ (
+ "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-99999999999999999"],
+ },
+ False,
+ ),
+ (
+ "eventBridgeEvent.json",
+ {
+ "func": lambda *_: None,
+ "detail_type": "EC2 Instance State-change Notification",
+ "source": "aws.lambda",
+ "resources": ["arn:aws:ec2:us-west-1:123456789012:instance/i-1234567890abcdef0"],
+ },
+ False,
+ ),
+ (
+ "eventBridgeEvent.json",
+ {
+ "func": lambda *_: None,
+ "detail_type": "EC2 Instance State-change Notification V2",
+ "source": "aws.ec2",
+ "resources": ["arn:aws:ec2:us-west-1:123456789012:instance/i-1234567890abcdef0"],
+ },
+ False,
+ ),
+ # with detail_type, source, resources
+ # match 1, unmatch 2
+ (
+ "eventBridgeEvent.json",
+ {
+ "func": lambda *_: None,
+ "detail_type": "EC2 Instance State-change Notification",
+ "source": "aws.lambda",
+ "resources": ["arn:aws:ec2:us-west-1:123456789012:instance/i-99999999999999999"],
+ },
+ False,
+ ),
+ (
+ "eventBridgeEvent.json",
+ {
+ "func": lambda *_: None,
+ "detail_type": "EC2 Instance State-change Notification V2",
+ "source": "aws.ec2",
+ "resources": ["arn:aws:ec2:us-west-1:123456789012:instance/i-99999999999999999"],
+ },
+ False,
+ ),
+ (
+ "eventBridgeEvent.json",
+ {
+ "func": lambda *_: None,
+ "detail_type": "EC2 Instance State-change Notification V2",
+ "source": "aws.lambda",
+ "resources": ["arn:aws:ec2:us-west-1:123456789012:instance/i-1234567890abcdef0"],
+ },
+ False,
+ ),
+ # with detail_type, source, resources
+ # unmatch all
+ (
+ "eventBridgeEvent.json",
+ {
+ "func": lambda *_: None,
+ "detail_type": "EC2 Instance State-change Notification V2",
+ "source": "aws.lambda",
+ "resources": ["arn:aws:ec2:us-west-1:123456789012:instance/i-99999999999999999"],
+ },
+ False,
+ ),
+ # with detail_type and source
+ # match all
+ (
+ "eventBridgeEvent.json",
+ {
+ "func": lambda *_: None,
+ "detail_type": "EC2 Instance State-change Notification",
+ "source": "aws.ec2",
+ },
+ True,
+ ),
+ # with detail_type and source
+ # match 1, unmatch 1
+ (
+ "eventBridgeEvent.json",
+ {
+ "func": lambda *_: None,
+ "detail_type": "EC2 Instance State-change Notification",
+ "source": "aws.lambda",
+ },
+ False,
+ ),
+ (
+ "eventBridgeEvent.json",
+ {
+ "func": lambda *_: None,
+ "detail_type": "EC2 Instance State-change Notification V2",
+ "source": "aws.ec2",
+ },
+ False,
+ ),
+ # with detail_type and source
+ # unmatch all
+ (
+ "eventBridgeEvent.json",
+ {
+ "func": lambda *_: None,
+ "detail_type": "EC2 Instance State-change Notification V2",
+ "source": "aws.lambda",
+ },
+ False,
+ ),
+ # with detail_type and resources
+ # match all
+ (
+ "eventBridgeEvent.json",
+ {
+ "func": lambda *_: None,
+ "detail_type": "EC2 Instance State-change Notification",
+ "resources": ["arn:aws:ec2:us-west-1:123456789012:instance/i-1234567890abcdef0"],
+ },
+ True,
+ ),
+ # with detail_type and resources
+ # match 1, unmatch 1
+ (
+ "eventBridgeEvent.json",
+ {
+ "func": lambda *_: None,
+ "detail_type": "EC2 Instance State-change Notification",
+ "resources": ["arn:aws:ec2:us-west-1:123456789012:instance/i-99999999999999999"],
+ },
+ False,
+ ),
+ (
+ "eventBridgeEvent.json",
+ {
+ "func": lambda *_: None,
+ "detail_type": "EC2 Instance State-change Notification V2",
+ "resources": ["arn:aws:ec2:us-west-1:123456789012:instance/i-1234567890abcdef0"],
+ },
+ False,
+ ),
+ # with detail_type and resources
+ # unmatch all
+ (
+ "eventBridgeEvent.json",
+ {
+ "func": lambda *_: None,
+ "detail_type": "EC2 Instance State-change Notification V2",
+ "resources": ["arn:aws:ec2:us-west-1:123456789012:instance/i-99999999999999999"],
+ },
+ False,
+ ),
+ # with source and resources
+ # match all
+ (
+ "eventBridgeEvent.json",
+ {
+ "func": lambda *_: None,
+ "source": "aws.ec2",
+ "resources": ["arn:aws:ec2:us-west-1:123456789012:instance/i-1234567890abcdef0"],
+ },
+ True,
+ ),
+ # with source and resources
+ # match 1, unmatch 1
+ (
+ "eventBridgeEvent.json",
+ {
+ "func": lambda *_: None,
+ "source": "aws.ec2",
+ "resources": ["arn:aws:ec2:us-west-1:123456789012:instance/i-99999999999999999"],
+ },
+ False,
+ ),
+ (
+ "eventBridgeEvent.json",
+ {
+ "func": lambda *_: None,
+ "source": "aws.lambda",
+ "resources": ["arn:aws:ec2:us-west-1:123456789012:instance/i-1234567890abcdef0"],
+ },
+ False,
+ ),
+ # with source and resources
+ # unmatch all
+ (
+ "eventBridgeEvent.json",
+ {
+ "func": lambda *_: None,
+ "source": "aws.lambda",
+ "resources": ["arn:aws:ec2:us-west-1:123456789012:instance/i-99999999999999999"],
+ },
+ False,
+ ),
+ # with detail_type
+ (
+ "eventBridgeEvent.json",
+ {
+ "func": lambda *_: None,
+ "detail_type": "EC2 Instance State-change Notification",
+ },
+ True,
+ ),
+ (
+ "eventBridgeEvent.json",
+ {
+ "func": lambda *_: None,
+ "detail_type": "EC2 Instance State-change Notification V2",
+ },
+ False,
+ ),
+ # with source
+ (
+ "eventBridgeEvent.json",
+ {
+ "func": lambda *_: None,
+ "source": "aws.ec2",
+ },
+ True,
+ ),
+ (
+ "eventBridgeEvent.json",
+ {
+ "func": lambda *_: None,
+ "source": "aws.lambda",
+ },
+ False,
+ ),
+ # with resources
+ (
+ "eventBridgeEvent.json",
+ {
+ "func": lambda *_: None,
+ "resources": ["arn:aws:ec2:us-west-1:123456789012:instance/i-1234567890abcdef0"],
+ },
+ True,
+ ),
+ (
+ "eventBridgeEvent.json",
+ {
+ "func": lambda *_: None,
+ "resources": ["arn:aws:ec2:us-west-1:123456789012:instance/i-99999999999999999"],
+ },
+ False,
+ ),
+ ],
+ )
+ def test_match_for_event_bridge_event(self, event_name, option_constructor, is_match):
+ event = load_event(file_name=event_name)
+ route = EventBridgeRoute(**option_constructor)
+ actual = route.match(event=event)
+ if is_match:
+ expected = route.func, EventBridgeEvent(event)
+ assert actual == expected
+ else:
+ assert actual is None
+
+ @pytest.mark.parametrize(
+ "event_name",
+ [
+ "activeMQEvent.json",
+ "albEvent.json",
+ "albEventPathTrailingSlash.json",
+ "albMultiValueHeadersEvent.json",
+ "albMultiValueQueryStringEvent.json",
+ "apiGatewayAuthorizerRequestEvent.json",
+ "apiGatewayAuthorizerTokenEvent.json",
+ "apiGatewayAuthorizerV2Event.json",
+ "apiGatewayProxyEvent.json",
+ "apiGatewayProxyEventAnotherPath.json",
+ "apiGatewayProxyEventNoOrigin.json",
+ "apiGatewayProxyEventPathTrailingSlash.json",
+ "apiGatewayProxyEventPrincipalId.json",
+ "apiGatewayProxyEvent_noVersionAuth.json",
+ "apiGatewayProxyOtherEvent.json",
+ "apiGatewayProxyV2Event.json",
+ "apiGatewayProxyV2EventPathTrailingSlash.json",
+ "apiGatewayProxyV2Event_GET.json",
+ "apiGatewayProxyV2IamEvent.json",
+ "apiGatewayProxyV2LambdaAuthorizerEvent.json",
+ "apiGatewayProxyV2OtherGetEvent.json",
+ "apiGatewayProxyV2SchemaMiddlwareInvalidEvent.json",
+ "apiGatewayProxyV2SchemaMiddlwareValidEvent.json",
+ "apigatewayeSchemaMiddlwareInvalidEvent.json",
+ "apigatewayeSchemaMiddlwareValidEvent.json",
+ "appSyncAuthorizerEvent.json",
+ "appSyncAuthorizerResponse.json",
+ "appSyncBatchEvent.json",
+ "appSyncDirectResolver.json",
+ "appSyncResolverEvent.json",
+ "awsConfigRuleConfigurationChanged.json",
+ "awsConfigRuleOversizedConfiguration.json",
+ "awsConfigRuleScheduled.json",
+ "bedrockAgentEvent.json",
+ "bedrockAgentEventWithPathParams.json",
+ "bedrockAgentPostEvent.json",
+ "cloudWatchAlarmEventCompositeMetric.json",
+ "cloudWatchAlarmEventSingleMetric.json",
+ "cloudWatchDashboardEvent.json",
+ "cloudWatchLogEvent.json",
+ "cloudWatchLogEventWithPolicyLevel.json",
+ "cloudformationCustomResourceCreate.json",
+ "cloudformationCustomResourceDelete.json",
+ "cloudformationCustomResourceUpdate.json",
+ "codeDeployLifecycleHookEvent.json",
+ "codePipelineEvent.json",
+ "codePipelineEventData.json",
+ "codePipelineEventEmptyUserParameters.json",
+ "codePipelineEventWithEncryptionKey.json",
+ "cognitoCreateAuthChallengeEvent.json",
+ "cognitoCustomEmailSenderEvent.json",
+ "cognitoCustomMessageEvent.json",
+ "cognitoCustomSMSSenderEvent.json",
+ "cognitoDefineAuthChallengeEvent.json",
+ "cognitoPostAuthenticationEvent.json",
+ "cognitoPostConfirmationEvent.json",
+ "cognitoPreAuthenticationEvent.json",
+ "cognitoPreSignUpEvent.json",
+ "cognitoPreTokenGenerationEvent.json",
+ "cognitoPreTokenV2GenerationEvent.json",
+ "cognitoUserMigrationEvent.json",
+ "cognitoVerifyAuthChallengeResponseEvent.json",
+ "connectContactFlowEventAll.json",
+ "connectContactFlowEventMin.json",
+ "dynamoStreamEvent.json",
+ "kafkaEventMsk.json",
+ "kafkaEventSelfManaged.json",
+ "kinesisFirehoseKinesisEvent.json",
+ "kinesisFirehosePutEvent.json",
+ "kinesisFirehoseSQSEvent.json",
+ "kinesisStreamCloudWatchLogsEvent.json",
+ "kinesisStreamEvent.json",
+ "kinesisStreamEventOneRecord.json",
+ "lambdaFunctionUrlEvent.json",
+ "lambdaFunctionUrlEventPathTrailingSlash.json",
+ "lambdaFunctionUrlEventWithHeaders.json",
+ "lambdaFunctionUrlIAMEvent.json",
+ "rabbitMQEvent.json",
+ "s3BatchOperationEventSchemaV1.json",
+ "s3BatchOperationEventSchemaV2.json",
+ "s3Event.json",
+ "s3EventBridgeNotificationObjectCreatedEvent.json",
+ "s3EventBridgeNotificationObjectDeletedEvent.json",
+ "s3EventBridgeNotificationObjectExpiredEvent.json",
+ "s3EventBridgeNotificationObjectRestoreCompletedEvent.json",
+ "s3EventDecodedKey.json",
+ "s3EventDeleteObject.json",
+ "s3EventGlacier.json",
+ "s3ObjectEventIAMUser.json",
+ "s3ObjectEventTempCredentials.json",
+ "s3SqsEvent.json",
+ "secretsManagerEvent.json",
+ "sesEvent.json",
+ "snsEvent.json",
+ "snsSqsEvent.json",
+ "snsSqsFifoEvent.json",
+ "sqsDlqTriggerEvent.json",
+ "sqsEvent.json",
+ "vpcLatticeEvent.json",
+ "vpcLatticeEventPathTrailingSlash.json",
+ "vpcLatticeEventV2PathTrailingSlash.json",
+ "vpcLatticeV2Event.json",
+ "vpcLatticeV2EventWithHeaders.json",
+ ],
+ )
+ def test_match_for_not_event_bridge_event(self, event_name):
+ event = load_event(file_name=event_name)
+ route = EventBridgeRoute(func=None, detail_type="EC2 Instance State-change Notification")
+ actual = route.match(event=event)
+ assert actual is None
diff --git a/tests/unit/event_handler/_async_execution/_routes/test_s3.py b/tests/unit/event_handler/_async_execution/_routes/test_s3.py
new file mode 100644
index 00000000000..0ad7c8062b5
--- /dev/null
+++ b/tests/unit/event_handler/_async_execution/_routes/test_s3.py
@@ -0,0 +1,1514 @@
+import pytest
+
+from aws_lambda_powertools.event_handler.async_execution.routes.s3 import S3Route
+from aws_lambda_powertools.utilities.data_classes.s3_event import S3Event
+from tests.functional.utils import load_event
+
+
+class TestS3Route:
+ def test_constructor_error(self):
+ with pytest.raises(ValueError):
+ S3Route(func=None)
+
+ @pytest.mark.parametrize(
+ "option_constructor, option_func, expected",
+ [
+ # without bucket at option_func
+ (
+ {"func": None, "bucket": "lambda-artifacts-deafc19498e3f2df", "bucket_prefix": "lambda-artifacts-d"},
+ {"bucket": None},
+ False,
+ ),
+ # with bucket and bucket_prefix
+ # match all
+ (
+ {"func": None, "bucket": "lambda-artifacts-deafc19498e3f2df", "bucket_prefix": "lambda-artifacts-d"},
+ {"bucket": "lambda-artifacts-deafc19498e3f2df"},
+ True,
+ ),
+ # with bucket and bucket_prefix
+ # match 1, unmatch 1
+ (
+ {"func": None, "bucket": "lambda-artifacts-deafc19498e3f2df", "bucket_prefix": "ambda-artifacts-d"},
+ {"bucket": "lambda-artifacts-deafc19498e3f2df"},
+ True,
+ ),
+ (
+ {"func": None, "bucket": "lambda-artifacts-9999999999999999", "bucket_prefix": "lambda-artifacts-d"},
+ {"bucket": "lambda-artifacts-deafc19498e3f2df"},
+ False,
+ ),
+ # with bucket and bucket_prefix
+ # unmatch all
+ (
+ {"func": None, "bucket": "lambda-artifacts-9999999999999999", "bucket_prefix": "ambda-artifacts-d"},
+ {"bucket": "lambda-artifacts-deafc19498e3f2df"},
+ False,
+ ),
+ # with bucket
+ (
+ {
+ "func": None,
+ "bucket": "lambda-artifacts-deafc19498e3f2df",
+ },
+ {"bucket": "lambda-artifacts-deafc19498e3f2df"},
+ True,
+ ),
+ (
+ {
+ "func": None,
+ "bucket": "lambda-artifacts-9999999999999999",
+ },
+ {"bucket": "lambda-artifacts-deafc19498e3f2df"},
+ False,
+ ),
+ # with bucket_prefix
+ (
+ {
+ "func": None,
+ "bucket_prefix": "lambda-a",
+ },
+ {"bucket": "lambda-artifacts-deafc19498e3f2df"},
+ True,
+ ),
+ (
+ {
+ "func": None,
+ "bucket_prefix": "ambda-a",
+ },
+ {"bucket": "lambda-artifacts-deafc19498e3f2df"},
+ False,
+ ),
+ # without bucket and bucket_prefix
+ (
+ {"func": None, "key": "b21b84d653bb07b05b1e6b33684dc11b"},
+ {"bucket": "lambda-artifacts-deafc19498e3f2df"},
+ False,
+ ),
+ ],
+ )
+ def test_is_target_with_bucket(self, option_constructor, option_func, expected):
+ route = S3Route(**option_constructor)
+ actual = route.is_target_with_bucket(**option_func)
+ assert actual == expected
+
+ @pytest.mark.parametrize(
+ "option_constructor, option_func, expected",
+ [
+ # without key at option_func
+ (
+ {"func": None, "key": "b21b84d653bb07b05b1e6b33684dc11b", "key_prefix": "b21b", "key_suffix": "c11b"},
+ {"key": None},
+ False,
+ ),
+ # without key, key_prefix, and key_suffix
+ (
+ {"func": None, "bucket": "lambda-artifacts-deafc19498e3f2df"},
+ {"key": "b21b84d653bb07b05b1e6b33684dc11b"},
+ False,
+ ),
+ # with key, key_prefix, and key_suffix
+ # match all
+ (
+ {"func": None, "key": "b21b84d653bb07b05b1e6b33684dc11b", "key_prefix": "b21b", "key_suffix": "c11b"},
+ {"key": "b21b84d653bb07b05b1e6b33684dc11b"},
+ True,
+ ),
+ # with key, key_prefix, and key_suffix
+ # match 2, unmatch 1
+ (
+ {"func": None, "key": "b21b84d653bb07b05b1e6b33684dc11b", "key_prefix": "b21b", "key_suffix": "9999"},
+ {"key": "b21b84d653bb07b05b1e6b33684dc11b"},
+ True,
+ ),
+ (
+ {"func": None, "key": "b21b84d653bb07b05b1e6b33684dc11b", "key_prefix": "9999", "key_suffix": "c11b"},
+ {"key": "b21b84d653bb07b05b1e6b33684dc11b"},
+ True,
+ ),
+ (
+ {"func": None, "key": "99999999999999999999999999999999", "key_prefix": "b21b", "key_suffix": "c11b"},
+ {"key": "b21b84d653bb07b05b1e6b33684dc11b"},
+ False,
+ ),
+ # with key, key_prefix, and key_suffix
+ # match 1, unmatch 2
+ (
+ {"func": None, "key": "b21b84d653bb07b05b1e6b33684dc11b", "key_prefix": "9999", "key_suffix": "9999"},
+ {"key": "b21b84d653bb07b05b1e6b33684dc11b"},
+ True,
+ ),
+ (
+ {"func": None, "key": "99999999999999999999999999999999", "key_prefix": "b21b", "key_suffix": "9999"},
+ {"key": "b21b84d653bb07b05b1e6b33684dc11b"},
+ False,
+ ),
+ (
+ {"func": None, "key": "99999999999999999999999999999999", "key_prefix": "9999", "key_suffix": "c11b"},
+ {"key": "b21b84d653bb07b05b1e6b33684dc11b"},
+ False,
+ ),
+ # with key, key_prefix, and key_suffix
+ # unmatch all
+ (
+ {"func": None, "key": "99999999999999999999999999999999", "key_prefix": "9999", "key_suffix": "9999"},
+ {"key": "b21b84d653bb07b05b1e6b33684dc11b"},
+ False,
+ ),
+ # with key, key_prefix
+ # match all
+ (
+ {
+ "func": None,
+ "key": "b21b84d653bb07b05b1e6b33684dc11b",
+ "key_prefix": "b21b",
+ },
+ {"key": "b21b84d653bb07b05b1e6b33684dc11b"},
+ True,
+ ),
+ # with key, key_prefix
+ # match 1, unmatch 1
+ (
+ {
+ "func": None,
+ "key": "b21b84d653bb07b05b1e6b33684dc11b",
+ "key_prefix": "9999",
+ },
+ {"key": "b21b84d653bb07b05b1e6b33684dc11b"},
+ True,
+ ),
+ (
+ {
+ "func": None,
+ "key": "99999999999999999999999999999999",
+ "key_prefix": "b21b",
+ },
+ {"key": "b21b84d653bb07b05b1e6b33684dc11b"},
+ False,
+ ),
+ # with key, key_prefix
+ # unmatch all
+ (
+ {
+ "func": None,
+ "key": "99999999999999999999999999999999",
+ "key_prefix": "9999",
+ },
+ {"key": "b21b84d653bb07b05b1e6b33684dc11b"},
+ False,
+ ),
+ # with key, key_suffix
+ # match all
+ (
+ {
+ "func": None,
+ "key": "b21b84d653bb07b05b1e6b33684dc11b",
+ "key_suffix": "c11b",
+ },
+ {"key": "b21b84d653bb07b05b1e6b33684dc11b"},
+ True,
+ ),
+ # with key, key_suffix
+ # match 1, unmatch 1
+ (
+ {
+ "func": None,
+ "key": "b21b84d653bb07b05b1e6b33684dc11b",
+ "key_suffix": "9999",
+ },
+ {"key": "b21b84d653bb07b05b1e6b33684dc11b"},
+ True,
+ ),
+ (
+ {
+ "func": None,
+ "key": "99999999999999999999999999999999",
+ "key_suffix": "c11b",
+ },
+ {"key": "b21b84d653bb07b05b1e6b33684dc11b"},
+ False,
+ ),
+ # with key, key_suffix
+ # unmatch all
+ (
+ {
+ "func": None,
+ "key": "99999999999999999999999999999999",
+ "key_suffix": "9999",
+ },
+ {"key": "b21b84d653bb07b05b1e6b33684dc11b"},
+ False,
+ ),
+ # with key_prefix, key_suffix
+ # match all
+ (
+ {
+ "func": None,
+ "key_prefix": "b21b",
+ "key_suffix": "c11b",
+ },
+ {"key": "b21b84d653bb07b05b1e6b33684dc11b"},
+ True,
+ ),
+ # with key_prefix, key_suffix
+ # match 1, unmatch 1
+ (
+ {
+ "func": None,
+ "key_prefix": "b21b",
+ "key_suffix": "9999",
+ },
+ {"key": "b21b84d653bb07b05b1e6b33684dc11b"},
+ False,
+ ),
+ (
+ {
+ "func": None,
+ "key_prefix": "9999",
+ "key_suffix": "c11b",
+ },
+ {"key": "b21b84d653bb07b05b1e6b33684dc11b"},
+ False,
+ ),
+ # with key_prefix, key_suffix
+ # unmatch all
+ (
+ {
+ "func": None,
+ "key_prefix": "9999",
+ "key_suffix": "9999",
+ },
+ {"key": "b21b84d653bb07b05b1e6b33684dc11b"},
+ False,
+ ),
+ # with key
+ (
+ {
+ "func": None,
+ "key": "b21b84d653bb07b05b1e6b33684dc11b",
+ },
+ {"key": "b21b84d653bb07b05b1e6b33684dc11b"},
+ True,
+ ),
+ (
+ {
+ "func": None,
+ "key": "99999999999999999999999999999999",
+ },
+ {"key": "b21b84d653bb07b05b1e6b33684dc11b"},
+ False,
+ ),
+ # with key_prefix
+ (
+ {
+ "func": None,
+ "key_prefix": "b21b",
+ },
+ {"key": "b21b84d653bb07b05b1e6b33684dc11b"},
+ True,
+ ),
+ (
+ {
+ "func": None,
+ "key_prefix": "9999",
+ },
+ {"key": "b21b84d653bb07b05b1e6b33684dc11b"},
+ False,
+ ),
+ # with_key_suffix
+ (
+ {
+ "func": None,
+ "key_suffix": "c11b",
+ },
+ {"key": "b21b84d653bb07b05b1e6b33684dc11b"},
+ True,
+ ),
+ (
+ {
+ "func": None,
+ "key_suffix": "9999",
+ },
+ {"key": "b21b84d653bb07b05b1e6b33684dc11b"},
+ False,
+ ),
+ ],
+ )
+ def test_is_target_with_key(self, option_constructor, option_func, expected):
+ route = S3Route(**option_constructor)
+ actual = route.is_target_with_key(**option_func)
+ assert actual == expected
+
+ @pytest.mark.parametrize(
+ "option_constructor, option_func, expected",
+ [
+ # without event_name and event_name_prefix
+ ({"func": None, "bucket": "lambda-artifacts-deafc19498e3f2df"}, {"event_name": "ObjectCreated:Put"}, False),
+ # without event_name at option_func
+ (
+ {"func": None, "event_name": "ObjectCreated:Put", "event_name_prefix": "ObjectCreated:"},
+ {"event_name": None},
+ False,
+ ),
+ # with event_name and event_name_prefix
+ # match all
+ (
+ {"func": None, "event_name": "ObjectCreated:Put", "event_name_prefix": "ObjectCreated:"},
+ {"event_name": "ObjectCreated:Put"},
+ True,
+ ),
+ # with event_name and event_name_prefix
+ # match 1, unmatch 1
+ (
+ {"func": None, "event_name": "ObjectCreated:Put", "event_name_prefix": "bjectCreated:"},
+ {"event_name": "ObjectCreated:Put"},
+ True,
+ ),
+ (
+ {"func": None, "event_name": "ObjectCreated:PutV2", "event_name_prefix": "ObjectCreated:"},
+ {"event_name": "ObjectCreated:Put"},
+ False,
+ ),
+ # with event_name and event_name_prefix
+ # unmatch all
+ (
+ {"func": None, "event_name": "ObjectCreated:PutV2", "event_name_prefix": "bjectCreated:"},
+ {"event_name": "ObjectCreated:Put"},
+ False,
+ ),
+ # with event_name
+ (
+ {
+ "func": None,
+ "event_name": "ObjectCreated:Put",
+ },
+ {"event_name": "ObjectCreated:Put"},
+ True,
+ ),
+ (
+ {
+ "func": None,
+ "event_name": "ObjectCreated:PutV2",
+ },
+ {"event_name": "ObjectCreated:Put"},
+ False,
+ ),
+ # with event_name_prefix
+ (
+ {
+ "func": None,
+ "event_name_prefix": "ObjectCreated:",
+ },
+ {"event_name": "ObjectCreated:Put"},
+ True,
+ ),
+ (
+ {
+ "func": None,
+ "event_name_prefix": "bjectCreated:",
+ },
+ {"event_name": "ObjectCreated:Put"},
+ False,
+ ),
+ ],
+ )
+ def test_is_target_with_event_name(self, option_constructor, option_func, expected):
+ route = S3Route(**option_constructor)
+ actual = route.is_target_with_event_name(**option_func)
+ assert actual == expected
+
+ @pytest.mark.parametrize(
+ "option_constructor, option_func, expected",
+ [
+ # without configuration_id and configuration_id_prefix
+ (
+ {"func": None, "bucket": "lambda-artifacts-deafc19498e3f2df"},
+ {"configuration_id": "828aa6fc-f7b5-4305-8584-487c791949c1"},
+ False,
+ ),
+ # without configuration_id at option_func
+ (
+ {
+ "func": None,
+ "configuration_id": "828aa6fc-f7b5-4305-8584-487c791949c1",
+ "configuration_id_prefix": "828aa6fc-f",
+ },
+ {"configuration_id": None},
+ False,
+ ),
+ # with configuration_id and configuration_id_prefix
+ # match all
+ (
+ {
+ "func": None,
+ "configuration_id": "828aa6fc-f7b5-4305-8584-487c791949c1",
+ "configuration_id_prefix": "828aa6fc-f",
+ },
+ {
+ "configuration_id": "828aa6fc-f7b5-4305-8584-487c791949c1",
+ },
+ True,
+ ),
+ # with configuration_id and configuration_id_prefix
+ # match 1, unmatch 1
+ (
+ {
+ "func": None,
+ "configuration_id": "828aa6fc-f7b5-4305-8584-487c791949c1",
+ "configuration_id_prefix": "28aa6fc-f",
+ },
+ {
+ "configuration_id": "828aa6fc-f7b5-4305-8584-487c791949c1",
+ },
+ True,
+ ),
+ (
+ {
+ "func": None,
+ "configuration_id": "99999999-9999-9999-9999-999999999999",
+ "configuration_id_prefix": "828aa6fc-f",
+ },
+ {
+ "configuration_id": "828aa6fc-f7b5-4305-8584-487c791949c1",
+ },
+ False,
+ ),
+ # with configuration_id and configuration_id_prefix
+ # unmatch all
+ (
+ {
+ "func": None,
+ "configuration_id": "99999999-9999-9999-9999-999999999999",
+ "configuration_id_prefix": "28aa6fc-f",
+ },
+ {
+ "configuration_id": "828aa6fc-f7b5-4305-8584-487c791949c1",
+ },
+ False,
+ ),
+ # with configuration_id
+ (
+ {
+ "func": None,
+ "configuration_id": "828aa6fc-f7b5-4305-8584-487c791949c1",
+ },
+ {
+ "configuration_id": "828aa6fc-f7b5-4305-8584-487c791949c1",
+ },
+ True,
+ ),
+ (
+ {
+ "func": None,
+ "configuration_id": "99999999-9999-9999-9999-999999999999",
+ },
+ {
+ "configuration_id": "828aa6fc-f7b5-4305-8584-487c791949c1",
+ },
+ False,
+ ),
+ # with configuration_id_prefix
+ (
+ {
+ "func": None,
+ "configuration_id_prefix": "828aa6fc-f",
+ },
+ {
+ "configuration_id": "828aa6fc-f7b5-4305-8584-487c791949c1",
+ },
+ True,
+ ),
+ (
+ {
+ "func": None,
+ "configuration_id_prefix": "28aa6fc-f",
+ },
+ {
+ "configuration_id": "828aa6fc-f7b5-4305-8584-487c791949c1",
+ },
+ False,
+ ),
+ ],
+ )
+ def test_is_target_with_configuration_id(self, option_constructor, option_func, expected):
+ route = S3Route(**option_constructor)
+ actual = route.is_target_with_configuration_id(**option_func)
+ assert actual == expected
+
+ @pytest.mark.parametrize(
+ "event_name, option_constructor, is_match",
+ [
+ # with bucket, key, event_name, and configuration_id
+ # match all
+ (
+ "s3Event.json",
+ {
+ "func": lambda *_: None,
+ "bucket": "lambda-artifacts-deafc19498e3f2df",
+ "key": "b21b84d653bb07b05b1e6b33684dc11b",
+ "event_name": "ObjectCreated:Put",
+ "configuration_id": "828aa6fc-f7b5-4305-8584-487c791949c1",
+ },
+ True,
+ ),
+ # with bucket, key, event_name, and configuration_id
+ # match 3, unmatch 1
+ (
+ "s3Event.json",
+ {
+ "func": lambda *_: None,
+ "bucket": "lambda-artifacts-deafc19498e3f2df",
+ "key": "b21b84d653bb07b05b1e6b33684dc11b",
+ "event_name": "ObjectCreated:Put",
+ "configuration_id": "828aa6fc-f7b5-4305-8584-999999999999",
+ },
+ False,
+ ),
+ (
+ "s3Event.json",
+ {
+ "func": lambda *_: None,
+ "bucket": "lambda-artifacts-deafc19498e3f2df",
+ "key": "b21b84d653bb07b05b1e6b33684dc11b",
+ "event_name": "ObjectCreated:PutV2",
+ "configuration_id": "828aa6fc-f7b5-4305-8584-487c791949c1",
+ },
+ False,
+ ),
+ (
+ "s3Event.json",
+ {
+ "func": lambda *_: None,
+ "bucket": "lambda-artifacts-deafc19498e3f2df",
+ "key": "99999999999999999999999999999999",
+ "event_name": "ObjectCreated:Put",
+ "configuration_id": "828aa6fc-f7b5-4305-8584-487c791949c1",
+ },
+ False,
+ ),
+ (
+ "s3Event.json",
+ {
+ "func": lambda *_: None,
+ "bucket": "lambda-artifacts-9999999999999999",
+ "key": "b21b84d653bb07b05b1e6b33684dc11b",
+ "event_name": "ObjectCreated:Put",
+ "configuration_id": "828aa6fc-f7b5-4305-8584-487c791949c1",
+ },
+ False,
+ ),
+ # with bucket, key, event_name, and configuration_id
+ # match 2, unmatch 2
+ (
+ "s3Event.json",
+ {
+ "func": lambda *_: None,
+ "bucket": "lambda-artifacts-deafc19498e3f2df",
+ "key": "b21b84d653bb07b05b1e6b33684dc11b",
+ "event_name": "ObjectCreated:PutV2",
+ "configuration_id": "828aa6fc-f7b5-4305-8584-999999999999",
+ },
+ False,
+ ),
+ (
+ "s3Event.json",
+ {
+ "func": lambda *_: None,
+ "bucket": "lambda-artifacts-deafc19498e3f2df",
+ "key": "99999999999999999999999999999999",
+ "event_name": "ObjectCreated:Put",
+ "configuration_id": "828aa6fc-f7b5-4305-8584-999999999999",
+ },
+ False,
+ ),
+ (
+ "s3Event.json",
+ {
+ "func": lambda *_: None,
+ "bucket": "lambda-artifacts-9999999999999999",
+ "key": "b21b84d653bb07b05b1e6b33684dc11b",
+ "event_name": "ObjectCreated:Put",
+ "configuration_id": "828aa6fc-f7b5-4305-8584-999999999999",
+ },
+ False,
+ ),
+ (
+ "s3Event.json",
+ {
+ "func": lambda *_: None,
+ "bucket": "lambda-artifacts-deafc19498e3f2df",
+ "key": "99999999999999999999999999999999",
+ "event_name": "ObjectCreated:PutV2",
+ "configuration_id": "828aa6fc-f7b5-4305-8584-487c791949c1",
+ },
+ False,
+ ),
+ (
+ "s3Event.json",
+ {
+ "func": lambda *_: None,
+ "bucket": "lambda-artifacts-9999999999999999",
+ "key": "b21b84d653bb07b05b1e6b33684dc11b",
+ "event_name": "ObjectCreated:PutV2",
+ "configuration_id": "828aa6fc-f7b5-4305-8584-487c791949c1",
+ },
+ False,
+ ),
+ (
+ "s3Event.json",
+ {
+ "func": lambda *_: None,
+ "bucket": "lambda-artifacts-9999999999999999",
+ "key": "99999999999999999999999999999999",
+ "event_name": "ObjectCreated:Put",
+ "configuration_id": "828aa6fc-f7b5-4305-8584-487c791949c1",
+ },
+ False,
+ ),
+ # with bucket, key, event_name, and configuration_id
+ # match 1, unmatch 3
+ (
+ "s3Event.json",
+ {
+ "func": lambda *_: None,
+ "bucket": "lambda-artifacts-deafc19498e3f2df",
+ "key": "99999999999999999999999999999999",
+ "event_name": "ObjectCreated:PutV2",
+ "configuration_id": "828aa6fc-f7b5-4305-8584-999999999999",
+ },
+ False,
+ ),
+ (
+ "s3Event.json",
+ {
+ "func": lambda *_: None,
+ "bucket": "lambda-artifacts-9999999999999999",
+ "key": "b21b84d653bb07b05b1e6b33684dc11b",
+ "event_name": "ObjectCreated:PutV2",
+ "configuration_id": "828aa6fc-f7b5-4305-8584-999999999999",
+ },
+ False,
+ ),
+ (
+ "s3Event.json",
+ {
+ "func": lambda *_: None,
+ "bucket": "lambda-artifacts-9999999999999999",
+ "key": "99999999999999999999999999999999",
+ "event_name": "ObjectCreated:Put",
+ "configuration_id": "828aa6fc-f7b5-4305-8584-999999999999",
+ },
+ False,
+ ),
+ (
+ "s3Event.json",
+ {
+ "func": lambda *_: None,
+ "bucket": "lambda-artifacts-9999999999999999",
+ "key": "99999999999999999999999999999999",
+ "event_name": "ObjectCreated:PutV2",
+ "configuration_id": "828aa6fc-f7b5-4305-8584-487c791949c1",
+ },
+ False,
+ ),
+ # with bucket, key, event_name, and configuration_id
+ # unmatch all
+ (
+ "s3Event.json",
+ {
+ "func": lambda *_: None,
+ "bucket": "lambda-artifacts-9999999999999999",
+ "key": "99999999999999999999999999999999",
+ "event_name": "ObjectCreated:PutV2",
+ "configuration_id": "828aa6fc-f7b5-4305-8584-999999999999",
+ },
+ False,
+ ),
+ # with bucket, key, and event_name
+ # match all
+ (
+ "s3Event.json",
+ {
+ "func": lambda *_: None,
+ "bucket": "lambda-artifacts-deafc19498e3f2df",
+ "key": "b21b84d653bb07b05b1e6b33684dc11b",
+ "event_name": "ObjectCreated:Put",
+ },
+ True,
+ ),
+ # with bucket, key, and event_name
+ # match 2, unmatch 1
+ (
+ "s3Event.json",
+ {
+ "func": lambda *_: None,
+ "bucket": "lambda-artifacts-deafc19498e3f2df",
+ "key": "b21b84d653bb07b05b1e6b33684dc11b",
+ "event_name": "ObjectCreated:PutV2",
+ },
+ False,
+ ),
+ (
+ "s3Event.json",
+ {
+ "func": lambda *_: None,
+ "bucket": "lambda-artifacts-deafc19498e3f2df",
+ "key": "99999999999999999999999999999999",
+ "event_name": "ObjectCreated:Put",
+ },
+ False,
+ ),
+ (
+ "s3Event.json",
+ {
+ "func": lambda *_: None,
+ "bucket": "lambda-artifacts-9999999999999999",
+ "key": "b21b84d653bb07b05b1e6b33684dc11b",
+ "event_name": "ObjectCreated:Put",
+ },
+ False,
+ ),
+ # with bucket, key, and event_name
+ # match 1, unmatch 2
+ (
+ "s3Event.json",
+ {
+ "func": lambda *_: None,
+ "bucket": "lambda-artifacts-deafc19498e3f2df",
+ "key": "99999999999999999999999999999999",
+ "event_name": "ObjectCreated:PutV2",
+ },
+ False,
+ ),
+ (
+ "s3Event.json",
+ {
+ "func": lambda *_: None,
+ "bucket": "lambda-artifacts-9999999999999999",
+ "key": "b21b84d653bb07b05b1e6b33684dc11b",
+ "event_name": "ObjectCreated:PutV2",
+ },
+ False,
+ ),
+ (
+ "s3Event.json",
+ {
+ "func": lambda *_: None,
+ "bucket": "lambda-artifacts-9999999999999999",
+ "key": "99999999999999999999999999999999",
+ "event_name": "ObjectCreated:Put",
+ },
+ False,
+ ),
+ # with bucket, key, and event_name
+ # unmatch all
+ (
+ "s3Event.json",
+ {
+ "func": lambda *_: None,
+ "bucket": "lambda-artifacts-9999999999999999",
+ "key": "99999999999999999999999999999999",
+ "event_name": "ObjectCreated:PutV2",
+ },
+ False,
+ ),
+ # with bucket, key, and configuration_id
+ # match all
+ (
+ "s3Event.json",
+ {
+ "func": lambda *_: None,
+ "bucket": "lambda-artifacts-deafc19498e3f2df",
+ "key": "b21b84d653bb07b05b1e6b33684dc11b",
+ "configuration_id": "828aa6fc-f7b5-4305-8584-487c791949c1",
+ },
+ True,
+ ),
+ # with bucket, key, and configuration_id
+ # match 2, unmatch 1
+ (
+ "s3Event.json",
+ {
+ "func": lambda *_: None,
+ "bucket": "lambda-artifacts-deafc19498e3f2df",
+ "key": "b21b84d653bb07b05b1e6b33684dc11b",
+ "configuration_id": "828aa6fc-f7b5-4305-8584-999999999999",
+ },
+ False,
+ ),
+ (
+ "s3Event.json",
+ {
+ "func": lambda *_: None,
+ "bucket": "lambda-artifacts-deafc19498e3f2df",
+ "key": "99999999999999999999999999999999",
+ "configuration_id": "828aa6fc-f7b5-4305-8584-487c791949c1",
+ },
+ False,
+ ),
+ (
+ "s3Event.json",
+ {
+ "func": lambda *_: None,
+ "bucket": "lambda-artifacts-9999999999999999",
+ "key": "b21b84d653bb07b05b1e6b33684dc11b",
+ "configuration_id": "828aa6fc-f7b5-4305-8584-487c791949c1",
+ },
+ False,
+ ),
+ # with bucket, key, and configuration_id
+ # match 1, unmatch 2
+ (
+ "s3Event.json",
+ {
+ "func": lambda *_: None,
+ "bucket": "lambda-artifacts-deafc19498e3f2df",
+ "key": "99999999999999999999999999999999",
+ "configuration_id": "828aa6fc-f7b5-4305-8584-999999999999",
+ },
+ False,
+ ),
+ (
+ "s3Event.json",
+ {
+ "func": lambda *_: None,
+ "bucket": "lambda-artifacts-9999999999999999",
+ "key": "b21b84d653bb07b05b1e6b33684dc11b",
+ "configuration_id": "828aa6fc-f7b5-4305-8584-999999999999",
+ },
+ False,
+ ),
+ (
+ "s3Event.json",
+ {
+ "func": lambda *_: None,
+ "bucket": "lambda-artifacts-deafc19498e3f2df",
+ "key": "99999999999999999999999999999999",
+ "configuration_id": "828aa6fc-f7b5-4305-8584-999999999999",
+ },
+ False,
+ ),
+ # with bucket, key, and configuration_id
+ # unmatch all
+ (
+ "s3Event.json",
+ {
+ "func": lambda *_: None,
+ "bucket": "lambda-artifacts-9999999999999999",
+ "key": "99999999999999999999999999999999",
+ "configuration_id": "828aa6fc-f7b5-4305-8584-999999999999",
+ },
+ False,
+ ),
+ # with bucket, event_name, and configuration_id
+ # match all
+ (
+ "s3Event.json",
+ {
+ "func": lambda *_: None,
+ "bucket": "lambda-artifacts-deafc19498e3f2df",
+ "event_name": "ObjectCreated:Put",
+ "configuration_id": "828aa6fc-f7b5-4305-8584-487c791949c1",
+ },
+ True,
+ ),
+ # with bucket, event_name, and configuration_id
+ # match 2, unmatch 1
+ (
+ "s3Event.json",
+ {
+ "func": lambda *_: None,
+ "bucket": "lambda-artifacts-deafc19498e3f2df",
+ "event_name": "ObjectCreated:Put",
+ "configuration_id": "828aa6fc-f7b5-4305-8584-999999999999",
+ },
+ False,
+ ),
+ (
+ "s3Event.json",
+ {
+ "func": lambda *_: None,
+ "bucket": "lambda-artifacts-deafc19498e3f2df",
+ "event_name": "ObjectCreated:PutV2",
+ "configuration_id": "828aa6fc-f7b5-4305-8584-487c791949c1",
+ },
+ False,
+ ),
+ (
+ "s3Event.json",
+ {
+ "func": lambda *_: None,
+ "bucket": "lambda-artifacts-9999999999999999",
+ "event_name": "ObjectCreated:Put",
+ "configuration_id": "828aa6fc-f7b5-4305-8584-487c791949c1",
+ },
+ False,
+ ),
+ # with bucket, event_name, and configuration_id
+ # match 1, unmatch 2
+ (
+ "s3Event.json",
+ {
+ "func": lambda *_: None,
+ "bucket": "lambda-artifacts-deafc19498e3f2df",
+ "event_name": "ObjectCreated:PutV2",
+ "configuration_id": "828aa6fc-f7b5-4305-8584-999999999999",
+ },
+ False,
+ ),
+ (
+ "s3Event.json",
+ {
+ "func": lambda *_: None,
+ "bucket": "lambda-artifacts-9999999999999999",
+ "event_name": "ObjectCreated:Put",
+ "configuration_id": "828aa6fc-f7b5-4305-8584-999999999999",
+ },
+ False,
+ ),
+ (
+ "s3Event.json",
+ {
+ "func": lambda *_: None,
+ "bucket": "lambda-artifacts-9999999999999999",
+ "event_name": "ObjectCreated:PutV2",
+ "configuration_id": "828aa6fc-f7b5-4305-8584-487c791949c1",
+ },
+ False,
+ ),
+ # with bucket, event_name, and configuration_id
+ # unmatch all
+ (
+ "s3Event.json",
+ {
+ "func": lambda *_: None,
+ "bucket": "lambda-artifacts-9999999999999999",
+ "event_name": "ObjectCreated:PutV2",
+ "configuration_id": "828aa6fc-f7b5-4305-8584-999999999999",
+ },
+ False,
+ ),
+ # with key, event_name, configuration_id
+ # match all
+ (
+ "s3Event.json",
+ {
+ "func": lambda *_: None,
+ "key": "b21b84d653bb07b05b1e6b33684dc11b",
+ "event_name": "ObjectCreated:Put",
+ "configuration_id": "828aa6fc-f7b5-4305-8584-487c791949c1",
+ },
+ True,
+ ),
+ # with key, event_name, configuration_id
+ # match 2, unmatch 1
+ (
+ "s3Event.json",
+ {
+ "func": lambda *_: None,
+ "key": "b21b84d653bb07b05b1e6b33684dc11b",
+ "event_name": "ObjectCreated:Put",
+ "configuration_id": "828aa6fc-f7b5-4305-8584-999999999999",
+ },
+ False,
+ ),
+ (
+ "s3Event.json",
+ {
+ "func": lambda *_: None,
+ "key": "b21b84d653bb07b05b1e6b33684dc11b",
+ "event_name": "ObjectCreated:PutV2",
+ "configuration_id": "828aa6fc-f7b5-4305-8584-487c791949c1",
+ },
+ False,
+ ),
+ (
+ "s3Event.json",
+ {
+ "func": lambda *_: None,
+ "key": "99999999999999999999999999999999",
+ "event_name": "ObjectCreated:Put",
+ "configuration_id": "828aa6fc-f7b5-4305-8584-487c791949c1",
+ },
+ False,
+ ),
+ # with key, event_name, configuration_id
+ # match 1, unmatch 2
+ (
+ "s3Event.json",
+ {
+ "func": lambda *_: None,
+ "key": "b21b84d653bb07b05b1e6b33684dc11b",
+ "event_name": "ObjectCreated:PutV2",
+ "configuration_id": "828aa6fc-f7b5-4305-8584-999999999999",
+ },
+ False,
+ ),
+ (
+ "s3Event.json",
+ {
+ "func": lambda *_: None,
+ "key": "99999999999999999999999999999999",
+ "event_name": "ObjectCreated:Put",
+ "configuration_id": "828aa6fc-f7b5-4305-8584-999999999999",
+ },
+ False,
+ ),
+ (
+ "s3Event.json",
+ {
+ "func": lambda *_: None,
+ "key": "99999999999999999999999999999999",
+ "event_name": "ObjectCreated:PutV2",
+ "configuration_id": "828aa6fc-f7b5-4305-8584-487c791949c1",
+ },
+ False,
+ ),
+ # with key, event_name, and configuration_id
+ # unmatch all
+ (
+ "s3Event.json",
+ {
+ "func": lambda *_: None,
+ "key": "99999999999999999999999999999999",
+ "event_name": "ObjectCreated:PutV2",
+ "configuration_id": "828aa6fc-f7b5-4305-8584-999999999999",
+ },
+ False,
+ ),
+ # with bucket and key
+ # match all
+ (
+ "s3Event.json",
+ {
+ "func": lambda *_: None,
+ "bucket": "lambda-artifacts-deafc19498e3f2df",
+ "key": "b21b84d653bb07b05b1e6b33684dc11b",
+ },
+ True,
+ ),
+ # with bucket and key
+ # match 1, unmatch 1
+ (
+ "s3Event.json",
+ {
+ "func": lambda *_: None,
+ "bucket": "lambda-artifacts-deafc19498e3f2df",
+ "key": "99999999999999999999999999999999",
+ },
+ False,
+ ),
+ (
+ "s3Event.json",
+ {
+ "func": lambda *_: None,
+ "bucket": "lambda-artifacts-9999999999999999",
+ "key": "b21b84d653bb07b05b1e6b33684dc11b",
+ },
+ False,
+ ),
+ # with bucket and key
+ # unmatch all
+ (
+ "s3Event.json",
+ {
+ "func": lambda *_: None,
+ "bucket": "lambda-artifacts-9999999999999999",
+ "key": "99999999999999999999999999999999",
+ },
+ False,
+ ),
+ # with bucket and event_name
+ # match all
+ (
+ "s3Event.json",
+ {
+ "func": lambda *_: None,
+ "bucket": "lambda-artifacts-deafc19498e3f2df",
+ "event_name": "ObjectCreated:Put",
+ },
+ True,
+ ),
+ # with bucket and event_name
+ # match 1, unmatch 1
+ (
+ "s3Event.json",
+ {
+ "func": lambda *_: None,
+ "bucket": "lambda-artifacts-deafc19498e3f2df",
+ "event_name": "ObjectCreated:PutV2",
+ },
+ False,
+ ),
+ (
+ "s3Event.json",
+ {
+ "func": lambda *_: None,
+ "bucket": "lambda-artifacts-9999999999999999",
+ "event_name": "ObjectCreated:Put",
+ },
+ False,
+ ),
+ # with bucket and event_name
+ # unmatch all
+ (
+ "s3Event.json",
+ {
+ "func": lambda *_: None,
+ "bucket": "lambda-artifacts-9999999999999999",
+ "event_name": "ObjectCreated:PutV2",
+ },
+ False,
+ ),
+ # with bucket and configuration_id
+ # match all
+ (
+ "s3Event.json",
+ {
+ "func": lambda *_: None,
+ "bucket": "lambda-artifacts-deafc19498e3f2df",
+ "configuration_id": "828aa6fc-f7b5-4305-8584-487c791949c1",
+ },
+ True,
+ ),
+ # with bucket and configuration_id
+ # match 1, unmatch 1
+ (
+ "s3Event.json",
+ {
+ "func": lambda *_: None,
+ "bucket": "lambda-artifacts-deafc19498e3f2df",
+ "configuration_id": "828aa6fc-f7b5-4305-8584-999999999999",
+ },
+ False,
+ ),
+ (
+ "s3Event.json",
+ {
+ "func": lambda *_: None,
+ "bucket": "lambda-artifacts-9999999999999999",
+ "configuration_id": "828aa6fc-f7b5-4305-8584-487c791949c1",
+ },
+ False,
+ ),
+ # with bucket and configuration_id
+ # unmatch all
+ (
+ "s3Event.json",
+ {
+ "func": lambda *_: None,
+ "bucket": "lambda-artifacts-9999999999999999",
+ "configuration_id": "828aa6fc-f7b5-4305-8584-999999999999",
+ },
+ False,
+ ),
+ # with key and event_name
+ # match all
+ (
+ "s3Event.json",
+ {
+ "func": lambda *_: None,
+ "key": "b21b84d653bb07b05b1e6b33684dc11b",
+ "event_name": "ObjectCreated:Put",
+ },
+ True,
+ ),
+ # with key and event_name
+ # match 1, unmatch 1
+ (
+ "s3Event.json",
+ {
+ "func": lambda *_: None,
+ "key": "b21b84d653bb07b05b1e6b33684dc11b",
+ "event_name": "ObjectCreated:PutV2",
+ },
+ False,
+ ),
+ (
+ "s3Event.json",
+ {
+ "func": lambda *_: None,
+ "key": "99999999999999999999999999999999",
+ "event_name": "ObjectCreated:Put",
+ },
+ False,
+ ),
+ # with key and event_name
+ # unmatch all
+ (
+ "s3Event.json",
+ {
+ "func": lambda *_: None,
+ "key": "99999999999999999999999999999999",
+ "event_name": "ObjectCreated:PutV2",
+ },
+ False,
+ ),
+ # with key and configuration_id
+ # match all
+ (
+ "s3Event.json",
+ {
+ "func": lambda *_: None,
+ "key": "b21b84d653bb07b05b1e6b33684dc11b",
+ "configuration_id": "828aa6fc-f7b5-4305-8584-487c791949c1",
+ },
+ True,
+ ),
+ # with key and configuration_id
+ # match 1, unmatch 1
+ (
+ "s3Event.json",
+ {
+ "func": lambda *_: None,
+ "key": "b21b84d653bb07b05b1e6b33684dc11b",
+ "configuration_id": "828aa6fc-f7b5-4305-8584-999999999999",
+ },
+ False,
+ ),
+ (
+ "s3Event.json",
+ {
+ "func": lambda *_: None,
+ "key": "99999999999999999999999999999999",
+ "configuration_id": "828aa6fc-f7b5-4305-8584-487c791949c1",
+ },
+ False,
+ ),
+ # with key and configuration_id
+ # unmatch all
+ (
+ "s3Event.json",
+ {
+ "func": lambda *_: None,
+ "key": "99999999999999999999999999999999",
+ "configuration_id": "828aa6fc-f7b5-4305-8584-999999999999",
+ },
+ False,
+ ),
+ # with event_name and configuration_id
+ # match all
+ (
+ "s3Event.json",
+ {
+ "func": lambda *_: None,
+ "event_name": "ObjectCreated:Put",
+ "configuration_id": "828aa6fc-f7b5-4305-8584-487c791949c1",
+ },
+ True,
+ ),
+ # with event_name and configuration_id
+ # match 1, unmatch 1
+ (
+ "s3Event.json",
+ {
+ "func": lambda *_: None,
+ "event_name": "ObjectCreated:Put",
+ "configuration_id": "828aa6fc-f7b5-4305-8584-999999999999",
+ },
+ False,
+ ),
+ (
+ "s3Event.json",
+ {
+ "func": lambda *_: None,
+ "event_name": "ObjectCreated:PutV2",
+ "configuration_id": "828aa6fc-f7b5-4305-8584-487c791949c1",
+ },
+ False,
+ ),
+ # with event_name and configuration_id
+ # unmatch all
+ (
+ "s3Event.json",
+ {
+ "func": lambda *_: None,
+ "event_name": "ObjectCreated:PutV2",
+ "configuration_id": "828aa6fc-f7b5-4305-8584-999999999999",
+ },
+ False,
+ ),
+ # with bucket
+ (
+ "s3Event.json",
+ {
+ "func": lambda *_: None,
+ "bucket": "lambda-artifacts-deafc19498e3f2df",
+ },
+ True,
+ ),
+ (
+ "s3Event.json",
+ {
+ "func": lambda *_: None,
+ "bucket": "lambda-artifacts-9999999999999999",
+ },
+ False,
+ ),
+ # with key
+ (
+ "s3Event.json",
+ {
+ "func": lambda *_: None,
+ "key": "b21b84d653bb07b05b1e6b33684dc11b",
+ },
+ True,
+ ),
+ (
+ "s3Event.json",
+ {
+ "func": lambda *_: None,
+ "key": "99999999999999999999999999999999",
+ },
+ False,
+ ),
+ # with event_name
+ (
+ "s3Event.json",
+ {
+ "func": lambda *_: None,
+ "event_name": "ObjectCreated:Put",
+ },
+ True,
+ ),
+ (
+ "s3Event.json",
+ {
+ "func": lambda *_: None,
+ "event_name": "ObjectCreated:PutV2",
+ },
+ False,
+ ),
+ # with configuration_id
+ (
+ "s3Event.json",
+ {"func": lambda *_: None, "configuration_id": "828aa6fc-f7b5-4305-8584-487c791949c1"},
+ True,
+ ),
+ (
+ "s3Event.json",
+ {"func": lambda *_: None, "configuration_id": "828aa6fc-f7b5-4305-8584-999999999999"},
+ False,
+ ),
+ ],
+ )
+ def test_match_for_s3_event(self, event_name, option_constructor, is_match):
+ event = load_event(file_name=event_name)
+ route = S3Route(**option_constructor)
+ actual = route.match(event=event)
+ if is_match:
+ expected = route.func, S3Event(event)
+ assert actual == expected
+ else:
+ assert actual is None
+
+ @pytest.mark.parametrize(
+ "event_name",
+ [
+ "activeMQEvent.json",
+ "albEvent.json",
+ "albEventPathTrailingSlash.json",
+ "albMultiValueHeadersEvent.json",
+ "albMultiValueQueryStringEvent.json",
+ "apiGatewayAuthorizerRequestEvent.json",
+ "apiGatewayAuthorizerTokenEvent.json",
+ "apiGatewayAuthorizerV2Event.json",
+ "apiGatewayProxyEvent.json",
+ "apiGatewayProxyEventAnotherPath.json",
+ "apiGatewayProxyEventNoOrigin.json",
+ "apiGatewayProxyEventPathTrailingSlash.json",
+ "apiGatewayProxyEventPrincipalId.json",
+ "apiGatewayProxyEvent_noVersionAuth.json",
+ "apiGatewayProxyOtherEvent.json",
+ "apiGatewayProxyV2Event.json",
+ "apiGatewayProxyV2EventPathTrailingSlash.json",
+ "apiGatewayProxyV2Event_GET.json",
+ "apiGatewayProxyV2IamEvent.json",
+ "apiGatewayProxyV2LambdaAuthorizerEvent.json",
+ "apiGatewayProxyV2OtherGetEvent.json",
+ "apiGatewayProxyV2SchemaMiddlwareInvalidEvent.json",
+ "apiGatewayProxyV2SchemaMiddlwareValidEvent.json",
+ "apigatewayeSchemaMiddlwareInvalidEvent.json",
+ "apigatewayeSchemaMiddlwareValidEvent.json",
+ "appSyncAuthorizerEvent.json",
+ "appSyncAuthorizerResponse.json",
+ "appSyncBatchEvent.json",
+ "appSyncDirectResolver.json",
+ "appSyncResolverEvent.json",
+ "awsConfigRuleConfigurationChanged.json",
+ "awsConfigRuleOversizedConfiguration.json",
+ "awsConfigRuleScheduled.json",
+ "bedrockAgentEvent.json",
+ "bedrockAgentEventWithPathParams.json",
+ "bedrockAgentPostEvent.json",
+ "cloudWatchAlarmEventCompositeMetric.json",
+ "cloudWatchAlarmEventSingleMetric.json",
+ "cloudWatchDashboardEvent.json",
+ "cloudWatchLogEvent.json",
+ "cloudWatchLogEventWithPolicyLevel.json",
+ "cloudformationCustomResourceCreate.json",
+ "cloudformationCustomResourceDelete.json",
+ "cloudformationCustomResourceUpdate.json",
+ "codeDeployLifecycleHookEvent.json",
+ "codePipelineEvent.json",
+ "codePipelineEventData.json",
+ "codePipelineEventEmptyUserParameters.json",
+ "codePipelineEventWithEncryptionKey.json",
+ "cognitoCreateAuthChallengeEvent.json",
+ "cognitoCustomEmailSenderEvent.json",
+ "cognitoCustomMessageEvent.json",
+ "cognitoCustomSMSSenderEvent.json",
+ "cognitoDefineAuthChallengeEvent.json",
+ "cognitoPostAuthenticationEvent.json",
+ "cognitoPostConfirmationEvent.json",
+ "cognitoPreAuthenticationEvent.json",
+ "cognitoPreSignUpEvent.json",
+ "cognitoPreTokenGenerationEvent.json",
+ "cognitoPreTokenV2GenerationEvent.json",
+ "cognitoUserMigrationEvent.json",
+ "cognitoVerifyAuthChallengeResponseEvent.json",
+ "connectContactFlowEventAll.json",
+ "connectContactFlowEventMin.json",
+ "dynamoStreamEvent.json",
+ "eventBridgeEvent.json",
+ "kafkaEventMsk.json",
+ "kafkaEventSelfManaged.json",
+ "kinesisFirehoseKinesisEvent.json",
+ "kinesisFirehosePutEvent.json",
+ "kinesisFirehoseSQSEvent.json",
+ "kinesisStreamCloudWatchLogsEvent.json",
+ "kinesisStreamEvent.json",
+ "kinesisStreamEventOneRecord.json",
+ "lambdaFunctionUrlEvent.json",
+ "lambdaFunctionUrlEventPathTrailingSlash.json",
+ "lambdaFunctionUrlEventWithHeaders.json",
+ "lambdaFunctionUrlIAMEvent.json",
+ "rabbitMQEvent.json",
+ "s3BatchOperationEventSchemaV1.json",
+ "s3BatchOperationEventSchemaV2.json",
+ "s3EventBridgeNotificationObjectCreatedEvent.json",
+ "s3EventBridgeNotificationObjectDeletedEvent.json",
+ "s3EventBridgeNotificationObjectExpiredEvent.json",
+ "s3EventBridgeNotificationObjectRestoreCompletedEvent.json",
+ "s3ObjectEventIAMUser.json",
+ "s3ObjectEventTempCredentials.json",
+ "s3SqsEvent.json",
+ "secretsManagerEvent.json",
+ "sesEvent.json",
+ "snsEvent.json",
+ "snsSqsEvent.json",
+ "snsSqsFifoEvent.json",
+ "sqsDlqTriggerEvent.json",
+ "sqsEvent.json",
+ "vpcLatticeEvent.json",
+ "vpcLatticeEventPathTrailingSlash.json",
+ "vpcLatticeEventV2PathTrailingSlash.json",
+ "vpcLatticeV2Event.json",
+ "vpcLatticeV2EventWithHeaders.json",
+ ],
+ )
+ def test_match_for_not_s3_event(self, event_name):
+ event = load_event(file_name=event_name)
+ route = S3Route(func=None, bucket="lambda-artifacts-deafc19498e3f2df")
+ actual = route.match(event=event)
+ assert actual is None
diff --git a/tests/unit/event_handler/_async_execution/_routes/test_secrets_manager.py b/tests/unit/event_handler/_async_execution/_routes/test_secrets_manager.py
new file mode 100644
index 00000000000..cbe5496d989
--- /dev/null
+++ b/tests/unit/event_handler/_async_execution/_routes/test_secrets_manager.py
@@ -0,0 +1,209 @@
+import pytest
+
+from aws_lambda_powertools.event_handler.async_execution.routes.secrets_manager import SecretsManagerRoute
+from aws_lambda_powertools.utilities.data_classes.secrets_manager_event import SecretsManagerEvent
+from tests.functional.utils import load_event
+
+
+class TestSecretsManagerRoute:
+ def test_constructor_error(self):
+ with pytest.raises(ValueError):
+ SecretsManagerRoute(func=lambda _: None)
+
+ @pytest.mark.parametrize(
+ "event_name, option_constructor, is_match",
+ [
+ # with secret_id and secret_name_prefix
+ # match all
+ (
+ "secretsManagerEvent.json",
+ {
+ "func": lambda *_: None,
+ "secret_id": "arn:aws:secretsmanager:us-west-2:123456789012:secret:MyTestDatabaseSecret-a1b2c3",
+ "secret_name_prefix": "MyTestDatabaseSecret",
+ },
+ True,
+ ),
+ # with secret_id and secret_name_prefix
+ # match 1, unmatch 1
+ (
+ "secretsManagerEvent.json",
+ {
+ "func": lambda *_: None,
+ "secret_id": "arn:aws:secretsmanager:us-west-2:123456789012:secret:MyTestDatabaseSecret-a1b2c3",
+ "secret_name_prefix": "MyTestDatabaseSecretV2",
+ },
+ False,
+ ),
+ (
+ "secretsManagerEvent.json",
+ {
+ "func": lambda *_: None,
+ "secret_id": "arn:aws:secretsmanager:us-west-2:123456789012:secret:MyTestDatabaseSecret-999999",
+ "secret_name_prefix": "MyTestDatabaseSecret",
+ },
+ False,
+ ),
+ # with secret_id and secret_name_prefix
+ # unmatch all
+ (
+ "secretsManagerEvent.json",
+ {
+ "func": lambda *_: None,
+ "secret_id": "arn:aws:secretsmanager:us-west-2:123456789012:secret:MyTestDatabaseSecret-999999",
+ "secret_name_prefix": "MyTestDatabaseSecretV2",
+ },
+ False,
+ ),
+ # with secret_id
+ (
+ "secretsManagerEvent.json",
+ {
+ "func": lambda *_: None,
+ "secret_id": "arn:aws:secretsmanager:us-west-2:123456789012:secret:MyTestDatabaseSecret-a1b2c3",
+ },
+ True,
+ ),
+ (
+ "secretsManagerEvent.json",
+ {
+ "func": lambda *_: None,
+ "secret_id": "arn:aws:secretsmanager:us-west-2:123456789012:secret:MyTestDatabaseSecret-999999",
+ },
+ False,
+ ),
+ # with secret_name_prefix
+ ("secretsManagerEvent.json", {"func": lambda *_: None, "secret_name_prefix": "MyTestDatabaseSecret"}, True),
+ (
+ "secretsManagerEvent.json",
+ {"func": lambda *_: None, "secret_name_prefix": "MyTestDatabaseSecretV2"},
+ False,
+ ),
+ ],
+ )
+ def test_match(self, event_name, option_constructor, is_match):
+ event = load_event(file_name=event_name)
+ route = SecretsManagerRoute(**option_constructor)
+ actual = route.match(event=event)
+ if is_match:
+ expected = (route.func, SecretsManagerEvent(event))
+ assert actual == expected
+ else:
+ assert actual is None
+
+ @pytest.mark.parametrize(
+ "event_name",
+ [
+ "activeMQEvent.json",
+ "albEvent.json",
+ "albEventPathTrailingSlash.json",
+ "albMultiValueHeadersEvent.json",
+ "albMultiValueQueryStringEvent.json",
+ "apiGatewayAuthorizerRequestEvent.json",
+ "apiGatewayAuthorizerTokenEvent.json",
+ "apiGatewayAuthorizerV2Event.json",
+ "apiGatewayProxyEvent.json",
+ "apiGatewayProxyEventAnotherPath.json",
+ "apiGatewayProxyEventNoOrigin.json",
+ "apiGatewayProxyEventPathTrailingSlash.json",
+ "apiGatewayProxyEventPrincipalId.json",
+ "apiGatewayProxyEvent_noVersionAuth.json",
+ "apiGatewayProxyOtherEvent.json",
+ "apiGatewayProxyV2Event.json",
+ "apiGatewayProxyV2EventPathTrailingSlash.json",
+ "apiGatewayProxyV2Event_GET.json",
+ "apiGatewayProxyV2IamEvent.json",
+ "apiGatewayProxyV2LambdaAuthorizerEvent.json",
+ "apiGatewayProxyV2OtherGetEvent.json",
+ "apiGatewayProxyV2SchemaMiddlwareInvalidEvent.json",
+ "apiGatewayProxyV2SchemaMiddlwareValidEvent.json",
+ "apigatewayeSchemaMiddlwareInvalidEvent.json",
+ "apigatewayeSchemaMiddlwareValidEvent.json",
+ "appSyncAuthorizerEvent.json",
+ "appSyncAuthorizerResponse.json",
+ "appSyncBatchEvent.json",
+ "appSyncDirectResolver.json",
+ "appSyncResolverEvent.json",
+ "awsConfigRuleConfigurationChanged.json",
+ "awsConfigRuleOversizedConfiguration.json",
+ "awsConfigRuleScheduled.json",
+ "bedrockAgentEvent.json",
+ "bedrockAgentEventWithPathParams.json",
+ "bedrockAgentPostEvent.json",
+ "cloudWatchAlarmEventCompositeMetric.json",
+ "cloudWatchAlarmEventSingleMetric.json",
+ "cloudWatchDashboardEvent.json",
+ "cloudWatchLogEvent.json",
+ "cloudWatchLogEventWithPolicyLevel.json",
+ "cloudformationCustomResourceCreate.json",
+ "cloudformationCustomResourceDelete.json",
+ "cloudformationCustomResourceUpdate.json",
+ "codeDeployLifecycleHookEvent.json",
+ "codePipelineEvent.json",
+ "codePipelineEventData.json",
+ "codePipelineEventEmptyUserParameters.json",
+ "codePipelineEventWithEncryptionKey.json",
+ "cognitoCreateAuthChallengeEvent.json",
+ "cognitoCustomEmailSenderEvent.json",
+ "cognitoCustomMessageEvent.json",
+ "cognitoCustomSMSSenderEvent.json",
+ "cognitoDefineAuthChallengeEvent.json",
+ "cognitoPostAuthenticationEvent.json",
+ "cognitoPostConfirmationEvent.json",
+ "cognitoPreAuthenticationEvent.json",
+ "cognitoPreSignUpEvent.json",
+ "cognitoPreTokenGenerationEvent.json",
+ "cognitoPreTokenV2GenerationEvent.json",
+ "cognitoUserMigrationEvent.json",
+ "cognitoVerifyAuthChallengeResponseEvent.json",
+ "connectContactFlowEventAll.json",
+ "connectContactFlowEventMin.json",
+ "dynamoStreamEvent.json",
+ "eventBridgeEvent.json",
+ "kafkaEventMsk.json",
+ "kafkaEventSelfManaged.json",
+ "kinesisFirehoseKinesisEvent.json",
+ "kinesisFirehosePutEvent.json",
+ "kinesisFirehoseSQSEvent.json",
+ "kinesisStreamCloudWatchLogsEvent.json",
+ "kinesisStreamEvent.json",
+ "kinesisStreamEventOneRecord.json",
+ "lambdaFunctionUrlEvent.json",
+ "lambdaFunctionUrlEventPathTrailingSlash.json",
+ "lambdaFunctionUrlEventWithHeaders.json",
+ "lambdaFunctionUrlIAMEvent.json",
+ "rabbitMQEvent.json",
+ "s3BatchOperationEventSchemaV1.json",
+ "s3BatchOperationEventSchemaV2.json",
+ "s3Event.json",
+ "s3EventBridgeNotificationObjectCreatedEvent.json",
+ "s3EventBridgeNotificationObjectDeletedEvent.json",
+ "s3EventBridgeNotificationObjectExpiredEvent.json",
+ "s3EventBridgeNotificationObjectRestoreCompletedEvent.json",
+ "s3EventDecodedKey.json",
+ "s3EventDeleteObject.json",
+ "s3EventGlacier.json",
+ "s3ObjectEventIAMUser.json",
+ "s3ObjectEventTempCredentials.json",
+ "s3SqsEvent.json",
+ "sesEvent.json",
+ "snsEvent.json",
+ "snsSqsEvent.json",
+ "snsSqsFifoEvent.json",
+ "sqsDlqTriggerEvent.json",
+ "sqsEvent.json",
+ "vpcLatticeEvent.json",
+ "vpcLatticeEventPathTrailingSlash.json",
+ "vpcLatticeEventV2PathTrailingSlash.json",
+ "vpcLatticeV2Event.json",
+ "vpcLatticeV2EventWithHeaders.json",
+ ],
+ )
+ def test_match_for_not_secrets_manager_event(self, event_name):
+ event = load_event(file_name=event_name)
+ route = SecretsManagerRoute(
+ func=None,
+ secret_id="arn:aws:secretsmanager:us-west-2:123456789012:secret:MyTestDatabaseSecret-a1b2c3",
+ )
+ actual = route.match(event=event)
+ assert actual is None
diff --git a/tests/unit/event_handler/_async_execution/_routes/test_ses.py b/tests/unit/event_handler/_async_execution/_routes/test_ses.py
new file mode 100644
index 00000000000..ebe4b4441fe
--- /dev/null
+++ b/tests/unit/event_handler/_async_execution/_routes/test_ses.py
@@ -0,0 +1,120 @@
+import pytest
+
+from aws_lambda_powertools.event_handler.async_execution.routes.ses import SESRoute
+from tests.functional.utils import load_event
+
+
+class TestSESRoute:
+ @pytest.mark.parametrize(
+ "event_name",
+ [
+ "activeMQEvent.json",
+ "albEvent.json",
+ "albEventPathTrailingSlash.json",
+ "albMultiValueHeadersEvent.json",
+ "albMultiValueQueryStringEvent.json",
+ "apiGatewayAuthorizerRequestEvent.json",
+ "apiGatewayAuthorizerTokenEvent.json",
+ "apiGatewayAuthorizerV2Event.json",
+ "apiGatewayProxyEvent.json",
+ "apiGatewayProxyEventAnotherPath.json",
+ "apiGatewayProxyEventNoOrigin.json",
+ "apiGatewayProxyEventPathTrailingSlash.json",
+ "apiGatewayProxyEventPrincipalId.json",
+ "apiGatewayProxyEvent_noVersionAuth.json",
+ "apiGatewayProxyOtherEvent.json",
+ "apiGatewayProxyV2Event.json",
+ "apiGatewayProxyV2EventPathTrailingSlash.json",
+ "apiGatewayProxyV2Event_GET.json",
+ "apiGatewayProxyV2IamEvent.json",
+ "apiGatewayProxyV2LambdaAuthorizerEvent.json",
+ "apiGatewayProxyV2OtherGetEvent.json",
+ "apiGatewayProxyV2SchemaMiddlwareInvalidEvent.json",
+ "apiGatewayProxyV2SchemaMiddlwareValidEvent.json",
+ "apigatewayeSchemaMiddlwareInvalidEvent.json",
+ "apigatewayeSchemaMiddlwareValidEvent.json",
+ "appSyncAuthorizerEvent.json",
+ "appSyncAuthorizerResponse.json",
+ "appSyncBatchEvent.json",
+ "appSyncDirectResolver.json",
+ "appSyncResolverEvent.json",
+ "awsConfigRuleConfigurationChanged.json",
+ "awsConfigRuleOversizedConfiguration.json",
+ "awsConfigRuleScheduled.json",
+ "bedrockAgentEvent.json",
+ "bedrockAgentEventWithPathParams.json",
+ "bedrockAgentPostEvent.json",
+ "cloudWatchAlarmEventCompositeMetric.json",
+ "cloudWatchAlarmEventSingleMetric.json",
+ "cloudWatchDashboardEvent.json",
+ "cloudWatchLogEvent.json",
+ "cloudWatchLogEventWithPolicyLevel.json",
+ "cloudformationCustomResourceCreate.json",
+ "cloudformationCustomResourceDelete.json",
+ "cloudformationCustomResourceUpdate.json",
+ "codeDeployLifecycleHookEvent.json",
+ "codePipelineEvent.json",
+ "codePipelineEventData.json",
+ "codePipelineEventEmptyUserParameters.json",
+ "codePipelineEventWithEncryptionKey.json",
+ "cognitoCreateAuthChallengeEvent.json",
+ "cognitoCustomEmailSenderEvent.json",
+ "cognitoCustomMessageEvent.json",
+ "cognitoCustomSMSSenderEvent.json",
+ "cognitoDefineAuthChallengeEvent.json",
+ "cognitoPostAuthenticationEvent.json",
+ "cognitoPostConfirmationEvent.json",
+ "cognitoPreAuthenticationEvent.json",
+ "cognitoPreSignUpEvent.json",
+ "cognitoPreTokenGenerationEvent.json",
+ "cognitoPreTokenV2GenerationEvent.json",
+ "cognitoUserMigrationEvent.json",
+ "cognitoVerifyAuthChallengeResponseEvent.json",
+ "connectContactFlowEventAll.json",
+ "connectContactFlowEventMin.json",
+ "dynamoStreamEvent.json",
+ "eventBridgeEvent.json",
+ "kafkaEventMsk.json",
+ "kafkaEventSelfManaged.json",
+ "kinesisFirehoseKinesisEvent.json",
+ "kinesisFirehosePutEvent.json",
+ "kinesisFirehoseSQSEvent.json",
+ "kinesisStreamCloudWatchLogsEvent.json",
+ "kinesisStreamEvent.json",
+ "kinesisStreamEventOneRecord.json",
+ "lambdaFunctionUrlEvent.json",
+ "lambdaFunctionUrlEventPathTrailingSlash.json",
+ "lambdaFunctionUrlEventWithHeaders.json",
+ "lambdaFunctionUrlIAMEvent.json",
+ "rabbitMQEvent.json",
+ "s3BatchOperationEventSchemaV1.json",
+ "s3BatchOperationEventSchemaV2.json",
+ "s3Event.json",
+ "s3EventBridgeNotificationObjectCreatedEvent.json",
+ "s3EventBridgeNotificationObjectDeletedEvent.json",
+ "s3EventBridgeNotificationObjectExpiredEvent.json",
+ "s3EventBridgeNotificationObjectRestoreCompletedEvent.json",
+ "s3EventDecodedKey.json",
+ "s3EventDeleteObject.json",
+ "s3EventGlacier.json",
+ "s3ObjectEventIAMUser.json",
+ "s3ObjectEventTempCredentials.json",
+ "s3SqsEvent.json",
+ "secretsManagerEvent.json",
+ "snsEvent.json",
+ "snsSqsEvent.json",
+ "snsSqsFifoEvent.json",
+ "sqsDlqTriggerEvent.json",
+ "sqsEvent.json",
+ "vpcLatticeEvent.json",
+ "vpcLatticeEventPathTrailingSlash.json",
+ "vpcLatticeEventV2PathTrailingSlash.json",
+ "vpcLatticeV2Event.json",
+ "vpcLatticeV2EventWithHeaders.json",
+ ],
+ )
+ def test_match_for_not_ses_event(self, event_name):
+ event = load_event(file_name=event_name)
+ route = SESRoute(func=None, mail_to=["johndoe@example.com"])
+ actual = route.match(event=event)
+ assert actual is None
diff --git a/tests/unit/event_handler/_async_execution/_routes/test_sns.py b/tests/unit/event_handler/_async_execution/_routes/test_sns.py
new file mode 100644
index 00000000000..e4dbb952e95
--- /dev/null
+++ b/tests/unit/event_handler/_async_execution/_routes/test_sns.py
@@ -0,0 +1,120 @@
+import pytest
+
+from aws_lambda_powertools.event_handler.async_execution.routes.sns import SNSRoute
+from tests.functional.utils import load_event
+
+
+class TestSNSRoute:
+ @pytest.mark.parametrize(
+ "event_name",
+ [
+ "activeMQEvent.json",
+ "albEvent.json",
+ "albEventPathTrailingSlash.json",
+ "albMultiValueHeadersEvent.json",
+ "albMultiValueQueryStringEvent.json",
+ "apiGatewayAuthorizerRequestEvent.json",
+ "apiGatewayAuthorizerTokenEvent.json",
+ "apiGatewayAuthorizerV2Event.json",
+ "apiGatewayProxyEvent.json",
+ "apiGatewayProxyEventAnotherPath.json",
+ "apiGatewayProxyEventNoOrigin.json",
+ "apiGatewayProxyEventPathTrailingSlash.json",
+ "apiGatewayProxyEventPrincipalId.json",
+ "apiGatewayProxyEvent_noVersionAuth.json",
+ "apiGatewayProxyOtherEvent.json",
+ "apiGatewayProxyV2Event.json",
+ "apiGatewayProxyV2EventPathTrailingSlash.json",
+ "apiGatewayProxyV2Event_GET.json",
+ "apiGatewayProxyV2IamEvent.json",
+ "apiGatewayProxyV2LambdaAuthorizerEvent.json",
+ "apiGatewayProxyV2OtherGetEvent.json",
+ "apiGatewayProxyV2SchemaMiddlwareInvalidEvent.json",
+ "apiGatewayProxyV2SchemaMiddlwareValidEvent.json",
+ "apigatewayeSchemaMiddlwareInvalidEvent.json",
+ "apigatewayeSchemaMiddlwareValidEvent.json",
+ "appSyncAuthorizerEvent.json",
+ "appSyncAuthorizerResponse.json",
+ "appSyncBatchEvent.json",
+ "appSyncDirectResolver.json",
+ "appSyncResolverEvent.json",
+ "awsConfigRuleConfigurationChanged.json",
+ "awsConfigRuleOversizedConfiguration.json",
+ "awsConfigRuleScheduled.json",
+ "bedrockAgentEvent.json",
+ "bedrockAgentEventWithPathParams.json",
+ "bedrockAgentPostEvent.json",
+ "cloudWatchAlarmEventCompositeMetric.json",
+ "cloudWatchAlarmEventSingleMetric.json",
+ "cloudWatchDashboardEvent.json",
+ "cloudWatchLogEvent.json",
+ "cloudWatchLogEventWithPolicyLevel.json",
+ "cloudformationCustomResourceCreate.json",
+ "cloudformationCustomResourceDelete.json",
+ "cloudformationCustomResourceUpdate.json",
+ "codeDeployLifecycleHookEvent.json",
+ "codePipelineEvent.json",
+ "codePipelineEventData.json",
+ "codePipelineEventEmptyUserParameters.json",
+ "codePipelineEventWithEncryptionKey.json",
+ "cognitoCreateAuthChallengeEvent.json",
+ "cognitoCustomEmailSenderEvent.json",
+ "cognitoCustomMessageEvent.json",
+ "cognitoCustomSMSSenderEvent.json",
+ "cognitoDefineAuthChallengeEvent.json",
+ "cognitoPostAuthenticationEvent.json",
+ "cognitoPostConfirmationEvent.json",
+ "cognitoPreAuthenticationEvent.json",
+ "cognitoPreSignUpEvent.json",
+ "cognitoPreTokenGenerationEvent.json",
+ "cognitoPreTokenV2GenerationEvent.json",
+ "cognitoUserMigrationEvent.json",
+ "cognitoVerifyAuthChallengeResponseEvent.json",
+ "connectContactFlowEventAll.json",
+ "connectContactFlowEventMin.json",
+ "dynamoStreamEvent.json",
+ "eventBridgeEvent.json",
+ "kafkaEventMsk.json",
+ "kafkaEventSelfManaged.json",
+ "kinesisFirehoseKinesisEvent.json",
+ "kinesisFirehosePutEvent.json",
+ "kinesisFirehoseSQSEvent.json",
+ "kinesisStreamCloudWatchLogsEvent.json",
+ "kinesisStreamEvent.json",
+ "kinesisStreamEventOneRecord.json",
+ "lambdaFunctionUrlEvent.json",
+ "lambdaFunctionUrlEventPathTrailingSlash.json",
+ "lambdaFunctionUrlEventWithHeaders.json",
+ "lambdaFunctionUrlIAMEvent.json",
+ "rabbitMQEvent.json",
+ "s3BatchOperationEventSchemaV1.json",
+ "s3BatchOperationEventSchemaV2.json",
+ "s3Event.json",
+ "s3EventBridgeNotificationObjectCreatedEvent.json",
+ "s3EventBridgeNotificationObjectDeletedEvent.json",
+ "s3EventBridgeNotificationObjectExpiredEvent.json",
+ "s3EventBridgeNotificationObjectRestoreCompletedEvent.json",
+ "s3EventDecodedKey.json",
+ "s3EventDeleteObject.json",
+ "s3EventGlacier.json",
+ "s3ObjectEventIAMUser.json",
+ "s3ObjectEventTempCredentials.json",
+ "s3SqsEvent.json",
+ "secretsManagerEvent.json",
+ "sesEvent.json",
+ "snsSqsEvent.json",
+ "snsSqsFifoEvent.json",
+ "sqsDlqTriggerEvent.json",
+ "sqsEvent.json",
+ "vpcLatticeEvent.json",
+ "vpcLatticeEventPathTrailingSlash.json",
+ "vpcLatticeEventV2PathTrailingSlash.json",
+ "vpcLatticeV2Event.json",
+ "vpcLatticeV2EventWithHeaders.json",
+ ],
+ )
+ def test_match_for_not_sns_event(self, event_name):
+ event = load_event(file_name=event_name)
+ route = SNSRoute(func=None, arn="arn:aws:sns:us-east-2:123456789012:sns-lambda")
+ actual = route.match(event=event)
+ assert actual is None