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