github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/helper/pluginutils/hclspecutils/dec_test.go (about)

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