Merge branch 'content-type' of github.com:jigargandhi/components-contrib into content-type

This commit is contained in:
jigargandhi 2021-10-25 15:13:50 +05:30
commit 63f87f0641
12 changed files with 312 additions and 179 deletions

View File

@ -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

View File

@ -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: |

View File

@ -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 {

View File

@ -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

View File

@ -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)

View File

@ -1,3 +1,8 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation and Dapr Contributors.
// Licensed under the MIT License.
// ------------------------------------------------------------
package query
import (

View File

@ -1,3 +1,8 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation and Dapr Contributors.
// Licensed under the MIT License.
// ------------------------------------------------------------
package query
import (

View File

@ -1,3 +1,8 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation and Dapr Contributors.
// Licensed under the MIT License.
// ------------------------------------------------------------
package query
import (

View File

@ -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"

View File

@ -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" ]

View File

@ -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":

View File

@ -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)
}
}