#2521 - Implement API Gateway Stage deletion
This commit is contained in:
parent
67f9dd12da
commit
2d32ee18a6
@ -2,6 +2,89 @@ from __future__ import unicode_literals
|
||||
from moto.core.exceptions import RESTError
|
||||
|
||||
|
||||
class BadRequestException(RESTError):
|
||||
pass
|
||||
|
||||
|
||||
class AwsProxyNotAllowed(BadRequestException):
|
||||
def __init__(self):
|
||||
super(AwsProxyNotAllowed, self).__init__(
|
||||
"BadRequestException",
|
||||
"Integrations of type 'AWS_PROXY' currently only supports Lambda function and Firehose stream invocations.",
|
||||
)
|
||||
|
||||
|
||||
class CrossAccountNotAllowed(RESTError):
|
||||
def __init__(self):
|
||||
super(CrossAccountNotAllowed, self).__init__(
|
||||
"AccessDeniedException", "Cross-account pass role is not allowed."
|
||||
)
|
||||
|
||||
|
||||
class RoleNotSpecified(BadRequestException):
|
||||
def __init__(self):
|
||||
super(RoleNotSpecified, self).__init__(
|
||||
"BadRequestException", "Role ARN must be specified for AWS integrations"
|
||||
)
|
||||
|
||||
|
||||
class IntegrationMethodNotDefined(BadRequestException):
|
||||
def __init__(self):
|
||||
super(IntegrationMethodNotDefined, self).__init__(
|
||||
"BadRequestException", "Enumeration value for HttpMethod must be non-empty"
|
||||
)
|
||||
|
||||
|
||||
class InvalidResourcePathException(BadRequestException):
|
||||
def __init__(self):
|
||||
super(InvalidResourcePathException, self).__init__(
|
||||
"BadRequestException",
|
||||
"Resource's path part only allow a-zA-Z0-9._- and curly braces at the beginning and the end.",
|
||||
)
|
||||
|
||||
|
||||
class InvalidHttpEndpoint(BadRequestException):
|
||||
def __init__(self):
|
||||
super(InvalidHttpEndpoint, self).__init__(
|
||||
"BadRequestException", "Invalid HTTP endpoint specified for URI"
|
||||
)
|
||||
|
||||
|
||||
class InvalidArn(BadRequestException):
|
||||
def __init__(self):
|
||||
super(InvalidArn, self).__init__(
|
||||
"BadRequestException", "Invalid ARN specified in the request"
|
||||
)
|
||||
|
||||
|
||||
class InvalidIntegrationArn(BadRequestException):
|
||||
def __init__(self):
|
||||
super(InvalidIntegrationArn, self).__init__(
|
||||
"BadRequestException", "AWS ARN for integration must contain path or action"
|
||||
)
|
||||
|
||||
|
||||
class InvalidRequestInput(BadRequestException):
|
||||
def __init__(self):
|
||||
super(InvalidRequestInput, self).__init__(
|
||||
"BadRequestException", "Invalid request input"
|
||||
)
|
||||
|
||||
|
||||
class NoIntegrationDefined(BadRequestException):
|
||||
def __init__(self):
|
||||
super(NoIntegrationDefined, self).__init__(
|
||||
"BadRequestException", "No integration defined for method"
|
||||
)
|
||||
|
||||
|
||||
class NoMethodDefined(BadRequestException):
|
||||
def __init__(self):
|
||||
super(NoMethodDefined, self).__init__(
|
||||
"BadRequestException", "The REST API doesn't contain any methods"
|
||||
)
|
||||
|
||||
|
||||
class StageNotFoundException(RESTError):
|
||||
code = 404
|
||||
|
||||
|
@ -3,15 +3,32 @@ from __future__ import unicode_literals
|
||||
|
||||
import random
|
||||
import string
|
||||
import re
|
||||
import requests
|
||||
import time
|
||||
|
||||
from boto3.session import Session
|
||||
from urlparse import urlparse
|
||||
import responses
|
||||
from moto.core import BaseBackend, BaseModel
|
||||
from .utils import create_id
|
||||
from moto.core.utils import path_url
|
||||
from .exceptions import StageNotFoundException, ApiKeyNotFoundException
|
||||
from moto.sts.models import ACCOUNT_ID
|
||||
from .exceptions import (
|
||||
ApiKeyNotFoundException,
|
||||
AwsProxyNotAllowed,
|
||||
CrossAccountNotAllowed,
|
||||
IntegrationMethodNotDefined,
|
||||
InvalidArn,
|
||||
InvalidIntegrationArn,
|
||||
InvalidHttpEndpoint,
|
||||
InvalidResourcePathException,
|
||||
InvalidRequestInput,
|
||||
StageNotFoundException,
|
||||
RoleNotSpecified,
|
||||
NoIntegrationDefined,
|
||||
NoMethodDefined,
|
||||
)
|
||||
|
||||
STAGE_URL = "https://{api_id}.execute-api.{region_name}.amazonaws.com/{stage_name}"
|
||||
|
||||
@ -534,6 +551,8 @@ class APIGatewayBackend(BaseBackend):
|
||||
return resource
|
||||
|
||||
def create_resource(self, function_id, parent_resource_id, path_part):
|
||||
if not re.match("^\\{?[a-zA-Z0-9._-]+\\}?$", path_part):
|
||||
raise InvalidResourcePathException()
|
||||
api = self.get_rest_api(function_id)
|
||||
child = api.add_child(path=path_part, parent_id=parent_resource_id)
|
||||
return child
|
||||
@ -594,6 +613,10 @@ class APIGatewayBackend(BaseBackend):
|
||||
stage = api.stages[stage_name] = Stage()
|
||||
return stage.apply_operations(patch_operations)
|
||||
|
||||
def delete_stage(self, function_id, stage_name):
|
||||
api = self.get_rest_api(function_id)
|
||||
del api.stages[stage_name]
|
||||
|
||||
def get_method_response(self, function_id, resource_id, method_type, response_code):
|
||||
method = self.get_method(function_id, resource_id, method_type)
|
||||
method_response = method.get_response(response_code)
|
||||
@ -620,9 +643,40 @@ class APIGatewayBackend(BaseBackend):
|
||||
method_type,
|
||||
integration_type,
|
||||
uri,
|
||||
integration_method=None,
|
||||
credentials=None,
|
||||
request_templates=None,
|
||||
):
|
||||
resource = self.get_resource(function_id, resource_id)
|
||||
if credentials and not re.match(
|
||||
"^arn:aws:iam::" + str(ACCOUNT_ID), credentials
|
||||
):
|
||||
raise CrossAccountNotAllowed()
|
||||
if not integration_method and integration_type in [
|
||||
"HTTP",
|
||||
"HTTP_PROXY",
|
||||
"AWS",
|
||||
"AWS_PROXY",
|
||||
]:
|
||||
raise IntegrationMethodNotDefined()
|
||||
if integration_type in ["AWS_PROXY"] and re.match(
|
||||
"^arn:aws:apigateway:[a-zA-Z0-9-]+:s3", uri
|
||||
):
|
||||
raise AwsProxyNotAllowed()
|
||||
if (
|
||||
integration_type in ["AWS"]
|
||||
and re.match("^arn:aws:apigateway:[a-zA-Z0-9-]+:s3", uri)
|
||||
and not credentials
|
||||
):
|
||||
raise RoleNotSpecified()
|
||||
if integration_type in ["HTTP", "HTTP_PROXY"] and not self._uri_validator(uri):
|
||||
raise InvalidHttpEndpoint()
|
||||
if integration_type in ["AWS", "AWS_PROXY"] and not re.match("^arn:aws:", uri):
|
||||
raise InvalidArn()
|
||||
if integration_type in ["AWS", "AWS_PROXY"] and not re.match(
|
||||
"^arn:aws:apigateway:[a-zA-Z0-9-]+:[a-zA-Z0-9-]+:(path|action)/", uri
|
||||
):
|
||||
raise InvalidIntegrationArn()
|
||||
integration = resource.add_integration(
|
||||
method_type, integration_type, uri, request_templates=request_templates
|
||||
)
|
||||
@ -637,8 +691,16 @@ class APIGatewayBackend(BaseBackend):
|
||||
return resource.delete_integration(method_type)
|
||||
|
||||
def create_integration_response(
|
||||
self, function_id, resource_id, method_type, status_code, selection_pattern
|
||||
self,
|
||||
function_id,
|
||||
resource_id,
|
||||
method_type,
|
||||
status_code,
|
||||
selection_pattern,
|
||||
response_templates,
|
||||
):
|
||||
if response_templates is None:
|
||||
raise InvalidRequestInput()
|
||||
integration = self.get_integration(function_id, resource_id, method_type)
|
||||
integration_response = integration.create_integration_response(
|
||||
status_code, selection_pattern
|
||||
@ -665,6 +727,18 @@ class APIGatewayBackend(BaseBackend):
|
||||
if stage_variables is None:
|
||||
stage_variables = {}
|
||||
api = self.get_rest_api(function_id)
|
||||
methods = [
|
||||
res.resource_methods.values()[0] for res in self.list_resources(function_id)
|
||||
]
|
||||
if not any(methods):
|
||||
raise NoMethodDefined()
|
||||
method_integrations = [
|
||||
method["methodIntegration"]
|
||||
for method in methods
|
||||
if "methodIntegration" in method
|
||||
]
|
||||
if not all(method_integrations):
|
||||
raise NoIntegrationDefined()
|
||||
deployment = api.create_deployment(name, description, stage_variables)
|
||||
return deployment
|
||||
|
||||
@ -753,6 +827,13 @@ class APIGatewayBackend(BaseBackend):
|
||||
self.usage_plan_keys[usage_plan_id].pop(key_id)
|
||||
return {}
|
||||
|
||||
def _uri_validator(self, uri):
|
||||
try:
|
||||
result = urlparse(uri)
|
||||
return all([result.scheme, result.netloc, result.path])
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
apigateway_backends = {}
|
||||
for region_name in Session().get_available_regions("apigateway"):
|
||||
|
@ -4,12 +4,24 @@ import json
|
||||
|
||||
from moto.core.responses import BaseResponse
|
||||
from .models import apigateway_backends
|
||||
from .exceptions import StageNotFoundException, ApiKeyNotFoundException
|
||||
from .exceptions import (
|
||||
ApiKeyNotFoundException,
|
||||
BadRequestException,
|
||||
CrossAccountNotAllowed,
|
||||
StageNotFoundException,
|
||||
)
|
||||
|
||||
|
||||
class APIGatewayResponse(BaseResponse):
|
||||
def error(self, type_, message, status=400):
|
||||
return (
|
||||
status,
|
||||
self.response_headers,
|
||||
json.dumps({"__type": type_, "message": message}),
|
||||
)
|
||||
|
||||
def _get_param(self, key):
|
||||
return json.loads(self.body).get(key)
|
||||
return json.loads(self.body).get(key) if self.body else None
|
||||
|
||||
def _get_param_with_default_value(self, key, default):
|
||||
jsonbody = json.loads(self.body)
|
||||
@ -63,14 +75,21 @@ class APIGatewayResponse(BaseResponse):
|
||||
function_id = self.path.replace("/restapis/", "", 1).split("/")[0]
|
||||
resource_id = self.path.split("/")[-1]
|
||||
|
||||
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())
|
||||
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(
|
||||
"com.amazonaws.dynamodb.v20111205#BadRequestException", e.message
|
||||
)
|
||||
|
||||
def resource_methods(self, request, full_url, headers):
|
||||
self.setup_class(request, full_url, headers)
|
||||
@ -165,6 +184,9 @@ class APIGatewayResponse(BaseResponse):
|
||||
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)
|
||||
|
||||
def integrations(self, request, full_url, headers):
|
||||
@ -174,27 +196,40 @@ class APIGatewayResponse(BaseResponse):
|
||||
resource_id = url_path_parts[4]
|
||||
method_type = url_path_parts[6]
|
||||
|
||||
if self.method == "GET":
|
||||
integration_response = self.backend.get_integration(
|
||||
function_id, resource_id, method_type
|
||||
try:
|
||||
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")
|
||||
integration_http_method = self._get_param("httpMethod")
|
||||
creds = self._get_param("credentials")
|
||||
request_templates = self._get_param("requestTemplates")
|
||||
integration_response = self.backend.create_integration(
|
||||
function_id,
|
||||
resource_id,
|
||||
method_type,
|
||||
integration_type,
|
||||
uri,
|
||||
credentials=creds,
|
||||
integration_method=integration_http_method,
|
||||
request_templates=request_templates,
|
||||
)
|
||||
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(
|
||||
"com.amazonaws.dynamodb.v20111205#BadRequestException", e.message
|
||||
)
|
||||
elif self.method == "PUT":
|
||||
integration_type = self._get_param("type")
|
||||
uri = self._get_param("uri")
|
||||
request_templates = self._get_param("requestTemplates")
|
||||
integration_response = self.backend.create_integration(
|
||||
function_id,
|
||||
resource_id,
|
||||
method_type,
|
||||
integration_type,
|
||||
uri,
|
||||
request_templates=request_templates,
|
||||
except CrossAccountNotAllowed as e:
|
||||
return self.error(
|
||||
"com.amazonaws.dynamodb.v20111205#AccessDeniedException", e.message
|
||||
)
|
||||
elif self.method == "DELETE":
|
||||
integration_response = self.backend.delete_integration(
|
||||
function_id, resource_id, method_type
|
||||
)
|
||||
return 200, {}, json.dumps(integration_response)
|
||||
|
||||
def integration_responses(self, request, full_url, headers):
|
||||
self.setup_class(request, full_url, headers)
|
||||
@ -204,36 +239,52 @@ class APIGatewayResponse(BaseResponse):
|
||||
method_type = url_path_parts[6]
|
||||
status_code = url_path_parts[9]
|
||||
|
||||
if self.method == "GET":
|
||||
integration_response = self.backend.get_integration_response(
|
||||
function_id, resource_id, method_type, status_code
|
||||
try:
|
||||
if self.method == "GET":
|
||||
integration_response = self.backend.get_integration_response(
|
||||
function_id, resource_id, method_type, status_code
|
||||
)
|
||||
elif self.method == "PUT":
|
||||
selection_pattern = self._get_param("selectionPattern")
|
||||
response_templates = self._get_param("responseTemplates")
|
||||
integration_response = self.backend.create_integration_response(
|
||||
function_id,
|
||||
resource_id,
|
||||
method_type,
|
||||
status_code,
|
||||
selection_pattern,
|
||||
response_templates,
|
||||
)
|
||||
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(
|
||||
"com.amazonaws.dynamodb.v20111205#BadRequestException", e.message
|
||||
)
|
||||
elif self.method == "PUT":
|
||||
selection_pattern = self._get_param("selectionPattern")
|
||||
integration_response = self.backend.create_integration_response(
|
||||
function_id, resource_id, method_type, status_code, selection_pattern
|
||||
)
|
||||
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)
|
||||
|
||||
def deployments(self, request, full_url, headers):
|
||||
self.setup_class(request, full_url, headers)
|
||||
function_id = self.path.replace("/restapis/", "", 1).split("/")[0]
|
||||
|
||||
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_with_default_value("description", "")
|
||||
stage_variables = self._get_param_with_default_value("variables", {})
|
||||
deployment = self.backend.create_deployment(
|
||||
function_id, name, description, stage_variables
|
||||
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_with_default_value("description", "")
|
||||
stage_variables = self._get_param_with_default_value("variables", {})
|
||||
deployment = self.backend.create_deployment(
|
||||
function_id, name, description, stage_variables
|
||||
)
|
||||
return 200, {}, json.dumps(deployment)
|
||||
except BadRequestException as e:
|
||||
return self.error(
|
||||
"com.amazonaws.dynamodb.v20111205#BadRequestException", e.message
|
||||
)
|
||||
return 200, {}, json.dumps(deployment)
|
||||
|
||||
def individual_deployment(self, request, full_url, headers):
|
||||
self.setup_class(request, full_url, headers)
|
||||
|
@ -9,6 +9,7 @@ from botocore.exceptions import ClientError
|
||||
|
||||
import responses
|
||||
from moto import mock_apigateway, settings
|
||||
from nose.tools import assert_raises
|
||||
|
||||
|
||||
@freeze_time("2015-01-01")
|
||||
@ -45,6 +46,32 @@ def test_list_and_delete_apis():
|
||||
len(response["items"]).should.equal(1)
|
||||
|
||||
|
||||
@mock_apigateway
|
||||
def test_create_resource__validate_name():
|
||||
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"]
|
||||
|
||||
resources = client.get_resources(restApiId=api_id)
|
||||
root_id = [resource for resource in resources["items"] if resource["path"] == "/"][
|
||||
0
|
||||
]["id"]
|
||||
|
||||
invalid_names = ["/users", "users/", "users/{user_id}", "us{er"]
|
||||
valid_names = ["users", "{user_id}", "user_09", "good-dog"]
|
||||
# All invalid names should throw an exception
|
||||
for name in invalid_names:
|
||||
with assert_raises(ClientError) as ex:
|
||||
client.create_resource(restApiId=api_id, parentId=root_id, pathPart=name)
|
||||
ex.exception.response["Error"]["Code"].should.equal("BadRequestException")
|
||||
ex.exception.response["Error"]["Message"].should.equal(
|
||||
"Resource's path part only allow a-zA-Z0-9._- and curly braces at the beginning and the end."
|
||||
)
|
||||
# All valid names should go through
|
||||
for name in valid_names:
|
||||
client.create_resource(restApiId=api_id, parentId=root_id, pathPart=name)
|
||||
|
||||
|
||||
@mock_apigateway
|
||||
def test_create_resource():
|
||||
client = boto3.client("apigateway", region_name="us-west-2")
|
||||
@ -69,9 +96,7 @@ def test_create_resource():
|
||||
}
|
||||
)
|
||||
|
||||
response = client.create_resource(
|
||||
restApiId=api_id, parentId=root_id, pathPart="/users"
|
||||
)
|
||||
client.create_resource(restApiId=api_id, parentId=root_id, pathPart="users")
|
||||
|
||||
resources = client.get_resources(restApiId=api_id)["items"]
|
||||
len(resources).should.equal(2)
|
||||
@ -79,9 +104,7 @@ def test_create_resource():
|
||||
0
|
||||
]
|
||||
|
||||
response = client.delete_resource(
|
||||
restApiId=api_id, resourceId=non_root_resource["id"]
|
||||
)
|
||||
client.delete_resource(restApiId=api_id, resourceId=non_root_resource["id"])
|
||||
|
||||
len(client.get_resources(restApiId=api_id)["items"]).should.equal(1)
|
||||
|
||||
@ -223,6 +246,7 @@ def test_integrations():
|
||||
httpMethod="GET",
|
||||
type="HTTP",
|
||||
uri="http://httpbin.org/robots.txt",
|
||||
integrationHttpMethod="POST",
|
||||
)
|
||||
# this is hard to match against, so remove it
|
||||
response["ResponseMetadata"].pop("HTTPHeaders", None)
|
||||
@ -308,6 +332,7 @@ def test_integrations():
|
||||
type="HTTP",
|
||||
uri=test_uri,
|
||||
requestTemplates=templates,
|
||||
integrationHttpMethod="POST",
|
||||
)
|
||||
# this is hard to match against, so remove it
|
||||
response["ResponseMetadata"].pop("HTTPHeaders", None)
|
||||
@ -340,12 +365,13 @@ def test_integration_response():
|
||||
restApiId=api_id, resourceId=root_id, httpMethod="GET", statusCode="200"
|
||||
)
|
||||
|
||||
response = client.put_integration(
|
||||
client.put_integration(
|
||||
restApiId=api_id,
|
||||
resourceId=root_id,
|
||||
httpMethod="GET",
|
||||
type="HTTP",
|
||||
uri="http://httpbin.org/robots.txt",
|
||||
integrationHttpMethod="POST",
|
||||
)
|
||||
|
||||
response = client.put_integration_response(
|
||||
@ -354,6 +380,7 @@ def test_integration_response():
|
||||
httpMethod="GET",
|
||||
statusCode="200",
|
||||
selectionPattern="foobar",
|
||||
responseTemplates={},
|
||||
)
|
||||
# this is hard to match against, so remove it
|
||||
response["ResponseMetadata"].pop("HTTPHeaders", None)
|
||||
@ -410,6 +437,7 @@ def test_update_stage_configuration():
|
||||
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"
|
||||
@ -534,7 +562,8 @@ def test_create_stage():
|
||||
response = client.create_rest_api(name="my_api", description="this is my api")
|
||||
api_id = response["id"]
|
||||
|
||||
response = client.create_deployment(restApiId=api_id, stageName=stage_name)
|
||||
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)
|
||||
@ -690,12 +719,323 @@ def test_create_stage():
|
||||
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 assert_raises(ClientError) as ex:
|
||||
client.create_deployment(restApiId=api_id, stageName=stage_name)["id"]
|
||||
ex.exception.response["Error"]["Code"].should.equal("BadRequestException")
|
||||
ex.exception.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 assert_raises(ClientError) as ex:
|
||||
client.create_deployment(restApiId=api_id, stageName=stage_name)["id"]
|
||||
ex.exception.response["Error"]["Code"].should.equal("BadRequestException")
|
||||
ex.exception.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
|
||||
# https://github.com/aws/aws-sdk-js/issues/2588
|
||||
def test_put_integration_response_requires_responseTemplate():
|
||||
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"]
|
||||
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"
|
||||
)
|
||||
client.put_method_response(
|
||||
restApiId=api_id, resourceId=root_id, httpMethod="GET", statusCode="200"
|
||||
)
|
||||
client.put_integration(
|
||||
restApiId=api_id,
|
||||
resourceId=root_id,
|
||||
httpMethod="GET",
|
||||
type="HTTP",
|
||||
uri="http://httpbin.org/robots.txt",
|
||||
integrationHttpMethod="POST",
|
||||
)
|
||||
|
||||
with assert_raises(ClientError) as ex:
|
||||
client.put_integration_response(
|
||||
restApiId=api_id, resourceId=root_id, httpMethod="GET", statusCode="200"
|
||||
)
|
||||
ex.exception.response["Error"]["Code"].should.equal("BadRequestException")
|
||||
ex.exception.response["Error"]["Message"].should.equal("Invalid request input")
|
||||
# Works fine if responseTemplate is defined
|
||||
client.put_integration_response(
|
||||
restApiId=api_id,
|
||||
resourceId=resource["id"],
|
||||
httpMethod="GET",
|
||||
statusCode="200",
|
||||
responseTemplates={},
|
||||
)
|
||||
|
||||
|
||||
@mock_apigateway
|
||||
def test_put_integration_validation():
|
||||
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"]
|
||||
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"
|
||||
)
|
||||
client.put_method_response(
|
||||
restApiId=api_id, resourceId=root_id, httpMethod="GET", statusCode="200"
|
||||
)
|
||||
|
||||
http_types = ["HTTP", "HTTP_PROXY"]
|
||||
aws_types = ["AWS", "AWS_PROXY"]
|
||||
types_requiring_integration_method = http_types + aws_types
|
||||
types_not_requiring_integration_method = ["MOCK"]
|
||||
|
||||
for type in types_requiring_integration_method:
|
||||
# Ensure that integrations of these types fail if no integrationHttpMethod is provided
|
||||
with assert_raises(ClientError) as ex:
|
||||
client.put_integration(
|
||||
restApiId=api_id,
|
||||
resourceId=root_id,
|
||||
httpMethod="GET",
|
||||
type=type,
|
||||
uri="http://httpbin.org/robots.txt",
|
||||
)
|
||||
ex.exception.response["Error"]["Code"].should.equal("BadRequestException")
|
||||
ex.exception.response["Error"]["Message"].should.equal(
|
||||
"Enumeration value for HttpMethod must be non-empty"
|
||||
)
|
||||
for type in types_not_requiring_integration_method:
|
||||
# Ensure that integrations of these types do not need the integrationHttpMethod
|
||||
client.put_integration(
|
||||
restApiId=api_id,
|
||||
resourceId=root_id,
|
||||
httpMethod="GET",
|
||||
type=type,
|
||||
uri="http://httpbin.org/robots.txt",
|
||||
)
|
||||
for type in http_types:
|
||||
# Ensure that it works fine when providing the integrationHttpMethod-argument
|
||||
client.put_integration(
|
||||
restApiId=api_id,
|
||||
resourceId=root_id,
|
||||
httpMethod="GET",
|
||||
type=type,
|
||||
uri="http://httpbin.org/robots.txt",
|
||||
integrationHttpMethod="POST",
|
||||
)
|
||||
for type in ["AWS"]:
|
||||
# Ensure that it works fine when providing the integrationHttpMethod + credentials
|
||||
client.put_integration(
|
||||
restApiId=api_id,
|
||||
resourceId=root_id,
|
||||
credentials="arn:aws:iam::123456789012:role/service-role/testfunction-role-oe783psq",
|
||||
httpMethod="GET",
|
||||
type=type,
|
||||
uri="arn:aws:apigateway:us-west-2:s3:path/b/k",
|
||||
integrationHttpMethod="POST",
|
||||
)
|
||||
for type in aws_types:
|
||||
# Ensure that credentials are not required when URI points to a Lambda stream
|
||||
client.put_integration(
|
||||
restApiId=api_id,
|
||||
resourceId=root_id,
|
||||
httpMethod="GET",
|
||||
type=type,
|
||||
uri="arn:aws:apigateway:eu-west-1:lambda:path/2015-03-31/functions/arn:aws:lambda:eu-west-1:012345678901:function:MyLambda/invocations",
|
||||
integrationHttpMethod="POST",
|
||||
)
|
||||
for type in ["AWS_PROXY"]:
|
||||
# Ensure that aws_proxy does not support S3
|
||||
with assert_raises(ClientError) as ex:
|
||||
client.put_integration(
|
||||
restApiId=api_id,
|
||||
resourceId=root_id,
|
||||
credentials="arn:aws:iam::123456789012:role/service-role/testfunction-role-oe783psq",
|
||||
httpMethod="GET",
|
||||
type=type,
|
||||
uri="arn:aws:apigateway:us-west-2:s3:path/b/k",
|
||||
integrationHttpMethod="POST",
|
||||
)
|
||||
ex.exception.response["Error"]["Code"].should.equal("BadRequestException")
|
||||
ex.exception.response["Error"]["Message"].should.equal(
|
||||
"Integrations of type 'AWS_PROXY' currently only supports Lambda function and Firehose stream invocations."
|
||||
)
|
||||
for type in aws_types:
|
||||
# Ensure that the Role ARN is for the current account
|
||||
with assert_raises(ClientError) as ex:
|
||||
client.put_integration(
|
||||
restApiId=api_id,
|
||||
resourceId=root_id,
|
||||
credentials="arn:aws:iam::000000000000:role/service-role/testrole",
|
||||
httpMethod="GET",
|
||||
type=type,
|
||||
uri="arn:aws:apigateway:us-west-2:s3:path/b/k",
|
||||
integrationHttpMethod="POST",
|
||||
)
|
||||
ex.exception.response["Error"]["Code"].should.equal("AccessDeniedException")
|
||||
ex.exception.response["Error"]["Message"].should.equal(
|
||||
"Cross-account pass role is not allowed."
|
||||
)
|
||||
for type in ["AWS"]:
|
||||
# Ensure that the Role ARN is specified for aws integrations
|
||||
with assert_raises(ClientError) as ex:
|
||||
client.put_integration(
|
||||
restApiId=api_id,
|
||||
resourceId=root_id,
|
||||
httpMethod="GET",
|
||||
type=type,
|
||||
uri="arn:aws:apigateway:us-west-2:s3:path/b/k",
|
||||
integrationHttpMethod="POST",
|
||||
)
|
||||
ex.exception.response["Error"]["Code"].should.equal("BadRequestException")
|
||||
ex.exception.response["Error"]["Message"].should.equal(
|
||||
"Role ARN must be specified for AWS integrations"
|
||||
)
|
||||
for type in http_types:
|
||||
# Ensure that the URI is valid HTTP
|
||||
with assert_raises(ClientError) as ex:
|
||||
client.put_integration(
|
||||
restApiId=api_id,
|
||||
resourceId=root_id,
|
||||
httpMethod="GET",
|
||||
type=type,
|
||||
uri="non-valid-http",
|
||||
integrationHttpMethod="POST",
|
||||
)
|
||||
ex.exception.response["Error"]["Code"].should.equal("BadRequestException")
|
||||
ex.exception.response["Error"]["Message"].should.equal(
|
||||
"Invalid HTTP endpoint specified for URI"
|
||||
)
|
||||
for type in aws_types:
|
||||
# Ensure that the URI is an ARN
|
||||
with assert_raises(ClientError) as ex:
|
||||
client.put_integration(
|
||||
restApiId=api_id,
|
||||
resourceId=root_id,
|
||||
httpMethod="GET",
|
||||
type=type,
|
||||
uri="non-valid-arn",
|
||||
integrationHttpMethod="POST",
|
||||
)
|
||||
ex.exception.response["Error"]["Code"].should.equal("BadRequestException")
|
||||
ex.exception.response["Error"]["Message"].should.equal(
|
||||
"Invalid ARN specified in the request"
|
||||
)
|
||||
for type in aws_types:
|
||||
# Ensure that the URI is a valid ARN
|
||||
with assert_raises(ClientError) as ex:
|
||||
client.put_integration(
|
||||
restApiId=api_id,
|
||||
resourceId=root_id,
|
||||
httpMethod="GET",
|
||||
type=type,
|
||||
uri="arn:aws:iam::0000000000:role/service-role/asdf",
|
||||
integrationHttpMethod="POST",
|
||||
)
|
||||
ex.exception.response["Error"]["Code"].should.equal("BadRequestException")
|
||||
ex.exception.response["Error"]["Message"].should.equal(
|
||||
"AWS ARN for integration must contain path or action"
|
||||
)
|
||||
|
||||
|
||||
@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"]
|
||||
[stage["stageName"] for stage in stages].should.equal(
|
||||
[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"]
|
||||
[stage["stageName"] for stage in stages].should.equal([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"]
|
||||
@ -719,7 +1059,7 @@ def test_deployment():
|
||||
response["items"][0].pop("createdDate")
|
||||
response["items"].should.equal([{"id": deployment_id, "description": ""}])
|
||||
|
||||
response = client.delete_deployment(restApiId=api_id, deploymentId=deployment_id)
|
||||
client.delete_deployment(restApiId=api_id, deploymentId=deployment_id)
|
||||
|
||||
response = client.get_deployments(restApiId=api_id)
|
||||
len(response["items"]).should.equal(0)
|
||||
@ -730,7 +1070,7 @@ def test_deployment():
|
||||
stage["stageName"].should.equal(stage_name)
|
||||
stage["deploymentId"].should.equal(deployment_id)
|
||||
|
||||
stage = client.update_stage(
|
||||
client.update_stage(
|
||||
restApiId=api_id,
|
||||
stageName=stage_name,
|
||||
patchOperations=[
|
||||
@ -774,6 +1114,7 @@ def test_http_proxying_integration():
|
||||
httpMethod="GET",
|
||||
type="HTTP",
|
||||
uri="http://httpbin.org/robots.txt",
|
||||
integrationHttpMethod="POST",
|
||||
)
|
||||
|
||||
stage_name = "staging"
|
||||
@ -888,7 +1229,6 @@ def test_usage_plans():
|
||||
@mock_apigateway
|
||||
def test_usage_plan_keys():
|
||||
region_name = "us-west-2"
|
||||
usage_plan_id = "test_usage_plan_id"
|
||||
client = boto3.client("apigateway", region_name=region_name)
|
||||
usage_plan_id = "test"
|
||||
|
||||
@ -932,7 +1272,6 @@ def test_usage_plan_keys():
|
||||
@mock_apigateway
|
||||
def test_create_usage_plan_key_non_existent_api_key():
|
||||
region_name = "us-west-2"
|
||||
usage_plan_id = "test_usage_plan_id"
|
||||
client = boto3.client("apigateway", region_name=region_name)
|
||||
usage_plan_id = "test"
|
||||
|
||||
@ -976,3 +1315,34 @@ def test_get_usage_plans_using_key_id():
|
||||
len(only_plans_with_key["items"]).should.equal(1)
|
||||
only_plans_with_key["items"][0]["name"].should.equal(attached_plan["name"])
|
||||
only_plans_with_key["items"][0]["id"].should.equal(attached_plan["id"])
|
||||
|
||||
|
||||
def create_method_integration(client, api_id, httpMethod="GET"):
|
||||
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=httpMethod,
|
||||
authorizationType="NONE",
|
||||
)
|
||||
client.put_method_response(
|
||||
restApiId=api_id, resourceId=root_id, httpMethod=httpMethod, statusCode="200"
|
||||
)
|
||||
client.put_integration(
|
||||
restApiId=api_id,
|
||||
resourceId=root_id,
|
||||
httpMethod=httpMethod,
|
||||
type="HTTP",
|
||||
uri="http://httpbin.org/robots.txt",
|
||||
integrationHttpMethod="POST",
|
||||
)
|
||||
client.put_integration_response(
|
||||
restApiId=api_id,
|
||||
resourceId=root_id,
|
||||
httpMethod=httpMethod,
|
||||
statusCode="200",
|
||||
responseTemplates={},
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user