github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/internal/plans/objchange/plan_valid_test.go (about)

     1  package objchange
     2  
     3  import (
     4  	"testing"
     5  
     6  	"github.com/apparentlymart/go-dump/dump"
     7  	"github.com/zclconf/go-cty/cty"
     8  
     9  	"github.com/hashicorp/terraform/internal/configs/configschema"
    10  	"github.com/hashicorp/terraform/internal/tfdiags"
    11  )
    12  
    13  func TestAssertPlanValid(t *testing.T) {
    14  	tests := map[string]struct {
    15  		Schema   *configschema.Block
    16  		Prior    cty.Value
    17  		Config   cty.Value
    18  		Planned  cty.Value
    19  		WantErrs []string
    20  	}{
    21  		"all empty": {
    22  			&configschema.Block{},
    23  			cty.EmptyObjectVal,
    24  			cty.EmptyObjectVal,
    25  			cty.EmptyObjectVal,
    26  			nil,
    27  		},
    28  		"no computed, all match": {
    29  			&configschema.Block{
    30  				Attributes: map[string]*configschema.Attribute{
    31  					"a": {
    32  						Type:     cty.String,
    33  						Optional: true,
    34  					},
    35  				},
    36  				BlockTypes: map[string]*configschema.NestedBlock{
    37  					"b": {
    38  						Nesting: configschema.NestingList,
    39  						Block: configschema.Block{
    40  							Attributes: map[string]*configschema.Attribute{
    41  								"c": {
    42  									Type:     cty.String,
    43  									Optional: true,
    44  								},
    45  							},
    46  						},
    47  					},
    48  				},
    49  			},
    50  			cty.ObjectVal(map[string]cty.Value{
    51  				"a": cty.StringVal("a value"),
    52  				"b": cty.ListVal([]cty.Value{
    53  					cty.ObjectVal(map[string]cty.Value{
    54  						"c": cty.StringVal("c value"),
    55  					}),
    56  				}),
    57  			}),
    58  			cty.ObjectVal(map[string]cty.Value{
    59  				"a": cty.StringVal("a value"),
    60  				"b": cty.ListVal([]cty.Value{
    61  					cty.ObjectVal(map[string]cty.Value{
    62  						"c": cty.StringVal("c value"),
    63  					}),
    64  				}),
    65  			}),
    66  			cty.ObjectVal(map[string]cty.Value{
    67  				"a": cty.StringVal("a value"),
    68  				"b": cty.ListVal([]cty.Value{
    69  					cty.ObjectVal(map[string]cty.Value{
    70  						"c": cty.StringVal("c value"),
    71  					}),
    72  				}),
    73  			}),
    74  			nil,
    75  		},
    76  		"no computed, plan matches, no prior": {
    77  			&configschema.Block{
    78  				Attributes: map[string]*configschema.Attribute{
    79  					"a": {
    80  						Type:     cty.String,
    81  						Optional: true,
    82  					},
    83  				},
    84  				BlockTypes: map[string]*configschema.NestedBlock{
    85  					"b": {
    86  						Nesting: configschema.NestingList,
    87  						Block: configschema.Block{
    88  							Attributes: map[string]*configschema.Attribute{
    89  								"c": {
    90  									Type:     cty.String,
    91  									Optional: true,
    92  								},
    93  							},
    94  						},
    95  					},
    96  				},
    97  			},
    98  			cty.NullVal(cty.Object(map[string]cty.Type{
    99  				"a": cty.String,
   100  				"b": cty.List(cty.Object(map[string]cty.Type{
   101  					"c": cty.String,
   102  				})),
   103  			})),
   104  			cty.ObjectVal(map[string]cty.Value{
   105  				"a": cty.StringVal("a value"),
   106  				"b": cty.ListVal([]cty.Value{
   107  					cty.ObjectVal(map[string]cty.Value{
   108  						"c": cty.StringVal("c value"),
   109  					}),
   110  				}),
   111  			}),
   112  			cty.ObjectVal(map[string]cty.Value{
   113  				"a": cty.StringVal("a value"),
   114  				"b": cty.ListVal([]cty.Value{
   115  					cty.ObjectVal(map[string]cty.Value{
   116  						"c": cty.StringVal("c value"),
   117  					}),
   118  				}),
   119  			}),
   120  			nil,
   121  		},
   122  		"no computed, invalid change in plan": {
   123  			&configschema.Block{
   124  				Attributes: map[string]*configschema.Attribute{
   125  					"a": {
   126  						Type:     cty.String,
   127  						Optional: true,
   128  					},
   129  				},
   130  				BlockTypes: map[string]*configschema.NestedBlock{
   131  					"b": {
   132  						Nesting: configschema.NestingList,
   133  						Block: configschema.Block{
   134  							Attributes: map[string]*configschema.Attribute{
   135  								"c": {
   136  									Type:     cty.String,
   137  									Optional: true,
   138  								},
   139  							},
   140  						},
   141  					},
   142  				},
   143  			},
   144  			cty.NullVal(cty.Object(map[string]cty.Type{
   145  				"a": cty.String,
   146  				"b": cty.List(cty.Object(map[string]cty.Type{
   147  					"c": cty.String,
   148  				})),
   149  			})),
   150  			cty.ObjectVal(map[string]cty.Value{
   151  				"a": cty.StringVal("a value"),
   152  				"b": cty.ListVal([]cty.Value{
   153  					cty.ObjectVal(map[string]cty.Value{
   154  						"c": cty.StringVal("c value"),
   155  					}),
   156  				}),
   157  			}),
   158  			cty.ObjectVal(map[string]cty.Value{
   159  				"a": cty.StringVal("a value"),
   160  				"b": cty.ListVal([]cty.Value{
   161  					cty.ObjectVal(map[string]cty.Value{
   162  						"c": cty.StringVal("new c value"),
   163  					}),
   164  				}),
   165  			}),
   166  			[]string{
   167  				`.b[0].c: planned value cty.StringVal("new c value") does not match config value cty.StringVal("c value")`,
   168  			},
   169  		},
   170  		"no computed, invalid change in plan sensitive": {
   171  			&configschema.Block{
   172  				Attributes: map[string]*configschema.Attribute{
   173  					"a": {
   174  						Type:     cty.String,
   175  						Optional: true,
   176  					},
   177  				},
   178  				BlockTypes: map[string]*configschema.NestedBlock{
   179  					"b": {
   180  						Nesting: configschema.NestingList,
   181  						Block: configschema.Block{
   182  							Attributes: map[string]*configschema.Attribute{
   183  								"c": {
   184  									Type:      cty.String,
   185  									Optional:  true,
   186  									Sensitive: true,
   187  								},
   188  							},
   189  						},
   190  					},
   191  				},
   192  			},
   193  			cty.NullVal(cty.Object(map[string]cty.Type{
   194  				"a": cty.String,
   195  				"b": cty.List(cty.Object(map[string]cty.Type{
   196  					"c": cty.String,
   197  				})),
   198  			})),
   199  			cty.ObjectVal(map[string]cty.Value{
   200  				"a": cty.StringVal("a value"),
   201  				"b": cty.ListVal([]cty.Value{
   202  					cty.ObjectVal(map[string]cty.Value{
   203  						"c": cty.StringVal("c value"),
   204  					}),
   205  				}),
   206  			}),
   207  			cty.ObjectVal(map[string]cty.Value{
   208  				"a": cty.StringVal("a value"),
   209  				"b": cty.ListVal([]cty.Value{
   210  					cty.ObjectVal(map[string]cty.Value{
   211  						"c": cty.StringVal("new c value"),
   212  					}),
   213  				}),
   214  			}),
   215  			[]string{
   216  				`.b[0].c: sensitive planned value does not match config value`,
   217  			},
   218  		},
   219  		"no computed, diff suppression in plan": {
   220  			&configschema.Block{
   221  				Attributes: map[string]*configschema.Attribute{
   222  					"a": {
   223  						Type:     cty.String,
   224  						Optional: true,
   225  					},
   226  				},
   227  				BlockTypes: map[string]*configschema.NestedBlock{
   228  					"b": {
   229  						Nesting: configschema.NestingList,
   230  						Block: configschema.Block{
   231  							Attributes: map[string]*configschema.Attribute{
   232  								"c": {
   233  									Type:     cty.String,
   234  									Optional: true,
   235  								},
   236  							},
   237  						},
   238  					},
   239  				},
   240  			},
   241  			cty.ObjectVal(map[string]cty.Value{
   242  				"a": cty.StringVal("a value"),
   243  				"b": cty.ListVal([]cty.Value{
   244  					cty.ObjectVal(map[string]cty.Value{
   245  						"c": cty.StringVal("c value"),
   246  					}),
   247  				}),
   248  			}),
   249  			cty.ObjectVal(map[string]cty.Value{
   250  				"a": cty.StringVal("a value"),
   251  				"b": cty.ListVal([]cty.Value{
   252  					cty.ObjectVal(map[string]cty.Value{
   253  						"c": cty.StringVal("new c value"),
   254  					}),
   255  				}),
   256  			}),
   257  			cty.ObjectVal(map[string]cty.Value{
   258  				"a": cty.StringVal("a value"),
   259  				"b": cty.ListVal([]cty.Value{
   260  					cty.ObjectVal(map[string]cty.Value{
   261  						"c": cty.StringVal("c value"), // plan uses value from prior object
   262  					}),
   263  				}),
   264  			}),
   265  			nil,
   266  		},
   267  		"no computed, all null": {
   268  			&configschema.Block{
   269  				Attributes: map[string]*configschema.Attribute{
   270  					"a": {
   271  						Type:     cty.String,
   272  						Optional: true,
   273  					},
   274  				},
   275  				BlockTypes: map[string]*configschema.NestedBlock{
   276  					"b": {
   277  						Nesting: configschema.NestingList,
   278  						Block: configschema.Block{
   279  							Attributes: map[string]*configschema.Attribute{
   280  								"c": {
   281  									Type:     cty.String,
   282  									Optional: true,
   283  								},
   284  							},
   285  						},
   286  					},
   287  				},
   288  			},
   289  			cty.ObjectVal(map[string]cty.Value{
   290  				"a": cty.NullVal(cty.String),
   291  				"b": cty.ListVal([]cty.Value{
   292  					cty.ObjectVal(map[string]cty.Value{
   293  						"c": cty.NullVal(cty.String),
   294  					}),
   295  				}),
   296  			}),
   297  			cty.ObjectVal(map[string]cty.Value{
   298  				"a": cty.NullVal(cty.String),
   299  				"b": cty.ListVal([]cty.Value{
   300  					cty.ObjectVal(map[string]cty.Value{
   301  						"c": cty.NullVal(cty.String),
   302  					}),
   303  				}),
   304  			}),
   305  			cty.ObjectVal(map[string]cty.Value{
   306  				"a": cty.NullVal(cty.String),
   307  				"b": cty.ListVal([]cty.Value{
   308  					cty.ObjectVal(map[string]cty.Value{
   309  						"c": cty.NullVal(cty.String),
   310  					}),
   311  				}),
   312  			}),
   313  			nil,
   314  		},
   315  		"nested map, normal update": {
   316  			&configschema.Block{
   317  				BlockTypes: map[string]*configschema.NestedBlock{
   318  					"b": {
   319  						Nesting: configschema.NestingMap,
   320  						Block: configschema.Block{
   321  							Attributes: map[string]*configschema.Attribute{
   322  								"c": {
   323  									Type:     cty.String,
   324  									Optional: true,
   325  								},
   326  							},
   327  						},
   328  					},
   329  				},
   330  			},
   331  			cty.ObjectVal(map[string]cty.Value{
   332  				"b": cty.MapVal(map[string]cty.Value{
   333  					"boop": cty.ObjectVal(map[string]cty.Value{
   334  						"c": cty.StringVal("hello"),
   335  					}),
   336  				}),
   337  			}),
   338  			cty.ObjectVal(map[string]cty.Value{
   339  				"b": cty.MapVal(map[string]cty.Value{
   340  					"boop": cty.ObjectVal(map[string]cty.Value{
   341  						"c": cty.StringVal("howdy"),
   342  					}),
   343  				}),
   344  			}),
   345  			cty.ObjectVal(map[string]cty.Value{
   346  				"b": cty.MapVal(map[string]cty.Value{
   347  					"boop": cty.ObjectVal(map[string]cty.Value{
   348  						"c": cty.StringVal("howdy"),
   349  					}),
   350  				}),
   351  			}),
   352  			nil,
   353  		},
   354  
   355  		// Nested block collections are never null
   356  		"nested list, null in plan": {
   357  			&configschema.Block{
   358  				BlockTypes: map[string]*configschema.NestedBlock{
   359  					"b": {
   360  						Nesting: configschema.NestingList,
   361  						Block: configschema.Block{
   362  							Attributes: map[string]*configschema.Attribute{
   363  								"c": {
   364  									Type:     cty.String,
   365  									Optional: true,
   366  								},
   367  							},
   368  						},
   369  					},
   370  				},
   371  			},
   372  			cty.NullVal(cty.Object(map[string]cty.Type{
   373  				"b": cty.List(cty.Object(map[string]cty.Type{
   374  					"c": cty.String,
   375  				})),
   376  			})),
   377  			cty.ObjectVal(map[string]cty.Value{
   378  				"b": cty.ListValEmpty(cty.Object(map[string]cty.Type{
   379  					"c": cty.String,
   380  				})),
   381  			}),
   382  			cty.ObjectVal(map[string]cty.Value{
   383  				"b": cty.NullVal(cty.List(cty.Object(map[string]cty.Type{
   384  					"c": cty.String,
   385  				}))),
   386  			}),
   387  			[]string{
   388  				`.b: attribute representing a list of nested blocks must be empty to indicate no blocks, not null`,
   389  			},
   390  		},
   391  
   392  		// but don't panic on a null list just in case
   393  		"nested list, null in config": {
   394  			&configschema.Block{
   395  				BlockTypes: map[string]*configschema.NestedBlock{
   396  					"b": {
   397  						Nesting: configschema.NestingList,
   398  						Block: configschema.Block{
   399  							Attributes: map[string]*configschema.Attribute{
   400  								"c": {
   401  									Type:     cty.String,
   402  									Optional: true,
   403  								},
   404  							},
   405  						},
   406  					},
   407  				},
   408  			},
   409  			cty.ObjectVal(map[string]cty.Value{
   410  				"b": cty.ListValEmpty(cty.Object(map[string]cty.Type{
   411  					"c": cty.String,
   412  				})),
   413  			}),
   414  			cty.ObjectVal(map[string]cty.Value{
   415  				"b": cty.NullVal(cty.List(cty.Object(map[string]cty.Type{
   416  					"c": cty.String,
   417  				}))),
   418  			}),
   419  			cty.ObjectVal(map[string]cty.Value{
   420  				"b": cty.ListValEmpty(cty.Object(map[string]cty.Type{
   421  					"c": cty.String,
   422  				})),
   423  			}),
   424  			nil,
   425  		},
   426  
   427  		// blocks can be unknown when using dynamic
   428  		"nested list, unknown nested dynamic": {
   429  			&configschema.Block{
   430  				BlockTypes: map[string]*configschema.NestedBlock{
   431  					"a": {
   432  						Nesting: configschema.NestingList,
   433  						Block: configschema.Block{
   434  							BlockTypes: map[string]*configschema.NestedBlock{
   435  								"b": {
   436  									Nesting: configschema.NestingList,
   437  									Block: configschema.Block{
   438  										Attributes: map[string]*configschema.Attribute{
   439  											"c": {
   440  												Type:     cty.String,
   441  												Optional: true,
   442  											},
   443  											"computed": {
   444  												Type:     cty.String,
   445  												Computed: true,
   446  											},
   447  										},
   448  									},
   449  								},
   450  							},
   451  						},
   452  					},
   453  				},
   454  			},
   455  
   456  			cty.ObjectVal(map[string]cty.Value{
   457  				"a": cty.ListVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{
   458  					"computed": cty.NullVal(cty.String),
   459  					"b": cty.ListVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{
   460  						"c": cty.StringVal("x"),
   461  					})}),
   462  				})}),
   463  			}),
   464  			cty.ObjectVal(map[string]cty.Value{
   465  				"a": cty.ListVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{
   466  					"b": cty.UnknownVal(cty.List(cty.Object(map[string]cty.Type{
   467  						"c":        cty.String,
   468  						"computed": cty.String,
   469  					}))),
   470  				})}),
   471  			}),
   472  			cty.ObjectVal(map[string]cty.Value{
   473  				"a": cty.ListVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{
   474  					"b": cty.UnknownVal(cty.List(cty.Object(map[string]cty.Type{
   475  						"c":        cty.String,
   476  						"computed": cty.String,
   477  					}))),
   478  				})}),
   479  			}),
   480  			[]string{},
   481  		},
   482  
   483  		"nested set, unknown dynamic cannot be planned": {
   484  			&configschema.Block{
   485  				Attributes: map[string]*configschema.Attribute{
   486  					"computed": {
   487  						Type:     cty.String,
   488  						Computed: true,
   489  					},
   490  				},
   491  				BlockTypes: map[string]*configschema.NestedBlock{
   492  					"b": {
   493  						Nesting: configschema.NestingSet,
   494  						Block: configschema.Block{
   495  							Attributes: map[string]*configschema.Attribute{
   496  								"c": {
   497  									Type:     cty.String,
   498  									Optional: true,
   499  								},
   500  							},
   501  						},
   502  					},
   503  				},
   504  			},
   505  
   506  			cty.ObjectVal(map[string]cty.Value{
   507  				"computed": cty.NullVal(cty.String),
   508  				"b": cty.SetVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{
   509  					"c": cty.StringVal("x"),
   510  				})}),
   511  			}),
   512  			cty.ObjectVal(map[string]cty.Value{
   513  				"computed": cty.NullVal(cty.String),
   514  				"b": cty.UnknownVal(cty.Set(cty.Object(map[string]cty.Type{
   515  					"c": cty.String,
   516  				}))),
   517  			}),
   518  			cty.ObjectVal(map[string]cty.Value{
   519  				"computed": cty.StringVal("default"),
   520  				"b": cty.SetVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{
   521  					"c": cty.StringVal("oops"),
   522  				})}),
   523  			}),
   524  
   525  			[]string{
   526  				`.b: planned value cty.SetVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{"c":cty.StringVal("oops")})}) for unknown dynamic block`,
   527  			},
   528  		},
   529  
   530  		"nested set, null in plan": {
   531  			&configschema.Block{
   532  				BlockTypes: map[string]*configschema.NestedBlock{
   533  					"b": {
   534  						Nesting: configschema.NestingSet,
   535  						Block: configschema.Block{
   536  							Attributes: map[string]*configschema.Attribute{
   537  								"c": {
   538  									Type:     cty.String,
   539  									Optional: true,
   540  								},
   541  							},
   542  						},
   543  					},
   544  				},
   545  			},
   546  			cty.NullVal(cty.Object(map[string]cty.Type{
   547  				"b": cty.Set(cty.Object(map[string]cty.Type{
   548  					"c": cty.String,
   549  				})),
   550  			})),
   551  			cty.ObjectVal(map[string]cty.Value{
   552  				"b": cty.SetValEmpty(cty.Object(map[string]cty.Type{
   553  					"c": cty.String,
   554  				})),
   555  			}),
   556  			cty.ObjectVal(map[string]cty.Value{
   557  				"b": cty.NullVal(cty.Set(cty.Object(map[string]cty.Type{
   558  					"c": cty.String,
   559  				}))),
   560  			}),
   561  			[]string{
   562  				`.b: attribute representing a set of nested blocks must be empty to indicate no blocks, not null`,
   563  			},
   564  		},
   565  		"nested map, null in plan": {
   566  			&configschema.Block{
   567  				BlockTypes: map[string]*configschema.NestedBlock{
   568  					"b": {
   569  						Nesting: configschema.NestingMap,
   570  						Block: configschema.Block{
   571  							Attributes: map[string]*configschema.Attribute{
   572  								"c": {
   573  									Type:     cty.String,
   574  									Optional: true,
   575  								},
   576  							},
   577  						},
   578  					},
   579  				},
   580  			},
   581  			cty.NullVal(cty.Object(map[string]cty.Type{
   582  				"b": cty.Map(cty.Object(map[string]cty.Type{
   583  					"c": cty.String,
   584  				})),
   585  			})),
   586  			cty.ObjectVal(map[string]cty.Value{
   587  				"b": cty.MapValEmpty(cty.Object(map[string]cty.Type{
   588  					"c": cty.String,
   589  				})),
   590  			}),
   591  			cty.ObjectVal(map[string]cty.Value{
   592  				"b": cty.NullVal(cty.Map(cty.Object(map[string]cty.Type{
   593  					"c": cty.String,
   594  				}))),
   595  			}),
   596  			[]string{
   597  				`.b: attribute representing a map of nested blocks must be empty to indicate no blocks, not null`,
   598  			},
   599  		},
   600  
   601  		// We don't actually do any validation for nested set blocks, and so
   602  		// the remaining cases here are just intending to ensure we don't
   603  		// inadvertently start generating errors incorrectly in future.
   604  		"nested set, no computed, no changes": {
   605  			&configschema.Block{
   606  				BlockTypes: map[string]*configschema.NestedBlock{
   607  					"b": {
   608  						Nesting: configschema.NestingSet,
   609  						Block: configschema.Block{
   610  							Attributes: map[string]*configschema.Attribute{
   611  								"c": {
   612  									Type:     cty.String,
   613  									Optional: true,
   614  								},
   615  							},
   616  						},
   617  					},
   618  				},
   619  			},
   620  			cty.ObjectVal(map[string]cty.Value{
   621  				"b": cty.SetVal([]cty.Value{
   622  					cty.ObjectVal(map[string]cty.Value{
   623  						"c": cty.StringVal("c value"),
   624  					}),
   625  				}),
   626  			}),
   627  			cty.ObjectVal(map[string]cty.Value{
   628  				"b": cty.SetVal([]cty.Value{
   629  					cty.ObjectVal(map[string]cty.Value{
   630  						"c": cty.StringVal("c value"),
   631  					}),
   632  				}),
   633  			}),
   634  			cty.ObjectVal(map[string]cty.Value{
   635  				"b": cty.SetVal([]cty.Value{
   636  					cty.ObjectVal(map[string]cty.Value{
   637  						"c": cty.StringVal("c value"),
   638  					}),
   639  				}),
   640  			}),
   641  			nil,
   642  		},
   643  		"nested set, no computed, invalid change in plan": {
   644  			&configschema.Block{
   645  				BlockTypes: map[string]*configschema.NestedBlock{
   646  					"b": {
   647  						Nesting: configschema.NestingSet,
   648  						Block: configschema.Block{
   649  							Attributes: map[string]*configschema.Attribute{
   650  								"c": {
   651  									Type:     cty.String,
   652  									Optional: true,
   653  								},
   654  							},
   655  						},
   656  					},
   657  				},
   658  			},
   659  			cty.ObjectVal(map[string]cty.Value{
   660  				"b": cty.SetVal([]cty.Value{
   661  					cty.ObjectVal(map[string]cty.Value{
   662  						"c": cty.StringVal("c value"),
   663  					}),
   664  				}),
   665  			}),
   666  			cty.ObjectVal(map[string]cty.Value{
   667  				"b": cty.SetVal([]cty.Value{
   668  					cty.ObjectVal(map[string]cty.Value{
   669  						"c": cty.StringVal("c value"),
   670  					}),
   671  				}),
   672  			}),
   673  			cty.ObjectVal(map[string]cty.Value{
   674  				"b": cty.SetVal([]cty.Value{
   675  					cty.ObjectVal(map[string]cty.Value{
   676  						"c": cty.StringVal("new c value"), // matches neither prior nor config
   677  					}),
   678  				}),
   679  			}),
   680  			nil,
   681  		},
   682  		"nested set, no computed, diff suppressed": {
   683  			&configschema.Block{
   684  				BlockTypes: map[string]*configschema.NestedBlock{
   685  					"b": {
   686  						Nesting: configschema.NestingSet,
   687  						Block: configschema.Block{
   688  							Attributes: map[string]*configschema.Attribute{
   689  								"c": {
   690  									Type:     cty.String,
   691  									Optional: true,
   692  								},
   693  							},
   694  						},
   695  					},
   696  				},
   697  			},
   698  			cty.ObjectVal(map[string]cty.Value{
   699  				"b": cty.SetVal([]cty.Value{
   700  					cty.ObjectVal(map[string]cty.Value{
   701  						"c": cty.StringVal("c value"),
   702  					}),
   703  				}),
   704  			}),
   705  			cty.ObjectVal(map[string]cty.Value{
   706  				"b": cty.SetVal([]cty.Value{
   707  					cty.ObjectVal(map[string]cty.Value{
   708  						"c": cty.StringVal("new c value"),
   709  					}),
   710  				}),
   711  			}),
   712  			cty.ObjectVal(map[string]cty.Value{
   713  				"b": cty.SetVal([]cty.Value{
   714  					cty.ObjectVal(map[string]cty.Value{
   715  						"c": cty.StringVal("c value"), // plan uses value from prior object
   716  					}),
   717  				}),
   718  			}),
   719  			nil,
   720  		},
   721  
   722  		// Attributes with NestedTypes
   723  		"NestedType attr, no computed, all match": {
   724  			&configschema.Block{
   725  				Attributes: map[string]*configschema.Attribute{
   726  					"a": {
   727  						NestedType: &configschema.Object{
   728  							Nesting: configschema.NestingList,
   729  							Attributes: map[string]*configschema.Attribute{
   730  								"b": {
   731  									Type:     cty.String,
   732  									Optional: true,
   733  								},
   734  							},
   735  						},
   736  						Optional: true,
   737  					},
   738  				},
   739  			},
   740  			cty.ObjectVal(map[string]cty.Value{
   741  				"a": cty.ListVal([]cty.Value{
   742  					cty.ObjectVal(map[string]cty.Value{
   743  						"b": cty.StringVal("b value"),
   744  					}),
   745  				}),
   746  			}),
   747  			cty.ObjectVal(map[string]cty.Value{
   748  				"a": cty.ListVal([]cty.Value{
   749  					cty.ObjectVal(map[string]cty.Value{
   750  						"b": cty.StringVal("b value"),
   751  					}),
   752  				}),
   753  			}),
   754  			cty.ObjectVal(map[string]cty.Value{
   755  				"a": cty.ListVal([]cty.Value{
   756  					cty.ObjectVal(map[string]cty.Value{
   757  						"b": cty.StringVal("b value"),
   758  					}),
   759  				}),
   760  			}),
   761  			nil,
   762  		},
   763  		"NestedType attr, no computed, plan matches, no prior": {
   764  			&configschema.Block{
   765  				Attributes: map[string]*configschema.Attribute{
   766  					"a": {
   767  						NestedType: &configschema.Object{
   768  							Nesting: configschema.NestingList,
   769  							Attributes: map[string]*configschema.Attribute{
   770  								"b": {
   771  									Type:     cty.String,
   772  									Optional: true,
   773  								},
   774  							},
   775  						},
   776  						Optional: true,
   777  					},
   778  				},
   779  			},
   780  			cty.NullVal(cty.Object(map[string]cty.Type{
   781  				"a": cty.List(cty.Object(map[string]cty.Type{
   782  					"b": cty.String,
   783  				})),
   784  			})),
   785  			cty.ObjectVal(map[string]cty.Value{
   786  				"a": cty.ListVal([]cty.Value{
   787  					cty.ObjectVal(map[string]cty.Value{
   788  						"b": cty.StringVal("c value"),
   789  					}),
   790  				}),
   791  			}),
   792  			cty.ObjectVal(map[string]cty.Value{
   793  				"a": cty.ListVal([]cty.Value{
   794  					cty.ObjectVal(map[string]cty.Value{
   795  						"b": cty.StringVal("c value"),
   796  					}),
   797  				}),
   798  			}),
   799  			nil,
   800  		},
   801  		"NestedType, no computed, invalid change in plan": {
   802  			&configschema.Block{
   803  				Attributes: map[string]*configschema.Attribute{
   804  					"a": {
   805  						NestedType: &configschema.Object{
   806  							Nesting: configschema.NestingList,
   807  							Attributes: map[string]*configschema.Attribute{
   808  								"b": {
   809  									Type:     cty.String,
   810  									Optional: true,
   811  								},
   812  							},
   813  						},
   814  						Optional: true,
   815  					},
   816  				},
   817  			},
   818  			cty.NullVal(cty.Object(map[string]cty.Type{
   819  				"a": cty.List(cty.Object(map[string]cty.Type{
   820  					"b": cty.String,
   821  				})),
   822  			})),
   823  			cty.ObjectVal(map[string]cty.Value{
   824  				"a": cty.ListVal([]cty.Value{
   825  					cty.ObjectVal(map[string]cty.Value{
   826  						"b": cty.StringVal("c value"),
   827  					}),
   828  				}),
   829  			}),
   830  			cty.ObjectVal(map[string]cty.Value{
   831  				"a": cty.ListVal([]cty.Value{
   832  					cty.ObjectVal(map[string]cty.Value{
   833  						"b": cty.StringVal("new c value"),
   834  					}),
   835  				}),
   836  			}),
   837  			[]string{
   838  				`.a[0].b: planned value cty.StringVal("new c value") does not match config value cty.StringVal("c value")`,
   839  			},
   840  		},
   841  		"NestedType attr, no computed, invalid change in plan sensitive": {
   842  			&configschema.Block{
   843  				Attributes: map[string]*configschema.Attribute{
   844  					"a": {
   845  						NestedType: &configschema.Object{
   846  							Nesting: configschema.NestingList,
   847  							Attributes: map[string]*configschema.Attribute{
   848  								"b": {
   849  									Type:      cty.String,
   850  									Optional:  true,
   851  									Sensitive: true,
   852  								},
   853  							},
   854  						},
   855  						Optional: true,
   856  					},
   857  				},
   858  			},
   859  			cty.NullVal(cty.Object(map[string]cty.Type{
   860  				"a": cty.List(cty.Object(map[string]cty.Type{
   861  					"b": cty.String,
   862  				})),
   863  			})),
   864  			cty.ObjectVal(map[string]cty.Value{
   865  				"a": cty.ListVal([]cty.Value{
   866  					cty.ObjectVal(map[string]cty.Value{
   867  						"b": cty.StringVal("b value"),
   868  					}),
   869  				}),
   870  			}),
   871  			cty.ObjectVal(map[string]cty.Value{
   872  				"a": cty.ListVal([]cty.Value{
   873  					cty.ObjectVal(map[string]cty.Value{
   874  						"b": cty.StringVal("new b value"),
   875  					}),
   876  				}),
   877  			}),
   878  			[]string{
   879  				`.a[0].b: sensitive planned value does not match config value`,
   880  			},
   881  		},
   882  		"NestedType attr, no computed, diff suppression in plan": {
   883  			&configschema.Block{
   884  				Attributes: map[string]*configschema.Attribute{
   885  					"a": {
   886  						NestedType: &configschema.Object{
   887  							Nesting: configschema.NestingList,
   888  							Attributes: map[string]*configschema.Attribute{
   889  								"b": {
   890  									Type:     cty.String,
   891  									Optional: true,
   892  								},
   893  							},
   894  						},
   895  						Optional: true,
   896  					},
   897  				},
   898  			},
   899  			cty.ObjectVal(map[string]cty.Value{
   900  				"a": cty.ListVal([]cty.Value{
   901  					cty.ObjectVal(map[string]cty.Value{
   902  						"b": cty.StringVal("b value"),
   903  					}),
   904  				}),
   905  			}),
   906  			cty.ObjectVal(map[string]cty.Value{
   907  				"a": cty.ListVal([]cty.Value{
   908  					cty.ObjectVal(map[string]cty.Value{
   909  						"b": cty.StringVal("new b value"),
   910  					}),
   911  				}),
   912  			}),
   913  			cty.ObjectVal(map[string]cty.Value{
   914  				"a": cty.ListVal([]cty.Value{
   915  					cty.ObjectVal(map[string]cty.Value{
   916  						"b": cty.StringVal("b value"), // plan uses value from prior object
   917  					}),
   918  				}),
   919  			}),
   920  			nil,
   921  		},
   922  		"NestedType attr, no computed, all null": {
   923  			&configschema.Block{
   924  				Attributes: map[string]*configschema.Attribute{
   925  					"a": {
   926  						NestedType: &configschema.Object{
   927  							Nesting: configschema.NestingList,
   928  							Attributes: map[string]*configschema.Attribute{
   929  								"b": {
   930  									Type:     cty.String,
   931  									Optional: true,
   932  								},
   933  							},
   934  						},
   935  						Optional: true,
   936  					},
   937  				},
   938  			},
   939  			cty.ObjectVal(map[string]cty.Value{
   940  				"a": cty.NullVal(cty.DynamicPseudoType),
   941  			}),
   942  			cty.ObjectVal(map[string]cty.Value{
   943  				"a": cty.NullVal(cty.DynamicPseudoType),
   944  			}),
   945  			cty.ObjectVal(map[string]cty.Value{
   946  				"a": cty.NullVal(cty.DynamicPseudoType),
   947  			}),
   948  			nil,
   949  		},
   950  		"NestedType attr, no computed, all zero value": {
   951  			&configschema.Block{
   952  				Attributes: map[string]*configschema.Attribute{
   953  					"a": {
   954  						NestedType: &configschema.Object{
   955  							Nesting: configschema.NestingList,
   956  							Attributes: map[string]*configschema.Attribute{
   957  								"b": {
   958  									Type:     cty.String,
   959  									Optional: true,
   960  								},
   961  							},
   962  						},
   963  						Optional: true,
   964  					},
   965  				},
   966  			},
   967  			cty.ObjectVal(map[string]cty.Value{
   968  				"a": cty.NullVal(cty.List(cty.Object(map[string]cty.Type{
   969  					"b": cty.String,
   970  				}))),
   971  			}),
   972  			cty.ObjectVal(map[string]cty.Value{
   973  				"a": cty.NullVal(cty.List(cty.Object(map[string]cty.Type{
   974  					"b": cty.String,
   975  				}))),
   976  			}),
   977  			cty.ObjectVal(map[string]cty.Value{
   978  				"a": cty.NullVal(cty.List(cty.Object(map[string]cty.Type{
   979  					"b": cty.String,
   980  				}))),
   981  			}),
   982  			nil,
   983  		},
   984  		"NestedType NestingSet attribute to null": {
   985  			&configschema.Block{
   986  				Attributes: map[string]*configschema.Attribute{
   987  					"bloop": {
   988  						NestedType: &configschema.Object{
   989  							Nesting: configschema.NestingSet,
   990  							Attributes: map[string]*configschema.Attribute{
   991  								"blop": {
   992  									Type:     cty.String,
   993  									Required: true,
   994  								},
   995  							},
   996  						},
   997  						Optional: true,
   998  					},
   999  				},
  1000  			},
  1001  			cty.ObjectVal(map[string]cty.Value{
  1002  				"bloop": cty.SetVal([]cty.Value{
  1003  					cty.ObjectVal(map[string]cty.Value{
  1004  						"blop": cty.StringVal("ok"),
  1005  					}),
  1006  				}),
  1007  			}),
  1008  			cty.ObjectVal(map[string]cty.Value{
  1009  				"bloop": cty.NullVal(cty.Set(cty.Object(map[string]cty.Type{
  1010  					"blop": cty.String,
  1011  				}))),
  1012  			}),
  1013  			cty.ObjectVal(map[string]cty.Value{
  1014  				"bloop": cty.NullVal(cty.Set(cty.Object(map[string]cty.Type{
  1015  					"blop": cty.String,
  1016  				}))),
  1017  			}),
  1018  			nil,
  1019  		},
  1020  		"NestedType deep nested optional set attribute to null": {
  1021  			&configschema.Block{
  1022  				Attributes: map[string]*configschema.Attribute{
  1023  					"bleep": {
  1024  						NestedType: &configschema.Object{
  1025  							Nesting: configschema.NestingList,
  1026  							Attributes: map[string]*configschema.Attribute{
  1027  								"bloop": {
  1028  									NestedType: &configschema.Object{
  1029  										Nesting: configschema.NestingSet,
  1030  										Attributes: map[string]*configschema.Attribute{
  1031  											"blome": {
  1032  												Type:     cty.String,
  1033  												Optional: true,
  1034  											},
  1035  										},
  1036  									},
  1037  									Optional: true,
  1038  								},
  1039  							},
  1040  						},
  1041  						Optional: true,
  1042  					},
  1043  				},
  1044  			},
  1045  			cty.ObjectVal(map[string]cty.Value{
  1046  				"bleep": cty.ListVal([]cty.Value{
  1047  					cty.ObjectVal(map[string]cty.Value{
  1048  						"bloop": cty.SetVal([]cty.Value{
  1049  							cty.ObjectVal(map[string]cty.Value{
  1050  								"blome": cty.StringVal("ok"),
  1051  							}),
  1052  						}),
  1053  					}),
  1054  				}),
  1055  			}),
  1056  			cty.ObjectVal(map[string]cty.Value{
  1057  				"bleep": cty.ListVal([]cty.Value{
  1058  					cty.ObjectVal(map[string]cty.Value{
  1059  						"bloop": cty.NullVal(cty.Set(
  1060  							cty.Object(map[string]cty.Type{
  1061  								"blome": cty.String,
  1062  							}),
  1063  						)),
  1064  					}),
  1065  				}),
  1066  			}),
  1067  			cty.ObjectVal(map[string]cty.Value{
  1068  				"bleep": cty.ListVal([]cty.Value{
  1069  					cty.ObjectVal(map[string]cty.Value{
  1070  						"bloop": cty.NullVal(cty.List(
  1071  							cty.Object(map[string]cty.Type{
  1072  								"blome": cty.String,
  1073  							}),
  1074  						)),
  1075  					}),
  1076  				}),
  1077  			}),
  1078  			nil,
  1079  		},
  1080  		"NestedType deep nested set": {
  1081  			&configschema.Block{
  1082  				Attributes: map[string]*configschema.Attribute{
  1083  					"bleep": {
  1084  						NestedType: &configschema.Object{
  1085  							Nesting: configschema.NestingList,
  1086  							Attributes: map[string]*configschema.Attribute{
  1087  								"bloop": {
  1088  									NestedType: &configschema.Object{
  1089  										Nesting: configschema.NestingSet,
  1090  										Attributes: map[string]*configschema.Attribute{
  1091  											"blome": {
  1092  												Type:     cty.String,
  1093  												Optional: true,
  1094  											},
  1095  										},
  1096  									},
  1097  									Optional: true,
  1098  								},
  1099  							},
  1100  						},
  1101  						Optional: true,
  1102  					},
  1103  				},
  1104  			},
  1105  			cty.ObjectVal(map[string]cty.Value{
  1106  				"bleep": cty.ListVal([]cty.Value{
  1107  					cty.ObjectVal(map[string]cty.Value{
  1108  						"bloop": cty.SetVal([]cty.Value{
  1109  							cty.ObjectVal(map[string]cty.Value{
  1110  								"blome": cty.StringVal("ok"),
  1111  							}),
  1112  						}),
  1113  					}),
  1114  				}),
  1115  			}),
  1116  			// Note: bloop is null in the config
  1117  			cty.ObjectVal(map[string]cty.Value{
  1118  				"bleep": cty.ListVal([]cty.Value{
  1119  					cty.ObjectVal(map[string]cty.Value{
  1120  						"bloop": cty.NullVal(cty.Set(
  1121  							cty.Object(map[string]cty.Type{
  1122  								"blome": cty.String,
  1123  							}),
  1124  						)),
  1125  					}),
  1126  				}),
  1127  			}),
  1128  			// provider sends back the prior value, not matching the config
  1129  			cty.ObjectVal(map[string]cty.Value{
  1130  				"bleep": cty.ListVal([]cty.Value{
  1131  					cty.ObjectVal(map[string]cty.Value{
  1132  						"bloop": cty.SetVal([]cty.Value{
  1133  							cty.ObjectVal(map[string]cty.Value{
  1134  								"blome": cty.StringVal("ok"),
  1135  							}),
  1136  						}),
  1137  					}),
  1138  				}),
  1139  			}),
  1140  			nil, // we cannot validate individual set elements, and trust the provider's response
  1141  		},
  1142  		"NestedType nested computed list attribute": {
  1143  			&configschema.Block{
  1144  				Attributes: map[string]*configschema.Attribute{
  1145  					"bloop": {
  1146  						NestedType: &configschema.Object{
  1147  							Nesting: configschema.NestingList,
  1148  							Attributes: map[string]*configschema.Attribute{
  1149  								"blop": {
  1150  									Type:     cty.String,
  1151  									Optional: true,
  1152  								},
  1153  							},
  1154  						},
  1155  						Computed: true,
  1156  					},
  1157  				},
  1158  			},
  1159  			cty.ObjectVal(map[string]cty.Value{
  1160  				"bloop": cty.ListVal([]cty.Value{
  1161  					cty.ObjectVal(map[string]cty.Value{
  1162  						"blop": cty.StringVal("ok"),
  1163  					}),
  1164  				}),
  1165  			}),
  1166  			cty.ObjectVal(map[string]cty.Value{
  1167  				"bloop": cty.NullVal(cty.List(cty.Object(map[string]cty.Type{
  1168  					"blop": cty.String,
  1169  				}))),
  1170  			}),
  1171  
  1172  			cty.ObjectVal(map[string]cty.Value{
  1173  				"bloop": cty.ListVal([]cty.Value{
  1174  					cty.ObjectVal(map[string]cty.Value{
  1175  						"blop": cty.StringVal("ok"),
  1176  					}),
  1177  				}),
  1178  			}),
  1179  			nil,
  1180  		},
  1181  		"NestedType nested list attribute to null": {
  1182  			&configschema.Block{
  1183  				Attributes: map[string]*configschema.Attribute{
  1184  					"bloop": {
  1185  						NestedType: &configschema.Object{
  1186  							Nesting: configschema.NestingList,
  1187  							Attributes: map[string]*configschema.Attribute{
  1188  								"blop": {
  1189  									Type:     cty.String,
  1190  									Optional: true,
  1191  								},
  1192  							},
  1193  						},
  1194  						Optional: true,
  1195  					},
  1196  				},
  1197  			},
  1198  			cty.ObjectVal(map[string]cty.Value{
  1199  				"bloop": cty.ListVal([]cty.Value{
  1200  					cty.ObjectVal(map[string]cty.Value{
  1201  						"blop": cty.StringVal("ok"),
  1202  					}),
  1203  				}),
  1204  			}),
  1205  			cty.ObjectVal(map[string]cty.Value{
  1206  				"bloop": cty.NullVal(cty.List(cty.Object(map[string]cty.Type{
  1207  					"blop": cty.String,
  1208  				}))),
  1209  			}),
  1210  
  1211  			// provider returned the old value
  1212  			cty.ObjectVal(map[string]cty.Value{
  1213  				"bloop": cty.ListVal([]cty.Value{
  1214  					cty.ObjectVal(map[string]cty.Value{
  1215  						"blop": cty.StringVal("ok"),
  1216  					}),
  1217  				}),
  1218  			}),
  1219  			[]string{`.bloop: planned value cty.ListVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{"blop":cty.StringVal("ok")})}) for a non-computed attribute`},
  1220  		},
  1221  		"NestedType nested set attribute to null": {
  1222  			&configschema.Block{
  1223  				Attributes: map[string]*configschema.Attribute{
  1224  					"bloop": {
  1225  						NestedType: &configschema.Object{
  1226  							Nesting: configschema.NestingSet,
  1227  							Attributes: map[string]*configschema.Attribute{
  1228  								"blop": {
  1229  									Type:     cty.String,
  1230  									Optional: true,
  1231  								},
  1232  							},
  1233  						},
  1234  						Optional: true,
  1235  					},
  1236  				},
  1237  			},
  1238  			cty.ObjectVal(map[string]cty.Value{
  1239  				"bloop": cty.SetVal([]cty.Value{
  1240  					cty.ObjectVal(map[string]cty.Value{
  1241  						"blop": cty.StringVal("ok"),
  1242  					}),
  1243  				}),
  1244  			}),
  1245  			cty.ObjectVal(map[string]cty.Value{
  1246  				"bloop": cty.NullVal(cty.Set(cty.Object(map[string]cty.Type{
  1247  					"blop": cty.String,
  1248  				}))),
  1249  			}),
  1250  			// provider returned the old value
  1251  			cty.ObjectVal(map[string]cty.Value{
  1252  				"bloop": cty.ListVal([]cty.Value{
  1253  					cty.ObjectVal(map[string]cty.Value{
  1254  						"blop": cty.StringVal("ok"),
  1255  					}),
  1256  				}),
  1257  			}),
  1258  			[]string{`.bloop: planned value cty.ListVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{"blop":cty.StringVal("ok")})}) for a non-computed attribute`},
  1259  		},
  1260  		"computed within nested objects": {
  1261  			&configschema.Block{
  1262  				Attributes: map[string]*configschema.Attribute{
  1263  					"map": {
  1264  						NestedType: &configschema.Object{
  1265  							Nesting: configschema.NestingMap,
  1266  							Attributes: map[string]*configschema.Attribute{
  1267  								"name": {
  1268  									Type:     cty.String,
  1269  									Computed: true,
  1270  								},
  1271  							},
  1272  						},
  1273  					},
  1274  					// When an object has dynamic attrs, the map may be
  1275  					// handled as an object.
  1276  					"map_as_obj": {
  1277  						NestedType: &configschema.Object{
  1278  							Nesting: configschema.NestingMap,
  1279  							Attributes: map[string]*configschema.Attribute{
  1280  								"name": {
  1281  									Type:     cty.String,
  1282  									Computed: true,
  1283  								},
  1284  							},
  1285  						},
  1286  					},
  1287  					"list": {
  1288  						NestedType: &configschema.Object{
  1289  							Nesting: configschema.NestingList,
  1290  							Attributes: map[string]*configschema.Attribute{
  1291  								"name": {
  1292  									Type:     cty.String,
  1293  									Computed: true,
  1294  								},
  1295  							},
  1296  						},
  1297  					},
  1298  					"set": {
  1299  						NestedType: &configschema.Object{
  1300  							Nesting: configschema.NestingSet,
  1301  							Attributes: map[string]*configschema.Attribute{
  1302  								"name": {
  1303  									Type:     cty.String,
  1304  									Computed: true,
  1305  								},
  1306  							},
  1307  						},
  1308  					},
  1309  					"single": {
  1310  						NestedType: &configschema.Object{
  1311  							Nesting: configschema.NestingSingle,
  1312  							Attributes: map[string]*configschema.Attribute{
  1313  								"name": {
  1314  									Type:     cty.DynamicPseudoType,
  1315  									Computed: true,
  1316  								},
  1317  							},
  1318  						},
  1319  					},
  1320  				},
  1321  			},
  1322  			cty.NullVal(cty.Object(map[string]cty.Type{
  1323  				"map": cty.Map(cty.Object(map[string]cty.Type{
  1324  					"name": cty.String,
  1325  				})),
  1326  				"map_as_obj": cty.Map(cty.Object(map[string]cty.Type{
  1327  					"name": cty.DynamicPseudoType,
  1328  				})),
  1329  				"list": cty.List(cty.Object(map[string]cty.Type{
  1330  					"name": cty.String,
  1331  				})),
  1332  				"set": cty.Set(cty.Object(map[string]cty.Type{
  1333  					"name": cty.String,
  1334  				})),
  1335  				"single": cty.Object(map[string]cty.Type{
  1336  					"name": cty.String,
  1337  				}),
  1338  			})),
  1339  			cty.ObjectVal(map[string]cty.Value{
  1340  				"map": cty.MapVal(map[string]cty.Value{
  1341  					"one": cty.ObjectVal(map[string]cty.Value{
  1342  						"name": cty.NullVal(cty.String),
  1343  					}),
  1344  				}),
  1345  				"map_as_obj": cty.MapVal(map[string]cty.Value{
  1346  					"one": cty.ObjectVal(map[string]cty.Value{
  1347  						"name": cty.NullVal(cty.DynamicPseudoType),
  1348  					}),
  1349  				}),
  1350  				"list": cty.ListVal([]cty.Value{
  1351  					cty.ObjectVal(map[string]cty.Value{
  1352  						"name": cty.NullVal(cty.String),
  1353  					}),
  1354  				}),
  1355  				"set": cty.SetVal([]cty.Value{
  1356  					cty.ObjectVal(map[string]cty.Value{
  1357  						"name": cty.NullVal(cty.String),
  1358  					}),
  1359  				}),
  1360  				"single": cty.ObjectVal(map[string]cty.Value{
  1361  					"name": cty.NullVal(cty.String),
  1362  				}),
  1363  			}),
  1364  			cty.ObjectVal(map[string]cty.Value{
  1365  				"map": cty.MapVal(map[string]cty.Value{
  1366  					"one": cty.ObjectVal(map[string]cty.Value{
  1367  						"name": cty.NullVal(cty.String),
  1368  					}),
  1369  				}),
  1370  				"map_as_obj": cty.ObjectVal(map[string]cty.Value{
  1371  					"one": cty.ObjectVal(map[string]cty.Value{
  1372  						"name": cty.StringVal("computed"),
  1373  					}),
  1374  				}),
  1375  				"list": cty.ListVal([]cty.Value{
  1376  					cty.ObjectVal(map[string]cty.Value{
  1377  						"name": cty.NullVal(cty.String),
  1378  					}),
  1379  				}),
  1380  				"set": cty.SetVal([]cty.Value{
  1381  					cty.ObjectVal(map[string]cty.Value{
  1382  						"name": cty.NullVal(cty.String),
  1383  					}),
  1384  				}),
  1385  				"single": cty.ObjectVal(map[string]cty.Value{
  1386  					"name": cty.NullVal(cty.String),
  1387  				}),
  1388  			}),
  1389  			nil,
  1390  		},
  1391  		"computed nested objects": {
  1392  			&configschema.Block{
  1393  				Attributes: map[string]*configschema.Attribute{
  1394  					"map": {
  1395  						NestedType: &configschema.Object{
  1396  							Nesting: configschema.NestingMap,
  1397  							Attributes: map[string]*configschema.Attribute{
  1398  								"name": {
  1399  									Type: cty.String,
  1400  								},
  1401  							},
  1402  						},
  1403  						Computed: true,
  1404  					},
  1405  					"list": {
  1406  						NestedType: &configschema.Object{
  1407  							Nesting: configschema.NestingList,
  1408  							Attributes: map[string]*configschema.Attribute{
  1409  								"name": {
  1410  									Type: cty.String,
  1411  								},
  1412  							},
  1413  						},
  1414  						Computed: true,
  1415  					},
  1416  					"set": {
  1417  						NestedType: &configschema.Object{
  1418  							Nesting: configschema.NestingSet,
  1419  							Attributes: map[string]*configschema.Attribute{
  1420  								"name": {
  1421  									Type: cty.String,
  1422  								},
  1423  							},
  1424  						},
  1425  						Optional: true,
  1426  						Computed: true,
  1427  					},
  1428  					"single": {
  1429  						NestedType: &configschema.Object{
  1430  							Nesting: configschema.NestingSingle,
  1431  							Attributes: map[string]*configschema.Attribute{
  1432  								"name": {
  1433  									Type: cty.DynamicPseudoType,
  1434  								},
  1435  							},
  1436  						},
  1437  						Computed: true,
  1438  					},
  1439  				},
  1440  			},
  1441  			cty.NullVal(cty.Object(map[string]cty.Type{
  1442  				"map": cty.Map(cty.Object(map[string]cty.Type{
  1443  					"name": cty.String,
  1444  				})),
  1445  				"list": cty.List(cty.Object(map[string]cty.Type{
  1446  					"name": cty.String,
  1447  				})),
  1448  				"set": cty.Set(cty.Object(map[string]cty.Type{
  1449  					"name": cty.String,
  1450  				})),
  1451  				"single": cty.Object(map[string]cty.Type{
  1452  					"name": cty.String,
  1453  				}),
  1454  			})),
  1455  			cty.ObjectVal(map[string]cty.Value{
  1456  				"map": cty.NullVal(cty.Map(cty.Object(map[string]cty.Type{
  1457  					"name": cty.String,
  1458  				}))),
  1459  				"list": cty.NullVal(cty.List(cty.Object(map[string]cty.Type{
  1460  					"name": cty.String,
  1461  				}))),
  1462  				"set": cty.SetVal([]cty.Value{
  1463  					cty.ObjectVal(map[string]cty.Value{
  1464  						"name": cty.StringVal("from_config"),
  1465  					}),
  1466  				}),
  1467  				"single": cty.NullVal(cty.Object(map[string]cty.Type{
  1468  					"name": cty.String,
  1469  				})),
  1470  			}),
  1471  			cty.ObjectVal(map[string]cty.Value{
  1472  				"map": cty.MapVal(map[string]cty.Value{
  1473  					"one": cty.UnknownVal(cty.Object(map[string]cty.Type{
  1474  						"name": cty.String,
  1475  					})),
  1476  				}),
  1477  				"list": cty.ListVal([]cty.Value{
  1478  					cty.ObjectVal(map[string]cty.Value{
  1479  						"name": cty.StringVal("computed"),
  1480  					}),
  1481  				}),
  1482  				"set": cty.SetVal([]cty.Value{
  1483  					cty.ObjectVal(map[string]cty.Value{
  1484  						"name": cty.StringVal("from_config"),
  1485  					}),
  1486  				}),
  1487  				"single": cty.UnknownVal(cty.Object(map[string]cty.Type{
  1488  					"name": cty.String,
  1489  				})),
  1490  			}),
  1491  			nil,
  1492  		},
  1493  		"optional computed within nested objects": {
  1494  			&configschema.Block{
  1495  				Attributes: map[string]*configschema.Attribute{
  1496  					"map": {
  1497  						NestedType: &configschema.Object{
  1498  							Nesting: configschema.NestingMap,
  1499  							Attributes: map[string]*configschema.Attribute{
  1500  								"name": {
  1501  									Type:     cty.String,
  1502  									Computed: true,
  1503  								},
  1504  							},
  1505  						},
  1506  					},
  1507  					// When an object has dynamic attrs, the map may be
  1508  					// handled as an object.
  1509  					"map_as_obj": {
  1510  						NestedType: &configschema.Object{
  1511  							Nesting: configschema.NestingMap,
  1512  							Attributes: map[string]*configschema.Attribute{
  1513  								"name": {
  1514  									Type:     cty.String,
  1515  									Optional: true,
  1516  									Computed: true,
  1517  								},
  1518  							},
  1519  						},
  1520  					},
  1521  					"list": {
  1522  						NestedType: &configschema.Object{
  1523  							Nesting: configschema.NestingList,
  1524  							Attributes: map[string]*configschema.Attribute{
  1525  								"name": {
  1526  									Type:     cty.String,
  1527  									Optional: true,
  1528  									Computed: true,
  1529  								},
  1530  							},
  1531  						},
  1532  					},
  1533  					"set": {
  1534  						NestedType: &configschema.Object{
  1535  							Nesting: configschema.NestingSet,
  1536  							Attributes: map[string]*configschema.Attribute{
  1537  								"name": {
  1538  									Type:     cty.String,
  1539  									Optional: true,
  1540  									Computed: true,
  1541  								},
  1542  							},
  1543  						},
  1544  					},
  1545  					"single": {
  1546  						NestedType: &configschema.Object{
  1547  							Nesting: configschema.NestingSingle,
  1548  							Attributes: map[string]*configschema.Attribute{
  1549  								"name": {
  1550  									Type:     cty.DynamicPseudoType,
  1551  									Optional: true,
  1552  									Computed: true,
  1553  								},
  1554  							},
  1555  						},
  1556  					},
  1557  				},
  1558  			},
  1559  			cty.NullVal(cty.Object(map[string]cty.Type{
  1560  				"map": cty.Map(cty.Object(map[string]cty.Type{
  1561  					"name": cty.String,
  1562  				})),
  1563  				"map_as_obj": cty.Map(cty.Object(map[string]cty.Type{
  1564  					"name": cty.DynamicPseudoType,
  1565  				})),
  1566  				"list": cty.List(cty.Object(map[string]cty.Type{
  1567  					"name": cty.String,
  1568  				})),
  1569  				"set": cty.Set(cty.Object(map[string]cty.Type{
  1570  					"name": cty.String,
  1571  				})),
  1572  				"single": cty.Object(map[string]cty.Type{
  1573  					"name": cty.String,
  1574  				}),
  1575  			})),
  1576  			cty.ObjectVal(map[string]cty.Value{
  1577  				"map": cty.MapVal(map[string]cty.Value{
  1578  					"one": cty.ObjectVal(map[string]cty.Value{
  1579  						"name": cty.StringVal("from_config"),
  1580  					}),
  1581  				}),
  1582  				"map_as_obj": cty.MapVal(map[string]cty.Value{
  1583  					"one": cty.ObjectVal(map[string]cty.Value{
  1584  						"name": cty.NullVal(cty.DynamicPseudoType),
  1585  					}),
  1586  				}),
  1587  				"list": cty.ListVal([]cty.Value{
  1588  					cty.ObjectVal(map[string]cty.Value{
  1589  						"name": cty.NullVal(cty.String),
  1590  					}),
  1591  				}),
  1592  				"set": cty.SetVal([]cty.Value{
  1593  					cty.ObjectVal(map[string]cty.Value{
  1594  						"name": cty.NullVal(cty.String),
  1595  					}),
  1596  				}),
  1597  				"single": cty.ObjectVal(map[string]cty.Value{
  1598  					"name": cty.StringVal("from_config"),
  1599  				}),
  1600  			}),
  1601  			cty.ObjectVal(map[string]cty.Value{
  1602  				"map": cty.MapVal(map[string]cty.Value{
  1603  					"one": cty.ObjectVal(map[string]cty.Value{
  1604  						"name": cty.StringVal("from_config"),
  1605  					}),
  1606  				}),
  1607  				"map_as_obj": cty.ObjectVal(map[string]cty.Value{
  1608  					"one": cty.ObjectVal(map[string]cty.Value{
  1609  						"name": cty.StringVal("computed"),
  1610  					}),
  1611  				}),
  1612  				"list": cty.ListVal([]cty.Value{
  1613  					cty.ObjectVal(map[string]cty.Value{
  1614  						"name": cty.StringVal("computed"),
  1615  					}),
  1616  				}),
  1617  				"set": cty.SetVal([]cty.Value{
  1618  					cty.ObjectVal(map[string]cty.Value{
  1619  						"name": cty.NullVal(cty.String),
  1620  					}),
  1621  				}),
  1622  				"single": cty.ObjectVal(map[string]cty.Value{
  1623  					"name": cty.StringVal("from_config"),
  1624  				}),
  1625  			}),
  1626  			nil,
  1627  		},
  1628  		"cannot replace config nested attr": {
  1629  			&configschema.Block{
  1630  				Attributes: map[string]*configschema.Attribute{
  1631  					"map": {
  1632  						NestedType: &configschema.Object{
  1633  							Nesting: configschema.NestingMap,
  1634  							Attributes: map[string]*configschema.Attribute{
  1635  								"name": {
  1636  									Type:     cty.String,
  1637  									Computed: true,
  1638  									Optional: true,
  1639  								},
  1640  							},
  1641  						},
  1642  					},
  1643  				},
  1644  			},
  1645  			cty.NullVal(cty.Object(map[string]cty.Type{
  1646  				"map": cty.Map(cty.Object(map[string]cty.Type{
  1647  					"name": cty.String,
  1648  				})),
  1649  			})),
  1650  			cty.ObjectVal(map[string]cty.Value{
  1651  				"map": cty.MapVal(map[string]cty.Value{
  1652  					"one": cty.ObjectVal(map[string]cty.Value{
  1653  						"name": cty.StringVal("from_config"),
  1654  					}),
  1655  				}),
  1656  			}),
  1657  			cty.ObjectVal(map[string]cty.Value{
  1658  				"map": cty.MapVal(map[string]cty.Value{
  1659  					"one": cty.ObjectVal(map[string]cty.Value{
  1660  						"name": cty.StringVal("from_provider"),
  1661  					}),
  1662  				}),
  1663  			}),
  1664  			[]string{`.map.one.name: planned value cty.StringVal("from_provider") does not match config value cty.StringVal("from_config")`},
  1665  		},
  1666  
  1667  		// If a config value ended up in a computed-only attribute it can still
  1668  		// be a valid plan. We either got here because the user ignore warnings
  1669  		// about ignore_changes on computed attributes, or we failed to
  1670  		// validate a config with computed values. Either way, we don't want to
  1671  		// indicate an error with the provider.
  1672  		"computed only value with config": {
  1673  			&configschema.Block{
  1674  				Attributes: map[string]*configschema.Attribute{
  1675  					"a": {
  1676  						Type:     cty.String,
  1677  						Computed: true,
  1678  					},
  1679  				},
  1680  			},
  1681  			cty.ObjectVal(map[string]cty.Value{
  1682  				"a": cty.StringVal("old"),
  1683  			}),
  1684  			cty.ObjectVal(map[string]cty.Value{
  1685  				"a": cty.StringVal("old"),
  1686  			}),
  1687  			cty.ObjectVal(map[string]cty.Value{
  1688  				"a": cty.UnknownVal(cty.String),
  1689  			}),
  1690  			nil,
  1691  		},
  1692  	}
  1693  
  1694  	for name, test := range tests {
  1695  		t.Run(name, func(t *testing.T) {
  1696  			errs := AssertPlanValid(test.Schema, test.Prior, test.Config, test.Planned)
  1697  
  1698  			wantErrs := make(map[string]struct{})
  1699  			gotErrs := make(map[string]struct{})
  1700  			for _, err := range errs {
  1701  				gotErrs[tfdiags.FormatError(err)] = struct{}{}
  1702  			}
  1703  			for _, msg := range test.WantErrs {
  1704  				wantErrs[msg] = struct{}{}
  1705  			}
  1706  
  1707  			t.Logf(
  1708  				"\nprior:  %sconfig:  %splanned: %s",
  1709  				dump.Value(test.Prior),
  1710  				dump.Value(test.Config),
  1711  				dump.Value(test.Planned),
  1712  			)
  1713  			for msg := range wantErrs {
  1714  				if _, ok := gotErrs[msg]; !ok {
  1715  					t.Errorf("missing expected error: %s", msg)
  1716  				}
  1717  			}
  1718  			for msg := range gotErrs {
  1719  				if _, ok := wantErrs[msg]; !ok {
  1720  					t.Errorf("unexpected extra error: %s", msg)
  1721  				}
  1722  			}
  1723  		})
  1724  	}
  1725  }