github.com/opentofu/opentofu@v1.7.1/internal/legacy/helper/schema/field_reader_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 TestAddrToSchema(t *testing.T) {
    14  	cases := map[string]struct {
    15  		Addr   []string
    16  		Schema map[string]*Schema
    17  		Result []ValueType
    18  	}{
    19  		"full object": {
    20  			[]string{},
    21  			map[string]*Schema{
    22  				"list": &Schema{
    23  					Type: TypeList,
    24  					Elem: &Schema{Type: TypeInt},
    25  				},
    26  			},
    27  			[]ValueType{typeObject},
    28  		},
    29  
    30  		"list": {
    31  			[]string{"list"},
    32  			map[string]*Schema{
    33  				"list": &Schema{
    34  					Type: TypeList,
    35  					Elem: &Schema{Type: TypeInt},
    36  				},
    37  			},
    38  			[]ValueType{TypeList},
    39  		},
    40  
    41  		"list.#": {
    42  			[]string{"list", "#"},
    43  			map[string]*Schema{
    44  				"list": &Schema{
    45  					Type: TypeList,
    46  					Elem: &Schema{Type: TypeInt},
    47  				},
    48  			},
    49  			[]ValueType{TypeList, TypeInt},
    50  		},
    51  
    52  		"list.0": {
    53  			[]string{"list", "0"},
    54  			map[string]*Schema{
    55  				"list": &Schema{
    56  					Type: TypeList,
    57  					Elem: &Schema{Type: TypeInt},
    58  				},
    59  			},
    60  			[]ValueType{TypeList, TypeInt},
    61  		},
    62  
    63  		"list.0 with resource": {
    64  			[]string{"list", "0"},
    65  			map[string]*Schema{
    66  				"list": &Schema{
    67  					Type: TypeList,
    68  					Elem: &Resource{
    69  						Schema: map[string]*Schema{
    70  							"field": &Schema{Type: TypeString},
    71  						},
    72  					},
    73  				},
    74  			},
    75  			[]ValueType{TypeList, typeObject},
    76  		},
    77  
    78  		"list.0.field": {
    79  			[]string{"list", "0", "field"},
    80  			map[string]*Schema{
    81  				"list": &Schema{
    82  					Type: TypeList,
    83  					Elem: &Resource{
    84  						Schema: map[string]*Schema{
    85  							"field": &Schema{Type: TypeString},
    86  						},
    87  					},
    88  				},
    89  			},
    90  			[]ValueType{TypeList, typeObject, TypeString},
    91  		},
    92  
    93  		"set": {
    94  			[]string{"set"},
    95  			map[string]*Schema{
    96  				"set": &Schema{
    97  					Type: TypeSet,
    98  					Elem: &Schema{Type: TypeInt},
    99  					Set: func(a interface{}) int {
   100  						return a.(int)
   101  					},
   102  				},
   103  			},
   104  			[]ValueType{TypeSet},
   105  		},
   106  
   107  		"set.#": {
   108  			[]string{"set", "#"},
   109  			map[string]*Schema{
   110  				"set": &Schema{
   111  					Type: TypeSet,
   112  					Elem: &Schema{Type: TypeInt},
   113  					Set: func(a interface{}) int {
   114  						return a.(int)
   115  					},
   116  				},
   117  			},
   118  			[]ValueType{TypeSet, TypeInt},
   119  		},
   120  
   121  		"set.0": {
   122  			[]string{"set", "0"},
   123  			map[string]*Schema{
   124  				"set": &Schema{
   125  					Type: TypeSet,
   126  					Elem: &Schema{Type: TypeInt},
   127  					Set: func(a interface{}) int {
   128  						return a.(int)
   129  					},
   130  				},
   131  			},
   132  			[]ValueType{TypeSet, TypeInt},
   133  		},
   134  
   135  		"set.0 with resource": {
   136  			[]string{"set", "0"},
   137  			map[string]*Schema{
   138  				"set": &Schema{
   139  					Type: TypeSet,
   140  					Elem: &Resource{
   141  						Schema: map[string]*Schema{
   142  							"field": &Schema{Type: TypeString},
   143  						},
   144  					},
   145  				},
   146  			},
   147  			[]ValueType{TypeSet, typeObject},
   148  		},
   149  
   150  		"mapElem": {
   151  			[]string{"map", "foo"},
   152  			map[string]*Schema{
   153  				"map": &Schema{Type: TypeMap},
   154  			},
   155  			[]ValueType{TypeMap, TypeString},
   156  		},
   157  
   158  		"setDeep": {
   159  			[]string{"set", "50", "index"},
   160  			map[string]*Schema{
   161  				"set": &Schema{
   162  					Type: TypeSet,
   163  					Elem: &Resource{
   164  						Schema: map[string]*Schema{
   165  							"index": &Schema{Type: TypeInt},
   166  							"value": &Schema{Type: TypeString},
   167  						},
   168  					},
   169  					Set: func(a interface{}) int {
   170  						return a.(map[string]interface{})["index"].(int)
   171  					},
   172  				},
   173  			},
   174  			[]ValueType{TypeSet, typeObject, TypeInt},
   175  		},
   176  	}
   177  
   178  	for name, tc := range cases {
   179  		result := addrToSchema(tc.Addr, tc.Schema)
   180  		types := make([]ValueType, len(result))
   181  		for i, v := range result {
   182  			types[i] = v.Type
   183  		}
   184  
   185  		if !reflect.DeepEqual(types, tc.Result) {
   186  			t.Fatalf("%s: %#v", name, types)
   187  		}
   188  	}
   189  }
   190  
   191  // testFieldReader is a helper that should be used to verify that
   192  // a FieldReader behaves properly in all the common cases.
   193  func testFieldReader(t *testing.T, f func(map[string]*Schema) FieldReader) {
   194  	schema := map[string]*Schema{
   195  		// Primitives
   196  		"bool":   &Schema{Type: TypeBool},
   197  		"float":  &Schema{Type: TypeFloat},
   198  		"int":    &Schema{Type: TypeInt},
   199  		"string": &Schema{Type: TypeString},
   200  
   201  		// Lists
   202  		"list": &Schema{
   203  			Type: TypeList,
   204  			Elem: &Schema{Type: TypeString},
   205  		},
   206  		"listInt": &Schema{
   207  			Type: TypeList,
   208  			Elem: &Schema{Type: TypeInt},
   209  		},
   210  		"listMap": &Schema{
   211  			Type: TypeList,
   212  			Elem: &Schema{
   213  				Type: TypeMap,
   214  			},
   215  		},
   216  
   217  		// Maps
   218  		"map": &Schema{Type: TypeMap},
   219  		"mapInt": &Schema{
   220  			Type: TypeMap,
   221  			Elem: TypeInt,
   222  		},
   223  
   224  		// This is used to verify that the type of a Map can be specified using the
   225  		// same syntax as for lists (as a nested *Schema passed to Elem)
   226  		"mapIntNestedSchema": &Schema{
   227  			Type: TypeMap,
   228  			Elem: &Schema{Type: TypeInt},
   229  		},
   230  		"mapFloat": &Schema{
   231  			Type: TypeMap,
   232  			Elem: TypeFloat,
   233  		},
   234  		"mapBool": &Schema{
   235  			Type: TypeMap,
   236  			Elem: TypeBool,
   237  		},
   238  
   239  		// Sets
   240  		"set": &Schema{
   241  			Type: TypeSet,
   242  			Elem: &Schema{Type: TypeInt},
   243  			Set: func(a interface{}) int {
   244  				return a.(int)
   245  			},
   246  		},
   247  		"setDeep": &Schema{
   248  			Type: TypeSet,
   249  			Elem: &Resource{
   250  				Schema: map[string]*Schema{
   251  					"index": &Schema{Type: TypeInt},
   252  					"value": &Schema{Type: TypeString},
   253  				},
   254  			},
   255  			Set: func(a interface{}) int {
   256  				return a.(map[string]interface{})["index"].(int)
   257  			},
   258  		},
   259  		"setEmpty": &Schema{
   260  			Type: TypeSet,
   261  			Elem: &Schema{Type: TypeInt},
   262  			Set: func(a interface{}) int {
   263  				return a.(int)
   264  			},
   265  		},
   266  	}
   267  
   268  	cases := map[string]struct {
   269  		Addr   []string
   270  		Result FieldReadResult
   271  		Err    bool
   272  	}{
   273  		"noexist": {
   274  			[]string{"boolNOPE"},
   275  			FieldReadResult{
   276  				Value:    nil,
   277  				Exists:   false,
   278  				Computed: false,
   279  			},
   280  			false,
   281  		},
   282  
   283  		"bool": {
   284  			[]string{"bool"},
   285  			FieldReadResult{
   286  				Value:    true,
   287  				Exists:   true,
   288  				Computed: false,
   289  			},
   290  			false,
   291  		},
   292  
   293  		"float": {
   294  			[]string{"float"},
   295  			FieldReadResult{
   296  				Value:    3.1415,
   297  				Exists:   true,
   298  				Computed: false,
   299  			},
   300  			false,
   301  		},
   302  
   303  		"int": {
   304  			[]string{"int"},
   305  			FieldReadResult{
   306  				Value:    42,
   307  				Exists:   true,
   308  				Computed: false,
   309  			},
   310  			false,
   311  		},
   312  
   313  		"string": {
   314  			[]string{"string"},
   315  			FieldReadResult{
   316  				Value:    "string",
   317  				Exists:   true,
   318  				Computed: false,
   319  			},
   320  			false,
   321  		},
   322  
   323  		"list": {
   324  			[]string{"list"},
   325  			FieldReadResult{
   326  				Value: []interface{}{
   327  					"foo",
   328  					"bar",
   329  				},
   330  				Exists:   true,
   331  				Computed: false,
   332  			},
   333  			false,
   334  		},
   335  
   336  		"listInt": {
   337  			[]string{"listInt"},
   338  			FieldReadResult{
   339  				Value: []interface{}{
   340  					21,
   341  					42,
   342  				},
   343  				Exists:   true,
   344  				Computed: false,
   345  			},
   346  			false,
   347  		},
   348  
   349  		"map": {
   350  			[]string{"map"},
   351  			FieldReadResult{
   352  				Value: map[string]interface{}{
   353  					"foo": "bar",
   354  					"bar": "baz",
   355  				},
   356  				Exists:   true,
   357  				Computed: false,
   358  			},
   359  			false,
   360  		},
   361  
   362  		"mapInt": {
   363  			[]string{"mapInt"},
   364  			FieldReadResult{
   365  				Value: map[string]interface{}{
   366  					"one": 1,
   367  					"two": 2,
   368  				},
   369  				Exists:   true,
   370  				Computed: false,
   371  			},
   372  			false,
   373  		},
   374  
   375  		"mapIntNestedSchema": {
   376  			[]string{"mapIntNestedSchema"},
   377  			FieldReadResult{
   378  				Value: map[string]interface{}{
   379  					"one": 1,
   380  					"two": 2,
   381  				},
   382  				Exists:   true,
   383  				Computed: false,
   384  			},
   385  			false,
   386  		},
   387  
   388  		"mapFloat": {
   389  			[]string{"mapFloat"},
   390  			FieldReadResult{
   391  				Value: map[string]interface{}{
   392  					"oneDotTwo": 1.2,
   393  				},
   394  				Exists:   true,
   395  				Computed: false,
   396  			},
   397  			false,
   398  		},
   399  
   400  		"mapBool": {
   401  			[]string{"mapBool"},
   402  			FieldReadResult{
   403  				Value: map[string]interface{}{
   404  					"True":  true,
   405  					"False": false,
   406  				},
   407  				Exists:   true,
   408  				Computed: false,
   409  			},
   410  			false,
   411  		},
   412  
   413  		"mapelem": {
   414  			[]string{"map", "foo"},
   415  			FieldReadResult{
   416  				Value:    "bar",
   417  				Exists:   true,
   418  				Computed: false,
   419  			},
   420  			false,
   421  		},
   422  
   423  		"set": {
   424  			[]string{"set"},
   425  			FieldReadResult{
   426  				Value:    []interface{}{10, 50},
   427  				Exists:   true,
   428  				Computed: false,
   429  			},
   430  			false,
   431  		},
   432  
   433  		"setDeep": {
   434  			[]string{"setDeep"},
   435  			FieldReadResult{
   436  				Value: []interface{}{
   437  					map[string]interface{}{
   438  						"index": 10,
   439  						"value": "foo",
   440  					},
   441  					map[string]interface{}{
   442  						"index": 50,
   443  						"value": "bar",
   444  					},
   445  				},
   446  				Exists:   true,
   447  				Computed: false,
   448  			},
   449  			false,
   450  		},
   451  
   452  		"setEmpty": {
   453  			[]string{"setEmpty"},
   454  			FieldReadResult{
   455  				Value:  []interface{}{},
   456  				Exists: false,
   457  			},
   458  			false,
   459  		},
   460  	}
   461  
   462  	for name, tc := range cases {
   463  		r := f(schema)
   464  		out, err := r.ReadField(tc.Addr)
   465  		if err != nil != tc.Err {
   466  			t.Fatalf("%s: err: %s", name, err)
   467  		}
   468  		if s, ok := out.Value.(*Set); ok {
   469  			// If it is a set, convert to a list so its more easily checked.
   470  			out.Value = s.List()
   471  		}
   472  		if !reflect.DeepEqual(tc.Result, out) {
   473  			t.Fatalf("%s: bad: %#v", name, out)
   474  		}
   475  	}
   476  }