diff --git a/IMPLEMENTATION_COVERAGE.md b/IMPLEMENTATION_COVERAGE.md
index 058854312..76c420447 100644
--- a/IMPLEMENTATION_COVERAGE.md
+++ b/IMPLEMENTATION_COVERAGE.md
@@ -22,7 +22,7 @@
## apigateway
-55% implemented
+59% implemented
- [X] create_api_key
- [X] create_authorizer
@@ -38,7 +38,7 @@
- [X] create_stage
- [X] create_usage_plan
- [X] create_usage_plan_key
-- [ ] create_vpc_link
+- [X] create_vpc_link
- [X] delete_api_key
- [X] delete_authorizer
- [X] delete_base_path_mapping
@@ -59,7 +59,7 @@
- [X] delete_stage
- [X] delete_usage_plan
- [X] delete_usage_plan_key
-- [ ] delete_vpc_link
+- [X] delete_vpc_link
- [ ] flush_stage_authorizers_cache
- [ ] flush_stage_cache
- [ ] generate_client_certificate
@@ -107,8 +107,8 @@
- [X] get_usage_plan_key
- [X] get_usage_plan_keys
- [X] get_usage_plans
-- [ ] get_vpc_link
-- [ ] get_vpc_links
+- [X] get_vpc_link
+- [X] get_vpc_links
- [ ] import_api_keys
- [ ] import_documentation_parts
- [ ] import_rest_api
@@ -146,6 +146,84 @@
- [ ] update_vpc_link
+## apigatewayv2
+
+58% implemented
+
+- [X] create_api
+- [ ] create_api_mapping
+- [X] create_authorizer
+- [ ] create_deployment
+- [ ] create_domain_name
+- [X] create_integration
+- [X] create_integration_response
+- [X] create_model
+- [X] create_route
+- [X] create_route_response
+- [ ] create_stage
+- [X] create_vpc_link
+- [ ] delete_access_log_settings
+- [X] delete_api
+- [ ] delete_api_mapping
+- [X] delete_authorizer
+- [X] delete_cors_configuration
+- [ ] delete_deployment
+- [ ] delete_domain_name
+- [X] delete_integration
+- [X] delete_integration_response
+- [X] delete_model
+- [X] delete_route
+- [X] delete_route_request_parameter
+- [X] delete_route_response
+- [ ] delete_route_settings
+- [ ] delete_stage
+- [X] delete_vpc_link
+- [ ] export_api
+- [X] get_api
+- [ ] get_api_mapping
+- [ ] get_api_mappings
+- [X] get_apis
+- [X] get_authorizer
+- [ ] get_authorizers
+- [ ] get_deployment
+- [ ] get_deployments
+- [ ] get_domain_name
+- [ ] get_domain_names
+- [X] get_integration
+- [X] get_integration_response
+- [X] get_integration_responses
+- [X] get_integrations
+- [X] get_model
+- [ ] get_model_template
+- [ ] get_models
+- [X] get_route
+- [X] get_route_response
+- [ ] get_route_responses
+- [X] get_routes
+- [ ] get_stage
+- [ ] get_stages
+- [X] get_tags
+- [X] get_vpc_link
+- [X] get_vpc_links
+- [ ] import_api
+- [X] reimport_api
+- [ ] reset_authorizers_cache
+- [X] tag_resource
+- [X] untag_resource
+- [X] update_api
+- [ ] update_api_mapping
+- [X] update_authorizer
+- [ ] update_deployment
+- [ ] update_domain_name
+- [X] update_integration
+- [X] update_integration_response
+- [X] update_model
+- [X] update_route
+- [ ] update_route_response
+- [ ] update_stage
+- [X] update_vpc_link
+
+
## application-autoscaling
60% implemented
@@ -3289,7 +3367,7 @@
## lambda
-41% implemented
+43% implemented
- [ ] add_layer_version_permission
- [X] add_permission
@@ -3308,7 +3386,7 @@
- [ ] delete_provisioned_concurrency_config
- [ ] get_account_settings
- [ ] get_alias
-- [ ] get_code_signing_config
+- [X] get_code_signing_config
- [X] get_event_source_mapping
- [X] get_function
- [ ] get_function_code_signing_config
@@ -5275,7 +5353,6 @@
- amplifybackend
- amplifyuibuilder
- apigatewaymanagementapi
-- apigatewayv2
- appconfig
- appconfigdata
- appflow
diff --git a/docs/docs/services/apigateway.rst b/docs/docs/services/apigateway.rst
index 7f3b66f04..0148c6c7b 100644
--- a/docs/docs/services/apigateway.rst
+++ b/docs/docs/services/apigateway.rst
@@ -41,7 +41,7 @@ apigateway
- [X] create_stage
- [X] create_usage_plan
- [X] create_usage_plan_key
-- [ ] create_vpc_link
+- [X] create_vpc_link
- [X] delete_api_key
- [X] delete_authorizer
- [X] delete_base_path_mapping
@@ -62,7 +62,7 @@ apigateway
- [X] delete_stage
- [X] delete_usage_plan
- [X] delete_usage_plan_key
-- [ ] delete_vpc_link
+- [X] delete_vpc_link
- [ ] flush_stage_authorizers_cache
- [ ] flush_stage_cache
- [ ] generate_client_certificate
@@ -110,8 +110,12 @@ apigateway
- [X] get_usage_plan_key
- [X] get_usage_plan_keys
- [X] get_usage_plans
-- [ ] get_vpc_link
-- [ ] get_vpc_links
+- [X] get_vpc_link
+- [X] get_vpc_links
+
+ Pagination has not yet been implemented
+
+
- [ ] import_api_keys
- [ ] import_documentation_parts
- [ ] import_rest_api
diff --git a/docs/docs/services/apigatewayv2.rst b/docs/docs/services/apigatewayv2.rst
new file mode 100644
index 000000000..c769ff5d4
--- /dev/null
+++ b/docs/docs/services/apigatewayv2.rst
@@ -0,0 +1,131 @@
+.. _implementedservice_apigatewayv2:
+
+.. |start-h3| raw:: html
+
+
+
+.. |end-h3| raw:: html
+
+
+
+============
+apigatewayv2
+============
+
+.. autoclass:: moto.apigatewayv2.models.ApiGatewayV2Backend
+
+|start-h3| Example usage |end-h3|
+
+.. sourcecode:: python
+
+ @mock_apigatewayv2
+ def test_apigatewayv2_behaviour:
+ boto3.client("apigatewayv2")
+ ...
+
+
+
+|start-h3| Implemented features for this service |end-h3|
+
+- [X] create_api
+
+ The following parameters are not yet implemented:
+ CredentialsArn, RouteKey, Tags, Target
+
+
+- [ ] create_api_mapping
+- [X] create_authorizer
+- [ ] create_deployment
+- [ ] create_domain_name
+- [X] create_integration
+- [X] create_integration_response
+- [X] create_model
+- [X] create_route
+- [X] create_route_response
+
+ The following parameters are not yet implemented: ResponseModels, ResponseParameters
+
+
+- [ ] create_stage
+- [X] create_vpc_link
+- [ ] delete_access_log_settings
+- [X] delete_api
+- [ ] delete_api_mapping
+- [X] delete_authorizer
+- [X] delete_cors_configuration
+- [ ] delete_deployment
+- [ ] delete_domain_name
+- [X] delete_integration
+- [X] delete_integration_response
+- [X] delete_model
+- [X] delete_route
+- [X] delete_route_request_parameter
+- [X] delete_route_response
+- [ ] delete_route_settings
+- [ ] delete_stage
+- [X] delete_vpc_link
+- [ ] export_api
+- [X] get_api
+- [ ] get_api_mapping
+- [ ] get_api_mappings
+- [X] get_apis
+
+ Pagination is not yet implemented
+
+
+- [X] get_authorizer
+- [ ] get_authorizers
+- [ ] get_deployment
+- [ ] get_deployments
+- [ ] get_domain_name
+- [ ] get_domain_names
+- [X] get_integration
+- [X] get_integration_response
+- [X] get_integration_responses
+- [X] get_integrations
+
+ Pagination is not yet implemented
+
+
+- [X] get_model
+- [ ] get_model_template
+- [ ] get_models
+- [X] get_route
+- [X] get_route_response
+- [ ] get_route_responses
+- [X] get_routes
+
+ Pagination is not yet implemented
+
+
+- [ ] get_stage
+- [ ] get_stages
+- [X] get_tags
+- [X] get_vpc_link
+- [X] get_vpc_links
+- [ ] import_api
+- [X] reimport_api
+
+ Only YAML is supported at the moment. Full OpenAPI-support is not guaranteed. Only limited validation is implemented
+
+
+- [ ] reset_authorizers_cache
+- [X] tag_resource
+- [X] untag_resource
+- [X] update_api
+
+ The following parameters have not yet been implemented: CredentialsArn, RouteKey, Target
+
+
+- [ ] update_api_mapping
+- [X] update_authorizer
+- [ ] update_deployment
+- [ ] update_domain_name
+- [X] update_integration
+- [X] update_integration_response
+- [X] update_model
+- [X] update_route
+- [ ] update_route_response
+- [ ] update_stage
+- [X] update_vpc_link
+
diff --git a/docs/docs/services/lambda.rst b/docs/docs/services/lambda.rst
index 614f38a10..466c4cc38 100644
--- a/docs/docs/services/lambda.rst
+++ b/docs/docs/services/lambda.rst
@@ -44,7 +44,7 @@ lambda
- [ ] delete_provisioned_concurrency_config
- [ ] get_account_settings
- [ ] get_alias
-- [ ] get_code_signing_config
+- [X] get_code_signing_config
- [X] get_event_source_mapping
- [X] get_function
- [ ] get_function_code_signing_config
diff --git a/moto/__init__.py b/moto/__init__.py
index bc472fca7..c42548c8c 100644
--- a/moto/__init__.py
+++ b/moto/__init__.py
@@ -26,7 +26,8 @@ def lazy_load(
mock_acm = lazy_load(".acm", "mock_acm")
mock_apigateway = lazy_load(".apigateway", "mock_apigateway")
-mock_appsync = lazy_load(".appsync", "mock_appsync", boto3_name="appsync")
+mock_apigatewayv2 = lazy_load(".apigatewayv2", "mock_apigatewayv2")
+mock_appsync = lazy_load(".appsync", "mock_appsync")
mock_athena = lazy_load(".athena", "mock_athena")
mock_applicationautoscaling = lazy_load(
".applicationautoscaling", "mock_applicationautoscaling"
@@ -63,6 +64,11 @@ mock_ec2 = lazy_load(".ec2", "mock_ec2")
mock_ec2instanceconnect = lazy_load(".ec2instanceconnect", "mock_ec2instanceconnect")
mock_ecr = lazy_load(".ecr", "mock_ecr")
mock_ecs = lazy_load(".ecs", "mock_ecs")
+mock_efs = lazy_load(".efs", "mock_efs")
+mock_eks = lazy_load(".eks", "mock_eks")
+mock_elasticache = lazy_load(
+ ".elasticache", "mock_elasticache", boto3_name="elasticache"
+)
mock_elastictranscoder = lazy_load(".elastictranscoder", "mock_elastictranscoder")
mock_elb = lazy_load(".elb", "mock_elb")
mock_elbv2 = lazy_load(".elbv2", "mock_elbv2")
@@ -81,9 +87,22 @@ mock_iam = lazy_load(".iam", "mock_iam")
mock_iot = lazy_load(".iot", "mock_iot")
mock_iotdata = lazy_load(".iotdata", "mock_iotdata", boto3_name="iot-data")
mock_kinesis = lazy_load(".kinesis", "mock_kinesis")
+mock_kinesisvideo = lazy_load(".kinesisvideo", "mock_kinesisvideo")
+mock_kinesisvideoarchivedmedia = lazy_load(
+ ".kinesisvideoarchivedmedia",
+ "mock_kinesisvideoarchivedmedia",
+ boto3_name="kinesis-video-archived-media",
+)
mock_kms = lazy_load(".kms", "mock_kms")
mock_logs = lazy_load(".logs", "mock_logs")
mock_managedblockchain = lazy_load(".managedblockchain", "mock_managedblockchain")
+mock_mediaconnect = lazy_load(".mediaconnect", "mock_mediaconnect")
+mock_medialive = lazy_load(".medialive", "mock_medialive")
+mock_mediapackage = lazy_load(".mediapackage", "mock_mediapackage")
+mock_mediastore = lazy_load(".mediastore", "mock_mediastore")
+mock_mediastoredata = lazy_load(
+ ".mediastoredata", "mock_mediastoredata", boto3_name="mediastore-data"
+)
mock_mq = lazy_load(".mq", "mock_mq", boto3_name="mq")
mock_opsworks = lazy_load(".opsworks", "mock_opsworks")
mock_organizations = lazy_load(".organizations", "mock_organizations")
@@ -105,6 +124,7 @@ mock_route53resolver = lazy_load(
mock_s3 = lazy_load(".s3", "mock_s3")
mock_s3control = lazy_load(".s3control", "mock_s3control")
mock_sagemaker = lazy_load(".sagemaker", "mock_sagemaker")
+mock_sdb = lazy_load(".sdb", "mock_sdb")
mock_secretsmanager = lazy_load(".secretsmanager", "mock_secretsmanager")
mock_ses = lazy_load(".ses", "mock_ses")
mock_sns = lazy_load(".sns", "mock_sns")
@@ -115,6 +135,7 @@ mock_stepfunctions = lazy_load(
".stepfunctions", "mock_stepfunctions", backend="stepfunction_backends"
)
mock_sts = lazy_load(".sts", "mock_sts")
+mock_support = lazy_load(".support", "mock_support")
mock_swf = lazy_load(".swf", "mock_swf")
mock_timestreamwrite = lazy_load(
".timestreamwrite", "mock_timestreamwrite", boto3_name="timestream-write"
@@ -123,27 +144,7 @@ mock_transcribe = lazy_load(".transcribe", "mock_transcribe")
XRaySegment = lazy_load(".xray", "XRaySegment")
mock_xray = lazy_load(".xray", "mock_xray")
mock_xray_client = lazy_load(".xray", "mock_xray_client")
-mock_kinesisvideo = lazy_load(".kinesisvideo", "mock_kinesisvideo")
-mock_kinesisvideoarchivedmedia = lazy_load(
- ".kinesisvideoarchivedmedia",
- "mock_kinesisvideoarchivedmedia",
- boto3_name="kinesis-video-archived-media",
-)
-mock_medialive = lazy_load(".medialive", "mock_medialive")
-mock_support = lazy_load(".support", "mock_support")
-mock_mediaconnect = lazy_load(".mediaconnect", "mock_mediaconnect")
-mock_mediapackage = lazy_load(".mediapackage", "mock_mediapackage")
-mock_mediastore = lazy_load(".mediastore", "mock_mediastore")
-mock_eks = lazy_load(".eks", "mock_eks")
-mock_mediastoredata = lazy_load(
- ".mediastoredata", "mock_mediastoredata", boto3_name="mediastore-data"
-)
-mock_efs = lazy_load(".efs", "mock_efs")
mock_wafv2 = lazy_load(".wafv2", "mock_wafv2")
-mock_sdb = lazy_load(".sdb", "mock_sdb")
-mock_elasticache = lazy_load(
- ".elasticache", "mock_elasticache", boto3_name="elasticache"
-)
class MockAll(ContextDecorator):
diff --git a/moto/apigateway/exceptions.py b/moto/apigateway/exceptions.py
index b08eb2ad8..37f970449 100644
--- a/moto/apigateway/exceptions.py
+++ b/moto/apigateway/exceptions.py
@@ -238,3 +238,10 @@ class BasePathNotFoundException(NotFoundException):
super().__init__(
"NotFoundException", "Invalid base path mapping identifier specified"
)
+
+
+class VpcLinkNotFound(NotFoundException):
+ code = 404
+
+ def __init__(self):
+ super().__init__("NotFoundException", "VPCLink not found")
diff --git a/moto/apigateway/models.py b/moto/apigateway/models.py
index a56461262..035a8dd4d 100644
--- a/moto/apigateway/models.py
+++ b/moto/apigateway/models.py
@@ -49,6 +49,7 @@ from .exceptions import (
InvalidStageException,
BasePathConflictException,
BasePathNotFoundException,
+ VpcLinkNotFound,
)
from ..core.models import responses_mock
from moto.apigateway.exceptions import MethodNotFoundException
@@ -712,6 +713,17 @@ class UsagePlanKey(BaseModel, dict):
self["value"] = value
+class VpcLink(BaseModel, dict):
+ def __init__(self, name, description, target_arns, tags):
+ super().__init__()
+ self["id"] = create_id()
+ self["name"] = name
+ self["description"] = description
+ self["targetArns"] = target_arns
+ self["tags"] = tags
+ self["status"] = "AVAILABLE"
+
+
class RestAPI(CloudFormationModel):
PROP_ID = "id"
@@ -1161,6 +1173,7 @@ class APIGatewayBackend(BaseBackend):
self.models = {}
self.region_name = region_name
self.base_path_mappings = {}
+ self.vpc_links = {}
def reset(self):
region_name = self.region_name
@@ -1881,5 +1894,26 @@ class APIGatewayBackend(BaseBackend):
return base_path_mapping
+ def create_vpc_link(self, name, description, target_arns, tags):
+ vpc_link = VpcLink(
+ name, description=description, target_arns=target_arns, tags=tags
+ )
+ self.vpc_links[vpc_link["id"]] = vpc_link
+ return vpc_link
+
+ def delete_vpc_link(self, vpc_link_id):
+ self.vpc_links.pop(vpc_link_id, None)
+
+ def get_vpc_link(self, vpc_link_id):
+ if vpc_link_id not in self.vpc_links:
+ raise VpcLinkNotFound
+ return self.vpc_links[vpc_link_id]
+
+ def get_vpc_links(self):
+ """
+ Pagination has not yet been implemented
+ """
+ return list(self.vpc_links.values())
+
apigateway_backends = BackendDict(APIGatewayBackend, "apigateway")
diff --git a/moto/apigateway/responses.py b/moto/apigateway/responses.py
index c162e8138..76398f734 100644
--- a/moto/apigateway/responses.py
+++ b/moto/apigateway/responses.py
@@ -909,3 +909,34 @@ class APIGatewayResponse(BaseResponse):
return self.error("BadRequestException", e.message)
except InvalidStageException as e:
return self.error("BadRequestException", e.message)
+
+ def vpc_link(self, request, full_url, headers):
+ self.setup_class(request, full_url, headers)
+ url_path_parts = self.path.split("/")
+ vpc_link_id = url_path_parts[-1]
+
+ try:
+ if self.method == "DELETE":
+ self.backend.delete_vpc_link(vpc_link_id=vpc_link_id)
+ return 200, {}, "{}"
+ if self.method == "GET":
+ vpc_link = self.backend.get_vpc_link(vpc_link_id=vpc_link_id)
+ return 200, {}, json.dumps(vpc_link)
+ except NotFoundException as e:
+ return self.error("NotFoundException", e.message, 404)
+
+ def vpc_links(self, request, full_url, headers):
+ self.setup_class(request, full_url, headers)
+
+ if self.method == "GET":
+ vpc_links = self.backend.get_vpc_links()
+ return 200, {}, json.dumps({"item": vpc_links})
+ if self.method == "POST":
+ name = self._get_param("name")
+ description = self._get_param("description")
+ target_arns = self._get_param("targetArns")
+ tags = self._get_param("tags")
+ vpc_link = self.backend.create_vpc_link(
+ name=name, description=description, target_arns=target_arns, tags=tags
+ )
+ return 200, {}, json.dumps(vpc_link)
diff --git a/moto/apigateway/urls.py b/moto/apigateway/urls.py
index 4ab409038..089bb610a 100644
--- a/moto/apigateway/urls.py
+++ b/moto/apigateway/urls.py
@@ -1,4 +1,5 @@
from .responses import APIGatewayResponse
+from ..apigatewayv2.urls import url_paths as url_paths_v2
response = APIGatewayResponse()
@@ -34,4 +35,9 @@ url_paths = {
"{0}/usageplans/(?P[^/]+)/keys/(?P[^/]+)/?$": response.usage_plan_key_individual,
"{0}/restapis/(?P[^/]+)/requestvalidators$": response.request_validators,
"{0}/restapis/(?P[^/]+)/requestvalidators/(?P[^/]+)/?$": response.request_validator_individual,
+ "{0}/vpclinks$": response.vpc_links,
+ "{0}/vpclinks/(?P[^/]+)": response.vpc_link,
}
+
+# Also manages the APIGatewayV2
+url_paths.update(url_paths_v2)
diff --git a/moto/apigatewayv2/__init__.py b/moto/apigatewayv2/__init__.py
new file mode 100644
index 000000000..2ddfce55b
--- /dev/null
+++ b/moto/apigatewayv2/__init__.py
@@ -0,0 +1,5 @@
+"""apigatewayv2 module initialization; sets value for base decorator."""
+from .models import apigatewayv2_backends
+from ..core.models import base_decorator
+
+mock_apigatewayv2 = base_decorator(apigatewayv2_backends)
diff --git a/moto/apigatewayv2/exceptions.py b/moto/apigatewayv2/exceptions.py
new file mode 100644
index 000000000..795805026
--- /dev/null
+++ b/moto/apigatewayv2/exceptions.py
@@ -0,0 +1,95 @@
+from moto.core.exceptions import JsonRESTError
+
+
+class APIGatewayV2Error(JsonRESTError):
+ pass
+
+
+class ApiNotFound(APIGatewayV2Error):
+ code = 404
+
+ def __init__(self, api_id):
+ super().__init__(
+ "NotFoundException", f"Invalid API identifier specified {api_id}"
+ )
+
+
+class AuthorizerNotFound(APIGatewayV2Error):
+ code = 404
+
+ def __init__(self, authorizer_id):
+ super().__init__(
+ "NotFoundException",
+ f"Invalid Authorizer identifier specified {authorizer_id}",
+ )
+
+
+class ModelNotFound(APIGatewayV2Error):
+ code = 404
+
+ def __init__(self, model_id):
+ super().__init__(
+ "NotFoundException", f"Invalid Model identifier specified {model_id}"
+ )
+
+
+class RouteResponseNotFound(APIGatewayV2Error):
+ code = 404
+
+ def __init__(self, rr_id):
+ super().__init__(
+ "NotFoundException", f"Invalid RouteResponse identifier specified {rr_id}"
+ )
+
+
+class BadRequestException(APIGatewayV2Error):
+ code = 400
+
+ def __init__(self, message):
+ super().__init__("BadRequestException", message)
+
+
+class IntegrationNotFound(APIGatewayV2Error):
+ code = 404
+
+ def __init__(self, integration_id):
+ super().__init__(
+ "NotFoundException",
+ f"Invalid Integration identifier specified {integration_id}",
+ )
+
+
+class IntegrationResponseNotFound(APIGatewayV2Error):
+ code = 404
+
+ def __init__(self, int_res_id):
+ super().__init__(
+ "NotFoundException",
+ f"Invalid IntegrationResponse identifier specified {int_res_id}",
+ )
+
+
+class RouteNotFound(APIGatewayV2Error):
+ code = 404
+
+ def __init__(self, route_id):
+ super().__init__(
+ "NotFoundException", f"Invalid Route identifier specified {route_id}"
+ )
+
+
+class VpcLinkNotFound(APIGatewayV2Error):
+ code = 404
+
+ def __init__(self, vpc_link_id):
+ super().__init__(
+ "NotFoundException", f"Invalid VpcLink identifier specified {vpc_link_id}"
+ )
+
+
+class UnknownProtocol(APIGatewayV2Error):
+ def __init__(self):
+ super().__init__(
+ "BadRequestException",
+ "Invalid protocol specified. Must be one of [HTTP, WEBSOCKET]",
+ )
diff --git a/moto/apigatewayv2/models.py b/moto/apigatewayv2/models.py
new file mode 100644
index 000000000..031473d60
--- /dev/null
+++ b/moto/apigatewayv2/models.py
@@ -0,0 +1,1484 @@
+"""ApiGatewayV2Backend class with methods for supported APIs."""
+import random
+import string
+import yaml
+
+from moto.core import BaseBackend, BaseModel
+from moto.core.utils import BackendDict, unix_time
+from moto.utilities.tagging_service import TaggingService
+
+from .exceptions import (
+ ApiNotFound,
+ AuthorizerNotFound,
+ BadRequestException,
+ ModelNotFound,
+ RouteResponseNotFound,
+ IntegrationNotFound,
+ IntegrationResponseNotFound,
+ RouteNotFound,
+ VpcLinkNotFound,
+)
+
+
+class Authorizer(BaseModel):
+ def __init__(
+ self,
+ auth_creds_arn,
+ auth_payload_format_version,
+ auth_result_ttl,
+ authorizer_type,
+ authorizer_uri,
+ enable_simple_response,
+ identity_source,
+ identity_validation_expr,
+ jwt_config,
+ name,
+ ):
+ self.id = "".join(random.choice(string.ascii_lowercase) for _ in range(8))
+ self.auth_creds_arn = auth_creds_arn
+ self.auth_payload_format_version = auth_payload_format_version
+ self.auth_result_ttl = auth_result_ttl
+ self.authorizer_type = authorizer_type
+ self.authorizer_uri = authorizer_uri
+ self.enable_simple_response = enable_simple_response
+ self.identity_source = identity_source
+ self.identity_validation_expr = identity_validation_expr
+ self.jwt_config = jwt_config
+ self.name = name
+
+ def update(
+ self,
+ auth_creds_arn,
+ auth_payload_format_version,
+ auth_result_ttl,
+ authorizer_type,
+ authorizer_uri,
+ enable_simple_response,
+ identity_source,
+ identity_validation_expr,
+ jwt_config,
+ name,
+ ):
+ if auth_creds_arn is not None:
+ self.auth_creds_arn = auth_creds_arn
+ if auth_payload_format_version is not None:
+ self.auth_payload_format_version = auth_payload_format_version
+ if auth_result_ttl is not None:
+ self.auth_result_ttl = auth_result_ttl
+ if authorizer_type is not None:
+ self.authorizer_type is authorizer_type
+ if authorizer_uri is not None:
+ self.authorizer_uri = authorizer_uri
+ if enable_simple_response is not None:
+ self.enable_simple_response = enable_simple_response
+ if identity_source is not None:
+ self.identity_source = identity_source
+ if identity_validation_expr is not None:
+ self.identity_validation_expr = identity_validation_expr
+ if jwt_config is not None:
+ self.jwt_config = jwt_config
+ if name is not None:
+ self.name = name
+
+ def to_json(self):
+ return {
+ "authorizerId": self.id,
+ "authorizerCredentialsArn": self.auth_creds_arn,
+ "authorizerPayloadFormatVersion": self.auth_payload_format_version,
+ "authorizerResultTtlInSeconds": self.auth_result_ttl,
+ "authorizerType": self.authorizer_type,
+ "authorizerUri": self.authorizer_uri,
+ "enableSimpleResponses": self.enable_simple_response,
+ "identitySource": self.identity_source,
+ "identityValidationExpression": self.identity_validation_expr,
+ "jwtConfiguration": self.jwt_config,
+ "name": self.name,
+ }
+
+
+class Integration(BaseModel):
+ def __init__(
+ self,
+ connection_id,
+ connection_type,
+ content_handling_strategy,
+ credentials_arn,
+ description,
+ integration_method,
+ integration_type,
+ integration_uri,
+ passthrough_behavior,
+ payload_format_version,
+ integration_subtype,
+ request_parameters,
+ request_templates,
+ response_parameters,
+ template_selection_expression,
+ timeout_in_millis,
+ tls_config,
+ ):
+ self.id = "".join(random.choice(string.ascii_lowercase) for _ in range(8))
+ self.connection_id = connection_id
+ self.connection_type = connection_type
+ self.content_handling_strategy = content_handling_strategy
+ self.credentials_arn = credentials_arn
+ self.description = description
+ self.integration_method = integration_method
+ self.integration_response_selection_expression = None
+ self.integration_type = integration_type
+ self.integration_subtype = integration_subtype
+ self.integration_uri = integration_uri
+ self.passthrough_behavior = passthrough_behavior
+ self.payload_format_version = payload_format_version
+ self.request_parameters = request_parameters
+ self.request_templates = request_templates
+ self.response_parameters = response_parameters
+ self.template_selection_expression = template_selection_expression
+ self.timeout_in_millis = timeout_in_millis
+ self.tls_config = tls_config
+
+ if self.integration_type in ["MOCK", "HTTP"]:
+ self.integration_response_selection_expression = (
+ "${integration.response.statuscode}"
+ )
+ elif self.integration_type in ["AWS"]:
+ self.integration_response_selection_expression = (
+ "${integration.response.body.errorMessage}"
+ )
+ if (
+ self.integration_type in ["AWS", "MOCK", "HTTP"]
+ and self.passthrough_behavior is None
+ ):
+ self.passthrough_behavior = "WHEN_NO_MATCH"
+ if self.integration_uri is not None and self.integration_method is None:
+ self.integration_method = "POST"
+ if self.integration_type in ["AWS", "MOCK"]:
+ self.timeout_in_millis = self.timeout_in_millis or 29000
+ else:
+ self.timeout_in_millis = self.timeout_in_millis or 30000
+
+ self.responses = dict()
+
+ def create_response(
+ self,
+ content_handling_strategy,
+ integration_response_key,
+ response_parameters,
+ response_templates,
+ template_selection_expression,
+ ):
+ response = IntegrationResponse(
+ content_handling_strategy=content_handling_strategy,
+ integration_response_key=integration_response_key,
+ response_parameters=response_parameters,
+ response_templates=response_templates,
+ template_selection_expression=template_selection_expression,
+ )
+ self.responses[response.id] = response
+ return response
+
+ def delete_response(self, integration_response_id):
+ self.responses.pop(integration_response_id)
+
+ def get_response(self, integration_response_id):
+ if integration_response_id not in self.responses:
+ raise IntegrationResponseNotFound(integration_response_id)
+ return self.responses[integration_response_id]
+
+ def get_responses(self):
+ return self.responses.values()
+
+ def update_response(
+ self,
+ integration_response_id,
+ content_handling_strategy,
+ integration_response_key,
+ response_parameters,
+ response_templates,
+ template_selection_expression,
+ ):
+ int_response = self.responses[integration_response_id]
+ int_response.update(
+ content_handling_strategy=content_handling_strategy,
+ integration_response_key=integration_response_key,
+ response_parameters=response_parameters,
+ response_templates=response_templates,
+ template_selection_expression=template_selection_expression,
+ )
+ return int_response
+
+ def update(
+ self,
+ connection_id,
+ connection_type,
+ content_handling_strategy,
+ credentials_arn,
+ description,
+ integration_method,
+ integration_type,
+ integration_uri,
+ passthrough_behavior,
+ payload_format_version,
+ integration_subtype,
+ request_parameters,
+ request_templates,
+ response_parameters,
+ template_selection_expression,
+ timeout_in_millis,
+ tls_config,
+ ):
+ if connection_id is not None:
+ self.connection_id = connection_id
+ if connection_type is not None:
+ self.connection_type = connection_type
+ if content_handling_strategy is not None:
+ self.content_handling_strategy = content_handling_strategy
+ if credentials_arn is not None:
+ self.credentials_arn = credentials_arn
+ if description is not None:
+ self.description = description
+ if integration_method is not None:
+ self.integration_method = integration_method
+ if integration_type is not None:
+ self.integration_type = integration_type
+ if integration_uri is not None:
+ self.integration_uri = integration_uri
+ if passthrough_behavior is not None:
+ self.passthrough_behavior = passthrough_behavior
+ if payload_format_version is not None:
+ self.payload_format_version = payload_format_version
+ if integration_subtype is not None:
+ self.integration_subtype = integration_subtype
+ if request_parameters is not None:
+ # Skip parameters with an empty value
+ req_params = {
+ key: value for (key, value) in request_parameters.items() if value
+ }
+ self.request_parameters = req_params
+ if request_templates is not None:
+ self.request_templates = request_templates
+ if response_parameters is not None:
+ self.response_parameters = response_parameters
+ if template_selection_expression is not None:
+ self.template_selection_expression = template_selection_expression
+ if timeout_in_millis is not None:
+ self.timeout_in_millis = timeout_in_millis
+ if tls_config is not None:
+ self.tls_config = tls_config
+
+ def to_json(self):
+ return {
+ "connectionId": self.connection_id,
+ "connectionType": self.connection_type,
+ "contentHandlingStrategy": self.content_handling_strategy,
+ "credentialsArn": self.credentials_arn,
+ "description": self.description,
+ "integrationId": self.id,
+ "integrationMethod": self.integration_method,
+ "integrationResponseSelectionExpression": self.integration_response_selection_expression,
+ "integrationType": self.integration_type,
+ "integrationSubtype": self.integration_subtype,
+ "integrationUri": self.integration_uri,
+ "passthroughBehavior": self.passthrough_behavior,
+ "payloadFormatVersion": self.payload_format_version,
+ "requestParameters": self.request_parameters,
+ "requestTemplates": self.request_templates,
+ "responseParameters": self.response_parameters,
+ "templateSelectionExpression": self.template_selection_expression,
+ "timeoutInMillis": self.timeout_in_millis,
+ "tlsConfig": self.tls_config,
+ }
+
+
+class IntegrationResponse(BaseModel):
+ def __init__(
+ self,
+ content_handling_strategy,
+ integration_response_key,
+ response_parameters,
+ response_templates,
+ template_selection_expression,
+ ):
+ self.id = "".join(random.choice(string.ascii_lowercase) for _ in range(8))
+ self.content_handling_strategy = content_handling_strategy
+ self.integration_response_key = integration_response_key
+ self.response_parameters = response_parameters
+ self.response_templates = response_templates
+ self.template_selection_expression = template_selection_expression
+
+ def update(
+ self,
+ content_handling_strategy,
+ integration_response_key,
+ response_parameters,
+ response_templates,
+ template_selection_expression,
+ ):
+ if content_handling_strategy is not None:
+ self.content_handling_strategy = content_handling_strategy
+ if integration_response_key is not None:
+ self.integration_response_key = integration_response_key
+ if response_parameters is not None:
+ self.response_parameters = response_parameters
+ if response_templates is not None:
+ self.response_templates = response_templates
+ if template_selection_expression is not None:
+ self.template_selection_expression = template_selection_expression
+
+ def to_json(self):
+ return {
+ "integrationResponseId": self.id,
+ "integrationResponseKey": self.integration_response_key,
+ "contentHandlingStrategy": self.content_handling_strategy,
+ "responseParameters": self.response_parameters,
+ "responseTemplates": self.response_templates,
+ "templateSelectionExpression": self.template_selection_expression,
+ }
+
+
+class Model(BaseModel):
+ def __init__(self, content_type, description, name, schema):
+ self.id = "".join(random.choice(string.ascii_lowercase) for _ in range(8))
+ self.content_type = content_type
+ self.description = description
+ self.name = name
+ self.schema = schema
+
+ def update(self, content_type, description, name, schema):
+ if content_type is not None:
+ self.content_type = content_type
+ if description is not None:
+ self.description = description
+ if name is not None:
+ self.name = name
+ if schema is not None:
+ self.schema = schema
+
+ def to_json(self):
+ return {
+ "modelId": self.id,
+ "contentType": self.content_type,
+ "description": self.description,
+ "name": self.name,
+ "schema": self.schema,
+ }
+
+
+class RouteResponse(BaseModel):
+ def __init__(self, route_response_key, model_selection_expression, response_models):
+ self.id = "".join(random.choice(string.ascii_lowercase) for _ in range(8))
+ self.route_response_key = route_response_key
+ self.model_selection_expression = model_selection_expression
+ self.response_models = response_models
+
+ def to_json(self):
+ return {
+ "modelSelectionExpression": self.model_selection_expression,
+ "responseModels": self.response_models,
+ "routeResponseId": self.id,
+ "routeResponseKey": self.route_response_key,
+ }
+
+
+class Route(BaseModel):
+ def __init__(
+ self,
+ api_key_required,
+ authorization_scopes,
+ authorization_type,
+ authorizer_id,
+ model_selection_expression,
+ operation_name,
+ request_models,
+ request_parameters,
+ route_key,
+ route_response_selection_expression,
+ target,
+ ):
+ self.route_id = "".join(random.choice(string.ascii_lowercase) for _ in range(8))
+ self.api_key_required = api_key_required
+ self.authorization_scopes = authorization_scopes
+ self.authorization_type = authorization_type
+ self.authorizer_id = authorizer_id
+ self.model_selection_expression = model_selection_expression
+ self.operation_name = operation_name
+ self.request_models = request_models
+ self.request_parameters = request_parameters or {}
+ self.route_key = route_key
+ self.route_response_selection_expression = route_response_selection_expression
+ self.target = target
+
+ self.route_responses = dict()
+
+ def create_route_response(
+ self, route_response_key, model_selection_expression, response_models
+ ):
+ route_response = RouteResponse(
+ route_response_key,
+ model_selection_expression=model_selection_expression,
+ response_models=response_models,
+ )
+ self.route_responses[route_response.id] = route_response
+ return route_response
+
+ def get_route_response(self, route_response_id):
+ if route_response_id not in self.route_responses:
+ raise RouteResponseNotFound(route_response_id)
+ return self.route_responses[route_response_id]
+
+ def delete_route_response(self, route_response_id):
+ self.route_responses.pop(route_response_id, None)
+
+ def delete_route_request_parameter(self, request_param):
+ del self.request_parameters[request_param]
+
+ def update(
+ self,
+ api_key_required,
+ authorization_scopes,
+ authorization_type,
+ authorizer_id,
+ model_selection_expression,
+ operation_name,
+ request_models,
+ request_parameters,
+ route_key,
+ route_response_selection_expression,
+ target,
+ ):
+ if api_key_required is not None:
+ self.api_key_required = api_key_required
+ if authorization_scopes:
+ self.authorization_scopes = authorization_scopes
+ if authorization_type:
+ self.authorization_type = authorization_type
+ if authorizer_id is not None:
+ self.authorizer_id = authorizer_id
+ if model_selection_expression:
+ self.model_selection_expression = model_selection_expression
+ if operation_name is not None:
+ self.operation_name = operation_name
+ if request_models:
+ self.request_models = request_models
+ if request_parameters:
+ self.request_parameters = request_parameters
+ if route_key:
+ self.route_key = route_key
+ if route_response_selection_expression is not None:
+ self.route_response_selection_expression = (
+ route_response_selection_expression
+ )
+ if target:
+ self.target = target
+
+ def to_json(self):
+ return {
+ "apiKeyRequired": self.api_key_required,
+ "authorizationScopes": self.authorization_scopes,
+ "authorizationType": self.authorization_type,
+ "authorizerId": self.authorizer_id,
+ "modelSelectionExpression": self.model_selection_expression,
+ "operationName": self.operation_name,
+ "requestModels": self.request_models,
+ "requestParameters": self.request_parameters,
+ "routeId": self.route_id,
+ "routeKey": self.route_key,
+ "routeResponseSelectionExpression": self.route_response_selection_expression,
+ "target": self.target,
+ }
+
+
+class Api(BaseModel):
+ def __init__(
+ self,
+ region,
+ name,
+ api_key_selection_expression,
+ cors_configuration,
+ description,
+ disable_execute_api_endpoint,
+ disable_schema_validation,
+ protocol_type,
+ route_selection_expression,
+ tags,
+ version,
+ backend,
+ ):
+ self.api_id = "".join(random.choice(string.ascii_lowercase) for _ in range(8))
+ self.api_endpoint = f"https://{self.api_id}.execute-api.{region}.amazonaws.com"
+ self.backend = backend
+ self.name = name
+ self.api_key_selection_expression = (
+ api_key_selection_expression or "$request.header.x-api-key"
+ )
+ self.created_date = unix_time()
+ self.cors_configuration = cors_configuration
+ self.description = description
+ self.disable_execute_api_endpoint = disable_execute_api_endpoint or False
+ self.disable_schema_validation = disable_schema_validation
+ self.protocol_type = protocol_type
+ self.route_selection_expression = (
+ route_selection_expression or "$request.method $request.path"
+ )
+ self.version = version
+
+ self.authorizers = dict()
+ self.integrations = dict()
+ self.models = dict()
+ self.routes = dict()
+
+ self.arn = f"arn:aws:apigateway:{region}::/apis/{self.api_id}"
+ self.backend.tag_resource(self.arn, tags)
+
+ def clear(self):
+ self.authorizers = dict()
+ self.integrations = dict()
+ self.models = dict()
+ self.routes = dict()
+
+ def delete_cors_configuration(self):
+ self.cors_configuration = None
+
+ def create_authorizer(
+ self,
+ auth_creds_arn,
+ auth_payload_format_version,
+ auth_result_ttl,
+ authorizer_type,
+ authorizer_uri,
+ enable_simple_response,
+ identity_source,
+ identity_validation_expr,
+ jwt_config,
+ name,
+ ):
+ authorizer = Authorizer(
+ auth_creds_arn=auth_creds_arn,
+ auth_payload_format_version=auth_payload_format_version,
+ auth_result_ttl=auth_result_ttl,
+ authorizer_type=authorizer_type,
+ authorizer_uri=authorizer_uri,
+ enable_simple_response=enable_simple_response,
+ identity_source=identity_source,
+ identity_validation_expr=identity_validation_expr,
+ jwt_config=jwt_config,
+ name=name,
+ )
+ self.authorizers[authorizer.id] = authorizer
+ return authorizer
+
+ def delete_authorizer(self, authorizer_id):
+ self.authorizers.pop(authorizer_id, None)
+
+ def get_authorizer(self, authorizer_id):
+ if authorizer_id not in self.authorizers:
+ raise AuthorizerNotFound(authorizer_id)
+ return self.authorizers[authorizer_id]
+
+ def update_authorizer(
+ self,
+ authorizer_id,
+ auth_creds_arn,
+ auth_payload_format_version,
+ auth_result_ttl,
+ authorizer_type,
+ authorizer_uri,
+ enable_simple_response,
+ identity_source,
+ identity_validation_expr,
+ jwt_config,
+ name,
+ ):
+ authorizer = self.authorizers[authorizer_id]
+ authorizer.update(
+ auth_creds_arn=auth_creds_arn,
+ auth_payload_format_version=auth_payload_format_version,
+ auth_result_ttl=auth_result_ttl,
+ authorizer_type=authorizer_type,
+ authorizer_uri=authorizer_uri,
+ enable_simple_response=enable_simple_response,
+ identity_source=identity_source,
+ identity_validation_expr=identity_validation_expr,
+ jwt_config=jwt_config,
+ name=name,
+ )
+ return authorizer
+
+ def create_model(self, content_type, description, name, schema):
+ model = Model(content_type, description, name, schema)
+ self.models[model.id] = model
+ return model
+
+ def delete_model(self, model_id):
+ self.models.pop(model_id, None)
+
+ def get_model(self, model_id):
+ if model_id not in self.models:
+ raise ModelNotFound(model_id)
+ return self.models[model_id]
+
+ def update_model(self, model_id, content_type, description, name, schema):
+ model = self.models[model_id]
+ model.update(content_type, description, name, schema)
+ return model
+
+ def import_api(self, body, fail_on_warnings):
+ self.clear()
+ body = yaml.safe_load(body)
+ for path, path_details in body.get("paths", {}).items():
+ for method, method_details in path_details.items():
+ route_key = f"{method.upper()} {path}"
+ for int_type, type_details in method_details.items():
+ if int_type == "responses":
+ for status_code, response_details in type_details.items():
+ content = response_details.get("content", {})
+ for content_type in content.values():
+ for ref in content_type.get("schema", {}).values():
+ if ref not in self.models and fail_on_warnings:
+ attr = f"paths.'{path}'({method}).{int_type}.{status_code}.content.schema.{ref}"
+ raise BadRequestException(
+ f"Warnings found during import:\n\tParse issue: attribute {attr} is missing"
+ )
+ if int_type == "x-amazon-apigateway-integration":
+ integration = self.create_integration(
+ connection_type="INTERNET",
+ description="AutoCreate from OpenAPI Import",
+ integration_type=type_details.get("type"),
+ integration_method=type_details.get("httpMethod"),
+ payload_format_version=type_details.get(
+ "payloadFormatVersion"
+ ),
+ integration_uri=type_details.get("uri"),
+ )
+ self.create_route(
+ api_key_required=False,
+ authorization_scopes=[],
+ route_key=route_key,
+ target=f"integrations/{integration.id}",
+ )
+ if "title" in body.get("info", {}):
+ self.name = body["info"]["title"]
+ if "version" in body.get("info", {}):
+ self.version = str(body["info"]["version"])
+ if "x-amazon-apigateway-cors" in body:
+ self.cors_configuration = body["x-amazon-apigateway-cors"]
+
+ def update(
+ self,
+ api_key_selection_expression,
+ cors_configuration,
+ description,
+ disable_schema_validation,
+ disable_execute_api_endpoint,
+ name,
+ route_selection_expression,
+ version,
+ ):
+ if api_key_selection_expression is not None:
+ self.api_key_selection_expression = api_key_selection_expression
+ if cors_configuration is not None:
+ self.cors_configuration = cors_configuration
+ if description is not None:
+ self.description = description
+ if disable_execute_api_endpoint is not None:
+ self.disable_execute_api_endpoint = disable_execute_api_endpoint
+ if disable_schema_validation is not None:
+ self.disable_schema_validation = disable_schema_validation
+ if name is not None:
+ self.name = name
+ if route_selection_expression is not None:
+ self.route_selection_expression = route_selection_expression
+ if version is not None:
+ self.version = version
+
+ def create_integration(
+ self,
+ connection_type,
+ description,
+ integration_method,
+ integration_type,
+ integration_uri,
+ connection_id=None,
+ content_handling_strategy=None,
+ credentials_arn=None,
+ passthrough_behavior=None,
+ payload_format_version=None,
+ integration_subtype=None,
+ request_parameters=None,
+ request_templates=None,
+ response_parameters=None,
+ template_selection_expression=None,
+ timeout_in_millis=None,
+ tls_config=None,
+ ):
+ integration = Integration(
+ connection_id=connection_id,
+ connection_type=connection_type,
+ content_handling_strategy=content_handling_strategy,
+ credentials_arn=credentials_arn,
+ description=description,
+ integration_method=integration_method,
+ integration_type=integration_type,
+ integration_uri=integration_uri,
+ passthrough_behavior=passthrough_behavior,
+ payload_format_version=payload_format_version,
+ integration_subtype=integration_subtype,
+ request_parameters=request_parameters,
+ request_templates=request_templates,
+ response_parameters=response_parameters,
+ template_selection_expression=template_selection_expression,
+ timeout_in_millis=timeout_in_millis,
+ tls_config=tls_config,
+ )
+ self.integrations[integration.id] = integration
+ return integration
+
+ def delete_integration(self, integration_id):
+ self.integrations.pop(integration_id, None)
+
+ def get_integration(self, integration_id):
+ if integration_id not in self.integrations:
+ raise IntegrationNotFound(integration_id)
+ return self.integrations[integration_id]
+
+ def get_integrations(self):
+ return self.integrations.values()
+
+ def update_integration(
+ self,
+ integration_id,
+ connection_id,
+ connection_type,
+ content_handling_strategy,
+ credentials_arn,
+ description,
+ integration_method,
+ integration_type,
+ integration_uri,
+ passthrough_behavior,
+ payload_format_version,
+ integration_subtype,
+ request_parameters,
+ request_templates,
+ response_parameters,
+ template_selection_expression,
+ timeout_in_millis,
+ tls_config,
+ ):
+ integration = self.integrations[integration_id]
+ integration.update(
+ connection_id=connection_id,
+ connection_type=connection_type,
+ content_handling_strategy=content_handling_strategy,
+ credentials_arn=credentials_arn,
+ description=description,
+ integration_method=integration_method,
+ integration_type=integration_type,
+ integration_uri=integration_uri,
+ passthrough_behavior=passthrough_behavior,
+ payload_format_version=payload_format_version,
+ integration_subtype=integration_subtype,
+ request_parameters=request_parameters,
+ request_templates=request_templates,
+ response_parameters=response_parameters,
+ template_selection_expression=template_selection_expression,
+ timeout_in_millis=timeout_in_millis,
+ tls_config=tls_config,
+ )
+ return integration
+
+ def create_integration_response(
+ self,
+ integration_id,
+ content_handling_strategy,
+ integration_response_key,
+ response_parameters,
+ response_templates,
+ template_selection_expression,
+ ):
+ integration = self.get_integration(integration_id)
+ return integration.create_response(
+ content_handling_strategy=content_handling_strategy,
+ integration_response_key=integration_response_key,
+ response_parameters=response_parameters,
+ response_templates=response_templates,
+ template_selection_expression=template_selection_expression,
+ )
+
+ def delete_integration_response(self, integration_id, integration_response_id):
+ integration = self.get_integration(integration_id)
+ integration.delete_response(integration_response_id)
+
+ def get_integration_response(self, integration_id, integration_response_id):
+ integration = self.get_integration(integration_id)
+ return integration.get_response(integration_response_id)
+
+ def get_integration_responses(self, integration_id):
+ integration = self.get_integration(integration_id)
+ return integration.get_responses()
+
+ def update_integration_response(
+ self,
+ integration_id,
+ integration_response_id,
+ content_handling_strategy,
+ integration_response_key,
+ response_parameters,
+ response_templates,
+ template_selection_expression,
+ ):
+ integration = self.get_integration(integration_id)
+ return integration.update_response(
+ integration_response_id=integration_response_id,
+ content_handling_strategy=content_handling_strategy,
+ integration_response_key=integration_response_key,
+ response_parameters=response_parameters,
+ response_templates=response_templates,
+ template_selection_expression=template_selection_expression,
+ )
+
+ def create_route(
+ self,
+ api_key_required,
+ authorization_scopes,
+ route_key,
+ target,
+ authorization_type=None,
+ authorizer_id=None,
+ model_selection_expression=None,
+ operation_name=None,
+ request_models=None,
+ request_parameters=None,
+ route_response_selection_expression=None,
+ ):
+ route = Route(
+ api_key_required=api_key_required,
+ authorization_scopes=authorization_scopes,
+ authorization_type=authorization_type,
+ authorizer_id=authorizer_id,
+ model_selection_expression=model_selection_expression,
+ operation_name=operation_name,
+ request_models=request_models,
+ request_parameters=request_parameters,
+ route_key=route_key,
+ route_response_selection_expression=route_response_selection_expression,
+ target=target,
+ )
+ self.routes[route.route_id] = route
+ return route
+
+ def delete_route(self, route_id):
+ self.routes.pop(route_id, None)
+
+ def delete_route_request_parameter(self, route_id, request_param):
+ route = self.get_route(route_id)
+ route.delete_route_request_parameter(request_param)
+
+ def get_route(self, route_id):
+ if route_id not in self.routes:
+ raise RouteNotFound(route_id)
+ return self.routes[route_id]
+
+ def get_routes(self):
+ return self.routes.values()
+
+ def update_route(
+ self,
+ route_id,
+ api_key_required,
+ authorization_scopes,
+ authorization_type,
+ authorizer_id,
+ model_selection_expression,
+ operation_name,
+ request_models,
+ request_parameters,
+ route_key,
+ route_response_selection_expression,
+ target,
+ ):
+ route = self.get_route(route_id)
+ route.update(
+ api_key_required=api_key_required,
+ authorization_scopes=authorization_scopes,
+ authorization_type=authorization_type,
+ authorizer_id=authorizer_id,
+ model_selection_expression=model_selection_expression,
+ operation_name=operation_name,
+ request_models=request_models,
+ request_parameters=request_parameters,
+ route_key=route_key,
+ route_response_selection_expression=route_response_selection_expression,
+ target=target,
+ )
+ return route
+
+ def create_route_response(
+ self, route_id, route_response_key, model_selection_expression, response_models
+ ):
+ route = self.get_route(route_id)
+ return route.create_route_response(
+ route_response_key,
+ model_selection_expression=model_selection_expression,
+ response_models=response_models,
+ )
+
+ def delete_route_response(self, route_id, route_response_id):
+ route = self.get_route(route_id)
+ route.delete_route_response(route_response_id)
+
+ def get_route_response(self, route_id, route_response_id):
+ route = self.get_route(route_id)
+ return route.get_route_response(route_response_id)
+
+ def to_json(self):
+ return {
+ "apiId": self.api_id,
+ "apiEndpoint": self.api_endpoint,
+ "apiKeySelectionExpression": self.api_key_selection_expression,
+ "createdDate": self.created_date,
+ "corsConfiguration": self.cors_configuration,
+ "description": self.description,
+ "disableExecuteApiEndpoint": self.disable_execute_api_endpoint,
+ "disableSchemaValidation": self.disable_schema_validation,
+ "name": self.name,
+ "protocolType": self.protocol_type,
+ "routeSelectionExpression": self.route_selection_expression,
+ "tags": self.backend.get_tags(self.arn),
+ "version": self.version,
+ }
+
+
+class VpcLink(BaseModel):
+ def __init__(self, name, sg_ids, subnet_ids, tags, backend):
+ self.created = unix_time()
+ self.id = "".join(random.choice(string.ascii_lowercase) for _ in range(8))
+ self.name = name
+ self.sg_ids = sg_ids
+ self.subnet_ids = subnet_ids
+
+ self.arn = f"arn:aws:apigateway:{backend.region_name}::/vpclinks/{self.id}"
+ self.backend = backend
+ self.backend.tag_resource(self.arn, tags)
+
+ def update(self, name):
+ self.name = name
+
+ def to_json(self):
+ return {
+ "createdDate": self.created,
+ "name": self.name,
+ "securityGroupIds": self.sg_ids,
+ "subnetIds": self.subnet_ids,
+ "tags": self.backend.get_tags(self.arn),
+ "vpcLinkId": self.id,
+ "vpcLinkStatus": "AVAILABLE",
+ "vpcLinkVersion": "V2",
+ }
+
+
+class ApiGatewayV2Backend(BaseBackend):
+ """Implementation of ApiGatewayV2 APIs."""
+
+ def __init__(self, region_name=None):
+ self.region_name = region_name
+ self.apis = dict()
+ self.vpc_links = dict()
+ self.tagger = TaggingService()
+
+ def reset(self):
+ """Re-initialize all attributes for this instance."""
+ region_name = self.region_name
+ self.__dict__ = {}
+ self.__init__(region_name)
+
+ def create_api(
+ self,
+ api_key_selection_expression,
+ cors_configuration,
+ credentials_arn,
+ description,
+ disable_schema_validation,
+ disable_execute_api_endpoint,
+ name,
+ protocol_type,
+ route_key,
+ route_selection_expression,
+ tags,
+ target,
+ version,
+ ):
+ """
+ The following parameters are not yet implemented:
+ CredentialsArn, RouteKey, Tags, Target
+ """
+ api = Api(
+ region=self.region_name,
+ cors_configuration=cors_configuration,
+ description=description,
+ name=name,
+ api_key_selection_expression=api_key_selection_expression,
+ disable_execute_api_endpoint=disable_execute_api_endpoint,
+ disable_schema_validation=disable_schema_validation,
+ protocol_type=protocol_type,
+ route_selection_expression=route_selection_expression,
+ tags=tags,
+ version=version,
+ backend=self,
+ )
+ self.apis[api.api_id] = api
+ return api
+
+ def delete_api(self, api_id):
+ self.apis.pop(api_id, None)
+
+ def get_api(self, api_id):
+ if api_id not in self.apis:
+ raise ApiNotFound(api_id)
+ return self.apis[api_id]
+
+ def get_apis(self):
+ """
+ Pagination is not yet implemented
+ """
+ return self.apis.values()
+
+ def update_api(
+ self,
+ api_id,
+ api_key_selection_expression,
+ cors_configuration,
+ description,
+ disable_schema_validation,
+ disable_execute_api_endpoint,
+ name,
+ route_selection_expression,
+ version,
+ ):
+ """
+ The following parameters have not yet been implemented: CredentialsArn, RouteKey, Target
+ """
+ api = self.get_api(api_id)
+ api.update(
+ api_key_selection_expression=api_key_selection_expression,
+ cors_configuration=cors_configuration,
+ description=description,
+ disable_schema_validation=disable_schema_validation,
+ disable_execute_api_endpoint=disable_execute_api_endpoint,
+ name=name,
+ route_selection_expression=route_selection_expression,
+ version=version,
+ )
+ return api
+
+ def reimport_api(self, api_id, body, fail_on_warnings):
+ """
+ Only YAML is supported at the moment. Full OpenAPI-support is not guaranteed. Only limited validation is implemented
+ """
+ api = self.get_api(api_id)
+ api.import_api(body, fail_on_warnings)
+ return api
+
+ def delete_cors_configuration(self, api_id):
+ api = self.get_api(api_id)
+ api.delete_cors_configuration()
+
+ def create_authorizer(
+ self,
+ api_id,
+ auth_creds_arn,
+ auth_payload_format_version,
+ auth_result_ttl,
+ authorizer_uri,
+ authorizer_type,
+ enable_simple_response,
+ identity_source,
+ identity_validation_expr,
+ jwt_config,
+ name,
+ ):
+ api = self.get_api(api_id)
+ authorizer = api.create_authorizer(
+ auth_creds_arn=auth_creds_arn,
+ auth_payload_format_version=auth_payload_format_version,
+ auth_result_ttl=auth_result_ttl,
+ authorizer_type=authorizer_type,
+ authorizer_uri=authorizer_uri,
+ enable_simple_response=enable_simple_response,
+ identity_source=identity_source,
+ identity_validation_expr=identity_validation_expr,
+ jwt_config=jwt_config,
+ name=name,
+ )
+ return authorizer
+
+ def delete_authorizer(self, api_id, authorizer_id):
+ api = self.get_api(api_id)
+ api.delete_authorizer(authorizer_id=authorizer_id)
+
+ def get_authorizer(self, api_id, authorizer_id):
+ api = self.get_api(api_id)
+ authorizer = api.get_authorizer(authorizer_id=authorizer_id)
+ return authorizer
+
+ def update_authorizer(
+ self,
+ api_id,
+ authorizer_id,
+ auth_creds_arn,
+ auth_payload_format_version,
+ auth_result_ttl,
+ authorizer_uri,
+ authorizer_type,
+ enable_simple_response,
+ identity_source,
+ identity_validation_expr,
+ jwt_config,
+ name,
+ ):
+ api = self.get_api(api_id)
+ authorizer = api.update_authorizer(
+ authorizer_id=authorizer_id,
+ auth_creds_arn=auth_creds_arn,
+ auth_payload_format_version=auth_payload_format_version,
+ auth_result_ttl=auth_result_ttl,
+ authorizer_type=authorizer_type,
+ authorizer_uri=authorizer_uri,
+ enable_simple_response=enable_simple_response,
+ identity_source=identity_source,
+ identity_validation_expr=identity_validation_expr,
+ jwt_config=jwt_config,
+ name=name,
+ )
+ return authorizer
+
+ def create_model(self, api_id, content_type, description, name, schema):
+ api = self.get_api(api_id)
+ model = api.create_model(
+ content_type=content_type, description=description, name=name, schema=schema
+ )
+ return model
+
+ def delete_model(self, api_id, model_id):
+ api = self.get_api(api_id)
+ api.delete_model(model_id=model_id)
+
+ def get_model(self, api_id, model_id):
+ api = self.get_api(api_id)
+ return api.get_model(model_id)
+
+ def update_model(self, api_id, model_id, content_type, description, name, schema):
+ api = self.get_api(api_id)
+ return api.update_model(model_id, content_type, description, name, schema)
+
+ def get_tags(self, resource_id):
+ return self.tagger.get_tag_dict_for_resource(resource_id)
+
+ def tag_resource(self, resource_arn, tags):
+ tags = TaggingService.convert_dict_to_tags_input(tags or {})
+ self.tagger.tag_resource(resource_arn, tags)
+
+ def untag_resource(self, resource_arn, tag_keys):
+ self.tagger.untag_resource_using_names(resource_arn, tag_keys)
+
+ def create_route(
+ self,
+ api_id,
+ api_key_required,
+ authorization_scopes,
+ authorization_type,
+ authorizer_id,
+ model_selection_expression,
+ operation_name,
+ request_models,
+ request_parameters,
+ route_key,
+ route_response_selection_expression,
+ target,
+ ):
+ api = self.get_api(api_id)
+ route = api.create_route(
+ api_key_required=api_key_required,
+ authorization_scopes=authorization_scopes,
+ authorization_type=authorization_type,
+ authorizer_id=authorizer_id,
+ model_selection_expression=model_selection_expression,
+ operation_name=operation_name,
+ request_models=request_models,
+ request_parameters=request_parameters,
+ route_key=route_key,
+ route_response_selection_expression=route_response_selection_expression,
+ target=target,
+ )
+ return route
+
+ def delete_route(self, api_id, route_id):
+ api = self.get_api(api_id)
+ api.delete_route(route_id)
+
+ def delete_route_request_parameter(self, api_id, route_id, request_param):
+ api = self.get_api(api_id)
+ api.delete_route_request_parameter(route_id, request_param)
+
+ def get_route(self, api_id, route_id):
+ api = self.get_api(api_id)
+ return api.get_route(route_id)
+
+ def get_routes(self, api_id):
+ """
+ Pagination is not yet implemented
+ """
+ api = self.get_api(api_id)
+ return api.get_routes()
+
+ def update_route(
+ self,
+ api_id,
+ api_key_required,
+ authorization_scopes,
+ authorization_type,
+ authorizer_id,
+ model_selection_expression,
+ operation_name,
+ request_models,
+ request_parameters,
+ route_id,
+ route_key,
+ route_response_selection_expression,
+ target,
+ ):
+ api = self.get_api(api_id)
+ route = api.update_route(
+ route_id=route_id,
+ api_key_required=api_key_required,
+ authorization_scopes=authorization_scopes,
+ authorization_type=authorization_type,
+ authorizer_id=authorizer_id,
+ model_selection_expression=model_selection_expression,
+ operation_name=operation_name,
+ request_models=request_models,
+ request_parameters=request_parameters,
+ route_key=route_key,
+ route_response_selection_expression=route_response_selection_expression,
+ target=target,
+ )
+ return route
+
+ def create_route_response(
+ self,
+ api_id,
+ route_id,
+ route_response_key,
+ model_selection_expression,
+ response_models,
+ ):
+ """
+ The following parameters are not yet implemented: ResponseModels, ResponseParameters
+ """
+ api = self.get_api(api_id)
+ return api.create_route_response(
+ route_id,
+ route_response_key,
+ model_selection_expression=model_selection_expression,
+ response_models=response_models,
+ )
+
+ def delete_route_response(self, api_id, route_id, route_response_id):
+ api = self.get_api(api_id)
+ api.delete_route_response(route_id, route_response_id)
+
+ def get_route_response(self, api_id, route_id, route_response_id):
+ api = self.get_api(api_id)
+ return api.get_route_response(route_id, route_response_id)
+
+ def create_integration(
+ self,
+ api_id,
+ connection_id,
+ connection_type,
+ content_handling_strategy,
+ credentials_arn,
+ description,
+ integration_method,
+ integration_subtype,
+ integration_type,
+ integration_uri,
+ passthrough_behavior,
+ payload_format_version,
+ request_parameters,
+ request_templates,
+ response_parameters,
+ template_selection_expression,
+ timeout_in_millis,
+ tls_config,
+ ):
+ api = self.get_api(api_id)
+ integration = api.create_integration(
+ connection_id=connection_id,
+ connection_type=connection_type,
+ content_handling_strategy=content_handling_strategy,
+ credentials_arn=credentials_arn,
+ description=description,
+ integration_method=integration_method,
+ integration_type=integration_type,
+ integration_uri=integration_uri,
+ passthrough_behavior=passthrough_behavior,
+ payload_format_version=payload_format_version,
+ integration_subtype=integration_subtype,
+ request_parameters=request_parameters,
+ request_templates=request_templates,
+ response_parameters=response_parameters,
+ template_selection_expression=template_selection_expression,
+ timeout_in_millis=timeout_in_millis,
+ tls_config=tls_config,
+ )
+ return integration
+
+ def get_integration(self, api_id, integration_id):
+ api = self.get_api(api_id)
+ integration = api.get_integration(integration_id)
+ return integration
+
+ def get_integrations(self, api_id):
+ """
+ Pagination is not yet implemented
+ """
+ api = self.get_api(api_id)
+ return api.get_integrations()
+
+ def delete_integration(self, api_id, integration_id):
+ api = self.get_api(api_id)
+ api.delete_integration(integration_id)
+
+ def update_integration(
+ self,
+ api_id,
+ connection_id,
+ connection_type,
+ content_handling_strategy,
+ credentials_arn,
+ description,
+ integration_id,
+ integration_method,
+ integration_subtype,
+ integration_type,
+ integration_uri,
+ passthrough_behavior,
+ payload_format_version,
+ request_parameters,
+ request_templates,
+ response_parameters,
+ template_selection_expression,
+ timeout_in_millis,
+ tls_config,
+ ):
+ api = self.get_api(api_id)
+ integration = api.update_integration(
+ integration_id=integration_id,
+ connection_id=connection_id,
+ connection_type=connection_type,
+ content_handling_strategy=content_handling_strategy,
+ credentials_arn=credentials_arn,
+ description=description,
+ integration_method=integration_method,
+ integration_type=integration_type,
+ integration_uri=integration_uri,
+ passthrough_behavior=passthrough_behavior,
+ payload_format_version=payload_format_version,
+ integration_subtype=integration_subtype,
+ request_parameters=request_parameters,
+ request_templates=request_templates,
+ response_parameters=response_parameters,
+ template_selection_expression=template_selection_expression,
+ timeout_in_millis=timeout_in_millis,
+ tls_config=tls_config,
+ )
+ return integration
+
+ def create_integration_response(
+ self,
+ api_id,
+ integration_id,
+ content_handling_strategy,
+ integration_response_key,
+ response_parameters,
+ response_templates,
+ template_selection_expression,
+ ):
+ api = self.get_api(api_id)
+ integration_response = api.create_integration_response(
+ integration_id=integration_id,
+ content_handling_strategy=content_handling_strategy,
+ integration_response_key=integration_response_key,
+ response_parameters=response_parameters,
+ response_templates=response_templates,
+ template_selection_expression=template_selection_expression,
+ )
+ return integration_response
+
+ def delete_integration_response(
+ self, api_id, integration_id, integration_response_id
+ ):
+ api = self.get_api(api_id)
+ api.delete_integration_response(
+ integration_id, integration_response_id=integration_response_id
+ )
+
+ def get_integration_response(self, api_id, integration_id, integration_response_id):
+ api = self.get_api(api_id)
+ return api.get_integration_response(
+ integration_id, integration_response_id=integration_response_id
+ )
+
+ def get_integration_responses(self, api_id, integration_id):
+ api = self.get_api(api_id)
+ return api.get_integration_responses(integration_id)
+
+ def update_integration_response(
+ self,
+ api_id,
+ integration_id,
+ integration_response_id,
+ content_handling_strategy,
+ integration_response_key,
+ response_parameters,
+ response_templates,
+ template_selection_expression,
+ ):
+ api = self.get_api(api_id)
+ integration_response = api.update_integration_response(
+ integration_id=integration_id,
+ integration_response_id=integration_response_id,
+ content_handling_strategy=content_handling_strategy,
+ integration_response_key=integration_response_key,
+ response_parameters=response_parameters,
+ response_templates=response_templates,
+ template_selection_expression=template_selection_expression,
+ )
+ return integration_response
+
+ def create_vpc_link(self, name, sg_ids, subnet_ids, tags):
+ vpc_link = VpcLink(
+ name, sg_ids=sg_ids, subnet_ids=subnet_ids, tags=tags, backend=self
+ )
+ self.vpc_links[vpc_link.id] = vpc_link
+ return vpc_link
+
+ def get_vpc_link(self, vpc_link_id):
+ if vpc_link_id not in self.vpc_links:
+ raise VpcLinkNotFound(vpc_link_id)
+ return self.vpc_links[vpc_link_id]
+
+ def delete_vpc_link(self, vpc_link_id):
+ self.vpc_links.pop(vpc_link_id, None)
+
+ def get_vpc_links(self):
+ return self.vpc_links.values()
+
+ def update_vpc_link(self, vpc_link_id, name):
+ vpc_link = self.get_vpc_link(vpc_link_id)
+ vpc_link.update(name)
+ return vpc_link
+
+
+apigatewayv2_backends = BackendDict(ApiGatewayV2Backend, "apigatewayv2")
diff --git a/moto/apigatewayv2/responses.py b/moto/apigatewayv2/responses.py
new file mode 100644
index 000000000..7811d0ff6
--- /dev/null
+++ b/moto/apigatewayv2/responses.py
@@ -0,0 +1,780 @@
+"""Handles incoming apigatewayv2 requests, invokes methods, returns responses."""
+import json
+
+from functools import wraps
+from moto.core.responses import BaseResponse
+from urllib.parse import unquote
+
+from .exceptions import APIGatewayV2Error, UnknownProtocol
+from .models import apigatewayv2_backends
+
+
+def error_handler(f):
+ @wraps(f)
+ def _wrapper(*args, **kwargs):
+ try:
+ return f(*args, **kwargs)
+ except APIGatewayV2Error as e:
+ return e.code, e.get_headers(), e.get_body()
+
+ return _wrapper
+
+
+class ApiGatewayV2Response(BaseResponse):
+ """Handler for ApiGatewayV2 requests and responses."""
+
+ @property
+ def apigatewayv2_backend(self):
+ """Return backend instance specific for this region."""
+ return apigatewayv2_backends[self.region]
+
+ @error_handler
+ def apis(self, request, full_url, headers):
+ self.setup_class(request, full_url, headers)
+
+ if self.method == "POST":
+ return self.create_api()
+ if self.method == "GET":
+ return self.get_apis()
+
+ @error_handler
+ def api(self, request, full_url, headers):
+ self.setup_class(request, full_url, headers)
+
+ if self.method == "GET":
+ return self.get_api()
+ if self.method == "PATCH":
+ return self.update_api()
+ if self.method == "PUT":
+ return self.reimport_api()
+ if self.method == "DELETE":
+ return self.delete_api()
+
+ @error_handler
+ def authorizer(self, request, full_url, headers):
+ self.setup_class(request, full_url, headers)
+
+ if self.method == "DELETE":
+ return self.delete_authorizer()
+ if self.method == "GET":
+ return self.get_authorizer()
+ if self.method == "PATCH":
+ return self.update_authorizer()
+
+ def authorizers(self, request, full_url, headers):
+ self.setup_class(request, full_url, headers)
+
+ if self.method == "POST":
+ return self.create_authorizer()
+
+ @error_handler
+ def cors(self, request, full_url, headers):
+ self.setup_class(request, full_url, headers)
+
+ if self.method == "DELETE":
+ return self.delete_cors_configuration()
+
+ def route_request_parameter(self, request, full_url, headers):
+ self.setup_class(request, full_url, headers)
+
+ if self.method == "DELETE":
+ return self.delete_route_request_parameter()
+
+ @error_handler
+ def model(self, request, full_url, headers):
+ self.setup_class(request, full_url, headers)
+
+ if self.method == "DELETE":
+ return self.delete_model()
+ if self.method == "GET":
+ return self.get_model()
+ if self.method == "PATCH":
+ return self.update_model()
+
+ def models(self, request, full_url, headers):
+ self.setup_class(request, full_url, headers)
+
+ if self.method == "POST":
+ return self.create_model()
+
+ @error_handler
+ def integration(self, request, full_url, headers):
+ self.setup_class(request, full_url, headers)
+
+ if self.method == "DELETE":
+ return self.delete_integration()
+ if self.method == "GET":
+ return self.get_integration()
+ if self.method == "PATCH":
+ return self.update_integration()
+
+ @error_handler
+ def integrations(self, request, full_url, headers):
+ self.setup_class(request, full_url, headers)
+
+ if self.method == "GET":
+ return self.get_integrations()
+ if self.method == "POST":
+ return self.create_integration()
+
+ @error_handler
+ def integration_response(self, request, full_url, headers):
+ self.setup_class(request, full_url, headers)
+
+ if self.method == "DELETE":
+ return self.delete_integration_response()
+ if self.method == "GET":
+ return self.get_integration_response()
+ if self.method == "PATCH":
+ return self.update_integration_response()
+
+ def integration_responses(self, request, full_url, headers):
+ self.setup_class(request, full_url, headers)
+
+ if self.method == "GET":
+ return self.get_integration_responses()
+ if self.method == "POST":
+ return self.create_integration_response()
+
+ @error_handler
+ def route(self, request, full_url, headers):
+ self.setup_class(request, full_url, headers)
+
+ if self.method == "DELETE":
+ return self.delete_route()
+ if self.method == "GET":
+ return self.get_route()
+ if self.method == "PATCH":
+ return self.update_route()
+
+ @error_handler
+ def routes(self, request, full_url, headers):
+ self.setup_class(request, full_url, headers)
+
+ if self.method == "GET":
+ return self.get_routes()
+ if self.method == "POST":
+ return self.create_route()
+
+ @error_handler
+ def route_response(self, request, full_url, headers):
+ self.setup_class(request, full_url, headers)
+
+ if self.method == "DELETE":
+ return self.delete_route_response()
+ if self.method == "GET":
+ return self.get_route_response()
+
+ def route_responses(self, request, full_url, headers):
+ self.setup_class(request, full_url, headers)
+
+ if self.method == "POST":
+ return self.create_route_response()
+
+ @error_handler
+ def tags(self, request, full_url, headers):
+ self.setup_class(request, full_url, headers)
+
+ if self.method == "POST":
+ return self.tag_resource()
+ if self.method == "GET":
+ return self.get_tags()
+ if self.method == "DELETE":
+ return self.untag_resource()
+
+ @error_handler
+ def vpc_link(self, request, full_url, headers):
+ self.setup_class(request, full_url, headers)
+
+ if request.method == "DELETE":
+ return self.delete_vpc_link()
+ if request.method == "GET":
+ return self.get_vpc_link()
+ if request.method == "PATCH":
+ return self.update_vpc_link()
+
+ def vpc_links(self, request, full_url, headers):
+ self.setup_class(request, full_url, headers)
+
+ if request.method == "GET":
+ return self.get_vpc_links()
+ if request.method == "POST":
+ return self.create_vpc_link()
+
+ def create_api(self):
+ params = json.loads(self.body)
+
+ api_key_selection_expression = params.get("apiKeySelectionExpression")
+ cors_configuration = params.get("corsConfiguration")
+ credentials_arn = params.get("credentialsArn")
+ description = params.get("description")
+ disable_schema_validation = params.get("disableSchemaValidation")
+ disable_execute_api_endpoint = params.get("disableExecuteApiEndpoint")
+ name = params.get("name")
+ protocol_type = params.get("protocolType")
+ route_key = params.get("routeKey")
+ route_selection_expression = params.get("routeSelectionExpression")
+ tags = params.get("tags")
+ target = params.get("target")
+ version = params.get("version")
+
+ if protocol_type not in ["HTTP", "WEBSOCKET"]:
+ raise UnknownProtocol
+
+ api = self.apigatewayv2_backend.create_api(
+ api_key_selection_expression=api_key_selection_expression,
+ cors_configuration=cors_configuration,
+ credentials_arn=credentials_arn,
+ description=description,
+ disable_schema_validation=disable_schema_validation,
+ disable_execute_api_endpoint=disable_execute_api_endpoint,
+ name=name,
+ protocol_type=protocol_type,
+ route_key=route_key,
+ route_selection_expression=route_selection_expression,
+ tags=tags,
+ target=target,
+ version=version,
+ )
+ return 200, {}, json.dumps(api.to_json())
+
+ def delete_api(self):
+ api_id = self.path.split("/")[-1]
+ self.apigatewayv2_backend.delete_api(api_id=api_id)
+ return 200, "", "{}"
+
+ def get_api(self):
+ api_id = self.path.split("/")[-1]
+ api = self.apigatewayv2_backend.get_api(api_id=api_id)
+ return 200, {}, json.dumps(api.to_json())
+
+ def get_apis(self):
+ apis = self.apigatewayv2_backend.get_apis()
+ return 200, {}, json.dumps({"items": [a.to_json() for a in apis]})
+
+ def update_api(self):
+ api_id = self.path.split("/")[-1]
+ params = json.loads(self.body)
+ api_key_selection_expression = params.get("apiKeySelectionExpression")
+ cors_configuration = params.get("corsConfiguration")
+ description = params.get("description")
+ disable_schema_validation = params.get("disableSchemaValidation")
+ disable_execute_api_endpoint = params.get("disableExecuteApiEndpoint")
+ name = params.get("name")
+ route_selection_expression = params.get("routeSelectionExpression")
+ version = params.get("version")
+ api = self.apigatewayv2_backend.update_api(
+ api_id=api_id,
+ api_key_selection_expression=api_key_selection_expression,
+ cors_configuration=cors_configuration,
+ description=description,
+ disable_schema_validation=disable_schema_validation,
+ disable_execute_api_endpoint=disable_execute_api_endpoint,
+ name=name,
+ route_selection_expression=route_selection_expression,
+ version=version,
+ )
+ return 200, {}, json.dumps(api.to_json())
+
+ def reimport_api(self):
+ api_id = self.path.split("/")[-1]
+ params = json.loads(self.body)
+ body = params.get("body")
+ fail_on_warnings = (
+ str(self._get_param("failOnWarnings", "false")).lower() == "true"
+ )
+
+ api = self.apigatewayv2_backend.reimport_api(api_id, body, fail_on_warnings)
+ return 201, {}, json.dumps(api.to_json())
+
+ def create_authorizer(self):
+ api_id = self.path.split("/")[-2]
+ params = json.loads(self.body)
+
+ auth_creds_arn = params.get("authorizerCredentialsArn")
+ auth_payload_format_version = params.get("authorizerPayloadFormatVersion")
+ auth_result_ttl = params.get("authorizerResultTtlInSeconds")
+ authorizer_type = params.get("authorizerType")
+ authorizer_uri = params.get("authorizerUri")
+ enable_simple_response = params.get("enableSimpleResponses")
+ identity_source = params.get("identitySource")
+ identity_validation_expr = params.get("identityValidationExpression")
+ jwt_config = params.get("jwtConfiguration")
+ name = params.get("name")
+ authorizer = self.apigatewayv2_backend.create_authorizer(
+ api_id,
+ auth_creds_arn=auth_creds_arn,
+ auth_payload_format_version=auth_payload_format_version,
+ auth_result_ttl=auth_result_ttl,
+ authorizer_type=authorizer_type,
+ authorizer_uri=authorizer_uri,
+ enable_simple_response=enable_simple_response,
+ identity_source=identity_source,
+ identity_validation_expr=identity_validation_expr,
+ jwt_config=jwt_config,
+ name=name,
+ )
+ return 200, {}, json.dumps(authorizer.to_json())
+
+ def delete_authorizer(self):
+ api_id = self.path.split("/")[-3]
+ authorizer_id = self.path.split("/")[-1]
+
+ self.apigatewayv2_backend.delete_authorizer(api_id, authorizer_id)
+ return 200, {}, "{}"
+
+ def get_authorizer(self):
+ api_id = self.path.split("/")[-3]
+ authorizer_id = self.path.split("/")[-1]
+
+ authorizer = self.apigatewayv2_backend.get_authorizer(api_id, authorizer_id)
+ return 200, {}, json.dumps(authorizer.to_json())
+
+ def update_authorizer(self):
+ api_id = self.path.split("/")[-3]
+ authorizer_id = self.path.split("/")[-1]
+ params = json.loads(self.body)
+
+ auth_creds_arn = params.get("authorizerCredentialsArn")
+ auth_payload_format_version = params.get("authorizerPayloadFormatVersion")
+ auth_result_ttl = params.get("authorizerResultTtlInSeconds")
+ authorizer_type = params.get("authorizerType")
+ authorizer_uri = params.get("authorizerUri")
+ enable_simple_response = params.get("enableSimpleResponses")
+ identity_source = params.get("identitySource")
+ identity_validation_expr = params.get("identityValidationExpression")
+ jwt_config = params.get("jwtConfiguration")
+ name = params.get("name")
+ authorizer = self.apigatewayv2_backend.update_authorizer(
+ api_id,
+ authorizer_id=authorizer_id,
+ auth_creds_arn=auth_creds_arn,
+ auth_payload_format_version=auth_payload_format_version,
+ auth_result_ttl=auth_result_ttl,
+ authorizer_type=authorizer_type,
+ authorizer_uri=authorizer_uri,
+ enable_simple_response=enable_simple_response,
+ identity_source=identity_source,
+ identity_validation_expr=identity_validation_expr,
+ jwt_config=jwt_config,
+ name=name,
+ )
+ return 200, {}, json.dumps(authorizer.to_json())
+
+ def delete_cors_configuration(self):
+ api_id = self.path.split("/")[-2]
+ self.apigatewayv2_backend.delete_cors_configuration(api_id)
+ return 200, {}, "{}"
+
+ def create_model(self):
+ api_id = self.path.split("/")[-2]
+ params = json.loads(self.body)
+
+ content_type = params.get("contentType")
+ description = params.get("description")
+ name = params.get("name")
+ schema = params.get("schema")
+ model = self.apigatewayv2_backend.create_model(
+ api_id, content_type, description, name, schema
+ )
+ return 200, {}, json.dumps(model.to_json())
+
+ def delete_model(self):
+ api_id = self.path.split("/")[-3]
+ model_id = self.path.split("/")[-1]
+
+ self.apigatewayv2_backend.delete_model(api_id, model_id)
+ return 200, {}, "{}"
+
+ def get_model(self):
+ api_id = self.path.split("/")[-3]
+ model_id = self.path.split("/")[-1]
+
+ model = self.apigatewayv2_backend.get_model(api_id, model_id)
+ return 200, {}, json.dumps(model.to_json())
+
+ def update_model(self):
+ api_id = self.path.split("/")[-3]
+ model_id = self.path.split("/")[-1]
+ params = json.loads(self.body)
+
+ content_type = params.get("contentType")
+ description = params.get("description")
+ name = params.get("name")
+ schema = params.get("schema")
+
+ model = self.apigatewayv2_backend.update_model(
+ api_id,
+ model_id,
+ content_type=content_type,
+ description=description,
+ name=name,
+ schema=schema,
+ )
+ return 200, {}, json.dumps(model.to_json())
+
+ def get_tags(self):
+ resource_arn = unquote(self.path.split("/tags/")[1])
+ tags = self.apigatewayv2_backend.get_tags(resource_arn)
+ return 200, {}, json.dumps({"tags": tags})
+
+ def tag_resource(self):
+ resource_arn = unquote(self.path.split("/tags/")[1])
+ tags = json.loads(self.body).get("tags", {})
+ self.apigatewayv2_backend.tag_resource(resource_arn, tags)
+ return 201, {}, "{}"
+
+ def untag_resource(self):
+ resource_arn = unquote(self.path.split("/tags/")[1])
+ tag_keys = self.querystring.get("tagKeys")
+ self.apigatewayv2_backend.untag_resource(resource_arn, tag_keys)
+ return 200, {}, "{}"
+
+ def create_route(self):
+ api_id = self.path.split("/")[-2]
+ params = json.loads(self.body)
+ api_key_required = params.get("apiKeyRequired", False)
+ authorization_scopes = params.get("authorizationScopes")
+ authorization_type = params.get("authorizationType", "NONE")
+ authorizer_id = params.get("authorizerId")
+ model_selection_expression = params.get("modelSelectionExpression")
+ operation_name = params.get("operationName")
+ request_models = params.get("requestModels")
+ request_parameters = params.get("requestParameters")
+ route_key = params.get("routeKey")
+ route_response_selection_expression = params.get(
+ "routeResponseSelectionExpression"
+ )
+ target = params.get("target")
+ route = self.apigatewayv2_backend.create_route(
+ api_id=api_id,
+ api_key_required=api_key_required,
+ authorization_scopes=authorization_scopes,
+ authorization_type=authorization_type,
+ authorizer_id=authorizer_id,
+ model_selection_expression=model_selection_expression,
+ operation_name=operation_name,
+ request_models=request_models,
+ request_parameters=request_parameters,
+ route_key=route_key,
+ route_response_selection_expression=route_response_selection_expression,
+ target=target,
+ )
+ return 201, {}, json.dumps(route.to_json())
+
+ def delete_route(self):
+ api_id = self.path.split("/")[-3]
+ route_id = self.path.split("/")[-1]
+ self.apigatewayv2_backend.delete_route(api_id=api_id, route_id=route_id)
+ return 200, {}, "{}"
+
+ def delete_route_request_parameter(self):
+ api_id = self.path.split("/")[-5]
+ route_id = self.path.split("/")[-3]
+ request_param = self.path.split("/")[-1]
+ self.apigatewayv2_backend.delete_route_request_parameter(
+ api_id, route_id, request_param
+ )
+ return 200, {}, "{}"
+
+ def get_route(self):
+ api_id = self.path.split("/")[-3]
+ route_id = self.path.split("/")[-1]
+ api = self.apigatewayv2_backend.get_route(api_id=api_id, route_id=route_id)
+ return 200, {}, json.dumps(api.to_json())
+
+ def get_routes(self):
+ api_id = self.path.split("/")[-2]
+ apis = self.apigatewayv2_backend.get_routes(api_id=api_id)
+ return 200, {}, json.dumps({"items": [api.to_json() for api in apis]})
+
+ def update_route(self):
+ api_id = self.path.split("/")[-3]
+ route_id = self.path.split("/")[-1]
+
+ params = json.loads(self.body)
+ api_key_required = params.get("apiKeyRequired")
+ authorization_scopes = params.get("authorizationScopes")
+ authorization_type = params.get("authorizationType")
+ authorizer_id = params.get("authorizerId")
+ model_selection_expression = params.get("modelSelectionExpression")
+ operation_name = params.get("operationName")
+ request_models = params.get("requestModels")
+ request_parameters = params.get("requestParameters")
+ route_key = params.get("routeKey")
+ route_response_selection_expression = params.get(
+ "routeResponseSelectionExpression"
+ )
+ target = params.get("target")
+ api = self.apigatewayv2_backend.update_route(
+ api_id=api_id,
+ api_key_required=api_key_required,
+ authorization_scopes=authorization_scopes,
+ authorization_type=authorization_type,
+ authorizer_id=authorizer_id,
+ model_selection_expression=model_selection_expression,
+ operation_name=operation_name,
+ request_models=request_models,
+ request_parameters=request_parameters,
+ route_id=route_id,
+ route_key=route_key,
+ route_response_selection_expression=route_response_selection_expression,
+ target=target,
+ )
+ return 200, {}, json.dumps(api.to_json())
+
+ def create_route_response(self):
+ api_id = self.path.split("/")[-4]
+ route_id = self.path.split("/")[-2]
+ params = json.loads(self.body)
+
+ response_models = params.get("responseModels")
+ route_response_key = params.get("routeResponseKey")
+ model_selection_expression = params.get("modelSelectionExpression")
+ route_response = self.apigatewayv2_backend.create_route_response(
+ api_id,
+ route_id,
+ route_response_key,
+ model_selection_expression=model_selection_expression,
+ response_models=response_models,
+ )
+ return 200, {}, json.dumps(route_response.to_json())
+
+ def delete_route_response(self):
+ api_id = self.path.split("/")[-5]
+ route_id = self.path.split("/")[-3]
+ route_response_id = self.path.split("/")[-1]
+
+ self.apigatewayv2_backend.delete_route_response(
+ api_id, route_id, route_response_id
+ )
+ return 200, {}, "{}"
+
+ def get_route_response(self):
+ api_id = self.path.split("/")[-5]
+ route_id = self.path.split("/")[-3]
+ route_response_id = self.path.split("/")[-1]
+
+ route_response = self.apigatewayv2_backend.get_route_response(
+ api_id, route_id, route_response_id
+ )
+ return 200, {}, json.dumps(route_response.to_json())
+
+ def create_integration(self):
+ api_id = self.path.split("/")[-2]
+
+ params = json.loads(self.body)
+ connection_id = params.get("connectionId")
+ connection_type = params.get("connectionType")
+ content_handling_strategy = params.get("contentHandlingStrategy")
+ credentials_arn = params.get("credentialsArn")
+ description = params.get("description")
+ integration_method = params.get("integrationMethod")
+ integration_subtype = params.get("integrationSubtype")
+ integration_type = params.get("integrationType")
+ integration_uri = params.get("integrationUri")
+ passthrough_behavior = params.get("passthroughBehavior")
+ payload_format_version = params.get("payloadFormatVersion")
+ request_parameters = params.get("requestParameters")
+ request_templates = params.get("requestTemplates")
+ response_parameters = params.get("responseParameters")
+ template_selection_expression = params.get("templateSelectionExpression")
+ timeout_in_millis = params.get("timeoutInMillis")
+ tls_config = params.get("tlsConfig")
+ integration = self.apigatewayv2_backend.create_integration(
+ api_id=api_id,
+ connection_id=connection_id,
+ connection_type=connection_type,
+ content_handling_strategy=content_handling_strategy,
+ credentials_arn=credentials_arn,
+ description=description,
+ integration_method=integration_method,
+ integration_subtype=integration_subtype,
+ integration_type=integration_type,
+ integration_uri=integration_uri,
+ passthrough_behavior=passthrough_behavior,
+ payload_format_version=payload_format_version,
+ request_parameters=request_parameters,
+ request_templates=request_templates,
+ response_parameters=response_parameters,
+ template_selection_expression=template_selection_expression,
+ timeout_in_millis=timeout_in_millis,
+ tls_config=tls_config,
+ )
+ return 200, {}, json.dumps(integration.to_json())
+
+ def get_integration(self):
+ api_id = self.path.split("/")[-3]
+ integration_id = self.path.split("/")[-1]
+
+ integration = self.apigatewayv2_backend.get_integration(
+ api_id=api_id, integration_id=integration_id
+ )
+ return 200, {}, json.dumps(integration.to_json())
+
+ def get_integrations(self):
+ api_id = self.path.split("/")[-2]
+
+ integrations = self.apigatewayv2_backend.get_integrations(api_id=api_id)
+ return 200, {}, json.dumps({"items": [i.to_json() for i in integrations]})
+
+ def delete_integration(self):
+ api_id = self.path.split("/")[-3]
+ integration_id = self.path.split("/")[-1]
+
+ self.apigatewayv2_backend.delete_integration(
+ api_id=api_id, integration_id=integration_id,
+ )
+ return 200, {}, "{}"
+
+ def update_integration(self):
+ api_id = self.path.split("/")[-3]
+ integration_id = self.path.split("/")[-1]
+
+ params = json.loads(self.body)
+ connection_id = params.get("connectionId")
+ connection_type = params.get("connectionType")
+ content_handling_strategy = params.get("contentHandlingStrategy")
+ credentials_arn = params.get("credentialsArn")
+ description = params.get("description")
+ integration_method = params.get("integrationMethod")
+ integration_subtype = params.get("integrationSubtype")
+ integration_type = params.get("integrationType")
+ integration_uri = params.get("integrationUri")
+ passthrough_behavior = params.get("passthroughBehavior")
+ payload_format_version = params.get("payloadFormatVersion")
+ request_parameters = params.get("requestParameters")
+ request_templates = params.get("requestTemplates")
+ response_parameters = params.get("responseParameters")
+ template_selection_expression = params.get("templateSelectionExpression")
+ timeout_in_millis = params.get("timeoutInMillis")
+ tls_config = params.get("tlsConfig")
+ integration = self.apigatewayv2_backend.update_integration(
+ api_id=api_id,
+ connection_id=connection_id,
+ connection_type=connection_type,
+ content_handling_strategy=content_handling_strategy,
+ credentials_arn=credentials_arn,
+ description=description,
+ integration_id=integration_id,
+ integration_method=integration_method,
+ integration_subtype=integration_subtype,
+ integration_type=integration_type,
+ integration_uri=integration_uri,
+ passthrough_behavior=passthrough_behavior,
+ payload_format_version=payload_format_version,
+ request_parameters=request_parameters,
+ request_templates=request_templates,
+ response_parameters=response_parameters,
+ template_selection_expression=template_selection_expression,
+ timeout_in_millis=timeout_in_millis,
+ tls_config=tls_config,
+ )
+ return 200, {}, json.dumps(integration.to_json())
+
+ def create_integration_response(self):
+ api_id = self.path.split("/")[-4]
+ int_id = self.path.split("/")[-2]
+
+ params = json.loads(self.body)
+ content_handling_strategy = params.get("contentHandlingStrategy")
+ integration_response_key = params.get("integrationResponseKey")
+ response_parameters = params.get("responseParameters")
+ response_templates = params.get("responseTemplates")
+ template_selection_expression = params.get("templateSelectionExpression")
+ integration_response = self.apigatewayv2_backend.create_integration_response(
+ api_id=api_id,
+ integration_id=int_id,
+ content_handling_strategy=content_handling_strategy,
+ integration_response_key=integration_response_key,
+ response_parameters=response_parameters,
+ response_templates=response_templates,
+ template_selection_expression=template_selection_expression,
+ )
+ return 200, {}, json.dumps(integration_response.to_json())
+
+ def delete_integration_response(self):
+ api_id = self.path.split("/")[-5]
+ int_id = self.path.split("/")[-3]
+ int_res_id = self.path.split("/")[-1]
+
+ self.apigatewayv2_backend.delete_integration_response(
+ api_id=api_id, integration_id=int_id, integration_response_id=int_res_id
+ )
+ return 200, {}, "{}"
+
+ def get_integration_response(self):
+ api_id = self.path.split("/")[-5]
+ int_id = self.path.split("/")[-3]
+ int_res_id = self.path.split("/")[-1]
+
+ int_response = self.apigatewayv2_backend.get_integration_response(
+ api_id=api_id, integration_id=int_id, integration_response_id=int_res_id
+ )
+ return 200, {}, json.dumps(int_response.to_json())
+
+ def get_integration_responses(self):
+ api_id = self.path.split("/")[-4]
+ int_id = self.path.split("/")[-2]
+
+ int_response = self.apigatewayv2_backend.get_integration_responses(
+ api_id=api_id, integration_id=int_id
+ )
+ return 200, {}, json.dumps({"items": [res.to_json() for res in int_response]})
+
+ def update_integration_response(self):
+ api_id = self.path.split("/")[-5]
+ int_id = self.path.split("/")[-3]
+ int_res_id = self.path.split("/")[-1]
+
+ params = json.loads(self.body)
+ content_handling_strategy = params.get("contentHandlingStrategy")
+ integration_response_key = params.get("integrationResponseKey")
+ response_parameters = params.get("responseParameters")
+ response_templates = params.get("responseTemplates")
+ template_selection_expression = params.get("templateSelectionExpression")
+ integration_response = self.apigatewayv2_backend.update_integration_response(
+ api_id=api_id,
+ integration_id=int_id,
+ integration_response_id=int_res_id,
+ content_handling_strategy=content_handling_strategy,
+ integration_response_key=integration_response_key,
+ response_parameters=response_parameters,
+ response_templates=response_templates,
+ template_selection_expression=template_selection_expression,
+ )
+ return 200, {}, json.dumps(integration_response.to_json())
+
+ def create_vpc_link(self):
+ params = json.loads(self.body)
+
+ name = params.get("name")
+ sg_ids = params.get("securityGroupIds")
+ subnet_ids = params.get("subnetIds")
+ tags = params.get("tags")
+ vpc_link = self.apigatewayv2_backend.create_vpc_link(
+ name, sg_ids, subnet_ids, tags
+ )
+ return 200, {}, json.dumps(vpc_link.to_json())
+
+ def delete_vpc_link(self):
+ vpc_link_id = self.path.split("/")[-1]
+ self.apigatewayv2_backend.delete_vpc_link(vpc_link_id)
+ return 200, {}, "{}"
+
+ def get_vpc_link(self):
+ vpc_link_id = self.path.split("/")[-1]
+ vpc_link = self.apigatewayv2_backend.get_vpc_link(vpc_link_id)
+ return 200, {}, json.dumps(vpc_link.to_json())
+
+ def get_vpc_links(self):
+ vpc_links = self.apigatewayv2_backend.get_vpc_links()
+ return 200, {}, json.dumps({"items": [l.to_json() for l in vpc_links]})
+
+ def update_vpc_link(self):
+ vpc_link_id = self.path.split("/")[-1]
+ params = json.loads(self.body)
+ name = params.get("name")
+
+ vpc_link = self.apigatewayv2_backend.update_vpc_link(vpc_link_id, name=name)
+ return 200, {}, json.dumps(vpc_link.to_json())
diff --git a/moto/apigatewayv2/urls.py b/moto/apigatewayv2/urls.py
new file mode 100644
index 000000000..375ca5b3e
--- /dev/null
+++ b/moto/apigatewayv2/urls.py
@@ -0,0 +1,32 @@
+"""apigatewayv2 base URL and path."""
+from .responses import ApiGatewayV2Response
+
+url_bases = [
+ r"https?://apigateway\.(.+)\.amazonaws\.com",
+]
+
+
+response_v2 = ApiGatewayV2Response()
+
+
+url_paths = {
+ "{0}/v2/apis$": response_v2.apis,
+ "{0}/v2/apis/(?P[^/]+)$": response_v2.api,
+ "{0}/v2/apis/(?P[^/]+)/authorizers$": response_v2.authorizers,
+ "{0}/v2/apis/(?P[^/]+)/authorizers/(?P[^/]+)$": response_v2.authorizer,
+ "{0}/v2/apis/(?P[^/]+)/cors$": response_v2.cors,
+ "{0}/v2/apis/(?P[^/]+)/integrations$": response_v2.integrations,
+ "{0}/v2/apis/(?P[^/]+)/integrations/(?P[^/]+)$": response_v2.integration,
+ "{0}/v2/apis/(?P[^/]+)/integrations/(?P[^/]+)/integrationresponses$": response_v2.integration_responses,
+ "{0}/v2/apis/(?P[^/]+)/integrations/(?P[^/]+)/integrationresponses/(?P[^/]+)$": response_v2.integration_response,
+ "{0}/v2/apis/(?P[^/]+)/models$": response_v2.models,
+ "{0}/v2/apis/(?P[^/]+)/models/(?P[^/]+)$": response_v2.model,
+ "{0}/v2/apis/(?P[^/]+)/routes$": response_v2.routes,
+ "{0}/v2/apis/(?P[^/]+)/routes/(?P[^/]+)$": response_v2.route,
+ "{0}/v2/apis/(?P[^/]+)/routes/(?P[^/]+)/routeresponses$": response_v2.route_responses,
+ "{0}/v2/apis/(?P[^/]+)/routes/(?P[^/]+)/routeresponses/(?P[^/]+)$": response_v2.route_response,
+ "{0}/v2/apis/(?P[^/]+)/routes/(?P[^/]+)/requestparameters/(?P[^/]+)$": response_v2.route_request_parameter,
+ "{0}/v2/tags/(?P.+)$": response_v2.tags,
+ "{0}/v2/vpclinks$": response_v2.vpc_links,
+ "{0}/v2/vpclinks/(?P[^/]+)$": response_v2.vpc_link,
+}
diff --git a/moto/awslambda/models.py b/moto/awslambda/models.py
index 8dbf14b58..4e9ae9834 100644
--- a/moto/awslambda/models.py
+++ b/moto/awslambda/models.py
@@ -334,9 +334,13 @@ class LambdaFunction(CloudFormationModel, DockerModel):
# optional
self.description = spec.get("Description", "")
self.memory_size = spec.get("MemorySize", 128)
+ self.package_type = spec.get("PackageType", None)
self.publish = spec.get("Publish", False) # this is ignored currently
self.timeout = spec.get("Timeout", 3)
self.layers = self._get_layers_data(spec.get("Layers", []))
+ self.signing_profile_version_arn = spec.get("SigningProfileVersionArn")
+ self.signing_job_arn = spec.get("SigningJobArn")
+ self.code_signing_config_arn = spec.get("CodeSigningConfigArn")
self.logs_group_name = "/aws/lambda/{}".format(self.function_name)
@@ -413,6 +417,12 @@ class LambdaFunction(CloudFormationModel, DockerModel):
)
return [{"Arn": lv.arn, "CodeSize": lv.code_size} for lv in layer_versions]
+ def get_code_signing_config(self):
+ return {
+ "CodeSigningConfigArn": self.code_signing_config_arn,
+ "FunctionName": self.function_name,
+ }
+
def get_configuration(self):
config = {
"CodeSha256": self.code_sha_256,
@@ -426,10 +436,13 @@ class LambdaFunction(CloudFormationModel, DockerModel):
"Role": self.role,
"Runtime": self.run_time,
"State": self.state,
+ "PackageType": self.package_type,
"Timeout": self.timeout,
"Version": str(self.version),
"VpcConfig": self.vpc_config,
"Layers": self.layers,
+ "SigningProfileVersionArn": self.signing_profile_version_arn,
+ "SigningJobArn": self.signing_job_arn,
}
if self.environment_vars:
config["Environment"] = {"Variables": self.environment_vars}
@@ -1454,6 +1467,10 @@ The following environment variables are available for fine-grained control over
fn = self.get_function(function_name)
fn.policy.del_statement(sid, revision)
+ def get_code_signing_config(self, function_name):
+ fn = self.get_function(function_name)
+ return fn.get_code_signing_config()
+
def get_policy(self, function_name):
fn = self.get_function(function_name)
return fn.policy.get_policy()
diff --git a/moto/awslambda/responses.py b/moto/awslambda/responses.py
index eabc7e4c9..232728e98 100644
--- a/moto/awslambda/responses.py
+++ b/moto/awslambda/responses.py
@@ -154,6 +154,11 @@ class LambdaResponse(BaseResponse):
else:
raise ValueError("Cannot handle request")
+ def code_signing_config(self, request, full_url, headers):
+ self.setup_class(request, full_url, headers)
+ if request.method == "GET":
+ return self._get_code_signing_config()
+
def function_concurrency(self, request, full_url, headers):
http_method = request.method
self.setup_class(request, full_url, headers)
@@ -420,6 +425,11 @@ class LambdaResponse(BaseResponse):
else:
return 404, {}, "{}"
+ def _get_code_signing_config(self):
+ function_name = unquote(self.path.rsplit("/", 2)[-2])
+ resp = self.lambda_backend.get_code_signing_config(function_name)
+ return 200, {}, json.dumps(resp)
+
def _get_function_concurrency(self, request):
path_function_name = unquote(self.path.rsplit("/", 2)[-2])
function_name = self.lambda_backend.get_function(path_function_name)
diff --git a/moto/awslambda/urls.py b/moto/awslambda/urls.py
index c58c6ed8c..6d94b95a4 100644
--- a/moto/awslambda/urls.py
+++ b/moto/awslambda/urls.py
@@ -18,6 +18,7 @@ url_paths = {
r"{0}/(?P[^/]+)/functions/(?P[\w_:%-]+)/policy/?$": response.policy,
r"{0}/(?P[^/]+)/functions/(?P[\w_:%-]+)/configuration/?$": response.configuration,
r"{0}/(?P[^/]+)/functions/(?P[\w_:%-]+)/code/?$": response.code,
+ r"{0}/(?P[^/]+)/functions/(?P[\w_:%-]+)/code-signing-config$": response.code_signing_config,
r"{0}/(?P[^/]+)/functions/(?P[\w_:%-]+)/concurrency/?$": response.function_concurrency,
r"{0}/(?P[^/]+)/layers/?$": response.list_layers,
r"{0}/(?P[^/]+)/layers/(?P[\w_-]+)/versions/?$": response.layers_versions,
diff --git a/scripts/update_backend_index.py b/scripts/update_backend_index.py
index a839bb6e0..7f39e765f 100755
--- a/scripts/update_backend_index.py
+++ b/scripts/update_backend_index.py
@@ -15,13 +15,16 @@ output_file = "moto/backend_index.py"
script_dir = os.path.dirname(os.path.abspath(__file__))
output_path = os.path.join(script_dir, "..", output_file)
-IGNORE_BACKENDS = ["moto_api", "instance_metadata"]
+# Ignore the MotoAPI and InstanceMetadata backend, as they do not represent AWS services
+# Ignore the APIGatewayV2, as it's URL's are managed by APIGateway
+# Ignore S3bucket_path, as the functionality is covered in the S3 service
+IGNORE_BACKENDS = ["moto_api", "instance_metadata", "apigatewayv2", "s3bucket_path"]
def iter_backend_url_patterns():
for backend, (module_name, _) in backends.BACKENDS.items():
if backend in IGNORE_BACKENDS:
- break
+ continue
# otherwise we need to import the module
url_module_name = f"moto.{module_name}.urls"
module = importlib.import_module(url_module_name)
diff --git a/setup.py b/setup.py
index edec733da..531c5e1ff 100755
--- a/setup.py
+++ b/setup.py
@@ -81,6 +81,7 @@ for service_name in [
extras_per_service.update(
{
"apigateway": [_dep_python_jose, _dep_python_jose_ecdsa_pin],
+ "apigatewayv2": [_dep_PyYAML],
"appsync": [_dep_graphql],
"awslambda": [_dep_docker],
"batch": [_dep_docker],
diff --git a/tests/terraform-tests.success.txt b/tests/terraform-tests.success.txt
index f533e2b9e..008262cc6 100644
--- a/tests/terraform-tests.success.txt
+++ b/tests/terraform-tests.success.txt
@@ -1,7 +1,13 @@
TestAccAWSAccessKey
-TestAccAWSAvailabilityZones
+TestAccAWSAPIGatewayV2Authorizer
+TestAccAWSAPIGatewayV2IntegrationResponse
+TestAccAWSAPIGatewayV2Model
+TestAccAWSAPIGatewayV2Route
+TestAccAWSAPIGatewayV2RouteResponse
+TestAccAWSAPIGatewayV2VpcLink
TestAccAWSAppsyncApiKey
TestAccAWSAppsyncGraphqlApi
+TestAccAWSAvailabilityZones
TestAccAWSBillingServiceAccount
TestAccAWSCallerIdentity
TestAccAWSCloudTrailServiceAccount
diff --git a/tests/test_apigateway/test_apigateway_vpclink.py b/tests/test_apigateway/test_apigateway_vpclink.py
new file mode 100644
index 000000000..3de19cd28
--- /dev/null
+++ b/tests/test_apigateway/test_apigateway_vpclink.py
@@ -0,0 +1,110 @@
+import boto3
+import pytest
+
+from botocore.exceptions import ClientError
+from moto import mock_apigateway
+
+
+@mock_apigateway
+def test_get_vpc_links_empty():
+ client = boto3.client("apigateway", region_name="eu-west-1")
+
+ resp = client.get_vpc_links()
+ resp.should.have.key("items").equals([])
+
+
+@mock_apigateway
+def test_create_vpc_link():
+ client = boto3.client("apigateway", region_name="eu-west-1")
+
+ resp = client.create_vpc_link(
+ name="vpcl",
+ description="my first vpc link",
+ targetArns=["elb:target:arn"],
+ tags={"key1": "value1"},
+ )
+
+ resp.should.have.key("id")
+ resp.should.have.key("name").equals("vpcl")
+ resp.should.have.key("description").equals("my first vpc link")
+ resp.should.have.key("targetArns").equals(["elb:target:arn"])
+ resp.should.have.key("status").equals("AVAILABLE")
+ resp.should.have.key("tags").equals({"key1": "value1"})
+
+
+@mock_apigateway
+def test_get_vpc_link():
+ client = boto3.client("apigateway", region_name="eu-west-1")
+
+ vpc_link_id = client.create_vpc_link(
+ name="vpcl",
+ description="my first vpc link",
+ targetArns=["elb:target:arn"],
+ tags={"key1": "value1"},
+ )["id"]
+
+ resp = client.get_vpc_link(vpcLinkId=vpc_link_id)
+
+ resp.should.have.key("id")
+ resp.should.have.key("name").equals("vpcl")
+ resp.should.have.key("description").equals("my first vpc link")
+ resp.should.have.key("targetArns").equals(["elb:target:arn"])
+ resp.should.have.key("status").equals("AVAILABLE")
+ resp.should.have.key("tags").equals({"key1": "value1"})
+
+
+@mock_apigateway
+def test_get_vpc_link_unknown():
+ client = boto3.client("apigateway", region_name="ap-southeast-1")
+
+ with pytest.raises(ClientError) as exc:
+ client.get_vpc_link(vpcLinkId="unknown")
+ err = exc.value.response["Error"]
+ err["Code"].should.equal("NotFoundException")
+ err["Message"].should.equal("VPCLink not found")
+
+
+@mock_apigateway
+def test_get_vpc_links():
+ client = boto3.client("apigateway", region_name="eu-west-1")
+
+ vpc_link_id = client.create_vpc_link(
+ name="vpcl",
+ description="my first vpc link",
+ targetArns=["elb:target:arn"],
+ tags={"key1": "value1"},
+ )["id"]
+
+ links = client.get_vpc_links()["items"]
+ links.should.have.length_of(1)
+ links[0]["id"].should.equal(vpc_link_id)
+
+ client.create_vpc_link(
+ name="vpcl2",
+ description="my first vpc link",
+ targetArns=["elb:target:arn"],
+ tags={"key2": "value2"},
+ )
+
+ links = client.get_vpc_links()["items"]
+ links.should.have.length_of(2)
+
+
+@mock_apigateway
+def test_delete_vpc_link():
+ client = boto3.client("apigateway", region_name="eu-north-1")
+
+ vpc_link_id = client.create_vpc_link(
+ name="vpcl",
+ description="my first vpc link",
+ targetArns=["elb:target:arn"],
+ tags={"key1": "value1"},
+ )["id"]
+
+ links = client.get_vpc_links()["items"]
+ links.should.have.length_of(1)
+
+ client.delete_vpc_link(vpcLinkId=vpc_link_id)
+
+ links = client.get_vpc_links()["items"]
+ links.should.have.length_of(0)
diff --git a/tests/test_apigatewayv2/__init__.py b/tests/test_apigatewayv2/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/test_apigatewayv2/test_apigatewayv2.py b/tests/test_apigatewayv2/test_apigatewayv2.py
new file mode 100644
index 000000000..d69e3abbe
--- /dev/null
+++ b/tests/test_apigatewayv2/test_apigatewayv2.py
@@ -0,0 +1,325 @@
+"""Unit tests for apigatewayv2-supported APIs."""
+import boto3
+import pytest
+import sure # noqa # pylint: disable=unused-import
+
+from botocore.exceptions import ClientError
+from moto import mock_apigatewayv2
+
+# See our Development Tips on writing tests for hints on how to write good tests:
+# http://docs.getmoto.org/en/latest/docs/contributing/development_tips/tests.html
+
+
+@mock_apigatewayv2
+def test_create_api_with_unknown_protocol_type():
+ client = boto3.client("apigatewayv2", region_name="eu-west-1")
+
+ with pytest.raises(ClientError) as exc:
+ client.create_api(Name="test-api", ProtocolType="?")
+ err = exc.value.response["Error"]
+ err["Code"].should.equal("BadRequestException")
+ err["Message"].should.equal(
+ "Invalid protocol specified. Must be one of [HTTP, WEBSOCKET]"
+ )
+
+
+@mock_apigatewayv2
+def test_create_api_minimal():
+ client = boto3.client("apigatewayv2", region_name="eu-west-1")
+ resp = client.create_api(Name="test-api", ProtocolType="HTTP")
+
+ resp.should.have.key("ApiId")
+ resp.should.have.key("ApiEndpoint").equals(
+ f"https://{resp['ApiId']}.execute-api.eu-west-1.amazonaws.com"
+ )
+ resp.should.have.key("ApiKeySelectionExpression").equals(
+ "$request.header.x-api-key"
+ )
+ resp.should.have.key("CreatedDate")
+ resp.should.have.key("DisableExecuteApiEndpoint").equals(False)
+ resp.should.have.key("Name").equals("test-api")
+ resp.should.have.key("ProtocolType").equals("HTTP")
+ resp.should.have.key("RouteSelectionExpression").equals(
+ "$request.method $request.path"
+ )
+
+
+@mock_apigatewayv2
+def test_create_api():
+ client = boto3.client("apigatewayv2", region_name="eu-west-1")
+ resp = client.create_api(
+ ApiKeySelectionExpression="s3l3ction",
+ CorsConfiguration={
+ "AllowCredentials": True,
+ "AllowHeaders": ["x-header1"],
+ "AllowMethods": ["GET", "PUT"],
+ "AllowOrigins": ["google.com"],
+ "ExposeHeaders": ["x-header1"],
+ "MaxAge": 2,
+ },
+ Description="my first api",
+ DisableSchemaValidation=True,
+ DisableExecuteApiEndpoint=True,
+ Name="test-api",
+ ProtocolType="HTTP",
+ RouteSelectionExpression="route_s3l3ction",
+ Version="1.0",
+ )
+
+ resp.should.have.key("ApiId")
+ resp.should.have.key("ApiEndpoint").equals(
+ f"https://{resp['ApiId']}.execute-api.eu-west-1.amazonaws.com"
+ )
+ resp.should.have.key("ApiKeySelectionExpression").equals("s3l3ction")
+ resp.should.have.key("CreatedDate")
+ resp.should.have.key("CorsConfiguration").equals(
+ {
+ "AllowCredentials": True,
+ "AllowHeaders": ["x-header1"],
+ "AllowMethods": ["GET", "PUT"],
+ "AllowOrigins": ["google.com"],
+ "ExposeHeaders": ["x-header1"],
+ "MaxAge": 2,
+ }
+ )
+ resp.should.have.key("Description").equals("my first api")
+ resp.should.have.key("DisableExecuteApiEndpoint").equals(True)
+ resp.should.have.key("DisableSchemaValidation").equals(True)
+ resp.should.have.key("Name").equals("test-api")
+ resp.should.have.key("ProtocolType").equals("HTTP")
+ resp.should.have.key("RouteSelectionExpression").equals("route_s3l3ction")
+ resp.should.have.key("Version").equals("1.0")
+
+
+@mock_apigatewayv2
+def test_delete_api():
+ client = boto3.client("apigatewayv2", region_name="us-east-2")
+ api_id = client.create_api(Name="t", ProtocolType="HTTP")["ApiId"]
+
+ client.delete_api(ApiId=api_id)
+
+ with pytest.raises(ClientError) as exc:
+ client.get_api(ApiId=api_id)
+ exc.value.response["Error"]["Code"].should.equal("NotFoundException")
+
+
+@mock_apigatewayv2
+def test_delete_cors_configuration():
+ client = boto3.client("apigatewayv2", region_name="eu-west-1")
+ api_id = client.create_api(
+ ApiKeySelectionExpression="s3l3ction",
+ CorsConfiguration={
+ "AllowCredentials": True,
+ "AllowHeaders": ["x-header1"],
+ "AllowMethods": ["GET", "PUT"],
+ "AllowOrigins": ["google.com"],
+ "ExposeHeaders": ["x-header1"],
+ "MaxAge": 2,
+ },
+ Description="my first api",
+ DisableSchemaValidation=True,
+ DisableExecuteApiEndpoint=True,
+ Name="test-api",
+ ProtocolType="HTTP",
+ RouteSelectionExpression="route_s3l3ction",
+ Version="1.0",
+ )["ApiId"]
+
+ client.delete_cors_configuration(ApiId=api_id)
+
+ resp = client.get_api(ApiId=api_id)
+
+ resp.shouldnt.have.key("CorsConfiguration")
+ resp.should.have.key("Description").equals("my first api")
+ resp.should.have.key("Name").equals("test-api")
+
+
+@mock_apigatewayv2
+def test_get_api_unknown():
+ client = boto3.client("apigatewayv2", region_name="ap-southeast-1")
+ with pytest.raises(ClientError) as exc:
+ client.get_api(ApiId="unknown")
+
+ err = exc.value.response["Error"]
+ err["Code"].should.equal("NotFoundException")
+ err["Message"].should.equal("Invalid API identifier specified unknown")
+
+
+@mock_apigatewayv2
+def test_get_api():
+ client = boto3.client("apigatewayv2", region_name="ap-southeast-1")
+ api_id = client.create_api(Name="test-get-api", ProtocolType="WEBSOCKET")["ApiId"]
+
+ resp = client.get_api(ApiId=api_id)
+
+ resp.should.have.key("ApiId").equals(api_id)
+ resp.should.have.key("ApiEndpoint").equals(
+ f"https://{resp['ApiId']}.execute-api.ap-southeast-1.amazonaws.com"
+ )
+ resp.should.have.key("ApiKeySelectionExpression").equals(
+ "$request.header.x-api-key"
+ )
+ resp.should.have.key("CreatedDate")
+ resp.should.have.key("DisableExecuteApiEndpoint").equals(False)
+ resp.should.have.key("Name").equals("test-get-api")
+ resp.should.have.key("ProtocolType").equals("WEBSOCKET")
+ resp.should.have.key("RouteSelectionExpression").equals(
+ "$request.method $request.path"
+ )
+
+
+@mock_apigatewayv2
+def test_get_apis():
+ client = boto3.client("apigatewayv2", region_name="ap-southeast-1")
+ client.get_apis().should.have.key("Items").length_of(0)
+
+ api_id_1 = client.create_api(Name="api1", ProtocolType="HTTP")["ApiId"]
+ api_id_2 = client.create_api(Name="api2", ProtocolType="WEBSOCKET")["ApiId"]
+ client.get_apis().should.have.key("Items").length_of(2)
+
+ api_ids = [i["ApiId"] for i in client.get_apis()["Items"]]
+ api_ids.should.contain(api_id_1)
+ api_ids.should.contain(api_id_2)
+
+
+@mock_apigatewayv2
+def test_update_api_minimal():
+ client = boto3.client("apigatewayv2", region_name="eu-west-1")
+ api_id = client.create_api(
+ ApiKeySelectionExpression="s3l3ction",
+ CorsConfiguration={
+ "AllowCredentials": True,
+ "AllowHeaders": ["x-header1"],
+ "AllowMethods": ["GET", "PUT"],
+ "AllowOrigins": ["google.com"],
+ "ExposeHeaders": ["x-header1"],
+ "MaxAge": 2,
+ },
+ Description="my first api",
+ DisableSchemaValidation=True,
+ DisableExecuteApiEndpoint=True,
+ Name="test-api",
+ ProtocolType="HTTP",
+ RouteSelectionExpression="route_s3l3ction",
+ Version="1.0",
+ )["ApiId"]
+
+ resp = client.update_api(
+ ApiId=api_id,
+ CorsConfiguration={
+ "AllowCredentials": False,
+ "AllowHeaders": ["x-header2"],
+ "AllowMethods": ["GET", "PUT"],
+ "AllowOrigins": ["google.com"],
+ "ExposeHeaders": ["x-header2"],
+ "MaxAge": 2,
+ },
+ )
+
+ resp.should.have.key("ApiId")
+ resp.should.have.key("ApiEndpoint").equals(
+ f"https://{resp['ApiId']}.execute-api.eu-west-1.amazonaws.com"
+ )
+ resp.should.have.key("ApiKeySelectionExpression").equals("s3l3ction")
+ resp.should.have.key("CreatedDate")
+ resp.should.have.key("CorsConfiguration").equals(
+ {
+ "AllowCredentials": False,
+ "AllowHeaders": ["x-header2"],
+ "AllowMethods": ["GET", "PUT"],
+ "AllowOrigins": ["google.com"],
+ "ExposeHeaders": ["x-header2"],
+ "MaxAge": 2,
+ }
+ )
+ resp.should.have.key("Description").equals("my first api")
+ resp.should.have.key("DisableExecuteApiEndpoint").equals(True)
+ resp.should.have.key("DisableSchemaValidation").equals(True)
+ resp.should.have.key("Name").equals("test-api")
+ resp.should.have.key("ProtocolType").equals("HTTP")
+ resp.should.have.key("RouteSelectionExpression").equals("route_s3l3ction")
+ resp.should.have.key("Version").equals("1.0")
+
+
+@mock_apigatewayv2
+def test_update_api_empty_fields():
+ client = boto3.client("apigatewayv2", region_name="eu-west-1")
+ api_id = client.create_api(
+ ApiKeySelectionExpression="s3l3ction",
+ CorsConfiguration={
+ "AllowCredentials": True,
+ "AllowHeaders": ["x-header1"],
+ "AllowMethods": ["GET", "PUT"],
+ "AllowOrigins": ["google.com"],
+ "ExposeHeaders": ["x-header1"],
+ "MaxAge": 2,
+ },
+ Description="my first api",
+ DisableSchemaValidation=True,
+ DisableExecuteApiEndpoint=True,
+ Name="test-api",
+ ProtocolType="HTTP",
+ RouteSelectionExpression="route_s3l3ction",
+ Version="1.0",
+ )["ApiId"]
+
+ resp = client.update_api(ApiId=api_id, Description="", Name="updated", Version="")
+
+ resp.should.have.key("ApiId")
+ resp.should.have.key("ApiEndpoint").equals(
+ f"https://{resp['ApiId']}.execute-api.eu-west-1.amazonaws.com"
+ )
+ resp.should.have.key("ApiKeySelectionExpression").equals("s3l3ction")
+ resp.should.have.key("Description").equals("")
+ resp.should.have.key("DisableExecuteApiEndpoint").equals(True)
+ resp.should.have.key("DisableSchemaValidation").equals(True)
+ resp.should.have.key("Name").equals("updated")
+ resp.should.have.key("ProtocolType").equals("HTTP")
+ resp.should.have.key("RouteSelectionExpression").equals("route_s3l3ction")
+ resp.should.have.key("Version").equals("")
+
+
+@mock_apigatewayv2
+def test_update_api():
+ client = boto3.client("apigatewayv2", region_name="us-east-2")
+ api_id = client.create_api(Name="test-api", ProtocolType="HTTP")["ApiId"]
+
+ resp = client.update_api(
+ ApiId=api_id,
+ ApiKeySelectionExpression="api_key_s3l3ction",
+ CorsConfiguration={
+ "AllowCredentials": True,
+ "AllowHeaders": ["X-Amz-Target"],
+ "AllowMethods": ["GET"],
+ },
+ CredentialsArn="credentials:arn",
+ Description="updated API",
+ DisableSchemaValidation=True,
+ DisableExecuteApiEndpoint=True,
+ Name="new name",
+ RouteKey="route key",
+ RouteSelectionExpression="route_s3l3ction",
+ Target="updated target",
+ Version="1.1",
+ )
+
+ resp.should.have.key("ApiId")
+ resp.should.have.key("ApiEndpoint").equals(
+ f"https://{resp['ApiId']}.execute-api.us-east-2.amazonaws.com"
+ )
+ resp.should.have.key("ApiKeySelectionExpression").equals("api_key_s3l3ction")
+ resp.should.have.key("CorsConfiguration").equals(
+ {
+ "AllowCredentials": True,
+ "AllowHeaders": ["X-Amz-Target"],
+ "AllowMethods": ["GET"],
+ }
+ )
+ resp.should.have.key("CreatedDate")
+ resp.should.have.key("Description").equals("updated API")
+ resp.should.have.key("DisableSchemaValidation").equals(True)
+ resp.should.have.key("DisableExecuteApiEndpoint").equals(True)
+ resp.should.have.key("Name").equals("new name")
+ resp.should.have.key("ProtocolType").equals("HTTP")
+ resp.should.have.key("RouteSelectionExpression").equals("route_s3l3ction")
+ resp.should.have.key("Version").equals("1.1")
diff --git a/tests/test_apigatewayv2/test_apigatewayv2_authorizers.py b/tests/test_apigatewayv2/test_apigatewayv2_authorizers.py
new file mode 100644
index 000000000..013a20d70
--- /dev/null
+++ b/tests/test_apigatewayv2/test_apigatewayv2_authorizers.py
@@ -0,0 +1,176 @@
+import boto3
+import pytest
+
+from botocore.exceptions import ClientError
+from moto import mock_apigatewayv2
+
+
+@mock_apigatewayv2
+def test_create_authorizer_minimum():
+ client = boto3.client("apigatewayv2", region_name="eu-west-1")
+ api_id = client.create_api(Name="test-api", ProtocolType="HTTP")["ApiId"]
+
+ resp = client.create_authorizer(
+ ApiId=api_id, AuthorizerType="REQUEST", IdentitySource=[], Name="auth1"
+ )
+
+ resp.should.have.key("AuthorizerId")
+ resp.should.have.key("AuthorizerType").equals("REQUEST")
+ resp.should.have.key("Name").equals("auth1")
+
+
+@mock_apigatewayv2
+def test_create_authorizer():
+ client = boto3.client("apigatewayv2", region_name="eu-west-1")
+ api_id = client.create_api(Name="test-api", ProtocolType="HTTP")["ApiId"]
+
+ resp = client.create_authorizer(
+ ApiId=api_id,
+ AuthorizerCredentialsArn="auth:creds:arn",
+ AuthorizerPayloadFormatVersion="2.0",
+ AuthorizerResultTtlInSeconds=3,
+ AuthorizerType="REQUEST",
+ AuthorizerUri="auth_uri",
+ EnableSimpleResponses=True,
+ IdentitySource=["$request.header.Authorization"],
+ IdentityValidationExpression="ive",
+ JwtConfiguration={"Audience": ["a1"], "Issuer": "moto.com"},
+ Name="auth1",
+ )
+
+ resp.should.have.key("AuthorizerId")
+ resp.should.have.key("AuthorizerCredentialsArn").equals("auth:creds:arn")
+ resp.should.have.key("AuthorizerPayloadFormatVersion").equals("2.0")
+ resp.should.have.key("AuthorizerResultTtlInSeconds").equals(3)
+ resp.should.have.key("AuthorizerType").equals("REQUEST")
+ resp.should.have.key("AuthorizerUri").equals("auth_uri")
+ resp.should.have.key("EnableSimpleResponses").equals(True)
+ resp.should.have.key("IdentitySource").equals(["$request.header.Authorization"])
+ resp.should.have.key("IdentityValidationExpression").equals("ive")
+ resp.should.have.key("JwtConfiguration").equals(
+ {"Audience": ["a1"], "Issuer": "moto.com"}
+ )
+ resp.should.have.key("Name").equals("auth1")
+
+
+@mock_apigatewayv2
+def test_get_authorizer():
+ client = boto3.client("apigatewayv2", region_name="eu-west-1")
+ api_id = client.create_api(Name="test-api", ProtocolType="HTTP")["ApiId"]
+
+ authorizer_id = client.create_authorizer(
+ ApiId=api_id, AuthorizerType="REQUEST", IdentitySource=[], Name="auth1"
+ )["AuthorizerId"]
+
+ resp = client.get_authorizer(ApiId=api_id, AuthorizerId=authorizer_id)
+
+ resp.should.have.key("AuthorizerId")
+ resp.should.have.key("AuthorizerType").equals("REQUEST")
+ resp.should.have.key("Name").equals("auth1")
+
+
+@mock_apigatewayv2
+def test_delete_authorizer():
+ client = boto3.client("apigatewayv2", region_name="eu-west-1")
+ api_id = client.create_api(Name="test-api", ProtocolType="HTTP")["ApiId"]
+
+ authorizer_id = client.create_authorizer(
+ ApiId=api_id, AuthorizerType="REQUEST", IdentitySource=[], Name="auth1"
+ )["AuthorizerId"]
+
+ client.delete_authorizer(ApiId=api_id, AuthorizerId=authorizer_id)
+
+ with pytest.raises(ClientError) as exc:
+ client.get_authorizer(ApiId=api_id, AuthorizerId="unknown")
+
+ err = exc.value.response["Error"]
+ err["Code"].should.equal("NotFoundException")
+
+
+@mock_apigatewayv2
+def test_get_authorizer_unknown():
+ client = boto3.client("apigatewayv2", region_name="eu-west-1")
+ api_id = client.create_api(Name="test-api", ProtocolType="HTTP")["ApiId"]
+
+ with pytest.raises(ClientError) as exc:
+ client.get_authorizer(ApiId=api_id, AuthorizerId="unknown")
+
+ err = exc.value.response["Error"]
+ err["Code"].should.equal("NotFoundException")
+
+
+@mock_apigatewayv2
+def test_update_authorizer_single():
+ client = boto3.client("apigatewayv2", region_name="eu-west-1")
+ api_id = client.create_api(Name="test-api", ProtocolType="HTTP")["ApiId"]
+
+ auth_id = client.create_authorizer(
+ ApiId=api_id,
+ AuthorizerCredentialsArn="auth:creds:arn",
+ AuthorizerPayloadFormatVersion="2.0",
+ AuthorizerResultTtlInSeconds=3,
+ AuthorizerType="REQUEST",
+ AuthorizerUri="auth_uri",
+ EnableSimpleResponses=True,
+ IdentitySource=["$request.header.Authorization"],
+ IdentityValidationExpression="ive",
+ JwtConfiguration={"Audience": ["a1"], "Issuer": "moto.com"},
+ Name="auth1",
+ )["AuthorizerId"]
+
+ resp = client.update_authorizer(ApiId=api_id, AuthorizerId=auth_id, Name="auth2")
+
+ resp.should.have.key("AuthorizerId")
+ resp.should.have.key("AuthorizerCredentialsArn").equals("auth:creds:arn")
+ resp.should.have.key("AuthorizerPayloadFormatVersion").equals("2.0")
+ resp.should.have.key("AuthorizerResultTtlInSeconds").equals(3)
+ resp.should.have.key("AuthorizerType").equals("REQUEST")
+ resp.should.have.key("AuthorizerUri").equals("auth_uri")
+ resp.should.have.key("EnableSimpleResponses").equals(True)
+ resp.should.have.key("IdentitySource").equals(["$request.header.Authorization"])
+ resp.should.have.key("IdentityValidationExpression").equals("ive")
+ resp.should.have.key("JwtConfiguration").equals(
+ {"Audience": ["a1"], "Issuer": "moto.com"}
+ )
+ resp.should.have.key("Name").equals("auth2")
+
+
+@mock_apigatewayv2
+def test_update_authorizer_all_attributes():
+ client = boto3.client("apigatewayv2", region_name="eu-west-1")
+ api_id = client.create_api(Name="test-api", ProtocolType="HTTP")["ApiId"]
+
+ auth_id = client.create_authorizer(
+ ApiId=api_id, AuthorizerType="REQUEST", IdentitySource=[], Name="auth1"
+ )["AuthorizerId"]
+
+ auth_id = client.update_authorizer(
+ ApiId=api_id,
+ AuthorizerId=auth_id,
+ AuthorizerCredentialsArn="",
+ AuthorizerPayloadFormatVersion="3.0",
+ AuthorizerResultTtlInSeconds=5,
+ AuthorizerType="REQUEST",
+ AuthorizerUri="auth_uri",
+ EnableSimpleResponses=False,
+ IdentitySource=["$request.header.Authentication"],
+ IdentityValidationExpression="ive2",
+ JwtConfiguration={"Audience": ["a2"], "Issuer": "moto.com"},
+ Name="auth1",
+ )["AuthorizerId"]
+
+ resp = client.update_authorizer(ApiId=api_id, AuthorizerId=auth_id, Name="auth2")
+
+ resp.should.have.key("AuthorizerId")
+ resp.should.have.key("AuthorizerCredentialsArn").equals("")
+ resp.should.have.key("AuthorizerPayloadFormatVersion").equals("3.0")
+ resp.should.have.key("AuthorizerResultTtlInSeconds").equals(5)
+ resp.should.have.key("AuthorizerType").equals("REQUEST")
+ resp.should.have.key("AuthorizerUri").equals("auth_uri")
+ resp.should.have.key("EnableSimpleResponses").equals(False)
+ resp.should.have.key("IdentitySource").equals(["$request.header.Authentication"])
+ resp.should.have.key("IdentityValidationExpression").equals("ive2")
+ resp.should.have.key("JwtConfiguration").equals(
+ {"Audience": ["a2"], "Issuer": "moto.com"}
+ )
+ resp.should.have.key("Name").equals("auth2")
diff --git a/tests/test_apigatewayv2/test_apigatewayv2_integrationresponses.py b/tests/test_apigatewayv2/test_apigatewayv2_integrationresponses.py
new file mode 100644
index 000000000..54443b8f5
--- /dev/null
+++ b/tests/test_apigatewayv2/test_apigatewayv2_integrationresponses.py
@@ -0,0 +1,174 @@
+import boto3
+import pytest
+
+from botocore.exceptions import ClientError
+from moto import mock_apigatewayv2
+
+
+@mock_apigatewayv2
+def test_get_integration_responses_empty():
+ client = boto3.client("apigatewayv2", region_name="eu-west-1")
+ api_id = client.create_api(Name="test-api", ProtocolType="HTTP")["ApiId"]
+ int_id = client.create_integration(ApiId=api_id, IntegrationType="HTTP")[
+ "IntegrationId"
+ ]
+
+ resp = client.get_integration_responses(ApiId=api_id, IntegrationId=int_id)
+ resp.should.have.key("Items").equals([])
+
+
+@mock_apigatewayv2
+def test_create_integration_response():
+ client = boto3.client("apigatewayv2", region_name="eu-west-1")
+ api_id = client.create_api(Name="test-api", ProtocolType="HTTP")["ApiId"]
+
+ int_id = client.create_integration(ApiId=api_id, IntegrationType="HTTP")[
+ "IntegrationId"
+ ]
+
+ int_res = client.create_integration_response(
+ ApiId=api_id,
+ ContentHandlingStrategy="CONVERT_TO_BINARY",
+ IntegrationId=int_id,
+ IntegrationResponseKey="int_res_key",
+ ResponseParameters={"x-header": "header-value"},
+ ResponseTemplates={"t": "template"},
+ TemplateSelectionExpression="tse",
+ )
+
+ int_res.should.have.key("ContentHandlingStrategy").equals("CONVERT_TO_BINARY")
+ int_res.should.have.key("IntegrationResponseId")
+ int_res.should.have.key("IntegrationResponseKey").equals("int_res_key")
+ int_res.should.have.key("ResponseParameters").equals({"x-header": "header-value"},)
+ int_res.should.have.key("ResponseTemplates").equals({"t": "template"})
+ int_res.should.have.key("TemplateSelectionExpression").equals("tse")
+
+
+@mock_apigatewayv2
+def test_get_integration_response():
+ client = boto3.client("apigatewayv2", region_name="eu-west-1")
+ api_id = client.create_api(Name="test-api", ProtocolType="HTTP")["ApiId"]
+ int_id = client.create_integration(ApiId=api_id, IntegrationType="HTTP")[
+ "IntegrationId"
+ ]
+
+ int_res_id = client.create_integration_response(
+ ApiId=api_id,
+ ContentHandlingStrategy="CONVERT_TO_BINARY",
+ IntegrationId=int_id,
+ IntegrationResponseKey="int_res_key",
+ ResponseParameters={"x-header": "header-value"},
+ ResponseTemplates={"t": "template"},
+ TemplateSelectionExpression="tse",
+ )["IntegrationResponseId"]
+
+ int_res = client.get_integration_response(
+ ApiId=api_id, IntegrationId=int_id, IntegrationResponseId=int_res_id
+ )
+
+ int_res.should.have.key("ContentHandlingStrategy").equals("CONVERT_TO_BINARY")
+ int_res.should.have.key("IntegrationResponseId")
+ int_res.should.have.key("IntegrationResponseKey").equals("int_res_key")
+ int_res.should.have.key("ResponseParameters").equals({"x-header": "header-value"},)
+ int_res.should.have.key("ResponseTemplates").equals({"t": "template"})
+ int_res.should.have.key("TemplateSelectionExpression").equals("tse")
+
+
+@mock_apigatewayv2
+def test_get_integration_response_unknown():
+ client = boto3.client("apigatewayv2", region_name="ap-southeast-1")
+ api_id = client.create_api(Name="test-api", ProtocolType="HTTP")["ApiId"]
+ int_id = client.create_integration(ApiId=api_id, IntegrationType="HTTP")[
+ "IntegrationId"
+ ]
+
+ with pytest.raises(ClientError) as exc:
+ client.get_integration_response(
+ ApiId=api_id, IntegrationId=int_id, IntegrationResponseId="unknown"
+ )
+ err = exc.value.response["Error"]
+ err["Code"].should.equal("NotFoundException")
+ err["Message"].should.equal(
+ "Invalid IntegrationResponse identifier specified unknown"
+ )
+
+
+@mock_apigatewayv2
+def test_delete_integration_response():
+ client = boto3.client("apigatewayv2", region_name="ap-southeast-1")
+ api_id = client.create_api(Name="test-api", ProtocolType="HTTP")["ApiId"]
+ int_id = client.create_integration(ApiId=api_id, IntegrationType="HTTP")[
+ "IntegrationId"
+ ]
+
+ int_res_id = client.create_integration_response(
+ ApiId=api_id, IntegrationId=int_id, IntegrationResponseKey="int_res_key",
+ )["IntegrationResponseId"]
+
+ client.delete_integration_response(
+ ApiId=api_id, IntegrationId=int_id, IntegrationResponseId=int_res_id
+ )
+
+ with pytest.raises(ClientError) as exc:
+ client.get_integration_response(
+ ApiId=api_id, IntegrationId=int_id, IntegrationResponseId=int_res_id
+ )
+ err = exc.value.response["Error"]
+ err["Code"].should.equal("NotFoundException")
+
+
+@mock_apigatewayv2
+def test_update_integration_response_single_attr():
+ client = boto3.client("apigatewayv2", region_name="ap-southeast-1")
+ api_id = client.create_api(Name="test-api", ProtocolType="HTTP")["ApiId"]
+ int_id = client.create_integration(ApiId=api_id, IntegrationType="HTTP")[
+ "IntegrationId"
+ ]
+
+ int_res_id = client.create_integration_response(
+ ApiId=api_id, IntegrationId=int_id, IntegrationResponseKey="int_res_key",
+ )["IntegrationResponseId"]
+
+ res = client.update_integration_response(
+ ApiId=api_id,
+ IntegrationId=int_id,
+ IntegrationResponseId=int_res_id,
+ ContentHandlingStrategy="CONVERT_TO_BINARY",
+ )
+
+ res.should.have.key("ContentHandlingStrategy").equals("CONVERT_TO_BINARY")
+ res.should.have.key("IntegrationResponseId")
+ res.should.have.key("IntegrationResponseKey").equals("int_res_key")
+
+
+@mock_apigatewayv2
+def test_update_integration_response_multiple_attrs():
+ client = boto3.client("apigatewayv2", region_name="ap-southeast-1")
+ api_id = client.create_api(Name="test-api", ProtocolType="HTTP")["ApiId"]
+ int_id = client.create_integration(ApiId=api_id, IntegrationType="HTTP")[
+ "IntegrationId"
+ ]
+
+ int_res_id = client.create_integration_response(
+ ApiId=api_id,
+ IntegrationId=int_id,
+ IntegrationResponseKey="int_res_key",
+ ContentHandlingStrategy="CONVERT_TO_BINARY",
+ )["IntegrationResponseId"]
+
+ res = client.update_integration_response(
+ ApiId=api_id,
+ IntegrationId=int_id,
+ IntegrationResponseId=int_res_id,
+ IntegrationResponseKey="int_res_key2",
+ ResponseParameters={"x-header": "header-value"},
+ ResponseTemplates={"t": "template"},
+ TemplateSelectionExpression="tse",
+ )
+
+ res.should.have.key("ContentHandlingStrategy").equals("CONVERT_TO_BINARY")
+ res.should.have.key("IntegrationResponseId")
+ res.should.have.key("IntegrationResponseKey").equals("int_res_key2")
+ res.should.have.key("ResponseParameters").equals({"x-header": "header-value"},)
+ res.should.have.key("ResponseTemplates").equals({"t": "template"})
+ res.should.have.key("TemplateSelectionExpression").equals("tse")
diff --git a/tests/test_apigatewayv2/test_apigatewayv2_integrations.py b/tests/test_apigatewayv2/test_apigatewayv2_integrations.py
new file mode 100644
index 000000000..a364ee3be
--- /dev/null
+++ b/tests/test_apigatewayv2/test_apigatewayv2_integrations.py
@@ -0,0 +1,317 @@
+import boto3
+import pytest
+
+from botocore.exceptions import ClientError
+from moto import mock_apigatewayv2
+
+
+@mock_apigatewayv2
+def test_get_integrations_empty():
+ client = boto3.client("apigatewayv2", region_name="eu-west-1")
+ api_id = client.create_api(Name="test-api", ProtocolType="HTTP")["ApiId"]
+
+ resp = client.get_integrations(ApiId=api_id)
+ resp.should.have.key("Items").equals([])
+
+
+@mock_apigatewayv2
+def test_create_integration_minimum():
+ client = boto3.client("apigatewayv2", region_name="eu-west-1")
+ api_id = client.create_api(Name="test-api", ProtocolType="HTTP")["ApiId"]
+
+ resp = client.create_integration(ApiId=api_id, IntegrationType="HTTP")
+
+ resp.should.have.key("IntegrationId")
+ resp.should.have.key("IntegrationType").equals("HTTP")
+
+
+@mock_apigatewayv2
+def test_create_integration_for_internet_mock():
+ client = boto3.client("apigatewayv2", region_name="eu-west-1")
+ api_id = client.create_api(Name="test-api", ProtocolType="HTTP")["ApiId"]
+
+ resp = client.create_integration(
+ ApiId=api_id, ConnectionType="INTERNET", IntegrationType="MOCK"
+ )
+
+ resp.should.have.key("IntegrationId")
+ resp.should.have.key("IntegrationType").equals("MOCK")
+ resp.should.have.key("IntegrationResponseSelectionExpression").equals(
+ "${integration.response.statuscode}"
+ )
+
+
+@mock_apigatewayv2
+def test_create_integration_full():
+ client = boto3.client("apigatewayv2", region_name="eu-west-1")
+ api_id = client.create_api(Name="test-api", ProtocolType="HTTP")["ApiId"]
+
+ resp = client.create_integration(
+ ApiId=api_id,
+ ConnectionId="conn_id",
+ ConnectionType="INTERNET",
+ ContentHandlingStrategy="CONVERT_TO_BINARY",
+ CredentialsArn="cred:arn",
+ Description="my full integration",
+ IntegrationMethod="PUT",
+ IntegrationType="HTTP",
+ IntegrationSubtype="n/a",
+ PassthroughBehavior="WHEN_NO_MATCH",
+ PayloadFormatVersion="1.0",
+ RequestParameters={"r": "p"},
+ RequestTemplates={"r": "t"},
+ ResponseParameters={"res": {"par": "am"}},
+ TemplateSelectionExpression="tse",
+ TimeoutInMillis=123,
+ TlsConfig={"ServerNameToVerify": "server"},
+ )
+
+ resp.should.have.key("IntegrationId")
+ resp.should.have.key("ConnectionId").equals("conn_id")
+ resp.should.have.key("ConnectionType").equals("INTERNET")
+ resp.should.have.key("ContentHandlingStrategy").equals("CONVERT_TO_BINARY")
+ resp.should.have.key("CredentialsArn").equals("cred:arn")
+ resp.should.have.key("Description").equals("my full integration")
+ resp.should.have.key("IntegrationMethod").equals("PUT")
+ resp.should.have.key("IntegrationType").equals("HTTP")
+ resp.should.have.key("IntegrationSubtype").equals("n/a")
+ resp.should.have.key("PassthroughBehavior").equals("WHEN_NO_MATCH")
+ resp.should.have.key("PayloadFormatVersion").equals("1.0")
+ resp.should.have.key("RequestParameters").equals({"r": "p"})
+ resp.should.have.key("RequestTemplates").equals({"r": "t"})
+ resp.should.have.key("ResponseParameters").equals({"res": {"par": "am"}})
+ resp.should.have.key("TemplateSelectionExpression").equals("tse")
+ resp.should.have.key("TimeoutInMillis").equals(123)
+ resp.should.have.key("TlsConfig").equals({"ServerNameToVerify": "server"})
+
+
+@mock_apigatewayv2
+def test_get_integration():
+ client = boto3.client("apigatewayv2", region_name="ap-southeast-1")
+ api_id = client.create_api(Name="test-api", ProtocolType="HTTP")["ApiId"]
+
+ integration_id = client.create_integration(ApiId=api_id, IntegrationType="HTTP")[
+ "IntegrationId"
+ ]
+
+ resp = client.get_integration(ApiId=api_id, IntegrationId=integration_id)
+
+ resp.should.have.key("IntegrationId")
+ resp.should.have.key("IntegrationType").equals("HTTP")
+
+
+@mock_apigatewayv2
+def test_get_integration_unknown():
+ client = boto3.client("apigatewayv2", region_name="ap-southeast-1")
+ api_id = client.create_api(Name="test-api", ProtocolType="HTTP")["ApiId"]
+
+ with pytest.raises(ClientError) as exc:
+ client.get_integration(ApiId=api_id, IntegrationId="unknown")
+ err = exc.value.response["Error"]
+ err["Code"].should.equal("NotFoundException")
+ err["Message"].should.equal("Invalid Integration identifier specified unknown")
+
+
+@mock_apigatewayv2
+def test_get_integrations():
+ client = boto3.client("apigatewayv2", region_name="us-east-2")
+ api_id = client.create_api(Name="test-api", ProtocolType="HTTP")["ApiId"]
+
+ integration_id = client.create_integration(ApiId=api_id, IntegrationType="HTTP")[
+ "IntegrationId"
+ ]
+ resp = client.get_integrations(ApiId=api_id)
+
+ resp.should.have.key("Items").length_of(1)
+ resp["Items"][0].should.have.key("IntegrationId").equals(integration_id)
+
+
+@mock_apigatewayv2
+def test_delete_integration():
+ client = boto3.client("apigatewayv2", region_name="ap-southeast-1")
+ api_id = client.create_api(Name="test-api", ProtocolType="HTTP")["ApiId"]
+
+ integration_id = client.create_integration(ApiId=api_id, IntegrationType="HTTP")[
+ "IntegrationId"
+ ]
+
+ client.delete_integration(ApiId=api_id, IntegrationId=integration_id)
+
+ with pytest.raises(ClientError) as exc:
+ client.get_integration(ApiId=api_id, IntegrationId=integration_id)
+ err = exc.value.response["Error"]
+ err["Code"].should.equal("NotFoundException")
+
+
+@mock_apigatewayv2
+def test_update_integration_single_attr():
+ client = boto3.client("apigatewayv2", region_name="eu-west-1")
+ api_id = client.create_api(Name="test-api", ProtocolType="HTTP")["ApiId"]
+
+ int_id = client.create_integration(
+ ApiId=api_id,
+ ConnectionId="conn_id",
+ ConnectionType="INTERNET",
+ ContentHandlingStrategy="CONVERT_TO_BINARY",
+ CredentialsArn="cred:arn",
+ Description="my full integration",
+ IntegrationMethod="PUT",
+ IntegrationType="HTTP",
+ IntegrationSubtype="n/a",
+ PassthroughBehavior="WHEN_NO_MATCH",
+ PayloadFormatVersion="1.0",
+ RequestParameters={"r": "p"},
+ RequestTemplates={"r": "t"},
+ ResponseParameters={"res": {"par": "am"}},
+ TemplateSelectionExpression="tse",
+ TimeoutInMillis=123,
+ TlsConfig={"ServerNameToVerify": "server"},
+ )["IntegrationId"]
+
+ resp = client.update_integration(
+ ApiId=api_id, IntegrationId=int_id, Description="updated int"
+ )
+
+ resp.should.have.key("IntegrationId")
+ resp.should.have.key("ConnectionId").equals("conn_id")
+ resp.should.have.key("ConnectionType").equals("INTERNET")
+ resp.should.have.key("ContentHandlingStrategy").equals("CONVERT_TO_BINARY")
+ resp.should.have.key("CredentialsArn").equals("cred:arn")
+ resp.should.have.key("Description").equals("updated int")
+ resp.should.have.key("IntegrationMethod").equals("PUT")
+ resp.should.have.key("IntegrationType").equals("HTTP")
+ resp.should.have.key("IntegrationSubtype").equals("n/a")
+ resp.should.have.key("PassthroughBehavior").equals("WHEN_NO_MATCH")
+ resp.should.have.key("PayloadFormatVersion").equals("1.0")
+ resp.should.have.key("RequestParameters").equals({"r": "p"})
+ resp.should.have.key("RequestTemplates").equals({"r": "t"})
+ resp.should.have.key("ResponseParameters").equals({"res": {"par": "am"}})
+ resp.should.have.key("TemplateSelectionExpression").equals("tse")
+ resp.should.have.key("TimeoutInMillis").equals(123)
+ resp.should.have.key("TlsConfig").equals({"ServerNameToVerify": "server"})
+
+
+@mock_apigatewayv2
+def test_update_integration_all_attrs():
+ client = boto3.client("apigatewayv2", region_name="eu-west-1")
+ api_id = client.create_api(Name="test-api", ProtocolType="HTTP")["ApiId"]
+
+ int_id = client.create_integration(
+ ApiId=api_id,
+ ConnectionId="conn_id",
+ ConnectionType="INTERNET",
+ ContentHandlingStrategy="CONVERT_TO_BINARY",
+ CredentialsArn="cred:arn",
+ Description="my full integration",
+ IntegrationMethod="PUT",
+ IntegrationType="HTTP",
+ IntegrationSubtype="n/a",
+ PassthroughBehavior="WHEN_NO_MATCH",
+ PayloadFormatVersion="1.0",
+ RequestParameters={"r": "p"},
+ RequestTemplates={"r": "t"},
+ ResponseParameters={"res": {"par": "am"}},
+ TemplateSelectionExpression="tse",
+ TimeoutInMillis=123,
+ TlsConfig={"ServerNameToVerify": "server"},
+ )["IntegrationId"]
+
+ resp = client.update_integration(
+ ApiId=api_id,
+ IntegrationId=int_id,
+ ConnectionType="VPC_LINK",
+ ContentHandlingStrategy="CONVERT_TO_TEXT",
+ CredentialsArn="",
+ IntegrationMethod="PATCH",
+ IntegrationType="AWS",
+ PassthroughBehavior="NEVER",
+ )
+
+ resp.should.have.key("IntegrationId")
+ resp.should.have.key("ConnectionId").equals("conn_id")
+ resp.should.have.key("ConnectionType").equals("VPC_LINK")
+ resp.should.have.key("ContentHandlingStrategy").equals("CONVERT_TO_TEXT")
+ resp.should.have.key("CredentialsArn").equals("")
+ resp.should.have.key("Description").equals("my full integration")
+ resp.should.have.key("IntegrationMethod").equals("PATCH")
+ resp.should.have.key("IntegrationType").equals("AWS")
+ resp.should.have.key("IntegrationSubtype").equals("n/a")
+ resp.should.have.key("PassthroughBehavior").equals("NEVER")
+ resp.should.have.key("PayloadFormatVersion").equals("1.0")
+ resp.should.have.key("RequestParameters").equals({"r": "p"})
+ resp.should.have.key("RequestTemplates").equals({"r": "t"})
+ resp.should.have.key("ResponseParameters").equals({"res": {"par": "am"}})
+ resp.should.have.key("TemplateSelectionExpression").equals("tse")
+ resp.should.have.key("TimeoutInMillis").equals(123)
+ resp.should.have.key("TlsConfig").equals({"ServerNameToVerify": "server"})
+
+
+@mock_apigatewayv2
+def test_update_integration_request_parameters():
+ client = boto3.client("apigatewayv2", region_name="eu-west-1")
+ api_id = client.create_api(Name="test-api", ProtocolType="HTTP")["ApiId"]
+
+ resp = client.create_integration(
+ ApiId=api_id,
+ ConnectionType="INTERNET",
+ IntegrationMethod="ANY",
+ IntegrationType="HTTP_PROXY",
+ IntegrationUri="http://www.example.com",
+ PayloadFormatVersion="1.0",
+ RequestParameters={
+ "append:header.header1": "$context.requestId",
+ "remove:querystring.qs1": "''",
+ },
+ ResponseParameters={
+ "404": {"append:header.error": "$stageVariables.environmentId"},
+ "500": {
+ "append:header.header1": "$context.requestId",
+ "overwrite:statuscode": "403",
+ },
+ },
+ )
+
+ int_id = resp["IntegrationId"]
+ # Having an empty value between quotes ("''") is valid
+ resp["RequestParameters"].should.equal(
+ {"append:header.header1": "$context.requestId", "remove:querystring.qs1": "''"}
+ )
+
+ resp = client.update_integration(
+ ApiId=api_id,
+ IntegrationId=int_id,
+ IntegrationType="HTTP_PROXY",
+ RequestParameters={
+ "append:header.header1": "$context.accountId",
+ "overwrite:header.header2": "$stageVariables.environmentId",
+ "remove:querystring.qs1": "",
+ },
+ ResponseParameters={
+ "404": {},
+ "500": {
+ "append:header.header1": "$context.requestId",
+ "overwrite:statuscode": "403",
+ },
+ },
+ )
+
+ resp.should.have.key("IntegrationId")
+ resp.should.have.key("IntegrationMethod").equals("ANY")
+ resp.should.have.key("IntegrationType").equals("HTTP_PROXY")
+ resp.should.have.key("PayloadFormatVersion").equals("1.0")
+ # Having no value ("") is not valid, so that param should not be persisted
+ resp.should.have.key("RequestParameters").equals(
+ {
+ "append:header.header1": "$context.accountId",
+ "overwrite:header.header2": "$stageVariables.environmentId",
+ }
+ )
+ resp.should.have.key("ResponseParameters").equals(
+ {
+ "404": {},
+ "500": {
+ "append:header.header1": "$context.requestId",
+ "overwrite:statuscode": "403",
+ },
+ }
+ )
diff --git a/tests/test_apigatewayv2/test_apigatewayv2_models.py b/tests/test_apigatewayv2/test_apigatewayv2_models.py
new file mode 100644
index 000000000..bbc28ee86
--- /dev/null
+++ b/tests/test_apigatewayv2/test_apigatewayv2_models.py
@@ -0,0 +1,115 @@
+import boto3
+import pytest
+
+from botocore.exceptions import ClientError
+from moto import mock_apigatewayv2
+
+
+@mock_apigatewayv2
+def test_create_model():
+ client = boto3.client("apigatewayv2", region_name="eu-west-1")
+ api_id = client.create_api(Name="test-api", ProtocolType="HTTP")["ApiId"]
+
+ resp = client.create_model(
+ ApiId=api_id, ContentType="app/xml", Description="desc", Name="nm", Schema="cs"
+ )
+
+ resp.should.have.key("ContentType").equals("app/xml")
+ resp.should.have.key("Description").equals("desc")
+ resp.should.have.key("ModelId")
+ resp.should.have.key("Name").equals("nm")
+ resp.should.have.key("Schema").equals("cs")
+
+
+@mock_apigatewayv2
+def test_get_model():
+ client = boto3.client("apigatewayv2", region_name="eu-west-1")
+ api_id = client.create_api(Name="test-api", ProtocolType="HTTP")["ApiId"]
+
+ model_id = client.create_model(
+ ApiId=api_id, ContentType="app/xml", Description="desc", Name="nm", Schema="cs"
+ )["ModelId"]
+
+ resp = client.get_model(ApiId=api_id, ModelId=model_id)
+
+ resp.should.have.key("ContentType").equals("app/xml")
+ resp.should.have.key("Description").equals("desc")
+ resp.should.have.key("ModelId")
+ resp.should.have.key("Name").equals("nm")
+ resp.should.have.key("Schema").equals("cs")
+
+
+@mock_apigatewayv2
+def test_delete_model():
+ client = boto3.client("apigatewayv2", region_name="eu-west-1")
+ api_id = client.create_api(Name="test-api", ProtocolType="HTTP")["ApiId"]
+
+ model_id = client.create_model(
+ ApiId=api_id, ContentType="app/xml", Description="desc", Name="nm", Schema="cs"
+ )["ModelId"]
+
+ client.delete_model(ApiId=api_id, ModelId=model_id)
+
+ with pytest.raises(ClientError) as exc:
+ client.get_model(ApiId=api_id, ModelId=model_id)
+
+ err = exc.value.response["Error"]
+ err["Code"].should.equal("NotFoundException")
+
+
+@mock_apigatewayv2
+def test_get_model_unknown():
+ client = boto3.client("apigatewayv2", region_name="eu-west-1")
+ api_id = client.create_api(Name="test-api", ProtocolType="HTTP")["ApiId"]
+
+ with pytest.raises(ClientError) as exc:
+ client.get_model(ApiId=api_id, ModelId="unknown")
+
+ err = exc.value.response["Error"]
+ err["Code"].should.equal("NotFoundException")
+
+
+@mock_apigatewayv2
+def test_update_model_single_attr():
+ client = boto3.client("apigatewayv2", region_name="eu-west-1")
+ api_id = client.create_api(Name="test-api", ProtocolType="HTTP")["ApiId"]
+
+ model_id = client.create_model(
+ ApiId=api_id, ContentType="app/xml", Description="desc", Name="nm", Schema="cs"
+ )["ModelId"]
+
+ model_id = client.get_model(ApiId=api_id, ModelId=model_id)["ModelId"]
+
+ resp = client.update_model(ApiId=api_id, ModelId=model_id, Schema="cs2")
+
+ resp.should.have.key("ContentType").equals("app/xml")
+ resp.should.have.key("Description").equals("desc")
+ resp.should.have.key("ModelId")
+ resp.should.have.key("Name").equals("nm")
+ resp.should.have.key("Schema").equals("cs2")
+
+
+@mock_apigatewayv2
+def test_update_model_all_attrs():
+ client = boto3.client("apigatewayv2", region_name="eu-west-1")
+ api_id = client.create_api(Name="test-api", ProtocolType="HTTP")["ApiId"]
+
+ model_id = client.create_model(
+ ApiId=api_id, ContentType="app/xml", Description="desc", Name="nm", Schema="cs"
+ )["ModelId"]
+
+ model_id = client.get_model(ApiId=api_id, ModelId=model_id)["ModelId"]
+
+ resp = client.update_model(
+ ApiId=api_id,
+ ModelId=model_id,
+ ContentType="app/html",
+ Description="html2.x",
+ Name="html-schema",
+ Schema="cs2",
+ )
+
+ resp.should.have.key("ContentType").equals("app/html")
+ resp.should.have.key("Description").equals("html2.x")
+ resp.should.have.key("Name").equals("html-schema")
+ resp.should.have.key("Schema").equals("cs2")
diff --git a/tests/test_apigatewayv2/test_apigatewayv2_reimport.py b/tests/test_apigatewayv2/test_apigatewayv2_reimport.py
new file mode 100644
index 000000000..cd6c1cef1
--- /dev/null
+++ b/tests/test_apigatewayv2/test_apigatewayv2_reimport.py
@@ -0,0 +1,94 @@
+import boto3
+import pytest
+import sure # noqa # pylint: disable=unused-import
+
+from botocore.exceptions import ClientError
+from moto import mock_apigatewayv2
+
+
+@mock_apigatewayv2
+def test_reimport_api_standard_fields():
+ client = boto3.client("apigatewayv2", region_name="eu-west-1")
+ api_id = client.create_api(Name="test-api", ProtocolType="HTTP")["ApiId"]
+
+ resp = client.reimport_api(
+ ApiId=api_id,
+ Body="---\nopenapi: 3.0.1\ninfo:\n title: tf-acc-test-2983214806752144669_DIFFERENT\n version: 2.0\nx-amazon-apigateway-cors:\n allow_methods:\n - delete\n allow_origins:\n - https://www.google.de\npaths:\n \"/test\":\n get:\n x-amazon-apigateway-integration:\n type: HTTP_PROXY\n httpMethod: GET\n payloadFormatVersion: '1.0'\n uri: https://www.google.de\n",
+ )
+
+ resp.should.have.key("CorsConfiguration").equals({})
+ resp.should.have.key("Name").equals("tf-acc-test-2983214806752144669_DIFFERENT")
+ resp.should.have.key("Version").equals("2.0")
+
+
+@mock_apigatewayv2
+def test_reimport_api_failonwarnings():
+ client = boto3.client("apigatewayv2", region_name="eu-west-1")
+ api_id = client.create_api(Name="test-import-api", ProtocolType="HTTP")["ApiId"]
+
+ with pytest.raises(ClientError) as exc:
+ client.reimport_api(
+ ApiId=api_id,
+ Body='{\n "openapi": "3.0.1",\n "info": {\n "title": "Title test",\n "version": "2.0",\n "description": "Description test"\n },\n "paths": {\n "/update": {\n "get": {\n "x-amazon-apigateway-integration": {\n "type": "HTTP_PROXY",\n "httpMethod": "GET",\n "payloadFormatVersion": "1.0",\n "uri": "https://www.google.de"\n },\n "responses": {\n "200": {\n "description": "Response description",\n "content": {\n "application/json": {\n "schema": {\n "$ref": "#/components/schemas/ModelThatDoesNotExist"\n }\n }\n }\n }\n }\n }\n }\n }\n}\n',
+ FailOnWarnings=True,
+ )
+ err = exc.value.response["Error"]
+ err["Code"].should.equal("BadRequestException")
+ err["Message"].should.equal(
+ "Warnings found during import:\n\tParse issue: attribute paths.'/update'(get).responses.200.content.schema.#/components/schemas/ModelThatDoesNotExist is missing"
+ )
+
+ # Title should not have been updated
+ resp = client.get_api(ApiId=api_id)
+ resp.should.have.key("Name").equals("test-import-api")
+
+
+@mock_apigatewayv2
+def test_reimport_api_do_not_failonwarnings():
+ client = boto3.client("apigatewayv2", region_name="eu-west-1")
+ api_id = client.create_api(Name="test-import-api", ProtocolType="HTTP")["ApiId"]
+
+ client.reimport_api(
+ ApiId=api_id,
+ Body='{\n "openapi": "3.0.1",\n "info": {\n "title": "Title test",\n "version": "2.0",\n "description": "Description test"\n },\n "paths": {\n "/update": {\n "get": {\n "x-amazon-apigateway-integration": {\n "type": "HTTP_PROXY",\n "httpMethod": "GET",\n "payloadFormatVersion": "1.0",\n "uri": "https://www.google.de"\n },\n "responses": {\n "200": {\n "description": "Response description",\n "content": {\n "application/json": {\n "schema": {\n "$ref": "#/components/schemas/ModelThatDoesNotExist"\n }\n }\n }\n }\n }\n }\n }\n }\n}\n',
+ FailOnWarnings=False,
+ )
+
+ # Title should have been updated
+ resp = client.get_api(ApiId=api_id)
+ resp.should.have.key("Name").equals("Title test")
+
+
+@mock_apigatewayv2
+def test_reimport_api_routes_and_integrations():
+ client = boto3.client("apigatewayv2", region_name="eu-west-1")
+ resp = client.create_api(Name="test-import-api", ProtocolType="HTTP")
+ api_id = resp["ApiId"]
+
+ client.reimport_api(
+ ApiId=api_id,
+ Body='{\n "openapi": "3.0.1",\n "info": {\n "title": "tf-acc-test-121780722915762033_DIFFERENT",\n "version": "1.0"\n },\n "paths": {\n "/test": {\n "get": {\n "x-amazon-apigateway-integration": {\n "type": "HTTP_PROXY",\n "httpMethod": "GET",\n "payloadFormatVersion": "1.0",\n "uri": "https://www.google.de"\n }\n }\n }\n }\n}\n',
+ )
+
+ resp = client.get_integrations(ApiId=api_id)
+ resp.should.have.key("Items").length_of(1)
+ integration = resp["Items"][0]
+ integration.should.have.key("ConnectionType").equals("INTERNET")
+ integration.should.have.key("IntegrationId")
+ integration.should.have.key("IntegrationMethod").equals("GET")
+ integration.should.have.key("IntegrationType").equals("HTTP_PROXY")
+ integration.should.have.key("IntegrationUri").equals("https://www.google.de")
+ integration.should.have.key("PayloadFormatVersion").equals("1.0")
+ integration.should.have.key("TimeoutInMillis").equals(30000)
+
+ resp = client.get_routes(ApiId=api_id)
+ resp.should.have.key("Items").length_of(1)
+ route = resp["Items"][0]
+ route.should.have.key("ApiKeyRequired").equals(False)
+ route.should.have.key("AuthorizationScopes").equals([])
+ route.should.have.key("RequestParameters").equals({})
+ route.should.have.key("RouteId")
+ route.should.have.key("RouteKey").equals("GET /test")
+ route.should.have.key("Target").should.equal(
+ f"integrations/{integration['IntegrationId']}"
+ )
diff --git a/tests/test_apigatewayv2/test_apigatewayv2_routes.py b/tests/test_apigatewayv2/test_apigatewayv2_routes.py
new file mode 100644
index 000000000..4553eb945
--- /dev/null
+++ b/tests/test_apigatewayv2/test_apigatewayv2_routes.py
@@ -0,0 +1,298 @@
+import boto3
+import pytest
+
+from botocore.exceptions import ClientError
+from moto import mock_apigatewayv2
+
+
+@mock_apigatewayv2
+def test_get_routes_empty():
+ client = boto3.client("apigatewayv2", region_name="eu-west-1")
+ api_id = client.create_api(Name="test-api", ProtocolType="HTTP")["ApiId"]
+
+ resp = client.get_routes(ApiId=api_id)
+ resp.should.have.key("Items").equals([])
+
+
+@mock_apigatewayv2
+def test_create_route_minimal():
+ client = boto3.client("apigatewayv2", region_name="ap-southeast-1")
+ api_id = client.create_api(Name="test-api", ProtocolType="HTTP")["ApiId"]
+ resp = client.create_route(ApiId=api_id, RouteKey="GET /")
+
+ resp.should.have.key("ApiKeyRequired").equals(False)
+ resp.should.have.key("AuthorizationType").equals("NONE")
+ resp.should.have.key("RouteId")
+ resp.should.have.key("RouteKey").equals("GET /")
+
+ resp.shouldnt.have.key("AuthorizationScopes")
+ resp.shouldnt.have.key("AuthorizerId")
+ resp.shouldnt.have.key("ModelSelectionExpression")
+ resp.shouldnt.have.key("OperationName")
+ resp.shouldnt.have.key("RequestModels")
+ resp.shouldnt.have.key("RouteResponseSelectionExpression")
+ resp.shouldnt.have.key("Target")
+
+
+@mock_apigatewayv2
+def test_create_route_full():
+ client = boto3.client("apigatewayv2", region_name="ap-southeast-1")
+ api_id = client.create_api(
+ Name="test-api",
+ ProtocolType="WEBSOCKET",
+ RouteSelectionExpression="${request.method}",
+ )["ApiId"]
+ resp = client.create_route(
+ ApiId=api_id,
+ ApiKeyRequired=True,
+ AuthorizerId="auth_id",
+ AuthorizationScopes=["scope1", "scope2"],
+ AuthorizationType="CUSTOM",
+ ModelSelectionExpression="mse",
+ OperationName="OP",
+ RequestModels={"req": "uest"},
+ RequestParameters={"action": {"Required": True}},
+ RouteKey="GET /",
+ RouteResponseSelectionExpression="$default",
+ Target="t",
+ )
+
+ resp.should.have.key("ApiKeyRequired").equals(True)
+ resp.should.have.key("AuthorizationType").equals("CUSTOM")
+ resp.should.have.key("AuthorizationScopes").equals(["scope1", "scope2"])
+ resp.should.have.key("AuthorizerId").equals("auth_id")
+ resp.should.have.key("RouteId")
+ resp.should.have.key("RouteKey").equals("GET /")
+
+ resp.should.have.key("ModelSelectionExpression").equals("mse")
+ resp.should.have.key("OperationName").equals("OP")
+ resp.should.have.key("RequestModels").equals({"req": "uest"})
+ resp.should.have.key("RequestParameters").equals({"action": {"Required": True}})
+ resp.should.have.key("RouteResponseSelectionExpression").equals("$default")
+ resp.should.have.key("Target").equals("t")
+
+
+@mock_apigatewayv2
+def test_delete_route():
+ client = boto3.client("apigatewayv2", region_name="eu-west-1")
+ api_id = client.create_api(Name="test-api", ProtocolType="HTTP")["ApiId"]
+ route_id = client.create_route(ApiId=api_id, RouteKey="GET /")["RouteId"]
+
+ client.delete_route(ApiId=api_id, RouteId=route_id)
+
+ resp = client.get_routes(ApiId=api_id)
+ resp.should.have.key("Items").length_of(0)
+
+
+@mock_apigatewayv2
+def test_get_route():
+ client = boto3.client("apigatewayv2", region_name="ap-southeast-1")
+ api_id = client.create_api(Name="test-api", ProtocolType="HTTP")["ApiId"]
+ route_id = client.create_route(ApiId=api_id, RouteKey="GET /")["RouteId"]
+
+ resp = client.get_route(ApiId=api_id, RouteId=route_id)
+
+ resp.should.have.key("ApiKeyRequired").equals(False)
+ resp.should.have.key("AuthorizationType").equals("NONE")
+ resp.should.have.key("RouteId")
+ resp.should.have.key("RouteKey").equals("GET /")
+
+ resp.shouldnt.have.key("AuthorizationScopes")
+ resp.shouldnt.have.key("AuthorizerId")
+ resp.shouldnt.have.key("ModelSelectionExpression")
+ resp.shouldnt.have.key("OperationName")
+ resp.shouldnt.have.key("RouteResponseSelectionExpression")
+ resp.shouldnt.have.key("Target")
+
+
+@mock_apigatewayv2
+def test_get_route_unknown():
+ client = boto3.client("apigatewayv2", region_name="ap-southeast-1")
+ api_id = client.create_api(Name="test-api", ProtocolType="HTTP")["ApiId"]
+ with pytest.raises(ClientError) as exc:
+ client.get_route(ApiId=api_id, RouteId="unknown")
+
+ err = exc.value.response["Error"]
+ err["Code"].should.equal("NotFoundException")
+ err["Message"].should.equal("Invalid Route identifier specified unknown")
+
+
+@mock_apigatewayv2
+def test_get_routes():
+ client = boto3.client("apigatewayv2", region_name="eu-west-1")
+ api_id = client.create_api(Name="test-api", ProtocolType="HTTP")["ApiId"]
+ client.create_route(ApiId=api_id, RouteKey="GET /")
+
+ resp = client.get_routes(ApiId=api_id)
+ resp.should.have.key("Items").length_of(1)
+
+
+@mock_apigatewayv2
+def test_update_route_single_attribute():
+ client = boto3.client("apigatewayv2", region_name="ap-southeast-1")
+ api_id = client.create_api(Name="test-api", ProtocolType="HTTP")["ApiId"]
+ route_id = client.create_route(ApiId=api_id, RouteKey="GET /")["RouteId"]
+
+ resp = client.update_route(ApiId=api_id, RouteId=route_id, RouteKey="POST /")
+
+ resp.should.have.key("ApiKeyRequired").equals(False)
+ resp.should.have.key("AuthorizationType").equals("NONE")
+ resp.should.have.key("RouteId").equals(route_id)
+ resp.should.have.key("RouteKey").equals("POST /")
+
+
+@mock_apigatewayv2
+def test_update_route_all_attributes():
+ client = boto3.client("apigatewayv2", region_name="ap-southeast-1")
+ api_id = client.create_api(Name="test-api", ProtocolType="HTTP")["ApiId"]
+ route_id = client.create_route(ApiId=api_id, ApiKeyRequired=True, RouteKey="GET /")[
+ "RouteId"
+ ]
+
+ resp = client.update_route(
+ ApiId=api_id,
+ RouteId=route_id,
+ ApiKeyRequired=False,
+ AuthorizationScopes=["scope"],
+ AuthorizerId="auth_id",
+ AuthorizationType="JWT",
+ ModelSelectionExpression="mse",
+ OperationName="OP",
+ RequestModels={"req": "uest"},
+ RequestParameters={"action": {"Required": True}},
+ RouteResponseSelectionExpression="$default",
+ Target="t",
+ )
+
+ resp.should.have.key("ApiKeyRequired").equals(False)
+ resp.should.have.key("AuthorizationType").equals("JWT")
+ resp.should.have.key("AuthorizationScopes").equals(["scope"])
+ resp.should.have.key("AuthorizerId").equals("auth_id")
+ resp.should.have.key("RouteId")
+ resp.should.have.key("RouteKey").equals("GET /")
+ resp.should.have.key("ModelSelectionExpression").equals("mse")
+ resp.should.have.key("OperationName").equals("OP")
+ resp.should.have.key("RequestModels").equals({"req": "uest"})
+ resp.should.have.key("RequestParameters").equals({"action": {"Required": True}})
+ resp.should.have.key("RouteResponseSelectionExpression").equals("$default")
+ resp.should.have.key("Target").equals("t")
+
+
+@mock_apigatewayv2
+def test_delete_route_request_parameter():
+ client = boto3.client("apigatewayv2", region_name="us-east-1")
+ api_id = client.create_api(Name="test-api", ProtocolType="HTTP")["ApiId"]
+ route_id = client.create_route(
+ ApiId=api_id,
+ RequestParameters={
+ "action": {"Required": True},
+ "route.request.header.authorization": {"Required": False},
+ "zparam": {"Required": False},
+ },
+ RouteKey="GET /",
+ )["RouteId"]
+
+ request_params = client.get_route(ApiId=api_id, RouteId=route_id)[
+ "RequestParameters"
+ ]
+ request_params.keys().should.have.length_of(3)
+
+ client.delete_route_request_parameter(
+ ApiId=api_id,
+ RouteId=route_id,
+ RequestParameterKey="route.request.header.authorization",
+ )
+
+ request_params = client.get_route(ApiId=api_id, RouteId=route_id)[
+ "RequestParameters"
+ ]
+ request_params.keys().should.have.length_of(2)
+ request_params.should.have.key("action")
+ request_params.should.have.key("zparam")
+
+
+@mock_apigatewayv2
+def test_create_route_response_minimal():
+ client = boto3.client("apigatewayv2", region_name="ap-southeast-1")
+ api_id = client.create_api(Name="test-api", ProtocolType="HTTP")["ApiId"]
+ route_id = client.create_route(ApiId=api_id, RouteKey="GET /")["RouteId"]
+
+ resp = client.create_route_response(
+ ApiId=api_id, RouteId=route_id, RouteResponseKey="$default"
+ )
+
+ resp.should.have.key("RouteResponseId")
+ resp.should.have.key("RouteResponseKey").equals("$default")
+
+
+@mock_apigatewayv2
+def test_create_route_response():
+ client = boto3.client("apigatewayv2", region_name="ap-southeast-1")
+ api_id = client.create_api(Name="test-api", ProtocolType="HTTP")["ApiId"]
+ route_id = client.create_route(ApiId=api_id, RouteKey="GET /")["RouteId"]
+
+ resp = client.create_route_response(
+ ApiId=api_id,
+ ModelSelectionExpression="mse",
+ ResponseModels={"test": "tfacctest5832545056931060873"},
+ RouteId=route_id,
+ RouteResponseKey="$default",
+ )
+
+ resp.should.have.key("RouteResponseId")
+ resp.should.have.key("RouteResponseKey").equals("$default")
+ resp.should.have.key("ModelSelectionExpression").equals("mse")
+ resp.should.have.key("ResponseModels").equals(
+ {"test": "tfacctest5832545056931060873"}
+ )
+
+
+@mock_apigatewayv2
+def test_get_route_response():
+ client = boto3.client("apigatewayv2", region_name="ap-southeast-1")
+ api_id = client.create_api(Name="test-api", ProtocolType="HTTP")["ApiId"]
+ route_id = client.create_route(ApiId=api_id, RouteKey="GET /")["RouteId"]
+
+ route_response_id = client.create_route_response(
+ ApiId=api_id, RouteId=route_id, RouteResponseKey="$default"
+ )["RouteResponseId"]
+
+ resp = client.get_route_response(
+ ApiId=api_id, RouteId=route_id, RouteResponseId=route_response_id
+ )
+
+ resp.should.have.key("RouteResponseId")
+ resp.should.have.key("RouteResponseKey").equals("$default")
+
+
+@mock_apigatewayv2
+def test_get_route_response_unknown():
+ client = boto3.client("apigatewayv2", region_name="ap-southeast-1")
+ api_id = client.create_api(Name="test-api", ProtocolType="HTTP")["ApiId"]
+ route_id = client.create_route(ApiId=api_id, RouteKey="GET /")["RouteId"]
+
+ with pytest.raises(ClientError) as exc:
+ client.get_route_response(
+ ApiId=api_id, RouteId=route_id, RouteResponseId="unknown"
+ )
+
+ err = exc.value.response["Error"]
+ err["Code"].should.equal("NotFoundException")
+
+
+@mock_apigatewayv2
+def test_delete_route_response_unknown():
+ client = boto3.client("apigatewayv2", region_name="ap-southeast-1")
+ api_id = client.create_api(Name="test-api", ProtocolType="HTTP")["ApiId"]
+ r_id = client.create_route(ApiId=api_id, RouteKey="GET /")["RouteId"]
+ rr_id = client.create_route_response(
+ ApiId=api_id, RouteId=r_id, RouteResponseKey="$default"
+ )["RouteResponseId"]
+
+ client.delete_route_response(ApiId=api_id, RouteId=r_id, RouteResponseId=rr_id)
+
+ with pytest.raises(ClientError) as exc:
+ client.get_route_response(ApiId=api_id, RouteId=r_id, RouteResponseId=rr_id)
+
+ err = exc.value.response["Error"]
+ err["Code"].should.equal("NotFoundException")
diff --git a/tests/test_apigatewayv2/test_apigatewayv2_tags.py b/tests/test_apigatewayv2/test_apigatewayv2_tags.py
new file mode 100644
index 000000000..35441b03b
--- /dev/null
+++ b/tests/test_apigatewayv2/test_apigatewayv2_tags.py
@@ -0,0 +1,62 @@
+import boto3
+
+from moto import mock_apigatewayv2
+
+
+@mock_apigatewayv2
+def test_create_api_with_tags():
+ client = boto3.client("apigatewayv2", region_name="eu-west-1")
+ resp = client.create_api(
+ Name="test-api", ProtocolType="HTTP", Tags={"key1": "value1", "key2": "value2"}
+ )
+
+ resp.should.have.key("Tags").equals({"key1": "value1", "key2": "value2"})
+
+
+@mock_apigatewayv2
+def test_tag_resource():
+ client = boto3.client("apigatewayv2", region_name="eu-west-1")
+ api_id = client.create_api(Name="test-api", ProtocolType="HTTP")["ApiId"]
+
+ resource_arn = f"arn:aws:apigateway:eu-west-1::/apis/{api_id}"
+ client.tag_resource(
+ ResourceArn=resource_arn, Tags={"key1": "value1", "key2": "value2"}
+ )
+
+ resp = client.get_api(ApiId=api_id)
+
+ resp.should.have.key("Tags").equals({"key1": "value1", "key2": "value2"})
+
+
+@mock_apigatewayv2
+def test_get_tags():
+ client = boto3.client("apigatewayv2", region_name="eu-west-2")
+ api_id = client.create_api(Name="test-api", ProtocolType="HTTP")["ApiId"]
+ resource_arn = f"arn:aws:apigateway:eu-west-2::/apis/{api_id}"
+
+ client.tag_resource(
+ ResourceArn=resource_arn, Tags={"key1": "value1", "key2": "value2"}
+ )
+
+ resp = client.get_tags(ResourceArn=resource_arn)
+
+ resp.should.have.key("Tags").equals({"key1": "value1", "key2": "value2"})
+
+
+@mock_apigatewayv2
+def test_untag_resource():
+ client = boto3.client("apigatewayv2", region_name="eu-west-2")
+ api_id = client.create_api(
+ Name="test-api", ProtocolType="HTTP", Tags={"key1": "value1"}
+ )["ApiId"]
+ resource_arn = f"arn:aws:apigateway:eu-west-2::/apis/{api_id}"
+
+ client.tag_resource(
+ ResourceArn=resource_arn, Tags={"key2": "value2", "key3": "value3"}
+ )
+
+ client.untag_resource(ResourceArn=resource_arn, TagKeys=["key2"])
+
+ resp = client.get_tags(ResourceArn=resource_arn)
+
+ resp.should.have.key("Tags").equals({"key1": "value1", "key3": "value3"})
diff --git a/tests/test_apigatewayv2/test_apigatewayv2_vpclinks.py b/tests/test_apigatewayv2/test_apigatewayv2_vpclinks.py
new file mode 100644
index 000000000..4696f392b
--- /dev/null
+++ b/tests/test_apigatewayv2/test_apigatewayv2_vpclinks.py
@@ -0,0 +1,162 @@
+import boto3
+import pytest
+
+from botocore.exceptions import ClientError
+from moto import mock_apigatewayv2
+
+
+@mock_apigatewayv2
+def test_get_vpc_links_empty():
+ client = boto3.client("apigatewayv2", region_name="eu-west-1")
+
+ resp = client.get_vpc_links()
+ resp.should.have.key("Items").equals([])
+
+
+@mock_apigatewayv2
+def test_create_vpc_links():
+ client = boto3.client("apigatewayv2", region_name="eu-west-1")
+
+ resp = client.create_vpc_link(
+ Name="vpcl",
+ SecurityGroupIds=["sg1", "sg2"],
+ SubnetIds=["sid1", "sid2"],
+ Tags={"key1": "value1"},
+ )
+
+ resp.should.have.key("CreatedDate")
+ resp.should.have.key("Name").equals("vpcl")
+ resp.should.have.key("SecurityGroupIds").equals(["sg1", "sg2"])
+ resp.should.have.key("SubnetIds").equals(["sid1", "sid2"])
+ resp.should.have.key("Tags").equals({"key1": "value1"})
+ resp.should.have.key("VpcLinkId")
+ resp.should.have.key("VpcLinkStatus").equals("AVAILABLE")
+ resp.should.have.key("VpcLinkVersion").equals("V2")
+
+
+@mock_apigatewayv2
+def test_get_vpc_link():
+ client = boto3.client("apigatewayv2", region_name="eu-west-1")
+
+ vpc_link_id = client.create_vpc_link(
+ Name="vpcl",
+ SecurityGroupIds=["sg1", "sg2"],
+ SubnetIds=["sid1", "sid2"],
+ Tags={"key1": "value1"},
+ )["VpcLinkId"]
+
+ resp = client.get_vpc_link(VpcLinkId=vpc_link_id)
+
+ resp.should.have.key("CreatedDate")
+ resp.should.have.key("Name").equals("vpcl")
+ resp.should.have.key("SecurityGroupIds").equals(["sg1", "sg2"])
+ resp.should.have.key("SubnetIds").equals(["sid1", "sid2"])
+ resp.should.have.key("Tags").equals({"key1": "value1"})
+ resp.should.have.key("VpcLinkId")
+ resp.should.have.key("VpcLinkStatus").equals("AVAILABLE")
+ resp.should.have.key("VpcLinkVersion").equals("V2")
+
+
+@mock_apigatewayv2
+def test_get_vpc_link_unknown():
+ client = boto3.client("apigatewayv2", region_name="ap-southeast-1")
+
+ with pytest.raises(ClientError) as exc:
+ client.get_vpc_link(VpcLinkId="unknown")
+ err = exc.value.response["Error"]
+ err["Code"].should.equal("NotFoundException")
+ err["Message"].should.equal("Invalid VpcLink identifier specified unknown")
+
+
+@mock_apigatewayv2
+def test_get_vpc_links():
+ client = boto3.client("apigatewayv2", region_name="eu-west-1")
+
+ vpc_link_id = client.create_vpc_link(
+ Name="vpcl",
+ SecurityGroupIds=["sg1", "sg2"],
+ SubnetIds=["sid1", "sid2"],
+ Tags={"key1": "value1"},
+ )["VpcLinkId"]
+
+ links = client.get_vpc_links()["Items"]
+ links.should.have.length_of(1)
+ links[0]["VpcLinkId"].should.equal(vpc_link_id)
+
+ client.create_vpc_link(
+ Name="vpcl",
+ SecurityGroupIds=["sg1", "sg2"],
+ SubnetIds=["sid1", "sid2"],
+ Tags={"key1": "value1"},
+ )
+
+ links = client.get_vpc_links()["Items"]
+ links.should.have.length_of(2)
+
+
+@mock_apigatewayv2
+def test_delete_vpc_link():
+ client = boto3.client("apigatewayv2", region_name="eu-north-1")
+
+ vpc_link_id = client.create_vpc_link(
+ Name="vpcl",
+ SecurityGroupIds=["sg1", "sg2"],
+ SubnetIds=["sid1", "sid2"],
+ Tags={"key1": "value1"},
+ )["VpcLinkId"]
+
+ links = client.get_vpc_links()["Items"]
+ links.should.have.length_of(1)
+
+ client.delete_vpc_link(VpcLinkId=vpc_link_id)
+
+ links = client.get_vpc_links()["Items"]
+ links.should.have.length_of(0)
+
+
+@mock_apigatewayv2
+def test_update_vpc_link():
+ client = boto3.client("apigatewayv2", region_name="eu-north-1")
+ vpc_link_id = client.create_vpc_link(
+ Name="vpcl",
+ SecurityGroupIds=["sg1", "sg2"],
+ SubnetIds=["sid1", "sid2"],
+ Tags={"key1": "value1"},
+ )["VpcLinkId"]
+
+ resp = client.update_vpc_link(VpcLinkId=vpc_link_id, Name="vpcl2")
+
+ resp.should.have.key("CreatedDate")
+ resp.should.have.key("Name").equals("vpcl2")
+ resp.should.have.key("SecurityGroupIds").equals(["sg1", "sg2"])
+ resp.should.have.key("SubnetIds").equals(["sid1", "sid2"])
+ resp.should.have.key("Tags").equals({"key1": "value1"})
+ resp.should.have.key("VpcLinkId")
+ resp.should.have.key("VpcLinkStatus").equals("AVAILABLE")
+ resp.should.have.key("VpcLinkVersion").equals("V2")
+
+
+@mock_apigatewayv2
+def test_untag_vpc_link():
+ client = boto3.client("apigatewayv2", region_name="eu-west-1")
+
+ vpc_link_id = client.create_vpc_link(
+ Name="vpcl",
+ SecurityGroupIds=["sg1", "sg2"],
+ SubnetIds=["sid1", "sid2"],
+ Tags={"Key1": "value1", "key2": "val2"},
+ )["VpcLinkId"]
+
+ arn = f"arn:aws:apigateway:eu-west-1::/vpclinks/{vpc_link_id}"
+ client.untag_resource(ResourceArn=arn, TagKeys=["Key1"])
+
+ resp = client.get_vpc_link(VpcLinkId=vpc_link_id)
+
+ resp.should.have.key("CreatedDate")
+ resp.should.have.key("Name").equals("vpcl")
+ resp.should.have.key("SecurityGroupIds").equals(["sg1", "sg2"])
+ resp.should.have.key("SubnetIds").equals(["sid1", "sid2"])
+ resp.should.have.key("Tags").equals({"key2": "val2"})
+ resp.should.have.key("VpcLinkId")
+ resp.should.have.key("VpcLinkStatus").equals("AVAILABLE")
+ resp.should.have.key("VpcLinkVersion").equals("V2")
diff --git a/tests/test_apigatewayv2/test_server.py b/tests/test_apigatewayv2/test_server.py
new file mode 100644
index 000000000..460bfe103
--- /dev/null
+++ b/tests/test_apigatewayv2/test_server.py
@@ -0,0 +1,13 @@
+import json
+import sure # noqa # pylint: disable=unused-import
+
+import moto.server as server
+
+
+def test_apigatewayv2_list_apis():
+ backend = server.create_backend_app("apigatewayv2")
+ test_client = backend.test_client()
+
+ resp = test_client.get("/v2/apis")
+ resp.status_code.should.equal(200)
+ json.loads(resp.data).should.equal({"items": []})
diff --git a/tests/test_awslambda/test_lambda.py b/tests/test_awslambda/test_lambda.py
index b60acc2f9..778302887 100644
--- a/tests/test_awslambda/test_lambda.py
+++ b/tests/test_awslambda/test_lambda.py
@@ -117,6 +117,7 @@ def test_create_function_from_aws_bucket():
Description="test lambda function",
Timeout=3,
MemorySize=128,
+ PackageType="ZIP",
Publish=True,
VpcConfig={"SecurityGroupIds": ["sg-123abc"], "SubnetIds": ["subnet-123abc"]},
)
@@ -139,6 +140,7 @@ def test_create_function_from_aws_bucket():
"Description": "test lambda function",
"Timeout": 3,
"MemorySize": 128,
+ "PackageType": "ZIP",
"Version": "1",
"VpcConfig": {
"SecurityGroupIds": ["sg-123abc"],
@@ -338,6 +340,38 @@ def test_get_function_configuration(key):
conn.get_function_configuration(FunctionName="junk", Qualifier="$LATEST")
+@pytest.mark.parametrize("key", ["FunctionName", "FunctionArn"])
+@mock_lambda
+@mock_s3
+def test_get_function_code_signing_config(key):
+ bucket_name = str(uuid4())
+ s3_conn = boto3.client("s3", _lambda_region)
+ s3_conn.create_bucket(
+ Bucket=bucket_name,
+ CreateBucketConfiguration={"LocationConstraint": _lambda_region},
+ )
+
+ zip_content = get_test_zip_file1()
+ s3_conn.put_object(Bucket=bucket_name, Key="test.zip", Body=zip_content)
+ conn = boto3.client("lambda", _lambda_region)
+ function_name = str(uuid4())[0:6]
+
+ fxn = conn.create_function(
+ FunctionName=function_name,
+ Runtime="python3.7",
+ Role=get_role_name(),
+ Handler="lambda_function.lambda_handler",
+ Code={"S3Bucket": bucket_name, "S3Key": "test.zip"},
+ CodeSigningConfigArn="csc:arn",
+ )
+ name_or_arn = fxn[key]
+
+ result = conn.get_function_code_signing_config(FunctionName=name_or_arn)
+
+ result["FunctionName"].should.equal(function_name)
+ result["CodeSigningConfigArn"].should.equal("csc:arn")
+
+
@mock_lambda
@mock_s3
def test_get_function_by_arn():