From 094d00a37a9a0e75fbcda4b695bf4dadd1f0c923 Mon Sep 17 00:00:00 2001 From: Bert Blommers Date: Tue, 8 Feb 2022 20:12:51 -0100 Subject: [PATCH] Feature: APIGatewayV2 (#4840) --- IMPLEMENTATION_COVERAGE.md | 93 +- docs/docs/services/apigateway.rst | 12 +- docs/docs/services/apigatewayv2.rst | 131 ++ docs/docs/services/lambda.rst | 2 +- moto/__init__.py | 43 +- moto/apigateway/exceptions.py | 7 + moto/apigateway/models.py | 34 + moto/apigateway/responses.py | 31 + moto/apigateway/urls.py | 6 + moto/apigatewayv2/__init__.py | 5 + moto/apigatewayv2/exceptions.py | 95 ++ moto/apigatewayv2/models.py | 1484 +++++++++++++++++ moto/apigatewayv2/responses.py | 780 +++++++++ moto/apigatewayv2/urls.py | 32 + moto/awslambda/models.py | 17 + moto/awslambda/responses.py | 10 + moto/awslambda/urls.py | 1 + scripts/update_backend_index.py | 7 +- setup.py | 1 + tests/terraform-tests.success.txt | 8 +- .../test_apigateway_vpclink.py | 110 ++ tests/test_apigatewayv2/__init__.py | 0 tests/test_apigatewayv2/test_apigatewayv2.py | 325 ++++ .../test_apigatewayv2_authorizers.py | 176 ++ .../test_apigatewayv2_integrationresponses.py | 174 ++ .../test_apigatewayv2_integrations.py | 317 ++++ .../test_apigatewayv2_models.py | 115 ++ .../test_apigatewayv2_reimport.py | 94 ++ .../test_apigatewayv2_routes.py | 298 ++++ .../test_apigatewayv2_tags.py | 62 + .../test_apigatewayv2_vpclinks.py | 162 ++ tests/test_apigatewayv2/test_server.py | 13 + tests/test_awslambda/test_lambda.py | 34 + 33 files changed, 4642 insertions(+), 37 deletions(-) create mode 100644 docs/docs/services/apigatewayv2.rst create mode 100644 moto/apigatewayv2/__init__.py create mode 100644 moto/apigatewayv2/exceptions.py create mode 100644 moto/apigatewayv2/models.py create mode 100644 moto/apigatewayv2/responses.py create mode 100644 moto/apigatewayv2/urls.py create mode 100644 tests/test_apigateway/test_apigateway_vpclink.py create mode 100644 tests/test_apigatewayv2/__init__.py create mode 100644 tests/test_apigatewayv2/test_apigatewayv2.py create mode 100644 tests/test_apigatewayv2/test_apigatewayv2_authorizers.py create mode 100644 tests/test_apigatewayv2/test_apigatewayv2_integrationresponses.py create mode 100644 tests/test_apigatewayv2/test_apigatewayv2_integrations.py create mode 100644 tests/test_apigatewayv2/test_apigatewayv2_models.py create mode 100644 tests/test_apigatewayv2/test_apigatewayv2_reimport.py create mode 100644 tests/test_apigatewayv2/test_apigatewayv2_routes.py create mode 100644 tests/test_apigatewayv2/test_apigatewayv2_tags.py create mode 100644 tests/test_apigatewayv2/test_apigatewayv2_vpclinks.py create mode 100644 tests/test_apigatewayv2/test_server.py 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():