1
0
mirror of https://github.com/redis/go-redis.git synced 2026-10-24 07:59:20 +03:00
Files
go-redis/string_commands_unit_test.go
Yingzi Zhang a1a0e268ba fix(cmd): keep SetArgs ExpireAt milliseconds (#4053)
SetArgs always sent ExpireAt as EXAT with Unix seconds, so any
millisecond part was silently dropped. Send PXAT with Unix milliseconds
when ExpireAt has millisecond precision, and keep EXAT otherwise.
2026-09-30 21:41:36 +03:00

220 lines
5.6 KiB
Go

package redis
import (
"context"
"reflect"
"testing"
"time"
)
// captureCmdable returns a cmdable that records the Cmder passed to it without
// dispatching the command. Used to assert that command builders produce the
// expected argument list.
func captureCmdable(out *Cmder) cmdable {
return func(_ context.Context, cmd Cmder) error {
*out = cmd
return nil
}
}
func TestIncrEXInt_Args(t *testing.T) {
tests := []struct {
name string
args IncrEXIntArgs
want []interface{}
}{
{
name: "default",
args: IncrEXIntArgs{},
want: []interface{}{"increx", "key"},
},
{
name: "byint",
args: IncrEXIntArgs{By: 4, HasBy: true},
want: []interface{}{"increx", "key", "byint", int64(4)},
},
{
name: "byint_zero_explicit",
args: IncrEXIntArgs{By: 0, HasBy: true},
want: []interface{}{"increx", "key", "byint", int64(0)},
},
{
name: "lbound_ubound",
args: IncrEXIntArgs{
By: 2, HasBy: true,
LBound: 0, HasLBound: true,
UBound: 20, HasUBound: true,
},
want: []interface{}{"increx", "key", "byint", int64(2), "lbound", int64(0), "ubound", int64(20)},
},
{
name: "lbound_zero_is_sent",
args: IncrEXIntArgs{
LBound: 0, HasLBound: true,
},
want: []interface{}{"increx", "key", "lbound", int64(0)},
},
{
name: "ex_and_enx",
args: IncrEXIntArgs{
By: 2, HasBy: true,
Expiration: &ExpirationOption{Mode: EX, Value: 60},
ENX: true,
},
want: []interface{}{"increx", "key", "byint", int64(2), "ex", int64(60), "enx"},
},
{
name: "px",
args: IncrEXIntArgs{Expiration: &ExpirationOption{Mode: PX, Value: 1500}},
want: []interface{}{"increx", "key", "px", int64(1500)},
},
{
name: "exat",
args: IncrEXIntArgs{Expiration: &ExpirationOption{Mode: EXAT, Value: 1753265054}},
want: []interface{}{"increx", "key", "exat", int64(1753265054)},
},
{
name: "pxat",
args: IncrEXIntArgs{Expiration: &ExpirationOption{Mode: PXAT, Value: 1753265054014}},
want: []interface{}{"increx", "key", "pxat", int64(1753265054014)},
},
{
name: "persist",
args: IncrEXIntArgs{Expiration: &ExpirationOption{Mode: PERSIST}},
want: []interface{}{"increx", "key", "persist"},
},
{
name: "saturate_with_lbound",
args: IncrEXIntArgs{
By: 1, HasBy: true,
LBound: 10, HasLBound: true,
Saturate: true,
},
want: []interface{}{"increx", "key", "byint", int64(1), "lbound", int64(10), "saturate"},
},
{
name: "saturate_with_ubound",
args: IncrEXIntArgs{
By: 5, HasBy: true,
UBound: 12, HasUBound: true,
Saturate: true,
},
want: []interface{}{"increx", "key", "byint", int64(5), "ubound", int64(12), "saturate"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var captured Cmder
c := captureCmdable(&captured)
cmd := c.IncrEXInt(context.Background(), "key", tt.args)
if cmd == nil {
t.Fatalf("IncrEXInt returned nil")
}
if !reflect.DeepEqual(cmd.Args(), tt.want) {
t.Errorf("args mismatch\n got: %#v\nwant: %#v", cmd.Args(), tt.want)
}
})
}
}
func TestIncrEXFloat_Args(t *testing.T) {
tests := []struct {
name string
args IncrEXFloatArgs
want []interface{}
}{
{
name: "default_zero_by",
args: IncrEXFloatArgs{},
want: []interface{}{"increx", "key", "byfloat", float64(0)},
},
{
name: "byfloat",
args: IncrEXFloatArgs{By: 0.25},
want: []interface{}{"increx", "key", "byfloat", 0.25},
},
{
name: "bounds_and_saturate",
args: IncrEXFloatArgs{
By: 0.7,
UBound: 2, HasUBound: true,
Saturate: true,
},
want: []interface{}{"increx", "key", "byfloat", 0.7, "ubound", float64(2), "saturate"},
},
{
name: "lbound_ubound",
args: IncrEXFloatArgs{
By: 0.5,
LBound: -1.5, HasLBound: true,
UBound: 1.5, HasUBound: true,
},
want: []interface{}{"increx", "key", "byfloat", 0.5, "lbound", -1.5, "ubound", 1.5},
},
{
name: "ex_and_enx",
args: IncrEXFloatArgs{
By: 1.5,
Expiration: &ExpirationOption{Mode: EX, Value: 30},
ENX: true,
},
want: []interface{}{"increx", "key", "byfloat", 1.5, "ex", int64(30), "enx"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var captured Cmder
c := captureCmdable(&captured)
cmd := c.IncrEXFloat(context.Background(), "key", tt.args)
if cmd == nil {
t.Fatalf("IncrEXFloat returned nil")
}
if !reflect.DeepEqual(cmd.Args(), tt.want) {
t.Errorf("args mismatch\n got: %#v\nwant: %#v", cmd.Args(), tt.want)
}
})
}
}
func TestSetArgs_ExpireAt(t *testing.T) {
tests := []struct {
name string
expireAt time.Time
want []interface{}
}{
{
name: "whole_seconds",
expireAt: time.Unix(1700000000, 0),
want: []interface{}{"set", "key", "value", "exat", int64(1700000000)},
},
{
name: "sub_millisecond_is_truncated",
expireAt: time.Unix(1700000000, 500_000),
want: []interface{}{"set", "key", "value", "exat", int64(1700000000)},
},
{
name: "milliseconds",
expireAt: time.UnixMilli(1700000000123),
want: []interface{}{"set", "key", "value", "pxat", int64(1700000000123)},
},
{
name: "beyond_nanosecond_range",
expireAt: time.Date(2270, time.January, 2, 3, 4, 5, 123000000, time.UTC),
want: []interface{}{"set", "key", "value", "pxat", int64(9467204645123)},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var cmd Cmder
c := captureCmdable(&cmd)
c.SetArgs(context.Background(), "key", "value", SetArgs{ExpireAt: tt.expireAt})
if got := cmd.Args(); !reflect.DeepEqual(got, tt.want) {
t.Errorf("args = %v, want %v", got, tt.want)
}
})
}
}