API Gateway - improve mocking of public API (#4613)
This commit is contained in:
parent
3d6d8c27fd
commit
34a3a03475
@ -22,7 +22,7 @@
|
|||||||
|
|
||||||
## apigateway
|
## apigateway
|
||||||
<details>
|
<details>
|
||||||
<summary>51% implemented</summary>
|
<summary>55% implemented</summary>
|
||||||
|
|
||||||
- [X] create_api_key
|
- [X] create_api_key
|
||||||
- [X] create_authorizer
|
- [X] create_authorizer
|
||||||
@ -113,10 +113,10 @@
|
|||||||
- [ ] import_documentation_parts
|
- [ ] import_documentation_parts
|
||||||
- [ ] import_rest_api
|
- [ ] import_rest_api
|
||||||
- [ ] put_gateway_response
|
- [ ] put_gateway_response
|
||||||
- [ ] put_integration
|
- [X] put_integration
|
||||||
- [ ] put_integration_response
|
- [X] put_integration_response
|
||||||
- [ ] put_method
|
- [X] put_method
|
||||||
- [ ] put_method_response
|
- [X] put_method_response
|
||||||
- [ ] put_rest_api
|
- [ ] put_rest_api
|
||||||
- [ ] tag_resource
|
- [ ] tag_resource
|
||||||
- [ ] test_invoke_authorizer
|
- [ ] test_invoke_authorizer
|
||||||
|
@ -12,6 +12,8 @@
|
|||||||
apigateway
|
apigateway
|
||||||
==========
|
==========
|
||||||
|
|
||||||
|
.. autoclass:: moto.apigateway.models.APIGatewayBackend
|
||||||
|
|
||||||
|start-h3| Example usage |end-h3|
|
|start-h3| Example usage |end-h3|
|
||||||
|
|
||||||
.. sourcecode:: python
|
.. sourcecode:: python
|
||||||
@ -114,10 +116,10 @@ apigateway
|
|||||||
- [ ] import_documentation_parts
|
- [ ] import_documentation_parts
|
||||||
- [ ] import_rest_api
|
- [ ] import_rest_api
|
||||||
- [ ] put_gateway_response
|
- [ ] put_gateway_response
|
||||||
- [ ] put_integration
|
- [X] put_integration
|
||||||
- [ ] put_integration_response
|
- [X] put_integration_response
|
||||||
- [ ] put_method
|
- [X] put_method
|
||||||
- [ ] put_method_response
|
- [X] put_method_response
|
||||||
- [ ] put_rest_api
|
- [ ] put_rest_api
|
||||||
- [ ] tag_resource
|
- [ ] tag_resource
|
||||||
- [ ] test_invoke_authorizer
|
- [ ] test_invoke_authorizer
|
||||||
|
0
moto/apigateway/integration_parsers/__init__.py
Normal file
0
moto/apigateway/integration_parsers/__init__.py
Normal file
24
moto/apigateway/integration_parsers/aws_parser.py
Normal file
24
moto/apigateway/integration_parsers/aws_parser.py
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import requests
|
||||||
|
|
||||||
|
|
||||||
|
class TypeAwsParser:
|
||||||
|
def invoke(self, request, integration):
|
||||||
|
# integration.uri = arn:aws:apigateway:{region}:{subdomain.service|service}:path|action/{service_api}
|
||||||
|
# example value = 'arn:aws:apigateway:us-west-2:dynamodb:action/PutItem'
|
||||||
|
try:
|
||||||
|
# We need a better way to support services automatically
|
||||||
|
# This is how AWS does it though - sending a new HTTP request to the target service
|
||||||
|
arn, action = integration["uri"].split("/")
|
||||||
|
_, _, _, region, service, path_or_action = arn.split(":")
|
||||||
|
if service == "dynamodb" and path_or_action == "action":
|
||||||
|
target_url = f"https://dynamodb.{region}.amazonaws.com/"
|
||||||
|
headers = {"X-Amz-Target": f"DynamoDB_20120810.{action}"}
|
||||||
|
res = requests.post(target_url, request.body, headers=headers)
|
||||||
|
return res.status_code, res.content
|
||||||
|
else:
|
||||||
|
return (
|
||||||
|
400,
|
||||||
|
f"Integration for service {service} / {path_or_action} is not yet supported",
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
return 400, str(e)
|
13
moto/apigateway/integration_parsers/http_parser.py
Normal file
13
moto/apigateway/integration_parsers/http_parser.py
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import requests
|
||||||
|
|
||||||
|
|
||||||
|
class TypeHttpParser:
|
||||||
|
"""
|
||||||
|
Parse invocations to a APIGateway resource with integration type HTTP
|
||||||
|
"""
|
||||||
|
|
||||||
|
def invoke(self, request, integration):
|
||||||
|
uri = integration["uri"]
|
||||||
|
requests_func = getattr(requests, integration["httpMethod"].lower())
|
||||||
|
response = requests_func(uri)
|
||||||
|
return response.status_code, response.text
|
8
moto/apigateway/integration_parsers/unknown_parser.py
Normal file
8
moto/apigateway/integration_parsers/unknown_parser.py
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
class TypeUnknownParser:
|
||||||
|
"""
|
||||||
|
Parse invocations to a APIGateway resource with an unknown integration type
|
||||||
|
"""
|
||||||
|
|
||||||
|
def invoke(self, request, integration):
|
||||||
|
_type = integration["type"]
|
||||||
|
raise NotImplementedError("The {0} type has not been implemented".format(_type))
|
@ -3,9 +3,9 @@ from __future__ import absolute_import
|
|||||||
import random
|
import random
|
||||||
import string
|
import string
|
||||||
import re
|
import re
|
||||||
|
from collections import defaultdict
|
||||||
from copy import copy
|
from copy import copy
|
||||||
|
|
||||||
import requests
|
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from boto3.session import Session
|
from boto3.session import Session
|
||||||
@ -18,6 +18,9 @@ import responses
|
|||||||
from moto.core import ACCOUNT_ID, BaseBackend, BaseModel, CloudFormationModel
|
from moto.core import ACCOUNT_ID, BaseBackend, BaseModel, CloudFormationModel
|
||||||
from .utils import create_id, to_path
|
from .utils import create_id, to_path
|
||||||
from moto.core.utils import path_url
|
from moto.core.utils import path_url
|
||||||
|
from .integration_parsers.aws_parser import TypeAwsParser
|
||||||
|
from .integration_parsers.http_parser import TypeHttpParser
|
||||||
|
from .integration_parsers.unknown_parser import TypeUnknownParser
|
||||||
from .exceptions import (
|
from .exceptions import (
|
||||||
ApiKeyNotFoundException,
|
ApiKeyNotFoundException,
|
||||||
UsagePlanNotFoundException,
|
UsagePlanNotFoundException,
|
||||||
@ -199,7 +202,7 @@ class Method(CloudFormationModel, dict):
|
|||||||
auth_type = properties["AuthorizationType"]
|
auth_type = properties["AuthorizationType"]
|
||||||
key_req = properties["ApiKeyRequired"]
|
key_req = properties["ApiKeyRequired"]
|
||||||
backend = apigateway_backends[region_name]
|
backend = apigateway_backends[region_name]
|
||||||
m = backend.create_method(
|
m = backend.put_method(
|
||||||
function_id=rest_api_id,
|
function_id=rest_api_id,
|
||||||
resource_id=resource_id,
|
resource_id=resource_id,
|
||||||
method_type=method_type,
|
method_type=method_type,
|
||||||
@ -209,7 +212,7 @@ class Method(CloudFormationModel, dict):
|
|||||||
int_method = properties["Integration"]["IntegrationHttpMethod"]
|
int_method = properties["Integration"]["IntegrationHttpMethod"]
|
||||||
int_type = properties["Integration"]["Type"]
|
int_type = properties["Integration"]["Type"]
|
||||||
int_uri = properties["Integration"]["Uri"]
|
int_uri = properties["Integration"]["Uri"]
|
||||||
backend.create_integration(
|
backend.put_integration(
|
||||||
function_id=rest_api_id,
|
function_id=rest_api_id,
|
||||||
resource_id=resource_id,
|
resource_id=resource_id,
|
||||||
method_type=method_type,
|
method_type=method_type,
|
||||||
@ -242,6 +245,9 @@ class Resource(CloudFormationModel):
|
|||||||
self.path_part = path_part
|
self.path_part = path_part
|
||||||
self.parent_id = parent_id
|
self.parent_id = parent_id
|
||||||
self.resource_methods = {}
|
self.resource_methods = {}
|
||||||
|
self.integration_parsers = defaultdict(TypeUnknownParser)
|
||||||
|
self.integration_parsers["HTTP"] = TypeHttpParser()
|
||||||
|
self.integration_parsers["AWS"] = TypeAwsParser()
|
||||||
|
|
||||||
def to_dict(self):
|
def to_dict(self):
|
||||||
response = {
|
response = {
|
||||||
@ -306,15 +312,11 @@ class Resource(CloudFormationModel):
|
|||||||
integration = self.get_integration(request.method)
|
integration = self.get_integration(request.method)
|
||||||
integration_type = integration["type"]
|
integration_type = integration["type"]
|
||||||
|
|
||||||
if integration_type == "HTTP":
|
status, result = self.integration_parsers[integration_type].invoke(
|
||||||
uri = integration["uri"]
|
request, integration
|
||||||
requests_func = getattr(requests, integration["httpMethod"].lower())
|
)
|
||||||
response = requests_func(uri)
|
|
||||||
else:
|
return status, result
|
||||||
raise NotImplementedError(
|
|
||||||
"The {0} type has not been implemented".format(integration_type)
|
|
||||||
)
|
|
||||||
return response.status_code, response.text
|
|
||||||
|
|
||||||
def add_method(
|
def add_method(
|
||||||
self,
|
self,
|
||||||
@ -893,9 +895,7 @@ class RestAPI(CloudFormationModel):
|
|||||||
|
|
||||||
def resource_callback(self, request):
|
def resource_callback(self, request):
|
||||||
path = path_url(request.url)
|
path = path_url(request.url)
|
||||||
path_after_stage_name = "/".join(path.split("/")[2:])
|
path_after_stage_name = "/" + "/".join(path.split("/")[2:])
|
||||||
if not path_after_stage_name:
|
|
||||||
path_after_stage_name = "/"
|
|
||||||
|
|
||||||
resource = self.get_resource_for_path(path_after_stage_name)
|
resource = self.get_resource_for_path(path_after_stage_name)
|
||||||
status_code, response = resource.get_response(request)
|
status_code, response = resource.get_response(request)
|
||||||
@ -909,17 +909,20 @@ class RestAPI(CloudFormationModel):
|
|||||||
api_id=self.id.upper(), region_name=self.region_name, stage_name=stage_name
|
api_id=self.id.upper(), region_name=self.region_name, stage_name=stage_name
|
||||||
)
|
)
|
||||||
|
|
||||||
for url in [stage_url_lower, stage_url_upper]:
|
for resource_id, resource in self.resources.items():
|
||||||
responses_mock._matches.insert(
|
path = resource.get_path()
|
||||||
0,
|
path = "" if path == "/" else path
|
||||||
responses.CallbackResponse(
|
|
||||||
url=url,
|
for http_method, method in resource.resource_methods.items():
|
||||||
method=responses.GET,
|
for url in [stage_url_lower, stage_url_upper]:
|
||||||
callback=self.resource_callback,
|
callback_response = responses.CallbackResponse(
|
||||||
content_type="text/plain",
|
url=url + path,
|
||||||
match_querystring=False,
|
method=http_method,
|
||||||
),
|
callback=self.resource_callback,
|
||||||
)
|
content_type="text/plain",
|
||||||
|
match_querystring=False,
|
||||||
|
)
|
||||||
|
responses_mock._matches.insert(0, callback_response)
|
||||||
|
|
||||||
def create_authorizer(
|
def create_authorizer(
|
||||||
self,
|
self,
|
||||||
@ -1105,6 +1108,32 @@ class BasePathMapping(BaseModel, dict):
|
|||||||
|
|
||||||
|
|
||||||
class APIGatewayBackend(BaseBackend):
|
class APIGatewayBackend(BaseBackend):
|
||||||
|
"""
|
||||||
|
API Gateway mock.
|
||||||
|
|
||||||
|
The public URLs of an API integration are mocked as well, i.e. the following would be supported in Moto:
|
||||||
|
|
||||||
|
.. sourcecode:: python
|
||||||
|
|
||||||
|
client.put_integration(
|
||||||
|
restApiId=api_id,
|
||||||
|
...,
|
||||||
|
uri="http://httpbin.org/robots.txt",
|
||||||
|
integrationHttpMethod="GET",
|
||||||
|
)
|
||||||
|
deploy_url = f"https://{api_id}.execute-api.us-east-1.amazonaws.com/dev"
|
||||||
|
requests.get(deploy_url).content.should.equal(b"a fake response")
|
||||||
|
|
||||||
|
Limitations:
|
||||||
|
- Integrations of type HTTP are supported
|
||||||
|
- Integrations of type AWS with service DynamoDB are supported
|
||||||
|
- Other types (AWS_PROXY, MOCK, etc) are ignored
|
||||||
|
- Other services are not yet supported
|
||||||
|
- The BasePath of an API is ignored
|
||||||
|
- TemplateMapping is not yet supported for requests/responses
|
||||||
|
- This only works when using the decorators, not in ServerMode
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self, region_name):
|
def __init__(self, region_name):
|
||||||
super(APIGatewayBackend, self).__init__()
|
super(APIGatewayBackend, self).__init__()
|
||||||
self.apis = {}
|
self.apis = {}
|
||||||
@ -1191,7 +1220,7 @@ class APIGatewayBackend(BaseBackend):
|
|||||||
resource = self.get_resource(function_id, resource_id)
|
resource = self.get_resource(function_id, resource_id)
|
||||||
return resource.get_method(method_type)
|
return resource.get_method(method_type)
|
||||||
|
|
||||||
def create_method(
|
def put_method(
|
||||||
self,
|
self,
|
||||||
function_id,
|
function_id,
|
||||||
resource_id,
|
resource_id,
|
||||||
@ -1322,7 +1351,7 @@ class APIGatewayBackend(BaseBackend):
|
|||||||
method_response = method.get_response(response_code)
|
method_response = method.get_response(response_code)
|
||||||
return method_response
|
return method_response
|
||||||
|
|
||||||
def create_method_response(
|
def put_method_response(
|
||||||
self,
|
self,
|
||||||
function_id,
|
function_id,
|
||||||
resource_id,
|
resource_id,
|
||||||
@ -1352,7 +1381,7 @@ class APIGatewayBackend(BaseBackend):
|
|||||||
method_response = method.delete_response(response_code)
|
method_response = method.delete_response(response_code)
|
||||||
return method_response
|
return method_response
|
||||||
|
|
||||||
def create_integration(
|
def put_integration(
|
||||||
self,
|
self,
|
||||||
function_id,
|
function_id,
|
||||||
resource_id,
|
resource_id,
|
||||||
@ -1414,7 +1443,7 @@ class APIGatewayBackend(BaseBackend):
|
|||||||
resource = self.get_resource(function_id, resource_id)
|
resource = self.get_resource(function_id, resource_id)
|
||||||
return resource.delete_integration(method_type)
|
return resource.delete_integration(method_type)
|
||||||
|
|
||||||
def create_integration_response(
|
def put_integration_response(
|
||||||
self,
|
self,
|
||||||
function_id,
|
function_id,
|
||||||
resource_id,
|
resource_id,
|
||||||
@ -1458,8 +1487,7 @@ class APIGatewayBackend(BaseBackend):
|
|||||||
if not any(methods):
|
if not any(methods):
|
||||||
raise NoMethodDefined()
|
raise NoMethodDefined()
|
||||||
method_integrations = [
|
method_integrations = [
|
||||||
method["methodIntegration"] if "methodIntegration" in method else None
|
method.get("methodIntegration", None) for method in methods
|
||||||
for method in methods
|
|
||||||
]
|
]
|
||||||
if not any(method_integrations):
|
if not any(method_integrations):
|
||||||
raise NoIntegrationDefined()
|
raise NoIntegrationDefined()
|
||||||
|
@ -193,7 +193,7 @@ class APIGatewayResponse(BaseResponse):
|
|||||||
authorizer_id = self._get_param("authorizerId")
|
authorizer_id = self._get_param("authorizerId")
|
||||||
authorization_scopes = self._get_param("authorizationScopes")
|
authorization_scopes = self._get_param("authorizationScopes")
|
||||||
request_validator_id = self._get_param("requestValidatorId")
|
request_validator_id = self._get_param("requestValidatorId")
|
||||||
method = self.backend.create_method(
|
method = self.backend.put_method(
|
||||||
function_id,
|
function_id,
|
||||||
resource_id,
|
resource_id,
|
||||||
method_type,
|
method_type,
|
||||||
@ -234,7 +234,7 @@ class APIGatewayResponse(BaseResponse):
|
|||||||
elif self.method == "PUT":
|
elif self.method == "PUT":
|
||||||
response_models = self._get_param("responseModels")
|
response_models = self._get_param("responseModels")
|
||||||
response_parameters = self._get_param("responseParameters")
|
response_parameters = self._get_param("responseParameters")
|
||||||
method_response = self.backend.create_method_response(
|
method_response = self.backend.put_method_response(
|
||||||
function_id,
|
function_id,
|
||||||
resource_id,
|
resource_id,
|
||||||
method_type,
|
method_type,
|
||||||
@ -482,7 +482,7 @@ class APIGatewayResponse(BaseResponse):
|
|||||||
"httpMethod"
|
"httpMethod"
|
||||||
) # default removed because it's a required parameter
|
) # default removed because it's a required parameter
|
||||||
|
|
||||||
integration_response = self.backend.create_integration(
|
integration_response = self.backend.put_integration(
|
||||||
function_id,
|
function_id,
|
||||||
resource_id,
|
resource_id,
|
||||||
method_type,
|
method_type,
|
||||||
@ -526,7 +526,7 @@ class APIGatewayResponse(BaseResponse):
|
|||||||
selection_pattern = self._get_param("selectionPattern")
|
selection_pattern = self._get_param("selectionPattern")
|
||||||
response_templates = self._get_param("responseTemplates")
|
response_templates = self._get_param("responseTemplates")
|
||||||
content_handling = self._get_param("contentHandling")
|
content_handling = self._get_param("contentHandling")
|
||||||
integration_response = self.backend.create_integration_response(
|
integration_response = self.backend.put_integration_response(
|
||||||
function_id,
|
function_id,
|
||||||
resource_id,
|
resource_id,
|
||||||
method_type,
|
method_type,
|
||||||
|
@ -2,13 +2,11 @@ import json
|
|||||||
|
|
||||||
import boto3
|
import boto3
|
||||||
from freezegun import freeze_time
|
from freezegun import freeze_time
|
||||||
import requests
|
|
||||||
import sure # noqa # pylint: disable=unused-import
|
import sure # noqa # pylint: disable=unused-import
|
||||||
from botocore.exceptions import ClientError
|
from botocore.exceptions import ClientError
|
||||||
|
|
||||||
from moto import mock_apigateway, mock_cognitoidp, settings
|
from moto import mock_apigateway, mock_cognitoidp, settings
|
||||||
from moto.core import ACCOUNT_ID
|
from moto.core import ACCOUNT_ID
|
||||||
from moto.core.models import responses_mock
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
@ -1811,50 +1809,6 @@ def test_get_model_with_invalid_name():
|
|||||||
ex.value.response["Error"]["Code"].should.equal("NotFoundException")
|
ex.value.response["Error"]["Code"].should.equal("NotFoundException")
|
||||||
|
|
||||||
|
|
||||||
@mock_apigateway
|
|
||||||
def test_http_proxying_integration():
|
|
||||||
responses_mock.add(
|
|
||||||
responses_mock.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",
|
|
||||||
integrationHttpMethod="GET",
|
|
||||||
)
|
|
||||||
|
|
||||||
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
|
|
||||||
)
|
|
||||||
|
|
||||||
if not settings.TEST_SERVER_MODE:
|
|
||||||
requests.get(deploy_url).content.should.equal(b"a fake response")
|
|
||||||
|
|
||||||
|
|
||||||
@mock_apigateway
|
@mock_apigateway
|
||||||
def test_api_key_value_min_length():
|
def test_api_key_value_min_length():
|
||||||
region_name = "us-east-1"
|
region_name = "us-east-1"
|
||||||
|
219
tests/test_apigateway/test_apigateway_integration.py
Normal file
219
tests/test_apigateway/test_apigateway_integration.py
Normal file
@ -0,0 +1,219 @@
|
|||||||
|
import boto3
|
||||||
|
import json
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from moto import mock_apigateway, mock_dynamodb2
|
||||||
|
from moto import settings
|
||||||
|
from moto.core.models import responses_mock
|
||||||
|
from unittest import SkipTest
|
||||||
|
|
||||||
|
|
||||||
|
@mock_apigateway
|
||||||
|
def test_http_integration():
|
||||||
|
if settings.TEST_SERVER_MODE:
|
||||||
|
raise SkipTest("Cannot test mock of execute-api.apigateway in ServerMode")
|
||||||
|
responses_mock.add(
|
||||||
|
responses_mock.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",
|
||||||
|
integrationHttpMethod="GET",
|
||||||
|
)
|
||||||
|
|
||||||
|
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(b"a fake response")
|
||||||
|
|
||||||
|
|
||||||
|
@mock_apigateway
|
||||||
|
@mock_dynamodb2
|
||||||
|
def test_aws_integration_dynamodb():
|
||||||
|
if settings.TEST_SERVER_MODE:
|
||||||
|
raise SkipTest("Cannot test mock of execute-api.apigateway in ServerMode")
|
||||||
|
|
||||||
|
client = boto3.client("apigateway", region_name="us-west-2")
|
||||||
|
dynamodb = boto3.client("dynamodb", region_name="us-west-2")
|
||||||
|
table_name = "test_1"
|
||||||
|
integration_action = "arn:aws:apigateway:us-west-2:dynamodb:action/PutItem"
|
||||||
|
stage_name = "staging"
|
||||||
|
|
||||||
|
create_table(dynamodb, table_name)
|
||||||
|
api_id, _ = create_integration_test_api(client, integration_action)
|
||||||
|
|
||||||
|
client.create_deployment(restApiId=api_id, stageName=stage_name)
|
||||||
|
|
||||||
|
res = requests.put(
|
||||||
|
f"https://{api_id}.execute-api.us-west-2.amazonaws.com/{stage_name}",
|
||||||
|
json={"TableName": table_name, "Item": {"name": {"S": "the-key"}}},
|
||||||
|
)
|
||||||
|
res.status_code.should.equal(200)
|
||||||
|
res.content.should.equal(b"{}")
|
||||||
|
|
||||||
|
|
||||||
|
@mock_apigateway
|
||||||
|
@mock_dynamodb2
|
||||||
|
def test_aws_integration_dynamodb_multiple_stages():
|
||||||
|
if settings.TEST_SERVER_MODE:
|
||||||
|
raise SkipTest("Cannot test mock of execute-api.apigateway in ServerMode")
|
||||||
|
|
||||||
|
client = boto3.client("apigateway", region_name="us-west-2")
|
||||||
|
dynamodb = boto3.client("dynamodb", region_name="us-west-2")
|
||||||
|
table_name = "test_1"
|
||||||
|
integration_action = "arn:aws:apigateway:us-west-2:dynamodb:action/PutItem"
|
||||||
|
|
||||||
|
create_table(dynamodb, table_name)
|
||||||
|
api_id, _ = create_integration_test_api(client, integration_action)
|
||||||
|
|
||||||
|
client.create_deployment(restApiId=api_id, stageName="dev")
|
||||||
|
client.create_deployment(restApiId=api_id, stageName="staging")
|
||||||
|
|
||||||
|
res = requests.put(
|
||||||
|
f"https://{api_id}.execute-api.us-west-2.amazonaws.com/dev",
|
||||||
|
json={"TableName": table_name, "Item": {"name": {"S": "the-key"}}},
|
||||||
|
)
|
||||||
|
res.status_code.should.equal(200)
|
||||||
|
|
||||||
|
res = requests.put(
|
||||||
|
f"https://{api_id}.execute-api.us-west-2.amazonaws.com/staging",
|
||||||
|
json={"TableName": table_name, "Item": {"name": {"S": "the-key"}}},
|
||||||
|
)
|
||||||
|
res.status_code.should.equal(200)
|
||||||
|
|
||||||
|
# We haven't pushed to prod yet
|
||||||
|
res = requests.put(
|
||||||
|
f"https://{api_id}.execute-api.us-west-2.amazonaws.com/prod",
|
||||||
|
json={"TableName": table_name, "Item": {"name": {"S": "the-key"}}},
|
||||||
|
)
|
||||||
|
res.status_code.should.equal(400)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_apigateway
|
||||||
|
@mock_dynamodb2
|
||||||
|
def test_aws_integration_dynamodb_multiple_resources():
|
||||||
|
if settings.TEST_SERVER_MODE:
|
||||||
|
raise SkipTest("Cannot test mock of execute-api.apigateway in ServerMode")
|
||||||
|
|
||||||
|
client = boto3.client("apigateway", region_name="us-west-2")
|
||||||
|
dynamodb = boto3.client("dynamodb", region_name="us-west-2")
|
||||||
|
table_name = "test_1"
|
||||||
|
create_table(dynamodb, table_name)
|
||||||
|
|
||||||
|
# Create API integration to PutItem
|
||||||
|
integration_action = "arn:aws:apigateway:us-west-2:dynamodb:action/PutItem"
|
||||||
|
api_id, root_id = create_integration_test_api(client, integration_action)
|
||||||
|
|
||||||
|
# Create API integration to GetItem
|
||||||
|
res = client.create_resource(restApiId=api_id, parentId=root_id, pathPart="item")
|
||||||
|
parent_id = res["id"]
|
||||||
|
integration_action = "arn:aws:apigateway:us-west-2:dynamodb:action/GetItem"
|
||||||
|
api_id, root_id = create_integration_test_api(
|
||||||
|
client,
|
||||||
|
integration_action,
|
||||||
|
api_id=api_id,
|
||||||
|
parent_id=parent_id,
|
||||||
|
http_method="GET",
|
||||||
|
)
|
||||||
|
|
||||||
|
client.create_deployment(restApiId=api_id, stageName="dev")
|
||||||
|
|
||||||
|
# Put item at the root resource
|
||||||
|
res = requests.put(
|
||||||
|
f"https://{api_id}.execute-api.us-west-2.amazonaws.com/dev",
|
||||||
|
json={
|
||||||
|
"TableName": table_name,
|
||||||
|
"Item": {"name": {"S": "the-key"}, "attr2": {"S": "sth"}},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
res.status_code.should.equal(200)
|
||||||
|
|
||||||
|
# Get item from child resource
|
||||||
|
res = requests.get(
|
||||||
|
f"https://{api_id}.execute-api.us-west-2.amazonaws.com/dev/item",
|
||||||
|
json={"TableName": table_name, "Key": {"name": {"S": "the-key"}}},
|
||||||
|
)
|
||||||
|
res.status_code.should.equal(200)
|
||||||
|
json.loads(res.content).should.equal(
|
||||||
|
{"Item": {"name": {"S": "the-key"}, "attr2": {"S": "sth"}}}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def create_table(dynamodb, table_name):
|
||||||
|
# Create DynamoDB table
|
||||||
|
dynamodb.create_table(
|
||||||
|
TableName=table_name,
|
||||||
|
KeySchema=[{"AttributeName": "name", "KeyType": "HASH"}],
|
||||||
|
AttributeDefinitions=[{"AttributeName": "name", "AttributeType": "S"}],
|
||||||
|
BillingMode="PAY_PER_REQUEST",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def create_integration_test_api(
|
||||||
|
client, integration_action, api_id=None, parent_id=None, http_method="PUT"
|
||||||
|
):
|
||||||
|
if not api_id:
|
||||||
|
# We do not have a root yet - create the API first
|
||||||
|
response = client.create_rest_api(name="my_api", description="this is my api")
|
||||||
|
api_id = response["id"]
|
||||||
|
if not parent_id:
|
||||||
|
resources = client.get_resources(restApiId=api_id)
|
||||||
|
parent_id = [
|
||||||
|
resource for resource in resources["items"] if resource["path"] == "/"
|
||||||
|
][0]["id"]
|
||||||
|
|
||||||
|
client.put_method(
|
||||||
|
restApiId=api_id,
|
||||||
|
resourceId=parent_id,
|
||||||
|
httpMethod=http_method,
|
||||||
|
authorizationType="NONE",
|
||||||
|
)
|
||||||
|
client.put_method_response(
|
||||||
|
restApiId=api_id,
|
||||||
|
resourceId=parent_id,
|
||||||
|
httpMethod=http_method,
|
||||||
|
statusCode="200",
|
||||||
|
)
|
||||||
|
client.put_integration(
|
||||||
|
restApiId=api_id,
|
||||||
|
resourceId=parent_id,
|
||||||
|
httpMethod=http_method,
|
||||||
|
type="AWS",
|
||||||
|
uri=integration_action,
|
||||||
|
integrationHttpMethod=http_method,
|
||||||
|
)
|
||||||
|
client.put_integration_response(
|
||||||
|
restApiId=api_id,
|
||||||
|
resourceId=parent_id,
|
||||||
|
httpMethod=http_method,
|
||||||
|
statusCode="200",
|
||||||
|
selectionPattern="",
|
||||||
|
responseTemplates={"application/json": "{}"},
|
||||||
|
)
|
||||||
|
return api_id, parent_id
|
Loading…
Reference in New Issue
Block a user