github.com/mattermosttest/mattermost-server/v5@v5.0.0-20200917143240-9dfa12e121f9/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/mattermost/mattermost-server/v5/model"
    11  	"github.com/mattermost/mattermost-server/v5/utils"
    12  	"github.com/stretchr/testify/require"
    13  )
    14  
    15  func TestPluginCommand(t *testing.T) {
    16  	th := Setup(t).InitBasic()
    17  	defer th.TearDown()
    18  
    19  	args := &model.CommandArgs{}
    20  	args.TeamId = th.BasicTeam.Id
    21  	args.ChannelId = th.BasicChannel.Id
    22  	args.UserId = th.BasicUser.Id
    23  	args.Command = "/plugin"
    24  
    25  	t.Run("error before plugin command registered", func(t *testing.T) {
    26  		_, err := th.App.ExecuteCommand(args)
    27  		require.NotNil(t, err)
    28  	})
    29  
    30  	t.Run("command handled by plugin", func(t *testing.T) {
    31  		th.App.UpdateConfig(func(cfg *model.Config) {
    32  			cfg.PluginSettings.Plugins["testloadpluginconfig"] = map[string]interface{}{
    33  				"TeamId": args.TeamId,
    34  			}
    35  		})
    36  
    37  		tearDown, pluginIds, activationErrors := SetAppEnvironmentWithPlugins(t, []string{`
    38  			package main
    39  
    40  			import (
    41  				"github.com/mattermost/mattermost-server/v5/plugin"
    42  				"github.com/mattermost/mattermost-server/v5/model"
    43  			)
    44  
    45  			type configuration struct {
    46  				TeamId string
    47  			}
    48  
    49  			type MyPlugin struct {
    50  				plugin.MattermostPlugin
    51  
    52  				configuration configuration
    53  			}
    54  
    55  			func (p *MyPlugin) OnConfigurationChange() error {
    56  				if err := p.API.LoadPluginConfiguration(&p.configuration); err != nil {
    57  					return err
    58  				}
    59  
    60  				return nil
    61  			}
    62  
    63  			func (p *MyPlugin) OnActivate() error {
    64  				err := p.API.RegisterCommand(&model.Command{
    65  					TeamId: p.configuration.TeamId,
    66  					Trigger: "plugin",
    67  					DisplayName: "Plugin Command",
    68  					AutoComplete: true,
    69  					AutoCompleteDesc: "autocomplete",
    70  				})
    71  				if err != nil {
    72  					p.API.LogError("error", "err", err)
    73  				}
    74  
    75  				return err
    76  			}
    77  
    78  			func (p *MyPlugin) ExecuteCommand(c *plugin.Context, args *model.CommandArgs) (*model.CommandResponse, *model.AppError) {
    79  				return &model.CommandResponse{
    80  					ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL,
    81  					Text: "text",
    82  				}, nil
    83  			}
    84  
    85  			func main() {
    86  				plugin.ClientMain(&MyPlugin{})
    87  			}
    88  		`}, th.App, th.App.NewPluginAPI)
    89  		defer tearDown()
    90  		require.Len(t, activationErrors, 1)
    91  		require.Nil(t, nil, activationErrors[0])
    92  
    93  		resp, err := th.App.ExecuteCommand(args)
    94  		require.Nil(t, err)
    95  		require.Equal(t, model.COMMAND_RESPONSE_TYPE_EPHEMERAL, resp.ResponseType)
    96  		require.Equal(t, "text", resp.Text)
    97  
    98  		err2 := th.App.DisablePlugin(pluginIds[0])
    99  		require.Nil(t, err2)
   100  
   101  		commands, err3 := th.App.ListAutocompleteCommands(args.TeamId, utils.T)
   102  		require.Nil(t, err3)
   103  
   104  		for _, commands := range commands {
   105  			require.NotEqual(t, "plugin", commands.Trigger)
   106  		}
   107  
   108  		th.App.RemovePlugin(pluginIds[0])
   109  	})
   110  
   111  	t.Run("re-entrant command registration on config change", func(t *testing.T) {
   112  		th.App.UpdateConfig(func(cfg *model.Config) {
   113  			cfg.PluginSettings.Plugins["testloadpluginconfig"] = map[string]interface{}{
   114  				"TeamId": args.TeamId,
   115  			}
   116  		})
   117  
   118  		tearDown, pluginIds, activationErrors := SetAppEnvironmentWithPlugins(t, []string{`
   119  			package main
   120  
   121  			import (
   122  				"github.com/mattermost/mattermost-server/v5/plugin"
   123  				"github.com/mattermost/mattermost-server/v5/model"
   124  			)
   125  
   126  			type configuration struct {
   127  				TeamId string
   128  			}
   129  
   130  			type MyPlugin struct {
   131  				plugin.MattermostPlugin
   132  
   133  				configuration configuration
   134  			}
   135  
   136  			func (p *MyPlugin) OnConfigurationChange() error {
   137  				p.API.LogInfo("OnConfigurationChange")
   138  				err := p.API.LoadPluginConfiguration(&p.configuration);
   139  				if err != nil {
   140  					return err
   141  				}
   142  
   143  				p.API.LogInfo("About to register")
   144  				err = p.API.RegisterCommand(&model.Command{
   145  					TeamId: p.configuration.TeamId,
   146  					Trigger: "plugin",
   147  					DisplayName: "Plugin Command",
   148  					AutoComplete: true,
   149  					AutoCompleteDesc: "autocomplete",
   150  				})
   151  				if err != nil {
   152  					p.API.LogInfo("Registered, with error", err, err.Error())
   153  					return err
   154  				}
   155  				p.API.LogInfo("Registered, without error")
   156  				return nil
   157  			}
   158  
   159  			func (p *MyPlugin) ExecuteCommand(c *plugin.Context, commandArgs *model.CommandArgs) (*model.CommandResponse, *model.AppError) {
   160  				p.API.LogInfo("ExecuteCommand")
   161  				// Saving the plugin config eventually results in a call to
   162  				// OnConfigurationChange. This used to deadlock on account of
   163  				// effectively acquiring a RWLock reentrantly.
   164  				err := p.API.SavePluginConfig(map[string]interface{}{
   165  					"TeamId": p.configuration.TeamId,
   166  				})
   167  				if err != nil {
   168  					p.API.LogError("Failed to save plugin config", err, err.Error())
   169  					return nil, err
   170  				}
   171  				p.API.LogInfo("ExecuteCommand, saved plugin config")
   172  
   173  				return &model.CommandResponse{
   174  					ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL,
   175  					Text: "text",
   176  				}, nil
   177  			}
   178  
   179  			func main() {
   180  				plugin.ClientMain(&MyPlugin{})
   181  			}
   182  		`}, th.App, th.App.NewPluginAPI)
   183  		defer tearDown()
   184  
   185  		require.Len(t, activationErrors, 1)
   186  		require.Nil(t, nil, activationErrors[0])
   187  
   188  		wait := make(chan bool)
   189  		killed := false
   190  		go func() {
   191  			defer close(wait)
   192  
   193  			resp, err := th.App.ExecuteCommand(args)
   194  
   195  			// Ignore if we kill below.
   196  			if !killed {
   197  				require.Nil(t, err)
   198  				require.Equal(t, model.COMMAND_RESPONSE_TYPE_EPHEMERAL, resp.ResponseType)
   199  				require.Equal(t, "text", resp.Text)
   200  			}
   201  		}()
   202  
   203  		select {
   204  		case <-wait:
   205  		case <-time.After(10 * time.Second):
   206  			killed = true
   207  		}
   208  
   209  		th.App.RemovePlugin(pluginIds[0])
   210  		require.False(t, killed, "execute command appears to have deadlocked")
   211  	})
   212  
   213  	t.Run("error after plugin command unregistered", func(t *testing.T) {
   214  		_, err := th.App.ExecuteCommand(args)
   215  		require.NotNil(t, err)
   216  	})
   217  
   218  	t.Run("plugins can override built-in commands", func(t *testing.T) {
   219  		th.App.UpdateConfig(func(cfg *model.Config) {
   220  			cfg.PluginSettings.Plugins["testloadpluginconfig"] = map[string]interface{}{
   221  				"TeamId": args.TeamId,
   222  			}
   223  		})
   224  
   225  		tearDown, pluginIds, activationErrors := SetAppEnvironmentWithPlugins(t, []string{`
   226  			package main
   227  
   228  			import (
   229  				"github.com/mattermost/mattermost-server/v5/plugin"
   230  				"github.com/mattermost/mattermost-server/v5/model"
   231  			)
   232  
   233  			type configuration struct {
   234  				TeamId string
   235  			}
   236  
   237  			type MyPlugin struct {
   238  				plugin.MattermostPlugin
   239  
   240  				configuration configuration
   241  			}
   242  
   243  			func (p *MyPlugin) OnConfigurationChange() error {
   244  				if err := p.API.LoadPluginConfiguration(&p.configuration); err != nil {
   245  					return err
   246  				}
   247  
   248  				return nil
   249  			}
   250  
   251  			func (p *MyPlugin) OnActivate() error {
   252  				err := p.API.RegisterCommand(&model.Command{
   253  					TeamId: p.configuration.TeamId,
   254  					Trigger: "code",
   255  					DisplayName: "Plugin Command",
   256  					AutoComplete: true,
   257  					AutoCompleteDesc: "autocomplete",
   258  				})
   259  				if err != nil {
   260  					p.API.LogError("error", "err", err)
   261  				}
   262  
   263  				return err
   264  			}
   265  
   266  			func (p *MyPlugin) ExecuteCommand(c *plugin.Context, args *model.CommandArgs) (*model.CommandResponse, *model.AppError) {
   267  				return &model.CommandResponse{
   268  					ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL,
   269  					Text: "text",
   270  				}, nil
   271  			}
   272  
   273  			func main() {
   274  				plugin.ClientMain(&MyPlugin{})
   275  			}
   276  		`}, th.App, th.App.NewPluginAPI)
   277  		defer tearDown()
   278  		require.Len(t, activationErrors, 1)
   279  		require.Nil(t, nil, activationErrors[0])
   280  
   281  		args.Command = "/code"
   282  		resp, err := th.App.ExecuteCommand(args)
   283  		require.Nil(t, err)
   284  		require.Equal(t, model.COMMAND_RESPONSE_TYPE_EPHEMERAL, resp.ResponseType)
   285  		require.Equal(t, "text", resp.Text)
   286  
   287  		th.App.RemovePlugin(pluginIds[0])
   288  	})
   289  }