github.com/opentofu/opentofu@v1.7.1/internal/tofu/reduce_plan_test.go (about)

     1  // Copyright (c) The OpenTofu Authors
     2  // SPDX-License-Identifier: MPL-2.0
     3  // Copyright (c) 2023 HashiCorp, Inc.
     4  // SPDX-License-Identifier: MPL-2.0
     5  
     6  package tofu
     7  
     8  import (
     9  	"testing"
    10  
    11  	"github.com/hashicorp/hcl/v2"
    12  	"github.com/hashicorp/hcl/v2/hclsyntax"
    13  	"github.com/zclconf/go-cty/cty"
    14  )
    15  
    16  func TestProcessIgnoreChangesIndividual(t *testing.T) {
    17  	tests := map[string]struct {
    18  		Old, New cty.Value
    19  		Ignore   []string
    20  		Want     cty.Value
    21  	}{
    22  		"string": {
    23  			cty.ObjectVal(map[string]cty.Value{
    24  				"a": cty.StringVal("a value"),
    25  				"b": cty.StringVal("b value"),
    26  			}),
    27  			cty.ObjectVal(map[string]cty.Value{
    28  				"a": cty.StringVal("new a value"),
    29  				"b": cty.StringVal("new b value"),
    30  			}),
    31  			[]string{"a"},
    32  			cty.ObjectVal(map[string]cty.Value{
    33  				"a": cty.StringVal("a value"),
    34  				"b": cty.StringVal("new b value"),
    35  			}),
    36  		},
    37  		"changed type": {
    38  			cty.ObjectVal(map[string]cty.Value{
    39  				"a": cty.StringVal("a value"),
    40  				"b": cty.StringVal("b value"),
    41  			}),
    42  			cty.ObjectVal(map[string]cty.Value{
    43  				"a": cty.NumberIntVal(1),
    44  				"b": cty.StringVal("new b value"),
    45  			}),
    46  			[]string{"a"},
    47  			cty.ObjectVal(map[string]cty.Value{
    48  				"a": cty.StringVal("a value"),
    49  				"b": cty.StringVal("new b value"),
    50  			}),
    51  		},
    52  		"list": {
    53  			cty.ObjectVal(map[string]cty.Value{
    54  				"a": cty.ListVal([]cty.Value{
    55  					cty.StringVal("a0 value"),
    56  					cty.StringVal("a1 value"),
    57  				}),
    58  				"b": cty.StringVal("b value"),
    59  			}),
    60  			cty.ObjectVal(map[string]cty.Value{
    61  				"a": cty.ListVal([]cty.Value{
    62  					cty.StringVal("new a0 value"),
    63  					cty.StringVal("new a1 value"),
    64  				}),
    65  				"b": cty.StringVal("new b value"),
    66  			}),
    67  			[]string{"a"},
    68  			cty.ObjectVal(map[string]cty.Value{
    69  				"a": cty.ListVal([]cty.Value{
    70  					cty.StringVal("a0 value"),
    71  					cty.StringVal("a1 value"),
    72  				}),
    73  				"b": cty.StringVal("new b value"),
    74  			}),
    75  		},
    76  		"list_index": {
    77  			cty.ObjectVal(map[string]cty.Value{
    78  				"a": cty.ListVal([]cty.Value{
    79  					cty.StringVal("a0 value"),
    80  					cty.StringVal("a1 value"),
    81  				}),
    82  				"b": cty.StringVal("b value"),
    83  			}),
    84  			cty.ObjectVal(map[string]cty.Value{
    85  				"a": cty.ListVal([]cty.Value{
    86  					cty.StringVal("new a0 value"),
    87  					cty.StringVal("new a1 value"),
    88  				}),
    89  				"b": cty.StringVal("new b value"),
    90  			}),
    91  			[]string{"a[1]"},
    92  			cty.ObjectVal(map[string]cty.Value{
    93  				"a": cty.ListVal([]cty.Value{
    94  					cty.StringVal("new a0 value"),
    95  					cty.StringVal("a1 value"),
    96  				}),
    97  				"b": cty.StringVal("new b value"),
    98  			}),
    99  		},
   100  		"map": {
   101  			cty.ObjectVal(map[string]cty.Value{
   102  				"a": cty.MapVal(map[string]cty.Value{
   103  					"a0": cty.StringVal("a0 value"),
   104  					"a1": cty.StringVal("a1 value"),
   105  				}),
   106  				"b": cty.StringVal("b value"),
   107  			}),
   108  			cty.ObjectVal(map[string]cty.Value{
   109  				"a": cty.MapVal(map[string]cty.Value{
   110  					"a0": cty.StringVal("new a0 value"),
   111  					"a1": cty.UnknownVal(cty.String),
   112  				}),
   113  				"b": cty.StringVal("b value"),
   114  			}),
   115  			[]string{`a`},
   116  			cty.ObjectVal(map[string]cty.Value{
   117  				"a": cty.MapVal(map[string]cty.Value{
   118  					"a0": cty.StringVal("a0 value"),
   119  					"a1": cty.StringVal("a1 value"),
   120  				}),
   121  				"b": cty.StringVal("b value"),
   122  			}),
   123  		},
   124  		"map_index": {
   125  			cty.ObjectVal(map[string]cty.Value{
   126  				"a": cty.MapVal(map[string]cty.Value{
   127  					"a0": cty.StringVal("a0 value"),
   128  					"a1": cty.StringVal("a1 value"),
   129  				}),
   130  				"b": cty.StringVal("b value"),
   131  			}),
   132  			cty.ObjectVal(map[string]cty.Value{
   133  				"a": cty.MapVal(map[string]cty.Value{
   134  					"a0": cty.StringVal("new a0 value"),
   135  					"a1": cty.StringVal("new a1 value"),
   136  				}),
   137  				"b": cty.StringVal("b value"),
   138  			}),
   139  			[]string{`a["a1"]`},
   140  			cty.ObjectVal(map[string]cty.Value{
   141  				"a": cty.MapVal(map[string]cty.Value{
   142  					"a0": cty.StringVal("new a0 value"),
   143  					"a1": cty.StringVal("a1 value"),
   144  				}),
   145  				"b": cty.StringVal("b value"),
   146  			}),
   147  		},
   148  		"map_index_no_config": {
   149  			cty.ObjectVal(map[string]cty.Value{
   150  				"a": cty.MapVal(map[string]cty.Value{
   151  					"a0": cty.StringVal("a0 value"),
   152  					"a1": cty.StringVal("a1 value"),
   153  				}),
   154  				"b": cty.StringVal("b value"),
   155  			}),
   156  			cty.ObjectVal(map[string]cty.Value{
   157  				"a": cty.NullVal(cty.Map(cty.String)),
   158  				"b": cty.StringVal("b value"),
   159  			}),
   160  			[]string{`a["a1"]`},
   161  			cty.ObjectVal(map[string]cty.Value{
   162  				"a": cty.MapVal(map[string]cty.Value{
   163  					"a1": cty.StringVal("a1 value"),
   164  				}),
   165  				"b": cty.StringVal("b value"),
   166  			}),
   167  		},
   168  		"map_index_unknown_value": {
   169  			cty.ObjectVal(map[string]cty.Value{
   170  				"a": cty.MapVal(map[string]cty.Value{
   171  					"a0": cty.StringVal("a0 value"),
   172  					"a1": cty.StringVal("a1 value"),
   173  				}),
   174  				"b": cty.StringVal("b value"),
   175  			}),
   176  			cty.ObjectVal(map[string]cty.Value{
   177  				"a": cty.MapVal(map[string]cty.Value{
   178  					"a0": cty.StringVal("a0 value"),
   179  					"a1": cty.UnknownVal(cty.String),
   180  				}),
   181  				"b": cty.StringVal("b value"),
   182  			}),
   183  			[]string{`a["a1"]`},
   184  			cty.ObjectVal(map[string]cty.Value{
   185  				"a": cty.MapVal(map[string]cty.Value{
   186  					"a0": cty.StringVal("a0 value"),
   187  					"a1": cty.StringVal("a1 value"),
   188  				}),
   189  				"b": cty.StringVal("b value"),
   190  			}),
   191  		},
   192  		"map_index_multiple_keys": {
   193  			cty.ObjectVal(map[string]cty.Value{
   194  				"a": cty.MapVal(map[string]cty.Value{
   195  					"a0": cty.StringVal("a0 value"),
   196  					"a1": cty.StringVal("a1 value"),
   197  					"a2": cty.StringVal("a2 value"),
   198  					"a3": cty.StringVal("a3 value"),
   199  				}),
   200  				"b": cty.StringVal("b value"),
   201  			}),
   202  			cty.ObjectVal(map[string]cty.Value{
   203  				"a": cty.NullVal(cty.Map(cty.String)),
   204  				"b": cty.StringVal("new b value"),
   205  			}),
   206  			[]string{`a["a1"]`, `a["a2"]`, `a["a3"]`, `b`},
   207  			cty.ObjectVal(map[string]cty.Value{
   208  				"a": cty.MapVal(map[string]cty.Value{
   209  					"a1": cty.StringVal("a1 value"),
   210  					"a2": cty.StringVal("a2 value"),
   211  					"a3": cty.StringVal("a3 value"),
   212  				}),
   213  				"b": cty.StringVal("b value"),
   214  			}),
   215  		},
   216  		"map_index_redundant": {
   217  			cty.ObjectVal(map[string]cty.Value{
   218  				"a": cty.MapVal(map[string]cty.Value{
   219  					"a0": cty.StringVal("a0 value"),
   220  					"a1": cty.StringVal("a1 value"),
   221  					"a2": cty.StringVal("a2 value"),
   222  				}),
   223  				"b": cty.StringVal("b value"),
   224  			}),
   225  			cty.ObjectVal(map[string]cty.Value{
   226  				"a": cty.NullVal(cty.Map(cty.String)),
   227  				"b": cty.StringVal("new b value"),
   228  			}),
   229  			[]string{`a["a1"]`, `a`, `b`},
   230  			cty.ObjectVal(map[string]cty.Value{
   231  				"a": cty.MapVal(map[string]cty.Value{
   232  					"a0": cty.StringVal("a0 value"),
   233  					"a1": cty.StringVal("a1 value"),
   234  					"a2": cty.StringVal("a2 value"),
   235  				}),
   236  				"b": cty.StringVal("b value"),
   237  			}),
   238  		},
   239  		"missing_map_index": {
   240  			cty.ObjectVal(map[string]cty.Value{
   241  				"a": cty.MapVal(map[string]cty.Value{
   242  					"a0": cty.StringVal("a0 value"),
   243  					"a1": cty.StringVal("a1 value"),
   244  				}),
   245  				"b": cty.StringVal("b value"),
   246  			}),
   247  			cty.ObjectVal(map[string]cty.Value{
   248  				"a": cty.MapValEmpty(cty.String),
   249  				"b": cty.StringVal("b value"),
   250  			}),
   251  			[]string{`a["a1"]`},
   252  			cty.ObjectVal(map[string]cty.Value{
   253  				"a": cty.MapVal(map[string]cty.Value{
   254  					"a1": cty.StringVal("a1 value"),
   255  				}),
   256  				"b": cty.StringVal("b value"),
   257  			}),
   258  		},
   259  		"missing_map_index_empty": {
   260  			cty.ObjectVal(map[string]cty.Value{
   261  				"a": cty.MapValEmpty(cty.String),
   262  			}),
   263  			cty.ObjectVal(map[string]cty.Value{
   264  				"a": cty.MapVal(map[string]cty.Value{
   265  					"a": cty.StringVal("a0 value"),
   266  				}),
   267  			}),
   268  			[]string{`a["a"]`},
   269  			cty.ObjectVal(map[string]cty.Value{
   270  				"a": cty.MapValEmpty(cty.String),
   271  			}),
   272  		},
   273  		"missing_map_index_to_object": {
   274  			cty.ObjectVal(map[string]cty.Value{
   275  				"a": cty.MapVal(map[string]cty.Value{
   276  					"a": cty.ObjectVal(map[string]cty.Value{
   277  						"a": cty.StringVal("aa0"),
   278  						"b": cty.StringVal("ab0"),
   279  					}),
   280  					"b": cty.ObjectVal(map[string]cty.Value{
   281  						"a": cty.StringVal("ba0"),
   282  						"b": cty.StringVal("bb0"),
   283  					}),
   284  				}),
   285  			}),
   286  			cty.ObjectVal(map[string]cty.Value{
   287  				"a": cty.MapValEmpty(
   288  					cty.Object(map[string]cty.Type{
   289  						"a": cty.String,
   290  						"b": cty.String,
   291  					}),
   292  				),
   293  			}),
   294  			// we expect the config to be used here, as the ignore changes was
   295  			// `a["a"].b`, but the change was larger than that removing
   296  			// `a["a"]` entirely.
   297  			[]string{`a["a"].b`},
   298  			cty.ObjectVal(map[string]cty.Value{
   299  				"a": cty.MapValEmpty(
   300  					cty.Object(map[string]cty.Type{
   301  						"a": cty.String,
   302  						"b": cty.String,
   303  					}),
   304  				),
   305  			}),
   306  		},
   307  		"missing_prior_map_index": {
   308  			cty.ObjectVal(map[string]cty.Value{
   309  				"a": cty.MapVal(map[string]cty.Value{
   310  					"a0": cty.StringVal("a0 value"),
   311  				}),
   312  				"b": cty.StringVal("b value"),
   313  			}),
   314  			cty.ObjectVal(map[string]cty.Value{
   315  				"a": cty.MapVal(map[string]cty.Value{
   316  					"a0": cty.StringVal("a0 value"),
   317  					"a1": cty.StringVal("new a1 value"),
   318  				}),
   319  				"b": cty.StringVal("b value"),
   320  			}),
   321  			[]string{`a["a1"]`},
   322  			cty.ObjectVal(map[string]cty.Value{
   323  				"a": cty.MapVal(map[string]cty.Value{
   324  					"a0": cty.StringVal("a0 value"),
   325  				}),
   326  				"b": cty.StringVal("b value"),
   327  			}),
   328  		},
   329  		"object attribute": {
   330  			cty.ObjectVal(map[string]cty.Value{
   331  				"a": cty.ObjectVal(map[string]cty.Value{
   332  					"foo": cty.StringVal("a.foo value"),
   333  					"bar": cty.StringVal("a.bar value"),
   334  				}),
   335  				"b": cty.StringVal("b value"),
   336  			}),
   337  			cty.ObjectVal(map[string]cty.Value{
   338  				"a": cty.ObjectVal(map[string]cty.Value{
   339  					"foo": cty.StringVal("new a.foo value"),
   340  					"bar": cty.StringVal("new a.bar value"),
   341  				}),
   342  				"b": cty.StringVal("new b value"),
   343  			}),
   344  			[]string{"a.bar"},
   345  			cty.ObjectVal(map[string]cty.Value{
   346  				"a": cty.ObjectVal(map[string]cty.Value{
   347  					"foo": cty.StringVal("new a.foo value"),
   348  					"bar": cty.StringVal("a.bar value"),
   349  				}),
   350  				"b": cty.StringVal("new b value"),
   351  			}),
   352  		},
   353  		"unknown_object_attribute": {
   354  			cty.ObjectVal(map[string]cty.Value{
   355  				"a": cty.ObjectVal(map[string]cty.Value{
   356  					"foo": cty.StringVal("a.foo value"),
   357  					"bar": cty.StringVal("a.bar value"),
   358  				}),
   359  				"b": cty.StringVal("b value"),
   360  			}),
   361  			cty.ObjectVal(map[string]cty.Value{
   362  				"a": cty.ObjectVal(map[string]cty.Value{
   363  					"foo": cty.StringVal("new a.foo value"),
   364  					"bar": cty.UnknownVal(cty.String),
   365  				}),
   366  				"b": cty.StringVal("new b value"),
   367  			}),
   368  			[]string{"a.bar"},
   369  			cty.ObjectVal(map[string]cty.Value{
   370  				"a": cty.ObjectVal(map[string]cty.Value{
   371  					"foo": cty.StringVal("new a.foo value"),
   372  					"bar": cty.StringVal("a.bar value"),
   373  				}),
   374  				"b": cty.StringVal("new b value"),
   375  			}),
   376  		},
   377  		"null_map": {
   378  			cty.ObjectVal(map[string]cty.Value{
   379  				"a": cty.StringVal("ok"),
   380  				"list": cty.ListVal([]cty.Value{
   381  					cty.ObjectVal(map[string]cty.Value{
   382  						"s":   cty.StringVal("ok"),
   383  						"map": cty.NullVal(cty.Map(cty.String)),
   384  					}),
   385  				}),
   386  			}),
   387  			cty.ObjectVal(map[string]cty.Value{
   388  				"a": cty.NullVal(cty.String),
   389  				"list": cty.ListVal([]cty.Value{
   390  					cty.ObjectVal(map[string]cty.Value{
   391  						"s":   cty.StringVal("ok"),
   392  						"map": cty.NullVal(cty.Map(cty.String)),
   393  					}),
   394  				}),
   395  			}),
   396  			[]string{"a"},
   397  			cty.ObjectVal(map[string]cty.Value{
   398  				"a": cty.StringVal("ok"),
   399  				"list": cty.ListVal([]cty.Value{
   400  					cty.ObjectVal(map[string]cty.Value{
   401  						"s":   cty.StringVal("ok"),
   402  						"map": cty.NullVal(cty.Map(cty.String)),
   403  					}),
   404  				}),
   405  			}),
   406  		},
   407  		"marked_map": {
   408  			cty.ObjectVal(map[string]cty.Value{
   409  				"map": cty.MapVal(map[string]cty.Value{
   410  					"key": cty.StringVal("val"),
   411  				}).Mark("marked"),
   412  			}),
   413  			cty.ObjectVal(map[string]cty.Value{
   414  				"map": cty.MapVal(map[string]cty.Value{
   415  					"key": cty.StringVal("new val"),
   416  				}).Mark("marked"),
   417  			}),
   418  			[]string{`map["key"]`},
   419  			cty.ObjectVal(map[string]cty.Value{
   420  				"map": cty.MapVal(map[string]cty.Value{
   421  					"key": cty.StringVal("val"),
   422  				}).Mark("marked"),
   423  			}),
   424  		},
   425  	}
   426  
   427  	for name, test := range tests {
   428  		t.Run(name, func(t *testing.T) {
   429  			ignore := make([]hcl.Traversal, len(test.Ignore))
   430  			for i, ignoreStr := range test.Ignore {
   431  				trav, diags := hclsyntax.ParseTraversalAbs([]byte(ignoreStr), "", hcl.Pos{Line: 1, Column: 1})
   432  				if diags.HasErrors() {
   433  					t.Fatalf("failed to parse %q: %s", ignoreStr, diags.Error())
   434  				}
   435  				ignore[i] = trav
   436  			}
   437  
   438  			ret, diags := processIgnoreChangesIndividual(test.Old, test.New, traversalsToPaths(ignore))
   439  			if diags.HasErrors() {
   440  				t.Fatal(diags.Err())
   441  			}
   442  
   443  			if got, want := ret, test.Want; !want.RawEquals(got) {
   444  				t.Errorf("wrong result\ngot:  %#v\nwant: %#v", got, want)
   445  			}
   446  		})
   447  	}
   448  }