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  }