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 }