Added explicit exception raise when no stack found. (#3559)

* Added explicit exception raise when no stack found.

Currently, any operation that uses 'get_stack' method from 'CloudFormationBackend' class
will fail with AttributeError or jinja2 exception if ran against non-existing stack(created/deleted)
To fix the issue I explicitly raised a 'ValidationError' exception.
Added tests for boto and boto3 responses.

* Moved non-existing stack tests to 'test_stack_events'

When using 'update_stack' to test raising an exception when the stack doesn't exist
test coverage dropped by 0.5%. I am using stack_events instead.

* Removed some unreachable paths

After adding the exception couple of paths in the code are unreachable as 'get_stack' doesn't return 'None' anymore.
This is the reason why coverall was reporting decreased coverage.

* Removed an unreachable path I missed

* Added couple of tests in cloudformation/models

* Added more assertions around raised exception

* Formatted document using black to fix issue with travis.
This commit is contained in:
Modhaffer Rahmani 2021-01-16 15:42:51 +01:00 committed by GitHub
parent 628c026a07
commit 8fe5a680a5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 46 additions and 7 deletions

View File

@ -707,6 +707,7 @@ class CloudFormationBackend(BaseBackend):
for stack in self.stacks.values():
if stack.name == name_or_stack_id:
return stack
raise ValidationError(name_or_stack_id)
def update_stack(self, name, template, role_arn=None, parameters=None, tags=None):
stack = self.get_stack(name)
@ -715,8 +716,6 @@ class CloudFormationBackend(BaseBackend):
def list_stack_resources(self, stack_name_or_id):
stack = self.get_stack(stack_name_or_id)
if stack is None:
return None
return stack.stack_resources
def delete_stack(self, name_or_stack_id):

View File

@ -232,8 +232,6 @@ class CloudFormationResponse(BaseResponse):
if stack_resource.logical_resource_id == logical_resource_id:
resource = stack_resource
break
else:
raise ValidationError(logical_resource_id)
template = self.response_template(DESCRIBE_STACK_RESOURCE_RESPONSE_TEMPLATE)
return template.render(stack=stack, resource=resource)
@ -267,9 +265,6 @@ class CloudFormationResponse(BaseResponse):
stack_name_or_id = self._get_param("StackName")
resources = self.cloudformation_backend.list_stack_resources(stack_name_or_id)
if resources is None:
raise ValidationError(stack_name_or_id)
template = self.response_template(LIST_STACKS_RESOURCES_RESPONSE)
return template.render(resources=resources)

View File

@ -570,6 +570,15 @@ def test_describe_stack_events_shows_create_update_and_delete():
list(stack_events_to_look_for).should.be.empty
with pytest.raises(BotoServerError) as exp:
conn.describe_stack_events("non_existing_stack")
err = exp.value
err.message.should.equal("Stack with id non_existing_stack does not exist")
err.body.should.match(r"Stack with id non_existing_stack does not exist")
err.error_code.should.equal("ValidationError")
err.reason.should.equal("Bad Request")
err.status.should.equal(400)
@mock_cloudformation_deprecated
def test_create_stack_lambda_and_dynamodb():

View File

@ -255,6 +255,7 @@ def test_boto3_filter_stacks():
conn.create_stack(StackName="test_stack", TemplateBody=dummy_template_json)
conn.create_stack(StackName="test_stack2", TemplateBody=dummy_template_json)
conn.update_stack(StackName="test_stack", TemplateBody=dummy_template_json2)
stacks = conn.list_stacks(StackStatusFilter=["CREATE_COMPLETE"])
stacks.get("StackSummaries").should.have.length_of(1)
stacks = conn.list_stacks(StackStatusFilter=["UPDATE_COMPLETE"])
@ -318,6 +319,18 @@ def test_boto3_describe_stack_set_operation():
response["StackSetOperation"]["Status"].should.equal("STOPPED")
response["StackSetOperation"]["Action"].should.equal("CREATE")
with pytest.raises(ClientError) as exp:
cf_conn.describe_stack_set_operation(
StackSetName="test_stack_set", OperationId="non_existing_operation"
)
exp_err = exp.value.response.get("Error")
exp_metadata = exp.value.response.get("ResponseMetadata")
exp_err.get("Code").should.match(r"ValidationError")
exp_err.get("Message").should.match(
r"Stack with id non_existing_operation does not exist"
)
exp_metadata.get("HTTPStatusCode").should.equal(400)
@mock_cloudformation
@ -1136,6 +1149,16 @@ def test_delete_change_set():
cf_conn.delete_change_set(ChangeSetName="NewChangeSet", StackName="NewStack")
cf_conn.list_change_sets(StackName="NewStack")["Summaries"].should.have.length_of(0)
# Testing deletion by arn
result = cf_conn.create_change_set(
StackName="NewStack",
TemplateBody=dummy_template_json,
ChangeSetName="NewChangeSet1",
ChangeSetType="CREATE",
)
cf_conn.delete_change_set(ChangeSetName=result.get("Id"), StackName="NewStack")
cf_conn.list_change_sets(StackName="NewStack")["Summaries"].should.have.length_of(0)
@mock_cloudformation
@mock_ec2
@ -1309,6 +1332,19 @@ def test_stack_events():
list(stack_events_to_look_for).should.be.empty
with pytest.raises(ClientError) as exp:
stack = cf.Stack("non_existing_stack")
events = list(stack.events.all())
exp_err = exp.value.response.get("Error")
exp_metadata = exp.value.response.get("ResponseMetadata")
exp_err.get("Code").should.match(r"ValidationError")
exp_err.get("Message").should.match(
r"Stack with id non_existing_stack does not exist"
)
exp_metadata.get("HTTPStatusCode").should.equal(400)
@mock_cloudformation
def test_list_exports():