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