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