2026-05-11 22:39:20 -07:00
|
|
|
// Copyright IBM Corp. 2025, 2026
|
2024-02-28 15:14:17 +00:00
|
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
|
|
|
|
|
|
package storage
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"testing"
|
2024-03-22 15:20:11 +00:00
|
|
|
"time"
|
2024-02-28 15:14:17 +00:00
|
|
|
|
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
|
"google.golang.org/protobuf/types/known/structpb"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func TestGetStorageAttributes(t *testing.T) {
|
|
|
|
|
tests := []struct {
|
|
|
|
|
name string
|
|
|
|
|
inAttributes *structpb.Struct
|
|
|
|
|
expStorageAttributes *StorageAttributes
|
|
|
|
|
expErrMsg string
|
|
|
|
|
}{
|
|
|
|
|
{
|
|
|
|
|
name: "nilAttributes",
|
|
|
|
|
inAttributes: nil,
|
2024-03-03 22:28:36 -05:00
|
|
|
expErrMsg: "empty attributes input",
|
2024-02-28 15:14:17 +00:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "nilAttributesFields",
|
|
|
|
|
inAttributes: &structpb.Struct{Fields: nil},
|
2024-03-03 22:28:36 -05:00
|
|
|
expErrMsg: "empty attributes input",
|
2024-02-28 15:14:17 +00:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "emptyAttributesFields",
|
|
|
|
|
inAttributes: &structpb.Struct{Fields: map[string]*structpb.Value{}},
|
2024-03-03 22:28:36 -05:00
|
|
|
expErrMsg: "empty attributes input",
|
2024-02-28 15:14:17 +00:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "emptyEndpointUrlAttribute",
|
|
|
|
|
inAttributes: &structpb.Struct{
|
|
|
|
|
Fields: map[string]*structpb.Value{
|
|
|
|
|
ConstEndpointUrl: structpb.NewStringValue(""),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
expErrMsg: "attributes.endpoint_url: value \"endpoint_url\" cannot be empty",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "endpointUrlAttributeBadType",
|
|
|
|
|
inAttributes: &structpb.Struct{
|
|
|
|
|
Fields: map[string]*structpb.Value{
|
|
|
|
|
ConstEndpointUrl: structpb.NewBoolValue(true),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
expErrMsg: "attributes.endpoint_url: unexpected type for value \"endpoint_url\": want string, got bool",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "badEndpointUrlAttributeFormat",
|
|
|
|
|
inAttributes: &structpb.Struct{
|
|
|
|
|
Fields: map[string]*structpb.Value{
|
|
|
|
|
ConstEndpointUrl: structpb.NewStringValue("bad://foo.bar"),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
expErrMsg: "attributes.endpoint_url.format: unknown protocol, should be http:// or https://",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "regionAttributeBadType",
|
|
|
|
|
inAttributes: &structpb.Struct{
|
|
|
|
|
Fields: map[string]*structpb.Value{
|
|
|
|
|
ConstEndpointUrl: structpb.NewStringValue("http://foo.bar"),
|
|
|
|
|
ConstRegion: structpb.NewBoolValue(true),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
expErrMsg: "attributes.region: unexpected type for value \"region\": want string, got bool",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "unknownAttribute",
|
|
|
|
|
inAttributes: &structpb.Struct{
|
|
|
|
|
Fields: map[string]*structpb.Value{
|
|
|
|
|
ConstEndpointUrl: structpb.NewStringValue("http://foo.bar"),
|
|
|
|
|
ConstRegion: structpb.NewStringValue("us-east-1"),
|
|
|
|
|
"foo": structpb.NewStringValue("bar"),
|
|
|
|
|
},
|
|
|
|
|
},
|
2024-03-03 22:28:36 -05:00
|
|
|
expErrMsg: "attributes.foo: unrecognized field",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "noEndpointUrlAttribute",
|
|
|
|
|
inAttributes: &structpb.Struct{
|
2024-02-28 15:14:17 +00:00
|
|
|
Fields: map[string]*structpb.Value{
|
2024-03-03 22:28:36 -05:00
|
|
|
"foo": structpb.NewStringValue("bar"),
|
2024-02-28 15:14:17 +00:00
|
|
|
},
|
|
|
|
|
},
|
2024-03-03 22:28:36 -05:00
|
|
|
expErrMsg: "attributes.endpoint_url: missing required value \"endpoint_url\"",
|
2024-02-28 15:14:17 +00:00
|
|
|
},
|
2024-03-22 15:20:11 +00:00
|
|
|
{
|
|
|
|
|
name: "disableCredRotationBadType",
|
|
|
|
|
inAttributes: &structpb.Struct{
|
|
|
|
|
Fields: map[string]*structpb.Value{
|
|
|
|
|
ConstEndpointUrl: structpb.NewStringValue("http://bar"),
|
|
|
|
|
ConstDisableCredentialRotation: structpb.NewStringValue("notbool"),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
expErrMsg: "attributes.disable_credential_rotation: unexpected type for value \"disable_credential_rotation\": want bool, got string",
|
|
|
|
|
},
|
2024-02-28 15:14:17 +00:00
|
|
|
{
|
2024-03-03 22:28:36 -05:00
|
|
|
name: "successWithNoSSL",
|
2024-02-28 15:14:17 +00:00
|
|
|
inAttributes: &structpb.Struct{
|
|
|
|
|
Fields: map[string]*structpb.Value{
|
2024-03-22 15:20:11 +00:00
|
|
|
ConstEndpointUrl: structpb.NewStringValue("http://foo.bar"),
|
|
|
|
|
ConstRegion: structpb.NewStringValue("us-east-1"),
|
|
|
|
|
ConstDisableCredentialRotation: structpb.NewBoolValue(true),
|
2024-02-28 15:14:17 +00:00
|
|
|
},
|
|
|
|
|
},
|
2024-03-03 22:28:36 -05:00
|
|
|
expStorageAttributes: &StorageAttributes{
|
2024-03-22 15:20:11 +00:00
|
|
|
EndpointUrl: "foo.bar",
|
|
|
|
|
Region: "us-east-1",
|
|
|
|
|
UseSSL: false,
|
|
|
|
|
DisableCredentialRotation: true,
|
2024-03-03 22:28:36 -05:00
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "successWithSSL",
|
|
|
|
|
inAttributes: &structpb.Struct{
|
|
|
|
|
Fields: map[string]*structpb.Value{
|
|
|
|
|
ConstEndpointUrl: structpb.NewStringValue("https://foo.bar"),
|
|
|
|
|
ConstRegion: structpb.NewStringValue("us-east-1"),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
expStorageAttributes: &StorageAttributes{
|
|
|
|
|
EndpointUrl: "foo.bar",
|
|
|
|
|
Region: "us-east-1",
|
|
|
|
|
UseSSL: true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, tt := range tests {
|
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
|
sa, err := getStorageAttributes(tt.inAttributes)
|
|
|
|
|
if tt.expErrMsg != "" {
|
|
|
|
|
require.ErrorContains(t, err, tt.expErrMsg)
|
|
|
|
|
require.Nil(t, sa)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
require.NotNil(t, sa)
|
|
|
|
|
require.EqualValues(t, tt.expStorageAttributes, sa)
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestGetStorageSecrets(t *testing.T) {
|
|
|
|
|
tests := []struct {
|
|
|
|
|
name string
|
|
|
|
|
inSecrets *structpb.Struct
|
|
|
|
|
expStorageSecrets *StorageSecrets
|
|
|
|
|
expErrMsg string
|
|
|
|
|
}{
|
|
|
|
|
{
|
|
|
|
|
name: "nilSecrets",
|
|
|
|
|
inSecrets: nil,
|
|
|
|
|
expErrMsg: "empty secrets input",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "nilSecretsFields",
|
|
|
|
|
inSecrets: &structpb.Struct{Fields: nil},
|
|
|
|
|
expErrMsg: "empty secrets input",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "emptySecretsFields",
|
|
|
|
|
inSecrets: &structpb.Struct{Fields: map[string]*structpb.Value{}},
|
|
|
|
|
expErrMsg: "empty secrets input",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "noAccessKeyIdSecret",
|
2024-02-28 15:14:17 +00:00
|
|
|
inSecrets: &structpb.Struct{
|
|
|
|
|
Fields: map[string]*structpb.Value{
|
|
|
|
|
ConstSecretAccessKey: structpb.NewStringValue("bar"),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
expErrMsg: "secrets.access_key_id: missing required value \"access_key_id\"",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "emptyAccessKeyIdSecret",
|
|
|
|
|
inSecrets: &structpb.Struct{
|
|
|
|
|
Fields: map[string]*structpb.Value{
|
|
|
|
|
ConstAccessKeyId: structpb.NewStringValue(""),
|
|
|
|
|
ConstSecretAccessKey: structpb.NewStringValue("bar"),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
expErrMsg: "secrets.access_key_id: value \"access_key_id\" cannot be empty",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "accessKeyIdSecretBadType",
|
|
|
|
|
inSecrets: &structpb.Struct{
|
|
|
|
|
Fields: map[string]*structpb.Value{
|
|
|
|
|
ConstAccessKeyId: structpb.NewBoolValue(true),
|
|
|
|
|
ConstSecretAccessKey: structpb.NewStringValue("bar"),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
expErrMsg: "secrets.access_key_id: unexpected type for value \"access_key_id\": want string, got bool",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "noSecretAccessKeySecret",
|
|
|
|
|
inSecrets: &structpb.Struct{
|
|
|
|
|
Fields: map[string]*structpb.Value{
|
|
|
|
|
ConstAccessKeyId: structpb.NewStringValue("bar"),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
expErrMsg: "secrets.secret_access_key: missing required value \"secret_access_key\"",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "emptySecretAccessKeySecret",
|
|
|
|
|
inSecrets: &structpb.Struct{
|
|
|
|
|
Fields: map[string]*structpb.Value{
|
|
|
|
|
ConstAccessKeyId: structpb.NewStringValue("bar"),
|
|
|
|
|
ConstSecretAccessKey: structpb.NewStringValue(""),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
expErrMsg: "secrets.secret_access_key: value \"secret_access_key\" cannot be empty",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "secretAccessKeySecretBadType",
|
|
|
|
|
inSecrets: &structpb.Struct{
|
|
|
|
|
Fields: map[string]*structpb.Value{
|
|
|
|
|
ConstAccessKeyId: structpb.NewStringValue("bar"),
|
|
|
|
|
ConstSecretAccessKey: structpb.NewBoolValue(true),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
expErrMsg: "secrets.secret_access_key: unexpected type for value \"secret_access_key\": want string, got bool",
|
|
|
|
|
},
|
2024-03-22 15:20:11 +00:00
|
|
|
{
|
|
|
|
|
name: "lastRotatedBadType",
|
|
|
|
|
inSecrets: &structpb.Struct{
|
|
|
|
|
Fields: map[string]*structpb.Value{
|
|
|
|
|
ConstAccessKeyId: structpb.NewStringValue("foo"),
|
|
|
|
|
ConstSecretAccessKey: structpb.NewStringValue("bar"),
|
|
|
|
|
ConstLastRotatedTime: structpb.NewStringValue("nottime"),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
expErrMsg: "secrets.creds_last_rotated_time: could not parse time in value \"creds_last_rotated_time\"",
|
|
|
|
|
},
|
2024-02-28 15:14:17 +00:00
|
|
|
{
|
|
|
|
|
name: "unknownSecret",
|
|
|
|
|
inSecrets: &structpb.Struct{
|
|
|
|
|
Fields: map[string]*structpb.Value{
|
|
|
|
|
ConstAccessKeyId: structpb.NewStringValue("bar"),
|
|
|
|
|
ConstSecretAccessKey: structpb.NewStringValue("bar"),
|
|
|
|
|
"foo": structpb.NewStringValue("bar"),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
expErrMsg: "secrets.foo: unrecognized field",
|
|
|
|
|
},
|
|
|
|
|
{
|
2024-03-03 22:28:36 -05:00
|
|
|
name: "success",
|
2024-02-28 15:14:17 +00:00
|
|
|
inSecrets: &structpb.Struct{
|
|
|
|
|
Fields: map[string]*structpb.Value{
|
|
|
|
|
ConstAccessKeyId: structpb.NewStringValue("foo"),
|
|
|
|
|
ConstSecretAccessKey: structpb.NewStringValue("bar"),
|
|
|
|
|
},
|
|
|
|
|
},
|
2024-03-03 22:28:36 -05:00
|
|
|
expStorageSecrets: &StorageSecrets{
|
2024-02-28 15:14:17 +00:00
|
|
|
AccessKeyId: "foo",
|
|
|
|
|
SecretAccessKey: "bar",
|
|
|
|
|
},
|
|
|
|
|
},
|
2024-03-22 15:20:11 +00:00
|
|
|
{
|
|
|
|
|
name: "successWithLastRotatedTime",
|
|
|
|
|
inSecrets: &structpb.Struct{
|
|
|
|
|
Fields: map[string]*structpb.Value{
|
|
|
|
|
ConstAccessKeyId: structpb.NewStringValue("foo"),
|
|
|
|
|
ConstSecretAccessKey: structpb.NewStringValue("bar"),
|
|
|
|
|
ConstLastRotatedTime: structpb.NewStringValue(time.Date(2024, 01, 02, 03, 04, 05, 06, time.UTC).Format(time.RFC3339Nano)),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
expStorageSecrets: &StorageSecrets{
|
|
|
|
|
AccessKeyId: "foo",
|
|
|
|
|
SecretAccessKey: "bar",
|
|
|
|
|
LastRotatedTime: time.Date(2024, 01, 02, 03, 04, 05, 06, time.UTC),
|
|
|
|
|
},
|
|
|
|
|
},
|
2024-02-28 15:14:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, tt := range tests {
|
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
2024-03-03 22:28:36 -05:00
|
|
|
sa, err := getStorageSecrets(tt.inSecrets)
|
2024-02-28 15:14:17 +00:00
|
|
|
if tt.expErrMsg != "" {
|
|
|
|
|
require.ErrorContains(t, err, tt.expErrMsg)
|
|
|
|
|
require.Nil(t, sa)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
require.NotNil(t, sa)
|
2024-03-03 22:28:36 -05:00
|
|
|
require.EqualValues(t, tt.expStorageSecrets, sa)
|
2024-02-28 15:14:17 +00:00
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-03 22:28:36 -05:00
|
|
|
func TestStorageSecretsAsMap(t *testing.T) {
|
2024-02-28 15:14:17 +00:00
|
|
|
tests := []struct {
|
|
|
|
|
name string
|
2024-03-03 22:28:36 -05:00
|
|
|
in *StorageSecrets
|
2024-02-28 15:14:17 +00:00
|
|
|
expMap map[string]any
|
|
|
|
|
}{
|
|
|
|
|
{
|
|
|
|
|
name: "completeObject",
|
2024-03-03 22:28:36 -05:00
|
|
|
in: &StorageSecrets{
|
2024-02-28 15:14:17 +00:00
|
|
|
AccessKeyId: "access_key_id_value",
|
|
|
|
|
SecretAccessKey: "secret_access_key_value",
|
2024-03-22 15:20:11 +00:00
|
|
|
LastRotatedTime: time.Date(2024, 01, 02, 03, 04, 05, 06, time.UTC),
|
2024-02-28 15:14:17 +00:00
|
|
|
},
|
|
|
|
|
expMap: map[string]any{
|
2024-03-22 15:20:11 +00:00
|
|
|
"access_key_id": "access_key_id_value",
|
|
|
|
|
"secret_access_key": "secret_access_key_value",
|
|
|
|
|
"creds_last_rotated_time": "2024-01-02T03:04:05.000000006Z",
|
2024-02-28 15:14:17 +00:00
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, tt := range tests {
|
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
2024-03-03 22:28:36 -05:00
|
|
|
m := tt.in.AsMap()
|
2024-02-28 15:14:17 +00:00
|
|
|
require.EqualValues(t, tt.expMap, m)
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-03-22 15:20:11 +00:00
|
|
|
|
|
|
|
|
func TestTestStorageSecretsClone(t *testing.T) {
|
|
|
|
|
now := time.Now()
|
|
|
|
|
in := &StorageSecrets{
|
|
|
|
|
AccessKeyId: "foo",
|
|
|
|
|
SecretAccessKey: "bar",
|
|
|
|
|
LastRotatedTime: now,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
secCl := in.Clone()
|
|
|
|
|
require.Equal(t, in.AccessKeyId, secCl.AccessKeyId)
|
|
|
|
|
require.Equal(t, in.AccessKeyId, secCl.AccessKeyId)
|
|
|
|
|
require.Equal(t, in.LastRotatedTime, secCl.LastRotatedTime)
|
|
|
|
|
require.NotSame(t, in, secCl)
|
|
|
|
|
}
|