mirror of
https://github.com/EchoVault/SugarDB.git
synced 2025-10-05 16:06:57 +08:00

Intersect receiver function on SortedSet reference has been deleted as it's no longer in use. Added test for ZINTERSTORE command handler.
1623 lines
57 KiB
Go
1623 lines
57 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"
|
|
"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
|
|
}
|
|
for _, member := range sortedSet.GetAll() {
|
|
expectedMember := test.expectedValue.Get(member.value)
|
|
if !expectedMember.exists {
|
|
t.Errorf("could not find member %+v in expected sorted set, found in stored set", member)
|
|
}
|
|
if member.score != expectedMember.score {
|
|
t.Errorf("expected member \"%s\" to have score %f, got score %f", expectedMember.value, expectedMember.score, member.score)
|
|
}
|
|
}
|
|
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 _, responseElement := range rv.Array() {
|
|
if !slices.Contains(test.expectedResponse, responseElement.String()) {
|
|
t.Errorf("could not find response element \"%s\" from expected response array", responseElement.String())
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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) {}
|
|
|
|
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 _, responseElement := range rv.Array() {
|
|
if !slices.Contains(test.expectedResponse, responseElement.String()) {
|
|
t.Errorf("could not find response element \"%s\" from expected response array", responseElement.String())
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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_HandleZMPOP(t *testing.T) {}
|
|
|
|
func Test_HandleZPOP(t *testing.T) {}
|
|
|
|
func Test_HandleZMSCORE(t *testing.T) {}
|
|
|
|
func Test_HandleZRANDMEMBER(t *testing.T) {}
|
|
|
|
func Test_HandleZRANK(t *testing.T) {}
|
|
|
|
func Test_HandleZREM(t *testing.T) {}
|
|
|
|
func Test_HandleZSCORE(t *testing.T) {}
|
|
|
|
func Test_HandleZREMRANGEBYSCORE(t *testing.T) {}
|
|
|
|
func Test_HandleZREMRANGEBYRANK(t *testing.T) {}
|
|
|
|
func Test_HandleZREMRANGEBYLEX(t *testing.T) {}
|
|
|
|
func Test_HandleZRANGE(t *testing.T) {}
|
|
|
|
func Test_HandleZRANGESTORE(t *testing.T) {}
|
|
|
|
func Test_HandleZUNION(t *testing.T) {}
|
|
|
|
func Test_HandleZUNIONSTORE(t *testing.T) {}
|