github.com/haalcala/mattermost-server-change-repo/v5@v5.33.2/app/plugin_commands_test.go (about)

     1  // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
     2  // See LICENSE.txt for license information.
     3  
     4  package app
     5  
     6  import (
     7  	"testing"
     8  	"time"
     9  
    10  	"github.com/stretchr/testify/require"
    11  
    12  	"github.com/mattermost/mattermost-server/v5/model"
    13  	"github.com/mattermost/mattermost-server/v5/utils"
    14  )
    15  
    16  func TestPluginCommand(t *testing.T) {
    17  	th := Setup(t).InitBasic()
    18  	defer th.TearDown()
    19  
    20  	args := &model.CommandArgs{}
    21  	args.TeamId = th.BasicTeam.Id
    22  	args.ChannelId = th.BasicChannel.Id
    23  	args.UserId = th.BasicUser.Id
    24  	args.Command = "/plugin"
    25  
    26  	t.Run("error before plugin command registered", func(t *testing.T) {
    27  		_, err := th.App.ExecuteCommand(args)
    28  		require.NotNil(t, err)
    29  	})
    30  
    31  	t.Run("command handled by plugin", func(t *testing.T) {
    32  		th.App.UpdateConfig(func(cfg *model.Config) {
    33  			cfg.PluginSettings.Plugins["testloadpluginconfig"] = map[string]interface{}{
    34  				"TeamId": args.TeamId,
    35  			}
    36  		})
    37  
    38  		tearDown, pluginIds, activationErrors := SetAppEnvironmentWithPlugins(t, []string{`
    39  			package main
    40  
    41  			import (
    42  				"github.com/mattermost/mattermost-server/v5/plugin"
    43  				"github.com/mattermost/mattermost-server/v5/model"
    44  			)
    45  
    46  			type configuration struct {
    47  				TeamId string
    48  			}
    49  
    50  			type MyPlugin struct {
    51  				plugin.MattermostPlugin
    52  
    53  				configuration configuration
    54  			}
    55  
    56  			func (p *MyPlugin) OnConfigurationChange() error {
    57  				if err := p.API.LoadPluginConfiguration(&p.configuration); err != nil {
    58  					return err
    59  				}
    60  
    61  				return nil
    62  			}
    63  
    64  			func (p *MyPlugin) OnActivate() error {
    65  				err := p.API.RegisterCommand(&model.Command{
    66  					TeamId: p.configuration.TeamId,
    67  					Trigger: "plugin",
    68  					DisplayName: "Plugin Command",
    69  					AutoComplete: true,
    70  					AutoCompleteDesc: "autocomplete",
    71  				})
    72  				if err != nil {
    73  					p.API.LogError("error", "err", err)
    74  				}
    75  
    76  				return err
    77  			}
    78  
    79  			func (p *MyPlugin) ExecuteCommand(c *plugin.Context, args *model.CommandArgs) (*model.CommandResponse, *model.AppError) {
    80  				return &model.CommandResponse{
    81  					ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL,
    82  					Text: "text",
    83  				}, nil
    84  			}
    85  
    86  			func main() {
    87  				plugin.ClientMain(&MyPlugin{})
    88  			}
    89  		`}, th.App, th.App.NewPluginAPI)
    90  		defer tearDown()
    91  		require.Len(t, activationErrors, 1)
    92  		require.Nil(t, nil, activationErrors[0])
    93  
    94  		resp, err := th.App.ExecuteCommand(args)
    95  		require.Nil(t, err)
    96  		require.Equal(t, model.COMMAND_RESPONSE_TYPE_EPHEMERAL, resp.ResponseType)
    97  		require.Equal(t, "text", resp.Text)
    98  
    99  		err2 := th.App.DisablePlugin(pluginIds[0])
   100  		require.Nil(t, err2)
   101  
   102  		commands, err3 := th.App.ListAutocompleteCommands(args.TeamId, utils.T)
   103  		require.Nil(t, err3)
   104  
   105  		for _, commands := range commands {
   106  			require.NotEqual(t, "plugin", commands.Trigger)
   107  		}
   108  
   109  		th.App.RemovePlugin(pluginIds[0])
   110  	})
   111  
   112  	t.Run("re-entrant command registration on config change", func(t *testing.T) {
   113  		th.App.UpdateConfig(func(cfg *model.Config) {
   114  			cfg.PluginSettings.Plugins["testloadpluginconfig"] = map[string]interface{}{
   115  				"TeamId": args.TeamId,
   116  			}
   117  		})
   118  
   119  		tearDown, pluginIds, activationErrors := SetAppEnvironmentWithPlugins(t, []string{`
   120  			package main
   121  
   122  			import (
   123  				"github.com/mattermost/mattermost-server/v5/plugin"
   124  				"github.com/mattermost/mattermost-server/v5/model"
   125  			)
   126  
   127  			type configuration struct {
   128  				TeamId string
   129  			}
   130  
   131  			type MyPlugin struct {
   132  				plugin.MattermostPlugin
   133  
   134  				configuration configuration
   135  			}
   136  
   137  			func (p *MyPlugin) OnConfigurationChange() error {
   138  				p.API.LogInfo("OnConfigurationChange")
   139  				err := p.API.LoadPluginConfiguration(&p.configuration);
   140  				if err != nil {
   141  					return err
   142  				}
   143  
   144  				p.API.LogInfo("About to register")
   145  				err = p.API.RegisterCommand(&model.Command{
   146  					TeamId: p.configuration.TeamId,
   147  					Trigger: "plugin",
   148  					DisplayName: "Plugin Command",
   149  					AutoComplete: true,
   150  					AutoCompleteDesc: "autocomplete",
   151  				})
   152  				if err != nil {
   153  					p.API.LogInfo("Registered, with error", err, err.Error())
   154  					return err
   155  				}
   156  				p.API.LogInfo("Registered, without error")
   157  				return nil
   158  			}
   159  
   160  			func (p *MyPlugin) ExecuteCommand(c *plugin.Context, commandArgs *model.CommandArgs) (*model.CommandResponse, *model.AppError) {
   161  				p.API.LogInfo("ExecuteCommand")
   162  				// Saving the plugin config eventually results in a call to
   163  				// OnConfigurationChange. This used to deadlock on account of
   164  				// effectively acquiring a RWLock reentrantly.
   165  				err := p.API.SavePluginConfig(map[string]interface{}{
   166  					"TeamId": p.configuration.TeamId,
   167  				})
   168  				if err != nil {
   169  					p.API.LogError("Failed to save plugin config", err, err.Error())
   170  					return nil, err
   171  				}
   172  				p.API.LogInfo("ExecuteCommand, saved plugin config")
   173  
   174  				return &model.CommandResponse{
   175  					ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL,
   176  					Text: "text",
   177  				}, nil
   178  			}
   179  
   180  			func main() {
   181  				plugin.ClientMain(&MyPlugin{})
   182  			}
   183  		`}, th.App, th.App.NewPluginAPI)
   184  		defer tearDown()
   185  
   186  		require.Len(t, activationErrors, 1)
   187  		require.Nil(t, nil, activationErrors[0])
   188  
   189  		wait := make(chan bool)
   190  		killed := false
   191  		go func() {
   192  			defer close(wait)
   193  
   194  			resp, err := th.App.ExecuteCommand(args)
   195  
   196  			// Ignore if we kill below.
   197  			if !killed {
   198  				require.Nil(t, err)
   199  				require.Equal(t, model.COMMAND_RESPONSE_TYPE_EPHEMERAL, resp.ResponseType)
   200  				require.Equal(t, "text", resp.Text)
   201  			}
   202  		}()
   203  
   204  		select {
   205  		case <-wait:
   206  		case <-time.After(10 * time.Second):
   207  			killed = true
   208  		}
   209  
   210  		th.App.RemovePlugin(pluginIds[0])
   211  		require.False(t, killed, "execute command appears to have deadlocked")
   212  	})
   213  
   214  	t.Run("error after plugin command unregistered", func(t *testing.T) {
   215  		_, err := th.App.ExecuteCommand(args)
   216  		require.NotNil(t, err)
   217  	})
   218  
   219  	t.Run("plugins can override built-in commands", func(t *testing.T) {
   220  		th.App.UpdateConfig(func(cfg *model.Config) {
   221  			cfg.PluginSettings.Plugins["testloadpluginconfig"] = map[string]interface{}{
   222  				"TeamId": args.TeamId,
   223  			}
   224  		})
   225  
   226  		tearDown, pluginIds, activationErrors := SetAppEnvironmentWithPlugins(t, []string{`
   227  			package main
   228  
   229  			import (
   230  				"github.com/mattermost/mattermost-server/v5/plugin"
   231  				"github.com/mattermost/mattermost-server/v5/model"
   232  			)
   233  
   234  			type configuration struct {
   235  				TeamId string
   236  			}
   237  
   238  			type MyPlugin struct {
   239  				plugin.MattermostPlugin
   240  
   241  				configuration configuration
   242  			}
   243  
   244  			func (p *MyPlugin) OnConfigurationChange() error {
   245  				if err := p.API.LoadPluginConfiguration(&p.configuration); err != nil {
   246  					return err
   247  				}
   248  
   249  				return nil
   250  			}
   251  
   252  			func (p *MyPlugin) OnActivate() error {
   253  				err := p.API.RegisterCommand(&model.Command{
   254  					TeamId: p.configuration.TeamId,
   255  					Trigger: "code",
   256  					DisplayName: "Plugin Command",
   257  					AutoComplete: true,
   258  					AutoCompleteDesc: "autocomplete",
   259  				})
   260  				if err != nil {
   261  					p.API.LogError("error", "err", err)
   262  				}
   263  
   264  				return err
   265  			}
   266  
   267  			func (p *MyPlugin) ExecuteCommand(c *plugin.Context, args *model.CommandArgs) (*model.CommandResponse, *model.AppError) {
   268  				return &model.CommandResponse{
   269  					ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL,
   270  					Text: "text",
   271  				}, nil
   272  			}
   273  
   274  			func main() {
   275  				plugin.ClientMain(&MyPlugin{})
   276  			}
   277  		`}, th.App, th.App.NewPluginAPI)
   278  		defer tearDown()
   279  		require.Len(t, activationErrors, 1)
   280  		require.Nil(t, nil, activationErrors[0])
   281  
   282  		args.Command = "/code"
   283  		resp, err := th.App.ExecuteCommand(args)
   284  		require.Nil(t, err)
   285  		require.Equal(t, model.COMMAND_RESPONSE_TYPE_EPHEMERAL, resp.ResponseType)
   286  		require.Equal(t, "text", resp.Text)
   287  
   288  		th.App.RemovePlugin(pluginIds[0])
   289  	})
   290  	t.Run("plugin has crashed before execution of command", func(t *testing.T) {
   291  		tearDown, pluginIds, activationErrors := SetAppEnvironmentWithPlugins(t, []string{`
   292  			package main
   293  
   294  			import (
   295  				"github.com/mattermost/mattermost-server/v5/plugin"
   296  				"github.com/mattermost/mattermost-server/v5/model"
   297  			)
   298  
   299  			type MyPlugin struct {
   300  				plugin.MattermostPlugin
   301  
   302  			}
   303  
   304  			func (p *MyPlugin) OnActivate() error {
   305  				err := p.API.RegisterCommand(&model.Command{
   306  					Trigger: "code",
   307  				})
   308  				if err != nil {
   309  					p.API.LogError("error", "err", err)
   310  				}
   311  				panic("Uncaught Error")
   312  
   313  				return err
   314  			}
   315  
   316  			func (p *MyPlugin) ExecuteCommand(c *plugin.Context, args *model.CommandArgs) (*model.CommandResponse, *model.AppError) {
   317  				return &model.CommandResponse{}, nil
   318  			}
   319  
   320  			func main() {
   321  				plugin.ClientMain(&MyPlugin{})
   322  			}
   323  		`}, th.App, th.App.NewPluginAPI)
   324  		defer tearDown()
   325  		require.Len(t, activationErrors, 1)
   326  		require.Nil(t, nil, activationErrors[0])
   327  		args.Command = "/code"
   328  		resp, err := th.App.ExecuteCommand(args)
   329  		require.Nil(t, resp)
   330  		require.NotNil(t, err)
   331  		require.Equal(t, err.Id, "model.plugin_command_error.error.app_error")
   332  		th.App.RemovePlugin(pluginIds[0])
   333  	})
   334  
   335  	t.Run("plugin has crashed due to the execution of the command", func(t *testing.T) {
   336  		tearDown, pluginIds, activationErrors := SetAppEnvironmentWithPlugins(t, []string{`
   337  			package main
   338  
   339  			import (
   340  				"github.com/mattermost/mattermost-server/v5/plugin"
   341  				"github.com/mattermost/mattermost-server/v5/model"
   342  			)
   343  
   344  			type MyPlugin struct {
   345  				plugin.MattermostPlugin
   346  
   347  			}
   348  
   349  			func (p *MyPlugin) OnActivate() error {
   350  				err := p.API.RegisterCommand(&model.Command{
   351  					Trigger: "code",
   352  				})
   353  				if err != nil {
   354  					p.API.LogError("error", "err", err)
   355  				}
   356  
   357  				return err
   358  			}
   359  
   360  			func (p *MyPlugin) ExecuteCommand(c *plugin.Context, args *model.CommandArgs) (*model.CommandResponse, *model.AppError) {
   361  				panic("Uncaught Error")
   362  				return &model.CommandResponse{}, nil
   363  			}
   364  
   365  			func main() {
   366  				plugin.ClientMain(&MyPlugin{})
   367  			}
   368  		`}, th.App, th.App.NewPluginAPI)
   369  		defer tearDown()
   370  		require.Len(t, activationErrors, 1)
   371  		require.Nil(t, nil, activationErrors[0])
   372  		args.Command = "/code"
   373  		resp, err := th.App.ExecuteCommand(args)
   374  		require.Nil(t, resp)
   375  		require.NotNil(t, err)
   376  		require.Equal(t, err.Id, "model.plugin_command_crash.error.app_error")
   377  		th.App.RemovePlugin(pluginIds[0])
   378  	})
   379  
   380  }