mirror of
https://github.com/EchoVault/SugarDB.git
synced 2025-09-27 04:16:06 +08:00
2248 lines
58 KiB
Go
2248 lines
58 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 sugardb
|
|
|
|
import (
|
|
"context"
|
|
"github.com/echovault/sugardb/internal"
|
|
"github.com/echovault/sugardb/internal/clock"
|
|
"github.com/echovault/sugardb/internal/config"
|
|
"github.com/echovault/sugardb/internal/constants"
|
|
"reflect"
|
|
"slices"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestSugarDB_Generic(t *testing.T) {
|
|
mockClock := clock.NewClock()
|
|
|
|
server := createSugarDB()
|
|
t.Cleanup(func() {
|
|
server.ShutDown()
|
|
})
|
|
|
|
t.Run("TestSugarDB_DEL", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
presetValues map[string]internal.KeyData
|
|
keys []string
|
|
want int
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "Delete several keys and return deleted count",
|
|
keys: []string{"del_key1", "del_key2", "del_key3", "del_key4", "del_key5"},
|
|
presetValues: map[string]internal.KeyData{
|
|
"del_key1": {Value: "value1", ExpireAt: time.Time{}},
|
|
"del_key2": {Value: "value2", ExpireAt: time.Time{}},
|
|
"del_key3": {Value: "value3", ExpireAt: time.Time{}},
|
|
"del_key4": {Value: "value4", ExpireAt: time.Time{}},
|
|
},
|
|
want: 4,
|
|
wantErr: false,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
if tt.presetValues != nil {
|
|
for k, d := range tt.presetValues {
|
|
presetKeyData(server, context.Background(), k, d)
|
|
}
|
|
}
|
|
got, err := server.Del(tt.keys...)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("DEL() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
if got != tt.want {
|
|
t.Errorf("DEL() got = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
|
|
t.Run("TestSugarDB_EXPIRE", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
presetValues map[string]internal.KeyData
|
|
cmd string
|
|
key string
|
|
time int
|
|
expireOpts ExpireOptions
|
|
want bool
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "Set new expire by seconds",
|
|
cmd: "EXPIRE",
|
|
key: "expire_key1",
|
|
time: 100,
|
|
expireOpts: nil,
|
|
presetValues: map[string]internal.KeyData{
|
|
"expire_key1": {Value: "value1", ExpireAt: time.Time{}},
|
|
},
|
|
want: true,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "Set new expire by milliseconds",
|
|
cmd: "PEXPIRE",
|
|
key: "expire_key2",
|
|
time: 1000,
|
|
expireOpts: nil,
|
|
presetValues: map[string]internal.KeyData{
|
|
"expire_key2": {Value: "value2", ExpireAt: time.Time{}},
|
|
},
|
|
want: true,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "Set new expire only when key does not have an expiry time with NX flag",
|
|
cmd: "EXPIRE",
|
|
key: "expire_key3",
|
|
time: 1000,
|
|
expireOpts: NX,
|
|
presetValues: map[string]internal.KeyData{
|
|
"expire_key3": {Value: "value3", ExpireAt: time.Time{}},
|
|
},
|
|
want: true,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "Return false when NX flag is provided and key already has an expiry time",
|
|
cmd: "EXPIRE",
|
|
key: "expire_key4",
|
|
time: 1000,
|
|
expireOpts: NX,
|
|
presetValues: map[string]internal.KeyData{
|
|
"expire_key4": {Value: "value4", ExpireAt: mockClock.Now().Add(1000 * time.Second)},
|
|
},
|
|
want: false,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "Set new expire time from now key only when the key already has an expiry time with XX flag",
|
|
cmd: "EXPIRE",
|
|
key: "expire_key5",
|
|
time: 1000,
|
|
expireOpts: XX,
|
|
presetValues: map[string]internal.KeyData{
|
|
"expire_key5": {Value: "value5", ExpireAt: mockClock.Now().Add(30 * time.Second)},
|
|
},
|
|
want: true,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "Return false when key does not have an expiry and the XX flag is provided",
|
|
cmd: "EXPIRE",
|
|
time: 1000,
|
|
expireOpts: XX,
|
|
key: "expire_key6",
|
|
presetValues: map[string]internal.KeyData{
|
|
"expire_key6": {Value: "value6", ExpireAt: time.Time{}},
|
|
},
|
|
want: false,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "Set expiry time when the provided time is after the current expiry time when GT flag is provided",
|
|
cmd: "EXPIRE",
|
|
key: "expire_key7",
|
|
time: 100000,
|
|
expireOpts: GT,
|
|
presetValues: map[string]internal.KeyData{
|
|
"expire_key7": {Value: "value7", ExpireAt: mockClock.Now().Add(30 * time.Second)},
|
|
},
|
|
want: true,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "Return false when GT flag is passed and current expiry time is greater than provided time",
|
|
cmd: "EXPIRE",
|
|
key: "expire_key8",
|
|
time: 1000,
|
|
expireOpts: GT,
|
|
presetValues: map[string]internal.KeyData{
|
|
"expire_key8": {Value: "value8", ExpireAt: mockClock.Now().Add(3000 * time.Second)},
|
|
},
|
|
want: false,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "Return false when GT flag is passed and key does not have an expiry time",
|
|
cmd: "EXPIRE",
|
|
key: "expire_key9",
|
|
time: 1000,
|
|
expireOpts: GT,
|
|
presetValues: map[string]internal.KeyData{
|
|
"expire_key9": {Value: "value9", ExpireAt: time.Time{}},
|
|
},
|
|
want: false,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "Set expiry time when the provided time is before the current expiry time when LT flag is provided",
|
|
cmd: "EXPIRE",
|
|
key: "expire_key10",
|
|
time: 1000,
|
|
expireOpts: LT,
|
|
presetValues: map[string]internal.KeyData{
|
|
"expire_key10": {Value: "value10", ExpireAt: mockClock.Now().Add(3000 * time.Second)},
|
|
},
|
|
want: true,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "Return false when LT flag is passed and current expiry time is less than provided time",
|
|
cmd: "EXPIRE",
|
|
key: "expire_key11",
|
|
time: 50000,
|
|
expireOpts: LT,
|
|
presetValues: map[string]internal.KeyData{
|
|
"expire_key11": {Value: "value11", ExpireAt: mockClock.Now().Add(30 * time.Second)},
|
|
},
|
|
want: false,
|
|
wantErr: false,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
if tt.presetValues != nil {
|
|
for k, d := range tt.presetValues {
|
|
presetKeyData(server, context.Background(), k, d)
|
|
}
|
|
}
|
|
var got bool
|
|
var err error
|
|
if strings.EqualFold(tt.cmd, "PEXPIRE") {
|
|
got, err = server.PExpire(tt.key, tt.time, tt.expireOpts)
|
|
} else {
|
|
got, err = server.Expire(tt.key, tt.time, tt.expireOpts)
|
|
}
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("(P)EXPIRE() error = %v, wantErr %v, key %s", err, tt.wantErr, tt.key)
|
|
return
|
|
}
|
|
if got != tt.want {
|
|
t.Errorf("(P)EXPIRE() got = %v, want %v, key %s", got, tt.want, tt.key)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
|
|
t.Run("TestSugarDB_EXPIREAT", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
presetValues map[string]internal.KeyData
|
|
cmd string
|
|
key string
|
|
time int
|
|
expireAtOpts ExpireOptions
|
|
want int
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "Set new expire by unix seconds",
|
|
cmd: "EXPIREAT",
|
|
key: "expireat_key1",
|
|
expireAtOpts: nil,
|
|
time: int(mockClock.Now().Add(1000 * time.Second).Unix()),
|
|
presetValues: map[string]internal.KeyData{
|
|
"expireat_key1": {Value: "value1", ExpireAt: time.Time{}},
|
|
},
|
|
want: 1,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "Set new expire by milliseconds",
|
|
cmd: "PEXPIREAT",
|
|
key: "expireat_key2",
|
|
expireAtOpts: nil,
|
|
time: int(mockClock.Now().Add(1000 * time.Second).UnixMilli()),
|
|
presetValues: map[string]internal.KeyData{
|
|
"expireat_key2": {Value: "value2", ExpireAt: time.Time{}},
|
|
},
|
|
want: 1,
|
|
wantErr: false,
|
|
},
|
|
{ // 3.
|
|
name: "Set new expire only when key does not have an expiry time with NX flag",
|
|
cmd: "EXPIREAT",
|
|
key: "expireat_key3",
|
|
time: int(mockClock.Now().Add(1000 * time.Second).Unix()),
|
|
expireAtOpts: NX,
|
|
presetValues: map[string]internal.KeyData{
|
|
"expireat_key3": {Value: "value3", ExpireAt: time.Time{}},
|
|
},
|
|
want: 1,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "Return 0, when NX flag is provided and key already has an expiry time",
|
|
cmd: "EXPIREAT",
|
|
time: int(mockClock.Now().Add(1000 * time.Second).Unix()),
|
|
expireAtOpts: NX,
|
|
key: "expireat_key4",
|
|
presetValues: map[string]internal.KeyData{
|
|
"expireat_key4": {Value: "value4", ExpireAt: mockClock.Now().Add(1000 * time.Second)},
|
|
},
|
|
want: 0,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "Set new expire time from now key only when the key already has an expiry time with XX flag",
|
|
cmd: "EXPIREAT",
|
|
time: int(mockClock.Now().Add(1000 * time.Second).Unix()),
|
|
key: "expireat_key5",
|
|
expireAtOpts: XX,
|
|
presetValues: map[string]internal.KeyData{
|
|
"expireat_key5": {Value: "value5", ExpireAt: mockClock.Now().Add(30 * time.Second)},
|
|
},
|
|
want: 1,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "Return 0 when key does not have an expiry and the XX flag is provided",
|
|
cmd: "EXPIREAT",
|
|
key: "expireat_key6",
|
|
time: int(mockClock.Now().Add(1000 * time.Second).Unix()),
|
|
expireAtOpts: XX,
|
|
presetValues: map[string]internal.KeyData{
|
|
"expireat_key6": {Value: "value6", ExpireAt: time.Time{}},
|
|
},
|
|
want: 0,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "Set expiry time when the provided time is after the current expiry time when GT flag is provided",
|
|
cmd: "EXPIREAT",
|
|
key: "expireat_key7",
|
|
time: int(mockClock.Now().Add(1000 * time.Second).Unix()),
|
|
expireAtOpts: GT,
|
|
presetValues: map[string]internal.KeyData{
|
|
"expireat_key7": {Value: "value7", ExpireAt: mockClock.Now().Add(30 * time.Second)},
|
|
},
|
|
want: 1,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "Return 0 when GT flag is passed and current expiry time is greater than provided time",
|
|
cmd: "EXPIREAT",
|
|
key: "expireat_key8",
|
|
time: int(mockClock.Now().Add(1000 * time.Second).Unix()),
|
|
expireAtOpts: GT,
|
|
presetValues: map[string]internal.KeyData{
|
|
"expireat_key8": {Value: "value8", ExpireAt: mockClock.Now().Add(3000 * time.Second)},
|
|
},
|
|
want: 0,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "Return 0 when GT flag is passed and key does not have an expiry time",
|
|
cmd: "EXPIREAT",
|
|
key: "expireat_key9",
|
|
time: int(mockClock.Now().Add(1000 * time.Second).Unix()),
|
|
expireAtOpts: GT,
|
|
presetValues: map[string]internal.KeyData{
|
|
"expireat_key9": {Value: "value9", ExpireAt: time.Time{}},
|
|
},
|
|
want: 0,
|
|
},
|
|
{
|
|
name: "Set expiry time when the provided time is before the current expiry time when LT flag is provided",
|
|
cmd: "EXPIREAT",
|
|
key: "expireat_key10",
|
|
time: int(mockClock.Now().Add(1000 * time.Second).Unix()),
|
|
expireAtOpts: LT,
|
|
presetValues: map[string]internal.KeyData{
|
|
"expireat_key10": {Value: "value10", ExpireAt: mockClock.Now().Add(3000 * time.Second)},
|
|
},
|
|
want: 1,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "Return 0 when LT flag is passed and current expiry time is less than provided time",
|
|
cmd: "EXPIREAT",
|
|
key: "expireat_key11",
|
|
time: int(mockClock.Now().Add(3000 * time.Second).Unix()),
|
|
expireAtOpts: LT,
|
|
presetValues: map[string]internal.KeyData{
|
|
"expireat_key11": {Value: "value11", ExpireAt: mockClock.Now().Add(1000 * time.Second)},
|
|
},
|
|
want: 0,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "Return 0 when LT flag is passed and key does not have an expiry time",
|
|
cmd: "EXPIREAT",
|
|
key: "expireat_key12",
|
|
time: int(mockClock.Now().Add(1000 * time.Second).Unix()),
|
|
expireAtOpts: LT,
|
|
presetValues: map[string]internal.KeyData{
|
|
"expireat_key12": {Value: "value12", ExpireAt: time.Time{}},
|
|
},
|
|
want: 1,
|
|
wantErr: false,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
if tt.presetValues != nil {
|
|
for k, d := range tt.presetValues {
|
|
presetKeyData(server, context.Background(), k, d)
|
|
}
|
|
}
|
|
var got int
|
|
var err error
|
|
if strings.EqualFold(tt.cmd, "PEXPIREAT") {
|
|
got, err = server.PExpireAt(tt.key, tt.time, tt.expireAtOpts)
|
|
} else {
|
|
got, err = server.ExpireAt(tt.key, tt.time, tt.expireAtOpts)
|
|
}
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("(P)EXPIREAT() error = %v, wantErr %v, KEY %s", err, tt.wantErr, tt.key)
|
|
return
|
|
}
|
|
if got != tt.want {
|
|
t.Errorf("(P)EXPIREAT() got = %v, want %v, KEY %s", got, tt.want, tt.key)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
|
|
t.Run("TestSugarDB_EXPIRETIME", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
presetValues map[string]internal.KeyData
|
|
key string
|
|
expiretimeFunc func(key string) (int, error)
|
|
want int
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "Return expire time in seconds",
|
|
key: "expiretime_key1",
|
|
presetValues: map[string]internal.KeyData{
|
|
"expiretime_key1": {Value: "value1", ExpireAt: mockClock.Now().Add(100 * time.Second)},
|
|
},
|
|
expiretimeFunc: server.ExpireTime,
|
|
want: int(mockClock.Now().Add(100 * time.Second).Unix()),
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "Return expire time in milliseconds",
|
|
key: "expiretime_key2",
|
|
presetValues: map[string]internal.KeyData{
|
|
"expiretime_key2": {Value: "value2", ExpireAt: mockClock.Now().Add(4096 * time.Millisecond)},
|
|
},
|
|
expiretimeFunc: server.PExpireTime,
|
|
want: int(mockClock.Now().Add(4096 * time.Millisecond).UnixMilli()),
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "If the key is non-volatile, return -1",
|
|
key: "expiretime_key3",
|
|
presetValues: map[string]internal.KeyData{
|
|
"expiretime_key3": {Value: "value3", ExpireAt: time.Time{}},
|
|
},
|
|
expiretimeFunc: server.PExpireTime,
|
|
want: -1,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "If the key is non-existent return -2",
|
|
presetValues: nil,
|
|
expiretimeFunc: server.PExpireTime,
|
|
want: -2,
|
|
wantErr: false,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
if tt.presetValues != nil {
|
|
for k, d := range tt.presetValues {
|
|
presetKeyData(server, context.Background(), k, d)
|
|
}
|
|
}
|
|
got, err := tt.expiretimeFunc(tt.key)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("(P)EXPIRETIME() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
if got != tt.want {
|
|
t.Errorf("(P)EXPIRETIME() got = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
|
|
t.Run("TestSugarDB_GET", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
presetValue interface{}
|
|
key string
|
|
want string
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "Return string from existing key",
|
|
presetValue: "value1",
|
|
key: "get_key1",
|
|
want: "value1",
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "Return empty string if the key does not exist",
|
|
presetValue: nil,
|
|
key: "get_key2",
|
|
want: "",
|
|
wantErr: false,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
if tt.presetValue != nil {
|
|
err := presetValue(server, context.Background(), tt.key, tt.presetValue)
|
|
if err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
}
|
|
got, err := server.Get(tt.key)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("GET() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
if got != tt.want {
|
|
t.Errorf("GET() got = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
|
|
t.Run("TestSugarDB_MGET", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
presetValues map[string]interface{}
|
|
keys []string
|
|
want []string
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "1. Get all values in the same order the keys were provided in",
|
|
presetValues: map[string]interface{}{
|
|
"mget_key1": "value1", "mget_key2": "value2", "mget_key3": "value3", "mget_key4": "value4",
|
|
},
|
|
keys: []string{"mget_key1", "mget_key4", "mget_key2", "mget_key3", "mget_key1"},
|
|
want: []string{"value1", "value4", "value2", "value3", "value1"},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "2. Return empty strings for non-existent keys",
|
|
presetValues: map[string]interface{}{
|
|
"mget_key5": "value5", "mget_key6": "value6", "mget_key7": "value7",
|
|
},
|
|
keys: []string{
|
|
"mget_key5", "mget_key6", "mget_non-existent", "mget_non-existent", "mget_key7", "mget_non-existent",
|
|
},
|
|
want: []string{"value5", "value6", "", "", "value7", ""},
|
|
wantErr: false,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
if tt.presetValues != nil {
|
|
for k, v := range tt.presetValues {
|
|
err := presetValue(server, context.Background(), k, v)
|
|
if err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
got, err := server.MGet(tt.keys...)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("MGET() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
if len(got) != len(tt.want) {
|
|
t.Errorf("MGET() got = %v, want %v", got, tt.want)
|
|
}
|
|
for _, g := range got {
|
|
if !slices.Contains(tt.want, g) {
|
|
t.Errorf("MGET() got = %v, want %v", got, tt.want)
|
|
}
|
|
}
|
|
if !reflect.DeepEqual(got, tt.want) {
|
|
t.Errorf("MGET() got = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
|
|
t.Run("TestSugarDB_SET", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
SetOptions := func(W SetWriteOption, EX SetExOption, EXTIME int, GET bool) SETOptions {
|
|
return SETOptions{
|
|
WriteOpt: W,
|
|
ExpireOpt: EX,
|
|
ExpireTime: EXTIME,
|
|
Get: GET,
|
|
}
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
presetValues map[string]internal.KeyData
|
|
key string
|
|
value string
|
|
options SETOptions
|
|
wantOk bool
|
|
wantPrev string
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "1. Set normal value",
|
|
presetValues: nil,
|
|
key: "set_key1",
|
|
value: "value1",
|
|
options: SetOptions(nil, nil, 0, false),
|
|
wantOk: true,
|
|
wantPrev: "",
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "2. Only set the value if the key does not exist",
|
|
presetValues: nil,
|
|
key: "set_key2",
|
|
value: "value2",
|
|
options: SetOptions(SETNX, nil, 0, false),
|
|
wantOk: true,
|
|
wantPrev: "",
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "3. Throw error when value already exists with NX flag passed",
|
|
presetValues: map[string]internal.KeyData{
|
|
"set_key3": {
|
|
Value: "preset-value3",
|
|
ExpireAt: time.Time{},
|
|
},
|
|
},
|
|
key: "set_key3",
|
|
value: "value3",
|
|
options: SetOptions(SETNX, nil, 0, false),
|
|
wantOk: false,
|
|
wantPrev: "",
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "4. Set new key value when key exists with XX flag passed",
|
|
presetValues: map[string]internal.KeyData{
|
|
"set_key4": {
|
|
Value: "preset-value4",
|
|
ExpireAt: time.Time{},
|
|
},
|
|
},
|
|
key: "set_key4",
|
|
value: "value4",
|
|
options: SetOptions(SETXX, nil, 0, false),
|
|
wantOk: true,
|
|
wantPrev: "",
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "5. Return error when setting non-existent key with XX flag",
|
|
presetValues: nil,
|
|
key: "set_key5",
|
|
value: "value5",
|
|
options: SetOptions(SETXX, nil, 0, false),
|
|
wantOk: false,
|
|
wantPrev: "",
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "6. Set expiry time on the key to 100 seconds from now",
|
|
presetValues: nil,
|
|
key: "set_key6",
|
|
value: "value6",
|
|
options: SetOptions(nil, SETEX, 100, false),
|
|
wantOk: true,
|
|
wantPrev: "",
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "7. Set expiry time on the key in unix milliseconds",
|
|
presetValues: nil,
|
|
key: "set_key7",
|
|
value: "value7",
|
|
options: SetOptions(nil, SETPX, 4096, false),
|
|
wantOk: true,
|
|
wantPrev: "",
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "8. Set exact expiry time in seconds from unix epoch",
|
|
presetValues: nil,
|
|
key: "set_key8",
|
|
value: "value8",
|
|
options: SetOptions(nil, SETEXAT, int(mockClock.Now().Add(200*time.Second).Unix()), false),
|
|
wantOk: true,
|
|
wantPrev: "",
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "9. Set exact expiry time in milliseconds from unix epoch",
|
|
key: "set_key9",
|
|
value: "value9",
|
|
options: SetOptions(nil, SETPXAT, int(mockClock.Now().Add(4096*time.Millisecond).UnixMilli()), false),
|
|
presetValues: nil,
|
|
wantOk: true,
|
|
wantPrev: "",
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "10. Get the previous value when GET flag is passed",
|
|
presetValues: map[string]internal.KeyData{
|
|
"set_key10": {
|
|
Value: "previous-value",
|
|
ExpireAt: time.Time{},
|
|
},
|
|
},
|
|
key: "set_key10",
|
|
value: "value10",
|
|
options: SetOptions(nil, SETEX, 1000, true),
|
|
wantOk: true,
|
|
wantPrev: "previous-value",
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "11. Return nil when GET value is passed and no previous value exists",
|
|
presetValues: nil,
|
|
key: "set_key11",
|
|
value: "value11",
|
|
options: SetOptions(nil, SETEX, 1000, true),
|
|
wantOk: true,
|
|
wantPrev: "",
|
|
wantErr: false,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
if tt.presetValues != nil {
|
|
for k, d := range tt.presetValues {
|
|
presetKeyData(server, context.Background(), k, d)
|
|
}
|
|
}
|
|
previousValue, ok, err := server.Set(
|
|
tt.key,
|
|
tt.value,
|
|
tt.options,
|
|
)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("SET() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
if ok != tt.wantOk {
|
|
t.Errorf("SET() ok got = %v, want %v", ok, tt.wantOk)
|
|
}
|
|
if previousValue != tt.wantPrev {
|
|
t.Errorf("SET() previous value got = %v, want %v", previousValue, tt.wantPrev)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
|
|
t.Run("TestSugarDB_MSET", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
kvPairs map[string]string
|
|
want bool
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "1. Set multiple keys",
|
|
kvPairs: map[string]string{"mset_key1": "value1", "mset_key2": "10", "mset_key3": "3.142"},
|
|
want: true,
|
|
wantErr: false,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
got, err := server.MSet(tt.kvPairs)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("MSET() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
if got != tt.want {
|
|
t.Errorf("MSET() got = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
|
|
t.Run("TestSugarDB_PERSIST", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
presetValues map[string]internal.KeyData
|
|
key string
|
|
want bool
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "1. Successfully persist a volatile key",
|
|
key: "persist_key1",
|
|
presetValues: map[string]internal.KeyData{
|
|
"persist_key1": {Value: "value1", ExpireAt: mockClock.Now().Add(1000 * time.Second)},
|
|
},
|
|
want: true,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "2. Return false when trying to persist a non-existent key",
|
|
key: "persist_key2",
|
|
presetValues: nil,
|
|
want: false,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "3. Return false when trying to persist a non-volatile key",
|
|
key: "persist_key3",
|
|
presetValues: map[string]internal.KeyData{
|
|
"persist_key3": {Value: "value3", ExpireAt: time.Time{}},
|
|
},
|
|
want: false,
|
|
wantErr: false,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
if tt.presetValues != nil {
|
|
for k, d := range tt.presetValues {
|
|
presetKeyData(server, context.Background(), k, d)
|
|
}
|
|
}
|
|
got, err := server.Persist(tt.key)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("PERSIST() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
if got != tt.want {
|
|
t.Errorf("PERSIST() got = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
|
|
t.Run("TestSugarDB_TTL", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
presetValues map[string]internal.KeyData
|
|
key string
|
|
ttlFunc func(key string) (int, error)
|
|
want int
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "1. Return TTL time in seconds",
|
|
key: "ttl_key1",
|
|
presetValues: map[string]internal.KeyData{
|
|
"ttl_key1": {Value: "value1", ExpireAt: mockClock.Now().Add(100 * time.Second)},
|
|
},
|
|
ttlFunc: server.TTL,
|
|
want: 100,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "2. Return TTL time in milliseconds",
|
|
key: "ttl_key2",
|
|
ttlFunc: server.PTTL,
|
|
presetValues: map[string]internal.KeyData{
|
|
"ttl_key2": {Value: "value2", ExpireAt: mockClock.Now().Add(4096 * time.Millisecond)},
|
|
},
|
|
want: 4096,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "3. If the key is non-volatile, return -1",
|
|
key: "ttl_key3",
|
|
ttlFunc: server.TTL,
|
|
presetValues: map[string]internal.KeyData{
|
|
"ttl_key3": {Value: "value3", ExpireAt: time.Time{}},
|
|
},
|
|
want: -1,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "4. If the key is non-existent return -2",
|
|
key: "ttl_key4",
|
|
ttlFunc: server.TTL,
|
|
presetValues: nil,
|
|
want: -2,
|
|
wantErr: false,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
if tt.presetValues != nil {
|
|
for k, d := range tt.presetValues {
|
|
presetKeyData(server, context.Background(), k, d)
|
|
}
|
|
}
|
|
got, err := tt.ttlFunc(tt.key)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("TTL() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
if got != tt.want {
|
|
t.Errorf("TTL() got = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
|
|
t.Run("TestSugarDB_INCR", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
key string
|
|
presetValues map[string]internal.KeyData
|
|
want int
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "1. Increment non-existent key",
|
|
key: "IncrKey1",
|
|
presetValues: nil,
|
|
want: 1,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "2. Increment existing key with integer value",
|
|
key: "IncrKey2",
|
|
presetValues: map[string]internal.KeyData{
|
|
"IncrKey2": {Value: "5"},
|
|
},
|
|
want: 6,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "3. Increment existing key with non-integer value",
|
|
key: "IncrKey3",
|
|
presetValues: map[string]internal.KeyData{
|
|
"IncrKey3": {Value: "not_an_int"},
|
|
},
|
|
want: 0,
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "4. Increment existing key with int64 value",
|
|
key: "IncrKey4",
|
|
presetValues: map[string]internal.KeyData{
|
|
"IncrKey4": {Value: int64(10)},
|
|
},
|
|
want: 11,
|
|
wantErr: false,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
if tt.presetValues != nil {
|
|
for k, d := range tt.presetValues {
|
|
presetKeyData(server, context.Background(), k, d)
|
|
}
|
|
}
|
|
got, err := server.Incr(tt.key)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("INCR() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
if got != tt.want {
|
|
t.Errorf("INCR() got = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
|
|
t.Run("TestSugarDB_DECR", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
key string
|
|
presetValues map[string]internal.KeyData
|
|
want int
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "1. Decrement non-existent key",
|
|
key: "DecrKey1",
|
|
presetValues: nil,
|
|
want: -1,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "2. Decrement existing key with integer value",
|
|
key: "DecrKey2",
|
|
presetValues: map[string]internal.KeyData{
|
|
"DecrKey2": {Value: "5"},
|
|
},
|
|
want: 4,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "3. Decrement existing key with non-integer value",
|
|
key: "DecrKey3",
|
|
presetValues: map[string]internal.KeyData{
|
|
"DecrKey3": {Value: "not_an_int"},
|
|
},
|
|
want: 0,
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "4. Decrement existing key with int64 value",
|
|
key: "DecrKey4",
|
|
presetValues: map[string]internal.KeyData{
|
|
"DecrKey4": {Value: int64(10)},
|
|
},
|
|
want: 9,
|
|
wantErr: false,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
if tt.presetValues != nil {
|
|
for k, d := range tt.presetValues {
|
|
presetKeyData(server, context.Background(), k, d)
|
|
}
|
|
}
|
|
got, err := server.Decr(tt.key)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("DECR() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
if got != tt.want {
|
|
t.Errorf("DECR() got = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
|
|
t.Run("TestSugarDB_INCRBY", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
key string
|
|
increment string
|
|
presetValues map[string]internal.KeyData
|
|
want int
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "1. Increment non-existent key by 4",
|
|
key: "IncrByKey1",
|
|
increment: "4",
|
|
presetValues: nil,
|
|
want: 4,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "2. Increment existing key with integer value by 3",
|
|
key: "IncrByKey2",
|
|
increment: "3",
|
|
presetValues: map[string]internal.KeyData{
|
|
"IncrByKey2": {Value: "5"},
|
|
},
|
|
want: 8,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "3. Increment existing key with non-integer value by 2",
|
|
key: "IncrByKey3",
|
|
increment: "2",
|
|
presetValues: map[string]internal.KeyData{
|
|
"IncrByKey3": {Value: "not_an_int"},
|
|
},
|
|
want: 0,
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "4. Increment existing key with int64 value by 7",
|
|
key: "IncrByKey4",
|
|
increment: "7",
|
|
presetValues: map[string]internal.KeyData{
|
|
"IncrByKey4": {Value: int64(10)},
|
|
},
|
|
want: 17,
|
|
wantErr: false,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
if tt.presetValues != nil {
|
|
for k, d := range tt.presetValues {
|
|
presetKeyData(server, context.Background(), k, d)
|
|
}
|
|
}
|
|
got, err := server.IncrBy(tt.key, tt.increment)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("IncrBy() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
if got != tt.want {
|
|
t.Errorf("IncrBy() got = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
|
|
t.Run("TestSugarDB_INCRBYFLOAT", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
key string
|
|
increment string
|
|
presetValues map[string]internal.KeyData
|
|
want float64
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "1. Increment non-existent key by 2.5",
|
|
key: "IncrByFloatKey1",
|
|
increment: "2.5",
|
|
presetValues: nil,
|
|
want: 2.5,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "2. Increment existing key with integer value by 1.2",
|
|
key: "IncrByFloatKey2",
|
|
increment: "1.2",
|
|
presetValues: map[string]internal.KeyData{
|
|
"IncrByFloatKey2": {Value: "5"},
|
|
},
|
|
want: 6.2,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "3. Increment existing key with float value by 0.7",
|
|
key: "IncrByFloatKey4",
|
|
increment: "0.7",
|
|
presetValues: map[string]internal.KeyData{
|
|
"IncrByFloatKey4": {Value: "10.0"},
|
|
},
|
|
want: 10.7,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "4. Increment existing key with scientific notation value by 200",
|
|
key: "IncrByFloatKey5",
|
|
increment: "200",
|
|
presetValues: map[string]internal.KeyData{
|
|
"IncrByFloatKey5": {Value: "5.0e3"},
|
|
},
|
|
want: 5200,
|
|
wantErr: false,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
if tt.presetValues != nil {
|
|
for k, d := range tt.presetValues {
|
|
presetKeyData(server, context.Background(), k, d)
|
|
}
|
|
}
|
|
got, err := server.IncrByFloat(tt.key, tt.increment)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("IncrByFloat() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
if err == nil && got != tt.want {
|
|
t.Errorf("IncrByFloat() got = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
|
|
t.Run("TestSugarDB_DECRBY", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
key string
|
|
decrement string
|
|
presetValues map[string]internal.KeyData
|
|
want int
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "1. Decrement non-existent key by 4",
|
|
key: "DecrByKey1",
|
|
decrement: "4",
|
|
presetValues: nil,
|
|
want: -4,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "2. Decrement existing key with integer value by 3",
|
|
key: "DecrByKey2",
|
|
decrement: "3",
|
|
presetValues: map[string]internal.KeyData{
|
|
"DecrByKey2": {Value: "-5"},
|
|
},
|
|
want: -8,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "3. Decrement existing key with non-integer value by 2",
|
|
key: "DecrByKey3",
|
|
decrement: "2",
|
|
presetValues: map[string]internal.KeyData{
|
|
"DecrByKey3": {Value: "not_an_int"},
|
|
},
|
|
want: 0,
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "4. Decrement existing key with int64 value by 7",
|
|
key: "DecrByKey4",
|
|
decrement: "7",
|
|
presetValues: map[string]internal.KeyData{
|
|
"DecrByKey4": {Value: int64(10)}},
|
|
want: 3,
|
|
wantErr: false,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
if tt.presetValues != nil {
|
|
for k, d := range tt.presetValues {
|
|
presetKeyData(server, context.Background(), k, d)
|
|
}
|
|
}
|
|
got, err := server.DecrBy(tt.key, tt.decrement)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("DecrBy() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
if got != tt.want {
|
|
t.Errorf("DecrBy() got = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
|
|
t.Run("TestSugarDB_Rename", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
oldKey string
|
|
newKey string
|
|
presetValues map[string]internal.KeyData
|
|
want string
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "1. Rename existing key",
|
|
oldKey: "rename_oldKey1",
|
|
newKey: "rename_newKey1",
|
|
presetValues: map[string]internal.KeyData{"rename_oldKey1": {Value: "value1"}},
|
|
want: "OK",
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "2. Rename non-existent key",
|
|
oldKey: "rename_oldKey2",
|
|
newKey: "rename_newKey2",
|
|
presetValues: nil,
|
|
want: "",
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "3. Rename to existing key",
|
|
oldKey: "rename_oldKey3",
|
|
newKey: "rename_newKey4",
|
|
presetValues: map[string]internal.KeyData{
|
|
"rename_oldKey3": {Value: "value3"},
|
|
"rename_newKey4": {Value: "value4"},
|
|
},
|
|
want: "OK",
|
|
wantErr: false,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
if tt.presetValues != nil {
|
|
for k, d := range tt.presetValues {
|
|
presetKeyData(server, context.Background(), k, d)
|
|
}
|
|
}
|
|
got, err := server.Rename(tt.oldKey, tt.newKey)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("Rename() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
if got != tt.want {
|
|
t.Errorf("Rename() got = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
|
|
t.Run("TestSugarDB_RENAMENX", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
oldKey string
|
|
newKey string
|
|
presetValues map[string]internal.KeyData
|
|
want string
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "1. Rename existing key",
|
|
oldKey: "renamenx_oldKey1",
|
|
newKey: "renamenx_newKey1",
|
|
presetValues: map[string]internal.KeyData{"renamenx_oldKey1": {Value: "value1"}},
|
|
want: "OK",
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "2. Rename non-existent key",
|
|
oldKey: "renamenx_oldKey2",
|
|
newKey: "renamenx_newKey2",
|
|
presetValues: nil,
|
|
want: "",
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "3. Rename to existing key",
|
|
oldKey: "renamenx_oldKey3",
|
|
newKey: "renamenx_newKey4",
|
|
presetValues: map[string]internal.KeyData{
|
|
"renamenx_oldKey3": {Value: "value3"},
|
|
"renamenx_newKey4": {Value: "value4"},
|
|
},
|
|
want: "",
|
|
wantErr: true,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
if tt.presetValues != nil {
|
|
for k, d := range tt.presetValues {
|
|
presetKeyData(server, context.Background(), k, d)
|
|
}
|
|
}
|
|
got, err := server.RenameNX(tt.oldKey, tt.newKey)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("Rename() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
if got != tt.want {
|
|
t.Errorf("Rename() got = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
|
|
t.Run("TestSugarDB_RANDOMKEY", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
server := createSugarDB()
|
|
t.Cleanup(func() {
|
|
server.ShutDown()
|
|
})
|
|
|
|
// test without keys
|
|
got, err := server.RandomKey()
|
|
if err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
if got != "" {
|
|
t.Errorf("RANDOMKEY error, expected emtpy string (%v), got (%v)", []byte(""), []byte(got))
|
|
}
|
|
|
|
// test with keys
|
|
testKeys := []string{"randomkey_key1", "randomkey_key2", "randomkey_key3"}
|
|
for _, k := range testKeys {
|
|
err := presetValue(server, context.Background(), k, "")
|
|
if err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
}
|
|
|
|
actual, err := server.RandomKey()
|
|
if err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
if !strings.Contains(actual, "key") {
|
|
t.Errorf("RANDOMKEY error, expected one of %v, got %s", testKeys, got)
|
|
}
|
|
|
|
})
|
|
|
|
t.Run("TestSugarDB_EXISTS", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// Test with no keys
|
|
keys := []string{"exists_key1", "exists_key2", "exists_key3"}
|
|
existsCount, err := server.Exists(keys...)
|
|
if err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
if existsCount != 0 {
|
|
t.Errorf("EXISTS error, expected 0, got %d", existsCount)
|
|
}
|
|
|
|
// Test with some keys
|
|
for _, k := range keys {
|
|
err := presetValue(server, context.Background(), k, "")
|
|
if err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
}
|
|
|
|
existsCount, err = server.Exists(keys...)
|
|
if err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
if existsCount != len(keys) {
|
|
t.Errorf("EXISTS error, expected %d, got %d", len(keys), existsCount)
|
|
}
|
|
})
|
|
|
|
t.Run("TestSugarDB_DBSIZE", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
server := createSugarDB()
|
|
t.Cleanup(func() {
|
|
server.ShutDown()
|
|
})
|
|
|
|
got, err := server.DBSize()
|
|
if err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
if got != 0 {
|
|
t.Errorf("DBSIZE error, expected 0, got %d", got)
|
|
}
|
|
|
|
// test with keys
|
|
testKeys := []string{"1", "2", "3"}
|
|
for _, k := range testKeys {
|
|
err := presetValue(server, context.Background(), k, "")
|
|
if err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
}
|
|
|
|
got, err = server.DBSize()
|
|
if err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
if got != len(testKeys) {
|
|
t.Errorf("DBSIZE error, expected %d, got %d", len(testKeys), got)
|
|
}
|
|
})
|
|
|
|
t.Run("TestSugarDB_GETDEL", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
presetValue interface{}
|
|
key string
|
|
want string
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "1. Return string from existing key",
|
|
presetValue: "value1",
|
|
key: "getdel_key1",
|
|
want: "value1",
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "2. Return empty string if the key does not exist",
|
|
presetValue: nil,
|
|
key: "getdel_key2",
|
|
want: "",
|
|
wantErr: false,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
if tt.presetValue != nil {
|
|
err := presetValue(server, context.Background(), tt.key, tt.presetValue)
|
|
if err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
}
|
|
// Check value received
|
|
got, err := server.GetDel(tt.key)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("GETDEL() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
if got != tt.want {
|
|
t.Errorf("GETDEL() got = %v, want %v", got, tt.want)
|
|
}
|
|
// Check key was deleted
|
|
if tt.presetValue != nil {
|
|
got, err := server.Get(tt.key)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("GETDEL() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
if got != "" {
|
|
t.Errorf("GETDEL() got = %v, want empty string", got)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
})
|
|
|
|
t.Run("TestSugarDB_GETEX", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
presetValue interface{}
|
|
getExOpt GetExOption
|
|
getExOptTime int
|
|
key string
|
|
want string
|
|
wantEx int
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "1. Return string from existing key, no expire options",
|
|
presetValue: "value1",
|
|
getExOpt: nil,
|
|
key: "getex_key1",
|
|
want: "value1",
|
|
wantEx: -1,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "2. Return empty string if the key does not exist",
|
|
presetValue: nil,
|
|
getExOpt: EX,
|
|
getExOptTime: int(mockClock.Now().Add(100 * time.Second).Unix()),
|
|
key: "getex_key2",
|
|
want: "",
|
|
wantEx: 0,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "3. Return key set expiry with EX",
|
|
presetValue: "value3",
|
|
getExOpt: EX,
|
|
getExOptTime: 100,
|
|
key: "getex_key3",
|
|
want: "value3",
|
|
wantEx: 100,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "4. Return key set expiry with PX",
|
|
presetValue: "value4",
|
|
getExOpt: PX,
|
|
getExOptTime: 100000,
|
|
key: "getex_key4",
|
|
want: "value4",
|
|
wantEx: 100,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "5. Return key set expiry with EXAT",
|
|
presetValue: "value5",
|
|
getExOpt: EXAT,
|
|
getExOptTime: int(mockClock.Now().Add(100 * time.Second).Unix()),
|
|
key: "getex_key5",
|
|
want: "value5",
|
|
wantEx: 100,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "6. Return key set expiry with PXAT",
|
|
presetValue: "value6",
|
|
getExOpt: PXAT,
|
|
getExOptTime: int(mockClock.Now().Add(100 * time.Second).UnixMilli()),
|
|
key: "getex_key6",
|
|
want: "value6",
|
|
wantEx: 100,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "7. Return key passing PERSIST",
|
|
presetValue: "value7",
|
|
getExOpt: PERSIST,
|
|
key: "getex_key7",
|
|
want: "value7",
|
|
wantEx: -1,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "8. Return key passing PERSIST, and include a UNIXTIME",
|
|
presetValue: "value8",
|
|
getExOpt: PERSIST,
|
|
getExOptTime: int(mockClock.Now().Add(100 * time.Second).Unix()),
|
|
key: "getex_key8",
|
|
want: "value8",
|
|
wantEx: -1,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "9. Return key and attempt to set expiry with EX without providing UNIXTIME",
|
|
presetValue: "value9",
|
|
getExOpt: EX,
|
|
key: "getex_key9",
|
|
want: "value9",
|
|
wantEx: -1,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "10. Return key and attempt to set expiry with PXAT without providing UNIXTIME",
|
|
presetValue: "value10",
|
|
getExOpt: PXAT,
|
|
key: "getex_key10",
|
|
want: "value10",
|
|
wantEx: -1,
|
|
wantErr: false,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
if tt.presetValue != nil {
|
|
err := presetValue(server, context.Background(), tt.key, tt.presetValue)
|
|
if err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
}
|
|
// Check value received
|
|
got, err := server.GetEx(tt.key, tt.getExOpt, tt.getExOptTime)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("GETEX() GET error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
if got != tt.want {
|
|
t.Errorf("GETEX() GET - got = %v, want %v", got, tt.want)
|
|
}
|
|
// Check expiry was set
|
|
if tt.presetValue != nil {
|
|
actual, err := server.TTL(tt.key)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("GETEX() EXPIRY error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
if actual != tt.wantEx {
|
|
t.Errorf("GETEX() EXPIRY - got = %v, want %v", actual, tt.wantEx)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
})
|
|
|
|
// Tests Touch and OBJECTFREQ commands
|
|
t.Run("TestSugarDB_LFU_TOUCH", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
duration := time.Duration(30) * time.Second
|
|
|
|
server := createSugarDBWithConfig(config.Config{
|
|
DataDir: "",
|
|
EvictionPolicy: constants.AllKeysLFU,
|
|
EvictionInterval: duration,
|
|
MaxMemory: 4000000,
|
|
})
|
|
t.Cleanup(func() {
|
|
server.ShutDown()
|
|
})
|
|
|
|
tests := []struct {
|
|
name string
|
|
keys []string
|
|
setKeys []bool
|
|
want int
|
|
wantErrs []bool
|
|
}{
|
|
{
|
|
name: "1. Touch key that exists.",
|
|
keys: []string{"Key1"},
|
|
setKeys: []bool{true},
|
|
want: 1,
|
|
wantErrs: []bool{false},
|
|
},
|
|
{
|
|
name: "2. Touch key that doesn't exist.",
|
|
keys: []string{"Key2"},
|
|
setKeys: []bool{false},
|
|
want: 0,
|
|
wantErrs: []bool{true},
|
|
},
|
|
{
|
|
name: "3. Touch multiple keys that all exist.",
|
|
keys: []string{"Key3", "Key3.1"},
|
|
setKeys: []bool{true, true},
|
|
want: 2,
|
|
wantErrs: []bool{false, false},
|
|
},
|
|
{
|
|
name: "4. Touch multiple keys, some don't exist.",
|
|
keys: []string{"Key4", "Key4.9"},
|
|
setKeys: []bool{true, false},
|
|
want: 1,
|
|
wantErrs: []bool{false, true},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// Preset values
|
|
for i, key := range tt.keys {
|
|
if tt.setKeys[i] {
|
|
err := presetValue(server, context.Background(), key, "___")
|
|
if err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// Touch keys
|
|
got, err := server.Touch(tt.keys...)
|
|
if err != nil {
|
|
t.Errorf("TOUCH() error - %v", err)
|
|
}
|
|
|
|
if got != tt.want {
|
|
t.Errorf("TOUCH() got %v, want %v, using keys %v setKeys %v", got, tt.want, tt.keys, tt.setKeys)
|
|
}
|
|
|
|
// Another touch to help testing object freq
|
|
got, err = server.Touch(tt.keys...)
|
|
if err != nil {
|
|
t.Errorf("TOUCH() error - %v", err)
|
|
}
|
|
|
|
if got != tt.want {
|
|
t.Errorf("TOUCH() got %v, want %v, using keys %v setKeys %v", got, tt.want, tt.keys, tt.setKeys)
|
|
}
|
|
|
|
// Wait to avoid race
|
|
ticker := time.NewTicker(300 * time.Millisecond)
|
|
<-ticker.C
|
|
ticker.Stop()
|
|
|
|
// Objectfreq
|
|
for i, key := range tt.keys {
|
|
actual, err := server.ObjectFreq(key)
|
|
if (err != nil) != tt.wantErrs[i] {
|
|
t.Errorf("OBJECTFREQ() error: %v, wanted error: %v", err, tt.wantErrs[i])
|
|
}
|
|
if !tt.wantErrs[i] && actual != 3 {
|
|
t.Errorf("OBJECTFREQ() error - expected 3 got %v for key %v", actual, key)
|
|
}
|
|
|
|
// Check error for object idletime
|
|
_, err = server.ObjectIdleTime(key)
|
|
if err == nil {
|
|
t.Errorf("OBJECTIDLETIME() error - expected error when used on server with lfu eviction policy but got none.")
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
}
|
|
})
|
|
|
|
// Tests Touch and OBJECTIDLETIME commands
|
|
t.Run("TestSugarDB_LRU_TOUCH", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
duration := time.Duration(30) * time.Second
|
|
|
|
server := createSugarDBWithConfig(config.Config{
|
|
DataDir: "",
|
|
EvictionPolicy: constants.AllKeysLRU,
|
|
EvictionInterval: duration,
|
|
MaxMemory: 4000000,
|
|
})
|
|
t.Cleanup(func() {
|
|
server.ShutDown()
|
|
})
|
|
|
|
tests := []struct {
|
|
name string
|
|
keys []string
|
|
setKeys []bool
|
|
want int
|
|
wantErrs []bool
|
|
}{
|
|
{
|
|
name: "1. Touch key that exists.",
|
|
keys: []string{"Key1"},
|
|
setKeys: []bool{true},
|
|
want: 1,
|
|
wantErrs: []bool{false},
|
|
},
|
|
{
|
|
name: "2. Touch key that doesn't exist.",
|
|
keys: []string{"Key2"},
|
|
setKeys: []bool{false},
|
|
want: 0,
|
|
wantErrs: []bool{true},
|
|
},
|
|
{
|
|
name: "3. Touch multiple keys that all exist.",
|
|
keys: []string{"Key3", "Key3.1"},
|
|
setKeys: []bool{true, true},
|
|
want: 2,
|
|
wantErrs: []bool{false, false},
|
|
},
|
|
{
|
|
name: "4. Touch multiple keys, some don't exist.",
|
|
keys: []string{"Key4", "Key4.9"},
|
|
setKeys: []bool{true, false},
|
|
want: 1,
|
|
wantErrs: []bool{false, true},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// Preset values
|
|
for i, key := range tt.keys {
|
|
if tt.setKeys[i] {
|
|
err := presetValue(server, context.Background(), key, "___")
|
|
if err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// Touch keys
|
|
got, err := server.Touch(tt.keys...)
|
|
if err != nil {
|
|
t.Errorf("TOUCH() error - %v", err)
|
|
}
|
|
|
|
if got != tt.want {
|
|
t.Errorf("TOUCH() got %v, want %v, using keys %v setKeys %v", got, tt.want, tt.keys, tt.setKeys)
|
|
}
|
|
|
|
// Sleep to more easily test Object idle time
|
|
// TODO: Update this ticker when updateKeysInCache implementation is updated
|
|
// Due to the event-based command execution, the actual touch may be done slightly later
|
|
// than the invocation time as it waits for earlier events to be handled.
|
|
ticker := time.NewTicker(200 * time.Millisecond)
|
|
<-ticker.C
|
|
ticker.Stop()
|
|
|
|
// Objectidletime
|
|
for i, key := range tt.keys {
|
|
actual, err := server.ObjectIdleTime(key)
|
|
if (err != nil) != tt.wantErrs[i] {
|
|
t.Errorf("OBJECTIDLETIME() error: %v, wanted error: %v", err, tt.wantErrs[i])
|
|
}
|
|
if !tt.wantErrs[i] && (actual <= 0) { // TODO: Fix updated condition to account for touch delay
|
|
t.Errorf("OBJECTIDLETIME() error - expected 0.2 got %v", actual)
|
|
}
|
|
|
|
// Check error for object freq
|
|
_, err = server.ObjectFreq(key)
|
|
if err == nil {
|
|
t.Errorf("OBJECTFREQ() error - expected error when used on server with lru eviction policy but got none.")
|
|
}
|
|
}
|
|
|
|
})
|
|
}
|
|
})
|
|
|
|
t.Run("TestSugarDB_TYPE", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
presetValue interface{}
|
|
key string
|
|
want string
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "1. Return string from existing key",
|
|
presetValue: "value1",
|
|
key: "type_key1",
|
|
want: "string",
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "2. Return empty string if the key does not exist",
|
|
presetValue: nil,
|
|
key: "type_key2",
|
|
want: "",
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "3. Return string from existing key",
|
|
presetValue: 10,
|
|
key: "type_key3",
|
|
want: "integer",
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "4. Return string from existing key",
|
|
presetValue: 10.1,
|
|
key: "type_key4",
|
|
want: "float",
|
|
wantErr: false,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
if tt.presetValue != nil {
|
|
err := presetValue(server, context.Background(), tt.key, tt.presetValue)
|
|
if err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
}
|
|
got, err := server.Type(tt.key)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("GET() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
if got != tt.want {
|
|
t.Errorf("GET() got = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
|
|
t.Run("TestSugarDB_COPY", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
CopyOptions := func(DB string, R bool) COPYOptions {
|
|
return COPYOptions{
|
|
Database: DB,
|
|
Replace: R,
|
|
}
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
sourceKeyPresetValue interface{}
|
|
sourceKey string
|
|
destKeyPresetValue interface{}
|
|
destinationKey string
|
|
options COPYOptions
|
|
expectedValue string
|
|
want int
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "1. Copy Value into non existing key",
|
|
sourceKeyPresetValue: "value1",
|
|
sourceKey: "copy_skey1",
|
|
destKeyPresetValue: nil,
|
|
destinationKey: "copy_dkey1",
|
|
options: CopyOptions("0", false),
|
|
expectedValue: "value1",
|
|
want: 1,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "2. Copy Value into existing key without replace option",
|
|
sourceKeyPresetValue: "value2",
|
|
sourceKey: "copy_skey2",
|
|
destKeyPresetValue: "dValue2",
|
|
destinationKey: "copy_dkey2",
|
|
options: CopyOptions("0", false),
|
|
expectedValue: "dValue2",
|
|
want: 0,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "3. Copy Value into existing key with replace option",
|
|
sourceKeyPresetValue: "value3",
|
|
sourceKey: "copy_skey3",
|
|
destKeyPresetValue: "dValue3",
|
|
destinationKey: "copy_dkey3",
|
|
options: CopyOptions("0", true),
|
|
expectedValue: "value3",
|
|
want: 1,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "4. Copy Value into different database",
|
|
sourceKeyPresetValue: "value4",
|
|
sourceKey: "copy_skey4",
|
|
destKeyPresetValue: nil,
|
|
destinationKey: "copy_dkey4",
|
|
options: CopyOptions("1", false),
|
|
expectedValue: "value4",
|
|
want: 1,
|
|
wantErr: false,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
if tt.sourceKeyPresetValue != nil {
|
|
err := presetValue(server, context.Background(), tt.sourceKey, tt.sourceKeyPresetValue)
|
|
if err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
}
|
|
if tt.destKeyPresetValue != nil {
|
|
err := presetValue(server, context.Background(), tt.destinationKey, tt.destKeyPresetValue)
|
|
if err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
}
|
|
|
|
got, err := server.Copy(tt.sourceKey, tt.destinationKey, tt.options)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("COPY() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
if got != tt.want {
|
|
t.Errorf("COPY() got = %v, want %v", got, tt.want)
|
|
}
|
|
|
|
val, err := getValue(server, context.Background(), tt.destinationKey, tt.options.Database)
|
|
if err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
|
|
if val != tt.expectedValue {
|
|
t.Errorf("COPY() value in destionation key: %v, should be: %v", val, tt.expectedValue)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
|
|
t.Run("TestSugarDB_MOVE", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
presetValue interface{}
|
|
key string
|
|
want int
|
|
}{
|
|
{
|
|
name: "1. Move key successfully",
|
|
presetValue: "value1",
|
|
key: "move_key1",
|
|
want: 1,
|
|
},
|
|
{
|
|
name: "2. Attempt to move key, unsuccessful",
|
|
presetValue: nil,
|
|
key: "move_key2",
|
|
want: 0,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
t.Log(tt.name)
|
|
if tt.presetValue != nil {
|
|
err := presetValue(server, context.Background(), tt.key, tt.presetValue)
|
|
if err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
}
|
|
|
|
got, err := server.Move(tt.key, 1)
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
|
|
if got != tt.want {
|
|
t.Errorf("MOVE() got %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
|
|
t.Run("TestSugarDB_KEYS", func(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
presetValues map[string]interface{}
|
|
pattern string
|
|
want []string
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "1. Return all keys with * pattern",
|
|
presetValues: map[string]interface{}{
|
|
"keys_key1": "value1",
|
|
"keys_key2": "value2",
|
|
"keys_key3": "value3",
|
|
},
|
|
pattern: "*",
|
|
want: []string{"keys_key1", "keys_key2", "keys_key3"},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "2. Return keys matching specific pattern",
|
|
presetValues: map[string]interface{}{
|
|
"keys_key1": "value1",
|
|
"keys_key2": "value2",
|
|
"other_key": "value3",
|
|
},
|
|
pattern: "keys_*",
|
|
want: []string{"keys_key1", "keys_key2"},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "3. Return keys matching single character pattern",
|
|
presetValues: map[string]interface{}{
|
|
"keys_key1": "value1",
|
|
"keys_key2": "value2",
|
|
"keys_kex3": "value3",
|
|
},
|
|
pattern: "keys_key?",
|
|
want: []string{"keys_key1", "keys_key2"},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "4. Return empty slice when no keys match pattern",
|
|
presetValues: map[string]interface{}{
|
|
"keys_key1": "value1",
|
|
"keys_key2": "value2",
|
|
},
|
|
pattern: "nonexistent_*",
|
|
want: []string{},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "5. Return empty slice when no keys exist",
|
|
presetValues: nil,
|
|
pattern: "*",
|
|
want: []string{},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "6. Return keys matching character class pattern",
|
|
presetValues: map[string]interface{}{
|
|
"keys_key1": "value1",
|
|
"keys_key2": "value2",
|
|
"keys_kex3": "value3",
|
|
},
|
|
pattern: "keys_key[12]",
|
|
want: []string{"keys_key1", "keys_key2"},
|
|
wantErr: false,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// Create a new server instance for each test case
|
|
server := createSugarDB()
|
|
t.Cleanup(func() {
|
|
server.ShutDown()
|
|
})
|
|
|
|
if tt.presetValues != nil {
|
|
for k, v := range tt.presetValues {
|
|
err := presetValue(server, context.Background(), k, v)
|
|
if err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
got, err := server.Keys(tt.pattern)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("KEYS() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
slices.Sort(got)
|
|
slices.Sort(tt.want)
|
|
if !reflect.DeepEqual(got, tt.want) {
|
|
t.Errorf("KEYS() got = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|