* MM-10856: deduplicate posts with the same pending post id Leverage a fixed size cache with a window of 30 seconds to deduplicate posts received by a single app server. Clients that duplicate the same pending post id will see one request potentially delayed until the first finishes, after which the same payload should be returned by both. Duplicate posts outside the 30 second window will not be de-duplicated. Note that the cache is not synchronized between app servers. In an HA cluster consisting of more than one app server, sticky load balancing (e.g. hashing by IP or Session ID) is required to route the users to the same app instance for multiple requests. Other options considered for this feature: * adding a column to the `Posts` table: rejected as being too heavyweight * maintaining a `PendingPostIds` table: similarly rejected for the database impact * using the pending post id as the post id and relying on the unique constraints on the Post table: rejected for being difficult to show that it's safe to use a client-provided value as the row identifier * utils/lru: simplify to ttl internally and for new methods * move seenPendingPostIdsCache to App.Server * just fail concurrent post requests, vs. trying to wait * add debug log when create post is deduplicated
949 строки
23 KiB
Go
949 строки
23 KiB
Go
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
|
// See License.txt for license information.
|
|
|
|
package app
|
|
|
|
import (
|
|
"bytes"
|
|
"io"
|
|
"io/ioutil"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/mattermost/mattermost-server/model"
|
|
"github.com/mattermost/mattermost-server/plugin"
|
|
"github.com/mattermost/mattermost-server/plugin/plugintest"
|
|
"github.com/mattermost/mattermost-server/plugin/plugintest/mock"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func compileGo(t *testing.T, sourceCode, outputPath string) {
|
|
dir, err := ioutil.TempDir(".", "")
|
|
require.NoError(t, err)
|
|
defer os.RemoveAll(dir)
|
|
require.NoError(t, ioutil.WriteFile(filepath.Join(dir, "main.go"), []byte(sourceCode), 0600))
|
|
cmd := exec.Command("go", "build", "-o", outputPath, "main.go")
|
|
cmd.Dir = dir
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
require.NoError(t, cmd.Run(), "failed to compile go")
|
|
}
|
|
|
|
func SetAppEnvironmentWithPlugins(t *testing.T, pluginCode []string, app *App, apiFunc func(*model.Manifest) plugin.API) (func(), []string, []error) {
|
|
pluginDir, err := ioutil.TempDir("", "")
|
|
require.NoError(t, err)
|
|
webappPluginDir, err := ioutil.TempDir("", "")
|
|
require.NoError(t, err)
|
|
|
|
env, err := plugin.NewEnvironment(apiFunc, pluginDir, webappPluginDir, app.Log)
|
|
require.NoError(t, err)
|
|
|
|
app.SetPluginsEnvironment(env)
|
|
pluginIds := []string{}
|
|
activationErrors := []error{}
|
|
for _, code := range pluginCode {
|
|
pluginId := model.NewId()
|
|
backend := filepath.Join(pluginDir, pluginId, "backend.exe")
|
|
compileGo(t, code, backend)
|
|
|
|
ioutil.WriteFile(filepath.Join(pluginDir, pluginId, "plugin.json"), []byte(`{"id": "`+pluginId+`", "backend": {"executable": "backend.exe"}}`), 0600)
|
|
_, _, activationErr := env.Activate(pluginId)
|
|
pluginIds = append(pluginIds, pluginId)
|
|
activationErrors = append(activationErrors, activationErr)
|
|
}
|
|
|
|
return func() {
|
|
os.RemoveAll(pluginDir)
|
|
os.RemoveAll(webappPluginDir)
|
|
}, pluginIds, activationErrors
|
|
}
|
|
|
|
func TestHookMessageWillBePosted(t *testing.T) {
|
|
t.Run("rejected", func(t *testing.T) {
|
|
th := Setup().InitBasic()
|
|
defer th.TearDown()
|
|
|
|
tearDown, _, _ := SetAppEnvironmentWithPlugins(t, []string{
|
|
`
|
|
package main
|
|
|
|
import (
|
|
"github.com/mattermost/mattermost-server/plugin"
|
|
"github.com/mattermost/mattermost-server/model"
|
|
)
|
|
|
|
type MyPlugin struct {
|
|
plugin.MattermostPlugin
|
|
}
|
|
|
|
func (p *MyPlugin) MessageWillBePosted(c *plugin.Context, post *model.Post) (*model.Post, string) {
|
|
return nil, "rejected"
|
|
}
|
|
|
|
func main() {
|
|
plugin.ClientMain(&MyPlugin{})
|
|
}
|
|
`,
|
|
}, th.App, th.App.NewPluginAPI)
|
|
defer tearDown()
|
|
|
|
post := &model.Post{
|
|
UserId: th.BasicUser.Id,
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "message_",
|
|
CreateAt: model.GetMillis() - 10000,
|
|
}
|
|
_, err := th.App.CreatePost(post, th.BasicChannel, false)
|
|
if assert.NotNil(t, err) {
|
|
assert.Equal(t, "Post rejected by plugin. rejected", err.Message)
|
|
}
|
|
})
|
|
|
|
t.Run("rejected, returned post ignored", func(t *testing.T) {
|
|
th := Setup().InitBasic()
|
|
defer th.TearDown()
|
|
|
|
tearDown, _, _ := SetAppEnvironmentWithPlugins(t, []string{
|
|
`
|
|
package main
|
|
|
|
import (
|
|
"github.com/mattermost/mattermost-server/plugin"
|
|
"github.com/mattermost/mattermost-server/model"
|
|
)
|
|
|
|
type MyPlugin struct {
|
|
plugin.MattermostPlugin
|
|
}
|
|
|
|
func (p *MyPlugin) MessageWillBePosted(c *plugin.Context, post *model.Post) (*model.Post, string) {
|
|
post.Message = "ignored"
|
|
return post, "rejected"
|
|
}
|
|
|
|
func main() {
|
|
plugin.ClientMain(&MyPlugin{})
|
|
}
|
|
`,
|
|
}, th.App, th.App.NewPluginAPI)
|
|
defer tearDown()
|
|
|
|
post := &model.Post{
|
|
UserId: th.BasicUser.Id,
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "message_",
|
|
CreateAt: model.GetMillis() - 10000,
|
|
}
|
|
_, err := th.App.CreatePost(post, th.BasicChannel, false)
|
|
if assert.NotNil(t, err) {
|
|
assert.Equal(t, "Post rejected by plugin. rejected", err.Message)
|
|
}
|
|
})
|
|
|
|
t.Run("allowed", func(t *testing.T) {
|
|
th := Setup().InitBasic()
|
|
defer th.TearDown()
|
|
|
|
tearDown, _, _ := SetAppEnvironmentWithPlugins(t, []string{
|
|
`
|
|
package main
|
|
|
|
import (
|
|
"github.com/mattermost/mattermost-server/plugin"
|
|
"github.com/mattermost/mattermost-server/model"
|
|
)
|
|
|
|
type MyPlugin struct {
|
|
plugin.MattermostPlugin
|
|
}
|
|
|
|
func (p *MyPlugin) MessageWillBePosted(c *plugin.Context, post *model.Post) (*model.Post, string) {
|
|
return nil, ""
|
|
}
|
|
|
|
func main() {
|
|
plugin.ClientMain(&MyPlugin{})
|
|
}
|
|
`,
|
|
}, th.App, th.App.NewPluginAPI)
|
|
defer tearDown()
|
|
|
|
post := &model.Post{
|
|
UserId: th.BasicUser.Id,
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "message",
|
|
CreateAt: model.GetMillis() - 10000,
|
|
}
|
|
post, err := th.App.CreatePost(post, th.BasicChannel, false)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
assert.Equal(t, "message", post.Message)
|
|
if result := <-th.App.Srv.Store.Post().GetSingle(post.Id); result.Err != nil {
|
|
t.Fatal(err)
|
|
} else {
|
|
assert.Equal(t, "message", result.Data.(*model.Post).Message)
|
|
}
|
|
})
|
|
|
|
t.Run("updated", func(t *testing.T) {
|
|
th := Setup().InitBasic()
|
|
defer th.TearDown()
|
|
|
|
tearDown, _, _ := SetAppEnvironmentWithPlugins(t, []string{
|
|
`
|
|
package main
|
|
|
|
import (
|
|
"github.com/mattermost/mattermost-server/plugin"
|
|
"github.com/mattermost/mattermost-server/model"
|
|
)
|
|
|
|
type MyPlugin struct {
|
|
plugin.MattermostPlugin
|
|
}
|
|
|
|
func (p *MyPlugin) MessageWillBePosted(c *plugin.Context, post *model.Post) (*model.Post, string) {
|
|
post.Message = post.Message + "_fromplugin"
|
|
return post, ""
|
|
}
|
|
|
|
func main() {
|
|
plugin.ClientMain(&MyPlugin{})
|
|
}
|
|
`,
|
|
}, th.App, th.App.NewPluginAPI)
|
|
defer tearDown()
|
|
|
|
post := &model.Post{
|
|
UserId: th.BasicUser.Id,
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "message",
|
|
CreateAt: model.GetMillis() - 10000,
|
|
}
|
|
post, err := th.App.CreatePost(post, th.BasicChannel, false)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
assert.Equal(t, "message_fromplugin", post.Message)
|
|
if result := <-th.App.Srv.Store.Post().GetSingle(post.Id); result.Err != nil {
|
|
t.Fatal(err)
|
|
} else {
|
|
assert.Equal(t, "message_fromplugin", result.Data.(*model.Post).Message)
|
|
}
|
|
})
|
|
|
|
t.Run("multiple updated", func(t *testing.T) {
|
|
th := Setup().InitBasic()
|
|
defer th.TearDown()
|
|
|
|
tearDown, _, _ := SetAppEnvironmentWithPlugins(t, []string{
|
|
`
|
|
package main
|
|
|
|
import (
|
|
"github.com/mattermost/mattermost-server/plugin"
|
|
"github.com/mattermost/mattermost-server/model"
|
|
)
|
|
|
|
type MyPlugin struct {
|
|
plugin.MattermostPlugin
|
|
}
|
|
|
|
func (p *MyPlugin) MessageWillBePosted(c *plugin.Context, post *model.Post) (*model.Post, string) {
|
|
|
|
post.Message = "prefix_" + post.Message
|
|
return post, ""
|
|
}
|
|
|
|
func main() {
|
|
plugin.ClientMain(&MyPlugin{})
|
|
}
|
|
`,
|
|
`
|
|
package main
|
|
|
|
import (
|
|
"github.com/mattermost/mattermost-server/plugin"
|
|
"github.com/mattermost/mattermost-server/model"
|
|
)
|
|
|
|
type MyPlugin struct {
|
|
plugin.MattermostPlugin
|
|
}
|
|
|
|
func (p *MyPlugin) MessageWillBePosted(c *plugin.Context, post *model.Post) (*model.Post, string) {
|
|
post.Message = post.Message + "_suffix"
|
|
return post, ""
|
|
}
|
|
|
|
func main() {
|
|
plugin.ClientMain(&MyPlugin{})
|
|
}
|
|
`,
|
|
}, th.App, th.App.NewPluginAPI)
|
|
defer tearDown()
|
|
|
|
post := &model.Post{
|
|
UserId: th.BasicUser.Id,
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "message",
|
|
CreateAt: model.GetMillis() - 10000,
|
|
}
|
|
post, err := th.App.CreatePost(post, th.BasicChannel, false)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
assert.Equal(t, "prefix_message_suffix", post.Message)
|
|
})
|
|
}
|
|
|
|
func TestHookMessageHasBeenPosted(t *testing.T) {
|
|
th := Setup().InitBasic()
|
|
defer th.TearDown()
|
|
|
|
var mockAPI plugintest.API
|
|
mockAPI.On("LoadPluginConfiguration", mock.Anything).Return(nil)
|
|
mockAPI.On("LogDebug", "message").Return(nil)
|
|
|
|
tearDown, _, _ := SetAppEnvironmentWithPlugins(t,
|
|
[]string{
|
|
`
|
|
package main
|
|
|
|
import (
|
|
"github.com/mattermost/mattermost-server/plugin"
|
|
"github.com/mattermost/mattermost-server/model"
|
|
)
|
|
|
|
type MyPlugin struct {
|
|
plugin.MattermostPlugin
|
|
}
|
|
|
|
func (p *MyPlugin) MessageHasBeenPosted(c *plugin.Context, post *model.Post) {
|
|
p.API.LogDebug(post.Message)
|
|
}
|
|
|
|
func main() {
|
|
plugin.ClientMain(&MyPlugin{})
|
|
}
|
|
`}, th.App, func(*model.Manifest) plugin.API { return &mockAPI })
|
|
defer tearDown()
|
|
|
|
post := &model.Post{
|
|
UserId: th.BasicUser.Id,
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "message",
|
|
CreateAt: model.GetMillis() - 10000,
|
|
}
|
|
_, err := th.App.CreatePost(post, th.BasicChannel, false)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestHookMessageWillBeUpdated(t *testing.T) {
|
|
th := Setup().InitBasic()
|
|
defer th.TearDown()
|
|
|
|
tearDown, _, _ := SetAppEnvironmentWithPlugins(t,
|
|
[]string{
|
|
`
|
|
package main
|
|
|
|
import (
|
|
"github.com/mattermost/mattermost-server/plugin"
|
|
"github.com/mattermost/mattermost-server/model"
|
|
)
|
|
|
|
type MyPlugin struct {
|
|
plugin.MattermostPlugin
|
|
}
|
|
|
|
func (p *MyPlugin) MessageWillBeUpdated(c *plugin.Context, newPost, oldPost *model.Post) (*model.Post, string) {
|
|
newPost.Message = newPost.Message + "fromplugin"
|
|
return newPost, ""
|
|
}
|
|
|
|
func main() {
|
|
plugin.ClientMain(&MyPlugin{})
|
|
}
|
|
`}, th.App, th.App.NewPluginAPI)
|
|
defer tearDown()
|
|
|
|
post := &model.Post{
|
|
UserId: th.BasicUser.Id,
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "message_",
|
|
CreateAt: model.GetMillis() - 10000,
|
|
}
|
|
post, err := th.App.CreatePost(post, th.BasicChannel, false)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
assert.Equal(t, "message_", post.Message)
|
|
post.Message = post.Message + "edited_"
|
|
post, err = th.App.UpdatePost(post, true)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
assert.Equal(t, "message_edited_fromplugin", post.Message)
|
|
}
|
|
|
|
func TestHookMessageHasBeenUpdated(t *testing.T) {
|
|
th := Setup().InitBasic()
|
|
defer th.TearDown()
|
|
|
|
var mockAPI plugintest.API
|
|
mockAPI.On("LoadPluginConfiguration", mock.Anything).Return(nil)
|
|
mockAPI.On("LogDebug", "message_edited").Return(nil)
|
|
mockAPI.On("LogDebug", "message_").Return(nil)
|
|
tearDown, _, _ := SetAppEnvironmentWithPlugins(t,
|
|
[]string{
|
|
`
|
|
package main
|
|
|
|
import (
|
|
"github.com/mattermost/mattermost-server/plugin"
|
|
"github.com/mattermost/mattermost-server/model"
|
|
)
|
|
|
|
type MyPlugin struct {
|
|
plugin.MattermostPlugin
|
|
}
|
|
|
|
func (p *MyPlugin) MessageHasBeenUpdated(c *plugin.Context, newPost, oldPost *model.Post) {
|
|
p.API.LogDebug(newPost.Message)
|
|
p.API.LogDebug(oldPost.Message)
|
|
}
|
|
|
|
func main() {
|
|
plugin.ClientMain(&MyPlugin{})
|
|
}
|
|
`}, th.App, func(*model.Manifest) plugin.API { return &mockAPI })
|
|
defer tearDown()
|
|
|
|
post := &model.Post{
|
|
UserId: th.BasicUser.Id,
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "message_",
|
|
CreateAt: model.GetMillis() - 10000,
|
|
}
|
|
post, err := th.App.CreatePost(post, th.BasicChannel, false)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
assert.Equal(t, "message_", post.Message)
|
|
post.Message = post.Message + "edited"
|
|
_, err = th.App.UpdatePost(post, true)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestHookFileWillBeUploaded(t *testing.T) {
|
|
t.Run("rejected", func(t *testing.T) {
|
|
th := Setup().InitBasic()
|
|
defer th.TearDown()
|
|
|
|
var mockAPI plugintest.API
|
|
mockAPI.On("LoadPluginConfiguration", mock.Anything).Return(nil)
|
|
mockAPI.On("LogDebug", "testhook.txt").Return(nil)
|
|
mockAPI.On("LogDebug", "inputfile").Return(nil)
|
|
tearDown, _, _ := SetAppEnvironmentWithPlugins(t, []string{
|
|
`
|
|
package main
|
|
|
|
import (
|
|
"io"
|
|
"github.com/mattermost/mattermost-server/plugin"
|
|
"github.com/mattermost/mattermost-server/model"
|
|
)
|
|
|
|
type MyPlugin struct {
|
|
plugin.MattermostPlugin
|
|
}
|
|
|
|
func (p *MyPlugin) FileWillBeUploaded(c *plugin.Context, info *model.FileInfo, file io.Reader, output io.Writer) (*model.FileInfo, string) {
|
|
return nil, "rejected"
|
|
}
|
|
|
|
func main() {
|
|
plugin.ClientMain(&MyPlugin{})
|
|
}
|
|
`,
|
|
}, th.App, func(*model.Manifest) plugin.API { return &mockAPI })
|
|
defer tearDown()
|
|
|
|
_, err := th.App.UploadFiles(
|
|
"noteam",
|
|
th.BasicChannel.Id,
|
|
th.BasicUser.Id,
|
|
[]io.ReadCloser{ioutil.NopCloser(bytes.NewBufferString("inputfile"))},
|
|
[]string{"testhook.txt"},
|
|
[]string{},
|
|
time.Now(),
|
|
)
|
|
if assert.NotNil(t, err) {
|
|
assert.Equal(t, "File rejected by plugin. rejected", err.Message)
|
|
}
|
|
})
|
|
|
|
t.Run("rejected, returned file ignored", func(t *testing.T) {
|
|
th := Setup().InitBasic()
|
|
defer th.TearDown()
|
|
|
|
var mockAPI plugintest.API
|
|
mockAPI.On("LoadPluginConfiguration", mock.Anything).Return(nil)
|
|
mockAPI.On("LogDebug", "testhook.txt").Return(nil)
|
|
mockAPI.On("LogDebug", "inputfile").Return(nil)
|
|
tearDown, _, _ := SetAppEnvironmentWithPlugins(t, []string{
|
|
`
|
|
package main
|
|
|
|
import (
|
|
"io"
|
|
"github.com/mattermost/mattermost-server/plugin"
|
|
"github.com/mattermost/mattermost-server/model"
|
|
)
|
|
|
|
type MyPlugin struct {
|
|
plugin.MattermostPlugin
|
|
}
|
|
|
|
func (p *MyPlugin) FileWillBeUploaded(c *plugin.Context, info *model.FileInfo, file io.Reader, output io.Writer) (*model.FileInfo, string) {
|
|
output.Write([]byte("ignored"))
|
|
info.Name = "ignored"
|
|
return info, "rejected"
|
|
}
|
|
|
|
func main() {
|
|
plugin.ClientMain(&MyPlugin{})
|
|
}
|
|
`,
|
|
}, th.App, func(*model.Manifest) plugin.API { return &mockAPI })
|
|
defer tearDown()
|
|
|
|
_, err := th.App.UploadFiles(
|
|
"noteam",
|
|
th.BasicChannel.Id,
|
|
th.BasicUser.Id,
|
|
[]io.ReadCloser{ioutil.NopCloser(bytes.NewBufferString("inputfile"))},
|
|
[]string{"testhook.txt"},
|
|
[]string{},
|
|
time.Now(),
|
|
)
|
|
if assert.NotNil(t, err) {
|
|
assert.Equal(t, "File rejected by plugin. rejected", err.Message)
|
|
}
|
|
})
|
|
|
|
t.Run("allowed", func(t *testing.T) {
|
|
th := Setup().InitBasic()
|
|
defer th.TearDown()
|
|
|
|
var mockAPI plugintest.API
|
|
mockAPI.On("LoadPluginConfiguration", mock.Anything).Return(nil)
|
|
mockAPI.On("LogDebug", "testhook.txt").Return(nil)
|
|
mockAPI.On("LogDebug", "inputfile").Return(nil)
|
|
tearDown, _, _ := SetAppEnvironmentWithPlugins(t, []string{
|
|
`
|
|
package main
|
|
|
|
import (
|
|
"io"
|
|
"github.com/mattermost/mattermost-server/plugin"
|
|
"github.com/mattermost/mattermost-server/model"
|
|
)
|
|
|
|
type MyPlugin struct {
|
|
plugin.MattermostPlugin
|
|
}
|
|
|
|
func (p *MyPlugin) FileWillBeUploaded(c *plugin.Context, info *model.FileInfo, file io.Reader, output io.Writer) (*model.FileInfo, string) {
|
|
return nil, ""
|
|
}
|
|
|
|
func main() {
|
|
plugin.ClientMain(&MyPlugin{})
|
|
}
|
|
`,
|
|
}, th.App, func(*model.Manifest) plugin.API { return &mockAPI })
|
|
defer tearDown()
|
|
|
|
response, err := th.App.UploadFiles(
|
|
"noteam",
|
|
th.BasicChannel.Id,
|
|
th.BasicUser.Id,
|
|
[]io.ReadCloser{ioutil.NopCloser(bytes.NewBufferString("inputfile"))},
|
|
[]string{"testhook.txt"},
|
|
[]string{},
|
|
time.Now(),
|
|
)
|
|
assert.Nil(t, err)
|
|
assert.NotNil(t, response)
|
|
assert.Equal(t, 1, len(response.FileInfos))
|
|
fileId := response.FileInfos[0].Id
|
|
|
|
fileInfo, err := th.App.GetFileInfo(fileId)
|
|
assert.Nil(t, err)
|
|
assert.NotNil(t, fileInfo)
|
|
assert.Equal(t, "testhook.txt", fileInfo.Name)
|
|
|
|
fileReader, err := th.App.FileReader(fileInfo.Path)
|
|
assert.Nil(t, err)
|
|
var resultBuf bytes.Buffer
|
|
io.Copy(&resultBuf, fileReader)
|
|
assert.Equal(t, "inputfile", resultBuf.String())
|
|
})
|
|
|
|
t.Run("updated", func(t *testing.T) {
|
|
th := Setup().InitBasic()
|
|
defer th.TearDown()
|
|
|
|
var mockAPI plugintest.API
|
|
mockAPI.On("LoadPluginConfiguration", mock.Anything).Return(nil)
|
|
mockAPI.On("LogDebug", "testhook.txt").Return(nil)
|
|
mockAPI.On("LogDebug", "inputfile").Return(nil)
|
|
tearDown, _, _ := SetAppEnvironmentWithPlugins(t, []string{
|
|
`
|
|
package main
|
|
|
|
import (
|
|
"io"
|
|
"bytes"
|
|
"github.com/mattermost/mattermost-server/plugin"
|
|
"github.com/mattermost/mattermost-server/model"
|
|
)
|
|
|
|
type MyPlugin struct {
|
|
plugin.MattermostPlugin
|
|
}
|
|
|
|
func (p *MyPlugin) FileWillBeUploaded(c *plugin.Context, info *model.FileInfo, file io.Reader, output io.Writer) (*model.FileInfo, string) {
|
|
p.API.LogDebug(info.Name)
|
|
var buf bytes.Buffer
|
|
buf.ReadFrom(file)
|
|
p.API.LogDebug(buf.String())
|
|
|
|
outbuf := bytes.NewBufferString("changedtext")
|
|
io.Copy(output, outbuf)
|
|
info.Name = "modifiedinfo"
|
|
return info, ""
|
|
}
|
|
|
|
func main() {
|
|
plugin.ClientMain(&MyPlugin{})
|
|
}
|
|
`,
|
|
}, th.App, func(*model.Manifest) plugin.API { return &mockAPI })
|
|
defer tearDown()
|
|
|
|
response, err := th.App.UploadFiles(
|
|
"noteam",
|
|
th.BasicChannel.Id,
|
|
th.BasicUser.Id,
|
|
[]io.ReadCloser{ioutil.NopCloser(bytes.NewBufferString("inputfile"))},
|
|
[]string{"testhook.txt"},
|
|
[]string{},
|
|
time.Now(),
|
|
)
|
|
assert.Nil(t, err)
|
|
assert.NotNil(t, response)
|
|
assert.Equal(t, 1, len(response.FileInfos))
|
|
fileId := response.FileInfos[0].Id
|
|
|
|
fileInfo, err := th.App.GetFileInfo(fileId)
|
|
assert.Nil(t, err)
|
|
assert.NotNil(t, fileInfo)
|
|
assert.Equal(t, "modifiedinfo", fileInfo.Name)
|
|
|
|
fileReader, err := th.App.FileReader(fileInfo.Path)
|
|
assert.Nil(t, err)
|
|
var resultBuf bytes.Buffer
|
|
io.Copy(&resultBuf, fileReader)
|
|
assert.Equal(t, "changedtext", resultBuf.String())
|
|
})
|
|
}
|
|
|
|
func TestUserWillLogIn_Blocked(t *testing.T) {
|
|
th := Setup().InitBasic()
|
|
defer th.TearDown()
|
|
|
|
err := th.App.UpdatePassword(th.BasicUser, "hunter2")
|
|
|
|
if err != nil {
|
|
t.Errorf("Error updating user password: %s", err)
|
|
}
|
|
|
|
tearDown, _, _ := SetAppEnvironmentWithPlugins(t,
|
|
[]string{
|
|
`
|
|
package main
|
|
|
|
import (
|
|
"github.com/mattermost/mattermost-server/plugin"
|
|
"github.com/mattermost/mattermost-server/model"
|
|
)
|
|
|
|
type MyPlugin struct {
|
|
plugin.MattermostPlugin
|
|
}
|
|
|
|
func (p *MyPlugin) UserWillLogIn(c *plugin.Context, user *model.User) string {
|
|
return "Blocked By Plugin"
|
|
}
|
|
|
|
func main() {
|
|
plugin.ClientMain(&MyPlugin{})
|
|
}
|
|
`}, th.App, th.App.NewPluginAPI)
|
|
defer tearDown()
|
|
|
|
user, err := th.App.AuthenticateUserForLogin("", th.BasicUser.Email, "hunter2", "", false)
|
|
|
|
if user != nil {
|
|
t.Errorf("Expected nil, got %+v", user)
|
|
}
|
|
|
|
if err == nil {
|
|
t.Errorf("Expected err, got nil")
|
|
}
|
|
}
|
|
|
|
func TestUserWillLogInIn_Passed(t *testing.T) {
|
|
th := Setup().InitBasic()
|
|
defer th.TearDown()
|
|
|
|
err := th.App.UpdatePassword(th.BasicUser, "hunter2")
|
|
|
|
if err != nil {
|
|
t.Errorf("Error updating user password: %s", err)
|
|
}
|
|
|
|
tearDown, _, _ := SetAppEnvironmentWithPlugins(t,
|
|
[]string{
|
|
`
|
|
package main
|
|
|
|
import (
|
|
"github.com/mattermost/mattermost-server/plugin"
|
|
"github.com/mattermost/mattermost-server/model"
|
|
)
|
|
|
|
type MyPlugin struct {
|
|
plugin.MattermostPlugin
|
|
}
|
|
|
|
func (p *MyPlugin) UserWillLogIn(c *plugin.Context, user *model.User) string {
|
|
return ""
|
|
}
|
|
|
|
func main() {
|
|
plugin.ClientMain(&MyPlugin{})
|
|
}
|
|
`}, th.App, th.App.NewPluginAPI)
|
|
defer tearDown()
|
|
|
|
user, err := th.App.AuthenticateUserForLogin("", th.BasicUser.Email, "hunter2", "", false)
|
|
|
|
if user == nil {
|
|
t.Errorf("Expected user object, got nil")
|
|
}
|
|
|
|
if err != nil {
|
|
t.Errorf("Expected nil, got %s", err)
|
|
}
|
|
}
|
|
|
|
func TestUserHasLoggedIn(t *testing.T) {
|
|
th := Setup().InitBasic()
|
|
defer th.TearDown()
|
|
|
|
err := th.App.UpdatePassword(th.BasicUser, "hunter2")
|
|
|
|
if err != nil {
|
|
t.Errorf("Error updating user password: %s", err)
|
|
}
|
|
|
|
tearDown, _, _ := SetAppEnvironmentWithPlugins(t,
|
|
[]string{
|
|
`
|
|
package main
|
|
|
|
import (
|
|
"github.com/mattermost/mattermost-server/plugin"
|
|
"github.com/mattermost/mattermost-server/model"
|
|
)
|
|
|
|
type MyPlugin struct {
|
|
plugin.MattermostPlugin
|
|
}
|
|
|
|
func (p *MyPlugin) UserHasLoggedIn(c *plugin.Context, user *model.User) {
|
|
user.FirstName = "plugin-callback-success"
|
|
p.API.UpdateUser(user)
|
|
}
|
|
|
|
func main() {
|
|
plugin.ClientMain(&MyPlugin{})
|
|
}
|
|
`}, th.App, th.App.NewPluginAPI)
|
|
defer tearDown()
|
|
|
|
user, err := th.App.AuthenticateUserForLogin("", th.BasicUser.Email, "hunter2", "", false)
|
|
|
|
if user == nil {
|
|
t.Errorf("Expected user object, got nil")
|
|
}
|
|
|
|
if err != nil {
|
|
t.Errorf("Expected nil, got %s", err)
|
|
}
|
|
|
|
time.Sleep(2 * time.Second)
|
|
|
|
user, _ = th.App.GetUser(th.BasicUser.Id)
|
|
|
|
if user.FirstName != "plugin-callback-success" {
|
|
t.Errorf("Expected firstname overwrite, got default")
|
|
}
|
|
}
|
|
|
|
func TestErrorString(t *testing.T) {
|
|
th := Setup().InitBasic()
|
|
defer th.TearDown()
|
|
|
|
t.Run("errors.New", func(t *testing.T) {
|
|
tearDown, _, activationErrors := SetAppEnvironmentWithPlugins(t,
|
|
[]string{
|
|
`
|
|
package main
|
|
|
|
import (
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/mattermost/mattermost-server/plugin"
|
|
)
|
|
|
|
type MyPlugin struct {
|
|
plugin.MattermostPlugin
|
|
}
|
|
|
|
func (p *MyPlugin) OnActivate() error {
|
|
return errors.New("simulate failure")
|
|
}
|
|
|
|
func main() {
|
|
plugin.ClientMain(&MyPlugin{})
|
|
}
|
|
`}, th.App, th.App.NewPluginAPI)
|
|
defer tearDown()
|
|
|
|
require.Len(t, activationErrors, 1)
|
|
require.NotNil(t, activationErrors[0])
|
|
require.Contains(t, activationErrors[0].Error(), "simulate failure")
|
|
})
|
|
|
|
t.Run("AppError", func(t *testing.T) {
|
|
tearDown, _, activationErrors := SetAppEnvironmentWithPlugins(t,
|
|
[]string{
|
|
`
|
|
package main
|
|
|
|
import (
|
|
"github.com/mattermost/mattermost-server/plugin"
|
|
"github.com/mattermost/mattermost-server/model"
|
|
)
|
|
|
|
type MyPlugin struct {
|
|
plugin.MattermostPlugin
|
|
}
|
|
|
|
func (p *MyPlugin) OnActivate() error {
|
|
return model.NewAppError("where", "id", map[string]interface{}{"param": 1}, "details", 42)
|
|
}
|
|
|
|
func main() {
|
|
plugin.ClientMain(&MyPlugin{})
|
|
}
|
|
`}, th.App, th.App.NewPluginAPI)
|
|
defer tearDown()
|
|
|
|
require.Len(t, activationErrors, 1)
|
|
require.NotNil(t, activationErrors[0])
|
|
|
|
cause := errors.Cause(activationErrors[0])
|
|
require.IsType(t, &model.AppError{}, cause)
|
|
|
|
// params not expected, since not exported
|
|
expectedErr := model.NewAppError("where", "id", nil, "details", 42)
|
|
require.Equal(t, expectedErr, cause)
|
|
})
|
|
}
|
|
|
|
func TestHookContext(t *testing.T) {
|
|
th := Setup().InitBasic()
|
|
defer th.TearDown()
|
|
|
|
// We don't actually have a session, we are faking it so just set something arbitrarily
|
|
th.App.Session.Id = model.NewId()
|
|
th.App.RequestId = model.NewId()
|
|
th.App.IpAddress = model.NewId()
|
|
th.App.AcceptLanguage = model.NewId()
|
|
th.App.UserAgent = model.NewId()
|
|
|
|
var mockAPI plugintest.API
|
|
mockAPI.On("LoadPluginConfiguration", mock.Anything).Return(nil)
|
|
mockAPI.On("LogDebug", th.App.Session.Id).Return(nil)
|
|
mockAPI.On("LogInfo", th.App.RequestId).Return(nil)
|
|
mockAPI.On("LogError", th.App.IpAddress).Return(nil)
|
|
mockAPI.On("LogWarn", th.App.AcceptLanguage).Return(nil)
|
|
mockAPI.On("DeleteTeam", th.App.UserAgent).Return(nil)
|
|
|
|
tearDown, _, _ := SetAppEnvironmentWithPlugins(t,
|
|
[]string{
|
|
`
|
|
package main
|
|
|
|
import (
|
|
"github.com/mattermost/mattermost-server/plugin"
|
|
"github.com/mattermost/mattermost-server/model"
|
|
)
|
|
|
|
type MyPlugin struct {
|
|
plugin.MattermostPlugin
|
|
}
|
|
|
|
func (p *MyPlugin) MessageHasBeenPosted(c *plugin.Context, post *model.Post) {
|
|
p.API.LogDebug(c.SessionId)
|
|
p.API.LogInfo(c.RequestId)
|
|
p.API.LogError(c.IpAddress)
|
|
p.API.LogWarn(c.AcceptLanguage)
|
|
p.API.DeleteTeam(c.UserAgent)
|
|
}
|
|
|
|
func main() {
|
|
plugin.ClientMain(&MyPlugin{})
|
|
}
|
|
`}, th.App, func(*model.Manifest) plugin.API { return &mockAPI })
|
|
defer tearDown()
|
|
|
|
post := &model.Post{
|
|
UserId: th.BasicUser.Id,
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "not this",
|
|
CreateAt: model.GetMillis() - 10000,
|
|
}
|
|
_, err := th.App.CreatePost(post, th.BasicChannel, false)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|