Repository: getmoto/moto
Pull request #7429 (2024-03-06 17:22:55)

fix: add dynamodb type mismatch validator
when doing dynamodb `update_item`, it should check for type mismatch 
e.g. for attribute defined as `String`, it should throws an error when `NULL`, or other data type e.g. `Number`, `Binary` is received


Patch:
diff --git a/moto/dynamodb/parsing/validators.py b/moto/dynamodb/parsing/validators.py
index 53d90b1df..fd54c7c80 100644
--- a/moto/dynamodb/parsing/validators.py
+++ b/moto/dynamodb/parsing/validators.py
@@ -1,6 +1,7 @@
 """
 See docstring class Validator below for more details on validation
 """
+
 from abc import abstractmethod
 from copy import deepcopy
 from typing import Any, Callable, Dict, List, Type, Union
@@ -12,6 +13,7 @@ from moto.dynamodb.exceptions import (
     ExpressionAttributeNameNotDefined,
     ExpressionAttributeValueNotDefined,
     IncorrectOperandType,
+    InvalidAttributeTypeError,
     InvalidUpdateExpressionInvalidDocumentPath,
     MockValidationException,
     ProvidedKeyDoesNotExist,
@@ -379,6 +381,34 @@ class EmptyStringKeyValueValidator(DepthFirstTraverser):  # type: ignore[misc]
         return node
 
 
+class TypeMismatchValidator(DepthFirstTraverser):  # type: ignore[misc]
+    def __init__(self, key_attributes_type: List[Dict[str, str]]):
+        self.key_attributes_type = key_attributes_type
+
+    def _processing_map(
+        self,
+    ) -> Dict[
+        Type[UpdateExpressionSetAction],
+        Callable[[UpdateExpressionSetAction], UpdateExpressionSetAction],
+    ]:
+        return {UpdateExpressionSetAction: self.check_for_type_mismatch}
+
+    def check_for_type_mismatch(
+        self, node: UpdateExpressionSetAction
+    ) -> UpdateExpressionSetAction:
+        """A node representing a SET action. Check that type matches with the definition"""
+        assert isinstance(node, UpdateExpressionSetAction)
+        assert len(node.children) == 2
+        key = node.children[0].children[0].children[0]
+        val_node = node.children[1].children[0]
+        for dct in self.key_attributes_type:
+            if dct["AttributeName"] == key and dct["AttributeType"] != val_node.type:
+                raise InvalidAttributeTypeError(
+                    key, dct["AttributeType"], val_node.type
+                )
+        return node
+
+
 class UpdateHashRangeKeyValidator(DepthFirstTraverser):  # type: ignore[misc]
     def __init__(
         self,
@@ -464,6 +494,7 @@ class UpdateExpressionValidator(Validator):
             UpdateExpressionFunctionEvaluator(),
             NoneExistingPathChecker(),
             ExecuteOperations(),
+            TypeMismatchValidator(self.table.attr),
             EmptyStringKeyValueValidator(self.table.attribute_keys),
         ]
         return processors
diff --git a/moto/ec2/models/vpcs.py b/moto/ec2/models/vpcs.py
index 4c4b67f5f..299f17d4b 100644
--- a/moto/ec2/models/vpcs.py
+++ b/moto/ec2/models/vpcs.py
@@ -263,6 +263,22 @@ class VPC(TaggedEC2Resource, CloudFormationModel):
         # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-vpc.html
         return "AWS::EC2::VPC"
 
+    def get_cfn_attribute(self, attribute_name: str) -> Any:
+        from moto.cloudformation.exceptions import UnformattedGetAttTemplateException
+
+        if attribute_name == "CidrBlock":
+            return self.cidr_block
+        elif attribute_name == "CidrBlockAssociations":
+            return self.cidr_block_association_set
+        elif attribute_name == "DefaultSecurityGroup":
+            sec_group = self.ec2_backend.get_security_group_from_name(
+                "default", vpc_id=self.id
+            )
+            return sec_group.id
+        elif attribute_name == "VpcId":
+            return self.id
+        raise UnformattedGetAttTemplateException()
+
     @classmethod
     def create_from_cloudformation_json(  # type: ignore[misc]
         cls,
diff --git a/moto/efs/models.py b/moto/efs/models.py
index 33df67491..ce1419243 100644
--- a/moto/efs/models.py
+++ b/moto/efs/models.py
@@ -10,7 +10,7 @@ from copy import deepcopy
 from typing import Any, Dict, Iterator, List, Optional, Set, Tuple, Union
 
 from moto.core.base_backend import BackendDict, BaseBackend
-from moto.core.common_models import BaseModel, CloudFormationModel
+from moto.core.common_models import CloudFormationModel
 from moto.core.utils import camelcase_to_underscores, underscores_to_camelcase
 from moto.ec2 import ec2_backends
 from moto.ec2.exceptions import InvalidSubnetIdError
@@ -43,7 +43,7 @@ def _lookup_az_id(account_id: str, az_name: str) -> Optional[str]:
     return None
 
 
-class AccessPoint(BaseModel):
+class AccessPoint(CloudFormationModel):
     def __init__(
         self,
         account_id: str,
@@ -84,6 +84,38 @@ class AccessPoint(BaseModel):
             "LifeCycleState": "available",
         }
 
+    @staticmethod
+    def cloudformation_type() -> str:
+        return "AWS::EFS::AccessPoint"
+
+    @classmethod
+    def create_from_cloudformation_json(  # type: ignore[misc]
+        cls,
+        resource_name: str,
+        cloudformation_json: Any,
+        account_id: str,
+        region_name: str,
+        **kwargs: Any,
+    ) -> "AccessPoint":
+        props = cloudformation_json["Properties"]
+        file_system_id = props["FileSystemId"]
+        posix_user = props.get("PosixUser", {})
+        root_directory = props.get("RootDirectory", {})
+        tags = props.get("AccessPointTags", [])
+
+        backend: EFSBackend = efs_backends[account_id][region_name]
+        return backend.create_access_point(
+            resource_name,
+            file_system_id=file_system_id,
+            posix_user=posix_user,
+            root_directory=root_directory,
+            tags=tags,
+        )
+
+    def delete(self, account_id: str, region_name: str) -> None:
+        backend: EFSBackend = efs_backends[account_id][region_name]
+        backend.delete_access_point(self.access_point_id)
+
 
 class FileSystem(CloudFormationModel):
     """A model for an EFS File System Volume."""
@@ -223,40 +255,36 @@ class FileSystem(CloudFormationModel):
         region_name: str,
         **kwargs: Any,
     ) -> "FileSystem":
-        # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-efs-filesystem.html
-        props = deepcopy(cloudformation_json["Properties"])
-        props = {camelcase_to_underscores(k): v for k, v in props.items()}
-        if "file_system_tags" in props:
-            props["tags"] = props.pop("file_system_tags")
-        if "backup_policy" in props:
-            if "status" not in props["backup_policy"]:
-                raise ValueError("BackupPolicy must be of type BackupPolicy.")
-            status = props.pop("backup_policy")["status"]
-            if status not in ["ENABLED", "DISABLED"]:
-                raise ValueError(f'Invalid status: "{status}".')
-            props["backup"] = status == "ENABLED"
-        if "bypass_policy_lockout_safety_check" in props:
-            raise ValueError(
-                "BypassPolicyLockoutSafetyCheck not currently "
-                "supported by AWS Cloudformation."
-            )
-
-        return efs_backends[account_id][region_name].create_file_system(
-            resource_name, **props
+        props = cloudformation_json["Properties"]
+        performance_mode = props.get("PerformanceMode", "generalPurpose")
+        encrypted = props.get("Encrypted", False)
+        kms_key_id = props.get("KmsKeyId")
+        throughput_mode = props.get("ThroughputMode", "bursting")
+        provisioned_throughput_in_mibps = props.get("ProvisionedThroughputInMibps", 0)
+        availability_zone_name = props.get("AvailabilityZoneName")
+        backup = props.get("BackupPolicy", {}).get("Status") == "ENABLED"
+        tags = props.get("FileSystemTags", [])
+
+        lifecycle_policies = props.get("LifecyclePolicies", [])
+
+        backend: EFSBackend = efs_backends[account_id][region_name]
+        fs = backend.create_file_system(
+            resource_name,
+            performance_mode=performance_mode,
+            encrypted=encrypted,
+            kms_key_id=kms_key_id,
+            throughput_mode=throughput_mode,
+            provisioned_throughput_in_mibps=provisioned_throughput_in_mibps,
+            availability_zone_name=availability_zone_name,
+            backup=backup,
+            tags=tags,
         )
 
-    @classmethod
-    def update_from_cloudformation_json(  # type: ignore[misc]
-        cls,
-        original_resource: Any,
-        new_resource_name: str,
-        cloudformation_json: Any,
-        account_id: str,
-        region_name: str,
-    ) -> None:
-        raise NotImplementedError(
-            "Update of EFS File System via cloudformation is not yet implemented."
-        )
+        if lifecycle_policies:
+            backend.put_lifecycle_configuration(
+                file_system_id=fs.file_system_id, policies=lifecycle_policies
+            )
+        return fs
 
     @classmethod
     def delete_from_cloudformation_json(  # type: ignore[misc]
@@ -357,7 +385,6 @@ class MountTarget(CloudFormationModel):
         region_name: str,
         **kwargs: Any,
     ) -> "MountTarget":
-        # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-efs-mounttarget.html
         props = deepcopy(cloudformation_json["Properties"])
         props = {camelcase_to_underscores(k): v for k, v in props.items()}
         return efs_backends[account_id][region_name].create_mount_target(**props)
diff --git a/moto/emr/models.py b/moto/emr/models.py
index 58f65613c..c73923919 100644
--- a/moto/emr/models.py
+++ b/moto/emr/models.py
@@ -449,13 +449,64 @@ class FakeCluster(CloudFormationModel):
         cluster.add_tags(tags)
         return cluster
 
+    @classmethod
+    def delete_from_cloudformation_json(  # type: ignore[misc]
+        cls,
+        resource_name: str,
+        cloudformation_json: Dict[str, Any],
+        account_id: str,
+        region_name: str,
+    ) -> None:
+        emr_backend: ElasticMapReduceBackend = emr_backends[account_id][region_name]
+
+        emr_backend.terminate_job_flows([resource_name])
 
-class FakeSecurityConfiguration(BaseModel):
+
+class FakeSecurityConfiguration(CloudFormationModel):
     def __init__(self, name: str, security_configuration: str):
         self.name = name
         self.security_configuration = security_configuration
         self.creation_date_time = datetime.now(timezone.utc)
 
+    @property
+    def physical_resource_id(self) -> str:
+        return self.name
+
+    @staticmethod
+    def cloudformation_type() -> str:
+        return "AWS::EMR::SecurityConfiguration"
+
+    @classmethod
+    def create_from_cloudformation_json(  # type: ignore[misc]
+        cls,
+        resource_name: str,
+        cloudformation_json: Any,
+        account_id: str,
+        region_name: str,
+        **kwargs: Any,
+    ) -> "FakeSecurityConfiguration":
+        emr_backend: ElasticMapReduceBackend = emr_backends[account_id][region_name]
+
+        properties = cloudformation_json["Properties"]
+        return emr_backend.create_security_configuration(
+            name=properties.get("Name") or resource_name,
+            security_configuration=properties.get("SecurityConfiguration", {}),
+        )
+
+    @classmethod
+    def delete_from_cloudformation_json(  # type: ignore[misc]
+        cls,
+        resource_name: str,
+        cloudformation_json: Dict[str, Any],
+        account_id: str,
+        region_name: str,
+    ) -> None:
+        emr_backend: ElasticMapReduceBackend = emr_backends[account_id][region_name]
+
+        properties = cloudformation_json["Properties"]
+        name = properties.get("Name") or resource_name
+        emr_backend.delete_security_configuration(name)
+
 
 class ElasticMapReduceBackend(BaseBackend):
     def __init__(self, region_name: str, account_id: str):
diff --git a/moto/iam/models.py b/moto/iam/models.py
index 6a21e0781..1231544cb 100644
--- a/moto/iam/models.py
+++ b/moto/iam/models.py
@@ -957,7 +957,9 @@ class InstanceProfile(CloudFormationModel):
         account_id: str,
         region_name: str,
     ) -> None:
-        iam_backends[account_id]["global"].delete_instance_profile(resource_name)
+        iam_backends[account_id]["global"].delete_instance_profile(
+            resource_name, ignore_attached_roles=True
+        )
 
     def delete_role(self, role_name: str) -> None:
         self.roles = [role for role in self.roles if role.name != role_name]
@@ -2370,9 +2372,11 @@ class IAMBackend(BaseBackend):
         self.instance_profiles[name] = instance_profile
         return instance_profile
 
-    def delete_instance_profile(self, name: str) -> None:
+    def delete_instance_profile(
+        self, name: str, ignore_attached_roles: bool = False
+    ) -> None:
         instance_profile = self.get_instance_profile(name)
-        if len(instance_profile.roles) > 0:
+        if len(instance_profile.roles) > 0 and not ignore_attached_roles:
             raise IAMConflictException(
                 code="DeleteConflict",
                 message="Cannot delete entity, must remove roles from instance profile first.",
diff --git a/moto/s3/models.py b/moto/s3/models.py
index 31df9c21d..cf5643d03 100644
--- a/moto/s3/models.py
+++ b/moto/s3/models.py
@@ -1298,7 +1298,7 @@ class FakeBucket(CloudFormationModel):
                 "The target bucket for logging does not exist."
             )
 
-        target_prefix = self.logging.get("TargetPrefix", None)
+        target_prefix = logging_config.get("TargetPrefix", None)
         has_policy_permissions = self._log_permissions_enabled_policy(
             target_bucket=target_bucket, target_prefix=target_prefix
         )
@@ -2599,10 +2599,17 @@ class S3Backend(BaseBackend, CloudWatchMetricProvider):
         bucket = self.get_bucket(bucket_name)
 
         response_meta = {}
+        delete_key = bucket.keys.get(key_name)
 
         try:
             if not bucket.is_versioned:
                 bucket.keys.pop(key_name)
+                notifications.send_event(
+                    self.account_id,
+                    notifications.S3NotificationEvent.OBJECT_REMOVED_DELETE_EVENT,
+                    bucket,
+                    delete_key,
+                )
             else:
                 if version_id is None:
                     delete_marker = self._set_delete_marker(bucket_name, key_name)
@@ -2645,6 +2652,13 @@ class S3Backend(BaseBackend, CloudWatchMetricProvider):
 
                     if not bucket.keys.getlist(key_name):
                         bucket.keys.pop(key_name)
+                        notifications.send_event(
+                            self.account_id,
+                            notifications.S3NotificationEvent.OBJECT_REMOVED_DELETE_EVENT,
+                            bucket,
+                            delete_key,
+                        )
+
             return True, response_meta
         except KeyError:
             return False, None


Test patch:
diff --git a/tests/test_dynamodb/test_dynamodb.py b/tests/test_dynamodb/test_dynamodb.py
index 808d48d2f..8feeb8fa2 100644
--- a/tests/test_dynamodb/test_dynamodb.py
+++ b/tests/test_dynamodb/test_dynamodb.py
@@ -5880,3 +5880,80 @@ def test_invalid_projection_expressions():
         ClientError, match="ProjectionExpression: Attribute name contains white space"
     ):
         client.scan(TableName=table_name, ProjectionExpression="not_a_keyword, na me")
+
+
+@mock_aws
+def test_update_item_with_global_secondary_index():
+    dynamodb = boto3.resource("dynamodb", region_name="us-east-1")
+
+    # Create the DynamoDB table
+    dynamodb.create_table(
+        TableName="test",
+        KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}],
+        AttributeDefinitions=[
+            {"AttributeName": "id", "AttributeType": "S"},
+            {"AttributeName": "gsi_hash_key_s", "AttributeType": "S"},
+            {"AttributeName": "gsi_hash_key_b", "AttributeType": "B"},
+            {"AttributeName": "gsi_hash_key_n", "AttributeType": "N"},
+        ],
+        ProvisionedThroughput={"ReadCapacityUnits": 1, "WriteCapacityUnits": 1},
+        GlobalSecondaryIndexes=[
+            {
+                "IndexName": "test_gsi_s",
+                "KeySchema": [
+                    {"AttributeName": "gsi_hash_key_s", "KeyType": "HASH"},
+                ],
+                "Projection": {"ProjectionType": "ALL"},
+                "ProvisionedThroughput": {
+                    "ReadCapacityUnits": 1,
+                    "WriteCapacityUnits": 1,
+                },
+            },
+            {
+                "IndexName": "test_gsi_b",
+                "KeySchema": [
+                    {"AttributeName": "gsi_hash_key_b", "KeyType": "HASH"},
+                ],
+                "Projection": {"ProjectionType": "ALL"},
+                "ProvisionedThroughput": {
+                    "ReadCapacityUnits": 1,
+                    "WriteCapacityUnits": 1,
+                },
+            },
+            {
+                "IndexName": "test_gsi_n",
+                "KeySchema": [
+                    {"AttributeName": "gsi_hash_key_n", "KeyType": "HASH"},
+                ],
+                "Projection": {"ProjectionType": "ALL"},
+                "ProvisionedThroughput": {
+                    "ReadCapacityUnits": 1,
+                    "WriteCapacityUnits": 1,
+                },
+            },
+        ],
+    )
+    table = dynamodb.Table("test")
+
+    table.put_item(
+        Item={"id": "test1"},
+    )
+
+    for key_name, values in {
+        "gsi_hash_key_s": [None, 0, b"binary"],
+        "gsi_hash_key_b": [None, "", 0],
+        "gsi_hash_key_n": [None, "", b"binary"],
+    }.items():
+        for v in values:
+            with pytest.raises(ClientError) as ex:
+                table.update_item(
+                    Key={"id": "test1"},
+                    UpdateExpression=f"SET {key_name} = :gsi_hash_key",
+                    ExpressionAttributeValues={":gsi_hash_key": v, ":abc": ""},
+                )
+            err = ex.value.response["Error"]
+            assert err["Code"] == "ValidationException"
+            assert (
+                "One or more parameter values were invalid: Type mismatch"
+                in err["Message"]
+            )
diff --git a/tests/test_efs/test_efs_cloudformation.py b/tests/test_efs/test_efs_cloudformation.py
new file mode 100644
index 000000000..98b443380
--- /dev/null
+++ b/tests/test_efs/test_efs_cloudformation.py
@@ -0,0 +1,189 @@
+import json
+
+import boto3
+
+from moto import mock_aws
+
+template_fs_simple = {
+    "AWSTemplateFormatVersion": "2010-09-09",
+    "Resources": {
+        "FileSystemResource": {"Type": "AWS::EFS::FileSystem", "Properties": {}},
+    },
+}
+
+
+template_complete = {
+    "AWSTemplateFormatVersion": "2010-09-09",
+    "Resources": {
+        "MountTargetVPC": {
+            "Type": "AWS::EC2::VPC",
+            "Properties": {"CidrBlock": "172.31.0.0/16"},
+        },
+        "MountTargetSubnetOne": {
+            "Type": "AWS::EC2::Subnet",
+            "Properties": {
+                "CidrBlock": "172.31.1.0/24",
+                "VpcId": {"Ref": "MountTargetVPC"},
+                "AvailabilityZone": "us-east-1a",
+            },
+        },
+        "MountTargetSubnetTwo": {
+            "Type": "AWS::EC2::Subnet",
+            "Properties": {
+                "CidrBlock": "172.31.2.0/24",
+                "VpcId": {"Ref": "MountTargetVPC"},
+                "AvailabilityZone": "us-east-1b",
+            },
+        },
+        "MountTargetSubnetThree": {
+            "Type": "AWS::EC2::Subnet",
+            "Properties": {
+                "CidrBlock": "172.31.3.0/24",
+                "VpcId": {"Ref": "MountTargetVPC"},
+                "AvailabilityZone": "us-east-1c",
+            },
+        },
+        "FileSystemResource": {
+            "Type": "AWS::EFS::FileSystem",
+            "Properties": {
+                "PerformanceMode": "maxIO",
+                "LifecyclePolicies": [
+                    {"TransitionToIA": "AFTER_30_DAYS"},
+                    {"TransitionToPrimaryStorageClass": "AFTER_1_ACCESS"},
+                ],
+                "Encrypted": True,
+                "FileSystemTags": [{"Key": "Name", "Value": "TestFileSystem"}],
+                "FileSystemPolicy": {
+                    "Version": "2012-10-17",
+                    "Statement": [
+                        {
+                            "Effect": "Allow",
+                            "Action": ["elasticfilesystem:ClientMount"],
+                            "Principal": {
+                                "AWS": "arn:aws:iam::111122223333:role/EfsReadOnly"
+                            },
+                        }
+                    ],
+                },
+                "BackupPolicy": {"Status": "ENABLED"},
+                "KmsKeyId": {"Fn::GetAtt": ["key", "Arn"]},
+            },
+        },
+        "key": {
+            "Type": "AWS::KMS::Key",
+            "Properties": {
+                "KeyPolicy": {
+                    "Version": "2012-10-17",
+                    "Id": "key-default-1",
+                    "Statement": [
+                        {
+                            "Sid": "Allow administration of the key",
+                            "Effect": "Allow",
+                            "Principal": {
+                                "AWS": {
+                                    "Fn::Join": [
+                                        "",
+                                        [
+                                            "arn:aws:iam::",
+                                            {"Ref": "AWS::AccountId"},
+                                            ":root",
+                                        ],
+                                    ]
+                                }
+                            },
+                            "Action": ["kms:*"],
+                            "Resource": "*",
+                        }
+                    ],
+                }
+            },
+        },
+        "MountTargetResource1": {
+            "Type": "AWS::EFS::MountTarget",
+            "Properties": {
+                "FileSystemId": {"Ref": "FileSystemResource"},
+                "SubnetId": {"Ref": "MountTargetSubnetOne"},
+                "SecurityGroups": [
+                    {"Fn::GetAtt": ["MountTargetVPC", "DefaultSecurityGroup"]}
+                ],
+            },
+        },
+        "MountTargetResource2": {
+            "Type": "AWS::EFS::MountTarget",
+            "Properties": {
+                "FileSystemId": {"Ref": "FileSystemResource"},
+                "SubnetId": {"Ref": "MountTargetSubnetTwo"},
+                "SecurityGroups": [
+                    {"Fn::GetAtt": ["MountTargetVPC", "DefaultSecurityGroup"]}
+                ],
+            },
+        },
+        "MountTargetResource3": {
+            "Type": "AWS::EFS::MountTarget",
+            "Properties": {
+                "FileSystemId": {"Ref": "FileSystemResource"},
+                "SubnetId": {"Ref": "MountTargetSubnetThree"},
+                "SecurityGroups": [
+                    {"Fn::GetAtt": ["MountTargetVPC", "DefaultSecurityGroup"]}
+                ],
+            },
+        },
+        "AccessPointResource": {
+            "Type": "AWS::EFS::AccessPoint",
+            "Properties": {
+                "FileSystemId": {"Ref": "FileSystemResource"},
+                "PosixUser": {
+                    "Uid": "13234",
+                    "Gid": "1322",
+                    "SecondaryGids": ["1344", "1452"],
+                },
+                "RootDirectory": {
+                    "CreationInfo": {
+                        "OwnerGid": "708798",
+                        "OwnerUid": "7987987",
+                        "Permissions": "0755",
+                    },
+                    "Path": "/testcfn/abc",
+                },
+            },
+        },
+    },
+}
+
+
+@mock_aws
+def test_simple_template():
+    region = "us-east-1"
+    cf = boto3.client("cloudformation", region_name=region)
+    cf.create_stack(StackName="teststack", TemplateBody=json.dumps(template_fs_simple))
+
+    efs = boto3.client("efs", region)
+    fs = efs.describe_file_systems()["FileSystems"][0]
+    assert fs["PerformanceMode"] == "generalPurpose"
+    assert fs["Encrypted"] is False
+    assert fs["ThroughputMode"] == "bursting"
+
+
+@mock_aws
+def test_full_template():
+    region = "us-east-1"
+    cf = boto3.client("cloudformation", region_name=region)
+    cf.create_stack(StackName="teststack", TemplateBody=json.dumps(template_complete))
+
+    efs = boto3.client("efs", region)
+    fs = efs.describe_file_systems()["FileSystems"][0]
+    fs_id = fs["FileSystemId"]
+    assert fs["Name"] == "TestFileSystem"
+    assert fs["KmsKeyId"]
+
+    lc = efs.describe_lifecycle_configuration(FileSystemId=fs_id)["LifecyclePolicies"]
+    assert {"TransitionToIA": "AFTER_30_DAYS"} in lc
+    assert {"TransitionToPrimaryStorageClass": "AFTER_1_ACCESS"} in lc
+
+    aps = efs.describe_access_points()["AccessPoints"][0]
+    assert aps["FileSystemId"] == fs_id
+
+    cf.delete_stack(StackName="teststack")
+
+    assert efs.describe_file_systems()["FileSystems"] == []
+    assert efs.describe_access_points()["AccessPoints"] == []
diff --git a/tests/test_emr/test_emr_cloudformation.py b/tests/test_emr/test_emr_cloudformation.py
index 3bebfd22a..84bef4b25 100644
--- a/tests/test_emr/test_emr_cloudformation.py
+++ b/tests/test_emr/test_emr_cloudformation.py
@@ -1,6 +1,8 @@
 import json
 
 import boto3
+import pytest
+from botocore.exceptions import ClientError
 
 from moto import mock_aws
 from tests import EXAMPLE_AMI_ID
@@ -496,6 +498,7 @@ template_with_kerberos_attrs = {
         "securityConfiguration": {
             "Type": "AWS::EMR::SecurityConfiguration",
             "Properties": {
+                "Name": "mysecconfig",
                 "SecurityConfiguration": {
                     "AuthenticationConfiguration": {
                         "KerberosConfiguration": {
@@ -511,7 +514,7 @@ template_with_kerberos_attrs = {
                             },
                         }
                     }
-                }
+                },
             },
         },
         "emrRole": {
@@ -618,3 +621,14 @@ def test_create_cluster_with_kerberos_attrs():
         "KdcAdminPassword": "adminp2ss",
         "CrossRealmTrustPrincipalPassword": "p2ss",
     }
+
+    # Verify everything can be deleted
+    cf.delete_stack(StackName="teststack")
+
+    cl = emr.describe_cluster(ClusterId=cluster_id)["Cluster"]
+    assert cl["Status"]["State"] == "TERMINATED"
+
+    with pytest.raises(ClientError) as exc:
+        emr.describe_security_configuration(Name="mysecconfig")
+    err = exc.value.response["Error"]
+    assert err["Code"] == "InvalidRequestException"
diff --git a/tests/test_iam/__init__.py b/tests/test_iam/__init__.py
index 08a1c1568..466103fb0 100644
--- a/tests/test_iam/__init__.py
+++ b/tests/test_iam/__init__.py
@@ -1,1 +1,28 @@
-# This file is intentionally left blank.
+import os
+from functools import wraps
+
+from moto import mock_aws
+
+
+def iam_aws_verified(func):
+    """
+    Function that is verified to work against AWS.
+    Can be run against AWS at any time by setting:
+      MOTO_TEST_ALLOW_AWS_REQUEST=true
+
+    If this environment variable is not set, the function runs in a `mock_aws` context.
+    """
+
+    @wraps(func)
+    def pagination_wrapper():
+        allow_aws_request = (
+            os.environ.get("MOTO_TEST_ALLOW_AWS_REQUEST", "false").lower() == "true"
+        )
+
+        if allow_aws_request:
+            return func()
+        else:
+            with mock_aws():
+                return func()
+
+    return pagination_wrapper
diff --git a/tests/test_iam/test_iam_cloudformation.py b/tests/test_iam/test_iam_cloudformation.py
index aae856d18..2ac97ff82 100644
--- a/tests/test_iam/test_iam_cloudformation.py
+++ b/tests/test_iam/test_iam_cloudformation.py
@@ -1,4 +1,5 @@
 import json
+from uuid import uuid4
 
 import boto3
 import pytest
@@ -8,6 +9,8 @@ from botocore.exceptions import ClientError
 from moto import mock_aws
 from moto.core import DEFAULT_ACCOUNT_ID as ACCOUNT_ID
 from tests import EXAMPLE_AMI_ID
+from tests.test_iam import iam_aws_verified
+from tests.test_iam.test_iam import MOCK_STS_EC2_POLICY_DOCUMENT
 
 TEMPLATE_MINIMAL_ROLE = """
 AWSTemplateFormatVersion: 2010-09-09
@@ -1609,3 +1612,46 @@ def test_iam_roles():
         if resource["ResourceType"] == "AWS::IAM::Role"
     ]
     assert {r["PhysicalResourceId"] for r in role_resources} == set(role_names)
+
+
+template_with_instance_profile = """
+Parameters:
+    InputRole:
+        Type: String
+        Default: "test-emr-role"
+
+Resources:
+    emrEc2InstanceProfile:
+        Type: 'AWS::IAM::InstanceProfile'
+        Properties:
+            Path: /
+            Roles:
+              - !Ref InputRole
+"""
+
+
+@iam_aws_verified
+def test_delete_instance_profile_with_existing_role():
+    region = "us-east-1"
+    iam = boto3.client("iam", region_name=region)
+    iam_role_name = f"moto_{str(uuid4())[0:6]}"
+    iam.create_role(
+        RoleName=iam_role_name, AssumeRolePolicyDocument=MOCK_STS_EC2_POLICY_DOCUMENT
+    )
+
+    try:
+        cf = boto3.client("cloudformation", region_name=region)
+        cf.create_stack(
+            StackName="teststack",
+            TemplateBody=template_with_instance_profile,
+            Parameters=[{"ParameterKey": "InputRole", "ParameterValue": iam_role_name}],
+            Capabilities=["CAPABILITY_NAMED_IAM"],
+        )
+
+        # Just verify that we can delete the InstanceProfile
+        cf.delete_stack(StackName="teststack")
+
+        # The role still exists at this point
+        iam.get_role(RoleName=iam_role_name)
+    finally:
+        iam.delete_role(RoleName=iam_role_name)
diff --git a/tests/test_s3/__init__.py b/tests/test_s3/__init__.py
index 2a177eb21..e89b1490c 100644
--- a/tests/test_s3/__init__.py
+++ b/tests/test_s3/__init__.py
@@ -51,22 +51,24 @@ def s3_aws_verified(func):
         finally:
             ### CLEANUP ###
 
-            versions = client.list_object_versions(Bucket=bucket_name).get(
-                "Versions", []
-            )
-            for key in versions:
-                client.delete_object(
-                    Bucket=bucket_name, Key=key["Key"], VersionId=key.get("VersionId")
-                )
-            delete_markers = client.list_object_versions(Bucket=bucket_name).get(
-                "DeleteMarkers", []
-            )
-            for key in delete_markers:
-                client.delete_object(
-                    Bucket=bucket_name, Key=key["Key"], VersionId=key.get("VersionId")
-                )
+            empty_bucket(client, bucket_name)
             client.delete_bucket(Bucket=bucket_name)
 
         return resp
 
     return pagination_wrapper
+
+
+def empty_bucket(client, bucket_name):
+    versions = client.list_object_versions(Bucket=bucket_name).get("Versions", [])
+    for key in versions:
+        client.delete_object(
+            Bucket=bucket_name, Key=key["Key"], VersionId=key.get("VersionId")
+        )
+    delete_markers = client.list_object_versions(Bucket=bucket_name).get(
+        "DeleteMarkers", []
+    )
+    for key in delete_markers:
+        client.delete_object(
+            Bucket=bucket_name, Key=key["Key"], VersionId=key.get("VersionId")
+        )
diff --git a/tests/test_s3/test_s3_eventbridge_integration.py b/tests/test_s3/test_s3_eventbridge_integration.py
index d84235020..dda1f2c04 100644
--- a/tests/test_s3/test_s3_eventbridge_integration.py
+++ b/tests/test_s3/test_s3_eventbridge_integration.py
@@ -142,3 +142,26 @@ def test_copy_object_notification():
     assert event_message["detail"]["bucket"]["name"] == bucket_name
     assert event_message["detail"]["object"]["key"] == object_key
     assert event_message["detail"]["reason"] == "ObjectCreated"
+
+
+@mock_aws
+def test_delete_object_notification():
+    resource_names = _seteup_bucket_notification_eventbridge()
+    bucket_name = resource_names["bucket_name"]
+    s3_client = boto3.client("s3", region_name=REGION_NAME)
+
+    # Put Object
+    s3_client.put_object(Bucket=bucket_name, Key="keyname", Body="bodyofnewobject")
+
+    # Delete Object
+    s3_client.delete_object(Bucket=bucket_name, Key="keyname")
+
+    events = _get_send_events()
+    assert len(events) == 2
+    event_message = json.loads(events[1]["message"])
+    assert event_message["detail-type"] == "Object Deleted"
+    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"] == "ObjectRemoved"
diff --git a/tests/test_s3/test_s3_logging.py b/tests/test_s3/test_s3_logging.py
index 152b6fdec..a891b4a14 100644
--- a/tests/test_s3/test_s3_logging.py
+++ b/tests/test_s3/test_s3_logging.py
@@ -1,6 +1,7 @@
 import json
 from unittest import SkipTest
 from unittest.mock import patch
+from uuid import uuid4
 
 import boto3
 import pytest
@@ -11,6 +12,7 @@ from moto.core import DEFAULT_ACCOUNT_ID
 from moto.s3 import s3_backends
 from moto.s3.models import FakeBucket
 from moto.s3.responses import DEFAULT_REGION_NAME
+from tests.test_s3 import empty_bucket, s3_aws_verified
 
 
 @mock_aws
@@ -587,3 +589,76 @@ def test_bucket_policy_resource():
     assert FakeBucket._log_permissions_enabled_policy(
         target_bucket=log_bucket_obj, target_prefix="prefix"
     )
+
+
+@s3_aws_verified
+@pytest.mark.aws_verified
+def test_put_logging_w_bucket_policy_no_prefix(bucket_name=None):
+    s3_client = boto3.client("s3", region_name=DEFAULT_REGION_NAME)
+
+    log_bucket_name = f"{uuid4()}"
+    s3_client.create_bucket(Bucket=log_bucket_name)
+    bucket_policy = {
+        "Version": "2012-10-17",
+        "Statement": [
+            {
+                "Sid": "S3ServerAccessLogsPolicy",
+                "Effect": "Allow",
+                "Principal": {"Service": "logging.s3.amazonaws.com"},
+                "Action": ["s3:PutObject"],
+                "Resource": f"arn:aws:s3:::{log_bucket_name}/*",
+            }
+        ],
+    }
+    s3_client.put_bucket_policy(
+        Bucket=log_bucket_name, Policy=json.dumps(bucket_policy)
+    )
+    s3_client.put_bucket_logging(
+        Bucket=bucket_name,
+        BucketLoggingStatus={
+            "LoggingEnabled": {"TargetBucket": log_bucket_name, "TargetPrefix": ""}
+        },
+    )
+    result = s3_client.get_bucket_logging(Bucket=bucket_name)
+    assert result["LoggingEnabled"]["TargetBucket"] == log_bucket_name
+    assert result["LoggingEnabled"]["TargetPrefix"] == ""
+
+    empty_bucket(s3_client, log_bucket_name)
+    s3_client.delete_bucket(Bucket=log_bucket_name)
+
+
+@s3_aws_verified
+@pytest.mark.aws_verified
+def test_put_logging_w_bucket_policy_w_prefix(bucket_name=None):
+    s3_client = boto3.client("s3", region_name=DEFAULT_REGION_NAME)
+
+    log_bucket_name = f"{uuid4()}"
+    s3_client.create_bucket(Bucket=log_bucket_name)
+    prefix = "some-prefix"
+    bucket_policy = {
+        "Version": "2012-10-17",
+        "Statement": [
+            {
+                "Sid": "S3ServerAccessLogsPolicy",
+                "Effect": "Allow",
+                "Principal": {"Service": "logging.s3.amazonaws.com"},
+                "Action": ["s3:PutObject"],
+                "Resource": f"arn:aws:s3:::{log_bucket_name}/{prefix}*",
+            }
+        ],
+    }
+    s3_client.put_bucket_policy(
+        Bucket=log_bucket_name, Policy=json.dumps(bucket_policy)
+    )
+    s3_client.put_bucket_logging(
+        Bucket=bucket_name,
+        BucketLoggingStatus={
+            "LoggingEnabled": {"TargetBucket": log_bucket_name, "TargetPrefix": prefix}
+        },
+    )
+    result = s3_client.get_bucket_logging(Bucket=bucket_name)
+    assert result["LoggingEnabled"]["TargetBucket"] == log_bucket_name
+    assert result["LoggingEnabled"]["TargetPrefix"] == prefix
+
+    empty_bucket(s3_client, log_bucket_name)
+    s3_client.delete_bucket(Bucket=log_bucket_name)