package plugincheck import ( "context" "testing" "github.com/grafana/grafana-app-sdk/logging" "github.com/stretchr/testify/assert" advisor "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1" "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins/repo" "github.com/grafana/grafana/pkg/services/pluginsintegration/managedplugins" "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginchecker" "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore" "github.com/grafana/grafana/pkg/services/pluginsintegration/provisionedplugins" ) func TestRun(t *testing.T) { tests := []struct { name string plugins []pluginstore.Plugin pluginInfo []repo.PluginInfo pluginPreinstalled []string pluginManaged []string pluginProvisioned []provisionedplugins.Plugin pluginErrors []*plugins.Error expectedFailures []advisor.CheckReportFailure }{ { name: "No plugins", plugins: []pluginstore.Plugin{}, expectedFailures: []advisor.CheckReportFailure{}, }, { name: "Deprecated plugin", plugins: []pluginstore.Plugin{ {JSONData: plugins.JSONData{ID: "plugin1", Name: "Plugin 1", Info: plugins.Info{Version: "1.0.0"}}}, }, pluginInfo: []repo.PluginInfo{ {Status: "deprecated", Slug: "plugin1", Version: "1.0.0"}, }, expectedFailures: []advisor.CheckReportFailure{ { Severity: advisor.CheckReportFailureSeverityHigh, StepID: "deprecation", Item: "Plugin 1", ItemID: "plugin1", Links: []advisor.CheckErrorLink{ { Url: "/plugins/plugin1", Message: "View plugin", }, }, }, }, }, { name: "Plugin with update", plugins: []pluginstore.Plugin{ {JSONData: plugins.JSONData{ID: "plugin2", Name: "Plugin 2", Info: plugins.Info{Version: "1.0.0"}}}, }, pluginInfo: []repo.PluginInfo{ {Status: "active", Slug: "plugin2", Version: "1.1.0"}, }, expectedFailures: []advisor.CheckReportFailure{ { Severity: advisor.CheckReportFailureSeverityLow, StepID: "update", Item: "Plugin 2", ItemID: "plugin2", Links: []advisor.CheckErrorLink{ { Url: "/plugins/plugin2?page=version-history", Message: "Upgrade", }, }, }, }, }, { name: "Plugin with update (non semver)", plugins: []pluginstore.Plugin{ {JSONData: plugins.JSONData{ID: "plugin2", Name: "Plugin 2", Info: plugins.Info{Version: "alpha"}}}, }, pluginInfo: []repo.PluginInfo{ {Status: "active", Slug: "plugin2", Version: "beta"}, }, expectedFailures: []advisor.CheckReportFailure{}, // Cannot be compared because the version is not semver }, { name: "Plugin pinned", plugins: []pluginstore.Plugin{ {JSONData: plugins.JSONData{ID: "plugin3", Name: "Plugin 3", Info: plugins.Info{Version: "1.0.0"}}}, }, pluginInfo: []repo.PluginInfo{ {Status: "deprecated", Slug: "plugin3"}, // This should be ignored }, pluginPreinstalled: []string{"plugin3"}, expectedFailures: []advisor.CheckReportFailure{}, }, { name: "Managed plugin", plugins: []pluginstore.Plugin{ {JSONData: plugins.JSONData{ID: "plugin4", Name: "Plugin 4", Info: plugins.Info{Version: "1.0.0"}}}, }, pluginInfo: []repo.PluginInfo{ {Status: "deprecated", Slug: "plugin4", Version: "1.1.0"}, // This should be ignored }, pluginManaged: []string{"plugin4"}, expectedFailures: []advisor.CheckReportFailure{}, }, { name: "Provisioned plugin", plugins: []pluginstore.Plugin{ {JSONData: plugins.JSONData{ID: "plugin5", Name: "Plugin 5", Info: plugins.Info{Version: "1.0.0"}}}, }, pluginInfo: []repo.PluginInfo{ {Status: "deprecated", Slug: "plugin5", Version: "1.1.0"}, // This should be ignored }, pluginProvisioned: []provisionedplugins.Plugin{{ID: "plugin5"}}, expectedFailures: []advisor.CheckReportFailure{}, }, { name: "Invalid signatures", plugins: []pluginstore.Plugin{ {JSONData: plugins.JSONData{ID: "plugin6", Name: "Plugin 6", Info: plugins.Info{Version: "1.0.0"}}, Signature: plugins.SignatureStatusInvalid}, {JSONData: plugins.JSONData{ID: "plugin7", Name: "Plugin 7", Info: plugins.Info{Version: "1.0.0"}}, Signature: plugins.SignatureStatusModified}, {JSONData: plugins.JSONData{ID: "plugin8", Name: "Plugin 8", Info: plugins.Info{Version: "1.0.0"}}, Signature: plugins.SignatureStatusUnsigned}, }, pluginInfo: []repo.PluginInfo{ {Status: "active", Slug: "plugin6", Version: "1.0.0"}, {Status: "active", Slug: "plugin7", Version: "1.0.0"}, {Status: "active", Slug: "plugin8", Version: "1.0.0"}, }, pluginErrors: []*plugins.Error{ {PluginID: "plugin9", ErrorCode: plugins.ErrorCodeSignatureInvalid}, {PluginID: "plugin10", ErrorCode: plugins.ErrorCodeSignatureModified}, {PluginID: "plugin11", ErrorCode: plugins.ErrorCodeSignatureMissing}, {PluginID: "plugin12", ErrorCode: plugins.ErrorCodeFailedBackendStart}, // This should be ignored atm }, expectedFailures: []advisor.CheckReportFailure{ { Severity: advisor.CheckReportFailureSeverityLow, StepID: UnsignedStepID, Item: "Plugin 6", ItemID: "plugin6", Links: []advisor.CheckErrorLink{{Url: "/plugins/plugin6", Message: "View plugin"}}, }, { Severity: advisor.CheckReportFailureSeverityLow, StepID: UnsignedStepID, Item: "Plugin 7", ItemID: "plugin7", Links: []advisor.CheckErrorLink{{Url: "/plugins/plugin7", Message: "View plugin"}}, }, { Severity: advisor.CheckReportFailureSeverityLow, StepID: UnsignedStepID, Item: "Plugin 8", ItemID: "plugin8", Links: []advisor.CheckErrorLink{{Url: "/plugins/plugin8", Message: "View plugin"}}, }, { Severity: advisor.CheckReportFailureSeverityHigh, StepID: UnsignedStepID, Item: "plugin9", ItemID: "plugin9", Links: []advisor.CheckErrorLink{}, }, { Severity: advisor.CheckReportFailureSeverityHigh, StepID: UnsignedStepID, Item: "plugin10", ItemID: "plugin10", Links: []advisor.CheckErrorLink{}, }, { Severity: advisor.CheckReportFailureSeverityHigh, StepID: UnsignedStepID, Item: "plugin11", ItemID: "plugin11", Links: []advisor.CheckErrorLink{}, }, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { pluginStore := &mockPluginStore{plugins: tt.plugins} pluginRepo := &mockPluginRepo{ pluginInfo: tt.pluginInfo, } pluginPreinstall := &mockPluginPreinstall{pinned: tt.pluginPreinstalled} managedPlugins := &mockManagedPlugins{managed: tt.pluginManaged} provisionedPlugins := &mockProvisionedPlugins{provisioned: tt.pluginProvisioned} updateChecker := pluginchecker.ProvideService(managedPlugins, provisionedPlugins, pluginPreinstall) pluginErrorResolver := &mockPluginErrorResolver{pluginErrors: tt.pluginErrors} check := New(pluginStore, pluginRepo, updateChecker, pluginErrorResolver, "12.0.0") items, err := check.Items(context.Background()) assert.NoError(t, err) failures := []advisor.CheckReportFailure{} err = check.Init(context.Background()) assert.NoError(t, err) for _, step := range check.Steps() { for _, item := range items { stepFailures, err := step.Run(context.Background(), logging.DefaultLogger, &advisor.CheckSpec{}, item) assert.NoError(t, err) if len(stepFailures) > 0 { failures = append(failures, stepFailures...) } } } assert.NoError(t, err) assert.Equal(t, len(tt.plugins)+len(tt.pluginErrors), len(items)) assert.ElementsMatch(t, tt.expectedFailures, failures) }) } } func TestCheck_Item(t *testing.T) { t.Run("should return nil when plugin is not found", func(t *testing.T) { pluginStore := &mockPluginStore{plugins: []pluginstore.Plugin{}} check := &check{ PluginStore: pluginStore, } item, err := check.Item(context.Background(), "invalid-uid") assert.NoError(t, err) assert.Nil(t, item) }) } type mockPluginStore struct { pluginstore.Store plugins []pluginstore.Plugin } func (m *mockPluginStore) Plugins(ctx context.Context, t ...plugins.Type) []pluginstore.Plugin { return m.plugins } func (m *mockPluginStore) Plugin(ctx context.Context, id string) (pluginstore.Plugin, bool) { if len(m.plugins) == 0 { return pluginstore.Plugin{}, false } return m.plugins[0], true } type mockPluginRepo struct { repo.Service pluginInfo []repo.PluginInfo } func (m *mockPluginRepo) GetPluginsInfo(ctx context.Context, options repo.GetPluginsInfoOptions, compatOpts repo.CompatOpts) ([]repo.PluginInfo, error) { return m.pluginInfo, nil } type mockPluginPreinstall struct { pluginchecker.Preinstall pinned []string } func (m *mockPluginPreinstall) IsPinned(pluginID string) bool { for _, p := range m.pinned { if p == pluginID { return true } } return false } type mockManagedPlugins struct { managedplugins.Manager managed []string } func (m *mockManagedPlugins) ManagedPlugins(ctx context.Context) []string { return m.managed } type mockProvisionedPlugins struct { provisionedplugins.Manager provisioned []provisionedplugins.Plugin } func (m *mockProvisionedPlugins) ProvisionedPlugins(_ context.Context) ([]provisionedplugins.Plugin, error) { return m.provisioned, nil } type mockPluginErrorResolver struct { plugins.ErrorResolver pluginErrors []*plugins.Error } func (m *mockPluginErrorResolver) PluginErrors(ctx context.Context) []*plugins.Error { return m.pluginErrors } func (m *mockPluginErrorResolver) PluginError(ctx context.Context, id string) *plugins.Error { for _, err := range m.pluginErrors { if err.PluginID == id { return err } } return nil }