opentofu/config/hcl2shim/paths_test.go
James Bardin bb62aba651 add (forces new resource) to provider test diffs
Add the (forces new resource) annotation to the diff output for provider
tests failures when we can. This helps providers narrow down what might
be triggering changes when encountering test failures with the new SDK.
2019-03-22 15:30:51 -04:00

409 lines
8.5 KiB
Go

package hcl2shim
import (
"fmt"
"reflect"
"strconv"
"strings"
"testing"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/google/go-cmp/cmp"
"github.com/zclconf/go-cty/cty"
)
var (
ignoreUnexported = cmpopts.IgnoreUnexported(cty.GetAttrStep{}, cty.IndexStep{})
valueComparer = cmp.Comparer(cty.Value.RawEquals)
)
func TestPathFromFlatmap(t *testing.T) {
tests := []struct {
Flatmap string
Type cty.Type
Want cty.Path
WantErr string
}{
{
Flatmap: "",
Type: cty.EmptyObject,
Want: nil,
},
{
Flatmap: "attr",
Type: cty.EmptyObject,
Want: nil,
WantErr: `attribute "attr" not found`,
},
{
Flatmap: "foo",
Type: cty.Object(map[string]cty.Type{
"foo": cty.String,
}),
Want: cty.Path{
cty.GetAttrStep{Name: "foo"},
},
},
{
Flatmap: "foo.#",
Type: cty.Object(map[string]cty.Type{
"foo": cty.List(cty.String),
}),
Want: cty.Path{
cty.GetAttrStep{Name: "foo"},
},
},
{
Flatmap: "foo.1",
Type: cty.Object(map[string]cty.Type{
"foo": cty.List(cty.String),
}),
Want: cty.Path{
cty.GetAttrStep{Name: "foo"},
cty.IndexStep{Key: cty.NumberIntVal(1)},
},
},
{
Flatmap: "foo.1",
Type: cty.Object(map[string]cty.Type{
"foo": cty.Tuple([]cty.Type{
cty.String,
cty.Bool,
}),
}),
Want: cty.Path{
cty.GetAttrStep{Name: "foo"},
cty.IndexStep{Key: cty.NumberIntVal(1)},
},
},
{
// a set index returns the set itself, since this being applied to
// a diff and the set is changing.
Flatmap: "foo.24534534",
Type: cty.Object(map[string]cty.Type{
"foo": cty.Set(cty.String),
}),
Want: cty.Path{
cty.GetAttrStep{Name: "foo"},
},
},
{
Flatmap: "foo.%",
Type: cty.Object(map[string]cty.Type{
"foo": cty.Map(cty.String),
}),
Want: cty.Path{
cty.GetAttrStep{Name: "foo"},
},
},
{
Flatmap: "foo.baz",
Type: cty.Object(map[string]cty.Type{
"foo": cty.Map(cty.Bool),
}),
Want: cty.Path{
cty.GetAttrStep{Name: "foo"},
cty.IndexStep{Key: cty.StringVal("baz")},
},
},
{
Flatmap: "foo.bar.baz",
Type: cty.Object(map[string]cty.Type{
"foo": cty.Map(
cty.Map(cty.Bool),
),
}),
Want: cty.Path{
cty.GetAttrStep{Name: "foo"},
cty.IndexStep{Key: cty.StringVal("bar")},
cty.IndexStep{Key: cty.StringVal("baz")},
},
},
{
Flatmap: "foo.bar.baz",
Type: cty.Object(map[string]cty.Type{
"foo": cty.Map(
cty.Object(map[string]cty.Type{
"baz": cty.String,
}),
),
}),
Want: cty.Path{
cty.GetAttrStep{Name: "foo"},
cty.IndexStep{Key: cty.StringVal("bar")},
cty.GetAttrStep{Name: "baz"},
},
},
{
Flatmap: "foo.0.bar",
Type: cty.Object(map[string]cty.Type{
"foo": cty.List(cty.Object(map[string]cty.Type{
"bar": cty.String,
"baz": cty.Bool,
})),
}),
Want: cty.Path{
cty.GetAttrStep{Name: "foo"},
cty.IndexStep{Key: cty.NumberIntVal(0)},
cty.GetAttrStep{Name: "bar"},
},
},
{
Flatmap: "foo.34534534.baz",
Type: cty.Object(map[string]cty.Type{
"foo": cty.Set(cty.Object(map[string]cty.Type{
"bar": cty.String,
"baz": cty.Bool,
})),
}),
Want: cty.Path{
cty.GetAttrStep{Name: "foo"},
},
},
{
Flatmap: "foo.bar.bang",
Type: cty.Object(map[string]cty.Type{
"foo": cty.String,
}),
WantErr: `invalid step "bar.bang"`,
},
{
// there should not be any attribute names with dots
Flatmap: "foo.bar.bang",
Type: cty.Object(map[string]cty.Type{
"foo.bar": cty.Map(cty.String),
}),
WantErr: `attribute "foo" not found`,
},
{
// We can only handle key names with dots if the map elements are a
// primitive type.
Flatmap: "foo.bar.bop",
Type: cty.Object(map[string]cty.Type{
"foo": cty.Map(cty.String),
}),
Want: cty.Path{
cty.GetAttrStep{Name: "foo"},
cty.IndexStep{Key: cty.StringVal("bar.bop")},
},
},
{
Flatmap: "foo.bar.0.baz",
Type: cty.Object(map[string]cty.Type{
"foo": cty.Map(
cty.List(
cty.Map(cty.String),
),
),
}),
Want: cty.Path{
cty.GetAttrStep{Name: "foo"},
cty.IndexStep{Key: cty.StringVal("bar")},
cty.IndexStep{Key: cty.NumberIntVal(0)},
cty.IndexStep{Key: cty.StringVal("baz")},
},
},
}
for _, test := range tests {
t.Run(fmt.Sprintf("%s as %#v", test.Flatmap, test.Type), func(t *testing.T) {
got, err := requiresReplacePath(test.Flatmap, test.Type)
if test.WantErr != "" {
if err == nil {
t.Fatalf("succeeded; want error: %s", test.WantErr)
}
if got, want := err.Error(), test.WantErr; !strings.Contains(got, want) {
t.Fatalf("wrong error\ngot: %s\nwant: %s", got, want)
}
return
} else {
if err != nil {
t.Fatalf("unexpected error: %s", err.Error())
}
}
if !reflect.DeepEqual(got, test.Want) {
t.Fatalf("incorrect path\ngot: %#v\nwant: %#v\n", got, test.Want)
}
})
}
}
func TestRequiresReplace(t *testing.T) {
for _, tc := range []struct {
name string
attrs []string
expected []cty.Path
ty cty.Type
}{
{
name: "basic",
attrs: []string{
"foo",
},
ty: cty.Object(map[string]cty.Type{
"foo": cty.String,
}),
expected: []cty.Path{
cty.Path{cty.GetAttrStep{Name: "foo"}},
},
},
{
name: "two",
attrs: []string{
"foo",
"bar",
},
ty: cty.Object(map[string]cty.Type{
"foo": cty.String,
"bar": cty.String,
}),
expected: []cty.Path{
cty.Path{cty.GetAttrStep{Name: "foo"}},
cty.Path{cty.GetAttrStep{Name: "bar"}},
},
},
{
name: "nested object",
attrs: []string{
"foo.bar",
},
ty: cty.Object(map[string]cty.Type{
"foo": cty.Object(map[string]cty.Type{
"bar": cty.String,
}),
}),
expected: []cty.Path{
cty.Path{cty.GetAttrStep{Name: "foo"}, cty.GetAttrStep{Name: "bar"}},
},
},
{
name: "nested objects",
attrs: []string{
"foo.bar.baz",
},
ty: cty.Object(map[string]cty.Type{
"foo": cty.Object(map[string]cty.Type{
"bar": cty.Object(map[string]cty.Type{
"baz": cty.String,
}),
}),
}),
expected: []cty.Path{
cty.Path{cty.GetAttrStep{Name: "foo"}, cty.GetAttrStep{Name: "bar"}, cty.GetAttrStep{Name: "baz"}},
},
},
{
name: "nested map",
attrs: []string{
"foo.%",
"foo.bar",
},
ty: cty.Object(map[string]cty.Type{
"foo": cty.Map(cty.String),
}),
expected: []cty.Path{
cty.Path{cty.GetAttrStep{Name: "foo"}},
},
},
{
name: "nested list",
attrs: []string{
"foo.#",
"foo.1",
},
ty: cty.Object(map[string]cty.Type{
"foo": cty.Map(cty.String),
}),
expected: []cty.Path{
cty.Path{cty.GetAttrStep{Name: "foo"}},
},
},
{
name: "object in map",
attrs: []string{
"foo.bar.baz",
},
ty: cty.Object(map[string]cty.Type{
"foo": cty.Map(cty.Object(
map[string]cty.Type{
"baz": cty.String,
},
)),
}),
expected: []cty.Path{
cty.Path{cty.GetAttrStep{Name: "foo"}, cty.IndexStep{Key: cty.StringVal("bar")}, cty.GetAttrStep{Name: "baz"}},
},
},
{
name: "object in list",
attrs: []string{
"foo.1.baz",
},
ty: cty.Object(map[string]cty.Type{
"foo": cty.List(cty.Object(
map[string]cty.Type{
"baz": cty.String,
},
)),
}),
expected: []cty.Path{
cty.Path{cty.GetAttrStep{Name: "foo"}, cty.IndexStep{Key: cty.NumberIntVal(1)}, cty.GetAttrStep{Name: "baz"}},
},
},
} {
t.Run(tc.name, func(t *testing.T) {
rp, err := RequiresReplace(tc.attrs, tc.ty)
if err != nil {
t.Fatal(err)
}
if !cmp.Equal(tc.expected, rp, ignoreUnexported, valueComparer) {
t.Fatalf("\nexpected: %#v\ngot: %#v\n", tc.expected, rp)
}
})
}
}
func TestFlatmapKeyFromPath(t *testing.T) {
for i, tc := range []struct {
path cty.Path
attr string
}{
{
path: cty.Path{
cty.GetAttrStep{Name: "force_new"},
},
attr: "force_new",
},
{
path: cty.Path{
cty.GetAttrStep{Name: "attr"},
cty.IndexStep{Key: cty.NumberIntVal(0)},
cty.GetAttrStep{Name: "force_new"},
},
attr: "attr.0.force_new",
},
{
path: cty.Path{
cty.GetAttrStep{Name: "attr"},
cty.IndexStep{Key: cty.StringVal("key")},
cty.GetAttrStep{Name: "obj_attr"},
cty.IndexStep{Key: cty.NumberIntVal(0)},
cty.GetAttrStep{Name: "force_new"},
},
attr: "attr.key.obj_attr.0.force_new",
},
} {
t.Run(strconv.Itoa(i), func(t *testing.T) {
attr := FlatmapKeyFromPath(tc.path)
if attr != tc.attr {
t.Fatalf("expected:%q got:%q", tc.attr, attr)
}
})
}
}