diff --git a/pkg/cache/interface.go b/pkg/cache/interface.go index a50a2e0..7318e86 100644 --- a/pkg/cache/interface.go +++ b/pkg/cache/interface.go @@ -17,6 +17,8 @@ type Interface interface { Set(ctx context.Context, k string, v []byte, ttl time.Duration) error // Get the key value from cache Get(ctx context.Context, k string) ([]byte, error) + // Del delete the key from cache + Del(ctx context.Context, k string) error // Close the cache Close() error } diff --git a/pkg/cache/local_cache.go b/pkg/cache/local_cache.go index eaee708..043d334 100644 --- a/pkg/cache/local_cache.go +++ b/pkg/cache/local_cache.go @@ -59,6 +59,19 @@ func (l *localCache) Get(_ context.Context, k string) ([]byte, error) { return v, err } +func (l *localCache) Del(_ context.Context, k string) error { + err := l.cache.Delete(k) + if err != nil { + if errors.Is(err, bigcache.ErrEntryNotFound) { + return nil + } + + return err + } + + return nil +} + // Close the cache func (l *localCache) Close() error { return l.cache.Close() diff --git a/pkg/cache/local_cache_test.go b/pkg/cache/local_cache_test.go index 8b3f02b..4e42f61 100644 --- a/pkg/cache/local_cache_test.go +++ b/pkg/cache/local_cache_test.go @@ -97,3 +97,27 @@ func Test_localCache_Get_Large(t *testing.T) { require.Equal(t, v, got) } } + +func Test_localCache_Del(t *testing.T) { + c, err := newLocalCache(tests.GlobalConfig.Cache.LocalCache) + require.NoError(t, err) + t.Cleanup(func() { + c.Close() + }) + + k, v := "key", []byte("value") + + t.Run("del", func(t *testing.T) { + require.NoError(t, c.Set(context.Background(), k, v, 10*time.Minute)) + + require.NoError(t, c.Del(context.Background(), k)) + + got, err := c.Get(context.Background(), k) + require.ErrorIs(t, err, ErrCacheMiss) + require.Nil(t, got) + }) + + t.Run("del_not_exist", func(t *testing.T) { + require.NoError(t, c.Del(context.Background(), "not_exist")) + }) +} diff --git a/pkg/cache/proxy.go b/pkg/cache/proxy.go index 93bd380..db18a1a 100644 --- a/pkg/cache/proxy.go +++ b/pkg/cache/proxy.go @@ -77,6 +77,18 @@ func (p *proxy) Get(ctx context.Context, k string) ([]byte, error) { return long, err } +func (p *proxy) Del(ctx context.Context, k string) error { + if err := p.distributedCache.Del(ctx, k); err != nil { + return fmt.Errorf("failed to delete distributed cache: %w", err) + } + + if err := p.localCache.Del(ctx, k); err != nil { + return fmt.Errorf("failed to delete local cache: %w", err) + } + + return nil +} + func (p *proxy) Close() error { if err := p.distributedCache.Close(); err != nil { return err diff --git a/pkg/cache/proxy_test.go b/pkg/cache/proxy_test.go index 6bed935..5c84f37 100644 --- a/pkg/cache/proxy_test.go +++ b/pkg/cache/proxy_test.go @@ -2,12 +2,14 @@ package cache import ( "context" + "errors" "testing" "time" "github.com/stretchr/testify/require" "github.com/beihai0xff/turl/internal/tests" + "github.com/beihai0xff/turl/internal/tests/mocks" ) func TestProxySet(t *testing.T) { @@ -47,6 +49,50 @@ func TestProxyGet(t *testing.T) { require.Equal(t, v, got) } +func TestProxyDel(t *testing.T) { + c, err := newProxy(tests.GlobalConfig.Cache) + require.NoError(t, err) + + k, v := "key", []byte("value") + + ctx := context.Background() + t.Run("del", func(t *testing.T) { + require.NoError(t, c.Set(ctx, k, v, 10*time.Minute)) + + require.NoError(t, c.Del(ctx, k)) + + got, err := c.Get(ctx, k) + require.ErrorIs(t, err, ErrCacheMiss) + require.Nil(t, got) + }) + + t.Run("del_not_exist", func(t *testing.T) { + require.NoError(t, c.Del(ctx, "not_exist"), ErrCacheMiss) + }) + + lc, rc := mocks.NewMockCache(t), mocks.NewMockCache(t) + c = &proxy{ + localCache: lc, + distributedCache: rc, + remoteCacheTTL: time.Minute, + localCacheTTL: time.Minute, + } + + testError := errors.New("test error") + t.Run("del_remote_cache_error", func(t *testing.T) { + rc.EXPECT().Del(ctx, k).Return(testError).Times(1) + + require.ErrorIs(t, c.Del(ctx, k), testError) + }) + + t.Run("del_local_cache_error", func(t *testing.T) { + rc.EXPECT().Del(ctx, k).Return(nil).Times(1) + lc.EXPECT().Del(ctx, k).Return(testError).Times(1) + + require.ErrorIs(t, c.Del(ctx, k), testError) + }) +} + func TestProxyClose(t *testing.T) { p, err := newProxy(tests.GlobalConfig.Cache) require.NoError(t, err) diff --git a/pkg/cache/redis_remote_cache.go b/pkg/cache/redis_remote_cache.go index 463bae2..6294c52 100644 --- a/pkg/cache/redis_remote_cache.go +++ b/pkg/cache/redis_remote_cache.go @@ -49,6 +49,10 @@ func (c *redisCache) Get(ctx context.Context, k string) ([]byte, error) { return value, err } +func (c *redisCache) Del(ctx context.Context, k string) error { + return c.rdb.Del(ctx, k).Err() +} + // Close the cache func (c *redisCache) Close() error { return c.rdb.Close() diff --git a/pkg/cache/redis_remote_cache_test.go b/pkg/cache/redis_remote_cache_test.go index 7763dcc..5aafbbf 100644 --- a/pkg/cache/redis_remote_cache_test.go +++ b/pkg/cache/redis_remote_cache_test.go @@ -51,3 +51,27 @@ func Test_redisCache_Get(t *testing.T) { require.ErrorIs(t, err, ErrCacheMiss) require.Nil(t, got) } + +func Test_redisCache_Del(t *testing.T) { + c := newRedisCache(tests.GlobalConfig.Cache.Redis) + t.Cleanup( + func() { + c.Close() + }) + + k, v := "key", []byte("value") + + t.Run("del", func(t *testing.T) { + require.NoError(t, c.Set(context.Background(), k, v, 10*time.Minute)) + + require.NoError(t, c.Del(context.Background(), k)) + + got, err := c.Get(context.Background(), k) + require.ErrorIs(t, err, ErrCacheMiss) + require.Nil(t, got) + }) + + t.Run("del_not_exist", func(t *testing.T) { + require.NoError(t, c.Del(context.Background(), "not_exist"), ErrCacheMiss) + }) +}