package objchange import ( "testing" "github.com/apparentlymart/go-dump/dump" "github.com/hashicorp/terraform/internal/configs/configschema" "github.com/zclconf/go-cty/cty" ) func TestNormalizeObjectFromLegacySDK(t *testing.T) { tests := map[string]struct { Schema *configschema.Block Input cty.Value Want cty.Value }{ "empty": { &configschema.Block{}, cty.EmptyObjectVal, cty.EmptyObjectVal, }, "attributes only": { &configschema.Block{ Attributes: map[string]*configschema.Attribute{ "a": {Type: cty.String, Required: true}, "b": {Type: cty.String, Optional: true}, }, }, cty.ObjectVal(map[string]cty.Value{ "a": cty.StringVal("a value"), "b": cty.StringVal("b value"), }), cty.ObjectVal(map[string]cty.Value{ "a": cty.StringVal("a value"), "b": cty.StringVal("b value"), }), }, "null block single": { &configschema.Block{ BlockTypes: map[string]*configschema.NestedBlock{ "a": { Nesting: configschema.NestingSingle, Block: configschema.Block{ Attributes: map[string]*configschema.Attribute{ "b": {Type: cty.String, Optional: true}, }, }, }, }, }, cty.ObjectVal(map[string]cty.Value{ "a": cty.NullVal(cty.Object(map[string]cty.Type{ "b": cty.String, })), }), cty.ObjectVal(map[string]cty.Value{ "a": cty.NullVal(cty.Object(map[string]cty.Type{ "b": cty.String, })), }), }, "unknown block single": { &configschema.Block{ BlockTypes: map[string]*configschema.NestedBlock{ "a": { Nesting: configschema.NestingSingle, Block: configschema.Block{ Attributes: map[string]*configschema.Attribute{ "b": {Type: cty.String, Optional: true}, }, BlockTypes: map[string]*configschema.NestedBlock{ "c": {Nesting: configschema.NestingSingle}, }, }, }, }, }, cty.ObjectVal(map[string]cty.Value{ "a": cty.UnknownVal(cty.Object(map[string]cty.Type{ "b": cty.String, "c": cty.EmptyObject, })), }), cty.ObjectVal(map[string]cty.Value{ "a": cty.ObjectVal(map[string]cty.Value{ "b": cty.UnknownVal(cty.String), "c": cty.EmptyObjectVal, }), }), }, "null block list": { &configschema.Block{ BlockTypes: map[string]*configschema.NestedBlock{ "a": { Nesting: configschema.NestingList, Block: configschema.Block{ Attributes: map[string]*configschema.Attribute{ "b": {Type: cty.String, Optional: true}, }, BlockTypes: map[string]*configschema.NestedBlock{ "c": {Nesting: configschema.NestingSingle}, }, }, }, }, }, cty.ObjectVal(map[string]cty.Value{ "a": cty.NullVal(cty.List(cty.Object(map[string]cty.Type{ "b": cty.String, "c": cty.EmptyObject, }))), }), cty.ObjectVal(map[string]cty.Value{ "a": cty.ListValEmpty(cty.Object(map[string]cty.Type{ "b": cty.String, "c": cty.EmptyObject, })), }), }, "unknown block list": { &configschema.Block{ BlockTypes: map[string]*configschema.NestedBlock{ "a": { Nesting: configschema.NestingList, Block: configschema.Block{ Attributes: map[string]*configschema.Attribute{ "b": {Type: cty.String, Optional: true}, }, }, }, }, }, cty.ObjectVal(map[string]cty.Value{ "a": cty.UnknownVal(cty.List(cty.Object(map[string]cty.Type{ "b": cty.String, }))), }), cty.ObjectVal(map[string]cty.Value{ "a": cty.ListVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ "b": cty.UnknownVal(cty.String), }), }), }), }, "null block set": { &configschema.Block{ BlockTypes: map[string]*configschema.NestedBlock{ "a": { Nesting: configschema.NestingSet, Block: configschema.Block{ Attributes: map[string]*configschema.Attribute{ "b": {Type: cty.String, Optional: true}, }, }, }, }, }, cty.ObjectVal(map[string]cty.Value{ "a": cty.NullVal(cty.Set(cty.Object(map[string]cty.Type{ "b": cty.String, }))), }), cty.ObjectVal(map[string]cty.Value{ "a": cty.SetValEmpty(cty.Object(map[string]cty.Type{ "b": cty.String, })), }), }, "unknown block set": { &configschema.Block{ BlockTypes: map[string]*configschema.NestedBlock{ "a": { Nesting: configschema.NestingSet, Block: configschema.Block{ Attributes: map[string]*configschema.Attribute{ "b": {Type: cty.String, Optional: true}, }, }, }, }, }, cty.ObjectVal(map[string]cty.Value{ "a": cty.UnknownVal(cty.Set(cty.Object(map[string]cty.Type{ "b": cty.String, }))), }), cty.ObjectVal(map[string]cty.Value{ "a": cty.SetVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ "b": cty.UnknownVal(cty.String), }), }), }), }, "map block passes through": { // Legacy SDK doesn't use NestingMap, so we don't do any transforms // related to it but we still need to verify that map blocks pass // through unscathed. &configschema.Block{ BlockTypes: map[string]*configschema.NestedBlock{ "a": { Nesting: configschema.NestingMap, Block: configschema.Block{ Attributes: map[string]*configschema.Attribute{ "b": {Type: cty.String, Optional: true}, }, }, }, }, }, cty.ObjectVal(map[string]cty.Value{ "a": cty.MapVal(map[string]cty.Value{ "foo": cty.ObjectVal(map[string]cty.Value{ "b": cty.StringVal("b value"), }), }), }), cty.ObjectVal(map[string]cty.Value{ "a": cty.MapVal(map[string]cty.Value{ "foo": cty.ObjectVal(map[string]cty.Value{ "b": cty.StringVal("b value"), }), }), }), }, "block list with dynamic type": { &configschema.Block{ BlockTypes: map[string]*configschema.NestedBlock{ "a": { Nesting: configschema.NestingList, Block: configschema.Block{ Attributes: map[string]*configschema.Attribute{ "b": {Type: cty.DynamicPseudoType, Optional: true}, }, }, }, }, }, cty.ObjectVal(map[string]cty.Value{ "a": cty.TupleVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ "b": cty.StringVal("hello"), }), cty.ObjectVal(map[string]cty.Value{ "b": cty.True, }), }), }), cty.ObjectVal(map[string]cty.Value{ "a": cty.TupleVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ "b": cty.StringVal("hello"), }), cty.ObjectVal(map[string]cty.Value{ "b": cty.True, }), }), }), }, "block map with dynamic type": { &configschema.Block{ BlockTypes: map[string]*configschema.NestedBlock{ "a": { Nesting: configschema.NestingMap, Block: configschema.Block{ Attributes: map[string]*configschema.Attribute{ "b": {Type: cty.DynamicPseudoType, Optional: true}, }, }, }, }, }, cty.ObjectVal(map[string]cty.Value{ "a": cty.ObjectVal(map[string]cty.Value{ "one": cty.ObjectVal(map[string]cty.Value{ "b": cty.StringVal("hello"), }), "another": cty.ObjectVal(map[string]cty.Value{ "b": cty.True, }), }), }), cty.ObjectVal(map[string]cty.Value{ "a": cty.ObjectVal(map[string]cty.Value{ "one": cty.ObjectVal(map[string]cty.Value{ "b": cty.StringVal("hello"), }), "another": cty.ObjectVal(map[string]cty.Value{ "b": cty.True, }), }), }), }, } for name, test := range tests { t.Run(name, func(t *testing.T) { got := NormalizeObjectFromLegacySDK(test.Input, test.Schema) if !got.RawEquals(test.Want) { t.Errorf( "wrong result\ngot: %s\nwant: %s", dump.Value(got), dump.Value(test.Want), ) } }) } }