github.com/haalcala/mattermost-server-change-repo/v5@v5.33.2/app/plugin_api_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 "encoding/json" 9 "errors" 10 "fmt" 11 "image" 12 "image/color" 13 "image/png" 14 "io/ioutil" 15 "net/http" 16 "net/http/httptest" 17 "os" 18 "path" 19 "path/filepath" 20 "strings" 21 "testing" 22 "time" 23 24 "github.com/stretchr/testify/assert" 25 "github.com/stretchr/testify/mock" 26 "github.com/stretchr/testify/require" 27 28 goi18n "github.com/mattermost/go-i18n/i18n" 29 "github.com/mattermost/mattermost-server/v5/einterfaces/mocks" 30 "github.com/mattermost/mattermost-server/v5/model" 31 "github.com/mattermost/mattermost-server/v5/plugin" 32 "github.com/mattermost/mattermost-server/v5/utils" 33 "github.com/mattermost/mattermost-server/v5/utils/fileutils" 34 ) 35 36 func getDefaultPluginSettingsSchema() string { 37 ret, _ := json.Marshal(model.PluginSettingsSchema{ 38 Settings: []*model.PluginSetting{ 39 {Key: "BasicChannelName", Type: "text"}, 40 {Key: "BasicChannelId", Type: "text"}, 41 {Key: "BasicTeamDisplayName", Type: "text"}, 42 {Key: "BasicTeamName", Type: "text"}, 43 {Key: "BasicTeamId", Type: "text"}, 44 {Key: "BasicUserEmail", Type: "text"}, 45 {Key: "BasicUserId", Type: "text"}, 46 {Key: "BasicUser2Email", Type: "text"}, 47 {Key: "BasicUser2Id", Type: "text"}, 48 {Key: "BasicPostMessage", Type: "text"}, 49 }, 50 }) 51 return string(ret) 52 } 53 54 func setDefaultPluginConfig(th *TestHelper, pluginId string) { 55 th.App.UpdateConfig(func(cfg *model.Config) { 56 cfg.PluginSettings.Plugins[pluginId] = map[string]interface{}{ 57 "BasicChannelName": th.BasicChannel.Name, 58 "BasicChannelId": th.BasicChannel.Id, 59 "BasicTeamName": th.BasicTeam.Name, 60 "BasicTeamId": th.BasicTeam.Id, 61 "BasicTeamDisplayName": th.BasicTeam.DisplayName, 62 "BasicUserEmail": th.BasicUser.Email, 63 "BasicUserId": th.BasicUser.Id, 64 "BasicUser2Email": th.BasicUser2.Email, 65 "BasicUser2Id": th.BasicUser2.Id, 66 "BasicPostMessage": th.BasicPost.Message, 67 } 68 }) 69 } 70 71 func setupMultiPluginApiTest(t *testing.T, pluginCodes []string, pluginManifests []string, pluginIds []string, app *App) string { 72 pluginDir, err := ioutil.TempDir("", "") 73 require.NoError(t, err) 74 t.Cleanup(func() { 75 err = os.RemoveAll(pluginDir) 76 if err != nil { 77 t.Logf("Failed to cleanup pluginDir %s", err.Error()) 78 } 79 }) 80 81 webappPluginDir, err := ioutil.TempDir("", "") 82 require.NoError(t, err) 83 t.Cleanup(func() { 84 err = os.RemoveAll(webappPluginDir) 85 if err != nil { 86 t.Logf("Failed to cleanup webappPluginDir %s", err.Error()) 87 } 88 }) 89 90 env, err := plugin.NewEnvironment(app.NewPluginAPI, pluginDir, webappPluginDir, app.Log(), nil) 91 require.NoError(t, err) 92 93 require.Equal(t, len(pluginCodes), len(pluginIds)) 94 require.Equal(t, len(pluginManifests), len(pluginIds)) 95 96 for i, pluginId := range pluginIds { 97 backend := filepath.Join(pluginDir, pluginId, "backend.exe") 98 utils.CompileGo(t, pluginCodes[i], backend) 99 100 ioutil.WriteFile(filepath.Join(pluginDir, pluginId, "plugin.json"), []byte(pluginManifests[i]), 0600) 101 manifest, activated, reterr := env.Activate(pluginId) 102 require.NoError(t, reterr) 103 require.NotNil(t, manifest) 104 require.True(t, activated) 105 106 app.UpdateConfig(func(cfg *model.Config) { 107 cfg.PluginSettings.PluginStates[pluginId] = &model.PluginState{ 108 Enable: true, 109 } 110 }) 111 } 112 113 app.SetPluginsEnvironment(env) 114 115 return pluginDir 116 } 117 118 func setupPluginApiTest(t *testing.T, pluginCode string, pluginManifest string, pluginId string, app *App) string { 119 return setupMultiPluginApiTest(t, []string{pluginCode}, []string{pluginManifest}, []string{pluginId}, app) 120 } 121 122 func TestPublicFilesPathConfiguration(t *testing.T) { 123 th := Setup(t) 124 defer th.TearDown() 125 126 pluginID := "com.mattermost.sample" 127 128 pluginDir := setupPluginApiTest(t, 129 ` 130 package main 131 132 import ( 133 "github.com/mattermost/mattermost-server/v5/plugin" 134 ) 135 136 type MyPlugin struct { 137 plugin.MattermostPlugin 138 } 139 140 func main() { 141 plugin.ClientMain(&MyPlugin{}) 142 } 143 `, 144 `{"id": "com.mattermost.sample", "server": {"executable": "backend.exe"}, "settings_schema": {"settings": []}}`, pluginID, th.App) 145 146 publicFilesFolderInTest := filepath.Join(pluginDir, pluginID, "public") 147 publicFilesPath, err := th.App.GetPluginsEnvironment().PublicFilesPath(pluginID) 148 assert.NoError(t, err) 149 assert.Equal(t, publicFilesPath, publicFilesFolderInTest) 150 } 151 152 func TestPluginAPIGetUserPreferences(t *testing.T) { 153 th := Setup(t) 154 defer th.TearDown() 155 api := th.SetupPluginAPI() 156 157 user1, err := th.App.CreateUser(&model.User{ 158 Email: strings.ToLower(model.NewId()) + "success+test@example.com", 159 Password: "password", 160 Username: "user1" + model.NewId(), 161 }) 162 require.Nil(t, err) 163 defer th.App.PermanentDeleteUser(user1) 164 165 preferences, err := api.GetPreferencesForUser(user1.Id) 166 require.Nil(t, err) 167 assert.Equal(t, 1, len(preferences)) 168 169 assert.Equal(t, user1.Id, preferences[0].UserId) 170 assert.Equal(t, model.PREFERENCE_CATEGORY_TUTORIAL_STEPS, preferences[0].Category) 171 assert.Equal(t, user1.Id, preferences[0].Name) 172 assert.Equal(t, "0", preferences[0].Value) 173 } 174 175 func TestPluginAPIDeleteUserPreferences(t *testing.T) { 176 th := Setup(t) 177 defer th.TearDown() 178 api := th.SetupPluginAPI() 179 180 user1, err := th.App.CreateUser(&model.User{ 181 Email: strings.ToLower(model.NewId()) + "success+test@example.com", 182 Password: "password", 183 Username: "user1" + model.NewId(), 184 }) 185 require.Nil(t, err) 186 defer th.App.PermanentDeleteUser(user1) 187 188 preferences, err := api.GetPreferencesForUser(user1.Id) 189 require.Nil(t, err) 190 assert.Equal(t, 1, len(preferences)) 191 192 err = api.DeletePreferencesForUser(user1.Id, preferences) 193 require.Nil(t, err) 194 preferences, err = api.GetPreferencesForUser(user1.Id) 195 require.Nil(t, err) 196 assert.Equal(t, 0, len(preferences)) 197 198 user2, err := th.App.CreateUser(&model.User{ 199 Email: strings.ToLower(model.NewId()) + "success+test@example.com", 200 Password: "password", 201 Username: "user2" + model.NewId(), 202 }) 203 require.Nil(t, err) 204 defer th.App.PermanentDeleteUser(user2) 205 206 preference := model.Preference{ 207 Name: user2.Id, 208 UserId: user2.Id, 209 Category: model.PREFERENCE_CATEGORY_THEME, 210 Value: `{"color": "#ff0000", "color2": "#faf"}`, 211 } 212 err = api.UpdatePreferencesForUser(user2.Id, []model.Preference{preference}) 213 require.Nil(t, err) 214 215 preferences, err = api.GetPreferencesForUser(user2.Id) 216 require.Nil(t, err) 217 assert.Equal(t, 2, len(preferences)) 218 219 err = api.DeletePreferencesForUser(user2.Id, []model.Preference{preference}) 220 require.Nil(t, err) 221 preferences, err = api.GetPreferencesForUser(user2.Id) 222 require.Nil(t, err) 223 assert.Equal(t, 1, len(preferences)) 224 assert.Equal(t, model.PREFERENCE_CATEGORY_TUTORIAL_STEPS, preferences[0].Category) 225 } 226 227 func TestPluginAPIUpdateUserPreferences(t *testing.T) { 228 th := Setup(t) 229 defer th.TearDown() 230 api := th.SetupPluginAPI() 231 232 user1, err := th.App.CreateUser(&model.User{ 233 Email: strings.ToLower(model.NewId()) + "success+test@example.com", 234 Password: "password", 235 Username: "user1" + model.NewId(), 236 }) 237 require.Nil(t, err) 238 defer th.App.PermanentDeleteUser(user1) 239 240 preferences, err := api.GetPreferencesForUser(user1.Id) 241 require.Nil(t, err) 242 assert.Equal(t, 1, len(preferences)) 243 assert.Equal(t, user1.Id, preferences[0].UserId) 244 assert.Equal(t, model.PREFERENCE_CATEGORY_TUTORIAL_STEPS, preferences[0].Category) 245 assert.Equal(t, user1.Id, preferences[0].Name) 246 assert.Equal(t, "0", preferences[0].Value) 247 248 preference := model.Preference{ 249 Name: user1.Id, 250 UserId: user1.Id, 251 Category: model.PREFERENCE_CATEGORY_THEME, 252 Value: `{"color": "#ff0000", "color2": "#faf"}`, 253 } 254 255 err = api.UpdatePreferencesForUser(user1.Id, []model.Preference{preference}) 256 require.Nil(t, err) 257 258 preferences, err = api.GetPreferencesForUser(user1.Id) 259 require.Nil(t, err) 260 261 assert.Equal(t, 2, len(preferences)) 262 expectedCategories := []string{model.PREFERENCE_CATEGORY_TUTORIAL_STEPS, model.PREFERENCE_CATEGORY_THEME} 263 for _, pref := range preferences { 264 assert.Contains(t, expectedCategories, pref.Category) 265 assert.Equal(t, user1.Id, pref.UserId) 266 assert.Equal(t, user1.Id, pref.Name) 267 if pref.Category == model.PREFERENCE_CATEGORY_TUTORIAL_STEPS { 268 assert.Equal(t, "0", pref.Value) 269 } else { 270 newTheme, _ := json.Marshal(map[string]string{"color": "#ff0000", "color2": "#faf"}) 271 assert.Equal(t, string(newTheme), pref.Value) 272 } 273 } 274 } 275 276 func TestPluginAPIGetUsers(t *testing.T) { 277 th := Setup(t) 278 defer th.TearDown() 279 api := th.SetupPluginAPI() 280 281 user1, err := th.App.CreateUser(&model.User{ 282 Email: strings.ToLower(model.NewId()) + "success+test@example.com", 283 Password: "password", 284 Username: "user1" + model.NewId(), 285 }) 286 require.Nil(t, err) 287 defer th.App.PermanentDeleteUser(user1) 288 289 user2, err := th.App.CreateUser(&model.User{ 290 Email: strings.ToLower(model.NewId()) + "success+test@example.com", 291 Password: "password", 292 Username: "user2" + model.NewId(), 293 }) 294 require.Nil(t, err) 295 defer th.App.PermanentDeleteUser(user2) 296 297 user3, err := th.App.CreateUser(&model.User{ 298 Email: strings.ToLower(model.NewId()) + "success+test@example.com", 299 Password: "password", 300 Username: "user3" + model.NewId(), 301 }) 302 require.Nil(t, err) 303 defer th.App.PermanentDeleteUser(user3) 304 305 user4, err := th.App.CreateUser(&model.User{ 306 Email: strings.ToLower(model.NewId()) + "success+test@example.com", 307 Password: "password", 308 Username: "user4" + model.NewId(), 309 }) 310 require.Nil(t, err) 311 defer th.App.PermanentDeleteUser(user4) 312 313 testCases := []struct { 314 Description string 315 Page int 316 PerPage int 317 ExpectedUsers []*model.User 318 }{ 319 { 320 "page 0, perPage 0", 321 0, 322 0, 323 []*model.User{}, 324 }, 325 { 326 "page 0, perPage 10", 327 0, 328 10, 329 []*model.User{user1, user2, user3, user4}, 330 }, 331 { 332 "page 0, perPage 2", 333 0, 334 2, 335 []*model.User{user1, user2}, 336 }, 337 { 338 "page 1, perPage 2", 339 1, 340 2, 341 []*model.User{user3, user4}, 342 }, 343 { 344 "page 10, perPage 10", 345 10, 346 10, 347 []*model.User{}, 348 }, 349 } 350 351 for _, testCase := range testCases { 352 t.Run(testCase.Description, func(t *testing.T) { 353 users, err := api.GetUsers(&model.UserGetOptions{ 354 Page: testCase.Page, 355 PerPage: testCase.PerPage, 356 }) 357 assert.Nil(t, err) 358 assert.Equal(t, testCase.ExpectedUsers, users) 359 }) 360 } 361 } 362 363 func TestPluginAPIGetUsersInTeam(t *testing.T) { 364 th := Setup(t) 365 defer th.TearDown() 366 api := th.SetupPluginAPI() 367 368 team1 := th.CreateTeam() 369 team2 := th.CreateTeam() 370 371 user1, err := th.App.CreateUser(&model.User{ 372 Email: strings.ToLower(model.NewId()) + "success+test@example.com", 373 Password: "password", 374 Username: "user1" + model.NewId(), 375 }) 376 require.Nil(t, err) 377 defer th.App.PermanentDeleteUser(user1) 378 379 user2, err := th.App.CreateUser(&model.User{ 380 Email: strings.ToLower(model.NewId()) + "success+test@example.com", 381 Password: "password", 382 Username: "user2" + model.NewId(), 383 }) 384 require.Nil(t, err) 385 defer th.App.PermanentDeleteUser(user2) 386 387 user3, err := th.App.CreateUser(&model.User{ 388 Email: strings.ToLower(model.NewId()) + "success+test@example.com", 389 Password: "password", 390 Username: "user3" + model.NewId(), 391 }) 392 require.Nil(t, err) 393 defer th.App.PermanentDeleteUser(user3) 394 395 user4, err := th.App.CreateUser(&model.User{ 396 Email: strings.ToLower(model.NewId()) + "success+test@example.com", 397 Password: "password", 398 Username: "user4" + model.NewId(), 399 }) 400 require.Nil(t, err) 401 defer th.App.PermanentDeleteUser(user4) 402 403 // Add all users to team 1 404 _, _, err = th.App.joinUserToTeam(team1, user1) 405 require.Nil(t, err) 406 _, _, err = th.App.joinUserToTeam(team1, user2) 407 require.Nil(t, err) 408 _, _, err = th.App.joinUserToTeam(team1, user3) 409 require.Nil(t, err) 410 _, _, err = th.App.joinUserToTeam(team1, user4) 411 require.Nil(t, err) 412 413 // Add only user3 and user4 to team 2 414 _, _, err = th.App.joinUserToTeam(team2, user3) 415 require.Nil(t, err) 416 _, _, err = th.App.joinUserToTeam(team2, user4) 417 require.Nil(t, err) 418 419 testCases := []struct { 420 Description string 421 TeamId string 422 Page int 423 PerPage int 424 ExpectedUsers []*model.User 425 }{ 426 { 427 "unknown team", 428 model.NewId(), 429 0, 430 0, 431 []*model.User{}, 432 }, 433 { 434 "team 1, page 0, perPage 10", 435 team1.Id, 436 0, 437 10, 438 []*model.User{user1, user2, user3, user4}, 439 }, 440 { 441 "team 1, page 0, perPage 2", 442 team1.Id, 443 0, 444 2, 445 []*model.User{user1, user2}, 446 }, 447 { 448 "team 1, page 1, perPage 2", 449 team1.Id, 450 1, 451 2, 452 []*model.User{user3, user4}, 453 }, 454 { 455 "team 2, page 0, perPage 10", 456 team2.Id, 457 0, 458 10, 459 []*model.User{user3, user4}, 460 }, 461 } 462 463 for _, testCase := range testCases { 464 t.Run(testCase.Description, func(t *testing.T) { 465 users, err := api.GetUsersInTeam(testCase.TeamId, testCase.Page, testCase.PerPage) 466 assert.Nil(t, err) 467 assert.Equal(t, testCase.ExpectedUsers, users) 468 }) 469 } 470 } 471 472 func TestPluginAPIGetFile(t *testing.T) { 473 th := Setup(t).InitBasic() 474 defer th.TearDown() 475 api := th.SetupPluginAPI() 476 477 // check a valid file first 478 uploadTime := time.Date(2007, 2, 4, 1, 2, 3, 4, time.Local) 479 filename := "testGetFile" 480 fileData := []byte("Hello World") 481 info, err := th.App.DoUploadFile(uploadTime, th.BasicTeam.Id, th.BasicChannel.Id, th.BasicUser.Id, filename, fileData) 482 require.Nil(t, err) 483 defer func() { 484 th.App.Srv().Store.FileInfo().PermanentDelete(info.Id) 485 th.App.RemoveFile(info.Path) 486 }() 487 488 data, err1 := api.GetFile(info.Id) 489 require.Nil(t, err1) 490 assert.Equal(t, data, fileData) 491 492 // then checking invalid file 493 data, err = api.GetFile("../fake/testingApi") 494 require.NotNil(t, err) 495 require.Nil(t, data) 496 } 497 498 func TestPluginAPIGetFileInfos(t *testing.T) { 499 th := Setup(t).InitBasic() 500 defer th.TearDown() 501 api := th.SetupPluginAPI() 502 503 fileInfo1, err := th.App.DoUploadFile( 504 time.Date(2020, 1, 1, 1, 1, 1, 1, time.UTC), 505 th.BasicTeam.Id, 506 th.BasicChannel.Id, 507 th.BasicUser.Id, 508 "testFile1", 509 []byte("testfile1 Content"), 510 ) 511 require.Nil(t, err) 512 defer func() { 513 th.App.Srv().Store.FileInfo().PermanentDelete(fileInfo1.Id) 514 th.App.RemoveFile(fileInfo1.Path) 515 }() 516 517 fileInfo2, err := th.App.DoUploadFile( 518 time.Date(2020, 1, 2, 1, 1, 1, 1, time.UTC), 519 th.BasicTeam.Id, 520 th.BasicChannel.Id, 521 th.BasicUser2.Id, 522 "testFile2", 523 []byte("testfile2 Content"), 524 ) 525 require.Nil(t, err) 526 defer func() { 527 th.App.Srv().Store.FileInfo().PermanentDelete(fileInfo2.Id) 528 th.App.RemoveFile(fileInfo2.Path) 529 }() 530 531 fileInfo3, err := th.App.DoUploadFile( 532 time.Date(2020, 1, 3, 1, 1, 1, 1, time.UTC), 533 th.BasicTeam.Id, 534 th.BasicChannel.Id, 535 th.BasicUser.Id, 536 "testFile3", 537 []byte("testfile3 Content"), 538 ) 539 require.Nil(t, err) 540 defer func() { 541 th.App.Srv().Store.FileInfo().PermanentDelete(fileInfo3.Id) 542 th.App.RemoveFile(fileInfo3.Path) 543 }() 544 545 _, err = api.CreatePost(&model.Post{ 546 Message: "testFile1", 547 UserId: th.BasicUser.Id, 548 ChannelId: th.BasicChannel.Id, 549 FileIds: model.StringArray{fileInfo1.Id}, 550 }) 551 require.Nil(t, err) 552 553 _, err = api.CreatePost(&model.Post{ 554 Message: "testFile2", 555 UserId: th.BasicUser2.Id, 556 ChannelId: th.BasicChannel.Id, 557 FileIds: model.StringArray{fileInfo2.Id}, 558 }) 559 require.Nil(t, err) 560 561 t.Run("get file infos with no options 2nd page of 1 per page", func(t *testing.T) { 562 fileInfos, err := api.GetFileInfos(1, 1, nil) 563 require.Nil(t, err) 564 require.Len(t, fileInfos, 1) 565 }) 566 t.Run("get file infos filtered by user", func(t *testing.T) { 567 fileInfos, err := api.GetFileInfos(0, 5, &model.GetFileInfosOptions{ 568 UserIds: []string{th.BasicUser.Id}, 569 }) 570 require.Nil(t, err) 571 require.Len(t, fileInfos, 2) 572 }) 573 t.Run("get file infos filtered by channel ordered by created at descending", func(t *testing.T) { 574 fileInfos, err := api.GetFileInfos(0, 5, &model.GetFileInfosOptions{ 575 ChannelIds: []string{th.BasicChannel.Id}, 576 SortBy: model.FILEINFO_SORT_BY_CREATED, 577 SortDescending: true, 578 }) 579 require.Nil(t, err) 580 require.Len(t, fileInfos, 2) 581 require.Equal(t, fileInfos[0].Id, fileInfo2.Id) 582 require.Equal(t, fileInfos[1].Id, fileInfo1.Id) 583 }) 584 } 585 586 func TestPluginAPISavePluginConfig(t *testing.T) { 587 th := Setup(t) 588 defer th.TearDown() 589 590 manifest := &model.Manifest{ 591 Id: "pluginid", 592 SettingsSchema: &model.PluginSettingsSchema{ 593 Settings: []*model.PluginSetting{ 594 {Key: "MyStringSetting", Type: "text"}, 595 {Key: "MyIntSetting", Type: "text"}, 596 {Key: "MyBoolSetting", Type: "bool"}, 597 }, 598 }, 599 } 600 601 api := NewPluginAPI(th.App, manifest) 602 603 pluginConfigJsonString := `{"mystringsetting": "str", "MyIntSetting": 32, "myboolsetting": true}` 604 605 var pluginConfig map[string]interface{} 606 err := json.Unmarshal([]byte(pluginConfigJsonString), &pluginConfig) 607 require.NoError(t, err) 608 609 appErr := api.SavePluginConfig(pluginConfig) 610 require.Nil(t, appErr) 611 612 type Configuration struct { 613 MyStringSetting string 614 MyIntSetting int 615 MyBoolSetting bool 616 } 617 618 savedConfiguration := new(Configuration) 619 err = api.LoadPluginConfiguration(savedConfiguration) 620 require.NoError(t, err) 621 622 expectedConfiguration := new(Configuration) 623 err = json.Unmarshal([]byte(pluginConfigJsonString), &expectedConfiguration) 624 require.NoError(t, err) 625 626 assert.Equal(t, expectedConfiguration, savedConfiguration) 627 } 628 629 func TestPluginAPIGetPluginConfig(t *testing.T) { 630 th := Setup(t) 631 defer th.TearDown() 632 633 manifest := &model.Manifest{ 634 Id: "pluginid", 635 SettingsSchema: &model.PluginSettingsSchema{ 636 Settings: []*model.PluginSetting{ 637 {Key: "MyStringSetting", Type: "text"}, 638 {Key: "MyIntSetting", Type: "text"}, 639 {Key: "MyBoolSetting", Type: "bool"}, 640 }, 641 }, 642 } 643 644 api := NewPluginAPI(th.App, manifest) 645 646 pluginConfigJsonString := `{"mystringsetting": "str", "myintsetting": 32, "myboolsetting": true}` 647 var pluginConfig map[string]interface{} 648 649 err := json.Unmarshal([]byte(pluginConfigJsonString), &pluginConfig) 650 require.NoError(t, err) 651 652 th.App.UpdateConfig(func(cfg *model.Config) { 653 cfg.PluginSettings.Plugins["pluginid"] = pluginConfig 654 }) 655 656 savedPluginConfig := api.GetPluginConfig() 657 assert.Equal(t, pluginConfig, savedPluginConfig) 658 } 659 660 func TestPluginAPILoadPluginConfiguration(t *testing.T) { 661 th := Setup(t) 662 defer th.TearDown() 663 664 var pluginJson map[string]interface{} 665 err := json.Unmarshal([]byte(`{"mystringsetting": "str", "MyIntSetting": 32, "myboolsetting": true}`), &pluginJson) 666 require.NoError(t, err) 667 668 th.App.UpdateConfig(func(cfg *model.Config) { 669 cfg.PluginSettings.Plugins["testloadpluginconfig"] = pluginJson 670 }) 671 672 testFolder, found := fileutils.FindDir("mattermost-server/app/plugin_api_tests") 673 require.True(t, found, "Cannot find tests folder") 674 fullPath := path.Join(testFolder, "manual.test_load_configuration_plugin", "main.go") 675 676 err = pluginAPIHookTest(t, th, fullPath, "testloadpluginconfig", `{"id": "testloadpluginconfig", "backend": {"executable": "backend.exe"}, "settings_schema": { 677 "settings": [ 678 { 679 "key": "MyStringSetting", 680 "type": "text" 681 }, 682 { 683 "key": "MyIntSetting", 684 "type": "text" 685 }, 686 { 687 "key": "MyBoolSetting", 688 "type": "bool" 689 } 690 ] 691 }}`) 692 require.NoError(t, err) 693 694 } 695 696 func TestPluginAPILoadPluginConfigurationDefaults(t *testing.T) { 697 th := Setup(t) 698 defer th.TearDown() 699 700 var pluginJson map[string]interface{} 701 err := json.Unmarshal([]byte(`{"mystringsetting": "override"}`), &pluginJson) 702 require.NoError(t, err) 703 704 th.App.UpdateConfig(func(cfg *model.Config) { 705 cfg.PluginSettings.Plugins["testloadpluginconfig"] = pluginJson 706 }) 707 708 testFolder, found := fileutils.FindDir("mattermost-server/app/plugin_api_tests") 709 require.True(t, found, "Cannot find tests folder") 710 fullPath := path.Join(testFolder, "manual.test_load_configuration_defaults_plugin", "main.go") 711 712 err = pluginAPIHookTest(t, th, fullPath, "testloadpluginconfig", `{ 713 "settings": [ 714 { 715 "key": "MyStringSetting", 716 "type": "text", 717 "default": "notthis" 718 }, 719 { 720 "key": "MyIntSetting", 721 "type": "text", 722 "default": 35 723 }, 724 { 725 "key": "MyBoolSetting", 726 "type": "bool", 727 "default": true 728 } 729 ] 730 }`) 731 732 require.NoError(t, err) 733 734 } 735 736 func TestPluginAPIGetPlugins(t *testing.T) { 737 th := Setup(t) 738 defer th.TearDown() 739 api := th.SetupPluginAPI() 740 741 pluginCode := ` 742 package main 743 744 import ( 745 "github.com/mattermost/mattermost-server/v5/plugin" 746 ) 747 748 type MyPlugin struct { 749 plugin.MattermostPlugin 750 } 751 752 func main() { 753 plugin.ClientMain(&MyPlugin{}) 754 } 755 ` 756 757 pluginDir, err := ioutil.TempDir("", "") 758 require.NoError(t, err) 759 webappPluginDir, err := ioutil.TempDir("", "") 760 require.NoError(t, err) 761 defer os.RemoveAll(pluginDir) 762 defer os.RemoveAll(webappPluginDir) 763 764 env, err := plugin.NewEnvironment(th.App.NewPluginAPI, pluginDir, webappPluginDir, th.App.Log(), nil) 765 require.NoError(t, err) 766 767 pluginIDs := []string{"pluginid1", "pluginid2", "pluginid3"} 768 var pluginManifests []*model.Manifest 769 for _, pluginID := range pluginIDs { 770 backend := filepath.Join(pluginDir, pluginID, "backend.exe") 771 utils.CompileGo(t, pluginCode, backend) 772 773 ioutil.WriteFile(filepath.Join(pluginDir, pluginID, "plugin.json"), []byte(fmt.Sprintf(`{"id": "%s", "server": {"executable": "backend.exe"}}`, pluginID)), 0600) 774 manifest, activated, reterr := env.Activate(pluginID) 775 776 require.NoError(t, reterr) 777 require.NotNil(t, manifest) 778 require.True(t, activated) 779 pluginManifests = append(pluginManifests, manifest) 780 } 781 th.App.SetPluginsEnvironment(env) 782 783 // Deactivate the last one for testing 784 success := env.Deactivate(pluginIDs[len(pluginIDs)-1]) 785 require.True(t, success) 786 787 // check existing user first 788 plugins, appErr := api.GetPlugins() 789 assert.Nil(t, appErr) 790 assert.NotEmpty(t, plugins) 791 assert.Equal(t, pluginManifests, plugins) 792 } 793 794 func TestPluginAPIInstallPlugin(t *testing.T) { 795 th := Setup(t) 796 defer th.TearDown() 797 api := th.SetupPluginAPI() 798 799 path, _ := fileutils.FindDir("tests") 800 tarData, err := ioutil.ReadFile(filepath.Join(path, "testplugin.tar.gz")) 801 require.NoError(t, err) 802 803 _, appErr := api.InstallPlugin(bytes.NewReader(tarData), true) 804 assert.NotNil(t, appErr, "should not allow upload if upload disabled") 805 assert.Equal(t, appErr.Error(), "installPlugin: Plugins and/or plugin uploads have been disabled., ") 806 807 th.App.UpdateConfig(func(cfg *model.Config) { 808 *cfg.PluginSettings.Enable = true 809 *cfg.PluginSettings.EnableUploads = true 810 }) 811 812 manifest, appErr := api.InstallPlugin(bytes.NewReader(tarData), true) 813 defer os.RemoveAll("plugins/testplugin") 814 require.Nil(t, appErr) 815 assert.Equal(t, "testplugin", manifest.Id) 816 817 // Successfully installed 818 pluginsResp, appErr := api.GetPlugins() 819 require.Nil(t, appErr) 820 821 found := false 822 for _, m := range pluginsResp { 823 if m.Id == manifest.Id { 824 found = true 825 } 826 } 827 828 assert.True(t, found) 829 } 830 831 func TestInstallPlugin(t *testing.T) { 832 // TODO(ilgooz): remove this setup func to use existent setupPluginApiTest(). 833 // following setupTest() func is a modified version of setupPluginApiTest(). 834 // we need a modified version of setupPluginApiTest() because it wasn't possible to use it directly here 835 // since it removes plugin dirs right after it returns, does not update App configs with the plugin 836 // dirs and this behavior tends to break this test as a result. 837 setupTest := func(t *testing.T, pluginCode string, pluginManifest string, pluginID string, app *App) (func(), string) { 838 pluginDir, err := ioutil.TempDir("", "") 839 require.NoError(t, err) 840 webappPluginDir, err := ioutil.TempDir("", "") 841 require.NoError(t, err) 842 843 app.UpdateConfig(func(cfg *model.Config) { 844 *cfg.PluginSettings.Directory = pluginDir 845 *cfg.PluginSettings.ClientDirectory = webappPluginDir 846 }) 847 848 env, err := plugin.NewEnvironment(app.NewPluginAPI, pluginDir, webappPluginDir, app.Log(), nil) 849 require.NoError(t, err) 850 851 app.SetPluginsEnvironment(env) 852 853 backend := filepath.Join(pluginDir, pluginID, "backend.exe") 854 utils.CompileGo(t, pluginCode, backend) 855 856 ioutil.WriteFile(filepath.Join(pluginDir, pluginID, "plugin.json"), []byte(pluginManifest), 0600) 857 manifest, activated, reterr := env.Activate(pluginID) 858 require.NoError(t, reterr) 859 require.NotNil(t, manifest) 860 require.True(t, activated) 861 862 return func() { 863 os.RemoveAll(pluginDir) 864 os.RemoveAll(webappPluginDir) 865 }, pluginDir 866 } 867 868 th := Setup(t) 869 defer th.TearDown() 870 871 // start an http server to serve plugin's tarball to the test. 872 path, _ := fileutils.FindDir("tests") 873 ts := httptest.NewServer(http.FileServer(http.Dir(path))) 874 defer ts.Close() 875 876 th.App.UpdateConfig(func(cfg *model.Config) { 877 *cfg.PluginSettings.Enable = true 878 *cfg.PluginSettings.EnableUploads = true 879 cfg.PluginSettings.Plugins["testinstallplugin"] = map[string]interface{}{ 880 "DownloadURL": ts.URL + "/testplugin.tar.gz", 881 } 882 }) 883 884 tearDown, _ := setupTest(t, 885 ` 886 package main 887 888 import ( 889 "net/http" 890 891 "github.com/pkg/errors" 892 893 "github.com/mattermost/mattermost-server/v5/plugin" 894 ) 895 896 type configuration struct { 897 DownloadURL string 898 } 899 900 type Plugin struct { 901 plugin.MattermostPlugin 902 903 configuration configuration 904 } 905 906 func (p *Plugin) OnConfigurationChange() error { 907 if err := p.API.LoadPluginConfiguration(&p.configuration); err != nil { 908 return err 909 } 910 return nil 911 } 912 913 func (p *Plugin) OnActivate() error { 914 resp, err := http.Get(p.configuration.DownloadURL) 915 if err != nil { 916 return err 917 } 918 defer resp.Body.Close() 919 _, aerr := p.API.InstallPlugin(resp.Body, true) 920 if aerr != nil { 921 return errors.Wrap(aerr, "cannot install plugin") 922 } 923 return nil 924 } 925 926 func main() { 927 plugin.ClientMain(&Plugin{}) 928 } 929 930 `, 931 `{"id": "testinstallplugin", "backend": {"executable": "backend.exe"}, "settings_schema": { 932 "settings": [ 933 { 934 "key": "DownloadURL", 935 "type": "text" 936 } 937 ] 938 }}`, "testinstallplugin", th.App) 939 defer tearDown() 940 941 hooks, err := th.App.GetPluginsEnvironment().HooksForPlugin("testinstallplugin") 942 require.NoError(t, err) 943 944 err = hooks.OnActivate() 945 require.NoError(t, err) 946 947 plugins, aerr := th.App.GetPlugins() 948 require.Nil(t, aerr) 949 require.Len(t, plugins.Inactive, 1) 950 require.Equal(t, "testplugin", plugins.Inactive[0].Id) 951 } 952 953 func TestPluginAPIGetTeamIcon(t *testing.T) { 954 th := Setup(t).InitBasic() 955 defer th.TearDown() 956 api := th.SetupPluginAPI() 957 958 // Create an 128 x 128 image 959 img := image.NewRGBA(image.Rect(0, 0, 128, 128)) 960 // Draw a red dot at (2, 3) 961 img.Set(2, 3, color.RGBA{255, 0, 0, 255}) 962 buf := new(bytes.Buffer) 963 err := png.Encode(buf, img) 964 require.NoError(t, err) 965 dataBytes := buf.Bytes() 966 fileReader := bytes.NewReader(dataBytes) 967 968 // Set the Team Icon 969 appErr := th.App.SetTeamIconFromFile(th.BasicTeam, fileReader) 970 require.Nil(t, appErr) 971 972 // Get the team icon to check 973 teamIcon, appErr := api.GetTeamIcon(th.BasicTeam.Id) 974 require.Nil(t, appErr) 975 require.NotEmpty(t, teamIcon) 976 977 colorful := color.NRGBA{255, 0, 0, 255} 978 byteReader := bytes.NewReader(teamIcon) 979 img2, _, err2 := image.Decode(byteReader) 980 require.NoError(t, err2) 981 require.Equal(t, img2.At(2, 3), colorful) 982 } 983 984 func TestPluginAPISetTeamIcon(t *testing.T) { 985 th := Setup(t).InitBasic() 986 defer th.TearDown() 987 api := th.SetupPluginAPI() 988 989 // Create an 128 x 128 image 990 img := image.NewRGBA(image.Rect(0, 0, 128, 128)) 991 // Draw a red dot at (2, 3) 992 img.Set(2, 3, color.RGBA{255, 0, 0, 255}) 993 buf := new(bytes.Buffer) 994 err := png.Encode(buf, img) 995 require.NoError(t, err) 996 dataBytes := buf.Bytes() 997 998 // Set the user profile image 999 appErr := api.SetTeamIcon(th.BasicTeam.Id, dataBytes) 1000 require.Nil(t, appErr) 1001 1002 // Get the user profile image to check 1003 teamIcon, appErr := api.GetTeamIcon(th.BasicTeam.Id) 1004 require.Nil(t, appErr) 1005 require.NotEmpty(t, teamIcon) 1006 1007 colorful := color.NRGBA{255, 0, 0, 255} 1008 byteReader := bytes.NewReader(teamIcon) 1009 img2, _, err2 := image.Decode(byteReader) 1010 require.NoError(t, err2) 1011 require.Equal(t, img2.At(2, 3), colorful) 1012 } 1013 1014 func TestPluginAPIRemoveTeamIcon(t *testing.T) { 1015 th := Setup(t).InitBasic() 1016 defer th.TearDown() 1017 api := th.SetupPluginAPI() 1018 1019 // Create an 128 x 128 image 1020 img := image.NewRGBA(image.Rect(0, 0, 128, 128)) 1021 1022 // Draw a red dot at (2, 3) 1023 img.Set(2, 3, color.RGBA{255, 0, 0, 255}) 1024 buf := new(bytes.Buffer) 1025 err1 := png.Encode(buf, img) 1026 require.NoError(t, err1) 1027 dataBytes := buf.Bytes() 1028 fileReader := bytes.NewReader(dataBytes) 1029 1030 // Set the Team Icon 1031 err := th.App.SetTeamIconFromFile(th.BasicTeam, fileReader) 1032 require.Nil(t, err) 1033 err = api.RemoveTeamIcon(th.BasicTeam.Id) 1034 require.Nil(t, err) 1035 } 1036 1037 func pluginAPIHookTest(t *testing.T, th *TestHelper, fileName string, id string, settingsSchema string) error { 1038 data, err := ioutil.ReadFile(fileName) 1039 if err != nil { 1040 return err 1041 } 1042 code := string(data) 1043 schema := `{"settings": [ ] }` 1044 if settingsSchema != "" { 1045 schema = settingsSchema 1046 } 1047 setupPluginApiTest(t, code, 1048 fmt.Sprintf(`{"id": "%v", "backend": {"executable": "backend.exe"}, "settings_schema": %v}`, id, schema), 1049 id, th.App) 1050 hooks, err := th.App.GetPluginsEnvironment().HooksForPlugin(id) 1051 require.NoError(t, err) 1052 require.NotNil(t, hooks) 1053 _, ret := hooks.MessageWillBePosted(nil, nil) 1054 if ret != "OK" { 1055 return errors.New(ret) 1056 } 1057 return nil 1058 } 1059 1060 // This is a meta-test function. It does the following: 1061 // 1. Scans "tests/plugin_tests" folder 1062 // 2. For each folder - compiles the main.go inside and executes it, validating it's result 1063 // 3. If folder starts with "manual." it is skipped ("manual." tests executed in other part of this file) 1064 // 4. Before compiling the main.go file is passed through templating and the following values are available in the template: BasicUser, BasicUser2, BasicChannel, BasicTeam, BasicPost 1065 // 5. Successfully running test should return nil, "OK". Any other returned string is considered and error 1066 1067 func TestBasicAPIPlugins(t *testing.T) { 1068 defaultSchema := getDefaultPluginSettingsSchema() 1069 testFolder, found := fileutils.FindDir("mattermost-server/app/plugin_api_tests") 1070 require.True(t, found, "Cannot read find app folder") 1071 dirs, err := ioutil.ReadDir(testFolder) 1072 require.NoError(t, err, "Cannot read test folder %v", testFolder) 1073 for _, dir := range dirs { 1074 d := dir.Name() 1075 if dir.IsDir() && !strings.HasPrefix(d, "manual.") { 1076 t.Run(d, func(t *testing.T) { 1077 mainPath := path.Join(testFolder, d, "main.go") 1078 _, err := os.Stat(mainPath) 1079 require.NoError(t, err, "Cannot find plugin main file at %v", mainPath) 1080 th := Setup(t).InitBasic() 1081 defer th.TearDown() 1082 setDefaultPluginConfig(th, dir.Name()) 1083 err = pluginAPIHookTest(t, th, mainPath, dir.Name(), defaultSchema) 1084 require.NoError(t, err) 1085 }) 1086 } 1087 } 1088 } 1089 1090 func TestPluginAPIKVCompareAndSet(t *testing.T) { 1091 th := Setup(t) 1092 defer th.TearDown() 1093 api := th.SetupPluginAPI() 1094 1095 testCases := []struct { 1096 Description string 1097 ExpectedValue []byte 1098 }{ 1099 { 1100 Description: "Testing non-nil, non-empty value", 1101 ExpectedValue: []byte("value1"), 1102 }, 1103 { 1104 Description: "Testing empty value", 1105 ExpectedValue: []byte(""), 1106 }, 1107 } 1108 1109 for i, testCase := range testCases { 1110 t.Run(testCase.Description, func(t *testing.T) { 1111 expectedKey := fmt.Sprintf("Key%d", i) 1112 expectedValueEmpty := []byte("") 1113 expectedValue1 := testCase.ExpectedValue 1114 expectedValue2 := []byte("value2") 1115 expectedValue3 := []byte("value3") 1116 1117 // Attempt update using an incorrect old value 1118 updated, err := api.KVCompareAndSet(expectedKey, expectedValue2, expectedValue1) 1119 require.Nil(t, err) 1120 require.False(t, updated) 1121 1122 // Make sure no key is already created 1123 value, err := api.KVGet(expectedKey) 1124 require.Nil(t, err) 1125 require.Nil(t, value) 1126 1127 // Insert using nil old value 1128 updated, err = api.KVCompareAndSet(expectedKey, nil, expectedValue1) 1129 require.Nil(t, err) 1130 require.True(t, updated) 1131 1132 // Get inserted value 1133 value, err = api.KVGet(expectedKey) 1134 require.Nil(t, err) 1135 require.Equal(t, expectedValue1, value) 1136 1137 // Attempt to insert again using nil old value 1138 updated, err = api.KVCompareAndSet(expectedKey, nil, expectedValue2) 1139 require.Nil(t, err) 1140 require.False(t, updated) 1141 1142 // Get old value to assert nothing has changed 1143 value, err = api.KVGet(expectedKey) 1144 require.Nil(t, err) 1145 require.Equal(t, expectedValue1, value) 1146 1147 // Update using correct old value 1148 updated, err = api.KVCompareAndSet(expectedKey, expectedValue1, expectedValue2) 1149 require.Nil(t, err) 1150 require.True(t, updated) 1151 1152 value, err = api.KVGet(expectedKey) 1153 require.Nil(t, err) 1154 require.Equal(t, expectedValue2, value) 1155 1156 // Update using incorrect old value 1157 updated, err = api.KVCompareAndSet(expectedKey, []byte("incorrect"), expectedValue3) 1158 require.Nil(t, err) 1159 require.False(t, updated) 1160 1161 value, err = api.KVGet(expectedKey) 1162 require.Nil(t, err) 1163 require.Equal(t, expectedValue2, value) 1164 1165 // Update using nil old value 1166 updated, err = api.KVCompareAndSet(expectedKey, nil, expectedValue3) 1167 require.Nil(t, err) 1168 require.False(t, updated) 1169 1170 value, err = api.KVGet(expectedKey) 1171 require.Nil(t, err) 1172 require.Equal(t, expectedValue2, value) 1173 1174 // Update using empty old value 1175 updated, err = api.KVCompareAndSet(expectedKey, expectedValueEmpty, expectedValue3) 1176 require.Nil(t, err) 1177 require.False(t, updated) 1178 1179 value, err = api.KVGet(expectedKey) 1180 require.Nil(t, err) 1181 require.Equal(t, expectedValue2, value) 1182 }) 1183 } 1184 } 1185 1186 func TestPluginAPIKVCompareAndDelete(t *testing.T) { 1187 th := Setup(t) 1188 defer th.TearDown() 1189 api := th.SetupPluginAPI() 1190 1191 testCases := []struct { 1192 Description string 1193 ExpectedValue []byte 1194 }{ 1195 { 1196 Description: "Testing non-nil, non-empty value", 1197 ExpectedValue: []byte("value1"), 1198 }, 1199 { 1200 Description: "Testing empty value", 1201 ExpectedValue: []byte(""), 1202 }, 1203 } 1204 1205 for i, testCase := range testCases { 1206 t.Run(testCase.Description, func(t *testing.T) { 1207 expectedKey := fmt.Sprintf("Key%d", i) 1208 expectedValue1 := testCase.ExpectedValue 1209 expectedValue2 := []byte("value2") 1210 1211 // Set the value 1212 err := api.KVSet(expectedKey, expectedValue1) 1213 require.Nil(t, err) 1214 1215 // Attempt delete using an incorrect old value 1216 deleted, err := api.KVCompareAndDelete(expectedKey, expectedValue2) 1217 require.Nil(t, err) 1218 require.False(t, deleted) 1219 1220 // Make sure the value is still there 1221 value, err := api.KVGet(expectedKey) 1222 require.Nil(t, err) 1223 require.Equal(t, expectedValue1, value) 1224 1225 // Attempt delete using the proper value 1226 deleted, err = api.KVCompareAndDelete(expectedKey, expectedValue1) 1227 require.Nil(t, err) 1228 require.True(t, deleted) 1229 1230 // Verify it's deleted 1231 value, err = api.KVGet(expectedKey) 1232 require.Nil(t, err) 1233 require.Nil(t, value) 1234 }) 1235 } 1236 } 1237 1238 func TestPluginCreateBot(t *testing.T) { 1239 th := Setup(t) 1240 defer th.TearDown() 1241 api := th.SetupPluginAPI() 1242 1243 bot, err := api.CreateBot(&model.Bot{ 1244 Username: model.NewRandomString(10), 1245 DisplayName: "bot", 1246 Description: "bot", 1247 }) 1248 require.Nil(t, err) 1249 1250 _, err = api.CreateBot(&model.Bot{ 1251 Username: model.NewRandomString(10), 1252 OwnerId: bot.UserId, 1253 DisplayName: "bot2", 1254 Description: "bot2", 1255 }) 1256 require.NotNil(t, err) 1257 1258 } 1259 1260 func TestPluginCreatePostWithUploadedFile(t *testing.T) { 1261 th := Setup(t).InitBasic() 1262 defer th.TearDown() 1263 api := th.SetupPluginAPI() 1264 1265 data := []byte("Hello World") 1266 channelId := th.BasicChannel.Id 1267 filename := "testGetFile" 1268 fileInfo, err := api.UploadFile(data, channelId, filename) 1269 require.Nil(t, err) 1270 defer func() { 1271 th.App.Srv().Store.FileInfo().PermanentDelete(fileInfo.Id) 1272 th.App.RemoveFile(fileInfo.Path) 1273 }() 1274 1275 actualData, err := api.GetFile(fileInfo.Id) 1276 require.Nil(t, err) 1277 assert.Equal(t, data, actualData) 1278 1279 userID := th.BasicUser.Id 1280 post, err := api.CreatePost(&model.Post{ 1281 Message: "test", 1282 UserId: userID, 1283 ChannelId: channelId, 1284 FileIds: model.StringArray{fileInfo.Id}, 1285 }) 1286 require.Nil(t, err) 1287 assert.Equal(t, model.StringArray{fileInfo.Id}, post.FileIds) 1288 1289 actualPost, err := api.GetPost(post.Id) 1290 require.Nil(t, err) 1291 assert.Equal(t, model.StringArray{fileInfo.Id}, actualPost.FileIds) 1292 } 1293 1294 func TestPluginAPIGetConfig(t *testing.T) { 1295 th := Setup(t) 1296 defer th.TearDown() 1297 api := th.SetupPluginAPI() 1298 1299 config := api.GetConfig() 1300 if config.LdapSettings.BindPassword != nil && *config.LdapSettings.BindPassword != "" { 1301 assert.Equal(t, *config.LdapSettings.BindPassword, model.FAKE_SETTING) 1302 } 1303 1304 assert.Equal(t, *config.FileSettings.PublicLinkSalt, model.FAKE_SETTING) 1305 1306 if *config.FileSettings.AmazonS3SecretAccessKey != "" { 1307 assert.Equal(t, *config.FileSettings.AmazonS3SecretAccessKey, model.FAKE_SETTING) 1308 } 1309 1310 if config.EmailSettings.SMTPPassword != nil && *config.EmailSettings.SMTPPassword != "" { 1311 assert.Equal(t, *config.EmailSettings.SMTPPassword, model.FAKE_SETTING) 1312 } 1313 1314 if *config.GitLabSettings.Secret != "" { 1315 assert.Equal(t, *config.GitLabSettings.Secret, model.FAKE_SETTING) 1316 } 1317 1318 assert.Equal(t, *config.SqlSettings.DataSource, model.FAKE_SETTING) 1319 assert.Equal(t, *config.SqlSettings.AtRestEncryptKey, model.FAKE_SETTING) 1320 assert.Equal(t, *config.ElasticsearchSettings.Password, model.FAKE_SETTING) 1321 1322 for i := range config.SqlSettings.DataSourceReplicas { 1323 assert.Equal(t, config.SqlSettings.DataSourceReplicas[i], model.FAKE_SETTING) 1324 } 1325 1326 for i := range config.SqlSettings.DataSourceSearchReplicas { 1327 assert.Equal(t, config.SqlSettings.DataSourceSearchReplicas[i], model.FAKE_SETTING) 1328 } 1329 } 1330 1331 func TestPluginAPIGetUnsanitizedConfig(t *testing.T) { 1332 th := Setup(t) 1333 defer th.TearDown() 1334 api := th.SetupPluginAPI() 1335 1336 config := api.GetUnsanitizedConfig() 1337 if config.LdapSettings.BindPassword != nil && *config.LdapSettings.BindPassword != "" { 1338 assert.NotEqual(t, *config.LdapSettings.BindPassword, model.FAKE_SETTING) 1339 } 1340 1341 assert.NotEqual(t, *config.FileSettings.PublicLinkSalt, model.FAKE_SETTING) 1342 1343 if *config.FileSettings.AmazonS3SecretAccessKey != "" { 1344 assert.NotEqual(t, *config.FileSettings.AmazonS3SecretAccessKey, model.FAKE_SETTING) 1345 } 1346 1347 if config.EmailSettings.SMTPPassword != nil && *config.EmailSettings.SMTPPassword != "" { 1348 assert.NotEqual(t, *config.EmailSettings.SMTPPassword, model.FAKE_SETTING) 1349 } 1350 1351 if *config.GitLabSettings.Secret != "" { 1352 assert.NotEqual(t, *config.GitLabSettings.Secret, model.FAKE_SETTING) 1353 } 1354 1355 assert.NotEqual(t, *config.SqlSettings.DataSource, model.FAKE_SETTING) 1356 assert.NotEqual(t, *config.SqlSettings.AtRestEncryptKey, model.FAKE_SETTING) 1357 assert.NotEqual(t, *config.ElasticsearchSettings.Password, model.FAKE_SETTING) 1358 1359 for i := range config.SqlSettings.DataSourceReplicas { 1360 assert.NotEqual(t, config.SqlSettings.DataSourceReplicas[i], model.FAKE_SETTING) 1361 } 1362 1363 for i := range config.SqlSettings.DataSourceSearchReplicas { 1364 assert.NotEqual(t, config.SqlSettings.DataSourceSearchReplicas[i], model.FAKE_SETTING) 1365 } 1366 } 1367 1368 func TestPluginAddUserToChannel(t *testing.T) { 1369 th := Setup(t).InitBasic() 1370 defer th.TearDown() 1371 api := th.SetupPluginAPI() 1372 1373 member, err := api.AddUserToChannel(th.BasicChannel.Id, th.BasicUser.Id, th.BasicUser2.Id) 1374 require.Nil(t, err) 1375 require.NotNil(t, member) 1376 require.Equal(t, th.BasicChannel.Id, member.ChannelId) 1377 require.Equal(t, th.BasicUser.Id, member.UserId) 1378 } 1379 1380 func TestInterpluginPluginHTTP(t *testing.T) { 1381 th := Setup(t) 1382 defer th.TearDown() 1383 1384 setupMultiPluginApiTest(t, 1385 []string{` 1386 package main 1387 1388 import ( 1389 "github.com/mattermost/mattermost-server/v5/plugin" 1390 "bytes" 1391 "net/http" 1392 ) 1393 1394 type MyPlugin struct { 1395 plugin.MattermostPlugin 1396 } 1397 1398 func (p *MyPlugin) ServeHTTP(c *plugin.Context, w http.ResponseWriter, r *http.Request) { 1399 if r.URL.Path != "/api/v2/test" { 1400 return 1401 } 1402 1403 if r.URL.Query().Get("abc") != "xyz" { 1404 return 1405 } 1406 1407 buf := bytes.Buffer{} 1408 buf.ReadFrom(r.Body) 1409 resp := "we got:" + buf.String() 1410 w.WriteHeader(598) 1411 w.Write([]byte(resp)) 1412 } 1413 1414 func main() { 1415 plugin.ClientMain(&MyPlugin{}) 1416 } 1417 `, 1418 ` 1419 package main 1420 1421 import ( 1422 "github.com/mattermost/mattermost-server/v5/plugin" 1423 "github.com/mattermost/mattermost-server/v5/model" 1424 "bytes" 1425 "net/http" 1426 "io/ioutil" 1427 ) 1428 1429 type MyPlugin struct { 1430 plugin.MattermostPlugin 1431 } 1432 1433 func (p *MyPlugin) MessageWillBePosted(c *plugin.Context, post *model.Post) (*model.Post, string) { 1434 buf := bytes.Buffer{} 1435 buf.WriteString("This is the request") 1436 req, err := http.NewRequest("GET", "/testplugininterserver/api/v2/test?abc=xyz", &buf) 1437 if err != nil { 1438 return nil, err.Error() 1439 } 1440 req.Header.Add("Mattermost-User-Id", "userid") 1441 resp := p.API.PluginHTTP(req) 1442 if resp == nil { 1443 return nil, "Nil resp" 1444 } 1445 if resp.Body == nil { 1446 return nil, "Nil body" 1447 } 1448 respbody, err := ioutil.ReadAll(resp.Body) 1449 if err != nil { 1450 return nil, err.Error() 1451 } 1452 if resp.StatusCode != 598 { 1453 return nil, "wrong status " + string(respbody) 1454 } 1455 return nil, string(respbody) 1456 } 1457 1458 func main() { 1459 plugin.ClientMain(&MyPlugin{}) 1460 } 1461 `, 1462 }, 1463 []string{ 1464 `{"id": "testplugininterserver", "backend": {"executable": "backend.exe"}}`, 1465 `{"id": "testplugininterclient", "backend": {"executable": "backend.exe"}}`, 1466 }, 1467 []string{ 1468 "testplugininterserver", 1469 "testplugininterclient", 1470 }, 1471 th.App, 1472 ) 1473 1474 hooks, err := th.App.GetPluginsEnvironment().HooksForPlugin("testplugininterclient") 1475 require.NoError(t, err) 1476 _, ret := hooks.MessageWillBePosted(nil, nil) 1477 assert.Equal(t, "we got:This is the request", ret) 1478 } 1479 1480 func TestApiMetrics(t *testing.T) { 1481 th := Setup(t) 1482 defer th.TearDown() 1483 1484 t.Run("", func(t *testing.T) { 1485 metricsMock := &mocks.MetricsInterface{} 1486 1487 pluginDir, err := ioutil.TempDir("", "") 1488 require.NoError(t, err) 1489 webappPluginDir, err := ioutil.TempDir("", "") 1490 require.NoError(t, err) 1491 defer os.RemoveAll(pluginDir) 1492 defer os.RemoveAll(webappPluginDir) 1493 1494 env, err := plugin.NewEnvironment(th.App.NewPluginAPI, pluginDir, webappPluginDir, th.App.Log(), metricsMock) 1495 require.NoError(t, err) 1496 1497 th.App.SetPluginsEnvironment(env) 1498 1499 pluginId := model.NewId() 1500 backend := filepath.Join(pluginDir, pluginId, "backend.exe") 1501 code := 1502 ` 1503 package main 1504 1505 import ( 1506 "github.com/mattermost/mattermost-server/v5/model" 1507 "github.com/mattermost/mattermost-server/v5/plugin" 1508 ) 1509 1510 type MyPlugin struct { 1511 plugin.MattermostPlugin 1512 } 1513 1514 func (p *MyPlugin) UserHasBeenCreated(c *plugin.Context, user *model.User) { 1515 user.Nickname = "plugin-callback-success" 1516 p.API.UpdateUser(user) 1517 } 1518 1519 func main() { 1520 plugin.ClientMain(&MyPlugin{}) 1521 } 1522 ` 1523 utils.CompileGo(t, code, backend) 1524 ioutil.WriteFile(filepath.Join(pluginDir, pluginId, "plugin.json"), []byte(`{"id": "`+pluginId+`", "backend": {"executable": "backend.exe"}}`), 0600) 1525 1526 // Don't care about these mocks 1527 metricsMock.On("ObservePluginHookDuration", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return() 1528 metricsMock.On("ObservePluginMultiHookIterationDuration", mock.Anything, mock.Anything, mock.Anything).Return() 1529 metricsMock.On("ObservePluginMultiHookDuration", mock.Anything).Return() 1530 1531 // Setup mocks 1532 metricsMock.On("ObservePluginApiDuration", pluginId, "UpdateUser", true, mock.Anything).Return() 1533 1534 _, _, activationErr := env.Activate(pluginId) 1535 require.NoError(t, activationErr) 1536 1537 require.True(t, th.App.GetPluginsEnvironment().IsActive(pluginId)) 1538 1539 user1 := &model.User{ 1540 Email: model.NewId() + "success+test@example.com", 1541 Nickname: "Darth Vader1", 1542 Username: "vader" + model.NewId(), 1543 Password: "passwd1", 1544 AuthService: "", 1545 } 1546 _, appErr := th.App.CreateUser(user1) 1547 require.Nil(t, appErr) 1548 time.Sleep(1 * time.Second) 1549 user1, appErr = th.App.GetUser(user1.Id) 1550 require.Nil(t, appErr) 1551 require.Equal(t, "plugin-callback-success", user1.Nickname) 1552 1553 // Disable plugin 1554 require.True(t, th.App.GetPluginsEnvironment().Deactivate(pluginId)) 1555 require.False(t, th.App.GetPluginsEnvironment().IsActive(pluginId)) 1556 1557 metricsMock.AssertExpectations(t) 1558 }) 1559 } 1560 1561 func TestPluginAPIGetPostsForChannel(t *testing.T) { 1562 require := require.New(t) 1563 1564 th := Setup(t).InitBasic() 1565 defer th.TearDown() 1566 api := th.SetupPluginAPI() 1567 1568 numPosts := 10 1569 1570 // GetPostsForChannel returns posts ordered with the most recent first, so we 1571 // need to invert the expected slice, the oldest post being BasicPost 1572 expectedPosts := make([]*model.Post, numPosts) 1573 expectedPosts[numPosts-1] = th.BasicPost 1574 for i := numPosts - 2; i >= 0; i-- { 1575 expectedPosts[i] = th.CreatePost(th.BasicChannel) 1576 } 1577 // CreatePost does not add Metadata, but initializes the structure. GetPostsForChannel 1578 // returns nil for an empty Metadata, so we need to match that behaviour 1579 for _, post := range expectedPosts { 1580 post.Metadata = nil 1581 } 1582 1583 postList, err := api.GetPostsForChannel(th.BasicChannel.Id, 0, 0) 1584 require.Nil(err) 1585 require.Nil(postList.ToSlice()) 1586 1587 postList, err = api.GetPostsForChannel(th.BasicChannel.Id, 0, numPosts/2) 1588 require.Nil(err) 1589 require.Equal(expectedPosts[:numPosts/2], postList.ToSlice()) 1590 1591 postList, err = api.GetPostsForChannel(th.BasicChannel.Id, 1, numPosts/2) 1592 require.Nil(err) 1593 require.Equal(expectedPosts[numPosts/2:], postList.ToSlice()) 1594 1595 postList, err = api.GetPostsForChannel(th.BasicChannel.Id, 2, numPosts/2) 1596 require.Nil(err) 1597 require.Nil(postList.ToSlice()) 1598 1599 postList, err = api.GetPostsForChannel(th.BasicChannel.Id, 0, numPosts+1) 1600 require.Nil(err) 1601 require.Equal(expectedPosts, postList.ToSlice()) 1602 } 1603 1604 func TestPluginHTTPConnHijack(t *testing.T) { 1605 th := Setup(t) 1606 defer th.TearDown() 1607 1608 testFolder, found := fileutils.FindDir("mattermost-server/app/plugin_api_tests") 1609 require.True(t, found, "Cannot find tests folder") 1610 fullPath := path.Join(testFolder, "manual.test_http_hijack_plugin", "main.go") 1611 1612 pluginCode, err := ioutil.ReadFile(fullPath) 1613 require.NoError(t, err) 1614 require.NotEmpty(t, pluginCode) 1615 1616 tearDown, ids, errors := SetAppEnvironmentWithPlugins(t, []string{string(pluginCode)}, th.App, th.App.NewPluginAPI) 1617 defer tearDown() 1618 require.NoError(t, errors[0]) 1619 require.Len(t, ids, 1) 1620 1621 pluginID := ids[0] 1622 require.NotEmpty(t, pluginID) 1623 1624 reqURL := fmt.Sprintf("http://localhost:%d/plugins/%s", th.Server.ListenAddr.Port, pluginID) 1625 req, err := http.NewRequest("GET", reqURL, nil) 1626 require.NoError(t, err) 1627 1628 client := &http.Client{} 1629 resp, err := client.Do(req) 1630 require.NoError(t, err) 1631 1632 defer resp.Body.Close() 1633 1634 body, err := ioutil.ReadAll(resp.Body) 1635 require.NoError(t, err) 1636 require.Equal(t, "OK", string(body)) 1637 } 1638 1639 func TestPluginHTTPUpgradeWebSocket(t *testing.T) { 1640 th := Setup(t) 1641 defer th.TearDown() 1642 1643 testFolder, found := fileutils.FindDir("mattermost-server/app/plugin_api_tests") 1644 require.True(t, found, "Cannot find tests folder") 1645 fullPath := path.Join(testFolder, "manual.test_http_upgrade_websocket_plugin", "main.go") 1646 1647 pluginCode, err := ioutil.ReadFile(fullPath) 1648 require.NoError(t, err) 1649 require.NotEmpty(t, pluginCode) 1650 1651 tearDown, ids, errors := SetAppEnvironmentWithPlugins(t, []string{string(pluginCode)}, th.App, th.App.NewPluginAPI) 1652 defer tearDown() 1653 require.NoError(t, errors[0]) 1654 require.Len(t, ids, 1) 1655 1656 pluginID := ids[0] 1657 require.NotEmpty(t, pluginID) 1658 1659 reqURL := fmt.Sprintf("ws://localhost:%d/plugins/%s", th.Server.ListenAddr.Port, pluginID) 1660 wsc, appErr := model.NewWebSocketClient(reqURL, "") 1661 require.Nil(t, appErr) 1662 require.NotNil(t, wsc) 1663 1664 wsc.Listen() 1665 defer wsc.Close() 1666 1667 resp := <-wsc.ResponseChannel 1668 require.Equal(t, resp.Status, model.STATUS_OK) 1669 1670 for i := 0; i < 10; i++ { 1671 wsc.SendMessage("custom_action", map[string]interface{}{"value": i}) 1672 var resp *model.WebSocketResponse 1673 select { 1674 case resp = <-wsc.ResponseChannel: 1675 case <-time.After(1 * time.Second): 1676 } 1677 require.NotNil(t, resp) 1678 require.Equal(t, resp.Status, model.STATUS_OK) 1679 require.Equal(t, "custom_action", resp.Data["action"]) 1680 require.Equal(t, float64(i), resp.Data["value"]) 1681 } 1682 } 1683 1684 type MockSlashCommandProvider struct { 1685 Args *model.CommandArgs 1686 Message string 1687 } 1688 1689 func (*MockSlashCommandProvider) GetTrigger() string { 1690 return "mock" 1691 } 1692 func (*MockSlashCommandProvider) GetCommand(a *App, T goi18n.TranslateFunc) *model.Command { 1693 return &model.Command{ 1694 Trigger: "mock", 1695 AutoComplete: true, 1696 AutoCompleteDesc: "mock", 1697 AutoCompleteHint: "mock", 1698 DisplayName: "mock", 1699 } 1700 } 1701 func (mscp *MockSlashCommandProvider) DoCommand(a *App, args *model.CommandArgs, message string) *model.CommandResponse { 1702 mscp.Args = args 1703 mscp.Message = message 1704 return &model.CommandResponse{ 1705 Text: "mock", 1706 ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL, 1707 } 1708 } 1709 1710 func TestPluginExecuteSlashCommand(t *testing.T) { 1711 th := Setup(t).InitBasic() 1712 defer th.TearDown() 1713 api := th.SetupPluginAPI() 1714 1715 slashCommandMock := &MockSlashCommandProvider{} 1716 RegisterCommandProvider(slashCommandMock) 1717 1718 newUser := th.CreateUser() 1719 th.LinkUserToTeam(newUser, th.BasicTeam) 1720 1721 t.Run("run invite command", func(t *testing.T) { 1722 args := &model.CommandArgs{ 1723 Command: "/mock @" + newUser.Username, 1724 TeamId: th.BasicTeam.Id, 1725 UserId: th.BasicUser.Id, 1726 ChannelId: th.BasicChannel.Id, 1727 } 1728 _, err := api.ExecuteSlashCommand(args) 1729 require.NoError(t, err) 1730 require.Equal(t, args, slashCommandMock.Args) 1731 require.Equal(t, "@"+newUser.Username, slashCommandMock.Message) 1732 }) 1733 } 1734 1735 func TestPluginAPISearchPostsInTeamByUser(t *testing.T) { 1736 th := Setup(t).InitBasic() 1737 defer th.TearDown() 1738 api := th.SetupPluginAPI() 1739 1740 basicPostText := &th.BasicPost.Message 1741 unknwonTerm := "Unknown Message" 1742 1743 testCases := []struct { 1744 description string 1745 teamID string 1746 userID string 1747 params model.SearchParameter 1748 expectedPostsLen int 1749 }{ 1750 { 1751 "empty params", 1752 th.BasicTeam.Id, 1753 th.BasicUser.Id, 1754 model.SearchParameter{}, 1755 0, 1756 }, 1757 { 1758 "doesn't match any posts", 1759 th.BasicTeam.Id, 1760 th.BasicUser.Id, 1761 model.SearchParameter{Terms: &unknwonTerm}, 1762 0, 1763 }, 1764 { 1765 "matched posts", 1766 th.BasicTeam.Id, 1767 th.BasicUser.Id, 1768 model.SearchParameter{Terms: basicPostText}, 1769 1, 1770 }, 1771 } 1772 1773 for _, testCase := range testCases { 1774 t.Run(testCase.description, func(t *testing.T) { 1775 searchResults, err := api.SearchPostsInTeamForUser(testCase.teamID, testCase.userID, testCase.params) 1776 assert.Nil(t, err) 1777 assert.Equal(t, testCase.expectedPostsLen, len(searchResults.Posts)) 1778 }) 1779 } 1780 } 1781 1782 func TestPluginAPICreateCommandAndListCommands(t *testing.T) { 1783 th := Setup(t).InitBasic() 1784 defer th.TearDown() 1785 api := th.SetupPluginAPI() 1786 1787 foundCommand := func(listXCommand func(teamID string) ([]*model.Command, error)) bool { 1788 cmds, appErr := listXCommand(th.BasicTeam.Id) 1789 require.NoError(t, appErr) 1790 1791 for _, cmd := range cmds { 1792 if cmd.Trigger == "testcmd" { 1793 return true 1794 } 1795 } 1796 return false 1797 } 1798 1799 require.False(t, foundCommand(api.ListCommands)) 1800 1801 cmd := &model.Command{ 1802 TeamId: th.BasicTeam.Id, 1803 Trigger: "testcmd", 1804 Method: "G", 1805 URL: "http://test.com/testcmd", 1806 } 1807 1808 cmd, appErr := api.CreateCommand(cmd) 1809 require.NoError(t, appErr) 1810 1811 newCmd, appErr := api.GetCommand(cmd.Id) 1812 require.NoError(t, appErr) 1813 require.Equal(t, "pluginid", newCmd.PluginId) 1814 require.Equal(t, "", newCmd.CreatorId) 1815 require.True(t, foundCommand(api.ListCommands)) 1816 require.True(t, foundCommand(api.ListCustomCommands)) 1817 require.False(t, foundCommand(api.ListPluginCommands)) 1818 } 1819 1820 func TestPluginAPIUpdateCommand(t *testing.T) { 1821 th := Setup(t).InitBasic() 1822 defer th.TearDown() 1823 api := th.SetupPluginAPI() 1824 1825 cmd := &model.Command{ 1826 TeamId: th.BasicTeam.Id, 1827 Trigger: "testcmd", 1828 Method: "G", 1829 URL: "http://test.com/testcmd", 1830 } 1831 1832 cmd, appErr := api.CreateCommand(cmd) 1833 require.NoError(t, appErr) 1834 1835 newCmd, appErr := api.GetCommand(cmd.Id) 1836 require.NoError(t, appErr) 1837 require.Equal(t, "pluginid", newCmd.PluginId) 1838 require.Equal(t, "", newCmd.CreatorId) 1839 1840 newCmd.Trigger = "NewTrigger" 1841 newCmd.PluginId = "CannotChangeMe" 1842 newCmd2, appErr := api.UpdateCommand(newCmd.Id, newCmd) 1843 require.NoError(t, appErr) 1844 require.Equal(t, "pluginid", newCmd2.PluginId) 1845 require.Equal(t, "newtrigger", newCmd2.Trigger) 1846 1847 team1 := th.CreateTeam() 1848 1849 newCmd2.PluginId = "CannotChangeMe" 1850 newCmd2.Trigger = "anotherNewTrigger" 1851 newCmd2.TeamId = team1.Id 1852 newCmd3, appErr := api.UpdateCommand(newCmd2.Id, newCmd2) 1853 require.NoError(t, appErr) 1854 require.Equal(t, "pluginid", newCmd3.PluginId) 1855 require.Equal(t, "anothernewtrigger", newCmd3.Trigger) 1856 require.Equal(t, team1.Id, newCmd3.TeamId) 1857 1858 newCmd3.Trigger = "anotherNewTriggerAgain" 1859 newCmd3.TeamId = "" 1860 newCmd4, appErr := api.UpdateCommand(newCmd2.Id, newCmd2) 1861 require.NoError(t, appErr) 1862 require.Equal(t, "anothernewtriggeragain", newCmd4.Trigger) 1863 require.Equal(t, team1.Id, newCmd4.TeamId) 1864 1865 }