github.com/xzl8028/xenia-server@v0.0.0-20190809101854-18450a97da63/app/plugin_hooks_test.go (about)

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