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