github.com/opentofu/opentofu@v1.7.1/internal/legacy/helper/schema/resource_diff_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 "fmt" 10 "reflect" 11 "sort" 12 "testing" 13 14 "github.com/davecgh/go-spew/spew" 15 "github.com/opentofu/opentofu/internal/configs/hcl2shim" 16 "github.com/opentofu/opentofu/internal/legacy/tofu" 17 ) 18 19 // testSetFunc is a very simple function we use to test a foo/bar complex set. 20 // Both "foo" and "bar" are int values. 21 // 22 // This is not foolproof as since it performs sums, you can run into 23 // collisions. Spec tests accordingly. :P 24 func testSetFunc(v interface{}) int { 25 m := v.(map[string]interface{}) 26 return m["foo"].(int) + m["bar"].(int) 27 } 28 29 // resourceDiffTestCase provides a test case struct for SetNew and SetDiff. 30 type resourceDiffTestCase struct { 31 Name string 32 Schema map[string]*Schema 33 State *tofu.InstanceState 34 Config *tofu.ResourceConfig 35 Diff *tofu.InstanceDiff 36 Key string 37 OldValue interface{} 38 NewValue interface{} 39 Expected *tofu.InstanceDiff 40 ExpectedKeys []string 41 ExpectedError bool 42 } 43 44 // testDiffCases produces a list of test cases for use with SetNew and SetDiff. 45 func testDiffCases(t *testing.T, oldPrefix string, oldOffset int, computed bool) []resourceDiffTestCase { 46 return []resourceDiffTestCase{ 47 resourceDiffTestCase{ 48 Name: "basic primitive diff", 49 Schema: map[string]*Schema{ 50 "foo": &Schema{ 51 Type: TypeString, 52 Optional: true, 53 Computed: true, 54 }, 55 }, 56 State: &tofu.InstanceState{ 57 Attributes: map[string]string{ 58 "foo": "bar", 59 }, 60 }, 61 Config: testConfig(t, map[string]interface{}{ 62 "foo": "baz", 63 }), 64 Diff: &tofu.InstanceDiff{ 65 Attributes: map[string]*tofu.ResourceAttrDiff{ 66 "foo": &tofu.ResourceAttrDiff{ 67 Old: "bar", 68 New: "baz", 69 }, 70 }, 71 }, 72 Key: "foo", 73 NewValue: "qux", 74 Expected: &tofu.InstanceDiff{ 75 Attributes: map[string]*tofu.ResourceAttrDiff{ 76 "foo": &tofu.ResourceAttrDiff{ 77 Old: "bar", 78 New: func() string { 79 if computed { 80 return "" 81 } 82 return "qux" 83 }(), 84 NewComputed: computed, 85 }, 86 }, 87 }, 88 }, 89 resourceDiffTestCase{ 90 Name: "basic set diff", 91 Schema: map[string]*Schema{ 92 "foo": &Schema{ 93 Type: TypeSet, 94 Optional: true, 95 Computed: true, 96 Elem: &Schema{Type: TypeString}, 97 Set: HashString, 98 }, 99 }, 100 State: &tofu.InstanceState{ 101 Attributes: map[string]string{ 102 "foo.#": "1", 103 "foo.1996459178": "bar", 104 }, 105 }, 106 Config: testConfig(t, map[string]interface{}{ 107 "foo": []interface{}{"baz"}, 108 }), 109 Diff: &tofu.InstanceDiff{ 110 Attributes: map[string]*tofu.ResourceAttrDiff{ 111 "foo.1996459178": &tofu.ResourceAttrDiff{ 112 Old: "bar", 113 New: "", 114 NewRemoved: true, 115 }, 116 "foo.2015626392": &tofu.ResourceAttrDiff{ 117 Old: "", 118 New: "baz", 119 }, 120 }, 121 }, 122 Key: "foo", 123 NewValue: []interface{}{"qux"}, 124 Expected: &tofu.InstanceDiff{ 125 Attributes: func() map[string]*tofu.ResourceAttrDiff { 126 result := map[string]*tofu.ResourceAttrDiff{} 127 if computed { 128 result["foo.#"] = &tofu.ResourceAttrDiff{ 129 Old: "1", 130 New: "", 131 NewComputed: true, 132 } 133 } else { 134 result["foo.1494962232"] = &tofu.ResourceAttrDiff{ 135 Old: "", 136 New: "qux", 137 } 138 result["foo.1996459178"] = &tofu.ResourceAttrDiff{ 139 Old: "bar", 140 New: "", 141 NewRemoved: true, 142 } 143 } 144 return result 145 }(), 146 }, 147 }, 148 resourceDiffTestCase{ 149 Name: "basic list diff", 150 Schema: map[string]*Schema{ 151 "foo": &Schema{ 152 Type: TypeList, 153 Optional: true, 154 Computed: true, 155 Elem: &Schema{Type: TypeString}, 156 }, 157 }, 158 State: &tofu.InstanceState{ 159 Attributes: map[string]string{ 160 "foo.#": "1", 161 "foo.0": "bar", 162 }, 163 }, 164 Config: testConfig(t, map[string]interface{}{ 165 "foo": []interface{}{"baz"}, 166 }), 167 Diff: &tofu.InstanceDiff{ 168 Attributes: map[string]*tofu.ResourceAttrDiff{ 169 "foo.0": &tofu.ResourceAttrDiff{ 170 Old: "bar", 171 New: "baz", 172 }, 173 }, 174 }, 175 Key: "foo", 176 NewValue: []interface{}{"qux"}, 177 Expected: &tofu.InstanceDiff{ 178 Attributes: func() map[string]*tofu.ResourceAttrDiff { 179 result := make(map[string]*tofu.ResourceAttrDiff) 180 if computed { 181 result["foo.#"] = &tofu.ResourceAttrDiff{ 182 Old: "1", 183 New: "", 184 NewComputed: true, 185 } 186 } else { 187 result["foo.0"] = &tofu.ResourceAttrDiff{ 188 Old: "bar", 189 New: "qux", 190 } 191 } 192 return result 193 }(), 194 }, 195 }, 196 resourceDiffTestCase{ 197 Name: "basic map diff", 198 Schema: map[string]*Schema{ 199 "foo": &Schema{ 200 Type: TypeMap, 201 Optional: true, 202 Computed: true, 203 }, 204 }, 205 State: &tofu.InstanceState{ 206 Attributes: map[string]string{ 207 "foo.%": "1", 208 "foo.bar": "baz", 209 }, 210 }, 211 Config: testConfig(t, map[string]interface{}{ 212 "foo": map[string]interface{}{"bar": "qux"}, 213 }), 214 Diff: &tofu.InstanceDiff{ 215 Attributes: map[string]*tofu.ResourceAttrDiff{ 216 "foo.bar": &tofu.ResourceAttrDiff{ 217 Old: "baz", 218 New: "qux", 219 }, 220 }, 221 }, 222 Key: "foo", 223 NewValue: map[string]interface{}{"bar": "quux"}, 224 Expected: &tofu.InstanceDiff{ 225 Attributes: func() map[string]*tofu.ResourceAttrDiff { 226 result := make(map[string]*tofu.ResourceAttrDiff) 227 if computed { 228 result["foo.%"] = &tofu.ResourceAttrDiff{ 229 Old: "", 230 New: "", 231 NewComputed: true, 232 } 233 result["foo.bar"] = &tofu.ResourceAttrDiff{ 234 Old: "baz", 235 New: "", 236 NewRemoved: true, 237 } 238 } else { 239 result["foo.bar"] = &tofu.ResourceAttrDiff{ 240 Old: "baz", 241 New: "quux", 242 } 243 } 244 return result 245 }(), 246 }, 247 }, 248 resourceDiffTestCase{ 249 Name: "additional diff with primitive", 250 Schema: map[string]*Schema{ 251 "foo": &Schema{ 252 Type: TypeString, 253 Optional: true, 254 }, 255 "one": &Schema{ 256 Type: TypeString, 257 Optional: true, 258 Computed: true, 259 }, 260 }, 261 State: &tofu.InstanceState{ 262 Attributes: map[string]string{ 263 "foo": "bar", 264 "one": "two", 265 }, 266 }, 267 Config: testConfig(t, map[string]interface{}{ 268 "foo": "baz", 269 }), 270 Diff: &tofu.InstanceDiff{ 271 Attributes: map[string]*tofu.ResourceAttrDiff{ 272 "foo": &tofu.ResourceAttrDiff{ 273 Old: "bar", 274 New: "baz", 275 }, 276 }, 277 }, 278 Key: "one", 279 NewValue: "four", 280 Expected: &tofu.InstanceDiff{ 281 Attributes: map[string]*tofu.ResourceAttrDiff{ 282 "foo": &tofu.ResourceAttrDiff{ 283 Old: "bar", 284 New: "baz", 285 }, 286 "one": &tofu.ResourceAttrDiff{ 287 Old: "two", 288 New: func() string { 289 if computed { 290 return "" 291 } 292 return "four" 293 }(), 294 NewComputed: computed, 295 }, 296 }, 297 }, 298 }, 299 resourceDiffTestCase{ 300 Name: "additional diff with primitive computed only", 301 Schema: map[string]*Schema{ 302 "foo": &Schema{ 303 Type: TypeString, 304 Optional: true, 305 }, 306 "one": &Schema{ 307 Type: TypeString, 308 Computed: true, 309 }, 310 }, 311 State: &tofu.InstanceState{ 312 Attributes: map[string]string{ 313 "foo": "bar", 314 "one": "two", 315 }, 316 }, 317 Config: testConfig(t, map[string]interface{}{ 318 "foo": "baz", 319 }), 320 Diff: &tofu.InstanceDiff{ 321 Attributes: map[string]*tofu.ResourceAttrDiff{ 322 "foo": &tofu.ResourceAttrDiff{ 323 Old: "bar", 324 New: "baz", 325 }, 326 }, 327 }, 328 Key: "one", 329 NewValue: "three", 330 Expected: &tofu.InstanceDiff{ 331 Attributes: map[string]*tofu.ResourceAttrDiff{ 332 "foo": &tofu.ResourceAttrDiff{ 333 Old: "bar", 334 New: "baz", 335 }, 336 "one": &tofu.ResourceAttrDiff{ 337 Old: "two", 338 New: func() string { 339 if computed { 340 return "" 341 } 342 return "three" 343 }(), 344 NewComputed: computed, 345 }, 346 }, 347 }, 348 }, 349 resourceDiffTestCase{ 350 Name: "complex-ish set diff", 351 Schema: map[string]*Schema{ 352 "top": &Schema{ 353 Type: TypeSet, 354 Optional: true, 355 Computed: true, 356 Elem: &Resource{ 357 Schema: map[string]*Schema{ 358 "foo": &Schema{ 359 Type: TypeInt, 360 Optional: true, 361 Computed: true, 362 }, 363 "bar": &Schema{ 364 Type: TypeInt, 365 Optional: true, 366 Computed: true, 367 }, 368 }, 369 }, 370 Set: testSetFunc, 371 }, 372 }, 373 State: &tofu.InstanceState{ 374 Attributes: map[string]string{ 375 "top.#": "2", 376 "top.3.foo": "1", 377 "top.3.bar": "2", 378 "top.23.foo": "11", 379 "top.23.bar": "12", 380 }, 381 }, 382 Config: testConfig(t, map[string]interface{}{ 383 "top": []interface{}{ 384 map[string]interface{}{ 385 "foo": 1, 386 "bar": 3, 387 }, 388 map[string]interface{}{ 389 "foo": 12, 390 "bar": 12, 391 }, 392 }, 393 }), 394 Diff: &tofu.InstanceDiff{ 395 Attributes: map[string]*tofu.ResourceAttrDiff{ 396 "top.4.foo": &tofu.ResourceAttrDiff{ 397 Old: "", 398 New: "1", 399 }, 400 "top.4.bar": &tofu.ResourceAttrDiff{ 401 Old: "", 402 New: "3", 403 }, 404 "top.24.foo": &tofu.ResourceAttrDiff{ 405 Old: "", 406 New: "12", 407 }, 408 "top.24.bar": &tofu.ResourceAttrDiff{ 409 Old: "", 410 New: "12", 411 }, 412 }, 413 }, 414 Key: "top", 415 NewValue: NewSet(testSetFunc, []interface{}{ 416 map[string]interface{}{ 417 "foo": 1, 418 "bar": 4, 419 }, 420 map[string]interface{}{ 421 "foo": 13, 422 "bar": 12, 423 }, 424 map[string]interface{}{ 425 "foo": 21, 426 "bar": 22, 427 }, 428 }), 429 Expected: &tofu.InstanceDiff{ 430 Attributes: func() map[string]*tofu.ResourceAttrDiff { 431 result := make(map[string]*tofu.ResourceAttrDiff) 432 if computed { 433 result["top.#"] = &tofu.ResourceAttrDiff{ 434 Old: "2", 435 New: "", 436 NewComputed: true, 437 } 438 } else { 439 result["top.#"] = &tofu.ResourceAttrDiff{ 440 Old: "2", 441 New: "3", 442 } 443 result["top.5.foo"] = &tofu.ResourceAttrDiff{ 444 Old: "", 445 New: "1", 446 } 447 result["top.5.bar"] = &tofu.ResourceAttrDiff{ 448 Old: "", 449 New: "4", 450 } 451 result["top.25.foo"] = &tofu.ResourceAttrDiff{ 452 Old: "", 453 New: "13", 454 } 455 result["top.25.bar"] = &tofu.ResourceAttrDiff{ 456 Old: "", 457 New: "12", 458 } 459 result["top.43.foo"] = &tofu.ResourceAttrDiff{ 460 Old: "", 461 New: "21", 462 } 463 result["top.43.bar"] = &tofu.ResourceAttrDiff{ 464 Old: "", 465 New: "22", 466 } 467 } 468 return result 469 }(), 470 }, 471 }, 472 resourceDiffTestCase{ 473 Name: "primitive, no diff, no refresh", 474 Schema: map[string]*Schema{ 475 "foo": &Schema{ 476 Type: TypeString, 477 Computed: true, 478 }, 479 }, 480 State: &tofu.InstanceState{ 481 Attributes: map[string]string{ 482 "foo": "bar", 483 }, 484 }, 485 Config: testConfig(t, map[string]interface{}{}), 486 Diff: &tofu.InstanceDiff{Attributes: map[string]*tofu.ResourceAttrDiff{}}, 487 Key: "foo", 488 NewValue: "baz", 489 Expected: &tofu.InstanceDiff{ 490 Attributes: map[string]*tofu.ResourceAttrDiff{ 491 "foo": &tofu.ResourceAttrDiff{ 492 Old: "bar", 493 New: func() string { 494 if computed { 495 return "" 496 } 497 return "baz" 498 }(), 499 NewComputed: computed, 500 }, 501 }, 502 }, 503 }, 504 resourceDiffTestCase{ 505 Name: "non-computed key, should error", 506 Schema: map[string]*Schema{ 507 "foo": &Schema{ 508 Type: TypeString, 509 Required: true, 510 }, 511 }, 512 State: &tofu.InstanceState{ 513 Attributes: map[string]string{ 514 "foo": "bar", 515 }, 516 }, 517 Config: testConfig(t, map[string]interface{}{ 518 "foo": "baz", 519 }), 520 Diff: &tofu.InstanceDiff{ 521 Attributes: map[string]*tofu.ResourceAttrDiff{ 522 "foo": &tofu.ResourceAttrDiff{ 523 Old: "bar", 524 New: "baz", 525 }, 526 }, 527 }, 528 Key: "foo", 529 NewValue: "qux", 530 ExpectedError: true, 531 }, 532 resourceDiffTestCase{ 533 Name: "bad key, should error", 534 Schema: map[string]*Schema{ 535 "foo": &Schema{ 536 Type: TypeString, 537 Required: true, 538 }, 539 }, 540 State: &tofu.InstanceState{ 541 Attributes: map[string]string{ 542 "foo": "bar", 543 }, 544 }, 545 Config: testConfig(t, map[string]interface{}{ 546 "foo": "baz", 547 }), 548 Diff: &tofu.InstanceDiff{ 549 Attributes: map[string]*tofu.ResourceAttrDiff{ 550 "foo": &tofu.ResourceAttrDiff{ 551 Old: "bar", 552 New: "baz", 553 }, 554 }, 555 }, 556 Key: "bad", 557 NewValue: "qux", 558 ExpectedError: true, 559 }, 560 resourceDiffTestCase{ 561 // NOTE: This case is technically impossible in the current 562 // implementation, because optional+computed values never show up in the 563 // diff, and we actually clear existing diffs when SetNew or 564 // SetNewComputed is run. This test is here to ensure that if either of 565 // these behaviors change that we don't introduce regressions. 566 Name: "NewRemoved in diff for Optional and Computed, should be fully overridden", 567 Schema: map[string]*Schema{ 568 "foo": &Schema{ 569 Type: TypeString, 570 Optional: true, 571 Computed: true, 572 }, 573 }, 574 State: &tofu.InstanceState{ 575 Attributes: map[string]string{ 576 "foo": "bar", 577 }, 578 }, 579 Config: testConfig(t, map[string]interface{}{}), 580 Diff: &tofu.InstanceDiff{ 581 Attributes: map[string]*tofu.ResourceAttrDiff{ 582 "foo": &tofu.ResourceAttrDiff{ 583 Old: "bar", 584 New: "", 585 NewRemoved: true, 586 }, 587 }, 588 }, 589 Key: "foo", 590 NewValue: "qux", 591 Expected: &tofu.InstanceDiff{ 592 Attributes: map[string]*tofu.ResourceAttrDiff{ 593 "foo": &tofu.ResourceAttrDiff{ 594 Old: "bar", 595 New: func() string { 596 if computed { 597 return "" 598 } 599 return "qux" 600 }(), 601 NewComputed: computed, 602 }, 603 }, 604 }, 605 }, 606 resourceDiffTestCase{ 607 Name: "NewComputed should always propagate", 608 Schema: map[string]*Schema{ 609 "foo": &Schema{ 610 Type: TypeString, 611 Computed: true, 612 }, 613 }, 614 State: &tofu.InstanceState{ 615 Attributes: map[string]string{ 616 "foo": "", 617 }, 618 ID: "pre-existing", 619 }, 620 Config: testConfig(t, map[string]interface{}{}), 621 Diff: &tofu.InstanceDiff{Attributes: map[string]*tofu.ResourceAttrDiff{}}, 622 Key: "foo", 623 NewValue: "", 624 Expected: &tofu.InstanceDiff{ 625 Attributes: func() map[string]*tofu.ResourceAttrDiff { 626 if computed { 627 return map[string]*tofu.ResourceAttrDiff{ 628 "foo": &tofu.ResourceAttrDiff{ 629 NewComputed: computed, 630 }, 631 } 632 } 633 return map[string]*tofu.ResourceAttrDiff{} 634 }(), 635 }, 636 }, 637 } 638 } 639 640 func TestSetNew(t *testing.T) { 641 testCases := testDiffCases(t, "", 0, false) 642 for _, tc := range testCases { 643 t.Run(tc.Name, func(t *testing.T) { 644 m := schemaMap(tc.Schema) 645 d := newResourceDiff(tc.Schema, tc.Config, tc.State, tc.Diff) 646 err := d.SetNew(tc.Key, tc.NewValue) 647 switch { 648 case err != nil && !tc.ExpectedError: 649 t.Fatalf("bad: %s", err) 650 case err == nil && tc.ExpectedError: 651 t.Fatalf("Expected error, got none") 652 case err != nil && tc.ExpectedError: 653 return 654 } 655 for _, k := range d.UpdatedKeys() { 656 if err := m.diff(k, m[k], tc.Diff, d, false); err != nil { 657 t.Fatalf("bad: %s", err) 658 } 659 } 660 if !reflect.DeepEqual(tc.Expected, tc.Diff) { 661 t.Fatalf("Expected %s, got %s", spew.Sdump(tc.Expected), spew.Sdump(tc.Diff)) 662 } 663 }) 664 } 665 } 666 667 func TestSetNewComputed(t *testing.T) { 668 testCases := testDiffCases(t, "", 0, true) 669 for _, tc := range testCases { 670 t.Run(tc.Name, func(t *testing.T) { 671 m := schemaMap(tc.Schema) 672 d := newResourceDiff(tc.Schema, tc.Config, tc.State, tc.Diff) 673 err := d.SetNewComputed(tc.Key) 674 switch { 675 case err != nil && !tc.ExpectedError: 676 t.Fatalf("bad: %s", err) 677 case err == nil && tc.ExpectedError: 678 t.Fatalf("Expected error, got none") 679 case err != nil && tc.ExpectedError: 680 return 681 } 682 for _, k := range d.UpdatedKeys() { 683 if err := m.diff(k, m[k], tc.Diff, d, false); err != nil { 684 t.Fatalf("bad: %s", err) 685 } 686 } 687 if !reflect.DeepEqual(tc.Expected, tc.Diff) { 688 t.Fatalf("Expected %s, got %s", spew.Sdump(tc.Expected), spew.Sdump(tc.Diff)) 689 } 690 }) 691 } 692 } 693 694 func TestForceNew(t *testing.T) { 695 cases := []resourceDiffTestCase{ 696 resourceDiffTestCase{ 697 Name: "basic primitive diff", 698 Schema: map[string]*Schema{ 699 "foo": &Schema{ 700 Type: TypeString, 701 Optional: true, 702 Computed: true, 703 }, 704 }, 705 State: &tofu.InstanceState{ 706 Attributes: map[string]string{ 707 "foo": "bar", 708 }, 709 }, 710 Config: testConfig(t, map[string]interface{}{ 711 "foo": "baz", 712 }), 713 Diff: &tofu.InstanceDiff{ 714 Attributes: map[string]*tofu.ResourceAttrDiff{ 715 "foo": &tofu.ResourceAttrDiff{ 716 Old: "bar", 717 New: "baz", 718 }, 719 }, 720 }, 721 Key: "foo", 722 Expected: &tofu.InstanceDiff{ 723 Attributes: map[string]*tofu.ResourceAttrDiff{ 724 "foo": &tofu.ResourceAttrDiff{ 725 Old: "bar", 726 New: "baz", 727 RequiresNew: true, 728 }, 729 }, 730 }, 731 }, 732 resourceDiffTestCase{ 733 Name: "no change, should error", 734 Schema: map[string]*Schema{ 735 "foo": &Schema{ 736 Type: TypeString, 737 Optional: true, 738 Computed: true, 739 }, 740 }, 741 State: &tofu.InstanceState{ 742 Attributes: map[string]string{ 743 "foo": "bar", 744 }, 745 }, 746 Config: testConfig(t, map[string]interface{}{ 747 "foo": "bar", 748 }), 749 ExpectedError: true, 750 }, 751 resourceDiffTestCase{ 752 Name: "basic primitive, non-computed key", 753 Schema: map[string]*Schema{ 754 "foo": &Schema{ 755 Type: TypeString, 756 Required: true, 757 }, 758 }, 759 State: &tofu.InstanceState{ 760 Attributes: map[string]string{ 761 "foo": "bar", 762 }, 763 }, 764 Config: testConfig(t, map[string]interface{}{ 765 "foo": "baz", 766 }), 767 Diff: &tofu.InstanceDiff{ 768 Attributes: map[string]*tofu.ResourceAttrDiff{ 769 "foo": &tofu.ResourceAttrDiff{ 770 Old: "bar", 771 New: "baz", 772 }, 773 }, 774 }, 775 Key: "foo", 776 Expected: &tofu.InstanceDiff{ 777 Attributes: map[string]*tofu.ResourceAttrDiff{ 778 "foo": &tofu.ResourceAttrDiff{ 779 Old: "bar", 780 New: "baz", 781 RequiresNew: true, 782 }, 783 }, 784 }, 785 }, 786 resourceDiffTestCase{ 787 Name: "nested field", 788 Schema: map[string]*Schema{ 789 "foo": &Schema{ 790 Type: TypeList, 791 Required: true, 792 MaxItems: 1, 793 Elem: &Resource{ 794 Schema: map[string]*Schema{ 795 "bar": { 796 Type: TypeString, 797 Optional: true, 798 }, 799 "baz": { 800 Type: TypeString, 801 Optional: true, 802 }, 803 }, 804 }, 805 }, 806 }, 807 State: &tofu.InstanceState{ 808 Attributes: map[string]string{ 809 "foo.#": "1", 810 "foo.0.bar": "abc", 811 "foo.0.baz": "xyz", 812 }, 813 }, 814 Config: testConfig(t, map[string]interface{}{ 815 "foo": []interface{}{ 816 map[string]interface{}{ 817 "bar": "abcdefg", 818 "baz": "changed", 819 }, 820 }, 821 }), 822 Diff: &tofu.InstanceDiff{ 823 Attributes: map[string]*tofu.ResourceAttrDiff{ 824 "foo.0.bar": &tofu.ResourceAttrDiff{ 825 Old: "abc", 826 New: "abcdefg", 827 }, 828 "foo.0.baz": &tofu.ResourceAttrDiff{ 829 Old: "xyz", 830 New: "changed", 831 }, 832 }, 833 }, 834 Key: "foo.0.baz", 835 Expected: &tofu.InstanceDiff{ 836 Attributes: map[string]*tofu.ResourceAttrDiff{ 837 "foo.0.bar": &tofu.ResourceAttrDiff{ 838 Old: "abc", 839 New: "abcdefg", 840 }, 841 "foo.0.baz": &tofu.ResourceAttrDiff{ 842 Old: "xyz", 843 New: "changed", 844 RequiresNew: true, 845 }, 846 }, 847 }, 848 }, 849 resourceDiffTestCase{ 850 Name: "preserve NewRemoved on existing diff", 851 Schema: map[string]*Schema{ 852 "foo": &Schema{ 853 Type: TypeString, 854 Optional: true, 855 }, 856 }, 857 State: &tofu.InstanceState{ 858 Attributes: map[string]string{ 859 "foo": "bar", 860 }, 861 }, 862 Config: testConfig(t, map[string]interface{}{}), 863 Diff: &tofu.InstanceDiff{ 864 Attributes: map[string]*tofu.ResourceAttrDiff{ 865 "foo": &tofu.ResourceAttrDiff{ 866 Old: "bar", 867 New: "", 868 NewRemoved: true, 869 }, 870 }, 871 }, 872 Key: "foo", 873 Expected: &tofu.InstanceDiff{ 874 Attributes: map[string]*tofu.ResourceAttrDiff{ 875 "foo": &tofu.ResourceAttrDiff{ 876 Old: "bar", 877 New: "", 878 RequiresNew: true, 879 NewRemoved: true, 880 }, 881 }, 882 }, 883 }, 884 resourceDiffTestCase{ 885 Name: "nested field, preserve original diff without zero values", 886 Schema: map[string]*Schema{ 887 "foo": &Schema{ 888 Type: TypeList, 889 Required: true, 890 MaxItems: 1, 891 Elem: &Resource{ 892 Schema: map[string]*Schema{ 893 "bar": { 894 Type: TypeString, 895 Optional: true, 896 }, 897 "baz": { 898 Type: TypeInt, 899 Optional: true, 900 }, 901 }, 902 }, 903 }, 904 }, 905 State: &tofu.InstanceState{ 906 Attributes: map[string]string{ 907 "foo.#": "1", 908 "foo.0.bar": "abc", 909 }, 910 }, 911 Config: testConfig(t, map[string]interface{}{ 912 "foo": []interface{}{ 913 map[string]interface{}{ 914 "bar": "abcdefg", 915 }, 916 }, 917 }), 918 Diff: &tofu.InstanceDiff{ 919 Attributes: map[string]*tofu.ResourceAttrDiff{ 920 "foo.0.bar": &tofu.ResourceAttrDiff{ 921 Old: "abc", 922 New: "abcdefg", 923 }, 924 }, 925 }, 926 Key: "foo.0.bar", 927 Expected: &tofu.InstanceDiff{ 928 Attributes: map[string]*tofu.ResourceAttrDiff{ 929 "foo.0.bar": &tofu.ResourceAttrDiff{ 930 Old: "abc", 931 New: "abcdefg", 932 RequiresNew: true, 933 }, 934 }, 935 }, 936 }, 937 } 938 for _, tc := range cases { 939 t.Run(tc.Name, func(t *testing.T) { 940 m := schemaMap(tc.Schema) 941 d := newResourceDiff(m, tc.Config, tc.State, tc.Diff) 942 err := d.ForceNew(tc.Key) 943 switch { 944 case err != nil && !tc.ExpectedError: 945 t.Fatalf("bad: %s", err) 946 case err == nil && tc.ExpectedError: 947 t.Fatalf("Expected error, got none") 948 case err != nil && tc.ExpectedError: 949 return 950 } 951 for _, k := range d.UpdatedKeys() { 952 if err := m.diff(k, m[k], tc.Diff, d, false); err != nil { 953 t.Fatalf("bad: %s", err) 954 } 955 } 956 if !reflect.DeepEqual(tc.Expected, tc.Diff) { 957 t.Fatalf("Expected %s, got %s", spew.Sdump(tc.Expected), spew.Sdump(tc.Diff)) 958 } 959 }) 960 } 961 } 962 963 func TestClear(t *testing.T) { 964 cases := []resourceDiffTestCase{ 965 resourceDiffTestCase{ 966 Name: "basic primitive diff", 967 Schema: map[string]*Schema{ 968 "foo": &Schema{ 969 Type: TypeString, 970 Optional: true, 971 Computed: true, 972 }, 973 }, 974 State: &tofu.InstanceState{ 975 Attributes: map[string]string{ 976 "foo": "bar", 977 }, 978 }, 979 Config: testConfig(t, map[string]interface{}{ 980 "foo": "baz", 981 }), 982 Diff: &tofu.InstanceDiff{ 983 Attributes: map[string]*tofu.ResourceAttrDiff{ 984 "foo": &tofu.ResourceAttrDiff{ 985 Old: "bar", 986 New: "baz", 987 }, 988 }, 989 }, 990 Key: "foo", 991 Expected: &tofu.InstanceDiff{Attributes: map[string]*tofu.ResourceAttrDiff{}}, 992 }, 993 resourceDiffTestCase{ 994 Name: "non-computed key, should error", 995 Schema: map[string]*Schema{ 996 "foo": &Schema{ 997 Type: TypeString, 998 Required: true, 999 }, 1000 }, 1001 State: &tofu.InstanceState{ 1002 Attributes: map[string]string{ 1003 "foo": "bar", 1004 }, 1005 }, 1006 Config: testConfig(t, map[string]interface{}{ 1007 "foo": "baz", 1008 }), 1009 Diff: &tofu.InstanceDiff{ 1010 Attributes: map[string]*tofu.ResourceAttrDiff{ 1011 "foo": &tofu.ResourceAttrDiff{ 1012 Old: "bar", 1013 New: "baz", 1014 }, 1015 }, 1016 }, 1017 Key: "foo", 1018 ExpectedError: true, 1019 }, 1020 resourceDiffTestCase{ 1021 Name: "multi-value, one removed", 1022 Schema: map[string]*Schema{ 1023 "foo": &Schema{ 1024 Type: TypeString, 1025 Optional: true, 1026 Computed: true, 1027 }, 1028 "one": &Schema{ 1029 Type: TypeString, 1030 Optional: true, 1031 Computed: true, 1032 }, 1033 }, 1034 State: &tofu.InstanceState{ 1035 Attributes: map[string]string{ 1036 "foo": "bar", 1037 "one": "two", 1038 }, 1039 }, 1040 Config: testConfig(t, map[string]interface{}{ 1041 "foo": "baz", 1042 "one": "three", 1043 }), 1044 Diff: &tofu.InstanceDiff{ 1045 Attributes: map[string]*tofu.ResourceAttrDiff{ 1046 "foo": &tofu.ResourceAttrDiff{ 1047 Old: "bar", 1048 New: "baz", 1049 }, 1050 "one": &tofu.ResourceAttrDiff{ 1051 Old: "two", 1052 New: "three", 1053 }, 1054 }, 1055 }, 1056 Key: "one", 1057 Expected: &tofu.InstanceDiff{ 1058 Attributes: map[string]*tofu.ResourceAttrDiff{ 1059 "foo": &tofu.ResourceAttrDiff{ 1060 Old: "bar", 1061 New: "baz", 1062 }, 1063 }, 1064 }, 1065 }, 1066 resourceDiffTestCase{ 1067 Name: "basic sub-block diff", 1068 Schema: map[string]*Schema{ 1069 "foo": &Schema{ 1070 Type: TypeList, 1071 Optional: true, 1072 Computed: true, 1073 Elem: &Resource{ 1074 Schema: map[string]*Schema{ 1075 "bar": &Schema{ 1076 Type: TypeString, 1077 Optional: true, 1078 Computed: true, 1079 }, 1080 "baz": &Schema{ 1081 Type: TypeString, 1082 Optional: true, 1083 Computed: true, 1084 }, 1085 }, 1086 }, 1087 }, 1088 }, 1089 State: &tofu.InstanceState{ 1090 Attributes: map[string]string{ 1091 "foo.0.bar": "bar1", 1092 "foo.0.baz": "baz1", 1093 }, 1094 }, 1095 Config: testConfig(t, map[string]interface{}{ 1096 "foo": []interface{}{ 1097 map[string]interface{}{ 1098 "bar": "bar2", 1099 "baz": "baz1", 1100 }, 1101 }, 1102 }), 1103 Diff: &tofu.InstanceDiff{ 1104 Attributes: map[string]*tofu.ResourceAttrDiff{ 1105 "foo.0.bar": &tofu.ResourceAttrDiff{ 1106 Old: "bar1", 1107 New: "bar2", 1108 }, 1109 }, 1110 }, 1111 Key: "foo.0.bar", 1112 Expected: &tofu.InstanceDiff{Attributes: map[string]*tofu.ResourceAttrDiff{}}, 1113 }, 1114 resourceDiffTestCase{ 1115 Name: "sub-block diff only partial clear", 1116 Schema: map[string]*Schema{ 1117 "foo": &Schema{ 1118 Type: TypeList, 1119 Optional: true, 1120 Computed: true, 1121 Elem: &Resource{ 1122 Schema: map[string]*Schema{ 1123 "bar": &Schema{ 1124 Type: TypeString, 1125 Optional: true, 1126 Computed: true, 1127 }, 1128 "baz": &Schema{ 1129 Type: TypeString, 1130 Optional: true, 1131 Computed: true, 1132 }, 1133 }, 1134 }, 1135 }, 1136 }, 1137 State: &tofu.InstanceState{ 1138 Attributes: map[string]string{ 1139 "foo.0.bar": "bar1", 1140 "foo.0.baz": "baz1", 1141 }, 1142 }, 1143 Config: testConfig(t, map[string]interface{}{ 1144 "foo": []interface{}{ 1145 map[string]interface{}{ 1146 "bar": "bar2", 1147 "baz": "baz2", 1148 }, 1149 }, 1150 }), 1151 Diff: &tofu.InstanceDiff{ 1152 Attributes: map[string]*tofu.ResourceAttrDiff{ 1153 "foo.0.bar": &tofu.ResourceAttrDiff{ 1154 Old: "bar1", 1155 New: "bar2", 1156 }, 1157 "foo.0.baz": &tofu.ResourceAttrDiff{ 1158 Old: "baz1", 1159 New: "baz2", 1160 }, 1161 }, 1162 }, 1163 Key: "foo.0.bar", 1164 Expected: &tofu.InstanceDiff{Attributes: map[string]*tofu.ResourceAttrDiff{ 1165 "foo.0.baz": &tofu.ResourceAttrDiff{ 1166 Old: "baz1", 1167 New: "baz2", 1168 }, 1169 }}, 1170 }, 1171 } 1172 for _, tc := range cases { 1173 t.Run(tc.Name, func(t *testing.T) { 1174 m := schemaMap(tc.Schema) 1175 d := newResourceDiff(m, tc.Config, tc.State, tc.Diff) 1176 err := d.Clear(tc.Key) 1177 switch { 1178 case err != nil && !tc.ExpectedError: 1179 t.Fatalf("bad: %s", err) 1180 case err == nil && tc.ExpectedError: 1181 t.Fatalf("Expected error, got none") 1182 case err != nil && tc.ExpectedError: 1183 return 1184 } 1185 for _, k := range d.UpdatedKeys() { 1186 if err := m.diff(k, m[k], tc.Diff, d, false); err != nil { 1187 t.Fatalf("bad: %s", err) 1188 } 1189 } 1190 if !reflect.DeepEqual(tc.Expected, tc.Diff) { 1191 t.Fatalf("Expected %s, got %s", spew.Sdump(tc.Expected), spew.Sdump(tc.Diff)) 1192 } 1193 }) 1194 } 1195 } 1196 1197 func TestGetChangedKeysPrefix(t *testing.T) { 1198 cases := []resourceDiffTestCase{ 1199 resourceDiffTestCase{ 1200 Name: "basic primitive diff", 1201 Schema: map[string]*Schema{ 1202 "foo": &Schema{ 1203 Type: TypeString, 1204 Optional: true, 1205 Computed: true, 1206 }, 1207 }, 1208 State: &tofu.InstanceState{ 1209 Attributes: map[string]string{ 1210 "foo": "bar", 1211 }, 1212 }, 1213 Config: testConfig(t, map[string]interface{}{ 1214 "foo": "baz", 1215 }), 1216 Diff: &tofu.InstanceDiff{ 1217 Attributes: map[string]*tofu.ResourceAttrDiff{ 1218 "foo": &tofu.ResourceAttrDiff{ 1219 Old: "bar", 1220 New: "baz", 1221 }, 1222 }, 1223 }, 1224 Key: "foo", 1225 ExpectedKeys: []string{ 1226 "foo", 1227 }, 1228 }, 1229 resourceDiffTestCase{ 1230 Name: "nested field filtering", 1231 Schema: map[string]*Schema{ 1232 "testfield": &Schema{ 1233 Type: TypeString, 1234 Required: true, 1235 }, 1236 "foo": &Schema{ 1237 Type: TypeList, 1238 Required: true, 1239 MaxItems: 1, 1240 Elem: &Resource{ 1241 Schema: map[string]*Schema{ 1242 "bar": { 1243 Type: TypeString, 1244 Optional: true, 1245 }, 1246 "baz": { 1247 Type: TypeString, 1248 Optional: true, 1249 }, 1250 }, 1251 }, 1252 }, 1253 }, 1254 State: &tofu.InstanceState{ 1255 Attributes: map[string]string{ 1256 "testfield": "blablah", 1257 "foo.#": "1", 1258 "foo.0.bar": "abc", 1259 "foo.0.baz": "xyz", 1260 }, 1261 }, 1262 Config: testConfig(t, map[string]interface{}{ 1263 "testfield": "modified", 1264 "foo": []interface{}{ 1265 map[string]interface{}{ 1266 "bar": "abcdefg", 1267 "baz": "changed", 1268 }, 1269 }, 1270 }), 1271 Diff: &tofu.InstanceDiff{ 1272 Attributes: map[string]*tofu.ResourceAttrDiff{ 1273 "testfield": &tofu.ResourceAttrDiff{ 1274 Old: "blablah", 1275 New: "modified", 1276 }, 1277 "foo.0.bar": &tofu.ResourceAttrDiff{ 1278 Old: "abc", 1279 New: "abcdefg", 1280 }, 1281 "foo.0.baz": &tofu.ResourceAttrDiff{ 1282 Old: "xyz", 1283 New: "changed", 1284 }, 1285 }, 1286 }, 1287 Key: "foo", 1288 ExpectedKeys: []string{ 1289 "foo.0.bar", 1290 "foo.0.baz", 1291 }, 1292 }, 1293 } 1294 for _, tc := range cases { 1295 t.Run(tc.Name, func(t *testing.T) { 1296 m := schemaMap(tc.Schema) 1297 d := newResourceDiff(m, tc.Config, tc.State, tc.Diff) 1298 keys := d.GetChangedKeysPrefix(tc.Key) 1299 1300 for _, k := range d.UpdatedKeys() { 1301 if err := m.diff(k, m[k], tc.Diff, d, false); err != nil { 1302 t.Fatalf("bad: %s", err) 1303 } 1304 } 1305 1306 sort.Strings(keys) 1307 1308 if !reflect.DeepEqual(tc.ExpectedKeys, keys) { 1309 t.Fatalf("Expected %s, got %s", spew.Sdump(tc.ExpectedKeys), spew.Sdump(keys)) 1310 } 1311 }) 1312 } 1313 } 1314 1315 func TestResourceDiffGetOkExists(t *testing.T) { 1316 cases := []struct { 1317 Name string 1318 Schema map[string]*Schema 1319 State *tofu.InstanceState 1320 Config *tofu.ResourceConfig 1321 Diff *tofu.InstanceDiff 1322 Key string 1323 Value interface{} 1324 Ok bool 1325 }{ 1326 /* 1327 * Primitives 1328 */ 1329 { 1330 Name: "string-literal-empty", 1331 Schema: map[string]*Schema{ 1332 "availability_zone": { 1333 Type: TypeString, 1334 Optional: true, 1335 Computed: true, 1336 ForceNew: true, 1337 }, 1338 }, 1339 1340 State: nil, 1341 Config: nil, 1342 1343 Diff: &tofu.InstanceDiff{ 1344 Attributes: map[string]*tofu.ResourceAttrDiff{ 1345 "availability_zone": { 1346 Old: "", 1347 New: "", 1348 }, 1349 }, 1350 }, 1351 1352 Key: "availability_zone", 1353 Value: "", 1354 Ok: true, 1355 }, 1356 1357 { 1358 Name: "string-computed-empty", 1359 Schema: map[string]*Schema{ 1360 "availability_zone": { 1361 Type: TypeString, 1362 Optional: true, 1363 Computed: true, 1364 ForceNew: true, 1365 }, 1366 }, 1367 1368 State: nil, 1369 Config: nil, 1370 1371 Diff: &tofu.InstanceDiff{ 1372 Attributes: map[string]*tofu.ResourceAttrDiff{ 1373 "availability_zone": { 1374 Old: "", 1375 New: "", 1376 NewComputed: true, 1377 }, 1378 }, 1379 }, 1380 1381 Key: "availability_zone", 1382 Value: "", 1383 Ok: false, 1384 }, 1385 1386 { 1387 Name: "string-optional-computed-nil-diff", 1388 Schema: map[string]*Schema{ 1389 "availability_zone": { 1390 Type: TypeString, 1391 Optional: true, 1392 Computed: true, 1393 ForceNew: true, 1394 }, 1395 }, 1396 1397 State: nil, 1398 Config: nil, 1399 1400 Diff: nil, 1401 1402 Key: "availability_zone", 1403 Value: "", 1404 Ok: false, 1405 }, 1406 1407 /* 1408 * Lists 1409 */ 1410 1411 { 1412 Name: "list-optional", 1413 Schema: map[string]*Schema{ 1414 "ports": { 1415 Type: TypeList, 1416 Optional: true, 1417 Elem: &Schema{Type: TypeInt}, 1418 }, 1419 }, 1420 1421 State: nil, 1422 Config: nil, 1423 1424 Diff: nil, 1425 1426 Key: "ports", 1427 Value: []interface{}{}, 1428 Ok: false, 1429 }, 1430 1431 /* 1432 * Map 1433 */ 1434 1435 { 1436 Name: "map-optional", 1437 Schema: map[string]*Schema{ 1438 "ports": { 1439 Type: TypeMap, 1440 Optional: true, 1441 }, 1442 }, 1443 1444 State: nil, 1445 Config: nil, 1446 1447 Diff: nil, 1448 1449 Key: "ports", 1450 Value: map[string]interface{}{}, 1451 Ok: false, 1452 }, 1453 1454 /* 1455 * Set 1456 */ 1457 1458 { 1459 Name: "set-optional", 1460 Schema: map[string]*Schema{ 1461 "ports": { 1462 Type: TypeSet, 1463 Optional: true, 1464 Elem: &Schema{Type: TypeInt}, 1465 Set: func(a interface{}) int { return a.(int) }, 1466 }, 1467 }, 1468 1469 State: nil, 1470 Config: nil, 1471 1472 Diff: nil, 1473 1474 Key: "ports", 1475 Value: []interface{}{}, 1476 Ok: false, 1477 }, 1478 1479 { 1480 Name: "set-optional-key", 1481 Schema: map[string]*Schema{ 1482 "ports": { 1483 Type: TypeSet, 1484 Optional: true, 1485 Elem: &Schema{Type: TypeInt}, 1486 Set: func(a interface{}) int { return a.(int) }, 1487 }, 1488 }, 1489 1490 State: nil, 1491 Config: nil, 1492 1493 Diff: nil, 1494 1495 Key: "ports.0", 1496 Value: 0, 1497 Ok: false, 1498 }, 1499 1500 { 1501 Name: "bool-literal-empty", 1502 Schema: map[string]*Schema{ 1503 "availability_zone": { 1504 Type: TypeBool, 1505 Optional: true, 1506 Computed: true, 1507 ForceNew: true, 1508 }, 1509 }, 1510 1511 State: nil, 1512 Config: nil, 1513 Diff: &tofu.InstanceDiff{ 1514 Attributes: map[string]*tofu.ResourceAttrDiff{ 1515 "availability_zone": { 1516 Old: "", 1517 New: "", 1518 }, 1519 }, 1520 }, 1521 1522 Key: "availability_zone", 1523 Value: false, 1524 Ok: true, 1525 }, 1526 1527 { 1528 Name: "bool-literal-set", 1529 Schema: map[string]*Schema{ 1530 "availability_zone": { 1531 Type: TypeBool, 1532 Optional: true, 1533 Computed: true, 1534 ForceNew: true, 1535 }, 1536 }, 1537 1538 State: nil, 1539 Config: nil, 1540 1541 Diff: &tofu.InstanceDiff{ 1542 Attributes: map[string]*tofu.ResourceAttrDiff{ 1543 "availability_zone": { 1544 New: "true", 1545 }, 1546 }, 1547 }, 1548 1549 Key: "availability_zone", 1550 Value: true, 1551 Ok: true, 1552 }, 1553 { 1554 Name: "value-in-config", 1555 Schema: map[string]*Schema{ 1556 "availability_zone": { 1557 Type: TypeString, 1558 Optional: true, 1559 }, 1560 }, 1561 1562 State: &tofu.InstanceState{ 1563 Attributes: map[string]string{ 1564 "availability_zone": "foo", 1565 }, 1566 }, 1567 Config: testConfig(t, map[string]interface{}{ 1568 "availability_zone": "foo", 1569 }), 1570 1571 Diff: &tofu.InstanceDiff{ 1572 Attributes: map[string]*tofu.ResourceAttrDiff{}, 1573 }, 1574 1575 Key: "availability_zone", 1576 Value: "foo", 1577 Ok: true, 1578 }, 1579 { 1580 Name: "new-value-in-config", 1581 Schema: map[string]*Schema{ 1582 "availability_zone": { 1583 Type: TypeString, 1584 Optional: true, 1585 }, 1586 }, 1587 1588 State: nil, 1589 Config: testConfig(t, map[string]interface{}{ 1590 "availability_zone": "foo", 1591 }), 1592 1593 Diff: &tofu.InstanceDiff{ 1594 Attributes: map[string]*tofu.ResourceAttrDiff{ 1595 "availability_zone": { 1596 Old: "", 1597 New: "foo", 1598 }, 1599 }, 1600 }, 1601 1602 Key: "availability_zone", 1603 Value: "foo", 1604 Ok: true, 1605 }, 1606 { 1607 Name: "optional-computed-value-in-config", 1608 Schema: map[string]*Schema{ 1609 "availability_zone": { 1610 Type: TypeString, 1611 Optional: true, 1612 Computed: true, 1613 }, 1614 }, 1615 1616 State: &tofu.InstanceState{ 1617 Attributes: map[string]string{ 1618 "availability_zone": "foo", 1619 }, 1620 }, 1621 Config: testConfig(t, map[string]interface{}{ 1622 "availability_zone": "bar", 1623 }), 1624 1625 Diff: &tofu.InstanceDiff{ 1626 Attributes: map[string]*tofu.ResourceAttrDiff{ 1627 "availability_zone": { 1628 Old: "foo", 1629 New: "bar", 1630 }, 1631 }, 1632 }, 1633 1634 Key: "availability_zone", 1635 Value: "bar", 1636 Ok: true, 1637 }, 1638 { 1639 Name: "removed-value", 1640 Schema: map[string]*Schema{ 1641 "availability_zone": { 1642 Type: TypeString, 1643 Optional: true, 1644 }, 1645 }, 1646 1647 State: &tofu.InstanceState{ 1648 Attributes: map[string]string{ 1649 "availability_zone": "foo", 1650 }, 1651 }, 1652 Config: testConfig(t, map[string]interface{}{}), 1653 1654 Diff: &tofu.InstanceDiff{ 1655 Attributes: map[string]*tofu.ResourceAttrDiff{ 1656 "availability_zone": { 1657 Old: "foo", 1658 New: "", 1659 NewRemoved: true, 1660 }, 1661 }, 1662 }, 1663 1664 Key: "availability_zone", 1665 Value: "", 1666 Ok: true, 1667 }, 1668 } 1669 1670 for i, tc := range cases { 1671 t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) { 1672 d := newResourceDiff(tc.Schema, tc.Config, tc.State, tc.Diff) 1673 1674 v, ok := d.GetOkExists(tc.Key) 1675 if s, ok := v.(*Set); ok { 1676 v = s.List() 1677 } 1678 1679 if !reflect.DeepEqual(v, tc.Value) { 1680 t.Fatalf("Bad %s: \n%#v", tc.Name, v) 1681 } 1682 if ok != tc.Ok { 1683 t.Fatalf("%s: expected ok: %t, got: %t", tc.Name, tc.Ok, ok) 1684 } 1685 }) 1686 } 1687 } 1688 1689 func TestResourceDiffGetOkExistsSetNew(t *testing.T) { 1690 tc := struct { 1691 Schema map[string]*Schema 1692 State *tofu.InstanceState 1693 Diff *tofu.InstanceDiff 1694 Key string 1695 Value interface{} 1696 Ok bool 1697 }{ 1698 Schema: map[string]*Schema{ 1699 "availability_zone": { 1700 Type: TypeString, 1701 Optional: true, 1702 Computed: true, 1703 }, 1704 }, 1705 1706 State: nil, 1707 1708 Diff: &tofu.InstanceDiff{ 1709 Attributes: map[string]*tofu.ResourceAttrDiff{}, 1710 }, 1711 1712 Key: "availability_zone", 1713 Value: "foobar", 1714 Ok: true, 1715 } 1716 1717 d := newResourceDiff(tc.Schema, testConfig(t, map[string]interface{}{}), tc.State, tc.Diff) 1718 d.SetNew(tc.Key, tc.Value) 1719 1720 v, ok := d.GetOkExists(tc.Key) 1721 if s, ok := v.(*Set); ok { 1722 v = s.List() 1723 } 1724 1725 if !reflect.DeepEqual(v, tc.Value) { 1726 t.Fatalf("Bad: \n%#v", v) 1727 } 1728 if ok != tc.Ok { 1729 t.Fatalf("expected ok: %t, got: %t", tc.Ok, ok) 1730 } 1731 } 1732 1733 func TestResourceDiffGetOkExistsSetNewComputed(t *testing.T) { 1734 tc := struct { 1735 Schema map[string]*Schema 1736 State *tofu.InstanceState 1737 Diff *tofu.InstanceDiff 1738 Key string 1739 Value interface{} 1740 Ok bool 1741 }{ 1742 Schema: map[string]*Schema{ 1743 "availability_zone": { 1744 Type: TypeString, 1745 Optional: true, 1746 Computed: true, 1747 }, 1748 }, 1749 1750 State: &tofu.InstanceState{ 1751 Attributes: map[string]string{ 1752 "availability_zone": "foo", 1753 }, 1754 }, 1755 1756 Diff: &tofu.InstanceDiff{ 1757 Attributes: map[string]*tofu.ResourceAttrDiff{}, 1758 }, 1759 1760 Key: "availability_zone", 1761 Value: "foobar", 1762 Ok: false, 1763 } 1764 1765 d := newResourceDiff(tc.Schema, testConfig(t, map[string]interface{}{}), tc.State, tc.Diff) 1766 d.SetNewComputed(tc.Key) 1767 1768 _, ok := d.GetOkExists(tc.Key) 1769 1770 if ok != tc.Ok { 1771 t.Fatalf("expected ok: %t, got: %t", tc.Ok, ok) 1772 } 1773 } 1774 1775 func TestResourceDiffNewValueKnown(t *testing.T) { 1776 cases := []struct { 1777 Name string 1778 Schema map[string]*Schema 1779 State *tofu.InstanceState 1780 Config *tofu.ResourceConfig 1781 Diff *tofu.InstanceDiff 1782 Key string 1783 Expected bool 1784 }{ 1785 { 1786 Name: "in config, no state", 1787 Schema: map[string]*Schema{ 1788 "availability_zone": { 1789 Type: TypeString, 1790 Optional: true, 1791 }, 1792 }, 1793 State: nil, 1794 Config: testConfig(t, map[string]interface{}{ 1795 "availability_zone": "foo", 1796 }), 1797 Diff: &tofu.InstanceDiff{ 1798 Attributes: map[string]*tofu.ResourceAttrDiff{ 1799 "availability_zone": { 1800 Old: "", 1801 New: "foo", 1802 }, 1803 }, 1804 }, 1805 Key: "availability_zone", 1806 Expected: true, 1807 }, 1808 { 1809 Name: "in config, has state, no diff", 1810 Schema: map[string]*Schema{ 1811 "availability_zone": { 1812 Type: TypeString, 1813 Optional: true, 1814 }, 1815 }, 1816 State: &tofu.InstanceState{ 1817 Attributes: map[string]string{ 1818 "availability_zone": "foo", 1819 }, 1820 }, 1821 Config: testConfig(t, map[string]interface{}{ 1822 "availability_zone": "foo", 1823 }), 1824 Diff: &tofu.InstanceDiff{ 1825 Attributes: map[string]*tofu.ResourceAttrDiff{}, 1826 }, 1827 Key: "availability_zone", 1828 Expected: true, 1829 }, 1830 { 1831 Name: "computed attribute, in state, no diff", 1832 Schema: map[string]*Schema{ 1833 "availability_zone": { 1834 Type: TypeString, 1835 Computed: true, 1836 }, 1837 }, 1838 State: &tofu.InstanceState{ 1839 Attributes: map[string]string{ 1840 "availability_zone": "foo", 1841 }, 1842 }, 1843 Config: testConfig(t, map[string]interface{}{}), 1844 Diff: &tofu.InstanceDiff{ 1845 Attributes: map[string]*tofu.ResourceAttrDiff{}, 1846 }, 1847 Key: "availability_zone", 1848 Expected: true, 1849 }, 1850 { 1851 Name: "optional and computed attribute, in state, no config", 1852 Schema: map[string]*Schema{ 1853 "availability_zone": { 1854 Type: TypeString, 1855 Optional: true, 1856 Computed: true, 1857 }, 1858 }, 1859 State: &tofu.InstanceState{ 1860 Attributes: map[string]string{ 1861 "availability_zone": "foo", 1862 }, 1863 }, 1864 Config: testConfig(t, map[string]interface{}{}), 1865 Diff: &tofu.InstanceDiff{ 1866 Attributes: map[string]*tofu.ResourceAttrDiff{}, 1867 }, 1868 Key: "availability_zone", 1869 Expected: true, 1870 }, 1871 { 1872 Name: "optional and computed attribute, in state, with config", 1873 Schema: map[string]*Schema{ 1874 "availability_zone": { 1875 Type: TypeString, 1876 Optional: true, 1877 Computed: true, 1878 }, 1879 }, 1880 State: &tofu.InstanceState{ 1881 Attributes: map[string]string{ 1882 "availability_zone": "foo", 1883 }, 1884 }, 1885 Config: testConfig(t, map[string]interface{}{ 1886 "availability_zone": "foo", 1887 }), 1888 Diff: &tofu.InstanceDiff{ 1889 Attributes: map[string]*tofu.ResourceAttrDiff{}, 1890 }, 1891 Key: "availability_zone", 1892 Expected: true, 1893 }, 1894 { 1895 Name: "computed value, through config reader", 1896 Schema: map[string]*Schema{ 1897 "availability_zone": { 1898 Type: TypeString, 1899 Optional: true, 1900 }, 1901 }, 1902 State: &tofu.InstanceState{ 1903 Attributes: map[string]string{ 1904 "availability_zone": "foo", 1905 }, 1906 }, 1907 Config: testConfig( 1908 t, 1909 map[string]interface{}{ 1910 "availability_zone": hcl2shim.UnknownVariableValue, 1911 }, 1912 ), 1913 Diff: &tofu.InstanceDiff{ 1914 Attributes: map[string]*tofu.ResourceAttrDiff{}, 1915 }, 1916 Key: "availability_zone", 1917 Expected: false, 1918 }, 1919 { 1920 Name: "computed value, through diff reader", 1921 Schema: map[string]*Schema{ 1922 "availability_zone": { 1923 Type: TypeString, 1924 Optional: true, 1925 }, 1926 }, 1927 State: &tofu.InstanceState{ 1928 Attributes: map[string]string{ 1929 "availability_zone": "foo", 1930 }, 1931 }, 1932 Config: testConfig( 1933 t, 1934 map[string]interface{}{ 1935 "availability_zone": hcl2shim.UnknownVariableValue, 1936 }, 1937 ), 1938 Diff: &tofu.InstanceDiff{ 1939 Attributes: map[string]*tofu.ResourceAttrDiff{ 1940 "availability_zone": { 1941 Old: "foo", 1942 New: "", 1943 NewComputed: true, 1944 }, 1945 }, 1946 }, 1947 Key: "availability_zone", 1948 Expected: false, 1949 }, 1950 } 1951 1952 for i, tc := range cases { 1953 t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) { 1954 d := newResourceDiff(tc.Schema, tc.Config, tc.State, tc.Diff) 1955 1956 actual := d.NewValueKnown(tc.Key) 1957 if tc.Expected != actual { 1958 t.Fatalf("%s: expected ok: %t, got: %t", tc.Name, tc.Expected, actual) 1959 } 1960 }) 1961 } 1962 } 1963 1964 func TestResourceDiffNewValueKnownSetNew(t *testing.T) { 1965 tc := struct { 1966 Schema map[string]*Schema 1967 State *tofu.InstanceState 1968 Config *tofu.ResourceConfig 1969 Diff *tofu.InstanceDiff 1970 Key string 1971 Value interface{} 1972 Expected bool 1973 }{ 1974 Schema: map[string]*Schema{ 1975 "availability_zone": { 1976 Type: TypeString, 1977 Optional: true, 1978 Computed: true, 1979 }, 1980 }, 1981 State: &tofu.InstanceState{ 1982 Attributes: map[string]string{ 1983 "availability_zone": "foo", 1984 }, 1985 }, 1986 Config: testConfig( 1987 t, 1988 map[string]interface{}{ 1989 "availability_zone": hcl2shim.UnknownVariableValue, 1990 }, 1991 ), 1992 Diff: &tofu.InstanceDiff{ 1993 Attributes: map[string]*tofu.ResourceAttrDiff{ 1994 "availability_zone": { 1995 Old: "foo", 1996 New: "", 1997 NewComputed: true, 1998 }, 1999 }, 2000 }, 2001 Key: "availability_zone", 2002 Value: "bar", 2003 Expected: true, 2004 } 2005 2006 d := newResourceDiff(tc.Schema, tc.Config, tc.State, tc.Diff) 2007 d.SetNew(tc.Key, tc.Value) 2008 2009 actual := d.NewValueKnown(tc.Key) 2010 if tc.Expected != actual { 2011 t.Fatalf("expected ok: %t, got: %t", tc.Expected, actual) 2012 } 2013 } 2014 2015 func TestResourceDiffNewValueKnownSetNewComputed(t *testing.T) { 2016 tc := struct { 2017 Schema map[string]*Schema 2018 State *tofu.InstanceState 2019 Config *tofu.ResourceConfig 2020 Diff *tofu.InstanceDiff 2021 Key string 2022 Expected bool 2023 }{ 2024 Schema: map[string]*Schema{ 2025 "availability_zone": { 2026 Type: TypeString, 2027 Computed: true, 2028 }, 2029 }, 2030 State: &tofu.InstanceState{ 2031 Attributes: map[string]string{ 2032 "availability_zone": "foo", 2033 }, 2034 }, 2035 Config: testConfig(t, map[string]interface{}{}), 2036 Diff: &tofu.InstanceDiff{ 2037 Attributes: map[string]*tofu.ResourceAttrDiff{}, 2038 }, 2039 Key: "availability_zone", 2040 Expected: false, 2041 } 2042 2043 d := newResourceDiff(tc.Schema, tc.Config, tc.State, tc.Diff) 2044 d.SetNewComputed(tc.Key) 2045 2046 actual := d.NewValueKnown(tc.Key) 2047 if tc.Expected != actual { 2048 t.Fatalf("expected ok: %t, got: %t", tc.Expected, actual) 2049 } 2050 }