github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/configs/hcl2shim/values_test.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package hcl2shim
     5  
     6  import (
     7  	"fmt"
     8  	"reflect"
     9  	"testing"
    10  
    11  	"github.com/terramate-io/tf/configs/configschema"
    12  	"github.com/zclconf/go-cty/cty"
    13  )
    14  
    15  func TestConfigValueFromHCL2Block(t *testing.T) {
    16  	tests := []struct {
    17  		Input  cty.Value
    18  		Schema *configschema.Block
    19  		Want   map[string]interface{}
    20  	}{
    21  		{
    22  			cty.ObjectVal(map[string]cty.Value{
    23  				"name": cty.StringVal("Ermintrude"),
    24  				"age":  cty.NumberIntVal(19),
    25  				"address": cty.ObjectVal(map[string]cty.Value{
    26  					"street": cty.ListVal([]cty.Value{cty.StringVal("421 Shoreham Loop")}),
    27  					"city":   cty.StringVal("Fridgewater"),
    28  					"state":  cty.StringVal("MA"),
    29  					"zip":    cty.StringVal("91037"),
    30  				}),
    31  			}),
    32  			&configschema.Block{
    33  				Attributes: map[string]*configschema.Attribute{
    34  					"name": {Type: cty.String, Optional: true},
    35  					"age":  {Type: cty.Number, Optional: true},
    36  				},
    37  				BlockTypes: map[string]*configschema.NestedBlock{
    38  					"address": {
    39  						Nesting: configschema.NestingSingle,
    40  						Block: configschema.Block{
    41  							Attributes: map[string]*configschema.Attribute{
    42  								"street": {Type: cty.List(cty.String), Optional: true},
    43  								"city":   {Type: cty.String, Optional: true},
    44  								"state":  {Type: cty.String, Optional: true},
    45  								"zip":    {Type: cty.String, Optional: true},
    46  							},
    47  						},
    48  					},
    49  				},
    50  			},
    51  			map[string]interface{}{
    52  				"name": "Ermintrude",
    53  				"age":  int(19),
    54  				"address": map[string]interface{}{
    55  					"street": []interface{}{"421 Shoreham Loop"},
    56  					"city":   "Fridgewater",
    57  					"state":  "MA",
    58  					"zip":    "91037",
    59  				},
    60  			},
    61  		},
    62  		{
    63  			cty.ObjectVal(map[string]cty.Value{
    64  				"name": cty.StringVal("Ermintrude"),
    65  				"age":  cty.NumberIntVal(19),
    66  				"address": cty.NullVal(cty.Object(map[string]cty.Type{
    67  					"street": cty.List(cty.String),
    68  					"city":   cty.String,
    69  					"state":  cty.String,
    70  					"zip":    cty.String,
    71  				})),
    72  			}),
    73  			&configschema.Block{
    74  				Attributes: map[string]*configschema.Attribute{
    75  					"name": {Type: cty.String, Optional: true},
    76  					"age":  {Type: cty.Number, Optional: true},
    77  				},
    78  				BlockTypes: map[string]*configschema.NestedBlock{
    79  					"address": {
    80  						Nesting: configschema.NestingSingle,
    81  						Block: configschema.Block{
    82  							Attributes: map[string]*configschema.Attribute{
    83  								"street": {Type: cty.List(cty.String), Optional: true},
    84  								"city":   {Type: cty.String, Optional: true},
    85  								"state":  {Type: cty.String, Optional: true},
    86  								"zip":    {Type: cty.String, Optional: true},
    87  							},
    88  						},
    89  					},
    90  				},
    91  			},
    92  			map[string]interface{}{
    93  				"name": "Ermintrude",
    94  				"age":  int(19),
    95  			},
    96  		},
    97  		{
    98  			cty.ObjectVal(map[string]cty.Value{
    99  				"name": cty.StringVal("Ermintrude"),
   100  				"age":  cty.NumberIntVal(19),
   101  				"address": cty.ObjectVal(map[string]cty.Value{
   102  					"street": cty.ListVal([]cty.Value{cty.StringVal("421 Shoreham Loop")}),
   103  					"city":   cty.StringVal("Fridgewater"),
   104  					"state":  cty.StringVal("MA"),
   105  					"zip":    cty.NullVal(cty.String), // should be omitted altogether in result
   106  				}),
   107  			}),
   108  			&configschema.Block{
   109  				Attributes: map[string]*configschema.Attribute{
   110  					"name": {Type: cty.String, Optional: true},
   111  					"age":  {Type: cty.Number, Optional: true},
   112  				},
   113  				BlockTypes: map[string]*configschema.NestedBlock{
   114  					"address": {
   115  						Nesting: configschema.NestingSingle,
   116  						Block: configschema.Block{
   117  							Attributes: map[string]*configschema.Attribute{
   118  								"street": {Type: cty.List(cty.String), Optional: true},
   119  								"city":   {Type: cty.String, Optional: true},
   120  								"state":  {Type: cty.String, Optional: true},
   121  								"zip":    {Type: cty.String, Optional: true},
   122  							},
   123  						},
   124  					},
   125  				},
   126  			},
   127  			map[string]interface{}{
   128  				"name": "Ermintrude",
   129  				"age":  int(19),
   130  				"address": map[string]interface{}{
   131  					"street": []interface{}{"421 Shoreham Loop"},
   132  					"city":   "Fridgewater",
   133  					"state":  "MA",
   134  				},
   135  			},
   136  		},
   137  		{
   138  			cty.ObjectVal(map[string]cty.Value{
   139  				"address": cty.ListVal([]cty.Value{cty.EmptyObjectVal}),
   140  			}),
   141  			&configschema.Block{
   142  				BlockTypes: map[string]*configschema.NestedBlock{
   143  					"address": {
   144  						Nesting: configschema.NestingList,
   145  						Block:   configschema.Block{},
   146  					},
   147  				},
   148  			},
   149  			map[string]interface{}{
   150  				"address": []interface{}{
   151  					map[string]interface{}{},
   152  				},
   153  			},
   154  		},
   155  		{
   156  			cty.ObjectVal(map[string]cty.Value{
   157  				"address": cty.ListValEmpty(cty.EmptyObject), // should be omitted altogether in result
   158  			}),
   159  			&configschema.Block{
   160  				BlockTypes: map[string]*configschema.NestedBlock{
   161  					"address": {
   162  						Nesting: configschema.NestingList,
   163  						Block:   configschema.Block{},
   164  					},
   165  				},
   166  			},
   167  			map[string]interface{}{},
   168  		},
   169  		{
   170  			cty.ObjectVal(map[string]cty.Value{
   171  				"address": cty.SetVal([]cty.Value{cty.EmptyObjectVal}),
   172  			}),
   173  			&configschema.Block{
   174  				BlockTypes: map[string]*configschema.NestedBlock{
   175  					"address": {
   176  						Nesting: configschema.NestingSet,
   177  						Block:   configschema.Block{},
   178  					},
   179  				},
   180  			},
   181  			map[string]interface{}{
   182  				"address": []interface{}{
   183  					map[string]interface{}{},
   184  				},
   185  			},
   186  		},
   187  		{
   188  			cty.ObjectVal(map[string]cty.Value{
   189  				"address": cty.SetValEmpty(cty.EmptyObject),
   190  			}),
   191  			&configschema.Block{
   192  				BlockTypes: map[string]*configschema.NestedBlock{
   193  					"address": {
   194  						Nesting: configschema.NestingSet,
   195  						Block:   configschema.Block{},
   196  					},
   197  				},
   198  			},
   199  			map[string]interface{}{},
   200  		},
   201  		{
   202  			cty.ObjectVal(map[string]cty.Value{
   203  				"address": cty.MapVal(map[string]cty.Value{"foo": cty.EmptyObjectVal}),
   204  			}),
   205  			&configschema.Block{
   206  				BlockTypes: map[string]*configschema.NestedBlock{
   207  					"address": {
   208  						Nesting: configschema.NestingMap,
   209  						Block:   configschema.Block{},
   210  					},
   211  				},
   212  			},
   213  			map[string]interface{}{
   214  				"address": map[string]interface{}{
   215  					"foo": map[string]interface{}{},
   216  				},
   217  			},
   218  		},
   219  		{
   220  			cty.ObjectVal(map[string]cty.Value{
   221  				"address": cty.MapValEmpty(cty.EmptyObject),
   222  			}),
   223  			&configschema.Block{
   224  				BlockTypes: map[string]*configschema.NestedBlock{
   225  					"address": {
   226  						Nesting: configschema.NestingMap,
   227  						Block:   configschema.Block{},
   228  					},
   229  				},
   230  			},
   231  			map[string]interface{}{},
   232  		},
   233  		{
   234  			cty.NullVal(cty.EmptyObject),
   235  			&configschema.Block{},
   236  			nil,
   237  		},
   238  	}
   239  
   240  	for _, test := range tests {
   241  		t.Run(fmt.Sprintf("%#v", test.Input), func(t *testing.T) {
   242  			got := ConfigValueFromHCL2Block(test.Input, test.Schema)
   243  			if !reflect.DeepEqual(got, test.Want) {
   244  				t.Errorf("wrong result\ninput: %#v\ngot:   %#v\nwant:  %#v", test.Input, got, test.Want)
   245  			}
   246  		})
   247  	}
   248  }
   249  
   250  func TestConfigValueFromHCL2(t *testing.T) {
   251  	tests := []struct {
   252  		Input cty.Value
   253  		Want  interface{}
   254  	}{
   255  		{
   256  			cty.True,
   257  			true,
   258  		},
   259  		{
   260  			cty.False,
   261  			false,
   262  		},
   263  		{
   264  			cty.NumberIntVal(12),
   265  			int(12),
   266  		},
   267  		{
   268  			cty.NumberFloatVal(12.5),
   269  			float64(12.5),
   270  		},
   271  		{
   272  			cty.StringVal("hello world"),
   273  			"hello world",
   274  		},
   275  		{
   276  			cty.ObjectVal(map[string]cty.Value{
   277  				"name": cty.StringVal("Ermintrude"),
   278  				"age":  cty.NumberIntVal(19),
   279  				"address": cty.ObjectVal(map[string]cty.Value{
   280  					"street": cty.ListVal([]cty.Value{cty.StringVal("421 Shoreham Loop")}),
   281  					"city":   cty.StringVal("Fridgewater"),
   282  					"state":  cty.StringVal("MA"),
   283  					"zip":    cty.StringVal("91037"),
   284  				}),
   285  			}),
   286  			map[string]interface{}{
   287  				"name": "Ermintrude",
   288  				"age":  int(19),
   289  				"address": map[string]interface{}{
   290  					"street": []interface{}{"421 Shoreham Loop"},
   291  					"city":   "Fridgewater",
   292  					"state":  "MA",
   293  					"zip":    "91037",
   294  				},
   295  			},
   296  		},
   297  		{
   298  			cty.MapVal(map[string]cty.Value{
   299  				"foo": cty.StringVal("bar"),
   300  				"bar": cty.StringVal("baz"),
   301  			}),
   302  			map[string]interface{}{
   303  				"foo": "bar",
   304  				"bar": "baz",
   305  			},
   306  		},
   307  		{
   308  			cty.TupleVal([]cty.Value{
   309  				cty.StringVal("foo"),
   310  				cty.True,
   311  			}),
   312  			[]interface{}{
   313  				"foo",
   314  				true,
   315  			},
   316  		},
   317  		{
   318  			cty.NullVal(cty.String),
   319  			nil,
   320  		},
   321  		{
   322  			cty.UnknownVal(cty.String),
   323  			UnknownVariableValue,
   324  		},
   325  	}
   326  
   327  	for _, test := range tests {
   328  		t.Run(fmt.Sprintf("%#v", test.Input), func(t *testing.T) {
   329  			got := ConfigValueFromHCL2(test.Input)
   330  			if !reflect.DeepEqual(got, test.Want) {
   331  				t.Errorf("wrong result\ninput: %#v\ngot:   %#v\nwant:  %#v", test.Input, got, test.Want)
   332  			}
   333  		})
   334  	}
   335  }
   336  
   337  func TestHCL2ValueFromConfigValue(t *testing.T) {
   338  	tests := []struct {
   339  		Input interface{}
   340  		Want  cty.Value
   341  	}{
   342  		{
   343  			nil,
   344  			cty.NullVal(cty.DynamicPseudoType),
   345  		},
   346  		{
   347  			UnknownVariableValue,
   348  			cty.DynamicVal,
   349  		},
   350  		{
   351  			true,
   352  			cty.True,
   353  		},
   354  		{
   355  			false,
   356  			cty.False,
   357  		},
   358  		{
   359  			int(12),
   360  			cty.NumberIntVal(12),
   361  		},
   362  		{
   363  			int(0),
   364  			cty.Zero,
   365  		},
   366  		{
   367  			float64(12.5),
   368  			cty.NumberFloatVal(12.5),
   369  		},
   370  		{
   371  			"hello world",
   372  			cty.StringVal("hello world"),
   373  		},
   374  		{
   375  			"O\u0308",               // decomposed letter + diacritic
   376  			cty.StringVal("\u00D6"), // NFC-normalized on entry into cty
   377  		},
   378  		{
   379  			[]interface{}{},
   380  			cty.EmptyTupleVal,
   381  		},
   382  		{
   383  			[]interface{}(nil),
   384  			cty.EmptyTupleVal,
   385  		},
   386  		{
   387  			[]interface{}{"hello", "world"},
   388  			cty.TupleVal([]cty.Value{cty.StringVal("hello"), cty.StringVal("world")}),
   389  		},
   390  		{
   391  			map[string]interface{}{},
   392  			cty.EmptyObjectVal,
   393  		},
   394  		{
   395  			map[string]interface{}(nil),
   396  			cty.EmptyObjectVal,
   397  		},
   398  		{
   399  			map[string]interface{}{
   400  				"foo": "bar",
   401  				"bar": "baz",
   402  			},
   403  			cty.ObjectVal(map[string]cty.Value{
   404  				"foo": cty.StringVal("bar"),
   405  				"bar": cty.StringVal("baz"),
   406  			}),
   407  		},
   408  	}
   409  
   410  	for _, test := range tests {
   411  		t.Run(fmt.Sprintf("%#v", test.Input), func(t *testing.T) {
   412  			got := HCL2ValueFromConfigValue(test.Input)
   413  			if !got.RawEquals(test.Want) {
   414  				t.Errorf("wrong result\ninput: %#v\ngot:   %#v\nwant:  %#v", test.Input, got, test.Want)
   415  			}
   416  		})
   417  	}
   418  }