opentofu/internal/configs/hcl2shim/paths_test.go
Martin Atkins 31349a9c3a Move configs/ to internal/configs/
This is part of a general effort to move all of Terraform's non-library
package surface under internal in order to reinforce that these are for
internal use within Terraform only.

If you were previously importing packages under this prefix into an
external codebase, you could pin to an earlier release tag as an interim
solution until you've make a plan to achieve the same functionality some
other way.
2021-05-17 14:09:07 -07: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)
}
})
}
}