diff --git a/src/modules/sorted_set/commands.go b/src/modules/sorted_set/commands.go index cd19f7c..a24465a 100644 --- a/src/modules/sorted_set/commands.go +++ b/src/modules/sorted_set/commands.go @@ -849,11 +849,12 @@ func handleZRANDMEMBER(ctx context.Context, cmd []string, server utils.Server, c } func handleZRANK(ctx context.Context, cmd []string, server utils.Server, conn *net.Conn) ([]byte, error) { - if len(cmd) < 3 || len(cmd) > 4 { - return nil, errors.New(utils.WRONG_ARGS_RESPONSE) + keys, err := zrankKeyFunc(cmd) + if err != nil { + return nil, err } - key := cmd[1] + key := keys[0] member := cmd[2] withscores := false @@ -862,10 +863,10 @@ func handleZRANK(ctx context.Context, cmd []string, server utils.Server, conn *n } if !server.KeyExists(key) { - return []byte("+(nil)\r\n\r\n"), nil + return []byte("$-1\r\n\r\n"), nil } - if _, err := server.KeyRLock(ctx, key); err != nil { + if _, err = server.KeyRLock(ctx, key); err != nil { return nil, err } defer server.KeyRUnlock(key) @@ -889,12 +890,12 @@ func handleZRANK(ctx context.Context, cmd []string, server utils.Server, conn *n score := strconv.FormatFloat(float64(members[i].score), 'f', -1, 64) return []byte(fmt.Sprintf("*2\r\n:%d\r\n$%d\r\n%s\r\n\r\n", i, len(score), score)), nil } else { - return []byte(fmt.Sprintf(":%d\r\n\r\n", i)), nil + return []byte(fmt.Sprintf("*1\r\n:%d\r\n\r\n", i)), nil } } } - return []byte("+(nil)\r\n\r\n"), nil + return []byte("$-1\r\n\r\n"), nil } func handleZREM(ctx context.Context, cmd []string, server utils.Server, conn *net.Conn) ([]byte, error) { diff --git a/src/modules/sorted_set/commands_test.go b/src/modules/sorted_set/commands_test.go index 587b1c7..04b6d50 100644 --- a/src/modules/sorted_set/commands_test.go +++ b/src/modules/sorted_set/commands_test.go @@ -1659,7 +1659,7 @@ func Test_HandleZSCORE(t *testing.T) { expectedResponse interface{} expectedError error }{ - { // 1. Return score from a set. + { // 1. Return score from a sorted set. preset: true, presetValues: map[string]interface{}{ "key1": NewSortedSet([]MemberParam{ @@ -1906,7 +1906,121 @@ func Test_HandleZRANDMEMBER(t *testing.T) { } } -func Test_HandleZRANK(t *testing.T) {} +func Test_HandleZRANK(t *testing.T) { + mockServer := server.NewServer(server.Opts{}) + + tests := []struct { + preset bool + presetValues map[string]interface{} + command []string + expectedResponse []string + expectedError error + }{ + { // 1. Return element's rank from a sorted set. + preset: true, + presetValues: map[string]interface{}{ + "key1": NewSortedSet([]MemberParam{ + {value: "one", score: 1}, {value: "two", score: 2}, + {value: "three", score: 3}, {value: "four", score: 4}, + {value: "five", score: 5}, + }), + }, + command: []string{"ZRANK", "key1", "four"}, + expectedResponse: []string{"3"}, + expectedError: nil, + }, + { // 2. Return element's rank from a sorted set with its score. + preset: true, + presetValues: map[string]interface{}{ + "key1": NewSortedSet([]MemberParam{ + {value: "one", score: 100.1}, {value: "two", score: 245}, + {value: "three", score: 305.43}, {value: "four", score: 411.055}, + {value: "five", score: 500}, + }), + }, + command: []string{"ZRANK", "key1", "four", "WITHSCORES"}, + expectedResponse: []string{"3", "411.055"}, + expectedError: nil, + }, + { // 3. If key does not exist, return nil value + preset: false, + presetValues: nil, + command: []string{"ZRANK", "key3", "one"}, + expectedResponse: nil, + expectedError: nil, + }, + { // 4. If key exists and is a sorted set, but the member does not exist, return nil + preset: true, + presetValues: map[string]interface{}{ + "key4": NewSortedSet([]MemberParam{ + {value: "one", score: 1.1}, {value: "two", score: 245}, + {value: "three", score: 3}, {value: "four", score: 4.055}, + {value: "five", score: 5}, + }), + }, + command: []string{"ZRANK", "key4", "non-existent"}, + expectedResponse: nil, + expectedError: nil, + }, + { // 5. Throw error when trying to find scores from elements that are not sorted sets + preset: true, + presetValues: map[string]interface{}{"key5": "Default value"}, + command: []string{"ZRANK", "key5", "one"}, + expectedError: errors.New("value at key5 is not a sorted set"), + }, + { // 5. Command too short + preset: false, + command: []string{"ZRANK"}, + expectedError: errors.New(utils.WRONG_ARGS_RESPONSE), + }, + { // 6. Command too long + preset: false, + command: []string{"ZRANK", "key5", "one", "WITHSCORES", "two"}, + expectedError: errors.New(utils.WRONG_ARGS_RESPONSE), + }, + } + + for _, test := range tests { + if test.preset { + for key, value := range test.presetValues { + if _, err := mockServer.CreateKeyAndLock(context.Background(), key); err != nil { + t.Error(err) + } + mockServer.SetValue(context.Background(), key, value) + mockServer.KeyUnlock(key) + } + } + res, err := handleZRANK(context.Background(), test.command, mockServer, nil) + if test.expectedError != nil { + if err.Error() != test.expectedError.Error() { + t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error()) + } + continue + } + if err != nil { + t.Error(err) + } + rd := resp.NewReader(bytes.NewBuffer(res)) + rv, _, err := rd.ReadValue() + if err != nil { + t.Error(err) + } + if test.expectedResponse == nil { + if !rv.IsNull() { + t.Errorf("expected nil response, got %+v", rv) + } + continue + } + if len(rv.Array()) != len(test.expectedResponse) { + t.Errorf("expected response %+v, got %+v", test.expectedResponse, rv.Array()) + } + for i := 0; i < len(test.expectedResponse); i++ { + if rv.Array()[i].String() != test.expectedResponse[i] { + t.Errorf("expected element at index %d to be %s, got %s", i, test.expectedResponse[i], rv.Array()[i].String()) + } + } + } +} func Test_HandleZREM(t *testing.T) {} diff --git a/src/modules/sorted_set/key_funcs.go b/src/modules/sorted_set/key_funcs.go index d8162fd..1e126e8 100644 --- a/src/modules/sorted_set/key_funcs.go +++ b/src/modules/sorted_set/key_funcs.go @@ -138,7 +138,7 @@ func zrandmemberKeyFunc(cmd []string) ([]string, error) { } func zrankKeyFunc(cmd []string) ([]string, error) { - if len(cmd) < 3 { + if len(cmd) < 3 || len(cmd) > 4 { return nil, errors.New(utils.WRONG_ARGS_RESPONSE) } return cmd[1:2], nil