github.com/iaas-resource-provision/iaas-rpc@v1.0.7-0.20211021023331-ed21f798c408/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/iaas-resource-provision/iaas-rpc/internal/configs/configschema"
    10  	"github.com/iaas-resource-provision/iaas-rpc/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  	}
  1226  
  1227  	for name, test := range tests {
  1228  		t.Run(name, func(t *testing.T) {
  1229  			errs := AssertPlanValid(test.Schema, test.Prior, test.Config, test.Planned)
  1230  
  1231  			wantErrs := make(map[string]struct{})
  1232  			gotErrs := make(map[string]struct{})
  1233  			for _, err := range errs {
  1234  				gotErrs[tfdiags.FormatError(err)] = struct{}{}
  1235  			}
  1236  			for _, msg := range test.WantErrs {
  1237  				wantErrs[msg] = struct{}{}
  1238  			}
  1239  
  1240  			t.Logf(
  1241  				"\nprior:  %sconfig:  %splanned: %s",
  1242  				dump.Value(test.Planned),
  1243  				dump.Value(test.Config),
  1244  				dump.Value(test.Planned),
  1245  			)
  1246  			for msg := range wantErrs {
  1247  				if _, ok := gotErrs[msg]; !ok {
  1248  					t.Errorf("missing expected error: %s", msg)
  1249  				}
  1250  			}
  1251  			for msg := range gotErrs {
  1252  				if _, ok := wantErrs[msg]; !ok {
  1253  					t.Errorf("unexpected extra error: %s", msg)
  1254  				}
  1255  			}
  1256  		})
  1257  	}
  1258  }