From 1aaafc5f070ce98042f932d95a5f887a035f709c Mon Sep 17 00:00:00 2001 From: dominictootell Date: Tue, 13 Sep 2016 12:44:17 +0100 Subject: [PATCH 1/4] added more api gateway coverage added: get_stages, stage not found exception, update stage configuration, descriptions on deployments, setting stage variables on deployments and stage creating --- moto/apigateway/exceptions.py | 12 ++ moto/apigateway/models.py | 152 ++++++++++++-- moto/apigateway/responses.py | 40 +++- moto/apigateway/urls.py | 1 + tests/test_apigateway/test_apigateway.py | 243 ++++++++++++++++++++++- 5 files changed, 428 insertions(+), 20 deletions(-) create mode 100644 moto/apigateway/exceptions.py diff --git a/moto/apigateway/exceptions.py b/moto/apigateway/exceptions.py new file mode 100644 index 000000000..ed007d61c --- /dev/null +++ b/moto/apigateway/exceptions.py @@ -0,0 +1,12 @@ +from __future__ import unicode_literals +from moto.core.exceptions import RESTError + + +class StageNonFoundException(RESTError): + code = 404 + def __init__(self): + super(StageNonFoundException, self).__init__( + "NonFoundException", "Invalid stage identifier specified") + + + diff --git a/moto/apigateway/models.py b/moto/apigateway/models.py index d20e01d2c..ff8af520b 100644 --- a/moto/apigateway/models.py +++ b/moto/apigateway/models.py @@ -7,15 +7,18 @@ import requests from moto.core import BaseBackend from moto.core.utils import iso_8601_datetime_with_milliseconds from .utils import create_id +from .exceptions import StageNonFoundException STAGE_URL = "https://{api_id}.execute-api.{region_name}.amazonaws.com/{stage_name}" class Deployment(dict): - def __init__(self, deployment_id, name): + def __init__(self, deployment_id, name, description=""): super(Deployment, self).__init__() self['id'] = deployment_id self['stageName'] = name + self['description'] = description + self['createdDate'] = iso_8601_datetime_with_milliseconds(datetime.datetime.now()) class IntegrationResponse(dict): @@ -150,24 +153,124 @@ class Resource(object): class Stage(dict): - def __init__(self, name=None, deployment_id=None): + + + def __init__(self, name=None, deployment_id=None, variables={}): super(Stage, self).__init__() self['stageName'] = name self['deploymentId'] = deployment_id self['methodSettings'] = {} - self['variables'] = {} + self['variables'] = variables self['description'] = '' + self['cacheClusterEnabled'] = False + self['cacheClusterSize'] = 0.5 def apply_operations(self, patch_operations): for op in patch_operations: - if op['op'] == 'replace': - # TODO: match the path against the values hash + if 'variables/' in op['path']: + self._apply_operation_to_variables(op) + elif '/cacheClusterEnabled' in op['path']: + self['cacheClusterEnabled'] = self._str2bool(op['value']) + elif '/cacheClusterSize' in op['path']: + self['cacheClusterSize'] = float(op['value']) + elif '/description' in op['path']: + self['description'] = op['value'] + elif '/deploymentId' in op['path']: + self['deploymentId'] = op['value'] + elif op['op'] == 'replace': + # Method Settings drop into here # (e.g., path could be '/*/*/logging/loglevel') - self[op['path']] = op['value'] + split_path = op['path'].split('/',3) + if len(split_path)!=4: + continue + self._patch_method_setting('/'.join(split_path[1:3]),split_path[3],op['value']) else: raise Exception('Patch operation "%s" not implemented' % op['op']) return self + def _patch_method_setting(self,resource_path_and_method,key,value): + updated_key = self._method_settings_translations(key) + if updated_key is not None: + if resource_path_and_method not in self['methodSettings']: + self['methodSettings'][resource_path_and_method] = self._get_default_method_settings() + self['methodSettings'][resource_path_and_method][updated_key] = self._convert_to_type(updated_key,value) + + + def _get_default_method_settings(self): + return { + "throttlingRateLimit": 1000.0, + "dataTraceEnabled": False, + "metricsEnabled": False, + "unauthorizedCacheControlHeaderStrategy": "SUCCEED_WITH_RESPONSE_HEADER", + "cacheTtlInSeconds": 300, + "cacheDataEncrypted": True, + "cachingEnabled": False, + "throttlingBurstLimit": 2000, + "requireAuthorizationForCacheControl": True + } + + def _method_settings_translations(self,key): + mappings = { + 'metrics/enabled' :'metricsEnabled', + 'logging/loglevel' : 'loggingLevel', + 'logging/dataTrace' : 'dataTraceEnabled' , + 'throttling/burstLimit' : 'throttlingBurstLimit', + 'throttling/rateLimit' : 'throttlingRateLimit', + 'caching/enabled' : 'cachingEnabled', + 'caching/ttlInSeconds' : 'cacheTtlInSeconds', + 'caching/dataEncrypted' : 'cacheDataEncrypted', + 'caching/requireAuthorizationForCacheControl' : 'requireAuthorizationForCacheControl', + 'caching/unauthorizedCacheControlHeaderStrategy' : 'unauthorizedCacheControlHeaderStrategy' + } + + if key in mappings: + return mappings[key] + else: + None + + def _str2bool(self,v): + return v.lower() == "true" + + def _convert_to_type(self,key,val): + type_mappings = { + 'metricsEnabled' : 'bool', + 'loggingLevel' : 'str', + 'dataTraceEnabled' : 'bool', + 'throttlingBurstLimit' : 'int', + 'throttlingRateLimit' : 'float', + 'cachingEnabled' : 'bool', + 'cacheTtlInSeconds' : 'int', + 'cacheDataEncrypted' : 'bool', + 'requireAuthorizationForCacheControl' :'bool', + 'unauthorizedCacheControlHeaderStrategy' : 'str' + } + + if key in type_mappings: + type_value = type_mappings[key] + + if type_value == 'bool': + return self._str2bool(val) + elif type_value == 'int': + return int(val) + elif type_value == 'float': + return float(val) + else: + return str(val) + else: + return str(val) + + + + def _apply_operation_to_variables(self,op): + key = op['path'][op['path'].rindex("variables/")+10:] + if op['op'] == 'remove': + self['variables'].pop(key, None) + elif op['op'] == 'replace': + self['variables'][key] = op['value'] + else: + raise Exception('Patch operation "%s" not implemented' % op['op']) + + class RestAPI(object): def __init__(self, id, region_name, name, description): @@ -219,12 +322,17 @@ class RestAPI(object): 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.stages[name] = Stage(name=name, deployment_id=deployment_id) + def create_stage(self, name, deployment_id,variables={}): + stage = Stage(name=name, deployment_id=deployment_id,variables=variables) + self.stages[name] = stage + self.update_integration_mocks(name) + return stage + def create_deployment(self, name, description="",stage_variables={}): + deployment_id = create_id() + deployment = Deployment(deployment_id, name, description) + self.deployments[deployment_id] = deployment + self.stages[name] = Stage(name=name, deployment_id=deployment_id,variables=stage_variables) self.update_integration_mocks(name) return deployment @@ -232,6 +340,9 @@ class RestAPI(object): def get_deployment(self, deployment_id): return self.deployments[deployment_id] + def get_stages(self): + return list(self.stages.values()) + def get_deployments(self): return list(self.deployments.values()) @@ -300,6 +411,21 @@ class APIGatewayBackend(BaseBackend): def get_stage(self, function_id, stage_name): api = self.get_rest_api(function_id) + stage = api.stages.get(stage_name) + if stage is None: + raise StageNonFoundException() + else: + return stage + + + def get_stages(self, function_id): + api = self.get_rest_api(function_id) + return api.get_stages() + + + def create_stage(self, function_id, stage_name, deploymentId,variables={}): + api = self.get_rest_api(function_id) + api.create_stage(stage_name,deploymentId,variables) return api.stages.get(stage_name) def update_stage(self, function_id, stage_name, patch_operations): @@ -354,9 +480,9 @@ class APIGatewayBackend(BaseBackend): integration_response = integration.delete_integration_response(status_code) return integration_response - def create_deployment(self, function_id, name): + def create_deployment(self, function_id, name, description ="", stage_variables={}): api = self.get_rest_api(function_id) - deployment = api.create_deployment(name) + deployment = api.create_deployment(name, description,stage_variables) return deployment def get_deployment(self, function_id, deployment_id): diff --git a/moto/apigateway/responses.py b/moto/apigateway/responses.py index 89714eec9..76183a5d0 100644 --- a/moto/apigateway/responses.py +++ b/moto/apigateway/responses.py @@ -4,6 +4,7 @@ import json from moto.core.responses import BaseResponse from .models import apigateway_backends +from .exceptions import StageNonFoundException class APIGatewayResponse(BaseResponse): @@ -11,6 +12,15 @@ class APIGatewayResponse(BaseResponse): def _get_param(self, key): return json.loads(self.body.decode("ascii")).get(key) + + def _get_param_with_default_value(self, key, default): + jsonbody = json.loads(self.body.decode("ascii")) + + if key in jsonbody: + return jsonbody.get(key) + else: + return default + @property def backend(self): return apigateway_backends[self.region] @@ -95,6 +105,23 @@ class APIGatewayResponse(BaseResponse): method_response = self.backend.delete_method_response(function_id, resource_id, method_type, response_code) return 200, headers, json.dumps(method_response) + def restapis_stages(self, request, full_url, headers): + self.setup_class(request, full_url, headers) + url_path_parts = self.path.split("/") + function_id = url_path_parts[2] + + if self.method == 'POST': + stage_name = self._get_param("stageName") + deployment_id = self._get_param("deploymentId") + stage_variables = self._get_param_with_default_value('variables',{}) + + stage_response = self.backend.create_stage(function_id, stage_name, deployment_id,variables=stage_variables) + elif self.method == 'GET': + stages = self.backend.get_stages(function_id) + return 200, headers, json.dumps({"item": stages}) + + return 200, headers, json.dumps(stage_response) + def stages(self, request, full_url, headers): self.setup_class(request, full_url, headers) url_path_parts = self.path.split("/") @@ -102,10 +129,13 @@ class APIGatewayResponse(BaseResponse): stage_name = url_path_parts[4] if self.method == 'GET': - stage_response = self.backend.get_stage(function_id, stage_name) + try: + stage_response = self.backend.get_stage(function_id, stage_name) + except StageNonFoundException as error: + return error.code, headers,'{{"message":"{0}","code":"{1}"}}'.format(error.message,error.error_type) elif self.method == 'PATCH': - path_operations = self._get_param('patchOperations') - stage_response = self.backend.update_stage(function_id, stage_name, path_operations) + patch_operations = self._get_param('patchOperations') + stage_response = self.backend.update_stage(function_id, stage_name, patch_operations) return 200, headers, json.dumps(stage_response) def integrations(self, request, full_url, headers): @@ -158,7 +188,9 @@ class APIGatewayResponse(BaseResponse): return 200, headers, json.dumps({"item": deployments}) elif self.method == 'POST': name = self._get_param("stageName") - deployment = self.backend.create_deployment(function_id, name) + description = self._get_param_with_default_value("description","") + stage_variables = self._get_param_with_default_value('variables',{}) + deployment = self.backend.create_deployment(function_id, name, description,stage_variables) return 200, headers, json.dumps(deployment) def individual_deployment(self, request, full_url, headers): diff --git a/moto/apigateway/urls.py b/moto/apigateway/urls.py index d302a83f3..5637699e0 100644 --- a/moto/apigateway/urls.py +++ b/moto/apigateway/urls.py @@ -9,6 +9,7 @@ url_paths = { '{0}/restapis$': APIGatewayResponse().restapis, '{0}/restapis/(?P[^/]+)/?$': APIGatewayResponse().restapis_individual, '{0}/restapis/(?P[^/]+)/resources$': APIGatewayResponse().resources, + '{0}/restapis/(?P[^/]+)/stages$': APIGatewayResponse().restapis_stages, '{0}/restapis/(?P[^/]+)/stages/(?P[^/]+)/?$': APIGatewayResponse().stages, '{0}/restapis/(?P[^/]+)/deployments$': APIGatewayResponse().deployments, '{0}/restapis/(?P[^/]+)/deployments/(?P[^/]+)/?$': APIGatewayResponse().individual_deployment, diff --git a/tests/test_apigateway/test_apigateway.py b/tests/test_apigateway/test_apigateway.py index 44c33b7a6..200cf5ff4 100644 --- a/tests/test_apigateway/test_apigateway.py +++ b/tests/test_apigateway/test_apigateway.py @@ -1,5 +1,6 @@ from __future__ import unicode_literals + from datetime import datetime from dateutil.tz import tzutc import boto3 @@ -7,6 +8,7 @@ from freezegun import freeze_time import httpretty import requests import sure # noqa +from botocore.exceptions import ClientError from moto import mock_apigateway @@ -360,6 +362,7 @@ def test_integrations(): uri=test_uri, requestTemplates=templates ) + response['ResponseMetadata'].pop('HTTPHeaders', None) # this is hard to match against, so remove it response['ResponseMetadata'].should.equal({'HTTPStatusCode': 200}) response = client.get_integration( @@ -469,6 +472,236 @@ def test_integration_response(): response['methodIntegration']['integrationResponses'].should.equal({}) +@mock_apigateway +def test_update_stage_configuration(): + client = boto3.client('apigateway', region_name='us-west-2') + stage_name = 'staging' + 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=stage_name, + description="1.0.1" + ) + deployment_id = response['id'] + + response = client.get_deployment( + restApiId=api_id, + deploymentId=deployment_id, + ) + response.pop('createdDate',None) # createdDate is hard to match against, remove it + response['ResponseMetadata'].pop('HTTPHeaders', None) # this is hard to match against, so remove it + response.should.equal({ + 'id': deployment_id, + 'ResponseMetadata': {'HTTPStatusCode': 200}, + 'description' : '1.0.1' + }) + + response = client.create_deployment( + restApiId=api_id, + stageName=stage_name, + description="1.0.2" + ) + deployment_id2 = response['id'] + + + stage = client.get_stage( + restApiId=api_id, + stageName=stage_name + ) + stage['stageName'].should.equal(stage_name) + stage['deploymentId'].should.equal(deployment_id2) + + client.update_stage(restApiId=api_id,stageName=stage_name, + patchOperations=[ + { + "op" : "replace", + "path" : "/deploymentId", + "value": deployment_id + }, + { + "op" : "replace", + "path" : "/variables/environment", + "value" : "dev" + }, + { + "op" : "replace", + "path" : "/variables/region", + "value" : "eu-west-1" + }, + { + "op" : "replace", + "path" : "/*/*/caching/dataEncrypted", + "value" : "True" + }, + { + "op" : "replace", + "path" : "/cacheClusterEnabled", + "value" : "True" + }, + { + "op" : "replace", + "path" : "/description", + "value" : "stage description update" + }, + { + "op" : "replace", + "path" : "/cacheClusterSize", + "value" : "1.6" + } + ]) + + client.update_stage(restApiId=api_id,stageName=stage_name, + patchOperations=[ + { + "op" : "remove", + "path" : "/variables/region", + "value" : "eu-west-1" + } + ]) + + stage = client.get_stage(restApiId=api_id,stageName=stage_name) + + stage['description'].should.match('stage description update') + stage['cacheClusterSize'].should.equal(1.6) + stage['variables']['environment'].should.match('dev') + stage['variables'].should_not.have.key('region') + stage['cacheClusterEnabled'].should.be.true + stage['deploymentId'].should.match(deployment_id) + stage['methodSettings'].should.have.key('*/*') + stage['methodSettings']['*/*'].should.have.key('cacheDataEncrypted').which.should.be.true + + try: + client.update_stage(restApiId=api_id,stageName=stage_name, + patchOperations=[ + { + "op" : "add", + "path" : "/notasetting", + "value" : "eu-west-1" + } + ]) + assert False.should.be.ok #Fail, should not be here + except Exception: + assert True.should.be.ok + +@mock_apigateway +def test_non_existent_stage(): + 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.get_stage.when.called_with(restApiId=api_id,stageName='xxx').should.throw(ClientError) + + + +@mock_apigateway +def test_create_stage(): + client = boto3.client('apigateway', region_name='us-west-2') + stage_name = 'staging' + 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=stage_name, + ) + deployment_id = response['id'] + + response = client.get_deployment( + restApiId=api_id, + deploymentId=deployment_id, + ) + response.pop('createdDate',None) # createdDate is hard to match against, remove it + response['ResponseMetadata'].pop('HTTPHeaders', None) # this is hard to match against, so remove it + response.should.equal({ + 'id': deployment_id, + 'ResponseMetadata': {'HTTPStatusCode': 200}, + 'description' : '' + }) + + response = client.create_deployment( + restApiId=api_id, + stageName=stage_name, + ) + + deployment_id2 = response['id'] + + + response = client.get_deployments( + restApiId=api_id, + ) + + response['ResponseMetadata'].pop('HTTPHeaders', None) # this is hard to match against, so remove it + + response['items'][0].pop('createdDate') + response['items'][1].pop('createdDate') + response['items'][0]['id'].should.match(r"{0}|{1}".format(deployment_id2,deployment_id)) + response['items'][1]['id'].should.match(r"{0}|{1}".format(deployment_id2,deployment_id)) + + + new_stage_name = 'current' + response = client.create_stage(restApiId=api_id,stageName=new_stage_name,deploymentId=deployment_id2) + + response['ResponseMetadata'].pop('HTTPHeaders', None) # this is hard to match against, so remove it + + response.should.equal({ + 'stageName':new_stage_name, + 'deploymentId':deployment_id2, + 'methodSettings':{}, + 'variables':{}, + 'ResponseMetadata': {'HTTPStatusCode': 200}, + 'description':'', + 'cacheClusterSize':0.5, + 'cacheClusterEnabled':False + }) + + new_stage_name_with_vars = 'stage_with_vars' + response = client.create_stage(restApiId=api_id,stageName=new_stage_name_with_vars,deploymentId=deployment_id2,variables={ + "env" : "dev" + }) + + response['ResponseMetadata'].pop('HTTPHeaders', None) # this is hard to match against, so remove it + + response.should.equal({ + 'stageName':new_stage_name_with_vars, + 'deploymentId':deployment_id2, + 'methodSettings':{}, + 'variables':{ "env" : "dev" }, + 'ResponseMetadata': {'HTTPStatusCode': 200}, + 'description':'', + 'cacheClusterSize':0.5, + 'cacheClusterEnabled':False + }) + + stage = client.get_stage( + restApiId=api_id, + stageName=new_stage_name + ) + stage['stageName'].should.equal(new_stage_name) + stage['deploymentId'].should.equal(deployment_id2) + + stage = client.get_stage( + restApiId=api_id, + stageName=new_stage_name_with_vars + ) + stage['stageName'].should.equal(new_stage_name_with_vars) + stage['deploymentId'].should.equal(deployment_id2) + stage['variables'].should.have.key('env').which.should.match("dev") + + + + @mock_apigateway def test_deployment(): client = boto3.client('apigateway', region_name='us-west-2') @@ -489,17 +722,21 @@ def test_deployment(): restApiId=api_id, deploymentId=deployment_id, ) + response.pop('createdDate',None) # createdDate is hard to match against, remove it response['ResponseMetadata'].pop('HTTPHeaders', None) # this is hard to match against, so remove it response.should.equal({ 'id': deployment_id, - 'ResponseMetadata': {'HTTPStatusCode': 200} + 'ResponseMetadata': {'HTTPStatusCode': 200}, + 'description' : '' }) response = client.get_deployments( restApiId=api_id, ) + + response['items'][0].pop('createdDate') response['items'].should.equal([ - {'id': deployment_id} + {'id': deployment_id, 'description': ''} ]) response = client.delete_deployment( @@ -527,7 +764,7 @@ def test_deployment(): patchOperations=[ { 'op': 'replace', - 'path': 'description', + 'path': '/description', 'value': '_new_description_' }, ] From f1454ee2806df45c3c20689e3d73c99c26af982b Mon Sep 17 00:00:00 2001 From: dominictootell Date: Wed, 14 Sep 2016 07:37:02 +0100 Subject: [PATCH 2/4] fix mutable default variables --- moto/apigateway/models.py | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/moto/apigateway/models.py b/moto/apigateway/models.py index ff8af520b..9166519f6 100644 --- a/moto/apigateway/models.py +++ b/moto/apigateway/models.py @@ -155,8 +155,10 @@ class Resource(object): class Stage(dict): - def __init__(self, name=None, deployment_id=None, variables={}): + def __init__(self, name=None, deployment_id=None, variables=None): super(Stage, self).__init__() + if variables is None: + variables = {} self['stageName'] = name self['deploymentId'] = deployment_id self['methodSettings'] = {} @@ -322,13 +324,17 @@ class RestAPI(object): for method in httpretty.httpretty.METHODS: httpretty.register_uri(method, stage_url, body=self.resource_callback) - def create_stage(self, name, deployment_id,variables={}): + def create_stage(self, name, deployment_id,variables=None): + if variables is None: + variables = {} stage = Stage(name=name, deployment_id=deployment_id,variables=variables) self.stages[name] = stage self.update_integration_mocks(name) return stage - def create_deployment(self, name, description="",stage_variables={}): + def create_deployment(self, name, description="",stage_variables=None): + if stage_variables is None: + stage_variables = {} deployment_id = create_id() deployment = Deployment(deployment_id, name, description) self.deployments[deployment_id] = deployment @@ -423,7 +429,9 @@ class APIGatewayBackend(BaseBackend): return api.get_stages() - def create_stage(self, function_id, stage_name, deploymentId,variables={}): + def create_stage(self, function_id, stage_name, deploymentId,variables=None): + if variables is None: + variables = {} api = self.get_rest_api(function_id) api.create_stage(stage_name,deploymentId,variables) return api.stages.get(stage_name) @@ -480,7 +488,9 @@ class APIGatewayBackend(BaseBackend): integration_response = integration.delete_integration_response(status_code) return integration_response - def create_deployment(self, function_id, name, description ="", stage_variables={}): + def create_deployment(self, function_id, name, description ="", stage_variables=None): + if stage_variables is None: + stage_variables = {} api = self.get_rest_api(function_id) deployment = api.create_deployment(name, description,stage_variables) return deployment From dd85f35f5a3e54fddffbd16c0953a83440f91d2e Mon Sep 17 00:00:00 2001 From: dominictootell Date: Wed, 14 Sep 2016 10:04:51 +0100 Subject: [PATCH 3/4] don't set cacheClusterSize if cache not enabled --- moto/apigateway/models.py | 28 ++++-- moto/apigateway/responses.py | 7 +- tests/test_apigateway/test_apigateway.py | 105 ++++++++++++++++++++--- 3 files changed, 119 insertions(+), 21 deletions(-) diff --git a/moto/apigateway/models.py b/moto/apigateway/models.py index 9166519f6..90e5b0bd8 100644 --- a/moto/apigateway/models.py +++ b/moto/apigateway/models.py @@ -155,7 +155,8 @@ class Resource(object): class Stage(dict): - def __init__(self, name=None, deployment_id=None, variables=None): + def __init__(self, name=None, deployment_id=None, variables=None, + description='',cacheClusterEnabled=False,cacheClusterSize=None): super(Stage, self).__init__() if variables is None: variables = {} @@ -163,9 +164,13 @@ class Stage(dict): self['deploymentId'] = deployment_id self['methodSettings'] = {} self['variables'] = variables - self['description'] = '' - self['cacheClusterEnabled'] = False - self['cacheClusterSize'] = 0.5 + self['description'] = description + self['cacheClusterEnabled'] = cacheClusterEnabled + if self['cacheClusterEnabled']: + self['cacheClusterSize'] = str(0.5) + + if cacheClusterSize is not None: + self['cacheClusterSize'] = str(cacheClusterSize) def apply_operations(self, patch_operations): for op in patch_operations: @@ -173,8 +178,10 @@ class Stage(dict): self._apply_operation_to_variables(op) elif '/cacheClusterEnabled' in op['path']: self['cacheClusterEnabled'] = self._str2bool(op['value']) + if 'cacheClusterSize' not in self and self['cacheClusterEnabled']: + self['cacheClusterSize'] = str(0.5) elif '/cacheClusterSize' in op['path']: - self['cacheClusterSize'] = float(op['value']) + self['cacheClusterSize'] = str(float(op['value'])) elif '/description' in op['path']: self['description'] = op['value'] elif '/deploymentId' in op['path']: @@ -324,10 +331,11 @@ class RestAPI(object): for method in httpretty.httpretty.METHODS: httpretty.register_uri(method, stage_url, body=self.resource_callback) - def create_stage(self, name, deployment_id,variables=None): + def create_stage(self, name, deployment_id,variables=None,description='',cacheClusterEnabled=None,cacheClusterSize=None): if variables is None: variables = {} - stage = Stage(name=name, deployment_id=deployment_id,variables=variables) + stage = Stage(name=name, deployment_id=deployment_id,variables=variables, + description=description,cacheClusterSize=cacheClusterSize,cacheClusterEnabled=cacheClusterEnabled) self.stages[name] = stage self.update_integration_mocks(name) return stage @@ -429,11 +437,13 @@ class APIGatewayBackend(BaseBackend): return api.get_stages() - def create_stage(self, function_id, stage_name, deploymentId,variables=None): + def create_stage(self, function_id, stage_name, deploymentId, + variables=None,description='',cacheClusterEnabled=None,cacheClusterSize=None): if variables is None: variables = {} api = self.get_rest_api(function_id) - api.create_stage(stage_name,deploymentId,variables) + api.create_stage(stage_name,deploymentId,variables=variables, + description=description,cacheClusterEnabled=cacheClusterEnabled,cacheClusterSize=cacheClusterSize) return api.stages.get(stage_name) def update_stage(self, function_id, stage_name, patch_operations): diff --git a/moto/apigateway/responses.py b/moto/apigateway/responses.py index 76183a5d0..e95d2887d 100644 --- a/moto/apigateway/responses.py +++ b/moto/apigateway/responses.py @@ -114,8 +114,13 @@ class APIGatewayResponse(BaseResponse): stage_name = self._get_param("stageName") deployment_id = self._get_param("deploymentId") stage_variables = self._get_param_with_default_value('variables',{}) + description = self._get_param_with_default_value('description','') + cacheClusterEnabled = self._get_param_with_default_value('cacheClusterEnabled',False) + cacheClusterSize = self._get_param_with_default_value('cacheClusterSize',None) - stage_response = self.backend.create_stage(function_id, stage_name, deployment_id,variables=stage_variables) + stage_response = self.backend.create_stage(function_id, stage_name, deployment_id, + variables=stage_variables, description=description, + cacheClusterEnabled=cacheClusterEnabled, cacheClusterSize=cacheClusterSize) elif self.method == 'GET': stages = self.backend.get_stages(function_id) return 200, headers, json.dumps({"item": stages}) diff --git a/tests/test_apigateway/test_apigateway.py b/tests/test_apigateway/test_apigateway.py index 200cf5ff4..b60f41e49 100644 --- a/tests/test_apigateway/test_apigateway.py +++ b/tests/test_apigateway/test_apigateway.py @@ -515,6 +515,40 @@ def test_update_stage_configuration(): ) stage['stageName'].should.equal(stage_name) stage['deploymentId'].should.equal(deployment_id2) + stage.shouldnt.have.key('cacheClusterSize') + + client.update_stage(restApiId=api_id,stageName=stage_name, + patchOperations=[ + { + "op" : "replace", + "path" : "/cacheClusterEnabled", + "value": "True" + } + ]) + + stage = client.get_stage( + restApiId=api_id, + stageName=stage_name + ) + + stage.should.have.key('cacheClusterSize').which.should.equal("0.5") + + client.update_stage(restApiId=api_id,stageName=stage_name, + patchOperations=[ + { + "op" : "replace", + "path" : "/cacheClusterSize", + "value": "1.6" + } + ]) + + stage = client.get_stage( + restApiId=api_id, + stageName=stage_name + ) + + stage.should.have.key('cacheClusterSize').which.should.equal("1.6") + client.update_stage(restApiId=api_id,stageName=stage_name, patchOperations=[ @@ -567,7 +601,7 @@ def test_update_stage_configuration(): stage = client.get_stage(restApiId=api_id,stageName=stage_name) stage['description'].should.match('stage description update') - stage['cacheClusterSize'].should.equal(1.6) + stage['cacheClusterSize'].should.equal("1.6") stage['variables']['environment'].should.match('dev') stage['variables'].should_not.have.key('region') stage['cacheClusterEnabled'].should.be.true @@ -662,10 +696,16 @@ def test_create_stage(): 'variables':{}, 'ResponseMetadata': {'HTTPStatusCode': 200}, 'description':'', - 'cacheClusterSize':0.5, 'cacheClusterEnabled':False }) + stage = client.get_stage( + restApiId=api_id, + stageName=new_stage_name + ) + stage['stageName'].should.equal(new_stage_name) + stage['deploymentId'].should.equal(deployment_id2) + new_stage_name_with_vars = 'stage_with_vars' response = client.create_stage(restApiId=api_id,stageName=new_stage_name_with_vars,deploymentId=deployment_id2,variables={ "env" : "dev" @@ -680,17 +720,9 @@ def test_create_stage(): 'variables':{ "env" : "dev" }, 'ResponseMetadata': {'HTTPStatusCode': 200}, 'description':'', - 'cacheClusterSize':0.5, - 'cacheClusterEnabled':False + 'cacheClusterEnabled': False }) - stage = client.get_stage( - restApiId=api_id, - stageName=new_stage_name - ) - stage['stageName'].should.equal(new_stage_name) - stage['deploymentId'].should.equal(deployment_id2) - stage = client.get_stage( restApiId=api_id, stageName=new_stage_name_with_vars @@ -699,6 +731,57 @@ def test_create_stage(): stage['deploymentId'].should.equal(deployment_id2) stage['variables'].should.have.key('env').which.should.match("dev") + new_stage_name = 'stage_with_vars_and_cache_settings' + response = client.create_stage(restApiId=api_id,stageName=new_stage_name,deploymentId=deployment_id2,variables={ + "env" : "dev" + }, cacheClusterEnabled=True,description="hello moto") + + response['ResponseMetadata'].pop('HTTPHeaders', None) # this is hard to match against, so remove it + + response.should.equal({ + 'stageName':new_stage_name, + 'deploymentId':deployment_id2, + 'methodSettings':{}, + 'variables':{ "env" : "dev" }, + 'ResponseMetadata': {'HTTPStatusCode': 200}, + 'description':'hello moto', + 'cacheClusterEnabled': True, + 'cacheClusterSize' : "0.5" + }) + + stage = client.get_stage( + restApiId=api_id, + stageName=new_stage_name + ) + + stage['cacheClusterSize'].should.equal("0.5") + + new_stage_name = 'stage_with_vars_and_cache_settings_and_size' + response = client.create_stage(restApiId=api_id,stageName=new_stage_name,deploymentId=deployment_id2,variables={ + "env" : "dev" + }, cacheClusterEnabled=True,cacheClusterSize="1.6",description="hello moto") + + response['ResponseMetadata'].pop('HTTPHeaders', None) # this is hard to match against, so remove it + + response.should.equal({ + 'stageName':new_stage_name, + 'deploymentId':deployment_id2, + 'methodSettings':{}, + 'variables':{ "env" : "dev" }, + 'ResponseMetadata': {'HTTPStatusCode': 200}, + 'description':'hello moto', + 'cacheClusterEnabled': True, + 'cacheClusterSize' : "1.6" + }) + + stage = client.get_stage( + restApiId=api_id, + stageName=new_stage_name + ) + stage['stageName'].should.equal(new_stage_name) + stage['deploymentId'].should.equal(deployment_id2) + stage['variables'].should.have.key('env').which.should.match("dev") + stage['cacheClusterSize'].should.equal("1.6") From 9e73d10e591bcf81805582246d59eb9f51ce865e Mon Sep 17 00:00:00 2001 From: dominictootell Date: Wed, 14 Sep 2016 11:52:56 +0100 Subject: [PATCH 4/4] Not, not Non for the exception. --- moto/apigateway/exceptions.py | 6 +++--- moto/apigateway/models.py | 4 ++-- moto/apigateway/responses.py | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/moto/apigateway/exceptions.py b/moto/apigateway/exceptions.py index ed007d61c..77a1c932a 100644 --- a/moto/apigateway/exceptions.py +++ b/moto/apigateway/exceptions.py @@ -2,11 +2,11 @@ from __future__ import unicode_literals from moto.core.exceptions import RESTError -class StageNonFoundException(RESTError): +class StageNotFoundException(RESTError): code = 404 def __init__(self): - super(StageNonFoundException, self).__init__( - "NonFoundException", "Invalid stage identifier specified") + super(StageNotFoundException, self).__init__( + "NotFoundException", "Invalid stage identifier specified") diff --git a/moto/apigateway/models.py b/moto/apigateway/models.py index 90e5b0bd8..11d650e05 100644 --- a/moto/apigateway/models.py +++ b/moto/apigateway/models.py @@ -7,7 +7,7 @@ import requests from moto.core import BaseBackend from moto.core.utils import iso_8601_datetime_with_milliseconds from .utils import create_id -from .exceptions import StageNonFoundException +from .exceptions import StageNotFoundException STAGE_URL = "https://{api_id}.execute-api.{region_name}.amazonaws.com/{stage_name}" @@ -427,7 +427,7 @@ class APIGatewayBackend(BaseBackend): api = self.get_rest_api(function_id) stage = api.stages.get(stage_name) if stage is None: - raise StageNonFoundException() + raise StageNotFoundException() else: return stage diff --git a/moto/apigateway/responses.py b/moto/apigateway/responses.py index e95d2887d..e8c353f4e 100644 --- a/moto/apigateway/responses.py +++ b/moto/apigateway/responses.py @@ -4,7 +4,7 @@ import json from moto.core.responses import BaseResponse from .models import apigateway_backends -from .exceptions import StageNonFoundException +from .exceptions import StageNotFoundException class APIGatewayResponse(BaseResponse): @@ -136,7 +136,7 @@ class APIGatewayResponse(BaseResponse): if self.method == 'GET': try: stage_response = self.backend.get_stage(function_id, stage_name) - except StageNonFoundException as error: + except StageNotFoundException as error: return error.code, headers,'{{"message":"{0}","code":"{1}"}}'.format(error.message,error.error_type) elif self.method == 'PATCH': patch_operations = self._get_param('patchOperations')