github.com/haalcala/mattermost-server-change-repo/v5@v5.33.2/app/plugin_hooks_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 "bytes" 8 "io" 9 "io/ioutil" 10 "net/http" 11 "net/http/httptest" 12 "os" 13 "path/filepath" 14 "testing" 15 "time" 16 17 "github.com/pkg/errors" 18 "github.com/stretchr/testify/assert" 19 "github.com/stretchr/testify/mock" 20 "github.com/stretchr/testify/require" 21 22 "github.com/mattermost/mattermost-server/v5/einterfaces/mocks" 23 "github.com/mattermost/mattermost-server/v5/model" 24 "github.com/mattermost/mattermost-server/v5/plugin" 25 "github.com/mattermost/mattermost-server/v5/plugin/plugintest" 26 "github.com/mattermost/mattermost-server/v5/utils" 27 ) 28 29 func SetAppEnvironmentWithPlugins(t *testing.T, pluginCode []string, app *App, apiFunc func(*model.Manifest) plugin.API) (func(), []string, []error) { 30 pluginDir, err := ioutil.TempDir("", "") 31 require.NoError(t, err) 32 webappPluginDir, err := ioutil.TempDir("", "") 33 require.NoError(t, err) 34 35 env, err := plugin.NewEnvironment(apiFunc, pluginDir, webappPluginDir, app.Log(), nil) 36 require.NoError(t, err) 37 38 app.SetPluginsEnvironment(env) 39 pluginIds := []string{} 40 activationErrors := []error{} 41 for _, code := range pluginCode { 42 pluginId := model.NewId() 43 backend := filepath.Join(pluginDir, pluginId, "backend.exe") 44 utils.CompileGo(t, code, backend) 45 46 ioutil.WriteFile(filepath.Join(pluginDir, pluginId, "plugin.json"), []byte(`{"id": "`+pluginId+`", "backend": {"executable": "backend.exe"}}`), 0600) 47 _, _, activationErr := env.Activate(pluginId) 48 pluginIds = append(pluginIds, pluginId) 49 activationErrors = append(activationErrors, activationErr) 50 51 app.UpdateConfig(func(cfg *model.Config) { 52 cfg.PluginSettings.PluginStates[pluginId] = &model.PluginState{ 53 Enable: true, 54 } 55 }) 56 } 57 58 return func() { 59 os.RemoveAll(pluginDir) 60 os.RemoveAll(webappPluginDir) 61 }, pluginIds, activationErrors 62 } 63 64 func TestHookMessageWillBePosted(t *testing.T) { 65 t.Run("rejected", func(t *testing.T) { 66 th := Setup(t).InitBasic() 67 defer th.TearDown() 68 69 tearDown, _, _ := SetAppEnvironmentWithPlugins(t, []string{ 70 ` 71 package main 72 73 import ( 74 "github.com/mattermost/mattermost-server/v5/plugin" 75 "github.com/mattermost/mattermost-server/v5/model" 76 ) 77 78 type MyPlugin struct { 79 plugin.MattermostPlugin 80 } 81 82 func (p *MyPlugin) MessageWillBePosted(c *plugin.Context, post *model.Post) (*model.Post, string) { 83 return nil, "rejected" 84 } 85 86 func main() { 87 plugin.ClientMain(&MyPlugin{}) 88 } 89 `, 90 }, th.App, th.App.NewPluginAPI) 91 defer tearDown() 92 93 post := &model.Post{ 94 UserId: th.BasicUser.Id, 95 ChannelId: th.BasicChannel.Id, 96 Message: "message_", 97 CreateAt: model.GetMillis() - 10000, 98 } 99 _, err := th.App.CreatePost(post, th.BasicChannel, false, true) 100 if assert.NotNil(t, err) { 101 assert.Equal(t, "Post rejected by plugin. rejected", err.Message) 102 } 103 }) 104 105 t.Run("rejected, returned post ignored", func(t *testing.T) { 106 th := Setup(t).InitBasic() 107 defer th.TearDown() 108 109 tearDown, _, _ := SetAppEnvironmentWithPlugins(t, []string{ 110 ` 111 package main 112 113 import ( 114 "github.com/mattermost/mattermost-server/v5/plugin" 115 "github.com/mattermost/mattermost-server/v5/model" 116 ) 117 118 type MyPlugin struct { 119 plugin.MattermostPlugin 120 } 121 122 func (p *MyPlugin) MessageWillBePosted(c *plugin.Context, post *model.Post) (*model.Post, string) { 123 post.Message = "ignored" 124 return post, "rejected" 125 } 126 127 func main() { 128 plugin.ClientMain(&MyPlugin{}) 129 } 130 `, 131 }, th.App, th.App.NewPluginAPI) 132 defer tearDown() 133 134 post := &model.Post{ 135 UserId: th.BasicUser.Id, 136 ChannelId: th.BasicChannel.Id, 137 Message: "message_", 138 CreateAt: model.GetMillis() - 10000, 139 } 140 _, err := th.App.CreatePost(post, th.BasicChannel, false, true) 141 if assert.NotNil(t, err) { 142 assert.Equal(t, "Post rejected by plugin. rejected", err.Message) 143 } 144 }) 145 146 t.Run("allowed", func(t *testing.T) { 147 th := Setup(t).InitBasic() 148 defer th.TearDown() 149 150 tearDown, _, _ := SetAppEnvironmentWithPlugins(t, []string{ 151 ` 152 package main 153 154 import ( 155 "github.com/mattermost/mattermost-server/v5/plugin" 156 "github.com/mattermost/mattermost-server/v5/model" 157 ) 158 159 type MyPlugin struct { 160 plugin.MattermostPlugin 161 } 162 163 func (p *MyPlugin) MessageWillBePosted(c *plugin.Context, post *model.Post) (*model.Post, string) { 164 return nil, "" 165 } 166 167 func main() { 168 plugin.ClientMain(&MyPlugin{}) 169 } 170 `, 171 }, th.App, th.App.NewPluginAPI) 172 defer tearDown() 173 174 post := &model.Post{ 175 UserId: th.BasicUser.Id, 176 ChannelId: th.BasicChannel.Id, 177 Message: "message", 178 CreateAt: model.GetMillis() - 10000, 179 } 180 post, err := th.App.CreatePost(post, th.BasicChannel, false, true) 181 require.Nil(t, err) 182 183 assert.Equal(t, "message", post.Message) 184 retrievedPost, errSingle := th.App.Srv().Store.Post().GetSingle(post.Id) 185 require.NoError(t, errSingle) 186 assert.Equal(t, "message", retrievedPost.Message) 187 }) 188 189 t.Run("updated", func(t *testing.T) { 190 th := Setup(t).InitBasic() 191 defer th.TearDown() 192 193 tearDown, _, _ := SetAppEnvironmentWithPlugins(t, []string{ 194 ` 195 package main 196 197 import ( 198 "github.com/mattermost/mattermost-server/v5/plugin" 199 "github.com/mattermost/mattermost-server/v5/model" 200 ) 201 202 type MyPlugin struct { 203 plugin.MattermostPlugin 204 } 205 206 func (p *MyPlugin) MessageWillBePosted(c *plugin.Context, post *model.Post) (*model.Post, string) { 207 post.Message = post.Message + "_fromplugin" 208 return post, "" 209 } 210 211 func main() { 212 plugin.ClientMain(&MyPlugin{}) 213 } 214 `, 215 }, th.App, th.App.NewPluginAPI) 216 defer tearDown() 217 218 post := &model.Post{ 219 UserId: th.BasicUser.Id, 220 ChannelId: th.BasicChannel.Id, 221 Message: "message", 222 CreateAt: model.GetMillis() - 10000, 223 } 224 post, err := th.App.CreatePost(post, th.BasicChannel, false, true) 225 require.Nil(t, err) 226 227 assert.Equal(t, "message_fromplugin", post.Message) 228 retrievedPost, errSingle := th.App.Srv().Store.Post().GetSingle(post.Id) 229 require.NoError(t, errSingle) 230 assert.Equal(t, "message_fromplugin", retrievedPost.Message) 231 }) 232 233 t.Run("multiple updated", func(t *testing.T) { 234 th := Setup(t).InitBasic() 235 defer th.TearDown() 236 237 tearDown, _, _ := SetAppEnvironmentWithPlugins(t, []string{ 238 ` 239 package main 240 241 import ( 242 "github.com/mattermost/mattermost-server/v5/plugin" 243 "github.com/mattermost/mattermost-server/v5/model" 244 ) 245 246 type MyPlugin struct { 247 plugin.MattermostPlugin 248 } 249 250 func (p *MyPlugin) MessageWillBePosted(c *plugin.Context, post *model.Post) (*model.Post, string) { 251 252 post.Message = "prefix_" + post.Message 253 return post, "" 254 } 255 256 func main() { 257 plugin.ClientMain(&MyPlugin{}) 258 } 259 `, 260 ` 261 package main 262 263 import ( 264 "github.com/mattermost/mattermost-server/v5/plugin" 265 "github.com/mattermost/mattermost-server/v5/model" 266 ) 267 268 type MyPlugin struct { 269 plugin.MattermostPlugin 270 } 271 272 func (p *MyPlugin) MessageWillBePosted(c *plugin.Context, post *model.Post) (*model.Post, string) { 273 post.Message = post.Message + "_suffix" 274 return post, "" 275 } 276 277 func main() { 278 plugin.ClientMain(&MyPlugin{}) 279 } 280 `, 281 }, th.App, th.App.NewPluginAPI) 282 defer tearDown() 283 284 post := &model.Post{ 285 UserId: th.BasicUser.Id, 286 ChannelId: th.BasicChannel.Id, 287 Message: "message", 288 CreateAt: model.GetMillis() - 10000, 289 } 290 post, err := th.App.CreatePost(post, th.BasicChannel, false, true) 291 require.Nil(t, err) 292 assert.Equal(t, "prefix_message_suffix", post.Message) 293 }) 294 } 295 296 func TestHookMessageHasBeenPosted(t *testing.T) { 297 th := Setup(t).InitBasic() 298 defer th.TearDown() 299 300 var mockAPI plugintest.API 301 mockAPI.On("LoadPluginConfiguration", mock.Anything).Return(nil) 302 mockAPI.On("LogDebug", "message").Return(nil) 303 304 tearDown, _, _ := SetAppEnvironmentWithPlugins(t, 305 []string{ 306 ` 307 package main 308 309 import ( 310 "github.com/mattermost/mattermost-server/v5/plugin" 311 "github.com/mattermost/mattermost-server/v5/model" 312 ) 313 314 type MyPlugin struct { 315 plugin.MattermostPlugin 316 } 317 318 func (p *MyPlugin) MessageHasBeenPosted(c *plugin.Context, post *model.Post) { 319 p.API.LogDebug(post.Message) 320 } 321 322 func main() { 323 plugin.ClientMain(&MyPlugin{}) 324 } 325 `}, th.App, func(*model.Manifest) plugin.API { return &mockAPI }) 326 defer tearDown() 327 328 post := &model.Post{ 329 UserId: th.BasicUser.Id, 330 ChannelId: th.BasicChannel.Id, 331 Message: "message", 332 CreateAt: model.GetMillis() - 10000, 333 } 334 _, err := th.App.CreatePost(post, th.BasicChannel, false, true) 335 require.Nil(t, err) 336 } 337 338 func TestHookMessageWillBeUpdated(t *testing.T) { 339 th := Setup(t).InitBasic() 340 defer th.TearDown() 341 342 tearDown, _, _ := SetAppEnvironmentWithPlugins(t, 343 []string{ 344 ` 345 package main 346 347 import ( 348 "github.com/mattermost/mattermost-server/v5/plugin" 349 "github.com/mattermost/mattermost-server/v5/model" 350 ) 351 352 type MyPlugin struct { 353 plugin.MattermostPlugin 354 } 355 356 func (p *MyPlugin) MessageWillBeUpdated(c *plugin.Context, newPost, oldPost *model.Post) (*model.Post, string) { 357 newPost.Message = newPost.Message + "fromplugin" 358 return newPost, "" 359 } 360 361 func main() { 362 plugin.ClientMain(&MyPlugin{}) 363 } 364 `}, th.App, th.App.NewPluginAPI) 365 defer tearDown() 366 367 post := &model.Post{ 368 UserId: th.BasicUser.Id, 369 ChannelId: th.BasicChannel.Id, 370 Message: "message_", 371 CreateAt: model.GetMillis() - 10000, 372 } 373 post, err := th.App.CreatePost(post, th.BasicChannel, false, true) 374 require.Nil(t, err) 375 assert.Equal(t, "message_", post.Message) 376 post.Message = post.Message + "edited_" 377 post, err = th.App.UpdatePost(post, true) 378 require.Nil(t, err) 379 assert.Equal(t, "message_edited_fromplugin", post.Message) 380 } 381 382 func TestHookMessageHasBeenUpdated(t *testing.T) { 383 th := Setup(t).InitBasic() 384 defer th.TearDown() 385 386 var mockAPI plugintest.API 387 mockAPI.On("LoadPluginConfiguration", mock.Anything).Return(nil) 388 mockAPI.On("LogDebug", "message_edited").Return(nil) 389 mockAPI.On("LogDebug", "message_").Return(nil) 390 tearDown, _, _ := SetAppEnvironmentWithPlugins(t, 391 []string{ 392 ` 393 package main 394 395 import ( 396 "github.com/mattermost/mattermost-server/v5/plugin" 397 "github.com/mattermost/mattermost-server/v5/model" 398 ) 399 400 type MyPlugin struct { 401 plugin.MattermostPlugin 402 } 403 404 func (p *MyPlugin) MessageHasBeenUpdated(c *plugin.Context, newPost, oldPost *model.Post) { 405 p.API.LogDebug(newPost.Message) 406 p.API.LogDebug(oldPost.Message) 407 } 408 409 func main() { 410 plugin.ClientMain(&MyPlugin{}) 411 } 412 `}, th.App, func(*model.Manifest) plugin.API { return &mockAPI }) 413 defer tearDown() 414 415 post := &model.Post{ 416 UserId: th.BasicUser.Id, 417 ChannelId: th.BasicChannel.Id, 418 Message: "message_", 419 CreateAt: model.GetMillis() - 10000, 420 } 421 post, err := th.App.CreatePost(post, th.BasicChannel, false, true) 422 require.Nil(t, err) 423 assert.Equal(t, "message_", post.Message) 424 post.Message = post.Message + "edited" 425 _, err = th.App.UpdatePost(post, true) 426 require.Nil(t, err) 427 } 428 429 func TestHookFileWillBeUploaded(t *testing.T) { 430 t.Run("rejected", func(t *testing.T) { 431 th := Setup(t).InitBasic() 432 defer th.TearDown() 433 434 var mockAPI plugintest.API 435 mockAPI.On("LoadPluginConfiguration", mock.Anything).Return(nil) 436 mockAPI.On("LogDebug", "testhook.txt").Return(nil) 437 mockAPI.On("LogDebug", "inputfile").Return(nil) 438 tearDown, _, _ := SetAppEnvironmentWithPlugins(t, []string{ 439 ` 440 package main 441 442 import ( 443 "io" 444 "github.com/mattermost/mattermost-server/v5/plugin" 445 "github.com/mattermost/mattermost-server/v5/model" 446 ) 447 448 type MyPlugin struct { 449 plugin.MattermostPlugin 450 } 451 452 func (p *MyPlugin) FileWillBeUploaded(c *plugin.Context, info *model.FileInfo, file io.Reader, output io.Writer) (*model.FileInfo, string) { 453 return nil, "rejected" 454 } 455 456 func main() { 457 plugin.ClientMain(&MyPlugin{}) 458 } 459 `, 460 }, th.App, func(*model.Manifest) plugin.API { return &mockAPI }) 461 defer tearDown() 462 463 _, err := th.App.UploadFiles( 464 "noteam", 465 th.BasicChannel.Id, 466 th.BasicUser.Id, 467 []io.ReadCloser{ioutil.NopCloser(bytes.NewBufferString("inputfile"))}, 468 []string{"testhook.txt"}, 469 []string{}, 470 time.Now(), 471 ) 472 if assert.NotNil(t, err) { 473 assert.Equal(t, "File rejected by plugin. rejected", err.Message) 474 } 475 }) 476 477 t.Run("rejected, returned file ignored", func(t *testing.T) { 478 th := Setup(t).InitBasic() 479 defer th.TearDown() 480 481 var mockAPI plugintest.API 482 mockAPI.On("LoadPluginConfiguration", mock.Anything).Return(nil) 483 mockAPI.On("LogDebug", "testhook.txt").Return(nil) 484 mockAPI.On("LogDebug", "inputfile").Return(nil) 485 tearDown, _, _ := SetAppEnvironmentWithPlugins(t, []string{ 486 ` 487 package main 488 489 import ( 490 "fmt" 491 "io" 492 "github.com/mattermost/mattermost-server/v5/plugin" 493 "github.com/mattermost/mattermost-server/v5/model" 494 ) 495 496 type MyPlugin struct { 497 plugin.MattermostPlugin 498 } 499 500 func (p *MyPlugin) FileWillBeUploaded(c *plugin.Context, info *model.FileInfo, file io.Reader, output io.Writer) (*model.FileInfo, string) { 501 n, err := output.Write([]byte("ignored")) 502 if err != nil { 503 return info, fmt.Sprintf("FAILED to write output file n: %v, err: %v", n, err) 504 } 505 info.Name = "ignored" 506 return info, "rejected" 507 } 508 509 func main() { 510 plugin.ClientMain(&MyPlugin{}) 511 } 512 `, 513 }, th.App, func(*model.Manifest) plugin.API { return &mockAPI }) 514 defer tearDown() 515 516 _, err := th.App.UploadFiles( 517 "noteam", 518 th.BasicChannel.Id, 519 th.BasicUser.Id, 520 []io.ReadCloser{ioutil.NopCloser(bytes.NewBufferString("inputfile"))}, 521 []string{"testhook.txt"}, 522 []string{}, 523 time.Now(), 524 ) 525 if assert.NotNil(t, err) { 526 assert.Equal(t, "File rejected by plugin. rejected", err.Message) 527 } 528 }) 529 530 t.Run("allowed", func(t *testing.T) { 531 th := Setup(t).InitBasic() 532 defer th.TearDown() 533 534 var mockAPI plugintest.API 535 mockAPI.On("LoadPluginConfiguration", mock.Anything).Return(nil) 536 mockAPI.On("LogDebug", "testhook.txt").Return(nil) 537 mockAPI.On("LogDebug", "inputfile").Return(nil) 538 tearDown, _, _ := SetAppEnvironmentWithPlugins(t, []string{ 539 ` 540 package main 541 542 import ( 543 "io" 544 "github.com/mattermost/mattermost-server/v5/plugin" 545 "github.com/mattermost/mattermost-server/v5/model" 546 ) 547 548 type MyPlugin struct { 549 plugin.MattermostPlugin 550 } 551 552 func (p *MyPlugin) FileWillBeUploaded(c *plugin.Context, info *model.FileInfo, file io.Reader, output io.Writer) (*model.FileInfo, string) { 553 return nil, "" 554 } 555 556 func main() { 557 plugin.ClientMain(&MyPlugin{}) 558 } 559 `, 560 }, th.App, func(*model.Manifest) plugin.API { return &mockAPI }) 561 defer tearDown() 562 563 response, err := th.App.UploadFiles( 564 "noteam", 565 th.BasicChannel.Id, 566 th.BasicUser.Id, 567 []io.ReadCloser{ioutil.NopCloser(bytes.NewBufferString("inputfile"))}, 568 []string{"testhook.txt"}, 569 []string{}, 570 time.Now(), 571 ) 572 assert.Nil(t, err) 573 assert.NotNil(t, response) 574 assert.Equal(t, 1, len(response.FileInfos)) 575 576 fileId := response.FileInfos[0].Id 577 fileInfo, err := th.App.GetFileInfo(fileId) 578 assert.Nil(t, err) 579 assert.NotNil(t, fileInfo) 580 assert.Equal(t, "testhook.txt", fileInfo.Name) 581 582 fileReader, err := th.App.FileReader(fileInfo.Path) 583 assert.Nil(t, err) 584 var resultBuf bytes.Buffer 585 io.Copy(&resultBuf, fileReader) 586 assert.Equal(t, "inputfile", resultBuf.String()) 587 }) 588 589 t.Run("updated", func(t *testing.T) { 590 th := Setup(t).InitBasic() 591 defer th.TearDown() 592 593 var mockAPI plugintest.API 594 mockAPI.On("LoadPluginConfiguration", mock.Anything).Return(nil) 595 mockAPI.On("LogDebug", "testhook.txt").Return(nil) 596 mockAPI.On("LogDebug", "inputfile").Return(nil) 597 tearDown, _, _ := SetAppEnvironmentWithPlugins(t, []string{ 598 ` 599 package main 600 601 import ( 602 "io" 603 "fmt" 604 "bytes" 605 "github.com/mattermost/mattermost-server/v5/plugin" 606 "github.com/mattermost/mattermost-server/v5/model" 607 ) 608 609 type MyPlugin struct { 610 plugin.MattermostPlugin 611 } 612 613 func (p *MyPlugin) FileWillBeUploaded(c *plugin.Context, info *model.FileInfo, file io.Reader, output io.Writer) (*model.FileInfo, string) { 614 var buf bytes.Buffer 615 n, err := buf.ReadFrom(file) 616 if err != nil { 617 panic(fmt.Sprintf("buf.ReadFrom failed, reading %d bytes: %s", err.Error())) 618 } 619 620 outbuf := bytes.NewBufferString("changedtext") 621 n, err = io.Copy(output, outbuf) 622 if err != nil { 623 panic(fmt.Sprintf("io.Copy failed after %d bytes: %s", n, err.Error())) 624 } 625 if n != 11 { 626 panic(fmt.Sprintf("io.Copy only copied %d bytes", n)) 627 } 628 info.Name = "modifiedinfo" 629 return info, "" 630 } 631 632 func main() { 633 plugin.ClientMain(&MyPlugin{}) 634 } 635 `, 636 }, th.App, func(*model.Manifest) plugin.API { return &mockAPI }) 637 defer tearDown() 638 639 response, err := th.App.UploadFiles( 640 "noteam", 641 th.BasicChannel.Id, 642 th.BasicUser.Id, 643 []io.ReadCloser{ioutil.NopCloser(bytes.NewBufferString("inputfile"))}, 644 []string{"testhook.txt"}, 645 []string{}, 646 time.Now(), 647 ) 648 assert.Nil(t, err) 649 assert.NotNil(t, response) 650 assert.Equal(t, 1, len(response.FileInfos)) 651 fileId := response.FileInfos[0].Id 652 653 fileInfo, err := th.App.GetFileInfo(fileId) 654 assert.Nil(t, err) 655 assert.NotNil(t, fileInfo) 656 assert.Equal(t, "modifiedinfo", fileInfo.Name) 657 658 fileReader, err := th.App.FileReader(fileInfo.Path) 659 assert.Nil(t, err) 660 var resultBuf bytes.Buffer 661 io.Copy(&resultBuf, fileReader) 662 assert.Equal(t, "changedtext", resultBuf.String()) 663 }) 664 } 665 666 func TestUserWillLogIn_Blocked(t *testing.T) { 667 th := Setup(t).InitBasic() 668 defer th.TearDown() 669 670 err := th.App.UpdatePassword(th.BasicUser, "hunter2") 671 assert.Nil(t, err, "Error updating user password: %s", err) 672 tearDown, _, _ := SetAppEnvironmentWithPlugins(t, 673 []string{ 674 ` 675 package main 676 677 import ( 678 "github.com/mattermost/mattermost-server/v5/plugin" 679 "github.com/mattermost/mattermost-server/v5/model" 680 ) 681 682 type MyPlugin struct { 683 plugin.MattermostPlugin 684 } 685 686 func (p *MyPlugin) UserWillLogIn(c *plugin.Context, user *model.User) string { 687 return "Blocked By Plugin" 688 } 689 690 func main() { 691 plugin.ClientMain(&MyPlugin{}) 692 } 693 `}, th.App, th.App.NewPluginAPI) 694 defer tearDown() 695 696 r := &http.Request{} 697 w := httptest.NewRecorder() 698 err = th.App.DoLogin(w, r, th.BasicUser, "", false, false, false) 699 700 assert.Contains(t, err.Id, "Login rejected by plugin", "Expected Login rejected by plugin, got %s", err.Id) 701 } 702 703 func TestUserWillLogInIn_Passed(t *testing.T) { 704 th := Setup(t).InitBasic() 705 defer th.TearDown() 706 707 err := th.App.UpdatePassword(th.BasicUser, "hunter2") 708 709 assert.Nil(t, err, "Error updating user password: %s", err) 710 711 tearDown, _, _ := SetAppEnvironmentWithPlugins(t, 712 []string{ 713 ` 714 package main 715 716 import ( 717 "github.com/mattermost/mattermost-server/v5/plugin" 718 "github.com/mattermost/mattermost-server/v5/model" 719 ) 720 721 type MyPlugin struct { 722 plugin.MattermostPlugin 723 } 724 725 func (p *MyPlugin) UserWillLogIn(c *plugin.Context, user *model.User) string { 726 return "" 727 } 728 729 func main() { 730 plugin.ClientMain(&MyPlugin{}) 731 } 732 `}, th.App, th.App.NewPluginAPI) 733 defer tearDown() 734 735 r := &http.Request{} 736 w := httptest.NewRecorder() 737 err = th.App.DoLogin(w, r, th.BasicUser, "", false, false, false) 738 739 assert.Nil(t, err, "Expected nil, got %s", err) 740 assert.Equal(t, th.App.Session().UserId, th.BasicUser.Id) 741 } 742 743 func TestUserHasLoggedIn(t *testing.T) { 744 th := Setup(t).InitBasic() 745 defer th.TearDown() 746 747 err := th.App.UpdatePassword(th.BasicUser, "hunter2") 748 749 assert.Nil(t, err, "Error updating user password: %s", err) 750 751 tearDown, _, _ := SetAppEnvironmentWithPlugins(t, 752 []string{ 753 ` 754 package main 755 756 import ( 757 "github.com/mattermost/mattermost-server/v5/plugin" 758 "github.com/mattermost/mattermost-server/v5/model" 759 ) 760 761 type MyPlugin struct { 762 plugin.MattermostPlugin 763 } 764 765 func (p *MyPlugin) UserHasLoggedIn(c *plugin.Context, user *model.User) { 766 user.FirstName = "plugin-callback-success" 767 p.API.UpdateUser(user) 768 } 769 770 func main() { 771 plugin.ClientMain(&MyPlugin{}) 772 } 773 `}, th.App, th.App.NewPluginAPI) 774 defer tearDown() 775 776 r := &http.Request{} 777 w := httptest.NewRecorder() 778 err = th.App.DoLogin(w, r, th.BasicUser, "", false, false, false) 779 780 assert.Nil(t, err, "Expected nil, got %s", err) 781 782 time.Sleep(2 * time.Second) 783 784 user, _ := th.App.GetUser(th.BasicUser.Id) 785 786 assert.Equal(t, user.FirstName, "plugin-callback-success", "Expected firstname overwrite, got default") 787 } 788 789 func TestUserHasBeenCreated(t *testing.T) { 790 th := Setup(t) 791 defer th.TearDown() 792 793 tearDown, _, _ := SetAppEnvironmentWithPlugins(t, 794 []string{ 795 ` 796 package main 797 798 import ( 799 "github.com/mattermost/mattermost-server/v5/plugin" 800 "github.com/mattermost/mattermost-server/v5/model" 801 ) 802 803 type MyPlugin struct { 804 plugin.MattermostPlugin 805 } 806 807 func (p *MyPlugin) UserHasBeenCreated(c *plugin.Context, user *model.User) { 808 user.Nickname = "plugin-callback-success" 809 p.API.UpdateUser(user) 810 } 811 812 func main() { 813 plugin.ClientMain(&MyPlugin{}) 814 } 815 `}, th.App, th.App.NewPluginAPI) 816 defer tearDown() 817 818 user := &model.User{ 819 Email: model.NewId() + "success+test@example.com", 820 Nickname: "Darth Vader", 821 Username: "vader" + model.NewId(), 822 Password: "passwd1", 823 AuthService: "", 824 } 825 _, err := th.App.CreateUser(user) 826 require.Nil(t, err) 827 828 time.Sleep(1 * time.Second) 829 830 user, err = th.App.GetUser(user.Id) 831 require.Nil(t, err) 832 require.Equal(t, "plugin-callback-success", user.Nickname) 833 } 834 835 func TestErrorString(t *testing.T) { 836 th := Setup(t) 837 defer th.TearDown() 838 839 t.Run("errors.New", func(t *testing.T) { 840 tearDown, _, activationErrors := SetAppEnvironmentWithPlugins(t, 841 []string{ 842 ` 843 package main 844 845 import ( 846 "errors" 847 848 "github.com/mattermost/mattermost-server/v5/plugin" 849 ) 850 851 type MyPlugin struct { 852 plugin.MattermostPlugin 853 } 854 855 func (p *MyPlugin) OnActivate() error { 856 return errors.New("simulate failure") 857 } 858 859 func main() { 860 plugin.ClientMain(&MyPlugin{}) 861 } 862 `}, th.App, th.App.NewPluginAPI) 863 defer tearDown() 864 865 require.Len(t, activationErrors, 1) 866 require.Error(t, activationErrors[0]) 867 require.Contains(t, activationErrors[0].Error(), "simulate failure") 868 }) 869 870 t.Run("AppError", func(t *testing.T) { 871 tearDown, _, activationErrors := SetAppEnvironmentWithPlugins(t, 872 []string{ 873 ` 874 package main 875 876 import ( 877 "github.com/mattermost/mattermost-server/v5/plugin" 878 "github.com/mattermost/mattermost-server/v5/model" 879 ) 880 881 type MyPlugin struct { 882 plugin.MattermostPlugin 883 } 884 885 func (p *MyPlugin) OnActivate() error { 886 return model.NewAppError("where", "id", map[string]interface{}{"param": 1}, "details", 42) 887 } 888 889 func main() { 890 plugin.ClientMain(&MyPlugin{}) 891 } 892 `}, th.App, th.App.NewPluginAPI) 893 defer tearDown() 894 895 require.Len(t, activationErrors, 1) 896 require.Error(t, activationErrors[0]) 897 898 cause := errors.Cause(activationErrors[0]) 899 require.IsType(t, &model.AppError{}, cause) 900 901 // params not expected, since not exported 902 expectedErr := model.NewAppError("where", "id", nil, "details", 42) 903 require.Equal(t, expectedErr, cause) 904 }) 905 } 906 907 func TestHookContext(t *testing.T) { 908 th := Setup(t).InitBasic() 909 defer th.TearDown() 910 911 // We don't actually have a session, we are faking it so just set something arbitrarily 912 th.App.Session().Id = model.NewId() 913 th.App.requestId = model.NewId() 914 th.App.ipAddress = model.NewId() 915 th.App.acceptLanguage = model.NewId() 916 th.App.userAgent = model.NewId() 917 918 var mockAPI plugintest.API 919 mockAPI.On("LoadPluginConfiguration", mock.Anything).Return(nil) 920 mockAPI.On("LogDebug", th.App.Session().Id).Return(nil) 921 mockAPI.On("LogInfo", th.App.RequestId()).Return(nil) 922 mockAPI.On("LogError", th.App.IpAddress()).Return(nil) 923 mockAPI.On("LogWarn", th.App.AcceptLanguage()).Return(nil) 924 mockAPI.On("DeleteTeam", th.App.UserAgent()).Return(nil) 925 926 tearDown, _, _ := SetAppEnvironmentWithPlugins(t, 927 []string{ 928 ` 929 package main 930 931 import ( 932 "github.com/mattermost/mattermost-server/v5/plugin" 933 "github.com/mattermost/mattermost-server/v5/model" 934 ) 935 936 type MyPlugin struct { 937 plugin.MattermostPlugin 938 } 939 940 func (p *MyPlugin) MessageHasBeenPosted(c *plugin.Context, post *model.Post) { 941 p.API.LogDebug(c.SessionId) 942 p.API.LogInfo(c.RequestId) 943 p.API.LogError(c.IpAddress) 944 p.API.LogWarn(c.AcceptLanguage) 945 p.API.DeleteTeam(c.UserAgent) 946 } 947 948 func main() { 949 plugin.ClientMain(&MyPlugin{}) 950 } 951 `}, th.App, func(*model.Manifest) plugin.API { return &mockAPI }) 952 defer tearDown() 953 954 post := &model.Post{ 955 UserId: th.BasicUser.Id, 956 ChannelId: th.BasicChannel.Id, 957 Message: "not this", 958 CreateAt: model.GetMillis() - 10000, 959 } 960 _, err := th.App.CreatePost(post, th.BasicChannel, false, true) 961 require.Nil(t, err) 962 } 963 964 func TestActiveHooks(t *testing.T) { 965 th := Setup(t) 966 defer th.TearDown() 967 968 t.Run("", func(t *testing.T) { 969 tearDown, pluginIds, _ := SetAppEnvironmentWithPlugins(t, 970 []string{ 971 ` 972 package main 973 974 import ( 975 "github.com/mattermost/mattermost-server/v5/model" 976 "github.com/mattermost/mattermost-server/v5/plugin" 977 ) 978 979 type MyPlugin struct { 980 plugin.MattermostPlugin 981 } 982 983 func (p *MyPlugin) OnActivate() error { 984 return nil 985 } 986 987 func (p *MyPlugin) OnConfigurationChange() error { 988 return nil 989 } 990 991 func (p *MyPlugin) UserHasBeenCreated(c *plugin.Context, user *model.User) { 992 user.Nickname = "plugin-callback-success" 993 p.API.UpdateUser(user) 994 } 995 996 func main() { 997 plugin.ClientMain(&MyPlugin{}) 998 } 999 `}, th.App, th.App.NewPluginAPI) 1000 defer tearDown() 1001 1002 require.Len(t, pluginIds, 1) 1003 pluginId := pluginIds[0] 1004 1005 require.True(t, th.App.GetPluginsEnvironment().IsActive(pluginId)) 1006 user1 := &model.User{ 1007 Email: model.NewId() + "success+test@example.com", 1008 Nickname: "Darth Vader1", 1009 Username: "vader" + model.NewId(), 1010 Password: "passwd1", 1011 AuthService: "", 1012 } 1013 _, appErr := th.App.CreateUser(user1) 1014 require.Nil(t, appErr) 1015 time.Sleep(1 * time.Second) 1016 user1, appErr = th.App.GetUser(user1.Id) 1017 require.Nil(t, appErr) 1018 require.Equal(t, "plugin-callback-success", user1.Nickname) 1019 1020 // Disable plugin 1021 require.True(t, th.App.GetPluginsEnvironment().Deactivate(pluginId)) 1022 require.False(t, th.App.GetPluginsEnvironment().IsActive(pluginId)) 1023 1024 hooks, err := th.App.GetPluginsEnvironment().HooksForPlugin(pluginId) 1025 require.Error(t, err) 1026 require.Nil(t, hooks) 1027 1028 // Should fail to find pluginId as it was deleted when deactivated 1029 path, err := th.App.GetPluginsEnvironment().PublicFilesPath(pluginId) 1030 require.Error(t, err) 1031 require.Empty(t, path) 1032 }) 1033 } 1034 1035 func TestHookMetrics(t *testing.T) { 1036 th := Setup(t) 1037 defer th.TearDown() 1038 1039 t.Run("", func(t *testing.T) { 1040 metricsMock := &mocks.MetricsInterface{} 1041 1042 pluginDir, err := ioutil.TempDir("", "") 1043 require.NoError(t, err) 1044 webappPluginDir, err := ioutil.TempDir("", "") 1045 require.NoError(t, err) 1046 defer os.RemoveAll(pluginDir) 1047 defer os.RemoveAll(webappPluginDir) 1048 1049 env, err := plugin.NewEnvironment(th.App.NewPluginAPI, pluginDir, webappPluginDir, th.App.Log(), metricsMock) 1050 require.NoError(t, err) 1051 1052 th.App.SetPluginsEnvironment(env) 1053 1054 pluginId := model.NewId() 1055 backend := filepath.Join(pluginDir, pluginId, "backend.exe") 1056 code := 1057 ` 1058 package main 1059 1060 import ( 1061 "github.com/mattermost/mattermost-server/v5/model" 1062 "github.com/mattermost/mattermost-server/v5/plugin" 1063 ) 1064 1065 type MyPlugin struct { 1066 plugin.MattermostPlugin 1067 } 1068 1069 func (p *MyPlugin) OnActivate() error { 1070 return nil 1071 } 1072 1073 func (p *MyPlugin) OnConfigurationChange() error { 1074 return nil 1075 } 1076 1077 func (p *MyPlugin) UserHasBeenCreated(c *plugin.Context, user *model.User) { 1078 user.Nickname = "plugin-callback-success" 1079 p.API.UpdateUser(user) 1080 } 1081 1082 func main() { 1083 plugin.ClientMain(&MyPlugin{}) 1084 } 1085 ` 1086 utils.CompileGo(t, code, backend) 1087 ioutil.WriteFile(filepath.Join(pluginDir, pluginId, "plugin.json"), []byte(`{"id": "`+pluginId+`", "backend": {"executable": "backend.exe"}}`), 0600) 1088 1089 // Setup mocks before activating 1090 metricsMock.On("ObservePluginHookDuration", pluginId, "Implemented", true, mock.Anything).Return() 1091 metricsMock.On("ObservePluginHookDuration", pluginId, "OnActivate", true, mock.Anything).Return() 1092 metricsMock.On("ObservePluginHookDuration", pluginId, "OnDeactivate", true, mock.Anything).Return() 1093 metricsMock.On("ObservePluginHookDuration", pluginId, "OnConfigurationChange", true, mock.Anything).Return() 1094 metricsMock.On("ObservePluginHookDuration", pluginId, "UserHasBeenCreated", true, mock.Anything).Return() 1095 1096 // Don't care about these calls. 1097 metricsMock.On("ObservePluginApiDuration", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return() 1098 metricsMock.On("ObservePluginMultiHookIterationDuration", mock.Anything, mock.Anything, mock.Anything).Return() 1099 metricsMock.On("ObservePluginMultiHookDuration", mock.Anything).Return() 1100 1101 _, _, activationErr := env.Activate(pluginId) 1102 require.NoError(t, activationErr) 1103 1104 th.App.UpdateConfig(func(cfg *model.Config) { 1105 cfg.PluginSettings.PluginStates[pluginId] = &model.PluginState{ 1106 Enable: true, 1107 } 1108 }) 1109 1110 require.True(t, th.App.GetPluginsEnvironment().IsActive(pluginId)) 1111 1112 user1 := &model.User{ 1113 Email: model.NewId() + "success+test@example.com", 1114 Nickname: "Darth Vader1", 1115 Username: "vader" + model.NewId(), 1116 Password: "passwd1", 1117 AuthService: "", 1118 } 1119 _, appErr := th.App.CreateUser(user1) 1120 require.Nil(t, appErr) 1121 time.Sleep(1 * time.Second) 1122 user1, appErr = th.App.GetUser(user1.Id) 1123 require.Nil(t, appErr) 1124 require.Equal(t, "plugin-callback-success", user1.Nickname) 1125 1126 // Disable plugin 1127 require.True(t, th.App.GetPluginsEnvironment().Deactivate(pluginId)) 1128 require.False(t, th.App.GetPluginsEnvironment().IsActive(pluginId)) 1129 1130 metricsMock.AssertExpectations(t) 1131 }) 1132 } 1133 1134 func TestHookReactionHasBeenAdded(t *testing.T) { 1135 th := Setup(t).InitBasic() 1136 defer th.TearDown() 1137 1138 var mockAPI plugintest.API 1139 mockAPI.On("LoadPluginConfiguration", mock.Anything).Return(nil) 1140 mockAPI.On("LogDebug", "smile").Return(nil) 1141 1142 tearDown, _, _ := SetAppEnvironmentWithPlugins(t, 1143 []string{ 1144 ` 1145 package main 1146 1147 import ( 1148 "github.com/mattermost/mattermost-server/v5/plugin" 1149 "github.com/mattermost/mattermost-server/v5/model" 1150 ) 1151 1152 type MyPlugin struct { 1153 plugin.MattermostPlugin 1154 } 1155 1156 func (p *MyPlugin) ReactionHasBeenAdded(c *plugin.Context, reaction *model.Reaction) { 1157 p.API.LogDebug(reaction.EmojiName) 1158 } 1159 1160 func main() { 1161 plugin.ClientMain(&MyPlugin{}) 1162 } 1163 `}, th.App, func(*model.Manifest) plugin.API { return &mockAPI }) 1164 defer tearDown() 1165 1166 reaction := &model.Reaction{ 1167 UserId: th.BasicUser.Id, 1168 PostId: th.BasicPost.Id, 1169 EmojiName: "smile", 1170 CreateAt: model.GetMillis() - 10000, 1171 } 1172 _, err := th.App.SaveReactionForPost(reaction) 1173 require.Nil(t, err) 1174 } 1175 1176 func TestHookReactionHasBeenRemoved(t *testing.T) { 1177 th := Setup(t).InitBasic() 1178 defer th.TearDown() 1179 1180 var mockAPI plugintest.API 1181 mockAPI.On("LoadPluginConfiguration", mock.Anything).Return(nil) 1182 mockAPI.On("LogDebug", "star").Return(nil) 1183 1184 tearDown, _, _ := SetAppEnvironmentWithPlugins(t, 1185 []string{ 1186 ` 1187 package main 1188 1189 import ( 1190 "github.com/mattermost/mattermost-server/v5/plugin" 1191 "github.com/mattermost/mattermost-server/v5/model" 1192 ) 1193 1194 type MyPlugin struct { 1195 plugin.MattermostPlugin 1196 } 1197 1198 func (p *MyPlugin) ReactionHasBeenRemoved(c *plugin.Context, reaction *model.Reaction) { 1199 p.API.LogDebug(reaction.EmojiName) 1200 } 1201 1202 func main() { 1203 plugin.ClientMain(&MyPlugin{}) 1204 } 1205 `}, th.App, func(*model.Manifest) plugin.API { return &mockAPI }) 1206 defer tearDown() 1207 1208 reaction := &model.Reaction{ 1209 UserId: th.BasicUser.Id, 1210 PostId: th.BasicPost.Id, 1211 EmojiName: "star", 1212 CreateAt: model.GetMillis() - 10000, 1213 } 1214 1215 err := th.App.DeleteReactionForPost(reaction) 1216 1217 require.Nil(t, err) 1218 }