github.com/opentofu/opentofu@v1.7.1/internal/plans/objchange/normalize_obj_test.go (about) 1 // Copyright (c) The OpenTofu Authors 2 // SPDX-License-Identifier: MPL-2.0 3 // Copyright (c) 2023 HashiCorp, Inc. 4 // SPDX-License-Identifier: MPL-2.0 5 6 package objchange 7 8 import ( 9 "testing" 10 11 "github.com/apparentlymart/go-dump/dump" 12 "github.com/opentofu/opentofu/internal/configs/configschema" 13 "github.com/zclconf/go-cty/cty" 14 ) 15 16 func TestNormalizeObjectFromLegacySDK(t *testing.T) { 17 tests := map[string]struct { 18 Schema *configschema.Block 19 Input cty.Value 20 Want cty.Value 21 }{ 22 "empty": { 23 &configschema.Block{}, 24 cty.EmptyObjectVal, 25 cty.EmptyObjectVal, 26 }, 27 "attributes only": { 28 &configschema.Block{ 29 Attributes: map[string]*configschema.Attribute{ 30 "a": {Type: cty.String, Required: true}, 31 "b": {Type: cty.String, Optional: true}, 32 }, 33 }, 34 cty.ObjectVal(map[string]cty.Value{ 35 "a": cty.StringVal("a value"), 36 "b": cty.StringVal("b value"), 37 }), 38 cty.ObjectVal(map[string]cty.Value{ 39 "a": cty.StringVal("a value"), 40 "b": cty.StringVal("b value"), 41 }), 42 }, 43 "null block single": { 44 &configschema.Block{ 45 BlockTypes: map[string]*configschema.NestedBlock{ 46 "a": { 47 Nesting: configschema.NestingSingle, 48 Block: configschema.Block{ 49 Attributes: map[string]*configschema.Attribute{ 50 "b": {Type: cty.String, Optional: true}, 51 }, 52 }, 53 }, 54 }, 55 }, 56 cty.ObjectVal(map[string]cty.Value{ 57 "a": cty.NullVal(cty.Object(map[string]cty.Type{ 58 "b": cty.String, 59 })), 60 }), 61 cty.ObjectVal(map[string]cty.Value{ 62 "a": cty.NullVal(cty.Object(map[string]cty.Type{ 63 "b": cty.String, 64 })), 65 }), 66 }, 67 "unknown block single": { 68 &configschema.Block{ 69 BlockTypes: map[string]*configschema.NestedBlock{ 70 "a": { 71 Nesting: configschema.NestingSingle, 72 Block: configschema.Block{ 73 Attributes: map[string]*configschema.Attribute{ 74 "b": {Type: cty.String, Optional: true}, 75 }, 76 BlockTypes: map[string]*configschema.NestedBlock{ 77 "c": {Nesting: configschema.NestingSingle}, 78 }, 79 }, 80 }, 81 }, 82 }, 83 cty.ObjectVal(map[string]cty.Value{ 84 "a": cty.UnknownVal(cty.Object(map[string]cty.Type{ 85 "b": cty.String, 86 "c": cty.EmptyObject, 87 })), 88 }), 89 cty.ObjectVal(map[string]cty.Value{ 90 "a": cty.ObjectVal(map[string]cty.Value{ 91 "b": cty.UnknownVal(cty.String), 92 "c": cty.EmptyObjectVal, 93 }), 94 }), 95 }, 96 "null block list": { 97 &configschema.Block{ 98 BlockTypes: map[string]*configschema.NestedBlock{ 99 "a": { 100 Nesting: configschema.NestingList, 101 Block: configschema.Block{ 102 Attributes: map[string]*configschema.Attribute{ 103 "b": {Type: cty.String, Optional: true}, 104 }, 105 BlockTypes: map[string]*configschema.NestedBlock{ 106 "c": {Nesting: configschema.NestingSingle}, 107 }, 108 }, 109 }, 110 }, 111 }, 112 cty.ObjectVal(map[string]cty.Value{ 113 "a": cty.NullVal(cty.List(cty.Object(map[string]cty.Type{ 114 "b": cty.String, 115 "c": cty.EmptyObject, 116 }))), 117 }), 118 cty.ObjectVal(map[string]cty.Value{ 119 "a": cty.ListValEmpty(cty.Object(map[string]cty.Type{ 120 "b": cty.String, 121 "c": cty.EmptyObject, 122 })), 123 }), 124 }, 125 "unknown block list": { 126 &configschema.Block{ 127 BlockTypes: map[string]*configschema.NestedBlock{ 128 "a": { 129 Nesting: configschema.NestingList, 130 Block: configschema.Block{ 131 Attributes: map[string]*configschema.Attribute{ 132 "b": {Type: cty.String, Optional: true}, 133 }, 134 }, 135 }, 136 }, 137 }, 138 cty.ObjectVal(map[string]cty.Value{ 139 "a": cty.UnknownVal(cty.List(cty.Object(map[string]cty.Type{ 140 "b": cty.String, 141 }))), 142 }), 143 cty.ObjectVal(map[string]cty.Value{ 144 "a": cty.ListVal([]cty.Value{ 145 cty.ObjectVal(map[string]cty.Value{ 146 "b": cty.UnknownVal(cty.String), 147 }), 148 }), 149 }), 150 }, 151 "null block set": { 152 &configschema.Block{ 153 BlockTypes: map[string]*configschema.NestedBlock{ 154 "a": { 155 Nesting: configschema.NestingSet, 156 Block: configschema.Block{ 157 Attributes: map[string]*configschema.Attribute{ 158 "b": {Type: cty.String, Optional: true}, 159 }, 160 }, 161 }, 162 }, 163 }, 164 cty.ObjectVal(map[string]cty.Value{ 165 "a": cty.NullVal(cty.Set(cty.Object(map[string]cty.Type{ 166 "b": cty.String, 167 }))), 168 }), 169 cty.ObjectVal(map[string]cty.Value{ 170 "a": cty.SetValEmpty(cty.Object(map[string]cty.Type{ 171 "b": cty.String, 172 })), 173 }), 174 }, 175 "unknown block set": { 176 &configschema.Block{ 177 BlockTypes: map[string]*configschema.NestedBlock{ 178 "a": { 179 Nesting: configschema.NestingSet, 180 Block: configschema.Block{ 181 Attributes: map[string]*configschema.Attribute{ 182 "b": {Type: cty.String, Optional: true}, 183 }, 184 }, 185 }, 186 }, 187 }, 188 cty.ObjectVal(map[string]cty.Value{ 189 "a": cty.UnknownVal(cty.Set(cty.Object(map[string]cty.Type{ 190 "b": cty.String, 191 }))), 192 }), 193 cty.ObjectVal(map[string]cty.Value{ 194 "a": cty.SetVal([]cty.Value{ 195 cty.ObjectVal(map[string]cty.Value{ 196 "b": cty.UnknownVal(cty.String), 197 }), 198 }), 199 }), 200 }, 201 "map block passes through": { 202 // Legacy SDK doesn't use NestingMap, so we don't do any transforms 203 // related to it but we still need to verify that map blocks pass 204 // through unscathed. 205 &configschema.Block{ 206 BlockTypes: map[string]*configschema.NestedBlock{ 207 "a": { 208 Nesting: configschema.NestingMap, 209 Block: configschema.Block{ 210 Attributes: map[string]*configschema.Attribute{ 211 "b": {Type: cty.String, Optional: true}, 212 }, 213 }, 214 }, 215 }, 216 }, 217 cty.ObjectVal(map[string]cty.Value{ 218 "a": cty.MapVal(map[string]cty.Value{ 219 "foo": cty.ObjectVal(map[string]cty.Value{ 220 "b": cty.StringVal("b value"), 221 }), 222 }), 223 }), 224 cty.ObjectVal(map[string]cty.Value{ 225 "a": cty.MapVal(map[string]cty.Value{ 226 "foo": cty.ObjectVal(map[string]cty.Value{ 227 "b": cty.StringVal("b value"), 228 }), 229 }), 230 }), 231 }, 232 "block list with dynamic type": { 233 &configschema.Block{ 234 BlockTypes: map[string]*configschema.NestedBlock{ 235 "a": { 236 Nesting: configschema.NestingList, 237 Block: configschema.Block{ 238 Attributes: map[string]*configschema.Attribute{ 239 "b": {Type: cty.DynamicPseudoType, Optional: true}, 240 }, 241 }, 242 }, 243 }, 244 }, 245 cty.ObjectVal(map[string]cty.Value{ 246 "a": cty.TupleVal([]cty.Value{ 247 cty.ObjectVal(map[string]cty.Value{ 248 "b": cty.StringVal("hello"), 249 }), 250 cty.ObjectVal(map[string]cty.Value{ 251 "b": cty.True, 252 }), 253 }), 254 }), 255 cty.ObjectVal(map[string]cty.Value{ 256 "a": cty.TupleVal([]cty.Value{ 257 cty.ObjectVal(map[string]cty.Value{ 258 "b": cty.StringVal("hello"), 259 }), 260 cty.ObjectVal(map[string]cty.Value{ 261 "b": cty.True, 262 }), 263 }), 264 }), 265 }, 266 "block map with dynamic type": { 267 &configschema.Block{ 268 BlockTypes: map[string]*configschema.NestedBlock{ 269 "a": { 270 Nesting: configschema.NestingMap, 271 Block: configschema.Block{ 272 Attributes: map[string]*configschema.Attribute{ 273 "b": {Type: cty.DynamicPseudoType, Optional: true}, 274 }, 275 }, 276 }, 277 }, 278 }, 279 cty.ObjectVal(map[string]cty.Value{ 280 "a": cty.ObjectVal(map[string]cty.Value{ 281 "one": cty.ObjectVal(map[string]cty.Value{ 282 "b": cty.StringVal("hello"), 283 }), 284 "another": cty.ObjectVal(map[string]cty.Value{ 285 "b": cty.True, 286 }), 287 }), 288 }), 289 cty.ObjectVal(map[string]cty.Value{ 290 "a": cty.ObjectVal(map[string]cty.Value{ 291 "one": cty.ObjectVal(map[string]cty.Value{ 292 "b": cty.StringVal("hello"), 293 }), 294 "another": cty.ObjectVal(map[string]cty.Value{ 295 "b": cty.True, 296 }), 297 }), 298 }), 299 }, 300 } 301 302 for name, test := range tests { 303 t.Run(name, func(t *testing.T) { 304 got := NormalizeObjectFromLegacySDK(test.Input, test.Schema) 305 if !got.RawEquals(test.Want) { 306 t.Errorf( 307 "wrong result\ngot: %s\nwant: %s", 308 dump.Value(got), dump.Value(test.Want), 309 ) 310 } 311 }) 312 } 313 }