From ba70d8fe8d6bcf861b79980055d8d86ff97e48bb Mon Sep 17 00:00:00 2001 From: Steve Pulec Date: Sat, 5 Mar 2016 10:54:19 -0500 Subject: [PATCH] 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 4b9b1fdbf..5dfffecca 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 f5b31b20e..b9b4ce9a9 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")