grafana/pkg/services/plugindashboards/service/dashboard_updater_test.go

583 lines
18 KiB
Go

package service
import (
"context"
"fmt"
"testing"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/services/dashboardimport"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/plugindashboards"
"github.com/grafana/grafana/pkg/services/pluginsettings"
"github.com/grafana/grafana/pkg/services/pluginsettings/service"
"github.com/stretchr/testify/require"
)
func TestDashboardUpdater(t *testing.T) {
t.Run("updateAppDashboards", func(t *testing.T) {
scenario(t, "Without any stored plugin settings shouldn't delete/import any dashboards",
scenarioInput{}, func(ctx *scenarioContext) {
ctx.dashboardUpdater.updateAppDashboards()
require.Len(t, ctx.pluginSettingsService.getPluginSettingsArgs, 1)
require.Equal(t, int64(0), ctx.pluginSettingsService.getPluginSettingsArgs[0])
require.Empty(t, ctx.dashboardService.deleteDashboardArgs)
require.Empty(t, ctx.importDashboardArgs)
})
scenario(t, "Without any stored enabled plugin shouldn't delete/import any dashboards",
scenarioInput{
storedPluginSettings: []*pluginsettings.DTO{
{
PluginID: "test",
Enabled: false,
},
},
pluginDashboards: []*plugindashboards.PluginDashboard{
{
PluginId: "test",
Reference: "dashboard.json",
},
},
}, func(ctx *scenarioContext) {
ctx.dashboardUpdater.updateAppDashboards()
require.NotEmpty(t, ctx.pluginSettingsService.getPluginSettingsArgs)
require.Empty(t, ctx.dashboardService.deleteDashboardArgs)
require.Empty(t, ctx.importDashboardArgs)
})
scenario(t, "With stored enabled plugin, but not installed shouldn't delete/import any dashboards",
scenarioInput{
storedPluginSettings: []*pluginsettings.DTO{
{
PluginID: "test",
Enabled: true,
},
},
pluginDashboards: []*plugindashboards.PluginDashboard{
{
PluginId: "test",
Reference: "dashboard.json",
},
},
}, func(ctx *scenarioContext) {
ctx.dashboardUpdater.updateAppDashboards()
require.NotEmpty(t, ctx.pluginSettingsService.getPluginSettingsArgs)
require.Empty(t, ctx.dashboardService.deleteDashboardArgs)
require.Empty(t, ctx.importDashboardArgs)
})
scenario(t, "With stored enabled plugin and installed with same version shouldn't delete/import any dashboards",
scenarioInput{
storedPluginSettings: []*pluginsettings.DTO{
{
PluginID: "test",
Enabled: true,
PluginVersion: "1.0.0",
},
},
installedPlugins: []plugins.PluginDTO{
{
JSONData: plugins.JSONData{
Info: plugins.Info{
Version: "1.0.0",
},
},
},
},
pluginDashboards: []*plugindashboards.PluginDashboard{
{
PluginId: "test",
Reference: "dashboard.json",
},
},
}, func(ctx *scenarioContext) {
ctx.dashboardUpdater.updateAppDashboards()
require.NotEmpty(t, ctx.pluginSettingsService.getPluginSettingsArgs)
require.Empty(t, ctx.dashboardService.deleteDashboardArgs)
require.Empty(t, ctx.importDashboardArgs)
})
scenario(t, "With stored enabled plugin and installed with different versions, but no dashboard updates shouldn't delete/import dashboards",
scenarioInput{
storedPluginSettings: []*pluginsettings.DTO{
{
PluginID: "test",
Enabled: true,
PluginVersion: "1.0.0",
},
},
installedPlugins: []plugins.PluginDTO{
{
JSONData: plugins.JSONData{
Info: plugins.Info{
Version: "1.0.1",
},
},
},
},
pluginDashboards: []*plugindashboards.PluginDashboard{
{
PluginId: "test",
Reference: "dashboard.json",
Removed: false,
Revision: 1,
ImportedRevision: 1,
},
},
}, func(ctx *scenarioContext) {
ctx.dashboardUpdater.updateAppDashboards()
require.NotEmpty(t, ctx.pluginSettingsService.getPluginSettingsArgs)
require.Empty(t, ctx.dashboardService.deleteDashboardArgs)
require.Empty(t, ctx.importDashboardArgs)
})
scenario(t, "With stored enabled plugin and installed with different versions and with dashboard updates should delete/import dashboards",
scenarioInput{
storedPluginSettings: []*pluginsettings.DTO{
{
PluginID: "test",
Enabled: true,
PluginVersion: "1.0.0",
OrgID: 2,
},
},
installedPlugins: []plugins.PluginDTO{
{
JSONData: plugins.JSONData{
ID: "test",
Info: plugins.Info{
Version: "1.0.1",
},
},
},
},
pluginDashboards: []*plugindashboards.PluginDashboard{
{
DashboardId: 3,
PluginId: "test",
Reference: "removed.json",
Removed: true,
},
{
DashboardId: 4,
PluginId: "test",
Reference: "not-updated.json",
},
{
DashboardId: 5,
PluginId: "test",
Reference: "updated.json",
Revision: 1,
ImportedRevision: 2,
},
},
}, func(ctx *scenarioContext) {
ctx.dashboardUpdater.updateAppDashboards()
require.NotEmpty(t, ctx.pluginSettingsService.getPluginSettingsArgs)
require.Len(t, ctx.dashboardService.deleteDashboardArgs, 1)
require.Equal(t, int64(2), ctx.dashboardService.deleteDashboardArgs[0].orgId)
require.Equal(t, int64(3), ctx.dashboardService.deleteDashboardArgs[0].dashboardId)
require.Len(t, ctx.importDashboardArgs, 1)
require.Equal(t, "test", ctx.importDashboardArgs[0].PluginId)
require.Equal(t, "updated.json", ctx.importDashboardArgs[0].Path)
require.Equal(t, int64(2), ctx.importDashboardArgs[0].User.OrgId)
require.Equal(t, models.ROLE_ADMIN, ctx.importDashboardArgs[0].User.OrgRole)
require.Equal(t, int64(0), ctx.importDashboardArgs[0].FolderId)
require.True(t, ctx.importDashboardArgs[0].Overwrite)
})
})
t.Run("handlePluginStateChanged", func(t *testing.T) {
scenario(t, "When app plugin is disabled that doesn't have any imported dashboards shouldn't delete any",
scenarioInput{}, func(ctx *scenarioContext) {
err := ctx.bus.Publish(context.Background(), &models.PluginStateChangedEvent{
PluginId: "test",
OrgId: 2,
Enabled: false,
})
require.NoError(t, err)
require.Len(t, ctx.dashboardPluginService.args, 1)
require.Equal(t, int64(2), ctx.dashboardPluginService.args[0].OrgId)
require.Equal(t, "test", ctx.dashboardPluginService.args[0].PluginId)
require.Empty(t, ctx.dashboardService.deleteDashboardArgs)
})
})
scenario(t, "When app plugin is disabled that have imported dashboards should delete them",
scenarioInput{
storedPluginSettings: []*pluginsettings.DTO{
{
PluginID: "test",
Enabled: true,
OrgID: 2,
},
},
installedPlugins: []plugins.PluginDTO{
{
JSONData: plugins.JSONData{
ID: "test",
},
},
},
pluginDashboards: []*plugindashboards.PluginDashboard{
{
DashboardId: 3,
PluginId: "test",
Reference: "dashboard1.json",
},
{
DashboardId: 4,
PluginId: "test",
Reference: "dashboard2.json",
},
{
DashboardId: 5,
PluginId: "test",
Reference: "dashboard3.json",
},
},
}, func(ctx *scenarioContext) {
err := ctx.bus.Publish(context.Background(), &models.PluginStateChangedEvent{
PluginId: "test",
OrgId: 2,
Enabled: false,
})
require.NoError(t, err)
require.Len(t, ctx.dashboardPluginService.args, 1)
require.Equal(t, int64(2), ctx.dashboardPluginService.args[0].OrgId)
require.Equal(t, "test", ctx.dashboardPluginService.args[0].PluginId)
require.Len(t, ctx.dashboardService.deleteDashboardArgs, 3)
})
scenario(t, "When app plugin is enabled, stored disabled plugin and with dashboard updates should import dashboards",
scenarioInput{
storedPluginSettings: []*pluginsettings.DTO{
{
PluginID: "test",
Enabled: false,
OrgID: 2,
PluginVersion: "1.0.0",
},
},
installedPlugins: []plugins.PluginDTO{
{
JSONData: plugins.JSONData{
ID: "test",
Info: plugins.Info{
Version: "1.0.0",
},
},
},
},
pluginDashboards: []*plugindashboards.PluginDashboard{
{
DashboardId: 3,
PluginId: "test",
Reference: "dashboard1.json",
Revision: 1,
ImportedRevision: 0,
},
{
DashboardId: 4,
PluginId: "test",
Reference: "dashboard2.json",
Revision: 1,
ImportedRevision: 0,
},
{
DashboardId: 5,
PluginId: "test",
Reference: "dashboard3.json",
Revision: 1,
ImportedRevision: 0,
},
},
}, func(ctx *scenarioContext) {
err := ctx.bus.Publish(context.Background(), &models.PluginStateChangedEvent{
PluginId: "test",
OrgId: 2,
Enabled: true,
})
require.NoError(t, err)
require.Empty(t, ctx.dashboardService.deleteDashboardArgs)
require.Len(t, ctx.importDashboardArgs, 3)
require.Equal(t, "test", ctx.importDashboardArgs[0].PluginId)
require.Equal(t, "dashboard1.json", ctx.importDashboardArgs[0].Path)
require.Equal(t, int64(2), ctx.importDashboardArgs[0].User.OrgId)
require.Equal(t, models.ROLE_ADMIN, ctx.importDashboardArgs[0].User.OrgRole)
require.Equal(t, int64(0), ctx.importDashboardArgs[0].FolderId)
require.True(t, ctx.importDashboardArgs[0].Overwrite)
require.Equal(t, "test", ctx.importDashboardArgs[1].PluginId)
require.Equal(t, "dashboard2.json", ctx.importDashboardArgs[1].Path)
require.Equal(t, int64(2), ctx.importDashboardArgs[1].User.OrgId)
require.Equal(t, models.ROLE_ADMIN, ctx.importDashboardArgs[1].User.OrgRole)
require.Equal(t, int64(0), ctx.importDashboardArgs[1].FolderId)
require.True(t, ctx.importDashboardArgs[1].Overwrite)
require.Equal(t, "test", ctx.importDashboardArgs[2].PluginId)
require.Equal(t, "dashboard3.json", ctx.importDashboardArgs[2].Path)
require.Equal(t, int64(2), ctx.importDashboardArgs[2].User.OrgId)
require.Equal(t, models.ROLE_ADMIN, ctx.importDashboardArgs[2].User.OrgRole)
require.Equal(t, int64(0), ctx.importDashboardArgs[2].FolderId)
require.True(t, ctx.importDashboardArgs[2].Overwrite)
})
}
type pluginStoreMock struct {
plugins.Store
pluginFunc func(ctx context.Context, pluginID string) (plugins.PluginDTO, bool)
}
func (m *pluginStoreMock) Plugin(ctx context.Context, pluginID string) (plugins.PluginDTO, bool) {
if m.pluginFunc != nil {
return m.pluginFunc(ctx, pluginID)
}
return plugins.PluginDTO{}, false
}
type pluginDashboardServiceMock struct {
listPluginDashboardsFunc func(ctx context.Context, req *plugindashboards.ListPluginDashboardsRequest) (*plugindashboards.ListPluginDashboardsResponse, error)
loadPluginDashboardfunc func(ctx context.Context, req *plugindashboards.LoadPluginDashboardRequest) (*plugindashboards.LoadPluginDashboardResponse, error)
}
func (m *pluginDashboardServiceMock) ListPluginDashboards(ctx context.Context, req *plugindashboards.ListPluginDashboardsRequest) (*plugindashboards.ListPluginDashboardsResponse, error) {
if m.listPluginDashboardsFunc != nil {
return m.listPluginDashboardsFunc(ctx, req)
}
return &plugindashboards.ListPluginDashboardsResponse{
Items: []*plugindashboards.PluginDashboard{},
}, nil
}
func (m *pluginDashboardServiceMock) LoadPluginDashboard(ctx context.Context, req *plugindashboards.LoadPluginDashboardRequest) (*plugindashboards.LoadPluginDashboardResponse, error) {
if m.loadPluginDashboardfunc != nil {
return m.loadPluginDashboardfunc(ctx, req)
}
return nil, nil
}
type importDashboardServiceMock struct {
dashboardimport.Service
importDashboardFunc func(ctx context.Context, req *dashboardimport.ImportDashboardRequest) (*dashboardimport.ImportDashboardResponse, error)
}
func (m *importDashboardServiceMock) ImportDashboard(ctx context.Context, req *dashboardimport.ImportDashboardRequest) (*dashboardimport.ImportDashboardResponse, error) {
if m.importDashboardFunc != nil {
return m.importDashboardFunc(ctx, req)
}
return nil, nil
}
type pluginsSettingsServiceMock struct {
service.Service
storedPluginSettings []*pluginsettings.DTO
getPluginSettingsArgs []int64
err error
}
func (s *pluginsSettingsServiceMock) GetPluginSettings(_ context.Context, args *pluginsettings.GetArgs) ([]*pluginsettings.DTO, error) {
s.getPluginSettingsArgs = append(s.getPluginSettingsArgs, args.OrgID)
return s.storedPluginSettings, s.err
}
func (s *pluginsSettingsServiceMock) GetPluginSettingByPluginID(_ context.Context, args *pluginsettings.GetByPluginIDArgs) (*pluginsettings.DTO, error) {
for _, setting := range s.storedPluginSettings {
if setting.PluginID == args.PluginID {
return &pluginsettings.DTO{
PluginID: args.PluginID,
OrgID: args.OrgID,
}, nil
}
}
return nil, s.err
}
func (s *pluginsSettingsServiceMock) UpdatePluginSettingPluginVersion(_ context.Context, _ *pluginsettings.UpdatePluginVersionArgs) error {
return s.err
}
func (s *pluginsSettingsServiceMock) UpdatePluginSetting(_ context.Context, _ *pluginsettings.UpdateArgs) error {
return s.err
}
func (s *pluginsSettingsServiceMock) DecryptedValues(_ *pluginsettings.DTO) map[string]string {
return nil
}
type dashboardServiceMock struct {
dashboards.DashboardService
deleteDashboardArgs []struct {
orgId int64
dashboardId int64
}
}
func (s *dashboardServiceMock) DeleteDashboard(_ context.Context, dashboardId int64, orgId int64) error {
s.deleteDashboardArgs = append(s.deleteDashboardArgs, struct {
orgId int64
dashboardId int64
}{
orgId: orgId,
dashboardId: dashboardId,
})
return nil
}
func (s *dashboardServiceMock) GetDashboardByPublicUid(ctx context.Context, dashboardPublicUid string) (*models.Dashboard, error) {
return nil, nil
}
type scenarioInput struct {
storedPluginSettings []*pluginsettings.DTO
installedPlugins []plugins.PluginDTO
pluginDashboards []*plugindashboards.PluginDashboard
}
type scenarioContext struct {
t *testing.T
bus bus.Bus
pluginSettingsService *pluginsSettingsServiceMock
pluginStore plugins.Store
pluginDashboardService plugindashboards.Service
importDashboardService dashboardimport.Service
dashboardPluginService *dashboardPluginServiceMock
dashboardService *dashboardServiceMock
importDashboardArgs []*dashboardimport.ImportDashboardRequest
getPluginSettingsByIdArgs []*models.GetPluginSettingByIdQuery
updatePluginSettingVersionArgs []*models.UpdatePluginSettingVersionCmd
dashboardUpdater *DashboardUpdater
}
func scenario(t *testing.T, desc string, input scenarioInput, f func(ctx *scenarioContext)) {
t.Helper()
tracer := tracing.InitializeTracerForTest()
sCtx := &scenarioContext{
t: t,
bus: bus.ProvideBus(tracer),
importDashboardArgs: []*dashboardimport.ImportDashboardRequest{},
getPluginSettingsByIdArgs: []*models.GetPluginSettingByIdQuery{},
updatePluginSettingVersionArgs: []*models.UpdatePluginSettingVersionCmd{},
}
getPlugin := func(ctx context.Context, pluginID string) (plugins.PluginDTO, bool) {
for _, p := range input.installedPlugins {
if p.ID == pluginID {
return p, true
}
}
return plugins.PluginDTO{}, false
}
sCtx.pluginSettingsService = &pluginsSettingsServiceMock{
storedPluginSettings: input.storedPluginSettings,
}
sCtx.pluginStore = &pluginStoreMock{
pluginFunc: getPlugin,
}
pluginDashboards := map[string][]*models.Dashboard{}
for _, pluginDashboard := range input.pluginDashboards {
if _, exists := pluginDashboards[pluginDashboard.PluginId]; !exists {
pluginDashboards[pluginDashboard.PluginId] = []*models.Dashboard{}
}
pluginDashboards[pluginDashboard.PluginId] = append(pluginDashboards[pluginDashboard.PluginId], &models.Dashboard{
PluginId: pluginDashboard.PluginId,
})
}
sCtx.dashboardPluginService = &dashboardPluginServiceMock{
pluginDashboards: pluginDashboards,
}
sCtx.dashboardService = &dashboardServiceMock{
deleteDashboardArgs: []struct {
orgId int64
dashboardId int64
}{},
}
listPluginDashboards := func(ctx context.Context, req *plugindashboards.ListPluginDashboardsRequest) (*plugindashboards.ListPluginDashboardsResponse, error) {
dashboards := []*plugindashboards.PluginDashboard{}
for _, d := range input.pluginDashboards {
if d.PluginId == req.PluginID {
dashboards = append(dashboards, d)
}
}
return &plugindashboards.ListPluginDashboardsResponse{
Items: dashboards,
}, nil
}
loadPluginDashboard := func(ctx context.Context, req *plugindashboards.LoadPluginDashboardRequest) (*plugindashboards.LoadPluginDashboardResponse, error) {
for _, d := range input.pluginDashboards {
if d.PluginId == req.PluginID && req.Reference == d.Reference {
return &plugindashboards.LoadPluginDashboardResponse{
Dashboard: &models.Dashboard{},
}, nil
}
}
return nil, fmt.Errorf("no match for loading plugin dashboard")
}
sCtx.pluginDashboardService = &pluginDashboardServiceMock{
listPluginDashboardsFunc: listPluginDashboards,
loadPluginDashboardfunc: loadPluginDashboard,
}
importDashboard := func(ctx context.Context, req *dashboardimport.ImportDashboardRequest) (*dashboardimport.ImportDashboardResponse, error) {
sCtx.importDashboardArgs = append(sCtx.importDashboardArgs, req)
return &dashboardimport.ImportDashboardResponse{
PluginId: req.PluginId,
}, nil
}
sCtx.importDashboardService = &importDashboardServiceMock{
importDashboardFunc: importDashboard,
}
sCtx.dashboardUpdater = newDashboardUpdater(
sCtx.bus,
sCtx.pluginStore,
sCtx.pluginDashboardService,
sCtx.importDashboardService,
sCtx.pluginSettingsService,
sCtx.dashboardPluginService,
sCtx.dashboardService,
)
t.Run(desc, func(t *testing.T) {
f(sCtx)
})
}