API Gateway: put_rest_api and import_rest_api (#5140)

This commit is contained in:
Bert Blommers 2022-05-16 15:13:23 +00:00 committed by GitHub
parent ecfd6d6065
commit 8bbd242d75
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 835 additions and 20 deletions

View File

@ -22,7 +22,7 @@
## apigateway
<details>
<summary>62% implemented</summary>
<summary>65% implemented</summary>
- [X] create_api_key
- [X] create_authorizer
@ -93,7 +93,7 @@
- [X] get_request_validator
- [X] get_request_validators
- [X] get_resource
- [ ] get_resources
- [X] get_resources
- [X] get_rest_api
- [ ] get_rest_apis
- [ ] get_sdk
@ -111,13 +111,13 @@
- [X] get_vpc_links
- [ ] import_api_keys
- [ ] import_documentation_parts
- [ ] import_rest_api
- [X] import_rest_api
- [X] put_gateway_response
- [X] put_integration
- [X] put_integration_response
- [X] put_method
- [X] put_method_response
- [ ] put_rest_api
- [X] put_rest_api
- [ ] tag_resource
- [ ] test_invoke_authorizer
- [ ] test_invoke_method

View File

@ -100,7 +100,7 @@ apigateway
- [X] get_request_validator
- [X] get_request_validators
- [X] get_resource
- [ ] get_resources
- [X] get_resources
- [X] get_rest_api
- [ ] get_rest_apis
- [ ] get_sdk
@ -122,13 +122,21 @@ apigateway
- [ ] import_api_keys
- [ ] import_documentation_parts
- [ ] import_rest_api
- [X] import_rest_api
Only a subset of the OpenAPI spec 3.x is currently implemented.
- [X] put_gateway_response
- [X] put_integration
- [X] put_integration_response
- [X] put_method
- [X] put_method_response
- [ ] put_rest_api
- [X] put_rest_api
Only a subset of the OpenAPI spec 3.x is currently implemented.
- [ ] tag_resource
- [ ] test_invoke_authorizer
- [ ] test_invoke_method

View File

@ -50,6 +50,25 @@ class IntegrationMethodNotDefined(BadRequestException):
super().__init__("Enumeration value for HttpMethod must be non-empty")
class InvalidOpenAPIDocumentException(BadRequestException):
def __init__(self, cause):
super().__init__(
f"Failed to parse the uploaded OpenAPI document due to: {cause.message}"
)
class InvalidOpenApiDocVersionException(BadRequestException):
def __init__(self):
super().__init__("Only OpenAPI 3.x.x are currently supported")
class InvalidOpenApiModeException(BadRequestException):
def __init__(self):
super().__init__(
'Enumeration value of OpenAPI import mode must be "overwrite" or "merge"',
)
class InvalidResourcePathException(BadRequestException):
def __init__(self):
super().__init__(
@ -233,6 +252,13 @@ class BasePathNotFoundException(NotFoundException):
super().__init__("Invalid base path mapping identifier specified")
class ResourceIdNotFoundException(NotFoundException):
code = 404
def __init__(self):
super().__init__("Invalid resource identifier specified")
class VpcLinkNotFound(NotFoundException):
code = 404

View File

@ -6,10 +6,13 @@ import re
from collections import defaultdict
from copy import copy
from openapi_spec_validator import validate_spec
import time
from urllib.parse import urlparse
import responses
from openapi_spec_validator.exceptions import OpenAPIValidationError
from moto.core import get_account_id, BaseBackend, BaseModel, CloudFormationModel
from .utils import create_id, to_path
from moto.core.utils import path_url, BackendDict
@ -27,9 +30,13 @@ from .exceptions import (
InvalidArn,
InvalidIntegrationArn,
InvalidHttpEndpoint,
InvalidOpenAPIDocumentException,
InvalidOpenApiDocVersionException,
InvalidOpenApiModeException,
InvalidResourcePathException,
AuthorizerNotFoundException,
StageNotFoundException,
ResourceIdNotFoundException,
RoleNotSpecified,
NoIntegrationDefined,
NoIntegrationResponseDefined,
@ -184,7 +191,7 @@ class Method(CloudFormationModel, dict):
requestValidatorId=kwargs.get("request_validator_id"),
)
)
self.method_responses = {}
self["methodResponses"] = {}
@staticmethod
def cloudformation_name_type():
@ -229,14 +236,14 @@ class Method(CloudFormationModel, dict):
method_response = MethodResponse(
response_code, response_models, response_parameters
)
self.method_responses[response_code] = method_response
self["methodResponses"][response_code] = method_response
return method_response
def get_response(self, response_code):
return self.method_responses.get(response_code)
return self["methodResponses"].get(response_code)
def delete_response(self, response_code):
return self.method_responses.pop(response_code, None)
return self["methodResponses"].pop(response_code, None)
class Resource(CloudFormationModel):
@ -288,7 +295,7 @@ class Resource(CloudFormationModel):
backend = apigateway_backends[region_name]
if parent == api_id:
# A Root path (/) is automatically created. Any new paths should use this as their parent
resources = backend.list_resources(function_id=api_id)
resources = backend.get_resources(function_id=api_id)
root_id = [resource for resource in resources if resource.path_part == "/"][
0
].id
@ -789,7 +796,7 @@ class RestAPI(CloudFormationModel):
self.resources = {}
self.models = {}
self.request_validators = {}
self.add_child("/") # Add default child
self.default = self.add_child("/") # Add default child
def __repr__(self):
return str(self.id)
@ -811,8 +818,6 @@ class RestAPI(CloudFormationModel):
}
def apply_patch_operations(self, patch_operations):
def to_path(prop):
return "/" + prop
for op in patch_operations:
path = op[self.OPERATION_PATH]
@ -1270,12 +1275,80 @@ class APIGatewayBackend(BaseBackend):
self.apis[api_id] = rest_api
return rest_api
def import_rest_api(self, api_doc, fail_on_warnings):
"""
Only a subset of the OpenAPI spec 3.x is currently implemented.
"""
if fail_on_warnings:
try:
validate_spec(api_doc)
except OpenAPIValidationError as e:
raise InvalidOpenAPIDocumentException(e)
name = api_doc["info"]["title"]
description = api_doc["info"]["description"]
api = self.create_rest_api(name=name, description=description)
self.put_rest_api(api.id, api_doc, fail_on_warnings=fail_on_warnings)
return api
def get_rest_api(self, function_id):
rest_api = self.apis.get(function_id)
if rest_api is None:
raise RestAPINotFound()
return rest_api
def put_rest_api(self, function_id, api_doc, mode="merge", fail_on_warnings=False):
"""
Only a subset of the OpenAPI spec 3.x is currently implemented.
"""
if mode not in ["merge", "overwrite"]:
raise InvalidOpenApiModeException()
if api_doc.get("swagger") is not None or (
api_doc.get("openapi") is not None and api_doc["openapi"][0] != "3"
):
raise InvalidOpenApiDocVersionException()
if fail_on_warnings:
try:
validate_spec(api_doc)
except OpenAPIValidationError as e:
raise InvalidOpenAPIDocumentException(e)
if mode == "overwrite":
api = self.get_rest_api(function_id)
api.resources = {}
api.default = api.add_child("/") # Add default child
for (path, resource_doc) in sorted(
api_doc["paths"].items(), key=lambda x: x[0]
):
parent_path_part = path[0 : path.rfind("/")] or "/"
parent_resource_id = (
self.apis[function_id].get_resource_for_path(parent_path_part).id
)
resource = self.create_resource(
function_id=function_id,
parent_resource_id=parent_resource_id,
path_part=path[path.rfind("/") + 1 :],
)
for (method_type, method_doc) in resource_doc.items():
method_type = method_type.upper()
if method_doc.get("x-amazon-apigateway-integration") is None:
self.put_method(function_id, resource.id, method_type, None)
method_responses = method_doc.get("responses", {}).items()
for (response_code, _) in method_responses:
self.put_method_response(
function_id,
resource.id,
method_type,
response_code,
response_models=None,
response_parameters=None,
)
return self.get_rest_api(function_id)
def update_rest_api(self, function_id, patch_operations):
rest_api = self.apis.get(function_id)
if rest_api is None:
@ -1290,19 +1363,24 @@ class APIGatewayBackend(BaseBackend):
rest_api = self.apis.pop(function_id)
return rest_api
def list_resources(self, function_id):
def get_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)
if resource_id not in api.resources:
raise ResourceIdNotFoundException
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)
if not path_part:
# We're attempting to create the default resource, which already exists.
return api.default
if not re.match("^\\{?[a-zA-Z0-9._-]+\\+?\\}?$", path_part):
raise InvalidResourcePathException()
api = self.get_rest_api(function_id)
child = api.add_child(path=path_part, parent_id=parent_resource_id)
return child
@ -1578,7 +1656,7 @@ class APIGatewayBackend(BaseBackend):
api = self.get_rest_api(function_id)
methods = [
list(res.resource_methods.values())
for res in self.list_resources(function_id)
for res in self.get_resources(function_id)
]
methods = [m for sublist in methods for m in sublist]
if not any(methods):

View File

@ -4,6 +4,7 @@ from urllib.parse import unquote
from moto.utilities.utils import merge_multiple_dicts
from moto.core.responses import BaseResponse
from .models import apigateway_backends
from .utils import deserialize_body
from .exceptions import InvalidRequestInput
API_KEY_SOURCES = ["AUTHORIZER", "HEADER"]
@ -56,8 +57,16 @@ class APIGatewayResponse(BaseResponse):
apis = self.backend.list_apis()
return 200, {}, json.dumps({"item": [api.to_dict() for api in apis]})
elif self.method == "POST":
api_doc = deserialize_body(self.body)
if api_doc:
fail_on_warnings = self._get_bool_param("failonwarnings")
rest_api = self.backend.import_rest_api(api_doc, fail_on_warnings)
return 200, {}, json.dumps(rest_api.to_dict())
name = self._get_param("name")
description = self._get_param("description")
api_key_source = self._get_param("apiKeySource")
endpoint_configuration = self._get_param("endpointConfiguration")
tags = self._get_param("tags")
@ -82,6 +91,7 @@ class APIGatewayResponse(BaseResponse):
policy=policy,
minimum_compression_size=minimum_compression_size,
)
return 200, {}, json.dumps(rest_api.to_dict())
def __validte_rest_patch_operations(self, patch_operations):
@ -99,6 +109,15 @@ class APIGatewayResponse(BaseResponse):
rest_api = self.backend.get_rest_api(function_id)
elif self.method == "DELETE":
rest_api = self.backend.delete_rest_api(function_id)
elif self.method == "PUT":
mode = self._get_param("mode", "merge")
fail_on_warnings = self._get_bool_param("failonwarnings", False)
api_doc = deserialize_body(self.body)
rest_api = self.backend.put_rest_api(
function_id, api_doc, mode, fail_on_warnings
)
elif self.method == "PATCH":
patch_operations = self._get_param("patchOperations")
response = self.__validte_rest_patch_operations(patch_operations)
@ -113,7 +132,7 @@ class APIGatewayResponse(BaseResponse):
function_id = self.path.replace("/restapis/", "", 1).split("/")[0]
if self.method == "GET":
resources = self.backend.list_resources(function_id)
resources = self.backend.get_resources(function_id)
return (
200,
{},

View File

@ -1,5 +1,7 @@
import random
import string
import json
import yaml
def create_id():
@ -8,5 +10,17 @@ def create_id():
return "".join(str(random.choice(chars)) for x in range(size))
def deserialize_body(body):
try:
api_doc = json.loads(body)
except json.JSONDecodeError:
api_doc = yaml.safe_load(body)
if "openapi" in api_doc or "swagger" in api_doc:
return api_doc
return None
def to_path(prop):
return "/" + prop

View File

@ -54,6 +54,7 @@ _dep_aws_xray_sdk = "aws-xray-sdk!=0.96,>=0.93"
_dep_idna = "idna<4,>=2.5"
_dep_cfn_lint = "cfn-lint>=0.4.0"
_dep_sshpubkeys = "sshpubkeys>=3.1.0"
_dep_openapi = "openapi-spec-validator>=0.2.8"
_dep_pyparsing = "pyparsing>=3.0.0"
_setuptools = "setuptools"
@ -69,6 +70,7 @@ all_extra_deps = [
_dep_cfn_lint,
_dep_sshpubkeys,
_dep_pyparsing,
_dep_openapi,
_setuptools,
]
all_server_deps = all_extra_deps + ["flask", "flask-cors"]
@ -82,7 +84,7 @@ for service_name in [
extras_per_service[service_name] = []
extras_per_service.update(
{
"apigateway": [_dep_PyYAML, _dep_python_jose, _dep_python_jose_ecdsa_pin],
"apigateway": [_dep_PyYAML, _dep_python_jose, _dep_python_jose_ecdsa_pin, _dep_openapi],
"apigatewayv2": [_dep_PyYAML],
"appsync": [_dep_graphql],
"awslambda": [_dep_docker],

View File

@ -0,0 +1,102 @@
{
"openapi": "3.0.0",
"info": {
"description": "description from JSON file",
"version": "1",
"title": "doc"
},
"paths": {
"/": {
"get": {
"description": "Method description.",
"responses": {
"200": {
"description": "200 response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Empty"
}
}
}
}
}
}
},
"/test": {
"post": {
"description": "Method description.",
"responses": {
"201": {
"description": "201 response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Empty"
}
}
}
}
}
}
}
},
"x-amazon-apigateway-documentation": {
"version": "1.0.3",
"documentationParts": [
{
"location": {
"type": "API"
},
"properties": {
"description": "API description",
"info": {
"description": "API info description 4",
"version": "API info version 3"
}
}
},
{
"location": {
"type": "METHOD",
"method": "GET"
},
"properties": {
"description": "Method description."
}
},
{
"location": {
"type": "MODEL",
"name": "Empty"
},
"properties": {
"title": "Empty Schema"
}
},
{
"location": {
"type": "RESPONSE",
"method": "GET",
"statusCode": "200"
},
"properties": {
"description": "200 response"
}
}
]
},
"servers": [
{
"url": "/"
}
],
"components": {
"schemas": {
"Empty": {
"type": "object",
"title": "Empty Schema"
}
}
}
}

View File

@ -0,0 +1,60 @@
openapi: 3.0.0
info:
description: description
version: '1'
title: doc
paths:
/:
get:
description: Method description.
responses:
'200':
description: 200 response
content:
application/json:
schema:
$ref: '#/components/schemas/Empty'
/test:
post:
description: Method description.
responses:
'200':
description: 200 response
content:
application/json:
schema:
$ref: '#/components/schemas/Empty'
x-amazon-apigateway-documentation:
version: 1.0.3
documentationParts:
- location:
type: API
properties:
description: API description
info:
description: API info description 4
version: API info version 3
- location:
type: METHOD
method: GET
properties:
description: Method description.
- location:
type: MODEL
name: Empty
properties:
title: Empty Schema
- location:
type: RESPONSE
method: GET
statusCode: '200'
properties:
description: 200 response
servers:
- url: /
components:
schemas:
Empty:
type: object
title: Empty Schema

View File

@ -0,0 +1,102 @@
{
"openapi": "3.0.0",
"info": {
"description": "description",
"version": "1",
"title": "doc"
},
"paaths": {
"/": {
"get": {
"description": "Method description.",
"responses": {
"200": {
"description": "200 response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Empty"
}
}
}
}
}
}
},
"/test": {
"post": {
"description": "Method description.",
"responses": {
"200": {
"description": "200 response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Empty"
}
}
}
}
}
}
}
},
"x-amazon-apigateway-documentation": {
"version": "1.0.3",
"documentationParts": [
{
"location": {
"type": "API"
},
"properties": {
"description": "API description",
"info": {
"description": "API info description 4",
"version": "API info version 3"
}
}
},
{
"location": {
"type": "METHOD",
"method": "GET"
},
"properties": {
"description": "Method description."
}
},
{
"location": {
"type": "MODEL",
"name": "Empty"
},
"properties": {
"title": "Empty Schema"
}
},
{
"location": {
"type": "RESPONSE",
"method": "GET",
"statusCode": "200"
},
"properties": {
"description": "200 response"
}
}
]
},
"servers": [
{
"url": "/"
}
],
"components": {
"schemas": {
"Empty": {
"type": "object",
"title": "Empty Schema"
}
}
}
}

View File

@ -0,0 +1,102 @@
{
"openapi": "2.0.0",
"info": {
"description": "description",
"version": "1",
"title": "doc"
},
"paths": {
"/": {
"get": {
"description": "Method description.",
"responses": {
"200": {
"description": "200 response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Empty"
}
}
}
}
}
}
},
"/test": {
"post": {
"description": "Method description.",
"responses": {
"200": {
"description": "200 response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Empty"
}
}
}
}
}
}
}
},
"x-amazon-apigateway-documentation": {
"version": "1.0.3",
"documentationParts": [
{
"location": {
"type": "API"
},
"properties": {
"description": "API description",
"info": {
"description": "API info description 4",
"version": "API info version 3"
}
}
},
{
"location": {
"type": "METHOD",
"method": "GET"
},
"properties": {
"description": "Method description."
}
},
{
"location": {
"type": "MODEL",
"name": "Empty"
},
"properties": {
"title": "Empty Schema"
}
},
{
"location": {
"type": "RESPONSE",
"method": "GET",
"statusCode": "200"
},
"properties": {
"description": "200 response"
}
}
]
},
"servers": [
{
"url": "/"
}
],
"components": {
"schemas": {
"Empty": {
"type": "object",
"title": "Empty Schema"
}
}
}
}

View File

@ -367,6 +367,7 @@ def test_create_method():
"httpMethod": "GET",
"authorizationType": "none",
"apiKeyRequired": False,
"methodResponses": {},
"ResponseMetadata": {"HTTPStatusCode": 200},
}
)
@ -401,6 +402,7 @@ def test_create_method_apikeyrequired():
"httpMethod": "GET",
"authorizationType": "none",
"apiKeyRequired": True,
"methodResponses": {},
"ResponseMetadata": {"HTTPStatusCode": 200},
}
)
@ -452,6 +454,19 @@ def test_create_method_response():
response.should.equal({"ResponseMetadata": {"HTTPStatusCode": 200}})
@mock_apigateway
def test_get_method_unknown_resource_id():
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"]
with pytest.raises(ClientError) as ex:
client.get_method(restApiId=api_id, resourceId="sth", httpMethod="GET")
err = ex.value.response["Error"]
err["Code"].should.equal("NotFoundException")
err["Message"].should.equal("Invalid resource identifier specified")
@mock_apigateway
def test_delete_method():
client = boto3.client("apigateway", region_name="us-west-2")

View File

@ -0,0 +1,70 @@
import boto3
import os
import pytest
from botocore.exceptions import ClientError
from moto import mock_apigateway
@mock_apigateway
def test_import_rest_api__api_is_created():
client = boto3.client("apigateway", region_name="us-west-2")
path = os.path.dirname(os.path.abspath(__file__))
with open(path + "/resources/test_api.json", "rb") as api_json:
response = client.import_rest_api(body=api_json.read())
response.should.have.key("id")
response.should.have.key("name").which.should.equal("doc")
response.should.have.key("description").which.should.equal(
"description from JSON file"
)
response = client.get_rest_api(restApiId=response["id"])
response.should.have.key("name").which.should.equal("doc")
response.should.have.key("description").which.should.equal(
"description from JSON file"
)
@mock_apigateway
def test_import_rest_api__invalid_api_creates_nothing():
client = boto3.client("apigateway", region_name="us-west-2")
path = os.path.dirname(os.path.abspath(__file__))
with open(path + "/resources/test_api_invalid.json", "rb") as api_json:
with pytest.raises(ClientError) as exc:
client.import_rest_api(body=api_json.read(), failOnWarnings=True)
err = exc.value.response["Error"]
err["Code"].should.equal("BadRequestException")
err["Message"].should.equal(
"Failed to parse the uploaded OpenAPI document due to: 'paths' is a required property"
)
client.get_rest_apis().should.have.key("items").length_of(0)
@mock_apigateway
def test_import_rest_api__methods_are_created():
client = boto3.client("apigateway", region_name="us-east-1")
path = os.path.dirname(os.path.abspath(__file__))
with open(path + "/resources/test_api.json", "rb") as api_json:
resp = client.import_rest_api(body=api_json.read())
api_id = resp["id"]
resources = client.get_resources(restApiId=api_id)
root_id = [res for res in resources["items"] if res["path"] == "/"][0]["id"]
# We have a GET-method
resp = client.get_method(restApiId=api_id, resourceId=root_id, httpMethod="GET")
resp["methodResponses"].should.equal({"200": {"statusCode": "200"}})
# We have a POST on /test
test_path_id = [res for res in resources["items"] if res["path"] == "/test"][0][
"id"
]
resp = client.get_method(
restApiId=api_id, resourceId=test_path_id, httpMethod="POST"
)
resp["methodResponses"].should.equal({"201": {"statusCode": "201"}})

View File

@ -0,0 +1,217 @@
import boto3
import os
import pytest
from botocore.exceptions import ClientError
from moto import mock_apigateway
@mock_apigateway
def test_put_rest_api__api_details_are_persisted():
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"]
path = os.path.dirname(os.path.abspath(__file__))
with open(path + "/resources/test_api.json", "rb") as api_json:
response = client.put_rest_api(
restApiId=api_id,
mode="overwrite",
failOnWarnings=True,
body=api_json.read(),
)
response.should.have.key("id").which.should.equal(api_id)
response.should.have.key("name").which.should.equal("my_api")
response.should.have.key("description").which.should.equal("this is my api")
@mock_apigateway
def test_put_rest_api__methods_are_created():
client = boto3.client("apigateway", region_name="us-east-2")
response = client.create_rest_api(name="my_api", description="this is my api")
api_id = response["id"]
path = os.path.dirname(os.path.abspath(__file__))
with open(path + "/resources/test_api.json", "rb") as api_json:
client.put_rest_api(restApiId=api_id, body=api_json.read())
resources = client.get_resources(restApiId=api_id)
root_id = [res for res in resources["items"] if res["path"] == "/"][0]["id"]
# We have a GET-method
resp = client.get_method(restApiId=api_id, resourceId=root_id, httpMethod="GET")
resp["methodResponses"].should.equal({"200": {"statusCode": "200"}})
# We have a POST on /test
test_path_id = [res for res in resources["items"] if res["path"] == "/test"][0][
"id"
]
resp = client.get_method(
restApiId=api_id, resourceId=test_path_id, httpMethod="POST"
)
resp["methodResponses"].should.equal({"201": {"statusCode": "201"}})
@mock_apigateway
def test_put_rest_api__existing_methods_are_overwritten():
client = boto3.client("apigateway", region_name="us-east-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="POST",
authorizationType="none",
)
response = client.get_method(
restApiId=api_id, resourceId=root_id, httpMethod="POST"
)
response.should.have.key("httpMethod").equals("POST")
path = os.path.dirname(os.path.abspath(__file__))
with open(path + "/resources/test_api.json", "rb") as api_json:
client.put_rest_api(
restApiId=api_id,
mode="overwrite",
failOnWarnings=True,
body=api_json.read(),
)
# Since we chose mode=overwrite, the root_id is different
resources = client.get_resources(restApiId=api_id)
new_root_id = [
resource for resource in resources["items"] if resource["path"] == "/"
][0]["id"]
new_root_id.shouldnt.equal(root_id)
# Our POST-method should be gone
with pytest.raises(ClientError) as exc:
client.get_method(restApiId=api_id, resourceId=new_root_id, httpMethod="POST")
err = exc.value.response["Error"]
err["Code"].should.equal("NotFoundException")
# We just have a GET-method, as defined in the JSON
client.get_method(restApiId=api_id, resourceId=new_root_id, httpMethod="GET")
@mock_apigateway
def test_put_rest_api__existing_methods_still_exist():
client = boto3.client("apigateway", region_name="us-east-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="POST",
authorizationType="none",
)
path = os.path.dirname(os.path.abspath(__file__))
with open(path + "/resources/test_api.json", "rb") as api_json:
client.put_rest_api(
restApiId=api_id,
mode="merge",
failOnWarnings=True,
body=api_json.read(),
)
response = client.get_method(
restApiId=api_id, resourceId=root_id, httpMethod="POST"
)
response.should.have.key("httpMethod").equals("POST")
@mock_apigateway
def test_put_rest_api__fail_on_invalid_spec():
client = boto3.client("apigateway", region_name="us-east-2")
response = client.create_rest_api(name="my_api", description="this is my api")
api_id = response["id"]
path = os.path.dirname(os.path.abspath(__file__))
with open(path + "/resources/test_api_invalid.json", "rb") as api_json:
with pytest.raises(ClientError) as exc:
client.put_rest_api(
restApiId=api_id, failOnWarnings=True, body=api_json.read()
)
err = exc.value.response["Error"]
err["Code"].should.equal("BadRequestException")
err["Message"].should.equal(
"Failed to parse the uploaded OpenAPI document due to: 'paths' is a required property"
)
@mock_apigateway
def test_put_rest_api__fail_on_invalid_version():
client = boto3.client("apigateway", region_name="us-east-2")
response = client.create_rest_api(name="my_api", description="this is my api")
api_id = response["id"]
path = os.path.dirname(os.path.abspath(__file__))
with open(path + "/resources/test_api_invalid_version.json", "rb") as api_json:
with pytest.raises(ClientError) as exc:
client.put_rest_api(
restApiId=api_id, failOnWarnings=True, body=api_json.read()
)
err = exc.value.response["Error"]
err["Code"].should.equal("BadRequestException")
err["Message"].should.equal("Only OpenAPI 3.x.x are currently supported")
@mock_apigateway
def test_put_rest_api__fail_on_invalid_mode():
client = boto3.client("apigateway", region_name="us-east-2")
response = client.create_rest_api(name="my_api", description="this is my api")
api_id = response["id"]
path = os.path.dirname(os.path.abspath(__file__))
with open(path + "/resources/test_api.json", "rb") as api_json:
with pytest.raises(ClientError) as exc:
client.put_rest_api(restApiId=api_id, mode="unknown", body=api_json.read())
err = exc.value.response["Error"]
err["Code"].should.equal("BadRequestException")
err["Message"].should.equal(
'Enumeration value of OpenAPI import mode must be "overwrite" or "merge"'
)
@mock_apigateway
def test_put_rest_api__as_yaml():
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"]
path = os.path.dirname(os.path.abspath(__file__))
with open(path + "/resources/test_api.yaml", "rb") as api_yaml:
response = client.put_rest_api(
restApiId=api_id,
mode="overwrite",
failOnWarnings=True,
body=api_yaml.read(),
)
response.should.have.key("id").which.should.equal(api_id)
response.should.have.key("name").which.should.equal("my_api")
response.should.have.key("description").which.should.equal("this is my api")