package cachekvstore import ( "context" "encoding/json" "fmt" "sort" "testing" "time" "github.com/stretchr/testify/require" "github.com/grafana/grafana/pkg/infra/kvstore" ) func TestNamespacedStore(t *testing.T) { const namespace = "namespace" t.Run("simple", func(t *testing.T) { store := NewCacheKvStore(kvstore.NewFakeKVStore(), namespace) t.Run("default last updated time is zero", func(t *testing.T) { ts, err := store.GetLastUpdated(context.Background()) require.NoError(t, err) require.Zero(t, ts) }) t.Run("Get returns false if key does not exist", func(t *testing.T) { _, ok, err := store.Get(context.Background(), "key") require.NoError(t, err) require.False(t, ok) }) t.Run("Set sets the value and updates the last updated time", func(t *testing.T) { ts, err := store.GetLastUpdated(context.Background()) require.NoError(t, err) require.Zero(t, ts) require.NoError(t, store.Set(context.Background(), "key", "value")) ts, err = store.GetLastUpdated(context.Background()) require.NoError(t, err) require.NotZero(t, ts) require.WithinDuration(t, ts, time.Now(), time.Second*10) v, ok, err := store.Get(context.Background(), "key") require.NoError(t, err) require.True(t, ok) require.Equal(t, "value", v) }) t.Run("Delete deletes the value", func(t *testing.T) { // First store require.NoError(t, store.Set(context.Background(), "key", "value")) // Then read it v, ok, err := store.Get(context.Background(), "key") require.NoError(t, err) require.True(t, ok) require.Equal(t, "value", v) // Delete it require.NoError(t, store.Delete(context.Background(), "key")) // Read it again _, ok, err = store.Get(context.Background(), "key") require.NoError(t, err) require.False(t, ok) }) t.Run("sets last updated on delete", func(t *testing.T) { store := NewCacheKvStore(kvstore.NewFakeKVStore(), namespace) ts, err := store.GetLastUpdated(context.Background()) require.NoError(t, err) require.Zero(t, ts) require.NoError(t, store.Delete(context.Background(), "key")) ts, err = store.GetLastUpdated(context.Background()) require.NoError(t, err) require.WithinDuration(t, time.Now(), ts, time.Second*10) }) t.Run("last updated key is used in GetLastUpdated", func(t *testing.T) { store := NewCacheKvStore(kvstore.NewFakeKVStore(), namespace) // Set in underlying store ts := time.Now() require.NoError(t, store.kv.Set(context.Background(), keyLastUpdated, ts.Format(time.RFC3339))) // Make sure we get the same value storeTs, err := store.GetLastUpdated(context.Background()) require.NoError(t, err) // Format to account for marshal/unmarshal differences require.Equal(t, ts.Format(time.RFC3339), storeTs.Format(time.RFC3339)) }) t.Run("last updated key is used in SetLastUpdated", func(t *testing.T) { store := NewCacheKvStore(kvstore.NewFakeKVStore(), namespace) require.NoError(t, store.SetLastUpdated(context.Background())) marshaledStoreTs, ok, err := store.kv.Get(context.Background(), keyLastUpdated) require.NoError(t, err) require.True(t, ok) storeTs, err := time.Parse(time.RFC3339, marshaledStoreTs) require.NoError(t, err) require.WithinDuration(t, time.Now(), storeTs, time.Second*10) }) t.Run("ListKeys", func(t *testing.T) { t.Run("returns empty list if no keys", func(t *testing.T) { keys, err := store.ListKeys(context.Background()) require.NoError(t, err) require.Empty(t, keys) }) t.Run("returns the keys", func(t *testing.T) { expectedKeys := make([]string, 0, 10) for i := 0; i < 10; i++ { k := fmt.Sprintf("key-%d", i) err := store.Set(context.Background(), k, fmt.Sprintf("value-%d", i)) expectedKeys = append(expectedKeys, k) require.NoError(t, err) } keys, err := store.ListKeys(context.Background()) require.NoError(t, err) sort.Strings(expectedKeys) sort.Strings(keys) require.Equal(t, expectedKeys, keys) }) }) }) t.Run("prefix", func(t *testing.T) { t.Run("no prefix", func(t *testing.T) { store := NewCacheKvStore(kvstore.NewFakeKVStore(), namespace) require.Equal(t, "k", store.storeKey("k")) }) t.Run("prefix", func(t *testing.T) { store := NewCacheKvStoreWithPrefix(kvstore.NewFakeKVStore(), namespace, "my-") require.Equal(t, "my-k", store.storeKey("k")) }) }) } func TestMarshal(t *testing.T) { t.Run("json", func(t *testing.T) { // Other type (rather than string, []byte or fmt.Stringer) marshals to JSON. var value struct { A string `json:"a"` B string `json:"b"` } expV, err := json.Marshal(value) require.NoError(t, err) v, err := marshal(value) require.NoError(t, err) require.Equal(t, string(expV), v) }) t.Run("string", func(t *testing.T) { v, err := marshal("value") require.NoError(t, err) require.Equal(t, "value", v) }) t.Run("stringer", func(t *testing.T) { var s stringer v, err := marshal(s) require.NoError(t, err) require.Equal(t, s.String(), v) }) t.Run("byte slice", func(t *testing.T) { v, err := marshal([]byte("value")) require.NoError(t, err) require.Equal(t, "value", v) }) } type stringer struct{} func (s stringer) String() string { return "aaaa" }