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