mirror of
https://github.com/hanzoai/team.git
synced 2026-09-30 01:57:35 +00:00
* Remove system_secure_connection_manager role The dedicated role for delegating secure connection management is no longer needed. The manage_secure_connections permission remains and continues to be granted to system admins via AllPermissions. Removes the role definition, migration, permissions migration, UI components, i18n strings, and all associated tests across server, webapp, and e2e-tests.
1731 lines
58 KiB
Go
1731 lines
58 KiB
Go
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
package app
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"sort"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/mattermost/mattermost/server/v8/channels/testlib"
|
|
|
|
"github.com/mattermost/mattermost/server/public/shared/mlog"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/mattermost/mattermost/server/public/model"
|
|
"github.com/mattermost/mattermost/server/v8/channels/app/imports"
|
|
"github.com/mattermost/mattermost/server/v8/channels/utils"
|
|
"github.com/mattermost/mattermost/server/v8/channels/utils/fileutils"
|
|
)
|
|
|
|
func TestReactionsOfPost(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
post := th.BasicPost
|
|
post.HasReactions = true
|
|
th.BasicUser2.DeleteAt = 1234
|
|
reactionObject := model.Reaction{
|
|
UserId: th.BasicUser.Id,
|
|
PostId: post.Id,
|
|
EmojiName: "smile",
|
|
CreateAt: model.GetMillis(),
|
|
}
|
|
reactionObjectDeleted := model.Reaction{
|
|
UserId: th.BasicUser2.Id,
|
|
PostId: post.Id,
|
|
EmojiName: "smile",
|
|
CreateAt: model.GetMillis(),
|
|
}
|
|
|
|
_, err := th.App.SaveReactionForPost(th.Context, &reactionObject)
|
|
require.Nil(t, err)
|
|
_, err = th.App.SaveReactionForPost(th.Context, &reactionObjectDeleted)
|
|
require.Nil(t, err)
|
|
reactionsOfPost, err := th.App.BuildPostReactions(th.Context, post.Id)
|
|
require.Nil(t, err)
|
|
|
|
assert.Equal(t, reactionObject.EmojiName, *(*reactionsOfPost)[0].EmojiName)
|
|
}
|
|
|
|
func TestExportUserNotifyProps(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := SetupWithStoreMock(t)
|
|
|
|
userNotifyProps := model.StringMap{
|
|
model.DesktopNotifyProp: model.UserNotifyAll,
|
|
model.DesktopSoundNotifyProp: "true",
|
|
model.EmailNotifyProp: "true",
|
|
model.PushNotifyProp: model.UserNotifyAll,
|
|
model.PushStatusNotifyProp: model.StatusOnline,
|
|
model.ChannelMentionsNotifyProp: "true",
|
|
model.CommentsNotifyProp: model.CommentsNotifyRoot,
|
|
model.MentionKeysNotifyProp: "valid,misc",
|
|
}
|
|
|
|
exportNotifyProps := th.App.buildUserNotifyProps(userNotifyProps)
|
|
|
|
require.Equal(t, userNotifyProps[model.DesktopNotifyProp], *exportNotifyProps.Desktop)
|
|
require.Equal(t, userNotifyProps[model.DesktopSoundNotifyProp], *exportNotifyProps.DesktopSound)
|
|
require.Equal(t, userNotifyProps[model.EmailNotifyProp], *exportNotifyProps.Email)
|
|
require.Equal(t, userNotifyProps[model.PushNotifyProp], *exportNotifyProps.Mobile)
|
|
require.Equal(t, userNotifyProps[model.PushStatusNotifyProp], *exportNotifyProps.MobilePushStatus)
|
|
require.Equal(t, userNotifyProps[model.ChannelMentionsNotifyProp], *exportNotifyProps.ChannelTrigger)
|
|
require.Equal(t, userNotifyProps[model.CommentsNotifyProp], *exportNotifyProps.CommentsTrigger)
|
|
require.Equal(t, userNotifyProps[model.MentionKeysNotifyProp], *exportNotifyProps.MentionKeys)
|
|
}
|
|
|
|
func TestExportUserChannels(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
channel := th.BasicChannel
|
|
user := th.BasicUser
|
|
team := th.BasicTeam
|
|
channelName := channel.Name
|
|
notifyProps := model.StringMap{
|
|
model.DesktopNotifyProp: model.UserNotifyAll,
|
|
model.PushNotifyProp: model.UserNotifyNone,
|
|
}
|
|
preference := model.Preference{
|
|
UserId: user.Id,
|
|
Category: model.PreferenceCategoryFavoriteChannel,
|
|
Name: channel.Id,
|
|
Value: "true",
|
|
}
|
|
|
|
_, appErr := th.App.MarkChannelsAsViewed(th.Context, []string{th.BasicPost.ChannelId}, user.Id, "", true, th.App.IsCRTEnabledForUser(th.Context, user.Id))
|
|
require.Nil(t, appErr)
|
|
|
|
var preferences model.Preferences
|
|
preferences = append(preferences, preference)
|
|
err := th.App.Srv().Store().Preference().Save(preferences)
|
|
require.NoError(t, err)
|
|
|
|
_, appErr = th.App.UpdateChannelMemberNotifyProps(th.Context, notifyProps, channel.Id, user.Id)
|
|
require.Nil(t, appErr)
|
|
exportData, appErr := th.App.buildUserChannelMemberships(th.Context, user.Id, team.Id, false)
|
|
require.Nil(t, appErr)
|
|
assert.Equal(t, len(*exportData), 3)
|
|
for _, data := range *exportData {
|
|
if *data.Name == channelName {
|
|
assert.Equal(t, "all", *data.NotifyProps.Desktop)
|
|
assert.Equal(t, "none", *data.NotifyProps.Mobile)
|
|
assert.Equal(t, "all", *data.NotifyProps.MarkUnread) // default value
|
|
assert.True(t, *data.Favorite)
|
|
assert.NotEqualValues(t, 0, *data.LastViewedAt)
|
|
assert.NotEqualValues(t, 0, *data.MsgCount)
|
|
} else { // default values
|
|
assert.Equal(t, "default", *data.NotifyProps.Desktop)
|
|
assert.Equal(t, "default", *data.NotifyProps.Mobile)
|
|
assert.Equal(t, "all", *data.NotifyProps.MarkUnread)
|
|
assert.False(t, *data.Favorite)
|
|
assert.EqualValues(t, 0, *data.LastViewedAt)
|
|
assert.EqualValues(t, 0, *data.MsgCount)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCopyEmojiImages(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := SetupWithStoreMock(t)
|
|
|
|
emoji := &model.Emoji{
|
|
Id: model.NewId(),
|
|
}
|
|
|
|
// Creating a dir named `exported_emoji_test` in the root of the repo
|
|
pathToDir := "../exported_emoji_test"
|
|
|
|
err := os.Mkdir(pathToDir, 0777)
|
|
require.NoError(t, err)
|
|
defer os.RemoveAll(pathToDir)
|
|
|
|
filePath := "../data/emoji/" + emoji.Id
|
|
emojiImagePath := filePath + "/image"
|
|
|
|
_, err = os.Stat(filePath)
|
|
if os.IsNotExist(err) {
|
|
err = os.MkdirAll(filePath, 0777)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
// Creating a file with the name `image` to copy it to `exported_emoji_test`
|
|
_, err = os.OpenFile(filePath+"/image", os.O_RDONLY|os.O_CREATE, 0777)
|
|
require.NoError(t, err)
|
|
defer os.RemoveAll(filePath)
|
|
|
|
copyError := th.App.copyEmojiImages(th.Context, emoji.Id, emojiImagePath, pathToDir)
|
|
require.NoError(t, copyError)
|
|
|
|
_, err = os.Stat(pathToDir + "/" + emoji.Id + "/image")
|
|
require.False(t, os.IsNotExist(err), "File should exist ")
|
|
}
|
|
|
|
func TestExportCustomEmoji(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t)
|
|
|
|
filePath := "../demo.json"
|
|
|
|
fileWriter, err := os.Create(filePath)
|
|
require.NoError(t, err)
|
|
defer os.Remove(filePath)
|
|
|
|
dirNameToExportEmoji := "exported_emoji_test"
|
|
defer os.RemoveAll("../" + dirNameToExportEmoji)
|
|
|
|
outPath, err := filepath.Abs(filePath)
|
|
require.NoError(t, err)
|
|
|
|
_, appErr := th.App.exportCustomEmoji(th.Context, nil, fileWriter, outPath, dirNameToExportEmoji, false)
|
|
require.Nil(t, appErr, "should not have failed")
|
|
}
|
|
|
|
func TestExportAllUsers(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th1 := Setup(t)
|
|
|
|
// Adding a user and deactivating it to check whether it gets included in bulk export
|
|
user := th1.CreateUser(t)
|
|
_, err := th1.App.UpdateActive(th1.Context, user, false)
|
|
require.Nil(t, err)
|
|
|
|
var b bytes.Buffer
|
|
err = th1.App.BulkExport(th1.Context, &b, "somePath", nil, model.BulkExportOpts{})
|
|
require.Nil(t, err)
|
|
|
|
var th2 *TestHelper
|
|
if mainHelper.Options.RunParallel {
|
|
th1.Store.DropAllTables()
|
|
th2 = th1
|
|
} else {
|
|
th2 = Setup(t)
|
|
}
|
|
|
|
i, err := th2.App.BulkImport(th2.Context, &b, nil, false, 5)
|
|
assert.Nil(t, err)
|
|
assert.EqualValues(t, 0, i)
|
|
|
|
users1, err := th1.App.GetUsersFromProfiles(&model.UserGetOptions{
|
|
Page: 0,
|
|
PerPage: 10,
|
|
})
|
|
assert.Nil(t, err)
|
|
users2, err := th2.App.GetUsersFromProfiles(&model.UserGetOptions{
|
|
Page: 0,
|
|
PerPage: 10,
|
|
})
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, len(users1), len(users2))
|
|
assert.ElementsMatch(t, users1, users2)
|
|
|
|
// Checking whether deactivated users were included in bulk export
|
|
deletedUsers1, err := th1.App.GetUsersFromProfiles(&model.UserGetOptions{
|
|
Inactive: true,
|
|
Page: 0,
|
|
PerPage: 10,
|
|
})
|
|
assert.Nil(t, err)
|
|
deletedUsers2, err := th1.App.GetUsersFromProfiles(&model.UserGetOptions{
|
|
Inactive: true,
|
|
Page: 0,
|
|
PerPage: 10,
|
|
})
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, len(deletedUsers1), len(deletedUsers2))
|
|
assert.ElementsMatch(t, deletedUsers1, deletedUsers2)
|
|
}
|
|
|
|
func TestExportAllBots(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th1 := Setup(t)
|
|
|
|
u := th1.CreateUser(t)
|
|
bot, err := th1.App.CreateBot(th1.Context, &model.Bot{
|
|
Username: "bot_1",
|
|
DisplayName: model.NewId(),
|
|
OwnerId: u.Id,
|
|
})
|
|
require.Nil(t, err)
|
|
|
|
var b bytes.Buffer
|
|
err = th1.App.BulkExport(th1.Context, &b, "somePath", nil, model.BulkExportOpts{})
|
|
require.Nil(t, err)
|
|
|
|
th2 := Setup(t)
|
|
|
|
i, err := th2.App.BulkImport(th2.Context, &b, nil, false, 5)
|
|
require.Nil(t, err)
|
|
assert.EqualValues(t, 0, i)
|
|
|
|
u, err = th2.App.GetUserByUsername(u.Username)
|
|
require.Nil(t, err)
|
|
|
|
bots, err := th2.App.GetBots(th2.Context, &model.BotGetOptions{
|
|
OwnerId: u.Id,
|
|
Page: 0,
|
|
PerPage: 10,
|
|
})
|
|
require.Nil(t, err)
|
|
require.Len(t, bots, 1)
|
|
assert.Equal(t, bot.Username, bots[0].Username)
|
|
}
|
|
|
|
func TestExportDMChannel(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
t.Run("Export a DM channel to another server", func(t *testing.T) {
|
|
th1 := Setup(t).InitBasic(t)
|
|
|
|
// DM Channel
|
|
ch := th1.CreateDmChannel(t, th1.BasicUser2)
|
|
|
|
err := th1.App.Srv().Store().Preference().Save(model.Preferences{
|
|
{
|
|
UserId: th1.BasicUser2.Id,
|
|
Category: model.PreferenceCategoryFavoriteChannel,
|
|
Name: ch.Id,
|
|
Value: "true",
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
var b bytes.Buffer
|
|
appErr := th1.App.BulkExport(th1.Context, &b, "somePath", nil, model.BulkExportOpts{})
|
|
require.Nil(t, appErr)
|
|
|
|
channels, nErr := th1.App.Srv().Store().Channel().GetAllDirectChannelsForExportAfter(1000, "00000000", false)
|
|
require.NoError(t, nErr)
|
|
assert.Equal(t, 1, len(channels))
|
|
|
|
th2 := Setup(t)
|
|
|
|
channels, nErr = th2.App.Srv().Store().Channel().GetAllDirectChannelsForExportAfter(1000, "00000000", false)
|
|
require.NoError(t, nErr)
|
|
assert.Equal(t, 0, len(channels))
|
|
|
|
// import the exported channel
|
|
i, appErr := th2.App.BulkImport(th2.Context, &b, nil, false, 5)
|
|
require.Nil(t, appErr)
|
|
assert.Equal(t, 0, i)
|
|
|
|
// Ensure the Members of the imported DM channel is the same was from the exported
|
|
channels, nErr = th2.App.Srv().Store().Channel().GetAllDirectChannelsForExportAfter(1000, "00000000", false)
|
|
require.NoError(t, nErr)
|
|
require.Len(t, channels, 1)
|
|
require.Len(t, channels[0].Members, 2)
|
|
assert.ElementsMatch(t, []string{th1.BasicUser.Username, th1.BasicUser2.Username}, []string{channels[0].Members[0].Username, channels[0].Members[1].Username})
|
|
|
|
// Verify the users were imported and get their IDs in th2
|
|
_, appErr = th2.App.GetUserByUsername(th1.BasicUser.Username)
|
|
require.Nil(t, appErr)
|
|
importedUser2, appErr := th2.App.GetUserByUsername(th1.BasicUser2.Username)
|
|
require.Nil(t, appErr)
|
|
|
|
// Ensure the favorited channel was retained for the imported user
|
|
fav, nErr := th2.App.Srv().Store().Preference().Get(importedUser2.Id, model.PreferenceCategoryFavoriteChannel, channels[0].Id)
|
|
require.NoError(t, nErr)
|
|
require.NotNil(t, fav)
|
|
require.Equal(t, "true", fav.Value)
|
|
})
|
|
|
|
t.Run("Invalid DM channel export", func(t *testing.T) {
|
|
th1 := Setup(t).InitBasic(t)
|
|
|
|
// DM Channel
|
|
th1.CreateDmChannel(t, th1.BasicUser2)
|
|
|
|
channels, nErr := th1.App.Srv().Store().Channel().GetAllDirectChannelsForExportAfter(1000, "00000000", false)
|
|
require.NoError(t, nErr)
|
|
assert.Equal(t, 1, len(channels))
|
|
|
|
appErr := th1.App.PermanentDeleteUser(th1.Context, th1.BasicUser2)
|
|
require.Nil(t, appErr)
|
|
appErr = th1.App.PermanentDeleteUser(th1.Context, th1.BasicUser)
|
|
require.Nil(t, appErr)
|
|
|
|
var b bytes.Buffer
|
|
appErr = th1.App.BulkExport(th1.Context, &b, "somePath", nil, model.BulkExportOpts{})
|
|
require.Nil(t, appErr)
|
|
|
|
th2 := Setup(t).InitBasic(t)
|
|
|
|
// import the exported channel
|
|
_, appErr = th2.App.BulkImport(th2.Context, &b, nil, true, 5)
|
|
require.Nil(t, appErr)
|
|
|
|
channels, nErr = th2.App.Srv().Store().Channel().GetAllDirectChannelsForExportAfter(1000, "00000000", false)
|
|
require.NoError(t, nErr)
|
|
assert.Empty(t, channels)
|
|
})
|
|
|
|
t.Run("Should not export DM channel if other user is permanently deleted", func(t *testing.T) {
|
|
th1 := Setup(t).InitBasic(t)
|
|
|
|
// Create a DM Channel with another user
|
|
dmc1 := th1.CreateDmChannel(t, th1.BasicUser2)
|
|
th1.CreatePost(t, dmc1)
|
|
|
|
// Create a DM Channel with self
|
|
dmc2 := th1.CreateDmChannel(t, th1.BasicUser)
|
|
th1.CreatePost(t, dmc2)
|
|
|
|
channels, nErr := th1.App.Srv().Store().Channel().GetAllDirectChannelsForExportAfter(1000, "00000000", false)
|
|
require.NoError(t, nErr)
|
|
assert.Equal(t, 2, len(channels))
|
|
|
|
// Permanentley delete other user
|
|
appErr := th1.App.PermanentDeleteUser(th1.Context, th1.BasicUser2)
|
|
require.Nil(t, appErr)
|
|
|
|
var b bytes.Buffer
|
|
err := th1.App.BulkExport(th1.Context, &b, "somePath", nil, model.BulkExportOpts{})
|
|
require.Nil(t, err)
|
|
|
|
th2 := Setup(t).InitBasic(t)
|
|
|
|
// import the exported channel
|
|
_, err = th2.App.BulkImport(th2.Context, &b, nil, false, 5)
|
|
require.Nil(t, err)
|
|
|
|
channels, nErr = th2.App.Srv().Store().Channel().GetAllDirectChannelsForExportAfter(1000, "00000000", false)
|
|
require.NoError(t, nErr)
|
|
assert.Equal(t, 1, len(channels))
|
|
|
|
// Ensure the posts of the deleted DM channel do not leak to the self-DM channel
|
|
posts, nErr := th2.App.Srv().Store().Post().GetPosts(th2.Context,
|
|
model.GetPostsOptions{
|
|
ChannelId: channels[0].Id,
|
|
PerPage: 1000,
|
|
IncludeDeleted: true,
|
|
}, false, nil)
|
|
require.NoError(t, nErr)
|
|
assert.Equal(t, 1, len(posts.Posts))
|
|
})
|
|
}
|
|
|
|
func TestExportDMChannelToSelf(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th1 := Setup(t).InitBasic(t)
|
|
|
|
// DM Channel with self (me channel)
|
|
th1.CreateDmChannel(t, th1.BasicUser)
|
|
|
|
var b bytes.Buffer
|
|
err := th1.App.BulkExport(th1.Context, &b, "somePath", nil, model.BulkExportOpts{})
|
|
require.Nil(t, err)
|
|
|
|
channels, nErr := th1.App.Srv().Store().Channel().GetAllDirectChannelsForExportAfter(1000, "00000000", false)
|
|
require.NoError(t, nErr)
|
|
assert.Equal(t, 1, len(channels))
|
|
|
|
th2 := Setup(t)
|
|
|
|
channels, nErr = th2.App.Srv().Store().Channel().GetAllDirectChannelsForExportAfter(1000, "00000000", false)
|
|
require.NoError(t, nErr)
|
|
assert.Equal(t, 0, len(channels))
|
|
|
|
// import the exported channel
|
|
i, err := th2.App.BulkImport(th2.Context, &b, nil, false, 5)
|
|
assert.Nil(t, err)
|
|
assert.EqualValues(t, 0, i)
|
|
|
|
channels, nErr = th2.App.Srv().Store().Channel().GetAllDirectChannelsForExportAfter(1000, "00000000", false)
|
|
require.NoError(t, nErr)
|
|
assert.Equal(t, 1, len(channels))
|
|
assert.Equal(t, 1, len((channels[0].Members)))
|
|
assert.Equal(t, th1.BasicUser.Username, channels[0].Members[0].Username)
|
|
}
|
|
|
|
func TestExportGMChannel(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th1 := Setup(t).InitBasic(t)
|
|
|
|
user1 := th1.CreateUser(t)
|
|
th1.LinkUserToTeam(t, user1, th1.BasicTeam)
|
|
user2 := th1.CreateUser(t)
|
|
th1.LinkUserToTeam(t, user2, th1.BasicTeam)
|
|
|
|
// GM Channel
|
|
th1.CreateGroupChannel(t, user1, user2)
|
|
|
|
var b bytes.Buffer
|
|
err := th1.App.BulkExport(th1.Context, &b, "somePath", nil, model.BulkExportOpts{})
|
|
require.Nil(t, err)
|
|
|
|
channels, nErr := th1.App.Srv().Store().Channel().GetAllDirectChannelsForExportAfter(1000, "00000000", false)
|
|
require.NoError(t, nErr)
|
|
assert.Equal(t, 1, len(channels))
|
|
|
|
th2 := Setup(t)
|
|
|
|
channels, nErr = th2.App.Srv().Store().Channel().GetAllDirectChannelsForExportAfter(1000, "00000000", false)
|
|
require.NoError(t, nErr)
|
|
assert.Equal(t, 0, len(channels))
|
|
}
|
|
|
|
func TestExportGMandDMChannels(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th1 := Setup(t).InitBasic(t)
|
|
|
|
// DM Channel
|
|
th1.CreateDmChannel(t, th1.BasicUser2)
|
|
|
|
user1 := th1.CreateUser(t)
|
|
th1.LinkUserToTeam(t, user1, th1.BasicTeam)
|
|
user2 := th1.CreateUser(t)
|
|
th1.LinkUserToTeam(t, user2, th1.BasicTeam)
|
|
|
|
// GM Channel
|
|
th1.CreateGroupChannel(t, user1, user2)
|
|
|
|
var b bytes.Buffer
|
|
err := th1.App.BulkExport(th1.Context, &b, "somePath", nil, model.BulkExportOpts{})
|
|
require.Nil(t, err)
|
|
|
|
channels, nErr := th1.App.Srv().Store().Channel().GetAllDirectChannelsForExportAfter(1000, "00000000", false)
|
|
require.NoError(t, nErr)
|
|
assert.Equal(t, 2, len(channels))
|
|
|
|
th2 := Setup(t)
|
|
|
|
channels, nErr = th2.App.Srv().Store().Channel().GetAllDirectChannelsForExportAfter(1000, "00000000", false)
|
|
require.NoError(t, nErr)
|
|
assert.Equal(t, 0, len(channels))
|
|
|
|
// import the exported channel
|
|
i, err := th2.App.BulkImport(th2.Context, &b, nil, false, 5)
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, 0, i)
|
|
|
|
// Ensure the Members of the imported GM channel is the same was from the exported
|
|
channels, nErr = th2.App.Srv().Store().Channel().GetAllDirectChannelsForExportAfter(1000, "00000000", false)
|
|
require.NoError(t, nErr)
|
|
|
|
// Adding some determinism so its possible to assert on slice index
|
|
sort.Slice(channels, func(i, j int) bool { return channels[i].Type > channels[j].Type })
|
|
assert.Equal(t, 2, len(channels))
|
|
assert.ElementsMatch(t, []string{th1.BasicUser.Username, user1.Username, user2.Username}, []string{channels[0].Members[0].Username, channels[0].Members[1].Username, channels[0].Members[2].Username})
|
|
assert.ElementsMatch(t, []string{th1.BasicUser.Username, th1.BasicUser2.Username}, []string{channels[1].Members[0].Username, channels[1].Members[1].Username})
|
|
}
|
|
|
|
func TestExportDMandGMPost(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th1 := Setup(t).InitBasic(t)
|
|
|
|
// DM Channel
|
|
dmChannel := th1.CreateDmChannel(t, th1.BasicUser2)
|
|
dmMembers := []string{th1.BasicUser.Username, th1.BasicUser2.Username}
|
|
|
|
user1 := th1.CreateUser(t)
|
|
th1.LinkUserToTeam(t, user1, th1.BasicTeam)
|
|
user2 := th1.CreateUser(t)
|
|
th1.LinkUserToTeam(t, user2, th1.BasicTeam)
|
|
|
|
// GM Channel
|
|
gmChannel := th1.CreateGroupChannel(t, user1, user2)
|
|
gmMembers := []string{th1.BasicUser.Username, user1.Username, user2.Username}
|
|
|
|
// DM posts
|
|
p1 := &model.Post{
|
|
ChannelId: dmChannel.Id,
|
|
Message: "aa" + model.NewId() + "a",
|
|
UserId: th1.BasicUser.Id,
|
|
}
|
|
_, _, appErr := th1.App.CreatePost(th1.Context, p1, dmChannel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, appErr)
|
|
|
|
p2 := &model.Post{
|
|
ChannelId: dmChannel.Id,
|
|
Message: "bb" + model.NewId() + "a",
|
|
UserId: th1.BasicUser.Id,
|
|
}
|
|
_, _, appErr = th1.App.CreatePost(th1.Context, p2, dmChannel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, appErr)
|
|
|
|
// GM posts
|
|
p3 := &model.Post{
|
|
ChannelId: gmChannel.Id,
|
|
Message: "cc" + model.NewId() + "a",
|
|
UserId: th1.BasicUser.Id,
|
|
}
|
|
_, _, appErr = th1.App.CreatePost(th1.Context, p3, gmChannel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, appErr)
|
|
|
|
p4 := &model.Post{
|
|
ChannelId: gmChannel.Id,
|
|
Message: "dd" + model.NewId() + "a",
|
|
UserId: th1.BasicUser.Id,
|
|
}
|
|
_, _, appErr = th1.App.CreatePost(th1.Context, p4, gmChannel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, appErr)
|
|
|
|
posts, err := th1.App.Srv().Store().Post().GetDirectPostParentsForExportAfter(1000, "0000000", false)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 4, len(posts))
|
|
|
|
var b bytes.Buffer
|
|
appErr = th1.App.BulkExport(th1.Context, &b, "somePath", nil, model.BulkExportOpts{})
|
|
require.Nil(t, appErr)
|
|
|
|
th2 := Setup(t)
|
|
|
|
posts, err = th2.App.Srv().Store().Post().GetDirectPostParentsForExportAfter(1000, "0000000", false)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 0, len(posts))
|
|
|
|
// import the exported posts
|
|
i, appErr := th2.App.BulkImport(th2.Context, &b, nil, false, 5)
|
|
assert.Nil(t, appErr)
|
|
assert.Equal(t, 0, i)
|
|
|
|
posts, err = th2.App.Srv().Store().Post().GetDirectPostParentsForExportAfter(1000, "0000000", false)
|
|
require.NoError(t, err)
|
|
|
|
// Adding some determinism so its possible to assert on slice index
|
|
sort.Slice(posts, func(i, j int) bool { return posts[i].Message > posts[j].Message })
|
|
assert.Equal(t, 4, len(posts))
|
|
assert.ElementsMatch(t, gmMembers, *posts[0].ChannelMembers)
|
|
assert.ElementsMatch(t, gmMembers, *posts[1].ChannelMembers)
|
|
assert.ElementsMatch(t, dmMembers, *posts[2].ChannelMembers)
|
|
assert.ElementsMatch(t, dmMembers, *posts[3].ChannelMembers)
|
|
}
|
|
|
|
func TestExportPostWithProps(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th1 := Setup(t).InitBasic(t)
|
|
|
|
attachments := []*model.MessageAttachment{{Footer: "footer"}}
|
|
|
|
// DM Channel
|
|
dmChannel := th1.CreateDmChannel(t, th1.BasicUser2)
|
|
dmMembers := []string{th1.BasicUser.Username, th1.BasicUser2.Username}
|
|
|
|
user1 := th1.CreateUser(t)
|
|
th1.LinkUserToTeam(t, user1, th1.BasicTeam)
|
|
user2 := th1.CreateUser(t)
|
|
th1.LinkUserToTeam(t, user2, th1.BasicTeam)
|
|
|
|
// GM Channel
|
|
gmChannel := th1.CreateGroupChannel(t, user1, user2)
|
|
gmMembers := []string{th1.BasicUser.Username, user1.Username, user2.Username}
|
|
|
|
// DM posts
|
|
p1 := &model.Post{
|
|
ChannelId: dmChannel.Id,
|
|
Message: "aa" + model.NewId() + "a",
|
|
Props: map[string]any{
|
|
model.PostPropsAttachments: attachments,
|
|
},
|
|
UserId: th1.BasicUser.Id,
|
|
}
|
|
_, _, appErr := th1.App.CreatePost(th1.Context, p1, dmChannel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, appErr)
|
|
|
|
p2 := &model.Post{
|
|
ChannelId: gmChannel.Id,
|
|
Message: "dd" + model.NewId() + "a",
|
|
Props: map[string]any{
|
|
model.PostPropsAttachments: attachments,
|
|
},
|
|
UserId: th1.BasicUser.Id,
|
|
}
|
|
_, _, appErr = th1.App.CreatePost(th1.Context, p2, gmChannel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, appErr)
|
|
|
|
posts, err := th1.App.Srv().Store().Post().GetDirectPostParentsForExportAfter(1000, "0000000", false)
|
|
require.NoError(t, err)
|
|
assert.Len(t, posts, 2)
|
|
require.NotEmpty(t, posts[0].Props)
|
|
require.NotEmpty(t, posts[1].Props)
|
|
|
|
var b bytes.Buffer
|
|
appErr = th1.App.BulkExport(th1.Context, &b, "somePath", nil, model.BulkExportOpts{})
|
|
require.Nil(t, appErr)
|
|
|
|
th2 := Setup(t)
|
|
|
|
posts, err = th2.App.Srv().Store().Post().GetDirectPostParentsForExportAfter(1000, "0000000", false)
|
|
require.NoError(t, err)
|
|
assert.Len(t, posts, 0)
|
|
|
|
// import the exported posts
|
|
i, appErr := th2.App.BulkImport(th2.Context, &b, nil, false, 5)
|
|
assert.Nil(t, appErr)
|
|
assert.Equal(t, 0, i)
|
|
|
|
posts, err = th2.App.Srv().Store().Post().GetDirectPostParentsForExportAfter(1000, "0000000", false)
|
|
require.NoError(t, err)
|
|
|
|
// Adding some determinism so its possible to assert on slice index
|
|
sort.Slice(posts, func(i, j int) bool { return posts[i].Message > posts[j].Message })
|
|
assert.Len(t, posts, 2)
|
|
assert.ElementsMatch(t, gmMembers, *posts[0].ChannelMembers)
|
|
assert.ElementsMatch(t, dmMembers, *posts[1].ChannelMembers)
|
|
assert.Contains(t, posts[0].Props[model.PostPropsAttachments].([]any)[0], "footer")
|
|
assert.Contains(t, posts[1].Props[model.PostPropsAttachments].([]any)[0], "footer")
|
|
}
|
|
|
|
func TestExportUserCustomStatus(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th1 := Setup(t).InitBasic(t)
|
|
|
|
cs := &model.CustomStatus{
|
|
Emoji: "palm_tree",
|
|
Text: "on a vacation",
|
|
Duration: "this_week",
|
|
ExpiresAt: time.Now().Add(24 * time.Hour),
|
|
}
|
|
appErr := th1.App.SetCustomStatus(th1.Context, th1.BasicUser.Id, cs)
|
|
require.Nil(t, appErr)
|
|
|
|
uname := th1.BasicUser.Username
|
|
|
|
var b bytes.Buffer
|
|
appErr = th1.App.BulkExport(th1.Context, &b, "somePath", nil, model.BulkExportOpts{})
|
|
require.Nil(t, appErr)
|
|
|
|
th2 := Setup(t)
|
|
|
|
i, appErr := th2.App.BulkImport(th2.Context, &b, nil, false, 1)
|
|
require.Nil(t, appErr)
|
|
assert.Equal(t, 0, i)
|
|
|
|
gotUser, err := th2.Server.Store().User().GetByUsername(uname)
|
|
require.NoError(t, err)
|
|
gotCs := gotUser.GetCustomStatus()
|
|
require.Equal(t, cs.Emoji, gotCs.Emoji)
|
|
require.Equal(t, cs.Text, gotCs.Text)
|
|
}
|
|
|
|
func TestExportDMPostWithSelf(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th1 := Setup(t).InitBasic(t)
|
|
|
|
// DM Channel with self (me channel)
|
|
dmChannel := th1.CreateDmChannel(t, th1.BasicUser)
|
|
|
|
th1.CreatePost(t, dmChannel)
|
|
|
|
var b bytes.Buffer
|
|
err := th1.App.BulkExport(th1.Context, &b, "somePath", nil, model.BulkExportOpts{})
|
|
require.Nil(t, err)
|
|
|
|
posts, nErr := th1.App.Srv().Store().Post().GetDirectPostParentsForExportAfter(1000, "0000000", false)
|
|
require.NoError(t, nErr)
|
|
assert.Equal(t, 1, len(posts))
|
|
|
|
th2 := Setup(t)
|
|
|
|
posts, nErr = th2.App.Srv().Store().Post().GetDirectPostParentsForExportAfter(1000, "0000000", false)
|
|
require.NoError(t, nErr)
|
|
assert.Equal(t, 0, len(posts))
|
|
|
|
// import the exported posts
|
|
i, err := th2.App.BulkImport(th2.Context, &b, nil, false, 5)
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, 0, i)
|
|
|
|
posts, nErr = th2.App.Srv().Store().Post().GetDirectPostParentsForExportAfter(1000, "0000000", false)
|
|
require.NoError(t, nErr)
|
|
assert.Equal(t, 1, len(posts))
|
|
assert.Equal(t, 1, len((*posts[0].ChannelMembers)))
|
|
assert.Equal(t, th1.BasicUser.Username, (*posts[0].ChannelMembers)[0])
|
|
}
|
|
|
|
func TestExportPostsWithThread(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th1 := Setup(t).InitBasic(t)
|
|
|
|
assertThreadFollowers := func(t *testing.T, b *bytes.Buffer, postCreateAt int64, userNames []string) {
|
|
scanner := bufio.NewScanner(b)
|
|
|
|
usersToAssert := make([]string, 0)
|
|
|
|
for scanner.Scan() {
|
|
var line imports.LineImportData
|
|
err := json.Unmarshal(scanner.Bytes(), &line)
|
|
require.NoError(t, err)
|
|
|
|
switch line.Type {
|
|
case "post":
|
|
postLine := line.Post
|
|
require.NotNil(t, postLine)
|
|
|
|
if postLine.CreateAt != nil && *postLine.CreateAt != postCreateAt {
|
|
continue
|
|
}
|
|
|
|
for _, follower := range *postLine.ThreadFollowers {
|
|
if follower.User == nil {
|
|
require.Fail(t, "follower.User is nil")
|
|
}
|
|
|
|
usersToAssert = append(usersToAssert, *follower.User)
|
|
}
|
|
case "direct_post":
|
|
postLine := line.DirectPost
|
|
require.NotNil(t, postLine)
|
|
|
|
if postLine.CreateAt != nil && *postLine.CreateAt != postCreateAt {
|
|
continue
|
|
}
|
|
|
|
for _, follower := range *postLine.ThreadFollowers {
|
|
if follower.User == nil {
|
|
require.Fail(t, "follower.User is nil")
|
|
}
|
|
|
|
usersToAssert = append(usersToAssert, *follower.User)
|
|
}
|
|
default:
|
|
continue
|
|
}
|
|
}
|
|
|
|
require.ElementsMatch(t, userNames, usersToAssert)
|
|
}
|
|
|
|
t.Run("Export thread followers for a thread (public channel)", func(t *testing.T) {
|
|
thread := th1.CreatePost(t, th1.BasicChannel)
|
|
_ = th1.CreatePostReply(t, thread)
|
|
|
|
appErr := th1.App.UpdateThreadFollowForUser(th1.BasicUser2.Id, th1.BasicTeam.Id, thread.Id, true)
|
|
require.Nil(t, appErr)
|
|
|
|
member1, appErr := th1.App.GetThreadMembershipForUser(th1.BasicUser.Id, thread.Id)
|
|
require.Nil(t, appErr)
|
|
require.NotNil(t, member1)
|
|
|
|
member2, appErr := th1.App.GetThreadMembershipForUser(th1.BasicUser2.Id, thread.Id)
|
|
require.Nil(t, appErr)
|
|
require.NotNil(t, member2)
|
|
|
|
var b bytes.Buffer
|
|
err := th1.App.BulkExport(th1.Context, &b, "somePath", nil, model.BulkExportOpts{})
|
|
require.Nil(t, err)
|
|
|
|
assertThreadFollowers(t, &b, thread.CreateAt, []string{th1.BasicUser.Username, th1.BasicUser2.Username})
|
|
})
|
|
|
|
t.Run("Export thread followers for a thread (direct messages)", func(t *testing.T) {
|
|
dmc := th1.CreateDmChannel(t, th1.BasicUser2)
|
|
|
|
thread := th1.CreatePost(t, dmc)
|
|
_ = th1.CreatePostReply(t, thread)
|
|
|
|
appErr := th1.App.UpdateThreadFollowForUser(th1.BasicUser2.Id, th1.BasicTeam.Id, thread.Id, true)
|
|
require.Nil(t, appErr)
|
|
|
|
member1, appErr := th1.App.GetThreadMembershipForUser(th1.BasicUser.Id, thread.Id)
|
|
require.Nil(t, appErr)
|
|
require.NotNil(t, member1)
|
|
|
|
member2, appErr := th1.App.GetThreadMembershipForUser(th1.BasicUser2.Id, thread.Id)
|
|
require.Nil(t, appErr)
|
|
require.NotNil(t, member2)
|
|
|
|
var b bytes.Buffer
|
|
err := th1.App.BulkExport(th1.Context, &b, "somePath", nil, model.BulkExportOpts{})
|
|
require.Nil(t, err)
|
|
assertThreadFollowers(t, &b, thread.CreateAt, []string{th1.BasicUser.Username, th1.BasicUser2.Username})
|
|
})
|
|
}
|
|
|
|
func TestExportFileWarnings(t *testing.T) {
|
|
testCases := []struct {
|
|
Description string
|
|
ConfigFunc func(cfg *model.Config)
|
|
}{
|
|
{
|
|
"local",
|
|
func(cfg *model.Config) {
|
|
cfg.FileSettings.DriverName = model.NewPointer(model.ImageDriverLocal)
|
|
},
|
|
},
|
|
{
|
|
"s3",
|
|
func(cfg *model.Config) {
|
|
s3Host := os.Getenv("CI_MINIO_HOST")
|
|
if s3Host == "" {
|
|
s3Host = "localhost"
|
|
}
|
|
|
|
s3Port := os.Getenv("CI_MINIO_PORT")
|
|
if s3Port == "" {
|
|
s3Port = "9000"
|
|
}
|
|
|
|
s3Endpoint := fmt.Sprintf("%s:%s", s3Host, s3Port)
|
|
cfg.FileSettings.DriverName = model.NewPointer(model.ImageDriverS3)
|
|
cfg.FileSettings.AmazonS3AccessKeyId = model.NewPointer(model.MinioAccessKey)
|
|
cfg.FileSettings.AmazonS3SecretAccessKey = model.NewPointer(model.MinioSecretKey)
|
|
cfg.FileSettings.AmazonS3Bucket = model.NewPointer(model.MinioBucket)
|
|
cfg.FileSettings.AmazonS3PathPrefix = model.NewPointer("")
|
|
cfg.FileSettings.AmazonS3Endpoint = model.NewPointer(s3Endpoint)
|
|
cfg.FileSettings.AmazonS3Region = model.NewPointer("")
|
|
cfg.FileSettings.AmazonS3SSL = model.NewPointer(false)
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, testCase := range testCases {
|
|
t.Run(testCase.Description, func(t *testing.T) {
|
|
th := Setup(t)
|
|
|
|
th.App.UpdateConfig(testCase.ConfigFunc)
|
|
|
|
// Create a buffer to capture logs
|
|
buffer := &mlog.Buffer{}
|
|
err := mlog.AddWriterTarget(th.TestLogger, buffer, true, mlog.StdAll...)
|
|
require.NoError(t, err)
|
|
|
|
testsDir, _ := fileutils.FindDir("tests")
|
|
dir, err := os.MkdirTemp("", "import_test")
|
|
require.NoError(t, err)
|
|
defer os.RemoveAll(dir)
|
|
|
|
extractImportFile := func(filePath string) *os.File {
|
|
importFile, err2 := os.Open(filePath)
|
|
require.NoError(t, err2)
|
|
defer importFile.Close()
|
|
|
|
info, err2 := importFile.Stat()
|
|
require.NoError(t, err2)
|
|
|
|
paths, err2 := utils.UnzipToPath(importFile, info.Size(), dir)
|
|
require.NoError(t, err2)
|
|
require.NotEmpty(t, paths)
|
|
|
|
jsonFile, err2 := os.Open(filepath.Join(dir, "import.jsonl"))
|
|
require.NoError(t, err2)
|
|
|
|
return jsonFile
|
|
}
|
|
|
|
jsonFile := extractImportFile(filepath.Join(testsDir, "import_test.zip"))
|
|
defer jsonFile.Close()
|
|
|
|
_, appErr := th.App.BulkImportWithPath(th.Context, jsonFile, nil, false, true, 1, dir)
|
|
require.Nil(t, appErr)
|
|
|
|
// delete one of the files
|
|
params := &model.SearchParams{Terms: "test3.png", SearchWithoutUserId: true}
|
|
results, err := th.App.Srv().Store().FileInfo().Search(th.Context, []*model.SearchParams{params}, "", "", 0, 20)
|
|
require.NoError(t, err)
|
|
require.Len(t, results.FileInfos, 1)
|
|
|
|
for _, info2 := range results.FileInfos {
|
|
err2 := th.App.RemoveFile(info2.Path)
|
|
require.Nil(t, err2)
|
|
}
|
|
|
|
exportFile, err := os.Create(filepath.Join(dir, "export.zip"))
|
|
require.NoError(t, err)
|
|
|
|
job, appErr := th.App.Srv().Jobs.CreateJob(th.Context, model.JobTypeExportProcess, nil)
|
|
require.Nil(t, appErr)
|
|
newJob, appErr := th.App.Srv().Jobs.ClaimJob(job)
|
|
require.Nil(t, appErr)
|
|
require.NotNil(t, newJob)
|
|
|
|
opts := model.BulkExportOpts{
|
|
IncludeAttachments: true,
|
|
CreateArchive: true,
|
|
}
|
|
appErr = th.App.BulkExport(th.Context, exportFile, dir, newJob, opts)
|
|
// should not get an error for the missing file
|
|
require.Nil(t, appErr)
|
|
|
|
// should get a warning instead:
|
|
testlib.AssertLog(t, buffer, mlog.LvlWarn.Name, "Unable to export file attachment")
|
|
|
|
// should get info in the newJob data:
|
|
newJob, appErr = th.App.Srv().Jobs.GetJob(th.Context, newJob.Id)
|
|
require.Nil(t, appErr)
|
|
warnings, ok := newJob.Data["num_warnings"]
|
|
require.True(t, ok)
|
|
require.Equal(t, "1", warnings)
|
|
|
|
exportFile.Close()
|
|
|
|
// Verify warnings.txt exists in the zip and contains expected content
|
|
exportZipPath := filepath.Join(dir, "export.zip")
|
|
exportZipFile, err := os.Open(exportZipPath)
|
|
require.NoError(t, err)
|
|
defer exportZipFile.Close()
|
|
|
|
info, err := exportZipFile.Stat()
|
|
require.NoError(t, err)
|
|
|
|
paths, err := utils.UnzipToPath(exportZipFile, info.Size(), dir)
|
|
require.NoError(t, err)
|
|
require.Contains(t, paths, filepath.Join(dir, warningsFilename))
|
|
|
|
warningsContent, err := os.ReadFile(filepath.Join(dir, warningsFilename))
|
|
require.NoError(t, err)
|
|
require.Contains(t, string(warningsContent), "Unable to export file attachment, attachment path:")
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestBulkExport(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t)
|
|
testsDir, _ := fileutils.FindDir("tests")
|
|
|
|
dir, err := os.MkdirTemp("", "import_test")
|
|
require.NoError(t, err)
|
|
defer os.RemoveAll(dir)
|
|
|
|
extractImportFile := func(filePath string) *os.File {
|
|
importFile, err2 := os.Open(filePath)
|
|
require.NoError(t, err2)
|
|
defer importFile.Close()
|
|
|
|
info, err2 := importFile.Stat()
|
|
require.NoError(t, err2)
|
|
|
|
paths, err2 := utils.UnzipToPath(importFile, info.Size(), dir)
|
|
require.NoError(t, err2)
|
|
require.NotEmpty(t, paths)
|
|
|
|
jsonFile, err2 := os.Open(filepath.Join(dir, "import.jsonl"))
|
|
require.NoError(t, err2)
|
|
|
|
return jsonFile
|
|
}
|
|
|
|
jsonFile := extractImportFile(filepath.Join(testsDir, "import_test.zip"))
|
|
defer jsonFile.Close()
|
|
|
|
_, appErr := th.App.BulkImportWithPath(th.Context, jsonFile, nil, false, true, 1, dir)
|
|
require.Nil(t, appErr)
|
|
|
|
exportFile, err := os.Create(filepath.Join(dir, "export.zip"))
|
|
require.NoError(t, err)
|
|
defer exportFile.Close()
|
|
|
|
opts := model.BulkExportOpts{
|
|
IncludeAttachments: true,
|
|
CreateArchive: true,
|
|
}
|
|
appErr = th.App.BulkExport(th.Context, exportFile, dir, nil, opts)
|
|
require.Nil(t, appErr)
|
|
|
|
th = Setup(t)
|
|
|
|
jsonFile = extractImportFile(filepath.Join(dir, "export.zip"))
|
|
defer jsonFile.Close()
|
|
|
|
_, appErr = th.App.BulkImportWithPath(th.Context, jsonFile, nil, false, true, 1, filepath.Join(dir, "data"))
|
|
require.Nil(t, appErr)
|
|
}
|
|
|
|
func TestBuildPostReplies(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
createPostWithAttachments := func(th *TestHelper, n int, rootID string) *model.Post {
|
|
var fileIDs []string
|
|
for i := range n {
|
|
info, err := th.App.Srv().Store().FileInfo().Save(th.Context, &model.FileInfo{
|
|
CreatorId: th.BasicUser.Id,
|
|
Name: fmt.Sprintf("file%d", i),
|
|
Path: fmt.Sprintf("/data/file%d", i),
|
|
})
|
|
require.NoError(t, err)
|
|
fileIDs = append(fileIDs, info.Id)
|
|
}
|
|
|
|
post, _, err := th.App.CreatePost(th.Context, &model.Post{UserId: th.BasicUser.Id, ChannelId: th.BasicChannel.Id, RootId: rootID, FileIds: fileIDs}, th.BasicChannel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, err)
|
|
|
|
return post
|
|
}
|
|
|
|
t.Run("basic post", func(t *testing.T) {
|
|
data, attachments, err := th.App.buildPostReplies(th.Context, th.BasicPost.Id, true)
|
|
require.Nil(t, err)
|
|
require.Empty(t, data)
|
|
require.Empty(t, attachments)
|
|
})
|
|
|
|
t.Run("root post with attachments and no replies", func(t *testing.T) {
|
|
post := createPostWithAttachments(th, 5, "")
|
|
data, attachments, err := th.App.buildPostReplies(th.Context, post.Id, true)
|
|
require.Nil(t, err)
|
|
require.Empty(t, data)
|
|
require.Empty(t, attachments)
|
|
})
|
|
|
|
t.Run("root post with attachments and a reply", func(t *testing.T) {
|
|
post := createPostWithAttachments(th, 5, "")
|
|
createPostWithAttachments(th, 0, post.Id)
|
|
data, attachments, err := th.App.buildPostReplies(th.Context, post.Id, true)
|
|
require.Nil(t, err)
|
|
require.Len(t, data, 1)
|
|
require.Empty(t, attachments)
|
|
})
|
|
|
|
t.Run("root post with attachments and multiple replies with attachments", func(t *testing.T) {
|
|
post := createPostWithAttachments(th, 5, "")
|
|
reply1 := createPostWithAttachments(th, 2, post.Id)
|
|
reply2 := createPostWithAttachments(th, 3, post.Id)
|
|
data, attachments, err := th.App.buildPostReplies(th.Context, post.Id, true)
|
|
require.Nil(t, err)
|
|
require.Len(t, data, 2)
|
|
require.Len(t, attachments, 5)
|
|
if reply1.Id < reply2.Id {
|
|
require.Len(t, *data[0].Attachments, 2)
|
|
require.Len(t, *data[1].Attachments, 3)
|
|
} else {
|
|
require.Len(t, *data[1].Attachments, 2)
|
|
require.Len(t, *data[0].Attachments, 3)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestExportDeletedTeams(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th1 := Setup(t).InitBasic(t)
|
|
|
|
team1 := th1.CreateTeam(t)
|
|
channel1 := th1.CreateChannel(t, team1)
|
|
th1.CreatePost(t, channel1)
|
|
|
|
// Delete the team to check that this is handled correctly on import.
|
|
err := th1.App.SoftDeleteTeam(team1.Id)
|
|
require.Nil(t, err)
|
|
|
|
var b bytes.Buffer
|
|
err = th1.App.BulkExport(th1.Context, &b, "somePath", nil, model.BulkExportOpts{})
|
|
require.Nil(t, err)
|
|
|
|
var th2 *TestHelper
|
|
if mainHelper.Options.RunParallel {
|
|
th1.Store.DropAllTables()
|
|
th2 = th1
|
|
} else {
|
|
th2 = Setup(t)
|
|
}
|
|
|
|
i, err := th2.App.BulkImport(th2.Context, &b, nil, false, 5)
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, 0, i)
|
|
|
|
teams1, err := th1.App.GetAllTeams()
|
|
assert.Nil(t, err)
|
|
teams2, err := th2.App.GetAllTeams()
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, len(teams1), len(teams2))
|
|
assert.ElementsMatch(t, teams1, teams2)
|
|
|
|
channels1, err := th1.App.GetAllChannels(th1.Context, 0, 10, model.ChannelSearchOpts{})
|
|
assert.Nil(t, err)
|
|
channels2, err := th2.App.GetAllChannels(th1.Context, 0, 10, model.ChannelSearchOpts{})
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, len(channels1), len(channels2))
|
|
assert.ElementsMatch(t, channels1, channels2)
|
|
for _, team := range teams2 {
|
|
assert.NotContains(t, team.Name, team1.Name)
|
|
assert.NotContains(t, team.Id, team1.Id)
|
|
}
|
|
}
|
|
|
|
func TestExportArchivedChannels(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th1 := Setup(t).InitBasic(t)
|
|
|
|
archivedChannel := th1.CreateChannel(t, th1.BasicTeam)
|
|
th1.CreatePost(t, archivedChannel)
|
|
appErr := th1.App.DeleteChannel(th1.Context, archivedChannel, th1.SystemAdminUser.Id)
|
|
require.Nil(t, appErr)
|
|
|
|
var b bytes.Buffer
|
|
appErr = th1.App.BulkExport(th1.Context, &b, "somePath", nil, model.BulkExportOpts{
|
|
IncludeArchivedChannels: true,
|
|
})
|
|
require.Nil(t, appErr)
|
|
|
|
th2 := Setup(t)
|
|
|
|
i, err := th2.App.BulkImport(th2.Context, &b, nil, false, 5)
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, 0, i)
|
|
|
|
channels2, err := th2.App.GetAllChannels(th1.Context, 0, 10, model.ChannelSearchOpts{
|
|
IncludeDeleted: true,
|
|
})
|
|
assert.Nil(t, err)
|
|
found := false
|
|
for i := range channels2 {
|
|
if channels2[i].Name == archivedChannel.Name {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
require.True(t, found, "archived channel not found after import")
|
|
}
|
|
|
|
func TestExportRoles(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
t.Run("defaults", func(t *testing.T) {
|
|
th1 := Setup(t).InitBasic(t)
|
|
|
|
var b bytes.Buffer
|
|
appErr := th1.App.BulkExport(th1.Context, &b, "", nil, model.BulkExportOpts{})
|
|
require.Nil(t, appErr)
|
|
|
|
exportedRoles, appErr := th1.App.GetAllRoles()
|
|
assert.Nil(t, appErr)
|
|
assert.NotEmpty(t, exportedRoles)
|
|
|
|
th2 := Setup(t)
|
|
|
|
i, appErr := th2.App.BulkImport(th2.Context, &b, nil, false, 1)
|
|
assert.Nil(t, appErr)
|
|
assert.Equal(t, 0, i)
|
|
|
|
importedRoles, appErr := th2.App.GetAllRoles()
|
|
assert.Nil(t, appErr)
|
|
assert.NotEmpty(t, importedRoles)
|
|
|
|
require.Equal(t, len(exportedRoles), len(importedRoles))
|
|
})
|
|
|
|
t.Run("modified roles", func(t *testing.T) {
|
|
th1 := Setup(t).InitBasic(t)
|
|
|
|
exportedRole, appErr := th1.App.GetRoleByName(th1.Context, model.TeamUserRoleId)
|
|
require.Nil(t, appErr)
|
|
|
|
exportedRole.Permissions = exportedRole.Permissions[1:]
|
|
|
|
_, appErr = th1.App.UpdateRole(exportedRole)
|
|
require.Nil(t, appErr)
|
|
|
|
var b bytes.Buffer
|
|
appErr = th1.App.BulkExport(th1.Context, &b, "", nil, model.BulkExportOpts{
|
|
IncludeRolesAndSchemes: true,
|
|
})
|
|
require.Nil(t, appErr)
|
|
|
|
th2 := Setup(t)
|
|
|
|
i, appErr := th2.App.BulkImport(th2.Context, &b, nil, false, 1)
|
|
require.Nil(t, appErr)
|
|
require.Equal(t, 0, i)
|
|
|
|
importedRole, appErr := th2.App.GetRoleByName(th2.Context, model.TeamUserRoleId)
|
|
require.Nil(t, appErr)
|
|
|
|
require.Equal(t, exportedRole.DisplayName, importedRole.DisplayName)
|
|
require.Equal(t, exportedRole.Description, importedRole.Description)
|
|
require.Equal(t, exportedRole.SchemeManaged, importedRole.SchemeManaged)
|
|
require.Equal(t, exportedRole.BuiltIn, importedRole.BuiltIn)
|
|
require.ElementsMatch(t, exportedRole.Permissions, importedRole.Permissions)
|
|
})
|
|
|
|
t.Run("custom roles", func(t *testing.T) {
|
|
th1 := Setup(t).InitBasic(t)
|
|
|
|
exportedRoles, appErr := th1.App.GetAllRoles()
|
|
require.Nil(t, appErr)
|
|
require.NotEmpty(t, exportedRoles)
|
|
|
|
customRole, appErr := th1.App.CreateRole(&model.Role{
|
|
Name: "custom_role",
|
|
DisplayName: "custom_role",
|
|
Permissions: exportedRoles[0].Permissions,
|
|
})
|
|
require.Nil(t, appErr)
|
|
|
|
var b bytes.Buffer
|
|
appErr = th1.App.BulkExport(th1.Context, &b, "", nil, model.BulkExportOpts{
|
|
IncludeRolesAndSchemes: true,
|
|
})
|
|
require.Nil(t, appErr)
|
|
|
|
th2 := Setup(t)
|
|
|
|
i, appErr := th2.App.BulkImport(th2.Context, &b, nil, false, 1)
|
|
require.Nil(t, appErr)
|
|
require.Equal(t, 0, i)
|
|
|
|
importedCustomRole, appErr := th2.App.GetRoleByName(th2.Context, customRole.Name)
|
|
require.Nil(t, appErr)
|
|
|
|
require.Equal(t, customRole.DisplayName, importedCustomRole.DisplayName)
|
|
require.Equal(t, customRole.Description, importedCustomRole.Description)
|
|
require.Equal(t, customRole.SchemeManaged, importedCustomRole.SchemeManaged)
|
|
require.Equal(t, customRole.BuiltIn, importedCustomRole.BuiltIn)
|
|
require.ElementsMatch(t, customRole.Permissions, importedCustomRole.Permissions)
|
|
})
|
|
}
|
|
|
|
func TestExportSchemes(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
t.Run("no schemes", func(t *testing.T) {
|
|
th1 := Setup(t).InitBasic(t)
|
|
|
|
// Need to set this or working with schemes won't work until the job is
|
|
// completed which is unnecessary for the purpose of this test.
|
|
err := th1.App.Srv().Store().System().Save(&model.System{Name: model.MigrationKeyAdvancedPermissionsPhase2, Value: "true"})
|
|
require.NoError(t, err)
|
|
|
|
schemes, err := th1.App.Srv().Store().Scheme().GetAllPage(model.SchemeScopeChannel, 0, 1)
|
|
require.NoError(t, err)
|
|
require.Empty(t, schemes)
|
|
|
|
schemes, err = th1.App.Srv().Store().Scheme().GetAllPage(model.SchemeScopeTeam, 0, 1)
|
|
require.NoError(t, err)
|
|
require.Empty(t, schemes)
|
|
|
|
var b bytes.Buffer
|
|
appErr := th1.App.BulkExport(th1.Context, &b, "", nil, model.BulkExportOpts{
|
|
IncludeRolesAndSchemes: true,
|
|
})
|
|
require.Nil(t, appErr)
|
|
|
|
// The following causes the original store to be wiped so from here on we are targeting the
|
|
// second instance where the import will be loaded.
|
|
th2 := Setup(t)
|
|
|
|
err = th2.App.Srv().Store().System().Save(&model.System{Name: model.MigrationKeyAdvancedPermissionsPhase2, Value: "true"})
|
|
require.NoError(t, err)
|
|
|
|
i, appErr := th2.App.BulkImport(th2.Context, &b, nil, false, 1)
|
|
require.Nil(t, appErr)
|
|
require.Equal(t, 0, i)
|
|
|
|
schemes, err = th2.App.Srv().Store().Scheme().GetAllPage(model.SchemeScopeChannel, 0, 1)
|
|
require.NoError(t, err)
|
|
require.Empty(t, schemes)
|
|
|
|
schemes, err = th2.App.Srv().Store().Scheme().GetAllPage(model.SchemeScopeTeam, 0, 1)
|
|
require.NoError(t, err)
|
|
require.Empty(t, schemes)
|
|
})
|
|
|
|
t.Run("skip export", func(t *testing.T) {
|
|
th1 := Setup(t).InitBasic(t)
|
|
|
|
// Need to set this or working with schemes won't work until the job is
|
|
// completed which is unnecessary for the purpose of this test.
|
|
err := th1.App.Srv().Store().System().Save(&model.System{Name: model.MigrationKeyAdvancedPermissionsPhase2, Value: "true"})
|
|
require.NoError(t, err)
|
|
|
|
customScheme, appErr := th1.App.CreateScheme(&model.Scheme{
|
|
Name: "custom_scheme",
|
|
DisplayName: "Custom Scheme",
|
|
Scope: model.SchemeScopeChannel,
|
|
})
|
|
require.Nil(t, appErr)
|
|
|
|
var b bytes.Buffer
|
|
appErr = th1.App.BulkExport(th1.Context, &b, "", nil, model.BulkExportOpts{})
|
|
require.Nil(t, appErr)
|
|
|
|
// The following causes the original store to be wiped so from here on we are targeting the
|
|
// second instance where the import will be loaded.
|
|
th2 := Setup(t)
|
|
|
|
err = th2.App.Srv().Store().System().Save(&model.System{Name: model.MigrationKeyAdvancedPermissionsPhase2, Value: "true"})
|
|
require.NoError(t, err)
|
|
|
|
i, appErr := th2.App.BulkImport(th2.Context, &b, nil, false, 1)
|
|
require.Nil(t, appErr)
|
|
require.Equal(t, 0, i)
|
|
|
|
// Verify the scheme doesn't exist which is the expectation as it wasn't exported.
|
|
_, appErr = th2.App.GetScheme(customScheme.Name)
|
|
require.NotNil(t, appErr)
|
|
})
|
|
|
|
t.Run("export channel scheme", func(t *testing.T) {
|
|
th1 := Setup(t).InitBasic(t)
|
|
|
|
// Need to set this or working with schemes won't work until the job is
|
|
// completed which is unnecessary for the purpose of this test.
|
|
err := th1.App.Srv().Store().System().Save(&model.System{Name: model.MigrationKeyAdvancedPermissionsPhase2, Value: "true"})
|
|
require.NoError(t, err)
|
|
|
|
builtInRoles := 24
|
|
defaultChannelSchemeRoles := 3
|
|
|
|
// Verify the roles count is expected prior to scheme creation.
|
|
roles, appErr := th1.App.GetAllRoles()
|
|
require.Nil(t, appErr)
|
|
require.Len(t, roles, builtInRoles)
|
|
|
|
customScheme, appErr := th1.App.CreateScheme(&model.Scheme{
|
|
Name: "custom_channel_scheme",
|
|
DisplayName: "Custom Channel Scheme",
|
|
Scope: model.SchemeScopeChannel,
|
|
})
|
|
require.Nil(t, appErr)
|
|
|
|
// Verify the roles count is expected after scheme creation.
|
|
roles, appErr = th1.App.GetAllRoles()
|
|
require.Nil(t, appErr)
|
|
require.Len(t, roles, builtInRoles+defaultChannelSchemeRoles)
|
|
|
|
// Fetch the scheme roles for later comparison
|
|
customChannelAdminRole, appErr := th1.App.GetRoleByName(th1.Context, customScheme.DefaultChannelAdminRole)
|
|
require.Nil(t, appErr)
|
|
customChannelUserRole, appErr := th1.App.GetRoleByName(th1.Context, customScheme.DefaultChannelUserRole)
|
|
require.Nil(t, appErr)
|
|
customChannelGuestRole, appErr := th1.App.GetRoleByName(th1.Context, customScheme.DefaultChannelGuestRole)
|
|
require.Nil(t, appErr)
|
|
|
|
var b bytes.Buffer
|
|
appErr = th1.App.BulkExport(th1.Context, &b, "", nil, model.BulkExportOpts{
|
|
IncludeRolesAndSchemes: true,
|
|
})
|
|
require.Nil(t, appErr)
|
|
|
|
// The following causes the original store to be wiped so from here on we are targeting the
|
|
// second instance where the import will be loaded.
|
|
th2 := Setup(t)
|
|
|
|
err = th2.App.Srv().Store().System().Save(&model.System{Name: model.MigrationKeyAdvancedPermissionsPhase2, Value: "true"})
|
|
require.NoError(t, err)
|
|
|
|
// Verify roles count before importing is as expected.
|
|
roles, appErr = th2.App.GetAllRoles()
|
|
require.Nil(t, appErr)
|
|
require.Len(t, roles, builtInRoles)
|
|
|
|
i, appErr := th2.App.BulkImport(th2.Context, &b, nil, false, 1)
|
|
require.Nil(t, appErr)
|
|
require.Equal(t, 0, i)
|
|
|
|
// Verify roles count after importing is as expected.
|
|
roles, appErr = th2.App.GetAllRoles()
|
|
require.Nil(t, appErr)
|
|
require.Len(t, roles, builtInRoles+defaultChannelSchemeRoles)
|
|
|
|
// Verify schemes match
|
|
importedScheme, appErr := th2.App.GetSchemeByName(customScheme.Name)
|
|
require.Nil(t, appErr)
|
|
require.Equal(t, customScheme.Name, importedScheme.Name)
|
|
require.Equal(t, customScheme.DisplayName, importedScheme.DisplayName)
|
|
require.Equal(t, customScheme.Description, importedScheme.Description)
|
|
require.Equal(t, customScheme.Scope, importedScheme.Scope)
|
|
|
|
// Verify scheme roles match
|
|
importedChannelAdminRole, appErr := th2.App.GetRoleByName(th2.Context, importedScheme.DefaultChannelAdminRole)
|
|
require.Nil(t, appErr)
|
|
require.Equal(t, customChannelAdminRole.DisplayName, importedChannelAdminRole.DisplayName)
|
|
require.Equal(t, customChannelAdminRole.Description, importedChannelAdminRole.Description)
|
|
require.Equal(t, customChannelAdminRole.Permissions, importedChannelAdminRole.Permissions)
|
|
require.Equal(t, customChannelAdminRole.SchemeManaged, importedChannelAdminRole.SchemeManaged)
|
|
require.Equal(t, customChannelAdminRole.BuiltIn, importedChannelAdminRole.BuiltIn)
|
|
|
|
importedChannelUserRole, appErr := th2.App.GetRoleByName(th2.Context, importedScheme.DefaultChannelUserRole)
|
|
require.Nil(t, appErr)
|
|
require.Equal(t, customChannelUserRole.DisplayName, importedChannelUserRole.DisplayName)
|
|
require.Equal(t, customChannelUserRole.Description, importedChannelUserRole.Description)
|
|
require.Equal(t, customChannelUserRole.Permissions, importedChannelUserRole.Permissions)
|
|
require.Equal(t, customChannelUserRole.SchemeManaged, importedChannelUserRole.SchemeManaged)
|
|
require.Equal(t, customChannelUserRole.BuiltIn, importedChannelUserRole.BuiltIn)
|
|
|
|
importedChannelGuestRole, appErr := th2.App.GetRoleByName(th2.Context, importedScheme.DefaultChannelGuestRole)
|
|
require.Nil(t, appErr)
|
|
require.Equal(t, customChannelGuestRole.DisplayName, importedChannelGuestRole.DisplayName)
|
|
require.Equal(t, customChannelGuestRole.Description, importedChannelGuestRole.Description)
|
|
require.Equal(t, customChannelGuestRole.Permissions, importedChannelGuestRole.Permissions)
|
|
require.Equal(t, customChannelGuestRole.SchemeManaged, importedChannelGuestRole.SchemeManaged)
|
|
require.Equal(t, customChannelGuestRole.BuiltIn, importedChannelGuestRole.BuiltIn)
|
|
})
|
|
|
|
t.Run("export team scheme", func(t *testing.T) {
|
|
th1 := Setup(t).InitBasic(t)
|
|
|
|
// Need to set this or working with schemes won't work until the job is
|
|
// completed which is unnecessary for the purpose of this test.
|
|
err := th1.App.Srv().Store().System().Save(&model.System{Name: model.MigrationKeyAdvancedPermissionsPhase2, Value: "true"})
|
|
require.NoError(t, err)
|
|
|
|
builtInRoles := 24
|
|
defaultTeamSchemeRoles := 10
|
|
|
|
// Verify the roles count is expected prior to scheme creation.
|
|
roles, appErr := th1.App.GetAllRoles()
|
|
require.Nil(t, appErr)
|
|
require.Len(t, roles, builtInRoles)
|
|
|
|
customScheme, appErr := th1.App.CreateScheme(&model.Scheme{
|
|
Name: "custom_team_scheme",
|
|
DisplayName: "Custom Team Scheme",
|
|
Scope: model.SchemeScopeTeam,
|
|
})
|
|
require.Nil(t, appErr)
|
|
|
|
// Verify the roles count is expected after scheme creation.
|
|
roles, appErr = th1.App.GetAllRoles()
|
|
require.Nil(t, appErr)
|
|
require.Len(t, roles, builtInRoles+defaultTeamSchemeRoles)
|
|
|
|
customChannelAdminRole, appErr := th1.App.GetRoleByName(th1.Context, customScheme.DefaultChannelAdminRole)
|
|
require.Nil(t, appErr)
|
|
|
|
customChannelUserRole, appErr := th1.App.GetRoleByName(th1.Context, customScheme.DefaultChannelUserRole)
|
|
require.Nil(t, appErr)
|
|
|
|
customChannelGuestRole, appErr := th1.App.GetRoleByName(th1.Context, customScheme.DefaultChannelGuestRole)
|
|
require.Nil(t, appErr)
|
|
|
|
customTeamAdminRole, appErr := th1.App.GetRoleByName(th1.Context, customScheme.DefaultTeamAdminRole)
|
|
require.Nil(t, appErr)
|
|
|
|
customTeamUserRole, appErr := th1.App.GetRoleByName(th1.Context, customScheme.DefaultTeamUserRole)
|
|
require.Nil(t, appErr)
|
|
|
|
customTeamGuestRole, appErr := th1.App.GetRoleByName(th1.Context, customScheme.DefaultTeamGuestRole)
|
|
require.Nil(t, appErr)
|
|
|
|
var b bytes.Buffer
|
|
appErr = th1.App.BulkExport(th1.Context, &b, "", nil, model.BulkExportOpts{
|
|
IncludeRolesAndSchemes: true,
|
|
})
|
|
require.Nil(t, appErr)
|
|
|
|
// The following causes the original store to be wiped so from here on we are targeting the
|
|
// second instance where the import will be loaded.
|
|
th2 := Setup(t)
|
|
|
|
err = th2.App.Srv().Store().System().Save(&model.System{Name: model.MigrationKeyAdvancedPermissionsPhase2, Value: "true"})
|
|
require.NoError(t, err)
|
|
|
|
// Verify roles count before importing is as expected.
|
|
roles, appErr = th2.App.GetAllRoles()
|
|
require.Nil(t, appErr)
|
|
require.Len(t, roles, builtInRoles)
|
|
|
|
i, appErr := th2.App.BulkImport(th2.Context, &b, nil, false, 1)
|
|
require.Nil(t, appErr)
|
|
require.Equal(t, 0, i)
|
|
|
|
// Verify roles count after importing is as expected.
|
|
roles, appErr = th2.App.GetAllRoles()
|
|
require.Nil(t, appErr)
|
|
require.Len(t, roles, builtInRoles+defaultTeamSchemeRoles)
|
|
|
|
// Verify schemes match
|
|
importedScheme, appErr := th2.App.GetSchemeByName(customScheme.Name)
|
|
require.Nil(t, appErr)
|
|
require.Equal(t, customScheme.Name, importedScheme.Name)
|
|
require.Equal(t, customScheme.DisplayName, importedScheme.DisplayName)
|
|
require.Equal(t, customScheme.Description, importedScheme.Description)
|
|
require.Equal(t, customScheme.Scope, importedScheme.Scope)
|
|
|
|
// Verify scheme roles match
|
|
importedChannelAdminRole, appErr := th2.App.GetRoleByName(th2.Context, importedScheme.DefaultChannelAdminRole)
|
|
require.Nil(t, appErr)
|
|
require.Equal(t, customChannelAdminRole.DisplayName, importedChannelAdminRole.DisplayName)
|
|
require.Equal(t, customChannelAdminRole.Description, importedChannelAdminRole.Description)
|
|
require.Equal(t, customChannelAdminRole.Permissions, importedChannelAdminRole.Permissions)
|
|
require.Equal(t, customChannelAdminRole.SchemeManaged, importedChannelAdminRole.SchemeManaged)
|
|
require.Equal(t, customChannelAdminRole.BuiltIn, importedChannelAdminRole.BuiltIn)
|
|
|
|
importedChannelUserRole, appErr := th2.App.GetRoleByName(th2.Context, importedScheme.DefaultChannelUserRole)
|
|
require.Nil(t, appErr)
|
|
require.Equal(t, customChannelUserRole.DisplayName, importedChannelUserRole.DisplayName)
|
|
require.Equal(t, customChannelUserRole.Description, importedChannelUserRole.Description)
|
|
require.Equal(t, customChannelUserRole.Permissions, importedChannelUserRole.Permissions)
|
|
require.Equal(t, customChannelUserRole.SchemeManaged, importedChannelUserRole.SchemeManaged)
|
|
require.Equal(t, customChannelUserRole.BuiltIn, importedChannelUserRole.BuiltIn)
|
|
|
|
importedChannelGuestRole, appErr := th2.App.GetRoleByName(th2.Context, importedScheme.DefaultChannelGuestRole)
|
|
require.Nil(t, appErr)
|
|
require.Equal(t, customChannelGuestRole.DisplayName, importedChannelGuestRole.DisplayName)
|
|
require.Equal(t, customChannelGuestRole.Description, importedChannelGuestRole.Description)
|
|
require.Equal(t, customChannelGuestRole.Permissions, importedChannelGuestRole.Permissions)
|
|
require.Equal(t, customChannelGuestRole.SchemeManaged, importedChannelGuestRole.SchemeManaged)
|
|
require.Equal(t, customChannelGuestRole.BuiltIn, importedChannelGuestRole.BuiltIn)
|
|
|
|
importedTeamAdminRole, appErr := th2.App.GetRoleByName(th2.Context, importedScheme.DefaultTeamAdminRole)
|
|
require.Nil(t, appErr)
|
|
require.Equal(t, customTeamAdminRole.DisplayName, importedTeamAdminRole.DisplayName)
|
|
require.Equal(t, customTeamAdminRole.Description, importedTeamAdminRole.Description)
|
|
require.Equal(t, customTeamAdminRole.Permissions, importedTeamAdminRole.Permissions)
|
|
require.Equal(t, customTeamAdminRole.SchemeManaged, importedTeamAdminRole.SchemeManaged)
|
|
require.Equal(t, customTeamAdminRole.BuiltIn, importedTeamAdminRole.BuiltIn)
|
|
|
|
importedTeamUserRole, appErr := th2.App.GetRoleByName(th2.Context, importedScheme.DefaultTeamUserRole)
|
|
require.Nil(t, appErr)
|
|
require.Equal(t, customTeamUserRole.DisplayName, importedTeamUserRole.DisplayName)
|
|
require.Equal(t, customTeamUserRole.Description, importedTeamUserRole.Description)
|
|
require.Equal(t, customTeamUserRole.Permissions, importedTeamUserRole.Permissions)
|
|
require.Equal(t, customTeamUserRole.SchemeManaged, importedTeamUserRole.SchemeManaged)
|
|
require.Equal(t, customTeamUserRole.BuiltIn, importedTeamUserRole.BuiltIn)
|
|
|
|
importedTeamGuestRole, appErr := th2.App.GetRoleByName(th2.Context, importedScheme.DefaultTeamGuestRole)
|
|
require.Nil(t, appErr)
|
|
require.Equal(t, customTeamGuestRole.DisplayName, importedTeamGuestRole.DisplayName)
|
|
require.Equal(t, customTeamGuestRole.Description, importedTeamGuestRole.Description)
|
|
require.Equal(t, customTeamGuestRole.Permissions, importedTeamGuestRole.Permissions)
|
|
require.Equal(t, customTeamGuestRole.SchemeManaged, importedTeamGuestRole.SchemeManaged)
|
|
require.Equal(t, customTeamGuestRole.BuiltIn, importedTeamGuestRole.BuiltIn)
|
|
})
|
|
}
|
|
|
|
// TestExportDeactivatedUserDMs specifically tests the MM-43598 bug
|
|
// by validating that direct messages from deactivated users are exported correctly
|
|
func TestExportDeactivatedUserDMs(t *testing.T) {
|
|
th1 := Setup(t).InitBasic(t)
|
|
|
|
// Create a DM Channel
|
|
user2 := th1.BasicUser2
|
|
dmChannel := th1.CreateDmChannel(t, user2)
|
|
|
|
// 1. First basic user (active) sends a message to user2 (who will later be deactivated)
|
|
initialMessage := "initial_message_from_basic_user"
|
|
initialPost := &model.Post{
|
|
ChannelId: dmChannel.Id,
|
|
Message: initialMessage,
|
|
UserId: th1.BasicUser.Id,
|
|
}
|
|
initialPostCreated, _, appErr := th1.App.CreatePost(th1.Context, initialPost, dmChannel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, appErr)
|
|
|
|
// 2. Have user2 reply with TWO types of replies:
|
|
|
|
// 2a. User2 replies in a thread (to the initial message)
|
|
threadedReplyMessage := "threaded_reply_from_user2"
|
|
threadedReply := &model.Post{
|
|
ChannelId: dmChannel.Id,
|
|
Message: threadedReplyMessage,
|
|
UserId: user2.Id,
|
|
RootId: initialPostCreated.Id, // This makes it a threaded reply
|
|
}
|
|
_, _, appErr = th1.App.CreatePost(th1.Context, threadedReply, dmChannel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, appErr)
|
|
|
|
// 2b. User2 sends a standalone reply (NOT in a thread)
|
|
nonThreadedReplyMessage := "non_threaded_reply_from_user2"
|
|
nonThreadedReply := &model.Post{
|
|
ChannelId: dmChannel.Id,
|
|
Message: nonThreadedReplyMessage,
|
|
UserId: user2.Id,
|
|
// No RootId, making it a standalone message, not a thread reply
|
|
}
|
|
_, _, appErr = th1.App.CreatePost(th1.Context, nonThreadedReply, dmChannel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, appErr)
|
|
|
|
// 3. Now deactivate user2
|
|
_, err := th1.App.UpdateActive(th1.Context, user2, false)
|
|
require.Nil(t, err)
|
|
|
|
// 4. Export data
|
|
var b bytes.Buffer
|
|
appErr = th1.App.BulkExport(th1.Context, &b, "somePath", nil, model.BulkExportOpts{})
|
|
require.Nil(t, appErr)
|
|
|
|
// 5. Make a copy of the buffer for export validation
|
|
var exportDataCopy bytes.Buffer
|
|
_, nErr := exportDataCopy.Write(b.Bytes())
|
|
require.NoError(t, nErr)
|
|
|
|
// 6. Validate export data directly to ensure both types of replies are present
|
|
scanner := bufio.NewScanner(&exportDataCopy)
|
|
foundThreadedReply := false
|
|
foundNonThreadedReply := false
|
|
|
|
for scanner.Scan() {
|
|
var line imports.LineImportData
|
|
err := json.Unmarshal(scanner.Bytes(), &line)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
// Check for direct posts
|
|
if line.Type == "direct_post" && line.DirectPost != nil {
|
|
// Check for the non-threaded reply (the standalone message)
|
|
if line.DirectPost.Message != nil && *line.DirectPost.Message == nonThreadedReplyMessage {
|
|
foundNonThreadedReply = true
|
|
// Verify username is correctly preserved in export
|
|
require.Equal(t, user2.Username, *line.DirectPost.User,
|
|
"Deactivated user's username should be preserved in export for non-threaded reply")
|
|
}
|
|
|
|
// Check for the thread starter and its replies
|
|
if line.DirectPost.Message != nil && *line.DirectPost.Message == initialMessage {
|
|
// Check if the threaded reply is in the replies array
|
|
if line.DirectPost.Replies != nil {
|
|
for _, reply := range *line.DirectPost.Replies {
|
|
if reply.Message != nil && *reply.Message == threadedReplyMessage {
|
|
foundThreadedReply = true
|
|
require.Equal(t, user2.Username, *reply.User,
|
|
"Deactivated user's username should be preserved in export for threaded reply")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// This is key for testing MM-43598
|
|
require.True(t, foundNonThreadedReply,
|
|
"Non-threaded reply from deactivated user must be present in export data")
|
|
require.True(t, foundThreadedReply,
|
|
"Threaded reply from deactivated user must be present in export data")
|
|
|
|
// 7. Import data into a new instance
|
|
th2 := Setup(t)
|
|
|
|
i, appErr := th2.App.BulkImport(th2.Context, &b, nil, false, 5)
|
|
require.Nil(t, appErr)
|
|
require.Equal(t, 0, i)
|
|
|
|
// 8. Verify the DM channel was imported
|
|
channels, nErr := th2.App.Srv().Store().Channel().GetAllDirectChannelsForExportAfter(1000, "00000000", false)
|
|
require.NoError(t, nErr)
|
|
require.Equal(t, 1, len(channels), "Direct channel should be imported")
|
|
|
|
// 9. Verify all posts were imported
|
|
posts, nErr := th2.App.Srv().Store().Post().GetPosts(th2.Context,
|
|
model.GetPostsOptions{
|
|
ChannelId: channels[0].Id,
|
|
PerPage: 1000,
|
|
}, false, nil)
|
|
require.NoError(t, nErr)
|
|
|
|
// We should have exactly 3 posts
|
|
require.Equal(t, 3, len(posts.Posts), "Should have imported exactly 3 posts")
|
|
|
|
// 10. Specifically check that both types of replies are present
|
|
foundThreadedReplyInImport := false
|
|
foundNonThreadedReplyInImport := false
|
|
|
|
for _, post := range posts.Posts {
|
|
if post.Message == threadedReplyMessage {
|
|
foundThreadedReplyInImport = true
|
|
// Verify this is a reply in a thread
|
|
require.NotEmpty(t, post.RootId, "Threaded reply should have a RootId")
|
|
}
|
|
if post.Message == nonThreadedReplyMessage {
|
|
foundNonThreadedReplyInImport = true
|
|
// Verify this is NOT a reply in a thread
|
|
require.Empty(t, post.RootId, "Non-threaded reply should not have a RootId")
|
|
}
|
|
}
|
|
|
|
// This directly tests the issue in MM-43598
|
|
require.True(t, foundNonThreadedReplyInImport,
|
|
"Non-threaded reply from deactivated user should be imported")
|
|
require.True(t, foundThreadedReplyInImport,
|
|
"Threaded reply from deactivated user should be imported")
|
|
}
|