def test_context_namespace_delimiter_is_overriden_and_is_empty(self): config = Config({"namespace": "namespace", "namespace_delimiter": ""}) context = Context(config=config) fqn = context.get_fqn("stack1") self.assertEqual(fqn, "namespacestack1")
class TestStack(unittest.TestCase): def setUp(self): self.sd = {"name": "test"} self.context = Context({"namespace": "namespace"}) self.stack = Stack( definition=generate_definition("vpc", 1), context=self.context, ) def test_stack_requires(self): definition = generate_definition( "vpc", 1, parameters={ "ExternalParameter": "fakeStack2::FakeParameter", }, requires=[self.context.get_fqn("fakeStack")], ) stack = Stack(definition=definition, context=self.context) self.assertIn( self.context.get_fqn("fakeStack"), stack.requires, ) self.assertIn( self.context.get_fqn("fakeStack2"), stack.requires, ) def test_empty_parameters(self): build_action_parameters = {} self.assertEqual({}, _gather_parameters(self.sd, build_action_parameters)) def test_generic_build_action_override(self): sdef = self.sd sdef["parameters"] = {"Address": "10.0.0.1", "Foo": "BAR"} build_action_parameters = {"Address": "192.168.1.1"} result = _gather_parameters(sdef, build_action_parameters) self.assertEqual(result["Address"], "192.168.1.1") self.assertEqual(result["Foo"], "BAR") def test_stack_specific_override(self): sdef = self.sd sdef["parameters"] = {"Address": "10.0.0.1", "Foo": "BAR"} build_action_parameters = {"test::Address": "192.168.1.1"} result = _gather_parameters(sdef, build_action_parameters) self.assertEqual(result["Address"], "192.168.1.1") self.assertEqual(result["Foo"], "BAR") def test_invalid_stack_specific_override(self): sdef = self.sd sdef["parameters"] = {"Address": "10.0.0.1", "Foo": "BAR"} build_action_parameters = {"FAKE::Address": "192.168.1.1"} result = _gather_parameters(sdef, build_action_parameters) self.assertEqual(result["Address"], "10.0.0.1") self.assertEqual(result["Foo"], "BAR") def test_specific_vs_generic_build_action_override(self): sdef = self.sd sdef["parameters"] = {"Address": "10.0.0.1", "Foo": "BAR"} build_action_parameters = { "test::Address": "192.168.1.1", "Address": "10.0.0.1" } result = _gather_parameters(sdef, build_action_parameters) self.assertEqual(result["Address"], "192.168.1.1") self.assertEqual(result["Foo"], "BAR")
def test_context_get_fqn_empty_namespace(self): context = Context(config=Config({"namespace": ""})) fqn = context.get_fqn("vpc") self.assertEqual(fqn, "vpc") self.assertEqual(context.tags, {})
def test_context_get_fqn_stack_name(self): context = Context(config=self.config) fqn = context.get_fqn("stack1") self.assertEqual(fqn, "namespace-stack1")
def test_context_get_fqn(self): context = Context(config=self.config) fqn = context.get_fqn() self.assertEqual(fqn, "namespace")
def test_context_get_fqn_replace_dot(self): context = Context(config=Config({"namespace": "my.namespace"})) fqn = context.get_fqn() self.assertEqual(fqn, "my-namespace")
class TestDestroyAction(unittest.TestCase): def setUp(self): self.context = Context({'namespace': 'namespace'}) self.context.config = { 'stacks': [ { 'name': 'vpc' }, { 'name': 'bastion', 'requires': ['vpc'] }, { 'name': 'instance', 'requires': ['vpc', 'bastion'] }, { 'name': 'db', 'requires': ['instance', 'vpc', 'bastion'] }, { 'name': 'other', 'requires': ['db'] }, ], } self.action = destroy.Action(self.context, provider=mock.MagicMock()) def test_generate_plan(self): plan = self.action._generate_plan() self.assertEqual( [ self.context.get_fqn(s) for s in ['other', 'db', 'instance', 'bastion', 'vpc'] ], plan.keys(), ) def test_only_execute_plan_when_forced(self): with mock.patch.object(self.action, '_generate_plan') as mock_generate_plan: self.action.run(force=False) self.assertEqual(mock_generate_plan().execute.call_count, 0) def test_execute_plan_when_forced(self): with mock.patch.object(self.action, '_generate_plan') as mock_generate_plan: self.action.run(force=True) self.assertEqual(mock_generate_plan().execute.call_count, 1) def test_destroy_stack_complete_if_state_submitted(self): # Simulate the provider not being able to find the stack (a result of # it being successfully deleted) self.action.provider = mock.MagicMock() self.action.provider.get_stack.side_effect = StackDoesNotExist('mock') status = self.action._destroy_stack(MockStack('vpc'), status=PENDING) # if we haven't processed the step (ie. has never been SUBMITTED, should be skipped) self.assertEqual(status, SKIPPED) status = self.action._destroy_stack(MockStack('vpc'), status=SUBMITTED) # if we have processed the step and then can't find the stack, it means # we successfully deleted it self.assertEqual(status, COMPLETE) def test_destroy_stack_in_parallel(self): count = {'_count': 0} mock_provider = mock.MagicMock() self.context.config = { 'stacks': [ { 'name': 'vpc' }, { 'name': 'bastion', 'requires': ['vpc'] }, { 'name': 'instance', 'requires': ['vpc'] }, { 'name': 'db', 'requies': ['vpc'] }, ], } stacks_dict = self.context.get_stacks_dict() def get_stack(stack_name): return stacks_dict.get(stack_name) def get_stack_staggered(stack_name): count['_count'] += 1 if not count['_count'] % 2: raise StackDoesNotExist(stack_name) return stacks_dict.get(stack_name) def wait_func(*args): # we want "get_stack" above to return staggered results for the stack # being "deleted" to simulate waiting on stacks to complete mock_provider.get_stack.side_effect = get_stack_staggered plan = self.action._generate_plan() plan._wait_func = wait_func # swap for mock provider plan.provider = mock_provider self.action.provider = mock_provider # we want "get_stack" to return the mock stacks above on the first # pass. "wait_func" will simulate the stack being deleted every second # pass mock_provider.get_stack.side_effect = get_stack mock_provider.is_stack_destroyed.return_value = False mock_provider.is_stack_in_progress.return_value = True independent_stacks = filter(lambda x: x.name != 'vpc', self.context.get_stacks()) while not plan._single_run(): # vpc should be the last stack that is deleted if plan['namespace-vpc'].completed: self.assertFalse(plan.list_pending()) # all of the independent stacks should be submitted at the same time if any([plan[stack.fqn].submitted for stack in independent_stacks]): self.assertTrue( all([ plan[stack.fqn].submitted for stack in independent_stacks ])) wait_func() def test_destroy_stack_step_statuses(self): mock_provider = mock.MagicMock() stacks_dict = self.context.get_stacks_dict() def get_stack(stack_name): return stacks_dict.get(stack_name) plan = self.action._generate_plan() _, step = plan.list_pending()[0] # we need the AWS provider to generate the plan, but swap it for # the mock one to make the test easier self.action.provider = mock_provider # simulate stack doesn't exist and we haven't submitted anything for deletion mock_provider.get_stack.side_effect = StackDoesNotExist('mock') status = step.run() self.assertEqual(status, SKIPPED) # simulate stack getting successfully deleted mock_provider.get_stack.side_effect = get_stack mock_provider.is_stack_destroyed.return_value = False mock_provider.is_stack_in_progress.return_value = False status = step.run() self.assertEqual(status, SUBMITTED) mock_provider.is_stack_destroyed.return_value = False mock_provider.is_stack_in_progress.return_value = True status = step.run() self.assertEqual(status, SUBMITTED) mock_provider.is_stack_destroyed.return_value = True mock_provider.is_stack_in_progress.return_value = False status = step.run() self.assertEqual(status, COMPLETE)
class TestDestroyAction(unittest.TestCase): def setUp(self): self.context = Context({'namespace': 'namespace'}) self.context.config = { 'stacks': [ {'name': 'vpc'}, {'name': 'bastion', 'requires': ['vpc']}, {'name': 'instance', 'requires': ['vpc', 'bastion']}, {'name': 'db', 'requires': ['instance', 'vpc', 'bastion']}, {'name': 'other', 'requires': ['db']}, ], } self.action = destroy.Action(self.context, provider=mock.MagicMock()) def test_generate_plan(self): plan = self.action._generate_plan() self.assertEqual( [self.context.get_fqn(s) for s in ['other', 'db', 'instance', 'bastion', 'vpc']], plan.keys(), ) def test_only_execute_plan_when_forced(self): with mock.patch.object(self.action, '_generate_plan') as mock_generate_plan: self.action.run(force=False) self.assertEqual(mock_generate_plan().execute.call_count, 0) def test_execute_plan_when_forced(self): with mock.patch.object(self.action, '_generate_plan') as mock_generate_plan: self.action.run(force=True) self.assertEqual(mock_generate_plan().execute.call_count, 1) def test_destroy_stack_complete_if_state_submitted(self): # Simulate the provider not being able to find the stack (a result of # it being successfully deleted) self.action.provider = mock.MagicMock() self.action.provider.get_stack.side_effect = StackDoesNotExist('mock') status = self.action._destroy_stack(MockStack('vpc'), status=PENDING) # if we haven't processed the step (ie. has never been SUBMITTED, should be skipped) self.assertEqual(status, SKIPPED) status = self.action._destroy_stack(MockStack('vpc'), status=SUBMITTED) # if we have processed the step and then can't find the stack, it means # we successfully deleted it self.assertEqual(status, COMPLETE) def test_destroy_stack_in_parallel(self): count = {'_count': 0} mock_provider = mock.MagicMock() self.context.config = { 'stacks': [ {'name': 'vpc'}, {'name': 'bastion', 'requires': ['vpc']}, {'name': 'instance', 'requires': ['vpc']}, {'name': 'db', 'requies': ['vpc']}, ], } stacks_dict = self.context.get_stacks_dict() def get_stack(stack_name): return stacks_dict.get(stack_name) def get_stack_staggered(stack_name): count['_count'] += 1 if not count['_count'] % 2: raise StackDoesNotExist(stack_name) return stacks_dict.get(stack_name) def wait_func(*args): # we want "get_stack" above to return staggered results for the stack # being "deleted" to simulate waiting on stacks to complete mock_provider.get_stack.side_effect = get_stack_staggered plan = self.action._generate_plan() plan._wait_func = wait_func # swap for mock provider plan.provider = mock_provider self.action.provider = mock_provider # we want "get_stack" to return the mock stacks above on the first # pass. "wait_func" will simulate the stack being deleted every second # pass mock_provider.get_stack.side_effect = get_stack mock_provider.is_stack_destroyed.return_value = False mock_provider.is_stack_in_progress.return_value = True independent_stacks = filter(lambda x: x.name != 'vpc', self.context.get_stacks()) while not plan._single_run(): # vpc should be the last stack that is deleted if plan['namespace-vpc'].completed: self.assertFalse(plan.list_pending()) # all of the independent stacks should be submitted at the same time if any([plan[stack.fqn].submitted for stack in independent_stacks]): self.assertTrue(all([plan[stack.fqn].submitted for stack in independent_stacks])) wait_func() def test_destroy_stack_step_statuses(self): mock_provider = mock.MagicMock() stacks_dict = self.context.get_stacks_dict() def get_stack(stack_name): return stacks_dict.get(stack_name) plan = self.action._generate_plan() _, step = plan.list_pending()[0] # we need the AWS provider to generate the plan, but swap it for # the mock one to make the test easier self.action.provider = mock_provider # simulate stack doesn't exist and we haven't submitted anything for deletion mock_provider.get_stack.side_effect = StackDoesNotExist('mock') status = step.run() self.assertEqual(status, SKIPPED) # simulate stack getting successfully deleted mock_provider.get_stack.side_effect = get_stack mock_provider.is_stack_destroyed.return_value = False mock_provider.is_stack_in_progress.return_value = False status = step.run() self.assertEqual(status, SUBMITTED) mock_provider.is_stack_destroyed.return_value = False mock_provider.is_stack_in_progress.return_value = True status = step.run() self.assertEqual(status, SUBMITTED) mock_provider.is_stack_destroyed.return_value = True mock_provider.is_stack_in_progress.return_value = False status = step.run() self.assertEqual(status, COMPLETE)
class TestStack(unittest.TestCase): def setUp(self): self.sd = {"name": "test"} self.context = Context({"namespace": "namespace"}) self.stack = Stack( definition=generate_definition("vpc", 1), context=self.context, ) def test_stack_requires(self): definition = generate_definition( base_name="vpc", stack_id=1, variables={ "Var1": "${noop fakeStack3::FakeOutput}", "Var2": ("some.template.value:${output fakeStack2::FakeOutput}:" "${output fakeStack::FakeOutput}"), "Var3": "${output fakeStack::FakeOutput}," "${output fakeStack2::FakeOutput}", }, requires=[self.context.get_fqn("fakeStack")], ) stack = Stack(definition=definition, context=self.context) self.assertEqual(len(stack.requires), 2) self.assertIn( self.context.get_fqn("fakeStack"), stack.requires, ) self.assertIn( self.context.get_fqn("fakeStack2"), stack.requires, ) def test_stack_requires_circular_ref(self): definition = generate_definition( base_name="vpc", stack_id=1, variables={ "Var1": "${output vpc.1::FakeOutput}", }, ) stack = Stack(definition=definition, context=self.context) with self.assertRaises(ValueError): stack.requires def test_stack_cfn_parameters(self): definition = generate_definition( base_name="vpc", stack_id=1, variables={ "Param1": "${output fakeStack::FakeOutput}", }, ) stack = Stack(definition=definition, context=self.context) stack._blueprint = MagicMock() stack._blueprint.get_parameter_values.return_value = { "Param2": "Some Resolved Value", } self.assertEqual(len(stack.parameter_values.keys()), 1) param = stack.parameter_values["Param2"] self.assertEqual(param, "Some Resolved Value") def test_gather_variables_fails_on_parameters_in_stack_def(self): sdef = self.sd sdef["parameters"] = {"Address": "10.0.0.1", "Foo": "BAR"} with self.assertRaises(AttributeError): _gather_variables(sdef)
class TestStack(unittest.TestCase): def setUp(self): self.sd = {"name": "test"} self.context = Context({"namespace": "namespace"}) self.stack = Stack( definition=generate_definition("vpc", 1), context=self.context, ) def test_stack_requires(self): definition = generate_definition( base_name="vpc", stack_id=1, parameters={ "ExternalParameter": "fakeStack2::FakeParameter", }, variables={ "Var1": "${noop fakeStack3::FakeOutput}", "Var2": ( "some.template.value:${fakeStack2::FakeOutput}:" "${fakeStack::FakeOutput}" ), "Var3": "${fakeStack::FakeOutput}," "${output fakeStack2::FakeOutput}", }, requires=[self.context.get_fqn("fakeStack")], ) stack = Stack(definition=definition, context=self.context) self.assertEqual(len(stack.requires), 2) self.assertIn( self.context.get_fqn("fakeStack"), stack.requires, ) self.assertIn( self.context.get_fqn("fakeStack2"), stack.requires, ) def test_stack_requires_circular_ref(self): definition = generate_definition( base_name="vpc", stack_id=1, variables={ "Var1": "${vpc.1::FakeOutput}", }, ) stack = Stack(definition=definition, context=self.context) with self.assertRaises(ValueError): stack.requires def test_stack_cfn_parameters(self): definition = generate_definition( base_name="vpc", stack_id=1, parameters={ "Param1": "fakeStack::FakeOutput", }, ) stack = Stack(definition=definition, context=self.context) stack._blueprint = MagicMock() stack._blueprint.get_cfn_parameters.return_value = { "Param2": "Some Resolved Value", } self.assertEqual(len(stack.cfn_parameters.keys()), 2) param = stack.cfn_parameters["Param2"] self.assertEqual(param, "Some Resolved Value") def test_empty_parameters(self): build_action_parameters = {} self.assertEqual({}, _gather_parameters(self.sd, build_action_parameters)) def test_generic_build_action_override(self): sdef = self.sd sdef["parameters"] = {"Address": "10.0.0.1", "Foo": "BAR"} build_action_parameters = {"Address": "192.168.1.1"} result = _gather_parameters(sdef, build_action_parameters) self.assertEqual(result["Address"], "192.168.1.1") self.assertEqual(result["Foo"], "BAR") def test_stack_specific_override(self): sdef = self.sd sdef["parameters"] = {"Address": "10.0.0.1", "Foo": "BAR"} build_action_parameters = {"test::Address": "192.168.1.1"} result = _gather_parameters(sdef, build_action_parameters) self.assertEqual(result["Address"], "192.168.1.1") self.assertEqual(result["Foo"], "BAR") def test_invalid_stack_specific_override(self): sdef = self.sd sdef["parameters"] = {"Address": "10.0.0.1", "Foo": "BAR"} build_action_parameters = {"FAKE::Address": "192.168.1.1"} result = _gather_parameters(sdef, build_action_parameters) self.assertEqual(result["Address"], "10.0.0.1") self.assertEqual(result["Foo"], "BAR") def test_specific_vs_generic_build_action_override(self): sdef = self.sd sdef["parameters"] = {"Address": "10.0.0.1", "Foo": "BAR"} build_action_parameters = { "test::Address": "192.168.1.1", "Address": "10.0.0.1"} result = _gather_parameters(sdef, build_action_parameters) self.assertEqual(result["Address"], "192.168.1.1") self.assertEqual(result["Foo"], "BAR")
class TestStack(unittest.TestCase): def setUp(self): self.sd = {"name": "test"} self.context = Context({"namespace": "namespace"}) self.stack = Stack( definition=generate_definition("vpc", 1), context=self.context, ) def test_stack_requires(self): definition = generate_definition( "vpc", 1, parameters={ "ExternalParameter": "fakeStack2::FakeParameter", }, requires=[self.context.get_fqn("fakeStack")], ) stack = Stack(definition=definition, context=self.context) self.assertIn( self.context.get_fqn("fakeStack"), stack.requires, ) self.assertIn( self.context.get_fqn("fakeStack2"), stack.requires, ) def test_empty_parameters(self): build_action_parameters = {} self.assertEqual({}, _gather_parameters(self.sd, build_action_parameters)) def test_generic_build_action_override(self): sdef = self.sd sdef["parameters"] = {"Address": "10.0.0.1", "Foo": "BAR"} build_action_parameters = {"Address": "192.168.1.1"} result = _gather_parameters(sdef, build_action_parameters) self.assertEqual(result["Address"], "192.168.1.1") self.assertEqual(result["Foo"], "BAR") def test_stack_specific_override(self): sdef = self.sd sdef["parameters"] = {"Address": "10.0.0.1", "Foo": "BAR"} build_action_parameters = {"test::Address": "192.168.1.1"} result = _gather_parameters(sdef, build_action_parameters) self.assertEqual(result["Address"], "192.168.1.1") self.assertEqual(result["Foo"], "BAR") def test_invalid_stack_specific_override(self): sdef = self.sd sdef["parameters"] = {"Address": "10.0.0.1", "Foo": "BAR"} build_action_parameters = {"FAKE::Address": "192.168.1.1"} result = _gather_parameters(sdef, build_action_parameters) self.assertEqual(result["Address"], "10.0.0.1") self.assertEqual(result["Foo"], "BAR") def test_specific_vs_generic_build_action_override(self): sdef = self.sd sdef["parameters"] = {"Address": "10.0.0.1", "Foo": "BAR"} build_action_parameters = { "test::Address": "192.168.1.1", "Address": "10.0.0.1"} result = _gather_parameters(sdef, build_action_parameters) self.assertEqual(result["Address"], "192.168.1.1") self.assertEqual(result["Foo"], "BAR")
class TestBuildAction(unittest.TestCase): def setUp(self): self.context = Context({'namespace': 'namespace'}) self.build_action = build.Action(self.context, provider=TestProvider()) def _get_context(self, **kwargs): config = {'stacks': [ {'name': 'vpc'}, {'name': 'bastion', 'parameters': {'test': 'vpc::something'}}, {'name': 'db', 'parameters': {'test': 'vpc::something', 'else': 'bastion::something'}}, {'name': 'other', 'parameters': {}} ]} return Context({'namespace': 'namespace'}, config=config, **kwargs) def test_resolve_parameters_referencing_non_existant_output(self): parameters = { 'param_1': 'mock::output_1', 'param_2': 'other::does_not_exist', } self.build_action.provider.set_outputs({ self.context.get_fqn('mock'): {'output_1': 'output'}, self.context.get_fqn('other'): {}, }) mock_blueprint = mock.MagicMock() type(mock_blueprint).parameters = parameters with self.assertRaises(exceptions.OutputDoesNotExist): self.build_action._resolve_parameters(parameters, mock_blueprint) def test_resolve_parameters(self): parameters = { 'param_1': 'mock::output_1', 'param_2': 'other::output_2', } self.build_action.provider.set_outputs({ self.context.get_fqn('mock'): {'output_1': 'output1'}, self.context.get_fqn('other'): {'output_2': 'output2'}, }) mock_blueprint = mock.MagicMock() type(mock_blueprint).parameters = parameters resolved_parameters = self.build_action._resolve_parameters( parameters, mock_blueprint, ) self.assertEqual(resolved_parameters['param_1'], 'output1') self.assertEqual(resolved_parameters['param_2'], 'output2') def test_resolve_parameters_referencing_non_existant_stack(self): parameters = { 'param_1': 'mock::output_1', } self.build_action.provider.set_outputs({}) mock_blueprint = mock.MagicMock() type(mock_blueprint).parameters = parameters with self.assertRaises(exceptions.StackDoesNotExist): self.build_action._resolve_parameters(parameters, mock_blueprint) def test_gather_missing_from_stack(self): stack_params = {'Address': '10.0.0.1'} stack = MockStack(stack_params) def_params = {} required = ['Address'] self.assertEqual( self.build_action._handle_missing_parameters(def_params, required, stack), stack_params.items()) def test_missing_params_no_stack(self): params = {} required = ['Address'] with self.assertRaises(exceptions.MissingParameterException) as cm: self.build_action._handle_missing_parameters(params, required) self.assertEqual(cm.exception.parameters, required) def test_stack_params_dont_override_given_params(self): stack_params = {'Address': '10.0.0.1'} stack = MockStack(stack_params) def_params = {'Address': '192.168.0.1'} required = ['Address'] result = self.build_action._handle_missing_parameters(def_params, required, stack) self.assertEqual(result, def_params.items()) def test_get_dependencies(self): context = self._get_context() build_action = build.Action(context) dependencies = build_action._get_dependencies() self.assertEqual( dependencies[context.get_fqn('bastion')], set([context.get_fqn('vpc')]), ) self.assertEqual( dependencies[context.get_fqn('db')], set([context.get_fqn(s) for s in['vpc', 'bastion']]), ) self.assertFalse(dependencies[context.get_fqn('other')]) def test_get_stack_execution_order(self): context = self._get_context() build_action = build.Action(context) dependencies = build_action._get_dependencies() execution_order = build_action.get_stack_execution_order(dependencies) self.assertEqual( execution_order, [context.get_fqn(s) for s in ['other', 'vpc', 'bastion', 'db']], ) def test_generate_plan(self): context = self._get_context() build_action = build.Action(context) plan = build_action._generate_plan() self.assertEqual( plan.keys(), [context.get_fqn(s) for s in ['other', 'vpc', 'bastion', 'db']], ) def test_dont_execute_plan_when_outline_specified(self): context = self._get_context() build_action = build.Action(context) with mock.patch.object(build_action, '_generate_plan') as \ mock_generate_plan: build_action.run(outline=True) self.assertEqual(mock_generate_plan().execute.call_count, 0) def test_execute_plan_when_outline_not_specified(self): context = self._get_context() build_action = build.Action(context) with mock.patch.object(build_action, '_generate_plan') as \ mock_generate_plan: build_action.run(outline=False) self.assertEqual(mock_generate_plan().execute.call_count, 1) def test_launch_stack_step_statuses(self): mock_provider = mock.MagicMock() mock_stack = mock.MagicMock() context = self._get_context() build_action = build.Action(context, provider=mock_provider) plan = build_action._generate_plan() _, step = plan.list_pending()[0] step.stack = mock.MagicMock() step.stack.locked = False # mock provider shouldn't return a stack at first since it hasn't been # launched mock_provider.get_stack.return_value = None with mock.patch.object(build_action, 's3_stack_push'): # initial run should return SUBMITTED since we've passed off to CF status = step.run() self.assertEqual(status, SUBMITTED) # provider should now return the CF stack since it exists mock_provider.get_stack.return_value = mock_stack # simulate that we're still in progress mock_provider.is_stack_in_progress.return_value = True mock_provider.is_stack_completed.return_value = False status = step.run() step.set_status(status) # status should still be SUBMITTED since we're waiting for it to # complete self.assertEqual(status, SUBMITTED) # simulate completed stack mock_provider.is_stack_completed.return_value = True mock_provider.is_stack_in_progress.return_value = False status = step.run() self.assertEqual(status, COMPLETE) # simulate stack should be skipped mock_provider.is_stack_completed.return_value = False mock_provider.is_stack_in_progress.return_value = False mock_provider.update_stack.side_effect = StackDidNotChange status = step.run() self.assertEqual(status, SKIPPED) # simulate an update is required mock_provider.reset_mock() mock_provider.update_stack.side_effect = None step.set_status(PENDING) status = step.run() self.assertEqual(status, SUBMITTED) self.assertEqual(mock_provider.update_stack.call_count, 1) def test_should_update(self): test_scenario = namedtuple('test_scenario', ['locked', 'force', 'result']) test_scenarios = ( test_scenario(locked=False, force=False, result=True), test_scenario(locked=False, force=True, result=True), test_scenario(locked=True, force=False, result=False), test_scenario(locked=True, force=True, result=True) ) mock_stack = mock.MagicMock(["locked", "force", "name"]) mock_stack.name = "test-stack" for t in test_scenarios: mock_stack.locked = t.locked mock_stack.force = t.force self.assertEqual(build.should_update(mock_stack), t.result) def test_should_submit(self): test_scenario = namedtuple('test_scenario', ['enabled', 'result']) test_scenarios = ( test_scenario(enabled=False, result=False), test_scenario(enabled=True, result=True), ) mock_stack = mock.MagicMock(["enabled", "name"]) mock_stack.name = "test-stack" for t in test_scenarios: mock_stack.enabled = t.enabled self.assertEqual(build.should_submit(mock_stack), t.result)