Files
upstream-mirror 240db4e2a7
Some checks failed
Server CI / Postgres (shard 0) (push) Has been skipped
Server CI / Postgres (shard 3) (push) Has been skipped
BuildEnv Docker Image / build-image (push) Failing after 4s
BuildEnv Docker Image / build-image-fips (push) Failing after 4s
Migration Review & Release Notes / Generate migration review & release notes (push) Failing after 3s
Server CI / Build Environment (push) Failing after 3s
Server CI / Postgres (shard 1) (push) Has been skipped
Tools CI / check-style (mattermost-govet) (push) Failing after 4s
Server CI / Postgres (shard 2) (push) Has been skipped
Tools CI / check-style (mmgotool) (push) Failing after 3s
Server CI / Elasticsearch v8 Compatibility (push) Has been skipped
Server CI / OpenSearch v2 Compatibility (push) Has been skipped
Server CI / Postgres FIPS (shard 0) (push) Has been skipped
Tools CI / Test (mattermost-govet) (push) Failing after 3s
Server CI / Postgres FIPS (shard 1) (push) Has been skipped
Server CI / Postgres FIPS (shard 2) (push) Has been skipped
Tools CI / Test (mmgotool) (push) Failing after 3s
Server CI / Postgres FIPS (shard 3) (push) Has been skipped
Server CI / Run mmctl tests (push) Has been skipped
Server CI / Run mmctl tests (FIPS) (push) Has been skipped
Server CI / Merge Postgres Test Results (push) Has been skipped
Server CI / Merge Postgres FIPS Test Results (push) Has been skipped
Server CI / Check backport migrations (push) Has been skipped
Server CI / Check generated files (push) Has been skipped
Server CI / check-style (push) Has been skipped
Server CI / Vet API (push) Has been skipped
Server CI / Build mattermost server app (push) Has been skipped
Server CI / Server CI Complete (push) Failing after 2s
Server CI / Server CI Artifacts (push) Has been skipped
Server CI / Sentry Upload (push) Has been skipped
Server CI / Server CI Report (push) Failing after 5m1s
Server CI Nightly Race / Race Detector (push) Has been skipped
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Webapp CI Cache Warm / Warm Webapp node_modules Cache (push) Has been cancelled
E2E CI Cache Warm / Warm E2E npm Registry Cache (push) Has been cancelled
API / build (push) Has been cancelled
Web App CI / check-types (push) Has been cancelled
Web App CI / check-lint (push) Has been cancelled
Web App CI / check-i18n (push) Has been cancelled
Web App CI / check-external-links (push) Has been cancelled
Web App CI / test (platform) (push) Has been cancelled
Web App CI / test (mattermost-redux) (push) Has been cancelled
Web App CI / test (channels shard 1/4) (push) Has been cancelled
Web App CI / test (channels shard 2/4) (push) Has been cancelled
Web App CI / test (channels shard 3/4) (push) Has been cancelled
Web App CI / test (channels shard 4/4) (push) Has been cancelled
Web App CI / upload-coverage (push) Has been cancelled
Web App CI / build (push) Has been cancelled
YAML Lint / yamllint (push) Has been cancelled
Push mirrored docker images / cd/Push mirrored docker images (push) Has been cancelled
docs-cd / Build and deploy docs (push) Has been cancelled
Docs CI / build (push) Has been cancelled
E2E Cache Warm: Cypress deps / Warm Cypress deps cache (push) Has been cancelled
Scorecards supply-chain security / Scorecard analysis (push) Has been cancelled
Server CI Weekly / Postgres FIPS (push) Has been skipped
Server CI Weekly / Run mmctl tests (FIPS) (push) Has been skipped
Server CI Weekly / Postgres with binary parameters (push) Has been skipped
Server CI Weekly / Build Environment (push) Failing after 1s
Server CI Cache Warm / Warm Go Module and Build Cache (push) Failing after 2s
Server CI Nightly Race / Build Environment (push) Failing after 1s
snapshot of https://github.com/mattermost/mattermost.git (master, 2026-09-16; blobs > 131072B omitted)
2026-09-16 00:29:55 +05:00

620 lines
18 KiB
Go

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package commands
import (
"context"
"net/http"
"os"
"strings"
"github.com/golang/mock/gomock"
"github.com/mattermost/mattermost/server/public/model"
"github.com/pkg/errors"
"github.com/mattermost/mattermost/server/v8/cmd/mmctl/printer"
"github.com/spf13/cobra"
)
func (s *MmctlUnitTestSuite) TestPluginAddCmd() {
s.Run("Add 1 plugin", func() {
printer.Clean()
tmpFile, err := os.CreateTemp("", "tmpPlugin")
s.Require().Nil(err)
defer os.Remove(tmpFile.Name())
pluginName := tmpFile.Name()
s.client.
EXPECT().
UploadPlugin(context.TODO(), gomock.AssignableToTypeOf(tmpFile)).
Return(&model.Manifest{}, &model.Response{}, nil).
Times(1)
err = pluginAddCmdF(s.client, &cobra.Command{}, []string{pluginName})
s.Require().NoError(err)
s.Require().Len(printer.GetLines(), 1)
s.Require().Equal(printer.GetLines()[0], "Added plugin: "+pluginName)
})
s.Run("Add 1 plugin, with force active", func() {
printer.Clean()
tmpFile, err := os.CreateTemp("", "tmpPlugin")
s.Require().Nil(err)
defer os.Remove(tmpFile.Name())
pluginName := tmpFile.Name()
s.client.
EXPECT().
UploadPluginForced(context.TODO(), gomock.AssignableToTypeOf(tmpFile)).
Return(&model.Manifest{}, &model.Response{}, nil).
Times(1)
cmd := &cobra.Command{}
cmd.Flags().Bool("force", true, "")
err = pluginAddCmdF(s.client, cmd, []string{pluginName})
s.Require().NoError(err)
s.Require().Len(printer.GetLines(), 1)
s.Require().Equal(printer.GetLines()[0], "Added plugin: "+pluginName)
})
s.Run("Add 1 plugin no file", func() {
printer.Clean()
err := pluginAddCmdF(s.client, &cobra.Command{}, []string{"non_existent_plugin"})
s.Require().NotNil(err)
s.Require().True(err.Error() == "open non_existent_plugin: no such file or directory" || err.Error() == "open non_existent_plugin: The system cannot find the file specified.")
})
s.Run("Add 1 plugin with error", func() {
printer.Clean()
tmpFile, err := os.CreateTemp("", "tmpPlugin")
s.Require().Nil(err)
defer os.Remove(tmpFile.Name())
pluginName := tmpFile.Name()
mockError := errors.New("plugin add error")
s.client.
EXPECT().
UploadPlugin(context.TODO(), gomock.AssignableToTypeOf(tmpFile)).
Return(&model.Manifest{}, &model.Response{}, mockError).
Times(1)
err = pluginAddCmdF(s.client, &cobra.Command{}, []string{pluginName})
s.Require().ErrorContains(err, "plugin add error")
s.Require().Len(printer.GetErrorLines(), 1)
s.Require().Equal(printer.GetErrorLines()[0], "Unable to add plugin: "+pluginName+". Error: "+mockError.Error())
})
s.Run("Add several plugins with some error", func() {
printer.Clean()
args := []string{"fail", "ok", "fail"}
mockError := errors.New("plugin add error")
for idx, arg := range args {
tmpFile, err := os.CreateTemp("", "tmpPlugin")
s.Require().Nil(err)
defer os.Remove(tmpFile.Name())
if arg == "fail" {
s.client.
EXPECT().
UploadPlugin(context.TODO(), gomock.AssignableToTypeOf(tmpFile)).
Return(nil, &model.Response{}, mockError).
Times(1)
} else {
s.client.
EXPECT().
UploadPlugin(context.TODO(), gomock.AssignableToTypeOf(tmpFile)).
Return(&model.Manifest{}, &model.Response{}, nil).
Times(1)
}
args[idx] = tmpFile.Name()
}
err := pluginAddCmdF(s.client, &cobra.Command{}, args)
s.Require().ErrorContains(err, "plugin add error")
s.Require().Len(printer.GetLines(), 1)
s.Require().Equal(printer.GetLines()[0], "Added plugin: "+args[1])
s.Require().Len(printer.GetErrorLines(), 2)
s.Require().Equal(printer.GetErrorLines()[0], "Unable to add plugin: "+args[0]+". Error: "+mockError.Error())
s.Require().Equal(printer.GetErrorLines()[1], "Unable to add plugin: "+args[2]+". Error: "+mockError.Error())
})
}
func (s *MmctlUnitTestSuite) TestPluginInstallUrlCmd() {
s.Run("Install multiple plugins", func() {
printer.Clean()
pluginURL1 := "https://example.com/plugin1.tar.gz"
pluginURL2 := "https://example.com/plugin2.tar.gz"
manifest1 := &model.Manifest{Name: "plugin one"}
manifest2 := &model.Manifest{Name: "plugin two"}
args := []string{pluginURL1, pluginURL2}
s.client.
EXPECT().
InstallPluginFromURL(context.TODO(), pluginURL1, false).
Return(manifest1, &model.Response{}, nil).
Times(1)
s.client.
EXPECT().
InstallPluginFromURL(context.TODO(), pluginURL2, false).
Return(manifest2, &model.Response{}, nil).
Times(1)
err := pluginInstallURLCmdF(s.client, &cobra.Command{}, args)
s.Require().NoError(err)
s.Require().Len(printer.GetErrorLines(), 0)
s.Require().Len(printer.GetLines(), 2)
s.Require().Equal(manifest1, printer.GetLines()[0])
s.Require().Equal(manifest2, printer.GetLines()[1])
})
s.Run("Install one plugin, with force active", func() {
printer.Clean()
pluginURL := "https://example.com/plugin.tar.gz"
manifest := &model.Manifest{Name: "plugin name"}
s.client.
EXPECT().
InstallPluginFromURL(context.TODO(), pluginURL, true).
Return(manifest, &model.Response{}, nil).
Times(1)
cmd := &cobra.Command{}
cmd.Flags().Bool("force", true, "")
err := pluginInstallURLCmdF(s.client, cmd, []string{pluginURL})
s.Require().NoError(err)
s.Require().Len(printer.GetErrorLines(), 0)
s.Require().Len(printer.GetLines(), 1)
s.Require().Equal(manifest, printer.GetLines()[0])
})
s.Run("Install multiple plugins, some failing", func() {
printer.Clean()
pluginURL1 := "https://example.com/plugin1.tar.gz"
pluginURL2 := "https://example.com/plugin2.tar.gz"
manifest1 := &model.Manifest{Name: "plugin one"}
args := []string{pluginURL1, pluginURL2}
s.client.
EXPECT().
InstallPluginFromURL(context.TODO(), pluginURL1, false).
Return(manifest1, &model.Response{}, nil).
Times(1)
s.client.
EXPECT().
InstallPluginFromURL(context.TODO(), pluginURL2, false).
Return(nil, &model.Response{}, errors.New("mock error")).
Times(1)
err := pluginInstallURLCmdF(s.client, &cobra.Command{}, args)
s.Require().ErrorContains(err, "mock error")
s.Require().Len(printer.GetErrorLines(), 1)
s.Require().Equal("Unable to install plugin from URL \"https://example.com/plugin2.tar.gz\". Error: mock error", printer.GetErrorLines()[0])
s.Require().Len(printer.GetLines(), 1)
s.Require().Equal(manifest1, printer.GetLines()[0])
})
}
func (s *MmctlUnitTestSuite) TestPluginDisableCmd() {
s.Run("Disable 1 plugin", func() {
printer.Clean()
arg := "plug1"
s.client.
EXPECT().
DisablePlugin(context.TODO(), arg).
Return(&model.Response{StatusCode: http.StatusBadRequest}, nil).
Times(1)
err := pluginDisableCmdF(s.client, &cobra.Command{}, []string{arg})
s.Require().Nil(err)
s.Require().Len(printer.GetLines(), 1)
s.Require().Equal(printer.GetLines()[0], "Disabled plugin: "+arg)
s.Require().Len(printer.GetErrorLines(), 0)
})
s.Run("Fail to disable 1 plugin", func() {
printer.Clean()
arg := "fail1"
mockError := errors.New("mock error")
s.client.
EXPECT().
DisablePlugin(context.TODO(), arg).
Return(&model.Response{StatusCode: http.StatusBadRequest}, mockError).
Times(1)
err := pluginDisableCmdF(s.client, &cobra.Command{}, []string{arg})
s.Require().ErrorContains(err, "mock error")
s.Require().Len(printer.GetLines(), 0)
s.Require().Len(printer.GetErrorLines(), 1)
s.Require().Equal(printer.GetErrorLines()[0], "Unable to disable plugin: "+arg+". Error: "+mockError.Error())
})
s.Run("Disble several plugin with some errors", func() {
printer.Clean()
args := []string{"fail1", "plug2", "plug3", "fail4"}
mockError := errors.New("mock error")
for _, arg := range args {
if strings.HasPrefix(arg, "fail") {
s.client.
EXPECT().
DisablePlugin(context.TODO(), arg).
Return(&model.Response{StatusCode: http.StatusBadRequest}, mockError).
Times(1)
} else {
s.client.
EXPECT().
DisablePlugin(context.TODO(), arg).
Return(&model.Response{StatusCode: http.StatusBadRequest}, nil).
Times(1)
}
}
err := pluginDisableCmdF(s.client, &cobra.Command{}, args)
s.Require().ErrorContains(err, "mock error")
s.Require().Len(printer.GetLines(), 2)
s.Require().Equal(printer.GetLines()[0], "Disabled plugin: "+args[1])
s.Require().Equal(printer.GetLines()[1], "Disabled plugin: "+args[2])
s.Require().Len(printer.GetErrorLines(), 2)
s.Require().Equal(printer.GetErrorLines()[0], "Unable to disable plugin: "+args[0]+". Error: "+mockError.Error())
s.Require().Equal(printer.GetErrorLines()[1], "Unable to disable plugin: "+args[3]+". Error: "+mockError.Error())
})
}
func (s *MmctlUnitTestSuite) TestPluginEnableCmd() {
s.Run("Enable 1 plugin", func() {
printer.Clean()
pluginArg := "test-plugin"
s.client.
EXPECT().
EnablePlugin(context.TODO(), pluginArg).
Return(&model.Response{StatusCode: http.StatusBadRequest}, nil).
Times(1)
err := pluginEnableCmdF(s.client, &cobra.Command{}, []string{pluginArg})
s.Require().Nil(err)
s.Require().Len(printer.GetErrorLines(), 0)
s.Require().Len(printer.GetLines(), 1)
s.Require().Equal(printer.GetLines()[0], "Enabled plugin: "+pluginArg)
})
s.Run("Enable multiple plugins", func() {
printer.Clean()
plugins := []string{"plugin1", "plugin2", "plugin3"}
for _, plugin := range plugins {
s.client.
EXPECT().
EnablePlugin(context.TODO(), plugin).
Return(&model.Response{StatusCode: http.StatusBadRequest}, nil).
Times(1)
}
err := pluginEnableCmdF(s.client, &cobra.Command{}, plugins)
s.Require().Nil(err)
s.Require().Len(printer.GetLines(), 3)
s.Require().Len(printer.GetErrorLines(), 0)
s.Require().Equal(printer.GetLines()[0], "Enabled plugin: "+plugins[0])
s.Require().Equal(printer.GetLines()[1], "Enabled plugin: "+plugins[1])
s.Require().Equal(printer.GetLines()[2], "Enabled plugin: "+plugins[2])
})
s.Run("Fail to enable plugin", func() {
printer.Clean()
pluginArg := "fail-plugin"
mockErr := errors.New("mock error")
s.client.
EXPECT().
EnablePlugin(context.TODO(), pluginArg).
Return(&model.Response{StatusCode: http.StatusBadRequest}, mockErr).
Times(1)
err := pluginEnableCmdF(s.client, &cobra.Command{}, []string{pluginArg})
s.Require().ErrorContains(err, "mock error")
s.Require().Len(printer.GetLines(), 0)
s.Require().Len(printer.GetErrorLines(), 1)
s.Require().Equal(printer.GetErrorLines()[0], "Unable to enable plugin: "+pluginArg+". Error: "+mockErr.Error())
})
s.Run("Enable multiple plugins with some having errors", func() {
printer.Clean()
okPlugins := []string{"ok-plugin-1", "ok-plugin-2"}
failPlugins := []string{"fail-plugin-1", "fail-plugin-2"}
allPlugins := okPlugins
allPlugins = append(allPlugins, failPlugins...)
mockErr := errors.New("mock error")
for _, plugin := range okPlugins {
s.client.
EXPECT().
EnablePlugin(context.TODO(), plugin).
Return(&model.Response{StatusCode: http.StatusBadRequest}, nil).
Times(1)
}
for _, plugin := range failPlugins {
s.client.
EXPECT().
EnablePlugin(context.TODO(), plugin).
Return(&model.Response{StatusCode: http.StatusBadRequest}, mockErr).
Times(1)
}
err := pluginEnableCmdF(s.client, &cobra.Command{}, allPlugins)
s.Require().ErrorContains(err, "mock error")
s.Require().Len(printer.GetLines(), 2)
s.Require().Equal(printer.GetLines()[0], "Enabled plugin: "+okPlugins[0])
s.Require().Equal(printer.GetLines()[1], "Enabled plugin: "+okPlugins[1])
s.Require().Len(printer.GetErrorLines(), 2)
s.Require().Equal(printer.GetErrorLines()[0], "Unable to enable plugin: "+failPlugins[0]+". Error: "+mockErr.Error())
s.Require().Equal(printer.GetErrorLines()[1], "Unable to enable plugin: "+failPlugins[1]+". Error: "+mockErr.Error())
})
}
func (s *MmctlUnitTestSuite) TestPluginListCmd() {
s.Run("List JSON plugins", func() {
printer.Clean()
mockList := &model.PluginsResponse{
Active: []*model.PluginInfo{
{
Manifest: model.Manifest{
Id: "id1",
Name: "name1",
Version: "v1",
},
},
{
Manifest: model.Manifest{
Id: "id2",
Name: "name2",
Version: "v2",
},
},
{
Manifest: model.Manifest{
Id: "id3",
Name: "name3",
Version: "v3",
},
},
}, Inactive: []*model.PluginInfo{
{
Manifest: model.Manifest{
Id: "id4",
Name: "name4",
Version: "v4",
},
},
{
Manifest: model.Manifest{
Id: "id5",
Name: "name5",
Version: "v5",
},
},
{
Manifest: model.Manifest{
Id: "id6",
Name: "name6",
Version: "v6",
},
},
},
}
s.client.
EXPECT().
GetPlugins(context.TODO()).
Return(mockList, &model.Response{}, nil).
Times(1)
err := pluginListCmdF(s.client, &cobra.Command{}, nil)
s.Require().NoError(err)
s.Require().Len(printer.GetErrorLines(), 0)
s.Require().Len(printer.GetLines(), 9)
s.Require().Equal("Listing enabled plugins", printer.GetLines()[0])
for i, plugin := range mockList.Active {
s.Require().Equal(plugin, printer.GetLines()[i+1])
}
s.Require().Equal("", printer.GetLines()[4])
s.Require().Equal("Listing disabled plugins", printer.GetLines()[5])
for i, plugin := range mockList.Inactive {
s.Require().Equal(plugin, printer.GetLines()[i+6])
}
})
s.Run("List Plain Plugins", func() {
printer.Clean()
printer.SetFormat(printer.FormatPlain)
defer printer.SetFormat(printer.FormatJSON)
mockList := &model.PluginsResponse{
Active: []*model.PluginInfo{
{
Manifest: model.Manifest{
Id: "id1",
Name: "name1",
Version: "v1",
},
},
{
Manifest: model.Manifest{
Id: "id2",
Name: "name2",
Version: "v2",
},
},
{
Manifest: model.Manifest{
Id: "id3",
Name: "name3",
Version: "v3",
},
},
}, Inactive: []*model.PluginInfo{
{
Manifest: model.Manifest{
Id: "id4",
Name: "name4",
Version: "v4",
},
},
{
Manifest: model.Manifest{
Id: "id5",
Name: "name5",
Version: "v5",
},
},
{
Manifest: model.Manifest{
Id: "id6",
Name: "name6",
Version: "v6",
},
},
},
}
s.client.
EXPECT().
GetPlugins(context.TODO()).
Return(mockList, &model.Response{}, nil).
Times(1)
err := pluginListCmdF(s.client, &cobra.Command{}, nil)
s.Require().NoError(err)
s.Require().Len(printer.GetErrorLines(), 0)
s.Require().Len(printer.GetLines(), 9)
s.Require().Equal("Listing enabled plugins", printer.GetLines()[0])
for i, plugin := range mockList.Active {
s.Require().Equal(plugin.Id+": "+plugin.Name+", Version: "+plugin.Version, printer.GetLines()[i+1])
}
s.Require().Equal("", printer.GetLines()[4])
s.Require().Equal("Listing disabled plugins", printer.GetLines()[5])
for i, plugin := range mockList.Inactive {
s.Require().Equal(plugin.Id+": "+plugin.Name+", Version: "+plugin.Version, printer.GetLines()[i+6])
}
})
s.Run("GetPlugins returns error", func() {
printer.Clean()
mockError := errors.New("mock error")
s.client.
EXPECT().
GetPlugins(context.TODO()).
Return(nil, &model.Response{}, mockError).
Times(1)
err := pluginListCmdF(s.client, &cobra.Command{}, nil)
s.Require().NotNil(err)
s.Require().Len(printer.GetLines(), 0)
s.Require().EqualError(err, "Unable to list plugins. Error: "+mockError.Error())
})
}
func (s *MmctlUnitTestSuite) TestPluginDeleteCmd() {
s.Run("Delete one plugin with error", func() {
printer.Clean()
args := "plugin"
mockError := errors.New("mock error")
s.client.
EXPECT().
RemovePlugin(context.TODO(), args).
Return(&model.Response{StatusCode: http.StatusBadRequest}, mockError).
Times(1)
err := pluginDeleteCmdF(s.client, &cobra.Command{}, []string{args})
s.Require().ErrorContains(err, "mock error")
s.Require().Len(printer.GetLines(), 0)
s.Require().Len(printer.GetErrorLines(), 1)
s.Require().Equal("Unable to delete plugin: "+args+". Error: "+mockError.Error(), printer.GetErrorLines()[0])
})
s.Run("Delete one plugin with no error", func() {
printer.Clean()
args := "plugin"
s.client.
EXPECT().
RemovePlugin(context.TODO(), args).
Return(&model.Response{StatusCode: http.StatusOK}, nil).
Times(1)
err := pluginDeleteCmdF(s.client, &cobra.Command{}, []string{args})
s.Require().NoError(err)
s.Require().Len(printer.GetLines(), 1)
s.Require().Equal("Deleted plugin: "+args, printer.GetLines()[0])
s.Require().Len(printer.GetErrorLines(), 0)
})
s.Run("Delete several plugins", func() {
printer.Clean()
args := []string{
"plugin0",
"error1",
"error2",
"plugin3",
}
mockErrors := []error{
errors.New("mock error1"),
errors.New("mock error2"),
}
s.client.
EXPECT().
RemovePlugin(context.TODO(), args[0]).
Return(&model.Response{StatusCode: http.StatusOK}, nil).
Times(1)
s.client.
EXPECT().
RemovePlugin(context.TODO(), args[1]).
Return(&model.Response{StatusCode: http.StatusBadRequest}, mockErrors[0]).
Times(1)
s.client.
EXPECT().
RemovePlugin(context.TODO(), args[2]).
Return(&model.Response{StatusCode: http.StatusBadRequest}, mockErrors[1]).
Times(1)
s.client.
EXPECT().
RemovePlugin(context.TODO(), args[3]).
Return(&model.Response{StatusCode: http.StatusOK}, nil).
Times(1)
err := pluginDeleteCmdF(s.client, &cobra.Command{}, args)
s.Require().ErrorContains(err, "mock error")
s.Require().Len(printer.GetLines(), 2)
s.Require().Equal("Deleted plugin: "+args[0], printer.GetLines()[0])
s.Require().Equal("Deleted plugin: "+args[3], printer.GetLines()[1])
s.Require().Len(printer.GetErrorLines(), 2)
s.Require().Equal("Unable to delete plugin: "+args[1]+". Error: "+mockErrors[0].Error(), printer.GetErrorLines()[0])
s.Require().Equal("Unable to delete plugin: "+args[2]+". Error: "+mockErrors[1].Error(), printer.GetErrorLines()[1])
})
}