...

Source file src/github.com/launchdarkly/go-server-sdk-redis-redigo/v2/redis_test.go

Documentation: github.com/launchdarkly/go-server-sdk-redis-redigo/v2

     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  	// Here we ensure that all Redis operations will fail by using an invalid hostname.
    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 { // SCAN returns 0 when the current result subset is the last one
    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