From 238e4ef2b0f48a0f71ed7af8d178dd1f8795c95e Mon Sep 17 00:00:00 2001 From: Vincent Composieux Date: Wed, 16 Oct 2019 18:12:50 +0200 Subject: [PATCH] feat: added cache & store delete() method --- README.md | 20 ++++++--- cache/cache.go | 10 ++++- cache/cache_test.go | 30 +++++++++++++ cache/chain.go | 9 ++++ cache/chain_test.go | 38 ++++++++++++++++ cache/interface.go | 1 + cache/loadable.go | 6 +++ cache/loadable_test.go | 38 ++++++++++++++++ cache/metric.go | 7 ++- cache/metric_test.go | 35 +++++++++++++++ codec/codec.go | 23 ++++++++-- codec/codec_test.go | 52 ++++++++++++++++++++++ codec/interface.go | 1 + marshaler/marshaler.go | 5 +++ marshaler/marshaler_test.go | 30 +++++++++++++ metrics/prometheus.go | 3 ++ metrics/prometheus_test.go | 18 ++++++-- store/bigcache.go | 10 ++++- store/bigcache_test.go | 35 +++++++++++++++ store/interface.go | 1 + store/memcache.go | 14 ++++-- store/memcache_test.go | 41 +++++++++++++++-- store/mock_bigcache_client_interface.go | 14 ++++++ store/mock_memcache_client_interface.go | 14 ++++++ store/mock_redis_client_interface.go | 22 +++++++++ store/mock_ristretto_client_interface.go | 5 +++ store/redis.go | 11 ++++- store/redis_test.go | 16 +++++++ store/ristretto.go | 11 ++++- store/ristretto_test.go | 16 +++++++ test/mocks/cache/cache_interface.go | 14 ++++++ test/mocks/cache/setter_cache_interface.go | 14 ++++++ test/mocks/codec/codec_interface.go | 14 ++++++ test/mocks/store/store_interface.go | 14 ++++++ 34 files changed, 562 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index 2b715ae..ddea286 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,8 @@ if err != nil { } value := cacheManager.Get("my-key") + +cacheManager.Delete("my-key") ``` #### Memory (using Bigcache) @@ -93,6 +95,8 @@ if err != nil { } value := cacheManager.Get("my-key") + +cacheManager.Delete("my-key") ``` #### Redis @@ -202,22 +206,24 @@ cacheManager := cache.NewMetric( ) // Initializes marshaler -marshaller := marshaler.New(cacheManager) +marshal := marshaler.New(cacheManager) key := BookQuery{Slug: "my-test-amazing-book"} value := Book{ID: 1, Name: "My test amazing book", Slug: "my-test-amazing-book"} -err = marshaller.Set(key, value) +err = marshal.Set(key, value) if err != nil { panic(err) } -returnedValue, err := marshaller.Get(key, new(Book)) +returnedValue, err := marshal.Get(key, new(Book)) if err != nil { panic(err) } // Then, do what you want with the value + +marshal.Delete("my-key") ``` The only thing you have to do is to specify the struct in which you want your value to be unmarshalled as a second argument when calling the `.Get()` method. @@ -283,22 +289,24 @@ func main() { ), )) - marshaller := marshaler.New(cacheManager) + marshal := marshaler.New(cacheManager) key := Book{Slug: "my-test-amazing-book"} value := Book{ID: 1, Name: "My test amazing book", Slug: "my-test-amazing-book"} - err = marshaller.Set(key, value, nil) + err = marshal.Set(key, value, nil) if err != nil { panic(err) } - returnedValue, err := marshaller.Get(key, new(Book)) + returnedValue, err := marshal.Get(key, new(Book)) if err != nil { panic(err) } fmt.Printf("%v\n", returnedValue) + + marshal.Delete(key) } ``` diff --git a/cache/cache.go b/cache/cache.go index 7ca1906..156361d 100644 --- a/cache/cache.go +++ b/cache/cache.go @@ -13,13 +13,13 @@ const ( // Cache represents the configuration needed by a cache type Cache struct { - codec codec.CodecInterface + codec codec.CodecInterface } // New instanciates a new cache entry func New(store store.StoreInterface) *Cache { return &Cache{ - codec: codec.New(store), + codec: codec.New(store), } } @@ -35,6 +35,12 @@ func (c *Cache) Set(key, object interface{}, options *store.Options) error { return c.codec.Set(cacheKey, object, options) } +// Delete removes the cache item using the given key +func (c *Cache) Delete(key interface{}) error { + cacheKey := c.getCacheKey(key) + return c.codec.Delete(cacheKey) +} + // GetCodec returns the current codec func (c *Cache) GetCodec() codec.CodecInterface { return c.codec diff --git a/cache/cache_test.go b/cache/cache_test.go index 5e2dbbb..6291fe3 100644 --- a/cache/cache_test.go +++ b/cache/cache_test.go @@ -135,3 +135,33 @@ func TestCacheGetType(t *testing.T) { // When - Then assert.Equal(t, CacheType, cache.GetType()) } + +func TestCacheDelete(t *testing.T) { + // Given + store := &mocksStore.StoreInterface{} + store.On("Delete", "9b1ac8a6e8ca8ca9477c0a252eb37756").Return(nil) + + cache := New(store) + + // When + err := cache.Delete("my-key") + + // Then + assert.Nil(t, err) +} + +func TestCacheDeleteWhenError(t *testing.T) { + // Given + expectedErr := errors.New("Unable to delete key") + + store := &mocksStore.StoreInterface{} + store.On("Delete", "9b1ac8a6e8ca8ca9477c0a252eb37756").Return(expectedErr) + + cache := New(store) + + // When + err := cache.Delete("my-key") + + // Then + assert.Equal(t, expectedErr, err) +} diff --git a/cache/chain.go b/cache/chain.go index 1542cf3..4e57955 100644 --- a/cache/chain.go +++ b/cache/chain.go @@ -56,6 +56,15 @@ func (c *ChainCache) Set(key, object interface{}, options *store.Options) error return nil } +// Delete removes a value from all available caches +func (c *ChainCache) Delete(key interface{}) error { + for _, cache := range c.caches { + cache.Delete(key) + } + + return nil +} + // setUntil sets a value in available caches, eventually until a given cache layer func (c *ChainCache) setUntil(key, object interface{}, until *string) error { for _, cache := range c.caches { diff --git a/cache/chain_test.go b/cache/chain_test.go index 59bd1ec..1f8235c 100644 --- a/cache/chain_test.go +++ b/cache/chain_test.go @@ -118,6 +118,44 @@ func TestChainGetWhenAvailableInSecondCache(t *testing.T) { assert.Equal(t, cacheValue, value) } +func TestChainDelete(t *testing.T) { + // Given + // Cache 1 + cache1 := &mocksCache.SetterCacheInterface{} + cache1.On("Delete", "my-key").Return(nil) + + // Cache 2 + cache2 := &mocksCache.SetterCacheInterface{} + cache2.On("Delete", "my-key").Return(nil) + + cache := NewChain(cache1, cache2) + + // When + err := cache.Delete("my-key") + + // Then + assert.Nil(t, err) +} + +func TestChainDeleteWhenError(t *testing.T) { + // Given + // Cache 1 + cache1 := &mocksCache.SetterCacheInterface{} + cache1.On("Delete", "my-key").Return(errors.New("An error has occured while deleting key")) + + // Cache 2 + cache2 := &mocksCache.SetterCacheInterface{} + cache2.On("Delete", "my-key").Return(nil) + + cache := NewChain(cache1, cache2) + + // When + err := cache.Delete("my-key") + + // Then + assert.Nil(t, err) +} + func TestChainGetType(t *testing.T) { // Given cache1 := &mocksCache.SetterCacheInterface{} diff --git a/cache/interface.go b/cache/interface.go index 80bcbd9..07b5536 100644 --- a/cache/interface.go +++ b/cache/interface.go @@ -9,6 +9,7 @@ import ( type CacheInterface interface { Get(key interface{}) (interface{}, error) Set(key, object interface{}, options *store.Options) error + Delete(key interface{}) error GetType() string } diff --git a/cache/loadable.go b/cache/loadable.go index cf0cdf2..447945f 100644 --- a/cache/loadable.go +++ b/cache/loadable.go @@ -2,6 +2,7 @@ package cache import ( "log" + "github.com/eko/gocache/store" ) @@ -52,6 +53,11 @@ func (c *LoadableCache) Set(key, object interface{}, options *store.Options) err return c.cache.Set(key, object, options) } +// Delete removes a value from cache +func (c *LoadableCache) Delete(key interface{}) error { + return c.cache.Delete(key) +} + // GetType returns the cache type func (c *LoadableCache) GetType() string { return LoadableType diff --git a/cache/loadable_test.go b/cache/loadable_test.go index 4756808..435ced2 100644 --- a/cache/loadable_test.go +++ b/cache/loadable_test.go @@ -90,6 +90,44 @@ func TestLoadableGetWhenAvailableInLoadFunc(t *testing.T) { assert.Equal(t, cacheValue, value) } +func TestLoadableDelete(t *testing.T) { + // Given + cache1 := &mocksCache.SetterCacheInterface{} + cache1.On("Delete", "my-key").Return(nil) + + loadFunc := func(key interface{}) (interface{}, error) { + return "a value", nil + } + + cache := NewLoadable(loadFunc, cache1) + + // When + err := cache.Delete("my-key") + + // Then + assert.Nil(t, err) +} + +func TestLoadableDeleteWhenError(t *testing.T) { + // Given + expectedErr := errors.New("Unable to delete key") + + cache1 := &mocksCache.SetterCacheInterface{} + cache1.On("Delete", "my-key").Return(expectedErr) + + loadFunc := func(key interface{}) (interface{}, error) { + return "a value", nil + } + + cache := NewLoadable(loadFunc, cache1) + + // When + err := cache.Delete("my-key") + + // Then + assert.Equal(t, expectedErr, err) +} + func TestLoadableGetType(t *testing.T) { // Given cache1 := &mocksCache.SetterCacheInterface{} diff --git a/cache/metric.go b/cache/metric.go index 317924f..244247f 100644 --- a/cache/metric.go +++ b/cache/metric.go @@ -32,11 +32,16 @@ func (c *MetricCache) Get(key interface{}) (interface{}, error) { return result, err } -// Set sets a value in cache and also records set metric +// Set sets a value from the cache func (c *MetricCache) Set(key, object interface{}, options *store.Options) error { return c.cache.Set(key, object, options) } +// Delete removes a value from the cache +func (c *MetricCache) Delete(key interface{}) error { + return c.cache.Delete(key) +} + // Get obtains a value from cache and also records metrics func (c *MetricCache) updateMetrics(cache CacheInterface) { switch current := cache.(type) { diff --git a/cache/metric_test.go b/cache/metric_test.go index 54ea335..2a493c9 100644 --- a/cache/metric_test.go +++ b/cache/metric_test.go @@ -1,6 +1,7 @@ package cache import ( + "errors" "testing" mocksCache "github.com/eko/gocache/test/mocks/cache" @@ -84,6 +85,40 @@ func TestMetricGetWhenChainCache(t *testing.T) { assert.Equal(t, cacheValue, value) } +func TestMetricDelete(t *testing.T) { + // Given + cache1 := &mocksCache.SetterCacheInterface{} + cache1.On("Delete", "my-key").Return(nil) + + metrics := &mocksMetrics.MetricsInterface{} + + cache := NewMetric(metrics, cache1) + + // When + err := cache.Delete("my-key") + + // Then + assert.Nil(t, err) +} + +func TestMetricDeleteWhenError(t *testing.T) { + // Given + expectedErr := errors.New("Unable to delete key") + + cache1 := &mocksCache.SetterCacheInterface{} + cache1.On("Delete", "my-key").Return(expectedErr) + + metrics := &mocksMetrics.MetricsInterface{} + + cache := NewMetric(metrics, cache1) + + // When + err := cache.Delete("my-key") + + // Then + assert.Equal(t, expectedErr, err) +} + func TestMetricGetType(t *testing.T) { // Given cache1 := &mocksCache.SetterCacheInterface{} diff --git a/codec/codec.go b/codec/codec.go index d48c3bc..5e6d25a 100644 --- a/codec/codec.go +++ b/codec/codec.go @@ -6,10 +6,12 @@ import ( // Stats allows to returns some statistics of codec usage type Stats struct { - Hits int - Miss int - SetSuccess int - SetError int + Hits int + Miss int + SetSuccess int + SetError int + DeleteSuccess int + DeleteError int } // Codec represents an instance of a cache store @@ -53,6 +55,19 @@ func (c *Codec) Set(key interface{}, value interface{}, options *store.Options) return err } +// Delete allows to remove a value for a given key identifier +func (c *Codec) Delete(key interface{}) error { + err := c.store.Delete(key) + + if err == nil { + c.stats.DeleteSuccess++ + } else { + c.stats.DeleteError++ + } + + return err +} + // GetStore returns the store associated to this codec func (c *Codec) GetStore() store.StoreInterface { return c.store diff --git a/codec/codec_test.go b/codec/codec_test.go index 0371343..a5b0631 100644 --- a/codec/codec_test.go +++ b/codec/codec_test.go @@ -45,6 +45,8 @@ func TestGetWhenHit(t *testing.T) { assert.Equal(t, 0, codec.GetStats().Miss) assert.Equal(t, 0, codec.GetStats().SetSuccess) assert.Equal(t, 0, codec.GetStats().SetError) + assert.Equal(t, 0, codec.GetStats().DeleteSuccess) + assert.Equal(t, 0, codec.GetStats().DeleteError) } func TestGetWhenMiss(t *testing.T) { @@ -67,6 +69,8 @@ func TestGetWhenMiss(t *testing.T) { assert.Equal(t, 1, codec.GetStats().Miss) assert.Equal(t, 0, codec.GetStats().SetSuccess) assert.Equal(t, 0, codec.GetStats().SetError) + assert.Equal(t, 0, codec.GetStats().DeleteSuccess) + assert.Equal(t, 0, codec.GetStats().DeleteError) } func TestSetWhenSuccess(t *testing.T) { @@ -96,6 +100,8 @@ func TestSetWhenSuccess(t *testing.T) { assert.Equal(t, 0, codec.GetStats().Miss) assert.Equal(t, 1, codec.GetStats().SetSuccess) assert.Equal(t, 0, codec.GetStats().SetError) + assert.Equal(t, 0, codec.GetStats().DeleteSuccess) + assert.Equal(t, 0, codec.GetStats().DeleteError) } func TestSetWhenError(t *testing.T) { @@ -127,6 +133,52 @@ func TestSetWhenError(t *testing.T) { assert.Equal(t, 0, codec.GetStats().Miss) assert.Equal(t, 0, codec.GetStats().SetSuccess) assert.Equal(t, 1, codec.GetStats().SetError) + assert.Equal(t, 0, codec.GetStats().DeleteSuccess) + assert.Equal(t, 0, codec.GetStats().DeleteError) +} + +func TestDeleteWhenSuccess(t *testing.T) { + // Given + store := &mocksStore.StoreInterface{} + store.On("Delete", "my-key").Return(nil) + + codec := New(store) + + // When + err := codec.Delete("my-key") + + // Then + assert.Nil(t, err) + + assert.Equal(t, 0, codec.GetStats().Hits) + assert.Equal(t, 0, codec.GetStats().Miss) + assert.Equal(t, 0, codec.GetStats().SetSuccess) + assert.Equal(t, 0, codec.GetStats().SetError) + assert.Equal(t, 1, codec.GetStats().DeleteSuccess) + assert.Equal(t, 0, codec.GetStats().DeleteError) +} + +func TesDeleteWhenError(t *testing.T) { + // Given + expectedErr := errors.New("Unable to delete key") + + store := &mocksStore.StoreInterface{} + store.On("Delete", "my-key").Return(expectedErr) + + codec := New(store) + + // When + err := codec.Delete("my-key") + + // Then + assert.Equal(t, expectedErr, err) + + assert.Equal(t, 0, codec.GetStats().Hits) + assert.Equal(t, 0, codec.GetStats().Miss) + assert.Equal(t, 0, codec.GetStats().SetSuccess) + assert.Equal(t, 0, codec.GetStats().SetError) + assert.Equal(t, 0, codec.GetStats().DeleteSuccess) + assert.Equal(t, 1, codec.GetStats().DeleteError) } func TestGetStore(t *testing.T) { diff --git a/codec/interface.go b/codec/interface.go index 0f9b954..f6f34c4 100644 --- a/codec/interface.go +++ b/codec/interface.go @@ -8,6 +8,7 @@ import ( type CodecInterface interface { Get(key interface{}) (interface{}, error) Set(key interface{}, value interface{}, options *store.Options) error + Delete(key interface{}) error GetStore() store.StoreInterface GetStats() *Stats diff --git a/marshaler/marshaler.go b/marshaler/marshaler.go index 9ea106c..87ce907 100644 --- a/marshaler/marshaler.go +++ b/marshaler/marshaler.go @@ -49,3 +49,8 @@ func (c *Marshaler) Set(key, object interface{}, options *store.Options) error { return c.cache.Set(key, bytes, options) } + +// Delete removes a value from the cache +func (c *Marshaler) Delete(key interface{}) error { + return c.cache.Delete(key) +} diff --git a/marshaler/marshaler_test.go b/marshaler/marshaler_test.go index 686ce94..dffdfd5 100644 --- a/marshaler/marshaler_test.go +++ b/marshaler/marshaler_test.go @@ -148,3 +148,33 @@ func TestSetWhenString(t *testing.T) { // Then assert.Nil(t, err) } + +func TestDelete(t *testing.T) { + // Given + cache := &mocksCache.CacheInterface{} + cache.On("Delete", "my-key").Return(nil) + + marshaler := New(cache) + + // When + err := marshaler.Delete("my-key") + + // Then + assert.Nil(t, err) +} + +func TestDeleteWhenError(t *testing.T) { + // Given + expectedErr := errors.New("Unable to delete key") + + cache := &mocksCache.CacheInterface{} + cache.On("Delete", "my-key").Return(expectedErr) + + marshaler := New(cache) + + // When + err := marshaler.Delete("my-key") + + // Then + assert.Equal(t, expectedErr, err) +} diff --git a/metrics/prometheus.go b/metrics/prometheus.go index b327d1f..5a4b24c 100644 --- a/metrics/prometheus.go +++ b/metrics/prometheus.go @@ -48,4 +48,7 @@ func (m *Prometheus) RecordFromCodec(codec codec.CodecInterface) { m.Record(storeType, "set_success", float64(stats.SetSuccess)) m.Record(storeType, "set_error", float64(stats.SetError)) + + m.Record(storeType, "delete_success", float64(stats.DeleteSuccess)) + m.Record(storeType, "delete_error", float64(stats.DeleteError)) } diff --git a/metrics/prometheus_test.go b/metrics/prometheus_test.go index 4300a2f..6030824 100644 --- a/metrics/prometheus_test.go +++ b/metrics/prometheus_test.go @@ -46,10 +46,12 @@ func TestRecordFromCodec(t *testing.T) { redisStore.On("GetType").Return("redis") stats := &codec.Stats{ - Hits: 4, - Miss: 6, - SetSuccess: 12, - SetError: 3, + Hits: 4, + Miss: 6, + SetSuccess: 12, + SetError: 3, + DeleteSuccess: 8, + DeleteError: 5, } testCodec := &mocksCodec.CodecInterface{} @@ -82,6 +84,14 @@ func TestRecordFromCodec(t *testing.T) { metricName: "set_error", expected: float64(stats.SetError), }, + { + metricName: "delete_success", + expected: float64(stats.DeleteSuccess), + }, + { + metricName: "delete_error", + expected: float64(stats.DeleteError), + }, } for _, tc := range testCases { diff --git a/store/bigcache.go b/store/bigcache.go index 5d17b4c..4463bd3 100644 --- a/store/bigcache.go +++ b/store/bigcache.go @@ -8,6 +8,7 @@ import ( type BigcacheClientInterface interface { Get(key string) ([]byte, error) Set(key string, entry []byte) error + Delete(key string) error } const ( @@ -16,7 +17,7 @@ const ( // BigcacheStore is a store for Redis type BigcacheStore struct { - client BigcacheClientInterface + client BigcacheClientInterface options *Options } @@ -27,7 +28,7 @@ func NewBigcache(client BigcacheClientInterface, options *Options) *BigcacheStor } return &BigcacheStore{ - client: client, + client: client, options: options, } } @@ -50,6 +51,11 @@ func (s *BigcacheStore) Set(key interface{}, value interface{}, options *Options return s.client.Set(key.(string), value.([]byte)) } +// Delete removes data from Redis for given key idntifier +func (s *BigcacheStore) Delete(key interface{}) error { + return s.client.Delete(key.(string)) +} + // GetType returns the store type func (s *BigcacheStore) GetType() string { return BigcacheType diff --git a/store/bigcache_test.go b/store/bigcache_test.go index 40d35b5..2d91c38 100644 --- a/store/bigcache_test.go +++ b/store/bigcache_test.go @@ -1,6 +1,7 @@ package store import ( + "errors" "testing" "github.com/stretchr/testify/assert" @@ -54,6 +55,40 @@ func TestBigcacheSet(t *testing.T) { assert.Nil(t, err) } +func TestBigcacheDelete(t *testing.T) { + // Given + cacheKey := "my-key" + + client := &MockBigcacheClientInterface{} + client.On("Delete", cacheKey).Return(nil) + + store := NewBigcache(client, nil) + + // When + err := store.Delete(cacheKey) + + // Then + assert.Nil(t, err) +} + +func TestBigcacheDeleteWhenError(t *testing.T) { + // Given + expectedErr := errors.New("Unable to delete key") + + cacheKey := "my-key" + + client := &MockBigcacheClientInterface{} + client.On("Delete", cacheKey).Return(expectedErr) + + store := NewBigcache(client, nil) + + // When + err := store.Delete(cacheKey) + + // Then + assert.Equal(t, expectedErr, err) +} + func TestBigcacheGetType(t *testing.T) { // Given client := &MockBigcacheClientInterface{} diff --git a/store/interface.go b/store/interface.go index 565d950..919d0ce 100644 --- a/store/interface.go +++ b/store/interface.go @@ -4,5 +4,6 @@ package store type StoreInterface interface { Get(key interface{}) (interface{}, error) Set(key interface{}, value interface{}, options *Options) error + Delete(key interface{}) error GetType() string } diff --git a/store/memcache.go b/store/memcache.go index 40165c2..1fe3040 100644 --- a/store/memcache.go +++ b/store/memcache.go @@ -10,15 +10,16 @@ import ( type MemcacheClientInterface interface { Get(key string) (item *memcache.Item, err error) Set(item *memcache.Item) error + Delete(item string) error } const ( MemcacheType = "memcache" ) -// MemcacheStore is a store for Redis +// MemcacheStore is a store for Memcache type MemcacheStore struct { - client MemcacheClientInterface + client MemcacheClientInterface options *Options } @@ -29,7 +30,7 @@ func NewMemcache(client MemcacheClientInterface, options *Options) *MemcacheStor } return &MemcacheStore{ - client: client, + client: client, options: options, } } @@ -47,7 +48,7 @@ func (s *MemcacheStore) Get(key interface{}) (interface{}, error) { return item.Value, err } -// Set defines data in Redis for given key idntifier +// Set defines data in Memcache for given key idntifier func (s *MemcacheStore) Set(key interface{}, value interface{}, options *Options) error { if options == nil { options = s.options @@ -62,6 +63,11 @@ func (s *MemcacheStore) Set(key interface{}, value interface{}, options *Options return s.client.Set(item) } +// Delete removes data from Memcache for given key idntifier +func (s *MemcacheStore) Delete(key interface{}) error { + return s.client.Delete(key.(string)) +} + // GetType returns the store type func (s *MemcacheStore) GetType() string { return MemcacheType diff --git a/store/memcache_test.go b/store/memcache_test.go index bf70ddb..59cc797 100644 --- a/store/memcache_test.go +++ b/store/memcache_test.go @@ -1,6 +1,7 @@ package store import ( + "errors" "testing" "time" @@ -11,7 +12,7 @@ import ( func TestNewMemcache(t *testing.T) { // Given client := &MockMemcacheClientInterface{} - options := &Options{Expiration: 3*time.Second} + options := &Options{Expiration: 3 * time.Second} // When store := NewMemcache(client, options) @@ -24,7 +25,7 @@ func TestNewMemcache(t *testing.T) { func TestMemcacheGet(t *testing.T) { // Given - options := &Options{Expiration: 3*time.Second} + options := &Options{Expiration: 3 * time.Second} cacheKey := "my-key" cacheValue := []byte("my-cache-value") @@ -46,7 +47,7 @@ func TestMemcacheGet(t *testing.T) { func TestMemcacheSet(t *testing.T) { // Given - options := &Options{Expiration: 3*time.Second} + options := &Options{Expiration: 3 * time.Second} cacheKey := "my-key" cacheValue := []byte("my-cache-value") @@ -69,6 +70,40 @@ func TestMemcacheSet(t *testing.T) { assert.Nil(t, err) } +func TestMemcacheDelete(t *testing.T) { + // Given + cacheKey := "my-key" + + client := &MockMemcacheClientInterface{} + client.On("Delete", cacheKey).Return(nil) + + store := NewMemcache(client, nil) + + // When + err := store.Delete(cacheKey) + + // Then + assert.Nil(t, err) +} + +func TestMemcacheDeleteWhenError(t *testing.T) { + // Given + expectedErr := errors.New("Unable to delete key") + + cacheKey := "my-key" + + client := &MockMemcacheClientInterface{} + client.On("Delete", cacheKey).Return(expectedErr) + + store := NewMemcache(client, nil) + + // When + err := store.Delete(cacheKey) + + // Then + assert.Equal(t, expectedErr, err) +} + func TestMemcacheGetType(t *testing.T) { // Given client := &MockMemcacheClientInterface{} diff --git a/store/mock_bigcache_client_interface.go b/store/mock_bigcache_client_interface.go index 95843ee..c68e40b 100644 --- a/store/mock_bigcache_client_interface.go +++ b/store/mock_bigcache_client_interface.go @@ -9,6 +9,20 @@ type MockBigcacheClientInterface struct { mock.Mock } +// Delete provides a mock function with given fields: key +func (_m *MockBigcacheClientInterface) Delete(key string) error { + ret := _m.Called(key) + + var r0 error + if rf, ok := ret.Get(0).(func(string) error); ok { + r0 = rf(key) + } else { + r0 = ret.Error(0) + } + + return r0 +} + // Get provides a mock function with given fields: key func (_m *MockBigcacheClientInterface) Get(key string) ([]byte, error) { ret := _m.Called(key) diff --git a/store/mock_memcache_client_interface.go b/store/mock_memcache_client_interface.go index 0f87d8d..31b47c4 100644 --- a/store/mock_memcache_client_interface.go +++ b/store/mock_memcache_client_interface.go @@ -10,6 +10,20 @@ type MockMemcacheClientInterface struct { mock.Mock } +// Delete provides a mock function with given fields: item +func (_m *MockMemcacheClientInterface) Delete(item string) error { + ret := _m.Called(item) + + var r0 error + if rf, ok := ret.Get(0).(func(string) error); ok { + r0 = rf(item) + } else { + r0 = ret.Error(0) + } + + return r0 +} + // Get provides a mock function with given fields: key func (_m *MockMemcacheClientInterface) Get(key string) (*memcache.Item, error) { ret := _m.Called(key) diff --git a/store/mock_redis_client_interface.go b/store/mock_redis_client_interface.go index f18fe51..7c52a2e 100644 --- a/store/mock_redis_client_interface.go +++ b/store/mock_redis_client_interface.go @@ -11,6 +11,28 @@ type MockRedisClientInterface struct { mock.Mock } +// Del provides a mock function with given fields: keys +func (_m *MockRedisClientInterface) Del(keys ...string) *redis.IntCmd { + _va := make([]interface{}, len(keys)) + for _i := range keys { + _va[_i] = keys[_i] + } + var _ca []interface{} + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *redis.IntCmd + if rf, ok := ret.Get(0).(func(...string) *redis.IntCmd); ok { + r0 = rf(keys...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*redis.IntCmd) + } + } + + return r0 +} + // Get provides a mock function with given fields: key func (_m *MockRedisClientInterface) Get(key string) *redis.StringCmd { ret := _m.Called(key) diff --git a/store/mock_ristretto_client_interface.go b/store/mock_ristretto_client_interface.go index ccf17b5..5d55479 100644 --- a/store/mock_ristretto_client_interface.go +++ b/store/mock_ristretto_client_interface.go @@ -9,6 +9,11 @@ type MockRistrettoClientInterface struct { mock.Mock } +// Del provides a mock function with given fields: key +func (_m *MockRistrettoClientInterface) Del(key interface{}) { + _m.Called(key) +} + // Get provides a mock function with given fields: key func (_m *MockRistrettoClientInterface) Get(key interface{}) (interface{}, bool) { ret := _m.Called(key) diff --git a/store/redis.go b/store/redis.go index 2599ddc..ad881b8 100644 --- a/store/redis.go +++ b/store/redis.go @@ -10,6 +10,7 @@ import ( type RedisClientInterface interface { Get(key string) *redis.StringCmd Set(key string, value interface{}, expiration time.Duration) *redis.StatusCmd + Del(keys ...string) *redis.IntCmd } const ( @@ -18,7 +19,7 @@ const ( // RedisStore is a store for Redis type RedisStore struct { - client RedisClientInterface + client RedisClientInterface options *Options } @@ -29,7 +30,7 @@ func NewRedis(client RedisClientInterface, options *Options) *RedisStore { } return &RedisStore{ - client: client, + client: client, options: options, } } @@ -48,6 +49,12 @@ func (s *RedisStore) Set(key interface{}, value interface{}, options *Options) e return s.client.Set(key.(string), value, options.ExpirationValue()).Err() } +// Delete removes data from Redis for given key idntifier +func (s *RedisStore) Delete(key interface{}) error { + _, err := s.client.Del(key.(string)).Result() + return err +} + // GetType returns the store type func (s *RedisStore) GetType() string { return RedisType diff --git a/store/redis_test.go b/store/redis_test.go index 4d14868..2a3ac32 100644 --- a/store/redis_test.go +++ b/store/redis_test.go @@ -61,6 +61,22 @@ func TestRedisSet(t *testing.T) { assert.Nil(t, err) } +func TestRedisDelete(t *testing.T) { + // Given + cacheKey := "my-key" + + client := &MockRedisClientInterface{} + client.On("Del", "my-key").Return(&redis.IntCmd{}) + + store := NewRedis(client, nil) + + // When + err := store.Delete(cacheKey) + + // Then + assert.Nil(t, err) +} + func TestRedisGetType(t *testing.T) { // Given client := &MockRedisClientInterface{} diff --git a/store/ristretto.go b/store/ristretto.go index c2a1f84..05c2075 100644 --- a/store/ristretto.go +++ b/store/ristretto.go @@ -13,11 +13,12 @@ const ( type RistrettoClientInterface interface { Get(key interface{}) (interface{}, bool) Set(key, value interface{}, cost int64) bool + Del(key interface{}) } // RistrettoStore is a store for Ristretto (memory) library type RistrettoStore struct { - client RistrettoClientInterface + client RistrettoClientInterface options *Options } @@ -28,7 +29,7 @@ func NewRistretto(client RistrettoClientInterface, options *Options) *RistrettoS } return &RistrettoStore{ - client: client, + client: client, options: options, } } @@ -60,6 +61,12 @@ func (s *RistrettoStore) Set(key interface{}, value interface{}, options *Option return err } +// Delete removes data in Ristretto memoey cache for given key idntifier +func (s *RistrettoStore) Delete(key interface{}) error { + s.client.Del(key) + return nil +} + // GetType returns the store type func (s *RistrettoStore) GetType() string { return RistrettoType diff --git a/store/ristretto_test.go b/store/ristretto_test.go index 3f9ec65..aa7b45a 100644 --- a/store/ristretto_test.go +++ b/store/ristretto_test.go @@ -62,6 +62,22 @@ func TestRistrettoSet(t *testing.T) { assert.Nil(t, err) } +func TestRistrettoDelete(t *testing.T) { + // Given + cacheKey := "my-key" + + client := &MockRistrettoClientInterface{} + client.On("Del", cacheKey).Return(nil) + + store := NewRistretto(client, nil) + + // When + err := store.Delete(cacheKey) + + // Then + assert.Nil(t, err) +} + func TestRistrettoGetType(t *testing.T) { // Given client := &MockRistrettoClientInterface{} diff --git a/test/mocks/cache/cache_interface.go b/test/mocks/cache/cache_interface.go index 57fdd2b..e52f8a4 100644 --- a/test/mocks/cache/cache_interface.go +++ b/test/mocks/cache/cache_interface.go @@ -10,6 +10,20 @@ type CacheInterface struct { mock.Mock } +// Delete provides a mock function with given fields: key +func (_m *CacheInterface) Delete(key interface{}) error { + ret := _m.Called(key) + + var r0 error + if rf, ok := ret.Get(0).(func(interface{}) error); ok { + r0 = rf(key) + } else { + r0 = ret.Error(0) + } + + return r0 +} + // Get provides a mock function with given fields: key func (_m *CacheInterface) Get(key interface{}) (interface{}, error) { ret := _m.Called(key) diff --git a/test/mocks/cache/setter_cache_interface.go b/test/mocks/cache/setter_cache_interface.go index 0c20ac7..15b69ca 100644 --- a/test/mocks/cache/setter_cache_interface.go +++ b/test/mocks/cache/setter_cache_interface.go @@ -11,6 +11,20 @@ type SetterCacheInterface struct { mock.Mock } +// Delete provides a mock function with given fields: key +func (_m *SetterCacheInterface) Delete(key interface{}) error { + ret := _m.Called(key) + + var r0 error + if rf, ok := ret.Get(0).(func(interface{}) error); ok { + r0 = rf(key) + } else { + r0 = ret.Error(0) + } + + return r0 +} + // Get provides a mock function with given fields: key func (_m *SetterCacheInterface) Get(key interface{}) (interface{}, error) { ret := _m.Called(key) diff --git a/test/mocks/codec/codec_interface.go b/test/mocks/codec/codec_interface.go index f7908b8..e4131bc 100644 --- a/test/mocks/codec/codec_interface.go +++ b/test/mocks/codec/codec_interface.go @@ -11,6 +11,20 @@ type CodecInterface struct { mock.Mock } +// Delete provides a mock function with given fields: key +func (_m *CodecInterface) Delete(key interface{}) error { + ret := _m.Called(key) + + var r0 error + if rf, ok := ret.Get(0).(func(interface{}) error); ok { + r0 = rf(key) + } else { + r0 = ret.Error(0) + } + + return r0 +} + // Get provides a mock function with given fields: key func (_m *CodecInterface) Get(key interface{}) (interface{}, error) { ret := _m.Called(key) diff --git a/test/mocks/store/store_interface.go b/test/mocks/store/store_interface.go index fd6f70a..70c32b4 100644 --- a/test/mocks/store/store_interface.go +++ b/test/mocks/store/store_interface.go @@ -10,6 +10,20 @@ type StoreInterface struct { mock.Mock } +// Delete provides a mock function with given fields: key +func (_m *StoreInterface) Delete(key interface{}) error { + ret := _m.Called(key) + + var r0 error + if rf, ok := ret.Get(0).(func(interface{}) error); ok { + r0 = rf(key) + } else { + r0 = ret.Error(0) + } + + return r0 +} + // Get provides a mock function with given fields: key func (_m *StoreInterface) Get(key interface{}) (interface{}, error) { ret := _m.Called(key)