mirror of
https://github.com/mattermost/mattermost.git
synced 2026-02-03 20:40:00 -05:00
* Guest cannot add file to post without upload_file permission * Move checks to api layer, addd checks in update patch post scheduled post * Minor * Linter fixes * i18n translations * removed the duplicated check from scheduled_post app layer * Move scheduled post permission test from app layer to API layer The permission check for updating scheduled posts belonging to other users was moved from the app layer to the API layer in the PR. This commit moves the corresponding test to the API layer to match. * Move scheduled post delete permission check to API layer Move the permission check for deleting scheduled posts from the app layer to the API layer, consistent with update permission check. Also enhance API tests to verify posts aren't modified after forbidden operations. * Fix inconsistent status code for non-existent scheduled post Return StatusNotFound instead of StatusInternalServerError when a scheduled post doesn't exist in UpdateScheduledPost, matching the API layer behavior. * Fix flaky TestAddUserToChannelCreatesChannelMemberHistoryRecord test Use ElementsMatch instead of Equal to compare user ID slices since the order returned from GetUsersInChannelDuring is not guaranteed. --------- Co-authored-by: Mattermost Build <build@mattermost.com> Co-authored-by: Jesse Hallam <jesse@mattermost.com>
6141 lines
219 KiB
Go
6141 lines
219 KiB
Go
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
package api4
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"maps"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"net/url"
|
|
"os"
|
|
"reflect"
|
|
"sort"
|
|
"strings"
|
|
"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/app"
|
|
"github.com/mattermost/mattermost/server/v8/channels/store/storetest/mocks"
|
|
"github.com/mattermost/mattermost/server/v8/channels/testlib"
|
|
"github.com/mattermost/mattermost/server/v8/channels/utils"
|
|
"github.com/mattermost/mattermost/server/v8/channels/utils/testutils"
|
|
)
|
|
|
|
// Helper to enable feature with license
|
|
func enableBurnOnReadFeature(th *TestHelper) {
|
|
th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterpriseAdvanced))
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
cfg.ServiceSettings.EnableBurnOnRead = model.NewPointer(true)
|
|
})
|
|
}
|
|
|
|
func TestCreatePost(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
|
|
th := Setup(t).InitBasic(t)
|
|
client := th.Client
|
|
|
|
basicPost := func() *model.Post {
|
|
p := &model.Post{
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "#hashtag a" + model.NewId() + "a",
|
|
DeleteAt: 101,
|
|
}
|
|
p.AddProp(model.PropsAddChannelMember, "no good")
|
|
return p
|
|
}
|
|
|
|
post := basicPost()
|
|
rootPost, resp2, err2 := client.CreatePost(context.Background(), post)
|
|
require.NoError(t, err2)
|
|
CheckCreatedStatus(t, resp2)
|
|
require.NotNil(t, rootPost)
|
|
|
|
require.Equal(t, post.Message, rootPost.Message, "message didn't match")
|
|
require.Equal(t, "#hashtag", rootPost.Hashtags, "hashtag didn't match")
|
|
require.Empty(t, rootPost.FileIds)
|
|
require.Equal(t, 0, int(rootPost.EditAt), "newly created post shouldn't have EditAt set")
|
|
require.Nil(t, rootPost.GetProp(model.PropsAddChannelMember), "newly created post shouldn't have Props['add_channel_member'] set")
|
|
require.Equal(t, 0, int(rootPost.DeleteAt), "newly created post shouldn't have DeleteAt set")
|
|
|
|
post = basicPost()
|
|
post.RootId = rootPost.Id
|
|
childPost, resp2, err2 := client.CreatePost(context.Background(), post)
|
|
require.NoError(t, err2)
|
|
CheckCreatedStatus(t, resp2)
|
|
require.NotNil(t, childPost)
|
|
|
|
t.Run("with file uploaded by same user", func(t *testing.T) {
|
|
fileResp, resp, err := client.UploadFile(context.Background(), []byte("data"), th.BasicChannel.Id, "test")
|
|
require.NoError(t, err)
|
|
CheckCreatedStatus(t, resp)
|
|
fileId := fileResp.FileInfos[0].Id
|
|
|
|
postWithFiles, resp, err := client.CreatePost(context.Background(), &model.Post{
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "with files",
|
|
FileIds: model.StringArray{fileId},
|
|
})
|
|
require.NoError(t, err)
|
|
CheckCreatedStatus(t, resp)
|
|
assert.Equal(t, model.StringArray{fileId}, postWithFiles.FileIds)
|
|
|
|
actualPostWithFiles, resp, err := client.GetPost(context.Background(), postWithFiles.Id, "")
|
|
require.NoError(t, err)
|
|
CheckOKStatus(t, resp)
|
|
assert.Equal(t, model.StringArray{fileId}, actualPostWithFiles.FileIds)
|
|
})
|
|
|
|
t.Run("with file uploaded by different user", func(t *testing.T) {
|
|
fileResp, resp, err := th.SystemAdminClient.UploadFile(context.Background(), []byte("data"), th.BasicChannel.Id, "test")
|
|
require.NoError(t, err)
|
|
CheckCreatedStatus(t, resp)
|
|
fileId := fileResp.FileInfos[0].Id
|
|
|
|
postWithFiles, resp, err := client.CreatePost(context.Background(), &model.Post{
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "with files",
|
|
FileIds: model.StringArray{fileId},
|
|
})
|
|
require.NoError(t, err)
|
|
CheckCreatedStatus(t, resp)
|
|
assert.Empty(t, postWithFiles.FileIds)
|
|
|
|
actualPostWithFiles, resp, err := client.GetPost(context.Background(), postWithFiles.Id, "")
|
|
require.NoError(t, err)
|
|
CheckOKStatus(t, resp)
|
|
assert.Empty(t, actualPostWithFiles.FileIds)
|
|
})
|
|
|
|
t.Run("with file uploaded by nouser", func(t *testing.T) {
|
|
fileInfo, appErr := th.App.UploadFile(th.Context, []byte("data"), th.BasicChannel.Id, "test")
|
|
require.Nil(t, appErr)
|
|
fileId := fileInfo.Id
|
|
|
|
postWithFiles, resp, err := client.CreatePost(context.Background(), &model.Post{
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "with files",
|
|
FileIds: model.StringArray{fileId},
|
|
})
|
|
require.NoError(t, err)
|
|
CheckCreatedStatus(t, resp)
|
|
assert.Equal(t, model.StringArray{fileId}, postWithFiles.FileIds)
|
|
|
|
actualPostWithFiles, resp, err := client.GetPost(context.Background(), postWithFiles.Id, "")
|
|
require.NoError(t, err)
|
|
CheckOKStatus(t, resp)
|
|
assert.Equal(t, model.StringArray{fileId}, actualPostWithFiles.FileIds)
|
|
})
|
|
|
|
t.Run("Create posts without the USE_CHANNEL_MENTIONS Permission - returns ephemeral message with mentions and no ephemeral message without mentions", func(t *testing.T) {
|
|
wsClient := th.CreateConnectedWebSocketClient(t)
|
|
|
|
defaultPerms := th.SaveDefaultRolePermissions(t)
|
|
defer th.RestoreDefaultRolePermissions(t, defaultPerms)
|
|
|
|
th.RemovePermissionFromRole(t, model.PermissionUseChannelMentions.Id, model.ChannelUserRoleId)
|
|
|
|
post := basicPost()
|
|
post.RootId = rootPost.Id
|
|
post.Message = "a post with no channel mentions"
|
|
rPost, resp, err := client.CreatePost(context.Background(), post)
|
|
require.NoError(t, err)
|
|
CheckCreatedStatus(t, resp)
|
|
require.NotNil(t, rPost)
|
|
|
|
// Message with no channel mentions should result in no ephemeral message
|
|
timeout := time.After(5 * time.Second)
|
|
waiting := true
|
|
for waiting {
|
|
select {
|
|
case event := <-wsClient.EventChannel:
|
|
require.NotEqual(t, model.WebsocketEventEphemeralMessage, event.EventType(), "should not have ephemeral message event")
|
|
case <-timeout:
|
|
waiting = false
|
|
}
|
|
}
|
|
|
|
post.Message = "a post with @channel"
|
|
rPost, resp, err = client.CreatePost(context.Background(), post)
|
|
require.NoError(t, err)
|
|
CheckCreatedStatus(t, resp)
|
|
require.NotNil(t, rPost)
|
|
|
|
post.Message = "a post with @all"
|
|
rPost, resp, err = client.CreatePost(context.Background(), post)
|
|
require.NoError(t, err)
|
|
CheckCreatedStatus(t, resp)
|
|
require.NotNil(t, rPost)
|
|
|
|
post.Message = "a post with @here"
|
|
rPost, resp, err = client.CreatePost(context.Background(), post)
|
|
require.NoError(t, err)
|
|
CheckCreatedStatus(t, resp)
|
|
require.NotNil(t, rPost)
|
|
|
|
timeout = time.After(5 * time.Second)
|
|
expectedEvents := 3 // 3 Posts created with @ mentions should result in 3 websocket events
|
|
gotEvents := 0
|
|
for gotEvents < expectedEvents {
|
|
select {
|
|
case event := <-wsClient.EventChannel:
|
|
if event.EventType() == model.WebsocketEventEphemeralMessage {
|
|
gotEvents++
|
|
}
|
|
case <-timeout:
|
|
require.Fail(t, fmt.Sprintf("Got %d ephemeral messages, expected: %d", gotEvents, expectedEvents))
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("err with integrations-reserved props", func(t *testing.T) {
|
|
originalHardenedModeSetting := *th.App.Config().ServiceSettings.ExperimentalEnableHardenedMode
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.ServiceSettings.ExperimentalEnableHardenedMode = true
|
|
})
|
|
|
|
defer th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.ServiceSettings.ExperimentalEnableHardenedMode = originalHardenedModeSetting
|
|
})
|
|
|
|
rpost, postResp, postErr := client.CreatePost(context.Background(), &model.Post{
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "with props",
|
|
Props: model.StringInterface{model.PostPropsFromWebhook: "true"},
|
|
})
|
|
|
|
require.Error(t, postErr)
|
|
CheckBadRequestStatus(t, postResp)
|
|
assert.Nil(t, rpost)
|
|
})
|
|
|
|
t.Run("invalid post type", func(t *testing.T) {
|
|
post := basicPost()
|
|
post.Type = model.PostTypeSystemGeneric
|
|
rpost, resp, err := client.CreatePost(context.Background(), post)
|
|
require.Error(t, err)
|
|
CheckBadRequestStatus(t, resp)
|
|
assert.Nil(t, rpost)
|
|
})
|
|
|
|
t.Run("invalid rootId type", func(t *testing.T) {
|
|
post := basicPost()
|
|
post.RootId = "junk"
|
|
rpost, resp, err := client.CreatePost(context.Background(), post)
|
|
require.Error(t, err)
|
|
CheckBadRequestStatus(t, resp)
|
|
assert.Nil(t, rpost)
|
|
})
|
|
|
|
t.Run("RootId points to child post", func(t *testing.T) {
|
|
post := basicPost()
|
|
post.RootId = childPost.Id
|
|
rpost, resp, err := client.CreatePost(context.Background(), post)
|
|
require.Error(t, err)
|
|
CheckBadRequestStatus(t, resp)
|
|
assert.Nil(t, rpost)
|
|
})
|
|
|
|
t.Run("invalid ChannelId", func(t *testing.T) {
|
|
post := basicPost()
|
|
post.ChannelId = "junk"
|
|
rpost, resp, err := client.CreatePost(context.Background(), post)
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
assert.Nil(t, rpost)
|
|
})
|
|
|
|
t.Run("invalid ChannelId", func(t *testing.T) {
|
|
post := basicPost()
|
|
post.ChannelId = model.NewId()
|
|
rpost, resp, err := client.CreatePost(context.Background(), post)
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
assert.Nil(t, rpost)
|
|
})
|
|
|
|
t.Run("invalid payload", func(t *testing.T) {
|
|
r, err := client.DoAPIPost(context.Background(), "/posts", "garbage")
|
|
require.Error(t, err)
|
|
require.Equal(t, http.StatusBadRequest, r.StatusCode)
|
|
})
|
|
|
|
t.Run("not logged in", func(t *testing.T) {
|
|
resp, err := client.Logout(context.Background())
|
|
require.NoError(t, err)
|
|
CheckOKStatus(t, resp)
|
|
|
|
post := basicPost()
|
|
rpost, resp, err := client.CreatePost(context.Background(), post)
|
|
require.Error(t, err)
|
|
CheckUnauthorizedStatus(t, resp)
|
|
assert.Nil(t, rpost)
|
|
})
|
|
|
|
t.Run("should prevent creating post with files when user lacks upload_file permission in target channel", func(t *testing.T) {
|
|
fileResp, resp, err := client.UploadFile(context.Background(), []byte("test file data"), th.BasicChannel.Id, "test-file.txt")
|
|
require.NoError(t, err)
|
|
CheckCreatedStatus(t, resp)
|
|
fileId := fileResp.FileInfos[0].Id
|
|
|
|
th.RemovePermissionFromRole(t, model.PermissionUploadFile.Id, model.ChannelUserRoleId)
|
|
defer func() {
|
|
th.AddPermissionToRole(t, model.PermissionUploadFile.Id, model.ChannelUserRoleId)
|
|
}()
|
|
|
|
post := &model.Post{
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "Test post with file",
|
|
FileIds: model.StringArray{fileId},
|
|
}
|
|
rpost, resp, err := client.CreatePost(context.Background(), post)
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
assert.Nil(t, rpost)
|
|
})
|
|
|
|
t.Run("should allow creating post with files when user has upload_file permission", func(t *testing.T) {
|
|
fileResp, resp, err := client.UploadFile(context.Background(), []byte("test file data"), th.BasicChannel.Id, "test-file.txt")
|
|
require.NoError(t, err)
|
|
CheckCreatedStatus(t, resp)
|
|
fileId := fileResp.FileInfos[0].Id
|
|
|
|
post := &model.Post{
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "Test post with file",
|
|
FileIds: model.StringArray{fileId},
|
|
}
|
|
rpost, resp, err := client.CreatePost(context.Background(), post)
|
|
require.NoError(t, err)
|
|
CheckCreatedStatus(t, resp)
|
|
require.NotNil(t, rpost)
|
|
assert.Contains(t, rpost.FileIds, fileId)
|
|
})
|
|
|
|
t.Run("CreateAt should match the one provided in the request", func(t *testing.T) {
|
|
post := basicPost()
|
|
post.CreateAt = 123
|
|
rpost, resp, err := th.SystemAdminClient.CreatePost(context.Background(), post)
|
|
require.NoError(t, err)
|
|
CheckCreatedStatus(t, resp)
|
|
assert.Equal(t, post.CreateAt, rpost.CreateAt, "create at should match")
|
|
})
|
|
|
|
t.Run("Should not be able to define the RemoteId of a post from the API", func(t *testing.T) {
|
|
newPost := &model.Post{
|
|
RemoteId: model.NewPointer(model.NewId()),
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "post content " + model.NewId(),
|
|
DeleteAt: 0,
|
|
}
|
|
|
|
respPost, resp, err := th.SystemAdminClient.CreatePost(context.Background(), newPost)
|
|
require.NoError(t, err)
|
|
CheckCreatedStatus(t, resp)
|
|
require.Zero(t, *respPost.RemoteId)
|
|
|
|
createdPost, appErr := th.App.GetSinglePost(th.Context, respPost.Id, false)
|
|
require.Nil(t, appErr)
|
|
require.Zero(t, *createdPost.RemoteId)
|
|
})
|
|
}
|
|
|
|
func TestCreatePostForPriority(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
|
|
th := Setup(t).InitBasic(t)
|
|
client := th.Client
|
|
|
|
th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuProfessional))
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.ServiceSettings.PostPriority = true
|
|
*cfg.ServiceSettings.AllowPersistentNotifications = true
|
|
})
|
|
|
|
t.Run("should return forbidden when post-priority is disabled", func(t *testing.T) {
|
|
originalPrioritySetting := *th.App.Config().ServiceSettings.PostPriority
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.ServiceSettings.PostPriority = false
|
|
})
|
|
|
|
defer th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.ServiceSettings.PostPriority = originalPrioritySetting
|
|
})
|
|
|
|
post := &model.Post{ChannelId: th.BasicChannel.Id, Message: "test", Metadata: &model.PostMetadata{
|
|
Priority: &model.PostPriority{
|
|
Priority: model.NewPointer("urgent"),
|
|
},
|
|
}}
|
|
|
|
_, resp, err := client.CreatePost(context.Background(), post)
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
})
|
|
|
|
t.Run("should return badRequest when priority is set for reply post", func(t *testing.T) {
|
|
rootPost := &model.Post{ChannelId: th.BasicChannel.Id, Message: "root"}
|
|
|
|
post, resp, err := client.CreatePost(context.Background(), rootPost)
|
|
require.NoError(t, err)
|
|
CheckCreatedStatus(t, resp)
|
|
|
|
replyPost := &model.Post{RootId: post.Id, ChannelId: th.BasicChannel.Id, Message: "reply", Metadata: &model.PostMetadata{
|
|
Priority: &model.PostPriority{
|
|
Priority: model.NewPointer("urgent"),
|
|
},
|
|
}}
|
|
_, resp, err = client.CreatePost(context.Background(), replyPost)
|
|
require.Error(t, err)
|
|
CheckBadRequestStatus(t, resp)
|
|
})
|
|
|
|
t.Run("should return statusNotImplemented when min. pro. license not available", func(t *testing.T) {
|
|
appErr := th.App.Srv().RemoveLicense()
|
|
require.Nil(t, appErr)
|
|
defer th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuProfessional))
|
|
// for Acknowledment
|
|
p1 := &model.Post{ChannelId: th.BasicChannel.Id, Message: "test", Metadata: &model.PostMetadata{
|
|
Priority: &model.PostPriority{
|
|
Priority: model.NewPointer("urgent"),
|
|
RequestedAck: model.NewPointer(true),
|
|
},
|
|
}}
|
|
_, resp, err := client.CreatePost(context.Background(), p1)
|
|
require.Error(t, err)
|
|
CheckNotImplementedStatus(t, resp)
|
|
|
|
// for Persistent Notification
|
|
p2 := &model.Post{ChannelId: th.BasicChannel.Id, Message: "test", Metadata: &model.PostMetadata{
|
|
Priority: &model.PostPriority{
|
|
Priority: model.NewPointer("urgent"),
|
|
PersistentNotifications: model.NewPointer(true),
|
|
},
|
|
}}
|
|
_, resp, err = client.CreatePost(context.Background(), p2)
|
|
require.Error(t, err)
|
|
CheckNotImplementedStatus(t, resp)
|
|
})
|
|
|
|
t.Run("should return forbidden when persistent notification not enabled", func(t *testing.T) {
|
|
originalSetting := *th.App.Config().ServiceSettings.AllowPersistentNotifications
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.ServiceSettings.AllowPersistentNotifications = false
|
|
})
|
|
|
|
defer th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.ServiceSettings.AllowPersistentNotifications = originalSetting
|
|
})
|
|
|
|
p1 := &model.Post{ChannelId: th.BasicChannel.Id, Message: "test", Metadata: &model.PostMetadata{
|
|
Priority: &model.PostPriority{
|
|
Priority: model.NewPointer("urgent"),
|
|
PersistentNotifications: model.NewPointer(true),
|
|
},
|
|
}}
|
|
_, resp, err := client.CreatePost(context.Background(), p1)
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
})
|
|
|
|
t.Run("should return badRequest when post is not urgent for persistent notification", func(t *testing.T) {
|
|
p1 := &model.Post{ChannelId: th.BasicChannel.Id, Message: "test", Metadata: &model.PostMetadata{
|
|
Priority: &model.PostPriority{
|
|
Priority: model.NewPointer("important"),
|
|
PersistentNotifications: model.NewPointer(true),
|
|
},
|
|
}}
|
|
_, resp, err := client.CreatePost(context.Background(), p1)
|
|
require.Error(t, err)
|
|
CheckBadRequestStatus(t, resp)
|
|
})
|
|
|
|
t.Run("should return forbidden when persistent notification is disabled for guest users", func(t *testing.T) {
|
|
originalSetting := *th.App.Config().ServiceSettings.AllowPersistentNotificationsForGuests
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.ServiceSettings.AllowPersistentNotificationsForGuests = false
|
|
})
|
|
defer th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.ServiceSettings.AllowPersistentNotificationsForGuests = originalSetting
|
|
})
|
|
|
|
appErr := th.App.DemoteUserToGuest(th.Context, th.BasicUser)
|
|
require.Nil(t, appErr)
|
|
defer func() {
|
|
appErr = th.App.PromoteGuestToUser(th.Context, th.BasicUser, th.SystemAdminUser.Id)
|
|
require.Nil(t, appErr)
|
|
}()
|
|
|
|
p1 := &model.Post{ChannelId: th.BasicChannel.Id, Message: "test", Metadata: &model.PostMetadata{
|
|
Priority: &model.PostPriority{
|
|
Priority: model.NewPointer("urgent"),
|
|
PersistentNotifications: model.NewPointer(true),
|
|
},
|
|
}}
|
|
_, resp, err := client.CreatePost(context.Background(), p1)
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
})
|
|
|
|
t.Run("should create priority post", func(t *testing.T) {
|
|
p1 := &model.Post{ChannelId: th.BasicChannel.Id, Message: "test", Metadata: &model.PostMetadata{
|
|
Priority: &model.PostPriority{
|
|
Priority: model.NewPointer("important"),
|
|
},
|
|
}}
|
|
_, resp, err := client.CreatePost(context.Background(), p1)
|
|
require.NoError(t, err)
|
|
CheckCreatedStatus(t, resp)
|
|
})
|
|
|
|
t.Run("should create acknowledge post", func(t *testing.T) {
|
|
p1 := &model.Post{ChannelId: th.BasicChannel.Id, Message: "test", Metadata: &model.PostMetadata{
|
|
Priority: &model.PostPriority{
|
|
Priority: model.NewPointer(""),
|
|
RequestedAck: model.NewPointer(true),
|
|
},
|
|
}}
|
|
_, resp, err := client.CreatePost(context.Background(), p1)
|
|
require.NoError(t, err)
|
|
CheckCreatedStatus(t, resp)
|
|
})
|
|
|
|
t.Run("should create persistent notification post", func(t *testing.T) {
|
|
p1 := &model.Post{ChannelId: th.BasicChannel.Id, Message: "test @" + th.BasicUser2.Username, Metadata: &model.PostMetadata{
|
|
Priority: &model.PostPriority{
|
|
Priority: model.NewPointer("urgent"),
|
|
RequestedAck: model.NewPointer(false),
|
|
PersistentNotifications: model.NewPointer(true),
|
|
},
|
|
}}
|
|
_, resp, err := client.CreatePost(context.Background(), p1)
|
|
require.NoError(t, err)
|
|
CheckCreatedStatus(t, resp)
|
|
})
|
|
}
|
|
|
|
func TestCreatePostWithOAuthClient(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
originalOAuthSetting := *th.App.Config().ServiceSettings.EnableOAuthServiceProvider
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.ServiceSettings.EnableOAuthServiceProvider = true
|
|
})
|
|
|
|
defer th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.ServiceSettings.EnableOAuthServiceProvider = originalOAuthSetting
|
|
})
|
|
|
|
oAuthApp, appErr := th.App.CreateOAuthApp(&model.OAuthApp{
|
|
CreatorId: th.SystemAdminUser.Id,
|
|
Name: "name",
|
|
CallbackUrls: []string{"http://test.com"},
|
|
Homepage: "http://test.com",
|
|
})
|
|
require.Nil(t, appErr, "should create an OAuthApp")
|
|
|
|
session, appErr := th.App.CreateSession(th.Context, &model.Session{
|
|
UserId: th.BasicUser.Id,
|
|
Token: "token",
|
|
IsOAuth: true,
|
|
Props: model.StringMap{model.SessionPropOAuthAppID: oAuthApp.Id},
|
|
})
|
|
require.Nil(t, appErr, "should create a session")
|
|
|
|
post, _, err := th.Client.CreatePost(context.Background(), &model.Post{
|
|
ChannelId: th.BasicPost.ChannelId,
|
|
Message: "test message",
|
|
})
|
|
require.NoError(t, err)
|
|
assert.NotContains(t, post.GetProps(), model.PostPropsFromOAuthApp, fmt.Sprintf("contains %s prop when not using OAuth client", model.PostPropsOverrideUsername))
|
|
|
|
client := th.CreateClient()
|
|
client.SetOAuthToken(session.Token)
|
|
post, _, err = client.CreatePost(context.Background(), &model.Post{
|
|
ChannelId: th.BasicPost.ChannelId,
|
|
Message: "test message",
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
assert.Contains(t, post.GetProps(), model.PostPropsFromOAuthApp, fmt.Sprintf("missing %s prop when using OAuth client", model.PostPropsOverrideUsername))
|
|
|
|
t.Run("allow username and icon overrides", func(t *testing.T) {
|
|
originalHardenedModeSetting := *th.App.Config().ServiceSettings.ExperimentalEnableHardenedMode
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.ServiceSettings.ExperimentalEnableHardenedMode = true
|
|
})
|
|
|
|
defer th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.ServiceSettings.ExperimentalEnableHardenedMode = originalHardenedModeSetting
|
|
})
|
|
|
|
post, _, err = client.CreatePost(context.Background(), &model.Post{
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "test message",
|
|
Props: model.StringInterface{model.PostPropsOverrideUsername: "newUsernameValue", model.PostPropsOverrideIconURL: "iconUrlOverrideValue"},
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
assert.Contains(t, post.GetProps(), model.PostPropsOverrideUsername, fmt.Sprintf("missing %s prop when using OAuth client", model.PostPropsOverrideUsername))
|
|
assert.Contains(t, post.GetProps(), model.PostPropsOverrideIconURL, fmt.Sprintf("missing %s prop when using OAuth client", model.PostPropsOverrideIconURL))
|
|
})
|
|
}
|
|
|
|
func TestCreatePostEphemeral(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
|
|
th := Setup(t).InitBasic(t)
|
|
client := th.SystemAdminClient
|
|
|
|
ephemeralPost := &model.PostEphemeral{
|
|
UserID: th.BasicUser2.Id,
|
|
Post: &model.Post{ChannelId: th.BasicChannel.Id, Message: "a" + model.NewId() + "a", Props: model.StringInterface{model.PropsAddChannelMember: "no good"}},
|
|
}
|
|
|
|
rpost, resp, err := client.CreatePostEphemeral(context.Background(), ephemeralPost)
|
|
require.NoError(t, err)
|
|
CheckCreatedStatus(t, resp)
|
|
require.Equal(t, ephemeralPost.Post.Message, rpost.Message, "message didn't match")
|
|
require.Equal(t, 0, int(rpost.EditAt), "newly created ephemeral post shouldn't have EditAt set")
|
|
|
|
r, err := client.DoAPIPost(context.Background(), "/posts/ephemeral", "garbage")
|
|
require.Error(t, err)
|
|
require.Equal(t, http.StatusBadRequest, r.StatusCode)
|
|
|
|
_, err = client.Logout(context.Background())
|
|
require.NoError(t, err)
|
|
_, resp, err = client.CreatePostEphemeral(context.Background(), ephemeralPost)
|
|
require.Error(t, err)
|
|
CheckUnauthorizedStatus(t, resp)
|
|
|
|
client = th.Client
|
|
_, resp, err = client.CreatePostEphemeral(context.Background(), ephemeralPost)
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
}
|
|
|
|
func testCreatePostWithOutgoingHook(
|
|
t *testing.T,
|
|
hookContentType, expectedContentType, message, triggerWord string,
|
|
fileIds []string,
|
|
triggerWhen int,
|
|
commentPostType bool,
|
|
) {
|
|
mainHelper.Parallel(t)
|
|
|
|
th := Setup(t).InitBasic(t)
|
|
user := th.SystemAdminUser
|
|
team := th.BasicTeam
|
|
channel := th.BasicChannel
|
|
|
|
enableOutgoingWebhooks := *th.App.Config().ServiceSettings.EnableOutgoingWebhooks
|
|
allowedUntrustedInternalConnections := *th.App.Config().ServiceSettings.AllowedUntrustedInternalConnections
|
|
defer func() {
|
|
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOutgoingWebhooks = enableOutgoingWebhooks })
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.ServiceSettings.AllowedUntrustedInternalConnections = allowedUntrustedInternalConnections
|
|
})
|
|
}()
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOutgoingWebhooks = true })
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.ServiceSettings.AllowedUntrustedInternalConnections = "localhost,127.0.0.1"
|
|
})
|
|
|
|
var hook *model.OutgoingWebhook
|
|
var post *model.Post
|
|
|
|
// Create a test server that is the target of the outgoing webhook. It will
|
|
// validate the webhook body fields and write to the success channel on
|
|
// success/failure.
|
|
success := make(chan bool)
|
|
wait := make(chan bool, 1)
|
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
<-wait
|
|
|
|
requestContentType := r.Header.Get("Content-Type")
|
|
if requestContentType != expectedContentType {
|
|
t.Logf("Content-Type is %s, should be %s", requestContentType, expectedContentType)
|
|
success <- false
|
|
return
|
|
}
|
|
|
|
expectedPayload := &model.OutgoingWebhookPayload{
|
|
Token: hook.Token,
|
|
TeamId: hook.TeamId,
|
|
TeamDomain: team.Name,
|
|
ChannelId: post.ChannelId,
|
|
ChannelName: channel.Name,
|
|
Timestamp: post.CreateAt,
|
|
UserId: post.UserId,
|
|
UserName: user.Username,
|
|
PostId: post.Id,
|
|
Text: post.Message,
|
|
TriggerWord: triggerWord,
|
|
FileIds: strings.Join(post.FileIds, ","),
|
|
}
|
|
|
|
// depending on the Content-Type, we expect to find a JSON or form encoded payload
|
|
if requestContentType == "application/json" {
|
|
decoder := json.NewDecoder(r.Body)
|
|
o := &model.OutgoingWebhookPayload{}
|
|
err := decoder.Decode(&o)
|
|
if err != nil {
|
|
th.TestLogger.Warn("Error decoding body", mlog.Err(err))
|
|
}
|
|
|
|
if !reflect.DeepEqual(expectedPayload, o) {
|
|
t.Logf("JSON payload is %+v, should be %+v", o, expectedPayload)
|
|
success <- false
|
|
return
|
|
}
|
|
} else {
|
|
err := r.ParseForm()
|
|
if err != nil {
|
|
t.Logf("Error parsing form: %q", err)
|
|
success <- false
|
|
return
|
|
}
|
|
|
|
expectedFormValues, _ := url.ParseQuery(expectedPayload.ToFormValues())
|
|
|
|
if !reflect.DeepEqual(expectedFormValues, r.Form) {
|
|
t.Logf("Form values are: %q\n, should be: %q\n", r.Form, expectedFormValues)
|
|
success <- false
|
|
return
|
|
}
|
|
}
|
|
|
|
respPostType := "" // if is empty or post will do a normal post.
|
|
if commentPostType {
|
|
respPostType = model.OutgoingHookResponseTypeComment
|
|
}
|
|
|
|
outGoingHookResponse := &model.OutgoingWebhookResponse{
|
|
Text: model.NewPointer("some test text"),
|
|
Username: "TestCommandServer",
|
|
IconURL: "https://mattermost.com/wp-content/uploads/2022/02/icon.png",
|
|
Type: "custom_as",
|
|
ResponseType: respPostType,
|
|
}
|
|
|
|
hookJSON, jsonErr := json.Marshal(outGoingHookResponse)
|
|
require.NoError(t, jsonErr)
|
|
_, err := w.Write(hookJSON)
|
|
require.NoError(t, err)
|
|
success <- true
|
|
}))
|
|
defer ts.Close()
|
|
|
|
// create an outgoing webhook, passing it the test server URL
|
|
var triggerWords []string
|
|
if triggerWord != "" {
|
|
triggerWords = []string{triggerWord}
|
|
}
|
|
|
|
hook = &model.OutgoingWebhook{
|
|
ChannelId: channel.Id,
|
|
TeamId: team.Id,
|
|
ContentType: hookContentType,
|
|
TriggerWords: triggerWords,
|
|
TriggerWhen: triggerWhen,
|
|
CallbackURLs: []string{ts.URL},
|
|
}
|
|
|
|
hook, _, err := th.SystemAdminClient.CreateOutgoingWebhook(context.Background(), hook)
|
|
require.NoError(t, err)
|
|
|
|
// create a post to trigger the webhook
|
|
post = &model.Post{
|
|
ChannelId: channel.Id,
|
|
Message: message,
|
|
FileIds: fileIds,
|
|
}
|
|
|
|
post, _, err = th.SystemAdminClient.CreatePost(context.Background(), post)
|
|
require.NoError(t, err)
|
|
|
|
wait <- true
|
|
|
|
// We wait for the test server to write to the success channel and we make
|
|
// the test fail if that doesn't happen before the timeout.
|
|
select {
|
|
case ok := <-success:
|
|
require.True(t, ok, "Test server did send an invalid webhook.")
|
|
case <-time.After(2 * time.Second):
|
|
require.FailNow(t, "Timeout, test server did not send the webhook.")
|
|
}
|
|
|
|
if commentPostType {
|
|
time.Sleep(time.Millisecond * 100)
|
|
postList, _, err := th.SystemAdminClient.GetPostThread(context.Background(), post.Id, "", false)
|
|
require.NoError(t, err)
|
|
require.Equal(t, post.Id, postList.Order[0], "wrong order")
|
|
|
|
_, ok := postList.Posts[post.Id]
|
|
require.True(t, ok, "should have had post")
|
|
require.Len(t, postList.Posts, 2, "should have 2 posts")
|
|
}
|
|
}
|
|
|
|
func TestCreatePostWithOutgoingHook_form_urlencoded(t *testing.T) {
|
|
t.Run("Case 1", func(t *testing.T) {
|
|
testCreatePostWithOutgoingHook(t, "application/x-www-form-urlencoded", "application/x-www-form-urlencoded", "triggerword lorem ipsum", "triggerword", []string{"file_id_1"}, app.TriggerwordsExactMatch, false)
|
|
})
|
|
t.Run("Case 2", func(t *testing.T) {
|
|
testCreatePostWithOutgoingHook(t, "application/x-www-form-urlencoded", "application/x-www-form-urlencoded", "triggerwordaaazzz lorem ipsum", "triggerword", []string{"file_id_1"}, app.TriggerwordsStartsWith, false)
|
|
})
|
|
t.Run("Case 3", func(t *testing.T) {
|
|
testCreatePostWithOutgoingHook(t, "application/x-www-form-urlencoded", "application/x-www-form-urlencoded", "", "", []string{"file_id_1"}, app.TriggerwordsExactMatch, false)
|
|
})
|
|
t.Run("Case 4", func(t *testing.T) {
|
|
testCreatePostWithOutgoingHook(t, "application/x-www-form-urlencoded", "application/x-www-form-urlencoded", "", "", []string{"file_id_1"}, app.TriggerwordsStartsWith, false)
|
|
})
|
|
t.Run("Case 5", func(t *testing.T) {
|
|
testCreatePostWithOutgoingHook(t, "application/x-www-form-urlencoded", "application/x-www-form-urlencoded", "triggerword lorem ipsum", "triggerword", []string{"file_id_1"}, app.TriggerwordsExactMatch, true)
|
|
})
|
|
t.Run("Case 6", func(t *testing.T) {
|
|
testCreatePostWithOutgoingHook(t, "application/x-www-form-urlencoded", "application/x-www-form-urlencoded", "triggerwordaaazzz lorem ipsum", "triggerword", []string{"file_id_1"}, app.TriggerwordsStartsWith, true)
|
|
})
|
|
}
|
|
|
|
func TestCreatePostWithOutgoingHook_json(t *testing.T) {
|
|
t.Run("Case 1", func(t *testing.T) {
|
|
testCreatePostWithOutgoingHook(t, "application/json", "application/json", "triggerword lorem ipsum", "triggerword", []string{"file_id_1, file_id_2"}, app.TriggerwordsExactMatch, false)
|
|
})
|
|
t.Run("Case 2", func(t *testing.T) {
|
|
testCreatePostWithOutgoingHook(t, "application/json", "application/json", "triggerwordaaazzz lorem ipsum", "triggerword", []string{"file_id_1, file_id_2"}, app.TriggerwordsStartsWith, false)
|
|
})
|
|
t.Run("Case 3", func(t *testing.T) {
|
|
testCreatePostWithOutgoingHook(t, "application/json", "application/json", "triggerword lorem ipsum", "", []string{"file_id_1"}, app.TriggerwordsExactMatch, false)
|
|
})
|
|
t.Run("Case 4", func(t *testing.T) {
|
|
testCreatePostWithOutgoingHook(t, "application/json", "application/json", "triggerwordaaazzz lorem ipsum", "", []string{"file_id_1"}, app.TriggerwordsStartsWith, false)
|
|
})
|
|
t.Run("Case 5", func(t *testing.T) {
|
|
testCreatePostWithOutgoingHook(t, "application/json", "application/json", "triggerword lorem ipsum", "triggerword", []string{"file_id_1, file_id_2"}, app.TriggerwordsExactMatch, true)
|
|
})
|
|
t.Run("Case 6", func(t *testing.T) {
|
|
testCreatePostWithOutgoingHook(t, "application/json", "application/json", "triggerwordaaazzz lorem ipsum", "", []string{"file_id_1"}, app.TriggerwordsStartsWith, true)
|
|
})
|
|
}
|
|
|
|
// hooks created before we added the ContentType field should be considered as
|
|
// application/x-www-form-urlencoded
|
|
func TestCreatePostWithOutgoingHook_no_content_type(t *testing.T) {
|
|
t.Run("Case 1", func(t *testing.T) {
|
|
testCreatePostWithOutgoingHook(t, "", "application/x-www-form-urlencoded", "triggerword lorem ipsum", "triggerword", []string{"file_id_1"}, app.TriggerwordsExactMatch, false)
|
|
})
|
|
t.Run("Case 2", func(t *testing.T) {
|
|
testCreatePostWithOutgoingHook(t, "", "application/x-www-form-urlencoded", "triggerwordaaazzz lorem ipsum", "triggerword", []string{"file_id_1"}, app.TriggerwordsStartsWith, false)
|
|
})
|
|
t.Run("Case 3", func(t *testing.T) {
|
|
testCreatePostWithOutgoingHook(t, "", "application/x-www-form-urlencoded", "triggerword lorem ipsum", "", []string{"file_id_1, file_id_2"}, app.TriggerwordsExactMatch, false)
|
|
})
|
|
t.Run("Case 4", func(t *testing.T) {
|
|
testCreatePostWithOutgoingHook(t, "", "application/x-www-form-urlencoded", "triggerwordaaazzz lorem ipsum", "", []string{"file_id_1, file_id_2"}, app.TriggerwordsStartsWith, false)
|
|
})
|
|
t.Run("Case 5", func(t *testing.T) {
|
|
testCreatePostWithOutgoingHook(t, "", "application/x-www-form-urlencoded", "triggerword lorem ipsum", "triggerword", []string{"file_id_1"}, app.TriggerwordsExactMatch, true)
|
|
})
|
|
t.Run("Case 6", func(t *testing.T) {
|
|
testCreatePostWithOutgoingHook(t, "", "application/x-www-form-urlencoded", "triggerword lorem ipsum", "", []string{"file_id_1, file_id_2"}, app.TriggerwordsExactMatch, true)
|
|
})
|
|
}
|
|
|
|
func TestMoveThread(t *testing.T) {
|
|
os.Setenv("MM_FEATUREFLAGS_MOVETHREADSENABLED", "true")
|
|
defer os.Unsetenv("MM_FEATUREFLAGS_MOVETHREADSENABLED")
|
|
th := SetupEnterprise(t).InitBasic(t)
|
|
|
|
th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterprise))
|
|
|
|
client := th.Client
|
|
|
|
ctx := context.Background()
|
|
|
|
basicUser1 := th.BasicUser
|
|
basicUser2 := th.BasicUser2
|
|
basicUser3 := th.CreateUser(t)
|
|
|
|
// Helper function to create a new public channel to move the post to
|
|
createPublicChannel := func(teamId, name, displayName string) *model.Channel {
|
|
channel, resp, err := client.CreateChannel(ctx, &model.Channel{
|
|
TeamId: teamId,
|
|
Name: name,
|
|
DisplayName: displayName,
|
|
Type: model.ChannelTypeOpen,
|
|
})
|
|
require.NoError(t, err)
|
|
require.NotNil(t, resp)
|
|
require.NotNil(t, channel)
|
|
|
|
return channel
|
|
}
|
|
|
|
// Create a new private channel to move the post to
|
|
privateChannel, resp, err := client.CreateChannel(ctx, &model.Channel{
|
|
TeamId: th.BasicTeam.Id,
|
|
Name: "test-private-channel",
|
|
DisplayName: "Test Private Channel",
|
|
Type: model.ChannelTypePrivate,
|
|
})
|
|
require.NoError(t, err)
|
|
require.NotNil(t, resp)
|
|
require.NotNil(t, privateChannel)
|
|
|
|
// Create a new direct message channel to move the post to
|
|
dmChannel, resp, err := client.CreateDirectChannel(ctx, basicUser1.Id, basicUser2.Id)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, resp)
|
|
require.NotNil(t, dmChannel)
|
|
|
|
// Create a new group message channel to move the post to
|
|
gmChannel, resp, err := client.CreateGroupChannel(ctx, []string{basicUser1.Id, basicUser2.Id, basicUser3.Id})
|
|
require.NoError(t, err)
|
|
require.NotNil(t, resp)
|
|
require.NotNil(t, gmChannel)
|
|
t.Run("Move to public channel", func(t *testing.T) {
|
|
// Create a public channel
|
|
publicChannel := createPublicChannel(th.BasicTeam.Id, "test-public-channel", "Test Public Channel")
|
|
|
|
// Create a new post to move
|
|
post := &model.Post{
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "test post",
|
|
}
|
|
newPost, resp, err := client.CreatePost(ctx, post)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, resp)
|
|
require.NotNil(t, newPost)
|
|
|
|
// Move the post to the public channel
|
|
moveThreadParams := &model.MoveThreadParams{
|
|
ChannelId: publicChannel.Id,
|
|
}
|
|
resp, err = client.MoveThread(ctx, newPost.Id, moveThreadParams)
|
|
require.NoError(t, err)
|
|
require.Equal(t, http.StatusOK, resp.StatusCode)
|
|
|
|
// Check that the post was moved to the public channel
|
|
posts, resp, err := client.GetPostsForChannel(ctx, publicChannel.Id, 0, 100, "", true, false)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, resp)
|
|
require.NotNil(t, posts)
|
|
// There should be 2 posts, the system join message for the user who moved it joining the channel, and the post we moved
|
|
require.Equal(t, 2, len(posts.Posts))
|
|
require.Equal(t, newPost.Message, posts.Posts[posts.Order[0]].Message)
|
|
})
|
|
|
|
t.Run("Move to private channel", func(t *testing.T) {
|
|
// Create a new post to move
|
|
post := &model.Post{
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "test post",
|
|
}
|
|
newPost, resp, err := client.CreatePost(ctx, post)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, resp)
|
|
require.NotNil(t, newPost)
|
|
|
|
// Move the post to the private channel
|
|
moveThreadParams := &model.MoveThreadParams{
|
|
ChannelId: privateChannel.Id,
|
|
}
|
|
resp, err = client.MoveThread(ctx, newPost.Id, moveThreadParams)
|
|
require.NoError(t, err)
|
|
require.Equal(t, http.StatusOK, resp.StatusCode)
|
|
|
|
// Check that the post was moved to the private channel
|
|
posts, resp, err := client.GetPostsForChannel(ctx, privateChannel.Id, 0, 100, "", true, false)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, resp)
|
|
require.NotNil(t, posts)
|
|
// There should be 2 posts, the system join message for the user who moved it joining the channel, and the post we moved
|
|
require.Equal(t, 2, len(posts.Posts))
|
|
require.Equal(t, newPost.Message, posts.Posts[posts.Order[0]].Message)
|
|
})
|
|
|
|
t.Run("Move to direct message channel", func(t *testing.T) {
|
|
// Create a new post to move
|
|
post := &model.Post{
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "test post",
|
|
}
|
|
newPost, resp, err := client.CreatePost(ctx, post)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, resp)
|
|
require.NotNil(t, newPost)
|
|
|
|
// Move the post to the direct message channel
|
|
moveThreadParams := &model.MoveThreadParams{
|
|
ChannelId: dmChannel.Id,
|
|
}
|
|
resp, err = client.MoveThread(ctx, newPost.Id, moveThreadParams)
|
|
require.NoError(t, err)
|
|
require.Equal(t, http.StatusOK, resp.StatusCode)
|
|
|
|
// Check that the post was moved to the direct message channel
|
|
posts, resp, err := client.GetPostsForChannel(ctx, dmChannel.Id, 0, 100, "", true, false)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, resp)
|
|
require.NotNil(t, posts)
|
|
// There should be 1 post, the post we moved
|
|
require.Equal(t, 1, len(posts.Posts))
|
|
require.Equal(t, newPost.Message, posts.Posts[posts.Order[0]].Message)
|
|
})
|
|
|
|
t.Run("Move to group message channel", func(t *testing.T) {
|
|
// Create a new post to move
|
|
post := &model.Post{
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "test post",
|
|
}
|
|
newPost, resp, err := client.CreatePost(ctx, post)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, resp)
|
|
require.NotNil(t, newPost)
|
|
|
|
// Move the post to the group message channel
|
|
moveThreadParams := &model.MoveThreadParams{
|
|
ChannelId: gmChannel.Id,
|
|
}
|
|
resp, err = client.MoveThread(ctx, newPost.Id, moveThreadParams)
|
|
require.NoError(t, err)
|
|
require.Equal(t, http.StatusOK, resp.StatusCode)
|
|
|
|
// Check that the post was moved to the group message channel
|
|
posts, resp, err := client.GetPostsForChannel(ctx, gmChannel.Id, 0, 100, "", true, false)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, resp)
|
|
require.NotNil(t, posts)
|
|
// There should be 1 post, the post we moved
|
|
require.Equal(t, 1, len(posts.Posts))
|
|
require.Equal(t, newPost.Message, posts.Posts[posts.Order[0]].Message)
|
|
})
|
|
|
|
t.Run("Move thread with more than one post", func(t *testing.T) {
|
|
// Create a new public channel to move the post to
|
|
pChannel, resp, err := client.CreateChannel(ctx, &model.Channel{
|
|
TeamId: th.BasicTeam.Id,
|
|
Name: "test-public-channel2",
|
|
DisplayName: "Test Public Channel",
|
|
Type: model.ChannelTypeOpen,
|
|
})
|
|
require.NoError(t, err)
|
|
require.NotNil(t, resp)
|
|
require.NotNil(t, pChannel)
|
|
// Create a new post to use as the root post
|
|
rootPost := &model.Post{
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "root post",
|
|
}
|
|
rootPost, resp, err = client.CreatePost(ctx, rootPost)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, resp)
|
|
require.NotNil(t, rootPost)
|
|
|
|
// Create a new post to move
|
|
post := &model.Post{
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "test post",
|
|
RootId: rootPost.Id,
|
|
}
|
|
newPost, resp, err := client.CreatePost(ctx, post)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, resp)
|
|
require.NotNil(t, newPost)
|
|
|
|
// Create another post in the thread
|
|
post = &model.Post{
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "test post 2",
|
|
RootId: rootPost.Id,
|
|
}
|
|
newPost2, resp, err := client.CreatePost(ctx, post)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, resp)
|
|
require.NotNil(t, newPost2)
|
|
|
|
// Move the thread to the public channel
|
|
moveThreadParams := &model.MoveThreadParams{
|
|
ChannelId: pChannel.Id,
|
|
}
|
|
resp, err = client.MoveThread(ctx, rootPost.Id, moveThreadParams)
|
|
require.NoError(t, err)
|
|
require.Equal(t, http.StatusOK, resp.StatusCode)
|
|
|
|
// Check that the thread was moved to the public channel
|
|
posts, resp, err := client.GetPostsForChannel(ctx, pChannel.Id, 0, 100, "", false, false)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, resp)
|
|
require.NotNil(t, posts)
|
|
require.Equal(t, "This thread was moved from another channel", posts.Posts[posts.Order[0]].Message)
|
|
require.Equal(t, newPost2.Message, posts.Posts[posts.Order[1]].Message)
|
|
require.Equal(t, newPost.Message, posts.Posts[posts.Order[2]].Message)
|
|
require.Equal(t, rootPost.Message, posts.Posts[posts.Order[3]].Message)
|
|
})
|
|
|
|
t.Run("Move thread when permitted role is channel admin", func(t *testing.T) {
|
|
// Create public channel
|
|
publicChannel := createPublicChannel(th.BasicTeam.Id, "test-public-channel-admin", "Test Public Channel Admin")
|
|
|
|
// Set permitted role as channel admin
|
|
enabled := true
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
cfg.WranglerSettings = model.WranglerSettings{
|
|
MoveThreadToAnotherTeamEnable: &enabled,
|
|
PermittedWranglerRoles: []string{model.PermissionsChannelAdmin},
|
|
}
|
|
})
|
|
defer th.App.UpdateConfig(func(cfg *model.Config) {
|
|
cfg.WranglerSettings = model.WranglerSettings{}
|
|
})
|
|
|
|
// Login as channel admin and add to channel
|
|
th.LoginTeamAdmin(t)
|
|
th.AddUserToChannel(t, th.TeamAdminUser, publicChannel)
|
|
defer th.LoginBasic(t)
|
|
|
|
// Create a new post to move
|
|
post := &model.Post{
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "test post",
|
|
}
|
|
newPost, resp, err := client.CreatePost(ctx, post)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, resp)
|
|
require.NotNil(t, newPost)
|
|
|
|
// Move the post to the public channel
|
|
moveThreadParams := &model.MoveThreadParams{
|
|
ChannelId: publicChannel.Id,
|
|
}
|
|
resp, err = client.MoveThread(ctx, newPost.Id, moveThreadParams)
|
|
require.NoError(t, err)
|
|
require.Equal(t, http.StatusOK, resp.StatusCode)
|
|
|
|
// Check that the post was moved to the public channel
|
|
posts, resp, err := client.GetPostsForChannel(ctx, publicChannel.Id, 0, 100, "", true, false)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, resp)
|
|
require.NotNil(t, posts)
|
|
// There should be 2 posts, the system join message for the user who moved it joining the channel, and the post we moved
|
|
require.Equal(t, 2, len(posts.Posts))
|
|
require.Equal(t, newPost.Message, posts.Posts[posts.Order[0]].Message)
|
|
})
|
|
|
|
t.Run("check permissions limited by AllowedEmailDomain", func(t *testing.T) {
|
|
th.App.UpdateConfig(func(c *model.Config) {
|
|
c.WranglerSettings.AllowedEmailDomain = []string{"foo.com", "bar.com"}
|
|
})
|
|
t.Cleanup(func() {
|
|
th.App.UpdateConfig(func(c *model.Config) {
|
|
c.WranglerSettings.AllowedEmailDomain = make([]string, 0)
|
|
})
|
|
})
|
|
|
|
// Create a public channel
|
|
publicChannel := createPublicChannel(th.BasicTeam.Id, "test-public-channel-allowed-email-domain", "Test Public Channel")
|
|
|
|
// Create a new post to move
|
|
post := &model.Post{
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "test post",
|
|
}
|
|
newPost, resp, err := client.CreatePost(ctx, post)
|
|
require.NoError(t, err)
|
|
CheckCreatedStatus(t, resp)
|
|
require.NotNil(t, newPost)
|
|
|
|
// Move the post to the public channel as a user without the configured domain
|
|
moveThreadParams := &model.MoveThreadParams{
|
|
ChannelId: publicChannel.Id,
|
|
}
|
|
resp, err = client.MoveThread(ctx, newPost.Id, moveThreadParams)
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
|
|
// Change the email domain to match the configured setting
|
|
th.BasicUser.Email = "basicuser@foo.com"
|
|
_, resp, err = client.UpdateUser(ctx, th.BasicUser)
|
|
require.NoError(t, err)
|
|
CheckOKStatus(t, resp)
|
|
|
|
resp, err = client.MoveThread(ctx, newPost.Id, moveThreadParams)
|
|
require.NoError(t, err)
|
|
CheckOKStatus(t, resp)
|
|
|
|
// Check that the post was moved to the public channel
|
|
posts, resp, err := client.GetPostsForChannel(ctx, publicChannel.Id, 0, 100, "", true, false)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, resp)
|
|
require.NotNil(t, posts)
|
|
// There should be 2 posts, the system join message for the user who moved it joining the channel, and the post we moved
|
|
require.Equal(t, 2, len(posts.Posts))
|
|
require.Equal(t, newPost.Message, posts.Posts[posts.Order[0]].Message)
|
|
})
|
|
}
|
|
|
|
func TestCreatePostPublic(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
|
|
th := Setup(t).InitBasic(t)
|
|
client := th.Client
|
|
|
|
post := &model.Post{ChannelId: th.BasicChannel.Id, Message: "#hashtag a" + model.NewId() + "a"}
|
|
|
|
user := model.User{Email: th.GenerateTestEmail(), Nickname: "Joram Wilander", Password: "hello1", Username: GenerateTestUsername(), Roles: model.SystemUserRoleId}
|
|
|
|
ruser, _, err := client.CreateUser(context.Background(), &user)
|
|
require.NoError(t, err)
|
|
|
|
_, _, err = client.Login(context.Background(), user.Email, user.Password)
|
|
require.NoError(t, err)
|
|
|
|
_, resp, err := client.CreatePost(context.Background(), post)
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
|
|
_, appErr := th.App.UpdateUserRoles(th.Context, ruser.Id, model.SystemUserRoleId+" "+model.SystemPostAllPublicRoleId, false)
|
|
require.Nil(t, appErr)
|
|
appErr = th.App.Srv().InvalidateAllCaches()
|
|
require.Nil(t, appErr)
|
|
|
|
_, _, err = client.Login(context.Background(), user.Email, user.Password)
|
|
require.NoError(t, err)
|
|
|
|
_, _, err = client.CreatePost(context.Background(), post)
|
|
require.NoError(t, err)
|
|
|
|
post.ChannelId = th.BasicPrivateChannel.Id
|
|
_, resp, err = client.CreatePost(context.Background(), post)
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
|
|
_, appErr = th.App.UpdateUserRoles(th.Context, ruser.Id, model.SystemUserRoleId, false)
|
|
require.Nil(t, appErr)
|
|
_, appErr = th.App.JoinUserToTeam(th.Context, th.BasicTeam, ruser, "")
|
|
require.Nil(t, appErr)
|
|
_, appErr = th.App.UpdateTeamMemberRoles(th.Context, th.BasicTeam.Id, ruser.Id, model.TeamUserRoleId+" "+model.TeamPostAllPublicRoleId)
|
|
require.Nil(t, appErr)
|
|
appErr = th.App.Srv().InvalidateAllCaches()
|
|
require.Nil(t, appErr)
|
|
|
|
_, _, err = client.Login(context.Background(), user.Email, user.Password)
|
|
require.NoError(t, err)
|
|
|
|
post.ChannelId = th.BasicPrivateChannel.Id
|
|
_, resp, err = client.CreatePost(context.Background(), post)
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
|
|
post.ChannelId = th.BasicChannel.Id
|
|
_, _, err = client.CreatePost(context.Background(), post)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestCreatePostAll(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
|
|
th := Setup(t).InitBasic(t)
|
|
client := th.Client
|
|
|
|
post := &model.Post{ChannelId: th.BasicChannel.Id, Message: "#hashtag a" + model.NewId() + "a"}
|
|
|
|
user := model.User{Email: th.GenerateTestEmail(), Nickname: "Joram Wilander", Password: "hello1", Username: GenerateTestUsername(), Roles: model.SystemUserRoleId}
|
|
|
|
directChannel, _ := th.App.GetOrCreateDirectChannel(th.Context, th.BasicUser.Id, th.BasicUser2.Id)
|
|
|
|
ruser, _, err := client.CreateUser(context.Background(), &user)
|
|
require.NoError(t, err)
|
|
|
|
_, _, err = client.Login(context.Background(), user.Email, user.Password)
|
|
require.NoError(t, err)
|
|
|
|
_, resp, err := client.CreatePost(context.Background(), post)
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
|
|
_, appErr := th.App.UpdateUserRoles(th.Context, ruser.Id, model.SystemUserRoleId+" "+model.SystemPostAllRoleId, false)
|
|
require.Nil(t, appErr)
|
|
appErr = th.App.Srv().InvalidateAllCaches()
|
|
require.Nil(t, appErr)
|
|
|
|
_, _, err = client.Login(context.Background(), user.Email, user.Password)
|
|
require.NoError(t, err)
|
|
|
|
_, _, err = client.CreatePost(context.Background(), post)
|
|
require.NoError(t, err)
|
|
|
|
post.ChannelId = th.BasicPrivateChannel.Id
|
|
_, _, err = client.CreatePost(context.Background(), post)
|
|
require.NoError(t, err)
|
|
|
|
post.ChannelId = directChannel.Id
|
|
_, _, err = client.CreatePost(context.Background(), post)
|
|
require.NoError(t, err)
|
|
|
|
_, appErr = th.App.UpdateUserRoles(th.Context, ruser.Id, model.SystemUserRoleId, false)
|
|
require.Nil(t, appErr)
|
|
_, appErr = th.App.JoinUserToTeam(th.Context, th.BasicTeam, ruser, "")
|
|
require.Nil(t, appErr)
|
|
_, appErr = th.App.UpdateTeamMemberRoles(th.Context, th.BasicTeam.Id, ruser.Id, model.TeamUserRoleId+" "+model.TeamPostAllRoleId)
|
|
require.Nil(t, appErr)
|
|
appErr = th.App.Srv().InvalidateAllCaches()
|
|
require.Nil(t, appErr)
|
|
|
|
_, _, err = client.Login(context.Background(), user.Email, user.Password)
|
|
require.NoError(t, err)
|
|
|
|
post.ChannelId = th.BasicPrivateChannel.Id
|
|
_, _, err = client.CreatePost(context.Background(), post)
|
|
require.NoError(t, err)
|
|
|
|
post.ChannelId = th.BasicChannel.Id
|
|
_, _, err = client.CreatePost(context.Background(), post)
|
|
require.NoError(t, err)
|
|
|
|
post.ChannelId = directChannel.Id
|
|
_, resp, err = client.CreatePost(context.Background(), post)
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
}
|
|
|
|
func TestCreatePostSendOutOfChannelMentions(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
|
|
th := Setup(t).InitBasic(t)
|
|
client := th.Client
|
|
|
|
WebSocketClient := th.CreateConnectedWebSocketClient(t)
|
|
|
|
inChannelUser := th.CreateUser(t)
|
|
th.LinkUserToTeam(t, inChannelUser, th.BasicTeam)
|
|
_, appErr := th.App.AddUserToChannel(th.Context, inChannelUser, th.BasicChannel, false)
|
|
require.Nil(t, appErr)
|
|
|
|
post1 := &model.Post{ChannelId: th.BasicChannel.Id, Message: "@" + inChannelUser.Username}
|
|
_, resp, err := client.CreatePost(context.Background(), post1)
|
|
require.NoError(t, err)
|
|
CheckCreatedStatus(t, resp)
|
|
|
|
timeout := time.After(5 * time.Second)
|
|
waiting := true
|
|
for waiting {
|
|
select {
|
|
case event := <-WebSocketClient.EventChannel:
|
|
require.NotEqual(t, model.WebsocketEventEphemeralMessage, event.EventType(), "should not have ephemeral message event")
|
|
case <-timeout:
|
|
waiting = false
|
|
}
|
|
}
|
|
|
|
outOfChannelUser := th.CreateUser(t)
|
|
th.LinkUserToTeam(t, outOfChannelUser, th.BasicTeam)
|
|
|
|
post2 := &model.Post{ChannelId: th.BasicChannel.Id, Message: "@" + outOfChannelUser.Username}
|
|
_, resp, err = client.CreatePost(context.Background(), post2)
|
|
require.NoError(t, err)
|
|
CheckCreatedStatus(t, resp)
|
|
|
|
timeout = time.After(5 * time.Second)
|
|
waiting = true
|
|
for waiting {
|
|
select {
|
|
case event := <-WebSocketClient.EventChannel:
|
|
if event.EventType() != model.WebsocketEventEphemeralMessage {
|
|
// Ignore any other events
|
|
continue
|
|
}
|
|
|
|
var wpost model.Post
|
|
err := json.Unmarshal([]byte(event.GetData()["post"].(string)), &wpost)
|
|
require.NoError(t, err)
|
|
|
|
acm, ok := wpost.GetProp(model.PropsAddChannelMember).(map[string]any)
|
|
require.True(t, ok, "should have received ephemeral post with 'add_channel_member' in props")
|
|
require.True(t, acm["post_id"] != nil, "should not be nil")
|
|
require.True(t, acm["user_ids"] != nil, "should not be nil")
|
|
require.True(t, acm["usernames"] != nil, "should not be nil")
|
|
waiting = false
|
|
case <-timeout:
|
|
require.FailNow(t, "timed out waiting for ephemeral message event")
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCreatePostCheckOnlineStatus(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
api, err := Init(th.Server)
|
|
require.NoError(t, err)
|
|
session, _ := th.App.GetSession(th.Client.AuthToken)
|
|
|
|
cli := th.CreateClient()
|
|
_, _, err = cli.Login(context.Background(), th.BasicUser2.Username, th.BasicUser2.Password)
|
|
require.NoError(t, err)
|
|
|
|
wsClient := th.CreateConnectedWebSocketClientWithClient(t, cli)
|
|
|
|
waitForEvent := func(isSetOnline bool) {
|
|
timeout := time.After(5 * time.Second)
|
|
for {
|
|
select {
|
|
case ev := <-wsClient.EventChannel:
|
|
if ev.EventType() == model.WebsocketEventPosted {
|
|
assert.True(t, ev.GetData()["set_online"].(bool) == isSetOnline)
|
|
return
|
|
}
|
|
case <-timeout:
|
|
// We just skip the test instead of failing because waiting for more than 5 seconds
|
|
// to get a response does not make sense, and it will unnecessarily slow down
|
|
// the tests further in an already congested CI environment.
|
|
t.Skip("timed out waiting for event")
|
|
}
|
|
}
|
|
}
|
|
|
|
handler := api.APIHandler(createPost)
|
|
resp := httptest.NewRecorder()
|
|
post := &model.Post{
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "some message",
|
|
}
|
|
|
|
postJSON, jsonErr := json.Marshal(post)
|
|
require.NoError(t, jsonErr)
|
|
req := httptest.NewRequest("POST", "/api/v4/posts?set_online=false", bytes.NewReader(postJSON))
|
|
req.Header.Set(model.HeaderAuth, "Bearer "+session.Token)
|
|
|
|
handler.ServeHTTP(resp, req)
|
|
assert.Equal(t, http.StatusCreated, resp.Code)
|
|
waitForEvent(false)
|
|
|
|
_, appErr := th.App.GetStatus(th.BasicUser.Id)
|
|
require.NotNil(t, appErr)
|
|
assert.Equal(t, "app.status.get.missing.app_error", appErr.Id)
|
|
|
|
postJSON, jsonErr = json.Marshal(post)
|
|
require.NoError(t, jsonErr)
|
|
req = httptest.NewRequest("POST", "/api/v4/posts", bytes.NewReader(postJSON))
|
|
req.Header.Set(model.HeaderAuth, "Bearer "+session.Token)
|
|
|
|
handler.ServeHTTP(resp, req)
|
|
assert.Equal(t, http.StatusCreated, resp.Code)
|
|
waitForEvent(true)
|
|
|
|
st, appErr := th.App.GetStatus(th.BasicUser.Id)
|
|
require.Nil(t, appErr)
|
|
assert.Equal(t, "online", st.Status)
|
|
}
|
|
|
|
func TestUpdatePost(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
|
|
th := Setup(t).InitBasic(t)
|
|
client := th.Client
|
|
channel := th.BasicChannel
|
|
|
|
th.App.Srv().SetLicense(model.NewTestLicense())
|
|
|
|
fileIds := make([]string, 3)
|
|
data, err2 := testutils.ReadTestFile("test.png")
|
|
require.NoError(t, err2)
|
|
for i := range fileIds {
|
|
fileResp, _, err := client.UploadFile(context.Background(), data, channel.Id, "test.png")
|
|
require.NoError(t, err)
|
|
fileIds[i] = fileResp.FileInfos[0].Id
|
|
}
|
|
|
|
rpost, appErr := th.App.CreatePost(th.Context, &model.Post{
|
|
UserId: th.BasicUser.Id,
|
|
ChannelId: channel.Id,
|
|
Message: "zz" + model.NewId() + "a",
|
|
FileIds: fileIds,
|
|
}, channel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, appErr)
|
|
|
|
assert.Equal(t, rpost.Message, rpost.Message, "full name didn't match")
|
|
assert.EqualValues(t, 0, rpost.EditAt, "Newly created post shouldn't have EditAt set")
|
|
assert.Equal(t, model.StringArray(fileIds), rpost.FileIds, "FileIds should have been set")
|
|
|
|
t.Run("new message, invalid props", func(t *testing.T) {
|
|
msg1 := "#hashtag a" + model.NewId() + " update post again"
|
|
rpost.Message = msg1
|
|
rpost.AddProp(model.PropsAddChannelMember, "no good")
|
|
rrupost, _, err := client.UpdatePost(context.Background(), rpost.Id, rpost)
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, msg1, rrupost.Message, "failed to update message")
|
|
assert.Equal(t, "#hashtag", rrupost.Hashtags, "failed to update hashtags")
|
|
assert.Nil(t, rrupost.GetProp(model.PropsAddChannelMember), "failed to sanitize Props['add_channel_member'], should be nil")
|
|
|
|
actual, _, err := client.GetPost(context.Background(), rpost.Id, "")
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, msg1, actual.Message, "failed to update message")
|
|
assert.Equal(t, "#hashtag", actual.Hashtags, "failed to update hashtags")
|
|
assert.Nil(t, actual.GetProp(model.PropsAddChannelMember), "failed to sanitize Props['add_channel_member'], should be nil")
|
|
})
|
|
|
|
t.Run("join/leave post", func(t *testing.T) {
|
|
var rpost2 *model.Post
|
|
rpost2, appErr = th.App.CreatePost(th.Context, &model.Post{
|
|
ChannelId: channel.Id,
|
|
Message: "zz" + model.NewId() + "a",
|
|
Type: model.PostTypeJoinLeave,
|
|
UserId: th.BasicUser.Id,
|
|
}, channel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, appErr)
|
|
|
|
up2 := &model.Post{
|
|
Id: rpost2.Id,
|
|
ChannelId: channel.Id,
|
|
Message: "zz" + model.NewId() + " update post 2",
|
|
}
|
|
_, resp, err := client.UpdatePost(context.Background(), rpost2.Id, up2)
|
|
require.Error(t, err)
|
|
CheckBadRequestStatus(t, resp)
|
|
})
|
|
|
|
rpost3, appErr := th.App.CreatePost(th.Context, &model.Post{
|
|
ChannelId: channel.Id,
|
|
Message: "zz" + model.NewId() + "a",
|
|
UserId: th.BasicUser.Id,
|
|
}, channel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, appErr)
|
|
|
|
t.Run("add slack attachments", func(t *testing.T) {
|
|
up4 := &model.Post{
|
|
Id: rpost3.Id,
|
|
ChannelId: channel.Id,
|
|
Message: "zz" + model.NewId() + " update post 3",
|
|
}
|
|
up4.AddProp(model.PostPropsAttachments, []model.SlackAttachment{
|
|
{
|
|
Text: "Hello World",
|
|
},
|
|
})
|
|
rrupost3, _, err := client.UpdatePost(context.Background(), rpost3.Id, up4)
|
|
require.NoError(t, err)
|
|
assert.NotEqual(t, rpost3.EditAt, rrupost3.EditAt)
|
|
assert.NotEqual(t, rpost3.Attachments(), rrupost3.Attachments())
|
|
})
|
|
|
|
t.Run("change message, but post too old", func(t *testing.T) {
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.ServiceSettings.PostEditTimeLimit = 1
|
|
})
|
|
defer th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.ServiceSettings.PostEditTimeLimit = -1
|
|
})
|
|
|
|
rpost4, appErr := th.App.CreatePost(th.Context, &model.Post{
|
|
ChannelId: channel.Id,
|
|
Message: "zz" + model.NewId() + "a",
|
|
UserId: th.BasicUser.Id,
|
|
CreateAt: model.GetMillis() - 2000,
|
|
}, channel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, appErr)
|
|
|
|
up4 := &model.Post{
|
|
Id: rpost4.Id,
|
|
ChannelId: channel.Id,
|
|
Message: "zz" + model.NewId() + " update post 4",
|
|
}
|
|
_, resp, err := client.UpdatePost(context.Background(), rpost4.Id, up4)
|
|
require.Error(t, err, "should fail on update old post")
|
|
CheckBadRequestStatus(t, resp)
|
|
})
|
|
|
|
t.Run("err with integrations-reserved props", func(t *testing.T) {
|
|
originalHardenedModeSetting := *th.App.Config().ServiceSettings.ExperimentalEnableHardenedMode
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.ServiceSettings.ExperimentalEnableHardenedMode = true
|
|
})
|
|
|
|
defer th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.ServiceSettings.ExperimentalEnableHardenedMode = originalHardenedModeSetting
|
|
})
|
|
|
|
_, resp, err := client.UpdatePost(context.Background(), rpost.Id, &model.Post{
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "with props",
|
|
Props: model.StringInterface{model.PostPropsFromWebhook: "true"},
|
|
})
|
|
|
|
require.Error(t, err)
|
|
CheckBadRequestStatus(t, resp)
|
|
})
|
|
|
|
t.Run("should prevent updating post with files when user lacks upload_file permission in target channel", func(t *testing.T) {
|
|
postWithoutFiles, appErr := th.App.CreatePost(th.Context, &model.Post{
|
|
UserId: th.BasicUser.Id,
|
|
ChannelId: channel.Id,
|
|
Message: "Post without files",
|
|
}, channel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, appErr)
|
|
|
|
fileResp, resp, err := client.UploadFile(context.Background(), []byte("test file data"), channel.Id, "test-file.txt")
|
|
require.NoError(t, err)
|
|
CheckCreatedStatus(t, resp)
|
|
fileId := fileResp.FileInfos[0].Id
|
|
|
|
th.RemovePermissionFromRole(t, model.PermissionUploadFile.Id, model.ChannelUserRoleId)
|
|
defer func() {
|
|
th.AddPermissionToRole(t, model.PermissionUploadFile.Id, model.ChannelUserRoleId)
|
|
}()
|
|
|
|
updatePost := &model.Post{
|
|
Id: postWithoutFiles.Id,
|
|
ChannelId: channel.Id,
|
|
Message: "Updated post with file",
|
|
FileIds: model.StringArray{fileId},
|
|
}
|
|
updatedPost, resp, err := client.UpdatePost(context.Background(), postWithoutFiles.Id, updatePost)
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
assert.Nil(t, updatedPost)
|
|
})
|
|
|
|
t.Run("should allow updating post with files when user has upload_file permission", func(t *testing.T) {
|
|
postWithoutFiles, appErr := th.App.CreatePost(th.Context, &model.Post{
|
|
UserId: th.BasicUser.Id,
|
|
ChannelId: channel.Id,
|
|
Message: "Post without files",
|
|
}, channel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, appErr)
|
|
|
|
fileResp, resp, err := client.UploadFile(context.Background(), []byte("test file data"), channel.Id, "test-file.txt")
|
|
require.NoError(t, err)
|
|
CheckCreatedStatus(t, resp)
|
|
fileId := fileResp.FileInfos[0].Id
|
|
|
|
updatePost := &model.Post{
|
|
Id: postWithoutFiles.Id,
|
|
ChannelId: channel.Id,
|
|
Message: "Updated post with file",
|
|
FileIds: model.StringArray{fileId},
|
|
}
|
|
updatedPost, resp, err := client.UpdatePost(context.Background(), postWithoutFiles.Id, updatePost)
|
|
require.NoError(t, err)
|
|
CheckOKStatus(t, resp)
|
|
require.NotNil(t, updatedPost)
|
|
assert.Contains(t, updatedPost.FileIds, fileId)
|
|
})
|
|
|
|
t.Run("logged out", func(t *testing.T) {
|
|
_, err := client.Logout(context.Background())
|
|
require.NoError(t, err)
|
|
_, resp, err := client.UpdatePost(context.Background(), rpost.Id, rpost)
|
|
require.Error(t, err)
|
|
CheckUnauthorizedStatus(t, resp)
|
|
})
|
|
|
|
t.Run("different user", func(t *testing.T) {
|
|
th.LoginBasic2(t)
|
|
_, resp, err := client.UpdatePost(context.Background(), rpost.Id, rpost)
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
|
|
_, err = client.Logout(context.Background())
|
|
require.NoError(t, err)
|
|
})
|
|
|
|
t.Run("different user, but team admin", func(t *testing.T) {
|
|
th.LoginTeamAdmin(t)
|
|
_, resp, err := client.UpdatePost(context.Background(), rpost.Id, rpost)
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
|
|
_, err = client.Logout(context.Background())
|
|
require.NoError(t, err)
|
|
})
|
|
|
|
t.Run("different user, but system admin", func(t *testing.T) {
|
|
_, _, err := th.SystemAdminClient.UpdatePost(context.Background(), rpost.Id, rpost)
|
|
require.NoError(t, err)
|
|
})
|
|
|
|
t.Run("should be able to add new files", func(t *testing.T) {
|
|
th.LoginBasic(t)
|
|
// create new file
|
|
fileResponse, _, err := client.UploadFile(context.Background(), data, channel.Id, "test.png")
|
|
require.NoError(t, err)
|
|
require.Equal(t, 1, len(fileResponse.FileInfos))
|
|
fileInfo := fileResponse.FileInfos[0]
|
|
|
|
// create new post
|
|
post, appErr := th.App.CreatePost(th.Context, &model.Post{
|
|
UserId: th.BasicUser.Id,
|
|
ChannelId: channel.Id,
|
|
Message: "zz" + model.NewId() + "a",
|
|
}, channel, model.CreatePostFlags{SetOnline: true})
|
|
|
|
require.Nil(t, appErr)
|
|
require.NotNil(t, post)
|
|
|
|
// update post with new file
|
|
post.FileIds = []string{fileInfo.Id}
|
|
_, _, err = client.UpdatePost(context.Background(), post.Id, post)
|
|
require.NoError(t, err)
|
|
|
|
updatedPost, _, err := client.GetPost(context.Background(), post.Id, "")
|
|
require.NoError(t, err)
|
|
require.Equal(t, post.Id, updatedPost.Id)
|
|
require.Equal(t, 1, len(updatedPost.FileIds))
|
|
require.Equal(t, fileInfo.Id, updatedPost.FileIds[0])
|
|
|
|
// verify file is attached to the post
|
|
fetchedFileInfo, _, err := client.GetFileInfo(context.Background(), fileInfo.Id)
|
|
require.NoError(t, err)
|
|
require.Equal(t, fileInfo.Id, fetchedFileInfo.Id)
|
|
require.Equal(t, post.Id, fetchedFileInfo.PostId)
|
|
})
|
|
|
|
t.Run("should be able to remove files", func(t *testing.T) {
|
|
th.LoginBasic(t)
|
|
// create new file
|
|
fileResponse, _, err := client.UploadFile(context.Background(), data, channel.Id, "test.png")
|
|
require.NoError(t, err)
|
|
require.Equal(t, 1, len(fileResponse.FileInfos))
|
|
fileInfo := fileResponse.FileInfos[0]
|
|
|
|
// create new post
|
|
post, appErr := th.App.CreatePost(th.Context, &model.Post{
|
|
UserId: th.BasicUser.Id,
|
|
ChannelId: channel.Id,
|
|
Message: "zz" + model.NewId() + "a",
|
|
FileIds: []string{fileInfo.Id},
|
|
}, channel, model.CreatePostFlags{SetOnline: true})
|
|
|
|
require.Nil(t, appErr)
|
|
require.NotNil(t, post)
|
|
require.Equal(t, 1, len(post.FileIds))
|
|
|
|
// remove files from post
|
|
post.FileIds = []string{}
|
|
_, _, err = client.UpdatePost(context.Background(), post.Id, post)
|
|
require.NoError(t, err)
|
|
|
|
updatedPost, _, err := client.GetPost(context.Background(), post.Id, "")
|
|
require.NoError(t, err)
|
|
require.Equal(t, post.Id, updatedPost.Id)
|
|
require.Equal(t, 0, len(updatedPost.FileIds))
|
|
|
|
// verify file is removed from the post
|
|
postFileInfos, err := th.App.Srv().Store().FileInfo().GetForPost(post.Id, true, true, false)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 1, len(postFileInfos))
|
|
require.Equal(t, fileInfo.Id, postFileInfos[0].Id)
|
|
require.Greater(t, postFileInfos[0].DeleteAt, int64(0))
|
|
})
|
|
|
|
t.Run("post files remain unchanged when fileIds is nil", func(t *testing.T) {
|
|
th.LoginBasic(t)
|
|
// create new file
|
|
fileResponse, _, err := client.UploadFile(context.Background(), data, channel.Id, "test.png")
|
|
require.NoError(t, err)
|
|
require.Equal(t, 1, len(fileResponse.FileInfos))
|
|
fileInfo := fileResponse.FileInfos[0]
|
|
|
|
// create new post
|
|
post, appErr := th.App.CreatePost(th.Context, &model.Post{
|
|
UserId: th.BasicUser.Id,
|
|
ChannelId: channel.Id,
|
|
Message: "zz" + model.NewId() + "a",
|
|
FileIds: []string{fileInfo.Id},
|
|
}, channel, model.CreatePostFlags{SetOnline: true})
|
|
|
|
require.Nil(t, appErr)
|
|
require.NotNil(t, post)
|
|
require.Equal(t, 1, len(post.FileIds))
|
|
|
|
// update post without specifying fileIds
|
|
post.FileIds = nil
|
|
post.Message = "updated message"
|
|
_, _, err = client.UpdatePost(context.Background(), post.Id, post)
|
|
require.NoError(t, err)
|
|
|
|
updatedPost, _, err := client.GetPost(context.Background(), post.Id, "")
|
|
require.NoError(t, err)
|
|
require.Equal(t, post.Id, updatedPost.Id)
|
|
require.Equal(t, 1, len(updatedPost.FileIds))
|
|
require.Equal(t, fileInfo.Id, updatedPost.FileIds[0])
|
|
require.Equal(t, "updated message", updatedPost.Message)
|
|
|
|
// verify file is still part of the post
|
|
postFileInfos, err := th.App.Srv().Store().FileInfo().GetForPost(post.Id, true, false, false)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 1, len(postFileInfos))
|
|
require.Equal(t, fileInfo.Id, postFileInfos[0].Id)
|
|
require.Equal(t, int64(0), postFileInfos[0].DeleteAt)
|
|
})
|
|
|
|
t.Run("should be able to add and remove files simultaneously", func(t *testing.T) {
|
|
th.LoginBasic(t)
|
|
// create new file
|
|
fileResponse1, _, err := client.UploadFile(context.Background(), data, channel.Id, "test.png")
|
|
require.NoError(t, err)
|
|
require.Equal(t, 1, len(fileResponse1.FileInfos))
|
|
fileInfo1 := fileResponse1.FileInfos[0]
|
|
|
|
fileResponse2, _, err := client.UploadFile(context.Background(), data, channel.Id, "test.png")
|
|
require.NoError(t, err)
|
|
require.Equal(t, 1, len(fileResponse2.FileInfos))
|
|
fileInfo2 := fileResponse2.FileInfos[0]
|
|
|
|
// create new post
|
|
post, appErr := th.App.CreatePost(th.Context, &model.Post{
|
|
UserId: th.BasicUser.Id,
|
|
ChannelId: channel.Id,
|
|
Message: "zz" + model.NewId() + "a",
|
|
FileIds: model.StringArray{fileInfo1.Id, fileInfo2.Id},
|
|
}, channel, model.CreatePostFlags{SetOnline: true})
|
|
|
|
require.Nil(t, appErr)
|
|
require.NotNil(t, post)
|
|
require.Equal(t, 2, len(post.FileIds))
|
|
|
|
// update post with new file
|
|
|
|
fileResponse3, _, err := client.UploadFile(context.Background(), data, channel.Id, "test.png")
|
|
require.NoError(t, err)
|
|
require.Equal(t, 1, len(fileResponse3.FileInfos))
|
|
fileInfo3 := fileResponse3.FileInfos[0]
|
|
|
|
fileResponse4, _, err := client.UploadFile(context.Background(), data, channel.Id, "test.png")
|
|
require.NoError(t, err)
|
|
require.Equal(t, 1, len(fileResponse4.FileInfos))
|
|
fileInfo4 := fileResponse4.FileInfos[0]
|
|
|
|
post.FileIds = []string{fileInfo3.Id, fileInfo4.Id}
|
|
_, _, err = client.UpdatePost(context.Background(), post.Id, post)
|
|
require.NoError(t, err)
|
|
|
|
updatedPost, _, err := client.GetPost(context.Background(), post.Id, "")
|
|
require.NoError(t, err)
|
|
require.Equal(t, post.Id, updatedPost.Id)
|
|
require.Equal(t, 2, len(updatedPost.FileIds))
|
|
require.Contains(t, updatedPost.FileIds, fileInfo3.Id)
|
|
require.Contains(t, updatedPost.FileIds, fileInfo4.Id)
|
|
|
|
postFiles, err := th.App.Srv().Store().FileInfo().GetForPost(post.Id, true, true, false)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 4, len(postFiles))
|
|
|
|
for _, postFile := range postFiles {
|
|
if postFile.Id == fileInfo1.Id || postFile.Id == fileInfo2.Id {
|
|
require.Greater(t, postFile.DeleteAt, int64(0))
|
|
}
|
|
|
|
if postFile.Id == fileInfo3.Id || postFile.Id == fileInfo4.Id {
|
|
require.Equal(t, postFile.PostId, post.Id)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestUpdateOthersPostInDirectMessageChannel(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
|
|
// This test checks that a sysadmin with the "EDIT_OTHERS_POSTS" permission can edit someone else's post in a
|
|
// channel without a team (DM/GM). This indirectly checks for the proper cascading all the way to system-wide roles
|
|
// on the user object of permissions based on a post in a channel with no team ID.
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
dmChannel := th.CreateDmChannel(t, th.SystemAdminUser)
|
|
|
|
post := &model.Post{
|
|
Message: "asd",
|
|
ChannelId: dmChannel.Id,
|
|
PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()),
|
|
UserId: th.BasicUser.Id,
|
|
CreateAt: 0,
|
|
}
|
|
|
|
post, _, err := th.Client.CreatePost(context.Background(), post)
|
|
require.NoError(t, err)
|
|
|
|
post.Message = "changed"
|
|
_, _, err = th.SystemAdminClient.UpdatePost(context.Background(), post.Id, post)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestPatchPost(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
|
|
th := Setup(t).InitBasic(t)
|
|
client := th.Client
|
|
channel := th.BasicChannel
|
|
|
|
th.App.Srv().SetLicense(model.NewTestLicense())
|
|
|
|
fileIDs := make([]string, 3)
|
|
data, err2 := testutils.ReadTestFile("test.png")
|
|
require.NoError(t, err2)
|
|
for i := range fileIDs {
|
|
fileResp, _, err := client.UploadFile(context.Background(), data, channel.Id, "test.png")
|
|
require.NoError(t, err)
|
|
fileIDs[i] = fileResp.FileInfos[0].Id
|
|
}
|
|
sort.Strings(fileIDs)
|
|
|
|
post := &model.Post{
|
|
ChannelId: channel.Id,
|
|
IsPinned: true,
|
|
Message: "#hashtag a message",
|
|
Props: model.StringInterface{"channel_header": "old_header"},
|
|
FileIds: fileIDs[0:2],
|
|
HasReactions: true,
|
|
}
|
|
post, _, err := client.CreatePost(context.Background(), post)
|
|
require.NoError(t, err)
|
|
|
|
var rpost *model.Post
|
|
t.Run("new message, props, files, HasReactions bit", func(t *testing.T) {
|
|
patch := &model.PostPatch{}
|
|
|
|
patch.IsPinned = model.NewPointer(false)
|
|
patch.Message = model.NewPointer("#otherhashtag other message")
|
|
patch.Props = &model.StringInterface{"channel_header": "new_header"}
|
|
patchFileIds := model.StringArray(fileIDs) // one extra file
|
|
patch.FileIds = &patchFileIds
|
|
patch.HasReactions = model.NewPointer(false)
|
|
|
|
rpost, _, err = client.PatchPost(context.Background(), post.Id, patch)
|
|
require.NoError(t, err)
|
|
|
|
assert.False(t, rpost.IsPinned, "IsPinned did not update properly")
|
|
assert.Equal(t, "#otherhashtag other message", rpost.Message, "Message did not update properly")
|
|
assert.Equal(t, *patch.Props, rpost.GetProps(), "Props did not update properly")
|
|
assert.Equal(t, "#otherhashtag", rpost.Hashtags, "Message did not update properly")
|
|
assert.Equal(t, model.StringArray(fileIDs), rpost.FileIds, "FileIds should not update")
|
|
assert.False(t, rpost.HasReactions, "HasReactions did not update properly")
|
|
})
|
|
|
|
t.Run("add slack attachments", func(t *testing.T) {
|
|
patch2 := &model.PostPatch{}
|
|
attachments := []model.SlackAttachment{
|
|
{
|
|
Text: "Hello World",
|
|
},
|
|
}
|
|
patch2.Props = &model.StringInterface{model.PostPropsAttachments: attachments}
|
|
|
|
var rpost2 *model.Post
|
|
rpost2, _, err = client.PatchPost(context.Background(), post.Id, patch2)
|
|
require.NoError(t, err)
|
|
assert.NotEmpty(t, rpost2.GetProp(model.PostPropsAttachments))
|
|
assert.NotEqual(t, rpost.EditAt, rpost2.EditAt)
|
|
})
|
|
|
|
t.Run("invalid requests", func(t *testing.T) {
|
|
var origEnableDeveloper bool
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
origEnableDeveloper = *cfg.ServiceSettings.EnableDeveloper
|
|
*cfg.ServiceSettings.EnableDeveloper = true
|
|
})
|
|
defer th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.ServiceSettings.EnableDeveloper = origEnableDeveloper
|
|
})
|
|
|
|
var r *http.Response
|
|
r, err = client.DoAPIPut(context.Background(), "/posts/"+post.Id+"/patch", "garbage")
|
|
require.EqualError(t, err, "Invalid or missing post in request body., invalid character 'g' looking for beginning of value")
|
|
require.Equal(t, http.StatusBadRequest, r.StatusCode, "wrong status code")
|
|
|
|
var resp *model.Response
|
|
patch := &model.PostPatch{}
|
|
_, resp, err = client.PatchPost(context.Background(), "junk", patch)
|
|
require.Error(t, err)
|
|
CheckBadRequestStatus(t, resp)
|
|
})
|
|
|
|
t.Run("unknown post", func(t *testing.T) {
|
|
var resp *model.Response
|
|
patch := &model.PostPatch{}
|
|
_, resp, err = client.PatchPost(context.Background(), GenerateTestID(), patch)
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
})
|
|
|
|
t.Run("logged out", func(t *testing.T) {
|
|
_, err = client.Logout(context.Background())
|
|
require.NoError(t, err)
|
|
patch := &model.PostPatch{}
|
|
_, resp, err := client.PatchPost(context.Background(), post.Id, patch)
|
|
require.Error(t, err)
|
|
CheckUnauthorizedStatus(t, resp)
|
|
})
|
|
|
|
t.Run("different user", func(t *testing.T) {
|
|
th.LoginBasic2(t)
|
|
patch := &model.PostPatch{}
|
|
_, resp, err := client.PatchPost(context.Background(), post.Id, patch)
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
})
|
|
|
|
t.Run("different user, but team admin", func(t *testing.T) {
|
|
th.LoginTeamAdmin(t)
|
|
patch := &model.PostPatch{}
|
|
_, resp, err := client.PatchPost(context.Background(), post.Id, patch)
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
})
|
|
|
|
t.Run("different user, but system admin", func(t *testing.T) {
|
|
patch := &model.PostPatch{}
|
|
_, _, err := th.SystemAdminClient.PatchPost(context.Background(), post.Id, patch)
|
|
require.NoError(t, err)
|
|
})
|
|
|
|
t.Run("edit others posts permission can function independently of edit own post", func(t *testing.T) {
|
|
th.LoginBasic2(t)
|
|
patch := &model.PostPatch{}
|
|
_, resp, err := client.PatchPost(context.Background(), post.Id, patch)
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
|
|
// Add permission to edit others'
|
|
defaultPerms := th.SaveDefaultRolePermissions(t)
|
|
defer th.RestoreDefaultRolePermissions(t, defaultPerms)
|
|
th.RemovePermissionFromRole(t, model.PermissionEditPost.Id, model.ChannelUserRoleId)
|
|
th.AddPermissionToRole(t, model.PermissionEditOthersPosts.Id, model.ChannelUserRoleId)
|
|
|
|
_, _, err = client.PatchPost(context.Background(), post.Id, patch)
|
|
require.NoError(t, err)
|
|
})
|
|
|
|
t.Run("time limit expired", func(t *testing.T) {
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.ServiceSettings.PostEditTimeLimit = 1
|
|
})
|
|
defer th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.ServiceSettings.PostEditTimeLimit = -1
|
|
})
|
|
|
|
post2 := &model.Post{
|
|
ChannelId: channel.Id,
|
|
Message: "#hashtag a message",
|
|
CreateAt: model.GetMillis() - 2000,
|
|
}
|
|
post2, _, err := th.SystemAdminClient.CreatePost(context.Background(), post2)
|
|
require.NoError(t, err)
|
|
|
|
patch2 := &model.PostPatch{
|
|
Message: model.NewPointer("new message"),
|
|
}
|
|
_, resp, err := th.SystemAdminClient.PatchPost(context.Background(), post2.Id, patch2)
|
|
require.Error(t, err)
|
|
CheckBadRequestStatus(t, resp)
|
|
require.Equal(t, "api.post.update_post.permissions_time_limit.app_error", err.(*model.AppError).Id, "should be time limit error")
|
|
})
|
|
|
|
t.Run("err with integrations-reserved props", func(t *testing.T) {
|
|
originalHardenedModeSetting := *th.App.Config().ServiceSettings.ExperimentalEnableHardenedMode
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.ServiceSettings.ExperimentalEnableHardenedMode = true
|
|
})
|
|
|
|
defer th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.ServiceSettings.ExperimentalEnableHardenedMode = originalHardenedModeSetting
|
|
})
|
|
|
|
post := &model.Post{
|
|
ChannelId: channel.Id,
|
|
Message: "#hashtag a message",
|
|
CreateAt: model.GetMillis() - 2000,
|
|
}
|
|
post, _, createErr := th.SystemAdminClient.CreatePost(context.Background(), post)
|
|
require.NoError(t, createErr)
|
|
|
|
patch := &model.PostPatch{}
|
|
patch.Props = &model.StringInterface{model.PostPropsFromWebhook: "true"}
|
|
_, patchResp, patchErr := client.PatchPost(context.Background(), post.Id, patch)
|
|
|
|
require.Error(t, patchErr)
|
|
CheckBadRequestStatus(t, patchResp)
|
|
})
|
|
|
|
t.Run("should be able to add new files", func(t *testing.T) {
|
|
post, _, err := client.CreatePost(context.Background(), &model.Post{
|
|
ChannelId: channel.Id,
|
|
Message: "#hashtag a message",
|
|
CreateAt: model.GetMillis() - 2000,
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
|
|
fileResponse, _, err := client.UploadFile(context.Background(), data, channel.Id, "test.png")
|
|
require.NoError(t, err)
|
|
require.Equal(t, 1, len(fileResponse.FileInfos))
|
|
fileInfo := fileResponse.FileInfos[0]
|
|
|
|
patch := &model.PostPatch{
|
|
FileIds: &model.StringArray{fileInfo.Id},
|
|
}
|
|
|
|
_, _, err = client.PatchPost(context.Background(), post.Id, patch)
|
|
require.NoError(t, err)
|
|
|
|
patchedPost, _, err := client.GetPost(context.Background(), post.Id, "")
|
|
require.NoError(t, err)
|
|
require.Equal(t, 1, len(patchedPost.FileIds))
|
|
require.Equal(t, fileInfo.Id, patchedPost.FileIds[0])
|
|
})
|
|
|
|
t.Run("should be able to remove some files", func(t *testing.T) {
|
|
fileResponse1, _, err := client.UploadFile(context.Background(), data, channel.Id, "test.png")
|
|
require.NoError(t, err)
|
|
require.Equal(t, 1, len(fileResponse1.FileInfos))
|
|
fileInfo1 := fileResponse1.FileInfos[0]
|
|
|
|
fileResponse2, _, err := client.UploadFile(context.Background(), data, channel.Id, "test.png")
|
|
require.NoError(t, err)
|
|
require.Equal(t, 1, len(fileResponse2.FileInfos))
|
|
fileInfo2 := fileResponse2.FileInfos[0]
|
|
|
|
post, _, err := client.CreatePost(context.Background(), &model.Post{
|
|
ChannelId: channel.Id,
|
|
Message: "#hashtag a message",
|
|
CreateAt: model.GetMillis() - 2000,
|
|
FileIds: model.StringArray{fileInfo1.Id, fileInfo2.Id},
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
require.Equal(t, 2, len(post.FileIds))
|
|
|
|
patch := &model.PostPatch{
|
|
FileIds: &model.StringArray{fileInfo2.Id},
|
|
}
|
|
|
|
_, _, err = client.PatchPost(context.Background(), post.Id, patch)
|
|
require.NoError(t, err)
|
|
|
|
patchedPost, _, err := client.GetPost(context.Background(), post.Id, "")
|
|
require.NoError(t, err)
|
|
require.Equal(t, 1, len(patchedPost.FileIds))
|
|
require.Equal(t, fileInfo2.Id, patchedPost.FileIds[0])
|
|
})
|
|
|
|
t.Run("should be able to remove all files", func(t *testing.T) {
|
|
fileResponse1, _, err := client.UploadFile(context.Background(), data, channel.Id, "test.png")
|
|
require.NoError(t, err)
|
|
require.Equal(t, 1, len(fileResponse1.FileInfos))
|
|
fileInfo1 := fileResponse1.FileInfos[0]
|
|
|
|
fileResponse2, _, err := client.UploadFile(context.Background(), data, channel.Id, "test.png")
|
|
require.NoError(t, err)
|
|
require.Equal(t, 1, len(fileResponse2.FileInfos))
|
|
fileInfo2 := fileResponse2.FileInfos[0]
|
|
|
|
post, _, err := client.CreatePost(context.Background(), &model.Post{
|
|
ChannelId: channel.Id,
|
|
Message: "#hashtag a message",
|
|
CreateAt: model.GetMillis() - 2000,
|
|
FileIds: model.StringArray{fileInfo1.Id, fileInfo2.Id},
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
require.Equal(t, 2, len(post.FileIds))
|
|
|
|
patch := &model.PostPatch{
|
|
FileIds: &model.StringArray{},
|
|
}
|
|
|
|
_, _, err = client.PatchPost(context.Background(), post.Id, patch)
|
|
require.NoError(t, err)
|
|
|
|
patchedPost, _, err := client.GetPost(context.Background(), post.Id, "")
|
|
require.NoError(t, err)
|
|
require.Equal(t, 0, len(patchedPost.FileIds))
|
|
})
|
|
|
|
t.Run("post files remain unchanged when fileIds is nil", func(t *testing.T) {
|
|
fileResponse1, _, err := client.UploadFile(context.Background(), data, channel.Id, "test.png")
|
|
require.NoError(t, err)
|
|
require.Equal(t, 1, len(fileResponse1.FileInfos))
|
|
fileInfo1 := fileResponse1.FileInfos[0]
|
|
|
|
fileResponse2, _, err := client.UploadFile(context.Background(), data, channel.Id, "test.png")
|
|
require.NoError(t, err)
|
|
require.Equal(t, 1, len(fileResponse2.FileInfos))
|
|
fileInfo2 := fileResponse2.FileInfos[0]
|
|
|
|
post, _, err := client.CreatePost(context.Background(), &model.Post{
|
|
ChannelId: channel.Id,
|
|
Message: "#hashtag a message",
|
|
CreateAt: model.GetMillis() - 2000,
|
|
FileIds: model.StringArray{fileInfo1.Id, fileInfo2.Id},
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
require.Equal(t, 2, len(post.FileIds))
|
|
|
|
patch := &model.PostPatch{
|
|
FileIds: nil,
|
|
}
|
|
|
|
_, _, err = client.PatchPost(context.Background(), post.Id, patch)
|
|
require.NoError(t, err)
|
|
|
|
patchedPost, _, err := client.GetPost(context.Background(), post.Id, "")
|
|
require.NoError(t, err)
|
|
require.Equal(t, 2, len(patchedPost.FileIds))
|
|
require.Contains(t, patchedPost.FileIds, fileInfo1.Id)
|
|
require.Contains(t, patchedPost.FileIds, fileInfo2.Id)
|
|
})
|
|
}
|
|
|
|
func TestPinPost(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
|
|
th := Setup(t).InitBasic(t)
|
|
client := th.Client
|
|
|
|
post := th.BasicPost
|
|
_, err := client.PinPost(context.Background(), post.Id)
|
|
require.NoError(t, err)
|
|
|
|
rpost, appErr := th.App.GetSinglePost(th.Context, post.Id, false)
|
|
require.Nil(t, appErr)
|
|
require.True(t, rpost.IsPinned, "failed to pin post")
|
|
|
|
resp, err := client.PinPost(context.Background(), "junk")
|
|
require.Error(t, err)
|
|
CheckBadRequestStatus(t, resp)
|
|
|
|
resp, err = client.PinPost(context.Background(), GenerateTestID())
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
|
|
_, err = client.Logout(context.Background())
|
|
require.NoError(t, err)
|
|
resp, err = client.PinPost(context.Background(), post.Id)
|
|
require.Error(t, err)
|
|
CheckUnauthorizedStatus(t, resp)
|
|
|
|
_, err = th.SystemAdminClient.PinPost(context.Background(), post.Id)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestUnpinPost(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
|
|
th := Setup(t).InitBasic(t)
|
|
client := th.Client
|
|
|
|
pinnedPost := th.CreatePinnedPost(t)
|
|
_, err := client.UnpinPost(context.Background(), pinnedPost.Id)
|
|
require.NoError(t, err)
|
|
|
|
rpost, appErr := th.App.GetSinglePost(th.Context, pinnedPost.Id, false)
|
|
require.Nil(t, appErr)
|
|
require.False(t, rpost.IsPinned)
|
|
|
|
resp, err := client.UnpinPost(context.Background(), "junk")
|
|
require.Error(t, err)
|
|
CheckBadRequestStatus(t, resp)
|
|
|
|
resp, err = client.UnpinPost(context.Background(), GenerateTestID())
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
|
|
_, err = client.Logout(context.Background())
|
|
require.NoError(t, err)
|
|
resp, err = client.UnpinPost(context.Background(), pinnedPost.Id)
|
|
require.Error(t, err)
|
|
CheckUnauthorizedStatus(t, resp)
|
|
|
|
_, err = th.SystemAdminClient.UnpinPost(context.Background(), pinnedPost.Id)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestGetPostsForChannel(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
|
|
th := Setup(t).InitBasic(t)
|
|
client := th.Client
|
|
|
|
post1 := th.CreatePost(t)
|
|
post2 := th.CreatePost(t)
|
|
post3 := &model.Post{ChannelId: th.BasicChannel.Id, Message: "zz" + model.NewId() + "a", RootId: post1.Id}
|
|
post3, _, _ = client.CreatePost(context.Background(), post3)
|
|
|
|
time.Sleep(300 * time.Millisecond)
|
|
since := model.GetMillis()
|
|
time.Sleep(300 * time.Millisecond)
|
|
|
|
post4 := th.CreatePost(t)
|
|
|
|
th.TestForAllClients(t, func(t *testing.T, c *model.Client4) {
|
|
posts, resp, err := c.GetPostsForChannel(context.Background(), th.BasicChannel.Id, 0, 60, "", false, false)
|
|
require.NoError(t, err)
|
|
require.Equal(t, post4.Id, posts.Order[0], "wrong order")
|
|
require.Equal(t, post3.Id, posts.Order[1], "wrong order")
|
|
require.Equal(t, post2.Id, posts.Order[2], "wrong order")
|
|
require.Equal(t, post1.Id, posts.Order[3], "wrong order")
|
|
|
|
require.Nil(t, posts.HasNext, "HasNext should not be returned")
|
|
|
|
posts, resp, _ = c.GetPostsForChannel(context.Background(), th.BasicChannel.Id, 0, 3, resp.Etag, false, false)
|
|
CheckEtag(t, posts, resp)
|
|
|
|
posts, _, err = c.GetPostsForChannel(context.Background(), th.BasicChannel.Id, 0, 3, "", false, false)
|
|
require.NoError(t, err)
|
|
require.Len(t, posts.Order, 3, "wrong number returned")
|
|
|
|
_, ok := posts.Posts[post3.Id]
|
|
require.True(t, ok, "missing comment")
|
|
_, ok = posts.Posts[post1.Id]
|
|
require.True(t, ok, "missing root post")
|
|
|
|
posts, _, err = c.GetPostsForChannel(context.Background(), th.BasicChannel.Id, 1, 1, "", false, false)
|
|
require.NoError(t, err)
|
|
require.Equal(t, post3.Id, posts.Order[0], "wrong order")
|
|
|
|
posts, _, err = c.GetPostsForChannel(context.Background(), th.BasicChannel.Id, 10000, 10000, "", false, false)
|
|
require.NoError(t, err)
|
|
require.Empty(t, posts.Order, "should be no posts")
|
|
})
|
|
|
|
post5 := th.CreatePost(t)
|
|
|
|
th.TestForAllClients(t, func(t *testing.T, c *model.Client4) {
|
|
posts, _, err := c.GetPostsSince(context.Background(), th.BasicChannel.Id, since, false)
|
|
require.NoError(t, err)
|
|
require.Len(t, posts.Posts, 2, "should return 2 posts")
|
|
|
|
// "since" query to return empty NextPostId and PrevPostId
|
|
require.Equal(t, "", posts.NextPostId, "should return an empty NextPostId")
|
|
require.Equal(t, "", posts.PrevPostId, "should return an empty PrevPostId")
|
|
|
|
found := make([]bool, 2)
|
|
for _, p := range posts.Posts {
|
|
require.LessOrEqual(t, since, p.CreateAt, "bad create at for post returned")
|
|
|
|
if p.Id == post4.Id {
|
|
found[0] = true
|
|
} else if p.Id == post5.Id {
|
|
found[1] = true
|
|
}
|
|
}
|
|
for _, f := range found {
|
|
require.True(t, f, "missing post")
|
|
}
|
|
|
|
_, resp, err := c.GetPostsForChannel(context.Background(), "", 0, 60, "", false, false)
|
|
require.Error(t, err)
|
|
CheckBadRequestStatus(t, resp)
|
|
|
|
_, resp, err = c.GetPostsForChannel(context.Background(), "junk", 0, 60, "", false, false)
|
|
require.Error(t, err)
|
|
CheckBadRequestStatus(t, resp)
|
|
})
|
|
|
|
_, resp, err := client.GetPostsForChannel(context.Background(), model.NewId(), 0, 60, "", false, false)
|
|
require.Error(t, err)
|
|
CheckNotFoundStatus(t, resp)
|
|
|
|
_, err = client.Logout(context.Background())
|
|
require.NoError(t, err)
|
|
_, resp, err = client.GetPostsForChannel(context.Background(), model.NewId(), 0, 60, "", false, false)
|
|
require.Error(t, err)
|
|
CheckUnauthorizedStatus(t, resp)
|
|
|
|
// more tests for next_post_id, prev_post_id, and order
|
|
// There are 12 posts composed of first 2 system messages and 10 created posts
|
|
_, _, err = client.Login(context.Background(), th.BasicUser.Email, th.BasicUser.Password)
|
|
require.NoError(t, err)
|
|
th.CreatePost(t) // post6
|
|
post7 := th.CreatePost(t)
|
|
post8 := th.CreatePost(t)
|
|
th.CreatePost(t) // post9
|
|
post10 := th.CreatePost(t)
|
|
|
|
var posts *model.PostList
|
|
th.TestForAllClients(t, func(t *testing.T, c *model.Client4) {
|
|
// get the system post IDs posted before the created posts above
|
|
posts, _, err = c.GetPostsBefore(context.Background(), th.BasicChannel.Id, post1.Id, 0, 2, "", false, false)
|
|
require.NoError(t, err)
|
|
systemPostId1 := posts.Order[1]
|
|
|
|
// similar to '/posts'
|
|
posts, _, err = c.GetPostsForChannel(context.Background(), th.BasicChannel.Id, 0, 60, "", false, false)
|
|
require.NoError(t, err)
|
|
require.Len(t, posts.Order, 12, "expected 12 posts")
|
|
require.Equal(t, post10.Id, posts.Order[0], "posts not in order")
|
|
require.Equal(t, systemPostId1, posts.Order[11], "posts not in order")
|
|
require.Equal(t, "", posts.NextPostId, "should return an empty NextPostId")
|
|
require.Equal(t, "", posts.PrevPostId, "should return an empty PrevPostId")
|
|
|
|
// similar to '/posts?per_page=3'
|
|
posts, _, err = c.GetPostsForChannel(context.Background(), th.BasicChannel.Id, 0, 3, "", false, false)
|
|
require.NoError(t, err)
|
|
require.Len(t, posts.Order, 3, "expected 3 posts")
|
|
require.Equal(t, post10.Id, posts.Order[0], "posts not in order")
|
|
require.Equal(t, post8.Id, posts.Order[2], "should return 3 posts and match order")
|
|
require.Equal(t, "", posts.NextPostId, "should return an empty NextPostId")
|
|
require.Equal(t, post7.Id, posts.PrevPostId, "should return post7.Id as PrevPostId")
|
|
|
|
// similar to '/posts?per_page=3&page=1'
|
|
posts, _, err = c.GetPostsForChannel(context.Background(), th.BasicChannel.Id, 1, 3, "", false, false)
|
|
require.NoError(t, err)
|
|
require.Len(t, posts.Order, 3, "expected 3 posts")
|
|
require.Equal(t, post7.Id, posts.Order[0], "posts not in order")
|
|
require.Equal(t, post5.Id, posts.Order[2], "posts not in order")
|
|
require.Equal(t, post8.Id, posts.NextPostId, "should return post8.Id as NextPostId")
|
|
require.Equal(t, post4.Id, posts.PrevPostId, "should return post4.Id as PrevPostId")
|
|
|
|
// similar to '/posts?per_page=3&page=2'
|
|
posts, _, err = c.GetPostsForChannel(context.Background(), th.BasicChannel.Id, 2, 3, "", false, false)
|
|
require.NoError(t, err)
|
|
require.Len(t, posts.Order, 3, "expected 3 posts")
|
|
require.Equal(t, post4.Id, posts.Order[0], "posts not in order")
|
|
require.Equal(t, post2.Id, posts.Order[2], "should return 3 posts and match order")
|
|
require.Equal(t, post5.Id, posts.NextPostId, "should return post5.Id as NextPostId")
|
|
require.Equal(t, post1.Id, posts.PrevPostId, "should return post1.Id as PrevPostId")
|
|
|
|
// similar to '/posts?per_page=3&page=3'
|
|
posts, _, err = c.GetPostsForChannel(context.Background(), th.BasicChannel.Id, 3, 3, "", false, false)
|
|
require.NoError(t, err)
|
|
require.Len(t, posts.Order, 3, "expected 3 posts")
|
|
require.Equal(t, post1.Id, posts.Order[0], "posts not in order")
|
|
require.Equal(t, systemPostId1, posts.Order[2], "should return 3 posts and match order")
|
|
require.Equal(t, post2.Id, posts.NextPostId, "should return post2.Id as NextPostId")
|
|
require.Equal(t, "", posts.PrevPostId, "should return an empty PrevPostId")
|
|
|
|
// similar to '/posts?per_page=3&page=4'
|
|
posts, _, err = c.GetPostsForChannel(context.Background(), th.BasicChannel.Id, 4, 3, "", false, false)
|
|
require.NoError(t, err)
|
|
require.Empty(t, posts.Order, "should return 0 post")
|
|
require.Equal(t, "", posts.NextPostId, "should return an empty NextPostId")
|
|
require.Equal(t, "", posts.PrevPostId, "should return an empty PrevPostId")
|
|
})
|
|
|
|
th.TestForAllClients(t, func(t *testing.T, c *model.Client4) {
|
|
channel := th.CreatePublicChannel(t)
|
|
th.CreatePostWithClient(t, th.SystemAdminClient, channel)
|
|
_, err = th.SystemAdminClient.DeleteChannel(context.Background(), channel.Id)
|
|
require.NoError(t, err)
|
|
|
|
_, _, err = c.GetPostsForChannel(context.Background(), channel.Id, 0, 10, "", false, false)
|
|
require.NoError(t, err)
|
|
}, "Should allow retrieving posts if the channel is archived")
|
|
|
|
_, err = client.DeletePost(context.Background(), post10.Id)
|
|
require.NoError(t, err)
|
|
_, err = client.DeletePost(context.Background(), post8.Id)
|
|
require.NoError(t, err)
|
|
|
|
// include deleted posts for non-admin users.
|
|
_, resp, err = client.GetPostsForChannel(context.Background(), th.BasicChannel.Id, 0, 100, "", false, true)
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
|
|
th.TestForSystemAdminAndLocal(t, func(t *testing.T, c *model.Client4) {
|
|
// include deleted posts for admin users.
|
|
posts, resp, err = c.GetPostsForChannel(context.Background(), th.BasicChannel.Id, 0, 100, "", false, true)
|
|
require.NoError(t, err)
|
|
CheckOKStatus(t, resp)
|
|
require.Len(t, posts.Order, 12, "expected 12 posts")
|
|
|
|
// not include deleted posts for admin users.
|
|
posts, resp, err = c.GetPostsForChannel(context.Background(), th.BasicChannel.Id, 0, 100, "", false, false)
|
|
require.NoError(t, err)
|
|
CheckOKStatus(t, resp)
|
|
require.Len(t, posts.Order, 10, "expected 10 posts")
|
|
|
|
// System admin can access public channel without being member
|
|
adminPublicChannel := th.CreatePublicChannel(t)
|
|
th.CreateMessagePostNoClient(t, adminPublicChannel, "admin channel post", model.GetMillis())
|
|
posts, resp, err = c.GetPostsForChannel(context.Background(), adminPublicChannel.Id, 0, 100, "", false, false)
|
|
require.NoError(t, err)
|
|
CheckOKStatus(t, resp)
|
|
require.NotEmpty(t, posts.Order)
|
|
|
|
// System admin can access private channel without being member
|
|
privateChannel := th.CreatePrivateChannel(t)
|
|
th.CreateMessagePostNoClient(t, privateChannel, "private channel post", model.GetMillis())
|
|
posts, resp, err = c.GetPostsForChannel(context.Background(), privateChannel.Id, 0, 100, "", false, false)
|
|
require.NoError(t, err)
|
|
CheckOKStatus(t, resp)
|
|
require.NotEmpty(t, posts.Order)
|
|
|
|
// System admin can access direct messages without being member
|
|
dmChannel := th.CreateDmChannel(t, th.BasicUser2)
|
|
th.CreateMessagePostNoClient(t, dmChannel, "test1", model.GetMillis())
|
|
posts, resp, err = c.GetPostsForChannel(context.Background(), dmChannel.Id, 0, 100, "", false, false)
|
|
require.NoError(t, err)
|
|
CheckOKStatus(t, resp)
|
|
require.NotEmpty(t, posts.Order)
|
|
|
|
// System admin can access group messages without being member
|
|
user3 := th.CreateUser(t)
|
|
gmChannel, _, err := th.Client.CreateGroupChannel(context.Background(), []string{th.BasicUser.Id, th.BasicUser2.Id, user3.Id})
|
|
require.NoError(t, err)
|
|
th.CreateMessagePostNoClient(t, gmChannel, "test2", model.GetMillis())
|
|
posts, resp, err = c.GetPostsForChannel(context.Background(), gmChannel.Id, 0, 100, "", false, false)
|
|
require.NoError(t, err)
|
|
CheckOKStatus(t, resp)
|
|
require.NotEmpty(t, posts.Order)
|
|
})
|
|
}
|
|
|
|
func TestGetFlaggedPostsForUser(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
|
|
th := Setup(t).InitBasic(t)
|
|
client := th.Client
|
|
user := th.BasicUser
|
|
team1 := th.BasicTeam
|
|
channel1 := th.BasicChannel
|
|
post1 := th.CreatePost(t)
|
|
channel2 := th.CreatePublicChannel(t)
|
|
post2 := th.CreatePostWithClient(t, client, channel2)
|
|
|
|
preference := model.Preference{
|
|
UserId: user.Id,
|
|
Category: model.PreferenceCategoryFlaggedPost,
|
|
Name: post1.Id,
|
|
Value: "true",
|
|
}
|
|
_, err := client.UpdatePreferences(context.Background(), user.Id, model.Preferences{preference})
|
|
require.NoError(t, err)
|
|
preference.Name = post2.Id
|
|
_, err = client.UpdatePreferences(context.Background(), user.Id, model.Preferences{preference})
|
|
require.NoError(t, err)
|
|
|
|
opl := model.NewPostList()
|
|
opl.AddPost(post1)
|
|
opl.AddOrder(post1.Id)
|
|
|
|
rpl, _, err := client.GetFlaggedPostsForUserInChannel(context.Background(), user.Id, channel1.Id, 0, 10)
|
|
require.NoError(t, err)
|
|
|
|
require.Len(t, rpl.Posts, 1, "should have returned 1 post")
|
|
require.Equal(t, opl.Posts, rpl.Posts, "posts should have matched")
|
|
|
|
rpl, _, err = client.GetFlaggedPostsForUserInChannel(context.Background(), user.Id, channel1.Id, 0, 1)
|
|
require.NoError(t, err)
|
|
require.Len(t, rpl.Posts, 1, "should have returned 1 post")
|
|
|
|
rpl, _, err = client.GetFlaggedPostsForUserInChannel(context.Background(), user.Id, channel1.Id, 1, 1)
|
|
require.NoError(t, err)
|
|
require.Empty(t, rpl.Posts)
|
|
|
|
rpl, _, err = client.GetFlaggedPostsForUserInChannel(context.Background(), user.Id, GenerateTestID(), 0, 10)
|
|
require.NoError(t, err)
|
|
require.Empty(t, rpl.Posts)
|
|
|
|
rpl, _, err = client.GetFlaggedPostsForUserInChannel(context.Background(), user.Id, "junk", 0, 10)
|
|
require.Error(t, err)
|
|
require.Nil(t, rpl)
|
|
|
|
opl.AddPost(post2)
|
|
opl.AddOrder(post2.Id)
|
|
|
|
rpl, _, err = client.GetFlaggedPostsForUserInTeam(context.Background(), user.Id, team1.Id, 0, 10)
|
|
require.NoError(t, err)
|
|
require.Len(t, rpl.Posts, 2, "should have returned 2 posts")
|
|
require.Equal(t, opl.Posts, rpl.Posts, "posts should have matched")
|
|
|
|
rpl, _, err = client.GetFlaggedPostsForUserInTeam(context.Background(), user.Id, team1.Id, 0, 1)
|
|
require.NoError(t, err)
|
|
require.Len(t, rpl.Posts, 1, "should have returned 1 post")
|
|
|
|
rpl, _, err = client.GetFlaggedPostsForUserInTeam(context.Background(), user.Id, team1.Id, 1, 1)
|
|
require.NoError(t, err)
|
|
require.Len(t, rpl.Posts, 1, "should have returned 1 post")
|
|
|
|
rpl, _, err = client.GetFlaggedPostsForUserInTeam(context.Background(), user.Id, team1.Id, 1000, 10)
|
|
require.NoError(t, err)
|
|
require.Empty(t, rpl.Posts)
|
|
|
|
rpl, _, err = client.GetFlaggedPostsForUserInTeam(context.Background(), user.Id, GenerateTestID(), 0, 10)
|
|
require.NoError(t, err)
|
|
require.Empty(t, rpl.Posts)
|
|
|
|
rpl, _, err = client.GetFlaggedPostsForUserInTeam(context.Background(), user.Id, "junk", 0, 10)
|
|
require.Error(t, err)
|
|
require.Nil(t, rpl)
|
|
|
|
channel3 := th.CreatePrivateChannel(t)
|
|
post4 := th.CreatePostWithClient(t, client, channel3)
|
|
|
|
preference.Name = post4.Id
|
|
_, err = client.UpdatePreferences(context.Background(), user.Id, model.Preferences{preference})
|
|
require.NoError(t, err)
|
|
|
|
opl.AddPost(post4)
|
|
opl.AddOrder(post4.Id)
|
|
|
|
rpl, _, err = client.GetFlaggedPostsForUser(context.Background(), user.Id, 0, 10)
|
|
require.NoError(t, err)
|
|
require.Len(t, rpl.Posts, 3, "should have returned 3 posts")
|
|
require.Equal(t, opl.Posts, rpl.Posts, "posts should have matched")
|
|
|
|
rpl, _, err = client.GetFlaggedPostsForUser(context.Background(), user.Id, 0, 2)
|
|
require.NoError(t, err)
|
|
require.Len(t, rpl.Posts, 2, "should have returned 2 posts")
|
|
|
|
rpl, _, err = client.GetFlaggedPostsForUser(context.Background(), user.Id, 2, 2)
|
|
require.NoError(t, err)
|
|
require.Len(t, rpl.Posts, 1, "should have returned 1 post")
|
|
|
|
rpl, _, err = client.GetFlaggedPostsForUser(context.Background(), user.Id, 1000, 10)
|
|
require.NoError(t, err)
|
|
require.Empty(t, rpl.Posts)
|
|
|
|
channel4 := th.CreateChannelWithClient(t, th.SystemAdminClient, model.ChannelTypePrivate)
|
|
post5 := th.CreatePostWithClient(t, th.SystemAdminClient, channel4)
|
|
|
|
preference.Name = post5.Id
|
|
resp, err := client.UpdatePreferences(context.Background(), user.Id, model.Preferences{preference})
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
|
|
rpl, _, err = client.GetFlaggedPostsForUser(context.Background(), user.Id, 0, 10)
|
|
require.NoError(t, err)
|
|
require.Len(t, rpl.Posts, 3, "should have returned 3 posts")
|
|
require.Equal(t, opl.Posts, rpl.Posts, "posts should have matched")
|
|
|
|
th.AddUserToChannel(t, user, channel4)
|
|
_, err = client.UpdatePreferences(context.Background(), user.Id, model.Preferences{preference})
|
|
require.NoError(t, err)
|
|
|
|
rpl, _, err = client.GetFlaggedPostsForUser(context.Background(), user.Id, 0, 10)
|
|
require.NoError(t, err)
|
|
|
|
opl.AddPost(post5)
|
|
opl.AddOrder(post5.Id)
|
|
require.Len(t, rpl.Posts, 4, "should have returned 4 posts")
|
|
require.Equal(t, opl.Posts, rpl.Posts, "posts should have matched")
|
|
|
|
appErr := th.App.RemoveUserFromChannel(th.Context, user.Id, "", channel4)
|
|
assert.Nil(t, appErr, "unable to remove user from channel")
|
|
|
|
rpl, _, err = client.GetFlaggedPostsForUser(context.Background(), user.Id, 0, 10)
|
|
require.NoError(t, err)
|
|
|
|
opl2 := model.NewPostList()
|
|
opl2.AddPost(post1)
|
|
opl2.AddOrder(post1.Id)
|
|
opl2.AddPost(post2)
|
|
opl2.AddOrder(post2.Id)
|
|
opl2.AddPost(post4)
|
|
opl2.AddOrder(post4.Id)
|
|
|
|
require.Len(t, rpl.Posts, 3, "should have returned 3 posts")
|
|
require.Equal(t, opl2.Posts, rpl.Posts, "posts should have matched")
|
|
|
|
_, resp, err = client.GetFlaggedPostsForUser(context.Background(), "junk", 0, 10)
|
|
require.Error(t, err)
|
|
CheckBadRequestStatus(t, resp)
|
|
|
|
_, resp, err = client.GetFlaggedPostsForUser(context.Background(), GenerateTestID(), 0, 10)
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
|
|
_, err = client.Logout(context.Background())
|
|
require.NoError(t, err)
|
|
|
|
_, resp, err = client.GetFlaggedPostsForUserInChannel(context.Background(), user.Id, channel1.Id, 0, 10)
|
|
require.Error(t, err)
|
|
CheckUnauthorizedStatus(t, resp)
|
|
|
|
_, resp, err = client.GetFlaggedPostsForUserInTeam(context.Background(), user.Id, team1.Id, 0, 10)
|
|
require.Error(t, err)
|
|
CheckUnauthorizedStatus(t, resp)
|
|
|
|
_, resp, err = client.GetFlaggedPostsForUser(context.Background(), user.Id, 0, 10)
|
|
require.Error(t, err)
|
|
CheckUnauthorizedStatus(t, resp)
|
|
|
|
_, _, err = th.SystemAdminClient.GetFlaggedPostsForUserInChannel(context.Background(), user.Id, channel1.Id, 0, 10)
|
|
require.NoError(t, err)
|
|
|
|
_, _, err = th.SystemAdminClient.GetFlaggedPostsForUserInTeam(context.Background(), user.Id, team1.Id, 0, 10)
|
|
require.NoError(t, err)
|
|
|
|
_, _, err = th.SystemAdminClient.GetFlaggedPostsForUser(context.Background(), user.Id, 0, 10)
|
|
require.NoError(t, err)
|
|
|
|
mockStore := mocks.Store{}
|
|
mockPostStore := mocks.PostStore{}
|
|
mockPostStore.On("GetFlaggedPosts", mock.AnythingOfType("string"), mock.AnythingOfType("int"), mock.AnythingOfType("int")).Return(nil, errors.New("some-error"))
|
|
mockPostStore.On("ClearCaches").Return()
|
|
mockStore.On("Team").Return(th.App.Srv().Store().Team())
|
|
mockStore.On("Channel").Return(th.App.Srv().Store().Channel())
|
|
mockStore.On("User").Return(th.App.Srv().Store().User())
|
|
mockStore.On("Scheme").Return(th.App.Srv().Store().Scheme())
|
|
mockStore.On("Post").Return(&mockPostStore)
|
|
mockStore.On("FileInfo").Return(th.App.Srv().Store().FileInfo())
|
|
mockStore.On("Webhook").Return(th.App.Srv().Store().Webhook())
|
|
mockStore.On("System").Return(th.App.Srv().Store().System())
|
|
mockStore.On("License").Return(th.App.Srv().Store().License())
|
|
mockStore.On("Role").Return(th.App.Srv().Store().Role())
|
|
mockStore.On("Close").Return(nil)
|
|
|
|
// Playbooks DB job requires a plugin mock
|
|
pluginStore := mocks.PluginStore{}
|
|
pluginStore.On("List", mock.Anything, mock.Anything, mock.Anything).Return([]string{}, nil)
|
|
mockStore.On("Plugin").Return(&pluginStore)
|
|
|
|
th.App.Srv().SetStore(&mockStore)
|
|
|
|
_, resp, err = th.SystemAdminClient.GetFlaggedPostsForUser(context.Background(), user.Id, 0, 10)
|
|
require.Error(t, err)
|
|
CheckInternalErrorStatus(t, resp)
|
|
}
|
|
|
|
func TestGetPostsBefore(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
|
|
th := Setup(t).InitBasic(t)
|
|
client := th.Client
|
|
|
|
post1 := th.CreatePost(t)
|
|
post2 := th.CreatePost(t)
|
|
post3 := th.CreatePost(t)
|
|
post4 := th.CreatePost(t)
|
|
post5 := th.CreatePost(t)
|
|
|
|
posts, _, err := client.GetPostsBefore(context.Background(), th.BasicChannel.Id, post3.Id, 0, 100, "", false, false)
|
|
require.NoError(t, err)
|
|
|
|
found := make([]bool, 2)
|
|
for _, p := range posts.Posts {
|
|
if p.Id == post1.Id {
|
|
found[0] = true
|
|
} else if p.Id == post2.Id {
|
|
found[1] = true
|
|
}
|
|
|
|
require.NotEqual(t, post4.Id, p.Id, "returned posts after")
|
|
require.NotEqual(t, post5.Id, p.Id, "returned posts after")
|
|
}
|
|
|
|
for _, f := range found {
|
|
require.True(t, f, "missing post")
|
|
}
|
|
|
|
require.Equal(t, post3.Id, posts.NextPostId, "should match NextPostId")
|
|
require.Equal(t, "", posts.PrevPostId, "should match empty PrevPostId")
|
|
|
|
posts, _, err = client.GetPostsBefore(context.Background(), th.BasicChannel.Id, post4.Id, 1, 1, "", false, false)
|
|
require.NoError(t, err)
|
|
require.Len(t, posts.Posts, 1, "too many posts returned")
|
|
require.Equal(t, post2.Id, posts.Order[0], "should match returned post")
|
|
require.Equal(t, post3.Id, posts.NextPostId, "should match NextPostId")
|
|
require.Equal(t, post1.Id, posts.PrevPostId, "should match PrevPostId")
|
|
|
|
_, resp, err := client.GetPostsBefore(context.Background(), th.BasicChannel.Id, "junk", 1, 1, "", false, false)
|
|
require.Error(t, err)
|
|
CheckBadRequestStatus(t, resp)
|
|
|
|
posts, _, err = client.GetPostsBefore(context.Background(), th.BasicChannel.Id, post5.Id, 0, 3, "", false, false)
|
|
require.NoError(t, err)
|
|
require.Len(t, posts.Posts, 3, "should match length of posts returned")
|
|
require.Equal(t, post4.Id, posts.Order[0], "should match returned post")
|
|
require.Equal(t, post2.Id, posts.Order[2], "should match returned post")
|
|
require.Equal(t, post5.Id, posts.NextPostId, "should match NextPostId")
|
|
require.Equal(t, post1.Id, posts.PrevPostId, "should match PrevPostId")
|
|
|
|
// get the system post IDs posted before the created posts above
|
|
posts, _, err = client.GetPostsBefore(context.Background(), th.BasicChannel.Id, post1.Id, 0, 2, "", false, false)
|
|
require.NoError(t, err)
|
|
systemPostId2 := posts.Order[0]
|
|
systemPostId1 := posts.Order[1]
|
|
|
|
posts, _, err = client.GetPostsBefore(context.Background(), th.BasicChannel.Id, post5.Id, 1, 3, "", false, false)
|
|
require.NoError(t, err)
|
|
require.Len(t, posts.Posts, 3, "should match length of posts returned")
|
|
require.Equal(t, post1.Id, posts.Order[0], "should match returned post")
|
|
require.Equal(t, systemPostId2, posts.Order[1], "should match returned post")
|
|
require.Equal(t, systemPostId1, posts.Order[2], "should match returned post")
|
|
require.Equal(t, post2.Id, posts.NextPostId, "should match NextPostId")
|
|
require.Equal(t, "", posts.PrevPostId, "should return empty PrevPostId")
|
|
|
|
// more tests for next_post_id, prev_post_id, and order
|
|
// There are 12 posts composed of first 2 system messages and 10 created posts
|
|
post6 := th.CreatePost(t)
|
|
th.CreatePost(t) // post7
|
|
post8 := th.CreatePost(t)
|
|
post9 := th.CreatePost(t)
|
|
post10 := th.CreatePost(t) // post10
|
|
|
|
// similar to '/posts?before=post9'
|
|
posts, _, err = client.GetPostsBefore(context.Background(), th.BasicChannel.Id, post9.Id, 0, 60, "", false, false)
|
|
require.NoError(t, err)
|
|
require.Len(t, posts.Order, 10, "expected 10 posts")
|
|
require.Equal(t, post8.Id, posts.Order[0], "posts not in order")
|
|
require.Equal(t, systemPostId1, posts.Order[9], "posts not in order")
|
|
require.Equal(t, post9.Id, posts.NextPostId, "should return post9.Id as NextPostId")
|
|
require.Equal(t, "", posts.PrevPostId, "should return an empty PrevPostId")
|
|
|
|
// similar to '/posts?before=post9&per_page=3'
|
|
posts, _, err = client.GetPostsBefore(context.Background(), th.BasicChannel.Id, post9.Id, 0, 3, "", false, false)
|
|
require.NoError(t, err)
|
|
require.Len(t, posts.Order, 3, "expected 3 posts")
|
|
require.Equal(t, post8.Id, posts.Order[0], "posts not in order")
|
|
require.Equal(t, post6.Id, posts.Order[2], "should return 3 posts and match order")
|
|
require.Equal(t, post9.Id, posts.NextPostId, "should return post9.Id as NextPostId")
|
|
require.Equal(t, post5.Id, posts.PrevPostId, "should return post5.Id as PrevPostId")
|
|
|
|
// similar to '/posts?before=post9&per_page=3&page=1'
|
|
posts, _, err = client.GetPostsBefore(context.Background(), th.BasicChannel.Id, post9.Id, 1, 3, "", false, false)
|
|
require.NoError(t, err)
|
|
require.Len(t, posts.Order, 3, "expected 3 posts")
|
|
require.Equal(t, post5.Id, posts.Order[0], "posts not in order")
|
|
require.Equal(t, post3.Id, posts.Order[2], "posts not in order")
|
|
require.Equal(t, post6.Id, posts.NextPostId, "should return post6.Id as NextPostId")
|
|
require.Equal(t, post2.Id, posts.PrevPostId, "should return post2.Id as PrevPostId")
|
|
|
|
// similar to '/posts?before=post9&per_page=3&page=2'
|
|
posts, _, err = client.GetPostsBefore(context.Background(), th.BasicChannel.Id, post9.Id, 2, 3, "", false, false)
|
|
require.NoError(t, err)
|
|
require.Len(t, posts.Order, 3, "expected 3 posts")
|
|
require.Equal(t, post2.Id, posts.Order[0], "posts not in order")
|
|
require.Equal(t, systemPostId2, posts.Order[2], "posts not in order")
|
|
require.Equal(t, post3.Id, posts.NextPostId, "should return post3.Id as NextPostId")
|
|
require.Equal(t, systemPostId1, posts.PrevPostId, "should return systemPostId1 as PrevPostId")
|
|
|
|
// similar to '/posts?before=post1&per_page=3'
|
|
posts, _, err = client.GetPostsBefore(context.Background(), th.BasicChannel.Id, post1.Id, 0, 3, "", false, false)
|
|
require.NoError(t, err)
|
|
require.Len(t, posts.Order, 2, "expected 2 posts")
|
|
require.Equal(t, systemPostId2, posts.Order[0], "posts not in order")
|
|
require.Equal(t, systemPostId1, posts.Order[1], "posts not in order")
|
|
require.Equal(t, post1.Id, posts.NextPostId, "should return post1.Id as NextPostId")
|
|
require.Equal(t, "", posts.PrevPostId, "should return an empty PrevPostId")
|
|
|
|
// similar to '/posts?before=systemPostId1'
|
|
posts, _, err = client.GetPostsBefore(context.Background(), th.BasicChannel.Id, systemPostId1, 0, 60, "", false, false)
|
|
require.NoError(t, err)
|
|
require.Empty(t, posts.Order, "should return 0 post")
|
|
require.Equal(t, systemPostId1, posts.NextPostId, "should return systemPostId1 as NextPostId")
|
|
require.Equal(t, "", posts.PrevPostId, "should return an empty PrevPostId")
|
|
|
|
// similar to '/posts?before=systemPostId1&per_page=60&page=1'
|
|
posts, _, err = client.GetPostsBefore(context.Background(), th.BasicChannel.Id, systemPostId1, 1, 60, "", false, false)
|
|
require.NoError(t, err)
|
|
require.Empty(t, posts.Order, "should return 0 posts")
|
|
require.Equal(t, "", posts.NextPostId, "should return an empty NextPostId")
|
|
require.Equal(t, "", posts.PrevPostId, "should return an empty PrevPostId")
|
|
|
|
// similar to '/posts?before=non-existent-post'
|
|
nonExistentPostId := model.NewId()
|
|
posts, _, err = client.GetPostsBefore(context.Background(), th.BasicChannel.Id, nonExistentPostId, 0, 60, "", false, false)
|
|
require.NoError(t, err)
|
|
require.Empty(t, posts.Order, "should return 0 post")
|
|
require.Equal(t, nonExistentPostId, posts.NextPostId, "should return nonExistentPostId as NextPostId")
|
|
require.Equal(t, "", posts.PrevPostId, "should return an empty PrevPostId")
|
|
|
|
_, err = client.DeletePost(context.Background(), post9.Id)
|
|
require.NoError(t, err)
|
|
_, err = client.DeletePost(context.Background(), post8.Id)
|
|
require.NoError(t, err)
|
|
|
|
// include deleted posts for non-admin users.
|
|
_, resp, err = client.GetPostsBefore(context.Background(), th.BasicChannel.Id, post9.Id, 0, 60, "", false, true)
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
|
|
th.TestForSystemAdminAndLocal(t, func(t *testing.T, c *model.Client4) {
|
|
// include deleted posts for admin users.
|
|
posts, resp, err = c.GetPostsBefore(context.Background(), th.BasicChannel.Id, post10.Id, 0, 60, "", false, true)
|
|
require.NoError(t, err)
|
|
CheckOKStatus(t, resp)
|
|
require.Len(t, posts.Order, 11, "expected 11 posts")
|
|
|
|
// not include deleted posts for admin users.
|
|
posts, resp, err = c.GetPostsBefore(context.Background(), th.BasicChannel.Id, post10.Id, 0, 60, "", false, false)
|
|
require.NoError(t, err)
|
|
CheckOKStatus(t, resp)
|
|
require.Len(t, posts.Order, 9, "expected 9 posts")
|
|
})
|
|
}
|
|
|
|
func TestGetPostsAfter(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
|
|
th := Setup(t).InitBasic(t)
|
|
client := th.Client
|
|
|
|
post1 := th.CreatePost(t)
|
|
post2 := th.CreatePost(t)
|
|
post3 := th.CreatePost(t)
|
|
post4 := th.CreatePost(t)
|
|
post5 := th.CreatePost(t)
|
|
|
|
posts, _, err := client.GetPostsAfter(context.Background(), th.BasicChannel.Id, post3.Id, 0, 100, "", false, false)
|
|
require.NoError(t, err)
|
|
|
|
found := make([]bool, 2)
|
|
for _, p := range posts.Posts {
|
|
if p.Id == post4.Id {
|
|
found[0] = true
|
|
} else if p.Id == post5.Id {
|
|
found[1] = true
|
|
}
|
|
require.NotEqual(t, post1.Id, p.Id, "returned posts before")
|
|
require.NotEqual(t, post2.Id, p.Id, "returned posts before")
|
|
}
|
|
|
|
for _, f := range found {
|
|
require.True(t, f, "missing post")
|
|
}
|
|
require.Equal(t, "", posts.NextPostId, "should match empty NextPostId")
|
|
require.Equal(t, post3.Id, posts.PrevPostId, "should match PrevPostId")
|
|
|
|
posts, _, err = client.GetPostsAfter(context.Background(), th.BasicChannel.Id, post2.Id, 1, 1, "", false, false)
|
|
require.NoError(t, err)
|
|
require.Len(t, posts.Posts, 1, "too many posts returned")
|
|
require.Equal(t, post4.Id, posts.Order[0], "should match returned post")
|
|
require.Equal(t, post5.Id, posts.NextPostId, "should match NextPostId")
|
|
require.Equal(t, post3.Id, posts.PrevPostId, "should match PrevPostId")
|
|
|
|
_, resp, err := client.GetPostsAfter(context.Background(), th.BasicChannel.Id, "junk", 1, 1, "", false, false)
|
|
require.Error(t, err)
|
|
CheckBadRequestStatus(t, resp)
|
|
|
|
posts, _, err = client.GetPostsAfter(context.Background(), th.BasicChannel.Id, post1.Id, 0, 3, "", false, false)
|
|
require.NoError(t, err)
|
|
require.Len(t, posts.Posts, 3, "should match length of posts returned")
|
|
require.Equal(t, post4.Id, posts.Order[0], "should match returned post")
|
|
require.Equal(t, post2.Id, posts.Order[2], "should match returned post")
|
|
require.Equal(t, post5.Id, posts.NextPostId, "should match NextPostId")
|
|
require.Equal(t, post1.Id, posts.PrevPostId, "should match PrevPostId")
|
|
|
|
posts, _, err = client.GetPostsAfter(context.Background(), th.BasicChannel.Id, post1.Id, 1, 3, "", false, false)
|
|
require.NoError(t, err)
|
|
require.Len(t, posts.Posts, 1, "should match length of posts returned")
|
|
require.Equal(t, post5.Id, posts.Order[0], "should match returned post")
|
|
require.Equal(t, "", posts.NextPostId, "should match NextPostId")
|
|
require.Equal(t, post4.Id, posts.PrevPostId, "should match PrevPostId")
|
|
|
|
// more tests for next_post_id, prev_post_id, and order
|
|
// There are 12 posts composed of first 2 system messages and 10 created posts
|
|
post6 := th.CreatePost(t)
|
|
th.CreatePost(t) // post7
|
|
post8 := th.CreatePost(t)
|
|
post9 := th.CreatePost(t)
|
|
post10 := th.CreatePost(t)
|
|
|
|
// similar to '/posts?after=post2'
|
|
posts, _, err = client.GetPostsAfter(context.Background(), th.BasicChannel.Id, post2.Id, 0, 60, "", false, false)
|
|
require.NoError(t, err)
|
|
require.Len(t, posts.Order, 8, "expected 8 posts")
|
|
require.Equal(t, post10.Id, posts.Order[0], "should match order")
|
|
require.Equal(t, post3.Id, posts.Order[7], "should match order")
|
|
require.Equal(t, "", posts.NextPostId, "should return an empty NextPostId")
|
|
require.Equal(t, post2.Id, posts.PrevPostId, "should return post2.Id as PrevPostId")
|
|
|
|
// similar to '/posts?after=post2&per_page=3'
|
|
posts, _, err = client.GetPostsAfter(context.Background(), th.BasicChannel.Id, post2.Id, 0, 3, "", false, false)
|
|
require.NoError(t, err)
|
|
require.Len(t, posts.Order, 3, "expected 3 posts")
|
|
require.Equal(t, post5.Id, posts.Order[0], "should match order")
|
|
require.Equal(t, post3.Id, posts.Order[2], "should return 3 posts and match order")
|
|
require.Equal(t, post6.Id, posts.NextPostId, "should return post6.Id as NextPostId")
|
|
require.Equal(t, post2.Id, posts.PrevPostId, "should return post2.Id as PrevPostId")
|
|
|
|
// similar to '/posts?after=post2&per_page=3&page=1'
|
|
posts, _, err = client.GetPostsAfter(context.Background(), th.BasicChannel.Id, post2.Id, 1, 3, "", false, false)
|
|
require.NoError(t, err)
|
|
require.Len(t, posts.Order, 3, "expected 3 posts")
|
|
require.Equal(t, post8.Id, posts.Order[0], "should match order")
|
|
require.Equal(t, post6.Id, posts.Order[2], "should match order")
|
|
require.Equal(t, post9.Id, posts.NextPostId, "should return post9.Id as NextPostId")
|
|
require.Equal(t, post5.Id, posts.PrevPostId, "should return post5.Id as PrevPostId")
|
|
|
|
// similar to '/posts?after=post2&per_page=3&page=2'
|
|
posts, _, err = client.GetPostsAfter(context.Background(), th.BasicChannel.Id, post2.Id, 2, 3, "", false, false)
|
|
require.NoError(t, err)
|
|
require.Len(t, posts.Order, 2, "expected 2 posts")
|
|
require.Equal(t, post10.Id, posts.Order[0], "should match order")
|
|
require.Equal(t, post9.Id, posts.Order[1], "should match order")
|
|
require.Equal(t, "", posts.NextPostId, "should return an empty NextPostId")
|
|
require.Equal(t, post8.Id, posts.PrevPostId, "should return post8.Id as PrevPostId")
|
|
|
|
// similar to '/posts?after=post10'
|
|
posts, _, err = client.GetPostsAfter(context.Background(), th.BasicChannel.Id, post10.Id, 0, 60, "", false, false)
|
|
require.NoError(t, err)
|
|
require.Empty(t, posts.Order, "should return 0 post")
|
|
require.Equal(t, "", posts.NextPostId, "should return an empty NextPostId")
|
|
require.Equal(t, post10.Id, posts.PrevPostId, "should return post10.Id as PrevPostId")
|
|
|
|
// similar to '/posts?after=post10&page=1'
|
|
posts, _, err = client.GetPostsAfter(context.Background(), th.BasicChannel.Id, post10.Id, 1, 60, "", false, false)
|
|
require.NoError(t, err)
|
|
require.Empty(t, posts.Order, "should return 0 post")
|
|
require.Equal(t, "", posts.NextPostId, "should return an empty NextPostId")
|
|
require.Equal(t, "", posts.PrevPostId, "should return an empty PrevPostId")
|
|
|
|
// similar to '/posts?after=non-existent-post'
|
|
nonExistentPostId := model.NewId()
|
|
posts, _, err = client.GetPostsAfter(context.Background(), th.BasicChannel.Id, nonExistentPostId, 0, 60, "", false, false)
|
|
require.NoError(t, err)
|
|
require.Empty(t, posts.Order, "should return 0 post")
|
|
require.Equal(t, "", posts.NextPostId, "should return an empty NextPostId")
|
|
require.Equal(t, nonExistentPostId, posts.PrevPostId, "should return nonExistentPostId as PrevPostId")
|
|
|
|
_, err = client.DeletePost(context.Background(), post10.Id)
|
|
require.NoError(t, err)
|
|
_, err = client.DeletePost(context.Background(), post9.Id)
|
|
require.NoError(t, err)
|
|
|
|
// include deleted posts for non-admin users.
|
|
_, resp, err = client.GetPostsAfter(context.Background(), th.BasicChannel.Id, post1.Id, 0, 60, "", false, true)
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
|
|
th.TestForSystemAdminAndLocal(t, func(t *testing.T, c *model.Client4) {
|
|
// include deleted posts for admin users.
|
|
posts, resp, err = c.GetPostsAfter(context.Background(), th.BasicChannel.Id, post1.Id, 0, 60, "", false, true)
|
|
require.NoError(t, err)
|
|
CheckOKStatus(t, resp)
|
|
require.Len(t, posts.Order, 9, "expected 9 posts")
|
|
|
|
// not include deleted posts for admin users.
|
|
posts, resp, err = c.GetPostsAfter(context.Background(), th.BasicChannel.Id, post1.Id, 0, 60, "", false, false)
|
|
require.NoError(t, err)
|
|
CheckOKStatus(t, resp)
|
|
require.Len(t, posts.Order, 7, "expected 7 posts")
|
|
})
|
|
}
|
|
|
|
func TestGetPostsForChannelAroundLastUnread(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
|
|
th := Setup(t).InitBasic(t)
|
|
client := th.Client
|
|
userId := th.BasicUser.Id
|
|
channelId := th.BasicChannel.Id
|
|
|
|
// 12 posts = 2 systems posts + 10 created posts below
|
|
post1 := th.CreatePost(t)
|
|
post2 := th.CreatePost(t)
|
|
post3 := th.CreatePost(t)
|
|
post4 := th.CreatePost(t)
|
|
post5 := th.CreatePost(t)
|
|
replyPost := &model.Post{ChannelId: channelId, Message: model.NewId(), RootId: post4.Id}
|
|
post6, _, err := client.CreatePost(context.Background(), replyPost)
|
|
require.NoError(t, err)
|
|
post7, _, err := client.CreatePost(context.Background(), replyPost)
|
|
require.NoError(t, err)
|
|
post8, _, err := client.CreatePost(context.Background(), replyPost)
|
|
require.NoError(t, err)
|
|
post9, _, err := client.CreatePost(context.Background(), replyPost)
|
|
require.NoError(t, err)
|
|
post10, _, err := client.CreatePost(context.Background(), replyPost)
|
|
require.NoError(t, err)
|
|
|
|
postIdNames := map[string]string{
|
|
post1.Id: "post1",
|
|
post2.Id: "post2",
|
|
post3.Id: "post3",
|
|
post4.Id: "post4",
|
|
post5.Id: "post5",
|
|
post6.Id: "post6 (reply to post4)",
|
|
post7.Id: "post7 (reply to post4)",
|
|
post8.Id: "post8 (reply to post4)",
|
|
post9.Id: "post9 (reply to post4)",
|
|
post10.Id: "post10 (reply to post4)",
|
|
}
|
|
|
|
namePost := func(postId string) string {
|
|
name, ok := postIdNames[postId]
|
|
if ok {
|
|
return name
|
|
}
|
|
|
|
return fmt.Sprintf("unknown (%s)", postId)
|
|
}
|
|
|
|
namePosts := func(postIds []string) []string {
|
|
namedPostIds := make([]string, 0, len(postIds))
|
|
for _, postId := range postIds {
|
|
namedPostIds = append(namedPostIds, namePost(postId))
|
|
}
|
|
|
|
return namedPostIds
|
|
}
|
|
|
|
namePostsMap := func(posts map[string]*model.Post) []string {
|
|
namedPostIds := make([]string, 0, len(posts))
|
|
for postId := range posts {
|
|
namedPostIds = append(namedPostIds, namePost(postId))
|
|
}
|
|
sort.Strings(namedPostIds)
|
|
|
|
return namedPostIds
|
|
}
|
|
|
|
assertPostList := func(t *testing.T, expected, actual *model.PostList) {
|
|
t.Helper()
|
|
|
|
require.Equal(t, namePosts(expected.Order), namePosts(actual.Order), "unexpected post order")
|
|
require.Equal(t, namePostsMap(expected.Posts), namePostsMap(actual.Posts), "unexpected posts")
|
|
require.Equal(t, namePost(expected.NextPostId), namePost(actual.NextPostId), "unexpected next post id")
|
|
require.Equal(t, namePost(expected.PrevPostId), namePost(actual.PrevPostId), "unexpected prev post id")
|
|
}
|
|
|
|
// Setting limit_after to zero should fail with a 400 BadRequest.
|
|
posts, resp, err := client.GetPostsAroundLastUnread(context.Background(), userId, channelId, 20, 0, false)
|
|
require.Error(t, err)
|
|
CheckErrorID(t, err, "api.context.invalid_url_param.app_error")
|
|
require.Equal(t, http.StatusBadRequest, resp.StatusCode)
|
|
require.Nil(t, posts)
|
|
|
|
// All returned posts are all read by the user, since it's created by the user itself.
|
|
posts, _, err = client.GetPostsAroundLastUnread(context.Background(), userId, channelId, 20, 20, false)
|
|
require.NoError(t, err)
|
|
require.Len(t, posts.Order, 12, "Should return 12 posts only since there's no unread post")
|
|
|
|
// Set channel member's last viewed to 0.
|
|
// All returned posts are latest posts as if all previous posts were already read by the user.
|
|
channelMember, err := th.App.Srv().Store().Channel().GetMember(th.Context, channelId, userId)
|
|
require.NoError(t, err)
|
|
channelMember.LastViewedAt = 0
|
|
_, err = th.App.Srv().Store().Channel().UpdateMember(th.Context, channelMember)
|
|
require.NoError(t, err)
|
|
th.App.Srv().Store().Post().InvalidateLastPostTimeCache(channelId)
|
|
|
|
posts, _, err = client.GetPostsAroundLastUnread(context.Background(), userId, channelId, 20, 20, false)
|
|
require.NoError(t, err)
|
|
|
|
require.Len(t, posts.Order, 12, "Should return 12 posts only since there's no unread post")
|
|
|
|
// get the first system post generated before the created posts above
|
|
posts, _, err = client.GetPostsBefore(context.Background(), th.BasicChannel.Id, post1.Id, 0, 2, "", false, false)
|
|
require.NoError(t, err)
|
|
systemPost0 := posts.Posts[posts.Order[0]]
|
|
postIdNames[systemPost0.Id] = "system post 0"
|
|
systemPost1 := posts.Posts[posts.Order[1]]
|
|
postIdNames[systemPost1.Id] = "system post 1"
|
|
|
|
// Set channel member's last viewed before post1.
|
|
channelMember, err = th.App.Srv().Store().Channel().GetMember(th.Context, channelId, userId)
|
|
require.NoError(t, err)
|
|
channelMember.LastViewedAt = post1.CreateAt - 1
|
|
_, err = th.App.Srv().Store().Channel().UpdateMember(th.Context, channelMember)
|
|
require.NoError(t, err)
|
|
th.App.Srv().Store().Post().InvalidateLastPostTimeCache(channelId)
|
|
|
|
posts, _, err = client.GetPostsAroundLastUnread(context.Background(), userId, channelId, 3, 3, false)
|
|
require.NoError(t, err)
|
|
|
|
assertPostList(t, &model.PostList{
|
|
Order: []string{post3.Id, post2.Id, post1.Id, systemPost0.Id, systemPost1.Id},
|
|
Posts: map[string]*model.Post{
|
|
systemPost0.Id: systemPost0,
|
|
systemPost1.Id: systemPost1,
|
|
post1.Id: post1,
|
|
post2.Id: post2,
|
|
post3.Id: post3,
|
|
},
|
|
NextPostId: post4.Id,
|
|
PrevPostId: "",
|
|
}, posts)
|
|
|
|
// Set channel member's last viewed before post6.
|
|
channelMember, err = th.App.Srv().Store().Channel().GetMember(th.Context, channelId, userId)
|
|
require.NoError(t, err)
|
|
channelMember.LastViewedAt = post6.CreateAt - 1
|
|
_, err = th.App.Srv().Store().Channel().UpdateMember(th.Context, channelMember)
|
|
require.NoError(t, err)
|
|
th.App.Srv().Store().Post().InvalidateLastPostTimeCache(channelId)
|
|
|
|
posts, _, err = client.GetPostsAroundLastUnread(context.Background(), userId, channelId, 3, 3, false)
|
|
require.NoError(t, err)
|
|
|
|
assertPostList(t, &model.PostList{
|
|
Order: []string{post8.Id, post7.Id, post6.Id, post5.Id, post4.Id, post3.Id},
|
|
Posts: map[string]*model.Post{
|
|
post3.Id: post3,
|
|
post4.Id: post4,
|
|
post5.Id: post5,
|
|
post6.Id: post6,
|
|
post7.Id: post7,
|
|
post8.Id: post8,
|
|
post9.Id: post9,
|
|
post10.Id: post10,
|
|
},
|
|
NextPostId: post9.Id,
|
|
PrevPostId: post2.Id,
|
|
}, posts)
|
|
|
|
// Set channel member's last viewed before post10.
|
|
channelMember, err = th.App.Srv().Store().Channel().GetMember(th.Context, channelId, userId)
|
|
require.NoError(t, err)
|
|
channelMember.LastViewedAt = post10.CreateAt - 1
|
|
_, err = th.App.Srv().Store().Channel().UpdateMember(th.Context, channelMember)
|
|
require.NoError(t, err)
|
|
th.App.Srv().Store().Post().InvalidateLastPostTimeCache(channelId)
|
|
|
|
posts, _, err = client.GetPostsAroundLastUnread(context.Background(), userId, channelId, 3, 3, false)
|
|
require.NoError(t, err)
|
|
|
|
assertPostList(t, &model.PostList{
|
|
Order: []string{post10.Id, post9.Id, post8.Id, post7.Id},
|
|
Posts: map[string]*model.Post{
|
|
post4.Id: post4,
|
|
post6.Id: post6,
|
|
post7.Id: post7,
|
|
post8.Id: post8,
|
|
post9.Id: post9,
|
|
post10.Id: post10,
|
|
},
|
|
NextPostId: "",
|
|
PrevPostId: post6.Id,
|
|
}, posts)
|
|
|
|
// Set channel member's last viewed equal to post10.
|
|
channelMember, err = th.App.Srv().Store().Channel().GetMember(th.Context, channelId, userId)
|
|
require.NoError(t, err)
|
|
channelMember.LastViewedAt = post10.CreateAt
|
|
_, err = th.App.Srv().Store().Channel().UpdateMember(th.Context, channelMember)
|
|
require.NoError(t, err)
|
|
th.App.Srv().Store().Post().InvalidateLastPostTimeCache(channelId)
|
|
|
|
posts, _, err = client.GetPostsAroundLastUnread(context.Background(), userId, channelId, 3, 3, false)
|
|
require.NoError(t, err)
|
|
|
|
assertPostList(t, &model.PostList{
|
|
Order: []string{post10.Id, post9.Id, post8.Id},
|
|
Posts: map[string]*model.Post{
|
|
post4.Id: post4,
|
|
post6.Id: post6,
|
|
post7.Id: post7,
|
|
post8.Id: post8,
|
|
post9.Id: post9,
|
|
post10.Id: post10,
|
|
},
|
|
NextPostId: "",
|
|
PrevPostId: post7.Id,
|
|
}, posts)
|
|
|
|
// Set channel member's last viewed to just before a new reply to a previous thread, not
|
|
// otherwise in the requested window.
|
|
post11 := th.CreatePost(t)
|
|
post12, _, err := client.CreatePost(context.Background(), &model.Post{
|
|
ChannelId: channelId,
|
|
Message: model.NewId(),
|
|
RootId: post4.Id,
|
|
})
|
|
require.NoError(t, err)
|
|
post13 := th.CreatePost(t)
|
|
|
|
postIdNames[post11.Id] = "post11"
|
|
postIdNames[post12.Id] = "post12 (reply to post4)"
|
|
postIdNames[post13.Id] = "post13"
|
|
|
|
channelMember, err = th.App.Srv().Store().Channel().GetMember(th.Context, channelId, userId)
|
|
require.NoError(t, err)
|
|
channelMember.LastViewedAt = post12.CreateAt - 1
|
|
_, err = th.App.Srv().Store().Channel().UpdateMember(th.Context, channelMember)
|
|
require.NoError(t, err)
|
|
th.App.Srv().Store().Post().InvalidateLastPostTimeCache(channelId)
|
|
|
|
posts, _, err = client.GetPostsAroundLastUnread(context.Background(), userId, channelId, 1, 2, false)
|
|
require.NoError(t, err)
|
|
|
|
assertPostList(t, &model.PostList{
|
|
Order: []string{post13.Id, post12.Id, post11.Id},
|
|
Posts: map[string]*model.Post{
|
|
post4.Id: post4,
|
|
post6.Id: post6,
|
|
post7.Id: post7,
|
|
post8.Id: post8,
|
|
post9.Id: post9,
|
|
post10.Id: post10,
|
|
post11.Id: post11,
|
|
post12.Id: post12,
|
|
post13.Id: post13,
|
|
},
|
|
NextPostId: "",
|
|
PrevPostId: post10.Id,
|
|
}, posts)
|
|
}
|
|
|
|
func TestGetPost(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
|
|
th := Setup(t).InitBasic(t)
|
|
// TODO: migrate this entirely to the subtest's client
|
|
// once the other methods are migrated too.
|
|
client := th.Client
|
|
|
|
var privatePost *model.Post
|
|
th.TestForAllClients(t, func(t *testing.T, c *model.Client4) {
|
|
t.Helper()
|
|
|
|
post, resp, err := c.GetPost(context.Background(), th.BasicPost.Id, "")
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, th.BasicPost.Id, post.Id, "post ids don't match")
|
|
|
|
post, resp, err = c.GetPost(context.Background(), th.BasicPost.Id, resp.Etag)
|
|
require.NoError(t, err)
|
|
CheckEtag(t, post, resp)
|
|
|
|
_, resp, err = c.GetPost(context.Background(), "", "")
|
|
require.Error(t, err)
|
|
CheckNotFoundStatus(t, resp)
|
|
|
|
_, resp, err = c.GetPost(context.Background(), "junk", "")
|
|
require.Error(t, err)
|
|
CheckBadRequestStatus(t, resp)
|
|
|
|
_, resp, err = c.GetPost(context.Background(), model.NewId(), "")
|
|
require.Error(t, err)
|
|
CheckNotFoundStatus(t, resp)
|
|
|
|
_, err = client.RemoveUserFromChannel(context.Background(), th.BasicChannel.Id, th.BasicUser.Id)
|
|
require.NoError(t, err)
|
|
t.Cleanup(func() {
|
|
// Add the user back to the channel
|
|
_, _, err = client.AddChannelMember(context.Background(), th.BasicChannel.Id, th.BasicUser.Id)
|
|
require.NoError(t, err)
|
|
})
|
|
|
|
// Channel is public, should be able to read post
|
|
_, _, err = c.GetPost(context.Background(), th.BasicPost.Id, "")
|
|
require.NoError(t, err)
|
|
|
|
privatePost = th.CreatePostWithClient(t, client, th.BasicPrivateChannel)
|
|
|
|
_, _, err = c.GetPost(context.Background(), privatePost.Id, "")
|
|
require.NoError(t, err)
|
|
})
|
|
|
|
_, err := client.RemoveUserFromChannel(context.Background(), th.BasicPrivateChannel.Id, th.BasicUser.Id)
|
|
require.NoError(t, err)
|
|
|
|
// Channel is private, should not be able to read post
|
|
_, resp, err := client.GetPost(context.Background(), privatePost.Id, "")
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
|
|
// But local client should.
|
|
_, _, err = th.LocalClient.GetPost(context.Background(), privatePost.Id, "")
|
|
require.NoError(t, err)
|
|
|
|
// Delete post
|
|
_, err = th.SystemAdminClient.DeletePost(context.Background(), th.BasicPost.Id)
|
|
require.NoError(t, err)
|
|
|
|
// Normal client should get 404 when trying to access deleted post normally
|
|
_, resp, err = client.GetPost(context.Background(), th.BasicPost.Id, "")
|
|
require.Error(t, err)
|
|
CheckNotFoundStatus(t, resp)
|
|
|
|
// Normal client should get unauthorized when trying to access deleted post
|
|
_, resp, err = client.GetPostIncludeDeleted(context.Background(), th.BasicPost.Id, "")
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
|
|
// System client should get 404 when trying to access deleted post normally
|
|
_, resp, err = th.SystemAdminClient.GetPost(context.Background(), th.BasicPost.Id, "")
|
|
require.Error(t, err)
|
|
CheckNotFoundStatus(t, resp)
|
|
|
|
// System client should be able to access deleted post with include_deleted param
|
|
post, _, err := th.SystemAdminClient.GetPostIncludeDeleted(context.Background(), th.BasicPost.Id, "")
|
|
require.NoError(t, err)
|
|
require.Equal(t, th.BasicPost.Id, post.Id)
|
|
|
|
_, err = client.Logout(context.Background())
|
|
require.NoError(t, err)
|
|
|
|
// Normal client should get unauthorized, but local client should get 404.
|
|
_, resp, err = client.GetPost(context.Background(), model.NewId(), "")
|
|
require.Error(t, err)
|
|
CheckUnauthorizedStatus(t, resp)
|
|
|
|
_, resp, err = th.LocalClient.GetPost(context.Background(), model.NewId(), "")
|
|
require.Error(t, err)
|
|
CheckNotFoundStatus(t, resp)
|
|
}
|
|
|
|
func TestDeletePost(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
|
|
th := Setup(t).InitBasic(t)
|
|
client := th.Client
|
|
|
|
t.Run("Post not found", func(t *testing.T) {
|
|
resp, err := client.DeletePost(context.Background(), "")
|
|
require.Error(t, err)
|
|
CheckNotFoundStatus(t, resp)
|
|
})
|
|
|
|
t.Run("Post doesn't exist", func(t *testing.T) {
|
|
resp, err := client.DeletePost(context.Background(), "junk")
|
|
require.Error(t, err)
|
|
CheckBadRequestStatus(t, resp)
|
|
})
|
|
|
|
t.Run("No permissions to delete a post", func(t *testing.T) {
|
|
resp, err := client.DeletePost(context.Background(), th.BasicPost.Id)
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
})
|
|
|
|
t.Run("Try to delete a post across different user roles", func(t *testing.T) {
|
|
_, _, err := client.Login(context.Background(), th.TeamAdminUser.Email, th.TeamAdminUser.Password)
|
|
require.NoError(t, err)
|
|
_, cErr := client.DeletePost(context.Background(), th.BasicPost.Id)
|
|
require.NoError(t, cErr)
|
|
|
|
post := th.CreatePost(t)
|
|
post2 := th.CreatePost(t)
|
|
user := th.CreateUser(t)
|
|
|
|
_, err = client.Logout(context.Background())
|
|
require.NoError(t, err)
|
|
_, _, err = client.Login(context.Background(), user.Email, user.Password)
|
|
require.NoError(t, err)
|
|
|
|
resp, err := client.DeletePost(context.Background(), post.Id)
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
|
|
_, err = client.Logout(context.Background())
|
|
require.NoError(t, err)
|
|
resp, err = client.DeletePost(context.Background(), model.NewId())
|
|
require.Error(t, err)
|
|
CheckUnauthorizedStatus(t, resp)
|
|
|
|
_, err = th.SystemAdminClient.DeletePost(context.Background(), post.Id)
|
|
require.NoError(t, err)
|
|
|
|
_, err = th.LocalClient.DeletePost(context.Background(), post2.Id)
|
|
require.NoError(t, err)
|
|
})
|
|
}
|
|
|
|
func TestPermanentDeletePost(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
|
|
th := Setup(t).InitBasic(t)
|
|
client := th.Client
|
|
|
|
enableAPIPostDeletion := *th.App.Config().ServiceSettings.EnableAPIPostDeletion
|
|
defer func() {
|
|
th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableAPIPostDeletion = &enableAPIPostDeletion })
|
|
}()
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableAPIPostDeletion = false })
|
|
|
|
t.Run("Post not found", func(t *testing.T) {
|
|
resp, err := client.PermanentDeletePost(context.Background(), "")
|
|
require.Error(t, err)
|
|
CheckNotFoundStatus(t, resp)
|
|
})
|
|
|
|
t.Run("Post doesn't exist", func(t *testing.T) {
|
|
resp, err := client.PermanentDeletePost(context.Background(), "junk")
|
|
require.Error(t, err)
|
|
CheckBadRequestStatus(t, resp)
|
|
})
|
|
|
|
t.Run("Permanent deletion not available through API if EnableAPIPostDeletion is not set", func(t *testing.T) {
|
|
resp, err := th.SystemAdminClient.PermanentDeletePost(context.Background(), th.BasicPost.Id)
|
|
require.Error(t, err)
|
|
CheckNotImplementedStatus(t, resp)
|
|
})
|
|
|
|
t.Run("Permanent deletion available through local mode even if EnableAPIPostDeletion is not set", func(t *testing.T) {
|
|
post := th.CreatePost(t)
|
|
_, err := th.LocalClient.PermanentDeletePost(context.Background(), post.Id)
|
|
require.NoError(t, err)
|
|
})
|
|
|
|
t.Run("No permissions to permanently delete a post", func(t *testing.T) {
|
|
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableAPIPostDeletion = true })
|
|
resp, err := client.PermanentDeletePost(context.Background(), th.BasicPost.Id)
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
})
|
|
|
|
t.Run("Try to permanently delete a post across different user roles", func(t *testing.T) {
|
|
_, _, err := client.Login(context.Background(), th.TeamAdminUser.Email, th.TeamAdminUser.Password)
|
|
require.NoError(t, err)
|
|
resp, err := client.PermanentDeletePost(context.Background(), th.BasicPost.Id)
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
|
|
post := th.CreatePost(t)
|
|
post2 := th.CreatePost(t)
|
|
user := th.CreateUser(t)
|
|
|
|
_, err = client.Logout(context.Background())
|
|
require.NoError(t, err)
|
|
_, _, err = client.Login(context.Background(), user.Email, user.Password)
|
|
require.NoError(t, err)
|
|
|
|
resp, err = client.PermanentDeletePost(context.Background(), post.Id)
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
|
|
_, err = client.Logout(context.Background())
|
|
require.NoError(t, err)
|
|
resp, err = client.PermanentDeletePost(context.Background(), post.Id)
|
|
require.Error(t, err)
|
|
CheckUnauthorizedStatus(t, resp)
|
|
|
|
_, err = th.SystemAdminClient.PermanentDeletePost(context.Background(), post.Id)
|
|
require.NoError(t, err)
|
|
|
|
_, err = th.LocalClient.PermanentDeletePost(context.Background(), post2.Id)
|
|
require.NoError(t, err)
|
|
})
|
|
}
|
|
|
|
func TestWebHubMembership(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
|
|
t.Run("WithChannelIteration", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
|
|
th := SetupConfig(t, func(cfg *model.Config) {
|
|
*cfg.ServiceSettings.EnableWebHubChannelIteration = true
|
|
}).InitBasic(t)
|
|
|
|
_testWebHubMembership(th, t)
|
|
})
|
|
|
|
t.Run("WithoutChannelIteration", func(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
_testWebHubMembership(th, t)
|
|
})
|
|
}
|
|
|
|
func _testWebHubMembership(th *TestHelper, t *testing.T) {
|
|
t.Helper()
|
|
|
|
u1 := th.CreateUser(t)
|
|
th.LinkUserToTeam(t, u1, th.BasicTeam)
|
|
th.AddUserToChannel(t, u1, th.BasicChannel)
|
|
|
|
ch2 := th.CreatePrivateChannel(t)
|
|
u2 := th.CreateUser(t)
|
|
th.LinkUserToTeam(t, u2, th.BasicTeam)
|
|
th.AddUserToChannel(t, u2, ch2)
|
|
|
|
quitChan := make(chan struct{})
|
|
var wg sync.WaitGroup
|
|
|
|
wg.Add(3)
|
|
for _, obj := range []struct {
|
|
testName string
|
|
user *model.User
|
|
}{
|
|
{
|
|
testName: "basicUser",
|
|
user: th.BasicUser,
|
|
},
|
|
{
|
|
testName: "u1",
|
|
user: u1,
|
|
},
|
|
{
|
|
testName: "u2",
|
|
user: u2,
|
|
},
|
|
} {
|
|
cli := th.CreateClient()
|
|
_, _, err := cli.Login(context.Background(), obj.user.Username, obj.user.Password)
|
|
require.NoError(t, err)
|
|
|
|
wsClient := th.CreateConnectedWebSocketClientWithClient(t, cli)
|
|
|
|
go func(testName string) {
|
|
defer wg.Done()
|
|
var cnt int
|
|
for {
|
|
select {
|
|
case event := <-wsClient.EventChannel:
|
|
if event.EventType() == model.WebsocketEventPosted {
|
|
var post model.Post
|
|
err := json.Unmarshal([]byte(event.GetData()["post"].(string)), &post)
|
|
require.NoError(t, err)
|
|
|
|
cnt++
|
|
// Cases:
|
|
// Post to basicChannel should go to u1 and basicUser.
|
|
// Add u1 to ch2.
|
|
// Post to ch2 should go to u1, u2 and basicUser.
|
|
// Remove u1 from ch2.
|
|
// Post to ch2 should go to u2 and basicUser.
|
|
switch testName {
|
|
case "basicUser":
|
|
if cnt == 1 {
|
|
assert.Equal(t, th.BasicChannel.Id, post.ChannelId)
|
|
} else if cnt == 2 {
|
|
assert.Equal(t, ch2.Id, post.ChannelId)
|
|
} else if cnt == 3 {
|
|
// After removing, there will be a "removed from channel post"
|
|
assert.Equal(t, ch2.Id, post.ChannelId)
|
|
} else if cnt == 4 {
|
|
assert.Equal(t, ch2.Id, post.ChannelId)
|
|
} else {
|
|
assert.Fail(t, "more than 4 messages arrived for basicUser")
|
|
}
|
|
case "u1":
|
|
// First msg should be from basicChannel
|
|
if cnt == 1 {
|
|
assert.Equal(t, th.BasicChannel.Id, post.ChannelId)
|
|
} else if cnt == 2 {
|
|
// second should be from ch2
|
|
assert.Equal(t, ch2.Id, post.ChannelId)
|
|
} else {
|
|
assert.Fail(t, "more than 2 messages arrived for u1")
|
|
}
|
|
case "u2":
|
|
if cnt == 1 {
|
|
assert.Equal(t, ch2.Id, post.ChannelId)
|
|
} else if cnt == 2 {
|
|
// After removing, there will be a "removed from channel post"
|
|
assert.Equal(t, ch2.Id, post.ChannelId)
|
|
} else if cnt == 3 {
|
|
assert.Equal(t, ch2.Id, post.ChannelId)
|
|
} else {
|
|
assert.Fail(t, "more than 3 messages arrived for u2")
|
|
}
|
|
}
|
|
}
|
|
case <-quitChan:
|
|
return
|
|
}
|
|
}
|
|
}(obj.testName)
|
|
}
|
|
|
|
// Will send to basic channel
|
|
th.CreatePost(t)
|
|
// Add u1 to ch2
|
|
th.AddUserToChannel(t, u1, ch2)
|
|
// Send post to ch2
|
|
th.CreatePostWithClient(t, th.Client, ch2)
|
|
// Remove u1 from ch2
|
|
th.RemoveUserFromChannel(t, u1, ch2)
|
|
// Send post to ch2
|
|
th.CreatePostWithClient(t, th.Client, ch2)
|
|
|
|
// It is possible to create a signalling mechanism from the goroutines
|
|
// after all events are received, but we also want to verify that no additional
|
|
// events are being sent.
|
|
time.Sleep(2 * time.Second)
|
|
close(quitChan)
|
|
wg.Wait()
|
|
}
|
|
|
|
func TestWebHubCloseConnOnDBFail(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
|
|
th := SetupConfig(t, func(cfg *model.Config) {
|
|
*cfg.ServiceSettings.EnableWebHubChannelIteration = true
|
|
}).InitBasic(t)
|
|
defer func() {
|
|
_, err := th.Server.Store().GetInternalMasterDB().Exec(`ALTER TABLE dummy RENAME to ChannelMembers`)
|
|
require.NoError(t, err)
|
|
// Asserting that the error message is present in the log
|
|
testlib.AssertLog(t, th.LogBuffer, mlog.LvlError.Name, "Error while registering to hub")
|
|
}()
|
|
|
|
cli := th.CreateClient()
|
|
_, _, err := cli.Login(context.Background(), th.BasicUser.Username, th.BasicUser.Password)
|
|
require.NoError(t, err)
|
|
|
|
_, err = th.Server.Store().GetInternalMasterDB().Exec(`ALTER TABLE ChannelMembers RENAME to dummy`)
|
|
require.NoError(t, err)
|
|
|
|
wsClient, err := th.CreateWebSocketClientWithClient(cli)
|
|
require.NoError(t, err)
|
|
|
|
wsClient.Listen()
|
|
select {
|
|
case <-wsClient.EventChannel: // event channel should be closed on failure
|
|
case <-time.After(5 * time.Second):
|
|
require.FailNow(t, "timed out waiting for event")
|
|
}
|
|
wsClient.Close()
|
|
|
|
require.NoError(t, th.TestLogger.Flush())
|
|
}
|
|
|
|
func TestDeletePostEvent(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
WebSocketClient := th.CreateConnectedWebSocketClient(t)
|
|
|
|
_, err := th.SystemAdminClient.DeletePost(context.Background(), th.BasicPost.Id)
|
|
require.NoError(t, err)
|
|
|
|
var received, exit bool
|
|
for !received && !exit {
|
|
select {
|
|
case event := <-WebSocketClient.EventChannel:
|
|
if event.EventType() == model.WebsocketEventPostDeleted {
|
|
var post model.Post
|
|
err := json.Unmarshal([]byte(event.GetData()["post"].(string)), &post)
|
|
require.NoError(t, err)
|
|
received = true
|
|
}
|
|
case <-time.After(5 * time.Second):
|
|
exit = true
|
|
}
|
|
}
|
|
|
|
require.True(t, received)
|
|
}
|
|
|
|
func TestDeletePostMessage(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
th.LinkUserToTeam(t, th.SystemAdminUser, th.BasicTeam)
|
|
_, appErr := th.App.AddUserToChannel(th.Context, th.SystemAdminUser, th.BasicChannel, false)
|
|
require.Nil(t, appErr)
|
|
|
|
testCases := []struct {
|
|
description string
|
|
client *model.Client4
|
|
delete_by any
|
|
}{
|
|
{"Do not send delete_by to regular user", th.Client, nil},
|
|
{"Send delete_by to system admin user", th.SystemAdminClient, th.SystemAdminUser.Id},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.description, func(t *testing.T) {
|
|
wsClient := th.CreateConnectedWebSocketClientWithClient(t, tc.client)
|
|
|
|
post := th.CreatePost(t)
|
|
|
|
_, err := th.SystemAdminClient.DeletePost(context.Background(), post.Id)
|
|
require.NoError(t, err)
|
|
|
|
timeout := time.After(5 * time.Second)
|
|
|
|
for {
|
|
select {
|
|
case ev := <-wsClient.EventChannel:
|
|
if ev.EventType() == model.WebsocketEventPostDeleted {
|
|
assert.Equal(t, tc.delete_by, ev.GetData()["delete_by"])
|
|
return
|
|
}
|
|
case <-timeout:
|
|
// We just skip the test instead of failing because waiting for more than 5 seconds
|
|
// to get a response does not make sense, and it will unnecessarily slow down
|
|
// the tests further in an already congested CI environment.
|
|
t.Skip("timed out waiting for event")
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetPostThread(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
|
|
th := Setup(t).InitBasic(t)
|
|
client := th.Client
|
|
|
|
post := &model.Post{ChannelId: th.BasicChannel.Id, Message: "zz" + model.NewId() + "a", RootId: th.BasicPost.Id}
|
|
post, _, _ = client.CreatePost(context.Background(), post)
|
|
|
|
list, resp, err := client.GetPostThread(context.Background(), th.BasicPost.Id, "", false)
|
|
require.NoError(t, err)
|
|
|
|
var list2 *model.PostList
|
|
list2, resp, _ = client.GetPostThread(context.Background(), th.BasicPost.Id, resp.Etag, false)
|
|
CheckEtag(t, list2, resp)
|
|
require.Equal(t, th.BasicPost.Id, list.Order[0], "wrong order")
|
|
|
|
_, ok := list.Posts[th.BasicPost.Id]
|
|
require.True(t, ok, "should have had post")
|
|
|
|
_, ok = list.Posts[post.Id]
|
|
require.True(t, ok, "should have had post")
|
|
|
|
_, resp, err = client.GetPostThread(context.Background(), "junk", "", false)
|
|
require.Error(t, err)
|
|
CheckBadRequestStatus(t, resp)
|
|
|
|
_, resp, err = client.GetPostThread(context.Background(), model.NewId(), "", false)
|
|
require.Error(t, err)
|
|
CheckNotFoundStatus(t, resp)
|
|
|
|
_, err = client.RemoveUserFromChannel(context.Background(), th.BasicChannel.Id, th.BasicUser.Id)
|
|
require.NoError(t, err)
|
|
|
|
// Channel is public, should be able to read post
|
|
_, _, err = client.GetPostThread(context.Background(), th.BasicPost.Id, "", false)
|
|
require.NoError(t, err)
|
|
|
|
privatePost := th.CreatePostWithClient(t, client, th.BasicPrivateChannel)
|
|
|
|
_, _, err = client.GetPostThread(context.Background(), privatePost.Id, "", false)
|
|
require.NoError(t, err)
|
|
|
|
_, err = client.RemoveUserFromChannel(context.Background(), th.BasicPrivateChannel.Id, th.BasicUser.Id)
|
|
require.NoError(t, err)
|
|
|
|
// Channel is private, should not be able to read post
|
|
_, resp, err = client.GetPostThread(context.Background(), privatePost.Id, "", false)
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
|
|
// Test the new query parameters - updatesOnly, fromUpdateAt
|
|
// Sending some bad params
|
|
_, resp, err = client.GetPostThreadWithOpts(context.Background(), th.BasicPost.Id, "", model.GetPostsOptions{
|
|
UpdatesOnly: true, // updatesOnly is true but fromUpdateAt is not set
|
|
})
|
|
require.Error(t, err)
|
|
CheckBadRequestStatus(t, resp)
|
|
|
|
// Test error when both fromUpdateAt and fromCreateAt are set
|
|
_, resp, err = client.GetPostThreadWithOpts(context.Background(), th.BasicPost.Id, "", model.GetPostsOptions{
|
|
FromUpdateAt: 12345,
|
|
FromCreateAt: 12345,
|
|
})
|
|
require.Error(t, err)
|
|
CheckBadRequestStatus(t, resp)
|
|
|
|
// Test error when updatesOnly is used with direction="up"
|
|
_, resp, err = client.GetPostThreadWithOpts(context.Background(), th.BasicPost.Id, "", model.GetPostsOptions{
|
|
UpdatesOnly: true,
|
|
FromUpdateAt: 12345,
|
|
Direction: "up",
|
|
})
|
|
require.Error(t, err)
|
|
CheckBadRequestStatus(t, resp)
|
|
|
|
// Test valid parameters
|
|
// This should work with proper parameters
|
|
_, resp, err = client.GetPostThreadWithOpts(context.Background(), th.BasicPost.Id, "", model.GetPostsOptions{
|
|
UpdatesOnly: true,
|
|
FromUpdateAt: 12345,
|
|
Direction: "down",
|
|
})
|
|
require.NoError(t, err)
|
|
CheckOKStatus(t, resp)
|
|
|
|
list, resp, err = client.GetPostThreadWithOpts(context.Background(), th.BasicPost.Id, "", model.GetPostsOptions{
|
|
UpdatesOnly: true,
|
|
Direction: "down",
|
|
FromUpdateAt: post.UpdateAt,
|
|
})
|
|
require.NoError(t, err)
|
|
CheckOKStatus(t, resp)
|
|
assert.Len(t, list.Order, 1)
|
|
assert.Len(t, list.Posts, 1)
|
|
require.Equal(t, th.BasicPost.Id, list.Order[0], "wrong order")
|
|
|
|
// Test with just fromUpdateAt parameter
|
|
_, resp, err = client.GetPostThreadWithOpts(context.Background(), th.BasicPost.Id, "", model.GetPostsOptions{
|
|
FromUpdateAt: 12345,
|
|
})
|
|
require.NoError(t, err)
|
|
CheckOKStatus(t, resp)
|
|
|
|
// Sending other bad params unrelated to the new changes
|
|
_, resp, err = client.GetPostThreadWithOpts(context.Background(), th.BasicPost.Id, "", model.GetPostsOptions{
|
|
CollapsedThreads: true,
|
|
FromPost: "something",
|
|
PerPage: 10,
|
|
})
|
|
require.Error(t, err)
|
|
CheckBadRequestStatus(t, resp)
|
|
|
|
_, resp, err = client.GetPostThreadWithOpts(context.Background(), th.BasicPost.Id, "", model.GetPostsOptions{
|
|
CollapsedThreads: true,
|
|
Direction: "sideways",
|
|
})
|
|
require.Error(t, err)
|
|
CheckBadRequestStatus(t, resp)
|
|
|
|
_, err = client.Logout(context.Background())
|
|
require.NoError(t, err)
|
|
_, resp, err = client.GetPostThread(context.Background(), model.NewId(), "", false)
|
|
require.Error(t, err)
|
|
CheckUnauthorizedStatus(t, resp)
|
|
|
|
_, _, err = th.SystemAdminClient.GetPostThread(context.Background(), th.BasicPost.Id, "", false)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestSearchPosts(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
th.LoginBasic(t)
|
|
client := th.Client
|
|
|
|
message := "search for post1"
|
|
_ = th.CreateMessagePost(t, message)
|
|
|
|
message = "search for post2"
|
|
post2 := th.CreateMessagePost(t, message)
|
|
|
|
message = "#hashtag search for post3"
|
|
post3 := th.CreateMessagePost(t, message)
|
|
|
|
message = "hashtag for post4"
|
|
_ = th.CreateMessagePost(t, message)
|
|
|
|
archivedChannel := th.CreatePublicChannel(t)
|
|
_ = th.CreateMessagePostWithClient(t, th.Client, archivedChannel, "#hashtag for post3")
|
|
_, err := th.Client.DeleteChannel(context.Background(), archivedChannel.Id)
|
|
require.NoError(t, err)
|
|
|
|
otherTeam := th.CreateTeam(t)
|
|
channelInOtherTeam := th.CreateChannelWithClientAndTeam(t, th.Client, model.ChannelTypeOpen, otherTeam.Id)
|
|
_ = th.AddUserToChannel(t, th.BasicUser, channelInOtherTeam)
|
|
_ = th.CreateMessagePostWithClient(t, th.Client, channelInOtherTeam, "search for post 5")
|
|
|
|
terms := "search"
|
|
isOrSearch := false
|
|
timezoneOffset := 5
|
|
searchParams := model.SearchParameter{
|
|
Terms: &terms,
|
|
IsOrSearch: &isOrSearch,
|
|
TimeZoneOffset: &timezoneOffset,
|
|
}
|
|
allTeamsPosts, _, err := client.SearchPostsWithParams(context.Background(), "", &searchParams)
|
|
require.NoError(t, err)
|
|
require.Len(t, allTeamsPosts.Order, 4, "wrong search along multiple teams")
|
|
|
|
terms = "search"
|
|
isOrSearch = false
|
|
timezoneOffset = 5
|
|
searchParams = model.SearchParameter{
|
|
Terms: &terms,
|
|
IsOrSearch: &isOrSearch,
|
|
TimeZoneOffset: &timezoneOffset,
|
|
}
|
|
posts, _, err := client.SearchPostsWithParams(context.Background(), th.BasicTeam.Id, &searchParams)
|
|
require.NoError(t, err)
|
|
require.Len(t, posts.Order, 3, "wrong search")
|
|
|
|
terms = "search"
|
|
page := 0
|
|
perPage := 2
|
|
searchParams = model.SearchParameter{
|
|
Terms: &terms,
|
|
IsOrSearch: &isOrSearch,
|
|
TimeZoneOffset: &timezoneOffset,
|
|
Page: &page,
|
|
PerPage: &perPage,
|
|
}
|
|
posts2, _, err := client.SearchPostsWithParams(context.Background(), th.BasicTeam.Id, &searchParams)
|
|
require.NoError(t, err)
|
|
// We don't support paging for DB search yet, modify this when we do.
|
|
require.Len(t, posts2.Order, 3, "Wrong number of posts")
|
|
assert.Equal(t, posts.Order[0], posts2.Order[0])
|
|
assert.Equal(t, posts.Order[1], posts2.Order[1])
|
|
|
|
page = 1
|
|
searchParams = model.SearchParameter{
|
|
Terms: &terms,
|
|
IsOrSearch: &isOrSearch,
|
|
TimeZoneOffset: &timezoneOffset,
|
|
Page: &page,
|
|
PerPage: &perPage,
|
|
}
|
|
posts2, _, err = client.SearchPostsWithParams(context.Background(), th.BasicTeam.Id, &searchParams)
|
|
require.NoError(t, err)
|
|
// We don't support paging for DB search yet, modify this when we do.
|
|
require.Empty(t, posts2.Order, "Wrong number of posts")
|
|
|
|
posts, _, err = client.SearchPosts(context.Background(), th.BasicTeam.Id, "search", false)
|
|
require.NoError(t, err)
|
|
require.Len(t, posts.Order, 3, "wrong search")
|
|
|
|
posts, _, err = client.SearchPosts(context.Background(), th.BasicTeam.Id, "post2", false)
|
|
require.NoError(t, err)
|
|
require.Len(t, posts.Order, 1, "wrong number of posts")
|
|
require.Equal(t, post2.Id, posts.Order[0], "wrong search")
|
|
|
|
posts, _, err = client.SearchPosts(context.Background(), th.BasicTeam.Id, "#hashtag", false)
|
|
require.NoError(t, err)
|
|
require.Len(t, posts.Order, 1, "wrong number of posts")
|
|
require.Equal(t, post3.Id, posts.Order[0], "wrong search")
|
|
|
|
terms = "#hashtag"
|
|
includeDeletedChannels := true
|
|
searchParams = model.SearchParameter{
|
|
Terms: &terms,
|
|
IsOrSearch: &isOrSearch,
|
|
TimeZoneOffset: &timezoneOffset,
|
|
IncludeDeletedChannels: &includeDeletedChannels,
|
|
}
|
|
posts, _, err = client.SearchPostsWithParams(context.Background(), th.BasicTeam.Id, &searchParams)
|
|
require.NoError(t, err)
|
|
require.Len(t, posts.Order, 2, "wrong search")
|
|
|
|
// Archived channels are always included now, so this should return the same result
|
|
posts, _, err = client.SearchPostsWithParams(context.Background(), th.BasicTeam.Id, &searchParams)
|
|
require.NoError(t, err)
|
|
require.Len(t, posts.Order, 2, "wrong search")
|
|
|
|
posts, _, _ = client.SearchPosts(context.Background(), th.BasicTeam.Id, "*", false)
|
|
require.Empty(t, posts.Order, "searching for just * shouldn't return any results")
|
|
|
|
posts, _, err = client.SearchPosts(context.Background(), th.BasicTeam.Id, "post1 post2", true)
|
|
require.NoError(t, err)
|
|
require.Len(t, posts.Order, 2, "wrong search results")
|
|
|
|
_, resp, err := client.SearchPosts(context.Background(), "junk", "#sgtitlereview", false)
|
|
require.Error(t, err)
|
|
CheckBadRequestStatus(t, resp)
|
|
|
|
_, resp, err = client.SearchPosts(context.Background(), model.NewId(), "#sgtitlereview", false)
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
|
|
_, resp, err = client.SearchPosts(context.Background(), th.BasicTeam.Id, "", false)
|
|
require.Error(t, err)
|
|
CheckBadRequestStatus(t, resp)
|
|
|
|
_, err = client.Logout(context.Background())
|
|
require.NoError(t, err)
|
|
_, resp, err = client.SearchPosts(context.Background(), th.BasicTeam.Id, "#sgtitlereview", false)
|
|
require.Error(t, err)
|
|
CheckUnauthorizedStatus(t, resp)
|
|
}
|
|
|
|
func TestSearchHashtagPosts(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
|
|
th := Setup(t).InitBasic(t)
|
|
th.LoginBasic(t)
|
|
client := th.Client
|
|
|
|
message := "#sgtitlereview with space"
|
|
assert.NotNil(t, th.CreateMessagePost(t, message))
|
|
|
|
message = "#sgtitlereview\n with return"
|
|
assert.NotNil(t, th.CreateMessagePost(t, message))
|
|
|
|
message = "no hashtag"
|
|
assert.NotNil(t, th.CreateMessagePost(t, message))
|
|
|
|
posts, _, err := client.SearchPosts(context.Background(), th.BasicTeam.Id, "#sgtitlereview", false)
|
|
require.NoError(t, err)
|
|
require.Len(t, posts.Order, 2, "wrong search results")
|
|
|
|
_, err = client.Logout(context.Background())
|
|
require.NoError(t, err)
|
|
_, resp, err := client.SearchPosts(context.Background(), th.BasicTeam.Id, "#sgtitlereview", false)
|
|
require.Error(t, err)
|
|
CheckUnauthorizedStatus(t, resp)
|
|
}
|
|
|
|
func TestSearchPostsInChannel(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
|
|
th := Setup(t).InitBasic(t)
|
|
th.LoginBasic(t)
|
|
client := th.Client
|
|
|
|
channel := th.CreatePublicChannel(t)
|
|
|
|
message := "sgtitlereview with space"
|
|
_ = th.CreateMessagePost(t, message)
|
|
|
|
message = "sgtitlereview\n with return"
|
|
_ = th.CreateMessagePostWithClient(t, client, th.BasicChannel2, message)
|
|
|
|
message = "other message with no return"
|
|
_ = th.CreateMessagePostWithClient(t, client, th.BasicChannel2, message)
|
|
|
|
message = "other message with no return"
|
|
_ = th.CreateMessagePostWithClient(t, client, channel, message)
|
|
|
|
posts, _, _ := client.SearchPosts(context.Background(), th.BasicTeam.Id, "channel:", false)
|
|
require.Empty(t, posts.Order, "wrong number of posts for search 'channel:'")
|
|
|
|
posts, _, _ = client.SearchPosts(context.Background(), th.BasicTeam.Id, "in:", false)
|
|
require.Empty(t, posts.Order, "wrong number of posts for search 'in:'")
|
|
|
|
posts, _, _ = client.SearchPosts(context.Background(), th.BasicTeam.Id, "channel:"+th.BasicChannel.Name, false)
|
|
require.Lenf(t, posts.Order, 2, "wrong number of posts returned for search 'channel:%v'", th.BasicChannel.Name)
|
|
|
|
posts, _, _ = client.SearchPosts(context.Background(), th.BasicTeam.Id, "in:"+th.BasicChannel2.Name, false)
|
|
require.Lenf(t, posts.Order, 2, "wrong number of posts returned for search 'in:%v'", th.BasicChannel2.Name)
|
|
|
|
posts, _, _ = client.SearchPosts(context.Background(), th.BasicTeam.Id, "channel:"+th.BasicChannel2.Name, false)
|
|
require.Lenf(t, posts.Order, 2, "wrong number of posts for search 'channel:%v'", th.BasicChannel2.Name)
|
|
|
|
posts, _, _ = client.SearchPosts(context.Background(), th.BasicTeam.Id, "ChAnNeL:"+th.BasicChannel2.Name, false)
|
|
require.Lenf(t, posts.Order, 2, "wrong number of posts for search 'ChAnNeL:%v'", th.BasicChannel2.Name)
|
|
|
|
posts, _, _ = client.SearchPosts(context.Background(), th.BasicTeam.Id, "sgtitlereview", false)
|
|
require.Lenf(t, posts.Order, 2, "wrong number of posts for search 'sgtitlereview'")
|
|
|
|
posts, _, _ = client.SearchPosts(context.Background(), th.BasicTeam.Id, "sgtitlereview channel:"+th.BasicChannel.Name, false)
|
|
require.Lenf(t, posts.Order, 1, "wrong number of posts for search 'sgtitlereview channel:%v'", th.BasicChannel.Name)
|
|
|
|
posts, _, _ = client.SearchPosts(context.Background(), th.BasicTeam.Id, "sgtitlereview in: "+th.BasicChannel2.Name, false)
|
|
require.Lenf(t, posts.Order, 1, "wrong number of posts for search 'sgtitlereview in: %v'", th.BasicChannel2.Name)
|
|
|
|
posts, _, _ = client.SearchPosts(context.Background(), th.BasicTeam.Id, "sgtitlereview channel: "+th.BasicChannel2.Name, false)
|
|
require.Lenf(t, posts.Order, 1, "wrong number of posts for search 'sgtitlereview channel: %v'", th.BasicChannel2.Name)
|
|
|
|
posts, _, _ = client.SearchPosts(context.Background(), th.BasicTeam.Id, "channel: "+th.BasicChannel2.Name+" channel: "+channel.Name, false)
|
|
require.Lenf(t, posts.Order, 3, "wrong number of posts for 'channel: %v channel: %v'", th.BasicChannel2.Name, channel.Name)
|
|
}
|
|
|
|
func TestSearchPostsFromUser(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
|
|
th := Setup(t).InitBasic(t)
|
|
client := th.Client
|
|
|
|
th.LoginTeamAdmin(t)
|
|
user := th.CreateUser(t)
|
|
th.LinkUserToTeam(t, user, th.BasicTeam)
|
|
_, appErr := th.App.AddUserToChannel(th.Context, user, th.BasicChannel, false)
|
|
require.Nil(t, appErr)
|
|
_, appErr = th.App.AddUserToChannel(th.Context, user, th.BasicChannel2, false)
|
|
require.Nil(t, appErr)
|
|
|
|
message := "sgtitlereview with space"
|
|
_ = th.CreateMessagePost(t, message)
|
|
|
|
_, err := client.Logout(context.Background())
|
|
require.NoError(t, err)
|
|
th.LoginBasic2(t)
|
|
|
|
message = "sgtitlereview\n with return"
|
|
_ = th.CreateMessagePostWithClient(t, client, th.BasicChannel2, message)
|
|
|
|
posts, _, err := client.SearchPosts(context.Background(), th.BasicTeam.Id, "from: "+th.TeamAdminUser.Username, false)
|
|
require.NoError(t, err)
|
|
require.Lenf(t, posts.Order, 2, "wrong number of posts for search 'from: %v'", th.TeamAdminUser.Username)
|
|
|
|
posts, _, err = client.SearchPosts(context.Background(), th.BasicTeam.Id, "from: "+th.BasicUser2.Username, false)
|
|
require.NoError(t, err)
|
|
require.Lenf(t, posts.Order, 1, "wrong number of posts for search 'from: %v", th.BasicUser2.Username)
|
|
|
|
posts, _, err = client.SearchPosts(context.Background(), th.BasicTeam.Id, "from: "+th.BasicUser2.Username+" sgtitlereview", false)
|
|
require.NoError(t, err)
|
|
require.Lenf(t, posts.Order, 1, "wrong number of posts for search 'from: %v'", th.BasicUser2.Username)
|
|
|
|
message = "hullo"
|
|
_ = th.CreateMessagePost(t, message)
|
|
|
|
posts, _, err = client.SearchPosts(context.Background(), th.BasicTeam.Id, "from: "+th.BasicUser2.Username+" in:"+th.BasicChannel.Name, false)
|
|
require.NoError(t, err)
|
|
require.Len(t, posts.Order, 1, "wrong number of posts for search 'from: %v in:", th.BasicUser2.Username, th.BasicChannel.Name)
|
|
|
|
_, _, err = client.Login(context.Background(), user.Email, user.Password)
|
|
require.NoError(t, err)
|
|
|
|
// wait for the join/leave messages to be created for user3 since they're done asynchronously
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
posts, _, err = client.SearchPosts(context.Background(), th.BasicTeam.Id, "from: "+th.BasicUser2.Username, false)
|
|
require.NoError(t, err)
|
|
require.Lenf(t, posts.Order, 2, "wrong number of posts for search 'from: %v'", th.BasicUser2.Username)
|
|
|
|
posts, _, err = client.SearchPosts(context.Background(), th.BasicTeam.Id, "from: "+th.BasicUser2.Username+" from: "+user.Username, false)
|
|
require.NoError(t, err)
|
|
require.Lenf(t, posts.Order, 2, "wrong number of posts for search 'from: %v from: %v'", th.BasicUser2.Username, user.Username)
|
|
|
|
posts, _, err = client.SearchPosts(context.Background(), th.BasicTeam.Id, "from: "+th.BasicUser2.Username+" from: "+user.Username+" in:"+th.BasicChannel2.Name, false)
|
|
require.NoError(t, err)
|
|
require.Len(t, posts.Order, 1, "wrong number of posts")
|
|
|
|
message = "coconut"
|
|
_ = th.CreateMessagePostWithClient(t, client, th.BasicChannel2, message)
|
|
|
|
posts, _, err = client.SearchPosts(context.Background(), th.BasicTeam.Id, "from: "+th.BasicUser2.Username+" from: "+user.Username+" in:"+th.BasicChannel2.Name+" coconut", false)
|
|
require.NoError(t, err)
|
|
require.Len(t, posts.Order, 1, "wrong number of posts")
|
|
}
|
|
|
|
func TestSearchPostsWithDateFlags(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
|
|
th := Setup(t).InitBasic(t)
|
|
th.LoginBasic(t)
|
|
client := th.Client
|
|
|
|
message := "sgtitlereview\n with return"
|
|
createDate := time.Date(2018, 8, 1, 5, 0, 0, 0, time.UTC)
|
|
_ = th.CreateMessagePostNoClient(t, th.BasicChannel, message, utils.MillisFromTime(createDate))
|
|
|
|
message = "other message with no return"
|
|
createDate = time.Date(2018, 8, 2, 5, 0, 0, 0, time.UTC)
|
|
_ = th.CreateMessagePostNoClient(t, th.BasicChannel, message, utils.MillisFromTime(createDate))
|
|
|
|
message = "other message with no return"
|
|
createDate = time.Date(2018, 8, 3, 5, 0, 0, 0, time.UTC)
|
|
_ = th.CreateMessagePostNoClient(t, th.BasicChannel, message, utils.MillisFromTime(createDate))
|
|
|
|
posts, _, _ := client.SearchPosts(context.Background(), th.BasicTeam.Id, "return", false)
|
|
require.Len(t, posts.Order, 3, "wrong number of posts")
|
|
|
|
posts, _, _ = client.SearchPosts(context.Background(), th.BasicTeam.Id, "on:", false)
|
|
require.Empty(t, posts.Order, "wrong number of posts")
|
|
|
|
posts, _, _ = client.SearchPosts(context.Background(), th.BasicTeam.Id, "after:", false)
|
|
require.Empty(t, posts.Order, "wrong number of posts")
|
|
|
|
posts, _, _ = client.SearchPosts(context.Background(), th.BasicTeam.Id, "before:", false)
|
|
require.Empty(t, posts.Order, "wrong number of posts")
|
|
|
|
posts, _, _ = client.SearchPosts(context.Background(), th.BasicTeam.Id, "on:2018-08-01", false)
|
|
require.Len(t, posts.Order, 1, "wrong number of posts")
|
|
|
|
posts, _, _ = client.SearchPosts(context.Background(), th.BasicTeam.Id, "after:2018-08-01", false)
|
|
resultCount := 0
|
|
for _, post := range posts.Posts {
|
|
if post.UserId == th.BasicUser.Id {
|
|
resultCount = resultCount + 1
|
|
}
|
|
}
|
|
require.Equal(t, 2, resultCount, "wrong number of posts")
|
|
|
|
posts, _, _ = client.SearchPosts(context.Background(), th.BasicTeam.Id, "before:2018-08-02", false)
|
|
require.Len(t, posts.Order, 1, "wrong number of posts")
|
|
|
|
posts, _, _ = client.SearchPosts(context.Background(), th.BasicTeam.Id, "before:2018-08-03 after:2018-08-02", false)
|
|
require.Empty(t, posts.Order, "wrong number of posts")
|
|
|
|
posts, _, _ = client.SearchPosts(context.Background(), th.BasicTeam.Id, "before:2018-08-03 after:2018-08-01", false)
|
|
require.Len(t, posts.Order, 1, "wrong number of posts")
|
|
}
|
|
|
|
func TestGetFileInfosForPost(t *testing.T) {
|
|
t.Skip("MM-46902")
|
|
th := Setup(t).InitBasic(t)
|
|
client := th.Client
|
|
|
|
fileIds := make([]string, 3)
|
|
data, err := testutils.ReadTestFile("test.png")
|
|
require.NoError(t, err)
|
|
for i := range 3 {
|
|
fileResp, _, _ := client.UploadFile(context.Background(), data, th.BasicChannel.Id, "test.png")
|
|
fileIds[i] = fileResp.FileInfos[0].Id
|
|
}
|
|
|
|
post := &model.Post{ChannelId: th.BasicChannel.Id, Message: "zz" + model.NewId() + "a", FileIds: fileIds}
|
|
post, _, _ = client.CreatePost(context.Background(), post)
|
|
|
|
infos, resp, err := client.GetFileInfosForPost(context.Background(), post.Id, "")
|
|
require.NoError(t, err)
|
|
|
|
require.Len(t, infos, 3, "missing file infos")
|
|
|
|
found := false
|
|
for _, info := range infos {
|
|
if info.Id == fileIds[0] {
|
|
found = true
|
|
}
|
|
}
|
|
|
|
require.True(t, found, "missing file info")
|
|
|
|
infos, resp, _ = client.GetFileInfosForPost(context.Background(), post.Id, resp.Etag)
|
|
CheckEtag(t, infos, resp)
|
|
|
|
infos, _, err = client.GetFileInfosForPost(context.Background(), th.BasicPost.Id, "")
|
|
require.NoError(t, err)
|
|
|
|
require.Empty(t, infos, "should have no file infos")
|
|
|
|
_, resp, err = client.GetFileInfosForPost(context.Background(), "junk", "")
|
|
require.Error(t, err)
|
|
CheckBadRequestStatus(t, resp)
|
|
|
|
_, resp, err = client.GetFileInfosForPost(context.Background(), model.NewId(), "")
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
|
|
// Delete post
|
|
_, err = th.SystemAdminClient.DeletePost(context.Background(), post.Id)
|
|
require.NoError(t, err)
|
|
|
|
// Normal client should get 404 when trying to access deleted post normally
|
|
_, resp, err = client.GetFileInfosForPost(context.Background(), post.Id, "")
|
|
require.Error(t, err)
|
|
CheckNotFoundStatus(t, resp)
|
|
|
|
// Normal client should get unauthorized when trying to access deleted post
|
|
_, resp, err = client.GetFileInfosForPostIncludeDeleted(context.Background(), post.Id, "")
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
|
|
// System client should get 404 when trying to access deleted post normally
|
|
_, resp, err = th.SystemAdminClient.GetFileInfosForPost(context.Background(), post.Id, "")
|
|
require.Error(t, err)
|
|
CheckNotFoundStatus(t, resp)
|
|
|
|
// System client should be able to access deleted post with include_deleted param
|
|
infos, _, err = th.SystemAdminClient.GetFileInfosForPostIncludeDeleted(context.Background(), post.Id, "")
|
|
require.NoError(t, err)
|
|
|
|
require.Len(t, infos, 3, "missing file infos")
|
|
|
|
found = false
|
|
for _, info := range infos {
|
|
if info.Id == fileIds[0] {
|
|
found = true
|
|
}
|
|
}
|
|
|
|
require.True(t, found, "missing file info")
|
|
|
|
_, err = client.Logout(context.Background())
|
|
require.NoError(t, err)
|
|
_, resp, err = client.GetFileInfosForPost(context.Background(), model.NewId(), "")
|
|
require.Error(t, err)
|
|
CheckUnauthorizedStatus(t, resp)
|
|
|
|
_, _, err = th.SystemAdminClient.GetFileInfosForPost(context.Background(), th.BasicPost.Id, "")
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestSetChannelUnread(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
u1 := th.BasicUser
|
|
u2 := th.BasicUser2
|
|
s2, _ := th.App.GetSession(th.Client.AuthToken)
|
|
_, _, err := th.Client.Login(context.Background(), u1.Email, u1.Password)
|
|
require.NoError(t, err)
|
|
c1 := th.BasicChannel
|
|
c1toc2 := &model.ChannelView{ChannelId: th.BasicChannel2.Id, PrevChannelId: c1.Id}
|
|
now := utils.MillisFromTime(time.Now())
|
|
th.CreateMessagePostNoClient(t, c1, "AAA", now)
|
|
p2 := th.CreateMessagePostNoClient(t, c1, "BBB", now+10)
|
|
th.CreateMessagePostNoClient(t, c1, "CCC", now+20)
|
|
|
|
pp1 := th.CreateMessagePostNoClient(t, th.BasicPrivateChannel, "Sssh!", now)
|
|
pp2 := th.CreateMessagePostNoClient(t, th.BasicPrivateChannel, "You Sssh!", now+10)
|
|
require.NotNil(t, pp1)
|
|
require.NotNil(t, pp2)
|
|
|
|
// Ensure that post have been read
|
|
unread, appErr := th.App.GetChannelUnread(th.Context, c1.Id, u1.Id)
|
|
require.Nil(t, appErr)
|
|
require.Equal(t, int64(4), unread.MsgCount)
|
|
unread, appErr = th.App.GetChannelUnread(th.Context, c1.Id, u2.Id)
|
|
require.Nil(t, appErr)
|
|
require.Equal(t, int64(4), unread.MsgCount)
|
|
_, appErr = th.App.ViewChannel(th.Context, c1toc2, u2.Id, s2.Id, false)
|
|
require.Nil(t, appErr)
|
|
unread, appErr = th.App.GetChannelUnread(th.Context, c1.Id, u2.Id)
|
|
require.Nil(t, appErr)
|
|
require.Equal(t, int64(0), unread.MsgCount)
|
|
|
|
t.Run("Unread last one", func(t *testing.T) {
|
|
var r *model.Response
|
|
r, err = th.Client.SetPostUnread(context.Background(), u1.Id, p2.Id, true)
|
|
require.NoError(t, err)
|
|
CheckOKStatus(t, r)
|
|
unread, appErr := th.App.GetChannelUnread(th.Context, c1.Id, u1.Id)
|
|
require.Nil(t, appErr)
|
|
assert.Equal(t, int64(2), unread.MsgCount)
|
|
})
|
|
|
|
t.Run("Unread on a direct channel", func(t *testing.T) {
|
|
dc := th.CreateDmChannel(t, u2)
|
|
th.CreateMessagePostNoClient(t, dc, "test1", now)
|
|
p := th.CreateMessagePostNoClient(t, dc, "test2", now+10)
|
|
require.NotNil(t, p)
|
|
th.CreateMessagePostNoClient(t, dc, "test3", now+20)
|
|
p1 := th.CreateMessagePostNoClient(t, dc, "test4", now+30)
|
|
require.NotNil(t, p1)
|
|
|
|
// Ensure that post have been read
|
|
unread, err := th.App.GetChannelUnread(th.Context, dc.Id, u1.Id)
|
|
require.Nil(t, err)
|
|
require.Equal(t, int64(4), unread.MsgCount)
|
|
cv := &model.ChannelView{ChannelId: dc.Id}
|
|
_, appErr := th.App.ViewChannel(th.Context, cv, u1.Id, s2.Id, false)
|
|
require.Nil(t, appErr)
|
|
unread, err = th.App.GetChannelUnread(th.Context, dc.Id, u1.Id)
|
|
require.Nil(t, err)
|
|
require.Equal(t, int64(0), unread.MsgCount)
|
|
|
|
r, _ := th.Client.SetPostUnread(context.Background(), u1.Id, p.Id, false)
|
|
assert.Equal(t, 200, r.StatusCode)
|
|
unread, err = th.App.GetChannelUnread(th.Context, dc.Id, u1.Id)
|
|
require.Nil(t, err)
|
|
require.Equal(t, int64(3), unread.MsgCount)
|
|
|
|
// Ensure that post have been read
|
|
_, appErr = th.App.ViewChannel(th.Context, cv, u1.Id, s2.Id, false)
|
|
require.Nil(t, appErr)
|
|
unread, err = th.App.GetChannelUnread(th.Context, dc.Id, u1.Id)
|
|
require.Nil(t, err)
|
|
require.Equal(t, int64(0), unread.MsgCount)
|
|
|
|
r, _ = th.Client.SetPostUnread(context.Background(), u1.Id, p1.Id, false)
|
|
assert.Equal(t, 200, r.StatusCode)
|
|
unread, err = th.App.GetChannelUnread(th.Context, dc.Id, u1.Id)
|
|
require.Nil(t, err)
|
|
require.Equal(t, int64(1), unread.MsgCount)
|
|
})
|
|
|
|
t.Run("Unread on a direct channel in a thread", func(t *testing.T) {
|
|
dc := th.CreateDmChannel(t, th.CreateUser(t))
|
|
rootPost, appErr := th.App.CreatePost(th.Context, &model.Post{UserId: u1.Id, CreateAt: now, ChannelId: dc.Id, Message: "root"}, dc, model.CreatePostFlags{})
|
|
require.Nil(t, appErr)
|
|
_, appErr = th.App.CreatePost(th.Context, &model.Post{RootId: rootPost.Id, UserId: u1.Id, CreateAt: now + 10, ChannelId: dc.Id, Message: "reply 1"}, dc, model.CreatePostFlags{})
|
|
require.Nil(t, appErr)
|
|
reply2, appErr := th.App.CreatePost(th.Context, &model.Post{RootId: rootPost.Id, UserId: u1.Id, CreateAt: now + 20, ChannelId: dc.Id, Message: "reply 2"}, dc, model.CreatePostFlags{})
|
|
require.Nil(t, appErr)
|
|
_, appErr = th.App.CreatePost(th.Context, &model.Post{RootId: rootPost.Id, UserId: u1.Id, CreateAt: now + 30, ChannelId: dc.Id, Message: "reply 3"}, dc, model.CreatePostFlags{})
|
|
require.Nil(t, appErr)
|
|
|
|
// Ensure that post have been read
|
|
unread, err := th.App.GetChannelUnread(th.Context, dc.Id, u1.Id)
|
|
require.Nil(t, err)
|
|
require.Equal(t, int64(4), unread.MsgCount)
|
|
require.Equal(t, int64(1), unread.MsgCountRoot)
|
|
cv := &model.ChannelView{ChannelId: dc.Id}
|
|
_, appErr = th.App.ViewChannel(th.Context, cv, u1.Id, s2.Id, false)
|
|
require.Nil(t, appErr)
|
|
unread, err = th.App.GetChannelUnread(th.Context, dc.Id, u1.Id)
|
|
require.Nil(t, err)
|
|
require.Equal(t, int64(0), unread.MsgCount)
|
|
require.Equal(t, int64(0), unread.MsgCountRoot)
|
|
|
|
r, _ := th.Client.SetPostUnread(context.Background(), u1.Id, rootPost.Id, false)
|
|
assert.Equal(t, 200, r.StatusCode)
|
|
unread, err = th.App.GetChannelUnread(th.Context, dc.Id, u1.Id)
|
|
require.Nil(t, err)
|
|
require.Equal(t, int64(4), unread.MsgCount)
|
|
require.Equal(t, int64(1), unread.MsgCountRoot)
|
|
|
|
// Ensure that post have been read
|
|
_, appErr = th.App.ViewChannel(th.Context, cv, u1.Id, s2.Id, false)
|
|
require.Nil(t, appErr)
|
|
unread, err = th.App.GetChannelUnread(th.Context, dc.Id, u1.Id)
|
|
require.Nil(t, err)
|
|
require.Equal(t, int64(0), unread.MsgCount)
|
|
require.Equal(t, int64(0), unread.MsgCountRoot)
|
|
|
|
r, _ = th.Client.SetPostUnread(context.Background(), u1.Id, reply2.Id, false)
|
|
assert.Equal(t, 200, r.StatusCode)
|
|
unread, err = th.App.GetChannelUnread(th.Context, dc.Id, u1.Id)
|
|
require.Nil(t, err)
|
|
require.Equal(t, int64(2), unread.MsgCount)
|
|
require.Equal(t, int64(0), unread.MsgCountRoot)
|
|
})
|
|
|
|
t.Run("Unread on a private channel", func(t *testing.T) {
|
|
r, _ := th.Client.SetPostUnread(context.Background(), u1.Id, pp2.Id, true)
|
|
assert.Equal(t, 200, r.StatusCode)
|
|
unread, appErr := th.App.GetChannelUnread(th.Context, th.BasicPrivateChannel.Id, u1.Id)
|
|
require.Nil(t, appErr)
|
|
assert.Equal(t, int64(1), unread.MsgCount)
|
|
r, _ = th.Client.SetPostUnread(context.Background(), u1.Id, pp1.Id, true)
|
|
assert.Equal(t, 200, r.StatusCode)
|
|
unread, appErr = th.App.GetChannelUnread(th.Context, th.BasicPrivateChannel.Id, u1.Id)
|
|
require.Nil(t, appErr)
|
|
assert.Equal(t, int64(2), unread.MsgCount)
|
|
})
|
|
|
|
t.Run("Can't unread an imaginary post", func(t *testing.T) {
|
|
r, _ := th.Client.SetPostUnread(context.Background(), u1.Id, "invalid4ofngungryquinj976y", true)
|
|
assert.Equal(t, http.StatusForbidden, r.StatusCode)
|
|
})
|
|
|
|
// let's create another user to test permissions
|
|
u3 := th.CreateUser(t)
|
|
c3 := th.CreateClient()
|
|
_, _, err = c3.Login(context.Background(), u3.Email, u3.Password)
|
|
require.NoError(t, err)
|
|
|
|
t.Run("Can't unread channels you don't belong to", func(t *testing.T) {
|
|
r, _ := c3.SetPostUnread(context.Background(), u3.Id, pp1.Id, true)
|
|
assert.Equal(t, http.StatusForbidden, r.StatusCode)
|
|
})
|
|
|
|
t.Run("Can't unread users you don't have permission to edit", func(t *testing.T) {
|
|
r, _ := c3.SetPostUnread(context.Background(), u1.Id, pp1.Id, true)
|
|
assert.Equal(t, http.StatusForbidden, r.StatusCode)
|
|
})
|
|
|
|
t.Run("Can't unread if user is not logged in", func(t *testing.T) {
|
|
_, err := th.Client.Logout(context.Background())
|
|
require.NoError(t, err)
|
|
response, err := th.Client.SetPostUnread(context.Background(), u1.Id, p2.Id, true)
|
|
require.Error(t, err)
|
|
CheckUnauthorizedStatus(t, response)
|
|
})
|
|
}
|
|
|
|
func TestSetPostUnreadWithoutCollapsedThreads(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
|
|
th := Setup(t).InitBasic(t)
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.ServiceSettings.ThreadAutoFollow = true
|
|
*cfg.ServiceSettings.CollapsedThreads = model.CollapsedThreadsDefaultOn
|
|
})
|
|
|
|
// user2: first root mention @user1
|
|
// - user1: hello
|
|
// - user2: mention @u1
|
|
// - user1: another reply
|
|
// - user2: another mention @u1
|
|
// user1: a root post
|
|
// user2: Another root mention @u1
|
|
user1Mention := " @" + th.BasicUser.Username
|
|
rootPost1, appErr := th.App.CreatePost(th.Context, &model.Post{UserId: th.BasicUser2.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "first root mention" + user1Mention}, th.BasicChannel, model.CreatePostFlags{})
|
|
require.Nil(t, appErr)
|
|
_, appErr = th.App.CreatePost(th.Context, &model.Post{RootId: rootPost1.Id, UserId: th.BasicUser.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "hello"}, th.BasicChannel, model.CreatePostFlags{})
|
|
require.Nil(t, appErr)
|
|
replyPost1, appErr := th.App.CreatePost(th.Context, &model.Post{RootId: rootPost1.Id, UserId: th.BasicUser2.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "mention" + user1Mention}, th.BasicChannel, model.CreatePostFlags{})
|
|
require.Nil(t, appErr)
|
|
_, appErr = th.App.CreatePost(th.Context, &model.Post{RootId: rootPost1.Id, UserId: th.BasicUser.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "another reply"}, th.BasicChannel, model.CreatePostFlags{})
|
|
require.Nil(t, appErr)
|
|
_, appErr = th.App.CreatePost(th.Context, &model.Post{RootId: rootPost1.Id, UserId: th.BasicUser2.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "another mention" + user1Mention}, th.BasicChannel, model.CreatePostFlags{})
|
|
require.Nil(t, appErr)
|
|
_, appErr = th.App.CreatePost(th.Context, &model.Post{UserId: th.BasicUser.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "a root post"}, th.BasicChannel, model.CreatePostFlags{})
|
|
require.Nil(t, appErr)
|
|
_, appErr = th.App.CreatePost(th.Context, &model.Post{UserId: th.BasicUser2.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "another root mention" + user1Mention}, th.BasicChannel, model.CreatePostFlags{})
|
|
require.Nil(t, appErr)
|
|
|
|
t.Run("Mark reply post as unread", func(t *testing.T) {
|
|
userWSClient := th.CreateConnectedWebSocketClient(t)
|
|
|
|
_, err := th.Client.SetPostUnread(context.Background(), th.BasicUser.Id, replyPost1.Id, false)
|
|
require.NoError(t, err)
|
|
channelUnread, appErr := th.App.GetChannelUnread(th.Context, th.BasicChannel.Id, th.BasicUser.Id)
|
|
require.Nil(t, appErr)
|
|
|
|
require.Equal(t, int64(3), channelUnread.MentionCount)
|
|
// MentionCountRoot should be zero so that supported clients don't show a mention badge for the channel
|
|
require.Equal(t, int64(0), channelUnread.MentionCountRoot)
|
|
|
|
require.Equal(t, int64(5), channelUnread.MsgCount)
|
|
// MentionCountRoot should be zero so that supported clients don't show the channel as unread
|
|
require.Equal(t, channelUnread.MsgCountRoot, int64(0))
|
|
|
|
// test websocket event for marking post as unread
|
|
var caught bool
|
|
var exit bool
|
|
var data map[string]any
|
|
for {
|
|
select {
|
|
case ev := <-userWSClient.EventChannel:
|
|
if ev.EventType() == model.WebsocketEventPostUnread {
|
|
caught = true
|
|
data = ev.GetData()
|
|
}
|
|
case <-time.After(5 * time.Second):
|
|
exit = true
|
|
}
|
|
if exit {
|
|
break
|
|
}
|
|
}
|
|
require.Truef(t, caught, "User should have received %s event", model.WebsocketEventPostUnread)
|
|
msgCount, ok := data["msg_count"]
|
|
require.True(t, ok)
|
|
require.EqualValues(t, 3, msgCount)
|
|
mentionCount, ok := data["mention_count"]
|
|
require.True(t, ok)
|
|
require.EqualValues(t, 3, mentionCount)
|
|
|
|
threadMembership, appErr := th.App.GetThreadMembershipForUser(th.BasicUser.Id, rootPost1.Id)
|
|
require.Nil(t, appErr)
|
|
thread, appErr := th.App.GetThreadForUser(th.Context, threadMembership, false)
|
|
require.Nil(t, appErr)
|
|
require.Equal(t, int64(2), thread.UnreadMentions)
|
|
require.Equal(t, int64(3), thread.UnreadReplies)
|
|
})
|
|
|
|
t.Run("Mark root post as unread", func(t *testing.T) {
|
|
_, err := th.Client.SetPostUnread(context.Background(), th.BasicUser.Id, rootPost1.Id, false)
|
|
require.NoError(t, err)
|
|
channelUnread, appErr := th.App.GetChannelUnread(th.Context, th.BasicChannel.Id, th.BasicUser.Id)
|
|
require.Nil(t, appErr)
|
|
|
|
require.Equal(t, int64(4), channelUnread.MentionCount)
|
|
require.Equal(t, int64(2), channelUnread.MentionCountRoot)
|
|
|
|
require.Equal(t, int64(7), channelUnread.MsgCount)
|
|
require.Equal(t, int64(3), channelUnread.MsgCountRoot)
|
|
})
|
|
}
|
|
|
|
func TestGetPostsByIds(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
|
|
th := Setup(t).InitBasic(t)
|
|
client := th.Client
|
|
|
|
post1 := th.CreatePost(t)
|
|
post2 := th.CreatePost(t)
|
|
|
|
posts, response, err := client.GetPostsByIds(context.Background(), []string{post1.Id, post2.Id})
|
|
require.NoError(t, err)
|
|
CheckOKStatus(t, response)
|
|
require.Len(t, posts, 2, "wrong number returned")
|
|
require.Equal(t, posts[0].Id, post2.Id)
|
|
require.Equal(t, posts[1].Id, post1.Id)
|
|
|
|
_, response, err = client.GetPostsByIds(context.Background(), []string{})
|
|
require.Error(t, err)
|
|
CheckBadRequestStatus(t, response)
|
|
|
|
_, response, err = client.GetPostsByIds(context.Background(), []string{"abc123"})
|
|
require.Error(t, err)
|
|
CheckNotFoundStatus(t, response)
|
|
}
|
|
|
|
func TestGetEditHistoryForPost(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
|
|
th := Setup(t).InitBasic(t)
|
|
client := th.Client
|
|
|
|
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)
|
|
|
|
time.Sleep(1 * time.Millisecond)
|
|
|
|
t.Run("unedited post", func(t *testing.T) {
|
|
history, resp, err := client.GetEditHistoryForPost(context.Background(), rpost.Id)
|
|
require.Error(t, err)
|
|
CheckNotFoundStatus(t, resp)
|
|
require.Len(t, history, 0)
|
|
})
|
|
|
|
// update the post message
|
|
patch := &model.PostPatch{
|
|
Message: model.NewPointer("new message edited"),
|
|
}
|
|
|
|
// Patch the post
|
|
_, response1, err1 := client.PatchPost(context.Background(), rpost.Id, patch)
|
|
require.NoError(t, err1)
|
|
CheckOKStatus(t, response1)
|
|
|
|
// update the post message again
|
|
patch = &model.PostPatch{
|
|
Message: model.NewPointer("new message edited again"),
|
|
}
|
|
|
|
_, response2, err2 := client.PatchPost(context.Background(), rpost.Id, patch)
|
|
require.NoError(t, err2)
|
|
CheckOKStatus(t, response2)
|
|
|
|
t.Run("update history correctly", func(t *testing.T) {
|
|
history, response3, err3 := client.GetEditHistoryForPost(context.Background(), rpost.Id)
|
|
require.NoError(t, err3)
|
|
CheckOKStatus(t, response3)
|
|
|
|
require.Len(t, history, 2)
|
|
require.Equal(t, "new message edited", history[0].Message)
|
|
require.Equal(t, "new message", history[1].Message)
|
|
})
|
|
|
|
t.Run("logged out", func(t *testing.T) {
|
|
_, err := client.Logout(context.Background())
|
|
require.NoError(t, err)
|
|
_, resp, err := client.GetEditHistoryForPost(context.Background(), rpost.Id)
|
|
require.Error(t, err)
|
|
CheckUnauthorizedStatus(t, resp)
|
|
})
|
|
|
|
t.Run("different user", func(t *testing.T) {
|
|
th.LoginBasic2(t)
|
|
_, resp, err := client.GetEditHistoryForPost(context.Background(), rpost.Id)
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
})
|
|
|
|
t.Run("edit history includes file metadata", func(t *testing.T) {
|
|
th.LoginBasic(t)
|
|
fileInfo1, appErr := th.App.UploadFile(th.Context, []byte("data"), th.BasicChannel.Id, "test")
|
|
require.Nil(t, appErr)
|
|
|
|
fileInfo2, appErr := th.App.UploadFile(th.Context, []byte("data"), th.BasicChannel.Id, "test")
|
|
require.Nil(t, appErr)
|
|
|
|
post := &model.Post{
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "new message",
|
|
UserId: th.BasicUser.Id,
|
|
FileIds: []string{fileInfo1.Id, fileInfo2.Id},
|
|
}
|
|
|
|
createdPost, appErr := th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, appErr)
|
|
require.Contains(t, createdPost.FileIds, fileInfo1.Id)
|
|
require.Contains(t, createdPost.FileIds, fileInfo2.Id)
|
|
|
|
patch = &model.PostPatch{
|
|
Message: model.NewPointer("new message 1"),
|
|
}
|
|
_, response, err := client.PatchPost(context.Background(), createdPost.Id, patch)
|
|
require.NoError(t, err)
|
|
CheckOKStatus(t, response)
|
|
|
|
patch = &model.PostPatch{
|
|
Message: model.NewPointer("new message 2"),
|
|
}
|
|
_, response, err = client.PatchPost(context.Background(), createdPost.Id, patch)
|
|
require.NoError(t, err)
|
|
CheckOKStatus(t, response)
|
|
|
|
patch = &model.PostPatch{
|
|
Message: model.NewPointer("new message 3"),
|
|
}
|
|
_, response, err = client.PatchPost(context.Background(), createdPost.Id, patch)
|
|
require.NoError(t, err)
|
|
CheckOKStatus(t, response)
|
|
|
|
editHistory, resp, err := client.GetEditHistoryForPost(context.Background(), createdPost.Id)
|
|
require.NoError(t, err)
|
|
CheckOKStatus(t, resp)
|
|
|
|
for _, editHistoryItem := range editHistory {
|
|
require.Len(t, editHistoryItem.FileIds, 2)
|
|
require.Contains(t, editHistoryItem.FileIds, fileInfo1.Id)
|
|
require.Contains(t, editHistoryItem.FileIds, fileInfo2.Id)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestCreatePostNotificationsWithCRT(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
|
|
th := Setup(t).InitBasic(t)
|
|
rpost := th.CreatePost(t)
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.ServiceSettings.ThreadAutoFollow = true
|
|
*cfg.ServiceSettings.CollapsedThreads = model.CollapsedThreadsDefaultOn
|
|
})
|
|
|
|
testCases := []struct {
|
|
name string
|
|
post *model.Post
|
|
notifyProps model.StringMap
|
|
mentions bool
|
|
followers bool
|
|
}{
|
|
{
|
|
name: "When default is NONE, comments is NEVER, desktop threads is ALL, and has no mentions",
|
|
post: &model.Post{
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "reply",
|
|
UserId: th.BasicUser2.Id,
|
|
RootId: rpost.Id,
|
|
},
|
|
notifyProps: model.StringMap{
|
|
model.DesktopNotifyProp: model.UserNotifyNone,
|
|
model.CommentsNotifyProp: model.CommentsNotifyNever,
|
|
model.DesktopThreadsNotifyProp: model.UserNotifyAll,
|
|
},
|
|
mentions: false,
|
|
followers: false,
|
|
},
|
|
{
|
|
name: "When default is NONE, comments is NEVER, desktop threads is ALL, and has mentions",
|
|
post: &model.Post{
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "mention @" + th.BasicUser.Username,
|
|
UserId: th.BasicUser2.Id,
|
|
RootId: rpost.Id,
|
|
},
|
|
notifyProps: model.StringMap{
|
|
model.DesktopNotifyProp: model.UserNotifyNone,
|
|
model.CommentsNotifyProp: model.CommentsNotifyNever,
|
|
model.DesktopThreadsNotifyProp: model.UserNotifyAll,
|
|
},
|
|
mentions: true,
|
|
followers: false,
|
|
},
|
|
{
|
|
name: "When default is MENTION, comments is NEVER, desktop threads is ALL, and has no mentions",
|
|
post: &model.Post{
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "reply",
|
|
UserId: th.BasicUser2.Id,
|
|
RootId: rpost.Id,
|
|
},
|
|
notifyProps: model.StringMap{
|
|
model.DesktopNotifyProp: model.UserNotifyMention,
|
|
model.CommentsNotifyProp: model.CommentsNotifyNever,
|
|
model.DesktopThreadsNotifyProp: model.UserNotifyAll,
|
|
},
|
|
mentions: false,
|
|
followers: true,
|
|
},
|
|
{
|
|
name: "When default is MENTION, comments is ANY, desktop threads is MENTION, and has no mentions",
|
|
post: &model.Post{
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "reply",
|
|
UserId: th.BasicUser2.Id,
|
|
RootId: rpost.Id,
|
|
},
|
|
notifyProps: model.StringMap{
|
|
model.DesktopNotifyProp: model.UserNotifyMention,
|
|
model.CommentsNotifyProp: model.CommentsNotifyAny,
|
|
model.DesktopThreadsNotifyProp: model.UserNotifyMention,
|
|
},
|
|
mentions: false,
|
|
followers: false,
|
|
},
|
|
{
|
|
name: "When default is MENTION, comments is NEVER, desktop threads is MENTION, and has mentions",
|
|
post: &model.Post{
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "reply @" + th.BasicUser.Username,
|
|
UserId: th.BasicUser2.Id,
|
|
RootId: rpost.Id,
|
|
},
|
|
notifyProps: model.StringMap{
|
|
model.DesktopNotifyProp: model.UserNotifyMention,
|
|
model.CommentsNotifyProp: model.CommentsNotifyNever,
|
|
model.DesktopThreadsNotifyProp: model.UserNotifyMention,
|
|
},
|
|
mentions: true,
|
|
followers: true,
|
|
},
|
|
}
|
|
|
|
// reset the cache so that channel member notify props includes all users
|
|
th.App.Srv().Store().Channel().ClearCaches()
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
userWSClient := th.CreateConnectedWebSocketClient(t)
|
|
|
|
patch := &model.UserPatch{}
|
|
patch.NotifyProps = model.CopyStringMap(th.BasicUser.NotifyProps)
|
|
maps.Copy(patch.NotifyProps, tc.notifyProps)
|
|
|
|
// update user's notify props
|
|
_, _, err := th.Client.PatchUser(context.Background(), th.BasicUser.Id, patch)
|
|
require.NoError(t, err)
|
|
|
|
// post a reply on the thread
|
|
_, appErr := th.App.CreatePostAsUser(th.Context, tc.post, th.Context.Session().Id, false)
|
|
require.Nil(t, appErr)
|
|
|
|
var caught bool
|
|
func() {
|
|
for {
|
|
select {
|
|
case ev := <-userWSClient.EventChannel:
|
|
if ev.EventType() == model.WebsocketEventPosted {
|
|
caught = true
|
|
data := ev.GetData()
|
|
|
|
users, ok := data["mentions"]
|
|
require.Equal(t, tc.mentions, ok)
|
|
if ok {
|
|
require.EqualValues(t, "[\""+th.BasicUser.Id+"\"]", users)
|
|
}
|
|
|
|
users, ok = data["followers"]
|
|
require.Equal(t, tc.followers, ok)
|
|
|
|
if ok {
|
|
require.EqualValues(t, "[\""+th.BasicUser.Id+"\"]", users)
|
|
}
|
|
}
|
|
case <-time.After(5 * time.Second):
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
|
|
require.Truef(t, caught, "User should have received %s event", model.WebsocketEventPosted)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetPostStripActionIntegrations(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
|
|
th := Setup(t).InitBasic(t)
|
|
client := th.Client
|
|
|
|
post := &model.Post{
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "with slack attachment action",
|
|
}
|
|
post.AddProp(model.PostPropsAttachments, []*model.SlackAttachment{
|
|
{
|
|
Text: "Slack Attachment Text",
|
|
Fields: []*model.SlackAttachmentField{
|
|
{
|
|
Title: "Test Field",
|
|
Value: "test value",
|
|
Short: true,
|
|
},
|
|
},
|
|
Actions: []*model.PostAction{
|
|
{
|
|
Type: model.PostActionTypeButton,
|
|
Name: "test-name",
|
|
Integration: &model.PostActionIntegration{
|
|
URL: "https://test.test/action",
|
|
Context: map[string]any{
|
|
"test-ctx": "some-value",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
rpost, resp, err2 := client.CreatePost(context.Background(), post)
|
|
require.NoError(t, err2)
|
|
CheckCreatedStatus(t, resp)
|
|
|
|
actualPost, _, err := client.GetPost(context.Background(), rpost.Id, "")
|
|
require.NoError(t, err)
|
|
attachments, _ := actualPost.Props[model.PostPropsAttachments].([]any)
|
|
require.Equal(t, 1, len(attachments))
|
|
att, _ := attachments[0].(map[string]any)
|
|
require.NotNil(t, att)
|
|
actions, _ := att["actions"].([]any)
|
|
require.Equal(t, 1, len(actions))
|
|
action, _ := actions[0].(map[string]any)
|
|
require.NotNil(t, action)
|
|
// integration must be omitted
|
|
require.Nil(t, action["integration"])
|
|
}
|
|
|
|
func TestPostReminder(t *testing.T) {
|
|
t.Skip("MM-60329")
|
|
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
client := th.Client
|
|
userWSClient := th.CreateConnectedWebSocketClient(t)
|
|
|
|
targetTime := time.Now().UTC().Unix()
|
|
resp, err := client.SetPostReminder(context.Background(), &model.PostReminder{
|
|
TargetTime: targetTime,
|
|
PostId: th.BasicPost.Id,
|
|
UserId: th.BasicUser.Id,
|
|
})
|
|
require.NoError(t, err)
|
|
CheckOKStatus(t, resp)
|
|
|
|
post, _, err := client.GetPost(context.Background(), th.BasicPost.Id, "")
|
|
require.NoError(t, err)
|
|
|
|
user, _, err := client.GetUser(context.Background(), post.UserId, "")
|
|
require.NoError(t, err)
|
|
|
|
var caught bool
|
|
func() {
|
|
for {
|
|
select {
|
|
case ev := <-userWSClient.EventChannel:
|
|
if ev.EventType() == model.WebsocketEventEphemeralMessage {
|
|
caught = true
|
|
data := ev.GetData()
|
|
|
|
post, ok := data["post"].(string)
|
|
require.True(t, ok)
|
|
|
|
var parsedPost model.Post
|
|
err := json.Unmarshal([]byte(post), &parsedPost)
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, model.PostTypeEphemeral, parsedPost.Type)
|
|
assert.Equal(t, th.BasicUser.Id, parsedPost.UserId)
|
|
assert.Equal(t, th.BasicPost.Id, parsedPost.RootId)
|
|
|
|
require.Equal(t, float64(targetTime), parsedPost.GetProp("target_time").(float64))
|
|
require.Equal(t, th.BasicPost.Id, parsedPost.GetProp("post_id").(string))
|
|
require.Equal(t, user.Username, parsedPost.GetProp("username").(string))
|
|
require.Equal(t, th.BasicTeam.Name, parsedPost.GetProp("team_name").(string))
|
|
return
|
|
}
|
|
case <-time.After(5 * time.Second):
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
|
|
require.Truef(t, caught, "User should have received %s event", model.WebsocketEventEphemeralMessage)
|
|
}
|
|
|
|
func TestPostGetInfo(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
defaultPerms := th.SaveDefaultRolePermissions(t)
|
|
defer th.RestoreDefaultRolePermissions(t, defaultPerms)
|
|
th.RemovePermissionFromRole(t, model.PermissionManagePrivateChannelMembers.Id, model.SystemUserRoleId)
|
|
th.RemovePermissionFromRole(t, model.PermissionManagePrivateChannelMembers.Id, model.ChannelUserRoleId)
|
|
th.RemovePermissionFromRole(t, model.PermissionManagePrivateChannelMembers.Id, model.TeamUserRoleId)
|
|
|
|
client := th.Client
|
|
sysadminClient := th.SystemAdminClient
|
|
_, _, err := sysadminClient.AddTeamMember(context.Background(), th.BasicTeam.Id, th.SystemAdminUser.Id)
|
|
require.NoError(t, err)
|
|
|
|
openChannel, _, err := client.CreateChannel(context.Background(), &model.Channel{TeamId: th.BasicTeam.Id, Type: model.ChannelTypeOpen, Name: "open-channel", DisplayName: "Open Channel"})
|
|
require.NoError(t, err)
|
|
_, _, err = sysadminClient.AddChannelMember(context.Background(), openChannel.Id, th.SystemAdminUser.Id)
|
|
require.NoError(t, err)
|
|
openPost, _, err := client.CreatePost(context.Background(), &model.Post{ChannelId: openChannel.Id})
|
|
require.NoError(t, err)
|
|
|
|
privateChannel, _, err := sysadminClient.CreateChannel(context.Background(), &model.Channel{TeamId: th.BasicTeam.Id, Type: model.ChannelTypePrivate, Name: "private-channel", DisplayName: "Private Channel"})
|
|
require.NoError(t, err)
|
|
privatePost, _, err := sysadminClient.CreatePost(context.Background(), &model.Post{ChannelId: privateChannel.Id})
|
|
require.NoError(t, err)
|
|
|
|
privateChannelBasicUser, _, err := client.CreateChannel(context.Background(), &model.Channel{TeamId: th.BasicTeam.Id, Type: model.ChannelTypePrivate, Name: "private-channel-basic-user", DisplayName: "Private Channel - Basic User"})
|
|
require.NoError(t, err)
|
|
privatePostBasicUser, _, err := client.CreatePost(context.Background(), &model.Post{ChannelId: privateChannelBasicUser.Id})
|
|
require.NoError(t, err)
|
|
|
|
user3 := th.CreateUser(t)
|
|
gmChannel, _, err := client.CreateGroupChannel(context.Background(), []string{th.BasicUser.Id, th.BasicUser2.Id, user3.Id})
|
|
require.NoError(t, err)
|
|
gmPost, _, err := client.CreatePost(context.Background(), &model.Post{ChannelId: gmChannel.Id})
|
|
require.NoError(t, err)
|
|
|
|
dmChannel, _, err := client.CreateDirectChannel(context.Background(), th.BasicUser.Id, th.BasicUser2.Id)
|
|
require.NoError(t, err)
|
|
dmPost, _, err := client.CreatePost(context.Background(), &model.Post{ChannelId: dmChannel.Id})
|
|
require.NoError(t, err)
|
|
|
|
openTeam, _, err := sysadminClient.CreateTeam(context.Background(), &model.Team{Type: model.TeamOpen, Name: "open-team", DisplayName: "Open Team", AllowOpenInvite: true})
|
|
require.NoError(t, err)
|
|
openTeamOpenChannel, _, err := sysadminClient.CreateChannel(context.Background(), &model.Channel{TeamId: openTeam.Id, Type: model.ChannelTypeOpen, Name: "open-team-open-channel", DisplayName: "Open Team - Open Channel"})
|
|
require.NoError(t, err)
|
|
openTeamOpenPost, _, err := sysadminClient.CreatePost(context.Background(), &model.Post{ChannelId: openTeamOpenChannel.Id})
|
|
require.NoError(t, err)
|
|
|
|
// Alt team is a team without the sysadmin in it.
|
|
altOpenTeam, _, err := client.CreateTeam(context.Background(), &model.Team{Type: model.TeamOpen, Name: "alt-open-team", DisplayName: "Alt Open Team", AllowOpenInvite: true})
|
|
require.NoError(t, err)
|
|
altOpenTeamOpenChannel, _, err := client.CreateChannel(context.Background(), &model.Channel{TeamId: altOpenTeam.Id, Type: model.ChannelTypeOpen, Name: "alt-open-team-open-channel", DisplayName: "Open Team - Open Channel"})
|
|
require.NoError(t, err)
|
|
altOpenTeamOpenPost, _, err := client.CreatePost(context.Background(), &model.Post{ChannelId: altOpenTeamOpenChannel.Id})
|
|
require.NoError(t, err)
|
|
|
|
inviteTeam, _, err := sysadminClient.CreateTeam(context.Background(), &model.Team{Type: model.TeamInvite, Name: "invite-team", DisplayName: "Invite Team"})
|
|
require.NoError(t, err)
|
|
inviteTeamOpenChannel, _, err := sysadminClient.CreateChannel(context.Background(), &model.Channel{TeamId: inviteTeam.Id, Type: model.ChannelTypeOpen, Name: "invite-team-open-channel", DisplayName: "Invite Team - Open Channel"})
|
|
require.NoError(t, err)
|
|
inviteTeamOpenPost, _, err := sysadminClient.CreatePost(context.Background(), &model.Post{ChannelId: inviteTeamOpenChannel.Id})
|
|
require.NoError(t, err)
|
|
|
|
testCases := []struct {
|
|
name string
|
|
team *model.Team
|
|
hasJoinedTeam bool
|
|
channel *model.Channel
|
|
hasJoinedChannel bool
|
|
post *model.Post
|
|
client *model.Client4
|
|
hasAccess bool
|
|
}{
|
|
// Open channel - Current Team
|
|
{
|
|
name: "Open post - Current team - Basic user",
|
|
team: th.BasicTeam,
|
|
hasJoinedTeam: true,
|
|
channel: openChannel,
|
|
hasJoinedChannel: true,
|
|
post: openPost,
|
|
client: client,
|
|
hasAccess: true,
|
|
},
|
|
{
|
|
name: "Open post - Current team - Sysadmin user",
|
|
team: th.BasicTeam,
|
|
hasJoinedTeam: true,
|
|
channel: openChannel,
|
|
hasJoinedChannel: true,
|
|
post: openPost,
|
|
client: sysadminClient,
|
|
hasAccess: true,
|
|
},
|
|
|
|
// Private channel - Current Team
|
|
{
|
|
name: "Private post by sysadmin - Current team - Basic user",
|
|
team: th.BasicTeam,
|
|
channel: privateChannel,
|
|
post: privatePost,
|
|
client: client,
|
|
hasAccess: false,
|
|
},
|
|
{
|
|
name: "Private post by sysadmin - Current team - Sysadmin user",
|
|
team: th.BasicTeam,
|
|
hasJoinedTeam: true,
|
|
channel: privateChannel,
|
|
hasJoinedChannel: true,
|
|
post: privatePost,
|
|
client: sysadminClient,
|
|
hasAccess: true,
|
|
},
|
|
{
|
|
name: "Private post by basic user - Current team - Basic user",
|
|
team: th.BasicTeam,
|
|
hasJoinedTeam: true,
|
|
channel: privateChannelBasicUser,
|
|
hasJoinedChannel: true,
|
|
post: privatePostBasicUser,
|
|
client: client,
|
|
hasAccess: true,
|
|
},
|
|
{
|
|
name: "Private post by basic user - Current team - Sysadmin user",
|
|
team: th.BasicTeam,
|
|
hasJoinedTeam: true,
|
|
channel: privateChannelBasicUser,
|
|
hasJoinedChannel: false,
|
|
post: privatePostBasicUser,
|
|
client: sysadminClient,
|
|
hasAccess: true,
|
|
},
|
|
|
|
// GM channel
|
|
{
|
|
name: "GM post - Current team - Basic user",
|
|
team: nil,
|
|
channel: gmChannel,
|
|
hasJoinedChannel: true,
|
|
post: gmPost,
|
|
client: client,
|
|
hasAccess: true,
|
|
},
|
|
{
|
|
name: "GM post - Current team - Sysadmin user",
|
|
team: nil,
|
|
channel: gmChannel,
|
|
post: gmPost,
|
|
client: sysadminClient,
|
|
hasAccess: true,
|
|
},
|
|
|
|
// DM channel
|
|
{
|
|
name: "DM post - Current team - Basic user",
|
|
team: nil,
|
|
channel: dmChannel,
|
|
hasJoinedChannel: true,
|
|
post: dmPost,
|
|
client: client,
|
|
hasAccess: true,
|
|
},
|
|
{
|
|
name: "DM post - Current team - Sysadmin user",
|
|
team: nil,
|
|
channel: dmChannel,
|
|
post: dmPost,
|
|
client: sysadminClient,
|
|
hasAccess: true,
|
|
},
|
|
|
|
// Open channel - Open Team
|
|
{
|
|
name: "Open post - Open team - Basic user",
|
|
team: openTeam,
|
|
hasJoinedTeam: false,
|
|
channel: openTeamOpenChannel,
|
|
hasJoinedChannel: false,
|
|
post: openTeamOpenPost,
|
|
client: client,
|
|
hasAccess: true,
|
|
},
|
|
{
|
|
name: "Open post - Open team - Sysadmin user",
|
|
team: openTeam,
|
|
hasJoinedTeam: true,
|
|
channel: openTeamOpenChannel,
|
|
hasJoinedChannel: true,
|
|
post: openTeamOpenPost,
|
|
client: sysadminClient,
|
|
hasAccess: true,
|
|
},
|
|
|
|
// Open channel - Alt Open Team
|
|
{
|
|
name: "Open post - Alt open team - Basic user",
|
|
team: altOpenTeam,
|
|
hasJoinedTeam: true,
|
|
channel: altOpenTeamOpenChannel,
|
|
hasJoinedChannel: true,
|
|
post: altOpenTeamOpenPost,
|
|
client: client,
|
|
hasAccess: true,
|
|
},
|
|
{
|
|
name: "Open post - Alt open team - Sysadmin user",
|
|
team: altOpenTeam,
|
|
hasJoinedTeam: false,
|
|
channel: altOpenTeamOpenChannel,
|
|
hasJoinedChannel: false,
|
|
post: altOpenTeamOpenPost,
|
|
client: sysadminClient,
|
|
hasAccess: true,
|
|
},
|
|
|
|
// Open channel - Invite Team
|
|
{
|
|
name: "Open post - Invite team - Basic user",
|
|
team: inviteTeam,
|
|
channel: inviteTeamOpenChannel,
|
|
post: inviteTeamOpenPost,
|
|
client: client,
|
|
hasAccess: false,
|
|
},
|
|
{
|
|
name: "Open post - Invite team - Sysadmin user",
|
|
team: inviteTeam,
|
|
hasJoinedTeam: true,
|
|
channel: inviteTeamOpenChannel,
|
|
hasJoinedChannel: true,
|
|
post: inviteTeamOpenPost,
|
|
client: sysadminClient,
|
|
hasAccess: true,
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
info, resp, err := tc.client.GetPostInfo(context.Background(), tc.post.Id)
|
|
if !tc.hasAccess {
|
|
require.Error(t, err)
|
|
CheckNotFoundStatus(t, resp)
|
|
return
|
|
}
|
|
|
|
require.NoError(t, err)
|
|
CheckOKStatus(t, resp)
|
|
require.Equal(t, tc.channel.Id, info.ChannelId)
|
|
require.Equal(t, tc.channel.Type, info.ChannelType)
|
|
require.Equal(t, tc.channel.DisplayName, info.ChannelDisplayName)
|
|
require.Equal(t, tc.hasJoinedChannel, info.HasJoinedChannel)
|
|
if tc.team != nil {
|
|
teamType := "I"
|
|
if tc.team.AllowOpenInvite {
|
|
teamType = "O"
|
|
}
|
|
require.Equal(t, tc.team.Id, info.TeamId)
|
|
require.Equal(t, teamType, info.TeamType)
|
|
require.Equal(t, tc.team.DisplayName, info.TeamDisplayName)
|
|
require.Equal(t, tc.hasJoinedTeam, info.HasJoinedTeam)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestAcknowledgePost(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
|
|
th := Setup(t).InitBasic(t)
|
|
th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuProfessional))
|
|
client := th.Client
|
|
|
|
post := th.BasicPost
|
|
ack, _, err := client.AcknowledgePost(context.Background(), post.Id, th.BasicUser.Id)
|
|
require.NoError(t, err)
|
|
|
|
acks, appErr := th.App.GetAcknowledgementsForPost(post.Id)
|
|
require.Nil(t, appErr)
|
|
require.Len(t, acks, 1)
|
|
require.Equal(t, acks[0], ack)
|
|
|
|
_, resp, err := client.AcknowledgePost(context.Background(), "junk", th.BasicUser.Id)
|
|
require.Error(t, err)
|
|
CheckBadRequestStatus(t, resp)
|
|
|
|
_, resp, err = client.AcknowledgePost(context.Background(), GenerateTestID(), th.BasicUser.Id)
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
|
|
_, resp, err = client.AcknowledgePost(context.Background(), post.Id, "junk")
|
|
require.Error(t, err)
|
|
CheckBadRequestStatus(t, resp)
|
|
|
|
_, resp, err = client.AcknowledgePost(context.Background(), post.Id, th.BasicUser2.Id)
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
|
|
_, err = client.Logout(context.Background())
|
|
require.NoError(t, err)
|
|
_, resp, err = client.AcknowledgePost(context.Background(), post.Id, th.BasicUser.Id)
|
|
require.Error(t, err)
|
|
CheckUnauthorizedStatus(t, resp)
|
|
|
|
_, _, err = th.SystemAdminClient.AcknowledgePost(context.Background(), post.Id, th.SystemAdminUser.Id)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestUnacknowledgePost(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
|
|
th := Setup(t).InitBasic(t)
|
|
th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuProfessional))
|
|
client := th.Client
|
|
|
|
post := th.BasicPost
|
|
ack, _, err := client.AcknowledgePost(context.Background(), post.Id, th.BasicUser.Id)
|
|
require.NoError(t, err)
|
|
|
|
acks, appErr := th.App.GetAcknowledgementsForPost(post.Id)
|
|
require.Nil(t, appErr)
|
|
require.Len(t, acks, 1)
|
|
require.Equal(t, acks[0], ack)
|
|
|
|
resp, err := client.UnacknowledgePost(context.Background(), "junk", th.BasicUser.Id)
|
|
require.Error(t, err)
|
|
CheckBadRequestStatus(t, resp)
|
|
|
|
resp, err = client.UnacknowledgePost(context.Background(), GenerateTestID(), th.BasicUser.Id)
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
|
|
resp, err = client.UnacknowledgePost(context.Background(), post.Id, "junk")
|
|
require.Error(t, err)
|
|
CheckBadRequestStatus(t, resp)
|
|
|
|
resp, err = client.UnacknowledgePost(context.Background(), post.Id, th.BasicUser2.Id)
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
|
|
_, err = client.UnacknowledgePost(context.Background(), post.Id, th.BasicUser.Id)
|
|
require.NoError(t, err)
|
|
|
|
acks, appErr = th.App.GetAcknowledgementsForPost(post.Id)
|
|
require.Nil(t, appErr)
|
|
require.Len(t, acks, 0)
|
|
|
|
_, err = client.Logout(context.Background())
|
|
require.NoError(t, err)
|
|
resp, err = client.UnacknowledgePost(context.Background(), post.Id, th.BasicUser.Id)
|
|
require.Error(t, err)
|
|
CheckUnauthorizedStatus(t, resp)
|
|
}
|
|
|
|
func TestRestorePostVersion(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
|
|
th := Setup(t).InitBasic(t)
|
|
client := th.Client
|
|
|
|
t.Run("should restore post version successfully", func(t *testing.T) {
|
|
post := &model.Post{
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "original message",
|
|
UserId: th.BasicUser.Id,
|
|
}
|
|
|
|
createdPost, response, err := client.CreatePost(context.Background(), post)
|
|
require.NoError(t, err)
|
|
CheckCreatedStatus(t, response)
|
|
|
|
patch, response, err := client.PatchPost(context.Background(), createdPost.Id, &model.PostPatch{
|
|
Message: model.NewPointer("edited message 1"),
|
|
})
|
|
require.NoError(t, err)
|
|
CheckOKStatus(t, response)
|
|
require.Equal(t, "edited message 1", patch.Message)
|
|
|
|
patch, response, err = client.PatchPost(context.Background(), createdPost.Id, &model.PostPatch{
|
|
Message: model.NewPointer("edited message 2"),
|
|
})
|
|
require.NoError(t, err)
|
|
CheckOKStatus(t, response)
|
|
require.Equal(t, "edited message 2", patch.Message)
|
|
|
|
// verify edit history
|
|
editHistory, response, err := client.GetEditHistoryForPost(context.Background(), createdPost.Id)
|
|
require.NoError(t, err)
|
|
CheckOKStatus(t, response)
|
|
require.Equal(t, 2, len(editHistory))
|
|
require.Equal(t, "edited message 1", editHistory[0].Message)
|
|
require.Equal(t, "original message", editHistory[1].Message)
|
|
|
|
// now we'll restore to the original version
|
|
restoredPost, response, err := client.RestorePostVersion(context.Background(), createdPost.Id, editHistory[1].Id)
|
|
require.NoError(t, err)
|
|
CheckOKStatus(t, response)
|
|
require.Equal(t, "original message", restoredPost.Message)
|
|
require.Equal(t, createdPost.Id, restoredPost.Id)
|
|
|
|
// verify restored post
|
|
fetchedPost, response, err := client.GetPost(context.Background(), createdPost.Id, "")
|
|
require.NoError(t, err)
|
|
CheckOKStatus(t, response)
|
|
require.Equal(t, "original message", fetchedPost.Message)
|
|
|
|
// verify edit history after restoring
|
|
editHistory, response, err = client.GetEditHistoryForPost(context.Background(), createdPost.Id)
|
|
require.NoError(t, err)
|
|
CheckOKStatus(t, response)
|
|
require.Equal(t, 3, len(editHistory))
|
|
require.Equal(t, "edited message 2", editHistory[0].Message)
|
|
require.Equal(t, "edited message 1", editHistory[1].Message)
|
|
require.Equal(t, "original message", editHistory[2].Message)
|
|
})
|
|
|
|
t.Run("should restore post version successfully with files", func(t *testing.T) {
|
|
fileResp, _, err := client.UploadFile(context.Background(), []byte("data"), th.BasicChannel.Id, "test")
|
|
require.NoError(t, err)
|
|
fileId := fileResp.FileInfos[0].Id
|
|
|
|
post := &model.Post{
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "original message",
|
|
UserId: th.BasicUser.Id,
|
|
FileIds: model.StringArray{fileId},
|
|
}
|
|
|
|
createdPost, response, err := client.CreatePost(context.Background(), post)
|
|
require.NoError(t, err)
|
|
CheckCreatedStatus(t, response)
|
|
require.Equal(t, 1, len(createdPost.FileIds))
|
|
|
|
patch, response, err := client.PatchPost(context.Background(), createdPost.Id, &model.PostPatch{
|
|
Message: model.NewPointer("edited message 1"),
|
|
FileIds: &model.StringArray{},
|
|
})
|
|
require.NoError(t, err)
|
|
CheckOKStatus(t, response)
|
|
require.Equal(t, "edited message 1", patch.Message)
|
|
require.Equal(t, 0, len(patch.FileIds))
|
|
|
|
// verify edit history
|
|
editHistory, response, err := client.GetEditHistoryForPost(context.Background(), createdPost.Id)
|
|
require.NoError(t, err)
|
|
CheckOKStatus(t, response)
|
|
require.Equal(t, 1, len(editHistory))
|
|
require.Equal(t, "original message", editHistory[0].Message)
|
|
require.Equal(t, 1, len(editHistory[0].FileIds))
|
|
|
|
// now we'll restore to the original version
|
|
restoredPost, response, err := client.RestorePostVersion(context.Background(), createdPost.Id, editHistory[0].Id)
|
|
require.NoError(t, err)
|
|
CheckOKStatus(t, response)
|
|
require.Equal(t, "original message", restoredPost.Message)
|
|
require.Equal(t, createdPost.Id, restoredPost.Id)
|
|
require.Equal(t, 1, len(restoredPost.FileIds))
|
|
|
|
// verify restored post
|
|
fetchedPost, response, err := client.GetPost(context.Background(), createdPost.Id, "")
|
|
require.NoError(t, err)
|
|
CheckOKStatus(t, response)
|
|
require.Equal(t, "original message", fetchedPost.Message)
|
|
require.Equal(t, 1, len(fetchedPost.FileIds))
|
|
|
|
// verify edit history after restoring
|
|
editHistory, response, err = client.GetEditHistoryForPost(context.Background(), createdPost.Id)
|
|
require.NoError(t, err)
|
|
CheckOKStatus(t, response)
|
|
require.Equal(t, 2, len(editHistory))
|
|
require.Equal(t, "edited message 1", editHistory[0].Message)
|
|
require.Equal(t, 0, len(editHistory[0].FileIds))
|
|
|
|
require.Equal(t, "original message", editHistory[1].Message)
|
|
require.Equal(t, 1, len(editHistory[1].FileIds))
|
|
})
|
|
|
|
t.Run("should get error when trying to restore non existent post ori history ID", func(t *testing.T) {
|
|
restoredPost, response, err := client.RestorePostVersion(context.Background(), model.NewId(), model.NewId())
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, response)
|
|
require.Nil(t, restoredPost)
|
|
|
|
post := &model.Post{
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "original message",
|
|
UserId: th.BasicUser.Id,
|
|
}
|
|
|
|
createdPost, response, err := client.CreatePost(context.Background(), post)
|
|
require.NoError(t, err)
|
|
CheckCreatedStatus(t, response)
|
|
|
|
restoredPost, response, err = client.RestorePostVersion(context.Background(), createdPost.Id, model.NewId())
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, response)
|
|
require.Nil(t, restoredPost)
|
|
|
|
post2 := &model.Post{
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "original message 2",
|
|
UserId: th.BasicUser.Id,
|
|
}
|
|
|
|
createdPost, response, err = client.CreatePost(context.Background(), post2)
|
|
require.NoError(t, err)
|
|
CheckCreatedStatus(t, response)
|
|
|
|
restoredPost, response, err = client.RestorePostVersion(context.Background(), createdPost.Id, post2.Id)
|
|
require.Error(t, err)
|
|
CheckNotFoundStatus(t, response)
|
|
require.Nil(t, restoredPost)
|
|
})
|
|
|
|
t.Run("user should not be able to restore someone else's post", func(t *testing.T) {
|
|
post := &model.Post{
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "original message",
|
|
UserId: th.BasicUser.Id,
|
|
}
|
|
|
|
createdPost, response, err := client.CreatePost(context.Background(), post)
|
|
require.NoError(t, err)
|
|
CheckCreatedStatus(t, response)
|
|
|
|
patch, response, err := client.PatchPost(context.Background(), createdPost.Id, &model.PostPatch{
|
|
Message: model.NewPointer("edited message 1"),
|
|
})
|
|
require.NoError(t, err)
|
|
CheckOKStatus(t, response)
|
|
require.Equal(t, "edited message 1", patch.Message)
|
|
|
|
// verify edit history
|
|
editHistory, response, err := client.GetEditHistoryForPost(context.Background(), createdPost.Id)
|
|
require.NoError(t, err)
|
|
CheckOKStatus(t, response)
|
|
require.Equal(t, 1, len(editHistory))
|
|
require.Equal(t, "original message", editHistory[0].Message)
|
|
|
|
// now we'll restore to the original version
|
|
th.LoginBasic2(t)
|
|
restoredPost, response, err := th.Client.RestorePostVersion(context.Background(), createdPost.Id, editHistory[0].Id)
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, response)
|
|
require.Nil(t, restoredPost)
|
|
})
|
|
|
|
t.Run("system admin should not be able to restore someone else's post", func(t *testing.T) {
|
|
th.LoginBasic(t)
|
|
post := &model.Post{
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "original message",
|
|
UserId: th.BasicUser.Id,
|
|
}
|
|
|
|
createdPost, response, err := th.Client.CreatePost(context.Background(), post)
|
|
require.NoError(t, err)
|
|
CheckCreatedStatus(t, response)
|
|
|
|
patch, response, err := th.Client.PatchPost(context.Background(), createdPost.Id, &model.PostPatch{
|
|
Message: model.NewPointer("edited message 1"),
|
|
})
|
|
require.NoError(t, err)
|
|
CheckOKStatus(t, response)
|
|
require.Equal(t, "edited message 1", patch.Message)
|
|
|
|
// verify edit history
|
|
editHistory, response, err := th.Client.GetEditHistoryForPost(context.Background(), createdPost.Id)
|
|
require.NoError(t, err)
|
|
CheckOKStatus(t, response)
|
|
require.Equal(t, 1, len(editHistory))
|
|
require.Equal(t, "original message", editHistory[0].Message)
|
|
|
|
// now we'll restore to the original version
|
|
th.LoginSystemAdmin(t)
|
|
restoredPost, response, err := th.SystemAdminClient.RestorePostVersion(context.Background(), createdPost.Id, editHistory[0].Id)
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, response)
|
|
require.Nil(t, restoredPost)
|
|
})
|
|
}
|
|
|
|
func TestRevealPost(t *testing.T) {
|
|
os.Setenv("MM_FEATUREFLAGS_BURNONREAD", "true")
|
|
t.Cleanup(func() {
|
|
os.Unsetenv("MM_FEATUREFLAGS_BURNONREAD")
|
|
})
|
|
|
|
th := SetupEnterprise(t).InitBasic(t)
|
|
|
|
th.LinkUserToTeam(t, th.SystemAdminUser, th.BasicTeam)
|
|
th.AddUserToChannel(t, th.SystemAdminUser, th.BasicChannel)
|
|
|
|
// Helper to create burn-on-read post
|
|
createBurnOnReadPost := func(client *model.Client4, channel *model.Channel) *model.Post {
|
|
post := &model.Post{
|
|
ChannelId: channel.Id,
|
|
Message: "burn on read message",
|
|
Type: model.PostTypeBurnOnRead,
|
|
}
|
|
post.AddProp(model.PostPropsExpireAt, model.GetMillis()+int64(model.DefaultExpirySeconds*1000))
|
|
|
|
createdPost, resp, err := client.CreatePost(context.Background(), post)
|
|
require.NoError(t, err)
|
|
CheckCreatedStatus(t, resp)
|
|
require.NotNil(t, createdPost)
|
|
return createdPost
|
|
}
|
|
|
|
// Helper to create and login second user
|
|
createSecondUser := func(channel *model.Channel) (*model.User, *model.Client4) {
|
|
user2 := th.CreateUser(t)
|
|
th.LinkUserToTeam(t, user2, th.BasicTeam)
|
|
if channel != nil {
|
|
th.AddUserToChannel(t, user2, channel)
|
|
}
|
|
client2 := th.CreateClient()
|
|
_, _, err := client2.Login(context.Background(), user2.Email, user2.Password)
|
|
require.NoError(t, err)
|
|
|
|
t.Cleanup(func() {
|
|
_, err = client2.Logout(context.Background())
|
|
require.NoError(t, err)
|
|
})
|
|
return user2, client2
|
|
}
|
|
|
|
t.Run("feature not enabled, should still allow reveal", func(t *testing.T) {
|
|
enableBurnOnReadFeature(th)
|
|
post := createBurnOnReadPost(th.SystemAdminClient, th.BasicChannel)
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
cfg.FeatureFlags.BurnOnRead = false
|
|
})
|
|
|
|
revealedPost, resp, err := th.Client.RevealPost(context.Background(), post.Id)
|
|
require.NoError(t, err)
|
|
CheckOKStatus(t, resp)
|
|
require.NotNil(t, revealedPost)
|
|
require.Equal(t, post.Id, revealedPost.Id)
|
|
require.Equal(t, "burn on read message", revealedPost.Message)
|
|
require.NotNil(t, revealedPost.Metadata)
|
|
require.NotZero(t, revealedPost.Metadata.ExpireAt)
|
|
})
|
|
|
|
th.TestForRegularAndSystemAdminClients(t, func(t *testing.T, client *model.Client4) {
|
|
enableBurnOnReadFeature(th)
|
|
|
|
regularPost := &model.Post{
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "regular message",
|
|
}
|
|
createdPost, resp, err := th.Client.CreatePost(context.Background(), regularPost)
|
|
require.NoError(t, err)
|
|
CheckCreatedStatus(t, resp)
|
|
|
|
_, client2 := createSecondUser(th.BasicChannel)
|
|
|
|
revealedPost, resp, err := client2.RevealPost(context.Background(), createdPost.Id)
|
|
require.Error(t, err)
|
|
CheckBadRequestStatus(t, resp)
|
|
CheckErrorID(t, err, "app.reveal_post.not_burn_on_read.app_error")
|
|
require.Nil(t, revealedPost)
|
|
}, "reveal regular post")
|
|
|
|
th.TestForRegularAndSystemAdminClients(t, func(t *testing.T, client *model.Client4) {
|
|
enableBurnOnReadFeature(th)
|
|
|
|
revealedPost, resp, err := th.Client.RevealPost(context.Background(), model.NewId())
|
|
require.Error(t, err)
|
|
CheckNotFoundStatus(t, resp)
|
|
require.Nil(t, revealedPost)
|
|
}, "reveal non-existing post")
|
|
|
|
th.TestForRegularAndSystemAdminClients(t, func(t *testing.T, client *model.Client4) {
|
|
enableBurnOnReadFeature(th)
|
|
|
|
post := createBurnOnReadPost(client, th.BasicChannel)
|
|
|
|
revealedPost, resp, err := client.RevealPost(context.Background(), post.Id)
|
|
require.Error(t, err)
|
|
CheckBadRequestStatus(t, resp)
|
|
require.Nil(t, revealedPost)
|
|
CheckErrorID(t, err, "api.post.reveal_post.cannot_reveal_own_post.app_error")
|
|
}, "try reveal own post")
|
|
|
|
th.TestForRegularAndSystemAdminClients(t, func(t *testing.T, client *model.Client4) {
|
|
enableBurnOnReadFeature(th)
|
|
_, client2 := createSecondUser(th.BasicChannel)
|
|
|
|
post := createBurnOnReadPost(client2, th.BasicChannel)
|
|
|
|
revealedPost, resp, err := client.RevealPost(context.Background(), post.Id)
|
|
require.NoError(t, err)
|
|
CheckOKStatus(t, resp)
|
|
require.NotNil(t, revealedPost)
|
|
require.Equal(t, post.Id, revealedPost.Id)
|
|
require.Equal(t, "burn on read message", revealedPost.Message)
|
|
require.NotNil(t, revealedPost.Metadata)
|
|
require.NotZero(t, revealedPost.Metadata.ExpireAt)
|
|
}, "reveal someone elses post")
|
|
|
|
th.TestForRegularAndSystemAdminClients(t, func(t *testing.T, client *model.Client4) {
|
|
enableBurnOnReadFeature(th)
|
|
|
|
post := &model.Post{
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "burn on read message",
|
|
Type: model.PostTypeBurnOnRead,
|
|
}
|
|
_, client2 := createSecondUser(th.BasicChannel)
|
|
|
|
createdPost, resp, err := client2.CreatePost(context.Background(), post)
|
|
require.NoError(t, err)
|
|
CheckCreatedStatus(t, resp)
|
|
|
|
// Manually expire the post
|
|
storePost, err := th.App.Srv().Store().Post().Get(th.Context, createdPost.Id, model.GetPostsOptions{}, "", th.App.Config().GetSanitizeOptions())
|
|
require.NoError(t, err)
|
|
require.Len(t, storePost.Posts, 1)
|
|
|
|
postToUpdate := storePost.Posts[createdPost.Id]
|
|
postToUpdate.AddProp(model.PostPropsExpireAt, model.GetMillis()-1000)
|
|
_, err = th.App.Srv().Store().Post().Overwrite(th.Context, postToUpdate)
|
|
require.NoError(t, err)
|
|
|
|
revealedPost, resp, err := client.RevealPost(context.Background(), createdPost.Id)
|
|
require.Error(t, err)
|
|
CheckBadRequestStatus(t, resp)
|
|
require.Nil(t, revealedPost)
|
|
CheckErrorID(t, err, "app.reveal_post.post_expired.app_error")
|
|
}, "reveal expired post")
|
|
|
|
th.TestForRegularAndSystemAdminClients(t, func(t *testing.T, client *model.Client4) {
|
|
enableBurnOnReadFeature(th)
|
|
|
|
_, client2 := createSecondUser(th.BasicChannel)
|
|
post := createBurnOnReadPost(client2, th.BasicChannel)
|
|
|
|
user := th.BasicUser
|
|
if client == th.SystemAdminClient {
|
|
user = th.SystemAdminUser
|
|
}
|
|
|
|
// Create expired read receipt
|
|
readReceipt, err := th.App.Srv().Store().ReadReceipt().Save(th.Context, &model.ReadReceipt{
|
|
PostID: post.Id,
|
|
UserID: user.Id,
|
|
ExpireAt: model.GetMillis() - 1000,
|
|
})
|
|
require.NoError(t, err)
|
|
require.NotNil(t, readReceipt)
|
|
|
|
revealedPost, resp, err := client.RevealPost(context.Background(), post.Id)
|
|
require.Error(t, err)
|
|
CheckNotFoundStatus(t, resp)
|
|
require.Nil(t, revealedPost)
|
|
CheckErrorID(t, err, "app.post.get.app_error")
|
|
}, "reveal post with expired read receipt")
|
|
|
|
th.TestForRegularAndSystemAdminClients(t, func(t *testing.T, client *model.Client4) {
|
|
enableBurnOnReadFeature(th)
|
|
|
|
_, client2 := createSecondUser(nil)
|
|
|
|
privateChannel, resp, err := client2.CreateChannel(context.Background(), &model.Channel{
|
|
TeamId: th.BasicTeam.Id,
|
|
Type: model.ChannelTypePrivate,
|
|
Name: GenerateTestChannelName(),
|
|
DisplayName: "Private Channel",
|
|
})
|
|
require.NoError(t, err)
|
|
CheckCreatedStatus(t, resp)
|
|
|
|
post := &model.Post{
|
|
ChannelId: privateChannel.Id,
|
|
Message: "burn on read message",
|
|
Type: model.PostTypeBurnOnRead,
|
|
}
|
|
post.AddProp(model.PostPropsExpireAt, model.GetMillis()+int64(model.DefaultExpirySeconds*1000))
|
|
|
|
createdPost, resp, err := client2.CreatePost(context.Background(), post)
|
|
require.NoError(t, err)
|
|
CheckCreatedStatus(t, resp)
|
|
|
|
revealedPost, resp, err := client.RevealPost(context.Background(), createdPost.Id)
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
require.Nil(t, revealedPost)
|
|
}, "user without channel access")
|
|
}
|
|
|
|
func TestCreateBurnOnReadPost(t *testing.T) {
|
|
os.Setenv("MM_FEATUREFLAGS_BURNONREAD", "true")
|
|
t.Cleanup(func() {
|
|
os.Unsetenv("MM_FEATUREFLAGS_BURNONREAD")
|
|
})
|
|
|
|
th := SetupEnterprise(t).InitBasic(t)
|
|
|
|
th.LinkUserToTeam(t, th.SystemAdminUser, th.BasicTeam)
|
|
th.AddUserToChannel(t, th.SystemAdminUser, th.BasicChannel)
|
|
|
|
th.TestForRegularAndSystemAdminClients(t, func(t *testing.T, client *model.Client4) {
|
|
enableBurnOnReadFeature(th)
|
|
|
|
post := &model.Post{
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "burn on read message",
|
|
Type: model.PostTypeBurnOnRead,
|
|
}
|
|
|
|
createdPost, resp, err := client.CreatePost(context.Background(), post)
|
|
require.NoError(t, err)
|
|
CheckCreatedStatus(t, resp)
|
|
require.NotNil(t, createdPost)
|
|
require.Equal(t, model.PostTypeBurnOnRead, createdPost.Type)
|
|
}, "create burn on read post")
|
|
|
|
t.Run("reveal burn on read post and verify in channel posts", func(t *testing.T) {
|
|
enableBurnOnReadFeature(th)
|
|
|
|
// Create burn-on-read post with basic user
|
|
post := &model.Post{
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "burn on read message",
|
|
Type: model.PostTypeBurnOnRead,
|
|
}
|
|
post.AddProp(model.PostPropsExpireAt, model.GetMillis()+int64(model.DefaultExpirySeconds*1000))
|
|
|
|
createdPost, resp, err := th.Client.CreatePost(context.Background(), post)
|
|
require.NoError(t, err)
|
|
CheckCreatedStatus(t, resp)
|
|
require.NotNil(t, createdPost)
|
|
|
|
// Create websocket client for system admin to receive reveal event
|
|
wsClient := th.CreateConnectedWebSocketClientWithClient(t, th.SystemAdminClient)
|
|
|
|
// Get posts for channel with system admin client - verify post is not revealed by default
|
|
posts, resp, err := th.SystemAdminClient.GetPostsForChannel(context.Background(), th.BasicChannel.Id, 0, 100, "", true, false)
|
|
require.NoError(t, err)
|
|
CheckOKStatus(t, resp)
|
|
require.NotNil(t, posts)
|
|
require.NotNil(t, posts.Posts[createdPost.Id])
|
|
unrevealedPost := posts.Posts[createdPost.Id]
|
|
require.Equal(t, "", unrevealedPost.Message)
|
|
// Check if the metadata is empty
|
|
require.Equal(t, model.PostMetadata{}, *unrevealedPost.Metadata)
|
|
|
|
// Reveal the post with system admin client
|
|
revealedPost, resp, err := th.SystemAdminClient.RevealPost(context.Background(), createdPost.Id)
|
|
require.NoError(t, err)
|
|
CheckOKStatus(t, resp)
|
|
require.NotNil(t, revealedPost)
|
|
require.Equal(t, "burn on read message", revealedPost.Message)
|
|
require.NotNil(t, revealedPost.Metadata)
|
|
require.NotZero(t, revealedPost.Metadata.ExpireAt)
|
|
|
|
// Verify websocket client receives the reveal event
|
|
var eventPost model.Post
|
|
require.Eventually(t, func() bool {
|
|
select {
|
|
case event := <-wsClient.EventChannel:
|
|
if event.EventType() == model.WebsocketEventPostRevealed {
|
|
eventPostJSON, ok := event.GetData()["post"].(string)
|
|
if !ok {
|
|
return false
|
|
}
|
|
err = json.Unmarshal([]byte(eventPostJSON), &eventPost)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
return eventPost.Id == createdPost.Id
|
|
}
|
|
default:
|
|
return false
|
|
}
|
|
return false
|
|
}, 5*time.Second, 100*time.Millisecond, "should have received post_revealed websocket event")
|
|
require.Equal(t, createdPost.Id, eventPost.Id)
|
|
require.Equal(t, "burn on read message", eventPost.Message)
|
|
require.NotNil(t, eventPost.Metadata)
|
|
require.NotZero(t, eventPost.Metadata.ExpireAt)
|
|
|
|
// Get the single post - verify it's revealed
|
|
singlePost, resp, err := th.SystemAdminClient.GetPost(context.Background(), createdPost.Id, "")
|
|
require.NoError(t, err)
|
|
CheckOKStatus(t, resp)
|
|
require.NotNil(t, singlePost)
|
|
require.Equal(t, "burn on read message", singlePost.Message)
|
|
require.NotNil(t, singlePost.Metadata)
|
|
require.NotZero(t, singlePost.Metadata.ExpireAt)
|
|
|
|
// Query for posts in channel again - verify this time it's revealed
|
|
postsAfterReveal, resp, err := th.SystemAdminClient.GetPostsForChannel(context.Background(), th.BasicChannel.Id, 0, 100, "", true, false)
|
|
require.NoError(t, err)
|
|
CheckOKStatus(t, resp)
|
|
require.NotNil(t, postsAfterReveal)
|
|
require.NotNil(t, postsAfterReveal.Posts[createdPost.Id])
|
|
revealedPostInChannel := postsAfterReveal.Posts[createdPost.Id]
|
|
require.Equal(t, "burn on read message", revealedPostInChannel.Message)
|
|
require.NotNil(t, revealedPostInChannel.Metadata)
|
|
require.NotZero(t, revealedPostInChannel.Metadata.ExpireAt)
|
|
})
|
|
|
|
t.Run("Create post send back pending post ID for post creator", func(t *testing.T) {
|
|
post := &model.Post{
|
|
ChannelId: th.BasicChannel.Id,
|
|
UserId: th.BasicUser.Id,
|
|
Message: "burn on read message",
|
|
Type: model.PostTypeBurnOnRead,
|
|
PendingPostId: model.NewId(),
|
|
}
|
|
|
|
createdPost, response, err := th.Client.CreatePost(context.Background(), post)
|
|
require.NoError(t, err)
|
|
CheckCreatedStatus(t, response)
|
|
require.NotNil(t, createdPost)
|
|
require.Equal(t, post.PendingPostId, createdPost.PendingPostId)
|
|
})
|
|
}
|
|
|
|
func TestBurnPost(t *testing.T) {
|
|
os.Setenv("MM_FEATUREFLAGS_BURNONREAD", "true")
|
|
t.Cleanup(func() {
|
|
os.Unsetenv("MM_FEATUREFLAGS_BURNONREAD")
|
|
})
|
|
|
|
th := SetupEnterprise(t).InitBasic(t)
|
|
|
|
th.LinkUserToTeam(t, th.SystemAdminUser, th.BasicTeam)
|
|
th.AddUserToChannel(t, th.SystemAdminUser, th.BasicChannel)
|
|
|
|
// Helper to create burn-on-read post
|
|
createBurnOnReadPost := func(client *model.Client4, channel *model.Channel) *model.Post {
|
|
post := &model.Post{
|
|
ChannelId: channel.Id,
|
|
Message: "burn on read message",
|
|
Type: model.PostTypeBurnOnRead,
|
|
}
|
|
post.AddProp(model.PostPropsExpireAt, model.GetMillis()+int64(model.DefaultExpirySeconds*1000))
|
|
|
|
createdPost, resp, err := client.CreatePost(context.Background(), post)
|
|
require.NoError(t, err)
|
|
CheckCreatedStatus(t, resp)
|
|
require.NotNil(t, createdPost)
|
|
return createdPost
|
|
}
|
|
|
|
// Helper to create and login second user
|
|
createSecondUser := func(channel *model.Channel) (*model.User, *model.Client4) {
|
|
user2 := th.CreateUser(t)
|
|
th.LinkUserToTeam(t, user2, th.BasicTeam)
|
|
if channel != nil {
|
|
th.AddUserToChannel(t, user2, channel)
|
|
}
|
|
client2 := th.CreateClient()
|
|
_, _, err := client2.Login(context.Background(), user2.Email, user2.Password)
|
|
require.NoError(t, err)
|
|
|
|
t.Cleanup(func() {
|
|
_, err = client2.Logout(context.Background())
|
|
require.NoError(t, err)
|
|
})
|
|
return user2, client2
|
|
}
|
|
|
|
t.Run("feature not enabled, burn post allowed", func(t *testing.T) {
|
|
enableBurnOnReadFeature(th)
|
|
post := createBurnOnReadPost(th.SystemAdminClient, th.BasicChannel)
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
cfg.ServiceSettings.EnableBurnOnRead = model.NewPointer(false)
|
|
})
|
|
|
|
_, resp, err := th.Client.RevealPost(context.Background(), post.Id)
|
|
require.NoError(t, err)
|
|
CheckOKStatus(t, resp)
|
|
resp, err = th.Client.BurnPost(context.Background(), post.Id)
|
|
require.NoError(t, err)
|
|
CheckOKStatus(t, resp)
|
|
})
|
|
|
|
th.TestForRegularAndSystemAdminClients(t, func(t *testing.T, client *model.Client4) {
|
|
enableBurnOnReadFeature(th)
|
|
|
|
regularPost := &model.Post{
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "regular message",
|
|
}
|
|
createdPost, resp, err := th.Client.CreatePost(context.Background(), regularPost)
|
|
require.NoError(t, err)
|
|
CheckCreatedStatus(t, resp)
|
|
|
|
resp, err = client.BurnPost(context.Background(), createdPost.Id)
|
|
require.Error(t, err)
|
|
CheckBadRequestStatus(t, resp)
|
|
CheckErrorID(t, err, "app.burn_post.not_burn_on_read.app_error")
|
|
}, "burn regular post")
|
|
|
|
th.TestForRegularAndSystemAdminClients(t, func(t *testing.T, client *model.Client4) {
|
|
enableBurnOnReadFeature(th)
|
|
|
|
resp, err := client.BurnPost(context.Background(), model.NewId())
|
|
require.Error(t, err)
|
|
CheckNotFoundStatus(t, resp)
|
|
}, "burn non-existing post")
|
|
|
|
th.TestForRegularAndSystemAdminClients(t, func(t *testing.T, client *model.Client4) {
|
|
enableBurnOnReadFeature(th)
|
|
|
|
post := createBurnOnReadPost(client, th.BasicChannel)
|
|
|
|
resp, err := client.BurnPost(context.Background(), post.Id)
|
|
require.NoError(t, err)
|
|
CheckOKStatus(t, resp)
|
|
|
|
// Verify post is permanently deleted
|
|
_, resp, err = client.GetPost(context.Background(), post.Id, "")
|
|
require.Error(t, err)
|
|
CheckNotFoundStatus(t, resp)
|
|
}, "author burns own post - permanently deleted")
|
|
|
|
th.TestForRegularAndSystemAdminClients(t, func(t *testing.T, client *model.Client4) {
|
|
enableBurnOnReadFeature(th)
|
|
_, client2 := createSecondUser(th.BasicChannel)
|
|
|
|
post := createBurnOnReadPost(client2, th.BasicChannel)
|
|
|
|
resp, err := client.BurnPost(context.Background(), post.Id)
|
|
require.Error(t, err)
|
|
CheckBadRequestStatus(t, resp)
|
|
CheckErrorID(t, err, "app.burn_post.not_revealed.app_error")
|
|
}, "non-author burns post without read receipt")
|
|
|
|
th.TestForRegularAndSystemAdminClients(t, func(t *testing.T, client *model.Client4) {
|
|
enableBurnOnReadFeature(th)
|
|
_, client2 := createSecondUser(th.BasicChannel)
|
|
post := createBurnOnReadPost(client2, th.BasicChannel)
|
|
|
|
// Create websocket client to receive burn event
|
|
wsClient := th.CreateConnectedWebSocketClientWithClient(t, client)
|
|
|
|
// Create expired read receipt
|
|
revealedPost, resp, err := client.RevealPost(context.Background(), post.Id)
|
|
require.NoError(t, err)
|
|
CheckOKStatus(t, resp)
|
|
require.NotNil(t, revealedPost)
|
|
|
|
resp, err = client.BurnPost(context.Background(), post.Id)
|
|
require.NoError(t, err)
|
|
CheckOKStatus(t, resp)
|
|
|
|
userID := th.BasicUser.Id
|
|
if client == th.SystemAdminClient {
|
|
userID = th.SystemAdminUser.Id
|
|
}
|
|
|
|
// Verify receipt ExpireAt is unchanged (no-op)
|
|
receipt, err := th.App.Srv().Store().ReadReceipt().Get(th.Context, post.Id, userID)
|
|
require.NoError(t, err)
|
|
require.LessOrEqual(t, receipt.ExpireAt, revealedPost.Metadata.ExpireAt)
|
|
|
|
// Verify websocket client receives the burn event
|
|
var eventPostID string
|
|
require.Eventually(t, func() bool {
|
|
select {
|
|
case event := <-wsClient.EventChannel:
|
|
if event.EventType() == model.WebsocketEventPostBurned {
|
|
var ok bool
|
|
eventPostID, ok = event.GetData()["post_id"].(string)
|
|
if !ok {
|
|
return false
|
|
}
|
|
return eventPostID == post.Id
|
|
}
|
|
default:
|
|
return false
|
|
}
|
|
return false
|
|
}, 5*time.Second, 100*time.Millisecond, "should have received post_burned websocket event")
|
|
require.Equal(t, post.Id, eventPostID)
|
|
}, "non-author burns post with expired read receipt")
|
|
|
|
th.TestForRegularAndSystemAdminClients(t, func(t *testing.T, client *model.Client4) {
|
|
enableBurnOnReadFeature(th)
|
|
|
|
_, client2 := createSecondUser(nil)
|
|
|
|
privateChannel, resp, err := client2.CreateChannel(context.Background(), &model.Channel{
|
|
TeamId: th.BasicTeam.Id,
|
|
Type: model.ChannelTypePrivate,
|
|
Name: GenerateTestChannelName(),
|
|
DisplayName: "Private Channel",
|
|
})
|
|
require.NoError(t, err)
|
|
CheckCreatedStatus(t, resp)
|
|
|
|
post := &model.Post{
|
|
ChannelId: privateChannel.Id,
|
|
Message: "burn on read message",
|
|
Type: model.PostTypeBurnOnRead,
|
|
}
|
|
post.AddProp(model.PostPropsExpireAt, model.GetMillis()+int64(model.DefaultExpirySeconds*1000))
|
|
|
|
createdPost, resp, err := client2.CreatePost(context.Background(), post)
|
|
require.NoError(t, err)
|
|
CheckCreatedStatus(t, resp)
|
|
|
|
resp, err = client.BurnPost(context.Background(), createdPost.Id)
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
}, "user without channel access")
|
|
|
|
t.Run("unauthorized access", func(t *testing.T) {
|
|
enableBurnOnReadFeature(th)
|
|
|
|
post := createBurnOnReadPost(th.Client, th.BasicChannel)
|
|
|
|
// Create unauthenticated client
|
|
unauthClient := th.CreateClient()
|
|
resp, err := unauthClient.BurnPost(context.Background(), post.Id)
|
|
require.Error(t, err)
|
|
CheckUnauthorizedStatus(t, resp)
|
|
})
|
|
}
|