def test_get_parameter_values(self): """Test get parameter values.""" class TestBlueprint(Blueprint): """Test blueprint.""" VARIABLES = { "Param1": { "type": int }, "Param2": { "type": CFNString }, } blueprint = TestBlueprint(name="test", context=MagicMock()) variables = [ Variable("Param1", 1, 'cfngin'), Variable("Param2", "Value", 'cfngin') ] blueprint.resolve_variables(variables) variables = blueprint.get_variables() self.assertEqual(len(variables), 2) parameters = blueprint.get_parameter_values() self.assertEqual(len(parameters), 1) self.assertEqual(parameters["Param2"], "Value")
def test_default(self, runway_context): """Test resolution of a default value.""" name = "/test/param" value = "test value" stubber = runway_context.add_stubber("ssm") var = Variable( "test_var", "${ssm /test/invalid::load=json, default=${ssm %s}}" % name, variable_type="runway", ) stubber.add_response( "get_parameter", get_parameter_response(name, value), get_parameter_request(name), ) stubber.add_client_error( "get_parameter", "ParameterNotFound", expected_params=get_parameter_request("/test/invalid"), ) with stubber as stub: var.resolve(context=runway_context) assert var.value == value stub.assert_no_pending_responses()
def test_resolve_variables(self): """Test resolve variables.""" class TestBlueprint(Blueprint): """Test blueprint.""" VARIABLES = { "Param1": { "default": 0, "type": int }, "Param2": { "type": str }, } blueprint = TestBlueprint(name="test", context=MagicMock()) variables = [ Variable("Param1", 1, 'cfngin'), Variable("Param2", "${output other-stack::Output}", 'cfngin'), Variable("Param3", 3, 'cfngin'), ] variables[1]._value._resolve("Test Output") 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_value_complex_str(self): """Multiple lookups should be usable within a single string.""" var = Variable('test', 'the ${env what} was ${env test}ful', 'runway') var.resolve(CONTEXT) self.assertEqual( var.value, 'the {} was {}ful'.format(VALUE['what'], VALUE['test']))
def to_json(self, variables=None): """Render the blueprint and return the template in json form. Args: variables (Optional[Dict[str, Any]]): Dictionary providing/overriding variable values. Returns: str: Rhe rendered CFN JSON template. """ variables_to_resolve = [] if variables: for key, value in variables.items(): variables_to_resolve.append(Variable(key, value, 'cfngin')) for k in self.get_parameter_definitions(): if not variables or k not in variables: # The provided value for a CFN parameter has no effect in this # context (generating the CFN template), so any string can be # provided for its value - just needs to be something variables_to_resolve.append( Variable(k, 'unused_value', 'cfngin')) self.resolve_variables(variables_to_resolve) return self.render_template()[1]
def test_simple_lookup(self) -> None: """Test simple lookup.""" var = Variable("Param1", "${test query}") assert isinstance(var._value, VariableValueLookup) var.resolve(MagicMock(), MagicMock()) assert var.resolved is True assert var.value == "resolved"
def test_dependencies(self, mocker: MockerFixture) -> None: """Test dependencies.""" assert Variable("Param", "val").dependencies == set() mocker.patch.object(VariableValue, "parse_obj", return_value=MagicMock(dependencies={"test"})) assert Variable("Param", "val").dependencies == {"test"}
def test_value_lookup_to_dict(self): """Variable lookups should be resolvable to a dict value.""" var = Variable('test', '${env dict_val}', 'runway') var.resolve(CONTEXT) # need to use data prop to compare the MutableMap contents self.assertEqual(var.value.data, VALUE['dict_val'])
def test_value_complex_str(self): """Multiple lookups should be usable within a single string.""" var = Variable("test", "the ${env what} was ${env test}ful", "runway") var.resolve(CONTEXT) self.assertEqual( var.value, "the {} was {}ful".format(VALUE["what"], VALUE["test"]))
def test_loaded_value(self, runway_context: MockRunwayContext) -> None: """Test resolution of a JSON value.""" name = "/test/param" raw_value = { "nested": { "bool": True, "nest_key": "nested_val" }, "test_key": "test_val", } stubber = runway_context.add_stubber("ssm") parsers = ["json", "yaml"] tests = [ { "lookup": "${{ssm {name}::load={parser}}}", "expected": raw_value }, { "lookup": "${{ssm {name}::load={parser},transform=str,indent=2}}", "expected": json.dumps(json.dumps(raw_value, indent=2)), }, { "lookup": "${{ssm {name}::load={parser},get=nested}}", "expected": raw_value["nested"], }, { "lookup": "${{ssm {name}::load={parser},get=nested.bool,transform=str}}", "expected": json.dumps("True"), }, ] for parser in parsers: for test in tests: var = Variable( "test_var.{}".format(parser), test["lookup"].format(name=name, parser=parser), # type: ignore variable_type="runway", ) if parser == "json": dumped_value = json.dumps(raw_value) elif parser == "yaml": dumped_value = yaml.dump(raw_value) else: raise ValueError stubber.add_response( "get_parameter", get_parameter_response(name, dumped_value), get_parameter_request(name), ) with stubber as stub: var.resolve(context=runway_context) assert var.value == test["expected"] stub.assert_no_pending_responses()
def test_legacy_valid_hook_data(self, cfngin_context): """Test valid hook data.""" cfngin_context.set_hook_data('fake_hook', {'result': 'good'}) variable = Variable('test', "${hook_data fake_hook::result}", variable_type='cfngin') with pytest.warns(DeprecationWarning): variable.resolve(cfngin_context) assert variable.value == 'good'
def test_multiple_lookup_string(self, mocker: MockerFixture) -> None: """Test multiple lookup string.""" var = Variable("Param1", "url://${test query0}@${test query1}") assert isinstance(var._value, VariableValueConcatenation) mocker.patch.object(MockLookupHandler, "side_effect", ["resolved0", "resolved1"]) var.resolve(MagicMock(), MagicMock()) assert var.resolved is True assert var.value == "url://resolved0@resolved1"
def test_legacy_valid_hook_data(self, cfngin_context): """Test valid hook data.""" cfngin_context.set_hook_data("fake_hook", {"result": "good"}) variable = Variable("test", "${hook_data fake_hook::result}", variable_type="cfngin") with pytest.warns(DeprecationWarning): variable.resolve(cfngin_context) assert variable.value == "good"
def test_value_simple_str_lookup(self): """Test value for simple str lookup.""" var = Variable("test", "${env test}", "runway") self.assertFalse(var.resolved) var.resolve(CONTEXT) self.assertTrue(var.resolved) self.assertEqual(var.value, VALUE["test"])
def test_resolve_variable_provided_resolved(self): """Test resolve variable provided resolved.""" var_name = "testVar" var_def = {"type": str} provided_variable = Variable(var_name, "${mock 1}", "cfngin") provided_variable.resolve(context=MagicMock(), provider=MagicMock()) blueprint_name = "testBlueprint" value = resolve_variable(var_name, var_def, provided_variable, blueprint_name) self.assertEqual(value, "1")
def test_value_simple_str_lookup(self): """Test value for simple str lookup.""" var = Variable('test', '${env test}', 'runway') self.assertFalse(var.resolved) var.resolve(CONTEXT) self.assertTrue(var.resolved) self.assertEqual(var.value, VALUE['test'])
def test_legacy_invalid_hook_data(self, cfngin_context): """Test invalid hook data.""" cfngin_context.set_hook_data("fake_hook", {"result": "good"}) variable = Variable("test", "${hook_data fake_hook::bad_key}", variable_type="cfngin") with pytest.raises(FailedVariableLookup) as err, pytest.warns( DeprecationWarning): variable.resolve(cfngin_context) assert "ValueError" in str(err.value)
def test_resolve(self, mocker: MockerFixture) -> None: """Test resolve.""" context = MagicMock() provider = MagicMock() obj = Variable("Param", "val") mock_resolve = mocker.patch.object(obj._value, "resolve") assert not obj.resolve(context, provider, kwarg="something") mock_resolve.assert_called_once_with(context, provider=provider, variables=None, kwarg="something")
def test_not_found(self, cfngin_context): """Test value not found and no default.""" variable = Variable('test', '${hook_data fake_hook.bad.result}', variable_type='cfngin') with pytest.raises(FailedVariableLookup) as err: variable.resolve(cfngin_context) assert 'ValueError' in str(err.value) assert 'Could not find a value for "fake_hook.bad.result"' in str( err.value)
def test_legacy_invalid_hook_data(self, cfngin_context): """Test invalid hook data.""" cfngin_context.set_hook_data('fake_hook', {'result': 'good'}) variable = Variable('test', "${hook_data fake_hook::bad_key}", variable_type='cfngin') with pytest.raises(FailedVariableLookup) as err, \ pytest.warns(DeprecationWarning): variable.resolve(cfngin_context) assert 'ValueError' in str(err.value)
def test_legacy_bad_value_hook_data(self, cfngin_context): """Test bad value hook data.""" variable = Variable('test', "${hook_data fake_hook::bad_key}", variable_type='cfngin') with pytest.raises(FailedVariableLookup) as err, \ pytest.warns(DeprecationWarning): variable.resolve(cfngin_context) assert 'ValueError' in str(err.value)
def test_not_found(self, cfngin_context: MockCFNginContext) -> None: """Test value not found and no default.""" variable = Variable("test", "${hook_data fake_hook.bad.result}", variable_type="cfngin") with pytest.raises(FailedVariableLookup) as err: variable.resolve(cfngin_context) assert str(err.value) == ( f'Could not resolve lookup "{variable._raw_value}" for variable "{variable.name}"' ) assert "Could not find a value for" in str(err.value.__cause__)
def test_resolve_failed(self, mocker: MockerFixture) -> None: """Test resolve FailedLookup.""" context = MagicMock() provider = MagicMock() obj = Variable("Param", "val") lookup_error = FailedLookup("something", KeyError("cause")) # type: ignore mocker.patch.object(obj._value, "resolve", side_effect=lookup_error) with pytest.raises(FailedVariableLookup) as excinfo: obj.resolve(context, provider, kwarg="something") assert excinfo.value.cause == lookup_error assert excinfo.value.variable == obj
def test_multiple_lookup_dict(self, mocker: MockerFixture) -> None: """Test multiple lookup dict.""" mocker.patch.object(MockLookupHandler, "side_effect", ["resolved0", "resolved1"]) value = { "something": "${test query0}", "other": "${test query1}", } var = Variable("Param1", value) assert isinstance(var._value, VariableValueDict) var.resolve(MagicMock(), MagicMock()) assert var.value == {"something": "resolved0", "other": "resolved1"}
def test_resolve_variable_allowed_values(self): """Test resolve variable allowed values.""" var_name = "testVar" var_def = {"type": str, "allowed_values": ["allowed"]} provided_variable = Variable(var_name, "not_allowed", "cfngin") blueprint_name = "testBlueprint" with self.assertRaises(ValueError): resolve_variable(var_name, var_def, provided_variable, blueprint_name) provided_variable = Variable(var_name, "allowed", "cfngin") value = resolve_variable(var_name, var_def, provided_variable, blueprint_name) self.assertEqual(value, "allowed")
def test_j2_to_json(self): """Verify jinja2 template parsing.""" expected_json = json.dumps( { "AWSTemplateFormatVersion": "2010-09-09", "Description": "TestTemplate", "Parameters": { "Param1": { "Type": "String" }, "Param2": { "Default": "default", "Type": "CommaDelimitedList" }, }, "Resources": { "Dummy": { "Type": "AWS::CloudFormation::WaitConditionHandle" } }, "Outputs": { "DummyId": { "Value": "dummy-bar-param1val-foo-1234" } }, }, sort_keys=True, indent=4, ) blueprint = RawTemplateBlueprint( name="stack1", context=mock_context( extra_config_args={ "stacks": [{ "name": "stack1", "template_path": "unused", "variables": { "Param1": "param1val", "bar": "foo" }, }] }, environment={"foo": "bar"}, ), raw_template_path=RAW_J2_TEMPLATE_PATH, ) blueprint.resolve_variables([ Variable("Param1", "param1val", "cfngin"), Variable("bar", "foo", "cfngin"), ]) self.assertEqual(expected_json, blueprint.to_json())
def test_basic(self, cfngin_context, runway_context): """Test resolution of a basic lookup.""" name = "/test/param" value = "test value" cfngin_stubber = cfngin_context.add_stubber("ssm") runway_stubber = runway_context.add_stubber("ssm") cfngin_var = Variable("test_var", "${ssm %s}" % name, variable_type="cfngin") runway_var = Variable("test_var", "${ssm %s}" % name, variable_type="runway") for stubber in [cfngin_stubber, runway_stubber]: stubber.add_response( "get_parameter", get_parameter_response(name, value), get_parameter_request(name), ) with cfngin_stubber as cfn_stub, runway_stubber as rw_stub: cfngin_var.resolve(context=cfngin_context) assert cfngin_var.value == value runway_var.resolve(context=runway_context) assert runway_var.value == value cfn_stub.assert_no_pending_responses() rw_stub.assert_no_pending_responses()
def test_resolve_variable_allowed_values() -> None: """Test resolve_variable.""" var_name = "testVar" var_def: BlueprintVariableTypeDef = {"type": str, "allowed_values": ["allowed"]} with pytest.raises(ValueError): resolve_variable( var_name, var_def, Variable(var_name, "not_allowed", "cfngin"), "test" ) assert ( resolve_variable( var_name, var_def, Variable(var_name, "allowed", "cfngin"), "test" ) == "allowed" )
def test_basic(self, cfngin_context, runway_context): """Test resolution of a basic lookup.""" name = '/test/param' value = 'test value' cfngin_stubber = cfngin_context.add_stubber('ssm') runway_stubber = runway_context.add_stubber('ssm') cfngin_var = Variable('test_var', '${ssm %s}' % name, variable_type='cfngin') runway_var = Variable('test_var', '${ssm %s}' % name, variable_type='runway') for stubber in [cfngin_stubber, runway_stubber]: stubber.add_response('get_parameter', get_parameter_response(name, value), get_parameter_request(name)) with cfngin_stubber as cfn_stub, runway_stubber as rw_stub: cfngin_var.resolve(context=cfngin_context) assert cfngin_var.value == value runway_var.resolve(context=runway_context) assert runway_var.value == value cfn_stub.assert_no_pending_responses() rw_stub.assert_no_pending_responses()
def test_resolve_variables_lookup_returns_non_string_invalid_combo(self): """Test resolve variables lookup returns non string invalid combo.""" def return_list_something(*_args, **_kwargs): """Return list something.""" return ["something"] register_lookup_handler("custom", return_list_something) variable = Variable( "Param1", "${custom non-string-return-val},${output some-stack::Output}", 'cfngin') variable._value[0].resolve({}, {}) with self.assertRaises(InvalidLookupCombination): variable.value() # pylint: disable=not-callable