Merge branch 'content-type' of github.com:jigargandhi/components-contrib into content-type
This commit is contained in:
commit
63f87f0641
|
|
@ -0,0 +1,11 @@
|
|||
version: '2'
|
||||
services:
|
||||
db:
|
||||
image: postgres
|
||||
restart: always
|
||||
ports:
|
||||
- "5432:5432"
|
||||
environment:
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: example
|
||||
POSTGRES_DB: dapr_test
|
||||
|
|
@ -67,6 +67,7 @@ jobs:
|
|||
- state.mongodb
|
||||
- state.redis
|
||||
- state.sqlserver
|
||||
- state.postgresql
|
||||
- state.mysql
|
||||
EOF
|
||||
)
|
||||
|
|
@ -274,6 +275,12 @@ jobs:
|
|||
uses: helm/kind-action@v1.0.0
|
||||
if: contains(matrix.component, 'kubernetes')
|
||||
|
||||
- name: Start postgresql
|
||||
run: |
|
||||
docker-compose -f ./.github/infrastructure/docker-compose-postgresql.yml -p postgresql up -d
|
||||
if: contains(matrix.component, 'postgresql')
|
||||
|
||||
|
||||
- name: Setup KinD test data
|
||||
if: contains(matrix.component, 'kubernetes')
|
||||
run: |
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ package mysql
|
|||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
|
@ -241,11 +242,12 @@ func (m *MySQL) ensureStateTable(stateTableName string) error {
|
|||
// eTag is a UUID stored as a 36 characters string. It needs to be passed
|
||||
// in on inserts and updates and is used for Optimistic Concurrency
|
||||
createTable := fmt.Sprintf(`CREATE TABLE %s (
|
||||
id varchar(255) NOT NULL PRIMARY KEY,
|
||||
value text NOT NULL,
|
||||
id VARCHAR(255) NOT NULL PRIMARY KEY,
|
||||
value JSON NOT NULL,
|
||||
isbinary BOOLEAN NOT NULL,
|
||||
insertDate TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updateDate TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
eTag varchar(36) NOT NULL
|
||||
eTag VARCHAR(36) NOT NULL
|
||||
);`, stateTableName)
|
||||
|
||||
_, err = m.db.Exec(createTable)
|
||||
|
|
@ -344,10 +346,11 @@ func (m *MySQL) Get(req *state.GetRequest) (*state.GetResponse, error) {
|
|||
}
|
||||
|
||||
var eTag, value string
|
||||
var isBinary bool
|
||||
|
||||
err := m.db.QueryRow(fmt.Sprintf(
|
||||
`SELECT value, eTag FROM %s WHERE id = ?`,
|
||||
m.tableName), req.Key).Scan(&value, &eTag)
|
||||
`SELECT value, eTag, isbinary FROM %s WHERE id = ?`,
|
||||
m.tableName), req.Key).Scan(&value, &eTag, &isBinary)
|
||||
if err != nil {
|
||||
// If no rows exist, return an empty response, otherwise return an error.
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
|
|
@ -357,13 +360,30 @@ func (m *MySQL) Get(req *state.GetRequest) (*state.GetResponse, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
response := &state.GetResponse{
|
||||
ETag: ptr.String(eTag),
|
||||
Metadata: req.Metadata,
|
||||
Data: []byte(value),
|
||||
if isBinary {
|
||||
var s string
|
||||
var data []byte
|
||||
|
||||
if err = json.Unmarshal([]byte(value), &s); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if data, err = base64.StdEncoding.DecodeString(s); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &state.GetResponse{
|
||||
Data: data,
|
||||
ETag: ptr.String(eTag),
|
||||
Metadata: req.Metadata,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return response, nil
|
||||
return &state.GetResponse{
|
||||
Data: []byte(value),
|
||||
ETag: ptr.String(eTag),
|
||||
Metadata: req.Metadata,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Set adds/updates an entity on store
|
||||
|
|
@ -386,8 +406,18 @@ func (m *MySQL) setValue(req *state.SetRequest) error {
|
|||
return fmt.Errorf("missing key in set operation")
|
||||
}
|
||||
|
||||
if v, ok := req.Value.(string); ok && v == "" {
|
||||
return fmt.Errorf("empty string is not allowed in set operation")
|
||||
}
|
||||
|
||||
v := req.Value
|
||||
byteArray, isBinary := req.Value.([]uint8)
|
||||
if isBinary {
|
||||
v = base64.StdEncoding.EncodeToString(byteArray)
|
||||
}
|
||||
|
||||
// Convert to json string
|
||||
bt, _ := utils.Marshal(req.Value, json.Marshal)
|
||||
bt, _ := utils.Marshal(v, json.Marshal)
|
||||
value := string(bt)
|
||||
|
||||
var result sql.Result
|
||||
|
|
@ -399,15 +429,15 @@ func (m *MySQL) setValue(req *state.SetRequest) error {
|
|||
if req.ETag == nil || *req.ETag == "" {
|
||||
// If this is a duplicate MySQL returns that two rows affected
|
||||
result, err = m.db.Exec(fmt.Sprintf(
|
||||
`INSERT INTO %s (value, id, eTag)
|
||||
VALUES (?, ?, ?) on duplicate key update value=?, eTag=?;`,
|
||||
m.tableName), value, req.Key, eTag, value, eTag)
|
||||
`INSERT INTO %s (value, id, eTag, isbinary)
|
||||
VALUES (?, ?, ?, ?) on duplicate key update value=?, eTag=?, isbinary=?;`,
|
||||
m.tableName), value, req.Key, eTag, isBinary, value, eTag, isBinary)
|
||||
} else {
|
||||
// When an eTag is provided do an update - not insert
|
||||
result, err = m.db.Exec(fmt.Sprintf(
|
||||
`UPDATE %s SET value = ?, eTag = ?
|
||||
`UPDATE %s SET value = ?, eTag = ?, isbinary = ?
|
||||
WHERE id = ? AND eTag = ?;`,
|
||||
m.tableName), value, eTag, req.Key, *req.ETag)
|
||||
m.tableName), value, eTag, isBinary, req.Key, *req.ETag)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ package mysql
|
|||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
|
@ -14,6 +16,7 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/dapr/components-contrib/state"
|
||||
"github.com/dapr/components-contrib/state/utils"
|
||||
"github.com/dapr/kit/logger"
|
||||
)
|
||||
|
||||
|
|
@ -477,19 +480,40 @@ func TestGetSucceeds(t *testing.T) {
|
|||
m, _ := mockDatabase(t)
|
||||
defer m.mySQL.Close()
|
||||
|
||||
rows := sqlmock.NewRows([]string{"value", "eTag"}).AddRow("{}", "946af56e")
|
||||
m.mock1.ExpectQuery("SELECT value, eTag FROM state WHERE id = ?").WillReturnRows(rows)
|
||||
t.Run("has json type", func(t *testing.T) {
|
||||
rows := sqlmock.NewRows([]string{"value", "eTag", "isbinary"}).AddRow("{}", "946af56e", false)
|
||||
m.mock1.ExpectQuery("SELECT value, eTag, isbinary FROM state WHERE id = ?").WillReturnRows(rows)
|
||||
|
||||
request := &state.GetRequest{
|
||||
Key: "UnitTest",
|
||||
}
|
||||
request := &state.GetRequest{
|
||||
Key: "UnitTest",
|
||||
}
|
||||
|
||||
// Act
|
||||
response, err := m.mySQL.Get(request)
|
||||
// Act
|
||||
response, err := m.mySQL.Get(request)
|
||||
|
||||
// Assert
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, response)
|
||||
// Assert
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, response)
|
||||
assert.Equal(t, "{}", string(response.Data))
|
||||
})
|
||||
|
||||
t.Run("has binary type", func(t *testing.T) {
|
||||
value, _ := utils.Marshal(base64.StdEncoding.EncodeToString([]byte("abcdefg")), json.Marshal)
|
||||
rows := sqlmock.NewRows([]string{"value", "eTag", "isbinary"}).AddRow(value, "946af56e", true)
|
||||
m.mock1.ExpectQuery("SELECT value, eTag, isbinary FROM state WHERE id = ?").WillReturnRows(rows)
|
||||
|
||||
request := &state.GetRequest{
|
||||
Key: "UnitTest",
|
||||
}
|
||||
|
||||
// Act
|
||||
response, err := m.mySQL.Get(request)
|
||||
|
||||
// Assert
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, response)
|
||||
assert.Equal(t, "abcdefg", string(response.Data))
|
||||
})
|
||||
}
|
||||
|
||||
// Verifies that the correct query is executed to test if the table
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ package postgresql
|
|||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
|
|
@ -98,8 +98,18 @@ func (p *postgresDBAccess) setValue(req *state.SetRequest) error {
|
|||
return fmt.Errorf("missing key in set operation")
|
||||
}
|
||||
|
||||
if v, ok := req.Value.(string); ok && v == "" {
|
||||
return fmt.Errorf("empty string is not allowed in set operation")
|
||||
}
|
||||
|
||||
v := req.Value
|
||||
byteArray, isBinary := req.Value.([]uint8)
|
||||
if isBinary {
|
||||
v = base64.StdEncoding.EncodeToString(byteArray)
|
||||
}
|
||||
|
||||
// Convert to json string
|
||||
bt, _ := utils.Marshal(req.Value, json.Marshal)
|
||||
bt, _ := utils.Marshal(v, json.Marshal)
|
||||
value := string(bt)
|
||||
|
||||
var result sql.Result
|
||||
|
|
@ -108,9 +118,9 @@ func (p *postgresDBAccess) setValue(req *state.SetRequest) error {
|
|||
// Other parameters use sql.DB parameter substitution.
|
||||
if req.ETag == nil {
|
||||
result, err = p.db.Exec(fmt.Sprintf(
|
||||
`INSERT INTO %s (key, value) VALUES ($1, $2)
|
||||
ON CONFLICT (key) DO UPDATE SET value = $2, updatedate = NOW();`,
|
||||
tableName), req.Key, value)
|
||||
`INSERT INTO %s (key, value, isbinary) VALUES ($1, $2, $3)
|
||||
ON CONFLICT (key) DO UPDATE SET value = $2, isbinary = $3, updatedate = NOW();`,
|
||||
tableName), req.Key, value, isBinary)
|
||||
} else {
|
||||
// Convert req.ETag to integer for postgres compatibility
|
||||
var etag int
|
||||
|
|
@ -121,12 +131,29 @@ func (p *postgresDBAccess) setValue(req *state.SetRequest) error {
|
|||
|
||||
// When an etag is provided do an update - no insert
|
||||
result, err = p.db.Exec(fmt.Sprintf(
|
||||
`UPDATE %s SET value = $1, updatedate = NOW()
|
||||
WHERE key = $2 AND xmin = $3;`,
|
||||
tableName), value, req.Key, etag)
|
||||
`UPDATE %s SET value = $1, isbinary = $2, updatedate = NOW()
|
||||
WHERE key = $3 AND xmin = $4;`,
|
||||
tableName), value, isBinary, req.Key, etag)
|
||||
}
|
||||
|
||||
return p.returnSingleDBResult(result, err)
|
||||
if err != nil {
|
||||
if req.ETag != nil && *req.ETag != "" {
|
||||
return state.NewETagError(state.ETagMismatch, err)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
rows, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if rows != 1 {
|
||||
return fmt.Errorf("no item was updated")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get returns data from the database. If data does not exist for the key an empty state.GetResponse will be returned.
|
||||
|
|
@ -137,8 +164,9 @@ func (p *postgresDBAccess) Get(req *state.GetRequest) (*state.GetResponse, error
|
|||
}
|
||||
|
||||
var value string
|
||||
var isBinary bool
|
||||
var etag int
|
||||
err := p.db.QueryRow(fmt.Sprintf("SELECT value, xmin as etag FROM %s WHERE key = $1", tableName), req.Key).Scan(&value, &etag)
|
||||
err := p.db.QueryRow(fmt.Sprintf("SELECT value, isbinary, xmin as etag FROM %s WHERE key = $1", tableName), req.Key).Scan(&value, &isBinary, &etag)
|
||||
if err != nil {
|
||||
// If no rows exist, return an empty response, otherwise return the error.
|
||||
if err == sql.ErrNoRows {
|
||||
|
|
@ -148,13 +176,30 @@ func (p *postgresDBAccess) Get(req *state.GetRequest) (*state.GetResponse, error
|
|||
return nil, err
|
||||
}
|
||||
|
||||
response := &state.GetResponse{
|
||||
if isBinary {
|
||||
var s string
|
||||
var data []byte
|
||||
|
||||
if err = json.Unmarshal([]byte(value), &s); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if data, err = base64.StdEncoding.DecodeString(s); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &state.GetResponse{
|
||||
Data: data,
|
||||
ETag: ptr.String(strconv.Itoa(etag)),
|
||||
Metadata: req.Metadata,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &state.GetResponse{
|
||||
Data: []byte(value),
|
||||
ETag: ptr.String(strconv.Itoa(etag)),
|
||||
Metadata: req.Metadata,
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Delete removes an item from the state store.
|
||||
|
|
@ -184,7 +229,20 @@ func (p *postgresDBAccess) deleteValue(req *state.DeleteRequest) error {
|
|||
result, err = p.db.Exec("DELETE FROM state WHERE key = $1 and xmin = $2", req.Key, etag)
|
||||
}
|
||||
|
||||
return p.returnSingleDBResult(result, err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rows, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if rows != 1 && req.ETag != nil && *req.ETag != "" {
|
||||
return state.NewETagError(state.ETagMismatch, nil)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *postgresDBAccess) ExecuteMulti(sets []state.SetRequest, deletes []state.DeleteRequest) error {
|
||||
|
|
@ -223,39 +281,6 @@ func (p *postgresDBAccess) ExecuteMulti(sets []state.SetRequest, deletes []state
|
|||
return err
|
||||
}
|
||||
|
||||
// Verifies that the sql.Result affected only one row and no errors exist.
|
||||
func (p *postgresDBAccess) returnSingleDBResult(result sql.Result, err error) error {
|
||||
if err != nil {
|
||||
p.logger.Debug(err)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
rowsAffected, resultErr := result.RowsAffected()
|
||||
|
||||
if resultErr != nil {
|
||||
p.logger.Error(resultErr)
|
||||
|
||||
return resultErr
|
||||
}
|
||||
|
||||
if rowsAffected == 0 {
|
||||
noRowsErr := state.NewETagError(state.ETagMismatch, err)
|
||||
p.logger.Error(noRowsErr)
|
||||
|
||||
return noRowsErr
|
||||
}
|
||||
|
||||
if rowsAffected > 1 {
|
||||
tooManyRowsErr := errors.New("database operation failed: more than one row affected, expected one")
|
||||
p.logger.Error(tooManyRowsErr)
|
||||
|
||||
return tooManyRowsErr
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close implements io.Close.
|
||||
func (p *postgresDBAccess) Close() error {
|
||||
if p.db != nil {
|
||||
|
|
@ -275,7 +300,8 @@ func (p *postgresDBAccess) ensureStateTable(stateTableName string) error {
|
|||
p.logger.Info("Creating PostgreSQL state table")
|
||||
createTable := fmt.Sprintf(`CREATE TABLE %s (
|
||||
key text NOT NULL PRIMARY KEY,
|
||||
value json NOT NULL,
|
||||
value jsonb NOT NULL,
|
||||
isbinary boolean NOT NULL,
|
||||
insertdate TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||
updatedate TIMESTAMP WITH TIME ZONE NULL);`, stateTableName)
|
||||
_, err = p.db.Exec(createTable)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,8 @@
|
|||
// ------------------------------------------------------------
|
||||
// Copyright (c) Microsoft Corporation and Dapr Contributors.
|
||||
// Licensed under the MIT License.
|
||||
// ------------------------------------------------------------
|
||||
|
||||
package query
|
||||
|
||||
import (
|
||||
|
|
|
|||
|
|
@ -1,3 +1,8 @@
|
|||
// ------------------------------------------------------------
|
||||
// Copyright (c) Microsoft Corporation and Dapr Contributors.
|
||||
// Licensed under the MIT License.
|
||||
// ------------------------------------------------------------
|
||||
|
||||
package query
|
||||
|
||||
import (
|
||||
|
|
|
|||
|
|
@ -1,3 +1,8 @@
|
|||
// ------------------------------------------------------------
|
||||
// Copyright (c) Microsoft Corporation and Dapr Contributors.
|
||||
// Licensed under the MIT License.
|
||||
// ------------------------------------------------------------
|
||||
|
||||
package query
|
||||
|
||||
import (
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
apiVersion: dapr.io/v1alpha1
|
||||
kind: Component
|
||||
metadata:
|
||||
name: statestore
|
||||
spec:
|
||||
type: state.postgresql
|
||||
metadata:
|
||||
- name: connectionString
|
||||
value: "host=localhost user=postgres password=example port=5432 connect_timeout=10 database=dapr_test"
|
||||
- name: actorStateStore
|
||||
value: "true"
|
||||
|
|
@ -14,6 +14,9 @@ components:
|
|||
- component: sqlserver
|
||||
allOperations: false
|
||||
operations: [ "set", "get", "delete", "bulkset", "bulkdelete", "transaction", "etag", "first-write" ]
|
||||
- component: postgresql
|
||||
allOperations: false
|
||||
operations: [ "set", "get", "delete", "bulkset", "bulkdelete", "transaction", "etag" ]
|
||||
- component: mysql
|
||||
allOperations: false
|
||||
operations: [ "set", "get", "delete", "bulkset", "bulkdelete", "transaction", "etag" ]
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ import (
|
|||
s_azuretablestorage "github.com/dapr/components-contrib/state/azure/tablestorage"
|
||||
s_mongodb "github.com/dapr/components-contrib/state/mongodb"
|
||||
s_mysql "github.com/dapr/components-contrib/state/mysql"
|
||||
s_postgresql "github.com/dapr/components-contrib/state/postgresql"
|
||||
s_redis "github.com/dapr/components-contrib/state/redis"
|
||||
s_sqlserver "github.com/dapr/components-contrib/state/sqlserver"
|
||||
conf_bindings "github.com/dapr/components-contrib/tests/conformance/bindings"
|
||||
|
|
@ -402,6 +403,8 @@ func loadStateStore(tc TestComponent) state.Store {
|
|||
fallthrough
|
||||
case "sqlserver":
|
||||
store = s_sqlserver.NewSQLServerStateStore(testLogger)
|
||||
case "postgresql":
|
||||
store = s_postgresql.NewPostgreSQLStateStore(testLogger)
|
||||
case "mysql":
|
||||
store = s_mysql.NewMySQLStateStore(testLogger)
|
||||
case "azure.tablestorage":
|
||||
|
|
|
|||
|
|
@ -24,13 +24,12 @@ type ValueType struct {
|
|||
}
|
||||
|
||||
type scenario struct {
|
||||
key string
|
||||
value interface{}
|
||||
expectedReadResponse []byte
|
||||
toBeDeleted bool
|
||||
bulkOnly bool
|
||||
transactionOnly bool
|
||||
transactionGroup int
|
||||
key string
|
||||
value interface{}
|
||||
toBeDeleted bool
|
||||
bulkOnly bool
|
||||
transactionOnly bool
|
||||
transactionGroup int
|
||||
}
|
||||
|
||||
type queryScenario struct {
|
||||
|
|
@ -63,135 +62,114 @@ func ConformanceTests(t *testing.T, props map[string]string, statestore state.St
|
|||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
key: fmt.Sprintf("%s-int", key),
|
||||
value: 123,
|
||||
expectedReadResponse: []byte("123"),
|
||||
key: fmt.Sprintf("%s-int", key),
|
||||
value: 123,
|
||||
},
|
||||
{
|
||||
key: fmt.Sprintf("%s-bool", key),
|
||||
value: true,
|
||||
expectedReadResponse: []byte("true"),
|
||||
key: fmt.Sprintf("%s-bool", key),
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
key: fmt.Sprintf("%s-bytes", key),
|
||||
value: []byte{0x1},
|
||||
expectedReadResponse: []byte{0x1},
|
||||
key: fmt.Sprintf("%s-bytes", key),
|
||||
value: []byte{0x1},
|
||||
},
|
||||
{
|
||||
key: fmt.Sprintf("%s-string-with-json", key),
|
||||
value: "{\"a\":\"b\"}",
|
||||
expectedReadResponse: []byte("\"{\\\"a\\\":\\\"b\\\"}\""),
|
||||
key: fmt.Sprintf("%s-string-with-json", key),
|
||||
value: "{\"a\":\"b\"}",
|
||||
},
|
||||
{
|
||||
key: fmt.Sprintf("%s-string", key),
|
||||
value: "hello world",
|
||||
expectedReadResponse: []byte("\"hello world\""),
|
||||
key: fmt.Sprintf("%s-string", key),
|
||||
value: "hello world",
|
||||
},
|
||||
{
|
||||
key: fmt.Sprintf("%s-struct", key),
|
||||
value: ValueType{Message: fmt.Sprintf("%s-test", key)},
|
||||
expectedReadResponse: []byte(fmt.Sprintf("{\"message\":\"%s-test\"}", key)),
|
||||
key: fmt.Sprintf("%s-struct", key),
|
||||
value: ValueType{Message: fmt.Sprintf("%s-test", key)},
|
||||
},
|
||||
{
|
||||
key: fmt.Sprintf("%s-to-be-deleted", key),
|
||||
value: "to be deleted",
|
||||
expectedReadResponse: []byte("\"to be deleted\""),
|
||||
toBeDeleted: true,
|
||||
key: fmt.Sprintf("%s-to-be-deleted", key),
|
||||
value: "to be deleted",
|
||||
toBeDeleted: true,
|
||||
},
|
||||
{
|
||||
key: fmt.Sprintf("%s-bulk-int", key),
|
||||
value: 123,
|
||||
expectedReadResponse: []byte("123"),
|
||||
bulkOnly: true,
|
||||
key: fmt.Sprintf("%s-bulk-int", key),
|
||||
value: 123,
|
||||
bulkOnly: true,
|
||||
},
|
||||
{
|
||||
key: fmt.Sprintf("%s-bulk-bool", key),
|
||||
value: true,
|
||||
expectedReadResponse: []byte("true"),
|
||||
bulkOnly: true,
|
||||
key: fmt.Sprintf("%s-bulk-bool", key),
|
||||
value: true,
|
||||
bulkOnly: true,
|
||||
},
|
||||
{
|
||||
key: fmt.Sprintf("%s-bulk-bytes", key),
|
||||
value: []byte{0x1},
|
||||
expectedReadResponse: []byte{0x1},
|
||||
bulkOnly: true,
|
||||
key: fmt.Sprintf("%s-bulk-bytes", key),
|
||||
value: []byte{0x1},
|
||||
bulkOnly: true,
|
||||
},
|
||||
{
|
||||
key: fmt.Sprintf("%s-bulk-string", key),
|
||||
value: "hello world",
|
||||
expectedReadResponse: []byte("\"hello world\""),
|
||||
bulkOnly: true,
|
||||
key: fmt.Sprintf("%s-bulk-string", key),
|
||||
value: "hello world",
|
||||
bulkOnly: true,
|
||||
},
|
||||
{
|
||||
key: fmt.Sprintf("%s-bulk-struct", key),
|
||||
value: ValueType{Message: "test"},
|
||||
expectedReadResponse: []byte("{\"message\":\"test\"}"),
|
||||
bulkOnly: true,
|
||||
key: fmt.Sprintf("%s-bulk-struct", key),
|
||||
value: ValueType{Message: "test"},
|
||||
bulkOnly: true,
|
||||
},
|
||||
{
|
||||
key: fmt.Sprintf("%s-bulk-to-be-deleted", key),
|
||||
value: "to be deleted",
|
||||
expectedReadResponse: []byte("\"to be deleted\""),
|
||||
toBeDeleted: true,
|
||||
bulkOnly: true,
|
||||
key: fmt.Sprintf("%s-bulk-to-be-deleted", key),
|
||||
value: "to be deleted",
|
||||
toBeDeleted: true,
|
||||
bulkOnly: true,
|
||||
},
|
||||
{
|
||||
key: fmt.Sprintf("%s-bulk-to-be-deleted-too", key),
|
||||
value: "to be deleted too",
|
||||
expectedReadResponse: []byte("\"to be deleted too\""),
|
||||
toBeDeleted: true,
|
||||
bulkOnly: true,
|
||||
key: fmt.Sprintf("%s-bulk-to-be-deleted-too", key),
|
||||
value: "to be deleted too",
|
||||
toBeDeleted: true,
|
||||
bulkOnly: true,
|
||||
},
|
||||
{
|
||||
key: fmt.Sprintf("%s-trx-int", key),
|
||||
value: 123,
|
||||
expectedReadResponse: []byte("123"),
|
||||
transactionOnly: true,
|
||||
transactionGroup: 1,
|
||||
key: fmt.Sprintf("%s-trx-int", key),
|
||||
value: 123,
|
||||
transactionOnly: true,
|
||||
transactionGroup: 1,
|
||||
},
|
||||
{
|
||||
key: fmt.Sprintf("%s-trx-bool", key),
|
||||
value: true,
|
||||
expectedReadResponse: []byte("true"),
|
||||
transactionOnly: true,
|
||||
transactionGroup: 1,
|
||||
key: fmt.Sprintf("%s-trx-bool", key),
|
||||
value: true,
|
||||
transactionOnly: true,
|
||||
transactionGroup: 1,
|
||||
},
|
||||
{
|
||||
key: fmt.Sprintf("%s-trx-bytes", key),
|
||||
value: []byte{0x1},
|
||||
expectedReadResponse: []byte{0x1},
|
||||
transactionOnly: true,
|
||||
transactionGroup: 1,
|
||||
key: fmt.Sprintf("%s-trx-bytes", key),
|
||||
value: []byte{0x1},
|
||||
transactionOnly: true,
|
||||
transactionGroup: 1,
|
||||
},
|
||||
{
|
||||
key: fmt.Sprintf("%s-trx-string", key),
|
||||
value: "hello world",
|
||||
expectedReadResponse: []byte("\"hello world\""),
|
||||
transactionOnly: true,
|
||||
transactionGroup: 1,
|
||||
key: fmt.Sprintf("%s-trx-string", key),
|
||||
value: "hello world",
|
||||
transactionOnly: true,
|
||||
transactionGroup: 1,
|
||||
},
|
||||
{
|
||||
key: fmt.Sprintf("%s-trx-struct", key),
|
||||
value: ValueType{Message: "test"},
|
||||
expectedReadResponse: []byte("{\"message\":\"test\"}"),
|
||||
transactionOnly: true,
|
||||
transactionGroup: 2,
|
||||
key: fmt.Sprintf("%s-trx-struct", key),
|
||||
value: ValueType{Message: "test"},
|
||||
transactionOnly: true,
|
||||
transactionGroup: 2,
|
||||
},
|
||||
{
|
||||
key: fmt.Sprintf("%s-trx-to-be-deleted", key),
|
||||
value: "to be deleted",
|
||||
expectedReadResponse: []byte("\"to be deleted\""),
|
||||
toBeDeleted: true,
|
||||
transactionOnly: true,
|
||||
transactionGroup: 1,
|
||||
key: fmt.Sprintf("%s-trx-to-be-deleted", key),
|
||||
value: "to be deleted",
|
||||
toBeDeleted: true,
|
||||
transactionOnly: true,
|
||||
transactionGroup: 1,
|
||||
},
|
||||
{
|
||||
key: fmt.Sprintf("%s-trx-to-be-deleted-too", key),
|
||||
value: "to be deleted too",
|
||||
expectedReadResponse: []byte("\"to be deleted too\""),
|
||||
toBeDeleted: true,
|
||||
transactionOnly: true,
|
||||
transactionGroup: 3,
|
||||
key: fmt.Sprintf("%s-trx-to-be-deleted-too", key),
|
||||
value: "to be deleted too",
|
||||
toBeDeleted: true,
|
||||
transactionOnly: true,
|
||||
transactionGroup: 3,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -258,7 +236,7 @@ func ConformanceTests(t *testing.T, props map[string]string, statestore state.St
|
|||
Key: scenario.key,
|
||||
})
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, scenario.expectedReadResponse, res.Data)
|
||||
assertEquals(t, scenario.value, res)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
@ -329,7 +307,7 @@ func ConformanceTests(t *testing.T, props map[string]string, statestore state.St
|
|||
Key: scenario.key,
|
||||
})
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, scenario.expectedReadResponse, res.Data)
|
||||
assertEquals(t, scenario.value, res)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
@ -424,7 +402,7 @@ func ConformanceTests(t *testing.T, props map[string]string, statestore state.St
|
|||
},
|
||||
})
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, scenario.expectedReadResponse, res.Data)
|
||||
assertEquals(t, scenario.value, res)
|
||||
}
|
||||
|
||||
if scenario.toBeDeleted && (scenario.transactionGroup == transactionGroup-1) {
|
||||
|
|
@ -481,7 +459,7 @@ func ConformanceTests(t *testing.T, props map[string]string, statestore state.St
|
|||
})
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, firstValue, res.Data)
|
||||
assertEquals(t, firstValue, res)
|
||||
etag := res.ETag
|
||||
|
||||
// Try and update with wrong ETag, expect failure.
|
||||
|
|
@ -505,7 +483,7 @@ func ConformanceTests(t *testing.T, props map[string]string, statestore state.St
|
|||
Key: testKey,
|
||||
})
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, secondValue, res.Data)
|
||||
assertEquals(t, secondValue, res)
|
||||
assert.NotEqual(t, etag, res.ETag)
|
||||
etag = res.ETag
|
||||
|
||||
|
|
@ -588,7 +566,7 @@ func ConformanceTests(t *testing.T, props map[string]string, statestore state.St
|
|||
Key: testKey,
|
||||
})
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, firstValue, res.Data)
|
||||
assertEquals(t, firstValue, res)
|
||||
|
||||
// Second write expect fail
|
||||
err = statestore.Set(requestSet[1])
|
||||
|
|
@ -620,7 +598,7 @@ func ConformanceTests(t *testing.T, props map[string]string, statestore state.St
|
|||
Key: testKey,
|
||||
})
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, firstValue, res.Data)
|
||||
assertEquals(t, firstValue, res)
|
||||
|
||||
etag := res.ETag
|
||||
|
||||
|
|
@ -642,7 +620,7 @@ func ConformanceTests(t *testing.T, props map[string]string, statestore state.St
|
|||
})
|
||||
assert.Nil(t, err)
|
||||
assert.NotEqual(t, etag, res.ETag)
|
||||
assert.Equal(t, secondValue, res.Data)
|
||||
assertEquals(t, secondValue, res)
|
||||
|
||||
request.ETag = etag
|
||||
|
||||
|
|
@ -652,3 +630,28 @@ func ConformanceTests(t *testing.T, props map[string]string, statestore state.St
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func assertEquals(t *testing.T, value interface{}, res *state.GetResponse) {
|
||||
switch v := value.(type) {
|
||||
case ValueType:
|
||||
// Custom type requires case mapping
|
||||
if err := json.Unmarshal(res.Data, &v); err != nil {
|
||||
assert.Failf(t, "unmarshal error", "error: %w, json: %s", err, string(res.Data))
|
||||
}
|
||||
assert.Equal(t, value, v)
|
||||
case int:
|
||||
// json.Unmarshal to float64 by default, case mapping to int coerces to int type
|
||||
if err := json.Unmarshal(res.Data, &v); err != nil {
|
||||
assert.Failf(t, "unmarshal error", "error: %w, json: %s", err, string(res.Data))
|
||||
}
|
||||
assert.Equal(t, value, v)
|
||||
case []byte:
|
||||
assert.Equal(t, value, res.Data)
|
||||
default:
|
||||
// Other golang primitive types (string, bool ...)
|
||||
if err := json.Unmarshal(res.Data, &v); err != nil {
|
||||
assert.Failf(t, "unmarshal error", "error: %w, json: %s", err, string(res.Data))
|
||||
}
|
||||
assert.Equal(t, value, v)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue