// 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 ( "errors" "fmt" "slices" "strconv" "strings" "testing" "time" "github.com/echovault/sugardb/internal" "github.com/echovault/sugardb/internal/clock" "github.com/echovault/sugardb/internal/config" "github.com/echovault/sugardb/internal/constants" "github.com/echovault/sugardb/internal/modules/hash" "github.com/echovault/sugardb/sugardb" "github.com/tidwall/resp" ) func Test_Hash(t *testing.T) { mockClock := clock.NewClock() port, err := internal.GetFreePort() if err != nil { t.Error(err) return } mockServer, err := sugardb.NewSugarDB( sugardb.WithConfig(config.Config{ BindAddr: "localhost", Port: uint16(port), DataDir: "", EvictionPolicy: constants.NoEviction, }), ) if err != nil { t.Error(err) return } go func() { mockServer.Start() }() t.Cleanup(func() { mockServer.ShutDown() }) t.Run("Test_HandleHSET", func(t *testing.T) { t.Parallel() conn, err := internal.GetConnection("localhost", port) if err != nil { t.Error(err) return } defer func() { _ = conn.Close() }() client := resp.NewConn(conn) // Tests for both HSet and HSetNX tests := []struct { name string key string presetValue interface{} command []string expectedResponse int // Change count expectedValue map[string]string expectedError error }{ { name: "1. HSETNX set field on non-existent hash map", key: "HsetKey1", presetValue: nil, command: []string{"HSETNX", "HsetKey1", "field1", "value1"}, expectedResponse: 1, expectedValue: map[string]string{"field1": "value1"}, expectedError: nil, }, { name: "2. HSETNX set field on existing hash map", key: "HsetKey2", presetValue: map[string]string{"field1": "value1"}, command: []string{"HSETNX", "HsetKey2", "field2", "value2"}, expectedResponse: 1, expectedValue: map[string]string{"field1": "value1", "field2": "value2"}, expectedError: nil, }, { name: "3. HSETNX skips operation when setting on existing field", key: "HsetKey3", presetValue: map[string]string{"field1": "value1"}, command: []string{"HSETNX", "HsetKey3", "field1", "value1-new"}, expectedResponse: 0, expectedValue: map[string]string{"field1": "value1"}, expectedError: nil, }, { name: "4. Regular HSET command on non-existent hash map", key: "HsetKey4", presetValue: nil, command: []string{"HSET", "HsetKey4", "field1", "value1", "field2", "value2"}, expectedResponse: 2, expectedValue: map[string]string{"field1": "value1", "field2": "value2"}, expectedError: nil, }, { name: "5. Regular HSET update on existing hash map", key: "HsetKey5", presetValue: map[string]string{"field1": "value1", "field2": "value2"}, command: []string{"HSET", "HsetKey5", "field1", "value1-new", "field2", "value2-ne2", "field3", "value3"}, expectedResponse: 3, expectedValue: map[string]string{"field1": "value1-new", "field2": "value2-ne2", "field3": "value3"}, expectedError: nil, }, { name: "6. HSET overwrites when the target key is not a map", key: "HsetKey6", presetValue: "Default preset value", command: []string{"HSET", "HsetKey6", "field1", "value1"}, expectedResponse: 1, expectedValue: map[string]string{"field1": "value1"}, expectedError: nil, }, { name: "7. HSET returns error when there's a mismatch in key/values", key: "HsetKey7", presetValue: nil, command: []string{"HSET", "HsetKey7", "field1", "value1", "field2"}, expectedResponse: 0, expectedValue: map[string]string{}, expectedError: errors.New("each field must have a corresponding value"), }, { name: "8. Command too short", key: "HsetKey8", presetValue: nil, command: []string{"HSET", "field1"}, expectedResponse: 0, expectedValue: map[string]string{}, expectedError: errors.New(constants.WrongArgsResponse), }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { if test.presetValue != nil { var command []resp.Value var expected string switch test.presetValue.(type) { case string: command = []resp.Value{ resp.StringValue("SET"), resp.StringValue(test.key), resp.StringValue(test.presetValue.(string)), } expected = "ok" case map[string]string: command = []resp.Value{resp.StringValue("HSET"), resp.StringValue(test.key)} for key, value := range test.presetValue.(map[string]string) { command = append(command, []resp.Value{ resp.StringValue(key), resp.StringValue(value)}..., ) } expected = strconv.Itoa(len(test.presetValue.(map[string]string))) } if err = client.WriteArray(command); err != nil { t.Error(err) } res, _, err := client.ReadValue() if err != nil { t.Error(err) } if !strings.EqualFold(res.String(), expected) { t.Errorf("expected preset response to be \"%s\", got %s", expected, res.String()) } } command := make([]resp.Value, len(test.command)) for i, c := range test.command { command[i] = resp.StringValue(c) } if err = client.WriteArray(command); err != nil { t.Error(err) } res, _, err := client.ReadValue() if err != nil { t.Error(err) } if test.expectedError != nil { if !strings.Contains(res.Error().Error(), test.expectedError.Error()) { t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error()) } return } if res.Integer() != test.expectedResponse { t.Errorf("expected response \"%d\", got \"%d\"", test.expectedResponse, res.Integer()) } // Check that all the values are what is expected if err := client.WriteArray([]resp.Value{ resp.StringValue("HGETALL"), resp.StringValue(test.key), }); err != nil { t.Error(err) } res, _, err = client.ReadValue() if err != nil { t.Error(err) } for idx, field := range res.Array() { if idx%2 == 0 { if res.Array()[idx+1].String() != test.expectedValue[field.String()] { t.Errorf( "expected value \"%+v\" for field \"%s\", got \"%+v\"", test.expectedValue[field.String()], field.String(), res.Array()[idx+1].String(), ) } } } }) } }) t.Run("Test_HandleHINCRBY", func(t *testing.T) { t.Parallel() conn, err := internal.GetConnection("localhost", port) if err != nil { t.Error(err) return } defer func() { _ = conn.Close() }() client := resp.NewConn(conn) // Tests for both HIncrBy and HIncrByFloat tests := []struct { name string key string presetValue interface{} command []string expectedResponse string // Change count expectedValue map[string]string expectedError error }{ { name: "1. Increment by integer on non-existent hash should create a new one", key: "HincrbyKey1", presetValue: nil, command: []string{"HINCRBY", "HincrbyKey1", "field1", "1"}, expectedResponse: "1", expectedValue: map[string]string{"field1": "1"}, expectedError: nil, }, { name: "2. Increment by float on non-existent hash should create one", key: "HincrbyKey2", presetValue: nil, command: []string{"HINCRBYFLOAT", "HincrbyKey2", "field1", "3.142"}, expectedResponse: "3.142", expectedValue: map[string]string{"field1": "3.142"}, expectedError: nil, }, { name: "3. Increment by integer on existing hash", key: "HincrbyKey3", presetValue: map[string]string{"field1": "1"}, command: []string{"HINCRBY", "HincrbyKey3", "field1", "10"}, expectedResponse: "11", expectedValue: map[string]string{"field1": "11"}, expectedError: nil, }, { name: "4. Increment by float on an existing hash", key: "HincrbyKey4", presetValue: map[string]string{"field1": "3.142"}, command: []string{"HINCRBYFLOAT", "HincrbyKey4", "field1", "3.142"}, expectedResponse: "6.284", expectedValue: map[string]string{"field1": "6.284"}, expectedError: nil, }, { name: "5. Command too short", key: "HincrbyKey5", presetValue: nil, command: []string{"HINCRBY", "HincrbyKey5"}, expectedResponse: "0", expectedValue: nil, expectedError: errors.New(constants.WrongArgsResponse), }, { name: "6. Command too long", key: "HincrbyKey6", presetValue: nil, command: []string{"HINCRBY", "HincrbyKey6", "field1", "23", "45"}, expectedResponse: "0", expectedValue: nil, expectedError: errors.New(constants.WrongArgsResponse), }, { name: "7. Error when increment by float does not pass valid float", key: "HincrbyKey7", presetValue: nil, command: []string{"HINCRBYFLOAT", "HincrbyKey7", "field1", "three point one four two"}, expectedResponse: "0", expectedValue: nil, expectedError: errors.New("increment must be a float"), }, { name: "8. Error when increment does not pass valid integer", key: "HincrbyKey8", presetValue: nil, command: []string{"HINCRBY", "HincrbyKey8", "field1", "three"}, expectedResponse: "0", expectedValue: nil, expectedError: errors.New("increment must be an integer"), }, { name: "9. Error when trying to increment on a key that is not a hash", key: "HincrbyKey9", presetValue: "Default value", command: []string{"HINCRBY", "HincrbyKey9", "field1", "3"}, expectedResponse: "0", expectedValue: nil, 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", key: "HincrbyKey10", presetValue: map[string]string{"field1": "value1"}, command: []string{"HINCRBY", "HincrbyKey10", "field1", "3"}, expectedResponse: "0", expectedValue: nil, expectedError: errors.New("value at field field1 is not a number"), }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { if test.presetValue != nil { var command []resp.Value var expected string switch test.presetValue.(type) { case string: command = []resp.Value{ resp.StringValue("SET"), resp.StringValue(test.key), resp.StringValue(test.presetValue.(string)), } expected = "ok" case map[string]string: command = []resp.Value{resp.StringValue("HSET"), resp.StringValue(test.key)} for key, value := range test.presetValue.(map[string]string) { command = append(command, []resp.Value{ resp.StringValue(key), resp.StringValue(value)}..., ) } expected = strconv.Itoa(len(test.presetValue.(map[string]string))) } if err = client.WriteArray(command); err != nil { t.Error(err) } res, _, err := client.ReadValue() if err != nil { t.Error(err) } if !strings.EqualFold(res.String(), expected) { t.Errorf("expected preset response to be \"%s\", got %s", expected, res.String()) } } command := make([]resp.Value, len(test.command)) for i, c := range test.command { command[i] = resp.StringValue(c) } if err = client.WriteArray(command); err != nil { t.Error(err) } res, _, err := client.ReadValue() if err != nil { t.Error(err) } if test.expectedError != nil { if !strings.Contains(res.Error().Error(), test.expectedError.Error()) { t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error()) } return } if res.String() != test.expectedResponse { t.Errorf("expected response \"%s\", got \"%s\"", test.expectedResponse, res.String()) } // Check that all the values are what is expected if err := client.WriteArray([]resp.Value{ resp.StringValue("HGETALL"), resp.StringValue(test.key), }); err != nil { t.Error(err) } res, _, err = client.ReadValue() if err != nil { t.Error(err) } for idx, field := range res.Array() { if idx%2 == 0 { if res.Array()[idx+1].String() != test.expectedValue[field.String()] { t.Errorf( "expected value \"%+v\" for field \"%s\", got \"%+v\"", test.expectedValue[field.String()], field.String(), res.Array()[idx+1].String(), ) } } } }) } }) t.Run("Test_HandleHGET", func(t *testing.T) { t.Parallel() conn, err := internal.GetConnection("localhost", port) if err != nil { t.Error(err) return } defer func() { _ = conn.Close() }() client := resp.NewConn(conn) tests := []struct { name string key string presetValue interface{} command []string expectedResponse []string // Change count expectedValue map[string]string expectedError error }{ { name: "1. Get values from existing hash.", key: "HgetKey1", presetValue: map[string]string{"field1": "value1", "field2": "365", "field3": "3.142"}, command: []string{"HGET", "HgetKey1", "field1", "field2", "field3", "field4"}, expectedResponse: []string{"value1", "365", "3.142", ""}, expectedValue: map[string]string{"field1": "value1", "field2": "365", "field3": "3.142"}, expectedError: nil, }, { name: "2. Return nil when attempting to get from non-existed key", key: "HgetKey2", presetValue: nil, command: []string{"HGET", "HgetKey2", "field1"}, expectedResponse: nil, expectedValue: nil, expectedError: nil, }, { name: "3. Error when trying to get from a value that is not a hash map", key: "HgetKey3", presetValue: "Default Value", command: []string{"HGET", "HgetKey3", "field1"}, expectedResponse: nil, expectedValue: nil, expectedError: errors.New("value at HgetKey3 is not a hash"), }, { name: "4. Command too short", key: "HgetKey4", presetValue: nil, command: []string{"HGET", "HgetKey4"}, expectedResponse: nil, expectedValue: nil, expectedError: errors.New(constants.WrongArgsResponse), }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { if test.presetValue != nil { var command []resp.Value var expected string switch test.presetValue.(type) { case string: command = []resp.Value{ resp.StringValue("SET"), resp.StringValue(test.key), resp.StringValue(test.presetValue.(string)), } expected = "ok" case map[string]string: command = []resp.Value{resp.StringValue("HSET"), resp.StringValue(test.key)} for key, value := range test.presetValue.(map[string]string) { command = append(command, []resp.Value{ resp.StringValue(key), resp.StringValue(value)}..., ) } expected = strconv.Itoa(len(test.presetValue.(map[string]string))) } if err = client.WriteArray(command); err != nil { t.Error(err) } res, _, err := client.ReadValue() if err != nil { t.Error(err) } if !strings.EqualFold(res.String(), expected) { t.Errorf("expected preset response to be \"%s\", got %s", expected, res.String()) } } command := make([]resp.Value, len(test.command)) for i, c := range test.command { command[i] = resp.StringValue(c) } if err = client.WriteArray(command); err != nil { t.Error(err) } res, _, err := client.ReadValue() if err != nil { t.Error(err) } if test.expectedError != nil { if !strings.Contains(res.Error().Error(), test.expectedError.Error()) { t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error()) } return } if test.expectedResponse == nil { if !res.IsNull() { t.Errorf("expected nil response, got %+v", res) } return } for _, item := range res.Array() { if !slices.Contains(test.expectedResponse, item.String()) { t.Errorf("unexpected element \"%s\" in response", item.String()) } } // Check that all the values are what is expected if err := client.WriteArray([]resp.Value{ resp.StringValue("HGETALL"), resp.StringValue(test.key), }); err != nil { t.Error(err) } res, _, err = client.ReadValue() if err != nil { t.Error(err) } for idx, field := range res.Array() { if idx%2 == 0 { if res.Array()[idx+1].String() != test.expectedValue[field.String()] { t.Errorf( "expected value \"%+v\" for field \"%s\", got \"%+v\"", test.expectedValue[field.String()], field.String(), res.Array()[idx+1].String(), ) } } } }) } }) t.Run("Test_HandleHMGET", func(t *testing.T) { t.Parallel() conn, err := internal.GetConnection("localhost", port) if err != nil { t.Error(err) return } defer func() { _ = conn.Close() }() client := resp.NewConn(conn) tests := []struct { name string key string presetValue interface{} command []string expectedResponse []string // Change count expectedValue map[string]string expectedError error }{ { name: "1. Get values from existing hash.", key: "HmgetKey1", presetValue: map[string]string{"field1": "value1", "field2": "365", "field3": "3.142"}, command: []string{"HMGET", "HmgetKey1", "field1", "field2", "field3", "field4"}, expectedResponse: []string{"value1", "365", "3.142", ""}, expectedValue: map[string]string{"field1": "value1", "field2": "365", "field3": "3.142"}, expectedError: nil, }, { name: "2. Return nil when attempting to get from non-existed key", key: "HmgetKey2", presetValue: nil, command: []string{"HMGET", "HmgetKey2", "field1"}, expectedResponse: nil, expectedValue: nil, expectedError: nil, }, { name: "3. Error when trying to get from a value that is not a hash map", key: "HmgetKey3", presetValue: "Default Value", command: []string{"HMGET", "HmgetKey3", "field1"}, expectedResponse: nil, expectedValue: nil, expectedError: errors.New("value at HmgetKey3 is not a hash"), }, { name: "4. Command too short", key: "HmgetKey4", presetValue: nil, command: []string{"HMGET", "HmgetKey4"}, expectedResponse: nil, expectedValue: nil, expectedError: errors.New(constants.WrongArgsResponse), }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { if test.presetValue != nil { var command []resp.Value var expected string switch test.presetValue.(type) { case string: command = []resp.Value{ resp.StringValue("SET"), resp.StringValue(test.key), resp.StringValue(test.presetValue.(string)), } expected = "ok" case map[string]string: command = []resp.Value{resp.StringValue("HSET"), resp.StringValue(test.key)} for key, value := range test.presetValue.(map[string]string) { command = append(command, []resp.Value{ resp.StringValue(key), resp.StringValue(value)}..., ) } expected = strconv.Itoa(len(test.presetValue.(map[string]string))) } if err = client.WriteArray(command); err != nil { t.Error(err) } res, _, err := client.ReadValue() if err != nil { t.Error(err) } if !strings.EqualFold(res.String(), expected) { t.Errorf("expected preset response to be \"%s\", got %s", expected, res.String()) } } command := make([]resp.Value, len(test.command)) for i, c := range test.command { command[i] = resp.StringValue(c) } if err = client.WriteArray(command); err != nil { t.Error(err) } res, _, err := client.ReadValue() if err != nil { t.Error(err) } if test.expectedError != nil { if !strings.Contains(res.Error().Error(), test.expectedError.Error()) { t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error()) } return } if test.expectedResponse == nil { if !res.IsNull() { t.Errorf("expected nil response, got %+v", res) } return } for _, item := range res.Array() { if !slices.Contains(test.expectedResponse, item.String()) { t.Errorf("unexpected element \"%s\" in response", item.String()) } } // Check that all the values are what is expected if err := client.WriteArray([]resp.Value{ resp.StringValue("HGETALL"), resp.StringValue(test.key), }); err != nil { t.Error(err) } res, _, err = client.ReadValue() if err != nil { t.Error(err) } for idx, field := range res.Array() { if idx%2 == 0 { if res.Array()[idx+1].String() != test.expectedValue[field.String()] { t.Errorf( "expected value \"%+v\" for field \"%s\", got \"%+v\"", test.expectedValue[field.String()], field.String(), res.Array()[idx+1].String(), ) } } } }) } }) t.Run("Test_HandleHSTRLEN", func(t *testing.T) { t.Parallel() conn, err := internal.GetConnection("localhost", port) if err != nil { t.Error(err) return } defer func() { _ = conn.Close() }() client := resp.NewConn(conn) tests := []struct { name string key string presetValue interface{} command []string expectedResponse []int // Change count expectedValue map[string]string 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.", key: "HstrlenKey1", presetValue: map[string]string{"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]string{"field1": "value1", "field2": "123456789", "field3": "3.142"}, expectedError: nil, }, { name: "2. Nil response when trying to get HSTRLEN non-existent key", key: "HstrlenKey2", presetValue: nil, command: []string{"HSTRLEN", "HstrlenKey2", "field1"}, expectedResponse: nil, expectedValue: nil, expectedError: nil, }, { name: "3. Command too short", key: "HstrlenKey3", presetValue: nil, command: []string{"HSTRLEN", "HstrlenKey3"}, expectedResponse: nil, expectedValue: nil, expectedError: errors.New(constants.WrongArgsResponse), }, { name: "4. Trying to get lengths on a non hash map returns error", key: "HstrlenKey4", presetValue: "Default value", command: []string{"HSTRLEN", "HstrlenKey4", "field1"}, expectedResponse: nil, expectedValue: nil, expectedError: errors.New("value at HstrlenKey4 is not a hash"), }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { if test.presetValue != nil { var command []resp.Value var expected string switch test.presetValue.(type) { case string: command = []resp.Value{ resp.StringValue("SET"), resp.StringValue(test.key), resp.StringValue(test.presetValue.(string)), } expected = "ok" case map[string]string: command = []resp.Value{resp.StringValue("HSET"), resp.StringValue(test.key)} for key, value := range test.presetValue.(map[string]string) { command = append(command, []resp.Value{ resp.StringValue(key), resp.StringValue(value)}..., ) } expected = strconv.Itoa(len(test.presetValue.(map[string]string))) } if err = client.WriteArray(command); err != nil { t.Error(err) } res, _, err := client.ReadValue() if err != nil { t.Error(err) } if !strings.EqualFold(res.String(), expected) { t.Errorf("expected preset response to be \"%s\", got %s", expected, res.String()) } } command := make([]resp.Value, len(test.command)) for i, c := range test.command { command[i] = resp.StringValue(c) } if err = client.WriteArray(command); err != nil { t.Error(err) } res, _, err := client.ReadValue() if err != nil { t.Error(err) } if test.expectedError != nil { if !strings.Contains(res.Error().Error(), test.expectedError.Error()) { t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error()) } return } if test.expectedResponse == nil { if !res.IsNull() { t.Errorf("expected nil response, got %+v", res) } return } for _, item := range res.Array() { if !slices.Contains(test.expectedResponse, item.Integer()) { t.Errorf("unexpected element \"%d\" in response", item.Integer()) } } // Check that all the values are what is expected if err := client.WriteArray([]resp.Value{ resp.StringValue("HGETALL"), resp.StringValue(test.key), }); err != nil { t.Error(err) } res, _, err = client.ReadValue() if err != nil { t.Error(err) } for idx, field := range res.Array() { if idx%2 == 0 { if res.Array()[idx+1].String() != test.expectedValue[field.String()] { t.Errorf( "expected value \"%+v\" for field \"%s\", got \"%+v\"", test.expectedValue[field.String()], field.String(), res.Array()[idx+1].String(), ) } } } }) } }) t.Run("Test_HandleHVALS", func(t *testing.T) { t.Parallel() conn, err := internal.GetConnection("localhost", port) if err != nil { t.Error(err) return } defer func() { _ = conn.Close() }() client := resp.NewConn(conn) tests := []struct { name string key string presetValue interface{} command []string expectedResponse []string expectedValue map[string]string expectedError error }{ { name: "1. Return all the values from a hash", key: "HvalsKey1", presetValue: map[string]string{"field1": "value1", "field2": "123456789", "field3": "3.142"}, command: []string{"HVALS", "HvalsKey1"}, expectedResponse: []string{"value1", "123456789", "3.142"}, expectedValue: map[string]string{"field1": "value1", "field2": "123456789", "field3": "3.142"}, expectedError: nil, }, { name: "2. Empty array response when trying to get HSTRLEN non-existent key", key: "HvalsKey2", presetValue: nil, command: []string{"HVALS", "HvalsKey2"}, expectedResponse: []string{}, expectedValue: nil, expectedError: nil, }, { name: "3. Command too short", key: "HvalsKey3", presetValue: nil, command: []string{"HVALS"}, expectedResponse: nil, expectedValue: nil, expectedError: errors.New(constants.WrongArgsResponse), }, { name: "4. Command too long", key: "HvalsKey4", presetValue: nil, command: []string{"HVALS", "HvalsKey4", "HvalsKey4"}, expectedResponse: nil, expectedValue: nil, expectedError: errors.New(constants.WrongArgsResponse), }, { name: "5. Trying to get lengths on a non hash map returns error", key: "HvalsKey5", presetValue: "Default value", command: []string{"HVALS", "HvalsKey5"}, expectedResponse: nil, expectedValue: nil, expectedError: errors.New("value at HvalsKey5 is not a hash"), }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { if test.presetValue != nil { var command []resp.Value var expected string switch test.presetValue.(type) { case string: command = []resp.Value{ resp.StringValue("SET"), resp.StringValue(test.key), resp.StringValue(test.presetValue.(string)), } expected = "ok" case map[string]string: command = []resp.Value{resp.StringValue("HSET"), resp.StringValue(test.key)} for key, value := range test.presetValue.(map[string]string) { command = append(command, []resp.Value{ resp.StringValue(key), resp.StringValue(value)}..., ) } expected = strconv.Itoa(len(test.presetValue.(map[string]string))) } if err = client.WriteArray(command); err != nil { t.Error(err) } res, _, err := client.ReadValue() if err != nil { t.Error(err) } if !strings.EqualFold(res.String(), expected) { t.Errorf("expected preset response to be \"%s\", got %s", expected, res.String()) } } command := make([]resp.Value, len(test.command)) for i, c := range test.command { command[i] = resp.StringValue(c) } if err = client.WriteArray(command); err != nil { t.Error(err) } res, _, err := client.ReadValue() if err != nil { t.Error(err) } if test.expectedError != nil { if !strings.Contains(res.Error().Error(), test.expectedError.Error()) { t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error()) } return } if test.expectedResponse == nil { if !res.IsNull() { t.Errorf("expected nil response, got %+v", res) } return } for _, item := range res.Array() { if !slices.Contains(test.expectedResponse, item.String()) { t.Errorf("unexpected element \"%s\" in response", item.String()) } } }) } }) t.Run("Test_HandleHRANDFIELD", func(t *testing.T) { t.Parallel() conn, err := internal.GetConnection("localhost", port) if err != nil { t.Error(err) return } defer func() { _ = conn.Close() }() client := resp.NewConn(conn) tests := []struct { name string key string presetValue interface{} command []string expectedResponse []string expectedError error }{ { name: "1. Get a random field", key: "HrandfieldKey1", presetValue: map[string]string{"field1": "value1", "field2": "123456789", "field3": "3.142"}, command: []string{"HRANDFIELD", "HrandfieldKey1"}, expectedResponse: []string{"field1", "field2", "field3"}, expectedError: nil, }, { name: "2. Get a random field with a value", key: "HrandfieldKey2", presetValue: map[string]string{"field1": "value1", "field2": "123456789", "field3": "3.142"}, command: []string{"HRANDFIELD", "HrandfieldKey2", "1", "WITHVALUES"}, expectedResponse: []string{"field1", "value1", "field2", "123456789", "field3", "3.142"}, expectedError: nil, }, { name: "3. Get several random fields", key: "HrandfieldKey3", presetValue: map[string]string{ "field1": "value1", "field2": "123456789", "field3": "3.142", "field4": "value4", "field5": "value5", }, command: []string{"HRANDFIELD", "HrandfieldKey3", "3"}, expectedResponse: []string{"field1", "field2", "field3", "field4", "field5"}, expectedError: nil, }, { name: "4. Get several random fields with their corresponding values", key: "HrandfieldKey4", presetValue: map[string]string{ "field1": "value1", "field2": "123456789", "field3": "3.142", "field4": "value4", "field5": "value5", }, command: []string{"HRANDFIELD", "HrandfieldKey4", "3", "WITHVALUES"}, expectedResponse: []string{ "field1", "value1", "field2", "123456789", "field3", "3.142", "field4", "value4", "field5", "value5", }, expectedError: nil, }, { name: "5. Get the entire hash", key: "HrandfieldKey5", presetValue: map[string]string{ "field1": "value1", "field2": "123456789", "field3": "3.142", "field4": "value4", "field5": "value5", }, command: []string{"HRANDFIELD", "HrandfieldKey5", "5"}, expectedResponse: []string{"field1", "field2", "field3", "field4", "field5"}, expectedError: nil, }, { name: "6. Get the entire hash with values", key: "HrandfieldKey5", presetValue: map[string]string{ "field1": "value1", "field2": "123456789", "field3": "3.142", "field4": "value4", "field5": "value5", }, command: []string{"HRANDFIELD", "HrandfieldKey5", "5", "WITHVALUES"}, expectedResponse: []string{ "field1", "value1", "field2", "123456789", "field3", "3.142", "field4", "value4", "field5", "value5", }, expectedError: nil, }, { name: "7. Command too short", key: "HrandfieldKey10", presetValue: nil, command: []string{"HRANDFIELD"}, expectedError: errors.New(constants.WrongArgsResponse), }, { name: "8. Command too long", key: "HrandfieldKey11", presetValue: nil, 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", 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", 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\"", key: "HrandfieldKey12", presetValue: "Default value", command: []string{"HRANDFIELD", "HrandfieldKey12", "10", "FLAG"}, expectedError: errors.New("result modifier must be withvalues"), }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { if test.presetValue != nil { var command []resp.Value var expected string switch test.presetValue.(type) { case string: command = []resp.Value{ resp.StringValue("SET"), resp.StringValue(test.key), resp.StringValue(test.presetValue.(string)), } expected = "ok" case map[string]string: command = []resp.Value{resp.StringValue("HSET"), resp.StringValue(test.key)} for key, value := range test.presetValue.(map[string]string) { command = append(command, []resp.Value{ resp.StringValue(key), resp.StringValue(value)}..., ) } expected = strconv.Itoa(len(test.presetValue.(map[string]string))) } if err = client.WriteArray(command); err != nil { t.Error(err) } res, _, err := client.ReadValue() if err != nil { t.Error(err) } if !strings.EqualFold(res.String(), expected) { t.Errorf("expected preset response to be \"%s\", got %s", expected, res.String()) } } command := make([]resp.Value, len(test.command)) for i, c := range test.command { command[i] = resp.StringValue(c) } if err = client.WriteArray(command); err != nil { t.Error(err) } res, _, err := client.ReadValue() if err != nil { t.Error(err) } if test.expectedError != nil { if !strings.Contains(res.Error().Error(), test.expectedError.Error()) { t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error()) } return } if test.expectedResponse == nil { if !res.IsNull() { t.Errorf("expected nil response, got %+v", res) } return } for _, item := range res.Array() { if !slices.Contains(test.expectedResponse, item.String()) { t.Errorf("unexpected element \"%s\" in response", item.String()) } } }) } }) t.Run("Test_HandleHLEN", func(t *testing.T) { t.Parallel() conn, err := internal.GetConnection("localhost", port) if err != nil { t.Error(err) return } defer func() { _ = conn.Close() }() client := resp.NewConn(conn) tests := []struct { name string key string presetValue interface{} command []string expectedResponse int // Change count expectedError error }{ { name: "1. Return the correct length of the hash", key: "HlenKey1", presetValue: map[string]string{"field1": "value1", "field2": "123456789", "field3": "3.142"}, command: []string{"HLEN", "HlenKey1"}, expectedResponse: 3, expectedError: nil, }, { name: "2. 0 response when trying to call HLEN on non-existent key", key: "HlenKey2", presetValue: nil, command: []string{"HLEN", "HlenKey2"}, expectedResponse: 0, expectedError: nil, }, { name: "3. Command too short", key: "HlenKey3", presetValue: nil, command: []string{"HLEN"}, expectedResponse: 0, expectedError: errors.New(constants.WrongArgsResponse), }, { name: "4. Command too long", presetValue: nil, command: []string{"HLEN", "HlenKey4", "HlenKey4"}, expectedResponse: 0, expectedError: errors.New(constants.WrongArgsResponse), }, { name: "5. Trying to get lengths on a non hash map returns error", key: "HlenKey5", presetValue: "Default value", command: []string{"HLEN", "HlenKey5"}, expectedResponse: 0, expectedError: errors.New("value at HlenKey5 is not a hash"), }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { if test.presetValue != nil { var command []resp.Value var expected string switch test.presetValue.(type) { case string: command = []resp.Value{ resp.StringValue("SET"), resp.StringValue(test.key), resp.StringValue(test.presetValue.(string)), } expected = "ok" case map[string]string: command = []resp.Value{resp.StringValue("HSET"), resp.StringValue(test.key)} for key, value := range test.presetValue.(map[string]string) { command = append(command, []resp.Value{ resp.StringValue(key), resp.StringValue(value)}..., ) } expected = strconv.Itoa(len(test.presetValue.(map[string]string))) } if err = client.WriteArray(command); err != nil { t.Error(err) } res, _, err := client.ReadValue() if err != nil { t.Error(err) } if !strings.EqualFold(res.String(), expected) { t.Errorf("expected preset response to be \"%s\", got %s", expected, res.String()) } } command := make([]resp.Value, len(test.command)) for i, c := range test.command { command[i] = resp.StringValue(c) } if err = client.WriteArray(command); err != nil { t.Error(err) } res, _, err := client.ReadValue() if err != nil { t.Error(err) } if test.expectedError != nil { if !strings.Contains(res.Error().Error(), test.expectedError.Error()) { t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error()) } return } if res.Integer() != test.expectedResponse { t.Errorf("expected response %d, got %d", test.expectedResponse, res.Integer()) } }) } }) t.Run("Test_HandleHKeys", func(t *testing.T) { t.Parallel() conn, err := internal.GetConnection("localhost", port) if err != nil { t.Error(err) return } defer func() { _ = conn.Close() }() client := resp.NewConn(conn) tests := []struct { name string key string presetValue interface{} command []string expectedResponse []string expectedError error }{ { name: "1. Return an array containing all the keys of the hash", key: "HkeysKey1", presetValue: map[string]string{"field1": "value1", "field2": "123456789", "field3": "3.142"}, command: []string{"HKEYS", "HkeysKey1"}, expectedResponse: []string{"field1", "field2", "field3"}, expectedError: nil, }, { name: "2. Empty array response when trying to call HKEYS on non-existent key", key: "HkeysKey2", presetValue: nil, command: []string{"HKEYS", "HkeysKey2"}, expectedResponse: []string{}, expectedError: nil, }, { name: "3. Command too short", key: "HkeysKey3", presetValue: nil, command: []string{"HKEYS"}, expectedResponse: nil, expectedError: errors.New(constants.WrongArgsResponse), }, { name: "4. Command too long", key: "HkeysKey4", presetValue: nil, command: []string{"HKEYS", "HkeysKey4", "HkeysKey4"}, expectedResponse: nil, expectedError: errors.New(constants.WrongArgsResponse), }, { name: "5. Trying to get lengths on a non hash map returns error", key: "HkeysKey5", presetValue: "Default value", command: []string{"HKEYS", "HkeysKey5"}, expectedError: errors.New("value at HkeysKey5 is not a hash"), }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { if test.presetValue != nil { var command []resp.Value var expected string switch test.presetValue.(type) { case string: command = []resp.Value{ resp.StringValue("SET"), resp.StringValue(test.key), resp.StringValue(test.presetValue.(string)), } expected = "ok" case map[string]string: command = []resp.Value{resp.StringValue("HSET"), resp.StringValue(test.key)} for key, value := range test.presetValue.(map[string]string) { command = append(command, []resp.Value{ resp.StringValue(key), resp.StringValue(value)}..., ) } expected = strconv.Itoa(len(test.presetValue.(map[string]string))) } if err = client.WriteArray(command); err != nil { t.Error(err) } res, _, err := client.ReadValue() if err != nil { t.Error(err) } if !strings.EqualFold(res.String(), expected) { t.Errorf("expected preset response to be \"%s\", got %s", expected, res.String()) } } command := make([]resp.Value, len(test.command)) for i, c := range test.command { command[i] = resp.StringValue(c) } if err = client.WriteArray(command); err != nil { t.Error(err) } res, _, err := client.ReadValue() if err != nil { t.Error(err) } if test.expectedError != nil { if !strings.Contains(res.Error().Error(), test.expectedError.Error()) { t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error()) } return } for _, item := range res.Array() { if !slices.Contains(test.expectedResponse, item.String()) { t.Errorf("unexpected value \"%s\" in response", item.String()) } } }) } }) t.Run("Test_HandleHGETALL", func(t *testing.T) { t.Parallel() conn, err := internal.GetConnection("localhost", port) if err != nil { t.Error(err) return } defer func() { _ = conn.Close() }() client := resp.NewConn(conn) tests := []struct { name string key string presetValue interface{} command []string expectedResponse hash.Hash expectedError error }{ { name: "1. Return an array containing all the fields and values of the hash", key: "HGetAllKey1", presetValue: hash.Hash{"field1": hash.HashValue{Value: "value1"}, "field2": hash.HashValue{Value: "123456789"}, "field3": hash.HashValue{Value: "3.142"}}, command: []string{"HGETALL", "HGetAllKey1"}, expectedResponse: hash.Hash{"field1": hash.HashValue{Value: "value1"}, "field2": hash.HashValue{Value: "123456789"}, "field3": hash.HashValue{Value: "3.142"}}, expectedError: nil, }, { name: "2. Empty array response when trying to call HGETALL on non-existent key", key: "HGetAllKey2", presetValue: nil, command: []string{"HGETALL", "HGetAllKey2"}, expectedResponse: nil, expectedError: nil, }, { name: "3. Command too short", key: "HGetAllKey3", presetValue: nil, command: []string{"HGETALL"}, expectedResponse: nil, expectedError: errors.New(constants.WrongArgsResponse), }, { name: "4. Command too long", key: "HGetAllKey4", presetValue: nil, command: []string{"HGETALL", "HGetAllKey4", "HGetAllKey4"}, expectedResponse: nil, expectedError: errors.New(constants.WrongArgsResponse), }, { name: "5. Trying to get lengths on a non hash map returns error", key: "HGetAllKey5", presetValue: "Default value", command: []string{"HGETALL", "HGetAllKey5"}, expectedResponse: nil, expectedError: errors.New("value at HGetAllKey5 is not a hash"), }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { if test.presetValue != nil { var command []resp.Value var expected string switch test.presetValue.(type) { case string: command = []resp.Value{ resp.StringValue("SET"), resp.StringValue(test.key), resp.StringValue(test.presetValue.(string)), } expected = "ok" case hash.Hash: command = []resp.Value{resp.StringValue("HSET"), resp.StringValue(test.key)} for key, value := range test.presetValue.(hash.Hash) { command = append(command, []resp.Value{ resp.StringValue(key), resp.StringValue(value.Value.(string))}..., ) } expected = strconv.Itoa(len(test.presetValue.(hash.Hash))) } if err = client.WriteArray(command); err != nil { t.Error(err) } res, _, err := client.ReadValue() if err != nil { t.Error(err) } if !strings.EqualFold(res.String(), expected) { t.Errorf("expected preset response to be \"%s\", got %s", expected, res.String()) } } command := make([]resp.Value, len(test.command)) for i, c := range test.command { command[i] = resp.StringValue(c) } if err = client.WriteArray(command); err != nil { t.Error(err) } res, _, err := client.ReadValue() if err != nil { t.Error(err) } if test.expectedError != nil { if !strings.Contains(res.Error().Error(), test.expectedError.Error()) { t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error()) } return } if test.expectedResponse == nil { if len(res.Array()) != 0 { t.Errorf("expected response to be empty array, got %+v", res) } return } for i, item := range res.Array() { if i%2 == 0 { field := item.String() value := hash.HashValue{Value: res.Array()[i+1].String()} if test.expectedResponse[field] != value { t.Errorf("expected value at field \"%s\" to be \"%s\", got \"%s\"", field, test.expectedResponse[field], value) } } } }) } }) t.Run("Test_HandleHEXISTS", func(t *testing.T) { t.Parallel() conn, err := internal.GetConnection("localhost", port) if err != nil { t.Error(err) return } defer func() { _ = conn.Close() }() client := resp.NewConn(conn) tests := []struct { name string key string presetValue interface{} command []string expectedResponse bool expectedError error }{ { name: "1. Return 1 if the field exists in the hash", key: "HexistsKey1", presetValue: map[string]string{"field1": "value1", "field2": "123456789", "field3": "3.142"}, command: []string{"HEXISTS", "HexistsKey1", "field1"}, expectedResponse: true, expectedError: nil, }, { name: "2. 0 response when trying to call HEXISTS on non-existent key", key: "HexistsKey2", presetValue: nil, command: []string{"HEXISTS", "HexistsKey2", "field1"}, expectedResponse: false, expectedError: nil, }, { name: "3. Command too short", key: "HexistsKey3", presetValue: nil, command: []string{"HEXISTS", "HexistsKey3"}, expectedResponse: false, expectedError: errors.New(constants.WrongArgsResponse), }, { name: "4. Command too long", key: "HexistsKey4", presetValue: nil, command: []string{"HEXISTS", "HexistsKey4", "field1", "field2"}, expectedResponse: false, expectedError: errors.New(constants.WrongArgsResponse), }, { name: "5. Trying to get lengths on a non hash map returns error", key: "HexistsKey5", presetValue: "Default value", command: []string{"HEXISTS", "HexistsKey5", "field1"}, expectedResponse: false, expectedError: errors.New("value at HexistsKey5 is not a hash"), }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { if test.presetValue != nil { var command []resp.Value var expected string switch test.presetValue.(type) { case string: command = []resp.Value{ resp.StringValue("SET"), resp.StringValue(test.key), resp.StringValue(test.presetValue.(string)), } expected = "ok" case map[string]string: command = []resp.Value{resp.StringValue("HSET"), resp.StringValue(test.key)} for key, value := range test.presetValue.(map[string]string) { command = append(command, []resp.Value{ resp.StringValue(key), resp.StringValue(value)}..., ) } expected = strconv.Itoa(len(test.presetValue.(map[string]string))) } if err = client.WriteArray(command); err != nil { t.Error(err) } res, _, err := client.ReadValue() if err != nil { t.Error(err) } if !strings.EqualFold(res.String(), expected) { t.Errorf("expected preset response to be \"%s\", got %s", expected, res.String()) } } command := make([]resp.Value, len(test.command)) for i, c := range test.command { command[i] = resp.StringValue(c) } if err = client.WriteArray(command); err != nil { t.Error(err) } res, _, err := client.ReadValue() if err != nil { t.Error(err) } if test.expectedError != nil { if !strings.Contains(res.Error().Error(), test.expectedError.Error()) { t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error()) } return } if res.Bool() != test.expectedResponse { t.Errorf("expected response to be %v, got %v", test.expectedResponse, res.Bool()) } }) } }) t.Run("Test_HandleHDEL", func(t *testing.T) { t.Parallel() conn, err := internal.GetConnection("localhost", port) if err != nil { t.Error(err) return } defer func() { _ = conn.Close() }() client := resp.NewConn(conn) tests := []struct { name string key string presetValue interface{} command []string expectedResponse int expectedValue map[string]string expectedError error }{ { name: "1. Return count of deleted fields in the specified hash", key: "HdelKey1", presetValue: map[string]string{"field1": "value1", "field2": "123456789", "field3": "3.142", "field7": "value7"}, command: []string{"HDEL", "HdelKey1", "field1", "field2", "field3", "field4", "field5", "field6"}, expectedResponse: 3, expectedValue: map[string]string{"field7": "value7"}, expectedError: nil, }, { name: "2. 0 response when passing delete fields that are non-existent on valid hash", key: "HdelKey2", presetValue: map[string]string{"field1": "value1", "field2": "value2", "field3": "value3"}, command: []string{"HDEL", "HdelKey2", "field4", "field5", "field6"}, expectedResponse: 0, expectedValue: map[string]string{"field1": "value1", "field2": "value2", "field3": "value3"}, expectedError: nil, }, { name: "3. 0 response when trying to call HDEL on non-existent key", key: "HdelKey3", presetValue: nil, command: []string{"HDEL", "HdelKey3", "field1"}, expectedResponse: 0, expectedValue: nil, expectedError: nil, }, { name: "4. Command too short", key: "HdelKey4", presetValue: nil, command: []string{"HDEL", "HdelKey4"}, expectedResponse: 0, expectedValue: nil, expectedError: errors.New(constants.WrongArgsResponse), }, { name: "5. Trying to get lengths on a non hash map returns error", key: "HdelKey5", presetValue: "Default value", command: []string{"HDEL", "HdelKey5", "field1"}, expectedResponse: 0, expectedValue: nil, expectedError: errors.New("value at HdelKey5 is not a hash"), }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { if test.presetValue != nil { var command []resp.Value var expected string switch test.presetValue.(type) { case string: command = []resp.Value{ resp.StringValue("SET"), resp.StringValue(test.key), resp.StringValue(test.presetValue.(string)), } expected = "ok" case map[string]string: command = []resp.Value{resp.StringValue("HSET"), resp.StringValue(test.key)} for key, value := range test.presetValue.(map[string]string) { command = append(command, []resp.Value{ resp.StringValue(key), resp.StringValue(value)}..., ) } expected = strconv.Itoa(len(test.presetValue.(map[string]string))) } if err = client.WriteArray(command); err != nil { t.Error(err) } res, _, err := client.ReadValue() if err != nil { t.Error(err) } if !strings.EqualFold(res.String(), expected) { t.Errorf("expected preset response to be \"%s\", got %s", expected, res.String()) } } command := make([]resp.Value, len(test.command)) for i, c := range test.command { command[i] = resp.StringValue(c) } if err = client.WriteArray(command); err != nil { t.Error(err) } res, _, err := client.ReadValue() if err != nil { t.Error(err) } if test.expectedError != nil { if !strings.Contains(res.Error().Error(), test.expectedError.Error()) { t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error()) } return } if res.Integer() != test.expectedResponse { t.Errorf("expected response %d, got %d", test.expectedResponse, res.Integer()) } for idx, field := range res.Array() { if idx%2 == 0 { if res.Array()[idx+1].String() != test.expectedValue[field.String()] { t.Errorf( "expected value \"%+v\" for field \"%s\", got \"%+v\"", test.expectedValue[field.String()], field.String(), res.Array()[idx+1].String(), ) } } } }) } }) t.Run("Test_HandleHEXPIRE", func(t *testing.T) { t.Parallel() conn, err := internal.GetConnection("localhost", port) if err != nil { t.Error(err) return } defer func() { _ = conn.Close() }() client := resp.NewConn(conn) tests := []struct { name string key string presetValue hash.Hash command []string expectedValue string expectedError error }{ { name: "1. Set expiration for all keys in hash, no options.", key: "HexpireKey1", presetValue: hash.Hash{ "HexpireK1Field1": hash.HashValue{ Value: "default1", }, "HexpireK1Field2": hash.HashValue{ Value: "default2", }, "HexpireK1Field3": hash.HashValue{ Value: "default3", }, }, command: []string{"HEXPIRE", "HexpireKey1", "5", "FIELDS", "3", "HexpireK1Field1", "HexpireK1Field2", "HexpireK1Field3"}, expectedValue: "[1 1 1]", expectedError: nil, }, { name: "2. Set expiration for one key in hash, no options.", key: "HexpireKey2", presetValue: hash.Hash{ "HexpireK2Field1": hash.HashValue{ Value: "default1", }, }, command: []string{"HEXPIRE", "HexpireKey2", "5", "FIELDS", "1", "HexpireK2Field1"}, expectedValue: "[1]", expectedError: nil, }, { name: "3. Set expiration, expireTime already populated, no options.", key: "HexpireKey3", presetValue: hash.Hash{ "HexpireK3Field1": hash.HashValue{ Value: "default1", ExpireAt: mockClock.Now().Add(500 * time.Second), }, }, command: []string{"HEXPIRE", "HexpireKey3", "100", "FIELDS", "1", "HexpireK3Field1"}, expectedValue: "[1]", expectedError: nil, }, { name: "4. Set expiration, option NX with no expire time currently set.", key: "HexpireKey4", presetValue: hash.Hash{ "HexpireK4Field1": hash.HashValue{ Value: "default1", }, }, command: []string{"HEXPIRE", "HexpireKey4", "5", "NX", "FIELDS", "1", "HexpireK4Field1"}, expectedValue: "[1]", expectedError: nil, }, { name: "5. Set expiration, option NX with an expire time already set.", key: "HexpireKey5", presetValue: hash.Hash{ "HexpireK5Field1": hash.HashValue{ Value: "default1", ExpireAt: mockClock.Now().Add(500 * time.Second), }, }, command: []string{"HEXPIRE", "HexpireKey5", "100", "NX", "FIELDS", "1", "HexpireK5Field1"}, expectedValue: "[0]", expectedError: nil, }, { name: "6. Set expiration, option XX with no expire time currently set.", key: "HexpireKey6", presetValue: hash.Hash{ "HexpireK6Field1": hash.HashValue{ Value: "default1", }, }, command: []string{"HEXPIRE", "HexpireKey6", "5", "XX", "FIELDS", "1", "HexpireK6Field1"}, expectedValue: "[0]", expectedError: nil, }, { name: "7. Set expiration, option XX with expire time already set.", key: "HexpireKey7", presetValue: hash.Hash{ "HexpireK7Field1": hash.HashValue{ Value: "default1", ExpireAt: mockClock.Now().Add(500 * time.Second), }, }, command: []string{"HEXPIRE", "HexpireKey7", "100", "XX", "FIELDS", "1", "HexpireK7Field1"}, expectedValue: "[1]", expectedError: nil, }, { name: "8. Set expiration, option GT with expire time less than one provided.", key: "HexpireKey8", presetValue: hash.Hash{ "HexpireK8Field1": hash.HashValue{ Value: "default1", ExpireAt: mockClock.Now().Add(500 * time.Second), }, }, command: []string{"HEXPIRE", "HexpireKey8", "1000", "GT", "FIELDS", "1", "HexpireK8Field1"}, expectedValue: "[1]", expectedError: nil, }, { name: "9. Set expiration, option GT with expire time greater than one provided.", key: "HexpireKey9", presetValue: hash.Hash{ "HexpireK9Field1": hash.HashValue{ Value: "default1", ExpireAt: mockClock.Now().Add(500 * time.Second), }, }, command: []string{"HEXPIRE", "HexpireKey9", "100", "GT", "FIELDS", "1", "HexpireK9Field1"}, expectedValue: "[0]", expectedError: nil, }, { name: "10. Set expiration, option LT with expire time less than one provided.", key: "HexpireKey10", presetValue: hash.Hash{ "HexpireK10Field1": hash.HashValue{ Value: "default1", ExpireAt: mockClock.Now().Add(500 * time.Second), }, }, command: []string{"HEXPIRE", "HexpireKey10", "1000", "LT", "FIELDS", "1", "HexpireK10Field1"}, expectedValue: "[0]", expectedError: nil, }, { name: "11. Set expiration, option LT with expire time greater than one provided.", key: "HexpireKey11", presetValue: hash.Hash{ "HexpireK11Field1": hash.HashValue{ Value: "default1", ExpireAt: mockClock.Now().Add(500 * time.Second), }, }, command: []string{"HEXPIRE", "HexpireKey11", "100", "LT", "FIELDS", "1", "HexpireK11Field1"}, expectedValue: "[1]", expectedError: nil, }, { name: "12. Set expiration, provide 0 seconds.", key: "HexpireKey12", presetValue: hash.Hash{ "HexpireK12Field1": hash.HashValue{ Value: "default1", }, }, command: []string{"HEXPIRE", "HexpireKey12", "0", "FIELDS", "1", "HexpireK12Field1"}, expectedValue: "[2]", expectedError: nil, }, { name: "13. Attempt to set expiration for non existent key.", key: "HexpireKeyNOTEXIST", presetValue: nil, command: []string{"HEXPIRE", "HexpireKeyNOTEXIST", "100", "FIELDS", "1", "HexpireKNEField1"}, expectedValue: "[-2]", expectedError: nil, }, { name: "14. Attempt to set expiration for field that doesn't exist.", key: "HexpireKey14", presetValue: hash.Hash{ "HexpireK14Field1": hash.HashValue{ Value: "default1", }, }, command: []string{"HEXPIRE", "HexpireKey14", "100", "FIELDS", "2", "HexpireK14BadField1", "HexpireK14Field1"}, expectedValue: "[-2 1]", expectedError: nil, }, { name: "15. Set expiration, command wrong length.", key: "HexpireKey15", presetValue: hash.Hash{ "HexpireK15Field1": hash.HashValue{ Value: "default1", }, }, command: []string{"HEXPIRE", "HexpireKey15", "100", "1", "HexpireK15Field1"}, expectedError: errors.New("Error wrong number of arguments"), }, { name: "16. Set expiration, command filed numfields is not a number.", key: "HexpireKey16", presetValue: hash.Hash{ "HexpireK16Field1": hash.HashValue{ Value: "default1", }, }, command: []string{"HEXPIRE", "HexpireKey16", "100", "FIELDS", "one", "HexpireK16Field1"}, expectedError: errors.New("Error numberfields must be integer, was provided \"one\""), }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { // set key with preset value if test.presetValue != nil { var command []resp.Value var expected string command = []resp.Value{resp.StringValue("HSET"), resp.StringValue(test.key)} for key, value := range test.presetValue { command = append(command, []resp.Value{ resp.StringValue(key), resp.StringValue(value.Value.(string))}..., ) } expected = strconv.Itoa(len(test.presetValue)) if err = client.WriteArray(command); err != nil { t.Error(err) } res, _, err := client.ReadValue() if err != nil { t.Error(err) } if !strings.EqualFold(res.String(), expected) { t.Errorf("expected preset response to be \"%s\", got %s", expected, res.String()) } } // preset Expire Time for field, value := range test.presetValue { if value.ExpireAt != (time.Time{}) { cmd := []resp.Value{ resp.StringValue("HEXPIRE"), resp.StringValue(test.key), resp.StringValue("500"), resp.StringValue("FIELDS"), resp.StringValue("1"), resp.StringValue(field), } if err = client.WriteArray(cmd); err != nil { t.Error(err) } res, _, err := client.ReadValue() if err != nil { t.Error(err) } if res.String() != "[1]" { t.Errorf("Error presetting expire time - Key: %s, Field: %s, response: %s", test.key, field, res.String()) } } } // run HEXPIRE command command := make([]resp.Value, len(test.command)) for i, c := range test.command { command[i] = resp.StringValue(c) } if err = client.WriteArray(command); err != nil { t.Error(err) } res, _, err := client.ReadValue() if err != nil { t.Error(err) } if test.expectedError != nil { if !strings.Contains(res.Error().Error(), test.expectedError.Error()) { t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), res.Error()) } return } if res.String() != test.expectedValue { t.Errorf("expected response %q, got %q", test.expectedValue, res.String()) } }) } }) t.Run("Test_HandleHTTL", func(t *testing.T) { t.Parallel() conn, err := internal.GetConnection("localhost", port) if err != nil { t.Error(err) return } defer func() { _ = conn.Close() }() client := resp.NewConn(conn) tests := []struct { name string command []string key string presetValue interface{} setExpire bool expectedValue string expectedError error }{ { name: "1. Get TTL for one field when expireTime is set.", key: "HTTLKey1", command: []string{"HTTL", "HTTLKey1", "FIELDS", "1", "HTTLK1Field1"}, presetValue: hash.Hash{ "HTTLK1Field1": hash.HashValue{ Value: "default1", }, }, setExpire: true, expectedValue: "[5]", expectedError: nil, }, { name: "2. Get TTL for multiple fields when expireTime is set.", key: "HTTLKey2", command: []string{"HTTL", "HTTLKey2", "FIELDS", "3", "HTTLK2Field1", "HTTLK2Field2", "HTTLK2Field3"}, presetValue: hash.Hash{ "HTTLK2Field1": hash.HashValue{ Value: "default1", }, "HTTLK2Field2": hash.HashValue{ Value: "default1", }, "HTTLK2Field3": hash.HashValue{ Value: "default1", }, }, setExpire: true, expectedValue: "[5 5 5]", expectedError: nil, }, { name: "3. Get TTL for one field when expireTime is not set.", key: "HTTLKey3", command: []string{"HTTL", "HTTLKey3", "FIELDS", "1", "HTTLK3Field1"}, presetValue: hash.Hash{ "HTTLK3Field1": hash.HashValue{ Value: "default1", }, }, setExpire: false, expectedValue: "[-1]", expectedError: nil, }, { name: "4. Get TTL for multiple fields when expireTime is not set.", key: "HTTLKey4", command: []string{"HTTL", "HTTLKey4", "FIELDS", "3", "HTTLK4Field1", "HTTLK4Field2", "HTTLK4Field3"}, presetValue: hash.Hash{ "HTTLK4Field1": hash.HashValue{ Value: "default1", }, "HTTLK4Field2": hash.HashValue{ Value: "default1", }, "HTTLK4Field3": hash.HashValue{ Value: "default1", }, }, setExpire: false, expectedValue: "[-1 -1 -1]", expectedError: nil, }, { name: "5. Try to get TTL for key that doesn't exist.", key: "HTTLKeyNOTEXIST", command: []string{"HTTL", "HTTLKeyNOTEXIST", "FIELDS", "1", "HTTLK1Field1"}, presetValue: nil, setExpire: false, expectedValue: "[-2]", expectedError: nil, }, { name: "6. Try to get TTL for key that isn't a hash.", key: "HTTLKey6", command: []string{"HTTL", "HTTLKey6", "FIELDS", "1", "HTTLK6Field1"}, presetValue: "NotaHash", setExpire: false, expectedError: errors.New("Error value at HTTLKey6 is not a hash"), }, { name: "7. Command missing 'FIELDS'.", key: "HTTLKey7", command: []string{"HTTL", "HTTLKey7", "1", "HTTLK7Field1"}, presetValue: hash.Hash{ "HTTLK7Field1": hash.HashValue{ Value: "default1", }, }, setExpire: false, expectedError: errors.New("Error wrong number of arguments"), }, { name: "8. Command numfields provided isn't a number.", key: "HTTLKey8", command: []string{"HTTL", "HTTLKey8", "FIELDS", "one", "HTTLK8Field1"}, presetValue: hash.Hash{ "HTTLK8Field1": hash.HashValue{ Value: "default1", }, }, setExpire: false, expectedError: errors.New("Error expire time must be integer, was provided \"one\""), }, { name: "9. Command missing numfields.", key: "HTTLKey9", command: []string{"HTTL", "HTTLKey9", "FIELDS", "HTTLK9Field1"}, presetValue: hash.Hash{ "HTTLK9Field1": hash.HashValue{ Value: "default1", }, }, setExpire: false, expectedError: errors.New("Error wrong number of arguments"), }, { name: "10. Command FIELDS index contains something else.", key: "HTTLKey10", command: []string{"HTTL", "HTTLKey10", "NOTFIELDS", "1", "HTTLK10Field1"}, presetValue: hash.Hash{ "HTTLK10Field1": hash.HashValue{ Value: "default1", }, }, setExpire: false, expectedError: errors.New("Error invalid command provided"), }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { // set preset values if test.presetValue != nil { var command []resp.Value var expected string switch test.presetValue.(type) { case string: command = []resp.Value{ resp.StringValue("SET"), resp.StringValue(test.key), resp.StringValue(test.presetValue.(string)), } expected = "ok" case hash.Hash: command = []resp.Value{resp.StringValue("HSET"), resp.StringValue(test.key)} for key, value := range test.presetValue.(hash.Hash) { command = append(command, []resp.Value{ resp.StringValue(key), resp.StringValue(value.Value.(string))}..., ) } expected = strconv.Itoa(len(test.presetValue.(hash.Hash))) } if err = client.WriteArray(command); err != nil { t.Error(err) } res, _, err := client.ReadValue() if err != nil { t.Error(err) } if !strings.EqualFold(res.String(), expected) { t.Errorf("expected preset response to be \"%s\", got %s", expected, res.String()) } } if test.setExpire { // set expire times command := make([]resp.Value, len(test.presetValue.(hash.Hash))+5) command[0] = resp.StringValue("HEXPIRE") command[1] = resp.StringValue(test.key) command[2] = resp.StringValue("5") command[3] = resp.StringValue("FIELDS") command[4] = resp.StringValue(fmt.Sprintf("%v", (len(test.presetValue.(hash.Hash))))) i := 0 for k, _ := range test.presetValue.(hash.Hash) { command[5+i] = resp.StringValue(k) i++ } if err = client.WriteArray(command); err != nil { t.Error(err) } _, _, err := client.ReadValue() if err != nil { t.Error(err) } } // read TTL command := make([]resp.Value, len(test.command)) for i, v := range test.command { command[i] = resp.StringValue(v) } if err = client.WriteArray(command); err != nil { t.Error(err) } resp, _, err := client.ReadValue() if err != nil { t.Error(err) } if test.expectedError != nil { if !strings.Contains(resp.Error().Error(), test.expectedError.Error()) { t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), resp.Error()) } return } if resp.String() != test.expectedValue { t.Errorf("Expected value %v but got %v", test.expectedValue, resp) } }) } }) }