Repository: getmoto/moto
Pull request #8567 (2025-02-05 13:27:20)

Admin: Compatibility with MyPy 1.15
MyPy 1.15 got a lot stricter in the rule: `Type of decorated function contains type "Any"`

 - Changed `list_..` methods to return an Object where possible, instead of Dict
 - Change `Dict[str, Any]` to `Dict[str, specific]` where possible
 - Type: ignored everything else
 - Fixed pagination for AppMesh methods

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/getmoto/moto/pull/8567?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 92.65%. Comparing base [(`a57c5c8`)](https://app.codecov.io/gh/getmoto/moto/commit/a57c5c85d4b0d3ecb08dcfc50c615e29aee875b8?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) to head [(`18dbfd1`)](https://app.codecov.io/gh/getmoto/moto/commit/18dbfd1b8d7322b0551b8774037892ed31f2626e?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto).
> Report is 11 commits behind head on master.

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


```diff
@@            Coverage Diff             @@
##           master    #8567      +/-   ##
==========================================
+ Coverage   92.63%   92.65%   +0.02%     
==========================================
  Files        1229     1231       +2     
  Lines      106437   106778     +341     
==========================================
+ Hits        98594    98936     +342     
+ Misses       7843     7842       -1     
```

| [Flag](https://app.codecov.io/gh/getmoto/moto/pull/8567/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/8567/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) | `28.07% <30.95%> (+0.15%)` | :arrow_up: |
| [unittests](https://app.codecov.io/gh/getmoto/moto/pull/8567/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) | `92.62% <100.00%> (+0.02%)` | :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/8567?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/appmesh/models.py b/moto/appmesh/models.py
index 800720129..1ef675b6e 100644
--- a/moto/appmesh/models.py
+++ b/moto/appmesh/models.py
@@ -1,6 +1,6 @@
 """AppMeshBackend class with methods for supported APIs."""
 
-from typing import Any, Dict, List, Literal, Optional, Union
+from typing import Dict, List, Literal, Optional, Union
 
 from moto.appmesh.dataclasses.mesh import (
     Mesh,
@@ -35,7 +35,7 @@ from moto.utilities.paginator import paginate
 PAGINATION_MODEL = {
     "list_meshes": {
         "input_token": "next_token",
-        "limit_key": "max_results",
+        "limit_key": "limit",
         "limit_default": 100,
         "unique_attribute": "meshName",
     },
@@ -55,13 +55,13 @@ PAGINATION_MODEL = {
         "input_token": "next_token",
         "limit_key": "limit",
         "limit_default": 100,
-        "unique_attribute": ["routeName"],
+        "unique_attribute": "route_name",
     },
     "list_virtual_nodes": {
         "input_token": "next_token",
         "limit_key": "limit",
         "limit_default": 100,
-        "unique_attribute": ["virtualNodeName"],
+        "unique_attribute": "virtual_node_name",
     },
 }
 
@@ -249,8 +249,8 @@ class AppMeshBackend(BaseBackend):
         del self.meshes[mesh_name]
         return mesh
 
-    @paginate(pagination_model=PAGINATION_MODEL)  # type: ignore
-    def list_meshes(self, limit: Optional[int], next_token: Optional[str]):
+    @paginate(pagination_model=PAGINATION_MODEL)
+    def list_meshes(self) -> List[Dict[str, Union[str, int]]]:
         return [
             {
                 "arn": mesh.metadata.arn,
@@ -284,7 +284,7 @@ class AppMeshBackend(BaseBackend):
         raise ResourceNotFoundError(resource_arn)
 
     @paginate(pagination_model=PAGINATION_MODEL)  # type: ignore
-    def list_tags_for_resource(self, limit: int, next_token: str, resource_arn: str):
+    def list_tags_for_resource(self, resource_arn: str) -> List[Dict[str, str]]:
         return self._get_resource_with_arn(resource_arn=resource_arn).tags
 
     def tag_resource(self, resource_arn: str, tags: List[Dict[str, str]]) -> None:
@@ -375,10 +375,10 @@ class AppMeshBackend(BaseBackend):
         del mesh.virtual_routers[virtual_router_name]
         return virtual_router
 
-    @paginate(pagination_model=PAGINATION_MODEL)  # type: ignore
+    @paginate(pagination_model=PAGINATION_MODEL)
     def list_virtual_routers(
-        self, limit: int, mesh_name: str, mesh_owner: Optional[str], next_token: str
-    ):
+        self, mesh_name: str, mesh_owner: Optional[str]
+    ) -> List[Dict[str, Union[str, int]]]:
         self._validate_mesh(mesh_name=mesh_name, mesh_owner=mesh_owner)
         return [
             {
@@ -507,25 +507,20 @@ class AppMeshBackend(BaseBackend):
         )
         return route
 
-    @paginate(pagination_model=PAGINATION_MODEL)  # type: ignore
+    @paginate(pagination_model=PAGINATION_MODEL)
     def list_routes(
         self,
-        limit: Optional[int],
         mesh_name: str,
         mesh_owner: Optional[str],
-        next_token: Optional[str],
         virtual_router_name: str,
-    ) -> List[Dict[str, Any]]:
+    ) -> List[RouteMetadata]:
         self._check_router_validity(
             mesh_name=mesh_name,
             mesh_owner=mesh_owner,
             virtual_router_name=virtual_router_name,
         )
         virtual_router = self.meshes[mesh_name].virtual_routers[virtual_router_name]
-        return [
-            route.metadata.formatted_for_list_api()
-            for route in virtual_router.routes.values()
-        ]
+        return [route.metadata for route in virtual_router.routes.values()]
 
     def describe_virtual_node(
         self, mesh_name: str, mesh_owner: Optional[str], virtual_node_name: str
@@ -602,20 +597,15 @@ class AppMeshBackend(BaseBackend):
         del self.meshes[mesh_name].virtual_nodes[virtual_node_name]
         return virtual_node
 
-    @paginate(pagination_model=PAGINATION_MODEL)  # type: ignore
+    @paginate(pagination_model=PAGINATION_MODEL)
     def list_virtual_nodes(
         self,
-        limit: Optional[int],
         mesh_name: str,
         mesh_owner: Optional[str],
-        next_token: Optional[str],
-    ) -> List[Dict[str, Any]]:
+    ) -> List[VirtualNodeMetadata]:
         self._validate_mesh(mesh_name=mesh_name, mesh_owner=mesh_owner)
         virtual_nodes = self.meshes[mesh_name].virtual_nodes
-        return [
-            virtual_node.metadata.formatted_for_list_api()
-            for virtual_node in virtual_nodes.values()
-        ]
+        return [virtual_node.metadata for virtual_node in virtual_nodes.values()]
 
 
 appmesh_backends = BackendDict(AppMeshBackend, "appmesh")
diff --git a/moto/appmesh/responses.py b/moto/appmesh/responses.py
index 86448dbe1..9b77ae27b 100644
--- a/moto/appmesh/responses.py
+++ b/moto/appmesh/responses.py
@@ -71,7 +71,7 @@ class AppMeshResponse(BaseResponse):
 
     def list_meshes(self) -> str:
         params = self._get_params()
-        limit = params.get("limit")
+        limit = self._get_int_param("limit")
         next_token = params.get("nextToken")
         meshes, next_token = self.appmesh_backend.list_meshes(
             limit=limit,
@@ -81,7 +81,7 @@ class AppMeshResponse(BaseResponse):
 
     def list_tags_for_resource(self) -> str:
         params = self._get_params()
-        limit = params.get("limit")
+        limit = self._get_int_param("limit")
         next_token = params.get("nextToken")
         resource_arn = params.get("resourceArn")
         tags, next_token = self.appmesh_backend.list_tags_for_resource(
@@ -158,7 +158,7 @@ class AppMeshResponse(BaseResponse):
         return json.dumps(virtual_router.to_dict())
 
     def list_virtual_routers(self) -> str:
-        limit = self._get_param("limit")
+        limit = self._get_int_param("limit")
         mesh_name = self._get_param("meshName")
         mesh_owner = self._get_param("meshOwner")
         next_token = self._get_param("nextToken")
@@ -235,7 +235,7 @@ class AppMeshResponse(BaseResponse):
         return json.dumps(route.to_dict())
 
     def list_routes(self) -> str:
-        limit = self._get_param("limit")
+        limit = self._get_int_param("limit")
         mesh_name = self._get_param("meshName")
         mesh_owner = self._get_param("meshOwner")
         next_token = self._get_param("nextToken")
@@ -247,7 +247,12 @@ class AppMeshResponse(BaseResponse):
             next_token=next_token,
             virtual_router_name=virtual_router_name,
         )
-        return json.dumps(dict(nextToken=next_token, routes=routes))
+        return json.dumps(
+            dict(
+                nextToken=next_token,
+                routes=[r.formatted_for_list_api() for r in routes],
+            )
+        )
 
     def describe_virtual_node(self) -> str:
         mesh_name = self._get_param("meshName")
@@ -306,7 +311,7 @@ class AppMeshResponse(BaseResponse):
         return json.dumps(virtual_node.to_dict())
 
     def list_virtual_nodes(self) -> str:
-        limit = self._get_param("limit")
+        limit = self._get_int_param("limit")
         mesh_name = self._get_param("meshName")
         mesh_owner = self._get_param("meshOwner")
         next_token = self._get_param("nextToken")
@@ -316,4 +321,9 @@ class AppMeshResponse(BaseResponse):
             mesh_owner=mesh_owner,
             next_token=next_token,
         )
-        return json.dumps(dict(nextToken=next_token, virtualNodes=virtual_nodes))
+        return json.dumps(
+            dict(
+                nextToken=next_token,
+                virtualNodes=[n.formatted_for_list_api() for n in virtual_nodes],
+            )
+        )
diff --git a/moto/bedrock/models.py b/moto/bedrock/models.py
index 4ace313a9..f44655605 100644
--- a/moto/bedrock/models.py
+++ b/moto/bedrock/models.py
@@ -191,13 +191,13 @@ class BedrockBackend(BaseBackend):
             "input_token": "next_token",
             "limit_key": "max_results",
             "limit_default": 100,
-            "unique_attribute": "jobArn",
+            "unique_attribute": "job_arn",
         },
         "list_custom_models": {
             "input_token": "next_token",
             "limit_key": "max_results",
             "limit_default": 100,
-            "unique_attribute": "modelArn",
+            "unique_attribute": "model_arn",
         },
     }
 
@@ -301,18 +301,16 @@ class BedrockBackend(BaseBackend):
             )
         return
 
-    @paginate(pagination_model=PAGINATION_MODEL)  # type: ignore
+    @paginate(pagination_model=PAGINATION_MODEL)
     def list_model_customization_jobs(
         self,
         creation_time_after: Optional[datetime],
         creation_time_before: Optional[datetime],
         status_equals: Optional[str],
         name_contains: Optional[str],
-        max_results: Optional[int],
-        next_token: Optional[str],
         sort_by: Optional[str],
         sort_order: Optional[str],
-    ) -> List[Any]:
+    ) -> List[ModelCustomizationJob]:
         customization_jobs_fetched = list(self.model_customization_jobs.values())
 
         if name_contains is not None:
@@ -363,23 +361,7 @@ class BedrockBackend(BaseBackend):
             else:
                 raise ValidationException(f"Invalid sort by field: {sort_by}")
 
-        model_customization_job_summaries = []
-        for model in customization_jobs_fetched:
-            model_customization_job_summaries.append(
-                {
-                    "jobArn": model.job_arn,
-                    "baseModelArn": model.base_model_arn,
-                    "jobName": model.job_name,
-                    "status": model.status,
-                    "lastModifiedTime": model.last_modified_time,
-                    "creationTime": model.creation_time,
-                    "endTime": model.end_time,
-                    "customModelArn": model.output_model_arn,
-                    "customModelName": model.custom_model_name,
-                    "customizationType": model.customization_type,
-                }
-            )
-        return model_customization_job_summaries
+        return customization_jobs_fetched
 
     def get_model_invocation_logging_configuration(self) -> Optional[Dict[str, Any]]:
         if self.model_invocation_logging_configuration:
@@ -418,7 +400,7 @@ class BedrockBackend(BaseBackend):
             )
         return
 
-    @paginate(pagination_model=PAGINATION_MODEL)  # type: ignore
+    @paginate(pagination_model=PAGINATION_MODEL)
     def list_custom_models(
         self,
         creation_time_before: Optional[datetime],
@@ -426,11 +408,9 @@ class BedrockBackend(BaseBackend):
         name_contains: Optional[str],
         base_model_arn_equals: Optional[str],
         foundation_model_arn_equals: Optional[str],
-        max_results: Optional[int],
-        next_token: Optional[str],
         sort_by: Optional[str],
         sort_order: Optional[str],
-    ) -> List[Any]:
+    ) -> List[CustomModel]:
         """
         The foundation_model_arn_equals-argument is not yet supported
         """
@@ -483,20 +463,7 @@ class BedrockBackend(BaseBackend):
                     raise ValidationException(f"Invalid sort order: {sort_order}")
             else:
                 raise ValidationException(f"Invalid sort by field: {sort_by}")
-        model_summaries = []
-        for model in custom_models_fetched:
-            model_summaries.append(
-                {
-                    "modelArn": model.model_arn,
-                    "modelName": model.model_name,
-                    "creationTime": model.creation_time,
-                    "baseModelArn": model.base_model_arn,
-                    "baseModelName": model.base_model_name,
-                    "jobArn": model.job_arn,
-                    "customizationType": model.customization_type,
-                }
-            )
-        return model_summaries
+        return custom_models_fetched
 
     def tag_resource(self, resource_arn: str, tags: List[Dict[str, str]]) -> None:
         if resource_arn not in self._list_arns():
diff --git a/moto/bedrock/responses.py b/moto/bedrock/responses.py
index c6adda635..a76bd4ae8 100644
--- a/moto/bedrock/responses.py
+++ b/moto/bedrock/responses.py
@@ -133,7 +133,19 @@ class BedrockResponse(BaseResponse):
             sort_by=sort_by,
             sort_order=sort_order,
         )
-        return json.dumps(dict(nextToken=next_token, modelSummaries=model_summaries))
+        summaries = [
+            {
+                "modelArn": model.model_arn,
+                "modelName": model.model_name,
+                "creationTime": model.creation_time,
+                "baseModelArn": model.base_model_arn,
+                "baseModelName": model.base_model_name,
+                "jobArn": model.job_arn,
+                "customizationType": model.customization_type,
+            }
+            for model in model_summaries
+        ]
+        return json.dumps(dict(nextToken=next_token, modelSummaries=summaries))
 
     def list_model_customization_jobs(self) -> str:
         params = self._get_params()
@@ -141,16 +153,12 @@ class BedrockResponse(BaseResponse):
         creation_time_before = params.get("creationTimeBefore")
         status_equals = params.get("statusEquals")
         name_contains = params.get("nameContains")
-        max_results = params.get("maxResults")
+        max_results = self._get_int_param("maxResults")
         next_token = params.get("nextToken")
         sort_by = params.get("sortBy")
         sort_order = params.get("sortOrder")
 
-        max_results = int(max_results) if max_results else None
-        (
-            model_customization_job_summaries,
-            next_token,
-        ) = self.bedrock_backend.list_model_customization_jobs(
+        jobs, next_token = self.bedrock_backend.list_model_customization_jobs(
             creation_time_after=creation_time_after,
             creation_time_before=creation_time_before,
             status_equals=status_equals,
@@ -160,10 +168,25 @@ class BedrockResponse(BaseResponse):
             sort_by=sort_by,
             sort_order=sort_order,
         )
+        job_summaries = [
+            {
+                "jobArn": job.job_arn,
+                "baseModelArn": job.base_model_arn,
+                "jobName": job.job_name,
+                "status": job.status,
+                "lastModifiedTime": job.last_modified_time,
+                "creationTime": job.creation_time,
+                "endTime": job.end_time,
+                "customModelArn": job.output_model_arn,
+                "customModelName": job.custom_model_name,
+                "customizationType": job.customization_type,
+            }
+            for job in jobs
+        ]
         return json.dumps(
             dict(
                 nextToken=next_token,
-                modelCustomizationJobSummaries=model_customization_job_summaries,
+                modelCustomizationJobSummaries=job_summaries,
             )
         )
 
diff --git a/moto/bedrockagent/models.py b/moto/bedrockagent/models.py
index e9c36eddf..d3b9cc28b 100644
--- a/moto/bedrockagent/models.py
+++ b/moto/bedrockagent/models.py
@@ -166,13 +166,13 @@ class AgentsforBedrockBackend(BaseBackend):
             "input_token": "next_token",
             "limit_key": "max_results",
             "limit_default": 100,
-            "unique_attribute": "agentId",
+            "unique_attribute": "agent_id",
         },
         "list_knowledge_bases": {
             "input_token": "next_token",
             "limit_key": "max_results",
             "limit_default": 100,
-            "unique_attribute": "knowledgeBaseId",
+            "unique_attribute": "knowledge_base_id",
         },
     }
 
@@ -224,12 +224,9 @@ class AgentsforBedrockBackend(BaseBackend):
             raise ResourceNotFoundException(f"Agent {agent_id} not found")
         return self.agents[agent_id]
 
-    @paginate(pagination_model=PAGINATION_MODEL)  # type: ignore
-    def list_agents(
-        self, max_results: Optional[int], next_token: Optional[str]
-    ) -> List[Any]:
-        agent_summaries = [agent.dict_summary() for agent in self.agents.values()]
-        return agent_summaries
+    @paginate(pagination_model=PAGINATION_MODEL)
+    def list_agents(self) -> List[Agent]:
+        return [agent for agent in self.agents.values()]
 
     def delete_agent(
         self, agent_id: str, skip_resource_in_use_check: Optional[bool]
@@ -273,15 +270,9 @@ class AgentsforBedrockBackend(BaseBackend):
             self.tag_resource(knowledge_base.knowledge_base_arn, tags)
         return knowledge_base
 
-    @paginate(pagination_model=PAGINATION_MODEL)  # type: ignore
-    def list_knowledge_bases(
-        self, max_results: Optional[int], next_token: Optional[str]
-    ) -> List[Any]:
-        knowledge_base_summaries = [
-            knowledge_base.dict_summary()
-            for knowledge_base in self.knowledge_bases.values()
-        ]
-        return knowledge_base_summaries
+    @paginate(pagination_model=PAGINATION_MODEL)
+    def list_knowledge_bases(self) -> List[KnowledgeBase]:
+        return [knowledge_base for knowledge_base in self.knowledge_bases.values()]
 
     def delete_knowledge_base(self, knowledge_base_id: str) -> Tuple[str, str]:
         if knowledge_base_id in self.knowledge_bases:
diff --git a/moto/bedrockagent/responses.py b/moto/bedrockagent/responses.py
index 7d3bada0e..111ab66d4 100644
--- a/moto/bedrockagent/responses.py
+++ b/moto/bedrockagent/responses.py
@@ -61,7 +61,7 @@ class AgentsforBedrockResponse(BaseResponse):
         )
         return json.dumps(
             {
-                "agentSummaries": agents,
+                "agentSummaries": [a.dict_summary() for a in agents],
                 "nextToken": next_token,
             }
         )
@@ -106,7 +106,7 @@ class AgentsforBedrockResponse(BaseResponse):
         )
         return json.dumps(
             {
-                "knowledgeBaseSummaries": knowledge_bases,
+                "knowledgeBaseSummaries": [kb.dict_summary() for kb in knowledge_bases],
                 "nextToken": next_token,
             }
         )
diff --git a/moto/core/responses.py b/moto/core/responses.py
index 054bcdf97..edd26019b 100644
--- a/moto/core/responses.py
+++ b/moto/core/responses.py
@@ -303,7 +303,7 @@ class BaseResponse(_TemplateEnvironmentMixin, ActionAuthenticatorMixin):
         """
 
         @functools.wraps(to_call)  # type: ignore
-        def _inner(request: Any, full_url: str, headers: Any) -> TYPE_RESPONSE:
+        def _inner(request: Any, full_url: str, headers: Any) -> TYPE_RESPONSE:  # type: ignore[misc]
             response = getattr(cls(), to_call.__name__)(request, full_url, headers)
             if isinstance(response, str):
                 status = 200
diff --git a/moto/identitystore/models.py b/moto/identitystore/models.py
index 170cffcb1..011710459 100644
--- a/moto/identitystore/models.py
+++ b/moto/identitystore/models.py
@@ -260,7 +260,7 @@ class IdentityStoreBackend(BaseBackend):
         return membership_id, identity_store_id
 
     @paginate(pagination_model=PAGINATION_MODEL)  # type: ignore
-    def list_group_memberships(
+    def list_group_memberships(  # type: ignore[misc]
         self, identity_store_id: str, group_id: str
     ) -> List[Any]:
         identity_store = self.__get_identity_store(identity_store_id)
@@ -271,8 +271,8 @@ class IdentityStoreBackend(BaseBackend):
             if m["GroupId"] == group_id
         ]
 
-    @paginate(pagination_model=PAGINATION_MODEL)  # type: ignore
-    def list_group_memberships_for_member(
+    @paginate(pagination_model=PAGINATION_MODEL)  # type: ignore[misc]
+    def list_group_memberships_for_member(  # type: ignore[misc]
         self, identity_store_id: str, member_id: Dict[str, str]
     ) -> List[Any]:
         identity_store = self.__get_identity_store(identity_store_id)
@@ -284,22 +284,22 @@ class IdentityStoreBackend(BaseBackend):
             if m["MemberId"]["UserId"] == user_id
         ]
 
-    @paginate(pagination_model=PAGINATION_MODEL)  # type: ignore
+    @paginate(pagination_model=PAGINATION_MODEL)
     def list_groups(
         self, identity_store_id: str, filters: List[Dict[str, str]]
-    ) -> List[Dict[str, Any]]:
+    ) -> List[Group]:
         identity_store = self.__get_identity_store(identity_store_id)
 
         if filters:
             if filters[0].get("AttributePath") == "DisplayName":
                 displayname = filters[0].get("AttributeValue")
                 return [
-                    m._asdict()
+                    m
                     for m in identity_store.groups.values()
                     if m.DisplayName == displayname
                 ]
 
-        return [m._asdict() for m in identity_store.groups.values()]
+        return [m for m in identity_store.groups.values()]
 
     @paginate(pagination_model=PAGINATION_MODEL)
     def list_users(
diff --git a/moto/identitystore/responses.py b/moto/identitystore/responses.py
index dd3ed4afd..db057d5fb 100644
--- a/moto/identitystore/responses.py
+++ b/moto/identitystore/responses.py
@@ -172,7 +172,9 @@ class IdentityStoreResponse(BaseResponse):
             filters=filters,
         )
 
-        return json.dumps(dict(Groups=groups, NextToken=next_token))
+        return json.dumps(
+            dict(Groups=[g._asdict() for g in groups], NextToken=next_token)
+        )
 
     def describe_group(self) -> str:
         identity_store_id = self._get_param("IdentityStoreId")
diff --git a/moto/iot/models.py b/moto/iot/models.py
index 9d4c05553..af1587164 100644
--- a/moto/iot/models.py
+++ b/moto/iot/models.py
@@ -6,7 +6,17 @@ from collections import OrderedDict
 from dataclasses import asdict, dataclass, field, replace
 from datetime import datetime, timedelta
 from json import JSONDecodeError
-from typing import TYPE_CHECKING, Any, Dict, Iterable, List, Optional, Pattern, Tuple
+from typing import (
+    TYPE_CHECKING,
+    Any,
+    Dict,
+    Iterable,
+    List,
+    Optional,
+    Pattern,
+    Tuple,
+    Union,
+)
 
 from cryptography import x509
 from cryptography.hazmat._oid import NameOID
@@ -827,7 +837,7 @@ class FakeJobTemplate(CloudFormationModel):
         self.timeout_config = timeout_config
         self.created_at = time.mktime(datetime(2015, 1, 1).timetuple())
 
-    def to_dict(self) -> Dict[str, Any]:
+    def to_dict(self) -> Dict[str, Union[str, float]]:
         return {
             "jobTemplateArn": self.job_template_arn,
             "jobTemplateId": self.job_template_id,
@@ -2304,11 +2314,11 @@ class IoTBackend(BaseBackend):
         return self.jobs[job_id]
 
     @paginate(PAGINATION_MODEL)  # type: ignore[misc]
-    def list_jobs(self) -> List[Dict[str, Any]]:
+    def list_jobs(self) -> List[FakeJob]:
         """
         The following parameter are not yet implemented: Status, TargetSelection, ThingGroupName, ThingGroupId
         """
-        return [_.to_dict() for _ in self.jobs.values()]
+        return [_ for _ in self.jobs.values()]
 
     def describe_job_execution(
         self, job_id: str, thing_name: str, execution_number: int
@@ -2364,57 +2374,26 @@ class IoTBackend(BaseBackend):
         else:
             raise InvalidStateTransitionException()
 
+    @paginate(PAGINATION_MODEL)
     def list_job_executions_for_job(
-        self, job_id: str, status: str, max_results: int, token: Optional[str]
-    ) -> Tuple[List[Dict[str, Any]], Optional[str]]:
-        job_executions = [
-            self.job_executions[je].to_dict()
-            for je in self.job_executions
-            if je[0] == job_id
+        self, job_id: str, status: str
+    ) -> List[FakeJobExecution]:
+        return [
+            job_exec
+            for (_id, _), job_exec in self.job_executions.items()
+            if _id == job_id and (not status or job_exec.status == status)
         ]
 
-        if status is not None:
-            job_executions = list(
-                filter(
-                    lambda elem: elem["jobExecutionSummary"].get("status") == status,
-                    job_executions,
-                )
-            )
-
-        if token is None:
-            job_executions = job_executions[0:max_results]
-            next_token = str(max_results) if len(job_executions) > max_results else None
-        else:
-            int_token = int(token)
-            job_executions = job_executions[int_token : int_token + max_results]
-            next_token = (
-                str(int_token + max_results)
-                if len(job_executions) > int_token + max_results
-                else None
-            )
-
-        return job_executions, next_token
-
     @paginate(PAGINATION_MODEL)  # type: ignore[misc]
     def list_job_executions_for_thing(
         self, thing_name: str, status: Optional[str]
-    ) -> List[Dict[str, Any]]:
-        job_executions = [
-            self.job_executions[je].to_dict()
-            for je in self.job_executions
-            if je[1] == thing_name
+    ) -> List[FakeJobExecution]:
+        return [
+            job_exec
+            for (_, name), job_exec in self.job_executions.items()
+            if name == thing_name and (not status or job_exec.status == status)
         ]
 
-        if status is not None:
-            job_executions = list(
-                filter(
-                    lambda elem: elem["jobExecutionSummary"].get("status") == status,
-                    job_executions,
-                )
-            )
-
-        return job_executions
-
     def list_topic_rules(self) -> List[Dict[str, Any]]:
         return [r.to_dict() for r in self.rules.values()]
 
@@ -2643,7 +2622,7 @@ class IoTBackend(BaseBackend):
         return job_template
 
     @paginate(PAGINATION_MODEL)  # type: ignore[misc]
-    def list_job_templates(self) -> List[Dict[str, Any]]:
+    def list_job_templates(self) -> List[Dict[str, Union[str, float]]]:
         return [_.to_dict() for _ in self.jobs_templates.values()]
 
     def delete_job_template(self, job_template_id: str) -> None:
diff --git a/moto/iot/responses.py b/moto/iot/responses.py
index 32ea5c6ea..6478eb382 100644
--- a/moto/iot/responses.py
+++ b/moto/iot/responses.py
@@ -231,7 +231,9 @@ class IoTResponse(BaseResponse):
             max_results=max_results, next_token=previous_next_token
         )
 
-        return json.dumps(dict(jobs=jobs, nextToken=next_token))
+        return json.dumps(
+            dict(jobs=[job.to_dict() for job in jobs], nextToken=next_token)
+        )
 
     def describe_job_execution(self) -> str:
         job_id = self._get_param("jobId")
@@ -280,7 +282,12 @@ class IoTResponse(BaseResponse):
             job_id=job_id, status=status, max_results=max_results, token=next_token
         )
 
-        return json.dumps(dict(executionSummaries=job_executions, nextToken=next_token))
+        return json.dumps(
+            dict(
+                executionSummaries=[je.to_dict() for je in job_executions],
+                nextToken=next_token,
+            )
+        )
 
     def list_job_executions_for_thing(self) -> str:
         thing_name = self._get_param("thingName")
@@ -296,7 +303,12 @@ class IoTResponse(BaseResponse):
             next_token=next_token,
         )
 
-        return json.dumps(dict(executionSummaries=job_executions, nextToken=next_token))
+        return json.dumps(
+            dict(
+                executionSummaries=[je.to_dict() for je in job_executions],
+                nextToken=next_token,
+            )
+        )
 
     def create_keys_and_certificate(self) -> str:
         set_as_active = self._get_bool_param("setAsActive")
diff --git a/moto/iot/utils.py b/moto/iot/utils.py
index fb71f639a..7e29fcf18 100644
--- a/moto/iot/utils.py
+++ b/moto/iot/utils.py
@@ -1,11 +1,17 @@
 from typing import Any
 
 PAGINATION_MODEL = {
+    "list_job_executions_for_job": {
+        "input_token": "token",
+        "limit_key": "max_results",
+        "limit_default": 100,
+        "unique_attribute": ["job_id", "thing_arn"],
+    },
     "list_job_executions_for_thing": {
         "input_token": "next_token",
         "limit_key": "max_results",
         "limit_default": 100,
-        "unique_attribute": "jobId",
+        "unique_attribute": "job_id",
     },
     "list_job_templates": {
         "input_token": "next_token",
diff --git a/moto/ivs/models.py b/moto/ivs/models.py
index f5a430f10..2b986c010 100644
--- a/moto/ivs/models.py
+++ b/moto/ivs/models.py
@@ -63,7 +63,7 @@ class IVSBackend(BaseBackend):
         return channel, stream_key
 
     @paginate(pagination_model=PAGINATION_MODEL)  # type: ignore[misc]
-    def list_channels(
+    def list_channels(  # type: ignore[misc]
         self,
         filter_by_name: Optional[str],
         filter_by_recording_configuration_arn: Optional[str],
diff --git a/moto/kinesis/models.py b/moto/kinesis/models.py
index f3492006b..f897ee898 100644
--- a/moto/kinesis/models.py
+++ b/moto/kinesis/models.py
@@ -808,13 +808,12 @@ class KinesisBackend(BaseBackend):
 
         return current_shard_count
 
-    @paginate(pagination_model=PAGINATION_MODEL)  # type: ignore[misc]
+    @paginate(pagination_model=PAGINATION_MODEL)
     def list_shards(
         self, stream_arn: Optional[str], stream_name: Optional[str]
-    ) -> List[Dict[str, Any]]:
+    ) -> List[Shard]:
         stream = self.describe_stream(stream_arn=stream_arn, stream_name=stream_name)
-        shards = sorted(stream.shards.values(), key=lambda x: x.shard_id)
-        return [shard.to_json() for shard in shards]
+        return sorted(stream.shards.values(), key=lambda x: x.shard_id)
 
     def increase_stream_retention_period(
         self,
diff --git a/moto/kinesis/responses.py b/moto/kinesis/responses.py
index e4d46410a..b1e09813c 100644
--- a/moto/kinesis/responses.py
+++ b/moto/kinesis/responses.py
@@ -164,7 +164,7 @@ class KinesisResponse(BaseResponse):
             limit=max_results,
             next_token=next_token,
         )
-        res = {"Shards": shards, "NextToken": token}
+        res = {"Shards": [s.to_json() for s in shards], "NextToken": token}
         return json.dumps(res)
 
     def update_shard_count(self) -> str:
diff --git a/moto/kinesis/utils.py b/moto/kinesis/utils.py
index 92f03b90a..60271cd5c 100644
--- a/moto/kinesis/utils.py
+++ b/moto/kinesis/utils.py
@@ -13,18 +13,7 @@ PAGINATION_MODEL = {
         "input_token": "next_token",
         "limit_key": "limit",
         "limit_default": 10000,
-        "unique_attribute": "ShardId",
-        "fail_on_invalid_token": False,
-    },
-}
-
-
-PAGINATION_MODEL = {
-    "list_shards": {
-        "input_token": "next_token",
-        "limit_key": "limit",
-        "limit_default": 10000,
-        "unique_attribute": "ShardId",
+        "unique_attribute": "shard_id",
         "fail_on_invalid_token": False,
     },
 }
diff --git a/moto/logs/models.py b/moto/logs/models.py
index 82cb82c7e..018b2fc55 100644
--- a/moto/logs/models.py
+++ b/moto/logs/models.py
@@ -802,18 +802,16 @@ class LogsBackend(BaseBackend):
             raise ResourceNotFoundException()
         del self.groups[log_group_name]
 
-    @paginate(pagination_model=PAGINATION_MODEL)  # type: ignore[misc]
+    @paginate(pagination_model=PAGINATION_MODEL)
     def describe_log_groups(
         self, log_group_name_prefix: Optional[str] = None
-    ) -> List[Dict[str, Any]]:
+    ) -> List[LogGroup]:
         groups = [
-            group.to_describe_dict()
+            group
             for name, group in self.groups.items()
             if name.startswith(log_group_name_prefix or "")
         ]
-        groups = sorted(groups, key=lambda x: x["logGroupName"])
-
-        return groups
+        return sorted(groups, key=lambda x: x.name)
 
     def get_destination(self, destination_name: str) -> Destination:
         for destination in self.destinations:
diff --git a/moto/logs/responses.py b/moto/logs/responses.py
index 44e7ffb06..b6522f4d7 100644
--- a/moto/logs/responses.py
+++ b/moto/logs/responses.py
@@ -178,7 +178,7 @@ class LogsResponse(BaseResponse):
             log_group_name_prefix=log_group_name_prefix,
             next_token=next_token,
         )
-        result = {"logGroups": groups}
+        result = {"logGroups": [g.to_describe_dict() for g in groups]}
         if next_token:
             result["nextToken"] = next_token
         return json.dumps(result)
diff --git a/moto/medialive/models.py b/moto/medialive/models.py
index e09777634..812063be5 100644
--- a/moto/medialive/models.py
+++ b/moto/medialive/models.py
@@ -173,12 +173,9 @@ class MediaLiveBackend(BaseBackend):
         self._channels[channel_id] = channel
         return channel
 
-    @paginate(pagination_model=PAGINATION_MODEL)  # type: ignore[misc]
-    def list_channels(self) -> List[Dict[str, Any]]:
-        channels = list(self._channels.values())
-        return [
-            c.to_dict(exclude=["encoderSettings", "pipelineDetails"]) for c in channels
-        ]
+    @paginate(pagination_model=PAGINATION_MODEL)
+    def list_channels(self) -> List[Channel]:
+        return [c for c in self._channels.values()]
 
     def describe_channel(self, channel_id: str) -> Channel:
         channel = self._channels[channel_id]
@@ -267,10 +264,9 @@ class MediaLiveBackend(BaseBackend):
         a_input._resolve_transient_states()
         return a_input
 
-    @paginate(PAGINATION_MODEL)  # type: ignore[misc]
-    def list_inputs(self) -> List[Dict[str, Any]]:
-        inputs = list(self._inputs.values())
-        return [i.to_dict() for i in inputs]
+    @paginate(PAGINATION_MODEL)
+    def list_inputs(self) -> List[Input]:
+        return [i for i in self._inputs.values()]
 
     def delete_input(self, input_id: str) -> None:
         a_input = self._inputs[input_id]
diff --git a/moto/medialive/responses.py b/moto/medialive/responses.py
index 2356bfa70..8fccb2f45 100644
--- a/moto/medialive/responses.py
+++ b/moto/medialive/responses.py
@@ -47,8 +47,11 @@ class MediaLiveResponse(BaseResponse):
         channels, next_token = self.medialive_backend.list_channels(
             max_results=max_results, next_token=next_token
         )
+        channel_dicts = [
+            c.to_dict(exclude=["encoderSettings", "pipelineDetails"]) for c in channels
+        ]
 
-        return json.dumps(dict(channels=channels, nextToken=next_token))
+        return json.dumps(dict(channels=channel_dicts, nextToken=next_token))
 
     def describe_channel(self) -> str:
         channel_id = self._get_param("channelId")
@@ -128,7 +131,9 @@ class MediaLiveResponse(BaseResponse):
             max_results=max_results, next_token=next_token
         )
 
-        return json.dumps(dict(inputs=inputs, nextToken=next_token))
+        return json.dumps(
+            dict(inputs=[i.to_dict() for i in inputs], nextToken=next_token)
+        )
 
     def delete_input(self) -> str:
         input_id = self._get_param("inputId")
diff --git a/moto/organizations/models.py b/moto/organizations/models.py
index 3f036076a..cb7057efa 100644
--- a/moto/organizations/models.py
+++ b/moto/organizations/models.py
@@ -537,16 +537,12 @@ class OrganizationsBackend(BaseBackend):
         ou = self.get_organizational_unit_by_id(kwargs["OrganizationalUnitId"])
         return ou.describe()
 
-    @paginate(pagination_model=PAGINATION_MODEL)  # type: ignore[misc]
+    @paginate(pagination_model=PAGINATION_MODEL)
     def list_organizational_units_for_parent(
-        self, **kwargs: Any
-    ) -> List[Dict[str, Any]]:
-        parent_id = self.validate_parent_id(kwargs["parent_id"])
-        return [
-            {"Id": ou.id, "Arn": ou.arn, "Name": ou.name}
-            for ou in self.ou
-            if ou.parent_id == parent_id
-        ]
+        self, parent_id: str
+    ) -> List[FakeOrganizationalUnit]:
+        parent_id = self.validate_parent_id(parent_id)
+        return [ou for ou in self.ou if ou.parent_id == parent_id]
 
     def create_account(self, **kwargs: Any) -> Dict[str, Any]:
         if self.org is None:
@@ -629,15 +625,13 @@ class OrganizationsBackend(BaseBackend):
         accounts = [account.describe() for account in self.accounts]
         return sorted(accounts, key=lambda x: x["JoinedTimestamp"])  # type: ignore
 
-    @paginate(pagination_model=PAGINATION_MODEL)  # type: ignore[misc]
-    def list_accounts_for_parent(self, **kwargs: Any) -> Any:
-        parent_id = self.validate_parent_id(kwargs["parent_id"])
+    @paginate(pagination_model=PAGINATION_MODEL)
+    def list_accounts_for_parent(self, parent_id: str) -> List[FakeAccount]:
+        parent_id = self.validate_parent_id(parent_id)
         accounts = [
-            account.describe()
-            for account in self.accounts
-            if account.parent_id == parent_id
+            account for account in self.accounts if account.parent_id == parent_id
         ]
-        return sorted(accounts, key=lambda x: x["JoinedTimestamp"])
+        return sorted(accounts, key=lambda x: x.create_time)
 
     def move_account(self, **kwargs: Any) -> None:
         new_parent_id = self.validate_parent_id(kwargs["DestinationParentId"])
diff --git a/moto/organizations/responses.py b/moto/organizations/responses.py
index 60b345661..17f6f9987 100644
--- a/moto/organizations/responses.py
+++ b/moto/organizations/responses.py
@@ -72,7 +72,11 @@ class OrganizationsResponse(BaseResponse):
         ) = self.organizations_backend.list_organizational_units_for_parent(
             max_results=max_results, next_token=next_token, parent_id=parent_id
         )
-        response = {"OrganizationalUnits": ous}
+        response = {
+            "OrganizationalUnits": [
+                {"Id": ou.id, "Arn": ou.arn, "Name": ou.name} for ou in ous
+            ]
+        }
         if next_token:
             response["NextToken"] = next_token
         return json.dumps(response)
@@ -126,7 +130,7 @@ class OrganizationsResponse(BaseResponse):
         accounts, next_token = self.organizations_backend.list_accounts_for_parent(
             max_results=max_results, next_token=next_token, parent_id=parent_id
         )
-        response = {"Accounts": accounts}
+        response = {"Accounts": [a.describe() for a in accounts]}
         if next_token:
             response["NextToken"] = next_token
         return json.dumps(response)
diff --git a/moto/organizations/utils.py b/moto/organizations/utils.py
index 903449c88..75560c3ec 100644
--- a/moto/organizations/utils.py
+++ b/moto/organizations/utils.py
@@ -45,14 +45,14 @@ PAGINATION_MODEL = {
         "limit_key": "max_results",
         "limit_default": 20,
         "result_key": "Accounts",
-        "unique_attribute": "JoinedTimestamp",
+        "unique_attribute": "id",
     },
     "list_organizational_units_for_parent": {
         "input_token": "next_token",
         "limit_key": "max_results",
         "limit_default": 20,
         "result_key": "OrganizationalUnits",
-        "unique_attribute": "Id",
+        "unique_attribute": "id",
     },
 }
 
diff --git a/moto/ram/models.py b/moto/ram/models.py
index 98060fc24..da26c41e0 100644
--- a/moto/ram/models.py
+++ b/moto/ram/models.py
@@ -94,7 +94,7 @@ class ResourceShare(BaseModel):
                     ) = self.organizations_backend.list_organizational_units_for_parent(
                         parent_id=root_id
                     )
-                    if any(principal == ou["Arn"] for ou in ous):
+                    if any(principal == ou.arn for ou in ous):
                         continue
 
                 raise UnknownResourceException(
diff --git a/moto/route53/models.py b/moto/route53/models.py
index dc8e4e21e..e5e41eb1d 100644
--- a/moto/route53/models.py
+++ b/moto/route53/models.py
@@ -915,7 +915,7 @@ class Route53Backend(BaseBackend):
 
         log_groups = logs_backends[self.account_id][region].describe_log_groups()
         for entry in log_groups[0] if log_groups else []:
-            if log_group_arn == entry["arn"]:
+            if log_group_arn == f"{entry.arn}:*":
                 break
         else:
             # There is no CloudWatch Logs log group with the specified ARN.
diff --git a/moto/route53resolver/models.py b/moto/route53resolver/models.py
index 15eaf64c2..bcb0dc716 100644
--- a/moto/route53resolver/models.py
+++ b/moto/route53resolver/models.py
@@ -278,7 +278,7 @@ class ResolverEndpoint(BaseModel):  # pylint: disable=too-many-instance-attribut
             "ModificationTime": self.modification_time,
         }
 
-    def ip_descriptions(self) -> List[Dict[str, Any]]:
+    def ip_descriptions(self) -> List[Dict[str, str]]:
         """Return a list of dicts describing resolver endpoint IP addresses."""
         description = []
         for subnet_id, ip_info in self.subnets.items():
@@ -755,7 +755,7 @@ class Route53ResolverBackend(BaseBackend):
     @paginate(pagination_model=PAGINATION_MODEL)  # type: ignore[misc]
     def list_resolver_endpoint_ip_addresses(
         self, resolver_endpoint_id: str
-    ) -> List[Dict[str, Any]]:
+    ) -> List[Dict[str, str]]:
         self._validate_resolver_endpoint_id(resolver_endpoint_id)
         endpoint = self.resolver_endpoints[resolver_endpoint_id]
         return endpoint.ip_descriptions()
diff --git a/moto/s3/models.py b/moto/s3/models.py
index d50c2b9c7..c34e30c57 100644
--- a/moto/s3/models.py
+++ b/moto/s3/models.py
@@ -197,7 +197,7 @@ class FakeKey(BaseModel, ManagedState):
         return f"arn:{self.partition}:s3:::{self.bucket_name}/{self.name}/{self.version_id}"
 
     @value.setter  # type: ignore
-    def value(self, new_value: bytes) -> None:
+    def value(self, new_value: bytes) -> None:  # type: ignore[misc]
         self._value_buffer.seek(0)
         self._value_buffer.truncate()
 
diff --git a/moto/ssoadmin/models.py b/moto/ssoadmin/models.py
index c28187d03..f80933016 100644
--- a/moto/ssoadmin/models.py
+++ b/moto/ssoadmin/models.py
@@ -246,10 +246,10 @@ class SSOAdminBackend(BaseBackend):
             f"Policy does not exist with ARN: {managed_policy_arn}"
         )
 
-    @paginate(PAGINATION_MODEL)  # type: ignore[misc]
+    @paginate(PAGINATION_MODEL)
     def list_account_assignments(
         self, instance_arn: str, account_id: str, permission_set_arn: str
-    ) -> List[Dict[str, Any]]:
+    ) -> List[Dict[str, str]]:
         account_assignments = []
         for assignment in self.account_assignments:
             if (
@@ -267,14 +267,14 @@ class SSOAdminBackend(BaseBackend):
                 )
         return account_assignments
 
-    @paginate(PAGINATION_MODEL)  # type: ignore[misc]
+    @paginate(PAGINATION_MODEL)
     def list_account_assignments_for_principal(
         self,
         filter_: Dict[str, Any],
         instance_arn: str,
         principal_id: str,
         principal_type: str,
-    ) -> List[Dict[str, Any]]:
+    ) -> List[Dict[str, str]]:
         return [
             {
                 "AccountId": account_assignment.target_id,
diff --git a/moto/utilities/paginator.py b/moto/utilities/paginator.py
index 54f80ba46..fc2776ca7 100644
--- a/moto/utilities/paginator.py
+++ b/moto/utilities/paginator.py
@@ -153,6 +153,7 @@ class Paginator:
             )
             if not str(curr_val) == predicate_values[index]:
                 return False
+
         return True
 
     def _build_next_token(self, next_item: Any) -> str:
diff --git a/moto/wafv2/models.py b/moto/wafv2/models.py
index b704f1635..adb62eb20 100644
--- a/moto/wafv2/models.py
+++ b/moto/wafv2/models.py
@@ -61,7 +61,7 @@ PAGINATION_MODEL = {
         "input_token": "next_marker",
         "limit_key": "limit",
         "limit_default": 100,
-        "unique_attribute": "ARN",
+        "unique_attribute": "arn",
     },
 }
 
@@ -514,15 +514,15 @@ class WAFV2Backend(BaseBackend):
         raise WAFNonexistentItemException
 
     @paginate(PAGINATION_MODEL)  # type: ignore
-    def list_web_acls(self) -> List[Dict[str, Any]]:
-        return [wacl.to_short_dict() for wacl in self.wacls.values()]
+    def list_web_acls(self) -> List[FakeWebACL]:
+        return [wacl for wacl in self.wacls.values()]
 
     def _is_duplicate_name(self, name: str) -> bool:
         all_wacl_names = set(wacl.name for wacl in self.wacls.values())
         return name in all_wacl_names
 
     @paginate(PAGINATION_MODEL)  # type: ignore
-    def list_rule_groups(self, scope: str) -> List[Any]:
+    def list_rule_groups(self, scope: str) -> List[FakeRuleGroup]:
         rule_groups = [
             group for group in self.rule_groups.values() if group.scope == scope
         ]
diff --git a/moto/wafv2/responses.py b/moto/wafv2/responses.py
index c00812708..1b5d6ac46 100644
--- a/moto/wafv2/responses.py
+++ b/moto/wafv2/responses.py
@@ -102,7 +102,10 @@ class WAFV2Response(BaseResponse):
         web_acls, next_marker = self.wafv2_backend.list_web_acls(
             limit=limit, next_marker=next_marker
         )
-        response = {"NextMarker": next_marker, "WebACLs": web_acls}
+        response = {
+            "NextMarker": next_marker,
+            "WebACLs": [web.to_short_dict() for web in web_acls],
+        }
         response_headers = {"Content-Type": "application/json"}
         return 200, response_headers, json.dumps(response)
 


Test patch:
diff --git a/tests/test_appmesh/test_appmesh.py b/tests/test_appmesh/test_appmesh.py
index ec7f37587..c9eb6b98d 100644
--- a/tests/test_appmesh/test_appmesh.py
+++ b/tests/test_appmesh/test_appmesh.py
@@ -127,6 +127,28 @@ def test_create_list_update_describe_delete_mesh(client):
     assert err["Message"] == "There are no meshes with the name mesh2."
 
 
+@mock_aws
+def test_list_meshes_paginated(client):
+    for i in range(5):
+        client.create_mesh(
+            meshName=f"mesh{i}",
+            spec={
+                "egressFilter": {"type": "DROP_ALL"},
+                "serviceDiscovery": {"ipPreference": "IPv4_ONLY"},
+            },
+            tags=[{"key": "owner", "value": "moto"}],
+        )
+
+    all_meshes = client.list_meshes()["meshes"]
+    assert len(all_meshes) == 5
+
+    page1 = client.list_meshes(limit=2)
+    assert len(page1["meshes"]) == 2
+
+    page2 = client.list_meshes(nextToken=page1["nextToken"])
+    assert len(page2["meshes"]) == 3
+
+
 @mock_aws
 def test_create_describe_list_update_delete_virtual_router(client):
     connection = client.create_mesh(
@@ -242,6 +264,43 @@ def test_create_describe_list_update_delete_virtual_router(client):
     )
 
 
+@mock_aws
+def test_list_virtual_routers_paginated(client):
+    mesh = client.create_mesh(
+        meshName=MESH_NAME,
+        spec={
+            "egressFilter": {"type": "DROP_ALL"},
+            "serviceDiscovery": {"ipPreference": "IPv4_ONLY"},
+        },
+        tags=[{"key": "owner", "value": "moto"}],
+    )["mesh"]
+    mesh_owner = mesh["metadata"]["meshOwner"]
+
+    for i in range(5):
+        client.create_virtual_router(
+            meshName=MESH_NAME,
+            meshOwner=mesh_owner,
+            virtualRouterName=f"router{i}",
+            spec={"listeners": [{"portMapping": {"port": 80, "protocol": "http"}}]},
+            tags=[{"key": "router_traffic", "value": "http"}],
+        )
+
+    all_routers = client.list_virtual_routers(meshName=MESH_NAME, meshOwner=mesh_owner)[
+        "virtualRouters"
+    ]
+    assert len(all_routers) == 5
+
+    page1 = client.list_virtual_routers(
+        meshName=MESH_NAME, meshOwner=mesh_owner, limit=2
+    )
+    assert len(page1["virtualRouters"]) == 2
+
+    page2 = client.list_virtual_routers(
+        meshName=MESH_NAME, meshOwner=mesh_owner, nextToken=page1["nextToken"]
+    )
+    assert len(page2["virtualRouters"]) == 3
+
+
 @mock_aws
 def test_create_describe_list_update_delete_route(client):
     ROUTER_NAME = "mock_virtual_router"
@@ -588,6 +647,54 @@ def test_create_describe_list_update_delete_route(client):
     )
 
 
+@mock_aws
+def test_list_routes_paginated(client):
+    ROUTER_NAME = "mock_virtual_router"
+
+    mesh = client.create_mesh(
+        meshName=MESH_NAME,
+        spec={
+            "egressFilter": {"type": "DROP_ALL"},
+            "serviceDiscovery": {"ipPreference": "IPv4_ONLY"},
+        },
+        tags=[{"key": "owner", "value": "moto"}],
+    )["mesh"]
+    mesh_owner = mesh["metadata"]["meshOwner"]
+
+    client.create_virtual_router(
+        meshName=MESH_NAME,
+        meshOwner=mesh_owner,
+        virtualRouterName=ROUTER_NAME,
+        spec={"listeners": [{"portMapping": {"port": 80, "protocol": "http"}}]},
+        tags=[{"key": "router_traffic", "value": "http"}],
+    )
+
+    for idx, license in enumerate(["apache", "mit", "mpl", "bsd"]):
+        client.create_route(
+            meshOwner=mesh_owner,
+            meshName=MESH_NAME,
+            virtualRouterName=ROUTER_NAME,
+            routeName=f"route{idx}",
+            tags=[{"key": "license", "value": license}],
+            spec=grpc_route_spec,
+        )
+
+    all_routes = client.list_routes(meshName=MESH_NAME, virtualRouterName=ROUTER_NAME)[
+        "routes"
+    ]
+    assert len(all_routes) == 4
+
+    page1 = client.list_routes(
+        meshName=MESH_NAME, virtualRouterName=ROUTER_NAME, limit=2
+    )
+    assert len(page1["routes"]) == 2
+
+    page2 = client.list_routes(
+        meshName=MESH_NAME, virtualRouterName=ROUTER_NAME, nextToken=page1["nextToken"]
+    )
+    assert len(page2["routes"]) == 2
+
+
 @mock_aws
 def test_create_describe_list_update_delete_virtual_node(client):
     connection = client.create_mesh(
@@ -1208,6 +1315,41 @@ def test_create_describe_list_update_delete_virtual_node(client):
     )
 
 
+@mock_aws
+def test_list_virtual_nodes_paginated(client):
+    mesh = client.create_mesh(
+        meshName=MESH_NAME,
+        spec={
+            "egressFilter": {"type": "DROP_ALL"},
+            "serviceDiscovery": {"ipPreference": "IPv4_ONLY"},
+        },
+        tags=[{"key": "owner", "value": "moto"}],
+    )["mesh"]
+    mesh_owner = mesh["metadata"]["meshOwner"]
+
+    for i in range(5):
+        client.create_virtual_node(
+            meshName=MESH_NAME,
+            meshOwner=mesh_owner,
+            spec=grpc_virtual_node_spec,
+            tags=[{"key": "type", "value": "grpc"}],
+            virtualNodeName=f"node{i}",
+        )
+
+    all_nodes = client.list_virtual_nodes(meshName=MESH_NAME, meshOwner=mesh_owner)[
+        "virtualNodes"
+    ]
+    assert len(all_nodes) == 5
+
+    page1 = client.list_virtual_nodes(meshName=MESH_NAME, meshOwner=mesh_owner, limit=2)
+    assert len(page1["virtualNodes"]) == 2
+
+    page2 = client.list_virtual_nodes(
+        meshName=MESH_NAME, meshOwner=mesh_owner, nextToken=page1["nextToken"]
+    )
+    assert len(page2["virtualNodes"]) == 3
+
+
 @mock_aws
 def test_tag_and_list_tags_for_resource(client):
     # create resources
@@ -1286,9 +1428,18 @@ def test_tag_and_list_tags_for_resource(client):
 
     client.tag_resource(
         resourceArn=virtual_node_arn,
-        tags=[{"key": "organization", "value": "how_moto_got_its_mojo_back"}],
+        tags=[{"key": "k2", "value": "v2"}, {"key": "k3", "value": "v3"}],
     )
     connection = client.list_tags_for_resource(resourceArn=virtual_node_arn)
     tags = connection["tags"]
     assert tags[0] == {"key": "type", "value": "http"}
-    assert tags[1] == {"key": "organization", "value": "how_moto_got_its_mojo_back"}
+    assert tags[1] == {"key": "k2", "value": "v2"}
+    assert tags[2] == {"key": "k3", "value": "v3"}
+
+    page1 = client.list_tags_for_resource(resourceArn=virtual_node_arn, limit=1)
+    assert page1["tags"] == [{"key": "type", "value": "http"}]
+
+    page2 = client.list_tags_for_resource(
+        resourceArn=virtual_node_arn, nextToken=page1["nextToken"]
+    )
+    assert page2["tags"] == [{"key": "k2", "value": "v2"}, {"key": "k3", "value": "v3"}]
diff --git a/tests/test_identitystore/test_identitystore.py b/tests/test_identitystore/test_identitystore.py
index bac5ef428..07299ec5d 100644
--- a/tests/test_identitystore/test_identitystore.py
+++ b/tests/test_identitystore/test_identitystore.py
@@ -417,8 +417,8 @@ def test_list_groups():
     identity_store_id = get_identity_store_id()
 
     start = 0
-    end = 266
-    batch_size = 100
+    end = 20
+    batch_size = 3
     next_token = None
 
     expected_groups = list()
@@ -509,8 +509,8 @@ def test_list_group_memberships():
     identity_store_id = get_identity_store_id()
 
     start = 0
-    end = 5000
-    batch_size = 321
+    end = 20
+    batch_size = 3
     next_token = None
     membership_ids = []
 
diff --git a/tests/test_iot/test_iot_job_executions.py b/tests/test_iot/test_iot_job_executions.py
index 13df3302c..27f906d7b 100644
--- a/tests/test_iot/test_iot_job_executions.py
+++ b/tests/test_iot/test_iot_job_executions.py
@@ -149,19 +149,18 @@ def test_delete_job_execution():
 @mock_aws
 def test_list_job_executions_for_job():
     client = boto3.client("iot", region_name="eu-west-1")
-    name = "my-thing"
     job_id = "TestJob"
     # thing
-    thing = client.create_thing(thingName=name)
-    assert thing["thingName"] == name
-    assert "thingArn" in thing
+    thing_arns = [
+        client.create_thing(thingName=f"mything_{i}")["thingArn"] for i in range(10)
+    ]
 
     # job document
     job_document = {"field": "value"}
 
     job = client.create_job(
         jobId=job_id,
-        targets=[thing["thingArn"]],
+        targets=thing_arns,
         document=json.dumps(job_document),
         description="Description",
         presignedUrlConfig={
@@ -176,11 +175,24 @@ def test_list_job_executions_for_job():
     assert "jobArn" in job
     assert "description" in job
 
-    job_execution = client.list_job_executions_for_job(jobId=job_id)
-    assert job_execution["executionSummaries"][0]["thingArn"] == thing["thingArn"]
+    job_executions = client.list_job_executions_for_job(jobId=job_id)[
+        "executionSummaries"
+    ]
+    assert len(job_executions) == 10
+    assert job_executions[0]["thingArn"] in thing_arns
+
+    job_executions = client.list_job_executions_for_job(jobId=job_id, status="QUEUED")[
+        "executionSummaries"
+    ]
+    assert len(job_executions) == 10
 
-    job_execution = client.list_job_executions_for_job(jobId=job_id, status="QUEUED")
-    assert job_execution["executionSummaries"][0]["thingArn"] == thing["thingArn"]
+    page1 = client.list_job_executions_for_job(jobId=job_id, maxResults=4)
+    assert len(page1["executionSummaries"]) == 4
+
+    page2 = client.list_job_executions_for_job(
+        jobId=job_id, nextToken=page1["nextToken"]
+    )
+    assert len(page2["executionSummaries"]) == 6
 
 
 @mock_aws