github.com/opentofu/opentofu@v1.7.1/internal/legacy/helper/schema/field_writer_map_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  
    13  func TestMapFieldWriter_impl(t *testing.T) {
    14  	var _ FieldWriter = new(MapFieldWriter)
    15  }
    16  
    17  func TestMapFieldWriter(t *testing.T) {
    18  	schema := map[string]*Schema{
    19  		"bool":   &Schema{Type: TypeBool},
    20  		"int":    &Schema{Type: TypeInt},
    21  		"string": &Schema{Type: TypeString},
    22  		"list": &Schema{
    23  			Type: TypeList,
    24  			Elem: &Schema{Type: TypeString},
    25  		},
    26  		"listInt": &Schema{
    27  			Type: TypeList,
    28  			Elem: &Schema{Type: TypeInt},
    29  		},
    30  		"listResource": &Schema{
    31  			Type:     TypeList,
    32  			Optional: true,
    33  			Computed: true,
    34  			Elem: &Resource{
    35  				Schema: map[string]*Schema{
    36  					"value": &Schema{
    37  						Type:     TypeInt,
    38  						Optional: true,
    39  					},
    40  				},
    41  			},
    42  		},
    43  		"map": &Schema{Type: TypeMap},
    44  		"set": &Schema{
    45  			Type: TypeSet,
    46  			Elem: &Schema{Type: TypeInt},
    47  			Set: func(a interface{}) int {
    48  				return a.(int)
    49  			},
    50  		},
    51  		"setDeep": &Schema{
    52  			Type: TypeSet,
    53  			Elem: &Resource{
    54  				Schema: map[string]*Schema{
    55  					"index": &Schema{Type: TypeInt},
    56  					"value": &Schema{Type: TypeString},
    57  				},
    58  			},
    59  			Set: func(a interface{}) int {
    60  				return a.(map[string]interface{})["index"].(int)
    61  			},
    62  		},
    63  	}
    64  
    65  	cases := map[string]struct {
    66  		Addr  []string
    67  		Value interface{}
    68  		Err   bool
    69  		Out   map[string]string
    70  	}{
    71  		"noexist": {
    72  			[]string{"noexist"},
    73  			42,
    74  			true,
    75  			map[string]string{},
    76  		},
    77  
    78  		"bool": {
    79  			[]string{"bool"},
    80  			false,
    81  			false,
    82  			map[string]string{
    83  				"bool": "false",
    84  			},
    85  		},
    86  
    87  		"int": {
    88  			[]string{"int"},
    89  			42,
    90  			false,
    91  			map[string]string{
    92  				"int": "42",
    93  			},
    94  		},
    95  
    96  		"string": {
    97  			[]string{"string"},
    98  			"42",
    99  			false,
   100  			map[string]string{
   101  				"string": "42",
   102  			},
   103  		},
   104  
   105  		"string nil": {
   106  			[]string{"string"},
   107  			nil,
   108  			false,
   109  			map[string]string{
   110  				"string": "",
   111  			},
   112  		},
   113  
   114  		"list of resources": {
   115  			[]string{"listResource"},
   116  			[]interface{}{
   117  				map[string]interface{}{
   118  					"value": 80,
   119  				},
   120  			},
   121  			false,
   122  			map[string]string{
   123  				"listResource.#":       "1",
   124  				"listResource.0.value": "80",
   125  			},
   126  		},
   127  
   128  		"list of resources empty": {
   129  			[]string{"listResource"},
   130  			[]interface{}{},
   131  			false,
   132  			map[string]string{
   133  				"listResource.#": "0",
   134  			},
   135  		},
   136  
   137  		"list of resources nil": {
   138  			[]string{"listResource"},
   139  			nil,
   140  			false,
   141  			map[string]string{
   142  				"listResource.#": "0",
   143  			},
   144  		},
   145  
   146  		"list of strings": {
   147  			[]string{"list"},
   148  			[]interface{}{"foo", "bar"},
   149  			false,
   150  			map[string]string{
   151  				"list.#": "2",
   152  				"list.0": "foo",
   153  				"list.1": "bar",
   154  			},
   155  		},
   156  
   157  		"list element": {
   158  			[]string{"list", "0"},
   159  			"string",
   160  			true,
   161  			map[string]string{},
   162  		},
   163  
   164  		"map": {
   165  			[]string{"map"},
   166  			map[string]interface{}{"foo": "bar"},
   167  			false,
   168  			map[string]string{
   169  				"map.%":   "1",
   170  				"map.foo": "bar",
   171  			},
   172  		},
   173  
   174  		"map delete": {
   175  			[]string{"map"},
   176  			nil,
   177  			false,
   178  			map[string]string{
   179  				"map": "",
   180  			},
   181  		},
   182  
   183  		"map element": {
   184  			[]string{"map", "foo"},
   185  			"bar",
   186  			true,
   187  			map[string]string{},
   188  		},
   189  
   190  		"set": {
   191  			[]string{"set"},
   192  			[]interface{}{1, 2, 5},
   193  			false,
   194  			map[string]string{
   195  				"set.#": "3",
   196  				"set.1": "1",
   197  				"set.2": "2",
   198  				"set.5": "5",
   199  			},
   200  		},
   201  
   202  		"set nil": {
   203  			[]string{"set"},
   204  			nil,
   205  			false,
   206  			map[string]string{
   207  				"set.#": "0",
   208  			},
   209  		},
   210  
   211  		"set typed nil": {
   212  			[]string{"set"},
   213  			func() *Set { return nil }(),
   214  			false,
   215  			map[string]string{
   216  				"set.#": "0",
   217  			},
   218  		},
   219  
   220  		"set resource": {
   221  			[]string{"setDeep"},
   222  			[]interface{}{
   223  				map[string]interface{}{
   224  					"index": 10,
   225  					"value": "foo",
   226  				},
   227  				map[string]interface{}{
   228  					"index": 50,
   229  					"value": "bar",
   230  				},
   231  			},
   232  			false,
   233  			map[string]string{
   234  				"setDeep.#":        "2",
   235  				"setDeep.10.index": "10",
   236  				"setDeep.10.value": "foo",
   237  				"setDeep.50.index": "50",
   238  				"setDeep.50.value": "bar",
   239  			},
   240  		},
   241  
   242  		"set element": {
   243  			[]string{"set", "5"},
   244  			5,
   245  			true,
   246  			map[string]string{},
   247  		},
   248  
   249  		"full object": {
   250  			nil,
   251  			map[string]interface{}{
   252  				"string": "foo",
   253  				"list":   []interface{}{"foo", "bar"},
   254  			},
   255  			false,
   256  			map[string]string{
   257  				"string": "foo",
   258  				"list.#": "2",
   259  				"list.0": "foo",
   260  				"list.1": "bar",
   261  			},
   262  		},
   263  	}
   264  
   265  	for name, tc := range cases {
   266  		w := &MapFieldWriter{Schema: schema}
   267  		err := w.WriteField(tc.Addr, tc.Value)
   268  		if err != nil != tc.Err {
   269  			t.Fatalf("%s: err: %s", name, err)
   270  		}
   271  
   272  		actual := w.Map()
   273  		if !reflect.DeepEqual(actual, tc.Out) {
   274  			t.Fatalf("%s: bad: %#v", name, actual)
   275  		}
   276  	}
   277  }
   278  
   279  func TestMapFieldWriterCleanSet(t *testing.T) {
   280  	schema := map[string]*Schema{
   281  		"setDeep": &Schema{
   282  			Type: TypeSet,
   283  			Elem: &Resource{
   284  				Schema: map[string]*Schema{
   285  					"index": &Schema{Type: TypeInt},
   286  					"value": &Schema{Type: TypeString},
   287  				},
   288  			},
   289  			Set: func(a interface{}) int {
   290  				return a.(map[string]interface{})["index"].(int)
   291  			},
   292  		},
   293  	}
   294  
   295  	values := []struct {
   296  		Addr  []string
   297  		Value interface{}
   298  		Out   map[string]string
   299  	}{
   300  		{
   301  			[]string{"setDeep"},
   302  			[]interface{}{
   303  				map[string]interface{}{
   304  					"index": 10,
   305  					"value": "foo",
   306  				},
   307  				map[string]interface{}{
   308  					"index": 50,
   309  					"value": "bar",
   310  				},
   311  			},
   312  			map[string]string{
   313  				"setDeep.#":        "2",
   314  				"setDeep.10.index": "10",
   315  				"setDeep.10.value": "foo",
   316  				"setDeep.50.index": "50",
   317  				"setDeep.50.value": "bar",
   318  			},
   319  		},
   320  		{
   321  			[]string{"setDeep"},
   322  			[]interface{}{
   323  				map[string]interface{}{
   324  					"index": 20,
   325  					"value": "baz",
   326  				},
   327  				map[string]interface{}{
   328  					"index": 60,
   329  					"value": "qux",
   330  				},
   331  			},
   332  			map[string]string{
   333  				"setDeep.#":        "2",
   334  				"setDeep.20.index": "20",
   335  				"setDeep.20.value": "baz",
   336  				"setDeep.60.index": "60",
   337  				"setDeep.60.value": "qux",
   338  			},
   339  		},
   340  		{
   341  			[]string{"setDeep"},
   342  			[]interface{}{
   343  				map[string]interface{}{
   344  					"index": 30,
   345  					"value": "one",
   346  				},
   347  				map[string]interface{}{
   348  					"index": 70,
   349  					"value": "two",
   350  				},
   351  			},
   352  			map[string]string{
   353  				"setDeep.#":        "2",
   354  				"setDeep.30.index": "30",
   355  				"setDeep.30.value": "one",
   356  				"setDeep.70.index": "70",
   357  				"setDeep.70.value": "two",
   358  			},
   359  		},
   360  	}
   361  
   362  	w := &MapFieldWriter{Schema: schema}
   363  
   364  	for n, tc := range values {
   365  		err := w.WriteField(tc.Addr, tc.Value)
   366  		if err != nil {
   367  			t.Fatalf("%d: err: %s", n, err)
   368  		}
   369  
   370  		actual := w.Map()
   371  		if !reflect.DeepEqual(actual, tc.Out) {
   372  			t.Fatalf("%d: bad: %#v", n, actual)
   373  		}
   374  	}
   375  }
   376  
   377  func TestMapFieldWriterCleanList(t *testing.T) {
   378  	schema := map[string]*Schema{
   379  		"listDeep": &Schema{
   380  			Type: TypeList,
   381  			Elem: &Resource{
   382  				Schema: map[string]*Schema{
   383  					"thing1": &Schema{Type: TypeString},
   384  					"thing2": &Schema{Type: TypeString},
   385  				},
   386  			},
   387  		},
   388  	}
   389  
   390  	values := []struct {
   391  		Addr  []string
   392  		Value interface{}
   393  		Out   map[string]string
   394  	}{
   395  		{
   396  			// Base list
   397  			[]string{"listDeep"},
   398  			[]interface{}{
   399  				map[string]interface{}{
   400  					"thing1": "a",
   401  					"thing2": "b",
   402  				},
   403  				map[string]interface{}{
   404  					"thing1": "c",
   405  					"thing2": "d",
   406  				},
   407  				map[string]interface{}{
   408  					"thing1": "e",
   409  					"thing2": "f",
   410  				},
   411  				map[string]interface{}{
   412  					"thing1": "g",
   413  					"thing2": "h",
   414  				},
   415  			},
   416  			map[string]string{
   417  				"listDeep.#":        "4",
   418  				"listDeep.0.thing1": "a",
   419  				"listDeep.0.thing2": "b",
   420  				"listDeep.1.thing1": "c",
   421  				"listDeep.1.thing2": "d",
   422  				"listDeep.2.thing1": "e",
   423  				"listDeep.2.thing2": "f",
   424  				"listDeep.3.thing1": "g",
   425  				"listDeep.3.thing2": "h",
   426  			},
   427  		},
   428  		{
   429  			// Remove an element
   430  			[]string{"listDeep"},
   431  			[]interface{}{
   432  				map[string]interface{}{
   433  					"thing1": "a",
   434  					"thing2": "b",
   435  				},
   436  				map[string]interface{}{
   437  					"thing1": "c",
   438  					"thing2": "d",
   439  				},
   440  				map[string]interface{}{
   441  					"thing1": "e",
   442  					"thing2": "f",
   443  				},
   444  			},
   445  			map[string]string{
   446  				"listDeep.#":        "3",
   447  				"listDeep.0.thing1": "a",
   448  				"listDeep.0.thing2": "b",
   449  				"listDeep.1.thing1": "c",
   450  				"listDeep.1.thing2": "d",
   451  				"listDeep.2.thing1": "e",
   452  				"listDeep.2.thing2": "f",
   453  			},
   454  		},
   455  		{
   456  			// Rewrite with missing keys. This should normally not be necessary, as
   457  			// hopefully the writers are writing zero values as necessary, but for
   458  			// brevity we want to make sure that what exists in the writer is exactly
   459  			// what the last write looked like coming from the provider.
   460  			[]string{"listDeep"},
   461  			[]interface{}{
   462  				map[string]interface{}{
   463  					"thing1": "a",
   464  				},
   465  				map[string]interface{}{
   466  					"thing1": "c",
   467  				},
   468  				map[string]interface{}{
   469  					"thing1": "e",
   470  				},
   471  			},
   472  			map[string]string{
   473  				"listDeep.#":        "3",
   474  				"listDeep.0.thing1": "a",
   475  				"listDeep.1.thing1": "c",
   476  				"listDeep.2.thing1": "e",
   477  			},
   478  		},
   479  	}
   480  
   481  	w := &MapFieldWriter{Schema: schema}
   482  
   483  	for n, tc := range values {
   484  		err := w.WriteField(tc.Addr, tc.Value)
   485  		if err != nil {
   486  			t.Fatalf("%d: err: %s", n, err)
   487  		}
   488  
   489  		actual := w.Map()
   490  		if !reflect.DeepEqual(actual, tc.Out) {
   491  			t.Fatalf("%d: bad: %#v", n, actual)
   492  		}
   493  	}
   494  }
   495  
   496  func TestMapFieldWriterCleanMap(t *testing.T) {
   497  	schema := map[string]*Schema{
   498  		"map": &Schema{
   499  			Type: TypeMap,
   500  		},
   501  	}
   502  
   503  	values := []struct {
   504  		Value interface{}
   505  		Out   map[string]string
   506  	}{
   507  		{
   508  			// Base map
   509  			map[string]interface{}{
   510  				"thing1": "a",
   511  				"thing2": "b",
   512  				"thing3": "c",
   513  				"thing4": "d",
   514  			},
   515  			map[string]string{
   516  				"map.%":      "4",
   517  				"map.thing1": "a",
   518  				"map.thing2": "b",
   519  				"map.thing3": "c",
   520  				"map.thing4": "d",
   521  			},
   522  		},
   523  		{
   524  			// Base map
   525  			map[string]interface{}{
   526  				"thing1": "a",
   527  				"thing2": "b",
   528  				"thing4": "d",
   529  			},
   530  			map[string]string{
   531  				"map.%":      "3",
   532  				"map.thing1": "a",
   533  				"map.thing2": "b",
   534  				"map.thing4": "d",
   535  			},
   536  		},
   537  	}
   538  
   539  	w := &MapFieldWriter{Schema: schema}
   540  
   541  	for n, tc := range values {
   542  		err := w.WriteField([]string{"map"}, tc.Value)
   543  		if err != nil {
   544  			t.Fatalf("%d: err: %s", n, err)
   545  		}
   546  
   547  		actual := w.Map()
   548  		if !reflect.DeepEqual(actual, tc.Out) {
   549  			t.Fatalf("%d: bad: %#v", n, actual)
   550  		}
   551  	}
   552  }