github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/client/taskenv/util_test.go (about)

     1  package taskenv
     2  
     3  import (
     4  	"fmt"
     5  	"testing"
     6  
     7  	"github.com/hashicorp/nomad/ci"
     8  	"github.com/stretchr/testify/require"
     9  	"github.com/zclconf/go-cty/cty"
    10  )
    11  
    12  // TestAddNestedKey_Ok asserts test cases that succeed when passed to
    13  // addNestedKey.
    14  func TestAddNestedKey_Ok(t *testing.T) {
    15  	ci.Parallel(t)
    16  
    17  	cases := []struct {
    18  		// M will be initialized if unset
    19  		M map[string]interface{}
    20  		K string
    21  		// Value is always "x"
    22  		Result map[string]interface{}
    23  	}{
    24  		{
    25  			K: "foo",
    26  			Result: map[string]interface{}{
    27  				"foo": "x",
    28  			},
    29  		},
    30  		{
    31  			K: "foo.bar",
    32  			Result: map[string]interface{}{
    33  				"foo": map[string]interface{}{
    34  					"bar": "x",
    35  				},
    36  			},
    37  		},
    38  		{
    39  			K: "foo.bar.quux",
    40  			Result: map[string]interface{}{
    41  				"foo": map[string]interface{}{
    42  					"bar": map[string]interface{}{
    43  						"quux": "x",
    44  					},
    45  				},
    46  			},
    47  		},
    48  		{
    49  			K: "a.b.c",
    50  			Result: map[string]interface{}{
    51  				"a": map[string]interface{}{
    52  					"b": map[string]interface{}{
    53  						"c": "x",
    54  					},
    55  				},
    56  			},
    57  		},
    58  		{
    59  			// Nested object b should take precedence over values
    60  			M: map[string]interface{}{
    61  				"a": map[string]interface{}{
    62  					"b": map[string]interface{}{
    63  						"c": "c",
    64  					},
    65  				},
    66  			},
    67  			K: "a.b",
    68  			Result: map[string]interface{}{
    69  				"a": map[string]interface{}{
    70  					"b": map[string]interface{}{
    71  						"c": "c",
    72  					},
    73  				},
    74  			},
    75  		},
    76  		{
    77  			M: map[string]interface{}{
    78  				"a": map[string]interface{}{
    79  					"x": "x",
    80  				},
    81  				"z": "z",
    82  			},
    83  			K: "a.b.c",
    84  			Result: map[string]interface{}{
    85  				"a": map[string]interface{}{
    86  					"b": map[string]interface{}{
    87  						"c": "x",
    88  					},
    89  					"x": "x",
    90  				},
    91  				"z": "z",
    92  			},
    93  		},
    94  		{
    95  			M: map[string]interface{}{
    96  				"foo": map[string]interface{}{
    97  					"bar": map[string]interface{}{
    98  						"a":    "z",
    99  						"quux": "z",
   100  					},
   101  				},
   102  			},
   103  			K: "foo.bar.quux",
   104  			Result: map[string]interface{}{
   105  				"foo": map[string]interface{}{
   106  					"bar": map[string]interface{}{
   107  						"a":    "z",
   108  						"quux": "x",
   109  					},
   110  				},
   111  			},
   112  		},
   113  		{
   114  			M: map[string]interface{}{
   115  				"foo":  "1",
   116  				"bar":  "2",
   117  				"quux": "3",
   118  			},
   119  			K: "a.bbbbbb.c",
   120  			Result: map[string]interface{}{
   121  				"foo":  "1",
   122  				"bar":  "2",
   123  				"quux": "3",
   124  				"a": map[string]interface{}{
   125  					"bbbbbb": map[string]interface{}{
   126  						"c": "x",
   127  					},
   128  				},
   129  			},
   130  		},
   131  		// Regardless of whether attr.driver.qemu = "1" is added first
   132  		// or second, attr.driver.qemu.version = "..." should take
   133  		// precedence (nested maps take precedence over values)
   134  		{
   135  			M: map[string]interface{}{
   136  				"attr": map[string]interface{}{
   137  					"driver": map[string]interface{}{
   138  						"qemu": "1",
   139  					},
   140  				},
   141  			},
   142  			K: "attr.driver.qemu.version",
   143  			Result: map[string]interface{}{
   144  				"attr": map[string]interface{}{
   145  					"driver": map[string]interface{}{
   146  						"qemu": map[string]interface{}{
   147  							"version": "x",
   148  						},
   149  					},
   150  				},
   151  			},
   152  		},
   153  		{
   154  			M: map[string]interface{}{
   155  				"attr": map[string]interface{}{
   156  					"driver": map[string]interface{}{
   157  						"qemu": map[string]interface{}{
   158  							"version": "1.2.3",
   159  						},
   160  					},
   161  				},
   162  			},
   163  			K: "attr.driver.qemu",
   164  			Result: map[string]interface{}{
   165  				"attr": map[string]interface{}{
   166  					"driver": map[string]interface{}{
   167  						"qemu": map[string]interface{}{
   168  							"version": "1.2.3",
   169  						},
   170  					},
   171  				},
   172  			},
   173  		},
   174  		{
   175  			M: map[string]interface{}{
   176  				"a": "a",
   177  			},
   178  			K: "a.b",
   179  			Result: map[string]interface{}{
   180  				"a": map[string]interface{}{
   181  					"b": "x",
   182  				},
   183  			},
   184  		},
   185  		{
   186  			M: map[string]interface{}{
   187  				"a": "a",
   188  				"foo": map[string]interface{}{
   189  					"b":   "b",
   190  					"bar": "quux",
   191  				},
   192  				"c": map[string]interface{}{},
   193  			},
   194  			K: "foo.bar.quux",
   195  			Result: map[string]interface{}{
   196  				"a": "a",
   197  				"foo": map[string]interface{}{
   198  					"b": "b",
   199  					"bar": map[string]interface{}{
   200  						"quux": "x",
   201  					},
   202  				},
   203  				"c": map[string]interface{}{},
   204  			},
   205  		},
   206  	}
   207  
   208  	for i := range cases {
   209  		tc := cases[i]
   210  		name := tc.K
   211  		if len(tc.M) > 0 {
   212  			name = fmt.Sprintf("%s-%d", name, len(tc.M))
   213  		}
   214  		t.Run(name, func(t *testing.T) {
   215  			ci.Parallel(t)
   216  			if tc.M == nil {
   217  				tc.M = map[string]interface{}{}
   218  			}
   219  			require.NoError(t, addNestedKey(tc.M, tc.K, "x"))
   220  			require.Equal(t, tc.Result, tc.M)
   221  		})
   222  	}
   223  }
   224  
   225  // TestAddNestedKey_Bad asserts test cases return an error when passed to
   226  // addNestedKey.
   227  func TestAddNestedKey_Bad(t *testing.T) {
   228  	ci.Parallel(t)
   229  
   230  	cases := []struct {
   231  		// M will be initialized if unset
   232  		M func() map[string]interface{}
   233  		K string
   234  		// Value is always "x"
   235  		// Result is compared by Error() string equality
   236  		Result error
   237  	}{
   238  		{
   239  			K:      ".",
   240  			Result: ErrInvalidObjectPath,
   241  		},
   242  		{
   243  			K:      ".foo",
   244  			Result: ErrInvalidObjectPath,
   245  		},
   246  		{
   247  			K:      "foo.",
   248  			Result: ErrInvalidObjectPath,
   249  		},
   250  		{
   251  			K:      ".a.",
   252  			Result: ErrInvalidObjectPath,
   253  		},
   254  		{
   255  			K:      "foo..bar",
   256  			Result: ErrInvalidObjectPath,
   257  		},
   258  		{
   259  			K:      "foo...bar",
   260  			Result: ErrInvalidObjectPath,
   261  		},
   262  		{
   263  			K:      "foo.bar..quux",
   264  			Result: ErrInvalidObjectPath,
   265  		},
   266  		{
   267  			K:      "foo..bar.quux",
   268  			Result: ErrInvalidObjectPath,
   269  		},
   270  		{
   271  			K:      "foo.bar.quux.",
   272  			Result: ErrInvalidObjectPath,
   273  		},
   274  		{
   275  			M: func() map[string]interface{} {
   276  				return map[string]interface{}{
   277  					"a": "a",
   278  					"foo": map[string]interface{}{
   279  						"b": "b",
   280  						"bar": map[string]interface{}{
   281  							"c": "c",
   282  						},
   283  					},
   284  				}
   285  			},
   286  			K:      "foo.bar.quux.",
   287  			Result: ErrInvalidObjectPath,
   288  		},
   289  		{
   290  			M: func() map[string]interface{} {
   291  				return map[string]interface{}{
   292  					"a": "a",
   293  					"foo": map[string]interface{}{
   294  						"b": "b",
   295  						"bar": map[string]interface{}{
   296  							"c": "c",
   297  						},
   298  					},
   299  				}
   300  			},
   301  			K:      "foo.bar..quux",
   302  			Result: ErrInvalidObjectPath,
   303  		},
   304  		{
   305  			M: func() map[string]interface{} {
   306  				return map[string]interface{}{
   307  					"a": "a",
   308  					"foo": map[string]interface{}{
   309  						"b": "b",
   310  						"bar": map[string]interface{}{
   311  							"c": "c",
   312  						},
   313  					},
   314  				}
   315  			},
   316  			K:      "foo.bar..quux",
   317  			Result: ErrInvalidObjectPath,
   318  		},
   319  	}
   320  
   321  	for i := range cases {
   322  		tc := cases[i]
   323  		name := tc.K
   324  		if tc.M != nil {
   325  			name += "-cleanup"
   326  		}
   327  		t.Run(name, func(t *testing.T) {
   328  			ci.Parallel(t)
   329  
   330  			// Copy original M value to ensure it doesn't get altered
   331  			if tc.M == nil {
   332  				tc.M = func() map[string]interface{} {
   333  					return map[string]interface{}{}
   334  				}
   335  			}
   336  
   337  			// Call func and assert error
   338  			m := tc.M()
   339  			err := addNestedKey(m, tc.K, "x")
   340  			require.EqualError(t, err, tc.Result.Error())
   341  
   342  			// Ensure M wasn't altered
   343  			require.Equal(t, tc.M(), m)
   344  		})
   345  	}
   346  }
   347  
   348  func TestCtyify_Ok(t *testing.T) {
   349  	ci.Parallel(t)
   350  
   351  	cases := []struct {
   352  		Name string
   353  		In   map[string]interface{}
   354  		Out  map[string]cty.Value
   355  	}{
   356  		{
   357  			Name: "OneVal",
   358  			In: map[string]interface{}{
   359  				"a": "b",
   360  			},
   361  			Out: map[string]cty.Value{
   362  				"a": cty.StringVal("b"),
   363  			},
   364  		},
   365  		{
   366  			Name: "MultiVal",
   367  			In: map[string]interface{}{
   368  				"a":   "b",
   369  				"foo": "bar",
   370  			},
   371  			Out: map[string]cty.Value{
   372  				"a":   cty.StringVal("b"),
   373  				"foo": cty.StringVal("bar"),
   374  			},
   375  		},
   376  		{
   377  			Name: "NestedVals",
   378  			In: map[string]interface{}{
   379  				"a": "b",
   380  				"foo": map[string]interface{}{
   381  					"c": "d",
   382  					"bar": map[string]interface{}{
   383  						"quux": "z",
   384  					},
   385  				},
   386  				"123": map[string]interface{}{
   387  					"bar": map[string]interface{}{
   388  						"456": "789",
   389  					},
   390  				},
   391  			},
   392  			Out: map[string]cty.Value{
   393  				"a": cty.StringVal("b"),
   394  				"foo": cty.ObjectVal(map[string]cty.Value{
   395  					"c": cty.StringVal("d"),
   396  					"bar": cty.ObjectVal(map[string]cty.Value{
   397  						"quux": cty.StringVal("z"),
   398  					}),
   399  				}),
   400  				"123": cty.ObjectVal(map[string]cty.Value{
   401  					"bar": cty.ObjectVal(map[string]cty.Value{
   402  						"456": cty.StringVal("789"),
   403  					}),
   404  				}),
   405  			},
   406  		},
   407  	}
   408  
   409  	for i := range cases {
   410  		tc := cases[i]
   411  		t.Run(tc.Name, func(t *testing.T) {
   412  			ci.Parallel(t)
   413  
   414  			// ctiyif and check for errors
   415  			result, err := ctyify(tc.In)
   416  			require.NoError(t, err)
   417  
   418  			// convert results to ObjectVals and compare with RawEquals
   419  			resultObj := cty.ObjectVal(result)
   420  			OutObj := cty.ObjectVal(tc.Out)
   421  			require.True(t, OutObj.RawEquals(resultObj))
   422  		})
   423  	}
   424  }
   425  
   426  func TestCtyify_Bad(t *testing.T) {
   427  	ci.Parallel(t)
   428  
   429  	cases := []struct {
   430  		Name string
   431  		In   map[string]interface{}
   432  		Out  map[string]cty.Value
   433  	}{
   434  		{
   435  			Name: "NonStringVal",
   436  			In: map[string]interface{}{
   437  				"a": 1,
   438  			},
   439  		},
   440  		{
   441  			Name: "NestedNonString",
   442  			In: map[string]interface{}{
   443  				"foo": map[string]interface{}{
   444  					"c": 1,
   445  				},
   446  			},
   447  		},
   448  	}
   449  
   450  	for i := range cases {
   451  		tc := cases[i]
   452  		t.Run(tc.Name, func(t *testing.T) {
   453  			ci.Parallel(t)
   454  
   455  			// ctiyif and check for errors
   456  			result, err := ctyify(tc.In)
   457  			require.Error(t, err)
   458  			require.Nil(t, result)
   459  		})
   460  	}
   461  }