github.com/opentofu/opentofu@v1.7.1/internal/command/jsonformat/differ/differ_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 differ 7 8 import ( 9 "encoding/json" 10 "fmt" 11 "testing" 12 13 "github.com/zclconf/go-cty/cty" 14 ctyjson "github.com/zclconf/go-cty/cty/json" 15 16 "github.com/opentofu/opentofu/internal/command/jsonformat/computed/renderers" 17 "github.com/opentofu/opentofu/internal/command/jsonformat/structured" 18 "github.com/opentofu/opentofu/internal/command/jsonformat/structured/attribute_path" 19 "github.com/opentofu/opentofu/internal/command/jsonprovider" 20 "github.com/opentofu/opentofu/internal/plans" 21 ) 22 23 type SetDiff struct { 24 Before SetDiffEntry 25 After SetDiffEntry 26 } 27 28 type SetDiffEntry struct { 29 SingleDiff renderers.ValidateDiffFunction 30 ObjectDiff map[string]renderers.ValidateDiffFunction 31 32 Replace bool 33 Action plans.Action 34 } 35 36 func (entry SetDiffEntry) Validate(obj func(attributes map[string]renderers.ValidateDiffFunction, action plans.Action, replace bool) renderers.ValidateDiffFunction) renderers.ValidateDiffFunction { 37 if entry.SingleDiff != nil { 38 return entry.SingleDiff 39 } 40 41 return obj(entry.ObjectDiff, entry.Action, entry.Replace) 42 } 43 44 func TestValue_SimpleBlocks(t *testing.T) { 45 // Most of the other test functions wrap the test cases in various 46 // collections or blocks. This function just very simply lets you specify 47 // individual test cases within blocks for some simple tests. 48 49 tcs := map[string]struct { 50 input structured.Change 51 block *jsonprovider.Block 52 validate renderers.ValidateDiffFunction 53 }{ 54 "delete_with_null_sensitive_value": { 55 input: structured.Change{ 56 Before: map[string]interface{}{ 57 "normal_attribute": "some value", 58 }, 59 After: nil, 60 BeforeSensitive: map[string]interface{}{ 61 "sensitive_attribute": true, 62 }, 63 AfterSensitive: false, 64 }, 65 block: &jsonprovider.Block{ 66 Attributes: map[string]*jsonprovider.Attribute{ 67 "normal_attribute": { 68 AttributeType: unmarshalType(t, cty.String), 69 }, 70 "sensitive_attribute": { 71 AttributeType: unmarshalType(t, cty.String), 72 }, 73 }, 74 }, 75 validate: renderers.ValidateBlock(map[string]renderers.ValidateDiffFunction{ 76 "normal_attribute": renderers.ValidatePrimitive("some value", nil, plans.Delete, false), 77 }, nil, nil, nil, nil, plans.Delete, false), 78 }, 79 "create_with_null_sensitive_value": { 80 input: structured.Change{ 81 Before: nil, 82 After: map[string]interface{}{ 83 "normal_attribute": "some value", 84 }, 85 BeforeSensitive: map[string]interface{}{ 86 "sensitive_attribute": true, 87 }, 88 AfterSensitive: false, 89 }, 90 block: &jsonprovider.Block{ 91 Attributes: map[string]*jsonprovider.Attribute{ 92 "normal_attribute": { 93 AttributeType: unmarshalType(t, cty.String), 94 }, 95 "sensitive_attribute": { 96 AttributeType: unmarshalType(t, cty.String), 97 }, 98 }, 99 }, 100 validate: renderers.ValidateBlock(map[string]renderers.ValidateDiffFunction{ 101 "normal_attribute": renderers.ValidatePrimitive(nil, "some value", plans.Create, false), 102 }, nil, nil, nil, nil, plans.Create, false), 103 }, 104 } 105 for name, tc := range tcs { 106 // Set some default values 107 if tc.input.ReplacePaths == nil { 108 tc.input.ReplacePaths = &attribute_path.PathMatcher{} 109 } 110 111 if tc.input.RelevantAttributes == nil { 112 tc.input.RelevantAttributes = attribute_path.AlwaysMatcher() 113 } 114 115 t.Run(name, func(t *testing.T) { 116 tc.validate(t, ComputeDiffForBlock(tc.input, tc.block)) 117 }) 118 } 119 } 120 121 func TestValue_ObjectAttributes(t *testing.T) { 122 // This function holds a range of test cases creating, deleting and editing 123 // objects. It is built in such a way that it can automatically test these 124 // operations on objects both directly and nested, as well as within all 125 // types of collections. 126 127 tcs := map[string]struct { 128 input structured.Change 129 attributes map[string]cty.Type 130 validateSingleDiff renderers.ValidateDiffFunction 131 validateObject renderers.ValidateDiffFunction 132 validateNestedObject renderers.ValidateDiffFunction 133 validateDiffs map[string]renderers.ValidateDiffFunction 134 validateList renderers.ValidateDiffFunction 135 validateReplace bool 136 validateAction plans.Action 137 // Sets break changes out differently to the other collections, so they 138 // have their own entry. 139 validateSetDiffs *SetDiff 140 }{ 141 "create": { 142 input: structured.Change{ 143 Before: nil, 144 After: map[string]interface{}{ 145 "attribute_one": "new", 146 }, 147 }, 148 attributes: map[string]cty.Type{ 149 "attribute_one": cty.String, 150 }, 151 validateDiffs: map[string]renderers.ValidateDiffFunction{ 152 "attribute_one": renderers.ValidatePrimitive(nil, "new", plans.Create, false), 153 }, 154 validateAction: plans.Create, 155 validateReplace: false, 156 }, 157 "delete": { 158 input: structured.Change{ 159 Before: map[string]interface{}{ 160 "attribute_one": "old", 161 }, 162 After: nil, 163 }, 164 attributes: map[string]cty.Type{ 165 "attribute_one": cty.String, 166 }, 167 validateDiffs: map[string]renderers.ValidateDiffFunction{ 168 "attribute_one": renderers.ValidatePrimitive("old", nil, plans.Delete, false), 169 }, 170 validateAction: plans.Delete, 171 validateReplace: false, 172 }, 173 "create_sensitive": { 174 input: structured.Change{ 175 Before: nil, 176 After: map[string]interface{}{ 177 "attribute_one": "new", 178 }, 179 AfterSensitive: true, 180 }, 181 attributes: map[string]cty.Type{ 182 "attribute_one": cty.String, 183 }, 184 validateSingleDiff: renderers.ValidateSensitive(renderers.ValidateObject(map[string]renderers.ValidateDiffFunction{ 185 "attribute_one": renderers.ValidatePrimitive(nil, "new", plans.Create, false), 186 }, plans.Create, false), 187 false, 188 true, 189 plans.Create, 190 false), 191 validateNestedObject: renderers.ValidateSensitive(renderers.ValidateNestedObject(map[string]renderers.ValidateDiffFunction{ 192 "attribute_one": renderers.ValidatePrimitive(nil, "new", plans.Create, false), 193 }, plans.Create, false), 194 false, 195 true, 196 plans.Create, 197 false), 198 }, 199 "delete_sensitive": { 200 input: structured.Change{ 201 Before: map[string]interface{}{ 202 "attribute_one": "old", 203 }, 204 BeforeSensitive: true, 205 After: nil, 206 }, 207 attributes: map[string]cty.Type{ 208 "attribute_one": cty.String, 209 }, 210 validateSingleDiff: renderers.ValidateSensitive(renderers.ValidateObject(map[string]renderers.ValidateDiffFunction{ 211 "attribute_one": renderers.ValidatePrimitive("old", nil, plans.Delete, false), 212 }, plans.Delete, false), true, false, plans.Delete, false), 213 validateNestedObject: renderers.ValidateSensitive(renderers.ValidateNestedObject(map[string]renderers.ValidateDiffFunction{ 214 "attribute_one": renderers.ValidatePrimitive("old", nil, plans.Delete, false), 215 }, plans.Delete, false), true, false, plans.Delete, false), 216 }, 217 "create_unknown": { 218 input: structured.Change{ 219 Before: nil, 220 After: nil, 221 Unknown: true, 222 }, 223 attributes: map[string]cty.Type{ 224 "attribute_one": cty.String, 225 }, 226 validateSingleDiff: renderers.ValidateUnknown(nil, plans.Create, false), 227 }, 228 "update_unknown": { 229 input: structured.Change{ 230 Before: map[string]interface{}{ 231 "attribute_one": "old", 232 }, 233 After: nil, 234 Unknown: true, 235 }, 236 attributes: map[string]cty.Type{ 237 "attribute_one": cty.String, 238 }, 239 validateObject: renderers.ValidateUnknown(renderers.ValidateObject(map[string]renderers.ValidateDiffFunction{ 240 "attribute_one": renderers.ValidatePrimitive("old", nil, plans.Delete, false), 241 }, plans.Delete, false), plans.Update, false), 242 validateNestedObject: renderers.ValidateUnknown(renderers.ValidateNestedObject(map[string]renderers.ValidateDiffFunction{ 243 "attribute_one": renderers.ValidateUnknown(renderers.ValidatePrimitive("old", nil, plans.Delete, false), plans.Update, false), 244 }, plans.Update, false), plans.Update, false), 245 validateSetDiffs: &SetDiff{ 246 Before: SetDiffEntry{ 247 ObjectDiff: map[string]renderers.ValidateDiffFunction{ 248 "attribute_one": renderers.ValidatePrimitive("old", nil, plans.Delete, false), 249 }, 250 Action: plans.Delete, 251 Replace: false, 252 }, 253 After: SetDiffEntry{ 254 SingleDiff: renderers.ValidateUnknown(nil, plans.Create, false), 255 }, 256 }, 257 }, 258 "create_attribute": { 259 input: structured.Change{ 260 Before: map[string]interface{}{}, 261 After: map[string]interface{}{ 262 "attribute_one": "new", 263 }, 264 }, 265 attributes: map[string]cty.Type{ 266 "attribute_one": cty.String, 267 }, 268 validateDiffs: map[string]renderers.ValidateDiffFunction{ 269 "attribute_one": renderers.ValidatePrimitive(nil, "new", plans.Create, false), 270 }, 271 validateAction: plans.Update, 272 validateReplace: false, 273 validateSetDiffs: &SetDiff{ 274 Before: SetDiffEntry{ 275 ObjectDiff: nil, 276 Action: plans.Delete, 277 Replace: false, 278 }, 279 After: SetDiffEntry{ 280 ObjectDiff: map[string]renderers.ValidateDiffFunction{ 281 "attribute_one": renderers.ValidatePrimitive(nil, "new", plans.Create, false), 282 }, 283 Action: plans.Create, 284 Replace: false, 285 }, 286 }, 287 }, 288 "create_attribute_from_explicit_null": { 289 input: structured.Change{ 290 Before: map[string]interface{}{ 291 "attribute_one": nil, 292 }, 293 After: map[string]interface{}{ 294 "attribute_one": "new", 295 }, 296 }, 297 attributes: map[string]cty.Type{ 298 "attribute_one": cty.String, 299 }, 300 validateDiffs: map[string]renderers.ValidateDiffFunction{ 301 "attribute_one": renderers.ValidatePrimitive(nil, "new", plans.Create, false), 302 }, 303 validateAction: plans.Update, 304 validateReplace: false, 305 validateSetDiffs: &SetDiff{ 306 Before: SetDiffEntry{ 307 ObjectDiff: nil, 308 Action: plans.Delete, 309 Replace: false, 310 }, 311 After: SetDiffEntry{ 312 ObjectDiff: map[string]renderers.ValidateDiffFunction{ 313 "attribute_one": renderers.ValidatePrimitive(nil, "new", plans.Create, false), 314 }, 315 Action: plans.Create, 316 Replace: false, 317 }, 318 }, 319 }, 320 "delete_attribute": { 321 input: structured.Change{ 322 Before: map[string]interface{}{ 323 "attribute_one": "old", 324 }, 325 After: map[string]interface{}{}, 326 }, 327 attributes: map[string]cty.Type{ 328 "attribute_one": cty.String, 329 }, 330 validateDiffs: map[string]renderers.ValidateDiffFunction{ 331 "attribute_one": renderers.ValidatePrimitive("old", nil, plans.Delete, false), 332 }, 333 validateAction: plans.Update, 334 validateReplace: false, 335 validateSetDiffs: &SetDiff{ 336 Before: SetDiffEntry{ 337 ObjectDiff: map[string]renderers.ValidateDiffFunction{ 338 "attribute_one": renderers.ValidatePrimitive("old", nil, plans.Delete, false), 339 }, 340 Action: plans.Delete, 341 Replace: false, 342 }, 343 After: SetDiffEntry{ 344 ObjectDiff: nil, 345 Action: plans.Create, 346 Replace: false, 347 }, 348 }, 349 }, 350 "delete_attribute_to_explicit_null": { 351 input: structured.Change{ 352 Before: map[string]interface{}{ 353 "attribute_one": "old", 354 }, 355 After: map[string]interface{}{ 356 "attribute_one": nil, 357 }, 358 }, 359 attributes: map[string]cty.Type{ 360 "attribute_one": cty.String, 361 }, 362 validateDiffs: map[string]renderers.ValidateDiffFunction{ 363 "attribute_one": renderers.ValidatePrimitive("old", nil, plans.Delete, false), 364 }, 365 validateAction: plans.Update, 366 validateReplace: false, 367 validateSetDiffs: &SetDiff{ 368 Before: SetDiffEntry{ 369 ObjectDiff: map[string]renderers.ValidateDiffFunction{ 370 "attribute_one": renderers.ValidatePrimitive("old", nil, plans.Delete, false), 371 }, 372 Action: plans.Delete, 373 Replace: false, 374 }, 375 After: SetDiffEntry{ 376 ObjectDiff: nil, 377 Action: plans.Create, 378 Replace: false, 379 }, 380 }, 381 }, 382 "update_attribute": { 383 input: structured.Change{ 384 Before: map[string]interface{}{ 385 "attribute_one": "old", 386 }, 387 After: map[string]interface{}{ 388 "attribute_one": "new", 389 }, 390 }, 391 attributes: map[string]cty.Type{ 392 "attribute_one": cty.String, 393 }, 394 validateDiffs: map[string]renderers.ValidateDiffFunction{ 395 "attribute_one": renderers.ValidatePrimitive("old", "new", plans.Update, false), 396 }, 397 validateAction: plans.Update, 398 validateReplace: false, 399 validateSetDiffs: &SetDiff{ 400 Before: SetDiffEntry{ 401 ObjectDiff: map[string]renderers.ValidateDiffFunction{ 402 "attribute_one": renderers.ValidatePrimitive("old", nil, plans.Delete, false), 403 }, 404 Action: plans.Delete, 405 Replace: false, 406 }, 407 After: SetDiffEntry{ 408 ObjectDiff: map[string]renderers.ValidateDiffFunction{ 409 "attribute_one": renderers.ValidatePrimitive(nil, "new", plans.Create, false), 410 }, 411 Action: plans.Create, 412 Replace: false, 413 }, 414 }, 415 }, 416 "create_sensitive_attribute": { 417 input: structured.Change{ 418 Before: map[string]interface{}{}, 419 After: map[string]interface{}{ 420 "attribute_one": "new", 421 }, 422 AfterSensitive: map[string]interface{}{ 423 "attribute_one": true, 424 }, 425 }, 426 attributes: map[string]cty.Type{ 427 "attribute_one": cty.String, 428 }, 429 validateDiffs: map[string]renderers.ValidateDiffFunction{ 430 "attribute_one": renderers.ValidateSensitive(renderers.ValidatePrimitive(nil, "new", plans.Create, false), false, true, plans.Create, false), 431 }, 432 validateAction: plans.Update, 433 validateReplace: false, 434 validateSetDiffs: &SetDiff{ 435 Before: SetDiffEntry{ 436 ObjectDiff: nil, 437 Action: plans.Delete, 438 Replace: false, 439 }, 440 After: SetDiffEntry{ 441 ObjectDiff: map[string]renderers.ValidateDiffFunction{ 442 "attribute_one": renderers.ValidateSensitive(renderers.ValidatePrimitive(nil, "new", plans.Create, false), false, true, plans.Create, false), 443 }, 444 Action: plans.Create, 445 Replace: false, 446 }, 447 }, 448 }, 449 "delete_sensitive_attribute": { 450 input: structured.Change{ 451 Before: map[string]interface{}{ 452 "attribute_one": "old", 453 }, 454 BeforeSensitive: map[string]interface{}{ 455 "attribute_one": true, 456 }, 457 After: map[string]interface{}{}, 458 }, 459 attributes: map[string]cty.Type{ 460 "attribute_one": cty.String, 461 }, 462 validateDiffs: map[string]renderers.ValidateDiffFunction{ 463 "attribute_one": renderers.ValidateSensitive(renderers.ValidatePrimitive("old", nil, plans.Delete, false), true, false, plans.Delete, false), 464 }, 465 validateAction: plans.Update, 466 validateReplace: false, 467 validateSetDiffs: &SetDiff{ 468 Before: SetDiffEntry{ 469 ObjectDiff: map[string]renderers.ValidateDiffFunction{ 470 "attribute_one": renderers.ValidateSensitive(renderers.ValidatePrimitive("old", nil, plans.Delete, false), true, false, plans.Delete, false), 471 }, 472 Action: plans.Delete, 473 Replace: false, 474 }, 475 After: SetDiffEntry{ 476 ObjectDiff: nil, 477 Action: plans.Create, 478 Replace: false, 479 }, 480 }, 481 }, 482 "update_sensitive_attribute": { 483 input: structured.Change{ 484 Before: map[string]interface{}{ 485 "attribute_one": "old", 486 }, 487 BeforeSensitive: map[string]interface{}{ 488 "attribute_one": true, 489 }, 490 After: map[string]interface{}{ 491 "attribute_one": "new", 492 }, 493 AfterSensitive: map[string]interface{}{ 494 "attribute_one": true, 495 }, 496 }, 497 attributes: map[string]cty.Type{ 498 "attribute_one": cty.String, 499 }, 500 validateDiffs: map[string]renderers.ValidateDiffFunction{ 501 "attribute_one": renderers.ValidateSensitive(renderers.ValidatePrimitive("old", "new", plans.Update, false), true, true, plans.Update, false), 502 }, 503 validateAction: plans.Update, 504 validateReplace: false, 505 validateSetDiffs: &SetDiff{ 506 Before: SetDiffEntry{ 507 ObjectDiff: map[string]renderers.ValidateDiffFunction{ 508 "attribute_one": renderers.ValidateSensitive(renderers.ValidatePrimitive("old", nil, plans.Delete, false), true, false, plans.Delete, false), 509 }, 510 Action: plans.Delete, 511 Replace: false, 512 }, 513 After: SetDiffEntry{ 514 ObjectDiff: map[string]renderers.ValidateDiffFunction{ 515 "attribute_one": renderers.ValidateSensitive(renderers.ValidatePrimitive(nil, "new", plans.Create, false), false, true, plans.Create, false), 516 }, 517 Action: plans.Create, 518 Replace: false, 519 }, 520 }, 521 }, 522 "create_computed_attribute": { 523 input: structured.Change{ 524 Before: map[string]interface{}{}, 525 After: map[string]interface{}{}, 526 Unknown: map[string]interface{}{ 527 "attribute_one": true, 528 }, 529 }, 530 attributes: map[string]cty.Type{ 531 "attribute_one": cty.String, 532 }, 533 validateDiffs: map[string]renderers.ValidateDiffFunction{ 534 "attribute_one": renderers.ValidateUnknown(nil, plans.Create, false), 535 }, 536 validateAction: plans.Update, 537 validateReplace: false, 538 }, 539 "update_computed_attribute": { 540 input: structured.Change{ 541 Before: map[string]interface{}{ 542 "attribute_one": "old", 543 }, 544 After: map[string]interface{}{}, 545 Unknown: map[string]interface{}{ 546 "attribute_one": true, 547 }, 548 }, 549 attributes: map[string]cty.Type{ 550 "attribute_one": cty.String, 551 }, 552 validateDiffs: map[string]renderers.ValidateDiffFunction{ 553 "attribute_one": renderers.ValidateUnknown( 554 renderers.ValidatePrimitive("old", nil, plans.Delete, false), 555 plans.Update, 556 false), 557 }, 558 validateAction: plans.Update, 559 validateReplace: false, 560 validateSetDiffs: &SetDiff{ 561 Before: SetDiffEntry{ 562 ObjectDiff: map[string]renderers.ValidateDiffFunction{ 563 "attribute_one": renderers.ValidatePrimitive("old", nil, plans.Delete, false), 564 }, 565 Action: plans.Delete, 566 Replace: false, 567 }, 568 After: SetDiffEntry{ 569 ObjectDiff: map[string]renderers.ValidateDiffFunction{ 570 "attribute_one": renderers.ValidateUnknown(nil, plans.Create, false), 571 }, 572 Action: plans.Create, 573 Replace: false, 574 }, 575 }, 576 }, 577 "ignores_unset_fields": { 578 input: structured.Change{ 579 Before: map[string]interface{}{}, 580 After: map[string]interface{}{}, 581 }, 582 attributes: map[string]cty.Type{ 583 "attribute_one": cty.String, 584 }, 585 validateDiffs: map[string]renderers.ValidateDiffFunction{}, 586 validateAction: plans.NoOp, 587 validateReplace: false, 588 }, 589 "update_replace_self": { 590 input: structured.Change{ 591 Before: map[string]interface{}{ 592 "attribute_one": "old", 593 }, 594 After: map[string]interface{}{ 595 "attribute_one": "new", 596 }, 597 ReplacePaths: &attribute_path.PathMatcher{ 598 Paths: [][]interface{}{ 599 {}, 600 }, 601 }, 602 }, 603 attributes: map[string]cty.Type{ 604 "attribute_one": cty.String, 605 }, 606 validateDiffs: map[string]renderers.ValidateDiffFunction{ 607 "attribute_one": renderers.ValidatePrimitive("old", "new", plans.Update, false), 608 }, 609 validateAction: plans.Update, 610 validateReplace: true, 611 validateSetDiffs: &SetDiff{ 612 Before: SetDiffEntry{ 613 ObjectDiff: map[string]renderers.ValidateDiffFunction{ 614 "attribute_one": renderers.ValidatePrimitive("old", nil, plans.Delete, false), 615 }, 616 Action: plans.Delete, 617 Replace: true, 618 }, 619 After: SetDiffEntry{ 620 ObjectDiff: map[string]renderers.ValidateDiffFunction{ 621 "attribute_one": renderers.ValidatePrimitive(nil, "new", plans.Create, false), 622 }, 623 Action: plans.Create, 624 Replace: true, 625 }, 626 }, 627 }, 628 "update_replace_attribute": { 629 input: structured.Change{ 630 Before: map[string]interface{}{ 631 "attribute_one": "old", 632 }, 633 After: map[string]interface{}{ 634 "attribute_one": "new", 635 }, 636 ReplacePaths: &attribute_path.PathMatcher{ 637 Paths: [][]interface{}{ 638 {"attribute_one"}, 639 }, 640 }, 641 }, 642 attributes: map[string]cty.Type{ 643 "attribute_one": cty.String, 644 }, 645 validateDiffs: map[string]renderers.ValidateDiffFunction{ 646 "attribute_one": renderers.ValidatePrimitive("old", "new", plans.Update, true), 647 }, 648 validateAction: plans.Update, 649 validateReplace: false, 650 validateSetDiffs: &SetDiff{ 651 Before: SetDiffEntry{ 652 ObjectDiff: map[string]renderers.ValidateDiffFunction{ 653 "attribute_one": renderers.ValidatePrimitive("old", nil, plans.Delete, true), 654 }, 655 Action: plans.Delete, 656 Replace: false, 657 }, 658 After: SetDiffEntry{ 659 ObjectDiff: map[string]renderers.ValidateDiffFunction{ 660 "attribute_one": renderers.ValidatePrimitive(nil, "new", plans.Create, true), 661 }, 662 Action: plans.Create, 663 Replace: false, 664 }, 665 }, 666 }, 667 "update_includes_relevant_attributes": { 668 input: structured.Change{ 669 Before: map[string]interface{}{ 670 "attribute_one": "old_one", 671 "attribute_two": "old_two", 672 }, 673 After: map[string]interface{}{ 674 "attribute_one": "new_one", 675 "attribute_two": "new_two", 676 }, 677 RelevantAttributes: &attribute_path.PathMatcher{ 678 Paths: [][]interface{}{ 679 {"attribute_one"}, 680 }, 681 }, 682 }, 683 attributes: map[string]cty.Type{ 684 "attribute_one": cty.String, 685 "attribute_two": cty.String, 686 }, 687 validateDiffs: map[string]renderers.ValidateDiffFunction{ 688 "attribute_one": renderers.ValidatePrimitive("old_one", "new_one", plans.Update, false), 689 "attribute_two": renderers.ValidatePrimitive("old_two", "old_two", plans.NoOp, false), 690 }, 691 validateList: renderers.ValidateList([]renderers.ValidateDiffFunction{ 692 renderers.ValidateObject(map[string]renderers.ValidateDiffFunction{ 693 // Lists are a bit special, and in this case is actually 694 // going to ignore the relevant attributes. This is 695 // deliberate. See the comments in list.go for an 696 // explanation. 697 "attribute_one": renderers.ValidatePrimitive("old_one", "new_one", plans.Update, false), 698 "attribute_two": renderers.ValidatePrimitive("old_two", "new_two", plans.Update, false), 699 }, plans.Update, false), 700 }, plans.Update, false), 701 validateAction: plans.Update, 702 validateReplace: false, 703 validateSetDiffs: &SetDiff{ 704 Before: SetDiffEntry{ 705 ObjectDiff: map[string]renderers.ValidateDiffFunction{ 706 "attribute_one": renderers.ValidatePrimitive("old_one", nil, plans.Delete, false), 707 "attribute_two": renderers.ValidatePrimitive("old_two", nil, plans.Delete, false), 708 }, 709 Action: plans.Delete, 710 Replace: false, 711 }, 712 After: SetDiffEntry{ 713 ObjectDiff: map[string]renderers.ValidateDiffFunction{ 714 "attribute_one": renderers.ValidatePrimitive(nil, "new_one", plans.Create, false), 715 "attribute_two": renderers.ValidatePrimitive(nil, "new_two", plans.Create, false), 716 }, 717 Action: plans.Create, 718 Replace: false, 719 }, 720 }, 721 }, 722 } 723 724 for name, tmp := range tcs { 725 tc := tmp 726 727 // Let's set some default values on the input. 728 if tc.input.RelevantAttributes == nil { 729 tc.input.RelevantAttributes = attribute_path.AlwaysMatcher() 730 } 731 if tc.input.ReplacePaths == nil { 732 tc.input.ReplacePaths = &attribute_path.PathMatcher{} 733 } 734 735 collectionDefaultAction := plans.Update 736 if name == "ignores_unset_fields" { 737 // Special case for this test, as it is the only one that doesn't 738 // have the collection types return an update. 739 collectionDefaultAction = plans.NoOp 740 } 741 742 t.Run(name, func(t *testing.T) { 743 t.Run("object", func(t *testing.T) { 744 attribute := &jsonprovider.Attribute{ 745 AttributeType: unmarshalType(t, cty.Object(tc.attributes)), 746 } 747 748 if tc.validateObject != nil { 749 tc.validateObject(t, ComputeDiffForAttribute(tc.input, attribute)) 750 return 751 } 752 753 if tc.validateSingleDiff != nil { 754 tc.validateSingleDiff(t, ComputeDiffForAttribute(tc.input, attribute)) 755 return 756 } 757 758 validate := renderers.ValidateObject(tc.validateDiffs, tc.validateAction, tc.validateReplace) 759 validate(t, ComputeDiffForAttribute(tc.input, attribute)) 760 }) 761 762 t.Run("map", func(t *testing.T) { 763 attribute := &jsonprovider.Attribute{ 764 AttributeType: unmarshalType(t, cty.Map(cty.Object(tc.attributes))), 765 } 766 767 input := wrapChangeInMap(tc.input) 768 769 if tc.validateObject != nil { 770 validate := renderers.ValidateMap(map[string]renderers.ValidateDiffFunction{ 771 "element": tc.validateObject, 772 }, collectionDefaultAction, false) 773 validate(t, ComputeDiffForAttribute(input, attribute)) 774 return 775 } 776 777 if tc.validateSingleDiff != nil { 778 validate := renderers.ValidateMap(map[string]renderers.ValidateDiffFunction{ 779 "element": tc.validateSingleDiff, 780 }, collectionDefaultAction, false) 781 validate(t, ComputeDiffForAttribute(input, attribute)) 782 return 783 } 784 785 validate := renderers.ValidateMap(map[string]renderers.ValidateDiffFunction{ 786 "element": renderers.ValidateObject(tc.validateDiffs, tc.validateAction, tc.validateReplace), 787 }, collectionDefaultAction, false) 788 validate(t, ComputeDiffForAttribute(input, attribute)) 789 }) 790 791 t.Run("list", func(t *testing.T) { 792 attribute := &jsonprovider.Attribute{ 793 AttributeType: unmarshalType(t, cty.List(cty.Object(tc.attributes))), 794 } 795 796 input := wrapChangeInSlice(tc.input) 797 798 if tc.validateList != nil { 799 tc.validateList(t, ComputeDiffForAttribute(input, attribute)) 800 return 801 } 802 803 if tc.validateObject != nil { 804 validate := renderers.ValidateList([]renderers.ValidateDiffFunction{ 805 tc.validateObject, 806 }, collectionDefaultAction, false) 807 validate(t, ComputeDiffForAttribute(input, attribute)) 808 return 809 } 810 811 if tc.validateSingleDiff != nil { 812 validate := renderers.ValidateList([]renderers.ValidateDiffFunction{ 813 tc.validateSingleDiff, 814 }, collectionDefaultAction, false) 815 validate(t, ComputeDiffForAttribute(input, attribute)) 816 return 817 } 818 819 validate := renderers.ValidateList([]renderers.ValidateDiffFunction{ 820 renderers.ValidateObject(tc.validateDiffs, tc.validateAction, tc.validateReplace), 821 }, collectionDefaultAction, false) 822 validate(t, ComputeDiffForAttribute(input, attribute)) 823 }) 824 825 t.Run("set", func(t *testing.T) { 826 attribute := &jsonprovider.Attribute{ 827 AttributeType: unmarshalType(t, cty.Set(cty.Object(tc.attributes))), 828 } 829 830 input := wrapChangeInSlice(tc.input) 831 832 if tc.validateSetDiffs != nil { 833 validate := renderers.ValidateSet(func() []renderers.ValidateDiffFunction { 834 var ret []renderers.ValidateDiffFunction 835 ret = append(ret, tc.validateSetDiffs.Before.Validate(renderers.ValidateObject)) 836 ret = append(ret, tc.validateSetDiffs.After.Validate(renderers.ValidateObject)) 837 return ret 838 }(), collectionDefaultAction, false) 839 validate(t, ComputeDiffForAttribute(input, attribute)) 840 return 841 } 842 843 if tc.validateObject != nil { 844 validate := renderers.ValidateSet([]renderers.ValidateDiffFunction{ 845 tc.validateObject, 846 }, collectionDefaultAction, false) 847 validate(t, ComputeDiffForAttribute(input, attribute)) 848 return 849 } 850 851 if tc.validateSingleDiff != nil { 852 validate := renderers.ValidateSet([]renderers.ValidateDiffFunction{ 853 tc.validateSingleDiff, 854 }, collectionDefaultAction, false) 855 validate(t, ComputeDiffForAttribute(input, attribute)) 856 return 857 } 858 859 validate := renderers.ValidateSet([]renderers.ValidateDiffFunction{ 860 renderers.ValidateObject(tc.validateDiffs, tc.validateAction, tc.validateReplace), 861 }, collectionDefaultAction, false) 862 validate(t, ComputeDiffForAttribute(input, attribute)) 863 }) 864 }) 865 866 t.Run(fmt.Sprintf("nested_%s", name), func(t *testing.T) { 867 t.Run("object", func(t *testing.T) { 868 attribute := &jsonprovider.Attribute{ 869 AttributeNestedType: &jsonprovider.NestedType{ 870 Attributes: func() map[string]*jsonprovider.Attribute { 871 attributes := make(map[string]*jsonprovider.Attribute) 872 for key, attribute := range tc.attributes { 873 attributes[key] = &jsonprovider.Attribute{ 874 AttributeType: unmarshalType(t, attribute), 875 } 876 } 877 return attributes 878 }(), 879 NestingMode: "single", 880 }, 881 } 882 883 if tc.validateNestedObject != nil { 884 tc.validateNestedObject(t, ComputeDiffForAttribute(tc.input, attribute)) 885 return 886 } 887 888 if tc.validateSingleDiff != nil { 889 tc.validateSingleDiff(t, ComputeDiffForAttribute(tc.input, attribute)) 890 return 891 } 892 893 validate := renderers.ValidateNestedObject(tc.validateDiffs, tc.validateAction, tc.validateReplace) 894 validate(t, ComputeDiffForAttribute(tc.input, attribute)) 895 }) 896 897 t.Run("map", func(t *testing.T) { 898 attribute := &jsonprovider.Attribute{ 899 AttributeNestedType: &jsonprovider.NestedType{ 900 Attributes: func() map[string]*jsonprovider.Attribute { 901 attributes := make(map[string]*jsonprovider.Attribute) 902 for key, attribute := range tc.attributes { 903 attributes[key] = &jsonprovider.Attribute{ 904 AttributeType: unmarshalType(t, attribute), 905 } 906 } 907 return attributes 908 }(), 909 NestingMode: "map", 910 }, 911 } 912 913 input := wrapChangeInMap(tc.input) 914 915 if tc.validateNestedObject != nil { 916 validate := renderers.ValidateMap(map[string]renderers.ValidateDiffFunction{ 917 "element": tc.validateNestedObject, 918 }, collectionDefaultAction, false) 919 validate(t, ComputeDiffForAttribute(input, attribute)) 920 return 921 } 922 923 if tc.validateSingleDiff != nil { 924 validate := renderers.ValidateMap(map[string]renderers.ValidateDiffFunction{ 925 "element": tc.validateSingleDiff, 926 }, collectionDefaultAction, false) 927 validate(t, ComputeDiffForAttribute(input, attribute)) 928 return 929 } 930 931 validate := renderers.ValidateMap(map[string]renderers.ValidateDiffFunction{ 932 "element": renderers.ValidateNestedObject(tc.validateDiffs, tc.validateAction, tc.validateReplace), 933 }, collectionDefaultAction, false) 934 validate(t, ComputeDiffForAttribute(input, attribute)) 935 }) 936 937 t.Run("list", func(t *testing.T) { 938 attribute := &jsonprovider.Attribute{ 939 AttributeNestedType: &jsonprovider.NestedType{ 940 Attributes: func() map[string]*jsonprovider.Attribute { 941 attributes := make(map[string]*jsonprovider.Attribute) 942 for key, attribute := range tc.attributes { 943 attributes[key] = &jsonprovider.Attribute{ 944 AttributeType: unmarshalType(t, attribute), 945 } 946 } 947 return attributes 948 }(), 949 NestingMode: "list", 950 }, 951 } 952 953 input := wrapChangeInSlice(tc.input) 954 955 if tc.validateNestedObject != nil { 956 validate := renderers.ValidateNestedList([]renderers.ValidateDiffFunction{ 957 tc.validateNestedObject, 958 }, collectionDefaultAction, false) 959 validate(t, ComputeDiffForAttribute(input, attribute)) 960 return 961 } 962 963 if tc.validateSingleDiff != nil { 964 validate := renderers.ValidateNestedList([]renderers.ValidateDiffFunction{ 965 tc.validateSingleDiff, 966 }, collectionDefaultAction, false) 967 validate(t, ComputeDiffForAttribute(input, attribute)) 968 return 969 } 970 971 validate := renderers.ValidateNestedList([]renderers.ValidateDiffFunction{ 972 renderers.ValidateNestedObject(tc.validateDiffs, tc.validateAction, tc.validateReplace), 973 }, collectionDefaultAction, false) 974 validate(t, ComputeDiffForAttribute(input, attribute)) 975 }) 976 977 t.Run("set", func(t *testing.T) { 978 attribute := &jsonprovider.Attribute{ 979 AttributeNestedType: &jsonprovider.NestedType{ 980 Attributes: func() map[string]*jsonprovider.Attribute { 981 attributes := make(map[string]*jsonprovider.Attribute) 982 for key, attribute := range tc.attributes { 983 attributes[key] = &jsonprovider.Attribute{ 984 AttributeType: unmarshalType(t, attribute), 985 } 986 } 987 return attributes 988 }(), 989 NestingMode: "set", 990 }, 991 } 992 993 input := wrapChangeInSlice(tc.input) 994 995 if tc.validateSetDiffs != nil { 996 validate := renderers.ValidateSet(func() []renderers.ValidateDiffFunction { 997 var ret []renderers.ValidateDiffFunction 998 ret = append(ret, tc.validateSetDiffs.Before.Validate(renderers.ValidateNestedObject)) 999 ret = append(ret, tc.validateSetDiffs.After.Validate(renderers.ValidateNestedObject)) 1000 return ret 1001 }(), collectionDefaultAction, false) 1002 validate(t, ComputeDiffForAttribute(input, attribute)) 1003 return 1004 } 1005 1006 if tc.validateNestedObject != nil { 1007 validate := renderers.ValidateSet([]renderers.ValidateDiffFunction{ 1008 tc.validateNestedObject, 1009 }, collectionDefaultAction, false) 1010 validate(t, ComputeDiffForAttribute(input, attribute)) 1011 return 1012 } 1013 1014 if tc.validateSingleDiff != nil { 1015 validate := renderers.ValidateSet([]renderers.ValidateDiffFunction{ 1016 tc.validateSingleDiff, 1017 }, collectionDefaultAction, false) 1018 validate(t, ComputeDiffForAttribute(input, attribute)) 1019 return 1020 } 1021 1022 validate := renderers.ValidateSet([]renderers.ValidateDiffFunction{ 1023 renderers.ValidateNestedObject(tc.validateDiffs, tc.validateAction, tc.validateReplace), 1024 }, collectionDefaultAction, false) 1025 validate(t, ComputeDiffForAttribute(input, attribute)) 1026 }) 1027 }) 1028 } 1029 } 1030 1031 func TestValue_BlockAttributesAndNestedBlocks(t *testing.T) { 1032 // This function tests manipulating simple attributes and blocks within 1033 // blocks. It automatically tests these operations within the contexts of 1034 // different block types. 1035 1036 tcs := map[string]struct { 1037 before interface{} 1038 after interface{} 1039 block *jsonprovider.Block 1040 validate renderers.ValidateDiffFunction 1041 validateSet []renderers.ValidateDiffFunction 1042 }{ 1043 "create_attribute": { 1044 before: map[string]interface{}{}, 1045 after: map[string]interface{}{ 1046 "attribute_one": "new", 1047 }, 1048 block: &jsonprovider.Block{ 1049 Attributes: map[string]*jsonprovider.Attribute{ 1050 "attribute_one": { 1051 AttributeType: unmarshalType(t, cty.String), 1052 }, 1053 }, 1054 }, 1055 validate: renderers.ValidateBlock(map[string]renderers.ValidateDiffFunction{ 1056 "attribute_one": renderers.ValidatePrimitive(nil, "new", plans.Create, false), 1057 }, nil, nil, nil, nil, plans.Update, false), 1058 validateSet: []renderers.ValidateDiffFunction{ 1059 renderers.ValidateBlock(nil, nil, nil, nil, nil, plans.Delete, false), 1060 renderers.ValidateBlock(map[string]renderers.ValidateDiffFunction{ 1061 "attribute_one": renderers.ValidatePrimitive(nil, "new", plans.Create, false), 1062 }, nil, nil, nil, nil, plans.Create, false), 1063 }, 1064 }, 1065 "update_attribute": { 1066 before: map[string]interface{}{ 1067 "attribute_one": "old", 1068 }, 1069 after: map[string]interface{}{ 1070 "attribute_one": "new", 1071 }, 1072 block: &jsonprovider.Block{ 1073 Attributes: map[string]*jsonprovider.Attribute{ 1074 "attribute_one": { 1075 AttributeType: unmarshalType(t, cty.String), 1076 }, 1077 }, 1078 }, 1079 validate: renderers.ValidateBlock(map[string]renderers.ValidateDiffFunction{ 1080 "attribute_one": renderers.ValidatePrimitive("old", "new", plans.Update, false), 1081 }, nil, nil, nil, nil, plans.Update, false), 1082 validateSet: []renderers.ValidateDiffFunction{ 1083 renderers.ValidateBlock(map[string]renderers.ValidateDiffFunction{ 1084 "attribute_one": renderers.ValidatePrimitive("old", nil, plans.Delete, false), 1085 }, nil, nil, nil, nil, plans.Delete, false), 1086 renderers.ValidateBlock(map[string]renderers.ValidateDiffFunction{ 1087 "attribute_one": renderers.ValidatePrimitive(nil, "new", plans.Create, false), 1088 }, nil, nil, nil, nil, plans.Create, false), 1089 }, 1090 }, 1091 "delete_attribute": { 1092 before: map[string]interface{}{ 1093 "attribute_one": "old", 1094 }, 1095 after: map[string]interface{}{}, 1096 block: &jsonprovider.Block{ 1097 Attributes: map[string]*jsonprovider.Attribute{ 1098 "attribute_one": { 1099 AttributeType: unmarshalType(t, cty.String), 1100 }, 1101 }, 1102 }, 1103 validate: renderers.ValidateBlock(map[string]renderers.ValidateDiffFunction{ 1104 "attribute_one": renderers.ValidatePrimitive("old", nil, plans.Delete, false), 1105 }, nil, nil, nil, nil, plans.Update, false), 1106 validateSet: []renderers.ValidateDiffFunction{ 1107 renderers.ValidateBlock(map[string]renderers.ValidateDiffFunction{ 1108 "attribute_one": renderers.ValidatePrimitive("old", nil, plans.Delete, false), 1109 }, nil, nil, nil, nil, plans.Delete, false), 1110 renderers.ValidateBlock(nil, nil, nil, nil, nil, plans.Create, false), 1111 }, 1112 }, 1113 "create_block": { 1114 before: map[string]interface{}{}, 1115 after: map[string]interface{}{ 1116 "block_one": map[string]interface{}{ 1117 "attribute_one": "new", 1118 }, 1119 }, 1120 block: &jsonprovider.Block{ 1121 BlockTypes: map[string]*jsonprovider.BlockType{ 1122 "block_one": { 1123 Block: &jsonprovider.Block{ 1124 Attributes: map[string]*jsonprovider.Attribute{ 1125 "attribute_one": { 1126 AttributeType: unmarshalType(t, cty.String), 1127 }, 1128 }, 1129 }, 1130 NestingMode: "single", 1131 }, 1132 }, 1133 }, 1134 validate: renderers.ValidateBlock(nil, map[string]renderers.ValidateDiffFunction{ 1135 "block_one": renderers.ValidateBlock(map[string]renderers.ValidateDiffFunction{ 1136 "attribute_one": renderers.ValidatePrimitive(nil, "new", plans.Create, false), 1137 }, nil, nil, nil, nil, plans.Create, false), 1138 }, nil, nil, nil, plans.Update, false), 1139 validateSet: []renderers.ValidateDiffFunction{ 1140 renderers.ValidateBlock(nil, nil, nil, nil, nil, plans.Delete, false), 1141 renderers.ValidateBlock(nil, map[string]renderers.ValidateDiffFunction{ 1142 "block_one": renderers.ValidateBlock(map[string]renderers.ValidateDiffFunction{ 1143 "attribute_one": renderers.ValidatePrimitive(nil, "new", plans.Create, false), 1144 }, nil, nil, nil, nil, plans.Create, false), 1145 }, nil, nil, nil, plans.Create, false), 1146 }, 1147 }, 1148 "update_block": { 1149 before: map[string]interface{}{ 1150 "block_one": map[string]interface{}{ 1151 "attribute_one": "old", 1152 }, 1153 }, 1154 after: map[string]interface{}{ 1155 "block_one": map[string]interface{}{ 1156 "attribute_one": "new", 1157 }, 1158 }, 1159 block: &jsonprovider.Block{ 1160 BlockTypes: map[string]*jsonprovider.BlockType{ 1161 "block_one": { 1162 Block: &jsonprovider.Block{ 1163 Attributes: map[string]*jsonprovider.Attribute{ 1164 "attribute_one": { 1165 AttributeType: unmarshalType(t, cty.String), 1166 }, 1167 }, 1168 }, 1169 NestingMode: "single", 1170 }, 1171 }, 1172 }, 1173 validate: renderers.ValidateBlock(nil, map[string]renderers.ValidateDiffFunction{ 1174 "block_one": renderers.ValidateBlock(map[string]renderers.ValidateDiffFunction{ 1175 "attribute_one": renderers.ValidatePrimitive("old", "new", plans.Update, false), 1176 }, nil, nil, nil, nil, plans.Update, false), 1177 }, nil, nil, nil, plans.Update, false), 1178 validateSet: []renderers.ValidateDiffFunction{ 1179 renderers.ValidateBlock(nil, map[string]renderers.ValidateDiffFunction{ 1180 "block_one": renderers.ValidateBlock(map[string]renderers.ValidateDiffFunction{ 1181 "attribute_one": renderers.ValidatePrimitive("old", nil, plans.Delete, false), 1182 }, nil, nil, nil, nil, plans.Delete, false), 1183 }, nil, nil, nil, plans.Delete, false), 1184 renderers.ValidateBlock(nil, map[string]renderers.ValidateDiffFunction{ 1185 "block_one": renderers.ValidateBlock(map[string]renderers.ValidateDiffFunction{ 1186 "attribute_one": renderers.ValidatePrimitive(nil, "new", plans.Create, false), 1187 }, nil, nil, nil, nil, plans.Create, false), 1188 }, nil, nil, nil, plans.Create, false), 1189 }, 1190 }, 1191 "delete_block": { 1192 before: map[string]interface{}{ 1193 "block_one": map[string]interface{}{ 1194 "attribute_one": "old", 1195 }, 1196 }, 1197 after: map[string]interface{}{}, 1198 block: &jsonprovider.Block{ 1199 BlockTypes: map[string]*jsonprovider.BlockType{ 1200 "block_one": { 1201 Block: &jsonprovider.Block{ 1202 Attributes: map[string]*jsonprovider.Attribute{ 1203 "attribute_one": { 1204 AttributeType: unmarshalType(t, cty.String), 1205 }, 1206 }, 1207 }, 1208 NestingMode: "single", 1209 }, 1210 }, 1211 }, 1212 validate: renderers.ValidateBlock(nil, map[string]renderers.ValidateDiffFunction{ 1213 "block_one": renderers.ValidateBlock(map[string]renderers.ValidateDiffFunction{ 1214 "attribute_one": renderers.ValidatePrimitive("old", nil, plans.Delete, false), 1215 }, nil, nil, nil, nil, plans.Delete, false), 1216 }, nil, nil, nil, plans.Update, false), 1217 validateSet: []renderers.ValidateDiffFunction{ 1218 renderers.ValidateBlock(nil, map[string]renderers.ValidateDiffFunction{ 1219 "block_one": renderers.ValidateBlock(map[string]renderers.ValidateDiffFunction{ 1220 "attribute_one": renderers.ValidatePrimitive("old", nil, plans.Delete, false), 1221 }, nil, nil, nil, nil, plans.Delete, false), 1222 }, nil, nil, nil, plans.Delete, false), 1223 renderers.ValidateBlock(nil, nil, nil, nil, nil, plans.Create, false), 1224 }, 1225 }, 1226 } 1227 for name, tmp := range tcs { 1228 tc := tmp 1229 1230 t.Run(name, func(t *testing.T) { 1231 t.Run("single", func(t *testing.T) { 1232 input := structured.Change{ 1233 Before: map[string]interface{}{ 1234 "block_type": tc.before, 1235 }, 1236 After: map[string]interface{}{ 1237 "block_type": tc.after, 1238 }, 1239 ReplacePaths: &attribute_path.PathMatcher{}, 1240 RelevantAttributes: attribute_path.AlwaysMatcher(), 1241 } 1242 1243 block := &jsonprovider.Block{ 1244 BlockTypes: map[string]*jsonprovider.BlockType{ 1245 "block_type": { 1246 Block: tc.block, 1247 NestingMode: "single", 1248 }, 1249 }, 1250 } 1251 1252 validate := renderers.ValidateBlock(nil, map[string]renderers.ValidateDiffFunction{ 1253 "block_type": tc.validate, 1254 }, nil, nil, nil, plans.Update, false) 1255 validate(t, ComputeDiffForBlock(input, block)) 1256 }) 1257 t.Run("map", func(t *testing.T) { 1258 input := structured.Change{ 1259 Before: map[string]interface{}{ 1260 "block_type": map[string]interface{}{ 1261 "one": tc.before, 1262 }, 1263 }, 1264 After: map[string]interface{}{ 1265 "block_type": map[string]interface{}{ 1266 "one": tc.after, 1267 }, 1268 }, 1269 ReplacePaths: &attribute_path.PathMatcher{}, 1270 RelevantAttributes: attribute_path.AlwaysMatcher(), 1271 } 1272 1273 block := &jsonprovider.Block{ 1274 BlockTypes: map[string]*jsonprovider.BlockType{ 1275 "block_type": { 1276 Block: tc.block, 1277 NestingMode: "map", 1278 }, 1279 }, 1280 } 1281 1282 validate := renderers.ValidateBlock(nil, nil, nil, map[string]map[string]renderers.ValidateDiffFunction{ 1283 "block_type": { 1284 "one": tc.validate, 1285 }, 1286 }, nil, plans.Update, false) 1287 validate(t, ComputeDiffForBlock(input, block)) 1288 }) 1289 t.Run("list", func(t *testing.T) { 1290 input := structured.Change{ 1291 Before: map[string]interface{}{ 1292 "block_type": []interface{}{ 1293 tc.before, 1294 }, 1295 }, 1296 After: map[string]interface{}{ 1297 "block_type": []interface{}{ 1298 tc.after, 1299 }, 1300 }, 1301 ReplacePaths: &attribute_path.PathMatcher{}, 1302 RelevantAttributes: attribute_path.AlwaysMatcher(), 1303 } 1304 1305 block := &jsonprovider.Block{ 1306 BlockTypes: map[string]*jsonprovider.BlockType{ 1307 "block_type": { 1308 Block: tc.block, 1309 NestingMode: "list", 1310 }, 1311 }, 1312 } 1313 1314 validate := renderers.ValidateBlock(nil, nil, map[string][]renderers.ValidateDiffFunction{ 1315 "block_type": { 1316 tc.validate, 1317 }, 1318 }, nil, nil, plans.Update, false) 1319 validate(t, ComputeDiffForBlock(input, block)) 1320 }) 1321 t.Run("set", func(t *testing.T) { 1322 input := structured.Change{ 1323 Before: map[string]interface{}{ 1324 "block_type": []interface{}{ 1325 tc.before, 1326 }, 1327 }, 1328 After: map[string]interface{}{ 1329 "block_type": []interface{}{ 1330 tc.after, 1331 }, 1332 }, 1333 ReplacePaths: &attribute_path.PathMatcher{}, 1334 RelevantAttributes: attribute_path.AlwaysMatcher(), 1335 } 1336 1337 block := &jsonprovider.Block{ 1338 BlockTypes: map[string]*jsonprovider.BlockType{ 1339 "block_type": { 1340 Block: tc.block, 1341 NestingMode: "set", 1342 }, 1343 }, 1344 } 1345 1346 validate := renderers.ValidateBlock(nil, nil, nil, nil, map[string][]renderers.ValidateDiffFunction{ 1347 "block_type": func() []renderers.ValidateDiffFunction { 1348 if tc.validateSet != nil { 1349 return tc.validateSet 1350 } 1351 return []renderers.ValidateDiffFunction{tc.validate} 1352 }(), 1353 }, plans.Update, false) 1354 validate(t, ComputeDiffForBlock(input, block)) 1355 }) 1356 }) 1357 } 1358 } 1359 1360 func TestValue_Outputs(t *testing.T) { 1361 tcs := map[string]struct { 1362 input structured.Change 1363 validateDiff renderers.ValidateDiffFunction 1364 }{ 1365 "primitive_create": { 1366 input: structured.Change{ 1367 Before: nil, 1368 After: "new", 1369 }, 1370 validateDiff: renderers.ValidatePrimitive(nil, "new", plans.Create, false), 1371 }, 1372 "object_create": { 1373 input: structured.Change{ 1374 Before: nil, 1375 After: map[string]interface{}{ 1376 "element_one": "new_one", 1377 "element_two": "new_two", 1378 }, 1379 }, 1380 validateDiff: renderers.ValidateObject(map[string]renderers.ValidateDiffFunction{ 1381 "element_one": renderers.ValidatePrimitive(nil, "new_one", plans.Create, false), 1382 "element_two": renderers.ValidatePrimitive(nil, "new_two", plans.Create, false), 1383 }, plans.Create, false), 1384 }, 1385 "list_create": { 1386 input: structured.Change{ 1387 Before: nil, 1388 After: []interface{}{ 1389 "new_one", 1390 "new_two", 1391 }, 1392 }, 1393 validateDiff: renderers.ValidateList([]renderers.ValidateDiffFunction{ 1394 renderers.ValidatePrimitive(nil, "new_one", plans.Create, false), 1395 renderers.ValidatePrimitive(nil, "new_two", plans.Create, false), 1396 }, plans.Create, false), 1397 }, 1398 "primitive_update": { 1399 input: structured.Change{ 1400 Before: "old", 1401 After: "new", 1402 }, 1403 validateDiff: renderers.ValidatePrimitive("old", "new", plans.Update, false), 1404 }, 1405 "object_update": { 1406 input: structured.Change{ 1407 Before: map[string]interface{}{ 1408 "element_one": "old_one", 1409 "element_two": "old_two", 1410 }, 1411 After: map[string]interface{}{ 1412 "element_one": "new_one", 1413 "element_two": "new_two", 1414 }, 1415 }, 1416 validateDiff: renderers.ValidateObject(map[string]renderers.ValidateDiffFunction{ 1417 "element_one": renderers.ValidatePrimitive("old_one", "new_one", plans.Update, false), 1418 "element_two": renderers.ValidatePrimitive("old_two", "new_two", plans.Update, false), 1419 }, plans.Update, false), 1420 }, 1421 "list_update": { 1422 input: structured.Change{ 1423 Before: []interface{}{ 1424 "old_one", 1425 "old_two", 1426 }, 1427 After: []interface{}{ 1428 "new_one", 1429 "new_two", 1430 }, 1431 }, 1432 validateDiff: renderers.ValidateList([]renderers.ValidateDiffFunction{ 1433 renderers.ValidatePrimitive("old_one", nil, plans.Delete, false), 1434 renderers.ValidatePrimitive("old_two", nil, plans.Delete, false), 1435 renderers.ValidatePrimitive(nil, "new_one", plans.Create, false), 1436 renderers.ValidatePrimitive(nil, "new_two", plans.Create, false), 1437 }, plans.Update, false), 1438 }, 1439 "primitive_delete": { 1440 input: structured.Change{ 1441 Before: "old", 1442 After: nil, 1443 }, 1444 validateDiff: renderers.ValidatePrimitive("old", nil, plans.Delete, false), 1445 }, 1446 "object_delete": { 1447 input: structured.Change{ 1448 Before: map[string]interface{}{ 1449 "element_one": "old_one", 1450 "element_two": "old_two", 1451 }, 1452 After: nil, 1453 }, 1454 validateDiff: renderers.ValidateObject(map[string]renderers.ValidateDiffFunction{ 1455 "element_one": renderers.ValidatePrimitive("old_one", nil, plans.Delete, false), 1456 "element_two": renderers.ValidatePrimitive("old_two", nil, plans.Delete, false), 1457 }, plans.Delete, false), 1458 }, 1459 "list_delete": { 1460 input: structured.Change{ 1461 Before: []interface{}{ 1462 "old_one", 1463 "old_two", 1464 }, 1465 After: nil, 1466 }, 1467 validateDiff: renderers.ValidateList([]renderers.ValidateDiffFunction{ 1468 renderers.ValidatePrimitive("old_one", nil, plans.Delete, false), 1469 renderers.ValidatePrimitive("old_two", nil, plans.Delete, false), 1470 }, plans.Delete, false), 1471 }, 1472 "primitive_to_list": { 1473 input: structured.Change{ 1474 Before: "old", 1475 After: []interface{}{ 1476 "new_one", 1477 "new_two", 1478 }, 1479 }, 1480 validateDiff: renderers.ValidateTypeChange( 1481 renderers.ValidatePrimitive("old", nil, plans.Delete, false), 1482 renderers.ValidateList([]renderers.ValidateDiffFunction{ 1483 renderers.ValidatePrimitive(nil, "new_one", plans.Create, false), 1484 renderers.ValidatePrimitive(nil, "new_two", plans.Create, false), 1485 }, plans.Create, false), plans.Update, false), 1486 }, 1487 "primitive_to_object": { 1488 input: structured.Change{ 1489 Before: "old", 1490 After: map[string]interface{}{ 1491 "element_one": "new_one", 1492 "element_two": "new_two", 1493 }, 1494 }, 1495 validateDiff: renderers.ValidateTypeChange( 1496 renderers.ValidatePrimitive("old", nil, plans.Delete, false), 1497 renderers.ValidateObject(map[string]renderers.ValidateDiffFunction{ 1498 "element_one": renderers.ValidatePrimitive(nil, "new_one", plans.Create, false), 1499 "element_two": renderers.ValidatePrimitive(nil, "new_two", plans.Create, false), 1500 }, plans.Create, false), plans.Update, false), 1501 }, 1502 "list_to_primitive": { 1503 input: structured.Change{ 1504 Before: []interface{}{ 1505 "old_one", 1506 "old_two", 1507 }, 1508 After: "new", 1509 }, 1510 validateDiff: renderers.ValidateTypeChange( 1511 renderers.ValidateList([]renderers.ValidateDiffFunction{ 1512 renderers.ValidatePrimitive("old_one", nil, plans.Delete, false), 1513 renderers.ValidatePrimitive("old_two", nil, plans.Delete, false), 1514 }, plans.Delete, false), 1515 renderers.ValidatePrimitive(nil, "new", plans.Create, false), 1516 plans.Update, false), 1517 }, 1518 "list_to_object": { 1519 input: structured.Change{ 1520 Before: []interface{}{ 1521 "old_one", 1522 "old_two", 1523 }, 1524 After: map[string]interface{}{ 1525 "element_one": "new_one", 1526 "element_two": "new_two", 1527 }, 1528 }, 1529 validateDiff: renderers.ValidateTypeChange( 1530 renderers.ValidateList([]renderers.ValidateDiffFunction{ 1531 renderers.ValidatePrimitive("old_one", nil, plans.Delete, false), 1532 renderers.ValidatePrimitive("old_two", nil, plans.Delete, false), 1533 }, plans.Delete, false), 1534 renderers.ValidateObject(map[string]renderers.ValidateDiffFunction{ 1535 "element_one": renderers.ValidatePrimitive(nil, "new_one", plans.Create, false), 1536 "element_two": renderers.ValidatePrimitive(nil, "new_two", plans.Create, false), 1537 }, plans.Create, false), plans.Update, false), 1538 }, 1539 "object_to_primitive": { 1540 input: structured.Change{ 1541 Before: map[string]interface{}{ 1542 "element_one": "old_one", 1543 "element_two": "old_two", 1544 }, 1545 After: "new", 1546 }, 1547 validateDiff: renderers.ValidateTypeChange( 1548 renderers.ValidateObject(map[string]renderers.ValidateDiffFunction{ 1549 "element_one": renderers.ValidatePrimitive("old_one", nil, plans.Delete, false), 1550 "element_two": renderers.ValidatePrimitive("old_two", nil, plans.Delete, false), 1551 }, plans.Delete, false), 1552 renderers.ValidatePrimitive(nil, "new", plans.Create, false), 1553 plans.Update, false), 1554 }, 1555 "object_to_list": { 1556 input: structured.Change{ 1557 Before: map[string]interface{}{ 1558 "element_one": "old_one", 1559 "element_two": "old_two", 1560 }, 1561 After: []interface{}{ 1562 "new_one", 1563 "new_two", 1564 }, 1565 }, 1566 validateDiff: renderers.ValidateTypeChange( 1567 renderers.ValidateObject(map[string]renderers.ValidateDiffFunction{ 1568 "element_one": renderers.ValidatePrimitive("old_one", nil, plans.Delete, false), 1569 "element_two": renderers.ValidatePrimitive("old_two", nil, plans.Delete, false), 1570 }, plans.Delete, false), 1571 renderers.ValidateList([]renderers.ValidateDiffFunction{ 1572 renderers.ValidatePrimitive(nil, "new_one", plans.Create, false), 1573 renderers.ValidatePrimitive(nil, "new_two", plans.Create, false), 1574 }, plans.Create, false), plans.Update, false), 1575 }, 1576 } 1577 1578 for name, tc := range tcs { 1579 1580 // Let's set some default values on the input. 1581 if tc.input.RelevantAttributes == nil { 1582 tc.input.RelevantAttributes = attribute_path.AlwaysMatcher() 1583 } 1584 if tc.input.ReplacePaths == nil { 1585 tc.input.ReplacePaths = &attribute_path.PathMatcher{} 1586 } 1587 1588 t.Run(name, func(t *testing.T) { 1589 tc.validateDiff(t, ComputeDiffForOutput(tc.input)) 1590 }) 1591 } 1592 } 1593 1594 func TestValue_PrimitiveAttributes(t *testing.T) { 1595 // This function tests manipulating primitives: creating, deleting and 1596 // updating. It also automatically tests these operations within the 1597 // contexts of collections. 1598 1599 tcs := map[string]struct { 1600 input structured.Change 1601 attribute cty.Type 1602 validateDiff renderers.ValidateDiffFunction 1603 validateSliceDiffs []renderers.ValidateDiffFunction // Lists are special in some cases. 1604 }{ 1605 "primitive_create": { 1606 input: structured.Change{ 1607 After: "new", 1608 }, 1609 attribute: cty.String, 1610 validateDiff: renderers.ValidatePrimitive(nil, "new", plans.Create, false), 1611 }, 1612 "primitive_delete": { 1613 input: structured.Change{ 1614 Before: "old", 1615 }, 1616 attribute: cty.String, 1617 validateDiff: renderers.ValidatePrimitive("old", nil, plans.Delete, false), 1618 }, 1619 "primitive_update": { 1620 input: structured.Change{ 1621 Before: "old", 1622 After: "new", 1623 }, 1624 attribute: cty.String, 1625 validateDiff: renderers.ValidatePrimitive("old", "new", plans.Update, false), 1626 validateSliceDiffs: []renderers.ValidateDiffFunction{ 1627 renderers.ValidatePrimitive("old", nil, plans.Delete, false), 1628 renderers.ValidatePrimitive(nil, "new", plans.Create, false), 1629 }, 1630 }, 1631 "primitive_set_explicit_null": { 1632 input: structured.Change{ 1633 Before: "old", 1634 After: nil, 1635 AfterExplicit: true, 1636 }, 1637 attribute: cty.String, 1638 validateDiff: renderers.ValidatePrimitive("old", nil, plans.Update, false), 1639 validateSliceDiffs: []renderers.ValidateDiffFunction{ 1640 renderers.ValidatePrimitive("old", nil, plans.Delete, false), 1641 renderers.ValidatePrimitive(nil, nil, plans.Create, false), 1642 }, 1643 }, 1644 "primitive_unset_explicit_null": { 1645 input: structured.Change{ 1646 BeforeExplicit: true, 1647 Before: nil, 1648 After: "new", 1649 }, 1650 attribute: cty.String, 1651 validateDiff: renderers.ValidatePrimitive(nil, "new", plans.Update, false), 1652 validateSliceDiffs: []renderers.ValidateDiffFunction{ 1653 renderers.ValidatePrimitive(nil, nil, plans.Delete, false), 1654 renderers.ValidatePrimitive(nil, "new", plans.Create, false), 1655 }, 1656 }, 1657 "primitive_create_sensitive": { 1658 input: structured.Change{ 1659 Before: nil, 1660 After: "new", 1661 AfterSensitive: true, 1662 }, 1663 attribute: cty.String, 1664 validateDiff: renderers.ValidateSensitive(renderers.ValidatePrimitive(nil, "new", plans.Create, false), false, true, plans.Create, false), 1665 }, 1666 "primitive_delete_sensitive": { 1667 input: structured.Change{ 1668 Before: "old", 1669 BeforeSensitive: true, 1670 After: nil, 1671 }, 1672 attribute: cty.String, 1673 validateDiff: renderers.ValidateSensitive(renderers.ValidatePrimitive("old", nil, plans.Delete, false), true, false, plans.Delete, false), 1674 }, 1675 "primitive_update_sensitive": { 1676 input: structured.Change{ 1677 Before: "old", 1678 BeforeSensitive: true, 1679 After: "new", 1680 AfterSensitive: true, 1681 }, 1682 attribute: cty.String, 1683 validateDiff: renderers.ValidateSensitive(renderers.ValidatePrimitive("old", "new", plans.Update, false), true, true, plans.Update, false), 1684 validateSliceDiffs: []renderers.ValidateDiffFunction{ 1685 renderers.ValidateSensitive(renderers.ValidatePrimitive("old", nil, plans.Delete, false), true, false, plans.Delete, false), 1686 renderers.ValidateSensitive(renderers.ValidatePrimitive(nil, "new", plans.Create, false), false, true, plans.Create, false), 1687 }, 1688 }, 1689 "primitive_create_computed": { 1690 input: structured.Change{ 1691 Before: nil, 1692 After: nil, 1693 Unknown: true, 1694 }, 1695 attribute: cty.String, 1696 validateDiff: renderers.ValidateUnknown(nil, plans.Create, false), 1697 }, 1698 "primitive_update_computed": { 1699 input: structured.Change{ 1700 Before: "old", 1701 After: nil, 1702 Unknown: true, 1703 }, 1704 attribute: cty.String, 1705 validateDiff: renderers.ValidateUnknown(renderers.ValidatePrimitive("old", nil, plans.Delete, false), plans.Update, false), 1706 validateSliceDiffs: []renderers.ValidateDiffFunction{ 1707 renderers.ValidatePrimitive("old", nil, plans.Delete, false), 1708 renderers.ValidateUnknown(nil, plans.Create, false), 1709 }, 1710 }, 1711 "primitive_update_replace": { 1712 input: structured.Change{ 1713 Before: "old", 1714 After: "new", 1715 ReplacePaths: &attribute_path.PathMatcher{ 1716 Paths: [][]interface{}{ 1717 {}, // An empty path suggests replace should be true. 1718 }, 1719 }, 1720 }, 1721 attribute: cty.String, 1722 validateDiff: renderers.ValidatePrimitive("old", "new", plans.Update, true), 1723 validateSliceDiffs: []renderers.ValidateDiffFunction{ 1724 renderers.ValidatePrimitive("old", nil, plans.Delete, true), 1725 renderers.ValidatePrimitive(nil, "new", plans.Create, true), 1726 }, 1727 }, 1728 "noop": { 1729 input: structured.Change{ 1730 Before: "old", 1731 After: "old", 1732 }, 1733 attribute: cty.String, 1734 validateDiff: renderers.ValidatePrimitive("old", "old", plans.NoOp, false), 1735 }, 1736 "dynamic": { 1737 input: structured.Change{ 1738 Before: "old", 1739 After: "new", 1740 }, 1741 attribute: cty.DynamicPseudoType, 1742 validateDiff: renderers.ValidatePrimitive("old", "new", plans.Update, false), 1743 validateSliceDiffs: []renderers.ValidateDiffFunction{ 1744 renderers.ValidatePrimitive("old", nil, plans.Delete, false), 1745 renderers.ValidatePrimitive(nil, "new", plans.Create, false), 1746 }, 1747 }, 1748 "dynamic_type_change": { 1749 input: structured.Change{ 1750 Before: "old", 1751 After: json.Number("4"), 1752 }, 1753 attribute: cty.DynamicPseudoType, 1754 validateDiff: renderers.ValidateTypeChange( 1755 renderers.ValidatePrimitive("old", nil, plans.Delete, false), 1756 renderers.ValidatePrimitive(nil, json.Number("4"), plans.Create, false), 1757 plans.Update, false), 1758 validateSliceDiffs: []renderers.ValidateDiffFunction{ 1759 renderers.ValidatePrimitive("old", nil, plans.Delete, false), 1760 renderers.ValidatePrimitive(nil, json.Number("4"), plans.Create, false), 1761 }, 1762 }, 1763 } 1764 for name, tmp := range tcs { 1765 tc := tmp 1766 1767 // Let's set some default values on the input. 1768 if tc.input.RelevantAttributes == nil { 1769 tc.input.RelevantAttributes = attribute_path.AlwaysMatcher() 1770 } 1771 if tc.input.ReplacePaths == nil { 1772 tc.input.ReplacePaths = &attribute_path.PathMatcher{} 1773 } 1774 1775 defaultCollectionsAction := plans.Update 1776 if name == "noop" { 1777 defaultCollectionsAction = plans.NoOp 1778 } 1779 1780 t.Run(name, func(t *testing.T) { 1781 t.Run("direct", func(t *testing.T) { 1782 tc.validateDiff(t, ComputeDiffForAttribute(tc.input, &jsonprovider.Attribute{ 1783 AttributeType: unmarshalType(t, tc.attribute), 1784 })) 1785 }) 1786 1787 t.Run("map", func(t *testing.T) { 1788 input := wrapChangeInMap(tc.input) 1789 attribute := &jsonprovider.Attribute{ 1790 AttributeType: unmarshalType(t, cty.Map(tc.attribute)), 1791 } 1792 1793 validate := renderers.ValidateMap(map[string]renderers.ValidateDiffFunction{ 1794 "element": tc.validateDiff, 1795 }, defaultCollectionsAction, false) 1796 validate(t, ComputeDiffForAttribute(input, attribute)) 1797 }) 1798 1799 t.Run("list", func(t *testing.T) { 1800 input := wrapChangeInSlice(tc.input) 1801 attribute := &jsonprovider.Attribute{ 1802 AttributeType: unmarshalType(t, cty.List(tc.attribute)), 1803 } 1804 1805 if tc.validateSliceDiffs != nil { 1806 validate := renderers.ValidateList(tc.validateSliceDiffs, defaultCollectionsAction, false) 1807 validate(t, ComputeDiffForAttribute(input, attribute)) 1808 return 1809 } 1810 1811 validate := renderers.ValidateList([]renderers.ValidateDiffFunction{ 1812 tc.validateDiff, 1813 }, defaultCollectionsAction, false) 1814 validate(t, ComputeDiffForAttribute(input, attribute)) 1815 }) 1816 1817 t.Run("set", func(t *testing.T) { 1818 input := wrapChangeInSlice(tc.input) 1819 attribute := &jsonprovider.Attribute{ 1820 AttributeType: unmarshalType(t, cty.Set(tc.attribute)), 1821 } 1822 1823 if tc.validateSliceDiffs != nil { 1824 validate := renderers.ValidateSet(tc.validateSliceDiffs, defaultCollectionsAction, false) 1825 validate(t, ComputeDiffForAttribute(input, attribute)) 1826 return 1827 } 1828 1829 validate := renderers.ValidateSet([]renderers.ValidateDiffFunction{ 1830 tc.validateDiff, 1831 }, defaultCollectionsAction, false) 1832 validate(t, ComputeDiffForAttribute(input, attribute)) 1833 }) 1834 }) 1835 } 1836 } 1837 1838 func TestValue_CollectionAttributes(t *testing.T) { 1839 // This function tests creating and deleting collections. Note, it does not 1840 // generally cover editing collections except in special cases as editing 1841 // collections is handled automatically by other functions. 1842 tcs := map[string]struct { 1843 input structured.Change 1844 attribute *jsonprovider.Attribute 1845 validateDiff renderers.ValidateDiffFunction 1846 }{ 1847 "map_create_empty": { 1848 input: structured.Change{ 1849 Before: nil, 1850 After: map[string]interface{}{}, 1851 }, 1852 attribute: &jsonprovider.Attribute{ 1853 AttributeType: unmarshalType(t, cty.Map(cty.String)), 1854 }, 1855 validateDiff: renderers.ValidateMap(nil, plans.Create, false), 1856 }, 1857 "map_create_populated": { 1858 input: structured.Change{ 1859 Before: nil, 1860 After: map[string]interface{}{ 1861 "element_one": "one", 1862 "element_two": "two", 1863 }, 1864 }, 1865 attribute: &jsonprovider.Attribute{ 1866 AttributeType: unmarshalType(t, cty.Map(cty.String)), 1867 }, 1868 validateDiff: renderers.ValidateMap(map[string]renderers.ValidateDiffFunction{ 1869 "element_one": renderers.ValidatePrimitive(nil, "one", plans.Create, false), 1870 "element_two": renderers.ValidatePrimitive(nil, "two", plans.Create, false), 1871 }, plans.Create, false), 1872 }, 1873 "map_delete_empty": { 1874 input: structured.Change{ 1875 Before: map[string]interface{}{}, 1876 After: nil, 1877 }, 1878 attribute: &jsonprovider.Attribute{ 1879 AttributeType: unmarshalType(t, cty.Map(cty.String)), 1880 }, 1881 validateDiff: renderers.ValidateMap(nil, plans.Delete, false), 1882 }, 1883 "map_delete_populated": { 1884 input: structured.Change{ 1885 Before: map[string]interface{}{ 1886 "element_one": "one", 1887 "element_two": "two", 1888 }, 1889 After: nil, 1890 }, 1891 attribute: &jsonprovider.Attribute{ 1892 AttributeType: unmarshalType(t, cty.Map(cty.String)), 1893 }, 1894 validateDiff: renderers.ValidateMap(map[string]renderers.ValidateDiffFunction{ 1895 "element_one": renderers.ValidatePrimitive("one", nil, plans.Delete, false), 1896 "element_two": renderers.ValidatePrimitive("two", nil, plans.Delete, false), 1897 }, plans.Delete, false), 1898 }, 1899 "map_create_sensitive": { 1900 input: structured.Change{ 1901 Before: nil, 1902 After: map[string]interface{}{}, 1903 AfterSensitive: true, 1904 }, 1905 attribute: &jsonprovider.Attribute{ 1906 AttributeType: unmarshalType(t, cty.Map(cty.String)), 1907 }, 1908 validateDiff: renderers.ValidateSensitive(renderers.ValidateMap(nil, plans.Create, false), false, true, plans.Create, false), 1909 }, 1910 "map_update_sensitive": { 1911 input: structured.Change{ 1912 Before: map[string]interface{}{ 1913 "element": "one", 1914 }, 1915 BeforeSensitive: true, 1916 After: map[string]interface{}{}, 1917 AfterSensitive: true, 1918 }, 1919 attribute: &jsonprovider.Attribute{ 1920 AttributeType: unmarshalType(t, cty.Map(cty.String)), 1921 }, 1922 validateDiff: renderers.ValidateSensitive(renderers.ValidateMap(map[string]renderers.ValidateDiffFunction{ 1923 "element": renderers.ValidatePrimitive("one", nil, plans.Delete, false), 1924 }, plans.Update, false), true, true, plans.Update, false), 1925 }, 1926 "map_delete_sensitive": { 1927 input: structured.Change{ 1928 Before: map[string]interface{}{}, 1929 BeforeSensitive: true, 1930 After: nil, 1931 }, 1932 attribute: &jsonprovider.Attribute{ 1933 AttributeType: unmarshalType(t, cty.Map(cty.String)), 1934 }, 1935 validateDiff: renderers.ValidateSensitive(renderers.ValidateMap(nil, plans.Delete, false), true, false, plans.Delete, false), 1936 }, 1937 "map_create_unknown": { 1938 input: structured.Change{ 1939 Before: nil, 1940 After: map[string]interface{}{}, 1941 Unknown: true, 1942 }, 1943 attribute: &jsonprovider.Attribute{ 1944 AttributeType: unmarshalType(t, cty.Map(cty.String)), 1945 }, 1946 validateDiff: renderers.ValidateUnknown(nil, plans.Create, false), 1947 }, 1948 "map_update_unknown": { 1949 input: structured.Change{ 1950 Before: map[string]interface{}{}, 1951 After: map[string]interface{}{ 1952 "element": "one", 1953 }, 1954 Unknown: true, 1955 }, 1956 attribute: &jsonprovider.Attribute{ 1957 AttributeType: unmarshalType(t, cty.Map(cty.String)), 1958 }, 1959 validateDiff: renderers.ValidateUnknown(renderers.ValidateMap(nil, plans.Delete, false), plans.Update, false), 1960 }, 1961 "list_create_empty": { 1962 input: structured.Change{ 1963 Before: nil, 1964 After: []interface{}{}, 1965 }, 1966 attribute: &jsonprovider.Attribute{ 1967 AttributeType: unmarshalType(t, cty.List(cty.String)), 1968 }, 1969 validateDiff: renderers.ValidateList(nil, plans.Create, false), 1970 }, 1971 "list_create_populated": { 1972 input: structured.Change{ 1973 Before: nil, 1974 After: []interface{}{"one", "two"}, 1975 }, 1976 attribute: &jsonprovider.Attribute{ 1977 AttributeType: unmarshalType(t, cty.List(cty.String)), 1978 }, 1979 validateDiff: renderers.ValidateList([]renderers.ValidateDiffFunction{ 1980 renderers.ValidatePrimitive(nil, "one", plans.Create, false), 1981 renderers.ValidatePrimitive(nil, "two", plans.Create, false), 1982 }, plans.Create, false), 1983 }, 1984 "list_delete_empty": { 1985 input: structured.Change{ 1986 Before: []interface{}{}, 1987 After: nil, 1988 }, 1989 attribute: &jsonprovider.Attribute{ 1990 AttributeType: unmarshalType(t, cty.List(cty.String)), 1991 }, 1992 validateDiff: renderers.ValidateList(nil, plans.Delete, false), 1993 }, 1994 "list_delete_populated": { 1995 input: structured.Change{ 1996 Before: []interface{}{"one", "two"}, 1997 After: nil, 1998 }, 1999 attribute: &jsonprovider.Attribute{ 2000 AttributeType: unmarshalType(t, cty.List(cty.String)), 2001 }, 2002 validateDiff: renderers.ValidateList([]renderers.ValidateDiffFunction{ 2003 renderers.ValidatePrimitive("one", nil, plans.Delete, false), 2004 renderers.ValidatePrimitive("two", nil, plans.Delete, false), 2005 }, plans.Delete, false), 2006 }, 2007 "list_create_sensitive": { 2008 input: structured.Change{ 2009 Before: nil, 2010 After: []interface{}{}, 2011 AfterSensitive: true, 2012 }, 2013 attribute: &jsonprovider.Attribute{ 2014 AttributeType: unmarshalType(t, cty.List(cty.String)), 2015 }, 2016 validateDiff: renderers.ValidateSensitive(renderers.ValidateList(nil, plans.Create, false), false, true, plans.Create, false), 2017 }, 2018 "list_update_sensitive": { 2019 input: structured.Change{ 2020 Before: []interface{}{"one"}, 2021 BeforeSensitive: true, 2022 After: []interface{}{}, 2023 AfterSensitive: true, 2024 }, 2025 attribute: &jsonprovider.Attribute{ 2026 AttributeType: unmarshalType(t, cty.List(cty.String)), 2027 }, 2028 validateDiff: renderers.ValidateSensitive(renderers.ValidateList([]renderers.ValidateDiffFunction{ 2029 renderers.ValidatePrimitive("one", nil, plans.Delete, false), 2030 }, plans.Update, false), true, true, plans.Update, false), 2031 }, 2032 "list_delete_sensitive": { 2033 input: structured.Change{ 2034 Before: []interface{}{}, 2035 BeforeSensitive: true, 2036 After: nil, 2037 }, 2038 attribute: &jsonprovider.Attribute{ 2039 AttributeType: unmarshalType(t, cty.List(cty.String)), 2040 }, 2041 validateDiff: renderers.ValidateSensitive(renderers.ValidateList(nil, plans.Delete, false), true, false, plans.Delete, false), 2042 }, 2043 "list_create_unknown": { 2044 input: structured.Change{ 2045 Before: nil, 2046 After: []interface{}{}, 2047 Unknown: true, 2048 }, 2049 attribute: &jsonprovider.Attribute{ 2050 AttributeType: unmarshalType(t, cty.List(cty.String)), 2051 }, 2052 validateDiff: renderers.ValidateUnknown(nil, plans.Create, false), 2053 }, 2054 "list_update_unknown": { 2055 input: structured.Change{ 2056 Before: []interface{}{}, 2057 After: []interface{}{"one"}, 2058 Unknown: true, 2059 }, 2060 attribute: &jsonprovider.Attribute{ 2061 AttributeType: unmarshalType(t, cty.List(cty.String)), 2062 }, 2063 validateDiff: renderers.ValidateUnknown(renderers.ValidateList(nil, plans.Delete, false), plans.Update, false), 2064 }, 2065 "set_create_empty": { 2066 input: structured.Change{ 2067 Before: nil, 2068 After: []interface{}{}, 2069 }, 2070 attribute: &jsonprovider.Attribute{ 2071 AttributeType: unmarshalType(t, cty.Set(cty.String)), 2072 }, 2073 validateDiff: renderers.ValidateSet(nil, plans.Create, false), 2074 }, 2075 "set_create_populated": { 2076 input: structured.Change{ 2077 Before: nil, 2078 After: []interface{}{"one", "two"}, 2079 }, 2080 attribute: &jsonprovider.Attribute{ 2081 AttributeType: unmarshalType(t, cty.Set(cty.String)), 2082 }, 2083 validateDiff: renderers.ValidateSet([]renderers.ValidateDiffFunction{ 2084 renderers.ValidatePrimitive(nil, "one", plans.Create, false), 2085 renderers.ValidatePrimitive(nil, "two", plans.Create, false), 2086 }, plans.Create, false), 2087 }, 2088 "set_delete_empty": { 2089 input: structured.Change{ 2090 Before: []interface{}{}, 2091 After: nil, 2092 }, 2093 attribute: &jsonprovider.Attribute{ 2094 AttributeType: unmarshalType(t, cty.Set(cty.String)), 2095 }, 2096 validateDiff: renderers.ValidateSet(nil, plans.Delete, false), 2097 }, 2098 "set_delete_populated": { 2099 input: structured.Change{ 2100 Before: []interface{}{"one", "two"}, 2101 After: nil, 2102 }, 2103 attribute: &jsonprovider.Attribute{ 2104 AttributeType: unmarshalType(t, cty.Set(cty.String)), 2105 }, 2106 validateDiff: renderers.ValidateSet([]renderers.ValidateDiffFunction{ 2107 renderers.ValidatePrimitive("one", nil, plans.Delete, false), 2108 renderers.ValidatePrimitive("two", nil, plans.Delete, false), 2109 }, plans.Delete, false), 2110 }, 2111 "set_create_sensitive": { 2112 input: structured.Change{ 2113 Before: nil, 2114 After: []interface{}{}, 2115 AfterSensitive: true, 2116 }, 2117 attribute: &jsonprovider.Attribute{ 2118 AttributeType: unmarshalType(t, cty.Set(cty.String)), 2119 }, 2120 validateDiff: renderers.ValidateSensitive(renderers.ValidateSet(nil, plans.Create, false), false, true, plans.Create, false), 2121 }, 2122 "set_update_sensitive": { 2123 input: structured.Change{ 2124 Before: []interface{}{"one"}, 2125 BeforeSensitive: true, 2126 After: []interface{}{}, 2127 AfterSensitive: true, 2128 }, 2129 attribute: &jsonprovider.Attribute{ 2130 AttributeType: unmarshalType(t, cty.Set(cty.String)), 2131 }, 2132 validateDiff: renderers.ValidateSensitive(renderers.ValidateSet([]renderers.ValidateDiffFunction{ 2133 renderers.ValidatePrimitive("one", nil, plans.Delete, false), 2134 }, plans.Update, false), true, true, plans.Update, false), 2135 }, 2136 "set_delete_sensitive": { 2137 input: structured.Change{ 2138 Before: []interface{}{}, 2139 BeforeSensitive: true, 2140 After: nil, 2141 }, 2142 attribute: &jsonprovider.Attribute{ 2143 AttributeType: unmarshalType(t, cty.Set(cty.String)), 2144 }, 2145 validateDiff: renderers.ValidateSensitive(renderers.ValidateSet(nil, plans.Delete, false), true, false, plans.Delete, false), 2146 }, 2147 "set_create_unknown": { 2148 input: structured.Change{ 2149 Before: nil, 2150 After: []interface{}{}, 2151 Unknown: true, 2152 }, 2153 attribute: &jsonprovider.Attribute{ 2154 AttributeType: unmarshalType(t, cty.Set(cty.String)), 2155 }, 2156 validateDiff: renderers.ValidateUnknown(nil, plans.Create, false), 2157 }, 2158 "set_update_unknown": { 2159 input: structured.Change{ 2160 Before: []interface{}{}, 2161 After: []interface{}{"one"}, 2162 Unknown: true, 2163 }, 2164 attribute: &jsonprovider.Attribute{ 2165 AttributeType: unmarshalType(t, cty.Set(cty.String)), 2166 }, 2167 validateDiff: renderers.ValidateUnknown(renderers.ValidateSet(nil, plans.Delete, false), plans.Update, false), 2168 }, 2169 "tuple_primitive": { 2170 input: structured.Change{ 2171 Before: []interface{}{ 2172 "one", 2173 json.Number("2"), 2174 "three", 2175 }, 2176 After: []interface{}{ 2177 "one", 2178 json.Number("4"), 2179 "three", 2180 }, 2181 }, 2182 attribute: &jsonprovider.Attribute{ 2183 AttributeType: unmarshalType(t, cty.Tuple([]cty.Type{cty.String, cty.Number, cty.String})), 2184 }, 2185 validateDiff: renderers.ValidateList([]renderers.ValidateDiffFunction{ 2186 renderers.ValidatePrimitive("one", "one", plans.NoOp, false), 2187 renderers.ValidatePrimitive(json.Number("2"), json.Number("4"), plans.Update, false), 2188 renderers.ValidatePrimitive("three", "three", plans.NoOp, false), 2189 }, plans.Update, false), 2190 }, 2191 } 2192 2193 for name, tc := range tcs { 2194 2195 // Let's set some default values on the input. 2196 if tc.input.RelevantAttributes == nil { 2197 tc.input.RelevantAttributes = attribute_path.AlwaysMatcher() 2198 } 2199 if tc.input.ReplacePaths == nil { 2200 tc.input.ReplacePaths = &attribute_path.PathMatcher{} 2201 } 2202 2203 t.Run(name, func(t *testing.T) { 2204 tc.validateDiff(t, ComputeDiffForAttribute(tc.input, tc.attribute)) 2205 }) 2206 } 2207 } 2208 2209 func TestRelevantAttributes(t *testing.T) { 2210 tcs := map[string]struct { 2211 input structured.Change 2212 block *jsonprovider.Block 2213 validate renderers.ValidateDiffFunction 2214 }{ 2215 "simple_attributes": { 2216 input: structured.Change{ 2217 Before: map[string]interface{}{ 2218 "id": "old_id", 2219 "ignore": "doesn't matter", 2220 }, 2221 After: map[string]interface{}{ 2222 "id": "new_id", 2223 "ignore": "doesn't matter but modified", 2224 }, 2225 RelevantAttributes: &attribute_path.PathMatcher{ 2226 Paths: [][]interface{}{ 2227 { 2228 "id", 2229 }, 2230 }, 2231 }, 2232 }, 2233 block: &jsonprovider.Block{ 2234 Attributes: map[string]*jsonprovider.Attribute{ 2235 "id": { 2236 AttributeType: unmarshalType(t, cty.String), 2237 }, 2238 "ignore": { 2239 AttributeType: unmarshalType(t, cty.String), 2240 }, 2241 }, 2242 }, 2243 validate: renderers.ValidateBlock(map[string]renderers.ValidateDiffFunction{ 2244 "id": renderers.ValidatePrimitive("old_id", "new_id", plans.Update, false), 2245 "ignore": renderers.ValidatePrimitive("doesn't matter", "doesn't matter", plans.NoOp, false), 2246 }, nil, nil, nil, nil, plans.Update, false), 2247 }, 2248 "nested_attributes": { 2249 input: structured.Change{ 2250 Before: map[string]interface{}{ 2251 "list_block": []interface{}{ 2252 map[string]interface{}{ 2253 "id": "old_one", 2254 }, 2255 map[string]interface{}{ 2256 "id": "ignored", 2257 }, 2258 }, 2259 }, 2260 After: map[string]interface{}{ 2261 "list_block": []interface{}{ 2262 map[string]interface{}{ 2263 "id": "new_one", 2264 }, 2265 map[string]interface{}{ 2266 "id": "ignored_but_changed", 2267 }, 2268 }, 2269 }, 2270 RelevantAttributes: &attribute_path.PathMatcher{ 2271 Paths: [][]interface{}{ 2272 { 2273 "list_block", 2274 float64(0), 2275 "id", 2276 }, 2277 }, 2278 }, 2279 }, 2280 block: &jsonprovider.Block{ 2281 BlockTypes: map[string]*jsonprovider.BlockType{ 2282 "list_block": { 2283 Block: &jsonprovider.Block{ 2284 Attributes: map[string]*jsonprovider.Attribute{ 2285 "id": { 2286 AttributeType: unmarshalType(t, cty.String), 2287 }, 2288 }, 2289 }, 2290 NestingMode: "list", 2291 }, 2292 }, 2293 }, 2294 validate: renderers.ValidateBlock(nil, nil, map[string][]renderers.ValidateDiffFunction{ 2295 "list_block": { 2296 renderers.ValidateBlock(map[string]renderers.ValidateDiffFunction{ 2297 "id": renderers.ValidatePrimitive("old_one", "new_one", plans.Update, false), 2298 }, nil, nil, nil, nil, plans.Update, false), 2299 renderers.ValidateBlock(map[string]renderers.ValidateDiffFunction{ 2300 "id": renderers.ValidatePrimitive("ignored", "ignored", plans.NoOp, false), 2301 }, nil, nil, nil, nil, plans.NoOp, false), 2302 }, 2303 }, nil, nil, plans.Update, false), 2304 }, 2305 "nested_attributes_in_object": { 2306 input: structured.Change{ 2307 Before: map[string]interface{}{ 2308 "object": map[string]interface{}{ 2309 "id": "old_id", 2310 }, 2311 }, 2312 After: map[string]interface{}{ 2313 "object": map[string]interface{}{ 2314 "id": "new_id", 2315 }, 2316 }, 2317 RelevantAttributes: &attribute_path.PathMatcher{ 2318 Propagate: true, 2319 Paths: [][]interface{}{ 2320 { 2321 "object", // Even though we just specify object, it should now include every below object as well. 2322 }, 2323 }, 2324 }, 2325 }, 2326 block: &jsonprovider.Block{ 2327 Attributes: map[string]*jsonprovider.Attribute{ 2328 "object": { 2329 AttributeType: unmarshalType(t, cty.Object(map[string]cty.Type{ 2330 "id": cty.String, 2331 })), 2332 }, 2333 }, 2334 }, 2335 validate: renderers.ValidateBlock(map[string]renderers.ValidateDiffFunction{ 2336 "object": renderers.ValidateObject(map[string]renderers.ValidateDiffFunction{ 2337 "id": renderers.ValidatePrimitive("old_id", "new_id", plans.Update, false), 2338 }, plans.Update, false), 2339 }, nil, nil, nil, nil, plans.Update, false), 2340 }, 2341 "elements_in_list": { 2342 input: structured.Change{ 2343 Before: map[string]interface{}{ 2344 "list": []interface{}{ 2345 json.Number("0"), json.Number("1"), json.Number("2"), json.Number("3"), json.Number("4"), 2346 }, 2347 }, 2348 After: map[string]interface{}{ 2349 "list": []interface{}{ 2350 json.Number("0"), json.Number("5"), json.Number("6"), json.Number("7"), json.Number("4"), 2351 }, 2352 }, 2353 RelevantAttributes: &attribute_path.PathMatcher{ 2354 Paths: [][]interface{}{ // The list is actually just going to ignore this. 2355 { 2356 "list", 2357 0.0, 2358 }, 2359 { 2360 "list", 2361 2.0, 2362 }, 2363 { 2364 "list", 2365 4.0, 2366 }, 2367 }, 2368 }, 2369 }, 2370 block: &jsonprovider.Block{ 2371 Attributes: map[string]*jsonprovider.Attribute{ 2372 "list": { 2373 AttributeType: unmarshalType(t, cty.List(cty.Number)), 2374 }, 2375 }, 2376 }, 2377 validate: renderers.ValidateBlock(map[string]renderers.ValidateDiffFunction{ 2378 // The list validator below just ignores our relevant 2379 // attributes. This is deliberate. 2380 "list": renderers.ValidateList([]renderers.ValidateDiffFunction{ 2381 renderers.ValidatePrimitive(json.Number("0"), json.Number("0"), plans.NoOp, false), 2382 renderers.ValidatePrimitive(json.Number("1"), nil, plans.Delete, false), 2383 renderers.ValidatePrimitive(json.Number("2"), nil, plans.Delete, false), 2384 renderers.ValidatePrimitive(json.Number("3"), nil, plans.Delete, false), 2385 renderers.ValidatePrimitive(nil, json.Number("5"), plans.Create, false), 2386 renderers.ValidatePrimitive(nil, json.Number("6"), plans.Create, false), 2387 renderers.ValidatePrimitive(nil, json.Number("7"), plans.Create, false), 2388 renderers.ValidatePrimitive(json.Number("4"), json.Number("4"), plans.NoOp, false), 2389 }, plans.Update, false), 2390 }, nil, nil, nil, nil, plans.Update, false), 2391 }, 2392 "elements_in_map": { 2393 input: structured.Change{ 2394 Before: map[string]interface{}{ 2395 "map": map[string]interface{}{ 2396 "key_one": "value_one", 2397 "key_two": "value_two", 2398 "key_three": "value_three", 2399 }, 2400 }, 2401 After: map[string]interface{}{ 2402 "map": map[string]interface{}{ 2403 "key_one": "value_three", 2404 "key_two": "value_seven", 2405 "key_four": "value_four", 2406 }, 2407 }, 2408 RelevantAttributes: &attribute_path.PathMatcher{ 2409 Paths: [][]interface{}{ 2410 { 2411 "map", 2412 "key_one", 2413 }, 2414 { 2415 "map", 2416 "key_three", 2417 }, 2418 { 2419 "map", 2420 "key_four", 2421 }, 2422 }, 2423 }, 2424 }, 2425 block: &jsonprovider.Block{ 2426 Attributes: map[string]*jsonprovider.Attribute{ 2427 "map": { 2428 AttributeType: unmarshalType(t, cty.Map(cty.String)), 2429 }, 2430 }, 2431 }, 2432 validate: renderers.ValidateBlock(map[string]renderers.ValidateDiffFunction{ 2433 "map": renderers.ValidateMap(map[string]renderers.ValidateDiffFunction{ 2434 "key_one": renderers.ValidatePrimitive("value_one", "value_three", plans.Update, false), 2435 "key_two": renderers.ValidatePrimitive("value_two", "value_two", plans.NoOp, false), 2436 "key_three": renderers.ValidatePrimitive("value_three", nil, plans.Delete, false), 2437 "key_four": renderers.ValidatePrimitive(nil, "value_four", plans.Create, false), 2438 }, plans.Update, false), 2439 }, nil, nil, nil, nil, plans.Update, false), 2440 }, 2441 "elements_in_set": { 2442 input: structured.Change{ 2443 Before: map[string]interface{}{ 2444 "set": []interface{}{ 2445 json.Number("0"), json.Number("1"), json.Number("2"), json.Number("3"), json.Number("4"), 2446 }, 2447 }, 2448 After: map[string]interface{}{ 2449 "set": []interface{}{ 2450 json.Number("0"), json.Number("2"), json.Number("4"), json.Number("5"), json.Number("6"), 2451 }, 2452 }, 2453 RelevantAttributes: &attribute_path.PathMatcher{ 2454 Propagate: true, 2455 Paths: [][]interface{}{ 2456 { 2457 "set", 2458 }, 2459 }, 2460 }, 2461 }, 2462 block: &jsonprovider.Block{ 2463 Attributes: map[string]*jsonprovider.Attribute{ 2464 "set": { 2465 AttributeType: unmarshalType(t, cty.Set(cty.Number)), 2466 }, 2467 }, 2468 }, 2469 validate: renderers.ValidateBlock(map[string]renderers.ValidateDiffFunction{ 2470 "set": renderers.ValidateSet([]renderers.ValidateDiffFunction{ 2471 renderers.ValidatePrimitive(json.Number("0"), json.Number("0"), plans.NoOp, false), 2472 renderers.ValidatePrimitive(json.Number("1"), nil, plans.Delete, false), 2473 renderers.ValidatePrimitive(json.Number("2"), json.Number("2"), plans.NoOp, false), 2474 renderers.ValidatePrimitive(json.Number("3"), nil, plans.Delete, false), 2475 renderers.ValidatePrimitive(json.Number("4"), json.Number("4"), plans.NoOp, false), 2476 renderers.ValidatePrimitive(nil, json.Number("5"), plans.Create, false), 2477 renderers.ValidatePrimitive(nil, json.Number("6"), plans.Create, false), 2478 }, plans.Update, false), 2479 }, nil, nil, nil, nil, plans.Update, false), 2480 }, 2481 "dynamic_types": { 2482 input: structured.Change{ 2483 Before: map[string]interface{}{ 2484 "dynamic_nested_type": map[string]interface{}{ 2485 "nested_id": "nomatch", 2486 "nested_object": map[string]interface{}{ 2487 "nested_nested_id": "matched", 2488 }, 2489 }, 2490 "dynamic_nested_type_match": map[string]interface{}{ 2491 "nested_id": "allmatch", 2492 "nested_object": map[string]interface{}{ 2493 "nested_nested_id": "allmatch", 2494 }, 2495 }, 2496 }, 2497 After: map[string]interface{}{ 2498 "dynamic_nested_type": map[string]interface{}{ 2499 "nested_id": "nomatch_changed", 2500 "nested_object": map[string]interface{}{ 2501 "nested_nested_id": "matched", 2502 }, 2503 }, 2504 "dynamic_nested_type_match": map[string]interface{}{ 2505 "nested_id": "allmatch", 2506 "nested_object": map[string]interface{}{ 2507 "nested_nested_id": "allmatch", 2508 }, 2509 }, 2510 }, 2511 RelevantAttributes: &attribute_path.PathMatcher{ 2512 Propagate: true, 2513 Paths: [][]interface{}{ 2514 { 2515 "dynamic_nested_type", 2516 "nested_object", 2517 "nested_nested_id", 2518 }, 2519 { 2520 "dynamic_nested_type_match", 2521 }, 2522 }, 2523 }, 2524 }, 2525 block: &jsonprovider.Block{ 2526 Attributes: map[string]*jsonprovider.Attribute{ 2527 "dynamic_nested_type": { 2528 AttributeType: unmarshalType(t, cty.DynamicPseudoType), 2529 }, 2530 "dynamic_nested_type_match": { 2531 AttributeType: unmarshalType(t, cty.DynamicPseudoType), 2532 }, 2533 }, 2534 }, 2535 validate: renderers.ValidateBlock(map[string]renderers.ValidateDiffFunction{ 2536 "dynamic_nested_type": renderers.ValidateObject(map[string]renderers.ValidateDiffFunction{ 2537 "nested_id": renderers.ValidatePrimitive("nomatch", "nomatch", plans.NoOp, false), 2538 "nested_object": renderers.ValidateObject(map[string]renderers.ValidateDiffFunction{ 2539 "nested_nested_id": renderers.ValidatePrimitive("matched", "matched", plans.NoOp, false), 2540 }, plans.NoOp, false), 2541 }, plans.NoOp, false), 2542 "dynamic_nested_type_match": renderers.ValidateObject(map[string]renderers.ValidateDiffFunction{ 2543 "nested_id": renderers.ValidatePrimitive("allmatch", "allmatch", plans.NoOp, false), 2544 "nested_object": renderers.ValidateObject(map[string]renderers.ValidateDiffFunction{ 2545 "nested_nested_id": renderers.ValidatePrimitive("allmatch", "allmatch", plans.NoOp, false), 2546 }, plans.NoOp, false), 2547 }, plans.NoOp, false), 2548 }, nil, nil, nil, nil, plans.NoOp, false), 2549 }, 2550 } 2551 for name, tc := range tcs { 2552 if tc.input.ReplacePaths == nil { 2553 tc.input.ReplacePaths = &attribute_path.PathMatcher{} 2554 } 2555 t.Run(name, func(t *testing.T) { 2556 tc.validate(t, ComputeDiffForBlock(tc.input, tc.block)) 2557 }) 2558 } 2559 } 2560 2561 func TestDynamicPseudoType(t *testing.T) { 2562 tcs := map[string]struct { 2563 input structured.Change 2564 validate renderers.ValidateDiffFunction 2565 }{ 2566 "after_sensitive_in_dynamic_type": { 2567 input: structured.Change{ 2568 Before: nil, 2569 After: map[string]interface{}{ 2570 "key": "value", 2571 }, 2572 Unknown: false, 2573 BeforeSensitive: false, 2574 AfterSensitive: map[string]interface{}{ 2575 "key": true, 2576 }, 2577 ReplacePaths: attribute_path.Empty(false), 2578 RelevantAttributes: attribute_path.AlwaysMatcher(), 2579 }, 2580 validate: renderers.ValidateObject(map[string]renderers.ValidateDiffFunction{ 2581 "key": renderers.ValidateSensitive(renderers.ValidatePrimitive(nil, "value", plans.Create, false), false, true, plans.Create, false), 2582 }, plans.Create, false), 2583 }, 2584 "before_sensitive_in_dynamic_type": { 2585 input: structured.Change{ 2586 Before: map[string]interface{}{ 2587 "key": "value", 2588 }, 2589 After: nil, 2590 Unknown: false, 2591 BeforeSensitive: map[string]interface{}{ 2592 "key": true, 2593 }, 2594 AfterSensitive: false, 2595 ReplacePaths: attribute_path.Empty(false), 2596 RelevantAttributes: attribute_path.AlwaysMatcher(), 2597 }, 2598 validate: renderers.ValidateObject(map[string]renderers.ValidateDiffFunction{ 2599 "key": renderers.ValidateSensitive(renderers.ValidatePrimitive("value", nil, plans.Delete, false), true, false, plans.Delete, false), 2600 }, plans.Delete, false), 2601 }, 2602 "sensitive_in_dynamic_type": { 2603 input: structured.Change{ 2604 Before: map[string]interface{}{ 2605 "key": "before", 2606 }, 2607 After: map[string]interface{}{ 2608 "key": "after", 2609 }, 2610 Unknown: false, 2611 BeforeSensitive: map[string]interface{}{ 2612 "key": true, 2613 }, 2614 AfterSensitive: map[string]interface{}{ 2615 "key": true, 2616 }, 2617 ReplacePaths: attribute_path.Empty(false), 2618 RelevantAttributes: attribute_path.AlwaysMatcher(), 2619 }, 2620 validate: renderers.ValidateObject(map[string]renderers.ValidateDiffFunction{ 2621 "key": renderers.ValidateSensitive(renderers.ValidatePrimitive("before", "after", plans.Update, false), true, true, plans.Update, false), 2622 }, plans.Update, false), 2623 }, 2624 "create_unknown_in_dynamic_type": { 2625 input: structured.Change{ 2626 Before: nil, 2627 After: map[string]interface{}{}, 2628 Unknown: map[string]interface{}{ 2629 "key": true, 2630 }, 2631 BeforeSensitive: false, 2632 AfterSensitive: false, 2633 ReplacePaths: attribute_path.Empty(false), 2634 RelevantAttributes: attribute_path.AlwaysMatcher(), 2635 }, 2636 validate: renderers.ValidateObject(map[string]renderers.ValidateDiffFunction{ 2637 "key": renderers.ValidateUnknown(nil, plans.Create, false), 2638 }, plans.Create, false), 2639 }, 2640 "update_unknown_in_dynamic_type": { 2641 input: structured.Change{ 2642 Before: map[string]interface{}{ 2643 "key": "before", 2644 }, 2645 After: map[string]interface{}{}, 2646 Unknown: map[string]interface{}{ 2647 "key": true, 2648 }, 2649 BeforeSensitive: false, 2650 AfterSensitive: false, 2651 ReplacePaths: attribute_path.Empty(false), 2652 RelevantAttributes: attribute_path.AlwaysMatcher(), 2653 }, 2654 validate: renderers.ValidateObject(map[string]renderers.ValidateDiffFunction{ 2655 "key": renderers.ValidateUnknown(renderers.ValidatePrimitive("before", nil, plans.Delete, false), plans.Update, false), 2656 }, plans.Update, false), 2657 }, 2658 } 2659 for key, tc := range tcs { 2660 t.Run(key, func(t *testing.T) { 2661 tc.validate(t, ComputeDiffForType(tc.input, cty.DynamicPseudoType)) 2662 }) 2663 } 2664 } 2665 2666 func TestSpecificCases(t *testing.T) { 2667 // This is a special test that can contain any combination of individual 2668 // cases and will execute against them. For testing/fixing specific issues 2669 // you can generally put the test case in here. 2670 tcs := map[string]struct { 2671 input structured.Change 2672 block *jsonprovider.Block 2673 validate renderers.ValidateDiffFunction 2674 }{ 2675 "issues/33016/unknown": { 2676 input: structured.Change{ 2677 Before: nil, 2678 After: map[string]interface{}{ 2679 "triggers": map[string]interface{}{}, 2680 }, 2681 Unknown: map[string]interface{}{ 2682 "id": true, 2683 "triggers": map[string]interface{}{ 2684 "rotation": true, 2685 }, 2686 }, 2687 BeforeSensitive: false, 2688 AfterSensitive: map[string]interface{}{ 2689 "triggers": map[string]interface{}{}, 2690 }, 2691 ReplacePaths: attribute_path.Empty(false), 2692 RelevantAttributes: attribute_path.AlwaysMatcher(), 2693 }, 2694 block: &jsonprovider.Block{ 2695 Attributes: map[string]*jsonprovider.Attribute{ 2696 "id": { 2697 AttributeType: unmarshalType(t, cty.String), 2698 }, 2699 "triggers": { 2700 AttributeType: unmarshalType(t, cty.Map(cty.String)), 2701 }, 2702 }, 2703 }, 2704 validate: renderers.ValidateBlock(map[string]renderers.ValidateDiffFunction{ 2705 "id": renderers.ValidateUnknown(nil, plans.Create, false), 2706 "triggers": renderers.ValidateMap(map[string]renderers.ValidateDiffFunction{ 2707 "rotation": renderers.ValidateUnknown(nil, plans.Create, false), 2708 }, plans.Create, false), 2709 }, nil, nil, nil, nil, plans.Create, false), 2710 }, 2711 "issues/33016/null": { 2712 input: structured.Change{ 2713 Before: nil, 2714 After: map[string]interface{}{ 2715 "triggers": map[string]interface{}{ 2716 "rotation": nil, 2717 }, 2718 }, 2719 Unknown: map[string]interface{}{ 2720 "id": true, 2721 "triggers": map[string]interface{}{}, 2722 }, 2723 BeforeSensitive: false, 2724 AfterSensitive: map[string]interface{}{ 2725 "triggers": map[string]interface{}{}, 2726 }, 2727 ReplacePaths: attribute_path.Empty(false), 2728 RelevantAttributes: attribute_path.AlwaysMatcher(), 2729 }, 2730 block: &jsonprovider.Block{ 2731 Attributes: map[string]*jsonprovider.Attribute{ 2732 "id": { 2733 AttributeType: unmarshalType(t, cty.String), 2734 }, 2735 "triggers": { 2736 AttributeType: unmarshalType(t, cty.Map(cty.String)), 2737 }, 2738 }, 2739 }, 2740 validate: renderers.ValidateBlock(map[string]renderers.ValidateDiffFunction{ 2741 "id": renderers.ValidateUnknown(nil, plans.Create, false), 2742 "triggers": renderers.ValidateMap(map[string]renderers.ValidateDiffFunction{ 2743 "rotation": renderers.ValidatePrimitive(nil, nil, plans.Create, false), 2744 }, plans.Create, false), 2745 }, nil, nil, nil, nil, plans.Create, false), 2746 }, 2747 2748 // The following tests are from issue 33472. Basically OpenTofu allows 2749 // callers to treat numbers as strings in references and expects us 2750 // to coerce the strings into numbers. For example the following are 2751 // equivalent. 2752 // - test_resource.resource.list[0].attribute 2753 // - test_resource.resource.list["0"].attribute 2754 // 2755 // We need our attribute_path package (used within the ReplacePaths and 2756 // RelevantAttributes fields) to handle coercing strings into numbers 2757 // when it's expected. 2758 2759 "issues/33472/expected": { 2760 input: structured.Change{ 2761 Before: map[string]interface{}{ 2762 "list": []interface{}{ 2763 map[string]interface{}{ 2764 "number": json.Number("-1"), 2765 }, 2766 }, 2767 }, 2768 After: map[string]interface{}{ 2769 "list": []interface{}{ 2770 map[string]interface{}{ 2771 "number": json.Number("2"), 2772 }, 2773 }, 2774 }, 2775 Unknown: false, 2776 BeforeSensitive: false, 2777 AfterSensitive: false, 2778 ReplacePaths: attribute_path.Empty(false), 2779 RelevantAttributes: &attribute_path.PathMatcher{ 2780 Propagate: true, 2781 Paths: [][]interface{}{ 2782 { 2783 "list", 2784 0.0, // This is normal and expected so easy case. 2785 "number", 2786 }, 2787 }, 2788 }, 2789 }, 2790 block: &jsonprovider.Block{ 2791 Attributes: map[string]*jsonprovider.Attribute{ 2792 "list": { 2793 AttributeType: unmarshalType(t, cty.List(cty.Object(map[string]cty.Type{ 2794 "number": cty.Number, 2795 }))), 2796 }, 2797 }, 2798 }, 2799 validate: renderers.ValidateBlock(map[string]renderers.ValidateDiffFunction{ 2800 "list": renderers.ValidateList([]renderers.ValidateDiffFunction{ 2801 renderers.ValidateObject(map[string]renderers.ValidateDiffFunction{ 2802 "number": renderers.ValidatePrimitive(json.Number("-1"), json.Number("2"), plans.Update, false), 2803 }, plans.Update, false), 2804 }, plans.Update, false), 2805 }, nil, nil, nil, nil, plans.Update, false), 2806 }, 2807 2808 "issues/33472/coerce": { 2809 input: structured.Change{ 2810 Before: map[string]interface{}{ 2811 "list": []interface{}{ 2812 map[string]interface{}{ 2813 "number": json.Number("-1"), 2814 }, 2815 }, 2816 }, 2817 After: map[string]interface{}{ 2818 "list": []interface{}{ 2819 map[string]interface{}{ 2820 "number": json.Number("2"), 2821 }, 2822 }, 2823 }, 2824 Unknown: false, 2825 BeforeSensitive: false, 2826 AfterSensitive: false, 2827 ReplacePaths: attribute_path.Empty(false), 2828 RelevantAttributes: &attribute_path.PathMatcher{ 2829 Propagate: true, 2830 Paths: [][]interface{}{ 2831 { 2832 "list", 2833 "0", // Difficult but allowed, we need to handle this. 2834 "number", 2835 }, 2836 }, 2837 }, 2838 }, 2839 block: &jsonprovider.Block{ 2840 Attributes: map[string]*jsonprovider.Attribute{ 2841 "list": { 2842 AttributeType: unmarshalType(t, cty.List(cty.Object(map[string]cty.Type{ 2843 "number": cty.Number, 2844 }))), 2845 }, 2846 }, 2847 }, 2848 validate: renderers.ValidateBlock(map[string]renderers.ValidateDiffFunction{ 2849 "list": renderers.ValidateList([]renderers.ValidateDiffFunction{ 2850 renderers.ValidateObject(map[string]renderers.ValidateDiffFunction{ 2851 "number": renderers.ValidatePrimitive(json.Number("-1"), json.Number("2"), plans.Update, false), 2852 }, plans.Update, false), 2853 }, plans.Update, false), 2854 }, nil, nil, nil, nil, plans.Update, false), 2855 }, 2856 } 2857 for name, tc := range tcs { 2858 t.Run(name, func(t *testing.T) { 2859 tc.validate(t, ComputeDiffForBlock(tc.input, tc.block)) 2860 }) 2861 } 2862 } 2863 2864 // unmarshalType converts a cty.Type into a json.RawMessage understood by the 2865 // schema. It also lets the testing framework handle any errors to keep the API 2866 // clean. 2867 func unmarshalType(t *testing.T, ctyType cty.Type) json.RawMessage { 2868 msg, err := ctyjson.MarshalType(ctyType) 2869 if err != nil { 2870 t.Fatalf("invalid type: %s", ctyType.FriendlyName()) 2871 } 2872 return msg 2873 } 2874 2875 // wrapChangeInSlice does the same as wrapChangeInMap, except it wraps it into a 2876 // slice internally. 2877 func wrapChangeInSlice(input structured.Change) structured.Change { 2878 return wrapChange(input, float64(0), func(value interface{}, unknown interface{}, explicit bool) interface{} { 2879 switch value.(type) { 2880 case nil: 2881 if set, ok := unknown.(bool); (set && ok) || explicit { 2882 return []interface{}{nil} 2883 2884 } 2885 return []interface{}{} 2886 default: 2887 return []interface{}{value} 2888 } 2889 }) 2890 } 2891 2892 // wrapChangeInMap access a single structured.Change and returns a new 2893 // structured.Change that represents a map with a single element. That single 2894 // element is the input value. 2895 func wrapChangeInMap(input structured.Change) structured.Change { 2896 return wrapChange(input, "element", func(value interface{}, unknown interface{}, explicit bool) interface{} { 2897 switch value.(type) { 2898 case nil: 2899 if set, ok := unknown.(bool); (set && ok) || explicit { 2900 return map[string]interface{}{ 2901 "element": nil, 2902 } 2903 } 2904 return map[string]interface{}{} 2905 default: 2906 return map[string]interface{}{ 2907 "element": value, 2908 } 2909 } 2910 }) 2911 } 2912 2913 func wrapChange(input structured.Change, step interface{}, wrap func(interface{}, interface{}, bool) interface{}) structured.Change { 2914 2915 replacePaths := &attribute_path.PathMatcher{} 2916 for _, path := range input.ReplacePaths.(*attribute_path.PathMatcher).Paths { 2917 var updated []interface{} 2918 updated = append(updated, step) 2919 updated = append(updated, path...) 2920 replacePaths.Paths = append(replacePaths.Paths, updated) 2921 } 2922 2923 // relevantAttributes usually default to AlwaysMatcher, which means we can 2924 // just ignore it. But if we have had some paths specified we need to wrap 2925 // those as well. 2926 relevantAttributes := input.RelevantAttributes 2927 if concrete, ok := relevantAttributes.(*attribute_path.PathMatcher); ok { 2928 2929 newRelevantAttributes := &attribute_path.PathMatcher{} 2930 for _, path := range concrete.Paths { 2931 var updated []interface{} 2932 updated = append(updated, step) 2933 updated = append(updated, path...) 2934 newRelevantAttributes.Paths = append(newRelevantAttributes.Paths, updated) 2935 } 2936 relevantAttributes = newRelevantAttributes 2937 } 2938 2939 return structured.Change{ 2940 Before: wrap(input.Before, nil, input.BeforeExplicit), 2941 After: wrap(input.After, input.Unknown, input.AfterExplicit), 2942 Unknown: wrap(input.Unknown, nil, false), 2943 BeforeSensitive: wrap(input.BeforeSensitive, nil, false), 2944 AfterSensitive: wrap(input.AfterSensitive, nil, false), 2945 ReplacePaths: replacePaths, 2946 RelevantAttributes: relevantAttributes, 2947 } 2948 }