github.com/haalcala/mattermost-server-change-repo/v5@v5.33.2/app/plugin_api_test.go (about)

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