flamingo.me/flamingo-commerce/v3@v3.11.0/checkout/infrastructure/contextstore/redis_test.go (about) 1 package contextstore_test 2 3 import ( 4 "bytes" 5 "context" 6 "encoding/gob" 7 "fmt" 8 "os/exec" 9 "testing" 10 11 "flamingo.me/flamingo/v3/framework/flamingo" 12 "github.com/go-test/deep" 13 "github.com/gomodule/redigo/redis" 14 "github.com/stretchr/testify/assert" 15 "github.com/stretchr/testify/require" 16 "github.com/stvp/tempredis" 17 "github.com/testcontainers/testcontainers-go" 18 "github.com/testcontainers/testcontainers-go/wait" 19 20 "flamingo.me/flamingo-commerce/v3/checkout/domain/placeorder/process" 21 "flamingo.me/flamingo-commerce/v3/checkout/infrastructure/contextstore" 22 ) 23 24 const ( 25 existingKey = "existing" 26 wrongDataKey = "wrong data" 27 ) 28 29 var ( 30 testContext = process.Context{UUID: "test"} 31 emptyContext = process.Context{} 32 ) 33 34 func getRedisStore(network, address string) *contextstore.Redis { 35 return new(contextstore.Redis).Inject( 36 new(flamingo.NullLogger), 37 &struct { 38 MaxIdle int `inject:"config:commerce.checkout.placeorder.contextstore.redis.maxIdle"` 39 IdleTimeoutMilliseconds int `inject:"config:commerce.checkout.placeorder.contextstore.redis.idleTimeoutMilliseconds"` 40 Network string `inject:"config:commerce.checkout.placeorder.contextstore.redis.network"` 41 Address string `inject:"config:commerce.checkout.placeorder.contextstore.redis.address"` 42 Database int `inject:"config:commerce.checkout.placeorder.contextstore.redis.database"` 43 TTL string `inject:"config:commerce.checkout.placeorder.contextstore.redis.ttl"` 44 }{MaxIdle: 3, IdleTimeoutMilliseconds: 240000, Network: network, Address: address, Database: 0, TTL: "2h"}) 45 } 46 47 func prepareData(t *testing.T, conn redis.Conn) { 48 buffer := new(bytes.Buffer) 49 require.NoError(t, gob.NewEncoder(buffer).Encode(testContext)) 50 _, err := conn.Do("SET", existingKey, buffer) 51 require.NoError(t, err) 52 _, err = conn.Do("SET", wrongDataKey, "wrong data") 53 require.NoError(t, err) 54 } 55 56 func startUpLocalRedis(t *testing.T) (*tempredis.Server, redis.Conn) { 57 t.Helper() 58 server, err := tempredis.Start(tempredis.Config{}) 59 if err != nil { 60 t.Fatal(err) 61 } 62 conn, err := redis.Dial("unix", server.Socket()) 63 if err != nil { 64 t.Fatal(err) 65 } 66 prepareData(t, conn) 67 68 return server, conn 69 } 70 71 func startUpDockerRedis(t *testing.T) (func(), string, redis.Conn) { 72 ctx := context.Background() 73 req := testcontainers.ContainerRequest{ 74 Image: "redis:latest", 75 ExposedPorts: []string{"6379/tcp"}, 76 WaitingFor: wait.ForLog("Ready to accept connections"), 77 } 78 redisC, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ 79 ContainerRequest: req, 80 Started: true, 81 }) 82 require.NoError(t, err) 83 84 port, err := redisC.MappedPort(ctx, "6379") 85 require.NoError(t, err) 86 87 host, err := redisC.Host(ctx) 88 require.NoError(t, err) 89 address := fmt.Sprintf("%s:%s", host, port.Port()) 90 91 conn, err := redis.Dial("tcp", address) 92 require.NoError(t, err) 93 94 _, err = conn.Do("PING") 95 require.NoError(t, err) 96 97 prepareData(t, conn) 98 99 return func() { _ = redisC.Terminate(ctx) }, address, conn 100 } 101 102 func TestRedis_Get(t *testing.T) { 103 runTestCases := func(t *testing.T, store *contextstore.Redis) { 104 tests := []struct { 105 name string 106 key string 107 expectedFound bool 108 expected process.Context 109 }{ 110 { 111 name: "load existing", 112 key: existingKey, 113 expectedFound: true, 114 expected: testContext, 115 }, 116 { 117 name: "load existing with wrong data", 118 key: wrongDataKey, 119 expectedFound: false, 120 expected: emptyContext, 121 }, 122 { 123 name: "load non existing", 124 key: "non", 125 expectedFound: false, 126 expected: emptyContext, 127 }, 128 } 129 for _, tt := range tests { 130 t.Run(tt.name, func(t *testing.T) { 131 got, ok := store.Get(context.Background(), tt.key) 132 assert.Equal(t, tt.expectedFound, ok) 133 if diff := deep.Equal(got, tt.expected); diff != nil { 134 t.Error("expected response is wrong: ", diff) 135 } 136 }) 137 } 138 } 139 140 t.Run("local-redis", func(t *testing.T) { 141 if _, err := exec.LookPath("redis-server"); err != nil { 142 t.Skip("redis-server not installed") 143 } 144 server, _ := startUpLocalRedis(t) 145 store := getRedisStore("unix", server.Socket()) 146 runTestCases(t, store) 147 }) 148 t.Run("docker-redis", func(t *testing.T) { 149 if _, err := exec.LookPath("docker"); err != nil { 150 t.Skip("docker not installed") 151 } 152 shutdown, address, _ := startUpDockerRedis(t) 153 defer shutdown() 154 store := getRedisStore("tcp", address) 155 runTestCases(t, store) 156 }) 157 158 } 159 160 func TestRedis_Store(t *testing.T) { 161 runTestCases := func(t *testing.T, store *contextstore.Redis, conn redis.Conn) { 162 tests := []struct { 163 name string 164 key string 165 value process.Context 166 }{ 167 { 168 name: "store new value", 169 key: "test_key", 170 value: process.Context{UUID: "test-uuid"}, 171 }, 172 { 173 name: "overwrite existing", 174 key: existingKey, 175 value: process.Context{UUID: "test-uuid"}, 176 }, 177 } 178 179 for _, tt := range tests { 180 t.Run(tt.name, func(t *testing.T) { 181 require.NoError(t, store.Store(context.Background(), tt.key, tt.value)) 182 183 result, err := redis.Bytes(conn.Do("GET", tt.key)) 184 require.NoError(t, err) 185 186 buffer := new(bytes.Buffer) 187 require.NoError(t, gob.NewEncoder(buffer).Encode(tt.value)) 188 189 assert.Equal(t, buffer.Bytes(), result) 190 }) 191 } 192 } 193 194 t.Run("local-redis", func(t *testing.T) { 195 if _, err := exec.LookPath("redis-server"); err != nil { 196 t.Skip("redis-server not installed") 197 } 198 server, conn := startUpLocalRedis(t) 199 store := getRedisStore("unix", server.Socket()) 200 runTestCases(t, store, conn) 201 }) 202 t.Run("docker-redis", func(t *testing.T) { 203 if _, err := exec.LookPath("docker"); err != nil { 204 t.Skip("docker not installed") 205 } 206 shutdown, address, conn := startUpDockerRedis(t) 207 defer shutdown() 208 store := getRedisStore("tcp", address) 209 runTestCases(t, store, conn) 210 }) 211 } 212 213 func TestRedis_Delete(t *testing.T) { 214 runTestCases := func(t *testing.T, store *contextstore.Redis, conn redis.Conn) { 215 tests := []struct { 216 name string 217 key string 218 }{ 219 { 220 name: "delete existing", 221 key: existingKey, 222 }, 223 { 224 name: "delete non existing", 225 key: "test_key", 226 }, 227 } 228 229 for _, tt := range tests { 230 t.Run(tt.name, func(t *testing.T) { 231 require.NoError(t, store.Delete(context.Background(), tt.key)) 232 233 _, err := redis.Bytes(conn.Do("GET", tt.key)) 234 require.Error(t, err, "entry not deleted") 235 }) 236 } 237 } 238 239 t.Run("local-redis", func(t *testing.T) { 240 if _, err := exec.LookPath("redis-server"); err != nil { 241 t.Skip("redis-server not installed") 242 } 243 server, conn := startUpLocalRedis(t) 244 store := getRedisStore("unix", server.Socket()) 245 runTestCases(t, store, conn) 246 }) 247 t.Run("docker-redis", func(t *testing.T) { 248 if _, err := exec.LookPath("docker"); err != nil { 249 t.Skip("docker not installed") 250 } 251 shutdown, address, conn := startUpDockerRedis(t) 252 defer shutdown() 253 store := getRedisStore("tcp", address) 254 runTestCases(t, store, conn) 255 }) 256 }