Merge pull request #471 from beckn/feature/redis-plugin
Initial commit for Cache Plugin
This commit is contained in:
5
go.mod
5
go.mod
@@ -2,6 +2,8 @@ module github.com/beckn/beckn-onix
|
|||||||
|
|
||||||
go 1.24
|
go 1.24
|
||||||
|
|
||||||
|
toolchain go1.23.4
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/kr/pretty v0.3.1 // indirect
|
github.com/kr/pretty v0.3.1 // indirect
|
||||||
github.com/rogpeppe/go-internal v1.13.1 // indirect
|
github.com/rogpeppe/go-internal v1.13.1 // indirect
|
||||||
@@ -25,6 +27,8 @@ require github.com/zenazn/pkcs7pad v0.0.0-20170308005700-253a5b1f0e03
|
|||||||
require golang.org/x/text v0.23.0 // indirect
|
require golang.org/x/text v0.23.0 // indirect
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||||
github.com/go-jose/go-jose/v4 v4.0.1 // indirect
|
github.com/go-jose/go-jose/v4 v4.0.1 // indirect
|
||||||
github.com/google/go-cmp v0.6.0 // indirect
|
github.com/google/go-cmp v0.6.0 // indirect
|
||||||
@@ -48,6 +52,7 @@ require (
|
|||||||
require (
|
require (
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/hashicorp/go-retryablehttp v0.7.7
|
github.com/hashicorp/go-retryablehttp v0.7.7
|
||||||
|
github.com/redis/go-redis/v9 v9.8.0
|
||||||
github.com/hashicorp/vault/api v1.16.0
|
github.com/hashicorp/vault/api v1.16.0
|
||||||
github.com/rabbitmq/amqp091-go v1.10.0
|
github.com/rabbitmq/amqp091-go v1.10.0
|
||||||
github.com/rs/zerolog v1.34.0
|
github.com/rs/zerolog v1.34.0
|
||||||
|
|||||||
10
go.sum
10
go.sum
@@ -1,3 +1,9 @@
|
|||||||
|
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||||
|
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
||||||
|
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||||
|
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
|
||||||
|
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||||
|
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||||
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
|
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
|
||||||
@@ -6,6 +12,8 @@ github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV
|
|||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||||
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
|
github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
|
||||||
@@ -72,6 +80,8 @@ github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR
|
|||||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/redis/go-redis/v9 v9.8.0 h1:q3nRvjrlge/6UD7eTu/DSg2uYiU2mCL0G/uzBWqhicI=
|
||||||
|
github.com/redis/go-redis/v9 v9.8.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
||||||
|
|||||||
102
pkg/plugin/implementation/cache/cache.go
vendored
Normal file
102
pkg/plugin/implementation/cache/cache.go
vendored
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
package cache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/beckn/beckn-onix/pkg/log"
|
||||||
|
"github.com/redis/go-redis/v9"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RedisCl global variable for the Redis client, can be overridden in tests
|
||||||
|
var RedisCl *redis.Client
|
||||||
|
|
||||||
|
// RedisClient is an interface for Redis operations that allows mocking
|
||||||
|
type RedisClient interface {
|
||||||
|
Get(ctx context.Context, key string) *redis.StringCmd
|
||||||
|
Set(ctx context.Context, key string, value interface{}, ttl time.Duration) *redis.StatusCmd
|
||||||
|
Del(ctx context.Context, keys ...string) *redis.IntCmd
|
||||||
|
FlushDB(ctx context.Context) *redis.StatusCmd
|
||||||
|
Ping(ctx context.Context) *redis.StatusCmd
|
||||||
|
Close() error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Config holds the configuration required to connect to Redis.
|
||||||
|
type Config struct {
|
||||||
|
Addr string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache wraps a Redis client to provide basic caching operations.
|
||||||
|
type Cache struct {
|
||||||
|
Client RedisClient
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error variables to describe common failure modes.
|
||||||
|
var (
|
||||||
|
ErrEmptyConfig = errors.New("empty config")
|
||||||
|
ErrAddrMissing = errors.New("missing required field 'Addr'")
|
||||||
|
ErrCredentialMissing = errors.New("missing Redis credentials in environment")
|
||||||
|
ErrConnectionFail = errors.New("failed to connect to Redis")
|
||||||
|
)
|
||||||
|
|
||||||
|
// validate checks if the provided Redis configuration is valid.
|
||||||
|
func validate(cfg *Config) error {
|
||||||
|
if cfg == nil {
|
||||||
|
return ErrEmptyConfig
|
||||||
|
}
|
||||||
|
if cfg.Addr == "" {
|
||||||
|
return ErrAddrMissing
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RedisClientFunc is a function variable that creates a Redis client based on the provided configuration.
|
||||||
|
// It can be overridden for testing purposes.
|
||||||
|
var RedisClientFunc = func(cfg *Config) RedisClient {
|
||||||
|
return redis.NewClient(&redis.Options{
|
||||||
|
Addr: cfg.Addr,
|
||||||
|
Password: os.Getenv("REDIS_PASSWORD"),
|
||||||
|
DB: 0,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// New initializes and returns a Cache instance along with a close function to release resources.
|
||||||
|
func New(ctx context.Context, cfg *Config) (*Cache, func() error, error) {
|
||||||
|
log.Debugf(ctx, "Initializing Cache with config: %+v", cfg)
|
||||||
|
if err := validate(cfg); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
client := RedisClientFunc(cfg)
|
||||||
|
|
||||||
|
if _, err := client.Ping(ctx).Result(); err != nil {
|
||||||
|
log.Errorf(ctx, err, "Failed to ping Redis server")
|
||||||
|
return nil, nil, fmt.Errorf("%w: %v", ErrConnectionFail, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof(ctx, "Cache connection to Redis established successfully")
|
||||||
|
return &Cache{Client: client}, client.Close, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get retrieves the value for the specified key from Redis.
|
||||||
|
func (c *Cache) Get(ctx context.Context, key string) (string, error) {
|
||||||
|
return c.Client.Get(ctx, key).Result()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set stores the given key-value pair in Redis with the specified TTL (time to live).
|
||||||
|
func (c *Cache) Set(ctx context.Context, key, value string, ttl time.Duration) error {
|
||||||
|
return c.Client.Set(ctx, key, value, ttl).Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete removes the specified key from Redis.
|
||||||
|
func (c *Cache) Delete(ctx context.Context, key string) error {
|
||||||
|
return c.Client.Del(ctx, key).Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear removes all keys in the currently selected Redis database.
|
||||||
|
func (c *Cache) Clear(ctx context.Context) error {
|
||||||
|
return c.Client.FlushDB(ctx).Err()
|
||||||
|
}
|
||||||
200
pkg/plugin/implementation/cache/cache_test.go
vendored
Normal file
200
pkg/plugin/implementation/cache/cache_test.go
vendored
Normal file
@@ -0,0 +1,200 @@
|
|||||||
|
package cache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/redis/go-redis/v9"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/mock"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MockRedisClient is a mock implementation of the RedisClient interface
|
||||||
|
type MockRedisClient struct {
|
||||||
|
mock.Mock
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockRedisClient) Get(ctx context.Context, key string) *redis.StringCmd {
|
||||||
|
args := m.Called(ctx, key)
|
||||||
|
return redis.NewStringResult(args.String(0), args.Error(1))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockRedisClient) Set(ctx context.Context, key string, value interface{}, ttl time.Duration) *redis.StatusCmd {
|
||||||
|
args := m.Called(ctx, key, value, ttl)
|
||||||
|
return redis.NewStatusResult(args.String(0), args.Error(1))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockRedisClient) Del(ctx context.Context, keys ...string) *redis.IntCmd {
|
||||||
|
args := m.Called(ctx, keys)
|
||||||
|
return redis.NewIntCmd(ctx, args.Int(0), args.Error(1))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockRedisClient) FlushDB(ctx context.Context) *redis.StatusCmd {
|
||||||
|
args := m.Called(ctx)
|
||||||
|
return redis.NewStatusResult(args.String(0), args.Error(1))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockRedisClient) Ping(ctx context.Context) *redis.StatusCmd {
|
||||||
|
args := m.Called(ctx)
|
||||||
|
return args.Get(0).(*redis.StatusCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockRedisClient) Close() error {
|
||||||
|
args := m.Called()
|
||||||
|
return args.Error(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestCache_Get tests the Get method of the Cache type
|
||||||
|
func TestCache_Get(t *testing.T) {
|
||||||
|
mockClient := new(MockRedisClient)
|
||||||
|
ctx := context.Background()
|
||||||
|
cache := &Cache{Client: mockClient}
|
||||||
|
|
||||||
|
mockClient.On("Get", ctx, "my-key").Return("my-value", nil)
|
||||||
|
|
||||||
|
value, err := cache.Get(ctx, "my-key")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "my-value", value)
|
||||||
|
mockClient.AssertExpectations(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestCache_Set tests the Set method of the Cache type
|
||||||
|
func TestCache_Set(t *testing.T) {
|
||||||
|
mockClient := new(MockRedisClient)
|
||||||
|
ctx := context.Background()
|
||||||
|
cache := &Cache{Client: mockClient}
|
||||||
|
|
||||||
|
mockClient.On("Set", ctx, "my-key", "my-value", time.Minute).Return("OK", nil)
|
||||||
|
|
||||||
|
err := cache.Set(ctx, "my-key", "my-value", time.Minute)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
mockClient.AssertExpectations(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestCache_Delete tests the Delete method of the Cache type
|
||||||
|
func TestCache_Delete(t *testing.T) {
|
||||||
|
mockClient := new(MockRedisClient)
|
||||||
|
ctx := context.Background()
|
||||||
|
cache := &Cache{Client: mockClient}
|
||||||
|
|
||||||
|
mockClient.On("Del", ctx, []string{"my-key"}).Return(1, nil)
|
||||||
|
|
||||||
|
err := cache.Delete(ctx, "my-key")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
mockClient.AssertExpectations(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestCache_Clear tests the Clear method of the Cache type
|
||||||
|
func TestCache_Clear(t *testing.T) {
|
||||||
|
mockClient := new(MockRedisClient)
|
||||||
|
ctx := context.Background()
|
||||||
|
cache := &Cache{Client: mockClient}
|
||||||
|
|
||||||
|
mockClient.On("FlushDB", ctx).Return("OK", nil)
|
||||||
|
|
||||||
|
err := cache.Clear(ctx)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
mockClient.AssertExpectations(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestValidate tests the validate function
|
||||||
|
func TestValidate(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
cfg *Config
|
||||||
|
wantErr error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "nil config",
|
||||||
|
cfg: nil,
|
||||||
|
wantErr: ErrEmptyConfig,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty addr",
|
||||||
|
cfg: &Config{Addr: ""},
|
||||||
|
wantErr: ErrAddrMissing,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid config",
|
||||||
|
cfg: &Config{Addr: "localhost:6379"},
|
||||||
|
wantErr: nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
err := validate(tt.cfg)
|
||||||
|
if tt.wantErr != nil {
|
||||||
|
assert.Equal(t, tt.wantErr, err)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestNew_Validation tests the validation parts of the New function
|
||||||
|
func TestNew_Validation(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
cfg *Config
|
||||||
|
wantErr bool
|
||||||
|
errType error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "nil config",
|
||||||
|
cfg: nil,
|
||||||
|
wantErr: true,
|
||||||
|
errType: ErrEmptyConfig,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty addr",
|
||||||
|
cfg: &Config{Addr: ""},
|
||||||
|
wantErr: true,
|
||||||
|
errType: ErrAddrMissing,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
_, _, err := New(context.Background(), tc.cfg)
|
||||||
|
|
||||||
|
if tc.wantErr {
|
||||||
|
assert.Error(t, err)
|
||||||
|
if tc.errType != nil {
|
||||||
|
assert.ErrorIs(t, err, tc.errType)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestNew_ConnectionFailure tests the connection failure in New function
|
||||||
|
func TestNew_ConnectionFailure(t *testing.T) {
|
||||||
|
// Set test env var
|
||||||
|
err := os.Setenv("REDIS_PASSWORD", "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to set REDIS_PASSWORD environment variable: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
err := os.Unsetenv("REDIS_PASSWORD")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to unset REDIS_PASSWORD environment variable: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Use an invalid connection address to force a connection failure
|
||||||
|
cfg := &Config{Addr: "invalid:1234"}
|
||||||
|
|
||||||
|
// Call New which should fail with a connection error
|
||||||
|
_, _, err = New(context.Background(), cfg)
|
||||||
|
|
||||||
|
// Verify error type is connection failure
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.ErrorIs(t, err, ErrConnectionFail)
|
||||||
|
}
|
||||||
36
pkg/plugin/implementation/cache/cmd/plugin.go
vendored
Normal file
36
pkg/plugin/implementation/cache/cmd/plugin.go
vendored
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/beckn/beckn-onix/pkg/log"
|
||||||
|
"github.com/beckn/beckn-onix/pkg/plugin/definition"
|
||||||
|
"github.com/beckn/beckn-onix/pkg/plugin/implementation/cache"
|
||||||
|
)
|
||||||
|
|
||||||
|
// cacheProvider implements the CacheProvider interface for the cache plugin.
|
||||||
|
type cacheProvider struct{}
|
||||||
|
|
||||||
|
// New creates a new cache plugin instance.
|
||||||
|
func (c cacheProvider) New(ctx context.Context, config map[string]string) (definition.Cache, func() error, error) {
|
||||||
|
if ctx == nil {
|
||||||
|
return nil, nil, errors.New("context cannot be nil")
|
||||||
|
}
|
||||||
|
// Create cache.Config directly from map - validation is handled by cache.New
|
||||||
|
cacheConfig := &cache.Config{
|
||||||
|
Addr: config["addr"],
|
||||||
|
}
|
||||||
|
log.Debugf(ctx, "Cache config mapped: %+v", cacheConfig)
|
||||||
|
cache, closer, err := cache.New(ctx, cacheConfig)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf(ctx, err, "Failed to create cache instance")
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof(ctx, "Cache instance created successfully")
|
||||||
|
return cache, closer, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Provider is the exported plugin instance
|
||||||
|
var Provider = cacheProvider{}
|
||||||
159
pkg/plugin/implementation/cache/cmd/plugin_test.go
vendored
Normal file
159
pkg/plugin/implementation/cache/cmd/plugin_test.go
vendored
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/beckn/beckn-onix/pkg/plugin/implementation/cache"
|
||||||
|
"github.com/redis/go-redis/v9"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/mock"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestProviderNew tests the New method of the cacheProvider
|
||||||
|
func TestProviderNew(t *testing.T) {
|
||||||
|
provider := cacheProvider{}
|
||||||
|
|
||||||
|
// Save original environment variable and restore it after test
|
||||||
|
origPassword := os.Getenv("REDIS_PASSWORD")
|
||||||
|
defer func() {
|
||||||
|
if err := os.Setenv("REDIS_PASSWORD", origPassword); err != nil {
|
||||||
|
t.Fatalf("Failed to restore REDIS_PASSWORD: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
// Set an empty password for testing
|
||||||
|
if err := os.Setenv("REDIS_PASSWORD", ""); err != nil {
|
||||||
|
t.Fatalf("Failed to set REDIS_PASSWORD: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
ctx context.Context
|
||||||
|
config map[string]string
|
||||||
|
expectErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "nil context",
|
||||||
|
ctx: nil,
|
||||||
|
config: map[string]string{"addr": "localhost:6379"},
|
||||||
|
expectErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid config",
|
||||||
|
ctx: context.Background(),
|
||||||
|
config: map[string]string{}, // Missing addr
|
||||||
|
expectErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
cache, cleanup, err := provider.New(tt.ctx, tt.config)
|
||||||
|
|
||||||
|
if tt.expectErr {
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Nil(t, cache)
|
||||||
|
assert.Nil(t, cleanup)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, cache)
|
||||||
|
assert.NotNil(t, cleanup)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestProviderVariable tests that the Provider variable is correctly initialized
|
||||||
|
func TestProviderVariable(t *testing.T) {
|
||||||
|
assert.NotNil(t, Provider, "Provider should not be nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
// mockRedisClient mocks the RedisClient interface from the cache package
|
||||||
|
type mockRedisClient struct {
|
||||||
|
mock.Mock
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockRedisClient) Get(ctx context.Context, key string) *redis.StringCmd {
|
||||||
|
args := m.Called(ctx, key)
|
||||||
|
cmd := redis.NewStringCmd(ctx)
|
||||||
|
cmd.SetVal(args.String(0))
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockRedisClient) Set(ctx context.Context, key string, value interface{}, ttl time.Duration) *redis.StatusCmd {
|
||||||
|
args := m.Called(ctx, key, value, ttl)
|
||||||
|
cmd := redis.NewStatusCmd(ctx)
|
||||||
|
cmd.SetVal(args.String(0))
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockRedisClient) Del(ctx context.Context, keys ...string) *redis.IntCmd {
|
||||||
|
args := m.Called(ctx, keys)
|
||||||
|
cmd := redis.NewIntCmd(ctx)
|
||||||
|
cmd.SetVal(int64(args.Int(0)))
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockRedisClient) FlushDB(ctx context.Context) *redis.StatusCmd {
|
||||||
|
args := m.Called(ctx)
|
||||||
|
cmd := redis.NewStatusCmd(ctx)
|
||||||
|
cmd.SetVal(args.String(0))
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockRedisClient) Ping(ctx context.Context) *redis.StatusCmd {
|
||||||
|
args := m.Called(ctx)
|
||||||
|
cmd := redis.NewStatusCmd(ctx)
|
||||||
|
cmd.SetVal(args.String(0))
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockRedisClient) Close() error {
|
||||||
|
args := m.Called()
|
||||||
|
return args.Error(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProviderIntegration(t *testing.T) {
|
||||||
|
// Save original RedisClientFunc and restore after test
|
||||||
|
original := cache.RedisClientFunc
|
||||||
|
defer func() { cache.RedisClientFunc = original }()
|
||||||
|
|
||||||
|
// Create and assign mock
|
||||||
|
mockClient := new(mockRedisClient)
|
||||||
|
cache.RedisClientFunc = func(cfg *cache.Config) cache.RedisClient {
|
||||||
|
return mockClient
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
// Expectations for the mock
|
||||||
|
mockClient.On("Ping", ctx).Return("PONG")
|
||||||
|
mockClient.On("Close").Return(nil)
|
||||||
|
|
||||||
|
// Create the config and convert it into a map[string]string
|
||||||
|
config := &cache.Config{
|
||||||
|
Addr: "localhost:6379",
|
||||||
|
}
|
||||||
|
// Convert the *cache.Config to map[string]string
|
||||||
|
configMap := map[string]string{
|
||||||
|
"addr": config.Addr,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call the plugin provider
|
||||||
|
provider := Provider
|
||||||
|
c, cleanup, err := provider.New(ctx, configMap)
|
||||||
|
|
||||||
|
// Assertions
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, c)
|
||||||
|
assert.NotNil(t, cleanup)
|
||||||
|
|
||||||
|
// Call cleanup and assert
|
||||||
|
err = cleanup()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// Verify expectations
|
||||||
|
mockClient.AssertExpectations(t)
|
||||||
|
}
|
||||||
103
test.go
Normal file
103
test.go
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"plugin"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/beckn/beckn-onix/pkg/plugin/definition"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
// Path to the compiled plugin .so file
|
||||||
|
// Adjust the path accordingly
|
||||||
|
pluginPath := "pkg/plugin/implementation/cache.so"
|
||||||
|
|
||||||
|
// Open the plugin
|
||||||
|
p, err := plugin.Open(pluginPath)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Failed to open plugin: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lookup the 'Provider' symbol
|
||||||
|
symProvider, err := p.Lookup("Provider")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Failed to lookup 'Provider': %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert that the symbol implements the CacheProvider interface
|
||||||
|
provider, ok := symProvider.(definition.CacheProvider)
|
||||||
|
if !ok {
|
||||||
|
fmt.Println("Plugin 'Provider' does not implement CacheProvider interface.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Println("Successfully loaded CacheProvider plugin.")
|
||||||
|
|
||||||
|
// Setup config
|
||||||
|
config := map[string]string{
|
||||||
|
"addr": "localhost:6379", // Adjust to your Redis instance
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new cache instance using the plugin provider
|
||||||
|
cacheInstance, cleanup, err := provider.New(ctx, config)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error creating cache instance: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if cleanup != nil {
|
||||||
|
_ = cleanup()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
fmt.Println("Cache instance created successfully.")
|
||||||
|
|
||||||
|
// Test Set
|
||||||
|
key := "plugin_test_key"
|
||||||
|
value := "plugin_test_value"
|
||||||
|
ttl := 10 * time.Second
|
||||||
|
|
||||||
|
err = cacheInstance.Set(ctx, key, value, ttl)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Set failed: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Println("Set operation successful.")
|
||||||
|
|
||||||
|
// Test Get
|
||||||
|
got, err := cacheInstance.Get(ctx, key)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Get failed: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Printf("Got value: %s\n", got)
|
||||||
|
|
||||||
|
// Test Delete
|
||||||
|
err = cacheInstance.Delete(ctx, key)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Delete failed: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Println("Delete operation successful.")
|
||||||
|
|
||||||
|
// Test Clear
|
||||||
|
// Add a key to test Clear
|
||||||
|
err = cacheInstance.Set(ctx, "another_plugin_key", "another_plugin_value", ttl)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Set for clear test failed: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Println("Added key for clear test.")
|
||||||
|
|
||||||
|
err = cacheInstance.Clear(ctx)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Clear failed: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Println("Clear operation successful.")
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user