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 }