The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
grafana/pkg/services/pluginsintegration/cachekvstore/cachekvstore_test.go

188 lines
5.2 KiB

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"
}