package values import ( "errors" "fmt" "io/ioutil" "os" "testing" "github.com/grafana/grafana/pkg/setting" "gopkg.in/ini.v1" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" . "github.com/smartystreets/goconvey/convey" "gopkg.in/yaml.v2" ) func TestValues(t *testing.T) { Convey("Values", t, func() { err := os.Setenv("INT", "1") So(err, ShouldBeNil) err = os.Setenv("STRING", "test") So(err, ShouldBeNil) err = os.Setenv("EMPTYSTRING", "") So(err, ShouldBeNil) err = os.Setenv("BOOL", "true") So(err, ShouldBeNil) Convey("IntValue", func() { type Data struct { Val IntValue `yaml:"val"` } d := &Data{} Convey("Should unmarshal simple number", func() { unmarshalingTest(`val: 1`, d) So(d.Val.Value(), ShouldEqual, 1) So(d.Val.Raw, ShouldEqual, "1") }) Convey("Should unmarshal env var", func() { unmarshalingTest(`val: $INT`, d) So(d.Val.Value(), ShouldEqual, 1) So(d.Val.Raw, ShouldEqual, "$INT") }) Convey("Should ignore empty value", func() { unmarshalingTest(`val: `, d) So(d.Val.Value(), ShouldEqual, 0) So(d.Val.Raw, ShouldEqual, "") }) }) Convey("StringValue", func() { type Data struct { Val StringValue `yaml:"val"` } d := &Data{} Convey("Should unmarshal simple string", func() { unmarshalingTest(`val: test`, d) So(d.Val.Value(), ShouldEqual, "test") So(d.Val.Raw, ShouldEqual, "test") }) Convey("Should unmarshal env var", func() { unmarshalingTest(`val: $STRING`, d) So(d.Val.Value(), ShouldEqual, "test") So(d.Val.Raw, ShouldEqual, "$STRING") }) Convey("Should ignore empty value", func() { unmarshalingTest(`val: `, d) So(d.Val.Value(), ShouldEqual, "") So(d.Val.Raw, ShouldEqual, "") }) Convey("empty var should have empty value", func() { unmarshalingTest(`val: $EMPTYSTRING`, d) So(d.Val.Value(), ShouldEqual, "") So(d.Val.Raw, ShouldEqual, "$EMPTYSTRING") }) Convey("$$ should be a literal $", func() { unmarshalingTest(`val: $$`, d) So(d.Val.Value(), ShouldEqual, "$") So(d.Val.Raw, ShouldEqual, "$$") }) Convey("$$ should be a literal $ and not expanded within a string", func() { unmarshalingTest(`val: mY,Passwo$$rd`, d) So(d.Val.Value(), ShouldEqual, "mY,Passwo$rd") So(d.Val.Raw, ShouldEqual, "mY,Passwo$$rd") }) }) Convey("BoolValue", func() { type Data struct { Val BoolValue `yaml:"val"` } d := &Data{} Convey("Should unmarshal bool value", func() { unmarshalingTest(`val: true`, d) So(d.Val.Value(), ShouldBeTrue) So(d.Val.Raw, ShouldEqual, "true") }) Convey("Should unmarshal explicit string", func() { unmarshalingTest(`val: "true"`, d) So(d.Val.Value(), ShouldBeTrue) So(d.Val.Raw, ShouldEqual, "true") }) Convey("Should unmarshal env var", func() { unmarshalingTest(`val: $BOOL`, d) So(d.Val.Value(), ShouldBeTrue) So(d.Val.Raw, ShouldEqual, "$BOOL") }) Convey("Should ignore empty value", func() { unmarshalingTest(`val: `, d) So(d.Val.Value(), ShouldBeFalse) So(d.Val.Raw, ShouldEqual, "") }) }) Convey("JSONValue", func() { type Data struct { Val JSONValue `yaml:"val"` } d := &Data{} Convey("Should unmarshal variable nesting", func() { doc := ` val: one: 1 two: $STRING three: - 1 - two - three: inside: $STRING - six: empty: four: nested: onemore: $INT multiline: > Some text with $STRING anchor: &label $INT anchored: *label ` unmarshalingTest(doc, d) type stringMap = map[string]interface{} So(d.Val.Value(), ShouldResemble, stringMap{ "one": 1, "two": "test", "three": []interface{}{ 1, "two", stringMap{ "three": stringMap{ "inside": "test", }, }, stringMap{ "six": stringMap{ "empty": interface{}(nil), }, }, }, "four": stringMap{ "nested": stringMap{ "onemore": "1", }, }, "multiline": "Some text with test\n", "anchor": "1", "anchored": "1", }) So(d.Val.Raw, ShouldResemble, stringMap{ "one": 1, "two": "$STRING", "three": []interface{}{ 1, "two", stringMap{ "three": stringMap{ "inside": "$STRING", }, }, stringMap{ "six": stringMap{ "empty": interface{}(nil), }, }, }, "four": stringMap{ "nested": stringMap{ "onemore": "$INT", }, }, "multiline": "Some text with $STRING\n", "anchor": "$INT", "anchored": "$INT", }) }) }) Convey("StringMapValue", func() { type Data struct { Val StringMapValue `yaml:"val"` } d := &Data{} Convey("Should unmarshal mapping", func() { doc := ` val: one: 1 two: "test string" three: $STRING four: true ` unmarshalingTest(doc, d) So(d.Val.Value(), ShouldResemble, map[string]string{ "one": "1", "two": "test string", "three": "test", "four": "true", }) So(d.Val.Raw, ShouldResemble, map[string]string{ "one": "1", "two": "test string", "three": "$STRING", "four": "true", }) }) }) Reset(func() { err := os.Unsetenv("INT") So(err, ShouldBeNil) err = os.Unsetenv("STRING") So(err, ShouldBeNil) err = os.Unsetenv("EMPTYSTRING") So(err, ShouldBeNil) err = os.Unsetenv("BOOL") So(err, ShouldBeNil) }) }) } func unmarshalingTest(document string, out interface{}) { err := yaml.Unmarshal([]byte(document), out) So(err, ShouldBeNil) } func TestValues_readFile(t *testing.T) { type Data struct { Val StringValue `yaml:"val"` } f, err := ioutil.TempFile(os.TempDir(), "file expansion *") require.NoError(t, err) file := f.Name() defer func() { require.NoError(t, os.Remove(file)) }() const expected = "hello, world" _, err = f.WriteString(expected) require.NoError(t, err) require.NoError(t, f.Close()) data := &Data{} err = yaml.Unmarshal([]byte(fmt.Sprintf("val: $__file{%s}", file)), data) require.NoError(t, err) assert.Equal(t, expected, data.Val.Value()) } func TestValues_expanderError(t *testing.T) { type Data struct { Top JSONValue `yaml:"top"` } setting.AddExpander("fail", 0, failExpander{}) data := &Data{} err := yaml.Unmarshal([]byte("top:\n val: $__fail{val}"), data) require.Error(t, err) require.Truef(t, errors.Is(err, errExpand), "expected error to wrap: %v\ngot: %v", errExpand, err) assert.Empty(t, data) } var errExpand = errors.New("test error: bad expander") type failExpander struct{} func (f failExpander) SetupExpander(file *ini.File) error { return nil } func (f failExpander) Expand(s string) (string, error) { return "", errExpand }