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