From 45f92fb4c73a95451ed71fd2754190e6de6b47ab Mon Sep 17 00:00:00 2001 From: Steve Pulec Date: Tue, 1 Mar 2016 12:03:59 -0500 Subject: [PATCH 1/8] base rest api endpoints. --- moto/__init__.py | 1 + moto/apigateway/__init__.py | 12 +++++ moto/apigateway/models.py | 55 ++++++++++++++++++++++ moto/apigateway/responses.py | 40 ++++++++++++++++ moto/apigateway/urls.py | 11 +++++ moto/apigateway/utils.py | 9 ++++ moto/backends.py | 2 + moto/core/responses.py | 6 ++- tests/test_apigateway/test_apigateway.py | 58 ++++++++++++++++++++++++ tests/test_apigateway/test_server.py | 16 +++++++ 10 files changed, 208 insertions(+), 2 deletions(-) create mode 100644 moto/apigateway/__init__.py create mode 100644 moto/apigateway/models.py create mode 100644 moto/apigateway/responses.py create mode 100644 moto/apigateway/urls.py create mode 100644 moto/apigateway/utils.py create mode 100644 tests/test_apigateway/test_apigateway.py create mode 100644 tests/test_apigateway/test_server.py diff --git a/moto/__init__.py b/moto/__init__.py index df283fc6..362218bf 100644 --- a/moto/__init__.py +++ b/moto/__init__.py @@ -5,6 +5,7 @@ logging.getLogger('boto').setLevel(logging.CRITICAL) __title__ = 'moto' __version__ = '0.4.22' +from .apigateway import mock_apigateway # flake8: noqa from .autoscaling import mock_autoscaling # flake8: noqa from .awslambda import mock_lambda # flake8: noqa from .cloudformation import mock_cloudformation # flake8: noqa diff --git a/moto/apigateway/__init__.py b/moto/apigateway/__init__.py new file mode 100644 index 00000000..47db4a70 --- /dev/null +++ b/moto/apigateway/__init__.py @@ -0,0 +1,12 @@ +from __future__ import unicode_literals +from .models import apigateway_backends +from ..core.models import MockAWS + +apigateway_backend = apigateway_backends['us-east-1'] + + +def mock_apigateway(func=None): + if func: + return MockAWS(apigateway_backends)(func) + else: + return MockAWS(apigateway_backends) diff --git a/moto/apigateway/models.py b/moto/apigateway/models.py new file mode 100644 index 00000000..3fc4aa2e --- /dev/null +++ b/moto/apigateway/models.py @@ -0,0 +1,55 @@ +from __future__ import unicode_literals + +import datetime +from moto.core import BaseBackend +from moto.core.utils import iso_8601_datetime_with_milliseconds +from .utils import create_rest_api_id + + +class RestAPI(object): + def __init__(self, id, name, description): + self.id = id + self.name = name + self.description = description + self.create_date = datetime.datetime.utcnow() + + def to_dict(self): + return { + "id": self.id, + "name": self.name, + "description": self.description, + "createdDate": iso_8601_datetime_with_milliseconds(self.create_date), + } + + +class APIGatewayBackend(BaseBackend): + def __init__(self, region_name): + super(APIGatewayBackend, self).__init__() + self.apis = {} + self.region_name = region_name + + def reset(self): + region_name = self.region_name + self.__dict__ = {} + self.__init__(region_name) + + def create_rest_api(self, name, description): + api_id = create_rest_api_id() + rest_api = RestAPI(api_id, name, description) + self.apis[api_id] = rest_api + return rest_api + + def get_rest_api(self, function_id): + rest_api = self.apis[function_id] + return rest_api + + def list_apis(self): + return self.apis.values() + + def delete_rest_api(self, function_id): + rest_api = self.apis.pop(function_id) + return rest_api + +apigateway_backends = {} +for region_name in ['us-east-1', 'us-west-2', 'eu-west-1', 'ap-northeast-1']: # Not available in boto yet + apigateway_backends[region_name] = APIGatewayBackend(region_name) diff --git a/moto/apigateway/responses.py b/moto/apigateway/responses.py new file mode 100644 index 00000000..018ca7c0 --- /dev/null +++ b/moto/apigateway/responses.py @@ -0,0 +1,40 @@ +from __future__ import unicode_literals + +import json + +from moto.core.responses import BaseResponse +from .models import apigateway_backends + + +class APIGatewayResponse(BaseResponse): + + def _get_param(self, key): + return json.loads(self.body).get(key) + + @property + def backend(self): + return apigateway_backends[self.region] + + def restapis(self, request, full_url, headers): + self.setup_class(request, full_url, headers) + + if self.method == 'GET': + apis = self.backend.list_apis() + return 200, headers, json.dumps({"item": [ + api.to_dict() for api in apis + ]}) + elif self.method == 'POST': + name = self._get_param('name') + description = self._get_param('description') + rest_api = self.backend.create_rest_api(name, description) + return 200, headers, json.dumps(rest_api.to_dict()) + + def restapis_individual(self, request, full_url, headers): + self.setup_class(request, full_url, headers) + function_id = self.path.split("/")[-1] + if self.method == 'GET': + rest_api = self.backend.get_rest_api(function_id) + return 200, headers, json.dumps(rest_api.to_dict()) + elif self.method == 'DELETE': + rest_api = self.backend.delete_rest_api(function_id) + return 200, headers, json.dumps(rest_api.to_dict()) diff --git a/moto/apigateway/urls.py b/moto/apigateway/urls.py new file mode 100644 index 00000000..363f352b --- /dev/null +++ b/moto/apigateway/urls.py @@ -0,0 +1,11 @@ +from __future__ import unicode_literals +from .responses import APIGatewayResponse + +url_bases = [ + "https?://apigateway.(.+).amazonaws.com" +] + +url_paths = { + '{0}/restapis': APIGatewayResponse().restapis, + '{0}/restapis/(?P[^/]+)/?$': APIGatewayResponse().restapis_individual, +} diff --git a/moto/apigateway/utils.py b/moto/apigateway/utils.py new file mode 100644 index 00000000..77212034 --- /dev/null +++ b/moto/apigateway/utils.py @@ -0,0 +1,9 @@ +from __future__ import unicode_literals +import six +import random + + +def create_rest_api_id(): + size = 10 + chars = list(range(10)) + ['A-Z'] + return ''.join(six.text_type(random.choice(chars)) for x in range(size)) diff --git a/moto/backends.py b/moto/backends.py index 7d4da577..b66af577 100644 --- a/moto/backends.py +++ b/moto/backends.py @@ -1,4 +1,5 @@ from __future__ import unicode_literals +from moto.apigateway import apigateway_backend from moto.autoscaling import autoscaling_backend from moto.awslambda import lambda_backend from moto.cloudwatch import cloudwatch_backend @@ -23,6 +24,7 @@ from moto.sts import sts_backend from moto.route53 import route53_backend BACKENDS = { + 'apigateway': apigateway_backend, 'autoscaling': autoscaling_backend, 'cloudformation': cloudformation_backend, 'cloudwatch': cloudwatch_backend, diff --git a/moto/core/responses.py b/moto/core/responses.py index 6595019f..20cbfec8 100644 --- a/moto/core/responses.py +++ b/moto/core/responses.py @@ -94,9 +94,8 @@ class BaseResponse(_TemplateEnvironmentMixin): def dispatch(cls, *args, **kwargs): return cls()._dispatch(*args, **kwargs) - def _dispatch(self, request, full_url, headers): + def setup_class(self, request, full_url, headers): querystring = {} - if hasattr(request, 'body'): # Boto self.body = request.body @@ -133,6 +132,9 @@ class BaseResponse(_TemplateEnvironmentMixin): self.headers = request.headers self.response_headers = headers + + def _dispatch(self, request, full_url, headers): + self.setup_class(request, full_url, headers) return self.call_action() def call_action(self): diff --git a/tests/test_apigateway/test_apigateway.py b/tests/test_apigateway/test_apigateway.py new file mode 100644 index 00000000..7a4cc25e --- /dev/null +++ b/tests/test_apigateway/test_apigateway.py @@ -0,0 +1,58 @@ +from __future__ import unicode_literals + +from datetime import datetime +from dateutil.tz import tzutc +import boto3 +from freezegun import freeze_time +import sure # noqa + +from moto import mock_apigateway + + +@freeze_time("2015-01-01") +@mock_apigateway +def test_create_and_get_rest_api(): + 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'] + + response = client.get_rest_api( + restApiId=api_id + ) + + response.pop('ResponseMetadata') + response.should.equal({ + 'id': api_id, + 'name': 'my_api', + 'description': 'this is my api', + 'createdDate': datetime(2015, 1, 1, tzinfo=tzutc()) + }) + + +@mock_apigateway +def test_list_and_delete_apis(): + 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.create_rest_api( + name='my_api2', + description='this is my api2', + ) + + response = client.get_rest_apis() + len(response['items']).should.equal(2) + + client.delete_rest_api( + restApiId=api_id + ) + + response = client.get_rest_apis() + len(response['items']).should.equal(1) diff --git a/tests/test_apigateway/test_server.py b/tests/test_apigateway/test_server.py new file mode 100644 index 00000000..f2a29e25 --- /dev/null +++ b/tests/test_apigateway/test_server.py @@ -0,0 +1,16 @@ +from __future__ import unicode_literals +import sure # noqa + +import moto.server as server + +''' +Test the different server responses +''' + + +def test_list_apis(): + backend = server.create_backend_app("apigateway") + test_client = backend.test_client() + + res = test_client.get('/restapis') + res.data.should.equal(b'{"item": []}') From a737fbed484b18b109bdda344717185c819c4167 Mon Sep 17 00:00:00 2001 From: Steve Pulec Date: Tue, 1 Mar 2016 18:50:06 -0500 Subject: [PATCH 2/8] create methods --- moto/apigateway/models.py | 119 +++++++++++++++++++- moto/apigateway/responses.py | 62 ++++++++++- moto/apigateway/urls.py | 6 +- moto/apigateway/utils.py | 2 +- tests/test_apigateway/test_apigateway.py | 131 +++++++++++++++++++++++ 5 files changed, 315 insertions(+), 5 deletions(-) diff --git a/moto/apigateway/models.py b/moto/apigateway/models.py index 3fc4aa2e..1146349f 100644 --- a/moto/apigateway/models.py +++ b/moto/apigateway/models.py @@ -3,7 +3,67 @@ from __future__ import unicode_literals import datetime from moto.core import BaseBackend from moto.core.utils import iso_8601_datetime_with_milliseconds -from .utils import create_rest_api_id +from .utils import create_id + + +class MethodResponse(dict): + def __init__(self, status_code): + super(MethodResponse, self).__init__() + self['statusCode'] = status_code + + +class Method(dict): + def __init__(self, method_type, authorization_type): + super(Method, self).__init__() + self.update(dict( + httpMethod=method_type, + authorizationType=authorization_type, + authorizerId=None, + apiKeyRequired=None, + requestParameters=None, + requestModels=None, + methodIntegration=None, + )) + self.method_responses = {} + + def create_response(self, response_code): + method_response = MethodResponse(response_code) + self.method_responses[response_code] = method_response + return method_response + + def get_response(self, response_code): + return self.method_responses[response_code] + + def delete_response(self, response_code): + return self.method_responses.pop(response_code) + + +class Resource(object): + def __init__(self, id, path_part, parent_id): + self.id = id + self.path_part = path_part + self.parent_id = parent_id + self.resource_methods = { + 'GET': {} + } + + def to_dict(self): + response = { + "path": self.path_part, + "id": self.id, + "resourceMethods": self.resource_methods, + } + if self.parent_id: + response['parent_id'] = self.parent_id + return response + + def add_method(self, method_type, authorization_type): + method = Method(method_type=method_type, authorization_type=authorization_type) + self.resource_methods[method_type] = method + return method + + def get_method(self, method_type): + return self.resource_methods[method_type] class RestAPI(object): @@ -13,6 +73,9 @@ class RestAPI(object): self.description = description self.create_date = datetime.datetime.utcnow() + self.resources = {} + self.add_child('/') # Add default child + def to_dict(self): return { "id": self.id, @@ -21,6 +84,12 @@ class RestAPI(object): "createdDate": iso_8601_datetime_with_milliseconds(self.create_date), } + def add_child(self, path, parent_id=None): + child_id = create_id() + child = Resource(id=child_id, path_part=path, parent_id=parent_id) + self.resources[child_id] = child + return child + class APIGatewayBackend(BaseBackend): def __init__(self, region_name): @@ -34,7 +103,7 @@ class APIGatewayBackend(BaseBackend): self.__init__(region_name) def create_rest_api(self, name, description): - api_id = create_rest_api_id() + api_id = create_id() rest_api = RestAPI(api_id, name, description) self.apis[api_id] = rest_api return rest_api @@ -50,6 +119,52 @@ class APIGatewayBackend(BaseBackend): rest_api = self.apis.pop(function_id) return rest_api + def list_resources(self, function_id): + api = self.get_rest_api(function_id) + return api.resources.values() + + def get_resource(self, function_id, resource_id): + api = self.get_rest_api(function_id) + resource = api.resources[resource_id] + return resource + + def create_resource(self, function_id, parent_resource_id, path_part): + api = self.get_rest_api(function_id) + child = api.add_child( + path=path_part, + parent_id=parent_resource_id, + ) + return child + + def delete_resource(self, function_id, resource_id): + api = self.get_rest_api(function_id) + resource = api.resources.pop(resource_id) + return resource + + def get_method(self, function_id, resource_id, method_type): + resource = self.get_resource(function_id, resource_id) + return resource.get_method(method_type) + + def create_method(self, function_id, resource_id, method_type, authorization_type): + resource = self.get_resource(function_id, resource_id) + method = resource.add_method(method_type, authorization_type) + return method + + 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) + return method_response + + def create_method_response(self, function_id, resource_id, method_type, response_code): + method = self.get_method(function_id, resource_id, method_type) + method_response = method.create_response(response_code) + return method_response + + def delete_method_response(self, function_id, resource_id, method_type, response_code): + method = self.get_method(function_id, resource_id, method_type) + method_response = method.delete_response(response_code) + return method_response + apigateway_backends = {} for region_name in ['us-east-1', 'us-west-2', 'eu-west-1', 'ap-northeast-1']: # Not available in boto yet apigateway_backends[region_name] = APIGatewayBackend(region_name) diff --git a/moto/apigateway/responses.py b/moto/apigateway/responses.py index 018ca7c0..c6e11710 100644 --- a/moto/apigateway/responses.py +++ b/moto/apigateway/responses.py @@ -31,10 +31,70 @@ class APIGatewayResponse(BaseResponse): def restapis_individual(self, request, full_url, headers): self.setup_class(request, full_url, headers) - function_id = self.path.split("/")[-1] + function_id = self.path.replace("/restapis/", "", 1).split("/")[0] + if self.method == 'GET': rest_api = self.backend.get_rest_api(function_id) return 200, headers, json.dumps(rest_api.to_dict()) elif self.method == 'DELETE': rest_api = self.backend.delete_rest_api(function_id) return 200, headers, json.dumps(rest_api.to_dict()) + + def resources(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': + resources = self.backend.list_resources(function_id) + return 200, headers, json.dumps({"item": [ + resource.to_dict() for resource in resources + ]}) + + 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] + + if self.method == 'GET': + resource = self.backend.get_resource(function_id, resource_id) + return 200, headers, json.dumps(resource.to_dict()) + elif self.method == 'POST': + path_part = self._get_param("pathPart") + resource = self.backend.create_resource(function_id, resource_id, path_part) + return 200, headers, json.dumps(resource.to_dict()) + elif self.method == 'DELETE': + resource = self.backend.delete_resource(function_id, resource_id) + return 200, headers, json.dumps(resource.to_dict()) + + def resource_methods(self, request, full_url, headers): + self.setup_class(request, full_url, headers) + url_path_parts = self.path.split("/") + function_id = url_path_parts[2] + resource_id = url_path_parts[4] + method_type = url_path_parts[6] + + if self.method == 'GET': + method = self.backend.get_method(function_id, resource_id, method_type) + return 200, headers, json.dumps(method) + elif self.method == 'PUT': + authorization_type = self._get_param("authorizationType") + method = self.backend.create_method(function_id, resource_id, method_type, authorization_type) + return 200, headers, json.dumps(method) + + def resource_method_responses(self, request, full_url, headers): + self.setup_class(request, full_url, headers) + url_path_parts = self.path.split("/") + function_id = url_path_parts[2] + resource_id = url_path_parts[4] + method_type = url_path_parts[6] + response_code = url_path_parts[8] + + if self.method == 'GET': + method_response = self.backend.get_method_response(function_id, resource_id, method_type, response_code) + return 200, headers, json.dumps(method_response) + elif self.method == 'PUT': + method_response = self.backend.create_method_response(function_id, resource_id, method_type, response_code) + return 200, headers, json.dumps(method_response) + elif self.method == 'DELETE': + method_response = self.backend.delete_method_response(function_id, resource_id, method_type, response_code) + return 200, headers, json.dumps(method_response) diff --git a/moto/apigateway/urls.py b/moto/apigateway/urls.py index 363f352b..d71a9e18 100644 --- a/moto/apigateway/urls.py +++ b/moto/apigateway/urls.py @@ -6,6 +6,10 @@ url_bases = [ ] url_paths = { - '{0}/restapis': APIGatewayResponse().restapis, + '{0}/restapis$': APIGatewayResponse().restapis, '{0}/restapis/(?P[^/]+)/?$': APIGatewayResponse().restapis_individual, + '{0}/restapis/(?P[^/]+)/resources$': APIGatewayResponse().resources, + '{0}/restapis/(?P[^/]+)/resources/(?P[^/]+)/?$': APIGatewayResponse().resource_individual, + '{0}/restapis/(?P[^/]+)/resources/(?P[^/]+)/methods/(?P[^/]+)/?$': APIGatewayResponse().resource_methods, + '{0}/restapis/(?P[^/]+)/resources/(?P[^/]+)/methods/(?P[^/]+)/responses/200$': APIGatewayResponse().resource_method_responses, } diff --git a/moto/apigateway/utils.py b/moto/apigateway/utils.py index 77212034..6d1e6ef1 100644 --- a/moto/apigateway/utils.py +++ b/moto/apigateway/utils.py @@ -3,7 +3,7 @@ import six import random -def create_rest_api_id(): +def create_id(): size = 10 chars = list(range(10)) + ['A-Z'] return ''.join(six.text_type(random.choice(chars)) for x in range(size)) diff --git a/tests/test_apigateway/test_apigateway.py b/tests/test_apigateway/test_apigateway.py index 7a4cc25e..3b4a31a8 100644 --- a/tests/test_apigateway/test_apigateway.py +++ b/tests/test_apigateway/test_apigateway.py @@ -56,3 +56,134 @@ def test_list_and_delete_apis(): response = client.get_rest_apis() len(response['items']).should.equal(1) + + +@mock_apigateway +def test_create_resource(): + 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'] + + root_resource = client.get_resource( + restApiId=api_id, + resourceId=root_id, + ) + root_resource.should.equal({ + 'path': '/', + 'id': root_id, + 'ResponseMetadata': {'HTTPStatusCode': 200}, + 'resourceMethods': { + 'GET': {} + } + }) + + response = client.create_resource( + restApiId=api_id, + parentId=root_id, + pathPart='/users', + ) + + resources = client.get_resources(restApiId=api_id)['items'] + len(resources).should.equal(2) + non_root_resource = [resource for resource in resources if resource['path'] != '/'][0] + + response = client.delete_resource( + restApiId=api_id, + resourceId=non_root_resource['id'] + ) + + len(client.get_resources(restApiId=api_id)['items']).should.equal(1) + + +@mock_apigateway +def test_create_method(): + 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', + ) + + response = client.get_method( + restApiId=api_id, + resourceId=root_id, + httpMethod='GET' + ) + + response.should.equal({ + 'httpMethod': 'GET', + 'authorizationType': 'none', + 'ResponseMetadata': {'HTTPStatusCode': 200} + }) + + +@mock_apigateway +def test_create_method_response(): + 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', + ) + + response = client.get_method( + restApiId=api_id, + resourceId=root_id, + httpMethod='GET' + ) + + response = client.put_method_response( + restApiId=api_id, + resourceId=root_id, + httpMethod='GET', + statusCode='200', + ) + response.should.equal({ + 'ResponseMetadata': {'HTTPStatusCode': 200}, + 'statusCode': '200' + }) + + response = client.get_method_response( + restApiId=api_id, + resourceId=root_id, + httpMethod='GET', + statusCode='200', + ) + response.should.equal({ + 'ResponseMetadata': {'HTTPStatusCode': 200}, + 'statusCode': '200' + }) + + response = client.delete_method_response( + restApiId=api_id, + resourceId=root_id, + httpMethod='GET', + statusCode='200', + ) + response.should.equal({'ResponseMetadata': {'HTTPStatusCode': 200}}) From aaaddf13e84baead8bd876bb0877c56eb7cf1c72 Mon Sep 17 00:00:00 2001 From: Steve Pulec Date: Wed, 2 Mar 2016 15:18:11 -0500 Subject: [PATCH 3/8] add integraiton test. --- tests/test_apigateway/test_apigateway.py | 41 ++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/tests/test_apigateway/test_apigateway.py b/tests/test_apigateway/test_apigateway.py index 3b4a31a8..5fae936e 100644 --- a/tests/test_apigateway/test_apigateway.py +++ b/tests/test_apigateway/test_apigateway.py @@ -187,3 +187,44 @@ def test_create_method_response(): statusCode='200', ) response.should.equal({'ResponseMetadata': {'HTTPStatusCode': 200}}) + + +@mock_apigateway +def test_integrations(): + 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.get_method( + restApiId=api_id, + resourceId=root_id, + httpMethod='GET' + ) + + client.put_method_response( + restApiId=api_id, + resourceId=root_id, + httpMethod='GET', + statusCode='200', + ) + + response = client.put_integration( + restApiId=api_id, + resourceId=root_id, + httpMethod='GET', + type='HTTP', + uri='http://httpbin.org/robots.txt', + ) From bd57233b10f8cca2b6e03586d5549974fac9a4f6 Mon Sep 17 00:00:00 2001 From: Steve Pulec Date: Fri, 4 Mar 2016 18:02:07 -0500 Subject: [PATCH 4/8] add integrations. --- moto/apigateway/models.py | 31 +++++++++++++++++ moto/apigateway/responses.py | 19 +++++++++++ moto/apigateway/urls.py | 1 + tests/test_apigateway/test_apigateway.py | 43 ++++++++++++++++++++---- 4 files changed, 88 insertions(+), 6 deletions(-) diff --git a/moto/apigateway/models.py b/moto/apigateway/models.py index 1146349f..a26b3c0f 100644 --- a/moto/apigateway/models.py +++ b/moto/apigateway/models.py @@ -6,6 +6,13 @@ from moto.core.utils import iso_8601_datetime_with_milliseconds from .utils import create_id +class Integration(dict): + def __init__(self, integration_type, uri): + super(Integration, self).__init__() + self['type'] = integration_type + self['uri'] = uri + + class MethodResponse(dict): def __init__(self, status_code): super(MethodResponse, self).__init__() @@ -65,6 +72,17 @@ class Resource(object): def get_method(self, method_type): return self.resource_methods[method_type] + def add_integration(self, method_type, integration_type, uri): + integration = Integration(integration_type, uri) + self.resource_methods[method_type]['methodIntegration'] = integration + return integration + + def get_integration(self, method_type): + return self.resource_methods[method_type]['methodIntegration'] + + def delete_integration(self, method_type): + return self.resource_methods[method_type].pop('methodIntegration') + class RestAPI(object): def __init__(self, id, name, description): @@ -165,6 +183,19 @@ class APIGatewayBackend(BaseBackend): method_response = method.delete_response(response_code) return method_response + def create_integration(self, function_id, resource_id, method_type, integration_type, uri): + resource = self.get_resource(function_id, resource_id) + integration = resource.add_integration(method_type, integration_type, uri) + return integration + + def get_integration(self, function_id, resource_id, method_type): + resource = self.get_resource(function_id, resource_id) + return resource.get_integration(method_type) + + def delete_integration(self, function_id, resource_id, method_type): + resource = self.get_resource(function_id, resource_id) + return resource.delete_integration(method_type) + apigateway_backends = {} for region_name in ['us-east-1', 'us-west-2', 'eu-west-1', 'ap-northeast-1']: # Not available in boto yet apigateway_backends[region_name] = APIGatewayBackend(region_name) diff --git a/moto/apigateway/responses.py b/moto/apigateway/responses.py index c6e11710..e11cfac9 100644 --- a/moto/apigateway/responses.py +++ b/moto/apigateway/responses.py @@ -98,3 +98,22 @@ class APIGatewayResponse(BaseResponse): elif self.method == 'DELETE': method_response = self.backend.delete_method_response(function_id, resource_id, method_type, response_code) return 200, headers, json.dumps(method_response) + + def integrations(self, request, full_url, headers): + self.setup_class(request, full_url, headers) + url_path_parts = self.path.split("/") + function_id = url_path_parts[2] + 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) + return 200, headers, json.dumps(integration_response) + elif self.method == 'PUT': + integration_type = self._get_param('type') + uri = self._get_param('uri') + integration_response = self.backend.create_integration(function_id, resource_id, method_type, integration_type, uri) + return 200, headers, json.dumps(integration_response) + elif self.method == 'DELETE': + integration_response = self.backend.delete_integration(function_id, resource_id, method_type) + return 200, headers, json.dumps(integration_response) diff --git a/moto/apigateway/urls.py b/moto/apigateway/urls.py index d71a9e18..a42c30cf 100644 --- a/moto/apigateway/urls.py +++ b/moto/apigateway/urls.py @@ -12,4 +12,5 @@ url_paths = { '{0}/restapis/(?P[^/]+)/resources/(?P[^/]+)/?$': APIGatewayResponse().resource_individual, '{0}/restapis/(?P[^/]+)/resources/(?P[^/]+)/methods/(?P[^/]+)/?$': APIGatewayResponse().resource_methods, '{0}/restapis/(?P[^/]+)/resources/(?P[^/]+)/methods/(?P[^/]+)/responses/200$': APIGatewayResponse().resource_method_responses, + '{0}/restapis/(?P[^/]+)/resources/(?P[^/]+)/methods/(?P[^/]+)/integration/?$': APIGatewayResponse().integrations, } diff --git a/tests/test_apigateway/test_apigateway.py b/tests/test_apigateway/test_apigateway.py index 5fae936e..cac502cc 100644 --- a/tests/test_apigateway/test_apigateway.py +++ b/tests/test_apigateway/test_apigateway.py @@ -208,12 +208,6 @@ def test_integrations(): authorizationType='none', ) - client.get_method( - restApiId=api_id, - resourceId=root_id, - httpMethod='GET' - ) - client.put_method_response( restApiId=api_id, resourceId=root_id, @@ -228,3 +222,40 @@ def test_integrations(): type='HTTP', uri='http://httpbin.org/robots.txt', ) + response.should.equal({ + 'type': 'HTTP', + 'uri': 'http://httpbin.org/robots.txt', + 'ResponseMetadata': {'HTTPStatusCode': 200} + }) + + response = client.get_integration( + restApiId=api_id, + resourceId=root_id, + httpMethod='GET' + ) + response.should.equal({ + 'type': 'HTTP', + 'uri': 'http://httpbin.org/robots.txt', + 'ResponseMetadata': {'HTTPStatusCode': 200} + }) + + response = client.get_resource( + restApiId=api_id, + resourceId=root_id, + ) + response['resourceMethods']['GET']['methodIntegration'].should.equal({ + 'type': 'HTTP', + 'uri': 'http://httpbin.org/robots.txt' + }) + + client.delete_integration( + restApiId=api_id, + resourceId=root_id, + httpMethod='GET' + ) + + response = client.get_resource( + restApiId=api_id, + resourceId=root_id, + ) + response['resourceMethods']['GET'].shouldnt.contain("methodIntegration") From e3ff8dc5100e5137727ec0edf6b85014925506ef Mon Sep 17 00:00:00 2001 From: Steve Pulec Date: Fri, 4 Mar 2016 18:35:03 -0500 Subject: [PATCH 5/8] Add deployments --- moto/apigateway/models.py | 41 +++++++++++++++++++++++ moto/apigateway/responses.py | 25 ++++++++++++++ moto/apigateway/urls.py | 2 ++ tests/test_apigateway/test_apigateway.py | 42 ++++++++++++++++++++++++ 4 files changed, 110 insertions(+) diff --git a/moto/apigateway/models.py b/moto/apigateway/models.py index a26b3c0f..e9f8e547 100644 --- a/moto/apigateway/models.py +++ b/moto/apigateway/models.py @@ -6,6 +6,13 @@ from moto.core.utils import iso_8601_datetime_with_milliseconds from .utils import create_id +class Deployment(dict): + def __init__(self, deployment_id, name): + super(Deployment, self).__init__() + self['id'] = deployment_id + self['stageName'] = name + + class Integration(dict): def __init__(self, integration_type, uri): super(Integration, self).__init__() @@ -91,6 +98,8 @@ class RestAPI(object): self.description = description self.create_date = datetime.datetime.utcnow() + self.deployments = {} + self.resources = {} self.add_child('/') # Add default child @@ -108,6 +117,21 @@ class RestAPI(object): self.resources[child_id] = child return child + def create_deployment(self, name): + deployment_id = create_id() + deployment = Deployment(deployment_id, name) + self.deployments[deployment_id] = deployment + return deployment + + def get_deployment(self, deployment_id): + return self.deployments[deployment_id] + + def get_deployments(self): + return self.deployments.values() + + def delete_deployment(self, deployment_id): + return self.deployments.pop(deployment_id) + class APIGatewayBackend(BaseBackend): def __init__(self, region_name): @@ -196,6 +220,23 @@ class APIGatewayBackend(BaseBackend): resource = self.get_resource(function_id, resource_id) return resource.delete_integration(method_type) + def create_deployment(self, function_id, name): + api = self.get_rest_api(function_id) + deployment = api.create_deployment(name) + return deployment + + def get_deployment(self, function_id, deployment_id): + api = self.get_rest_api(function_id) + return api.get_deployment(deployment_id) + + def get_deployments(self, function_id): + api = self.get_rest_api(function_id) + return api.get_deployments() + + def delete_deployment(self, function_id, deployment_id): + api = self.get_rest_api(function_id) + return api.delete_deployment(deployment_id) + apigateway_backends = {} for region_name in ['us-east-1', 'us-west-2', 'eu-west-1', 'ap-northeast-1']: # Not available in boto yet apigateway_backends[region_name] = APIGatewayBackend(region_name) diff --git a/moto/apigateway/responses.py b/moto/apigateway/responses.py index e11cfac9..cebd2c7d 100644 --- a/moto/apigateway/responses.py +++ b/moto/apigateway/responses.py @@ -117,3 +117,28 @@ class APIGatewayResponse(BaseResponse): elif self.method == 'DELETE': integration_response = self.backend.delete_integration(function_id, resource_id, method_type) return 200, headers, 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, headers, json.dumps({"item": deployments}) + elif self.method == 'POST': + name = self._get_param("stageName") + deployment = self.backend.create_deployment(function_id, name) + return 200, headers, json.dumps(deployment) + + 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] + + if self.method == 'GET': + deployment = self.backend.get_deployment(function_id, deployment_id) + return 200, headers, json.dumps(deployment) + elif self.method == 'DELETE': + deployment = self.backend.delete_deployment(function_id, deployment_id) + return 200, headers, json.dumps(deployment) diff --git a/moto/apigateway/urls.py b/moto/apigateway/urls.py index a42c30cf..731c0131 100644 --- a/moto/apigateway/urls.py +++ b/moto/apigateway/urls.py @@ -9,6 +9,8 @@ url_paths = { '{0}/restapis$': APIGatewayResponse().restapis, '{0}/restapis/(?P[^/]+)/?$': APIGatewayResponse().restapis_individual, '{0}/restapis/(?P[^/]+)/resources$': APIGatewayResponse().resources, + '{0}/restapis/(?P[^/]+)/deployments$': APIGatewayResponse().deployments, + '{0}/restapis/(?P[^/]+)/deployments/(?P[^/]+)/?$': APIGatewayResponse().individual_deployment, '{0}/restapis/(?P[^/]+)/resources/(?P[^/]+)/?$': APIGatewayResponse().resource_individual, '{0}/restapis/(?P[^/]+)/resources/(?P[^/]+)/methods/(?P[^/]+)/?$': APIGatewayResponse().resource_methods, '{0}/restapis/(?P[^/]+)/resources/(?P[^/]+)/methods/(?P[^/]+)/responses/200$': APIGatewayResponse().resource_method_responses, diff --git a/tests/test_apigateway/test_apigateway.py b/tests/test_apigateway/test_apigateway.py index cac502cc..e7ac1167 100644 --- a/tests/test_apigateway/test_apigateway.py +++ b/tests/test_apigateway/test_apigateway.py @@ -259,3 +259,45 @@ def test_integrations(): resourceId=root_id, ) response['resourceMethods']['GET'].shouldnt.contain("methodIntegration") + + +@mock_apigateway +def test_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'] + + response = client.create_deployment( + restApiId=api_id, + stageName='staging', + ) + deployment_id = response['id'] + + response = client.get_deployment( + restApiId=api_id, + deploymentId=deployment_id, + ) + response.should.equal({ + 'id': deployment_id, + 'ResponseMetadata': {'HTTPStatusCode': 200} + }) + + response = client.get_deployments( + restApiId=api_id, + ) + response['items'].should.equal([ + {'id': deployment_id} + ]) + + response = client.delete_deployment( + restApiId=api_id, + deploymentId=deployment_id, + ) + + response = client.get_deployments( + restApiId=api_id, + ) + len(response['items']).should.equal(0) From 95f9b3fb35018dfdf88d0d25fa8594ef265eea12 Mon Sep 17 00:00:00 2001 From: Steve Pulec Date: Sat, 5 Mar 2016 09:48:37 -0500 Subject: [PATCH 6/8] add integration responses. --- moto/apigateway/models.py | 42 +++++++- moto/apigateway/responses.py | 38 +++++-- moto/apigateway/urls.py | 1 + tests/test_apigateway/test_apigateway.py | 130 ++++++++++++++++++++++- 4 files changed, 194 insertions(+), 17 deletions(-) diff --git a/moto/apigateway/models.py b/moto/apigateway/models.py index e9f8e547..4b9b1fdb 100644 --- a/moto/apigateway/models.py +++ b/moto/apigateway/models.py @@ -13,11 +13,34 @@ class Deployment(dict): self['stageName'] = name +class IntegrationResponse(dict): + def __init__(self, status_code, selection_pattern=None): + self['responseTemplates'] = {"application/json": None} + self['statusCode'] = status_code + if selection_pattern: + self['selectionPattern'] = selection_pattern + + class Integration(dict): - def __init__(self, integration_type, uri): + def __init__(self, integration_type, uri, http_method): super(Integration, self).__init__() self['type'] = integration_type self['uri'] = uri + self['httpMethod'] = http_method + self["integrationResponses"] = { + "200": IntegrationResponse(200) + } + + def create_integration_response(self, status_code, selection_pattern): + integration_response = IntegrationResponse(status_code, selection_pattern) + self["integrationResponses"][status_code] = integration_response + return integration_response + + def get_integration_response(self, status_code): + return self["integrationResponses"][status_code] + + def delete_integration_response(self, status_code): + return self["integrationResponses"].pop(status_code) class MethodResponse(dict): @@ -80,7 +103,7 @@ class Resource(object): return self.resource_methods[method_type] def add_integration(self, method_type, integration_type, uri): - integration = Integration(integration_type, uri) + integration = Integration(integration_type, uri, method_type) self.resource_methods[method_type]['methodIntegration'] = integration return integration @@ -220,6 +243,21 @@ class APIGatewayBackend(BaseBackend): resource = self.get_resource(function_id, resource_id) return resource.delete_integration(method_type) + def create_integration_response(self, function_id, resource_id, method_type, status_code, selection_pattern): + integration = self.get_integration(function_id, resource_id, method_type) + integration_response = integration.create_integration_response(status_code, selection_pattern) + return integration_response + + def get_integration_response(self, function_id, resource_id, method_type, status_code): + integration = self.get_integration(function_id, resource_id, method_type) + integration_response = integration.get_integration_response(status_code) + return integration_response + + def delete_integration_response(self, function_id, resource_id, method_type, status_code): + integration = self.get_integration(function_id, resource_id, method_type) + integration_response = integration.delete_integration_response(status_code) + return integration_response + def create_deployment(self, function_id, name): api = self.get_rest_api(function_id) deployment = api.create_deployment(name) diff --git a/moto/apigateway/responses.py b/moto/apigateway/responses.py index cebd2c7d..03776c9f 100644 --- a/moto/apigateway/responses.py +++ b/moto/apigateway/responses.py @@ -57,14 +57,12 @@ class APIGatewayResponse(BaseResponse): if self.method == 'GET': resource = self.backend.get_resource(function_id, resource_id) - return 200, headers, json.dumps(resource.to_dict()) elif self.method == 'POST': path_part = self._get_param("pathPart") resource = self.backend.create_resource(function_id, resource_id, path_part) - return 200, headers, json.dumps(resource.to_dict()) elif self.method == 'DELETE': resource = self.backend.delete_resource(function_id, resource_id) - return 200, headers, json.dumps(resource.to_dict()) + return 200, headers, json.dumps(resource.to_dict()) def resource_methods(self, request, full_url, headers): self.setup_class(request, full_url, headers) @@ -91,13 +89,11 @@ class APIGatewayResponse(BaseResponse): if self.method == 'GET': method_response = self.backend.get_method_response(function_id, resource_id, method_type, response_code) - return 200, headers, json.dumps(method_response) elif self.method == 'PUT': method_response = self.backend.create_method_response(function_id, resource_id, method_type, response_code) - return 200, headers, json.dumps(method_response) elif self.method == 'DELETE': method_response = self.backend.delete_method_response(function_id, resource_id, method_type, response_code) - return 200, headers, json.dumps(method_response) + return 200, headers, json.dumps(method_response) def integrations(self, request, full_url, headers): self.setup_class(request, full_url, headers) @@ -108,15 +104,36 @@ class APIGatewayResponse(BaseResponse): if self.method == 'GET': integration_response = self.backend.get_integration(function_id, resource_id, method_type) - return 200, headers, json.dumps(integration_response) elif self.method == 'PUT': integration_type = self._get_param('type') uri = self._get_param('uri') integration_response = self.backend.create_integration(function_id, resource_id, method_type, integration_type, uri) - return 200, headers, json.dumps(integration_response) elif self.method == 'DELETE': integration_response = self.backend.delete_integration(function_id, resource_id, method_type) - return 200, headers, json.dumps(integration_response) + return 200, headers, json.dumps(integration_response) + + def integration_responses(self, request, full_url, headers): + self.setup_class(request, full_url, headers) + url_path_parts = self.path.split("/") + function_id = url_path_parts[2] + resource_id = url_path_parts[4] + 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 + ) + 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, headers, json.dumps(integration_response) def deployments(self, request, full_url, headers): self.setup_class(request, full_url, headers) @@ -138,7 +155,6 @@ class APIGatewayResponse(BaseResponse): if self.method == 'GET': deployment = self.backend.get_deployment(function_id, deployment_id) - return 200, headers, json.dumps(deployment) elif self.method == 'DELETE': deployment = self.backend.delete_deployment(function_id, deployment_id) - return 200, headers, json.dumps(deployment) + return 200, headers, json.dumps(deployment) diff --git a/moto/apigateway/urls.py b/moto/apigateway/urls.py index 731c0131..e34dbbe6 100644 --- a/moto/apigateway/urls.py +++ b/moto/apigateway/urls.py @@ -15,4 +15,5 @@ url_paths = { '{0}/restapis/(?P[^/]+)/resources/(?P[^/]+)/methods/(?P[^/]+)/?$': APIGatewayResponse().resource_methods, '{0}/restapis/(?P[^/]+)/resources/(?P[^/]+)/methods/(?P[^/]+)/responses/200$': APIGatewayResponse().resource_method_responses, '{0}/restapis/(?P[^/]+)/resources/(?P[^/]+)/methods/(?P[^/]+)/integration/?$': APIGatewayResponse().integrations, + '{0}/restapis/(?P[^/]+)/resources/(?P[^/]+)/methods/(?P[^/]+)/integration/responses/(?P\d+)/?$': APIGatewayResponse().integration_responses, } diff --git a/tests/test_apigateway/test_apigateway.py b/tests/test_apigateway/test_apigateway.py index e7ac1167..f5b31b20 100644 --- a/tests/test_apigateway/test_apigateway.py +++ b/tests/test_apigateway/test_apigateway.py @@ -223,9 +223,18 @@ def test_integrations(): uri='http://httpbin.org/robots.txt', ) response.should.equal({ + 'ResponseMetadata': {'HTTPStatusCode': 200}, + 'httpMethod': 'GET', + 'integrationResponses': { + '200': { + 'responseTemplates': { + 'application/json': None + }, + 'statusCode': 200 + } + }, 'type': 'HTTP', - 'uri': 'http://httpbin.org/robots.txt', - 'ResponseMetadata': {'HTTPStatusCode': 200} + 'uri': 'http://httpbin.org/robots.txt' }) response = client.get_integration( @@ -234,9 +243,18 @@ def test_integrations(): httpMethod='GET' ) response.should.equal({ + 'ResponseMetadata': {'HTTPStatusCode': 200}, + 'httpMethod': 'GET', + 'integrationResponses': { + '200': { + 'responseTemplates': { + 'application/json': None + }, + 'statusCode': 200 + } + }, 'type': 'HTTP', - 'uri': 'http://httpbin.org/robots.txt', - 'ResponseMetadata': {'HTTPStatusCode': 200} + 'uri': 'http://httpbin.org/robots.txt' }) response = client.get_resource( @@ -244,6 +262,15 @@ def test_integrations(): resourceId=root_id, ) response['resourceMethods']['GET']['methodIntegration'].should.equal({ + 'httpMethod': 'GET', + 'integrationResponses': { + '200': { + 'responseTemplates': { + 'application/json': None + }, + 'statusCode': 200 + } + }, 'type': 'HTTP', 'uri': 'http://httpbin.org/robots.txt' }) @@ -261,6 +288,101 @@ def test_integrations(): response['resourceMethods']['GET'].shouldnt.contain("methodIntegration") +@mock_apigateway +def test_integration_response(): + 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', + ) + + response = client.put_integration( + restApiId=api_id, + resourceId=root_id, + httpMethod='GET', + type='HTTP', + uri='http://httpbin.org/robots.txt', + ) + + response = client.put_integration_response( + restApiId=api_id, + resourceId=root_id, + httpMethod='GET', + statusCode='200', + selectionPattern='foobar', + ) + response.should.equal({ + 'statusCode': '200', + 'selectionPattern': 'foobar', + 'ResponseMetadata': {'HTTPStatusCode': 200}, + 'responseTemplates': { + 'application/json': None + } + }) + + response = client.get_integration_response( + restApiId=api_id, + resourceId=root_id, + httpMethod='GET', + statusCode='200', + ) + response.should.equal({ + 'statusCode': '200', + 'selectionPattern': 'foobar', + 'ResponseMetadata': {'HTTPStatusCode': 200}, + 'responseTemplates': { + 'application/json': None + } + }) + + response = client.get_method( + restApiId=api_id, + resourceId=root_id, + httpMethod='GET', + ) + response['methodIntegration']['integrationResponses'].should.equal({ + '200': { + 'responseTemplates': { + 'application/json': None + }, + 'selectionPattern': 'foobar', + 'statusCode': '200' + } + }) + + response = client.delete_integration_response( + restApiId=api_id, + resourceId=root_id, + httpMethod='GET', + statusCode='200', + ) + + response = client.get_method( + restApiId=api_id, + resourceId=root_id, + httpMethod='GET', + ) + response['methodIntegration']['integrationResponses'].should.equal({}) + + @mock_apigateway def test_deployment(): client = boto3.client('apigateway', region_name='us-west-2') From ba70d8fe8d6bcf861b79980055d8d86ff97e48bb Mon Sep 17 00:00:00 2001 From: Steve Pulec Date: Sat, 5 Mar 2016 10:54:19 -0500 Subject: [PATCH 7/8] implement http integration. --- moto/apigateway/models.py | 72 ++++++++++++++++-- tests/test_apigateway/test_apigateway.py | 93 ++++++++++++++++++++++++ 2 files changed, 159 insertions(+), 6 deletions(-) diff --git a/moto/apigateway/models.py b/moto/apigateway/models.py index 4b9b1fdb..5dfffecc 100644 --- a/moto/apigateway/models.py +++ b/moto/apigateway/models.py @@ -1,10 +1,15 @@ from __future__ import unicode_literals import datetime +import httpretty +import requests + from moto.core import BaseBackend from moto.core.utils import iso_8601_datetime_with_milliseconds from .utils import create_id +STAGE_URL = "https://{api_id}.execute-api.{region_name}.amazonaws.com/{stage_name}" + class Deployment(dict): def __init__(self, deployment_id, name): @@ -76,8 +81,10 @@ class Method(dict): class Resource(object): - def __init__(self, id, path_part, parent_id): + def __init__(self, id, region_name, api_id, path_part, parent_id): self.id = id + self.region_name = region_name + self.api_id = api_id self.path_part = path_part self.parent_id = parent_id self.resource_methods = { @@ -86,14 +93,41 @@ class Resource(object): def to_dict(self): response = { - "path": self.path_part, + "path": self.get_path(), "id": self.id, "resourceMethods": self.resource_methods, } if self.parent_id: - response['parent_id'] = self.parent_id + response['parentId'] = self.parent_id + response['pathPart'] = self.path_part return response + def get_path(self): + return self.get_parent_path() + self.path_part + + def get_parent_path(self): + if self.parent_id: + backend = apigateway_backends[self.region_name] + parent = backend.get_resource(self.api_id, self.parent_id) + parent_path = parent.get_path() + if parent_path != '/': # Root parent + parent_path += '/' + return parent_path + else: + return '' + + def get_response(self, request): + integration = self.get_integration(request.method) + integration_type = integration['type'] + + if integration_type == 'HTTP': + uri = integration['uri'] + requests_func = getattr(requests, integration['httpMethod'].lower()) + response = requests_func(uri) + else: + raise NotImplementedError("The {0} type has not been implemented".format(integration_type)) + return response.status_code, response.text + def add_method(self, method_type, authorization_type): method = Method(method_type=method_type, authorization_type=authorization_type) self.resource_methods[method_type] = method @@ -115,8 +149,9 @@ class Resource(object): class RestAPI(object): - def __init__(self, id, name, description): + def __init__(self, id, region_name, name, description): self.id = id + self.region_name = region_name self.name = name self.description = description self.create_date = datetime.datetime.utcnow() @@ -136,14 +171,39 @@ class RestAPI(object): def add_child(self, path, parent_id=None): child_id = create_id() - child = Resource(id=child_id, path_part=path, parent_id=parent_id) + child = Resource(id=child_id, region_name=self.region_name, api_id=self.id, path_part=path, parent_id=parent_id) self.resources[child_id] = child return child + def get_resource_for_path(self, path_after_stage_name): + for resource in self.resources.values(): + if resource.get_path() == path_after_stage_name: + return resource + # TODO deal with no matching resource + + def resource_callback(self, request, full_url, headers): + path_after_stage_name = '/'.join(request.path.split("/")[2:]) + if not path_after_stage_name: + path_after_stage_name = '/' + + resource = self.get_resource_for_path(path_after_stage_name) + status_code, response = resource.get_response(request) + return status_code, headers, response + + def update_integration_mocks(self, stage_name): + httpretty.enable() + + stage_url = STAGE_URL.format(api_id=self.id, region_name=self.region_name, stage_name=stage_name) + for method in httpretty.httpretty.METHODS: + httpretty.register_uri(method, stage_url, body=self.resource_callback) + def create_deployment(self, name): deployment_id = create_id() deployment = Deployment(deployment_id, name) self.deployments[deployment_id] = deployment + + self.update_integration_mocks(name) + return deployment def get_deployment(self, deployment_id): @@ -169,7 +229,7 @@ class APIGatewayBackend(BaseBackend): def create_rest_api(self, name, description): api_id = create_id() - rest_api = RestAPI(api_id, name, description) + rest_api = RestAPI(api_id, self.region_name, name, description) self.apis[api_id] = rest_api return rest_api diff --git a/tests/test_apigateway/test_apigateway.py b/tests/test_apigateway/test_apigateway.py index f5b31b20..b9b4ce9a 100644 --- a/tests/test_apigateway/test_apigateway.py +++ b/tests/test_apigateway/test_apigateway.py @@ -4,6 +4,8 @@ from datetime import datetime from dateutil.tz import tzutc import boto3 from freezegun import freeze_time +import httpretty +import requests import sure # noqa from moto import mock_apigateway @@ -101,6 +103,46 @@ def test_create_resource(): len(client.get_resources(restApiId=api_id)['items']).should.equal(1) +@mock_apigateway +def test_child_resource(): + 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'] + + response = client.create_resource( + restApiId=api_id, + parentId=root_id, + pathPart='users', + ) + users_id = response['id'] + + response = client.create_resource( + restApiId=api_id, + parentId=users_id, + pathPart='tags', + ) + tags_id = response['id'] + + child_resource = client.get_resource( + restApiId=api_id, + resourceId=tags_id, + ) + child_resource.should.equal({ + 'path': '/users/tags', + 'pathPart': 'tags', + 'parentId': users_id, + 'id': tags_id, + 'ResponseMetadata': {'HTTPStatusCode': 200}, + 'resourceMethods': {'GET': {}}, + }) + + @mock_apigateway def test_create_method(): client = boto3.client('apigateway', region_name='us-west-2') @@ -423,3 +465,54 @@ def test_deployment(): restApiId=api_id, ) len(response['items']).should.equal(0) + + +@httpretty.activate +@mock_apigateway +def test_http_proxying_integration(): + httpretty.register_uri( + httpretty.GET, "http://httpbin.org/robots.txt", body='a fake response' + ) + + region_name = 'us-west-2' + client = boto3.client('apigateway', region_name=region_name) + 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', + ) + + response = client.put_integration( + restApiId=api_id, + resourceId=root_id, + httpMethod='GET', + type='HTTP', + uri='http://httpbin.org/robots.txt', + ) + + stage_name = 'staging' + client.create_deployment( + restApiId=api_id, + stageName=stage_name, + ) + + deploy_url = "https://{api_id}.execute-api.{region_name}.amazonaws.com/{stage_name}".format(api_id=api_id, region_name=region_name, stage_name=stage_name) + + requests.get(deploy_url).content.should.equal("a fake response") From 88f64deff9df1870d543803c6254aa43914b6ed2 Mon Sep 17 00:00:00 2001 From: Steve Pulec Date: Sat, 5 Mar 2016 18:01:57 -0500 Subject: [PATCH 8/8] fix py3 --- moto/apigateway/models.py | 2 +- moto/apigateway/responses.py | 2 +- tests/test_apigateway/test_apigateway.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/moto/apigateway/models.py b/moto/apigateway/models.py index 5dfffecc..68017d5d 100644 --- a/moto/apigateway/models.py +++ b/moto/apigateway/models.py @@ -210,7 +210,7 @@ class RestAPI(object): return self.deployments[deployment_id] def get_deployments(self): - return self.deployments.values() + return list(self.deployments.values()) def delete_deployment(self, deployment_id): return self.deployments.pop(deployment_id) diff --git a/moto/apigateway/responses.py b/moto/apigateway/responses.py index 03776c9f..c954d69d 100644 --- a/moto/apigateway/responses.py +++ b/moto/apigateway/responses.py @@ -9,7 +9,7 @@ from .models import apigateway_backends class APIGatewayResponse(BaseResponse): def _get_param(self, key): - return json.loads(self.body).get(key) + return json.loads(self.body.decode("ascii")).get(key) @property def backend(self): diff --git a/tests/test_apigateway/test_apigateway.py b/tests/test_apigateway/test_apigateway.py index b9b4ce9a..03fa797a 100644 --- a/tests/test_apigateway/test_apigateway.py +++ b/tests/test_apigateway/test_apigateway.py @@ -515,4 +515,4 @@ def test_http_proxying_integration(): deploy_url = "https://{api_id}.execute-api.{region_name}.amazonaws.com/{stage_name}".format(api_id=api_id, region_name=region_name, stage_name=stage_name) - requests.get(deploy_url).content.should.equal("a fake response") + requests.get(deploy_url).content.should.equal(b"a fake response")