mirror of https://github.com/hashicorp/consul
561 lines
12 KiB
Go
561 lines
12 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package protohcl
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"reflect"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
"github.com/zclconf/go-cty/cty"
|
|
"github.com/zclconf/go-cty/cty/function"
|
|
"google.golang.org/protobuf/proto"
|
|
"google.golang.org/protobuf/types/known/anypb"
|
|
|
|
"github.com/hashicorp/consul/internal/protohcl/testproto"
|
|
"github.com/hashicorp/hcl/v2/hclparse"
|
|
)
|
|
|
|
func TestPrimitives(t *testing.T) {
|
|
hcl := `
|
|
double_val = 1.234
|
|
float_val = 2.345
|
|
int32_val = 536870912
|
|
int64_val = 25769803776
|
|
uint32_val = 2148532224
|
|
uint64_val = 9223372041149743104
|
|
sint32_val = 536870912
|
|
sint64_val = 25769803776
|
|
fixed32_val = 2148532224
|
|
fixed64_val = 9223372041149743104
|
|
sfixed32_val = 536870912
|
|
sfixed64_val = 25769803776
|
|
bool_val = true
|
|
string_val = "foo"
|
|
// This is base64 encoded "bar"
|
|
byte_val = "YmFy"
|
|
`
|
|
|
|
var out testproto.Primitives
|
|
|
|
err := Unmarshal([]byte(hcl), &out)
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, out.DoubleVal, float64(1.234))
|
|
require.Equal(t, out.FloatVal, float32(2.345))
|
|
require.Equal(t, out.Int32Val, int32(536870912))
|
|
require.Equal(t, out.Int64Val, int64(25769803776))
|
|
require.Equal(t, out.Uint32Val, uint32(2148532224))
|
|
require.Equal(t, out.Uint64Val, uint64(9223372041149743104))
|
|
require.Equal(t, out.Sint32Val, int32(536870912))
|
|
require.Equal(t, out.Sint64Val, int64(25769803776))
|
|
require.Equal(t, out.Fixed32Val, uint32(2148532224))
|
|
require.Equal(t, out.Fixed64Val, uint64(9223372041149743104))
|
|
require.Equal(t, out.Sfixed32Val, int32(536870912))
|
|
require.Equal(t, out.Sfixed64Val, int64(25769803776))
|
|
require.Equal(t, out.BoolVal, true)
|
|
require.Equal(t, out.StringVal, "foo")
|
|
require.Equal(t, out.ByteVal, []byte("bar"))
|
|
}
|
|
|
|
func TestNestedAndCollections(t *testing.T) {
|
|
hcl := `
|
|
primitives {
|
|
uint32_val = 42
|
|
}
|
|
|
|
primitives_map "foo" {
|
|
uint32_val = 42
|
|
}
|
|
|
|
protocol_map = {
|
|
"foo" = "PROTOCOL_TCP"
|
|
}
|
|
|
|
primitives_list {
|
|
uint32_val = 42
|
|
}
|
|
|
|
primitives_list {
|
|
uint32_val = 56
|
|
}
|
|
|
|
int_list = [
|
|
1,
|
|
2
|
|
]
|
|
|
|
`
|
|
|
|
var out testproto.NestedAndCollections
|
|
|
|
err := Unmarshal([]byte(hcl), &out)
|
|
require.NoError(t, err)
|
|
|
|
require.NotNil(t, out.Primitives)
|
|
require.Equal(t, out.Primitives.Uint32Val, uint32(42))
|
|
require.NotNil(t, out.PrimitivesMap)
|
|
require.Equal(t, out.PrimitivesMap["foo"].Uint32Val, uint32(42))
|
|
require.NotNil(t, out.ProtocolMap)
|
|
require.Equal(t, out.ProtocolMap["foo"], testproto.Protocol_PROTOCOL_TCP)
|
|
require.Len(t, out.PrimitivesList, 2)
|
|
require.Equal(t, out.PrimitivesList[0].Uint32Val, uint32(42))
|
|
require.Equal(t, out.PrimitivesList[1].Uint32Val, uint32(56))
|
|
require.Len(t, out.IntList, 2)
|
|
require.Equal(t, out.IntList[1], int32(2))
|
|
}
|
|
|
|
func TestPrimitiveWrappers(t *testing.T) {
|
|
hcl := `
|
|
double_val = 1.234
|
|
float_val = 2.345
|
|
int32_val = 536870912
|
|
int64_val = 25769803776
|
|
uint32_val = 2148532224
|
|
uint64_val = 9223372041149743104
|
|
bool_val = true
|
|
string_val = "foo"
|
|
// This is base64 encoded "bar"
|
|
bytes_val = "YmFy"
|
|
`
|
|
var out testproto.Wrappers
|
|
|
|
err := Unmarshal([]byte(hcl), &out)
|
|
require.NoError(t, err)
|
|
require.Equal(t, out.DoubleVal.Value, float64(1.234))
|
|
require.Equal(t, out.FloatVal.Value, float32(2.345))
|
|
require.Equal(t, out.Int32Val.Value, int32(536870912))
|
|
require.Equal(t, out.Int64Val.Value, int64(25769803776))
|
|
require.Equal(t, out.Uint32Val.Value, uint32(2148532224))
|
|
require.Equal(t, out.Uint64Val.Value, uint64(9223372041149743104))
|
|
require.Equal(t, out.BoolVal.Value, true)
|
|
require.Equal(t, out.StringVal.Value, "foo")
|
|
require.Equal(t, out.BytesVal.Value, []byte("bar"))
|
|
}
|
|
|
|
func TestNonDynamicWellKnown(t *testing.T) {
|
|
hcl := `
|
|
empty_val = {}
|
|
timestamp_val = "2023-02-27T12:34:56.789Z"
|
|
duration_val = "12s"
|
|
`
|
|
var out testproto.NonDynamicWellKnown
|
|
|
|
err := Unmarshal([]byte(hcl), &out)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, out.EmptyVal)
|
|
require.NotNil(t, out.TimestampVal)
|
|
require.Equal(t, out.TimestampVal.AsTime(), time.Date(2023, 2, 27, 12, 34, 56, 789000000, time.UTC))
|
|
require.NotNil(t, out.DurationVal)
|
|
require.Equal(t, out.DurationVal.AsDuration(), time.Second*12)
|
|
}
|
|
|
|
func TestInvalidTimestamp(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("too slow for testing.Short")
|
|
}
|
|
|
|
t.Parallel()
|
|
|
|
cases := map[string]struct {
|
|
hcl string
|
|
expectXDS bool
|
|
}{
|
|
"invalid": {
|
|
hcl: `
|
|
timestamp_val = "Sat Jun 12 2023 14:59:57 GMT+0200"
|
|
`,
|
|
},
|
|
"range error": {
|
|
hcl: `
|
|
timestamp_val = "2023-02-27T25:34:56.789Z"
|
|
`,
|
|
},
|
|
}
|
|
|
|
for name, tc := range cases {
|
|
tc := tc
|
|
var out testproto.NonDynamicWellKnown
|
|
t.Run(name, func(t *testing.T) {
|
|
|
|
err := Unmarshal([]byte(tc.hcl), &out)
|
|
require.Error(t, err)
|
|
require.Nil(t, out.TimestampVal)
|
|
require.ErrorContains(t, err, "error parsing timestamp")
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestInvalidDuration(t *testing.T) {
|
|
hcl := `
|
|
duration_val = "abc"
|
|
`
|
|
var out testproto.NonDynamicWellKnown
|
|
|
|
err := Unmarshal([]byte(hcl), &out)
|
|
require.ErrorContains(t, err, "error parsing string duration:")
|
|
require.Nil(t, out.DurationVal)
|
|
}
|
|
|
|
func TestOneOf(t *testing.T) {
|
|
hcl1 := `
|
|
int32_val = 3
|
|
`
|
|
|
|
hcl2 := `
|
|
primitives {
|
|
int32_val = 3
|
|
}
|
|
`
|
|
|
|
hcl3 := `
|
|
int32_val = 3
|
|
primitives {
|
|
int32_val = 4
|
|
}
|
|
`
|
|
|
|
var out testproto.OneOf
|
|
|
|
err := Unmarshal([]byte(hcl1), &out)
|
|
require.NoError(t, err)
|
|
require.Equal(t, out.GetInt32Val(), int32(3))
|
|
|
|
err = Unmarshal([]byte(hcl2), &out)
|
|
require.NoError(t, err)
|
|
primitives := out.GetPrimitives()
|
|
require.NotNil(t, primitives)
|
|
require.Equal(t, primitives.Int32Val, int32(3))
|
|
|
|
err = Unmarshal([]byte(hcl3), &out)
|
|
require.Error(t, err)
|
|
}
|
|
|
|
func TestAny(t *testing.T) {
|
|
hcl := `
|
|
any_val {
|
|
type_url = "hashicorp.consul.internal.protohcl.testproto.Primitives"
|
|
uint32_val = 42
|
|
}
|
|
|
|
any_list = [
|
|
{
|
|
type_url = "hashicorp.consul.internal.protohcl.testproto.Primitives"
|
|
uint32_val = 123
|
|
},
|
|
{
|
|
type_url = "hashicorp.consul.internal.protohcl.testproto.Wrappers"
|
|
uint32_val = 321
|
|
}
|
|
]
|
|
`
|
|
var out testproto.DynamicWellKnown
|
|
|
|
err := Unmarshal([]byte(hcl), &out)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, out.AnyVal)
|
|
require.Equal(t, out.AnyVal.TypeUrl, "hashicorp.consul.internal.protohcl.testproto.Primitives")
|
|
|
|
raw, err := anypb.UnmarshalNew(out.AnyVal, proto.UnmarshalOptions{})
|
|
require.NoError(t, err)
|
|
require.NotNil(t, raw)
|
|
|
|
primitives, ok := raw.(*testproto.Primitives)
|
|
require.True(t, ok)
|
|
require.Equal(t, primitives.Uint32Val, uint32(42))
|
|
}
|
|
|
|
func TestAnyTypeDynamicWellKnown(t *testing.T) {
|
|
hcl := `
|
|
any_val {
|
|
type_url = "hashicorp.consul.internal.protohcl.testproto.DynamicWellKnown"
|
|
any_val {
|
|
type_url = "hashicorp.consul.internal.protohcl.testproto.Primitives"
|
|
uint32_val = 42
|
|
}
|
|
}
|
|
`
|
|
var out testproto.DynamicWellKnown
|
|
|
|
err := Unmarshal([]byte(hcl), &out)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, out.AnyVal)
|
|
require.Equal(t, out.AnyVal.TypeUrl, "hashicorp.consul.internal.protohcl.testproto.DynamicWellKnown")
|
|
|
|
raw, err := anypb.UnmarshalNew(out.AnyVal, proto.UnmarshalOptions{})
|
|
require.NoError(t, err)
|
|
require.NotNil(t, raw)
|
|
|
|
anyVal, ok := raw.(*testproto.DynamicWellKnown)
|
|
require.True(t, ok)
|
|
|
|
res, err := anypb.UnmarshalNew(anyVal.AnyVal, proto.UnmarshalOptions{})
|
|
require.NoError(t, err)
|
|
require.NotNil(t, res)
|
|
|
|
primitives, ok := res.(*testproto.Primitives)
|
|
require.True(t, ok)
|
|
require.Equal(t, primitives.Uint32Val, uint32(42))
|
|
}
|
|
|
|
func TestAnyTypeNestedAndCollections(t *testing.T) {
|
|
hcl := `
|
|
any_val {
|
|
type_url = "hashicorp.consul.internal.protohcl.testproto.NestedAndCollections"
|
|
primitives {
|
|
uint32_val = 42
|
|
}
|
|
}
|
|
`
|
|
var out testproto.DynamicWellKnown
|
|
|
|
err := Unmarshal([]byte(hcl), &out)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, out.AnyVal)
|
|
require.Equal(t, out.AnyVal.TypeUrl, "hashicorp.consul.internal.protohcl.testproto.NestedAndCollections")
|
|
|
|
raw, err := anypb.UnmarshalNew(out.AnyVal, proto.UnmarshalOptions{})
|
|
require.NoError(t, err)
|
|
require.NotNil(t, raw)
|
|
|
|
nestedCollections, ok := raw.(*testproto.NestedAndCollections)
|
|
require.True(t, ok)
|
|
require.NotNil(t, nestedCollections.Primitives)
|
|
require.Equal(t, nestedCollections.Primitives.Uint32Val, uint32(42))
|
|
}
|
|
|
|
func TestAnyTypeErrors(t *testing.T) {
|
|
type testCase struct {
|
|
description string
|
|
hcl string
|
|
error string
|
|
}
|
|
testCases := []testCase{
|
|
{
|
|
description: "type_url is expected",
|
|
hcl: `
|
|
any_val {
|
|
uint32_val = 42
|
|
}
|
|
`,
|
|
error: "type_url field is required to decode Any",
|
|
},
|
|
{
|
|
description: "type_url is unknown",
|
|
hcl: `
|
|
any_val {
|
|
type_url = "hashicorp.consul.internal.protohcl.testproto.Integer"
|
|
uint32_val = 42
|
|
}
|
|
`,
|
|
error: "error looking up type information for hashicorp.consul.internal.protohcl.testproto.Integer",
|
|
},
|
|
{
|
|
description: "unknown field",
|
|
hcl: `
|
|
any_val {
|
|
type_url = "hashicorp.consul.internal.protohcl.testproto.Primitives"
|
|
int_val = 42
|
|
}
|
|
`,
|
|
error: "Unsupported argument; An argument named \"int_val\" is not expected here",
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
tc := tc
|
|
t.Run(tc.description, func(t *testing.T) {
|
|
t.Parallel()
|
|
var out testproto.DynamicWellKnown
|
|
|
|
err := Unmarshal([]byte(tc.hcl), &out)
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), tc.error)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestStruct(t *testing.T) {
|
|
hcl := `
|
|
struct_val = {
|
|
"null"= null
|
|
"bool"= true
|
|
"foo" = "bar"
|
|
"baz" = 1.234
|
|
"nested" = {
|
|
"foo" = 12,
|
|
"bar" = "something"
|
|
}
|
|
}
|
|
`
|
|
|
|
var out testproto.DynamicWellKnown
|
|
|
|
err := Unmarshal([]byte(hcl), &out)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, out.StructVal)
|
|
|
|
valMap := out.StructVal.AsMap()
|
|
jsonVal, err := json.Marshal(valMap)
|
|
require.NoError(t, err)
|
|
|
|
expected := `{
|
|
"null": null,
|
|
"bool": true,
|
|
"foo": "bar",
|
|
"baz": 1.234,
|
|
"nested": {
|
|
"foo": 12,
|
|
"bar": "something"
|
|
}
|
|
}
|
|
`
|
|
require.JSONEq(t, expected, string(jsonVal))
|
|
}
|
|
|
|
func TestStructList(t *testing.T) {
|
|
hcl := `
|
|
struct_val = {
|
|
"list_int" = [
|
|
1,
|
|
2,
|
|
3,
|
|
]
|
|
"list_string": [
|
|
"abc",
|
|
"def"
|
|
]
|
|
"list_bool": [
|
|
true,
|
|
false
|
|
]
|
|
"list_maps" = [
|
|
{
|
|
"arrr" = "matey"
|
|
},
|
|
{
|
|
"hoist" = "the colors"
|
|
}
|
|
]
|
|
"list_list" = [
|
|
[
|
|
"hello",
|
|
"world",
|
|
null
|
|
]
|
|
]
|
|
}
|
|
`
|
|
|
|
var out testproto.DynamicWellKnown
|
|
|
|
err := Unmarshal([]byte(hcl), &out)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, out.StructVal)
|
|
|
|
valMap := out.StructVal.AsMap()
|
|
jsonVal, err := json.Marshal(valMap)
|
|
require.NoError(t, err)
|
|
|
|
expected := `{
|
|
"list_int": [
|
|
1,
|
|
2,
|
|
3
|
|
],
|
|
"list_string": [
|
|
"abc",
|
|
"def"
|
|
],
|
|
"list_bool": [
|
|
true,
|
|
false
|
|
],
|
|
"list_maps": [
|
|
{
|
|
"arrr": "matey"
|
|
},
|
|
{
|
|
"hoist": "the colors"
|
|
}
|
|
],
|
|
"list_list": [
|
|
[
|
|
"hello",
|
|
"world",
|
|
null
|
|
]
|
|
]
|
|
}
|
|
`
|
|
require.JSONEq(t, expected, string(jsonVal))
|
|
}
|
|
|
|
func TestFunctionExecution(t *testing.T) {
|
|
hcl := `
|
|
primitives = primitive_defaults()
|
|
`
|
|
|
|
var out testproto.NestedAndCollections
|
|
|
|
var (
|
|
testType = cty.Capsule("type", reflect.TypeOf(testproto.Primitives{}))
|
|
|
|
test = function.New(&function.Spec{
|
|
Params: []function.Parameter{},
|
|
Type: function.StaticReturnType(testType),
|
|
Impl: func(args []cty.Value, _ cty.Type) (cty.Value, error) {
|
|
t := &testproto.Primitives{
|
|
StringVal: "test",
|
|
Int32Val: 10,
|
|
BoolVal: false,
|
|
}
|
|
return cty.CapsuleVal(testType, t), nil
|
|
},
|
|
})
|
|
)
|
|
|
|
err := UnmarshalOptions{
|
|
Functions: map[string]function.Function{"primitive_defaults": test},
|
|
}.Unmarshal([]byte(hcl), &out)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.NotNil(t, out.Primitives)
|
|
require.Equal(t, out.Primitives.StringVal, "test")
|
|
require.Equal(t, out.Primitives.Int32Val, int32(10))
|
|
require.Equal(t, out.Primitives.BoolVal, false)
|
|
}
|
|
|
|
func TestSkipFields(t *testing.T) {
|
|
|
|
u := UnmarshalOptions{}
|
|
|
|
hcl := `
|
|
any_val {
|
|
type_url = "hashicorp.consul.internal.protohcl.testproto.Primitives"
|
|
uint32_val = 10
|
|
}`
|
|
|
|
file, diags := hclparse.NewParser().ParseHCL([]byte(hcl), "")
|
|
|
|
require.False(t, diags.HasErrors())
|
|
|
|
decoder := u.bodyDecoder(file.Body)
|
|
|
|
decoder = decoder.SkipFields("type_url")
|
|
|
|
decoder = decoder.SkipFields("type_url", "uint32_val")
|
|
|
|
expected := map[string]struct{}{
|
|
"type_url": {},
|
|
"uint32_val": {},
|
|
}
|
|
|
|
require.Contains(t, fmt.Sprintf("%v", decoder), fmt.Sprintf("%v", expected))
|
|
}
|