package setting import ( "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestPluginSettings(t *testing.T) { cfg := NewCfg() sec, err := cfg.Raw.NewSection("plugin") require.NoError(t, err) _, err = sec.NewKey("key", "value") require.NoError(t, err) sec, err = cfg.Raw.NewSection("plugin.plugin") require.NoError(t, err) _, err = sec.NewKey("key1", "value1") require.NoError(t, err) _, err = sec.NewKey("key2", "value2") require.NoError(t, err) sec, err = cfg.Raw.NewSection("plugin.plugin2") require.NoError(t, err) _, err = sec.NewKey("key3", "value3") require.NoError(t, err) _, err = sec.NewKey("key4", "value4") require.NoError(t, err) sec, err = cfg.Raw.NewSection("other") require.NoError(t, err) _, err = sec.NewKey("keySomething", "whatever") require.NoError(t, err) ps := extractPluginSettings(cfg.Raw.Sections()) require.Len(t, ps, 2) require.Len(t, ps["plugin"], 2) require.Equal(t, ps["plugin"]["key1"], "value1") require.Equal(t, ps["plugin"]["key2"], "value2") require.Len(t, ps["plugin2"], 2) require.Equal(t, ps["plugin2"]["key3"], "value3") require.Equal(t, ps["plugin2"]["key4"], "value4") } func Test_readPluginSettings(t *testing.T) { t.Run("should parse separated plugin ids", func(t *testing.T) { for _, tc := range []struct { name string f func(ids ...string) string }{ { name: "commas", f: func(ids ...string) string { return strings.Join(ids, ",") }, }, { name: "commas and a space", f: func(ids ...string) string { return strings.Join(ids, ", ") }, }, { name: "spaces", f: func(ids ...string) string { return strings.Join(ids, " ") }, }, { name: "json-like", f: func(ids ...string) string { return `["` + strings.Join(ids, `","`) + `"]` }, }, } { t.Run(tc.name, func(t *testing.T) { cfg := NewCfg() sec, err := cfg.Raw.NewSection("plugins") require.NoError(t, err) _, err = sec.NewKey("disable_plugins", tc.f("plugin1", "plugin2")) require.NoError(t, err) _, err = sec.NewKey("plugin_catalog_hidden_plugins", tc.f("plugin3")) require.NoError(t, err) _, err = sec.NewKey("hide_angular_deprecation", tc.f("a", "b", "c")) require.NoError(t, err) err = cfg.readPluginSettings(cfg.Raw) require.NoError(t, err) require.Equal(t, []string{"plugin1", "plugin2"}, cfg.DisablePlugins) require.Equal(t, []string{"plugin3", "plugin1", "plugin2"}, cfg.PluginCatalogHiddenPlugins) require.Equal(t, []string{"a", "b", "c"}, cfg.HideAngularDeprecation) }) } }) t.Run("when plugins.preinstall_sync is defined", func(t *testing.T) { tests := []struct { name string rawInput string expected []InstallPlugin disablePlugins string disablePreinstall bool }{ { name: "should add the plugin to the sync list - not contain default plugins like async list", rawInput: "plugin1", expected: []InstallPlugin{ {ID: "plugin1", Version: "", URL: ""}, }, }, { name: "it should remove the disabled plugin", rawInput: "plugin1,plugin2", disablePlugins: "plugin1", expected: []InstallPlugin{{ID: "plugin2"}}, }, { name: "should not process at all when preinstall is disabled", rawInput: "plugin1", disablePreinstall: true, expected: nil, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { cfg := NewCfg() sec, err := cfg.Raw.NewSection("plugins") require.NoError(t, err) _, err = sec.NewKey("preinstall_sync", tc.rawInput) require.NoError(t, err) if tc.disablePreinstall { _, err = sec.NewKey("preinstall_disabled", "true") require.NoError(t, err) } if tc.disablePlugins != "" { _, err = sec.NewKey("disable_plugins", tc.disablePlugins) require.NoError(t, err) } err = cfg.readPluginSettings(cfg.Raw) require.NoError(t, err) assert.ElementsMatch(t, cfg.PreinstallPluginsSync, tc.expected) }) } }) t.Run("when plugins.preinstall is defined", func(t *testing.T) { defaultPreinstallPluginsList := make([]InstallPlugin, 0, len(defaultPreinstallPlugins)) defaultPreinstallPluginsIDs := []string{} for _, p := range defaultPreinstallPlugins { defaultPreinstallPluginsList = append(defaultPreinstallPluginsList, p) defaultPreinstallPluginsIDs = append(defaultPreinstallPluginsIDs, p.ID) } tests := []struct { name string rawInput string rawInputSync string disablePreinstall bool expected []InstallPlugin expectedSync []InstallPlugin disableAsync bool disablePlugins string }{ { name: "should add the default preinstalled plugin", rawInput: "", expected: defaultPreinstallPluginsList, }, { name: "should add the default preinstalled plugin and the one defined", rawInput: "plugin1", expected: append(defaultPreinstallPluginsList, InstallPlugin{ID: "plugin1", Version: "", URL: ""}), }, { name: "should add the default preinstalled plugin and the one defined with version", rawInput: "plugin1@1.0.0", expected: append(defaultPreinstallPluginsList, InstallPlugin{ID: "plugin1", Version: "1.0.0", URL: ""}), }, { name: "it should remove the disabled plugin", rawInput: "plugin1", disablePlugins: "plugin1", expected: defaultPreinstallPluginsList, }, { name: "it should remove default plugins", rawInput: "", disablePlugins: strings.Join(defaultPreinstallPluginsIDs, ","), expected: nil, }, { name: "should ignore input when preinstall is disabled", rawInput: "plugin1", disablePreinstall: true, expected: nil, }, { name: "should mark preinstall as sync", rawInput: "plugin1", disableAsync: true, expected: nil, expectedSync: append(defaultPreinstallPluginsList, InstallPlugin{"plugin1", "", ""}), }, { name: "should parse a plugin with version and URL", rawInput: "plugin1@1.0.1@https://example.com/plugin1.tar.gz", expected: append(defaultPreinstallPluginsList, InstallPlugin{ID: "plugin1", Version: "1.0.1", URL: "https://example.com/plugin1.tar.gz"}), }, { name: "should parse a plugin with URL", rawInput: "plugin1@@https://example.com/plugin1.tar.gz", expected: append(defaultPreinstallPluginsList, InstallPlugin{ID: "plugin1", Version: "", URL: "https://example.com/plugin1.tar.gz"}), }, { name: "when preinstall_async is false, should add all plugins to preinstall_sync", rawInput: "plugin1", rawInputSync: "plugin2", disableAsync: true, expected: nil, expectedSync: append(defaultPreinstallPluginsList, InstallPlugin{"plugin1", "", ""}, InstallPlugin{"plugin2", "", ""}), }, { name: "should overwrite default when user pins a version", rawInput: "grafana-pyroscope-app@4.0.0", expected: func() []InstallPlugin { var plugins []InstallPlugin for _, p := range defaultPreinstallPlugins { if p.ID == "grafana-pyroscope-app" { plugins = append(plugins, InstallPlugin{"grafana-pyroscope-app", "4.0.0", ""}) } else { plugins = append(plugins, p) } } return plugins }(), }, { name: "when same plugin is defined in preinstall and preinstall_sync, should be only in preinstallSync", rawInput: "plugin1", rawInputSync: "plugin1", expected: defaultPreinstallPluginsList, expectedSync: []InstallPlugin{{ID: "plugin1"}}, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { cfg := NewCfg() sec, err := cfg.Raw.NewSection("plugins") require.NoError(t, err) _, err = sec.NewKey("preinstall", tc.rawInput) require.NoError(t, err) if tc.rawInputSync != "" { _, err = sec.NewKey("preinstall_sync", tc.rawInputSync) require.NoError(t, err) } if tc.disablePreinstall { _, err = sec.NewKey("preinstall_disabled", "true") require.NoError(t, err) } if tc.disableAsync { _, err = sec.NewKey("preinstall_async", "false") require.NoError(t, err) } if tc.disablePlugins != "" { _, err = sec.NewKey("disable_plugins", tc.disablePlugins) require.NoError(t, err) } err = cfg.readPluginSettings(cfg.Raw) require.NoError(t, err) assert.ElementsMatch(t, cfg.PreinstallPluginsAsync, tc.expected) if tc.expectedSync != nil { assert.ElementsMatch(t, cfg.PreinstallPluginsSync, tc.expectedSync) } }) } }) } func Test_migrateInstallPluginsToPreinstallPluginsSync(t *testing.T) { tests := []struct { name string installPluginsVal string installPluginsForce string preinstallPlugins map[string]InstallPlugin expectedPlugins map[string]InstallPlugin }{ { name: "should return empty map when GF_INSTALL_PLUGINS is not set", installPluginsVal: "", preinstallPlugins: map[string]InstallPlugin{}, expectedPlugins: map[string]InstallPlugin{}, }, { name: "should parse URL with folder format", installPluginsVal: "https://grafana.com/grafana/plugins/grafana-piechart-panel/;grafana-piechart-panel", preinstallPlugins: map[string]InstallPlugin{}, expectedPlugins: map[string]InstallPlugin{ "grafana-piechart-panel": { ID: "grafana-piechart-panel", Version: "", URL: "https://grafana.com/grafana/plugins/grafana-piechart-panel/", }, }, }, { name: "should parse mixed formats", installPluginsVal: "https://github.com/VolkovLabs/business-links/releases/download/v1.2.1/volkovlabs-links-panel-1.2.1.zip;volkovlabs-links-panel,marcusolsson-static-datasource,volkovlabs-variable-panel", preinstallPlugins: map[string]InstallPlugin{}, expectedPlugins: map[string]InstallPlugin{ "volkovlabs-links-panel": { ID: "volkovlabs-links-panel", Version: "", URL: "https://github.com/VolkovLabs/business-links/releases/download/v1.2.1/volkovlabs-links-panel-1.2.1.zip", }, "marcusolsson-static-datasource": { ID: "marcusolsson-static-datasource", Version: "", URL: "", }, "volkovlabs-variable-panel": { ID: "volkovlabs-variable-panel", Version: "", URL: "", }, }, }, { name: "should parse ID with version format", installPluginsVal: "volkovlabs-links-panel 1.2.1,marcusolsson-static-datasource 1.0.0,volkovlabs-variable-panel", preinstallPlugins: map[string]InstallPlugin{}, expectedPlugins: map[string]InstallPlugin{ "volkovlabs-links-panel": { ID: "volkovlabs-links-panel", Version: "1.2.1", URL: "", }, "marcusolsson-static-datasource": { ID: "marcusolsson-static-datasource", Version: "1.0.0", URL: "", }, "volkovlabs-variable-panel": { ID: "volkovlabs-variable-panel", Version: "", URL: "", }, }, }, { name: "should return empty map when GF_INSTALL_PLUGINS_FORCE is true", installPluginsVal: "grafana-piechart-panel", installPluginsForce: "true", preinstallPlugins: map[string]InstallPlugin{}, expectedPlugins: map[string]InstallPlugin{}, }, { name: "should skip plugins that are already configured", installPluginsVal: "plugin1 1.0.0,plugin2,plugin3", preinstallPlugins: map[string]InstallPlugin{ "plugin1": {ID: "plugin1", Version: "1.0.1"}, "plugin3": {ID: "plugin3"}, }, expectedPlugins: map[string]InstallPlugin{ "plugin2": { ID: "plugin2", }, "plugin3": { ID: "plugin3", }, "plugin1": { ID: "plugin1", Version: "1.0.1", }, }, }, { name: "should trim the space in the input", installPluginsVal: " plugin1 1.0.0, plugin2, plugin3 ", preinstallPlugins: map[string]InstallPlugin{}, expectedPlugins: map[string]InstallPlugin{ "plugin2": { ID: "plugin2", }, "plugin3": { ID: "plugin3", }, "plugin1": { ID: "plugin1", Version: "1.0.0", }, }, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { cfg := NewCfg() cfg.migrateInstallPluginsToPreinstallPluginsSync(tc.installPluginsVal, tc.installPluginsForce, tc.preinstallPlugins) assert.Equal(t, len(tc.expectedPlugins), len(tc.preinstallPlugins), "Number of plugins doesn't match") // Check each expected plugin exists with correct values for id, expectedPlugin := range tc.expectedPlugins { actualPlugin, exists := tc.preinstallPlugins[id] assert.True(t, exists, "Expected plugin %s not found", id) if exists { assert.Equal(t, expectedPlugin.ID, actualPlugin.ID, "Plugin ID mismatch for %s", id) assert.Equal(t, expectedPlugin.Version, actualPlugin.Version, "Plugin version mismatch for %s", id) assert.Equal(t, expectedPlugin.URL, actualPlugin.URL, "Plugin URL mismatch for %s", id) } } }) } }