github.com/eliastor/durgaform@v0.0.0-20220816172711-d0ab2d17673e/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/eliastor/durgaform/internal/configs/configschema"
    10  	"github.com/eliastor/durgaform/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  		// blocks can be unknown when using dynamic
   393  		"nested list, unknown nested dynamic": {
   394  			&configschema.Block{
   395  				BlockTypes: map[string]*configschema.NestedBlock{
   396  					"a": {
   397  						Nesting: configschema.NestingList,
   398  						Block: configschema.Block{
   399  							BlockTypes: map[string]*configschema.NestedBlock{
   400  								"b": {
   401  									Nesting: configschema.NestingList,
   402  									Block: configschema.Block{
   403  										Attributes: map[string]*configschema.Attribute{
   404  											"c": {
   405  												Type:     cty.String,
   406  												Optional: true,
   407  											},
   408  											"computed": {
   409  												Type:     cty.String,
   410  												Computed: true,
   411  											},
   412  										},
   413  									},
   414  								},
   415  							},
   416  						},
   417  					},
   418  				},
   419  			},
   420  
   421  			cty.ObjectVal(map[string]cty.Value{
   422  				"a": cty.ListVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{
   423  					"computed": cty.NullVal(cty.String),
   424  					"b": cty.ListVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{
   425  						"c": cty.StringVal("x"),
   426  					})}),
   427  				})}),
   428  			}),
   429  			cty.ObjectVal(map[string]cty.Value{
   430  				"a": cty.ListVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{
   431  					"b": cty.UnknownVal(cty.List(cty.Object(map[string]cty.Type{
   432  						"c":        cty.String,
   433  						"computed": cty.String,
   434  					}))),
   435  				})}),
   436  			}),
   437  			cty.ObjectVal(map[string]cty.Value{
   438  				"a": cty.ListVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{
   439  					"b": cty.UnknownVal(cty.List(cty.Object(map[string]cty.Type{
   440  						"c":        cty.String,
   441  						"computed": cty.String,
   442  					}))),
   443  				})}),
   444  			}),
   445  			[]string{},
   446  		},
   447  
   448  		"nested set, unknown dynamic cannot be planned": {
   449  			&configschema.Block{
   450  				Attributes: map[string]*configschema.Attribute{
   451  					"computed": {
   452  						Type:     cty.String,
   453  						Computed: true,
   454  					},
   455  				},
   456  				BlockTypes: map[string]*configschema.NestedBlock{
   457  					"b": {
   458  						Nesting: configschema.NestingSet,
   459  						Block: configschema.Block{
   460  							Attributes: map[string]*configschema.Attribute{
   461  								"c": {
   462  									Type:     cty.String,
   463  									Optional: true,
   464  								},
   465  							},
   466  						},
   467  					},
   468  				},
   469  			},
   470  
   471  			cty.ObjectVal(map[string]cty.Value{
   472  				"computed": cty.NullVal(cty.String),
   473  				"b": cty.SetVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{
   474  					"c": cty.StringVal("x"),
   475  				})}),
   476  			}),
   477  			cty.ObjectVal(map[string]cty.Value{
   478  				"computed": cty.NullVal(cty.String),
   479  				"b": cty.UnknownVal(cty.Set(cty.Object(map[string]cty.Type{
   480  					"c": cty.String,
   481  				}))),
   482  			}),
   483  			cty.ObjectVal(map[string]cty.Value{
   484  				"computed": cty.StringVal("default"),
   485  				"b": cty.SetVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{
   486  					"c": cty.StringVal("oops"),
   487  				})}),
   488  			}),
   489  
   490  			[]string{
   491  				`.b: planned value cty.SetVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{"c":cty.StringVal("oops")})}) for unknown dynamic block`,
   492  			},
   493  		},
   494  
   495  		"nested set, null in plan": {
   496  			&configschema.Block{
   497  				BlockTypes: map[string]*configschema.NestedBlock{
   498  					"b": {
   499  						Nesting: configschema.NestingSet,
   500  						Block: configschema.Block{
   501  							Attributes: map[string]*configschema.Attribute{
   502  								"c": {
   503  									Type:     cty.String,
   504  									Optional: true,
   505  								},
   506  							},
   507  						},
   508  					},
   509  				},
   510  			},
   511  			cty.NullVal(cty.Object(map[string]cty.Type{
   512  				"b": cty.Set(cty.Object(map[string]cty.Type{
   513  					"c": cty.String,
   514  				})),
   515  			})),
   516  			cty.ObjectVal(map[string]cty.Value{
   517  				"b": cty.SetValEmpty(cty.Object(map[string]cty.Type{
   518  					"c": cty.String,
   519  				})),
   520  			}),
   521  			cty.ObjectVal(map[string]cty.Value{
   522  				"b": cty.NullVal(cty.Set(cty.Object(map[string]cty.Type{
   523  					"c": cty.String,
   524  				}))),
   525  			}),
   526  			[]string{
   527  				`.b: attribute representing a set of nested blocks must be empty to indicate no blocks, not null`,
   528  			},
   529  		},
   530  		"nested map, null in plan": {
   531  			&configschema.Block{
   532  				BlockTypes: map[string]*configschema.NestedBlock{
   533  					"b": {
   534  						Nesting: configschema.NestingMap,
   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.Map(cty.Object(map[string]cty.Type{
   548  					"c": cty.String,
   549  				})),
   550  			})),
   551  			cty.ObjectVal(map[string]cty.Value{
   552  				"b": cty.MapValEmpty(cty.Object(map[string]cty.Type{
   553  					"c": cty.String,
   554  				})),
   555  			}),
   556  			cty.ObjectVal(map[string]cty.Value{
   557  				"b": cty.NullVal(cty.Map(cty.Object(map[string]cty.Type{
   558  					"c": cty.String,
   559  				}))),
   560  			}),
   561  			[]string{
   562  				`.b: attribute representing a map of nested blocks must be empty to indicate no blocks, not null`,
   563  			},
   564  		},
   565  
   566  		// We don't actually do any validation for nested set blocks, and so
   567  		// the remaining cases here are just intending to ensure we don't
   568  		// inadvertently start generating errors incorrectly in future.
   569  		"nested set, no computed, no changes": {
   570  			&configschema.Block{
   571  				BlockTypes: map[string]*configschema.NestedBlock{
   572  					"b": {
   573  						Nesting: configschema.NestingSet,
   574  						Block: configschema.Block{
   575  							Attributes: map[string]*configschema.Attribute{
   576  								"c": {
   577  									Type:     cty.String,
   578  									Optional: true,
   579  								},
   580  							},
   581  						},
   582  					},
   583  				},
   584  			},
   585  			cty.ObjectVal(map[string]cty.Value{
   586  				"b": cty.SetVal([]cty.Value{
   587  					cty.ObjectVal(map[string]cty.Value{
   588  						"c": cty.StringVal("c value"),
   589  					}),
   590  				}),
   591  			}),
   592  			cty.ObjectVal(map[string]cty.Value{
   593  				"b": cty.SetVal([]cty.Value{
   594  					cty.ObjectVal(map[string]cty.Value{
   595  						"c": cty.StringVal("c value"),
   596  					}),
   597  				}),
   598  			}),
   599  			cty.ObjectVal(map[string]cty.Value{
   600  				"b": cty.SetVal([]cty.Value{
   601  					cty.ObjectVal(map[string]cty.Value{
   602  						"c": cty.StringVal("c value"),
   603  					}),
   604  				}),
   605  			}),
   606  			nil,
   607  		},
   608  		"nested set, no computed, invalid change in plan": {
   609  			&configschema.Block{
   610  				BlockTypes: map[string]*configschema.NestedBlock{
   611  					"b": {
   612  						Nesting: configschema.NestingSet,
   613  						Block: configschema.Block{
   614  							Attributes: map[string]*configschema.Attribute{
   615  								"c": {
   616  									Type:     cty.String,
   617  									Optional: true,
   618  								},
   619  							},
   620  						},
   621  					},
   622  				},
   623  			},
   624  			cty.ObjectVal(map[string]cty.Value{
   625  				"b": cty.SetVal([]cty.Value{
   626  					cty.ObjectVal(map[string]cty.Value{
   627  						"c": cty.StringVal("c value"),
   628  					}),
   629  				}),
   630  			}),
   631  			cty.ObjectVal(map[string]cty.Value{
   632  				"b": cty.SetVal([]cty.Value{
   633  					cty.ObjectVal(map[string]cty.Value{
   634  						"c": cty.StringVal("c value"),
   635  					}),
   636  				}),
   637  			}),
   638  			cty.ObjectVal(map[string]cty.Value{
   639  				"b": cty.SetVal([]cty.Value{
   640  					cty.ObjectVal(map[string]cty.Value{
   641  						"c": cty.StringVal("new c value"), // matches neither prior nor config
   642  					}),
   643  				}),
   644  			}),
   645  			nil,
   646  		},
   647  		"nested set, no computed, diff suppressed": {
   648  			&configschema.Block{
   649  				BlockTypes: map[string]*configschema.NestedBlock{
   650  					"b": {
   651  						Nesting: configschema.NestingSet,
   652  						Block: configschema.Block{
   653  							Attributes: map[string]*configschema.Attribute{
   654  								"c": {
   655  									Type:     cty.String,
   656  									Optional: true,
   657  								},
   658  							},
   659  						},
   660  					},
   661  				},
   662  			},
   663  			cty.ObjectVal(map[string]cty.Value{
   664  				"b": cty.SetVal([]cty.Value{
   665  					cty.ObjectVal(map[string]cty.Value{
   666  						"c": cty.StringVal("c value"),
   667  					}),
   668  				}),
   669  			}),
   670  			cty.ObjectVal(map[string]cty.Value{
   671  				"b": cty.SetVal([]cty.Value{
   672  					cty.ObjectVal(map[string]cty.Value{
   673  						"c": cty.StringVal("new c value"),
   674  					}),
   675  				}),
   676  			}),
   677  			cty.ObjectVal(map[string]cty.Value{
   678  				"b": cty.SetVal([]cty.Value{
   679  					cty.ObjectVal(map[string]cty.Value{
   680  						"c": cty.StringVal("c value"), // plan uses value from prior object
   681  					}),
   682  				}),
   683  			}),
   684  			nil,
   685  		},
   686  
   687  		// Attributes with NestedTypes
   688  		"NestedType attr, no computed, all match": {
   689  			&configschema.Block{
   690  				Attributes: map[string]*configschema.Attribute{
   691  					"a": {
   692  						NestedType: &configschema.Object{
   693  							Nesting: configschema.NestingList,
   694  							Attributes: map[string]*configschema.Attribute{
   695  								"b": {
   696  									Type:     cty.String,
   697  									Optional: true,
   698  								},
   699  							},
   700  						},
   701  						Optional: true,
   702  					},
   703  				},
   704  			},
   705  			cty.ObjectVal(map[string]cty.Value{
   706  				"a": cty.ListVal([]cty.Value{
   707  					cty.ObjectVal(map[string]cty.Value{
   708  						"b": cty.StringVal("b value"),
   709  					}),
   710  				}),
   711  			}),
   712  			cty.ObjectVal(map[string]cty.Value{
   713  				"a": cty.ListVal([]cty.Value{
   714  					cty.ObjectVal(map[string]cty.Value{
   715  						"b": cty.StringVal("b value"),
   716  					}),
   717  				}),
   718  			}),
   719  			cty.ObjectVal(map[string]cty.Value{
   720  				"a": cty.ListVal([]cty.Value{
   721  					cty.ObjectVal(map[string]cty.Value{
   722  						"b": cty.StringVal("b value"),
   723  					}),
   724  				}),
   725  			}),
   726  			nil,
   727  		},
   728  		"NestedType attr, no computed, plan matches, no prior": {
   729  			&configschema.Block{
   730  				Attributes: map[string]*configschema.Attribute{
   731  					"a": {
   732  						NestedType: &configschema.Object{
   733  							Nesting: configschema.NestingList,
   734  							Attributes: map[string]*configschema.Attribute{
   735  								"b": {
   736  									Type:     cty.String,
   737  									Optional: true,
   738  								},
   739  							},
   740  						},
   741  						Optional: true,
   742  					},
   743  				},
   744  			},
   745  			cty.NullVal(cty.Object(map[string]cty.Type{
   746  				"a": cty.List(cty.Object(map[string]cty.Type{
   747  					"b": cty.String,
   748  				})),
   749  			})),
   750  			cty.ObjectVal(map[string]cty.Value{
   751  				"a": cty.ListVal([]cty.Value{
   752  					cty.ObjectVal(map[string]cty.Value{
   753  						"b": cty.StringVal("c value"),
   754  					}),
   755  				}),
   756  			}),
   757  			cty.ObjectVal(map[string]cty.Value{
   758  				"a": cty.ListVal([]cty.Value{
   759  					cty.ObjectVal(map[string]cty.Value{
   760  						"b": cty.StringVal("c value"),
   761  					}),
   762  				}),
   763  			}),
   764  			nil,
   765  		},
   766  		"NestedType, no computed, invalid change in plan": {
   767  			&configschema.Block{
   768  				Attributes: map[string]*configschema.Attribute{
   769  					"a": {
   770  						NestedType: &configschema.Object{
   771  							Nesting: configschema.NestingList,
   772  							Attributes: map[string]*configschema.Attribute{
   773  								"b": {
   774  									Type:     cty.String,
   775  									Optional: true,
   776  								},
   777  							},
   778  						},
   779  						Optional: true,
   780  					},
   781  				},
   782  			},
   783  			cty.NullVal(cty.Object(map[string]cty.Type{
   784  				"a": cty.List(cty.Object(map[string]cty.Type{
   785  					"b": cty.String,
   786  				})),
   787  			})),
   788  			cty.ObjectVal(map[string]cty.Value{
   789  				"a": cty.ListVal([]cty.Value{
   790  					cty.ObjectVal(map[string]cty.Value{
   791  						"b": cty.StringVal("c value"),
   792  					}),
   793  				}),
   794  			}),
   795  			cty.ObjectVal(map[string]cty.Value{
   796  				"a": cty.ListVal([]cty.Value{
   797  					cty.ObjectVal(map[string]cty.Value{
   798  						"b": cty.StringVal("new c value"),
   799  					}),
   800  				}),
   801  			}),
   802  			[]string{
   803  				`.a[0].b: planned value cty.StringVal("new c value") does not match config value cty.StringVal("c value")`,
   804  			},
   805  		},
   806  		"NestedType attr, no computed, invalid change in plan sensitive": {
   807  			&configschema.Block{
   808  				Attributes: map[string]*configschema.Attribute{
   809  					"a": {
   810  						NestedType: &configschema.Object{
   811  							Nesting: configschema.NestingList,
   812  							Attributes: map[string]*configschema.Attribute{
   813  								"b": {
   814  									Type:      cty.String,
   815  									Optional:  true,
   816  									Sensitive: true,
   817  								},
   818  							},
   819  						},
   820  						Optional: true,
   821  					},
   822  				},
   823  			},
   824  			cty.NullVal(cty.Object(map[string]cty.Type{
   825  				"a": cty.List(cty.Object(map[string]cty.Type{
   826  					"b": cty.String,
   827  				})),
   828  			})),
   829  			cty.ObjectVal(map[string]cty.Value{
   830  				"a": cty.ListVal([]cty.Value{
   831  					cty.ObjectVal(map[string]cty.Value{
   832  						"b": cty.StringVal("b value"),
   833  					}),
   834  				}),
   835  			}),
   836  			cty.ObjectVal(map[string]cty.Value{
   837  				"a": cty.ListVal([]cty.Value{
   838  					cty.ObjectVal(map[string]cty.Value{
   839  						"b": cty.StringVal("new b value"),
   840  					}),
   841  				}),
   842  			}),
   843  			[]string{
   844  				`.a[0].b: sensitive planned value does not match config value`,
   845  			},
   846  		},
   847  		"NestedType attr, no computed, diff suppression in plan": {
   848  			&configschema.Block{
   849  				Attributes: map[string]*configschema.Attribute{
   850  					"a": {
   851  						NestedType: &configschema.Object{
   852  							Nesting: configschema.NestingList,
   853  							Attributes: map[string]*configschema.Attribute{
   854  								"b": {
   855  									Type:     cty.String,
   856  									Optional: true,
   857  								},
   858  							},
   859  						},
   860  						Optional: true,
   861  					},
   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  			cty.ObjectVal(map[string]cty.Value{
   879  				"a": cty.ListVal([]cty.Value{
   880  					cty.ObjectVal(map[string]cty.Value{
   881  						"b": cty.StringVal("b value"), // plan uses value from prior object
   882  					}),
   883  				}),
   884  			}),
   885  			nil,
   886  		},
   887  		"NestedType attr, no computed, all null": {
   888  			&configschema.Block{
   889  				Attributes: map[string]*configschema.Attribute{
   890  					"a": {
   891  						NestedType: &configschema.Object{
   892  							Nesting: configschema.NestingList,
   893  							Attributes: map[string]*configschema.Attribute{
   894  								"b": {
   895  									Type:     cty.String,
   896  									Optional: true,
   897  								},
   898  							},
   899  						},
   900  						Optional: true,
   901  					},
   902  				},
   903  			},
   904  			cty.ObjectVal(map[string]cty.Value{
   905  				"a": cty.NullVal(cty.DynamicPseudoType),
   906  			}),
   907  			cty.ObjectVal(map[string]cty.Value{
   908  				"a": cty.NullVal(cty.DynamicPseudoType),
   909  			}),
   910  			cty.ObjectVal(map[string]cty.Value{
   911  				"a": cty.NullVal(cty.DynamicPseudoType),
   912  			}),
   913  			nil,
   914  		},
   915  		"NestedType attr, no computed, all zero value": {
   916  			&configschema.Block{
   917  				Attributes: map[string]*configschema.Attribute{
   918  					"a": {
   919  						NestedType: &configschema.Object{
   920  							Nesting: configschema.NestingList,
   921  							Attributes: map[string]*configschema.Attribute{
   922  								"b": {
   923  									Type:     cty.String,
   924  									Optional: true,
   925  								},
   926  							},
   927  						},
   928  						Optional: true,
   929  					},
   930  				},
   931  			},
   932  			cty.ObjectVal(map[string]cty.Value{
   933  				"a": cty.NullVal(cty.List(cty.Object(map[string]cty.Type{
   934  					"b": cty.String,
   935  				}))),
   936  			}),
   937  			cty.ObjectVal(map[string]cty.Value{
   938  				"a": cty.NullVal(cty.List(cty.Object(map[string]cty.Type{
   939  					"b": cty.String,
   940  				}))),
   941  			}),
   942  			cty.ObjectVal(map[string]cty.Value{
   943  				"a": cty.NullVal(cty.List(cty.Object(map[string]cty.Type{
   944  					"b": cty.String,
   945  				}))),
   946  			}),
   947  			nil,
   948  		},
   949  		"NestedType NestingSet attribute to null": {
   950  			&configschema.Block{
   951  				Attributes: map[string]*configschema.Attribute{
   952  					"bloop": {
   953  						NestedType: &configschema.Object{
   954  							Nesting: configschema.NestingSet,
   955  							Attributes: map[string]*configschema.Attribute{
   956  								"blop": {
   957  									Type:     cty.String,
   958  									Required: true,
   959  								},
   960  							},
   961  						},
   962  						Optional: true,
   963  					},
   964  				},
   965  			},
   966  			cty.ObjectVal(map[string]cty.Value{
   967  				"bloop": cty.SetVal([]cty.Value{
   968  					cty.ObjectVal(map[string]cty.Value{
   969  						"blop": cty.StringVal("ok"),
   970  					}),
   971  				}),
   972  			}),
   973  			cty.ObjectVal(map[string]cty.Value{
   974  				"bloop": cty.NullVal(cty.Set(cty.Object(map[string]cty.Type{
   975  					"blop": cty.String,
   976  				}))),
   977  			}),
   978  			cty.ObjectVal(map[string]cty.Value{
   979  				"bloop": cty.NullVal(cty.Set(cty.Object(map[string]cty.Type{
   980  					"blop": cty.String,
   981  				}))),
   982  			}),
   983  			nil,
   984  		},
   985  		"NestedType deep nested optional set attribute to null": {
   986  			&configschema.Block{
   987  				Attributes: map[string]*configschema.Attribute{
   988  					"bleep": {
   989  						NestedType: &configschema.Object{
   990  							Nesting: configschema.NestingList,
   991  							Attributes: map[string]*configschema.Attribute{
   992  								"bloop": {
   993  									NestedType: &configschema.Object{
   994  										Nesting: configschema.NestingSet,
   995  										Attributes: map[string]*configschema.Attribute{
   996  											"blome": {
   997  												Type:     cty.String,
   998  												Optional: true,
   999  											},
  1000  										},
  1001  									},
  1002  									Optional: true,
  1003  								},
  1004  							},
  1005  						},
  1006  						Optional: true,
  1007  					},
  1008  				},
  1009  			},
  1010  			cty.ObjectVal(map[string]cty.Value{
  1011  				"bleep": cty.ListVal([]cty.Value{
  1012  					cty.ObjectVal(map[string]cty.Value{
  1013  						"bloop": cty.SetVal([]cty.Value{
  1014  							cty.ObjectVal(map[string]cty.Value{
  1015  								"blome": cty.StringVal("ok"),
  1016  							}),
  1017  						}),
  1018  					}),
  1019  				}),
  1020  			}),
  1021  			cty.ObjectVal(map[string]cty.Value{
  1022  				"bleep": cty.ListVal([]cty.Value{
  1023  					cty.ObjectVal(map[string]cty.Value{
  1024  						"bloop": cty.NullVal(cty.Set(
  1025  							cty.Object(map[string]cty.Type{
  1026  								"blome": cty.String,
  1027  							}),
  1028  						)),
  1029  					}),
  1030  				}),
  1031  			}),
  1032  			cty.ObjectVal(map[string]cty.Value{
  1033  				"bleep": cty.ListVal([]cty.Value{
  1034  					cty.ObjectVal(map[string]cty.Value{
  1035  						"bloop": cty.NullVal(cty.List(
  1036  							cty.Object(map[string]cty.Type{
  1037  								"blome": cty.String,
  1038  							}),
  1039  						)),
  1040  					}),
  1041  				}),
  1042  			}),
  1043  			nil,
  1044  		},
  1045  		"NestedType deep nested set": {
  1046  			&configschema.Block{
  1047  				Attributes: map[string]*configschema.Attribute{
  1048  					"bleep": {
  1049  						NestedType: &configschema.Object{
  1050  							Nesting: configschema.NestingList,
  1051  							Attributes: map[string]*configschema.Attribute{
  1052  								"bloop": {
  1053  									NestedType: &configschema.Object{
  1054  										Nesting: configschema.NestingSet,
  1055  										Attributes: map[string]*configschema.Attribute{
  1056  											"blome": {
  1057  												Type:     cty.String,
  1058  												Optional: true,
  1059  											},
  1060  										},
  1061  									},
  1062  									Optional: true,
  1063  								},
  1064  							},
  1065  						},
  1066  						Optional: true,
  1067  					},
  1068  				},
  1069  			},
  1070  			cty.ObjectVal(map[string]cty.Value{
  1071  				"bleep": cty.ListVal([]cty.Value{
  1072  					cty.ObjectVal(map[string]cty.Value{
  1073  						"bloop": cty.SetVal([]cty.Value{
  1074  							cty.ObjectVal(map[string]cty.Value{
  1075  								"blome": cty.StringVal("ok"),
  1076  							}),
  1077  						}),
  1078  					}),
  1079  				}),
  1080  			}),
  1081  			// Note: bloop is null in the config
  1082  			cty.ObjectVal(map[string]cty.Value{
  1083  				"bleep": cty.ListVal([]cty.Value{
  1084  					cty.ObjectVal(map[string]cty.Value{
  1085  						"bloop": cty.NullVal(cty.Set(
  1086  							cty.Object(map[string]cty.Type{
  1087  								"blome": cty.String,
  1088  							}),
  1089  						)),
  1090  					}),
  1091  				}),
  1092  			}),
  1093  			// provider sends back the prior value, not matching the config
  1094  			cty.ObjectVal(map[string]cty.Value{
  1095  				"bleep": cty.ListVal([]cty.Value{
  1096  					cty.ObjectVal(map[string]cty.Value{
  1097  						"bloop": cty.SetVal([]cty.Value{
  1098  							cty.ObjectVal(map[string]cty.Value{
  1099  								"blome": cty.StringVal("ok"),
  1100  							}),
  1101  						}),
  1102  					}),
  1103  				}),
  1104  			}),
  1105  			nil, // we cannot validate individual set elements, and trust the provider's response
  1106  		},
  1107  		"NestedType nested computed list attribute": {
  1108  			&configschema.Block{
  1109  				Attributes: map[string]*configschema.Attribute{
  1110  					"bloop": {
  1111  						NestedType: &configschema.Object{
  1112  							Nesting: configschema.NestingList,
  1113  							Attributes: map[string]*configschema.Attribute{
  1114  								"blop": {
  1115  									Type:     cty.String,
  1116  									Optional: true,
  1117  								},
  1118  							},
  1119  						},
  1120  						Computed: true,
  1121  					},
  1122  				},
  1123  			},
  1124  			cty.ObjectVal(map[string]cty.Value{
  1125  				"bloop": cty.ListVal([]cty.Value{
  1126  					cty.ObjectVal(map[string]cty.Value{
  1127  						"blop": cty.StringVal("ok"),
  1128  					}),
  1129  				}),
  1130  			}),
  1131  			cty.ObjectVal(map[string]cty.Value{
  1132  				"bloop": cty.NullVal(cty.List(cty.Object(map[string]cty.Type{
  1133  					"blop": cty.String,
  1134  				}))),
  1135  			}),
  1136  
  1137  			cty.ObjectVal(map[string]cty.Value{
  1138  				"bloop": cty.ListVal([]cty.Value{
  1139  					cty.ObjectVal(map[string]cty.Value{
  1140  						"blop": cty.StringVal("ok"),
  1141  					}),
  1142  				}),
  1143  			}),
  1144  			nil,
  1145  		},
  1146  		"NestedType nested list attribute to null": {
  1147  			&configschema.Block{
  1148  				Attributes: map[string]*configschema.Attribute{
  1149  					"bloop": {
  1150  						NestedType: &configschema.Object{
  1151  							Nesting: configschema.NestingList,
  1152  							Attributes: map[string]*configschema.Attribute{
  1153  								"blop": {
  1154  									Type:     cty.String,
  1155  									Optional: true,
  1156  								},
  1157  							},
  1158  						},
  1159  						Optional: true,
  1160  					},
  1161  				},
  1162  			},
  1163  			cty.ObjectVal(map[string]cty.Value{
  1164  				"bloop": cty.ListVal([]cty.Value{
  1165  					cty.ObjectVal(map[string]cty.Value{
  1166  						"blop": cty.StringVal("ok"),
  1167  					}),
  1168  				}),
  1169  			}),
  1170  			cty.ObjectVal(map[string]cty.Value{
  1171  				"bloop": cty.NullVal(cty.List(cty.Object(map[string]cty.Type{
  1172  					"blop": cty.String,
  1173  				}))),
  1174  			}),
  1175  
  1176  			// provider returned the old value
  1177  			cty.ObjectVal(map[string]cty.Value{
  1178  				"bloop": cty.ListVal([]cty.Value{
  1179  					cty.ObjectVal(map[string]cty.Value{
  1180  						"blop": cty.StringVal("ok"),
  1181  					}),
  1182  				}),
  1183  			}),
  1184  			[]string{`.bloop: planned value cty.ListVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{"blop":cty.StringVal("ok")})}) for a non-computed attribute`},
  1185  		},
  1186  		"NestedType nested set attribute to null": {
  1187  			&configschema.Block{
  1188  				Attributes: map[string]*configschema.Attribute{
  1189  					"bloop": {
  1190  						NestedType: &configschema.Object{
  1191  							Nesting: configschema.NestingSet,
  1192  							Attributes: map[string]*configschema.Attribute{
  1193  								"blop": {
  1194  									Type:     cty.String,
  1195  									Optional: true,
  1196  								},
  1197  							},
  1198  						},
  1199  						Optional: true,
  1200  					},
  1201  				},
  1202  			},
  1203  			cty.ObjectVal(map[string]cty.Value{
  1204  				"bloop": cty.SetVal([]cty.Value{
  1205  					cty.ObjectVal(map[string]cty.Value{
  1206  						"blop": cty.StringVal("ok"),
  1207  					}),
  1208  				}),
  1209  			}),
  1210  			cty.ObjectVal(map[string]cty.Value{
  1211  				"bloop": cty.NullVal(cty.Set(cty.Object(map[string]cty.Type{
  1212  					"blop": cty.String,
  1213  				}))),
  1214  			}),
  1215  			// provider returned the old value
  1216  			cty.ObjectVal(map[string]cty.Value{
  1217  				"bloop": cty.ListVal([]cty.Value{
  1218  					cty.ObjectVal(map[string]cty.Value{
  1219  						"blop": cty.StringVal("ok"),
  1220  					}),
  1221  				}),
  1222  			}),
  1223  			[]string{`.bloop: planned value cty.ListVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{"blop":cty.StringVal("ok")})}) for a non-computed attribute`},
  1224  		},
  1225  		"computed within nested objects": {
  1226  			&configschema.Block{
  1227  				Attributes: map[string]*configschema.Attribute{
  1228  					"map": {
  1229  						NestedType: &configschema.Object{
  1230  							Nesting: configschema.NestingMap,
  1231  							Attributes: map[string]*configschema.Attribute{
  1232  								"name": {
  1233  									Type:     cty.String,
  1234  									Computed: true,
  1235  								},
  1236  							},
  1237  						},
  1238  					},
  1239  					// When an object has dynamic attrs, the map may be
  1240  					// handled as an object.
  1241  					"map_as_obj": {
  1242  						NestedType: &configschema.Object{
  1243  							Nesting: configschema.NestingMap,
  1244  							Attributes: map[string]*configschema.Attribute{
  1245  								"name": {
  1246  									Type:     cty.String,
  1247  									Computed: true,
  1248  								},
  1249  							},
  1250  						},
  1251  					},
  1252  					"list": {
  1253  						NestedType: &configschema.Object{
  1254  							Nesting: configschema.NestingList,
  1255  							Attributes: map[string]*configschema.Attribute{
  1256  								"name": {
  1257  									Type:     cty.String,
  1258  									Computed: true,
  1259  								},
  1260  							},
  1261  						},
  1262  					},
  1263  					"set": {
  1264  						NestedType: &configschema.Object{
  1265  							Nesting: configschema.NestingSet,
  1266  							Attributes: map[string]*configschema.Attribute{
  1267  								"name": {
  1268  									Type:     cty.String,
  1269  									Computed: true,
  1270  								},
  1271  							},
  1272  						},
  1273  					},
  1274  					"single": {
  1275  						NestedType: &configschema.Object{
  1276  							Nesting: configschema.NestingSingle,
  1277  							Attributes: map[string]*configschema.Attribute{
  1278  								"name": {
  1279  									Type:     cty.DynamicPseudoType,
  1280  									Computed: true,
  1281  								},
  1282  							},
  1283  						},
  1284  					},
  1285  				},
  1286  			},
  1287  			cty.NullVal(cty.Object(map[string]cty.Type{
  1288  				"map": cty.Map(cty.Object(map[string]cty.Type{
  1289  					"name": cty.String,
  1290  				})),
  1291  				"map_as_obj": cty.Map(cty.Object(map[string]cty.Type{
  1292  					"name": cty.DynamicPseudoType,
  1293  				})),
  1294  				"list": cty.List(cty.Object(map[string]cty.Type{
  1295  					"name": cty.String,
  1296  				})),
  1297  				"set": cty.Set(cty.Object(map[string]cty.Type{
  1298  					"name": cty.String,
  1299  				})),
  1300  				"single": cty.Object(map[string]cty.Type{
  1301  					"name": cty.String,
  1302  				}),
  1303  			})),
  1304  			cty.ObjectVal(map[string]cty.Value{
  1305  				"map": cty.MapVal(map[string]cty.Value{
  1306  					"one": cty.ObjectVal(map[string]cty.Value{
  1307  						"name": cty.NullVal(cty.String),
  1308  					}),
  1309  				}),
  1310  				"map_as_obj": cty.MapVal(map[string]cty.Value{
  1311  					"one": cty.ObjectVal(map[string]cty.Value{
  1312  						"name": cty.NullVal(cty.DynamicPseudoType),
  1313  					}),
  1314  				}),
  1315  				"list": cty.ListVal([]cty.Value{
  1316  					cty.ObjectVal(map[string]cty.Value{
  1317  						"name": cty.NullVal(cty.String),
  1318  					}),
  1319  				}),
  1320  				"set": cty.SetVal([]cty.Value{
  1321  					cty.ObjectVal(map[string]cty.Value{
  1322  						"name": cty.NullVal(cty.String),
  1323  					}),
  1324  				}),
  1325  				"single": cty.ObjectVal(map[string]cty.Value{
  1326  					"name": cty.NullVal(cty.String),
  1327  				}),
  1328  			}),
  1329  			cty.ObjectVal(map[string]cty.Value{
  1330  				"map": cty.MapVal(map[string]cty.Value{
  1331  					"one": cty.ObjectVal(map[string]cty.Value{
  1332  						"name": cty.NullVal(cty.String),
  1333  					}),
  1334  				}),
  1335  				"map_as_obj": cty.ObjectVal(map[string]cty.Value{
  1336  					"one": cty.ObjectVal(map[string]cty.Value{
  1337  						"name": cty.StringVal("computed"),
  1338  					}),
  1339  				}),
  1340  				"list": cty.ListVal([]cty.Value{
  1341  					cty.ObjectVal(map[string]cty.Value{
  1342  						"name": cty.NullVal(cty.String),
  1343  					}),
  1344  				}),
  1345  				"set": cty.SetVal([]cty.Value{
  1346  					cty.ObjectVal(map[string]cty.Value{
  1347  						"name": cty.NullVal(cty.String),
  1348  					}),
  1349  				}),
  1350  				"single": cty.ObjectVal(map[string]cty.Value{
  1351  					"name": cty.NullVal(cty.String),
  1352  				}),
  1353  			}),
  1354  			nil,
  1355  		},
  1356  		"computed nested objects": {
  1357  			&configschema.Block{
  1358  				Attributes: map[string]*configschema.Attribute{
  1359  					"map": {
  1360  						NestedType: &configschema.Object{
  1361  							Nesting: configschema.NestingMap,
  1362  							Attributes: map[string]*configschema.Attribute{
  1363  								"name": {
  1364  									Type: cty.String,
  1365  								},
  1366  							},
  1367  						},
  1368  						Computed: true,
  1369  					},
  1370  					"list": {
  1371  						NestedType: &configschema.Object{
  1372  							Nesting: configschema.NestingList,
  1373  							Attributes: map[string]*configschema.Attribute{
  1374  								"name": {
  1375  									Type: cty.String,
  1376  								},
  1377  							},
  1378  						},
  1379  						Computed: true,
  1380  					},
  1381  					"set": {
  1382  						NestedType: &configschema.Object{
  1383  							Nesting: configschema.NestingSet,
  1384  							Attributes: map[string]*configschema.Attribute{
  1385  								"name": {
  1386  									Type: cty.String,
  1387  								},
  1388  							},
  1389  						},
  1390  						Optional: true,
  1391  						Computed: true,
  1392  					},
  1393  					"single": {
  1394  						NestedType: &configschema.Object{
  1395  							Nesting: configschema.NestingSingle,
  1396  							Attributes: map[string]*configschema.Attribute{
  1397  								"name": {
  1398  									Type: cty.DynamicPseudoType,
  1399  								},
  1400  							},
  1401  						},
  1402  						Computed: true,
  1403  					},
  1404  				},
  1405  			},
  1406  			cty.NullVal(cty.Object(map[string]cty.Type{
  1407  				"map": cty.Map(cty.Object(map[string]cty.Type{
  1408  					"name": cty.String,
  1409  				})),
  1410  				"list": cty.List(cty.Object(map[string]cty.Type{
  1411  					"name": cty.String,
  1412  				})),
  1413  				"set": cty.Set(cty.Object(map[string]cty.Type{
  1414  					"name": cty.String,
  1415  				})),
  1416  				"single": cty.Object(map[string]cty.Type{
  1417  					"name": cty.String,
  1418  				}),
  1419  			})),
  1420  			cty.ObjectVal(map[string]cty.Value{
  1421  				"map": cty.NullVal(cty.Map(cty.Object(map[string]cty.Type{
  1422  					"name": cty.String,
  1423  				}))),
  1424  				"list": cty.NullVal(cty.List(cty.Object(map[string]cty.Type{
  1425  					"name": cty.String,
  1426  				}))),
  1427  				"set": cty.SetVal([]cty.Value{
  1428  					cty.ObjectVal(map[string]cty.Value{
  1429  						"name": cty.StringVal("from_config"),
  1430  					}),
  1431  				}),
  1432  				"single": cty.NullVal(cty.Object(map[string]cty.Type{
  1433  					"name": cty.String,
  1434  				})),
  1435  			}),
  1436  			cty.ObjectVal(map[string]cty.Value{
  1437  				"map": cty.MapVal(map[string]cty.Value{
  1438  					"one": cty.UnknownVal(cty.Object(map[string]cty.Type{
  1439  						"name": cty.String,
  1440  					})),
  1441  				}),
  1442  				"list": cty.ListVal([]cty.Value{
  1443  					cty.ObjectVal(map[string]cty.Value{
  1444  						"name": cty.StringVal("computed"),
  1445  					}),
  1446  				}),
  1447  				"set": cty.SetVal([]cty.Value{
  1448  					cty.ObjectVal(map[string]cty.Value{
  1449  						"name": cty.StringVal("from_config"),
  1450  					}),
  1451  				}),
  1452  				"single": cty.UnknownVal(cty.Object(map[string]cty.Type{
  1453  					"name": cty.String,
  1454  				})),
  1455  			}),
  1456  			nil,
  1457  		},
  1458  		"optional computed within nested objects": {
  1459  			&configschema.Block{
  1460  				Attributes: map[string]*configschema.Attribute{
  1461  					"map": {
  1462  						NestedType: &configschema.Object{
  1463  							Nesting: configschema.NestingMap,
  1464  							Attributes: map[string]*configschema.Attribute{
  1465  								"name": {
  1466  									Type:     cty.String,
  1467  									Computed: true,
  1468  								},
  1469  							},
  1470  						},
  1471  					},
  1472  					// When an object has dynamic attrs, the map may be
  1473  					// handled as an object.
  1474  					"map_as_obj": {
  1475  						NestedType: &configschema.Object{
  1476  							Nesting: configschema.NestingMap,
  1477  							Attributes: map[string]*configschema.Attribute{
  1478  								"name": {
  1479  									Type:     cty.String,
  1480  									Optional: true,
  1481  									Computed: true,
  1482  								},
  1483  							},
  1484  						},
  1485  					},
  1486  					"list": {
  1487  						NestedType: &configschema.Object{
  1488  							Nesting: configschema.NestingList,
  1489  							Attributes: map[string]*configschema.Attribute{
  1490  								"name": {
  1491  									Type:     cty.String,
  1492  									Optional: true,
  1493  									Computed: true,
  1494  								},
  1495  							},
  1496  						},
  1497  					},
  1498  					"set": {
  1499  						NestedType: &configschema.Object{
  1500  							Nesting: configschema.NestingSet,
  1501  							Attributes: map[string]*configschema.Attribute{
  1502  								"name": {
  1503  									Type:     cty.String,
  1504  									Optional: true,
  1505  									Computed: true,
  1506  								},
  1507  							},
  1508  						},
  1509  					},
  1510  					"single": {
  1511  						NestedType: &configschema.Object{
  1512  							Nesting: configschema.NestingSingle,
  1513  							Attributes: map[string]*configschema.Attribute{
  1514  								"name": {
  1515  									Type:     cty.DynamicPseudoType,
  1516  									Optional: true,
  1517  									Computed: true,
  1518  								},
  1519  							},
  1520  						},
  1521  					},
  1522  				},
  1523  			},
  1524  			cty.NullVal(cty.Object(map[string]cty.Type{
  1525  				"map": cty.Map(cty.Object(map[string]cty.Type{
  1526  					"name": cty.String,
  1527  				})),
  1528  				"map_as_obj": cty.Map(cty.Object(map[string]cty.Type{
  1529  					"name": cty.DynamicPseudoType,
  1530  				})),
  1531  				"list": cty.List(cty.Object(map[string]cty.Type{
  1532  					"name": cty.String,
  1533  				})),
  1534  				"set": cty.Set(cty.Object(map[string]cty.Type{
  1535  					"name": cty.String,
  1536  				})),
  1537  				"single": cty.Object(map[string]cty.Type{
  1538  					"name": cty.String,
  1539  				}),
  1540  			})),
  1541  			cty.ObjectVal(map[string]cty.Value{
  1542  				"map": cty.MapVal(map[string]cty.Value{
  1543  					"one": cty.ObjectVal(map[string]cty.Value{
  1544  						"name": cty.StringVal("from_config"),
  1545  					}),
  1546  				}),
  1547  				"map_as_obj": cty.MapVal(map[string]cty.Value{
  1548  					"one": cty.ObjectVal(map[string]cty.Value{
  1549  						"name": cty.NullVal(cty.DynamicPseudoType),
  1550  					}),
  1551  				}),
  1552  				"list": cty.ListVal([]cty.Value{
  1553  					cty.ObjectVal(map[string]cty.Value{
  1554  						"name": cty.NullVal(cty.String),
  1555  					}),
  1556  				}),
  1557  				"set": cty.SetVal([]cty.Value{
  1558  					cty.ObjectVal(map[string]cty.Value{
  1559  						"name": cty.NullVal(cty.String),
  1560  					}),
  1561  				}),
  1562  				"single": cty.ObjectVal(map[string]cty.Value{
  1563  					"name": cty.StringVal("from_config"),
  1564  				}),
  1565  			}),
  1566  			cty.ObjectVal(map[string]cty.Value{
  1567  				"map": cty.MapVal(map[string]cty.Value{
  1568  					"one": cty.ObjectVal(map[string]cty.Value{
  1569  						"name": cty.StringVal("from_config"),
  1570  					}),
  1571  				}),
  1572  				"map_as_obj": cty.ObjectVal(map[string]cty.Value{
  1573  					"one": cty.ObjectVal(map[string]cty.Value{
  1574  						"name": cty.StringVal("computed"),
  1575  					}),
  1576  				}),
  1577  				"list": cty.ListVal([]cty.Value{
  1578  					cty.ObjectVal(map[string]cty.Value{
  1579  						"name": cty.StringVal("computed"),
  1580  					}),
  1581  				}),
  1582  				"set": cty.SetVal([]cty.Value{
  1583  					cty.ObjectVal(map[string]cty.Value{
  1584  						"name": cty.NullVal(cty.String),
  1585  					}),
  1586  				}),
  1587  				"single": cty.ObjectVal(map[string]cty.Value{
  1588  					"name": cty.StringVal("from_config"),
  1589  				}),
  1590  			}),
  1591  			nil,
  1592  		},
  1593  		"cannot replace config nested attr": {
  1594  			&configschema.Block{
  1595  				Attributes: map[string]*configschema.Attribute{
  1596  					"map": {
  1597  						NestedType: &configschema.Object{
  1598  							Nesting: configschema.NestingMap,
  1599  							Attributes: map[string]*configschema.Attribute{
  1600  								"name": {
  1601  									Type:     cty.String,
  1602  									Computed: true,
  1603  									Optional: true,
  1604  								},
  1605  							},
  1606  						},
  1607  					},
  1608  				},
  1609  			},
  1610  			cty.NullVal(cty.Object(map[string]cty.Type{
  1611  				"map": cty.Map(cty.Object(map[string]cty.Type{
  1612  					"name": cty.String,
  1613  				})),
  1614  			})),
  1615  			cty.ObjectVal(map[string]cty.Value{
  1616  				"map": cty.MapVal(map[string]cty.Value{
  1617  					"one": cty.ObjectVal(map[string]cty.Value{
  1618  						"name": cty.StringVal("from_config"),
  1619  					}),
  1620  				}),
  1621  			}),
  1622  			cty.ObjectVal(map[string]cty.Value{
  1623  				"map": cty.MapVal(map[string]cty.Value{
  1624  					"one": cty.ObjectVal(map[string]cty.Value{
  1625  						"name": cty.StringVal("from_provider"),
  1626  					}),
  1627  				}),
  1628  			}),
  1629  			[]string{`.map.one.name: planned value cty.StringVal("from_provider") does not match config value cty.StringVal("from_config")`},
  1630  		},
  1631  
  1632  		// If a config value ended up in a computed-only attribute it can still
  1633  		// be a valid plan. We either got here because the user ignore warnings
  1634  		// about ignore_changes on computed attributes, or we failed to
  1635  		// validate a config with computed values. Either way, we don't want to
  1636  		// indicate an error with the provider.
  1637  		"computed only value with config": {
  1638  			&configschema.Block{
  1639  				Attributes: map[string]*configschema.Attribute{
  1640  					"a": {
  1641  						Type:     cty.String,
  1642  						Computed: true,
  1643  					},
  1644  				},
  1645  			},
  1646  			cty.ObjectVal(map[string]cty.Value{
  1647  				"a": cty.StringVal("old"),
  1648  			}),
  1649  			cty.ObjectVal(map[string]cty.Value{
  1650  				"a": cty.StringVal("old"),
  1651  			}),
  1652  			cty.ObjectVal(map[string]cty.Value{
  1653  				"a": cty.UnknownVal(cty.String),
  1654  			}),
  1655  			nil,
  1656  		},
  1657  	}
  1658  
  1659  	for name, test := range tests {
  1660  		t.Run(name, func(t *testing.T) {
  1661  			errs := AssertPlanValid(test.Schema, test.Prior, test.Config, test.Planned)
  1662  
  1663  			wantErrs := make(map[string]struct{})
  1664  			gotErrs := make(map[string]struct{})
  1665  			for _, err := range errs {
  1666  				gotErrs[tfdiags.FormatError(err)] = struct{}{}
  1667  			}
  1668  			for _, msg := range test.WantErrs {
  1669  				wantErrs[msg] = struct{}{}
  1670  			}
  1671  
  1672  			t.Logf(
  1673  				"\nprior:  %sconfig:  %splanned: %s",
  1674  				dump.Value(test.Planned),
  1675  				dump.Value(test.Config),
  1676  				dump.Value(test.Planned),
  1677  			)
  1678  			for msg := range wantErrs {
  1679  				if _, ok := gotErrs[msg]; !ok {
  1680  					t.Errorf("missing expected error: %s", msg)
  1681  				}
  1682  			}
  1683  			for msg := range gotErrs {
  1684  				if _, ok := wantErrs[msg]; !ok {
  1685  					t.Errorf("unexpected extra error: %s", msg)
  1686  				}
  1687  			}
  1688  		})
  1689  	}
  1690  }