<file_sep>class InvalidLookupCombination(Exception):

    def __init__(self, lookup, lookups, value, *args, **kwargs):
        message = (
            "Lookup: \"{}\" has non-string return value, must be only lookup "
            "present (not {}) in \"{}\""
        ).format(lookup.raw, len(lookups), value)
        super(InvalidLookupCombination, self).__init__(message,
                                                       *args,
                                                       **kwargs)


class UnknownLookupType(Exception):

    def __init__(self, lookup, *args, **kwargs):
        message = "Unknown lookup type: \"{}\"".format(lookup.type)
        super(UnknownLookupType, self).__init__(message, *args, **kwargs)


class UnresolvedVariables(Exception):

    def __init__(self, blueprint, *args, **kwargs):
        message = "Blueprint: \"%s\" hasn't resolved it's variables" % (
            blueprint.name)
        super(UnresolvedVariables, self).__init__(message, *args, **kwargs)


class UnresolvedVariable(Exception):

    def __init__(self, blueprint, variable, *args, **kwargs):
        message = (
            "Variable \"%s\" in blueprint \"%s\" hasn't been resolved"
        ) % (variable.name, blueprint.name)
        super(UnresolvedVariable, self).__init__(message, *args, **kwargs)


class MissingVariable(Exception):

    def __init__(self, blueprint, variable_name, *args, **kwargs):
        message = "Variable \"%s\" in blueprint \"%s\" is missing" % (
            variable_name, blueprint.name)
        super(MissingVariable, self).__init__(message, *args, **kwargs)


class StackDoesNotExist(Exception):

    def __init__(self, stack_name, *args, **kwargs):
        message = "Stack: \"%s\" does not exist in outputs" % (stack_name,)
        super(StackDoesNotExist, self).__init__(message, *args, **kwargs)


class MissingParameterException(Exception):

    def __init__(self, parameters, *args, **kwargs):
        self.parameters = parameters
        message = "Missing required cloudformation parameters: %s" % (
            ", ".join(parameters),
        )
        super(MissingParameterException, self).__init__(message, *args,
                                                        **kwargs)


class MissingLocalParameterException(Exception):

    def __init__(self, parameter, *args, **kwargs):
        self.parameter = parameter
        message = "Missing required local parameter: %s" % parameter
        super(MissingLocalParameterException, self).__init__(message, *args,
                                                             **kwargs)


class OutputDoesNotExist(Exception):

    def __init__(self, stack_name, output, *args, **kwargs):
        self.stack_name = stack_name
        self.output = output

        message = "Output %s does not exist on stack %s" % (output,
                                                            stack_name)
        super(OutputDoesNotExist, self).__init__(message, *args, **kwargs)


class MissingEnvironment(Exception):

    def __init__(self, key, *args, **kwargs):
        self.key = key
        message = "Environment missing key %s." % (key,)
        super(MissingEnvironment, self).__init__(message, *args, **kwargs)


class ImproperlyConfigured(Exception):

    def __init__(self, cls, error, *args, **kwargs):
        message = "Class \"%s\" is improperly configured: %s" % (
            cls,
            error,
        )
        super(ImproperlyConfigured, self).__init__(message, *args, **kwargs)


class StackDidNotChange(Exception):
    """Exception raised when there are no changes to be made by the
    provider.
    """


class CancelExecution(Exception):
    """Exception raised when we want to cancel executing the plan."""
<file_sep>import unittest

from mock import MagicMock
from troposphere import (
    Base64,
    Ref,
)

from stacker.blueprints.base import (
    Blueprint,
    CFNParameter,
    build_parameter,
    get_local_parameters,
)
from stacker.blueprints.variables.types import (
    CFNString,
    EC2AvailabilityZoneNameList,
)
from stacker.exceptions import (
    InvalidLookupCombination,
    MissingLocalParameterException,
    MissingVariable,
    UnresolvedVariables,
)
from stacker.variables import Variable

from ..factories import mock_lookup


class TestLocalParameters(unittest.TestCase):
    def test_default_parameter(self):
        parameter_def = {"Param1": {"default": 0}}
        parameters = {}

        local = get_local_parameters(parameter_def, parameters)
        self.assertEquals(local["Param1"], 0)

    def test_missing_required(self):
        parameter_def = {"Param1": {"default": 0}, "Param2": {}}
        parameters = {}

        with self.assertRaises(MissingLocalParameterException) as cm:
            get_local_parameters(parameter_def, parameters)

        self.assertEquals("Param2", cm.exception.parameter)

    def test_supplied_parameter(self):
        parameter_def = {"Param1": {"default": 0}, "Param2": {}}
        parameters = {"Param1": 1, "Param2": 2}

        local = get_local_parameters(parameter_def, parameters)
        self.assertEquals(parameters, local)


class TestBuildParameter(unittest.TestCase):
    def test_base_parameter(self):
        p = build_parameter("BasicParam", {"type": "String"})
        p.validate()
        self.assertEquals(p.Type, "String")


class TestVariables(unittest.TestCase):

    def test_defined_variables(self):
        class TestBlueprint(Blueprint):
            VARIABLES = {
                "Param1": {"default": "default", "type": str},
            }

        blueprint = TestBlueprint(name="test", context=MagicMock())
        self.assertEqual(
            blueprint.defined_variables(),
            blueprint.VARIABLES,
        )

    def test_defined_variables_subclass(self):
        class TestBlueprint(Blueprint):
            VARIABLES = {
                "Param1": {"default": 0, "type": int},
                "Param2": {"default": 0, "type": int},
            }

        class TestBlueprintSublcass(TestBlueprint):
            def defined_variables(self):
                variables = super(TestBlueprintSublcass,
                                  self).defined_variables()
                variables["Param2"]["default"] = 1
                variables["Param3"] = {"default": 1, "type": int}
                return variables

        blueprint = TestBlueprintSublcass(name="test", context=MagicMock())
        variables = blueprint.defined_variables()
        self.assertEqual(len(variables.keys()), 3)
        self.assertEqual(variables["Param2"]["default"], 1)

    def test_get_variables_unresolved_variables(self):
        class TestBlueprint(Blueprint):
            pass

        blueprint = TestBlueprint(name="test", context=MagicMock())
        with self.assertRaises(UnresolvedVariables):
            blueprint.get_variables()

    def test_resolve_variables(self):
        class TestBlueprint(Blueprint):
            VARIABLES = {
                "Param1": {"default": 0, "type": int},
                "Param2": {"type": str},
            }

        blueprint = TestBlueprint(name="test", context=MagicMock())
        variables = [
            Variable("Param1", 1),
            Variable("Param2", "${other-stack::Output}"),
            Variable("Param3", 3),
        ]
        resolved_lookups = {
            mock_lookup("other-stack::Output"): "Test Output",
        }
        for var in variables:
            var.replace(resolved_lookups)

        blueprint.resolve_variables(variables)
        self.assertEqual(blueprint.resolved_variables["Param1"], 1)
        self.assertEqual(blueprint.resolved_variables["Param2"], "Test Output")
        self.assertIsNone(blueprint.resolved_variables.get("Param3"))

    def test_resolve_variables_lookup_returns_non_string(self):
        class TestBlueprint(Blueprint):
            VARIABLES = {
                "Param1": {"type": list},
            }

        blueprint = TestBlueprint(name="test", context=MagicMock())
        variables = [Variable("Param1", "${custom non-string-return-val}")]
        lookup = mock_lookup("non-string-return-val", "custom",
                             "custom non-string-return-val")
        resolved_lookups = {
            lookup: ["something"],
        }
        for var in variables:
            var.replace(resolved_lookups)

        blueprint.resolve_variables(variables)
        self.assertEqual(blueprint.resolved_variables["Param1"], ["something"])

    def test_resolve_variables_lookup_returns_troposphere_obj(self):
        class TestBlueprint(Blueprint):
            VARIABLES = {
                "Param1": {"type": Base64},
            }

        blueprint = TestBlueprint(name="test", context=MagicMock())
        variables = [Variable("Param1", "${custom non-string-return-val}")]
        lookup = mock_lookup("non-string-return-val", "custom",
                             "custom non-string-return-val")
        resolved_lookups = {
            lookup: Base64("test"),
        }
        for var in variables:
            var.replace(resolved_lookups)

        blueprint.resolve_variables(variables)
        self.assertEqual(blueprint.resolved_variables["Param1"].data,
                         Base64("test").data)

    def test_resolve_variables_lookup_returns_non_string_invalid_combo(self):
        class TestBlueprint(Blueprint):
            VARIABLES = {
                "Param1": {"type": list},
            }

        variables = [
            Variable(
                "Param1",
                "${custom non-string-return-val},${some-stack::Output}",
            )
        ]
        lookup = mock_lookup("non-string-return-val", "custom",
                             "custom non-string-return-val")
        resolved_lookups = {
            lookup: ["something"],
        }
        with self.assertRaises(InvalidLookupCombination):
            for var in variables:
                var.replace(resolved_lookups)

    def test_get_variables(self):
        class TestBlueprint(Blueprint):
            VARIABLES = {
                "Param1": {"type": int},
                "Param2": {"type": str},
            }

        blueprint = TestBlueprint(name="test", context=MagicMock())
        variables = [Variable("Param1", 1), Variable("Param2", "Test Output")]
        blueprint.resolve_variables(variables)
        variables = blueprint.get_variables()
        self.assertEqual(variables["Param1"], 1)
        self.assertEqual(variables["Param2"], "Test Output")

    def test_resolve_variables_missing_variable(self):
        class TestBlueprint(Blueprint):
            VARIABLES = {
                "Param1": {"type": int},
                "Param2": {"type": str},
            }

        blueprint = TestBlueprint(name="test", context=MagicMock())
        variables = [Variable("Param1", 1)]
        with self.assertRaises(MissingVariable):
            blueprint.resolve_variables(variables)

    def test_resolve_variables_incorrect_type(self):
        class TestBlueprint(Blueprint):
            VARIABLES = {
                "Param1": {"type": int},
            }

        blueprint = TestBlueprint(name="test", context=MagicMock())
        variables = [Variable("Param1", "Something")]
        with self.assertRaises(ValueError):
            blueprint.resolve_variables(variables)

    def test_get_variables_default_value(self):
        class TestBlueprint(Blueprint):
            VARIABLES = {
                "Param1": {"type": int, "default": 1},
                "Param2": {"type": str},
            }

        blueprint = TestBlueprint(name="test", context=MagicMock())
        variables = [Variable("Param2", "Test Output")]
        blueprint.resolve_variables(variables)
        variables = blueprint.get_variables()
        self.assertEqual(variables["Param1"], 1)
        self.assertEqual(variables["Param2"], "Test Output")

    def test_resolve_variables_convert_type(self):
        class TestBlueprint(Blueprint):
            VARIABLES = {
                "Param1": {"type": int},
            }

        blueprint = TestBlueprint(name="test", context=MagicMock())
        variables = [Variable("Param1", "1")]
        blueprint.resolve_variables(variables)
        variables = blueprint.get_variables()
        self.assertTrue(isinstance(variables["Param1"], int))

    def test_resolve_variables_cfn_type(self):
        class TestBlueprint(Blueprint):
            VARIABLES = {
                "Param1": {"type": CFNString},
            }

        blueprint = TestBlueprint(name="test", context=MagicMock())
        variables = [Variable("Param1", "Value")]
        blueprint.resolve_variables(variables)
        variables = blueprint.get_variables()
        self.assertTrue(isinstance(variables["Param1"], CFNParameter))

    def test_resolve_variables_cfn_type_list(self):
        class TestBlueprint(Blueprint):
            VARIABLES = {
                "Param1": {"type": EC2AvailabilityZoneNameList},
            }

        blueprint = TestBlueprint(name="test", context=MagicMock())
        variables = [Variable("Param1", ["us-east-1", "us-west-2"])]
        blueprint.resolve_variables(variables)
        variables = blueprint.get_variables()
        self.assertTrue(isinstance(variables["Param1"], CFNParameter))
        self.assertEqual(variables["Param1"].value, ["us-east-1", "us-west-2"])
        self.assertEqual(variables["Param1"].ref.data, Ref("Param1").data)
        parameters = blueprint.get_cfn_parameters()
        self.assertEqual(parameters["Param1"], ["us-east-1", "us-west-2"])

    def test_resolve_variables_cfn_type_list_invalid_value(self):
        class TestBlueprint(Blueprint):
            VARIABLES = {
                "Param1": {"type": EC2AvailabilityZoneNameList},
            }

        blueprint = TestBlueprint(name="test", context=MagicMock())
        variables = [Variable("Param1", {"main": "us-east-1"})]
        with self.assertRaises(ValueError):
            blueprint.resolve_variables(variables)
        variables = blueprint.get_variables()

    def test_get_parameters_cfn_type_list(self):
        class TestBlueprint(Blueprint):
            VARIABLES = {
                "Param1": {"type": EC2AvailabilityZoneNameList},
            }

        blueprint = TestBlueprint(name="test", context=MagicMock())
        parameters = blueprint._get_parameters()
        self.assertTrue("Param1" in parameters)
        parameter = parameters["Param1"]
        self.assertEqual(parameter["type"],
                         "List<AWS::EC2::AvailabilityZone::Name>")

    def test_get_parameters_cfn_type(self):
        class TestBlueprint(Blueprint):
            VARIABLES = {
                "Param1": {"type": CFNString},
            }

        blueprint = TestBlueprint(name="test", context=MagicMock())
        parameters = blueprint._get_parameters()
        self.assertTrue("Param1" in parameters)
        parameter = parameters["Param1"]
        self.assertEqual(parameter["type"], "String")

    def test_required_parameters_cfn_type(self):
        class TestBlueprint(Blueprint):
            VARIABLES = {
                "Param1": {"type": CFNString},
            }

        blueprint = TestBlueprint(name="test", context=MagicMock())
        blueprint.setup_parameters()
        self.assertEqual(blueprint.required_parameters[0][0], "Param1")

    def test_get_cfn_parameters(self):
        class TestBlueprint(Blueprint):
            VARIABLES = {
                "Param1": {"type": int},
                "Param2": {"type": CFNString},
            }

        blueprint = TestBlueprint(name="test", context=MagicMock())
        variables = [Variable("Param1", "1"), Variable("Param2", "Value")]
        blueprint.resolve_variables(variables)
        variables = blueprint.get_variables()
        self.assertEqual(len(variables), 2)
        parameters = blueprint.get_cfn_parameters()
        self.assertEqual(len(parameters.keys()), 1)
        self.assertEqual(parameters["Param2"], "Value")
