blob: 00fb987311384950e7d40dcc20e72c0a4507fed1 [file] [log] [blame]
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
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)
}
})
}
}