Repository: getmoto/moto
Pull request #8230 (2024-10-14 22:26:22)

Custom id tag support
# Motivation

As a follow up to #8216, This pr aims at expanding support of custom ids to resources that can be created with tags. Providing a tag named `_custom_ids` at creation can be used to create a custom id for the created resource.

# Changes

- Fixed `get_id_from_tags` method to by able to accept tags created as dict (e.g `{"_custom_id_": "myCustomId"}`) or as a list (e.g. `[ {"Key": "_custom_id_", "Value": "myCustomId"} ]`)
- Added tests for apigw and secrets manager to showcase an example of each type

# TODO

- [ ] Rebase on master, currently based on #8231 to allow for ci to run


codecov[bot]: ## [Codecov](https://app.codecov.io/gh/getmoto/moto/pull/8230?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 [(`7f14f9b`)](https://app.codecov.io/gh/getmoto/moto/commit/7f14f9b4be6f4fec6a6a5e199c45c780bb708334?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) to head [(`c766095`)](https://app.codecov.io/gh/getmoto/moto/commit/c7660953d47919460f8e97415ba82e4bcd07b3f6?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto).

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


```diff
@@           Coverage Diff           @@
##           master    #8230   +/-   ##
=======================================
  Coverage   94.54%   94.54%           
=======================================
  Files        1158     1158           
  Lines      100089   100096    +7     
=======================================
+ Hits        94629    94636    +7     
  Misses       5460     5460           
```

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