github.com/opentofu/opentofu@v1.7.1/internal/legacy/helper/schema/resource_diff_test.go (about)

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