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) } }) } }