457 lines
11 KiB
Go
457 lines
11 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package plugin
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
"github.com/hashicorp/boundary/sdk/pbs/plugin"
|
|
"github.com/stretchr/testify/require"
|
|
"google.golang.org/grpc/codes"
|
|
"google.golang.org/grpc/status"
|
|
"google.golang.org/protobuf/testing/protocmp"
|
|
"google.golang.org/protobuf/types/known/structpb"
|
|
)
|
|
|
|
func TestGetCatalogAttributes(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
in *structpb.Struct
|
|
expected *CatalogAttributes
|
|
expectedErrContains string
|
|
}{
|
|
{
|
|
name: "missing region",
|
|
in: mustStruct(map[string]interface{}{}),
|
|
expectedErrContains: "missing required value \"region\"",
|
|
},
|
|
{
|
|
name: "bad value for disable_credential_rotation",
|
|
in: mustStruct(map[string]interface{}{
|
|
constRegion: "us-west-2",
|
|
constDisableCredentialRotation: "sure",
|
|
}),
|
|
expectedErrContains: "unexpected type for value \"disable_credential_rotation\": want bool, got string",
|
|
},
|
|
{
|
|
name: "unknown fields",
|
|
in: mustStruct(map[string]interface{}{
|
|
constRegion: "us-west-2",
|
|
"foo": true,
|
|
"bar": true,
|
|
}),
|
|
expectedErrContains: "attributes.bar: unrecognized field, attributes.foo: unrecognized field",
|
|
},
|
|
{
|
|
name: "default",
|
|
in: mustStruct(map[string]interface{}{
|
|
constRegion: "us-west-2",
|
|
}),
|
|
expected: &CatalogAttributes{
|
|
Region: "us-west-2",
|
|
DisableCredentialRotation: false,
|
|
},
|
|
},
|
|
{
|
|
name: "with disable_credential_rotation",
|
|
in: mustStruct(map[string]interface{}{
|
|
constRegion: "us-west-2",
|
|
constDisableCredentialRotation: true,
|
|
}),
|
|
expected: &CatalogAttributes{
|
|
Region: "us-west-2",
|
|
DisableCredentialRotation: true,
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
tc := tc
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
require := require.New(t)
|
|
actual, err := getCatalogAttributes(tc.in)
|
|
if tc.expectedErrContains != "" {
|
|
require.Error(err)
|
|
require.Contains(err.Error(), tc.expectedErrContains)
|
|
require.Equal(status.Code(err), codes.InvalidArgument)
|
|
return
|
|
}
|
|
|
|
require.NoError(err)
|
|
require.Equal(tc.expected, actual)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetCatalogSecrets(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
in *structpb.Struct
|
|
expected *CatalogSecrets
|
|
expectedErrContains string
|
|
}{
|
|
{
|
|
name: "missing access_key_id",
|
|
in: mustStruct(map[string]interface{}{}),
|
|
expectedErrContains: "missing required value \"access_key_id\"",
|
|
},
|
|
{
|
|
name: "missing secret_access_key",
|
|
in: mustStruct(map[string]interface{}{
|
|
constAccessKeyId: "foobar",
|
|
}),
|
|
expectedErrContains: "missing required value \"secret_access_key\"",
|
|
},
|
|
{
|
|
name: "unknown fields",
|
|
in: mustStruct(map[string]interface{}{
|
|
constAccessKeyId: "foobar",
|
|
constSecretAccessKey: "bazqux",
|
|
"foo": true,
|
|
"bar": true,
|
|
}),
|
|
expectedErrContains: "secrets.bar: unrecognized field, secrets.foo: unrecognized field",
|
|
},
|
|
{
|
|
name: "good",
|
|
in: mustStruct(map[string]interface{}{
|
|
constAccessKeyId: "foobar",
|
|
constSecretAccessKey: "bazqux",
|
|
}),
|
|
expected: &CatalogSecrets{
|
|
AccessKeyId: "foobar",
|
|
SecretAccessKey: "bazqux",
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
tc := tc
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
require := require.New(t)
|
|
actual, err := getCatalogSecrets(tc.in)
|
|
if tc.expectedErrContains != "" {
|
|
require.Error(err)
|
|
require.Contains(err.Error(), tc.expectedErrContains)
|
|
require.Equal(status.Code(err), codes.InvalidArgument)
|
|
return
|
|
}
|
|
|
|
require.NoError(err)
|
|
require.Equal(tc.expected, actual)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetSetAttributes(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
in *structpb.Struct
|
|
normalized *structpb.Struct
|
|
expected *SetAttributes
|
|
expectedErrContains string
|
|
}{
|
|
{
|
|
name: "missing",
|
|
in: mustStruct(map[string]interface{}{}),
|
|
expected: &SetAttributes{},
|
|
},
|
|
{
|
|
name: "non-string-slice value",
|
|
in: mustStruct(map[string]interface{}{
|
|
constDescribeInstancesFilters: "zip=foo,bar",
|
|
}),
|
|
expected: &SetAttributes{
|
|
Filters: []string{"zip=foo,bar"},
|
|
},
|
|
},
|
|
{
|
|
name: "bad filter element value",
|
|
in: mustStruct(map[string]interface{}{
|
|
constDescribeInstancesFilters: []interface{}{1},
|
|
}),
|
|
expectedErrContains: "expected type 'string', got unconvertible type 'float64'",
|
|
},
|
|
{
|
|
name: "unknown fields",
|
|
in: mustStruct(map[string]interface{}{
|
|
"foo": true,
|
|
"bar": true,
|
|
}),
|
|
expectedErrContains: "attributes.bar: unrecognized field, attributes.foo: unrecognized field",
|
|
},
|
|
{
|
|
name: "good",
|
|
in: mustStruct(map[string]interface{}{
|
|
constDescribeInstancesFilters: []interface{}{
|
|
"foo=bar",
|
|
"zip=zap",
|
|
},
|
|
}),
|
|
expected: &SetAttributes{
|
|
Filters: []string{"foo=bar", "zip=zap"},
|
|
},
|
|
},
|
|
{
|
|
name: "good with filter transform",
|
|
in: mustStruct(map[string]interface{}{
|
|
constDescribeInstancesFilters: "foo=bar",
|
|
}),
|
|
normalized: mustStruct(map[string]interface{}{
|
|
constDescribeInstancesFilters: []interface{}{"foo=bar"},
|
|
}),
|
|
expected: &SetAttributes{
|
|
Filters: []string{"foo=bar"},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
tc := tc
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
require := require.New(t)
|
|
p := new(AwsPlugin)
|
|
normalizedOut, err := p.NormalizeSetData(context.Background(), &plugin.NormalizeSetDataRequest{Attributes: tc.in})
|
|
require.NoError(err)
|
|
if tc.normalized != nil {
|
|
require.Empty(cmp.Diff(tc.normalized, normalizedOut.Attributes, protocmp.Transform()))
|
|
}
|
|
tc.in = normalizedOut.Attributes
|
|
actual, err := getSetAttributes(tc.in)
|
|
if tc.expectedErrContains != "" {
|
|
require.Error(err)
|
|
require.Contains(err.Error(), tc.expectedErrContains)
|
|
require.Equal(status.Code(err), codes.InvalidArgument)
|
|
return
|
|
}
|
|
|
|
require.NoError(err)
|
|
require.Equal(tc.expected, actual)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetStringValue(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
in *structpb.Struct
|
|
key string
|
|
required bool
|
|
expected string
|
|
expectedErr string
|
|
}{
|
|
{
|
|
name: "required missing",
|
|
in: mustStruct(map[string]interface{}{}),
|
|
key: "foo",
|
|
required: true,
|
|
expectedErr: "missing required value \"foo\"",
|
|
},
|
|
{
|
|
name: "optional missing",
|
|
in: mustStruct(map[string]interface{}{}),
|
|
key: "foo",
|
|
expected: "",
|
|
},
|
|
{
|
|
name: "non-string value",
|
|
in: mustStruct(map[string]interface{}{
|
|
"foo": 1,
|
|
}),
|
|
key: "foo",
|
|
expectedErr: "unexpected type for value \"foo\": want string, got float64",
|
|
},
|
|
{
|
|
name: "required empty",
|
|
in: mustStruct(map[string]interface{}{
|
|
"foo": "",
|
|
}),
|
|
key: "foo",
|
|
required: true,
|
|
expectedErr: "value \"foo\" cannot be empty",
|
|
},
|
|
{
|
|
name: "good",
|
|
in: mustStruct(map[string]interface{}{
|
|
"foo": "bar",
|
|
}),
|
|
key: "foo",
|
|
expected: "bar",
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
tc := tc
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
require := require.New(t)
|
|
actual, err := getStringValue(tc.in, tc.key, tc.required)
|
|
if tc.expectedErr != "" {
|
|
require.EqualError(err, tc.expectedErr)
|
|
return
|
|
}
|
|
|
|
require.NoError(err)
|
|
require.Equal(tc.expected, actual)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetBoolValue(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
in *structpb.Struct
|
|
key string
|
|
required bool
|
|
expected bool
|
|
expectedErr string
|
|
}{
|
|
{
|
|
name: "required missing",
|
|
in: mustStruct(map[string]interface{}{}),
|
|
key: "foo",
|
|
required: true,
|
|
expectedErr: "missing required value \"foo\"",
|
|
},
|
|
{
|
|
name: "optional missing",
|
|
in: mustStruct(map[string]interface{}{}),
|
|
key: "foo",
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "non-bool value",
|
|
in: mustStruct(map[string]interface{}{
|
|
"foo": "bar",
|
|
}),
|
|
key: "foo",
|
|
expectedErr: "unexpected type for value \"foo\": want bool, got string",
|
|
},
|
|
{
|
|
name: "good",
|
|
in: mustStruct(map[string]interface{}{
|
|
"foo": true,
|
|
}),
|
|
key: "foo",
|
|
expected: true,
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
tc := tc
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
require := require.New(t)
|
|
actual, err := getBoolValue(tc.in, tc.key, tc.required)
|
|
if tc.expectedErr != "" {
|
|
require.EqualError(err, tc.expectedErr)
|
|
return
|
|
}
|
|
|
|
require.NoError(err)
|
|
require.Equal(tc.expected, actual)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetTimeValue(t *testing.T) {
|
|
staticTime := time.Now()
|
|
|
|
cases := []struct {
|
|
name string
|
|
in *structpb.Struct
|
|
key string
|
|
expected time.Time
|
|
expectedErr string
|
|
}{
|
|
{
|
|
name: "missing",
|
|
in: mustStruct(map[string]interface{}{}),
|
|
key: "foo",
|
|
expected: time.Time{},
|
|
},
|
|
{
|
|
name: "non-time value",
|
|
in: mustStruct(map[string]interface{}{
|
|
"foo": 1,
|
|
}),
|
|
key: "foo",
|
|
expectedErr: "unexpected type for value \"foo\": want string, got float64",
|
|
},
|
|
{
|
|
name: "bad parse",
|
|
in: mustStruct(map[string]interface{}{
|
|
"foo": "bar",
|
|
}),
|
|
key: "foo",
|
|
expectedErr: "could not parse time in value \"foo\": parsing time \"bar\" as \"2006-01-02T15:04:05.999999999Z07:00\": cannot parse \"bar\" as \"2006\"",
|
|
},
|
|
{
|
|
name: "good",
|
|
in: mustStruct(map[string]interface{}{
|
|
"foo": staticTime.Format(time.RFC3339Nano),
|
|
}),
|
|
key: "foo",
|
|
expected: func() time.Time {
|
|
u, err := time.Parse(time.RFC3339Nano, staticTime.Format(time.RFC3339Nano))
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
return u
|
|
}(),
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
tc := tc
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
require := require.New(t)
|
|
actual, err := getTimeValue(tc.in, tc.key)
|
|
if tc.expectedErr != "" {
|
|
require.EqualError(err, tc.expectedErr)
|
|
return
|
|
}
|
|
|
|
require.NoError(err)
|
|
require.Equal(tc.expected, actual)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestValidateRegion(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
in string
|
|
expectedErr string
|
|
}{
|
|
{
|
|
name: "invalid region",
|
|
in: "foobar",
|
|
expectedErr: "not a valid region: foobar",
|
|
},
|
|
{
|
|
name: "good",
|
|
in: "us-west-2",
|
|
expectedErr: "",
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
tc := tc
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
require := require.New(t)
|
|
err := validateRegion(tc.in)
|
|
if tc.expectedErr != "" {
|
|
require.Contains(err.Error(), tc.expectedErr)
|
|
require.Equal(status.Code(err), codes.InvalidArgument)
|
|
return
|
|
}
|
|
|
|
require.NoError(err)
|
|
})
|
|
}
|
|
}
|