Repository: getmoto/moto
Pull request #7683 (2024-05-11 18:24:29)

DynamoDB: Update PyPartiqlParser version
`py-partiql-parser` 0.5.5 contains performance improvements for S3 files that contains many documents, useful when using the `import_table` feature.

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/getmoto/moto/pull/7683?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.32%. Comparing base [(`fa52c54`)](https://app.codecov.io/gh/getmoto/moto/commit/fa52c542f6b1070103c53e725713706ef63f1a88?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) to head [(`993e4ab`)](https://app.codecov.io/gh/getmoto/moto/pull/7683?dropdown=coverage&src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto).
> Report is 5 commits behind head on master.


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


```diff
@@            Coverage Diff             @@
##           master    #7683      +/-   ##
==========================================
+ Coverage   94.29%   94.32%   +0.02%     
==========================================
  Files        1087     1087              
  Lines       92397    92560     +163     
==========================================
+ Hits        87127    87303     +176     
+ Misses       5270     5257      -13     
```

| [Flag](https://app.codecov.io/gh/getmoto/moto/pull/7683/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/7683/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) | `29.42% <0.00%> (-0.03%)` | :arrow_down: |
| [unittests](https://app.codecov.io/gh/getmoto/moto/pull/7683/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) | `94.29% <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/7683?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/IMPLEMENTATION_COVERAGE.md b/IMPLEMENTATION_COVERAGE.md
index 2bd7ccbcb..efd1c542f 100644
--- a/IMPLEMENTATION_COVERAGE.md
+++ b/IMPLEMENTATION_COVERAGE.md
@@ -706,27 +706,37 @@
 
 ## bedrock
 <details>
-<summary>65% implemented</summary>
+<summary>43% implemented</summary>
 
+- [ ] create_evaluation_job
+- [ ] create_guardrail
+- [ ] create_guardrail_version
 - [X] create_model_customization_job
 - [ ] create_provisioned_model_throughput
 - [X] delete_custom_model
+- [ ] delete_guardrail
 - [X] delete_model_invocation_logging_configuration
 - [ ] delete_provisioned_model_throughput
 - [X] get_custom_model
+- [ ] get_evaluation_job
 - [ ] get_foundation_model
+- [ ] get_guardrail
 - [X] get_model_customization_job
 - [X] get_model_invocation_logging_configuration
 - [ ] get_provisioned_model_throughput
 - [X] list_custom_models
+- [ ] list_evaluation_jobs
 - [ ] list_foundation_models
+- [ ] list_guardrails
 - [X] list_model_customization_jobs
 - [ ] list_provisioned_model_throughputs
 - [X] list_tags_for_resource
 - [X] put_model_invocation_logging_configuration
+- [ ] stop_evaluation_job
 - [X] stop_model_customization_job
 - [X] tag_resource
 - [X] untag_resource
+- [ ] update_guardrail
 - [ ] update_provisioned_model_throughput
 </details>
 
@@ -1334,6 +1344,7 @@
 - [ ] put_webhook
 - [ ] register_webhook_with_third_party
 - [ ] retry_stage_execution
+- [ ] rollback_stage
 - [ ] start_pipeline_execution
 - [ ] stop_pipeline_execution
 - [X] tag_resource
@@ -2554,6 +2565,7 @@
 - [ ] get_host_reservation_purchase_preview
 - [ ] get_image_block_public_access_state
 - [ ] get_instance_metadata_defaults
+- [ ] get_instance_tpm_ek_pub
 - [ ] get_instance_types_from_instance_requirements
 - [ ] get_instance_uefi_data
 - [ ] get_ipam_address_history
@@ -3257,11 +3269,12 @@
 
 ## emr-containers
 <details>
-<summary>40% implemented</summary>
+<summary>34% implemented</summary>
 
 - [X] cancel_job_run
 - [ ] create_job_template
 - [ ] create_managed_endpoint
+- [ ] create_security_configuration
 - [X] create_virtual_cluster
 - [ ] delete_job_template
 - [ ] delete_managed_endpoint
@@ -3269,11 +3282,13 @@
 - [X] describe_job_run
 - [ ] describe_job_template
 - [ ] describe_managed_endpoint
+- [ ] describe_security_configuration
 - [X] describe_virtual_cluster
 - [ ] get_managed_endpoint_session_credentials
 - [X] list_job_runs
 - [ ] list_job_templates
 - [ ] list_managed_endpoints
+- [ ] list_security_configurations
 - [ ] list_tags_for_resource
 - [X] list_virtual_clusters
 - [X] start_job_run
@@ -3283,19 +3298,19 @@
 
 ## emr-serverless
 <details>
-<summary>46% implemented</summary>
+<summary>73% implemented</summary>
 
-- [ ] cancel_job_run
+- [X] cancel_job_run
 - [X] create_application
 - [X] delete_application
 - [X] get_application
 - [ ] get_dashboard_for_job_run
-- [ ] get_job_run
+- [X] get_job_run
 - [X] list_applications
-- [ ] list_job_runs
+- [X] list_job_runs
 - [ ] list_tags_for_resource
 - [X] start_application
-- [ ] start_job_run
+- [X] start_job_run
 - [X] stop_application
 - [ ] tag_resource
 - [ ] untag_resource
@@ -4069,7 +4084,7 @@
 - [ ] set_security_token_service_preferences
 - [ ] simulate_custom_policy
 - [ ] simulate_principal_policy
-- [ ] tag_instance_profile
+- [X] tag_instance_profile
 - [ ] tag_mfa_device
 - [X] tag_open_id_connect_provider
 - [X] tag_policy
@@ -4077,7 +4092,7 @@
 - [ ] tag_saml_provider
 - [ ] tag_server_certificate
 - [X] tag_user
-- [ ] untag_instance_profile
+- [X] untag_instance_profile
 - [ ] untag_mfa_device
 - [X] untag_open_id_connect_provider
 - [X] untag_policy
@@ -5487,6 +5502,7 @@
 - [ ] create_batch_inference_job
 - [ ] create_batch_segment_job
 - [ ] create_campaign
+- [ ] create_data_deletion_job
 - [ ] create_dataset
 - [ ] create_dataset_export_job
 - [ ] create_dataset_group
@@ -5511,6 +5527,7 @@
 - [ ] describe_batch_inference_job
 - [ ] describe_batch_segment_job
 - [ ] describe_campaign
+- [ ] describe_data_deletion_job
 - [ ] describe_dataset
 - [ ] describe_dataset_export_job
 - [ ] describe_dataset_group
@@ -5528,6 +5545,7 @@
 - [ ] list_batch_inference_jobs
 - [ ] list_batch_segment_jobs
 - [ ] list_campaigns
+- [ ] list_data_deletion_jobs
 - [ ] list_dataset_export_jobs
 - [ ] list_dataset_groups
 - [ ] list_dataset_import_jobs
@@ -5860,6 +5878,7 @@
 - [ ] update_public_sharing_settings
 - [ ] update_refresh_schedule
 - [ ] update_role_custom_permission
+- [ ] update_spice_capacity_configuration
 - [ ] update_template
 - [ ] update_template_alias
 - [ ] update_template_permissions
@@ -6789,7 +6808,7 @@
 
 ## s3control
 <details>
-<summary>9% implemented</summary>
+<summary>10% implemented</summary>
 
 - [ ] associate_access_grants_identity_center
 - [ ] create_access_grant
@@ -6864,7 +6883,7 @@
 - [ ] list_tags_for_resource
 - [ ] put_access_grants_instance_resource_policy
 - [ ] put_access_point_configuration_for_object_lambda
-- [ ] put_access_point_policy
+- [X] put_access_point_policy
 - [ ] put_access_point_policy_for_object_lambda
 - [ ] put_bucket_lifecycle_configuration
 - [ ] put_bucket_policy
@@ -7674,6 +7693,7 @@
 - [ ] describe_instance_patch_states
 - [ ] describe_instance_patch_states_for_patch_group
 - [ ] describe_instance_patches
+- [ ] describe_instance_properties
 - [ ] describe_inventory_deletions
 - [ ] describe_maintenance_window_execution_task_invocations
 - [ ] describe_maintenance_window_execution_tasks
@@ -7772,7 +7792,7 @@
 
 ## sso-admin
 <details>
-<summary>24% implemented</summary>
+<summary>27% implemented</summary>
 
 - [X] attach_customer_managed_policy_reference_to_permission_set
 - [X] attach_managed_policy_to_permission_set
@@ -7795,8 +7815,8 @@
 - [X] delete_permission_set
 - [ ] delete_permissions_boundary_from_permission_set
 - [ ] delete_trusted_token_issuer
-- [ ] describe_account_assignment_creation_status
-- [ ] describe_account_assignment_deletion_status
+- [X] describe_account_assignment_creation_status
+- [X] describe_account_assignment_deletion_status
 - [ ] describe_application
 - [ ] describe_application_assignment
 - [ ] describe_application_provider
@@ -7851,7 +7871,7 @@
 
 ## stepfunctions
 <details>
-<summary>55% implemented</summary>
+<summary>54% implemented</summary>
 
 - [ ] create_activity
 - [X] create_state_machine
@@ -7889,6 +7909,7 @@
 - [X] update_map_run
 - [X] update_state_machine
 - [ ] update_state_machine_alias
+- [ ] validate_state_machine_definition
 </details>
 
 ## sts
diff --git a/docs/docs/services/bedrock.rst b/docs/docs/services/bedrock.rst
index e86d0faf0..f9f907a8e 100644
--- a/docs/docs/services/bedrock.rst
+++ b/docs/docs/services/bedrock.rst
@@ -16,13 +16,19 @@ bedrock
 
 |start-h3| Implemented features for this service |end-h3|
 
+- [ ] create_evaluation_job
+- [ ] create_guardrail
+- [ ] create_guardrail_version
 - [X] create_model_customization_job
 - [ ] create_provisioned_model_throughput
 - [X] delete_custom_model
+- [ ] delete_guardrail
 - [X] delete_model_invocation_logging_configuration
 - [ ] delete_provisioned_model_throughput
 - [X] get_custom_model
+- [ ] get_evaluation_job
 - [ ] get_foundation_model
+- [ ] get_guardrail
 - [X] get_model_customization_job
 - [X] get_model_invocation_logging_configuration
 - [ ] get_provisioned_model_throughput
@@ -31,13 +37,17 @@ bedrock
         The foundation_model_arn_equals-argument is not yet supported
         
 
+- [ ] list_evaluation_jobs
 - [ ] list_foundation_models
+- [ ] list_guardrails
 - [X] list_model_customization_jobs
 - [ ] list_provisioned_model_throughputs
 - [X] list_tags_for_resource
 - [X] put_model_invocation_logging_configuration
+- [ ] stop_evaluation_job
 - [X] stop_model_customization_job
 - [X] tag_resource
 - [X] untag_resource
+- [ ] update_guardrail
 - [ ] update_provisioned_model_throughput
 
diff --git a/docs/docs/services/codepipeline.rst b/docs/docs/services/codepipeline.rst
index 9247933f1..4a53c967e 100644
--- a/docs/docs/services/codepipeline.rst
+++ b/docs/docs/services/codepipeline.rst
@@ -47,6 +47,7 @@ codepipeline
 - [ ] put_webhook
 - [ ] register_webhook_with_third_party
 - [ ] retry_stage_execution
+- [ ] rollback_stage
 - [ ] start_pipeline_execution
 - [ ] stop_pipeline_execution
 - [X] tag_resource
diff --git a/docs/docs/services/ec2.rst b/docs/docs/services/ec2.rst
index e6c917d32..e5745efcb 100644
--- a/docs/docs/services/ec2.rst
+++ b/docs/docs/services/ec2.rst
@@ -495,6 +495,7 @@ ec2
 - [ ] get_host_reservation_purchase_preview
 - [ ] get_image_block_public_access_state
 - [ ] get_instance_metadata_defaults
+- [ ] get_instance_tpm_ek_pub
 - [ ] get_instance_types_from_instance_requirements
 - [ ] get_instance_uefi_data
 - [ ] get_ipam_address_history
diff --git a/docs/docs/services/emr-containers.rst b/docs/docs/services/emr-containers.rst
index 779f0d27f..888b7cc84 100644
--- a/docs/docs/services/emr-containers.rst
+++ b/docs/docs/services/emr-containers.rst
@@ -19,6 +19,7 @@ emr-containers
 - [X] cancel_job_run
 - [ ] create_job_template
 - [ ] create_managed_endpoint
+- [ ] create_security_configuration
 - [X] create_virtual_cluster
 - [ ] delete_job_template
 - [ ] delete_managed_endpoint
@@ -26,11 +27,13 @@ emr-containers
 - [X] describe_job_run
 - [ ] describe_job_template
 - [ ] describe_managed_endpoint
+- [ ] describe_security_configuration
 - [X] describe_virtual_cluster
 - [ ] get_managed_endpoint_session_credentials
 - [X] list_job_runs
 - [ ] list_job_templates
 - [ ] list_managed_endpoints
+- [ ] list_security_configurations
 - [ ] list_tags_for_resource
 - [X] list_virtual_clusters
 - [X] start_job_run
diff --git a/docs/docs/services/emr-serverless.rst b/docs/docs/services/emr-serverless.rst
index 4513d1743..ac63cc902 100644
--- a/docs/docs/services/emr-serverless.rst
+++ b/docs/docs/services/emr-serverless.rst
@@ -16,17 +16,17 @@ emr-serverless
 
 |start-h3| Implemented features for this service |end-h3|
 
-- [ ] cancel_job_run
+- [X] cancel_job_run
 - [X] create_application
 - [X] delete_application
 - [X] get_application
 - [ ] get_dashboard_for_job_run
-- [ ] get_job_run
+- [X] get_job_run
 - [X] list_applications
-- [ ] list_job_runs
+- [X] list_job_runs
 - [ ] list_tags_for_resource
 - [X] start_application
-- [ ] start_job_run
+- [X] start_job_run
 - [X] stop_application
 - [ ] tag_resource
 - [ ] untag_resource
diff --git a/docs/docs/services/iam.rst b/docs/docs/services/iam.rst
index 86655948c..424f977be 100644
--- a/docs/docs/services/iam.rst
+++ b/docs/docs/services/iam.rst
@@ -164,7 +164,7 @@ iam
 - [ ] set_security_token_service_preferences
 - [ ] simulate_custom_policy
 - [ ] simulate_principal_policy
-- [ ] tag_instance_profile
+- [X] tag_instance_profile
 - [ ] tag_mfa_device
 - [X] tag_open_id_connect_provider
 - [X] tag_policy
@@ -172,7 +172,7 @@ iam
 - [ ] tag_saml_provider
 - [ ] tag_server_certificate
 - [X] tag_user
-- [ ] untag_instance_profile
+- [X] untag_instance_profile
 - [ ] untag_mfa_device
 - [X] untag_open_id_connect_provider
 - [X] untag_policy
diff --git a/docs/docs/services/personalize.rst b/docs/docs/services/personalize.rst
index 2a07c5726..756a25694 100644
--- a/docs/docs/services/personalize.rst
+++ b/docs/docs/services/personalize.rst
@@ -19,6 +19,7 @@ personalize
 - [ ] create_batch_inference_job
 - [ ] create_batch_segment_job
 - [ ] create_campaign
+- [ ] create_data_deletion_job
 - [ ] create_dataset
 - [ ] create_dataset_export_job
 - [ ] create_dataset_group
@@ -43,6 +44,7 @@ personalize
 - [ ] describe_batch_inference_job
 - [ ] describe_batch_segment_job
 - [ ] describe_campaign
+- [ ] describe_data_deletion_job
 - [ ] describe_dataset
 - [ ] describe_dataset_export_job
 - [ ] describe_dataset_group
@@ -60,6 +62,7 @@ personalize
 - [ ] list_batch_inference_jobs
 - [ ] list_batch_segment_jobs
 - [ ] list_campaigns
+- [ ] list_data_deletion_jobs
 - [ ] list_dataset_export_jobs
 - [ ] list_dataset_groups
 - [ ] list_dataset_import_jobs
diff --git a/docs/docs/services/quicksight.rst b/docs/docs/services/quicksight.rst
index 15ff6cd9c..67f3aaed2 100644
--- a/docs/docs/services/quicksight.rst
+++ b/docs/docs/services/quicksight.rst
@@ -193,6 +193,7 @@ quicksight
 - [ ] update_public_sharing_settings
 - [ ] update_refresh_schedule
 - [ ] update_role_custom_permission
+- [ ] update_spice_capacity_configuration
 - [ ] update_template
 - [ ] update_template_alias
 - [ ] update_template_permissions
diff --git a/docs/docs/services/s3control.rst b/docs/docs/services/s3control.rst
index c5476c1cb..281c64ec9 100644
--- a/docs/docs/services/s3control.rst
+++ b/docs/docs/services/s3control.rst
@@ -91,7 +91,7 @@ s3control
 - [ ] list_tags_for_resource
 - [ ] put_access_grants_instance_resource_policy
 - [ ] put_access_point_configuration_for_object_lambda
-- [ ] put_access_point_policy
+- [X] put_access_point_policy
 - [ ] put_access_point_policy_for_object_lambda
 - [ ] put_bucket_lifecycle_configuration
 - [ ] put_bucket_policy
diff --git a/docs/docs/services/ssm.rst b/docs/docs/services/ssm.rst
index 24db63e85..dd733ff34 100644
--- a/docs/docs/services/ssm.rst
+++ b/docs/docs/services/ssm.rst
@@ -85,6 +85,7 @@ ssm
 - [ ] describe_instance_patch_states
 - [ ] describe_instance_patch_states_for_patch_group
 - [ ] describe_instance_patches
+- [ ] describe_instance_properties
 - [ ] describe_inventory_deletions
 - [ ] describe_maintenance_window_execution_task_invocations
 - [ ] describe_maintenance_window_execution_tasks
diff --git a/docs/docs/services/sso-admin.rst b/docs/docs/services/sso-admin.rst
index 2768397ff..600e7c99b 100644
--- a/docs/docs/services/sso-admin.rst
+++ b/docs/docs/services/sso-admin.rst
@@ -37,8 +37,8 @@ sso-admin
 - [X] delete_permission_set
 - [ ] delete_permissions_boundary_from_permission_set
 - [ ] delete_trusted_token_issuer
-- [ ] describe_account_assignment_creation_status
-- [ ] describe_account_assignment_deletion_status
+- [X] describe_account_assignment_creation_status
+- [X] describe_account_assignment_deletion_status
 - [ ] describe_application
 - [ ] describe_application_assignment
 - [ ] describe_application_provider
diff --git a/docs/docs/services/stepfunctions.rst b/docs/docs/services/stepfunctions.rst
index 067ddedee..5c01dc0fd 100644
--- a/docs/docs/services/stepfunctions.rst
+++ b/docs/docs/services/stepfunctions.rst
@@ -52,4 +52,5 @@ stepfunctions
 - [X] update_map_run
 - [X] update_state_machine
 - [ ] update_state_machine_alias
+- [ ] validate_state_machine_definition
 
diff --git a/moto/dynamodb/models/table_import.py b/moto/dynamodb/models/table_import.py
index 22e720312..905a70219 100644
--- a/moto/dynamodb/models/table_import.py
+++ b/moto/dynamodb/models/table_import.py
@@ -100,18 +100,15 @@ class TableImport(Thread):
             max_keys=None,
         )
 
-        from py_partiql_parser._internal.json_parser import JsonParser
-
-        parser = JsonParser()
+        from py_partiql_parser import JsonParser
 
         for key in keys:
             if self.compression_type == "GZIP":
                 content = gzip_decompress(key.value).decode("utf-8")
             else:
                 content = key.value.decode("utf-8")
-            result = parser.parse(original=content)
-            if not isinstance(result, list):
-                result = [result]
+            result = JsonParser.parse(original=content)
+
             for json_object in result:
                 try:
                     self.processed_count += 1
diff --git a/moto/emrserverless/exceptions.py b/moto/emrserverless/exceptions.py
index de7c7b924..46d779882 100644
--- a/moto/emrserverless/exceptions.py
+++ b/moto/emrserverless/exceptions.py
@@ -6,9 +6,9 @@ from moto.core.exceptions import JsonRESTError
 class ResourceNotFoundException(JsonRESTError):
     code = 400
 
-    def __init__(self, resource: str):
+    def __init__(self, resource: str, resource_type: str = "Application"):
         super().__init__(
-            "ResourceNotFoundException", f"Application {resource} does not exist"
+            "ResourceNotFoundException", f"{resource_type} {resource} does not exist"
         )
 
 
@@ -17,3 +17,10 @@ class ValidationException(JsonRESTError):
 
     def __init__(self, message: str):
         super().__init__("ValidationException", message)
+
+
+class AccessDeniedException(JsonRESTError):
+    code = 400
+
+    def __init__(self, message: str):
+        super().__init__("AccessDeniedException", message)
diff --git a/moto/emrserverless/models.py b/moto/emrserverless/models.py
index af5212210..493f08440 100644
--- a/moto/emrserverless/models.py
+++ b/moto/emrserverless/models.py
@@ -3,25 +3,31 @@
 import inspect
 import re
 from datetime import datetime
-from typing import Any, Dict, Iterator, List, Optional, Tuple
+from typing import Any, Dict, Iterator, List, Optional, Tuple, Union
 
 from moto.core.base_backend import BackendDict, BaseBackend
 from moto.core.common_models import BaseModel
 from moto.core.utils import iso_8601_datetime_without_milliseconds
 from moto.emrcontainers.utils import get_partition, paginated_list
 
-from .exceptions import ResourceNotFoundException, ValidationException
+from .exceptions import (
+    AccessDeniedException,
+    ResourceNotFoundException,
+    ValidationException,
+)
 from .utils import (
     default_auto_start_configuration,
     default_auto_stop_configuration,
     random_appplication_id,
+    random_job_id,
 )
 
-APPLICATION_ARN_TEMPLATE = "arn:{partition}:emr-containers:{region}:{account_id}:/applications/{application_id}"
+APPLICATION_ARN_TEMPLATE = "arn:{partition}:emr-serverless:{region}:{account_id}:/applications/{application_id}"
+JOB_RUN_ARN_TEMPLATE = "arn:{partition}:emr-serverless:{region}:{account_id}:/applications/{application_id}/jobruns/{job_run_id}"
 
 # Defaults used for creating an EMR Serverless application
 APPLICATION_STATUS = "STARTED"
-JOB_STATUS = "RUNNING"
+JOB_STATUS = "SUCCESS"
 
 
 class FakeApplication(BaseModel):
@@ -131,14 +137,118 @@ class FakeApplication(BaseModel):
         return response
 
 
+class FakeJobRun(BaseModel):
+    def __init__(
+        self,
+        application_id: str,
+        client_token: str,
+        execution_role_arn: str,
+        account_id: str,
+        region_name: str,
+        release_label: str,
+        application_type: str,
+        job_driver: Optional[Dict[str, Dict[str, Union[str, List[str]]]]],
+        configuration_overrides: Optional[Dict[str, Union[List[Any], Dict[str, Any]]]],
+        tags: Optional[Dict[str, str]],
+        network_configuration: Optional[Dict[str, List[str]]],
+        execution_timeout_minutes: Optional[int],
+        name: Optional[str],
+    ):
+        self.name = name
+        self.application_id = application_id
+        self.client_token = client_token
+        self.execution_role_arn = execution_role_arn
+        self.job_driver = job_driver
+        self.configuration_overrides = configuration_overrides
+        self.network_configuration = network_configuration
+        self.execution_timeout_minutes = execution_timeout_minutes or 720
+
+        # Service-generated-parameters
+        self.id = random_job_id()
+
+        self.arn = JOB_RUN_ARN_TEMPLATE.format(
+            partition="aws",
+            account_id=account_id,
+            application_id=self.application_id,
+            region=region_name,
+            job_run_id=self.id,
+        )
+
+        self.release_label = release_label
+        self.application_type = application_type
+
+        self.state = JOB_STATUS
+        self.state_details: Optional[str] = None
+
+        self.created_by: Optional[str] = None
+
+        self.created_at: str = iso_8601_datetime_without_milliseconds(
+            datetime.today().replace(hour=0, minute=0, second=0, microsecond=0)
+        )
+        self.updated_at: str = self.created_at
+
+        self.total_execution_duration_seconds: int = 0
+        self.billed_resource_utilization: Dict[str, float] = {
+            "vCPUHour": 0.0,
+            "memoryGBHour": 0.0,
+            "storageGBHour": 0.0,
+        }
+
+        self.tags = tags
+
+    def to_dict(self, caller_methods_type: str) -> Dict[str, Any]:
+        # The response structure is different for get/update and list
+        if caller_methods_type in ["get", "update"]:
+            response = {
+                "applicationId": self.application_id,
+                "jobRunId": self.id,
+                "name": self.name,
+                "arn": self.arn,
+                "createdBy": self.created_by,
+                "createdAt": self.created_at,
+                "updatedAt": self.updated_at,
+                "executionRole": self.execution_role_arn,
+                "state": self.state,
+                "stateDetails": self.state_details,
+                "releaseLabel": self.release_label,
+                "configurationOverrides": self.configuration_overrides,
+                "jobDriver": self.job_driver,
+                "tags": self.tags,
+                "networkConfiguration": self.network_configuration,
+                "totalExecutionDurationSeconds": self.total_execution_duration_seconds,
+                "executionTimeoutMinutes": self.execution_timeout_minutes,
+                "billedResourceUtilization": self.billed_resource_utilization,
+            }
+        else:
+            response = {
+                "applicationId": self.application_id,
+                "id": self.id,
+                "name": self.name,
+                "arn": self.arn,
+                "createdBy": self.created_by,
+                "createdAt": self.created_at,
+                "updatedAt": self.updated_at,
+                "executionRole": self.execution_role_arn,
+                "state": self.state,
+                "stateDetails": self.state_details,
+                "releaseLabel": self.release_label,
+                "type": self.application_type,
+            }
+
+        return response
+
+
 class EMRServerlessBackend(BaseBackend):
     """Implementation of EMRServerless APIs."""
 
     def __init__(self, region_name: str, account_id: str):
         super().__init__(region_name, account_id)
         self.region_name = region_name
-        self.applications: Dict[str, FakeApplication] = dict()
         self.partition = get_partition(region_name)
+        self.applications: Dict[str, FakeApplication] = dict()
+        self.job_runs: Dict[str, List[FakeJobRun]] = (
+            dict()
+        )  # {application_id: [job_run1, job_run2]}
 
     def create_application(
         self,
@@ -267,5 +377,114 @@ class EMRServerlessBackend(BaseBackend):
 
         return self.applications[application_id].to_dict()
 
+    def start_job_run(
+        self,
+        application_id: str,
+        client_token: str,
+        execution_role_arn: str,
+        job_driver: Optional[Dict[str, Dict[str, Union[str, List[str]]]]],
+        configuration_overrides: Optional[Dict[str, Union[List[Any], Dict[str, Any]]]],
+        tags: Optional[Dict[str, str]],
+        execution_timeout_minutes: Optional[int],
+        name: Optional[str],
+    ) -> FakeJobRun:
+        role_account_id = execution_role_arn.split(":")[4]
+        if role_account_id != self.account_id:
+            raise AccessDeniedException("Cross-account pass role is not allowed.")
+
+        if execution_timeout_minutes and execution_timeout_minutes < 5:
+            raise ValidationException("RunTimeout must be at least 5 minutes.")
+
+        application_resp = self.get_application(application_id)
+        job_run = FakeJobRun(
+            application_id=application_id,
+            client_token=client_token,
+            execution_role_arn=execution_role_arn,
+            account_id=self.account_id,
+            region_name=self.region_name,
+            release_label=application_resp["releaseLabel"],
+            application_type=application_resp["type"],
+            job_driver=job_driver,
+            configuration_overrides=configuration_overrides,
+            tags=tags,
+            network_configuration=application_resp.get("networkConfiguration"),
+            execution_timeout_minutes=execution_timeout_minutes,
+            name=name,
+        )
+
+        if application_resp["state"] == "TERMINATED":
+            raise ValidationException(
+                f"Application {application_id} is terminated. Cannot start job run."
+            )
+
+        if application_id not in self.job_runs:
+            self.job_runs[application_id] = []
+        self.job_runs[application_id].append(job_run)
+
+        return job_run
+
+    def get_job_run(self, application_id: str, job_run_id: str) -> FakeJobRun:
+        if application_id not in self.job_runs.keys():
+            raise ResourceNotFoundException(application_id, "Application")
+        job_run_ids = [job_run.id for job_run in self.job_runs[application_id]]
+        if job_run_id not in job_run_ids:
+            raise ResourceNotFoundException(job_run_id, "JobRun")
+
+        filtered_job_runs = [
+            job_run
+            for job_run in self.job_runs[application_id]
+            if job_run.id == job_run_id
+        ]
+        assert len(filtered_job_runs) == 1
+        job_run: FakeJobRun = filtered_job_runs[0]
+
+        return job_run
+
+    def cancel_job_run(self, application_id: str, job_run_id: str) -> Tuple[str, str]:
+        # implement here
+        if application_id not in self.job_runs.keys():
+            raise ResourceNotFoundException(application_id, "Application")
+        job_run_ids = [job_run.id for job_run in self.job_runs[application_id]]
+        if job_run_id not in job_run_ids:
+            raise ResourceNotFoundException(job_run_id, "JobRun")
+
+        self.job_runs[application_id][job_run_ids.index(job_run_id)].state = "CANCELLED"
+
+        return application_id, job_run_id
+
+    def list_job_runs(
+        self,
+        application_id: str,
+        max_results: int,
+        next_token: Optional[str],
+        created_at_after: Optional[str],
+        created_at_before: Optional[str],
+        states: Optional[List[str]],
+    ) -> Tuple[List[Dict[str, Any]], Optional[str]]:
+        if application_id not in self.job_runs.keys():
+            raise ResourceNotFoundException(application_id, "Application")
+        job_runs = self.job_runs[application_id]
+        if states:
+            job_runs = [job_run for job_run in job_runs if job_run.state in states]
+        if created_at_after:
+            job_runs = [
+                job_run
+                for job_run in job_runs
+                if datetime.strptime(job_run.created_at, "%Y-%m-%dT%H:%M:%SZ")
+                > datetime.strptime(created_at_after, "%Y-%m-%dT%H:%M:%SZ")
+            ]
+        if created_at_before:
+            job_runs = [
+                job_run
+                for job_run in job_runs
+                if datetime.strptime(job_run.created_at, "%Y-%m-%dT%H:%M:%SZ")
+                < datetime.strptime(created_at_before, "%Y-%m-%dT%H:%M:%SZ")
+            ]
+
+        job_run_dicts = [job_run.to_dict("list") for job_run in job_runs]
+
+        sort_key = "createdAt"
+        return paginated_list(job_run_dicts, sort_key, max_results, next_token)
+
 
 emrserverless_backends = BackendDict(EMRServerlessBackend, "emr-serverless")
diff --git a/moto/emrserverless/responses.py b/moto/emrserverless/responses.py
index b83fc121e..409927f1a 100644
--- a/moto/emrserverless/responses.py
+++ b/moto/emrserverless/responses.py
@@ -11,21 +11,21 @@ DEFAULT_MAX_RESULTS = 100
 DEFAULT_NEXT_TOKEN = ""
 
 """
-These are the available methos:
+These are the available methods:
     can_paginate()
-    cancel_job_run()
+    cancel_job_run() -> DONE
     close()
     create_application() -> DONE
     delete_application() -> DONE
     get_application() -> DONE
-    get_job_run()
+    get_job_run() -> DONE
     get_paginator()
     get_waiter()
     list_applications() -> DONE
-    list_job_runs()
+    list_job_runs() -> DONE
     list_tags_for_resource()
     start_application() -> DONE
-    start_job_run()
+    start_job_run() -> DONE
     stop_application() -> DONE
     tag_resource()
     untag_resource()
@@ -128,3 +128,77 @@ class EMRServerlessResponse(BaseResponse):
         )
         response = {"application": application}
         return 200, {}, json.dumps(response)
+
+    def start_job_run(self) -> TYPE_RESPONSE:
+        # params = self._get_params()
+        application_id = self._get_param(
+            "applicationId"
+        )  # params["application_id"] #.get()
+        client_token = self._get_param("clientToken")
+        execution_role_arn = self._get_param("executionRoleArn")
+        job_driver = self._get_param("jobDriver")
+        configuration_overrides = self._get_param("configurationOverrides")
+        tags = self._get_param("tags")
+        execution_timeout_minutes = self._get_int_param("executionTimeoutMinutes")
+        name = self._get_param("name")
+        job_run = self.emrserverless_backend.start_job_run(
+            application_id=application_id,
+            client_token=client_token,
+            execution_role_arn=execution_role_arn,
+            job_driver=job_driver,
+            configuration_overrides=configuration_overrides,
+            tags=tags,
+            execution_timeout_minutes=execution_timeout_minutes,
+            name=name,
+        )
+        return (
+            200,
+            {},
+            json.dumps(
+                {
+                    "applicationId": job_run.application_id,
+                    "jobRunId": job_run.id,
+                    "arn": job_run.arn,
+                }
+            ),
+        )
+
+    def get_job_run(self) -> TYPE_RESPONSE:
+        application_id = self._get_param("applicationId")
+        job_run_id = self._get_param("jobRunId")
+        job_run = self.emrserverless_backend.get_job_run(
+            application_id=application_id, job_run_id=job_run_id
+        )
+        # TODO: adjust response
+        return 200, {}, json.dumps({"jobRun": job_run.to_dict("get")})
+
+    def cancel_job_run(self) -> TYPE_RESPONSE:
+        application_id = self._get_param("applicationId")
+        job_run_id = self._get_param("jobRunId")
+        application_id, job_run_id = self.emrserverless_backend.cancel_job_run(
+            application_id=application_id,
+            job_run_id=job_run_id,
+        )
+
+        return (
+            200,
+            {},
+            json.dumps(dict(applicationId=application_id, jobRunId=job_run_id)),
+        )
+
+    def list_job_runs(self) -> TYPE_RESPONSE:
+        application_id = self._get_param("applicationId")
+        next_token = self._get_param("nextToken", DEFAULT_NEXT_TOKEN)
+        max_results = self._get_int_param("maxResults", DEFAULT_MAX_RESULTS)
+        created_at_after = self._get_param("createdAtAfter")
+        created_at_before = self._get_param("createdAtBefore")
+        states = self.querystring.get("states", [])
+        job_runs, next_token = self.emrserverless_backend.list_job_runs(
+            application_id=application_id,
+            next_token=next_token,
+            max_results=max_results,
+            created_at_after=created_at_after,
+            created_at_before=created_at_before,
+            states=states,
+        )
+        return 200, {}, json.dumps({"jobRuns": job_runs, "nextToken": next_token})
diff --git a/moto/iam/models.py b/moto/iam/models.py
index 22a26b643..cf12506c4 100644
--- a/moto/iam/models.py
+++ b/moto/iam/models.py
@@ -3320,6 +3320,22 @@ class IAMBackend(BaseBackend):
         """
         return True
 
+    def tag_instance_profile(
+        self, instance_profile_name: str, tags: List[Dict[str, str]] = []
+    ) -> None:
+        profile = self.get_instance_profile(profile_name=instance_profile_name)
+
+        for value in tags:
+            profile.tags[value["Key"]] = value["Value"]
+
+    def untag_instance_profile(
+        self, instance_profile_name: str, tagKeys: List[str] = []
+    ) -> None:
+        profile = self.get_instance_profile(profile_name=instance_profile_name)
+
+        for value in tagKeys:
+            del profile.tags[value]
+
 
 iam_backends = BackendDict(
     IAMBackend, "iam", use_boto3_regions=False, additional_regions=["global"]
diff --git a/moto/iam/responses.py b/moto/iam/responses.py
index fde3c10ae..8008fa95e 100644
--- a/moto/iam/responses.py
+++ b/moto/iam/responses.py
@@ -1128,6 +1128,28 @@ class IamResponse(BaseResponse):
         )
         return template.render()
 
+    def tag_instance_profile(self) -> str:
+        instance_profile_name = self._get_param("InstanceProfileName")
+        tags = self._get_multi_param("Tags.member")
+
+        self.backend.tag_instance_profile(
+            instance_profile_name=instance_profile_name,
+            tags=tags,
+        )
+        template = self.response_template(TAG_INSTANCE_PROFILE_TEMPLATE)
+        return template.render()
+
+    def untag_instance_profile(self) -> str:
+        instance_profile_name = self._get_param("InstanceProfileName")
+        tags = self._get_multi_param("TagKeys.member")
+
+        self.backend.untag_instance_profile(
+            instance_profile_name=instance_profile_name,
+            tagKeys=tags,
+        )
+        template = self.response_template(UNTAG_INSTANCE_PROFILE_TEMPLATE)
+        return template.render()
+
 
 LIST_ENTITIES_FOR_POLICY_TEMPLATE = """<ListEntitiesForPolicyResponse>
  <ListEntitiesForPolicyResult>
@@ -2774,3 +2796,17 @@ UNTAG_USER_TEMPLATE = """<UntagUserResponse xmlns="https://iam.amazonaws.com/doc
     <RequestId>EXAMPLE8-90ab-cdef-fedc-ba987EXAMPLE</RequestId>
   </ResponseMetadata>
 </UntagUserResponse>"""
+
+TAG_INSTANCE_PROFILE_TEMPLATE = """<TagInstanceProfileResponse xmlns="https://iam.amazonaws.com/doc/2010-05-08/">
+  <ResponseMetadata>
+    <RequestId>EXAMPLE8-90ab-cdef-fedc-ba987EXAMPLE</RequestId>
+  </ResponseMetadata>
+</TagInstanceProfileResponse>
+"""
+
+UNTAG_INSTANCE_PROFILE_TEMPLATE = """<UntagInstanceProfileResponse xmlns="https://iam.amazonaws.com/doc/2010-05-08/">
+  <ResponseMetadata>
+    <RequestId>EXAMPLE8-90ab-cdef-fedc-ba987EXAMPLE</RequestId>
+  </ResponseMetadata>
+</UntagInstanceProfileResponse>
+"""
diff --git a/setup.cfg b/setup.cfg
index 1594df76a..9892d8a51 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -54,7 +54,7 @@ all =
     openapi-spec-validator>=0.5.0
     pyparsing>=3.0.7
     jsondiff>=1.1.2
-    py-partiql-parser==0.5.4
+    py-partiql-parser==0.5.5
     aws-xray-sdk!=0.96,>=0.93
     setuptools
     multipart
@@ -69,7 +69,7 @@ proxy =
     openapi-spec-validator>=0.5.0
     pyparsing>=3.0.7
     jsondiff>=1.1.2
-    py-partiql-parser==0.5.4
+    py-partiql-parser==0.5.5
     aws-xray-sdk!=0.96,>=0.93
     setuptools
     multipart
@@ -84,7 +84,7 @@ server =
     openapi-spec-validator>=0.5.0
     pyparsing>=3.0.7
     jsondiff>=1.1.2
-    py-partiql-parser==0.5.4
+    py-partiql-parser==0.5.5
     aws-xray-sdk!=0.96,>=0.93
     setuptools
     flask!=2.2.0,!=2.2.1
@@ -119,7 +119,7 @@ cloudformation =
     openapi-spec-validator>=0.5.0
     pyparsing>=3.0.7
     jsondiff>=1.1.2
-    py-partiql-parser==0.5.4
+    py-partiql-parser==0.5.5
     aws-xray-sdk!=0.96,>=0.93
     setuptools
 cloudfront =
@@ -141,10 +141,10 @@ dms =
 ds =
 dynamodb =
     docker>=3.0.0
-    py-partiql-parser==0.5.4
+    py-partiql-parser==0.5.5
 dynamodbstreams =
     docker>=3.0.0
-    py-partiql-parser==0.5.4
+    py-partiql-parser==0.5.5
 ebs =
 ec2 =
 ec2instanceconnect =
@@ -208,15 +208,15 @@ resourcegroupstaggingapi =
     openapi-spec-validator>=0.5.0
     pyparsing>=3.0.7
     jsondiff>=1.1.2
-    py-partiql-parser==0.5.4
+    py-partiql-parser==0.5.5
 route53 =
 route53resolver =
 s3 =
     PyYAML>=5.1
-    py-partiql-parser==0.5.4
+    py-partiql-parser==0.5.5
 s3crc32c =
     PyYAML>=5.1
-    py-partiql-parser==0.5.4
+    py-partiql-parser==0.5.5
     crc32c
 s3control =
 sagemaker =


Test patch:
diff --git a/tests/test_emrserverless/test_emrserverless.py b/tests/test_emrserverless/test_emrserverless.py
index c32f5b6d3..9a9ba6f4d 100644
--- a/tests/test_emrserverless/test_emrserverless.py
+++ b/tests/test_emrserverless/test_emrserverless.py
@@ -70,6 +70,106 @@ def fixture_application_factory(client):
     yield application_list
 
 
+@pytest.fixture(scope="function", name="available_application")
+def fixture_available_application(client):
+    resp = client.create_application(
+        name="test-emr-serverless-application",
+        type="SPARK",
+        releaseLabel=DEFAULT_RELEASE_LABEL,
+    )
+
+    yield resp["applicationId"]
+
+
+@pytest.fixture(scope="function", name="job_run_factory")
+def fixture_job_run_factory(client):
+    app_1_resp = client.create_application(
+        name="test-emr-serverless-application-1",
+        type="SPARK",
+        releaseLabel=DEFAULT_RELEASE_LABEL,
+    )
+    app_1_id = app_1_resp["applicationId"]
+
+    job_1_resp = client.start_job_run(
+        name="Test Job Run 1",
+        applicationId=app_1_id,
+        executionRoleArn=f"arn:aws:iam::{ACCOUNT_ID}:role/emr-serverless-role",
+        jobDriver={
+            "sparkSubmit": {
+                "entryPoint": "test.jar",
+                "entryPointArguments": ["-h"],
+                "sparkSubmitParameters": "--num-executors 1",
+            }
+        },
+    )
+
+    job_2_resp = client.start_job_run(
+        name="Test Job Run 2",
+        applicationId=app_1_id,
+        executionRoleArn=f"arn:aws:iam::{ACCOUNT_ID}:role/emr-serverless-role",
+        jobDriver={
+            "sparkSubmit": {
+                "entryPoint": "test.jar",
+                "entryPointArguments": ["-h"],
+                "sparkSubmitParameters": "--num-executors 1",
+            }
+        },
+        configurationOverrides={
+            "monitoringConfiguration": {
+                "s3MonitoringConfiguration": {"logUri": "s3://DOC-EXAMPLE-BUCKET/logs"}
+            }
+        },
+        tags={"tag1": "tag1_val"},
+        executionTimeoutMinutes=5,
+    )
+
+    app_2_resp = client.create_application(
+        name="test-emr-serverless-application-2",
+        type="SPARK",
+        releaseLabel=DEFAULT_RELEASE_LABEL,
+        tags={"tag1": "tag1_val"},
+    )
+    app_2_id = app_2_resp["applicationId"]
+
+    job_3_resp = client.start_job_run(
+        name="Test Job Run 3",
+        applicationId=app_2_id,
+        executionRoleArn=f"arn:aws:iam::{ACCOUNT_ID}:role/emr-serverless-role",
+        jobDriver={
+            "sparkSubmit": {
+                "entryPoint": "test.jar",
+                "entryPointArguments": ["-h"],
+                "sparkSubmitParameters": "--num-executors 1",
+            }
+        },
+    )
+
+    job_4_resp = client.start_job_run(
+        name="Test Job Run 4",
+        applicationId=app_2_id,
+        executionRoleArn=f"arn:aws:iam::{ACCOUNT_ID}:role/emr-serverless-role",
+        jobDriver={
+            "sparkSubmit": {
+                "entryPoint": "test.jar",
+                "entryPointArguments": ["-h"],
+                "sparkSubmitParameters": "--num-executors 1",
+            }
+        },
+        configurationOverrides={
+            "monitoringConfiguration": {
+                "s3MonitoringConfiguration": {"logUri": "s3://DOC-EXAMPLE-BUCKET/logs"}
+            }
+        },
+        tags={"tag1": "tag1_val"},
+        executionTimeoutMinutes=5,
+    )
+
+    yield {
+        app_1_id: [job_1_resp["jobRunId"], job_2_resp["jobRunId"]],
+        app_2_id: [job_3_resp["jobRunId"], job_4_resp["jobRunId"]],
+    }
+
+
 class TestCreateApplication:
     @staticmethod
     @mock_aws
@@ -83,7 +183,7 @@ class TestCreateApplication:
         assert resp["name"] == "test-emr-serverless-application"
         assert re.match(r"[a-z,0-9]{16}", resp["applicationId"])
         assert resp["arn"] == (
-            f"arn:aws:emr-containers:us-east-1:{ACCOUNT_ID}"
+            f"arn:aws:emr-serverless:{DEFAULT_REGION}:{ACCOUNT_ID}"
             f":/applications/{resp['applicationId']}"
         )
 
@@ -183,7 +283,7 @@ class TestGetApplication:
         response = {
             "applicationId": application_id,
             "name": "test-emr-serverless-application",
-            "arn": f"arn:aws:emr-containers:us-east-1:123456789012:/applications/{application_id}",
+            "arn": f"arn:aws:emr-serverless:{DEFAULT_REGION}:{ACCOUNT_ID}:/applications/{application_id}",
             "releaseLabel": "emr-6.6.0",
             "type": "Spark",
             "state": "STARTED",
@@ -292,7 +392,7 @@ class TestListApplication:
             "id": self.application_ids[0],
             "name": "test-emr-serverless-application-STARTED",
             "arn": (
-                f"arn:aws:emr-containers:us-east-1:123456789012"
+                f"arn:aws:emr-serverless:{DEFAULT_REGION}:{ACCOUNT_ID}"
                 f":/applications/{self.application_ids[0]}"
             ),
             "releaseLabel": "emr-6.6.0",
@@ -407,7 +507,7 @@ class TestUpdateApplication:
         response = {
             "applicationId": application_id,
             "name": "test-emr-serverless-application-STOPPED",
-            "arn": f"arn:aws:emr-containers:us-east-1:123456789012:/applications/{application_id}",
+            "arn": f"arn:aws:emr-serverless:{DEFAULT_REGION}:{ACCOUNT_ID}:/applications/{application_id}",
             "releaseLabel": "emr-6.6.0",
             "type": "Spark",
             "state": "STOPPED",
@@ -547,3 +647,258 @@ class TestUpdateApplication:
         err = exc.value.response["Error"]
         assert err["Code"] == "ResourceNotFoundException"
         assert err["Message"] == "Application fake_application_id does not exist"
+
+
+class TestStartJobRun:
+    @pytest.fixture(autouse=True)
+    def _setup_environment(
+        self, client, application_factory, available_application, job_run_factory
+    ):
+        self.client = client
+        self.application_ids: list[str] = application_factory
+        self.job_run_lookup: dict[str, list[dict]] = job_run_factory
+        self.available_application: str = available_application
+
+    def test_start_job_run(self):
+        application_id = self.available_application
+        resp = self.client.start_job_run(
+            applicationId=application_id,
+            executionRoleArn=f"arn:aws:iam::{ACCOUNT_ID}:role/emr-serverless-role",
+            jobDriver={
+                "sparkSubmit": {
+                    "entryPoint": "test.jar",
+                    "entryPointArguments": ["-h"],
+                    "sparkSubmitParameters": "--num-executors 1",
+                }
+            },
+            configurationOverrides={
+                "monitoringConfiguration": {
+                    "s3MonitoringConfiguration": {
+                        "logUri": "s3://DOC-EXAMPLE-BUCKET/logs"
+                    }
+                }
+            },
+            tags={"tag1": "tag1_val"},
+            executionTimeoutMinutes=10,
+            name="Test Job Run",
+        )
+
+        assert isinstance(resp, dict)
+        assert resp["ResponseMetadata"]["HTTPStatusCode"] == 200
+        assert resp["applicationId"] == application_id
+        assert resp["arn"].startswith(
+            f"arn:aws:emr-serverless:{DEFAULT_REGION}:{ACCOUNT_ID}:/applications/{application_id}/jobruns/"
+        )
+        assert re.match(r"[a-z,0-9]{16}", resp["jobRunId"])
+
+    def test_invalid_application_id(self):
+        with pytest.raises(ClientError) as exc:
+            self.client.start_job_run(
+                applicationId="fake_application_id",
+                executionRoleArn="arn:aws:iam::{ACCOUNT_ID}:role/emr-serverless-role",
+                jobDriver={
+                    "sparkSubmit": {
+                        "entryPoint": "test.jar",
+                        "entryPointArguments": ["-h"],
+                        "sparkSubmitParameters": "--num-executors 1",
+                    }
+                },
+            )
+
+            err = exc.value.response["Error"]
+            assert err["Code"] == "ResourceNotFoundException"
+            assert err["Message"] == "Application fake_application_id does not exist"
+
+    def test_cross_account_role(self):
+        with pytest.raises(ClientError) as exc:
+            different_account_id = "999999999999"
+            self.client.start_job_run(
+                applicationId=self.application_ids[0],
+                executionRoleArn=f"arn:aws:iam::{different_account_id}:role/emr-serverless-role",
+                jobDriver={
+                    "sparkSubmit": {
+                        "entryPoint": "test.jar",
+                        "entryPointArguments": ["-h"],
+                        "sparkSubmitParameters": "--num-executors 1",
+                    }
+                },
+            )
+        err = exc.value.response["Error"]
+        assert err["Code"] == "AccessDeniedException"
+        assert err["Message"] == "Cross-account pass role is not allowed."
+
+    def test_run_timeout(self):
+        with pytest.raises(ClientError) as exc:
+            self.client.start_job_run(
+                applicationId=self.application_ids[0],
+                executionRoleArn=f"arn:aws:iam::{ACCOUNT_ID}:role/emr-serverless-role",
+                jobDriver={
+                    "sparkSubmit": {
+                        "entryPoint": "test.jar",
+                        "entryPointArguments": ["-h"],
+                        "sparkSubmitParameters": "--num-executors 1",
+                    }
+                },
+                executionTimeoutMinutes=4,
+            )
+        err = exc.value.response["Error"]
+        assert err["Code"] == "ValidationException"
+        assert err["Message"] == "RunTimeout must be at least 5 minutes."
+
+
+class TestGetJobRun:
+    @pytest.fixture(autouse=True)
+    def _setup_environment(
+        self, client, application_factory, available_application, job_run_factory
+    ):
+        self.client = client
+        self.application_ids: list[str] = application_factory
+        self.job_run_lookup: dict[str, list[dict]] = job_run_factory
+        self.available_application: str = available_application
+
+    def test_job_not_belongs_to_other_application(self):
+        app_1_id, app_2_id, *_ = self.job_run_lookup.keys()
+        app_2_job_run_ids = self.job_run_lookup[app_2_id]
+        for run_id in app_2_job_run_ids:
+            # Use application 1 ID and job run from application 2
+            with pytest.raises(ClientError) as exc:
+                _ = self.client.get_job_run(applicationId=app_1_id, jobRunId=run_id)
+            err = exc.value.response["Error"]
+            assert err["Code"] == "ResourceNotFoundException"
+
+    def test_get_job_run(self):
+        for app_id, run_ids in self.job_run_lookup.items():
+            for run_id in run_ids:
+                resp = self.client.get_job_run(applicationId=app_id, jobRunId=run_id)
+                assert resp is not None
+                assert resp["ResponseMetadata"]["HTTPStatusCode"] == 200
+                assert resp["jobRun"]["applicationId"] == app_id
+                assert resp["jobRun"]["jobRunId"] == run_id
+
+    def test_invalid_application_id(self):
+        for _, run_ids in self.job_run_lookup.items():
+            fake_app_id = "fakeapp"
+            for run_id in run_ids:
+                with pytest.raises(ClientError) as exc:
+                    self.client.get_job_run(applicationId=fake_app_id, jobRunId=run_id)
+                err = exc.value.response["Error"]
+                assert err["Code"] == "ResourceNotFoundException"
+
+    def test_invalid_job_run_id(self):
+        for app_id, _ in self.job_run_lookup.items():
+            job_run_id = "fakejobrun"
+            with pytest.raises(ClientError) as exc:
+                self.client.get_job_run(applicationId=app_id, jobRunId=job_run_id)
+            err = exc.value.response["Error"]
+            assert err["Code"] == "ResourceNotFoundException"
+
+
+class TestListJobRun:
+    @pytest.fixture(autouse=True)
+    def _setup_environment(
+        self, client, application_factory, available_application, job_run_factory
+    ):
+        self.client = client
+        self.application_ids: list[str] = application_factory
+        self.job_run_lookup: dict[str, list[dict]] = job_run_factory
+        self.available_application: str = available_application
+
+    def test_list_job_runs(self):
+        for app_id, run_ids in self.job_run_lookup.items():
+            resp = self.client.list_job_runs(applicationId=app_id)
+            assert resp is not None
+            assert resp["ResponseMetadata"]["HTTPStatusCode"] == 200
+            assert len(resp["jobRuns"]) == len(run_ids)
+            assert all(run["applicationId"] == app_id for run in resp["jobRuns"])
+            assert all(run["id"] in run_ids for run in resp["jobRuns"])
+
+    def test_invalid_application_id(self):
+        fake_app_id = "fakeapp"
+        with pytest.raises(ClientError) as exc:
+            self.client.list_job_runs(applicationId=fake_app_id)
+        err = exc.value.response["Error"]
+        assert err["Code"] == "ResourceNotFoundException"
+
+    def test_application_states(self):
+        for app_id, run_ids in self.job_run_lookup.items():
+            resp = self.client.list_job_runs(applicationId=app_id, states=["COMPLETED"])
+            assert resp is not None
+            assert resp["ResponseMetadata"]["HTTPStatusCode"] == 200
+            assert all(run["applicationId"] == app_id for run in resp["jobRuns"])
+            assert all(
+                run["id"] in run_ids
+                for run in resp["jobRuns"]
+                if run["state"] == "COMPLETED"
+            )
+
+    def test_created_filters(self):
+        for app_id, _ in self.job_run_lookup.items():
+            resp = self.client.list_job_runs(
+                applicationId=app_id,
+                createdAtAfter=datetime(2024, 1, 1),
+                createdAtBefore=datetime(2024, 1, 2),
+            )
+            assert resp is not None
+            assert resp["ResponseMetadata"]["HTTPStatusCode"] == 200
+            assert len(resp["jobRuns"]) == 0
+
+    def test_created_after(self):
+        for app_id, run_ids in self.job_run_lookup.items():
+            resp = self.client.list_job_runs(
+                applicationId=app_id, createdAtAfter=datetime(2024, 1, 1)
+            )
+            assert resp is not None
+            assert resp["ResponseMetadata"]["HTTPStatusCode"] == 200
+            assert len(resp["jobRuns"]) == len(run_ids)
+
+    def test_max_results(self):
+        for app_id, _ in self.job_run_lookup.items():
+            resp = self.client.list_job_runs(applicationId=app_id, maxResults=1)
+            assert resp is not None
+            assert resp["ResponseMetadata"]["HTTPStatusCode"] == 200
+            assert len(resp["jobRuns"]) == 1
+
+    def test_invalid_job_run_id(self):
+        for app_id, _ in self.job_run_lookup.items():
+            job_run_id = "fakejobrun"
+            with pytest.raises(ClientError) as exc:
+                self.client.cancel_job_run(applicationId=app_id, jobRunId=job_run_id)
+            err = exc.value.response["Error"]
+            assert err["Code"] == "ResourceNotFoundException"
+
+
+class TestCancelJobRun:
+    @pytest.fixture(autouse=True)
+    def _setup_environment(
+        self, client, application_factory, available_application, job_run_factory
+    ):
+        self.client = client
+        self.application_ids: list[str] = application_factory
+        self.job_run_lookup: dict[str, list[dict]] = job_run_factory
+        self.available_application: str = available_application
+
+    def test_cancel_job_run(self):
+        for app_id, run_ids in self.job_run_lookup.items():
+            for run_id in run_ids:
+                resp = self.client.cancel_job_run(applicationId=app_id, jobRunId=run_id)
+                assert resp is not None
+                assert resp["ResponseMetadata"]["HTTPStatusCode"] == 200
+                assert resp["applicationId"] == app_id
+                assert resp["jobRunId"] == run_id
+
+    def test_invalid_application_id(self):
+        for _, run_ids in self.job_run_lookup.items():
+            fake_app_id = "fakeapp"
+            for run_id in run_ids:
+                with pytest.raises(ClientError) as exc:
+                    self.client.get_job_run(applicationId=fake_app_id, jobRunId=run_id)
+                err = exc.value.response["Error"]
+                assert err["Code"] == "ResourceNotFoundException"
+
+    def test_invalid_job_run_id(self):
+        for app_id, _ in self.job_run_lookup.items():
+            job_run_id = "fakejobrun"
+            with pytest.raises(ClientError) as exc:
+                self.client.cancel_job_run(applicationId=app_id, jobRunId=job_run_id)
+            err = exc.value.response["Error"]
+            assert err["Code"] == "ResourceNotFoundException"
diff --git a/tests/test_iam/test_iam.py b/tests/test_iam/test_iam.py
index 7dbf5431a..1fbd4ad5c 100644
--- a/tests/test_iam/test_iam.py
+++ b/tests/test_iam/test_iam.py
@@ -4803,3 +4803,39 @@ def test_delete_service_linked_role():
     err = ex.value.response["Error"]
     assert err["Code"] == "NoSuchEntity"
     assert "not found" in err["Message"]
+
+
+@mock_aws
+def test_tag_instance_profile():
+    client = boto3.client("iam", region_name="eu-central-1")
+
+    name = "test-ip"
+    tags = [{"Key": "MyKey", "Value": "myValue"}]
+
+    client.create_instance_profile(InstanceProfileName=name)
+    client.tag_instance_profile(InstanceProfileName=name, Tags=tags)
+    ip = client.get_instance_profile(InstanceProfileName=name)
+
+    assert ip["InstanceProfile"]["Tags"] == tags
+
+    # add another tag
+    addTags = [{"Key": "MyKey2", "Value": "myValue2"}]
+    client.tag_instance_profile(InstanceProfileName=name, Tags=addTags)
+    ip = client.get_instance_profile(InstanceProfileName=name)
+
+    assert ip["InstanceProfile"]["Tags"] == tags + addTags
+
+
+@mock_aws
+def test_untag_instance_profile():
+    client = boto3.client("iam", region_name="eu-central-1")
+
+    name = "test-ip"
+    tags = [{"Key": "MyKey", "Value": "myValue"}]
+    unTags = ["MyKey"]
+
+    client.create_instance_profile(InstanceProfileName=name, Tags=tags)
+    client.untag_instance_profile(InstanceProfileName=name, TagKeys=unTags)
+    ip = client.get_instance_profile(InstanceProfileName=name)
+
+    assert ip["InstanceProfile"]["Tags"] == []