github.com/ben-turner/terraform@v0.11.8-0.20180503104400-0cc9e050ecd4/helper/schema/resource_diff_test.go (about)

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