Repository: getmoto/moto
Pull request #7756 (2024-06-09 09:04:29)

CognitoIDP: feat: support pagination for admin_list_groups_by_user
# Description

boto3 supports pagination for `admin_list_groups_by_user`. This commit adds the same flags (Limit and NextToken) to the moto cognitoidp module.
See: https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/cognito-idp/client/admin_list_groups_for_user.html

# Testing

Unit tests for pagination are added as part of the pull request. Most of this is just copied over code from list_groups with modifications for adjusting to the `admin_list_groups_by_user` API.


Patch:
diff --git a/moto/cognitoidp/models.py b/moto/cognitoidp/models.py
index 66a350a81..1e4537a06 100644
--- a/moto/cognitoidp/models.py
+++ b/moto/cognitoidp/models.py
@@ -1212,6 +1212,7 @@ class CognitoIdpBackend(BaseBackend):
         group = self.get_group(user_pool_id, group_name)
         return list(filter(lambda user: user in group.users, user_pool.users.values()))
 
+    @paginate(pagination_model=PAGINATION_MODEL)
     def admin_list_groups_for_user(
         self, user_pool_id: str, username: str
     ) -> List[CognitoIdpGroup]:
diff --git a/moto/cognitoidp/responses.py b/moto/cognitoidp/responses.py
index faa12a03a..19b8ecb71 100644
--- a/moto/cognitoidp/responses.py
+++ b/moto/cognitoidp/responses.py
@@ -307,8 +307,15 @@ class CognitoIdpResponse(BaseResponse):
     def admin_list_groups_for_user(self) -> str:
         username = self._get_param("Username")
         user_pool_id = self._get_param("UserPoolId")
-        groups = self.backend.admin_list_groups_for_user(user_pool_id, username)
-        return json.dumps({"Groups": [group.to_json() for group in groups]})
+        limit = self._get_param("Limit")
+        token = self._get_param("NextToken")
+        groups, token = self.backend.admin_list_groups_for_user(
+            user_pool_id, username, limit=limit, next_token=token
+        )
+        response = {"Groups": [group.to_json() for group in groups]}
+        if token:
+            response["NextToken"] = token
+        return json.dumps(response)
 
     def admin_remove_user_from_group(self) -> str:
         user_pool_id = self._get_param("UserPoolId")
diff --git a/moto/cognitoidp/utils.py b/moto/cognitoidp/utils.py
index 5a758ffcf..80777aef5 100644
--- a/moto/cognitoidp/utils.py
+++ b/moto/cognitoidp/utils.py
@@ -44,6 +44,12 @@ PAGINATION_MODEL = {
         "limit_default": 60,
         "unique_attribute": "group_name",
     },
+    "admin_list_groups_for_user": {
+        "input_token": "next_token",
+        "limit_key": "limit",
+        "limit_default": 60,
+        "unique_attribute": "group_name",
+    },
     "list_users_in_group": {
         "input_token": "next_token",
         "limit_key": "limit",
diff --git a/moto/ec2/models/amis.py b/moto/ec2/models/amis.py
index 4e558b6bd..c58be632a 100644
--- a/moto/ec2/models/amis.py
+++ b/moto/ec2/models/amis.py
@@ -276,10 +276,19 @@ class AmiBackend:
             if exec_users:
                 tmp_images = []
                 for ami in images:
-                    for user_id in exec_users:
-                        for lp in ami.launch_permissions:
-                            if lp.get("UserId") == user_id:
-                                tmp_images.append(ami)
+                    if ami.is_public:
+                        tmp_images.append(ami)
+                    else:
+                        # support filtering by ExecutableUsers=['self']
+                        if "self" in exec_users:
+                            exec_users = list(set(exec_users))
+                            exec_users.remove("self")
+                            exec_users.append(self.account_id)  # type: ignore[attr-defined]
+
+                        for user_id in exec_users:
+                            for lp in ami.launch_permissions:
+                                if lp.get("UserId") == user_id:
+                                    tmp_images.append(ami)
                 images = tmp_images
 
             # Limit by owner ids
diff --git a/moto/ec2/resources/instance_type_offerings/availability-zone-id/ca-central-1.json b/moto/ec2/resources/instance_type_offerings/availability-zone-id/ca-central-1.json
index 93e18dcac..479842480 100644
--- a/moto/ec2/resources/instance_type_offerings/availability-zone-id/ca-central-1.json
+++ b/moto/ec2/resources/instance_type_offerings/availability-zone-id/ca-central-1.json
@@ -339,6 +339,46 @@
   "InstanceType": "c6i.xlarge",
   "Location": "cac1-az1"
  },
+ {
+  "InstanceType": "c6id.12xlarge",
+  "Location": "cac1-az1"
+ },
+ {
+  "InstanceType": "c6id.16xlarge",
+  "Location": "cac1-az1"
+ },
+ {
+  "InstanceType": "c6id.24xlarge",
+  "Location": "cac1-az1"
+ },
+ {
+  "InstanceType": "c6id.2xlarge",
+  "Location": "cac1-az1"
+ },
+ {
+  "InstanceType": "c6id.32xlarge",
+  "Location": "cac1-az1"
+ },
+ {
+  "InstanceType": "c6id.4xlarge",
+  "Location": "cac1-az1"
+ },
+ {
+  "InstanceType": "c6id.8xlarge",
+  "Location": "cac1-az1"
+ },
+ {
+  "InstanceType": "c6id.large",
+  "Location": "cac1-az1"
+ },
+ {
+  "InstanceType": "c6id.metal",
+  "Location": "cac1-az1"
+ },
+ {
+  "InstanceType": "c6id.xlarge",
+  "Location": "cac1-az1"
+ },
  {
   "InstanceType": "c6in.12xlarge",
   "Location": "cac1-az1"
@@ -2207,6 +2247,46 @@
   "InstanceType": "c6i.xlarge",
   "Location": "cac1-az2"
  },
+ {
+  "InstanceType": "c6id.12xlarge",
+  "Location": "cac1-az2"
+ },
+ {
+  "InstanceType": "c6id.16xlarge",
+  "Location": "cac1-az2"
+ },
+ {
+  "InstanceType": "c6id.24xlarge",
+  "Location": "cac1-az2"
+ },
+ {
+  "InstanceType": "c6id.2xlarge",
+  "Location": "cac1-az2"
+ },
+ {
+  "InstanceType": "c6id.32xlarge",
+  "Location": "cac1-az2"
+ },
+ {
+  "InstanceType": "c6id.4xlarge",
+  "Location": "cac1-az2"
+ },
+ {
+  "InstanceType": "c6id.8xlarge",
+  "Location": "cac1-az2"
+ },
+ {
+  "InstanceType": "c6id.large",
+  "Location": "cac1-az2"
+ },
+ {
+  "InstanceType": "c6id.metal",
+  "Location": "cac1-az2"
+ },
+ {
+  "InstanceType": "c6id.xlarge",
+  "Location": "cac1-az2"
+ },
  {
   "InstanceType": "c6in.12xlarge",
   "Location": "cac1-az2"
diff --git a/moto/ec2/resources/instance_type_offerings/availability-zone-id/sa-east-1.json b/moto/ec2/resources/instance_type_offerings/availability-zone-id/sa-east-1.json
index 5c36eb08a..36207b666 100644
--- a/moto/ec2/resources/instance_type_offerings/availability-zone-id/sa-east-1.json
+++ b/moto/ec2/resources/instance_type_offerings/availability-zone-id/sa-east-1.json
@@ -399,6 +399,46 @@
   "InstanceType": "c6i.xlarge",
   "Location": "sae1-az1"
  },
+ {
+  "InstanceType": "c6id.12xlarge",
+  "Location": "sae1-az1"
+ },
+ {
+  "InstanceType": "c6id.16xlarge",
+  "Location": "sae1-az1"
+ },
+ {
+  "InstanceType": "c6id.24xlarge",
+  "Location": "sae1-az1"
+ },
+ {
+  "InstanceType": "c6id.2xlarge",
+  "Location": "sae1-az1"
+ },
+ {
+  "InstanceType": "c6id.32xlarge",
+  "Location": "sae1-az1"
+ },
+ {
+  "InstanceType": "c6id.4xlarge",
+  "Location": "sae1-az1"
+ },
+ {
+  "InstanceType": "c6id.8xlarge",
+  "Location": "sae1-az1"
+ },
+ {
+  "InstanceType": "c6id.large",
+  "Location": "sae1-az1"
+ },
+ {
+  "InstanceType": "c6id.metal",
+  "Location": "sae1-az1"
+ },
+ {
+  "InstanceType": "c6id.xlarge",
+  "Location": "sae1-az1"
+ },
  {
   "InstanceType": "c6in.12xlarge",
   "Location": "sae1-az1"
@@ -3707,6 +3747,46 @@
   "InstanceType": "c6i.xlarge",
   "Location": "sae1-az3"
  },
+ {
+  "InstanceType": "c6id.12xlarge",
+  "Location": "sae1-az3"
+ },
+ {
+  "InstanceType": "c6id.16xlarge",
+  "Location": "sae1-az3"
+ },
+ {
+  "InstanceType": "c6id.24xlarge",
+  "Location": "sae1-az3"
+ },
+ {
+  "InstanceType": "c6id.2xlarge",
+  "Location": "sae1-az3"
+ },
+ {
+  "InstanceType": "c6id.32xlarge",
+  "Location": "sae1-az3"
+ },
+ {
+  "InstanceType": "c6id.4xlarge",
+  "Location": "sae1-az3"
+ },
+ {
+  "InstanceType": "c6id.8xlarge",
+  "Location": "sae1-az3"
+ },
+ {
+  "InstanceType": "c6id.large",
+  "Location": "sae1-az3"
+ },
+ {
+  "InstanceType": "c6id.metal",
+  "Location": "sae1-az3"
+ },
+ {
+  "InstanceType": "c6id.xlarge",
+  "Location": "sae1-az3"
+ },
  {
   "InstanceType": "c6in.12xlarge",
   "Location": "sae1-az3"
diff --git a/moto/ec2/resources/instance_type_offerings/availability-zone/ca-central-1.json b/moto/ec2/resources/instance_type_offerings/availability-zone/ca-central-1.json
index a509696a1..ad0374f5f 100644
--- a/moto/ec2/resources/instance_type_offerings/availability-zone/ca-central-1.json
+++ b/moto/ec2/resources/instance_type_offerings/availability-zone/ca-central-1.json
@@ -339,6 +339,46 @@
   "InstanceType": "c6i.xlarge",
   "Location": "ca-central-1a"
  },
+ {
+  "InstanceType": "c6id.12xlarge",
+  "Location": "ca-central-1a"
+ },
+ {
+  "InstanceType": "c6id.16xlarge",
+  "Location": "ca-central-1a"
+ },
+ {
+  "InstanceType": "c6id.24xlarge",
+  "Location": "ca-central-1a"
+ },
+ {
+  "InstanceType": "c6id.2xlarge",
+  "Location": "ca-central-1a"
+ },
+ {
+  "InstanceType": "c6id.32xlarge",
+  "Location": "ca-central-1a"
+ },
+ {
+  "InstanceType": "c6id.4xlarge",
+  "Location": "ca-central-1a"
+ },
+ {
+  "InstanceType": "c6id.8xlarge",
+  "Location": "ca-central-1a"
+ },
+ {
+  "InstanceType": "c6id.large",
+  "Location": "ca-central-1a"
+ },
+ {
+  "InstanceType": "c6id.metal",
+  "Location": "ca-central-1a"
+ },
+ {
+  "InstanceType": "c6id.xlarge",
+  "Location": "ca-central-1a"
+ },
  {
   "InstanceType": "c6in.12xlarge",
   "Location": "ca-central-1a"
@@ -2207,6 +2247,46 @@
   "InstanceType": "c6i.xlarge",
   "Location": "ca-central-1b"
  },
+ {
+  "InstanceType": "c6id.12xlarge",
+  "Location": "ca-central-1b"
+ },
+ {
+  "InstanceType": "c6id.16xlarge",
+  "Location": "ca-central-1b"
+ },
+ {
+  "InstanceType": "c6id.24xlarge",
+  "Location": "ca-central-1b"
+ },
+ {
+  "InstanceType": "c6id.2xlarge",
+  "Location": "ca-central-1b"
+ },
+ {
+  "InstanceType": "c6id.32xlarge",
+  "Location": "ca-central-1b"
+ },
+ {
+  "InstanceType": "c6id.4xlarge",
+  "Location": "ca-central-1b"
+ },
+ {
+  "InstanceType": "c6id.8xlarge",
+  "Location": "ca-central-1b"
+ },
+ {
+  "InstanceType": "c6id.large",
+  "Location": "ca-central-1b"
+ },
+ {
+  "InstanceType": "c6id.metal",
+  "Location": "ca-central-1b"
+ },
+ {
+  "InstanceType": "c6id.xlarge",
+  "Location": "ca-central-1b"
+ },
  {
   "InstanceType": "c6in.12xlarge",
   "Location": "ca-central-1b"
diff --git a/moto/ec2/resources/instance_type_offerings/availability-zone/sa-east-1.json b/moto/ec2/resources/instance_type_offerings/availability-zone/sa-east-1.json
index 85540da41..eca8cf5ea 100644
--- a/moto/ec2/resources/instance_type_offerings/availability-zone/sa-east-1.json
+++ b/moto/ec2/resources/instance_type_offerings/availability-zone/sa-east-1.json
@@ -399,6 +399,46 @@
   "InstanceType": "c6i.xlarge",
   "Location": "sa-east-1a"
  },
+ {
+  "InstanceType": "c6id.12xlarge",
+  "Location": "sa-east-1a"
+ },
+ {
+  "InstanceType": "c6id.16xlarge",
+  "Location": "sa-east-1a"
+ },
+ {
+  "InstanceType": "c6id.24xlarge",
+  "Location": "sa-east-1a"
+ },
+ {
+  "InstanceType": "c6id.2xlarge",
+  "Location": "sa-east-1a"
+ },
+ {
+  "InstanceType": "c6id.32xlarge",
+  "Location": "sa-east-1a"
+ },
+ {
+  "InstanceType": "c6id.4xlarge",
+  "Location": "sa-east-1a"
+ },
+ {
+  "InstanceType": "c6id.8xlarge",
+  "Location": "sa-east-1a"
+ },
+ {
+  "InstanceType": "c6id.large",
+  "Location": "sa-east-1a"
+ },
+ {
+  "InstanceType": "c6id.metal",
+  "Location": "sa-east-1a"
+ },
+ {
+  "InstanceType": "c6id.xlarge",
+  "Location": "sa-east-1a"
+ },
  {
   "InstanceType": "c6in.12xlarge",
   "Location": "sa-east-1a"
@@ -3707,6 +3747,46 @@
   "InstanceType": "c6i.xlarge",
   "Location": "sa-east-1c"
  },
+ {
+  "InstanceType": "c6id.12xlarge",
+  "Location": "sa-east-1c"
+ },
+ {
+  "InstanceType": "c6id.16xlarge",
+  "Location": "sa-east-1c"
+ },
+ {
+  "InstanceType": "c6id.24xlarge",
+  "Location": "sa-east-1c"
+ },
+ {
+  "InstanceType": "c6id.2xlarge",
+  "Location": "sa-east-1c"
+ },
+ {
+  "InstanceType": "c6id.32xlarge",
+  "Location": "sa-east-1c"
+ },
+ {
+  "InstanceType": "c6id.4xlarge",
+  "Location": "sa-east-1c"
+ },
+ {
+  "InstanceType": "c6id.8xlarge",
+  "Location": "sa-east-1c"
+ },
+ {
+  "InstanceType": "c6id.large",
+  "Location": "sa-east-1c"
+ },
+ {
+  "InstanceType": "c6id.metal",
+  "Location": "sa-east-1c"
+ },
+ {
+  "InstanceType": "c6id.xlarge",
+  "Location": "sa-east-1c"
+ },
  {
   "InstanceType": "c6in.12xlarge",
   "Location": "sa-east-1c"
diff --git a/moto/ec2/resources/instance_type_offerings/region/ca-central-1.json b/moto/ec2/resources/instance_type_offerings/region/ca-central-1.json
index 4cd7c5f96..dc6eda134 100644
--- a/moto/ec2/resources/instance_type_offerings/region/ca-central-1.json
+++ b/moto/ec2/resources/instance_type_offerings/region/ca-central-1.json
@@ -339,6 +339,46 @@
   "InstanceType": "c6i.xlarge",
   "Location": "ca-central-1"
  },
+ {
+  "InstanceType": "c6id.12xlarge",
+  "Location": "ca-central-1"
+ },
+ {
+  "InstanceType": "c6id.16xlarge",
+  "Location": "ca-central-1"
+ },
+ {
+  "InstanceType": "c6id.24xlarge",
+  "Location": "ca-central-1"
+ },
+ {
+  "InstanceType": "c6id.2xlarge",
+  "Location": "ca-central-1"
+ },
+ {
+  "InstanceType": "c6id.32xlarge",
+  "Location": "ca-central-1"
+ },
+ {
+  "InstanceType": "c6id.4xlarge",
+  "Location": "ca-central-1"
+ },
+ {
+  "InstanceType": "c6id.8xlarge",
+  "Location": "ca-central-1"
+ },
+ {
+  "InstanceType": "c6id.large",
+  "Location": "ca-central-1"
+ },
+ {
+  "InstanceType": "c6id.metal",
+  "Location": "ca-central-1"
+ },
+ {
+  "InstanceType": "c6id.xlarge",
+  "Location": "ca-central-1"
+ },
  {
   "InstanceType": "c6in.12xlarge",
   "Location": "ca-central-1"
diff --git a/moto/ec2/resources/instance_type_offerings/region/sa-east-1.json b/moto/ec2/resources/instance_type_offerings/region/sa-east-1.json
index 7e12447bd..07e93833b 100644
--- a/moto/ec2/resources/instance_type_offerings/region/sa-east-1.json
+++ b/moto/ec2/resources/instance_type_offerings/region/sa-east-1.json
@@ -399,6 +399,46 @@
   "InstanceType": "c6i.xlarge",
   "Location": "sa-east-1"
  },
+ {
+  "InstanceType": "c6id.12xlarge",
+  "Location": "sa-east-1"
+ },
+ {
+  "InstanceType": "c6id.16xlarge",
+  "Location": "sa-east-1"
+ },
+ {
+  "InstanceType": "c6id.24xlarge",
+  "Location": "sa-east-1"
+ },
+ {
+  "InstanceType": "c6id.2xlarge",
+  "Location": "sa-east-1"
+ },
+ {
+  "InstanceType": "c6id.32xlarge",
+  "Location": "sa-east-1"
+ },
+ {
+  "InstanceType": "c6id.4xlarge",
+  "Location": "sa-east-1"
+ },
+ {
+  "InstanceType": "c6id.8xlarge",
+  "Location": "sa-east-1"
+ },
+ {
+  "InstanceType": "c6id.large",
+  "Location": "sa-east-1"
+ },
+ {
+  "InstanceType": "c6id.metal",
+  "Location": "sa-east-1"
+ },
+ {
+  "InstanceType": "c6id.xlarge",
+  "Location": "sa-east-1"
+ },
  {
   "InstanceType": "c6in.12xlarge",
   "Location": "sa-east-1"
diff --git a/moto/rds/models.py b/moto/rds/models.py
index 217b4089c..40acb891e 100644
--- a/moto/rds/models.py
+++ b/moto/rds/models.py
@@ -247,6 +247,31 @@ class Cluster:
             )
         self.backup_retention_period = kwargs.get("backup_retention_period") or 1
 
+        if backtrack := kwargs.get("backtrack_window"):
+            if self.engine == "aurora-mysql":
+                # https://docs.aws.amazon.com/AmazonRDS/latest/APIReference/API_CreateDBCluster.html
+                if 0 <= backtrack <= 259200:
+                    self.backtrack_window: int = backtrack
+                else:
+                    raise InvalidParameterValue(
+                        f"The specified value ({backtrack}) is not a valid Backtrack Window. "
+                        "Allowed values are within the range of 0 to 259200"
+                    )
+            else:
+                raise InvalidParameterValue(
+                    "Backtrack is not enabled for the postgres engine."
+                )
+        else:
+            self.backtrack_window = 0
+
+        self.iam_auth: bool = False
+        if auth := kwargs.get("enable_iam_database_authentication", False):
+            if not self.engine.startswith("aurora-"):
+                raise InvalidParameterCombination(
+                    "IAM Authentication is currently not supported by Multi-AZ DB clusters."
+                )
+            self.iam_auth = auth
+
     @property
     def is_multi_az(self) -> bool:
         return (
@@ -330,7 +355,7 @@ class Cluster:
               {% endfor %}
               </AvailabilityZones>
               <BackupRetentionPeriod>{{ cluster.backup_retention_period }}</BackupRetentionPeriod>
-              <BacktrackWindow>0</BacktrackWindow>
+              <BacktrackWindow>{{ cluster.backtrack_window }}</BacktrackWindow>
               <DBInstanceStatus>{{ cluster.status }}</DBInstanceStatus>
               {% if cluster.db_name %}<DatabaseName>{{ cluster.db_name }}</DatabaseName>{% endif %}
               {% if cluster.kms_key_id %}<KmsKeyId>{{ cluster.kms_key_id }}</KmsKeyId>{% endif %}
@@ -384,7 +409,7 @@ class Cluster:
               <DbClusterResourceId>{{ cluster.resource_id }}</DbClusterResourceId>
               <DBClusterArn>{{ cluster.db_cluster_arn }}</DBClusterArn>
               <AssociatedRoles></AssociatedRoles>
-              <IAMDatabaseAuthenticationEnabled>false</IAMDatabaseAuthenticationEnabled>
+              <IAMDatabaseAuthenticationEnabled>{{ cluster.iam_auth | string | lower }}</IAMDatabaseAuthenticationEnabled>
               <EngineMode>{{ cluster.engine_mode }}</EngineMode>
               <DeletionProtection>{{ 'true' if cluster.deletion_protection else 'false' }}</DeletionProtection>
               <HttpEndpointEnabled>{{ 'true' if cluster.enable_http_endpoint else 'false' }}</HttpEndpointEnabled>
@@ -592,7 +617,7 @@ class Database(CloudFormationModel):
         self.replicas: List[str] = []
         self.account_id: str = kwargs["account_id"]
         self.region_name: str = kwargs["region"]
-        self.engine = kwargs.get("engine")
+        self.engine: str = kwargs["engine"]
         if self.engine not in DbInstanceEngine.valid_db_instance_engine():
             raise InvalidParameterValue(
                 f"Value {self.engine} for parameter Engine is invalid. Reason: engine {self.engine} not supported"
diff --git a/moto/rds/responses.py b/moto/rds/responses.py
index 4ea10cff7..c573ad656 100644
--- a/moto/rds/responses.py
+++ b/moto/rds/responses.py
@@ -97,6 +97,7 @@ class RDSResponse(BaseResponse):
             "allocated_storage": self._get_int_param("AllocatedStorage"),
             "availability_zone": self._get_param("AvailabilityZone"),
             "backup_retention_period": self._get_param("BackupRetentionPeriod"),
+            "backtrack_window": self._get_param("BacktrackWindow"),
             "copy_tags_to_snapshot": self._get_param("CopyTagsToSnapshot"),
             "db_instance_class": self._get_param("DBInstanceClass"),
             "db_cluster_identifier": self._get_param("DBClusterIdentifier"),
@@ -184,7 +185,11 @@ class RDSResponse(BaseResponse):
             "availability_zones": self._get_multi_param(
                 "AvailabilityZones.AvailabilityZone"
             ),
+            "backtrack_window": self._get_int_param("BacktrackWindow"),
             "enable_cloudwatch_logs_exports": params.get("EnableCloudwatchLogsExports"),
+            "enable_iam_database_authentication": self._get_bool_param(
+                "EnableIAMDatabaseAuthentication"
+            ),
             "db_name": self._get_param("DatabaseName"),
             "db_cluster_identifier": self._get_param("DBClusterIdentifier"),
             "db_subnet_group_name": self._get_param("DBSubnetGroupName"),


Test patch:
diff --git a/tests/test_cognitoidp/test_cognitoidp.py b/tests/test_cognitoidp/test_cognitoidp.py
index 567727b8b..1158b304d 100644
--- a/tests/test_cognitoidp/test_cognitoidp.py
+++ b/tests/test_cognitoidp/test_cognitoidp.py
@@ -1949,6 +1949,42 @@ def test_admin_list_groups_for_user_ignores_deleted_group():
     assert result["Groups"][0]["GroupName"] == group_name2
 
 
+@mock_aws
+def test_admin_list_groups_for_users_returns_pagination_tokens():
+    conn = boto3.client("cognito-idp", "us-west-2")
+    user_pool_id = conn.create_user_pool(PoolName=str(uuid.uuid4()))["UserPool"]["Id"]
+
+    # Given a user
+    username = str(uuid.uuid4())
+    conn.admin_create_user(UserPoolId=user_pool_id, Username=username)
+    # And given 10 groups
+    group_count = 10
+    for idx in range(group_count):
+        group_name = f"group_{idx}"
+        conn.create_group(UserPoolId=user_pool_id, GroupName=group_name)
+        conn.admin_add_user_to_group(
+            UserPoolId=user_pool_id, Username=username, GroupName=group_name
+        )
+
+    # When listing groups for the user pagination tokens are returned if limit is reached.
+    max_results = 5
+    result = conn.admin_list_groups_for_user(
+        UserPoolId=user_pool_id, Username=username, Limit=max_results
+    )
+    assert len(result["Groups"]) == max_results
+    assert "NextToken" in result
+
+    next_token = result["NextToken"]
+    result_2 = conn.admin_list_groups_for_user(
+        UserPoolId=user_pool_id,
+        Username=username,
+        Limit=max_results,
+        NextToken=next_token,
+    )
+    assert len(result_2["Groups"]) == max_results
+    assert "NextToken" not in result_2
+
+
 @mock_aws
 def test_admin_remove_user_from_group():
     conn = boto3.client("cognito-idp", "us-west-2")
diff --git a/tests/test_ec2/test_amis.py b/tests/test_ec2/test_amis.py
index 885ab4c2e..23a01c0de 100644
--- a/tests/test_ec2/test_amis.py
+++ b/tests/test_ec2/test_amis.py
@@ -770,8 +770,7 @@ def test_ami_describe_executable_users():
     assert len(attributes["LaunchPermissions"]) == 1
     assert attributes["LaunchPermissions"][0]["UserId"] == USER1
     images = conn.describe_images(ExecutableUsers=[USER1])["Images"]
-    assert len(images) == 1
-    assert images[0]["ImageId"] == image_id
+    assert image_id in [image["ImageId"] for image in images]
 
 
 @mock_aws
@@ -795,7 +794,6 @@ def test_ami_describe_executable_users_negative():
         "UserIds": [USER1],
     }
 
-    # Add users and get no images
     # Add users and get no images
     conn.modify_image_attribute(**ADD_USER_ARGS)
 
@@ -805,7 +803,7 @@ def test_ami_describe_executable_users_negative():
     assert len(attributes["LaunchPermissions"]) == 1
     assert attributes["LaunchPermissions"][0]["UserId"] == USER1
     images = conn.describe_images(ExecutableUsers=[USER2])["Images"]
-    assert len(images) == 0
+    assert image_id not in [image["ImageId"] for image in images]
 
 
 @mock_aws
@@ -841,8 +839,75 @@ def test_ami_describe_executable_users_and_filter():
     images = conn.describe_images(
         ExecutableUsers=[USER1], Filters=[{"Name": "state", "Values": ["available"]}]
     )["Images"]
-    assert len(images) == 1
-    assert images[0]["ImageId"] == image_id
+    assert image_id in [image["ImageId"] for image in images]
+
+
+@mock_aws
+def test_ami_describe_images_executable_user_public():
+    conn = boto3.client("ec2", region_name="us-east-1")
+    ec2 = boto3.resource("ec2", "us-east-1")
+    ec2.create_instances(ImageId=EXAMPLE_AMI_ID, MinCount=1, MaxCount=1)
+    response = conn.describe_instances(
+        Filters=[{"Name": "instance-state-name", "Values": ["running"]}]
+    )
+    instance_id = response["Reservations"][0]["Instances"][0]["InstanceId"]
+    public_image_id = conn.create_image(InstanceId=instance_id, Name="PublicImage")[
+        "ImageId"
+    ]
+    private_image_id = conn.create_image(InstanceId=instance_id, Name="PrivateImage")[
+        "ImageId"
+    ]
+
+    USER1 = "".join([f"{random.randint(0, 9)}" for _ in range(0, 12)])
+    USER2 = "".join([f"{random.randint(0, 9)}" for _ in range(0, 12)])
+
+    SET_IMAGE_PUBLIC = {
+        "ImageId": public_image_id,
+        "Attribute": "launchPermission",
+        "OperationType": "add",
+        "UserGroups": ["all"],
+    }
+    conn.modify_image_attribute(**SET_IMAGE_PUBLIC)
+
+    SET_IMAGE_PRIVATE = {
+        "ImageId": private_image_id,
+        "Attribute": "launchPermission",
+        "OperationType": "add",
+        "UserIds": [USER2],
+    }
+    conn.modify_image_attribute(**SET_IMAGE_PRIVATE)
+
+    images = conn.describe_images(
+        ExecutableUsers=[USER1],
+    )["Images"]
+
+    image_ids = [image["ImageId"] for image in images]
+    assert public_image_id in image_ids
+    assert private_image_id not in image_ids
+
+
+@mock_aws
+def test_ami_describe_images_executable_users_self():
+    conn = boto3.client("ec2", region_name="us-east-1")
+    ec2 = boto3.resource("ec2", "us-east-1")
+    ec2.create_instances(ImageId=EXAMPLE_AMI_ID, MinCount=1, MaxCount=1)
+    response = conn.describe_instances(
+        Filters=[{"Name": "instance-state-name", "Values": ["running"]}]
+    )
+    instance_id = response["Reservations"][0]["Instances"][0]["InstanceId"]
+    image_id = conn.create_image(InstanceId=instance_id, Name="PublicImage")["ImageId"]
+
+    SET_IMAGE_TO_SELF = {
+        "ImageId": image_id,
+        "Attribute": "launchPermission",
+        "OperationType": "add",
+        "UserIds": [ACCOUNT_ID],
+    }
+    conn.modify_image_attribute(**SET_IMAGE_TO_SELF)
+
+    images = conn.describe_images(ExecutableUsers=["self"])["Images"]
+
+    assert image_id in [image["ImageId"] for image in images]
 
 
 @mock_aws
diff --git a/tests/test_rds/test_rds_clusters.py b/tests/test_rds/test_rds_clusters.py
index 00554c2b0..a5a59fd7e 100644
--- a/tests/test_rds/test_rds_clusters.py
+++ b/tests/test_rds/test_rds_clusters.py
@@ -249,6 +249,7 @@ def test_create_db_cluster_additional_parameters():
             "MaxCapacity": 4,
         },
         VpcSecurityGroupIds=["sg1", "sg2"],
+        EnableIAMDatabaseAuthentication=True,
     )
 
     cluster = resp["DBCluster"]
@@ -276,6 +277,7 @@ def test_create_db_cluster_additional_parameters():
     assert len(security_groups) == 2
     assert {"VpcSecurityGroupId": "sg1", "Status": "active"} in security_groups
     assert {"VpcSecurityGroupId": "sg2", "Status": "active"} in security_groups
+    assert cluster["IAMDatabaseAuthenticationEnabled"] is True
 
 
 @mock_aws
@@ -1100,3 +1102,71 @@ def test_modify_db_cluster_snapshot_attribute():
     assert resp["DBClusterSnapshotAttributesResult"]["DBClusterSnapshotAttributes"][0][
         "AttributeValues"
     ] == ["test2"]
+
+
+@mock_aws
+def test_backtrack_window():
+    client = boto3.client("rds", region_name=RDS_REGION)
+    window = 86400
+    resp = client.create_db_cluster(
+        AvailabilityZones=["eu-north-1b"],
+        DatabaseName="users",
+        DBClusterIdentifier="cluster-id",
+        Engine="aurora-mysql",
+        EngineVersion="8.0.mysql_aurora.3.01.0",
+        MasterUsername="root",
+        MasterUserPassword="hunter2_",
+        Port=1234,
+        DeletionProtection=True,
+        EnableCloudwatchLogsExports=["audit"],
+        NetworkType="IPV4",
+        DBSubnetGroupName="subnetgroupname",
+        StorageEncrypted=True,
+        VpcSecurityGroupIds=["sg1", "sg2"],
+        BacktrackWindow=window,
+    )
+
+    assert resp["DBCluster"]["BacktrackWindow"] == window
+
+
+@mock_aws
+@pytest.mark.parametrize(
+    "params",
+    [
+        (
+            "aurora-mysql",
+            -1,
+            "The specified value (-1) is not a valid Backtrack Window. Allowed values are within the range of 0 to 259200",
+        ),
+        (
+            "aurora-mysql",
+            10000000,
+            "The specified value (10000000) is not a valid Backtrack Window. Allowed values are within the range of 0 to 259200",
+        ),
+        (
+            "aurora-postgresql",
+            20,
+            "Backtrack is not enabled for the postgres engine.",
+        ),
+    ],
+)
+def test_backtrack_errors(params):
+    client = boto3.client("rds", region_name=RDS_REGION)
+
+    with pytest.raises(ClientError) as ex:
+        client.create_db_cluster(
+            AvailabilityZones=["eu-north-1b"],
+            DatabaseName="users",
+            DBClusterIdentifier="cluster-id",
+            Engine=params[0],
+            EngineVersion="8.0.mysql_aurora.3.01.0",
+            MasterUsername="root",
+            MasterUserPassword="hunter2_",
+            DBSubnetGroupName="subnetgroupname",
+            StorageEncrypted=True,
+            VpcSecurityGroupIds=["sg1", "sg2"],
+            BacktrackWindow=params[1],
+        )
+    err = ex.value.response["Error"]
+    assert err["Code"] == "InvalidParameterValue"
+    assert err["Message"] == params[2]