This adds checks for unknown attributes through the entire plugin lifecycle for all attributes, secrets, and persisted state. It also fixes the E2E test which seems to have drifted.
424 lines
9.4 KiB
Go
424 lines
9.4 KiB
Go
package plugin
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
"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: "unknown catalog attribute fields provided: bar, foo",
|
|
},
|
|
{
|
|
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)
|
|
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: "unknown catalog secret fields provided: bar, foo",
|
|
},
|
|
{
|
|
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)
|
|
return
|
|
}
|
|
|
|
require.NoError(err)
|
|
require.Equal(tc.expected, actual)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetSetAttributes(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
in *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: "unknown set attribute fields provided: bar, foo",
|
|
},
|
|
{
|
|
name: "good",
|
|
in: mustStruct(map[string]interface{}{
|
|
constDescribeInstancesFilters: []interface{}{
|
|
"foo=bar",
|
|
"zip=zap",
|
|
},
|
|
}),
|
|
expected: &SetAttributes{
|
|
Filters: []string{"foo=bar", "zip=zap"},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
tc := tc
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
require := require.New(t)
|
|
actual, err := getSetAttributes(tc.in)
|
|
if tc.expectedErrContains != "" {
|
|
require.Error(err)
|
|
require.Contains(err.Error(), tc.expectedErrContains)
|
|
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.EqualError(err, tc.expectedErr)
|
|
return
|
|
}
|
|
|
|
require.NoError(err)
|
|
})
|
|
}
|
|
}
|