grafana/pkg/services/annotations/annotationsimpl/composite_store_test.go
William Wernert 62bdbe5b44
Annotations/Alerting: Add Loki historian store stub (#78363)
* Add Loki historian store stub

* Add composite store

* Use composite store if Loki historian enabled

* Split store interface into read/write

* Make composite + historian stores read only

* Use variadic constructor for composite

* Modify Loki store enable logic

* Use dskit.concurrency.ForEachJob for parallelism
2023-12-12 17:43:09 -05:00

174 lines
3.6 KiB
Go

package annotationsimpl
import (
"context"
"errors"
"fmt"
"testing"
"time"
"github.com/grafana/grafana/pkg/services/annotations"
"github.com/grafana/grafana/pkg/services/annotations/accesscontrol"
"github.com/stretchr/testify/require"
)
var (
errGet = errors.New("get error")
errGetTags = errors.New("get tags error")
)
func TestCompositeStore(t *testing.T) {
t.Run("should return first error", func(t *testing.T) {
err1 := errors.New("error 1")
r1 := newFakeReader(withError(err1))
err2 := errors.New("error 2")
r2 := newFakeReader(withError(err2), withWait(10*time.Millisecond))
store := &CompositeStore{
[]readStore{r1, r2},
}
tc := []struct {
f func() (any, error)
err error
}{
{
f: func() (any, error) { return store.Get(context.Background(), nil, nil) },
err: errGet,
},
{
f: func() (any, error) { return store.GetTags(context.Background(), nil) },
err: errGetTags,
},
}
for _, tt := range tc {
_, err := tt.f()
require.Error(t, err)
require.ErrorIs(t, err, err1)
require.NotErrorIs(t, err, err2)
}
})
t.Run("should combine and sort results from Get", func(t *testing.T) {
items1 := []*annotations.ItemDTO{
{TimeEnd: 1, Time: 2},
{TimeEnd: 2, Time: 1},
}
r1 := newFakeReader(withItems(items1))
items2 := []*annotations.ItemDTO{
{TimeEnd: 1, Time: 1},
{TimeEnd: 1, Time: 3},
}
r2 := newFakeReader(withItems(items2))
store := &CompositeStore{
[]readStore{r1, r2},
}
expected := []*annotations.ItemDTO{
{TimeEnd: 2, Time: 1},
{TimeEnd: 1, Time: 3},
{TimeEnd: 1, Time: 2},
{TimeEnd: 1, Time: 1},
}
items, _ := store.Get(context.Background(), nil, nil)
require.Equal(t, expected, items)
})
t.Run("should combine and sort results from GetTags", func(t *testing.T) {
tags1 := []*annotations.TagsDTO{
{Tag: "key1:val1"},
{Tag: "key2:val1"},
}
r1 := newFakeReader(withTags(tags1))
tags2 := []*annotations.TagsDTO{
{Tag: "key1:val2"},
{Tag: "key2:val2"},
}
r2 := newFakeReader(withTags(tags2))
store := &CompositeStore{
[]readStore{r1, r2},
}
expected := []*annotations.TagsDTO{
{Tag: "key1:val1"},
{Tag: "key1:val2"},
{Tag: "key2:val1"},
{Tag: "key2:val2"},
}
res, _ := store.GetTags(context.Background(), nil)
require.Equal(t, expected, res.Tags)
})
}
type fakeReader struct {
items []*annotations.ItemDTO
tagRes annotations.FindTagsResult
wait time.Duration
err error
}
func (f *fakeReader) Get(ctx context.Context, query *annotations.ItemQuery, accessResources *accesscontrol.AccessResources) ([]*annotations.ItemDTO, error) {
if f.wait > 0 {
time.Sleep(f.wait)
}
if f.err != nil {
err := fmt.Errorf("%w: %w", errGet, f.err)
return nil, err
}
return f.items, nil
}
func (f *fakeReader) GetTags(ctx context.Context, query *annotations.TagsQuery) (annotations.FindTagsResult, error) {
if f.wait > 0 {
time.Sleep(f.wait)
}
if f.err != nil {
err := fmt.Errorf("%w: %w", errGetTags, f.err)
return annotations.FindTagsResult{}, err
}
return f.tagRes, nil
}
func withWait(wait time.Duration) func(*fakeReader) {
return func(f *fakeReader) {
f.wait = wait
}
}
func withError(err error) func(*fakeReader) {
return func(f *fakeReader) {
f.err = err
}
}
func withItems(items []*annotations.ItemDTO) func(*fakeReader) {
return func(f *fakeReader) {
f.items = items
}
}
func withTags(tags []*annotations.TagsDTO) func(*fakeReader) {
return func(f *fakeReader) {
f.tagRes = annotations.FindTagsResult{Tags: tags}
}
}
func newFakeReader(opts ...func(*fakeReader)) *fakeReader {
f := &fakeReader{}
for _, opt := range opts {
opt(f)
}
return f
}