opentofu/helper/plugin/unknown_test.go
James Bardin 82588af892 switch blocks based on value type, and check attrs
Check attributes on null objects, and fill in unknowns. If we're
evaluating the object, it either means we are at the top level, or a
NestingSingle block was present, and in either case we need to treat the
attributes as null rather than the entire object.

Switch on the block types rather than Nesting, so we don't need add any
logic to change between List/Tuple or Map/Object when DynamicPseudoType
is involved.
2019-02-08 14:46:29 -05:00

484 lines
12 KiB
Go

package plugin
import (
"testing"
"github.com/hashicorp/terraform/configs/configschema"
"github.com/zclconf/go-cty/cty"
)
func TestSetUnknowns(t *testing.T) {
for n, tc := range map[string]struct {
Schema *configschema.Block
Val cty.Value
Expected cty.Value
}{
"empty": {
&configschema.Block{},
cty.EmptyObjectVal,
cty.EmptyObjectVal,
},
"no prior": {
&configschema.Block{
Attributes: map[string]*configschema.Attribute{
"foo": {
Type: cty.String,
Optional: true,
},
"bar": {
Type: cty.String,
Computed: true,
},
},
BlockTypes: map[string]*configschema.NestedBlock{
"baz": {
Nesting: configschema.NestingSingle,
Block: configschema.Block{
Attributes: map[string]*configschema.Attribute{
"boz": {
Type: cty.String,
Optional: true,
Computed: true,
},
"biz": {
Type: cty.String,
Optional: true,
Computed: true,
},
},
},
},
},
},
cty.NullVal(cty.Object(map[string]cty.Type{
"foo": cty.String,
"bar": cty.String,
"baz": cty.Object(map[string]cty.Type{
"boz": cty.String,
"biz": cty.String,
}),
})),
cty.ObjectVal(map[string]cty.Value{
"foo": cty.NullVal(cty.String),
"bar": cty.UnknownVal(cty.String),
}),
},
"null stays null": {
// if the object has no computed attributes, it should stay null
&configschema.Block{
Attributes: map[string]*configschema.Attribute{
"foo": &configschema.Attribute{
Type: cty.String,
},
},
BlockTypes: map[string]*configschema.NestedBlock{
"baz": {
Nesting: configschema.NestingSet,
Block: configschema.Block{
Attributes: map[string]*configschema.Attribute{
"boz": {
Type: cty.String,
Optional: true,
Computed: true,
},
},
},
},
},
},
cty.NullVal(cty.Object(map[string]cty.Type{
"foo": cty.String,
"baz": cty.Set(cty.Object(map[string]cty.Type{
"boz": cty.String,
})),
})),
cty.NullVal(cty.Object(map[string]cty.Type{
"foo": cty.String,
"baz": cty.Set(cty.Object(map[string]cty.Type{
"boz": cty.String,
})),
})),
},
"no prior with set": {
// the set value should remain null
&configschema.Block{
Attributes: map[string]*configschema.Attribute{
"foo": &configschema.Attribute{
Type: cty.String,
Computed: true,
},
},
BlockTypes: map[string]*configschema.NestedBlock{
"baz": {
Nesting: configschema.NestingSet,
Block: configschema.Block{
Attributes: map[string]*configschema.Attribute{
"boz": {
Type: cty.String,
Optional: true,
Computed: true,
},
},
},
},
},
},
cty.NullVal(cty.Object(map[string]cty.Type{
"foo": cty.String,
"baz": cty.Set(cty.Object(map[string]cty.Type{
"boz": cty.String,
})),
})),
cty.ObjectVal(map[string]cty.Value{
"foo": cty.UnknownVal(cty.String),
}),
},
"prior attributes": {
&configschema.Block{
Attributes: map[string]*configschema.Attribute{
"foo": {
Type: cty.String,
Optional: true,
},
"bar": {
Type: cty.String,
Computed: true,
},
"baz": {
Type: cty.String,
Optional: true,
Computed: true,
},
"boz": {
Type: cty.String,
Optional: true,
Computed: true,
},
},
},
cty.ObjectVal(map[string]cty.Value{
"foo": cty.StringVal("bonjour"),
"bar": cty.StringVal("petit dejeuner"),
"baz": cty.StringVal("grande dejeuner"),
}),
cty.ObjectVal(map[string]cty.Value{
"foo": cty.StringVal("bonjour"),
"bar": cty.StringVal("petit dejeuner"),
"baz": cty.StringVal("grande dejeuner"),
"boz": cty.UnknownVal(cty.String),
}),
},
"prior nested single": {
&configschema.Block{
BlockTypes: map[string]*configschema.NestedBlock{
"foo": {
Nesting: configschema.NestingSingle,
Block: configschema.Block{
Attributes: map[string]*configschema.Attribute{
"bar": {
Type: cty.String,
Optional: true,
Computed: true,
},
"baz": {
Type: cty.String,
Optional: true,
Computed: true,
},
},
},
},
},
},
cty.ObjectVal(map[string]cty.Value{
"foo": cty.ObjectVal(map[string]cty.Value{
"bar": cty.StringVal("beep"),
}),
}),
cty.ObjectVal(map[string]cty.Value{
"foo": cty.ObjectVal(map[string]cty.Value{
"bar": cty.StringVal("beep"),
"baz": cty.UnknownVal(cty.String),
}),
}),
},
"prior nested list": {
&configschema.Block{
BlockTypes: map[string]*configschema.NestedBlock{
"foo": {
Nesting: configschema.NestingList,
Block: configschema.Block{
Attributes: map[string]*configschema.Attribute{
"bar": {
Type: cty.String,
Optional: true,
Computed: true,
},
"baz": {
Type: cty.String,
Optional: true,
Computed: true,
},
},
},
},
},
},
cty.ObjectVal(map[string]cty.Value{
"foo": cty.ListVal([]cty.Value{
cty.ObjectVal(map[string]cty.Value{
"bar": cty.StringVal("bap"),
}),
cty.ObjectVal(map[string]cty.Value{
"bar": cty.StringVal("blep"),
}),
}),
}),
cty.ObjectVal(map[string]cty.Value{
"foo": cty.ListVal([]cty.Value{
cty.ObjectVal(map[string]cty.Value{
"bar": cty.StringVal("bap"),
"baz": cty.UnknownVal(cty.String),
}),
cty.ObjectVal(map[string]cty.Value{
"bar": cty.StringVal("blep"),
"baz": cty.UnknownVal(cty.String),
}),
}),
}),
},
"prior nested map": {
&configschema.Block{
BlockTypes: map[string]*configschema.NestedBlock{
"foo": {
Nesting: configschema.NestingMap,
Block: configschema.Block{
Attributes: map[string]*configschema.Attribute{
"bar": {
Type: cty.String,
Optional: true,
Computed: true,
},
"baz": {
Type: cty.String,
Optional: true,
Computed: true,
},
},
},
},
},
},
cty.ObjectVal(map[string]cty.Value{
"foo": cty.MapVal(map[string]cty.Value{
"a": cty.ObjectVal(map[string]cty.Value{
"bar": cty.NullVal(cty.String),
"baz": cty.StringVal("boop"),
}),
"b": cty.ObjectVal(map[string]cty.Value{
"bar": cty.StringVal("blep"),
"baz": cty.NullVal(cty.String),
}),
}),
}),
cty.ObjectVal(map[string]cty.Value{
"foo": cty.MapVal(map[string]cty.Value{
"a": cty.ObjectVal(map[string]cty.Value{
"bar": cty.UnknownVal(cty.String),
"baz": cty.StringVal("boop"),
}),
"b": cty.ObjectVal(map[string]cty.Value{
"bar": cty.StringVal("blep"),
"baz": cty.UnknownVal(cty.String),
}),
}),
}),
},
"prior nested set": {
&configschema.Block{
BlockTypes: map[string]*configschema.NestedBlock{
"foo": {
Nesting: configschema.NestingSet,
Block: configschema.Block{
Attributes: map[string]*configschema.Attribute{
"bar": {
Type: cty.String,
Optional: true,
},
"baz": {
Type: cty.String,
Optional: true,
Computed: true,
},
},
},
},
},
},
cty.ObjectVal(map[string]cty.Value{
"foo": cty.SetVal([]cty.Value{
cty.ObjectVal(map[string]cty.Value{
"bar": cty.StringVal("blep"),
"baz": cty.NullVal(cty.String),
}),
cty.ObjectVal(map[string]cty.Value{
"bar": cty.StringVal("boop"),
"baz": cty.NullVal(cty.String),
}),
}),
}),
cty.ObjectVal(map[string]cty.Value{
"foo": cty.SetVal([]cty.Value{
cty.ObjectVal(map[string]cty.Value{
"bar": cty.StringVal("blep"),
"baz": cty.UnknownVal(cty.String),
}),
cty.ObjectVal(map[string]cty.Value{
"bar": cty.StringVal("boop"),
"baz": cty.UnknownVal(cty.String),
}),
}),
}),
},
"sets differing only by unknown": {
&configschema.Block{
BlockTypes: map[string]*configschema.NestedBlock{
"foo": {
Nesting: configschema.NestingSet,
Block: configschema.Block{
Attributes: map[string]*configschema.Attribute{
"bar": {
Type: cty.String,
Optional: true,
},
"baz": {
Type: cty.String,
Optional: true,
Computed: true,
},
},
},
},
},
},
cty.ObjectVal(map[string]cty.Value{
"foo": cty.SetVal([]cty.Value{
cty.ObjectVal(map[string]cty.Value{
"bar": cty.StringVal("boop"),
"baz": cty.NullVal(cty.String),
}),
cty.ObjectVal(map[string]cty.Value{
"bar": cty.StringVal("boop"),
"baz": cty.UnknownVal(cty.String),
}),
}),
}),
cty.ObjectVal(map[string]cty.Value{
"foo": cty.SetVal([]cty.Value{
cty.ObjectVal(map[string]cty.Value{
"bar": cty.StringVal("boop"),
"baz": cty.UnknownVal(cty.String),
}),
cty.ObjectVal(map[string]cty.Value{
"bar": cty.StringVal("boop"),
"baz": cty.UnknownVal(cty.String),
}),
}),
}),
},
"prior nested list with dynamic": {
&configschema.Block{
BlockTypes: map[string]*configschema.NestedBlock{
"foo": {
Nesting: configschema.NestingList,
Block: configschema.Block{
Attributes: map[string]*configschema.Attribute{
"bar": {
Type: cty.String,
Optional: true,
Computed: true,
},
"baz": {
Type: cty.DynamicPseudoType,
Optional: true,
Computed: true,
},
},
},
},
},
},
cty.ObjectVal(map[string]cty.Value{
"foo": cty.TupleVal([]cty.Value{
cty.ObjectVal(map[string]cty.Value{
"bar": cty.NullVal(cty.String),
"baz": cty.NumberIntVal(8),
}),
}),
}),
cty.ObjectVal(map[string]cty.Value{
"foo": cty.TupleVal([]cty.Value{
cty.ObjectVal(map[string]cty.Value{
"bar": cty.UnknownVal(cty.String),
"baz": cty.NumberIntVal(8),
}),
}),
}),
},
"prior nested map with dynamic": {
&configschema.Block{
BlockTypes: map[string]*configschema.NestedBlock{
"foo": {
Nesting: configschema.NestingMap,
Block: configschema.Block{
Attributes: map[string]*configschema.Attribute{
"bar": {
Type: cty.String,
Optional: true,
Computed: true,
},
"baz": {
Type: cty.DynamicPseudoType,
Optional: true,
Computed: true,
},
},
},
},
},
},
cty.ObjectVal(map[string]cty.Value{
"foo": cty.ObjectVal(map[string]cty.Value{
"a": cty.ObjectVal(map[string]cty.Value{
"bar": cty.StringVal("beep"),
"baz": cty.NullVal(cty.DynamicPseudoType),
}),
"b": cty.ObjectVal(map[string]cty.Value{
"bar": cty.StringVal("boop"),
"baz": cty.NumberIntVal(8),
}),
}),
}),
cty.ObjectVal(map[string]cty.Value{
"foo": cty.ObjectVal(map[string]cty.Value{
"a": cty.ObjectVal(map[string]cty.Value{
"bar": cty.StringVal("beep"),
"baz": cty.UnknownVal(cty.DynamicPseudoType),
}),
"b": cty.ObjectVal(map[string]cty.Value{
"bar": cty.StringVal("boop"),
"baz": cty.NumberIntVal(8),
}),
}),
}),
},
} {
t.Run(n, func(t *testing.T) {
got := SetUnknowns(tc.Val, tc.Schema)
if !got.RawEquals(tc.Expected) {
t.Fatalf("\nexpected: %#v\ngot: %#v\n", tc.Expected, got)
}
})
}
}