From cca4f3de8b6a7a637e9bb5fbaa08a968cfbba294 Mon Sep 17 00:00:00 2001 From: vikram Date: Thu, 24 Oct 2024 02:24:14 +0530 Subject: [PATCH] test: websocket executer integration test for json.del and json.forget --- integration_tests/commands/http/json_test.go | 115 +++++- .../commands/http/toggle_test.go | 109 ------ integration_tests/commands/resp/json_test.go | 158 ++++++-- .../commands/websocket/json_test.go | 352 ++++++++++++++++-- testutils/json.go | 6 + 5 files changed, 559 insertions(+), 181 deletions(-) delete mode 100644 integration_tests/commands/http/toggle_test.go diff --git a/integration_tests/commands/http/json_test.go b/integration_tests/commands/http/json_test.go index 35b4b973d..f8641a6a7 100644 --- a/integration_tests/commands/http/json_test.go +++ b/integration_tests/commands/http/json_test.go @@ -14,6 +14,24 @@ import ( "gotest.tools/v3/assert" ) +func compareJSON(t *testing.T, expected, actual string) { + var expectedMap map[string]interface{} + var actualMap map[string]interface{} + + err1 := json.Unmarshal([]byte(expected), &expectedMap) + err2 := json.Unmarshal([]byte(actual), &actualMap) + + assert.NilError(t, err1) + assert.NilError(t, err2) + + assert.DeepEqual(t, expectedMap, actualMap) +} + +func isJSONString(s string) bool { + var js json.RawMessage + return json.Unmarshal([]byte(s), &js) == nil +} + func TestJSONOperations(t *testing.T) { exec := NewHTTPCommandExecutor() simpleJsonString := `{"name":"John","age":30}` @@ -628,6 +646,89 @@ func TestJSONForgetOperations(t *testing.T) { exec.FireCommand(HTTPCommand{Command: "DEL", Body: map[string]interface{}{"key": "k"}}) } +func TestJSONTOGGLE(t *testing.T) { + exec := NewHTTPCommandExecutor() + + simpleJSON := `{"name":true,"age":false}` + complexJson := `{"field":true,"nested":{"field":false,"nested":{"field":true}}}` + + testCases := []struct { + name string + commands []HTTPCommand + expected []interface{} + }{ + { + name: "JSON.TOGGLE with existing key", + commands: []HTTPCommand{ + {Command: "JSON.SET", Body: map[string]interface{}{"key": "user", "path": "$", "value": simpleJSON}}, + {Command: "JSON.TOGGLE", Body: map[string]interface{}{"key": "user", "path": "$.name"}}, + }, + expected: []interface{}{"OK", []any{float64(0)}}, + }, + { + name: "JSON.TOGGLE with non-existing key", + commands: []HTTPCommand{ + {Command: "JSON.TOGGLE", Body: map[string]interface{}{"key": "user", "path": "$.flag"}}, + }, + expected: []interface{}{"ERR could not perform this operation on a key that doesn't exist"}, + }, + { + name: "JSON.TOGGLE with invalid path", + commands: []HTTPCommand{ + {Command: "JSON.TOGGLE", Body: map[string]interface{}{"key": "user", "path": "$.invalidPath"}}, + }, + expected: []interface{}{"ERR could not perform this operation on a key that doesn't exist"}, + }, + { + name: "JSON.TOGGLE with invalid command format", + commands: []HTTPCommand{ + {Command: "JSON.TOGGLE", Body: map[string]interface{}{"key": "testKey"}}, + }, + expected: []interface{}{"ERR wrong number of arguments for 'json.toggle' command"}, + }, + { + name: "deeply nested JSON structure with multiple matching fields", + commands: []HTTPCommand{ + {Command: "JSON.SET", Body: map[string]interface{}{"key": "user", "path": "$", "value": complexJson}}, + {Command: "JSON.GET", Body: map[string]interface{}{"key": "user"}}, + {Command: "JSON.TOGGLE", Body: map[string]interface{}{"key": "user", "path": "$..field"}}, + {Command: "JSON.GET", Body: map[string]interface{}{"key": "user"}}, + }, + expected: []interface{}{ + "OK", + `{"field":true,"nested":{"field":false,"nested":{"field":true}}}`, + []any{float64(0), float64(1), float64(0)}, // Toggle: true -> false, false -> true, true -> false + `{"field":false,"nested":{"field":true,"nested":{"field":false}}}`, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + exec.FireCommand(HTTPCommand{ + Command: "DEL", + Body: map[string]interface{}{"key": "user"}, + }) + for i, cmd := range tc.commands { + result, _ := exec.FireCommand(cmd) + switch expected := tc.expected[i].(type) { + case string: + if isJSONString(expected) { + compareJSON(t, expected, result.(string)) + } else { + assert.Equal(t, expected, result) + } + case []interface{}: + assert.Assert(t, testutils.UnorderedEqual(expected, result)) + default: + assert.DeepEqual(t, expected, result) + } + } + }) + } + +} + func TestJsonStrlen(t *testing.T) { exec := NewHTTPCommandExecutor() testCases := []TestCase{ @@ -1070,49 +1171,49 @@ func TestJsonObjLen(t *testing.T) { expected: []interface{}{"ERR Path '$[1' does not exist"}, }, { - name: "JSON.OBJLEN with legacy path - root", + name: "JSON.OBJLEN with legacy path - root", commands: []HTTPCommand{ {Command: "json.objlen", Body: map[string]interface{}{"key": "c", "path": "."}}, }, expected: []interface{}{3.0}, }, { - name: "JSON.OBJLEN with legacy path - inner existing path", + name: "JSON.OBJLEN with legacy path - inner existing path", commands: []HTTPCommand{ {Command: "json.objlen", Body: map[string]interface{}{"key": "c", "path": ".partner2"}}, }, expected: []interface{}{2.0}, }, { - name: "JSON.OBJLEN with legacy path - inner existing path v2", + name: "JSON.OBJLEN with legacy path - inner existing path v2", commands: []HTTPCommand{ {Command: "json.objlen", Body: map[string]interface{}{"key": "c", "path": "partner"}}, }, expected: []interface{}{2.0}, }, { - name: "JSON.OBJLEN with legacy path - inner non-existent path", + name: "JSON.OBJLEN with legacy path - inner non-existent path", commands: []HTTPCommand{ {Command: "json.objlen", Body: map[string]interface{}{"key": "c", "path": ".idonotexist"}}, }, expected: []interface{}{nil}, }, { - name: "JSON.OBJLEN with legacy path - inner non-existent path v2", + name: "JSON.OBJLEN with legacy path - inner non-existent path v2", commands: []HTTPCommand{ {Command: "json.objlen", Body: map[string]interface{}{"key": "c", "path": "idonotexist"}}, }, expected: []interface{}{nil}, }, { - name: "JSON.OBJLEN with legacy path - inner existent path with nonJSON object", + name: "JSON.OBJLEN with legacy path - inner existent path with nonJSON object", commands: []HTTPCommand{ {Command: "json.objlen", Body: map[string]interface{}{"key": "c", "path": ".name"}}, }, expected: []interface{}{"WRONGTYPE Operation against a key holding the wrong kind of value"}, }, { - name: "JSON.OBJLEN with legacy path - inner existent path recursive object", + name: "JSON.OBJLEN with legacy path - inner existent path recursive object", commands: []HTTPCommand{ {Command: "json.objlen", Body: map[string]interface{}{"key": "c", "path": "..partner"}}, }, diff --git a/integration_tests/commands/http/toggle_test.go b/integration_tests/commands/http/toggle_test.go deleted file mode 100644 index 7337e197d..000000000 --- a/integration_tests/commands/http/toggle_test.go +++ /dev/null @@ -1,109 +0,0 @@ -package http - -import ( - "encoding/json" - "testing" - - "github.com/dicedb/dice/testutils" - "gotest.tools/v3/assert" -) - -func compareJSON(t *testing.T, expected, actual string) { - var expectedMap map[string]interface{} - var actualMap map[string]interface{} - - err1 := json.Unmarshal([]byte(expected), &expectedMap) - err2 := json.Unmarshal([]byte(actual), &actualMap) - - assert.NilError(t, err1) - assert.NilError(t, err2) - - assert.DeepEqual(t, expectedMap, actualMap) -} -func TestJSONTOGGLE(t *testing.T) { - exec := NewHTTPCommandExecutor() - - simpleJSON := `{"name":true,"age":false}` - complexJson := `{"field":true,"nested":{"field":false,"nested":{"field":true}}}` - - testCases := []struct { - name string - commands []HTTPCommand - expected []interface{} - }{ - { - name: "JSON.TOGGLE with existing key", - commands: []HTTPCommand{ - {Command: "JSON.SET", Body: map[string]interface{}{"key": "user", "path": "$", "value": simpleJSON}}, - {Command: "JSON.TOGGLE", Body: map[string]interface{}{"key": "user", "path": "$.name"}}, - }, - expected: []interface{}{"OK", []any{float64(0)}}, - }, - { - name: "JSON.TOGGLE with non-existing key", - commands: []HTTPCommand{ - {Command: "JSON.TOGGLE", Body: map[string]interface{}{"key": "user", "path": "$.flag"}}, - }, - expected: []interface{}{"ERR could not perform this operation on a key that doesn't exist"}, - }, - { - name: "JSON.TOGGLE with invalid path", - commands: []HTTPCommand{ - {Command: "JSON.TOGGLE", Body: map[string]interface{}{"key": "user", "path": "$.invalidPath"}}, - }, - expected: []interface{}{"ERR could not perform this operation on a key that doesn't exist"}, - }, - { - name: "JSON.TOGGLE with invalid command format", - commands: []HTTPCommand{ - {Command: "JSON.TOGGLE", Body: map[string]interface{}{"key": "testKey"}}, - }, - expected: []interface{}{"ERR wrong number of arguments for 'json.toggle' command"}, - }, - { - name: "deeply nested JSON structure with multiple matching fields", - commands: []HTTPCommand{ - {Command: "JSON.SET", Body: map[string]interface{}{"key": "user", "path": "$", "value": complexJson}}, - {Command: "JSON.GET", Body: map[string]interface{}{"key": "user"}}, - {Command: "JSON.TOGGLE", Body: map[string]interface{}{"key": "user", "path": "$..field"}}, - {Command: "JSON.GET", Body: map[string]interface{}{"key": "user"}}, - }, - expected: []interface{}{ - "OK", - `{"field":true,"nested":{"field":false,"nested":{"field":true}}}`, - []any{float64(0), float64(1), float64(0)}, // Toggle: true -> false, false -> true, true -> false - `{"field":false,"nested":{"field":true,"nested":{"field":false}}}`, - }, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - exec.FireCommand(HTTPCommand{ - Command: "DEL", - Body: map[string]interface{}{"key": "user"}, - }) - for i, cmd := range tc.commands { - result, _ := exec.FireCommand(cmd) - switch expected := tc.expected[i].(type) { - case string: - if isJSONString(expected) { - compareJSON(t, expected, result.(string)) - } else { - assert.Equal(t, expected, result) - } - case []interface{}: - assert.Assert(t, testutils.UnorderedEqual(expected, result)) - default: - assert.DeepEqual(t, expected, result) - } - } - }) - } - -} - -func isJSONString(s string) bool { - var js json.RawMessage - return json.Unmarshal([]byte(s), &js) == nil -} diff --git a/integration_tests/commands/resp/json_test.go b/integration_tests/commands/resp/json_test.go index 7128e6aed..07f4b19fe 100644 --- a/integration_tests/commands/resp/json_test.go +++ b/integration_tests/commands/resp/json_test.go @@ -1,10 +1,124 @@ package resp import ( - "gotest.tools/v3/assert" "testing" + + "gotest.tools/v3/assert" ) +func arraysArePermutations[T comparable](a, b []T) bool { + // If lengths are different, they cannot be permutations + if len(a) != len(b) { + return false + } + + // Count occurrences of each element in array 'a' + countA := make(map[T]int) + for _, elem := range a { + countA[elem]++ + } + + // Subtract occurrences based on array 'b' + for _, elem := range b { + countA[elem]-- + if countA[elem] < 0 { + return false + } + } + + // Check if all counts are zero + for _, count := range countA { + if count != 0 { + return false + } + } + + return true +} + +func TestJsonDel(t *testing.T) { + conn := getLocalConnection() + defer conn.Close() + FireCommand(conn, "DEL doc") + + testCases := []struct { + name string + commands []string + expected []interface{} + }{ + { + name: "jsonstrlen with root path", + commands: []string{ + `JSON.SET doc $ ["hello","world"]`, + "JSON.STRLEN doc $", + }, + expected: []interface{}{"OK", []interface{}{"(nil)"}}, + }, + { + name: "jsonstrlen nested", + commands: []string{ + `JSON.SET doc $ {"name":"jerry","partner":{"name":"tom"}}`, + "JSON.STRLEN doc $..name", + }, + expected: []interface{}{"OK", []interface{}{int64(5), int64(3)}}, + }, + { + name: "jsonstrlen with no path and object at root", + commands: []string{ + `JSON.SET doc $ {"name":"bhima","age":10}`, + "JSON.STRLEN doc", + }, + expected: []interface{}{"OK", "ERR wrong type of path value - expected string but found object"}, + }, + { + name: "jsonstrlen with no path and object at boolean", + commands: []string{ + `JSON.SET doc $ true`, + "JSON.STRLEN doc", + }, + expected: []interface{}{"OK", "ERR wrong type of path value - expected string but found boolean"}, + }, + { + name: "jsonstrlen with no path and object at array", + commands: []string{ + `JSON.SET doc $ [1,2,3,4]`, + "JSON.STRLEN doc", + }, + expected: []interface{}{"OK", "ERR wrong type of path value - expected string but found array"}, + }, + { + name: "jsonstrlen with no path and object at integer", + commands: []string{ + `JSON.SET doc $ 1`, + "JSON.STRLEN doc", + }, + expected: []interface{}{"OK", "ERR wrong type of path value - expected string but found integer"}, + }, + { + name: "jsonstrlen with no path and object at number", + commands: []string{ + `JSON.SET doc $ 1.9`, + "JSON.STRLEN doc", + }, + expected: []interface{}{"OK", "ERR wrong type of path value - expected string but found number"}, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + for i, cmd := range tc.commands { + result := FireCommand(conn, cmd) + stringResult, ok := result.(string) + if ok { + assert.Equal(t, tc.expected[i], stringResult) + } else { + assert.Assert(t, arraysArePermutations(tc.expected[i].([]interface{}), result.([]interface{}))) + } + } + }) + } +} + func TestJsonStrlen(t *testing.T) { conn := getLocalConnection() defer conn.Close() @@ -247,32 +361,32 @@ func TestJsonObjLen(t *testing.T) { }, { name: "JSON.OBJLEN with legacy path - inner existing path", - commands: []string{"json.set obj $ " + c, "json.objlen obj .partner", "json.objlen obj .partner2",}, + commands: []string{"json.set obj $ " + c, "json.objlen obj .partner", "json.objlen obj .partner2"}, expected: []interface{}{"OK", int64(2), int64(2)}, }, { name: "JSON.OBJLEN with legacy path - inner existing path v2", - commands: []string{"json.set obj $ " + c, "json.objlen obj partner", "json.objlen obj partner2",}, + commands: []string{"json.set obj $ " + c, "json.objlen obj partner", "json.objlen obj partner2"}, expected: []interface{}{"OK", int64(2), int64(2)}, }, { name: "JSON.OBJLEN with legacy path - inner non-existent path", - commands: []string{"json.set obj $ " + c, "json.objlen obj .idonotexist",}, + commands: []string{"json.set obj $ " + c, "json.objlen obj .idonotexist"}, expected: []interface{}{"OK", "(nil)"}, }, { name: "JSON.OBJLEN with legacy path - inner non-existent path v2", - commands: []string{"json.set obj $ " + c, "json.objlen obj idonotexist",}, + commands: []string{"json.set obj $ " + c, "json.objlen obj idonotexist"}, expected: []interface{}{"OK", "(nil)"}, }, { name: "JSON.OBJLEN with legacy path - inner existent path with nonJSON object", - commands: []string{"json.set obj $ " + c, "json.objlen obj .name",}, + commands: []string{"json.set obj $ " + c, "json.objlen obj .name"}, expected: []interface{}{"OK", "WRONGTYPE Operation against a key holding the wrong kind of value"}, }, { name: "JSON.OBJLEN with legacy path - inner existent path recursive object", - commands: []string{"json.set obj $ " + c, "json.objlen obj ..partner",}, + commands: []string{"json.set obj $ " + c, "json.objlen obj ..partner"}, expected: []interface{}{"OK", int64(2)}, }, } @@ -289,33 +403,3 @@ func TestJsonObjLen(t *testing.T) { }) } } - -func arraysArePermutations[T comparable](a, b []T) bool { - // If lengths are different, they cannot be permutations - if len(a) != len(b) { - return false - } - - // Count occurrences of each element in array 'a' - countA := make(map[T]int) - for _, elem := range a { - countA[elem]++ - } - - // Subtract occurrences based on array 'b' - for _, elem := range b { - countA[elem]-- - if countA[elem] < 0 { - return false - } - } - - // Check if all counts are zero - for _, count := range countA { - if count != 0 { - return false - } - } - - return true -} diff --git a/integration_tests/commands/websocket/json_test.go b/integration_tests/commands/websocket/json_test.go index 35faf3ba9..3bbfff2d3 100644 --- a/integration_tests/commands/websocket/json_test.go +++ b/integration_tests/commands/websocket/json_test.go @@ -1,11 +1,34 @@ package websocket import ( + "encoding/json" + "fmt" "testing" - "github.com/stretchr/testify/assert" + "github.com/dicedb/dice/testutils" + testifyAssert "github.com/stretchr/testify/assert" + + "gotest.tools/v3/assert" ) +func compareJSON(t *testing.T, expected, actual string) { + var expectedMap map[string]interface{} + var actualMap map[string]interface{} + + err1 := json.Unmarshal([]byte(expected), &expectedMap) + err2 := json.Unmarshal([]byte(actual), &actualMap) + + assert.NilError(t, err1) + assert.NilError(t, err2) + + assert.DeepEqual(t, expectedMap, actualMap) +} + +func isJSONString(s string) bool { + var js json.RawMessage + return json.Unmarshal([]byte(s), &js) == nil +} + func TestJSONClearOperations(t *testing.T) { exec := NewWebsocketCommandExecutor() conn := exec.ConnectToServer() @@ -14,8 +37,7 @@ func TestJSONClearOperations(t *testing.T) { DeleteKey(t, conn, exec, "user") defer func() { - resp, err := exec.FireCommandAndReadResponse(conn, "DEL user") - assert.Nil(t, err) + resp, _ := exec.FireCommandAndReadResponse(conn, "DEL user") assert.Equal(t, float64(1), resp) }() @@ -88,14 +110,290 @@ func TestJSONClearOperations(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { for i, cmd := range tc.commands { - result, err := exec.FireCommandAndReadResponse(conn, cmd) - assert.Nil(t, err) + result, _ := exec.FireCommandAndReadResponse(conn, cmd) assert.Equal(t, tc.expected[i], result) } }) } } +func TestJSONDelOperations(t *testing.T) { + exec := NewWebsocketCommandExecutor() + conn := exec.ConnectToServer() + defer conn.Close() + + defer func() { + resp, _ := exec.FireCommandAndReadResponse(conn, "DEL user") + assert.Equal(t, float64(0), resp) + }() + + beforeTestSetup := "DEL user" + + testCases := []struct { + name string + setupData string + commands []string + expected []interface{} + cleanUp []string + }{ + { + name: "Delete root path", + setupData: `JSON.SET user $ {"age":13,"high":1.60,"flag":true,"name":"jerry","pet":null,"language":["python","golang"],"partner":{"name":"tom","language":["rust"]}}`, + commands: []string{ + "JSON.DEL user $", + "JSON.GET user $", + }, + expected: []interface{}{float64(1), "(nil)"}, + cleanUp: []string{"DEL user"}, + }, + { + name: "Delete nested field", + setupData: `JSON.SET user $ {"age":13,"high":1.60,"flag":true,"name":"jerry","pet":null,"language":["python","golang"],"partner":{"name":"tom","language":["rust"]}}`, + commands: []string{ + "JSON.DEL user $.partner.name", + "JSON.GET user $", + }, + expected: []interface{}{float64(1), `{"age":13,"high":1.60,"flag":true,"name":"jerry","pet":null,"language":["python","golang"],"partner":{"language":["rust"]}}`}, + cleanUp: []string{"DEL user"}, + }, + { + name: "del string type", + setupData: `JSON.SET user $ {"flag":true,"name":"Tom"}`, + commands: []string{ + "JSON.DEL user $.name", + "JSON.GET user $"}, + expected: []interface{}{float64(1), `{"flag":true}`}, + cleanUp: []string{"DEL user"}, + }, + { + name: "del bool type", + setupData: `JSON.SET user $ {"flag":true,"name":"Tom"}`, + commands: []string{ + "JSON.DEL user $.flag", + "JSON.GET user $"}, + expected: []interface{}{float64(1), `{"name":"Tom"}`}, + cleanUp: []string{"DEL user"}, + }, + { + name: "del null type", + setupData: `JSON.SET user $ {"name":null,"age":28}`, + commands: []string{ + "JSON.DEL user $.name", + "JSON.GET user $"}, + expected: []interface{}{float64(1), `{"age":28}`}, + cleanUp: []string{"DEL user"}, + }, + { + name: "del array type", + setupData: `JSON.SET user $ {"names":["Rahul","Tom"],"bosses":{"names":["Jerry","Rocky"],"hobby":"swim"}}`, + commands: []string{ + "JSON.DEL user $..names", + "JSON.GET user $"}, + expected: []interface{}{float64(2), `{"bosses":{"hobby":"swim"}}`}, + cleanUp: []string{"DEL user"}, + }, + { + name: "del integer type", + setupData: `JSON.SET user $ {"age":28,"name":"Tom"}`, + commands: []string{ + "JSON.DEL user $.age", + "JSON.GET user $"}, + expected: []interface{}{float64(1), `{"name":"Tom"}`}, + cleanUp: []string{"DEL user"}, + }, + { + name: "del float type", + setupData: `JSON.SET user $ {"price":3.14,"name":"sugar"}`, + commands: []string{ + "JSON.DEL user $.price", + "JSON.GET user $"}, + expected: []interface{}{float64(1), `{"name":"sugar"}`}, + cleanUp: []string{"DEL user"}, + }, + { + name: "delete key with []", + setupData: `JSON.SET data $ {"key[0]":"value","array":["a","b"]}`, + commands: []string{ + `JSON.DEL data ["key[0]"]`, + "JSON.GET data $"}, + expected: []interface{}{float64(1), `{"array": ["a","b"]}`}, + cleanUp: []string{"DEL data"}, + }, + } + + for _, tc := range testCases { + if beforeTestSetup != "" { + resp, _ := exec.FireCommandAndReadResponse(conn, beforeTestSetup) + assert.Equal(t, float64(0), resp) + } + + t.Run(tc.name, func(t *testing.T) { + if tc.setupData != "" { + result, _ := exec.FireCommandAndReadResponse(conn, tc.setupData) + assert.Equal(t, "OK", result) + } + + for i, cmd := range tc.commands { + result, _ := exec.FireCommandAndReadResponse(conn, cmd) + fmt.Printf("Type of result: %T\n", result) + jsonResult, isString := result.(string) + if isString && testutils.IsJSONResponse(jsonResult) { + testifyAssert.JSONEq(t, tc.expected[i].(string), jsonResult) + } else { + assert.Equal(t, tc.expected[i], result) + } + } + + for i := 0; i < len(tc.cleanUp); i++ { + exec.FireCommandAndReadResponse(conn, tc.cleanUp[i]) + } + + }) + } +} + +func TestJSONForgetOperations(t *testing.T) { + exec := NewWebsocketCommandExecutor() + conn := exec.ConnectToServer() + defer conn.Close() + + defer func() { + resp, _ := exec.FireCommandAndReadResponse(conn, "DEL user") + assert.Equal(t, float64(0), resp) + }() + + beforeTestSetup := "DEL user" + testCaseCleanUp := "DEL user" + + testCases := []struct { + name string + setupData string + commands []string + expected []interface{} + cleanUp []string + }{ + { + name: "Forget root path", + setupData: `JSON.SET user $ {"age":13,"high":1.60,"flag":true,"name":"jerry","pet":null,"language":["python","golang"],"partner":{"name":"tom","language":["rust"]}}`, + commands: []string{ + "JSON.FORGET user $", + "JSON.GET user $", + }, + expected: []interface{}{float64(1), "(nil)"}, + cleanUp: []string{"DEL user"}, + }, + { + name: "Forget nested field", + setupData: `JSON.SET user $ {"age":13,"high":1.60,"flag":true,"name":"jerry","pet":null,"language":["python","golang"],"partner":{"name":"tom","language":["rust"]}}`, + commands: []string{ + "JSON.FORGET user $.partner.name", + "JSON.GET user $", + }, + expected: []interface{}{float64(1), `{"age":13,"high":1.60,"flag":true,"name":"jerry","pet":null,"language":["python","golang"],"partner":{"language":["rust"]}}`}, + cleanUp: []string{"DEL user"}, + }, + + { + name: "forget string type", + setupData: `JSON.SET user $ {"flag":true,"name":"Tom"}`, + commands: []string{ + "JSON.FORGET user $.name", + "JSON.GET user $"}, + expected: []interface{}{float64(1), `{"flag":true}`}, + cleanUp: []string{"DEL user"}, + }, + { + name: "forget bool type", + setupData: `JSON.SET user $ {"flag":true,"name":"Tom"}`, + commands: []string{ + "JSON.FORGET user $.flag", + "JSON.GET user $"}, + expected: []interface{}{float64(1), `{"name":"Tom"}`}, + cleanUp: []string{"DEL user"}, + }, + { + name: "forget null type", + setupData: `JSON.SET user $ {"name":null,"age":28}`, + commands: []string{ + "JSON.FORGET user $.name", + "JSON.GET user $"}, + expected: []interface{}{float64(1), `{"age":28}`}, + cleanUp: []string{"DEL user"}, + }, + { + name: "forget array type", + setupData: `JSON.SET user $ {"names":["Rahul","Tom"],"bosses":{"names":["Jerry","Rocky"],"hobby":"swim"}}`, + commands: []string{ + "JSON.FORGET user $..names", + "JSON.GET user $"}, + expected: []interface{}{float64(2), `{"bosses":{"hobby":"swim"}}`}, + cleanUp: []string{"DEL user"}, + }, + { + name: "forget integer type", + setupData: `JSON.SET user $ {"age":28,"name":"Tom"}`, + commands: []string{ + "JSON.FORGET user $.age", + "JSON.GET user $"}, + expected: []interface{}{float64(1), `{"name":"Tom"}`}, + cleanUp: []string{"DEL user"}, + }, + { + name: "forget float type", + setupData: `JSON.SET user $ {"price":3.14,"name":"sugar"}`, + commands: []string{ + "JSON.FORGET user $.price", + "JSON.GET user $"}, + expected: []interface{}{float64(1), `{"name":"sugar"}`}, + cleanUp: []string{"DEL user"}, + }, + { + name: "forget array element", + setupData: `JSON.SET user $ {"names":["Rahul","Tom"],"bosses":{"names":["Jerry","Rocky"],"hobby":"swim"}}`, + commands: []string{ + "JSON.FORGET user $.names[0]", + "JSON.GET user $"}, + expected: []interface{}{float64(1), `{"names":["Tom"],"bosses":{"names":["Jerry","Rocky"],"hobby":"swim"}}`}, + cleanUp: []string{"DEL user"}, + }, + } + + for _, tc := range testCases { + if beforeTestSetup != "" { + resp, _ := exec.FireCommandAndReadResponse(conn, beforeTestSetup) + assert.Equal(t, float64(0), resp) + } + + t.Run(tc.name, func(t *testing.T) { + if tc.setupData != "" { + result, _ := exec.FireCommandAndReadResponse(conn, tc.setupData) + assert.Equal(t, "OK", result) + } + + for i, cmd := range tc.commands { + result, _ := exec.FireCommandAndReadResponse(conn, cmd) + jsonResult, isString := result.(string) + if isString && testutils.IsJSONResponse(jsonResult) { + testifyAssert.JSONEq(t, tc.expected[i].(string), jsonResult) + } else { + assert.Equal(t, tc.expected[i], result) + } + } + + for i := 0; i < len(tc.cleanUp); i++ { + exec.FireCommandAndReadResponse(conn, tc.cleanUp[i]) + } + + if testCaseCleanUp != "" { + resp, _ := exec.FireCommandAndReadResponse(conn, testCaseCleanUp) + assert.Equal(t, float64(0), resp) + } + + }) + } + +} + func TestJsonStrlen(t *testing.T) { exec := NewWebsocketCommandExecutor() conn := exec.ConnectToServer() @@ -105,8 +403,7 @@ func TestJsonStrlen(t *testing.T) { DeleteKey(t, conn, exec, "doc") defer func() { - resp, err := exec.FireCommandAndReadResponse(conn, "DEL doc") - assert.Nil(t, err) + resp, _ := exec.FireCommandAndReadResponse(conn, "DEL doc") assert.Equal(t, float64(1), resp) }() @@ -126,7 +423,7 @@ func TestJsonStrlen(t *testing.T) { { name: "jsonstrlen nested", commands: []string{ - `JSON.SET doc $ {"name":"jerry","partner":{"name":"tom"}}`, + `JSON.SET doc $ {"name":"j erry","partner":{"name":"tom"}}`, "JSON.STRLEN doc $..name", }, expected: []interface{}{"OK", []interface{}{float64(5), float64(3)}}, @@ -176,13 +473,12 @@ func TestJsonStrlen(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { for i, cmd := range tc.commands { - result, err := exec.FireCommandAndReadResponse(conn, cmd) - assert.Nil(t, err, "error: %v", err) + result, _ := exec.FireCommandAndReadResponse(conn, cmd) stringResult, ok := result.(string) if ok { assert.Equal(t, tc.expected[i], stringResult) } else { - assert.True(t, arraysArePermutations(tc.expected[i].([]interface{}), result.([]interface{}))) + assert.Assert(t, arraysArePermutations(tc.expected[i].([]interface{}), result.([]interface{}))) } } }) @@ -202,8 +498,7 @@ func TestJsonObjLen(t *testing.T) { d := `["this","is","an","array"]` defer func() { - resp, err := exec.FireCommandAndReadResponse(conn, "DEL obj") - assert.Nil(t, err) + resp, _ := exec.FireCommandAndReadResponse(conn, "DEL obj") assert.Equal(t, float64(1), resp) }() @@ -269,48 +564,49 @@ func TestJsonObjLen(t *testing.T) { }, { name: "JSON.OBJLEN with legacy path - inner existing path", - commands: []string{"json.set obj $ " + c, "json.objlen obj .partner", "json.objlen obj .partner2",}, + commands: []string{"json.set obj $ " + c, "json.objlen obj .partner", "json.objlen obj .partner2"}, expected: []interface{}{"OK", float64(2), float64(2)}, }, { name: "JSON.OBJLEN with legacy path - inner existing path v2", - commands: []string{"json.set obj $ " + c, "json.objlen obj partner", "json.objlen obj partner2",}, + commands: []string{"json.set obj $ " + c, "json.objlen obj partner", "json.objlen obj partner2"}, expected: []interface{}{"OK", float64(2), float64(2)}, }, { name: "JSON.OBJLEN with legacy path - inner non-existent path", - commands: []string{"json.set obj $ " + c, "json.objlen obj .idonotexist",}, + commands: []string{"json.set obj $ " + c, "json.objlen obj .idonotexist"}, expected: []interface{}{"OK", nil}, }, { name: "JSON.OBJLEN with legacy path - inner non-existent path v2", - commands: []string{"json.set obj $ " + c, "json.objlen obj idonotexist",}, + commands: []string{"json.set obj $ " + c, "json.objlen obj idonotexist"}, expected: []interface{}{"OK", nil}, }, { name: "JSON.OBJLEN with legacy path - inner existent path with nonJSON object", - commands: []string{"json.set obj $ " + c, "json.objlen obj .name",}, + commands: []string{"json.set obj $ " + c, "json.objlen obj .name"}, expected: []interface{}{"OK", "WRONGTYPE Operation against a key holding the wrong kind of value"}, }, { name: "JSON.OBJLEN with legacy path - inner existent path recursive object", - commands: []string{"json.set obj $ " + c, "json.objlen obj ..partner",}, + commands: []string{"json.set obj $ " + c, "json.objlen obj ..partner"}, expected: []interface{}{"OK", float64(2)}, }, } - for _, tcase := range testCases { - DeleteKey(t, conn, exec, "obj") - t.Run(tcase.name, func(t *testing.T) { - for i := 0; i < len(tcase.commands); i++ { - cmd := tcase.commands[i] - out := tcase.expected[i] - result, err := exec.FireCommandAndReadResponse(conn, cmd) - assert.Nil(t, err) - assert.Equal(t, out, result) + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + for i, cmd := range tc.commands { + result, _ := exec.FireCommandAndReadResponse(conn, cmd) + if slice, ok := tc.expected[i].([]interface{}); ok { + assert.Assert(t, arraysArePermutations(slice, result.([]interface{}))) + } else { + assert.DeepEqual(t, tc.expected[i], result) + } } }) } + } func arraysArePermutations[T comparable](a, b []T) bool { diff --git a/testutils/json.go b/testutils/json.go index 6f97a661d..fbd0e84db 100644 --- a/testutils/json.go +++ b/testutils/json.go @@ -1,6 +1,7 @@ package testutils import ( + "encoding/json" "reflect" "testing" @@ -56,3 +57,8 @@ func NormalizeJSON(v interface{}) interface{} { return v } } + +func isJSONString(s string) bool { + var js json.RawMessage + return json.Unmarshal([]byte(s), &js) == nil +}