diff --git a/Makefile b/Makefile index 1df77258..41a195ca 100644 --- a/Makefile +++ b/Makefile @@ -7,9 +7,7 @@ mocks: mockery -case=snake -name=SetterCacheInterface -dir=cache/ -output test/mocks/cache/ mockery -case=snake -name=MetricsInterface -dir=metrics/ -output test/mocks/metrics/ mockery -case=snake -name=StoreInterface -dir=store/ -output test/mocks/store/ - - # in package store clients mocks - mockery -case=snake -inpkg -name=BigcacheClientInterface -dir=store/ -output store/ - mockery -case=snake -inpkg -name=MemcacheClientInterface -dir=store/ -output store/ - mockery -case=snake -inpkg -name=RedisClientInterface -dir=store/ -output store/ - mockery -case=snake -inpkg -name=RistrettoClientInterface -dir=store/ -output store/ + mockery -case=snake -name=BigcacheClientInterface -dir=store/ -output test/mocks/store/clients/ + mockery -case=snake -name=MemcacheClientInterface -dir=store/ -output test/mocks/store/clients/ + mockery -case=snake -name=RedisClientInterface -dir=store/ -output test/mocks/store/clients/ + mockery -case=snake -name=RistrettoClientInterface -dir=store/ -output test/mocks/store/clients/ diff --git a/cache/cache.go b/cache/cache.go index 28ac52e1..ec31bfbc 100644 --- a/cache/cache.go +++ b/cache/cache.go @@ -1,6 +1,9 @@ package cache import ( + "crypto" + "fmt" + "reflect" "strings" "github.com/eko/gocache/codec" @@ -62,3 +65,13 @@ func (c *Cache) GetType() string { func (c *Cache) getCacheKey(key interface{}) string { return strings.ToLower(checksum(key)) } + +// checksum hashes a given object into a string +func checksum(object interface{}) string { + digester := crypto.MD5.New() + fmt.Fprint(digester, reflect.TypeOf(object)) + fmt.Fprint(digester, object) + hash := digester.Sum(nil) + + return fmt.Sprintf("%x", hash) +} diff --git a/cache/chain.go b/cache/chain.go index a702f8c9..f26b4746 100644 --- a/cache/chain.go +++ b/cache/chain.go @@ -76,20 +76,14 @@ func (c *ChainCache) Invalidate(options store.InvalidateOptions) error { } // setUntil sets a value in available caches, eventually until a given cache layer -func (c *ChainCache) setUntil(key, object interface{}, until *string) error { +func (c *ChainCache) setUntil(key, object interface{}, until *string) { for _, cache := range c.caches { if until != nil && *until == cache.GetCodec().GetStore().GetType() { break } - err := cache.Set(key, object, nil) - if err != nil { - storeType := cache.GetCodec().GetStore().GetType() - return fmt.Errorf("Unable to set item into cache with store '%s': %v", storeType, err) - } + cache.Set(key, object, nil) } - - return nil } // GetCaches returns all Chaind caches diff --git a/cache/chain_test.go b/cache/chain_test.go index a8da6e93..6797c649 100644 --- a/cache/chain_test.go +++ b/cache/chain_test.go @@ -2,6 +2,7 @@ package cache import ( "errors" + "fmt" "testing" "github.com/eko/gocache/store" @@ -118,6 +119,105 @@ func TestChainGetWhenAvailableInSecondCache(t *testing.T) { assert.Equal(t, cacheValue, value) } +func TestChainGetWhenNotAvailableInAnyCache(t *testing.T) { + // Given + // Cache 1 + store1 := &mocksStore.StoreInterface{} + store1.On("GetType").Return("store1") + + codec1 := &mocksCodec.CodecInterface{} + codec1.On("GetStore").Return(store1) + + cache1 := &mocksCache.SetterCacheInterface{} + cache1.On("GetCodec").Return(codec1) + cache1.On("Get", "my-key").Return(nil, errors.New("Unable to find in cache 1")) + + // Cache 2 + store2 := &mocksStore.StoreInterface{} + store2.On("GetType").Return("store2") + + codec2 := &mocksCodec.CodecInterface{} + codec2.On("GetStore").Return(store2) + + cache2 := &mocksCache.SetterCacheInterface{} + cache2.On("GetCodec").Return(codec2) + cache2.On("Get", "my-key").Return(nil, errors.New("Unable to find in cache 2")) + cache2.AssertNotCalled(t, "Set") + + cache := NewChain(cache1, cache2) + + // When + value, err := cache.Get("my-key") + + // Then + assert.Equal(t, errors.New("Unable to find in cache 2"), err) + assert.Equal(t, nil, value) +} + +func TestChainSet(t *testing.T) { + // Given + cacheValue := &struct { + Hello string + }{ + Hello: "world", + } + + options := &store.Options{} + + // Cache 1 + cache1 := &mocksCache.SetterCacheInterface{} + cache1.On("Set", "my-key", cacheValue, options).Return(nil) + + // Cache 2 + cache2 := &mocksCache.SetterCacheInterface{} + cache2.On("Set", "my-key", cacheValue, options).Return(nil) + + cache := NewChain(cache1, cache2) + + // When + err := cache.Set("my-key", cacheValue, options) + + // Then + assert.Nil(t, err) +} + +func TestChainSetWhenErrorOnSetting(t *testing.T) { + // Given + cacheValue := &struct { + Hello string + }{ + Hello: "world", + } + + options := &store.Options{} + + expectedErr := errors.New("An unexpected error occurred while setting data") + + // Cache 1 + store1 := &mocksStore.StoreInterface{} + store1.On("GetType").Return("store1") + + codec1 := &mocksCodec.CodecInterface{} + codec1.On("GetStore").Return(store1) + + cache1 := &mocksCache.SetterCacheInterface{} + cache1.On("GetCodec").Return(codec1) + cache1.On("Set", "my-key", cacheValue, options).Return(expectedErr) + + // Cache 2 + cache2 := &mocksCache.SetterCacheInterface{} + cache2.AssertNotCalled(t, "Set") + + cache := NewChain(cache1, cache2) + + // When + err := cache.Set("my-key", cacheValue, options) + + // Then + assert.Error(t, err) + assert.Equal(t, err.Error(), fmt.Sprintf("Unable to set item into cache with store 'store1': %s", expectedErr.Error())) +} + func TestChainDelete(t *testing.T) { // Given // Cache 1 @@ -211,3 +311,23 @@ func TestChainGetType(t *testing.T) { // When - Then assert.Equal(t, ChainType, cache.GetType()) } + +func TestCacheChecksum(t *testing.T) { + testCases := []struct { + value interface{} + expectedHash string + }{ + {value: 273273623, expectedHash: "a187c153af38575778244cb3796536da"}, + {value: "hello-world", expectedHash: "f31215be6928a6f6e0c7c1cf2c68054e"}, + {value: []byte(`hello-world`), expectedHash: "f097ebac995e666eb074e019cd39d99b"}, + {value: struct{ Label string }{}, expectedHash: "2938da2beee350d6ea988e404109f428"}, + {value: struct{ Label string }{Label: "hello-world"}, expectedHash: "4119a1c8530a0420859f1c6ecf2dc0b7"}, + {value: struct{ Label string }{Label: "hello-everyone"}, expectedHash: "1d7e7ed4acd56d2635f7cb33aa702bdd"}, + } + + for _, tc := range testCases { + value := checksum(tc.value) + + assert.Equal(t, tc.expectedHash, value) + } +} diff --git a/cache/common.go b/cache/common.go deleted file mode 100644 index 3adb63d2..00000000 --- a/cache/common.go +++ /dev/null @@ -1,17 +0,0 @@ -package cache - -import ( - "crypto" - "fmt" - "reflect" -) - -// checksum hashes a given object into a string -func checksum(object interface{}) string { - digester := crypto.MD5.New() - fmt.Fprint(digester, reflect.TypeOf(object)) - fmt.Fprint(digester, object) - hash := digester.Sum(nil) - - return fmt.Sprintf("%x", hash) -} diff --git a/cache/common_test.go b/cache/common_test.go deleted file mode 100644 index 4a618c54..00000000 --- a/cache/common_test.go +++ /dev/null @@ -1,27 +0,0 @@ -package cache - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestCommonChecksum(t *testing.T) { - testCases := []struct { - value interface{} - expectedHash string - }{ - {value: 273273623, expectedHash: "a187c153af38575778244cb3796536da"}, - {value: "hello-world", expectedHash: "f31215be6928a6f6e0c7c1cf2c68054e"}, - {value: []byte(`hello-world`), expectedHash: "f097ebac995e666eb074e019cd39d99b"}, - {value: struct{ Label string }{}, expectedHash: "2938da2beee350d6ea988e404109f428"}, - {value: struct{ Label string }{Label: "hello-world"}, expectedHash: "4119a1c8530a0420859f1c6ecf2dc0b7"}, - {value: struct{ Label string }{Label: "hello-everyone"}, expectedHash: "1d7e7ed4acd56d2635f7cb33aa702bdd"}, - } - - for _, tc := range testCases { - value := checksum(tc.value) - - assert.Equal(t, tc.expectedHash, value) - } -} diff --git a/cache/loadable_test.go b/cache/loadable_test.go index ff17685c..5e495822 100644 --- a/cache/loadable_test.go +++ b/cache/loadable_test.go @@ -29,6 +29,31 @@ func TestNewLoadable(t *testing.T) { assert.Equal(t, cache1, cache.cache) } +func TestLoadableGetWhenAlreadyInCache(t *testing.T) { + // Given + cacheValue := &struct { + Hello string + }{ + Hello: "world", + } + + cache1 := &mocksCache.SetterCacheInterface{} + cache1.On("Get", "my-key").Return(cacheValue, nil) + + loadFunc := func(key interface{}) (interface{}, error) { + return nil, errors.New("Should not be called") + } + + cache := NewLoadable(loadFunc, cache1) + + // When + value, err := cache.Get("my-key") + + // Then + assert.Nil(t, err) + assert.Equal(t, cacheValue, value) +} + func TestLoadableGetWhenNotAvailableInLoadFunc(t *testing.T) { // Given // Cache diff --git a/cache/metric_test.go b/cache/metric_test.go index e64eb6de..f106fc01 100644 --- a/cache/metric_test.go +++ b/cache/metric_test.go @@ -3,6 +3,7 @@ package cache import ( "errors" "testing" + "time" "github.com/eko/gocache/store" mocksCache "github.com/eko/gocache/test/mocks/cache" @@ -86,6 +87,32 @@ func TestMetricGetWhenChainCache(t *testing.T) { assert.Equal(t, cacheValue, value) } +func TestMetricSet(t *testing.T) { + // Given + value := &struct { + Hello string + }{ + Hello: "world", + } + + options := &store.Options{ + Expiration: 5 * time.Second, + } + + cache1 := &mocksCache.SetterCacheInterface{} + cache1.On("Set", "my-key", value, options).Return(nil) + + metrics := &mocksMetrics.MetricsInterface{} + + cache := NewMetric(metrics, cache1) + + // When + err := cache.Set("my-key", value, options) + + // Then + assert.Nil(t, err) +} + func TestMetricDelete(t *testing.T) { // Given cache1 := &mocksCache.SetterCacheInterface{} diff --git a/go.mod b/go.mod index 693d52f3..3d1d9ca7 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,10 @@ go 1.13 require ( github.com/allegro/bigcache v1.2.1 github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b + github.com/coreos/etcd v3.3.17+incompatible + github.com/dgraph-io/ristretto v0.0.0-20191010170704-2ba187ef9534 github.com/go-redis/redis/v7 v7.0.0-beta.4 + github.com/jonboulle/clockwork v0.1.0 // indirect github.com/kr/pretty v0.1.0 // indirect github.com/prometheus/client_golang v1.1.0 github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 diff --git a/go.sum b/go.sum index 407c7c1f..503d40ff 100644 --- a/go.sum +++ b/go.sum @@ -10,9 +10,14 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b h1:L/QXpzIa3pOvUGt1D1lA5KjYhPBAN/3iWdP7xeFS9F0= github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA= +github.com/coreos/etcd v3.3.17+incompatible h1:f/Z3EoDSx1yjaIjLQGo1diYUlQYSBrrAQ5vP8NjwXwo= +github.com/coreos/etcd v3.3.17+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgraph-io/ristretto v0.0.0-20191010170704-2ba187ef9534 h1:9G6fVccQriMJu4nXwpwLDoy9y31t/KUSLAbPcoBgv+4= +github.com/dgraph-io/ristretto v0.0.0-20191010170704-2ba187ef9534/go.mod h1:edzKIzGvqUCMzhTVWbiTSe75zD9Xxq0GtSBtFmaUTZs= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= @@ -30,6 +35,8 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= diff --git a/marshaler/marshaler_test.go b/marshaler/marshaler_test.go index d47f652a..bf9a0a21 100644 --- a/marshaler/marshaler_test.go +++ b/marshaler/marshaler_test.go @@ -149,6 +149,28 @@ func TestSetWhenString(t *testing.T) { assert.Nil(t, err) } +func TestSetWhenError(t *testing.T) { + // Given + cacheValue := "test" + + options := &store.Options{ + Expiration: 5 * time.Second, + } + + expectedErr := errors.New("An unexpected error occurred") + + cache := &mocksCache.CacheInterface{} + cache.On("Set", "my-key", []byte{0xa4, 0x74, 0x65, 0x73, 0x74}, options).Return(expectedErr) + + marshaler := New(cache) + + // When + err := marshaler.Set("my-key", cacheValue, options) + + // Then + assert.Equal(t, expectedErr, err) +} + func TestDelete(t *testing.T) { // Given cache := &mocksCache.CacheInterface{} diff --git a/store/bigcache_test.go b/store/bigcache_test.go index a1f4ff4c..b71fd1ca 100644 --- a/store/bigcache_test.go +++ b/store/bigcache_test.go @@ -4,12 +4,13 @@ import ( "errors" "testing" + mocksStore "github.com/eko/gocache/test/mocks/store/clients" "github.com/stretchr/testify/assert" ) func TestNewBigcache(t *testing.T) { // Given - client := &MockBigcacheClientInterface{} + client := &mocksStore.BigcacheClientInterface{} // When store := NewBigcache(client, nil) @@ -25,7 +26,7 @@ func TestBigcacheGet(t *testing.T) { cacheKey := "my-key" cacheValue := []byte("my-cache-value") - client := &MockBigcacheClientInterface{} + client := &mocksStore.BigcacheClientInterface{} client.On("Get", cacheKey).Return(cacheValue, nil) store := NewBigcache(client, nil) @@ -38,29 +39,71 @@ func TestBigcacheGet(t *testing.T) { assert.Equal(t, cacheValue, value) } +func TestBigcacheGetWhenError(t *testing.T) { + // Given + cacheKey := "my-key" + + expectedErr := errors.New("An unexpected error occurred") + + client := &mocksStore.BigcacheClientInterface{} + client.On("Get", cacheKey).Return(nil, expectedErr) + + store := NewBigcache(client, nil) + + // When + value, err := store.Get(cacheKey) + + // Then + assert.Equal(t, expectedErr, err) + assert.Nil(t, value) +} + func TestBigcacheSet(t *testing.T) { // Given cacheKey := "my-key" cacheValue := []byte("my-cache-value") - client := &MockBigcacheClientInterface{} + options := &Options{} + + client := &mocksStore.BigcacheClientInterface{} client.On("Set", cacheKey, cacheValue).Return(nil) store := NewBigcache(client, nil) // When - err := store.Set(cacheKey, cacheValue, nil) + err := store.Set(cacheKey, cacheValue, options) // Then assert.Nil(t, err) } +func TestBigcacheSetWhenError(t *testing.T) { + // Given + cacheKey := "my-key" + cacheValue := []byte("my-cache-value") + + options := &Options{} + + expectedErr := errors.New("An unexpected error occurred") + + client := &mocksStore.BigcacheClientInterface{} + client.On("Set", cacheKey, cacheValue).Return(expectedErr) + + store := NewBigcache(client, options) + + // When + err := store.Set(cacheKey, cacheValue, nil) + + // Then + assert.Equal(t, expectedErr, err) +} + func TestBigcacheSetWithTags(t *testing.T) { // Given cacheKey := "my-key" cacheValue := []byte("my-cache-value") - client := &MockBigcacheClientInterface{} + client := &mocksStore.BigcacheClientInterface{} client.On("Set", cacheKey, cacheValue).Return(nil) client.On("Get", "gocache_tag_tag1").Return(nil, nil) client.On("Set", "gocache_tag_tag1", []byte("my-key")).Return(nil) @@ -74,11 +117,30 @@ func TestBigcacheSetWithTags(t *testing.T) { assert.Nil(t, err) } +func TestBigcacheSetWithTagsWhenAlreadyInserted(t *testing.T) { + // Given + cacheKey := "my-key" + cacheValue := []byte("my-cache-value") + + client := &mocksStore.BigcacheClientInterface{} + client.On("Set", cacheKey, cacheValue).Return(nil) + client.On("Get", "gocache_tag_tag1").Return([]byte("my-key,a-second-key"), nil) + client.On("Set", "gocache_tag_tag1", []byte("my-key,a-second-key")).Return(nil) + + store := NewBigcache(client, nil) + + // When + err := store.Set(cacheKey, cacheValue, &Options{Tags: []string{"tag1"}}) + + // Then + assert.Nil(t, err) +} + func TestBigcacheDelete(t *testing.T) { // Given cacheKey := "my-key" - client := &MockBigcacheClientInterface{} + client := &mocksStore.BigcacheClientInterface{} client.On("Delete", cacheKey).Return(nil) store := NewBigcache(client, nil) @@ -96,7 +158,7 @@ func TestBigcacheDeleteWhenError(t *testing.T) { cacheKey := "my-key" - client := &MockBigcacheClientInterface{} + client := &mocksStore.BigcacheClientInterface{} client.On("Delete", cacheKey).Return(expectedErr) store := NewBigcache(client, nil) @@ -116,7 +178,7 @@ func TestBigcacheInvalidate(t *testing.T) { cacheKeys := []byte("a23fdf987h2svc23,jHG2372x38hf74") - client := &MockBigcacheClientInterface{} + client := &mocksStore.BigcacheClientInterface{} client.On("Get", "gocache_tag_tag1").Return(cacheKeys, nil) client.On("Delete", "a23fdf987h2svc23").Return(nil) client.On("Delete", "jHG2372x38hf74").Return(nil) @@ -138,7 +200,7 @@ func TestBigcacheInvalidateWhenError(t *testing.T) { cacheKeys := []byte("a23fdf987h2svc23,jHG2372x38hf74") - client := &MockBigcacheClientInterface{} + client := &mocksStore.BigcacheClientInterface{} client.On("Get", "gocache_tag_tag1").Return(cacheKeys, nil) client.On("Delete", "a23fdf987h2svc23").Return(errors.New("Unexpected error")) client.On("Delete", "jHG2372x38hf74").Return(nil) @@ -154,7 +216,7 @@ func TestBigcacheInvalidateWhenError(t *testing.T) { func TestBigcacheGetType(t *testing.T) { // Given - client := &MockBigcacheClientInterface{} + client := &mocksStore.BigcacheClientInterface{} store := NewBigcache(client, nil) diff --git a/store/memcache_test.go b/store/memcache_test.go index f0e5c43e..0cb65d34 100644 --- a/store/memcache_test.go +++ b/store/memcache_test.go @@ -6,13 +6,14 @@ import ( "time" "github.com/bradfitz/gomemcache/memcache" + mocksStore "github.com/eko/gocache/test/mocks/store/clients" "github.com/stretchr/testify/assert" mock "github.com/stretchr/testify/mock" ) func TestNewMemcache(t *testing.T) { // Given - client := &MockMemcacheClientInterface{} + client := &mocksStore.MemcacheClientInterface{} options := &Options{Expiration: 3 * time.Second} // When @@ -31,7 +32,7 @@ func TestMemcacheGet(t *testing.T) { cacheKey := "my-key" cacheValue := []byte("my-cache-value") - client := &MockMemcacheClientInterface{} + client := &mocksStore.MemcacheClientInterface{} client.On("Get", cacheKey).Return(&memcache.Item{ Value: cacheValue, }, nil) @@ -46,6 +47,27 @@ func TestMemcacheGet(t *testing.T) { assert.Equal(t, cacheValue, value) } +func TestMemcacheGetWhenError(t *testing.T) { + // Given + options := &Options{Expiration: 3 * time.Second} + + cacheKey := "my-key" + + expectedErr := errors.New("An unexpected error occurred") + + client := &mocksStore.MemcacheClientInterface{} + client.On("Get", cacheKey).Return(nil, expectedErr) + + store := NewMemcache(client, options) + + // When + value, err := store.Get(cacheKey) + + // Then + assert.Equal(t, expectedErr, err) + assert.Nil(t, value) +} + func TestMemcacheSet(t *testing.T) { // Given options := &Options{Expiration: 3 * time.Second} @@ -53,7 +75,7 @@ func TestMemcacheSet(t *testing.T) { cacheKey := "my-key" cacheValue := []byte("my-cache-value") - client := &MockMemcacheClientInterface{} + client := &mocksStore.MemcacheClientInterface{} client.On("Set", &memcache.Item{ Key: cacheKey, Value: cacheValue, @@ -71,12 +93,60 @@ func TestMemcacheSet(t *testing.T) { assert.Nil(t, err) } +func TestMemcacheSetWhenNoOptionsGiven(t *testing.T) { + // Given + options := &Options{Expiration: 3 * time.Second} + + cacheKey := "my-key" + cacheValue := []byte("my-cache-value") + + client := &mocksStore.MemcacheClientInterface{} + client.On("Set", &memcache.Item{ + Key: cacheKey, + Value: cacheValue, + Expiration: int32(3), + }).Return(nil) + + store := NewMemcache(client, options) + + // When + err := store.Set(cacheKey, cacheValue, nil) + + // Then + assert.Nil(t, err) +} + +func TestMemcacheSetWhenError(t *testing.T) { + // Given + options := &Options{Expiration: 3 * time.Second} + + cacheKey := "my-key" + cacheValue := []byte("my-cache-value") + + expectedErr := errors.New("An unexpected error occurred") + + client := &mocksStore.MemcacheClientInterface{} + client.On("Set", &memcache.Item{ + Key: cacheKey, + Value: cacheValue, + Expiration: int32(3), + }).Return(expectedErr) + + store := NewMemcache(client, options) + + // When + err := store.Set(cacheKey, cacheValue, nil) + + // Then + assert.Equal(t, expectedErr, err) +} + func TestMemcacheSetWithTags(t *testing.T) { // Given cacheKey := "my-key" cacheValue := []byte("my-cache-value") - client := &MockMemcacheClientInterface{} + client := &mocksStore.MemcacheClientInterface{} client.On("Set", mock.Anything).Return(nil) client.On("Get", "gocache_tag_tag1").Return(nil, nil) @@ -90,11 +160,32 @@ func TestMemcacheSetWithTags(t *testing.T) { client.AssertNumberOfCalls(t, "Set", 2) } +func TestMemcacheSetWithTagsWhenAlreadyInserted(t *testing.T) { + // Given + cacheKey := "my-key" + cacheValue := []byte("my-cache-value") + + client := &mocksStore.MemcacheClientInterface{} + client.On("Set", mock.Anything).Return(nil) + client.On("Get", "gocache_tag_tag1").Return(&memcache.Item{ + Value: []byte("my-key,a-second-key"), + }, nil) + + store := NewMemcache(client, nil) + + // When + err := store.Set(cacheKey, cacheValue, &Options{Tags: []string{"tag1"}}) + + // Then + assert.Nil(t, err) + client.AssertNumberOfCalls(t, "Set", 2) +} + func TestMemcacheDelete(t *testing.T) { // Given cacheKey := "my-key" - client := &MockMemcacheClientInterface{} + client := &mocksStore.MemcacheClientInterface{} client.On("Delete", cacheKey).Return(nil) store := NewMemcache(client, nil) @@ -112,7 +203,7 @@ func TestMemcacheDeleteWhenError(t *testing.T) { cacheKey := "my-key" - client := &MockMemcacheClientInterface{} + client := &mocksStore.MemcacheClientInterface{} client.On("Delete", cacheKey).Return(expectedErr) store := NewMemcache(client, nil) @@ -134,7 +225,7 @@ func TestMemcacheInvalidate(t *testing.T) { Value: []byte("a23fdf987h2svc23,jHG2372x38hf74"), } - client := &MockMemcacheClientInterface{} + client := &mocksStore.MemcacheClientInterface{} client.On("Get", "gocache_tag_tag1").Return(cacheKeys, nil) client.On("Delete", "a23fdf987h2svc23").Return(nil) client.On("Delete", "jHG2372x38hf74").Return(nil) @@ -158,7 +249,7 @@ func TestMemcacheInvalidateWhenError(t *testing.T) { Value: []byte("a23fdf987h2svc23,jHG2372x38hf74"), } - client := &MockMemcacheClientInterface{} + client := &mocksStore.MemcacheClientInterface{} client.On("Get", "gocache_tag_tag1").Return(cacheKeys, nil) client.On("Delete", "a23fdf987h2svc23").Return(errors.New("Unexpected error")) client.On("Delete", "jHG2372x38hf74").Return(nil) @@ -174,7 +265,7 @@ func TestMemcacheInvalidateWhenError(t *testing.T) { func TestMemcacheGetType(t *testing.T) { // Given - client := &MockMemcacheClientInterface{} + client := &mocksStore.MemcacheClientInterface{} store := NewMemcache(client, nil) diff --git a/store/redis_test.go b/store/redis_test.go index 04e737b2..232f69aa 100644 --- a/store/redis_test.go +++ b/store/redis_test.go @@ -4,13 +4,14 @@ import ( "testing" "time" + mocksStore "github.com/eko/gocache/test/mocks/store/clients" "github.com/go-redis/redis/v7" "github.com/stretchr/testify/assert" ) func TestNewRedis(t *testing.T) { // Given - client := &MockRedisClientInterface{} + client := &mocksStore.RedisClientInterface{} options := &Options{ Expiration: 6 * time.Second, } @@ -26,7 +27,7 @@ func TestNewRedis(t *testing.T) { func TestRedisGet(t *testing.T) { // Given - client := &MockRedisClientInterface{} + client := &mocksStore.RedisClientInterface{} client.On("Get", "my-key").Return(&redis.StringCmd{}) store := NewRedis(client, nil) @@ -47,7 +48,7 @@ func TestRedisSet(t *testing.T) { Expiration: 6 * time.Second, } - client := &MockRedisClientInterface{} + client := &mocksStore.RedisClientInterface{} client.On("Set", "my-key", cacheValue, 5*time.Second).Return(&redis.StatusCmd{}) store := NewRedis(client, options) @@ -61,12 +62,32 @@ func TestRedisSet(t *testing.T) { assert.Nil(t, err) } +func TestRedisSetWhenNoOptionsGiven(t *testing.T) { + // Given + cacheKey := "my-key" + cacheValue := "my-cache-value" + options := &Options{ + Expiration: 6 * time.Second, + } + + client := &mocksStore.RedisClientInterface{} + client.On("Set", "my-key", cacheValue, 6*time.Second).Return(&redis.StatusCmd{}) + + store := NewRedis(client, options) + + // When + err := store.Set(cacheKey, cacheValue, nil) + + // Then + assert.Nil(t, err) +} + func TestRedisSetWithTags(t *testing.T) { // Given cacheKey := "my-key" cacheValue := []byte("my-cache-value") - client := &MockRedisClientInterface{} + client := &mocksStore.RedisClientInterface{} client.On("Set", cacheKey, cacheValue, time.Duration(0)).Return(&redis.StatusCmd{}) client.On("Get", "gocache_tag_tag1").Return(&redis.StringCmd{}) client.On("Set", "gocache_tag_tag1", []byte("my-key"), 720*time.Hour).Return(&redis.StatusCmd{}) @@ -84,7 +105,7 @@ func TestRedisDelete(t *testing.T) { // Given cacheKey := "my-key" - client := &MockRedisClientInterface{} + client := &mocksStore.RedisClientInterface{} client.On("Del", "my-key").Return(&redis.IntCmd{}) store := NewRedis(client, nil) @@ -104,7 +125,7 @@ func TestRedisInvalidate(t *testing.T) { cacheKeys := &redis.StringCmd{} - client := &MockRedisClientInterface{} + client := &mocksStore.RedisClientInterface{} client.On("Get", "gocache_tag_tag1").Return(cacheKeys, nil) store := NewRedis(client, nil) @@ -118,7 +139,7 @@ func TestRedisInvalidate(t *testing.T) { func TestRedisGetType(t *testing.T) { // Given - client := &MockRedisClientInterface{} + client := &mocksStore.RedisClientInterface{} store := NewRedis(client, nil) diff --git a/store/ristretto_test.go b/store/ristretto_test.go index 17c1111f..a69ecc06 100644 --- a/store/ristretto_test.go +++ b/store/ristretto_test.go @@ -1,14 +1,17 @@ package store import ( + "errors" + "fmt" "testing" + mocksStore "github.com/eko/gocache/test/mocks/store/clients" "github.com/stretchr/testify/assert" ) func TestNewRistretto(t *testing.T) { // Given - client := &MockRistrettoClientInterface{} + client := &mocksStore.RistrettoClientInterface{} options := &Options{ Cost: 8, } @@ -27,7 +30,7 @@ func TestRistrettoGet(t *testing.T) { cacheKey := "my-key" cacheValue := "my-cache-value" - client := &MockRistrettoClientInterface{} + client := &mocksStore.RistrettoClientInterface{} client.On("Get", cacheKey).Return(cacheValue, true) store := NewRistretto(client, nil) @@ -40,6 +43,23 @@ func TestRistrettoGet(t *testing.T) { assert.Equal(t, cacheValue, value) } +func TestRistrettoGetWhenError(t *testing.T) { + // Given + cacheKey := "my-key" + + client := &mocksStore.RistrettoClientInterface{} + client.On("Get", cacheKey).Return(nil, false) + + store := NewRistretto(client, nil) + + // When + value, err := store.Get(cacheKey) + + // Then + assert.Nil(t, value) + assert.Equal(t, errors.New("Value not found in Ristretto store"), err) +} + func TestRistrettoSet(t *testing.T) { // Given cacheKey := "my-key" @@ -48,7 +68,7 @@ func TestRistrettoSet(t *testing.T) { Cost: 7, } - client := &MockRistrettoClientInterface{} + client := &mocksStore.RistrettoClientInterface{} client.On("Set", cacheKey, cacheValue, int64(4)).Return(true) store := NewRistretto(client, options) @@ -62,12 +82,52 @@ func TestRistrettoSet(t *testing.T) { assert.Nil(t, err) } +func TestRistrettoSetWhenNoOptionsGiven(t *testing.T) { + // Given + cacheKey := "my-key" + cacheValue := "my-cache-value" + options := &Options{ + Cost: 7, + } + + client := &mocksStore.RistrettoClientInterface{} + client.On("Set", cacheKey, cacheValue, int64(7)).Return(true) + + store := NewRistretto(client, options) + + // When + err := store.Set(cacheKey, cacheValue, nil) + + // Then + assert.Nil(t, err) +} + +func TestRistrettoSetWhenError(t *testing.T) { + // Given + cacheKey := "my-key" + cacheValue := "my-cache-value" + options := &Options{ + Cost: 7, + } + + client := &mocksStore.RistrettoClientInterface{} + client.On("Set", cacheKey, cacheValue, int64(7)).Return(false) + + store := NewRistretto(client, options) + + // When + err := store.Set(cacheKey, cacheValue, nil) + + // Then + assert.Equal(t, fmt.Errorf("An error has occurred while setting value '%v' on key '%v'", cacheValue, cacheKey), err) +} + func TestRistrettoSetWithTags(t *testing.T) { // Given cacheKey := "my-key" cacheValue := []byte("my-cache-value") - client := &MockRistrettoClientInterface{} + client := &mocksStore.RistrettoClientInterface{} client.On("Set", cacheKey, cacheValue, int64(0)).Return(true) client.On("Get", "gocache_tag_tag1").Return(nil, true) client.On("Set", "gocache_tag_tag1", []byte("my-key"), int64(0)).Return(true) @@ -81,11 +141,30 @@ func TestRistrettoSetWithTags(t *testing.T) { assert.Nil(t, err) } +func TestRistrettoSetWithTagsWhenAlreadyInserted(t *testing.T) { + // Given + cacheKey := "my-key" + cacheValue := []byte("my-cache-value") + + client := &mocksStore.RistrettoClientInterface{} + client.On("Set", cacheKey, cacheValue, int64(0)).Return(true) + client.On("Get", "gocache_tag_tag1").Return([]byte("my-key,a-second-key"), true) + client.On("Set", "gocache_tag_tag1", []byte("my-key,a-second-key"), int64(0)).Return(true) + + store := NewRistretto(client, nil) + + // When + err := store.Set(cacheKey, cacheValue, &Options{Tags: []string{"tag1"}}) + + // Then + assert.Nil(t, err) +} + func TestRistrettoDelete(t *testing.T) { // Given cacheKey := "my-key" - client := &MockRistrettoClientInterface{} + client := &mocksStore.RistrettoClientInterface{} client.On("Del", cacheKey).Return(nil) store := NewRistretto(client, nil) @@ -105,7 +184,7 @@ func TestRistrettoInvalidate(t *testing.T) { cacheKeys := []byte("a23fdf987h2svc23,jHG2372x38hf74") - client := &MockRistrettoClientInterface{} + client := &mocksStore.RistrettoClientInterface{} client.On("Get", "gocache_tag_tag1").Return(cacheKeys, true) client.On("Del", "a23fdf987h2svc23").Return(nil) client.On("Del", "jHG2372x38hf74").Return(nil) @@ -127,7 +206,7 @@ func TestRistrettoInvalidateWhenError(t *testing.T) { cacheKeys := []byte("a23fdf987h2svc23,jHG2372x38hf74") - client := &MockRistrettoClientInterface{} + client := &mocksStore.RistrettoClientInterface{} client.On("Get", "gocache_tag_tag1").Return(cacheKeys, false) client.On("Del", "a23fdf987h2svc23").Return(nil) client.On("Del", "jHG2372x38hf74").Return(nil) @@ -143,7 +222,7 @@ func TestRistrettoInvalidateWhenError(t *testing.T) { func TestRistrettoGetType(t *testing.T) { // Given - client := &MockRistrettoClientInterface{} + client := &mocksStore.RistrettoClientInterface{} store := NewRistretto(client, nil) diff --git a/store/mock_bigcache_client_interface.go b/test/mocks/store/clients/bigcache_client_interface.go similarity index 71% rename from store/mock_bigcache_client_interface.go rename to test/mocks/store/clients/bigcache_client_interface.go index c68e40b1..3969f4a1 100644 --- a/store/mock_bigcache_client_interface.go +++ b/test/mocks/store/clients/bigcache_client_interface.go @@ -1,16 +1,16 @@ // Code generated by mockery v1.0.0. DO NOT EDIT. -package store +package mocks import mock "github.com/stretchr/testify/mock" -// MockBigcacheClientInterface is an autogenerated mock type for the BigcacheClientInterface type -type MockBigcacheClientInterface struct { +// BigcacheClientInterface is an autogenerated mock type for the BigcacheClientInterface type +type BigcacheClientInterface struct { mock.Mock } // Delete provides a mock function with given fields: key -func (_m *MockBigcacheClientInterface) Delete(key string) error { +func (_m *BigcacheClientInterface) Delete(key string) error { ret := _m.Called(key) var r0 error @@ -24,7 +24,7 @@ func (_m *MockBigcacheClientInterface) Delete(key string) error { } // Get provides a mock function with given fields: key -func (_m *MockBigcacheClientInterface) Get(key string) ([]byte, error) { +func (_m *BigcacheClientInterface) Get(key string) ([]byte, error) { ret := _m.Called(key) var r0 []byte @@ -47,7 +47,7 @@ func (_m *MockBigcacheClientInterface) Get(key string) ([]byte, error) { } // Set provides a mock function with given fields: key, entry -func (_m *MockBigcacheClientInterface) Set(key string, entry []byte) error { +func (_m *BigcacheClientInterface) Set(key string, entry []byte) error { ret := _m.Called(key, entry) var r0 error diff --git a/store/mock_memcache_client_interface.go b/test/mocks/store/clients/memcache_client_interface.go similarity index 72% rename from store/mock_memcache_client_interface.go rename to test/mocks/store/clients/memcache_client_interface.go index 31b47c45..c8974c77 100644 --- a/store/mock_memcache_client_interface.go +++ b/test/mocks/store/clients/memcache_client_interface.go @@ -1,17 +1,17 @@ // Code generated by mockery v1.0.0. DO NOT EDIT. -package store +package mocks import memcache "github.com/bradfitz/gomemcache/memcache" import mock "github.com/stretchr/testify/mock" -// MockMemcacheClientInterface is an autogenerated mock type for the MemcacheClientInterface type -type MockMemcacheClientInterface struct { +// MemcacheClientInterface is an autogenerated mock type for the MemcacheClientInterface type +type MemcacheClientInterface struct { mock.Mock } // Delete provides a mock function with given fields: item -func (_m *MockMemcacheClientInterface) Delete(item string) error { +func (_m *MemcacheClientInterface) Delete(item string) error { ret := _m.Called(item) var r0 error @@ -25,7 +25,7 @@ func (_m *MockMemcacheClientInterface) Delete(item string) error { } // Get provides a mock function with given fields: key -func (_m *MockMemcacheClientInterface) Get(key string) (*memcache.Item, error) { +func (_m *MemcacheClientInterface) Get(key string) (*memcache.Item, error) { ret := _m.Called(key) var r0 *memcache.Item @@ -48,7 +48,7 @@ func (_m *MockMemcacheClientInterface) Get(key string) (*memcache.Item, error) { } // Set provides a mock function with given fields: item -func (_m *MockMemcacheClientInterface) Set(item *memcache.Item) error { +func (_m *MemcacheClientInterface) Set(item *memcache.Item) error { ret := _m.Called(item) var r0 error diff --git a/store/mock_redis_client_interface.go b/test/mocks/store/clients/redis_client_interface.go similarity index 75% rename from store/mock_redis_client_interface.go rename to test/mocks/store/clients/redis_client_interface.go index 7c52a2ed..32ac7909 100644 --- a/store/mock_redis_client_interface.go +++ b/test/mocks/store/clients/redis_client_interface.go @@ -1,18 +1,19 @@ // Code generated by mockery v1.0.0. DO NOT EDIT. -package store +package mocks import mock "github.com/stretchr/testify/mock" import redis "github.com/go-redis/redis/v7" + import time "time" -// MockRedisClientInterface is an autogenerated mock type for the RedisClientInterface type -type MockRedisClientInterface struct { +// RedisClientInterface is an autogenerated mock type for the RedisClientInterface type +type RedisClientInterface struct { mock.Mock } // Del provides a mock function with given fields: keys -func (_m *MockRedisClientInterface) Del(keys ...string) *redis.IntCmd { +func (_m *RedisClientInterface) Del(keys ...string) *redis.IntCmd { _va := make([]interface{}, len(keys)) for _i := range keys { _va[_i] = keys[_i] @@ -34,7 +35,7 @@ func (_m *MockRedisClientInterface) Del(keys ...string) *redis.IntCmd { } // Get provides a mock function with given fields: key -func (_m *MockRedisClientInterface) Get(key string) *redis.StringCmd { +func (_m *RedisClientInterface) Get(key string) *redis.StringCmd { ret := _m.Called(key) var r0 *redis.StringCmd @@ -50,7 +51,7 @@ func (_m *MockRedisClientInterface) Get(key string) *redis.StringCmd { } // Set provides a mock function with given fields: key, value, expiration -func (_m *MockRedisClientInterface) Set(key string, value interface{}, expiration time.Duration) *redis.StatusCmd { +func (_m *RedisClientInterface) Set(key string, value interface{}, expiration time.Duration) *redis.StatusCmd { ret := _m.Called(key, value, expiration) var r0 *redis.StatusCmd diff --git a/store/mock_ristretto_client_interface.go b/test/mocks/store/clients/ristretto_client_interface.go similarity index 68% rename from store/mock_ristretto_client_interface.go rename to test/mocks/store/clients/ristretto_client_interface.go index 5d55479e..4c7eb23b 100644 --- a/store/mock_ristretto_client_interface.go +++ b/test/mocks/store/clients/ristretto_client_interface.go @@ -1,21 +1,21 @@ // Code generated by mockery v1.0.0. DO NOT EDIT. -package store +package mocks import mock "github.com/stretchr/testify/mock" -// MockRistrettoClientInterface is an autogenerated mock type for the RistrettoClientInterface type -type MockRistrettoClientInterface struct { +// RistrettoClientInterface is an autogenerated mock type for the RistrettoClientInterface type +type RistrettoClientInterface struct { mock.Mock } // Del provides a mock function with given fields: key -func (_m *MockRistrettoClientInterface) Del(key interface{}) { +func (_m *RistrettoClientInterface) Del(key interface{}) { _m.Called(key) } // Get provides a mock function with given fields: key -func (_m *MockRistrettoClientInterface) Get(key interface{}) (interface{}, bool) { +func (_m *RistrettoClientInterface) Get(key interface{}) (interface{}, bool) { ret := _m.Called(key) var r0 interface{} @@ -38,7 +38,7 @@ func (_m *MockRistrettoClientInterface) Get(key interface{}) (interface{}, bool) } // Set provides a mock function with given fields: key, value, cost -func (_m *MockRistrettoClientInterface) Set(key interface{}, value interface{}, cost int64) bool { +func (_m *RistrettoClientInterface) Set(key interface{}, value interface{}, cost int64) bool { ret := _m.Called(key, value, cost) var r0 bool