From d9d85c5e3f05ef029e3c743bd75bafaa06d21278 Mon Sep 17 00:00:00 2001 From: Ryan Matthews Date: Mon, 22 Sep 2025 16:29:54 -0400 Subject: [PATCH 1/8] Add JSON support to mock library --- expect.go | 845 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ go.mod | 2 +- go.sum | 10 +- mock.go | 191 ++++++++++++ 4 files changed, 1041 insertions(+), 7 deletions(-) diff --git a/expect.go b/expect.go index c7c5b97..3579045 100644 --- a/expect.go +++ b/expect.go @@ -1,8 +1,14 @@ package redismock import ( + "encoding" + "encoding/json" + "errors" "fmt" + "net" "reflect" + "strconv" + "strings" "sync" "time" "unsafe" @@ -394,6 +400,845 @@ type baseMock interface { ExpectTSMRevRangeWithArgs(fromTimestamp int, toTimestamp int, filterExpr []string, options *redis.TSMRevRangeOptions) *ExpectedMapStringSliceInterface ExpectTSMGet(filters []string) *ExpectedMapStringSliceInterface ExpectTSMGetWithArgs(filters []string, options *redis.TSMGetOptions) *ExpectedMapStringSliceInterface + + ExpectJSONArrAppend(key, path string, values ...interface{}) *ExpectedIntSliceCmd + ExpectJSONArrIndex(key, path string, value ...interface{}) *ExpectedIntSliceCmd + ExpectJSONArrIndexWithArgs(key, path string, options *redis.JSONArrIndexArgs, value ...interface{}) *ExpectedIntSliceCmd + ExpectJSONArrInsert(key, path string, index int64, values ...interface{}) *ExpectedIntSliceCmd + ExpectJSONArrLen(key, path string) *ExpectedIntSliceCmd + ExpectJSONArrPop(key, path string, index int) *ExpectedStringSliceCmd + ExpectJSONArrTrim(key, path string) *ExpectedIntSliceCmd + ExpectJSONArrTrimWithArgs(key, path string, options *redis.JSONArrTrimArgs) *ExpectedIntSliceCmd + ExpectJSONClear(key, path string) *ExpectedIntCmd + ExpectJSONDebugMemory(key, path string) *ExpectedIntCmd + ExpectJSONDel(key, path string) *ExpectedIntCmd + ExpectJSONForget(key, path string) *ExpectedIntCmd + ExpectJSONGet(key string, paths ...string) *ExpectedJSONCmd + ExpectJSONGetWithArgs(key string, options *redis.JSONGetArgs, paths ...string) *ExpectedJSONCmd + ExpectJSONMerge(key, path string, value string) *ExpectedStatusCmd + ExpectJSONMSetArgs(docs []redis.JSONSetArgs) *ExpectedStatusCmd + ExpectJSONMSet(params ...interface{}) *ExpectedStatusCmd + ExpectJSONMGet(path string, keys ...string) *ExpectedJSONSliceCmd + ExpectJSONNumIncrBy(key, path string, value float64) *ExpectedJSONCmd + ExpectJSONObjKeys(key, path string) *ExpectedSliceCmd + ExpectJSONObjLen(key, path string) *ExpectedIntPointerSliceCmd + ExpectJSONSet(key, path string, value interface{}) *ExpectedStatusCmd + ExpectJSONSetMode(key, path string, value interface{}, mode string) *ExpectedStatusCmd + ExpectJSONStrAppend(key, path, value string) *ExpectedIntPointerSliceCmd + ExpectJSONStrLen(key, path string) *ExpectedIntPointerSliceCmd + ExpectJSONToggle(key, path string) *ExpectedIntPointerSliceCmd + ExpectJSONType(key, path string) *ExpectedJSONSliceCmd +} + +type ExpectedIntSliceCmd struct { + expectedBase + + val []int64 +} + +func (cmd *ExpectedIntSliceCmd) SetVal(val []int64) { + cmd.setVal = true + cmd.val = make([]int64, len(val)) + copy(cmd.val, val) +} + +func (cmd *ExpectedIntSliceCmd) inflow(c redis.Cmder) { + inflow(c, "val", cmd.val) +} + +func (cmd *ExpectedIntSliceCmd) Result() ([]int64, error) { + return cmd.val, cmd.err +} + +func (cmd *ExpectedIntSliceCmd) String() string { + return cmdString(cmd.cmd, cmd.val) +} + +func (cmd *ExpectedIntSliceCmd) Val() []int64 { + return cmd.val +} + +type ExpectedStatusCmd struct { + expectedBase + + val string +} + +func (cmd *ExpectedStatusCmd) inflow(c redis.Cmder) { + inflow(c, "val", cmd.val) +} + +func (cmd *ExpectedStatusCmd) SetVal(val string) { + cmd.val = val +} + +func (cmd *ExpectedStatusCmd) Val() string { + return cmd.val +} + +func (cmd *ExpectedStatusCmd) Result() (string, error) { + return cmd.val, cmd.err +} + +func (cmd *ExpectedStatusCmd) Bytes() ([]byte, error) { + return StringToBytes(cmd.val), cmd.err +} + +func (cmd *ExpectedStatusCmd) String() string { + return cmdString(cmd.cmd, cmd.val) +} + +type ExpectedIntCmd struct { + expectedBase + + val int64 +} + +func (cmd *ExpectedIntCmd) inflow(c redis.Cmder) { + inflow(c, "val", cmd.val) +} + +func (cmd *ExpectedIntCmd) SetVal(val int64) { + cmd.val = val +} + +func (cmd *ExpectedIntCmd) Val() int64 { + return cmd.val +} + +func (cmd *ExpectedIntCmd) Result() (int64, error) { + return cmd.val, cmd.err +} + +func (cmd *ExpectedIntCmd) Uint64() (uint64, error) { + return uint64(cmd.val), cmd.err +} + +func (cmd *ExpectedIntCmd) String() string { + return cmdString(cmd.cmd, cmd.val) +} + +type ExpectedIntPointerSliceCmd struct { + expectedBase + + val []*int64 +} + +func (cmd *ExpectedIntPointerSliceCmd) inflow(c redis.Cmder) { + inflow(c, "val", cmd.val) +} + +func (cmd *ExpectedIntPointerSliceCmd) String() string { + return cmdString(cmd.cmd, cmd.val) +} + +func (cmd *ExpectedIntPointerSliceCmd) SetVal(val []*int64) { + cmd.val = val +} + +func (cmd *ExpectedIntPointerSliceCmd) Val() []*int64 { + return cmd.val +} + +func (cmd *ExpectedIntPointerSliceCmd) Result() ([]*int64, error) { + return cmd.val, cmd.err +} + +type ExpectedJSONCmd struct { + expectedBase + + val string + expanded interface{} +} + +func (cmd *ExpectedJSONCmd) inflow(c redis.Cmder) { + inflow(c, "val", cmd.val) +} + +func (cmd *ExpectedJSONCmd) String() string { + return cmdString(cmd.cmd, cmd.val) +} + +func (cmd *ExpectedJSONCmd) SetVal(val string) { + cmd.val = val +} + +func (cmd *ExpectedJSONCmd) Val() string { + if len(cmd.val) == 0 && cmd.expanded != nil { + val, err := json.Marshal(cmd.expanded) + if err != nil { + cmd.SetErr(err) + return "" + } + return string(val) + + } else { + return cmd.val + } +} + +func (cmd *ExpectedJSONCmd) Result() (string, error) { + return cmd.Val(), cmd.cmd.Err() +} + +func (cmd *ExpectedJSONCmd) Expanded() (interface{}, error) { + if len(cmd.val) != 0 && cmd.expanded == nil { + err := json.Unmarshal([]byte(cmd.val), &cmd.expanded) + if err != nil { + return nil, err + } + } + + return cmd.expanded, nil +} + +type ExpectedJSONSliceCmd struct { + expectedBase + + val []interface{} +} + +func (cmd *ExpectedJSONSliceCmd) inflow(c redis.Cmder) { + inflow(c, "val", cmd.val) +} + +func (cmd *ExpectedJSONSliceCmd) String() string { + return cmdString(cmd.cmd, cmd.val) +} + +func (cmd *ExpectedJSONSliceCmd) SetVal(val []interface{}) { + cmd.val = val +} + +func (cmd *ExpectedJSONSliceCmd) Val() []interface{} { + return cmd.val +} + +func (cmd *ExpectedJSONSliceCmd) Result() ([]interface{}, error) { + return cmd.val, cmd.err +} + +type ExpectedStringSliceCmd struct { + expectedBase + + val []string +} + +func (cmd *ExpectedStringSliceCmd) inflow(c redis.Cmder) { + inflow(c, "val", cmd.val) +} + +func (cmd *ExpectedStringSliceCmd) SetVal(val []string) { + cmd.val = val +} + +func (cmd *ExpectedStringSliceCmd) Val() []string { + return cmd.val +} + +func (cmd *ExpectedStringSliceCmd) Result() ([]string, error) { + return cmd.val, cmd.err +} + +func (cmd *ExpectedStringSliceCmd) String() string { + return cmdString(cmd.cmd, cmd.val) +} + +func (cmd *ExpectedStringSliceCmd) ScanSlice(container interface{}) error { + return ScanSlice(cmd.Val(), container) +} + +type ExpectedSliceCmd struct { + expectedBase + + val []interface{} +} + +func (cmd *ExpectedSliceCmd) inflow(c redis.Cmder) { + inflow(c, "val", cmd.val) +} + +func (cmd *ExpectedSliceCmd) SetVal(val []interface{}) { + cmd.val = val +} + +func (cmd *ExpectedSliceCmd) Val() []interface{} { + return cmd.val +} + +func (cmd *ExpectedSliceCmd) Result() ([]interface{}, error) { + return cmd.val, cmd.err +} + +func (cmd *ExpectedSliceCmd) String() string { + return cmdString(cmd.cmd, cmd.val) +} + +// Scan scans the results from the map into a destination struct. The map keys +// are matched in the Redis struct fields by the `redis:"field"` tag. +func (cmd *ExpectedSliceCmd) Scan(dst interface{}) error { + if cmd.err != nil { + return cmd.err + } + + // Pass the list of keys and values. + // Skip the first two args for: HMGET key + var args []interface{} + if cmd.args()[0] == "hmget" { + args = cmd.args()[2:] + } else { + // Otherwise, it's: MGET field field ... + args = cmd.args()[1:] + } + + return HScan(dst, args, cmd.val) +} + +type decoderFunc func(reflect.Value, string) error + +type structField struct { + index int + fn decoderFunc +} + +type structSpec struct { + m map[string]*structField +} + +func (s *structSpec) set(tag string, sf *structField) { + s.m[tag] = sf +} + +type StructValue struct { + spec *structSpec + value reflect.Value +} + +type Scanner interface { + ScanRedis(s string) error +} + +func (s StructValue) Scan(key string, value string) error { + field, ok := s.spec.m[key] + if !ok { + return nil + } + + v := s.value.Field(field.index) + isPtr := v.Kind() == reflect.Ptr + + if isPtr && v.IsNil() { + v.Set(reflect.New(v.Type().Elem())) + } + if !isPtr && v.Type().Name() != "" && v.CanAddr() { + v = v.Addr() + isPtr = true + } + + if isPtr && v.Type().NumMethod() > 0 && v.CanInterface() { + switch scan := v.Interface().(type) { + case Scanner: + return scan.ScanRedis(value) + case encoding.TextUnmarshaler: + return scan.UnmarshalText(StringToBytes(value)) + } + } + + if isPtr { + v = v.Elem() + } + + if err := field.fn(v, value); err != nil { + t := s.value.Type() + return fmt.Errorf("cannot scan redis.result %s into struct field %s.%s of type %s, error-%s", + value, t.Name(), t.Field(field.index).Name, t.Field(field.index).Type, err.Error()) + } + return nil +} + +type structMap struct { + m sync.Map +} + +func newStructMap() *structMap { + return new(structMap) +} + +func (s *structMap) get(t reflect.Type) *structSpec { + if v, ok := s.m.Load(t); ok { + return v.(*structSpec) + } + + spec := newStructSpec(t, "redis") + s.m.Store(t, spec) + return spec +} + +var ( + decoders = []decoderFunc{ + reflect.Bool: decodeBool, + reflect.Int: decodeInt, + reflect.Int8: decodeInt8, + reflect.Int16: decodeInt16, + reflect.Int32: decodeInt32, + reflect.Int64: decodeInt64, + reflect.Uint: decodeUint, + reflect.Uint8: decodeUint8, + reflect.Uint16: decodeUint16, + reflect.Uint32: decodeUint32, + reflect.Uint64: decodeUint64, + reflect.Float32: decodeFloat32, + reflect.Float64: decodeFloat64, + reflect.Complex64: decodeUnsupported, + reflect.Complex128: decodeUnsupported, + reflect.Array: decodeUnsupported, + reflect.Chan: decodeUnsupported, + reflect.Func: decodeUnsupported, + reflect.Interface: decodeUnsupported, + reflect.Map: decodeUnsupported, + reflect.Ptr: decodeUnsupported, + reflect.Slice: decodeSlice, + reflect.String: decodeString, + reflect.Struct: decodeUnsupported, + reflect.UnsafePointer: decodeUnsupported, + } + globalStructMap = newStructMap() +) + +func decodeBool(f reflect.Value, s string) error { + b, err := strconv.ParseBool(s) + if err != nil { + return err + } + f.SetBool(b) + return nil +} + +func decodeInt8(f reflect.Value, s string) error { + return decodeNumber(f, s, 8) +} + +func decodeInt16(f reflect.Value, s string) error { + return decodeNumber(f, s, 16) +} + +func decodeInt32(f reflect.Value, s string) error { + return decodeNumber(f, s, 32) +} + +func decodeInt64(f reflect.Value, s string) error { + return decodeNumber(f, s, 64) +} + +func decodeInt(f reflect.Value, s string) error { + return decodeNumber(f, s, 0) +} + +func decodeNumber(f reflect.Value, s string, bitSize int) error { + v, err := strconv.ParseInt(s, 10, bitSize) + if err != nil { + return err + } + f.SetInt(v) + return nil +} + +func decodeUint8(f reflect.Value, s string) error { + return decodeUnsignedNumber(f, s, 8) +} + +func decodeUint16(f reflect.Value, s string) error { + return decodeUnsignedNumber(f, s, 16) +} + +func decodeUint32(f reflect.Value, s string) error { + return decodeUnsignedNumber(f, s, 32) +} + +func decodeUint64(f reflect.Value, s string) error { + return decodeUnsignedNumber(f, s, 64) +} + +func decodeUint(f reflect.Value, s string) error { + return decodeUnsignedNumber(f, s, 0) +} + +func decodeUnsignedNumber(f reflect.Value, s string, bitSize int) error { + v, err := strconv.ParseUint(s, 10, bitSize) + if err != nil { + return err + } + f.SetUint(v) + return nil +} + +func decodeFloat32(f reflect.Value, s string) error { + v, err := strconv.ParseFloat(s, 32) + if err != nil { + return err + } + f.SetFloat(v) + return nil +} + +// although the default is float64, but we better define it. +func decodeFloat64(f reflect.Value, s string) error { + v, err := strconv.ParseFloat(s, 64) + if err != nil { + return err + } + f.SetFloat(v) + return nil +} + +func decodeString(f reflect.Value, s string) error { + f.SetString(s) + return nil +} + +func decodeSlice(f reflect.Value, s string) error { + // []byte slice ([]uint8). + if f.Type().Elem().Kind() == reflect.Uint8 { + f.SetBytes([]byte(s)) + } + return nil +} + +func decodeUnsupported(v reflect.Value, s string) error { + return fmt.Errorf("redis.Scan(unsupported %s)", v.Type()) +} + +func newStructSpec(t reflect.Type, fieldTag string) *structSpec { + numField := t.NumField() + out := &structSpec{ + m: make(map[string]*structField, numField), + } + + for i := 0; i < numField; i++ { + f := t.Field(i) + + tag := f.Tag.Get(fieldTag) + if tag == "" || tag == "-" { + continue + } + + tag = strings.Split(tag, ",")[0] + if tag == "" { + continue + } + + // Use the built-in decoder. + kind := f.Type.Kind() + if kind == reflect.Pointer { + kind = f.Type.Elem().Kind() + } + out.set(tag, &structField{index: i, fn: decoders[kind]}) + } + + return out +} + +func Struct(dst interface{}) (StructValue, error) { + v := reflect.ValueOf(dst) + + // The destination to scan into should be a struct pointer. + if v.Kind() != reflect.Ptr || v.IsNil() { + return StructValue{}, fmt.Errorf("redis.Scan(non-pointer %T)", dst) + } + + v = v.Elem() + if v.Kind() != reflect.Struct { + return StructValue{}, fmt.Errorf("redis.Scan(non-struct %T)", dst) + } + + return StructValue{ + spec: globalStructMap.get(v.Type()), + value: v, + }, nil +} + +func HScan(dst interface{}, keys []interface{}, vals []interface{}) error { + if len(keys) != len(vals) { + return errors.New("args should have the same number of keys and vals") + } + + strct, err := Struct(dst) + if err != nil { + return err + } + + // Iterate through the (key, value) sequence. + for i := 0; i < len(vals); i++ { + key, ok := keys[i].(string) + if !ok { + continue + } + + val, ok := vals[i].(string) + if !ok { + continue + } + + if err := strct.Scan(key, val); err != nil { + return err + } + } + + return nil +} + +func cmdString(cmd redis.Cmder, val interface{}) string { + b := make([]byte, 0, 64) + + for i, arg := range cmd.Args() { + if i > 0 { + b = append(b, ' ') + } + b = AppendArg(b, arg) + } + + if err := cmd.Err(); err != nil { + b = append(b, ": "...) + b = append(b, err.Error()...) + } else if val != nil { + b = append(b, ": "...) + b = AppendArg(b, val) + } + + return BytesToString(b) +} + +func AppendArg(b []byte, v interface{}) []byte { + switch v := v.(type) { + case nil: + return append(b, ""...) + case string: + return appendUTF8String(b, StringToBytes(v)) + case []byte: + return appendUTF8String(b, v) + case int: + return strconv.AppendInt(b, int64(v), 10) + case int8: + return strconv.AppendInt(b, int64(v), 10) + case int16: + return strconv.AppendInt(b, int64(v), 10) + case int32: + return strconv.AppendInt(b, int64(v), 10) + case int64: + return strconv.AppendInt(b, v, 10) + case uint: + return strconv.AppendUint(b, uint64(v), 10) + case uint8: + return strconv.AppendUint(b, uint64(v), 10) + case uint16: + return strconv.AppendUint(b, uint64(v), 10) + case uint32: + return strconv.AppendUint(b, uint64(v), 10) + case uint64: + return strconv.AppendUint(b, v, 10) + case float32: + return strconv.AppendFloat(b, float64(v), 'f', -1, 64) + case float64: + return strconv.AppendFloat(b, v, 'f', -1, 64) + case bool: + if v { + return append(b, "true"...) + } + return append(b, "false"...) + case time.Time: + return v.AppendFormat(b, time.RFC3339Nano) + default: + return append(b, fmt.Sprint(v)...) + } +} + +func appendUTF8String(dst []byte, src []byte) []byte { + dst = append(dst, src...) + return dst +} + +func BytesToString(b []byte) string { + return *(*string)(unsafe.Pointer(&b)) +} + +func ScanSlice(data []string, slice interface{}) error { + v := reflect.ValueOf(slice) + if !v.IsValid() { + return fmt.Errorf("redis: ScanSlice(nil)") + } + if v.Kind() != reflect.Ptr { + return fmt.Errorf("redis: ScanSlice(non-pointer %T)", slice) + } + v = v.Elem() + if v.Kind() != reflect.Slice { + return fmt.Errorf("redis: ScanSlice(non-slice %T)", slice) + } + + next := makeSliceNextElemFunc(v) + for i, s := range data { + elem := next() + if err := Scan([]byte(s), elem.Addr().Interface()); err != nil { + err = fmt.Errorf("redis: ScanSlice index=%d value=%q failed: %w", i, s, err) + return err + } + } + + return nil +} + +func Scan(b []byte, v interface{}) error { + switch v := v.(type) { + case nil: + return fmt.Errorf("redis: Scan(nil)") + case *string: + *v = BytesToString(b) + return nil + case *[]byte: + *v = b + return nil + case *int: + var err error + *v, err = strconv.Atoi(BytesToString(b)) + return err + case *int8: + n, err := strconv.ParseInt(BytesToString(b), 10, 8) + if err != nil { + return err + } + *v = int8(n) + return nil + case *int16: + n, err := strconv.ParseInt(BytesToString(b), 10, 16) + if err != nil { + return err + } + *v = int16(n) + return nil + case *int32: + n, err := strconv.ParseInt(BytesToString(b), 10, 32) + if err != nil { + return err + } + *v = int32(n) + return nil + case *int64: + n, err := strconv.ParseInt(BytesToString(b), 10, 64) + if err != nil { + return err + } + *v = n + return nil + case *uint: + n, err := strconv.ParseUint(BytesToString(b), 10, 64) + if err != nil { + return err + } + *v = uint(n) + return nil + case *uint8: + n, err := strconv.ParseUint(BytesToString(b), 10, 8) + if err != nil { + return err + } + *v = uint8(n) + return nil + case *uint16: + n, err := strconv.ParseUint(BytesToString(b), 10, 16) + if err != nil { + return err + } + *v = uint16(n) + return nil + case *uint32: + n, err := strconv.ParseUint(BytesToString(b), 10, 32) + if err != nil { + return err + } + *v = uint32(n) + return nil + case *uint64: + n, err := strconv.ParseUint(BytesToString(b), 10, 64) + if err != nil { + return err + } + *v = n + return nil + case *float32: + n, err := strconv.ParseFloat(BytesToString(b), 32) + if err != nil { + return err + } + *v = float32(n) + return err + case *float64: + var err error + *v, err = strconv.ParseFloat(BytesToString(b), 64) + return err + case *bool: + *v = len(b) == 1 && b[0] == '1' + return nil + case *time.Time: + var err error + *v, err = time.Parse(time.RFC3339Nano, BytesToString(b)) + return err + case *time.Duration: + n, err := strconv.ParseInt(BytesToString(b), 10, 64) + if err != nil { + return err + } + *v = time.Duration(n) + return nil + case encoding.BinaryUnmarshaler: + return v.UnmarshalBinary(b) + case *net.IP: + *v = b + return nil + default: + return fmt.Errorf( + "redis: can't unmarshal %T (consider implementing BinaryUnmarshaler)", v) + } +} + +func makeSliceNextElemFunc(v reflect.Value) func() reflect.Value { + elemType := v.Type().Elem() + + if elemType.Kind() == reflect.Ptr { + elemType = elemType.Elem() + return func() reflect.Value { + if v.Len() < v.Cap() { + v.Set(v.Slice(0, v.Len()+1)) + elem := v.Index(v.Len() - 1) + if elem.IsNil() { + elem.Set(reflect.New(elemType)) + } + return elem.Elem() + } + + elem := reflect.New(elemType) + v.Set(reflect.Append(v, elem)) + return elem.Elem() + } + } + + zero := reflect.Zero(elemType) + return func() reflect.Value { + if v.Len() < v.Cap() { + v.Set(v.Slice(0, v.Len()+1)) + return v.Index(v.Len() - 1) + } + + v.Set(reflect.Append(v, zero)) + return v.Index(v.Len() - 1) + } +} + +func StringToBytes(s string) []byte { + return *(*[]byte)(unsafe.Pointer( + &struct { + string + Cap int + }{s, len(s)}, + )) } type pipelineMock interface { diff --git a/go.mod b/go.mod index bbc013e..e68cccd 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.18 require ( github.com/onsi/ginkgo v1.16.5 github.com/onsi/gomega v1.25.0 - github.com/redis/go-redis/v9 v9.2.0 + github.com/redis/go-redis/v9 v9.3.0 ) require ( diff --git a/go.sum b/go.sum index 0151d92..877cb0e 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/bsm/ginkgo/v2 v2.7.0 h1:ItPMPH90RbmZJt5GtkcNvIRuGEdwlBItdNVoyzaNQao= -github.com/bsm/gomega v1.26.0 h1:LhQm+AFcgV2M0WyKroMASzAzCAJVpAxQXv4SaI9a69Y= +github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -38,10 +38,8 @@ github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1y github.com/onsi/gomega v1.25.0 h1:Vw7br2PCDYijJHSfBOWhov+8cAnUf8MfMaIOV323l6Y= github.com/onsi/gomega v1.25.0/go.mod h1:r+zV744Re+DiYCIPRlYOTxn0YkOLcAnW8k1xXdMPGhM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/redis/go-redis/v9 v9.0.3 h1:+7mmR26M0IvyLxGZUHxu4GiBkJkVDid0Un+j4ScYu4k= -github.com/redis/go-redis/v9 v9.0.3/go.mod h1:WqMKv5vnQbRuZstUwxQI195wHy+t4PuXDOjzMvcuQHk= -github.com/redis/go-redis/v9 v9.2.0 h1:zwMdX0A4eVzse46YN18QhuDiM4uf3JmkOB4VZrdt5uI= -github.com/redis/go-redis/v9 v9.2.0/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= +github.com/redis/go-redis/v9 v9.3.0 h1:RiVDjmig62jIWp7Kk4XVLs0hzV6pI3PyTnnL0cnn0u0= +github.com/redis/go-redis/v9 v9.3.0/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= diff --git a/mock.go b/mock.go index 3c6a3b9..add153e 100644 --- a/mock.go +++ b/mock.go @@ -2898,3 +2898,194 @@ func (m *mock) ExpectTSMGetWithArgs(filters []string, options *redis.TSMGetOptio m.pushExpect(e) return e } + +// ---------------------------------------------------------------------------- + +func (m *mock) ExpectJSONArrAppend(key, path string, values ...interface{}) *ExpectedIntSliceCmd { + e := &ExpectedIntSliceCmd{} + e.cmd = m.factory.JSONArrAppend(m.ctx, key, path, values) + m.pushExpect(e) + return e +} + +func (m *mock) ExpectJSONArrIndex(key, path string, value ...interface{}) *ExpectedIntSliceCmd { + e := &ExpectedIntSliceCmd{} + e.cmd = m.factory.JSONArrIndex(m.ctx, key, path, value) + m.pushExpect(e) + return e +} + +func (m *mock) ExpectJSONArrIndexWithArgs(key, path string, options *redis.JSONArrIndexArgs, value ...interface{}) *ExpectedIntSliceCmd { + e := &ExpectedIntSliceCmd{} + e.cmd = m.factory.JSONArrIndexWithArgs(m.ctx, key, path, options, value) + m.pushExpect(e) + return e +} + +func (m *mock) ExpectJSONArrInsert(key, path string, index int64, values ...interface{}) *ExpectedIntSliceCmd { + e := &ExpectedIntSliceCmd{} + e.cmd = m.factory.JSONArrInsert(m.ctx, key, path, index, values) + m.pushExpect(e) + return e +} + +func (m *mock) ExpectJSONArrLen(key, path string) *ExpectedIntSliceCmd { + e := &ExpectedIntSliceCmd{} + e.cmd = m.factory.JSONArrLen(m.ctx, key, path) + m.pushExpect(e) + return e +} + +func (m *mock) ExpectJSONArrPop(key, path string, index int) *ExpectedStringSliceCmd { + e := &ExpectedStringSliceCmd{} + e.cmd = m.factory.JSONArrPop(m.ctx, key, path, index) + m.pushExpect(e) + return e +} + +func (m *mock) ExpectJSONArrTrim(key, path string) *ExpectedIntSliceCmd { + e := &ExpectedIntSliceCmd{} + e.cmd = m.factory.JSONArrTrim(m.ctx, key, path) + m.pushExpect(e) + return e +} + +func (m *mock) ExpectJSONArrTrimWithArgs(key, path string, options *redis.JSONArrTrimArgs) *ExpectedIntSliceCmd { + e := &ExpectedIntSliceCmd{} + e.cmd = m.factory.JSONArrTrimWithArgs(m.ctx, key, path, options) + m.pushExpect(e) + return e +} + +func (m *mock) ExpectJSONClear(key, path string) *ExpectedIntCmd { + e := &ExpectedIntCmd{} + e.cmd = m.factory.JSONClear(m.ctx, key, path) + m.pushExpect(e) + return e +} + +func (m *mock) ExpectJSONDebugMemory(key, path string) *ExpectedIntCmd { + e := &ExpectedIntCmd{} + e.cmd = m.factory.JSONDebugMemory(m.ctx, key, path) + m.pushExpect(e) + return e +} + +func (m *mock) ExpectJSONDel(key, path string) *ExpectedIntCmd { + e := &ExpectedIntCmd{} + e.cmd = m.factory.JSONDel(m.ctx, key, path) + m.pushExpect(e) + return e +} + +func (m *mock) ExpectJSONForget(key, path string) *ExpectedIntCmd { + e := &ExpectedIntCmd{} + e.cmd = m.factory.JSONForget(m.ctx, key, path) + m.pushExpect(e) + return e +} + +func (m *mock) ExpectJSONGet(key string, paths ...string) *ExpectedJSONCmd { + e := &ExpectedJSONCmd{} + e.cmd = m.factory.JSONGet(m.ctx, key, paths...) + m.pushExpect(e) + return e +} + +func (m *mock) ExpectJSONGetWithArgs(key string, options *redis.JSONGetArgs, paths ...string) *ExpectedJSONCmd { + e := &ExpectedJSONCmd{} + e.cmd = m.factory.JSONGetWithArgs(m.ctx, key, options, paths...) + m.pushExpect(e) + return e +} + +func (m *mock) ExpectJSONMerge(key, path string, value string) *ExpectedStatusCmd { + e := &ExpectedStatusCmd{} + e.cmd = m.factory.JSONMerge(m.ctx, key, path, value) + m.pushExpect(e) + return e +} + +func (m *mock) ExpectJSONMSetArgs(docs []redis.JSONSetArgs) *ExpectedStatusCmd { + e := &ExpectedStatusCmd{} + e.cmd = m.factory.JSONMSetArgs(m.ctx, docs) + m.pushExpect(e) + return e +} + +func (m *mock) ExpectJSONMSet(params ...interface{}) *ExpectedStatusCmd { + e := &ExpectedStatusCmd{} + e.cmd = m.factory.JSONMSet(m.ctx, params...) + m.pushExpect(e) + return e +} + +func (m *mock) ExpectJSONMGet(path string, keys ...string) *ExpectedJSONSliceCmd { + e := &ExpectedJSONSliceCmd{} + e.cmd = m.factory.JSONMGet(m.ctx, path, keys...) + m.pushExpect(e) + return e +} + +func (m *mock) ExpectJSONNumIncrBy(key, path string, value float64) *ExpectedJSONCmd { + e := &ExpectedJSONCmd{} + e.cmd = m.factory.JSONNumIncrBy(m.ctx, key, path, value) + m.pushExpect(e) + return e +} + +func (m *mock) ExpectJSONObjKeys(key, path string) *ExpectedSliceCmd { + e := &ExpectedSliceCmd{} + e.cmd = m.factory.JSONObjKeys(m.ctx, key, path) + m.pushExpect(e) + return e +} + +func (m *mock) ExpectJSONObjLen(key, path string) *ExpectedIntPointerSliceCmd { + e := &ExpectedIntPointerSliceCmd{} + e.cmd = m.factory.JSONObjLen(m.ctx, key, path) + m.pushExpect(e) + return e +} + +func (m *mock) ExpectJSONSet(key, path string, value interface{}) *ExpectedStatusCmd { + e := &ExpectedStatusCmd{} + e.cmd = m.factory.JSONSet(m.ctx, key, path, value) + m.pushExpect(e) + return e +} + +func (m *mock) ExpectJSONSetMode(key, path string, value interface{}, mode string) *ExpectedStatusCmd { + e := &ExpectedStatusCmd{} + e.cmd = m.factory.JSONSetMode(m.ctx, key, path, value, mode) + m.pushExpect(e) + return e +} + +func (m *mock) ExpectJSONStrAppend(key, path, value string) *ExpectedIntPointerSliceCmd { + e := &ExpectedIntPointerSliceCmd{} + e.cmd = m.factory.JSONStrAppend(m.ctx, key, path, value) + m.pushExpect(e) + return e +} + +func (m *mock) ExpectJSONStrLen(key, path string) *ExpectedIntPointerSliceCmd { + e := &ExpectedIntPointerSliceCmd{} + e.cmd = m.factory.JSONStrLen(m.ctx, key, path) + m.pushExpect(e) + return e +} + +func (m *mock) ExpectJSONToggle(key, path string) *ExpectedIntPointerSliceCmd { + e := &ExpectedIntPointerSliceCmd{} + e.cmd = m.factory.JSONToggle(m.ctx, key, path) + m.pushExpect(e) + return e +} + +func (m *mock) ExpectJSONType(key, path string) *ExpectedJSONSliceCmd { + e := &ExpectedJSONSliceCmd{} + e.cmd = m.factory.JSONType(m.ctx, key, path) + m.pushExpect(e) + return e +} From e2fbfafac824b3995b12738090b213e2103fcfcb Mon Sep 17 00:00:00 2001 From: Ryan Matthews Date: Mon, 22 Sep 2025 16:35:15 -0400 Subject: [PATCH 2/8] Change module name for testing --- example/example.go | 2 +- example/ordinary/main_test.go | 2 +- go.mod | 4 +++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/example/example.go b/example/example.go index 7d21150..9acd2fb 100644 --- a/example/example.go +++ b/example/example.go @@ -5,7 +5,7 @@ import ( "errors" "time" - "github.com/go-redis/redismock/v9" + "github.com/mrrsm/redismock/v9" ) var _ = example diff --git a/example/ordinary/main_test.go b/example/ordinary/main_test.go index 8992337..63251ef 100644 --- a/example/ordinary/main_test.go +++ b/example/ordinary/main_test.go @@ -4,7 +4,7 @@ import ( "errors" "testing" - "github.com/go-redis/redismock/v9" + "github.com/mrrsm/redismock/v9" ) func TestItemCacheFail(t *testing.T) { diff --git a/go.mod b/go.mod index e68cccd..d55b2f0 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/go-redis/redismock/v9 +module github.com/mrrsm/redismock/v9 go 1.18 @@ -20,3 +20,5 @@ require ( gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) + +replace github.com/go-redis/redismock/v9 => github.com/mrrsm/redismock/v9 v9.2.0 From d7a02fb1a184badb5bcc72620d5edfc95a7f4798 Mon Sep 17 00:00:00 2001 From: Ryan Matthews Date: Wed, 24 Sep 2025 14:01:28 -0400 Subject: [PATCH 3/8] Add FT support and create more files for easier management --- expect.go | 845 ++----------------------------------------------- expect_ft.go | 301 ++++++++++++++++++ expect_json.go | 382 ++++++++++++++++++++++ go.mod | 4 +- go.sum | 4 +- helper.go | 482 ++++++++++++++++++++++++++++ mock.go | 191 ----------- mock_ft.go | 199 ++++++++++++ mock_json.go | 192 +++++++++++ 9 files changed, 1588 insertions(+), 1012 deletions(-) create mode 100644 expect_ft.go create mode 100644 expect_json.go create mode 100644 helper.go create mode 100644 mock_ft.go create mode 100644 mock_json.go diff --git a/expect.go b/expect.go index 3579045..1d4116b 100644 --- a/expect.go +++ b/expect.go @@ -1,14 +1,8 @@ package redismock import ( - "encoding" - "encoding/json" - "errors" "fmt" - "net" "reflect" - "strconv" - "strings" "sync" "time" "unsafe" @@ -428,817 +422,35 @@ type baseMock interface { ExpectJSONStrLen(key, path string) *ExpectedIntPointerSliceCmd ExpectJSONToggle(key, path string) *ExpectedIntPointerSliceCmd ExpectJSONType(key, path string) *ExpectedJSONSliceCmd -} - -type ExpectedIntSliceCmd struct { - expectedBase - - val []int64 -} - -func (cmd *ExpectedIntSliceCmd) SetVal(val []int64) { - cmd.setVal = true - cmd.val = make([]int64, len(val)) - copy(cmd.val, val) -} - -func (cmd *ExpectedIntSliceCmd) inflow(c redis.Cmder) { - inflow(c, "val", cmd.val) -} - -func (cmd *ExpectedIntSliceCmd) Result() ([]int64, error) { - return cmd.val, cmd.err -} - -func (cmd *ExpectedIntSliceCmd) String() string { - return cmdString(cmd.cmd, cmd.val) -} - -func (cmd *ExpectedIntSliceCmd) Val() []int64 { - return cmd.val -} - -type ExpectedStatusCmd struct { - expectedBase - - val string -} - -func (cmd *ExpectedStatusCmd) inflow(c redis.Cmder) { - inflow(c, "val", cmd.val) -} - -func (cmd *ExpectedStatusCmd) SetVal(val string) { - cmd.val = val -} - -func (cmd *ExpectedStatusCmd) Val() string { - return cmd.val -} - -func (cmd *ExpectedStatusCmd) Result() (string, error) { - return cmd.val, cmd.err -} - -func (cmd *ExpectedStatusCmd) Bytes() ([]byte, error) { - return StringToBytes(cmd.val), cmd.err -} - -func (cmd *ExpectedStatusCmd) String() string { - return cmdString(cmd.cmd, cmd.val) -} - -type ExpectedIntCmd struct { - expectedBase - - val int64 -} - -func (cmd *ExpectedIntCmd) inflow(c redis.Cmder) { - inflow(c, "val", cmd.val) -} - -func (cmd *ExpectedIntCmd) SetVal(val int64) { - cmd.val = val -} - -func (cmd *ExpectedIntCmd) Val() int64 { - return cmd.val -} - -func (cmd *ExpectedIntCmd) Result() (int64, error) { - return cmd.val, cmd.err -} - -func (cmd *ExpectedIntCmd) Uint64() (uint64, error) { - return uint64(cmd.val), cmd.err -} - -func (cmd *ExpectedIntCmd) String() string { - return cmdString(cmd.cmd, cmd.val) -} - -type ExpectedIntPointerSliceCmd struct { - expectedBase - - val []*int64 -} - -func (cmd *ExpectedIntPointerSliceCmd) inflow(c redis.Cmder) { - inflow(c, "val", cmd.val) -} - -func (cmd *ExpectedIntPointerSliceCmd) String() string { - return cmdString(cmd.cmd, cmd.val) -} - -func (cmd *ExpectedIntPointerSliceCmd) SetVal(val []*int64) { - cmd.val = val -} - -func (cmd *ExpectedIntPointerSliceCmd) Val() []*int64 { - return cmd.val -} - -func (cmd *ExpectedIntPointerSliceCmd) Result() ([]*int64, error) { - return cmd.val, cmd.err -} - -type ExpectedJSONCmd struct { - expectedBase - - val string - expanded interface{} -} - -func (cmd *ExpectedJSONCmd) inflow(c redis.Cmder) { - inflow(c, "val", cmd.val) -} - -func (cmd *ExpectedJSONCmd) String() string { - return cmdString(cmd.cmd, cmd.val) -} - -func (cmd *ExpectedJSONCmd) SetVal(val string) { - cmd.val = val -} - -func (cmd *ExpectedJSONCmd) Val() string { - if len(cmd.val) == 0 && cmd.expanded != nil { - val, err := json.Marshal(cmd.expanded) - if err != nil { - cmd.SetErr(err) - return "" - } - return string(val) - - } else { - return cmd.val - } -} - -func (cmd *ExpectedJSONCmd) Result() (string, error) { - return cmd.Val(), cmd.cmd.Err() -} - -func (cmd *ExpectedJSONCmd) Expanded() (interface{}, error) { - if len(cmd.val) != 0 && cmd.expanded == nil { - err := json.Unmarshal([]byte(cmd.val), &cmd.expanded) - if err != nil { - return nil, err - } - } - - return cmd.expanded, nil -} - -type ExpectedJSONSliceCmd struct { - expectedBase - - val []interface{} -} - -func (cmd *ExpectedJSONSliceCmd) inflow(c redis.Cmder) { - inflow(c, "val", cmd.val) -} - -func (cmd *ExpectedJSONSliceCmd) String() string { - return cmdString(cmd.cmd, cmd.val) -} - -func (cmd *ExpectedJSONSliceCmd) SetVal(val []interface{}) { - cmd.val = val -} - -func (cmd *ExpectedJSONSliceCmd) Val() []interface{} { - return cmd.val -} - -func (cmd *ExpectedJSONSliceCmd) Result() ([]interface{}, error) { - return cmd.val, cmd.err -} - -type ExpectedStringSliceCmd struct { - expectedBase - - val []string -} - -func (cmd *ExpectedStringSliceCmd) inflow(c redis.Cmder) { - inflow(c, "val", cmd.val) -} - -func (cmd *ExpectedStringSliceCmd) SetVal(val []string) { - cmd.val = val -} - -func (cmd *ExpectedStringSliceCmd) Val() []string { - return cmd.val -} - -func (cmd *ExpectedStringSliceCmd) Result() ([]string, error) { - return cmd.val, cmd.err -} - -func (cmd *ExpectedStringSliceCmd) String() string { - return cmdString(cmd.cmd, cmd.val) -} - -func (cmd *ExpectedStringSliceCmd) ScanSlice(container interface{}) error { - return ScanSlice(cmd.Val(), container) -} - -type ExpectedSliceCmd struct { - expectedBase - - val []interface{} -} - -func (cmd *ExpectedSliceCmd) inflow(c redis.Cmder) { - inflow(c, "val", cmd.val) -} - -func (cmd *ExpectedSliceCmd) SetVal(val []interface{}) { - cmd.val = val -} - -func (cmd *ExpectedSliceCmd) Val() []interface{} { - return cmd.val -} - -func (cmd *ExpectedSliceCmd) Result() ([]interface{}, error) { - return cmd.val, cmd.err -} - -func (cmd *ExpectedSliceCmd) String() string { - return cmdString(cmd.cmd, cmd.val) -} - -// Scan scans the results from the map into a destination struct. The map keys -// are matched in the Redis struct fields by the `redis:"field"` tag. -func (cmd *ExpectedSliceCmd) Scan(dst interface{}) error { - if cmd.err != nil { - return cmd.err - } - - // Pass the list of keys and values. - // Skip the first two args for: HMGET key - var args []interface{} - if cmd.args()[0] == "hmget" { - args = cmd.args()[2:] - } else { - // Otherwise, it's: MGET field field ... - args = cmd.args()[1:] - } - - return HScan(dst, args, cmd.val) -} - -type decoderFunc func(reflect.Value, string) error - -type structField struct { - index int - fn decoderFunc -} - -type structSpec struct { - m map[string]*structField -} - -func (s *structSpec) set(tag string, sf *structField) { - s.m[tag] = sf -} - -type StructValue struct { - spec *structSpec - value reflect.Value -} - -type Scanner interface { - ScanRedis(s string) error -} - -func (s StructValue) Scan(key string, value string) error { - field, ok := s.spec.m[key] - if !ok { - return nil - } - - v := s.value.Field(field.index) - isPtr := v.Kind() == reflect.Ptr - - if isPtr && v.IsNil() { - v.Set(reflect.New(v.Type().Elem())) - } - if !isPtr && v.Type().Name() != "" && v.CanAddr() { - v = v.Addr() - isPtr = true - } - - if isPtr && v.Type().NumMethod() > 0 && v.CanInterface() { - switch scan := v.Interface().(type) { - case Scanner: - return scan.ScanRedis(value) - case encoding.TextUnmarshaler: - return scan.UnmarshalText(StringToBytes(value)) - } - } - - if isPtr { - v = v.Elem() - } - - if err := field.fn(v, value); err != nil { - t := s.value.Type() - return fmt.Errorf("cannot scan redis.result %s into struct field %s.%s of type %s, error-%s", - value, t.Name(), t.Field(field.index).Name, t.Field(field.index).Type, err.Error()) - } - return nil -} - -type structMap struct { - m sync.Map -} - -func newStructMap() *structMap { - return new(structMap) -} - -func (s *structMap) get(t reflect.Type) *structSpec { - if v, ok := s.m.Load(t); ok { - return v.(*structSpec) - } - - spec := newStructSpec(t, "redis") - s.m.Store(t, spec) - return spec -} - -var ( - decoders = []decoderFunc{ - reflect.Bool: decodeBool, - reflect.Int: decodeInt, - reflect.Int8: decodeInt8, - reflect.Int16: decodeInt16, - reflect.Int32: decodeInt32, - reflect.Int64: decodeInt64, - reflect.Uint: decodeUint, - reflect.Uint8: decodeUint8, - reflect.Uint16: decodeUint16, - reflect.Uint32: decodeUint32, - reflect.Uint64: decodeUint64, - reflect.Float32: decodeFloat32, - reflect.Float64: decodeFloat64, - reflect.Complex64: decodeUnsupported, - reflect.Complex128: decodeUnsupported, - reflect.Array: decodeUnsupported, - reflect.Chan: decodeUnsupported, - reflect.Func: decodeUnsupported, - reflect.Interface: decodeUnsupported, - reflect.Map: decodeUnsupported, - reflect.Ptr: decodeUnsupported, - reflect.Slice: decodeSlice, - reflect.String: decodeString, - reflect.Struct: decodeUnsupported, - reflect.UnsafePointer: decodeUnsupported, - } - globalStructMap = newStructMap() -) - -func decodeBool(f reflect.Value, s string) error { - b, err := strconv.ParseBool(s) - if err != nil { - return err - } - f.SetBool(b) - return nil -} - -func decodeInt8(f reflect.Value, s string) error { - return decodeNumber(f, s, 8) -} - -func decodeInt16(f reflect.Value, s string) error { - return decodeNumber(f, s, 16) -} - -func decodeInt32(f reflect.Value, s string) error { - return decodeNumber(f, s, 32) -} - -func decodeInt64(f reflect.Value, s string) error { - return decodeNumber(f, s, 64) -} - -func decodeInt(f reflect.Value, s string) error { - return decodeNumber(f, s, 0) -} - -func decodeNumber(f reflect.Value, s string, bitSize int) error { - v, err := strconv.ParseInt(s, 10, bitSize) - if err != nil { - return err - } - f.SetInt(v) - return nil -} - -func decodeUint8(f reflect.Value, s string) error { - return decodeUnsignedNumber(f, s, 8) -} - -func decodeUint16(f reflect.Value, s string) error { - return decodeUnsignedNumber(f, s, 16) -} - -func decodeUint32(f reflect.Value, s string) error { - return decodeUnsignedNumber(f, s, 32) -} - -func decodeUint64(f reflect.Value, s string) error { - return decodeUnsignedNumber(f, s, 64) -} - -func decodeUint(f reflect.Value, s string) error { - return decodeUnsignedNumber(f, s, 0) -} - -func decodeUnsignedNumber(f reflect.Value, s string, bitSize int) error { - v, err := strconv.ParseUint(s, 10, bitSize) - if err != nil { - return err - } - f.SetUint(v) - return nil -} - -func decodeFloat32(f reflect.Value, s string) error { - v, err := strconv.ParseFloat(s, 32) - if err != nil { - return err - } - f.SetFloat(v) - return nil -} - -// although the default is float64, but we better define it. -func decodeFloat64(f reflect.Value, s string) error { - v, err := strconv.ParseFloat(s, 64) - if err != nil { - return err - } - f.SetFloat(v) - return nil -} - -func decodeString(f reflect.Value, s string) error { - f.SetString(s) - return nil -} - -func decodeSlice(f reflect.Value, s string) error { - // []byte slice ([]uint8). - if f.Type().Elem().Kind() == reflect.Uint8 { - f.SetBytes([]byte(s)) - } - return nil -} - -func decodeUnsupported(v reflect.Value, s string) error { - return fmt.Errorf("redis.Scan(unsupported %s)", v.Type()) -} - -func newStructSpec(t reflect.Type, fieldTag string) *structSpec { - numField := t.NumField() - out := &structSpec{ - m: make(map[string]*structField, numField), - } - - for i := 0; i < numField; i++ { - f := t.Field(i) - - tag := f.Tag.Get(fieldTag) - if tag == "" || tag == "-" { - continue - } - - tag = strings.Split(tag, ",")[0] - if tag == "" { - continue - } - - // Use the built-in decoder. - kind := f.Type.Kind() - if kind == reflect.Pointer { - kind = f.Type.Elem().Kind() - } - out.set(tag, &structField{index: i, fn: decoders[kind]}) - } - - return out -} - -func Struct(dst interface{}) (StructValue, error) { - v := reflect.ValueOf(dst) - - // The destination to scan into should be a struct pointer. - if v.Kind() != reflect.Ptr || v.IsNil() { - return StructValue{}, fmt.Errorf("redis.Scan(non-pointer %T)", dst) - } - - v = v.Elem() - if v.Kind() != reflect.Struct { - return StructValue{}, fmt.Errorf("redis.Scan(non-struct %T)", dst) - } - - return StructValue{ - spec: globalStructMap.get(v.Type()), - value: v, - }, nil -} - -func HScan(dst interface{}, keys []interface{}, vals []interface{}) error { - if len(keys) != len(vals) { - return errors.New("args should have the same number of keys and vals") - } - - strct, err := Struct(dst) - if err != nil { - return err - } - - // Iterate through the (key, value) sequence. - for i := 0; i < len(vals); i++ { - key, ok := keys[i].(string) - if !ok { - continue - } - - val, ok := vals[i].(string) - if !ok { - continue - } - - if err := strct.Scan(key, val); err != nil { - return err - } - } - - return nil -} - -func cmdString(cmd redis.Cmder, val interface{}) string { - b := make([]byte, 0, 64) - - for i, arg := range cmd.Args() { - if i > 0 { - b = append(b, ' ') - } - b = AppendArg(b, arg) - } - - if err := cmd.Err(); err != nil { - b = append(b, ": "...) - b = append(b, err.Error()...) - } else if val != nil { - b = append(b, ": "...) - b = AppendArg(b, val) - } - - return BytesToString(b) -} - -func AppendArg(b []byte, v interface{}) []byte { - switch v := v.(type) { - case nil: - return append(b, ""...) - case string: - return appendUTF8String(b, StringToBytes(v)) - case []byte: - return appendUTF8String(b, v) - case int: - return strconv.AppendInt(b, int64(v), 10) - case int8: - return strconv.AppendInt(b, int64(v), 10) - case int16: - return strconv.AppendInt(b, int64(v), 10) - case int32: - return strconv.AppendInt(b, int64(v), 10) - case int64: - return strconv.AppendInt(b, v, 10) - case uint: - return strconv.AppendUint(b, uint64(v), 10) - case uint8: - return strconv.AppendUint(b, uint64(v), 10) - case uint16: - return strconv.AppendUint(b, uint64(v), 10) - case uint32: - return strconv.AppendUint(b, uint64(v), 10) - case uint64: - return strconv.AppendUint(b, v, 10) - case float32: - return strconv.AppendFloat(b, float64(v), 'f', -1, 64) - case float64: - return strconv.AppendFloat(b, v, 'f', -1, 64) - case bool: - if v { - return append(b, "true"...) - } - return append(b, "false"...) - case time.Time: - return v.AppendFormat(b, time.RFC3339Nano) - default: - return append(b, fmt.Sprint(v)...) - } -} - -func appendUTF8String(dst []byte, src []byte) []byte { - dst = append(dst, src...) - return dst -} - -func BytesToString(b []byte) string { - return *(*string)(unsafe.Pointer(&b)) -} - -func ScanSlice(data []string, slice interface{}) error { - v := reflect.ValueOf(slice) - if !v.IsValid() { - return fmt.Errorf("redis: ScanSlice(nil)") - } - if v.Kind() != reflect.Ptr { - return fmt.Errorf("redis: ScanSlice(non-pointer %T)", slice) - } - v = v.Elem() - if v.Kind() != reflect.Slice { - return fmt.Errorf("redis: ScanSlice(non-slice %T)", slice) - } - - next := makeSliceNextElemFunc(v) - for i, s := range data { - elem := next() - if err := Scan([]byte(s), elem.Addr().Interface()); err != nil { - err = fmt.Errorf("redis: ScanSlice index=%d value=%q failed: %w", i, s, err) - return err - } - } - - return nil -} - -func Scan(b []byte, v interface{}) error { - switch v := v.(type) { - case nil: - return fmt.Errorf("redis: Scan(nil)") - case *string: - *v = BytesToString(b) - return nil - case *[]byte: - *v = b - return nil - case *int: - var err error - *v, err = strconv.Atoi(BytesToString(b)) - return err - case *int8: - n, err := strconv.ParseInt(BytesToString(b), 10, 8) - if err != nil { - return err - } - *v = int8(n) - return nil - case *int16: - n, err := strconv.ParseInt(BytesToString(b), 10, 16) - if err != nil { - return err - } - *v = int16(n) - return nil - case *int32: - n, err := strconv.ParseInt(BytesToString(b), 10, 32) - if err != nil { - return err - } - *v = int32(n) - return nil - case *int64: - n, err := strconv.ParseInt(BytesToString(b), 10, 64) - if err != nil { - return err - } - *v = n - return nil - case *uint: - n, err := strconv.ParseUint(BytesToString(b), 10, 64) - if err != nil { - return err - } - *v = uint(n) - return nil - case *uint8: - n, err := strconv.ParseUint(BytesToString(b), 10, 8) - if err != nil { - return err - } - *v = uint8(n) - return nil - case *uint16: - n, err := strconv.ParseUint(BytesToString(b), 10, 16) - if err != nil { - return err - } - *v = uint16(n) - return nil - case *uint32: - n, err := strconv.ParseUint(BytesToString(b), 10, 32) - if err != nil { - return err - } - *v = uint32(n) - return nil - case *uint64: - n, err := strconv.ParseUint(BytesToString(b), 10, 64) - if err != nil { - return err - } - *v = n - return nil - case *float32: - n, err := strconv.ParseFloat(BytesToString(b), 32) - if err != nil { - return err - } - *v = float32(n) - return err - case *float64: - var err error - *v, err = strconv.ParseFloat(BytesToString(b), 64) - return err - case *bool: - *v = len(b) == 1 && b[0] == '1' - return nil - case *time.Time: - var err error - *v, err = time.Parse(time.RFC3339Nano, BytesToString(b)) - return err - case *time.Duration: - n, err := strconv.ParseInt(BytesToString(b), 10, 64) - if err != nil { - return err - } - *v = time.Duration(n) - return nil - case encoding.BinaryUnmarshaler: - return v.UnmarshalBinary(b) - case *net.IP: - *v = b - return nil - default: - return fmt.Errorf( - "redis: can't unmarshal %T (consider implementing BinaryUnmarshaler)", v) - } -} - -func makeSliceNextElemFunc(v reflect.Value) func() reflect.Value { - elemType := v.Type().Elem() - - if elemType.Kind() == reflect.Ptr { - elemType = elemType.Elem() - return func() reflect.Value { - if v.Len() < v.Cap() { - v.Set(v.Slice(0, v.Len()+1)) - elem := v.Index(v.Len() - 1) - if elem.IsNil() { - elem.Set(reflect.New(elemType)) - } - return elem.Elem() - } - - elem := reflect.New(elemType) - v.Set(reflect.Append(v, elem)) - return elem.Elem() - } - } - - zero := reflect.Zero(elemType) - return func() reflect.Value { - if v.Len() < v.Cap() { - v.Set(v.Slice(0, v.Len()+1)) - return v.Index(v.Len() - 1) - } - - v.Set(reflect.Append(v, zero)) - return v.Index(v.Len() - 1) - } -} -func StringToBytes(s string) []byte { - return *(*[]byte)(unsafe.Pointer( - &struct { - string - Cap int - }{s, len(s)}, - )) + ExpectFT_List() *ExpectedStringSliceCmd + ExpectFTAggregate(index string, query string) *ExpectedMapStringInterfaceCmd + ExpectFTAggregateWithArgs(index string, query string, options *redis.FTAggregateOptions) *ExpectedAggregateCmd + ExpectFTAliasAdd(index string, alias string) *ExpectedStatusCmd + ExpectFTAliasDel(alias string) *ExpectedStatusCmd + ExpectFTAliasUpdate(index string, alias string) *ExpectedStatusCmd + ExpectFTAlter(index string, skipInitialScan bool, definition []interface{}) *ExpectedStatusCmd + ExpectFTConfigGet(option string) *ExpectedMapMapStringInterfaceCmd + ExpectFTConfigSet(option string, value interface{}) *ExpectedStatusCmd + ExpectFTCreate(index string, options *redis.FTCreateOptions, schema ...*redis.FieldSchema) *ExpectedStatusCmd + ExpectFTCursorDel(index string, cursorId int) *ExpectedStatusCmd + ExpectFTCursorRead(index string, cursorId int, count int) *ExpectedMapStringInterfaceCmd + ExpectFTDictAdd(dict string, term ...interface{}) *ExpectedIntCmd + ExpectFTDictDel(dict string, term ...interface{}) *ExpectedIntCmd + ExpectFTDictDump(dict string) *ExpectedStringSliceCmd + ExpectFTDropIndex(index string) *ExpectedStatusCmd + ExpectFTDropIndexWithArgs(index string, options *redis.FTDropIndexOptions) *ExpectedStatusCmd + ExpectFTExplain(index string, query string) *ExpectedStringCmd + ExpectFTExplainWithArgs(index string, query string, options *redis.FTExplainOptions) *ExpectedStringCmd + ExpectFTInfo(index string) *ExpectedFTInfoCmd + ExpectFTSpellCheck(index string, query string) *ExpectedFTSpellCheckCmd + ExpectFTSpellCheckWithArgs(index string, query string, options *redis.FTSpellCheckOptions) *ExpectedFTSpellCheckCmd + ExpectFTSearch(index string, query string) *ExpectedFTSearchCmd + ExpectFTSearchWithArgs(index string, query string, options *redis.FTSearchOptions) *ExpectedFTSearchCmd + ExpectFTSynDump(index string) *ExpectedFTSynDumpCmd + ExpectFTSynUpdate(index string, synGroupId interface{}, terms []interface{}) *ExpectedStatusCmd + ExpectFTSynUpdateWithArgs(index string, synGroupId interface{}, options *redis.FTSynUpdateOptions, terms []interface{}) *ExpectedStatusCmd + ExpectFTTagVals(index string, field string) *ExpectedStringSliceCmd } type pipelineMock interface { @@ -1309,6 +521,7 @@ type expectedBase struct { setVal bool regexpMatch bool customMatch CustomMatch + rawVal interface{} rw sync.RWMutex } diff --git a/expect_ft.go b/expect_ft.go new file mode 100644 index 0000000..08a4835 --- /dev/null +++ b/expect_ft.go @@ -0,0 +1,301 @@ +package redismock + +import ( + "strconv" + "time" + + "github.com/redis/go-redis/v9" +) + +type ExpectedFTSearchCmd struct { + expectedBase + + val redis.FTSearchResult +} + +func (cmd *ExpectedFTSearchCmd) inflow(c redis.Cmder) { + inflow(c, "val", cmd.val) +} + +func (cmd *ExpectedFTSearchCmd) String() string { + return cmdString(cmd.cmd, cmd.val) +} + +func (cmd *ExpectedFTSearchCmd) SetVal(val redis.FTSearchResult) { + cmd.setVal = true + cmd.val = val +} + +func (cmd *ExpectedFTSearchCmd) Result() (redis.FTSearchResult, error) { + return cmd.val, cmd.err +} + +func (cmd *ExpectedFTSearchCmd) Val() redis.FTSearchResult { + return cmd.val +} + +func (cmd *ExpectedFTSearchCmd) RawVal() interface{} { + return cmd.rawVal +} + +func (cmd *ExpectedFTSearchCmd) RawResult() (interface{}, error) { + return cmd.rawVal, cmd.err +} + +type ExpectedFTSpellCheckCmd struct { + expectedBase + + val []redis.SpellCheckResult +} + +func (cmd *ExpectedFTSpellCheckCmd) inflow(c redis.Cmder) { + inflow(c, "val", cmd.val) +} + +func (cmd *ExpectedFTSpellCheckCmd) String() string { + return cmdString(cmd.cmd, cmd.val) +} + +func (cmd *ExpectedFTSpellCheckCmd) SetVal(val []redis.SpellCheckResult) { + cmd.setVal = true + cmd.val = val +} + +func (cmd *ExpectedFTSpellCheckCmd) Result() ([]redis.SpellCheckResult, error) { + return cmd.val, cmd.err +} + +func (cmd *ExpectedFTSpellCheckCmd) Val() []redis.SpellCheckResult { + return cmd.val +} + +func (cmd *ExpectedFTSpellCheckCmd) RawVal() interface{} { + return cmd.rawVal +} + +func (cmd *ExpectedFTSpellCheckCmd) RawResult() (interface{}, error) { + return cmd.rawVal, cmd.err +} + +type ExpectedMapStringInterfaceCmd struct { + expectedBase + + val map[string]interface{} +} + +func (cmd *ExpectedMapStringInterfaceCmd) inflow(c redis.Cmder) { + inflow(c, "val", cmd.val) +} + +func (cmd *ExpectedMapStringInterfaceCmd) SetVal(val map[string]interface{}) { + cmd.setVal = true + cmd.val = val +} + +func (cmd *ExpectedMapStringInterfaceCmd) Val() map[string]interface{} { + return cmd.val +} + +func (cmd *ExpectedMapStringInterfaceCmd) Result() (map[string]interface{}, error) { + return cmd.val, cmd.err +} + +func (cmd *ExpectedMapStringInterfaceCmd) String() string { + return cmdString(cmd.cmd, cmd.val) +} + +type ExpectedStringCmd struct { + expectedBase + + val string +} + +func (cmd *ExpectedStringCmd) inflow(c redis.Cmder) { + inflow(c, "val", cmd.val) +} + +func (cmd *ExpectedStringCmd) SetVal(val string) { + cmd.setVal = true + cmd.val = val +} + +func (cmd *ExpectedStringCmd) Val() string { + return cmd.val +} + +func (cmd *ExpectedStringCmd) Result() (string, error) { + return cmd.val, cmd.err +} + +func (cmd *ExpectedStringCmd) Bytes() ([]byte, error) { + return StringToBytes(cmd.val), cmd.err +} + +func (cmd *ExpectedStringCmd) Bool() (bool, error) { + if cmd.err != nil { + return false, cmd.err + } + return strconv.ParseBool(cmd.val) +} + +func (cmd *ExpectedStringCmd) Int() (int, error) { + if cmd.err != nil { + return 0, cmd.err + } + return strconv.Atoi(cmd.Val()) +} + +func (cmd *ExpectedStringCmd) Int64() (int64, error) { + if cmd.err != nil { + return 0, cmd.err + } + return strconv.ParseInt(cmd.Val(), 10, 64) +} + +func (cmd *ExpectedStringCmd) Uint64() (uint64, error) { + if cmd.err != nil { + return 0, cmd.err + } + return strconv.ParseUint(cmd.Val(), 10, 64) +} + +func (cmd *ExpectedStringCmd) Float32() (float32, error) { + if cmd.err != nil { + return 0, cmd.err + } + f, err := strconv.ParseFloat(cmd.Val(), 32) + if err != nil { + return 0, err + } + return float32(f), nil +} + +func (cmd *ExpectedStringCmd) Float64() (float64, error) { + if cmd.err != nil { + return 0, cmd.err + } + return strconv.ParseFloat(cmd.Val(), 64) +} + +func (cmd *ExpectedStringCmd) Time() (time.Time, error) { + if cmd.err != nil { + return time.Time{}, cmd.err + } + return time.Parse(time.RFC3339Nano, cmd.Val()) +} + +func (cmd *ExpectedStringCmd) Scan(val interface{}) error { + if cmd.err != nil { + return cmd.err + } + return Scan([]byte(cmd.val), val) +} + +func (cmd *ExpectedStringCmd) String() string { + return cmdString(cmd.cmd, cmd.val) +} + +type ExpectedAggregateCmd struct { + expectedBase + + val *redis.FTAggregateResult +} + +func (cmd *ExpectedAggregateCmd) inflow(c redis.Cmder) { + inflow(c, "val", cmd.val) +} + +func (cmd *ExpectedAggregateCmd) SetVal(val *redis.FTAggregateResult) { + cmd.setVal = true + cmd.val = val +} + +func (cmd *ExpectedAggregateCmd) Val() *redis.FTAggregateResult { + return cmd.val +} + +func (cmd *ExpectedAggregateCmd) Result() (*redis.FTAggregateResult, error) { + return cmd.val, cmd.err +} + +func (cmd *ExpectedAggregateCmd) RawVal() interface{} { + return cmd.rawVal +} + +func (cmd *ExpectedAggregateCmd) RawResult() (interface{}, error) { + return cmd.rawVal, cmd.err +} + +func (cmd *ExpectedAggregateCmd) String() string { + return cmdString(cmd.cmd, cmd.val) +} + +type ExpectedFTInfoCmd struct { + expectedBase + + val redis.FTInfoResult +} + +func (cmd *ExpectedFTInfoCmd) inflow(c redis.Cmder) { + inflow(c, "val", cmd.val) +} + +func (cmd *ExpectedFTInfoCmd) String() string { + return cmdString(cmd.cmd, cmd.val) +} + +func (cmd *ExpectedFTInfoCmd) SetVal(val redis.FTInfoResult) { + cmd.setVal = true + cmd.val = val +} + +func (cmd *ExpectedFTInfoCmd) Result() (redis.FTInfoResult, error) { + return cmd.val, cmd.err +} + +func (cmd *ExpectedFTInfoCmd) Val() redis.FTInfoResult { + return cmd.val +} + +func (cmd *ExpectedFTInfoCmd) RawVal() interface{} { + return cmd.rawVal +} + +func (cmd *ExpectedFTInfoCmd) RawResult() (interface{}, error) { + return cmd.rawVal, cmd.err +} + +type ExpectedFTSynDumpCmd struct { + expectedBase + + val []redis.FTSynDumpResult +} + +func (cmd *ExpectedFTSynDumpCmd) inflow(c redis.Cmder) { + inflow(c, "val", cmd.val) +} + +func (cmd *ExpectedFTSynDumpCmd) String() string { + return cmdString(cmd.cmd, cmd.val) +} + +func (cmd *ExpectedFTSynDumpCmd) SetVal(val []redis.FTSynDumpResult) { + cmd.setVal = true + cmd.val = val +} + +func (cmd *ExpectedFTSynDumpCmd) Val() []redis.FTSynDumpResult { + return cmd.val +} + +func (cmd *ExpectedFTSynDumpCmd) Result() ([]redis.FTSynDumpResult, error) { + return cmd.val, cmd.err +} + +func (cmd *ExpectedFTSynDumpCmd) RawVal() interface{} { + return cmd.rawVal +} + +func (cmd *ExpectedFTSynDumpCmd) RawResult() (interface{}, error) { + return cmd.rawVal, cmd.err +} diff --git a/expect_json.go b/expect_json.go new file mode 100644 index 0000000..189fece --- /dev/null +++ b/expect_json.go @@ -0,0 +1,382 @@ +package redismock + +import ( + "encoding" + "encoding/json" + "fmt" + "reflect" + "sync" + + "github.com/redis/go-redis/v9" +) + +type ExpectedMapMapStringInterfaceCmd struct { + expectedBase + + val map[string]interface{} +} + +func (cmd *ExpectedMapMapStringInterfaceCmd) inflow(c redis.Cmder) { + inflow(c, "val", cmd.val) +} + +func (cmd *ExpectedMapMapStringInterfaceCmd) String() string { + return cmdString(cmd.cmd, cmd.val) +} + +func (cmd *ExpectedMapMapStringInterfaceCmd) SetVal(val map[string]interface{}) { + cmd.setVal = true + cmd.val = val +} + +func (cmd *ExpectedMapMapStringInterfaceCmd) Result() (map[string]interface{}, error) { + return cmd.val, cmd.err +} + +func (cmd *ExpectedMapMapStringInterfaceCmd) Val() map[string]interface{} { + return cmd.val +} + +type ExpectedIntSliceCmd struct { + expectedBase + + val []int64 +} + +func (cmd *ExpectedIntSliceCmd) SetVal(val []int64) { + cmd.setVal = true + cmd.val = make([]int64, len(val)) + copy(cmd.val, val) +} + +func (cmd *ExpectedIntSliceCmd) inflow(c redis.Cmder) { + inflow(c, "val", cmd.val) +} + +func (cmd *ExpectedIntSliceCmd) Result() ([]int64, error) { + return cmd.val, cmd.err +} + +func (cmd *ExpectedIntSliceCmd) String() string { + return cmdString(cmd.cmd, cmd.val) +} + +func (cmd *ExpectedIntSliceCmd) Val() []int64 { + return cmd.val +} + +type ExpectedStatusCmd struct { + expectedBase + + val string +} + +func (cmd *ExpectedStatusCmd) inflow(c redis.Cmder) { + inflow(c, "val", cmd.val) +} + +func (cmd *ExpectedStatusCmd) SetVal(val string) { + cmd.val = val +} + +func (cmd *ExpectedStatusCmd) Val() string { + return cmd.val +} + +func (cmd *ExpectedStatusCmd) Result() (string, error) { + return cmd.val, cmd.err +} + +func (cmd *ExpectedStatusCmd) Bytes() ([]byte, error) { + return StringToBytes(cmd.val), cmd.err +} + +func (cmd *ExpectedStatusCmd) String() string { + return cmdString(cmd.cmd, cmd.val) +} + +type ExpectedIntCmd struct { + expectedBase + + val int64 +} + +func (cmd *ExpectedIntCmd) inflow(c redis.Cmder) { + inflow(c, "val", cmd.val) +} + +func (cmd *ExpectedIntCmd) SetVal(val int64) { + cmd.val = val +} + +func (cmd *ExpectedIntCmd) Val() int64 { + return cmd.val +} + +func (cmd *ExpectedIntCmd) Result() (int64, error) { + return cmd.val, cmd.err +} + +func (cmd *ExpectedIntCmd) Uint64() (uint64, error) { + return uint64(cmd.val), cmd.err +} + +func (cmd *ExpectedIntCmd) String() string { + return cmdString(cmd.cmd, cmd.val) +} + +type ExpectedIntPointerSliceCmd struct { + expectedBase + + val []*int64 +} + +func (cmd *ExpectedIntPointerSliceCmd) inflow(c redis.Cmder) { + inflow(c, "val", cmd.val) +} + +func (cmd *ExpectedIntPointerSliceCmd) String() string { + return cmdString(cmd.cmd, cmd.val) +} + +func (cmd *ExpectedIntPointerSliceCmd) SetVal(val []*int64) { + cmd.val = val +} + +func (cmd *ExpectedIntPointerSliceCmd) Val() []*int64 { + return cmd.val +} + +func (cmd *ExpectedIntPointerSliceCmd) Result() ([]*int64, error) { + return cmd.val, cmd.err +} + +type ExpectedJSONCmd struct { + expectedBase + + val string + expanded interface{} +} + +func (cmd *ExpectedJSONCmd) inflow(c redis.Cmder) { + inflow(c, "val", cmd.val) +} + +func (cmd *ExpectedJSONCmd) String() string { + return cmdString(cmd.cmd, cmd.val) +} + +func (cmd *ExpectedJSONCmd) SetVal(val string) { + cmd.val = val +} + +func (cmd *ExpectedJSONCmd) Val() string { + if len(cmd.val) == 0 && cmd.expanded != nil { + val, err := json.Marshal(cmd.expanded) + if err != nil { + cmd.SetErr(err) + return "" + } + return string(val) + + } else { + return cmd.val + } +} + +func (cmd *ExpectedJSONCmd) Result() (string, error) { + return cmd.Val(), cmd.cmd.Err() +} + +func (cmd *ExpectedJSONCmd) Expanded() (interface{}, error) { + if len(cmd.val) != 0 && cmd.expanded == nil { + err := json.Unmarshal([]byte(cmd.val), &cmd.expanded) + if err != nil { + return nil, err + } + } + + return cmd.expanded, nil +} + +type ExpectedJSONSliceCmd struct { + expectedBase + + val []interface{} +} + +func (cmd *ExpectedJSONSliceCmd) inflow(c redis.Cmder) { + inflow(c, "val", cmd.val) +} + +func (cmd *ExpectedJSONSliceCmd) String() string { + return cmdString(cmd.cmd, cmd.val) +} + +func (cmd *ExpectedJSONSliceCmd) SetVal(val []interface{}) { + cmd.val = val +} + +func (cmd *ExpectedJSONSliceCmd) Val() []interface{} { + return cmd.val +} + +func (cmd *ExpectedJSONSliceCmd) Result() ([]interface{}, error) { + return cmd.val, cmd.err +} + +type ExpectedStringSliceCmd struct { + expectedBase + + val []string +} + +func (cmd *ExpectedStringSliceCmd) inflow(c redis.Cmder) { + inflow(c, "val", cmd.val) +} + +func (cmd *ExpectedStringSliceCmd) SetVal(val []string) { + cmd.val = val +} + +func (cmd *ExpectedStringSliceCmd) Val() []string { + return cmd.val +} + +func (cmd *ExpectedStringSliceCmd) Result() ([]string, error) { + return cmd.val, cmd.err +} + +func (cmd *ExpectedStringSliceCmd) String() string { + return cmdString(cmd.cmd, cmd.val) +} + +func (cmd *ExpectedStringSliceCmd) ScanSlice(container interface{}) error { + return ScanSlice(cmd.Val(), container) +} + +type ExpectedSliceCmd struct { + expectedBase + + val []interface{} +} + +func (cmd *ExpectedSliceCmd) inflow(c redis.Cmder) { + inflow(c, "val", cmd.val) +} + +func (cmd *ExpectedSliceCmd) SetVal(val []interface{}) { + cmd.val = val +} + +func (cmd *ExpectedSliceCmd) Val() []interface{} { + return cmd.val +} + +func (cmd *ExpectedSliceCmd) Result() ([]interface{}, error) { + return cmd.val, cmd.err +} + +func (cmd *ExpectedSliceCmd) String() string { + return cmdString(cmd.cmd, cmd.val) +} + +// Scan scans the results from the map into a destination struct. The map keys +// are matched in the Redis struct fields by the `redis:"field"` tag. +func (cmd *ExpectedSliceCmd) Scan(dst interface{}) error { + if cmd.err != nil { + return cmd.err + } + + // Pass the list of keys and values. + // Skip the first two args for: HMGET key + var args []interface{} + if cmd.args()[0] == "hmget" { + args = cmd.args()[2:] + } else { + // Otherwise, it's: MGET field field ... + args = cmd.args()[1:] + } + + return HScan(dst, args, cmd.val) +} + +type decoderFunc func(reflect.Value, string) error + +type structField struct { + index int + fn decoderFunc +} + +type structSpec struct { + m map[string]*structField +} + +func (s *structSpec) set(tag string, sf *structField) { + s.m[tag] = sf +} + +type StructValue struct { + spec *structSpec + value reflect.Value +} + +type Scanner interface { + ScanRedis(s string) error +} + +func (s StructValue) Scan(key string, value string) error { + field, ok := s.spec.m[key] + if !ok { + return nil + } + + v := s.value.Field(field.index) + isPtr := v.Kind() == reflect.Ptr + + if isPtr && v.IsNil() { + v.Set(reflect.New(v.Type().Elem())) + } + if !isPtr && v.Type().Name() != "" && v.CanAddr() { + v = v.Addr() + isPtr = true + } + + if isPtr && v.Type().NumMethod() > 0 && v.CanInterface() { + switch scan := v.Interface().(type) { + case Scanner: + return scan.ScanRedis(value) + case encoding.TextUnmarshaler: + return scan.UnmarshalText(StringToBytes(value)) + } + } + + if isPtr { + v = v.Elem() + } + + if err := field.fn(v, value); err != nil { + t := s.value.Type() + return fmt.Errorf("cannot scan redis.result %s into struct field %s.%s of type %s, error-%s", + value, t.Name(), t.Field(field.index).Name, t.Field(field.index).Type, err.Error()) + } + return nil +} + +type structMap struct { + m sync.Map +} + +func newStructMap() *structMap { + return new(structMap) +} + +func (s *structMap) get(t reflect.Type) *structSpec { + if v, ok := s.m.Load(t); ok { + return v.(*structSpec) + } + + spec := newStructSpec(t, "redis") + s.m.Store(t, spec) + return spec +} diff --git a/go.mod b/go.mod index d55b2f0..defa770 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.18 require ( github.com/onsi/ginkgo v1.16.5 github.com/onsi/gomega v1.25.0 - github.com/redis/go-redis/v9 v9.3.0 + github.com/redis/go-redis/v9 v9.7.0 ) require ( @@ -20,5 +20,3 @@ require ( gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) - -replace github.com/go-redis/redismock/v9 => github.com/mrrsm/redismock/v9 v9.2.0 diff --git a/go.sum b/go.sum index 877cb0e..3025949 100644 --- a/go.sum +++ b/go.sum @@ -38,8 +38,8 @@ github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1y github.com/onsi/gomega v1.25.0 h1:Vw7br2PCDYijJHSfBOWhov+8cAnUf8MfMaIOV323l6Y= github.com/onsi/gomega v1.25.0/go.mod h1:r+zV744Re+DiYCIPRlYOTxn0YkOLcAnW8k1xXdMPGhM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/redis/go-redis/v9 v9.3.0 h1:RiVDjmig62jIWp7Kk4XVLs0hzV6pI3PyTnnL0cnn0u0= -github.com/redis/go-redis/v9 v9.3.0/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= +github.com/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa9E= +github.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= diff --git a/helper.go b/helper.go new file mode 100644 index 0000000..3d194b7 --- /dev/null +++ b/helper.go @@ -0,0 +1,482 @@ +package redismock + +import ( + "encoding" + "errors" + "fmt" + "net" + "reflect" + "strconv" + "strings" + "time" + "unsafe" + + "github.com/redis/go-redis/v9" +) + +var ( + decoders = []decoderFunc{ + reflect.Bool: decodeBool, + reflect.Int: decodeInt, + reflect.Int8: decodeInt8, + reflect.Int16: decodeInt16, + reflect.Int32: decodeInt32, + reflect.Int64: decodeInt64, + reflect.Uint: decodeUint, + reflect.Uint8: decodeUint8, + reflect.Uint16: decodeUint16, + reflect.Uint32: decodeUint32, + reflect.Uint64: decodeUint64, + reflect.Float32: decodeFloat32, + reflect.Float64: decodeFloat64, + reflect.Complex64: decodeUnsupported, + reflect.Complex128: decodeUnsupported, + reflect.Array: decodeUnsupported, + reflect.Chan: decodeUnsupported, + reflect.Func: decodeUnsupported, + reflect.Interface: decodeUnsupported, + reflect.Map: decodeUnsupported, + reflect.Ptr: decodeUnsupported, + reflect.Slice: decodeSlice, + reflect.String: decodeString, + reflect.Struct: decodeUnsupported, + reflect.UnsafePointer: decodeUnsupported, + } + globalStructMap = newStructMap() +) + +func decodeBool(f reflect.Value, s string) error { + b, err := strconv.ParseBool(s) + if err != nil { + return err + } + f.SetBool(b) + return nil +} + +func decodeInt8(f reflect.Value, s string) error { + return decodeNumber(f, s, 8) +} + +func decodeInt16(f reflect.Value, s string) error { + return decodeNumber(f, s, 16) +} + +func decodeInt32(f reflect.Value, s string) error { + return decodeNumber(f, s, 32) +} + +func decodeInt64(f reflect.Value, s string) error { + return decodeNumber(f, s, 64) +} + +func decodeInt(f reflect.Value, s string) error { + return decodeNumber(f, s, 0) +} + +func decodeNumber(f reflect.Value, s string, bitSize int) error { + v, err := strconv.ParseInt(s, 10, bitSize) + if err != nil { + return err + } + f.SetInt(v) + return nil +} + +func decodeUint8(f reflect.Value, s string) error { + return decodeUnsignedNumber(f, s, 8) +} + +func decodeUint16(f reflect.Value, s string) error { + return decodeUnsignedNumber(f, s, 16) +} + +func decodeUint32(f reflect.Value, s string) error { + return decodeUnsignedNumber(f, s, 32) +} + +func decodeUint64(f reflect.Value, s string) error { + return decodeUnsignedNumber(f, s, 64) +} + +func decodeUint(f reflect.Value, s string) error { + return decodeUnsignedNumber(f, s, 0) +} + +func decodeUnsignedNumber(f reflect.Value, s string, bitSize int) error { + v, err := strconv.ParseUint(s, 10, bitSize) + if err != nil { + return err + } + f.SetUint(v) + return nil +} + +func decodeFloat32(f reflect.Value, s string) error { + v, err := strconv.ParseFloat(s, 32) + if err != nil { + return err + } + f.SetFloat(v) + return nil +} + +// although the default is float64, but we better define it. +func decodeFloat64(f reflect.Value, s string) error { + v, err := strconv.ParseFloat(s, 64) + if err != nil { + return err + } + f.SetFloat(v) + return nil +} + +func decodeString(f reflect.Value, s string) error { + f.SetString(s) + return nil +} + +func decodeSlice(f reflect.Value, s string) error { + // []byte slice ([]uint8). + if f.Type().Elem().Kind() == reflect.Uint8 { + f.SetBytes([]byte(s)) + } + return nil +} + +func decodeUnsupported(v reflect.Value, s string) error { + return fmt.Errorf("redis.Scan(unsupported %s)", v.Type()) +} + +func newStructSpec(t reflect.Type, fieldTag string) *structSpec { + numField := t.NumField() + out := &structSpec{ + m: make(map[string]*structField, numField), + } + + for i := 0; i < numField; i++ { + f := t.Field(i) + + tag := f.Tag.Get(fieldTag) + if tag == "" || tag == "-" { + continue + } + + tag = strings.Split(tag, ",")[0] + if tag == "" { + continue + } + + // Use the built-in decoder. + kind := f.Type.Kind() + if kind == reflect.Pointer { + kind = f.Type.Elem().Kind() + } + out.set(tag, &structField{index: i, fn: decoders[kind]}) + } + + return out +} + +func Struct(dst interface{}) (StructValue, error) { + v := reflect.ValueOf(dst) + + // The destination to scan into should be a struct pointer. + if v.Kind() != reflect.Ptr || v.IsNil() { + return StructValue{}, fmt.Errorf("redis.Scan(non-pointer %T)", dst) + } + + v = v.Elem() + if v.Kind() != reflect.Struct { + return StructValue{}, fmt.Errorf("redis.Scan(non-struct %T)", dst) + } + + return StructValue{ + spec: globalStructMap.get(v.Type()), + value: v, + }, nil +} + +func HScan(dst interface{}, keys []interface{}, vals []interface{}) error { + if len(keys) != len(vals) { + return errors.New("args should have the same number of keys and vals") + } + + strct, err := Struct(dst) + if err != nil { + return err + } + + // Iterate through the (key, value) sequence. + for i := 0; i < len(vals); i++ { + key, ok := keys[i].(string) + if !ok { + continue + } + + val, ok := vals[i].(string) + if !ok { + continue + } + + if err := strct.Scan(key, val); err != nil { + return err + } + } + + return nil +} + +func cmdString(cmd redis.Cmder, val interface{}) string { + b := make([]byte, 0, 64) + + for i, arg := range cmd.Args() { + if i > 0 { + b = append(b, ' ') + } + b = AppendArg(b, arg) + } + + if err := cmd.Err(); err != nil { + b = append(b, ": "...) + b = append(b, err.Error()...) + } else if val != nil { + b = append(b, ": "...) + b = AppendArg(b, val) + } + + return BytesToString(b) +} + +func AppendArg(b []byte, v interface{}) []byte { + switch v := v.(type) { + case nil: + return append(b, ""...) + case string: + return appendUTF8String(b, StringToBytes(v)) + case []byte: + return appendUTF8String(b, v) + case int: + return strconv.AppendInt(b, int64(v), 10) + case int8: + return strconv.AppendInt(b, int64(v), 10) + case int16: + return strconv.AppendInt(b, int64(v), 10) + case int32: + return strconv.AppendInt(b, int64(v), 10) + case int64: + return strconv.AppendInt(b, v, 10) + case uint: + return strconv.AppendUint(b, uint64(v), 10) + case uint8: + return strconv.AppendUint(b, uint64(v), 10) + case uint16: + return strconv.AppendUint(b, uint64(v), 10) + case uint32: + return strconv.AppendUint(b, uint64(v), 10) + case uint64: + return strconv.AppendUint(b, v, 10) + case float32: + return strconv.AppendFloat(b, float64(v), 'f', -1, 64) + case float64: + return strconv.AppendFloat(b, v, 'f', -1, 64) + case bool: + if v { + return append(b, "true"...) + } + return append(b, "false"...) + case time.Time: + return v.AppendFormat(b, time.RFC3339Nano) + default: + return append(b, fmt.Sprint(v)...) + } +} + +func appendUTF8String(dst []byte, src []byte) []byte { + dst = append(dst, src...) + return dst +} + +func BytesToString(b []byte) string { + return *(*string)(unsafe.Pointer(&b)) +} + +func ScanSlice(data []string, slice interface{}) error { + v := reflect.ValueOf(slice) + if !v.IsValid() { + return fmt.Errorf("redis: ScanSlice(nil)") + } + if v.Kind() != reflect.Ptr { + return fmt.Errorf("redis: ScanSlice(non-pointer %T)", slice) + } + v = v.Elem() + if v.Kind() != reflect.Slice { + return fmt.Errorf("redis: ScanSlice(non-slice %T)", slice) + } + + next := makeSliceNextElemFunc(v) + for i, s := range data { + elem := next() + if err := Scan([]byte(s), elem.Addr().Interface()); err != nil { + err = fmt.Errorf("redis: ScanSlice index=%d value=%q failed: %w", i, s, err) + return err + } + } + + return nil +} + +func Scan(b []byte, v interface{}) error { + switch v := v.(type) { + case nil: + return fmt.Errorf("redis: Scan(nil)") + case *string: + *v = BytesToString(b) + return nil + case *[]byte: + *v = b + return nil + case *int: + var err error + *v, err = strconv.Atoi(BytesToString(b)) + return err + case *int8: + n, err := strconv.ParseInt(BytesToString(b), 10, 8) + if err != nil { + return err + } + *v = int8(n) + return nil + case *int16: + n, err := strconv.ParseInt(BytesToString(b), 10, 16) + if err != nil { + return err + } + *v = int16(n) + return nil + case *int32: + n, err := strconv.ParseInt(BytesToString(b), 10, 32) + if err != nil { + return err + } + *v = int32(n) + return nil + case *int64: + n, err := strconv.ParseInt(BytesToString(b), 10, 64) + if err != nil { + return err + } + *v = n + return nil + case *uint: + n, err := strconv.ParseUint(BytesToString(b), 10, 64) + if err != nil { + return err + } + *v = uint(n) + return nil + case *uint8: + n, err := strconv.ParseUint(BytesToString(b), 10, 8) + if err != nil { + return err + } + *v = uint8(n) + return nil + case *uint16: + n, err := strconv.ParseUint(BytesToString(b), 10, 16) + if err != nil { + return err + } + *v = uint16(n) + return nil + case *uint32: + n, err := strconv.ParseUint(BytesToString(b), 10, 32) + if err != nil { + return err + } + *v = uint32(n) + return nil + case *uint64: + n, err := strconv.ParseUint(BytesToString(b), 10, 64) + if err != nil { + return err + } + *v = n + return nil + case *float32: + n, err := strconv.ParseFloat(BytesToString(b), 32) + if err != nil { + return err + } + *v = float32(n) + return err + case *float64: + var err error + *v, err = strconv.ParseFloat(BytesToString(b), 64) + return err + case *bool: + *v = len(b) == 1 && b[0] == '1' + return nil + case *time.Time: + var err error + *v, err = time.Parse(time.RFC3339Nano, BytesToString(b)) + return err + case *time.Duration: + n, err := strconv.ParseInt(BytesToString(b), 10, 64) + if err != nil { + return err + } + *v = time.Duration(n) + return nil + case encoding.BinaryUnmarshaler: + return v.UnmarshalBinary(b) + case *net.IP: + *v = b + return nil + default: + return fmt.Errorf( + "redis: can't unmarshal %T (consider implementing BinaryUnmarshaler)", v) + } +} + +func makeSliceNextElemFunc(v reflect.Value) func() reflect.Value { + elemType := v.Type().Elem() + + if elemType.Kind() == reflect.Ptr { + elemType = elemType.Elem() + return func() reflect.Value { + if v.Len() < v.Cap() { + v.Set(v.Slice(0, v.Len()+1)) + elem := v.Index(v.Len() - 1) + if elem.IsNil() { + elem.Set(reflect.New(elemType)) + } + return elem.Elem() + } + + elem := reflect.New(elemType) + v.Set(reflect.Append(v, elem)) + return elem.Elem() + } + } + + zero := reflect.Zero(elemType) + return func() reflect.Value { + if v.Len() < v.Cap() { + v.Set(v.Slice(0, v.Len()+1)) + return v.Index(v.Len() - 1) + } + + v.Set(reflect.Append(v, zero)) + return v.Index(v.Len() - 1) + } +} + +func StringToBytes(s string) []byte { + return *(*[]byte)(unsafe.Pointer( + &struct { + string + Cap int + }{s, len(s)}, + )) +} diff --git a/mock.go b/mock.go index add153e..3c6a3b9 100644 --- a/mock.go +++ b/mock.go @@ -2898,194 +2898,3 @@ func (m *mock) ExpectTSMGetWithArgs(filters []string, options *redis.TSMGetOptio m.pushExpect(e) return e } - -// ---------------------------------------------------------------------------- - -func (m *mock) ExpectJSONArrAppend(key, path string, values ...interface{}) *ExpectedIntSliceCmd { - e := &ExpectedIntSliceCmd{} - e.cmd = m.factory.JSONArrAppend(m.ctx, key, path, values) - m.pushExpect(e) - return e -} - -func (m *mock) ExpectJSONArrIndex(key, path string, value ...interface{}) *ExpectedIntSliceCmd { - e := &ExpectedIntSliceCmd{} - e.cmd = m.factory.JSONArrIndex(m.ctx, key, path, value) - m.pushExpect(e) - return e -} - -func (m *mock) ExpectJSONArrIndexWithArgs(key, path string, options *redis.JSONArrIndexArgs, value ...interface{}) *ExpectedIntSliceCmd { - e := &ExpectedIntSliceCmd{} - e.cmd = m.factory.JSONArrIndexWithArgs(m.ctx, key, path, options, value) - m.pushExpect(e) - return e -} - -func (m *mock) ExpectJSONArrInsert(key, path string, index int64, values ...interface{}) *ExpectedIntSliceCmd { - e := &ExpectedIntSliceCmd{} - e.cmd = m.factory.JSONArrInsert(m.ctx, key, path, index, values) - m.pushExpect(e) - return e -} - -func (m *mock) ExpectJSONArrLen(key, path string) *ExpectedIntSliceCmd { - e := &ExpectedIntSliceCmd{} - e.cmd = m.factory.JSONArrLen(m.ctx, key, path) - m.pushExpect(e) - return e -} - -func (m *mock) ExpectJSONArrPop(key, path string, index int) *ExpectedStringSliceCmd { - e := &ExpectedStringSliceCmd{} - e.cmd = m.factory.JSONArrPop(m.ctx, key, path, index) - m.pushExpect(e) - return e -} - -func (m *mock) ExpectJSONArrTrim(key, path string) *ExpectedIntSliceCmd { - e := &ExpectedIntSliceCmd{} - e.cmd = m.factory.JSONArrTrim(m.ctx, key, path) - m.pushExpect(e) - return e -} - -func (m *mock) ExpectJSONArrTrimWithArgs(key, path string, options *redis.JSONArrTrimArgs) *ExpectedIntSliceCmd { - e := &ExpectedIntSliceCmd{} - e.cmd = m.factory.JSONArrTrimWithArgs(m.ctx, key, path, options) - m.pushExpect(e) - return e -} - -func (m *mock) ExpectJSONClear(key, path string) *ExpectedIntCmd { - e := &ExpectedIntCmd{} - e.cmd = m.factory.JSONClear(m.ctx, key, path) - m.pushExpect(e) - return e -} - -func (m *mock) ExpectJSONDebugMemory(key, path string) *ExpectedIntCmd { - e := &ExpectedIntCmd{} - e.cmd = m.factory.JSONDebugMemory(m.ctx, key, path) - m.pushExpect(e) - return e -} - -func (m *mock) ExpectJSONDel(key, path string) *ExpectedIntCmd { - e := &ExpectedIntCmd{} - e.cmd = m.factory.JSONDel(m.ctx, key, path) - m.pushExpect(e) - return e -} - -func (m *mock) ExpectJSONForget(key, path string) *ExpectedIntCmd { - e := &ExpectedIntCmd{} - e.cmd = m.factory.JSONForget(m.ctx, key, path) - m.pushExpect(e) - return e -} - -func (m *mock) ExpectJSONGet(key string, paths ...string) *ExpectedJSONCmd { - e := &ExpectedJSONCmd{} - e.cmd = m.factory.JSONGet(m.ctx, key, paths...) - m.pushExpect(e) - return e -} - -func (m *mock) ExpectJSONGetWithArgs(key string, options *redis.JSONGetArgs, paths ...string) *ExpectedJSONCmd { - e := &ExpectedJSONCmd{} - e.cmd = m.factory.JSONGetWithArgs(m.ctx, key, options, paths...) - m.pushExpect(e) - return e -} - -func (m *mock) ExpectJSONMerge(key, path string, value string) *ExpectedStatusCmd { - e := &ExpectedStatusCmd{} - e.cmd = m.factory.JSONMerge(m.ctx, key, path, value) - m.pushExpect(e) - return e -} - -func (m *mock) ExpectJSONMSetArgs(docs []redis.JSONSetArgs) *ExpectedStatusCmd { - e := &ExpectedStatusCmd{} - e.cmd = m.factory.JSONMSetArgs(m.ctx, docs) - m.pushExpect(e) - return e -} - -func (m *mock) ExpectJSONMSet(params ...interface{}) *ExpectedStatusCmd { - e := &ExpectedStatusCmd{} - e.cmd = m.factory.JSONMSet(m.ctx, params...) - m.pushExpect(e) - return e -} - -func (m *mock) ExpectJSONMGet(path string, keys ...string) *ExpectedJSONSliceCmd { - e := &ExpectedJSONSliceCmd{} - e.cmd = m.factory.JSONMGet(m.ctx, path, keys...) - m.pushExpect(e) - return e -} - -func (m *mock) ExpectJSONNumIncrBy(key, path string, value float64) *ExpectedJSONCmd { - e := &ExpectedJSONCmd{} - e.cmd = m.factory.JSONNumIncrBy(m.ctx, key, path, value) - m.pushExpect(e) - return e -} - -func (m *mock) ExpectJSONObjKeys(key, path string) *ExpectedSliceCmd { - e := &ExpectedSliceCmd{} - e.cmd = m.factory.JSONObjKeys(m.ctx, key, path) - m.pushExpect(e) - return e -} - -func (m *mock) ExpectJSONObjLen(key, path string) *ExpectedIntPointerSliceCmd { - e := &ExpectedIntPointerSliceCmd{} - e.cmd = m.factory.JSONObjLen(m.ctx, key, path) - m.pushExpect(e) - return e -} - -func (m *mock) ExpectJSONSet(key, path string, value interface{}) *ExpectedStatusCmd { - e := &ExpectedStatusCmd{} - e.cmd = m.factory.JSONSet(m.ctx, key, path, value) - m.pushExpect(e) - return e -} - -func (m *mock) ExpectJSONSetMode(key, path string, value interface{}, mode string) *ExpectedStatusCmd { - e := &ExpectedStatusCmd{} - e.cmd = m.factory.JSONSetMode(m.ctx, key, path, value, mode) - m.pushExpect(e) - return e -} - -func (m *mock) ExpectJSONStrAppend(key, path, value string) *ExpectedIntPointerSliceCmd { - e := &ExpectedIntPointerSliceCmd{} - e.cmd = m.factory.JSONStrAppend(m.ctx, key, path, value) - m.pushExpect(e) - return e -} - -func (m *mock) ExpectJSONStrLen(key, path string) *ExpectedIntPointerSliceCmd { - e := &ExpectedIntPointerSliceCmd{} - e.cmd = m.factory.JSONStrLen(m.ctx, key, path) - m.pushExpect(e) - return e -} - -func (m *mock) ExpectJSONToggle(key, path string) *ExpectedIntPointerSliceCmd { - e := &ExpectedIntPointerSliceCmd{} - e.cmd = m.factory.JSONToggle(m.ctx, key, path) - m.pushExpect(e) - return e -} - -func (m *mock) ExpectJSONType(key, path string) *ExpectedJSONSliceCmd { - e := &ExpectedJSONSliceCmd{} - e.cmd = m.factory.JSONType(m.ctx, key, path) - m.pushExpect(e) - return e -} diff --git a/mock_ft.go b/mock_ft.go new file mode 100644 index 0000000..1e05551 --- /dev/null +++ b/mock_ft.go @@ -0,0 +1,199 @@ +package redismock + +import "github.com/redis/go-redis/v9" + +func (m *mock) ExpectFT_List() *ExpectedStringSliceCmd { + e := &ExpectedStringSliceCmd{} + e.cmd = m.factory.FT_List(m.ctx) + m.pushExpect(e) + return e +} + +func (m *mock) ExpectFTAggregate(index string, query string) *ExpectedMapStringInterfaceCmd { + e := &ExpectedMapStringInterfaceCmd{} + e.cmd = m.factory.FTAggregate(m.ctx, index, query) + m.pushExpect(e) + return e +} + +func (m *mock) ExpectFTAggregateWithArgs(index string, query string, options *redis.FTAggregateOptions) *ExpectedAggregateCmd { + e := &ExpectedAggregateCmd{} + e.cmd = m.factory.FTAggregateWithArgs(m.ctx, index, query, options) + m.pushExpect(e) + return e +} + +func (m *mock) ExpectFTAliasAdd(index string, alias string) *ExpectedStatusCmd { + e := &ExpectedStatusCmd{} + e.cmd = m.factory.FTAliasAdd(m.ctx, index, alias) + m.pushExpect(e) + return e +} + +func (m *mock) ExpectFTAliasDel(alias string) *ExpectedStatusCmd { + e := &ExpectedStatusCmd{} + e.cmd = m.factory.FTAliasDel(m.ctx, alias) + m.pushExpect(e) + return e +} + +func (m *mock) ExpectFTAliasUpdate(index string, alias string) *ExpectedStatusCmd { + e := &ExpectedStatusCmd{} + e.cmd = m.factory.FTAliasUpdate(m.ctx, index, alias) + m.pushExpect(e) + return e +} + +func (m *mock) ExpectFTAlter(index string, skipInitialScan bool, definition []interface{}) *ExpectedStatusCmd { + e := &ExpectedStatusCmd{} + e.cmd = m.factory.FTAlter(m.ctx, index, skipInitialScan, definition) + m.pushExpect(e) + return e +} + +func (m *mock) ExpectFTConfigGet(option string) *ExpectedMapMapStringInterfaceCmd { + e := &ExpectedMapMapStringInterfaceCmd{} + e.cmd = m.factory.FTConfigGet(m.ctx, option) + m.pushExpect(e) + return e +} + +func (m *mock) ExpectFTConfigSet(option string, value interface{}) *ExpectedStatusCmd { + e := &ExpectedStatusCmd{} + e.cmd = m.factory.FTConfigSet(m.ctx, option, value) + m.pushExpect(e) + return e +} + +func (m *mock) ExpectFTCreate(index string, options *redis.FTCreateOptions, schema ...*redis.FieldSchema) *ExpectedStatusCmd { + e := &ExpectedStatusCmd{} + e.cmd = m.factory.FTCreate(m.ctx, index, options, schema...) + m.pushExpect(e) + return e +} + +func (m *mock) ExpectFTCursorDel(index string, cursorId int) *ExpectedStatusCmd { + e := &ExpectedStatusCmd{} + e.cmd = m.factory.FTCursorDel(m.ctx, index, cursorId) + m.pushExpect(e) + return e +} + +func (m *mock) ExpectFTCursorRead(index string, cursorId int, count int) *ExpectedMapStringInterfaceCmd { + e := &ExpectedMapStringInterfaceCmd{} + e.cmd = m.factory.FTCursorRead(m.ctx, index, cursorId, count) + m.pushExpect(e) + return e +} + +func (m *mock) ExpectFTDictAdd(dict string, term ...interface{}) *ExpectedIntCmd { + e := &ExpectedIntCmd{} + e.cmd = m.factory.FTDictAdd(m.ctx, dict, term) + m.pushExpect(e) + return e +} + +func (m *mock) ExpectFTDictDel(dict string, term ...interface{}) *ExpectedIntCmd { + e := &ExpectedIntCmd{} + e.cmd = m.factory.FTDictDel(m.ctx, dict, term) + m.pushExpect(e) + return e +} + +func (m *mock) ExpectFTDictDump(dict string) *ExpectedStringSliceCmd { + e := &ExpectedStringSliceCmd{} + e.cmd = m.factory.FTDictDump(m.ctx, dict) + m.pushExpect(e) + return e +} + +func (m *mock) ExpectFTDropIndex(index string) *ExpectedStatusCmd { + e := &ExpectedStatusCmd{} + e.cmd = m.factory.FTDropIndex(m.ctx, index) + m.pushExpect(e) + return e +} + +func (m *mock) ExpectFTDropIndexWithArgs(index string, options *redis.FTDropIndexOptions) *ExpectedStatusCmd { + e := &ExpectedStatusCmd{} + e.cmd = m.factory.FTDropIndexWithArgs(m.ctx, index, options) + m.pushExpect(e) + return e +} + +func (m *mock) ExpectFTExplain(index string, query string) *ExpectedStringCmd { + e := &ExpectedStringCmd{} + e.cmd = m.factory.FTExplain(m.ctx, index, query) + m.pushExpect(e) + return e +} + +func (m *mock) ExpectFTExplainWithArgs(index string, query string, options *redis.FTExplainOptions) *ExpectedStringCmd { + e := &ExpectedStringCmd{} + e.cmd = m.factory.FTExplainWithArgs(m.ctx, index, query, options) + m.pushExpect(e) + return e +} + +func (m *mock) ExpectFTInfo(index string) *ExpectedFTInfoCmd { + e := &ExpectedFTInfoCmd{} + e.cmd = m.factory.FTInfo(m.ctx, index) + m.pushExpect(e) + return e +} + +func (m *mock) ExpectFTSpellCheck(index string, query string) *ExpectedFTSpellCheckCmd { + e := &ExpectedFTSpellCheckCmd{} + e.cmd = m.factory.FTSpellCheck(m.ctx, index, query) + m.pushExpect(e) + return e +} + +func (m *mock) ExpectFTSpellCheckWithArgs(index string, query string, options *redis.FTSpellCheckOptions) *ExpectedFTSpellCheckCmd { + e := &ExpectedFTSpellCheckCmd{} + e.cmd = m.factory.FTSpellCheckWithArgs(m.ctx, index, query, options) + m.pushExpect(e) + return e +} + +func (m *mock) ExpectFTSearch(index string, query string) *ExpectedFTSearchCmd { + e := &ExpectedFTSearchCmd{} + e.cmd = m.factory.FTSearch(m.ctx, index, query) + m.pushExpect(e) + return e +} + +func (m *mock) ExpectFTSearchWithArgs(index string, query string, options *redis.FTSearchOptions) *ExpectedFTSearchCmd { + e := &ExpectedFTSearchCmd{} + e.cmd = m.factory.FTSearchWithArgs(m.ctx, index, query, options) + m.pushExpect(e) + return e +} + +func (m *mock) ExpectFTSynDump(index string) *ExpectedFTSynDumpCmd { + e := &ExpectedFTSynDumpCmd{} + e.cmd = m.factory.FTSynDump(m.ctx, index) + m.pushExpect(e) + return e +} + +func (m *mock) ExpectFTSynUpdate(index string, synGroupId interface{}, terms []interface{}) *ExpectedStatusCmd { + e := &ExpectedStatusCmd{} + e.cmd = m.factory.FTSynUpdate(m.ctx, index, synGroupId, terms) + m.pushExpect(e) + return e +} + +func (m *mock) ExpectFTSynUpdateWithArgs(index string, synGroupId interface{}, options *redis.FTSynUpdateOptions, terms []interface{}) *ExpectedStatusCmd { + e := &ExpectedStatusCmd{} + e.cmd = m.factory.FTSynUpdateWithArgs(m.ctx, index, synGroupId, options, terms) + m.pushExpect(e) + return e +} + +func (m *mock) ExpectFTTagVals(index string, field string) *ExpectedStringSliceCmd { + e := &ExpectedStringSliceCmd{} + e.cmd = m.factory.FTTagVals(m.ctx, index, field) + m.pushExpect(e) + return e +} diff --git a/mock_json.go b/mock_json.go new file mode 100644 index 0000000..c5247dc --- /dev/null +++ b/mock_json.go @@ -0,0 +1,192 @@ +package redismock + +import "github.com/redis/go-redis/v9" + +func (m *mock) ExpectJSONArrAppend(key, path string, values ...interface{}) *ExpectedIntSliceCmd { + e := &ExpectedIntSliceCmd{} + e.cmd = m.factory.JSONArrAppend(m.ctx, key, path, values) + m.pushExpect(e) + return e +} + +func (m *mock) ExpectJSONArrIndex(key, path string, value ...interface{}) *ExpectedIntSliceCmd { + e := &ExpectedIntSliceCmd{} + e.cmd = m.factory.JSONArrIndex(m.ctx, key, path, value) + m.pushExpect(e) + return e +} + +func (m *mock) ExpectJSONArrIndexWithArgs(key, path string, options *redis.JSONArrIndexArgs, value ...interface{}) *ExpectedIntSliceCmd { + e := &ExpectedIntSliceCmd{} + e.cmd = m.factory.JSONArrIndexWithArgs(m.ctx, key, path, options, value) + m.pushExpect(e) + return e +} + +func (m *mock) ExpectJSONArrInsert(key, path string, index int64, values ...interface{}) *ExpectedIntSliceCmd { + e := &ExpectedIntSliceCmd{} + e.cmd = m.factory.JSONArrInsert(m.ctx, key, path, index, values) + m.pushExpect(e) + return e +} + +func (m *mock) ExpectJSONArrLen(key, path string) *ExpectedIntSliceCmd { + e := &ExpectedIntSliceCmd{} + e.cmd = m.factory.JSONArrLen(m.ctx, key, path) + m.pushExpect(e) + return e +} + +func (m *mock) ExpectJSONArrPop(key, path string, index int) *ExpectedStringSliceCmd { + e := &ExpectedStringSliceCmd{} + e.cmd = m.factory.JSONArrPop(m.ctx, key, path, index) + m.pushExpect(e) + return e +} + +func (m *mock) ExpectJSONArrTrim(key, path string) *ExpectedIntSliceCmd { + e := &ExpectedIntSliceCmd{} + e.cmd = m.factory.JSONArrTrim(m.ctx, key, path) + m.pushExpect(e) + return e +} + +func (m *mock) ExpectJSONArrTrimWithArgs(key, path string, options *redis.JSONArrTrimArgs) *ExpectedIntSliceCmd { + e := &ExpectedIntSliceCmd{} + e.cmd = m.factory.JSONArrTrimWithArgs(m.ctx, key, path, options) + m.pushExpect(e) + return e +} + +func (m *mock) ExpectJSONClear(key, path string) *ExpectedIntCmd { + e := &ExpectedIntCmd{} + e.cmd = m.factory.JSONClear(m.ctx, key, path) + m.pushExpect(e) + return e +} + +func (m *mock) ExpectJSONDebugMemory(key, path string) *ExpectedIntCmd { + e := &ExpectedIntCmd{} + e.cmd = m.factory.JSONDebugMemory(m.ctx, key, path) + m.pushExpect(e) + return e +} + +func (m *mock) ExpectJSONDel(key, path string) *ExpectedIntCmd { + e := &ExpectedIntCmd{} + e.cmd = m.factory.JSONDel(m.ctx, key, path) + m.pushExpect(e) + return e +} + +func (m *mock) ExpectJSONForget(key, path string) *ExpectedIntCmd { + e := &ExpectedIntCmd{} + e.cmd = m.factory.JSONForget(m.ctx, key, path) + m.pushExpect(e) + return e +} + +func (m *mock) ExpectJSONGet(key string, paths ...string) *ExpectedJSONCmd { + e := &ExpectedJSONCmd{} + e.cmd = m.factory.JSONGet(m.ctx, key, paths...) + m.pushExpect(e) + return e +} + +func (m *mock) ExpectJSONGetWithArgs(key string, options *redis.JSONGetArgs, paths ...string) *ExpectedJSONCmd { + e := &ExpectedJSONCmd{} + e.cmd = m.factory.JSONGetWithArgs(m.ctx, key, options, paths...) + m.pushExpect(e) + return e +} + +func (m *mock) ExpectJSONMerge(key, path string, value string) *ExpectedStatusCmd { + e := &ExpectedStatusCmd{} + e.cmd = m.factory.JSONMerge(m.ctx, key, path, value) + m.pushExpect(e) + return e +} + +func (m *mock) ExpectJSONMSetArgs(docs []redis.JSONSetArgs) *ExpectedStatusCmd { + e := &ExpectedStatusCmd{} + e.cmd = m.factory.JSONMSetArgs(m.ctx, docs) + m.pushExpect(e) + return e +} + +func (m *mock) ExpectJSONMSet(params ...interface{}) *ExpectedStatusCmd { + e := &ExpectedStatusCmd{} + e.cmd = m.factory.JSONMSet(m.ctx, params...) + m.pushExpect(e) + return e +} + +func (m *mock) ExpectJSONMGet(path string, keys ...string) *ExpectedJSONSliceCmd { + e := &ExpectedJSONSliceCmd{} + e.cmd = m.factory.JSONMGet(m.ctx, path, keys...) + m.pushExpect(e) + return e +} + +func (m *mock) ExpectJSONNumIncrBy(key, path string, value float64) *ExpectedJSONCmd { + e := &ExpectedJSONCmd{} + e.cmd = m.factory.JSONNumIncrBy(m.ctx, key, path, value) + m.pushExpect(e) + return e +} + +func (m *mock) ExpectJSONObjKeys(key, path string) *ExpectedSliceCmd { + e := &ExpectedSliceCmd{} + e.cmd = m.factory.JSONObjKeys(m.ctx, key, path) + m.pushExpect(e) + return e +} + +func (m *mock) ExpectJSONObjLen(key, path string) *ExpectedIntPointerSliceCmd { + e := &ExpectedIntPointerSliceCmd{} + e.cmd = m.factory.JSONObjLen(m.ctx, key, path) + m.pushExpect(e) + return e +} + +func (m *mock) ExpectJSONSet(key, path string, value interface{}) *ExpectedStatusCmd { + e := &ExpectedStatusCmd{} + e.cmd = m.factory.JSONSet(m.ctx, key, path, value) + m.pushExpect(e) + return e +} + +func (m *mock) ExpectJSONSetMode(key, path string, value interface{}, mode string) *ExpectedStatusCmd { + e := &ExpectedStatusCmd{} + e.cmd = m.factory.JSONSetMode(m.ctx, key, path, value, mode) + m.pushExpect(e) + return e +} + +func (m *mock) ExpectJSONStrAppend(key, path, value string) *ExpectedIntPointerSliceCmd { + e := &ExpectedIntPointerSliceCmd{} + e.cmd = m.factory.JSONStrAppend(m.ctx, key, path, value) + m.pushExpect(e) + return e +} + +func (m *mock) ExpectJSONStrLen(key, path string) *ExpectedIntPointerSliceCmd { + e := &ExpectedIntPointerSliceCmd{} + e.cmd = m.factory.JSONStrLen(m.ctx, key, path) + m.pushExpect(e) + return e +} + +func (m *mock) ExpectJSONToggle(key, path string) *ExpectedIntPointerSliceCmd { + e := &ExpectedIntPointerSliceCmd{} + e.cmd = m.factory.JSONToggle(m.ctx, key, path) + m.pushExpect(e) + return e +} + +func (m *mock) ExpectJSONType(key, path string) *ExpectedJSONSliceCmd { + e := &ExpectedJSONSliceCmd{} + e.cmd = m.factory.JSONType(m.ctx, key, path) + m.pushExpect(e) + return e +} From 07be869b89cd65050fe2273cb8502a46c6218aaa Mon Sep 17 00:00:00 2001 From: Ryan Matthews Date: Wed, 24 Sep 2025 14:05:09 -0400 Subject: [PATCH 4/8] Ensure setVal is being set --- expect_json.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/expect_json.go b/expect_json.go index 189fece..04fa18a 100644 --- a/expect_json.go +++ b/expect_json.go @@ -76,6 +76,7 @@ func (cmd *ExpectedStatusCmd) inflow(c redis.Cmder) { } func (cmd *ExpectedStatusCmd) SetVal(val string) { + cmd.setVal = true cmd.val = val } @@ -106,6 +107,7 @@ func (cmd *ExpectedIntCmd) inflow(c redis.Cmder) { } func (cmd *ExpectedIntCmd) SetVal(val int64) { + cmd.setVal = true cmd.val = val } @@ -140,6 +142,7 @@ func (cmd *ExpectedIntPointerSliceCmd) String() string { } func (cmd *ExpectedIntPointerSliceCmd) SetVal(val []*int64) { + cmd.setVal = true cmd.val = val } @@ -167,6 +170,7 @@ func (cmd *ExpectedJSONCmd) String() string { } func (cmd *ExpectedJSONCmd) SetVal(val string) { + cmd.setVal = true cmd.val = val } @@ -214,6 +218,7 @@ func (cmd *ExpectedJSONSliceCmd) String() string { } func (cmd *ExpectedJSONSliceCmd) SetVal(val []interface{}) { + cmd.setVal = true cmd.val = val } @@ -236,6 +241,7 @@ func (cmd *ExpectedStringSliceCmd) inflow(c redis.Cmder) { } func (cmd *ExpectedStringSliceCmd) SetVal(val []string) { + cmd.setVal = true cmd.val = val } @@ -266,6 +272,7 @@ func (cmd *ExpectedSliceCmd) inflow(c redis.Cmder) { } func (cmd *ExpectedSliceCmd) SetVal(val []interface{}) { + cmd.setVal = true cmd.val = val } From 92a751ecc61d520cc4a0f71c7aad5bed48748258 Mon Sep 17 00:00:00 2001 From: Ryan Matthews Date: Wed, 8 Oct 2025 14:25:24 -0700 Subject: [PATCH 5/8] chore: Add tests for JSON functions --- commands_json_test.go | 246 ++++++++++++++++++++++++++++++++++++++++++ expect.go | 88 +++++++-------- expect_json.go | 206 ++++------------------------------- mock_ft.go | 64 +++++------ mock_json.go | 116 ++++++++++---------- mock_test.go | 79 ++++++++++++++ 6 files changed, 478 insertions(+), 321 deletions(-) create mode 100644 commands_json_test.go diff --git a/commands_json_test.go b/commands_json_test.go new file mode 100644 index 0000000..92ed57b --- /dev/null +++ b/commands_json_test.go @@ -0,0 +1,246 @@ +package redismock + +import ( + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "github.com/redis/go-redis/v9" +) + +var _ = Describe("JSONCommands", func() { + var ( + clientMock baseMock + client mockCmdable + ) + + callCommandTest := func() { + It("ExpectJSONArrAppend", func() { + operationIntSliceCmd(clientMock, func() *ExpectedIntSlice { + return clientMock.ExpectJSONArrAppend("key", "path", "value") + }, func() *redis.IntSliceCmd { + return client.JSONArrAppend(ctx, "key", "path", "value") + }) + }) + + It("ExpectJSONArrIndex", func() { + operationIntSliceCmd(clientMock, func() *ExpectedIntSlice { + return clientMock.ExpectJSONArrIndex("key", "path", "value") + }, func() *redis.IntSliceCmd { + return client.JSONArrIndex(ctx, "key", "path", "value") + }) + }) + + It("ExpectJSONArrIndexWithArgs", func() { + operationIntSliceCmd(clientMock, func() *ExpectedIntSlice { + return clientMock.ExpectJSONArrIndexWithArgs("key", "path", &redis.JSONArrIndexArgs{}, "value") + }, func() *redis.IntSliceCmd { + return client.JSONArrIndexWithArgs(ctx, "key", "path", &redis.JSONArrIndexArgs{}, "value") + }) + }) + + It("ExpectJSONArrInsert", func() { + operationIntSliceCmd(clientMock, func() *ExpectedIntSlice { + return clientMock.ExpectJSONArrInsert("key", "path", 1, "value") + }, func() *redis.IntSliceCmd { + return client.JSONArrInsert(ctx, "key", "path", 1, "value") + }) + }) + + It("ExpectJSONArrLen", func() { + operationIntSliceCmd(clientMock, func() *ExpectedIntSlice { + return clientMock.ExpectJSONArrLen("key", "path") + }, func() *redis.IntSliceCmd { + return client.JSONArrLen(ctx, "key", "path") + }) + }) + + It("ExpectJSONArrPop", func() { + operationStringSliceCmd(clientMock, func() *ExpectedStringSlice { + return clientMock.ExpectJSONArrPop("key", "path", 1) + }, func() *redis.StringSliceCmd { + return client.JSONArrPop(ctx, "key", "path", 1) + }) + }) + + It("ExpectJSONArrTrim", func() { + operationIntSliceCmd(clientMock, func() *ExpectedIntSlice { + return clientMock.ExpectJSONArrTrim("key", "path") + }, func() *redis.IntSliceCmd { + return client.JSONArrTrim(ctx, "key", "path") + }) + }) + + It("ExpectJSONArrTrimWithArgs", func() { + operationIntSliceCmd(clientMock, func() *ExpectedIntSlice { + return clientMock.ExpectJSONArrTrimWithArgs("key", "path", &redis.JSONArrTrimArgs{}) + }, func() *redis.IntSliceCmd { + return client.JSONArrTrimWithArgs(ctx, "key", "path", &redis.JSONArrTrimArgs{}) + }) + }) + + It("ExpectJSONClear", func() { + operationIntCmd(clientMock, func() *ExpectedInt { + return clientMock.ExpectJSONClear("key", "path") + }, func() *redis.IntCmd { + return client.JSONClear(ctx, "key", "path") + }) + }) + + // This is a panic in the go-redis library + // It("ExpectJSONDebugMemory", func() { + // operationIntCmd(clientMock, func() *ExpectedInt { + // return clientMock.ExpectJSONDebugMemory("key", "path") + // }, func() *redis.IntCmd { + // return client.JSONDebugMemory(ctx, "key", "path") + // }) + // }) + + It("ExpectJSONDel", func() { + operationIntCmd(clientMock, func() *ExpectedInt { + return clientMock.ExpectJSONDel("key", "path") + }, func() *redis.IntCmd { + return client.JSONDel(ctx, "key", "path") + }) + }) + + It("ExpectJSONForget", func() { + operationIntCmd(clientMock, func() *ExpectedInt { + return clientMock.ExpectJSONForget("key", "path") + }, func() *redis.IntCmd { + return client.JSONForget(ctx, "key", "path") + }) + }) + + It("ExpectJSONGet", func() { + operationJSONCmd(clientMock, func() *ExpectedJSON { + return clientMock.ExpectJSONGet("key", "paths") + }, func() *redis.JSONCmd { + return client.JSONGet(ctx, "key", "paths") + }) + }) + + It("ExpectJSONGetWithArgs", func() { + operationJSONCmd(clientMock, func() *ExpectedJSON { + return clientMock.ExpectJSONGetWithArgs("key", &redis.JSONGetArgs{}, "paths") + }, func() *redis.JSONCmd { + return client.JSONGetWithArgs(ctx, "key", &redis.JSONGetArgs{}, "paths") + }) + }) + + It("ExpectJSONMerge", func() { + operationStatusCmd(clientMock, func() *ExpectedStatus { + return clientMock.ExpectJSONMerge("key", "path", "value") + }, func() *redis.StatusCmd { + return client.JSONMerge(ctx, "key", "path", "value") + }) + }) + + It("ExpectJSONMSetArgs", func() { + operationStatusCmd(clientMock, func() *ExpectedStatus { + return clientMock.ExpectJSONMSetArgs([]redis.JSONSetArgs{}) + }, func() *redis.StatusCmd { + return client.JSONMSetArgs(ctx, []redis.JSONSetArgs{}) + }) + }) + + It("ExpectJSONMSet", func() { + operationStatusCmd(clientMock, func() *ExpectedStatus { + return clientMock.ExpectJSONMSet("params") + }, func() *redis.StatusCmd { + return client.JSONMSet(ctx, "params") + }) + }) + + It("ExpectJSONMGet", func() { + operationJSONSliceCmd(clientMock, func() *ExpectedJSONSlice { + return clientMock.ExpectJSONMGet("path", "keys") + }, func() *redis.JSONSliceCmd { + return client.JSONMGet(ctx, "path", "keys") + }) + }) + + It("ExpectJSONNumIncrBy", func() { + operationJSONCmd(clientMock, func() *ExpectedJSON { + return clientMock.ExpectJSONNumIncrBy("key", "path", 0.1) + }, func() *redis.JSONCmd { + return client.JSONNumIncrBy(ctx, "key", "path", 0.1) + }) + }) + + It("ExpectJSONObjKeys", func() { + operationSliceCmd(clientMock, func() *ExpectedSlice { + return clientMock.ExpectJSONObjKeys("key", "path") + }, func() *redis.SliceCmd { + return client.JSONObjKeys(ctx, "key", "path") + }) + }) + + It("ExpectJSONObjLen", func() { + operationIntPointerSliceCmd(clientMock, func() *ExpectedIntPointerSlice { + return clientMock.ExpectJSONObjLen("key", "path") + }, func() *redis.IntPointerSliceCmd { + return client.JSONObjLen(ctx, "key", "path") + }) + }) + + It("ExpectJSONSet", func() { + operationStatusCmd(clientMock, func() *ExpectedStatus { + return clientMock.ExpectJSONSet("key", "path", "value") + }, func() *redis.StatusCmd { + return client.JSONSet(ctx, "key", "path", "value") + }) + }) + + It("ExpectJSONSetMode", func() { + operationStatusCmd(clientMock, func() *ExpectedStatus { + return clientMock.ExpectJSONSetMode("key", "path", "value", "NX") + }, func() *redis.StatusCmd { + return client.JSONSetMode(ctx, "key", "path", "value", "NX") + }) + }) + + It("ExpectJSONStrAppend", func() { + operationIntPointerSliceCmd(clientMock, func() *ExpectedIntPointerSlice { + return clientMock.ExpectJSONStrAppend("key", "path", "value") + }, func() *redis.IntPointerSliceCmd { + return client.JSONStrAppend(ctx, "key", "path", "value") + }) + }) + + It("ExpectJSONStrLen", func() { + operationIntPointerSliceCmd(clientMock, func() *ExpectedIntPointerSlice { + return clientMock.ExpectJSONStrLen("key", "path") + }, func() *redis.IntPointerSliceCmd { + return client.JSONStrLen(ctx, "key", "path") + }) + }) + + It("ExpectJSONToggle", func() { + operationIntPointerSliceCmd(clientMock, func() *ExpectedIntPointerSlice { + return clientMock.ExpectJSONToggle("key", "path") + }, func() *redis.IntPointerSliceCmd { + return client.JSONToggle(ctx, "key", "path") + }) + }) + + It("ExpectJSONType", func() { + operationJSONSliceCmd(clientMock, func() *ExpectedJSONSlice { + return clientMock.ExpectJSONType("key", "path") + }, func() *redis.JSONSliceCmd { + return client.JSONType(ctx, "key", "path") + }) + }) + } + + Describe("client", func() { + BeforeEach(func() { + client, clientMock = NewClientMock() + }) + + AfterEach(func() { + Expect(client.(*redis.Client).Close()).NotTo(HaveOccurred()) + Expect(clientMock.ExpectationsWereMet()).NotTo(HaveOccurred()) + }) + + callCommandTest() + }) +}) diff --git a/expect.go b/expect.go index 1d4116b..2c98c67 100644 --- a/expect.go +++ b/expect.go @@ -395,51 +395,51 @@ type baseMock interface { ExpectTSMGet(filters []string) *ExpectedMapStringSliceInterface ExpectTSMGetWithArgs(filters []string, options *redis.TSMGetOptions) *ExpectedMapStringSliceInterface - ExpectJSONArrAppend(key, path string, values ...interface{}) *ExpectedIntSliceCmd - ExpectJSONArrIndex(key, path string, value ...interface{}) *ExpectedIntSliceCmd - ExpectJSONArrIndexWithArgs(key, path string, options *redis.JSONArrIndexArgs, value ...interface{}) *ExpectedIntSliceCmd - ExpectJSONArrInsert(key, path string, index int64, values ...interface{}) *ExpectedIntSliceCmd - ExpectJSONArrLen(key, path string) *ExpectedIntSliceCmd - ExpectJSONArrPop(key, path string, index int) *ExpectedStringSliceCmd - ExpectJSONArrTrim(key, path string) *ExpectedIntSliceCmd - ExpectJSONArrTrimWithArgs(key, path string, options *redis.JSONArrTrimArgs) *ExpectedIntSliceCmd - ExpectJSONClear(key, path string) *ExpectedIntCmd - ExpectJSONDebugMemory(key, path string) *ExpectedIntCmd - ExpectJSONDel(key, path string) *ExpectedIntCmd - ExpectJSONForget(key, path string) *ExpectedIntCmd - ExpectJSONGet(key string, paths ...string) *ExpectedJSONCmd - ExpectJSONGetWithArgs(key string, options *redis.JSONGetArgs, paths ...string) *ExpectedJSONCmd - ExpectJSONMerge(key, path string, value string) *ExpectedStatusCmd - ExpectJSONMSetArgs(docs []redis.JSONSetArgs) *ExpectedStatusCmd - ExpectJSONMSet(params ...interface{}) *ExpectedStatusCmd - ExpectJSONMGet(path string, keys ...string) *ExpectedJSONSliceCmd - ExpectJSONNumIncrBy(key, path string, value float64) *ExpectedJSONCmd - ExpectJSONObjKeys(key, path string) *ExpectedSliceCmd - ExpectJSONObjLen(key, path string) *ExpectedIntPointerSliceCmd - ExpectJSONSet(key, path string, value interface{}) *ExpectedStatusCmd - ExpectJSONSetMode(key, path string, value interface{}, mode string) *ExpectedStatusCmd - ExpectJSONStrAppend(key, path, value string) *ExpectedIntPointerSliceCmd - ExpectJSONStrLen(key, path string) *ExpectedIntPointerSliceCmd - ExpectJSONToggle(key, path string) *ExpectedIntPointerSliceCmd - ExpectJSONType(key, path string) *ExpectedJSONSliceCmd - - ExpectFT_List() *ExpectedStringSliceCmd + ExpectJSONArrAppend(key, path string, values ...interface{}) *ExpectedIntSlice + ExpectJSONArrIndex(key, path string, value ...interface{}) *ExpectedIntSlice + ExpectJSONArrIndexWithArgs(key, path string, options *redis.JSONArrIndexArgs, value ...interface{}) *ExpectedIntSlice + ExpectJSONArrInsert(key, path string, index int64, values ...interface{}) *ExpectedIntSlice + ExpectJSONArrLen(key, path string) *ExpectedIntSlice + ExpectJSONArrPop(key, path string, index int) *ExpectedStringSlice + ExpectJSONArrTrim(key, path string) *ExpectedIntSlice + ExpectJSONArrTrimWithArgs(key, path string, options *redis.JSONArrTrimArgs) *ExpectedIntSlice + ExpectJSONClear(key, path string) *ExpectedInt + ExpectJSONDebugMemory(key, path string) *ExpectedInt + ExpectJSONDel(key, path string) *ExpectedInt + ExpectJSONForget(key, path string) *ExpectedInt + ExpectJSONGet(key string, paths ...string) *ExpectedJSON + ExpectJSONGetWithArgs(key string, options *redis.JSONGetArgs, paths ...string) *ExpectedJSON + ExpectJSONMerge(key, path string, value string) *ExpectedStatus + ExpectJSONMSetArgs(docs []redis.JSONSetArgs) *ExpectedStatus + ExpectJSONMSet(params ...interface{}) *ExpectedStatus + ExpectJSONMGet(path string, keys ...string) *ExpectedJSONSlice + ExpectJSONNumIncrBy(key, path string, value float64) *ExpectedJSON + ExpectJSONObjKeys(key, path string) *ExpectedSlice + ExpectJSONObjLen(key, path string) *ExpectedIntPointerSlice + ExpectJSONSet(key, path string, value interface{}) *ExpectedStatus + ExpectJSONSetMode(key, path string, value interface{}, mode string) *ExpectedStatus + ExpectJSONStrAppend(key, path, value string) *ExpectedIntPointerSlice + ExpectJSONStrLen(key, path string) *ExpectedIntPointerSlice + ExpectJSONToggle(key, path string) *ExpectedIntPointerSlice + ExpectJSONType(key, path string) *ExpectedJSONSlice + + ExpectFT_List() *ExpectedStringSlice ExpectFTAggregate(index string, query string) *ExpectedMapStringInterfaceCmd ExpectFTAggregateWithArgs(index string, query string, options *redis.FTAggregateOptions) *ExpectedAggregateCmd - ExpectFTAliasAdd(index string, alias string) *ExpectedStatusCmd - ExpectFTAliasDel(alias string) *ExpectedStatusCmd - ExpectFTAliasUpdate(index string, alias string) *ExpectedStatusCmd - ExpectFTAlter(index string, skipInitialScan bool, definition []interface{}) *ExpectedStatusCmd + ExpectFTAliasAdd(index string, alias string) *ExpectedStatus + ExpectFTAliasDel(alias string) *ExpectedStatus + ExpectFTAliasUpdate(index string, alias string) *ExpectedStatus + ExpectFTAlter(index string, skipInitialScan bool, definition []interface{}) *ExpectedStatus ExpectFTConfigGet(option string) *ExpectedMapMapStringInterfaceCmd - ExpectFTConfigSet(option string, value interface{}) *ExpectedStatusCmd - ExpectFTCreate(index string, options *redis.FTCreateOptions, schema ...*redis.FieldSchema) *ExpectedStatusCmd - ExpectFTCursorDel(index string, cursorId int) *ExpectedStatusCmd + ExpectFTConfigSet(option string, value interface{}) *ExpectedStatus + ExpectFTCreate(index string, options *redis.FTCreateOptions, schema ...*redis.FieldSchema) *ExpectedStatus + ExpectFTCursorDel(index string, cursorId int) *ExpectedStatus ExpectFTCursorRead(index string, cursorId int, count int) *ExpectedMapStringInterfaceCmd - ExpectFTDictAdd(dict string, term ...interface{}) *ExpectedIntCmd - ExpectFTDictDel(dict string, term ...interface{}) *ExpectedIntCmd - ExpectFTDictDump(dict string) *ExpectedStringSliceCmd - ExpectFTDropIndex(index string) *ExpectedStatusCmd - ExpectFTDropIndexWithArgs(index string, options *redis.FTDropIndexOptions) *ExpectedStatusCmd + ExpectFTDictAdd(dict string, term ...interface{}) *ExpectedInt + ExpectFTDictDel(dict string, term ...interface{}) *ExpectedInt + ExpectFTDictDump(dict string) *ExpectedStringSlice + ExpectFTDropIndex(index string) *ExpectedStatus + ExpectFTDropIndexWithArgs(index string, options *redis.FTDropIndexOptions) *ExpectedStatus ExpectFTExplain(index string, query string) *ExpectedStringCmd ExpectFTExplainWithArgs(index string, query string, options *redis.FTExplainOptions) *ExpectedStringCmd ExpectFTInfo(index string) *ExpectedFTInfoCmd @@ -448,9 +448,9 @@ type baseMock interface { ExpectFTSearch(index string, query string) *ExpectedFTSearchCmd ExpectFTSearchWithArgs(index string, query string, options *redis.FTSearchOptions) *ExpectedFTSearchCmd ExpectFTSynDump(index string) *ExpectedFTSynDumpCmd - ExpectFTSynUpdate(index string, synGroupId interface{}, terms []interface{}) *ExpectedStatusCmd - ExpectFTSynUpdateWithArgs(index string, synGroupId interface{}, options *redis.FTSynUpdateOptions, terms []interface{}) *ExpectedStatusCmd - ExpectFTTagVals(index string, field string) *ExpectedStringSliceCmd + ExpectFTSynUpdate(index string, synGroupId interface{}, terms []interface{}) *ExpectedStatus + ExpectFTSynUpdateWithArgs(index string, synGroupId interface{}, options *redis.FTSynUpdateOptions, terms []interface{}) *ExpectedStatus + ExpectFTTagVals(index string, field string) *ExpectedStringSlice } type pipelineMock interface { diff --git a/expect_json.go b/expect_json.go index 04fa18a..3b163ca 100644 --- a/expect_json.go +++ b/expect_json.go @@ -37,144 +37,54 @@ func (cmd *ExpectedMapMapStringInterfaceCmd) Val() map[string]interface{} { return cmd.val } -type ExpectedIntSliceCmd struct { - expectedBase - - val []int64 -} - -func (cmd *ExpectedIntSliceCmd) SetVal(val []int64) { - cmd.setVal = true - cmd.val = make([]int64, len(val)) - copy(cmd.val, val) -} - -func (cmd *ExpectedIntSliceCmd) inflow(c redis.Cmder) { - inflow(c, "val", cmd.val) -} - -func (cmd *ExpectedIntSliceCmd) Result() ([]int64, error) { - return cmd.val, cmd.err -} - -func (cmd *ExpectedIntSliceCmd) String() string { - return cmdString(cmd.cmd, cmd.val) -} - -func (cmd *ExpectedIntSliceCmd) Val() []int64 { - return cmd.val -} - -type ExpectedStatusCmd struct { - expectedBase - - val string -} - -func (cmd *ExpectedStatusCmd) inflow(c redis.Cmder) { - inflow(c, "val", cmd.val) -} - -func (cmd *ExpectedStatusCmd) SetVal(val string) { - cmd.setVal = true - cmd.val = val -} - -func (cmd *ExpectedStatusCmd) Val() string { - return cmd.val -} - -func (cmd *ExpectedStatusCmd) Result() (string, error) { - return cmd.val, cmd.err -} - -func (cmd *ExpectedStatusCmd) Bytes() ([]byte, error) { - return StringToBytes(cmd.val), cmd.err -} - -func (cmd *ExpectedStatusCmd) String() string { - return cmdString(cmd.cmd, cmd.val) -} - -type ExpectedIntCmd struct { - expectedBase - - val int64 -} - -func (cmd *ExpectedIntCmd) inflow(c redis.Cmder) { - inflow(c, "val", cmd.val) -} - -func (cmd *ExpectedIntCmd) SetVal(val int64) { - cmd.setVal = true - cmd.val = val -} - -func (cmd *ExpectedIntCmd) Val() int64 { - return cmd.val -} - -func (cmd *ExpectedIntCmd) Result() (int64, error) { - return cmd.val, cmd.err -} - -func (cmd *ExpectedIntCmd) Uint64() (uint64, error) { - return uint64(cmd.val), cmd.err -} - -func (cmd *ExpectedIntCmd) String() string { - return cmdString(cmd.cmd, cmd.val) -} - -type ExpectedIntPointerSliceCmd struct { +type ExpectedIntPointerSlice struct { expectedBase val []*int64 } -func (cmd *ExpectedIntPointerSliceCmd) inflow(c redis.Cmder) { +func (cmd *ExpectedIntPointerSlice) inflow(c redis.Cmder) { inflow(c, "val", cmd.val) } -func (cmd *ExpectedIntPointerSliceCmd) String() string { +func (cmd *ExpectedIntPointerSlice) String() string { return cmdString(cmd.cmd, cmd.val) } -func (cmd *ExpectedIntPointerSliceCmd) SetVal(val []*int64) { +func (cmd *ExpectedIntPointerSlice) SetVal(val []*int64) { cmd.setVal = true cmd.val = val } -func (cmd *ExpectedIntPointerSliceCmd) Val() []*int64 { +func (cmd *ExpectedIntPointerSlice) Val() []*int64 { return cmd.val } -func (cmd *ExpectedIntPointerSliceCmd) Result() ([]*int64, error) { +func (cmd *ExpectedIntPointerSlice) Result() ([]*int64, error) { return cmd.val, cmd.err } -type ExpectedJSONCmd struct { +type ExpectedJSON struct { expectedBase val string expanded interface{} } -func (cmd *ExpectedJSONCmd) inflow(c redis.Cmder) { +func (cmd *ExpectedJSON) inflow(c redis.Cmder) { inflow(c, "val", cmd.val) } -func (cmd *ExpectedJSONCmd) String() string { +func (cmd *ExpectedJSON) String() string { return cmdString(cmd.cmd, cmd.val) } -func (cmd *ExpectedJSONCmd) SetVal(val string) { +func (cmd *ExpectedJSON) SetVal(val string) { cmd.setVal = true cmd.val = val } -func (cmd *ExpectedJSONCmd) Val() string { +func (cmd *ExpectedJSON) Val() string { if len(cmd.val) == 0 && cmd.expanded != nil { val, err := json.Marshal(cmd.expanded) if err != nil { @@ -188,11 +98,11 @@ func (cmd *ExpectedJSONCmd) Val() string { } } -func (cmd *ExpectedJSONCmd) Result() (string, error) { +func (cmd *ExpectedJSON) Result() (string, error) { return cmd.Val(), cmd.cmd.Err() } -func (cmd *ExpectedJSONCmd) Expanded() (interface{}, error) { +func (cmd *ExpectedJSON) Expanded() (interface{}, error) { if len(cmd.val) != 0 && cmd.expanded == nil { err := json.Unmarshal([]byte(cmd.val), &cmd.expanded) if err != nil { @@ -203,111 +113,33 @@ func (cmd *ExpectedJSONCmd) Expanded() (interface{}, error) { return cmd.expanded, nil } -type ExpectedJSONSliceCmd struct { +type ExpectedJSONSlice struct { expectedBase val []interface{} } -func (cmd *ExpectedJSONSliceCmd) inflow(c redis.Cmder) { - inflow(c, "val", cmd.val) -} - -func (cmd *ExpectedJSONSliceCmd) String() string { - return cmdString(cmd.cmd, cmd.val) -} - -func (cmd *ExpectedJSONSliceCmd) SetVal(val []interface{}) { - cmd.setVal = true - cmd.val = val -} - -func (cmd *ExpectedJSONSliceCmd) Val() []interface{} { - return cmd.val -} - -func (cmd *ExpectedJSONSliceCmd) Result() ([]interface{}, error) { - return cmd.val, cmd.err -} - -type ExpectedStringSliceCmd struct { - expectedBase - - val []string -} - -func (cmd *ExpectedStringSliceCmd) inflow(c redis.Cmder) { +func (cmd *ExpectedJSONSlice) inflow(c redis.Cmder) { inflow(c, "val", cmd.val) } -func (cmd *ExpectedStringSliceCmd) SetVal(val []string) { - cmd.setVal = true - cmd.val = val -} - -func (cmd *ExpectedStringSliceCmd) Val() []string { - return cmd.val -} - -func (cmd *ExpectedStringSliceCmd) Result() ([]string, error) { - return cmd.val, cmd.err -} - -func (cmd *ExpectedStringSliceCmd) String() string { +func (cmd *ExpectedJSONSlice) String() string { return cmdString(cmd.cmd, cmd.val) } -func (cmd *ExpectedStringSliceCmd) ScanSlice(container interface{}) error { - return ScanSlice(cmd.Val(), container) -} - -type ExpectedSliceCmd struct { - expectedBase - - val []interface{} -} - -func (cmd *ExpectedSliceCmd) inflow(c redis.Cmder) { - inflow(c, "val", cmd.val) -} - -func (cmd *ExpectedSliceCmd) SetVal(val []interface{}) { +func (cmd *ExpectedJSONSlice) SetVal(val []interface{}) { cmd.setVal = true cmd.val = val } -func (cmd *ExpectedSliceCmd) Val() []interface{} { +func (cmd *ExpectedJSONSlice) Val() []interface{} { return cmd.val } -func (cmd *ExpectedSliceCmd) Result() ([]interface{}, error) { +func (cmd *ExpectedJSONSlice) Result() ([]interface{}, error) { return cmd.val, cmd.err } -func (cmd *ExpectedSliceCmd) String() string { - return cmdString(cmd.cmd, cmd.val) -} - -// Scan scans the results from the map into a destination struct. The map keys -// are matched in the Redis struct fields by the `redis:"field"` tag. -func (cmd *ExpectedSliceCmd) Scan(dst interface{}) error { - if cmd.err != nil { - return cmd.err - } - - // Pass the list of keys and values. - // Skip the first two args for: HMGET key - var args []interface{} - if cmd.args()[0] == "hmget" { - args = cmd.args()[2:] - } else { - // Otherwise, it's: MGET field field ... - args = cmd.args()[1:] - } - - return HScan(dst, args, cmd.val) -} - type decoderFunc func(reflect.Value, string) error type structField struct { diff --git a/mock_ft.go b/mock_ft.go index 1e05551..ced544e 100644 --- a/mock_ft.go +++ b/mock_ft.go @@ -2,8 +2,8 @@ package redismock import "github.com/redis/go-redis/v9" -func (m *mock) ExpectFT_List() *ExpectedStringSliceCmd { - e := &ExpectedStringSliceCmd{} +func (m *mock) ExpectFT_List() *ExpectedStringSlice { + e := &ExpectedStringSlice{} e.cmd = m.factory.FT_List(m.ctx) m.pushExpect(e) return e @@ -23,29 +23,29 @@ func (m *mock) ExpectFTAggregateWithArgs(index string, query string, options *re return e } -func (m *mock) ExpectFTAliasAdd(index string, alias string) *ExpectedStatusCmd { - e := &ExpectedStatusCmd{} +func (m *mock) ExpectFTAliasAdd(index string, alias string) *ExpectedStatus { + e := &ExpectedStatus{} e.cmd = m.factory.FTAliasAdd(m.ctx, index, alias) m.pushExpect(e) return e } -func (m *mock) ExpectFTAliasDel(alias string) *ExpectedStatusCmd { - e := &ExpectedStatusCmd{} +func (m *mock) ExpectFTAliasDel(alias string) *ExpectedStatus { + e := &ExpectedStatus{} e.cmd = m.factory.FTAliasDel(m.ctx, alias) m.pushExpect(e) return e } -func (m *mock) ExpectFTAliasUpdate(index string, alias string) *ExpectedStatusCmd { - e := &ExpectedStatusCmd{} +func (m *mock) ExpectFTAliasUpdate(index string, alias string) *ExpectedStatus { + e := &ExpectedStatus{} e.cmd = m.factory.FTAliasUpdate(m.ctx, index, alias) m.pushExpect(e) return e } -func (m *mock) ExpectFTAlter(index string, skipInitialScan bool, definition []interface{}) *ExpectedStatusCmd { - e := &ExpectedStatusCmd{} +func (m *mock) ExpectFTAlter(index string, skipInitialScan bool, definition []interface{}) *ExpectedStatus { + e := &ExpectedStatus{} e.cmd = m.factory.FTAlter(m.ctx, index, skipInitialScan, definition) m.pushExpect(e) return e @@ -58,22 +58,22 @@ func (m *mock) ExpectFTConfigGet(option string) *ExpectedMapMapStringInterfaceCm return e } -func (m *mock) ExpectFTConfigSet(option string, value interface{}) *ExpectedStatusCmd { - e := &ExpectedStatusCmd{} +func (m *mock) ExpectFTConfigSet(option string, value interface{}) *ExpectedStatus { + e := &ExpectedStatus{} e.cmd = m.factory.FTConfigSet(m.ctx, option, value) m.pushExpect(e) return e } -func (m *mock) ExpectFTCreate(index string, options *redis.FTCreateOptions, schema ...*redis.FieldSchema) *ExpectedStatusCmd { - e := &ExpectedStatusCmd{} +func (m *mock) ExpectFTCreate(index string, options *redis.FTCreateOptions, schema ...*redis.FieldSchema) *ExpectedStatus { + e := &ExpectedStatus{} e.cmd = m.factory.FTCreate(m.ctx, index, options, schema...) m.pushExpect(e) return e } -func (m *mock) ExpectFTCursorDel(index string, cursorId int) *ExpectedStatusCmd { - e := &ExpectedStatusCmd{} +func (m *mock) ExpectFTCursorDel(index string, cursorId int) *ExpectedStatus { + e := &ExpectedStatus{} e.cmd = m.factory.FTCursorDel(m.ctx, index, cursorId) m.pushExpect(e) return e @@ -86,36 +86,36 @@ func (m *mock) ExpectFTCursorRead(index string, cursorId int, count int) *Expect return e } -func (m *mock) ExpectFTDictAdd(dict string, term ...interface{}) *ExpectedIntCmd { - e := &ExpectedIntCmd{} +func (m *mock) ExpectFTDictAdd(dict string, term ...interface{}) *ExpectedInt { + e := &ExpectedInt{} e.cmd = m.factory.FTDictAdd(m.ctx, dict, term) m.pushExpect(e) return e } -func (m *mock) ExpectFTDictDel(dict string, term ...interface{}) *ExpectedIntCmd { - e := &ExpectedIntCmd{} +func (m *mock) ExpectFTDictDel(dict string, term ...interface{}) *ExpectedInt { + e := &ExpectedInt{} e.cmd = m.factory.FTDictDel(m.ctx, dict, term) m.pushExpect(e) return e } -func (m *mock) ExpectFTDictDump(dict string) *ExpectedStringSliceCmd { - e := &ExpectedStringSliceCmd{} +func (m *mock) ExpectFTDictDump(dict string) *ExpectedStringSlice { + e := &ExpectedStringSlice{} e.cmd = m.factory.FTDictDump(m.ctx, dict) m.pushExpect(e) return e } -func (m *mock) ExpectFTDropIndex(index string) *ExpectedStatusCmd { - e := &ExpectedStatusCmd{} +func (m *mock) ExpectFTDropIndex(index string) *ExpectedStatus { + e := &ExpectedStatus{} e.cmd = m.factory.FTDropIndex(m.ctx, index) m.pushExpect(e) return e } -func (m *mock) ExpectFTDropIndexWithArgs(index string, options *redis.FTDropIndexOptions) *ExpectedStatusCmd { - e := &ExpectedStatusCmd{} +func (m *mock) ExpectFTDropIndexWithArgs(index string, options *redis.FTDropIndexOptions) *ExpectedStatus { + e := &ExpectedStatus{} e.cmd = m.factory.FTDropIndexWithArgs(m.ctx, index, options) m.pushExpect(e) return e @@ -177,22 +177,22 @@ func (m *mock) ExpectFTSynDump(index string) *ExpectedFTSynDumpCmd { return e } -func (m *mock) ExpectFTSynUpdate(index string, synGroupId interface{}, terms []interface{}) *ExpectedStatusCmd { - e := &ExpectedStatusCmd{} +func (m *mock) ExpectFTSynUpdate(index string, synGroupId interface{}, terms []interface{}) *ExpectedStatus { + e := &ExpectedStatus{} e.cmd = m.factory.FTSynUpdate(m.ctx, index, synGroupId, terms) m.pushExpect(e) return e } -func (m *mock) ExpectFTSynUpdateWithArgs(index string, synGroupId interface{}, options *redis.FTSynUpdateOptions, terms []interface{}) *ExpectedStatusCmd { - e := &ExpectedStatusCmd{} +func (m *mock) ExpectFTSynUpdateWithArgs(index string, synGroupId interface{}, options *redis.FTSynUpdateOptions, terms []interface{}) *ExpectedStatus { + e := &ExpectedStatus{} e.cmd = m.factory.FTSynUpdateWithArgs(m.ctx, index, synGroupId, options, terms) m.pushExpect(e) return e } -func (m *mock) ExpectFTTagVals(index string, field string) *ExpectedStringSliceCmd { - e := &ExpectedStringSliceCmd{} +func (m *mock) ExpectFTTagVals(index string, field string) *ExpectedStringSlice { + e := &ExpectedStringSlice{} e.cmd = m.factory.FTTagVals(m.ctx, index, field) m.pushExpect(e) return e diff --git a/mock_json.go b/mock_json.go index c5247dc..9d9ef82 100644 --- a/mock_json.go +++ b/mock_json.go @@ -2,190 +2,190 @@ package redismock import "github.com/redis/go-redis/v9" -func (m *mock) ExpectJSONArrAppend(key, path string, values ...interface{}) *ExpectedIntSliceCmd { - e := &ExpectedIntSliceCmd{} - e.cmd = m.factory.JSONArrAppend(m.ctx, key, path, values) +func (m *mock) ExpectJSONArrAppend(key, path string, values ...interface{}) *ExpectedIntSlice { + e := &ExpectedIntSlice{} + e.cmd = m.factory.JSONArrAppend(m.ctx, key, path, values...) m.pushExpect(e) return e } -func (m *mock) ExpectJSONArrIndex(key, path string, value ...interface{}) *ExpectedIntSliceCmd { - e := &ExpectedIntSliceCmd{} - e.cmd = m.factory.JSONArrIndex(m.ctx, key, path, value) +func (m *mock) ExpectJSONArrIndex(key, path string, value ...interface{}) *ExpectedIntSlice { + e := &ExpectedIntSlice{} + e.cmd = m.factory.JSONArrIndex(m.ctx, key, path, value...) m.pushExpect(e) return e } -func (m *mock) ExpectJSONArrIndexWithArgs(key, path string, options *redis.JSONArrIndexArgs, value ...interface{}) *ExpectedIntSliceCmd { - e := &ExpectedIntSliceCmd{} - e.cmd = m.factory.JSONArrIndexWithArgs(m.ctx, key, path, options, value) +func (m *mock) ExpectJSONArrIndexWithArgs(key, path string, options *redis.JSONArrIndexArgs, value ...interface{}) *ExpectedIntSlice { + e := &ExpectedIntSlice{} + e.cmd = m.factory.JSONArrIndexWithArgs(m.ctx, key, path, options, value...) m.pushExpect(e) return e } -func (m *mock) ExpectJSONArrInsert(key, path string, index int64, values ...interface{}) *ExpectedIntSliceCmd { - e := &ExpectedIntSliceCmd{} - e.cmd = m.factory.JSONArrInsert(m.ctx, key, path, index, values) +func (m *mock) ExpectJSONArrInsert(key, path string, index int64, values ...interface{}) *ExpectedIntSlice { + e := &ExpectedIntSlice{} + e.cmd = m.factory.JSONArrInsert(m.ctx, key, path, index, values...) m.pushExpect(e) return e } -func (m *mock) ExpectJSONArrLen(key, path string) *ExpectedIntSliceCmd { - e := &ExpectedIntSliceCmd{} +func (m *mock) ExpectJSONArrLen(key, path string) *ExpectedIntSlice { + e := &ExpectedIntSlice{} e.cmd = m.factory.JSONArrLen(m.ctx, key, path) m.pushExpect(e) return e } -func (m *mock) ExpectJSONArrPop(key, path string, index int) *ExpectedStringSliceCmd { - e := &ExpectedStringSliceCmd{} +func (m *mock) ExpectJSONArrPop(key, path string, index int) *ExpectedStringSlice { + e := &ExpectedStringSlice{} e.cmd = m.factory.JSONArrPop(m.ctx, key, path, index) m.pushExpect(e) return e } -func (m *mock) ExpectJSONArrTrim(key, path string) *ExpectedIntSliceCmd { - e := &ExpectedIntSliceCmd{} +func (m *mock) ExpectJSONArrTrim(key, path string) *ExpectedIntSlice { + e := &ExpectedIntSlice{} e.cmd = m.factory.JSONArrTrim(m.ctx, key, path) m.pushExpect(e) return e } -func (m *mock) ExpectJSONArrTrimWithArgs(key, path string, options *redis.JSONArrTrimArgs) *ExpectedIntSliceCmd { - e := &ExpectedIntSliceCmd{} +func (m *mock) ExpectJSONArrTrimWithArgs(key, path string, options *redis.JSONArrTrimArgs) *ExpectedIntSlice { + e := &ExpectedIntSlice{} e.cmd = m.factory.JSONArrTrimWithArgs(m.ctx, key, path, options) m.pushExpect(e) return e } -func (m *mock) ExpectJSONClear(key, path string) *ExpectedIntCmd { - e := &ExpectedIntCmd{} +func (m *mock) ExpectJSONClear(key, path string) *ExpectedInt { + e := &ExpectedInt{} e.cmd = m.factory.JSONClear(m.ctx, key, path) m.pushExpect(e) return e } -func (m *mock) ExpectJSONDebugMemory(key, path string) *ExpectedIntCmd { - e := &ExpectedIntCmd{} +func (m *mock) ExpectJSONDebugMemory(key, path string) *ExpectedInt { + e := &ExpectedInt{} e.cmd = m.factory.JSONDebugMemory(m.ctx, key, path) m.pushExpect(e) return e } -func (m *mock) ExpectJSONDel(key, path string) *ExpectedIntCmd { - e := &ExpectedIntCmd{} +func (m *mock) ExpectJSONDel(key, path string) *ExpectedInt { + e := &ExpectedInt{} e.cmd = m.factory.JSONDel(m.ctx, key, path) m.pushExpect(e) return e } -func (m *mock) ExpectJSONForget(key, path string) *ExpectedIntCmd { - e := &ExpectedIntCmd{} +func (m *mock) ExpectJSONForget(key, path string) *ExpectedInt { + e := &ExpectedInt{} e.cmd = m.factory.JSONForget(m.ctx, key, path) m.pushExpect(e) return e } -func (m *mock) ExpectJSONGet(key string, paths ...string) *ExpectedJSONCmd { - e := &ExpectedJSONCmd{} +func (m *mock) ExpectJSONGet(key string, paths ...string) *ExpectedJSON { + e := &ExpectedJSON{} e.cmd = m.factory.JSONGet(m.ctx, key, paths...) m.pushExpect(e) return e } -func (m *mock) ExpectJSONGetWithArgs(key string, options *redis.JSONGetArgs, paths ...string) *ExpectedJSONCmd { - e := &ExpectedJSONCmd{} +func (m *mock) ExpectJSONGetWithArgs(key string, options *redis.JSONGetArgs, paths ...string) *ExpectedJSON { + e := &ExpectedJSON{} e.cmd = m.factory.JSONGetWithArgs(m.ctx, key, options, paths...) m.pushExpect(e) return e } -func (m *mock) ExpectJSONMerge(key, path string, value string) *ExpectedStatusCmd { - e := &ExpectedStatusCmd{} +func (m *mock) ExpectJSONMerge(key, path string, value string) *ExpectedStatus { + e := &ExpectedStatus{} e.cmd = m.factory.JSONMerge(m.ctx, key, path, value) m.pushExpect(e) return e } -func (m *mock) ExpectJSONMSetArgs(docs []redis.JSONSetArgs) *ExpectedStatusCmd { - e := &ExpectedStatusCmd{} +func (m *mock) ExpectJSONMSetArgs(docs []redis.JSONSetArgs) *ExpectedStatus { + e := &ExpectedStatus{} e.cmd = m.factory.JSONMSetArgs(m.ctx, docs) m.pushExpect(e) return e } -func (m *mock) ExpectJSONMSet(params ...interface{}) *ExpectedStatusCmd { - e := &ExpectedStatusCmd{} +func (m *mock) ExpectJSONMSet(params ...interface{}) *ExpectedStatus { + e := &ExpectedStatus{} e.cmd = m.factory.JSONMSet(m.ctx, params...) m.pushExpect(e) return e } -func (m *mock) ExpectJSONMGet(path string, keys ...string) *ExpectedJSONSliceCmd { - e := &ExpectedJSONSliceCmd{} +func (m *mock) ExpectJSONMGet(path string, keys ...string) *ExpectedJSONSlice { + e := &ExpectedJSONSlice{} e.cmd = m.factory.JSONMGet(m.ctx, path, keys...) m.pushExpect(e) return e } -func (m *mock) ExpectJSONNumIncrBy(key, path string, value float64) *ExpectedJSONCmd { - e := &ExpectedJSONCmd{} +func (m *mock) ExpectJSONNumIncrBy(key, path string, value float64) *ExpectedJSON { + e := &ExpectedJSON{} e.cmd = m.factory.JSONNumIncrBy(m.ctx, key, path, value) m.pushExpect(e) return e } -func (m *mock) ExpectJSONObjKeys(key, path string) *ExpectedSliceCmd { - e := &ExpectedSliceCmd{} +func (m *mock) ExpectJSONObjKeys(key, path string) *ExpectedSlice { + e := &ExpectedSlice{} e.cmd = m.factory.JSONObjKeys(m.ctx, key, path) m.pushExpect(e) return e } -func (m *mock) ExpectJSONObjLen(key, path string) *ExpectedIntPointerSliceCmd { - e := &ExpectedIntPointerSliceCmd{} +func (m *mock) ExpectJSONObjLen(key, path string) *ExpectedIntPointerSlice { + e := &ExpectedIntPointerSlice{} e.cmd = m.factory.JSONObjLen(m.ctx, key, path) m.pushExpect(e) return e } -func (m *mock) ExpectJSONSet(key, path string, value interface{}) *ExpectedStatusCmd { - e := &ExpectedStatusCmd{} +func (m *mock) ExpectJSONSet(key, path string, value interface{}) *ExpectedStatus { + e := &ExpectedStatus{} e.cmd = m.factory.JSONSet(m.ctx, key, path, value) m.pushExpect(e) return e } -func (m *mock) ExpectJSONSetMode(key, path string, value interface{}, mode string) *ExpectedStatusCmd { - e := &ExpectedStatusCmd{} +func (m *mock) ExpectJSONSetMode(key, path string, value interface{}, mode string) *ExpectedStatus { + e := &ExpectedStatus{} e.cmd = m.factory.JSONSetMode(m.ctx, key, path, value, mode) m.pushExpect(e) return e } -func (m *mock) ExpectJSONStrAppend(key, path, value string) *ExpectedIntPointerSliceCmd { - e := &ExpectedIntPointerSliceCmd{} +func (m *mock) ExpectJSONStrAppend(key, path, value string) *ExpectedIntPointerSlice { + e := &ExpectedIntPointerSlice{} e.cmd = m.factory.JSONStrAppend(m.ctx, key, path, value) m.pushExpect(e) return e } -func (m *mock) ExpectJSONStrLen(key, path string) *ExpectedIntPointerSliceCmd { - e := &ExpectedIntPointerSliceCmd{} +func (m *mock) ExpectJSONStrLen(key, path string) *ExpectedIntPointerSlice { + e := &ExpectedIntPointerSlice{} e.cmd = m.factory.JSONStrLen(m.ctx, key, path) m.pushExpect(e) return e } -func (m *mock) ExpectJSONToggle(key, path string) *ExpectedIntPointerSliceCmd { - e := &ExpectedIntPointerSliceCmd{} +func (m *mock) ExpectJSONToggle(key, path string) *ExpectedIntPointerSlice { + e := &ExpectedIntPointerSlice{} e.cmd = m.factory.JSONToggle(m.ctx, key, path) m.pushExpect(e) return e } -func (m *mock) ExpectJSONType(key, path string) *ExpectedJSONSliceCmd { - e := &ExpectedJSONSliceCmd{} +func (m *mock) ExpectJSONType(key, path string) *ExpectedJSONSlice { + e := &ExpectedJSONSlice{} e.cmd = m.factory.JSONType(m.ctx, key, path) m.pushExpect(e) return e diff --git a/mock_test.go b/mock_test.go index 0be8c7d..e8470ec 100644 --- a/mock_test.go +++ b/mock_test.go @@ -312,6 +312,32 @@ func operationIntSliceCmd(base baseMock, expected func() *ExpectedIntSlice, actu Expect(val).To(Equal([]int64{1, 2, 3, 4})) } +func operationIntPointerSliceCmd(base baseMock, expected func() *ExpectedIntPointerSlice, actual func() *redis.IntPointerSliceCmd) { + var ( + setErr = errors.New("int slice cmd error") + val []*int64 + err error + ) + + base.ClearExpect() + expected().SetErr(setErr) + val, err = actual().Result() + Expect(err).To(Equal(setErr)) + Expect(val).To(Equal([]*int64(nil))) + + base.ClearExpect() + expected() + val, err = actual().Result() + Expect(err).To(HaveOccurred()) + Expect(val).To(Equal([]*int64(nil))) + + base.ClearExpect() + expected().SetVal([]*int64{}) + val, err = actual().Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal([]*int64{})) +} + func operationScanCmd(base baseMock, expected func() *ExpectedScan, actual func() *redis.ScanCmd) { var ( setErr = errors.New("scan cmd error") @@ -1785,3 +1811,56 @@ func operationMapStringSliceInterfaceCmd(base baseMock, expected func() *Expecte "key2": {"val2", 2}, })) } + +func operationJSONCmd(base baseMock, expected func() *ExpectedJSON, actual func() *redis.JSONCmd) { + var ( + setErr = errors.New("JSON cmd error") + val string + err error + ) + + base.ClearExpect() + expected().SetErr(setErr) + val, err = actual().Result() + Expect(err).To(Equal(setErr)) + Expect(val).To(Equal("")) + + base.ClearExpect() + expected() + val, err = actual().Result() + Expect(err).To(HaveOccurred()) + Expect(val).To(Equal("")) + + base.ClearExpect() + expected().SetVal("test") + val, err = actual().Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal("test")) +} + +func operationJSONSliceCmd(base baseMock, expected func() *ExpectedJSONSlice, actual func() *redis.JSONSliceCmd) { + // func operationStringSliceCmd(base baseMock, expected func() *ExpectedStringSlice, actual func() *redis.StringSliceCmd) { + var ( + setErr = errors.New("string slice cmd error") + val []interface{} + err error + ) + + base.ClearExpect() + expected().SetErr(setErr) + val, err = actual().Result() + Expect(err).To(Equal(setErr)) + Expect(val).To(Equal([]interface{}(nil))) + + base.ClearExpect() + expected() + val, err = actual().Result() + Expect(err).To(HaveOccurred()) + Expect(val).To(Equal([]interface{}(nil))) + + base.ClearExpect() + expected().SetVal([]interface{}{"redis", "mock"}) + val, err = actual().Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal([]interface{}{"redis", "mock"})) +} From e6d683833b6209260f034373a77a879f7df72b97 Mon Sep 17 00:00:00 2001 From: Ryan Matthews Date: Wed, 8 Oct 2025 16:59:05 -0700 Subject: [PATCH 6/8] chore: Add FT tests --- commands_ft_test.go | 254 +++++++++++++++++++++++++++ commands_json_test.go | 11 +- expect.go | 24 +-- expect_ft.go | 198 ++++++--------------- expect_json.go | 111 ------------ helper.go | 389 ------------------------------------------ mock_ft.go | 52 +++--- mock_ft_test.go | 139 +++++++++++++++ mock_json_test.go | 60 +++++++ mock_test.go | 85 ++++----- 10 files changed, 578 insertions(+), 745 deletions(-) create mode 100644 commands_ft_test.go create mode 100644 mock_ft_test.go create mode 100644 mock_json_test.go diff --git a/commands_ft_test.go b/commands_ft_test.go new file mode 100644 index 0000000..c81bd19 --- /dev/null +++ b/commands_ft_test.go @@ -0,0 +1,254 @@ +package redismock + +import ( + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "github.com/redis/go-redis/v9" +) + +var _ = Describe("FTCommands", func() { + var ( + clientMock baseMock + client mockCmdable + ) + + callCommandTest := func() { + It("ExpectFT_List", func() { + operationStringSliceCmd(clientMock, func() *ExpectedStringSlice { + return clientMock.ExpectFT_List() + }, func() *redis.StringSliceCmd { + return client.FT_List(ctx) + }) + }) + + It("ExpectFTAggregate", func() { + operationMapStringInterfaceCmd(clientMock, func() *ExpectedMapStringInterface { + return clientMock.ExpectFTAggregate("index", "query") + }, func() *redis.MapStringInterfaceCmd { + return client.FTAggregate(ctx, "index", "query") + }) + }) + + // ExpectFTAggregateWithArgs(index string, query string, options *redis.FTAggregateOptions) *ExpectedAggregate + It("ExpectFTAggregateWithArgs", func() { + operationAggregateCmd(clientMock, func() *ExpectedAggregate { + return clientMock.ExpectFTAggregateWithArgs("index", "query", &redis.FTAggregateOptions{}) + }, func() *redis.AggregateCmd { + return client.FTAggregateWithArgs(ctx, "index", "query", &redis.FTAggregateOptions{}) + }) + }) + + It("ExpectFTAliasAdd", func() { + operationStatusCmd(clientMock, func() *ExpectedStatus { + return clientMock.ExpectFTAliasAdd("index", "alias") + }, func() *redis.StatusCmd { + return client.FTAliasAdd(ctx, "index", "alias") + }) + }) + + It("ExpectFTAliasDel", func() { + operationStatusCmd(clientMock, func() *ExpectedStatus { + return clientMock.ExpectFTAliasDel("alias") + }, func() *redis.StatusCmd { + return client.FTAliasDel(ctx, "alias") + }) + }) + + It("ExpectFTAliasUpdate", func() { + operationStatusCmd(clientMock, func() *ExpectedStatus { + return clientMock.ExpectFTAliasUpdate("index", "alias") + }, func() *redis.StatusCmd { + return client.FTAliasUpdate(ctx, "index", "alias") + }) + }) + + It("ExpectFTAlter", func() { + operationStatusCmd(clientMock, func() *ExpectedStatus { + return clientMock.ExpectFTAlter("index", false, []interface{}(nil)) + }, func() *redis.StatusCmd { + return client.FTAlter(ctx, "index", false, []interface{}(nil)) + }) + }) + + It("ExpectFTConfigGet", func() { + operationMapMapStringInterfaceCmd(clientMock, func() *ExpectedMapMapStringInterface { + return clientMock.ExpectFTConfigGet("option") + }, func() *redis.MapMapStringInterfaceCmd { + return client.FTConfigGet(ctx, "option") + }) + }) + + It("ExpectFTConfigSet", func() { + operationStatusCmd(clientMock, func() *ExpectedStatus { + return clientMock.ExpectFTConfigSet("option", interface{}(nil)) + }, func() *redis.StatusCmd { + return client.FTConfigSet(ctx, "option", interface{}(nil)) + }) + }) + + It("ExpectFTCreate", func() { + operationStatusCmd(clientMock, func() *ExpectedStatus { + return clientMock.ExpectFTCreate("index", &redis.FTCreateOptions{}, &redis.FieldSchema{FieldName: "test", FieldType: redis.SearchFieldTypeGeo}) + }, func() *redis.StatusCmd { + return client.FTCreate(ctx, "index", &redis.FTCreateOptions{}, &redis.FieldSchema{FieldName: "test", FieldType: redis.SearchFieldTypeGeo}) + }) + }) + + It("ExpectFTCursorDel", func() { + operationStatusCmd(clientMock, func() *ExpectedStatus { + return clientMock.ExpectFTCursorDel("index", 0) + }, func() *redis.StatusCmd { + return client.FTCursorDel(ctx, "index", 0) + }) + }) + + It("ExpectFTCursorRead", func() { + operationMapStringInterfaceCmd(clientMock, func() *ExpectedMapStringInterface { + return clientMock.ExpectFTCursorRead("index", 0, 1) + }, func() *redis.MapStringInterfaceCmd { + return client.FTCursorRead(ctx, "index", 0, 1) + }) + }) + + It("ExpectFTDictAdd", func() { + operationIntCmd(clientMock, func() *ExpectedInt { + return clientMock.ExpectFTDictAdd("dict", "term") + }, func() *redis.IntCmd { + return client.FTDictAdd(ctx, "dict", "term") + }) + }) + + It("ExpectFTDictDel", func() { + operationIntCmd(clientMock, func() *ExpectedInt { + return clientMock.ExpectFTDictDel("dict", "term") + }, func() *redis.IntCmd { + return client.FTDictDel(ctx, "dict", "term") + }) + }) + + It("ExpectFTDictDump", func() { + operationStringSliceCmd(clientMock, func() *ExpectedStringSlice { + return clientMock.ExpectFTDictDump("dict") + }, func() *redis.StringSliceCmd { + return client.FTDictDump(ctx, "dict") + }) + }) + + It("ExpectFTDropIndex", func() { + operationStatusCmd(clientMock, func() *ExpectedStatus { + return clientMock.ExpectFTDropIndex("index") + }, func() *redis.StatusCmd { + return client.FTDropIndex(ctx, "index") + }) + }) + + It("ExpectFTDropIndexWithArgs", func() { + operationStatusCmd(clientMock, func() *ExpectedStatus { + return clientMock.ExpectFTDropIndexWithArgs("index", &redis.FTDropIndexOptions{}) + }, func() *redis.StatusCmd { + return client.FTDropIndexWithArgs(ctx, "index", &redis.FTDropIndexOptions{}) + }) + }) + + It("ExpectFTExplain", func() { + operationStringCmd(clientMock, func() *ExpectedString { + return clientMock.ExpectFTExplain("key", "query") + }, func() *redis.StringCmd { + return client.FTExplain(ctx, "key", "query") + }) + }) + + It("ExpectFTExplainWithArgs", func() { + operationStringCmd(clientMock, func() *ExpectedString { + return clientMock.ExpectFTExplainWithArgs("key", "query", &redis.FTExplainOptions{}) + }, func() *redis.StringCmd { + return client.FTExplainWithArgs(ctx, "key", "query", &redis.FTExplainOptions{}) + }) + }) + + It("ExpectFTInfo", func() { + operationFTInfoCmd(clientMock, func() *ExpectedFTInfo { + return clientMock.ExpectFTInfo("index") + }, func() *redis.FTInfoCmd { + return client.FTInfo(ctx, "index") + }) + }) + + It("ExpectFTSpellCheck", func() { + operationFTSpellCheckCmd(clientMock, func() *ExpectedFTSpellCheck { + return clientMock.ExpectFTSpellCheck("index", "query") + }, func() *redis.FTSpellCheckCmd { + return client.FTSpellCheck(ctx, "index", "query") + }) + }) + + It("ExpectFTSpellCheckWithArgs", func() { + operationFTSpellCheckCmd(clientMock, func() *ExpectedFTSpellCheck { + return clientMock.ExpectFTSpellCheckWithArgs("index", "query", &redis.FTSpellCheckOptions{}) + }, func() *redis.FTSpellCheckCmd { + return client.FTSpellCheckWithArgs(ctx, "index", "query", &redis.FTSpellCheckOptions{}) + }) + }) + + It("ExpectFTSearch", func() { + operationFTSearchCmd(clientMock, func() *ExpectedFTSearch { + return clientMock.ExpectFTSearch("index", "query") + }, func() *redis.FTSearchCmd { + return client.FTSearch(ctx, "index", "query") + }) + }) + + It("ExpectFTSearchWithArgs", func() { + operationFTSearchCmd(clientMock, func() *ExpectedFTSearch { + return clientMock.ExpectFTSearchWithArgs("index", "query", &redis.FTSearchOptions{}) + }, func() *redis.FTSearchCmd { + return client.FTSearchWithArgs(ctx, "index", "query", &redis.FTSearchOptions{}) + }) + }) + + It("ExpectFTSynDump", func() { + operationFTSynDumpCmd(clientMock, func() *ExpectedFTSynDump { + return clientMock.ExpectFTSynDump("index") + }, func() *redis.FTSynDumpCmd { + return client.FTSynDump(ctx, "index") + }) + }) + + It("ExpectFTSynUpdate", func() { + operationStatusCmd(clientMock, func() *ExpectedStatus { + return clientMock.ExpectFTSynUpdate("index", interface{}(nil), []interface{}{}) + }, func() *redis.StatusCmd { + return client.FTSynUpdate(ctx, "index", interface{}(nil), []interface{}{}) + }) + }) + + It("ExpectFTSynUpdateWithArgs", func() { + operationStatusCmd(clientMock, func() *ExpectedStatus { + return clientMock.ExpectFTSynUpdateWithArgs("index", interface{}(nil), &redis.FTSynUpdateOptions{}, []interface{}{}) + }, func() *redis.StatusCmd { + return client.FTSynUpdateWithArgs(ctx, "index", interface{}(nil), &redis.FTSynUpdateOptions{}, []interface{}{}) + }) + }) + + It("ExpectFTTagVals", func() { + operationStringSliceCmd(clientMock, func() *ExpectedStringSlice { + return clientMock.ExpectFTTagVals("index", "field") + }, func() *redis.StringSliceCmd { + return client.FTTagVals(ctx, "index", "field") + }) + }) + } + + Describe("client", func() { + BeforeEach(func() { + client, clientMock = NewClientMock() + }) + + AfterEach(func() { + Expect(client.(*redis.Client).Close()).NotTo(HaveOccurred()) + Expect(clientMock.ExpectationsWereMet()).NotTo(HaveOccurred()) + }) + + callCommandTest() + }) +}) diff --git a/commands_json_test.go b/commands_json_test.go index 92ed57b..ef73b27 100644 --- a/commands_json_test.go +++ b/commands_json_test.go @@ -85,14 +85,9 @@ var _ = Describe("JSONCommands", func() { }) }) - // This is a panic in the go-redis library - // It("ExpectJSONDebugMemory", func() { - // operationIntCmd(clientMock, func() *ExpectedInt { - // return clientMock.ExpectJSONDebugMemory("key", "path") - // }, func() *redis.IntCmd { - // return client.JSONDebugMemory(ctx, "key", "path") - // }) - // }) + It("ExpectJSONDebugMemory", func() { + Skip("ExpectJSONDebugMemory just panics") + }) It("ExpectJSONDel", func() { operationIntCmd(clientMock, func() *ExpectedInt { diff --git a/expect.go b/expect.go index 2c98c67..b6a3c9d 100644 --- a/expect.go +++ b/expect.go @@ -424,30 +424,30 @@ type baseMock interface { ExpectJSONType(key, path string) *ExpectedJSONSlice ExpectFT_List() *ExpectedStringSlice - ExpectFTAggregate(index string, query string) *ExpectedMapStringInterfaceCmd - ExpectFTAggregateWithArgs(index string, query string, options *redis.FTAggregateOptions) *ExpectedAggregateCmd + ExpectFTAggregate(index string, query string) *ExpectedMapStringInterface + ExpectFTAggregateWithArgs(index string, query string, options *redis.FTAggregateOptions) *ExpectedAggregate ExpectFTAliasAdd(index string, alias string) *ExpectedStatus ExpectFTAliasDel(alias string) *ExpectedStatus ExpectFTAliasUpdate(index string, alias string) *ExpectedStatus ExpectFTAlter(index string, skipInitialScan bool, definition []interface{}) *ExpectedStatus - ExpectFTConfigGet(option string) *ExpectedMapMapStringInterfaceCmd + ExpectFTConfigGet(option string) *ExpectedMapMapStringInterface ExpectFTConfigSet(option string, value interface{}) *ExpectedStatus ExpectFTCreate(index string, options *redis.FTCreateOptions, schema ...*redis.FieldSchema) *ExpectedStatus ExpectFTCursorDel(index string, cursorId int) *ExpectedStatus - ExpectFTCursorRead(index string, cursorId int, count int) *ExpectedMapStringInterfaceCmd + ExpectFTCursorRead(index string, cursorId int, count int) *ExpectedMapStringInterface ExpectFTDictAdd(dict string, term ...interface{}) *ExpectedInt ExpectFTDictDel(dict string, term ...interface{}) *ExpectedInt ExpectFTDictDump(dict string) *ExpectedStringSlice ExpectFTDropIndex(index string) *ExpectedStatus ExpectFTDropIndexWithArgs(index string, options *redis.FTDropIndexOptions) *ExpectedStatus - ExpectFTExplain(index string, query string) *ExpectedStringCmd - ExpectFTExplainWithArgs(index string, query string, options *redis.FTExplainOptions) *ExpectedStringCmd - ExpectFTInfo(index string) *ExpectedFTInfoCmd - ExpectFTSpellCheck(index string, query string) *ExpectedFTSpellCheckCmd - ExpectFTSpellCheckWithArgs(index string, query string, options *redis.FTSpellCheckOptions) *ExpectedFTSpellCheckCmd - ExpectFTSearch(index string, query string) *ExpectedFTSearchCmd - ExpectFTSearchWithArgs(index string, query string, options *redis.FTSearchOptions) *ExpectedFTSearchCmd - ExpectFTSynDump(index string) *ExpectedFTSynDumpCmd + ExpectFTExplain(index string, query string) *ExpectedString + ExpectFTExplainWithArgs(index string, query string, options *redis.FTExplainOptions) *ExpectedString + ExpectFTInfo(index string) *ExpectedFTInfo + ExpectFTSpellCheck(index string, query string) *ExpectedFTSpellCheck + ExpectFTSpellCheckWithArgs(index string, query string, options *redis.FTSpellCheckOptions) *ExpectedFTSpellCheck + ExpectFTSearch(index string, query string) *ExpectedFTSearch + ExpectFTSearchWithArgs(index string, query string, options *redis.FTSearchOptions) *ExpectedFTSearch + ExpectFTSynDump(index string) *ExpectedFTSynDump ExpectFTSynUpdate(index string, synGroupId interface{}, terms []interface{}) *ExpectedStatus ExpectFTSynUpdateWithArgs(index string, synGroupId interface{}, options *redis.FTSynUpdateOptions, terms []interface{}) *ExpectedStatus ExpectFTTagVals(index string, field string) *ExpectedStringSlice diff --git a/expect_ft.go b/expect_ft.go index 08a4835..162c860 100644 --- a/expect_ft.go +++ b/expect_ft.go @@ -1,301 +1,207 @@ package redismock import ( - "strconv" - "time" - "github.com/redis/go-redis/v9" ) -type ExpectedFTSearchCmd struct { +type ExpectedMapMapStringInterface struct { expectedBase - val redis.FTSearchResult + val map[string]interface{} } -func (cmd *ExpectedFTSearchCmd) inflow(c redis.Cmder) { +func (cmd *ExpectedMapMapStringInterface) inflow(c redis.Cmder) { inflow(c, "val", cmd.val) } -func (cmd *ExpectedFTSearchCmd) String() string { +func (cmd *ExpectedMapMapStringInterface) String() string { return cmdString(cmd.cmd, cmd.val) } -func (cmd *ExpectedFTSearchCmd) SetVal(val redis.FTSearchResult) { +func (cmd *ExpectedMapMapStringInterface) SetVal(val map[string]interface{}) { cmd.setVal = true cmd.val = val } -func (cmd *ExpectedFTSearchCmd) Result() (redis.FTSearchResult, error) { +func (cmd *ExpectedMapMapStringInterface) Result() (map[string]interface{}, error) { return cmd.val, cmd.err } -func (cmd *ExpectedFTSearchCmd) Val() redis.FTSearchResult { +func (cmd *ExpectedMapMapStringInterface) Val() map[string]interface{} { return cmd.val } -func (cmd *ExpectedFTSearchCmd) RawVal() interface{} { - return cmd.rawVal -} - -func (cmd *ExpectedFTSearchCmd) RawResult() (interface{}, error) { - return cmd.rawVal, cmd.err -} - -type ExpectedFTSpellCheckCmd struct { +type ExpectedFTSearch struct { expectedBase - val []redis.SpellCheckResult + val redis.FTSearchResult } -func (cmd *ExpectedFTSpellCheckCmd) inflow(c redis.Cmder) { +func (cmd *ExpectedFTSearch) inflow(c redis.Cmder) { inflow(c, "val", cmd.val) } -func (cmd *ExpectedFTSpellCheckCmd) String() string { +func (cmd *ExpectedFTSearch) String() string { return cmdString(cmd.cmd, cmd.val) } -func (cmd *ExpectedFTSpellCheckCmd) SetVal(val []redis.SpellCheckResult) { +func (cmd *ExpectedFTSearch) SetVal(val redis.FTSearchResult) { cmd.setVal = true cmd.val = val } -func (cmd *ExpectedFTSpellCheckCmd) Result() ([]redis.SpellCheckResult, error) { +func (cmd *ExpectedFTSearch) Result() (redis.FTSearchResult, error) { return cmd.val, cmd.err } -func (cmd *ExpectedFTSpellCheckCmd) Val() []redis.SpellCheckResult { +func (cmd *ExpectedFTSearch) Val() redis.FTSearchResult { return cmd.val } -func (cmd *ExpectedFTSpellCheckCmd) RawVal() interface{} { +func (cmd *ExpectedFTSearch) RawVal() interface{} { return cmd.rawVal } -func (cmd *ExpectedFTSpellCheckCmd) RawResult() (interface{}, error) { +func (cmd *ExpectedFTSearch) RawResult() (interface{}, error) { return cmd.rawVal, cmd.err } -type ExpectedMapStringInterfaceCmd struct { +type ExpectedFTSpellCheck struct { expectedBase - val map[string]interface{} + val []redis.SpellCheckResult } -func (cmd *ExpectedMapStringInterfaceCmd) inflow(c redis.Cmder) { +func (cmd *ExpectedFTSpellCheck) inflow(c redis.Cmder) { inflow(c, "val", cmd.val) } -func (cmd *ExpectedMapStringInterfaceCmd) SetVal(val map[string]interface{}) { - cmd.setVal = true - cmd.val = val -} - -func (cmd *ExpectedMapStringInterfaceCmd) Val() map[string]interface{} { - return cmd.val -} - -func (cmd *ExpectedMapStringInterfaceCmd) Result() (map[string]interface{}, error) { - return cmd.val, cmd.err -} - -func (cmd *ExpectedMapStringInterfaceCmd) String() string { +func (cmd *ExpectedFTSpellCheck) String() string { return cmdString(cmd.cmd, cmd.val) } -type ExpectedStringCmd struct { - expectedBase - - val string -} - -func (cmd *ExpectedStringCmd) inflow(c redis.Cmder) { - inflow(c, "val", cmd.val) -} - -func (cmd *ExpectedStringCmd) SetVal(val string) { +func (cmd *ExpectedFTSpellCheck) SetVal(val []redis.SpellCheckResult) { cmd.setVal = true cmd.val = val } -func (cmd *ExpectedStringCmd) Val() string { - return cmd.val -} - -func (cmd *ExpectedStringCmd) Result() (string, error) { +func (cmd *ExpectedFTSpellCheck) Result() ([]redis.SpellCheckResult, error) { return cmd.val, cmd.err } -func (cmd *ExpectedStringCmd) Bytes() ([]byte, error) { - return StringToBytes(cmd.val), cmd.err -} - -func (cmd *ExpectedStringCmd) Bool() (bool, error) { - if cmd.err != nil { - return false, cmd.err - } - return strconv.ParseBool(cmd.val) -} - -func (cmd *ExpectedStringCmd) Int() (int, error) { - if cmd.err != nil { - return 0, cmd.err - } - return strconv.Atoi(cmd.Val()) -} - -func (cmd *ExpectedStringCmd) Int64() (int64, error) { - if cmd.err != nil { - return 0, cmd.err - } - return strconv.ParseInt(cmd.Val(), 10, 64) -} - -func (cmd *ExpectedStringCmd) Uint64() (uint64, error) { - if cmd.err != nil { - return 0, cmd.err - } - return strconv.ParseUint(cmd.Val(), 10, 64) -} - -func (cmd *ExpectedStringCmd) Float32() (float32, error) { - if cmd.err != nil { - return 0, cmd.err - } - f, err := strconv.ParseFloat(cmd.Val(), 32) - if err != nil { - return 0, err - } - return float32(f), nil -} - -func (cmd *ExpectedStringCmd) Float64() (float64, error) { - if cmd.err != nil { - return 0, cmd.err - } - return strconv.ParseFloat(cmd.Val(), 64) -} - -func (cmd *ExpectedStringCmd) Time() (time.Time, error) { - if cmd.err != nil { - return time.Time{}, cmd.err - } - return time.Parse(time.RFC3339Nano, cmd.Val()) +func (cmd *ExpectedFTSpellCheck) Val() []redis.SpellCheckResult { + return cmd.val } -func (cmd *ExpectedStringCmd) Scan(val interface{}) error { - if cmd.err != nil { - return cmd.err - } - return Scan([]byte(cmd.val), val) +func (cmd *ExpectedFTSpellCheck) RawVal() interface{} { + return cmd.rawVal } -func (cmd *ExpectedStringCmd) String() string { - return cmdString(cmd.cmd, cmd.val) +func (cmd *ExpectedFTSpellCheck) RawResult() (interface{}, error) { + return cmd.rawVal, cmd.err } -type ExpectedAggregateCmd struct { +type ExpectedAggregate struct { expectedBase val *redis.FTAggregateResult } -func (cmd *ExpectedAggregateCmd) inflow(c redis.Cmder) { +func (cmd *ExpectedAggregate) inflow(c redis.Cmder) { inflow(c, "val", cmd.val) } -func (cmd *ExpectedAggregateCmd) SetVal(val *redis.FTAggregateResult) { +func (cmd *ExpectedAggregate) SetVal(val *redis.FTAggregateResult) { cmd.setVal = true cmd.val = val } -func (cmd *ExpectedAggregateCmd) Val() *redis.FTAggregateResult { +func (cmd *ExpectedAggregate) Val() *redis.FTAggregateResult { return cmd.val } -func (cmd *ExpectedAggregateCmd) Result() (*redis.FTAggregateResult, error) { +func (cmd *ExpectedAggregate) Result() (*redis.FTAggregateResult, error) { return cmd.val, cmd.err } -func (cmd *ExpectedAggregateCmd) RawVal() interface{} { +func (cmd *ExpectedAggregate) RawVal() interface{} { return cmd.rawVal } -func (cmd *ExpectedAggregateCmd) RawResult() (interface{}, error) { +func (cmd *ExpectedAggregate) RawResult() (interface{}, error) { return cmd.rawVal, cmd.err } -func (cmd *ExpectedAggregateCmd) String() string { +func (cmd *ExpectedAggregate) String() string { return cmdString(cmd.cmd, cmd.val) } -type ExpectedFTInfoCmd struct { +type ExpectedFTInfo struct { expectedBase val redis.FTInfoResult } -func (cmd *ExpectedFTInfoCmd) inflow(c redis.Cmder) { +func (cmd *ExpectedFTInfo) inflow(c redis.Cmder) { inflow(c, "val", cmd.val) } -func (cmd *ExpectedFTInfoCmd) String() string { +func (cmd *ExpectedFTInfo) String() string { return cmdString(cmd.cmd, cmd.val) } -func (cmd *ExpectedFTInfoCmd) SetVal(val redis.FTInfoResult) { +func (cmd *ExpectedFTInfo) SetVal(val redis.FTInfoResult) { cmd.setVal = true cmd.val = val } -func (cmd *ExpectedFTInfoCmd) Result() (redis.FTInfoResult, error) { +func (cmd *ExpectedFTInfo) Result() (redis.FTInfoResult, error) { return cmd.val, cmd.err } -func (cmd *ExpectedFTInfoCmd) Val() redis.FTInfoResult { +func (cmd *ExpectedFTInfo) Val() redis.FTInfoResult { return cmd.val } -func (cmd *ExpectedFTInfoCmd) RawVal() interface{} { +func (cmd *ExpectedFTInfo) RawVal() interface{} { return cmd.rawVal } -func (cmd *ExpectedFTInfoCmd) RawResult() (interface{}, error) { +func (cmd *ExpectedFTInfo) RawResult() (interface{}, error) { return cmd.rawVal, cmd.err } -type ExpectedFTSynDumpCmd struct { +type ExpectedFTSynDump struct { expectedBase val []redis.FTSynDumpResult } -func (cmd *ExpectedFTSynDumpCmd) inflow(c redis.Cmder) { +func (cmd *ExpectedFTSynDump) inflow(c redis.Cmder) { inflow(c, "val", cmd.val) } -func (cmd *ExpectedFTSynDumpCmd) String() string { +func (cmd *ExpectedFTSynDump) String() string { return cmdString(cmd.cmd, cmd.val) } -func (cmd *ExpectedFTSynDumpCmd) SetVal(val []redis.FTSynDumpResult) { +func (cmd *ExpectedFTSynDump) SetVal(val []redis.FTSynDumpResult) { cmd.setVal = true cmd.val = val } -func (cmd *ExpectedFTSynDumpCmd) Val() []redis.FTSynDumpResult { +func (cmd *ExpectedFTSynDump) Val() []redis.FTSynDumpResult { return cmd.val } -func (cmd *ExpectedFTSynDumpCmd) Result() ([]redis.FTSynDumpResult, error) { +func (cmd *ExpectedFTSynDump) Result() ([]redis.FTSynDumpResult, error) { return cmd.val, cmd.err } -func (cmd *ExpectedFTSynDumpCmd) RawVal() interface{} { +func (cmd *ExpectedFTSynDump) RawVal() interface{} { return cmd.rawVal } -func (cmd *ExpectedFTSynDumpCmd) RawResult() (interface{}, error) { +func (cmd *ExpectedFTSynDump) RawResult() (interface{}, error) { return cmd.rawVal, cmd.err } diff --git a/expect_json.go b/expect_json.go index 3b163ca..4cd0bd5 100644 --- a/expect_json.go +++ b/expect_json.go @@ -1,42 +1,11 @@ package redismock import ( - "encoding" "encoding/json" - "fmt" - "reflect" - "sync" "github.com/redis/go-redis/v9" ) -type ExpectedMapMapStringInterfaceCmd struct { - expectedBase - - val map[string]interface{} -} - -func (cmd *ExpectedMapMapStringInterfaceCmd) inflow(c redis.Cmder) { - inflow(c, "val", cmd.val) -} - -func (cmd *ExpectedMapMapStringInterfaceCmd) String() string { - return cmdString(cmd.cmd, cmd.val) -} - -func (cmd *ExpectedMapMapStringInterfaceCmd) SetVal(val map[string]interface{}) { - cmd.setVal = true - cmd.val = val -} - -func (cmd *ExpectedMapMapStringInterfaceCmd) Result() (map[string]interface{}, error) { - return cmd.val, cmd.err -} - -func (cmd *ExpectedMapMapStringInterfaceCmd) Val() map[string]interface{} { - return cmd.val -} - type ExpectedIntPointerSlice struct { expectedBase @@ -139,83 +108,3 @@ func (cmd *ExpectedJSONSlice) Val() []interface{} { func (cmd *ExpectedJSONSlice) Result() ([]interface{}, error) { return cmd.val, cmd.err } - -type decoderFunc func(reflect.Value, string) error - -type structField struct { - index int - fn decoderFunc -} - -type structSpec struct { - m map[string]*structField -} - -func (s *structSpec) set(tag string, sf *structField) { - s.m[tag] = sf -} - -type StructValue struct { - spec *structSpec - value reflect.Value -} - -type Scanner interface { - ScanRedis(s string) error -} - -func (s StructValue) Scan(key string, value string) error { - field, ok := s.spec.m[key] - if !ok { - return nil - } - - v := s.value.Field(field.index) - isPtr := v.Kind() == reflect.Ptr - - if isPtr && v.IsNil() { - v.Set(reflect.New(v.Type().Elem())) - } - if !isPtr && v.Type().Name() != "" && v.CanAddr() { - v = v.Addr() - isPtr = true - } - - if isPtr && v.Type().NumMethod() > 0 && v.CanInterface() { - switch scan := v.Interface().(type) { - case Scanner: - return scan.ScanRedis(value) - case encoding.TextUnmarshaler: - return scan.UnmarshalText(StringToBytes(value)) - } - } - - if isPtr { - v = v.Elem() - } - - if err := field.fn(v, value); err != nil { - t := s.value.Type() - return fmt.Errorf("cannot scan redis.result %s into struct field %s.%s of type %s, error-%s", - value, t.Name(), t.Field(field.index).Name, t.Field(field.index).Type, err.Error()) - } - return nil -} - -type structMap struct { - m sync.Map -} - -func newStructMap() *structMap { - return new(structMap) -} - -func (s *structMap) get(t reflect.Type) *structSpec { - if v, ok := s.m.Load(t); ok { - return v.(*structSpec) - } - - spec := newStructSpec(t, "redis") - s.m.Store(t, spec) - return spec -} diff --git a/helper.go b/helper.go index 3d194b7..4a67f3a 100644 --- a/helper.go +++ b/helper.go @@ -1,232 +1,14 @@ package redismock import ( - "encoding" - "errors" "fmt" - "net" - "reflect" "strconv" - "strings" "time" "unsafe" "github.com/redis/go-redis/v9" ) -var ( - decoders = []decoderFunc{ - reflect.Bool: decodeBool, - reflect.Int: decodeInt, - reflect.Int8: decodeInt8, - reflect.Int16: decodeInt16, - reflect.Int32: decodeInt32, - reflect.Int64: decodeInt64, - reflect.Uint: decodeUint, - reflect.Uint8: decodeUint8, - reflect.Uint16: decodeUint16, - reflect.Uint32: decodeUint32, - reflect.Uint64: decodeUint64, - reflect.Float32: decodeFloat32, - reflect.Float64: decodeFloat64, - reflect.Complex64: decodeUnsupported, - reflect.Complex128: decodeUnsupported, - reflect.Array: decodeUnsupported, - reflect.Chan: decodeUnsupported, - reflect.Func: decodeUnsupported, - reflect.Interface: decodeUnsupported, - reflect.Map: decodeUnsupported, - reflect.Ptr: decodeUnsupported, - reflect.Slice: decodeSlice, - reflect.String: decodeString, - reflect.Struct: decodeUnsupported, - reflect.UnsafePointer: decodeUnsupported, - } - globalStructMap = newStructMap() -) - -func decodeBool(f reflect.Value, s string) error { - b, err := strconv.ParseBool(s) - if err != nil { - return err - } - f.SetBool(b) - return nil -} - -func decodeInt8(f reflect.Value, s string) error { - return decodeNumber(f, s, 8) -} - -func decodeInt16(f reflect.Value, s string) error { - return decodeNumber(f, s, 16) -} - -func decodeInt32(f reflect.Value, s string) error { - return decodeNumber(f, s, 32) -} - -func decodeInt64(f reflect.Value, s string) error { - return decodeNumber(f, s, 64) -} - -func decodeInt(f reflect.Value, s string) error { - return decodeNumber(f, s, 0) -} - -func decodeNumber(f reflect.Value, s string, bitSize int) error { - v, err := strconv.ParseInt(s, 10, bitSize) - if err != nil { - return err - } - f.SetInt(v) - return nil -} - -func decodeUint8(f reflect.Value, s string) error { - return decodeUnsignedNumber(f, s, 8) -} - -func decodeUint16(f reflect.Value, s string) error { - return decodeUnsignedNumber(f, s, 16) -} - -func decodeUint32(f reflect.Value, s string) error { - return decodeUnsignedNumber(f, s, 32) -} - -func decodeUint64(f reflect.Value, s string) error { - return decodeUnsignedNumber(f, s, 64) -} - -func decodeUint(f reflect.Value, s string) error { - return decodeUnsignedNumber(f, s, 0) -} - -func decodeUnsignedNumber(f reflect.Value, s string, bitSize int) error { - v, err := strconv.ParseUint(s, 10, bitSize) - if err != nil { - return err - } - f.SetUint(v) - return nil -} - -func decodeFloat32(f reflect.Value, s string) error { - v, err := strconv.ParseFloat(s, 32) - if err != nil { - return err - } - f.SetFloat(v) - return nil -} - -// although the default is float64, but we better define it. -func decodeFloat64(f reflect.Value, s string) error { - v, err := strconv.ParseFloat(s, 64) - if err != nil { - return err - } - f.SetFloat(v) - return nil -} - -func decodeString(f reflect.Value, s string) error { - f.SetString(s) - return nil -} - -func decodeSlice(f reflect.Value, s string) error { - // []byte slice ([]uint8). - if f.Type().Elem().Kind() == reflect.Uint8 { - f.SetBytes([]byte(s)) - } - return nil -} - -func decodeUnsupported(v reflect.Value, s string) error { - return fmt.Errorf("redis.Scan(unsupported %s)", v.Type()) -} - -func newStructSpec(t reflect.Type, fieldTag string) *structSpec { - numField := t.NumField() - out := &structSpec{ - m: make(map[string]*structField, numField), - } - - for i := 0; i < numField; i++ { - f := t.Field(i) - - tag := f.Tag.Get(fieldTag) - if tag == "" || tag == "-" { - continue - } - - tag = strings.Split(tag, ",")[0] - if tag == "" { - continue - } - - // Use the built-in decoder. - kind := f.Type.Kind() - if kind == reflect.Pointer { - kind = f.Type.Elem().Kind() - } - out.set(tag, &structField{index: i, fn: decoders[kind]}) - } - - return out -} - -func Struct(dst interface{}) (StructValue, error) { - v := reflect.ValueOf(dst) - - // The destination to scan into should be a struct pointer. - if v.Kind() != reflect.Ptr || v.IsNil() { - return StructValue{}, fmt.Errorf("redis.Scan(non-pointer %T)", dst) - } - - v = v.Elem() - if v.Kind() != reflect.Struct { - return StructValue{}, fmt.Errorf("redis.Scan(non-struct %T)", dst) - } - - return StructValue{ - spec: globalStructMap.get(v.Type()), - value: v, - }, nil -} - -func HScan(dst interface{}, keys []interface{}, vals []interface{}) error { - if len(keys) != len(vals) { - return errors.New("args should have the same number of keys and vals") - } - - strct, err := Struct(dst) - if err != nil { - return err - } - - // Iterate through the (key, value) sequence. - for i := 0; i < len(vals); i++ { - key, ok := keys[i].(string) - if !ok { - continue - } - - val, ok := vals[i].(string) - if !ok { - continue - } - - if err := strct.Scan(key, val); err != nil { - return err - } - } - - return nil -} - func cmdString(cmd redis.Cmder, val interface{}) string { b := make([]byte, 0, 64) @@ -301,177 +83,6 @@ func BytesToString(b []byte) string { return *(*string)(unsafe.Pointer(&b)) } -func ScanSlice(data []string, slice interface{}) error { - v := reflect.ValueOf(slice) - if !v.IsValid() { - return fmt.Errorf("redis: ScanSlice(nil)") - } - if v.Kind() != reflect.Ptr { - return fmt.Errorf("redis: ScanSlice(non-pointer %T)", slice) - } - v = v.Elem() - if v.Kind() != reflect.Slice { - return fmt.Errorf("redis: ScanSlice(non-slice %T)", slice) - } - - next := makeSliceNextElemFunc(v) - for i, s := range data { - elem := next() - if err := Scan([]byte(s), elem.Addr().Interface()); err != nil { - err = fmt.Errorf("redis: ScanSlice index=%d value=%q failed: %w", i, s, err) - return err - } - } - - return nil -} - -func Scan(b []byte, v interface{}) error { - switch v := v.(type) { - case nil: - return fmt.Errorf("redis: Scan(nil)") - case *string: - *v = BytesToString(b) - return nil - case *[]byte: - *v = b - return nil - case *int: - var err error - *v, err = strconv.Atoi(BytesToString(b)) - return err - case *int8: - n, err := strconv.ParseInt(BytesToString(b), 10, 8) - if err != nil { - return err - } - *v = int8(n) - return nil - case *int16: - n, err := strconv.ParseInt(BytesToString(b), 10, 16) - if err != nil { - return err - } - *v = int16(n) - return nil - case *int32: - n, err := strconv.ParseInt(BytesToString(b), 10, 32) - if err != nil { - return err - } - *v = int32(n) - return nil - case *int64: - n, err := strconv.ParseInt(BytesToString(b), 10, 64) - if err != nil { - return err - } - *v = n - return nil - case *uint: - n, err := strconv.ParseUint(BytesToString(b), 10, 64) - if err != nil { - return err - } - *v = uint(n) - return nil - case *uint8: - n, err := strconv.ParseUint(BytesToString(b), 10, 8) - if err != nil { - return err - } - *v = uint8(n) - return nil - case *uint16: - n, err := strconv.ParseUint(BytesToString(b), 10, 16) - if err != nil { - return err - } - *v = uint16(n) - return nil - case *uint32: - n, err := strconv.ParseUint(BytesToString(b), 10, 32) - if err != nil { - return err - } - *v = uint32(n) - return nil - case *uint64: - n, err := strconv.ParseUint(BytesToString(b), 10, 64) - if err != nil { - return err - } - *v = n - return nil - case *float32: - n, err := strconv.ParseFloat(BytesToString(b), 32) - if err != nil { - return err - } - *v = float32(n) - return err - case *float64: - var err error - *v, err = strconv.ParseFloat(BytesToString(b), 64) - return err - case *bool: - *v = len(b) == 1 && b[0] == '1' - return nil - case *time.Time: - var err error - *v, err = time.Parse(time.RFC3339Nano, BytesToString(b)) - return err - case *time.Duration: - n, err := strconv.ParseInt(BytesToString(b), 10, 64) - if err != nil { - return err - } - *v = time.Duration(n) - return nil - case encoding.BinaryUnmarshaler: - return v.UnmarshalBinary(b) - case *net.IP: - *v = b - return nil - default: - return fmt.Errorf( - "redis: can't unmarshal %T (consider implementing BinaryUnmarshaler)", v) - } -} - -func makeSliceNextElemFunc(v reflect.Value) func() reflect.Value { - elemType := v.Type().Elem() - - if elemType.Kind() == reflect.Ptr { - elemType = elemType.Elem() - return func() reflect.Value { - if v.Len() < v.Cap() { - v.Set(v.Slice(0, v.Len()+1)) - elem := v.Index(v.Len() - 1) - if elem.IsNil() { - elem.Set(reflect.New(elemType)) - } - return elem.Elem() - } - - elem := reflect.New(elemType) - v.Set(reflect.Append(v, elem)) - return elem.Elem() - } - } - - zero := reflect.Zero(elemType) - return func() reflect.Value { - if v.Len() < v.Cap() { - v.Set(v.Slice(0, v.Len()+1)) - return v.Index(v.Len() - 1) - } - - v.Set(reflect.Append(v, zero)) - return v.Index(v.Len() - 1) - } -} - func StringToBytes(s string) []byte { return *(*[]byte)(unsafe.Pointer( &struct { diff --git a/mock_ft.go b/mock_ft.go index ced544e..c0a10ef 100644 --- a/mock_ft.go +++ b/mock_ft.go @@ -9,15 +9,15 @@ func (m *mock) ExpectFT_List() *ExpectedStringSlice { return e } -func (m *mock) ExpectFTAggregate(index string, query string) *ExpectedMapStringInterfaceCmd { - e := &ExpectedMapStringInterfaceCmd{} +func (m *mock) ExpectFTAggregate(index string, query string) *ExpectedMapStringInterface { + e := &ExpectedMapStringInterface{} e.cmd = m.factory.FTAggregate(m.ctx, index, query) m.pushExpect(e) return e } -func (m *mock) ExpectFTAggregateWithArgs(index string, query string, options *redis.FTAggregateOptions) *ExpectedAggregateCmd { - e := &ExpectedAggregateCmd{} +func (m *mock) ExpectFTAggregateWithArgs(index string, query string, options *redis.FTAggregateOptions) *ExpectedAggregate { + e := &ExpectedAggregate{} e.cmd = m.factory.FTAggregateWithArgs(m.ctx, index, query, options) m.pushExpect(e) return e @@ -51,8 +51,8 @@ func (m *mock) ExpectFTAlter(index string, skipInitialScan bool, definition []in return e } -func (m *mock) ExpectFTConfigGet(option string) *ExpectedMapMapStringInterfaceCmd { - e := &ExpectedMapMapStringInterfaceCmd{} +func (m *mock) ExpectFTConfigGet(option string) *ExpectedMapMapStringInterface { + e := &ExpectedMapMapStringInterface{} e.cmd = m.factory.FTConfigGet(m.ctx, option) m.pushExpect(e) return e @@ -79,8 +79,8 @@ func (m *mock) ExpectFTCursorDel(index string, cursorId int) *ExpectedStatus { return e } -func (m *mock) ExpectFTCursorRead(index string, cursorId int, count int) *ExpectedMapStringInterfaceCmd { - e := &ExpectedMapStringInterfaceCmd{} +func (m *mock) ExpectFTCursorRead(index string, cursorId int, count int) *ExpectedMapStringInterface { + e := &ExpectedMapStringInterface{} e.cmd = m.factory.FTCursorRead(m.ctx, index, cursorId, count) m.pushExpect(e) return e @@ -88,14 +88,14 @@ func (m *mock) ExpectFTCursorRead(index string, cursorId int, count int) *Expect func (m *mock) ExpectFTDictAdd(dict string, term ...interface{}) *ExpectedInt { e := &ExpectedInt{} - e.cmd = m.factory.FTDictAdd(m.ctx, dict, term) + e.cmd = m.factory.FTDictAdd(m.ctx, dict, term...) m.pushExpect(e) return e } func (m *mock) ExpectFTDictDel(dict string, term ...interface{}) *ExpectedInt { e := &ExpectedInt{} - e.cmd = m.factory.FTDictDel(m.ctx, dict, term) + e.cmd = m.factory.FTDictDel(m.ctx, dict, term...) m.pushExpect(e) return e } @@ -121,57 +121,57 @@ func (m *mock) ExpectFTDropIndexWithArgs(index string, options *redis.FTDropInde return e } -func (m *mock) ExpectFTExplain(index string, query string) *ExpectedStringCmd { - e := &ExpectedStringCmd{} +func (m *mock) ExpectFTExplain(index string, query string) *ExpectedString { + e := &ExpectedString{} e.cmd = m.factory.FTExplain(m.ctx, index, query) m.pushExpect(e) return e } -func (m *mock) ExpectFTExplainWithArgs(index string, query string, options *redis.FTExplainOptions) *ExpectedStringCmd { - e := &ExpectedStringCmd{} +func (m *mock) ExpectFTExplainWithArgs(index string, query string, options *redis.FTExplainOptions) *ExpectedString { + e := &ExpectedString{} e.cmd = m.factory.FTExplainWithArgs(m.ctx, index, query, options) m.pushExpect(e) return e } -func (m *mock) ExpectFTInfo(index string) *ExpectedFTInfoCmd { - e := &ExpectedFTInfoCmd{} +func (m *mock) ExpectFTInfo(index string) *ExpectedFTInfo { + e := &ExpectedFTInfo{} e.cmd = m.factory.FTInfo(m.ctx, index) m.pushExpect(e) return e } -func (m *mock) ExpectFTSpellCheck(index string, query string) *ExpectedFTSpellCheckCmd { - e := &ExpectedFTSpellCheckCmd{} +func (m *mock) ExpectFTSpellCheck(index string, query string) *ExpectedFTSpellCheck { + e := &ExpectedFTSpellCheck{} e.cmd = m.factory.FTSpellCheck(m.ctx, index, query) m.pushExpect(e) return e } -func (m *mock) ExpectFTSpellCheckWithArgs(index string, query string, options *redis.FTSpellCheckOptions) *ExpectedFTSpellCheckCmd { - e := &ExpectedFTSpellCheckCmd{} +func (m *mock) ExpectFTSpellCheckWithArgs(index string, query string, options *redis.FTSpellCheckOptions) *ExpectedFTSpellCheck { + e := &ExpectedFTSpellCheck{} e.cmd = m.factory.FTSpellCheckWithArgs(m.ctx, index, query, options) m.pushExpect(e) return e } -func (m *mock) ExpectFTSearch(index string, query string) *ExpectedFTSearchCmd { - e := &ExpectedFTSearchCmd{} +func (m *mock) ExpectFTSearch(index string, query string) *ExpectedFTSearch { + e := &ExpectedFTSearch{} e.cmd = m.factory.FTSearch(m.ctx, index, query) m.pushExpect(e) return e } -func (m *mock) ExpectFTSearchWithArgs(index string, query string, options *redis.FTSearchOptions) *ExpectedFTSearchCmd { - e := &ExpectedFTSearchCmd{} +func (m *mock) ExpectFTSearchWithArgs(index string, query string, options *redis.FTSearchOptions) *ExpectedFTSearch { + e := &ExpectedFTSearch{} e.cmd = m.factory.FTSearchWithArgs(m.ctx, index, query, options) m.pushExpect(e) return e } -func (m *mock) ExpectFTSynDump(index string) *ExpectedFTSynDumpCmd { - e := &ExpectedFTSynDumpCmd{} +func (m *mock) ExpectFTSynDump(index string) *ExpectedFTSynDump { + e := &ExpectedFTSynDump{} e.cmd = m.factory.FTSynDump(m.ctx, index) m.pushExpect(e) return e diff --git a/mock_ft_test.go b/mock_ft_test.go new file mode 100644 index 0000000..22a4cf3 --- /dev/null +++ b/mock_ft_test.go @@ -0,0 +1,139 @@ +package redismock + +import ( + "errors" + + . "github.com/onsi/gomega" + "github.com/redis/go-redis/v9" +) + +func operationAggregateCmd(base baseMock, expected func() *ExpectedAggregate, actual func() *redis.AggregateCmd) { + var ( + setErr = errors.New("aggregate cmd error") + val *redis.FTAggregateResult + err error + uninitalizedResult *redis.FTAggregateResult + ) + + base.ClearExpect() + expected().SetErr(setErr) + val, err = actual().Result() + Expect(err).To(Equal(setErr)) + Expect(val).To(Equal(uninitalizedResult)) + + base.ClearExpect() + expected() + val, err = actual().Result() + Expect(err).To(HaveOccurred()) + Expect(val).To(Equal(uninitalizedResult)) + + base.ClearExpect() + expected().SetVal(&redis.FTAggregateResult{Total: 1}) + val, err = actual().Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal(&redis.FTAggregateResult{Total: 1})) +} + +func operationFTInfoCmd(base baseMock, expected func() *ExpectedFTInfo, actual func() *redis.FTInfoCmd) { + var ( + setErr = errors.New("FTInfo cmd error") + val redis.FTInfoResult + err error + ) + + base.ClearExpect() + expected().SetErr(setErr) + val, err = actual().Result() + Expect(err).To(Equal(setErr)) + Expect(val).To(Equal(redis.FTInfoResult{})) + + base.ClearExpect() + expected() + val, err = actual().Result() + Expect(err).To(HaveOccurred()) + Expect(val).To(Equal(redis.FTInfoResult{})) + + base.ClearExpect() + expected().SetVal(redis.FTInfoResult{IndexName: "test"}) + val, err = actual().Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal(redis.FTInfoResult{IndexName: "test"})) +} + +func operationFTSpellCheckCmd(base baseMock, expected func() *ExpectedFTSpellCheck, actual func() *redis.FTSpellCheckCmd) { + var ( + setErr = errors.New("FTSpellCheck cmd error") + val []redis.SpellCheckResult + err error + ) + + base.ClearExpect() + expected().SetErr(setErr) + val, err = actual().Result() + Expect(err).To(Equal(setErr)) + Expect(val).To(Equal([]redis.SpellCheckResult(nil))) + + base.ClearExpect() + expected() + val, err = actual().Result() + Expect(err).To(HaveOccurred()) + Expect(val).To(Equal([]redis.SpellCheckResult(nil))) + + base.ClearExpect() + expected().SetVal([]redis.SpellCheckResult{{Term: "test"}}) + val, err = actual().Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal([]redis.SpellCheckResult{{Term: "test"}})) +} + +func operationFTSearchCmd(base baseMock, expected func() *ExpectedFTSearch, actual func() *redis.FTSearchCmd) { + var ( + setErr = errors.New("FTSearch cmd error") + val redis.FTSearchResult + err error + ) + + base.ClearExpect() + expected().SetErr(setErr) + val, err = actual().Result() + Expect(err).To(Equal(setErr)) + Expect(val).To(Equal(redis.FTSearchResult{})) + + base.ClearExpect() + expected() + val, err = actual().Result() + Expect(err).To(HaveOccurred()) + Expect(val).To(Equal(redis.FTSearchResult{})) + + base.ClearExpect() + expected().SetVal(redis.FTSearchResult{Total: 5}) + val, err = actual().Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal(redis.FTSearchResult{Total: 5})) +} + +func operationFTSynDumpCmd(base baseMock, expected func() *ExpectedFTSynDump, actual func() *redis.FTSynDumpCmd) { + var ( + setErr = errors.New("FTSearch cmd error") + val []redis.FTSynDumpResult + err error + ) + + base.ClearExpect() + expected().SetErr(setErr) + val, err = actual().Result() + Expect(err).To(Equal(setErr)) + Expect(val).To(Equal([]redis.FTSynDumpResult(nil))) + + base.ClearExpect() + expected() + val, err = actual().Result() + Expect(err).To(HaveOccurred()) + Expect(val).To(Equal([]redis.FTSynDumpResult(nil))) + + base.ClearExpect() + expected().SetVal([]redis.FTSynDumpResult{{Term: "test"}}) + val, err = actual().Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal([]redis.FTSynDumpResult{{Term: "test"}})) +} diff --git a/mock_json_test.go b/mock_json_test.go new file mode 100644 index 0000000..72277fb --- /dev/null +++ b/mock_json_test.go @@ -0,0 +1,60 @@ +package redismock + +import ( + "errors" + + . "github.com/onsi/gomega" + "github.com/redis/go-redis/v9" +) + +func operationJSONCmd(base baseMock, expected func() *ExpectedJSON, actual func() *redis.JSONCmd) { + var ( + setErr = errors.New("JSON cmd error") + val string + err error + ) + + base.ClearExpect() + expected().SetErr(setErr) + val, err = actual().Result() + Expect(err).To(Equal(setErr)) + Expect(val).To(Equal("")) + + base.ClearExpect() + expected() + val, err = actual().Result() + Expect(err).To(HaveOccurred()) + Expect(val).To(Equal("")) + + base.ClearExpect() + expected().SetVal("test") + val, err = actual().Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal("test")) +} + +func operationJSONSliceCmd(base baseMock, expected func() *ExpectedJSONSlice, actual func() *redis.JSONSliceCmd) { + var ( + setErr = errors.New("string slice cmd error") + val []interface{} + err error + ) + + base.ClearExpect() + expected().SetErr(setErr) + val, err = actual().Result() + Expect(err).To(Equal(setErr)) + Expect(val).To(Equal([]interface{}(nil))) + + base.ClearExpect() + expected() + val, err = actual().Result() + Expect(err).To(HaveOccurred()) + Expect(val).To(Equal([]interface{}(nil))) + + base.ClearExpect() + expected().SetVal([]interface{}{"redis", "mock"}) + val, err = actual().Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal([]interface{}{"redis", "mock"})) +} diff --git a/mock_test.go b/mock_test.go index e8470ec..02519d7 100644 --- a/mock_test.go +++ b/mock_test.go @@ -1743,6 +1743,38 @@ func operationMapStringInterfaceCmd(base baseMock, expected func() *ExpectedMapS })) } +func operationMapMapStringInterfaceCmd(base baseMock, expected func() *ExpectedMapMapStringInterface, actual func() *redis.MapMapStringInterfaceCmd) { + var ( + setErr = errors.New("map map string interface cmd error") + val map[string]interface{} + err error + ) + + base.ClearExpect() + expected().SetErr(setErr) + val, err = actual().Result() + Expect(err).To(Equal(setErr)) + Expect(val).To(Equal(map[string]interface{}(nil))) + + base.ClearExpect() + expected() + val, err = actual().Result() + Expect(err).To(HaveOccurred()) + Expect(val).To(Equal(map[string]interface{}(nil))) + + base.ClearExpect() + expected().SetVal(map[string]interface{}{ + "key1": "val1", + "key2": 2, + }) + val, err = actual().Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal(map[string]interface{}{ + "key1": "val1", + "key2": 2, + })) +} + func operationTSTimestampValueSliceCmd(base baseMock, expected func() *ExpectedTSTimestampValueSlice, actual func() *redis.TSTimestampValueSliceCmd) { var ( setErr = errors.New("ts timestamp value slice cmd error") @@ -1811,56 +1843,3 @@ func operationMapStringSliceInterfaceCmd(base baseMock, expected func() *Expecte "key2": {"val2", 2}, })) } - -func operationJSONCmd(base baseMock, expected func() *ExpectedJSON, actual func() *redis.JSONCmd) { - var ( - setErr = errors.New("JSON cmd error") - val string - err error - ) - - base.ClearExpect() - expected().SetErr(setErr) - val, err = actual().Result() - Expect(err).To(Equal(setErr)) - Expect(val).To(Equal("")) - - base.ClearExpect() - expected() - val, err = actual().Result() - Expect(err).To(HaveOccurred()) - Expect(val).To(Equal("")) - - base.ClearExpect() - expected().SetVal("test") - val, err = actual().Result() - Expect(err).NotTo(HaveOccurred()) - Expect(val).To(Equal("test")) -} - -func operationJSONSliceCmd(base baseMock, expected func() *ExpectedJSONSlice, actual func() *redis.JSONSliceCmd) { - // func operationStringSliceCmd(base baseMock, expected func() *ExpectedStringSlice, actual func() *redis.StringSliceCmd) { - var ( - setErr = errors.New("string slice cmd error") - val []interface{} - err error - ) - - base.ClearExpect() - expected().SetErr(setErr) - val, err = actual().Result() - Expect(err).To(Equal(setErr)) - Expect(val).To(Equal([]interface{}(nil))) - - base.ClearExpect() - expected() - val, err = actual().Result() - Expect(err).To(HaveOccurred()) - Expect(val).To(Equal([]interface{}(nil))) - - base.ClearExpect() - expected().SetVal([]interface{}{"redis", "mock"}) - val, err = actual().Result() - Expect(err).NotTo(HaveOccurred()) - Expect(val).To(Equal([]interface{}{"redis", "mock"})) -} From e84f85ffc8282576fe3e602fe73f0a5ad8da0e86 Mon Sep 17 00:00:00 2001 From: Ryan Matthews Date: Thu, 9 Oct 2025 09:51:33 -0700 Subject: [PATCH 7/8] fix: Clean up all of the extra code that wasn't needed --- commands_ft_test.go | 1 - expect.go | 2 +- expect_ft.go | 112 -------------------------------------------- expect_json.go | 59 ----------------------- helper.go | 93 ------------------------------------ mock_json.go | 7 --- 6 files changed, 1 insertion(+), 273 deletions(-) delete mode 100644 helper.go diff --git a/commands_ft_test.go b/commands_ft_test.go index c81bd19..8174bd2 100644 --- a/commands_ft_test.go +++ b/commands_ft_test.go @@ -29,7 +29,6 @@ var _ = Describe("FTCommands", func() { }) }) - // ExpectFTAggregateWithArgs(index string, query string, options *redis.FTAggregateOptions) *ExpectedAggregate It("ExpectFTAggregateWithArgs", func() { operationAggregateCmd(clientMock, func() *ExpectedAggregate { return clientMock.ExpectFTAggregateWithArgs("index", "query", &redis.FTAggregateOptions{}) diff --git a/expect.go b/expect.go index b6a3c9d..91872fe 100644 --- a/expect.go +++ b/expect.go @@ -404,7 +404,7 @@ type baseMock interface { ExpectJSONArrTrim(key, path string) *ExpectedIntSlice ExpectJSONArrTrimWithArgs(key, path string, options *redis.JSONArrTrimArgs) *ExpectedIntSlice ExpectJSONClear(key, path string) *ExpectedInt - ExpectJSONDebugMemory(key, path string) *ExpectedInt + // ExpectJSONDebugMemory(key, path string) *ExpectedInt // This just panics in the go-redis code ExpectJSONDel(key, path string) *ExpectedInt ExpectJSONForget(key, path string) *ExpectedInt ExpectJSONGet(key string, paths ...string) *ExpectedJSON diff --git a/expect_ft.go b/expect_ft.go index 162c860..06c4e3a 100644 --- a/expect_ft.go +++ b/expect_ft.go @@ -14,23 +14,11 @@ func (cmd *ExpectedMapMapStringInterface) inflow(c redis.Cmder) { inflow(c, "val", cmd.val) } -func (cmd *ExpectedMapMapStringInterface) String() string { - return cmdString(cmd.cmd, cmd.val) -} - func (cmd *ExpectedMapMapStringInterface) SetVal(val map[string]interface{}) { cmd.setVal = true cmd.val = val } -func (cmd *ExpectedMapMapStringInterface) Result() (map[string]interface{}, error) { - return cmd.val, cmd.err -} - -func (cmd *ExpectedMapMapStringInterface) Val() map[string]interface{} { - return cmd.val -} - type ExpectedFTSearch struct { expectedBase @@ -41,31 +29,11 @@ func (cmd *ExpectedFTSearch) inflow(c redis.Cmder) { inflow(c, "val", cmd.val) } -func (cmd *ExpectedFTSearch) String() string { - return cmdString(cmd.cmd, cmd.val) -} - func (cmd *ExpectedFTSearch) SetVal(val redis.FTSearchResult) { cmd.setVal = true cmd.val = val } -func (cmd *ExpectedFTSearch) Result() (redis.FTSearchResult, error) { - return cmd.val, cmd.err -} - -func (cmd *ExpectedFTSearch) Val() redis.FTSearchResult { - return cmd.val -} - -func (cmd *ExpectedFTSearch) RawVal() interface{} { - return cmd.rawVal -} - -func (cmd *ExpectedFTSearch) RawResult() (interface{}, error) { - return cmd.rawVal, cmd.err -} - type ExpectedFTSpellCheck struct { expectedBase @@ -76,31 +44,11 @@ func (cmd *ExpectedFTSpellCheck) inflow(c redis.Cmder) { inflow(c, "val", cmd.val) } -func (cmd *ExpectedFTSpellCheck) String() string { - return cmdString(cmd.cmd, cmd.val) -} - func (cmd *ExpectedFTSpellCheck) SetVal(val []redis.SpellCheckResult) { cmd.setVal = true cmd.val = val } -func (cmd *ExpectedFTSpellCheck) Result() ([]redis.SpellCheckResult, error) { - return cmd.val, cmd.err -} - -func (cmd *ExpectedFTSpellCheck) Val() []redis.SpellCheckResult { - return cmd.val -} - -func (cmd *ExpectedFTSpellCheck) RawVal() interface{} { - return cmd.rawVal -} - -func (cmd *ExpectedFTSpellCheck) RawResult() (interface{}, error) { - return cmd.rawVal, cmd.err -} - type ExpectedAggregate struct { expectedBase @@ -116,26 +64,6 @@ func (cmd *ExpectedAggregate) SetVal(val *redis.FTAggregateResult) { cmd.val = val } -func (cmd *ExpectedAggregate) Val() *redis.FTAggregateResult { - return cmd.val -} - -func (cmd *ExpectedAggregate) Result() (*redis.FTAggregateResult, error) { - return cmd.val, cmd.err -} - -func (cmd *ExpectedAggregate) RawVal() interface{} { - return cmd.rawVal -} - -func (cmd *ExpectedAggregate) RawResult() (interface{}, error) { - return cmd.rawVal, cmd.err -} - -func (cmd *ExpectedAggregate) String() string { - return cmdString(cmd.cmd, cmd.val) -} - type ExpectedFTInfo struct { expectedBase @@ -146,31 +74,11 @@ func (cmd *ExpectedFTInfo) inflow(c redis.Cmder) { inflow(c, "val", cmd.val) } -func (cmd *ExpectedFTInfo) String() string { - return cmdString(cmd.cmd, cmd.val) -} - func (cmd *ExpectedFTInfo) SetVal(val redis.FTInfoResult) { cmd.setVal = true cmd.val = val } -func (cmd *ExpectedFTInfo) Result() (redis.FTInfoResult, error) { - return cmd.val, cmd.err -} - -func (cmd *ExpectedFTInfo) Val() redis.FTInfoResult { - return cmd.val -} - -func (cmd *ExpectedFTInfo) RawVal() interface{} { - return cmd.rawVal -} - -func (cmd *ExpectedFTInfo) RawResult() (interface{}, error) { - return cmd.rawVal, cmd.err -} - type ExpectedFTSynDump struct { expectedBase @@ -181,27 +89,7 @@ func (cmd *ExpectedFTSynDump) inflow(c redis.Cmder) { inflow(c, "val", cmd.val) } -func (cmd *ExpectedFTSynDump) String() string { - return cmdString(cmd.cmd, cmd.val) -} - func (cmd *ExpectedFTSynDump) SetVal(val []redis.FTSynDumpResult) { cmd.setVal = true cmd.val = val } - -func (cmd *ExpectedFTSynDump) Val() []redis.FTSynDumpResult { - return cmd.val -} - -func (cmd *ExpectedFTSynDump) Result() ([]redis.FTSynDumpResult, error) { - return cmd.val, cmd.err -} - -func (cmd *ExpectedFTSynDump) RawVal() interface{} { - return cmd.rawVal -} - -func (cmd *ExpectedFTSynDump) RawResult() (interface{}, error) { - return cmd.rawVal, cmd.err -} diff --git a/expect_json.go b/expect_json.go index 4cd0bd5..fd18a55 100644 --- a/expect_json.go +++ b/expect_json.go @@ -1,8 +1,6 @@ package redismock import ( - "encoding/json" - "github.com/redis/go-redis/v9" ) @@ -16,23 +14,11 @@ func (cmd *ExpectedIntPointerSlice) inflow(c redis.Cmder) { inflow(c, "val", cmd.val) } -func (cmd *ExpectedIntPointerSlice) String() string { - return cmdString(cmd.cmd, cmd.val) -} - func (cmd *ExpectedIntPointerSlice) SetVal(val []*int64) { cmd.setVal = true cmd.val = val } -func (cmd *ExpectedIntPointerSlice) Val() []*int64 { - return cmd.val -} - -func (cmd *ExpectedIntPointerSlice) Result() ([]*int64, error) { - return cmd.val, cmd.err -} - type ExpectedJSON struct { expectedBase @@ -44,44 +30,11 @@ func (cmd *ExpectedJSON) inflow(c redis.Cmder) { inflow(c, "val", cmd.val) } -func (cmd *ExpectedJSON) String() string { - return cmdString(cmd.cmd, cmd.val) -} - func (cmd *ExpectedJSON) SetVal(val string) { cmd.setVal = true cmd.val = val } -func (cmd *ExpectedJSON) Val() string { - if len(cmd.val) == 0 && cmd.expanded != nil { - val, err := json.Marshal(cmd.expanded) - if err != nil { - cmd.SetErr(err) - return "" - } - return string(val) - - } else { - return cmd.val - } -} - -func (cmd *ExpectedJSON) Result() (string, error) { - return cmd.Val(), cmd.cmd.Err() -} - -func (cmd *ExpectedJSON) Expanded() (interface{}, error) { - if len(cmd.val) != 0 && cmd.expanded == nil { - err := json.Unmarshal([]byte(cmd.val), &cmd.expanded) - if err != nil { - return nil, err - } - } - - return cmd.expanded, nil -} - type ExpectedJSONSlice struct { expectedBase @@ -92,19 +45,7 @@ func (cmd *ExpectedJSONSlice) inflow(c redis.Cmder) { inflow(c, "val", cmd.val) } -func (cmd *ExpectedJSONSlice) String() string { - return cmdString(cmd.cmd, cmd.val) -} - func (cmd *ExpectedJSONSlice) SetVal(val []interface{}) { cmd.setVal = true cmd.val = val } - -func (cmd *ExpectedJSONSlice) Val() []interface{} { - return cmd.val -} - -func (cmd *ExpectedJSONSlice) Result() ([]interface{}, error) { - return cmd.val, cmd.err -} diff --git a/helper.go b/helper.go deleted file mode 100644 index 4a67f3a..0000000 --- a/helper.go +++ /dev/null @@ -1,93 +0,0 @@ -package redismock - -import ( - "fmt" - "strconv" - "time" - "unsafe" - - "github.com/redis/go-redis/v9" -) - -func cmdString(cmd redis.Cmder, val interface{}) string { - b := make([]byte, 0, 64) - - for i, arg := range cmd.Args() { - if i > 0 { - b = append(b, ' ') - } - b = AppendArg(b, arg) - } - - if err := cmd.Err(); err != nil { - b = append(b, ": "...) - b = append(b, err.Error()...) - } else if val != nil { - b = append(b, ": "...) - b = AppendArg(b, val) - } - - return BytesToString(b) -} - -func AppendArg(b []byte, v interface{}) []byte { - switch v := v.(type) { - case nil: - return append(b, ""...) - case string: - return appendUTF8String(b, StringToBytes(v)) - case []byte: - return appendUTF8String(b, v) - case int: - return strconv.AppendInt(b, int64(v), 10) - case int8: - return strconv.AppendInt(b, int64(v), 10) - case int16: - return strconv.AppendInt(b, int64(v), 10) - case int32: - return strconv.AppendInt(b, int64(v), 10) - case int64: - return strconv.AppendInt(b, v, 10) - case uint: - return strconv.AppendUint(b, uint64(v), 10) - case uint8: - return strconv.AppendUint(b, uint64(v), 10) - case uint16: - return strconv.AppendUint(b, uint64(v), 10) - case uint32: - return strconv.AppendUint(b, uint64(v), 10) - case uint64: - return strconv.AppendUint(b, v, 10) - case float32: - return strconv.AppendFloat(b, float64(v), 'f', -1, 64) - case float64: - return strconv.AppendFloat(b, v, 'f', -1, 64) - case bool: - if v { - return append(b, "true"...) - } - return append(b, "false"...) - case time.Time: - return v.AppendFormat(b, time.RFC3339Nano) - default: - return append(b, fmt.Sprint(v)...) - } -} - -func appendUTF8String(dst []byte, src []byte) []byte { - dst = append(dst, src...) - return dst -} - -func BytesToString(b []byte) string { - return *(*string)(unsafe.Pointer(&b)) -} - -func StringToBytes(s string) []byte { - return *(*[]byte)(unsafe.Pointer( - &struct { - string - Cap int - }{s, len(s)}, - )) -} diff --git a/mock_json.go b/mock_json.go index 9d9ef82..9d28c72 100644 --- a/mock_json.go +++ b/mock_json.go @@ -65,13 +65,6 @@ func (m *mock) ExpectJSONClear(key, path string) *ExpectedInt { return e } -func (m *mock) ExpectJSONDebugMemory(key, path string) *ExpectedInt { - e := &ExpectedInt{} - e.cmd = m.factory.JSONDebugMemory(m.ctx, key, path) - m.pushExpect(e) - return e -} - func (m *mock) ExpectJSONDel(key, path string) *ExpectedInt { e := &ExpectedInt{} e.cmd = m.factory.JSONDel(m.ctx, key, path) From b6291a442116e9a470043bfe5d3caa61e069c7a7 Mon Sep 17 00:00:00 2001 From: Ryan Matthews Date: Thu, 9 Oct 2025 12:39:22 -0700 Subject: [PATCH 8/8] chore: Revert module to go-redis --- example/example.go | 2 +- example/ordinary/main_test.go | 2 +- go.mod | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/example/example.go b/example/example.go index 9acd2fb..7d21150 100644 --- a/example/example.go +++ b/example/example.go @@ -5,7 +5,7 @@ import ( "errors" "time" - "github.com/mrrsm/redismock/v9" + "github.com/go-redis/redismock/v9" ) var _ = example diff --git a/example/ordinary/main_test.go b/example/ordinary/main_test.go index 63251ef..8992337 100644 --- a/example/ordinary/main_test.go +++ b/example/ordinary/main_test.go @@ -4,7 +4,7 @@ import ( "errors" "testing" - "github.com/mrrsm/redismock/v9" + "github.com/go-redis/redismock/v9" ) func TestItemCacheFail(t *testing.T) { diff --git a/go.mod b/go.mod index defa770..fb5d9c1 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/mrrsm/redismock/v9 +module github.com/go-redis/redismock/v9 go 1.18