Files
redka/redsrv/internal/command/key/scan_test.go
2025-07-13 22:37:43 +05:00

288 lines
6.3 KiB
Go

package key
import (
"fmt"
"testing"
"github.com/nalgeon/be"
"github.com/nalgeon/redka/internal/rkey"
"github.com/nalgeon/redka/redsrv/internal/redis"
)
func TestScanParse(t *testing.T) {
tests := []struct {
cmd string
cursor int
match string
ktype string
count int
err error
}{
{
cmd: "scan",
cursor: 0,
match: "*",
count: 0,
err: redis.ErrInvalidArgNum,
},
{
cmd: "scan 15",
cursor: 15,
match: "*",
count: 0,
err: nil,
},
{
cmd: "scan 15 match *",
cursor: 15,
match: "*",
count: 0,
err: nil,
},
{
cmd: "scan 15 match * count 5",
cursor: 15,
match: "*",
count: 5,
err: nil,
},
{
cmd: "scan 15 match * count ok",
cursor: 15,
match: "*",
count: 0,
err: redis.ErrInvalidInt,
},
{
cmd: "scan 15 count 5 match *",
cursor: 15,
match: "*",
count: 5,
err: nil,
},
{
cmd: "scan 15 match k2* count 5",
cursor: 15,
match: "k2*",
count: 5,
err: nil,
},
{
cmd: "scan 15 match k2* type string",
cursor: 15,
match: "k2*",
ktype: "string",
count: 0,
err: nil,
},
{
cmd: "scan 15 match k2* count 5 type string",
cursor: 15,
match: "k2*",
ktype: "string",
count: 5,
err: nil,
},
{
cmd: "scan ten",
cursor: 0,
match: "",
count: 0,
err: redis.ErrInvalidInt,
},
{
cmd: "scan 15 *",
cursor: 0,
match: "",
count: 0,
err: redis.ErrSyntaxError,
},
{
cmd: "scan 15 * 5",
cursor: 0,
match: "",
count: 0,
err: redis.ErrSyntaxError,
},
}
for _, test := range tests {
t.Run(test.cmd, func(t *testing.T) {
cmd, err := redis.Parse(ParseScan, test.cmd)
be.Equal(t, err, test.err)
if err == nil {
be.Equal(t, cmd.cursor, test.cursor)
be.Equal(t, cmd.match, test.match)
be.Equal(t, cmd.ktype, test.ktype)
be.Equal(t, cmd.count, test.count)
} else {
be.Equal(t, cmd, Scan{})
}
})
}
}
func TestScanExec(t *testing.T) {
t.Run("scan all", func(t *testing.T) {
red := getRedka(t)
_ = red.Str().Set("k11", "11")
_ = red.Str().Set("k12", "12")
_ = red.Str().Set("k21", "21")
_ = red.Str().Set("k22", "22")
_ = red.Str().Set("k31", "31")
var cursor int
{
cmd := redis.MustParse(ParseScan, "scan 0")
conn := redis.NewFakeConn()
res, err := cmd.Run(conn, red)
be.Err(t, err, nil)
sres := res.(rkey.ScanResult)
be.True(t, sres.Cursor > 0)
be.Equal(t, len(sres.Keys), 5)
be.Equal(t, sres.Keys[0].Key, "k11")
be.Equal(t, sres.Keys[4].Key, "k31")
wantOut := fmt.Sprintf("2,%d,5,k11,k12,k21,k22,k31", sres.Cursor)
be.Equal(t, conn.Out(), wantOut)
cursor = sres.Cursor
}
{
next := fmt.Sprintf("scan %d", cursor)
cmd := redis.MustParse(ParseScan, next)
conn := redis.NewFakeConn()
res, err := cmd.Run(conn, red)
be.Err(t, err, nil)
sres := res.(rkey.ScanResult)
be.Equal(t, sres.Cursor, 0)
be.Equal(t, len(sres.Keys), 0)
be.Equal(t, conn.Out(), "2,0,0")
}
})
t.Run("scan pattern", func(t *testing.T) {
red := getRedka(t)
_ = red.Str().Set("k11", "11")
_ = red.Str().Set("k12", "12")
_ = red.Str().Set("k21", "21")
_ = red.Str().Set("k22", "22")
_ = red.Str().Set("k31", "31")
cmd := redis.MustParse(ParseScan, "scan 0 match k2*")
conn := redis.NewFakeConn()
res, err := cmd.Run(conn, red)
be.Err(t, err, nil)
sres := res.(rkey.ScanResult)
be.True(t, sres.Cursor > 0)
be.Equal(t, len(sres.Keys), 2)
be.Equal(t, sres.Keys[0].Key, "k21")
be.Equal(t, sres.Keys[1].Key, "k22")
wantOut := fmt.Sprintf("2,%d,2,k21,k22", sres.Cursor)
be.Equal(t, conn.Out(), wantOut)
})
t.Run("scan type", func(t *testing.T) {
red := getRedka(t)
_ = red.Str().Set("t1", "str")
_, _ = red.List().PushBack("t2", "elem")
_, _ = red.Hash().Set("t4", "field", "value")
_, _ = red.ZSet().Add("t5", "elem", 11)
cmd := redis.MustParse(ParseScan, "scan 0 match t* type hash")
conn := redis.NewFakeConn()
res, err := cmd.Run(conn, red)
be.Err(t, err, nil)
sres := res.(rkey.ScanResult)
be.True(t, sres.Cursor > 0)
be.Equal(t, len(sres.Keys), 1)
be.Equal(t, sres.Keys[0].Key, "t4")
wantOut := fmt.Sprintf("2,%d,1,t4", sres.Cursor)
be.Equal(t, conn.Out(), wantOut)
})
t.Run("scan count", func(t *testing.T) {
red := getRedka(t)
_ = red.Str().Set("k11", "11")
_ = red.Str().Set("k12", "12")
_ = red.Str().Set("k21", "21")
_ = red.Str().Set("k22", "22")
_ = red.Str().Set("k31", "31")
var cursor int
{
// page 1
cmd := redis.MustParse(ParseScan, "scan 0 match * count 2")
conn := redis.NewFakeConn()
res, err := cmd.Run(conn, red)
be.Err(t, err, nil)
sres := res.(rkey.ScanResult)
be.True(t, sres.Cursor > cursor)
be.Equal(t, len(sres.Keys), 2)
be.Equal(t, sres.Keys[0].Key, "k11")
be.Equal(t, sres.Keys[1].Key, "k12")
wantOut := fmt.Sprintf("2,%d,2,k11,k12", sres.Cursor)
be.Equal(t, conn.Out(), wantOut)
cursor = sres.Cursor
}
{
// page 2
next := fmt.Sprintf("scan %d match * count 2", cursor)
cmd := redis.MustParse(ParseScan, next)
conn := redis.NewFakeConn()
res, err := cmd.Run(conn, red)
be.Err(t, err, nil)
sres := res.(rkey.ScanResult)
be.True(t, sres.Cursor > cursor)
be.Equal(t, len(sres.Keys), 2)
be.Equal(t, sres.Keys[0].Key, "k21")
be.Equal(t, sres.Keys[1].Key, "k22")
wantOut := fmt.Sprintf("2,%d,2,k21,k22", sres.Cursor)
be.Equal(t, conn.Out(), wantOut)
cursor = sres.Cursor
}
{
// page 3
next := fmt.Sprintf("scan %d match * count 2", cursor)
cmd := redis.MustParse(ParseScan, next)
conn := redis.NewFakeConn()
res, err := cmd.Run(conn, red)
be.Err(t, err, nil)
sres := res.(rkey.ScanResult)
be.True(t, sres.Cursor > cursor)
be.Equal(t, len(sres.Keys), 1)
be.Equal(t, sres.Keys[0].Key, "k31")
wantOut := fmt.Sprintf("2,%d,1,k31", sres.Cursor)
be.Equal(t, conn.Out(), wantOut)
cursor = sres.Cursor
}
{
// no more pages
next := fmt.Sprintf("scan %d match * count 2", cursor)
cmd := redis.MustParse(ParseScan, next)
conn := redis.NewFakeConn()
res, err := cmd.Run(conn, red)
be.Err(t, err, nil)
sres := res.(rkey.ScanResult)
be.Equal(t, sres.Cursor, 0)
be.Equal(t, len(sres.Keys), 0)
be.Equal(t, conn.Out(), "2,0,0")
}
})
}