299 lines
13 KiB
Go
299 lines
13 KiB
Go
package objectstorage
|
|
|
|
// run the test for example in ~/dapr-dev/components-contrib
|
|
// go test -v github.com/dapr/components-contrib/state/oci/objectstorage.
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/dapr/components-contrib/state"
|
|
"github.com/dapr/kit/logger"
|
|
)
|
|
|
|
const (
|
|
configFilePathEnvKey = "DAPR_TEST_OCI_CONFIG_FILE_PATH"
|
|
bucketNameEnvKey = "DAPR_TEST_OCI_BUCKET_NAME"
|
|
compartmentOCIDEnvKey = "DAPR_TEST_OCI_COMPARTMENT_OCID"
|
|
configFileProfileEnvKey = "DAPR_TEST_OCI_CONFIG_PROFILE"
|
|
)
|
|
|
|
func getConfigFilePathString() string {
|
|
return os.Getenv(configFilePathEnvKey)
|
|
}
|
|
|
|
func getConfigProfile() string {
|
|
return os.Getenv(configFileProfileEnvKey)
|
|
}
|
|
|
|
func getCompartmentOCID() string {
|
|
return os.Getenv(compartmentOCIDEnvKey)
|
|
}
|
|
|
|
func getBucketName() string {
|
|
return os.Getenv(bucketNameEnvKey)
|
|
}
|
|
|
|
func TestOCIObjectStorageIntegration(t *testing.T) {
|
|
ociObjectStorageConfiguration := map[string]string{
|
|
"configFileAuthentication": "true",
|
|
}
|
|
configFilePath := getConfigFilePathString()
|
|
if configFilePath == "" {
|
|
// first run export DAPR_TEST_OCI_CONFIG_FILE_PATH="/home/app/.oci/config".
|
|
t.Skipf("OCI ObjectStorage state integration tests skipped. To enable define the configuration file path string using environment variable '%s' (example 'export %s=\"/home/app/.oci/config\")", configFilePathEnvKey, configFilePathEnvKey)
|
|
}
|
|
ociObjectStorageConfiguration["configFilePath"] = configFilePath
|
|
ociObjectStorageConfiguration["configFileProfile"] = getConfigProfile()
|
|
|
|
compartmentOCID := getCompartmentOCID()
|
|
if compartmentOCID == "" {
|
|
t.Skipf("OCI ObjectStorage state integration tests skipped. To enable define the compartment OCID string using environment variable '%s' (example 'export %s=\"ocid1.compartment.oc1..aaaaaaaacsssekayq4d34nl5h3eqs5e6ak3j5s4jhlws7rr5pxmt3zrq\")", compartmentOCIDEnvKey, compartmentOCIDEnvKey)
|
|
}
|
|
ociObjectStorageConfiguration["compartmentOCID"] = compartmentOCID
|
|
|
|
bucketName := getBucketName()
|
|
if bucketName == "" {
|
|
bucketName = "DAPR_TEST_BUCKET"
|
|
}
|
|
ociObjectStorageConfiguration["bucketName"] = bucketName
|
|
|
|
t.Run("Test Get", func(t *testing.T) {
|
|
t.Parallel()
|
|
testGet(t, ociObjectStorageConfiguration)
|
|
})
|
|
t.Run("Test Set", func(t *testing.T) {
|
|
t.Parallel()
|
|
testSet(t, ociObjectStorageConfiguration)
|
|
})
|
|
t.Run("Test Delete", func(t *testing.T) {
|
|
t.Parallel()
|
|
testDelete(t, ociObjectStorageConfiguration)
|
|
})
|
|
t.Run("Test Ping", func(t *testing.T) {
|
|
t.Parallel()
|
|
testPing(t, ociObjectStorageConfiguration)
|
|
})
|
|
}
|
|
|
|
func testGet(t *testing.T, ociProperties map[string]string) {
|
|
statestore := NewOCIObjectStorageStore(logger.NewLogger("logger"))
|
|
meta := state.Metadata{}
|
|
meta.Properties = ociProperties
|
|
|
|
t.Run("Get an non-existing key", func(t *testing.T) {
|
|
err := statestore.Init(context.Background(), meta)
|
|
require.NoError(t, err)
|
|
getResponse, err := statestore.Get(context.Background(), &state.GetRequest{Key: "xyzq"})
|
|
assert.Equal(t, &state.GetResponse{}, getResponse, "Response must be empty")
|
|
require.NoError(t, err, "Non-existing key must not be treated as error")
|
|
})
|
|
t.Run("Get an existing key", func(t *testing.T) {
|
|
err := statestore.Init(context.Background(), meta)
|
|
require.NoError(t, err)
|
|
err = statestore.Set(context.Background(), &state.SetRequest{Key: "test-key", Value: []byte("test-value")})
|
|
require.NoError(t, err)
|
|
getResponse, err := statestore.Get(context.Background(), &state.GetRequest{Key: "test-key"})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "test-value", string(getResponse.Data), "Value retrieved should be equal to value set")
|
|
assert.NotNil(t, *getResponse.ETag, "ETag should be set")
|
|
})
|
|
t.Run("Get an existing composed key", func(t *testing.T) {
|
|
err := statestore.Init(context.Background(), meta)
|
|
require.NoError(t, err)
|
|
err = statestore.Set(context.Background(), &state.SetRequest{Key: "test-app||test-key", Value: []byte("test-value")})
|
|
require.NoError(t, err)
|
|
getResponse, err := statestore.Get(context.Background(), &state.GetRequest{Key: "test-app||test-key"})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "test-value", string(getResponse.Data), "Value retrieved should be equal to value set")
|
|
})
|
|
t.Run("Get an unexpired state element with TTL set", func(t *testing.T) {
|
|
testKey := "unexpired-ttl-test-key"
|
|
err := statestore.Init(context.Background(), meta)
|
|
require.NoError(t, err)
|
|
err = statestore.Set(context.Background(), &state.SetRequest{Key: testKey, Value: []byte("test-value"), Metadata: (map[string]string{
|
|
"ttlInSeconds": "100",
|
|
})})
|
|
require.NoError(t, err)
|
|
getResponse, err := statestore.Get(context.Background(), &state.GetRequest{Key: testKey})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "test-value", string(getResponse.Data), "Value retrieved should be equal to value set despite TTL setting")
|
|
})
|
|
t.Run("Get a state element with TTL set to -1 (not expire)", func(t *testing.T) {
|
|
testKey := "never-expiring-ttl-test-key"
|
|
err := statestore.Init(context.Background(), meta)
|
|
require.NoError(t, err)
|
|
err = statestore.Set(context.Background(), &state.SetRequest{Key: testKey, Value: []byte("test-value"), Metadata: (map[string]string{
|
|
"ttlInSeconds": "-1",
|
|
})})
|
|
require.NoError(t, err)
|
|
getResponse, err := statestore.Get(context.Background(), &state.GetRequest{Key: testKey})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "test-value", string(getResponse.Data), "Value retrieved should be equal (TTL setting of -1 means never expire)")
|
|
})
|
|
t.Run("Get an expired (TTL in the past) state element", func(t *testing.T) {
|
|
err := statestore.Init(context.Background(), meta)
|
|
require.NoError(t, err)
|
|
err = statestore.Set(context.Background(), &state.SetRequest{Key: "ttl-test-key", Value: []byte("test-value"), Metadata: (map[string]string{
|
|
"ttlInSeconds": "1",
|
|
})})
|
|
require.NoError(t, err)
|
|
time.Sleep(time.Second * 2)
|
|
getResponse, err := statestore.Get(context.Background(), &state.GetRequest{Key: "ttl-test-key"})
|
|
assert.Equal(t, &state.GetResponse{}, getResponse, "Response must be empty")
|
|
require.NoError(t, err, "Expired element must not be treated as error")
|
|
})
|
|
}
|
|
|
|
func testSet(t *testing.T, ociProperties map[string]string) {
|
|
meta := state.Metadata{}
|
|
meta.Properties = ociProperties
|
|
statestore := NewOCIObjectStorageStore(logger.NewLogger("logger"))
|
|
t.Run("Set without a key", func(t *testing.T) {
|
|
err := statestore.Init(context.Background(), meta)
|
|
require.NoError(t, err)
|
|
err = statestore.Set(context.Background(), &state.SetRequest{Value: []byte("test-value")})
|
|
assert.Equal(t, err, fmt.Errorf("key for value to set was missing from request"), "Lacking Key results in error")
|
|
})
|
|
t.Run("Regular Set Operation", func(t *testing.T) {
|
|
testKey := "local-test-key"
|
|
err := statestore.Init(context.Background(), meta)
|
|
require.NoError(t, err)
|
|
|
|
err = statestore.Set(context.Background(), &state.SetRequest{Key: testKey, Value: []byte("test-value")})
|
|
require.NoError(t, err, "Setting a value with a proper key should be errorfree")
|
|
getResponse, err := statestore.Get(context.Background(), &state.GetRequest{Key: testKey})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "test-value", string(getResponse.Data), "Value retrieved should be equal to value set")
|
|
assert.NotNil(t, *getResponse.ETag, "ETag should be set")
|
|
})
|
|
t.Run("Regular Set Operation with composite key", func(t *testing.T) {
|
|
testKey := "test-app||other-test-key"
|
|
err := statestore.Init(context.Background(), meta)
|
|
require.NoError(t, err)
|
|
|
|
err = statestore.Set(context.Background(), &state.SetRequest{Key: testKey, Value: []byte("test-value")})
|
|
require.NoError(t, err, "Setting a value with a proper composite key should be errorfree")
|
|
getResponse, err := statestore.Get(context.Background(), &state.GetRequest{Key: testKey})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "test-value", string(getResponse.Data), "Value retrieved should be equal to value set")
|
|
assert.NotNil(t, *getResponse.ETag, "ETag should be set")
|
|
})
|
|
t.Run("Regular Set Operation with TTL", func(t *testing.T) {
|
|
testKey := "test-key-with-ttl"
|
|
err := statestore.Set(context.Background(), &state.SetRequest{Key: testKey, Value: []byte("test-value"), Metadata: (map[string]string{
|
|
"ttlInSeconds": "500",
|
|
})})
|
|
require.NoError(t, err, "Setting a value with a proper key and a correct TTL value should be errorfree")
|
|
err = statestore.Set(context.Background(), &state.SetRequest{Key: testKey, Value: []byte("test-value"), Metadata: (map[string]string{
|
|
"ttlInSeconds": "XXX",
|
|
})})
|
|
require.Error(t, err, "Setting a value with a proper key and a incorrect TTL value should be produce an error")
|
|
})
|
|
|
|
t.Run("Testing Set & Concurrency (ETags)", func(t *testing.T) {
|
|
testKey := "etag-test-key"
|
|
err := statestore.Init(context.Background(), meta)
|
|
require.NoError(t, err)
|
|
|
|
err = statestore.Set(context.Background(), &state.SetRequest{Key: testKey, Value: []byte("test-value")})
|
|
require.NoError(t, err, "Setting a value with a proper key should be errorfree")
|
|
getResponse, _ := statestore.Get(context.Background(), &state.GetRequest{Key: testKey})
|
|
etag := getResponse.ETag
|
|
err = statestore.Set(context.Background(), &state.SetRequest{Key: testKey, Value: []byte("overwritten-value"), ETag: etag, Options: state.SetStateOption{
|
|
Concurrency: state.FirstWrite,
|
|
}})
|
|
require.NoError(t, err, "Updating value with proper etag should go fine")
|
|
|
|
err = statestore.Set(context.Background(), &state.SetRequest{Key: testKey, Value: []byte("more-overwritten-value"), ETag: etag, Options: state.SetStateOption{
|
|
Concurrency: state.FirstWrite,
|
|
}})
|
|
require.Error(t, err, "Updating value with the old etag should be refused")
|
|
|
|
// retrieve the latest etag - assigned by the previous set operation.
|
|
getResponse, _ = statestore.Get(context.Background(), &state.GetRequest{Key: testKey})
|
|
assert.NotNil(t, *getResponse.ETag, "ETag should be set")
|
|
etag = getResponse.ETag
|
|
err = statestore.Set(context.Background(), &state.SetRequest{Key: testKey, Value: []byte("more-overwritten-value"), ETag: etag, Options: state.SetStateOption{
|
|
Concurrency: state.FirstWrite,
|
|
}})
|
|
require.NoError(t, err, "Updating value with the latest etag should be accepted")
|
|
})
|
|
}
|
|
|
|
func testDelete(t *testing.T, ociProperties map[string]string) {
|
|
m := state.Metadata{}
|
|
m.Properties = ociProperties
|
|
s := NewOCIObjectStorageStore(logger.NewLogger("logger"))
|
|
t.Run("Delete without a key", func(t *testing.T) {
|
|
err := s.Init(context.Background(), m)
|
|
require.NoError(t, err)
|
|
err = s.Delete(context.Background(), &state.DeleteRequest{})
|
|
assert.Equal(t, err, fmt.Errorf("key for value to delete was missing from request"), "Lacking Key results in error")
|
|
})
|
|
t.Run("Regular Delete Operation", func(t *testing.T) {
|
|
testKey := "test-key"
|
|
err := s.Init(context.Background(), m)
|
|
require.NoError(t, err)
|
|
|
|
err = s.Set(context.Background(), &state.SetRequest{Key: testKey, Value: []byte("test-value")})
|
|
require.NoError(t, err, "Setting a value with a proper key should be errorfree")
|
|
err = s.Delete(context.Background(), &state.DeleteRequest{Key: testKey})
|
|
require.NoError(t, err, "Deleting an existing value with a proper key should be errorfree")
|
|
})
|
|
t.Run("Regular Delete Operation for composite key", func(t *testing.T) {
|
|
testKey := "test-app||some-test-key"
|
|
err := s.Init(context.Background(), m)
|
|
require.NoError(t, err)
|
|
|
|
err = s.Set(context.Background(), &state.SetRequest{Key: testKey, Value: []byte("test-value")})
|
|
require.NoError(t, err, "Setting a value with a proper composite key should be errorfree")
|
|
err = s.Delete(context.Background(), &state.DeleteRequest{Key: testKey})
|
|
require.NoError(t, err, "Deleting an existing value with a proper composite key should be errorfree")
|
|
})
|
|
t.Run("Delete with an unknown key", func(t *testing.T) {
|
|
err := s.Delete(context.Background(), &state.DeleteRequest{Key: "unknownKey"})
|
|
assert.Contains(t, err.Error(), "404", "Unknown Key results in error: http status code 404, object not found")
|
|
})
|
|
|
|
t.Run("Testing Delete & Concurrency (ETags)", func(t *testing.T) {
|
|
testKey := "etag-test-delete-key"
|
|
err := s.Init(context.Background(), m)
|
|
require.NoError(t, err)
|
|
// create document.
|
|
err = s.Set(context.Background(), &state.SetRequest{Key: testKey, Value: []byte("test-value")})
|
|
require.NoError(t, err, "Setting a value with a proper key should be errorfree")
|
|
getResponse, _ := s.Get(context.Background(), &state.GetRequest{Key: testKey})
|
|
etag := getResponse.ETag
|
|
|
|
incorrectETag := "someRandomETag"
|
|
err = s.Delete(context.Background(), &state.DeleteRequest{Key: testKey, ETag: &incorrectETag, Options: state.DeleteStateOption{
|
|
Concurrency: state.FirstWrite,
|
|
}})
|
|
require.Error(t, err, "Deleting value with an incorrect etag should be prevented")
|
|
|
|
err = s.Delete(context.Background(), &state.DeleteRequest{Key: testKey, ETag: etag, Options: state.DeleteStateOption{
|
|
Concurrency: state.FirstWrite,
|
|
}})
|
|
require.NoError(t, err, "Deleting value with proper etag should go fine")
|
|
})
|
|
}
|
|
|
|
func testPing(t *testing.T, ociProperties map[string]string) {
|
|
m := state.Metadata{}
|
|
m.Properties = ociProperties
|
|
s := NewOCIObjectStorageStore(logger.NewLogger("logger")).(*StateStore)
|
|
t.Run("Ping", func(t *testing.T) {
|
|
err := s.Init(context.Background(), m)
|
|
require.NoError(t, err)
|
|
err = s.Ping(context.Background())
|
|
require.NoError(t, err, "Ping should be successful")
|
|
})
|
|
}
|