mirror of
https://github.com/EchoVault/SugarDB.git
synced 2025-10-08 17:30:47 +08:00
Implemented unit tests for generic module API
This commit is contained in:
2
Makefile
2
Makefile
@@ -8,4 +8,4 @@ run:
|
|||||||
make build && docker-compose up --build
|
make build && docker-compose up --build
|
||||||
|
|
||||||
test:
|
test:
|
||||||
go clean -testcache && go test ./pkg/... -coverprofile coverage/coverage.out
|
go clean -testcache && go test ./... -coverprofile coverage/coverage.out
|
||||||
|
File diff suppressed because it is too large
Load Diff
@@ -13,3 +13,931 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package echovault
|
package echovault
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/echovault/echovault/internal"
|
||||||
|
"github.com/echovault/echovault/internal/config"
|
||||||
|
"github.com/echovault/echovault/pkg/commands"
|
||||||
|
"github.com/echovault/echovault/pkg/constants"
|
||||||
|
"reflect"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var timeNow = func() time.Time {
|
||||||
|
now := time.Now()
|
||||||
|
return func() time.Time {
|
||||||
|
return now.Add(5 * time.Hour).Add(30 * time.Minute).Add(30 * time.Second).Add(10 * time.Millisecond)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEchoVault_DEL(t *testing.T) {
|
||||||
|
server := NewEchoVault(
|
||||||
|
WithCommands(commands.All()),
|
||||||
|
WithConfig(config.Config{
|
||||||
|
EvictionPolicy: constants.NoEviction,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
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{"key1", "key2", "key3", "key4", "key5"},
|
||||||
|
presetValues: map[string]internal.KeyData{
|
||||||
|
"key1": {Value: "value1", ExpireAt: time.Time{}},
|
||||||
|
"key2": {Value: "value2", ExpireAt: time.Time{}},
|
||||||
|
"key3": {Value: "value3", ExpireAt: time.Time{}},
|
||||||
|
"key4": {Value: "value4", ExpireAt: time.Time{}},
|
||||||
|
},
|
||||||
|
want: 4,
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if tt.presetValues != nil {
|
||||||
|
for k, d := range tt.presetValues {
|
||||||
|
presetKeyData(server, 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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEchoVault_EXPIRE(t *testing.T) {
|
||||||
|
server := NewEchoVault(
|
||||||
|
WithCommands(commands.All()),
|
||||||
|
WithConfig(config.Config{
|
||||||
|
EvictionPolicy: constants.NoEviction,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
presetValues map[string]internal.KeyData
|
||||||
|
cmd string
|
||||||
|
key string
|
||||||
|
time int
|
||||||
|
expireOpts EXPIREOptions
|
||||||
|
pexpireOpts PEXPIREOptions
|
||||||
|
want int
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Set new expire by seconds",
|
||||||
|
cmd: "EXPIRE",
|
||||||
|
key: "key1",
|
||||||
|
time: 100,
|
||||||
|
expireOpts: EXPIREOptions{},
|
||||||
|
presetValues: map[string]internal.KeyData{
|
||||||
|
"key1": {Value: "value1", ExpireAt: time.Time{}},
|
||||||
|
},
|
||||||
|
want: 1,
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Set new expire by milliseconds",
|
||||||
|
cmd: "PEXPIRE",
|
||||||
|
key: "key2",
|
||||||
|
time: 1000,
|
||||||
|
pexpireOpts: PEXPIREOptions{},
|
||||||
|
presetValues: map[string]internal.KeyData{
|
||||||
|
"key2": {Value: "value2", ExpireAt: time.Time{}},
|
||||||
|
},
|
||||||
|
want: 1,
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Set new expire only when key does not have an expiry time with NX flag",
|
||||||
|
cmd: "EXPIRE",
|
||||||
|
key: "key3",
|
||||||
|
time: 1000,
|
||||||
|
expireOpts: EXPIREOptions{NX: true},
|
||||||
|
presetValues: map[string]internal.KeyData{
|
||||||
|
"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: "EXPIRE",
|
||||||
|
key: "key4",
|
||||||
|
time: 1000,
|
||||||
|
expireOpts: EXPIREOptions{NX: true},
|
||||||
|
presetValues: map[string]internal.KeyData{
|
||||||
|
"key4": {Value: "value4", ExpireAt: timeNow().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: "EXPIRE",
|
||||||
|
key: "key5",
|
||||||
|
time: 1000,
|
||||||
|
expireOpts: EXPIREOptions{XX: true},
|
||||||
|
presetValues: map[string]internal.KeyData{
|
||||||
|
"key5": {Value: "value5", ExpireAt: timeNow().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: "EXPIRE",
|
||||||
|
time: 1000,
|
||||||
|
expireOpts: EXPIREOptions{XX: true},
|
||||||
|
key: "key6",
|
||||||
|
presetValues: map[string]internal.KeyData{
|
||||||
|
"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: "EXPIRE",
|
||||||
|
key: "key7",
|
||||||
|
time: 100000,
|
||||||
|
expireOpts: EXPIREOptions{GT: true},
|
||||||
|
presetValues: map[string]internal.KeyData{
|
||||||
|
"key7": {Value: "value7", ExpireAt: timeNow().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: "EXPIRE",
|
||||||
|
key: "key8",
|
||||||
|
time: 1000,
|
||||||
|
expireOpts: EXPIREOptions{GT: true},
|
||||||
|
presetValues: map[string]internal.KeyData{
|
||||||
|
"key8": {Value: "value8", ExpireAt: timeNow().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: "EXPIRE",
|
||||||
|
key: "key9",
|
||||||
|
time: 1000,
|
||||||
|
expireOpts: EXPIREOptions{GT: true},
|
||||||
|
presetValues: map[string]internal.KeyData{
|
||||||
|
"key9": {Value: "value9", ExpireAt: time.Time{}},
|
||||||
|
},
|
||||||
|
want: 0,
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Set expiry time when the provided time is before the current expiry time when LT flag is provided",
|
||||||
|
cmd: "EXPIRE",
|
||||||
|
key: "key10",
|
||||||
|
time: 1000,
|
||||||
|
expireOpts: EXPIREOptions{LT: true},
|
||||||
|
presetValues: map[string]internal.KeyData{
|
||||||
|
"key10": {Value: "value10", ExpireAt: timeNow().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: "EXPIRE",
|
||||||
|
key: "key11",
|
||||||
|
time: 50000,
|
||||||
|
expireOpts: EXPIREOptions{LT: true},
|
||||||
|
presetValues: map[string]internal.KeyData{
|
||||||
|
"key11": {Value: "value11", ExpireAt: timeNow().Add(30 * time.Second)},
|
||||||
|
},
|
||||||
|
want: 0,
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if tt.presetValues != nil {
|
||||||
|
for k, d := range tt.presetValues {
|
||||||
|
presetKeyData(server, k, d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var got int
|
||||||
|
var err error
|
||||||
|
if strings.EqualFold(tt.cmd, "PEXPIRE") {
|
||||||
|
got, err = server.PEXPIRE(tt.key, tt.time, tt.pexpireOpts)
|
||||||
|
} 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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEchoVault_EXPIREAT(t *testing.T) {
|
||||||
|
server := NewEchoVault(
|
||||||
|
WithCommands(commands.All()),
|
||||||
|
WithConfig(config.Config{
|
||||||
|
EvictionPolicy: constants.NoEviction,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
presetValues map[string]internal.KeyData
|
||||||
|
cmd string
|
||||||
|
key string
|
||||||
|
time int
|
||||||
|
expireAtOpts EXPIREATOptions
|
||||||
|
pexpireAtOpts PEXPIREATOptions
|
||||||
|
want int
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Set new expire by unix seconds",
|
||||||
|
cmd: "EXPIREAT",
|
||||||
|
key: "key1",
|
||||||
|
expireAtOpts: EXPIREATOptions{},
|
||||||
|
time: int(timeNow().Add(1000 * time.Second).Unix()),
|
||||||
|
presetValues: map[string]internal.KeyData{
|
||||||
|
"key1": {Value: "value1", ExpireAt: time.Time{}},
|
||||||
|
},
|
||||||
|
want: 1,
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Set new expire by milliseconds",
|
||||||
|
cmd: "PEXPIREAT",
|
||||||
|
key: "key2",
|
||||||
|
pexpireAtOpts: PEXPIREATOptions{},
|
||||||
|
time: int(timeNow().Add(1000 * time.Second).UnixMilli()),
|
||||||
|
presetValues: map[string]internal.KeyData{
|
||||||
|
"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: "key3",
|
||||||
|
time: int(timeNow().Add(1000 * time.Second).Unix()),
|
||||||
|
expireAtOpts: EXPIREATOptions{NX: true},
|
||||||
|
presetValues: map[string]internal.KeyData{
|
||||||
|
"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(timeNow().Add(1000 * time.Second).Unix()),
|
||||||
|
expireAtOpts: EXPIREATOptions{NX: true},
|
||||||
|
key: "key4",
|
||||||
|
presetValues: map[string]internal.KeyData{
|
||||||
|
"key4": {Value: "value4", ExpireAt: timeNow().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(timeNow().Add(1000 * time.Second).Unix()),
|
||||||
|
key: "key5",
|
||||||
|
expireAtOpts: EXPIREATOptions{XX: true},
|
||||||
|
presetValues: map[string]internal.KeyData{
|
||||||
|
"key5": {Value: "value5", ExpireAt: timeNow().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: "key6",
|
||||||
|
time: int(timeNow().Add(1000 * time.Second).Unix()),
|
||||||
|
expireAtOpts: EXPIREATOptions{XX: true},
|
||||||
|
presetValues: map[string]internal.KeyData{
|
||||||
|
"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: "key7",
|
||||||
|
time: int(timeNow().Add(1000 * time.Second).Unix()),
|
||||||
|
expireAtOpts: EXPIREATOptions{GT: true},
|
||||||
|
presetValues: map[string]internal.KeyData{
|
||||||
|
"key7": {Value: "value7", ExpireAt: timeNow().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: "key8",
|
||||||
|
time: int(timeNow().Add(1000 * time.Second).Unix()),
|
||||||
|
expireAtOpts: EXPIREATOptions{GT: true},
|
||||||
|
presetValues: map[string]internal.KeyData{
|
||||||
|
"key8": {Value: "value8", ExpireAt: timeNow().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: "key9",
|
||||||
|
time: int(timeNow().Add(1000 * time.Second).Unix()),
|
||||||
|
expireAtOpts: EXPIREATOptions{GT: true},
|
||||||
|
presetValues: map[string]internal.KeyData{
|
||||||
|
"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: "key10",
|
||||||
|
time: int(timeNow().Add(1000 * time.Second).Unix()),
|
||||||
|
expireAtOpts: EXPIREATOptions{LT: true},
|
||||||
|
presetValues: map[string]internal.KeyData{
|
||||||
|
"key10": {Value: "value10", ExpireAt: timeNow().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: "key11",
|
||||||
|
time: int(timeNow().Add(3000 * time.Second).Unix()),
|
||||||
|
expireAtOpts: EXPIREATOptions{LT: true},
|
||||||
|
presetValues: map[string]internal.KeyData{
|
||||||
|
"key11": {Value: "value11", ExpireAt: timeNow().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: "key12",
|
||||||
|
time: int(timeNow().Add(1000 * time.Second).Unix()),
|
||||||
|
expireAtOpts: EXPIREATOptions{LT: true},
|
||||||
|
presetValues: map[string]internal.KeyData{
|
||||||
|
"key12": {Value: "value12", ExpireAt: time.Time{}},
|
||||||
|
},
|
||||||
|
want: 1,
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if tt.presetValues != nil {
|
||||||
|
for k, d := range tt.presetValues {
|
||||||
|
presetKeyData(server, k, d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var got int
|
||||||
|
var err error
|
||||||
|
if strings.EqualFold(tt.cmd, "PEXPIREAT") {
|
||||||
|
got, err = server.PEXPIREAT(tt.key, tt.time, tt.pexpireAtOpts)
|
||||||
|
} 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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEchoVault_EXPIRETIME(t *testing.T) {
|
||||||
|
server := NewEchoVault(
|
||||||
|
WithCommands(commands.All()),
|
||||||
|
WithConfig(config.Config{
|
||||||
|
EvictionPolicy: constants.NoEviction,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
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: "key1",
|
||||||
|
presetValues: map[string]internal.KeyData{
|
||||||
|
"key1": {Value: "value1", ExpireAt: timeNow().Add(100 * time.Second)},
|
||||||
|
},
|
||||||
|
expiretimeFunc: server.EXPIRETIME,
|
||||||
|
want: int(timeNow().Add(100 * time.Second).Unix()),
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Return expire time in milliseconds",
|
||||||
|
key: "key2",
|
||||||
|
presetValues: map[string]internal.KeyData{
|
||||||
|
"key2": {Value: "value2", ExpireAt: timeNow().Add(4096 * time.Millisecond)},
|
||||||
|
},
|
||||||
|
expiretimeFunc: server.PEXPIRETIME,
|
||||||
|
want: int(timeNow().Add(4096 * time.Millisecond).UnixMilli()),
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "If the key is non-volatile, return -1",
|
||||||
|
key: "key3",
|
||||||
|
presetValues: map[string]internal.KeyData{
|
||||||
|
"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) {
|
||||||
|
if tt.presetValues != nil {
|
||||||
|
for k, d := range tt.presetValues {
|
||||||
|
presetKeyData(server, 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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEchoVault_GET(t *testing.T) {
|
||||||
|
server := NewEchoVault(
|
||||||
|
WithCommands(commands.All()),
|
||||||
|
WithConfig(config.Config{
|
||||||
|
EvictionPolicy: constants.NoEviction,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
presetValue interface{}
|
||||||
|
key string
|
||||||
|
want string
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Return string from existing key",
|
||||||
|
presetValue: "value1",
|
||||||
|
key: "key1",
|
||||||
|
want: "value1",
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Return empty string if the key does not exist",
|
||||||
|
presetValue: nil,
|
||||||
|
key: "key2",
|
||||||
|
want: "",
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if tt.presetValue != nil {
|
||||||
|
presetValue(server, tt.key, tt.presetValue)
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEchoVault_MGET(t *testing.T) {
|
||||||
|
server := NewEchoVault(
|
||||||
|
WithCommands(commands.All()),
|
||||||
|
WithConfig(config.Config{
|
||||||
|
EvictionPolicy: constants.NoEviction,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
presetValues map[string]interface{}
|
||||||
|
keys []string
|
||||||
|
want []string
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Get all values in the same order the keys were provided in",
|
||||||
|
presetValues: map[string]interface{}{"key1": "value1", "key2": "value2", "key3": "value3", "key4": "value4"},
|
||||||
|
keys: []string{"key1", "key4", "key2", "key3", "key1"},
|
||||||
|
want: []string{"value1", "value4", "value2", "value3", "value1"},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Return empty strings for non-existent keys",
|
||||||
|
presetValues: map[string]interface{}{"key5": "value5", "key6": "value6", "key7": "value7"},
|
||||||
|
keys: []string{"key5", "key6", "non-existent", "non-existent", "key7", "non-existent"},
|
||||||
|
want: []string{"value5", "value6", "", "", "value7", ""},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if tt.presetValues != nil {
|
||||||
|
for k, v := range tt.presetValues {
|
||||||
|
presetValue(server, k, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEchoVault_SET(t *testing.T) {
|
||||||
|
server := NewEchoVault(
|
||||||
|
WithCommands(commands.All()),
|
||||||
|
WithConfig(config.Config{
|
||||||
|
EvictionPolicy: constants.NoEviction,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
presetValues map[string]internal.KeyData
|
||||||
|
key string
|
||||||
|
value string
|
||||||
|
options SETOptions
|
||||||
|
want string
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Set normal value",
|
||||||
|
presetValues: nil,
|
||||||
|
key: "key1",
|
||||||
|
value: "value1",
|
||||||
|
options: SETOptions{},
|
||||||
|
want: "OK",
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Only set the value if the key does not exist",
|
||||||
|
presetValues: nil,
|
||||||
|
key: "key2",
|
||||||
|
value: "value2",
|
||||||
|
options: SETOptions{NX: true},
|
||||||
|
want: "OK",
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Throw error when value already exists with NX flag passed",
|
||||||
|
presetValues: map[string]internal.KeyData{
|
||||||
|
"key3": {
|
||||||
|
Value: "preset-value3",
|
||||||
|
ExpireAt: time.Time{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
key: "key3",
|
||||||
|
value: "value3",
|
||||||
|
options: SETOptions{NX: true},
|
||||||
|
want: "",
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Set new key value when key exists with XX flag passed",
|
||||||
|
presetValues: map[string]internal.KeyData{
|
||||||
|
"key4": {
|
||||||
|
Value: "preset-value4",
|
||||||
|
ExpireAt: time.Time{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
key: "key4",
|
||||||
|
value: "value4",
|
||||||
|
options: SETOptions{XX: true},
|
||||||
|
want: "OK",
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Return error when setting non-existent key with XX flag",
|
||||||
|
presetValues: nil,
|
||||||
|
key: "key5",
|
||||||
|
value: "value5",
|
||||||
|
options: SETOptions{XX: true},
|
||||||
|
want: "",
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Set expiry time on the key to 100 seconds from now",
|
||||||
|
presetValues: nil,
|
||||||
|
key: "key6",
|
||||||
|
value: "value6",
|
||||||
|
options: SETOptions{EX: 100},
|
||||||
|
want: "OK",
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Set expiry time on the key in unix milliseconds",
|
||||||
|
presetValues: nil,
|
||||||
|
key: "key7",
|
||||||
|
value: "value7",
|
||||||
|
options: SETOptions{PX: 4096},
|
||||||
|
want: "OK",
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Set exact expiry time in seconds from unix epoch",
|
||||||
|
presetValues: nil,
|
||||||
|
key: "key8",
|
||||||
|
value: "value8",
|
||||||
|
options: SETOptions{EXAT: int(timeNow().Add(200 * time.Second).Unix())},
|
||||||
|
want: "OK",
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Set exact expiry time in milliseconds from unix epoch",
|
||||||
|
key: "key9",
|
||||||
|
value: "value9",
|
||||||
|
options: SETOptions{PXAT: int(timeNow().Add(4096 * time.Millisecond).UnixMilli())},
|
||||||
|
presetValues: nil,
|
||||||
|
want: "OK",
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Get the previous value when GET flag is passed",
|
||||||
|
presetValues: map[string]internal.KeyData{
|
||||||
|
"key10": {
|
||||||
|
Value: "previous-value",
|
||||||
|
ExpireAt: time.Time{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
key: "key10",
|
||||||
|
value: "value10",
|
||||||
|
options: SETOptions{GET: true, EX: 1000},
|
||||||
|
want: "previous-value",
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Return nil when GET value is passed and no previous value exists",
|
||||||
|
presetValues: nil,
|
||||||
|
key: "key11",
|
||||||
|
value: "value11",
|
||||||
|
options: SETOptions{GET: true, EX: 1000},
|
||||||
|
want: "",
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if tt.presetValues != nil {
|
||||||
|
for k, d := range tt.presetValues {
|
||||||
|
presetKeyData(server, k, d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
got, 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 got != tt.want {
|
||||||
|
t.Errorf("SET() got = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEchoVault_MSET(t *testing.T) {
|
||||||
|
server := NewEchoVault(
|
||||||
|
WithCommands(commands.All()),
|
||||||
|
WithConfig(config.Config{
|
||||||
|
EvictionPolicy: constants.NoEviction,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
kvPairs map[string]string
|
||||||
|
want string
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Set multiple keys",
|
||||||
|
kvPairs: map[string]string{"key1": "value1", "key2": "10", "key3": "3.142"},
|
||||||
|
want: "OK",
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEchoVault_PERSIST(t *testing.T) {
|
||||||
|
server := NewEchoVault(
|
||||||
|
WithCommands(commands.All()),
|
||||||
|
WithConfig(config.Config{
|
||||||
|
EvictionPolicy: constants.NoEviction,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
presetValues map[string]internal.KeyData
|
||||||
|
key string
|
||||||
|
want int
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Successfully persist a volatile key",
|
||||||
|
key: "key1",
|
||||||
|
presetValues: map[string]internal.KeyData{
|
||||||
|
"key1": {Value: "value1", ExpireAt: timeNow().Add(1000 * time.Second)},
|
||||||
|
},
|
||||||
|
want: 1,
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Return 0 when trying to persist a non-existent key",
|
||||||
|
key: "key2",
|
||||||
|
presetValues: nil,
|
||||||
|
want: 0,
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Return 0 when trying to persist a non-volatile key",
|
||||||
|
key: "key3",
|
||||||
|
presetValues: map[string]internal.KeyData{
|
||||||
|
"key3": {Value: "value3", ExpireAt: time.Time{}},
|
||||||
|
},
|
||||||
|
want: 0,
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if tt.presetValues != nil {
|
||||||
|
for k, d := range tt.presetValues {
|
||||||
|
presetKeyData(server, 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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEchoVault_TTL(t *testing.T) {
|
||||||
|
server := NewEchoVault(
|
||||||
|
WithCommands(commands.All()),
|
||||||
|
WithConfig(config.Config{
|
||||||
|
EvictionPolicy: constants.NoEviction,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
presetValues map[string]internal.KeyData
|
||||||
|
key string
|
||||||
|
ttlFunc func(key string) (int, error)
|
||||||
|
want int
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Return TTL time in seconds",
|
||||||
|
key: "key1",
|
||||||
|
presetValues: map[string]internal.KeyData{
|
||||||
|
"key1": {Value: "value1", ExpireAt: timeNow().Add(100 * time.Second)},
|
||||||
|
},
|
||||||
|
ttlFunc: server.TTL,
|
||||||
|
want: 19930,
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Return TTL time in milliseconds",
|
||||||
|
key: "key2",
|
||||||
|
ttlFunc: server.PTTL,
|
||||||
|
presetValues: map[string]internal.KeyData{
|
||||||
|
"key2": {Value: "value2", ExpireAt: timeNow().Add(4096 * time.Millisecond)},
|
||||||
|
},
|
||||||
|
want: 19834106,
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "If the key is non-volatile, return -1",
|
||||||
|
key: "key3",
|
||||||
|
ttlFunc: server.TTL,
|
||||||
|
presetValues: map[string]internal.KeyData{
|
||||||
|
"key3": {Value: "value3", ExpireAt: time.Time{}},
|
||||||
|
},
|
||||||
|
want: -1,
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "If the key is non-existent return -2",
|
||||||
|
key: "key4",
|
||||||
|
ttlFunc: server.TTL,
|
||||||
|
presetValues: nil,
|
||||||
|
want: -2,
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if tt.presetValues != nil {
|
||||||
|
for k, d := range tt.presetValues {
|
||||||
|
presetKeyData(server, 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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -15,12 +15,20 @@
|
|||||||
package echovault
|
package echovault
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/echovault/echovault/internal/config"
|
||||||
"github.com/echovault/echovault/pkg/commands"
|
"github.com/echovault/echovault/pkg/commands"
|
||||||
|
"github.com/echovault/echovault/pkg/constants"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestEchoVault_SUBSTR(t *testing.T) {
|
func TestEchoVault_SUBSTR(t *testing.T) {
|
||||||
server := NewEchoVault(WithCommands(commands.All()))
|
server := NewEchoVault(
|
||||||
|
WithCommands(commands.All()),
|
||||||
|
WithConfig(config.Config{
|
||||||
|
DataDir: "",
|
||||||
|
EvictionPolicy: constants.NoEviction,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@@ -175,7 +183,13 @@ func TestEchoVault_SUBSTR(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestEchoVault_SETRANGE(t *testing.T) {
|
func TestEchoVault_SETRANGE(t *testing.T) {
|
||||||
server := NewEchoVault(WithCommands(commands.All()))
|
server := NewEchoVault(
|
||||||
|
WithCommands(commands.All()),
|
||||||
|
WithConfig(config.Config{
|
||||||
|
DataDir: "",
|
||||||
|
EvictionPolicy: constants.NoEviction,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@@ -259,7 +273,13 @@ func TestEchoVault_SETRANGE(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestEchoVault_STRLEN(t *testing.T) {
|
func TestEchoVault_STRLEN(t *testing.T) {
|
||||||
server := NewEchoVault(WithCommands(commands.All()))
|
server := NewEchoVault(
|
||||||
|
WithCommands(commands.All()),
|
||||||
|
WithConfig(config.Config{
|
||||||
|
DataDir: "",
|
||||||
|
EvictionPolicy: constants.NoEviction,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
@@ -571,3 +571,10 @@ func presetValue(server *EchoVault, key string, value interface{}) {
|
|||||||
_ = server.SetValue(server.context, key, value)
|
_ = server.SetValue(server.context, key, value)
|
||||||
server.KeyUnlock(server.context, key)
|
server.KeyUnlock(server.context, key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func presetKeyData(server *EchoVault, key string, data internal.KeyData) {
|
||||||
|
_, _ = server.CreateKeyAndLock(server.context, key)
|
||||||
|
defer server.KeyUnlock(server.context, key)
|
||||||
|
_ = server.SetValue(server.context, key, data.Value)
|
||||||
|
server.SetExpiry(server.context, key, data.ExpireAt, false)
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user