// Copyright 2024 Kelvin Clement Mwinuka // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package sorted_set import ( "context" "github.com/echovault/echovault/echovault" "github.com/echovault/echovault/internal" "github.com/echovault/echovault/internal/config" ss "github.com/echovault/echovault/internal/modules/sorted_set" "math" "reflect" "strconv" "testing" ) func createEchoVault() *echovault.EchoVault { ev, _ := echovault.NewEchoVault( echovault.WithConfig(config.Config{ DataDir: "", }), ) return ev } func presetValue(server *echovault.EchoVault, ctx context.Context, key string, value interface{}) error { if _, err := server.CreateKeyAndLock(ctx, key); err != nil { return err } if err := server.SetValue(ctx, key, value); err != nil { return err } server.KeyUnlock(ctx, key) return nil } func TestEchoVault_ZADD(t *testing.T) { server := createEchoVault() tests := []struct { name string preset bool presetValue *ss.SortedSet key string entries map[string]float64 options echovault.ZADDOptions want int wantErr bool }{ { name: "Create new sorted set and return the cardinality of the new sorted set", preset: false, presetValue: nil, key: "key1", entries: map[string]float64{ "member1": 5.5, "member2": 67.77, "member3": 10, "member4": math.Inf(-1), "member5": math.Inf(1), }, options: echovault.ZADDOptions{}, want: 5, wantErr: false, }, { name: "Only add the elements that do not currently exist in the sorted set when NX flag is provided", preset: true, presetValue: ss.NewSortedSet([]ss.MemberParam{ {Value: "member1", Score: ss.Score(5.5)}, {Value: "member2", Score: ss.Score(67.77)}, {Value: "member3", Score: ss.Score(10)}, }), key: "key2", entries: map[string]float64{ "member1": 5.5, "member4": 67.77, "member5": 10, }, options: echovault.ZADDOptions{NX: true}, want: 2, wantErr: false, }, { name: "Do not add any elements when providing existing members with NX flag", preset: true, presetValue: ss.NewSortedSet([]ss.MemberParam{ {Value: "member1", Score: ss.Score(5.5)}, {Value: "member2", Score: ss.Score(67.77)}, {Value: "member3", Score: ss.Score(10)}, }), key: "key3", entries: map[string]float64{ "member1": 5.5, "member2": 67.77, "member3": 10, }, options: echovault.ZADDOptions{NX: true}, want: 0, wantErr: false, }, { name: "Successfully add elements to an existing set when XX flag is provided with existing elements", preset: true, presetValue: ss.NewSortedSet([]ss.MemberParam{ {Value: "member1", Score: ss.Score(5.5)}, {Value: "member2", Score: ss.Score(67.77)}, {Value: "member3", Score: ss.Score(10)}, }), key: "key4", entries: map[string]float64{ "member1": 55, "member2": 1005, "member3": 15, "member4": 99.75, }, options: echovault.ZADDOptions{XX: true, CH: true}, want: 3, wantErr: false, }, { name: "Fail to add element when providing XX flag with elements that do not exist in the sorted set", preset: true, presetValue: ss.NewSortedSet([]ss.MemberParam{ {Value: "member1", Score: ss.Score(5.5)}, {Value: "member2", Score: ss.Score(67.77)}, {Value: "member3", Score: ss.Score(10)}, }), key: "key5", entries: map[string]float64{ "member4": 5.5, "member5": 100.5, "member6": 15, }, options: echovault.ZADDOptions{XX: true}, want: 0, wantErr: false, }, { // Only update the elements where provided score is greater than current score if GT flag // Return only the new elements added by default name: "Only update the elements where provided score is greater than current score if GT flag", preset: true, presetValue: ss.NewSortedSet([]ss.MemberParam{ {Value: "member1", Score: ss.Score(5.5)}, {Value: "member2", Score: ss.Score(67.77)}, {Value: "member3", Score: ss.Score(10)}, }), key: "key6", entries: map[string]float64{ "member1": 7.5, "member4": 100.5, "member5": 15, }, options: echovault.ZADDOptions{XX: true, CH: true, GT: true}, want: 1, wantErr: false, }, { // 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. name: "Only update the elements where provided score is less than current score if LT flag is provided", preset: true, presetValue: ss.NewSortedSet([]ss.MemberParam{ {Value: "member1", Score: ss.Score(5.5)}, {Value: "member2", Score: ss.Score(67.77)}, {Value: "member3", Score: ss.Score(10)}, }), key: "key7", entries: map[string]float64{ "member1": 3.5, "member4": 100.5, "member5": 15, }, options: echovault.ZADDOptions{XX: true, LT: true}, want: 0, wantErr: false, }, { name: "Return all the elements that were updated AND added when CH flag is provided", preset: true, presetValue: ss.NewSortedSet([]ss.MemberParam{ {Value: "member1", Score: ss.Score(5.5)}, {Value: "member2", Score: ss.Score(67.77)}, {Value: "member3", Score: ss.Score(10)}, }), key: "key8", entries: map[string]float64{ "member1": 3.5, "member4": 100.5, "member5": 15, }, options: echovault.ZADDOptions{XX: true, LT: true, CH: true}, want: 1, wantErr: false, }, { name: "Increment the member by score", preset: true, presetValue: ss.NewSortedSet([]ss.MemberParam{ {Value: "member1", Score: ss.Score(5.5)}, {Value: "member2", Score: ss.Score(67.77)}, {Value: "member3", Score: ss.Score(10)}, }), key: "key9", entries: map[string]float64{ "member3": 5.5, }, options: echovault.ZADDOptions{INCR: true}, want: 0, wantErr: false, }, { name: "Fail when GT/LT flag is provided alongside NX flag", preset: false, presetValue: nil, key: "key10", entries: map[string]float64{ "member1": 3.5, "member5": 15, }, options: echovault.ZADDOptions{NX: true, LT: true, CH: true}, want: 0, wantErr: true, }, { name: "Throw error when INCR flag is passed with more than one score/member pair", preset: false, presetValue: nil, key: "key11", entries: map[string]float64{ "member1": 10.5, "member2": 12.5, }, options: echovault.ZADDOptions{INCR: true}, want: 0, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.preset { err := presetValue(server, context.Background(), tt.key, tt.presetValue) if err != nil { t.Error(err) return } } got, err := server.ZADD(tt.key, tt.entries, tt.options) if (err != nil) != tt.wantErr { t.Errorf("ZADD() error = %v, wantErr %v", err, tt.wantErr) return } if got != tt.want { t.Errorf("ZADD() got = %v, want %v", got, tt.want) } }) } } func TestEchoVault_ZCARD(t *testing.T) { server := createEchoVault() tests := []struct { name string preset bool presetValue interface{} key string want int wantErr bool }{ { name: "Get cardinality of valid sorted set", preset: true, presetValue: ss.NewSortedSet([]ss.MemberParam{ {Value: "member1", Score: ss.Score(5.5)}, {Value: "member2", Score: ss.Score(67.77)}, {Value: "member3", Score: ss.Score(10)}, }), key: "key1", want: 3, wantErr: false, }, { name: "Return 0 when trying to get cardinality from non-existent key", preset: false, presetValue: nil, key: "key2", want: 0, wantErr: false, }, { name: "Return error when not a sorted set", preset: true, presetValue: "Default value", key: "key3", want: 0, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.preset { err := presetValue(server, context.Background(), tt.key, tt.presetValue) if err != nil { t.Error(err) return } } got, err := server.ZCARD(tt.key) if (err != nil) != tt.wantErr { t.Errorf("ZCARD() error = %v, wantErr %v", err, tt.wantErr) return } if got != tt.want { t.Errorf("ZCARD() got = %v, want %v", got, tt.want) } }) } } func TestEchoVault_ZCOUNT(t *testing.T) { server := createEchoVault() tests := []struct { name string preset bool presetValue interface{} key string min float64 max float64 want int wantErr bool }{ { name: "Get entire count using infinity boundaries", preset: true, presetValue: ss.NewSortedSet([]ss.MemberParam{ {Value: "member1", Score: ss.Score(5.5)}, {Value: "member2", Score: ss.Score(67.77)}, {Value: "member3", Score: ss.Score(10)}, {Value: "member4", Score: ss.Score(1083.13)}, {Value: "member5", Score: ss.Score(11)}, {Value: "member6", Score: ss.Score(math.Inf(-1))}, {Value: "member7", Score: ss.Score(math.Inf(1))}, }), key: "key1", min: math.Inf(-1), max: math.Inf(1), want: 7, wantErr: false, }, { name: "Get count of sub-set from -inf to limit", preset: true, presetValue: ss.NewSortedSet([]ss.MemberParam{ {Value: "member1", Score: ss.Score(5.5)}, {Value: "member2", Score: ss.Score(67.77)}, {Value: "member3", Score: ss.Score(10)}, {Value: "member4", Score: ss.Score(1083.13)}, {Value: "member5", Score: ss.Score(11)}, {Value: "member6", Score: ss.Score(math.Inf(-1))}, {Value: "member7", Score: ss.Score(math.Inf(1))}, }), key: "key2", min: math.Inf(-1), max: 90, want: 5, wantErr: false, }, { name: "Get count of sub-set from bottom boundary to +inf limit", preset: true, presetValue: ss.NewSortedSet([]ss.MemberParam{ {Value: "member1", Score: ss.Score(5.5)}, {Value: "member2", Score: ss.Score(67.77)}, {Value: "member3", Score: ss.Score(10)}, {Value: "member4", Score: ss.Score(1083.13)}, {Value: "member5", Score: ss.Score(11)}, {Value: "member6", Score: ss.Score(math.Inf(-1))}, {Value: "member7", Score: ss.Score(math.Inf(1))}, }), key: "key3", min: 1000, max: math.Inf(1), want: 2, wantErr: false, }, { name: "Throw error when value at the key is not a sorted set", preset: true, presetValue: "Default value", key: "key4", min: 1, max: 10, want: 0, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.preset { err := presetValue(server, context.Background(), tt.key, tt.presetValue) if err != nil { t.Error(err) return } } got, err := server.ZCOUNT(tt.key, tt.min, tt.max) if (err != nil) != tt.wantErr { t.Errorf("ZCOUNT() error = %v, wantErr %v", err, tt.wantErr) return } if got != tt.want { t.Errorf("ZCOUNT() got = %v, want %v", got, tt.want) } }) } } func TestEchoVault_ZDIFF(t *testing.T) { server := createEchoVault() tests := []struct { name string preset bool presetValues map[string]interface{} withscores bool keys []string want map[string]float64 wantErr bool }{ { name: "Get the difference between 2 sorted sets without scores", preset: true, presetValues: map[string]interface{}{ "key1": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, }), "key2": ss.NewSortedSet([]ss.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}, }), }, withscores: false, keys: []string{"key1", "key2"}, want: map[string]float64{"one": 0, "two": 0}, wantErr: false, }, { name: "Get the difference between 2 sorted sets with scores", preset: true, presetValues: map[string]interface{}{ "key3": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, }), "key4": ss.NewSortedSet([]ss.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}, }), }, withscores: true, keys: []string{"key3", "key4"}, want: map[string]float64{"one": 1, "two": 2}, wantErr: false, }, { name: "Get the difference between 3 sets with scores", preset: true, presetValues: map[string]interface{}{ "key5": ss.NewSortedSet([]ss.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}, }), "key6": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, {Value: "eleven", Score: 11}, }), "key7": ss.NewSortedSet([]ss.MemberParam{ {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, {Value: "twelve", Score: 12}, }), }, withscores: true, keys: []string{"key5", "key6", "key7"}, want: map[string]float64{"three": 3, "four": 4, "five": 5, "six": 6}, wantErr: false, }, { name: "Return sorted set if only one key exists and is a sorted set", preset: true, presetValues: map[string]interface{}{ "key8": ss.NewSortedSet([]ss.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}, }), }, withscores: true, keys: []string{"key8", "non-existent-key-1", "non-existent-key-2", "non-existent-key-3"}, want: map[string]float64{ "one": 1, "two": 2, "three": 3, "four": 4, "five": 5, "six": 6, "seven": 7, "eight": 8, }, wantErr: false, }, { name: "Throw error when one of the keys is not a sorted set", preset: true, presetValues: map[string]interface{}{ "key9": "Default value", "key10": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, {Value: "eleven", Score: 11}, }), "key11": ss.NewSortedSet([]ss.MemberParam{ {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, {Value: "twelve", Score: 12}, }), }, withscores: false, keys: []string{"key9", "key10", "key11"}, want: nil, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.preset { for k, v := range tt.presetValues { err := presetValue(server, context.Background(), k, v) if err != nil { t.Error(err) return } } } got, err := server.ZDIFF(tt.withscores, tt.keys...) if (err != nil) != tt.wantErr { t.Errorf("ZDIFF() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("ZDIFF() got = %v, want %v", got, tt.want) } }) } } func TestEchoVault_ZDIFFSTORE(t *testing.T) { server := createEchoVault() tests := []struct { name string preset bool presetValues map[string]interface{} destination string keys []string want int wantErr bool }{ { name: "Get the difference between 2 sorted sets", preset: true, presetValues: map[string]interface{}{ "key1": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, {Value: "five", Score: 5}, }), "key2": ss.NewSortedSet([]ss.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", keys: []string{"key1", "key2"}, want: 2, wantErr: false, }, { name: "Get the difference between 3 sorted sets", preset: true, presetValues: map[string]interface{}{ "key3": ss.NewSortedSet([]ss.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": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, {Value: "eleven", Score: 11}, }), "key5": ss.NewSortedSet([]ss.MemberParam{ {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, {Value: "twelve", Score: 12}, }), }, destination: "destination2", keys: []string{"key3", "key4", "key5"}, want: 4, wantErr: false, }, { name: "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": ss.NewSortedSet([]ss.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", keys: []string{"key6", "non-existent-key-1", "non-existent-key-2"}, want: 8, wantErr: false, }, { name: "Throw error when base sorted set is not a set", preset: true, presetValues: map[string]interface{}{ "key7": "Default value", "key8": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, {Value: "eleven", Score: 11}, }), "key9": ss.NewSortedSet([]ss.MemberParam{ {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, {Value: "twelve", Score: 12}, }), }, destination: "destination4", keys: []string{"key7", "key8", "key9"}, want: 0, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.preset { for k, v := range tt.presetValues { err := presetValue(server, context.Background(), k, v) if err != nil { t.Error(err) return } } } got, err := server.ZDIFFSTORE(tt.destination, tt.keys...) if (err != nil) != tt.wantErr { t.Errorf("ZDIFFSTORE() error = %v, wantErr %v", err, tt.wantErr) return } if got != tt.want { t.Errorf("ZDIFFSTORE() got = %v, want %v", got, tt.want) } }) } } func TestEchoVault_ZINCRBY(t *testing.T) { server := createEchoVault() tests := []struct { name string preset bool presetValue interface{} key string increment float64 member string want float64 wantErr bool }{ { name: "Successfully increment by int. Return the new score", preset: true, presetValue: ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, {Value: "five", Score: 5}, }), key: "key1", increment: 5, member: "one", want: 6, wantErr: false, }, { name: "Successfully increment by float. Return new score", preset: true, presetValue: ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, {Value: "five", Score: 5}, }), key: "key2", increment: 346.785, member: "one", want: 347.785, }, { name: "Increment on non-existent sorted set will create the set with the member and increment as its score", preset: false, presetValue: nil, key: "key3", increment: 346.785, member: "one", want: 346.785, wantErr: false, }, { // 4. name: "Increment score to +inf", preset: true, presetValue: ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, {Value: "five", Score: 5}, }), key: "key4", increment: math.Inf(1), member: "one", want: math.Inf(1), wantErr: false, }, { name: "Increment score to -inf", preset: true, presetValue: ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, {Value: "five", Score: 5}, }), key: "key5", increment: math.Inf(-1), member: "one", want: math.Inf(-1), wantErr: false, }, { name: "Incrementing score by negative increment should lower the score", preset: true, presetValue: ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, {Value: "five", Score: 5}, }), key: "key6", increment: -2.5, member: "five", want: 2.5, wantErr: false, }, { name: "Return error when attempting to increment on a value that is not a valid sorted set", preset: true, presetValue: "Default value", key: "key7", increment: -2.5, member: "five", want: 0, wantErr: true, }, { name: "Return error when trying to increment a member that already has score -inf", preset: true, presetValue: ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: ss.Score(math.Inf(-1))}, }), key: "key8", increment: 2.5, member: "one", want: 0, wantErr: true, }, { name: "Return error when trying to increment a member that already has score +inf", preset: true, presetValue: ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: ss.Score(math.Inf(1))}, }), key: "key9", increment: 2.5, member: "one", want: 0, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.preset { err := presetValue(server, context.Background(), tt.key, tt.presetValue) if err != nil { t.Error(err) return } } got, err := server.ZINCRBY(tt.key, tt.increment, tt.member) if (err != nil) != tt.wantErr { t.Errorf("ZINCRBY() error = %v, wantErr %v", err, tt.wantErr) return } if got != tt.want { t.Errorf("ZINCRBY() got = %v, want %v", got, tt.want) } }) } } func TestEchoVault_ZINTER(t *testing.T) { server := createEchoVault() tests := []struct { name string preset bool presetValues map[string]interface{} keys []string options echovault.ZINTEROptions want map[string]float64 wantErr bool }{ { name: "Get the intersection between 2 sorted sets", preset: true, presetValues: map[string]interface{}{ "key1": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, {Value: "five", Score: 5}, }), "key2": ss.NewSortedSet([]ss.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}, }), }, keys: []string{"key1", "key2"}, options: echovault.ZINTEROptions{}, want: map[string]float64{"three": 0, "four": 0, "five": 0}, wantErr: false, }, { // Get the intersection between 3 sorted sets with scores. // By default, the SUM aggregate will be used. name: "Get the intersection between 3 sorted sets with scores", preset: true, presetValues: map[string]interface{}{ "key3": ss.NewSortedSet([]ss.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": ss.NewSortedSet([]ss.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": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "eight", Score: 8}, {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, {Value: "twelve", Score: 12}, }), }, keys: []string{"key3", "key4", "key5"}, options: echovault.ZINTEROptions{WithScores: true}, want: map[string]float64{"one": 3, "eight": 24}, wantErr: false, }, { // Get the intersection between 3 sorted sets with scores. // Use MIN aggregate. name: "Get the intersection between 3 sorted sets with scores", preset: true, presetValues: map[string]interface{}{ "key6": ss.NewSortedSet([]ss.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": ss.NewSortedSet([]ss.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": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1000}, {Value: "eight", Score: 800}, {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, {Value: "twelve", Score: 12}, }), }, keys: []string{"key6", "key7", "key8"}, options: echovault.ZINTEROptions{Aggregate: "MIN", WithScores: true}, want: map[string]float64{"one": 1, "eight": 8}, wantErr: false, }, { // Get the intersection between 3 sorted sets with scores. // Use MAX aggregate. preset: true, presetValues: map[string]interface{}{ "key9": ss.NewSortedSet([]ss.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": ss.NewSortedSet([]ss.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": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1000}, {Value: "eight", Score: 800}, {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, {Value: "twelve", Score: 12}, }), }, keys: []string{"key9", "key10", "key11"}, options: echovault.ZINTEROptions{WithScores: true, Aggregate: "MAX"}, want: map[string]float64{"one": 1000, "eight": 800}, wantErr: false, }, { // Get the intersection between 3 sorted sets with scores. // Use SUM aggregate with weights modifier. name: "Get the intersection between 3 sorted sets with scores", preset: true, presetValues: map[string]interface{}{ "key12": ss.NewSortedSet([]ss.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": ss.NewSortedSet([]ss.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": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1000}, {Value: "eight", Score: 800}, {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, {Value: "twelve", Score: 12}, }), }, keys: []string{"key12", "key13", "key14"}, options: echovault.ZINTEROptions{WithScores: true, Aggregate: "SUM", Weights: []float64{1, 5, 3}}, want: map[string]float64{"one": 3105, "eight": 2808}, wantErr: false, }, { // Get the intersection between 3 sorted sets with scores. // Use MAX aggregate with added weights. name: "Get the intersection between 3 sorted sets with scores", preset: true, presetValues: map[string]interface{}{ "key15": ss.NewSortedSet([]ss.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": ss.NewSortedSet([]ss.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": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1000}, {Value: "eight", Score: 800}, {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, {Value: "twelve", Score: 12}, }), }, keys: []string{"key15", "key16", "key17"}, options: echovault.ZINTEROptions{WithScores: true, Aggregate: "MAX", Weights: []float64{1, 5, 3}}, want: map[string]float64{"one": 3000, "eight": 2400}, wantErr: false, }, { // Get the intersection between 3 sorted sets with scores. // Use MIN aggregate with added weights. name: "Get the intersection between 3 sorted sets with scores", preset: true, presetValues: map[string]interface{}{ "key18": ss.NewSortedSet([]ss.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": ss.NewSortedSet([]ss.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": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1000}, {Value: "eight", Score: 800}, {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, {Value: "twelve", Score: 12}, }), }, keys: []string{"key18", "key19", "key20"}, options: echovault.ZINTEROptions{WithScores: true, Aggregate: "MIN", Weights: []float64{1, 5, 3}}, want: map[string]float64{"one": 5, "eight": 8}, wantErr: false, }, { name: "Throw an error if there are more weights than keys", preset: true, presetValues: map[string]interface{}{ "key21": ss.NewSortedSet([]ss.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": ss.NewSortedSet([]ss.MemberParam{{Value: "one", Score: 1}}), }, keys: []string{"key21", "key22"}, options: echovault.ZINTEROptions{Weights: []float64{1, 2, 3}}, want: nil, wantErr: true, }, { name: "Throw an error if there are fewer weights than keys", preset: true, presetValues: map[string]interface{}{ "key23": ss.NewSortedSet([]ss.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": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, }), "key25": ss.NewSortedSet([]ss.MemberParam{{Value: "one", Score: 1}}), }, keys: []string{"key23", "key24", "key25"}, options: echovault.ZINTEROptions{Weights: []float64{5, 4}}, want: nil, wantErr: true, }, { name: "Throw an error if there are no keys provided", preset: true, presetValues: map[string]interface{}{ "key26": ss.NewSortedSet([]ss.MemberParam{{Value: "one", Score: 1}}), "key27": ss.NewSortedSet([]ss.MemberParam{{Value: "one", Score: 1}}), "key28": ss.NewSortedSet([]ss.MemberParam{{Value: "one", Score: 1}}), }, keys: []string{}, options: echovault.ZINTEROptions{}, want: nil, wantErr: true, }, { name: "Throw an error if any of the provided keys are not sorted sets", preset: true, presetValues: map[string]interface{}{ "key29": ss.NewSortedSet([]ss.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": ss.NewSortedSet([]ss.MemberParam{{Value: "one", Score: 1}}), }, keys: []string{"key29", "key30", "key31"}, options: echovault.ZINTEROptions{}, want: nil, wantErr: true, }, { name: "If any of the keys does not exist, return an empty array", preset: true, presetValues: map[string]interface{}{ "key32": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, {Value: "eleven", Score: 11}, }), "key33": ss.NewSortedSet([]ss.MemberParam{ {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, {Value: "twelve", Score: 12}, }), }, keys: []string{"non-existent", "key32", "key33"}, options: echovault.ZINTEROptions{}, want: map[string]float64{}, wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.preset { for k, v := range tt.presetValues { err := presetValue(server, context.Background(), k, v) if err != nil { t.Error(err) return } } } got, err := server.ZINTER(tt.keys, tt.options) if (err != nil) != tt.wantErr { t.Errorf("ZINTER() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("ZINTER() got = %v, want %v", got, tt.want) } }) } } func TestEchoVault_ZINTERSTORE(t *testing.T) { server := createEchoVault() tests := []struct { name string preset bool presetValues map[string]interface{} destination string keys []string options echovault.ZINTERSTOREOptions want int wantErr bool }{ { name: "Get the intersection between 2 sorted sets", preset: true, presetValues: map[string]interface{}{ "key1": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, {Value: "five", Score: 5}, }), "key2": ss.NewSortedSet([]ss.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", keys: []string{"key1", "key2"}, options: echovault.ZINTERSTOREOptions{}, want: 3, wantErr: false, }, { // Get the intersection between 3 sorted sets with scores. // By default, the SUM aggregate will be used. name: "Get the intersection between 3 sorted sets with scores", preset: true, presetValues: map[string]interface{}{ "key3": ss.NewSortedSet([]ss.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": ss.NewSortedSet([]ss.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": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "eight", Score: 8}, {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, {Value: "twelve", Score: 12}, }), }, destination: "destination2", keys: []string{"key3", "key4", "key5"}, options: echovault.ZINTERSTOREOptions{WithScores: true}, want: 2, wantErr: false, }, { // Get the intersection between 3 sorted sets with scores. // Use MIN aggregate. name: "Get the intersection between 3 sorted sets with scores", preset: true, presetValues: map[string]interface{}{ "key6": ss.NewSortedSet([]ss.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": ss.NewSortedSet([]ss.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": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1000}, {Value: "eight", Score: 800}, {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, {Value: "twelve", Score: 12}, }), }, destination: "destination3", keys: []string{"key6", "key7", "key8"}, options: echovault.ZINTERSTOREOptions{WithScores: true, Aggregate: "MIN"}, want: 2, wantErr: false, }, { // Get the intersection between 3 sorted sets with scores. // Use MAX aggregate. name: "Get the intersection between 3 sorted sets with scores", preset: true, presetValues: map[string]interface{}{ "key9": ss.NewSortedSet([]ss.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": ss.NewSortedSet([]ss.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": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1000}, {Value: "eight", Score: 800}, {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, {Value: "twelve", Score: 12}, }), }, destination: "destination4", keys: []string{"key9", "key10", "key11"}, options: echovault.ZINTERSTOREOptions{WithScores: true, Aggregate: "MAX"}, want: 2, wantErr: false, }, { // Get the intersection between 3 sorted sets with scores. // Use SUM aggregate with weights modifier. name: "Get the intersection between 3 sorted sets with scores", preset: true, presetValues: map[string]interface{}{ "key12": ss.NewSortedSet([]ss.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": ss.NewSortedSet([]ss.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": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1000}, {Value: "eight", Score: 800}, {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, {Value: "twelve", Score: 12}, }), }, destination: "destination5", keys: []string{"key12", "key13", "key14"}, options: echovault.ZINTERSTOREOptions{WithScores: true, Aggregate: "SUM", Weights: []float64{1, 5, 3}}, want: 2, wantErr: false, }, { // Get the intersection between 3 sorted sets with scores. // Use MAX aggregate with added weights. name: "Get the intersection between 3 sorted sets with scores", preset: true, presetValues: map[string]interface{}{ "key15": ss.NewSortedSet([]ss.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": ss.NewSortedSet([]ss.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": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1000}, {Value: "eight", Score: 800}, {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, {Value: "twelve", Score: 12}, }), }, destination: "destination6", keys: []string{"key15", "key16", "key17"}, options: echovault.ZINTERSTOREOptions{WithScores: true, Aggregate: "MAX", Weights: []float64{1, 5, 3}}, want: 2, wantErr: false, }, { // Get the intersection between 3 sorted sets with scores. // Use MIN aggregate with added weights. name: "Get the intersection between 3 sorted sets with scores", preset: true, presetValues: map[string]interface{}{ "key18": ss.NewSortedSet([]ss.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": ss.NewSortedSet([]ss.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": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1000}, {Value: "eight", Score: 800}, {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, {Value: "twelve", Score: 12}, }), }, destination: "destination7", keys: []string{"key18", "key19", "key20"}, options: echovault.ZINTERSTOREOptions{WithScores: true, Aggregate: "MIN", Weights: []float64{1, 5, 3}}, want: 2, wantErr: false, }, { name: "Throw an error if there are more weights than keys", preset: true, presetValues: map[string]interface{}{ "key21": ss.NewSortedSet([]ss.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": ss.NewSortedSet([]ss.MemberParam{{Value: "one", Score: 1}}), }, destination: "destination8", keys: []string{"key21", "key22"}, options: echovault.ZINTERSTOREOptions{Weights: []float64{1, 2, 3}}, want: 0, wantErr: true, }, { name: "Throw an error if there are fewer weights than keys", preset: true, presetValues: map[string]interface{}{ "key23": ss.NewSortedSet([]ss.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": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, }), "key25": ss.NewSortedSet([]ss.MemberParam{{Value: "one", Score: 1}}), }, destination: "destination9", keys: []string{"key23", "key24"}, options: echovault.ZINTERSTOREOptions{Weights: []float64{5}}, want: 0, wantErr: true, }, { name: "Throw an error if there are no keys provided", preset: true, presetValues: map[string]interface{}{ "key26": ss.NewSortedSet([]ss.MemberParam{{Value: "one", Score: 1}}), "key27": ss.NewSortedSet([]ss.MemberParam{{Value: "one", Score: 1}}), "key28": ss.NewSortedSet([]ss.MemberParam{{Value: "one", Score: 1}}), }, destination: "destination10", keys: []string{}, options: echovault.ZINTERSTOREOptions{Weights: []float64{5, 4}}, want: 0, wantErr: true, }, { name: "Throw an error if any of the provided keys are not sorted sets", preset: true, presetValues: map[string]interface{}{ "key29": ss.NewSortedSet([]ss.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": ss.NewSortedSet([]ss.MemberParam{{Value: "one", Score: 1}}), }, destination: "destination11", keys: []string{"key29", "key30", "key31"}, options: echovault.ZINTERSTOREOptions{}, want: 0, wantErr: true, }, { name: "If any of the keys does not exist, return an empty array", preset: true, presetValues: map[string]interface{}{ "key32": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, {Value: "eleven", Score: 11}, }), "key33": ss.NewSortedSet([]ss.MemberParam{ {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, {Value: "twelve", Score: 12}, }), }, destination: "destination12", keys: []string{"non-existent", "key32", "key33"}, options: echovault.ZINTERSTOREOptions{}, want: 0, wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.preset { for k, v := range tt.presetValues { err := presetValue(server, context.Background(), k, v) if err != nil { t.Error(err) return } } } got, err := server.ZINTERSTORE(tt.destination, tt.keys, tt.options) if (err != nil) != tt.wantErr { t.Errorf("ZINTERSTORE() error = %v, wantErr %v", err, tt.wantErr) return } if got != tt.want { t.Errorf("ZINTERSTORE() got = %v, want %v", got, tt.want) } }) } } func TestEchoVault_ZLEXCOUNT(t *testing.T) { server := createEchoVault() tests := []struct { name string preset bool presetValue interface{} key string min string max string want int wantErr bool }{ { name: "Get entire count using infinity boundaries", preset: true, presetValue: ss.NewSortedSet([]ss.MemberParam{ {Value: "e", Score: ss.Score(1)}, {Value: "f", Score: ss.Score(1)}, {Value: "g", Score: ss.Score(1)}, {Value: "h", Score: ss.Score(1)}, {Value: "i", Score: ss.Score(1)}, {Value: "j", Score: ss.Score(1)}, {Value: "k", Score: ss.Score(1)}, }), key: "key1", min: "f", max: "j", want: 5, wantErr: false, }, { name: "Return 0 when the members do not have the same score", preset: true, presetValue: ss.NewSortedSet([]ss.MemberParam{ {Value: "a", Score: ss.Score(5.5)}, {Value: "b", Score: ss.Score(67.77)}, {Value: "c", Score: ss.Score(10)}, {Value: "d", Score: ss.Score(1083.13)}, {Value: "e", Score: ss.Score(11)}, {Value: "f", Score: ss.Score(math.Inf(-1))}, {Value: "g", Score: ss.Score(math.Inf(1))}, }), key: "key2", min: "a", max: "b", want: 0, wantErr: false, }, { name: "Return 0 when the key does not exist", preset: false, presetValue: nil, key: "key3", min: "a", max: "z", want: 0, wantErr: false, }, { name: "Return error when the value at the key is not a sorted set", preset: true, presetValue: "Default value", key: "key4", min: "a", max: "z", want: 0, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.preset { err := presetValue(server, context.Background(), tt.key, tt.presetValue) if err != nil { t.Error(err) return } } got, err := server.ZLEXCOUNT(tt.key, tt.min, tt.max) if (err != nil) != tt.wantErr { t.Errorf("ZLEXCOUNT() error = %v, wantErr %v", err, tt.wantErr) return } if got != tt.want { t.Errorf("ZLEXCOUNT() got = %v, want %v", got, tt.want) } }) } } func TestEchoVault_ZMPOP(t *testing.T) { server := createEchoVault() tests := []struct { name string preset bool presetValues map[string]interface{} keys []string options echovault.ZMPOPOptions want [][]string wantErr bool }{ { name: "Successfully pop one min element by default", preset: true, presetValues: map[string]interface{}{ "key1": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, {Value: "five", Score: 5}, }), }, keys: []string{"key1"}, options: echovault.ZMPOPOptions{}, want: [][]string{ {"one", "1"}, }, wantErr: false, }, { name: "Successfully pop one min element by specifying MIN", preset: true, presetValues: map[string]interface{}{ "key2": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, {Value: "five", Score: 5}, }), }, keys: []string{"key2"}, options: echovault.ZMPOPOptions{Min: true}, want: [][]string{ {"one", "1"}, }, wantErr: false, }, { name: "Successfully pop one max element by specifying MAX modifier", preset: true, presetValues: map[string]interface{}{ "key3": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, {Value: "five", Score: 5}, }), }, keys: []string{"key3"}, options: echovault.ZMPOPOptions{Max: true}, want: [][]string{ {"five", "5"}, }, wantErr: false, }, { name: "Successfully pop multiple min elements", preset: true, presetValues: map[string]interface{}{ "key4": ss.NewSortedSet([]ss.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}, }), }, keys: []string{"key4"}, options: echovault.ZMPOPOptions{Min: true, Count: 5}, want: [][]string{ {"one", "1"}, {"two", "2"}, {"three", "3"}, {"four", "4"}, {"five", "5"}, }, wantErr: false, }, { name: "Successfully pop multiple max elements", preset: true, presetValues: map[string]interface{}{ "key5": ss.NewSortedSet([]ss.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}, }), }, keys: []string{"key5"}, options: echovault.ZMPOPOptions{Max: true, Count: 5}, want: [][]string{{"two", "2"}, {"three", "3"}, {"four", "4"}, {"five", "5"}, {"six", "6"}}, wantErr: false, }, { name: "Successfully pop elements from the first set which is non-empty", preset: true, presetValues: map[string]interface{}{ "key6": ss.NewSortedSet([]ss.MemberParam{}), "key7": ss.NewSortedSet([]ss.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}, }), }, keys: []string{"key6", "key7"}, options: echovault.ZMPOPOptions{Max: true, Count: 5}, want: [][]string{{"two", "2"}, {"three", "3"}, {"four", "4"}, {"five", "5"}, {"six", "6"}}, wantErr: false, }, { name: "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": ss.NewSortedSet([]ss.MemberParam{}), "key11": ss.NewSortedSet([]ss.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}, }), }, keys: []string{"key8", "key9", "key10", "key11"}, options: echovault.ZMPOPOptions{Min: true, Count: 5}, want: [][]string{{"one", "1"}, {"two", "2"}, {"three", "3"}, {"four", "4"}, {"five", "5"}}, wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.preset { for k, v := range tt.presetValues { err := presetValue(server, context.Background(), k, v) if err != nil { t.Error(err) return } } } got, err := server.ZMPOP(tt.keys, tt.options) if (err != nil) != tt.wantErr { t.Errorf("ZMPOP() error = %v, wantErr %v", err, tt.wantErr) return } if !internal.CompareNestedStringArrays(got, tt.want) { t.Errorf("ZMPOP() got = %v, want %v", got, tt.want) } }) } } func TestEchoVault_ZMSCORE(t *testing.T) { server := createEchoVault() tests := []struct { name string preset bool presetValue interface{} key string members []string want []interface{} wantErr bool }{ { // Return multiple scores from the sorted set. // Return nil for elements that do not exist in the sorted set. name: "Return multiple scores from the sorted set", preset: true, presetValue: ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1.1}, {Value: "two", Score: 245}, {Value: "three", Score: 3}, {Value: "four", Score: 4.055}, {Value: "five", Score: 5}, }), key: "key1", members: []string{"one", "none", "two", "one", "three", "four", "none", "five"}, want: []interface{}{"1.1", nil, "245", "1.1", "3", "4.055", nil, "5"}, wantErr: false, }, { name: "If key does not exist, return empty array", preset: false, presetValue: nil, key: "key2", members: []string{"one", "two", "three", "four"}, want: []interface{}{}, wantErr: false, }, { name: "Throw error when trying to find scores from elements that are not sorted sets", preset: true, presetValue: "Default value", key: "key3", members: []string{"one", "two", "three"}, want: []interface{}{}, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.preset { err := presetValue(server, context.Background(), tt.key, tt.presetValue) if err != nil { t.Error(err) return } } got, err := server.ZMSCORE(tt.key, tt.members...) if (err != nil) != tt.wantErr { t.Errorf("ZMSCORE() error = %v, wantErr %v", err, tt.wantErr) return } if len(got) != len(tt.want) { t.Errorf("ZMSCORE() got length = %v, want length %v", len(got), len(tt.want)) return } for i := 0; i < len(got); i++ { if got[i] == nil && tt.want[i] == nil { continue } if (got[i] == nil) != (tt.want[i] == nil) { t.Errorf("ZMSCORE() got[%d] = %v, want[%d] %v", i, got, i, tt.want) } wantf, _ := strconv.ParseFloat(tt.want[i].(string), 64) if got[i] != wantf { t.Errorf("ZMSCORE() got[%d] = %v, want[%d] %v", i, got[i], i, wantf) } } }) } } func TestEchoVault_ZPOP(t *testing.T) { server := createEchoVault() tests := []struct { name string preset bool presetValue interface{} key string count uint popFunc func(key string, count uint) ([][]string, error) want [][]string wantErr bool }{ { name: "Successfully pop one min element", preset: true, presetValue: ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, {Value: "five", Score: 5}, }), key: "key1", count: 1, popFunc: server.ZPOPMIN, want: [][]string{ {"one", "1"}, }, wantErr: false, }, { name: "Successfully pop one max element", preset: true, presetValue: ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, {Value: "five", Score: 5}, }), key: "key2", count: 1, popFunc: server.ZPOPMAX, want: [][]string{{"five", "5"}}, wantErr: false, }, { name: "Successfully pop multiple min elements", preset: true, presetValue: ss.NewSortedSet([]ss.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}, }), popFunc: server.ZPOPMIN, key: "key3", count: 5, want: [][]string{ {"one", "1"}, {"two", "2"}, {"three", "3"}, {"four", "4"}, {"five", "5"}, }, wantErr: false, }, { name: "Successfully pop multiple max elements", preset: true, presetValue: ss.NewSortedSet([]ss.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}, }), popFunc: server.ZPOPMAX, key: "key4", count: 5, want: [][]string{{"two", "2"}, {"three", "3"}, {"four", "4"}, {"five", "5"}, {"six", "6"}}, wantErr: false, }, { name: "Throw an error when trying to pop from an element that's not a sorted set", preset: true, presetValue: "Default value", popFunc: server.ZPOPMIN, key: "key5", count: 1, want: [][]string{}, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.preset { err := presetValue(server, context.Background(), tt.key, tt.presetValue) if err != nil { t.Error(err) return } } got, err := tt.popFunc(tt.key, tt.count) if (err != nil) != tt.wantErr { t.Errorf("ZPOPMAX() error = %v, wantErr %v", err, tt.wantErr) return } if !internal.CompareNestedStringArrays(got, tt.want) { t.Errorf("ZPOPMAX() got = %v, want %v", got, tt.want) } }) } } func TestEchoVault_ZRANDMEMBER(t *testing.T) { server := createEchoVault() tests := []struct { name string preset bool presetValue interface{} key string count int withscores bool want int wantErr bool }{ { // Return multiple random elements without removing them. // Count is positive, do not allow repeated elements. name: "Return multiple random elements without removing them", preset: true, key: "key1", presetValue: ss.NewSortedSet([]ss.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}, }), count: 3, withscores: false, want: 3, wantErr: false, }, { // Return multiple random elements and their scores without removing them. // Count is negative, so allow repeated numbers. name: "Return multiple random elements and their scores without removing them", preset: true, key: "key2", presetValue: ss.NewSortedSet([]ss.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}, }), count: -5, withscores: true, want: 5, wantErr: false, }, { name: "Return error when the source key is not a sorted set", preset: true, key: "key3", presetValue: "Default value", count: 1, withscores: false, want: 0, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.preset { err := presetValue(server, context.Background(), tt.key, tt.presetValue) if err != nil { t.Error(err) return } } got, err := server.ZRANDMEMBER(tt.key, tt.count, tt.withscores) if (err != nil) != tt.wantErr { t.Errorf("ZRANDMEMBER() error = %v, wantErr %v", err, tt.wantErr) return } if len(got) != tt.want { t.Errorf("ZRANDMEMBER() got = %v, want %v", len(got), tt.want) } }) } } func TestEchoVault_ZRANGE(t *testing.T) { server := createEchoVault() tests := []struct { name string preset bool presetValue interface{} key string start string stop string options echovault.ZRANGEOptions want map[string]float64 wantErr bool }{ { name: "Get elements withing score range without score", preset: true, presetValue: ss.NewSortedSet([]ss.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}, }), key: "key1", start: "3", stop: "7", options: echovault.ZRANGEOptions{ByScore: true}, want: map[string]float64{"three": 0, "four": 0, "five": 0, "six": 0, "seven": 0}, wantErr: false, }, { name: "Get elements within score range with score", preset: true, presetValue: ss.NewSortedSet([]ss.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}, }), key: "key2", start: "3", stop: "7", options: echovault.ZRANGEOptions{ByScore: true, WithScores: true}, want: map[string]float64{"three": 3, "four": 4, "five": 5, "six": 6, "seven": 7}, wantErr: false, }, { // Get elements within score range with offset and limit. // Offset and limit are in where we start and stop counting in the original sorted set (NOT THE RESULT). name: "Get elements within score range with offset and limit", preset: true, presetValue: ss.NewSortedSet([]ss.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}, }), key: "key3", start: "3", stop: "7", options: echovault.ZRANGEOptions{WithScores: true, ByScore: true, Offset: 2, Count: 4}, want: map[string]float64{"three": 3, "four": 4, "five": 5}, wantErr: false, }, { name: "Get elements within lex range without score", preset: true, presetValue: ss.NewSortedSet([]ss.MemberParam{ {Value: "a", Score: 1}, {Value: "e", Score: 1}, {Value: "b", Score: 1}, {Value: "f", Score: 1}, {Value: "c", Score: 1}, {Value: "g", Score: 1}, {Value: "d", Score: 1}, {Value: "h", Score: 1}, }), key: "key4", start: "c", stop: "g", options: echovault.ZRANGEOptions{ByLex: true}, want: map[string]float64{"c": 0, "d": 0, "e": 0, "f": 0, "g": 0}, wantErr: false, }, { name: "Get elements within lex range with score", preset: true, presetValue: ss.NewSortedSet([]ss.MemberParam{ {Value: "a", Score: 1}, {Value: "e", Score: 1}, {Value: "b", Score: 1}, {Value: "f", Score: 1}, {Value: "c", Score: 1}, {Value: "g", Score: 1}, {Value: "d", Score: 1}, {Value: "h", Score: 1}, }), key: "key5", start: "a", stop: "f", options: echovault.ZRANGEOptions{ByLex: true, WithScores: true}, want: map[string]float64{"a": 1, "b": 1, "c": 1, "d": 1, "e": 1, "f": 1}, wantErr: false, }, { // Get elements within lex range with offset and limit. // Offset and limit are in where we start and stop counting in the original sorted set (NOT THE RESULT). name: "Get elements within lex range with offset and limit", preset: true, presetValue: ss.NewSortedSet([]ss.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}, }), key: "key6", start: "a", stop: "h", options: echovault.ZRANGEOptions{WithScores: true, ByLex: true, Offset: 2, Count: 4}, want: map[string]float64{"c": 1, "d": 1, "e": 1}, wantErr: false, }, { name: "Return an empty map when we use BYLEX while elements have different scores", preset: true, presetValue: ss.NewSortedSet([]ss.MemberParam{ {Value: "a", Score: 1}, {Value: "b", Score: 5}, {Value: "c", Score: 2}, {Value: "d", Score: 6}, {Value: "e", Score: 3}, {Value: "f", Score: 7}, {Value: "g", Score: 4}, {Value: "h", Score: 8}, }), key: "key7", start: "a", stop: "h", options: echovault.ZRANGEOptions{WithScores: true, ByLex: true, Offset: 2, Count: 4}, want: map[string]float64{}, wantErr: false, }, { name: "Throw error when the key does not hold a sorted set", preset: true, presetValue: "Default value", key: "key10", start: "a", stop: "h", options: echovault.ZRANGEOptions{WithScores: true, ByLex: true, Offset: 2, Count: 4}, want: nil, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.preset { err := presetValue(server, context.Background(), tt.key, tt.presetValue) if err != nil { t.Error(err) return } } got, err := server.ZRANGE(tt.key, tt.start, tt.stop, tt.options) if (err != nil) != tt.wantErr { t.Errorf("ZRANGE() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("ZRANGE() got = %v, want %v", got, tt.want) } }) } } func TestEchoVault_ZRANGESTORE(t *testing.T) { server := createEchoVault() tests := []struct { name string preset bool presetValues map[string]interface{} destination string source string start string stop string options echovault.ZRANGESTOREOptions want int wantErr bool }{ { name: "Get elements within score range without score", preset: true, presetValues: map[string]interface{}{ "key1": ss.NewSortedSet([]ss.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: "destination1", source: "key1", start: "3", stop: "7", options: echovault.ZRANGESTOREOptions{ByScore: true}, want: 5, wantErr: false, }, { name: "Get elements within score range with score", preset: true, presetValues: map[string]interface{}{ "key2": ss.NewSortedSet([]ss.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: "destination2", source: "key2", start: "3", stop: "7", options: echovault.ZRANGESTOREOptions{WithScores: true, ByScore: true}, want: 5, wantErr: false, }, { // Get elements within score range with offset and limit. // Offset and limit are in where we start and stop counting in the original sorted set (NOT THE RESULT). name: "Get elements within score range with offset and limit", preset: true, presetValues: map[string]interface{}{ "key3": ss.NewSortedSet([]ss.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", source: "key3", start: "3", stop: "7", options: echovault.ZRANGESTOREOptions{ByScore: true, WithScores: true, Offset: 2, Count: 4}, want: 3, wantErr: false, }, { name: "Get elements within lex range without score", preset: true, presetValues: map[string]interface{}{ "key4": ss.NewSortedSet([]ss.MemberParam{ {Value: "a", Score: 1}, {Value: "e", Score: 1}, {Value: "b", Score: 1}, {Value: "f", Score: 1}, {Value: "c", Score: 1}, {Value: "g", Score: 1}, {Value: "d", Score: 1}, {Value: "h", Score: 1}, }), }, destination: "destination4", source: "key4", start: "c", stop: "g", options: echovault.ZRANGESTOREOptions{ByLex: true}, want: 5, wantErr: false, }, { name: "Get elements within lex range with score", preset: true, presetValues: map[string]interface{}{ "key5": ss.NewSortedSet([]ss.MemberParam{ {Value: "a", Score: 1}, {Value: "e", Score: 1}, {Value: "b", Score: 1}, {Value: "f", Score: 1}, {Value: "c", Score: 1}, {Value: "g", Score: 1}, {Value: "d", Score: 1}, {Value: "h", Score: 1}, }), }, destination: "destination5", source: "key5", start: "a", stop: "f", options: echovault.ZRANGESTOREOptions{ByLex: true, WithScores: true}, want: 6, wantErr: false, }, { // Get elements within lex range with offset and limit. // Offset and limit are in where we start and stop counting in the original sorted set (NOT THE RESULT). name: "Get elements within lex range with offset and limit", preset: true, presetValues: map[string]interface{}{ "key6": ss.NewSortedSet([]ss.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}, }), }, destination: "destination6", source: "key6", start: "a", stop: "h", options: echovault.ZRANGESTOREOptions{WithScores: true, ByLex: true, Offset: 2, Count: 4}, want: 3, wantErr: false, }, { // Get elements within lex range with offset and limit + reverse the results. // Offset and limit are in where we start and stop counting in the original sorted set (NOT THE RESULT). // REV reverses the original set before getting the range. name: "Get elements within lex range with offset and limit + reverse the results", preset: true, presetValues: map[string]interface{}{ "key7": ss.NewSortedSet([]ss.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}, }), }, destination: "destination7", source: "key7", start: "a", stop: "h", options: echovault.ZRANGESTOREOptions{WithScores: true, ByLex: true, Offset: 2, Count: 4}, want: 3, wantErr: false, }, { name: "Return an empty slice when we use BYLEX while elements have different scores", preset: true, presetValues: map[string]interface{}{ "key8": ss.NewSortedSet([]ss.MemberParam{ {Value: "a", Score: 1}, {Value: "b", Score: 5}, {Value: "c", Score: 2}, {Value: "d", Score: 6}, {Value: "e", Score: 3}, {Value: "f", Score: 7}, {Value: "g", Score: 4}, {Value: "h", Score: 8}, }), }, destination: "destination8", source: "key8", start: "a", stop: "h", options: echovault.ZRANGESTOREOptions{WithScores: true, ByLex: true, Offset: 2, Count: 4}, want: 0, wantErr: false, }, { name: "Throw error when the key does not hold a sorted set", preset: true, presetValues: map[string]interface{}{ "key9": "Default value", }, destination: "destination9", source: "key9", start: "a", stop: "h", options: echovault.ZRANGESTOREOptions{WithScores: true, ByLex: true, Offset: 2, Count: 4}, want: 0, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.preset { for k, v := range tt.presetValues { err := presetValue(server, context.Background(), k, v) if err != nil { t.Error(err) return } } } got, err := server.ZRANGESTORE(tt.destination, tt.source, tt.start, tt.stop, tt.options) if (err != nil) != tt.wantErr { t.Errorf("ZRANGESTORE() error = %v, wantErr %v", err, tt.wantErr) return } if got != tt.want { t.Errorf("ZRANGESTORE() got = %v, want %v", got, tt.want) } }) } } func TestEchoVault_ZRANK(t *testing.T) { server := createEchoVault() tests := []struct { name string preset bool presetValue interface{} key string member string withscores bool want map[int]float64 wantErr bool }{ { name: "Return element's rank from a sorted set", preset: true, presetValue: ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, {Value: "five", Score: 5}, }), key: "key1", member: "four", withscores: false, want: map[int]float64{3: 0}, wantErr: false, }, { name: "Return element's rank from a sorted set with its score", preset: true, presetValue: ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 100.1}, {Value: "two", Score: 245}, {Value: "three", Score: 305.43}, {Value: "four", Score: 411.055}, {Value: "five", Score: 500}, }), key: "key2", member: "four", withscores: true, want: map[int]float64{3: 411.055}, wantErr: false, }, { name: "If key does not exist, return nil value", preset: false, presetValue: nil, key: "key3", member: "one", withscores: false, want: nil, wantErr: false, }, { name: "If key exists and is a sorted set, but the member does not exist, return nil", preset: true, presetValue: ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1.1}, {Value: "two", Score: 245}, {Value: "three", Score: 3}, {Value: "four", Score: 4.055}, {Value: "five", Score: 5}, }), key: "key4", member: "non-existent", withscores: false, want: nil, wantErr: false, }, { name: "Throw error when trying to find scores from elements that are not sorted sets", preset: true, presetValue: "Default value", key: "key5", member: "one", withscores: false, want: nil, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.preset { err := presetValue(server, context.Background(), tt.key, tt.presetValue) if err != nil { t.Error(err) return } } got, err := server.ZRANK(tt.key, tt.member, tt.withscores) if (err != nil) != tt.wantErr { t.Errorf("ZRANK() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("ZRANK() got = %v, want %v", got, tt.want) } }) } } func TestEchoVault_ZREM(t *testing.T) { server := createEchoVault() tests := []struct { name string preset bool presetValue interface{} key string members []string want int wantErr bool }{ { // Successfully remove multiple elements from sorted set, skipping non-existent members. // Return deleted count. name: "Successfully remove multiple elements from sorted set, skipping non-existent members", preset: true, presetValue: ss.NewSortedSet([]ss.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}, }), key: "key1", members: []string{"three", "four", "five", "none", "six", "none", "seven"}, want: 5, wantErr: false, }, { name: "If key does not exist, return 0", preset: false, presetValue: nil, key: "key2", members: []string{"member"}, want: 0, wantErr: false, }, { name: "Return error key is not a sorted set", preset: true, presetValue: "Default value", key: "key3", members: []string{"member"}, want: 0, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.preset { err := presetValue(server, context.Background(), tt.key, tt.presetValue) if err != nil { t.Error(err) return } } got, err := server.ZREM(tt.key, tt.members...) if (err != nil) != tt.wantErr { t.Errorf("ZREM() error = %v, wantErr %v", err, tt.wantErr) return } if got != tt.want { t.Errorf("ZREM() got = %v, want %v", got, tt.want) } }) } } func TestEchoVault_ZREMRANGEBYSCORE(t *testing.T) { server := createEchoVault() tests := []struct { name string preset bool presetValue interface{} key string min float64 max float64 want int wantErr bool }{ { name: "Successfully remove multiple elements with scores inside the provided range", preset: true, presetValue: ss.NewSortedSet([]ss.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}, }), key: "key1", min: 3, max: 7, want: 5, wantErr: false, }, { name: "If key does not exist, return 0", preset: false, key: "key2", min: 2, max: 4, want: 0, wantErr: false, }, { name: "Return error key is not a sorted set", preset: true, presetValue: "Default value", key: "key3", min: 2, max: 4, want: 0, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.preset { err := presetValue(server, context.Background(), tt.key, tt.presetValue) if err != nil { t.Error(err) return } } got, err := server.ZREMRANGEBYSCORE(tt.key, tt.min, tt.max) if (err != nil) != tt.wantErr { t.Errorf("ZREMRANGEBYSCORE() error = %v, wantErr %v", err, tt.wantErr) return } if got != tt.want { t.Errorf("ZREMRANGEBYSCORE() got = %v, want %v", got, tt.want) } }) } } func TestEchoVault_ZSCORE(t *testing.T) { server := createEchoVault() tests := []struct { name string preset bool presetValue interface{} key string member string want interface{} wantErr bool }{ { name: "Return score from a sorted set", preset: true, presetValue: ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1.1}, {Value: "two", Score: 245}, {Value: "three", Score: 3}, {Value: "four", Score: 4.055}, {Value: "five", Score: 5}, }), key: "key1", member: "four", want: 4.055, wantErr: false, }, { name: "If key does not exist, return nil value", preset: false, presetValue: nil, key: "key2", member: "one", want: nil, wantErr: false, }, { name: "If key exists and is a sorted set, but the member does not exist, return nil", preset: true, presetValue: ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1.1}, {Value: "two", Score: 245}, {Value: "three", Score: 3}, {Value: "four", Score: 4.055}, {Value: "five", Score: 5}, }), key: "key3", member: "non-existent", want: nil, wantErr: false, }, { name: "Throw error when trying to find scores from elements that are not sorted sets", preset: true, presetValue: "Default value", key: "key4", member: "one", want: 0, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.preset { err := presetValue(server, context.Background(), tt.key, tt.presetValue) if err != nil { t.Error(err) return } } got, err := server.ZSCORE(tt.key, tt.member) if (err != nil) != tt.wantErr { t.Errorf("ZSCORE() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("ZSCORE() got = %v, want %v", got, tt.want) } }) } } func TestEchoVault_ZUNION(t *testing.T) { server := createEchoVault() tests := []struct { name string preset bool presetValues map[string]interface{} keys []string options echovault.ZUNIONOptions want map[string]float64 wantErr bool }{ { name: "Get the union between 2 sorted sets", preset: true, presetValues: map[string]interface{}{ "key1": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, {Value: "five", Score: 5}, }), "key2": ss.NewSortedSet([]ss.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}, }), }, keys: []string{"key1", "key2"}, options: echovault.ZUNIONOptions{}, want: map[string]float64{ "one": 0, "two": 0, "three": 0, "four": 0, "five": 0, "six": 0, "seven": 0, "eight": 0, }, wantErr: false, }, { // Get the union between 3 sorted sets with scores. // By default, the SUM aggregate will be used. name: "Get the union between 3 sorted sets with scores", preset: true, presetValues: map[string]interface{}{ "key3": ss.NewSortedSet([]ss.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": ss.NewSortedSet([]ss.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": ss.NewSortedSet([]ss.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}, }), }, keys: []string{"key3", "key4", "key5"}, options: echovault.ZUNIONOptions{WithScores: true}, want: map[string]float64{ "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, }, wantErr: false, }, { // Get the union between 3 sorted sets with scores. // Use MIN aggregate. name: "Get the union between 3 sorted sets with scores", preset: true, presetValues: map[string]interface{}{ "key6": ss.NewSortedSet([]ss.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": ss.NewSortedSet([]ss.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": ss.NewSortedSet([]ss.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}, }), }, keys: []string{"key6", "key7", "key8"}, options: echovault.ZUNIONOptions{WithScores: true, Aggregate: "MIN"}, want: map[string]float64{ "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, }, wantErr: false, }, { // Get the union between 3 sorted sets with scores. // Use MAX aggregate. name: "Get the union between 3 sorted sets with scores", preset: true, presetValues: map[string]interface{}{ "key9": ss.NewSortedSet([]ss.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": ss.NewSortedSet([]ss.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": ss.NewSortedSet([]ss.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}, }), }, keys: []string{"key9", "key10", "key11"}, options: echovault.ZUNIONOptions{WithScores: true, Aggregate: "MAX"}, want: map[string]float64{ "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, }, wantErr: false, }, { // Get the union between 3 sorted sets with scores. // Use SUM aggregate with weights modifier. name: "Get the union between 3 sorted sets with scores", preset: true, presetValues: map[string]interface{}{ "key12": ss.NewSortedSet([]ss.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": ss.NewSortedSet([]ss.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": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1000}, {Value: "eight", Score: 800}, {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, {Value: "twelve", Score: 12}, }), }, keys: []string{"key12", "key13", "key14"}, options: echovault.ZUNIONOptions{WithScores: true, Aggregate: "SUM", Weights: []float64{1, 2, 3}}, want: map[string]float64{ "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, }, wantErr: false, }, { // Get the union between 3 sorted sets with scores. // Use MAX aggregate with added weights. name: "Get the union between 3 sorted sets with scores", preset: true, presetValues: map[string]interface{}{ "key15": ss.NewSortedSet([]ss.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": ss.NewSortedSet([]ss.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": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1000}, {Value: "eight", Score: 800}, {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, {Value: "twelve", Score: 12}, }), }, keys: []string{"key15", "key16", "key17"}, options: echovault.ZUNIONOptions{WithScores: true, Aggregate: "MAX", Weights: []float64{1, 2, 3}}, want: map[string]float64{ "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, }, wantErr: false, }, { // Get the union between 3 sorted sets with scores. // Use MIN aggregate with added weights. name: "Get the union between 3 sorted sets with scores", preset: true, presetValues: map[string]interface{}{ "key18": ss.NewSortedSet([]ss.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": ss.NewSortedSet([]ss.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": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1000}, {Value: "eight", Score: 800}, {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, {Value: "twelve", Score: 12}, }), }, keys: []string{"key18", "key19", "key20"}, options: echovault.ZUNIONOptions{WithScores: true, Aggregate: "MIN", Weights: []float64{1, 2, 3}}, want: map[string]float64{ "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, }, wantErr: false, }, { name: "Throw an error if there are more weights than keys", preset: true, presetValues: map[string]interface{}{ "key21": ss.NewSortedSet([]ss.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": ss.NewSortedSet([]ss.MemberParam{{Value: "one", Score: 1}}), }, keys: []string{"key21", "key22"}, options: echovault.ZUNIONOptions{Weights: []float64{1, 2, 3}}, want: nil, wantErr: true, }, { name: "Throw an error if there are fewer weights than keys", preset: true, presetValues: map[string]interface{}{ "key23": ss.NewSortedSet([]ss.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": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, }), "key25": ss.NewSortedSet([]ss.MemberParam{{Value: "one", Score: 1}}), }, keys: []string{"key23", "key24", "key25"}, options: echovault.ZUNIONOptions{Weights: []float64{5, 4}}, want: nil, wantErr: true, }, { name: "Throw an error if there are no keys provided", preset: true, presetValues: map[string]interface{}{ "key26": ss.NewSortedSet([]ss.MemberParam{{Value: "one", Score: 1}}), "key27": ss.NewSortedSet([]ss.MemberParam{{Value: "one", Score: 1}}), "key28": ss.NewSortedSet([]ss.MemberParam{{Value: "one", Score: 1}}), }, keys: []string{}, options: echovault.ZUNIONOptions{Weights: []float64{5, 4}}, want: nil, wantErr: true, }, { name: "Throw an error if any of the provided keys are not sorted sets", preset: true, presetValues: map[string]interface{}{ "key29": ss.NewSortedSet([]ss.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": ss.NewSortedSet([]ss.MemberParam{{Value: "one", Score: 1}}), }, keys: []string{"key29", "key30", "key31"}, options: echovault.ZUNIONOptions{}, want: nil, wantErr: true, }, { name: "If any of the keys does not exist, skip it", preset: true, presetValues: map[string]interface{}{ "key32": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, {Value: "eleven", Score: 11}, }), "key33": ss.NewSortedSet([]ss.MemberParam{ {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, {Value: "twelve", Score: 12}, }), }, keys: []string{"non-existent", "key32", "key33"}, options: echovault.ZUNIONOptions{}, want: map[string]float64{ "one": 0, "two": 0, "thirty-six": 0, "twelve": 0, "eleven": 0, "seven": 0, "eight": 0, "nine": 0, "ten": 0, }, wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.preset { for k, v := range tt.presetValues { err := presetValue(server, context.Background(), k, v) if err != nil { t.Error(err) return } } } got, err := server.ZUNION(tt.keys, tt.options) if (err != nil) != tt.wantErr { t.Errorf("ZUNION() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("ZUNION() got = %v, want %v", got, tt.want) } }) } } func TestEchoVault_ZUNIONSTORE(t *testing.T) { server := createEchoVault() tests := []struct { name string preset bool presetValues map[string]interface{} destination string keys []string options echovault.ZUNIONSTOREOptions want int wantErr bool }{ { name: "Get the union between 2 sorted sets", preset: true, presetValues: map[string]interface{}{ "key1": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "three", Score: 3}, {Value: "four", Score: 4}, {Value: "five", Score: 5}, }), "key2": ss.NewSortedSet([]ss.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", keys: []string{"key1", "key2"}, options: echovault.ZUNIONSTOREOptions{}, want: 8, wantErr: false, }, { // Get the union between 3 sorted sets with scores. // By default, the SUM aggregate will be used. name: "Get the union between 3 sorted sets with scores", preset: true, presetValues: map[string]interface{}{ "key3": ss.NewSortedSet([]ss.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": ss.NewSortedSet([]ss.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": ss.NewSortedSet([]ss.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", keys: []string{"key3", "key4", "key5"}, options: echovault.ZUNIONSTOREOptions{WithScores: true}, want: 13, wantErr: false, }, { // Get the union between 3 sorted sets with scores. // Use MIN aggregate. name: "Get the union between 3 sorted sets with scores", preset: true, presetValues: map[string]interface{}{ "key6": ss.NewSortedSet([]ss.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": ss.NewSortedSet([]ss.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": ss.NewSortedSet([]ss.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", keys: []string{"key6", "key7", "key8"}, options: echovault.ZUNIONSTOREOptions{WithScores: true, Aggregate: "MIN"}, want: 13, wantErr: false, }, { // Get the union between 3 sorted sets with scores. // Use MAX aggregate. name: "Get the union between 3 sorted sets with scores", preset: true, presetValues: map[string]interface{}{ "key9": ss.NewSortedSet([]ss.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": ss.NewSortedSet([]ss.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": ss.NewSortedSet([]ss.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", keys: []string{"key9", "key10", "key11"}, options: echovault.ZUNIONSTOREOptions{WithScores: true, Aggregate: "MAX"}, want: 13, wantErr: false, }, { // Get the union between 3 sorted sets with scores. // Use SUM aggregate with weights modifier. name: "Get the union between 3 sorted sets with scores", preset: true, presetValues: map[string]interface{}{ "key12": ss.NewSortedSet([]ss.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": ss.NewSortedSet([]ss.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": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1000}, {Value: "eight", Score: 800}, {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, {Value: "twelve", Score: 12}, }), }, destination: "destination5", keys: []string{"key12", "key13", "key14"}, options: echovault.ZUNIONSTOREOptions{WithScores: true, Aggregate: "SUM", Weights: []float64{1, 2, 3}}, want: 13, wantErr: false, }, { // Get the union between 3 sorted sets with scores. // Use MAX aggregate with added weights. name: "Get the union between 3 sorted sets with scores", preset: true, presetValues: map[string]interface{}{ "key15": ss.NewSortedSet([]ss.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": ss.NewSortedSet([]ss.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": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1000}, {Value: "eight", Score: 800}, {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, {Value: "twelve", Score: 12}, }), }, destination: "destination6", keys: []string{"key15", "key16", "key17"}, options: echovault.ZUNIONSTOREOptions{WithScores: true, Aggregate: "MAX", Weights: []float64{1, 2, 3}}, want: 13, wantErr: false, }, { // Get the union between 3 sorted sets with scores. // Use MIN aggregate with added weights. name: "Get the union between 3 sorted sets with scores", preset: true, presetValues: map[string]interface{}{ "key18": ss.NewSortedSet([]ss.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": ss.NewSortedSet([]ss.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": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1000}, {Value: "eight", Score: 800}, {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, {Value: "twelve", Score: 12}, }), }, destination: "destination7", keys: []string{"destination7", "key18", "key19", "key20"}, options: echovault.ZUNIONSTOREOptions{WithScores: true, Aggregate: "MIN", Weights: []float64{1, 2, 3}}, want: 13, wantErr: false, }, { name: "Throw an error if there are more weights than keys", preset: true, presetValues: map[string]interface{}{ "key21": ss.NewSortedSet([]ss.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": ss.NewSortedSet([]ss.MemberParam{{Value: "one", Score: 1}}), }, destination: "destination8", keys: []string{"key21", "key22"}, options: echovault.ZUNIONSTOREOptions{Weights: []float64{1, 2, 3}}, want: 0, wantErr: true, }, { name: "Throw an error if there are fewer weights than keys", preset: true, presetValues: map[string]interface{}{ "key23": ss.NewSortedSet([]ss.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": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, }), "key25": ss.NewSortedSet([]ss.MemberParam{{Value: "one", Score: 1}}), }, destination: "destination9", keys: []string{"key23", "key24", "key25"}, options: echovault.ZUNIONSTOREOptions{Weights: []float64{5, 4}}, want: 0, wantErr: true, }, { name: "Throw an error if any of the provided keys are not sorted sets", preset: true, presetValues: map[string]interface{}{ "key29": ss.NewSortedSet([]ss.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": ss.NewSortedSet([]ss.MemberParam{{Value: "one", Score: 1}}), }, destination: "destination11", keys: []string{"key29", "key30", "key31"}, options: echovault.ZUNIONSTOREOptions{}, want: 0, wantErr: true, }, { name: "If any of the keys does not exist, skip it", preset: true, presetValues: map[string]interface{}{ "key32": ss.NewSortedSet([]ss.MemberParam{ {Value: "one", Score: 1}, {Value: "two", Score: 2}, {Value: "thirty-six", Score: 36}, {Value: "twelve", Score: 12}, {Value: "eleven", Score: 11}, }), "key33": ss.NewSortedSet([]ss.MemberParam{ {Value: "seven", Score: 7}, {Value: "eight", Score: 8}, {Value: "nine", Score: 9}, {Value: "ten", Score: 10}, {Value: "twelve", Score: 12}, }), }, destination: "destination12", keys: []string{"non-existent", "key32", "key33"}, want: 9, wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.preset { for k, v := range tt.presetValues { err := presetValue(server, context.Background(), k, v) if err != nil { t.Error(err) return } } } got, err := server.ZUNIONSTORE(tt.destination, tt.keys, tt.options) if (err != nil) != tt.wantErr { t.Errorf("ZUNIONSTORE() error = %v, wantErr %v", err, tt.wantErr) return } if got != tt.want { t.Errorf("ZUNIONSTORE() got = %v, want %v", got, tt.want) } }) } }