Repository: getmoto/moto
Pull request #8973 (2025-06-12 20:50:36)

kms - use UTC rather than local time
When scheduling deletion of a KMS key the a local time value is created but then treated as UTC when converted to a float. So use the `utcnow` utility function instead.


Patch:
diff --git a/AUTHORS.md b/AUTHORS.md
index d5de2bbb6..f4c9dfcd1 100644
--- a/AUTHORS.md
+++ b/AUTHORS.md
@@ -64,3 +64,5 @@ Moto is written by Steve Pulec with contributions from:
 * [Bryan Chen](https://github.com/bchen1116)
 * [Jonas Bulik](https://github.com/MrGreenTea)
 * [Rita Cheung](https://github.com/rita-cheung)
+* [David Acevedo](https://github.com/dacevedo12)
+* [Damon Williams](https://github.com/footballencarta)
diff --git a/IMPLEMENTATION_COVERAGE.md b/IMPLEMENTATION_COVERAGE.md
index 0a39e42aa..6b6029df3 100644
--- a/IMPLEMENTATION_COVERAGE.md
+++ b/IMPLEMENTATION_COVERAGE.md
@@ -229,7 +229,7 @@
 
 ## apigatewayv2
 <details>
-<summary>75% implemented</summary>
+<summary>70% implemented</summary>
 
 - [X] create_api
 - [X] create_api_mapping
@@ -241,6 +241,7 @@
 - [X] create_model
 - [X] create_route
 - [X] create_route_response
+- [ ] create_routing_rule
 - [X] create_stage
 - [X] create_vpc_link
 - [ ] delete_access_log_settings
@@ -257,6 +258,7 @@
 - [X] delete_route_request_parameter
 - [X] delete_route_response
 - [ ] delete_route_settings
+- [ ] delete_routing_rule
 - [X] delete_stage
 - [X] delete_vpc_link
 - [ ] export_api
@@ -281,12 +283,15 @@
 - [X] get_route_response
 - [ ] get_route_responses
 - [X] get_routes
+- [ ] get_routing_rule
 - [X] get_stage
 - [X] get_stages
 - [X] get_tags
 - [X] get_vpc_link
 - [X] get_vpc_links
 - [ ] import_api
+- [ ] list_routing_rules
+- [ ] put_routing_rule
 - [X] reimport_api
 - [ ] reset_authorizers_cache
 - [X] tag_resource
@@ -422,25 +427,25 @@
 
 ## appsync
 <details>
-<summary>27% implemented</summary>
+<summary>36% implemented</summary>
 
 - [ ] associate_api
 - [ ] associate_merged_graphql_api
 - [ ] associate_source_graphql_api
-- [ ] create_api
+- [X] create_api
 - [X] create_api_cache
 - [X] create_api_key
-- [ ] create_channel_namespace
+- [X] create_channel_namespace
 - [ ] create_data_source
 - [ ] create_domain_name
 - [ ] create_function
 - [X] create_graphql_api
 - [ ] create_resolver
 - [ ] create_type
-- [ ] delete_api
+- [X] delete_api
 - [X] delete_api_cache
 - [X] delete_api_key
-- [ ] delete_channel_namespace
+- [X] delete_channel_namespace
 - [ ] delete_data_source
 - [ ] delete_domain_name
 - [ ] delete_function
@@ -453,7 +458,7 @@
 - [ ] evaluate_code
 - [ ] evaluate_mapping_template
 - [X] flush_api_cache
-- [ ] get_api
+- [X] get_api
 - [ ] get_api_association
 - [X] get_api_cache
 - [ ] get_channel_namespace
@@ -469,8 +474,8 @@
 - [ ] get_source_api_association
 - [X] get_type
 - [X] list_api_keys
-- [ ] list_apis
-- [ ] list_channel_namespaces
+- [X] list_apis
+- [X] list_channel_namespaces
 - [ ] list_data_sources
 - [ ] list_domain_names
 - [ ] list_functions
@@ -502,7 +507,7 @@
 
 ## athena
 <details>
-<summary>25% implemented</summary>
+<summary>26% implemented</summary>
 
 - [ ] batch_get_named_query
 - [ ] batch_get_prepared_statement
@@ -534,7 +539,7 @@
 - [X] get_prepared_statement
 - [X] get_query_execution
 - [X] get_query_results
-- [ ] get_query_runtime_statistics
+- [X] get_query_runtime_statistics
 - [ ] get_session
 - [ ] get_session_status
 - [ ] get_table_metadata
@@ -959,7 +964,7 @@
 
 ## ce
 <details>
-<summary>18% implemented</summary>
+<summary>17% implemented</summary>
 
 - [ ] create_anomaly_monitor
 - [ ] create_anomaly_subscription
@@ -974,8 +979,10 @@
 - [ ] get_approximate_usage_records
 - [ ] get_commitment_purchase_analysis
 - [X] get_cost_and_usage
+- [ ] get_cost_and_usage_comparisons
 - [ ] get_cost_and_usage_with_resources
 - [ ] get_cost_categories
+- [ ] get_cost_comparison_drivers
 - [ ] get_cost_forecast
 - [ ] get_dimension_values
 - [ ] get_reservation_coverage
@@ -1009,7 +1016,7 @@
 
 ## clouddirectory
 <details>
-<summary>10% implemented</summary>
+<summary>19% implemented</summary>
 
 - [ ] add_facet_to_object
 - [X] apply_schema
@@ -1352,7 +1359,7 @@
 
 ## cloudtrail
 <details>
-<summary>28% implemented</summary>
+<summary>27% implemented</summary>
 
 - [X] add_tags
 - [ ] cancel_query
@@ -1373,6 +1380,7 @@
 - [ ] generate_query
 - [ ] get_channel
 - [ ] get_dashboard
+- [ ] get_event_configuration
 - [ ] get_event_data_store
 - [X] get_event_selectors
 - [ ] get_import
@@ -1392,6 +1400,7 @@
 - [X] list_tags
 - [X] list_trails
 - [ ] lookup_events
+- [ ] put_event_configuration
 - [X] put_event_selectors
 - [X] put_insight_selectors
 - [ ] put_resource_policy
@@ -2244,7 +2253,7 @@
 
 ## directconnect
 <details>
-<summary>12% implemented</summary>
+<summary>15% implemented</summary>
 
 - [ ] accept_direct_connect_gateway_association_proposal
 - [ ] allocate_connection_on_interconnect
@@ -2294,7 +2303,7 @@
 - [ ] describe_loa
 - [ ] describe_locations
 - [ ] describe_router_configuration
-- [X] describe_tags
+- [ ] describe_tags
 - [ ] describe_virtual_gateways
 - [ ] describe_virtual_interfaces
 - [ ] disassociate_connection_from_lag
@@ -2607,7 +2616,7 @@
 
 ## ec2
 <details>
-<summary>30% implemented</summary>
+<summary>31% implemented</summary>
 
 - [ ] accept_address_transfer
 - [ ] accept_capacity_reservation_billing_ownership
@@ -3084,6 +3093,7 @@
 - [ ] export_image
 - [ ] export_transit_gateway_routes
 - [ ] export_verified_access_instance_client_configuration
+- [ ] get_active_vpn_tunnel_status
 - [ ] get_allowed_images_settings
 - [ ] get_associated_enclave_certificate_iam_roles
 - [ ] get_associated_ipv6_pool_cidrs
@@ -4190,7 +4200,7 @@
 
 ## glue
 <details>
-<summary>30% implemented</summary>
+<summary>31% implemented</summary>
 
 - [X] batch_create_partition
 - [ ] batch_delete_connection
@@ -6252,7 +6262,7 @@
 
 ## network-firewall
 <details>
-<summary>11% implemented</summary>
+<summary>10% implemented</summary>
 
 - [ ] associate_firewall_policy
 - [ ] associate_subnets
@@ -6260,12 +6270,15 @@
 - [ ] create_firewall_policy
 - [ ] create_rule_group
 - [ ] create_tls_inspection_configuration
+- [ ] create_vpc_endpoint_association
 - [ ] delete_firewall
 - [ ] delete_firewall_policy
 - [ ] delete_resource_policy
 - [ ] delete_rule_group
 - [ ] delete_tls_inspection_configuration
+- [ ] delete_vpc_endpoint_association
 - [X] describe_firewall
+- [ ] describe_firewall_metadata
 - [ ] describe_firewall_policy
 - [ ] describe_flow_operation
 - [X] describe_logging_configuration
@@ -6273,6 +6286,7 @@
 - [ ] describe_rule_group
 - [ ] describe_rule_group_metadata
 - [ ] describe_tls_inspection_configuration
+- [ ] describe_vpc_endpoint_association
 - [ ] disassociate_subnets
 - [ ] get_analysis_report_results
 - [ ] list_analysis_reports
@@ -6283,6 +6297,7 @@
 - [ ] list_rule_groups
 - [ ] list_tags_for_resource
 - [ ] list_tls_inspection_configurations
+- [ ] list_vpc_endpoint_associations
 - [ ] put_resource_policy
 - [ ] start_analysis_report
 - [ ] start_flow_capture
@@ -9534,7 +9549,7 @@
 
 ## stepfunctions
 <details>
-<summary>54% implemented</summary>
+<summary>64% implemented</summary>
 
 - [X] create_activity
 - [X] create_state_machine
@@ -10199,6 +10214,7 @@
 - eks-auth
 - entityresolution
 - evidently
+- evs
 - finspace
 - finspace-data
 - fis
@@ -10365,4 +10381,4 @@
 - workmailmessageflow
 - workspaces-thin-client
 - xray
-</details>
+</details>
\ No newline at end of file
diff --git a/docs/docs/services/apigatewayv2.rst b/docs/docs/services/apigatewayv2.rst
index 17e5bd765..dc1eb4488 100644
--- a/docs/docs/services/apigatewayv2.rst
+++ b/docs/docs/services/apigatewayv2.rst
@@ -35,6 +35,7 @@ apigatewayv2
         The following parameters are not yet implemented: ResponseModels, ResponseParameters
         
 
+- [ ] create_routing_rule
 - [X] create_stage
 - [X] create_vpc_link
 - [ ] delete_access_log_settings
@@ -51,6 +52,7 @@ apigatewayv2
 - [X] delete_route_request_parameter
 - [X] delete_route_response
 - [ ] delete_route_settings
+- [ ] delete_routing_rule
 - [X] delete_stage
 - [X] delete_vpc_link
 - [ ] export_api
@@ -91,12 +93,15 @@ apigatewayv2
         Pagination is not yet implemented
         
 
+- [ ] get_routing_rule
 - [X] get_stage
 - [X] get_stages
 - [X] get_tags
 - [X] get_vpc_link
 - [X] get_vpc_links
 - [ ] import_api
+- [ ] list_routing_rules
+- [ ] put_routing_rule
 - [X] reimport_api
   
         Only YAML is supported at the moment. Full OpenAPI-support is not guaranteed. Only limited validation is implemented
diff --git a/docs/docs/services/appsync.rst b/docs/docs/services/appsync.rst
index ad3823991..012e85b1c 100644
--- a/docs/docs/services/appsync.rst
+++ b/docs/docs/services/appsync.rst
@@ -19,20 +19,20 @@ appsync
 - [ ] associate_api
 - [ ] associate_merged_graphql_api
 - [ ] associate_source_graphql_api
-- [ ] create_api
+- [X] create_api
 - [X] create_api_cache
 - [X] create_api_key
-- [ ] create_channel_namespace
+- [X] create_channel_namespace
 - [ ] create_data_source
 - [ ] create_domain_name
 - [ ] create_function
 - [X] create_graphql_api
 - [ ] create_resolver
 - [ ] create_type
-- [ ] delete_api
+- [X] delete_api
 - [X] delete_api_cache
 - [X] delete_api_key
-- [ ] delete_channel_namespace
+- [X] delete_channel_namespace
 - [ ] delete_data_source
 - [ ] delete_domain_name
 - [ ] delete_function
@@ -45,7 +45,7 @@ appsync
 - [ ] evaluate_code
 - [ ] evaluate_mapping_template
 - [X] flush_api_cache
-- [ ] get_api
+- [X] get_api
 - [ ] get_api_association
 - [X] get_api_cache
 - [ ] get_channel_namespace
@@ -65,8 +65,12 @@ appsync
         Pagination or the maxResults-parameter have not yet been implemented.
         
 
-- [ ] list_apis
-- [ ] list_channel_namespaces
+- [X] list_apis
+  
+        Pagination or the maxResults-parameter have not yet been implemented.
+        
+
+- [X] list_channel_namespaces
 - [ ] list_data_sources
 - [ ] list_domain_names
 - [ ] list_functions
diff --git a/docs/docs/services/athena.rst b/docs/docs/services/athena.rst
index f016139ab..92da6ffb5 100644
--- a/docs/docs/services/athena.rst
+++ b/docs/docs/services/athena.rst
@@ -92,7 +92,7 @@ athena
 
         
 
-- [ ] get_query_runtime_statistics
+- [X] get_query_runtime_statistics
 - [ ] get_session
 - [ ] get_session_status
 - [ ] get_table_metadata
diff --git a/docs/docs/services/ce.rst b/docs/docs/services/ce.rst
index 34a56e40b..7ee305e76 100644
--- a/docs/docs/services/ce.rst
+++ b/docs/docs/services/ce.rst
@@ -84,8 +84,10 @@ ce
             resp = ce.get_cost_and_usage(...)
         
 
+- [ ] get_cost_and_usage_comparisons
 - [ ] get_cost_and_usage_with_resources
 - [ ] get_cost_categories
+- [ ] get_cost_comparison_drivers
 - [ ] get_cost_forecast
 - [ ] get_dimension_values
 - [ ] get_reservation_coverage
diff --git a/docs/docs/services/clouddirectory.rst b/docs/docs/services/clouddirectory.rst
index 3870d3692..a26d9d6f9 100644
--- a/docs/docs/services/clouddirectory.rst
+++ b/docs/docs/services/clouddirectory.rst
@@ -17,7 +17,7 @@ clouddirectory
 |start-h3| Implemented features for this service |end-h3|
 
 - [ ] add_facet_to_object
-- [ ] apply_schema
+- [X] apply_schema
 - [ ] attach_object
 - [ ] attach_policy
 - [ ] attach_to_index
@@ -28,12 +28,12 @@ clouddirectory
 - [ ] create_facet
 - [ ] create_index
 - [ ] create_object
-- [ ] create_schema
+- [X] create_schema
 - [ ] create_typed_link_facet
 - [X] delete_directory
 - [ ] delete_facet
 - [ ] delete_object
-- [ ] delete_schema
+- [X] delete_schema
 - [ ] delete_typed_link_facet
 - [ ] detach_from_index
 - [ ] detach_object
@@ -51,7 +51,7 @@ clouddirectory
 - [ ] get_typed_link_facet_information
 - [ ] list_applied_schema_arns
 - [ ] list_attached_indices
-- [ ] list_development_schema_arns
+- [X] list_development_schema_arns
 - [X] list_directories
 - [ ] list_facet_attributes
 - [ ] list_facet_names
@@ -65,12 +65,12 @@ clouddirectory
 - [ ] list_object_policies
 - [ ] list_outgoing_typed_links
 - [ ] list_policy_attachments
-- [ ] list_published_schema_arns
+- [X] list_published_schema_arns
 - [X] list_tags_for_resource
 - [ ] list_typed_link_facet_attributes
 - [ ] list_typed_link_facet_names
 - [ ] lookup_policy
-- [ ] publish_schema
+- [X] publish_schema
 - [ ] put_schema_from_json
 - [ ] remove_facet_from_object
 - [X] tag_resource
diff --git a/docs/docs/services/cloudtrail.rst b/docs/docs/services/cloudtrail.rst
index 28fc2b37f..298a434fa 100644
--- a/docs/docs/services/cloudtrail.rst
+++ b/docs/docs/services/cloudtrail.rst
@@ -35,6 +35,7 @@ cloudtrail
 - [ ] generate_query
 - [ ] get_channel
 - [ ] get_dashboard
+- [ ] get_event_configuration
 - [ ] get_event_data_store
 - [X] get_event_selectors
 - [ ] get_import
@@ -58,6 +59,7 @@ cloudtrail
 
 - [X] list_trails
 - [ ] lookup_events
+- [ ] put_event_configuration
 - [X] put_event_selectors
 - [X] put_insight_selectors
 - [ ] put_resource_policy
diff --git a/docs/docs/services/directconnect.rst b/docs/docs/services/directconnect.rst
index ca37569dc..c2aa1d9ae 100644
--- a/docs/docs/services/directconnect.rst
+++ b/docs/docs/services/directconnect.rst
@@ -72,8 +72,8 @@ directconnect
 - [ ] list_virtual_interface_test_history
 - [ ] start_bgp_failover_test
 - [ ] stop_bgp_failover_test
-- [ ] tag_resource
-- [ ] untag_resource
+- [X] tag_resource
+- [X] untag_resource
 - [X] update_connection
 - [ ] update_direct_connect_gateway
 - [ ] update_direct_connect_gateway_association
diff --git a/docs/docs/services/ec2.rst b/docs/docs/services/ec2.rst
index d1cefa531..3b9ecce82 100644
--- a/docs/docs/services/ec2.rst
+++ b/docs/docs/services/ec2.rst
@@ -527,6 +527,7 @@ ec2
 - [ ] export_image
 - [ ] export_transit_gateway_routes
 - [ ] export_verified_access_instance_client_configuration
+- [ ] get_active_vpn_tunnel_status
 - [ ] get_allowed_images_settings
 - [ ] get_associated_enclave_certificate_iam_roles
 - [ ] get_associated_ipv6_pool_cidrs
diff --git a/docs/docs/services/glue.rst b/docs/docs/services/glue.rst
index e1ce4657b..85561f457 100644
--- a/docs/docs/services/glue.rst
+++ b/docs/docs/services/glue.rst
@@ -88,7 +88,7 @@ glue
 - [X] delete_partition
 - [ ] delete_partition_index
 - [X] delete_registry
-- [ ] delete_resource_policy
+- [X] delete_resource_policy
 - [X] delete_schema
 - [ ] delete_schema_versions
 - [ ] delete_security_configuration
diff --git a/docs/docs/services/network-firewall.rst b/docs/docs/services/network-firewall.rst
index 0895bedcd..34467b558 100644
--- a/docs/docs/services/network-firewall.rst
+++ b/docs/docs/services/network-firewall.rst
@@ -22,12 +22,15 @@ network-firewall
 - [ ] create_firewall_policy
 - [ ] create_rule_group
 - [ ] create_tls_inspection_configuration
+- [ ] create_vpc_endpoint_association
 - [ ] delete_firewall
 - [ ] delete_firewall_policy
 - [ ] delete_resource_policy
 - [ ] delete_rule_group
 - [ ] delete_tls_inspection_configuration
+- [ ] delete_vpc_endpoint_association
 - [X] describe_firewall
+- [ ] describe_firewall_metadata
 - [ ] describe_firewall_policy
 - [ ] describe_flow_operation
 - [X] describe_logging_configuration
@@ -35,6 +38,7 @@ network-firewall
 - [ ] describe_rule_group
 - [ ] describe_rule_group_metadata
 - [ ] describe_tls_inspection_configuration
+- [ ] describe_vpc_endpoint_association
 - [ ] disassociate_subnets
 - [ ] get_analysis_report_results
 - [ ] list_analysis_reports
@@ -45,6 +49,7 @@ network-firewall
 - [ ] list_rule_groups
 - [ ] list_tags_for_resource
 - [ ] list_tls_inspection_configurations
+- [ ] list_vpc_endpoint_associations
 - [ ] put_resource_policy
 - [ ] start_analysis_report
 - [ ] start_flow_capture
diff --git a/docs/docs/services/stepfunctions.rst b/docs/docs/services/stepfunctions.rst
index 5c01dc0fd..d73357310 100644
--- a/docs/docs/services/stepfunctions.rst
+++ b/docs/docs/services/stepfunctions.rst
@@ -16,14 +16,14 @@ stepfunctions
 
 |start-h3| Implemented features for this service |end-h3|
 
-- [ ] create_activity
+- [X] create_activity
 - [X] create_state_machine
 - [ ] create_state_machine_alias
-- [ ] delete_activity
+- [X] delete_activity
 - [X] delete_state_machine
 - [ ] delete_state_machine_alias
 - [ ] delete_state_machine_version
-- [ ] describe_activity
+- [X] describe_activity
 - [X] describe_execution
 - [X] describe_map_run
 - [X] describe_state_machine
@@ -31,7 +31,7 @@ stepfunctions
 - [X] describe_state_machine_for_execution
 - [ ] get_activity_task
 - [X] get_execution_history
-- [ ] list_activities
+- [X] list_activities
 - [X] list_executions
 - [X] list_map_runs
 - [ ] list_state_machine_aliases
diff --git a/moto/athena/models.py b/moto/athena/models.py
index 4cb7a235a..a8fcdbcdc 100644
--- a/moto/athena/models.py
+++ b/moto/athena/models.py
@@ -452,5 +452,12 @@ class AthenaBackend(BaseBackend):
                 return ps
         return None
 
+    def get_query_runtime_statistics(
+        self, query_execution_id: str
+    ) -> Optional[Execution]:
+        if query_execution_id in self.executions:
+            return self.executions[query_execution_id]
+        return None
+
 
 athena_backends = BackendDict(AthenaBackend, "athena")
diff --git a/moto/athena/responses.py b/moto/athena/responses.py
index 727711167..60cd55526 100644
--- a/moto/athena/responses.py
+++ b/moto/athena/responses.py
@@ -220,3 +220,43 @@ class AthenaResponse(BaseResponse):
                 }
             }
         )
+
+    def get_query_runtime_statistics(self) -> Union[str, Tuple[str, Dict[str, int]]]:
+        query_execution_id = self._get_param("QueryExecutionId")
+
+        ps = self.athena_backend.get_query_runtime_statistics(
+            query_execution_id=query_execution_id
+        )
+
+        if ps is None:
+            return self.error(f"QueryExecution {query_execution_id} was not found", 400)
+
+        return json.dumps(
+            {
+                "QueryRuntimeStatistics": {
+                    "OutputStage": {
+                        "ExecutionTime": 100,
+                        "InputBytes": 0,
+                        "InputRows": 0,
+                        "OutputBytes": 1,
+                        "OutputRows": 1,
+                        "StageId": 1,
+                        "State": ps.status,
+                    },
+                    "Rows": {
+                        "InputBytes": 0,
+                        "InputRows": 0,
+                        "OutputBytes": 2,
+                        "OutputRows": 2,
+                    },
+                    "Timeline": {
+                        "EngineExecutionTimeInMillis": 0,
+                        "QueryPlanningTimeInMillis": 0,
+                        "QueryQueueTimeInMillis": 0,
+                        "ServicePreProcessingTimeInMillis": 0,
+                        "ServiceProcessingTimeInMillis": 0,
+                        "TotalExecutionTimeInMillis": 0,
+                    },
+                }
+            }
+        )
diff --git a/moto/awslambda/models.py b/moto/awslambda/models.py
index 7346fc5aa..310a1c5ec 100644
--- a/moto/awslambda/models.py
+++ b/moto/awslambda/models.py
@@ -1286,7 +1286,12 @@ class EventSourceMapping(CloudFormationModel):
 
         # optional
         self.batch_size = spec.get("BatchSize")  # type: ignore[assignment]
-        self.starting_position = spec.get("StartingPosition", "TRIM_HORIZON")
+        self.starting_position = spec.get("StartingPosition")
+        if (
+            not self.starting_position
+            and "sqs" not in self._get_service_source_from_arn()
+        ):
+            self.starting_position = "TRIM_HORIZON"
         self.enabled = spec.get("Enabled", True)
         self.starting_position_timestamp = spec.get("StartingPositionTimestamp")
         self.kafka_config = spec.get("AmazonManagedKafkaEventSourceConfig")
@@ -1316,13 +1321,14 @@ class EventSourceMapping(CloudFormationModel):
         self.function_arn: str = spec["FunctionArn"]
         self.last_modified = time.mktime(utcnow().timetuple())
 
-    def _get_service_source_from_arn(self, event_source_arn: str) -> str:
-        return event_source_arn.split(":")[2].lower()
-
-    def _validate_event_source(self, event_source_arn: str) -> bool:
-        valid_services = ("dynamodb", "kinesis", "sqs")
-        service = self._get_service_source_from_arn(event_source_arn)
-        return service in valid_services
+    def _get_service_source_from_arn(
+        self, event_source_arn: Optional[str] = None
+    ) -> str:
+        arn = event_source_arn or self.event_source_arn
+        service = arn.split(":")[2].lower()
+        if service == "sqs" and arn.endswith(".fifo"):
+            return "sqs-fifo"
+        return service
 
     @property
     def event_source_arn(self) -> str:
@@ -1330,7 +1336,8 @@ class EventSourceMapping(CloudFormationModel):
 
     @event_source_arn.setter
     def event_source_arn(self, event_source_arn: str) -> None:
-        if not self._validate_event_source(event_source_arn):
+        service = self._get_service_source_from_arn(event_source_arn)
+        if service not in ("dynamodb", "kinesis", "sqs", "sqs-fifo"):
             raise ValueError(
                 "InvalidParameterValueException", "Unsupported event source type"
             )
@@ -1341,25 +1348,24 @@ class EventSourceMapping(CloudFormationModel):
         return self._batch_size
 
     @batch_size.setter
-    def batch_size(self, batch_size: Optional[int]) -> None:
+    def batch_size(self, new_batch_size: Optional[int]) -> None:
         batch_size_service_map = {
             "kinesis": (100, 10000),
-            "dynamodb": (100, 1000),
-            "sqs": (10, 10),
+            "dynamodb": (100, 10000),
+            "sqs": (10, 10000),
+            "sqs-fifo": (10, 10),
         }
 
-        source_type = self._get_service_source_from_arn(self.event_source_arn)
+        source_type = self._get_service_source_from_arn()
         batch_size_for_source = batch_size_service_map[source_type]
 
-        if batch_size is None:
+        if new_batch_size is None:
             self._batch_size = batch_size_for_source[0]
-        elif batch_size > batch_size_for_source[1]:
-            error_message = (
-                f"BatchSize {batch_size} exceeds the max of {batch_size_for_source[1]}"
-            )
+        elif new_batch_size > batch_size_for_source[1]:
+            error_message = f"BatchSize {new_batch_size} exceeds the max of {batch_size_for_source[1]}"
             raise ValueError("InvalidParameterValueException", error_message)
         else:
-            self._batch_size = int(batch_size)
+            self._batch_size = int(new_batch_size)
 
     def get_configuration(self) -> Dict[str, Any]:
         response_dict = {
diff --git a/moto/core/responses.py b/moto/core/responses.py
index 2c8e3cb51..be15dab19 100644
--- a/moto/core/responses.py
+++ b/moto/core/responses.py
@@ -97,8 +97,15 @@ def _get_method_urls(service_name: str, region: str) -> Dict[str, Dict[str, str]
             # Terraform 5.50 made a request to /rrset/
             # Terraform 5.51+ makes a request to /rrset - so we have to intercept both variants
             request_uri += "?"
-        if service_name == "lambda" and request_uri.endswith("/functions/"):
-            # AWS JS SDK behaves differently from other SDK's, does not send a trailing slash
+        if service_name == "lambda":
+            if request_uri.endswith("/event-source-mappings/"):
+                # AWS GO SDK behaves differently from other SDK's, does not send a trailing slash
+                request_uri += "?"
+            elif request_uri.endswith("/functions/"):
+                # AWS JS SDK behaves differently from other SDK's, does not send a trailing slash
+                request_uri += "?"
+        if service_name == "opensearch" and request_uri.endswith("/tags/"):
+            # AWS GO SDK behaves differently from other SDK's, does not send a trailing slash
             request_uri += "?"
         uri_regexp = BaseResponse.uri_to_regexp(request_uri)
         method_urls[_method][uri_regexp] = op_model.name
diff --git a/moto/kms/models.py b/moto/kms/models.py
index 61a95990c..24a487ac2 100644
--- a/moto/kms/models.py
+++ b/moto/kms/models.py
@@ -9,7 +9,7 @@ from typing import Any, Dict, Iterable, List, Optional, Set, Tuple, Union
 from moto.core.base_backend import BackendDict, BaseBackend
 from moto.core.common_models import BaseModel, CloudFormationModel
 from moto.core.exceptions import JsonRESTError
-from moto.core.utils import unix_time
+from moto.core.utils import unix_time, utcnow
 from moto.moto_api._internal import mock_random
 from moto.utilities.paginator import paginate
 from moto.utilities.tagging_service import TaggingService
@@ -501,7 +501,7 @@ class KmsBackend(BaseBackend):
         if 7 <= pending_window_in_days <= 30:
             self.keys[key_id].enabled = False
             self.keys[key_id].key_state = "PendingDeletion"
-            self.keys[key_id].deletion_date = datetime.now() + timedelta(
+            self.keys[key_id].deletion_date = utcnow() + timedelta(
                 days=pending_window_in_days
             )
             return unix_time(self.keys[key_id].deletion_date)
@@ -749,7 +749,7 @@ class KmsBackend(BaseBackend):
 
         rotation = {
             "KeyId": key_id,
-            "RotationDate": datetime.now().timestamp(),
+            "RotationDate": unix_time(),
             "RotationType": "ON_DEMAND",
         }
 
diff --git a/moto/opensearch/models.py b/moto/opensearch/models.py
index 4927dcbb5..cec5ce8a7 100644
--- a/moto/opensearch/models.py
+++ b/moto/opensearch/models.py
@@ -150,7 +150,6 @@ class OpenSearchDomain(BaseModel):
         return {
             "Endpoint": self.endpoint,
             "Endpoints": self.endpoints,
-            "EngineVersion": self.engine_version.to_dict(),
             "ClusterConfig": self.cluster_config,
             "EBSOptions": self.ebs_options,
             "AccessPolicies": self.access_policies,
@@ -177,6 +176,7 @@ class OpenSearchDomain(BaseModel):
             "ARN": self.arn,
             "Created": True,
             "Deleted": self.deleted,
+            "EngineVersion": self.engine_version.options,
             "Processing": self.processing,
             "UpgradeProcessing": False,
         }
@@ -186,7 +186,9 @@ class OpenSearchDomain(BaseModel):
         return dct
 
     def to_config_dict(self) -> Dict[str, Any]:
-        dct: Dict[str, Any] = dict()
+        dct = {
+            "EngineVersion": self.engine_version.to_dict(),
+        }
         for key, value in self.dct_options().items():
             if value is not None:
                 dct[key] = {"Options": value}
@@ -303,8 +305,10 @@ class OpenSearchServiceBackend(BaseBackend):
             self.add_tags(domain.arn, tag_list)
         return domain
 
-    def get_compatible_versions(self, domain_name: str) -> List[Dict[str, Any]]:
-        if domain_name not in self.domains:
+    def get_compatible_versions(
+        self, domain_name: Optional[str]
+    ) -> List[Dict[str, Any]]:
+        if domain_name and domain_name not in self.domains:
             raise ResourceNotFoundException(domain_name)
         return compatible_versions
 
diff --git a/moto/opensearch/responses.py b/moto/opensearch/responses.py
index 3c3828e3c..228b18376 100644
--- a/moto/opensearch/responses.py
+++ b/moto/opensearch/responses.py
@@ -137,7 +137,7 @@ class OpenSearchServiceResponse(BaseResponse):
         return json.dumps(dict(DomainStatus=domain.to_dict()))
 
     def describe_domain_config(self) -> str:
-        domain_name = self.path.split("/")[-1]
+        domain_name = self.path.split("/")[-2]
         domain = self.opensearch_backend.describe_domain_config(
             domain_name=domain_name,
         )
diff --git a/other_langs/terraform/awslambda/event_source_mapping.tf b/other_langs/terraform/awslambda/event_source_mapping.tf
index 6e43b9419..924b7521b 100644
--- a/other_langs/terraform/awslambda/event_source_mapping.tf
+++ b/other_langs/terraform/awslambda/event_source_mapping.tf
@@ -17,49 +17,6 @@ resource "aws_kinesis_stream" "test_stream" {
   }
 }
 
-data "aws_iam_policy_document" "assume_role" {
-  statement {
-    effect = "Allow"
-
-    principals {
-      type        = "Service"
-      identifiers = ["lambda.amazonaws.com"]
-    }
-
-    actions = ["sts:AssumeRole"]
-  }
-}
-
-resource "aws_iam_role" "iam_for_lambda" {
-  name               = "iam_for_lambda"
-  assume_role_policy = data.aws_iam_policy_document.assume_role.json
-}
-
-data "archive_file" "lambda" {
-  type        = "zip"
-  source_file = "lambda.js"
-  output_path = "lambda_function_payload.zip"
-}
-
-resource "aws_lambda_function" "test_lambda" {
-  # If the file is not in the current working directory you will need to include a
-  # path.module in the filename.
-  filename      = "lambda_function_payload.zip"
-  function_name = "lambda_function_name"
-  role          = aws_iam_role.iam_for_lambda.arn
-  handler       = "index.test"
-
-  source_code_hash = data.archive_file.lambda.output_base64sha256
-
-  runtime = "nodejs18.x"
-
-  environment {
-    variables = {
-      foo = "bar"
-    }
-  }
-}
-
 resource "aws_lambda_event_source_mapping" "kinesis_to_sqs" {
   event_source_arn  = aws_kinesis_stream.test_stream.arn
   function_name     = aws_lambda_function.test_lambda.arn
diff --git a/other_langs/terraform/awslambda/event_source_mapping_common.tf b/other_langs/terraform/awslambda/event_source_mapping_common.tf
new file mode 100644
index 000000000..ab4ce7ae9
--- /dev/null
+++ b/other_langs/terraform/awslambda/event_source_mapping_common.tf
@@ -0,0 +1,42 @@
+data "aws_iam_policy_document" "assume_role" {
+  statement {
+    effect = "Allow"
+
+    principals {
+      type        = "Service"
+      identifiers = ["lambda.amazonaws.com"]
+    }
+
+    actions = ["sts:AssumeRole"]
+  }
+}
+
+resource "aws_iam_role" "iam_for_lambda" {
+  name               = "iam_for_lambda"
+  assume_role_policy = data.aws_iam_policy_document.assume_role.json
+}
+
+data "archive_file" "lambda" {
+  type        = "zip"
+  source_file = "lambda.js"
+  output_path = "lambda_function_payload.zip"
+}
+
+resource "aws_lambda_function" "test_lambda" {
+  # If the file is not in the current working directory you will need to include a
+  # path.module in the filename.
+  filename      = "lambda_function_payload.zip"
+  function_name = "lambda_function_name"
+  role          = aws_iam_role.iam_for_lambda.arn
+  handler       = "index.test"
+
+  source_code_hash = data.archive_file.lambda.output_base64sha256
+
+  runtime = "nodejs18.x"
+
+  environment {
+    variables = {
+      foo = "bar"
+    }
+  }
+}
\ No newline at end of file
diff --git a/other_langs/terraform/awslambda/event_source_mapping_sqs.tf b/other_langs/terraform/awslambda/event_source_mapping_sqs.tf
new file mode 100644
index 000000000..e9bf37829
--- /dev/null
+++ b/other_langs/terraform/awslambda/event_source_mapping_sqs.tf
@@ -0,0 +1,10 @@
+resource "aws_sqs_queue" "esm_queue" {
+  name                        = "test-queue"
+}
+
+
+resource "aws_lambda_event_source_mapping" "esm_to_sqs_queue" {
+  event_source_arn = aws_sqs_queue.esm_queue.arn
+  function_name    = aws_lambda_function.test_lambda.arn
+  batch_size       = 1000
+}
\ No newline at end of file
diff --git a/other_langs/terraform/awslambda/event_source_mapping_sqs_fifo.tf b/other_langs/terraform/awslambda/event_source_mapping_sqs_fifo.tf
new file mode 100644
index 000000000..21746d7d0
--- /dev/null
+++ b/other_langs/terraform/awslambda/event_source_mapping_sqs_fifo.tf
@@ -0,0 +1,11 @@
+resource "aws_sqs_queue" "esm_fifo_queue" {
+  name                        = "testfifoqueue.fifo"
+  fifo_queue                  = true
+}
+
+
+resource "aws_lambda_event_source_mapping" "esm_to_sqs_fifo_queue" {
+  event_source_arn = aws_sqs_queue.esm_fifo_queue.arn
+  function_name    = aws_lambda_function.test_lambda.arn
+  batch_size       = 10
+}
\ No newline at end of file


Test patch:
diff --git a/tests/test_athena/test_athena.py b/tests/test_athena/test_athena.py
index 1e75ebc86..6229dec8a 100644
--- a/tests/test_athena/test_athena.py
+++ b/tests/test_athena/test_athena.py
@@ -548,3 +548,35 @@ def test_get_prepared_statement():
         StatementName="stmt-name", WorkGroup="athena_workgroup"
     )
     assert "PreparedStatement" in resp
+
+
+@mock_aws
+def test_get_query_runtime_statistics_no_execution_id():
+    client = boto3.client("athena", region_name="us-east-1")
+    create_basic_workgroup(client=client, name="athena_workgroup")
+
+    with pytest.raises(ClientError) as exc:
+        client.get_query_runtime_statistics(QueryExecutionId="1")
+
+    err = exc.value.response["Error"]
+    assert err["Code"] == "InvalidRequestException"
+    assert err["Message"] == "QueryExecution 1 was not found"
+
+
+@mock_aws
+def test_get_query_runtime_statistics_with_execution_id():
+    client = boto3.client("athena", region_name="us-east-1")
+    create_basic_workgroup(client=client, name="athena_workgroup")
+    response = client.start_query_execution(
+        QueryString="query1",
+        QueryExecutionContext={"Database": "string"},
+        ResultConfiguration={"OutputLocation": "string"},
+        WorkGroup="athena_workgroup",
+    )
+    assert "QueryExecutionId" in response
+
+    statistics = client.get_query_runtime_statistics(
+        QueryExecutionId=response["QueryExecutionId"]
+    )
+
+    assert "QueryRuntimeStatistics" in statistics
diff --git a/tests/test_opensearch/test_opensearch.py b/tests/test_opensearch/test_opensearch.py
index 7dd80864a..878acabcc 100644
--- a/tests/test_opensearch/test_opensearch.py
+++ b/tests/test_opensearch/test_opensearch.py
@@ -1,6 +1,9 @@
+from datetime import datetime
+
 import boto3
 import pytest
 from botocore.exceptions import ClientError
+from dateutil.tz import tzlocal
 from freezegun import freeze_time
 
 from moto import mock_aws, settings
@@ -48,7 +51,7 @@ def test_create_domain_with_some_options():
         EngineVersion="OpenSearch_1.1",
     )["DomainStatus"]
     assert status["Created"]
-    assert status["EngineVersion"]["Options"] == "OpenSearch_1.1"
+    assert status["EngineVersion"] == "OpenSearch_1.1"
     assert status["DomainEndpointOptions"] == {
         "EnforceHTTPS": True,
         "TLSSecurityPolicy": "Policy-Min-TLS-1-0-2019-07",
@@ -63,6 +66,8 @@ def test_get_compatible_versions():
     client = boto3.client("opensearch", region_name="us-east-2")
     client.create_domain(DomainName="testdn")
 
+    versions = client.get_compatible_versions()["CompatibleVersions"]
+    assert len(versions) == 22
     versions = client.get_compatible_versions(DomainName="testdn")["CompatibleVersions"]
     assert len(versions) == 22
 
@@ -90,7 +95,6 @@ def test_describe_unknown_domain():
 
 
 @mock_aws
-@freeze_time("2015-01-01 12:00:00")
 def test_describe_domain():
     # Setup
     client = boto3.client("opensearch", region_name="eu-west-1")
@@ -98,19 +102,33 @@ def test_describe_domain():
 
     # Execute
     status = client.describe_domain(DomainName="testdn")["DomainStatus"]
-    ev_status = status["EngineVersion"]["Status"]
 
     # Verify
     assert "DomainId" in status
     assert "DomainName" in status
     assert status["DomainName"] == "testdn"
-    assert status["EngineVersion"]["Options"] == "OpenSearch_2.5"
+    assert status["EngineVersion"] == "OpenSearch_2.5"
+
+
+@mock_aws
+@freeze_time("2015-01-01 12:00:00")
+def test_describe_domain_config():
+    # Setup
+    client = boto3.client("opensearch", region_name="eu-west-1")
+    client.create_domain(DomainName="testdn")
+
+    # Execute
+    config = client.describe_domain_config(DomainName="testdn")["DomainConfig"]
+    ev_status = config["EngineVersion"]["Status"]
+
+    # Verify
+    assert config["EngineVersion"]["Options"] == "OpenSearch_2.5"
     assert ev_status["State"] == "Active"
     assert ev_status["PendingDeletion"] is False
 
     if not settings.TEST_SERVER_MODE:
-        assert ev_status["CreationDate"] == 1420113600.0
-        assert ev_status["UpdateDate"] == 1420113600.0
+        assert ev_status["CreationDate"] == datetime(2015, 1, 1, 12, tzinfo=tzlocal())
+        assert ev_status["UpdateDate"] == datetime(2015, 1, 1, 12, tzinfo=tzlocal())
 
 
 @mock_aws