mirror of
https://github.com/hanzoai/team.git
synced 2026-09-30 01:57:35 +00:00
* Replace hardcoded test passwords with model.NewTestPassword() Add model.NewTestPassword() utility that generates 14+ character passwords meeting complexity requirements for FIPS compliance. Replace all short hardcoded test passwords across the test suite with calls to this function. * Enforce FIPS compliance for passwords and HMAC keys FIPS OpenSSL requires HMAC keys to be at least 14 bytes. PBKDF2 uses the password as the HMAC key internally, so short passwords cause PKCS5_PBKDF2_HMAC to fail. - Add FIPSEnabled and PasswordFIPSMinimumLength build-tag constants - Raise the password minimum length floor to 14 when compiled with requirefips, applied in SetDefaults only when unset and validated independently in IsValid - Return ErrMismatchedHashAndPassword for too-short passwords in PBKDF2 CompareHashAndPassword rather than a cryptic OpenSSL error - Validate atmos/camo HMAC key length under FIPS and lengthen test keys accordingly - Adjust password validation tests to use PasswordFIPSMinimumLength so they work under both FIPS and non-FIPS builds * CI: shard FIPS test suite and extract merge template Run FIPS tests on PRs that touch go.mod or have 'fips' in the branch name. Shard FIPS tests across 4 runners matching the normal Postgres suite. Extract the test result merge logic into a reusable workflow template to deduplicate the normal and FIPS merge jobs. * more * Fix email test helper to respect FIPS minimum password length * Fix test helpers to respect FIPS minimum password length * Remove unnecessary "disable strict password requirements" blocks from test helpers * Fix CodeRabbit review comments on PR #35905 - Add server-test-merge-template.yml to server-ci.yml pull_request.paths so changes to the reusable merge workflow trigger Server CI validation - Skip merge-postgres-fips-test-results job when test-postgres-normal-fips was skipped, preventing failures due to missing artifacts - Set guest.Password on returned guest in CreateGuestAndClient helper to keep contract consistent with CreateUserWithClient - Use shared LowercaseLetters/UppercaseLetters/NUMBERS/PasswordFIPSMinimumLength constants in NewTestPassword() to avoid drift if FIPS floor changes * Rename FIPS test artifact to match server-ci-report pattern The server-ci-report job searches for artifacts matching "*-test-logs", so rename from postgres-server-test-logs-fips to postgres-server-fips-test-logs to be included in the report. --------- Co-authored-by: Hanzo Dev <[email protected]>
2735 lines
72 KiB
Go
2735 lines
72 KiB
Go
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
package app
|
|
|
|
import (
|
|
"bytes"
|
|
_ "embed"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/pkg/errors"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/mock"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/mattermost/mattermost/server/public/model"
|
|
"github.com/mattermost/mattermost/server/public/plugin"
|
|
"github.com/mattermost/mattermost/server/public/plugin/plugintest"
|
|
"github.com/mattermost/mattermost/server/public/plugin/utils"
|
|
"github.com/mattermost/mattermost/server/public/shared/request"
|
|
"github.com/mattermost/mattermost/server/v8/channels/utils/testutils"
|
|
"github.com/mattermost/mattermost/server/v8/einterfaces/mocks"
|
|
)
|
|
|
|
func SetAppEnvironmentWithPlugins(t *testing.T, pluginCode []string, app *App, apiFunc func(*model.Manifest) plugin.API) (func(), []string, []error) {
|
|
return setAppEnvironmentWithPlugins(t, pluginCode, app, apiFunc, "")
|
|
}
|
|
|
|
func SetAppEnvironmentWithPluginsGoVersion(t *testing.T, pluginCode []string, app *App, apiFunc func(*model.Manifest) plugin.API, goVersion string) (func(), []string, []error) {
|
|
return setAppEnvironmentWithPlugins(t, pluginCode, app, apiFunc, goVersion)
|
|
}
|
|
|
|
func setAppEnvironmentWithPlugins(t *testing.T, pluginCode []string, app *App, apiFunc func(*model.Manifest) plugin.API, goVersion string) (func(), []string, []error) {
|
|
pluginDir, err := os.MkdirTemp("", "")
|
|
require.NoError(t, err)
|
|
webappPluginDir, err := os.MkdirTemp("", "")
|
|
require.NoError(t, err)
|
|
|
|
env, err := plugin.NewEnvironment(apiFunc, NewDriverImpl(app.Srv()), pluginDir, webappPluginDir, app.Log(), nil)
|
|
require.NoError(t, err)
|
|
|
|
app.ch.SetPluginsEnvironment(env)
|
|
pluginIDs := []string{}
|
|
activationErrors := []error{}
|
|
for _, code := range pluginCode {
|
|
pluginID := model.NewId()
|
|
backend := filepath.Join(pluginDir, pluginID, "backend.exe")
|
|
utils.CompileGoVersion(t, goVersion, code, backend)
|
|
|
|
err = os.WriteFile(filepath.Join(pluginDir, pluginID, "plugin.json"), []byte(`{"id": "`+pluginID+`", "server": {"executable": "backend.exe"}}`), 0600)
|
|
require.NoError(t, err)
|
|
_, _, activationErr := env.Activate(pluginID)
|
|
pluginIDs = append(pluginIDs, pluginID)
|
|
activationErrors = append(activationErrors, activationErr)
|
|
|
|
app.UpdateConfig(func(cfg *model.Config) {
|
|
cfg.PluginSettings.PluginStates[pluginID] = &model.PluginState{
|
|
Enable: true,
|
|
}
|
|
})
|
|
}
|
|
|
|
return func() {
|
|
os.RemoveAll(pluginDir)
|
|
os.RemoveAll(webappPluginDir)
|
|
}, pluginIDs, activationErrors
|
|
}
|
|
|
|
func TestHookMessageWillBePosted(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
t.Run("rejected", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
tearDown, _, _ := SetAppEnvironmentWithPlugins(t, []string{
|
|
`
|
|
package main
|
|
|
|
import (
|
|
"github.com/mattermost/mattermost/server/public/plugin"
|
|
"github.com/mattermost/mattermost/server/public/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.NewPluginAPI)
|
|
defer tearDown()
|
|
|
|
post := &model.Post{
|
|
UserId: th.BasicUser.Id,
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "message_",
|
|
CreateAt: model.GetMillis() - 10000,
|
|
}
|
|
_, _, err := th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{SetOnline: true})
|
|
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) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
tearDown, _, _ := SetAppEnvironmentWithPlugins(t, []string{
|
|
`
|
|
package main
|
|
|
|
import (
|
|
"github.com/mattermost/mattermost/server/public/plugin"
|
|
"github.com/mattermost/mattermost/server/public/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.NewPluginAPI)
|
|
defer tearDown()
|
|
|
|
post := &model.Post{
|
|
UserId: th.BasicUser.Id,
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "message_",
|
|
CreateAt: model.GetMillis() - 10000,
|
|
}
|
|
_, _, err := th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{SetOnline: true})
|
|
if assert.NotNil(t, err) {
|
|
assert.Equal(t, "Post rejected by plugin. rejected", err.Message)
|
|
}
|
|
})
|
|
|
|
t.Run("allowed", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
tearDown, _, _ := SetAppEnvironmentWithPlugins(t, []string{
|
|
`
|
|
package main
|
|
|
|
import (
|
|
"github.com/mattermost/mattermost/server/public/plugin"
|
|
"github.com/mattermost/mattermost/server/public/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.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(th.Context, post, th.BasicChannel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err)
|
|
|
|
assert.Equal(t, "message", post.Message)
|
|
retrievedPost, errSingle := th.App.Srv().Store().Post().GetSingle(th.Context, post.Id, false)
|
|
require.NoError(t, errSingle)
|
|
assert.Equal(t, "message", retrievedPost.Message)
|
|
})
|
|
|
|
t.Run("updated", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
tearDown, _, _ := SetAppEnvironmentWithPlugins(t, []string{
|
|
`
|
|
package main
|
|
|
|
import (
|
|
"github.com/mattermost/mattermost/server/public/plugin"
|
|
"github.com/mattermost/mattermost/server/public/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.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(th.Context, post, th.BasicChannel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err)
|
|
|
|
assert.Equal(t, "message_fromplugin", post.Message)
|
|
retrievedPost, errSingle := th.App.Srv().Store().Post().GetSingle(th.Context, post.Id, false)
|
|
require.NoError(t, errSingle)
|
|
assert.Equal(t, "message_fromplugin", retrievedPost.Message)
|
|
})
|
|
|
|
t.Run("multiple updated", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
tearDown, _, _ := SetAppEnvironmentWithPlugins(t, []string{
|
|
`
|
|
package main
|
|
|
|
import (
|
|
"github.com/mattermost/mattermost/server/public/plugin"
|
|
"github.com/mattermost/mattermost/server/public/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/public/plugin"
|
|
"github.com/mattermost/mattermost/server/public/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.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(th.Context, post, th.BasicChannel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err)
|
|
assert.Equal(t, "prefix_message_suffix", post.Message)
|
|
})
|
|
}
|
|
|
|
func TestHookMessageHasBeenPosted(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
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/public/plugin"
|
|
"github.com/mattermost/mattermost/server/public/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(th.Context, post, th.BasicChannel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err)
|
|
}
|
|
|
|
func TestHookMessageWillBeUpdated(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
tearDown, _, _ := SetAppEnvironmentWithPlugins(t,
|
|
[]string{
|
|
`
|
|
package main
|
|
|
|
import (
|
|
"github.com/mattermost/mattermost/server/public/plugin"
|
|
"github.com/mattermost/mattermost/server/public/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.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(th.Context, post, th.BasicChannel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err)
|
|
assert.Equal(t, "message_", post.Message)
|
|
post.Message = post.Message + "edited_"
|
|
post, _, err = th.App.UpdatePost(th.Context, post, &model.UpdatePostOptions{SafeUpdate: true})
|
|
require.Nil(t, err)
|
|
assert.Equal(t, "message_edited_fromplugin", post.Message)
|
|
}
|
|
|
|
func TestHookMessageHasBeenUpdated(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
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/public/plugin"
|
|
"github.com/mattermost/mattermost/server/public/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(th.Context, post, th.BasicChannel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err)
|
|
assert.Equal(t, "message_", post.Message)
|
|
post.Message = post.Message + "edited"
|
|
_, _, err = th.App.UpdatePost(th.Context, post, &model.UpdatePostOptions{SafeUpdate: true})
|
|
require.Nil(t, err)
|
|
}
|
|
|
|
func TestHookMessageHasBeenDeleted(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
var mockAPI plugintest.API
|
|
mockAPI.On("LoadPluginConfiguration", mock.Anything).Return(nil)
|
|
mockAPI.On("LogDebug", "message").Return(nil).Times(1)
|
|
|
|
tearDown, _, _ := SetAppEnvironmentWithPlugins(t,
|
|
[]string{
|
|
`
|
|
package main
|
|
|
|
import (
|
|
"github.com/mattermost/mattermost/server/public/plugin"
|
|
"github.com/mattermost/mattermost/server/public/model"
|
|
)
|
|
|
|
type MyPlugin struct {
|
|
plugin.MattermostPlugin
|
|
}
|
|
|
|
func (p *MyPlugin) MessageHasBeenDeleted(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(th.Context, post, th.BasicChannel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err)
|
|
_, err = th.App.DeletePost(th.Context, post.Id, th.BasicUser.Id)
|
|
require.Nil(t, err)
|
|
}
|
|
|
|
func TestHookFileWillBeUploaded(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
t.Run("rejected", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
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/public/plugin"
|
|
"github.com/mattermost/mattermost/server/public/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()
|
|
|
|
_, appErr := th.App.UploadFile(th.Context,
|
|
[]byte("inputfile"),
|
|
th.BasicChannel.Id,
|
|
"testhook.txt",
|
|
)
|
|
|
|
if assert.NotNil(t, appErr) {
|
|
assert.Equal(t, "File rejected by plugin. rejected", appErr.Message)
|
|
}
|
|
})
|
|
|
|
t.Run("rejected, returned file ignored", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
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 (
|
|
"fmt"
|
|
"io"
|
|
"github.com/mattermost/mattermost/server/public/plugin"
|
|
"github.com/mattermost/mattermost/server/public/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) {
|
|
n, err := output.Write([]byte("ignored"))
|
|
if err != nil {
|
|
return info, fmt.Sprintf("FAILED to write output file n: %v, err: %v", n, err)
|
|
}
|
|
info.Name = "ignored"
|
|
return info, "rejected"
|
|
}
|
|
|
|
func main() {
|
|
plugin.ClientMain(&MyPlugin{})
|
|
}
|
|
`,
|
|
}, th.App, func(*model.Manifest) plugin.API { return &mockAPI })
|
|
defer tearDown()
|
|
|
|
_, appErr := th.App.UploadFile(th.Context,
|
|
[]byte("inputfile"),
|
|
th.BasicChannel.Id,
|
|
"testhook.txt",
|
|
)
|
|
|
|
if assert.NotNil(t, appErr) {
|
|
assert.Equal(t, "File rejected by plugin. rejected", appErr.Message)
|
|
}
|
|
})
|
|
|
|
t.Run("allowed", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
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/public/plugin"
|
|
"github.com/mattermost/mattermost/server/public/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, appErr := th.App.UploadFile(th.Context,
|
|
[]byte("inputfile"),
|
|
th.BasicChannel.Id,
|
|
"testhook.txt",
|
|
)
|
|
|
|
assert.Nil(t, appErr)
|
|
assert.NotNil(t, response)
|
|
|
|
fileID := response.Id
|
|
fileInfo, appErr := th.App.GetFileInfo(th.Context, fileID)
|
|
assert.Nil(t, appErr)
|
|
assert.NotNil(t, fileInfo)
|
|
assert.Equal(t, "testhook.txt", fileInfo.Name)
|
|
|
|
fileReader, appErr := th.App.FileReader(fileInfo.Path)
|
|
assert.Nil(t, appErr)
|
|
var resultBuf bytes.Buffer
|
|
_, err := io.Copy(&resultBuf, fileReader)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "inputfile", resultBuf.String())
|
|
})
|
|
|
|
t.Run("updated", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
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"
|
|
"fmt"
|
|
"bytes"
|
|
"github.com/mattermost/mattermost/server/public/plugin"
|
|
"github.com/mattermost/mattermost/server/public/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) {
|
|
var buf bytes.Buffer
|
|
n, err := buf.ReadFrom(file)
|
|
if err != nil {
|
|
panic(fmt.Sprintf("buf.ReadFrom failed, reading %d bytes: %s", err.Error()))
|
|
}
|
|
|
|
outbuf := bytes.NewBufferString("changedtext")
|
|
n, err = io.Copy(output, outbuf)
|
|
if err != nil {
|
|
panic(fmt.Sprintf("io.Copy failed after %d bytes: %s", n, err.Error()))
|
|
}
|
|
if n != 11 {
|
|
panic(fmt.Sprintf("io.Copy only copied %d bytes", n))
|
|
}
|
|
info.Name = "modifiedinfo"
|
|
return info, ""
|
|
}
|
|
|
|
func main() {
|
|
plugin.ClientMain(&MyPlugin{})
|
|
}
|
|
`,
|
|
}, th.App, func(*model.Manifest) plugin.API { return &mockAPI })
|
|
defer tearDown()
|
|
|
|
response, appErr := th.App.UploadFile(th.Context,
|
|
[]byte("inputfile"),
|
|
th.BasicChannel.Id,
|
|
"testhook.txt",
|
|
)
|
|
assert.Nil(t, appErr)
|
|
assert.NotNil(t, response)
|
|
fileID := response.Id
|
|
|
|
fileInfo, appErr := th.App.GetFileInfo(th.Context, fileID)
|
|
assert.Nil(t, appErr)
|
|
assert.NotNil(t, fileInfo)
|
|
assert.Equal(t, "modifiedinfo", fileInfo.Name)
|
|
|
|
fileReader, appErr := th.App.FileReader(fileInfo.Path)
|
|
assert.Nil(t, appErr)
|
|
var resultBuf bytes.Buffer
|
|
_, err := io.Copy(&resultBuf, fileReader)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "changedtext", resultBuf.String())
|
|
})
|
|
}
|
|
|
|
func TestUserWillLogIn_Blocked(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
err := th.App.UpdatePassword(th.Context, th.BasicUser, model.NewTestPassword())
|
|
assert.Nil(t, err, "Error updating user password: %s", err)
|
|
tearDown, _, _ := SetAppEnvironmentWithPlugins(t,
|
|
[]string{
|
|
`
|
|
package main
|
|
|
|
import (
|
|
"github.com/mattermost/mattermost/server/public/plugin"
|
|
"github.com/mattermost/mattermost/server/public/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.NewPluginAPI)
|
|
defer tearDown()
|
|
|
|
r := &http.Request{}
|
|
w := httptest.NewRecorder()
|
|
session, err := th.App.DoLogin(th.Context, w, r, th.BasicUser, "", false, false, false)
|
|
|
|
assert.Contains(t, err.Id, "Login rejected by plugin", "Expected Login rejected by plugin, got %s", err.Id)
|
|
assert.Nil(t, session)
|
|
}
|
|
|
|
func TestUserWillLogInIn_Passed(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
err := th.App.UpdatePassword(th.Context, th.BasicUser, model.NewTestPassword())
|
|
|
|
assert.Nil(t, err, "Error updating user password: %s", err)
|
|
|
|
tearDown, _, _ := SetAppEnvironmentWithPlugins(t,
|
|
[]string{
|
|
`
|
|
package main
|
|
|
|
import (
|
|
"github.com/mattermost/mattermost/server/public/plugin"
|
|
"github.com/mattermost/mattermost/server/public/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.NewPluginAPI)
|
|
defer tearDown()
|
|
|
|
r := &http.Request{}
|
|
w := httptest.NewRecorder()
|
|
session, err := th.App.DoLogin(th.Context, w, r, th.BasicUser, "", false, false, false)
|
|
|
|
assert.Nil(t, err, "Expected nil, got %s", err)
|
|
require.NotNil(t, session)
|
|
assert.Equal(t, session.UserId, th.BasicUser.Id)
|
|
}
|
|
|
|
func TestUserHasLoggedIn(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
err := th.App.UpdatePassword(th.Context, th.BasicUser, model.NewTestPassword())
|
|
|
|
assert.Nil(t, err, "Error updating user password: %s", err)
|
|
|
|
tearDown, _, _ := SetAppEnvironmentWithPlugins(t,
|
|
[]string{
|
|
`
|
|
package main
|
|
|
|
import (
|
|
"github.com/mattermost/mattermost/server/public/plugin"
|
|
"github.com/mattermost/mattermost/server/public/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.NewPluginAPI)
|
|
defer tearDown()
|
|
|
|
r := &http.Request{}
|
|
w := httptest.NewRecorder()
|
|
session, err := th.App.DoLogin(th.Context, w, r, th.BasicUser, "", false, false, false)
|
|
|
|
assert.Nil(t, err, "Expected nil, got %s", err)
|
|
assert.NotNil(t, session)
|
|
|
|
require.EventuallyWithT(t, func(c *assert.CollectT) {
|
|
user, _ := th.App.GetUser(th.BasicUser.Id)
|
|
assert.Equal(c, user.FirstName, "plugin-callback-success", "Expected firstname overwrite, got default")
|
|
}, 2*time.Second, 100*time.Millisecond)
|
|
}
|
|
|
|
func TestUserHasBeenDeactivated(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t)
|
|
|
|
tearDown, _, _ := SetAppEnvironmentWithPlugins(t,
|
|
[]string{
|
|
`
|
|
package main
|
|
|
|
import (
|
|
"github.com/mattermost/mattermost/server/public/plugin"
|
|
"github.com/mattermost/mattermost/server/public/model"
|
|
)
|
|
|
|
type MyPlugin struct {
|
|
plugin.MattermostPlugin
|
|
}
|
|
|
|
func (p *MyPlugin) UserHasBeenDeactivated(c *plugin.Context, user *model.User) {
|
|
user.Nickname = "plugin-callback-success"
|
|
p.API.UpdateUser(user)
|
|
}
|
|
|
|
func main() {
|
|
plugin.ClientMain(&MyPlugin{})
|
|
}
|
|
`,
|
|
}, th.App, th.NewPluginAPI)
|
|
defer tearDown()
|
|
|
|
user := &model.User{
|
|
Email: "[email protected]",
|
|
Nickname: "testnickname",
|
|
Username: "testusername",
|
|
Password: model.NewTestPassword(),
|
|
}
|
|
|
|
_, err := th.App.CreateUser(th.Context, user)
|
|
require.Nil(t, err)
|
|
|
|
_, err = th.App.UpdateActive(th.Context, user, false)
|
|
require.Nil(t, err)
|
|
|
|
time.Sleep(2 * time.Second)
|
|
user, err = th.App.GetUser(user.Id)
|
|
require.Nil(t, err)
|
|
require.Equal(t, "plugin-callback-success", user.Nickname)
|
|
}
|
|
|
|
func TestUserHasBeenCreated(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t)
|
|
|
|
tearDown, _, _ := SetAppEnvironmentWithPlugins(t,
|
|
[]string{
|
|
`
|
|
package main
|
|
|
|
import (
|
|
"github.com/mattermost/mattermost/server/public/plugin"
|
|
"github.com/mattermost/mattermost/server/public/model"
|
|
)
|
|
|
|
type MyPlugin struct {
|
|
plugin.MattermostPlugin
|
|
}
|
|
|
|
func (p *MyPlugin) UserHasBeenCreated(c *plugin.Context, user *model.User) {
|
|
user.Nickname = "plugin-callback-success"
|
|
p.API.UpdateUser(user)
|
|
}
|
|
|
|
func main() {
|
|
plugin.ClientMain(&MyPlugin{})
|
|
}
|
|
`,
|
|
}, th.App, th.NewPluginAPI)
|
|
defer tearDown()
|
|
|
|
user := &model.User{
|
|
Email: "[email protected]",
|
|
Nickname: "testnickname",
|
|
Username: "testusername",
|
|
Password: model.NewTestPassword(),
|
|
}
|
|
_, err := th.App.CreateUser(th.Context, user)
|
|
require.Nil(t, err)
|
|
|
|
time.Sleep(2 * time.Second)
|
|
user, err = th.App.GetUser(user.Id)
|
|
require.Nil(t, err)
|
|
require.Equal(t, "plugin-callback-success", user.Nickname)
|
|
}
|
|
|
|
func TestErrorString(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t)
|
|
|
|
t.Run("errors.New", func(t *testing.T) {
|
|
tearDown, _, activationErrors := SetAppEnvironmentWithPlugins(t,
|
|
[]string{
|
|
`
|
|
package main
|
|
|
|
import (
|
|
"errors"
|
|
|
|
"github.com/mattermost/mattermost/server/public/plugin"
|
|
)
|
|
|
|
type MyPlugin struct {
|
|
plugin.MattermostPlugin
|
|
}
|
|
|
|
func (p *MyPlugin) OnActivate() error {
|
|
return errors.New("simulate failure")
|
|
}
|
|
|
|
func main() {
|
|
plugin.ClientMain(&MyPlugin{})
|
|
}
|
|
`,
|
|
}, th.App, th.NewPluginAPI)
|
|
defer tearDown()
|
|
|
|
require.Len(t, activationErrors, 1)
|
|
require.Error(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/public/plugin"
|
|
"github.com/mattermost/mattermost/server/public/model"
|
|
)
|
|
|
|
type MyPlugin struct {
|
|
plugin.MattermostPlugin
|
|
}
|
|
|
|
func (p *MyPlugin) OnActivate() error {
|
|
return model.NewAppError("where", "id", map[string]any{"param": 1}, "details", 42)
|
|
}
|
|
|
|
func main() {
|
|
plugin.ClientMain(&MyPlugin{})
|
|
}
|
|
`,
|
|
}, th.App, th.NewPluginAPI)
|
|
defer tearDown()
|
|
|
|
require.Len(t, activationErrors, 1)
|
|
require.Error(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) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
ctx := request.EmptyContext(th.TestLogger)
|
|
|
|
// We don't actually have a session, we are faking it so just set something arbitrarily
|
|
ctx.Session().Id = model.NewId()
|
|
|
|
var mockAPI plugintest.API
|
|
mockAPI.On("LoadPluginConfiguration", mock.Anything).Return(nil)
|
|
mockAPI.On("LogDebug", ctx.Session().Id).Return(nil)
|
|
mockAPI.On("LogInfo", ctx.RequestId()).Return(nil)
|
|
mockAPI.On("LogError", ctx.IPAddress()).Return(nil)
|
|
mockAPI.On("LogWarn", ctx.AcceptLanguage()).Return(nil)
|
|
mockAPI.On("DeleteTeam", ctx.UserAgent()).Return(nil)
|
|
|
|
tearDown, _, _ := SetAppEnvironmentWithPlugins(t,
|
|
[]string{
|
|
`
|
|
package main
|
|
|
|
import (
|
|
"github.com/mattermost/mattermost/server/public/plugin"
|
|
"github.com/mattermost/mattermost/server/public/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(ctx, post, th.BasicChannel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err)
|
|
}
|
|
|
|
func TestActiveHooks(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t)
|
|
|
|
t.Run("", func(t *testing.T) {
|
|
tearDown, pluginIDs, _ := SetAppEnvironmentWithPlugins(t,
|
|
[]string{
|
|
`
|
|
package main
|
|
|
|
import (
|
|
"github.com/mattermost/mattermost/server/public/model"
|
|
"github.com/mattermost/mattermost/server/public/plugin"
|
|
)
|
|
|
|
type MyPlugin struct {
|
|
plugin.MattermostPlugin
|
|
}
|
|
|
|
func (p *MyPlugin) OnActivate() error {
|
|
return nil
|
|
}
|
|
|
|
func (p *MyPlugin) OnConfigurationChange() error {
|
|
return nil
|
|
}
|
|
|
|
func (p *MyPlugin) UserHasBeenCreated(c *plugin.Context, user *model.User) {
|
|
user.Nickname = "plugin-callback-success"
|
|
p.API.UpdateUser(user)
|
|
}
|
|
|
|
func main() {
|
|
plugin.ClientMain(&MyPlugin{})
|
|
}
|
|
`,
|
|
}, th.App, th.NewPluginAPI)
|
|
defer tearDown()
|
|
|
|
require.Len(t, pluginIDs, 1)
|
|
pluginID := pluginIDs[0]
|
|
|
|
require.True(t, th.App.GetPluginsEnvironment().IsActive(pluginID))
|
|
user1 := &model.User{
|
|
Email: "[email protected]",
|
|
Nickname: "testnickname",
|
|
Username: "testusername",
|
|
Password: model.NewTestPassword(),
|
|
}
|
|
_, appErr := th.App.CreateUser(th.Context, user1)
|
|
require.Nil(t, appErr)
|
|
time.Sleep(2 * time.Second)
|
|
user1, appErr = th.App.GetUser(user1.Id)
|
|
require.Nil(t, appErr)
|
|
require.Equal(t, "plugin-callback-success", user1.Nickname)
|
|
|
|
// Disable plugin
|
|
require.True(t, th.App.GetPluginsEnvironment().Deactivate(pluginID))
|
|
require.False(t, th.App.GetPluginsEnvironment().IsActive(pluginID))
|
|
|
|
hooks, err := th.App.GetPluginsEnvironment().HooksForPlugin(pluginID)
|
|
require.Error(t, err)
|
|
require.Nil(t, hooks)
|
|
|
|
// Should fail to find pluginID as it was deleted when deactivated
|
|
path, err := th.App.GetPluginsEnvironment().PublicFilesPath(pluginID)
|
|
require.Error(t, err)
|
|
require.Empty(t, path)
|
|
})
|
|
}
|
|
|
|
func TestHookMetrics(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t)
|
|
|
|
t.Run("", func(t *testing.T) {
|
|
metricsMock := &mocks.MetricsInterface{}
|
|
|
|
pluginDir, err := os.MkdirTemp("", "")
|
|
require.NoError(t, err)
|
|
webappPluginDir, err := os.MkdirTemp("", "")
|
|
require.NoError(t, err)
|
|
defer os.RemoveAll(pluginDir)
|
|
defer os.RemoveAll(webappPluginDir)
|
|
|
|
env, err := plugin.NewEnvironment(th.NewPluginAPI, NewDriverImpl(th.Server), pluginDir, webappPluginDir, th.App.Log(), metricsMock)
|
|
require.NoError(t, err)
|
|
|
|
th.App.ch.SetPluginsEnvironment(env)
|
|
|
|
pluginID := model.NewId()
|
|
backend := filepath.Join(pluginDir, pluginID, "backend.exe")
|
|
code := `
|
|
package main
|
|
|
|
import (
|
|
"github.com/mattermost/mattermost/server/public/model"
|
|
"github.com/mattermost/mattermost/server/public/plugin"
|
|
)
|
|
|
|
type MyPlugin struct {
|
|
plugin.MattermostPlugin
|
|
}
|
|
|
|
func (p *MyPlugin) OnActivate() error {
|
|
return nil
|
|
}
|
|
|
|
func (p *MyPlugin) OnConfigurationChange() error {
|
|
return nil
|
|
}
|
|
|
|
func (p *MyPlugin) UserHasBeenCreated(c *plugin.Context, user *model.User) {
|
|
user.Nickname = "plugin-callback-success"
|
|
p.API.UpdateUser(user)
|
|
}
|
|
|
|
func main() {
|
|
plugin.ClientMain(&MyPlugin{})
|
|
}
|
|
`
|
|
utils.CompileGo(t, code, backend)
|
|
err = os.WriteFile(filepath.Join(pluginDir, pluginID, "plugin.json"), []byte(`{"id": "`+pluginID+`", "server": {"executable": "backend.exe"}}`), 0600)
|
|
require.NoError(t, err)
|
|
|
|
// Setup mocks before activating
|
|
metricsMock.On("ObservePluginHookDuration", pluginID, "Implemented", true, mock.Anything).Return()
|
|
metricsMock.On("ObservePluginHookDuration", pluginID, "OnActivate", true, mock.Anything).Return()
|
|
metricsMock.On("ObservePluginHookDuration", pluginID, "OnDeactivate", true, mock.Anything).Return()
|
|
metricsMock.On("ObservePluginHookDuration", pluginID, "OnConfigurationChange", true, mock.Anything).Return()
|
|
metricsMock.On("ObservePluginHookDuration", pluginID, "UserHasBeenCreated", true, mock.Anything).Return()
|
|
|
|
// Don't care about these calls.
|
|
metricsMock.On("ObservePluginAPIDuration", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return()
|
|
metricsMock.On("ObservePluginMultiHookIterationDuration", mock.Anything, mock.Anything, mock.Anything).Return()
|
|
metricsMock.On("ObservePluginMultiHookDuration", mock.Anything).Return()
|
|
|
|
_, _, activationErr := env.Activate(pluginID)
|
|
require.NoError(t, activationErr)
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
cfg.PluginSettings.PluginStates[pluginID] = &model.PluginState{
|
|
Enable: true,
|
|
}
|
|
})
|
|
|
|
require.True(t, th.App.GetPluginsEnvironment().IsActive(pluginID))
|
|
|
|
user1 := &model.User{
|
|
Email: "[email protected]",
|
|
Nickname: "testnickname",
|
|
Username: "testusername",
|
|
Password: model.NewTestPassword(),
|
|
AuthService: "",
|
|
}
|
|
_, appErr := th.App.CreateUser(th.Context, user1)
|
|
require.Nil(t, appErr)
|
|
time.Sleep(2 * time.Second)
|
|
user1, appErr = th.App.GetUser(user1.Id)
|
|
require.Nil(t, appErr)
|
|
require.Equal(t, "plugin-callback-success", user1.Nickname)
|
|
|
|
// Disable plugin
|
|
require.True(t, th.App.GetPluginsEnvironment().Deactivate(pluginID))
|
|
require.False(t, th.App.GetPluginsEnvironment().IsActive(pluginID))
|
|
|
|
metricsMock.AssertExpectations(t)
|
|
})
|
|
}
|
|
|
|
func TestHookReactionHasBeenAdded(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
var mockAPI plugintest.API
|
|
mockAPI.On("LogDebug", "smile").Return(nil)
|
|
|
|
tearDown, _, _ := SetAppEnvironmentWithPlugins(t,
|
|
[]string{
|
|
`
|
|
package main
|
|
|
|
import (
|
|
"github.com/mattermost/mattermost/server/public/plugin"
|
|
"github.com/mattermost/mattermost/server/public/model"
|
|
)
|
|
|
|
type MyPlugin struct {
|
|
plugin.MattermostPlugin
|
|
}
|
|
|
|
func (p *MyPlugin) ReactionHasBeenAdded(c *plugin.Context, reaction *model.Reaction) {
|
|
p.API.LogDebug(reaction.EmojiName)
|
|
}
|
|
|
|
func main() {
|
|
plugin.ClientMain(&MyPlugin{})
|
|
}
|
|
`,
|
|
}, th.App, func(*model.Manifest) plugin.API { return &mockAPI })
|
|
defer tearDown()
|
|
|
|
reaction := &model.Reaction{
|
|
UserId: th.BasicUser.Id,
|
|
PostId: th.BasicPost.Id,
|
|
EmojiName: "smile",
|
|
CreateAt: model.GetMillis() - 10000,
|
|
}
|
|
_, err := th.App.SaveReactionForPost(th.Context, reaction)
|
|
require.Nil(t, err)
|
|
|
|
assert.EventuallyWithT(t, func(c *assert.CollectT) {
|
|
mockAPI.AssertExpectations(&testutils.CollectTWithLogf{CollectT: c})
|
|
}, 5*time.Second, 100*time.Millisecond)
|
|
}
|
|
|
|
func TestHookReactionHasBeenRemoved(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
var mockAPI plugintest.API
|
|
mockAPI.On("LogDebug", "star").Return(nil)
|
|
|
|
tearDown, _, _ := SetAppEnvironmentWithPlugins(t,
|
|
[]string{
|
|
`
|
|
package main
|
|
|
|
import (
|
|
"github.com/mattermost/mattermost/server/public/plugin"
|
|
"github.com/mattermost/mattermost/server/public/model"
|
|
)
|
|
|
|
type MyPlugin struct {
|
|
plugin.MattermostPlugin
|
|
}
|
|
|
|
func (p *MyPlugin) ReactionHasBeenRemoved(c *plugin.Context, reaction *model.Reaction) {
|
|
p.API.LogDebug(reaction.EmojiName)
|
|
}
|
|
|
|
func main() {
|
|
plugin.ClientMain(&MyPlugin{})
|
|
}
|
|
`,
|
|
}, th.App, func(*model.Manifest) plugin.API { return &mockAPI })
|
|
defer tearDown()
|
|
|
|
reaction := &model.Reaction{
|
|
UserId: th.BasicUser.Id,
|
|
PostId: th.BasicPost.Id,
|
|
EmojiName: "star",
|
|
CreateAt: model.GetMillis() - 10000,
|
|
}
|
|
|
|
err := th.App.DeleteReactionForPost(th.Context, reaction)
|
|
|
|
require.Nil(t, err)
|
|
|
|
assert.EventuallyWithT(t, func(c *assert.CollectT) {
|
|
mockAPI.AssertExpectations(&testutils.CollectTWithLogf{CollectT: c})
|
|
}, 5*time.Second, 100*time.Millisecond)
|
|
}
|
|
|
|
func TestHookRunDataRetention(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
tearDown, pluginIDs, _ := SetAppEnvironmentWithPlugins(t,
|
|
[]string{
|
|
`
|
|
package main
|
|
|
|
import (
|
|
"github.com/mattermost/mattermost/server/public/plugin"
|
|
)
|
|
|
|
type MyPlugin struct {
|
|
plugin.MattermostPlugin
|
|
}
|
|
|
|
func (p *MyPlugin) RunDataRetention(nowMillis, batchSize int64) (int64, error){
|
|
return 100, nil
|
|
}
|
|
|
|
func main() {
|
|
plugin.ClientMain(&MyPlugin{})
|
|
}
|
|
`,
|
|
}, th.App, th.NewPluginAPI)
|
|
defer tearDown()
|
|
|
|
require.Len(t, pluginIDs, 1)
|
|
pluginID := pluginIDs[0]
|
|
|
|
require.True(t, th.App.GetPluginsEnvironment().IsActive(pluginID))
|
|
|
|
hookCalled := false
|
|
th.App.Channels().RunMultiHook(func(hooks plugin.Hooks, _ *model.Manifest) bool {
|
|
n, _ := hooks.RunDataRetention(0, 0)
|
|
// Ensure return it correct
|
|
assert.Equal(t, int64(100), n)
|
|
hookCalled = true
|
|
return hookCalled
|
|
}, plugin.RunDataRetentionID)
|
|
|
|
require.True(t, hookCalled)
|
|
}
|
|
|
|
func TestHookOnSendDailyTelemetry(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
tearDown, pluginIDs, _ := SetAppEnvironmentWithPlugins(t,
|
|
[]string{
|
|
`
|
|
package main
|
|
|
|
import (
|
|
"github.com/mattermost/mattermost/server/public/plugin"
|
|
)
|
|
|
|
type MyPlugin struct {
|
|
plugin.MattermostPlugin
|
|
}
|
|
|
|
func (p *MyPlugin) OnSendDailyTelemetry() {
|
|
return
|
|
}
|
|
|
|
func main() {
|
|
plugin.ClientMain(&MyPlugin{})
|
|
}
|
|
`,
|
|
}, th.App, th.NewPluginAPI)
|
|
defer tearDown()
|
|
|
|
require.Len(t, pluginIDs, 1)
|
|
pluginID := pluginIDs[0]
|
|
|
|
require.True(t, th.App.GetPluginsEnvironment().IsActive(pluginID))
|
|
|
|
hookCalled := false
|
|
th.App.Channels().RunMultiHook(func(hooks plugin.Hooks, _ *model.Manifest) bool {
|
|
hooks.OnSendDailyTelemetry()
|
|
|
|
hookCalled = true
|
|
return hookCalled
|
|
}, plugin.OnSendDailyTelemetryID)
|
|
|
|
require.True(t, hookCalled)
|
|
}
|
|
|
|
func TestHookOnCloudLimitsUpdated(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
tearDown, pluginIDs, _ := SetAppEnvironmentWithPlugins(t,
|
|
[]string{
|
|
`
|
|
package main
|
|
|
|
import (
|
|
"github.com/mattermost/mattermost/server/public/model"
|
|
"github.com/mattermost/mattermost/server/public/plugin"
|
|
)
|
|
|
|
type MyPlugin struct {
|
|
plugin.MattermostPlugin
|
|
}
|
|
|
|
func (p *MyPlugin) OnCloudLimitsUpdated(_ *model.ProductLimits) {
|
|
return
|
|
}
|
|
|
|
func main() {
|
|
plugin.ClientMain(&MyPlugin{})
|
|
}
|
|
`,
|
|
}, th.App, th.NewPluginAPI)
|
|
defer tearDown()
|
|
|
|
require.Len(t, pluginIDs, 1)
|
|
pluginID := pluginIDs[0]
|
|
|
|
require.True(t, th.App.GetPluginsEnvironment().IsActive(pluginID))
|
|
|
|
hookCalled := false
|
|
th.App.Channels().RunMultiHook(func(hooks plugin.Hooks, _ *model.Manifest) bool {
|
|
hooks.OnCloudLimitsUpdated(nil)
|
|
|
|
hookCalled = true
|
|
return hookCalled
|
|
}, plugin.OnCloudLimitsUpdatedID)
|
|
|
|
require.True(t, hookCalled)
|
|
}
|
|
|
|
//go:embed test_templates/hook_notification_will_be_pushed.tmpl
|
|
var hookNotificationWillBePushedTmpl string
|
|
|
|
func TestHookNotificationWillBePushed(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
if testing.Short() {
|
|
t.Skip("skipping TestHookNotificationWillBePushed test in short mode")
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
testCode string
|
|
expectedNotifications int
|
|
expectedNotificationMessage string
|
|
}{
|
|
{
|
|
name: "successfully pushed",
|
|
testCode: `return nil, ""`,
|
|
expectedNotifications: 6,
|
|
},
|
|
{
|
|
name: "push notification rejected",
|
|
testCode: `return nil, "rejected"`,
|
|
expectedNotifications: 0,
|
|
},
|
|
{
|
|
name: "push notification modified",
|
|
testCode: `notification.Message = "brand new message"
|
|
return notification, ""`,
|
|
expectedNotifications: 6,
|
|
expectedNotificationMessage: "brand new message",
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
templatedPlugin := fmt.Sprintf(hookNotificationWillBePushedTmpl, tt.testCode)
|
|
tearDown, _, _ := SetAppEnvironmentWithPlugins(t, []string{templatedPlugin}, th.App, th.NewPluginAPI)
|
|
defer tearDown()
|
|
|
|
// Create 3 users, each having 2 sessions.
|
|
type userSession struct {
|
|
user *model.User
|
|
session *model.Session
|
|
}
|
|
var userSessions []userSession
|
|
for range 3 {
|
|
u := th.CreateUser(t)
|
|
sess, err := th.App.CreateSession(th.Context, &model.Session{
|
|
UserId: u.Id,
|
|
DeviceId: "deviceID" + u.Id,
|
|
ExpiresAt: model.GetMillis() + 100000,
|
|
})
|
|
require.Nil(t, err)
|
|
// We don't need to track the 2nd session.
|
|
_, err = th.App.CreateSession(th.Context, &model.Session{
|
|
UserId: u.Id,
|
|
DeviceId: "deviceID" + u.Id,
|
|
ExpiresAt: model.GetMillis() + 100000,
|
|
})
|
|
require.Nil(t, err)
|
|
_, err = th.App.AddTeamMember(th.Context, th.BasicTeam.Id, u.Id)
|
|
require.Nil(t, err)
|
|
th.AddUserToChannel(t, u, th.BasicChannel)
|
|
userSessions = append(userSessions, userSession{
|
|
user: u,
|
|
session: sess,
|
|
})
|
|
}
|
|
|
|
handler := &testPushNotificationHandler{
|
|
t: t,
|
|
behavior: "simple",
|
|
}
|
|
pushServer := httptest.NewServer(
|
|
http.HandlerFunc(handler.handleReq),
|
|
)
|
|
defer pushServer.Close()
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.EmailSettings.PushNotificationContents = model.GenericNotification
|
|
*cfg.EmailSettings.PushNotificationServer = pushServer.URL
|
|
})
|
|
|
|
var wg sync.WaitGroup
|
|
for _, data := range userSessions {
|
|
wg.Add(1)
|
|
go func(user model.User) {
|
|
defer wg.Done()
|
|
notification := &PostNotification{
|
|
Post: th.CreatePost(t, th.BasicChannel),
|
|
Channel: th.BasicChannel,
|
|
ProfileMap: map[string]*model.User{
|
|
user.Id: &user,
|
|
},
|
|
Sender: &user,
|
|
}
|
|
th.App.sendPushNotification(notification, &user, true, false, model.CommentsNotifyAny)
|
|
}(*data.user)
|
|
}
|
|
wg.Wait()
|
|
|
|
// Hack to let the worker goroutines complete.
|
|
time.Sleep(2 * time.Second)
|
|
// Server side verification.
|
|
assert.Equal(t, tt.expectedNotifications, handler.numReqs())
|
|
var numMessages int
|
|
for _, n := range handler.notifications() {
|
|
switch n.Type {
|
|
case model.PushTypeMessage:
|
|
numMessages++
|
|
assert.Equal(t, th.BasicChannel.Id, n.ChannelId)
|
|
if tt.expectedNotificationMessage != "" {
|
|
assert.Equal(t, tt.expectedNotificationMessage, n.Message)
|
|
} else {
|
|
assert.Contains(t, n.Message, "mentioned you")
|
|
}
|
|
default:
|
|
assert.Fail(t, "should not receive any other push notification types")
|
|
}
|
|
}
|
|
assert.Equal(t, tt.expectedNotifications, numMessages)
|
|
})
|
|
}
|
|
}
|
|
|
|
//go:embed test_templates/hook_email_notification_will_be_sent.tmpl
|
|
var hookEmailNotificationWillBeSentTmpl string
|
|
|
|
func TestHookEmailNotificationWillBeSent(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
|
|
tests := []struct {
|
|
name string
|
|
testCode string
|
|
expectedNotificationSubject string
|
|
expectedNotificationTitle string
|
|
expectedButtonText string
|
|
expectedFooterText string
|
|
}{
|
|
{
|
|
name: "successfully sent",
|
|
testCode: `return nil, ""`,
|
|
},
|
|
{
|
|
name: "email notification rejected",
|
|
testCode: `return nil, "rejected"`,
|
|
},
|
|
{
|
|
name: "email notification modified",
|
|
testCode: `content := &model.EmailNotificationContent{
|
|
Subject: "Modified Subject by Plugin",
|
|
Title: "Modified Title by Plugin",
|
|
ButtonText: "Modified Button by Plugin",
|
|
FooterText: "Modified Footer by Plugin",
|
|
}
|
|
return content, ""`,
|
|
expectedNotificationSubject: "Modified Subject by Plugin",
|
|
expectedNotificationTitle: "Modified Title by Plugin",
|
|
expectedButtonText: "Modified Button by Plugin",
|
|
expectedFooterText: "Modified Footer by Plugin",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
// Create a test user for email notifications
|
|
user := th.CreateUser(t)
|
|
th.LinkUserToTeam(t, user, th.BasicTeam)
|
|
th.AddUserToChannel(t, user, th.BasicChannel)
|
|
|
|
// Set up email notification preferences to disable batching
|
|
appErr := th.App.UpdatePreferences(th.Context, user.Id, model.Preferences{
|
|
{
|
|
UserId: user.Id,
|
|
Category: model.PreferenceCategoryNotifications,
|
|
Name: model.PreferenceNameEmailInterval,
|
|
Value: model.PreferenceEmailIntervalNoBatchingSeconds,
|
|
},
|
|
})
|
|
require.Nil(t, appErr)
|
|
|
|
// Disable email batching in config
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.EmailSettings.EnableEmailBatching = false
|
|
})
|
|
|
|
// Create and set up plugin
|
|
templatedPlugin := fmt.Sprintf(hookEmailNotificationWillBeSentTmpl, tt.testCode)
|
|
tearDown, _, _ := SetAppEnvironmentWithPlugins(t, []string{templatedPlugin}, th.App, th.NewPluginAPI)
|
|
defer tearDown()
|
|
|
|
// For the modification test, create a simple test that verifies the hook is called
|
|
// The detailed verification would require more complex mocking which is beyond this test's scope
|
|
|
|
// Create a post that will trigger email notification
|
|
post := &model.Post{
|
|
UserId: th.BasicUser.Id,
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "@" + user.Username + " test message",
|
|
CreateAt: model.GetMillis(),
|
|
}
|
|
|
|
// Create notification
|
|
notification := &PostNotification{
|
|
Post: post,
|
|
Channel: th.BasicChannel,
|
|
ProfileMap: map[string]*model.User{
|
|
user.Id: user,
|
|
},
|
|
Sender: th.BasicUser,
|
|
}
|
|
|
|
// Send email notification (this will trigger the hook)
|
|
// Use assert.Eventually to handle any potential race conditions with plugin activation/deactivation
|
|
assert.Eventually(t, func() bool {
|
|
modifiedNotification, err := th.App.sendNotificationEmail(th.Context, notification, user, th.BasicTeam, nil)
|
|
|
|
// For the rejected test case, we expect the notification to be rejected
|
|
if tt.name == "email notification rejected" {
|
|
// When rejected, sendNotificationEmail returns nil for the notification
|
|
return modifiedNotification == nil && err == nil
|
|
}
|
|
if err != nil || modifiedNotification == nil {
|
|
return false
|
|
}
|
|
|
|
// Verify the modified notification fields
|
|
if tt.expectedNotificationSubject != "" && modifiedNotification.Subject != tt.expectedNotificationSubject {
|
|
return false
|
|
}
|
|
if tt.expectedNotificationTitle != "" && modifiedNotification.Title != tt.expectedNotificationTitle {
|
|
return false
|
|
}
|
|
if tt.expectedButtonText != "" && modifiedNotification.ButtonText != tt.expectedButtonText {
|
|
return false
|
|
}
|
|
if tt.expectedFooterText != "" && modifiedNotification.FooterText != tt.expectedFooterText {
|
|
return false
|
|
}
|
|
return true
|
|
}, 2*time.Second, 100*time.Millisecond)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestHookMessagesWillBeConsumed(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
|
|
setupPlugin := func(t *testing.T, th *TestHelper) {
|
|
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/public/plugin"
|
|
"github.com/mattermost/mattermost/server/public/model"
|
|
)
|
|
|
|
type MyPlugin struct {
|
|
plugin.MattermostPlugin
|
|
}
|
|
|
|
func (p *MyPlugin) MessagesWillBeConsumed(posts []*model.Post) []*model.Post {
|
|
for _, post := range posts {
|
|
post.Message = "mwbc_plugin:" + post.Message
|
|
}
|
|
return posts
|
|
}
|
|
|
|
func main() {
|
|
plugin.ClientMain(&MyPlugin{})
|
|
}
|
|
`}, th.App, func(*model.Manifest) plugin.API { return &mockAPI })
|
|
t.Cleanup(tearDown)
|
|
}
|
|
|
|
t.Run("feature flag disabled", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
|
|
th := SetupConfig(t, func(cfg *model.Config) {
|
|
cfg.FeatureFlags.ConsumePostHook = false
|
|
}).InitBasic(t)
|
|
|
|
setupPlugin(t, th)
|
|
|
|
newPost := &model.Post{
|
|
UserId: th.BasicUser.Id,
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "message",
|
|
CreateAt: model.GetMillis() - 10000,
|
|
}
|
|
_, _, err := th.App.CreatePost(th.Context, newPost, th.BasicChannel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err)
|
|
|
|
post, err := th.App.GetSinglePost(th.Context, newPost.Id, true)
|
|
require.Nil(t, err)
|
|
assert.Equal(t, "message", post.Message)
|
|
})
|
|
|
|
t.Run("feature flag enabled", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
|
|
th := SetupConfig(t, func(cfg *model.Config) {
|
|
cfg.FeatureFlags.ConsumePostHook = true
|
|
}).InitBasic(t)
|
|
|
|
setupPlugin(t, th)
|
|
|
|
newPost := &model.Post{
|
|
UserId: th.BasicUser.Id,
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "message",
|
|
CreateAt: model.GetMillis() - 10000,
|
|
}
|
|
_, _, err := th.App.CreatePost(th.Context, newPost, th.BasicChannel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err)
|
|
|
|
post, err := th.App.GetSinglePost(th.Context, newPost.Id, true)
|
|
require.Nil(t, err)
|
|
assert.Equal(t, "mwbc_plugin:message", post.Message)
|
|
})
|
|
}
|
|
|
|
func TestHookPreferencesHaveChanged(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
t.Run("should be called when preferences are changed by non-plugin code", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
// Setup plugin
|
|
var mockAPI plugintest.API
|
|
|
|
tearDown, pluginIDs, _ := SetAppEnvironmentWithPlugins(t, []string{`
|
|
package main
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/mattermost/mattermost/server/public/plugin"
|
|
"github.com/mattermost/mattermost/server/public/model"
|
|
)
|
|
|
|
type MyPlugin struct {
|
|
plugin.MattermostPlugin
|
|
}
|
|
|
|
func (p *MyPlugin) PreferencesHaveChanged(c *plugin.Context, preferences []model.Preference) {
|
|
for _, preference := range preferences {
|
|
p.API.LogDebug(fmt.Sprintf("category=%s name=%s value=%s", preference.Category, preference.Name, preference.Value))
|
|
}
|
|
}
|
|
|
|
func main() {
|
|
plugin.ClientMain(&MyPlugin{})
|
|
}
|
|
`}, th.App, func(*model.Manifest) plugin.API { return &mockAPI })
|
|
defer tearDown()
|
|
|
|
// Confirm plugin is actually running
|
|
require.Len(t, pluginIDs, 1)
|
|
pluginID := pluginIDs[0]
|
|
|
|
require.True(t, th.App.GetPluginsEnvironment().IsActive(pluginID))
|
|
|
|
// Setup test
|
|
preferences := model.Preferences{
|
|
{
|
|
UserId: th.BasicUser.Id,
|
|
Category: "test_category",
|
|
Name: "test_name_1",
|
|
Value: "test_value_1",
|
|
},
|
|
{
|
|
UserId: th.BasicUser.Id,
|
|
Category: "test_category",
|
|
Name: "test_name_2",
|
|
Value: "test_value_2",
|
|
},
|
|
}
|
|
|
|
mockAPI.On("LogDebug", "category=test_category name=test_name_1 value=test_value_1")
|
|
mockAPI.On("LogDebug", "category=test_category name=test_name_2 value=test_value_2")
|
|
|
|
// Run test
|
|
err := th.App.UpdatePreferences(th.Context, th.BasicUser.Id, preferences)
|
|
|
|
require.Nil(t, err)
|
|
|
|
assert.EventuallyWithT(t, func(c *assert.CollectT) {
|
|
mockAPI.AssertExpectations(&testutils.CollectTWithLogf{CollectT: c})
|
|
}, 5*time.Second, 100*time.Millisecond)
|
|
})
|
|
|
|
t.Run("should be called when preferences are changed by plugin code", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
// Setup plugin
|
|
pluginCode := `
|
|
package main
|
|
|
|
import (
|
|
"github.com/mattermost/mattermost/server/public/plugin"
|
|
"github.com/mattermost/mattermost/server/public/model"
|
|
)
|
|
|
|
const (
|
|
userID = "` + th.BasicUser.Id + `"
|
|
)
|
|
|
|
type MyPlugin struct {
|
|
plugin.MattermostPlugin
|
|
}
|
|
|
|
func (p *MyPlugin) PreferencesHaveChanged(c *plugin.Context, preferences []model.Preference) {
|
|
// Note that plugin hooks can trigger themselves, and this test sets a preference to trigger that
|
|
// it has run, so be careful not to introduce an infinite loop here
|
|
|
|
if len(preferences) == 1 && preferences[0].Category == "test_category" && preferences[0].Name == "test_name" {
|
|
if preferences[0].Value == "test_value_first" {
|
|
appErr := p.API.UpdatePreferencesForUser(userID, []model.Preference{
|
|
{
|
|
UserId: userID,
|
|
Category: "test_category",
|
|
Name: "test_name",
|
|
Value: "test_value_second",
|
|
},
|
|
})
|
|
if appErr != nil {
|
|
panic("error setting preference to second value")
|
|
}
|
|
} else if preferences[0].Value == "test_value_second" {
|
|
appErr := p.API.UpdatePreferencesForUser(userID, []model.Preference{
|
|
{
|
|
UserId: userID,
|
|
Category: "test_category",
|
|
Name: "test_name",
|
|
Value: "test_value_third",
|
|
},
|
|
})
|
|
if appErr != nil {
|
|
panic("error setting preference to third value")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func main() {
|
|
plugin.ClientMain(&MyPlugin{})
|
|
}
|
|
`
|
|
pluginID := "testplugin"
|
|
pluginManifest := `{"id": "testplugin", "server": {"executable": "backend.exe"}}`
|
|
|
|
setupPluginAPITest(t, pluginCode, pluginManifest, pluginID, th.App, th.Context)
|
|
|
|
// Confirm plugin is actually running
|
|
require.True(t, th.App.GetPluginsEnvironment().IsActive(pluginID))
|
|
|
|
appErr := th.App.UpdatePreferences(th.Context, th.BasicUser.Id, model.Preferences{
|
|
{
|
|
UserId: th.BasicUser.Id,
|
|
Category: "test_category",
|
|
Name: "test_name",
|
|
Value: "test_value_first",
|
|
},
|
|
})
|
|
require.Nil(t, appErr)
|
|
|
|
assert.EventuallyWithT(t, func(t *assert.CollectT) {
|
|
preference, appErr := th.App.GetPreferenceByCategoryAndNameForUser(th.Context, th.BasicUser.Id, "test_category", "test_name")
|
|
|
|
require.Nil(t, appErr)
|
|
assert.Equal(t, "test_value_third", preference.Value)
|
|
}, 5*time.Second, 100*time.Millisecond)
|
|
})
|
|
}
|
|
|
|
func TestChannelHasBeenCreated(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
getPluginCode := func(th *TestHelper) string {
|
|
return `
|
|
package main
|
|
|
|
import (
|
|
"github.com/mattermost/mattermost/server/public/plugin"
|
|
"github.com/mattermost/mattermost/server/public/model"
|
|
)
|
|
|
|
const (
|
|
adminUserID = "` + th.SystemAdminUser.Id + `"
|
|
)
|
|
|
|
type MyPlugin struct {
|
|
plugin.MattermostPlugin
|
|
}
|
|
|
|
func (p *MyPlugin) ChannelHasBeenCreated(c *plugin.Context, channel *model.Channel) {
|
|
_, appErr := p.API.CreatePost(&model.Post{
|
|
UserId: adminUserID,
|
|
ChannelId: channel.Id,
|
|
Message: "ChannelHasBeenCreated has been called for " + channel.Id,
|
|
})
|
|
if appErr != nil {
|
|
panic(appErr)
|
|
}
|
|
}
|
|
|
|
func main() {
|
|
plugin.ClientMain(&MyPlugin{})
|
|
}
|
|
`
|
|
}
|
|
pluginID := "testplugin"
|
|
pluginManifest := `{"id": "testplugin", "server": {"executable": "backend.exe"}}`
|
|
|
|
t.Run("should call hook when a regular channel is created", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
// Setup plugin
|
|
setupPluginAPITest(t, getPluginCode(th), pluginManifest, pluginID, th.App, th.Context)
|
|
|
|
user1 := th.CreateUser(t)
|
|
|
|
channel, appErr := th.App.CreateChannel(th.Context, &model.Channel{
|
|
CreatorId: user1.Id,
|
|
TeamId: th.BasicTeam.Id,
|
|
Name: "test_channel",
|
|
Type: model.ChannelTypeOpen,
|
|
}, false)
|
|
require.Nil(t, appErr)
|
|
require.NotNil(t, channel)
|
|
|
|
assert.EventuallyWithT(t, func(t *assert.CollectT) {
|
|
posts, appErr := th.App.GetPosts(th.Context, channel.Id, 0, 1)
|
|
|
|
require.Nil(t, appErr)
|
|
|
|
if assert.NotEmpty(t, posts.Order) {
|
|
post := posts.Posts[posts.Order[0]]
|
|
assert.Equal(t, channel.Id, post.ChannelId)
|
|
assert.Equal(t, "ChannelHasBeenCreated has been called for "+channel.Id, post.Message)
|
|
}
|
|
}, 5*time.Second, 100*time.Millisecond)
|
|
})
|
|
|
|
t.Run("should call hook when a DM is created", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
// Setup plugin
|
|
setupPluginAPITest(t, getPluginCode(th), pluginManifest, pluginID, th.App, th.Context)
|
|
|
|
user1 := th.CreateUser(t)
|
|
user2 := th.CreateUser(t)
|
|
|
|
channel, appErr := th.App.GetOrCreateDirectChannel(th.Context, user1.Id, user2.Id)
|
|
require.Nil(t, appErr)
|
|
require.NotNil(t, channel)
|
|
|
|
assert.EventuallyWithT(t, func(t *assert.CollectT) {
|
|
posts, appErr := th.App.GetPosts(th.Context, channel.Id, 0, 1)
|
|
|
|
require.Nil(t, appErr)
|
|
if assert.NotEmpty(t, posts.Order) {
|
|
post := posts.Posts[posts.Order[0]]
|
|
assert.Equal(t, channel.Id, post.ChannelId)
|
|
assert.Equal(t, "ChannelHasBeenCreated has been called for "+channel.Id, post.Message)
|
|
}
|
|
}, 5*time.Second, 100*time.Millisecond)
|
|
})
|
|
|
|
t.Run("should call hook when a GM is created", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
// Setup plugin
|
|
setupPluginAPITest(t, getPluginCode(th), pluginManifest, pluginID, th.App, th.Context)
|
|
|
|
user1 := th.CreateUser(t)
|
|
user2 := th.CreateUser(t)
|
|
user3 := th.CreateUser(t)
|
|
|
|
channel, appErr := th.App.CreateGroupChannel(th.Context, []string{user1.Id, user2.Id, user3.Id}, user1.Id)
|
|
require.Nil(t, appErr)
|
|
require.NotNil(t, channel)
|
|
|
|
assert.EventuallyWithT(t, func(t *assert.CollectT) {
|
|
posts, appErr := th.App.GetPosts(th.Context, channel.Id, 0, 1)
|
|
|
|
require.Nil(t, appErr)
|
|
if assert.NotEmpty(t, posts.Order) {
|
|
post := posts.Posts[posts.Order[0]]
|
|
assert.Equal(t, channel.Id, post.ChannelId)
|
|
assert.Equal(t, "ChannelHasBeenCreated has been called for "+channel.Id, post.Message)
|
|
}
|
|
}, 5*time.Second, 100*time.Millisecond)
|
|
})
|
|
}
|
|
|
|
func TestUserHasJoinedChannel(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
getPluginCode := func(th *TestHelper) string {
|
|
return `
|
|
package main
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/mattermost/mattermost/server/public/plugin"
|
|
"github.com/mattermost/mattermost/server/public/model"
|
|
)
|
|
|
|
const (
|
|
adminUserID = "` + th.SystemAdminUser.Id + `"
|
|
)
|
|
|
|
type MyPlugin struct {
|
|
plugin.MattermostPlugin
|
|
}
|
|
|
|
func (p *MyPlugin) UserHasJoinedChannel(c *plugin.Context, channelMember *model.ChannelMember, actor *model.User) {
|
|
message := fmt.Sprintf("Test: User %s joined %s", channelMember.UserId, channelMember.ChannelId)
|
|
if actor != nil && actor.Id != channelMember.UserId {
|
|
message = fmt.Sprintf("Test: User %s added to %s by %s", channelMember.UserId, channelMember.ChannelId, actor.Id)
|
|
}
|
|
|
|
_, appErr := p.API.CreatePost(&model.Post{
|
|
UserId: adminUserID,
|
|
ChannelId: channelMember.ChannelId,
|
|
Message: message,
|
|
})
|
|
if appErr != nil {
|
|
panic(appErr)
|
|
}
|
|
}
|
|
|
|
func main() {
|
|
plugin.ClientMain(&MyPlugin{})
|
|
}
|
|
`
|
|
}
|
|
pluginID := "testplugin"
|
|
pluginManifest := `{"id": "testplugin", "server": {"executable": "backend.exe"}}`
|
|
|
|
t.Run("should call hook when a user joins an existing channel", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
user1 := th.CreateUser(t)
|
|
th.LinkUserToTeam(t, user1, th.BasicTeam)
|
|
user2 := th.CreateUser(t)
|
|
th.LinkUserToTeam(t, user2, th.BasicTeam)
|
|
|
|
channel, appErr := th.App.CreateChannel(th.Context, &model.Channel{
|
|
CreatorId: user1.Id,
|
|
TeamId: th.BasicTeam.Id,
|
|
Name: "test_channel",
|
|
Type: model.ChannelTypeOpen,
|
|
}, false)
|
|
require.Nil(t, appErr)
|
|
require.NotNil(t, channel)
|
|
|
|
// Setup plugin after creating the channel
|
|
setupPluginAPITest(t, getPluginCode(th), pluginManifest, pluginID, th.App, th.Context)
|
|
|
|
_, appErr = th.App.AddChannelMember(th.Context, user2.Id, channel, ChannelMemberOpts{
|
|
UserRequestorID: user2.Id,
|
|
})
|
|
require.Nil(t, appErr)
|
|
|
|
assert.EventuallyWithT(t, func(t *assert.CollectT) {
|
|
posts, appErr := th.App.GetPosts(th.Context, channel.Id, 0, 30)
|
|
|
|
require.Nil(t, appErr)
|
|
assert.True(t, len(posts.Order) > 0)
|
|
|
|
found := false
|
|
for _, post := range posts.Posts {
|
|
if post.Message == fmt.Sprintf("Test: User %s joined %s", user2.Id, channel.Id) {
|
|
found = true
|
|
}
|
|
}
|
|
|
|
if !found {
|
|
assert.Fail(t, "Couldn't find user joined channel hook message post")
|
|
}
|
|
}, 5*time.Second, 100*time.Millisecond)
|
|
})
|
|
|
|
t.Run("should call hook when a user is added to an existing channel", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
user1 := th.CreateUser(t)
|
|
th.LinkUserToTeam(t, user1, th.BasicTeam)
|
|
user2 := th.CreateUser(t)
|
|
th.LinkUserToTeam(t, user2, th.BasicTeam)
|
|
|
|
channel, appErr := th.App.CreateChannel(th.Context, &model.Channel{
|
|
CreatorId: user1.Id,
|
|
TeamId: th.BasicTeam.Id,
|
|
Name: "test_channel",
|
|
Type: model.ChannelTypeOpen,
|
|
}, false)
|
|
require.Nil(t, appErr)
|
|
require.NotNil(t, channel)
|
|
|
|
// Setup plugin after creating the channel
|
|
setupPluginAPITest(t, getPluginCode(th), pluginManifest, pluginID, th.App, th.Context)
|
|
|
|
_, appErr = th.App.AddChannelMember(th.Context, user2.Id, channel, ChannelMemberOpts{
|
|
UserRequestorID: user1.Id,
|
|
})
|
|
require.Nil(t, appErr)
|
|
|
|
expectedMessage := fmt.Sprintf("Test: User %s added to %s by %s", user2.Id, channel.Id, user1.Id)
|
|
assert.Eventually(t, func() bool {
|
|
// Typically, the post we're looking for will be the latest, but there's a race between the plugin and
|
|
// "User has joined the channel" post which means the plugin post may not the the latest one
|
|
posts, appErr := th.App.GetPosts(th.Context, channel.Id, 0, 10)
|
|
require.Nil(t, appErr)
|
|
|
|
for _, postId := range posts.Order {
|
|
post := posts.Posts[postId]
|
|
|
|
if post.Message == expectedMessage {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}, 5*time.Second, 100*time.Millisecond)
|
|
})
|
|
|
|
t.Run("should not call hook when a regular channel is created", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
// Setup plugin
|
|
setupPluginAPITest(t, getPluginCode(th), pluginManifest, pluginID, th.App, th.Context)
|
|
|
|
user1 := th.CreateUser(t)
|
|
|
|
channel, appErr := th.App.CreateChannel(th.Context, &model.Channel{
|
|
CreatorId: user1.Id,
|
|
TeamId: th.BasicTeam.Id,
|
|
Name: "test_channel",
|
|
Type: model.ChannelTypeOpen,
|
|
}, false)
|
|
require.Nil(t, appErr)
|
|
require.NotNil(t, channel)
|
|
|
|
var posts *model.PostList
|
|
require.EventuallyWithT(t, func(c *assert.CollectT) {
|
|
posts, appErr = th.App.GetPosts(th.Context, channel.Id, 0, 10)
|
|
assert.Nil(t, appErr)
|
|
}, 2*time.Second, 100*time.Millisecond)
|
|
|
|
for _, postID := range posts.Order {
|
|
post := posts.Posts[postID]
|
|
|
|
if strings.HasPrefix(post.Message, "Test: ") {
|
|
t.Log("Plugin message found:", post.Message)
|
|
t.FailNow()
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("should not call hook when a DM is created", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
// Setup plugin
|
|
setupPluginAPITest(t, getPluginCode(th), pluginManifest, pluginID, th.App, th.Context)
|
|
|
|
user1 := th.CreateUser(t)
|
|
user2 := th.CreateUser(t)
|
|
|
|
channel, appErr := th.App.GetOrCreateDirectChannel(th.Context, user1.Id, user2.Id)
|
|
require.Nil(t, appErr)
|
|
require.NotNil(t, channel)
|
|
|
|
var posts *model.PostList
|
|
require.EventuallyWithT(t, func(c *assert.CollectT) {
|
|
posts, appErr = th.App.GetPosts(th.Context, channel.Id, 0, 10)
|
|
assert.Nil(t, appErr)
|
|
}, 2*time.Second, 100*time.Millisecond)
|
|
|
|
for _, postID := range posts.Order {
|
|
post := posts.Posts[postID]
|
|
|
|
if strings.HasPrefix(post.Message, "Test: ") {
|
|
t.Log("Plugin message found:", post.Message)
|
|
t.FailNow()
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("should not call hook when a GM is created", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
// Setup plugin
|
|
setupPluginAPITest(t, getPluginCode(th), pluginManifest, pluginID, th.App, th.Context)
|
|
|
|
user1 := th.CreateUser(t)
|
|
user2 := th.CreateUser(t)
|
|
user3 := th.CreateUser(t)
|
|
|
|
channel, appErr := th.App.CreateGroupChannel(th.Context, []string{user1.Id, user2.Id, user3.Id}, user1.Id)
|
|
require.Nil(t, appErr)
|
|
require.NotNil(t, channel)
|
|
|
|
var posts *model.PostList
|
|
require.EventuallyWithT(t, func(c *assert.CollectT) {
|
|
posts, appErr = th.App.GetPosts(th.Context, channel.Id, 0, 10)
|
|
assert.Nil(t, appErr)
|
|
}, 2*time.Second, 100*time.Millisecond)
|
|
|
|
for _, postID := range posts.Order {
|
|
post := posts.Posts[postID]
|
|
|
|
if strings.HasPrefix(post.Message, "Test: ") {
|
|
t.Log("Plugin message found:", post.Message)
|
|
t.FailNow()
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestHookChannelMemberWillBeAdded(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
t.Run("rejected", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
tearDown, _, _ := SetAppEnvironmentWithPlugins(t, []string{
|
|
`
|
|
package main
|
|
|
|
import (
|
|
"github.com/mattermost/mattermost/server/public/plugin"
|
|
"github.com/mattermost/mattermost/server/public/model"
|
|
)
|
|
|
|
type MyPlugin struct {
|
|
plugin.MattermostPlugin
|
|
}
|
|
|
|
func (p *MyPlugin) ChannelMemberWillBeAdded(c *plugin.Context, channelMember *model.ChannelMember) (*model.ChannelMember, string) {
|
|
return nil, "not allowed"
|
|
}
|
|
|
|
func main() {
|
|
plugin.ClientMain(&MyPlugin{})
|
|
}
|
|
`,
|
|
}, th.App, th.NewPluginAPI)
|
|
defer tearDown()
|
|
|
|
user := th.CreateUser(t)
|
|
_, _, appErr := th.App.AddUserToTeam(th.Context, th.BasicTeam.Id, user.Id, "")
|
|
require.Nil(t, appErr)
|
|
|
|
_, appErr = th.App.AddChannelMember(th.Context, user.Id, th.BasicChannel, ChannelMemberOpts{})
|
|
require.NotNil(t, appErr)
|
|
assert.Contains(t, appErr.Id, "rejected_by_plugin")
|
|
})
|
|
|
|
t.Run("modified", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
tearDown, _, _ := SetAppEnvironmentWithPlugins(t, []string{
|
|
`
|
|
package main
|
|
|
|
import (
|
|
"github.com/mattermost/mattermost/server/public/plugin"
|
|
"github.com/mattermost/mattermost/server/public/model"
|
|
)
|
|
|
|
type MyPlugin struct {
|
|
plugin.MattermostPlugin
|
|
}
|
|
|
|
func (p *MyPlugin) ChannelMemberWillBeAdded(c *plugin.Context, channelMember *model.ChannelMember) (*model.ChannelMember, string) {
|
|
channelMember.NotifyProps = model.GetDefaultChannelNotifyProps()
|
|
channelMember.NotifyProps[model.DesktopNotifyProp] = model.ChannelNotifyAll
|
|
return channelMember, ""
|
|
}
|
|
|
|
func main() {
|
|
plugin.ClientMain(&MyPlugin{})
|
|
}
|
|
`,
|
|
}, th.App, th.NewPluginAPI)
|
|
defer tearDown()
|
|
|
|
user := th.CreateUser(t)
|
|
_, _, appErr := th.App.AddUserToTeam(th.Context, th.BasicTeam.Id, user.Id, "")
|
|
require.Nil(t, appErr)
|
|
|
|
member, appErr := th.App.AddChannelMember(th.Context, user.Id, th.BasicChannel, ChannelMemberOpts{})
|
|
require.Nil(t, appErr)
|
|
assert.Equal(t, model.ChannelNotifyAll, member.NotifyProps[model.DesktopNotifyProp])
|
|
})
|
|
|
|
t.Run("allowed", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
tearDown, _, _ := SetAppEnvironmentWithPlugins(t, []string{
|
|
`
|
|
package main
|
|
|
|
import (
|
|
"github.com/mattermost/mattermost/server/public/plugin"
|
|
"github.com/mattermost/mattermost/server/public/model"
|
|
)
|
|
|
|
type MyPlugin struct {
|
|
plugin.MattermostPlugin
|
|
}
|
|
|
|
func (p *MyPlugin) ChannelMemberWillBeAdded(c *plugin.Context, channelMember *model.ChannelMember) (*model.ChannelMember, string) {
|
|
return nil, ""
|
|
}
|
|
|
|
func main() {
|
|
plugin.ClientMain(&MyPlugin{})
|
|
}
|
|
`,
|
|
}, th.App, th.NewPluginAPI)
|
|
defer tearDown()
|
|
|
|
user := th.CreateUser(t)
|
|
_, _, appErr := th.App.AddUserToTeam(th.Context, th.BasicTeam.Id, user.Id, "")
|
|
require.Nil(t, appErr)
|
|
|
|
member, appErr := th.App.AddChannelMember(th.Context, user.Id, th.BasicChannel, ChannelMemberOpts{})
|
|
require.Nil(t, appErr)
|
|
assert.Equal(t, th.BasicChannel.Id, member.ChannelId)
|
|
assert.Equal(t, user.Id, member.UserId)
|
|
})
|
|
}
|
|
|
|
func TestHookTeamMemberWillBeAdded(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
t.Run("rejected", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
tearDown, _, _ := SetAppEnvironmentWithPlugins(t, []string{
|
|
`
|
|
package main
|
|
|
|
import (
|
|
"github.com/mattermost/mattermost/server/public/plugin"
|
|
"github.com/mattermost/mattermost/server/public/model"
|
|
)
|
|
|
|
type MyPlugin struct {
|
|
plugin.MattermostPlugin
|
|
}
|
|
|
|
func (p *MyPlugin) TeamMemberWillBeAdded(c *plugin.Context, teamMember *model.TeamMember) (*model.TeamMember, string) {
|
|
return nil, "not allowed"
|
|
}
|
|
|
|
func main() {
|
|
plugin.ClientMain(&MyPlugin{})
|
|
}
|
|
`,
|
|
}, th.App, th.NewPluginAPI)
|
|
defer tearDown()
|
|
|
|
user := th.CreateUser(t)
|
|
team := th.CreateTeam(t)
|
|
|
|
_, appErr := th.App.JoinUserToTeam(th.Context, team, user, "")
|
|
require.NotNil(t, appErr)
|
|
assert.Contains(t, appErr.Id, "rejected_by_plugin")
|
|
})
|
|
|
|
t.Run("modified", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
tearDown, _, _ := SetAppEnvironmentWithPlugins(t, []string{
|
|
`
|
|
package main
|
|
|
|
import (
|
|
"github.com/mattermost/mattermost/server/public/plugin"
|
|
"github.com/mattermost/mattermost/server/public/model"
|
|
)
|
|
|
|
type MyPlugin struct {
|
|
plugin.MattermostPlugin
|
|
}
|
|
|
|
func (p *MyPlugin) TeamMemberWillBeAdded(c *plugin.Context, teamMember *model.TeamMember) (*model.TeamMember, string) {
|
|
teamMember.SchemeAdmin = true
|
|
return teamMember, ""
|
|
}
|
|
|
|
func main() {
|
|
plugin.ClientMain(&MyPlugin{})
|
|
}
|
|
`,
|
|
}, th.App, th.NewPluginAPI)
|
|
defer tearDown()
|
|
|
|
user := th.CreateUser(t)
|
|
team := th.CreateTeam(t)
|
|
|
|
member, appErr := th.App.JoinUserToTeam(th.Context, team, user, "")
|
|
require.Nil(t, appErr)
|
|
assert.True(t, member.SchemeAdmin)
|
|
})
|
|
|
|
t.Run("allowed", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
tearDown, _, _ := SetAppEnvironmentWithPlugins(t, []string{
|
|
`
|
|
package main
|
|
|
|
import (
|
|
"github.com/mattermost/mattermost/server/public/plugin"
|
|
"github.com/mattermost/mattermost/server/public/model"
|
|
)
|
|
|
|
type MyPlugin struct {
|
|
plugin.MattermostPlugin
|
|
}
|
|
|
|
func (p *MyPlugin) TeamMemberWillBeAdded(c *plugin.Context, teamMember *model.TeamMember) (*model.TeamMember, string) {
|
|
return nil, ""
|
|
}
|
|
|
|
func main() {
|
|
plugin.ClientMain(&MyPlugin{})
|
|
}
|
|
`,
|
|
}, th.App, th.NewPluginAPI)
|
|
defer tearDown()
|
|
|
|
user := th.CreateUser(t)
|
|
team := th.CreateTeam(t)
|
|
|
|
member, appErr := th.App.JoinUserToTeam(th.Context, team, user, "")
|
|
require.Nil(t, appErr)
|
|
assert.Equal(t, team.Id, member.TeamId)
|
|
assert.Equal(t, user.Id, member.UserId)
|
|
})
|
|
|
|
t.Run("already active member skips hook", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
tearDown, _, _ := SetAppEnvironmentWithPlugins(t, []string{
|
|
`
|
|
package main
|
|
|
|
import (
|
|
"github.com/mattermost/mattermost/server/public/plugin"
|
|
"github.com/mattermost/mattermost/server/public/model"
|
|
)
|
|
|
|
type MyPlugin struct {
|
|
plugin.MattermostPlugin
|
|
}
|
|
|
|
func (p *MyPlugin) TeamMemberWillBeAdded(c *plugin.Context, teamMember *model.TeamMember) (*model.TeamMember, string) {
|
|
return nil, "should not fire for existing member"
|
|
}
|
|
|
|
func main() {
|
|
plugin.ClientMain(&MyPlugin{})
|
|
}
|
|
`,
|
|
}, th.App, th.NewPluginAPI)
|
|
defer tearDown()
|
|
|
|
// BasicUser is already a member of BasicTeam via InitBasic
|
|
member, appErr := th.App.JoinUserToTeam(th.Context, th.BasicTeam, th.BasicUser, "")
|
|
require.Nil(t, appErr)
|
|
assert.Equal(t, th.BasicTeam.Id, member.TeamId)
|
|
})
|
|
|
|
t.Run("re-join after leaving applies hook", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
tearDown, _, _ := SetAppEnvironmentWithPlugins(t, []string{
|
|
`
|
|
package main
|
|
|
|
import (
|
|
"github.com/mattermost/mattermost/server/public/plugin"
|
|
"github.com/mattermost/mattermost/server/public/model"
|
|
)
|
|
|
|
type MyPlugin struct {
|
|
plugin.MattermostPlugin
|
|
}
|
|
|
|
func (p *MyPlugin) TeamMemberWillBeAdded(c *plugin.Context, teamMember *model.TeamMember) (*model.TeamMember, string) {
|
|
teamMember.SchemeAdmin = true
|
|
return teamMember, ""
|
|
}
|
|
|
|
func main() {
|
|
plugin.ClientMain(&MyPlugin{})
|
|
}
|
|
`,
|
|
}, th.App, th.NewPluginAPI)
|
|
defer tearDown()
|
|
|
|
user := th.CreateUser(t)
|
|
team := th.CreateTeam(t)
|
|
|
|
// First join
|
|
_, appErr := th.App.JoinUserToTeam(th.Context, team, user, "")
|
|
require.Nil(t, appErr)
|
|
|
|
// Leave
|
|
err := th.App.LeaveTeam(th.Context, team, user, "")
|
|
require.Nil(t, err)
|
|
|
|
// Re-join — hook should fire on the re-add path
|
|
member, appErr := th.App.JoinUserToTeam(th.Context, team, user, "")
|
|
require.Nil(t, appErr)
|
|
assert.True(t, member.SchemeAdmin)
|
|
})
|
|
|
|
t.Run("CreateTeamWithUser rejected by hook", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
tearDown, _, _ := SetAppEnvironmentWithPlugins(t, []string{
|
|
`
|
|
package main
|
|
|
|
import (
|
|
"github.com/mattermost/mattermost/server/public/plugin"
|
|
"github.com/mattermost/mattermost/server/public/model"
|
|
)
|
|
|
|
type MyPlugin struct {
|
|
plugin.MattermostPlugin
|
|
}
|
|
|
|
func (p *MyPlugin) TeamMemberWillBeAdded(c *plugin.Context, teamMember *model.TeamMember) (*model.TeamMember, string) {
|
|
return nil, "team join blocked"
|
|
}
|
|
|
|
func main() {
|
|
plugin.ClientMain(&MyPlugin{})
|
|
}
|
|
`,
|
|
}, th.App, th.NewPluginAPI)
|
|
defer tearDown()
|
|
|
|
team := &model.Team{
|
|
DisplayName: "Test Team",
|
|
Name: "test-team-" + model.NewId()[:8],
|
|
Type: model.TeamOpen,
|
|
}
|
|
_, appErr := th.App.CreateTeamWithUser(th.Context, team, th.BasicUser.Id)
|
|
require.NotNil(t, appErr)
|
|
assert.Contains(t, appErr.Id, "rejected_by_plugin")
|
|
})
|
|
}
|
|
|
|
func TestHookChannelWillBeArchived(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
t.Run("rejected", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
tearDown, _, _ := SetAppEnvironmentWithPlugins(t, []string{
|
|
`
|
|
package main
|
|
|
|
import (
|
|
"github.com/mattermost/mattermost/server/public/plugin"
|
|
"github.com/mattermost/mattermost/server/public/model"
|
|
)
|
|
|
|
type MyPlugin struct {
|
|
plugin.MattermostPlugin
|
|
}
|
|
|
|
func (p *MyPlugin) ChannelWillBeArchived(c *plugin.Context, channel *model.Channel) string {
|
|
return "archive not permitted"
|
|
}
|
|
|
|
func main() {
|
|
plugin.ClientMain(&MyPlugin{})
|
|
}
|
|
`,
|
|
}, th.App, th.NewPluginAPI)
|
|
defer tearDown()
|
|
|
|
appErr := th.App.DeleteChannel(th.Context, th.BasicChannel, th.BasicUser.Id)
|
|
require.NotNil(t, appErr)
|
|
assert.Contains(t, appErr.Id, "rejected_by_plugin")
|
|
|
|
// Verify channel was NOT archived
|
|
ch, err := th.App.GetChannel(th.Context, th.BasicChannel.Id)
|
|
require.Nil(t, err)
|
|
assert.Equal(t, int64(0), ch.DeleteAt)
|
|
})
|
|
|
|
t.Run("allowed", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
tearDown, _, _ := SetAppEnvironmentWithPlugins(t, []string{
|
|
`
|
|
package main
|
|
|
|
import (
|
|
"github.com/mattermost/mattermost/server/public/plugin"
|
|
"github.com/mattermost/mattermost/server/public/model"
|
|
)
|
|
|
|
type MyPlugin struct {
|
|
plugin.MattermostPlugin
|
|
}
|
|
|
|
func (p *MyPlugin) ChannelWillBeArchived(c *plugin.Context, channel *model.Channel) string {
|
|
return ""
|
|
}
|
|
|
|
func main() {
|
|
plugin.ClientMain(&MyPlugin{})
|
|
}
|
|
`,
|
|
}, th.App, th.NewPluginAPI)
|
|
defer tearDown()
|
|
|
|
appErr := th.App.DeleteChannel(th.Context, th.BasicChannel, th.BasicUser.Id)
|
|
require.Nil(t, appErr)
|
|
|
|
// Verify channel was archived
|
|
ch, err := th.App.GetChannel(th.Context, th.BasicChannel.Id)
|
|
require.Nil(t, err)
|
|
assert.NotEqual(t, int64(0), ch.DeleteAt)
|
|
})
|
|
}
|