def test_uses_abbreviated_tag_for_yaml_scalar(self):
        # Setup
        dumper = create_refsafe_dumper(None)
        func = Base64("Something something")

        # Exercise
        node = func.as_yaml_node(dumper)

        # Verify
        assert isinstance(node, ScalarNode)
        assert node.tag == "!Base64"
    def test_input_values_can_include_functions(self):
        # Setup
        dumper = create_refsafe_dumper(None)
        func = Join(".", [Base64("Something"), "foo", "bar"])

        # Exercise
        node = func.as_yaml_node(dumper)

        # Verify
        assert node.value[1].value[0].tag == "!Base64"
        self._verify_values(node, ["Something", "foo", "bar"])
    def test_yaml_output_doesnt_modify_string(self):
        # Setup
        func = Base64("Some 6HSsort of text_?:%#")
        data = SingleAttributeObject(one=func)

        # Exercise
        output = data.export("yaml")

        # Verify
        assert output == dedent("""
            ---
            one: !Base64 Some 6HSsort of text_?:%#
            """)
    def test_yaml_output(self):
        # Setup
        func = Base64("Something")
        data = SingleAttributeObject(one=func)

        # Exercise
        output = data.export("yaml")

        # Verify
        assert output == dedent("""
            ---
            one: !Base64 Something
            """)
    def test_nested_function_forces_longform_name(self):
        # TODO #37 do this with a Sub to be more realistic
        # Setup
        dumper = create_refsafe_dumper(Stack())
        func = Base64(Ref(AWS_StackName))

        # Exercise
        node = func.as_yaml_node(dumper)

        # Verify
        assert node.tag == dumper.DEFAULT_MAPPING_TAG
        assert len(node.value) == 1

        function_name = get_mapping_node_key(node, 0)
        assert function_name == "Fn::Base64"
    def test_yaml_output_with_nested_function(self):
        """Nested YAML functions can't both use the ! short form."""
        # TODO #37 do this with a Sub to be more realistic

        # Setup
        func = Base64(Ref(AWS_StackName))
        data = SingleAttributeObject(one=func)
        stack = Stack(Resources=dict(SomeResource=data))
        del stack.Metadata

        # Exercise
        output = stack.export("yaml")

        # Verify
        assert output == dedent("""
            ---
            AWSTemplateFormatVersion: '2010-09-09'
            Resources:
              SomeResource:
                one:
                  Fn::Base64: !Ref AWS::StackName
            """)
class TestSubWithVariableMapping:
    """Test behaviour/output of the Sub function, when a variable mapping is used."""

    # YAML Output
    # -----------
    def test_uses_abbreviated_tag_for_yaml_sequence(self):
        # Setup
        dumper = create_refsafe_dumper(None)
        func = Sub("Something-${foo}", foo="bar")

        # Exercise
        node = func.as_yaml_node(dumper)

        # Verify
        assert isinstance(node, SequenceNode)
        assert node.tag == "!Sub"

    def test_yaml_output(self):
        # Setup
        func = Sub("Something-${foo}", foo="bar")
        data = SingleAttributeObject(one=func)

        # Exercise
        output = data.export("yaml")

        # Verify
        assert output == dedent("""
            ---
            one: !Sub
            - Something-${foo}
            - foo: bar
            """)

    def test_yaml_output_doesnt_modify_complex_string(self):
        # Setup. Use (modified) example from AWS documentation
        func = Sub("arn:aws:ec2:${AWS::Region}:${AWS::AccountId}:vpc/${vpc}",
                   vpc="someid")
        data = SingleAttributeObject(one=func)

        # Exercise
        output = data.export("yaml")

        # Verify
        assert output == dedent("""
            ---
            one: !Sub
            - arn:aws:ec2:${AWS::Region}:${AWS::AccountId}:vpc/${vpc}
            - vpc: someid
            """)

    def test_yaml_output_doesnt_modify_multiline_string(self):
        # Setup. Use (modified) example from AWS documentation
        func = Sub(
            dedent("""
            #!/bin/bash -xe
            yum update -y aws-cfn-bootstrap
            /opt/aws/bin/cfn-init -v --stack ${AWS::StackName} --resource LaunchConfig --configsets wordpress_install --region ${AWS::Region}
            /opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource WebServerGroup --region ${AWS::Region}
            echo hello from ${user}
            """),
            user="******",
        )

        data = SingleAttributeObject(one=func)

        # Exercise
        output = data.export("yaml")

        # Verify
        assert output == dedent("""
            ---
            one: !Sub
            - |
              #!/bin/bash -xe
              yum update -y aws-cfn-bootstrap
              /opt/aws/bin/cfn-init -v --stack ${AWS::StackName} --resource LaunchConfig --configsets wordpress_install --region ${AWS::Region}
              /opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource WebServerGroup --region ${AWS::Region}
              echo hello from ${user}
            - user: bobbytables
            """)

    # Variable Map
    # ------------
    def test_variable_map_can_include_functions(self):
        # Setup
        dumper = create_refsafe_dumper(None)
        func = Sub(
            "arn:aws:ec2:${AWS::Region}:${account}:vpc/${vpc}",
            account="123456789012",
            vpc=ImportValue("my-vpc-id"),
        )

        # Exercise
        node = func.as_yaml_node(dumper)

        # Verify
        varmap_node = node.value[1]
        vpc_var_node = varmap_node.value[1]
        assert (vpc_var_node[1].tag == "!ImportValue"
                )  # The dict value is the second element in a tuple

    # Parameters
    # ----------
    @pytest.mark.parametrize(
        "input",
        [
            AWS_Region,  # string-like PseudoParameter
            Base64("Some string with ${AWS::Region} embedded"
                   ),  # String-like function
        ],
    )
    def test_nonstring_input_is_rejected_immediately(self, input):
        with pytest.raises(TypeError):
            _ = Sub(input, foo="bar")

    @pytest.mark.parametrize("input", [123])  # number
    def test_nonstring_variable_name_is_rejected_immediately(self, input):
        with pytest.raises(TypeError):
            _ = Sub("Something something", **{input: "bar"})
class TestSubWithoutExplicitVariables:
    """Test behaviour/output of the Sub function, when a variable mapping is not used."""

    # YAML Output
    # -----------
    def test_uses_abbreviated_tag_for_yaml_scalar(self):
        # Setup
        dumper = create_refsafe_dumper(None)
        func = Sub("Something something")

        # Exercise
        node = func.as_yaml_node(dumper)

        # Verify
        assert isinstance(node, ScalarNode)
        assert node.tag == "!Sub"

    def test_always_uses_string_quoting_for_input(self):
        # Setup
        dumper = create_refsafe_dumper(None)
        func = Sub("Something something")

        # Exercise
        node = func.as_yaml_node(dumper)

        # Verify
        assert isinstance(node, ScalarNode)
        assert node.style != ""

    def test_yaml_output(self):
        # Setup
        func = Sub("Something")
        data = SingleAttributeObject(one=func)

        # Exercise
        output = data.export("yaml")

        # Verify
        assert output == dedent("""
            ---
            one: !Sub 'Something'
            """)

    def test_yaml_output_doesnt_modify_complex_string(self):
        # Setup. Use example from AWS documentation
        func = Sub("arn:aws:ec2:${AWS::Region}:${AWS::AccountId}:vpc/${vpc}")
        data = SingleAttributeObject(one=func)

        # Exercise
        output = data.export("yaml")

        # Verify
        assert output == dedent("""
            ---
            one: !Sub 'arn:aws:ec2:${AWS::Region}:${AWS::AccountId}:vpc/${vpc}'
            """)

    def test_yaml_output_doesnt_modify_multiline_string(self):
        # Setup. Use example from AWS documentation
        func = Sub(
            dedent("""
            #!/bin/bash -xe
            yum update -y aws-cfn-bootstrap
            /opt/aws/bin/cfn-init -v --stack ${AWS::StackName} --resource LaunchConfig --configsets wordpress_install --region ${AWS::Region}
            /opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource WebServerGroup --region ${AWS::Region}
            """))

        data = SingleAttributeObject(one=func)

        # Exercise
        output = data.export("yaml")

        # Verify
        assert output == dedent("""
            ---
            one: !Sub |
              #!/bin/bash -xe
              yum update -y aws-cfn-bootstrap
              /opt/aws/bin/cfn-init -v --stack ${AWS::StackName} --resource LaunchConfig --configsets wordpress_install --region ${AWS::Region}
              /opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource WebServerGroup --region ${AWS::Region}
            """)

    # Parameters
    # ----------
    @pytest.mark.parametrize(
        "input",
        [
            AWS_Region,  # string-like PseudoParameter
            Base64("Some string with ${AWS::Region} embedded"
                   ),  # String-like function
        ],
    )
    def test_nonstring_input_is_rejected_immediately(self, input):
        with pytest.raises(TypeError):
            _ = Sub(input)