github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/legacy/helper/schema/field_writer_map_test.go (about)

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