github.com/scottwinkler/terraform@v0.11.6-0.20180329211809-05143987aea8/helper/schema/resource_diff_test.go (about)

     1  package schema
     2  
     3  import (
     4  	"reflect"
     5  	"sort"
     6  	"testing"
     7  
     8  	"github.com/davecgh/go-spew/spew"
     9  	"github.com/hashicorp/terraform/terraform"
    10  )
    11  
    12  // testSetFunc is a very simple function we use to test a foo/bar complex set.
    13  // Both "foo" and "bar" are int values.
    14  //
    15  // This is not foolproof as since it performs sums, you can run into
    16  // collisions. Spec tests accordingly. :P
    17  func testSetFunc(v interface{}) int {
    18  	m := v.(map[string]interface{})
    19  	return m["foo"].(int) + m["bar"].(int)
    20  }
    21  
    22  // resourceDiffTestCase provides a test case struct for SetNew and SetDiff.
    23  type resourceDiffTestCase struct {
    24  	Name          string
    25  	Schema        map[string]*Schema
    26  	State         *terraform.InstanceState
    27  	Config        *terraform.ResourceConfig
    28  	Diff          *terraform.InstanceDiff
    29  	Key           string
    30  	OldValue      interface{}
    31  	NewValue      interface{}
    32  	Expected      *terraform.InstanceDiff
    33  	ExpectedKeys  []string
    34  	ExpectedError bool
    35  }
    36  
    37  // testDiffCases produces a list of test cases for use with SetNew and SetDiff.
    38  func testDiffCases(t *testing.T, oldPrefix string, oldOffset int, computed bool) []resourceDiffTestCase {
    39  	return []resourceDiffTestCase{
    40  		resourceDiffTestCase{
    41  			Name: "basic primitive diff",
    42  			Schema: map[string]*Schema{
    43  				"foo": &Schema{
    44  					Type:     TypeString,
    45  					Optional: true,
    46  					Computed: true,
    47  				},
    48  			},
    49  			State: &terraform.InstanceState{
    50  				Attributes: map[string]string{
    51  					"foo": "bar",
    52  				},
    53  			},
    54  			Config: testConfig(t, map[string]interface{}{
    55  				"foo": "baz",
    56  			}),
    57  			Diff: &terraform.InstanceDiff{
    58  				Attributes: map[string]*terraform.ResourceAttrDiff{
    59  					"foo": &terraform.ResourceAttrDiff{
    60  						Old: "bar",
    61  						New: "baz",
    62  					},
    63  				},
    64  			},
    65  			Key:      "foo",
    66  			NewValue: "qux",
    67  			Expected: &terraform.InstanceDiff{
    68  				Attributes: map[string]*terraform.ResourceAttrDiff{
    69  					"foo": &terraform.ResourceAttrDiff{
    70  						Old: "bar",
    71  						New: func() string {
    72  							if computed {
    73  								return ""
    74  							}
    75  							return "qux"
    76  						}(),
    77  						NewComputed: computed,
    78  					},
    79  				},
    80  			},
    81  		},
    82  		resourceDiffTestCase{
    83  			Name: "basic set diff",
    84  			Schema: map[string]*Schema{
    85  				"foo": &Schema{
    86  					Type:     TypeSet,
    87  					Optional: true,
    88  					Computed: true,
    89  					Elem:     &Schema{Type: TypeString},
    90  					Set:      HashString,
    91  				},
    92  			},
    93  			State: &terraform.InstanceState{
    94  				Attributes: map[string]string{
    95  					"foo.#":          "1",
    96  					"foo.1996459178": "bar",
    97  				},
    98  			},
    99  			Config: testConfig(t, map[string]interface{}{
   100  				"foo": []interface{}{"baz"},
   101  			}),
   102  			Diff: &terraform.InstanceDiff{
   103  				Attributes: map[string]*terraform.ResourceAttrDiff{
   104  					"foo.1996459178": &terraform.ResourceAttrDiff{
   105  						Old:        "bar",
   106  						New:        "",
   107  						NewRemoved: true,
   108  					},
   109  					"foo.2015626392": &terraform.ResourceAttrDiff{
   110  						Old: "",
   111  						New: "baz",
   112  					},
   113  				},
   114  			},
   115  			Key:      "foo",
   116  			NewValue: []interface{}{"qux"},
   117  			Expected: &terraform.InstanceDiff{
   118  				Attributes: func() map[string]*terraform.ResourceAttrDiff {
   119  					result := map[string]*terraform.ResourceAttrDiff{}
   120  					if computed {
   121  						result["foo.#"] = &terraform.ResourceAttrDiff{
   122  							Old:         "1",
   123  							New:         "",
   124  							NewComputed: true,
   125  						}
   126  					} else {
   127  						result["foo.2800005064"] = &terraform.ResourceAttrDiff{
   128  							Old: "",
   129  							New: "qux",
   130  						}
   131  						result["foo.1996459178"] = &terraform.ResourceAttrDiff{
   132  							Old:        "bar",
   133  							New:        "",
   134  							NewRemoved: true,
   135  						}
   136  					}
   137  					return result
   138  				}(),
   139  			},
   140  		},
   141  		resourceDiffTestCase{
   142  			Name: "basic list diff",
   143  			Schema: map[string]*Schema{
   144  				"foo": &Schema{
   145  					Type:     TypeList,
   146  					Optional: true,
   147  					Computed: true,
   148  					Elem:     &Schema{Type: TypeString},
   149  				},
   150  			},
   151  			State: &terraform.InstanceState{
   152  				Attributes: map[string]string{
   153  					"foo.#": "1",
   154  					"foo.0": "bar",
   155  				},
   156  			},
   157  			Config: testConfig(t, map[string]interface{}{
   158  				"foo": []interface{}{"baz"},
   159  			}),
   160  			Diff: &terraform.InstanceDiff{
   161  				Attributes: map[string]*terraform.ResourceAttrDiff{
   162  					"foo.0": &terraform.ResourceAttrDiff{
   163  						Old: "bar",
   164  						New: "baz",
   165  					},
   166  				},
   167  			},
   168  			Key:      "foo",
   169  			NewValue: []interface{}{"qux"},
   170  			Expected: &terraform.InstanceDiff{
   171  				Attributes: func() map[string]*terraform.ResourceAttrDiff {
   172  					result := make(map[string]*terraform.ResourceAttrDiff)
   173  					if computed {
   174  						result["foo.#"] = &terraform.ResourceAttrDiff{
   175  							Old:         "1",
   176  							New:         "",
   177  							NewComputed: true,
   178  						}
   179  					} else {
   180  						result["foo.0"] = &terraform.ResourceAttrDiff{
   181  							Old: "bar",
   182  							New: "qux",
   183  						}
   184  					}
   185  					return result
   186  				}(),
   187  			},
   188  		},
   189  		resourceDiffTestCase{
   190  			Name: "basic map diff",
   191  			Schema: map[string]*Schema{
   192  				"foo": &Schema{
   193  					Type:     TypeMap,
   194  					Optional: true,
   195  					Computed: true,
   196  				},
   197  			},
   198  			State: &terraform.InstanceState{
   199  				Attributes: map[string]string{
   200  					"foo.%":   "1",
   201  					"foo.bar": "baz",
   202  				},
   203  			},
   204  			Config: testConfig(t, map[string]interface{}{
   205  				"foo": map[string]interface{}{"bar": "qux"},
   206  			}),
   207  			Diff: &terraform.InstanceDiff{
   208  				Attributes: map[string]*terraform.ResourceAttrDiff{
   209  					"foo.bar": &terraform.ResourceAttrDiff{
   210  						Old: "baz",
   211  						New: "qux",
   212  					},
   213  				},
   214  			},
   215  			Key:      "foo",
   216  			NewValue: map[string]interface{}{"bar": "quux"},
   217  			Expected: &terraform.InstanceDiff{
   218  				Attributes: func() map[string]*terraform.ResourceAttrDiff {
   219  					result := make(map[string]*terraform.ResourceAttrDiff)
   220  					if computed {
   221  						result["foo.%"] = &terraform.ResourceAttrDiff{
   222  							Old:         "",
   223  							New:         "",
   224  							NewComputed: true,
   225  						}
   226  						result["foo.bar"] = &terraform.ResourceAttrDiff{
   227  							Old:        "baz",
   228  							New:        "",
   229  							NewRemoved: true,
   230  						}
   231  					} else {
   232  						result["foo.bar"] = &terraform.ResourceAttrDiff{
   233  							Old: "baz",
   234  							New: "quux",
   235  						}
   236  					}
   237  					return result
   238  				}(),
   239  			},
   240  		},
   241  		resourceDiffTestCase{
   242  			Name: "additional diff with primitive",
   243  			Schema: map[string]*Schema{
   244  				"foo": &Schema{
   245  					Type:     TypeString,
   246  					Optional: true,
   247  				},
   248  				"one": &Schema{
   249  					Type:     TypeString,
   250  					Optional: true,
   251  					Computed: true,
   252  				},
   253  			},
   254  			State: &terraform.InstanceState{
   255  				Attributes: map[string]string{
   256  					"foo": "bar",
   257  					"one": "two",
   258  				},
   259  			},
   260  			Config: testConfig(t, map[string]interface{}{
   261  				"foo": "baz",
   262  			}),
   263  			Diff: &terraform.InstanceDiff{
   264  				Attributes: map[string]*terraform.ResourceAttrDiff{
   265  					"foo": &terraform.ResourceAttrDiff{
   266  						Old: "bar",
   267  						New: "baz",
   268  					},
   269  				},
   270  			},
   271  			Key:      "one",
   272  			NewValue: "four",
   273  			Expected: &terraform.InstanceDiff{
   274  				Attributes: map[string]*terraform.ResourceAttrDiff{
   275  					"foo": &terraform.ResourceAttrDiff{
   276  						Old: "bar",
   277  						New: "baz",
   278  					},
   279  					"one": &terraform.ResourceAttrDiff{
   280  						Old: "two",
   281  						New: func() string {
   282  							if computed {
   283  								return ""
   284  							}
   285  							return "four"
   286  						}(),
   287  						NewComputed: computed,
   288  					},
   289  				},
   290  			},
   291  		},
   292  		resourceDiffTestCase{
   293  			Name: "additional diff with primitive computed only",
   294  			Schema: map[string]*Schema{
   295  				"foo": &Schema{
   296  					Type:     TypeString,
   297  					Optional: true,
   298  				},
   299  				"one": &Schema{
   300  					Type:     TypeString,
   301  					Computed: true,
   302  				},
   303  			},
   304  			State: &terraform.InstanceState{
   305  				Attributes: map[string]string{
   306  					"foo": "bar",
   307  					"one": "two",
   308  				},
   309  			},
   310  			Config: testConfig(t, map[string]interface{}{
   311  				"foo": "baz",
   312  			}),
   313  			Diff: &terraform.InstanceDiff{
   314  				Attributes: map[string]*terraform.ResourceAttrDiff{
   315  					"foo": &terraform.ResourceAttrDiff{
   316  						Old: "bar",
   317  						New: "baz",
   318  					},
   319  				},
   320  			},
   321  			Key:      "one",
   322  			NewValue: "three",
   323  			Expected: &terraform.InstanceDiff{
   324  				Attributes: map[string]*terraform.ResourceAttrDiff{
   325  					"foo": &terraform.ResourceAttrDiff{
   326  						Old: "bar",
   327  						New: "baz",
   328  					},
   329  					"one": &terraform.ResourceAttrDiff{
   330  						Old: "two",
   331  						New: func() string {
   332  							if computed {
   333  								return ""
   334  							}
   335  							return "three"
   336  						}(),
   337  						NewComputed: computed,
   338  					},
   339  				},
   340  			},
   341  		},
   342  		resourceDiffTestCase{
   343  			Name: "complex-ish set diff",
   344  			Schema: map[string]*Schema{
   345  				"top": &Schema{
   346  					Type:     TypeSet,
   347  					Optional: true,
   348  					Computed: true,
   349  					Elem: &Resource{
   350  						Schema: map[string]*Schema{
   351  							"foo": &Schema{
   352  								Type:     TypeInt,
   353  								Optional: true,
   354  								Computed: true,
   355  							},
   356  							"bar": &Schema{
   357  								Type:     TypeInt,
   358  								Optional: true,
   359  								Computed: true,
   360  							},
   361  						},
   362  					},
   363  					Set: testSetFunc,
   364  				},
   365  			},
   366  			State: &terraform.InstanceState{
   367  				Attributes: map[string]string{
   368  					"top.#":      "2",
   369  					"top.3.foo":  "1",
   370  					"top.3.bar":  "2",
   371  					"top.23.foo": "11",
   372  					"top.23.bar": "12",
   373  				},
   374  			},
   375  			Config: testConfig(t, map[string]interface{}{
   376  				"top": []interface{}{
   377  					map[string]interface{}{
   378  						"foo": 1,
   379  						"bar": 3,
   380  					},
   381  					map[string]interface{}{
   382  						"foo": 12,
   383  						"bar": 12,
   384  					},
   385  				},
   386  			}),
   387  			Diff: &terraform.InstanceDiff{
   388  				Attributes: map[string]*terraform.ResourceAttrDiff{
   389  					"top.4.foo": &terraform.ResourceAttrDiff{
   390  						Old: "",
   391  						New: "1",
   392  					},
   393  					"top.4.bar": &terraform.ResourceAttrDiff{
   394  						Old: "",
   395  						New: "3",
   396  					},
   397  					"top.24.foo": &terraform.ResourceAttrDiff{
   398  						Old: "",
   399  						New: "12",
   400  					},
   401  					"top.24.bar": &terraform.ResourceAttrDiff{
   402  						Old: "",
   403  						New: "12",
   404  					},
   405  				},
   406  			},
   407  			Key: "top",
   408  			NewValue: NewSet(testSetFunc, []interface{}{
   409  				map[string]interface{}{
   410  					"foo": 1,
   411  					"bar": 4,
   412  				},
   413  				map[string]interface{}{
   414  					"foo": 13,
   415  					"bar": 12,
   416  				},
   417  				map[string]interface{}{
   418  					"foo": 21,
   419  					"bar": 22,
   420  				},
   421  			}),
   422  			Expected: &terraform.InstanceDiff{
   423  				Attributes: func() map[string]*terraform.ResourceAttrDiff {
   424  					result := make(map[string]*terraform.ResourceAttrDiff)
   425  					if computed {
   426  						result["top.#"] = &terraform.ResourceAttrDiff{
   427  							Old:         "2",
   428  							New:         "",
   429  							NewComputed: true,
   430  						}
   431  					} else {
   432  						result["top.#"] = &terraform.ResourceAttrDiff{
   433  							Old: "2",
   434  							New: "3",
   435  						}
   436  						result["top.5.foo"] = &terraform.ResourceAttrDiff{
   437  							Old: "",
   438  							New: "1",
   439  						}
   440  						result["top.5.bar"] = &terraform.ResourceAttrDiff{
   441  							Old: "",
   442  							New: "4",
   443  						}
   444  						result["top.25.foo"] = &terraform.ResourceAttrDiff{
   445  							Old: "",
   446  							New: "13",
   447  						}
   448  						result["top.25.bar"] = &terraform.ResourceAttrDiff{
   449  							Old: "",
   450  							New: "12",
   451  						}
   452  						result["top.43.foo"] = &terraform.ResourceAttrDiff{
   453  							Old: "",
   454  							New: "21",
   455  						}
   456  						result["top.43.bar"] = &terraform.ResourceAttrDiff{
   457  							Old: "",
   458  							New: "22",
   459  						}
   460  					}
   461  					return result
   462  				}(),
   463  			},
   464  		},
   465  		resourceDiffTestCase{
   466  			Name: "primitive, no diff, no refresh",
   467  			Schema: map[string]*Schema{
   468  				"foo": &Schema{
   469  					Type:     TypeString,
   470  					Computed: true,
   471  				},
   472  			},
   473  			State: &terraform.InstanceState{
   474  				Attributes: map[string]string{
   475  					"foo": "bar",
   476  				},
   477  			},
   478  			Config:   testConfig(t, map[string]interface{}{}),
   479  			Diff:     &terraform.InstanceDiff{Attributes: map[string]*terraform.ResourceAttrDiff{}},
   480  			Key:      "foo",
   481  			NewValue: "baz",
   482  			Expected: &terraform.InstanceDiff{
   483  				Attributes: map[string]*terraform.ResourceAttrDiff{
   484  					"foo": &terraform.ResourceAttrDiff{
   485  						Old: "bar",
   486  						New: func() string {
   487  							if computed {
   488  								return ""
   489  							}
   490  							return "baz"
   491  						}(),
   492  						NewComputed: computed,
   493  					},
   494  				},
   495  			},
   496  		},
   497  		resourceDiffTestCase{
   498  			Name: "non-computed key, should error",
   499  			Schema: map[string]*Schema{
   500  				"foo": &Schema{
   501  					Type:     TypeString,
   502  					Required: true,
   503  				},
   504  			},
   505  			State: &terraform.InstanceState{
   506  				Attributes: map[string]string{
   507  					"foo": "bar",
   508  				},
   509  			},
   510  			Config: testConfig(t, map[string]interface{}{
   511  				"foo": "baz",
   512  			}),
   513  			Diff: &terraform.InstanceDiff{
   514  				Attributes: map[string]*terraform.ResourceAttrDiff{
   515  					"foo": &terraform.ResourceAttrDiff{
   516  						Old: "bar",
   517  						New: "baz",
   518  					},
   519  				},
   520  			},
   521  			Key:           "foo",
   522  			NewValue:      "qux",
   523  			ExpectedError: true,
   524  		},
   525  		resourceDiffTestCase{
   526  			Name: "bad key, should error",
   527  			Schema: map[string]*Schema{
   528  				"foo": &Schema{
   529  					Type:     TypeString,
   530  					Required: true,
   531  				},
   532  			},
   533  			State: &terraform.InstanceState{
   534  				Attributes: map[string]string{
   535  					"foo": "bar",
   536  				},
   537  			},
   538  			Config: testConfig(t, map[string]interface{}{
   539  				"foo": "baz",
   540  			}),
   541  			Diff: &terraform.InstanceDiff{
   542  				Attributes: map[string]*terraform.ResourceAttrDiff{
   543  					"foo": &terraform.ResourceAttrDiff{
   544  						Old: "bar",
   545  						New: "baz",
   546  					},
   547  				},
   548  			},
   549  			Key:           "bad",
   550  			NewValue:      "qux",
   551  			ExpectedError: true,
   552  		},
   553  	}
   554  }
   555  
   556  func TestSetNew(t *testing.T) {
   557  	testCases := testDiffCases(t, "", 0, false)
   558  	for _, tc := range testCases {
   559  		t.Run(tc.Name, func(t *testing.T) {
   560  			m := schemaMap(tc.Schema)
   561  			d := newResourceDiff(tc.Schema, tc.Config, tc.State, tc.Diff)
   562  			err := d.SetNew(tc.Key, tc.NewValue)
   563  			switch {
   564  			case err != nil && !tc.ExpectedError:
   565  				t.Fatalf("bad: %s", err)
   566  			case err == nil && tc.ExpectedError:
   567  				t.Fatalf("Expected error, got none")
   568  			case err != nil && tc.ExpectedError:
   569  				return
   570  			}
   571  			for _, k := range d.UpdatedKeys() {
   572  				if err := m.diff(k, m[k], tc.Diff, d, false); err != nil {
   573  					t.Fatalf("bad: %s", err)
   574  				}
   575  			}
   576  			if !reflect.DeepEqual(tc.Expected, tc.Diff) {
   577  				t.Fatalf("Expected %s, got %s", spew.Sdump(tc.Expected), spew.Sdump(tc.Diff))
   578  			}
   579  		})
   580  	}
   581  }
   582  
   583  func TestSetNewComputed(t *testing.T) {
   584  	testCases := testDiffCases(t, "", 0, true)
   585  	for _, tc := range testCases {
   586  		t.Run(tc.Name, func(t *testing.T) {
   587  			m := schemaMap(tc.Schema)
   588  			d := newResourceDiff(tc.Schema, tc.Config, tc.State, tc.Diff)
   589  			err := d.SetNewComputed(tc.Key)
   590  			switch {
   591  			case err != nil && !tc.ExpectedError:
   592  				t.Fatalf("bad: %s", err)
   593  			case err == nil && tc.ExpectedError:
   594  				t.Fatalf("Expected error, got none")
   595  			case err != nil && tc.ExpectedError:
   596  				return
   597  			}
   598  			for _, k := range d.UpdatedKeys() {
   599  				if err := m.diff(k, m[k], tc.Diff, d, false); err != nil {
   600  					t.Fatalf("bad: %s", err)
   601  				}
   602  			}
   603  			if !reflect.DeepEqual(tc.Expected, tc.Diff) {
   604  				t.Fatalf("Expected %s, got %s", spew.Sdump(tc.Expected), spew.Sdump(tc.Diff))
   605  			}
   606  		})
   607  	}
   608  }
   609  
   610  func TestForceNew(t *testing.T) {
   611  	cases := []resourceDiffTestCase{
   612  		resourceDiffTestCase{
   613  			Name: "basic primitive diff",
   614  			Schema: map[string]*Schema{
   615  				"foo": &Schema{
   616  					Type:     TypeString,
   617  					Optional: true,
   618  					Computed: true,
   619  				},
   620  			},
   621  			State: &terraform.InstanceState{
   622  				Attributes: map[string]string{
   623  					"foo": "bar",
   624  				},
   625  			},
   626  			Config: testConfig(t, map[string]interface{}{
   627  				"foo": "baz",
   628  			}),
   629  			Diff: &terraform.InstanceDiff{
   630  				Attributes: map[string]*terraform.ResourceAttrDiff{
   631  					"foo": &terraform.ResourceAttrDiff{
   632  						Old: "bar",
   633  						New: "baz",
   634  					},
   635  				},
   636  			},
   637  			Key: "foo",
   638  			Expected: &terraform.InstanceDiff{
   639  				Attributes: map[string]*terraform.ResourceAttrDiff{
   640  					"foo": &terraform.ResourceAttrDiff{
   641  						Old:         "bar",
   642  						New:         "baz",
   643  						RequiresNew: true,
   644  					},
   645  				},
   646  			},
   647  		},
   648  		resourceDiffTestCase{
   649  			Name: "no change, should error",
   650  			Schema: map[string]*Schema{
   651  				"foo": &Schema{
   652  					Type:     TypeString,
   653  					Optional: true,
   654  					Computed: true,
   655  				},
   656  			},
   657  			State: &terraform.InstanceState{
   658  				Attributes: map[string]string{
   659  					"foo": "bar",
   660  				},
   661  			},
   662  			Config: testConfig(t, map[string]interface{}{
   663  				"foo": "bar",
   664  			}),
   665  			ExpectedError: true,
   666  		},
   667  		resourceDiffTestCase{
   668  			Name: "basic primitive, non-computed key",
   669  			Schema: map[string]*Schema{
   670  				"foo": &Schema{
   671  					Type:     TypeString,
   672  					Required: true,
   673  				},
   674  			},
   675  			State: &terraform.InstanceState{
   676  				Attributes: map[string]string{
   677  					"foo": "bar",
   678  				},
   679  			},
   680  			Config: testConfig(t, map[string]interface{}{
   681  				"foo": "baz",
   682  			}),
   683  			Diff: &terraform.InstanceDiff{
   684  				Attributes: map[string]*terraform.ResourceAttrDiff{
   685  					"foo": &terraform.ResourceAttrDiff{
   686  						Old: "bar",
   687  						New: "baz",
   688  					},
   689  				},
   690  			},
   691  			Key: "foo",
   692  			Expected: &terraform.InstanceDiff{
   693  				Attributes: map[string]*terraform.ResourceAttrDiff{
   694  					"foo": &terraform.ResourceAttrDiff{
   695  						Old:         "bar",
   696  						New:         "baz",
   697  						RequiresNew: true,
   698  					},
   699  				},
   700  			},
   701  		},
   702  		resourceDiffTestCase{
   703  			Name: "nested field",
   704  			Schema: map[string]*Schema{
   705  				"foo": &Schema{
   706  					Type:     TypeList,
   707  					Required: true,
   708  					MaxItems: 1,
   709  					Elem: &Resource{
   710  						Schema: map[string]*Schema{
   711  							"bar": {
   712  								Type:     TypeString,
   713  								Optional: true,
   714  							},
   715  							"baz": {
   716  								Type:     TypeString,
   717  								Optional: true,
   718  							},
   719  						},
   720  					},
   721  				},
   722  			},
   723  			State: &terraform.InstanceState{
   724  				Attributes: map[string]string{
   725  					"foo.#":     "1",
   726  					"foo.0.bar": "abc",
   727  					"foo.0.baz": "xyz",
   728  				},
   729  			},
   730  			Config: testConfig(t, map[string]interface{}{
   731  				"foo": []map[string]interface{}{
   732  					map[string]interface{}{
   733  						"bar": "abcdefg",
   734  						"baz": "changed",
   735  					},
   736  				},
   737  			}),
   738  			Diff: &terraform.InstanceDiff{
   739  				Attributes: map[string]*terraform.ResourceAttrDiff{
   740  					"foo.0.bar": &terraform.ResourceAttrDiff{
   741  						Old: "abc",
   742  						New: "abcdefg",
   743  					},
   744  					"foo.0.baz": &terraform.ResourceAttrDiff{
   745  						Old: "xyz",
   746  						New: "changed",
   747  					},
   748  				},
   749  			},
   750  			Key: "foo.0.baz",
   751  			Expected: &terraform.InstanceDiff{
   752  				Attributes: map[string]*terraform.ResourceAttrDiff{
   753  					"foo.0.bar": &terraform.ResourceAttrDiff{
   754  						Old: "abc",
   755  						New: "abcdefg",
   756  					},
   757  					"foo.0.baz": &terraform.ResourceAttrDiff{
   758  						Old:         "xyz",
   759  						New:         "changed",
   760  						RequiresNew: true,
   761  					},
   762  				},
   763  			},
   764  		},
   765  	}
   766  	for _, tc := range cases {
   767  		t.Run(tc.Name, func(t *testing.T) {
   768  			m := schemaMap(tc.Schema)
   769  			d := newResourceDiff(m, tc.Config, tc.State, tc.Diff)
   770  			err := d.ForceNew(tc.Key)
   771  			switch {
   772  			case err != nil && !tc.ExpectedError:
   773  				t.Fatalf("bad: %s", err)
   774  			case err == nil && tc.ExpectedError:
   775  				t.Fatalf("Expected error, got none")
   776  			case err != nil && tc.ExpectedError:
   777  				return
   778  			}
   779  			for _, k := range d.UpdatedKeys() {
   780  				if err := m.diff(k, m[k], tc.Diff, d, false); err != nil {
   781  					t.Fatalf("bad: %s", err)
   782  				}
   783  			}
   784  			if !reflect.DeepEqual(tc.Expected, tc.Diff) {
   785  				t.Fatalf("Expected %s, got %s", spew.Sdump(tc.Expected), spew.Sdump(tc.Diff))
   786  			}
   787  		})
   788  	}
   789  }
   790  
   791  func TestClear(t *testing.T) {
   792  	cases := []resourceDiffTestCase{
   793  		resourceDiffTestCase{
   794  			Name: "basic primitive diff",
   795  			Schema: map[string]*Schema{
   796  				"foo": &Schema{
   797  					Type:     TypeString,
   798  					Optional: true,
   799  					Computed: true,
   800  				},
   801  			},
   802  			State: &terraform.InstanceState{
   803  				Attributes: map[string]string{
   804  					"foo": "bar",
   805  				},
   806  			},
   807  			Config: testConfig(t, map[string]interface{}{
   808  				"foo": "baz",
   809  			}),
   810  			Diff: &terraform.InstanceDiff{
   811  				Attributes: map[string]*terraform.ResourceAttrDiff{
   812  					"foo": &terraform.ResourceAttrDiff{
   813  						Old: "bar",
   814  						New: "baz",
   815  					},
   816  				},
   817  			},
   818  			Key:      "foo",
   819  			Expected: &terraform.InstanceDiff{Attributes: map[string]*terraform.ResourceAttrDiff{}},
   820  		},
   821  		resourceDiffTestCase{
   822  			Name: "non-computed key, should error",
   823  			Schema: map[string]*Schema{
   824  				"foo": &Schema{
   825  					Type:     TypeString,
   826  					Required: true,
   827  				},
   828  			},
   829  			State: &terraform.InstanceState{
   830  				Attributes: map[string]string{
   831  					"foo": "bar",
   832  				},
   833  			},
   834  			Config: testConfig(t, map[string]interface{}{
   835  				"foo": "baz",
   836  			}),
   837  			Diff: &terraform.InstanceDiff{
   838  				Attributes: map[string]*terraform.ResourceAttrDiff{
   839  					"foo": &terraform.ResourceAttrDiff{
   840  						Old: "bar",
   841  						New: "baz",
   842  					},
   843  				},
   844  			},
   845  			Key:           "foo",
   846  			ExpectedError: true,
   847  		},
   848  		resourceDiffTestCase{
   849  			Name: "multi-value, one removed",
   850  			Schema: map[string]*Schema{
   851  				"foo": &Schema{
   852  					Type:     TypeString,
   853  					Optional: true,
   854  					Computed: true,
   855  				},
   856  				"one": &Schema{
   857  					Type:     TypeString,
   858  					Optional: true,
   859  					Computed: true,
   860  				},
   861  			},
   862  			State: &terraform.InstanceState{
   863  				Attributes: map[string]string{
   864  					"foo": "bar",
   865  					"one": "two",
   866  				},
   867  			},
   868  			Config: testConfig(t, map[string]interface{}{
   869  				"foo": "baz",
   870  				"one": "three",
   871  			}),
   872  			Diff: &terraform.InstanceDiff{
   873  				Attributes: map[string]*terraform.ResourceAttrDiff{
   874  					"foo": &terraform.ResourceAttrDiff{
   875  						Old: "bar",
   876  						New: "baz",
   877  					},
   878  					"one": &terraform.ResourceAttrDiff{
   879  						Old: "two",
   880  						New: "three",
   881  					},
   882  				},
   883  			},
   884  			Key: "one",
   885  			Expected: &terraform.InstanceDiff{
   886  				Attributes: map[string]*terraform.ResourceAttrDiff{
   887  					"foo": &terraform.ResourceAttrDiff{
   888  						Old: "bar",
   889  						New: "baz",
   890  					},
   891  				},
   892  			},
   893  		},
   894  	}
   895  	for _, tc := range cases {
   896  		t.Run(tc.Name, func(t *testing.T) {
   897  			m := schemaMap(tc.Schema)
   898  			d := newResourceDiff(m, tc.Config, tc.State, tc.Diff)
   899  			err := d.Clear(tc.Key)
   900  			switch {
   901  			case err != nil && !tc.ExpectedError:
   902  				t.Fatalf("bad: %s", err)
   903  			case err == nil && tc.ExpectedError:
   904  				t.Fatalf("Expected error, got none")
   905  			case err != nil && tc.ExpectedError:
   906  				return
   907  			}
   908  			for _, k := range d.UpdatedKeys() {
   909  				if err := m.diff(k, m[k], tc.Diff, d, false); err != nil {
   910  					t.Fatalf("bad: %s", err)
   911  				}
   912  			}
   913  			if !reflect.DeepEqual(tc.Expected, tc.Diff) {
   914  				t.Fatalf("Expected %s, got %s", spew.Sdump(tc.Expected), spew.Sdump(tc.Diff))
   915  			}
   916  		})
   917  	}
   918  }
   919  
   920  func TestGetChangedKeysPrefix(t *testing.T) {
   921  	cases := []resourceDiffTestCase{
   922  		resourceDiffTestCase{
   923  			Name: "basic primitive diff",
   924  			Schema: map[string]*Schema{
   925  				"foo": &Schema{
   926  					Type:     TypeString,
   927  					Optional: true,
   928  					Computed: true,
   929  				},
   930  			},
   931  			State: &terraform.InstanceState{
   932  				Attributes: map[string]string{
   933  					"foo": "bar",
   934  				},
   935  			},
   936  			Config: testConfig(t, map[string]interface{}{
   937  				"foo": "baz",
   938  			}),
   939  			Diff: &terraform.InstanceDiff{
   940  				Attributes: map[string]*terraform.ResourceAttrDiff{
   941  					"foo": &terraform.ResourceAttrDiff{
   942  						Old: "bar",
   943  						New: "baz",
   944  					},
   945  				},
   946  			},
   947  			Key: "foo",
   948  			ExpectedKeys: []string{
   949  				"foo",
   950  			},
   951  		},
   952  		resourceDiffTestCase{
   953  			Name: "nested field filtering",
   954  			Schema: map[string]*Schema{
   955  				"testfield": &Schema{
   956  					Type:     TypeString,
   957  					Required: true,
   958  				},
   959  				"foo": &Schema{
   960  					Type:     TypeList,
   961  					Required: true,
   962  					MaxItems: 1,
   963  					Elem: &Resource{
   964  						Schema: map[string]*Schema{
   965  							"bar": {
   966  								Type:     TypeString,
   967  								Optional: true,
   968  							},
   969  							"baz": {
   970  								Type:     TypeString,
   971  								Optional: true,
   972  							},
   973  						},
   974  					},
   975  				},
   976  			},
   977  			State: &terraform.InstanceState{
   978  				Attributes: map[string]string{
   979  					"testfield": "blablah",
   980  					"foo.#":     "1",
   981  					"foo.0.bar": "abc",
   982  					"foo.0.baz": "xyz",
   983  				},
   984  			},
   985  			Config: testConfig(t, map[string]interface{}{
   986  				"testfield": "modified",
   987  				"foo": []map[string]interface{}{
   988  					map[string]interface{}{
   989  						"bar": "abcdefg",
   990  						"baz": "changed",
   991  					},
   992  				},
   993  			}),
   994  			Diff: &terraform.InstanceDiff{
   995  				Attributes: map[string]*terraform.ResourceAttrDiff{
   996  					"testfield": &terraform.ResourceAttrDiff{
   997  						Old: "blablah",
   998  						New: "modified",
   999  					},
  1000  					"foo.0.bar": &terraform.ResourceAttrDiff{
  1001  						Old: "abc",
  1002  						New: "abcdefg",
  1003  					},
  1004  					"foo.0.baz": &terraform.ResourceAttrDiff{
  1005  						Old: "xyz",
  1006  						New: "changed",
  1007  					},
  1008  				},
  1009  			},
  1010  			Key: "foo",
  1011  			ExpectedKeys: []string{
  1012  				"foo.0.bar",
  1013  				"foo.0.baz",
  1014  			},
  1015  		},
  1016  	}
  1017  	for _, tc := range cases {
  1018  		t.Run(tc.Name, func(t *testing.T) {
  1019  			m := schemaMap(tc.Schema)
  1020  			d := newResourceDiff(m, tc.Config, tc.State, tc.Diff)
  1021  			keys := d.GetChangedKeysPrefix(tc.Key)
  1022  
  1023  			for _, k := range d.UpdatedKeys() {
  1024  				if err := m.diff(k, m[k], tc.Diff, d, false); err != nil {
  1025  					t.Fatalf("bad: %s", err)
  1026  				}
  1027  			}
  1028  
  1029  			sort.Strings(keys)
  1030  
  1031  			if !reflect.DeepEqual(tc.ExpectedKeys, keys) {
  1032  				t.Fatalf("Expected %s, got %s", spew.Sdump(tc.ExpectedKeys), spew.Sdump(keys))
  1033  			}
  1034  		})
  1035  	}
  1036  }