Full Mattermost server source with integrated Community Enterprise features. Includes vendor directory for offline/air-gapped builds. Structure: - enterprise-impl/: Enterprise feature implementations - enterprise-community/: Init files that register implementations - enterprise/: Bridge imports (community_imports.go) - vendor/: All dependencies for offline builds Build (online): go build ./cmd/mattermost Build (offline/air-gapped): go build -mod=vendor ./cmd/mattermost 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
4457 lines
145 KiB
Go
4457 lines
145 KiB
Go
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
package app
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"strconv"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/mattermost/mattermost/server/public/model"
|
|
"github.com/mattermost/mattermost/server/public/plugin/plugintest/mock"
|
|
"github.com/mattermost/mattermost/server/public/shared/mlog"
|
|
"github.com/mattermost/mattermost/server/v8/channels/store"
|
|
storemocks "github.com/mattermost/mattermost/server/v8/channels/store/storetest/mocks"
|
|
"github.com/mattermost/mattermost/server/v8/channels/testlib"
|
|
"github.com/mattermost/mattermost/server/v8/platform/services/imageproxy"
|
|
"github.com/mattermost/mattermost/server/v8/platform/services/searchengine/mocks"
|
|
)
|
|
|
|
func makePendingPostId(user *model.User) string {
|
|
return fmt.Sprintf("%s:%s", user.Id, strconv.FormatInt(model.GetMillis(), 10))
|
|
}
|
|
|
|
func TestCreatePostDeduplicate(t *testing.T) {
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
t.Run("duplicate create post is idempotent", func(t *testing.T) {
|
|
session := &model.Session{
|
|
UserId: th.BasicUser.Id,
|
|
}
|
|
session, err := th.App.CreateSession(th.Context, session)
|
|
require.Nil(t, err)
|
|
|
|
pendingPostId := makePendingPostId(th.BasicUser)
|
|
|
|
post, err := th.App.CreatePostAsUser(th.Context.WithSession(session), &model.Post{
|
|
UserId: th.BasicUser.Id,
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "message",
|
|
PendingPostId: pendingPostId,
|
|
}, session.Id, true)
|
|
require.Nil(t, err)
|
|
require.Equal(t, "message", post.Message)
|
|
|
|
duplicatePost, err := th.App.CreatePostAsUser(th.Context.WithSession(session), &model.Post{
|
|
UserId: th.BasicUser.Id,
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "message",
|
|
PendingPostId: pendingPostId,
|
|
}, session.Id, true)
|
|
require.Nil(t, err)
|
|
require.Equal(t, post.Id, duplicatePost.Id, "should have returned previously created post id")
|
|
require.Equal(t, "message", duplicatePost.Message)
|
|
})
|
|
|
|
t.Run("post rejected by plugin leaves cache ready for non-deduplicated try", func(t *testing.T) {
|
|
setupPluginAPITest(t, `
|
|
package main
|
|
|
|
import (
|
|
"github.com/mattermost/mattermost/server/public/plugin"
|
|
"github.com/mattermost/mattermost/server/public/model"
|
|
)
|
|
|
|
type MyPlugin struct {
|
|
plugin.MattermostPlugin
|
|
allow bool
|
|
}
|
|
|
|
func (p *MyPlugin) MessageWillBePosted(c *plugin.Context, post *model.Post) (*model.Post, string) {
|
|
if !p.allow {
|
|
p.allow = true
|
|
return nil, "rejected"
|
|
}
|
|
|
|
return nil, ""
|
|
}
|
|
|
|
func main() {
|
|
plugin.ClientMain(&MyPlugin{})
|
|
}
|
|
`, `{"id": "testrejectfirstpost", "server": {"executable": "backend.exe"}}`, "testrejectfirstpost", th.App, th.Context)
|
|
|
|
session := &model.Session{
|
|
UserId: th.BasicUser.Id,
|
|
}
|
|
session, err := th.App.CreateSession(th.Context, session)
|
|
require.Nil(t, err)
|
|
|
|
pendingPostId := makePendingPostId(th.BasicUser)
|
|
|
|
post, err := th.App.CreatePostAsUser(th.Context.WithSession(session), &model.Post{
|
|
UserId: th.BasicUser.Id,
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "message",
|
|
PendingPostId: pendingPostId,
|
|
}, session.Id, true)
|
|
require.NotNil(t, err)
|
|
require.Equal(t, "Post rejected by plugin. rejected", err.Id)
|
|
require.Nil(t, post)
|
|
|
|
duplicatePost, err := th.App.CreatePostAsUser(th.Context.WithSession(session), &model.Post{
|
|
UserId: th.BasicUser.Id,
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "message",
|
|
PendingPostId: pendingPostId,
|
|
}, session.Id, true)
|
|
require.Nil(t, err)
|
|
require.Equal(t, "message", duplicatePost.Message)
|
|
})
|
|
|
|
t.Run("slow posting after cache entry blocks duplicate request", func(t *testing.T) {
|
|
setupPluginAPITest(t, `
|
|
package main
|
|
|
|
import (
|
|
"github.com/mattermost/mattermost/server/public/plugin"
|
|
"github.com/mattermost/mattermost/server/public/model"
|
|
"time"
|
|
)
|
|
|
|
type MyPlugin struct {
|
|
plugin.MattermostPlugin
|
|
instant bool
|
|
}
|
|
|
|
func (p *MyPlugin) MessageWillBePosted(c *plugin.Context, post *model.Post) (*model.Post, string) {
|
|
if !p.instant {
|
|
p.instant = true
|
|
time.Sleep(3 * time.Second)
|
|
}
|
|
|
|
return nil, ""
|
|
}
|
|
|
|
func main() {
|
|
plugin.ClientMain(&MyPlugin{})
|
|
}
|
|
`, `{"id": "testdelayfirstpost", "server": {"executable": "backend.exe"}}`, "testdelayfirstpost", th.App, th.Context)
|
|
|
|
session := &model.Session{
|
|
UserId: th.BasicUser.Id,
|
|
}
|
|
session, err := th.App.CreateSession(th.Context, session)
|
|
require.Nil(t, err)
|
|
|
|
var post *model.Post
|
|
pendingPostId := makePendingPostId(th.BasicUser)
|
|
|
|
wg := sync.WaitGroup{}
|
|
|
|
// Launch a goroutine to make the first CreatePost call that will get delayed
|
|
// by the plugin above.
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
var appErr *model.AppError
|
|
post, appErr = th.App.CreatePostAsUser(th.Context.WithSession(session), &model.Post{
|
|
UserId: th.BasicUser.Id,
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "plugin delayed",
|
|
PendingPostId: pendingPostId,
|
|
}, session.Id, true)
|
|
require.Nil(t, appErr)
|
|
require.Equal(t, post.Message, "plugin delayed")
|
|
}()
|
|
|
|
// Give the goroutine above a chance to start and get delayed by the plugin.
|
|
time.Sleep(2 * time.Second)
|
|
|
|
// Try creating a duplicate post
|
|
duplicatePost, err := th.App.CreatePostAsUser(th.Context.WithSession(session), &model.Post{
|
|
UserId: th.BasicUser.Id,
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "plugin delayed",
|
|
PendingPostId: pendingPostId,
|
|
}, session.Id, true)
|
|
require.NotNil(t, err)
|
|
require.Equal(t, "api.post.deduplicate_create_post.pending", err.Id)
|
|
require.Nil(t, duplicatePost)
|
|
|
|
// Wait for the first CreatePost to finish to ensure assertions are made.
|
|
wg.Wait()
|
|
})
|
|
|
|
t.Run("duplicate create post after cache expires is not idempotent", func(t *testing.T) {
|
|
originalCacheTTL := pendingPostIDsCacheTTL
|
|
pendingPostIDsCacheTTL = time.Second
|
|
t.Cleanup(func() {
|
|
pendingPostIDsCacheTTL = originalCacheTTL
|
|
})
|
|
|
|
session := &model.Session{
|
|
UserId: th.BasicUser.Id,
|
|
}
|
|
session, err := th.App.CreateSession(th.Context, session)
|
|
require.Nil(t, err)
|
|
|
|
pendingPostId := makePendingPostId(th.BasicUser)
|
|
|
|
post, err := th.App.CreatePostAsUser(th.Context.WithSession(session), &model.Post{
|
|
UserId: th.BasicUser.Id,
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "message",
|
|
PendingPostId: pendingPostId,
|
|
}, session.Id, true)
|
|
require.Nil(t, err)
|
|
require.Equal(t, "message", post.Message)
|
|
|
|
time.Sleep(pendingPostIDsCacheTTL)
|
|
|
|
duplicatePost, err := th.App.CreatePostAsUser(th.Context.WithSession(session), &model.Post{
|
|
UserId: th.BasicUser.Id,
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "message",
|
|
PendingPostId: pendingPostId,
|
|
}, session.Id, true)
|
|
require.Nil(t, err)
|
|
require.NotEqual(t, post.Id, duplicatePost.Id, "should have created new post id")
|
|
require.Equal(t, "message", duplicatePost.Message)
|
|
})
|
|
|
|
t.Run("Permissison to post required to resolve from pending post cache", func(t *testing.T) {
|
|
sessionBasicUser := &model.Session{
|
|
UserId: th.BasicUser.Id,
|
|
}
|
|
sessionBasicUser, err := th.App.CreateSession(th.Context, sessionBasicUser)
|
|
require.Nil(t, err)
|
|
|
|
sessionBasicUser2 := &model.Session{
|
|
UserId: th.BasicUser2.Id,
|
|
}
|
|
sessionBasicUser2, err = th.App.CreateSession(th.Context, sessionBasicUser2)
|
|
require.Nil(t, err)
|
|
|
|
pendingPostId := makePendingPostId(th.BasicUser)
|
|
|
|
privateChannel := th.CreatePrivateChannel(th.Context, th.BasicTeam)
|
|
th.AddUserToChannel(th.BasicUser, privateChannel)
|
|
|
|
post, err := th.App.CreatePostAsUser(th.Context.WithSession(sessionBasicUser), &model.Post{
|
|
UserId: th.BasicUser.Id,
|
|
ChannelId: privateChannel.Id,
|
|
Message: "message",
|
|
PendingPostId: pendingPostId,
|
|
}, sessionBasicUser.Id, true)
|
|
require.Nil(t, err)
|
|
require.Equal(t, "message", post.Message)
|
|
|
|
postAsDifferentUser, err := th.App.CreatePostAsUser(th.Context.WithSession(sessionBasicUser2), &model.Post{
|
|
UserId: th.BasicUser2.Id,
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "message2",
|
|
PendingPostId: pendingPostId,
|
|
}, sessionBasicUser2.Id, true)
|
|
require.Nil(t, err)
|
|
require.NotEqual(t, post.Id, postAsDifferentUser.Id, "should have created new post id")
|
|
require.Equal(t, "message2", postAsDifferentUser.Message)
|
|
|
|
// Both posts should exist unchanged
|
|
actualPost, err := th.App.GetSinglePost(th.Context, post.Id, false)
|
|
require.Nil(t, err)
|
|
assert.Equal(t, "message", actualPost.Message)
|
|
assert.Equal(t, privateChannel.Id, actualPost.ChannelId)
|
|
|
|
actualPostAsDifferentUser, err := th.App.GetSinglePost(th.Context, postAsDifferentUser.Id, false)
|
|
require.Nil(t, err)
|
|
assert.Equal(t, "message2", actualPostAsDifferentUser.Message)
|
|
assert.Equal(t, th.BasicChannel.Id, actualPostAsDifferentUser.ChannelId)
|
|
})
|
|
}
|
|
|
|
func TestAttachFilesToPost(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
t.Run("should attach files", func(t *testing.T) {
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
info1, err := th.App.Srv().Store().FileInfo().Save(th.Context,
|
|
&model.FileInfo{
|
|
CreatorId: th.BasicUser.Id,
|
|
Path: "path.txt",
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
info2, err := th.App.Srv().Store().FileInfo().Save(th.Context,
|
|
&model.FileInfo{
|
|
CreatorId: th.BasicUser.Id,
|
|
Path: "path.txt",
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
post := th.BasicPost
|
|
post.FileIds = []string{info1.Id, info2.Id}
|
|
|
|
appErr := th.App.attachFilesToPost(th.Context, post)
|
|
assert.Nil(t, appErr)
|
|
|
|
infos, _, appErr := th.App.GetFileInfosForPost(th.Context, post.Id, false, false)
|
|
assert.Nil(t, appErr)
|
|
assert.Len(t, infos, 2)
|
|
})
|
|
|
|
t.Run("should update File.PostIds after failing to add files", func(t *testing.T) {
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
info1, err := th.App.Srv().Store().FileInfo().Save(th.Context,
|
|
&model.FileInfo{
|
|
CreatorId: th.BasicUser.Id,
|
|
Path: "path.txt",
|
|
PostId: model.NewId(),
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
info2, err := th.App.Srv().Store().FileInfo().Save(th.Context,
|
|
&model.FileInfo{
|
|
CreatorId: th.BasicUser.Id,
|
|
Path: "path.txt",
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
post := th.BasicPost
|
|
post.FileIds = []string{info1.Id, info2.Id}
|
|
|
|
appErr := th.App.attachFilesToPost(th.Context, post)
|
|
assert.Nil(t, appErr)
|
|
|
|
infos, _, appErr := th.App.GetFileInfosForPost(th.Context, post.Id, false, false)
|
|
assert.Nil(t, appErr)
|
|
assert.Len(t, infos, 1)
|
|
assert.Equal(t, info2.Id, infos[0].Id)
|
|
|
|
updated, appErr := th.App.GetSinglePost(th.Context, post.Id, false)
|
|
require.Nil(t, appErr)
|
|
assert.Len(t, updated.FileIds, 1)
|
|
assert.Contains(t, updated.FileIds, info2.Id)
|
|
})
|
|
}
|
|
|
|
func TestUpdatePostEditAt(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
post := th.BasicPost.Clone()
|
|
|
|
post.IsPinned = true
|
|
saved, err := th.App.UpdatePost(th.Context, post, &model.UpdatePostOptions{SafeUpdate: true})
|
|
require.Nil(t, err)
|
|
assert.Equal(t, saved.EditAt, post.EditAt, "shouldn't have updated post.EditAt when pinning post")
|
|
post = saved.Clone()
|
|
|
|
time.Sleep(time.Millisecond * 100)
|
|
|
|
post.Message = model.NewId()
|
|
saved, err = th.App.UpdatePost(th.Context, post, &model.UpdatePostOptions{SafeUpdate: true})
|
|
require.Nil(t, err)
|
|
assert.NotEqual(t, saved.EditAt, post.EditAt, "should have updated post.EditAt when updating post message")
|
|
|
|
time.Sleep(time.Millisecond * 200)
|
|
}
|
|
|
|
func TestUpdatePostTimeLimit(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
post := th.BasicPost.Clone()
|
|
|
|
th.App.Srv().SetLicense(model.NewTestLicense())
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.ServiceSettings.PostEditTimeLimit = -1
|
|
})
|
|
_, err := th.App.UpdatePost(th.Context, post, &model.UpdatePostOptions{SafeUpdate: true})
|
|
require.Nil(t, err)
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.ServiceSettings.PostEditTimeLimit = 1000000000
|
|
})
|
|
post.Message = model.NewId()
|
|
|
|
_, err = th.App.UpdatePost(th.Context, post, &model.UpdatePostOptions{SafeUpdate: true})
|
|
require.Nil(t, err, "should allow you to edit the post")
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.ServiceSettings.PostEditTimeLimit = 1
|
|
})
|
|
post.Message = model.NewId()
|
|
_, err = th.App.UpdatePost(th.Context, post, &model.UpdatePostOptions{SafeUpdate: true})
|
|
require.Nil(t, err, "should allow you to edit an old post because the time check is applied above in the call hierarchy")
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.ServiceSettings.PostEditTimeLimit = -1
|
|
})
|
|
}
|
|
|
|
func TestUpdatePostInArchivedChannel(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
archivedChannel := th.CreateChannel(th.Context, th.BasicTeam)
|
|
post := th.CreatePost(archivedChannel)
|
|
appErr := th.App.DeleteChannel(th.Context, archivedChannel, "")
|
|
require.Nil(t, appErr)
|
|
|
|
_, err := th.App.UpdatePost(th.Context, post, &model.UpdatePostOptions{SafeUpdate: true})
|
|
require.NotNil(t, err)
|
|
require.Equal(t, "api.post.update_post.can_not_update_post_in_deleted.error", err.Id)
|
|
}
|
|
|
|
func TestPostReplyToPostWhereRootPosterLeftChannel(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
// This test ensures that when replying to a root post made by a user who has since left the channel, the reply
|
|
// post completes successfully. This is a regression test for PLT-6523.
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
channel := th.BasicChannel
|
|
userInChannel := th.BasicUser2
|
|
userNotInChannel := th.BasicUser
|
|
rootPost := th.BasicPost
|
|
|
|
_, err := th.App.AddUserToChannel(th.Context, userInChannel, channel, false)
|
|
require.Nil(t, err)
|
|
|
|
err = th.App.RemoveUserFromChannel(th.Context, userNotInChannel.Id, "", channel)
|
|
require.Nil(t, err)
|
|
replyPost := model.Post{
|
|
Message: "asd",
|
|
ChannelId: channel.Id,
|
|
RootId: rootPost.Id,
|
|
PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()),
|
|
UserId: userInChannel.Id,
|
|
CreateAt: 0,
|
|
}
|
|
|
|
_, err = th.App.CreatePostAsUser(th.Context, &replyPost, "", true)
|
|
require.Nil(t, err)
|
|
}
|
|
|
|
func TestPostAttachPostToChildPost(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
channel := th.BasicChannel
|
|
user := th.BasicUser
|
|
rootPost := th.BasicPost
|
|
|
|
replyPost1 := model.Post{
|
|
Message: "reply one",
|
|
ChannelId: channel.Id,
|
|
RootId: rootPost.Id,
|
|
PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()),
|
|
UserId: user.Id,
|
|
CreateAt: 0,
|
|
}
|
|
|
|
res1, err := th.App.CreatePostAsUser(th.Context, &replyPost1, "", true)
|
|
require.Nil(t, err)
|
|
|
|
replyPost2 := model.Post{
|
|
Message: "reply two",
|
|
ChannelId: channel.Id,
|
|
RootId: res1.Id,
|
|
PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()),
|
|
UserId: user.Id,
|
|
CreateAt: 0,
|
|
}
|
|
|
|
_, err = th.App.CreatePostAsUser(th.Context, &replyPost2, "", true)
|
|
assert.Equalf(t, err.StatusCode, http.StatusBadRequest, "Expected BadRequest error, got %v", err)
|
|
|
|
replyPost3 := model.Post{
|
|
Message: "reply three",
|
|
ChannelId: channel.Id,
|
|
RootId: rootPost.Id,
|
|
PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()),
|
|
UserId: user.Id,
|
|
CreateAt: 0,
|
|
}
|
|
|
|
_, err = th.App.CreatePostAsUser(th.Context, &replyPost3, "", true)
|
|
assert.Nil(t, err)
|
|
}
|
|
|
|
func TestUpdatePostPluginHooks(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
t.Run("Should stop processing at first reject", func(t *testing.T) {
|
|
setupMultiPluginAPITest(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) {
|
|
return nil, "rejected"
|
|
}
|
|
|
|
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) MessageWillBeUpdated(c *plugin.Context, newPost, oldPost *model.Post) (*model.Post, string) {
|
|
if (newPost == nil) {
|
|
return nil, "nil post"
|
|
}
|
|
newPost.Message = newPost.Message + "fromplugin"
|
|
return newPost, ""
|
|
}
|
|
|
|
func main() {
|
|
plugin.ClientMain(&MyPlugin{})
|
|
}
|
|
`,
|
|
}, []string{
|
|
`{"id": "testrejectfirstpost", "server": {"executable": "backend.exe"}}`,
|
|
`{"id": "testupdatepost", "server": {"executable": "backend.exe"}}`,
|
|
}, []string{
|
|
"testrejectfirstpost", "testupdatepost",
|
|
}, true, th.App, th.Context)
|
|
|
|
pendingPostId := makePendingPostId(th.BasicUser)
|
|
post, err := th.App.CreatePostAsUser(th.Context, &model.Post{
|
|
UserId: th.BasicUser.Id,
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "message",
|
|
PendingPostId: pendingPostId,
|
|
}, "", true)
|
|
require.Nil(t, err)
|
|
|
|
post.Message = "new message"
|
|
updatedPost, err := th.App.UpdatePost(th.Context, post, nil)
|
|
require.Nil(t, updatedPost)
|
|
require.NotNil(t, err)
|
|
require.Equal(t, "Post rejected by plugin. rejected", err.Id)
|
|
})
|
|
|
|
t.Run("Should update", func(t *testing.T) {
|
|
setupMultiPluginAPITest(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 + " 1"
|
|
return newPost, ""
|
|
}
|
|
|
|
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) MessageWillBeUpdated(c *plugin.Context, newPost, oldPost *model.Post) (*model.Post, string) {
|
|
newPost.Message = "2 " + newPost.Message
|
|
return newPost, ""
|
|
}
|
|
|
|
func main() {
|
|
plugin.ClientMain(&MyPlugin{})
|
|
}
|
|
`,
|
|
}, []string{
|
|
`{"id": "testaddone", "server": {"executable": "backend.exe"}}`,
|
|
`{"id": "testaddtwo", "server": {"executable": "backend.exe"}}`,
|
|
}, []string{
|
|
"testaddone", "testaddtwo",
|
|
}, true, th.App, th.Context)
|
|
|
|
pendingPostId := makePendingPostId(th.BasicUser)
|
|
post, err := th.App.CreatePostAsUser(th.Context, &model.Post{
|
|
UserId: th.BasicUser.Id,
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "message",
|
|
PendingPostId: pendingPostId,
|
|
}, "", true)
|
|
require.Nil(t, err)
|
|
|
|
post.Message = "new message"
|
|
updatedPost, err := th.App.UpdatePost(th.Context, post, nil)
|
|
require.Nil(t, err)
|
|
require.NotNil(t, updatedPost)
|
|
require.Equal(t, "2 new message 1", updatedPost.Message)
|
|
})
|
|
}
|
|
|
|
func TestPostChannelMentions(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
channel := th.BasicChannel
|
|
user := th.BasicUser
|
|
|
|
channelToMention, err := th.App.CreateChannel(th.Context, &model.Channel{
|
|
DisplayName: "Mention Test",
|
|
Name: "mention-test",
|
|
Type: model.ChannelTypeOpen,
|
|
TeamId: th.BasicTeam.Id,
|
|
}, false)
|
|
require.Nil(t, err)
|
|
defer func() {
|
|
appErr := th.App.PermanentDeleteChannel(th.Context, channelToMention)
|
|
require.Nil(t, appErr)
|
|
}()
|
|
channelToMention2, err := th.App.CreateChannel(th.Context, &model.Channel{
|
|
DisplayName: "Mention Test2",
|
|
Name: "mention-test2",
|
|
Type: model.ChannelTypeOpen,
|
|
TeamId: th.BasicTeam.Id,
|
|
}, false)
|
|
require.Nil(t, err)
|
|
defer func() {
|
|
appErr := th.App.PermanentDeleteChannel(th.Context, channelToMention2)
|
|
require.Nil(t, appErr)
|
|
}()
|
|
|
|
_, err = th.App.AddUserToChannel(th.Context, user, channel, false)
|
|
require.Nil(t, err)
|
|
|
|
post := &model.Post{
|
|
Message: fmt.Sprintf("hello, ~%v!", channelToMention.Name),
|
|
ChannelId: channel.Id,
|
|
PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()),
|
|
UserId: user.Id,
|
|
CreateAt: 0,
|
|
}
|
|
|
|
post, err = th.App.CreatePostAsUser(th.Context, post, "", true)
|
|
require.Nil(t, err)
|
|
assert.Equal(t, map[string]any{
|
|
"mention-test": map[string]any{
|
|
"display_name": "Mention Test",
|
|
"team_name": th.BasicTeam.Name,
|
|
},
|
|
}, post.GetProp(model.PostPropsChannelMentions))
|
|
|
|
post.Message = fmt.Sprintf("goodbye, ~%v!", channelToMention2.Name)
|
|
result, err := th.App.UpdatePost(th.Context, post, nil)
|
|
require.Nil(t, err)
|
|
assert.Equal(t, map[string]any{
|
|
"mention-test2": map[string]any{
|
|
"display_name": "Mention Test2",
|
|
"team_name": th.BasicTeam.Name,
|
|
},
|
|
}, result.GetProp(model.PostPropsChannelMentions))
|
|
|
|
result.Message = "no more mentions!"
|
|
result, err = th.App.UpdatePost(th.Context, result, nil)
|
|
require.Nil(t, err)
|
|
assert.Nil(t, result.GetProp(model.PostPropsChannelMentions))
|
|
}
|
|
|
|
func TestImageProxy(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := SetupWithStoreMock(t)
|
|
defer th.TearDown()
|
|
|
|
mockStore := th.App.Srv().Store().(*storemocks.Store)
|
|
mockUserStore := storemocks.UserStore{}
|
|
mockUserStore.On("Count", mock.Anything).Return(int64(10), nil)
|
|
mockPostStore := storemocks.PostStore{}
|
|
mockPostStore.On("GetMaxPostSize").Return(65535, nil)
|
|
mockSystemStore := storemocks.SystemStore{}
|
|
mockSystemStore.On("GetByName", "UpgradedFromTE").Return(&model.System{Name: "UpgradedFromTE", Value: "false"}, nil)
|
|
mockSystemStore.On("GetByName", "InstallationDate").Return(&model.System{Name: "InstallationDate", Value: "10"}, nil)
|
|
mockSystemStore.On("GetByName", "FirstServerRunTimestamp").Return(&model.System{Name: "FirstServerRunTimestamp", Value: "10"}, nil)
|
|
|
|
mockStore.On("User").Return(&mockUserStore)
|
|
mockStore.On("Post").Return(&mockPostStore)
|
|
mockStore.On("System").Return(&mockSystemStore)
|
|
mockStore.On("GetDBSchemaVersion").Return(1, nil)
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.ServiceSettings.SiteURL = "http://mymattermost.com"
|
|
})
|
|
|
|
th.App.ch.imageProxy = imageproxy.MakeImageProxy(th.Server.platform, th.Server.HTTPService(), th.Server.Log())
|
|
|
|
for name, tc := range map[string]struct {
|
|
ProxyType string
|
|
ProxyURL string
|
|
ProxyOptions string
|
|
ImageURL string
|
|
ProxiedImageURL string
|
|
ProxiedRemovedImageURL string
|
|
}{
|
|
"atmos/camo": {
|
|
ProxyType: model.ImageProxyTypeAtmosCamo,
|
|
ProxyURL: "https://127.0.0.1",
|
|
ProxyOptions: "foo",
|
|
ImageURL: "http://mydomain.com/myimage",
|
|
ProxiedRemovedImageURL: "http://mydomain.com/myimage",
|
|
ProxiedImageURL: "http://mymattermost.com/api/v4/image?url=http%3A%2F%2Fmydomain.com%2Fmyimage",
|
|
},
|
|
"atmos/camo_SameSite": {
|
|
ProxyType: model.ImageProxyTypeAtmosCamo,
|
|
ProxyURL: "https://127.0.0.1",
|
|
ProxyOptions: "foo",
|
|
ImageURL: "http://mymattermost.com/myimage",
|
|
ProxiedRemovedImageURL: "http://mymattermost.com/myimage",
|
|
ProxiedImageURL: "http://mymattermost.com/myimage",
|
|
},
|
|
"atmos/camo_PathOnly": {
|
|
ProxyType: model.ImageProxyTypeAtmosCamo,
|
|
ProxyURL: "https://127.0.0.1",
|
|
ProxyOptions: "foo",
|
|
ImageURL: "/myimage",
|
|
ProxiedRemovedImageURL: "http://mymattermost.com/myimage",
|
|
ProxiedImageURL: "http://mymattermost.com/myimage",
|
|
},
|
|
"atmos/camo_EmptyImageURL": {
|
|
ProxyType: model.ImageProxyTypeAtmosCamo,
|
|
ProxyURL: "https://127.0.0.1",
|
|
ProxyOptions: "foo",
|
|
ImageURL: "",
|
|
ProxiedRemovedImageURL: "",
|
|
ProxiedImageURL: "",
|
|
},
|
|
"local": {
|
|
ProxyType: model.ImageProxyTypeLocal,
|
|
ImageURL: "http://mydomain.com/myimage",
|
|
ProxiedRemovedImageURL: "http://mydomain.com/myimage",
|
|
ProxiedImageURL: "http://mymattermost.com/api/v4/image?url=http%3A%2F%2Fmydomain.com%2Fmyimage",
|
|
},
|
|
"local_SameSite": {
|
|
ProxyType: model.ImageProxyTypeLocal,
|
|
ImageURL: "http://mymattermost.com/myimage",
|
|
ProxiedRemovedImageURL: "http://mymattermost.com/myimage",
|
|
ProxiedImageURL: "http://mymattermost.com/myimage",
|
|
},
|
|
"local_PathOnly": {
|
|
ProxyType: model.ImageProxyTypeLocal,
|
|
ImageURL: "/myimage",
|
|
ProxiedRemovedImageURL: "http://mymattermost.com/myimage",
|
|
ProxiedImageURL: "http://mymattermost.com/myimage",
|
|
},
|
|
"local_EmptyImageURL": {
|
|
ProxyType: model.ImageProxyTypeLocal,
|
|
ImageURL: "",
|
|
ProxiedRemovedImageURL: "",
|
|
ProxiedImageURL: "",
|
|
},
|
|
} {
|
|
t.Run(name, func(t *testing.T) {
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
cfg.ImageProxySettings.Enable = model.NewPointer(true)
|
|
cfg.ImageProxySettings.ImageProxyType = model.NewPointer(tc.ProxyType)
|
|
cfg.ImageProxySettings.RemoteImageProxyOptions = model.NewPointer(tc.ProxyOptions)
|
|
cfg.ImageProxySettings.RemoteImageProxyURL = model.NewPointer(tc.ProxyURL)
|
|
})
|
|
|
|
post := &model.Post{
|
|
Id: model.NewId(),
|
|
Message: "",
|
|
}
|
|
|
|
list := model.NewPostList()
|
|
list.Posts[post.Id] = post
|
|
|
|
assert.Equal(t, "", th.App.PostWithProxyAddedToImageURLs(post).Message)
|
|
|
|
assert.Equal(t, "", th.App.PostWithProxyRemovedFromImageURLs(post).Message)
|
|
post.Message = ""
|
|
assert.Equal(t, "", th.App.PostWithProxyRemovedFromImageURLs(post).Message)
|
|
|
|
if tc.ImageURL != "" {
|
|
post.Message = ""
|
|
assert.Equal(t, "", th.App.PostWithProxyAddedToImageURLs(post).Message)
|
|
assert.Equal(t, "", th.App.PostWithProxyRemovedFromImageURLs(post).Message)
|
|
post.Message = ""
|
|
assert.Equal(t, "", th.App.PostWithProxyRemovedFromImageURLs(post).Message)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDeletePostWithFileAttachments(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
// Create a post with a file attachment.
|
|
teamID := th.BasicTeam.Id
|
|
channelID := th.BasicChannel.Id
|
|
userID := th.BasicUser.Id
|
|
filename := "test"
|
|
data := []byte("abcd")
|
|
|
|
info1, err := th.App.DoUploadFile(th.Context, time.Date(2007, 2, 4, 1, 2, 3, 4, time.Local), teamID, channelID, userID, filename, data, true)
|
|
require.Nil(t, err)
|
|
defer func() {
|
|
err := th.App.Srv().Store().FileInfo().PermanentDelete(th.Context, info1.Id)
|
|
require.NoError(t, err)
|
|
appErr := th.App.RemoveFile(info1.Path)
|
|
require.Nil(t, appErr)
|
|
}()
|
|
|
|
post := &model.Post{
|
|
Message: "asd",
|
|
ChannelId: channelID,
|
|
PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()),
|
|
UserId: userID,
|
|
CreateAt: 0,
|
|
FileIds: []string{info1.Id},
|
|
}
|
|
|
|
post, err = th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{SetOnline: true})
|
|
assert.Nil(t, err)
|
|
|
|
// Delete the post.
|
|
_, err = th.App.DeletePost(th.Context, post.Id, userID)
|
|
assert.Nil(t, err)
|
|
|
|
// Wait for the cleanup routine to finish.
|
|
time.Sleep(time.Millisecond * 100)
|
|
|
|
// Check that the file can no longer be reached.
|
|
_, err = th.App.GetFileInfo(th.Context, info1.Id)
|
|
assert.NotNil(t, err)
|
|
}
|
|
|
|
func TestDeletePostWithRestrictedDM(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
t.Run("cannot delete post in restricted DM", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.TeamSettings.RestrictDirectMessage = model.DirectMessageTeam
|
|
})
|
|
|
|
// Create a DM channel between two users who don't share a team
|
|
dmChannel := th.CreateDmChannel(th.BasicUser2)
|
|
|
|
// Ensure the two users do not share a team
|
|
teams, err := th.App.GetTeamsForUser(th.BasicUser.Id)
|
|
require.Nil(t, err)
|
|
for _, team := range teams {
|
|
teamErr := th.App.RemoveUserFromTeam(th.Context, team.Id, th.BasicUser.Id, th.SystemAdminUser.Id)
|
|
require.Nil(t, teamErr)
|
|
}
|
|
teams, err = th.App.GetTeamsForUser(th.BasicUser2.Id)
|
|
require.Nil(t, err)
|
|
for _, team := range teams {
|
|
teamErr := th.App.RemoveUserFromTeam(th.Context, team.Id, th.BasicUser2.Id, th.SystemAdminUser.Id)
|
|
require.Nil(t, teamErr)
|
|
}
|
|
|
|
// Create separate teams for each user
|
|
team1 := th.CreateTeam()
|
|
team2 := th.CreateTeam()
|
|
th.LinkUserToTeam(th.BasicUser, team1)
|
|
th.LinkUserToTeam(th.BasicUser2, team2)
|
|
|
|
// Create a post in the DM channel
|
|
post := &model.Post{
|
|
UserId: th.BasicUser.Id,
|
|
ChannelId: dmChannel.Id,
|
|
Message: "test post",
|
|
}
|
|
post, err = th.App.CreatePost(th.Context, post, dmChannel, model.CreatePostFlags{})
|
|
require.Nil(t, err)
|
|
|
|
// Try to delete the post
|
|
_, appErr := th.App.DeletePost(th.Context, post.Id, th.BasicUser.Id)
|
|
require.NotNil(t, appErr)
|
|
require.Equal(t, "api.post.delete_post.can_not_delete_from_restricted_dm.error", appErr.Id)
|
|
require.Equal(t, http.StatusBadRequest, appErr.StatusCode)
|
|
|
|
// Reset config
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.TeamSettings.RestrictDirectMessage = model.DirectMessageAny
|
|
})
|
|
})
|
|
}
|
|
|
|
func TestDeletePostInArchivedChannel(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
archivedChannel := th.CreateChannel(th.Context, th.BasicTeam)
|
|
post := th.CreatePost(archivedChannel)
|
|
appErr := th.App.DeleteChannel(th.Context, archivedChannel, "")
|
|
require.Nil(t, appErr)
|
|
|
|
_, err := th.App.DeletePost(th.Context, post.Id, "")
|
|
require.NotNil(t, err)
|
|
require.Equal(t, "api.post.delete_post.can_not_delete_post_in_deleted.error", err.Id)
|
|
}
|
|
|
|
func TestCreatePost(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
t.Run("call PreparePostForClient before returning", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.ServiceSettings.SiteURL = "http://mymattermost.com"
|
|
*cfg.ImageProxySettings.Enable = true
|
|
*cfg.ImageProxySettings.ImageProxyType = "atmos/camo"
|
|
*cfg.ImageProxySettings.RemoteImageProxyURL = "https://127.0.0.1"
|
|
*cfg.ImageProxySettings.RemoteImageProxyOptions = "foo"
|
|
})
|
|
|
|
th.App.ch.imageProxy = imageproxy.MakeImageProxy(th.Server.platform, th.Server.HTTPService(), th.Server.Log())
|
|
|
|
imageURL := "http://mydomain.com/myimage"
|
|
proxiedImageURL := "http://mymattermost.com/api/v4/image?url=http%3A%2F%2Fmydomain.com%2Fmyimage"
|
|
|
|
post := &model.Post{
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "",
|
|
UserId: th.BasicUser.Id,
|
|
}
|
|
|
|
rpost, err := th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err)
|
|
assert.Equal(t, "", rpost.Message)
|
|
})
|
|
|
|
t.Run("Sets prop MENTION_HIGHLIGHT_DISABLED when it should", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
th.AddUserToChannel(th.BasicUser, th.BasicChannel)
|
|
|
|
t.Run("Does not set prop when user has USE_CHANNEL_MENTIONS", func(t *testing.T) {
|
|
postWithNoMention := &model.Post{
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "This post does not have mentions",
|
|
UserId: th.BasicUser.Id,
|
|
}
|
|
rpost, err := th.App.CreatePost(th.Context, postWithNoMention, th.BasicChannel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err)
|
|
assert.Equal(t, rpost.GetProps(), model.StringInterface{})
|
|
|
|
postWithMention := &model.Post{
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "This post has @here mention @all",
|
|
UserId: th.BasicUser.Id,
|
|
}
|
|
rpost, err = th.App.CreatePost(th.Context, postWithMention, th.BasicChannel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err)
|
|
assert.Equal(t, rpost.GetProps(), model.StringInterface{})
|
|
})
|
|
|
|
t.Run("Sets prop when post has mentions and user does not have USE_CHANNEL_MENTIONS", func(t *testing.T) {
|
|
th.RemovePermissionFromRole(model.PermissionUseChannelMentions.Id, model.ChannelUserRoleId)
|
|
th.RemovePermissionFromRole(model.PermissionUseChannelMentions.Id, model.ChannelAdminRoleId)
|
|
|
|
postWithNoMention := &model.Post{
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "This post does not have mentions",
|
|
UserId: th.BasicUser.Id,
|
|
}
|
|
rpost, err := th.App.CreatePost(th.Context, postWithNoMention, th.BasicChannel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err)
|
|
assert.Equal(t, rpost.GetProps(), model.StringInterface{})
|
|
|
|
postWithMention := &model.Post{
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "This post has @here mention @all",
|
|
UserId: th.BasicUser.Id,
|
|
}
|
|
rpost, err = th.App.CreatePost(th.Context, postWithMention, th.BasicChannel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err)
|
|
assert.Equal(t, rpost.GetProp(model.PostPropsMentionHighlightDisabled), true)
|
|
|
|
th.AddPermissionToRole(model.PermissionUseChannelMentions.Id, model.ChannelUserRoleId)
|
|
th.AddPermissionToRole(model.PermissionUseChannelMentions.Id, model.ChannelAdminRoleId)
|
|
})
|
|
})
|
|
|
|
t.Run("Sets PostPropsPreviewedPost when a permalink is the first link", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
th.AddUserToChannel(th.BasicUser, th.BasicChannel)
|
|
|
|
referencedPost := &model.Post{
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "hello world",
|
|
UserId: th.BasicUser.Id,
|
|
}
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.ServiceSettings.SiteURL = "http://mymattermost.com"
|
|
})
|
|
|
|
th.Context.Session().UserId = th.BasicUser.Id
|
|
|
|
referencedPost, err := th.App.CreatePost(th.Context, referencedPost, th.BasicChannel, model.CreatePostFlags{})
|
|
require.Nil(t, err)
|
|
|
|
permalink := fmt.Sprintf("%s/%s/pl/%s", *th.App.Config().ServiceSettings.SiteURL, th.BasicTeam.Name, referencedPost.Id)
|
|
|
|
channelForPreview := th.CreateChannel(th.Context, th.BasicTeam)
|
|
previewPost := &model.Post{
|
|
ChannelId: channelForPreview.Id,
|
|
Message: permalink,
|
|
UserId: th.BasicUser.Id,
|
|
}
|
|
|
|
previewPost, err = th.App.CreatePost(th.Context, previewPost, channelForPreview, model.CreatePostFlags{})
|
|
require.Nil(t, err)
|
|
|
|
assert.Equal(t, previewPost.GetProps(), model.StringInterface{"previewed_post": referencedPost.Id})
|
|
})
|
|
|
|
t.Run("creates a single record for a permalink preview post", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
channelForPreview := th.CreateChannel(th.Context, th.BasicTeam)
|
|
|
|
referencedPost := &model.Post{
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "hello world",
|
|
UserId: th.BasicUser.Id,
|
|
}
|
|
referencedPost, err := th.App.CreatePost(th.Context, referencedPost, th.BasicChannel, model.CreatePostFlags{})
|
|
require.Nil(t, err)
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.ServiceSettings.SiteURL = "http://foobar.com"
|
|
*cfg.ServiceSettings.EnablePermalinkPreviews = true
|
|
})
|
|
|
|
permalink := fmt.Sprintf("%s/%s/pl/%s", *th.App.Config().ServiceSettings.SiteURL, th.BasicTeam.Name, referencedPost.Id)
|
|
|
|
previewPost := &model.Post{
|
|
ChannelId: channelForPreview.Id,
|
|
Message: permalink,
|
|
UserId: th.BasicUser.Id,
|
|
}
|
|
|
|
previewPost, err = th.App.CreatePost(th.Context, previewPost, channelForPreview, model.CreatePostFlags{})
|
|
require.Nil(t, err)
|
|
|
|
sqlStore := th.GetSqlStore()
|
|
sql := fmt.Sprintf("select count(*) from Posts where Id = '%[1]s' or OriginalId = '%[1]s';", previewPost.Id)
|
|
var val int64
|
|
err2 := sqlStore.GetMaster().Get(&val, sql)
|
|
require.NoError(t, err2)
|
|
|
|
require.EqualValues(t, int64(1), val)
|
|
})
|
|
|
|
t.Run("sanitizes post metadata appropriately", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.ServiceSettings.SiteURL = "http://mymattermost.com"
|
|
})
|
|
|
|
th.AddUserToChannel(th.BasicUser, th.BasicChannel)
|
|
|
|
user1 := th.CreateUser()
|
|
user2 := th.CreateUser()
|
|
directChannel, err := th.App.createDirectChannel(th.Context, user1.Id, user2.Id)
|
|
require.Nil(t, err)
|
|
|
|
th.Context.Session().UserId = th.BasicUser.Id
|
|
|
|
testCases := []struct {
|
|
Description string
|
|
Channel *model.Channel
|
|
Author string
|
|
Length int
|
|
}{
|
|
{
|
|
Description: "removes metadata from post for members who cannot read channel",
|
|
Channel: directChannel,
|
|
Author: user1.Id,
|
|
Length: 0,
|
|
},
|
|
{
|
|
Description: "does not remove metadata from post for members who can read channel",
|
|
Channel: th.BasicChannel,
|
|
Author: th.BasicUser.Id,
|
|
Length: 1,
|
|
},
|
|
}
|
|
|
|
for _, testCase := range testCases {
|
|
t.Run(testCase.Description, func(t *testing.T) {
|
|
referencedPost := &model.Post{
|
|
ChannelId: testCase.Channel.Id,
|
|
Message: "hello world",
|
|
UserId: testCase.Author,
|
|
}
|
|
referencedPost, err = th.App.CreatePost(th.Context, referencedPost, testCase.Channel, model.CreatePostFlags{})
|
|
require.Nil(t, err)
|
|
|
|
permalink := fmt.Sprintf("%s/%s/pl/%s", *th.App.Config().ServiceSettings.SiteURL, th.BasicTeam.Name, referencedPost.Id)
|
|
previewPost := &model.Post{
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: permalink,
|
|
UserId: th.BasicUser.Id,
|
|
}
|
|
|
|
previewPost, err = th.App.CreatePost(th.Context, previewPost, th.BasicChannel, model.CreatePostFlags{})
|
|
require.Nil(t, err)
|
|
|
|
require.Len(t, previewPost.Metadata.Embeds, testCase.Length)
|
|
})
|
|
}
|
|
})
|
|
|
|
t.Run("Should not allow to create posts on shared DMs", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := setupSharedChannels(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
user1 := th.CreateUser()
|
|
user2 := th.CreateUser()
|
|
dm, appErr := th.App.createDirectChannel(th.Context, user1.Id, user2.Id)
|
|
require.Nil(t, appErr)
|
|
require.NotNil(t, dm)
|
|
|
|
// we can't create direct channels with remote users, so we
|
|
// have to force the channel to be shared through the store to
|
|
// simulate preexisting shared DMs
|
|
sc := &model.SharedChannel{
|
|
ChannelId: dm.Id,
|
|
Type: dm.Type,
|
|
Home: true,
|
|
ShareName: "shareddm",
|
|
CreatorId: user1.Id,
|
|
RemoteId: model.NewId(),
|
|
}
|
|
_, scErr := th.Server.Store().SharedChannel().Save(sc)
|
|
require.NoError(t, scErr)
|
|
|
|
// and we update the channel to mark it as shared
|
|
dm.Shared = model.NewPointer(true)
|
|
_, err := th.Server.Store().Channel().Update(th.Context, dm)
|
|
require.NoError(t, err)
|
|
|
|
newPost := &model.Post{
|
|
ChannelId: dm.Id,
|
|
Message: "hello world",
|
|
UserId: user1.Id,
|
|
}
|
|
createdPost, appErr := th.App.CreatePost(th.Context, newPost, dm, model.CreatePostFlags{})
|
|
require.NotNil(t, appErr)
|
|
require.Nil(t, createdPost)
|
|
})
|
|
|
|
t.Run("Should not allow to create posts on shared GMs", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := setupSharedChannels(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
user1 := th.CreateUser()
|
|
user2 := th.CreateUser()
|
|
user3 := th.CreateUser()
|
|
gm, appErr := th.App.createGroupChannel(th.Context, []string{user1.Id, user2.Id, user3.Id}, user1.Id)
|
|
require.Nil(t, appErr)
|
|
require.NotNil(t, gm)
|
|
|
|
// we can't create group channels with remote users, so we
|
|
// have to force the channel to be shared through the store to
|
|
// simulate preexisting shared GMs
|
|
sc := &model.SharedChannel{
|
|
ChannelId: gm.Id,
|
|
Type: gm.Type,
|
|
Home: true,
|
|
ShareName: "sharedgm",
|
|
CreatorId: user1.Id,
|
|
RemoteId: model.NewId(),
|
|
}
|
|
_, err := th.Server.Store().SharedChannel().Save(sc)
|
|
require.NoError(t, err)
|
|
|
|
// and we update the channel to mark it as shared
|
|
gm.Shared = model.NewPointer(true)
|
|
_, err = th.Server.Store().Channel().Update(th.Context, gm)
|
|
require.NoError(t, err)
|
|
|
|
newPost := &model.Post{
|
|
ChannelId: gm.Id,
|
|
Message: "hello world",
|
|
UserId: user1.Id,
|
|
}
|
|
createdPost, appErr := th.App.CreatePost(th.Context, newPost, gm, model.CreatePostFlags{})
|
|
require.NotNil(t, appErr)
|
|
require.Nil(t, createdPost)
|
|
})
|
|
|
|
t.Run("MM-40016 should not panic with `concurrent map read and map write`", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
channelForPreview := th.CreateChannel(th.Context, th.BasicTeam)
|
|
|
|
for range 20 {
|
|
user := th.CreateUser()
|
|
th.LinkUserToTeam(user, th.BasicTeam)
|
|
th.AddUserToChannel(user, channelForPreview)
|
|
}
|
|
|
|
referencedPost := &model.Post{
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "hello world",
|
|
UserId: th.BasicUser.Id,
|
|
}
|
|
referencedPost, err := th.App.CreatePost(th.Context, referencedPost, th.BasicChannel, model.CreatePostFlags{})
|
|
require.Nil(t, err)
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.ServiceSettings.SiteURL = "http://example.com"
|
|
*cfg.ServiceSettings.EnablePermalinkPreviews = true
|
|
})
|
|
|
|
permalink := fmt.Sprintf("%s/%s/pl/%s", *th.App.Config().ServiceSettings.SiteURL, th.BasicTeam.Name, referencedPost.Id)
|
|
|
|
previewPost := &model.Post{
|
|
ChannelId: channelForPreview.Id,
|
|
Message: permalink,
|
|
UserId: th.BasicUser.Id,
|
|
}
|
|
|
|
previewPost, err = th.App.CreatePost(th.Context, previewPost, channelForPreview, model.CreatePostFlags{})
|
|
require.Nil(t, err)
|
|
|
|
n := 1000
|
|
var wg sync.WaitGroup
|
|
wg.Add(n)
|
|
for range n {
|
|
go func() {
|
|
defer wg.Done()
|
|
post := previewPost.Clone()
|
|
_, appErr := th.App.UpdatePost(th.Context, post, nil)
|
|
require.Nil(t, appErr)
|
|
}()
|
|
}
|
|
|
|
wg.Wait()
|
|
})
|
|
|
|
t.Run("should sanitize the force notifications prop if the flag is not set", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
th.AddUserToChannel(th.BasicUser, th.BasicChannel)
|
|
|
|
postToCreate := &model.Post{
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "hello world",
|
|
UserId: th.BasicUser.Id,
|
|
}
|
|
postToCreate.AddProp(model.PostPropsForceNotification, model.NewId())
|
|
createdPost, err := th.App.CreatePost(th.Context, postToCreate, th.BasicChannel, model.CreatePostFlags{})
|
|
require.Nil(t, err)
|
|
require.Empty(t, createdPost.GetProp(model.PostPropsForceNotification))
|
|
})
|
|
|
|
t.Run("should add the force notifications prop if the flag is set", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
th.AddUserToChannel(th.BasicUser, th.BasicChannel)
|
|
|
|
postToCreate := &model.Post{
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "hello world",
|
|
UserId: th.BasicUser.Id,
|
|
}
|
|
createdPost, err := th.App.CreatePost(th.Context, postToCreate, th.BasicChannel, model.CreatePostFlags{ForceNotification: true})
|
|
require.Nil(t, err)
|
|
require.NotEmpty(t, createdPost.GetProp(model.PostPropsForceNotification))
|
|
})
|
|
}
|
|
|
|
func TestPatchPost(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
t.Run("call PreparePostForClient before returning", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.ServiceSettings.SiteURL = "http://mymattermost.com"
|
|
*cfg.ImageProxySettings.Enable = true
|
|
*cfg.ImageProxySettings.ImageProxyType = "atmos/camo"
|
|
*cfg.ImageProxySettings.RemoteImageProxyURL = "https://127.0.0.1"
|
|
*cfg.ImageProxySettings.RemoteImageProxyOptions = "foo"
|
|
})
|
|
|
|
th.App.ch.imageProxy = imageproxy.MakeImageProxy(th.Server.platform, th.Server.HTTPService(), th.Server.Log())
|
|
|
|
imageURL := "http://mydomain.com/myimage"
|
|
proxiedImageURL := "http://mymattermost.com/api/v4/image?url=http%3A%2F%2Fmydomain.com%2Fmyimage"
|
|
|
|
post := &model.Post{
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "",
|
|
UserId: th.BasicUser.Id,
|
|
}
|
|
|
|
rpost, err := th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err)
|
|
assert.NotEqual(t, "", rpost.Message)
|
|
|
|
patch := &model.PostPatch{
|
|
Message: model.NewPointer(""),
|
|
}
|
|
|
|
rpost, err = th.App.PatchPost(th.Context, rpost.Id, patch, nil)
|
|
require.Nil(t, err)
|
|
assert.Equal(t, "", rpost.Message)
|
|
})
|
|
|
|
t.Run("Sets Prop MENTION_HIGHLIGHT_DISABLED when it should", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
th.AddUserToChannel(th.BasicUser, th.BasicChannel)
|
|
|
|
post := &model.Post{
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "This post does not have mentions",
|
|
UserId: th.BasicUser.Id,
|
|
}
|
|
|
|
rpost, err := th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err)
|
|
|
|
t.Run("Does not set prop when user has USE_CHANNEL_MENTIONS", func(t *testing.T) {
|
|
patchWithNoMention := &model.PostPatch{Message: model.NewPointer("This patch has no channel mention")}
|
|
|
|
rpost, err = th.App.PatchPost(th.Context, rpost.Id, patchWithNoMention, nil)
|
|
require.Nil(t, err)
|
|
assert.Equal(t, rpost.GetProps(), model.StringInterface{})
|
|
|
|
patchWithMention := &model.PostPatch{Message: model.NewPointer("This patch has a mention now @here")}
|
|
|
|
rpost, err = th.App.PatchPost(th.Context, rpost.Id, patchWithMention, nil)
|
|
require.Nil(t, err)
|
|
assert.Equal(t, rpost.GetProps(), model.StringInterface{})
|
|
})
|
|
|
|
t.Run("Sets prop when user does not have USE_CHANNEL_MENTIONS", func(t *testing.T) {
|
|
th.RemovePermissionFromRole(model.PermissionUseChannelMentions.Id, model.ChannelUserRoleId)
|
|
th.RemovePermissionFromRole(model.PermissionUseChannelMentions.Id, model.ChannelAdminRoleId)
|
|
|
|
patchWithNoMention := &model.PostPatch{Message: model.NewPointer("This patch still does not have a mention")}
|
|
rpost, err = th.App.PatchPost(th.Context, rpost.Id, patchWithNoMention, nil)
|
|
require.Nil(t, err)
|
|
assert.Equal(t, rpost.GetProps(), model.StringInterface{})
|
|
|
|
patchWithMention := &model.PostPatch{Message: model.NewPointer("This patch has a mention now @here")}
|
|
|
|
rpost, err = th.App.PatchPost(th.Context, rpost.Id, patchWithMention, nil)
|
|
require.Nil(t, err)
|
|
assert.Equal(t, rpost.GetProp(model.PostPropsMentionHighlightDisabled), true)
|
|
|
|
th.AddPermissionToRole(model.PermissionUseChannelMentions.Id, model.ChannelUserRoleId)
|
|
th.AddPermissionToRole(model.PermissionUseChannelMentions.Id, model.ChannelAdminRoleId)
|
|
})
|
|
})
|
|
|
|
t.Run("cannot patch post in restricted DM", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.TeamSettings.RestrictDirectMessage = model.DirectMessageTeam
|
|
})
|
|
|
|
// Create a DM channel between two users who don't share a team
|
|
dmChannel := th.CreateDmChannel(th.BasicUser2)
|
|
|
|
// Ensure the two users do not share a team
|
|
teams, err := th.App.GetTeamsForUser(th.BasicUser.Id)
|
|
require.Nil(t, err)
|
|
for _, team := range teams {
|
|
teamErr := th.App.RemoveUserFromTeam(th.Context, team.Id, th.BasicUser.Id, th.SystemAdminUser.Id)
|
|
require.Nil(t, teamErr)
|
|
}
|
|
teams, err = th.App.GetTeamsForUser(th.BasicUser2.Id)
|
|
require.Nil(t, err)
|
|
for _, team := range teams {
|
|
teamErr := th.App.RemoveUserFromTeam(th.Context, team.Id, th.BasicUser2.Id, th.SystemAdminUser.Id)
|
|
require.Nil(t, teamErr)
|
|
}
|
|
|
|
// Create separate teams for each user
|
|
team1 := th.CreateTeam()
|
|
team2 := th.CreateTeam()
|
|
th.LinkUserToTeam(th.BasicUser, team1)
|
|
th.LinkUserToTeam(th.BasicUser2, team2)
|
|
|
|
// Create a post in the DM channel
|
|
post := &model.Post{
|
|
UserId: th.BasicUser.Id,
|
|
ChannelId: dmChannel.Id,
|
|
Message: "test post",
|
|
}
|
|
post, err = th.App.CreatePost(th.Context, post, dmChannel, model.CreatePostFlags{})
|
|
require.Nil(t, err)
|
|
|
|
// Try to patch the post
|
|
patch := &model.PostPatch{
|
|
Message: model.NewPointer("updated message"),
|
|
}
|
|
_, appErr := th.App.PatchPost(th.Context, post.Id, patch, model.DefaultUpdatePostOptions())
|
|
require.NotNil(t, appErr)
|
|
require.Equal(t, "api.post.patch_post.can_not_update_post_in_restricted_dm.error", appErr.Id)
|
|
require.Equal(t, http.StatusBadRequest, appErr.StatusCode)
|
|
|
|
// Reset config
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.TeamSettings.RestrictDirectMessage = model.DirectMessageAny
|
|
})
|
|
})
|
|
}
|
|
|
|
func TestCreatePostAsUser(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
t.Run("marks channel as viewed for regular user", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
post := &model.Post{
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "test",
|
|
UserId: th.BasicUser.Id,
|
|
}
|
|
|
|
channelMemberBefore, err := th.App.Srv().Store().Channel().GetMember(th.Context, th.BasicChannel.Id, th.BasicUser.Id)
|
|
require.NoError(t, err)
|
|
|
|
time.Sleep(1 * time.Millisecond)
|
|
_, appErr := th.App.CreatePostAsUser(th.Context, post, "", true)
|
|
require.Nil(t, appErr)
|
|
|
|
channelMemberAfter, err := th.App.Srv().Store().Channel().GetMember(th.Context, th.BasicChannel.Id, th.BasicUser.Id)
|
|
require.NoError(t, err)
|
|
|
|
require.Greater(t, channelMemberAfter.LastViewedAt, channelMemberBefore.LastViewedAt)
|
|
})
|
|
|
|
t.Run("does not mark channel as viewed for webhook from user", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
post := &model.Post{
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "test",
|
|
UserId: th.BasicUser.Id,
|
|
}
|
|
post.AddProp(model.PostPropsFromWebhook, "true")
|
|
|
|
channelMemberBefore, err := th.App.Srv().Store().Channel().GetMember(th.Context, th.BasicChannel.Id, th.BasicUser.Id)
|
|
require.NoError(t, err)
|
|
|
|
time.Sleep(1 * time.Millisecond)
|
|
_, appErr := th.App.CreatePostAsUser(th.Context, post, "", true)
|
|
require.Nil(t, appErr)
|
|
|
|
channelMemberAfter, err := th.App.Srv().Store().Channel().GetMember(th.Context, th.BasicChannel.Id, th.BasicUser.Id)
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, channelMemberAfter.LastViewedAt, channelMemberBefore.LastViewedAt)
|
|
})
|
|
|
|
t.Run("does not mark channel as viewed for bot user in channel", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
bot := th.CreateBot()
|
|
|
|
botUser, appErr := th.App.GetUser(bot.UserId)
|
|
require.Nil(t, appErr)
|
|
|
|
th.LinkUserToTeam(botUser, th.BasicTeam)
|
|
th.AddUserToChannel(botUser, th.BasicChannel)
|
|
|
|
post := &model.Post{
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "test",
|
|
UserId: bot.UserId,
|
|
}
|
|
|
|
channelMemberBefore, err := th.App.Srv().Store().Channel().GetMember(th.Context, th.BasicChannel.Id, th.BasicUser.Id)
|
|
require.NoError(t, err)
|
|
|
|
time.Sleep(1 * time.Millisecond)
|
|
_, appErr = th.App.CreatePostAsUser(th.Context, post, "", true)
|
|
require.Nil(t, appErr)
|
|
|
|
channelMemberAfter, err := th.App.Srv().Store().Channel().GetMember(th.Context, th.BasicChannel.Id, th.BasicUser.Id)
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, channelMemberAfter.LastViewedAt, channelMemberBefore.LastViewedAt)
|
|
})
|
|
|
|
t.Run("does not log warning for bot user not in channel", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
bot := th.CreateBot()
|
|
|
|
botUser, appErr := th.App.GetUser(bot.UserId)
|
|
require.Nil(t, appErr)
|
|
|
|
th.LinkUserToTeam(botUser, th.BasicTeam)
|
|
|
|
post := &model.Post{
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "test",
|
|
UserId: bot.UserId,
|
|
}
|
|
|
|
_, appErr = th.App.CreatePostAsUser(th.Context, post, "", true)
|
|
require.Nil(t, appErr)
|
|
|
|
require.NoError(t, th.TestLogger.Flush())
|
|
|
|
testlib.AssertNoLog(t, th.LogBuffer, mlog.LvlWarn.Name, "Failed to get membership")
|
|
})
|
|
|
|
t.Run("marks channel as viewed for reply post when CRT is off", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.ServiceSettings.CollapsedThreads = model.CollapsedThreadsDefaultOff
|
|
})
|
|
|
|
post := &model.Post{
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "test",
|
|
UserId: th.BasicUser2.Id,
|
|
}
|
|
rootPost, appErr := th.App.CreatePostAsUser(th.Context, post, "", true)
|
|
require.Nil(t, appErr)
|
|
|
|
channelMemberBefore, err := th.App.Srv().Store().Channel().GetMember(th.Context, th.BasicChannel.Id, th.BasicUser.Id)
|
|
require.NoError(t, err)
|
|
|
|
time.Sleep(1 * time.Millisecond)
|
|
replyPost := &model.Post{
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "test reply",
|
|
UserId: th.BasicUser.Id,
|
|
RootId: rootPost.Id,
|
|
}
|
|
_, appErr = th.App.CreatePostAsUser(th.Context, replyPost, "", true)
|
|
require.Nil(t, appErr)
|
|
|
|
channelMemberAfter, err := th.App.Srv().Store().Channel().GetMember(th.Context, th.BasicChannel.Id, th.BasicUser.Id)
|
|
require.NoError(t, err)
|
|
|
|
require.NotEqual(t, channelMemberAfter.LastViewedAt, channelMemberBefore.LastViewedAt)
|
|
})
|
|
|
|
t.Run("does not mark channel as viewed for reply post when CRT is on", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.ServiceSettings.ThreadAutoFollow = true
|
|
*cfg.ServiceSettings.CollapsedThreads = model.CollapsedThreadsDefaultOn
|
|
})
|
|
|
|
post := &model.Post{
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "test",
|
|
UserId: th.BasicUser2.Id,
|
|
}
|
|
rootPost, appErr := th.App.CreatePostAsUser(th.Context, post, "", true)
|
|
require.Nil(t, appErr)
|
|
|
|
channelMemberBefore, err := th.App.Srv().Store().Channel().GetMember(th.Context, th.BasicChannel.Id, th.BasicUser.Id)
|
|
require.NoError(t, err)
|
|
|
|
time.Sleep(1 * time.Millisecond)
|
|
replyPost := &model.Post{
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "test reply",
|
|
UserId: th.BasicUser.Id,
|
|
RootId: rootPost.Id,
|
|
}
|
|
_, appErr = th.App.CreatePostAsUser(th.Context, replyPost, "", true)
|
|
require.Nil(t, appErr)
|
|
|
|
channelMemberAfter, err := th.App.Srv().Store().Channel().GetMember(th.Context, th.BasicChannel.Id, th.BasicUser.Id)
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, channelMemberAfter.LastViewedAt, channelMemberBefore.LastViewedAt)
|
|
})
|
|
|
|
t.Run("cannot create post as user in restricted DM", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.TeamSettings.RestrictDirectMessage = model.DirectMessageTeam
|
|
})
|
|
|
|
// Create a DM channel between two users who don't share a team
|
|
dmChannel := th.CreateDmChannel(th.BasicUser2)
|
|
|
|
// Ensure the two users do not share a team
|
|
teams, err := th.App.GetTeamsForUser(th.BasicUser.Id)
|
|
require.Nil(t, err)
|
|
for _, team := range teams {
|
|
teamErr := th.App.RemoveUserFromTeam(th.Context, team.Id, th.BasicUser.Id, th.SystemAdminUser.Id)
|
|
require.Nil(t, teamErr)
|
|
}
|
|
teams, err = th.App.GetTeamsForUser(th.BasicUser2.Id)
|
|
require.Nil(t, err)
|
|
for _, team := range teams {
|
|
teamErr := th.App.RemoveUserFromTeam(th.Context, team.Id, th.BasicUser2.Id, th.SystemAdminUser.Id)
|
|
require.Nil(t, teamErr)
|
|
}
|
|
|
|
// Create separate teams for each user
|
|
team1 := th.CreateTeam()
|
|
team2 := th.CreateTeam()
|
|
th.LinkUserToTeam(th.BasicUser, team1)
|
|
th.LinkUserToTeam(th.BasicUser2, team2)
|
|
|
|
post := &model.Post{
|
|
UserId: th.BasicUser.Id,
|
|
ChannelId: dmChannel.Id,
|
|
Message: "test post",
|
|
}
|
|
|
|
_, appErr := th.App.CreatePostAsUser(th.Context, post, "", true)
|
|
require.NotNil(t, appErr)
|
|
require.Equal(t, "api.post.create_post.can_not_post_in_restricted_dm.error", appErr.Id)
|
|
require.Equal(t, http.StatusBadRequest, appErr.StatusCode)
|
|
|
|
// Reset config
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.TeamSettings.RestrictDirectMessage = model.DirectMessageAny
|
|
})
|
|
})
|
|
}
|
|
|
|
func TestPatchPostInArchivedChannel(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
archivedChannel := th.CreateChannel(th.Context, th.BasicTeam)
|
|
post := th.CreatePost(archivedChannel)
|
|
appErr := th.App.DeleteChannel(th.Context, archivedChannel, "")
|
|
require.Nil(t, appErr)
|
|
|
|
_, err := th.App.PatchPost(th.Context, post.Id, &model.PostPatch{IsPinned: model.NewPointer(true)}, nil)
|
|
require.NotNil(t, err)
|
|
require.Equal(t, "api.post.patch_post.can_not_update_post_in_deleted.error", err.Id)
|
|
}
|
|
|
|
func TestUpdateEphemeralPost(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
t.Run("Post contains preview if the user has permissions", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
th.AddUserToChannel(th.BasicUser, th.BasicChannel)
|
|
|
|
referencedPost := &model.Post{
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "hello world",
|
|
UserId: th.BasicUser.Id,
|
|
}
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.ServiceSettings.SiteURL = "http://mymattermost.com"
|
|
})
|
|
|
|
th.Context.Session().UserId = th.BasicUser.Id
|
|
|
|
referencedPost, err := th.App.CreatePost(th.Context, referencedPost, th.BasicChannel, model.CreatePostFlags{})
|
|
require.Nil(t, err)
|
|
|
|
permalink := fmt.Sprintf("%s/%s/pl/%s", *th.App.Config().ServiceSettings.SiteURL, th.BasicTeam.Name, referencedPost.Id)
|
|
|
|
testPost := &model.Post{
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: permalink,
|
|
UserId: th.BasicUser.Id,
|
|
}
|
|
|
|
testPost = th.App.UpdateEphemeralPost(th.Context, th.BasicUser.Id, testPost)
|
|
require.NotNil(t, testPost.Metadata)
|
|
require.Len(t, testPost.Metadata.Embeds, 1)
|
|
require.Equal(t, model.PostEmbedPermalink, testPost.Metadata.Embeds[0].Type)
|
|
})
|
|
|
|
t.Run("Post does not contain preview if the user has no permissions", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
privateChannel := th.CreatePrivateChannel(th.Context, th.BasicTeam)
|
|
th.AddUserToChannel(th.BasicUser, privateChannel)
|
|
th.AddUserToChannel(th.BasicUser2, th.BasicChannel)
|
|
|
|
referencedPost := &model.Post{
|
|
ChannelId: privateChannel.Id,
|
|
Message: "hello world",
|
|
UserId: th.BasicUser.Id,
|
|
}
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.ServiceSettings.SiteURL = "http://mymattermost.com"
|
|
})
|
|
|
|
th.Context.Session().UserId = th.BasicUser.Id
|
|
|
|
referencedPost, err := th.App.CreatePost(th.Context, referencedPost, th.BasicChannel, model.CreatePostFlags{})
|
|
require.Nil(t, err)
|
|
|
|
permalink := fmt.Sprintf("%s/%s/pl/%s", *th.App.Config().ServiceSettings.SiteURL, th.BasicTeam.Name, referencedPost.Id)
|
|
|
|
testPost := &model.Post{
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: permalink,
|
|
UserId: th.BasicUser2.Id,
|
|
}
|
|
|
|
testPost = th.App.UpdateEphemeralPost(th.Context, th.BasicUser2.Id, testPost)
|
|
require.Nil(t, testPost.Metadata.Embeds)
|
|
})
|
|
}
|
|
|
|
func TestUpdatePost(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
t.Run("call PreparePostForClient before returning", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.ServiceSettings.SiteURL = "http://mymattermost.com"
|
|
*cfg.ImageProxySettings.Enable = true
|
|
*cfg.ImageProxySettings.ImageProxyType = "atmos/camo"
|
|
*cfg.ImageProxySettings.RemoteImageProxyURL = "https://127.0.0.1"
|
|
*cfg.ImageProxySettings.RemoteImageProxyOptions = "foo"
|
|
})
|
|
|
|
th.App.ch.imageProxy = imageproxy.MakeImageProxy(th.Server.platform, th.Server.HTTPService(), th.Server.Log())
|
|
|
|
imageURL := "http://mydomain.com/myimage"
|
|
proxiedImageURL := "http://mymattermost.com/api/v4/image?url=http%3A%2F%2Fmydomain.com%2Fmyimage"
|
|
|
|
post := &model.Post{
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "",
|
|
UserId: th.BasicUser.Id,
|
|
}
|
|
|
|
rpost, err := th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err)
|
|
assert.NotEqual(t, "", rpost.Message)
|
|
|
|
post.Id = rpost.Id
|
|
post.Message = ""
|
|
|
|
rpost, err = th.App.UpdatePost(th.Context, post, nil)
|
|
require.Nil(t, err)
|
|
assert.Equal(t, "", rpost.Message)
|
|
})
|
|
|
|
t.Run("Sets PostPropsPreviewedPost when a post is updated to have a permalink as the first link", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
th.AddUserToChannel(th.BasicUser, th.BasicChannel)
|
|
|
|
referencedPost := &model.Post{
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "hello world",
|
|
UserId: th.BasicUser.Id,
|
|
}
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.ServiceSettings.SiteURL = "http://mymattermost.com"
|
|
})
|
|
|
|
th.Context.Session().UserId = th.BasicUser.Id
|
|
|
|
referencedPost, err := th.App.CreatePost(th.Context, referencedPost, th.BasicChannel, model.CreatePostFlags{})
|
|
require.Nil(t, err)
|
|
|
|
permalink := fmt.Sprintf("%s/%s/pl/%s", *th.App.Config().ServiceSettings.SiteURL, th.BasicTeam.Name, referencedPost.Id)
|
|
|
|
channelForTestPost := th.CreateChannel(th.Context, th.BasicTeam)
|
|
testPost := &model.Post{
|
|
ChannelId: channelForTestPost.Id,
|
|
Message: "hello world",
|
|
UserId: th.BasicUser.Id,
|
|
}
|
|
|
|
testPost, err = th.App.CreatePost(th.Context, testPost, channelForTestPost, model.CreatePostFlags{})
|
|
require.Nil(t, err)
|
|
assert.Equal(t, model.StringInterface{}, testPost.GetProps())
|
|
|
|
testPost.Message = permalink
|
|
testPost, err = th.App.UpdatePost(th.Context, testPost, nil)
|
|
require.Nil(t, err)
|
|
assert.Equal(t, model.StringInterface{model.PostPropsPreviewedPost: referencedPost.Id}, testPost.GetProps())
|
|
})
|
|
|
|
t.Run("sanitizes post metadata appropriately", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.ServiceSettings.SiteURL = "http://mymattermost.com"
|
|
})
|
|
|
|
th.AddUserToChannel(th.BasicUser, th.BasicChannel)
|
|
|
|
user1 := th.CreateUser()
|
|
user2 := th.CreateUser()
|
|
directChannel, err := th.App.createDirectChannel(th.Context, user1.Id, user2.Id)
|
|
require.Nil(t, err)
|
|
|
|
th.Context.Session().UserId = th.BasicUser.Id
|
|
|
|
testCases := []struct {
|
|
Description string
|
|
Channel *model.Channel
|
|
Author string
|
|
Length int
|
|
}{
|
|
{
|
|
Description: "removes metadata from post for members who cannot read channel",
|
|
Channel: directChannel,
|
|
Author: user1.Id,
|
|
Length: 0,
|
|
},
|
|
{
|
|
Description: "does not remove metadata from post for members who can read channel",
|
|
Channel: th.BasicChannel,
|
|
Author: th.BasicUser.Id,
|
|
Length: 1,
|
|
},
|
|
}
|
|
|
|
for _, testCase := range testCases {
|
|
t.Run(testCase.Description, func(t *testing.T) {
|
|
referencedPost := &model.Post{
|
|
ChannelId: testCase.Channel.Id,
|
|
Message: "hello world",
|
|
UserId: testCase.Author,
|
|
}
|
|
_, err = th.App.CreatePost(th.Context, referencedPost, testCase.Channel, model.CreatePostFlags{})
|
|
require.Nil(t, err)
|
|
|
|
previewPost := &model.Post{
|
|
ChannelId: th.BasicChannel.Id,
|
|
UserId: th.BasicUser.Id,
|
|
}
|
|
previewPost, err = th.App.CreatePost(th.Context, previewPost, th.BasicChannel, model.CreatePostFlags{})
|
|
require.Nil(t, err)
|
|
|
|
permalink := fmt.Sprintf("%s/%s/pl/%s", *th.App.Config().ServiceSettings.SiteURL, th.BasicTeam.Name, referencedPost.Id)
|
|
previewPost.Message = permalink
|
|
previewPost, err = th.App.UpdatePost(th.Context, previewPost, nil)
|
|
require.Nil(t, err)
|
|
|
|
require.Len(t, previewPost.Metadata.Embeds, testCase.Length)
|
|
})
|
|
}
|
|
})
|
|
|
|
t.Run("cannot update post in restricted DM", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.TeamSettings.RestrictDirectMessage = model.DirectMessageTeam
|
|
})
|
|
|
|
// Create a DM channel between two users who don't share a team
|
|
dmChannel := th.CreateDmChannel(th.BasicUser2)
|
|
|
|
// Ensure the two users do not share a team
|
|
teams, err := th.App.GetTeamsForUser(th.BasicUser.Id)
|
|
require.Nil(t, err)
|
|
for _, team := range teams {
|
|
teamErr := th.App.RemoveUserFromTeam(th.Context, team.Id, th.BasicUser.Id, th.SystemAdminUser.Id)
|
|
require.Nil(t, teamErr)
|
|
}
|
|
teams, err = th.App.GetTeamsForUser(th.BasicUser2.Id)
|
|
require.Nil(t, err)
|
|
for _, team := range teams {
|
|
teamErr := th.App.RemoveUserFromTeam(th.Context, team.Id, th.BasicUser2.Id, th.SystemAdminUser.Id)
|
|
require.Nil(t, teamErr)
|
|
}
|
|
|
|
// Create separate teams for each user
|
|
team1 := th.CreateTeam()
|
|
team2 := th.CreateTeam()
|
|
th.LinkUserToTeam(th.BasicUser, team1)
|
|
th.LinkUserToTeam(th.BasicUser2, team2)
|
|
|
|
// Create a post in the DM channel
|
|
post := &model.Post{
|
|
UserId: th.BasicUser.Id,
|
|
ChannelId: dmChannel.Id,
|
|
Message: "test post",
|
|
}
|
|
post, err = th.App.CreatePost(th.Context, post, dmChannel, model.CreatePostFlags{})
|
|
require.Nil(t, err)
|
|
|
|
// Try to update the post
|
|
post.Message = "updated message"
|
|
_, appErr := th.App.UpdatePost(th.Context, post, model.DefaultUpdatePostOptions())
|
|
require.NotNil(t, appErr)
|
|
require.Equal(t, "api.post.update_post.can_not_update_post_in_restricted_dm.error", appErr.Id)
|
|
require.Equal(t, http.StatusBadRequest, appErr.StatusCode)
|
|
|
|
// Reset config
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.TeamSettings.RestrictDirectMessage = model.DirectMessageAny
|
|
})
|
|
})
|
|
}
|
|
|
|
func TestSearchPostsForUser(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
perPage := 5
|
|
searchTerm := "searchTerm"
|
|
|
|
setup := func(t *testing.T, enableElasticsearch bool) (*TestHelper, []*model.Post) {
|
|
th := Setup(t).InitBasic()
|
|
|
|
posts := make([]*model.Post, 7)
|
|
for i := 0; i < cap(posts); i++ {
|
|
post, err := th.App.CreatePost(th.Context, &model.Post{
|
|
UserId: th.BasicUser.Id,
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: searchTerm,
|
|
}, th.BasicChannel, model.CreatePostFlags{SetOnline: true})
|
|
|
|
require.Nil(t, err)
|
|
|
|
posts[i] = post
|
|
}
|
|
|
|
if enableElasticsearch {
|
|
th.App.Srv().SetLicense(model.NewTestLicense("elastic_search"))
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.ElasticsearchSettings.EnableIndexing = true
|
|
*cfg.ElasticsearchSettings.EnableSearching = true
|
|
})
|
|
} else {
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.ElasticsearchSettings.EnableSearching = false
|
|
})
|
|
}
|
|
|
|
return th, posts
|
|
}
|
|
|
|
t.Run("should return everything as first page of posts from database", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th, posts := setup(t, false)
|
|
defer th.TearDown()
|
|
|
|
page := 0
|
|
|
|
results, err := th.App.SearchPostsForUser(th.Context, searchTerm, th.BasicUser.Id, th.BasicTeam.Id, false, false, 0, page, perPage)
|
|
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, []string{
|
|
posts[6].Id,
|
|
posts[5].Id,
|
|
posts[4].Id,
|
|
posts[3].Id,
|
|
posts[2].Id,
|
|
posts[1].Id,
|
|
posts[0].Id,
|
|
}, results.Order)
|
|
})
|
|
|
|
t.Run("should not return later pages of posts from database", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th, _ := setup(t, false)
|
|
defer th.TearDown()
|
|
|
|
page := 1
|
|
|
|
results, err := th.App.SearchPostsForUser(th.Context, searchTerm, th.BasicUser.Id, th.BasicTeam.Id, false, false, 0, page, perPage)
|
|
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, []string{}, results.Order)
|
|
})
|
|
|
|
t.Run("should return first page of posts from ElasticSearch", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th, posts := setup(t, true)
|
|
defer th.TearDown()
|
|
|
|
page := 0
|
|
resultsPage := []string{
|
|
posts[6].Id,
|
|
posts[5].Id,
|
|
posts[4].Id,
|
|
posts[3].Id,
|
|
posts[2].Id,
|
|
}
|
|
|
|
es := &mocks.SearchEngineInterface{}
|
|
es.On("SearchPosts", mock.Anything, mock.Anything, page, perPage).Return(resultsPage, nil, nil)
|
|
es.On("Start").Return(nil).Maybe()
|
|
es.On("IsActive").Return(true)
|
|
es.On("IsSearchEnabled").Return(true)
|
|
th.App.Srv().Platform().SearchEngine.ElasticsearchEngine = es
|
|
defer func() {
|
|
th.App.Srv().Platform().SearchEngine.ElasticsearchEngine = nil
|
|
}()
|
|
|
|
results, err := th.App.SearchPostsForUser(th.Context, searchTerm, th.BasicUser.Id, th.BasicTeam.Id, false, false, 0, page, perPage)
|
|
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, resultsPage, results.Order)
|
|
es.AssertExpectations(t)
|
|
})
|
|
|
|
t.Run("should return later pages of posts from ElasticSearch", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th, posts := setup(t, true)
|
|
defer th.TearDown()
|
|
|
|
page := 1
|
|
resultsPage := []string{
|
|
posts[1].Id,
|
|
posts[0].Id,
|
|
}
|
|
|
|
es := &mocks.SearchEngineInterface{}
|
|
es.On("SearchPosts", mock.Anything, mock.Anything, page, perPage).Return(resultsPage, nil, nil)
|
|
es.On("Start").Return(nil).Maybe()
|
|
es.On("IsActive").Return(true)
|
|
es.On("IsSearchEnabled").Return(true)
|
|
th.App.Srv().Platform().SearchEngine.ElasticsearchEngine = es
|
|
defer func() {
|
|
th.App.Srv().Platform().SearchEngine.ElasticsearchEngine = nil
|
|
}()
|
|
|
|
results, err := th.App.SearchPostsForUser(th.Context, searchTerm, th.BasicUser.Id, th.BasicTeam.Id, false, false, 0, page, perPage)
|
|
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, resultsPage, results.Order)
|
|
es.AssertExpectations(t)
|
|
})
|
|
|
|
t.Run("should fall back to database if ElasticSearch fails on first page", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th, posts := setup(t, true)
|
|
defer th.TearDown()
|
|
|
|
page := 0
|
|
|
|
es := &mocks.SearchEngineInterface{}
|
|
es.On("SearchPosts", mock.Anything, mock.Anything, page, perPage).Return(nil, nil, &model.AppError{})
|
|
es.On("GetName").Return("mock")
|
|
es.On("Start").Return(nil).Maybe()
|
|
es.On("IsActive").Return(true)
|
|
es.On("IsSearchEnabled").Return(true)
|
|
th.App.Srv().Platform().SearchEngine.ElasticsearchEngine = es
|
|
defer func() {
|
|
th.App.Srv().Platform().SearchEngine.ElasticsearchEngine = nil
|
|
}()
|
|
|
|
results, err := th.App.SearchPostsForUser(th.Context, searchTerm, th.BasicUser.Id, th.BasicTeam.Id, false, false, 0, page, perPage)
|
|
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, []string{
|
|
posts[6].Id,
|
|
posts[5].Id,
|
|
posts[4].Id,
|
|
posts[3].Id,
|
|
posts[2].Id,
|
|
posts[1].Id,
|
|
posts[0].Id,
|
|
}, results.Order)
|
|
es.AssertExpectations(t)
|
|
})
|
|
|
|
t.Run("should return nothing if ElasticSearch fails on later pages", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th, _ := setup(t, true)
|
|
defer th.TearDown()
|
|
|
|
page := 1
|
|
|
|
es := &mocks.SearchEngineInterface{}
|
|
es.On("SearchPosts", mock.Anything, mock.Anything, page, perPage).Return(nil, nil, &model.AppError{})
|
|
es.On("GetName").Return("mock")
|
|
es.On("Start").Return(nil).Maybe()
|
|
es.On("IsActive").Return(true)
|
|
es.On("IsSearchEnabled").Return(true)
|
|
th.App.Srv().Platform().SearchEngine.ElasticsearchEngine = es
|
|
defer func() {
|
|
th.App.Srv().Platform().SearchEngine.ElasticsearchEngine = nil
|
|
}()
|
|
|
|
results, err := th.App.SearchPostsForUser(th.Context, searchTerm, th.BasicUser.Id, th.BasicTeam.Id, false, false, 0, page, perPage)
|
|
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, []string{}, results.Order)
|
|
es.AssertExpectations(t)
|
|
})
|
|
|
|
t.Run("should return the same results if there is a tilde in the channel name", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th, _ := setup(t, false)
|
|
defer th.TearDown()
|
|
|
|
page := 0
|
|
|
|
searchQueryWithPrefix := fmt.Sprintf("in:~%s %s", th.BasicChannel.Name, searchTerm)
|
|
|
|
resultsWithPrefix, err := th.App.SearchPostsForUser(th.Context, searchQueryWithPrefix, th.BasicUser.Id, th.BasicTeam.Id, false, false, 0, page, perPage)
|
|
assert.Nil(t, err)
|
|
assert.Greater(t, len(resultsWithPrefix.PostList.Posts), 0, "searching using a tilde in front of a channel should return results")
|
|
searchQueryWithoutPrefix := fmt.Sprintf("in:%s %s", th.BasicChannel.Name, searchTerm)
|
|
|
|
resultsWithoutPrefix, err := th.App.SearchPostsForUser(th.Context, searchQueryWithoutPrefix, th.BasicUser.Id, th.BasicTeam.Id, false, false, 0, page, perPage)
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, len(resultsWithPrefix.Posts), len(resultsWithoutPrefix.Posts), "searching using a tilde in front of a channel should return the same number of results")
|
|
for k, v := range resultsWithPrefix.Posts {
|
|
assert.Equal(t, v, resultsWithoutPrefix.Posts[k], "post at %s was different", k)
|
|
}
|
|
})
|
|
|
|
t.Run("should return the same results if there is an 'at' in the user", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th, _ := setup(t, false)
|
|
defer th.TearDown()
|
|
|
|
page := 0
|
|
|
|
searchQueryWithPrefix := fmt.Sprintf("from:@%s %s", th.BasicUser.Username, searchTerm)
|
|
|
|
resultsWithPrefix, err := th.App.SearchPostsForUser(th.Context, searchQueryWithPrefix, th.BasicUser.Id, th.BasicTeam.Id, false, false, 0, page, perPage)
|
|
assert.Nil(t, err)
|
|
assert.Greater(t, len(resultsWithPrefix.PostList.Posts), 0, "searching using a 'at' symbol in front of a channel should return results")
|
|
searchQueryWithoutPrefix := fmt.Sprintf("from:@%s %s", th.BasicUser.Username, searchTerm)
|
|
|
|
resultsWithoutPrefix, err := th.App.SearchPostsForUser(th.Context, searchQueryWithoutPrefix, th.BasicUser.Id, th.BasicTeam.Id, false, false, 0, page, perPage)
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, len(resultsWithPrefix.Posts), len(resultsWithoutPrefix.Posts), "searching using an 'at' symbol in front of a channel should return the same number of results")
|
|
for k, v := range resultsWithPrefix.Posts {
|
|
assert.Equal(t, v, resultsWithoutPrefix.Posts[k], "post at %s was different", k)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestCountMentionsFromPost(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
t.Run("should not count posts without mentions", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
user1 := th.BasicUser
|
|
user2 := th.BasicUser2
|
|
|
|
channel := th.CreateChannel(th.Context, th.BasicTeam)
|
|
th.AddUserToChannel(user2, channel)
|
|
|
|
post1, err := th.App.CreatePost(th.Context, &model.Post{
|
|
UserId: user1.Id,
|
|
ChannelId: channel.Id,
|
|
Message: "test",
|
|
}, channel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err)
|
|
_, err = th.App.CreatePost(th.Context, &model.Post{
|
|
UserId: user1.Id,
|
|
ChannelId: channel.Id,
|
|
Message: "test2",
|
|
}, channel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err)
|
|
_, err = th.App.CreatePost(th.Context, &model.Post{
|
|
UserId: user1.Id,
|
|
ChannelId: channel.Id,
|
|
Message: "test3",
|
|
}, channel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err)
|
|
|
|
count, _, _, err := th.App.countMentionsFromPost(th.Context, user2, post1)
|
|
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, 0, count)
|
|
})
|
|
|
|
t.Run("should count keyword mentions", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
user1 := th.BasicUser
|
|
user2 := th.BasicUser2
|
|
|
|
channel := th.CreateChannel(th.Context, th.BasicTeam)
|
|
th.AddUserToChannel(user2, channel)
|
|
|
|
user2.NotifyProps[model.MentionKeysNotifyProp] = "apple"
|
|
|
|
post1, err := th.App.CreatePost(th.Context, &model.Post{
|
|
UserId: user1.Id,
|
|
ChannelId: channel.Id,
|
|
Message: fmt.Sprintf("@%s", user2.Username),
|
|
}, channel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err)
|
|
_, err = th.App.CreatePost(th.Context, &model.Post{
|
|
UserId: user1.Id,
|
|
ChannelId: channel.Id,
|
|
Message: "test2",
|
|
}, channel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err)
|
|
_, err = th.App.CreatePost(th.Context, &model.Post{
|
|
UserId: user1.Id,
|
|
ChannelId: channel.Id,
|
|
Message: "apple",
|
|
}, channel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err)
|
|
|
|
// post1 and post3 should mention the user
|
|
|
|
count, _, _, err := th.App.countMentionsFromPost(th.Context, user2, post1)
|
|
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, 2, count)
|
|
})
|
|
|
|
t.Run("should count channel-wide mentions when enabled", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
user1 := th.BasicUser
|
|
user2 := th.BasicUser2
|
|
|
|
channel := th.CreateChannel(th.Context, th.BasicTeam)
|
|
th.AddUserToChannel(user2, channel)
|
|
|
|
user2.NotifyProps[model.ChannelMentionsNotifyProp] = "true"
|
|
|
|
post1, err := th.App.CreatePost(th.Context, &model.Post{
|
|
UserId: user1.Id,
|
|
ChannelId: channel.Id,
|
|
Message: "test",
|
|
}, channel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err)
|
|
_, err = th.App.CreatePost(th.Context, &model.Post{
|
|
UserId: user1.Id,
|
|
ChannelId: channel.Id,
|
|
Message: "@channel",
|
|
}, channel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err)
|
|
_, err = th.App.CreatePost(th.Context, &model.Post{
|
|
UserId: user1.Id,
|
|
ChannelId: channel.Id,
|
|
Message: "@all",
|
|
}, channel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err)
|
|
|
|
// post2 and post3 should mention the user
|
|
|
|
count, _, _, err := th.App.countMentionsFromPost(th.Context, user2, post1)
|
|
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, 2, count)
|
|
})
|
|
|
|
t.Run("should not count channel-wide mentions when disabled for user", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
user1 := th.BasicUser
|
|
user2 := th.BasicUser2
|
|
|
|
channel := th.CreateChannel(th.Context, th.BasicTeam)
|
|
th.AddUserToChannel(user2, channel)
|
|
|
|
user2.NotifyProps[model.ChannelMentionsNotifyProp] = "false"
|
|
|
|
post1, err := th.App.CreatePost(th.Context, &model.Post{
|
|
UserId: user1.Id,
|
|
ChannelId: channel.Id,
|
|
Message: "test",
|
|
}, channel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err)
|
|
_, err = th.App.CreatePost(th.Context, &model.Post{
|
|
UserId: user1.Id,
|
|
ChannelId: channel.Id,
|
|
Message: "@channel",
|
|
}, channel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err)
|
|
_, err = th.App.CreatePost(th.Context, &model.Post{
|
|
UserId: user1.Id,
|
|
ChannelId: channel.Id,
|
|
Message: "@all",
|
|
}, channel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err)
|
|
|
|
count, _, _, err := th.App.countMentionsFromPost(th.Context, user2, post1)
|
|
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, 0, count)
|
|
})
|
|
|
|
t.Run("should not count channel-wide mentions when disabled for channel", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
user1 := th.BasicUser
|
|
user2 := th.BasicUser2
|
|
|
|
channel := th.CreateChannel(th.Context, th.BasicTeam)
|
|
th.AddUserToChannel(user2, channel)
|
|
|
|
user2.NotifyProps[model.ChannelMentionsNotifyProp] = "true"
|
|
|
|
_, err := th.App.UpdateChannelMemberNotifyProps(th.Context, map[string]string{
|
|
model.IgnoreChannelMentionsNotifyProp: model.IgnoreChannelMentionsOn,
|
|
}, channel.Id, user2.Id)
|
|
require.Nil(t, err)
|
|
|
|
post1, err := th.App.CreatePost(th.Context, &model.Post{
|
|
UserId: user1.Id,
|
|
ChannelId: channel.Id,
|
|
Message: "test",
|
|
}, channel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err)
|
|
_, err = th.App.CreatePost(th.Context, &model.Post{
|
|
UserId: user1.Id,
|
|
ChannelId: channel.Id,
|
|
Message: "@channel",
|
|
}, channel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err)
|
|
_, err = th.App.CreatePost(th.Context, &model.Post{
|
|
UserId: user1.Id,
|
|
ChannelId: channel.Id,
|
|
Message: "@all",
|
|
}, channel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err)
|
|
|
|
count, _, _, err := th.App.countMentionsFromPost(th.Context, user2, post1)
|
|
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, 0, count)
|
|
})
|
|
|
|
t.Run("should count comment mentions when using COMMENTS_NOTIFY_ROOT", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
user1 := th.BasicUser
|
|
user2 := th.BasicUser2
|
|
|
|
channel := th.CreateChannel(th.Context, th.BasicTeam)
|
|
th.AddUserToChannel(user2, channel)
|
|
|
|
user2.NotifyProps[model.CommentsNotifyProp] = model.CommentsNotifyRoot
|
|
|
|
post1, err := th.App.CreatePost(th.Context, &model.Post{
|
|
UserId: user2.Id,
|
|
ChannelId: channel.Id,
|
|
Message: "test",
|
|
}, channel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err)
|
|
_, err = th.App.CreatePost(th.Context, &model.Post{
|
|
UserId: user1.Id,
|
|
ChannelId: channel.Id,
|
|
RootId: post1.Id,
|
|
Message: "test2",
|
|
}, channel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err)
|
|
post3, err := th.App.CreatePost(th.Context, &model.Post{
|
|
UserId: user1.Id,
|
|
ChannelId: channel.Id,
|
|
Message: "test3",
|
|
}, channel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err)
|
|
_, err = th.App.CreatePost(th.Context, &model.Post{
|
|
UserId: user2.Id,
|
|
ChannelId: channel.Id,
|
|
RootId: post3.Id,
|
|
Message: "test4",
|
|
}, channel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err)
|
|
_, err = th.App.CreatePost(th.Context, &model.Post{
|
|
UserId: user1.Id,
|
|
ChannelId: channel.Id,
|
|
RootId: post3.Id,
|
|
Message: "test5",
|
|
}, channel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err)
|
|
|
|
// post2 should mention the user
|
|
|
|
count, _, _, err := th.App.countMentionsFromPost(th.Context, user2, post1)
|
|
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, 1, count)
|
|
})
|
|
|
|
t.Run("should count comment mentions when using COMMENTS_NOTIFY_ANY", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
user1 := th.BasicUser
|
|
user2 := th.BasicUser2
|
|
|
|
channel := th.CreateChannel(th.Context, th.BasicTeam)
|
|
th.AddUserToChannel(user2, channel)
|
|
|
|
user2.NotifyProps[model.CommentsNotifyProp] = model.CommentsNotifyAny
|
|
|
|
post1, err := th.App.CreatePost(th.Context, &model.Post{
|
|
UserId: user2.Id,
|
|
ChannelId: channel.Id,
|
|
Message: "test",
|
|
}, channel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err)
|
|
_, err = th.App.CreatePost(th.Context, &model.Post{
|
|
UserId: user1.Id,
|
|
ChannelId: channel.Id,
|
|
RootId: post1.Id,
|
|
Message: "test2",
|
|
}, channel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err)
|
|
post3, err := th.App.CreatePost(th.Context, &model.Post{
|
|
UserId: user1.Id,
|
|
ChannelId: channel.Id,
|
|
Message: "test3",
|
|
}, channel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err)
|
|
_, err = th.App.CreatePost(th.Context, &model.Post{
|
|
UserId: user2.Id,
|
|
ChannelId: channel.Id,
|
|
RootId: post3.Id,
|
|
Message: "test4",
|
|
}, channel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err)
|
|
_, err = th.App.CreatePost(th.Context, &model.Post{
|
|
UserId: user1.Id,
|
|
ChannelId: channel.Id,
|
|
RootId: post3.Id,
|
|
Message: "test5",
|
|
}, channel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err)
|
|
|
|
// post2 and post5 should mention the user
|
|
|
|
count, _, _, err := th.App.countMentionsFromPost(th.Context, user2, post1)
|
|
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, 2, count)
|
|
})
|
|
|
|
t.Run("should count mentions caused by being added to the channel", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
user1 := th.BasicUser
|
|
user2 := th.BasicUser2
|
|
|
|
channel := th.CreateChannel(th.Context, th.BasicTeam)
|
|
th.AddUserToChannel(user2, channel)
|
|
|
|
post1, err := th.App.CreatePost(th.Context, &model.Post{
|
|
UserId: user1.Id,
|
|
ChannelId: channel.Id,
|
|
Message: "test",
|
|
Type: model.PostTypeAddToChannel,
|
|
Props: map[string]any{
|
|
model.PostPropsAddedUserId: model.NewId(),
|
|
},
|
|
}, channel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err)
|
|
_, err = th.App.CreatePost(th.Context, &model.Post{
|
|
UserId: user1.Id,
|
|
ChannelId: channel.Id,
|
|
Message: "test2",
|
|
Type: model.PostTypeAddToChannel,
|
|
Props: map[string]any{
|
|
model.PostPropsAddedUserId: user2.Id,
|
|
},
|
|
}, channel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err)
|
|
_, err = th.App.CreatePost(th.Context, &model.Post{
|
|
UserId: user1.Id,
|
|
ChannelId: channel.Id,
|
|
Message: "test3",
|
|
Type: model.PostTypeAddToChannel,
|
|
Props: map[string]any{
|
|
model.PostPropsAddedUserId: user2.Id,
|
|
},
|
|
}, channel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err)
|
|
|
|
// should be mentioned by post2 and post3
|
|
|
|
count, _, _, err := th.App.countMentionsFromPost(th.Context, user2, post1)
|
|
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, 2, count)
|
|
})
|
|
|
|
t.Run("should return the number of posts made by the other user for a direct channel", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
user1 := th.BasicUser
|
|
user2 := th.BasicUser2
|
|
|
|
channel, err := th.App.createDirectChannel(th.Context, user1.Id, user2.Id)
|
|
require.Nil(t, err)
|
|
|
|
post1, err := th.App.CreatePost(th.Context, &model.Post{
|
|
UserId: user1.Id,
|
|
ChannelId: channel.Id,
|
|
Message: "test",
|
|
}, channel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err)
|
|
|
|
_, err = th.App.CreatePost(th.Context, &model.Post{
|
|
UserId: user1.Id,
|
|
ChannelId: channel.Id,
|
|
Message: "test2",
|
|
}, channel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err)
|
|
|
|
count, _, _, err := th.App.countMentionsFromPost(th.Context, user2, post1)
|
|
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, 2, count)
|
|
|
|
count, _, _, err = th.App.countMentionsFromPost(th.Context, user1, post1)
|
|
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, 0, count)
|
|
})
|
|
|
|
t.Run("should return the number of posts made by the other user for a group message", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
user1 := th.BasicUser
|
|
user2 := th.BasicUser2
|
|
user3 := th.SystemAdminUser
|
|
|
|
channel, err := th.App.createGroupChannel(th.Context, []string{user1.Id, user2.Id, user3.Id}, user1.Id)
|
|
require.Nil(t, err)
|
|
|
|
post1, err := th.App.CreatePost(th.Context, &model.Post{
|
|
UserId: user1.Id,
|
|
ChannelId: channel.Id,
|
|
Message: "test",
|
|
}, channel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err)
|
|
|
|
_, err = th.App.CreatePost(th.Context, &model.Post{
|
|
UserId: user1.Id,
|
|
ChannelId: channel.Id,
|
|
Message: "test2",
|
|
}, channel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err)
|
|
|
|
_, err = th.App.CreatePost(th.Context, &model.Post{
|
|
UserId: user3.Id,
|
|
ChannelId: channel.Id,
|
|
Message: "test3",
|
|
}, channel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err)
|
|
|
|
count, _, _, err := th.App.countMentionsFromPost(th.Context, user2, post1)
|
|
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, 3, count)
|
|
|
|
count, _, _, err = th.App.countMentionsFromPost(th.Context, user1, post1)
|
|
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, 1, count)
|
|
})
|
|
|
|
t.Run("should not count mentions from the before the given post", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
user1 := th.BasicUser
|
|
user2 := th.BasicUser2
|
|
|
|
channel := th.CreateChannel(th.Context, th.BasicTeam)
|
|
th.AddUserToChannel(user2, channel)
|
|
|
|
_, err := th.App.CreatePost(th.Context, &model.Post{
|
|
UserId: user1.Id,
|
|
ChannelId: channel.Id,
|
|
Message: fmt.Sprintf("@%s", user2.Username),
|
|
}, channel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err)
|
|
post2, err := th.App.CreatePost(th.Context, &model.Post{
|
|
UserId: user1.Id,
|
|
ChannelId: channel.Id,
|
|
Message: "test2",
|
|
}, channel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err)
|
|
_, err = th.App.CreatePost(th.Context, &model.Post{
|
|
UserId: user1.Id,
|
|
ChannelId: channel.Id,
|
|
Message: fmt.Sprintf("@%s", user2.Username),
|
|
}, channel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err)
|
|
|
|
// post1 and post3 should mention the user, but we only count post3
|
|
|
|
count, _, _, err := th.App.countMentionsFromPost(th.Context, user2, post2)
|
|
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, 1, count)
|
|
})
|
|
|
|
t.Run("should not count mentions from the user's own posts", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
user1 := th.BasicUser
|
|
user2 := th.BasicUser2
|
|
|
|
channel := th.CreateChannel(th.Context, th.BasicTeam)
|
|
th.AddUserToChannel(user2, channel)
|
|
|
|
post1, err := th.App.CreatePost(th.Context, &model.Post{
|
|
UserId: user1.Id,
|
|
ChannelId: channel.Id,
|
|
Message: fmt.Sprintf("@%s", user2.Username),
|
|
}, channel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err)
|
|
_, err = th.App.CreatePost(th.Context, &model.Post{
|
|
UserId: user2.Id,
|
|
ChannelId: channel.Id,
|
|
Message: fmt.Sprintf("@%s", user2.Username),
|
|
}, channel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err)
|
|
|
|
// post2 should mention the user
|
|
|
|
count, _, _, err := th.App.countMentionsFromPost(th.Context, user2, post1)
|
|
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, 1, count)
|
|
})
|
|
|
|
t.Run("should include comments made before the given post when counting comment mentions", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
user1 := th.BasicUser
|
|
user2 := th.BasicUser2
|
|
|
|
channel := th.CreateChannel(th.Context, th.BasicTeam)
|
|
th.AddUserToChannel(user2, channel)
|
|
|
|
user2.NotifyProps[model.CommentsNotifyProp] = model.CommentsNotifyAny
|
|
|
|
post1, err := th.App.CreatePost(th.Context, &model.Post{
|
|
UserId: user1.Id,
|
|
ChannelId: channel.Id,
|
|
Message: "test1",
|
|
}, channel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err)
|
|
_, err = th.App.CreatePost(th.Context, &model.Post{
|
|
UserId: user2.Id,
|
|
ChannelId: channel.Id,
|
|
RootId: post1.Id,
|
|
Message: "test2",
|
|
}, channel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err)
|
|
post3, err := th.App.CreatePost(th.Context, &model.Post{
|
|
UserId: user1.Id,
|
|
ChannelId: channel.Id,
|
|
Message: "test3",
|
|
}, channel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err)
|
|
_, err = th.App.CreatePost(th.Context, &model.Post{
|
|
UserId: user1.Id,
|
|
ChannelId: channel.Id,
|
|
RootId: post1.Id,
|
|
Message: "test4",
|
|
}, channel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err)
|
|
|
|
// post4 should mention the user
|
|
|
|
count, _, _, err := th.App.countMentionsFromPost(th.Context, user2, post3)
|
|
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, 1, count)
|
|
})
|
|
|
|
t.Run("should not include comments made before the given post when rootPost is inaccessible", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
// Create an Entry license with post history limits
|
|
license := model.NewTestLicenseSKU(model.LicenseShortSkuMattermostEntry)
|
|
license.Limits = &model.LicenseLimits{
|
|
PostHistory: 10000, // Set some post history limit to enable filtering
|
|
}
|
|
th.App.Srv().SetLicense(license)
|
|
|
|
user1 := th.BasicUser
|
|
user2 := th.BasicUser2
|
|
|
|
channel := th.CreateChannel(th.Context, th.BasicTeam)
|
|
th.AddUserToChannel(user2, channel)
|
|
|
|
user2.NotifyProps[model.CommentsNotifyProp] = model.CommentsNotifyAny
|
|
|
|
post1, err := th.App.CreatePost(th.Context, &model.Post{
|
|
UserId: user1.Id,
|
|
ChannelId: channel.Id,
|
|
Message: "test1",
|
|
}, channel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err)
|
|
_, err = th.App.CreatePost(th.Context, &model.Post{
|
|
UserId: user2.Id,
|
|
ChannelId: channel.Id,
|
|
RootId: post1.Id,
|
|
Message: "test2",
|
|
}, channel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err)
|
|
|
|
time.Sleep(time.Millisecond * 2)
|
|
|
|
post3, err := th.App.CreatePost(th.Context, &model.Post{
|
|
UserId: user1.Id,
|
|
ChannelId: channel.Id,
|
|
Message: "test3",
|
|
}, channel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err)
|
|
_, err = th.App.CreatePost(th.Context, &model.Post{
|
|
UserId: user1.Id,
|
|
ChannelId: channel.Id,
|
|
RootId: post1.Id,
|
|
Message: "test4",
|
|
}, channel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err)
|
|
|
|
// Make posts created before post3 inaccessible
|
|
e := th.App.Srv().Store().System().SaveOrUpdate(&model.System{
|
|
Name: model.SystemLastAccessiblePostTime,
|
|
Value: strconv.FormatInt(post3.CreateAt, 10),
|
|
})
|
|
require.NoError(t, e)
|
|
|
|
// post4 should mention the user, but since post2 is inaccessible due to the cloud plan's limit,
|
|
// post4 does not notify the user.
|
|
|
|
count, _, _, err := th.App.countMentionsFromPost(th.Context, user2, post3)
|
|
|
|
assert.Nil(t, err)
|
|
assert.Zero(t, count)
|
|
})
|
|
|
|
t.Run("should count mentions from the user's webhook posts", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
user1 := th.BasicUser
|
|
user2 := th.BasicUser2
|
|
|
|
channel := th.CreateChannel(th.Context, th.BasicTeam)
|
|
th.AddUserToChannel(user2, channel)
|
|
|
|
post1, err := th.App.CreatePost(th.Context, &model.Post{
|
|
UserId: user1.Id,
|
|
ChannelId: channel.Id,
|
|
Message: "test1",
|
|
}, channel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err)
|
|
_, err = th.App.CreatePost(th.Context, &model.Post{
|
|
UserId: user2.Id,
|
|
ChannelId: channel.Id,
|
|
Message: fmt.Sprintf("@%s", user2.Username),
|
|
}, channel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err)
|
|
_, err = th.App.CreatePost(th.Context, &model.Post{
|
|
UserId: user2.Id,
|
|
ChannelId: channel.Id,
|
|
Message: fmt.Sprintf("@%s", user2.Username),
|
|
Props: map[string]any{
|
|
model.PostPropsFromWebhook: "true",
|
|
},
|
|
}, channel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err)
|
|
|
|
// post3 should mention the user
|
|
|
|
count, _, _, err := th.App.countMentionsFromPost(th.Context, user2, post1)
|
|
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, 1, count)
|
|
})
|
|
|
|
t.Run("should count multiple pages of mentions", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
user1 := th.BasicUser
|
|
user2 := th.BasicUser2
|
|
|
|
channel := th.CreateChannel(th.Context, th.BasicTeam)
|
|
th.AddUserToChannel(user2, channel)
|
|
|
|
numPosts := 215
|
|
|
|
post1, err := th.App.CreatePost(th.Context, &model.Post{
|
|
UserId: user1.Id,
|
|
ChannelId: channel.Id,
|
|
Message: fmt.Sprintf("@%s", user2.Username),
|
|
}, channel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err)
|
|
|
|
for i := 0; i < numPosts-1; i++ {
|
|
_, err = th.App.CreatePost(th.Context, &model.Post{
|
|
UserId: user1.Id,
|
|
ChannelId: channel.Id,
|
|
Message: fmt.Sprintf("@%s", user2.Username),
|
|
}, channel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err)
|
|
}
|
|
|
|
// Every post should mention the user
|
|
|
|
count, _, _, err := th.App.countMentionsFromPost(th.Context, user2, post1)
|
|
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, numPosts, count)
|
|
})
|
|
|
|
t.Run("should count urgent mentions", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.ServiceSettings.PostPriority = true
|
|
})
|
|
|
|
user1 := th.BasicUser
|
|
user2 := th.BasicUser2
|
|
|
|
channel := th.CreateChannel(th.Context, th.BasicTeam)
|
|
th.AddUserToChannel(user2, channel)
|
|
|
|
user2.NotifyProps[model.MentionKeysNotifyProp] = "apple"
|
|
|
|
post1, err := th.App.CreatePost(th.Context, &model.Post{
|
|
UserId: user1.Id,
|
|
ChannelId: channel.Id,
|
|
Message: fmt.Sprintf("@%s", user2.Username),
|
|
Metadata: &model.PostMetadata{
|
|
Priority: &model.PostPriority{
|
|
Priority: model.NewPointer(model.PostPriorityUrgent),
|
|
},
|
|
},
|
|
}, channel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err)
|
|
|
|
_, err = th.App.CreatePost(th.Context, &model.Post{
|
|
UserId: user1.Id,
|
|
ChannelId: channel.Id,
|
|
Message: fmt.Sprintf("@%s", user2.Username),
|
|
}, channel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err)
|
|
|
|
_, err = th.App.CreatePost(th.Context, &model.Post{
|
|
UserId: user1.Id,
|
|
ChannelId: channel.Id,
|
|
Message: "apple",
|
|
Metadata: &model.PostMetadata{
|
|
Priority: &model.PostPriority{
|
|
Priority: model.NewPointer(model.PostPriorityUrgent),
|
|
},
|
|
},
|
|
}, channel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err)
|
|
|
|
// all posts mention the user but only post1, post3 are urgent
|
|
|
|
_, _, count, err := th.App.countMentionsFromPost(th.Context, user2, post1)
|
|
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, 2, count)
|
|
})
|
|
}
|
|
|
|
func TestFillInPostProps(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
t.Run("should not add disable group highlight to post props for user with group mention permissions", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
th.App.Srv().SetLicense(model.NewTestLicense("ldap"))
|
|
|
|
user1 := th.BasicUser
|
|
|
|
channel := th.CreateChannel(th.Context, th.BasicTeam)
|
|
|
|
post1, err := th.App.CreatePost(th.Context, &model.Post{
|
|
UserId: user1.Id,
|
|
ChannelId: channel.Id,
|
|
Message: "test123123 @group1 @group2 blah blah blah",
|
|
}, channel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err)
|
|
|
|
err = th.App.FillInPostProps(th.Context, post1, channel)
|
|
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, post1.Props, model.StringInterface{})
|
|
})
|
|
|
|
t.Run("should not add disable group highlight to post props for app without license", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
id := model.NewId()
|
|
guest := &model.User{
|
|
Email: "success+" + id + "@simulator.amazonses.com",
|
|
Username: "un_" + id,
|
|
Nickname: "nn_" + id,
|
|
Password: "Password1",
|
|
EmailVerified: true,
|
|
}
|
|
guest, err := th.App.CreateGuest(th.Context, guest)
|
|
require.Nil(t, err)
|
|
th.LinkUserToTeam(guest, th.BasicTeam)
|
|
|
|
channel := th.CreateChannel(th.Context, th.BasicTeam)
|
|
th.AddUserToChannel(guest, channel)
|
|
|
|
post1, err := th.App.CreatePost(th.Context, &model.Post{
|
|
UserId: guest.Id,
|
|
ChannelId: channel.Id,
|
|
Message: "test123123 @group1 @group2 blah blah blah",
|
|
}, channel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err)
|
|
|
|
err = th.App.FillInPostProps(th.Context, post1, channel)
|
|
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, post1.Props, model.StringInterface{})
|
|
})
|
|
|
|
t.Run("should add disable group highlight to post props for guest user", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
th.App.Srv().SetLicense(model.NewTestLicense("ldap"))
|
|
|
|
id := model.NewId()
|
|
guest := &model.User{
|
|
Email: "success+" + id + "@simulator.amazonses.com",
|
|
Username: "un_" + id,
|
|
Nickname: "nn_" + id,
|
|
Password: "Password1",
|
|
EmailVerified: true,
|
|
}
|
|
guest, err := th.App.CreateGuest(th.Context, guest)
|
|
require.Nil(t, err)
|
|
th.LinkUserToTeam(guest, th.BasicTeam)
|
|
|
|
channel := th.CreateChannel(th.Context, th.BasicTeam)
|
|
th.AddUserToChannel(guest, channel)
|
|
|
|
post1, err := th.App.CreatePost(th.Context, &model.Post{
|
|
UserId: guest.Id,
|
|
ChannelId: channel.Id,
|
|
Message: "test123123 @group1 @group2 blah blah blah",
|
|
}, channel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err)
|
|
|
|
err = th.App.FillInPostProps(th.Context, post1, channel)
|
|
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, post1.Props, model.StringInterface{"disable_group_highlight": true})
|
|
})
|
|
|
|
t.Run("should set AI-generated username when user ID is post creator", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
user1 := th.BasicUser
|
|
channel := th.CreateChannel(th.Context, th.BasicTeam)
|
|
|
|
post1 := &model.Post{
|
|
UserId: user1.Id,
|
|
ChannelId: channel.Id,
|
|
Message: "test post",
|
|
}
|
|
post1.AddProp(model.PostPropsAIGeneratedByUserID, user1.Id)
|
|
|
|
err := th.App.FillInPostProps(th.Context, post1, channel)
|
|
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, user1.Id, post1.GetProp(model.PostPropsAIGeneratedByUserID))
|
|
assert.Equal(t, user1.Username, post1.GetProp(model.PostPropsAIGeneratedByUsername))
|
|
})
|
|
|
|
t.Run("should set AI-generated username when user ID is a bot", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
user1 := th.BasicUser
|
|
channel := th.CreateChannel(th.Context, th.BasicTeam)
|
|
|
|
// Create a bot
|
|
bot, appErr := th.App.CreateBot(th.Context, &model.Bot{
|
|
Username: "testbot",
|
|
Description: "test bot",
|
|
OwnerId: user1.Id,
|
|
})
|
|
require.Nil(t, appErr)
|
|
|
|
post1 := &model.Post{
|
|
UserId: user1.Id,
|
|
ChannelId: channel.Id,
|
|
Message: "test post generated by bot",
|
|
}
|
|
post1.AddProp(model.PostPropsAIGeneratedByUserID, bot.UserId)
|
|
|
|
err := th.App.FillInPostProps(th.Context, post1, channel)
|
|
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, bot.UserId, post1.GetProp(model.PostPropsAIGeneratedByUserID))
|
|
assert.Equal(t, bot.Username, post1.GetProp(model.PostPropsAIGeneratedByUsername))
|
|
})
|
|
|
|
t.Run("should return error when user ID is a different non-bot user", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
user1 := th.BasicUser
|
|
user2 := th.BasicUser2
|
|
channel := th.CreateChannel(th.Context, th.BasicTeam)
|
|
|
|
post1 := &model.Post{
|
|
UserId: user1.Id,
|
|
ChannelId: channel.Id,
|
|
Message: "test post",
|
|
}
|
|
// Try to set AI-generated user ID to a different user (not post creator, not bot)
|
|
post1.AddProp(model.PostPropsAIGeneratedByUserID, user2.Id)
|
|
|
|
err := th.App.FillInPostProps(th.Context, post1, channel)
|
|
|
|
// Should return an error since user2 is neither the post creator nor a bot
|
|
assert.NotNil(t, err)
|
|
assert.Equal(t, "FillInPostProps", err.Where)
|
|
assert.Equal(t, http.StatusBadRequest, err.StatusCode)
|
|
})
|
|
|
|
t.Run("should remove AI-generated prop when user ID does not exist", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
user1 := th.BasicUser
|
|
channel := th.CreateChannel(th.Context, th.BasicTeam)
|
|
|
|
post1 := &model.Post{
|
|
UserId: user1.Id,
|
|
ChannelId: channel.Id,
|
|
Message: "test post",
|
|
}
|
|
// Set AI-generated user ID to a non-existent user
|
|
post1.AddProp(model.PostPropsAIGeneratedByUserID, model.NewId())
|
|
|
|
err := th.App.FillInPostProps(th.Context, post1, channel)
|
|
|
|
assert.Nil(t, err)
|
|
// The property should be removed since the user doesn't exist
|
|
assert.Nil(t, post1.GetProp(model.PostPropsAIGeneratedByUserID))
|
|
assert.Nil(t, post1.GetProp(model.PostPropsAIGeneratedByUsername))
|
|
})
|
|
}
|
|
|
|
func TestThreadMembership(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
t.Run("should update memberships for conversation participants", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.ServiceSettings.ThreadAutoFollow = true
|
|
*cfg.ServiceSettings.CollapsedThreads = model.CollapsedThreadsDefaultOn
|
|
})
|
|
|
|
user1 := th.BasicUser
|
|
user2 := th.BasicUser2
|
|
|
|
channel := th.CreateChannel(th.Context, th.BasicTeam)
|
|
th.AddUserToChannel(user2, channel)
|
|
|
|
postRoot, err := th.App.CreatePost(th.Context, &model.Post{
|
|
UserId: user1.Id,
|
|
ChannelId: channel.Id,
|
|
Message: "root post",
|
|
}, channel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err)
|
|
|
|
_, err = th.App.CreatePost(th.Context, &model.Post{
|
|
UserId: user1.Id,
|
|
ChannelId: channel.Id,
|
|
RootId: postRoot.Id,
|
|
Message: fmt.Sprintf("@%s", user2.Username),
|
|
}, channel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err)
|
|
|
|
// first user should now be part of the thread since they replied to a post
|
|
memberships, err2 := th.App.GetThreadMembershipsForUser(user1.Id, th.BasicTeam.Id)
|
|
require.NoError(t, err2)
|
|
require.Len(t, memberships, 1)
|
|
// second user should also be part of a thread since they were mentioned
|
|
memberships, err2 = th.App.GetThreadMembershipsForUser(user2.Id, th.BasicTeam.Id)
|
|
require.NoError(t, err2)
|
|
require.Len(t, memberships, 1)
|
|
|
|
post2, err := th.App.CreatePost(th.Context, &model.Post{
|
|
UserId: user2.Id,
|
|
ChannelId: channel.Id,
|
|
Message: "second post",
|
|
}, channel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err)
|
|
|
|
_, err = th.App.CreatePost(th.Context, &model.Post{
|
|
UserId: user2.Id,
|
|
ChannelId: channel.Id,
|
|
RootId: post2.Id,
|
|
Message: fmt.Sprintf("@%s", user1.Username),
|
|
}, channel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err)
|
|
|
|
// first user should now be part of two threads
|
|
memberships, err2 = th.App.GetThreadMembershipsForUser(user1.Id, th.BasicTeam.Id)
|
|
require.NoError(t, err2)
|
|
require.Len(t, memberships, 2)
|
|
})
|
|
}
|
|
|
|
func TestFollowThreadSkipsParticipants(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.ServiceSettings.ThreadAutoFollow = true
|
|
*cfg.ServiceSettings.CollapsedThreads = model.CollapsedThreadsDefaultOn
|
|
})
|
|
|
|
channel := th.BasicChannel
|
|
user := th.BasicUser
|
|
user2 := th.BasicUser2
|
|
sysadmin := th.SystemAdminUser
|
|
|
|
appErr := th.App.JoinChannel(th.Context, channel, user.Id)
|
|
require.Nil(t, appErr)
|
|
appErr = th.App.JoinChannel(th.Context, channel, user2.Id)
|
|
require.Nil(t, appErr)
|
|
_, appErr = th.App.JoinUserToTeam(th.Context, th.BasicTeam, sysadmin, sysadmin.Id)
|
|
require.Nil(t, appErr)
|
|
appErr = th.App.JoinChannel(th.Context, channel, sysadmin.Id)
|
|
require.Nil(t, appErr)
|
|
|
|
p1, appErr := th.App.CreatePost(th.Context, &model.Post{UserId: user.Id, ChannelId: channel.Id, Message: "Hi @" + sysadmin.Username}, channel, model.CreatePostFlags{})
|
|
require.Nil(t, appErr)
|
|
_, appErr = th.App.CreatePost(th.Context, &model.Post{RootId: p1.Id, UserId: user.Id, ChannelId: channel.Id, Message: "Hola"}, channel, model.CreatePostFlags{})
|
|
require.Nil(t, appErr)
|
|
|
|
threadMembership, appErr := th.App.GetThreadMembershipForUser(user.Id, p1.Id)
|
|
require.Nil(t, appErr)
|
|
thread, appErr := th.App.GetThreadForUser(th.Context, threadMembership, false)
|
|
require.Nil(t, appErr)
|
|
require.Len(t, thread.Participants, 1) // length should be 1, the original poster, since sysadmin was just mentioned but didn't post
|
|
|
|
_, appErr = th.App.CreatePost(th.Context, &model.Post{RootId: p1.Id, UserId: sysadmin.Id, ChannelId: channel.Id, Message: "sysadmin reply"}, channel, model.CreatePostFlags{})
|
|
require.Nil(t, appErr)
|
|
|
|
threadMembership, appErr = th.App.GetThreadMembershipForUser(user.Id, p1.Id)
|
|
require.Nil(t, appErr)
|
|
thread, appErr = th.App.GetThreadForUser(th.Context, threadMembership, false)
|
|
require.Nil(t, appErr)
|
|
require.Len(t, thread.Participants, 2) // length should be 2, the original poster and sysadmin, since sysadmin participated now
|
|
|
|
// another user follows the thread
|
|
appErr = th.App.UpdateThreadFollowForUser(user2.Id, th.BasicTeam.Id, p1.Id, true)
|
|
require.Nil(t, appErr)
|
|
|
|
threadMembership, appErr = th.App.GetThreadMembershipForUser(user2.Id, p1.Id)
|
|
require.Nil(t, appErr)
|
|
thread, appErr = th.App.GetThreadForUser(th.Context, threadMembership, false)
|
|
require.Nil(t, appErr)
|
|
require.Len(t, thread.Participants, 2) // length should be 2, since follow shouldn't update participant list, only user1 and sysadmin are participants
|
|
for _, p := range thread.Participants {
|
|
require.True(t, p.Id == sysadmin.Id || p.Id == user.Id)
|
|
}
|
|
|
|
oldID := threadMembership.PostId
|
|
threadMembership.PostId = "notfound"
|
|
_, appErr = th.App.GetThreadForUser(th.Context, threadMembership, false)
|
|
require.NotNil(t, appErr)
|
|
assert.Equal(t, http.StatusNotFound, appErr.StatusCode)
|
|
|
|
threadMembership.Following = false
|
|
threadMembership.PostId = oldID
|
|
_, appErr = th.App.GetThreadForUser(th.Context, threadMembership, false)
|
|
require.NotNil(t, appErr)
|
|
assert.Equal(t, http.StatusNotFound, appErr.StatusCode)
|
|
}
|
|
|
|
func TestAutofollowBasedOnRootPost(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.ServiceSettings.ThreadAutoFollow = true
|
|
*cfg.ServiceSettings.CollapsedThreads = model.CollapsedThreadsDefaultOn
|
|
})
|
|
|
|
channel := th.BasicChannel
|
|
user := th.BasicUser
|
|
user2 := th.BasicUser2
|
|
appErr := th.App.JoinChannel(th.Context, channel, user.Id)
|
|
require.Nil(t, appErr)
|
|
appErr = th.App.JoinChannel(th.Context, channel, user2.Id)
|
|
require.Nil(t, appErr)
|
|
p1, appErr := th.App.CreatePost(th.Context, &model.Post{UserId: user.Id, ChannelId: channel.Id, Message: "Hi @" + user2.Username}, channel, model.CreatePostFlags{})
|
|
require.Nil(t, appErr)
|
|
m, err := th.App.GetThreadMembershipsForUser(user2.Id, th.BasicTeam.Id)
|
|
require.NoError(t, err)
|
|
require.Len(t, m, 0)
|
|
_, appErr = th.App.CreatePost(th.Context, &model.Post{RootId: p1.Id, UserId: user.Id, ChannelId: channel.Id, Message: "Hola"}, channel, model.CreatePostFlags{})
|
|
require.Nil(t, appErr)
|
|
m, err = th.App.GetThreadMembershipsForUser(user2.Id, th.BasicTeam.Id)
|
|
require.NoError(t, err)
|
|
require.Len(t, m, 1)
|
|
}
|
|
|
|
func TestViewChannelShouldNotUpdateThreads(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.ServiceSettings.ThreadAutoFollow = true
|
|
*cfg.ServiceSettings.CollapsedThreads = model.CollapsedThreadsDefaultOn
|
|
})
|
|
|
|
channel := th.BasicChannel
|
|
user := th.BasicUser
|
|
user2 := th.BasicUser2
|
|
appErr := th.App.JoinChannel(th.Context, channel, user.Id)
|
|
require.Nil(t, appErr)
|
|
appErr = th.App.JoinChannel(th.Context, channel, user2.Id)
|
|
require.Nil(t, appErr)
|
|
p1, appErr := th.App.CreatePost(th.Context, &model.Post{UserId: user.Id, ChannelId: channel.Id, Message: "Hi @" + user2.Username}, channel, model.CreatePostFlags{})
|
|
require.Nil(t, appErr)
|
|
_, appErr = th.App.CreatePost(th.Context, &model.Post{RootId: p1.Id, UserId: user.Id, ChannelId: channel.Id, Message: "Hola"}, channel, model.CreatePostFlags{})
|
|
require.Nil(t, appErr)
|
|
m, err := th.App.GetThreadMembershipsForUser(user2.Id, th.BasicTeam.Id)
|
|
require.NoError(t, err)
|
|
|
|
_, appErr = th.App.ViewChannel(th.Context, &model.ChannelView{
|
|
ChannelId: channel.Id,
|
|
PrevChannelId: "",
|
|
}, user2.Id, "", true)
|
|
require.Nil(t, appErr)
|
|
|
|
m1, err := th.App.GetThreadMembershipsForUser(user2.Id, th.BasicTeam.Id)
|
|
require.NoError(t, err)
|
|
require.Equal(t, m[0].LastViewed, m1[0].LastViewed) // opening the channel shouldn't update threads
|
|
}
|
|
|
|
func TestCollapsedThreadFetch(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.ServiceSettings.ThreadAutoFollow = true
|
|
*cfg.ServiceSettings.CollapsedThreads = model.CollapsedThreadsDefaultOn
|
|
})
|
|
user1 := th.BasicUser
|
|
user2 := th.BasicUser2
|
|
|
|
t.Run("should only return root posts, enriched", func(t *testing.T) {
|
|
channel := th.CreateChannel(th.Context, th.BasicTeam)
|
|
th.AddUserToChannel(user2, channel)
|
|
defer func() {
|
|
appErr := th.App.DeleteChannel(th.Context, channel, user1.Id)
|
|
require.Nil(t, appErr)
|
|
}()
|
|
|
|
postRoot, appErr := th.App.CreatePost(th.Context, &model.Post{
|
|
UserId: user1.Id,
|
|
ChannelId: channel.Id,
|
|
Message: "root post",
|
|
}, channel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, appErr)
|
|
|
|
_, appErr = th.App.CreatePost(th.Context, &model.Post{
|
|
UserId: user1.Id,
|
|
ChannelId: channel.Id,
|
|
RootId: postRoot.Id,
|
|
Message: fmt.Sprintf("@%s", user2.Username),
|
|
}, channel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, appErr)
|
|
thread, err := th.App.Srv().Store().Thread().Get(postRoot.Id)
|
|
require.NoError(t, err)
|
|
require.Len(t, thread.Participants, 1)
|
|
_, appErr = th.App.MarkChannelAsUnreadFromPost(th.Context, postRoot.Id, user1.Id, true)
|
|
require.Nil(t, appErr)
|
|
l, appErr := th.App.GetPostsForChannelAroundLastUnread(th.Context, channel.Id, user1.Id, 10, 10, true, true, false)
|
|
require.Nil(t, appErr)
|
|
require.Len(t, l.Order, 1)
|
|
require.EqualValues(t, 1, l.Posts[postRoot.Id].ReplyCount)
|
|
require.EqualValues(t, []string{user1.Id}, []string{l.Posts[postRoot.Id].Participants[0].Id})
|
|
require.Empty(t, l.Posts[postRoot.Id].Participants[0].Email)
|
|
require.NotZero(t, l.Posts[postRoot.Id].LastReplyAt)
|
|
require.True(t, *l.Posts[postRoot.Id].IsFollowing)
|
|
|
|
// try extended fetch
|
|
l, appErr = th.App.GetPostsForChannelAroundLastUnread(th.Context, channel.Id, user1.Id, 10, 10, true, true, true)
|
|
require.Nil(t, appErr)
|
|
require.Len(t, l.Order, 1)
|
|
require.NotEmpty(t, l.Posts[postRoot.Id].Participants[0].Email)
|
|
})
|
|
|
|
t.Run("Should not panic on unexpected db error", func(t *testing.T) {
|
|
channel := th.CreateChannel(th.Context, th.BasicTeam)
|
|
th.AddUserToChannel(user2, channel)
|
|
defer func() {
|
|
appErr := th.App.DeleteChannel(th.Context, channel, user1.Id)
|
|
require.Nil(t, appErr)
|
|
}()
|
|
|
|
postRoot, err := th.App.CreatePost(th.Context, &model.Post{
|
|
UserId: user1.Id,
|
|
ChannelId: channel.Id,
|
|
Message: "root post",
|
|
}, channel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err)
|
|
|
|
// we introduce a race to trigger an unexpected error from the db side.
|
|
var wg sync.WaitGroup
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
err := th.Server.Store().Post().PermanentDeleteByUser(th.Context, user1.Id)
|
|
require.NoError(t, err)
|
|
}()
|
|
|
|
require.NotPanics(t, func() {
|
|
// We're only testing that this doesn't panic, not checking the error
|
|
// #nosec G104 - purposely not checking error as we're in a NotPanics block
|
|
_, _ = th.App.CreatePost(th.Context, &model.Post{
|
|
UserId: user1.Id,
|
|
ChannelId: channel.Id,
|
|
RootId: postRoot.Id,
|
|
Message: fmt.Sprintf("@%s", user2.Username),
|
|
}, channel, model.CreatePostFlags{SetOnline: true})
|
|
})
|
|
|
|
wg.Wait()
|
|
})
|
|
|
|
t.Run("should sanitize participant data", func(t *testing.T) {
|
|
id := model.NewId()
|
|
user3, appErr := th.App.CreateUser(th.Context, &model.User{
|
|
Email: "success+" + id + "@simulator.amazonses.com",
|
|
Username: "un_" + id,
|
|
Nickname: "nn_" + id,
|
|
AuthData: model.NewPointer("bobbytables"),
|
|
AuthService: "saml",
|
|
EmailVerified: true,
|
|
})
|
|
require.Nil(t, appErr)
|
|
defer func() {
|
|
appErr = th.App.PermanentDeleteUser(th.Context, user3)
|
|
require.Nil(t, appErr)
|
|
}()
|
|
|
|
channel := th.CreateChannel(th.Context, th.BasicTeam)
|
|
defer func() {
|
|
appErr = th.App.DeleteChannel(th.Context, channel, user1.Id)
|
|
require.Nil(t, appErr)
|
|
}()
|
|
|
|
th.LinkUserToTeam(user3, th.BasicTeam)
|
|
th.AddUserToChannel(user3, channel)
|
|
|
|
postRoot, appErr := th.App.CreatePost(th.Context, &model.Post{
|
|
UserId: user1.Id,
|
|
ChannelId: channel.Id,
|
|
Message: "root post",
|
|
}, channel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, appErr)
|
|
|
|
_, appErr = th.App.CreatePost(th.Context, &model.Post{
|
|
UserId: user3.Id,
|
|
ChannelId: channel.Id,
|
|
RootId: postRoot.Id,
|
|
Message: "reply",
|
|
}, channel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, appErr)
|
|
thread, err := th.App.Srv().Store().Thread().Get(postRoot.Id)
|
|
require.NoError(t, err)
|
|
require.Len(t, thread.Participants, 1)
|
|
|
|
// extended fetch posts page
|
|
l, appErr := th.App.GetPostsPage(th.Context, model.GetPostsOptions{
|
|
UserId: user1.Id,
|
|
ChannelId: channel.Id,
|
|
PerPage: int(10),
|
|
SkipFetchThreads: false,
|
|
CollapsedThreads: true,
|
|
CollapsedThreadsExtended: true,
|
|
})
|
|
require.Nil(t, appErr)
|
|
require.Len(t, l.Order, 1)
|
|
require.NotEmpty(t, l.Posts[postRoot.Id].Participants[0].Email)
|
|
require.Empty(t, l.Posts[postRoot.Id].Participants[0].AuthData)
|
|
|
|
_, appErr = th.App.MarkChannelAsUnreadFromPost(th.Context, postRoot.Id, user1.Id, true)
|
|
require.Nil(t, appErr)
|
|
|
|
// extended fetch posts around
|
|
l, appErr = th.App.GetPostsForChannelAroundLastUnread(th.Context, channel.Id, user1.Id, 10, 10, true, true, true)
|
|
require.Nil(t, appErr)
|
|
require.Len(t, l.Order, 1)
|
|
require.NotEmpty(t, l.Posts[postRoot.Id].Participants[0].Email)
|
|
require.Empty(t, l.Posts[postRoot.Id].Participants[0].AuthData)
|
|
|
|
// extended fetch post thread
|
|
opts := model.GetPostsOptions{
|
|
SkipFetchThreads: false,
|
|
CollapsedThreads: true,
|
|
CollapsedThreadsExtended: true,
|
|
}
|
|
|
|
l, appErr = th.App.GetPostThread(th.Context, postRoot.Id, opts, user1.Id)
|
|
require.Nil(t, appErr)
|
|
require.Len(t, l.Order, 2)
|
|
require.NotEmpty(t, l.Posts[postRoot.Id].Participants[0].Email)
|
|
require.Empty(t, l.Posts[postRoot.Id].Participants[0].AuthData)
|
|
})
|
|
}
|
|
|
|
func TestSharedChannelSyncForPostActions(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
t.Run("creating a post in a shared channel performs a content sync when sync service is running on that node", func(t *testing.T) {
|
|
th := setupSharedChannels(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
sharedChannelService := NewMockSharedChannelService(th.Server.GetSharedChannelSyncService())
|
|
th.Server.SetSharedChannelSyncService(sharedChannelService)
|
|
testCluster := &testlib.FakeClusterInterface{}
|
|
th.Server.Platform().SetCluster(testCluster)
|
|
|
|
user := th.BasicUser
|
|
|
|
channel := th.CreateChannel(th.Context, th.BasicTeam, WithShared(true))
|
|
|
|
_, err := th.App.CreatePost(th.Context, &model.Post{
|
|
UserId: user.Id,
|
|
ChannelId: channel.Id,
|
|
Message: "Hello folks",
|
|
}, channel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err, "Creating a post should not error")
|
|
|
|
require.Len(t, sharedChannelService.channelNotifications, 1)
|
|
assert.Equal(t, channel.Id, sharedChannelService.channelNotifications[0])
|
|
})
|
|
|
|
t.Run("updating a post in a shared channel performs a content sync when sync service is running on that node", func(t *testing.T) {
|
|
th := setupSharedChannels(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
sharedChannelService := NewMockSharedChannelService(th.Server.GetSharedChannelSyncService())
|
|
th.Server.SetSharedChannelSyncService(sharedChannelService)
|
|
testCluster := &testlib.FakeClusterInterface{}
|
|
th.Server.Platform().SetCluster(testCluster)
|
|
|
|
user := th.BasicUser
|
|
|
|
channel := th.CreateChannel(th.Context, th.BasicTeam, WithShared(true))
|
|
|
|
post, err := th.App.CreatePost(th.Context, &model.Post{
|
|
UserId: user.Id,
|
|
ChannelId: channel.Id,
|
|
Message: "Hello folks",
|
|
}, channel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err, "Creating a post should not error")
|
|
|
|
_, err = th.App.UpdatePost(th.Context, post, &model.UpdatePostOptions{SafeUpdate: true})
|
|
require.Nil(t, err, "Updating a post should not error")
|
|
|
|
require.Len(t, sharedChannelService.channelNotifications, 2)
|
|
assert.Equal(t, channel.Id, sharedChannelService.channelNotifications[0])
|
|
assert.Equal(t, channel.Id, sharedChannelService.channelNotifications[1])
|
|
})
|
|
|
|
t.Run("deleting a post in a shared channel performs a content sync when sync service is running on that node", func(t *testing.T) {
|
|
th := setupSharedChannels(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
sharedChannelService := NewMockSharedChannelService(th.Server.GetSharedChannelSyncService())
|
|
th.Server.SetSharedChannelSyncService(sharedChannelService)
|
|
testCluster := &testlib.FakeClusterInterface{}
|
|
th.Server.Platform().SetCluster(testCluster)
|
|
|
|
user := th.BasicUser
|
|
|
|
channel := th.CreateChannel(th.Context, th.BasicTeam, WithShared(true))
|
|
|
|
post, err := th.App.CreatePost(th.Context, &model.Post{
|
|
UserId: user.Id,
|
|
ChannelId: channel.Id,
|
|
Message: "Hello folks",
|
|
}, channel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err, "Creating a post should not error")
|
|
|
|
_, err = th.App.DeletePost(th.Context, post.Id, user.Id)
|
|
require.Nil(t, err, "Deleting a post should not error")
|
|
|
|
// one creation and two deletes
|
|
require.Len(t, sharedChannelService.channelNotifications, 3)
|
|
assert.Equal(t, channel.Id, sharedChannelService.channelNotifications[0])
|
|
assert.Equal(t, channel.Id, sharedChannelService.channelNotifications[1])
|
|
assert.Equal(t, channel.Id, sharedChannelService.channelNotifications[2])
|
|
})
|
|
}
|
|
|
|
func TestAutofollowOnPostingAfterUnfollow(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.ServiceSettings.ThreadAutoFollow = true
|
|
*cfg.ServiceSettings.CollapsedThreads = model.CollapsedThreadsDefaultOn
|
|
})
|
|
|
|
channel := th.BasicChannel
|
|
user := th.BasicUser
|
|
user2 := th.BasicUser2
|
|
appErr := th.App.JoinChannel(th.Context, channel, user.Id)
|
|
require.Nil(t, appErr)
|
|
appErr = th.App.JoinChannel(th.Context, channel, user2.Id)
|
|
require.Nil(t, appErr)
|
|
p1, appErr := th.App.CreatePost(th.Context, &model.Post{UserId: user.Id, ChannelId: channel.Id, Message: "Hi @" + user2.Username}, channel, model.CreatePostFlags{})
|
|
require.Nil(t, appErr)
|
|
_, appErr = th.App.CreatePost(th.Context, &model.Post{RootId: p1.Id, UserId: user2.Id, ChannelId: channel.Id, Message: "Hola"}, channel, model.CreatePostFlags{})
|
|
require.Nil(t, appErr)
|
|
_, appErr = th.App.CreatePost(th.Context, &model.Post{RootId: p1.Id, UserId: user.Id, ChannelId: channel.Id, Message: "reply"}, channel, model.CreatePostFlags{})
|
|
require.Nil(t, appErr)
|
|
|
|
// unfollow thread
|
|
m, err := th.App.Srv().Store().Thread().MaintainMembership(user.Id, p1.Id, store.ThreadMembershipOpts{
|
|
Following: false,
|
|
UpdateFollowing: true,
|
|
})
|
|
require.NoError(t, err)
|
|
require.False(t, m.Following)
|
|
|
|
_, appErr = th.App.CreatePost(th.Context, &model.Post{RootId: p1.Id, UserId: user.Id, ChannelId: channel.Id, Message: "another reply"}, channel, model.CreatePostFlags{})
|
|
require.Nil(t, appErr)
|
|
|
|
// User should be following thread after posting in it, even after previously
|
|
// unfollowing it, if ThreadAutoFollow is true
|
|
m, appErr = th.App.GetThreadMembershipForUser(user.Id, p1.Id)
|
|
require.Nil(t, appErr)
|
|
require.True(t, m.Following)
|
|
}
|
|
|
|
func TestGetPostIfAuthorized(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
t.Run("Private channel", func(t *testing.T) {
|
|
privateChannel := th.CreatePrivateChannel(th.Context, th.BasicTeam)
|
|
post, err := th.App.CreatePost(th.Context, &model.Post{UserId: th.BasicUser.Id, ChannelId: privateChannel.Id, Message: "Hello"}, privateChannel, model.CreatePostFlags{})
|
|
require.Nil(t, err)
|
|
require.NotNil(t, post)
|
|
|
|
session1, err := th.App.CreateSession(th.Context, &model.Session{UserId: th.BasicUser.Id, Props: model.StringMap{}})
|
|
require.Nil(t, err)
|
|
require.NotNil(t, session1)
|
|
|
|
session2, err := th.App.CreateSession(th.Context, &model.Session{UserId: th.BasicUser2.Id, Props: model.StringMap{}})
|
|
require.Nil(t, err)
|
|
require.NotNil(t, session2)
|
|
|
|
// User is not authorized to get post
|
|
_, err = th.App.GetPostIfAuthorized(th.Context, post.Id, session2, false)
|
|
require.NotNil(t, err)
|
|
|
|
// User is authorized to get post
|
|
_, err = th.App.GetPostIfAuthorized(th.Context, post.Id, session1, false)
|
|
require.Nil(t, err)
|
|
})
|
|
|
|
t.Run("Public channel", func(t *testing.T) {
|
|
publicChannel := th.CreateChannel(th.Context, th.BasicTeam)
|
|
post, err := th.App.CreatePost(th.Context, &model.Post{UserId: th.BasicUser.Id, ChannelId: publicChannel.Id, Message: "Hello"}, publicChannel, model.CreatePostFlags{})
|
|
require.Nil(t, err)
|
|
require.NotNil(t, post)
|
|
|
|
session1, err := th.App.CreateSession(th.Context, &model.Session{UserId: th.BasicUser.Id, Props: model.StringMap{}})
|
|
require.Nil(t, err)
|
|
require.NotNil(t, session1)
|
|
|
|
session2, err := th.App.CreateSession(th.Context, &model.Session{UserId: th.BasicUser2.Id, Props: model.StringMap{}})
|
|
require.Nil(t, err)
|
|
require.NotNil(t, session2)
|
|
|
|
// User is authorized to get post
|
|
_, err = th.App.GetPostIfAuthorized(th.Context, post.Id, session2, false)
|
|
require.Nil(t, err)
|
|
|
|
// User is authorized to get post
|
|
_, err = th.App.GetPostIfAuthorized(th.Context, post.Id, session1, false)
|
|
require.Nil(t, err)
|
|
|
|
th.App.UpdateConfig(func(c *model.Config) {
|
|
b := true
|
|
c.ComplianceSettings.Enable = &b
|
|
})
|
|
|
|
// User is not authorized to get post
|
|
_, err = th.App.GetPostIfAuthorized(th.Context, post.Id, session2, false)
|
|
require.NotNil(t, err)
|
|
|
|
// User is authorized to get post
|
|
_, err = th.App.GetPostIfAuthorized(th.Context, post.Id, session1, false)
|
|
require.Nil(t, err)
|
|
})
|
|
}
|
|
|
|
func TestShouldNotRefollowOnOthersReply(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.ServiceSettings.ThreadAutoFollow = true
|
|
*cfg.ServiceSettings.CollapsedThreads = model.CollapsedThreadsDefaultOn
|
|
})
|
|
|
|
channel := th.BasicChannel
|
|
user := th.BasicUser
|
|
user2 := th.BasicUser2
|
|
appErr := th.App.JoinChannel(th.Context, channel, user.Id)
|
|
require.Nil(t, appErr)
|
|
appErr = th.App.JoinChannel(th.Context, channel, user2.Id)
|
|
require.Nil(t, appErr)
|
|
p1, appErr := th.App.CreatePost(th.Context, &model.Post{UserId: user.Id, ChannelId: channel.Id, Message: "Hi @" + user2.Username}, channel, model.CreatePostFlags{})
|
|
require.Nil(t, appErr)
|
|
_, appErr = th.App.CreatePost(th.Context, &model.Post{RootId: p1.Id, UserId: user2.Id, ChannelId: channel.Id, Message: "Hola"}, channel, model.CreatePostFlags{})
|
|
require.Nil(t, appErr)
|
|
|
|
// User2 unfollows thread
|
|
m, err := th.App.Srv().Store().Thread().MaintainMembership(user2.Id, p1.Id, store.ThreadMembershipOpts{
|
|
Following: false,
|
|
UpdateFollowing: true,
|
|
})
|
|
require.NoError(t, err)
|
|
require.False(t, m.Following)
|
|
|
|
// user posts in the thread
|
|
_, appErr = th.App.CreatePost(th.Context, &model.Post{RootId: p1.Id, UserId: user.Id, ChannelId: channel.Id, Message: "another reply"}, channel, model.CreatePostFlags{})
|
|
require.Nil(t, appErr)
|
|
|
|
// User2 should still not be following the thread because they manually
|
|
// unfollowed the thread
|
|
m, appErr = th.App.GetThreadMembershipForUser(user2.Id, p1.Id)
|
|
require.Nil(t, appErr)
|
|
require.False(t, m.Following)
|
|
|
|
// user posts in the thread mentioning user2
|
|
_, appErr = th.App.CreatePost(th.Context, &model.Post{RootId: p1.Id, UserId: user.Id, ChannelId: channel.Id, Message: "reply with mention @" + user2.Username}, channel, model.CreatePostFlags{})
|
|
require.Nil(t, appErr)
|
|
|
|
// User2 should now be following the thread because they were explicitly mentioned
|
|
m, appErr = th.App.GetThreadMembershipForUser(user2.Id, p1.Id)
|
|
require.Nil(t, appErr)
|
|
require.True(t, m.Following)
|
|
}
|
|
|
|
func TestGetLastAccessiblePostTime(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := SetupWithStoreMock(t)
|
|
defer th.TearDown()
|
|
|
|
// Setup store mocks needed for GetServerLimits
|
|
mockStore := th.App.Srv().Store().(*storemocks.Store)
|
|
mockUserStore := storemocks.UserStore{}
|
|
mockUserStore.On("Count", mock.Anything).Return(int64(10), nil)
|
|
mockStore.On("User").Return(&mockUserStore)
|
|
|
|
// Test with no license - should return 0
|
|
r, err := th.App.GetLastAccessiblePostTime()
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, int64(0), r)
|
|
|
|
// Test with Entry license but no limits configured - should return 0
|
|
entryLicenseNoLimits := model.NewTestLicenseSKU(model.LicenseShortSkuMattermostEntry)
|
|
entryLicenseNoLimits.Limits = nil // No limits configured
|
|
th.App.Srv().SetLicense(entryLicenseNoLimits)
|
|
r, err = th.App.GetLastAccessiblePostTime()
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, int64(0), r, "Entry license with no limits should return 0")
|
|
|
|
// Test with Entry license with zero post history limit - should return 0
|
|
entryLicenseZeroLimit := model.NewTestLicenseSKU(model.LicenseShortSkuMattermostEntry)
|
|
entryLicenseZeroLimit.Limits = &model.LicenseLimits{PostHistory: 0} // Zero limit
|
|
th.App.Srv().SetLicense(entryLicenseZeroLimit)
|
|
r, err = th.App.GetLastAccessiblePostTime()
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, int64(0), r, "Entry license with zero post history limit should return 0")
|
|
|
|
// Test with Entry license that has post history limits
|
|
entryLicenseWithLimits := model.NewTestLicenseSKU(model.LicenseShortSkuMattermostEntry)
|
|
entryLicenseWithLimits.Limits = &model.LicenseLimits{PostHistory: 1000} // Actual limit
|
|
th.App.Srv().SetLicense(entryLicenseWithLimits)
|
|
|
|
// Test case 1: No system value found (ErrNotFound) - should return 0
|
|
mockSystemStore := storemocks.SystemStore{}
|
|
mockStore.On("System").Return(&mockSystemStore)
|
|
mockSystemStore.On("GetByName", mock.Anything).Return(nil, store.NewErrNotFound("", ""))
|
|
r, err = th.App.GetLastAccessiblePostTime()
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, int64(0), r)
|
|
|
|
// Test case 2: Database error - should return error
|
|
mockSystemStore = storemocks.SystemStore{}
|
|
mockStore.On("System").Return(&mockSystemStore)
|
|
mockSystemStore.On("GetByName", mock.Anything).Return(nil, errors.New("database error"))
|
|
_, err = th.App.GetLastAccessiblePostTime()
|
|
assert.NotNil(t, err)
|
|
|
|
// Test case 3: Valid system value found - should return parsed timestamp
|
|
mockSystemStore = storemocks.SystemStore{}
|
|
mockStore.On("System").Return(&mockSystemStore)
|
|
mockSystemStore.On("GetByName", mock.Anything).Return(&model.System{Name: model.SystemLastAccessiblePostTime, Value: "1234567890"}, nil)
|
|
r, err = th.App.GetLastAccessiblePostTime()
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, int64(1234567890), r)
|
|
}
|
|
|
|
func TestComputeLastAccessiblePostTime(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
t.Run("Updates the time, if Entry license limit is applicable", func(t *testing.T) {
|
|
th := SetupWithStoreMock(t)
|
|
defer th.TearDown()
|
|
|
|
// Set Entry license with post history limit of 100 messages
|
|
entryLicensePostsLimit := model.NewTestLicenseSKU(model.LicenseShortSkuMattermostEntry)
|
|
entryLicensePostsLimit.Limits = &model.LicenseLimits{PostHistory: 100}
|
|
th.App.Srv().SetLicense(entryLicensePostsLimit)
|
|
|
|
mockStore := th.App.Srv().Store().(*storemocks.Store)
|
|
mockPostStore := storemocks.PostStore{}
|
|
mockPostStore.On("GetNthRecentPostTime", int64(100)).Return(int64(1234567890), nil)
|
|
mockSystemStore := storemocks.SystemStore{}
|
|
mockSystemStore.On("SaveOrUpdate", mock.Anything).Return(nil)
|
|
mockStore.On("Post").Return(&mockPostStore)
|
|
mockStore.On("System").Return(&mockSystemStore)
|
|
|
|
err := th.App.ComputeLastAccessiblePostTime()
|
|
assert.NoError(t, err)
|
|
|
|
// Verify that the system value was saved with the calculated timestamp
|
|
mockSystemStore.AssertCalled(t, "SaveOrUpdate", &model.System{
|
|
Name: model.SystemLastAccessiblePostTime,
|
|
Value: "1234567890",
|
|
})
|
|
})
|
|
|
|
t.Run("Remove the time if license limit is NOT applicable", func(t *testing.T) {
|
|
th := SetupWithStoreMock(t)
|
|
defer th.TearDown()
|
|
|
|
// Set license without post history limits (using test license without limits)
|
|
license := model.NewTestLicense()
|
|
license.Limits = nil // No limits
|
|
th.App.Srv().SetLicense(license)
|
|
|
|
mockStore := th.App.Srv().Store().(*storemocks.Store)
|
|
mockSystemStore := storemocks.SystemStore{}
|
|
mockSystemStore.On("GetByName", model.SystemLastAccessiblePostTime).Return(&model.System{Name: model.SystemLastAccessiblePostTime, Value: "1234567890"}, nil)
|
|
mockSystemStore.On("PermanentDeleteByName", model.SystemLastAccessiblePostTime).Return(nil, nil)
|
|
mockStore.On("System").Return(&mockSystemStore)
|
|
|
|
err := th.App.ComputeLastAccessiblePostTime()
|
|
assert.NoError(t, err)
|
|
|
|
// Verify that SaveOrUpdate was not called (no new timestamp calculated)
|
|
mockSystemStore.AssertNotCalled(t, "SaveOrUpdate", mock.Anything)
|
|
// Verify that the previous value was deleted
|
|
mockSystemStore.AssertCalled(t, "PermanentDeleteByName", model.SystemLastAccessiblePostTime)
|
|
})
|
|
}
|
|
|
|
func TestGetEditHistoryForPost(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
post := &model.Post{
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "new message",
|
|
UserId: th.BasicUser.Id,
|
|
}
|
|
|
|
rpost, err := th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err)
|
|
|
|
// update the post message
|
|
patch := &model.PostPatch{
|
|
Message: model.NewPointer("new message edited"),
|
|
}
|
|
_, err1 := th.App.PatchPost(th.Context, rpost.Id, patch, nil)
|
|
require.Nil(t, err1)
|
|
|
|
// update the post message again
|
|
patch = &model.PostPatch{
|
|
Message: model.NewPointer("new message edited again"),
|
|
}
|
|
|
|
_, err2 := th.App.PatchPost(th.Context, rpost.Id, patch, nil)
|
|
require.Nil(t, err2)
|
|
|
|
t.Run("should return the edit history", func(t *testing.T) {
|
|
edits, err := th.App.GetEditHistoryForPost(post.Id)
|
|
require.Nil(t, err)
|
|
|
|
require.Len(t, edits, 2)
|
|
require.Equal(t, "new message edited", edits[0].Message)
|
|
require.Equal(t, "new message", edits[1].Message)
|
|
})
|
|
|
|
t.Run("should return an error if the post is not found", func(t *testing.T) {
|
|
edits, err := th.App.GetEditHistoryForPost("invalid-post-id")
|
|
require.NotNil(t, err)
|
|
require.Empty(t, edits)
|
|
})
|
|
|
|
t.Run("edit history should contain file metadata", func(t *testing.T) {
|
|
fileBytes := []byte("file contents")
|
|
fileInfo, err := th.App.UploadFile(th.Context, fileBytes, th.BasicChannel.Id, "file.txt")
|
|
require.Nil(t, err)
|
|
|
|
post := &model.Post{
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "new message",
|
|
UserId: th.BasicUser.Id,
|
|
FileIds: model.StringArray{fileInfo.Id},
|
|
}
|
|
|
|
_, err = th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err)
|
|
|
|
patch := &model.PostPatch{
|
|
Message: model.NewPointer("new message edited"),
|
|
}
|
|
_, appErr := th.App.PatchPost(th.Context, post.Id, patch, nil)
|
|
require.Nil(t, appErr)
|
|
|
|
patch = &model.PostPatch{
|
|
Message: model.NewPointer("new message edited 2"),
|
|
}
|
|
_, appErr = th.App.PatchPost(th.Context, post.Id, patch, nil)
|
|
require.Nil(t, appErr)
|
|
|
|
patch = &model.PostPatch{
|
|
Message: model.NewPointer("new message edited 3"),
|
|
}
|
|
_, appErr = th.App.PatchPost(th.Context, post.Id, patch, nil)
|
|
require.Nil(t, appErr)
|
|
|
|
edits, err := th.App.GetEditHistoryForPost(post.Id)
|
|
require.Nil(t, err)
|
|
|
|
require.Len(t, edits, 3)
|
|
|
|
for _, edit := range edits {
|
|
require.Len(t, edit.FileIds, 1)
|
|
require.Equal(t, fileInfo.Id, edit.FileIds[0])
|
|
require.Len(t, edit.Metadata.Files, 1)
|
|
require.Equal(t, fileInfo.Id, edit.Metadata.Files[0].Id)
|
|
}
|
|
})
|
|
|
|
t.Run("edit history should contain file metadata even if the file info is deleted", func(t *testing.T) {
|
|
fileBytes := []byte("file contents")
|
|
fileInfo, appErr := th.App.UploadFile(th.Context, fileBytes, th.BasicChannel.Id, "file.txt")
|
|
require.Nil(t, appErr)
|
|
|
|
post := &model.Post{
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "new message",
|
|
UserId: th.BasicUser.Id,
|
|
FileIds: model.StringArray{fileInfo.Id},
|
|
}
|
|
|
|
_, appErr = th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, appErr)
|
|
|
|
patch := &model.PostPatch{
|
|
Message: model.NewPointer("new message edited"),
|
|
}
|
|
_, appErr = th.App.PatchPost(th.Context, post.Id, patch, nil)
|
|
require.Nil(t, appErr)
|
|
|
|
patch = &model.PostPatch{
|
|
Message: model.NewPointer("new message edited 2"),
|
|
}
|
|
_, appErr = th.App.PatchPost(th.Context, post.Id, patch, nil)
|
|
require.Nil(t, appErr)
|
|
|
|
patch = &model.PostPatch{
|
|
Message: model.NewPointer("new message edited 3"),
|
|
}
|
|
_, appErr = th.App.PatchPost(th.Context, post.Id, patch, nil)
|
|
require.Nil(t, appErr)
|
|
|
|
// now delete the file info, and it should still be include in edit history metadata
|
|
_, err := th.App.Srv().Store().FileInfo().DeleteForPost(th.Context, post.Id)
|
|
require.NoError(t, err)
|
|
|
|
edits, appErr := th.App.GetEditHistoryForPost(post.Id)
|
|
require.Nil(t, appErr)
|
|
|
|
require.Len(t, edits, 3)
|
|
|
|
for _, edit := range edits {
|
|
require.Len(t, edit.FileIds, 1)
|
|
require.Equal(t, fileInfo.Id, edit.FileIds[0])
|
|
require.Len(t, edit.Metadata.Files, 1)
|
|
require.Equal(t, fileInfo.Id, edit.Metadata.Files[0].Id)
|
|
require.Greater(t, edit.Metadata.Files[0].DeleteAt, int64(0))
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestCopyWranglerPostlist(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
// Create a post with a file attachment
|
|
fileBytes := []byte("file contents")
|
|
fileInfo, err := th.App.UploadFile(th.Context, fileBytes, th.BasicChannel.Id, "file.txt")
|
|
require.Nil(t, err)
|
|
post := &model.Post{
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "test message",
|
|
UserId: th.BasicUser.Id,
|
|
FileIds: []string{fileInfo.Id},
|
|
}
|
|
rootPost, err := th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err)
|
|
|
|
// Add a reaction to the post
|
|
reaction := &model.Reaction{
|
|
UserId: th.BasicUser.Id,
|
|
PostId: rootPost.Id,
|
|
EmojiName: "smile",
|
|
}
|
|
_, err = th.App.SaveReactionForPost(th.Context, reaction)
|
|
require.Nil(t, err)
|
|
|
|
// Copy the post to a new channel
|
|
targetChannel := &model.Channel{
|
|
TeamId: th.BasicTeam.Id,
|
|
Name: "test-channel",
|
|
Type: model.ChannelTypeOpen,
|
|
}
|
|
targetChannel, err = th.App.CreateChannel(th.Context, targetChannel, false)
|
|
require.Nil(t, err)
|
|
wpl := &model.WranglerPostList{
|
|
Posts: []*model.Post{rootPost},
|
|
FileAttachmentCount: 1,
|
|
}
|
|
newRootPost, err := th.App.CopyWranglerPostlist(th.Context, wpl, targetChannel)
|
|
require.Nil(t, err)
|
|
|
|
// Check that the new post has the same message and file attachment
|
|
require.Equal(t, rootPost.Message, newRootPost.Message)
|
|
require.Len(t, newRootPost.FileIds, 1)
|
|
|
|
// Check that the new post has the same reaction
|
|
reactions, err := th.App.GetReactionsForPost(newRootPost.Id)
|
|
require.Nil(t, err)
|
|
require.Len(t, reactions, 1)
|
|
require.Equal(t, reaction.EmojiName, reactions[0].EmojiName)
|
|
}
|
|
|
|
func TestValidateMoveOrCopy(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
cfg.WranglerSettings.MoveThreadFromPrivateChannelEnable = model.NewPointer(true)
|
|
cfg.WranglerSettings.MoveThreadFromDirectMessageChannelEnable = model.NewPointer(true)
|
|
cfg.WranglerSettings.MoveThreadFromGroupMessageChannelEnable = model.NewPointer(true)
|
|
cfg.WranglerSettings.MoveThreadToAnotherTeamEnable = model.NewPointer(true)
|
|
cfg.WranglerSettings.MoveThreadMaxCount = model.NewPointer(int64(100))
|
|
})
|
|
|
|
t.Run("empty post list", func(t *testing.T) {
|
|
err := th.App.ValidateMoveOrCopy(th.Context, &model.WranglerPostList{}, th.BasicChannel, th.BasicChannel, th.BasicUser)
|
|
require.Error(t, err)
|
|
require.Equal(t, "The wrangler post list contains no posts", err.Error())
|
|
})
|
|
|
|
t.Run("moving from private channel with MoveThreadFromPrivateChannelEnable disabled", func(t *testing.T) {
|
|
privateChannel := &model.Channel{
|
|
TeamId: th.BasicTeam.Id,
|
|
Name: "private-channel",
|
|
Type: model.ChannelTypePrivate,
|
|
}
|
|
privateChannel, err := th.App.CreateChannel(th.Context, privateChannel, false)
|
|
require.Nil(t, err)
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
cfg.WranglerSettings.MoveThreadFromPrivateChannelEnable = model.NewPointer(false)
|
|
})
|
|
|
|
e := th.App.ValidateMoveOrCopy(th.Context, &model.WranglerPostList{Posts: []*model.Post{{ChannelId: privateChannel.Id}}}, privateChannel, th.BasicChannel, th.BasicUser)
|
|
require.Error(t, e)
|
|
require.Equal(t, "Wrangler is currently configured to not allow moving posts from private channels", e.Error())
|
|
})
|
|
|
|
t.Run("moving from direct channel with MoveThreadFromDirectMessageChannelEnable disabled", func(t *testing.T) {
|
|
directChannel, err := th.App.createDirectChannel(th.Context, th.BasicUser.Id, th.BasicUser2.Id)
|
|
require.Nil(t, err)
|
|
require.NotNil(t, directChannel)
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
cfg.WranglerSettings.MoveThreadFromDirectMessageChannelEnable = model.NewPointer(false)
|
|
})
|
|
|
|
e := th.App.ValidateMoveOrCopy(th.Context, &model.WranglerPostList{Posts: []*model.Post{{ChannelId: directChannel.Id}}}, directChannel, th.BasicChannel, th.BasicUser)
|
|
require.Error(t, e)
|
|
require.Equal(t, "Wrangler is currently configured to not allow moving posts from direct message channels", e.Error())
|
|
})
|
|
|
|
t.Run("moving from group channel with MoveThreadFromGroupMessageChannelEnable disabled", func(t *testing.T) {
|
|
groupChannel := &model.Channel{
|
|
TeamId: th.BasicTeam.Id,
|
|
Name: "group-channel",
|
|
Type: model.ChannelTypeGroup,
|
|
}
|
|
groupChannel, err := th.App.CreateChannel(th.Context, groupChannel, false)
|
|
require.Nil(t, err)
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
cfg.WranglerSettings.MoveThreadFromGroupMessageChannelEnable = model.NewPointer(false)
|
|
})
|
|
|
|
e := th.App.ValidateMoveOrCopy(th.Context, &model.WranglerPostList{Posts: []*model.Post{{ChannelId: groupChannel.Id}}}, groupChannel, th.BasicChannel, th.BasicUser)
|
|
require.Error(t, e)
|
|
require.Equal(t, "Wrangler is currently configured to not allow moving posts from group message channels", e.Error())
|
|
})
|
|
|
|
t.Run("moving to different team with MoveThreadToAnotherTeamEnable disabled", func(t *testing.T) {
|
|
team := &model.Team{
|
|
Name: "testteam",
|
|
DisplayName: "testteam",
|
|
Type: model.TeamOpen,
|
|
}
|
|
|
|
targetTeam, err := th.App.CreateTeam(th.Context, team)
|
|
require.Nil(t, err)
|
|
require.NotNil(t, targetTeam)
|
|
|
|
targetChannel := &model.Channel{
|
|
TeamId: targetTeam.Id,
|
|
Name: "test-channel",
|
|
Type: model.ChannelTypeOpen,
|
|
}
|
|
|
|
targetChannel, err = th.App.CreateChannel(th.Context, targetChannel, false)
|
|
require.Nil(t, err)
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
cfg.WranglerSettings.MoveThreadToAnotherTeamEnable = model.NewPointer(false)
|
|
})
|
|
|
|
e := th.App.ValidateMoveOrCopy(th.Context, &model.WranglerPostList{Posts: []*model.Post{{ChannelId: th.BasicChannel.Id}}}, th.BasicChannel, targetChannel, th.BasicUser)
|
|
require.Error(t, e)
|
|
require.Equal(t, "Wrangler is currently configured to not allow moving messages to different teams", e.Error())
|
|
})
|
|
|
|
t.Run("moving to channel user is not a member of", func(t *testing.T) {
|
|
targetChannel := &model.Channel{
|
|
TeamId: th.BasicTeam.Id,
|
|
Name: "test-channel",
|
|
Type: model.ChannelTypePrivate,
|
|
}
|
|
targetChannel, err := th.App.CreateChannel(th.Context, targetChannel, false)
|
|
require.Nil(t, err)
|
|
|
|
err = th.App.RemoveUserFromChannel(th.Context, th.BasicUser.Id, th.SystemAdminUser.Id, th.BasicChannel)
|
|
require.Nil(t, err)
|
|
|
|
e := th.App.ValidateMoveOrCopy(th.Context, &model.WranglerPostList{Posts: []*model.Post{{ChannelId: th.BasicChannel.Id}}}, th.BasicChannel, targetChannel, th.BasicUser)
|
|
require.Error(t, e)
|
|
require.Equal(t, fmt.Sprintf("channel with ID %s doesn't exist or you are not a member", targetChannel.Id), e.Error())
|
|
})
|
|
|
|
t.Run("moving thread longer than MoveThreadMaxCount", func(t *testing.T) {
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.WranglerSettings.MoveThreadMaxCount = 1
|
|
})
|
|
|
|
e := th.App.ValidateMoveOrCopy(th.Context, &model.WranglerPostList{Posts: []*model.Post{{ChannelId: th.BasicChannel.Id}, {ChannelId: th.BasicChannel.Id}}}, th.BasicChannel, th.BasicChannel, th.BasicUser)
|
|
require.Error(t, e)
|
|
require.Equal(t, "the thread is 2 posts long, but this command is configured to only move threads of up to 1 posts", e.Error())
|
|
})
|
|
}
|
|
|
|
func TestPermanentDeletePost(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
t.Run("should permanently delete a post and its file attachment", func(t *testing.T) {
|
|
// Create a post with a file attachment.
|
|
teamID := th.BasicTeam.Id
|
|
channelID := th.BasicChannel.Id
|
|
userID := th.BasicUser.Id
|
|
filename := "test"
|
|
data := []byte("abcd")
|
|
|
|
info1, err := th.App.DoUploadFile(th.Context, time.Date(2007, 2, 4, 1, 2, 3, 4, time.Local), teamID, channelID, userID, filename, data, true)
|
|
assert.Nil(t, err)
|
|
|
|
post := &model.Post{
|
|
Message: "asd",
|
|
ChannelId: channelID,
|
|
PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()),
|
|
UserId: userID,
|
|
CreateAt: 0,
|
|
FileIds: []string{info1.Id},
|
|
}
|
|
|
|
post, err = th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{SetOnline: true})
|
|
assert.Nil(t, err)
|
|
|
|
// Delete the post.
|
|
err = th.App.PermanentDeletePost(th.Context, post.Id, userID)
|
|
assert.Nil(t, err)
|
|
|
|
// Wait for the cleanup routine to finish.
|
|
time.Sleep(time.Millisecond * 100)
|
|
|
|
// Check that the post can no longer be reached.
|
|
_, err = th.App.GetSinglePost(th.Context, post.Id, true)
|
|
assert.NotNil(t, err)
|
|
|
|
// Check that the file can no longer be reached.
|
|
_, err = th.App.GetFileInfo(th.Context, info1.Id)
|
|
assert.NotNil(t, err)
|
|
})
|
|
|
|
t.Run("should permanently delete a post that is soft deleted", func(t *testing.T) {
|
|
// Create a post with a file attachment.
|
|
teamID := th.BasicTeam.Id
|
|
channelID := th.BasicChannel.Id
|
|
userID := th.BasicUser.Id
|
|
filename := "test"
|
|
data := []byte("abcd")
|
|
|
|
info1, appErr := th.App.DoUploadFile(th.Context, time.Date(2007, 2, 4, 1, 2, 3, 4, time.Local), teamID, channelID, userID, filename, data, true)
|
|
require.Nil(t, appErr)
|
|
|
|
post := &model.Post{
|
|
Message: "asd",
|
|
ChannelId: channelID,
|
|
PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()),
|
|
UserId: userID,
|
|
CreateAt: 0,
|
|
FileIds: []string{info1.Id},
|
|
}
|
|
|
|
post, appErr = th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{SetOnline: true})
|
|
assert.Nil(t, appErr)
|
|
|
|
infos, err := th.App.Srv().Store().FileInfo().GetForPost(post.Id, true, true, false)
|
|
require.NoError(t, err)
|
|
assert.Len(t, infos, 1)
|
|
|
|
// Soft delete the post.
|
|
_, appErr = th.App.DeletePost(th.Context, post.Id, userID)
|
|
assert.Nil(t, appErr)
|
|
|
|
// Wait for the cleanup routine to finish.
|
|
time.Sleep(time.Millisecond * 100)
|
|
|
|
// Delete the post.
|
|
appErr = th.App.PermanentDeletePost(th.Context, post.Id, userID)
|
|
assert.Nil(t, appErr)
|
|
|
|
// Check that the post can no longer be reached.
|
|
_, appErr = th.App.GetSinglePost(th.Context, post.Id, true)
|
|
assert.NotNil(t, appErr)
|
|
|
|
infos, err = th.App.Srv().Store().FileInfo().GetForPost(post.Id, true, true, false)
|
|
require.NoError(t, err)
|
|
assert.Len(t, infos, 0)
|
|
})
|
|
}
|
|
|
|
func TestSendTestMessage(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
t.Run("Should create the post with the correct prop", func(t *testing.T) {
|
|
post, result := th.App.SendTestMessage(th.Context, th.BasicUser.Id)
|
|
assert.Nil(t, result)
|
|
assert.NotEmpty(t, post.GetProp(model.PostPropsForceNotification))
|
|
})
|
|
}
|
|
|
|
func TestPopulateEditHistoryFileMetadata(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
t.Run("should populate file metadata for all posts", func(t *testing.T) {
|
|
fileInfo1, err := th.App.Srv().Store().FileInfo().Save(th.Context,
|
|
&model.FileInfo{
|
|
CreatorId: th.BasicUser.Id,
|
|
Path: "path.txt",
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
fileInfo2, err := th.App.Srv().Store().FileInfo().Save(th.Context,
|
|
&model.FileInfo{
|
|
CreatorId: th.BasicUser.Id,
|
|
Path: "path.txt",
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
post1 := th.CreatePost(th.BasicChannel, func(post *model.Post) {
|
|
post.FileIds = model.StringArray{fileInfo1.Id}
|
|
})
|
|
|
|
post2 := th.CreatePost(th.BasicChannel, func(post *model.Post) {
|
|
post.FileIds = model.StringArray{fileInfo2.Id}
|
|
})
|
|
|
|
appErr := th.App.populateEditHistoryFileMetadata([]*model.Post{post1, post2})
|
|
require.Nil(t, appErr)
|
|
|
|
require.Len(t, post1.Metadata.Files, 1)
|
|
require.Equal(t, fileInfo1.Id, post1.Metadata.Files[0].Id)
|
|
|
|
require.Len(t, post2.Metadata.Files, 1)
|
|
require.Equal(t, fileInfo2.Id, post2.Metadata.Files[0].Id)
|
|
})
|
|
|
|
t.Run("should populate file metadata even for deleted posts", func(t *testing.T) {
|
|
fileInfo1, err := th.App.Srv().Store().FileInfo().Save(th.Context,
|
|
&model.FileInfo{
|
|
CreatorId: th.BasicUser.Id,
|
|
Path: "path.txt",
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
fileInfo2, err := th.App.Srv().Store().FileInfo().Save(th.Context,
|
|
&model.FileInfo{
|
|
CreatorId: th.BasicUser.Id,
|
|
Path: "path.txt",
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
post1 := th.CreatePost(th.BasicChannel, func(post *model.Post) {
|
|
post.FileIds = model.StringArray{fileInfo1.Id}
|
|
})
|
|
|
|
post2 := th.CreatePost(th.BasicChannel, func(post *model.Post) {
|
|
post.FileIds = model.StringArray{fileInfo2.Id}
|
|
})
|
|
|
|
_, appErr := th.App.DeletePost(th.Context, post1.Id, th.BasicUser.Id)
|
|
require.Nil(t, appErr)
|
|
|
|
_, appErr = th.App.DeletePost(th.Context, post2.Id, th.BasicUser.Id)
|
|
require.Nil(t, appErr)
|
|
|
|
appErr = th.App.populateEditHistoryFileMetadata([]*model.Post{post1, post2})
|
|
require.Nil(t, appErr)
|
|
|
|
require.Len(t, post1.Metadata.Files, 1)
|
|
require.Equal(t, fileInfo1.Id, post1.Metadata.Files[0].Id)
|
|
|
|
require.Len(t, post2.Metadata.Files, 1)
|
|
require.Equal(t, fileInfo2.Id, post2.Metadata.Files[0].Id)
|
|
})
|
|
|
|
t.Run("should populate file metadata even for deleted fileInfos", func(t *testing.T) {
|
|
fileInfo1, err := th.App.Srv().Store().FileInfo().Save(th.Context,
|
|
&model.FileInfo{
|
|
CreatorId: th.BasicUser.Id,
|
|
Path: "path.txt",
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
fileInfo2, err := th.App.Srv().Store().FileInfo().Save(th.Context,
|
|
&model.FileInfo{
|
|
CreatorId: th.BasicUser.Id,
|
|
Path: "path.txt",
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
post1 := th.CreatePost(th.BasicChannel, func(post *model.Post) {
|
|
post.FileIds = model.StringArray{fileInfo1.Id}
|
|
})
|
|
|
|
post2 := th.CreatePost(th.BasicChannel, func(post *model.Post) {
|
|
post.FileIds = model.StringArray{fileInfo2.Id}
|
|
})
|
|
|
|
_, err = th.App.Srv().Store().FileInfo().DeleteForPost(th.Context, post1.Id)
|
|
require.NoError(t, err)
|
|
|
|
_, err = th.App.Srv().Store().FileInfo().DeleteForPost(th.Context, post2.Id)
|
|
require.NoError(t, err)
|
|
|
|
appErr := th.App.populateEditHistoryFileMetadata([]*model.Post{post1, post2})
|
|
require.Nil(t, appErr)
|
|
|
|
require.Len(t, post1.Metadata.Files, 1)
|
|
require.Equal(t, fileInfo1.Id, post1.Metadata.Files[0].Id)
|
|
require.Greater(t, post1.Metadata.Files[0].DeleteAt, int64(0))
|
|
|
|
require.Len(t, post2.Metadata.Files, 1)
|
|
require.Equal(t, fileInfo2.Id, post2.Metadata.Files[0].Id)
|
|
require.Greater(t, post2.Metadata.Files[0].DeleteAt, int64(0))
|
|
})
|
|
}
|