github.com/hernad/nomad@v1.6.112/helper/pluginutils/hclspecutils/dec_test.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package hclspecutils
     5  
     6  import (
     7  	"testing"
     8  
     9  	"github.com/hashicorp/hcl/v2/hcldec"
    10  	"github.com/hernad/nomad/ci"
    11  	"github.com/hernad/nomad/plugins/shared/hclspec"
    12  	"github.com/stretchr/testify/require"
    13  	"github.com/zclconf/go-cty/cty"
    14  )
    15  
    16  type testConversions struct {
    17  	Name          string
    18  	Input         *hclspec.Spec
    19  	Expected      hcldec.Spec
    20  	ExpectedError string
    21  }
    22  
    23  func testSpecConversions(t *testing.T, cases []testConversions) {
    24  	t.Helper()
    25  
    26  	for _, c := range cases {
    27  		t.Run(c.Name, func(t *testing.T) {
    28  			act, diag := Convert(c.Input)
    29  			if diag.HasErrors() {
    30  				if c.ExpectedError == "" {
    31  					t.Fatalf("Convert %q failed: %v", c.Name, diag.Error())
    32  				}
    33  
    34  				require.Contains(t, diag.Error(), c.ExpectedError)
    35  			} else if c.ExpectedError != "" {
    36  				t.Fatalf("Expected error %q", c.ExpectedError)
    37  			}
    38  
    39  			require.EqualValues(t, c.Expected, act)
    40  		})
    41  	}
    42  }
    43  
    44  func TestDec_Convert_Object(t *testing.T) {
    45  	ci.Parallel(t)
    46  
    47  	tests := []testConversions{
    48  		{
    49  			Name: "Object w/ only attributes",
    50  			Input: &hclspec.Spec{
    51  				Block: &hclspec.Spec_Object{
    52  					Object: &hclspec.Object{
    53  						Attributes: map[string]*hclspec.Spec{
    54  							"foo": {
    55  								Block: &hclspec.Spec_Attr{
    56  									Attr: &hclspec.Attr{
    57  										Type:     "string",
    58  										Required: false,
    59  									},
    60  								},
    61  							},
    62  							"bar": {
    63  								Block: &hclspec.Spec_Attr{
    64  									Attr: &hclspec.Attr{
    65  										Type:     "number",
    66  										Required: true,
    67  									},
    68  								},
    69  							},
    70  							"baz": {
    71  								Block: &hclspec.Spec_Attr{
    72  									Attr: &hclspec.Attr{
    73  										Type: "bool",
    74  									},
    75  								},
    76  							},
    77  						},
    78  					},
    79  				},
    80  			},
    81  			Expected: hcldec.ObjectSpec(map[string]hcldec.Spec{
    82  				"foo": &hcldec.AttrSpec{
    83  					Name:     "foo",
    84  					Type:     cty.String,
    85  					Required: false,
    86  				},
    87  				"bar": &hcldec.AttrSpec{
    88  					Name:     "bar",
    89  					Type:     cty.Number,
    90  					Required: true,
    91  				},
    92  				"baz": &hcldec.AttrSpec{
    93  					Name:     "baz",
    94  					Type:     cty.Bool,
    95  					Required: false,
    96  				},
    97  			}),
    98  		},
    99  	}
   100  
   101  	testSpecConversions(t, tests)
   102  }
   103  
   104  func TestDec_Convert_Array(t *testing.T) {
   105  	ci.Parallel(t)
   106  
   107  	tests := []testConversions{
   108  		{
   109  			Name: "array basic",
   110  			Input: &hclspec.Spec{
   111  				Block: &hclspec.Spec_Array{
   112  					Array: &hclspec.Array{
   113  						Values: []*hclspec.Spec{
   114  							{
   115  								Block: &hclspec.Spec_Attr{
   116  									Attr: &hclspec.Attr{
   117  										Name:     "foo",
   118  										Required: true,
   119  										Type:     "string",
   120  									},
   121  								},
   122  							},
   123  							{
   124  								Block: &hclspec.Spec_Attr{
   125  									Attr: &hclspec.Attr{
   126  										Name:     "bar",
   127  										Required: true,
   128  										Type:     "string",
   129  									},
   130  								},
   131  							},
   132  						},
   133  					},
   134  				},
   135  			},
   136  			Expected: hcldec.TupleSpec{
   137  				&hcldec.AttrSpec{
   138  					Name:     "foo",
   139  					Type:     cty.String,
   140  					Required: true,
   141  				},
   142  				&hcldec.AttrSpec{
   143  					Name:     "bar",
   144  					Type:     cty.String,
   145  					Required: true,
   146  				},
   147  			},
   148  		},
   149  	}
   150  
   151  	testSpecConversions(t, tests)
   152  }
   153  
   154  func TestDec_Convert_Attr(t *testing.T) {
   155  	ci.Parallel(t)
   156  
   157  	tests := []testConversions{
   158  		{
   159  			Name: "attr basic type",
   160  			Input: &hclspec.Spec{
   161  				Block: &hclspec.Spec_Attr{
   162  					Attr: &hclspec.Attr{
   163  						Name:     "foo",
   164  						Required: true,
   165  						Type:     "string",
   166  					},
   167  				},
   168  			},
   169  			Expected: &hcldec.AttrSpec{
   170  				Name:     "foo",
   171  				Type:     cty.String,
   172  				Required: true,
   173  			},
   174  		},
   175  		{
   176  			Name: "attr object type",
   177  			Input: &hclspec.Spec{
   178  				Block: &hclspec.Spec_Attr{
   179  					Attr: &hclspec.Attr{
   180  						Name:     "foo",
   181  						Required: true,
   182  						Type:     "object({name1 = string, name2 = bool})",
   183  					},
   184  				},
   185  			},
   186  			Expected: &hcldec.AttrSpec{
   187  				Name: "foo",
   188  				Type: cty.Object(map[string]cty.Type{
   189  					"name1": cty.String,
   190  					"name2": cty.Bool,
   191  				}),
   192  				Required: true,
   193  			},
   194  		},
   195  		{
   196  			Name: "attr no name",
   197  			Input: &hclspec.Spec{
   198  				Block: &hclspec.Spec_Attr{
   199  					Attr: &hclspec.Attr{
   200  						Required: true,
   201  						Type:     "string",
   202  					},
   203  				},
   204  			},
   205  			ExpectedError: "Missing name in attribute spec",
   206  		},
   207  	}
   208  
   209  	testSpecConversions(t, tests)
   210  }
   211  
   212  func TestDec_Convert_Block(t *testing.T) {
   213  	ci.Parallel(t)
   214  
   215  	tests := []testConversions{
   216  		{
   217  			Name: "block with attr",
   218  			Input: &hclspec.Spec{
   219  				Block: &hclspec.Spec_BlockValue{
   220  					BlockValue: &hclspec.Block{
   221  						Name:     "test",
   222  						Required: true,
   223  						Nested: &hclspec.Spec{
   224  							Block: &hclspec.Spec_Attr{
   225  								Attr: &hclspec.Attr{
   226  									Name: "foo",
   227  									Type: "string",
   228  								},
   229  							},
   230  						},
   231  					},
   232  				},
   233  			},
   234  			Expected: &hcldec.BlockSpec{
   235  				TypeName: "test",
   236  				Required: true,
   237  				Nested: &hcldec.AttrSpec{
   238  					Name:     "foo",
   239  					Type:     cty.String,
   240  					Required: false,
   241  				},
   242  			},
   243  		},
   244  		{
   245  			Name: "block with nested block",
   246  			Input: &hclspec.Spec{
   247  				Block: &hclspec.Spec_BlockValue{
   248  					BlockValue: &hclspec.Block{
   249  						Name:     "test",
   250  						Required: true,
   251  						Nested: &hclspec.Spec{
   252  							Block: &hclspec.Spec_BlockValue{
   253  								BlockValue: &hclspec.Block{
   254  									Name:     "test",
   255  									Required: true,
   256  									Nested: &hclspec.Spec{
   257  										Block: &hclspec.Spec_Attr{
   258  											Attr: &hclspec.Attr{
   259  												Name: "foo",
   260  												Type: "string",
   261  											},
   262  										},
   263  									},
   264  								},
   265  							},
   266  						},
   267  					},
   268  				},
   269  			},
   270  			Expected: &hcldec.BlockSpec{
   271  				TypeName: "test",
   272  				Required: true,
   273  				Nested: &hcldec.BlockSpec{
   274  					TypeName: "test",
   275  					Required: true,
   276  					Nested: &hcldec.AttrSpec{
   277  						Name:     "foo",
   278  						Type:     cty.String,
   279  						Required: false,
   280  					},
   281  				},
   282  			},
   283  		},
   284  	}
   285  
   286  	testSpecConversions(t, tests)
   287  }
   288  
   289  func TestDec_Convert_BlockAttrs(t *testing.T) {
   290  	ci.Parallel(t)
   291  
   292  	tests := []testConversions{
   293  		{
   294  			Name: "block attr",
   295  			Input: &hclspec.Spec{
   296  				Block: &hclspec.Spec_BlockAttrs{
   297  					BlockAttrs: &hclspec.BlockAttrs{
   298  						Name:     "test",
   299  						Type:     "string",
   300  						Required: true,
   301  					},
   302  				},
   303  			},
   304  			Expected: &hcldec.BlockAttrsSpec{
   305  				TypeName:    "test",
   306  				ElementType: cty.String,
   307  				Required:    true,
   308  			},
   309  		},
   310  		{
   311  			Name: "block list no name",
   312  			Input: &hclspec.Spec{
   313  				Block: &hclspec.Spec_BlockAttrs{
   314  					BlockAttrs: &hclspec.BlockAttrs{
   315  						Type:     "string",
   316  						Required: true,
   317  					},
   318  				},
   319  			},
   320  			ExpectedError: "Missing name in block_attrs spec",
   321  		},
   322  	}
   323  
   324  	testSpecConversions(t, tests)
   325  }
   326  
   327  func TestDec_Convert_BlockList(t *testing.T) {
   328  	ci.Parallel(t)
   329  
   330  	tests := []testConversions{
   331  		{
   332  			Name: "block list with attr",
   333  			Input: &hclspec.Spec{
   334  				Block: &hclspec.Spec_BlockList{
   335  					BlockList: &hclspec.BlockList{
   336  						Name:     "test",
   337  						MinItems: 1,
   338  						MaxItems: 3,
   339  						Nested: &hclspec.Spec{
   340  							Block: &hclspec.Spec_Attr{
   341  								Attr: &hclspec.Attr{
   342  									Name: "foo",
   343  									Type: "string",
   344  								},
   345  							},
   346  						},
   347  					},
   348  				},
   349  			},
   350  			Expected: &hcldec.BlockListSpec{
   351  				TypeName: "test",
   352  				MinItems: 1,
   353  				MaxItems: 3,
   354  				Nested: &hcldec.AttrSpec{
   355  					Name:     "foo",
   356  					Type:     cty.String,
   357  					Required: false,
   358  				},
   359  			},
   360  		},
   361  		{
   362  			Name: "block list no name",
   363  			Input: &hclspec.Spec{
   364  				Block: &hclspec.Spec_BlockList{
   365  					BlockList: &hclspec.BlockList{
   366  						MinItems: 1,
   367  						MaxItems: 3,
   368  						Nested: &hclspec.Spec{
   369  							Block: &hclspec.Spec_Attr{
   370  								Attr: &hclspec.Attr{
   371  									Name: "foo",
   372  									Type: "string",
   373  								},
   374  							},
   375  						},
   376  					},
   377  				},
   378  			},
   379  			ExpectedError: "Missing name in block_list spec",
   380  		},
   381  	}
   382  
   383  	testSpecConversions(t, tests)
   384  }
   385  
   386  func TestDec_Convert_BlockSet(t *testing.T) {
   387  	ci.Parallel(t)
   388  
   389  	tests := []testConversions{
   390  		{
   391  			Name: "block set with attr",
   392  			Input: &hclspec.Spec{
   393  				Block: &hclspec.Spec_BlockSet{
   394  					BlockSet: &hclspec.BlockSet{
   395  						Name:     "test",
   396  						MinItems: 1,
   397  						MaxItems: 3,
   398  						Nested: &hclspec.Spec{
   399  							Block: &hclspec.Spec_Attr{
   400  								Attr: &hclspec.Attr{
   401  									Name: "foo",
   402  									Type: "string",
   403  								},
   404  							},
   405  						},
   406  					},
   407  				},
   408  			},
   409  			Expected: &hcldec.BlockSetSpec{
   410  				TypeName: "test",
   411  				MinItems: 1,
   412  				MaxItems: 3,
   413  				Nested: &hcldec.AttrSpec{
   414  					Name:     "foo",
   415  					Type:     cty.String,
   416  					Required: false,
   417  				},
   418  			},
   419  		},
   420  		{
   421  			Name: "block set missing name",
   422  			Input: &hclspec.Spec{
   423  				Block: &hclspec.Spec_BlockSet{
   424  					BlockSet: &hclspec.BlockSet{
   425  						MinItems: 1,
   426  						MaxItems: 3,
   427  						Nested: &hclspec.Spec{
   428  							Block: &hclspec.Spec_Attr{
   429  								Attr: &hclspec.Attr{
   430  									Name: "foo",
   431  									Type: "string",
   432  								},
   433  							},
   434  						},
   435  					},
   436  				},
   437  			},
   438  			ExpectedError: "Missing name in block_set spec",
   439  		},
   440  	}
   441  
   442  	testSpecConversions(t, tests)
   443  }
   444  
   445  func TestDec_Convert_BlockMap(t *testing.T) {
   446  	ci.Parallel(t)
   447  
   448  	tests := []testConversions{
   449  		{
   450  			Name: "block map with attr",
   451  			Input: &hclspec.Spec{
   452  				Block: &hclspec.Spec_BlockMap{
   453  					BlockMap: &hclspec.BlockMap{
   454  						Name:   "test",
   455  						Labels: []string{"key1", "key2"},
   456  						Nested: &hclspec.Spec{
   457  							Block: &hclspec.Spec_Attr{
   458  								Attr: &hclspec.Attr{
   459  									Name: "foo",
   460  									Type: "string",
   461  								},
   462  							},
   463  						},
   464  					},
   465  				},
   466  			},
   467  			Expected: &hcldec.BlockMapSpec{
   468  				TypeName:   "test",
   469  				LabelNames: []string{"key1", "key2"},
   470  				Nested: &hcldec.AttrSpec{
   471  					Name:     "foo",
   472  					Type:     cty.String,
   473  					Required: false,
   474  				},
   475  			},
   476  		},
   477  		{
   478  			Name: "block map missing name",
   479  			Input: &hclspec.Spec{
   480  				Block: &hclspec.Spec_BlockMap{
   481  					BlockMap: &hclspec.BlockMap{
   482  						Labels: []string{"key1", "key2"},
   483  						Nested: &hclspec.Spec{
   484  							Block: &hclspec.Spec_Attr{
   485  								Attr: &hclspec.Attr{
   486  									Name: "foo",
   487  									Type: "string",
   488  								},
   489  							},
   490  						},
   491  					},
   492  				},
   493  			},
   494  			ExpectedError: "Missing name in block_map spec",
   495  		},
   496  		{
   497  			Name: "block map missing labels",
   498  			Input: &hclspec.Spec{
   499  				Block: &hclspec.Spec_BlockMap{
   500  					BlockMap: &hclspec.BlockMap{
   501  						Name: "foo",
   502  						Nested: &hclspec.Spec{
   503  							Block: &hclspec.Spec_Attr{
   504  								Attr: &hclspec.Attr{
   505  									Name: "foo",
   506  									Type: "string",
   507  								},
   508  							},
   509  						},
   510  					},
   511  				},
   512  			},
   513  			ExpectedError: "Invalid block label name list",
   514  		},
   515  	}
   516  
   517  	testSpecConversions(t, tests)
   518  }
   519  
   520  func TestDec_Convert_Default(t *testing.T) {
   521  	ci.Parallel(t)
   522  
   523  	tests := []testConversions{
   524  		{
   525  			Name: "default attr",
   526  			Input: &hclspec.Spec{
   527  				Block: &hclspec.Spec_Default{
   528  					Default: &hclspec.Default{
   529  						Primary: &hclspec.Spec{
   530  							Block: &hclspec.Spec_Attr{
   531  								Attr: &hclspec.Attr{
   532  									Name:     "foo",
   533  									Type:     "string",
   534  									Required: true,
   535  								},
   536  							},
   537  						},
   538  						Default: &hclspec.Spec{
   539  							Block: &hclspec.Spec_Literal{
   540  								Literal: &hclspec.Literal{
   541  									Value: "\"hi\"",
   542  								},
   543  							},
   544  						},
   545  					},
   546  				},
   547  			},
   548  			Expected: &hcldec.DefaultSpec{
   549  				Primary: &hcldec.AttrSpec{
   550  					Name:     "foo",
   551  					Type:     cty.String,
   552  					Required: true,
   553  				},
   554  				Default: &hcldec.LiteralSpec{
   555  					Value: cty.StringVal("hi"),
   556  				},
   557  			},
   558  		},
   559  	}
   560  
   561  	testSpecConversions(t, tests)
   562  }
   563  
   564  func TestDec_Convert_Literal(t *testing.T) {
   565  	ci.Parallel(t)
   566  
   567  	tests := []testConversions{
   568  		{
   569  			Name: "bool: true",
   570  			Input: &hclspec.Spec{
   571  				Block: &hclspec.Spec_Literal{
   572  					Literal: &hclspec.Literal{
   573  						Value: "true",
   574  					},
   575  				},
   576  			},
   577  			Expected: &hcldec.LiteralSpec{
   578  				Value: cty.BoolVal(true),
   579  			},
   580  		},
   581  		{
   582  			Name: "bool: false",
   583  			Input: &hclspec.Spec{
   584  				Block: &hclspec.Spec_Literal{
   585  					Literal: &hclspec.Literal{
   586  						Value: "false",
   587  					},
   588  				},
   589  			},
   590  			Expected: &hcldec.LiteralSpec{
   591  				Value: cty.BoolVal(false),
   592  			},
   593  		},
   594  		{
   595  			Name: "string",
   596  			Input: &hclspec.Spec{
   597  				Block: &hclspec.Spec_Literal{
   598  					Literal: &hclspec.Literal{
   599  						Value: "\"hi\"",
   600  					},
   601  				},
   602  			},
   603  			Expected: &hcldec.LiteralSpec{
   604  				Value: cty.StringVal("hi"),
   605  			},
   606  		},
   607  		{
   608  			Name: "string w/ func",
   609  			Input: &hclspec.Spec{
   610  				Block: &hclspec.Spec_Literal{
   611  					Literal: &hclspec.Literal{
   612  						Value: "reverse(\"hi\")",
   613  					},
   614  				},
   615  			},
   616  			Expected: &hcldec.LiteralSpec{
   617  				Value: cty.StringVal("ih"),
   618  			},
   619  		},
   620  		{
   621  			Name: "list string",
   622  			Input: &hclspec.Spec{
   623  				Block: &hclspec.Spec_Literal{
   624  					Literal: &hclspec.Literal{
   625  						Value: "[\"hi\", \"bye\"]",
   626  					},
   627  				},
   628  			},
   629  			Expected: &hcldec.LiteralSpec{
   630  				Value: cty.TupleVal([]cty.Value{cty.StringVal("hi"), cty.StringVal("bye")}),
   631  			},
   632  		},
   633  	}
   634  
   635  	testSpecConversions(t, tests)
   636  }