def test_export_basic_output(self): """Should be able to create and export a simple CloudFormation Output object.""" output = Output(Description="Stuff we need", Value=6644, Export={"Name": "ImportantThingy"}) exported = output.export("yaml") assert exported == dedent(""" --- Description: Stuff we need Export: Name: ImportantThingy Value: 6644 """)
def test_cannot_merge_if_two_outputs_have_the_same_export_name(self): # Setup export_name = "SpecialExportedValue" source_output = Output(Value=123, Export={"Name": export_name}) source = Stack(Outputs={"SourceOutput": source_output}) target_output = Output(Value=987, Export={"Name": export_name}) target = Stack(Outputs={"TargetOutput": target_output}) # Exercise & Verify with pytest.raises(StackMergeError) as excinfo: target.merge_stack(source) assert "the target stack already has exports" in str( excinfo.value).lower()
def test_dont_create_export_name_for_output_when_it_is_not_set(self): # Setup name = "SomeItemName" stack = Stack(Outputs={name: (Output(Value="HelloWorld"))}) # Exercise new_stack = stack.with_prefixed_names(self.STACK_PREFIX) # Verify new_output = new_stack.Outputs[self.STACK_PREFIX + name] assert getattr(new_output, "Export", None) is None
def test_prefix_export_name_for_output(self): # Setup name = "SomeItemName" export_name = "SomeGloballyScopedValue" output = Output(Value="HelloWorld", Export={"Name": export_name}) stack = Stack(Outputs={name: output}) # Exercise new_stack = stack.with_prefixed_names(self.STACK_PREFIX) # Verify assert new_stack.Outputs[ self.STACK_PREFIX + name]["Export"]["Name"] == self.STACK_PREFIX + export_name
def generate_stack_template(): stack = Stack() stack.Resources["WebServer"] = create_ec2_instance("webserver") stack.Resources["DatabaseServer"] = dbserver = create_ec2_instance( "dbserver", "t2.medium") dbserver.DeletionPolicy = "Retain" stack.Outputs["DatabaseServerIp"] = Output( Description=f"Internal IP address for the database server", Value=GetAtt(dbserver, "PrivateIp"), ) stack.tag(application="api-service", environment="test", owner=os.environ.get("USER")) return stack.export("yaml")
class TestPrefixedNames: """Verify the object name prefixing functionality.""" ATTRIBUTE_PARAMETRIZE_NAMES = 'stack_attribute,item' PREFIXABLE_ATTRIBUTE_EXAMPLES = [ ("Parameters", Parameter(Type="String")), ("Resources", SimpleResource()), # TODO #87 Add Condition when we have a helper class # TODO #87 Add Mapping when we have a helper class # TODO #87 Add Metadata when we have a helper class. Consider whether we should retain the Metadata or just throw it away ] OUTPUT_EXAMPLES = [ ("Outputs", Output(Value="HelloWorld")), ("Outputs", Output(Value="HelloWorld", Export={"Name": "SomeGloballyScopedValue"})), ] STACK_PREFIX = "NewScope" # Basic Prefixing Behaviour # ------------------------- def test_return_value_is_a_new_stack(self): # Setup stack = Stack(Resources={"SomeName": SimpleResource()}) # Exercise new_stack = stack.with_prefixed_names(self.STACK_PREFIX) # Verify assert isinstance(new_stack, Stack) assert new_stack is not stack @given(aws_logical_name_strategy()) def test_prefix_is_an_underscored_alphanumeric_string(self, prefix): # Setup stack = Stack(Resources={"SomeName": SimpleResource()}) # Exercise & Verify _ = stack.with_prefixed_names(prefix) # Should not throw an error def test_prefix_cannot_be_empty(self): # Setup stack = Stack(Resources={"SomeName": SimpleResource()}) # Exercise & Verify with pytest.raises(ValueError) as excinfo: _ = stack.with_prefixed_names("") assert "empty" in str(excinfo.value).lower() def test_prefix_must_have_leading_capital(self): # Setup stack = Stack(Resources={"SomeName": SimpleResource()}) # Exercise & Verify with pytest.raises(ValueError) as excinfo: _ = stack.with_prefixed_names( "lowercasedCamelsAreBactrianButInvalid") assert "uppercase" in str(excinfo.value).lower() @given(st.from_regex(re.compile(r"^[A-Z]\w*\W+\w*$", re.ASCII))) def test_prefix_cannot_contain_special_characters(self, prefix): # Setup stack = Stack(Resources={"SomeName": SimpleResource()}) # Exercise & Verify with pytest.raises(ValueError) as excinfo: _ = stack.with_prefixed_names(prefix) assert "alphanumeric" in str(excinfo.value).lower() @pytest.mark.parametrize('prefix', [ None, 123, Stack(), ]) def test_prefix_must_be_string(self, prefix): # Setup stack = Stack(Resources={"SomeName": SimpleResource()}) # Exercise & Verify with pytest.raises(TypeError) as excinfo: _ = stack.with_prefixed_names(prefix) assert "string" in str(excinfo.value).lower() # Prefixable Dictionaries # ----------------------- @pytest.mark.parametrize(ATTRIBUTE_PARAMETRIZE_NAMES, PREFIXABLE_ATTRIBUTE_EXAMPLES) def test_item_is_prefixed_in_the_new_stack(self, stack_attribute, item): # Setup item_name = "SomeItemName" stack = Stack() stack[stack_attribute] = {item_name: item} # Exercise new_stack = stack.with_prefixed_names(self.STACK_PREFIX) # Verify new_name = self.STACK_PREFIX + item_name assert len(new_stack[stack_attribute]) == 1 assert item_name not in new_stack[stack_attribute] assert new_stack[stack_attribute][new_name] is item @pytest.mark.parametrize(ATTRIBUTE_PARAMETRIZE_NAMES, OUTPUT_EXAMPLES) def test_output_is_prefixed_in_the_new_stack_but_not_same_object( self, stack_attribute, item): # Setup item_name = "SomeItemName" stack = Stack() stack[stack_attribute] = {item_name: item} # Exercise new_stack = stack.with_prefixed_names(self.STACK_PREFIX) # Verify new_name = self.STACK_PREFIX + item_name assert len(new_stack[stack_attribute]) == 1 assert item_name not in new_stack[stack_attribute] new_item = new_stack[stack_attribute][new_name] assert new_item is not item assert getattr(new_item, "Description", None) == getattr(item, "Description", None) assert getattr(new_item, "Value", None) is getattr(item, "Value", None), \ "This should be the same because it might be a Reference function or some such" @pytest.mark.parametrize(ATTRIBUTE_PARAMETRIZE_NAMES, PREFIXABLE_ATTRIBUTE_EXAMPLES + OUTPUT_EXAMPLES) def test_item_is_not_removed_from_original_stack(self, stack_attribute, item): # Setup item_name = "SomeItemName" stack = Stack() stack[stack_attribute] = {item_name: item} # Exercise _ = stack.with_prefixed_names(self.STACK_PREFIX) # Verify assert len(stack[stack_attribute]) == 1 assert stack[stack_attribute][item_name] is item assert stack[stack_attribute][item_name] == item # Special Cases # ------------- def test_copy_cfn_template_version(self): """AWSTemplateFormatVersion should be a string which we copy across unchanged""" # Setup version_string = "WhatIfThisWaSemanticallyVersioned.1.0" stack = Stack(AWSTemplateFormatVersion=version_string) # Exercise new_stack = stack.with_prefixed_names(self.STACK_PREFIX) # Verify assert new_stack.AWSTemplateFormatVersion == version_string assert stack.AWSTemplateFormatVersion == version_string, "Old stack should not be modified" def test_copy_sam_version_in_transform(self): """If set, Transform should be the version of the Serverless Application Model being used, which we copy across unchanged. """ # Setup version_string = "AWS::Serverless-1999-12-31" stack = Stack(Transform=version_string) # Exercise new_stack = stack.with_prefixed_names(self.STACK_PREFIX) # Verify assert new_stack.Transform == version_string assert stack.Transform == version_string, "Old stack should not be modified" def test_modify_content_of_description(self): # Setup stack = Stack(Description=LOREM_IPSUM) # Exercise new_stack = stack.with_prefixed_names(self.STACK_PREFIX) # Verify assert self.STACK_PREFIX in new_stack.Description assert LOREM_IPSUM in new_stack.Description assert stack.Description == LOREM_IPSUM, "Old stack should not be modified" def test_create_description_when_it_is_not_set(self): # Setup stack = Stack() # Exercise new_stack = stack.with_prefixed_names(self.STACK_PREFIX) # Verify assert self.STACK_PREFIX == new_stack.Description assert not hasattr( stack, "Description" ) or stack.Description is None, "Old stack should not be modified" def test_prefix_export_name_for_output(self): # Setup name = "SomeItemName" export_name = "SomeGloballyScopedValue" output = Output(Value="HelloWorld", Export={"Name": export_name}) stack = Stack(Outputs={name: output}) # Exercise new_stack = stack.with_prefixed_names(self.STACK_PREFIX) # Verify assert new_stack.Outputs[ self.STACK_PREFIX + name]["Export"]["Name"] == self.STACK_PREFIX + export_name def test_dont_create_export_name_for_output_when_it_is_not_set(self): # Setup name = "SomeItemName" stack = Stack(Outputs={name: (Output(Value="HelloWorld"))}) # Exercise new_stack = stack.with_prefixed_names(self.STACK_PREFIX) # Verify new_output = new_stack.Outputs[self.STACK_PREFIX + name] assert getattr(new_output, "Export", None) is None
class TestMergeStack: """Verify the stack merge functionality.""" PARAMETRIZE_NAMES = 'stack_attribute,item' MERGED_ATTRIBUTE_EXAMPLES = [ ("Outputs", Output(Value="HelloWorld")), ("Parameters", Parameter(Type="String")), ("Resources", SimpleResource()), # TODO #87 Add Condition when we have a helper class # TODO #87 Add Mapping when we have a helper class ] # TODO #87 test_does_not_copy_metadata when we have a Metadata class def test_merge_returns_target_stack(self): # Setup source = Stack(Resources={"SomeResource": SimpleResource()}) target = Stack() # Exercise result = target.merge_stack(source) # Verify assert target is result @pytest.mark.parametrize(PARAMETRIZE_NAMES, MERGED_ATTRIBUTE_EXAMPLES) def test_item_is_added_to_the_target_stack(self, stack_attribute, item): # Setup item_name = "SomeChildProperty" source = Stack() source[stack_attribute] = {item_name: item} target = Stack() # Exercise target.merge_stack(source) # Verify assert len(target[stack_attribute]) == 1 assert target[stack_attribute][item_name] is item @pytest.mark.parametrize(PARAMETRIZE_NAMES, MERGED_ATTRIBUTE_EXAMPLES) def test_item_is_not_removed_from_source_stack(self, stack_attribute, item): # Setup item_name = "SomeChildProperty" source = Stack() source[stack_attribute] = {item_name: item} target = Stack() # Exercise target.merge_stack(source) # Verify assert len(source[stack_attribute]) == 1 assert source[stack_attribute][item_name] is item @pytest.mark.parametrize(PARAMETRIZE_NAMES, MERGED_ATTRIBUTE_EXAMPLES) def test_does_not_clobber_existing_items_in_target_stack( self, stack_attribute, item): # Setup item_name = "SomeChildProperty" source = Stack() source[stack_attribute] = {item_name: item} existing_item = copy(item) existing_item_name = "SomeOldItem" target = Stack() target[stack_attribute] = {existing_item_name: existing_item} # Exercise target.merge_stack(source) # Verify assert len(target[stack_attribute]) == 2 assert target[stack_attribute][item_name] is item assert target[stack_attribute][existing_item_name] is existing_item def test_does_not_copy_description(self): # Setup source = Stack(Description="Source Description") original_description = "Target Description" target = Stack(Description=original_description) # Exercise target.merge_stack(source) # Verify assert target.Description == original_description def test_cannot_merge_if_template_version_is_different(self): source = Stack(AWSTemplateFormatVersion="123") target = Stack(AWSTemplateFormatVersion="456") # Exercise & Verify with pytest.raises(StackMergeError) as excinfo: target.merge_stack(source) assert "template version" in str(excinfo.value).lower() def test_cannot_merge_if_sam_transform_version_is_different(self): source = Stack(Transform="123") target = Stack(Transform="456") # Exercise & Verify with pytest.raises(StackMergeError) as excinfo: target.merge_stack(source) assert "transform version" in str(excinfo.value).lower() @pytest.mark.parametrize(PARAMETRIZE_NAMES, MERGED_ATTRIBUTE_EXAMPLES) def test_cannot_merge_if_logical_name_is_already_used_for_that_item_type( self, stack_attribute, item): # Setup item_name = "SomeChildProperty" source = Stack() source[stack_attribute] = {item_name: item} existing_item = copy(item) target = Stack() target[stack_attribute] = {item_name: existing_item} # Exercise & Verify with pytest.raises(StackMergeError) as excinfo: target.merge_stack(source) assert "in this stack already has an item with the logical name" in str( excinfo.value) def test_cannot_merge_if_two_outputs_have_the_same_export_name(self): # Setup export_name = "SpecialExportedValue" source_output = Output(Value=123, Export={"Name": export_name}) source = Stack(Outputs={"SourceOutput": source_output}) target_output = Output(Value=987, Export={"Name": export_name}) target = Stack(Outputs={"TargetOutput": target_output}) # Exercise & Verify with pytest.raises(StackMergeError) as excinfo: target.merge_stack(source) assert "the target stack already has exports" in str( excinfo.value).lower()