From 649a7234dc6ff636861bfb9e1d612759ad80d939 Mon Sep 17 00:00:00 2001 From: Bernd Verst <4535280+berndverst@users.noreply.github.com> Date: Wed, 17 Aug 2022 13:57:32 -0700 Subject: [PATCH 1/2] Fix CosmosDB state store handling of nulls and wrongly encoded binary data Signed-off-by: Bernd Verst <4535280+berndverst@users.noreply.github.com> --- state/azure/cosmosdb/cosmosdb.go | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/state/azure/cosmosdb/cosmosdb.go b/state/azure/cosmosdb/cosmosdb.go index 1567b735a7..e0ebd315f8 100644 --- a/state/azure/cosmosdb/cosmosdb.go +++ b/state/azure/cosmosdb/cosmosdb.go @@ -249,7 +249,18 @@ func (c *StateStore) Get(req *state.GetRequest) (*state.GetResponse, error) { } if items[0].IsBinary { - bytes, _ := base64.StdEncoding.DecodeString(items[0].Value.(string)) + if items[0].Value == nil || len(items[0].Value.(string)) == 0 { + return &state.GetResponse{ + Data: make([]byte, 0), + ETag: ptr.String(items[0].Etag), + }, nil + } + + bytes, decodeErr := base64.StdEncoding.DecodeString(items[0].Value.(string)) + if decodeErr != nil { + c.logger.Warnf("CosmosDB state store Get request could not decode binary string: %v. Returning raw string instead.", decodeErr) + bytes = []byte(items[0].Value.(string)) + } return &state.GetResponse{ Data: bytes, @@ -583,6 +594,9 @@ func (c *StateStore) findCollection() (*documentdb.Collection, error) { func createUpsertItem(contentType string, req state.SetRequest, partitionKey string) (CosmosItem, error) { byteArray, isBinary := req.Value.([]uint8) + if len(byteArray) == 0 { + isBinary = false + } ttl, err := parseTTL(req.Metadata) if err != nil { From 5b1eb80e17d0e4a3d138f92ff792bf22b4fe9954 Mon Sep 17 00:00:00 2001 From: Bernd Verst <4535280+berndverst@users.noreply.github.com> Date: Wed, 17 Aug 2022 15:55:23 -0700 Subject: [PATCH 2/2] add unit tests for cosmosdb nulls Signed-off-by: Bernd Verst <4535280+berndverst@users.noreply.github.com> --- state/azure/cosmosdb/cosmosdb.go | 2 +- state/azure/cosmosdb/cosmosdb_test.go | 91 +++++++++++++++++++++++++++ 2 files changed, 92 insertions(+), 1 deletion(-) diff --git a/state/azure/cosmosdb/cosmosdb.go b/state/azure/cosmosdb/cosmosdb.go index e0ebd315f8..8b72725c2f 100644 --- a/state/azure/cosmosdb/cosmosdb.go +++ b/state/azure/cosmosdb/cosmosdb.go @@ -249,7 +249,7 @@ func (c *StateStore) Get(req *state.GetRequest) (*state.GetResponse, error) { } if items[0].IsBinary { - if items[0].Value == nil || len(items[0].Value.(string)) == 0 { + if items[0].Value == nil { return &state.GetResponse{ Data: make([]byte, 0), ETag: ptr.String(items[0].Etag), diff --git a/state/azure/cosmosdb/cosmosdb_test.go b/state/azure/cosmosdb/cosmosdb_test.go index 31a2eb6479..364f1b1055 100644 --- a/state/azure/cosmosdb/cosmosdb_test.go +++ b/state/azure/cosmosdb/cosmosdb_test.go @@ -182,6 +182,97 @@ func TestCreateCosmosItem(t *testing.T) { assert.Equal(t, "AQ==", m) }) + + t.Run("create item with null data", func(t *testing.T) { + // Bytes are handled the same way, does not matter if is JSON or JPEG. + bytes, err := json.Marshal(nil) + assert.NoError(t, err) + + req := state.SetRequest{ + Key: "testKey", + Value: bytes, + } + + item, err := createUpsertItem("application/json", req, partitionKey) + assert.NoError(t, err) + assert.Equal(t, partitionKey, item.PartitionKey) + assert.Equal(t, "testKey", item.ID) + assert.False(t, item.IsBinary) + assert.Nil(t, item.TTL) + + // items need to be marshallable to JSON with encoding/json + b, err := json.Marshal(item) + assert.NoError(t, err) + + j := map[string]interface{}{} + err = json.Unmarshal(b, &j) + assert.NoError(t, err) + + assert.Nil(t, j["value"]) + }) + + t.Run("create item with empty string data and JSON content type", func(t *testing.T) { + // Bytes are handled the same way, does not matter if is JSON or JPEG. + bytes := []byte("") + + req := state.SetRequest{ + Key: "testKey", + Value: bytes, + } + + item, err := createUpsertItem("application/json", req, partitionKey) + assert.NoError(t, err) + assert.Equal(t, partitionKey, item.PartitionKey) + assert.Equal(t, "testKey", item.ID) + assert.False(t, item.IsBinary) + assert.Nil(t, item.TTL) + + // items need to be marshallable to JSON with encoding/json + b, err := json.Marshal(item) + assert.NoError(t, err) + + j := map[string]interface{}{} + err = json.Unmarshal(b, &j) + assert.NoError(t, err) + + m, ok := (j["value"].(string)) + + assert.Truef(t, ok, "value should be a string") + assert.NotContains(t, j, "ttl") + assert.Equal(t, "", m) + }) + + t.Run("create item with empty string data and string content type", func(t *testing.T) { + // Bytes are handled the same way, does not matter if is JSON or JPEG. + bytes := []byte("") + + req := state.SetRequest{ + Key: "testKey", + Value: bytes, + } + + item, err := createUpsertItem("text/plain", req, partitionKey) + assert.NoError(t, err) + assert.Equal(t, partitionKey, item.PartitionKey) + assert.Equal(t, "testKey", item.ID) + assert.False(t, item.IsBinary) + assert.Nil(t, item.TTL) + + // items need to be marshallable to JSON with encoding/json + b, err := json.Marshal(item) + assert.NoError(t, err) + + j := map[string]interface{}{} + err = json.Unmarshal(b, &j) + assert.NoError(t, err) + + m, ok := (j["value"].(string)) + + assert.Truef(t, ok, "value should be a string") + assert.NotContains(t, j, "ttl") + + assert.Equal(t, "", m) + }) } func TestCreateCosmosItemWithTTL(t *testing.T) {