diff --git a/IMPLEMENTATION_COVERAGE.md b/IMPLEMENTATION_COVERAGE.md index e6e9f5ad6..f9088b4ec 100644 --- a/IMPLEMENTATION_COVERAGE.md +++ b/IMPLEMENTATION_COVERAGE.md @@ -22,7 +22,7 @@ ## apigateway
-59% implemented +62% implemented - [X] create_api_key - [X] create_authorizer @@ -47,7 +47,7 @@ - [ ] delete_documentation_part - [ ] delete_documentation_version - [X] delete_domain_name -- [ ] delete_gateway_response +- [X] delete_gateway_response - [X] delete_integration - [X] delete_integration_response - [X] delete_method @@ -81,8 +81,8 @@ - [X] get_domain_name - [X] get_domain_names - [ ] get_export -- [ ] get_gateway_response -- [ ] get_gateway_responses +- [X] get_gateway_response +- [X] get_gateway_responses - [X] get_integration - [X] get_integration_response - [X] get_method @@ -112,7 +112,7 @@ - [ ] import_api_keys - [ ] import_documentation_parts - [ ] import_rest_api -- [ ] put_gateway_response +- [X] put_gateway_response - [X] put_integration - [X] put_integration_response - [X] put_method diff --git a/docs/docs/services/apigateway.rst b/docs/docs/services/apigateway.rst index 0148c6c7b..5369da258 100644 --- a/docs/docs/services/apigateway.rst +++ b/docs/docs/services/apigateway.rst @@ -50,7 +50,7 @@ apigateway - [ ] delete_documentation_part - [ ] delete_documentation_version - [X] delete_domain_name -- [ ] delete_gateway_response +- [X] delete_gateway_response - [X] delete_integration - [X] delete_integration_response - [X] delete_method @@ -84,8 +84,12 @@ apigateway - [X] get_domain_name - [X] get_domain_names - [ ] get_export -- [ ] get_gateway_response -- [ ] get_gateway_responses +- [X] get_gateway_response +- [X] get_gateway_responses + + Pagination is not yet implemented + + - [X] get_integration - [X] get_integration_response - [X] get_method @@ -119,7 +123,7 @@ apigateway - [ ] import_api_keys - [ ] import_documentation_parts - [ ] import_rest_api -- [ ] put_gateway_response +- [X] put_gateway_response - [X] put_integration - [X] put_integration_response - [X] put_method diff --git a/moto/apigateway/exceptions.py b/moto/apigateway/exceptions.py index 37f970449..1cc242801 100644 --- a/moto/apigateway/exceptions.py +++ b/moto/apigateway/exceptions.py @@ -1,26 +1,34 @@ from moto.core.exceptions import JsonRESTError -class BadRequestException(JsonRESTError): +class ApiGatewayException(JsonRESTError): pass -class NotFoundException(JsonRESTError): +class BadRequestException(ApiGatewayException): + def __init__(self, message): + super().__init__("BadRequestException", message) + + +class NotFoundException(ApiGatewayException): + def __init__(self, message): + super().__init__("NotFoundException", message) + + +class AccessDeniedException(ApiGatewayException): pass -class AccessDeniedException(JsonRESTError): - pass - - -class ConflictException(JsonRESTError): +class ConflictException(ApiGatewayException): code = 409 + def __init__(self, message): + super().__init__("ConflictException", message) + class AwsProxyNotAllowed(BadRequestException): def __init__(self): super().__init__( - "BadRequestException", "Integrations of type 'AWS_PROXY' currently only supports Lambda function and Firehose stream invocations.", ) @@ -34,98 +42,87 @@ class CrossAccountNotAllowed(AccessDeniedException): class RoleNotSpecified(BadRequestException): def __init__(self): - super().__init__( - "BadRequestException", "Role ARN must be specified for AWS integrations" - ) + super().__init__("Role ARN must be specified for AWS integrations") class IntegrationMethodNotDefined(BadRequestException): def __init__(self): - super().__init__( - "BadRequestException", "Enumeration value for HttpMethod must be non-empty" - ) + super().__init__("Enumeration value for HttpMethod must be non-empty") class InvalidResourcePathException(BadRequestException): def __init__(self): super().__init__( - "BadRequestException", "Resource's path part only allow a-zA-Z0-9._- and curly braces at the beginning and the end and an optional plus sign before the closing brace.", ) class InvalidHttpEndpoint(BadRequestException): def __init__(self): - super().__init__( - "BadRequestException", "Invalid HTTP endpoint specified for URI" - ) + super().__init__("Invalid HTTP endpoint specified for URI") class InvalidArn(BadRequestException): def __init__(self): - super().__init__("BadRequestException", "Invalid ARN specified in the request") + super().__init__("Invalid ARN specified in the request") class InvalidIntegrationArn(BadRequestException): def __init__(self): - super().__init__( - "BadRequestException", "AWS ARN for integration must contain path or action" - ) + super().__init__("AWS ARN for integration must contain path or action") class InvalidRequestInput(BadRequestException): def __init__(self): - super().__init__("BadRequestException", "Invalid request input") + super().__init__("Invalid request input") class NoIntegrationDefined(NotFoundException): def __init__(self): - super().__init__("NotFoundException", "No integration defined for method") + super().__init__("No integration defined for method") class NoIntegrationResponseDefined(NotFoundException): code = 404 def __init__(self, code=None): - super().__init__("NotFoundException", "Invalid Response status code specified") + super().__init__("Invalid Response status code specified") class NoMethodDefined(BadRequestException): def __init__(self): - super().__init__( - "BadRequestException", "The REST API doesn't contain any methods" - ) + super().__init__("The REST API doesn't contain any methods") class AuthorizerNotFoundException(NotFoundException): code = 404 def __init__(self): - super().__init__("NotFoundException", "Invalid Authorizer identifier specified") + super().__init__("Invalid Authorizer identifier specified") class StageNotFoundException(NotFoundException): code = 404 def __init__(self): - super().__init__("NotFoundException", "Invalid stage identifier specified") + super().__init__("Invalid stage identifier specified") class ApiKeyNotFoundException(NotFoundException): code = 404 def __init__(self): - super().__init__("NotFoundException", "Invalid API Key identifier specified") + super().__init__("Invalid API Key identifier specified") class UsagePlanNotFoundException(NotFoundException): code = 404 def __init__(self): - super().__init__("NotFoundException", "Invalid Usage Plan ID specified") + super().__init__("Invalid Usage Plan ID specified") -class ApiKeyAlreadyExists(JsonRESTError): +class ApiKeyAlreadyExists(ApiGatewayException): code = 409 def __init__(self): @@ -136,67 +133,63 @@ class InvalidDomainName(BadRequestException): code = 404 def __init__(self): - super().__init__("BadRequestException", "No Domain Name specified") + super().__init__("No Domain Name specified") class DomainNameNotFound(NotFoundException): code = 404 def __init__(self): - super().__init__( - "NotFoundException", "Invalid domain name identifier specified" - ) + super().__init__("Invalid domain name identifier specified") class InvalidRestApiId(BadRequestException): code = 404 def __init__(self): - super().__init__("BadRequestException", "No Rest API Id specified") + super().__init__("No Rest API Id specified") class InvalidModelName(BadRequestException): code = 404 def __init__(self): - super().__init__("BadRequestException", "No Model Name specified") + super().__init__("No Model Name specified") class RestAPINotFound(NotFoundException): code = 404 def __init__(self): - super().__init__("NotFoundException", "Invalid Rest API Id specified") + super().__init__("Invalid Rest API Id specified") class RequestValidatorNotFound(BadRequestException): code = 400 def __init__(self): - super().__init__("NotFoundException", "Invalid Request Validator Id specified") + super().__init__("Invalid Request Validator Id specified") class ModelNotFound(NotFoundException): code = 404 def __init__(self): - super().__init__("NotFoundException", "Invalid Model Name specified") + super().__init__("Invalid Model Name specified") class ApiKeyValueMinLength(BadRequestException): code = 400 def __init__(self): - super().__init__( - "BadRequestException", "API Key value should be at least 20 characters" - ) + super().__init__("API Key value should be at least 20 characters") class MethodNotFoundException(NotFoundException): code = 404 def __init__(self): - super().__init__("NotFoundException", "Invalid Method identifier specified") + super().__init__("Invalid Method identifier specified") class InvalidBasePathException(BadRequestException): @@ -204,44 +197,63 @@ class InvalidBasePathException(BadRequestException): def __init__(self): super().__init__( - "BadRequestException", "API Gateway V1 doesn't support the slash character (/) in base path mappings. " "To create a multi-level base path mapping, use API Gateway V2.", ) +class DeploymentNotFoundException(NotFoundException): + def __init__(self): + super().__init__("Invalid Deployment identifier specified") + + class InvalidRestApiIdForBasePathMappingException(BadRequestException): code = 400 def __init__(self): - super().__init__("BadRequestException", "Invalid REST API identifier specified") + super().__init__("Invalid REST API identifier specified") class InvalidStageException(BadRequestException): code = 400 def __init__(self): - super().__init__("BadRequestException", "Invalid stage identifier specified") + super().__init__("Invalid stage identifier specified") class BasePathConflictException(ConflictException): def __init__(self): - super().__init__( - "ConflictException", "Base path already exists for this domain name" - ) + super().__init__("Base path already exists for this domain name") class BasePathNotFoundException(NotFoundException): code = 404 def __init__(self): - super().__init__( - "NotFoundException", "Invalid base path mapping identifier specified" - ) + super().__init__("Invalid base path mapping identifier specified") class VpcLinkNotFound(NotFoundException): code = 404 def __init__(self): - super().__init__("NotFoundException", "VPCLink not found") + super().__init__("VPCLink not found") + + +class ValidationException(ApiGatewayException): + code = 400 + + def __init__(self, message): + super().__init__("ValidationException", message) + + +class StageStillActive(BadRequestException): + def __init__(self): + super().__init__( + "Active stages pointing to this deployment must be moved or deleted" + ) + + +class GatewayResponseNotFound(NotFoundException): + def __init__(self): + super().__init__("GatewayResponse not found") diff --git a/moto/apigateway/models.py b/moto/apigateway/models.py index 035a8dd4d..5ccc1b38a 100644 --- a/moto/apigateway/models.py +++ b/moto/apigateway/models.py @@ -20,6 +20,8 @@ from .integration_parsers.aws_parser import TypeAwsParser from .integration_parsers.http_parser import TypeHttpParser from .integration_parsers.unknown_parser import TypeUnknownParser from .exceptions import ( + ConflictException, + DeploymentNotFoundException, ApiKeyNotFoundException, UsagePlanNotFoundException, AwsProxyNotAllowed, @@ -49,7 +51,10 @@ from .exceptions import ( InvalidStageException, BasePathConflictException, BasePathNotFoundException, + StageStillActive, VpcLinkNotFound, + ValidationException, + GatewayResponseNotFound, ) from ..core.models import responses_mock from moto.apigateway.exceptions import MethodNotFoundException @@ -119,6 +124,7 @@ class Integration(BaseModel, dict): request_templates=None, tls_config=None, cache_namespace=None, + timeout_in_millis=None, ): super().__init__() self["type"] = integration_type @@ -131,6 +137,7 @@ class Integration(BaseModel, dict): ] = None # prevent json serialization from including them if none provided self["tlsConfig"] = tls_config self["cacheNamespace"] = cache_namespace + self["timeoutInMillis"] = timeout_in_millis def create_integration_response( self, status_code, selection_pattern, response_templates, content_handling @@ -361,6 +368,7 @@ class Resource(CloudFormationModel): integration_method=None, tls_config=None, cache_namespace=None, + timeout_in_millis=None, ): integration_method = integration_method or method_type integration = Integration( @@ -370,6 +378,7 @@ class Resource(CloudFormationModel): request_templates=request_templates, tls_config=tls_config, cache_namespace=cache_namespace, + timeout_in_millis=timeout_in_millis, ) self.resource_methods[method_type]["methodIntegration"] = integration return integration @@ -451,6 +460,7 @@ class Stage(BaseModel, dict): self["description"] = description self["cacheClusterEnabled"] = cacheClusterEnabled if self["cacheClusterEnabled"]: + self["cacheClusterStatus"] = "AVAILABLE" self["cacheClusterSize"] = str(0.5) if cacheClusterSize is not None: self["cacheClusterSize"] = str(cacheClusterSize) @@ -465,25 +475,39 @@ class Stage(BaseModel, dict): self._apply_operation_to_variables(op) elif "/cacheClusterEnabled" in op["path"]: self["cacheClusterEnabled"] = self._str2bool(op["value"]) - if "cacheClusterSize" not in self and self["cacheClusterEnabled"]: - self["cacheClusterSize"] = str(0.5) + if self["cacheClusterEnabled"]: + self["cacheClusterStatus"] = "AVAILABLE" + if "cacheClusterSize" not in self: + self["cacheClusterSize"] = str(0.5) + else: + self["cacheClusterStatus"] = "NOT_AVAILABLE" elif "/cacheClusterSize" in op["path"]: - self["cacheClusterSize"] = str(float(op["value"])) + self["cacheClusterSize"] = str(op["value"]) elif "/description" in op["path"]: self["description"] = op["value"] elif "/deploymentId" in op["path"]: self["deploymentId"] = op["value"] elif op["op"] == "replace": - # Method Settings drop into here - # (e.g., path could be '/*/*/logging/loglevel') - split_path = op["path"].split("/", 3) - if len(split_path) != 4: - continue - self._patch_method_setting( - "/".join(split_path[1:3]), split_path[3], op["value"] - ) + if op["path"] == "/tracingEnabled": + self["tracingEnabled"] = self._str2bool(op["value"]) + elif op["path"].startswith("/accessLogSettings/"): + self["accessLogSettings"] = self.get("accessLogSettings", {}) + self["accessLogSettings"][op["path"].split("/")[-1]] = op["value"] + else: + # (e.g., path could be '/*/*/logging/loglevel') + split_path = op["path"].split("/", 3) + if len(split_path) != 4: + continue + self._patch_method_setting( + "/".join(split_path[1:3]), split_path[3], op["value"] + ) + elif op["op"] == "remove": + if op["path"] == "/accessLogSettings": + self["accessLogSettings"] = None else: - raise Exception('Patch operation "%s" not implemented' % op["op"]) + raise ValidationException( + "Member must satisfy enum value set: [add, remove, move, test, replace, copy]" + ) return self def _patch_method_setting(self, resource_path_and_method, key, value): @@ -768,6 +792,7 @@ class RestAPI(CloudFormationModel): self.minimum_compression_size = kwargs.get("minimum_compression_size") self.deployments = {} self.authorizers = {} + self.gateway_responses = {} self.stages = {} self.resources = {} self.models = {} @@ -972,6 +997,8 @@ class RestAPI(CloudFormationModel): tags=None, tracing_enabled=None, ): + if name in self.stages: + raise ConflictException("Stage already exists") if variables is None: variables = {} stage = Stage( @@ -994,9 +1021,10 @@ class RestAPI(CloudFormationModel): deployment_id = create_id() deployment = Deployment(deployment_id, name, description) self.deployments[deployment_id] = deployment - self.stages[name] = Stage( - name=name, deployment_id=deployment_id, variables=stage_variables - ) + if name: + self.stages[name] = Stage( + name=name, deployment_id=deployment_id, variables=stage_variables + ) self.update_integration_mocks(name) return deployment @@ -1014,6 +1042,13 @@ class RestAPI(CloudFormationModel): return list(self.deployments.values()) def delete_deployment(self, deployment_id): + if deployment_id not in self.deployments: + raise DeploymentNotFoundException() + deployment = self.deployments[deployment_id] + if deployment["stageName"] and deployment["stageName"] in self.stages: + # Stage is still active + raise StageStillActive() + return self.deployments.pop(deployment_id) def create_request_validator( @@ -1046,6 +1081,29 @@ class RestAPI(CloudFormationModel): self.request_validators[validator_id].apply_patch_operations(patch_operations) return self.request_validators[validator_id] + def put_gateway_response( + self, response_type, status_code, response_parameters, response_templates + ): + response = GatewayResponse( + response_type=response_type, + status_code=status_code, + response_parameters=response_parameters, + response_templates=response_templates, + ) + self.gateway_responses[response_type] = response + return response + + def get_gateway_response(self, response_type): + if response_type not in self.gateway_responses: + raise GatewayResponseNotFound() + return self.gateway_responses[response_type] + + def get_gateway_responses(self): + return list(self.gateway_responses.values()) + + def delete_gateway_response(self, response_type): + self.gateway_responses.pop(response_type, None) + class DomainName(BaseModel, dict): def __init__(self, domain_name, **kwargs): @@ -1136,6 +1194,21 @@ class BasePathMapping(BaseModel, dict): self["stage"] = value +class GatewayResponse(BaseModel, dict): + def __init__( + self, response_type, status_code, response_parameters, response_templates + ): + super().__init__() + self["responseType"] = response_type + if status_code is not None: + self["statusCode"] = status_code + if response_parameters is not None: + self["responseParameters"] = response_parameters + if response_templates is not None: + self["responseTemplates"] = response_templates + self["defaultResponse"] = False + + class APIGatewayBackend(BaseBackend): """ API Gateway mock. @@ -1423,6 +1496,7 @@ class APIGatewayBackend(BaseBackend): request_templates=None, tls_config=None, cache_namespace=None, + timeout_in_millis=None, ): resource = self.get_resource(function_id, resource_id) if credentials and not re.match( @@ -1462,6 +1536,7 @@ class APIGatewayBackend(BaseBackend): request_templates=request_templates, tls_config=tls_config, cache_namespace=cache_namespace, + timeout_in_millis=timeout_in_millis, ) return integration @@ -1915,5 +1990,37 @@ class APIGatewayBackend(BaseBackend): """ return list(self.vpc_links.values()) + def put_gateway_response( + self, + rest_api_id, + response_type, + status_code, + response_parameters, + response_templates, + ): + api = self.get_rest_api(rest_api_id) + response = api.put_gateway_response( + response_type, + status_code=status_code, + response_parameters=response_parameters, + response_templates=response_templates, + ) + return response + + def get_gateway_response(self, rest_api_id, response_type): + api = self.get_rest_api(rest_api_id) + return api.get_gateway_response(response_type) + + def get_gateway_responses(self, rest_api_id): + """ + Pagination is not yet implemented + """ + api = self.get_rest_api(rest_api_id) + return api.get_gateway_responses() + + def delete_gateway_response(self, rest_api_id, response_type): + api = self.get_rest_api(rest_api_id) + api.delete_gateway_response(response_type) + apigateway_backends = BackendDict(APIGatewayBackend, "apigateway") diff --git a/moto/apigateway/responses.py b/moto/apigateway/responses.py index 76398f734..de36c2b1a 100644 --- a/moto/apigateway/responses.py +++ b/moto/apigateway/responses.py @@ -1,31 +1,13 @@ import json +from functools import wraps from urllib.parse import unquote from moto.utilities.utils import merge_multiple_dicts from moto.core.responses import BaseResponse from .models import apigateway_backends from .exceptions import ( - ApiKeyNotFoundException, - UsagePlanNotFoundException, - BadRequestException, - CrossAccountNotAllowed, - AuthorizerNotFoundException, - StageNotFoundException, - ApiKeyAlreadyExists, - DomainNameNotFound, - InvalidDomainName, - InvalidRestApiId, - InvalidModelName, - RestAPINotFound, - ModelNotFound, - ApiKeyValueMinLength, + ApiGatewayException, InvalidRequestInput, - NoIntegrationDefined, - NoIntegrationResponseDefined, - NotFoundException, - ConflictException, - InvalidRestApiIdForBasePathMappingException, - InvalidStageException, ) API_KEY_SOURCES = ["AUTHORIZER", "HEADER"] @@ -33,6 +15,17 @@ AUTHORIZER_TYPES = ["TOKEN", "REQUEST", "COGNITO_USER_POOLS"] ENDPOINT_CONFIGURATION_TYPES = ["PRIVATE", "EDGE", "REGIONAL"] +def error_handler(f): + @wraps(f) + def _wrapper(*args, **kwargs): + try: + return f(*args, **kwargs) + except ApiGatewayException as e: + return e.code, e.get_headers(), e.get_body() + + return _wrapper + + class APIGatewayResponse(BaseResponse): def error(self, type_, message, status=400): headers = self.response_headers or {} @@ -117,6 +110,7 @@ class APIGatewayResponse(BaseResponse): value = op["value"] return self.__validate_api_key_source(value) + @error_handler def restapis_individual(self, request, full_url, headers): self.setup_class(request, full_url, headers) function_id = self.path.replace("/restapis/", "", 1).split("/")[0] @@ -130,16 +124,7 @@ class APIGatewayResponse(BaseResponse): response = self.__validte_rest_patch_operations(patch_operations) if response is not None: return response - try: - rest_api = self.backend.update_rest_api(function_id, patch_operations) - except RestAPINotFound as error: - return ( - error.code, - {}, - '{{"message":"{0}","code":"{1}"}}'.format( - error.message, error.error_type - ), - ) + rest_api = self.backend.update_rest_api(function_id, patch_operations) return 200, {}, json.dumps(rest_api.to_dict()) @@ -155,25 +140,37 @@ class APIGatewayResponse(BaseResponse): json.dumps({"item": [resource.to_dict() for resource in resources]}), ) + @error_handler + def gateway_response(self, request, full_url, headers): + self.setup_class(request, full_url, headers) + if request.method == "PUT": + return self.put_gateway_response() + elif request.method == "GET": + return self.get_gateway_response() + elif request.method == "DELETE": + return self.delete_gateway_response() + + def gateway_responses(self, request, full_url, headers): + self.setup_class(request, full_url, headers) + if request.method == "GET": + return self.get_gateway_responses() + + @error_handler def resource_individual(self, request, full_url, headers): self.setup_class(request, full_url, headers) function_id = self.path.replace("/restapis/", "", 1).split("/")[0] resource_id = self.path.split("/")[-1] - try: - if self.method == "GET": - resource = self.backend.get_resource(function_id, resource_id) - elif self.method == "POST": - path_part = self._get_param("pathPart") - resource = self.backend.create_resource( - function_id, resource_id, path_part - ) - elif self.method == "DELETE": - resource = self.backend.delete_resource(function_id, resource_id) - return 200, {}, json.dumps(resource.to_dict()) - except BadRequestException as e: - return self.error("BadRequestException", e.message) + if self.method == "GET": + resource = self.backend.get_resource(function_id, resource_id) + elif self.method == "POST": + path_part = self._get_param("pathPart") + resource = self.backend.create_resource(function_id, resource_id, path_part) + elif self.method == "DELETE": + resource = self.backend.delete_resource(function_id, resource_id) + return 200, {}, json.dumps(resource.to_dict()) + @error_handler def resource_methods(self, request, full_url, headers): self.setup_class(request, full_url, headers) url_path_parts = self.path.split("/") @@ -182,11 +179,8 @@ class APIGatewayResponse(BaseResponse): method_type = url_path_parts[6] if self.method == "GET": - try: - method = self.backend.get_method(function_id, resource_id, method_type) - return 200, {}, json.dumps(method) - except NotFoundException as nfe: - return self.error("NotFoundException", nfe.message) + method = self.backend.get_method(function_id, resource_id, method_type) + return 200, {}, json.dumps(method) elif self.method == "PUT": authorization_type = self._get_param("authorizationType") api_key_required = self._get_param("apiKeyRequired") @@ -308,54 +302,48 @@ class APIGatewayResponse(BaseResponse): return 200, {}, json.dumps(authorizer_response) + @error_handler def request_validators(self, request, full_url, headers): self.setup_class(request, full_url, headers) url_path_parts = self.path.split("/") restapi_id = url_path_parts[2] - try: - if self.method == "GET": - validators = self.backend.get_request_validators(restapi_id) - res = json.dumps( - {"item": [validator.to_dict() for validator in validators]} - ) - return 200, {}, res - if self.method == "POST": - name = self._get_param("name") - body = self._get_bool_param("validateRequestBody") - params = self._get_bool_param("validateRequestParameters") - validator = self.backend.create_request_validator( - restapi_id, name, body, params - ) - return 200, {}, json.dumps(validator) - except BadRequestException as e: - return self.error("BadRequestException", e.message) - except CrossAccountNotAllowed as e: - return self.error("AccessDeniedException", e.message) + if self.method == "GET": + validators = self.backend.get_request_validators(restapi_id) + res = json.dumps( + {"item": [validator.to_dict() for validator in validators]} + ) + return 200, {}, res + if self.method == "POST": + name = self._get_param("name") + body = self._get_bool_param("validateRequestBody") + params = self._get_bool_param("validateRequestParameters") + validator = self.backend.create_request_validator( + restapi_id, name, body, params + ) + return 200, {}, json.dumps(validator) + @error_handler def request_validator_individual(self, request, full_url, headers): self.setup_class(request, full_url, headers) url_path_parts = self.path.split("/") restapi_id = url_path_parts[2] validator_id = url_path_parts[4] - try: - if self.method == "GET": - validator = self.backend.get_request_validator(restapi_id, validator_id) - return 200, {}, json.dumps(validator) - if self.method == "DELETE": - self.backend.delete_request_validator(restapi_id, validator_id) - return 202, {}, "" - if self.method == "PATCH": - patch_operations = self._get_param("patchOperations") - validator = self.backend.update_request_validator( - restapi_id, validator_id, patch_operations - ) - return 200, {}, json.dumps(validator) - except BadRequestException as e: - return self.error("BadRequestException", e.message) - except CrossAccountNotAllowed as e: - return self.error("AccessDeniedException", e.message) + if self.method == "GET": + validator = self.backend.get_request_validator(restapi_id, validator_id) + return 200, {}, json.dumps(validator) + if self.method == "DELETE": + self.backend.delete_request_validator(restapi_id, validator_id) + return 202, {}, "" + if self.method == "PATCH": + patch_operations = self._get_param("patchOperations") + validator = self.backend.update_request_validator( + restapi_id, validator_id, patch_operations + ) + return 200, {}, json.dumps(validator) + + @error_handler def authorizers(self, request, full_url, headers): self.setup_class(request, full_url, headers) url_path_parts = self.path.split("/") @@ -363,18 +351,7 @@ class APIGatewayResponse(BaseResponse): authorizer_id = url_path_parts[4] if self.method == "GET": - try: - authorizer_response = self.backend.get_authorizer( - restapi_id, authorizer_id - ) - except AuthorizerNotFoundException as error: - return ( - error.code, - {}, - '{{"message":"{0}","code":"{1}"}}'.format( - error.message, error.error_type - ), - ) + authorizer_response = self.backend.get_authorizer(restapi_id, authorizer_id) elif self.method == "PATCH": patch_operations = self._get_param("patchOperations") authorizer_response = self.backend.update_authorizer( @@ -385,6 +362,7 @@ class APIGatewayResponse(BaseResponse): return 202, {}, "{}" return 200, {}, json.dumps(authorizer_response) + @error_handler def restapis_stages(self, request, full_url, headers): self.setup_class(request, full_url, headers) url_path_parts = self.path.split("/") @@ -435,28 +413,27 @@ class APIGatewayResponse(BaseResponse): stage["tags"].pop(tag, None) return 200, {}, json.dumps({"item": ""}) + @error_handler def stages(self, request, full_url, headers): self.setup_class(request, full_url, headers) url_path_parts = self.path.split("/") function_id = url_path_parts[2] stage_name = url_path_parts[4] - try: - if self.method == "GET": - stage_response = self.backend.get_stage(function_id, stage_name) + if self.method == "GET": + stage_response = self.backend.get_stage(function_id, stage_name) - elif self.method == "PATCH": - patch_operations = self._get_param("patchOperations") - stage_response = self.backend.update_stage( - function_id, stage_name, patch_operations - ) - elif self.method == "DELETE": - self.backend.delete_stage(function_id, stage_name) - return 202, {}, "{}" - return 200, {}, json.dumps(stage_response) - except StageNotFoundException as error: - return error.code, {}, error.get_body() + elif self.method == "PATCH": + patch_operations = self._get_param("patchOperations") + stage_response = self.backend.update_stage( + function_id, stage_name, patch_operations + ) + elif self.method == "DELETE": + self.backend.delete_stage(function_id, stage_name) + return 202, {}, "{}" + return 200, {}, json.dumps(stage_response) + @error_handler def integrations(self, request, full_url, headers): self.setup_class(request, full_url, headers) url_path_parts = self.path.split("/") @@ -464,50 +441,47 @@ class APIGatewayResponse(BaseResponse): resource_id = url_path_parts[4] method_type = url_path_parts[6] - try: - integration_response = {} + integration_response = {} - if self.method == "GET": - integration_response = self.backend.get_integration( - function_id, resource_id, method_type - ) - elif self.method == "PUT": - integration_type = self._get_param("type") - uri = self._get_param("uri") - credentials = self._get_param("credentials") - request_templates = self._get_param("requestTemplates") - tls_config = self._get_param("tlsConfig") - cache_namespace = self._get_param("cacheNamespace") - self.backend.get_method(function_id, resource_id, method_type) + if self.method == "GET": + integration_response = self.backend.get_integration( + function_id, resource_id, method_type + ) + elif self.method == "PUT": + integration_type = self._get_param("type") + uri = self._get_param("uri") + credentials = self._get_param("credentials") + request_templates = self._get_param("requestTemplates") + tls_config = self._get_param("tlsConfig") + cache_namespace = self._get_param("cacheNamespace") + timeout_in_millis = self._get_param("timeoutInMillis") + self.backend.get_method(function_id, resource_id, method_type) - integration_http_method = self._get_param( - "httpMethod" - ) # default removed because it's a required parameter + integration_http_method = self._get_param( + "httpMethod" + ) # default removed because it's a required parameter - integration_response = self.backend.put_integration( - function_id, - resource_id, - method_type, - integration_type, - uri, - credentials=credentials, - integration_method=integration_http_method, - request_templates=request_templates, - tls_config=tls_config, - cache_namespace=cache_namespace, - ) - elif self.method == "DELETE": - integration_response = self.backend.delete_integration( - function_id, resource_id, method_type - ) + integration_response = self.backend.put_integration( + function_id, + resource_id, + method_type, + integration_type, + uri, + credentials=credentials, + integration_method=integration_http_method, + request_templates=request_templates, + tls_config=tls_config, + cache_namespace=cache_namespace, + timeout_in_millis=timeout_in_millis, + ) + elif self.method == "DELETE": + integration_response = self.backend.delete_integration( + function_id, resource_id, method_type + ) - return 200, {}, json.dumps(integration_response) - - except BadRequestException as e: - return self.error("BadRequestException", e.message) - except CrossAccountNotAllowed as e: - return self.error("AccessDeniedException", e.message) + return 200, {}, json.dumps(integration_response) + @error_handler def integration_responses(self, request, full_url, headers): self.setup_class(request, full_url, headers) url_path_parts = self.path.split("/") @@ -516,94 +490,69 @@ class APIGatewayResponse(BaseResponse): method_type = url_path_parts[6] status_code = url_path_parts[9] - try: - if self.method == "GET": - integration_response = self.backend.get_integration_response( - function_id, resource_id, method_type, status_code - ) - elif self.method == "PUT": - if not self.body: - raise InvalidRequestInput() + if self.method == "GET": + integration_response = self.backend.get_integration_response( + function_id, resource_id, method_type, status_code + ) + elif self.method == "PUT": + if not self.body: + raise InvalidRequestInput() - selection_pattern = self._get_param("selectionPattern") - response_templates = self._get_param("responseTemplates") - content_handling = self._get_param("contentHandling") - integration_response = self.backend.put_integration_response( - function_id, - resource_id, - method_type, - status_code, - selection_pattern, - response_templates, - content_handling, - ) - elif self.method == "DELETE": - integration_response = self.backend.delete_integration_response( - function_id, resource_id, method_type, status_code - ) - return 200, {}, json.dumps(integration_response) - except BadRequestException as e: - return self.error("BadRequestException", e.message) - except (NoIntegrationDefined, NoIntegrationResponseDefined) as e: - return self.error("NotFoundException", e.message) + selection_pattern = self._get_param("selectionPattern") + response_templates = self._get_param("responseTemplates") + content_handling = self._get_param("contentHandling") + integration_response = self.backend.put_integration_response( + function_id, + resource_id, + method_type, + status_code, + selection_pattern, + response_templates, + content_handling, + ) + elif self.method == "DELETE": + integration_response = self.backend.delete_integration_response( + function_id, resource_id, method_type, status_code + ) + return 200, {}, json.dumps(integration_response) + @error_handler def deployments(self, request, full_url, headers): self.setup_class(request, full_url, headers) function_id = self.path.replace("/restapis/", "", 1).split("/")[0] - try: - if self.method == "GET": - deployments = self.backend.get_deployments(function_id) - return 200, {}, json.dumps({"item": deployments}) - elif self.method == "POST": - name = self._get_param("stageName") - description = self._get_param("description", if_none="") - stage_variables = self._get_param("variables", if_none={}) - deployment = self.backend.create_deployment( - function_id, name, description, stage_variables - ) - return 200, {}, json.dumps(deployment) - except BadRequestException as e: - return self.error("BadRequestException", e.message) - except NotFoundException as e: - return self.error("NotFoundException", e.message) + if self.method == "GET": + deployments = self.backend.get_deployments(function_id) + return 200, {}, json.dumps({"item": deployments}) + elif self.method == "POST": + name = self._get_param("stageName") + description = self._get_param("description") + stage_variables = self._get_param("variables", if_none={}) + deployment = self.backend.create_deployment( + function_id, name, description, stage_variables + ) + return 200, {}, json.dumps(deployment) + @error_handler def individual_deployment(self, request, full_url, headers): self.setup_class(request, full_url, headers) url_path_parts = self.path.split("/") function_id = url_path_parts[2] deployment_id = url_path_parts[4] - deployment = None if self.method == "GET": deployment = self.backend.get_deployment(function_id, deployment_id) + return 200, {}, json.dumps(deployment) elif self.method == "DELETE": deployment = self.backend.delete_deployment(function_id, deployment_id) - return 200, {}, json.dumps(deployment) + return 202, {}, json.dumps(deployment) + @error_handler def apikeys(self, request, full_url, headers): self.setup_class(request, full_url, headers) if self.method == "POST": - try: - apikey_response = self.backend.create_api_key(json.loads(self.body)) - except ApiKeyAlreadyExists as error: - return ( - error.code, - {}, - '{{"message":"{0}","code":"{1}"}}'.format( - error.message, error.error_type - ), - ) - - except ApiKeyValueMinLength as error: - return ( - error.code, - {}, - '{{"message":"{0}","code":"{1}"}}'.format( - error.message, error.error_type - ), - ) + apikey_response = self.backend.create_api_key(json.loads(self.body)) return 201, {}, json.dumps(apikey_response) elif self.method == "GET": @@ -611,6 +560,7 @@ class APIGatewayResponse(BaseResponse): apikeys_response = self.backend.get_api_keys(include_values=include_values) return 200, {}, json.dumps({"item": apikeys_response}) + @error_handler def apikey_individual(self, request, full_url, headers): self.setup_class(request, full_url, headers) @@ -620,12 +570,9 @@ class APIGatewayResponse(BaseResponse): status_code = 200 if self.method == "GET": include_value = self._get_bool_param("includeValue") - try: - apikey_response = self.backend.get_api_key( - apikey, include_value=include_value - ) - except ApiKeyNotFoundException as e: - return self.error("NotFoundException", e.message) + apikey_response = self.backend.get_api_key( + apikey, include_value=include_value + ) elif self.method == "PATCH": patch_operations = self._get_param("patchOperations") apikey_response = self.backend.update_api_key(apikey, patch_operations) @@ -645,6 +592,7 @@ class APIGatewayResponse(BaseResponse): return 200, {}, json.dumps({"item": usage_plans_response}) return 200, {}, json.dumps(usage_plan_response) + @error_handler def usage_plan_individual(self, request, full_url, headers): self.setup_class(request, full_url, headers) @@ -652,16 +600,7 @@ class APIGatewayResponse(BaseResponse): usage_plan = url_path_parts[2] if self.method == "GET": - try: - usage_plan_response = self.backend.get_usage_plan(usage_plan) - except (UsagePlanNotFoundException) as error: - return ( - error.code, - {}, - '{{"message":"{0}","code":"{1}"}}'.format( - error.message, error.error_type - ), - ) + usage_plan_response = self.backend.get_usage_plan(usage_plan) elif self.method == "DELETE": usage_plan_response = self.backend.delete_usage_plan(usage_plan) elif self.method == "PATCH": @@ -671,6 +610,7 @@ class APIGatewayResponse(BaseResponse): ) return 200, {}, json.dumps(usage_plan_response) + @error_handler def usage_plan_keys(self, request, full_url, headers): self.setup_class(request, full_url, headers) @@ -678,23 +618,15 @@ class APIGatewayResponse(BaseResponse): usage_plan_id = url_path_parts[2] if self.method == "POST": - try: - usage_plan_response = self.backend.create_usage_plan_key( - usage_plan_id, json.loads(self.body) - ) - except ApiKeyNotFoundException as error: - return ( - error.code, - {}, - '{{"message":"{0}","code":"{1}"}}'.format( - error.message, error.error_type - ), - ) + usage_plan_response = self.backend.create_usage_plan_key( + usage_plan_id, json.loads(self.body) + ) return 201, {}, json.dumps(usage_plan_response) elif self.method == "GET": usage_plans_response = self.backend.get_usage_plan_keys(usage_plan_id) return 200, {}, json.dumps({"item": usage_plans_response}) + @error_handler def usage_plan_key_individual(self, request, full_url, headers): self.setup_class(request, full_url, headers) @@ -703,183 +635,133 @@ class APIGatewayResponse(BaseResponse): key_id = url_path_parts[4] if self.method == "GET": - try: - usage_plan_response = self.backend.get_usage_plan_key( - usage_plan_id, key_id - ) - except (UsagePlanNotFoundException, ApiKeyNotFoundException) as error: - return ( - error.code, - {}, - '{{"message":"{0}","code":"{1}"}}'.format( - error.message, error.error_type - ), - ) + usage_plan_response = self.backend.get_usage_plan_key(usage_plan_id, key_id) elif self.method == "DELETE": usage_plan_response = self.backend.delete_usage_plan_key( usage_plan_id, key_id ) return 200, {}, json.dumps(usage_plan_response) + @error_handler def domain_names(self, request, full_url, headers): self.setup_class(request, full_url, headers) - try: - if self.method == "GET": - domain_names = self.backend.get_domain_names() - return 200, {}, json.dumps({"item": domain_names}) + if self.method == "GET": + domain_names = self.backend.get_domain_names() + return 200, {}, json.dumps({"item": domain_names}) - elif self.method == "POST": - domain_name = self._get_param("domainName") - certificate_name = self._get_param("certificateName") - tags = self._get_param("tags") - certificate_arn = self._get_param("certificateArn") - certificate_body = self._get_param("certificateBody") - certificate_private_key = self._get_param("certificatePrivateKey") - certificate_chain = self._get_param("certificateChain") - regional_certificate_name = self._get_param("regionalCertificateName") - regional_certificate_arn = self._get_param("regionalCertificateArn") - endpoint_configuration = self._get_param("endpointConfiguration") - security_policy = self._get_param("securityPolicy") - generate_cli_skeleton = self._get_param("generateCliSkeleton") - domain_name_resp = self.backend.create_domain_name( - domain_name, - certificate_name, - tags, - certificate_arn, - certificate_body, - certificate_private_key, - certificate_chain, - regional_certificate_name, - regional_certificate_arn, - endpoint_configuration, - security_policy, - generate_cli_skeleton, - ) - return 200, {}, json.dumps(domain_name_resp) - - except InvalidDomainName as error: - return ( - error.code, - {}, - '{{"message":"{0}","code":"{1}"}}'.format( - error.message, error.error_type - ), + elif self.method == "POST": + domain_name = self._get_param("domainName") + certificate_name = self._get_param("certificateName") + tags = self._get_param("tags") + certificate_arn = self._get_param("certificateArn") + certificate_body = self._get_param("certificateBody") + certificate_private_key = self._get_param("certificatePrivateKey") + certificate_chain = self._get_param("certificateChain") + regional_certificate_name = self._get_param("regionalCertificateName") + regional_certificate_arn = self._get_param("regionalCertificateArn") + endpoint_configuration = self._get_param("endpointConfiguration") + security_policy = self._get_param("securityPolicy") + generate_cli_skeleton = self._get_param("generateCliSkeleton") + domain_name_resp = self.backend.create_domain_name( + domain_name, + certificate_name, + tags, + certificate_arn, + certificate_body, + certificate_private_key, + certificate_chain, + regional_certificate_name, + regional_certificate_arn, + endpoint_configuration, + security_policy, + generate_cli_skeleton, ) + return 200, {}, json.dumps(domain_name_resp) + @error_handler def domain_name_induvidual(self, request, full_url, headers): self.setup_class(request, full_url, headers) url_path_parts = self.path.split("/") domain_name = url_path_parts[2] domain_names = {} - try: - if self.method == "GET": - if domain_name is not None: - domain_names = self.backend.get_domain_name(domain_name) - elif self.method == "DELETE": - if domain_name is not None: - self.backend.delete_domain_name(domain_name) - elif self.method == "PATCH": - if domain_name is not None: - patch_operations = self._get_param("patchOperations") - self.backend.update_domain_name(domain_name, patch_operations) - else: - msg = ( - 'Method "%s" for API GW domain names not implemented' % self.method - ) - return 404, {}, json.dumps({"error": msg}) - return 200, {}, json.dumps(domain_names) - except DomainNameNotFound as error: - return self.error("NotFoundException", error.message) + if self.method == "GET": + if domain_name is not None: + domain_names = self.backend.get_domain_name(domain_name) + elif self.method == "DELETE": + if domain_name is not None: + self.backend.delete_domain_name(domain_name) + elif self.method == "PATCH": + if domain_name is not None: + patch_operations = self._get_param("patchOperations") + self.backend.update_domain_name(domain_name, patch_operations) + else: + msg = 'Method "%s" for API GW domain names not implemented' % self.method + return 404, {}, json.dumps({"error": msg}) + return 200, {}, json.dumps(domain_names) + + @error_handler def models(self, request, full_url, headers): self.setup_class(request, full_url, headers) rest_api_id = self.path.replace("/restapis/", "", 1).split("/")[0] - try: - if self.method == "GET": - models = self.backend.get_models(rest_api_id) - return 200, {}, json.dumps({"item": models}) + if self.method == "GET": + models = self.backend.get_models(rest_api_id) + return 200, {}, json.dumps({"item": models}) - elif self.method == "POST": - name = self._get_param("name") - description = self._get_param("description") - schema = self._get_param("schema") - content_type = self._get_param("contentType") - cli_input_json = self._get_param("cliInputJson") - generate_cli_skeleton = self._get_param("generateCliSkeleton") - model = self.backend.create_model( - rest_api_id, - name, - content_type, - description, - schema, - cli_input_json, - generate_cli_skeleton, - ) - - return 200, {}, json.dumps(model) - - except (InvalidRestApiId, InvalidModelName, RestAPINotFound) as error: - return ( - error.code, - {}, - '{{"message":"{0}","code":"{1}"}}'.format( - error.message, error.error_type - ), + elif self.method == "POST": + name = self._get_param("name") + description = self._get_param("description") + schema = self._get_param("schema") + content_type = self._get_param("contentType") + cli_input_json = self._get_param("cliInputJson") + generate_cli_skeleton = self._get_param("generateCliSkeleton") + model = self.backend.create_model( + rest_api_id, + name, + content_type, + description, + schema, + cli_input_json, + generate_cli_skeleton, ) + return 200, {}, json.dumps(model) + + @error_handler def model_induvidual(self, request, full_url, headers): self.setup_class(request, full_url, headers) url_path_parts = self.path.split("/") rest_api_id = url_path_parts[2] model_name = url_path_parts[4] model_info = {} - try: - if self.method == "GET": - model_info = self.backend.get_model(rest_api_id, model_name) - return 200, {}, json.dumps(model_info) - except ( - ModelNotFound, - RestAPINotFound, - InvalidRestApiId, - InvalidModelName, - ) as error: - return ( - error.code, - {}, - '{{"message":"{0}","code":"{1}"}}'.format( - error.message, error.error_type - ), - ) + if self.method == "GET": + model_info = self.backend.get_model(rest_api_id, model_name) + return 200, {}, json.dumps(model_info) + @error_handler def base_path_mappings(self, request, full_url, headers): self.setup_class(request, full_url, headers) url_path_parts = self.path.split("/") domain_name = url_path_parts[2] - try: - if self.method == "GET": - base_path_mappings = self.backend.get_base_path_mappings(domain_name) - return 200, {}, json.dumps({"item": base_path_mappings}) - elif self.method == "POST": - base_path = self._get_param("basePath") - rest_api_id = self._get_param("restApiId") - stage = self._get_param("stage") + if self.method == "GET": + base_path_mappings = self.backend.get_base_path_mappings(domain_name) + return 200, {}, json.dumps({"item": base_path_mappings}) + elif self.method == "POST": + base_path = self._get_param("basePath") + rest_api_id = self._get_param("restApiId") + stage = self._get_param("stage") - base_path_mapping_resp = self.backend.create_base_path_mapping( - domain_name, rest_api_id, base_path, stage, - ) - return 201, {}, json.dumps(base_path_mapping_resp) - except BadRequestException as e: - return self.error("BadRequestException", e.message) - except NotFoundException as e: - return self.error("NotFoundException", e.message, 404) - except ConflictException as e: - return self.error("ConflictException", e.message, 409) + base_path_mapping_resp = self.backend.create_base_path_mapping( + domain_name, rest_api_id, base_path, stage, + ) + return 201, {}, json.dumps(base_path_mapping_resp) + @error_handler def base_path_mapping_individual(self, request, full_url, headers): self.setup_class(request, full_url, headers) @@ -888,42 +770,33 @@ class APIGatewayResponse(BaseResponse): domain_name = url_path_parts[2] base_path = unquote(url_path_parts[4]) - try: - if self.method == "GET": - base_path_mapping = self.backend.get_base_path_mapping( - domain_name, base_path - ) - return 200, {}, json.dumps(base_path_mapping) - elif self.method == "DELETE": - self.backend.delete_base_path_mapping(domain_name, base_path) - return 202, {}, "" - elif self.method == "PATCH": - patch_operations = self._get_param("patchOperations") - base_path_mapping = self.backend.update_base_path_mapping( - domain_name, base_path, patch_operations - ) + if self.method == "GET": + base_path_mapping = self.backend.get_base_path_mapping( + domain_name, base_path + ) return 200, {}, json.dumps(base_path_mapping) - except NotFoundException as e: - return self.error("NotFoundException", e.message, 404) - except InvalidRestApiIdForBasePathMappingException as e: - return self.error("BadRequestException", e.message) - except InvalidStageException as e: - return self.error("BadRequestException", e.message) + elif self.method == "DELETE": + self.backend.delete_base_path_mapping(domain_name, base_path) + return 202, {}, "" + elif self.method == "PATCH": + patch_operations = self._get_param("patchOperations") + base_path_mapping = self.backend.update_base_path_mapping( + domain_name, base_path, patch_operations + ) + return 200, {}, json.dumps(base_path_mapping) + @error_handler 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) + 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) def vpc_links(self, request, full_url, headers): self.setup_class(request, full_url, headers) @@ -940,3 +813,40 @@ class APIGatewayResponse(BaseResponse): name=name, description=description, target_arns=target_arns, tags=tags ) return 200, {}, json.dumps(vpc_link) + + def put_gateway_response(self): + rest_api_id = self.path.split("/")[-3] + response_type = self.path.split("/")[-1] + params = json.loads(self.body) + status_code = params.get("statusCode") + response_parameters = params.get("responseParameters") + response_templates = params.get("responseTemplates") + response = self.backend.put_gateway_response( + rest_api_id=rest_api_id, + response_type=response_type, + status_code=status_code, + response_parameters=response_parameters, + response_templates=response_templates, + ) + return 200, {}, json.dumps(response) + + def get_gateway_response(self): + rest_api_id = self.path.split("/")[-3] + response_type = self.path.split("/")[-1] + response = self.backend.get_gateway_response( + rest_api_id=rest_api_id, response_type=response_type, + ) + return 200, {}, json.dumps(response) + + def get_gateway_responses(self): + rest_api_id = self.path.split("/")[-2] + responses = self.backend.get_gateway_responses(rest_api_id=rest_api_id,) + return 200, {}, json.dumps(dict(item=responses)) + + def delete_gateway_response(self): + rest_api_id = self.path.split("/")[-3] + response_type = self.path.split("/")[-1] + self.backend.delete_gateway_response( + rest_api_id=rest_api_id, response_type=response_type, + ) + return 202, {}, json.dumps(dict()) diff --git a/moto/apigateway/urls.py b/moto/apigateway/urls.py index 089bb610a..816395a32 100644 --- a/moto/apigateway/urls.py +++ b/moto/apigateway/urls.py @@ -35,6 +35,8 @@ 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}/restapis/(?P[^/]+)/gatewayresponses/?$": response.gateway_responses, + "{0}/restapis/(?P[^/]+)/gatewayresponses/(?P[^/]+)/?$": response.gateway_response, "{0}/vpclinks$": response.vpc_links, "{0}/vpclinks/(?P[^/]+)": response.vpc_link, } diff --git a/moto/firehose/models.py b/moto/firehose/models.py index fdf24b158..1f469b9a5 100644 --- a/moto/firehose/models.py +++ b/moto/firehose/models.py @@ -151,7 +151,7 @@ class DeliveryStream( del self.destinations[0][destination_name]["S3Configuration"] self.delivery_stream_status = "ACTIVE" - self.delivery_stream_arn = f"arn:aws:firehose:{region}:{ACCOUNT_ID}:/delivery_stream/{delivery_stream_name}" + self.delivery_stream_arn = f"arn:aws:firehose:{region}:{ACCOUNT_ID}:deliverystream/{delivery_stream_name}" self.create_timestamp = datetime.now(timezone.utc).isoformat() self.version_id = "1" # Used to track updates of destination configs diff --git a/tests/terraform-tests.failures.txt b/tests/terraform-tests.failures.txt index 80c7b5ab0..92378672e 100644 --- a/tests/terraform-tests.failures.txt +++ b/tests/terraform-tests.failures.txt @@ -3,7 +3,6 @@ TestAccAWSEc2TransitGatewayPeeringAttachmentAccepter TestAccAWSEc2TransitGatewayRouteTableAssociation TestAccAWSEc2TransitGatewayVpcAttachment TestAccAWSFms -TestAccAWSIAMRolePolicy TestAccAWSSecurityGroup_forceRevokeRules_ TestAccAWSDefaultSecurityGroup_Classic_ TestAccDataSourceAwsNetworkInterface_CarrierIPAssociation diff --git a/tests/terraform-tests.success.txt b/tests/terraform-tests.success.txt index a5865a903..aa2e68b28 100644 --- a/tests/terraform-tests.success.txt +++ b/tests/terraform-tests.success.txt @@ -94,9 +94,8 @@ TestAccAWSUserGroupMembership TestAccAWSUserPolicyAttachment TestAccAWSUserSSHKey TestAccAWSVpc_ -TestAccAWSAPIGatewayStage_basic -TestAccAWSAPIGatewayStage_accessLogSettings_kinesis -TestAccAWSAPIGatewayStage_accessLogSettings +TestAccAWSAPIGatewayGatewayResponse +TestAccAWSAPIGatewayStage TestAccAWSSsmDocumentDataSource TestAccAwsEc2ManagedPrefixList TestAccAWSEgressOnlyInternetGateway diff --git a/tests/test_apigateway/test_apigateway.py b/tests/test_apigateway/test_apigateway.py index cb3d400d5..82ac1cbcf 100644 --- a/tests/test_apigateway/test_apigateway.py +++ b/tests/test_apigateway/test_apigateway.py @@ -5,7 +5,7 @@ from freezegun import freeze_time import sure # noqa # pylint: disable=unused-import from botocore.exceptions import ClientError -from moto import mock_apigateway, mock_cognitoidp, settings +from moto import mock_apigateway, mock_cognitoidp from moto.core import ACCOUNT_ID import pytest @@ -576,17 +576,15 @@ def test_integrations(): uri=test_uri, requestTemplates=templates, integrationHttpMethod="POST", + timeoutInMillis=29000, ) - # this is hard to match against, so remove it - response["ResponseMetadata"].pop("HTTPHeaders", None) - response["ResponseMetadata"].pop("RetryAttempts", None) - response["ResponseMetadata"].should.equal({"HTTPStatusCode": 200}) response = client.get_integration( restApiId=api_id, resourceId=root_id, httpMethod="POST" ) response["uri"].should.equal(test_uri) response["requestTemplates"].should.equal(templates) + response.should.have.key("timeoutInMillis").equals(29000) @mock_apigateway @@ -980,354 +978,6 @@ def test_delete_authorizer(): ) -@mock_apigateway -def test_update_stage_configuration(): - client = boto3.client("apigateway", region_name="us-west-2") - stage_name = "staging" - response = client.create_rest_api(name="my_api", description="this is my api") - api_id = response["id"] - create_method_integration(client, api_id) - - response = client.create_deployment( - restApiId=api_id, stageName=stage_name, description="1.0.1" - ) - deployment_id = response["id"] - - response = client.get_deployment(restApiId=api_id, deploymentId=deployment_id) - # createdDate is hard to match against, remove it - response.pop("createdDate", None) - # this is hard to match against, so remove it - response["ResponseMetadata"].pop("HTTPHeaders", None) - response["ResponseMetadata"].pop("RetryAttempts", None) - response.should.equal( - { - "id": deployment_id, - "ResponseMetadata": {"HTTPStatusCode": 200}, - "description": "1.0.1", - } - ) - - response = client.create_deployment( - restApiId=api_id, stageName=stage_name, description="1.0.2" - ) - deployment_id2 = response["id"] - - stage = client.get_stage(restApiId=api_id, stageName=stage_name) - stage["stageName"].should.equal(stage_name) - stage["deploymentId"].should.equal(deployment_id2) - stage.shouldnt.have.key("cacheClusterSize") - - client.update_stage( - restApiId=api_id, - stageName=stage_name, - patchOperations=[ - {"op": "replace", "path": "/cacheClusterEnabled", "value": "True"} - ], - ) - - stage = client.get_stage(restApiId=api_id, stageName=stage_name) - - stage.should.have.key("cacheClusterSize").which.should.equal("0.5") - - client.update_stage( - restApiId=api_id, - stageName=stage_name, - patchOperations=[ - {"op": "replace", "path": "/cacheClusterSize", "value": "1.6"} - ], - ) - - stage = client.get_stage(restApiId=api_id, stageName=stage_name) - - stage.should.have.key("cacheClusterSize").which.should.equal("1.6") - - client.update_stage( - restApiId=api_id, - stageName=stage_name, - patchOperations=[ - {"op": "replace", "path": "/deploymentId", "value": deployment_id}, - {"op": "replace", "path": "/variables/environment", "value": "dev"}, - {"op": "replace", "path": "/variables/region", "value": "eu-west-1"}, - {"op": "replace", "path": "/*/*/caching/dataEncrypted", "value": "True"}, - {"op": "replace", "path": "/cacheClusterEnabled", "value": "True"}, - { - "op": "replace", - "path": "/description", - "value": "stage description update", - }, - {"op": "replace", "path": "/cacheClusterSize", "value": "1.6"}, - ], - ) - - client.update_stage( - restApiId=api_id, - stageName=stage_name, - patchOperations=[ - {"op": "remove", "path": "/variables/region", "value": "eu-west-1"} - ], - ) - - stage = client.get_stage(restApiId=api_id, stageName=stage_name) - - stage["description"].should.match("stage description update") - stage["cacheClusterSize"].should.equal("1.6") - stage["variables"]["environment"].should.match("dev") - stage["variables"].should_not.have.key("region") - stage["cacheClusterEnabled"].should.be.true - stage["deploymentId"].should.match(deployment_id) - stage["methodSettings"].should.have.key("*/*") - stage["methodSettings"]["*/*"].should.have.key( - "cacheDataEncrypted" - ).which.should.be.true - - try: - client.update_stage( - restApiId=api_id, - stageName=stage_name, - patchOperations=[ - {"op": "add", "path": "/notasetting", "value": "eu-west-1"} - ], - ) - assert False.should.be.ok # Fail, should not be here - except Exception: - assert True.should.be.ok - - -@mock_apigateway -def test_non_existent_stage(): - client = boto3.client("apigateway", region_name="us-west-2") - response = client.create_rest_api(name="my_api", description="this is my api") - api_id = response["id"] - - client.get_stage.when.called_with(restApiId=api_id, stageName="xxx").should.throw( - ClientError - ) - - -@mock_apigateway -def test_create_stage(): - client = boto3.client("apigateway", region_name="us-west-2") - stage_name = "staging" - response = client.create_rest_api(name="my_api", description="this is my api") - api_id = response["id"] - - create_method_integration(client, api_id) - response = client.create_deployment(restApiId=api_id, stageName=stage_name) - deployment_id = response["id"] - - response = client.get_deployment(restApiId=api_id, deploymentId=deployment_id) - # createdDate is hard to match against, remove it - response.pop("createdDate", None) - # this is hard to match against, so remove it - response["ResponseMetadata"].pop("HTTPHeaders", None) - response["ResponseMetadata"].pop("RetryAttempts", None) - response.should.equal( - { - "id": deployment_id, - "ResponseMetadata": {"HTTPStatusCode": 200}, - "description": "", - } - ) - - response = client.create_deployment(restApiId=api_id, stageName=stage_name) - - deployment_id2 = response["id"] - - response = client.get_deployments(restApiId=api_id) - - # this is hard to match against, so remove it - response["ResponseMetadata"].pop("HTTPHeaders", None) - response["ResponseMetadata"].pop("RetryAttempts", None) - - response["items"][0].pop("createdDate") - response["items"][1].pop("createdDate") - response["items"][0]["id"].should.match( - r"{0}|{1}".format(deployment_id2, deployment_id) - ) - response["items"][1]["id"].should.match( - r"{0}|{1}".format(deployment_id2, deployment_id) - ) - - new_stage_name = "current" - response = client.create_stage( - restApiId=api_id, stageName=new_stage_name, deploymentId=deployment_id2 - ) - - # this is hard to match against, so remove it - response["ResponseMetadata"].pop("HTTPHeaders", None) - response["ResponseMetadata"].pop("RetryAttempts", None) - - response.should.equal( - { - "stageName": new_stage_name, - "deploymentId": deployment_id2, - "methodSettings": {}, - "variables": {}, - "ResponseMetadata": {"HTTPStatusCode": 200}, - "description": "", - "cacheClusterEnabled": False, - } - ) - - stage = client.get_stage(restApiId=api_id, stageName=new_stage_name) - stage["stageName"].should.equal(new_stage_name) - stage["deploymentId"].should.equal(deployment_id2) - - new_stage_name_with_vars = "stage_with_vars" - response = client.create_stage( - restApiId=api_id, - stageName=new_stage_name_with_vars, - deploymentId=deployment_id2, - variables={"env": "dev"}, - ) - - # this is hard to match against, so remove it - response["ResponseMetadata"].pop("HTTPHeaders", None) - response["ResponseMetadata"].pop("RetryAttempts", None) - - response.should.equal( - { - "stageName": new_stage_name_with_vars, - "deploymentId": deployment_id2, - "methodSettings": {}, - "variables": {"env": "dev"}, - "ResponseMetadata": {"HTTPStatusCode": 200}, - "description": "", - "cacheClusterEnabled": False, - } - ) - - stage = client.get_stage(restApiId=api_id, stageName=new_stage_name_with_vars) - stage["stageName"].should.equal(new_stage_name_with_vars) - stage["deploymentId"].should.equal(deployment_id2) - stage["variables"].should.have.key("env").which.should.match("dev") - - new_stage_name = "stage_with_vars_and_cache_settings" - response = client.create_stage( - restApiId=api_id, - stageName=new_stage_name, - deploymentId=deployment_id2, - variables={"env": "dev"}, - cacheClusterEnabled=True, - description="hello moto", - ) - - # this is hard to match against, so remove it - response["ResponseMetadata"].pop("HTTPHeaders", None) - response["ResponseMetadata"].pop("RetryAttempts", None) - - response.should.equal( - { - "stageName": new_stage_name, - "deploymentId": deployment_id2, - "methodSettings": {}, - "variables": {"env": "dev"}, - "ResponseMetadata": {"HTTPStatusCode": 200}, - "description": "hello moto", - "cacheClusterEnabled": True, - "cacheClusterSize": "0.5", - } - ) - - stage = client.get_stage(restApiId=api_id, stageName=new_stage_name) - - stage["cacheClusterSize"].should.equal("0.5") - - new_stage_name = "stage_with_vars_and_cache_settings_and_size" - response = client.create_stage( - restApiId=api_id, - stageName=new_stage_name, - deploymentId=deployment_id2, - variables={"env": "dev"}, - cacheClusterEnabled=True, - cacheClusterSize="1.6", - description="hello moto", - ) - - # this is hard to match against, so remove it - response["ResponseMetadata"].pop("HTTPHeaders", None) - response["ResponseMetadata"].pop("RetryAttempts", None) - - response.should.equal( - { - "stageName": new_stage_name, - "deploymentId": deployment_id2, - "methodSettings": {}, - "variables": {"env": "dev"}, - "ResponseMetadata": {"HTTPStatusCode": 200}, - "description": "hello moto", - "cacheClusterEnabled": True, - "cacheClusterSize": "1.6", - } - ) - - stage = client.get_stage(restApiId=api_id, stageName=new_stage_name) - stage["stageName"].should.equal(new_stage_name) - stage["deploymentId"].should.equal(deployment_id2) - stage["variables"].should.have.key("env").which.should.match("dev") - stage["cacheClusterSize"].should.equal("1.6") - - -@mock_apigateway -def test_create_deployment_requires_REST_methods(): - client = boto3.client("apigateway", region_name="us-west-2") - stage_name = "staging" - response = client.create_rest_api(name="my_api", description="this is my api") - api_id = response["id"] - - with pytest.raises(ClientError) as ex: - client.create_deployment(restApiId=api_id, stageName=stage_name)["id"] - ex.value.response["Error"]["Code"].should.equal("BadRequestException") - ex.value.response["Error"]["Message"].should.equal( - "The REST API doesn't contain any methods" - ) - - -@mock_apigateway -def test_create_deployment_requires_REST_method_integrations(): - client = boto3.client("apigateway", region_name="us-west-2") - stage_name = "staging" - response = client.create_rest_api(name="my_api", description="this is my api") - api_id = response["id"] - resources = client.get_resources(restApiId=api_id) - root_id = [resource for resource in resources["items"] if resource["path"] == "/"][ - 0 - ]["id"] - - client.put_method( - restApiId=api_id, resourceId=root_id, httpMethod="GET", authorizationType="NONE" - ) - - with pytest.raises(ClientError) as ex: - client.create_deployment(restApiId=api_id, stageName=stage_name)["id"] - ex.value.response["Error"]["Code"].should.equal("NotFoundException") - ex.value.response["Error"]["Message"].should.equal( - "No integration defined for method" - ) - - -@mock_apigateway -def test_create_simple_deployment_with_get_method(): - client = boto3.client("apigateway", region_name="us-west-2") - stage_name = "staging" - response = client.create_rest_api(name="my_api", description="this is my api") - api_id = response["id"] - create_method_integration(client, api_id) - deployment = client.create_deployment(restApiId=api_id, stageName=stage_name) - assert "id" in deployment - - -@mock_apigateway -def test_create_simple_deployment_with_post_method(): - client = boto3.client("apigateway", region_name="us-west-2") - stage_name = "staging" - response = client.create_rest_api(name="my_api", description="this is my api") - api_id = response["id"] - create_method_integration(client, api_id, httpMethod="POST") - deployment = client.create_deployment(restApiId=api_id, stageName=stage_name) - assert "id" in deployment - - @mock_apigateway def test_put_integration_response_with_response_template(): client = boto3.client("apigateway", region_name="us-west-2") @@ -1553,101 +1203,6 @@ def test_put_integration_validation(): ) -@mock_apigateway -def test_delete_stage(): - client = boto3.client("apigateway", region_name="us-west-2") - stage_name = "staging" - response = client.create_rest_api(name="my_api", description="this is my api") - api_id = response["id"] - create_method_integration(client, api_id) - deployment_id1 = client.create_deployment(restApiId=api_id, stageName=stage_name)[ - "id" - ] - deployment_id2 = client.create_deployment(restApiId=api_id, stageName=stage_name)[ - "id" - ] - - new_stage_name = "current" - client.create_stage( - restApiId=api_id, stageName=new_stage_name, deploymentId=deployment_id1 - ) - - new_stage_name_with_vars = "stage_with_vars" - client.create_stage( - restApiId=api_id, - stageName=new_stage_name_with_vars, - deploymentId=deployment_id2, - variables={"env": "dev"}, - ) - stages = client.get_stages(restApiId=api_id)["item"] - sorted([stage["stageName"] for stage in stages]).should.equal( - sorted([new_stage_name, new_stage_name_with_vars, stage_name]) - ) - # delete stage - response = client.delete_stage(restApiId=api_id, stageName=new_stage_name_with_vars) - response["ResponseMetadata"]["HTTPStatusCode"].should.equal(202) - # verify other stage still exists - stages = client.get_stages(restApiId=api_id)["item"] - sorted([stage["stageName"] for stage in stages]).should.equal( - sorted([new_stage_name, stage_name]) - ) - - -@mock_apigateway -def test_deployment(): - client = boto3.client("apigateway", region_name="us-west-2") - stage_name = "staging" - response = client.create_rest_api(name="my_api", description="this is my api") - api_id = response["id"] - create_method_integration(client, api_id) - - response = client.create_deployment(restApiId=api_id, stageName=stage_name) - deployment_id = response["id"] - - response = client.get_deployment(restApiId=api_id, deploymentId=deployment_id) - # createdDate is hard to match against, remove it - response.pop("createdDate", None) - # this is hard to match against, so remove it - response["ResponseMetadata"].pop("HTTPHeaders", None) - response["ResponseMetadata"].pop("RetryAttempts", None) - response.should.equal( - { - "id": deployment_id, - "ResponseMetadata": {"HTTPStatusCode": 200}, - "description": "", - } - ) - - response = client.get_deployments(restApiId=api_id) - - response["items"][0].pop("createdDate") - response["items"].should.equal([{"id": deployment_id, "description": ""}]) - - client.delete_deployment(restApiId=api_id, deploymentId=deployment_id) - - response = client.get_deployments(restApiId=api_id) - len(response["items"]).should.equal(0) - - # test deployment stages - - stage = client.get_stage(restApiId=api_id, stageName=stage_name) - stage["stageName"].should.equal(stage_name) - stage["deploymentId"].should.equal(deployment_id) - - client.update_stage( - restApiId=api_id, - stageName=stage_name, - patchOperations=[ - {"op": "replace", "path": "/description", "value": "_new_description_"} - ], - ) - - stage = client.get_stage(restApiId=api_id, stageName=stage_name) - stage["stageName"].should.equal(stage_name) - stage["deploymentId"].should.equal(deployment_id) - stage["description"].should.equal("_new_description_") - - @mock_apigateway def test_create_domain_names(): client = boto3.client("apigateway", region_name="us-west-2") @@ -1908,11 +1463,9 @@ def test_create_api_key(): response = client.get_api_keys() len(response["items"]).should.equal(1) - client.create_api_key.when.called_with(**payload).should.throw(ClientError) - @mock_apigateway -def test_create_api_headers(): +def test_create_api_key_twice(): region_name = "us-west-2" client = boto3.client("apigateway", region_name=region_name) @@ -1924,8 +1477,6 @@ def test_create_api_headers(): with pytest.raises(ClientError) as ex: client.create_api_key(**payload) ex.value.response["Error"]["Code"].should.equal("ConflictException") - if not settings.TEST_SERVER_MODE: - ex.value.response["ResponseMetadata"]["HTTPHeaders"].should.equal({}) @mock_apigateway @@ -2242,20 +1793,6 @@ def test_get_integration_response_unknown_response(): ex.value.response["Error"]["Code"].should.equal("NotFoundException") -@mock_apigateway -def test_delete_stage_unknown_stage(): - client = boto3.client("apigateway", region_name="us-west-2") - response = client.create_rest_api(name="my_api", description="this is my api") - api_id = response["id"] - with pytest.raises(ClientError) as ex: - client.delete_stage(restApiId=api_id, stageName="unknown") - - ex.value.response["Error"]["Message"].should.equal( - "Invalid stage identifier specified" - ) - ex.value.response["Error"]["Code"].should.equal("NotFoundException") - - @mock_apigateway def test_get_api_key_unknown_apikey(): client = boto3.client("apigateway", region_name="us-east-1") diff --git a/tests/test_apigateway/test_apigateway_deployments.py b/tests/test_apigateway/test_apigateway_deployments.py new file mode 100644 index 000000000..346f4f8cc --- /dev/null +++ b/tests/test_apigateway/test_apigateway_deployments.py @@ -0,0 +1,215 @@ +import boto3 +import sure # noqa # pylint: disable=unused-import +from botocore.exceptions import ClientError + +from moto import mock_apigateway +import pytest + +from .test_apigateway import create_method_integration + + +@mock_apigateway +def test_create_deployment_requires_REST_methods(): + client = boto3.client("apigateway", region_name="us-west-2") + stage_name = "staging" + response = client.create_rest_api(name="my_api", description="this is my api") + api_id = response["id"] + + with pytest.raises(ClientError) as ex: + client.create_deployment(restApiId=api_id, stageName=stage_name)["id"] + ex.value.response["Error"]["Code"].should.equal("BadRequestException") + ex.value.response["Error"]["Message"].should.equal( + "The REST API doesn't contain any methods" + ) + + +@mock_apigateway +def test_create_deployment_requires_REST_method_integrations(): + client = boto3.client("apigateway", region_name="us-west-2") + stage_name = "staging" + response = client.create_rest_api(name="my_api", description="this is my api") + api_id = response["id"] + resources = client.get_resources(restApiId=api_id) + root_id = [r for r in resources["items"] if r["path"] == "/"][0]["id"] + + client.put_method( + restApiId=api_id, resourceId=root_id, httpMethod="GET", authorizationType="NONE" + ) + + with pytest.raises(ClientError) as ex: + client.create_deployment(restApiId=api_id, stageName=stage_name)["id"] + ex.value.response["Error"]["Code"].should.equal("NotFoundException") + ex.value.response["Error"]["Message"].should.equal( + "No integration defined for method" + ) + + +@mock_apigateway +def test_create_simple_deployment_with_get_method(): + client = boto3.client("apigateway", region_name="us-west-2") + stage_name = "staging" + response = client.create_rest_api(name="my_api", description="this is my api") + api_id = response["id"] + create_method_integration(client, api_id) + + deployment = client.create_deployment(restApiId=api_id, stageName=stage_name) + deployment.should.have.key("id") + + +@mock_apigateway +def test_create_simple_deployment_with_post_method(): + client = boto3.client("apigateway", region_name="us-west-2") + stage_name = "staging" + response = client.create_rest_api(name="my_api", description="this is my api") + api_id = response["id"] + create_method_integration(client, api_id, httpMethod="POST") + + deployment = client.create_deployment(restApiId=api_id, stageName=stage_name) + deployment.should.have.key("id") + + +@mock_apigateway +def test_create_deployment_minimal(): + client = boto3.client("apigateway", region_name="us-west-2") + stage_name = "staging" + response = client.create_rest_api(name="my_api", description="this is my api") + api_id = response["id"] + create_method_integration(client, api_id) + + response = client.create_deployment(restApiId=api_id, stageName=stage_name) + deployment_id = response["id"] + + response = client.get_deployment(restApiId=api_id, deploymentId=deployment_id) + response.should.have.key("id").equals(deployment_id) + response.should.have.key("ResponseMetadata").should.have.key( + "HTTPStatusCode" + ).equals(200) + + +@mock_apigateway +def test_create_deployment_with_empty_stage(): + client = boto3.client("apigateway", region_name="us-west-2") + response = client.create_rest_api(name="my_api", description="this is my api") + api_id = response["id"] + create_method_integration(client, api_id) + + response = client.create_deployment(restApiId=api_id, stageName="") + deployment_id = response["id"] + + response = client.get_deployment(restApiId=api_id, deploymentId=deployment_id) + response.should.have.key("id") + response.should.have.key("createdDate") + response.shouldnt.have.key("stageName") + + # This should not create an empty stage + stages = client.get_stages(restApiId=api_id)["item"] + stages.should.equal([]) + + +@mock_apigateway +def test_get_deployments(): + client = boto3.client("apigateway", region_name="us-west-2") + stage_name = "staging" + response = client.create_rest_api(name="my_api", description="this is my api") + api_id = response["id"] + create_method_integration(client, api_id) + + response = client.create_deployment(restApiId=api_id, stageName=stage_name) + deployment_id = response["id"] + + response = client.get_deployments(restApiId=api_id) + response.should.have.key("items").length_of(1) + + response["items"][0].pop("createdDate") + response["items"].should.equal([{"id": deployment_id}]) + + +@mock_apigateway +def test_create_multiple_deployments(): + client = boto3.client("apigateway", region_name="us-west-2") + stage_name = "staging" + response = client.create_rest_api(name="my_api", description="this is my api") + api_id = response["id"] + + create_method_integration(client, api_id) + response = client.create_deployment(restApiId=api_id, stageName=stage_name) + deployment_id = response["id"] + + response = client.get_deployment(restApiId=api_id, deploymentId=deployment_id) + + response = client.create_deployment(restApiId=api_id, stageName=stage_name) + + deployment_id2 = response["id"] + + response = client.get_deployments(restApiId=api_id) + + response["items"][0]["id"].should.match( + r"{0}|{1}".format(deployment_id2, deployment_id) + ) + response["items"][1]["id"].should.match( + r"{0}|{1}".format(deployment_id2, deployment_id) + ) + + +@mock_apigateway +def test_delete_deployment__requires_stage_to_be_deleted(): + client = boto3.client("apigateway", region_name="us-west-2") + stage_name = "staging" + response = client.create_rest_api(name="my_api", description="this is my api") + api_id = response["id"] + create_method_integration(client, api_id) + + response = client.create_deployment(restApiId=api_id, stageName=stage_name) + deployment_id = response["id"] + + # Can't delete deployment immediately + with pytest.raises(ClientError) as exc: + client.delete_deployment(restApiId=api_id, deploymentId=deployment_id) + err = exc.value.response["Error"] + err["Code"].should.equal("BadRequestException") + err["Message"].should.equal( + "Active stages pointing to this deployment must be moved or deleted" + ) + + # Deployment still exists + deployments = client.get_deployments(restApiId=api_id)["items"] + deployments.should.have.length_of(1) + + # Stage still exists + stages = client.get_stages(restApiId=api_id)["item"] + stages.should.have.length_of(1) + + # Delete stage first + resp = client.delete_stage(restApiId=api_id, stageName=stage_name) + resp["ResponseMetadata"].should.have.key("HTTPStatusCode").equals(202) + + # Deployment still exists + deployments = client.get_deployments(restApiId=api_id)["items"] + print(deployments) + deployments.should.have.length_of(1) + + # Now delete deployment + resp = client.delete_deployment(restApiId=api_id, deploymentId=deployment_id) + resp["ResponseMetadata"].should.have.key("HTTPStatusCode").equals(202) + + # Deployment is gone + deployments = client.get_deployments(restApiId=api_id)["items"] + deployments.should.have.length_of(0) + + # Stage is gone + stages = client.get_stages(restApiId=api_id)["item"] + stages.should.have.length_of(0) + + +@mock_apigateway +def test_delete_unknown_deployment(): + client = boto3.client("apigateway", region_name="us-west-2") + response = client.create_rest_api(name="my_api", description="this is my api") + api_id = response["id"] + create_method_integration(client, api_id) + + with pytest.raises(ClientError) as exc: + client.delete_deployment(restApiId=api_id, deploymentId="unknown") + err = exc.value.response["Error"] + err["Code"].should.equal("NotFoundException") + err["Message"].should.equal("Invalid Deployment identifier specified") diff --git a/tests/test_apigateway/test_apigateway_gatewayresponses.py b/tests/test_apigateway/test_apigateway_gatewayresponses.py new file mode 100644 index 000000000..6f2ed2ef2 --- /dev/null +++ b/tests/test_apigateway/test_apigateway_gatewayresponses.py @@ -0,0 +1,144 @@ +import boto3 +import pytest + +from botocore.exceptions import ClientError +from moto import mock_apigateway + + +@mock_apigateway +def test_put_gateway_response_minimal(): + client = boto3.client("apigateway", region_name="us-east-2") + api_id = client.create_rest_api(name="my_api", description="d")["id"] + + resp = client.put_gateway_response(restApiId=api_id, responseType="DEFAULT_4XX") + + resp.should.have.key("responseType").equals("DEFAULT_4XX") + resp.should.have.key("defaultResponse").equals(False) + + +@mock_apigateway +def test_put_gateway_response(): + client = boto3.client("apigateway", region_name="us-east-2") + api_id = client.create_rest_api(name="my_api", description="d")["id"] + + resp = client.put_gateway_response( + restApiId=api_id, + responseType="DEFAULT_4XX", + statusCode="401", + responseParameters={"gatewayresponse.header.Authorization": "'Basic'"}, + responseTemplates={ + "application/xml": "#set($inputRoot = $input.path('$'))\n{ }" + }, + ) + + resp.should.have.key("responseType").equals("DEFAULT_4XX") + resp.should.have.key("defaultResponse").equals(False) + resp.should.have.key("statusCode").equals("401") + resp.should.have.key("responseParameters").equals( + {"gatewayresponse.header.Authorization": "'Basic'"} + ) + resp.should.have.key("responseTemplates").equals( + {"application/xml": "#set($inputRoot = $input.path('$'))\n{ }"} + ) + + +@mock_apigateway +def test_get_gateway_response_minimal(): + client = boto3.client("apigateway", region_name="ap-southeast-1") + api_id = client.create_rest_api(name="my_api", description="d")["id"] + + client.put_gateway_response(restApiId=api_id, responseType="DEFAULT_4XX") + + resp = client.get_gateway_response(restApiId=api_id, responseType="DEFAULT_4XX") + + resp.should.have.key("responseType").equals("DEFAULT_4XX") + resp.should.have.key("defaultResponse").equals(False) + + +@mock_apigateway +def test_get_gateway_response(): + client = boto3.client("apigateway", region_name="us-east-2") + api_id = client.create_rest_api(name="my_api", description="d")["id"] + + client.put_gateway_response( + restApiId=api_id, + responseType="DEFAULT_4XX", + statusCode="401", + responseParameters={"gatewayresponse.header.Authorization": "'Basic'"}, + responseTemplates={ + "application/xml": "#set($inputRoot = $input.path('$'))\n{ }" + }, + ) + + resp = client.get_gateway_response(restApiId=api_id, responseType="DEFAULT_4XX") + + resp.should.have.key("responseType").equals("DEFAULT_4XX") + resp.should.have.key("defaultResponse").equals(False) + resp.should.have.key("statusCode").equals("401") + resp.should.have.key("responseParameters").equals( + {"gatewayresponse.header.Authorization": "'Basic'"} + ) + resp.should.have.key("responseTemplates").equals( + {"application/xml": "#set($inputRoot = $input.path('$'))\n{ }"} + ) + + +@mock_apigateway +def test_get_gateway_response_unknown(): + client = boto3.client("apigateway", region_name="us-east-2") + api_id = client.create_rest_api(name="my_api", description="d")["id"] + + with pytest.raises(ClientError) as exc: + client.get_gateway_response(restApiId=api_id, responseType="DEFAULT_4XX") + err = exc.value.response["Error"] + err["Code"].should.equal("NotFoundException") + + +@mock_apigateway +def test_get_gateway_responses_empty(): + client = boto3.client("apigateway", region_name="ap-southeast-1") + api_id = client.create_rest_api(name="my_api", description="d")["id"] + resp = client.get_gateway_responses(restApiId=api_id) + + resp.should.have.key("items").equals([]) + + +@mock_apigateway +def test_get_gateway_responses(): + client = boto3.client("apigateway", region_name="ap-southeast-1") + api_id = client.create_rest_api(name="my_api", description="d")["id"] + client.put_gateway_response(restApiId=api_id, responseType="DEFAULT_4XX") + client.put_gateway_response( + restApiId=api_id, responseType="DEFAULT_5XX", statusCode="503" + ) + + resp = client.get_gateway_responses(restApiId=api_id) + resp.should.have.key("items").length_of(2) + + resp["items"].should.contain( + {"responseType": "DEFAULT_4XX", "defaultResponse": False} + ) + resp["items"].should.contain( + {"responseType": "DEFAULT_5XX", "defaultResponse": False, "statusCode": "503"} + ) + + +@mock_apigateway +def test_delete_gateway_response(): + client = boto3.client("apigateway", region_name="ap-southeast-1") + api_id = client.create_rest_api(name="my_api", description="d")["id"] + client.put_gateway_response(restApiId=api_id, responseType="DEFAULT_4XX") + client.put_gateway_response( + restApiId=api_id, responseType="DEFAULT_5XX", statusCode="503" + ) + + resp = client.get_gateway_responses(restApiId=api_id) + resp.should.have.key("items").length_of(2) + + resp = client.delete_gateway_response(restApiId=api_id, responseType="DEFAULT_5XX") + + resp = client.get_gateway_responses(restApiId=api_id) + resp.should.have.key("items").length_of(1) + resp["items"].should.contain( + {"responseType": "DEFAULT_4XX", "defaultResponse": False} + ) diff --git a/tests/test_apigateway/test_apigateway_stage.py b/tests/test_apigateway/test_apigateway_stage.py new file mode 100644 index 000000000..51b93d62d --- /dev/null +++ b/tests/test_apigateway/test_apigateway_stage.py @@ -0,0 +1,509 @@ +import boto3 +import pytest +import sure # noqa # pylint: disable=unused-import + +from botocore.exceptions import ClientError +from moto import mock_apigateway + +from .test_apigateway import create_method_integration + + +@mock_apigateway +def test_create_stage_minimal(): + client = boto3.client("apigateway", region_name="us-west-2") + stage_name = "staging" + response = client.create_rest_api(name="my_api", description="this is my api") + api_id = response["id"] + + create_method_integration(client, api_id) + response = client.create_deployment(restApiId=api_id, stageName=stage_name) + deployment_id = response["id"] + + new_stage_name = "current" + response = client.create_stage( + restApiId=api_id, stageName=new_stage_name, deploymentId=deployment_id + ) + + response.should.have.key("stageName").equals(new_stage_name) + response.should.have.key("deploymentId").equals(deployment_id) + response.should.have.key("methodSettings").equals({}) + response.should.have.key("variables").equals({}) + response.should.have.key("ResponseMetadata").should.have.key( + "HTTPStatusCode" + ).equals(200) + response.should.have.key("description").equals("") + response.shouldnt.have.key("cacheClusterStatus") + response.should.have.key("cacheClusterEnabled").equals(False) + + stage = client.get_stage(restApiId=api_id, stageName=new_stage_name) + stage["stageName"].should.equal(new_stage_name) + stage["deploymentId"].should.equal(deployment_id) + + +@mock_apigateway +def test_create_stage_with_env_vars(): + client = boto3.client("apigateway", region_name="us-west-2") + stage_name = "staging" + response = client.create_rest_api(name="my_api", description="this is my api") + api_id = response["id"] + + create_method_integration(client, api_id) + response = client.create_deployment(restApiId=api_id, stageName=stage_name) + deployment_id = response["id"] + + new_stage_name_with_vars = "stage_with_vars" + response = client.create_stage( + restApiId=api_id, + stageName=new_stage_name_with_vars, + deploymentId=deployment_id, + variables={"env": "dev"}, + ) + + response.should.have.key("stageName").equals(new_stage_name_with_vars) + response.should.have.key("deploymentId").equals(deployment_id) + response.should.have.key("methodSettings").equals({}) + response.should.have.key("variables").equals({"env": "dev"}) + response.should.have.key("ResponseMetadata").should.have.key( + "HTTPStatusCode" + ).equals(200) + response.should.have.key("description").equals("") + response.shouldnt.have.key("cacheClusterStatus") + response.should.have.key("cacheClusterEnabled").equals(False) + + stage = client.get_stage(restApiId=api_id, stageName=new_stage_name_with_vars) + stage["stageName"].should.equal(new_stage_name_with_vars) + stage["deploymentId"].should.equal(deployment_id) + stage["variables"].should.have.key("env").which.should.match("dev") + + +@mock_apigateway +def test_create_stage_with_vars_and_cache(): + client = boto3.client("apigateway", region_name="us-west-2") + stage_name = "staging" + response = client.create_rest_api(name="my_api", description="this is my api") + api_id = response["id"] + + create_method_integration(client, api_id) + response = client.create_deployment(restApiId=api_id, stageName=stage_name) + deployment_id = response["id"] + + new_stage_name = "stage_with_vars_and_cache_settings" + response = client.create_stage( + restApiId=api_id, + stageName=new_stage_name, + deploymentId=deployment_id, + variables={"env": "dev"}, + cacheClusterEnabled=True, + description="hello moto", + ) + + response.should.have.key("stageName").equals(new_stage_name) + response.should.have.key("deploymentId").equals(deployment_id) + response.should.have.key("methodSettings").equals({}) + response.should.have.key("variables").equals({"env": "dev"}) + response.should.have.key("ResponseMetadata").should.have.key( + "HTTPStatusCode" + ).equals(200) + response.should.have.key("description").equals("hello moto") + response.should.have.key("cacheClusterStatus").equals("AVAILABLE") + response.should.have.key("cacheClusterEnabled").equals(True) + response.should.have.key("cacheClusterSize").equals("0.5") + + stage = client.get_stage(restApiId=api_id, stageName=new_stage_name) + + stage["cacheClusterSize"].should.equal("0.5") + + +@mock_apigateway +def test_create_stage_with_cache_settings(): + client = boto3.client("apigateway", region_name="us-west-2") + stage_name = "staging" + response = client.create_rest_api(name="my_api", description="this is my api") + api_id = response["id"] + + create_method_integration(client, api_id) + response = client.create_deployment(restApiId=api_id, stageName=stage_name) + deployment_id = response["id"] + + new_stage_name = "stage_with_vars_and_cache_settings_and_size" + response = client.create_stage( + restApiId=api_id, + stageName=new_stage_name, + deploymentId=deployment_id, + variables={"env": "dev"}, + cacheClusterEnabled=True, + cacheClusterSize="1.6", + tracingEnabled=True, + description="hello moto", + ) + + response.should.have.key("stageName").equals(new_stage_name) + response.should.have.key("deploymentId").equals(deployment_id) + response.should.have.key("methodSettings").equals({}) + response.should.have.key("variables").equals({"env": "dev"}) + response.should.have.key("ResponseMetadata").should.have.key( + "HTTPStatusCode" + ).equals(200) + response.should.have.key("description").equals("hello moto") + response.should.have.key("cacheClusterStatus").equals("AVAILABLE") + response.should.have.key("cacheClusterEnabled").equals(True) + response.should.have.key("cacheClusterSize").equals("1.6") + response.should.have.key("tracingEnabled").equals(True) + + stage = client.get_stage(restApiId=api_id, stageName=new_stage_name) + stage["stageName"].should.equal(new_stage_name) + stage["deploymentId"].should.equal(deployment_id) + stage["variables"].should.have.key("env").which.should.match("dev") + stage["cacheClusterSize"].should.equal("1.6") + + +@mock_apigateway +def test_recreate_stage_from_deployment(): + client = boto3.client("apigateway", region_name="us-west-2") + stage_name = "staging" + response = client.create_rest_api(name="my_api", description="this is my api") + api_id = response["id"] + create_method_integration(client, api_id) + depl_id1 = client.create_deployment(restApiId=api_id, stageName=stage_name)["id"] + + with pytest.raises(ClientError) as exc: + client.create_stage( + restApiId=api_id, stageName=stage_name, deploymentId=depl_id1 + ) + err = exc.value.response["Error"] + err["Code"].should.equal("ConflictException") + err["Message"].should.equal("Stage already exists") + + +@mock_apigateway +def test_create_stage_twice(): + client = boto3.client("apigateway", region_name="us-west-2") + stage_name = "staging" + response = client.create_rest_api(name="my_api", description="this is my api") + api_id = response["id"] + create_method_integration(client, api_id) + depl_id1 = client.create_deployment(restApiId=api_id, stageName=stage_name)["id"] + + new_stage_name = "current" + client.create_stage( + restApiId=api_id, stageName=new_stage_name, deploymentId=depl_id1 + ) + + with pytest.raises(ClientError) as exc: + client.create_stage( + restApiId=api_id, stageName=new_stage_name, deploymentId=depl_id1 + ) + err = exc.value.response["Error"] + err["Code"].should.equal("ConflictException") + err["Message"].should.equal("Stage already exists") + + +@mock_apigateway +def test_delete_stage(): + client = boto3.client("apigateway", region_name="us-west-2") + stage_name = "staging" + response = client.create_rest_api(name="my_api", description="this is my api") + api_id = response["id"] + create_method_integration(client, api_id) + depl_id1 = client.create_deployment(restApiId=api_id, stageName=stage_name)["id"] + depl_id2 = client.create_deployment(restApiId=api_id, stageName=stage_name)["id"] + + new_stage_name = "current" + client.create_stage( + restApiId=api_id, stageName=new_stage_name, deploymentId=depl_id1 + ) + + new_stage_name_with_vars = "stage_with_vars" + client.create_stage( + restApiId=api_id, + stageName=new_stage_name_with_vars, + deploymentId=depl_id2, + variables={"env": "dev"}, + ) + stages = client.get_stages(restApiId=api_id)["item"] + stage_names = [stage["stageName"] for stage in stages] + stage_names.should.have.length_of(3) + stage_names.should.contain(stage_name) + stage_names.should.contain(new_stage_name) + stage_names.should.contain(new_stage_name_with_vars) + + # delete stage + response = client.delete_stage(restApiId=api_id, stageName=new_stage_name_with_vars) + response["ResponseMetadata"]["HTTPStatusCode"].should.equal(202) + + # verify other stage still exists + stages = client.get_stages(restApiId=api_id)["item"] + stage_names = [stage["stageName"] for stage in stages] + stage_names.should.have.length_of(2) + stage_names.shouldnt.contain(new_stage_name_with_vars) + + +@mock_apigateway +def test_delete_stage_created_by_deployment(): + client = boto3.client("apigateway", region_name="us-west-2") + stage_name = "staging" + response = client.create_rest_api(name="my_api", description="this is my api") + api_id = response["id"] + create_method_integration(client, api_id) + depl_id1 = client.create_deployment(restApiId=api_id, stageName=stage_name)["id"] + + # Sanity check that the deployment exists + depls = client.get_deployments(restApiId=api_id)["items"] + depls.should.have.length_of(1) + set(depls[0].keys()).should.equal({"id", "createdDate"}) + + # Sanity check that the stage exists + stage = client.get_stages(restApiId=api_id)["item"][0] + stage.should.have.key("deploymentId").equals(depl_id1) + stage.should.have.key("stageName").equals(stage_name) + + # delete stage + response = client.delete_stage(restApiId=api_id, stageName=stage_name) + response["ResponseMetadata"]["HTTPStatusCode"].should.equal(202) + + # verify no stage exists + stages = client.get_stages(restApiId=api_id) + stages.should.have.key("item").equals([]) + + # verify deployment still exists, unchanged + depls = client.get_deployments(restApiId=api_id)["items"] + depls.should.have.length_of(1) + set(depls[0].keys()).should.equal({"id", "createdDate"}) + + +@mock_apigateway +def test_delete_stage_unknown_stage(): + client = boto3.client("apigateway", region_name="us-west-2") + response = client.create_rest_api(name="my_api", description="this is my api") + api_id = response["id"] + with pytest.raises(ClientError) as exc: + client.delete_stage(restApiId=api_id, stageName="unknown") + err = exc.value.response["Error"] + err["Message"].should.equal("Invalid stage identifier specified") + err["Code"].should.equal("NotFoundException") + + +@mock_apigateway +def test_update_stage_configuration(): + client = boto3.client("apigateway", region_name="us-west-2") + stage_name = "staging" + response = client.create_rest_api(name="my_api", description="this is my api") + api_id = response["id"] + create_method_integration(client, api_id) + + response = client.create_deployment( + restApiId=api_id, stageName=stage_name, description="1.0.1" + ) + deployment_id = response["id"] + + response = client.get_deployment(restApiId=api_id, deploymentId=deployment_id) + + response.should.have.key("id").equals(deployment_id) + response.should.have.key("ResponseMetadata").should.have.key( + "HTTPStatusCode" + ).equals(200) + response.should.have.key("description").equals("1.0.1") + + response = client.create_deployment( + restApiId=api_id, stageName=stage_name, description="1.0.2" + ) + deployment_id2 = response["id"] + + stage = client.get_stage(restApiId=api_id, stageName=stage_name) + stage["stageName"].should.equal(stage_name) + stage["deploymentId"].should.equal(deployment_id2) + stage.shouldnt.have.key("cacheClusterSize") + stage.shouldnt.have.key("cacheClusterStatus") + + client.update_stage( + restApiId=api_id, + stageName=stage_name, + patchOperations=[ + {"op": "replace", "path": "/cacheClusterEnabled", "value": "True"} + ], + ) + + stage = client.get_stage(restApiId=api_id, stageName=stage_name) + + stage.should.have.key("cacheClusterSize").which.should.equal("0.5") + stage.should.have.key("cacheClusterStatus").equals("AVAILABLE") + + client.update_stage( + restApiId=api_id, + stageName=stage_name, + patchOperations=[ + {"op": "replace", "path": "/cacheClusterSize", "value": "1.6"} + ], + ) + + stage = client.get_stage(restApiId=api_id, stageName=stage_name) + + stage.should.have.key("cacheClusterSize").which.should.equal("1.6") + + client.update_stage( + restApiId=api_id, + stageName=stage_name, + patchOperations=[ + {"op": "replace", "path": "/deploymentId", "value": deployment_id}, + {"op": "replace", "path": "/variables/environment", "value": "dev"}, + {"op": "replace", "path": "/variables/region", "value": "eu-west-1"}, + {"op": "replace", "path": "/*/*/caching/dataEncrypted", "value": "True"}, + {"op": "replace", "path": "/cacheClusterEnabled", "value": "True"}, + { + "op": "replace", + "path": "/description", + "value": "stage description update", + }, + {"op": "replace", "path": "/cacheClusterSize", "value": "1.6"}, + ], + ) + + client.update_stage( + restApiId=api_id, + stageName=stage_name, + patchOperations=[ + {"op": "remove", "path": "/variables/region", "value": "eu-west-1"} + ], + ) + + stage = client.get_stage(restApiId=api_id, stageName=stage_name) + + stage["description"].should.match("stage description update") + stage["cacheClusterSize"].should.equal("1.6") + stage["variables"]["environment"].should.match("dev") + stage["variables"].should_not.have.key("region") + stage["cacheClusterEnabled"].should.be.true + stage["deploymentId"].should.match(deployment_id) + stage["methodSettings"].should.have.key("*/*") + stage["methodSettings"]["*/*"].should.have.key( + "cacheDataEncrypted" + ).which.should.be.true + + +@mock_apigateway +def test_update_stage_add_access_log_settings(): + client = boto3.client("apigateway", region_name="us-west-2") + stage_name = "staging" + response = client.create_rest_api(name="my_api", description="this is my api") + api_id = response["id"] + create_method_integration(client, api_id) + + client.create_deployment( + restApiId=api_id, stageName=stage_name, description="1.0.1" + ) + + client.update_stage( + restApiId=api_id, + stageName=stage_name, + patchOperations=[ + { + "op": "replace", + "path": "/accessLogSettings/destinationArn", + "value": "arn:aws:logs:us-east-1:123456789012:log-group:foo-bar-x0hyv", + }, + { + "op": "replace", + "path": "/accessLogSettings/format", + "value": "$context.identity.sourceIp msg", + }, + ], + ) + + stage = client.get_stage(restApiId=api_id, stageName=stage_name) + stage.should.have.key("accessLogSettings").equals( + { + "format": "$context.identity.sourceIp msg", + "destinationArn": "arn:aws:logs:us-east-1:123456789012:log-group:foo-bar-x0hyv", + } + ) + + +@mock_apigateway +def test_update_stage_tracing_disabled(): + client = boto3.client("apigateway", region_name="us-west-2") + stage_name = "staging" + response = client.create_rest_api(name="my_api", description="this is my api") + api_id = response["id"] + create_method_integration(client, api_id) + response = client.create_deployment(restApiId=api_id, stageName=stage_name) + + client.update_stage( + restApiId=api_id, + stageName=stage_name, + patchOperations=[ + {"op": "replace", "path": "/tracingEnabled", "value": "false"} + ], + ) + + stage = client.get_stage(restApiId=api_id, stageName=stage_name) + stage.should.have.key("tracingEnabled").equals(False) + + client.update_stage( + restApiId=api_id, + stageName=stage_name, + patchOperations=[{"op": "replace", "path": "/tracingEnabled", "value": "true"}], + ) + + stage = client.get_stage(restApiId=api_id, stageName=stage_name) + stage.should.have.key("tracingEnabled").equals(True) + + +@mock_apigateway +def test_update_stage_remove_access_log_settings(): + client = boto3.client("apigateway", region_name="us-west-2") + stage_name = "staging" + response = client.create_rest_api(name="my_api", description="this is my api") + api_id = response["id"] + create_method_integration(client, api_id) + + client.create_deployment( + restApiId=api_id, stageName=stage_name, description="1.0.1" + ) + + client.update_stage( + restApiId=api_id, + stageName=stage_name, + patchOperations=[{"op": "remove", "path": "/accessLogSettings"}], + ) + + stage = client.get_stage(restApiId=api_id, stageName=stage_name) + stage.shouldnt.have.key("accessLogSettings") + + +@mock_apigateway +def test_update_stage_configuration_unknown_operation(): + client = boto3.client("apigateway", region_name="us-west-2") + stage_name = "staging" + response = client.create_rest_api(name="my_api", description="this is my api") + api_id = response["id"] + create_method_integration(client, api_id) + + client.create_deployment( + restApiId=api_id, stageName=stage_name, description="1.0.1" + ) + + with pytest.raises(ClientError) as exc: + client.update_stage( + restApiId=api_id, + stageName=stage_name, + patchOperations=[ + {"op": "unknown_op", "path": "/notasetting", "value": "eu-west-1"} + ], + ) + err = exc.value.response["Error"] + err["Code"].should.equal("ValidationException") + err["Message"].should.equal( + "Member must satisfy enum value set: [add, remove, move, test, replace, copy]" + ) + + +@mock_apigateway +def test_non_existent_stage(): + client = boto3.client("apigateway", region_name="us-west-2") + response = client.create_rest_api(name="my_api", description="this is my api") + api_id = response["id"] + + with pytest.raises(ClientError) as exc: + client.get_stage(restApiId=api_id, stageName="xxx") + err = exc.value.response["Error"] + err["Code"].should.equal("NotFoundException") diff --git a/tests/test_firehose/test_firehose.py b/tests/test_firehose/test_firehose.py index 6488f0aaa..61339df58 100644 --- a/tests/test_firehose/test_firehose.py +++ b/tests/test_firehose/test_firehose.py @@ -248,7 +248,7 @@ def test_describe_delivery_stream(): assert description["DeliveryStreamName"] == stream_name assert ( description["DeliveryStreamARN"] - == f"arn:aws:firehose:{TEST_REGION}:{ACCOUNT_ID}:/delivery_stream/{stream_name}" + == f"arn:aws:firehose:{TEST_REGION}:{ACCOUNT_ID}:deliverystream/{stream_name}" ) assert description["DeliveryStreamStatus"] == "ACTIVE" assert description["DeliveryStreamType"] == "KinesisStreamAsSource" @@ -281,7 +281,7 @@ def test_describe_delivery_stream(): assert description["DeliveryStreamName"] == stream_name assert ( description["DeliveryStreamARN"] - == f"arn:aws:firehose:{TEST_REGION}:{ACCOUNT_ID}:/delivery_stream/{stream_name}" + == f"arn:aws:firehose:{TEST_REGION}:{ACCOUNT_ID}:deliverystream/{stream_name}" ) assert description["DeliveryStreamStatus"] == "ACTIVE" assert description["DeliveryStreamType"] == "KinesisStreamAsSource"