mirror of
https://github.com/EchoVault/SugarDB.git
synced 2025-10-04 23:52:42 +08:00
1536 lines
50 KiB
Go
1536 lines
50 KiB
Go
// Copyright 2024 Kelvin Clement Mwinuka
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package hash_test
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"github.com/echovault/echovault/echovault"
|
|
"github.com/echovault/echovault/internal"
|
|
"github.com/echovault/echovault/internal/config"
|
|
"github.com/echovault/echovault/internal/constants"
|
|
"github.com/tidwall/resp"
|
|
"net"
|
|
"reflect"
|
|
"slices"
|
|
"strings"
|
|
"testing"
|
|
"unsafe"
|
|
)
|
|
|
|
var mockServer *echovault.EchoVault
|
|
|
|
func init() {
|
|
mockServer, _ = echovault.NewEchoVault(
|
|
echovault.WithConfig(config.Config{
|
|
DataDir: "",
|
|
EvictionPolicy: constants.NoEviction,
|
|
}),
|
|
)
|
|
}
|
|
|
|
func getUnexportedField(field reflect.Value) interface{} {
|
|
return reflect.NewAt(field.Type(), unsafe.Pointer(field.UnsafeAddr())).Elem().Interface()
|
|
}
|
|
|
|
func getHandler(commands ...string) internal.HandlerFunc {
|
|
if len(commands) == 0 {
|
|
return nil
|
|
}
|
|
getCommands :=
|
|
getUnexportedField(reflect.ValueOf(mockServer).Elem().FieldByName("getCommands")).(func() []internal.Command)
|
|
for _, c := range getCommands() {
|
|
if strings.EqualFold(commands[0], c.Command) && len(commands) == 1 {
|
|
// Get command handler
|
|
return c.HandlerFunc
|
|
}
|
|
if strings.EqualFold(commands[0], c.Command) {
|
|
// Get sub-command handler
|
|
for _, sc := range c.SubCommands {
|
|
if strings.EqualFold(commands[1], sc.Command) {
|
|
return sc.HandlerFunc
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func getHandlerFuncParams(ctx context.Context, cmd []string, conn *net.Conn) internal.HandlerFuncParams {
|
|
return internal.HandlerFuncParams{
|
|
Context: ctx,
|
|
Command: cmd,
|
|
Connection: conn,
|
|
KeyExists: mockServer.KeyExists,
|
|
CreateKeyAndLock: mockServer.CreateKeyAndLock,
|
|
KeyLock: mockServer.KeyLock,
|
|
KeyRLock: mockServer.KeyRLock,
|
|
KeyUnlock: mockServer.KeyUnlock,
|
|
KeyRUnlock: mockServer.KeyRUnlock,
|
|
GetValue: mockServer.GetValue,
|
|
SetValue: mockServer.SetValue,
|
|
}
|
|
}
|
|
|
|
func Test_HandleHSET(t *testing.T) {
|
|
// Tests for both HSet and HSetNX
|
|
tests := []struct {
|
|
name string
|
|
preset bool
|
|
key string
|
|
presetValue interface{}
|
|
command []string
|
|
expectedResponse int // Change count
|
|
expectedValue map[string]interface{}
|
|
expectedError error
|
|
}{
|
|
{
|
|
name: "1. HSETNX set field on non-existent hash map",
|
|
preset: false,
|
|
key: "HsetKey1",
|
|
presetValue: map[string]interface{}{},
|
|
command: []string{"HSETNX", "HsetKey1", "field1", "value1"},
|
|
expectedResponse: 1,
|
|
expectedValue: map[string]interface{}{"field1": "value1"},
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
name: "2. HSETNX set field on existing hash map",
|
|
preset: true,
|
|
key: "HsetKey2",
|
|
presetValue: map[string]interface{}{"field1": "value1"},
|
|
command: []string{"HSETNX", "HsetKey2", "field2", "value2"},
|
|
expectedResponse: 1,
|
|
expectedValue: map[string]interface{}{"field1": "value1", "field2": "value2"},
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
name: "3. HSETNX skips operation when setting on existing field",
|
|
preset: true,
|
|
key: "HsetKey3",
|
|
presetValue: map[string]interface{}{"field1": "value1"},
|
|
command: []string{"HSETNX", "HsetKey3", "field1", "value1-new"},
|
|
expectedResponse: 0,
|
|
expectedValue: map[string]interface{}{"field1": "value1"},
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
name: "4. Regular HSET command on non-existent hash map",
|
|
preset: false,
|
|
key: "HsetKey4",
|
|
presetValue: map[string]interface{}{},
|
|
command: []string{"HSET", "HsetKey4", "field1", "value1", "field2", "value2"},
|
|
expectedResponse: 2,
|
|
expectedValue: map[string]interface{}{"field1": "value1", "field2": "value2"},
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
name: "5. Regular HSET update on existing hash map",
|
|
preset: true,
|
|
key: "HsetKey5",
|
|
presetValue: map[string]interface{}{"field1": "value1", "field2": "value2"},
|
|
command: []string{"HSET", "HsetKey5", "field1", "value1-new", "field2", "value2-ne2", "field3", "value3"},
|
|
expectedResponse: 3,
|
|
expectedValue: map[string]interface{}{"field1": "value1-new", "field2": "value2-ne2", "field3": "value3"},
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
name: "6. HSET returns error when the target key is not a map",
|
|
preset: true,
|
|
key: "HsetKey6",
|
|
presetValue: "Default preset value",
|
|
command: []string{"HSET", "HsetKey6", "field1", "value1"},
|
|
expectedResponse: 0,
|
|
expectedValue: map[string]interface{}{},
|
|
expectedError: errors.New("value at HsetKey6 is not a hash"),
|
|
},
|
|
{
|
|
name: "7. HSET returns error when there's a mismatch in key/values",
|
|
preset: false,
|
|
key: "HsetKey7",
|
|
presetValue: nil,
|
|
command: []string{"HSET", "HsetKey7", "field1", "value1", "field2"},
|
|
expectedResponse: 0,
|
|
expectedValue: map[string]interface{}{},
|
|
expectedError: errors.New("each field must have a corresponding value"),
|
|
},
|
|
{
|
|
name: "8. Command too short",
|
|
preset: true,
|
|
key: "HsetKey8",
|
|
presetValue: nil,
|
|
command: []string{"HSET", "field1"},
|
|
expectedResponse: 0,
|
|
expectedValue: map[string]interface{}{},
|
|
expectedError: errors.New(constants.WrongArgsResponse),
|
|
},
|
|
}
|
|
|
|
for i, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
ctx := context.WithValue(context.Background(), "test_name", fmt.Sprintf("HSET/HSETNX, %d", i))
|
|
if test.preset {
|
|
if _, err := mockServer.CreateKeyAndLock(ctx, test.key); err != nil {
|
|
t.Error(err)
|
|
}
|
|
if err := mockServer.SetValue(ctx, test.key, test.presetValue); err != nil {
|
|
t.Error(err)
|
|
}
|
|
mockServer.KeyUnlock(ctx, test.key)
|
|
}
|
|
|
|
handler := getHandler(test.command[0])
|
|
if handler == nil {
|
|
t.Errorf("no handler found for command %s", test.command[0])
|
|
return
|
|
}
|
|
|
|
res, err := handler(getHandlerFuncParams(ctx, test.command, nil))
|
|
if test.expectedError != nil {
|
|
if err.Error() != test.expectedError.Error() {
|
|
t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error())
|
|
}
|
|
return
|
|
}
|
|
rd := resp.NewReader(bytes.NewBuffer(res))
|
|
rv, _, err := rd.ReadValue()
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
if rv.Integer() != test.expectedResponse {
|
|
t.Errorf("expected response \"%d\", got \"%d\"", test.expectedResponse, rv.Integer())
|
|
}
|
|
// Check that all the values are what is expected
|
|
if _, err = mockServer.KeyRLock(ctx, test.key); err != nil {
|
|
t.Error(err)
|
|
}
|
|
h, ok := mockServer.GetValue(ctx, test.key).(map[string]interface{})
|
|
if !ok {
|
|
t.Errorf("value at key \"%s\" is not a hash map", test.key)
|
|
}
|
|
for field, value := range h {
|
|
if value != test.expectedValue[field] {
|
|
t.Errorf("expected value \"%+v\" for field \"%+v\", got \"%+v\"", test.expectedValue[field], field, value)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_HandleHINCRBY(t *testing.T) {
|
|
// Tests for both HIncrBy and HIncrByFloat
|
|
tests := []struct {
|
|
name string
|
|
preset bool
|
|
key string
|
|
presetValue interface{}
|
|
command []string
|
|
expectedResponse interface{} // Change count
|
|
expectedValue map[string]interface{}
|
|
expectedError error
|
|
}{
|
|
{
|
|
name: "1. Increment by integer on non-existent hash should create a new one",
|
|
preset: false,
|
|
key: "HincrbyKey1",
|
|
presetValue: nil,
|
|
command: []string{"HINCRBY", "HincrbyKey1", "field1", "1"},
|
|
expectedResponse: 1,
|
|
expectedValue: map[string]interface{}{"field1": 1},
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
name: "2. Increment by float on non-existent hash should create one",
|
|
preset: false,
|
|
key: "HincrbyKey2",
|
|
presetValue: nil,
|
|
command: []string{"HINCRBYFLOAT", "HincrbyKey2", "field1", "3.142"},
|
|
expectedResponse: 3.142,
|
|
expectedValue: map[string]interface{}{"field1": 3.142},
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
name: "3. Increment by integer on existing hash",
|
|
preset: true,
|
|
key: "HincrbyKey3",
|
|
presetValue: map[string]interface{}{"field1": 1},
|
|
command: []string{"HINCRBY", "HincrbyKey3", "field1", "10"},
|
|
expectedResponse: 11,
|
|
expectedValue: map[string]interface{}{"field1": 11},
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
name: "4. Increment by float on an existing hash",
|
|
preset: true,
|
|
key: "HincrbyKey4",
|
|
presetValue: map[string]interface{}{"field1": 3.142},
|
|
command: []string{"HINCRBYFLOAT", "HincrbyKey4", "field1", "3.142"},
|
|
expectedResponse: 6.284,
|
|
expectedValue: map[string]interface{}{"field1": 6.284},
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
name: "5. Command too short",
|
|
preset: false,
|
|
key: "HincrbyKey5",
|
|
presetValue: nil,
|
|
command: []string{"HINCRBY", "HincrbyKey5"},
|
|
expectedResponse: 0,
|
|
expectedValue: map[string]interface{}{},
|
|
expectedError: errors.New(constants.WrongArgsResponse),
|
|
},
|
|
{
|
|
name: "6. Command too long",
|
|
preset: false,
|
|
key: "HincrbyKey6",
|
|
presetValue: nil,
|
|
command: []string{"HINCRBY", "HincrbyKey6", "field1", "23", "45"},
|
|
expectedResponse: 0,
|
|
expectedValue: map[string]interface{}{},
|
|
expectedError: errors.New(constants.WrongArgsResponse),
|
|
},
|
|
{
|
|
name: "7. Error when increment by float does not pass valid float",
|
|
preset: false,
|
|
key: "HincrbyKey7",
|
|
presetValue: nil,
|
|
command: []string{"HINCRBYFLOAT", "HincrbyKey7", "field1", "three point one four two"},
|
|
expectedResponse: 0,
|
|
expectedValue: map[string]interface{}{},
|
|
expectedError: errors.New("increment must be a float"),
|
|
},
|
|
{
|
|
name: "8. Error when increment does not pass valid integer",
|
|
preset: false,
|
|
key: "HincrbyKey8",
|
|
presetValue: nil,
|
|
command: []string{"HINCRBY", "HincrbyKey8", "field1", "three"},
|
|
expectedResponse: 0,
|
|
expectedValue: map[string]interface{}{},
|
|
expectedError: errors.New("increment must be an integer"),
|
|
},
|
|
{
|
|
name: "9. Error when trying to increment on a key that is not a hash",
|
|
preset: true,
|
|
key: "HincrbyKey9",
|
|
presetValue: "Default value",
|
|
command: []string{"HINCRBY", "HincrbyKey9", "field1", "3"},
|
|
expectedResponse: 0,
|
|
expectedValue: map[string]interface{}{},
|
|
expectedError: errors.New("value at HincrbyKey9 is not a hash"),
|
|
},
|
|
{
|
|
name: "10. Error when trying to increment a hash field that is not a number",
|
|
preset: true,
|
|
key: "HincrbyKey10",
|
|
presetValue: map[string]interface{}{"field1": "value1"},
|
|
command: []string{"HINCRBY", "HincrbyKey10", "field1", "3"},
|
|
expectedResponse: 0,
|
|
expectedValue: map[string]interface{}{},
|
|
expectedError: errors.New("value at field field1 is not a number"),
|
|
},
|
|
}
|
|
|
|
for i, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
ctx := context.WithValue(context.Background(), "test_name", fmt.Sprintf("HINCRBY, %d", i))
|
|
|
|
if test.preset {
|
|
if _, err := mockServer.CreateKeyAndLock(ctx, test.key); err != nil {
|
|
t.Error(err)
|
|
}
|
|
if err := mockServer.SetValue(ctx, test.key, test.presetValue); err != nil {
|
|
t.Error(err)
|
|
}
|
|
mockServer.KeyUnlock(ctx, test.key)
|
|
}
|
|
|
|
handler := getHandler(test.command[0])
|
|
if handler == nil {
|
|
t.Errorf("no handler found for command %s", test.command[0])
|
|
return
|
|
}
|
|
|
|
res, err := handler(getHandlerFuncParams(ctx, test.command, nil))
|
|
if test.expectedError != nil {
|
|
if err.Error() != test.expectedError.Error() {
|
|
t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error())
|
|
}
|
|
return
|
|
}
|
|
rd := resp.NewReader(bytes.NewBuffer(res))
|
|
rv, _, err := rd.ReadValue()
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
switch test.expectedResponse.(type) {
|
|
default:
|
|
t.Error("expectedResponse must be an integer or string")
|
|
case int:
|
|
if rv.Integer() != test.expectedResponse {
|
|
t.Errorf("expected response \"%+v\", got \"%d\"", test.expectedResponse, rv.Integer())
|
|
}
|
|
case float64:
|
|
if rv.Float() != test.expectedResponse {
|
|
t.Errorf("expected response \"%+v\", got \"%+v\"", test.expectedResponse, rv.Float())
|
|
}
|
|
}
|
|
// Check that all the values are what is expected
|
|
if _, err = mockServer.KeyRLock(ctx, test.key); err != nil {
|
|
t.Error(err)
|
|
}
|
|
h, ok := mockServer.GetValue(ctx, test.key).(map[string]interface{})
|
|
if !ok {
|
|
t.Errorf("value at key \"%s\" is not a hash map", test.key)
|
|
}
|
|
for field, value := range h {
|
|
if value != test.expectedValue[field] {
|
|
t.Errorf("expected value \"%+v\" for field \"%+v\", got \"%+v\"", test.expectedValue[field], field, value)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_HandleHGET(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
preset bool
|
|
key string
|
|
presetValue interface{}
|
|
command []string
|
|
expectedResponse interface{} // Change count
|
|
expectedValue map[string]interface{}
|
|
expectedError error
|
|
}{
|
|
{
|
|
name: "1. Return nil when attempting to get from non-existed key",
|
|
preset: true,
|
|
key: "HgetKey1",
|
|
presetValue: map[string]interface{}{"field1": "value1", "field2": 365, "field3": 3.142},
|
|
command: []string{"HGET", "HgetKey1", "field1", "field2", "field3", "field4"},
|
|
expectedResponse: []interface{}{"value1", 365, "3.142", nil},
|
|
expectedValue: map[string]interface{}{},
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
name: "2. Return nil when attempting to get from non-existed key",
|
|
preset: false,
|
|
key: "HgetKey2",
|
|
presetValue: map[string]interface{}{},
|
|
command: []string{"HGET", "HgetKey2", "field1"},
|
|
expectedResponse: nil,
|
|
expectedValue: map[string]interface{}{},
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
name: "3. Error when trying to get from a value that is not a hash map",
|
|
preset: true,
|
|
key: "HgetKey3",
|
|
presetValue: "Default Value",
|
|
command: []string{"HGET", "HgetKey3", "field1"},
|
|
expectedResponse: 0,
|
|
expectedValue: map[string]interface{}{},
|
|
expectedError: errors.New("value at HgetKey3 is not a hash"),
|
|
},
|
|
{
|
|
name: "4. Command too short",
|
|
preset: false,
|
|
key: "HgetKey4",
|
|
presetValue: map[string]interface{}{},
|
|
command: []string{"HGET", "HgetKey4"},
|
|
expectedResponse: 0,
|
|
expectedValue: map[string]interface{}{},
|
|
expectedError: errors.New(constants.WrongArgsResponse),
|
|
},
|
|
}
|
|
|
|
for i, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
ctx := context.WithValue(context.Background(), "test_name", fmt.Sprintf("HINCRBY, %d", i))
|
|
|
|
if test.preset {
|
|
if _, err := mockServer.CreateKeyAndLock(ctx, test.key); err != nil {
|
|
t.Error(err)
|
|
}
|
|
if err := mockServer.SetValue(ctx, test.key, test.presetValue); err != nil {
|
|
t.Error(err)
|
|
}
|
|
mockServer.KeyUnlock(ctx, test.key)
|
|
}
|
|
|
|
handler := getHandler(test.command[0])
|
|
if handler == nil {
|
|
t.Errorf("no handler found for command %s", test.command[0])
|
|
return
|
|
}
|
|
|
|
res, err := handler(getHandlerFuncParams(ctx, test.command, nil))
|
|
if test.expectedError != nil {
|
|
if err.Error() != test.expectedError.Error() {
|
|
t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error())
|
|
}
|
|
return
|
|
}
|
|
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)
|
|
}
|
|
return
|
|
}
|
|
if expectedArr, ok := test.expectedResponse.([]interface{}); ok {
|
|
for i, v := range rv.Array() {
|
|
switch v.Type().String() {
|
|
default:
|
|
t.Error("unexpected type encountered")
|
|
case "Integer":
|
|
if v.Integer() != expectedArr[i] {
|
|
t.Errorf("expected \"%+v\", got \"%d\"", expectedArr[i], v.Integer())
|
|
}
|
|
case "BulkString":
|
|
if len(v.String()) == 0 && expectedArr[i] == nil {
|
|
continue
|
|
}
|
|
if v.String() != expectedArr[i] {
|
|
t.Errorf("expected \"%+v\", got \"%s\"", expectedArr[i], v.String())
|
|
}
|
|
}
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_HandleHSTRLEN(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
preset bool
|
|
key string
|
|
presetValue interface{}
|
|
command []string
|
|
expectedResponse interface{} // Change count
|
|
expectedValue map[string]interface{}
|
|
expectedError error
|
|
}{
|
|
{
|
|
// Return lengths of field values.
|
|
// If the key does not exist, its length should be 0.
|
|
name: "1. Return lengths of field values.",
|
|
preset: true,
|
|
key: "HstrlenKey1",
|
|
presetValue: map[string]interface{}{"field1": "value1", "field2": 123456789, "field3": 3.142},
|
|
command: []string{"HSTRLEN", "HstrlenKey1", "field1", "field2", "field3", "field4"},
|
|
expectedResponse: []int{len("value1"), len("123456789"), len("3.142"), 0},
|
|
expectedValue: map[string]interface{}{},
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
name: "2. Nil response when trying to get HSTRLEN non-existent key",
|
|
preset: false,
|
|
key: "HstrlenKey2",
|
|
presetValue: map[string]interface{}{},
|
|
command: []string{"HSTRLEN", "HstrlenKey2", "field1"},
|
|
expectedResponse: nil,
|
|
expectedValue: map[string]interface{}{},
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
name: "3. Command too short",
|
|
preset: false,
|
|
key: "HstrlenKey3",
|
|
presetValue: map[string]interface{}{},
|
|
command: []string{"HSTRLEN", "HstrlenKey3"},
|
|
expectedResponse: 0,
|
|
expectedValue: map[string]interface{}{},
|
|
expectedError: errors.New(constants.WrongArgsResponse),
|
|
},
|
|
{
|
|
name: "4. Trying to get lengths on a non hash map returns error",
|
|
preset: true,
|
|
key: "HstrlenKey4",
|
|
presetValue: "Default value",
|
|
command: []string{"HSTRLEN", "HstrlenKey4", "field1"},
|
|
expectedResponse: 0,
|
|
expectedValue: map[string]interface{}{},
|
|
expectedError: errors.New("value at HstrlenKey4 is not a hash"),
|
|
},
|
|
}
|
|
|
|
for i, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
ctx := context.WithValue(context.Background(), "test_name", fmt.Sprintf("HSTRLEN, %d", i))
|
|
|
|
if test.preset {
|
|
if _, err := mockServer.CreateKeyAndLock(ctx, test.key); err != nil {
|
|
t.Error(err)
|
|
}
|
|
if err := mockServer.SetValue(ctx, test.key, test.presetValue); err != nil {
|
|
t.Error(err)
|
|
}
|
|
mockServer.KeyUnlock(ctx, test.key)
|
|
}
|
|
|
|
handler := getHandler(test.command[0])
|
|
if handler == nil {
|
|
t.Errorf("no handler found for command %s", test.command[0])
|
|
return
|
|
}
|
|
|
|
res, err := handler(getHandlerFuncParams(ctx, test.command, nil))
|
|
if test.expectedError != nil {
|
|
if err.Error() != test.expectedError.Error() {
|
|
t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error())
|
|
}
|
|
return
|
|
}
|
|
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)
|
|
}
|
|
return
|
|
}
|
|
expectedResponse, _ := test.expectedResponse.([]int)
|
|
for i, v := range rv.Array() {
|
|
if v.Integer() != expectedResponse[i] {
|
|
t.Errorf("expected \"%d\", got \"%d\"", expectedResponse[i], v.Integer())
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_HandleHVALS(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
preset bool
|
|
key string
|
|
presetValue interface{}
|
|
command []string
|
|
expectedResponse []interface{}
|
|
expectedValue map[string]interface{}
|
|
expectedError error
|
|
}{
|
|
{
|
|
name: "1. Return all the values from a hash",
|
|
preset: true,
|
|
key: "HvalsKey1",
|
|
presetValue: map[string]interface{}{"field1": "value1", "field2": 123456789, "field3": 3.142},
|
|
command: []string{"HVALS", "HvalsKey1"},
|
|
expectedResponse: []interface{}{"value1", 123456789, "3.142"},
|
|
expectedValue: map[string]interface{}{},
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
name: "2. Empty array response when trying to get HSTRLEN non-existent key",
|
|
preset: false,
|
|
key: "HvalsKey2",
|
|
presetValue: map[string]interface{}{},
|
|
command: []string{"HVALS", "HvalsKey2"},
|
|
expectedResponse: []interface{}{},
|
|
expectedValue: map[string]interface{}{},
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
name: "3. Command too short",
|
|
preset: false,
|
|
key: "HvalsKey3",
|
|
presetValue: map[string]interface{}{},
|
|
command: []string{"HVALS"},
|
|
expectedResponse: nil,
|
|
expectedValue: map[string]interface{}{},
|
|
expectedError: errors.New(constants.WrongArgsResponse),
|
|
},
|
|
{
|
|
name: "4. Command too long",
|
|
preset: false,
|
|
key: "HvalsKey4",
|
|
presetValue: map[string]interface{}{},
|
|
command: []string{"HVALS", "HvalsKey4", "HvalsKey4"},
|
|
expectedResponse: nil,
|
|
expectedValue: map[string]interface{}{},
|
|
expectedError: errors.New(constants.WrongArgsResponse),
|
|
},
|
|
{
|
|
name: "5. Trying to get lengths on a non hash map returns error",
|
|
preset: true,
|
|
key: "HvalsKey5",
|
|
presetValue: "Default value",
|
|
command: []string{"HVALS", "HvalsKey5"},
|
|
expectedResponse: nil,
|
|
expectedValue: map[string]interface{}{},
|
|
expectedError: errors.New("value at HvalsKey5 is not a hash"),
|
|
},
|
|
}
|
|
|
|
for i, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
ctx := context.WithValue(context.Background(), "test_name", fmt.Sprintf("HVALS, %d", i))
|
|
|
|
if test.preset {
|
|
if _, err := mockServer.CreateKeyAndLock(ctx, test.key); err != nil {
|
|
t.Error(err)
|
|
}
|
|
if err := mockServer.SetValue(ctx, test.key, test.presetValue); err != nil {
|
|
t.Error(err)
|
|
}
|
|
mockServer.KeyUnlock(ctx, test.key)
|
|
}
|
|
|
|
handler := getHandler(test.command[0])
|
|
if handler == nil {
|
|
t.Errorf("no handler found for command %s", test.command[0])
|
|
return
|
|
}
|
|
|
|
res, err := handler(getHandlerFuncParams(ctx, test.command, nil))
|
|
if test.expectedError != nil {
|
|
if err.Error() != test.expectedError.Error() {
|
|
t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error())
|
|
}
|
|
return
|
|
}
|
|
rd := resp.NewReader(bytes.NewBuffer(res))
|
|
rv, _, err := rd.ReadValue()
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
switch len(test.expectedResponse) {
|
|
case 0:
|
|
if len(rv.Array()) != 0 {
|
|
t.Errorf("expected empty array, got length \"%d\"", len(rv.Array()))
|
|
}
|
|
default:
|
|
for _, v := range rv.Array() {
|
|
switch v.Type().String() {
|
|
default:
|
|
t.Errorf("unexpected error type")
|
|
case "Integer":
|
|
// Value is an integer, check if it is contained in the expected response
|
|
if !slices.ContainsFunc(test.expectedResponse, func(e interface{}) bool {
|
|
expectedValue, ok := e.(int)
|
|
return ok && expectedValue == v.Integer()
|
|
}) {
|
|
t.Errorf("couldn't find response value \"%d\" in expected values", v.Integer())
|
|
}
|
|
case "BulkString":
|
|
// Value is a string, check if it is contained in the expected response
|
|
if !slices.ContainsFunc(test.expectedResponse, func(e interface{}) bool {
|
|
expectedValue, ok := e.(string)
|
|
return ok && expectedValue == v.String()
|
|
}) {
|
|
t.Errorf("couldn't find response value \"%s\" in expected values", v.String())
|
|
}
|
|
}
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_HandleHRANDFIELD(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
preset bool
|
|
key string
|
|
presetValue interface{}
|
|
command []string
|
|
withValues bool
|
|
expectedCount int
|
|
expectedResponse []string
|
|
expectedError error
|
|
}{
|
|
{
|
|
name: "1. Get a random field",
|
|
preset: true,
|
|
key: "HrandfieldKey1",
|
|
presetValue: map[string]interface{}{"field1": "value1", "field2": 123456789, "field3": 3.142},
|
|
command: []string{"HRANDFIELD", "HrandfieldKey1"},
|
|
withValues: false,
|
|
expectedCount: 1,
|
|
expectedResponse: []string{"field1", "field2", "field3"},
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
name: "2. Get a random field with a value",
|
|
preset: true,
|
|
key: "HrandfieldKey2",
|
|
presetValue: map[string]interface{}{"field1": "value1", "field2": 123456789, "field3": 3.142},
|
|
command: []string{"HRANDFIELD", "HrandfieldKey2", "1", "WITHVALUES"},
|
|
withValues: true,
|
|
expectedCount: 2,
|
|
expectedResponse: []string{"field1", "value1", "field2", "123456789", "field3", "3.142"},
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
name: "3. Get several random fields",
|
|
preset: true,
|
|
key: "HrandfieldKey3",
|
|
presetValue: map[string]interface{}{
|
|
"field1": "value1",
|
|
"field2": 123456789,
|
|
"field3": 3.142,
|
|
"field4": "value4",
|
|
"field5": "value5",
|
|
},
|
|
command: []string{"HRANDFIELD", "HrandfieldKey3", "3"},
|
|
withValues: false,
|
|
expectedCount: 3,
|
|
expectedResponse: []string{"field1", "field2", "field3", "field4", "field5"},
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
name: "4. Get several random fields with their corresponding values",
|
|
preset: true,
|
|
key: "HrandfieldKey4",
|
|
presetValue: map[string]interface{}{
|
|
"field1": "value1",
|
|
"field2": 123456789,
|
|
"field3": 3.142,
|
|
"field4": "value4",
|
|
"field5": "value5",
|
|
},
|
|
command: []string{"HRANDFIELD", "HrandfieldKey4", "3", "WITHVALUES"},
|
|
withValues: true,
|
|
expectedCount: 6,
|
|
expectedResponse: []string{
|
|
"field1", "value1", "field2", "123456789", "field3",
|
|
"3.142", "field4", "value4", "field5", "value5",
|
|
},
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
name: "5. Get the entire hash",
|
|
preset: true,
|
|
key: "HrandfieldKey5",
|
|
presetValue: map[string]interface{}{
|
|
"field1": "value1",
|
|
"field2": 123456789,
|
|
"field3": 3.142,
|
|
"field4": "value4",
|
|
"field5": "value5",
|
|
},
|
|
command: []string{"HRANDFIELD", "HrandfieldKey5", "5"},
|
|
withValues: false,
|
|
expectedCount: 5,
|
|
expectedResponse: []string{"field1", "field2", "field3", "field4", "field5"},
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
name: "6. Get the entire hash with values",
|
|
preset: true,
|
|
key: "HrandfieldKey5",
|
|
presetValue: map[string]interface{}{
|
|
"field1": "value1",
|
|
"field2": 123456789,
|
|
"field3": 3.142,
|
|
"field4": "value4",
|
|
"field5": "value5",
|
|
},
|
|
command: []string{"HRANDFIELD", "HrandfieldKey5", "5", "WITHVALUES"},
|
|
withValues: true,
|
|
expectedCount: 10,
|
|
expectedResponse: []string{
|
|
"field1", "value1", "field2", "123456789", "field3",
|
|
"3.142", "field4", "value4", "field5", "value5",
|
|
},
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
name: "7. Command too short",
|
|
preset: false,
|
|
key: "HrandfieldKey10",
|
|
presetValue: map[string]interface{}{},
|
|
command: []string{"HRANDFIELD"},
|
|
expectedError: errors.New(constants.WrongArgsResponse),
|
|
},
|
|
{
|
|
name: "8. Command too long",
|
|
preset: false,
|
|
key: "HrandfieldKey11",
|
|
presetValue: map[string]interface{}{},
|
|
command: []string{"HRANDFIELD", "HrandfieldKey11", "HrandfieldKey11", "HrandfieldKey11", "HrandfieldKey11"},
|
|
expectedError: errors.New(constants.WrongArgsResponse),
|
|
},
|
|
{
|
|
name: "9. Trying to get random field on a non hash map returns error",
|
|
preset: true,
|
|
key: "HrandfieldKey12",
|
|
presetValue: "Default value",
|
|
command: []string{"HRANDFIELD", "HrandfieldKey12"},
|
|
expectedError: errors.New("value at HrandfieldKey12 is not a hash"),
|
|
},
|
|
{
|
|
name: "10. Throw error when count provided is not an integer",
|
|
preset: true,
|
|
key: "HrandfieldKey12",
|
|
presetValue: "Default value",
|
|
command: []string{"HRANDFIELD", "HrandfieldKey12", "COUNT"},
|
|
expectedError: errors.New("count must be an integer"),
|
|
},
|
|
{
|
|
name: "11. If fourth argument is provided, it must be \"WITHVALUES\"",
|
|
preset: true,
|
|
key: "HrandfieldKey12",
|
|
presetValue: "Default value",
|
|
command: []string{"HRANDFIELD", "HrandfieldKey12", "10", "FLAG"},
|
|
expectedError: errors.New("result modifier must be withvalues"),
|
|
},
|
|
}
|
|
|
|
for i, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
ctx := context.WithValue(context.Background(), "test_name", fmt.Sprintf("HRANDFIELD, %d", i))
|
|
|
|
if test.preset {
|
|
if _, err := mockServer.CreateKeyAndLock(ctx, test.key); err != nil {
|
|
t.Error(err)
|
|
}
|
|
if err := mockServer.SetValue(ctx, test.key, test.presetValue); err != nil {
|
|
t.Error(err)
|
|
}
|
|
mockServer.KeyUnlock(ctx, test.key)
|
|
}
|
|
|
|
handler := getHandler(test.command[0])
|
|
if handler == nil {
|
|
t.Errorf("no handler found for command %s", test.command[0])
|
|
return
|
|
}
|
|
|
|
res, err := handler(getHandlerFuncParams(ctx, test.command, nil))
|
|
if test.expectedError != nil {
|
|
if err.Error() != test.expectedError.Error() {
|
|
t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error())
|
|
}
|
|
return
|
|
}
|
|
rd := resp.NewReader(bytes.NewBuffer(res))
|
|
rv, _, err := rd.ReadValue()
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
if len(rv.Array()) != test.expectedCount {
|
|
t.Errorf("expected response array of length \"%d\", got length \"%d\"", test.expectedCount, len(rv.Array()))
|
|
}
|
|
switch test.withValues {
|
|
case false:
|
|
for _, v := range rv.Array() {
|
|
if !slices.ContainsFunc(test.expectedResponse, func(expected string) bool {
|
|
return expected == v.String()
|
|
}) {
|
|
t.Errorf("could not find response element \"%s\" in expected response", v.String())
|
|
}
|
|
}
|
|
case true:
|
|
responseArray := rv.Array()
|
|
for i := 0; i < len(responseArray); i++ {
|
|
if i%2 == 0 {
|
|
field := responseArray[i].String()
|
|
value := responseArray[i+1].String()
|
|
|
|
expectedFieldIndex := slices.Index(test.expectedResponse, field)
|
|
if expectedFieldIndex == -1 {
|
|
t.Errorf("could not find response value \"%s\" in expected values", field)
|
|
}
|
|
expectedValue := test.expectedResponse[expectedFieldIndex+1]
|
|
|
|
if value != expectedValue {
|
|
t.Errorf("expected value \"%s\", got \"%s\"", expectedValue, value)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_HandleHLEN(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
preset bool
|
|
key string
|
|
presetValue interface{}
|
|
command []string
|
|
expectedResponse interface{} // Change count
|
|
expectedValue map[string]interface{}
|
|
expectedError error
|
|
}{
|
|
{
|
|
name: "1. Return the correct length of the hash",
|
|
preset: true,
|
|
key: "HlenKey1",
|
|
presetValue: map[string]interface{}{"field1": "value1", "field2": 123456789, "field3": 3.142},
|
|
command: []string{"HLEN", "HlenKey1"},
|
|
expectedResponse: 3,
|
|
expectedValue: map[string]interface{}{},
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
name: "2. 0 response when trying to call HLEN on non-existent key",
|
|
preset: false,
|
|
key: "HlenKey2",
|
|
presetValue: map[string]interface{}{},
|
|
command: []string{"HLEN", "HlenKey2"},
|
|
expectedResponse: 0,
|
|
expectedValue: map[string]interface{}{},
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
name: "3. Command too short",
|
|
preset: false,
|
|
key: "HlenKey3",
|
|
presetValue: map[string]interface{}{},
|
|
command: []string{"HLEN"},
|
|
expectedResponse: 0,
|
|
expectedValue: map[string]interface{}{},
|
|
expectedError: errors.New(constants.WrongArgsResponse),
|
|
},
|
|
{
|
|
name: "4. Command too long",
|
|
preset: false,
|
|
key: "HlenKey4",
|
|
presetValue: map[string]interface{}{},
|
|
command: []string{"HLEN", "HlenKey4", "HlenKey4"},
|
|
expectedResponse: 0,
|
|
expectedValue: map[string]interface{}{},
|
|
expectedError: errors.New(constants.WrongArgsResponse),
|
|
},
|
|
{
|
|
name: "5. Trying to get lengths on a non hash map returns error",
|
|
preset: true,
|
|
key: "HlenKey5",
|
|
presetValue: "Default value",
|
|
command: []string{"HLEN", "HlenKey5"},
|
|
expectedResponse: 0,
|
|
expectedValue: map[string]interface{}{},
|
|
expectedError: errors.New("value at HlenKey5 is not a hash"),
|
|
},
|
|
}
|
|
|
|
for i, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
ctx := context.WithValue(context.Background(), "test_name", fmt.Sprintf("HLEN, %d", i))
|
|
|
|
if test.preset {
|
|
if _, err := mockServer.CreateKeyAndLock(ctx, test.key); err != nil {
|
|
t.Error(err)
|
|
}
|
|
if err := mockServer.SetValue(ctx, test.key, test.presetValue); err != nil {
|
|
t.Error(err)
|
|
}
|
|
mockServer.KeyUnlock(ctx, test.key)
|
|
}
|
|
|
|
handler := getHandler(test.command[0])
|
|
if handler == nil {
|
|
t.Errorf("no handler found for command %s", test.command[0])
|
|
return
|
|
}
|
|
|
|
res, err := handler(getHandlerFuncParams(ctx, test.command, nil))
|
|
if test.expectedError != nil {
|
|
if err.Error() != test.expectedError.Error() {
|
|
t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error())
|
|
}
|
|
return
|
|
}
|
|
rd := resp.NewReader(bytes.NewBuffer(res))
|
|
rv, _, err := rd.ReadValue()
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
if expectedResponse, ok := test.expectedResponse.(int); ok {
|
|
if rv.Integer() != expectedResponse {
|
|
t.Errorf("expected ineger \"%d\", got \"%d\"", expectedResponse, rv.Integer())
|
|
}
|
|
return
|
|
}
|
|
t.Error("expected integer response, got another type")
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_HandleHKeys(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
preset bool
|
|
key string
|
|
presetValue interface{}
|
|
command []string
|
|
expectedResponse interface{} // Change count
|
|
expectedValue map[string]interface{}
|
|
expectedError error
|
|
}{
|
|
{
|
|
name: "1. Return an array containing all the keys of the hash",
|
|
preset: true,
|
|
key: "HkeysKey1",
|
|
presetValue: map[string]interface{}{"field1": "value1", "field2": 123456789, "field3": 3.142},
|
|
command: []string{"HKEYS", "HkeysKey1"},
|
|
expectedResponse: []string{"field1", "field2", "field3"},
|
|
expectedValue: map[string]interface{}{},
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
name: "2. Empty array response when trying to call HKEYS on non-existent key",
|
|
preset: false,
|
|
key: "HkeysKey2",
|
|
presetValue: map[string]interface{}{},
|
|
command: []string{"HKEYS", "HkeysKey2"},
|
|
expectedResponse: []string{},
|
|
expectedValue: map[string]interface{}{},
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
name: "3. Command too short",
|
|
preset: false,
|
|
key: "HkeysKey3",
|
|
presetValue: map[string]interface{}{},
|
|
command: []string{"HKEYS"},
|
|
expectedResponse: nil,
|
|
expectedValue: map[string]interface{}{},
|
|
expectedError: errors.New(constants.WrongArgsResponse),
|
|
},
|
|
{
|
|
name: "4. Command too long",
|
|
preset: false,
|
|
key: "HkeysKey4",
|
|
presetValue: map[string]interface{}{},
|
|
command: []string{"HKEYS", "HkeysKey4", "HkeysKey4"},
|
|
expectedResponse: nil,
|
|
expectedValue: map[string]interface{}{},
|
|
expectedError: errors.New(constants.WrongArgsResponse),
|
|
},
|
|
{
|
|
name: "5. Trying to get lengths on a non hash map returns error",
|
|
preset: true,
|
|
key: "HkeysKey5",
|
|
presetValue: "Default value",
|
|
command: []string{"HKEYS", "HkeysKey5"},
|
|
expectedResponse: 0,
|
|
expectedValue: map[string]interface{}{},
|
|
expectedError: errors.New("value at HkeysKey5 is not a hash"),
|
|
},
|
|
}
|
|
|
|
for i, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
ctx := context.WithValue(context.Background(), "test_name", fmt.Sprintf("HKEYS, %d", i))
|
|
|
|
if test.preset {
|
|
if _, err := mockServer.CreateKeyAndLock(ctx, test.key); err != nil {
|
|
t.Error(err)
|
|
}
|
|
if err := mockServer.SetValue(ctx, test.key, test.presetValue); err != nil {
|
|
t.Error(err)
|
|
}
|
|
mockServer.KeyUnlock(ctx, test.key)
|
|
}
|
|
|
|
handler := getHandler(test.command[0])
|
|
if handler == nil {
|
|
t.Errorf("no handler found for command %s", test.command[0])
|
|
return
|
|
}
|
|
|
|
res, err := handler(getHandlerFuncParams(ctx, test.command, nil))
|
|
if test.expectedError != nil {
|
|
if err.Error() != test.expectedError.Error() {
|
|
t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error())
|
|
}
|
|
return
|
|
}
|
|
rd := resp.NewReader(bytes.NewBuffer(res))
|
|
rv, _, err := rd.ReadValue()
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
if expectedResponse, ok := test.expectedResponse.([]string); ok {
|
|
if len(rv.Array()) != len(expectedResponse) {
|
|
t.Errorf("expected length \"%d\", got \"%d\"", len(expectedResponse), len(rv.Array()))
|
|
}
|
|
for _, field := range expectedResponse {
|
|
if !slices.ContainsFunc(rv.Array(), func(value resp.Value) bool {
|
|
return value.String() == field
|
|
}) {
|
|
t.Errorf("could not find expected to find key \"%s\" in response", field)
|
|
}
|
|
}
|
|
return
|
|
}
|
|
t.Error("expected array response, got another type")
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_HandleHGETALL(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
preset bool
|
|
key string
|
|
presetValue interface{}
|
|
command []string
|
|
expectedResponse []string
|
|
expectedValue map[string]interface{}
|
|
expectedError error
|
|
}{
|
|
{
|
|
name: "1. Return an array containing all the fields and values of the hash",
|
|
preset: true,
|
|
key: "HGetAllKey1",
|
|
presetValue: map[string]interface{}{"field1": "value1", "field2": 123456789, "field3": 3.142},
|
|
command: []string{"HGETALL", "HGetAllKey1"},
|
|
expectedResponse: []string{"field1", "value1", "field2", "123456789", "field3", "3.142"},
|
|
expectedValue: map[string]interface{}{},
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
name: "2. Empty array response when trying to call HGETALL on non-existent key",
|
|
preset: false,
|
|
key: "HGetAllKey2",
|
|
presetValue: map[string]interface{}{},
|
|
command: []string{"HGETALL", "HGetAllKey2"},
|
|
expectedResponse: []string{},
|
|
expectedValue: map[string]interface{}{},
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
name: "3. Command too short",
|
|
preset: false,
|
|
key: "HGetAllKey3",
|
|
presetValue: map[string]interface{}{},
|
|
command: []string{"HGETALL"},
|
|
expectedResponse: nil,
|
|
expectedValue: map[string]interface{}{},
|
|
expectedError: errors.New(constants.WrongArgsResponse),
|
|
},
|
|
{
|
|
name: "4. Command too long",
|
|
preset: false,
|
|
key: "HGetAllKey4",
|
|
presetValue: map[string]interface{}{},
|
|
command: []string{"HGETALL", "HGetAllKey4", "HGetAllKey4"},
|
|
expectedResponse: nil,
|
|
expectedValue: map[string]interface{}{},
|
|
expectedError: errors.New(constants.WrongArgsResponse),
|
|
},
|
|
{
|
|
name: "5. Trying to get lengths on a non hash map returns error",
|
|
preset: true,
|
|
key: "HGetAllKey5",
|
|
presetValue: "Default value",
|
|
command: []string{"HGETALL", "HGetAllKey5"},
|
|
expectedResponse: nil,
|
|
expectedValue: map[string]interface{}{},
|
|
expectedError: errors.New("value at HGetAllKey5 is not a hash"),
|
|
},
|
|
}
|
|
|
|
for i, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
ctx := context.WithValue(context.Background(), "test_name", fmt.Sprintf("HGETALL, %d", i))
|
|
|
|
if test.preset {
|
|
if _, err := mockServer.CreateKeyAndLock(ctx, test.key); err != nil {
|
|
t.Error(err)
|
|
}
|
|
if err := mockServer.SetValue(ctx, test.key, test.presetValue); err != nil {
|
|
t.Error(err)
|
|
}
|
|
mockServer.KeyUnlock(ctx, test.key)
|
|
}
|
|
|
|
handler := getHandler(test.command[0])
|
|
if handler == nil {
|
|
t.Errorf("no handler found for command %s", test.command[0])
|
|
return
|
|
}
|
|
|
|
res, err := handler(getHandlerFuncParams(ctx, test.command, nil))
|
|
if test.expectedError != nil {
|
|
if err.Error() != test.expectedError.Error() {
|
|
t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error())
|
|
}
|
|
return
|
|
}
|
|
rd := resp.NewReader(bytes.NewBuffer(res))
|
|
rv, _, err := rd.ReadValue()
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
if len(rv.Array()) != len(test.expectedResponse) {
|
|
t.Errorf("expected length \"%d\", got \"%d\"", len(test.expectedResponse), len(rv.Array()))
|
|
}
|
|
// In the response:
|
|
// The order of results is not guaranteed,
|
|
// However, each field in the array will be reliably followed by its corresponding value
|
|
responseArray := rv.Array()
|
|
for i := 0; i < len(responseArray); i++ {
|
|
if i%2 == 0 {
|
|
// We're on a field in the response
|
|
field := responseArray[i].String()
|
|
value := responseArray[i+1].String()
|
|
|
|
expectedFieldIndex := slices.Index(test.expectedResponse, field)
|
|
if expectedFieldIndex == -1 {
|
|
t.Errorf("received unexpected field \"%s\" in response", field)
|
|
}
|
|
expectedValue := test.expectedResponse[expectedFieldIndex+1]
|
|
if expectedValue != value {
|
|
t.Errorf("expected entry \"%s\", got \"%s\"", expectedValue, value)
|
|
}
|
|
}
|
|
|
|
}
|
|
return
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_HandleHEXISTS(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
preset bool
|
|
key string
|
|
presetValue interface{}
|
|
command []string
|
|
expectedResponse interface{}
|
|
expectedValue map[string]interface{}
|
|
expectedError error
|
|
}{
|
|
{
|
|
name: "1. Return 1 if the field exists in the hash",
|
|
preset: true,
|
|
key: "HexistsKey1",
|
|
presetValue: map[string]interface{}{"field1": "value1", "field2": 123456789, "field3": 3.142},
|
|
command: []string{"HEXISTS", "HexistsKey1", "field1"},
|
|
expectedResponse: 1,
|
|
expectedValue: map[string]interface{}{},
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
name: "2. 0 response when trying to call HEXISTS on non-existent key",
|
|
preset: false,
|
|
key: "HexistsKey2",
|
|
presetValue: map[string]interface{}{},
|
|
command: []string{"HEXISTS", "HexistsKey2", "field1"},
|
|
expectedResponse: 0,
|
|
expectedValue: map[string]interface{}{},
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
name: "3. Command too short",
|
|
preset: false,
|
|
key: "HexistsKey3",
|
|
presetValue: map[string]interface{}{},
|
|
command: []string{"HEXISTS", "HexistsKey3"},
|
|
expectedResponse: nil,
|
|
expectedValue: map[string]interface{}{},
|
|
expectedError: errors.New(constants.WrongArgsResponse),
|
|
},
|
|
{
|
|
name: "4. Command too long",
|
|
preset: false,
|
|
key: "HexistsKey4",
|
|
presetValue: map[string]interface{}{},
|
|
command: []string{"HEXISTS", "HexistsKey4", "field1", "field2"},
|
|
expectedResponse: nil,
|
|
expectedValue: map[string]interface{}{},
|
|
expectedError: errors.New(constants.WrongArgsResponse),
|
|
},
|
|
{
|
|
name: "5. Trying to get lengths on a non hash map returns error",
|
|
preset: true,
|
|
key: "HexistsKey5",
|
|
presetValue: "Default value",
|
|
command: []string{"HEXISTS", "HexistsKey5", "field1"},
|
|
expectedResponse: 0,
|
|
expectedValue: map[string]interface{}{},
|
|
expectedError: errors.New("value at HexistsKey5 is not a hash"),
|
|
},
|
|
}
|
|
|
|
for i, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
ctx := context.WithValue(context.Background(), "test_name", fmt.Sprintf("HEXISTS, %d", i))
|
|
|
|
if test.preset {
|
|
if _, err := mockServer.CreateKeyAndLock(ctx, test.key); err != nil {
|
|
t.Error(err)
|
|
}
|
|
if err := mockServer.SetValue(ctx, test.key, test.presetValue); err != nil {
|
|
t.Error(err)
|
|
}
|
|
mockServer.KeyUnlock(ctx, test.key)
|
|
}
|
|
|
|
handler := getHandler(test.command[0])
|
|
if handler == nil {
|
|
t.Errorf("no handler found for command %s", test.command[0])
|
|
return
|
|
}
|
|
|
|
res, err := handler(getHandlerFuncParams(ctx, test.command, nil))
|
|
if test.expectedError != nil {
|
|
if err.Error() != test.expectedError.Error() {
|
|
t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error())
|
|
}
|
|
return
|
|
}
|
|
rd := resp.NewReader(bytes.NewBuffer(res))
|
|
rv, _, err := rd.ReadValue()
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
if expectedResponse, ok := test.expectedResponse.(int); ok {
|
|
if rv.Integer() != expectedResponse {
|
|
t.Errorf("expected \"%d\", got \"%d\"", expectedResponse, rv.Integer())
|
|
}
|
|
return
|
|
}
|
|
t.Error("expected integer response, got another type")
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_HandleHDEL(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
preset bool
|
|
key string
|
|
presetValue interface{}
|
|
command []string
|
|
expectedResponse interface{}
|
|
expectedValue map[string]interface{}
|
|
expectedError error
|
|
}{
|
|
{
|
|
name: "1. Return count of deleted fields in the specified hash",
|
|
preset: true,
|
|
key: "HdelKey1",
|
|
presetValue: map[string]interface{}{"field1": "value1", "field2": 123456789, "field3": 3.142, "field7": "value7"},
|
|
command: []string{"HDEL", "HdelKey1", "field1", "field2", "field3", "field4", "field5", "field6"},
|
|
expectedResponse: 3,
|
|
expectedValue: map[string]interface{}{"field1": nil, "field2": nil, "field3": nil, "field7": "value1"},
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
name: "2. 0 response when passing delete fields that are non-existent on valid hash",
|
|
preset: true,
|
|
key: "HdelKey2",
|
|
presetValue: map[string]interface{}{"field1": "value1", "field2": "value2", "field3": "value3"},
|
|
command: []string{"HDEL", "HdelKey2", "field4", "field5", "field6"},
|
|
expectedResponse: 0,
|
|
expectedValue: map[string]interface{}{"field1": "value1", "field2": "value2", "field3": "value3"},
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
name: "3. 0 response when trying to call HDEL on non-existent key",
|
|
preset: false,
|
|
key: "HdelKey3",
|
|
presetValue: map[string]interface{}{},
|
|
command: []string{"HDEL", "HdelKey3", "field1"},
|
|
expectedResponse: 0,
|
|
expectedValue: map[string]interface{}{},
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
name: "4. Command too short",
|
|
preset: false,
|
|
key: "HdelKey4",
|
|
presetValue: map[string]interface{}{},
|
|
command: []string{"HDEL", "HdelKey4"},
|
|
expectedResponse: nil,
|
|
expectedValue: map[string]interface{}{},
|
|
expectedError: errors.New(constants.WrongArgsResponse),
|
|
},
|
|
{
|
|
name: "5. Trying to get lengths on a non hash map returns error",
|
|
preset: true,
|
|
key: "HdelKey5",
|
|
presetValue: "Default value",
|
|
command: []string{"HDEL", "HdelKey5", "field1"},
|
|
expectedResponse: 0,
|
|
expectedValue: map[string]interface{}{},
|
|
expectedError: errors.New("value at HdelKey5 is not a hash"),
|
|
},
|
|
}
|
|
|
|
for i, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
ctx := context.WithValue(context.Background(), "test_name", fmt.Sprintf("HDEL, %d", i))
|
|
|
|
if test.preset {
|
|
if _, err := mockServer.CreateKeyAndLock(ctx, test.key); err != nil {
|
|
t.Error(err)
|
|
}
|
|
if err := mockServer.SetValue(ctx, test.key, test.presetValue); err != nil {
|
|
t.Error(err)
|
|
}
|
|
mockServer.KeyUnlock(ctx, test.key)
|
|
}
|
|
|
|
handler := getHandler(test.command[0])
|
|
if handler == nil {
|
|
t.Errorf("no handler found for command %s", test.command[0])
|
|
return
|
|
}
|
|
|
|
res, err := handler(getHandlerFuncParams(ctx, test.command, nil))
|
|
if test.expectedError != nil {
|
|
if err.Error() != test.expectedError.Error() {
|
|
t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error())
|
|
}
|
|
return
|
|
}
|
|
rd := resp.NewReader(bytes.NewBuffer(res))
|
|
rv, _, err := rd.ReadValue()
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
if expectedResponse, ok := test.expectedResponse.(int); ok {
|
|
if rv.Integer() != expectedResponse {
|
|
t.Errorf("expected \"%d\", got \"%d\"", expectedResponse, rv.Integer())
|
|
}
|
|
return
|
|
}
|
|
if _, err = mockServer.KeyRLock(ctx, test.key); err != nil {
|
|
t.Error(err)
|
|
}
|
|
if h, ok := mockServer.GetValue(ctx, test.key).(map[string]interface{}); ok {
|
|
for field, value := range h {
|
|
if value != test.expectedValue[field] {
|
|
t.Errorf("expected value \"%+v\", got \"%+v\"", test.expectedValue[field], value)
|
|
}
|
|
}
|
|
return
|
|
}
|
|
t.Error("expected hash value but got another type")
|
|
})
|
|
}
|
|
}
|