github.com/mehmetalisavas/terraform@v0.7.10/terraform/diff_test.go (about) 1 package terraform 2 3 import ( 4 "reflect" 5 "strings" 6 "testing" 7 ) 8 9 func TestDiffEmpty(t *testing.T) { 10 var diff *Diff 11 if !diff.Empty() { 12 t.Fatal("should be empty") 13 } 14 15 diff = new(Diff) 16 if !diff.Empty() { 17 t.Fatal("should be empty") 18 } 19 20 mod := diff.AddModule(rootModulePath) 21 mod.Resources["nodeA"] = &InstanceDiff{ 22 Attributes: map[string]*ResourceAttrDiff{ 23 "foo": &ResourceAttrDiff{ 24 Old: "foo", 25 New: "bar", 26 }, 27 }, 28 } 29 30 if diff.Empty() { 31 t.Fatal("should not be empty") 32 } 33 } 34 35 func TestDiffEmpty_taintedIsNotEmpty(t *testing.T) { 36 diff := new(Diff) 37 38 mod := diff.AddModule(rootModulePath) 39 mod.Resources["nodeA"] = &InstanceDiff{ 40 DestroyTainted: true, 41 } 42 43 if diff.Empty() { 44 t.Fatal("should not be empty, since DestroyTainted was set") 45 } 46 } 47 48 func TestDiffEqual(t *testing.T) { 49 cases := map[string]struct { 50 D1, D2 *Diff 51 Equal bool 52 }{ 53 "nil": { 54 nil, 55 new(Diff), 56 false, 57 }, 58 59 "empty": { 60 new(Diff), 61 new(Diff), 62 true, 63 }, 64 65 "different module order": { 66 &Diff{ 67 Modules: []*ModuleDiff{ 68 &ModuleDiff{Path: []string{"root", "foo"}}, 69 &ModuleDiff{Path: []string{"root", "bar"}}, 70 }, 71 }, 72 &Diff{ 73 Modules: []*ModuleDiff{ 74 &ModuleDiff{Path: []string{"root", "bar"}}, 75 &ModuleDiff{Path: []string{"root", "foo"}}, 76 }, 77 }, 78 true, 79 }, 80 81 "different module diff destroys": { 82 &Diff{ 83 Modules: []*ModuleDiff{ 84 &ModuleDiff{Path: []string{"root", "foo"}, Destroy: true}, 85 }, 86 }, 87 &Diff{ 88 Modules: []*ModuleDiff{ 89 &ModuleDiff{Path: []string{"root", "foo"}, Destroy: false}, 90 }, 91 }, 92 true, 93 }, 94 } 95 96 for name, tc := range cases { 97 t.Run(name, func(t *testing.T) { 98 actual := tc.D1.Equal(tc.D2) 99 if actual != tc.Equal { 100 t.Fatalf("expected: %v\n\n%#v\n\n%#v", tc.Equal, tc.D1, tc.D2) 101 } 102 }) 103 } 104 } 105 106 func TestDiffPrune(t *testing.T) { 107 cases := map[string]struct { 108 D1, D2 *Diff 109 }{ 110 "nil": { 111 nil, 112 nil, 113 }, 114 115 "empty": { 116 new(Diff), 117 new(Diff), 118 }, 119 120 "empty module": { 121 &Diff{ 122 Modules: []*ModuleDiff{ 123 &ModuleDiff{Path: []string{"root", "foo"}}, 124 }, 125 }, 126 &Diff{}, 127 }, 128 129 "destroy module": { 130 &Diff{ 131 Modules: []*ModuleDiff{ 132 &ModuleDiff{Path: []string{"root", "foo"}, Destroy: true}, 133 }, 134 }, 135 &Diff{ 136 Modules: []*ModuleDiff{ 137 &ModuleDiff{Path: []string{"root", "foo"}, Destroy: true}, 138 }, 139 }, 140 }, 141 } 142 143 for name, tc := range cases { 144 t.Run(name, func(t *testing.T) { 145 tc.D1.Prune() 146 if !tc.D1.Equal(tc.D2) { 147 t.Fatalf("bad:\n\n%#v\n\n%#v", tc.D1, tc.D2) 148 } 149 }) 150 } 151 } 152 153 func TestModuleDiff_ChangeType(t *testing.T) { 154 cases := []struct { 155 Diff *ModuleDiff 156 Result DiffChangeType 157 }{ 158 { 159 &ModuleDiff{}, 160 DiffNone, 161 }, 162 { 163 &ModuleDiff{ 164 Resources: map[string]*InstanceDiff{ 165 "foo": &InstanceDiff{Destroy: true}, 166 }, 167 }, 168 DiffDestroy, 169 }, 170 { 171 &ModuleDiff{ 172 Resources: map[string]*InstanceDiff{ 173 "foo": &InstanceDiff{ 174 Attributes: map[string]*ResourceAttrDiff{ 175 "foo": &ResourceAttrDiff{ 176 Old: "", 177 New: "bar", 178 }, 179 }, 180 }, 181 }, 182 }, 183 DiffUpdate, 184 }, 185 { 186 &ModuleDiff{ 187 Resources: map[string]*InstanceDiff{ 188 "foo": &InstanceDiff{ 189 Attributes: map[string]*ResourceAttrDiff{ 190 "foo": &ResourceAttrDiff{ 191 Old: "", 192 New: "bar", 193 RequiresNew: true, 194 }, 195 }, 196 }, 197 }, 198 }, 199 DiffCreate, 200 }, 201 { 202 &ModuleDiff{ 203 Resources: map[string]*InstanceDiff{ 204 "foo": &InstanceDiff{ 205 Destroy: true, 206 Attributes: map[string]*ResourceAttrDiff{ 207 "foo": &ResourceAttrDiff{ 208 Old: "", 209 New: "bar", 210 RequiresNew: true, 211 }, 212 }, 213 }, 214 }, 215 }, 216 DiffUpdate, 217 }, 218 } 219 220 for i, tc := range cases { 221 actual := tc.Diff.ChangeType() 222 if actual != tc.Result { 223 t.Fatalf("%d: %#v", i, actual) 224 } 225 } 226 } 227 228 func TestDiff_DeepCopy(t *testing.T) { 229 cases := map[string]*Diff{ 230 "empty": &Diff{}, 231 232 "basic diff": &Diff{ 233 Modules: []*ModuleDiff{ 234 &ModuleDiff{ 235 Path: []string{"root"}, 236 Resources: map[string]*InstanceDiff{ 237 "aws_instance.foo": &InstanceDiff{ 238 Attributes: map[string]*ResourceAttrDiff{ 239 "num": &ResourceAttrDiff{ 240 Old: "0", 241 New: "2", 242 }, 243 }, 244 }, 245 }, 246 }, 247 }, 248 }, 249 } 250 251 for name, tc := range cases { 252 t.Run(name, func(t *testing.T) { 253 dup := tc.DeepCopy() 254 if !reflect.DeepEqual(dup, tc) { 255 t.Fatalf("\n%#v\n\n%#v", dup, tc) 256 } 257 }) 258 } 259 } 260 261 func TestModuleDiff_Empty(t *testing.T) { 262 diff := new(ModuleDiff) 263 if !diff.Empty() { 264 t.Fatal("should be empty") 265 } 266 267 diff.Resources = map[string]*InstanceDiff{ 268 "nodeA": &InstanceDiff{}, 269 } 270 271 if !diff.Empty() { 272 t.Fatal("should be empty") 273 } 274 275 diff.Resources["nodeA"].Attributes = map[string]*ResourceAttrDiff{ 276 "foo": &ResourceAttrDiff{ 277 Old: "foo", 278 New: "bar", 279 }, 280 } 281 282 if diff.Empty() { 283 t.Fatal("should not be empty") 284 } 285 286 diff.Resources["nodeA"].Attributes = nil 287 diff.Resources["nodeA"].Destroy = true 288 289 if diff.Empty() { 290 t.Fatal("should not be empty") 291 } 292 } 293 294 func TestModuleDiff_String(t *testing.T) { 295 diff := &ModuleDiff{ 296 Resources: map[string]*InstanceDiff{ 297 "nodeA": &InstanceDiff{ 298 Attributes: map[string]*ResourceAttrDiff{ 299 "foo": &ResourceAttrDiff{ 300 Old: "foo", 301 New: "bar", 302 }, 303 "bar": &ResourceAttrDiff{ 304 Old: "foo", 305 NewComputed: true, 306 }, 307 "longfoo": &ResourceAttrDiff{ 308 Old: "foo", 309 New: "bar", 310 RequiresNew: true, 311 }, 312 "secretfoo": &ResourceAttrDiff{ 313 Old: "foo", 314 New: "bar", 315 Sensitive: true, 316 }, 317 }, 318 }, 319 }, 320 } 321 322 actual := strings.TrimSpace(diff.String()) 323 expected := strings.TrimSpace(moduleDiffStrBasic) 324 if actual != expected { 325 t.Fatalf("bad:\n%s", actual) 326 } 327 } 328 329 func TestInstanceDiff_ChangeType(t *testing.T) { 330 cases := []struct { 331 Diff *InstanceDiff 332 Result DiffChangeType 333 }{ 334 { 335 &InstanceDiff{}, 336 DiffNone, 337 }, 338 { 339 &InstanceDiff{Destroy: true}, 340 DiffDestroy, 341 }, 342 { 343 &InstanceDiff{ 344 Attributes: map[string]*ResourceAttrDiff{ 345 "foo": &ResourceAttrDiff{ 346 Old: "", 347 New: "bar", 348 }, 349 }, 350 }, 351 DiffUpdate, 352 }, 353 { 354 &InstanceDiff{ 355 Attributes: map[string]*ResourceAttrDiff{ 356 "foo": &ResourceAttrDiff{ 357 Old: "", 358 New: "bar", 359 RequiresNew: true, 360 }, 361 }, 362 }, 363 DiffCreate, 364 }, 365 { 366 &InstanceDiff{ 367 Destroy: true, 368 Attributes: map[string]*ResourceAttrDiff{ 369 "foo": &ResourceAttrDiff{ 370 Old: "", 371 New: "bar", 372 RequiresNew: true, 373 }, 374 }, 375 }, 376 DiffDestroyCreate, 377 }, 378 { 379 &InstanceDiff{ 380 DestroyTainted: true, 381 Attributes: map[string]*ResourceAttrDiff{ 382 "foo": &ResourceAttrDiff{ 383 Old: "", 384 New: "bar", 385 RequiresNew: true, 386 }, 387 }, 388 }, 389 DiffDestroyCreate, 390 }, 391 } 392 393 for i, tc := range cases { 394 actual := tc.Diff.ChangeType() 395 if actual != tc.Result { 396 t.Fatalf("%d: %#v", i, actual) 397 } 398 } 399 } 400 401 func TestInstanceDiff_Empty(t *testing.T) { 402 var rd *InstanceDiff 403 404 if !rd.Empty() { 405 t.Fatal("should be empty") 406 } 407 408 rd = new(InstanceDiff) 409 410 if !rd.Empty() { 411 t.Fatal("should be empty") 412 } 413 414 rd = &InstanceDiff{Destroy: true} 415 416 if rd.Empty() { 417 t.Fatal("should not be empty") 418 } 419 420 rd = &InstanceDiff{ 421 Attributes: map[string]*ResourceAttrDiff{ 422 "foo": &ResourceAttrDiff{ 423 New: "bar", 424 }, 425 }, 426 } 427 428 if rd.Empty() { 429 t.Fatal("should not be empty") 430 } 431 } 432 433 func TestModuleDiff_Instances(t *testing.T) { 434 yesDiff := &InstanceDiff{Destroy: true} 435 noDiff := &InstanceDiff{Destroy: true, DestroyTainted: true} 436 437 cases := []struct { 438 Diff *ModuleDiff 439 Id string 440 Result []*InstanceDiff 441 }{ 442 { 443 &ModuleDiff{ 444 Resources: map[string]*InstanceDiff{ 445 "foo": yesDiff, 446 "bar": noDiff, 447 }, 448 }, 449 "foo", 450 []*InstanceDiff{ 451 yesDiff, 452 }, 453 }, 454 455 { 456 &ModuleDiff{ 457 Resources: map[string]*InstanceDiff{ 458 "foo": yesDiff, 459 "foo.0": yesDiff, 460 "bar": noDiff, 461 }, 462 }, 463 "foo", 464 []*InstanceDiff{ 465 yesDiff, 466 yesDiff, 467 }, 468 }, 469 470 { 471 &ModuleDiff{ 472 Resources: map[string]*InstanceDiff{ 473 "foo": yesDiff, 474 "foo.0": yesDiff, 475 "foo_bar": noDiff, 476 "bar": noDiff, 477 }, 478 }, 479 "foo", 480 []*InstanceDiff{ 481 yesDiff, 482 yesDiff, 483 }, 484 }, 485 } 486 487 for i, tc := range cases { 488 actual := tc.Diff.Instances(tc.Id) 489 if !reflect.DeepEqual(actual, tc.Result) { 490 t.Fatalf("%d: %#v", i, actual) 491 } 492 } 493 } 494 495 func TestInstanceDiff_RequiresNew(t *testing.T) { 496 rd := &InstanceDiff{ 497 Attributes: map[string]*ResourceAttrDiff{ 498 "foo": &ResourceAttrDiff{}, 499 }, 500 } 501 502 if rd.RequiresNew() { 503 t.Fatal("should not require new") 504 } 505 506 rd.Attributes["foo"].RequiresNew = true 507 508 if !rd.RequiresNew() { 509 t.Fatal("should require new") 510 } 511 } 512 513 func TestInstanceDiff_RequiresNew_nil(t *testing.T) { 514 var rd *InstanceDiff 515 516 if rd.RequiresNew() { 517 t.Fatal("should not require new") 518 } 519 } 520 521 func TestInstanceDiffSame(t *testing.T) { 522 cases := []struct { 523 One, Two *InstanceDiff 524 Same bool 525 Reason string 526 }{ 527 { 528 &InstanceDiff{}, 529 &InstanceDiff{}, 530 true, 531 "", 532 }, 533 534 { 535 nil, 536 nil, 537 true, 538 "", 539 }, 540 541 { 542 &InstanceDiff{Destroy: false}, 543 &InstanceDiff{Destroy: true}, 544 false, 545 "diff: Destroy; old: false, new: true", 546 }, 547 548 { 549 &InstanceDiff{Destroy: true}, 550 &InstanceDiff{Destroy: true}, 551 true, 552 "", 553 }, 554 555 { 556 &InstanceDiff{ 557 Attributes: map[string]*ResourceAttrDiff{ 558 "foo": &ResourceAttrDiff{}, 559 }, 560 }, 561 &InstanceDiff{ 562 Attributes: map[string]*ResourceAttrDiff{ 563 "foo": &ResourceAttrDiff{}, 564 }, 565 }, 566 true, 567 "", 568 }, 569 570 { 571 &InstanceDiff{ 572 Attributes: map[string]*ResourceAttrDiff{ 573 "bar": &ResourceAttrDiff{}, 574 }, 575 }, 576 &InstanceDiff{ 577 Attributes: map[string]*ResourceAttrDiff{ 578 "foo": &ResourceAttrDiff{}, 579 }, 580 }, 581 false, 582 "attribute mismatch: bar", 583 }, 584 585 // Extra attributes 586 { 587 &InstanceDiff{ 588 Attributes: map[string]*ResourceAttrDiff{ 589 "foo": &ResourceAttrDiff{}, 590 }, 591 }, 592 &InstanceDiff{ 593 Attributes: map[string]*ResourceAttrDiff{ 594 "foo": &ResourceAttrDiff{}, 595 "bar": &ResourceAttrDiff{}, 596 }, 597 }, 598 false, 599 "extra attributes: bar", 600 }, 601 602 { 603 &InstanceDiff{ 604 Attributes: map[string]*ResourceAttrDiff{ 605 "foo": &ResourceAttrDiff{RequiresNew: true}, 606 }, 607 }, 608 &InstanceDiff{ 609 Attributes: map[string]*ResourceAttrDiff{ 610 "foo": &ResourceAttrDiff{RequiresNew: false}, 611 }, 612 }, 613 false, 614 "diff RequiresNew; old: true, new: false", 615 }, 616 617 // NewComputed on primitive 618 { 619 &InstanceDiff{ 620 Attributes: map[string]*ResourceAttrDiff{ 621 "foo": &ResourceAttrDiff{ 622 Old: "", 623 New: "${var.foo}", 624 NewComputed: true, 625 }, 626 }, 627 }, 628 &InstanceDiff{ 629 Attributes: map[string]*ResourceAttrDiff{ 630 "foo": &ResourceAttrDiff{ 631 Old: "0", 632 New: "1", 633 }, 634 }, 635 }, 636 true, 637 "", 638 }, 639 640 // NewComputed on primitive, removed 641 { 642 &InstanceDiff{ 643 Attributes: map[string]*ResourceAttrDiff{ 644 "foo": &ResourceAttrDiff{ 645 Old: "", 646 New: "${var.foo}", 647 NewComputed: true, 648 }, 649 }, 650 }, 651 &InstanceDiff{ 652 Attributes: map[string]*ResourceAttrDiff{}, 653 }, 654 true, 655 "", 656 }, 657 658 // NewComputed on set, removed 659 { 660 &InstanceDiff{ 661 Attributes: map[string]*ResourceAttrDiff{ 662 "foo.#": &ResourceAttrDiff{ 663 Old: "", 664 New: "", 665 NewComputed: true, 666 }, 667 }, 668 }, 669 &InstanceDiff{ 670 Attributes: map[string]*ResourceAttrDiff{ 671 "foo.1": &ResourceAttrDiff{ 672 Old: "foo", 673 New: "", 674 NewRemoved: true, 675 }, 676 "foo.2": &ResourceAttrDiff{ 677 Old: "", 678 New: "bar", 679 }, 680 }, 681 }, 682 true, 683 "", 684 }, 685 686 { 687 &InstanceDiff{ 688 Attributes: map[string]*ResourceAttrDiff{ 689 "foo.#": &ResourceAttrDiff{NewComputed: true}, 690 }, 691 }, 692 &InstanceDiff{ 693 Attributes: map[string]*ResourceAttrDiff{ 694 "foo.#": &ResourceAttrDiff{ 695 Old: "0", 696 New: "1", 697 }, 698 "foo.0": &ResourceAttrDiff{ 699 Old: "", 700 New: "12", 701 }, 702 }, 703 }, 704 true, 705 "", 706 }, 707 708 { 709 &InstanceDiff{ 710 Attributes: map[string]*ResourceAttrDiff{ 711 "foo.#": &ResourceAttrDiff{ 712 Old: "0", 713 New: "1", 714 }, 715 "foo.~35964334.bar": &ResourceAttrDiff{ 716 Old: "", 717 New: "${var.foo}", 718 }, 719 }, 720 }, 721 &InstanceDiff{ 722 Attributes: map[string]*ResourceAttrDiff{ 723 "foo.#": &ResourceAttrDiff{ 724 Old: "0", 725 New: "1", 726 }, 727 "foo.87654323.bar": &ResourceAttrDiff{ 728 Old: "", 729 New: "12", 730 }, 731 }, 732 }, 733 true, 734 "", 735 }, 736 737 { 738 &InstanceDiff{ 739 Attributes: map[string]*ResourceAttrDiff{ 740 "foo.#": &ResourceAttrDiff{ 741 Old: "0", 742 NewComputed: true, 743 }, 744 }, 745 }, 746 &InstanceDiff{ 747 Attributes: map[string]*ResourceAttrDiff{}, 748 }, 749 true, 750 "", 751 }, 752 753 // Computed sets may not contain all fields in the original diff, and 754 // because multiple entries for the same set can compute to the same 755 // hash before the values are computed or interpolated, the overall 756 // count can change as well. 757 { 758 &InstanceDiff{ 759 Attributes: map[string]*ResourceAttrDiff{ 760 "foo.#": &ResourceAttrDiff{ 761 Old: "0", 762 New: "1", 763 }, 764 "foo.~35964334.bar": &ResourceAttrDiff{ 765 Old: "", 766 New: "${var.foo}", 767 }, 768 }, 769 }, 770 &InstanceDiff{ 771 Attributes: map[string]*ResourceAttrDiff{ 772 "foo.#": &ResourceAttrDiff{ 773 Old: "0", 774 New: "2", 775 }, 776 "foo.87654323.bar": &ResourceAttrDiff{ 777 Old: "", 778 New: "12", 779 }, 780 "foo.87654325.bar": &ResourceAttrDiff{ 781 Old: "", 782 New: "12", 783 }, 784 "foo.87654325.baz": &ResourceAttrDiff{ 785 Old: "", 786 New: "12", 787 }, 788 }, 789 }, 790 true, 791 "", 792 }, 793 794 // Computed values in maps will fail the "Same" check as well 795 { 796 &InstanceDiff{ 797 Attributes: map[string]*ResourceAttrDiff{ 798 "foo.%": &ResourceAttrDiff{ 799 Old: "", 800 New: "", 801 NewComputed: true, 802 }, 803 }, 804 }, 805 &InstanceDiff{ 806 Attributes: map[string]*ResourceAttrDiff{ 807 "foo.%": &ResourceAttrDiff{ 808 Old: "0", 809 New: "1", 810 NewComputed: false, 811 }, 812 "foo.val": &ResourceAttrDiff{ 813 Old: "", 814 New: "something", 815 }, 816 }, 817 }, 818 true, 819 "", 820 }, 821 822 // In a DESTROY/CREATE scenario, the plan diff will be run against the 823 // state of the old instance, while the apply diff will be run against an 824 // empty state (because the state is cleared when the destroy runs.) 825 // For complex attributes, this can result in keys that seem to disappear 826 // between the two diffs, when in reality everything is working just fine. 827 // 828 // Same() needs to take into account this scenario by analyzing NewRemoved 829 // and treating as "Same" a diff that does indeed have that key removed. 830 { 831 &InstanceDiff{ 832 Attributes: map[string]*ResourceAttrDiff{ 833 "somemap.oldkey": &ResourceAttrDiff{ 834 Old: "long ago", 835 New: "", 836 NewRemoved: true, 837 }, 838 "somemap.newkey": &ResourceAttrDiff{ 839 Old: "", 840 New: "brave new world", 841 }, 842 }, 843 }, 844 &InstanceDiff{ 845 Attributes: map[string]*ResourceAttrDiff{ 846 "somemap.newkey": &ResourceAttrDiff{ 847 Old: "", 848 New: "brave new world", 849 }, 850 }, 851 }, 852 true, 853 "", 854 }, 855 856 // Another thing that can occur in DESTROY/CREATE scenarios is that list 857 // values that are going to zero have diffs that show up at plan time but 858 // are gone at apply time. The NewRemoved handling catches the fields and 859 // treats them as OK, but it also needs to treat the .# field itself as 860 // okay to be present in the old diff but not in the new one. 861 { 862 &InstanceDiff{ 863 Attributes: map[string]*ResourceAttrDiff{ 864 "reqnew": &ResourceAttrDiff{ 865 Old: "old", 866 New: "new", 867 RequiresNew: true, 868 }, 869 "somemap.#": &ResourceAttrDiff{ 870 Old: "1", 871 New: "0", 872 }, 873 "somemap.oldkey": &ResourceAttrDiff{ 874 Old: "long ago", 875 New: "", 876 NewRemoved: true, 877 }, 878 }, 879 }, 880 &InstanceDiff{ 881 Attributes: map[string]*ResourceAttrDiff{ 882 "reqnew": &ResourceAttrDiff{ 883 Old: "", 884 New: "new", 885 RequiresNew: true, 886 }, 887 }, 888 }, 889 true, 890 "", 891 }, 892 893 { 894 &InstanceDiff{ 895 Attributes: map[string]*ResourceAttrDiff{ 896 "reqnew": &ResourceAttrDiff{ 897 Old: "old", 898 New: "new", 899 RequiresNew: true, 900 }, 901 "somemap.%": &ResourceAttrDiff{ 902 Old: "1", 903 New: "0", 904 }, 905 "somemap.oldkey": &ResourceAttrDiff{ 906 Old: "long ago", 907 New: "", 908 NewRemoved: true, 909 }, 910 }, 911 }, 912 &InstanceDiff{ 913 Attributes: map[string]*ResourceAttrDiff{ 914 "reqnew": &ResourceAttrDiff{ 915 Old: "", 916 New: "new", 917 RequiresNew: true, 918 }, 919 }, 920 }, 921 true, 922 "", 923 }, 924 } 925 926 for i, tc := range cases { 927 same, reason := tc.One.Same(tc.Two) 928 if same != tc.Same { 929 t.Fatalf("%d: expected same: %t, got %t (%s)\n\n one: %#v\n\ntwo: %#v", 930 i, tc.Same, same, reason, tc.One, tc.Two) 931 } 932 if reason != tc.Reason { 933 t.Fatalf( 934 "%d: bad reason\n\nexpected: %#v\n\ngot: %#v", i, tc.Reason, reason) 935 } 936 } 937 } 938 939 const moduleDiffStrBasic = ` 940 CREATE: nodeA 941 bar: "foo" => "<computed>" 942 foo: "foo" => "bar" 943 longfoo: "foo" => "bar" (forces new resource) 944 secretfoo: "<sensitive>" => "<sensitive>" (attribute changed) 945 `