github.com/opentofu/opentofu@v1.7.1/internal/configs/hcl2shim/flatmap_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 hcl2shim 7 8 import ( 9 "fmt" 10 "testing" 11 12 "github.com/go-test/deep" 13 14 "github.com/zclconf/go-cty/cty" 15 ) 16 17 func TestFlatmapValueFromHCL2(t *testing.T) { 18 tests := []struct { 19 Value cty.Value 20 Want map[string]string 21 }{ 22 { 23 cty.EmptyObjectVal, 24 map[string]string{}, 25 }, 26 { 27 cty.ObjectVal(map[string]cty.Value{ 28 "foo": cty.StringVal("hello"), 29 }), 30 map[string]string{ 31 "foo": "hello", 32 }, 33 }, 34 { 35 cty.ObjectVal(map[string]cty.Value{ 36 "foo": cty.UnknownVal(cty.Bool), 37 }), 38 map[string]string{ 39 "foo": UnknownVariableValue, 40 }, 41 }, 42 { 43 cty.ObjectVal(map[string]cty.Value{ 44 "foo": cty.NumberIntVal(12), 45 }), 46 map[string]string{ 47 "foo": "12", 48 }, 49 }, 50 { 51 cty.ObjectVal(map[string]cty.Value{ 52 "foo": cty.True, 53 "bar": cty.False, 54 }), 55 map[string]string{ 56 "foo": "true", 57 "bar": "false", 58 }, 59 }, 60 { 61 cty.ObjectVal(map[string]cty.Value{ 62 "foo": cty.StringVal("hello"), 63 "bar": cty.StringVal("world"), 64 "baz": cty.StringVal("whelp"), 65 }), 66 map[string]string{ 67 "foo": "hello", 68 "bar": "world", 69 "baz": "whelp", 70 }, 71 }, 72 { 73 cty.ObjectVal(map[string]cty.Value{ 74 "foo": cty.ListValEmpty(cty.String), 75 }), 76 map[string]string{ 77 "foo.#": "0", 78 }, 79 }, 80 { 81 cty.ObjectVal(map[string]cty.Value{ 82 "foo": cty.UnknownVal(cty.List(cty.String)), 83 }), 84 map[string]string{ 85 "foo.#": UnknownVariableValue, 86 }, 87 }, 88 { 89 cty.ObjectVal(map[string]cty.Value{ 90 "foo": cty.ListVal([]cty.Value{ 91 cty.StringVal("hello"), 92 }), 93 }), 94 map[string]string{ 95 "foo.#": "1", 96 "foo.0": "hello", 97 }, 98 }, 99 { 100 cty.ObjectVal(map[string]cty.Value{ 101 "foo": cty.ListVal([]cty.Value{ 102 cty.StringVal("hello"), 103 cty.StringVal("world"), 104 }), 105 }), 106 map[string]string{ 107 "foo.#": "2", 108 "foo.0": "hello", 109 "foo.1": "world", 110 }, 111 }, 112 { 113 cty.ObjectVal(map[string]cty.Value{ 114 "foo": cty.MapVal(map[string]cty.Value{ 115 "hello": cty.NumberIntVal(12), 116 "hello.world": cty.NumberIntVal(10), 117 }), 118 }), 119 map[string]string{ 120 "foo.%": "2", 121 "foo.hello": "12", 122 "foo.hello.world": "10", 123 }, 124 }, 125 { 126 cty.ObjectVal(map[string]cty.Value{ 127 "foo": cty.UnknownVal(cty.Map(cty.String)), 128 }), 129 map[string]string{ 130 "foo.%": UnknownVariableValue, 131 }, 132 }, 133 { 134 cty.ObjectVal(map[string]cty.Value{ 135 "foo": cty.MapVal(map[string]cty.Value{ 136 "hello": cty.NumberIntVal(12), 137 "hello.world": cty.NumberIntVal(10), 138 }), 139 }), 140 map[string]string{ 141 "foo.%": "2", 142 "foo.hello": "12", 143 "foo.hello.world": "10", 144 }, 145 }, 146 { 147 cty.ObjectVal(map[string]cty.Value{ 148 "foo": cty.SetVal([]cty.Value{ 149 cty.StringVal("hello"), 150 cty.StringVal("world"), 151 }), 152 }), 153 map[string]string{ 154 "foo.#": "2", 155 "foo.0": "hello", 156 "foo.1": "world", 157 }, 158 }, 159 { 160 cty.ObjectVal(map[string]cty.Value{ 161 "foo": cty.UnknownVal(cty.Set(cty.Number)), 162 }), 163 map[string]string{ 164 "foo.#": UnknownVariableValue, 165 }, 166 }, 167 { 168 cty.ObjectVal(map[string]cty.Value{ 169 "foo": cty.ListVal([]cty.Value{ 170 cty.ObjectVal(map[string]cty.Value{ 171 "bar": cty.StringVal("hello"), 172 "baz": cty.StringVal("world"), 173 }), 174 cty.ObjectVal(map[string]cty.Value{ 175 "bar": cty.StringVal("bloo"), 176 "baz": cty.StringVal("blaa"), 177 }), 178 }), 179 }), 180 map[string]string{ 181 "foo.#": "2", 182 "foo.0.bar": "hello", 183 "foo.0.baz": "world", 184 "foo.1.bar": "bloo", 185 "foo.1.baz": "blaa", 186 }, 187 }, 188 { 189 cty.ObjectVal(map[string]cty.Value{ 190 "foo": cty.ListVal([]cty.Value{ 191 cty.ObjectVal(map[string]cty.Value{ 192 "bar": cty.StringVal("hello"), 193 "baz": cty.ListVal([]cty.Value{ 194 cty.True, 195 cty.True, 196 }), 197 }), 198 cty.ObjectVal(map[string]cty.Value{ 199 "bar": cty.StringVal("bloo"), 200 "baz": cty.ListVal([]cty.Value{ 201 cty.False, 202 cty.True, 203 }), 204 }), 205 }), 206 }), 207 map[string]string{ 208 "foo.#": "2", 209 "foo.0.bar": "hello", 210 "foo.0.baz.#": "2", 211 "foo.0.baz.0": "true", 212 "foo.0.baz.1": "true", 213 "foo.1.bar": "bloo", 214 "foo.1.baz.#": "2", 215 "foo.1.baz.0": "false", 216 "foo.1.baz.1": "true", 217 }, 218 }, 219 { 220 cty.ObjectVal(map[string]cty.Value{ 221 "foo": cty.ListVal([]cty.Value{ 222 cty.UnknownVal(cty.Object(map[string]cty.Type{ 223 "bar": cty.String, 224 "baz": cty.List(cty.Bool), 225 "bap": cty.Map(cty.Number), 226 })), 227 }), 228 }), 229 map[string]string{ 230 "foo.#": "1", 231 "foo.0.bar": UnknownVariableValue, 232 "foo.0.baz.#": UnknownVariableValue, 233 "foo.0.bap.%": UnknownVariableValue, 234 }, 235 }, 236 { 237 cty.NullVal(cty.Object(map[string]cty.Type{ 238 "foo": cty.Set(cty.Object(map[string]cty.Type{ 239 "bar": cty.String, 240 })), 241 })), 242 nil, 243 }, 244 } 245 246 for _, test := range tests { 247 t.Run(test.Value.GoString(), func(t *testing.T) { 248 got := FlatmapValueFromHCL2(test.Value) 249 250 for _, problem := range deep.Equal(got, test.Want) { 251 t.Error(problem) 252 } 253 }) 254 } 255 } 256 257 func TestFlatmapValueFromHCL2FromFlatmap(t *testing.T) { 258 tests := []struct { 259 Name string 260 Map map[string]string 261 Type cty.Type 262 }{ 263 { 264 "empty flatmap with collections", 265 map[string]string{}, 266 cty.Object(map[string]cty.Type{ 267 "foo": cty.Map(cty.String), 268 "bar": cty.Set(cty.String), 269 }), 270 }, 271 { 272 "nil flatmap with collections", 273 nil, 274 cty.Object(map[string]cty.Type{ 275 "foo": cty.Map(cty.String), 276 "bar": cty.Set(cty.String), 277 }), 278 }, 279 { 280 "empty flatmap with nested collections", 281 map[string]string{}, 282 cty.Object(map[string]cty.Type{ 283 "foo": cty.Object( 284 map[string]cty.Type{ 285 "baz": cty.Map(cty.String), 286 }, 287 ), 288 "bar": cty.Set(cty.String), 289 }), 290 }, 291 { 292 "partial flatmap with nested collections", 293 map[string]string{ 294 "foo.baz.%": "1", 295 "foo.baz.key": "val", 296 }, 297 cty.Object(map[string]cty.Type{ 298 "foo": cty.Object( 299 map[string]cty.Type{ 300 "baz": cty.Map(cty.String), 301 "biz": cty.Map(cty.String), 302 }, 303 ), 304 "bar": cty.Set(cty.String), 305 }), 306 }, 307 } 308 309 for _, test := range tests { 310 t.Run(test.Name, func(t *testing.T) { 311 val, err := HCL2ValueFromFlatmap(test.Map, test.Type) 312 if err != nil { 313 t.Fatal(err) 314 } 315 316 got := FlatmapValueFromHCL2(val) 317 318 for _, problem := range deep.Equal(got, test.Map) { 319 t.Error(problem) 320 } 321 }) 322 } 323 } 324 func TestHCL2ValueFromFlatmap(t *testing.T) { 325 tests := []struct { 326 Flatmap map[string]string 327 Type cty.Type 328 Want cty.Value 329 WantErr string 330 }{ 331 { 332 Flatmap: map[string]string{}, 333 Type: cty.EmptyObject, 334 Want: cty.EmptyObjectVal, 335 }, 336 { 337 Flatmap: map[string]string{ 338 "ignored": "foo", 339 }, 340 Type: cty.EmptyObject, 341 Want: cty.EmptyObjectVal, 342 }, 343 { 344 Flatmap: map[string]string{ 345 "foo": "blah", 346 "bar": "true", 347 "baz": "12.5", 348 "unk": UnknownVariableValue, 349 }, 350 Type: cty.Object(map[string]cty.Type{ 351 "foo": cty.String, 352 "bar": cty.Bool, 353 "baz": cty.Number, 354 "unk": cty.Bool, 355 }), 356 Want: cty.ObjectVal(map[string]cty.Value{ 357 "foo": cty.StringVal("blah"), 358 "bar": cty.True, 359 "baz": cty.NumberFloatVal(12.5), 360 "unk": cty.UnknownVal(cty.Bool), 361 }), 362 }, 363 { 364 Flatmap: map[string]string{ 365 "foo.#": "0", 366 }, 367 Type: cty.Object(map[string]cty.Type{ 368 "foo": cty.List(cty.String), 369 }), 370 Want: cty.ObjectVal(map[string]cty.Value{ 371 "foo": cty.ListValEmpty(cty.String), 372 }), 373 }, 374 { 375 Flatmap: map[string]string{ 376 "foo.#": UnknownVariableValue, 377 }, 378 Type: cty.Object(map[string]cty.Type{ 379 "foo": cty.List(cty.String), 380 }), 381 Want: cty.ObjectVal(map[string]cty.Value{ 382 "foo": cty.UnknownVal(cty.List(cty.String)), 383 }), 384 }, 385 { 386 Flatmap: map[string]string{ 387 "foo.#": "1", 388 "foo.0": "hello", 389 }, 390 Type: cty.Object(map[string]cty.Type{ 391 "foo": cty.List(cty.String), 392 }), 393 Want: cty.ObjectVal(map[string]cty.Value{ 394 "foo": cty.ListVal([]cty.Value{ 395 cty.StringVal("hello"), 396 }), 397 }), 398 }, 399 { 400 Flatmap: map[string]string{ 401 "foo.#": "2", 402 "foo.0": "true", 403 "foo.1": "false", 404 "foo.2": "ignored", // (because the count is 2, so this is out of range) 405 }, 406 Type: cty.Object(map[string]cty.Type{ 407 "foo": cty.List(cty.Bool), 408 }), 409 Want: cty.ObjectVal(map[string]cty.Value{ 410 "foo": cty.ListVal([]cty.Value{ 411 cty.True, 412 cty.False, 413 }), 414 }), 415 }, 416 { 417 Flatmap: map[string]string{ 418 "foo.#": "2", 419 "foo.0": "hello", 420 }, 421 Type: cty.Object(map[string]cty.Type{ 422 "foo": cty.Tuple([]cty.Type{ 423 cty.String, 424 cty.Bool, 425 }), 426 }), 427 Want: cty.ObjectVal(map[string]cty.Value{ 428 "foo": cty.TupleVal([]cty.Value{ 429 cty.StringVal("hello"), 430 cty.NullVal(cty.Bool), 431 }), 432 }), 433 }, 434 { 435 Flatmap: map[string]string{ 436 "foo.#": UnknownVariableValue, 437 }, 438 Type: cty.Object(map[string]cty.Type{ 439 "foo": cty.Tuple([]cty.Type{ 440 cty.String, 441 cty.Bool, 442 }), 443 }), 444 Want: cty.ObjectVal(map[string]cty.Value{ 445 "foo": cty.UnknownVal(cty.Tuple([]cty.Type{ 446 cty.String, 447 cty.Bool, 448 })), 449 }), 450 }, 451 { 452 Flatmap: map[string]string{ 453 "foo.#": "0", 454 }, 455 Type: cty.Object(map[string]cty.Type{ 456 "foo": cty.Set(cty.String), 457 }), 458 Want: cty.ObjectVal(map[string]cty.Value{ 459 "foo": cty.SetValEmpty(cty.String), 460 }), 461 }, 462 { 463 Flatmap: map[string]string{ 464 "foo.#": UnknownVariableValue, 465 }, 466 Type: cty.Object(map[string]cty.Type{ 467 "foo": cty.Set(cty.String), 468 }), 469 Want: cty.ObjectVal(map[string]cty.Value{ 470 "foo": cty.UnknownVal(cty.Set(cty.String)), 471 }), 472 }, 473 { 474 Flatmap: map[string]string{ 475 "foo.#": "1", 476 "foo.24534534": "hello", 477 }, 478 Type: cty.Object(map[string]cty.Type{ 479 "foo": cty.Set(cty.String), 480 }), 481 Want: cty.ObjectVal(map[string]cty.Value{ 482 "foo": cty.SetVal([]cty.Value{ 483 cty.StringVal("hello"), 484 }), 485 }), 486 }, 487 { 488 Flatmap: map[string]string{ 489 "foo.#": "1", 490 "foo.24534534": "true", 491 "foo.95645644": "true", 492 "foo.34533452": "false", 493 }, 494 Type: cty.Object(map[string]cty.Type{ 495 "foo": cty.Set(cty.Bool), 496 }), 497 Want: cty.ObjectVal(map[string]cty.Value{ 498 "foo": cty.SetVal([]cty.Value{ 499 cty.True, 500 cty.False, 501 }), 502 }), 503 }, 504 { 505 Flatmap: map[string]string{ 506 "foo.%": "0", 507 }, 508 Type: cty.Object(map[string]cty.Type{ 509 "foo": cty.Map(cty.String), 510 }), 511 Want: cty.ObjectVal(map[string]cty.Value{ 512 "foo": cty.MapValEmpty(cty.String), 513 }), 514 }, 515 { 516 Flatmap: map[string]string{ 517 "foo.%": "2", 518 "foo.baz": "true", 519 "foo.bar.baz": "false", 520 }, 521 Type: cty.Object(map[string]cty.Type{ 522 "foo": cty.Map(cty.Bool), 523 }), 524 Want: cty.ObjectVal(map[string]cty.Value{ 525 "foo": cty.MapVal(map[string]cty.Value{ 526 "baz": cty.True, 527 "bar.baz": cty.False, 528 }), 529 }), 530 }, 531 { 532 Flatmap: map[string]string{ 533 "foo.%": UnknownVariableValue, 534 }, 535 Type: cty.Object(map[string]cty.Type{ 536 "foo": cty.Map(cty.Bool), 537 }), 538 Want: cty.ObjectVal(map[string]cty.Value{ 539 "foo": cty.UnknownVal(cty.Map(cty.Bool)), 540 }), 541 }, 542 { 543 Flatmap: map[string]string{ 544 "foo.#": "2", 545 "foo.0.bar": "hello", 546 "foo.0.baz": "1", 547 "foo.1.bar": "world", 548 "foo.1.baz": "false", 549 }, 550 Type: cty.Object(map[string]cty.Type{ 551 "foo": cty.List(cty.Object(map[string]cty.Type{ 552 "bar": cty.String, 553 "baz": cty.Bool, 554 })), 555 }), 556 Want: cty.ObjectVal(map[string]cty.Value{ 557 "foo": cty.ListVal([]cty.Value{ 558 cty.ObjectVal(map[string]cty.Value{ 559 "bar": cty.StringVal("hello"), 560 "baz": cty.True, 561 }), 562 cty.ObjectVal(map[string]cty.Value{ 563 "bar": cty.StringVal("world"), 564 "baz": cty.False, 565 }), 566 }), 567 }), 568 }, 569 { 570 Flatmap: map[string]string{ 571 "foo.#": "2", 572 "foo.34534534.bar": "hello", 573 "foo.34534534.baz": "1", 574 "foo.93453345.bar": "world", 575 "foo.93453345.baz": "false", 576 }, 577 Type: cty.Object(map[string]cty.Type{ 578 "foo": cty.Set(cty.Object(map[string]cty.Type{ 579 "bar": cty.String, 580 "baz": cty.Bool, 581 })), 582 }), 583 Want: cty.ObjectVal(map[string]cty.Value{ 584 "foo": cty.SetVal([]cty.Value{ 585 cty.ObjectVal(map[string]cty.Value{ 586 "bar": cty.StringVal("hello"), 587 "baz": cty.True, 588 }), 589 cty.ObjectVal(map[string]cty.Value{ 590 "bar": cty.StringVal("world"), 591 "baz": cty.False, 592 }), 593 }), 594 }), 595 }, 596 { 597 Flatmap: map[string]string{ 598 "foo.#": "not-valid", 599 }, 600 Type: cty.Object(map[string]cty.Type{ 601 "foo": cty.List(cty.String), 602 }), 603 WantErr: `invalid count value for "foo." in state: strconv.Atoi: parsing "not-valid": invalid syntax`, 604 }, 605 { 606 Flatmap: nil, 607 Type: cty.Object(map[string]cty.Type{ 608 "foo": cty.Set(cty.Object(map[string]cty.Type{ 609 "bar": cty.String, 610 })), 611 }), 612 Want: cty.NullVal(cty.Object(map[string]cty.Type{ 613 "foo": cty.Set(cty.Object(map[string]cty.Type{ 614 "bar": cty.String, 615 })), 616 })), 617 }, 618 { 619 Flatmap: map[string]string{ 620 "foo.#": "2", 621 "foo.0.%": "2", 622 "foo.0.a": "a", 623 "foo.0.b": "b", 624 "foo.1.%": "1", 625 "foo.1.a": "a", 626 }, 627 Type: cty.Object(map[string]cty.Type{ 628 "foo": cty.List(cty.Map(cty.String)), 629 }), 630 631 Want: cty.ObjectVal(map[string]cty.Value{ 632 "foo": cty.ListVal([]cty.Value{ 633 cty.MapVal(map[string]cty.Value{ 634 "a": cty.StringVal("a"), 635 "b": cty.StringVal("b"), 636 }), 637 cty.MapVal(map[string]cty.Value{ 638 "a": cty.StringVal("a"), 639 }), 640 }), 641 }), 642 }, 643 { 644 Flatmap: map[string]string{ 645 "single.#": "1", 646 "single.~1.value": "a", 647 "single.~1.optional": UnknownVariableValue, 648 "two.#": "2", 649 "two.~2381914684.value": "a", 650 "two.~2381914684.optional": UnknownVariableValue, 651 "two.~2798940671.value": "b", 652 "two.~2798940671.optional": UnknownVariableValue, 653 }, 654 Type: cty.Object(map[string]cty.Type{ 655 "single": cty.Set( 656 cty.Object(map[string]cty.Type{ 657 "value": cty.String, 658 "optional": cty.String, 659 }), 660 ), 661 "two": cty.Set( 662 cty.Object(map[string]cty.Type{ 663 "optional": cty.String, 664 "value": cty.String, 665 }), 666 ), 667 }), 668 Want: cty.ObjectVal(map[string]cty.Value{ 669 "single": cty.SetVal([]cty.Value{ 670 cty.ObjectVal(map[string]cty.Value{ 671 "value": cty.StringVal("a"), 672 "optional": cty.UnknownVal(cty.String), 673 }), 674 }), 675 "two": cty.SetVal([]cty.Value{ 676 cty.ObjectVal(map[string]cty.Value{ 677 "value": cty.StringVal("a"), 678 "optional": cty.UnknownVal(cty.String), 679 }), 680 cty.ObjectVal(map[string]cty.Value{ 681 "value": cty.StringVal("b"), 682 "optional": cty.UnknownVal(cty.String), 683 }), 684 }), 685 }), 686 }, 687 { 688 Flatmap: map[string]string{ 689 "foo.#": "1", 690 }, 691 Type: cty.Object(map[string]cty.Type{ 692 "foo": cty.Set(cty.Object(map[string]cty.Type{ 693 "bar": cty.String, 694 })), 695 }), 696 Want: cty.ObjectVal(map[string]cty.Value{ 697 "foo": cty.SetVal([]cty.Value{ 698 cty.ObjectVal(map[string]cty.Value{ 699 "bar": cty.NullVal(cty.String), 700 }), 701 }), 702 }), 703 }, 704 { 705 Flatmap: map[string]string{ 706 "multi.#": "1", 707 "multi.2.set.#": "1", 708 "multi.2.set.3.required": "val", 709 }, 710 Type: cty.Object(map[string]cty.Type{ 711 "multi": cty.Set(cty.Object(map[string]cty.Type{ 712 "set": cty.Set(cty.Object(map[string]cty.Type{ 713 "required": cty.String, 714 })), 715 })), 716 }), 717 Want: cty.ObjectVal(map[string]cty.Value{ 718 "multi": cty.SetVal([]cty.Value{ 719 cty.ObjectVal(map[string]cty.Value{ 720 "set": cty.SetVal([]cty.Value{ 721 cty.ObjectVal(map[string]cty.Value{ 722 "required": cty.StringVal("val"), 723 }), 724 }), 725 }), 726 }), 727 }), 728 }, 729 } 730 731 for i, test := range tests { 732 t.Run(fmt.Sprintf("%d %#v as %#v", i, test.Flatmap, test.Type), func(t *testing.T) { 733 got, err := HCL2ValueFromFlatmap(test.Flatmap, test.Type) 734 735 if test.WantErr != "" { 736 if err == nil { 737 t.Fatalf("succeeded; want error: %s", test.WantErr) 738 } 739 if got, want := err.Error(), test.WantErr; got != want { 740 t.Fatalf("wrong error\ngot: %s\nwant: %s", got, want) 741 } 742 if got == cty.NilVal { 743 t.Fatalf("result is cty.NilVal; want valid placeholder value") 744 } 745 return 746 } else { 747 if err != nil { 748 t.Fatalf("unexpected error: %s", err.Error()) 749 } 750 } 751 752 if !got.RawEquals(test.Want) { 753 t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) 754 } 755 }) 756 } 757 }