open-nomad/helper/pluginutils/hclspecutils/dec_test.go

634 lines
12 KiB
Go

package hclspecutils
import (
"testing"
"github.com/hashicorp/hcl/v2/hcldec"
"github.com/hashicorp/nomad/ci"
"github.com/hashicorp/nomad/plugins/shared/hclspec"
"github.com/stretchr/testify/require"
"github.com/zclconf/go-cty/cty"
)
type testConversions struct {
Name string
Input *hclspec.Spec
Expected hcldec.Spec
ExpectedError string
}
func testSpecConversions(t *testing.T, cases []testConversions) {
t.Helper()
for _, c := range cases {
t.Run(c.Name, func(t *testing.T) {
act, diag := Convert(c.Input)
if diag.HasErrors() {
if c.ExpectedError == "" {
t.Fatalf("Convert %q failed: %v", c.Name, diag.Error())
}
require.Contains(t, diag.Error(), c.ExpectedError)
} else if c.ExpectedError != "" {
t.Fatalf("Expected error %q", c.ExpectedError)
}
require.EqualValues(t, c.Expected, act)
})
}
}
func TestDec_Convert_Object(t *testing.T) {
ci.Parallel(t)
tests := []testConversions{
{
Name: "Object w/ only attributes",
Input: &hclspec.Spec{
Block: &hclspec.Spec_Object{
Object: &hclspec.Object{
Attributes: map[string]*hclspec.Spec{
"foo": {
Block: &hclspec.Spec_Attr{
Attr: &hclspec.Attr{
Type: "string",
Required: false,
},
},
},
"bar": {
Block: &hclspec.Spec_Attr{
Attr: &hclspec.Attr{
Type: "number",
Required: true,
},
},
},
"baz": {
Block: &hclspec.Spec_Attr{
Attr: &hclspec.Attr{
Type: "bool",
},
},
},
},
},
},
},
Expected: hcldec.ObjectSpec(map[string]hcldec.Spec{
"foo": &hcldec.AttrSpec{
Name: "foo",
Type: cty.String,
Required: false,
},
"bar": &hcldec.AttrSpec{
Name: "bar",
Type: cty.Number,
Required: true,
},
"baz": &hcldec.AttrSpec{
Name: "baz",
Type: cty.Bool,
Required: false,
},
}),
},
}
testSpecConversions(t, tests)
}
func TestDec_Convert_Array(t *testing.T) {
ci.Parallel(t)
tests := []testConversions{
{
Name: "array basic",
Input: &hclspec.Spec{
Block: &hclspec.Spec_Array{
Array: &hclspec.Array{
Values: []*hclspec.Spec{
{
Block: &hclspec.Spec_Attr{
Attr: &hclspec.Attr{
Name: "foo",
Required: true,
Type: "string",
},
},
},
{
Block: &hclspec.Spec_Attr{
Attr: &hclspec.Attr{
Name: "bar",
Required: true,
Type: "string",
},
},
},
},
},
},
},
Expected: hcldec.TupleSpec{
&hcldec.AttrSpec{
Name: "foo",
Type: cty.String,
Required: true,
},
&hcldec.AttrSpec{
Name: "bar",
Type: cty.String,
Required: true,
},
},
},
}
testSpecConversions(t, tests)
}
func TestDec_Convert_Attr(t *testing.T) {
ci.Parallel(t)
tests := []testConversions{
{
Name: "attr basic type",
Input: &hclspec.Spec{
Block: &hclspec.Spec_Attr{
Attr: &hclspec.Attr{
Name: "foo",
Required: true,
Type: "string",
},
},
},
Expected: &hcldec.AttrSpec{
Name: "foo",
Type: cty.String,
Required: true,
},
},
{
Name: "attr object type",
Input: &hclspec.Spec{
Block: &hclspec.Spec_Attr{
Attr: &hclspec.Attr{
Name: "foo",
Required: true,
Type: "object({name1 = string, name2 = bool})",
},
},
},
Expected: &hcldec.AttrSpec{
Name: "foo",
Type: cty.Object(map[string]cty.Type{
"name1": cty.String,
"name2": cty.Bool,
}),
Required: true,
},
},
{
Name: "attr no name",
Input: &hclspec.Spec{
Block: &hclspec.Spec_Attr{
Attr: &hclspec.Attr{
Required: true,
Type: "string",
},
},
},
ExpectedError: "Missing name in attribute spec",
},
}
testSpecConversions(t, tests)
}
func TestDec_Convert_Block(t *testing.T) {
ci.Parallel(t)
tests := []testConversions{
{
Name: "block with attr",
Input: &hclspec.Spec{
Block: &hclspec.Spec_BlockValue{
BlockValue: &hclspec.Block{
Name: "test",
Required: true,
Nested: &hclspec.Spec{
Block: &hclspec.Spec_Attr{
Attr: &hclspec.Attr{
Name: "foo",
Type: "string",
},
},
},
},
},
},
Expected: &hcldec.BlockSpec{
TypeName: "test",
Required: true,
Nested: &hcldec.AttrSpec{
Name: "foo",
Type: cty.String,
Required: false,
},
},
},
{
Name: "block with nested block",
Input: &hclspec.Spec{
Block: &hclspec.Spec_BlockValue{
BlockValue: &hclspec.Block{
Name: "test",
Required: true,
Nested: &hclspec.Spec{
Block: &hclspec.Spec_BlockValue{
BlockValue: &hclspec.Block{
Name: "test",
Required: true,
Nested: &hclspec.Spec{
Block: &hclspec.Spec_Attr{
Attr: &hclspec.Attr{
Name: "foo",
Type: "string",
},
},
},
},
},
},
},
},
},
Expected: &hcldec.BlockSpec{
TypeName: "test",
Required: true,
Nested: &hcldec.BlockSpec{
TypeName: "test",
Required: true,
Nested: &hcldec.AttrSpec{
Name: "foo",
Type: cty.String,
Required: false,
},
},
},
},
}
testSpecConversions(t, tests)
}
func TestDec_Convert_BlockAttrs(t *testing.T) {
ci.Parallel(t)
tests := []testConversions{
{
Name: "block attr",
Input: &hclspec.Spec{
Block: &hclspec.Spec_BlockAttrs{
BlockAttrs: &hclspec.BlockAttrs{
Name: "test",
Type: "string",
Required: true,
},
},
},
Expected: &hcldec.BlockAttrsSpec{
TypeName: "test",
ElementType: cty.String,
Required: true,
},
},
{
Name: "block list no name",
Input: &hclspec.Spec{
Block: &hclspec.Spec_BlockAttrs{
BlockAttrs: &hclspec.BlockAttrs{
Type: "string",
Required: true,
},
},
},
ExpectedError: "Missing name in block_attrs spec",
},
}
testSpecConversions(t, tests)
}
func TestDec_Convert_BlockList(t *testing.T) {
ci.Parallel(t)
tests := []testConversions{
{
Name: "block list with attr",
Input: &hclspec.Spec{
Block: &hclspec.Spec_BlockList{
BlockList: &hclspec.BlockList{
Name: "test",
MinItems: 1,
MaxItems: 3,
Nested: &hclspec.Spec{
Block: &hclspec.Spec_Attr{
Attr: &hclspec.Attr{
Name: "foo",
Type: "string",
},
},
},
},
},
},
Expected: &hcldec.BlockListSpec{
TypeName: "test",
MinItems: 1,
MaxItems: 3,
Nested: &hcldec.AttrSpec{
Name: "foo",
Type: cty.String,
Required: false,
},
},
},
{
Name: "block list no name",
Input: &hclspec.Spec{
Block: &hclspec.Spec_BlockList{
BlockList: &hclspec.BlockList{
MinItems: 1,
MaxItems: 3,
Nested: &hclspec.Spec{
Block: &hclspec.Spec_Attr{
Attr: &hclspec.Attr{
Name: "foo",
Type: "string",
},
},
},
},
},
},
ExpectedError: "Missing name in block_list spec",
},
}
testSpecConversions(t, tests)
}
func TestDec_Convert_BlockSet(t *testing.T) {
ci.Parallel(t)
tests := []testConversions{
{
Name: "block set with attr",
Input: &hclspec.Spec{
Block: &hclspec.Spec_BlockSet{
BlockSet: &hclspec.BlockSet{
Name: "test",
MinItems: 1,
MaxItems: 3,
Nested: &hclspec.Spec{
Block: &hclspec.Spec_Attr{
Attr: &hclspec.Attr{
Name: "foo",
Type: "string",
},
},
},
},
},
},
Expected: &hcldec.BlockSetSpec{
TypeName: "test",
MinItems: 1,
MaxItems: 3,
Nested: &hcldec.AttrSpec{
Name: "foo",
Type: cty.String,
Required: false,
},
},
},
{
Name: "block set missing name",
Input: &hclspec.Spec{
Block: &hclspec.Spec_BlockSet{
BlockSet: &hclspec.BlockSet{
MinItems: 1,
MaxItems: 3,
Nested: &hclspec.Spec{
Block: &hclspec.Spec_Attr{
Attr: &hclspec.Attr{
Name: "foo",
Type: "string",
},
},
},
},
},
},
ExpectedError: "Missing name in block_set spec",
},
}
testSpecConversions(t, tests)
}
func TestDec_Convert_BlockMap(t *testing.T) {
ci.Parallel(t)
tests := []testConversions{
{
Name: "block map with attr",
Input: &hclspec.Spec{
Block: &hclspec.Spec_BlockMap{
BlockMap: &hclspec.BlockMap{
Name: "test",
Labels: []string{"key1", "key2"},
Nested: &hclspec.Spec{
Block: &hclspec.Spec_Attr{
Attr: &hclspec.Attr{
Name: "foo",
Type: "string",
},
},
},
},
},
},
Expected: &hcldec.BlockMapSpec{
TypeName: "test",
LabelNames: []string{"key1", "key2"},
Nested: &hcldec.AttrSpec{
Name: "foo",
Type: cty.String,
Required: false,
},
},
},
{
Name: "block map missing name",
Input: &hclspec.Spec{
Block: &hclspec.Spec_BlockMap{
BlockMap: &hclspec.BlockMap{
Labels: []string{"key1", "key2"},
Nested: &hclspec.Spec{
Block: &hclspec.Spec_Attr{
Attr: &hclspec.Attr{
Name: "foo",
Type: "string",
},
},
},
},
},
},
ExpectedError: "Missing name in block_map spec",
},
{
Name: "block map missing labels",
Input: &hclspec.Spec{
Block: &hclspec.Spec_BlockMap{
BlockMap: &hclspec.BlockMap{
Name: "foo",
Nested: &hclspec.Spec{
Block: &hclspec.Spec_Attr{
Attr: &hclspec.Attr{
Name: "foo",
Type: "string",
},
},
},
},
},
},
ExpectedError: "Invalid block label name list",
},
}
testSpecConversions(t, tests)
}
func TestDec_Convert_Default(t *testing.T) {
ci.Parallel(t)
tests := []testConversions{
{
Name: "default attr",
Input: &hclspec.Spec{
Block: &hclspec.Spec_Default{
Default: &hclspec.Default{
Primary: &hclspec.Spec{
Block: &hclspec.Spec_Attr{
Attr: &hclspec.Attr{
Name: "foo",
Type: "string",
Required: true,
},
},
},
Default: &hclspec.Spec{
Block: &hclspec.Spec_Literal{
Literal: &hclspec.Literal{
Value: "\"hi\"",
},
},
},
},
},
},
Expected: &hcldec.DefaultSpec{
Primary: &hcldec.AttrSpec{
Name: "foo",
Type: cty.String,
Required: true,
},
Default: &hcldec.LiteralSpec{
Value: cty.StringVal("hi"),
},
},
},
}
testSpecConversions(t, tests)
}
func TestDec_Convert_Literal(t *testing.T) {
ci.Parallel(t)
tests := []testConversions{
{
Name: "bool: true",
Input: &hclspec.Spec{
Block: &hclspec.Spec_Literal{
Literal: &hclspec.Literal{
Value: "true",
},
},
},
Expected: &hcldec.LiteralSpec{
Value: cty.BoolVal(true),
},
},
{
Name: "bool: false",
Input: &hclspec.Spec{
Block: &hclspec.Spec_Literal{
Literal: &hclspec.Literal{
Value: "false",
},
},
},
Expected: &hcldec.LiteralSpec{
Value: cty.BoolVal(false),
},
},
{
Name: "string",
Input: &hclspec.Spec{
Block: &hclspec.Spec_Literal{
Literal: &hclspec.Literal{
Value: "\"hi\"",
},
},
},
Expected: &hcldec.LiteralSpec{
Value: cty.StringVal("hi"),
},
},
{
Name: "string w/ func",
Input: &hclspec.Spec{
Block: &hclspec.Spec_Literal{
Literal: &hclspec.Literal{
Value: "reverse(\"hi\")",
},
},
},
Expected: &hcldec.LiteralSpec{
Value: cty.StringVal("ih"),
},
},
{
Name: "list string",
Input: &hclspec.Spec{
Block: &hclspec.Spec_Literal{
Literal: &hclspec.Literal{
Value: "[\"hi\", \"bye\"]",
},
},
},
Expected: &hcldec.LiteralSpec{
Value: cty.TupleVal([]cty.Value{cty.StringVal("hi"), cty.StringVal("bye")}),
},
},
}
testSpecConversions(t, tests)
}