From 45f92fb4c73a95451ed71fd2754190e6de6b47ab Mon Sep 17 00:00:00 2001 From: Steve Pulec Date: Tue, 1 Mar 2016 12:03:59 -0500 Subject: [PATCH] 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 df283fc63..362218bf1 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 000000000..47db4a703 --- /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 000000000..3fc4aa2e9 --- /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 000000000..018ca7c06 --- /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 000000000..363f352bd --- /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 000000000..77212034f --- /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 7d4da577f..b66af5778 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 6595019fb..20cbfec82 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 000000000..7a4cc25e0 --- /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 000000000..f2a29e253 --- /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": []}')