github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/configs/hcl2shim/values_test.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 package hcl2shim 5 6 import ( 7 "fmt" 8 "reflect" 9 "testing" 10 11 "github.com/terramate-io/tf/configs/configschema" 12 "github.com/zclconf/go-cty/cty" 13 ) 14 15 func TestConfigValueFromHCL2Block(t *testing.T) { 16 tests := []struct { 17 Input cty.Value 18 Schema *configschema.Block 19 Want map[string]interface{} 20 }{ 21 { 22 cty.ObjectVal(map[string]cty.Value{ 23 "name": cty.StringVal("Ermintrude"), 24 "age": cty.NumberIntVal(19), 25 "address": cty.ObjectVal(map[string]cty.Value{ 26 "street": cty.ListVal([]cty.Value{cty.StringVal("421 Shoreham Loop")}), 27 "city": cty.StringVal("Fridgewater"), 28 "state": cty.StringVal("MA"), 29 "zip": cty.StringVal("91037"), 30 }), 31 }), 32 &configschema.Block{ 33 Attributes: map[string]*configschema.Attribute{ 34 "name": {Type: cty.String, Optional: true}, 35 "age": {Type: cty.Number, Optional: true}, 36 }, 37 BlockTypes: map[string]*configschema.NestedBlock{ 38 "address": { 39 Nesting: configschema.NestingSingle, 40 Block: configschema.Block{ 41 Attributes: map[string]*configschema.Attribute{ 42 "street": {Type: cty.List(cty.String), Optional: true}, 43 "city": {Type: cty.String, Optional: true}, 44 "state": {Type: cty.String, Optional: true}, 45 "zip": {Type: cty.String, Optional: true}, 46 }, 47 }, 48 }, 49 }, 50 }, 51 map[string]interface{}{ 52 "name": "Ermintrude", 53 "age": int(19), 54 "address": map[string]interface{}{ 55 "street": []interface{}{"421 Shoreham Loop"}, 56 "city": "Fridgewater", 57 "state": "MA", 58 "zip": "91037", 59 }, 60 }, 61 }, 62 { 63 cty.ObjectVal(map[string]cty.Value{ 64 "name": cty.StringVal("Ermintrude"), 65 "age": cty.NumberIntVal(19), 66 "address": cty.NullVal(cty.Object(map[string]cty.Type{ 67 "street": cty.List(cty.String), 68 "city": cty.String, 69 "state": cty.String, 70 "zip": cty.String, 71 })), 72 }), 73 &configschema.Block{ 74 Attributes: map[string]*configschema.Attribute{ 75 "name": {Type: cty.String, Optional: true}, 76 "age": {Type: cty.Number, Optional: true}, 77 }, 78 BlockTypes: map[string]*configschema.NestedBlock{ 79 "address": { 80 Nesting: configschema.NestingSingle, 81 Block: configschema.Block{ 82 Attributes: map[string]*configschema.Attribute{ 83 "street": {Type: cty.List(cty.String), Optional: true}, 84 "city": {Type: cty.String, Optional: true}, 85 "state": {Type: cty.String, Optional: true}, 86 "zip": {Type: cty.String, Optional: true}, 87 }, 88 }, 89 }, 90 }, 91 }, 92 map[string]interface{}{ 93 "name": "Ermintrude", 94 "age": int(19), 95 }, 96 }, 97 { 98 cty.ObjectVal(map[string]cty.Value{ 99 "name": cty.StringVal("Ermintrude"), 100 "age": cty.NumberIntVal(19), 101 "address": cty.ObjectVal(map[string]cty.Value{ 102 "street": cty.ListVal([]cty.Value{cty.StringVal("421 Shoreham Loop")}), 103 "city": cty.StringVal("Fridgewater"), 104 "state": cty.StringVal("MA"), 105 "zip": cty.NullVal(cty.String), // should be omitted altogether in result 106 }), 107 }), 108 &configschema.Block{ 109 Attributes: map[string]*configschema.Attribute{ 110 "name": {Type: cty.String, Optional: true}, 111 "age": {Type: cty.Number, Optional: true}, 112 }, 113 BlockTypes: map[string]*configschema.NestedBlock{ 114 "address": { 115 Nesting: configschema.NestingSingle, 116 Block: configschema.Block{ 117 Attributes: map[string]*configschema.Attribute{ 118 "street": {Type: cty.List(cty.String), Optional: true}, 119 "city": {Type: cty.String, Optional: true}, 120 "state": {Type: cty.String, Optional: true}, 121 "zip": {Type: cty.String, Optional: true}, 122 }, 123 }, 124 }, 125 }, 126 }, 127 map[string]interface{}{ 128 "name": "Ermintrude", 129 "age": int(19), 130 "address": map[string]interface{}{ 131 "street": []interface{}{"421 Shoreham Loop"}, 132 "city": "Fridgewater", 133 "state": "MA", 134 }, 135 }, 136 }, 137 { 138 cty.ObjectVal(map[string]cty.Value{ 139 "address": cty.ListVal([]cty.Value{cty.EmptyObjectVal}), 140 }), 141 &configschema.Block{ 142 BlockTypes: map[string]*configschema.NestedBlock{ 143 "address": { 144 Nesting: configschema.NestingList, 145 Block: configschema.Block{}, 146 }, 147 }, 148 }, 149 map[string]interface{}{ 150 "address": []interface{}{ 151 map[string]interface{}{}, 152 }, 153 }, 154 }, 155 { 156 cty.ObjectVal(map[string]cty.Value{ 157 "address": cty.ListValEmpty(cty.EmptyObject), // should be omitted altogether in result 158 }), 159 &configschema.Block{ 160 BlockTypes: map[string]*configschema.NestedBlock{ 161 "address": { 162 Nesting: configschema.NestingList, 163 Block: configschema.Block{}, 164 }, 165 }, 166 }, 167 map[string]interface{}{}, 168 }, 169 { 170 cty.ObjectVal(map[string]cty.Value{ 171 "address": cty.SetVal([]cty.Value{cty.EmptyObjectVal}), 172 }), 173 &configschema.Block{ 174 BlockTypes: map[string]*configschema.NestedBlock{ 175 "address": { 176 Nesting: configschema.NestingSet, 177 Block: configschema.Block{}, 178 }, 179 }, 180 }, 181 map[string]interface{}{ 182 "address": []interface{}{ 183 map[string]interface{}{}, 184 }, 185 }, 186 }, 187 { 188 cty.ObjectVal(map[string]cty.Value{ 189 "address": cty.SetValEmpty(cty.EmptyObject), 190 }), 191 &configschema.Block{ 192 BlockTypes: map[string]*configschema.NestedBlock{ 193 "address": { 194 Nesting: configschema.NestingSet, 195 Block: configschema.Block{}, 196 }, 197 }, 198 }, 199 map[string]interface{}{}, 200 }, 201 { 202 cty.ObjectVal(map[string]cty.Value{ 203 "address": cty.MapVal(map[string]cty.Value{"foo": cty.EmptyObjectVal}), 204 }), 205 &configschema.Block{ 206 BlockTypes: map[string]*configschema.NestedBlock{ 207 "address": { 208 Nesting: configschema.NestingMap, 209 Block: configschema.Block{}, 210 }, 211 }, 212 }, 213 map[string]interface{}{ 214 "address": map[string]interface{}{ 215 "foo": map[string]interface{}{}, 216 }, 217 }, 218 }, 219 { 220 cty.ObjectVal(map[string]cty.Value{ 221 "address": cty.MapValEmpty(cty.EmptyObject), 222 }), 223 &configschema.Block{ 224 BlockTypes: map[string]*configschema.NestedBlock{ 225 "address": { 226 Nesting: configschema.NestingMap, 227 Block: configschema.Block{}, 228 }, 229 }, 230 }, 231 map[string]interface{}{}, 232 }, 233 { 234 cty.NullVal(cty.EmptyObject), 235 &configschema.Block{}, 236 nil, 237 }, 238 } 239 240 for _, test := range tests { 241 t.Run(fmt.Sprintf("%#v", test.Input), func(t *testing.T) { 242 got := ConfigValueFromHCL2Block(test.Input, test.Schema) 243 if !reflect.DeepEqual(got, test.Want) { 244 t.Errorf("wrong result\ninput: %#v\ngot: %#v\nwant: %#v", test.Input, got, test.Want) 245 } 246 }) 247 } 248 } 249 250 func TestConfigValueFromHCL2(t *testing.T) { 251 tests := []struct { 252 Input cty.Value 253 Want interface{} 254 }{ 255 { 256 cty.True, 257 true, 258 }, 259 { 260 cty.False, 261 false, 262 }, 263 { 264 cty.NumberIntVal(12), 265 int(12), 266 }, 267 { 268 cty.NumberFloatVal(12.5), 269 float64(12.5), 270 }, 271 { 272 cty.StringVal("hello world"), 273 "hello world", 274 }, 275 { 276 cty.ObjectVal(map[string]cty.Value{ 277 "name": cty.StringVal("Ermintrude"), 278 "age": cty.NumberIntVal(19), 279 "address": cty.ObjectVal(map[string]cty.Value{ 280 "street": cty.ListVal([]cty.Value{cty.StringVal("421 Shoreham Loop")}), 281 "city": cty.StringVal("Fridgewater"), 282 "state": cty.StringVal("MA"), 283 "zip": cty.StringVal("91037"), 284 }), 285 }), 286 map[string]interface{}{ 287 "name": "Ermintrude", 288 "age": int(19), 289 "address": map[string]interface{}{ 290 "street": []interface{}{"421 Shoreham Loop"}, 291 "city": "Fridgewater", 292 "state": "MA", 293 "zip": "91037", 294 }, 295 }, 296 }, 297 { 298 cty.MapVal(map[string]cty.Value{ 299 "foo": cty.StringVal("bar"), 300 "bar": cty.StringVal("baz"), 301 }), 302 map[string]interface{}{ 303 "foo": "bar", 304 "bar": "baz", 305 }, 306 }, 307 { 308 cty.TupleVal([]cty.Value{ 309 cty.StringVal("foo"), 310 cty.True, 311 }), 312 []interface{}{ 313 "foo", 314 true, 315 }, 316 }, 317 { 318 cty.NullVal(cty.String), 319 nil, 320 }, 321 { 322 cty.UnknownVal(cty.String), 323 UnknownVariableValue, 324 }, 325 } 326 327 for _, test := range tests { 328 t.Run(fmt.Sprintf("%#v", test.Input), func(t *testing.T) { 329 got := ConfigValueFromHCL2(test.Input) 330 if !reflect.DeepEqual(got, test.Want) { 331 t.Errorf("wrong result\ninput: %#v\ngot: %#v\nwant: %#v", test.Input, got, test.Want) 332 } 333 }) 334 } 335 } 336 337 func TestHCL2ValueFromConfigValue(t *testing.T) { 338 tests := []struct { 339 Input interface{} 340 Want cty.Value 341 }{ 342 { 343 nil, 344 cty.NullVal(cty.DynamicPseudoType), 345 }, 346 { 347 UnknownVariableValue, 348 cty.DynamicVal, 349 }, 350 { 351 true, 352 cty.True, 353 }, 354 { 355 false, 356 cty.False, 357 }, 358 { 359 int(12), 360 cty.NumberIntVal(12), 361 }, 362 { 363 int(0), 364 cty.Zero, 365 }, 366 { 367 float64(12.5), 368 cty.NumberFloatVal(12.5), 369 }, 370 { 371 "hello world", 372 cty.StringVal("hello world"), 373 }, 374 { 375 "O\u0308", // decomposed letter + diacritic 376 cty.StringVal("\u00D6"), // NFC-normalized on entry into cty 377 }, 378 { 379 []interface{}{}, 380 cty.EmptyTupleVal, 381 }, 382 { 383 []interface{}(nil), 384 cty.EmptyTupleVal, 385 }, 386 { 387 []interface{}{"hello", "world"}, 388 cty.TupleVal([]cty.Value{cty.StringVal("hello"), cty.StringVal("world")}), 389 }, 390 { 391 map[string]interface{}{}, 392 cty.EmptyObjectVal, 393 }, 394 { 395 map[string]interface{}(nil), 396 cty.EmptyObjectVal, 397 }, 398 { 399 map[string]interface{}{ 400 "foo": "bar", 401 "bar": "baz", 402 }, 403 cty.ObjectVal(map[string]cty.Value{ 404 "foo": cty.StringVal("bar"), 405 "bar": cty.StringVal("baz"), 406 }), 407 }, 408 } 409 410 for _, test := range tests { 411 t.Run(fmt.Sprintf("%#v", test.Input), func(t *testing.T) { 412 got := HCL2ValueFromConfigValue(test.Input) 413 if !got.RawEquals(test.Want) { 414 t.Errorf("wrong result\ninput: %#v\ngot: %#v\nwant: %#v", test.Input, got, test.Want) 415 } 416 }) 417 } 418 }