github.com/opentofu/opentofu@v1.7.1/internal/legacy/helper/schema/field_reader_diff_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 schema
     7  
     8  import (
     9  	"reflect"
    10  	"testing"
    11  
    12  	"github.com/opentofu/opentofu/internal/legacy/tofu"
    13  )
    14  
    15  func TestDiffFieldReader_impl(t *testing.T) {
    16  	var _ FieldReader = new(DiffFieldReader)
    17  }
    18  
    19  func TestDiffFieldReader_NestedSetUpdate(t *testing.T) {
    20  	hashFn := func(a interface{}) int {
    21  		m := a.(map[string]interface{})
    22  		return m["val"].(int)
    23  	}
    24  
    25  	schema := map[string]*Schema{
    26  		"list_of_sets_1": &Schema{
    27  			Type: TypeList,
    28  			Elem: &Resource{
    29  				Schema: map[string]*Schema{
    30  					"nested_set": &Schema{
    31  						Type: TypeSet,
    32  						Elem: &Resource{
    33  							Schema: map[string]*Schema{
    34  								"val": &Schema{
    35  									Type: TypeInt,
    36  								},
    37  							},
    38  						},
    39  						Set: hashFn,
    40  					},
    41  				},
    42  			},
    43  		},
    44  		"list_of_sets_2": &Schema{
    45  			Type: TypeList,
    46  			Elem: &Resource{
    47  				Schema: map[string]*Schema{
    48  					"nested_set": &Schema{
    49  						Type: TypeSet,
    50  						Elem: &Resource{
    51  							Schema: map[string]*Schema{
    52  								"val": &Schema{
    53  									Type: TypeInt,
    54  								},
    55  							},
    56  						},
    57  						Set: hashFn,
    58  					},
    59  				},
    60  			},
    61  		},
    62  	}
    63  
    64  	r := &DiffFieldReader{
    65  		Schema: schema,
    66  		Diff: &tofu.InstanceDiff{
    67  			Attributes: map[string]*tofu.ResourceAttrDiff{
    68  				"list_of_sets_1.0.nested_set.1.val": &tofu.ResourceAttrDiff{
    69  					Old:        "1",
    70  					New:        "0",
    71  					NewRemoved: true,
    72  				},
    73  				"list_of_sets_1.0.nested_set.2.val": &tofu.ResourceAttrDiff{
    74  					New: "2",
    75  				},
    76  			},
    77  		},
    78  	}
    79  
    80  	r.Source = &MultiLevelFieldReader{
    81  		Readers: map[string]FieldReader{
    82  			"diff": r,
    83  			"set":  &MapFieldReader{Schema: schema},
    84  			"state": &MapFieldReader{
    85  				Map: &BasicMapReader{
    86  					"list_of_sets_1.#":                  "1",
    87  					"list_of_sets_1.0.nested_set.#":     "1",
    88  					"list_of_sets_1.0.nested_set.1.val": "1",
    89  					"list_of_sets_2.#":                  "1",
    90  					"list_of_sets_2.0.nested_set.#":     "1",
    91  					"list_of_sets_2.0.nested_set.1.val": "1",
    92  				},
    93  				Schema: schema,
    94  			},
    95  		},
    96  		Levels: []string{"state", "config"},
    97  	}
    98  
    99  	out, err := r.ReadField([]string{"list_of_sets_2"})
   100  	if err != nil {
   101  		t.Fatalf("err: %v", err)
   102  	}
   103  
   104  	s := &Set{F: hashFn}
   105  	s.Add(map[string]interface{}{"val": 1})
   106  	expected := s.List()
   107  
   108  	l := out.Value.([]interface{})
   109  	i := l[0].(map[string]interface{})
   110  	actual := i["nested_set"].(*Set).List()
   111  
   112  	if !reflect.DeepEqual(expected, actual) {
   113  		t.Fatalf("bad: NestedSetUpdate\n\nexpected: %#v\n\ngot: %#v\n\n", expected, actual)
   114  	}
   115  }
   116  
   117  // https://github.com/hashicorp/terraform/issues/914
   118  func TestDiffFieldReader_MapHandling(t *testing.T) {
   119  	schema := map[string]*Schema{
   120  		"tags": &Schema{
   121  			Type: TypeMap,
   122  		},
   123  	}
   124  	r := &DiffFieldReader{
   125  		Schema: schema,
   126  		Diff: &tofu.InstanceDiff{
   127  			Attributes: map[string]*tofu.ResourceAttrDiff{
   128  				"tags.%": &tofu.ResourceAttrDiff{
   129  					Old: "1",
   130  					New: "2",
   131  				},
   132  				"tags.baz": &tofu.ResourceAttrDiff{
   133  					Old: "",
   134  					New: "qux",
   135  				},
   136  			},
   137  		},
   138  		Source: &MapFieldReader{
   139  			Schema: schema,
   140  			Map: BasicMapReader(map[string]string{
   141  				"tags.%":   "1",
   142  				"tags.foo": "bar",
   143  			}),
   144  		},
   145  	}
   146  
   147  	result, err := r.ReadField([]string{"tags"})
   148  	if err != nil {
   149  		t.Fatalf("ReadField failed: %#v", err)
   150  	}
   151  
   152  	expected := map[string]interface{}{
   153  		"foo": "bar",
   154  		"baz": "qux",
   155  	}
   156  
   157  	if !reflect.DeepEqual(expected, result.Value) {
   158  		t.Fatalf("bad: DiffHandling\n\nexpected: %#v\n\ngot: %#v\n\n", expected, result.Value)
   159  	}
   160  }
   161  
   162  func TestDiffFieldReader_extra(t *testing.T) {
   163  	schema := map[string]*Schema{
   164  		"stringComputed": &Schema{Type: TypeString},
   165  
   166  		"listMap": &Schema{
   167  			Type: TypeList,
   168  			Elem: &Schema{
   169  				Type: TypeMap,
   170  			},
   171  		},
   172  
   173  		"mapRemove": &Schema{Type: TypeMap},
   174  
   175  		"setChange": &Schema{
   176  			Type:     TypeSet,
   177  			Optional: true,
   178  			Elem: &Resource{
   179  				Schema: map[string]*Schema{
   180  					"index": &Schema{
   181  						Type:     TypeInt,
   182  						Required: true,
   183  					},
   184  
   185  					"value": &Schema{
   186  						Type:     TypeString,
   187  						Required: true,
   188  					},
   189  				},
   190  			},
   191  			Set: func(a interface{}) int {
   192  				m := a.(map[string]interface{})
   193  				return m["index"].(int)
   194  			},
   195  		},
   196  
   197  		"setEmpty": &Schema{
   198  			Type:     TypeSet,
   199  			Optional: true,
   200  			Elem: &Resource{
   201  				Schema: map[string]*Schema{
   202  					"index": &Schema{
   203  						Type:     TypeInt,
   204  						Required: true,
   205  					},
   206  
   207  					"value": &Schema{
   208  						Type:     TypeString,
   209  						Required: true,
   210  					},
   211  				},
   212  			},
   213  			Set: func(a interface{}) int {
   214  				m := a.(map[string]interface{})
   215  				return m["index"].(int)
   216  			},
   217  		},
   218  	}
   219  
   220  	r := &DiffFieldReader{
   221  		Schema: schema,
   222  		Diff: &tofu.InstanceDiff{
   223  			Attributes: map[string]*tofu.ResourceAttrDiff{
   224  				"stringComputed": &tofu.ResourceAttrDiff{
   225  					Old:         "foo",
   226  					New:         "bar",
   227  					NewComputed: true,
   228  				},
   229  
   230  				"listMap.0.bar": &tofu.ResourceAttrDiff{
   231  					NewRemoved: true,
   232  				},
   233  
   234  				"mapRemove.bar": &tofu.ResourceAttrDiff{
   235  					NewRemoved: true,
   236  				},
   237  
   238  				"setChange.10.value": &tofu.ResourceAttrDiff{
   239  					Old: "50",
   240  					New: "80",
   241  				},
   242  
   243  				"setEmpty.#": &tofu.ResourceAttrDiff{
   244  					Old: "2",
   245  					New: "0",
   246  				},
   247  			},
   248  		},
   249  
   250  		Source: &MapFieldReader{
   251  			Schema: schema,
   252  			Map: BasicMapReader(map[string]string{
   253  				"listMap.#":     "2",
   254  				"listMap.0.foo": "bar",
   255  				"listMap.0.bar": "baz",
   256  				"listMap.1.baz": "baz",
   257  
   258  				"mapRemove.foo": "bar",
   259  				"mapRemove.bar": "bar",
   260  
   261  				"setChange.#":        "1",
   262  				"setChange.10.index": "10",
   263  				"setChange.10.value": "50",
   264  
   265  				"setEmpty.#":        "2",
   266  				"setEmpty.10.index": "10",
   267  				"setEmpty.10.value": "50",
   268  				"setEmpty.20.index": "20",
   269  				"setEmpty.20.value": "50",
   270  			}),
   271  		},
   272  	}
   273  
   274  	cases := map[string]struct {
   275  		Addr   []string
   276  		Result FieldReadResult
   277  		Err    bool
   278  	}{
   279  		"stringComputed": {
   280  			[]string{"stringComputed"},
   281  			FieldReadResult{
   282  				Value:    "",
   283  				Exists:   true,
   284  				Computed: true,
   285  			},
   286  			false,
   287  		},
   288  
   289  		"listMapRemoval": {
   290  			[]string{"listMap"},
   291  			FieldReadResult{
   292  				Value: []interface{}{
   293  					map[string]interface{}{
   294  						"foo": "bar",
   295  					},
   296  					map[string]interface{}{
   297  						"baz": "baz",
   298  					},
   299  				},
   300  				Exists: true,
   301  			},
   302  			false,
   303  		},
   304  
   305  		"mapRemove": {
   306  			[]string{"mapRemove"},
   307  			FieldReadResult{
   308  				Value: map[string]interface{}{
   309  					"foo": "bar",
   310  				},
   311  				Exists:   true,
   312  				Computed: false,
   313  			},
   314  			false,
   315  		},
   316  
   317  		"setChange": {
   318  			[]string{"setChange"},
   319  			FieldReadResult{
   320  				Value: []interface{}{
   321  					map[string]interface{}{
   322  						"index": 10,
   323  						"value": "80",
   324  					},
   325  				},
   326  				Exists: true,
   327  			},
   328  			false,
   329  		},
   330  
   331  		"setEmpty": {
   332  			[]string{"setEmpty"},
   333  			FieldReadResult{
   334  				Value:  []interface{}{},
   335  				Exists: true,
   336  			},
   337  			false,
   338  		},
   339  	}
   340  
   341  	for name, tc := range cases {
   342  		out, err := r.ReadField(tc.Addr)
   343  		if err != nil != tc.Err {
   344  			t.Fatalf("%s: err: %s", name, err)
   345  		}
   346  		if s, ok := out.Value.(*Set); ok {
   347  			// If it is a set, convert to a list so its more easily checked.
   348  			out.Value = s.List()
   349  		}
   350  		if !reflect.DeepEqual(tc.Result, out) {
   351  			t.Fatalf("%s: bad: %#v", name, out)
   352  		}
   353  	}
   354  }
   355  
   356  func TestDiffFieldReader(t *testing.T) {
   357  	testFieldReader(t, func(s map[string]*Schema) FieldReader {
   358  		return &DiffFieldReader{
   359  			Schema: s,
   360  			Diff: &tofu.InstanceDiff{
   361  				Attributes: map[string]*tofu.ResourceAttrDiff{
   362  					"bool": &tofu.ResourceAttrDiff{
   363  						Old: "",
   364  						New: "true",
   365  					},
   366  
   367  					"int": &tofu.ResourceAttrDiff{
   368  						Old: "",
   369  						New: "42",
   370  					},
   371  
   372  					"float": &tofu.ResourceAttrDiff{
   373  						Old: "",
   374  						New: "3.1415",
   375  					},
   376  
   377  					"string": &tofu.ResourceAttrDiff{
   378  						Old: "",
   379  						New: "string",
   380  					},
   381  
   382  					"stringComputed": &tofu.ResourceAttrDiff{
   383  						Old:         "foo",
   384  						New:         "bar",
   385  						NewComputed: true,
   386  					},
   387  
   388  					"list.#": &tofu.ResourceAttrDiff{
   389  						Old: "0",
   390  						New: "2",
   391  					},
   392  
   393  					"list.0": &tofu.ResourceAttrDiff{
   394  						Old: "",
   395  						New: "foo",
   396  					},
   397  
   398  					"list.1": &tofu.ResourceAttrDiff{
   399  						Old: "",
   400  						New: "bar",
   401  					},
   402  
   403  					"listInt.#": &tofu.ResourceAttrDiff{
   404  						Old: "0",
   405  						New: "2",
   406  					},
   407  
   408  					"listInt.0": &tofu.ResourceAttrDiff{
   409  						Old: "",
   410  						New: "21",
   411  					},
   412  
   413  					"listInt.1": &tofu.ResourceAttrDiff{
   414  						Old: "",
   415  						New: "42",
   416  					},
   417  
   418  					"map.foo": &tofu.ResourceAttrDiff{
   419  						Old: "",
   420  						New: "bar",
   421  					},
   422  
   423  					"map.bar": &tofu.ResourceAttrDiff{
   424  						Old: "",
   425  						New: "baz",
   426  					},
   427  
   428  					"mapInt.%": &tofu.ResourceAttrDiff{
   429  						Old: "",
   430  						New: "2",
   431  					},
   432  					"mapInt.one": &tofu.ResourceAttrDiff{
   433  						Old: "",
   434  						New: "1",
   435  					},
   436  					"mapInt.two": &tofu.ResourceAttrDiff{
   437  						Old: "",
   438  						New: "2",
   439  					},
   440  
   441  					"mapIntNestedSchema.%": &tofu.ResourceAttrDiff{
   442  						Old: "",
   443  						New: "2",
   444  					},
   445  					"mapIntNestedSchema.one": &tofu.ResourceAttrDiff{
   446  						Old: "",
   447  						New: "1",
   448  					},
   449  					"mapIntNestedSchema.two": &tofu.ResourceAttrDiff{
   450  						Old: "",
   451  						New: "2",
   452  					},
   453  
   454  					"mapFloat.%": &tofu.ResourceAttrDiff{
   455  						Old: "",
   456  						New: "1",
   457  					},
   458  					"mapFloat.oneDotTwo": &tofu.ResourceAttrDiff{
   459  						Old: "",
   460  						New: "1.2",
   461  					},
   462  
   463  					"mapBool.%": &tofu.ResourceAttrDiff{
   464  						Old: "",
   465  						New: "2",
   466  					},
   467  					"mapBool.True": &tofu.ResourceAttrDiff{
   468  						Old: "",
   469  						New: "true",
   470  					},
   471  					"mapBool.False": &tofu.ResourceAttrDiff{
   472  						Old: "",
   473  						New: "false",
   474  					},
   475  
   476  					"set.#": &tofu.ResourceAttrDiff{
   477  						Old: "0",
   478  						New: "2",
   479  					},
   480  
   481  					"set.10": &tofu.ResourceAttrDiff{
   482  						Old: "",
   483  						New: "10",
   484  					},
   485  
   486  					"set.50": &tofu.ResourceAttrDiff{
   487  						Old: "",
   488  						New: "50",
   489  					},
   490  
   491  					"setDeep.#": &tofu.ResourceAttrDiff{
   492  						Old: "0",
   493  						New: "2",
   494  					},
   495  
   496  					"setDeep.10.index": &tofu.ResourceAttrDiff{
   497  						Old: "",
   498  						New: "10",
   499  					},
   500  
   501  					"setDeep.10.value": &tofu.ResourceAttrDiff{
   502  						Old: "",
   503  						New: "foo",
   504  					},
   505  
   506  					"setDeep.50.index": &tofu.ResourceAttrDiff{
   507  						Old: "",
   508  						New: "50",
   509  					},
   510  
   511  					"setDeep.50.value": &tofu.ResourceAttrDiff{
   512  						Old: "",
   513  						New: "bar",
   514  					},
   515  				},
   516  			},
   517  
   518  			Source: &MapFieldReader{
   519  				Schema: s,
   520  				Map: BasicMapReader(map[string]string{
   521  					"listMap.#":     "2",
   522  					"listMap.0.foo": "bar",
   523  					"listMap.0.bar": "baz",
   524  					"listMap.1.baz": "baz",
   525  				}),
   526  			},
   527  		}
   528  	})
   529  }