...
1 package ldredis
2
3 import (
4 "fmt"
5 "strconv"
6 "strings"
7 "testing"
8
9 r "github.com/gomodule/redigo/redis"
10 "github.com/stretchr/testify/assert"
11
12 "github.com/launchdarkly/go-sdk-common/v3/ldvalue"
13 "github.com/launchdarkly/go-server-sdk/v6/subsystems"
14 "github.com/launchdarkly/go-server-sdk/v6/testhelpers/storetest"
15 )
16
17 const redisURL = "redis://localhost:6379"
18
19 func TestRedisDataStore(t *testing.T) {
20 storetest.NewPersistentDataStoreTestSuite(makeTestStore, clearTestData).
21 ErrorStoreFactory(makeFailedStore(), verifyFailedStoreError).
22 ConcurrentModificationHook(setConcurrentModificationHook).
23 Run(t)
24 }
25
26 func makeTestStore(prefix string) subsystems.ComponentConfigurer[subsystems.PersistentDataStore] {
27 return DataStore().Prefix(prefix)
28 }
29
30 func makeFailedStore() subsystems.ComponentConfigurer[subsystems.PersistentDataStore] {
31
32 return DataStore().URL("redis://not-a-real-host")
33 }
34
35 func verifyFailedStoreError(t assert.TestingT, err error) {
36 assert.Contains(t, err.Error(), "no such host")
37 }
38
39 func clearTestData(prefix string) error {
40 if prefix == "" {
41 prefix = DefaultPrefix
42 }
43
44 client, err := r.DialURL(redisURL)
45 if err != nil {
46 return err
47 }
48 defer client.Close()
49
50 cursor := 0
51 for {
52 resp, err := client.Do("SCAN", fmt.Sprintf("%d", cursor), "MATCH", prefix+":*")
53 if err != nil {
54 return err
55 }
56 respValue, err := parseRedisResponseAsValue(resp)
57 badResponse := func() error {
58 return fmt.Errorf("unexpected format of Redis response: %s", respValue)
59 }
60 if err != nil {
61 return err
62 }
63 if respValue.Count() != 2 {
64 return badResponse()
65 }
66 cursor, err = strconv.Atoi(respValue.GetByIndex(0).StringValue())
67 if err != nil {
68 return badResponse()
69 }
70 respLines := respValue.GetByIndex(1)
71 if respLines.Type() != ldvalue.ArrayType {
72 return badResponse()
73 }
74 var failure error
75 for i := 0; i < respLines.Count(); i++ {
76 value := respLines.GetByIndex(i)
77 redisKey := strings.TrimPrefix(strings.TrimSuffix(value.String(), `"`), `"`)
78 failure = client.Send("DEL", redisKey)
79 if failure != nil {
80 break
81 }
82 }
83 if failure != nil {
84 return failure
85 }
86 if cursor == 0 {
87 break
88 }
89 }
90 return client.Flush()
91 }
92
93 func setConcurrentModificationHook(store subsystems.PersistentDataStore, hook func()) {
94 store.(*redisDataStoreImpl).testTxHook = hook
95 }
96
97 func parseRedisResponseAsValue(resp interface{}) (ldvalue.Value, error) {
98 switch t := resp.(type) {
99 case []interface{}:
100 a := ldvalue.ArrayBuild()
101 for _, item := range t {
102 v, err := parseRedisResponseAsValue(item)
103 if err != nil {
104 return ldvalue.Null(), err
105 }
106 a.Add(v)
107 }
108 return a.Build(), nil
109 case []byte:
110 return ldvalue.String(string(t)), nil
111 default:
112 return ldvalue.Null(), fmt.Errorf("unexpected data type in response: %T", resp)
113 }
114 }
115
View as plain text