Repository: getmoto/moto
Pull request #8229 (2024-10-14 03:28:52)

Adds support for UsePreviousTemplate to create_change_set
Adds missing support for using the UsePreviousTemplate param with UsePreviousValue in the parameter list with calling the cloudformation create_change_set method.
Adds unit tests.
Code clean-up.

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/getmoto/moto/pull/8229?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 94.54%. Comparing base [(`81d6f95`)](https://app.codecov.io/gh/getmoto/moto/commit/81d6f95f0459b3a3fc4a0780319e089c4eb0e1ad?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) to head [(`935b7e9`)](https://app.codecov.io/gh/getmoto/moto/commit/935b7e9fa251e7b7310e48bbf4f64854f1fbf2f6?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto).
> Report is 6 commits behind head on master.

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


```diff
@@           Coverage Diff            @@
##           master    #8229    +/-   ##
========================================
  Coverage   94.53%   94.54%            
========================================
  Files        1157     1158     +1     
  Lines       99973   100102   +129     
========================================
+ Hits        94513    94639   +126     
- Misses       5460     5463     +3     
```

| [Flag](https://app.codecov.io/gh/getmoto/moto/pull/8229/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/8229/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) | `28.87% <7.69%> (+0.06%)` | :arrow_up: |
| [unittests](https://app.codecov.io/gh/getmoto/moto/pull/8229/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) | `94.51% <100.00%> (+<0.01%)` | :arrow_up: |

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/8229?dropdown=coverage&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/apigateway/models.py b/moto/apigateway/models.py
index fad7ca7e1..43b71cfc6 100644
--- a/moto/apigateway/models.py
+++ b/moto/apigateway/models.py
@@ -1658,7 +1658,7 @@ class APIGatewayBackend(BaseBackend):
     ) -> RestAPI:
         api_id = ApigwRestApiIdentifier(
             self.account_id, self.region_name, name
-        ).generate()
+        ).generate(tags=tags)
         rest_api = RestAPI(
             api_id,
             self.account_id,
diff --git a/moto/cloudformation/responses.py b/moto/cloudformation/responses.py
index b17187a6e..3c57efeb9 100644
--- a/moto/cloudformation/responses.py
+++ b/moto/cloudformation/responses.py
@@ -154,23 +154,54 @@ class CloudFormationResponse(BaseResponse):
                 return True
         return False
 
+    def validate_template_and_stack_body(self) -> None:
+        if (
+            self._get_param("TemplateBody") or self._get_param("TemplateURL")
+        ) and self._get_param("UsePreviousTemplate", "false").lower() == "true":
+            raise ValidationError(
+                message="An error occurred (ValidationError) when calling the CreateChangeSet operation: You cannot specify both usePreviousTemplate and Template Body/Template URL."
+            )
+        elif (
+            not self._get_param("TemplateBody")
+            and not self._get_param("TemplateURL")
+            and self._get_param("UsePreviousTemplate", "false").lower() == "false"
+        ):
+            raise ValidationError(
+                message="An error occurred (ValidationError) when calling the CreateChangeSet operation: Either Template URL or Template Body must be specified."
+            )
+
     def create_change_set(self) -> str:
         stack_name = self._get_param("StackName")
         change_set_name = self._get_param("ChangeSetName")
         stack_body = self._get_param("TemplateBody")
         template_url = self._get_param("TemplateURL")
+        update_or_create = self._get_param("ChangeSetType", "CREATE")
+        use_previous_template = (
+            self._get_param("UsePreviousTemplate", "false").lower() == "true"
+        )
+        if update_or_create == "UPDATE":
+            stack = self.cloudformation_backend.get_stack(stack_name)
+            self.validate_template_and_stack_body()
+
+            if use_previous_template:
+                stack_body = stack.template
         description = self._get_param("Description")
         role_arn = self._get_param("RoleARN")
-        update_or_create = self._get_param("ChangeSetType", "CREATE")
         parameters_list = self._get_list_prefix("Parameters.member")
         tags = dict(
             (item["key"], item["value"])
             for item in self._get_list_prefix("Tags.member")
         )
         parameters = {
-            param["parameter_key"]: param["parameter_value"]
+            param["parameter_key"]: stack.parameters[param["parameter_key"]]
+            if param.get("use_previous_value", "").lower() == "true"
+            and use_previous_template
+            else param["parameter_value"]
             for param in parameters_list
         }
+        if update_or_create == "UPDATE":
+            self._validate_different_update(parameters_list, stack_body, stack)
+
         if template_url:
             stack_body = self._get_stack_from_s3_url(template_url)
         stack_notification_arns = self._get_multi_param("NotificationARNs.member")
diff --git a/moto/ec2/models/iam_instance_profile.py b/moto/ec2/models/iam_instance_profile.py
index 4ced625bf..199e98bc9 100644
--- a/moto/ec2/models/iam_instance_profile.py
+++ b/moto/ec2/models/iam_instance_profile.py
@@ -62,7 +62,7 @@ class IamInstanceProfileAssociationBackend:
         iam_instance_profile_association = IamInstanceProfileAssociation(
             self,
             iam_association_id,
-            self.get_instance(instance_id) if instance_id else None,  # type: ignore[attr-defined]
+            self.get_instance(instance_id) if instance_id else None,  # type: ignore[attr-defined, arg-type]
             instance_profile,
         )
         # Regarding to AWS there can be only one association with ec2.
diff --git a/moto/ec2/responses/vpcs.py b/moto/ec2/responses/vpcs.py
index 19de3cc2b..c5dc708e4 100644
--- a/moto/ec2/responses/vpcs.py
+++ b/moto/ec2/responses/vpcs.py
@@ -1,6 +1,9 @@
+from typing import List
+
 from moto.core.utils import camelcase_to_underscores
 from moto.ec2.utils import add_tag_specification
 
+from ..models.managed_prefixes import ManagedPrefixList
 from ._base_response import EC2BaseResponse
 
 
@@ -294,7 +297,7 @@ class VPCs(EC2BaseResponse):
         managed_prefix_list = self.ec2_backend.get_managed_prefix_list_entries(
             prefix_list_id=prefix_list_id
         )
-        entries = []
+        entries: List[ManagedPrefixList] = []
         if managed_prefix_list:
             entries = (
                 list(managed_prefix_list.entries.values())[-1]
diff --git a/moto/ecs/models.py b/moto/ecs/models.py
index 302fa2efd..fec148d86 100644
--- a/moto/ecs/models.py
+++ b/moto/ecs/models.py
@@ -1692,6 +1692,7 @@ class EC2ContainerServiceBackend(BaseBackend):
         task_definition_str = service_properties.pop("task_definition", None)
         cluster = self._get_cluster(cluster_str)
         service_name = service_properties.pop("service").split("/")[-1]
+        force_new_deployment = service_properties.pop("force_new_deployment", False)
         cluster_service_pair = f"{cluster.name}:{service_name}"
 
         if cluster_service_pair in self.services:
@@ -1705,6 +1706,12 @@ class EC2ContainerServiceBackend(BaseBackend):
             if task_definition_str:
                 self.describe_task_definition(task_definition_str)
                 current_service.task_definition = task_definition_str
+            if force_new_deployment and current_service.deployments:
+                deployment = current_service.deployments[0]
+                deployment["id"] = f"ecs-svc/{mock_random.randint(0, 32**12)}"
+                now = datetime.now(timezone.utc)
+                deployment["createdAt"] = now
+                deployment["updatedAt"] = now
             return current_service
         else:
             raise ServiceNotFoundException
diff --git a/moto/elbv2/models.py b/moto/elbv2/models.py
index 961be5ee1..f0b2e772c 100644
--- a/moto/elbv2/models.py
+++ b/moto/elbv2/models.py
@@ -1990,6 +1990,7 @@ Member must satisfy regular expression pattern: {expression}"
 
     def remove_tags(self, resource_arns: List[str], tag_keys: List[str]) -> None:
         for arn in resource_arns:
+            self._get_resource_by_arn(arn)
             self.tagging_service.untag_resource_using_names(arn, tag_keys)
 
     def describe_tags(self, resource_arns: List[str]) -> Dict[str, Dict[str, str]]:
diff --git a/moto/iam/models.py b/moto/iam/models.py
index 3e06a6cfb..aece6258f 100644
--- a/moto/iam/models.py
+++ b/moto/iam/models.py
@@ -3,7 +3,7 @@ import json
 import os
 import re
 import string
-from datetime import datetime
+from datetime import date, datetime
 from typing import Any, Dict, Iterable, List, Optional, Tuple, Union
 from urllib import parse
 
@@ -101,6 +101,12 @@ def mark_account_as_visited(
         pass
 
 
+def _serialize_version_datetime(value: Any) -> str:
+    if isinstance(value, date):
+        return value.strftime("%Y-%m-%d")
+    raise TypeError("Unable to serialize value.")
+
+
 LIMIT_KEYS_PER_USER = 2
 
 
@@ -745,10 +751,16 @@ class Role(CloudFormationModel):
         properties = cloudformation_json["Properties"]
         role_name = properties.get("RoleName", resource_name)
 
+        assume_role_policy_document = properties["AssumeRolePolicyDocument"]
+        if not isinstance(assume_role_policy_document, str):
+            assume_role_policy_document = json.dumps(
+                assume_role_policy_document, default=_serialize_version_datetime
+            )
+
         iam_backend = iam_backends[account_id][get_partition(region_name)]
         role = iam_backend.create_role(
             role_name=role_name,
-            assume_role_policy_document=properties["AssumeRolePolicyDocument"],
+            assume_role_policy_document=assume_role_policy_document,
             path=properties.get("Path", "/"),
             permissions_boundary=properties.get("PermissionsBoundary", ""),
             description=properties.get("Description", ""),
@@ -898,7 +910,7 @@ class Role(CloudFormationModel):
       <Path>{{ role.path }}</Path>
       <Arn>{{ role.arn }}</Arn>
       <RoleName>{{ role.name }}</RoleName>
-      <AssumeRolePolicyDocument>{{ role.assume_role_policy_document }}</AssumeRolePolicyDocument>
+      <AssumeRolePolicyDocument>{{ role.assume_role_policy_document | urlencode }}</AssumeRolePolicyDocument>
       {% if role.description is not none %}
       <Description>{{ role.description_escaped }}</Description>
       {% endif %}
diff --git a/moto/iam/responses.py b/moto/iam/responses.py
index 1b2be7c1f..dd3b1836e 100644
--- a/moto/iam/responses.py
+++ b/moto/iam/responses.py
@@ -1403,7 +1403,7 @@ CREATE_INSTANCE_PROFILE_TEMPLATE = """<CreateInstanceProfileResponse xmlns="http
           <Path>{{ role.path }}</Path>
           <Arn>{{ role.arn }}</Arn>
           <RoleName>{{ role.name }}</RoleName>
-          <AssumeRolePolicyDocument>{{ role.assume_role_policy_document }}</AssumeRolePolicyDocument>
+          <AssumeRolePolicyDocument>{{ role.assume_role_policy_document | urlencode }}</AssumeRolePolicyDocument>
           <CreateDate>{{ role.created_iso_8601 }}</CreateDate>
           <RoleId>{{ role.id }}</RoleId>
         </member>
@@ -1444,7 +1444,7 @@ GET_INSTANCE_PROFILE_TEMPLATE = """<GetInstanceProfileResponse xmlns="https://ia
           <Path>{{ role.path }}</Path>
           <Arn>{{ role.arn }}</Arn>
           <RoleName>{{ role.name }}</RoleName>
-          <AssumeRolePolicyDocument>{{ role.assume_role_policy_document }}</AssumeRolePolicyDocument>
+          <AssumeRolePolicyDocument>{{ role.assume_role_policy_document | urlencode }}</AssumeRolePolicyDocument>
           <CreateDate>{{ role.created_iso_8601 }}</CreateDate>
           <RoleId>{{ role.id }}</RoleId>
         </member>
@@ -1482,7 +1482,7 @@ GET_ROLE_POLICY_TEMPLATE = """<GetRolePolicyResponse xmlns="https://iam.amazonaw
 <GetRolePolicyResult>
   <PolicyName>{{ policy_name }}</PolicyName>
   <RoleName>{{ role_name }}</RoleName>
-  <PolicyDocument>{{ policy_document }}</PolicyDocument>
+  <PolicyDocument>{{ policy_document | urlencode }}</PolicyDocument>
 </GetRolePolicyResult>
 <ResponseMetadata>
   <RequestId>7e7cd8bc-99ef-11e1-a4c3-27EXAMPLE804</RequestId>
@@ -1566,7 +1566,7 @@ LIST_ROLES_TEMPLATE = """<ListRolesResponse xmlns="https://iam.amazonaws.com/doc
         <Path>{{ role.path }}</Path>
         <Arn>{{ role.arn }}</Arn>
         <RoleName>{{ role.name }}</RoleName>
-        <AssumeRolePolicyDocument>{{ role.assume_role_policy_document }}</AssumeRolePolicyDocument>
+        <AssumeRolePolicyDocument>{{ role.assume_role_policy_document | urlencode }}</AssumeRolePolicyDocument>
         <CreateDate>{{ role.created_iso_8601 }}</CreateDate>
         <RoleId>{{ role.id }}</RoleId>
         <MaxSessionDuration>{{ role.max_session_duration }}</MaxSessionDuration>
@@ -1662,7 +1662,7 @@ LIST_INSTANCE_PROFILES_TEMPLATE = """<ListInstanceProfilesResponse xmlns="https:
             <Path>{{ role.path }}</Path>
             <Arn>{{ role.arn }}</Arn>
             <RoleName>{{ role.name }}</RoleName>
-            <AssumeRolePolicyDocument>{{ role.assume_role_policy_document }}</AssumeRolePolicyDocument>
+            <AssumeRolePolicyDocument>{{ role.assume_role_policy_document | urlencode }}</AssumeRolePolicyDocument>
             <CreateDate>{{ role.created_iso_8601 }}</CreateDate>
             <RoleId>{{ role.id }}</RoleId>
           </member>
@@ -1831,7 +1831,7 @@ GET_GROUP_POLICY_TEMPLATE = """<GetGroupPolicyResponse xmlns="https://iam.amazon
 <GetGroupPolicyResult>
   <PolicyName>{{ policy_name }}</PolicyName>
   <GroupName>{{ group_name }}</GroupName>
-  <PolicyDocument>{{ policy_document }}</PolicyDocument>
+  <PolicyDocument>{{ policy_document | urlencode }}</PolicyDocument>
 </GetGroupPolicyResult>
 <ResponseMetadata>
   <RequestId>7e7cd8bc-99ef-11e1-a4c3-27EXAMPLE804</RequestId>
@@ -1927,7 +1927,7 @@ GET_USER_POLICY_TEMPLATE = """<GetUserPolicyResponse>
       <UserName>{{ user_name }}</UserName>
       <PolicyName>{{ policy_name }}</PolicyName>
       <PolicyDocument>
-      {{ policy_document }}
+      {{ policy_document | urlencode }}
       </PolicyDocument>
    </GetUserPolicyResult>
    <ResponseMetadata>
@@ -2134,7 +2134,7 @@ LIST_INSTANCE_PROFILES_FOR_ROLE_TEMPLATE = """<ListInstanceProfilesForRoleRespon
         <Path>{{ role.path }}</Path>
         <Arn>{{ role.arn }}</Arn>
         <RoleName>{{ role.name }}</RoleName>
-        <AssumeRolePolicyDocument>{{ role.assume_policy_document }}</AssumeRolePolicyDocument>
+        <AssumeRolePolicyDocument>{{ role.assume_policy_document | urlencode }}</AssumeRolePolicyDocument>
         <CreateDate>{{ role.created_iso_8601 }}</CreateDate>
         <RoleId>{{ role.id }}</RoleId>
       </member>
@@ -2319,7 +2319,7 @@ GET_ACCOUNT_AUTHORIZATION_DETAILS_TEMPLATE = """<GetAccountAuthorizationDetailsR
         {% for policy in user.policies %}
             <member>
                 <PolicyName>{{ policy }}</PolicyName>
-                <PolicyDocument>{{ user.policies[policy] }}</PolicyDocument>
+                <PolicyDocument>{{ user.policies[policy] | urlencode }}</PolicyDocument>
             </member>
         {% endfor %}
         </UserPolicyList>
@@ -2355,7 +2355,7 @@ GET_ACCOUNT_AUTHORIZATION_DETAILS_TEMPLATE = """<GetAccountAuthorizationDetailsR
         {% for policy in group.policies %}
             <member>
                 <PolicyName>{{ policy }}</PolicyName>
-                <PolicyDocument>{{ group.policies[policy] }}</PolicyDocument>
+                <PolicyDocument>{{ group.policies[policy] | urlencode }}</PolicyDocument>
             </member>
         {% endfor %}
         </GroupPolicyList>
@@ -2369,7 +2369,7 @@ GET_ACCOUNT_AUTHORIZATION_DETAILS_TEMPLATE = """<GetAccountAuthorizationDetailsR
         {% for inline_policy in role.policies %}
             <member>
                 <PolicyName>{{ inline_policy }}</PolicyName>
-                <PolicyDocument>{{ role.policies[inline_policy] }}</PolicyDocument>
+                <PolicyDocument>{{ role.policies[inline_policy] | urlencode }}</PolicyDocument>
             </member>
         {% endfor %}
         </RolePolicyList>
@@ -2399,7 +2399,7 @@ GET_ACCOUNT_AUTHORIZATION_DETAILS_TEMPLATE = """<GetAccountAuthorizationDetailsR
                 <Path>{{ role.path }}</Path>
                 <Arn>{{ role.arn }}</Arn>
                 <RoleName>{{ role.name }}</RoleName>
-                <AssumeRolePolicyDocument>{{ role.assume_role_policy_document }}</AssumeRolePolicyDocument>
+                <AssumeRolePolicyDocument>{{ role.assume_role_policy_document | urlencode }}</AssumeRolePolicyDocument>
                 {% if role.description is not none %}
                 <Description>{{ role.description_escaped }}</Description>
                 {% endif %}
@@ -2424,7 +2424,7 @@ GET_ACCOUNT_AUTHORIZATION_DETAILS_TEMPLATE = """<GetAccountAuthorizationDetailsR
         <Path>{{ role.path }}</Path>
         <Arn>{{ role.arn }}</Arn>
         <RoleName>{{ role.name }}</RoleName>
-        <AssumeRolePolicyDocument>{{ role.assume_role_policy_document }}</AssumeRolePolicyDocument>
+        <AssumeRolePolicyDocument>{{ role.assume_role_policy_document | urlencode }}</AssumeRolePolicyDocument>
         <CreateDate>{{ role.created_iso_8601 }}</CreateDate>
         <RoleId>{{ role.id }}</RoleId>
       </member>
diff --git a/moto/packages/boto/ec2/instance.py b/moto/packages/boto/ec2/instance.py
index 2ab190770..9c4707d97 100644
--- a/moto/packages/boto/ec2/instance.py
+++ b/moto/packages/boto/ec2/instance.py
@@ -25,7 +25,7 @@
 Represents an EC2 Instance
 """
 
-from typing import Any
+from typing import Any, Optional
 
 from moto.packages.boto.ec2.ec2object import EC2Object, TaggedEC2Object
 from moto.packages.boto.ec2.image import ProductCodes
@@ -159,8 +159,8 @@ class Instance(TaggedEC2Object):
         self.platform = None
         self.interfaces: Any = []
         self.hypervisor = None
-        self.virtualization_type = None
-        self.architecture = None
+        self.virtualization_type: Optional[str] = None
+        self.architecture: Optional[str] = None
         self.instance_profile = None
         self._previous_state = None
         self._placement = InstancePlacement()
diff --git a/moto/pinpoint/models.py b/moto/pinpoint/models.py
index 69053ce47..a482a783a 100644
--- a/moto/pinpoint/models.py
+++ b/moto/pinpoint/models.py
@@ -14,7 +14,7 @@ from .exceptions import ApplicationNotFound, EventStreamNotFound
 class App(BaseModel):
     def __init__(self, account_id: str, region_name: str, name: str):
         self.application_id = str(mock_random.uuid4()).replace("-", "")
-        self.arn = f"arn:{get_partition(region_name)}:mobiletargeting:us-east-1:{account_id}:apps/{self.application_id}"
+        self.arn = f"arn:{get_partition(region_name)}:mobiletargeting:{region_name}:{account_id}:apps/{self.application_id}"
         self.name = name
         self.created = unix_time()
         self.settings = AppSettings()
diff --git a/moto/s3/models.py b/moto/s3/models.py
index 5ae966e9d..79a33340f 100644
--- a/moto/s3/models.py
+++ b/moto/s3/models.py
@@ -658,6 +658,9 @@ def get_canned_acl(acl: str) -> FakeAcl:
 
 
 class LifecycleFilter(BaseModel):
+    tag_key: Optional[str]
+    tag_value: Optional[str]
+
     def __init__(
         self,
         prefix: Optional[str] = None,
diff --git a/moto/secretsmanager/models.py b/moto/secretsmanager/models.py
index 8d3d1d5a2..b2f185fb0 100644
--- a/moto/secretsmanager/models.py
+++ b/moto/secretsmanager/models.py
@@ -100,7 +100,7 @@ class FakeSecret:
         self.name = secret_id
         self.arn = SecretsManagerSecretIdentifier(
             account_id, region_name, secret_id
-        ).generate()
+        ).generate(tags=tags)
         self.account_id = account_id
         self.region = region_name
         self.secret_string = secret_string
diff --git a/moto/utilities/id_generator.py b/moto/utilities/id_generator.py
index c5f0a2c93..a26464a43 100644
--- a/moto/utilities/id_generator.py
+++ b/moto/utilities/id_generator.py
@@ -8,7 +8,7 @@ from moto.moto_api._internal import mock_random
 log = logging.getLogger(__name__)
 
 ExistingIds = Union[List[str], None]
-Tags = Union[Dict[str, str], None]
+Tags = Union[Dict[str, str], List[Dict[str, str]], None]
 
 # Custom resource tag to override the generated resource ID.
 TAG_KEY_CUSTOM_ID = "_custom_id_"
@@ -71,7 +71,8 @@ class MotoIdManager:
     def get_custom_id(
         self, resource_identifier: ResourceIdentifier
     ) -> Union[str, None]:
-        # retrieves a custom_id for a resource. Returns None
+        # retrieves a custom_id for a resource. Returns None if no id were registered
+        # that matches the `resource_identifier`
         return self._custom_ids.get(resource_identifier.unique_identifier)
 
     def set_custom_id(
@@ -95,10 +96,21 @@ class MotoIdManager:
 
     @staticmethod
     def get_id_from_tags(id_source_context: IdSourceContext) -> Union[str, None]:
-        if tags := id_source_context.get("tags"):
+        if not (tags := id_source_context.get("tags")):
+            return None
+
+        if isinstance(tags, dict):
             return tags.get(TAG_KEY_CUSTOM_ID)
 
-        return None
+        if isinstance(tags, list):
+            return next(
+                (
+                    tag.get("Value")
+                    for tag in tags
+                    if tag.get("Key") == TAG_KEY_CUSTOM_ID
+                ),
+                None,
+            )
 
     def get_custom_id_from_context(
         self, id_source_context: IdSourceContext


Test patch:
diff --git a/tests/test_apigateway/test_apigateway_custom_ids.py b/tests/test_apigateway/test_apigateway_custom_ids.py
index 1d2debbba..3bfdc544c 100644
--- a/tests/test_apigateway/test_apigateway_custom_ids.py
+++ b/tests/test_apigateway/test_apigateway_custom_ids.py
@@ -11,6 +11,8 @@ from moto.apigateway.utils import (
     ApigwRestApiIdentifier,
     ApigwUsagePlanIdentifier,
 )
+from moto.utilities.id_generator import TAG_KEY_CUSTOM_ID
+from tests import DEFAULT_ACCOUNT_ID
 
 API_ID = "ApiId"
 API_KEY_ID = "ApiKeyId"
@@ -27,7 +29,7 @@ USAGE_PLAN_ID = "UPlanId"
 @pytest.mark.skipif(
     not settings.TEST_DECORATOR_MODE, reason="Can't access the id manager in proxy mode"
 )
-def test_custom_id_rest_api(set_custom_id, account_id):
+def test_custom_id_rest_api(set_custom_id):
     region_name = "us-west-2"
     rest_api_name = "my-api"
     model_name = "modelName"
@@ -37,33 +39,40 @@ def test_custom_id_rest_api(set_custom_id, account_id):
     client = boto3.client("apigateway", region_name=region_name)
 
     set_custom_id(
-        ApigwRestApiIdentifier(account_id, region_name, rest_api_name), API_ID
+        ApigwRestApiIdentifier(DEFAULT_ACCOUNT_ID, region_name, rest_api_name), API_ID
     )
     set_custom_id(
-        ApigwResourceIdentifier(account_id, region_name, path_name="/"),
+        ApigwResourceIdentifier(DEFAULT_ACCOUNT_ID, region_name, path_name="/"),
         ROOT_RESOURCE_ID,
     )
     set_custom_id(
         ApigwResourceIdentifier(
-            account_id, region_name, parent_id=ROOT_RESOURCE_ID, path_name="pet"
+            DEFAULT_ACCOUNT_ID, region_name, parent_id=ROOT_RESOURCE_ID, path_name="pet"
         ),
         PET_1_RESOURCE_ID,
     )
     set_custom_id(
         ApigwResourceIdentifier(
-            account_id, region_name, parent_id=PET_1_RESOURCE_ID, path_name="pet"
+            DEFAULT_ACCOUNT_ID,
+            region_name,
+            parent_id=PET_1_RESOURCE_ID,
+            path_name="pet",
         ),
         PET_2_RESOURCE_ID,
     )
-    set_custom_id(ApigwModelIdentifier(account_id, region_name, model_name), MODEL_ID)
+    set_custom_id(
+        ApigwModelIdentifier(DEFAULT_ACCOUNT_ID, region_name, model_name), MODEL_ID
+    )
     set_custom_id(
         ApigwRequestValidatorIdentifier(
-            account_id, region_name, request_validator_name
+            DEFAULT_ACCOUNT_ID, region_name, request_validator_name
         ),
         REQUEST_VALIDATOR_ID,
     )
     set_custom_id(
-        ApigwDeploymentIdentifier(account_id, region_name, stage_name=stage_name),
+        ApigwDeploymentIdentifier(
+            DEFAULT_ACCOUNT_ID, region_name, stage_name=stage_name
+        ),
         DEPLOYMENT_ID,
     )
 
@@ -113,7 +122,7 @@ def test_custom_id_rest_api(set_custom_id, account_id):
 @pytest.mark.skipif(
     not settings.TEST_DECORATOR_MODE, reason="Can't access the id manager in proxy mode"
 )
-def test_custom_id_api_key(account_id, set_custom_id):
+def test_custom_id_api_key(set_custom_id):
     region_name = "us-west-2"
     api_key_value = "01234567890123456789"
     usage_plan_name = "usage-plan"
@@ -121,10 +130,11 @@ def test_custom_id_api_key(account_id, set_custom_id):
     client = boto3.client("apigateway", region_name=region_name)
 
     set_custom_id(
-        ApigwApiKeyIdentifier(account_id, region_name, value=api_key_value), API_KEY_ID
+        ApigwApiKeyIdentifier(DEFAULT_ACCOUNT_ID, region_name, value=api_key_value),
+        API_KEY_ID,
     )
     set_custom_id(
-        ApigwUsagePlanIdentifier(account_id, region_name, usage_plan_name),
+        ApigwUsagePlanIdentifier(DEFAULT_ACCOUNT_ID, region_name, usage_plan_name),
         USAGE_PLAN_ID,
     )
 
@@ -138,3 +148,15 @@ def test_custom_id_api_key(account_id, set_custom_id):
 
     assert api_key["id"] == API_KEY_ID
     assert usage_plan["id"] == USAGE_PLAN_ID
+
+
+@mock_aws
+def test_create_rest_api_with_custom_id_tag():
+    rest_api_name = "rest_api"
+    api_id = "testTagId"
+    client = boto3.client("apigateway", region_name="us-west-2")
+    rest_api = client.create_rest_api(
+        name=rest_api_name, tags={TAG_KEY_CUSTOM_ID: api_id}
+    )
+
+    assert rest_api["id"] == api_id
diff --git a/tests/test_cloudformation/test_cloudformation_stack_crud_boto3.py b/tests/test_cloudformation/test_cloudformation_stack_crud_boto3.py
index 9590a066d..26df7c3bc 100644
--- a/tests/test_cloudformation/test_cloudformation_stack_crud_boto3.py
+++ b/tests/test_cloudformation/test_cloudformation_stack_crud_boto3.py
@@ -1568,18 +1568,18 @@ def test_describe_change_set(stack_template, change_template):
         ChangeSetType="CREATE",
     )
 
-    stack = cf.describe_change_set(ChangeSetName="NewChangeSet")
+    change_set = cf.describe_change_set(ChangeSetName="NewChangeSet")
 
-    assert stack["ChangeSetName"] == "NewChangeSet"
-    assert stack["StackName"] == "NewStack"
-    assert stack["Status"] == "CREATE_COMPLETE"
-    assert stack["ExecutionStatus"] == "AVAILABLE"
+    assert change_set["ChangeSetName"] == "NewChangeSet"
+    assert change_set["StackName"] == "NewStack"
+    assert change_set["Status"] == "CREATE_COMPLETE"
+    assert change_set["ExecutionStatus"] == "AVAILABLE"
     two_secs_ago = datetime.now(tz=timezone.utc) - timedelta(seconds=2)
     assert (
-        two_secs_ago < stack["CreationTime"] < datetime.now(tz=timezone.utc)
+        two_secs_ago < change_set["CreationTime"] < datetime.now(tz=timezone.utc)
     ), "Change set should have been created recently"
-    assert len(stack["Changes"]) == 1
-    assert stack["Changes"][0] == {
+    assert len(change_set["Changes"]) == 1
+    assert change_set["Changes"][0] == {
         "Type": "Resource",
         "ResourceChange": {
             "Action": "Add",
@@ -1611,10 +1611,10 @@ def test_describe_change_set(stack_template, change_template):
         Parameters=[{"ParameterKey": "KeyName", "ParameterValue": "value"}],
     )
 
-    stack = cf.describe_change_set(ChangeSetName="NewChangeSet2")
-    assert stack["ChangeSetName"] == "NewChangeSet2"
-    assert stack["StackName"] == "NewStack"
-    assert len(stack["Changes"]) == 2
+    change_set = cf.describe_change_set(ChangeSetName="NewChangeSet2")
+    assert change_set["ChangeSetName"] == "NewChangeSet2"
+    assert change_set["StackName"] == "NewStack"
+    assert len(change_set["Changes"]) == 2
 
     # Execute change set
     cf.execute_change_set(ChangeSetName="NewChangeSet2")
@@ -2629,6 +2629,152 @@ def test_base64_function():
     waiter.wait(StackName=name)
 
 
+@mock_aws
+@pytest.mark.parametrize(
+    "error_message, kwargs",
+    [
+        ("No updates are to be performed.", {"UsePreviousTemplate": True}),
+        (
+            "An error occurred (ValidationError) when calling the CreateChangeSet operation: Either Template URL or Template Body must be specified.",
+            {},
+        ),
+        (
+            "An error occurred (ValidationError) when calling the CreateChangeSet operation: You cannot specify both usePreviousTemplate and Template Body/Template URL.",
+            {
+                "UsePreviousTemplate": True,
+                "TemplateBody": json.dumps(dummy_template_with_parameters),
+            },
+        ),
+        (
+            "An error occurred (ValidationError) when calling the CreateChangeSet operation: You cannot specify both usePreviousTemplate and Template Body/Template URL.",
+            {"UsePreviousTemplate": True, "TemplateURL": ""},
+        ),
+    ],
+    ids=[
+        "no_changes",
+        "no_template_body_or_url",
+        "use_previous_w_template_body",
+        "use_previous_w_template_url",
+    ],
+)
+def test_create_change_set_w_previous_template_faillures(error_message, kwargs):
+    stack_name = "stack-name"
+    bucket_name = "test-bucket"
+    change_set_name = "test-change-set"
+    if "TemplateURL" in kwargs.keys():
+        cf = boto3.client("cloudformation", region_name=REGION_NAME)
+
+        s3 = boto3.client("s3", region_name=REGION_NAME)
+        s3_conn = boto3.resource("s3", region_name=REGION_NAME)
+        s3_conn.create_bucket(Bucket="foobar")
+
+        s3_conn.Object("foobar", "template-key").put(
+            Body=json.dumps(dummy_template_with_parameters)
+        )
+        key_url = s3.generate_presigned_url(
+            ClientMethod="get_object",
+            Params={"Bucket": "foobar", "Key": "template-key"},
+        )
+
+        cf.create_stack(
+            StackName=stack_name,
+            TemplateURL=key_url,
+            Parameters=[
+                {"ParameterKey": "Name", "ParameterValue": bucket_name},
+                {"ParameterKey": "Another", "ParameterValue": "A"},
+            ],
+        )
+        kwargs["TemplateURL"] = key_url
+    else:
+        cf = boto3.client("cloudformation", region_name=REGION_NAME)
+        cf.create_stack(
+            StackName=stack_name,
+            TemplateBody=json.dumps(dummy_template_with_parameters),
+            Parameters=[
+                {"ParameterKey": "Name", "ParameterValue": bucket_name},
+                {"ParameterKey": "Another", "ParameterValue": "A"},
+            ],
+        )
+
+    with pytest.raises(ClientError) as exp:
+        cf.create_change_set(
+            StackName=stack_name,
+            ChangeSetName=change_set_name,
+            ChangeSetType="UPDATE",
+            Parameters=[
+                {"ParameterKey": "Name", "UsePreviousValue": True},
+                {"ParameterKey": "Another", "UsePreviousValue": True},
+            ],
+            **kwargs,
+        )
+    exp_err = exp.value.response.get("Error")
+    exp_metadata = exp.value.response.get("ResponseMetadata")
+
+    assert exp_err["Code"] == "ValidationError"
+    assert exp_err["Message"] == error_message
+    assert exp_metadata.get("HTTPStatusCode") == 400
+
+
+@mock_aws
+@pytest.mark.parametrize(
+    "params_list, updated_params",
+    [
+        (
+            [
+                {"ParameterKey": "Name", "ParameterValue": "test-bucket-2"},
+                {"ParameterKey": "Another", "ParameterValue": "B"},
+            ],
+            ["test-bucket-2", "B"],
+        ),
+        (
+            [
+                {"ParameterKey": "Name", "ParameterValue": "test-bucket-2"},
+                {"ParameterKey": "Another", "UsePreviousValue": True},
+            ],
+            ["test-bucket-2", "A"],
+        ),
+    ],
+    ids=["all_new_values", "some_new_values"],
+)
+def test_create_change_set_w_previous_template_success(params_list, updated_params):
+    stack_name = "stack-name"
+    bucket_name = "test-bucket"
+    change_set_name = "test-change-set"
+
+    cf = boto3.client("cloudformation", region_name=REGION_NAME)
+    cf.create_stack(
+        StackName=stack_name,
+        TemplateBody=json.dumps(dummy_template_with_parameters),
+        Parameters=[
+            {"ParameterKey": "Name", "ParameterValue": bucket_name},
+            {"ParameterKey": "Another", "ParameterValue": "A"},
+        ],
+    )
+    cf.create_change_set(
+        StackName=stack_name,
+        UsePreviousTemplate=True,
+        ChangeSetName=change_set_name,
+        ChangeSetType="UPDATE",
+        Parameters=params_list,
+    )
+
+    change_set = cf.describe_change_set(ChangeSetName=change_set_name)
+    assert change_set["ChangeSetName"] == change_set_name
+    assert change_set["StackName"] == stack_name
+
+    cf.execute_change_set(ChangeSetName=change_set_name)
+
+    change_set = cf.describe_change_set(ChangeSetName=change_set_name)
+    assert change_set["ChangeSetName"] == change_set_name
+    assert change_set["StackName"] == stack_name
+    assert change_set["ExecutionStatus"] == "EXECUTE_COMPLETE"
+
+    stacks = cf.describe_stacks(StackName=stack_name)["Stacks"]
+    assert len(stacks) == 1
+    stack = stacks[0]
+    assert [param["ParameterValue"] for param in stack["Parameters"]] == updated_params
+
+
 def get_role_name():
     with mock_aws():
         iam = boto3.client("iam", region_name=REGION_NAME)
diff --git a/tests/test_ecs/test_ecs_boto3.py b/tests/test_ecs/test_ecs_boto3.py
index 0aaec121d..5f6baba84 100644
--- a/tests/test_ecs/test_ecs_boto3.py
+++ b/tests/test_ecs/test_ecs_boto3.py
@@ -1225,6 +1225,8 @@ def test_update_service():
         desiredCount=2,
     )
     assert response["service"]["desiredCount"] == 2
+    id = response["service"]["deployments"][0]["id"]
+    created_at = response["service"]["deployments"][0]["createdAt"]
 
     response = client.update_service(
         cluster="test_ecs_cluster",
@@ -1248,6 +1250,20 @@ def test_update_service():
     assert response["service"]["runningCount"] == 1
     assert response["service"]["pendingCount"] == 0
 
+    response = client.update_service(
+        cluster="test_ecs_cluster",
+        service="test_ecs_service",
+        taskDefinition="test_ecs_task",
+        desiredCount=1,
+        forceNewDeployment=True,
+    )
+    assert response["service"]["deployments"][0]["id"] != id
+    assert response["service"]["deployments"][0]["createdAt"] != created_at
+    assert (
+        response["service"]["deployments"][0]["createdAt"]
+        == response["service"]["deployments"][0]["updatedAt"]
+    )
+
 
 @mock_aws
 def test_update_missing_service():
diff --git a/tests/test_elbv2/test_elbv2.py b/tests/test_elbv2/test_elbv2.py
index 5abc626c8..512f31b39 100644
--- a/tests/test_elbv2/test_elbv2.py
+++ b/tests/test_elbv2/test_elbv2.py
@@ -198,6 +198,13 @@ def test_add_remove_tags():
 
     conn.remove_tags(ResourceArns=[lb["LoadBalancerArn"]], TagKeys=["a"])
 
+    with pytest.raises(ClientError) as exc:
+        # add a random string in the ARN to make the resource non-existent
+        BAD_LB_ARN = lb["LoadBalancerArn"] + "randomstring"
+        conn.remove_tags(ResourceArns=[BAD_LB_ARN], TagKeys=["a"])
+    err = exc.value.response["Error"]
+    assert err["Code"] == "LoadBalancerNotFound"
+
     tags = {
         d["Key"]: d["Value"]
         for d in conn.describe_tags(ResourceArns=[lb["LoadBalancerArn"]])[
diff --git a/tests/test_elbv2/test_elbv2_listener_rule_tags.py b/tests/test_elbv2/test_elbv2_listener_rule_tags.py
index 7a03da309..02b5d1351 100644
--- a/tests/test_elbv2/test_elbv2_listener_rule_tags.py
+++ b/tests/test_elbv2/test_elbv2_listener_rule_tags.py
@@ -1,3 +1,6 @@
+import pytest
+from botocore.exceptions import ClientError
+
 from moto import mock_aws
 
 from .test_elbv2 import create_load_balancer
@@ -83,3 +86,35 @@ def test_listener_rule_add_remove_tags():
         {"Key": "b", "Value": "b"},
         {"Key": "c", "Value": "b"},
     ]
+
+
+@mock_aws
+def test_remove_tags_to_invalid_listener_rule():
+    response, _, _, _, _, elbv2 = create_load_balancer()
+    load_balancer_arn = response.get("LoadBalancers")[0].get("LoadBalancerArn")
+
+    listener_arn = elbv2.create_listener(
+        LoadBalancerArn=load_balancer_arn,
+        Protocol="HTTP",
+        Port=80,
+        DefaultActions=[],
+    )["Listeners"][0]["ListenerArn"]
+    rule_arn = elbv2.create_rule(
+        ListenerArn=listener_arn,
+        Priority=100,
+        Conditions=[create_condition],
+        Actions=[default_action],
+        Tags=[{"Key": "k1", "Value": "v1"}],
+    )["Rules"][0]["RuleArn"]
+
+    # add a random string in the ARN to make the resource non-existent
+    BAD_ARN = rule_arn + "randostring"
+
+    # test for exceptions on remove tags
+    with pytest.raises(ClientError) as err:
+        elbv2.remove_tags(ResourceArns=[BAD_ARN], TagKeys=["a"])
+
+    assert err.value.response["Error"]["Code"] == "RuleNotFound"
+    assert (
+        err.value.response["Error"]["Message"] == "The specified rule does not exist."
+    )
diff --git a/tests/test_elbv2/test_elbv2_listener_tags.py b/tests/test_elbv2/test_elbv2_listener_tags.py
index 3b80537ce..4c1fed730 100644
--- a/tests/test_elbv2/test_elbv2_listener_tags.py
+++ b/tests/test_elbv2/test_elbv2_listener_tags.py
@@ -1,3 +1,6 @@
+import pytest
+from botocore.exceptions import ClientError
+
 from moto import mock_aws
 
 from .test_elbv2 import create_load_balancer
@@ -73,3 +76,29 @@ def test_listener_add_remove_tags():
         {"Key": "b", "Value": "b"},
         {"Key": "c", "Value": "b"},
     ]
+
+
+@mock_aws
+def test_remove_tags_to_invalid_listener():
+    response, _, _, _, _, elbv2 = create_load_balancer()
+    load_balancer_arn = response.get("LoadBalancers")[0].get("LoadBalancerArn")
+
+    listener_arn = elbv2.create_listener(
+        LoadBalancerArn=load_balancer_arn,
+        Protocol="HTTP",
+        Port=80,
+        DefaultActions=[],
+    )["Listeners"][0]["ListenerArn"]
+
+    # add a random string in the ARN to make the resource non-existent
+    BAD_ARN = listener_arn + "randomstring"
+
+    # test for exceptions on remove tags
+    with pytest.raises(ClientError) as err:
+        elbv2.remove_tags(ResourceArns=[BAD_ARN], TagKeys=["a"])
+
+    assert err.value.response["Error"]["Code"] == "ListenerNotFound"
+    assert (
+        err.value.response["Error"]["Message"]
+        == "The specified listener does not exist."
+    )
diff --git a/tests/test_elbv2/test_elbv2_target_groups.py b/tests/test_elbv2/test_elbv2_target_groups.py
index eefbd4544..d125d1f8c 100644
--- a/tests/test_elbv2/test_elbv2_target_groups.py
+++ b/tests/test_elbv2/test_elbv2_target_groups.py
@@ -75,6 +75,41 @@ def test_create_target_group_with_tags():
     assert tags == [{"Key": "key2", "Value": "val2"}]
 
 
+@mock_aws
+def test_remove_tags_to_invalid_target_group():
+    response, vpc, _, _, _, elbv2 = create_load_balancer()
+
+    response = elbv2.create_target_group(
+        Name="a-target",
+        Protocol="HTTP",
+        Port=8080,
+        VpcId=vpc.id,
+        HealthCheckProtocol="HTTP",
+        HealthCheckPort="8080",
+        HealthCheckPath="/",
+        HealthCheckIntervalSeconds=5,
+        HealthCheckTimeoutSeconds=3,
+        HealthyThresholdCount=5,
+        UnhealthyThresholdCount=2,
+        Matcher={"HttpCode": "200"},
+        Tags=[{"Key": "key1", "Value": "val1"}],
+    )
+    target_group = response["TargetGroups"][0]
+    target_group_arn = target_group["TargetGroupArn"]
+
+    # add a random string in the ARN to make the resource non-existent
+    BAD_ARN = target_group_arn + "randomstring"
+
+    # test for exceptions on remove tags
+    with pytest.raises(ClientError) as err:
+        elbv2.remove_tags(ResourceArns=[BAD_ARN], TagKeys=["a"])
+
+    assert err.value.response["Error"]["Code"] == "TargetGroupNotFound"
+    assert (
+        err.value.response["Error"]["Message"] == "One or more target groups not found"
+    )
+
+
 @mock_aws
 def test_create_target_group_and_listeners():
     response, vpc, _, _, _, conn = create_load_balancer()
diff --git a/tests/test_iam/test_iam.py b/tests/test_iam/test_iam.py
index cab288905..f103c4cb6 100644
--- a/tests/test_iam/test_iam.py
+++ b/tests/test_iam/test_iam.py
@@ -209,8 +209,11 @@ def test_get_instance_profile__should_throw__when_instance_profile_does_not_exis
 def test_create_role_and_instance_profile():
     conn = boto3.client("iam", region_name="us-east-1")
     conn.create_instance_profile(InstanceProfileName="my-profile", Path="my-path")
+    assume_role_policy_document = {"value": "some policy"}
     conn.create_role(
-        RoleName="my-role", AssumeRolePolicyDocument="some policy", Path="/my-path/"
+        RoleName="my-role",
+        AssumeRolePolicyDocument=json.dumps(assume_role_policy_document),
+        Path="/my-path/",
     )
 
     conn.add_role_to_instance_profile(
@@ -219,7 +222,7 @@ def test_create_role_and_instance_profile():
 
     role = conn.get_role(RoleName="my-role")["Role"]
     assert role["Path"] == "/my-path/"
-    assert role["AssumeRolePolicyDocument"] == "some policy"
+    assert role["AssumeRolePolicyDocument"] == assume_role_policy_document
 
     profile = conn.get_instance_profile(InstanceProfileName="my-profile")[
         "InstanceProfile"
@@ -3209,6 +3212,98 @@ def test_create_role_no_path():
     assert resp["Role"]["Description"] == "test"
 
 
+@mock_aws()
+def test_role_policy_encoding():
+    role_name = "my-role"
+    policy_name = "my-policy"
+    conn = boto3.client("iam", region_name="us-east-1")
+    assume_policy_document = {
+        "Version": "2012-10-17",
+        "Statement": [
+            {
+                "Action": "sts:AssumeRole",
+                "Principal": {"Service": "lambda.amazonaws.com"},
+                "Effect": "Allow",
+                "Condition": {
+                    "StringEquals": {"aws:SourceArn": "arn:aws:test%3Aencoded%3Astring"}
+                },
+            }
+        ],
+    }
+    policy_document = {
+        "Version": "2012-10-17",
+        "Statement": [
+            {
+                "Effect": "Allow",
+                "Action": ["apigatway:PUT"],
+                "Resource": ["arn:aws:test%3Aencoded%3Astring"],
+            }
+        ],
+    }
+    resp = conn.create_role(
+        RoleName=role_name, AssumeRolePolicyDocument=json.dumps(assume_policy_document)
+    )
+    assert resp["Role"]["AssumeRolePolicyDocument"] == assume_policy_document
+    conn.put_role_policy(
+        RoleName=role_name,
+        PolicyName=policy_name,
+        PolicyDocument=json.dumps(policy_document),
+    )
+    resp = conn.get_role_policy(RoleName=role_name, PolicyName=policy_name)
+    assert resp["PolicyDocument"] == policy_document
+
+
+@mock_aws()
+def test_user_policy_encoding():
+    user_name = "my-user"
+    policy_name = "my-policy"
+    conn = boto3.client("iam", region_name="us-east-1")
+
+    policy_document = {
+        "Version": "2012-10-17",
+        "Statement": [
+            {
+                "Effect": "Allow",
+                "Action": ["apigatway:PUT"],
+                "Resource": ["arn:aws:test%3Aencoded%3Astring"],
+            }
+        ],
+    }
+    conn.create_user(UserName=user_name)
+    conn.put_user_policy(
+        UserName=user_name,
+        PolicyName=policy_name,
+        PolicyDocument=json.dumps(policy_document),
+    )
+    resp = conn.get_user_policy(UserName=user_name, PolicyName=policy_name)
+    assert resp["PolicyDocument"] == policy_document
+
+
+@mock_aws()
+def test_group_policy_encoding():
+    group_name = "my-group"
+    policy_name = "my-policy"
+    conn = boto3.client("iam", region_name="us-east-1")
+    policy_document = {
+        "Version": "2012-10-17",
+        "Statement": [
+            {
+                "Effect": "Allow",
+                "Action": ["apigatway:PUT"],
+                "Resource": ["arn:aws:test%3Aencoded%3Astring"],
+            }
+        ],
+    }
+    conn.create_group(GroupName=group_name)
+    conn.put_group_policy(
+        GroupName=group_name,
+        PolicyName=policy_name,
+        PolicyDocument=json.dumps(policy_document),
+    )
+    resp = conn.get_group_policy(GroupName=group_name, PolicyName=policy_name)
+    assert resp["PolicyDocument"] == policy_document
+
+
 @mock_aws()
 @pytest.mark.parametrize(
     "region,partition", [("us-west-2", "aws"), ("cn-north-1", "aws-cn")]
diff --git a/tests/test_pinpoint/test_pinpoint.py b/tests/test_pinpoint/test_pinpoint.py
index bdf4cc042..adea2e753 100644
--- a/tests/test_pinpoint/test_pinpoint.py
+++ b/tests/test_pinpoint/test_pinpoint.py
@@ -17,6 +17,7 @@ def test_create_app():
 
     assert "ApplicationResponse" in resp
     assert "Arn" in resp["ApplicationResponse"]
+    assert "us-east-1" in resp["ApplicationResponse"]["Arn"]
     assert "Id" in resp["ApplicationResponse"]
     assert resp["ApplicationResponse"]["Name"] == "myfirstapp"
     assert "CreationDate" in resp["ApplicationResponse"]
@@ -50,6 +51,7 @@ def test_get_app():
 
     assert "ApplicationResponse" in resp
     assert "Arn" in resp["ApplicationResponse"]
+    assert "eu-west-1" in resp["ApplicationResponse"]["Arn"]
     assert "Id" in resp["ApplicationResponse"]
     assert resp["ApplicationResponse"]["Name"] == "myfirstapp"
     assert "CreationDate" in resp["ApplicationResponse"]
diff --git a/tests/test_secretsmanager/test_secretsmanager.py b/tests/test_secretsmanager/test_secretsmanager.py
index 734fcf384..b98349b4c 100644
--- a/tests/test_secretsmanager/test_secretsmanager.py
+++ b/tests/test_secretsmanager/test_secretsmanager.py
@@ -14,7 +14,9 @@ from freezegun import freeze_time
 from moto import mock_aws, settings
 from moto.core import DEFAULT_ACCOUNT_ID as ACCOUNT_ID
 from moto.secretsmanager.utils import SecretsManagerSecretIdentifier
+from moto.utilities.id_generator import TAG_KEY_CUSTOM_ID
 
+from .. import DEFAULT_ACCOUNT_ID
 from . import secretsmanager_aws_verified
 
 DEFAULT_SECRET_NAME = "test-secret7"
@@ -1968,7 +1970,7 @@ def test_update_secret_version_stage_dont_specify_current_stage(secret_arn=None)
 @pytest.mark.skipif(
     not settings.TEST_DECORATOR_MODE, reason="Can't access the id manager in proxy mode"
 )
-def test_create_secret_custom_id(account_id, set_custom_id):
+def test_create_secret_custom_id(set_custom_id):
     secret_suffix = "randomSuffix"
     secret_name = "secret-name"
     region_name = "us-east-1"
@@ -1976,9 +1978,25 @@ def test_create_secret_custom_id(account_id, set_custom_id):
     client = boto3.client("secretsmanager", region_name=region_name)
 
     set_custom_id(
-        SecretsManagerSecretIdentifier(account_id, region_name, secret_name),
+        SecretsManagerSecretIdentifier(DEFAULT_ACCOUNT_ID, region_name, secret_name),
         secret_suffix,
     )
     secret = client.create_secret(Name=secret_name, SecretString="my secret")
 
     assert secret["ARN"].split(":")[-1] == f"{secret_name}-{secret_suffix}"
+
+
+@mock_aws
+def test_create_secret_with_tag_custom_id(set_custom_id):
+    secret_suffix = "randomSuffix"
+    secret_name = "secret-name"
+
+    client = boto3.client("secretsmanager")
+
+    secret = client.create_secret(
+        Name=secret_name,
+        SecretString="my secret",
+        Tags=[{"Key": TAG_KEY_CUSTOM_ID, "Value": secret_suffix}],
+    )
+
+    assert secret["ARN"].split(":")[-1] == f"{secret_name}-{secret_suffix}"
diff --git a/tests/test_utilities/test_id_generator.py b/tests/test_utilities/test_id_generator.py
index 7ee85fd32..45ff2a043 100644
--- a/tests/test_utilities/test_id_generator.py
+++ b/tests/test_utilities/test_id_generator.py
@@ -12,7 +12,7 @@ REGION = "us-east-1"
 RESOURCE_NAME = "my-resource"
 
 CUSTOM_ID = "custom"
-GENERATED_ID = "generated"
+GENERIC_ID = "generated"
 TAG_ID = "fromTag"
 SERVICE = "test-service"
 RESOURCE = "test-resource"
@@ -24,7 +24,7 @@ def generate_test_id(
     existing_ids: ExistingIds = None,
     tags: Tags = None,
 ):
-    return GENERATED_ID
+    return GENERIC_ID
 
 
 class TestResourceIdentifier(ResourceIdentifier):
@@ -39,7 +39,7 @@ class TestResourceIdentifier(ResourceIdentifier):
 
 def test_generate_with_no_resource_identifier():
     generated_id = generate_test_id(None)
-    assert generated_id == GENERATED_ID
+    assert generated_id == GENERIC_ID
 
 
 def test_generate_with_matching_resource_identifier(set_custom_id):
@@ -58,7 +58,7 @@ def test_generate_with_non_matching_resource_identifier(set_custom_id):
     set_custom_id(resource_identifier, CUSTOM_ID)
 
     generated_id = generate_test_id(resource_identifier=resource_identifier_2)
-    assert generated_id == GENERATED_ID
+    assert generated_id == GENERIC_ID
 
 
 def test_generate_with_custom_id_tag():
@@ -87,7 +87,7 @@ def test_generate_with_existing_id(set_custom_id):
     generated_id = generate_test_id(
         resource_identifier=resource_identifier, existing_ids=[CUSTOM_ID]
     )
-    assert generated_id == GENERATED_ID
+    assert generated_id == GENERIC_ID
 
 
 def test_generate_with_tags_and_existing_id(set_custom_id):
@@ -98,7 +98,7 @@ def test_generate_with_tags_and_existing_id(set_custom_id):
         existing_ids=[TAG_ID],
         tags={TAG_KEY_CUSTOM_ID: TAG_ID},
     )
-    assert generated_id == GENERATED_ID
+    assert generated_id == GENERIC_ID
 
 
 def test_generate_with_tags_fallback(set_custom_id):