Repository: getmoto/moto
Pull request #7252 (2024-01-26 10:52:55)

S3: Adding notification for eventbridge
Implement S3 EventBridge notification.
- Expand notification configuration API.
- Send notifications to EventBridge backend (skip tests when moto is in proxy or server mode). 
- Testing on `put_object` notification.

Document of EventBridge event message structure: https://docs.aws.amazon.com/AmazonS3/latest/userguide/ev-events.html

Document of EventBridge notification: https://docs.aws.amazon.com/AmazonS3/latest/userguide/EventBridge.html


codecov[bot]: ## [Codecov](https://app.codecov.io/gh/getmoto/moto/pull/7252?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) Report
Attention: `24 lines` in your changes are missing coverage. Please review.
> Comparison is base [(`9aef694`)](https://app.codecov.io/gh/getmoto/moto/commit/9aef6944da6a6f1b84be99cefb34f1724e6ca58f?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) 95.88% compared to head [(`9d9abc7`)](https://app.codecov.io/gh/getmoto/moto/pull/7252?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) 95.86%.
> Report is 10 commits behind head on master.

| [Files](https://app.codecov.io/gh/getmoto/moto/pull/7252?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) | Patch % | Lines |
|---|---|---|
| [moto/s3/notifications.py](https://app.codecov.io/gh/getmoto/moto/pull/7252?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto#diff-bW90by9zMy9ub3RpZmljYXRpb25zLnB5) | 52.27% | [21 Missing :warning: ](https://app.codecov.io/gh/getmoto/moto/pull/7252?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) |
| [moto/autoscaling/models.py](https://app.codecov.io/gh/getmoto/moto/pull/7252?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto#diff-bW90by9hdXRvc2NhbGluZy9tb2RlbHMucHk=) | 91.30% | [2 Missing :warning: ](https://app.codecov.io/gh/getmoto/moto/pull/7252?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) |
| [moto/route53/models.py](https://app.codecov.io/gh/getmoto/moto/pull/7252?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto#diff-bW90by9yb3V0ZTUzL21vZGVscy5weQ==) | 90.90% | [1 Missing :warning: ](https://app.codecov.io/gh/getmoto/moto/pull/7252?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master    #7252      +/-   ##
==========================================
- Coverage   95.88%   95.86%   -0.03%     
==========================================
  Files         843      843              
  Lines       82578    82679     +101     
==========================================
+ Hits        79178    79258      +80     
- Misses       3400     3421      +21     
```

| [Flag](https://app.codecov.io/gh/getmoto/moto/pull/7252/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) | Coverage Δ | |
|---|---|---|
| [servertests](https://app.codecov.io/gh/getmoto/moto/pull/7252/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) | `32.50% <32.76%> (-0.02%)` | :arrow_down: |
| [unittests](https://app.codecov.io/gh/getmoto/moto/pull/7252/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) | `95.82% <89.78%> (-0.03%)` | :arrow_down: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto#carryforward-flags-in-the-pull-request-comment) to find out more.


</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/getmoto/moto/pull/7252?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto).


Patch:
diff --git a/moto/awslambda/models.py b/moto/awslambda/models.py
index bb402f493..8b6ca83a9 100644
--- a/moto/awslambda/models.py
+++ b/moto/awslambda/models.py
@@ -1001,6 +1001,10 @@ class LambdaFunction(CloudFormationModel, DockerModel):
                             "host.docker.internal": "host-gateway"
                         }
 
+                    # Change entry point if requested
+                    if self.image_config.entry_point:
+                        run_kwargs["entrypoint"] = self.image_config.entry_point
+
                     # The requested image can be found in one of a few repos:
                     # - User-provided repo
                     # - mlupin/docker-lambda (the repo with up-to-date AWSLambda images
diff --git a/moto/dynamodb/responses.py b/moto/dynamodb/responses.py
index 5e8f93fd9..03aeea2f3 100644
--- a/moto/dynamodb/responses.py
+++ b/moto/dynamodb/responses.py
@@ -237,6 +237,14 @@ class DynamoHandler(BaseResponse):
                 raise UnknownKeyType(
                     key_type=key_type, position=f"keySchema.{idx}.member.keyType"
                 )
+        if len(key_schema) > 2:
+            key_elements = [
+                f"KeySchemaElement(attributeName={key.get('AttributeName')}, keyType={key['KeyType']})"
+                for key in key_schema
+            ]
+            provided_keys = ", ".join(key_elements)
+            err = f"1 validation error detected: Value '[{provided_keys}]' at 'keySchema' failed to satisfy constraint: Member must have length less than or equal to 2"
+            raise MockValidationException(err)
         # getting attribute definition
         attr = body["AttributeDefinitions"]
 
diff --git a/moto/rds/exceptions.py b/moto/rds/exceptions.py
index cc78be6d0..678d1a78c 100644
--- a/moto/rds/exceptions.py
+++ b/moto/rds/exceptions.py
@@ -218,3 +218,35 @@ class InvalidDBInstanceEngine(InvalidParameterCombination):
             f"The engine name requested for your DB instance ({instance_engine}) doesn't match "
             f"the engine name of your DB cluster ({cluster_engine})."
         )
+
+
+class InvalidSubnet(RDSClientError):
+    def __init__(self, subnet_identifier: str):
+        super().__init__(
+            "InvalidSubnet",
+            f"The requested subnet {subnet_identifier} is invalid, or multiple subnets were requested that are not all in a common VPC.",
+        )
+
+
+class DBProxyAlreadyExistsFault(RDSClientError):
+    def __init__(self, db_proxy_identifier: str):
+        super().__init__(
+            "DBProxyAlreadyExistsFault",
+            f"Cannot create the DBProxy because a DBProxy with the identifier {db_proxy_identifier} already exists.",
+        )
+
+
+class DBProxyQuotaExceededFault(RDSClientError):
+    def __init__(self) -> None:
+        super().__init__(
+            "DBProxyQuotaExceeded",
+            "The request cannot be processed because it would exceed the maximum number of DBProxies.",
+        )
+
+
+class DBProxyNotFoundFault(RDSClientError):
+    def __init__(self, db_proxy_identifier: str):
+        super().__init__(
+            "DBProxyNotFoundFault",
+            f"The specified proxy name {db_proxy_identifier} doesn't correspond to a proxy owned by your Amazon Web Services account in the specified Amazon Web Services Region.",
+        )
diff --git a/moto/rds/models.py b/moto/rds/models.py
index 460360be5..18ffab32f 100644
--- a/moto/rds/models.py
+++ b/moto/rds/models.py
@@ -24,6 +24,9 @@ from .exceptions import (
     DBClusterToBeDeletedHasActiveMembers,
     DBInstanceNotFoundError,
     DBParameterGroupNotFoundError,
+    DBProxyAlreadyExistsFault,
+    DBProxyNotFoundFault,
+    DBProxyQuotaExceededFault,
     DBSecurityGroupNotFoundError,
     DBSnapshotAlreadyExistsError,
     DBSnapshotNotFoundError,
@@ -39,6 +42,7 @@ from .exceptions import (
     InvalidGlobalClusterStateFault,
     InvalidParameterCombination,
     InvalidParameterValue,
+    InvalidSubnet,
     OptionGroupNotFoundFaultError,
     RDSClientError,
     SnapshotQuotaExceededError,
@@ -1573,11 +1577,128 @@ class SubnetGroup(CloudFormationModel):
         backend.delete_subnet_group(self.subnet_name)
 
 
+class DBProxy(BaseModel):
+    def __init__(
+        self,
+        db_proxy_name: str,
+        engine_family: str,
+        auth: List[Dict[str, str]],
+        role_arn: str,
+        vpc_subnet_ids: List[str],
+        region_name: str,
+        account_id: str,
+        vpc_security_group_ids: Optional[List[str]],
+        require_tls: Optional[bool] = False,
+        idle_client_timeout: Optional[int] = 1800,
+        debug_logging: Optional[bool] = False,
+        tags: Optional[List[Dict[str, str]]] = None,
+    ):
+        self.db_proxy_name = db_proxy_name
+        self.engine_family = engine_family
+        if self.engine_family not in ["MYSQL", "POSTGRESQ", "SQLSERVER"]:
+            raise InvalidParameterValue("Provided EngineFamily is not valid.")
+        self.auth = auth
+        self.role_arn = role_arn
+        self.vpc_subnet_ids = vpc_subnet_ids
+        self.vpc_security_group_ids = vpc_security_group_ids
+        self.require_tls = require_tls
+        if idle_client_timeout is None:
+            self.idle_client_timeout = 1800
+        else:
+            if int(idle_client_timeout) < 1:
+                self.idle_client_timeout = 1
+            elif int(idle_client_timeout) > 28800:
+                self.idle_client_timeout = 28800
+            else:
+                self.idle_client_timeout = idle_client_timeout
+        self.debug_logging = debug_logging
+        self.created_date = iso_8601_datetime_with_milliseconds()
+        self.updated_date = iso_8601_datetime_with_milliseconds()
+        if tags is None:
+            self.tags = []
+        else:
+            self.tags = tags
+        self.region_name = region_name
+        self.account_id = account_id
+        self.db_proxy_arn = f"arn:aws:rds:{self.region_name}:{self.account_id}:db-proxy:{self.db_proxy_name}"
+        self.arn = self.db_proxy_arn
+        ec2_backend = ec2_backends[self.account_id][self.region_name]
+        subnets = ec2_backend.describe_subnets(subnet_ids=self.vpc_subnet_ids)
+        vpcs = []
+        for subnet in subnets:
+            vpcs.append(subnet.vpc_id)
+            if subnet.vpc_id != vpcs[0]:
+                raise InvalidSubnet(subnet_identifier=subnet.id)
+
+        self.vpc_id = ec2_backend.describe_subnets(subnet_ids=[self.vpc_subnet_ids[0]])[
+            0
+        ].vpc_id
+        self.status = "availible"
+        self.url_identifier = "".join(
+            random.choice(string.ascii_lowercase + string.digits) for _ in range(12)
+        )
+        self.endpoint = f"{self.db_proxy_name}.db-proxy-{self.url_identifier}.{self.region_name}.rds.amazonaws.com"
+
+    def get_tags(self) -> List[Dict[str, str]]:
+        return self.tags
+
+    def add_tags(self, tags: List[Dict[str, str]]) -> List[Dict[str, str]]:
+        new_keys = [tag_set["Key"] for tag_set in tags]
+        self.tags = [tag_set for tag_set in self.tags if tag_set["Key"] not in new_keys]
+        self.tags.extend(tags)
+        return self.tags
+
+    def remove_tags(self, tag_keys: List[str]) -> None:
+        self.tags = [tag_set for tag_set in self.tags if tag_set["Key"] not in tag_keys]
+
+    def to_xml(self) -> str:
+        template = Template(
+            """
+                <RequireTLS>{{ dbproxy.require_tls }}</RequireTLS>
+                <VpcSecurityGroupIds>
+                {% if dbproxy.VpcSecurityGroupIds %}
+                  {% for vpcsecuritygroupid in dbproxy.VpcSecurityGroupIds %}
+                    <member>{{ vpcsecuritygroupid }}</member>
+                  {% endfor %}
+                {% endif %}
+                </VpcSecurityGroupIds>
+                <Auth>
+                  {% for auth in dbproxy.auth %}
+                    <member>
+                        <UserName>{{ auth["UserName"] }}</UserName>
+                        <AuthScheme>{{ auth["AuthScheme"] }}</AuthScheme>
+                        <SecretArn>{{ auth["SecretArn"] }}</SecretArn>
+                        <IAMAuth>{{ auth["IAMAuth"] }}</IAMAuth>
+                        <ClientPasswordAuthType>{{ auth["ClientPasswordAuthType"] }}</ClientPasswordAuthType>
+                    </member>
+                  {% endfor %}
+                </Auth>
+                <EngineFamily>{{ dbproxy.engine_family }}</EngineFamily>
+                <UpdatedDate>{{ dbproxy.updated_date }}</UpdatedDate>
+                <DBProxyName>{{ dbproxy.db_proxy_name }}</DBProxyName>
+                <IdleClientTimeout>{{ dbproxy.idle_client_timeout }}</IdleClientTimeout>
+                <Endpoint>{{ dbproxy.endpoint }}</Endpoint>
+                <CreatedDate>{{ dbproxy.created_date }}</CreatedDate>
+                <RoleArn>{{ dbproxy.role_arn }}</RoleArn>
+                <DebugLogging>{{ dbproxy.debug_logging }}</DebugLogging>
+                <VpcId>{{ dbproxy.vpc_id }}</VpcId>
+                <DBProxyArn>{{ dbproxy.db_proxy_arn }}</DBProxyArn>
+                <VpcSubnetIds>
+                  {% for vpcsubnetid in dbproxy.vpc_subnet_ids %}
+                    <member>{{ vpcsubnetid }}</member>
+                  {% endfor %}
+                </VpcSubnetIds>
+                <Status>{{ dbproxy.status }}</Status>
+        """
+        )
+        return template.render(dbproxy=self)
+
+
 class RDSBackend(BaseBackend):
     def __init__(self, region_name: str, account_id: str):
         super().__init__(region_name, account_id)
         self.arn_regex = re_compile(
-            r"^arn:aws:rds:.*:[0-9]*:(db|cluster|es|og|pg|ri|secgrp|snapshot|cluster-snapshot|subgrp):.*$"
+            r"^arn:aws:rds:.*:[0-9]*:(db|cluster|es|og|pg|ri|secgrp|snapshot|cluster-snapshot|subgrp|db-proxy):.*$"
         )
         self.clusters: Dict[str, Cluster] = OrderedDict()
         self.global_clusters: Dict[str, GlobalCluster] = OrderedDict()
@@ -1592,6 +1713,7 @@ class RDSBackend(BaseBackend):
         self.security_groups: Dict[str, SecurityGroup] = {}
         self.subnet_groups: Dict[str, SubnetGroup] = {}
         self._db_cluster_options: Optional[List[Dict[str, Any]]] = None
+        self.db_proxies: Dict[str, DBProxy] = OrderedDict()
 
     def reset(self) -> None:
         self.neptune.reset()
@@ -2586,6 +2708,9 @@ class RDSBackend(BaseBackend):
             elif resource_type == "subgrp":  # DB subnet group
                 if resource_name in self.subnet_groups:
                     return self.subnet_groups[resource_name].get_tags()
+            elif resource_type == "db-proxy":  # DB Proxy
+                if resource_name in self.db_proxies:
+                    return self.db_proxies[resource_name].get_tags()
         else:
             raise RDSClientError(
                 "InvalidParameterValue", f"Invalid resource name: {arn}"
@@ -2628,6 +2753,9 @@ class RDSBackend(BaseBackend):
             elif resource_type == "subgrp":  # DB subnet group
                 if resource_name in self.subnet_groups:
                     self.subnet_groups[resource_name].remove_tags(tag_keys)
+            elif resource_type == "db-proxy":  # DB Proxy
+                if resource_name in self.db_proxies:
+                    self.db_proxies[resource_name].remove_tags(tag_keys)
         else:
             raise RDSClientError(
                 "InvalidParameterValue", f"Invalid resource name: {arn}"
@@ -2669,6 +2797,9 @@ class RDSBackend(BaseBackend):
             elif resource_type == "subgrp":  # DB subnet group
                 if resource_name in self.subnet_groups:
                     return self.subnet_groups[resource_name].add_tags(tags)
+            elif resource_type == "db-proxy":  # DB Proxy
+                if resource_name in self.db_proxies:
+                    return self.db_proxies[resource_name].add_tags(tags)
         else:
             raise RDSClientError(
                 "InvalidParameterValue", f"Invalid resource name: {arn}"
@@ -2910,6 +3041,56 @@ class RDSBackend(BaseBackend):
             )
         return snapshot.attributes
 
+    def create_db_proxy(
+        self,
+        db_proxy_name: str,
+        engine_family: str,
+        auth: List[Dict[str, str]],
+        role_arn: str,
+        vpc_subnet_ids: List[str],
+        vpc_security_group_ids: Optional[List[str]],
+        require_tls: Optional[bool],
+        idle_client_timeout: Optional[int],
+        debug_logging: Optional[bool],
+        tags: Optional[List[Dict[str, str]]],
+    ) -> DBProxy:
+        self._validate_db_identifier(db_proxy_name)
+        if db_proxy_name in self.db_proxies:
+            raise DBProxyAlreadyExistsFault(db_proxy_name)
+        if len(self.db_proxies) >= int(os.environ.get("MOTO_RDS_PROXY_LIMIT", "100")):
+            raise DBProxyQuotaExceededFault()
+        db_proxy = DBProxy(
+            db_proxy_name,
+            engine_family,
+            auth,
+            role_arn,
+            vpc_subnet_ids,
+            self.region_name,
+            self.account_id,
+            vpc_security_group_ids,
+            require_tls,
+            idle_client_timeout,
+            debug_logging,
+            tags,
+        )
+        self.db_proxies[db_proxy_name] = db_proxy
+        return db_proxy
+
+    def describe_db_proxies(
+        self,
+        db_proxy_name: Optional[str],
+        filters: Optional[List[Dict[str, Any]]] = None,
+    ) -> List[DBProxy]:
+        """
+        The filters-argument is not yet supported
+        """
+        db_proxies = list(self.db_proxies.values())
+        if db_proxy_name and db_proxy_name in self.db_proxies.keys():
+            db_proxies = [self.db_proxies[db_proxy_name]]
+        if db_proxy_name and db_proxy_name not in self.db_proxies.keys():
+            raise DBProxyNotFoundFault(db_proxy_name)
+        return db_proxies
+
 
 class OptionGroup:
     def __init__(
diff --git a/moto/rds/responses.py b/moto/rds/responses.py
index e3dbbe1cc..29cc66509 100644
--- a/moto/rds/responses.py
+++ b/moto/rds/responses.py
@@ -882,6 +882,46 @@ class RDSResponse(BaseResponse):
             db_cluster_snapshot_identifier=db_cluster_snapshot_identifier,
         )
 
+    def describe_db_proxies(self) -> str:
+        params = self._get_params()
+        db_proxy_name = params.get("DBProxyName")
+        # filters = params.get("Filters")
+        marker = params.get("Marker")
+        db_proxies = self.backend.describe_db_proxies(
+            db_proxy_name=db_proxy_name,
+            # filters=filters,
+        )
+        template = self.response_template(DESCRIBE_DB_PROXIES_TEMPLATE)
+        rendered = template.render(dbproxies=db_proxies, marker=marker)
+        return rendered
+
+    def create_db_proxy(self) -> str:
+        params = self._get_params()
+        db_proxy_name = params["DBProxyName"]
+        engine_family = params["EngineFamily"]
+        auth = params["Auth"]
+        role_arn = params["RoleArn"]
+        vpc_subnet_ids = params["VpcSubnetIds"]
+        vpc_security_group_ids = params.get("VpcSecurityGroupIds")
+        require_tls = params.get("RequireTLS")
+        idle_client_timeout = params.get("IdleClientTimeout")
+        debug_logging = params.get("DebugLogging")
+        tags = self.unpack_list_params("Tags", "Tag")
+        db_proxy = self.backend.create_db_proxy(
+            db_proxy_name=db_proxy_name,
+            engine_family=engine_family,
+            auth=auth,
+            role_arn=role_arn,
+            vpc_subnet_ids=vpc_subnet_ids,
+            vpc_security_group_ids=vpc_security_group_ids,
+            require_tls=require_tls,
+            idle_client_timeout=idle_client_timeout,
+            debug_logging=debug_logging,
+            tags=tags,
+        )
+        template = self.response_template(CREATE_DB_PROXY_TEMPLATE)
+        return template.render(dbproxy=db_proxy)
+
 
 CREATE_DATABASE_TEMPLATE = """<CreateDBInstanceResponse xmlns="http://rds.amazonaws.com/doc/2014-09-01/">
   <CreateDBInstanceResult>
@@ -1630,3 +1670,30 @@ DESCRIBE_DB_CLUSTER_SNAPSHOT_ATTRIBUTES_TEMPLATE = """<DescribeDBClusterSnapshot
     <RequestId>1549581b-12b7-11e3-895e-1334a</RequestId>
   </ResponseMetadata>
 </DescribeDBClusterSnapshotAttributesResponse>"""
+
+CREATE_DB_PROXY_TEMPLATE = """<CreateDBProxyResponse xmlns="http://rds.amazonaws.com/doc/2014-10-31/">
+  <CreateDBProxyResult>
+    <DBProxy>
+    {{ dbproxy.to_xml() }}
+    </DBProxy>
+  </CreateDBProxyResult>
+  <ResponseMetadata>
+    <RequestId>1549581b-12b7-11e3-895e-1334aEXAMPLE</RequestId>
+  </ResponseMetadata>
+</CreateDBProxyResponse>"""
+
+DESCRIBE_DB_PROXIES_TEMPLATE = """<DescribeDBProxiesResponse xmlns="http://rds.amazonaws.com/doc/2014-10-31/">
+    <DescribeDBProxiesResult>
+        <DBProxies>
+          {% for dbproxy in dbproxies %}
+            <member>
+              {{ dbproxy.to_xml() }}
+            </member>
+          {% endfor %}
+        </DBProxies>
+    </DescribeDBProxiesResult>
+    <ResponseMetadata>
+        <RequestId>1549581b-12b7-11e3-895e-1334a</RequestId>
+    </ResponseMetadata>
+</DescribeDBProxiesResponse>
+"""
diff --git a/moto/resourcegroupstaggingapi/models.py b/moto/resourcegroupstaggingapi/models.py
index c54af77a2..f54213ceb 100644
--- a/moto/resourcegroupstaggingapi/models.py
+++ b/moto/resourcegroupstaggingapi/models.py
@@ -19,6 +19,7 @@ from moto.moto_api._internal import mock_random
 from moto.rds.models import RDSBackend, rds_backends
 from moto.redshift.models import RedshiftBackend, redshift_backends
 from moto.s3.models import S3Backend, s3_backends
+from moto.sns.models import SNSBackend, sns_backends
 from moto.sqs.models import SQSBackend, sqs_backends
 from moto.utilities.tagging_service import TaggingService
 
@@ -97,6 +98,10 @@ class ResourceGroupsTaggingAPIBackend(BaseBackend):
     def acm_backend(self) -> AWSCertificateManagerBackend:
         return acm_backends[self.account_id][self.region_name]
 
+    @property
+    def sns_backend(self) -> SNSBackend:
+        return sns_backends[self.account_id][self.region_name]
+
     @property
     def sqs_backend(self) -> SQSBackend:
         return sqs_backends[self.account_id][self.region_name]
@@ -340,6 +345,24 @@ class ResourceGroupsTaggingAPIBackend(BaseBackend):
                     "Tags": tags,
                 }
 
+        # ELB (Classic Load Balancers)
+        if (
+            not resource_type_filters
+            or "elb" in resource_type_filters
+            or "elb:loadbalancer" in resource_type_filters
+        ):
+            for elb in self.elb_backend.load_balancers.values():
+                tags = format_tags(elb.tags)
+                if not tags or not tag_filter(
+                    tags
+                ):  # Skip if no tags, or invalid filter
+                    continue
+
+                yield {
+                    "ResourceARN": f"arn:aws:elasticloadbalancing:{self.region_name}:{self.account_id}:loadbalancer/{elb.name}",
+                    "Tags": tags,
+                }
+
         # TODO add these to the keys and values functions / combine functions
         # ELB, resource type elasticloadbalancing:loadbalancer
         if (
@@ -347,14 +370,14 @@ class ResourceGroupsTaggingAPIBackend(BaseBackend):
             or "elasticloadbalancing" in resource_type_filters
             or "elasticloadbalancing:loadbalancer" in resource_type_filters
         ):
-            for elb in self.elbv2_backend.load_balancers.values():
+            for elbv2 in self.elbv2_backend.load_balancers.values():
                 tags = self.elbv2_backend.tagging_service.list_tags_for_resource(
-                    elb.arn
+                    elbv2.arn
                 )["Tags"]
                 if not tag_filter(tags):  # Skip if no tags, or invalid filter
                     continue
 
-                yield {"ResourceARN": f"{elb.arn}", "Tags": tags}
+                yield {"ResourceARN": f"{elbv2.arn}", "Tags": tags}
 
         # ELB Target Group, resource type elasticloadbalancing:targetgroup
         if (
@@ -433,6 +456,7 @@ class ResourceGroupsTaggingAPIBackend(BaseBackend):
             "rds:db": self.rds_backend.databases,
             "rds:snapshot": self.rds_backend.database_snapshots,
             "rds:cluster-snapshot": self.rds_backend.cluster_snapshots,
+            "rds:db-proxy": self.rds_backend.db_proxies,
         }
         for resource_type, resource_source in resource_map.items():
             if (
@@ -474,6 +498,17 @@ class ResourceGroupsTaggingAPIBackend(BaseBackend):
 
                 yield {"ResourceARN": f"{queue.queue_arn}", "Tags": tags}
 
+        # SNS
+        if not resource_type_filters or "sns" in resource_type_filters:
+
+            for topic in self.sns_backend.topics.values():
+                tags = format_tags(topic._tags)
+                if not tags or not tag_filter(
+                    tags
+                ):  # Skip if no tags, or invalid filter
+                    continue
+                yield {"ResourceARN": f"{topic.arn}", "Tags": tags}
+
         # VPC
         if (
             not resource_type_filters
diff --git a/moto/s3/models.py b/moto/s3/models.py
index 4365f1e19..0983631eb 100644
--- a/moto/s3/models.py
+++ b/moto/s3/models.py
@@ -883,6 +883,7 @@ class NotificationConfiguration(BaseModel):
         topic: Optional[List[Dict[str, Any]]] = None,
         queue: Optional[List[Dict[str, Any]]] = None,
         cloud_function: Optional[List[Dict[str, Any]]] = None,
+        event_bridge: Optional[Dict[str, Any]] = None,
     ):
         self.topic = (
             [
@@ -923,6 +924,7 @@ class NotificationConfiguration(BaseModel):
             if cloud_function
             else []
         )
+        self.event_bridge = event_bridge
 
     def to_config_dict(self) -> Dict[str, Any]:
         data: Dict[str, Any] = {"configurations": {}}
@@ -945,6 +947,8 @@ class NotificationConfiguration(BaseModel):
             cf_config["type"] = "LambdaConfiguration"
             data["configurations"][cloud_function.id] = cf_config
 
+        if self.event_bridge is not None:
+            data["configurations"]["EventBridgeConfiguration"] = self.event_bridge
         return data
 
 
@@ -1325,6 +1329,7 @@ class FakeBucket(CloudFormationModel):
             topic=notification_config.get("TopicConfiguration"),
             queue=notification_config.get("QueueConfiguration"),
             cloud_function=notification_config.get("CloudFunctionConfiguration"),
+            event_bridge=notification_config.get("EventBridgeConfiguration"),
         )
 
         # Validate that the region is correct:
@@ -2315,9 +2320,9 @@ class S3Backend(BaseBackend, CloudWatchMetricProvider):
          - AWSLambda
          - SNS
          - SQS
+         - EventBridge
 
         For the following events:
-
          - 's3:ObjectCreated:Copy'
          - 's3:ObjectCreated:Put'
         """
diff --git a/moto/s3/notifications.py b/moto/s3/notifications.py
index 57f080077..bc9208695 100644
--- a/moto/s3/notifications.py
+++ b/moto/s3/notifications.py
@@ -1,8 +1,11 @@
+import copy
 import json
 from datetime import datetime
 from enum import Enum
 from typing import Any, Dict, List
 
+from moto.core.utils import unix_time
+
 _EVENT_TIME_FORMAT = "%Y-%m-%dT%H:%M:%S.%f"
 
 
@@ -122,6 +125,9 @@ def send_event(
 
             _send_sns_message(account_id, event_body, topic_arn, region_name)
 
+    if bucket.notification_configuration.event_bridge is not None:
+        _send_event_bridge_message(account_id, bucket, event_name, key)
+
 
 def _send_sqs_message(
     account_id: str, event_body: Any, queue_name: str, region_name: str
@@ -157,6 +163,98 @@ def _send_sns_message(
         pass
 
 
+def _send_event_bridge_message(
+    account_id: str,
+    bucket: Any,
+    event_name: str,
+    key: Any,
+) -> None:
+    try:
+        from moto.events.models import events_backends
+        from moto.events.utils import _BASE_EVENT_MESSAGE
+
+        event = copy.deepcopy(_BASE_EVENT_MESSAGE)
+        event["detail-type"] = _detail_type(event_name)
+        event["source"] = "aws.s3"
+        event["account"] = account_id
+        event["time"] = unix_time()
+        event["region"] = bucket.region_name
+        event["resources"] = [f"arn:aws:s3:::{bucket.name}"]
+        event["detail"] = {
+            "version": "0",
+            "bucket": {"name": bucket.name},
+            "object": {
+                "key": key.name,
+                "size": key.size,
+                "eTag": key.etag.replace('"', ""),
+                "version-id": key.version_id,
+                "sequencer": "617f08299329d189",
+            },
+            "request-id": "N4N7GDK58NMKJ12R",
+            "requester": "123456789012",
+            "source-ip-address": "1.2.3.4",
+            # ex) s3:ObjectCreated:Put -> ObjectCreated
+            "reason": event_name.split(":")[1],
+        }
+
+        events_backend = events_backends[account_id][bucket.region_name]
+        for event_bus in events_backend.event_buses.values():
+            for rule in event_bus.rules.values():
+                rule.send_to_targets(event)
+
+    except:  # noqa
+        # This is an async action in AWS.
+        # Even if this part fails, the calling function should pass, so catch all errors
+        # Possible exceptions that could be thrown:
+        # - EventBridge does not exist
+        pass
+
+
+def _detail_type(event_name: str) -> str:
+    """Detail type field values for event messages of s3 EventBridge notification
+
+    document: https://docs.aws.amazon.com/AmazonS3/latest/userguide/EventBridge.html
+    """
+    if event_name in [e for e in S3NotificationEvent.events() if "ObjectCreated" in e]:
+        return "Object Created"
+    elif event_name in [
+        e
+        for e in S3NotificationEvent.events()
+        if "ObjectRemoved" in e or "LifecycleExpiration" in e
+    ]:
+        return "Object Deleted"
+    elif event_name in [
+        e for e in S3NotificationEvent.events() if "ObjectRestore" in e
+    ]:
+        if event_name == S3NotificationEvent.OBJECT_RESTORE_POST_EVENT:
+            return "Object Restore Initiated"
+        elif event_name == S3NotificationEvent.OBJECT_RESTORE_COMPLETED_EVENT:
+            return "Object Restore Completed"
+        else:
+            # s3:ObjectRestore:Delete event
+            return "Object Restore Expired"
+    elif event_name in [
+        e for e in S3NotificationEvent.events() if "LifecycleTransition" in e
+    ]:
+        return "Object Storage Class Changed"
+    elif event_name in [
+        e for e in S3NotificationEvent.events() if "IntelligentTiering" in e
+    ]:
+        return "Object Access Tier Changed"
+    elif event_name in [e for e in S3NotificationEvent.events() if "ObjectAcl" in e]:
+        return "Object ACL Updated"
+    elif event_name in [e for e in S3NotificationEvent.events() if "ObjectTagging"]:
+        if event_name == S3NotificationEvent.OBJECT_TAGGING_PUT_EVENT:
+            return "Object Tags Added"
+        else:
+            # s3:ObjectTagging:Delete event
+            return "Object Tags Deleted"
+    else:
+        raise ValueError(
+            f"unsupported event `{event_name}` for s3 eventbridge notification (https://docs.aws.amazon.com/AmazonS3/latest/userguide/EventBridge.html)"
+        )
+
+
 def _invoke_awslambda(
     account_id: str, event_body: Any, fn_arn: str, region_name: str
 ) -> None:
diff --git a/moto/s3/responses.py b/moto/s3/responses.py
index d6eaf1705..575b5bcc7 100644
--- a/moto/s3/responses.py
+++ b/moto/s3/responses.py
@@ -2092,12 +2092,19 @@ class S3Response(BaseResponse):
             ("Topic", "sns"),
             ("Queue", "sqs"),
             ("CloudFunction", "lambda"),
+            ("EventBridge", "events"),
         ]
 
         found_notifications = (
             0  # Tripwire -- if this is not ever set, then there were no notifications
         )
         for name, arn_string in notification_fields:
+            # EventBridgeConfiguration is passed as an empty dict.
+            if name == "EventBridge":
+                events_field = f"{name}Configuration"
+                if events_field in parsed_xml["NotificationConfiguration"]:
+                    parsed_xml["NotificationConfiguration"][events_field] = {}
+                    found_notifications += 1
             # 1st verify that the proper notification configuration has been passed in (with an ARN that is close
             # to being correct -- nothing too complex in the ARN logic):
             the_notification = parsed_xml["NotificationConfiguration"].get(
diff --git a/moto/ses/exceptions.py b/moto/ses/exceptions.py
index 1c6eb9860..ccb5da15f 100644
--- a/moto/ses/exceptions.py
+++ b/moto/ses/exceptions.py
@@ -1,100 +1,82 @@
+from typing import Any
+
 from moto.core.exceptions import RESTError
 
 
-class MessageRejectedError(RESTError):
+class SesError(RESTError):
     code = 400
 
+    def __init__(self, error_type: str, message: str, *args: Any, **kwargs: Any):
+        kwargs.setdefault("template", "wrapped_single_error")
+        super().__init__(error_type, message, *args, **kwargs)
+
+
+class MessageRejectedError(SesError):
     def __init__(self, message: str):
         super().__init__("MessageRejected", message)
 
 
-class ConfigurationSetDoesNotExist(RESTError):
-    code = 400
-
+class ConfigurationSetDoesNotExist(SesError):
     def __init__(self, message: str):
         super().__init__("ConfigurationSetDoesNotExist", message)
 
 
-class ConfigurationSetAlreadyExists(RESTError):
-    code = 400
-
+class ConfigurationSetAlreadyExists(SesError):
     def __init__(self, message: str):
         super().__init__("ConfigurationSetAlreadyExists", message)
 
 
-class EventDestinationAlreadyExists(RESTError):
-    code = 400
-
+class EventDestinationAlreadyExists(SesError):
     def __init__(self, message: str):
         super().__init__("EventDestinationAlreadyExists", message)
 
 
-class TemplateNameAlreadyExists(RESTError):
-    code = 400
-
+class TemplateNameAlreadyExists(SesError):
     def __init__(self, message: str):
         super().__init__("TemplateNameAlreadyExists", message)
 
 
-class ValidationError(RESTError):
-    code = 400
-
+class ValidationError(SesError):
     def __init__(self, message: str):
         super().__init__("ValidationError", message)
 
 
-class InvalidParameterValue(RESTError):
-    code = 400
-
+class InvalidParameterValue(SesError):
     def __init__(self, message: str):
         super().__init__("InvalidParameterValue", message)
 
 
-class InvalidRenderingParameterException(RESTError):
-    code = 400
-
+class InvalidRenderingParameterException(SesError):
     def __init__(self, message: str):
         super().__init__("InvalidRenderingParameterException", message)
 
 
-class TemplateDoesNotExist(RESTError):
-    code = 400
-
+class TemplateDoesNotExist(SesError):
     def __init__(self, message: str):
         super().__init__("TemplateDoesNotExist", message)
 
 
-class RuleSetNameAlreadyExists(RESTError):
-    code = 400
-
+class RuleSetNameAlreadyExists(SesError):
     def __init__(self, message: str):
         super().__init__("RuleSetNameAlreadyExists", message)
 
 
-class RuleAlreadyExists(RESTError):
-    code = 400
-
+class RuleAlreadyExists(SesError):
     def __init__(self, message: str):
         super().__init__("RuleAlreadyExists", message)
 
 
-class RuleSetDoesNotExist(RESTError):
-    code = 400
-
+class RuleSetDoesNotExist(SesError):
     def __init__(self, message: str):
         super().__init__("RuleSetDoesNotExist", message)
 
 
-class RuleDoesNotExist(RESTError):
-    code = 400
-
+class RuleDoesNotExist(SesError):
     def __init__(self, message: str):
         super().__init__("RuleDoesNotExist", message)
 
 
-class MissingRenderingAttributeException(RESTError):
-    code = 400
-
+class MissingRenderingAttributeException(SesError):
     def __init__(self, var: str):
         super().__init__(
             "MissingRenderingAttributeException",
diff --git a/scripts/implementation_coverage.py b/scripts/implementation_coverage.py
index 8e4ad0863..05adab0b8 100755
--- a/scripts/implementation_coverage.py
+++ b/scripts/implementation_coverage.py
@@ -15,7 +15,11 @@ alternative_service_names = {"lambda": "awslambda"}
 def get_moto_implementation(service_name):
     try:
         backends = get_backend(service_name)
-        backend = backends[0]["us-east-1"] if "us-east-1" in backends[0] else backends[0]["global"]
+        backend = (
+            backends[0]["us-east-1"]
+            if "us-east-1" in backends[0]
+            else backends[0]["global"]
+        )
         return backend, service_name
     except ModuleNotFoundError:
         return None, service_name
@@ -24,9 +28,9 @@ def get_moto_implementation(service_name):
 def get_module_name(o):
     klass = o.__class__
     module = klass.__module__
-    if module == 'builtins':
-        return klass.__qualname__ # avoid outputs like 'builtins.str'
-    return module + '.' + klass.__qualname__
+    if module == "builtins":
+        return klass.__qualname__  # avoid outputs like 'builtins.str'
+    return module + "." + klass.__qualname__
 
 
 def calculate_extended_implementation_coverage():
@@ -161,7 +165,9 @@ def write_implementation_coverage_to_file(coverage):
 
 
 def write_implementation_coverage_to_docs(coverage):
-    implementation_coverage_file = "{}/../docs/docs/services/index.rst".format(script_dir)
+    implementation_coverage_file = "{}/../docs/docs/services/index.rst".format(
+        script_dir
+    )
     # rewrite the implementation coverage file with updated values
     # try deleting the implementation coverage file
     try:
@@ -179,7 +185,9 @@ def write_implementation_coverage_to_docs(coverage):
         not_implemented = coverage.get(service_name)["not_implemented"]
         operations = sorted(list(implemented.keys()) + not_implemented)
 
-        service_coverage_file = "{}/../docs/docs/services/{}.rst".format(script_dir, service_name)
+        service_coverage_file = "{}/../docs/docs/services/{}.rst".format(
+            script_dir, service_name
+        )
         shorthand = service_name.replace(" ", "_")
 
         with open(service_coverage_file, "w+") as file:
@@ -202,7 +210,9 @@ def write_implementation_coverage_to_docs(coverage):
 
             if coverage[service_name]["docs"]:
                 # Only show auto-generated documentation if it exists
-                file.write(".. autoclass:: " + coverage[service_name].get("module_name"))
+                file.write(
+                    ".. autoclass:: " + coverage[service_name].get("module_name")
+                )
                 file.write("\n\n")
 
             file.write("|start-h3| Implemented features for this service |end-h3|\n\n")
@@ -217,7 +227,6 @@ def write_implementation_coverage_to_docs(coverage):
                     file.write("- [ ] {}\n".format(op))
             file.write("\n")
 
-
     with open(implementation_coverage_file, "w+") as file:
         file.write(".. _implemented_services:\n")
         file.write("\n")
@@ -227,7 +236,9 @@ def write_implementation_coverage_to_docs(coverage):
         file.write("Implemented Services\n")
         file.write("====================\n")
         file.write("\n")
-        file.write("Please see a list of all currently supported services. Each service will have a list of the endpoints that are implemented.\n")
+        file.write(
+            "Please see a list of all currently supported services. Each service will have a list of the endpoints that are implemented.\n"
+        )
 
         file.write("\n")
         file.write(".. toctree::\n")
diff --git a/scripts/update_backend_index.py b/scripts/update_backend_index.py
index e46ea9dc6..c956d61db 100755
--- a/scripts/update_backend_index.py
+++ b/scripts/update_backend_index.py
@@ -19,7 +19,20 @@ output_path = os.path.join(script_dir, "..", output_file)
 # Ignore S3bucket_path, as the functionality is covered in the S3 service
 # Ignore neptune, as it shares a URL with RDS
 # Ignore OpenSearch, as it shares a URL with ElasticSearch
-IGNORE_BACKENDS = ["moto_server", "moto_proxy", "apigatewayv2", "awslambda_simple", "batch_simple", "core", "dynamodb_v20111205", "packages", "utilities", "s3bucket_path", "neptune", "opensearch"]
+IGNORE_BACKENDS = [
+    "moto_server",
+    "moto_proxy",
+    "apigatewayv2",
+    "awslambda_simple",
+    "batch_simple",
+    "core",
+    "dynamodb_v20111205",
+    "packages",
+    "utilities",
+    "s3bucket_path",
+    "neptune",
+    "opensearch",
+]
 
 
 def iter_backend_url_patterns():


Test patch:
diff --git a/other_langs/tests_java/pom.xml b/other_langs/tests_java/pom.xml
index 62769a762..4515fd75c 100644
--- a/other_langs/tests_java/pom.xml
+++ b/other_langs/tests_java/pom.xml
@@ -37,9 +37,13 @@
       <scope>test</scope>
     </dependency>
     <dependency>
-    <groupId>software.amazon.awssdk</groupId>
-    <artifactId>dynamodb</artifactId>
-  </dependency>
+      <groupId>software.amazon.awssdk</groupId>
+      <artifactId>dynamodb</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>software.amazon.awssdk</groupId>
+      <artifactId>ses</artifactId>
+    </dependency>
     <dependency>
       <groupId>software.amazon.awssdk</groupId>
       <artifactId>s3</artifactId>
diff --git a/other_langs/tests_java/src/main/java/moto/tests/DependencyFactory.java b/other_langs/tests_java/src/main/java/moto/tests/DependencyFactory.java
index ad1def3ea..7cfa0940d 100644
--- a/other_langs/tests_java/src/main/java/moto/tests/DependencyFactory.java
+++ b/other_langs/tests_java/src/main/java/moto/tests/DependencyFactory.java
@@ -6,6 +6,7 @@ import software.amazon.awssdk.http.apache.ApacheHttpClient;
 import software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient;
 import software.amazon.awssdk.regions.Region;
 import software.amazon.awssdk.services.s3.S3Client;
+import software.amazon.awssdk.services.ses.SesClient;
 import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
 import software.amazon.awssdk.utils.AttributeMap;
 
@@ -32,6 +33,14 @@ public class DependencyFactory {
                 .build();
     }
 
+    public static SesClient sesClient() {
+        return SesClient.builder()
+                .region(Region.US_EAST_1)
+                .httpClientBuilder(ApacheHttpClient.builder())
+                .endpointOverride(MOTO_URI)
+                .build();
+    }
+
     public static DynamoDbClient dynamoClient() {
         final AttributeMap attributeMap = AttributeMap.builder()
                 .put(SdkHttpConfigurationOption.TRUST_ALL_CERTIFICATES, true)
diff --git a/other_langs/tests_java/src/test/java/moto/tests/SESTest.java b/other_langs/tests_java/src/test/java/moto/tests/SESTest.java
new file mode 100644
index 000000000..6e313cc34
--- /dev/null
+++ b/other_langs/tests_java/src/test/java/moto/tests/SESTest.java
@@ -0,0 +1,45 @@
+package moto.tests;
+
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+import software.amazon.awssdk.services.ses.SesClient;
+import software.amazon.awssdk.services.ses.model.Body;
+import software.amazon.awssdk.services.ses.model.Content;
+import software.amazon.awssdk.services.ses.model.Destination;
+import software.amazon.awssdk.services.ses.model.Message;
+import software.amazon.awssdk.services.ses.model.SendEmailRequest;
+import software.amazon.awssdk.services.ses.model.SendEmailResponse;
+import software.amazon.awssdk.services.ses.model.SesException;
+
+public class SESTest {
+
+    @Test
+    public void testUnverifiedEmail() {
+        SesClient sesClient = DependencyFactory.sesClient();
+
+        Message message = Message
+                .builder()
+                .subject(Content.builder().charset("UTF-8").data("subject").build())
+                .body(Body.builder().text(Content.builder().charset("UTF-8").data("body").build()).build())
+                .build();
+
+        SendEmailRequest request = SendEmailRequest
+                .builder()
+                .source("noreply@thedomain.com")
+                .destination(Destination.builder().toAddresses(List.of("one@one.com")).build())
+                .message(message)
+                .build();
+
+        try {
+            System.out.println("Attempting to send an email...");
+            sesClient.sendEmail(request);
+
+        } catch (SesException e) {
+            assertEquals(e.awsErrorDetails().errorMessage(), "Email address not verified noreply@thedomain.com");
+        }
+    }
+}
diff --git a/tests/test_awslambda/test_lambda_invoke.py b/tests/test_awslambda/test_lambda_invoke.py
index 0d6ed9a01..7000cc163 100644
--- a/tests/test_awslambda/test_lambda_invoke.py
+++ b/tests/test_awslambda/test_lambda_invoke.py
@@ -377,3 +377,39 @@ def test_invoke_lambda_with_proxy():
 
     expected_payload = {"id": vol.id, "state": vol.state, "size": vol.size}
     assert json.loads(payload) == expected_payload
+
+
+@mock_aws
+def test_invoke_lambda_with_entrypoint():
+    conn = boto3.client("lambda", _lambda_region)
+    function_name = str(uuid4())[0:6]
+    conn.create_function(
+        FunctionName=function_name,
+        Runtime=PYTHON_VERSION,
+        Role=get_role_name(),
+        Handler="lambda_function.lambda_handler",
+        Code={"ZipFile": get_test_zip_file1()},
+        ImageConfig={
+            "EntryPoint": [
+                "/var/rapid/init",
+                "--bootstrap",
+                "/var/runtime/bootstrap",
+                "--enable-msg-logs",
+            ],
+        },
+        Description="test lambda function",
+        Timeout=3,
+        MemorySize=128,
+        Publish=True,
+    )
+
+    in_data = {"hello": "world"}
+    result = conn.invoke(
+        FunctionName=function_name,
+        InvocationType="RequestResponse",
+        Payload=json.dumps(in_data),
+    )
+    assert result["StatusCode"] == 200
+    payload = result["Payload"].read().decode("utf-8")
+
+    assert json.loads(payload) == in_data
diff --git a/tests/test_dynamodb/exceptions/test_dynamodb_exceptions.py b/tests/test_dynamodb/exceptions/test_dynamodb_exceptions.py
index 1152f5285..486d3dbf1 100644
--- a/tests/test_dynamodb/exceptions/test_dynamodb_exceptions.py
+++ b/tests/test_dynamodb/exceptions/test_dynamodb_exceptions.py
@@ -1242,3 +1242,34 @@ def test_scan_with_missing_value(table_name=None):
         err["Message"]
         == 'ExpressionAttributeValues contains invalid key: Syntax error; key: "loc"'
     )
+
+
+@mock_aws
+def test_too_many_key_schema_attributes():
+    ddb = boto3.resource("dynamodb", "us-east-1")
+    TableName = "my_test_"
+
+    AttributeDefinitions = [
+        {"AttributeName": "UUID", "AttributeType": "S"},
+        {"AttributeName": "ComicBook", "AttributeType": "S"},
+    ]
+
+    KeySchema = [
+        {"AttributeName": "UUID", "KeyType": "HASH"},
+        {"AttributeName": "ComicBook", "KeyType": "RANGE"},
+        {"AttributeName": "Creator", "KeyType": "RANGE"},
+    ]
+
+    ProvisionedThroughput = {"ReadCapacityUnits": 5, "WriteCapacityUnits": 5}
+
+    expected_err = "1 validation error detected: Value '[KeySchemaElement(attributeName=UUID, keyType=HASH), KeySchemaElement(attributeName=ComicBook, keyType=RANGE), KeySchemaElement(attributeName=Creator, keyType=RANGE)]' at 'keySchema' failed to satisfy constraint: Member must have length less than or equal to 2"
+    with pytest.raises(ClientError) as exc:
+        ddb.create_table(
+            TableName=TableName,
+            KeySchema=KeySchema,
+            AttributeDefinitions=AttributeDefinitions,
+            ProvisionedThroughput=ProvisionedThroughput,
+        )
+    err = exc.value.response["Error"]
+    assert err["Code"] == "ValidationException"
+    assert err["Message"] == expected_err
diff --git a/tests/test_rds/test_rds_proxy.py b/tests/test_rds/test_rds_proxy.py
new file mode 100644
index 000000000..314337836
--- /dev/null
+++ b/tests/test_rds/test_rds_proxy.py
@@ -0,0 +1,266 @@
+import boto3
+import pytest
+from botocore.exceptions import ClientError
+
+from moto import mock_aws
+from moto.core import DEFAULT_ACCOUNT_ID as ACCOUNT_ID
+
+DEFAULT_REGION = "us-west-2"
+
+
+@mock_aws
+def test_create_db_proxy():
+    rds_client = boto3.client("rds", region_name=DEFAULT_REGION)
+    ec2_client = boto3.client("ec2", region_name=DEFAULT_REGION)
+    vpc_id = ec2_client.create_vpc(CidrBlock="10.0.0.0/16")["Vpc"]["VpcId"]
+    subnet_id = ec2_client.create_subnet(CidrBlock="10.0.1.0/24", VpcId=vpc_id)[
+        "Subnet"
+    ]["SubnetId"]
+    subnet_id_2 = ec2_client.create_subnet(CidrBlock="10.0.2.0/24", VpcId=vpc_id)[
+        "Subnet"
+    ]["SubnetId"]
+    resp = rds_client.create_db_proxy(
+        DBProxyName="testrdsproxy",
+        EngineFamily="MYSQL",
+        Auth=[
+            {
+                "Description": "Test Description",
+                "UserName": "Test Username",
+                "AuthScheme": "SECRETS",
+                "SecretArn": "TestSecretARN",
+                "IAMAuth": "ENABLED",
+                "ClientPasswordAuthType": "MYSQL_NATIVE_PASSWORD",
+            },
+        ],
+        RoleArn="TestArn",
+        VpcSubnetIds=[subnet_id, subnet_id_2],
+        RequireTLS=True,
+        Tags=[{"Key": "TestKey", "Value": "TestValue"}],
+    )
+    db_proxy = resp["DBProxy"]
+    assert db_proxy["DBProxyName"] == "testrdsproxy"
+    assert (
+        db_proxy["DBProxyArn"]
+        == f"arn:aws:rds:us-west-2:{ACCOUNT_ID}:db-proxy:testrdsproxy"
+    )
+    assert db_proxy["Status"] == "availible"
+    assert db_proxy["EngineFamily"] == "MYSQL"
+    assert db_proxy["VpcId"] == vpc_id
+    assert db_proxy["VpcSecurityGroupIds"] == []
+    assert db_proxy["VpcSubnetIds"] == [subnet_id, subnet_id_2]
+    assert db_proxy["Auth"] == [
+        {
+            "UserName": "Test Username",
+            "AuthScheme": "SECRETS",
+            "SecretArn": "TestSecretARN",
+            "IAMAuth": "ENABLED",
+            "ClientPasswordAuthType": "MYSQL_NATIVE_PASSWORD",
+        }
+    ]
+    assert db_proxy["RoleArn"] == "TestArn"
+    assert db_proxy["RequireTLS"] is True
+    assert db_proxy["IdleClientTimeout"] == 1800
+    assert db_proxy["DebugLogging"] is False
+
+
+@mock_aws
+def test_describe_db_proxies():
+    rds_client = boto3.client("rds", region_name=DEFAULT_REGION)
+    ec2_client = boto3.client("ec2", region_name=DEFAULT_REGION)
+    vpc_id = ec2_client.create_vpc(CidrBlock="10.0.0.0/16")["Vpc"]["VpcId"]
+    subnet_id = ec2_client.create_subnet(CidrBlock="10.0.1.0/24", VpcId=vpc_id)[
+        "Subnet"
+    ]["SubnetId"]
+    subnet_id_2 = ec2_client.create_subnet(CidrBlock="10.0.2.0/24", VpcId=vpc_id)[
+        "Subnet"
+    ]["SubnetId"]
+    rds_client.create_db_proxy(
+        DBProxyName="testrdsproxydescribe",
+        EngineFamily="MYSQL",
+        Auth=[
+            {
+                "Description": "Test Description",
+                "UserName": "Test Username",
+                "AuthScheme": "SECRETS",
+                "SecretArn": "TestSecretARN",
+                "IAMAuth": "ENABLED",
+                "ClientPasswordAuthType": "MYSQL_NATIVE_PASSWORD",
+            },
+        ],
+        RoleArn="TestArn",
+        VpcSubnetIds=[subnet_id, subnet_id_2],
+        RequireTLS=True,
+        Tags=[
+            {"Key": "TestKey", "Value": "TestValue"},
+            {"Key": "aaa", "Value": "bbb"},
+        ],
+    )
+    response = rds_client.describe_db_proxies(DBProxyName="testrdsproxydescribe")
+    db_proxy = response["DBProxies"][0]
+    assert db_proxy["DBProxyName"] == "testrdsproxydescribe"
+    assert (
+        db_proxy["DBProxyArn"]
+        == f"arn:aws:rds:us-west-2:{ACCOUNT_ID}:db-proxy:testrdsproxydescribe"
+    )
+    assert db_proxy["Status"] == "availible"
+    assert db_proxy["EngineFamily"] == "MYSQL"
+    assert db_proxy["VpcId"] == vpc_id
+    assert db_proxy["VpcSecurityGroupIds"] == []
+    assert db_proxy["VpcSubnetIds"] == [subnet_id, subnet_id_2]
+    assert db_proxy["Auth"] == [
+        {
+            "UserName": "Test Username",
+            "AuthScheme": "SECRETS",
+            "SecretArn": "TestSecretARN",
+            "IAMAuth": "ENABLED",
+            "ClientPasswordAuthType": "MYSQL_NATIVE_PASSWORD",
+        }
+    ]
+    assert db_proxy["RoleArn"] == "TestArn"
+    assert db_proxy["RequireTLS"] is True
+    assert db_proxy["IdleClientTimeout"] == 1800
+    assert db_proxy["DebugLogging"] is False
+
+
+@mock_aws
+def test_list_tags_db_proxy():
+    rds_client = boto3.client("rds", region_name=DEFAULT_REGION)
+    ec2_client = boto3.client("ec2", region_name=DEFAULT_REGION)
+    vpc_id = ec2_client.create_vpc(CidrBlock="10.0.0.0/16")["Vpc"]["VpcId"]
+    subnet_id = ec2_client.create_subnet(CidrBlock="10.0.1.0/24", VpcId=vpc_id)[
+        "Subnet"
+    ]["SubnetId"]
+    subnet_id_2 = ec2_client.create_subnet(CidrBlock="10.0.2.0/24", VpcId=vpc_id)[
+        "Subnet"
+    ]["SubnetId"]
+    resp = rds_client.create_db_proxy(
+        DBProxyName="testrdsproxydescribe",
+        EngineFamily="MYSQL",
+        Auth=[
+            {
+                "Description": "Test Description",
+                "UserName": "Test Username",
+                "AuthScheme": "SECRETS",
+                "SecretArn": "TestSecretARN",
+                "IAMAuth": "ENABLED",
+                "ClientPasswordAuthType": "MYSQL_NATIVE_PASSWORD",
+            },
+        ],
+        RoleArn="TestArn",
+        VpcSubnetIds=[subnet_id, subnet_id_2],
+        RequireTLS=True,
+        Tags=[
+            {"Key": "TestKey", "Value": "TestValue"},
+            {"Key": "aaa", "Value": "bbb"},
+        ],
+    )
+    arn = resp["DBProxy"]["DBProxyArn"]
+    resp = rds_client.list_tags_for_resource(ResourceName=arn)
+    assert resp["TagList"] == [
+        {"Value": "TestValue", "Key": "TestKey"},
+        {"Value": "bbb", "Key": "aaa"},
+    ]
+
+
+@mock_aws
+def test_create_db_proxy_invalid_subnet():
+    rds_client = boto3.client("rds", region_name=DEFAULT_REGION)
+    ec2_client = boto3.client("ec2", region_name=DEFAULT_REGION)
+    vpc_id = ec2_client.create_vpc(CidrBlock="10.0.0.0/16")["Vpc"]["VpcId"]
+    vpc_id_2 = ec2_client.create_vpc(CidrBlock="10.0.0.0/16")["Vpc"]["VpcId"]
+    subnet_id = ec2_client.create_subnet(CidrBlock="10.0.1.0/24", VpcId=vpc_id)[
+        "Subnet"
+    ]["SubnetId"]
+    subnet_id_2 = ec2_client.create_subnet(CidrBlock="10.0.2.0/24", VpcId=vpc_id_2)[
+        "Subnet"
+    ]["SubnetId"]
+    with pytest.raises(ClientError) as ex:
+        rds_client.create_db_proxy(
+            DBProxyName="testrdsproxy",
+            EngineFamily="MYSQL",
+            Auth=[
+                {
+                    "Description": "Test Description",
+                    "UserName": "Test Username",
+                    "AuthScheme": "SECRETS",
+                    "SecretArn": "TestSecretARN",
+                    "IAMAuth": "ENABLED",
+                    "ClientPasswordAuthType": "MYSQL_NATIVE_PASSWORD",
+                },
+            ],
+            RoleArn="TestArn",
+            VpcSubnetIds=[subnet_id, subnet_id_2],
+            RequireTLS=True,
+            Tags=[{"Key": "TestKey", "Value": "TestValue"}],
+        )
+    err = ex.value.response["Error"]
+    assert err["Code"] == "InvalidSubnet"
+
+
+@mock_aws
+def test_create_db_proxy_duplicate_name():
+    rds_client = boto3.client("rds", region_name=DEFAULT_REGION)
+    ec2_client = boto3.client("ec2", region_name=DEFAULT_REGION)
+    vpc_id = ec2_client.create_vpc(CidrBlock="10.0.0.0/16")["Vpc"]["VpcId"]
+    subnet_id = ec2_client.create_subnet(CidrBlock="10.0.1.0/24", VpcId=vpc_id)[
+        "Subnet"
+    ]["SubnetId"]
+    subnet_id_2 = ec2_client.create_subnet(CidrBlock="10.0.2.0/24", VpcId=vpc_id)[
+        "Subnet"
+    ]["SubnetId"]
+    rds_client.create_db_proxy(
+        DBProxyName="testrdsproxy",
+        EngineFamily="MYSQL",
+        Auth=[
+            {
+                "Description": "Test Description",
+                "UserName": "Test Username",
+                "AuthScheme": "SECRETS",
+                "SecretArn": "TestSecretARN",
+                "IAMAuth": "ENABLED",
+                "ClientPasswordAuthType": "MYSQL_NATIVE_PASSWORD",
+            },
+        ],
+        RoleArn="TestArn",
+        VpcSubnetIds=[subnet_id, subnet_id_2],
+        RequireTLS=True,
+        Tags=[{"Key": "TestKey", "Value": "TestValue"}],
+    )
+    with pytest.raises(ClientError) as ex:
+        rds_client.create_db_proxy(
+            DBProxyName="testrdsproxy",
+            EngineFamily="MYSQL",
+            Auth=[
+                {
+                    "Description": "Test Description",
+                    "UserName": "Test Username",
+                    "AuthScheme": "SECRETS",
+                    "SecretArn": "TestSecretARN",
+                    "IAMAuth": "ENABLED",
+                    "ClientPasswordAuthType": "MYSQL_NATIVE_PASSWORD",
+                },
+            ],
+            RoleArn="TestArn",
+            VpcSubnetIds=[subnet_id, subnet_id_2],
+            RequireTLS=True,
+            Tags=[{"Key": "TestKey", "Value": "TestValue"}],
+        )
+    err = ex.value.response["Error"]
+    assert err["Code"] == "DBProxyAlreadyExistsFault"
+    assert (
+        err["Message"]
+        == "Cannot create the DBProxy because a DBProxy with the identifier testrdsproxy already exists."
+    )
+
+
+@mock_aws
+def test_describe_db_proxies_not_found():
+    rds_client = boto3.client("rds", region_name=DEFAULT_REGION)
+    with pytest.raises(ClientError) as ex:
+        rds_client.describe_db_proxies(DBProxyName="testrdsproxydescribe")
+    err = ex.value.response["Error"]
+    assert err["Code"] == "DBProxyNotFoundFault"
+    assert (
+        err["Message"]
+        == "The specified proxy name testrdsproxydescribe doesn't correspond to a proxy owned by your Amazon Web Services account in the specified Amazon Web Services Region."
+    )
diff --git a/tests/test_resourcegroupstaggingapi/test_resourcegroupstaggingapi.py b/tests/test_resourcegroupstaggingapi/test_resourcegroupstaggingapi.py
index e01291123..4034621d9 100644
--- a/tests/test_resourcegroupstaggingapi/test_resourcegroupstaggingapi.py
+++ b/tests/test_resourcegroupstaggingapi/test_resourcegroupstaggingapi.py
@@ -745,6 +745,21 @@ def test_get_resources_sqs():
     assert {"Key": "Test", "Value": "1"} in resp["ResourceTagMappingList"][0]["Tags"]
 
 
+@mock_aws
+def test_get_resources_sns():
+    sns = boto3.client("sns", region_name="us-east-1")
+
+    sns.create_topic(Name="test", Tags=[{"Key": "Shape", "Value": "Square"}])
+
+    rtapi = boto3.client("resourcegroupstaggingapi", region_name="us-east-1")
+    resp = rtapi.get_resources(ResourceTypeFilters=["sns"])
+
+    assert len(resp["ResourceTagMappingList"]) == 1
+    assert {"Key": "Shape", "Value": "Square"} in resp["ResourceTagMappingList"][0][
+        "Tags"
+    ]
+
+
 @mock_aws
 def test_tag_resources_for_unknown_service():
     rtapi = boto3.client("resourcegroupstaggingapi", region_name="us-west-2")
@@ -757,3 +772,53 @@ def test_tag_resources_for_unknown_service():
         missing_resources["arn:aws:service_x"]["ErrorCode"]
         == "InternalServiceException"
     )
+
+
+@mock_aws
+def test_get_resources_elb():
+    client = boto3.client("elb", region_name="us-east-1")
+    lb_name = "my-lb"
+    lb2_name = "second-lb"
+    client.create_load_balancer(
+        LoadBalancerName=lb_name,
+        Listeners=[
+            {"Protocol": "tcp", "LoadBalancerPort": 80, "InstancePort": 8080},
+            {"Protocol": "http", "LoadBalancerPort": 81, "InstancePort": 9000},
+        ],
+        Scheme="internal",
+    )
+    client.add_tags(
+        LoadBalancerNames=[lb_name],
+        Tags=[
+            {"Key": "burger", "Value": "krabby patty"},
+        ],
+    )
+    client.create_load_balancer(
+        LoadBalancerName=lb2_name,
+        Listeners=[
+            {"Protocol": "tcp", "LoadBalancerPort": 80, "InstancePort": 8080},
+            {"Protocol": "http", "LoadBalancerPort": 81, "InstancePort": 9000},
+        ],
+        Scheme="internal",
+    )
+    client.add_tags(
+        LoadBalancerNames=[lb2_name],
+        Tags=[
+            {"Key": "color", "Value": "blue"},
+            {"Key": "shape", "Value": "square"},
+        ],
+    )
+    rgta_client = boto3.client("resourcegroupstaggingapi", region_name="us-east-1")
+    resources_no_filter = rgta_client.get_resources(
+        ResourceTypeFilters=["elb"],
+    )
+    assert len(resources_no_filter["ResourceTagMappingList"]) == 2
+
+    resources_burger_filter = rgta_client.get_resources(
+        TagFilters=[{"Key": "burger", "Values": ["krabby patty"]}]
+    )
+    assert len(resources_burger_filter["ResourceTagMappingList"]) == 1
+    assert (
+        f"arn:aws:elasticloadbalancing:us-east-1:123456789012:loadbalancer/{lb_name}"
+        == resources_burger_filter["ResourceTagMappingList"][0]["ResourceARN"]
+    )
diff --git a/tests/test_s3/test_s3_config.py b/tests/test_s3/test_s3_config.py
index da40514bd..31edb04af 100644
--- a/tests/test_s3/test_s3_config.py
+++ b/tests/test_s3/test_s3_config.py
@@ -339,6 +339,7 @@ def test_s3_notification_config_dict():
                 },
             }
         ],
+        "EventBridgeConfiguration": {},
     }
 
     s3_config_query.backends[DEFAULT_ACCOUNT_ID][
@@ -389,6 +390,7 @@ def test_s3_notification_config_dict():
                 "queueARN": "arn:aws:lambda:us-west-2:012345678910:function:mylambda",
                 "type": "LambdaConfiguration",
             },
+            "EventBridgeConfiguration": {},
         }
     }
 
diff --git a/tests/test_s3/test_s3_eventbridge_integration.py b/tests/test_s3/test_s3_eventbridge_integration.py
new file mode 100644
index 000000000..40854e6a5
--- /dev/null
+++ b/tests/test_s3/test_s3_eventbridge_integration.py
@@ -0,0 +1,59 @@
+import json
+from uuid import uuid4
+
+import boto3
+
+from moto import mock_aws
+from moto.core import DEFAULT_ACCOUNT_ID as ACCOUNT_ID
+
+REGION_NAME = "us-east-1"
+
+
+@mock_aws
+def test_pub_object_notification():
+    s3_res = boto3.resource("s3", region_name=REGION_NAME)
+    s3_client = boto3.client("s3", region_name=REGION_NAME)
+    events_client = boto3.client("events", region_name=REGION_NAME)
+    logs_client = boto3.client("logs", region_name=REGION_NAME)
+
+    rule_name = "test-rule"
+    events_client.put_rule(
+        Name=rule_name, EventPattern=json.dumps({"account": [ACCOUNT_ID]})
+    )
+    log_group_name = "/test-group"
+    logs_client.create_log_group(logGroupName=log_group_name)
+    events_client.put_targets(
+        Rule=rule_name,
+        Targets=[
+            {
+                "Id": "test",
+                "Arn": f"arn:aws:logs:{REGION_NAME}:{ACCOUNT_ID}:log-group:{log_group_name}",
+            }
+        ],
+    )
+
+    # Create S3 bucket
+    bucket_name = str(uuid4())
+    s3_res.create_bucket(Bucket=bucket_name)
+
+    # Put Notification
+    s3_client.put_bucket_notification_configuration(
+        Bucket=bucket_name,
+        NotificationConfiguration={"EventBridgeConfiguration": {}},
+    )
+
+    # Put Object
+    s3_client.put_object(Bucket=bucket_name, Key="keyname", Body="bodyofnewobject")
+
+    events = sorted(
+        logs_client.filter_log_events(logGroupName=log_group_name)["events"],
+        key=lambda item: item["eventId"],
+    )
+    assert len(events) == 1
+    event_message = json.loads(events[0]["message"])
+    assert event_message["detail-type"] == "Object Created"
+    assert event_message["source"] == "aws.s3"
+    assert event_message["account"] == ACCOUNT_ID
+    assert event_message["region"] == REGION_NAME
+    assert event_message["detail"]["bucket"]["name"] == bucket_name
+    assert event_message["detail"]["reason"] == "ObjectCreated"
diff --git a/tests/test_s3/test_s3_notifications.py b/tests/test_s3/test_s3_notifications.py
new file mode 100644
index 000000000..a7c1c5c4a
--- /dev/null
+++ b/tests/test_s3/test_s3_notifications.py
@@ -0,0 +1,135 @@
+import json
+from typing import List
+from unittest import SkipTest
+from uuid import uuid4
+
+import boto3
+import pytest
+
+from moto import mock_aws, settings
+from moto.core import DEFAULT_ACCOUNT_ID as ACCOUNT_ID
+from moto.s3.models import FakeBucket, FakeKey
+from moto.s3.notifications import (
+    S3NotificationEvent,
+    _detail_type,
+    _send_event_bridge_message,
+)
+
+REGION_NAME = "us-east-1"
+
+
+@pytest.mark.parametrize(
+    "event_names, expected_event_message",
+    [
+        (
+            [
+                S3NotificationEvent.OBJECT_CREATED_PUT_EVENT,
+                S3NotificationEvent.OBJECT_CREATED_POST_EVENT,
+                S3NotificationEvent.OBJECT_CREATED_COPY_EVENT,
+                S3NotificationEvent.OBJECT_CREATED_COMPLETE_MULTIPART_UPLOAD_EVENT,
+            ],
+            "Object Created",
+        ),
+        (
+            [
+                S3NotificationEvent.OBJECT_REMOVED_DELETE_EVENT,
+                S3NotificationEvent.OBJECT_REMOVED_DELETE_MARKER_CREATED_EVENT,
+            ],
+            "Object Deleted",
+        ),
+        ([S3NotificationEvent.OBJECT_RESTORE_POST_EVENT], "Object Restore Initiated"),
+        (
+            [S3NotificationEvent.OBJECT_RESTORE_COMPLETED_EVENT],
+            "Object Restore Completed",
+        ),
+        (
+            [S3NotificationEvent.OBJECT_RESTORE_DELETE_EVENT],
+            "Object Restore Expired",
+        ),
+        (
+            [S3NotificationEvent.LIFECYCLE_TRANSITION_EVENT],
+            "Object Storage Class Changed",
+        ),
+        ([S3NotificationEvent.INTELLIGENT_TIERING_EVENT], "Object Access Tier Changed"),
+        ([S3NotificationEvent.OBJECT_ACL_EVENT], "Object ACL Updated"),
+        ([S3NotificationEvent.OBJECT_TAGGING_PUT_EVENT], "Object Tags Added"),
+        ([S3NotificationEvent.OBJECT_TAGGING_DELETE_EVENT], "Object Tags Deleted"),
+    ],
+)
+def test_detail_type(event_names: List[str], expected_event_message: str):
+    for event_name in event_names:
+        assert _detail_type(event_name) == expected_event_message
+
+
+def test_detail_type_unknown_event():
+    with pytest.raises(ValueError) as ex:
+        _detail_type("unknown event")
+    assert (
+        str(ex.value)
+        == "unsupported event `unknown event` for s3 eventbridge notification (https://docs.aws.amazon.com/AmazonS3/latest/userguide/EventBridge.html)"
+    )
+
+
+@mock_aws
+def test_send_event_bridge_message():
+    # setup mocks
+    events_client = boto3.client("events", region_name=REGION_NAME)
+    logs_client = boto3.client("logs", region_name=REGION_NAME)
+    rule_name = "test-rule"
+    events_client.put_rule(
+        Name=rule_name, EventPattern=json.dumps({"account": [ACCOUNT_ID]})
+    )
+    log_group_name = "/test-group"
+    logs_client.create_log_group(logGroupName=log_group_name)
+    mocked_bucket = FakeBucket(str(uuid4()), ACCOUNT_ID, REGION_NAME)
+    mocked_key = FakeKey(
+        "test-key", bytes("test content", encoding="utf-8"), ACCOUNT_ID
+    )
+
+    # do nothing if event target does not exists.
+    _send_event_bridge_message(
+        ACCOUNT_ID,
+        mocked_bucket,
+        S3NotificationEvent.OBJECT_CREATED_PUT_EVENT,
+        mocked_key,
+    )
+    assert (
+        len(logs_client.filter_log_events(logGroupName=log_group_name)["events"]) == 0
+    )
+
+    # do nothing even if an error is raised while sending events.
+    events_client.put_targets(
+        Rule=rule_name,
+        Targets=[
+            {
+                "Id": "test",
+                "Arn": f"arn:aws:logs:{REGION_NAME}:{ACCOUNT_ID}:log-group:{log_group_name}",
+            }
+        ],
+    )
+
+    _send_event_bridge_message(ACCOUNT_ID, mocked_bucket, "unknown-event", mocked_key)
+    assert (
+        len(logs_client.filter_log_events(logGroupName=log_group_name)["events"]) == 0
+    )
+
+    if not settings.TEST_DECORATOR_MODE:
+        raise SkipTest(("Doesn't quite work right with the Proxy or Server"))
+    # an event is correctly sent to the log group.
+    _send_event_bridge_message(
+        ACCOUNT_ID,
+        mocked_bucket,
+        S3NotificationEvent.OBJECT_CREATED_PUT_EVENT,
+        mocked_key,
+    )
+    events = logs_client.filter_log_events(logGroupName=log_group_name)["events"]
+    assert len(events) == 1
+    event_msg = json.loads(events[0]["message"])
+    assert event_msg["detail-type"] == "Object Created"
+    assert event_msg["source"] == "aws.s3"
+    assert event_msg["region"] == REGION_NAME
+    assert event_msg["resources"] == [f"arn:aws:s3:::{mocked_bucket.name}"]
+    event_detail = event_msg["detail"]
+    assert event_detail["bucket"] == {"name": mocked_bucket.name}
+    assert event_detail["object"]["key"] == mocked_key.name
+    assert event_detail["reason"] == "ObjectCreated"