add storage test

This commit is contained in:
Todd Treece
2025-12-11 21:22:28 -05:00
parent 306186c4ea
commit 07bb48e874
+249
View File
@@ -0,0 +1,249 @@
package app
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"slices"
"strings"
"testing"
"github.com/grafana/grafana-app-sdk/resource"
"github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apiserver/pkg/endpoints/request"
pluginsv0alpha1 "github.com/grafana/grafana/apps/plugins/pkg/apis/plugins/v0alpha1"
"github.com/grafana/grafana/apps/plugins/pkg/app/meta"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore"
)
func TestMetaStorageListPreload(t *testing.T) {
ctx := request.WithNamespace(context.Background(), "default")
preloadPlugin := pluginstore.Plugin{
JSONData: plugins.JSONData{
ID: "test-plugin",
Name: "Test Plugin",
Type: plugins.TypeDataSource,
Info: plugins.Info{Version: "1.0.0"},
Preload: true,
},
}
nonPreloadPlugin := pluginstore.Plugin{
JSONData: plugins.JSONData{
ID: "test-plugin-2",
Name: "Test Plugin 2",
Type: plugins.TypeDataSource,
Info: plugins.Info{Version: "1.0.0"},
Preload: false,
},
}
store := &mockPluginStore{plugins: map[string]pluginstore.Plugin{
"test-plugin": preloadPlugin,
}}
store2 := &mockPluginStore{plugins: map[string]pluginstore.Plugin{
"test-plugin-2": nonPreloadPlugin,
}}
catalogServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
require.Equal(t, http.MethodGet, r.Method)
require.Equal(t, "application/json", r.Header.Get("Accept"))
require.Equal(t, "grafana-plugins-app", r.Header.Get("User-Agent"))
segments := strings.Split(strings.Trim(r.URL.Path, "/"), "/")
require.Len(t, segments, 5)
require.Equal(t, "api", segments[0])
require.Equal(t, "plugins", segments[1])
require.Equal(t, "versions", segments[3])
preload := true
response := struct {
PluginID string `json:"pluginSlug"`
Version string `json:"version"`
JSON pluginsv0alpha1.MetaJSONData `json:"json"`
}{
PluginID: segments[2],
Version: segments[4],
JSON: pluginsv0alpha1.MetaJSONData{
Id: segments[2],
Name: segments[2],
Type: pluginsv0alpha1.MetaJSONDataTypeDatasource,
Preload: &preload,
},
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
require.NoError(t, json.NewEncoder(w).Encode(response))
}))
defer catalogServer.Close()
provider := meta.NewLocalProvider(store, mockPluginAssets{})
provider2 := meta.NewLocalProvider(store2, mockPluginAssets{})
catalogProvider := meta.NewCatalogProvider(catalogServer.URL + "/api/plugins")
metaManager := meta.NewProviderManager(provider2, provider, catalogProvider)
pluginClient := pluginsv0alpha1.NewPluginClient(&mockResourceClient{
listFunc: func(ctx context.Context, namespace string, opts resource.ListOptions) (resource.ListObject, error) {
return newPluginList(), nil
},
})
storage := NewMetaStorage(metaManager, func(ctx context.Context) (*pluginsv0alpha1.PluginClient, error) {
return pluginClient, nil
})
obj, err := storage.List(ctx, nil)
require.NoError(t, err)
metaList, ok := obj.(*pluginsv0alpha1.MetaList)
require.True(t, ok)
require.Len(t, metaList.Items, 3)
require.NotNil(t, metaList.Items[0].Spec.PluginJson.Preload)
require.True(t, *metaList.Items[0].Spec.PluginJson.Preload)
require.NotNil(t, metaList.Items[1].Spec.PluginJson.Preload)
require.True(t, *metaList.Items[1].Spec.PluginJson.Preload)
require.Nil(t, metaList.Items[2].Spec.PluginJson.Preload)
obj, err = storage.List(ctx, nil)
require.NoError(t, err)
metaList, ok = obj.(*pluginsv0alpha1.MetaList)
require.True(t, ok)
require.Len(t, metaList.Items, 3)
require.NotNil(t, metaList.Items[0].Spec.PluginJson.Preload)
require.True(t, *metaList.Items[0].Spec.PluginJson.Preload)
require.NotNil(t, metaList.Items[1].Spec.PluginJson.Preload)
require.True(t, *metaList.Items[1].Spec.PluginJson.Preload)
require.Nil(t, metaList.Items[2].Spec.PluginJson.Preload)
}
type mockPluginAssets struct{}
func (mockPluginAssets) LoadingStrategy(ctx context.Context, p pluginstore.Plugin) plugins.LoadingStrategy {
return plugins.LoadingStrategyFetch
}
func (mockPluginAssets) ModuleHash(ctx context.Context, p pluginstore.Plugin) string {
return "hash"
}
type mockPluginStore struct {
plugins map[string]pluginstore.Plugin
}
func (m *mockPluginStore) Plugin(ctx context.Context, pluginID string) (pluginstore.Plugin, bool) {
if m.plugins[pluginID].ID != pluginID {
return pluginstore.Plugin{}, false
}
return m.plugins[pluginID], true
}
func (m *mockPluginStore) Plugins(ctx context.Context, pluginTypes ...plugins.Type) []pluginstore.Plugin {
result := []pluginstore.Plugin{}
for _, plugin := range m.plugins {
if len(pluginTypes) == 0 || slices.Contains(pluginTypes, plugin.Type) {
result = append(result, plugin)
}
}
return result
}
func newPluginList() *pluginsv0alpha1.PluginList {
return &pluginsv0alpha1.PluginList{
Items: []pluginsv0alpha1.Plugin{
{
ObjectMeta: metav1.ObjectMeta{Name: "grafana-plugins-app", Namespace: "org-1"},
Spec: pluginsv0alpha1.PluginSpec{Id: "grafana-plugins-app", Version: "1.0.0"},
},
{
ObjectMeta: metav1.ObjectMeta{Name: "test-plugin", Namespace: "org-1"},
Spec: pluginsv0alpha1.PluginSpec{Id: "test-plugin", Version: "1.0.0"},
},
{
ObjectMeta: metav1.ObjectMeta{Name: "test-plugin-2", Namespace: "org-1"},
Spec: pluginsv0alpha1.PluginSpec{Id: "test-plugin-2", Version: "1.0.0"},
},
},
}
}
type mockResourceClient struct {
listFunc func(ctx context.Context, namespace string, opts resource.ListOptions) (resource.ListObject, error)
}
func (m *mockResourceClient) List(ctx context.Context, namespace string, opts resource.ListOptions) (resource.ListObject, error) {
if m.listFunc != nil {
return m.listFunc(ctx, namespace, opts)
}
return &pluginsv0alpha1.PluginList{}, nil
}
func (m *mockResourceClient) ListInto(ctx context.Context, namespace string, opts resource.ListOptions, into resource.ListObject) error {
list, err := m.List(ctx, namespace, opts)
if err != nil {
return err
}
if src, ok := list.(*pluginsv0alpha1.PluginList); ok {
if dst, ok := into.(*pluginsv0alpha1.PluginList); ok {
*dst = *src
}
}
return nil
}
func (m *mockResourceClient) Get(ctx context.Context, identifier resource.Identifier) (resource.Object, error) {
return nil, nil
}
func (m *mockResourceClient) GetInto(ctx context.Context, identifier resource.Identifier, into resource.Object) error {
return nil
}
func (m *mockResourceClient) Create(ctx context.Context, identifier resource.Identifier, obj resource.Object, opts resource.CreateOptions) (resource.Object, error) {
return nil, nil
}
func (m *mockResourceClient) CreateInto(ctx context.Context, identifier resource.Identifier, obj resource.Object, opts resource.CreateOptions, into resource.Object) error {
return nil
}
func (m *mockResourceClient) Update(ctx context.Context, identifier resource.Identifier, obj resource.Object, opts resource.UpdateOptions) (resource.Object, error) {
return nil, nil
}
func (m *mockResourceClient) UpdateInto(ctx context.Context, identifier resource.Identifier, obj resource.Object, opts resource.UpdateOptions, into resource.Object) error {
return nil
}
func (m *mockResourceClient) Patch(ctx context.Context, identifier resource.Identifier, patch resource.PatchRequest, opts resource.PatchOptions) (resource.Object, error) {
return nil, nil
}
func (m *mockResourceClient) PatchInto(ctx context.Context, identifier resource.Identifier, patch resource.PatchRequest, opts resource.PatchOptions, into resource.Object) error {
return nil
}
func (m *mockResourceClient) Delete(ctx context.Context, identifier resource.Identifier, opts resource.DeleteOptions) error {
return nil
}
func (m *mockResourceClient) SubresourceRequest(ctx context.Context, identifier resource.Identifier, req resource.CustomRouteRequestOptions) ([]byte, error) {
return nil, nil
}
func (m *mockResourceClient) Watch(ctx context.Context, namespace string, opts resource.WatchOptions) (resource.WatchResponse, error) {
return &mockWatchResponse{}, nil
}
type mockWatchResponse struct{}
func (m *mockWatchResponse) Stop() {}
func (m *mockWatchResponse) WatchEvents() <-chan resource.WatchEvent {
ch := make(chan resource.WatchEvent)
close(ch)
return ch
}