mirror of
https://github.com/EchoVault/SugarDB.git
synced 2025-10-06 16:36:54 +08:00
3913 lines
137 KiB
Go
3913 lines
137 KiB
Go
package sorted_set
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"errors"
|
|
"github.com/echovault/echovault/src/server"
|
|
"github.com/echovault/echovault/src/utils"
|
|
"github.com/tidwall/resp"
|
|
"math"
|
|
"slices"
|
|
"strconv"
|
|
"testing"
|
|
)
|
|
|
|
func Test_HandleZADD(t *testing.T) {
|
|
mockServer := server.NewServer(server.Opts{})
|
|
|
|
tests := []struct {
|
|
preset bool
|
|
presetValue *SortedSet
|
|
key string
|
|
command []string
|
|
expectedValue *SortedSet
|
|
expectedResponse int
|
|
expectedError error
|
|
}{
|
|
{ // 1. Create new sorted set and return the cardinality of the new sorted set.
|
|
preset: false,
|
|
presetValue: nil,
|
|
key: "key1",
|
|
command: []string{"ZADD", "key1", "5.5", "member1", "67.77", "member2", "10", "member3", "-inf", "member4", "+inf", "member5"},
|
|
expectedValue: NewSortedSet([]MemberParam{
|
|
{value: "member1", score: Score(5.5)},
|
|
{value: "member2", score: Score(67.77)},
|
|
{value: "member3", score: Score(10)},
|
|
{value: "member4", score: Score(math.Inf(-1))},
|
|
{value: "member5", score: Score(math.Inf(1))},
|
|
}),
|
|
expectedResponse: 5,
|
|
expectedError: nil,
|
|
},
|
|
{ // 2. Only add the elements that do not currently exist in the sorted set when NX flag is provided
|
|
preset: true,
|
|
presetValue: NewSortedSet([]MemberParam{
|
|
{value: "member1", score: Score(5.5)},
|
|
{value: "member2", score: Score(67.77)},
|
|
{value: "member3", score: Score(10)},
|
|
}),
|
|
key: "key2",
|
|
command: []string{"ZADD", "key2", "NX", "5.5", "member1", "67.77", "member4", "10", "member5"},
|
|
expectedValue: NewSortedSet([]MemberParam{
|
|
{value: "member1", score: Score(5.5)},
|
|
{value: "member2", score: Score(67.77)},
|
|
{value: "member3", score: Score(10)},
|
|
{value: "member4", score: Score(67.77)},
|
|
{value: "member5", score: Score(10)},
|
|
}),
|
|
expectedResponse: 2,
|
|
expectedError: nil,
|
|
},
|
|
{ // 3. Do not add any elements when providing existing members with NX flag
|
|
preset: true,
|
|
presetValue: NewSortedSet([]MemberParam{
|
|
{value: "member1", score: Score(5.5)},
|
|
{value: "member2", score: Score(67.77)},
|
|
{value: "member3", score: Score(10)},
|
|
}),
|
|
key: "key3",
|
|
command: []string{"ZADD", "key3", "NX", "5.5", "member1", "67.77", "member2", "10", "member3"},
|
|
expectedValue: NewSortedSet([]MemberParam{
|
|
{value: "member1", score: Score(5.5)},
|
|
{value: "member2", score: Score(67.77)},
|
|
{value: "member3", score: Score(10)},
|
|
}),
|
|
expectedResponse: 0,
|
|
expectedError: nil,
|
|
},
|
|
{ // 4. Successfully add elements to an existing set when XX flag is provided with existing elements
|
|
preset: true,
|
|
presetValue: NewSortedSet([]MemberParam{
|
|
{value: "member1", score: Score(5.5)},
|
|
{value: "member2", score: Score(67.77)},
|
|
{value: "member3", score: Score(10)},
|
|
}),
|
|
key: "key4",
|
|
command: []string{"ZADD", "key4", "XX", "CH", "55", "member1", "1005", "member2", "15", "member3", "99.75", "member4"},
|
|
expectedValue: NewSortedSet([]MemberParam{
|
|
{value: "member1", score: Score(55)},
|
|
{value: "member2", score: Score(1005)},
|
|
{value: "member3", score: Score(15)},
|
|
}),
|
|
expectedResponse: 3,
|
|
expectedError: nil,
|
|
},
|
|
{ // 5. Fail to add element when providing XX flag with elements that do not exist in the sorted set.
|
|
preset: true,
|
|
presetValue: NewSortedSet([]MemberParam{
|
|
{value: "member1", score: Score(5.5)},
|
|
{value: "member2", score: Score(67.77)},
|
|
{value: "member3", score: Score(10)},
|
|
}),
|
|
key: "key5",
|
|
command: []string{"ZADD", "key5", "XX", "5.5", "member4", "100.5", "member5", "15", "member6"},
|
|
expectedValue: NewSortedSet([]MemberParam{
|
|
{value: "member1", score: Score(5.5)},
|
|
{value: "member2", score: Score(67.77)},
|
|
{value: "member3", score: Score(10)},
|
|
}),
|
|
expectedResponse: 0,
|
|
expectedError: nil,
|
|
},
|
|
{ // 6. Only update the elements where provided score is greater than current score if GT flag
|
|
// Return only the new elements added by default
|
|
preset: true,
|
|
presetValue: NewSortedSet([]MemberParam{
|
|
{value: "member1", score: Score(5.5)},
|
|
{value: "member2", score: Score(67.77)},
|
|
{value: "member3", score: Score(10)},
|
|
}),
|
|
key: "key6",
|
|
command: []string{"ZADD", "key6", "XX", "CH", "GT", "7.5", "member1", "100.5", "member4", "15", "member5"},
|
|
expectedValue: NewSortedSet([]MemberParam{
|
|
{value: "member1", score: Score(7.5)},
|
|
{value: "member2", score: Score(67.77)},
|
|
{value: "member3", score: Score(10)},
|
|
}),
|
|
expectedResponse: 1,
|
|
expectedError: nil,
|
|
},
|
|
{ // 7. Only update the elements where provided score is less than current score if LT flag is provided
|
|
// Return only the new elements added by default.
|
|
preset: true,
|
|
presetValue: NewSortedSet([]MemberParam{
|
|
{value: "member1", score: Score(5.5)},
|
|
{value: "member2", score: Score(67.77)},
|
|
{value: "member3", score: Score(10)},
|
|
}),
|
|
key: "key7",
|
|
command: []string{"ZADD", "key7", "XX", "LT", "3.5", "member1", "100.5", "member4", "15", "member5"},
|
|
expectedValue: NewSortedSet([]MemberParam{
|
|
{value: "member1", score: Score(3.5)},
|
|
{value: "member2", score: Score(67.77)},
|
|
{value: "member3", score: Score(10)},
|
|
}),
|
|
expectedResponse: 0,
|
|
expectedError: nil,
|
|
},
|
|
{ // 8. Return all the elements that were updated AND added when CH flag is provided
|
|
preset: true,
|
|
presetValue: NewSortedSet([]MemberParam{
|
|
{value: "member1", score: Score(5.5)},
|
|
{value: "member2", score: Score(67.77)},
|
|
{value: "member3", score: Score(10)},
|
|
}),
|
|
key: "key8",
|
|
command: []string{"ZADD", "key8", "XX", "LT", "CH", "3.5", "member1", "100.5", "member4", "15", "member5"},
|
|
expectedValue: NewSortedSet([]MemberParam{
|
|
{value: "member1", score: Score(3.5)},
|
|
{value: "member2", score: Score(67.77)},
|
|
{value: "member3", score: Score(10)},
|
|
}),
|
|
expectedResponse: 1,
|
|
expectedError: nil,
|
|
},
|
|
{ // 9. Increment the member by score
|
|
preset: true,
|
|
presetValue: NewSortedSet([]MemberParam{
|
|
{value: "member1", score: Score(5.5)},
|
|
{value: "member2", score: Score(67.77)},
|
|
{value: "member3", score: Score(10)},
|
|
}),
|
|
key: "key9",
|
|
command: []string{"ZADD", "key9", "INCR", "5.5", "member3"},
|
|
expectedValue: NewSortedSet([]MemberParam{
|
|
{value: "member1", score: Score(5.5)},
|
|
{value: "member2", score: Score(67.77)},
|
|
{value: "member3", score: Score(15.5)},
|
|
}),
|
|
expectedResponse: 0,
|
|
expectedError: nil,
|
|
},
|
|
{ // 10. Fail when GT/LT flag is provided alongside NX flag
|
|
preset: false,
|
|
presetValue: nil,
|
|
key: "key10",
|
|
command: []string{"ZADD", "key10", "NX", "LT", "CH", "3.5", "member1", "100.5", "member4", "15", "member5"},
|
|
expectedValue: nil,
|
|
expectedResponse: 0,
|
|
expectedError: errors.New("GT/LT flags not allowed if NX flag is provided"),
|
|
},
|
|
{ // 11. Command is too short
|
|
preset: false,
|
|
presetValue: nil,
|
|
key: "key11",
|
|
command: []string{"ZADD", "key11"},
|
|
expectedValue: nil,
|
|
expectedResponse: 0,
|
|
expectedError: errors.New(utils.WRONG_ARGS_RESPONSE),
|
|
},
|
|
{ // 12. Throw error when score/member entries are do not match
|
|
preset: false,
|
|
presetValue: nil,
|
|
key: "key11",
|
|
command: []string{"ZADD", "key12", "10.5", "member1", "12.5"},
|
|
expectedValue: nil,
|
|
expectedResponse: 0,
|
|
expectedError: errors.New("score/member pairs must be float/string"),
|
|
},
|
|
{ // 13. Throw error when INCR flag is passed with more than one score/member pair
|
|
preset: false,
|
|
presetValue: nil,
|
|
key: "key13",
|
|
command: []string{"ZADD", "key13", "INCR", "10.5", "member1", "12.5", "member2"},
|
|
expectedValue: nil,
|
|
expectedResponse: 0,
|
|
expectedError: errors.New("cannot pass more than one score/member pair when INCR flag is provided"),
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
if test.preset {
|
|
if _, err := mockServer.CreateKeyAndLock(context.Background(), test.key); err != nil {
|
|
t.Error(err)
|
|
}
|
|
mockServer.SetValue(context.Background(), test.key, test.presetValue)
|
|
mockServer.KeyUnlock(test.key)
|
|
}
|
|
res, err := handleZADD(context.Background(), test.command, mockServer, nil)
|
|
if test.expectedError != nil {
|
|
if err.Error() != test.expectedError.Error() {
|
|
t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error())
|
|
}
|
|
continue
|
|
}
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
rd := resp.NewReader(bytes.NewReader(res))
|
|
rv, _, err := rd.ReadValue()
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
if rv.Integer() != test.expectedResponse {
|
|
t.Errorf("expected response %d at key \"%s\", got %d", test.expectedResponse, test.key, rv.Integer())
|
|
}
|
|
// Fetch the sorted set from the server and check it against the expected result
|
|
if _, err = mockServer.KeyRLock(context.Background(), test.key); err != nil {
|
|
t.Error(err)
|
|
}
|
|
sortedSet, ok := mockServer.GetValue(test.key).(*SortedSet)
|
|
if !ok {
|
|
t.Errorf("expected the value at key \"%s\" to be a sorted set, got another type", test.key)
|
|
}
|
|
if test.expectedValue == nil {
|
|
continue
|
|
}
|
|
if !sortedSet.Equals(test.expectedValue) {
|
|
t.Errorf("expected sorted set %+v, got %+v", test.expectedValue, sortedSet)
|
|
}
|
|
mockServer.KeyRUnlock(test.key)
|
|
}
|
|
}
|
|
|
|
func Test_HandleZCARD(t *testing.T) {
|
|
mockServer := server.NewServer(server.Opts{})
|
|
|
|
tests := []struct {
|
|
preset bool
|
|
presetValue interface{}
|
|
key string
|
|
command []string
|
|
expectedValue *SortedSet
|
|
expectedResponse int
|
|
expectedError error
|
|
}{
|
|
{ // 1. Get cardinality of valid sorted set.
|
|
preset: true,
|
|
presetValue: NewSortedSet([]MemberParam{
|
|
{value: "member1", score: Score(5.5)},
|
|
{value: "member2", score: Score(67.77)},
|
|
{value: "member3", score: Score(10)},
|
|
}),
|
|
key: "key1",
|
|
command: []string{"ZCARD", "key1"},
|
|
expectedValue: nil,
|
|
expectedResponse: 3,
|
|
expectedError: nil,
|
|
},
|
|
{ // 2. Return 0 when trying to get cardinality from non-existent key
|
|
preset: false,
|
|
presetValue: nil,
|
|
key: "key2",
|
|
command: []string{"ZCARD", "key2"},
|
|
expectedValue: nil,
|
|
expectedResponse: 0,
|
|
expectedError: nil,
|
|
},
|
|
{ // 3. Command is too short
|
|
preset: false,
|
|
presetValue: nil,
|
|
key: "key3",
|
|
command: []string{"ZCARD"},
|
|
expectedValue: nil,
|
|
expectedResponse: 0,
|
|
expectedError: errors.New(utils.WRONG_ARGS_RESPONSE),
|
|
},
|
|
{ // 4. Command too long
|
|
preset: false,
|
|
presetValue: nil,
|
|
key: "key4",
|
|
command: []string{"ZCARD", "key4", "key5"},
|
|
expectedValue: nil,
|
|
expectedResponse: 0,
|
|
expectedError: errors.New(utils.WRONG_ARGS_RESPONSE),
|
|
},
|
|
{ // 5. Return error when not a sorted set
|
|
preset: true,
|
|
presetValue: "Default value",
|
|
key: "key5",
|
|
command: []string{"ZCARD", "key5"},
|
|
expectedValue: nil,
|
|
expectedResponse: 0,
|
|
expectedError: errors.New("value at key5 is not a sorted set"),
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
if test.preset {
|
|
if _, err := mockServer.CreateKeyAndLock(context.Background(), test.key); err != nil {
|
|
t.Error(err)
|
|
}
|
|
mockServer.SetValue(context.Background(), test.key, test.presetValue)
|
|
mockServer.KeyUnlock(test.key)
|
|
}
|
|
res, err := handleZCARD(context.Background(), test.command, mockServer, nil)
|
|
if test.expectedError != nil {
|
|
if err.Error() != test.expectedError.Error() {
|
|
t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error())
|
|
}
|
|
continue
|
|
}
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
rd := resp.NewReader(bytes.NewReader(res))
|
|
rv, _, err := rd.ReadValue()
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
if rv.Integer() != test.expectedResponse {
|
|
t.Errorf("expected response %d at key \"%s\", got %d", test.expectedResponse, test.key, rv.Integer())
|
|
}
|
|
}
|
|
}
|
|
|
|
func Test_HandleZCOUNT(t *testing.T) {
|
|
mockServer := server.NewServer(server.Opts{})
|
|
|
|
tests := []struct {
|
|
preset bool
|
|
presetValue interface{}
|
|
key string
|
|
command []string
|
|
expectedValue *SortedSet
|
|
expectedResponse int
|
|
expectedError error
|
|
}{
|
|
{ // 1. Get entire count using infinity boundaries
|
|
preset: true,
|
|
presetValue: NewSortedSet([]MemberParam{
|
|
{value: "member1", score: Score(5.5)},
|
|
{value: "member2", score: Score(67.77)},
|
|
{value: "member3", score: Score(10)},
|
|
{value: "member4", score: Score(1083.13)},
|
|
{value: "member5", score: Score(11)},
|
|
{value: "member6", score: Score(math.Inf(-1))},
|
|
{value: "member7", score: Score(math.Inf(1))},
|
|
}),
|
|
key: "key1",
|
|
command: []string{"ZCOUNT", "key1", "-inf", "+inf"},
|
|
expectedValue: nil,
|
|
expectedResponse: 7,
|
|
expectedError: nil,
|
|
},
|
|
{ // 2. Get count of sub-set from -inf to limit
|
|
preset: true,
|
|
presetValue: NewSortedSet([]MemberParam{
|
|
{value: "member1", score: Score(5.5)},
|
|
{value: "member2", score: Score(67.77)},
|
|
{value: "member3", score: Score(10)},
|
|
{value: "member4", score: Score(1083.13)},
|
|
{value: "member5", score: Score(11)},
|
|
{value: "member6", score: Score(math.Inf(-1))},
|
|
{value: "member7", score: Score(math.Inf(1))},
|
|
}),
|
|
key: "key2",
|
|
command: []string{"ZCOUNT", "key2", "-inf", "90"},
|
|
expectedValue: nil,
|
|
expectedResponse: 5,
|
|
expectedError: nil,
|
|
},
|
|
{ // 3. Get count of sub-set from bottom boundary to +inf limit
|
|
preset: true,
|
|
presetValue: NewSortedSet([]MemberParam{
|
|
{value: "member1", score: Score(5.5)},
|
|
{value: "member2", score: Score(67.77)},
|
|
{value: "member3", score: Score(10)},
|
|
{value: "member4", score: Score(1083.13)},
|
|
{value: "member5", score: Score(11)},
|
|
{value: "member6", score: Score(math.Inf(-1))},
|
|
{value: "member7", score: Score(math.Inf(1))},
|
|
}),
|
|
key: "key3",
|
|
command: []string{"ZCOUNT", "key3", "1000", "+inf"},
|
|
expectedValue: nil,
|
|
expectedResponse: 2,
|
|
expectedError: nil,
|
|
},
|
|
{ // 4. Return error when bottom boundary is not a valid double/float
|
|
preset: false,
|
|
presetValue: nil,
|
|
key: "key4",
|
|
command: []string{"ZCOUNT", "key4", "min", "10"},
|
|
expectedValue: nil,
|
|
expectedResponse: 0,
|
|
expectedError: errors.New("min constraint must be a double"),
|
|
},
|
|
{ // 5. Return error when top boundary is not a valid double/float
|
|
preset: false,
|
|
presetValue: nil,
|
|
key: "key5",
|
|
command: []string{"ZCOUNT", "key5", "-10", "max"},
|
|
expectedValue: nil,
|
|
expectedResponse: 0,
|
|
expectedError: errors.New("max constraint must be a double"),
|
|
},
|
|
{ // 6. Command is too short
|
|
preset: false,
|
|
presetValue: nil,
|
|
key: "key6",
|
|
command: []string{"ZCOUNT"},
|
|
expectedValue: nil,
|
|
expectedResponse: 0,
|
|
expectedError: errors.New(utils.WRONG_ARGS_RESPONSE),
|
|
},
|
|
{ // 7. Command too long
|
|
preset: false,
|
|
presetValue: nil,
|
|
key: "key7",
|
|
command: []string{"ZCOUNT", "key4", "min", "max", "count"},
|
|
expectedValue: nil,
|
|
expectedResponse: 0,
|
|
expectedError: errors.New(utils.WRONG_ARGS_RESPONSE),
|
|
},
|
|
{ // 8. Throw error when value at the key is not a sorted set
|
|
preset: true,
|
|
presetValue: "Default value",
|
|
key: "key8",
|
|
command: []string{"ZCOUNT", "key8", "1", "10"},
|
|
expectedValue: nil,
|
|
expectedResponse: 0,
|
|
expectedError: errors.New("value at key8 is not a sorted set"),
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
if test.preset {
|
|
if _, err := mockServer.CreateKeyAndLock(context.Background(), test.key); err != nil {
|
|
t.Error(err)
|
|
}
|
|
mockServer.SetValue(context.Background(), test.key, test.presetValue)
|
|
mockServer.KeyUnlock(test.key)
|
|
}
|
|
res, err := handleZCOUNT(context.Background(), test.command, mockServer, nil)
|
|
if test.expectedError != nil {
|
|
if err.Error() != test.expectedError.Error() {
|
|
t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error())
|
|
}
|
|
continue
|
|
}
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
rd := resp.NewReader(bytes.NewReader(res))
|
|
rv, _, err := rd.ReadValue()
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
if rv.Integer() != test.expectedResponse {
|
|
t.Errorf("expected response %d at key \"%s\", got %d", test.expectedResponse, test.key, rv.Integer())
|
|
}
|
|
}
|
|
}
|
|
|
|
func Test_HandleZLEXCOUNT(t *testing.T) {
|
|
mockServer := server.NewServer(server.Opts{})
|
|
|
|
tests := []struct {
|
|
preset bool
|
|
presetValue interface{}
|
|
key string
|
|
command []string
|
|
expectedValue *SortedSet
|
|
expectedResponse int
|
|
expectedError error
|
|
}{
|
|
{ // 1. Get entire count using infinity boundaries
|
|
preset: true,
|
|
presetValue: NewSortedSet([]MemberParam{
|
|
{value: "e", score: Score(1)},
|
|
{value: "f", score: Score(1)},
|
|
{value: "g", score: Score(1)},
|
|
{value: "h", score: Score(1)},
|
|
{value: "i", score: Score(1)},
|
|
{value: "j", score: Score(1)},
|
|
{value: "k", score: Score(1)},
|
|
}),
|
|
key: "key1",
|
|
command: []string{"ZLEXCOUNT", "key1", "f", "j"},
|
|
expectedValue: nil,
|
|
expectedResponse: 5,
|
|
expectedError: nil,
|
|
},
|
|
{ // 2. Return 0 when the members do not have the same score
|
|
preset: true,
|
|
presetValue: NewSortedSet([]MemberParam{
|
|
{value: "a", score: Score(5.5)},
|
|
{value: "b", score: Score(67.77)},
|
|
{value: "c", score: Score(10)},
|
|
{value: "d", score: Score(1083.13)},
|
|
{value: "e", score: Score(11)},
|
|
{value: "f", score: Score(math.Inf(-1))},
|
|
{value: "g", score: Score(math.Inf(1))},
|
|
}),
|
|
key: "key2",
|
|
command: []string{"ZLEXCOUNT", "key2", "a", "b"},
|
|
expectedValue: nil,
|
|
expectedResponse: 0,
|
|
expectedError: nil,
|
|
},
|
|
{ // 3. Return 0 when the key does not exist
|
|
preset: false,
|
|
presetValue: nil,
|
|
key: "key3",
|
|
command: []string{"ZLEXCOUNT", "key3", "a", "z"},
|
|
expectedValue: nil,
|
|
expectedResponse: 0,
|
|
expectedError: nil,
|
|
},
|
|
{ // 4. Return error when the value at the key is not a sorted set
|
|
preset: true,
|
|
presetValue: "Default value",
|
|
key: "key4",
|
|
command: []string{"ZLEXCOUNT", "key4", "a", "z"},
|
|
expectedValue: nil,
|
|
expectedResponse: 0,
|
|
expectedError: errors.New("value at key4 is not a sorted set"),
|
|
},
|
|
{ // 5. Command is too short
|
|
preset: false,
|
|
presetValue: nil,
|
|
key: "key5",
|
|
command: []string{"ZLEXCOUNT"},
|
|
expectedValue: nil,
|
|
expectedResponse: 0,
|
|
expectedError: errors.New(utils.WRONG_ARGS_RESPONSE),
|
|
},
|
|
{ // 6. Command too long
|
|
preset: false,
|
|
presetValue: nil,
|
|
key: "key6",
|
|
command: []string{"ZLEXCOUNT", "key6", "min", "max", "count"},
|
|
expectedValue: nil,
|
|
expectedResponse: 0,
|
|
expectedError: errors.New(utils.WRONG_ARGS_RESPONSE),
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
if test.preset {
|
|
if _, err := mockServer.CreateKeyAndLock(context.Background(), test.key); err != nil {
|
|
t.Error(err)
|
|
}
|
|
mockServer.SetValue(context.Background(), test.key, test.presetValue)
|
|
mockServer.KeyUnlock(test.key)
|
|
}
|
|
res, err := handleZLEXCOUNT(context.Background(), test.command, mockServer, nil)
|
|
if test.expectedError != nil {
|
|
if err.Error() != test.expectedError.Error() {
|
|
t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error())
|
|
}
|
|
continue
|
|
}
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
rd := resp.NewReader(bytes.NewReader(res))
|
|
rv, _, err := rd.ReadValue()
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
if rv.Integer() != test.expectedResponse {
|
|
t.Errorf("expected response %d at key \"%s\", got %d", test.expectedResponse, test.key, rv.Integer())
|
|
}
|
|
}
|
|
}
|
|
|
|
func Test_HandleZDIFF(t *testing.T) {
|
|
mockServer := server.NewServer(server.Opts{})
|
|
|
|
tests := []struct {
|
|
preset bool
|
|
presetValues map[string]interface{}
|
|
command []string
|
|
expectedResponse [][]string
|
|
expectedError error
|
|
}{
|
|
{ // 1. Get the difference between 2 sorted sets without scores.
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key1": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1},
|
|
{value: "two", score: 2},
|
|
{value: "three", score: 3},
|
|
{value: "four", score: 4},
|
|
}),
|
|
"key2": NewSortedSet([]MemberParam{
|
|
{value: "three", score: 3},
|
|
{value: "four", score: 4},
|
|
{value: "five", score: 5},
|
|
{value: "six", score: 6},
|
|
{value: "seven", score: 7},
|
|
{value: "eight", score: 8},
|
|
}),
|
|
},
|
|
command: []string{"ZDIFF", "key1", "key2"},
|
|
expectedResponse: [][]string{{"one"}, {"two"}},
|
|
expectedError: nil,
|
|
},
|
|
{ // 2. Get the difference between 2 sorted sets with scores.
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key1": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1},
|
|
{value: "two", score: 2},
|
|
{value: "three", score: 3},
|
|
{value: "four", score: 4},
|
|
}),
|
|
"key2": NewSortedSet([]MemberParam{
|
|
{value: "three", score: 3},
|
|
{value: "four", score: 4},
|
|
{value: "five", score: 5},
|
|
{value: "six", score: 6},
|
|
{value: "seven", score: 7},
|
|
{value: "eight", score: 8},
|
|
}),
|
|
},
|
|
command: []string{"ZDIFF", "key1", "key2", "WITHSCORES"},
|
|
expectedResponse: [][]string{{"one", "1"}, {"two", "2"}},
|
|
expectedError: nil,
|
|
},
|
|
{ // 3. Get the difference between 3 sets with scores.
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key3": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5}, {value: "six", score: 6},
|
|
{value: "seven", score: 7}, {value: "eight", score: 8},
|
|
}),
|
|
"key4": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "thirty-six", score: 36}, {value: "twelve", score: 12},
|
|
{value: "eleven", score: 11},
|
|
}),
|
|
"key5": NewSortedSet([]MemberParam{
|
|
{value: "seven", score: 7}, {value: "eight", score: 8},
|
|
{value: "nine", score: 9}, {value: "ten", score: 10},
|
|
{value: "twelve", score: 12},
|
|
}),
|
|
},
|
|
command: []string{"ZDIFF", "key3", "key4", "key5", "WITHSCORES"},
|
|
expectedResponse: [][]string{{"three", "3"}, {"four", "4"}, {"five", "5"}, {"six", "6"}},
|
|
expectedError: nil,
|
|
},
|
|
{ // 3. Return sorted set if only one key exists and is a sorted set
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key6": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5}, {value: "six", score: 6},
|
|
{value: "seven", score: 7}, {value: "eight", score: 8},
|
|
}),
|
|
},
|
|
command: []string{"ZDIFF", "key6", "key7", "key8", "WITHSCORES"},
|
|
expectedResponse: [][]string{
|
|
{"one", "1"}, {"two", "2"}, {"three", "3"}, {"four", "4"}, {"five", "5"},
|
|
{"six", "6"}, {"seven", "7"}, {"eight", "8"},
|
|
},
|
|
expectedError: nil,
|
|
},
|
|
{ // 4. Throw error when one of the keys is not a sorted set.
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key9": "Default value",
|
|
"key10": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "thirty-six", score: 36}, {value: "twelve", score: 12},
|
|
{value: "eleven", score: 11},
|
|
}),
|
|
"key11": NewSortedSet([]MemberParam{
|
|
{value: "seven", score: 7}, {value: "eight", score: 8},
|
|
{value: "nine", score: 9}, {value: "ten", score: 10},
|
|
{value: "twelve", score: 12},
|
|
}),
|
|
},
|
|
command: []string{"ZDIFF", "key9", "key10", "key11"},
|
|
expectedResponse: nil,
|
|
expectedError: errors.New("value at key9 is not a sorted set"),
|
|
},
|
|
{ // 6. Command too short
|
|
preset: false,
|
|
command: []string{"ZDIFF"},
|
|
expectedResponse: [][]string{},
|
|
expectedError: errors.New(utils.WRONG_ARGS_RESPONSE),
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
if test.preset {
|
|
for key, value := range test.presetValues {
|
|
if _, err := mockServer.CreateKeyAndLock(context.Background(), key); err != nil {
|
|
t.Error(err)
|
|
}
|
|
mockServer.SetValue(context.Background(), key, value)
|
|
mockServer.KeyUnlock(key)
|
|
}
|
|
}
|
|
res, err := handleZDIFF(context.Background(), test.command, mockServer, nil)
|
|
if test.expectedError != nil {
|
|
if err.Error() != test.expectedError.Error() {
|
|
t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error())
|
|
}
|
|
continue
|
|
}
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
rd := resp.NewReader(bytes.NewBuffer(res))
|
|
rv, _, err := rd.ReadValue()
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
for _, element := range rv.Array() {
|
|
if !slices.ContainsFunc(test.expectedResponse, func(expected []string) bool {
|
|
// The current sub-slice is a different length, return false because they're not equal
|
|
if len(element.Array()) != len(expected) {
|
|
return false
|
|
}
|
|
for i := 0; i < len(expected); i++ {
|
|
if element.Array()[i].String() != expected[i] {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}) {
|
|
t.Errorf("expected response %+v, got %+v", test.expectedResponse, rv.Array())
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func Test_HandleZDIFFSTORE(t *testing.T) {
|
|
mockServer := server.NewServer(server.Opts{})
|
|
|
|
tests := []struct {
|
|
preset bool
|
|
presetValues map[string]interface{}
|
|
destination string
|
|
command []string
|
|
expectedValue *SortedSet
|
|
expectedResponse int
|
|
expectedError error
|
|
}{
|
|
{ // 1. Get the difference between 2 sorted sets.
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key1": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5},
|
|
}),
|
|
"key2": NewSortedSet([]MemberParam{
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5}, {value: "six", score: 6},
|
|
{value: "seven", score: 7}, {value: "eight", score: 8},
|
|
}),
|
|
},
|
|
destination: "destination1",
|
|
command: []string{"ZDIFFSTORE", "destination1", "key1", "key2"},
|
|
expectedValue: NewSortedSet([]MemberParam{{value: "one", score: 1}, {value: "two", score: 2}}),
|
|
expectedResponse: 2,
|
|
expectedError: nil,
|
|
},
|
|
{ // 2. Get the difference between 3 sorted sets.
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key3": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5}, {value: "six", score: 6},
|
|
{value: "seven", score: 7}, {value: "eight", score: 8},
|
|
}),
|
|
"key4": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "thirty-six", score: 36}, {value: "twelve", score: 12},
|
|
{value: "eleven", score: 11},
|
|
}),
|
|
"key5": NewSortedSet([]MemberParam{
|
|
{value: "seven", score: 7}, {value: "eight", score: 8},
|
|
{value: "nine", score: 9}, {value: "ten", score: 10},
|
|
{value: "twelve", score: 12},
|
|
}),
|
|
},
|
|
destination: "destination2",
|
|
command: []string{"ZDIFFSTORE", "destination2", "key3", "key4", "key5"},
|
|
expectedValue: NewSortedSet([]MemberParam{
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5}, {value: "six", score: 6},
|
|
}),
|
|
expectedResponse: 4,
|
|
expectedError: nil,
|
|
},
|
|
{ // 3. Return base sorted set element if base set is the only existing key provided and is a valid sorted set
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key6": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5}, {value: "six", score: 6},
|
|
{value: "seven", score: 7}, {value: "eight", score: 8},
|
|
}),
|
|
},
|
|
destination: "destination3",
|
|
command: []string{"ZDIFFSTORE", "destination3", "key6", "key7", "key8"},
|
|
expectedValue: NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5}, {value: "six", score: 6},
|
|
{value: "seven", score: 7}, {value: "eight", score: 8},
|
|
}),
|
|
expectedResponse: 8,
|
|
expectedError: nil,
|
|
},
|
|
{ // 4. Throw error when base sorted set is not a set.
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key9": "Default value",
|
|
"key10": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "thirty-six", score: 36}, {value: "twelve", score: 12},
|
|
{value: "eleven", score: 11},
|
|
}),
|
|
"key11": NewSortedSet([]MemberParam{
|
|
{value: "seven", score: 7}, {value: "eight", score: 8},
|
|
{value: "nine", score: 9}, {value: "ten", score: 10},
|
|
{value: "twelve", score: 12},
|
|
}),
|
|
},
|
|
destination: "destination4",
|
|
command: []string{"ZDIFFSTORE", "destination4", "key9", "key10", "key11"},
|
|
expectedValue: nil,
|
|
expectedResponse: 0,
|
|
expectedError: errors.New("value at key9 is not a sorted set"),
|
|
},
|
|
{ // 5. Throw error when base set is non-existent.
|
|
preset: true,
|
|
destination: "destination5",
|
|
presetValues: map[string]interface{}{
|
|
"key12": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "thirty-six", score: 36}, {value: "twelve", score: 12},
|
|
{value: "eleven", score: 11},
|
|
}),
|
|
"key13": NewSortedSet([]MemberParam{
|
|
{value: "seven", score: 7}, {value: "eight", score: 8},
|
|
{value: "nine", score: 9}, {value: "ten", score: 10},
|
|
{value: "twelve", score: 12},
|
|
}),
|
|
},
|
|
command: []string{"ZDIFFSTORE", "destination5", "non-existent", "key12", "key13"},
|
|
expectedValue: nil,
|
|
expectedResponse: 0,
|
|
expectedError: nil,
|
|
},
|
|
{ // 6. Command too short
|
|
preset: false,
|
|
command: []string{"ZDIFFSTORE", "destination6"},
|
|
expectedResponse: 0,
|
|
expectedError: errors.New(utils.WRONG_ARGS_RESPONSE),
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
if test.preset {
|
|
for key, value := range test.presetValues {
|
|
if _, err := mockServer.CreateKeyAndLock(context.Background(), key); err != nil {
|
|
t.Error(err)
|
|
}
|
|
mockServer.SetValue(context.Background(), key, value)
|
|
mockServer.KeyUnlock(key)
|
|
}
|
|
}
|
|
res, err := handleZDIFFSTORE(context.Background(), test.command, mockServer, nil)
|
|
if test.expectedError != nil {
|
|
if err.Error() != test.expectedError.Error() {
|
|
t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error())
|
|
}
|
|
continue
|
|
}
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
rd := resp.NewReader(bytes.NewBuffer(res))
|
|
rv, _, err := rd.ReadValue()
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
if rv.Integer() != test.expectedResponse {
|
|
t.Errorf("expected response integer %d, got %d", test.expectedResponse, rv.Integer())
|
|
}
|
|
if test.expectedValue != nil {
|
|
if _, err = mockServer.KeyRLock(context.Background(), test.destination); err != nil {
|
|
t.Error(err)
|
|
}
|
|
set, ok := mockServer.GetValue(test.destination).(*SortedSet)
|
|
if !ok {
|
|
t.Errorf("expected vaule at key %s to be set, got another type", test.destination)
|
|
}
|
|
for _, elem := range set.GetAll() {
|
|
if !test.expectedValue.Contains(elem.value) {
|
|
t.Errorf("could not find element %s in the expected values", elem.value)
|
|
}
|
|
}
|
|
mockServer.KeyRUnlock(test.destination)
|
|
}
|
|
}
|
|
}
|
|
|
|
func Test_HandleZINCRBY(t *testing.T) {
|
|
mockServer := server.NewServer(server.Opts{})
|
|
|
|
tests := []struct {
|
|
preset bool
|
|
presetValue interface{}
|
|
key string
|
|
command []string
|
|
expectedValue *SortedSet
|
|
expectedResponse string
|
|
expectedError error
|
|
}{
|
|
{ // 1. Successfully increment by int. Return the new score
|
|
preset: true,
|
|
presetValue: NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5},
|
|
}),
|
|
key: "key1",
|
|
command: []string{"ZINCRBY", "key1", "5", "one"},
|
|
expectedValue: NewSortedSet([]MemberParam{
|
|
{value: "one", score: 6}, {value: "two", score: 2},
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5},
|
|
}),
|
|
expectedResponse: "6",
|
|
expectedError: nil,
|
|
},
|
|
{ // 2. Successfully increment by float. Return new score
|
|
preset: true,
|
|
presetValue: NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5},
|
|
}),
|
|
key: "key2",
|
|
command: []string{"ZINCRBY", "key2", "346.785", "one"},
|
|
expectedValue: NewSortedSet([]MemberParam{
|
|
{value: "one", score: 347.785}, {value: "two", score: 2},
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5},
|
|
}),
|
|
expectedResponse: "347.785",
|
|
expectedError: nil,
|
|
},
|
|
{ // 3. Increment on non-existent sorted set will create the set with the member and increment as its score
|
|
preset: false,
|
|
presetValue: nil,
|
|
key: "key3",
|
|
command: []string{"ZINCRBY", "key3", "346.785", "one"},
|
|
expectedValue: NewSortedSet([]MemberParam{
|
|
{value: "one", score: 346.785},
|
|
}),
|
|
expectedResponse: "346.785",
|
|
expectedError: nil,
|
|
},
|
|
{ // 4. Increment score to +inf
|
|
preset: true,
|
|
presetValue: NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5},
|
|
}),
|
|
key: "key4",
|
|
command: []string{"ZINCRBY", "key4", "+inf", "one"},
|
|
expectedValue: NewSortedSet([]MemberParam{
|
|
{value: "one", score: Score(math.Inf(1))}, {value: "two", score: 2},
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5},
|
|
}),
|
|
expectedResponse: "+Inf",
|
|
expectedError: nil,
|
|
},
|
|
{ // 5. Increment score to -inf
|
|
preset: true,
|
|
presetValue: NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5},
|
|
}),
|
|
key: "key5",
|
|
command: []string{"ZINCRBY", "key5", "-inf", "one"},
|
|
expectedValue: NewSortedSet([]MemberParam{
|
|
{value: "one", score: Score(math.Inf(-1))}, {value: "two", score: 2},
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5},
|
|
}),
|
|
expectedResponse: "-Inf",
|
|
expectedError: nil,
|
|
},
|
|
{ // 6. Incrementing score by negative increment should lower the score
|
|
preset: true,
|
|
presetValue: NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5},
|
|
}),
|
|
key: "key6",
|
|
command: []string{"ZINCRBY", "key6", "-2.5", "five"},
|
|
expectedValue: NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 2.5},
|
|
}),
|
|
expectedResponse: "2.5",
|
|
expectedError: nil,
|
|
},
|
|
{ // 7. Return error when attempting to increment on a value that is not a valid sorted set
|
|
preset: true,
|
|
presetValue: "Default value",
|
|
key: "key7",
|
|
command: []string{"ZINCRBY", "key7", "-2.5", "five"},
|
|
expectedValue: nil,
|
|
expectedResponse: "",
|
|
expectedError: errors.New("value at key7 is not a sorted set"),
|
|
},
|
|
{ // 8. Return error when trying to increment a member that already has score -inf
|
|
preset: true,
|
|
presetValue: NewSortedSet([]MemberParam{
|
|
{value: "one", score: Score(math.Inf(-1))},
|
|
}),
|
|
key: "key8",
|
|
command: []string{"ZINCRBY", "key8", "2.5", "one"},
|
|
expectedValue: NewSortedSet([]MemberParam{
|
|
{value: "one", score: Score(math.Inf(-1))},
|
|
}),
|
|
expectedResponse: "",
|
|
expectedError: errors.New("cannot increment -inf or +inf"),
|
|
},
|
|
{ // 9. Return error when trying to increment a member that already has score +inf
|
|
preset: true,
|
|
presetValue: NewSortedSet([]MemberParam{
|
|
{value: "one", score: Score(math.Inf(1))},
|
|
}),
|
|
key: "key9",
|
|
command: []string{"ZINCRBY", "key9", "2.5", "one"},
|
|
expectedValue: NewSortedSet([]MemberParam{
|
|
{value: "one", score: Score(math.Inf(-1))},
|
|
}),
|
|
expectedResponse: "",
|
|
expectedError: errors.New("cannot increment -inf or +inf"),
|
|
},
|
|
{ // 10. Return error when increment is not a valid number
|
|
preset: true,
|
|
presetValue: NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1},
|
|
}),
|
|
key: "key10",
|
|
command: []string{"ZINCRBY", "key10", "increment", "one"},
|
|
expectedValue: NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1},
|
|
}),
|
|
expectedResponse: "",
|
|
expectedError: errors.New("increment must be a double"),
|
|
},
|
|
{ // 11. Command too short
|
|
key: "key11",
|
|
command: []string{"ZINCRBY", "key11", "one"},
|
|
expectedResponse: "",
|
|
expectedError: errors.New(utils.WRONG_ARGS_RESPONSE),
|
|
},
|
|
{ // 12. Command too long
|
|
key: "key12",
|
|
command: []string{"ZINCRBY", "key12", "one", "1", "2"},
|
|
expectedResponse: "",
|
|
expectedError: errors.New(utils.WRONG_ARGS_RESPONSE),
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
if test.preset {
|
|
if _, err := mockServer.CreateKeyAndLock(context.Background(), test.key); err != nil {
|
|
t.Error(err)
|
|
}
|
|
mockServer.SetValue(context.Background(), test.key, test.presetValue)
|
|
mockServer.KeyUnlock(test.key)
|
|
}
|
|
res, err := handleZINCRBY(context.Background(), test.command, mockServer, nil)
|
|
if test.expectedError != nil {
|
|
if err.Error() != test.expectedError.Error() {
|
|
t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error())
|
|
}
|
|
continue
|
|
}
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
rd := resp.NewReader(bytes.NewBuffer(res))
|
|
rv, _, err := rd.ReadValue()
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
if rv.String() != test.expectedResponse {
|
|
t.Errorf("expected response integer %s, got %s", test.expectedResponse, rv.String())
|
|
}
|
|
if test.expectedValue != nil {
|
|
if _, err = mockServer.KeyRLock(context.Background(), test.key); err != nil {
|
|
t.Error(err)
|
|
}
|
|
set, ok := mockServer.GetValue(test.key).(*SortedSet)
|
|
if !ok {
|
|
t.Errorf("expected vaule at key %s to be set, got another type", test.key)
|
|
}
|
|
for _, elem := range set.GetAll() {
|
|
if !test.expectedValue.Contains(elem.value) {
|
|
t.Errorf("could not find element %s in the expected values", elem.value)
|
|
}
|
|
if test.expectedValue.Get(elem.value).score != elem.score {
|
|
t.Errorf("expected score of element \"%s\" from set at key \"%s\" to be %s, got %s",
|
|
elem.value, test.key,
|
|
strconv.FormatFloat(float64(test.expectedValue.Get(elem.value).score), 'f', -1, 64),
|
|
strconv.FormatFloat(float64(elem.score), 'f', -1, 64),
|
|
)
|
|
}
|
|
}
|
|
mockServer.KeyRUnlock(test.key)
|
|
}
|
|
}
|
|
}
|
|
|
|
func Test_HandleZMPOP(t *testing.T) {
|
|
mockServer := server.NewServer(server.Opts{})
|
|
|
|
tests := []struct {
|
|
preset bool
|
|
presetValues map[string]interface{}
|
|
command []string
|
|
expectedValues map[string]*SortedSet
|
|
expectedResponse [][]string
|
|
expectedError error
|
|
}{
|
|
{ // 1. Successfully pop one min element by default
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key1": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5},
|
|
}),
|
|
},
|
|
command: []string{"ZMPOP", "key1"},
|
|
expectedValues: map[string]*SortedSet{
|
|
"key1": NewSortedSet([]MemberParam{
|
|
{value: "two", score: 2},
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5},
|
|
}),
|
|
},
|
|
expectedResponse: [][]string{
|
|
{"one", "1"},
|
|
},
|
|
expectedError: nil,
|
|
},
|
|
{ // 2. Successfully pop one min element by specifying MIN
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key2": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5},
|
|
}),
|
|
},
|
|
command: []string{"ZMPOP", "key2", "MIN"},
|
|
expectedValues: map[string]*SortedSet{
|
|
"key2": NewSortedSet([]MemberParam{
|
|
{value: "two", score: 2},
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5},
|
|
}),
|
|
},
|
|
expectedResponse: [][]string{
|
|
{"one", "1"},
|
|
},
|
|
expectedError: nil,
|
|
},
|
|
{ // 3. Successfully pop one max element by specifying MAX modifier
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key3": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5},
|
|
}),
|
|
},
|
|
command: []string{"ZMPOP", "key3", "MAX"},
|
|
expectedValues: map[string]*SortedSet{
|
|
"key3": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
}),
|
|
},
|
|
expectedResponse: [][]string{
|
|
{"five", "5"},
|
|
},
|
|
expectedError: nil,
|
|
},
|
|
{ // 4. Successfully pop multiple min elements
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key4": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5}, {value: "six", score: 6},
|
|
}),
|
|
},
|
|
command: []string{"ZMPOP", "key4", "MIN", "COUNT", "5"},
|
|
expectedValues: map[string]*SortedSet{
|
|
"key4": NewSortedSet([]MemberParam{
|
|
{value: "six", score: 6},
|
|
}),
|
|
},
|
|
expectedResponse: [][]string{
|
|
{"one", "1"}, {"two", "2"}, {"three", "3"},
|
|
{"four", "4"}, {"five", "5"},
|
|
},
|
|
expectedError: nil,
|
|
},
|
|
{ // 5. Successfully pop multiple max elements
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key5": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5}, {value: "six", score: 6},
|
|
}),
|
|
},
|
|
command: []string{"ZMPOP", "key5", "MAX", "COUNT", "5"},
|
|
expectedValues: map[string]*SortedSet{
|
|
"key5": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1},
|
|
}),
|
|
},
|
|
expectedResponse: [][]string{{"two", "2"}, {"three", "3"}, {"four", "4"}, {"five", "5"}, {"six", "6"}},
|
|
expectedError: nil,
|
|
},
|
|
{ // 6. Successfully pop elements from the first set which is non-empty
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key6": NewSortedSet([]MemberParam{}),
|
|
"key7": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5}, {value: "six", score: 6},
|
|
}),
|
|
},
|
|
command: []string{"ZMPOP", "key6", "key7", "MAX", "COUNT", "5"},
|
|
expectedValues: map[string]*SortedSet{
|
|
"key6": NewSortedSet([]MemberParam{}),
|
|
"key7": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1},
|
|
}),
|
|
},
|
|
expectedResponse: [][]string{{"two", "2"}, {"three", "3"}, {"four", "4"}, {"five", "5"}, {"six", "6"}},
|
|
expectedError: nil,
|
|
},
|
|
{ // 7. Skip the non-set items and pop elements from the first non-empty sorted set found
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key8": "Default value",
|
|
"key9": 56,
|
|
"key10": NewSortedSet([]MemberParam{}),
|
|
"key11": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5}, {value: "six", score: 6},
|
|
}),
|
|
},
|
|
command: []string{"ZMPOP", "key8", "key9", "key10", "key11", "MIN", "COUNT", "5"},
|
|
expectedValues: map[string]*SortedSet{
|
|
"key10": NewSortedSet([]MemberParam{}),
|
|
"key11": NewSortedSet([]MemberParam{
|
|
{value: "six", score: 6},
|
|
}),
|
|
},
|
|
expectedResponse: [][]string{{"one", "1"}, {"two", "2"}, {"three", "3"}, {"four", "4"}, {"five", "5"}},
|
|
expectedError: nil,
|
|
},
|
|
{ // 9. Return error when count is a negative integer
|
|
preset: false,
|
|
command: []string{"ZMPOP", "key8", "MAX", "COUNT", "-20"},
|
|
expectedError: errors.New("count must be a positive integer"),
|
|
},
|
|
{ // 9. Command too short
|
|
preset: false,
|
|
command: []string{"ZMPOP"},
|
|
expectedError: errors.New(utils.WRONG_ARGS_RESPONSE),
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
if test.preset {
|
|
for key, value := range test.presetValues {
|
|
if _, err := mockServer.CreateKeyAndLock(context.Background(), key); err != nil {
|
|
t.Error(err)
|
|
}
|
|
mockServer.SetValue(context.Background(), key, value)
|
|
mockServer.KeyUnlock(key)
|
|
}
|
|
}
|
|
res, err := handleZMPOP(context.Background(), test.command, mockServer, nil)
|
|
if test.expectedError != nil {
|
|
if err.Error() != test.expectedError.Error() {
|
|
t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error())
|
|
}
|
|
continue
|
|
}
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
rd := resp.NewReader(bytes.NewBuffer(res))
|
|
rv, _, err := rd.ReadValue()
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
for _, element := range rv.Array() {
|
|
if !slices.ContainsFunc(test.expectedResponse, func(expected []string) bool {
|
|
// The current sub-slice is a different length, return false because they're not equal
|
|
if len(element.Array()) != len(expected) {
|
|
return false
|
|
}
|
|
for i := 0; i < len(expected); i++ {
|
|
if element.Array()[i].String() != expected[i] {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}) {
|
|
t.Errorf("expected response %+v, got %+v", test.expectedResponse, rv.Array())
|
|
}
|
|
}
|
|
for key, expectedSortedSet := range test.expectedValues {
|
|
if _, err = mockServer.KeyRLock(context.Background(), key); err != nil {
|
|
t.Error(err)
|
|
}
|
|
set, ok := mockServer.GetValue(key).(*SortedSet)
|
|
if !ok {
|
|
t.Errorf("expected key \"%s\" to be a sorted set, got another type", key)
|
|
}
|
|
if !set.Equals(expectedSortedSet) {
|
|
t.Errorf("expected sorted set at key \"%s\" %+v, got %+v", key, expectedSortedSet, set)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func Test_HandleZPOP(t *testing.T) {
|
|
mockServer := server.NewServer(server.Opts{})
|
|
|
|
tests := []struct {
|
|
preset bool
|
|
presetValues map[string]interface{}
|
|
command []string
|
|
expectedValues map[string]*SortedSet
|
|
expectedResponse [][]string
|
|
expectedError error
|
|
}{
|
|
{ // 1. Successfully pop one min element by default
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key1": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5},
|
|
}),
|
|
},
|
|
command: []string{"ZPOPMIN", "key1"},
|
|
expectedValues: map[string]*SortedSet{
|
|
"key1": NewSortedSet([]MemberParam{
|
|
{value: "two", score: 2},
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5},
|
|
}),
|
|
},
|
|
expectedResponse: [][]string{
|
|
{"one", "1"},
|
|
},
|
|
expectedError: nil,
|
|
},
|
|
{ // 2. Successfully pop one max element by default
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key2": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5},
|
|
}),
|
|
},
|
|
command: []string{"ZPOPMAX", "key2"},
|
|
expectedValues: map[string]*SortedSet{
|
|
"key2": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
}),
|
|
},
|
|
expectedResponse: [][]string{
|
|
{"five", "5"},
|
|
},
|
|
expectedError: nil,
|
|
},
|
|
{ // 3. Successfully pop multiple min elements
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key3": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5}, {value: "six", score: 6},
|
|
}),
|
|
},
|
|
command: []string{"ZPOPMIN", "key3", "5"},
|
|
expectedValues: map[string]*SortedSet{
|
|
"key3": NewSortedSet([]MemberParam{
|
|
{value: "six", score: 6},
|
|
}),
|
|
},
|
|
expectedResponse: [][]string{
|
|
{"one", "1"}, {"two", "2"}, {"three", "3"},
|
|
{"four", "4"}, {"five", "5"},
|
|
},
|
|
expectedError: nil,
|
|
},
|
|
{ // 4. Successfully pop multiple max elements
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key4": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5}, {value: "six", score: 6},
|
|
}),
|
|
},
|
|
command: []string{"ZPOPMAX", "key4", "5"},
|
|
expectedValues: map[string]*SortedSet{
|
|
"key4": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1},
|
|
}),
|
|
},
|
|
expectedResponse: [][]string{{"two", "2"}, {"three", "3"}, {"four", "4"}, {"five", "5"}, {"six", "6"}},
|
|
expectedError: nil,
|
|
},
|
|
{ // 5. Throw an error when trying to pop from an element that's not a sorted set
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key5": "Default value",
|
|
},
|
|
command: []string{"ZPOPMIN", "key5"},
|
|
expectedValues: nil,
|
|
expectedResponse: nil,
|
|
expectedError: errors.New("value at key key5 is not a sorted set"),
|
|
},
|
|
{ // 6. Command too short
|
|
preset: false,
|
|
command: []string{"ZPOPMAX"},
|
|
expectedError: errors.New(utils.WRONG_ARGS_RESPONSE),
|
|
},
|
|
{ // 7. Command too long
|
|
preset: false,
|
|
command: []string{"ZPOPMAX", "key7", "6", "3"},
|
|
expectedError: errors.New(utils.WRONG_ARGS_RESPONSE),
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
if test.preset {
|
|
for key, value := range test.presetValues {
|
|
if _, err := mockServer.CreateKeyAndLock(context.Background(), key); err != nil {
|
|
t.Error(err)
|
|
}
|
|
mockServer.SetValue(context.Background(), key, value)
|
|
mockServer.KeyUnlock(key)
|
|
}
|
|
}
|
|
res, err := handleZPOP(context.Background(), test.command, mockServer, nil)
|
|
if test.expectedError != nil {
|
|
if err.Error() != test.expectedError.Error() {
|
|
t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error())
|
|
}
|
|
continue
|
|
}
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
rd := resp.NewReader(bytes.NewBuffer(res))
|
|
rv, _, err := rd.ReadValue()
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
for _, element := range rv.Array() {
|
|
if !slices.ContainsFunc(test.expectedResponse, func(expected []string) bool {
|
|
// The current sub-slice is a different length, return false because they're not equal
|
|
if len(element.Array()) != len(expected) {
|
|
return false
|
|
}
|
|
for i := 0; i < len(expected); i++ {
|
|
if element.Array()[i].String() != expected[i] {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}) {
|
|
t.Errorf("expected response %+v, got %+v", test.expectedResponse, rv.Array())
|
|
}
|
|
}
|
|
for key, expectedSortedSet := range test.expectedValues {
|
|
if _, err = mockServer.KeyRLock(context.Background(), key); err != nil {
|
|
t.Error(err)
|
|
}
|
|
set, ok := mockServer.GetValue(key).(*SortedSet)
|
|
if !ok {
|
|
t.Errorf("expected key \"%s\" to be a sorted set, got another type", key)
|
|
}
|
|
if !set.Equals(expectedSortedSet) {
|
|
t.Errorf("expected sorted set at key \"%s\" %+v, got %+v", key, expectedSortedSet, set)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func Test_HandleZMSCORE(t *testing.T) {
|
|
mockServer := server.NewServer(server.Opts{})
|
|
|
|
tests := []struct {
|
|
preset bool
|
|
presetValues map[string]interface{}
|
|
command []string
|
|
expectedResponse []interface{}
|
|
expectedError error
|
|
}{
|
|
{ // 1. Return multiple scores from the sorted set.
|
|
// Return nil for elements that do not exist in the sorted set.
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key1": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1.1}, {value: "two", score: 245},
|
|
{value: "three", score: 3}, {value: "four", score: 4.055},
|
|
{value: "five", score: 5},
|
|
}),
|
|
},
|
|
command: []string{"ZMSCORE", "key1", "one", "none", "two", "one", "three", "four", "none", "five"},
|
|
expectedResponse: []interface{}{"1.1", nil, "245", "1.1", "3", "4.055", nil, "5"},
|
|
expectedError: nil,
|
|
},
|
|
{ // 2. If key does not exist, return empty array
|
|
preset: false,
|
|
presetValues: nil,
|
|
command: []string{"ZMSCORE", "key2", "one", "two", "three", "four"},
|
|
expectedResponse: []interface{}{},
|
|
expectedError: nil,
|
|
},
|
|
{ // 3. Throw error when trying to find scores from elements that are not sorted sets
|
|
preset: true,
|
|
presetValues: map[string]interface{}{"key3": "Default value"},
|
|
command: []string{"ZMSCORE", "key3", "one", "two", "three"},
|
|
expectedError: errors.New("value at key3 is not a sorted set"),
|
|
},
|
|
{ // 9. Command too short
|
|
preset: false,
|
|
command: []string{"ZMSCORE"},
|
|
expectedError: errors.New(utils.WRONG_ARGS_RESPONSE),
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
if test.preset {
|
|
for key, value := range test.presetValues {
|
|
if _, err := mockServer.CreateKeyAndLock(context.Background(), key); err != nil {
|
|
t.Error(err)
|
|
}
|
|
mockServer.SetValue(context.Background(), key, value)
|
|
mockServer.KeyUnlock(key)
|
|
}
|
|
}
|
|
res, err := handleZMSCORE(context.Background(), test.command, mockServer, nil)
|
|
if test.expectedError != nil {
|
|
if err.Error() != test.expectedError.Error() {
|
|
t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error())
|
|
}
|
|
continue
|
|
}
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
rd := resp.NewReader(bytes.NewBuffer(res))
|
|
rv, _, err := rd.ReadValue()
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
for i := 0; i < len(rv.Array()); i++ {
|
|
if rv.Array()[i].IsNull() {
|
|
if test.expectedResponse[i] != nil {
|
|
t.Errorf("expected element at index %d to be %+v, got %+v", i, test.expectedResponse[i], rv.Array()[i])
|
|
}
|
|
continue
|
|
}
|
|
if rv.Array()[i].String() != test.expectedResponse[i] {
|
|
t.Errorf("expected \"%s\" at index %d, got %s", test.expectedResponse[i], i, rv.Array()[i].String())
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func Test_HandleZSCORE(t *testing.T) {
|
|
mockServer := server.NewServer(server.Opts{})
|
|
|
|
tests := []struct {
|
|
preset bool
|
|
presetValues map[string]interface{}
|
|
command []string
|
|
expectedResponse interface{}
|
|
expectedError error
|
|
}{
|
|
{ // 1. Return score from a sorted set.
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key1": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1.1}, {value: "two", score: 245},
|
|
{value: "three", score: 3}, {value: "four", score: 4.055},
|
|
{value: "five", score: 5},
|
|
}),
|
|
},
|
|
command: []string{"ZSCORE", "key1", "four"},
|
|
expectedResponse: "4.055",
|
|
expectedError: nil,
|
|
},
|
|
{ // 2. If key does not exist, return nil value
|
|
preset: false,
|
|
presetValues: nil,
|
|
command: []string{"ZSCORE", "key2", "one"},
|
|
expectedResponse: nil,
|
|
expectedError: nil,
|
|
},
|
|
{ // 3. If key exists and is a sorted set, but the member does not exist, return nil
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key3": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1.1}, {value: "two", score: 245},
|
|
{value: "three", score: 3}, {value: "four", score: 4.055},
|
|
{value: "five", score: 5},
|
|
}),
|
|
},
|
|
command: []string{"ZSCORE", "key3", "non-existent"},
|
|
expectedResponse: nil,
|
|
expectedError: nil,
|
|
},
|
|
{ // 4. Throw error when trying to find scores from elements that are not sorted sets
|
|
preset: true,
|
|
presetValues: map[string]interface{}{"key4": "Default value"},
|
|
command: []string{"ZSCORE", "key4", "one"},
|
|
expectedError: errors.New("value at key4 is not a sorted set"),
|
|
},
|
|
{ // 5. Command too short
|
|
preset: false,
|
|
command: []string{"ZSCORE"},
|
|
expectedError: errors.New(utils.WRONG_ARGS_RESPONSE),
|
|
},
|
|
{ // 6. Command too long
|
|
preset: false,
|
|
command: []string{"ZSCORE", "key5", "one", "two"},
|
|
expectedError: errors.New(utils.WRONG_ARGS_RESPONSE),
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
if test.preset {
|
|
for key, value := range test.presetValues {
|
|
if _, err := mockServer.CreateKeyAndLock(context.Background(), key); err != nil {
|
|
t.Error(err)
|
|
}
|
|
mockServer.SetValue(context.Background(), key, value)
|
|
mockServer.KeyUnlock(key)
|
|
}
|
|
}
|
|
res, err := handleZSCORE(context.Background(), test.command, mockServer, nil)
|
|
if test.expectedError != nil {
|
|
if err.Error() != test.expectedError.Error() {
|
|
t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error())
|
|
}
|
|
continue
|
|
}
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
rd := resp.NewReader(bytes.NewBuffer(res))
|
|
rv, _, err := rd.ReadValue()
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
if test.expectedResponse == nil {
|
|
if !rv.IsNull() {
|
|
t.Errorf("expected nil response, got %+v", rv)
|
|
}
|
|
continue
|
|
}
|
|
if rv.String() != test.expectedResponse {
|
|
t.Errorf("expected response \"%s\", got %s", test.expectedResponse, rv.String())
|
|
}
|
|
}
|
|
}
|
|
|
|
func Test_HandleZRANDMEMBER(t *testing.T) {
|
|
mockServer := server.NewServer(server.Opts{})
|
|
|
|
tests := []struct {
|
|
preset bool
|
|
key string
|
|
presetValue interface{}
|
|
command []string
|
|
expectedValue int // The final cardinality of the resulting set
|
|
allowRepeat bool
|
|
expectedResponse [][]string
|
|
expectedError error
|
|
}{
|
|
{ // 1. Return multiple random elements without removing them
|
|
// Count is positive, do not allow repeated elements
|
|
preset: true,
|
|
key: "key1",
|
|
presetValue: NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2}, {value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5}, {value: "six", score: 6}, {value: "seven", score: 7}, {value: "eight", score: 8},
|
|
}),
|
|
command: []string{"ZRANDMEMBER", "key1", "3"},
|
|
expectedValue: 8,
|
|
allowRepeat: false,
|
|
expectedResponse: [][]string{
|
|
{"one"}, {"two"}, {"three"}, {"four"},
|
|
{"five"}, {"six"}, {"seven"}, {"eight"},
|
|
},
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
// 2. Return multiple random elements and their scores without removing them.
|
|
// Count is negative, so allow repeated numbers.
|
|
preset: true,
|
|
key: "key2",
|
|
presetValue: NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2}, {value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5}, {value: "six", score: 6}, {value: "seven", score: 7}, {value: "eight", score: 8},
|
|
}),
|
|
command: []string{"ZRANDMEMBER", "key2", "-5", "WITHSCORES"},
|
|
expectedValue: 8,
|
|
allowRepeat: true,
|
|
expectedResponse: [][]string{
|
|
{"one", "1"}, {"two", "2"}, {"three", "3"}, {"four", "4"},
|
|
{"five", "5"}, {"six", "6"}, {"seven", "7"}, {"eight", "8"},
|
|
},
|
|
expectedError: nil,
|
|
},
|
|
{ // 2. Return error when the source key is not a sorted set.
|
|
preset: true,
|
|
key: "key3",
|
|
presetValue: "Default value",
|
|
command: []string{"ZRANDMEMBER", "key3"},
|
|
expectedValue: 0,
|
|
expectedError: errors.New("value at key3 is not a sorted set"),
|
|
},
|
|
{ // 5. Command too short
|
|
preset: false,
|
|
command: []string{"ZRANDMEMBER"},
|
|
expectedError: errors.New(utils.WRONG_ARGS_RESPONSE),
|
|
},
|
|
{ // 6. Command too long
|
|
preset: false,
|
|
command: []string{"ZRANDMEMBER", "source5", "source6", "member1", "member2"},
|
|
expectedError: errors.New(utils.WRONG_ARGS_RESPONSE),
|
|
},
|
|
{ // 7. Throw error when count is not an integer
|
|
preset: false,
|
|
command: []string{"SRANDMEMBER", "key1", "count"},
|
|
expectedError: errors.New("count must be an integer"),
|
|
},
|
|
{ // 8. Throw error when the fourth argument is not WITHSCORES
|
|
preset: false,
|
|
command: []string{"SRANDMEMBER", "key1", "8", "ANOTHER"},
|
|
expectedError: errors.New("last option must be WITHSCORES"),
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
if test.preset {
|
|
if _, err := mockServer.CreateKeyAndLock(context.Background(), test.key); err != nil {
|
|
t.Error(err)
|
|
}
|
|
mockServer.SetValue(context.Background(), test.key, test.presetValue)
|
|
mockServer.KeyUnlock(test.key)
|
|
}
|
|
res, err := handleZRANDMEMBER(context.Background(), test.command, mockServer, nil)
|
|
if test.expectedError != nil {
|
|
if err.Error() != test.expectedError.Error() {
|
|
t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error())
|
|
}
|
|
continue
|
|
}
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
rd := resp.NewReader(bytes.NewBuffer(res))
|
|
rv, _, err := rd.ReadValue()
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
// 1. Check if the response array members are all included in test.expectedResponse.
|
|
for _, element := range rv.Array() {
|
|
if !slices.ContainsFunc(test.expectedResponse, func(expected []string) bool {
|
|
// The current sub-slice is a different length, return false because they're not equal
|
|
if len(element.Array()) != len(expected) {
|
|
return false
|
|
}
|
|
for i := 0; i < len(expected); i++ {
|
|
if element.Array()[i].String() != expected[i] {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}) {
|
|
t.Errorf("expected response %+v, got %+v", test.expectedResponse, rv.Array())
|
|
}
|
|
}
|
|
// 2. Fetch the set and check if its cardinality is what we expect.
|
|
if _, err = mockServer.KeyRLock(context.Background(), test.key); err != nil {
|
|
t.Error(err)
|
|
}
|
|
set, ok := mockServer.GetValue(test.key).(*SortedSet)
|
|
if !ok {
|
|
t.Errorf("expected value at key \"%s\" to be a set, got another type", test.key)
|
|
}
|
|
if set.Cardinality() != test.expectedValue {
|
|
t.Errorf("expected cardinality of final set to be %d, got %d", test.expectedValue, set.Cardinality())
|
|
}
|
|
// 3. Check if all the returned elements we received are still in the set.
|
|
for _, element := range rv.Array() {
|
|
if !set.Contains(Value(element.Array()[0].String())) {
|
|
t.Errorf("expected element \"%s\" to be in set but it was not found", element.String())
|
|
}
|
|
}
|
|
// 4. If allowRepeat is false, check that all the elements make a valid set
|
|
if !test.allowRepeat {
|
|
var elems []MemberParam
|
|
for _, e := range rv.Array() {
|
|
if len(e.Array()) == 1 {
|
|
elems = append(elems, MemberParam{
|
|
value: Value(e.Array()[0].String()),
|
|
score: 1,
|
|
})
|
|
continue
|
|
}
|
|
elems = append(elems, MemberParam{
|
|
value: Value(e.Array()[0].String()),
|
|
score: Score(e.Array()[1].Float()),
|
|
})
|
|
}
|
|
s := NewSortedSet(elems)
|
|
if s.Cardinality() != len(elems) {
|
|
t.Errorf("expected non-repeating elements for random elements at key \"%s\"", test.key)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func Test_HandleZRANK(t *testing.T) {
|
|
mockServer := server.NewServer(server.Opts{})
|
|
|
|
tests := []struct {
|
|
preset bool
|
|
presetValues map[string]interface{}
|
|
command []string
|
|
expectedResponse []string
|
|
expectedError error
|
|
}{
|
|
{ // 1. Return element's rank from a sorted set.
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key1": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5},
|
|
}),
|
|
},
|
|
command: []string{"ZRANK", "key1", "four"},
|
|
expectedResponse: []string{"3"},
|
|
expectedError: nil,
|
|
},
|
|
{ // 2. Return element's rank from a sorted set with its score.
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key1": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 100.1}, {value: "two", score: 245},
|
|
{value: "three", score: 305.43}, {value: "four", score: 411.055},
|
|
{value: "five", score: 500},
|
|
}),
|
|
},
|
|
command: []string{"ZRANK", "key1", "four", "WITHSCORES"},
|
|
expectedResponse: []string{"3", "411.055"},
|
|
expectedError: nil,
|
|
},
|
|
{ // 3. If key does not exist, return nil value
|
|
preset: false,
|
|
presetValues: nil,
|
|
command: []string{"ZRANK", "key3", "one"},
|
|
expectedResponse: nil,
|
|
expectedError: nil,
|
|
},
|
|
{ // 4. If key exists and is a sorted set, but the member does not exist, return nil
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key4": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1.1}, {value: "two", score: 245},
|
|
{value: "three", score: 3}, {value: "four", score: 4.055},
|
|
{value: "five", score: 5},
|
|
}),
|
|
},
|
|
command: []string{"ZRANK", "key4", "non-existent"},
|
|
expectedResponse: nil,
|
|
expectedError: nil,
|
|
},
|
|
{ // 5. Throw error when trying to find scores from elements that are not sorted sets
|
|
preset: true,
|
|
presetValues: map[string]interface{}{"key5": "Default value"},
|
|
command: []string{"ZRANK", "key5", "one"},
|
|
expectedError: errors.New("value at key5 is not a sorted set"),
|
|
},
|
|
{ // 5. Command too short
|
|
preset: false,
|
|
command: []string{"ZRANK"},
|
|
expectedError: errors.New(utils.WRONG_ARGS_RESPONSE),
|
|
},
|
|
{ // 6. Command too long
|
|
preset: false,
|
|
command: []string{"ZRANK", "key5", "one", "WITHSCORES", "two"},
|
|
expectedError: errors.New(utils.WRONG_ARGS_RESPONSE),
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
if test.preset {
|
|
for key, value := range test.presetValues {
|
|
if _, err := mockServer.CreateKeyAndLock(context.Background(), key); err != nil {
|
|
t.Error(err)
|
|
}
|
|
mockServer.SetValue(context.Background(), key, value)
|
|
mockServer.KeyUnlock(key)
|
|
}
|
|
}
|
|
res, err := handleZRANK(context.Background(), test.command, mockServer, nil)
|
|
if test.expectedError != nil {
|
|
if err.Error() != test.expectedError.Error() {
|
|
t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error())
|
|
}
|
|
continue
|
|
}
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
rd := resp.NewReader(bytes.NewBuffer(res))
|
|
rv, _, err := rd.ReadValue()
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
if test.expectedResponse == nil {
|
|
if !rv.IsNull() {
|
|
t.Errorf("expected nil response, got %+v", rv)
|
|
}
|
|
continue
|
|
}
|
|
if len(rv.Array()) != len(test.expectedResponse) {
|
|
t.Errorf("expected response %+v, got %+v", test.expectedResponse, rv.Array())
|
|
}
|
|
for i := 0; i < len(test.expectedResponse); i++ {
|
|
if rv.Array()[i].String() != test.expectedResponse[i] {
|
|
t.Errorf("expected element at index %d to be %s, got %s", i, test.expectedResponse[i], rv.Array()[i].String())
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func Test_HandleZREM(t *testing.T) {
|
|
mockServer := server.NewServer(server.Opts{})
|
|
|
|
tests := []struct {
|
|
preset bool
|
|
presetValues map[string]interface{}
|
|
command []string
|
|
expectedValues map[string]*SortedSet
|
|
expectedResponse int
|
|
expectedError error
|
|
}{
|
|
{ // 1. Successfully remove multiple elements from sorted set, skipping non-existent members.
|
|
// Return deleted count.
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key1": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5}, {value: "six", score: 6},
|
|
{value: "seven", score: 7}, {value: "eight", score: 8},
|
|
{value: "nine", score: 9}, {value: "ten", score: 10},
|
|
}),
|
|
},
|
|
command: []string{"ZREM", "key1", "three", "four", "five", "none", "six", "none", "seven"},
|
|
expectedValues: map[string]*SortedSet{
|
|
"key1": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2}, {value: "eight", score: 8},
|
|
{value: "nine", score: 9}, {value: "ten", score: 10},
|
|
}),
|
|
},
|
|
expectedResponse: 5,
|
|
expectedError: nil,
|
|
},
|
|
{ // 2. If key does not exist, return 0
|
|
preset: false,
|
|
presetValues: nil,
|
|
command: []string{"ZREM", "key2", "member"},
|
|
expectedValues: nil,
|
|
expectedResponse: 0,
|
|
expectedError: nil,
|
|
},
|
|
{ // 3. Return error key is not a sorted set
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key3": "Default value",
|
|
},
|
|
command: []string{"ZREM", "key3", "member"},
|
|
expectedError: errors.New("value at key3 is not a sorted set"),
|
|
},
|
|
{ // 9. Command too short
|
|
preset: false,
|
|
command: []string{"ZREM"},
|
|
expectedError: errors.New(utils.WRONG_ARGS_RESPONSE),
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
if test.preset {
|
|
for key, value := range test.presetValues {
|
|
if _, err := mockServer.CreateKeyAndLock(context.Background(), key); err != nil {
|
|
t.Error(err)
|
|
}
|
|
mockServer.SetValue(context.Background(), key, value)
|
|
mockServer.KeyUnlock(key)
|
|
}
|
|
}
|
|
res, err := handleZREM(context.Background(), test.command, mockServer, nil)
|
|
if test.expectedError != nil {
|
|
if err.Error() != test.expectedError.Error() {
|
|
t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error())
|
|
}
|
|
continue
|
|
}
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
rd := resp.NewReader(bytes.NewBuffer(res))
|
|
rv, _, err := rd.ReadValue()
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
if rv.Integer() != test.expectedResponse {
|
|
t.Errorf("expected response %d, got %d", test.expectedResponse, rv.Integer())
|
|
}
|
|
// Check if the expected sorted set is the same at the current one
|
|
if test.expectedValues != nil {
|
|
for key, expectedSet := range test.expectedValues {
|
|
if _, err = mockServer.KeyRLock(context.Background(), key); err != nil {
|
|
t.Error(err)
|
|
}
|
|
set, ok := mockServer.GetValue(key).(*SortedSet)
|
|
if !ok {
|
|
t.Errorf("expected value at key \"%s\" to be a sorted set, got another type", key)
|
|
}
|
|
if !set.Equals(expectedSet) {
|
|
t.Errorf("exptected sorted set %+v, got %+v", expectedSet, set)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func Test_HandleZREMRANGEBYSCORE(t *testing.T) {
|
|
mockServer := server.NewServer(server.Opts{})
|
|
|
|
tests := []struct {
|
|
preset bool
|
|
presetValues map[string]interface{}
|
|
command []string
|
|
expectedValues map[string]*SortedSet
|
|
expectedResponse int
|
|
expectedError error
|
|
}{
|
|
{ // 1. Successfully remove multiple elements with scores inside the provided range
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key1": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5}, {value: "six", score: 6},
|
|
{value: "seven", score: 7}, {value: "eight", score: 8},
|
|
{value: "nine", score: 9}, {value: "ten", score: 10},
|
|
}),
|
|
},
|
|
command: []string{"ZREMRANGEBYSCORE", "key1", "3", "7"},
|
|
expectedValues: map[string]*SortedSet{
|
|
"key1": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2}, {value: "eight", score: 8},
|
|
{value: "nine", score: 9}, {value: "ten", score: 10},
|
|
}),
|
|
},
|
|
expectedResponse: 5,
|
|
expectedError: nil,
|
|
},
|
|
{ // 2. If key does not exist, return 0
|
|
preset: false,
|
|
presetValues: nil,
|
|
command: []string{"ZREMRANGEBYSCORE", "key2", "2", "4"},
|
|
expectedValues: nil,
|
|
expectedResponse: 0,
|
|
expectedError: nil,
|
|
},
|
|
{ // 3. Return error key is not a sorted set
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key3": "Default value",
|
|
},
|
|
command: []string{"ZREMRANGEBYSCORE", "key3", "4", "4"},
|
|
expectedError: errors.New("value at key3 is not a sorted set"),
|
|
},
|
|
{ // 4. Command too short
|
|
preset: false,
|
|
command: []string{"ZREMRANGEBYSCORE", "key4", "3"},
|
|
expectedError: errors.New(utils.WRONG_ARGS_RESPONSE),
|
|
},
|
|
{ // 5. Command too long
|
|
preset: false,
|
|
command: []string{"ZREMRANGEBYSCORE", "key5", "4", "5", "8"},
|
|
expectedError: errors.New(utils.WRONG_ARGS_RESPONSE),
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
if test.preset {
|
|
for key, value := range test.presetValues {
|
|
if _, err := mockServer.CreateKeyAndLock(context.Background(), key); err != nil {
|
|
t.Error(err)
|
|
}
|
|
mockServer.SetValue(context.Background(), key, value)
|
|
mockServer.KeyUnlock(key)
|
|
}
|
|
}
|
|
res, err := handleZREMRANGEBYSCORE(context.Background(), test.command, mockServer, nil)
|
|
if test.expectedError != nil {
|
|
if err.Error() != test.expectedError.Error() {
|
|
t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error())
|
|
}
|
|
continue
|
|
}
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
rd := resp.NewReader(bytes.NewBuffer(res))
|
|
rv, _, err := rd.ReadValue()
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
if rv.Integer() != test.expectedResponse {
|
|
t.Errorf("expected response %d, got %d", test.expectedResponse, rv.Integer())
|
|
}
|
|
// Check if the expected values are the same
|
|
if test.expectedValues != nil {
|
|
for key, expectedSet := range test.expectedValues {
|
|
if _, err = mockServer.KeyRLock(context.Background(), key); err != nil {
|
|
t.Error(err)
|
|
}
|
|
set, ok := mockServer.GetValue(key).(*SortedSet)
|
|
if !ok {
|
|
t.Errorf("expected value at key \"%s\" to be a sorted set, got another type", key)
|
|
}
|
|
if !set.Equals(expectedSet) {
|
|
t.Errorf("exptected sorted set %+v, got %+v", expectedSet, set)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func Test_HandleZREMRANGEBYRANK(t *testing.T) {
|
|
mockServer := server.NewServer(server.Opts{})
|
|
|
|
tests := []struct {
|
|
preset bool
|
|
presetValues map[string]interface{}
|
|
command []string
|
|
expectedValues map[string]*SortedSet
|
|
expectedResponse int
|
|
expectedError error
|
|
}{
|
|
{ // 1. Successfully remove multiple elements within range
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key1": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5}, {value: "six", score: 6},
|
|
{value: "seven", score: 7}, {value: "eight", score: 8},
|
|
{value: "nine", score: 9}, {value: "ten", score: 10},
|
|
}),
|
|
},
|
|
command: []string{"ZREMRANGEBYRANK", "key1", "0", "5"},
|
|
expectedValues: map[string]*SortedSet{
|
|
"key1": NewSortedSet([]MemberParam{
|
|
{value: "seven", score: 7}, {value: "eight", score: 8},
|
|
{value: "nine", score: 9}, {value: "ten", score: 10},
|
|
}),
|
|
},
|
|
expectedResponse: 6,
|
|
expectedError: nil,
|
|
},
|
|
{ // 2. Establish boundaries from the end of the set when negative boundaries are provided
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key2": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5}, {value: "six", score: 6},
|
|
{value: "seven", score: 7}, {value: "eight", score: 8},
|
|
{value: "nine", score: 9}, {value: "ten", score: 10},
|
|
}),
|
|
},
|
|
command: []string{"ZREMRANGEBYRANK", "key2", "-6", "-3"},
|
|
expectedValues: map[string]*SortedSet{
|
|
"key2": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "nine", score: 9}, {value: "ten", score: 10},
|
|
}),
|
|
},
|
|
expectedResponse: 4,
|
|
expectedError: nil,
|
|
},
|
|
{ // 2. If key does not exist, return 0
|
|
preset: false,
|
|
presetValues: nil,
|
|
command: []string{"ZREMRANGEBYRANK", "key3", "2", "4"},
|
|
expectedValues: nil,
|
|
expectedResponse: 0,
|
|
expectedError: nil,
|
|
},
|
|
{ // 3. Return error key is not a sorted set
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key3": "Default value",
|
|
},
|
|
command: []string{"ZREMRANGEBYRANK", "key3", "4", "4"},
|
|
expectedError: errors.New("value at key3 is not a sorted set"),
|
|
},
|
|
{ // 4. Command too short
|
|
preset: false,
|
|
command: []string{"ZREMRANGEBYRANK", "key4", "3"},
|
|
expectedError: errors.New(utils.WRONG_ARGS_RESPONSE),
|
|
},
|
|
{ // 5. Return error when start index is out of bounds
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key5": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5}, {value: "six", score: 6},
|
|
{value: "seven", score: 7}, {value: "eight", score: 8},
|
|
{value: "nine", score: 9}, {value: "ten", score: 10},
|
|
}),
|
|
},
|
|
command: []string{"ZREMRANGEBYRANK", "key5", "-12", "5"},
|
|
expectedValues: nil,
|
|
expectedResponse: 0,
|
|
expectedError: errors.New("indices out of bounds"),
|
|
},
|
|
{ // 6. Return error when end index is out of bounds
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key6": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5}, {value: "six", score: 6},
|
|
{value: "seven", score: 7}, {value: "eight", score: 8},
|
|
{value: "nine", score: 9}, {value: "ten", score: 10},
|
|
}),
|
|
},
|
|
command: []string{"ZREMRANGEBYRANK", "key6", "0", "11"},
|
|
expectedValues: nil,
|
|
expectedResponse: 0,
|
|
expectedError: errors.New("indices out of bounds"),
|
|
},
|
|
{ // 7. Command too long
|
|
preset: false,
|
|
command: []string{"ZREMRANGEBYRANK", "key7", "4", "5", "8"},
|
|
expectedError: errors.New(utils.WRONG_ARGS_RESPONSE),
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
if test.preset {
|
|
for key, value := range test.presetValues {
|
|
if _, err := mockServer.CreateKeyAndLock(context.Background(), key); err != nil {
|
|
t.Error(err)
|
|
}
|
|
mockServer.SetValue(context.Background(), key, value)
|
|
mockServer.KeyUnlock(key)
|
|
}
|
|
}
|
|
res, err := handleZREMRANGEBYRANK(context.Background(), test.command, mockServer, nil)
|
|
if test.expectedError != nil {
|
|
if err.Error() != test.expectedError.Error() {
|
|
t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error())
|
|
}
|
|
continue
|
|
}
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
rd := resp.NewReader(bytes.NewBuffer(res))
|
|
rv, _, err := rd.ReadValue()
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
if rv.Integer() != test.expectedResponse {
|
|
t.Errorf("expected response %d, got %d", test.expectedResponse, rv.Integer())
|
|
}
|
|
// Check if the expected values are the same
|
|
if test.expectedValues != nil {
|
|
for key, expectedSet := range test.expectedValues {
|
|
if _, err = mockServer.KeyRLock(context.Background(), key); err != nil {
|
|
t.Error(err)
|
|
}
|
|
set, ok := mockServer.GetValue(key).(*SortedSet)
|
|
if !ok {
|
|
t.Errorf("expected value at key \"%s\" to be a sorted set, got another type", key)
|
|
}
|
|
if !set.Equals(expectedSet) {
|
|
t.Errorf("exptected sorted set %+v, got %+v", expectedSet, set)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func Test_HandleZREMRANGEBYLEX(t *testing.T) {
|
|
mockServer := server.NewServer(server.Opts{})
|
|
|
|
tests := []struct {
|
|
preset bool
|
|
presetValues map[string]interface{}
|
|
command []string
|
|
expectedValues map[string]*SortedSet
|
|
expectedResponse int
|
|
expectedError error
|
|
}{
|
|
{ // 1. Successfully remove multiple elements with scores inside the provided range
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key1": NewSortedSet([]MemberParam{
|
|
{value: "a", score: 1}, {value: "b", score: 1},
|
|
{value: "c", score: 1}, {value: "d", score: 1},
|
|
{value: "e", score: 1}, {value: "f", score: 1},
|
|
{value: "g", score: 1}, {value: "h", score: 1},
|
|
{value: "i", score: 1}, {value: "j", score: 1},
|
|
}),
|
|
},
|
|
command: []string{"ZREMRANGEBYLEX", "key1", "a", "d"},
|
|
expectedValues: map[string]*SortedSet{
|
|
"key1": NewSortedSet([]MemberParam{
|
|
{value: "e", score: 1}, {value: "f", score: 1},
|
|
{value: "g", score: 1}, {value: "h", score: 1},
|
|
{value: "i", score: 1}, {value: "j", score: 1},
|
|
}),
|
|
},
|
|
expectedResponse: 4,
|
|
expectedError: nil,
|
|
},
|
|
{ // 2. Return 0 if the members do not have the same score
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key2": NewSortedSet([]MemberParam{
|
|
{value: "a", score: 1}, {value: "b", score: 2},
|
|
{value: "c", score: 3}, {value: "d", score: 4},
|
|
{value: "e", score: 5}, {value: "f", score: 6},
|
|
{value: "g", score: 7}, {value: "h", score: 8},
|
|
{value: "i", score: 9}, {value: "j", score: 10},
|
|
}),
|
|
},
|
|
command: []string{"ZREMRANGEBYLEX", "key2", "d", "g"},
|
|
expectedValues: map[string]*SortedSet{
|
|
"key2": NewSortedSet([]MemberParam{
|
|
{value: "a", score: 1}, {value: "b", score: 2},
|
|
{value: "c", score: 3}, {value: "d", score: 4},
|
|
{value: "e", score: 5}, {value: "f", score: 6},
|
|
{value: "g", score: 7}, {value: "h", score: 8},
|
|
{value: "i", score: 9}, {value: "j", score: 10},
|
|
}),
|
|
},
|
|
expectedResponse: 0,
|
|
expectedError: nil,
|
|
},
|
|
{ // 3. If key does not exist, return 0
|
|
preset: false,
|
|
presetValues: nil,
|
|
command: []string{"ZREMRANGEBYLEX", "key3", "2", "4"},
|
|
expectedValues: nil,
|
|
expectedResponse: 0,
|
|
expectedError: nil,
|
|
},
|
|
{ // 3. Return error key is not a sorted set
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key3": "Default value",
|
|
},
|
|
command: []string{"ZREMRANGEBYLEX", "key3", "a", "d"},
|
|
expectedError: errors.New("value at key3 is not a sorted set"),
|
|
},
|
|
{ // 4. Command too short
|
|
preset: false,
|
|
command: []string{"ZREMRANGEBYLEX", "key4", "a"},
|
|
expectedError: errors.New(utils.WRONG_ARGS_RESPONSE),
|
|
},
|
|
{ // 5. Command too long
|
|
preset: false,
|
|
command: []string{"ZREMRANGEBYLEX", "key5", "a", "b", "c"},
|
|
expectedError: errors.New(utils.WRONG_ARGS_RESPONSE),
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
if test.preset {
|
|
for key, value := range test.presetValues {
|
|
if _, err := mockServer.CreateKeyAndLock(context.Background(), key); err != nil {
|
|
t.Error(err)
|
|
}
|
|
mockServer.SetValue(context.Background(), key, value)
|
|
mockServer.KeyUnlock(key)
|
|
}
|
|
}
|
|
res, err := handleZREMRANGEBYLEX(context.Background(), test.command, mockServer, nil)
|
|
if test.expectedError != nil {
|
|
if err.Error() != test.expectedError.Error() {
|
|
t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error())
|
|
}
|
|
continue
|
|
}
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
rd := resp.NewReader(bytes.NewBuffer(res))
|
|
rv, _, err := rd.ReadValue()
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
if rv.Integer() != test.expectedResponse {
|
|
t.Errorf("expected response %d, got %d", test.expectedResponse, rv.Integer())
|
|
}
|
|
// Check if the expected values are the same
|
|
if test.expectedValues != nil {
|
|
for key, expectedSet := range test.expectedValues {
|
|
if _, err = mockServer.KeyRLock(context.Background(), key); err != nil {
|
|
t.Error(err)
|
|
}
|
|
set, ok := mockServer.GetValue(key).(*SortedSet)
|
|
if !ok {
|
|
t.Errorf("expected value at key \"%s\" to be a sorted set, got another type", key)
|
|
}
|
|
if !set.Equals(expectedSet) {
|
|
t.Errorf("exptected sorted set %+v, got %+v", expectedSet, set)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func Test_HandleZRANGE(t *testing.T) {}
|
|
|
|
func Test_HandleZRANGESTORE(t *testing.T) {}
|
|
|
|
func Test_HandleZINTER(t *testing.T) {
|
|
mockServer := server.NewServer(server.Opts{})
|
|
|
|
tests := []struct {
|
|
preset bool
|
|
presetValues map[string]interface{}
|
|
command []string
|
|
expectedResponse [][]string
|
|
expectedError error
|
|
}{
|
|
{ // 1. Get the intersection between 2 sorted sets.
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key1": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5},
|
|
}),
|
|
"key2": NewSortedSet([]MemberParam{
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5}, {value: "six", score: 6},
|
|
{value: "seven", score: 7}, {value: "eight", score: 8},
|
|
}),
|
|
},
|
|
command: []string{"ZINTER", "key1", "key2"},
|
|
expectedResponse: [][]string{{"three"}, {"four"}, {"five"}},
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
// 2. Get the intersection between 3 sorted sets with scores.
|
|
// By default, the SUM aggregate will be used.
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key3": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5}, {value: "six", score: 6},
|
|
{value: "seven", score: 7}, {value: "eight", score: 8},
|
|
}),
|
|
"key4": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "thirty-six", score: 36}, {value: "twelve", score: 12},
|
|
{value: "eleven", score: 11}, {value: "eight", score: 8},
|
|
}),
|
|
"key5": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "eight", score: 8},
|
|
{value: "nine", score: 9}, {value: "ten", score: 10},
|
|
{value: "twelve", score: 12},
|
|
}),
|
|
},
|
|
command: []string{"ZINTER", "key3", "key4", "key5", "WITHSCORES"},
|
|
expectedResponse: [][]string{{"one", "3"}, {"eight", "24"}},
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
// 3. Get the intersection between 3 sorted sets with scores.
|
|
// Use MIN aggregate.
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key6": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 100}, {value: "two", score: 2},
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5}, {value: "six", score: 6},
|
|
{value: "seven", score: 7}, {value: "eight", score: 8},
|
|
}),
|
|
"key7": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "thirty-six", score: 36}, {value: "twelve", score: 12},
|
|
{value: "eleven", score: 11}, {value: "eight", score: 80},
|
|
}),
|
|
"key8": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1000}, {value: "eight", score: 800},
|
|
{value: "nine", score: 9}, {value: "ten", score: 10},
|
|
{value: "twelve", score: 12},
|
|
}),
|
|
},
|
|
command: []string{"ZINTER", "key6", "key7", "key8", "WITHSCORES", "AGGREGATE", "MIN"},
|
|
expectedResponse: [][]string{{"one", "1"}, {"eight", "8"}},
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
// 4. Get the intersection between 3 sorted sets with scores.
|
|
// Use MAX aggregate.
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key9": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 100}, {value: "two", score: 2},
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5}, {value: "six", score: 6},
|
|
{value: "seven", score: 7}, {value: "eight", score: 8},
|
|
}),
|
|
"key10": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "thirty-six", score: 36}, {value: "twelve", score: 12},
|
|
{value: "eleven", score: 11}, {value: "eight", score: 80},
|
|
}),
|
|
"key11": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1000}, {value: "eight", score: 800},
|
|
{value: "nine", score: 9}, {value: "ten", score: 10},
|
|
{value: "twelve", score: 12},
|
|
}),
|
|
},
|
|
command: []string{"ZINTER", "key9", "key10", "key11", "WITHSCORES", "AGGREGATE", "MAX"},
|
|
expectedResponse: [][]string{{"one", "1000"}, {"eight", "800"}},
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
// 5. Get the intersection between 3 sorted sets with scores.
|
|
// Use SUM aggregate with weights modifier.
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key12": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 100}, {value: "two", score: 2},
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5}, {value: "six", score: 6},
|
|
{value: "seven", score: 7}, {value: "eight", score: 8},
|
|
}),
|
|
"key13": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "thirty-six", score: 36}, {value: "twelve", score: 12},
|
|
{value: "eleven", score: 11}, {value: "eight", score: 80},
|
|
}),
|
|
"key14": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1000}, {value: "eight", score: 800},
|
|
{value: "nine", score: 9}, {value: "ten", score: 10},
|
|
{value: "twelve", score: 12},
|
|
}),
|
|
},
|
|
command: []string{"ZINTER", "key12", "key13", "key14", "WITHSCORES", "AGGREGATE", "SUM", "WEIGHTS", "1", "5", "3"},
|
|
expectedResponse: [][]string{{"one", "3105"}, {"eight", "2808"}},
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
// 6. Get the intersection between 3 sorted sets with scores.
|
|
// Use MAX aggregate with added weights.
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key15": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 100}, {value: "two", score: 2},
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5}, {value: "six", score: 6},
|
|
{value: "seven", score: 7}, {value: "eight", score: 8},
|
|
}),
|
|
"key16": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "thirty-six", score: 36}, {value: "twelve", score: 12},
|
|
{value: "eleven", score: 11}, {value: "eight", score: 80},
|
|
}),
|
|
"key17": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1000}, {value: "eight", score: 800},
|
|
{value: "nine", score: 9}, {value: "ten", score: 10},
|
|
{value: "twelve", score: 12},
|
|
}),
|
|
},
|
|
command: []string{"ZINTER", "key15", "key16", "key17", "WITHSCORES", "AGGREGATE", "MAX", "WEIGHTS", "1", "5", "3"},
|
|
expectedResponse: [][]string{{"one", "3000"}, {"eight", "2400"}},
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
// 7. Get the intersection between 3 sorted sets with scores.
|
|
// Use MIN aggregate with added weights.
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key18": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 100}, {value: "two", score: 2},
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5}, {value: "six", score: 6},
|
|
{value: "seven", score: 7}, {value: "eight", score: 8},
|
|
}),
|
|
"key19": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "thirty-six", score: 36}, {value: "twelve", score: 12},
|
|
{value: "eleven", score: 11}, {value: "eight", score: 80},
|
|
}),
|
|
"key20": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1000}, {value: "eight", score: 800},
|
|
{value: "nine", score: 9}, {value: "ten", score: 10},
|
|
{value: "twelve", score: 12},
|
|
}),
|
|
},
|
|
command: []string{"ZINTER", "key18", "key19", "key20", "WITHSCORES", "AGGREGATE", "MIN", "WEIGHTS", "1", "5", "3"},
|
|
expectedResponse: [][]string{{"one", "5"}, {"eight", "8"}},
|
|
expectedError: nil,
|
|
},
|
|
{ // 8. Throw an error if there are more weights than keys
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key21": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5}, {value: "six", score: 6},
|
|
{value: "seven", score: 7}, {value: "eight", score: 8},
|
|
}),
|
|
"key22": NewSortedSet([]MemberParam{{value: "one", score: 1}}),
|
|
},
|
|
command: []string{"ZINTER", "key21", "key22", "WEIGHTS", "1", "2", "3"},
|
|
expectedResponse: nil,
|
|
expectedError: errors.New("number of weights should match number of keys"),
|
|
},
|
|
{ // 9. Throw an error if there are fewer weights than keys
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key23": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5}, {value: "six", score: 6},
|
|
{value: "seven", score: 7}, {value: "eight", score: 8},
|
|
}),
|
|
"key24": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
}),
|
|
"key25": NewSortedSet([]MemberParam{{value: "one", score: 1}}),
|
|
},
|
|
command: []string{"ZINTER", "key23", "key24", "key25", "WEIGHTS", "5", "4"},
|
|
expectedResponse: nil,
|
|
expectedError: errors.New("number of weights should match number of keys"),
|
|
},
|
|
{ // 10. Throw an error if there are no keys provided
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key26": NewSortedSet([]MemberParam{{value: "one", score: 1}}),
|
|
"key27": NewSortedSet([]MemberParam{{value: "one", score: 1}}),
|
|
"key28": NewSortedSet([]MemberParam{{value: "one", score: 1}}),
|
|
},
|
|
command: []string{"ZINTER", "WEIGHTS", "5", "4"},
|
|
expectedResponse: nil,
|
|
expectedError: errors.New(utils.WRONG_ARGS_RESPONSE),
|
|
},
|
|
{ // 11. Throw an error if any of the provided keys are not sorted sets
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key29": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5}, {value: "six", score: 6},
|
|
{value: "seven", score: 7}, {value: "eight", score: 8},
|
|
}),
|
|
"key30": "Default value",
|
|
"key31": NewSortedSet([]MemberParam{{value: "one", score: 1}}),
|
|
},
|
|
command: []string{"ZINTER", "key29", "key30", "key31"},
|
|
expectedResponse: nil,
|
|
expectedError: errors.New("value at key30 is not a sorted set"),
|
|
},
|
|
{ // 12. If any of the keys does not exist, return an empty array.
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key32": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "thirty-six", score: 36}, {value: "twelve", score: 12},
|
|
{value: "eleven", score: 11},
|
|
}),
|
|
"key33": NewSortedSet([]MemberParam{
|
|
{value: "seven", score: 7}, {value: "eight", score: 8},
|
|
{value: "nine", score: 9}, {value: "ten", score: 10},
|
|
{value: "twelve", score: 12},
|
|
}),
|
|
},
|
|
command: []string{"ZINTER", "non-existent", "key32", "key33"},
|
|
expectedResponse: [][]string{},
|
|
expectedError: nil,
|
|
},
|
|
{ // 13. Command too short
|
|
preset: false,
|
|
command: []string{"ZINTER"},
|
|
expectedResponse: [][]string{},
|
|
expectedError: errors.New(utils.WRONG_ARGS_RESPONSE),
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
if test.preset {
|
|
for key, value := range test.presetValues {
|
|
if _, err := mockServer.CreateKeyAndLock(context.Background(), key); err != nil {
|
|
t.Error(err)
|
|
}
|
|
mockServer.SetValue(context.Background(), key, value)
|
|
mockServer.KeyUnlock(key)
|
|
}
|
|
}
|
|
res, err := handleZINTER(context.Background(), test.command, mockServer, nil)
|
|
if test.expectedError != nil {
|
|
if err.Error() != test.expectedError.Error() {
|
|
t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error())
|
|
}
|
|
continue
|
|
}
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
rd := resp.NewReader(bytes.NewBuffer(res))
|
|
rv, _, err := rd.ReadValue()
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
for _, element := range rv.Array() {
|
|
if !slices.ContainsFunc(test.expectedResponse, func(expected []string) bool {
|
|
// The current sub-slice is a different length, return false because they're not equal
|
|
if len(element.Array()) != len(expected) {
|
|
return false
|
|
}
|
|
for i := 0; i < len(expected); i++ {
|
|
if element.Array()[i].String() != expected[i] {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}) {
|
|
t.Errorf("expected response %+v, got %+v", test.expectedResponse, rv.Array())
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func Test_HandleZINTERSTORE(t *testing.T) {
|
|
mockServer := server.NewServer(server.Opts{})
|
|
|
|
tests := []struct {
|
|
preset bool
|
|
presetValues map[string]interface{}
|
|
destination string
|
|
command []string
|
|
expectedValue *SortedSet
|
|
expectedResponse int
|
|
expectedError error
|
|
}{
|
|
{ // 1. Get the intersection between 2 sorted sets.
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key1": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5},
|
|
}),
|
|
"key2": NewSortedSet([]MemberParam{
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5}, {value: "six", score: 6},
|
|
{value: "seven", score: 7}, {value: "eight", score: 8},
|
|
}),
|
|
},
|
|
destination: "destination1",
|
|
command: []string{"ZINTERSTORE", "destination1", "key1", "key2"},
|
|
expectedValue: NewSortedSet([]MemberParam{
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5},
|
|
}),
|
|
expectedResponse: 3,
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
// 2. Get the intersection between 3 sorted sets with scores.
|
|
// By default, the SUM aggregate will be used.
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key3": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5}, {value: "six", score: 6},
|
|
{value: "seven", score: 7}, {value: "eight", score: 8},
|
|
}),
|
|
"key4": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "thirty-six", score: 36}, {value: "twelve", score: 12},
|
|
{value: "eleven", score: 11}, {value: "eight", score: 8},
|
|
}),
|
|
"key5": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "eight", score: 8},
|
|
{value: "nine", score: 9}, {value: "ten", score: 10},
|
|
{value: "twelve", score: 12},
|
|
}),
|
|
},
|
|
destination: "destination2",
|
|
command: []string{"ZINTERSTORE", "destination2", "key3", "key4", "key5", "WITHSCORES"},
|
|
expectedValue: NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "eight", score: 24},
|
|
}),
|
|
expectedResponse: 2,
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
// 3. Get the intersection between 3 sorted sets with scores.
|
|
// Use MIN aggregate.
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key6": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 100}, {value: "two", score: 2},
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5}, {value: "six", score: 6},
|
|
{value: "seven", score: 7}, {value: "eight", score: 8},
|
|
}),
|
|
"key7": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "thirty-six", score: 36}, {value: "twelve", score: 12},
|
|
{value: "eleven", score: 11}, {value: "eight", score: 80},
|
|
}),
|
|
"key8": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1000}, {value: "eight", score: 800},
|
|
{value: "nine", score: 9}, {value: "ten", score: 10},
|
|
{value: "twelve", score: 12},
|
|
}),
|
|
},
|
|
destination: "destination3",
|
|
command: []string{"ZINTERSTORE", "destination3", "key6", "key7", "key8", "WITHSCORES", "AGGREGATE", "MIN"},
|
|
expectedValue: NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "eight", score: 8},
|
|
}),
|
|
expectedResponse: 2,
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
// 4. Get the intersection between 3 sorted sets with scores.
|
|
// Use MAX aggregate.
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key9": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 100}, {value: "two", score: 2},
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5}, {value: "six", score: 6},
|
|
{value: "seven", score: 7}, {value: "eight", score: 8},
|
|
}),
|
|
"key10": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "thirty-six", score: 36}, {value: "twelve", score: 12},
|
|
{value: "eleven", score: 11}, {value: "eight", score: 80},
|
|
}),
|
|
"key11": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1000}, {value: "eight", score: 800},
|
|
{value: "nine", score: 9}, {value: "ten", score: 10},
|
|
{value: "twelve", score: 12},
|
|
}),
|
|
},
|
|
destination: "destination4",
|
|
command: []string{"ZINTERSTORE", "destination4", "key9", "key10", "key11", "WITHSCORES", "AGGREGATE", "MAX"},
|
|
expectedValue: NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1000}, {value: "eight", score: 800},
|
|
}),
|
|
expectedResponse: 2,
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
// 5. Get the intersection between 3 sorted sets with scores.
|
|
// Use SUM aggregate with weights modifier.
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key12": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 100}, {value: "two", score: 2},
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5}, {value: "six", score: 6},
|
|
{value: "seven", score: 7}, {value: "eight", score: 8},
|
|
}),
|
|
"key13": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "thirty-six", score: 36}, {value: "twelve", score: 12},
|
|
{value: "eleven", score: 11}, {value: "eight", score: 80},
|
|
}),
|
|
"key14": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1000}, {value: "eight", score: 800},
|
|
{value: "nine", score: 9}, {value: "ten", score: 10},
|
|
{value: "twelve", score: 12},
|
|
}),
|
|
},
|
|
destination: "destination5",
|
|
command: []string{"ZINTERSTORE", "destination5", "key12", "key13", "key14", "WITHSCORES", "AGGREGATE", "SUM", "WEIGHTS", "1", "5", "3"},
|
|
expectedValue: NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "eight", score: 2808},
|
|
}),
|
|
expectedResponse: 2,
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
// 6. Get the intersection between 3 sorted sets with scores.
|
|
// Use MAX aggregate with added weights.
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key15": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 100}, {value: "two", score: 2},
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5}, {value: "six", score: 6},
|
|
{value: "seven", score: 7}, {value: "eight", score: 8},
|
|
}),
|
|
"key16": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "thirty-six", score: 36}, {value: "twelve", score: 12},
|
|
{value: "eleven", score: 11}, {value: "eight", score: 80},
|
|
}),
|
|
"key17": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1000}, {value: "eight", score: 800},
|
|
{value: "nine", score: 9}, {value: "ten", score: 10},
|
|
{value: "twelve", score: 12},
|
|
}),
|
|
},
|
|
destination: "destination6",
|
|
command: []string{"ZINTERSTORE", "destination6", "key15", "key16", "key17", "WITHSCORES", "AGGREGATE", "MAX", "WEIGHTS", "1", "5", "3"},
|
|
expectedValue: NewSortedSet([]MemberParam{
|
|
{value: "one", score: 3000}, {value: "eight", score: 2400},
|
|
}),
|
|
expectedResponse: 2,
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
// 7. Get the intersection between 3 sorted sets with scores.
|
|
// Use MIN aggregate with added weights.
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key18": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 100}, {value: "two", score: 2},
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5}, {value: "six", score: 6},
|
|
{value: "seven", score: 7}, {value: "eight", score: 8},
|
|
}),
|
|
"key19": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "thirty-six", score: 36}, {value: "twelve", score: 12},
|
|
{value: "eleven", score: 11}, {value: "eight", score: 80},
|
|
}),
|
|
"key20": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1000}, {value: "eight", score: 800},
|
|
{value: "nine", score: 9}, {value: "ten", score: 10},
|
|
{value: "twelve", score: 12},
|
|
}),
|
|
},
|
|
destination: "destination7",
|
|
command: []string{"ZINTERSTORE", "destination7", "key18", "key19", "key20", "WITHSCORES", "AGGREGATE", "MIN", "WEIGHTS", "1", "5", "3"},
|
|
expectedValue: NewSortedSet([]MemberParam{
|
|
{value: "one", score: 5}, {value: "eight", score: 8},
|
|
}),
|
|
expectedResponse: 2,
|
|
expectedError: nil,
|
|
},
|
|
{ // 8. Throw an error if there are more weights than keys
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key21": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5}, {value: "six", score: 6},
|
|
{value: "seven", score: 7}, {value: "eight", score: 8},
|
|
}),
|
|
"key22": NewSortedSet([]MemberParam{{value: "one", score: 1}}),
|
|
},
|
|
command: []string{"ZINTERSTORE", "destination8", "key21", "key22", "WEIGHTS", "1", "2", "3"},
|
|
expectedResponse: 0,
|
|
expectedError: errors.New("number of weights should match number of keys"),
|
|
},
|
|
{ // 9. Throw an error if there are fewer weights than keys
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key23": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5}, {value: "six", score: 6},
|
|
{value: "seven", score: 7}, {value: "eight", score: 8},
|
|
}),
|
|
"key24": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
}),
|
|
"key25": NewSortedSet([]MemberParam{{value: "one", score: 1}}),
|
|
},
|
|
command: []string{"ZINTERSTORE", "destination9", "key23", "key24", "key25", "WEIGHTS", "5", "4"},
|
|
expectedResponse: 0,
|
|
expectedError: errors.New("number of weights should match number of keys"),
|
|
},
|
|
{ // 10. Throw an error if there are no keys provided
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key26": NewSortedSet([]MemberParam{{value: "one", score: 1}}),
|
|
"key27": NewSortedSet([]MemberParam{{value: "one", score: 1}}),
|
|
"key28": NewSortedSet([]MemberParam{{value: "one", score: 1}}),
|
|
},
|
|
command: []string{"ZINTERSTORE", "WEIGHTS", "5", "4"},
|
|
expectedResponse: 0,
|
|
expectedError: errors.New(utils.WRONG_ARGS_RESPONSE),
|
|
},
|
|
{ // 11. Throw an error if any of the provided keys are not sorted sets
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key29": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5}, {value: "six", score: 6},
|
|
{value: "seven", score: 7}, {value: "eight", score: 8},
|
|
}),
|
|
"key30": "Default value",
|
|
"key31": NewSortedSet([]MemberParam{{value: "one", score: 1}}),
|
|
},
|
|
command: []string{"ZINTERSTORE", "key29", "key30", "key31"},
|
|
expectedResponse: 0,
|
|
expectedError: errors.New("value at key30 is not a sorted set"),
|
|
},
|
|
{ // 12. If any of the keys does not exist, return an empty array.
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key32": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "thirty-six", score: 36}, {value: "twelve", score: 12},
|
|
{value: "eleven", score: 11},
|
|
}),
|
|
"key33": NewSortedSet([]MemberParam{
|
|
{value: "seven", score: 7}, {value: "eight", score: 8},
|
|
{value: "nine", score: 9}, {value: "ten", score: 10},
|
|
{value: "twelve", score: 12},
|
|
}),
|
|
},
|
|
command: []string{"ZINTERSTORE", "destination12", "non-existent", "key32", "key33"},
|
|
expectedResponse: 0,
|
|
expectedError: nil,
|
|
},
|
|
{ // 13. Command too short
|
|
preset: false,
|
|
command: []string{"ZINTERSTORE"},
|
|
expectedResponse: 0,
|
|
expectedError: errors.New(utils.WRONG_ARGS_RESPONSE),
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
if test.preset {
|
|
for key, value := range test.presetValues {
|
|
if _, err := mockServer.CreateKeyAndLock(context.Background(), key); err != nil {
|
|
t.Error(err)
|
|
}
|
|
mockServer.SetValue(context.Background(), key, value)
|
|
mockServer.KeyUnlock(key)
|
|
}
|
|
}
|
|
res, err := handleZINTERSTORE(context.Background(), test.command, mockServer, nil)
|
|
if test.expectedError != nil {
|
|
if err.Error() != test.expectedError.Error() {
|
|
t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error())
|
|
}
|
|
continue
|
|
}
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
rd := resp.NewReader(bytes.NewBuffer(res))
|
|
rv, _, err := rd.ReadValue()
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
if rv.Integer() != test.expectedResponse {
|
|
t.Errorf("expected response integer %d, got %d", test.expectedResponse, rv.Integer())
|
|
}
|
|
if test.expectedValue != nil {
|
|
if _, err = mockServer.KeyRLock(context.Background(), test.destination); err != nil {
|
|
t.Error(err)
|
|
}
|
|
set, ok := mockServer.GetValue(test.destination).(*SortedSet)
|
|
if !ok {
|
|
t.Errorf("expected vaule at key %s to be set, got another type", test.destination)
|
|
}
|
|
for _, elem := range set.GetAll() {
|
|
if !test.expectedValue.Contains(elem.value) {
|
|
t.Errorf("could not find element %s in the expected values", elem.value)
|
|
}
|
|
}
|
|
mockServer.KeyRUnlock(test.destination)
|
|
}
|
|
}
|
|
}
|
|
|
|
func Test_HandleZUNION(t *testing.T) {
|
|
mockServer := server.NewServer(server.Opts{})
|
|
|
|
tests := []struct {
|
|
preset bool
|
|
presetValues map[string]interface{}
|
|
command []string
|
|
expectedResponse [][]string
|
|
expectedError error
|
|
}{
|
|
{ // 1. Get the union between 2 sorted sets.
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key1": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5},
|
|
}),
|
|
"key2": NewSortedSet([]MemberParam{
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5}, {value: "six", score: 6},
|
|
{value: "seven", score: 7}, {value: "eight", score: 8},
|
|
}),
|
|
},
|
|
command: []string{"ZUNION", "key1", "key2"},
|
|
expectedResponse: [][]string{{"one"}, {"two"}, {"three"}, {"four"}, {"five"}, {"six"}, {"seven"}, {"eight"}},
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
// 2. Get the union between 3 sorted sets with scores.
|
|
// By default, the SUM aggregate will be used.
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key3": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5}, {value: "six", score: 6},
|
|
{value: "seven", score: 7}, {value: "eight", score: 8},
|
|
}),
|
|
"key4": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "thirty-six", score: 36}, {value: "twelve", score: 12},
|
|
{value: "eleven", score: 11}, {value: "eight", score: 8},
|
|
}),
|
|
"key5": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "eight", score: 8},
|
|
{value: "nine", score: 9}, {value: "ten", score: 10},
|
|
{value: "twelve", score: 12}, {value: "thirty-six", score: 36},
|
|
}),
|
|
},
|
|
command: []string{"ZUNION", "key3", "key4", "key5", "WITHSCORES"},
|
|
expectedResponse: [][]string{
|
|
{"one", "3"}, {"two", "4"}, {"three", "3"}, {"four", "4"}, {"five", "5"}, {"six", "6"},
|
|
{"seven", "7"}, {"eight", "24"}, {"nine", "9"}, {"ten", "10"}, {"eleven", "11"},
|
|
{"twelve", "24"}, {"thirty-six", "72"},
|
|
},
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
// 3. Get the union between 3 sorted sets with scores.
|
|
// Use MIN aggregate.
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key6": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 100}, {value: "two", score: 2},
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5}, {value: "six", score: 6},
|
|
{value: "seven", score: 7}, {value: "eight", score: 8},
|
|
}),
|
|
"key7": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "thirty-six", score: 36}, {value: "twelve", score: 12},
|
|
{value: "eleven", score: 11}, {value: "eight", score: 80},
|
|
}),
|
|
"key8": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1000}, {value: "eight", score: 800},
|
|
{value: "nine", score: 9}, {value: "ten", score: 10},
|
|
{value: "twelve", score: 12}, {value: "thirty-six", score: 72},
|
|
}),
|
|
},
|
|
command: []string{"ZUNION", "key6", "key7", "key8", "WITHSCORES", "AGGREGATE", "MIN"},
|
|
expectedResponse: [][]string{
|
|
{"one", "1"}, {"two", "2"}, {"three", "3"}, {"four", "4"}, {"five", "5"}, {"six", "6"},
|
|
{"seven", "7"}, {"eight", "8"}, {"nine", "9"}, {"ten", "10"}, {"eleven", "11"},
|
|
{"twelve", "12"}, {"thirty-six", "36"},
|
|
},
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
// 4. Get the union between 3 sorted sets with scores.
|
|
// Use MAX aggregate.
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key9": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 100}, {value: "two", score: 2},
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5}, {value: "six", score: 6},
|
|
{value: "seven", score: 7}, {value: "eight", score: 8},
|
|
}),
|
|
"key10": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "thirty-six", score: 36}, {value: "twelve", score: 12},
|
|
{value: "eleven", score: 11}, {value: "eight", score: 80},
|
|
}),
|
|
"key11": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1000}, {value: "eight", score: 800},
|
|
{value: "nine", score: 9}, {value: "ten", score: 10},
|
|
{value: "twelve", score: 12}, {value: "thirty-six", score: 72},
|
|
}),
|
|
},
|
|
command: []string{"ZUNION", "key9", "key10", "key11", "WITHSCORES", "AGGREGATE", "MAX"},
|
|
expectedResponse: [][]string{
|
|
{"one", "1000"}, {"two", "2"}, {"three", "3"}, {"four", "4"}, {"five", "5"}, {"six", "6"},
|
|
{"seven", "7"}, {"eight", "800"}, {"nine", "9"}, {"ten", "10"}, {"eleven", "11"},
|
|
{"twelve", "12"}, {"thirty-six", "72"},
|
|
},
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
// 5. Get the union between 3 sorted sets with scores.
|
|
// Use SUM aggregate with weights modifier.
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key12": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 100}, {value: "two", score: 2},
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5}, {value: "six", score: 6},
|
|
{value: "seven", score: 7}, {value: "eight", score: 8},
|
|
}),
|
|
"key13": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "thirty-six", score: 36}, {value: "twelve", score: 12},
|
|
{value: "eleven", score: 11}, {value: "eight", score: 80},
|
|
}),
|
|
"key14": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1000}, {value: "eight", score: 800},
|
|
{value: "nine", score: 9}, {value: "ten", score: 10},
|
|
{value: "twelve", score: 12},
|
|
}),
|
|
},
|
|
command: []string{"ZUNION", "key12", "key13", "key14", "WITHSCORES", "AGGREGATE", "SUM", "WEIGHTS", "1", "2", "3"},
|
|
expectedResponse: [][]string{
|
|
{"one", "3102"}, {"two", "6"}, {"three", "3"}, {"four", "4"}, {"five", "5"}, {"six", "6"},
|
|
{"seven", "7"}, {"eight", "2568"}, {"nine", "27"}, {"ten", "30"}, {"eleven", "22"},
|
|
{"twelve", "60"}, {"thirty-six", "72"},
|
|
},
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
// 6. Get the union between 3 sorted sets with scores.
|
|
// Use MAX aggregate with added weights.
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key15": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 100}, {value: "two", score: 2},
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5}, {value: "six", score: 6},
|
|
{value: "seven", score: 7}, {value: "eight", score: 8},
|
|
}),
|
|
"key16": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "thirty-six", score: 36}, {value: "twelve", score: 12},
|
|
{value: "eleven", score: 11}, {value: "eight", score: 80},
|
|
}),
|
|
"key17": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1000}, {value: "eight", score: 800},
|
|
{value: "nine", score: 9}, {value: "ten", score: 10},
|
|
{value: "twelve", score: 12},
|
|
}),
|
|
},
|
|
command: []string{"ZUNION", "key15", "key16", "key17", "WITHSCORES", "AGGREGATE", "MAX", "WEIGHTS", "1", "2", "3"},
|
|
expectedResponse: [][]string{
|
|
{"one", "3000"}, {"two", "4"}, {"three", "3"}, {"four", "4"}, {"five", "5"}, {"six", "6"},
|
|
{"seven", "7"}, {"eight", "2400"}, {"nine", "27"}, {"ten", "30"}, {"eleven", "22"},
|
|
{"twelve", "36"}, {"thirty-six", "72"},
|
|
},
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
// 7. Get the union between 3 sorted sets with scores.
|
|
// Use MIN aggregate with added weights.
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key18": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 100}, {value: "two", score: 2},
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5}, {value: "six", score: 6},
|
|
{value: "seven", score: 7}, {value: "eight", score: 8},
|
|
}),
|
|
"key19": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "thirty-six", score: 36}, {value: "twelve", score: 12},
|
|
{value: "eleven", score: 11}, {value: "eight", score: 80},
|
|
}),
|
|
"key20": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1000}, {value: "eight", score: 800},
|
|
{value: "nine", score: 9}, {value: "ten", score: 10},
|
|
{value: "twelve", score: 12},
|
|
}),
|
|
},
|
|
command: []string{"ZUNION", "key18", "key19", "key20", "WITHSCORES", "AGGREGATE", "MIN", "WEIGHTS", "1", "2", "3"},
|
|
expectedResponse: [][]string{
|
|
{"one", "2"}, {"two", "2"}, {"three", "3"}, {"four", "4"}, {"five", "5"}, {"six", "6"}, {"seven", "7"},
|
|
{"eight", "8"}, {"nine", "27"}, {"ten", "30"}, {"eleven", "22"}, {"twelve", "24"}, {"thirty-six", "72"},
|
|
},
|
|
expectedError: nil,
|
|
},
|
|
{ // 8. Throw an error if there are more weights than keys
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key21": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5}, {value: "six", score: 6},
|
|
{value: "seven", score: 7}, {value: "eight", score: 8},
|
|
}),
|
|
"key22": NewSortedSet([]MemberParam{{value: "one", score: 1}}),
|
|
},
|
|
command: []string{"ZUNION", "key21", "key22", "WEIGHTS", "1", "2", "3"},
|
|
expectedResponse: nil,
|
|
expectedError: errors.New("number of weights should match number of keys"),
|
|
},
|
|
{ // 9. Throw an error if there are fewer weights than keys
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key23": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5}, {value: "six", score: 6},
|
|
{value: "seven", score: 7}, {value: "eight", score: 8},
|
|
}),
|
|
"key24": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
}),
|
|
"key25": NewSortedSet([]MemberParam{{value: "one", score: 1}}),
|
|
},
|
|
command: []string{"ZUNION", "key23", "key24", "key25", "WEIGHTS", "5", "4"},
|
|
expectedResponse: nil,
|
|
expectedError: errors.New("number of weights should match number of keys"),
|
|
},
|
|
{ // 10. Throw an error if there are no keys provided
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key26": NewSortedSet([]MemberParam{{value: "one", score: 1}}),
|
|
"key27": NewSortedSet([]MemberParam{{value: "one", score: 1}}),
|
|
"key28": NewSortedSet([]MemberParam{{value: "one", score: 1}}),
|
|
},
|
|
command: []string{"ZUNION", "WEIGHTS", "5", "4"},
|
|
expectedResponse: nil,
|
|
expectedError: errors.New(utils.WRONG_ARGS_RESPONSE),
|
|
},
|
|
{ // 11. Throw an error if any of the provided keys are not sorted sets
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key29": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5}, {value: "six", score: 6},
|
|
{value: "seven", score: 7}, {value: "eight", score: 8},
|
|
}),
|
|
"key30": "Default value",
|
|
"key31": NewSortedSet([]MemberParam{{value: "one", score: 1}}),
|
|
},
|
|
command: []string{"ZUNION", "key29", "key30", "key31"},
|
|
expectedResponse: nil,
|
|
expectedError: errors.New("value at key30 is not a sorted set"),
|
|
},
|
|
{ // 12. If any of the keys does not exist, skip it.
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key32": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "thirty-six", score: 36}, {value: "twelve", score: 12},
|
|
{value: "eleven", score: 11},
|
|
}),
|
|
"key33": NewSortedSet([]MemberParam{
|
|
{value: "seven", score: 7}, {value: "eight", score: 8},
|
|
{value: "nine", score: 9}, {value: "ten", score: 10},
|
|
{value: "twelve", score: 12},
|
|
}),
|
|
},
|
|
command: []string{"ZUNION", "non-existent", "key32", "key33"},
|
|
expectedResponse: [][]string{
|
|
{"one"}, {"two"}, {"thirty-six"}, {"twelve"}, {"eleven"},
|
|
{"seven"}, {"eight"}, {"nine"}, {"ten"},
|
|
},
|
|
expectedError: nil,
|
|
},
|
|
{ // 13. Command too short
|
|
preset: false,
|
|
command: []string{"ZUNION"},
|
|
expectedError: errors.New(utils.WRONG_ARGS_RESPONSE),
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
if test.preset {
|
|
for key, value := range test.presetValues {
|
|
if _, err := mockServer.CreateKeyAndLock(context.Background(), key); err != nil {
|
|
t.Error(err)
|
|
}
|
|
mockServer.SetValue(context.Background(), key, value)
|
|
mockServer.KeyUnlock(key)
|
|
}
|
|
}
|
|
res, err := handleZUNION(context.Background(), test.command, mockServer, nil)
|
|
if test.expectedError != nil {
|
|
if err.Error() != test.expectedError.Error() {
|
|
t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error())
|
|
}
|
|
continue
|
|
}
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
rd := resp.NewReader(bytes.NewBuffer(res))
|
|
rv, _, err := rd.ReadValue()
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
for _, element := range rv.Array() {
|
|
if !slices.ContainsFunc(test.expectedResponse, func(expected []string) bool {
|
|
// The current sub-slice is a different length, return false because they're not equal
|
|
if len(element.Array()) != len(expected) {
|
|
return false
|
|
}
|
|
for i := 0; i < len(expected); i++ {
|
|
if element.Array()[i].String() != expected[i] {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}) {
|
|
t.Errorf("expected response %+v, got %+v", test.expectedResponse, rv.Array())
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func Test_HandleZUNIONSTORE(t *testing.T) {
|
|
mockServer := server.NewServer(server.Opts{})
|
|
|
|
tests := []struct {
|
|
preset bool
|
|
presetValues map[string]interface{}
|
|
destination string
|
|
command []string
|
|
expectedValue *SortedSet
|
|
expectedResponse int
|
|
expectedError error
|
|
}{
|
|
{ // 1. Get the union between 2 sorted sets.
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key1": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5},
|
|
}),
|
|
"key2": NewSortedSet([]MemberParam{
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5}, {value: "six", score: 6},
|
|
{value: "seven", score: 7}, {value: "eight", score: 8},
|
|
}),
|
|
},
|
|
destination: "destination1",
|
|
command: []string{"ZUNIONSTORE", "destination1", "key1", "key2"},
|
|
expectedValue: NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5}, {value: "six", score: 6},
|
|
{value: "seven", score: 7}, {value: "eight", score: 8},
|
|
}),
|
|
expectedResponse: 8,
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
// 2. Get the union between 3 sorted sets with scores.
|
|
// By default, the SUM aggregate will be used.
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key3": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5}, {value: "six", score: 6},
|
|
{value: "seven", score: 7}, {value: "eight", score: 8},
|
|
}),
|
|
"key4": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "thirty-six", score: 36}, {value: "twelve", score: 12},
|
|
{value: "eleven", score: 11}, {value: "eight", score: 8},
|
|
}),
|
|
"key5": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "eight", score: 8},
|
|
{value: "nine", score: 9}, {value: "ten", score: 10},
|
|
{value: "twelve", score: 12}, {value: "thirty-six", score: 36},
|
|
}),
|
|
},
|
|
destination: "destination2",
|
|
command: []string{"ZUNIONSTORE", "destination2", "key3", "key4", "key5", "WITHSCORES"},
|
|
expectedValue: NewSortedSet([]MemberParam{
|
|
{value: "one", score: 3}, {value: "two", score: 4}, {value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5}, {value: "six", score: 6}, {value: "seven", score: 7}, {value: "eight", score: 24},
|
|
{value: "nine", score: 9}, {value: "ten", score: 10}, {value: "eleven", score: 11},
|
|
{value: "twelve", score: 24}, {value: "thirty-six", score: 72},
|
|
}),
|
|
expectedResponse: 13,
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
// 3. Get the union between 3 sorted sets with scores.
|
|
// Use MIN aggregate.
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key6": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 100}, {value: "two", score: 2},
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5}, {value: "six", score: 6},
|
|
{value: "seven", score: 7}, {value: "eight", score: 8},
|
|
}),
|
|
"key7": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "thirty-six", score: 36}, {value: "twelve", score: 12},
|
|
{value: "eleven", score: 11}, {value: "eight", score: 80},
|
|
}),
|
|
"key8": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1000}, {value: "eight", score: 800},
|
|
{value: "nine", score: 9}, {value: "ten", score: 10},
|
|
{value: "twelve", score: 12}, {value: "thirty-six", score: 72},
|
|
}),
|
|
},
|
|
destination: "destination3",
|
|
command: []string{"ZUNIONSTORE", "destination3", "key6", "key7", "key8", "WITHSCORES", "AGGREGATE", "MIN"},
|
|
expectedValue: NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2}, {value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5}, {value: "six", score: 6}, {value: "seven", score: 7}, {value: "eight", score: 8},
|
|
{value: "nine", score: 9}, {value: "ten", score: 10}, {value: "eleven", score: 11},
|
|
{value: "twelve", score: 12}, {value: "thirty-six", score: 36},
|
|
}),
|
|
expectedResponse: 13,
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
// 4. Get the union between 3 sorted sets with scores.
|
|
// Use MAX aggregate.
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key9": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 100}, {value: "two", score: 2},
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5}, {value: "six", score: 6},
|
|
{value: "seven", score: 7}, {value: "eight", score: 8},
|
|
}),
|
|
"key10": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "thirty-six", score: 36}, {value: "twelve", score: 12},
|
|
{value: "eleven", score: 11}, {value: "eight", score: 80},
|
|
}),
|
|
"key11": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1000}, {value: "eight", score: 800},
|
|
{value: "nine", score: 9}, {value: "ten", score: 10},
|
|
{value: "twelve", score: 12}, {value: "thirty-six", score: 72},
|
|
}),
|
|
},
|
|
destination: "destination4",
|
|
command: []string{
|
|
"ZUNIONSTORE", "destination4", "key9", "key10", "key11", "WITHSCORES", "AGGREGATE", "MAX",
|
|
},
|
|
expectedValue: NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1000}, {value: "two", score: 2}, {value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5}, {value: "six", score: 6}, {value: "seven", score: 7}, {value: "eight", score: 800},
|
|
{value: "nine", score: 9}, {value: "ten", score: 10}, {value: "eleven", score: 11},
|
|
{value: "twelve", score: 12}, {value: "thirty-six", score: 72},
|
|
}),
|
|
expectedResponse: 13,
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
// 5. Get the union between 3 sorted sets with scores.
|
|
// Use SUM aggregate with weights modifier.
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key12": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 100}, {value: "two", score: 2},
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5}, {value: "six", score: 6},
|
|
{value: "seven", score: 7}, {value: "eight", score: 8},
|
|
}),
|
|
"key13": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "thirty-six", score: 36}, {value: "twelve", score: 12},
|
|
{value: "eleven", score: 11}, {value: "eight", score: 80},
|
|
}),
|
|
"key14": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1000}, {value: "eight", score: 800},
|
|
{value: "nine", score: 9}, {value: "ten", score: 10},
|
|
{value: "twelve", score: 12},
|
|
}),
|
|
},
|
|
destination: "destination5",
|
|
command: []string{
|
|
"ZUNIONSTORE", "destination5", "key12", "key13", "key14",
|
|
"WITHSCORES", "AGGREGATE", "SUM", "WEIGHTS", "1", "2", "3",
|
|
},
|
|
expectedValue: NewSortedSet([]MemberParam{
|
|
{value: "one", score: 3102}, {value: "two", score: 6}, {value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5}, {value: "six", score: 6}, {value: "seven", score: 7}, {value: "eight", score: 2568},
|
|
{value: "nine", score: 27}, {value: "ten", score: 30}, {value: "eleven", score: 22},
|
|
{value: "twelve", score: 60}, {value: "thirty-six", score: 72},
|
|
}),
|
|
expectedResponse: 13,
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
// 6. Get the union between 3 sorted sets with scores.
|
|
// Use MAX aggregate with added weights.
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key15": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 100}, {value: "two", score: 2},
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5}, {value: "six", score: 6},
|
|
{value: "seven", score: 7}, {value: "eight", score: 8},
|
|
}),
|
|
"key16": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "thirty-six", score: 36}, {value: "twelve", score: 12},
|
|
{value: "eleven", score: 11}, {value: "eight", score: 80},
|
|
}),
|
|
"key17": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1000}, {value: "eight", score: 800},
|
|
{value: "nine", score: 9}, {value: "ten", score: 10},
|
|
{value: "twelve", score: 12},
|
|
}),
|
|
},
|
|
destination: "destination6",
|
|
command: []string{
|
|
"ZUNIONSTORE", "destination6", "key15", "key16", "key17",
|
|
"WITHSCORES", "AGGREGATE", "MAX", "WEIGHTS", "1", "2", "3"},
|
|
expectedValue: NewSortedSet([]MemberParam{
|
|
{value: "one", score: 3000}, {value: "two", score: 4}, {value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5}, {value: "six", score: 6}, {value: "seven", score: 7}, {value: "eight", score: 2400},
|
|
{value: "nine", score: 27}, {value: "ten", score: 30}, {value: "eleven", score: 22},
|
|
{value: "twelve", score: 36}, {value: "thirty-six", score: 72},
|
|
}),
|
|
expectedResponse: 13,
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
// 7. Get the union between 3 sorted sets with scores.
|
|
// Use MIN aggregate with added weights.
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key18": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 100}, {value: "two", score: 2},
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5}, {value: "six", score: 6},
|
|
{value: "seven", score: 7}, {value: "eight", score: 8},
|
|
}),
|
|
"key19": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "thirty-six", score: 36}, {value: "twelve", score: 12},
|
|
{value: "eleven", score: 11}, {value: "eight", score: 80},
|
|
}),
|
|
"key20": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1000}, {value: "eight", score: 800},
|
|
{value: "nine", score: 9}, {value: "ten", score: 10},
|
|
{value: "twelve", score: 12},
|
|
}),
|
|
},
|
|
destination: "destination7",
|
|
command: []string{
|
|
"ZUNIONSTORE", "destination7", "key18", "key19", "key20",
|
|
"WITHSCORES", "AGGREGATE", "MIN", "WEIGHTS", "1", "2", "3",
|
|
},
|
|
expectedValue: NewSortedSet([]MemberParam{
|
|
{value: "one", score: 2}, {value: "two", score: 2}, {value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5}, {value: "six", score: 6}, {value: "seven", score: 7}, {value: "eight", score: 8},
|
|
{value: "nine", score: 27}, {value: "ten", score: 30}, {value: "eleven", score: 22},
|
|
{value: "twelve", score: 24}, {value: "thirty-six", score: 72},
|
|
}),
|
|
expectedResponse: 13,
|
|
expectedError: nil,
|
|
},
|
|
{ // 8. Throw an error if there are more weights than keys
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key21": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5}, {value: "six", score: 6},
|
|
{value: "seven", score: 7}, {value: "eight", score: 8},
|
|
}),
|
|
"key22": NewSortedSet([]MemberParam{{value: "one", score: 1}}),
|
|
},
|
|
destination: "destination8",
|
|
command: []string{"ZUNIONSTORE", "destination8", "key21", "key22", "WEIGHTS", "1", "2", "3"},
|
|
expectedResponse: 0,
|
|
expectedError: errors.New("number of weights should match number of keys"),
|
|
},
|
|
{ // 9. Throw an error if there are fewer weights than keys
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key23": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5}, {value: "six", score: 6},
|
|
{value: "seven", score: 7}, {value: "eight", score: 8},
|
|
}),
|
|
"key24": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
}),
|
|
"key25": NewSortedSet([]MemberParam{{value: "one", score: 1}}),
|
|
},
|
|
destination: "destination9",
|
|
command: []string{"ZUNIONSTORE", "destination9", "key23", "key24", "key25", "WEIGHTS", "5", "4"},
|
|
expectedResponse: 0,
|
|
expectedError: errors.New("number of weights should match number of keys"),
|
|
},
|
|
{ // 10. Throw an error if there are no keys provided
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key26": NewSortedSet([]MemberParam{{value: "one", score: 1}}),
|
|
"key27": NewSortedSet([]MemberParam{{value: "one", score: 1}}),
|
|
"key28": NewSortedSet([]MemberParam{{value: "one", score: 1}}),
|
|
},
|
|
command: []string{"ZUNIONSTORE", "WEIGHTS", "5", "4"},
|
|
expectedResponse: 0,
|
|
expectedError: errors.New(utils.WRONG_ARGS_RESPONSE),
|
|
},
|
|
{ // 11. Throw an error if any of the provided keys are not sorted sets
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key29": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "three", score: 3}, {value: "four", score: 4},
|
|
{value: "five", score: 5}, {value: "six", score: 6},
|
|
{value: "seven", score: 7}, {value: "eight", score: 8},
|
|
}),
|
|
"key30": "Default value",
|
|
"key31": NewSortedSet([]MemberParam{{value: "one", score: 1}}),
|
|
},
|
|
destination: "destination11",
|
|
command: []string{"ZUNIONSTORE", "destination11", "key29", "key30", "key31"},
|
|
expectedResponse: 0,
|
|
expectedError: errors.New("value at key30 is not a sorted set"),
|
|
},
|
|
{ // 12. If any of the keys does not exist, skip it.
|
|
preset: true,
|
|
presetValues: map[string]interface{}{
|
|
"key32": NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2},
|
|
{value: "thirty-six", score: 36}, {value: "twelve", score: 12},
|
|
{value: "eleven", score: 11},
|
|
}),
|
|
"key33": NewSortedSet([]MemberParam{
|
|
{value: "seven", score: 7}, {value: "eight", score: 8},
|
|
{value: "nine", score: 9}, {value: "ten", score: 10},
|
|
{value: "twelve", score: 12},
|
|
}),
|
|
},
|
|
destination: "destination12",
|
|
command: []string{"ZUNIONSTORE", "destination12", "non-existent", "key32", "key33"},
|
|
expectedValue: NewSortedSet([]MemberParam{
|
|
{value: "one", score: 1}, {value: "two", score: 2}, {value: "seven", score: 7}, {value: "eight", score: 8},
|
|
{value: "nine", score: 9}, {value: "ten", score: 10}, {value: "eleven", score: 11}, {value: "twelve", score: 12},
|
|
{value: "thirty-six", score: 36},
|
|
}),
|
|
expectedResponse: 9,
|
|
expectedError: nil,
|
|
},
|
|
{ // 13. Command too short
|
|
preset: false,
|
|
command: []string{"ZUNIONSTORE"},
|
|
expectedResponse: 0,
|
|
expectedError: errors.New(utils.WRONG_ARGS_RESPONSE),
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
if test.preset {
|
|
for key, value := range test.presetValues {
|
|
if _, err := mockServer.CreateKeyAndLock(context.Background(), key); err != nil {
|
|
t.Error(err)
|
|
}
|
|
mockServer.SetValue(context.Background(), key, value)
|
|
mockServer.KeyUnlock(key)
|
|
}
|
|
}
|
|
res, err := handleZUNIONSTORE(context.Background(), test.command, mockServer, nil)
|
|
if test.expectedError != nil {
|
|
if err.Error() != test.expectedError.Error() {
|
|
t.Errorf("expected error \"%s\", got \"%s\"", test.expectedError.Error(), err.Error())
|
|
}
|
|
continue
|
|
}
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
rd := resp.NewReader(bytes.NewBuffer(res))
|
|
rv, _, err := rd.ReadValue()
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
if rv.Integer() != test.expectedResponse {
|
|
t.Errorf("expected response integer %d, got %d", test.expectedResponse, rv.Integer())
|
|
}
|
|
if test.expectedValue != nil {
|
|
if _, err = mockServer.KeyRLock(context.Background(), test.destination); err != nil {
|
|
t.Error(err)
|
|
}
|
|
set, ok := mockServer.GetValue(test.destination).(*SortedSet)
|
|
if !ok {
|
|
t.Errorf("expected vaule at key %s to be set, got another type", test.destination)
|
|
}
|
|
for _, elem := range set.GetAll() {
|
|
if !test.expectedValue.Contains(elem.value) {
|
|
t.Errorf("could not find element %s in the expected values", elem.value)
|
|
}
|
|
}
|
|
mockServer.KeyRUnlock(test.destination)
|
|
}
|
|
}
|
|
}
|