Repository: getmoto/moto
Pull request #8979 (2025-06-14 10:37:05)

AWSLambda: TF compatibility
Follow up # #8972

`StartingPosition` now only defaults to `TRIM_HORIZON` when the source is not SQS. (Based on [the TF docs](https://registry.terraform.io/providers/hashicorp/aws/4.58.0/docs/resources/lambda_event_source_mapping#starting_position-1).)
This ensures that ESM's are only updated when something changes, and are not destroyed and recreated with every `terraform apply`.

Includes some minor refactorings to make things more readable (like removing the `_validate_event_source`-method, and making the `event_source_arn`-parameter to `_get_service_source_from_arn` optional

New Terraform scripts are now included to verify that we can create/manage/delete ESM's without problems.


Patch:
diff --git a/AUTHORS.md b/AUTHORS.md
index d5de2bbb6..88ec8b5af 100644
--- a/AUTHORS.md
+++ b/AUTHORS.md
@@ -64,3 +64,4 @@ 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)
+* [Damon Williams](https://github.com/footballencarta)
\ No newline at end of file
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 de7e7f945..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,24 +1321,23 @@ 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:
-        service = event_source_arn.split(":")[2].lower()
-        if service == "sqs" and event_source_arn.endswith(".fifo"):
+    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
 
-    def _validate_event_source(self, event_source_arn: str) -> bool:
-        valid_services = ("dynamodb", "kinesis", "sqs", "sqs-fifo")
-        service = self._get_service_source_from_arn(event_source_arn)
-        return service in valid_services
-
     @property
     def event_source_arn(self) -> str:
         return self._event_source_arn
 
     @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"
             )
@@ -1344,7 +1348,7 @@ 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, 10000),
@@ -1352,18 +1356,16 @@ class EventSourceMapping(CloudFormationModel):
             "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/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