github.com/opentofu/opentofu@v1.7.1/internal/legacy/helper/schema/field_reader_config_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 schema 7 8 import ( 9 "bytes" 10 "fmt" 11 "reflect" 12 "testing" 13 14 "github.com/opentofu/opentofu/internal/configs/hcl2shim" 15 "github.com/opentofu/opentofu/internal/legacy/helper/hashcode" 16 "github.com/opentofu/opentofu/internal/legacy/tofu" 17 ) 18 19 func TestConfigFieldReader_impl(t *testing.T) { 20 var _ FieldReader = new(ConfigFieldReader) 21 } 22 23 func TestConfigFieldReader(t *testing.T) { 24 testFieldReader(t, func(s map[string]*Schema) FieldReader { 25 return &ConfigFieldReader{ 26 Schema: s, 27 28 Config: testConfig(t, map[string]interface{}{ 29 "bool": true, 30 "float": 3.1415, 31 "int": 42, 32 "string": "string", 33 34 "list": []interface{}{"foo", "bar"}, 35 36 "listInt": []interface{}{21, 42}, 37 38 "map": map[string]interface{}{ 39 "foo": "bar", 40 "bar": "baz", 41 }, 42 "mapInt": map[string]interface{}{ 43 "one": "1", 44 "two": "2", 45 }, 46 "mapIntNestedSchema": map[string]interface{}{ 47 "one": "1", 48 "two": "2", 49 }, 50 "mapFloat": map[string]interface{}{ 51 "oneDotTwo": "1.2", 52 }, 53 "mapBool": map[string]interface{}{ 54 "True": "true", 55 "False": "false", 56 }, 57 58 "set": []interface{}{10, 50}, 59 "setDeep": []interface{}{ 60 map[string]interface{}{ 61 "index": 10, 62 "value": "foo", 63 }, 64 map[string]interface{}{ 65 "index": 50, 66 "value": "bar", 67 }, 68 }, 69 }), 70 } 71 }) 72 } 73 74 // This contains custom table tests for our ConfigFieldReader 75 func TestConfigFieldReader_custom(t *testing.T) { 76 schema := map[string]*Schema{ 77 "bool": &Schema{ 78 Type: TypeBool, 79 }, 80 } 81 82 cases := map[string]struct { 83 Addr []string 84 Result FieldReadResult 85 Config *tofu.ResourceConfig 86 Err bool 87 }{ 88 "basic": { 89 []string{"bool"}, 90 FieldReadResult{ 91 Value: true, 92 Exists: true, 93 }, 94 testConfig(t, map[string]interface{}{ 95 "bool": true, 96 }), 97 false, 98 }, 99 100 "computed": { 101 []string{"bool"}, 102 FieldReadResult{ 103 Exists: true, 104 Computed: true, 105 }, 106 testConfig(t, map[string]interface{}{ 107 "bool": hcl2shim.UnknownVariableValue, 108 }), 109 false, 110 }, 111 } 112 113 for name, tc := range cases { 114 t.Run(name, func(t *testing.T) { 115 r := &ConfigFieldReader{ 116 Schema: schema, 117 Config: tc.Config, 118 } 119 out, err := r.ReadField(tc.Addr) 120 if err != nil != tc.Err { 121 t.Fatalf("%s: err: %s", name, err) 122 } 123 if s, ok := out.Value.(*Set); ok { 124 // If it is a set, convert to a list so its more easily checked. 125 out.Value = s.List() 126 } 127 if !reflect.DeepEqual(tc.Result, out) { 128 t.Fatalf("%s: bad: %#v", name, out) 129 } 130 }) 131 } 132 } 133 134 func TestConfigFieldReader_DefaultHandling(t *testing.T) { 135 schema := map[string]*Schema{ 136 "strWithDefault": &Schema{ 137 Type: TypeString, 138 Default: "ImADefault", 139 }, 140 "strWithDefaultFunc": &Schema{ 141 Type: TypeString, 142 DefaultFunc: func() (interface{}, error) { 143 return "FuncDefault", nil 144 }, 145 }, 146 } 147 148 cases := map[string]struct { 149 Addr []string 150 Result FieldReadResult 151 Config *tofu.ResourceConfig 152 Err bool 153 }{ 154 "gets default value when no config set": { 155 []string{"strWithDefault"}, 156 FieldReadResult{ 157 Value: "ImADefault", 158 Exists: true, 159 Computed: false, 160 }, 161 testConfig(t, map[string]interface{}{}), 162 false, 163 }, 164 "config overrides default value": { 165 []string{"strWithDefault"}, 166 FieldReadResult{ 167 Value: "fromConfig", 168 Exists: true, 169 Computed: false, 170 }, 171 testConfig(t, map[string]interface{}{ 172 "strWithDefault": "fromConfig", 173 }), 174 false, 175 }, 176 "gets default from function when no config set": { 177 []string{"strWithDefaultFunc"}, 178 FieldReadResult{ 179 Value: "FuncDefault", 180 Exists: true, 181 Computed: false, 182 }, 183 testConfig(t, map[string]interface{}{}), 184 false, 185 }, 186 "config overrides default function": { 187 []string{"strWithDefaultFunc"}, 188 FieldReadResult{ 189 Value: "fromConfig", 190 Exists: true, 191 Computed: false, 192 }, 193 testConfig(t, map[string]interface{}{ 194 "strWithDefaultFunc": "fromConfig", 195 }), 196 false, 197 }, 198 } 199 200 for name, tc := range cases { 201 r := &ConfigFieldReader{ 202 Schema: schema, 203 Config: tc.Config, 204 } 205 out, err := r.ReadField(tc.Addr) 206 if err != nil != tc.Err { 207 t.Fatalf("%s: err: %s", name, err) 208 } 209 if s, ok := out.Value.(*Set); ok { 210 // If it is a set, convert to a list so its more easily checked. 211 out.Value = s.List() 212 } 213 if !reflect.DeepEqual(tc.Result, out) { 214 t.Fatalf("%s: bad: %#v", name, out) 215 } 216 } 217 } 218 219 func TestConfigFieldReader_ComputedMap(t *testing.T) { 220 schema := map[string]*Schema{ 221 "map": &Schema{ 222 Type: TypeMap, 223 Computed: true, 224 }, 225 "listmap": &Schema{ 226 Type: TypeMap, 227 Computed: true, 228 Elem: TypeList, 229 }, 230 "maplist": &Schema{ 231 Type: TypeList, 232 Computed: true, 233 Elem: TypeMap, 234 }, 235 } 236 237 cases := []struct { 238 Name string 239 Addr []string 240 Result FieldReadResult 241 Config *tofu.ResourceConfig 242 Err bool 243 }{ 244 { 245 "set, normal", 246 []string{"map"}, 247 FieldReadResult{ 248 Value: map[string]interface{}{ 249 "foo": "bar", 250 }, 251 Exists: true, 252 Computed: false, 253 }, 254 testConfig(t, map[string]interface{}{ 255 "map": map[string]interface{}{ 256 "foo": "bar", 257 }, 258 }), 259 false, 260 }, 261 262 { 263 "computed element", 264 []string{"map"}, 265 FieldReadResult{ 266 Exists: true, 267 Computed: true, 268 }, 269 testConfig(t, map[string]interface{}{ 270 "map": map[string]interface{}{ 271 "foo": hcl2shim.UnknownVariableValue, 272 }, 273 }), 274 false, 275 }, 276 277 { 278 "native map", 279 []string{"map"}, 280 FieldReadResult{ 281 Value: map[string]interface{}{ 282 "bar": "baz", 283 "baz": "bar", 284 }, 285 Exists: true, 286 Computed: false, 287 }, 288 testConfig(t, map[string]interface{}{ 289 "map": map[string]interface{}{ 290 "bar": "baz", 291 "baz": "bar", 292 }, 293 }), 294 false, 295 }, 296 297 { 298 "map-from-list-of-maps", 299 []string{"maplist", "0"}, 300 FieldReadResult{ 301 Value: map[string]interface{}{ 302 "key": "bar", 303 }, 304 Exists: true, 305 Computed: false, 306 }, 307 testConfig(t, map[string]interface{}{ 308 "maplist": []interface{}{ 309 map[string]interface{}{ 310 "key": "bar", 311 }, 312 }, 313 }), 314 false, 315 }, 316 317 { 318 "value-from-list-of-maps", 319 []string{"maplist", "0", "key"}, 320 FieldReadResult{ 321 Value: "bar", 322 Exists: true, 323 Computed: false, 324 }, 325 testConfig(t, map[string]interface{}{ 326 "maplist": []interface{}{ 327 map[string]interface{}{ 328 "key": "bar", 329 }, 330 }, 331 }), 332 false, 333 }, 334 335 { 336 "list-from-map-of-lists", 337 []string{"listmap", "key"}, 338 FieldReadResult{ 339 Value: []interface{}{"bar"}, 340 Exists: true, 341 Computed: false, 342 }, 343 testConfig(t, map[string]interface{}{ 344 "listmap": map[string]interface{}{ 345 "key": []interface{}{ 346 "bar", 347 }, 348 }, 349 }), 350 false, 351 }, 352 353 { 354 "value-from-map-of-lists", 355 []string{"listmap", "key", "0"}, 356 FieldReadResult{ 357 Value: "bar", 358 Exists: true, 359 Computed: false, 360 }, 361 testConfig(t, map[string]interface{}{ 362 "listmap": map[string]interface{}{ 363 "key": []interface{}{ 364 "bar", 365 }, 366 }, 367 }), 368 false, 369 }, 370 } 371 372 for i, tc := range cases { 373 t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) { 374 r := &ConfigFieldReader{ 375 Schema: schema, 376 Config: tc.Config, 377 } 378 out, err := r.ReadField(tc.Addr) 379 if err != nil != tc.Err { 380 t.Fatal(err) 381 } 382 if s, ok := out.Value.(*Set); ok { 383 // If it is a set, convert to the raw map 384 out.Value = s.m 385 if len(s.m) == 0 { 386 out.Value = nil 387 } 388 } 389 if !reflect.DeepEqual(tc.Result, out) { 390 t.Fatalf("\nexpected: %#v\ngot: %#v", tc.Result, out) 391 } 392 }) 393 } 394 } 395 396 func TestConfigFieldReader_ComputedSet(t *testing.T) { 397 schema := map[string]*Schema{ 398 "strSet": &Schema{ 399 Type: TypeSet, 400 Elem: &Schema{Type: TypeString}, 401 Set: HashString, 402 }, 403 } 404 405 cases := map[string]struct { 406 Addr []string 407 Result FieldReadResult 408 Config *tofu.ResourceConfig 409 Err bool 410 }{ 411 "set, normal": { 412 []string{"strSet"}, 413 FieldReadResult{ 414 Value: map[string]interface{}{ 415 "1938594527": "foo", 416 }, 417 Exists: true, 418 Computed: false, 419 }, 420 testConfig(t, map[string]interface{}{ 421 "strSet": []interface{}{"foo"}, 422 }), 423 false, 424 }, 425 426 "set, computed element": { 427 []string{"strSet"}, 428 FieldReadResult{ 429 Value: nil, 430 Exists: true, 431 Computed: true, 432 }, 433 testConfig(t, map[string]interface{}{ 434 "strSet": []interface{}{hcl2shim.UnknownVariableValue}, 435 }), 436 false, 437 }, 438 } 439 440 for name, tc := range cases { 441 r := &ConfigFieldReader{ 442 Schema: schema, 443 Config: tc.Config, 444 } 445 out, err := r.ReadField(tc.Addr) 446 if err != nil != tc.Err { 447 t.Fatalf("%s: err: %s", name, err) 448 } 449 if s, ok := out.Value.(*Set); ok { 450 // If it is a set, convert to the raw map 451 out.Value = s.m 452 if len(s.m) == 0 { 453 out.Value = nil 454 } 455 } 456 if !reflect.DeepEqual(tc.Result, out) { 457 t.Fatalf("%s: bad: %#v", name, out) 458 } 459 } 460 } 461 462 func TestConfigFieldReader_computedComplexSet(t *testing.T) { 463 hashfunc := func(v interface{}) int { 464 var buf bytes.Buffer 465 m := v.(map[string]interface{}) 466 buf.WriteString(fmt.Sprintf("%s-", m["name"].(string))) 467 buf.WriteString(fmt.Sprintf("%s-", m["vhd_uri"].(string))) 468 return hashcode.String(buf.String()) 469 } 470 471 schema := map[string]*Schema{ 472 "set": &Schema{ 473 Type: TypeSet, 474 Elem: &Resource{ 475 Schema: map[string]*Schema{ 476 "name": { 477 Type: TypeString, 478 Required: true, 479 }, 480 481 "vhd_uri": { 482 Type: TypeString, 483 Required: true, 484 }, 485 }, 486 }, 487 Set: hashfunc, 488 }, 489 } 490 491 cases := map[string]struct { 492 Addr []string 493 Result FieldReadResult 494 Config *tofu.ResourceConfig 495 Err bool 496 }{ 497 "set, normal": { 498 []string{"set"}, 499 FieldReadResult{ 500 Value: map[string]interface{}{ 501 "532860136": map[string]interface{}{ 502 "name": "myosdisk1", 503 "vhd_uri": "bar", 504 }, 505 }, 506 Exists: true, 507 Computed: false, 508 }, 509 testConfig(t, map[string]interface{}{ 510 "set": []interface{}{ 511 map[string]interface{}{ 512 "name": "myosdisk1", 513 "vhd_uri": "bar", 514 }, 515 }, 516 }), 517 false, 518 }, 519 } 520 521 for name, tc := range cases { 522 r := &ConfigFieldReader{ 523 Schema: schema, 524 Config: tc.Config, 525 } 526 out, err := r.ReadField(tc.Addr) 527 if err != nil != tc.Err { 528 t.Fatalf("%s: err: %s", name, err) 529 } 530 if s, ok := out.Value.(*Set); ok { 531 // If it is a set, convert to the raw map 532 out.Value = s.m 533 if len(s.m) == 0 { 534 out.Value = nil 535 } 536 } 537 if !reflect.DeepEqual(tc.Result, out) { 538 t.Fatalf("%s: bad: %#v", name, out) 539 } 540 } 541 } 542 543 func testConfig(t *testing.T, raw map[string]interface{}) *tofu.ResourceConfig { 544 return tofu.NewResourceConfigRaw(raw) 545 }