Full Mattermost server source with integrated Community Enterprise features. Includes vendor directory for offline/air-gapped builds. Structure: - enterprise-impl/: Enterprise feature implementations - enterprise-community/: Init files that register implementations - enterprise/: Bridge imports (community_imports.go) - vendor/: All dependencies for offline builds Build (online): go build ./cmd/mattermost Build (offline/air-gapped): go build -mod=vendor ./cmd/mattermost 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
3654 lines
127 KiB
Go
3654 lines
127 KiB
Go
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
package app
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"sort"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/mattermost/mattermost/server/v8/channels/app/teams"
|
|
"github.com/mattermost/mattermost/server/v8/channels/app/users"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/mock"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/mattermost/mattermost/server/public/model"
|
|
"github.com/mattermost/mattermost/server/v8/channels/store/storetest/mocks"
|
|
)
|
|
|
|
func TestPermanentDeleteChannel(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.ServiceSettings.EnableIncomingWebhooks = true
|
|
*cfg.ServiceSettings.EnableOutgoingWebhooks = true
|
|
})
|
|
|
|
channel, appErr := th.App.CreateChannel(th.Context, &model.Channel{DisplayName: "deletion-test", Name: "deletion-test", Type: model.ChannelTypeOpen, TeamId: th.BasicTeam.Id}, false)
|
|
require.NotNil(t, channel, "Channel shouldn't be nil")
|
|
require.Nil(t, appErr)
|
|
defer func() {
|
|
appErr = th.App.PermanentDeleteChannel(th.Context, channel)
|
|
require.Nil(t, appErr)
|
|
}()
|
|
|
|
incoming, appErr := th.App.CreateIncomingWebhookForChannel(th.BasicUser.Id, channel, &model.IncomingWebhook{ChannelId: channel.Id})
|
|
require.NotNil(t, incoming, "incoming webhook should not be nil")
|
|
require.Nil(t, appErr, "Unable to create Incoming Webhook for Channel")
|
|
defer func(hookID string) {
|
|
appErr = th.App.DeleteIncomingWebhook(hookID)
|
|
require.Nil(t, appErr)
|
|
}(incoming.Id)
|
|
|
|
incoming, appErr = th.App.GetIncomingWebhook(incoming.Id)
|
|
require.NotNil(t, incoming, "incoming webhook should not be nil")
|
|
require.Nil(t, appErr, "Unable to get new incoming webhook")
|
|
|
|
outgoing, appErr := th.App.CreateOutgoingWebhook(&model.OutgoingWebhook{
|
|
ChannelId: channel.Id,
|
|
TeamId: channel.TeamId,
|
|
CreatorId: th.BasicUser.Id,
|
|
CallbackURLs: []string{"https://foo"},
|
|
})
|
|
require.Nil(t, appErr)
|
|
defer func(hookID string) {
|
|
appErr = th.App.DeleteOutgoingWebhook(hookID)
|
|
require.Nil(t, appErr)
|
|
}(outgoing.Id)
|
|
|
|
outgoing, appErr = th.App.GetOutgoingWebhook(outgoing.Id)
|
|
require.NotNil(t, outgoing, "Outgoing webhook should not be nil")
|
|
require.Nil(t, appErr, "Unable to get new outgoing webhook")
|
|
|
|
appErr = th.App.PermanentDeleteChannel(th.Context, channel)
|
|
require.Nil(t, appErr)
|
|
|
|
incoming, appErr = th.App.GetIncomingWebhook(incoming.Id)
|
|
require.Nil(t, incoming, "Incoming webhook should be nil")
|
|
require.NotNil(t, appErr, "Incoming webhook wasn't deleted")
|
|
|
|
outgoing, appErr = th.App.GetOutgoingWebhook(outgoing.Id)
|
|
require.Nil(t, outgoing, "Outgoing webhook should be nil")
|
|
require.NotNil(t, appErr, "Outgoing webhook wasn't deleted")
|
|
}
|
|
|
|
func TestRemoveAllDeactivatedMembersFromChannel(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
var appErr *model.AppError
|
|
|
|
team := th.CreateTeam()
|
|
channel := th.CreateChannel(th.Context, team)
|
|
defer func() {
|
|
appErr = th.App.PermanentDeleteChannel(th.Context, channel)
|
|
require.Nil(t, appErr)
|
|
appErr = th.App.PermanentDeleteTeam(th.Context, team)
|
|
require.Nil(t, appErr)
|
|
}()
|
|
|
|
_, _, appErr = th.App.AddUserToTeam(th.Context, team.Id, th.BasicUser.Id, "")
|
|
require.Nil(t, appErr)
|
|
|
|
deactivatedUser := th.CreateUser()
|
|
_, _, appErr = th.App.AddUserToTeam(th.Context, team.Id, deactivatedUser.Id, "")
|
|
require.Nil(t, appErr)
|
|
_, appErr = th.App.AddUserToChannel(th.Context, deactivatedUser, channel, false)
|
|
require.Nil(t, appErr)
|
|
channelMembers, appErr := th.App.GetChannelMembersPage(th.Context, channel.Id, 0, 10000000)
|
|
require.Nil(t, appErr)
|
|
require.Len(t, channelMembers, 2)
|
|
_, appErr = th.App.UpdateActive(th.Context, deactivatedUser, false)
|
|
require.Nil(t, appErr)
|
|
|
|
appErr = th.App.RemoveAllDeactivatedMembersFromChannel(th.Context, channel)
|
|
require.Nil(t, appErr)
|
|
|
|
channelMembers, appErr = th.App.GetChannelMembersPage(th.Context, channel.Id, 0, 10000000)
|
|
require.Nil(t, appErr)
|
|
require.Len(t, channelMembers, 1)
|
|
}
|
|
|
|
func TestMoveChannel(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
t.Run("should move channels between teams", func(t *testing.T) {
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
var appErr *model.AppError
|
|
|
|
sourceTeam := th.CreateTeam()
|
|
targetTeam := th.CreateTeam()
|
|
channel1 := th.CreateChannel(th.Context, sourceTeam)
|
|
defer func() {
|
|
appErr = th.App.PermanentDeleteChannel(th.Context, channel1)
|
|
require.Nil(t, appErr)
|
|
|
|
appErr = th.App.PermanentDeleteTeam(th.Context, sourceTeam)
|
|
require.Nil(t, appErr)
|
|
|
|
appErr = th.App.PermanentDeleteTeam(th.Context, targetTeam)
|
|
require.Nil(t, appErr)
|
|
}()
|
|
|
|
_, _, appErr = th.App.AddUserToTeam(th.Context, sourceTeam.Id, th.BasicUser.Id, "")
|
|
require.Nil(t, appErr)
|
|
|
|
_, _, appErr = th.App.AddUserToTeam(th.Context, sourceTeam.Id, th.BasicUser2.Id, "")
|
|
require.Nil(t, appErr)
|
|
|
|
_, _, appErr = th.App.AddUserToTeam(th.Context, targetTeam.Id, th.BasicUser.Id, "")
|
|
require.Nil(t, appErr)
|
|
|
|
_, appErr = th.App.AddUserToChannel(th.Context, th.BasicUser, channel1, false)
|
|
require.Nil(t, appErr)
|
|
|
|
_, appErr = th.App.AddUserToChannel(th.Context, th.BasicUser2, channel1, false)
|
|
require.Nil(t, appErr)
|
|
|
|
appErr = th.App.MoveChannel(th.Context, targetTeam, channel1, th.BasicUser)
|
|
require.NotNil(t, appErr, "Should have failed due to mismatched members.")
|
|
|
|
_, _, appErr = th.App.AddUserToTeam(th.Context, targetTeam.Id, th.BasicUser2.Id, "")
|
|
require.Nil(t, appErr)
|
|
|
|
appErr = th.App.MoveChannel(th.Context, targetTeam, channel1, th.BasicUser)
|
|
require.Nil(t, appErr)
|
|
|
|
// Test moving a channel with a deactivated user who isn't in the destination team.
|
|
// It should fail, unless removeDeactivatedMembers is true.
|
|
deactivatedUser := th.CreateUser()
|
|
channel2 := th.CreateChannel(th.Context, sourceTeam)
|
|
defer func() {
|
|
appErr = th.App.PermanentDeleteChannel(th.Context, channel2)
|
|
require.Nil(t, appErr)
|
|
}()
|
|
|
|
_, _, appErr = th.App.AddUserToTeam(th.Context, sourceTeam.Id, deactivatedUser.Id, "")
|
|
require.Nil(t, appErr)
|
|
_, appErr = th.App.AddUserToChannel(th.Context, th.BasicUser, channel2, false)
|
|
require.Nil(t, appErr)
|
|
|
|
_, appErr = th.App.AddUserToChannel(th.Context, deactivatedUser, channel2, false)
|
|
require.Nil(t, appErr)
|
|
|
|
_, appErr = th.App.UpdateActive(th.Context, deactivatedUser, false)
|
|
require.Nil(t, appErr)
|
|
|
|
appErr = th.App.MoveChannel(th.Context, targetTeam, channel2, th.BasicUser)
|
|
require.NotNil(t, appErr, "Should have failed due to mismatched deactivated member.")
|
|
|
|
// Test moving a channel with no members.
|
|
channel3 := &model.Channel{
|
|
DisplayName: "dn_" + model.NewId(),
|
|
Name: "name_" + model.NewId(),
|
|
Type: model.ChannelTypeOpen,
|
|
TeamId: sourceTeam.Id,
|
|
CreatorId: th.BasicUser.Id,
|
|
}
|
|
|
|
channel3, appErr = th.App.CreateChannel(th.Context, channel3, false)
|
|
require.Nil(t, appErr)
|
|
defer func() {
|
|
appErr = th.App.PermanentDeleteChannel(th.Context, channel3)
|
|
require.Nil(t, appErr)
|
|
}()
|
|
|
|
appErr = th.App.MoveChannel(th.Context, targetTeam, channel3, th.BasicUser)
|
|
assert.Nil(t, appErr)
|
|
})
|
|
|
|
t.Run("should remove sidebar entries when moving channels from one team to another", func(t *testing.T) {
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
sourceTeam := th.CreateTeam()
|
|
targetTeam := th.CreateTeam()
|
|
channel := th.CreateChannel(th.Context, sourceTeam)
|
|
|
|
th.LinkUserToTeam(th.BasicUser, sourceTeam)
|
|
th.LinkUserToTeam(th.BasicUser, targetTeam)
|
|
th.AddUserToChannel(th.BasicUser, channel)
|
|
|
|
// Put the channel in a custom category so that it explicitly exists in SidebarChannels
|
|
category, appErr := th.App.CreateSidebarCategory(th.Context, th.BasicUser.Id, sourceTeam.Id, &model.SidebarCategoryWithChannels{
|
|
SidebarCategory: model.SidebarCategory{
|
|
DisplayName: "new category",
|
|
},
|
|
Channels: []string{channel.Id},
|
|
})
|
|
require.Nil(t, appErr)
|
|
require.Equal(t, []string{channel.Id}, category.Channels)
|
|
|
|
appErr = th.App.MoveChannel(th.Context, targetTeam, channel, th.BasicUser)
|
|
require.Nil(t, appErr)
|
|
|
|
moved, appErr := th.App.GetChannel(th.Context, channel.Id)
|
|
require.Nil(t, appErr)
|
|
require.Equal(t, targetTeam.Id, moved.TeamId)
|
|
|
|
// The channel should no longer be on the old team
|
|
updatedCategory, appErr := th.App.GetSidebarCategory(th.Context, category.Id)
|
|
require.Nil(t, appErr)
|
|
assert.Equal(t, []string{}, updatedCategory.Channels)
|
|
|
|
// And it should be on the new team instead
|
|
categories, appErr := th.App.GetSidebarCategoriesForTeamForUser(th.Context, th.BasicUser.Id, targetTeam.Id)
|
|
require.Nil(t, appErr)
|
|
require.Equal(t, model.SidebarCategoryChannels, categories.Categories[1].Type)
|
|
assert.Contains(t, categories.Categories[1].Channels, channel.Id)
|
|
})
|
|
|
|
t.Run("should update threads when moving channels between teams", func(t *testing.T) {
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
sourceTeam := th.CreateTeam()
|
|
targetTeam := th.CreateTeam()
|
|
channel := th.CreateChannel(th.Context, sourceTeam)
|
|
|
|
th.LinkUserToTeam(th.BasicUser, sourceTeam)
|
|
th.LinkUserToTeam(th.BasicUser, targetTeam)
|
|
th.AddUserToChannel(th.BasicUser, channel)
|
|
|
|
// Create a thread in the channel
|
|
post := &model.Post{
|
|
UserId: th.BasicUser.Id,
|
|
ChannelId: channel.Id,
|
|
Message: "test",
|
|
}
|
|
post, appErr := th.App.CreatePost(th.Context, post, channel, model.CreatePostFlags{})
|
|
require.Nil(t, appErr)
|
|
|
|
// Post a reply to the thread
|
|
reply := &model.Post{
|
|
UserId: th.BasicUser.Id,
|
|
ChannelId: channel.Id,
|
|
RootId: post.Id,
|
|
Message: "reply",
|
|
}
|
|
_, appErr = th.App.CreatePost(th.Context, reply, channel, model.CreatePostFlags{})
|
|
require.Nil(t, appErr)
|
|
|
|
// Check that the thread count before move
|
|
threads, appErr := th.App.GetThreadsForUser(th.Context, th.BasicUser.Id, targetTeam.Id, model.GetUserThreadsOpts{})
|
|
require.Nil(t, appErr)
|
|
|
|
require.Zero(t, threads.Total)
|
|
|
|
// Move the channel to the target team
|
|
appErr = th.App.MoveChannel(th.Context, targetTeam, channel, th.BasicUser)
|
|
require.Nil(t, appErr)
|
|
|
|
// Check that the thread was moved
|
|
threads, appErr = th.App.GetThreadsForUser(th.Context, th.BasicUser.Id, targetTeam.Id, model.GetUserThreadsOpts{})
|
|
require.Nil(t, appErr)
|
|
|
|
require.Equal(t, int64(1), threads.Total)
|
|
// Check that the thread count after move
|
|
threads, appErr = th.App.GetThreadsForUser(th.Context, th.BasicUser.Id, sourceTeam.Id, model.GetUserThreadsOpts{})
|
|
require.Nil(t, appErr)
|
|
|
|
require.Zero(t, threads.Total)
|
|
})
|
|
}
|
|
|
|
func TestRemoveUsersFromChannelNotMemberOfTeam(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
team := th.CreateTeam()
|
|
team2 := th.CreateTeam()
|
|
channel1 := th.CreateChannel(th.Context, team)
|
|
defer func() {
|
|
appErr := th.App.PermanentDeleteChannel(th.Context, channel1)
|
|
require.Nil(t, appErr)
|
|
|
|
appErr = th.App.PermanentDeleteTeam(th.Context, team)
|
|
require.Nil(t, appErr)
|
|
|
|
appErr = th.App.PermanentDeleteTeam(th.Context, team2)
|
|
require.Nil(t, appErr)
|
|
}()
|
|
|
|
_, _, appErr := th.App.AddUserToTeam(th.Context, team.Id, th.BasicUser.Id, "")
|
|
require.Nil(t, appErr)
|
|
_, _, appErr = th.App.AddUserToTeam(th.Context, team2.Id, th.BasicUser.Id, "")
|
|
require.Nil(t, appErr)
|
|
_, _, appErr = th.App.AddUserToTeam(th.Context, team.Id, th.BasicUser2.Id, "")
|
|
require.Nil(t, appErr)
|
|
|
|
_, appErr = th.App.AddUserToChannel(th.Context, th.BasicUser, channel1, false)
|
|
require.Nil(t, appErr)
|
|
_, appErr = th.App.AddUserToChannel(th.Context, th.BasicUser2, channel1, false)
|
|
require.Nil(t, appErr)
|
|
|
|
appErr = th.App.RemoveUsersFromChannelNotMemberOfTeam(th.Context, th.SystemAdminUser, channel1, team2)
|
|
require.Nil(t, appErr)
|
|
|
|
channelMembers, appErr := th.App.GetChannelMembersPage(th.Context, channel1.Id, 0, 10000000)
|
|
require.Nil(t, appErr)
|
|
require.Len(t, channelMembers, 1)
|
|
members := make([]model.ChannelMember, len(channelMembers))
|
|
copy(members, channelMembers)
|
|
require.Equal(t, members[0].UserId, th.BasicUser.Id)
|
|
}
|
|
|
|
func TestJoinDefaultChannelsCreatesChannelMemberHistoryRecordTownSquare(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
// figure out the initial number of users in town square
|
|
channel, err := th.App.Srv().Store().Channel().GetByName(th.BasicTeam.Id, "town-square", true)
|
|
require.NoError(t, err)
|
|
townSquareChannelID := channel.Id
|
|
users, nErr := th.App.Srv().Store().ChannelMemberHistory().GetUsersInChannelDuring(model.GetMillis()-100, model.GetMillis()+100, []string{townSquareChannelID})
|
|
require.NoError(t, nErr)
|
|
initialNumTownSquareUsers := len(users)
|
|
|
|
// create a new user that joins the default channels
|
|
user := th.CreateUser()
|
|
appErr := th.App.JoinDefaultChannels(th.Context, th.BasicTeam.Id, user, false, "")
|
|
require.Nil(t, appErr)
|
|
|
|
// there should be a ChannelMemberHistory record for the user
|
|
histories, nErr := th.App.Srv().Store().ChannelMemberHistory().GetUsersInChannelDuring(model.GetMillis()-100, model.GetMillis()+100, []string{townSquareChannelID})
|
|
require.NoError(t, nErr)
|
|
assert.Len(t, histories, initialNumTownSquareUsers+1)
|
|
|
|
found := false
|
|
for _, history := range histories {
|
|
if user.Id == history.UserId && townSquareChannelID == history.ChannelId {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
assert.True(t, found)
|
|
}
|
|
|
|
func TestJoinDefaultChannelsCreatesChannelMemberHistoryRecordOffTopic(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
// figure out the initial number of users in off-topic
|
|
channel, err := th.App.Srv().Store().Channel().GetByName(th.BasicTeam.Id, "off-topic", true)
|
|
require.NoError(t, err)
|
|
offTopicChannelId := channel.Id
|
|
users, nErr := th.App.Srv().Store().ChannelMemberHistory().GetUsersInChannelDuring(model.GetMillis()-100, model.GetMillis()+100, []string{offTopicChannelId})
|
|
require.NoError(t, nErr)
|
|
initialNumTownSquareUsers := len(users)
|
|
|
|
// create a new user that joins the default channels
|
|
user := th.CreateUser()
|
|
appError := th.App.JoinDefaultChannels(th.Context, th.BasicTeam.Id, user, false, "")
|
|
require.Nil(t, appError)
|
|
|
|
// there should be a ChannelMemberHistory record for the user
|
|
histories, nErr := th.App.Srv().Store().ChannelMemberHistory().GetUsersInChannelDuring(model.GetMillis()-100, model.GetMillis()+100, []string{offTopicChannelId})
|
|
require.NoError(t, nErr)
|
|
assert.Len(t, histories, initialNumTownSquareUsers+1)
|
|
|
|
found := false
|
|
for _, history := range histories {
|
|
if user.Id == history.UserId && offTopicChannelId == history.ChannelId {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
assert.True(t, found)
|
|
}
|
|
|
|
func TestJoinDefaultChannelsExperimentalDefaultChannels(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
basicChannel2 := th.CreateChannel(th.Context, th.BasicTeam)
|
|
defer func() {
|
|
appErr := th.App.PermanentDeleteChannel(th.Context, basicChannel2)
|
|
require.Nil(t, appErr)
|
|
}()
|
|
defaultChannelList := []string{th.BasicChannel.Name, basicChannel2.Name, basicChannel2.Name}
|
|
th.App.Config().TeamSettings.ExperimentalDefaultChannels = defaultChannelList
|
|
|
|
user := th.CreateUser()
|
|
appErr := th.App.JoinDefaultChannels(th.Context, th.BasicTeam.Id, user, false, "")
|
|
require.Nil(t, appErr)
|
|
|
|
for _, channelName := range defaultChannelList {
|
|
channel, appErr := th.App.GetChannelByName(th.Context, channelName, th.BasicTeam.Id, false)
|
|
require.Nil(t, appErr, "Expected nil, didn't receive nil")
|
|
|
|
member, appErr := th.App.GetChannelMember(th.Context, channel.Id, user.Id)
|
|
require.Nil(t, appErr, "Expected nil object, didn't receive nil")
|
|
require.NotNil(t, member, "Expected member object, got nil")
|
|
}
|
|
}
|
|
|
|
func TestJoinDefaultChannelsExperimentalDefaultChannelsMissing(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
basicChannel2 := th.CreateChannel(th.Context, th.BasicTeam)
|
|
defer func() {
|
|
appErr := th.App.PermanentDeleteChannel(th.Context, basicChannel2)
|
|
require.Nil(t, appErr)
|
|
}()
|
|
defaultChannelList := []string{th.BasicChannel.Name, basicChannel2.Name, "thischanneldoesnotexist", basicChannel2.Name}
|
|
th.App.Config().TeamSettings.ExperimentalDefaultChannels = defaultChannelList
|
|
|
|
user := th.CreateUser()
|
|
require.Nil(t, th.App.JoinDefaultChannels(th.Context, th.BasicTeam.Id, user, false, ""))
|
|
|
|
for _, channelName := range defaultChannelList {
|
|
if channelName == "thischanneldoesnotexist" {
|
|
continue // skip the non-existent channel
|
|
}
|
|
|
|
channel, appErr := th.App.GetChannelByName(th.Context, channelName, th.BasicTeam.Id, false)
|
|
require.Nil(t, appErr, "Expected nil, didn't receive nil")
|
|
|
|
member, appErr := th.App.GetChannelMember(th.Context, channel.Id, user.Id)
|
|
|
|
require.NotNil(t, member, "Expected member object, got nil")
|
|
require.Nil(t, appErr, "Expected nil object, didn't receive nil")
|
|
}
|
|
}
|
|
|
|
func TestCreateChannelPublicCreatesChannelMemberHistoryRecord(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
// creates a public channel and adds basic user to it
|
|
publicChannel := th.createChannel(th.Context, th.BasicTeam, model.ChannelTypeOpen)
|
|
|
|
// there should be a ChannelMemberHistory record for the user
|
|
histories, err := th.App.Srv().Store().ChannelMemberHistory().GetUsersInChannelDuring(model.GetMillis()-100, model.GetMillis()+100, []string{publicChannel.Id})
|
|
require.NoError(t, err)
|
|
assert.Len(t, histories, 1)
|
|
assert.Equal(t, th.BasicUser.Id, histories[0].UserId)
|
|
assert.Equal(t, publicChannel.Id, histories[0].ChannelId)
|
|
}
|
|
|
|
func TestCreateChannelPrivateCreatesChannelMemberHistoryRecord(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
// creates a private channel and adds basic user to it
|
|
privateChannel := th.createChannel(th.Context, th.BasicTeam, model.ChannelTypePrivate)
|
|
|
|
// there should be a ChannelMemberHistory record for the user
|
|
histories, err := th.App.Srv().Store().ChannelMemberHistory().GetUsersInChannelDuring(model.GetMillis()-100, model.GetMillis()+100, []string{privateChannel.Id})
|
|
require.NoError(t, err)
|
|
assert.Len(t, histories, 1)
|
|
assert.Equal(t, th.BasicUser.Id, histories[0].UserId)
|
|
assert.Equal(t, privateChannel.Id, histories[0].ChannelId)
|
|
}
|
|
|
|
func TestCreateChannelDisplayNameTrimsWhitespace(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
channel, appErr := th.App.CreateChannel(th.Context, &model.Channel{DisplayName: " Public 1 ", Name: "public1", Type: model.ChannelTypeOpen, TeamId: th.BasicTeam.Id}, false)
|
|
defer func() {
|
|
appErr = th.App.PermanentDeleteChannel(th.Context, channel)
|
|
require.Nil(t, appErr)
|
|
}()
|
|
require.Nil(t, appErr)
|
|
require.Equal(t, channel.DisplayName, "Public 1")
|
|
}
|
|
|
|
func TestUpdateChannelPrivacy(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
privateChannel := th.createChannel(th.Context, th.BasicTeam, model.ChannelTypePrivate)
|
|
privateChannel.Type = model.ChannelTypeOpen
|
|
|
|
publicChannel, appErr := th.App.UpdateChannelPrivacy(th.Context, privateChannel, th.BasicUser)
|
|
require.Nil(t, appErr, "Failed to update channel privacy.")
|
|
assert.Equal(t, publicChannel.Id, privateChannel.Id)
|
|
assert.Equal(t, publicChannel.Type, model.ChannelTypeOpen)
|
|
}
|
|
|
|
func TestGetOrCreateDirectChannel(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
team1 := th.CreateTeam()
|
|
team2 := th.CreateTeam()
|
|
|
|
user1 := th.CreateUser()
|
|
th.LinkUserToTeam(user1, team1)
|
|
|
|
user2 := th.CreateUser()
|
|
th.LinkUserToTeam(user2, team2)
|
|
|
|
bot1 := th.CreateBot()
|
|
|
|
t.Run("Bot can create with restriction", func(t *testing.T) {
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
setting := model.DirectMessageTeam
|
|
cfg.TeamSettings.RestrictDirectMessage = &setting
|
|
})
|
|
|
|
// Create a session for the bot owner so IsBotOwnedByCurrentUserOrPlugin can work
|
|
session, err := th.App.CreateSession(th.Context, &model.Session{
|
|
UserId: th.BasicUser.Id,
|
|
Roles: th.BasicUser.GetRawRoles(),
|
|
})
|
|
require.Nil(t, err)
|
|
rctx := th.Context.WithSession(session)
|
|
|
|
// check with bot in first userid param
|
|
channel, appErr := th.App.GetOrCreateDirectChannel(rctx, bot1.UserId, user1.Id)
|
|
require.NotNil(t, channel, "channel should be non-nil")
|
|
require.Nil(t, appErr)
|
|
|
|
// check with bot in second userid param
|
|
channel, appErr = th.App.GetOrCreateDirectChannel(rctx, user1.Id, bot1.UserId)
|
|
require.NotNil(t, channel, "channel should be non-nil")
|
|
require.Nil(t, appErr)
|
|
})
|
|
|
|
t.Run("User from other team cannot create with restriction", func(t *testing.T) {
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
setting := model.DirectMessageTeam
|
|
cfg.TeamSettings.RestrictDirectMessage = &setting
|
|
})
|
|
|
|
channel, appErr := th.App.GetOrCreateDirectChannel(th.Context, user1.Id, user2.Id)
|
|
require.Nil(t, channel, "channel should be nil")
|
|
require.NotNil(t, appErr)
|
|
})
|
|
|
|
t.Run("Cannot create with a remote user", func(t *testing.T) {
|
|
user2.RemoteId = model.NewPointer(model.NewId())
|
|
_, appErr := th.App.UpdateUser(th.Context, user2, false)
|
|
require.Nil(t, appErr)
|
|
|
|
dm, appErr := th.App.GetOrCreateDirectChannel(th.Context, user1.Id, user2.Id)
|
|
require.Nil(t, dm)
|
|
require.NotNil(t, appErr)
|
|
})
|
|
}
|
|
|
|
func TestCreateGroupChannel(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
user1 := th.CreateUser()
|
|
user2 := th.CreateUser()
|
|
|
|
groupUserIds := make([]string, 0)
|
|
groupUserIds = append(groupUserIds, user1.Id)
|
|
groupUserIds = append(groupUserIds, user2.Id)
|
|
groupUserIds = append(groupUserIds, th.BasicUser.Id)
|
|
|
|
t.Run("Should not allow to create a group with a remote user", func(t *testing.T) {
|
|
user2.RemoteId = model.NewPointer(model.NewId())
|
|
_, appErr := th.App.UpdateUser(th.Context, user2, false)
|
|
require.Nil(t, appErr)
|
|
|
|
dm, appErr := th.App.CreateGroupChannel(th.Context, groupUserIds, th.BasicUser.Id)
|
|
require.NotNil(t, appErr)
|
|
require.Nil(t, dm)
|
|
})
|
|
}
|
|
|
|
func TestCreateGroupChannelCreatesChannelMemberHistoryRecord(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
user1 := th.CreateUser()
|
|
user2 := th.CreateUser()
|
|
|
|
groupUserIds := make([]string, 0)
|
|
groupUserIds = append(groupUserIds, user1.Id)
|
|
groupUserIds = append(groupUserIds, user2.Id)
|
|
groupUserIds = append(groupUserIds, th.BasicUser.Id)
|
|
|
|
channel, appErr := th.App.CreateGroupChannel(th.Context, groupUserIds, th.BasicUser.Id)
|
|
|
|
require.Nil(t, appErr, "Failed to create group channel.")
|
|
histories, nErr := th.App.Srv().Store().ChannelMemberHistory().GetUsersInChannelDuring(model.GetMillis()-100, model.GetMillis()+100, []string{channel.Id})
|
|
require.NoError(t, nErr)
|
|
assert.Len(t, histories, 3)
|
|
|
|
channelMemberHistoryUserIds := make([]string, 0)
|
|
for _, history := range histories {
|
|
assert.Equal(t, channel.Id, history.ChannelId)
|
|
channelMemberHistoryUserIds = append(channelMemberHistoryUserIds, history.UserId)
|
|
}
|
|
|
|
sort.Strings(groupUserIds)
|
|
sort.Strings(channelMemberHistoryUserIds)
|
|
assert.Equal(t, groupUserIds, channelMemberHistoryUserIds)
|
|
}
|
|
|
|
func TestCreateDirectChannelCreatesChannelMemberHistoryRecord(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t)
|
|
defer th.TearDown()
|
|
|
|
user1 := th.CreateUser()
|
|
user2 := th.CreateUser()
|
|
|
|
channel, appErr := th.App.GetOrCreateDirectChannel(th.Context, user1.Id, user2.Id)
|
|
require.Nil(t, appErr, "Failed to create direct channel.")
|
|
|
|
histories, nErr := th.App.Srv().Store().ChannelMemberHistory().GetUsersInChannelDuring(model.GetMillis()-100, model.GetMillis()+100, []string{channel.Id})
|
|
require.NoError(t, nErr)
|
|
assert.Len(t, histories, 2)
|
|
|
|
historyId0 := histories[0].UserId
|
|
historyId1 := histories[1].UserId
|
|
switch historyId0 {
|
|
case user1.Id:
|
|
assert.Equal(t, user2.Id, historyId1)
|
|
case user2.Id:
|
|
assert.Equal(t, user1.Id, historyId1)
|
|
default:
|
|
require.Fail(t, "Unexpected user id in ChannelMemberHistory table", historyId0)
|
|
}
|
|
}
|
|
|
|
func TestGetDirectChannelCreatesChannelMemberHistoryRecord(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t)
|
|
defer th.TearDown()
|
|
|
|
user1 := th.CreateUser()
|
|
user2 := th.CreateUser()
|
|
|
|
// this function call implicitly creates a direct channel between the two users if one doesn't already exist
|
|
channel, appErr := th.App.GetOrCreateDirectChannel(th.Context, user1.Id, user2.Id)
|
|
require.Nil(t, appErr, "Failed to create direct channel.")
|
|
|
|
// there should be a ChannelMemberHistory record for both users
|
|
histories, nErr := th.App.Srv().Store().ChannelMemberHistory().GetUsersInChannelDuring(model.GetMillis()-100, model.GetMillis()+100, []string{channel.Id})
|
|
require.NoError(t, nErr)
|
|
assert.Len(t, histories, 2)
|
|
|
|
historyId0 := histories[0].UserId
|
|
historyId1 := histories[1].UserId
|
|
switch historyId0 {
|
|
case user1.Id:
|
|
assert.Equal(t, user2.Id, historyId1)
|
|
case user2.Id:
|
|
assert.Equal(t, user1.Id, historyId1)
|
|
default:
|
|
require.Fail(t, "Unexpected user id in ChannelMemberHistory table", historyId0)
|
|
}
|
|
}
|
|
|
|
func TestAddUserToChannelCreatesChannelMemberHistoryRecord(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic().DeleteBots()
|
|
defer th.TearDown()
|
|
|
|
// create a user and add it to a channel
|
|
user := th.CreateUser()
|
|
_, appErr := th.App.AddTeamMember(th.Context, th.BasicTeam.Id, user.Id)
|
|
require.Nil(t, appErr, "Failed to add user to team.")
|
|
|
|
groupUserIds := make([]string, 0)
|
|
groupUserIds = append(groupUserIds, th.BasicUser.Id)
|
|
groupUserIds = append(groupUserIds, user.Id)
|
|
|
|
channel := th.createChannel(th.Context, th.BasicTeam, model.ChannelTypeOpen)
|
|
|
|
_, appErr = th.App.AddUserToChannel(th.Context, user, channel, false)
|
|
require.Nil(t, appErr, "Failed to add user to channel.")
|
|
|
|
// there should be a ChannelMemberHistory record for the user
|
|
histories, nErr := th.App.Srv().Store().ChannelMemberHistory().GetUsersInChannelDuring(model.GetMillis()-100, model.GetMillis()+100, []string{channel.Id})
|
|
require.NoError(t, nErr)
|
|
assert.Len(t, histories, 2)
|
|
channelMemberHistoryUserIds := make([]string, 0)
|
|
for _, history := range histories {
|
|
assert.Equal(t, channel.Id, history.ChannelId)
|
|
channelMemberHistoryUserIds = append(channelMemberHistoryUserIds, history.UserId)
|
|
}
|
|
assert.Equal(t, groupUserIds, channelMemberHistoryUserIds)
|
|
}
|
|
|
|
func TestUsersAndPostsCreateActivityInChannel(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic().DeleteBots()
|
|
defer th.TearDown()
|
|
|
|
user := th.CreateUser()
|
|
_, err := th.App.AddTeamMember(th.Context, th.BasicTeam.Id, user.Id)
|
|
require.Nil(t, err, "Failed to add user to team.")
|
|
user3 := th.CreateUser()
|
|
_, err = th.App.AddTeamMember(th.Context, th.BasicTeam.Id, user3.Id)
|
|
require.Nil(t, err, "Failed to add user to team.")
|
|
user4 := th.CreateUser()
|
|
_, err = th.App.AddTeamMember(th.Context, th.BasicTeam.Id, user4.Id)
|
|
require.Nil(t, err, "Failed to add user to team.")
|
|
|
|
channel1 := th.createChannel(th.Context, th.BasicTeam, model.ChannelTypeOpen)
|
|
channel2 := th.createChannel(th.Context, th.BasicTeam, model.ChannelTypeOpen)
|
|
channel3 := th.createChannel(th.Context, th.BasicTeam, model.ChannelTypeOpen)
|
|
channel4 := th.createChannel(th.Context, th.BasicTeam, model.ChannelTypeOpen)
|
|
channel5 := th.createChannel(th.Context, th.BasicTeam, model.ChannelTypeOpen)
|
|
channel6 := th.createChannel(th.Context, th.BasicTeam, model.ChannelTypeOpen)
|
|
|
|
// user3 is already in channel3
|
|
_, err = th.App.AddUserToChannel(th.Context, user3, channel3, false)
|
|
require.Nil(t, err, "Failed to add user to channel.")
|
|
// user4 is already in channel4 (for the second part of the test)
|
|
_, err = th.App.AddUserToChannel(th.Context, user4, channel4, false)
|
|
require.Nil(t, err, "Failed to add user to channel.")
|
|
|
|
// make sure we don't catch earlier posts
|
|
time.Sleep(10 * time.Millisecond)
|
|
testStart := model.GetMillis()
|
|
|
|
// Test: previous activity (user3 and 4's adds) aren't showing up:
|
|
channelIds, nErr := th.App.Srv().Store().ChannelMemberHistory().GetChannelsWithActivityDuring(testStart, testStart+10000)
|
|
require.NoError(t, nErr)
|
|
assert.Len(t, channelIds, 0)
|
|
|
|
// Posts, adds, and leaves should create activity
|
|
post := &model.Post{
|
|
ChannelId: channel1.Id,
|
|
Message: "root post",
|
|
UserId: th.BasicUser.Id,
|
|
}
|
|
_, err = th.App.CreatePost(th.Context, post, channel1, model.CreatePostFlags{})
|
|
require.Nil(t, err, "Failed to create post.")
|
|
|
|
_, err = th.App.AddUserToChannel(th.Context, user, channel2, false)
|
|
require.Nil(t, err, "Failed to add user to channel.")
|
|
|
|
err = th.App.RemoveUserFromChannel(th.Context, user3.Id, user3.Id, channel3)
|
|
require.Nil(t, err, "Failed to add user to channel.")
|
|
|
|
// Test: there should be a ChannelMemberHistory record for the users and the post
|
|
channelIds, nErr = th.App.Srv().Store().ChannelMemberHistory().GetChannelsWithActivityDuring(testStart, model.GetMillis())
|
|
require.NoError(t, nErr)
|
|
assert.Len(t, channelIds, 3)
|
|
assert.ElementsMatch(t, []string{channel1.Id, channel2.Id, channel3.Id}, channelIds)
|
|
|
|
testEnd := model.GetMillis()
|
|
// In case the tests are running very fast:
|
|
time.Sleep(10 * time.Millisecond)
|
|
|
|
// Now, we do not find activity for new posts, leaves, or adds after the test is over
|
|
post2 := &model.Post{
|
|
ChannelId: channel5.Id,
|
|
Message: "root post",
|
|
UserId: th.BasicUser.Id,
|
|
}
|
|
err = th.App.RemoveUserFromChannel(th.Context, user4.Id, user4.Id, channel4)
|
|
require.Nil(t, err, "Failed to create post.")
|
|
_, err = th.App.CreatePost(th.Context, post2, channel5, model.CreatePostFlags{})
|
|
require.Nil(t, err, "Failed to create post.")
|
|
_, err = th.App.AddUserToChannel(th.Context, user, channel6, false)
|
|
require.Nil(t, err, "Failed to add user to channel.")
|
|
|
|
// Test: we get the same three channels as before, not channels 4, 5, 6 which have activity after testEnd
|
|
channelIds, nErr = th.App.Srv().Store().ChannelMemberHistory().GetChannelsWithActivityDuring(testStart, testEnd)
|
|
require.NoError(t, nErr)
|
|
assert.Len(t, channelIds, 3)
|
|
assert.ElementsMatch(t, []string{channel1.Id, channel2.Id, channel3.Id}, channelIds)
|
|
}
|
|
|
|
func TestLeaveDefaultChannel(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
guest := th.CreateGuest()
|
|
th.LinkUserToTeam(guest, th.BasicTeam)
|
|
|
|
townSquare, appErr := th.App.GetChannelByName(th.Context, "town-square", th.BasicTeam.Id, false)
|
|
require.Nil(t, appErr)
|
|
th.AddUserToChannel(guest, townSquare)
|
|
th.AddUserToChannel(th.BasicUser, townSquare)
|
|
|
|
t.Run("User tries to leave the default channel", func(t *testing.T) {
|
|
appErr = th.App.LeaveChannel(th.Context, townSquare.Id, th.BasicUser.Id)
|
|
assert.NotNil(t, appErr, "It should fail to remove a regular user from the default channel")
|
|
assert.Equal(t, appErr.Id, "api.channel.remove.default.app_error")
|
|
_, appErr = th.App.GetChannelMember(th.Context, townSquare.Id, th.BasicUser.Id)
|
|
assert.Nil(t, appErr)
|
|
})
|
|
|
|
t.Run("Guest leaves the default channel", func(t *testing.T) {
|
|
appErr = th.App.LeaveChannel(th.Context, townSquare.Id, guest.Id)
|
|
assert.Nil(t, appErr, "It should allow to remove a guest user from the default channel")
|
|
_, appErr = th.App.GetChannelMember(th.Context, townSquare.Id, guest.Id)
|
|
assert.NotNil(t, appErr)
|
|
})
|
|
|
|
t.Run("Trying to leave the default channel should not delete thread memberships", func(t *testing.T) {
|
|
post := &model.Post{
|
|
ChannelId: townSquare.Id,
|
|
Message: "root post",
|
|
UserId: th.BasicUser.Id,
|
|
}
|
|
rpost, appErr := th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, appErr)
|
|
|
|
reply := &model.Post{
|
|
ChannelId: townSquare.Id,
|
|
Message: "reply post",
|
|
UserId: th.BasicUser.Id,
|
|
RootId: rpost.Id,
|
|
}
|
|
_, appErr = th.App.CreatePost(th.Context, reply, th.BasicChannel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, appErr)
|
|
|
|
threads, appErr := th.App.GetThreadsForUser(th.Context, th.BasicUser.Id, townSquare.TeamId, model.GetUserThreadsOpts{})
|
|
require.Nil(t, appErr)
|
|
require.Len(t, threads.Threads, 1)
|
|
|
|
appErr = th.App.LeaveChannel(th.Context, townSquare.Id, th.BasicUser.Id)
|
|
assert.NotNil(t, appErr, "It should fail to remove a regular user from the default channel")
|
|
assert.Equal(t, appErr.Id, "api.channel.remove.default.app_error")
|
|
|
|
threads, appErr = th.App.GetThreadsForUser(th.Context, th.BasicUser.Id, townSquare.TeamId, model.GetUserThreadsOpts{})
|
|
require.Nil(t, appErr)
|
|
require.Len(t, threads.Threads, 1)
|
|
})
|
|
}
|
|
|
|
func TestLeaveChannel(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
createThread := func(channel *model.Channel) (rpost *model.Post) {
|
|
t.Helper()
|
|
post := &model.Post{
|
|
ChannelId: channel.Id,
|
|
Message: "root post",
|
|
UserId: th.BasicUser.Id,
|
|
}
|
|
|
|
rpost, appErr := th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, appErr)
|
|
|
|
reply := &model.Post{
|
|
ChannelId: channel.Id,
|
|
Message: "reply post",
|
|
UserId: th.BasicUser.Id,
|
|
RootId: rpost.Id,
|
|
}
|
|
_, appErr = th.App.CreatePost(th.Context, reply, th.BasicChannel, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, appErr)
|
|
|
|
return rpost
|
|
}
|
|
|
|
t.Run("thread memberships are deleted", func(t *testing.T) {
|
|
createThread(th.BasicChannel)
|
|
channel2 := th.createChannel(th.Context, th.BasicTeam, model.ChannelTypeOpen)
|
|
createThread(channel2)
|
|
|
|
threads, appErr := th.App.GetThreadsForUser(th.Context, th.BasicUser.Id, th.BasicChannel.TeamId, model.GetUserThreadsOpts{})
|
|
require.Nil(t, appErr)
|
|
require.Len(t, threads.Threads, 2)
|
|
|
|
appErr = th.App.LeaveChannel(th.Context, th.BasicChannel.Id, th.BasicUser.Id)
|
|
require.Nil(t, appErr)
|
|
|
|
_, appErr = th.App.GetChannelMember(th.Context, th.BasicChannel.Id, th.BasicUser.Id)
|
|
require.NotNil(t, appErr, "It should remove channel membership")
|
|
|
|
threads, appErr = th.App.GetThreadsForUser(th.Context, th.BasicUser.Id, th.BasicChannel.TeamId, model.GetUserThreadsOpts{})
|
|
require.Nil(t, appErr)
|
|
require.Len(t, threads.Threads, 1)
|
|
})
|
|
|
|
t.Run("can leave private channel as last member", func(t *testing.T) {
|
|
channel := th.createChannel(th.Context, th.BasicTeam, model.ChannelTypePrivate)
|
|
|
|
count, appErr := th.App.GetChannelMemberCount(th.Context, channel.Id)
|
|
require.Nil(t, appErr, "It should get the channel member count")
|
|
require.Equal(t, int64(1), count)
|
|
|
|
appErr = th.App.LeaveChannel(th.Context, channel.Id, th.BasicUser.Id)
|
|
require.Nil(t, appErr)
|
|
})
|
|
}
|
|
|
|
func TestLeaveLastChannel(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
guest := th.CreateGuest()
|
|
th.LinkUserToTeam(guest, th.BasicTeam)
|
|
|
|
townSquare, appErr := th.App.GetChannelByName(th.Context, "town-square", th.BasicTeam.Id, false)
|
|
require.Nil(t, appErr)
|
|
th.AddUserToChannel(guest, townSquare)
|
|
th.AddUserToChannel(guest, th.BasicChannel)
|
|
|
|
t.Run("Guest leaves not last channel", func(t *testing.T) {
|
|
appErr = th.App.LeaveChannel(th.Context, townSquare.Id, guest.Id)
|
|
require.Nil(t, appErr)
|
|
_, appErr = th.App.GetTeamMember(th.Context, th.BasicTeam.Id, guest.Id)
|
|
assert.Nil(t, appErr, "It should maintain the team membership")
|
|
})
|
|
|
|
t.Run("Guest leaves last channel", func(t *testing.T) {
|
|
appErr = th.App.LeaveChannel(th.Context, th.BasicChannel.Id, guest.Id)
|
|
assert.Nil(t, appErr, "It should allow to remove a guest user from the default channel")
|
|
_, appErr = th.App.GetChannelMember(th.Context, th.BasicChannel.Id, guest.Id)
|
|
assert.NotNil(t, appErr)
|
|
_, appErr = th.App.GetTeamMember(th.Context, th.BasicTeam.Id, guest.Id)
|
|
assert.Nil(t, appErr, "It should remove the team membership")
|
|
})
|
|
}
|
|
|
|
func TestAddChannelMemberNoUserRequestor(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
// create a user and add it to a channel
|
|
user := th.CreateUser()
|
|
_, appErr := th.App.AddTeamMember(th.Context, th.BasicTeam.Id, user.Id)
|
|
require.Nil(t, appErr)
|
|
|
|
groupUserIds := make([]string, 0)
|
|
groupUserIds = append(groupUserIds, th.BasicUser.Id)
|
|
groupUserIds = append(groupUserIds, user.Id)
|
|
|
|
channel := th.createChannel(th.Context, th.BasicTeam, model.ChannelTypeOpen)
|
|
|
|
_, appErr = th.App.AddChannelMember(th.Context, user.Id, channel, ChannelMemberOpts{})
|
|
require.Nil(t, appErr, "Failed to add user to channel.")
|
|
|
|
// there should be a ChannelMemberHistory record for the user
|
|
histories, nErr := th.App.Srv().Store().ChannelMemberHistory().GetUsersInChannelDuring(model.GetMillis()-100, model.GetMillis()+100, []string{channel.Id})
|
|
require.NoError(t, nErr)
|
|
assert.Len(t, histories, 2)
|
|
channelMemberHistoryUserIds := make([]string, 0)
|
|
for _, history := range histories {
|
|
assert.Equal(t, channel.Id, history.ChannelId)
|
|
channelMemberHistoryUserIds = append(channelMemberHistoryUserIds, history.UserId)
|
|
}
|
|
assert.Equal(t, groupUserIds, channelMemberHistoryUserIds)
|
|
|
|
postList, nErr := th.App.Srv().Store().Post().GetPosts(th.Context, model.GetPostsOptions{ChannelId: channel.Id, Page: 0, PerPage: 1}, false, map[string]bool{})
|
|
require.NoError(t, nErr)
|
|
|
|
if assert.Len(t, postList.Order, 1) {
|
|
post := postList.Posts[postList.Order[0]]
|
|
|
|
assert.Equal(t, model.PostTypeJoinChannel, post.Type)
|
|
assert.Equal(t, user.Id, post.UserId)
|
|
assert.Equal(t, user.Username, post.GetProp("username"))
|
|
}
|
|
}
|
|
|
|
func TestAddChannelMemberDeletedUser(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
user := th.CreateUser()
|
|
_, appErr := th.App.AddTeamMember(th.Context, th.BasicTeam.Id, user.Id)
|
|
require.Nil(t, appErr)
|
|
|
|
deactivated, appErr := th.App.UpdateActive(th.Context, user, false)
|
|
require.Greater(t, deactivated.DeleteAt, int64(0))
|
|
|
|
require.Nil(t, appErr)
|
|
_, appErr = th.App.AddChannelMember(th.Context, user.Id, th.BasicChannel, ChannelMemberOpts{})
|
|
require.NotNil(t, appErr)
|
|
}
|
|
|
|
func TestAppUpdateChannelScheme(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
channel := th.BasicChannel
|
|
mockID := model.NewPointer("x")
|
|
channel.SchemeId = mockID
|
|
|
|
updatedChannel, appErr := th.App.UpdateChannelScheme(th.Context, channel)
|
|
require.Nil(t, appErr)
|
|
|
|
if updatedChannel.SchemeId != mockID {
|
|
require.Fail(t, "Wrong Channel SchemeId")
|
|
}
|
|
}
|
|
|
|
func TestSetChannelsMuted(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
t.Run("should mute and unmute the given channels", func(t *testing.T) {
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
channel1 := th.BasicChannel
|
|
|
|
channel2 := th.CreateChannel(th.Context, th.BasicTeam)
|
|
th.AddUserToChannel(th.BasicUser, channel2)
|
|
|
|
// Ensure that both channels start unmuted
|
|
member1, appErr := th.App.GetChannelMember(th.Context, channel1.Id, th.BasicUser.Id)
|
|
require.Nil(t, appErr)
|
|
require.False(t, member1.IsChannelMuted())
|
|
|
|
member2, appErr := th.App.GetChannelMember(th.Context, channel2.Id, th.BasicUser.Id)
|
|
require.Nil(t, appErr)
|
|
require.False(t, member2.IsChannelMuted())
|
|
|
|
// Mute both channels
|
|
updated, appErr := th.App.setChannelsMuted(th.Context, []string{channel1.Id, channel2.Id}, th.BasicUser.Id, true)
|
|
require.Nil(t, appErr)
|
|
assert.True(t, updated[0].IsChannelMuted())
|
|
assert.True(t, updated[1].IsChannelMuted())
|
|
|
|
// Verify that the channels are muted in the database
|
|
member1, appErr = th.App.GetChannelMember(th.Context, channel1.Id, th.BasicUser.Id)
|
|
require.Nil(t, appErr)
|
|
require.True(t, member1.IsChannelMuted())
|
|
|
|
member2, appErr = th.App.GetChannelMember(th.Context, channel2.Id, th.BasicUser.Id)
|
|
require.Nil(t, appErr)
|
|
require.True(t, member2.IsChannelMuted())
|
|
|
|
// Unm both channels
|
|
updated, appErr = th.App.setChannelsMuted(th.Context, []string{channel1.Id, channel2.Id}, th.BasicUser.Id, false)
|
|
require.Nil(t, appErr)
|
|
assert.False(t, updated[0].IsChannelMuted())
|
|
assert.False(t, updated[1].IsChannelMuted())
|
|
|
|
// Verify that the channels are muted in the database
|
|
member1, appErr = th.App.GetChannelMember(th.Context, channel1.Id, th.BasicUser.Id)
|
|
require.Nil(t, appErr)
|
|
require.False(t, member1.IsChannelMuted())
|
|
|
|
member2, appErr = th.App.GetChannelMember(th.Context, channel2.Id, th.BasicUser.Id)
|
|
require.Nil(t, appErr)
|
|
require.False(t, member2.IsChannelMuted())
|
|
})
|
|
}
|
|
|
|
func TestFillInChannelProps(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
channelPublic1, appErr := th.App.CreateChannel(th.Context, &model.Channel{DisplayName: "Public 1", Name: "public1", Type: model.ChannelTypeOpen, TeamId: th.BasicTeam.Id}, false)
|
|
require.Nil(t, appErr)
|
|
defer func() {
|
|
appErr = th.App.PermanentDeleteChannel(th.Context, channelPublic1)
|
|
require.Nil(t, appErr)
|
|
}()
|
|
|
|
channelPublic2, appErr := th.App.CreateChannel(th.Context, &model.Channel{DisplayName: "Public 2", Name: "public2", Type: model.ChannelTypeOpen, TeamId: th.BasicTeam.Id}, false)
|
|
require.Nil(t, appErr)
|
|
defer func() {
|
|
appErr = th.App.PermanentDeleteChannel(th.Context, channelPublic2)
|
|
require.Nil(t, appErr)
|
|
}()
|
|
|
|
channelPrivate, appErr := th.App.CreateChannel(th.Context, &model.Channel{DisplayName: "Private", Name: "private", Type: model.ChannelTypePrivate, TeamId: th.BasicTeam.Id}, false)
|
|
require.Nil(t, appErr)
|
|
defer func() {
|
|
appErr = th.App.PermanentDeleteChannel(th.Context, channelPrivate)
|
|
require.Nil(t, appErr)
|
|
}()
|
|
|
|
otherTeamId := model.NewId()
|
|
otherTeam := &model.Team{
|
|
DisplayName: "dn_" + otherTeamId,
|
|
Name: "name" + otherTeamId,
|
|
Email: "success+" + otherTeamId + "@simulator.amazonses.com",
|
|
Type: model.TeamOpen,
|
|
}
|
|
otherTeam, appErr = th.App.CreateTeam(th.Context, otherTeam)
|
|
require.Nil(t, appErr)
|
|
defer func() {
|
|
appErr = th.App.PermanentDeleteTeam(th.Context, otherTeam)
|
|
require.Nil(t, appErr)
|
|
}()
|
|
|
|
channelOtherTeam, appErr := th.App.CreateChannel(th.Context, &model.Channel{DisplayName: "Other Team Channel", Name: "other-team", Type: model.ChannelTypeOpen, TeamId: otherTeam.Id}, false)
|
|
require.Nil(t, appErr)
|
|
defer func() {
|
|
appErr = th.App.PermanentDeleteChannel(th.Context, channelOtherTeam)
|
|
require.Nil(t, appErr)
|
|
}()
|
|
|
|
// Note that purpose is intentionally plaintext below.
|
|
|
|
t.Run("single channels", func(t *testing.T) {
|
|
testCases := []struct {
|
|
Description string
|
|
Channel *model.Channel
|
|
ExpectedChannelProps map[string]any
|
|
}{
|
|
{
|
|
"channel on basic team without references",
|
|
&model.Channel{
|
|
TeamId: th.BasicTeam.Id,
|
|
Header: "No references",
|
|
Purpose: "No references",
|
|
},
|
|
nil,
|
|
},
|
|
{
|
|
"channel on basic team",
|
|
&model.Channel{
|
|
TeamId: th.BasicTeam.Id,
|
|
Header: "~public1, ~private, ~other-team",
|
|
Purpose: "~public2, ~private, ~other-team",
|
|
},
|
|
map[string]any{
|
|
"channel_mentions": map[string]any{
|
|
"public1": map[string]any{
|
|
"display_name": "Public 1",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
"channel on other team",
|
|
&model.Channel{
|
|
TeamId: otherTeam.Id,
|
|
Header: "~public1, ~private, ~other-team",
|
|
Purpose: "~public2, ~private, ~other-team",
|
|
},
|
|
map[string]any{
|
|
"channel_mentions": map[string]any{
|
|
"other-team": map[string]any{
|
|
"display_name": "Other Team Channel",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, testCase := range testCases {
|
|
t.Run(testCase.Description, func(t *testing.T) {
|
|
appErr = th.App.FillInChannelProps(th.Context, testCase.Channel)
|
|
require.Nil(t, appErr)
|
|
|
|
assert.Equal(t, testCase.ExpectedChannelProps, testCase.Channel.Props)
|
|
})
|
|
}
|
|
})
|
|
|
|
t.Run("multiple channels", func(t *testing.T) {
|
|
testCases := []struct {
|
|
Description string
|
|
Channels model.ChannelList
|
|
ExpectedChannelProps map[string]any
|
|
}{
|
|
{
|
|
"single channel on basic team",
|
|
model.ChannelList{
|
|
{
|
|
Name: "test",
|
|
TeamId: th.BasicTeam.Id,
|
|
Header: "~public1, ~private, ~other-team",
|
|
Purpose: "~public2, ~private, ~other-team",
|
|
},
|
|
},
|
|
map[string]any{
|
|
"test": map[string]any{
|
|
"channel_mentions": map[string]any{
|
|
"public1": map[string]any{
|
|
"display_name": "Public 1",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
"multiple channels on basic team",
|
|
model.ChannelList{
|
|
{
|
|
Name: "test",
|
|
TeamId: th.BasicTeam.Id,
|
|
Header: "~public1, ~private, ~other-team",
|
|
Purpose: "~public2, ~private, ~other-team",
|
|
},
|
|
{
|
|
Name: "test2",
|
|
TeamId: th.BasicTeam.Id,
|
|
Header: "~private, ~other-team",
|
|
Purpose: "~public2, ~private, ~other-team",
|
|
},
|
|
{
|
|
Name: "test3",
|
|
TeamId: th.BasicTeam.Id,
|
|
Header: "No references",
|
|
Purpose: "No references",
|
|
},
|
|
},
|
|
map[string]any{
|
|
"test": map[string]any{
|
|
"channel_mentions": map[string]any{
|
|
"public1": map[string]any{
|
|
"display_name": "Public 1",
|
|
},
|
|
},
|
|
},
|
|
"test2": map[string]any(nil),
|
|
"test3": map[string]any(nil),
|
|
},
|
|
},
|
|
{
|
|
"multiple channels across teams",
|
|
model.ChannelList{
|
|
{
|
|
Name: "test",
|
|
TeamId: th.BasicTeam.Id,
|
|
Header: "~public1, ~private, ~other-team",
|
|
Purpose: "~public2, ~private, ~other-team",
|
|
},
|
|
{
|
|
Name: "test2",
|
|
TeamId: otherTeam.Id,
|
|
Header: "~private, ~other-team",
|
|
Purpose: "~public2, ~private, ~other-team",
|
|
},
|
|
{
|
|
Name: "test3",
|
|
TeamId: th.BasicTeam.Id,
|
|
Header: "No references",
|
|
Purpose: "No references",
|
|
},
|
|
},
|
|
map[string]any{
|
|
"test": map[string]any{
|
|
"channel_mentions": map[string]any{
|
|
"public1": map[string]any{
|
|
"display_name": "Public 1",
|
|
},
|
|
},
|
|
},
|
|
"test2": map[string]any{
|
|
"channel_mentions": map[string]any{
|
|
"other-team": map[string]any{
|
|
"display_name": "Other Team Channel",
|
|
},
|
|
},
|
|
},
|
|
"test3": map[string]any(nil),
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, testCase := range testCases {
|
|
t.Run(testCase.Description, func(t *testing.T) {
|
|
appErr = th.App.FillInChannelsProps(th.Context, testCase.Channels)
|
|
require.Nil(t, appErr)
|
|
|
|
for _, channel := range testCase.Channels {
|
|
assert.Equal(t, testCase.ExpectedChannelProps[channel.Name], channel.Props)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestRenameChannel(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
testCases := []struct {
|
|
Name string
|
|
Channel *model.Channel
|
|
ExpectError bool
|
|
ChannelName string
|
|
ExpectedName string
|
|
ExpectedDisplayName string
|
|
}{
|
|
{
|
|
"Rename open channel",
|
|
th.createChannel(th.Context, th.BasicTeam, model.ChannelTypeOpen),
|
|
false,
|
|
"newchannelname",
|
|
"newchannelname",
|
|
"New Display Name",
|
|
},
|
|
{
|
|
"Fail on rename open channel with bad name",
|
|
th.createChannel(th.Context, th.BasicTeam, model.ChannelTypeOpen),
|
|
true,
|
|
"6zii9a9g6pruzj451x3esok54h__wr4j4g8zqtnhmkw771pfpynqwo",
|
|
"",
|
|
"",
|
|
},
|
|
{
|
|
"Success on rename open channel with consecutive underscores in name",
|
|
th.createChannel(th.Context, th.BasicTeam, model.ChannelTypeOpen),
|
|
false,
|
|
"foo__bar",
|
|
"foo__bar",
|
|
"New Display Name",
|
|
},
|
|
{
|
|
"Fail on rename direct message channel",
|
|
th.CreateDmChannel(th.BasicUser2),
|
|
true,
|
|
"newchannelname",
|
|
"",
|
|
"",
|
|
},
|
|
{
|
|
"Fail on rename group message channel",
|
|
th.CreateGroupChannel(th.Context, th.BasicUser2, th.CreateUser()),
|
|
true,
|
|
"newchannelname",
|
|
"",
|
|
"",
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.Name, func(t *testing.T) {
|
|
channel, err := th.App.RenameChannel(th.Context, tc.Channel, tc.ChannelName, "New Display Name")
|
|
if tc.ExpectError {
|
|
assert.NotNil(t, err)
|
|
} else {
|
|
assert.Equal(t, tc.ExpectedName, channel.Name)
|
|
assert.Equal(t, tc.ExpectedDisplayName, channel.DisplayName)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetChannelMembersTimezones(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
_, appErr := th.App.AddChannelMember(th.Context, th.BasicUser2.Id, th.BasicChannel, ChannelMemberOpts{})
|
|
require.Nil(t, appErr, "Failed to add user to channel.")
|
|
|
|
user := th.BasicUser
|
|
user.Timezone["useAutomaticTimezone"] = "false"
|
|
user.Timezone["manualTimezone"] = "XOXO/BLABLA"
|
|
_, appErr = th.App.UpdateUser(th.Context, user, false)
|
|
require.Nil(t, appErr)
|
|
|
|
user2 := th.BasicUser2
|
|
user2.Timezone["automaticTimezone"] = "NoWhere/Island"
|
|
_, appErr = th.App.UpdateUser(th.Context, user2, false)
|
|
require.Nil(t, appErr)
|
|
|
|
user3 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""}
|
|
ruser, appErr := th.App.CreateUser(th.Context, &user3)
|
|
require.Nil(t, appErr)
|
|
|
|
_, _, appErr = th.App.AddUserToTeam(th.Context, th.BasicTeam.Id, ruser.Id, "")
|
|
require.Nil(t, appErr)
|
|
|
|
_, appErr = th.App.AddUserToChannel(th.Context, ruser, th.BasicChannel, false)
|
|
require.Nil(t, appErr)
|
|
|
|
ruser.Timezone["automaticTimezone"] = "NoWhere/Island"
|
|
_, appErr = th.App.UpdateUser(th.Context, ruser, false)
|
|
require.Nil(t, appErr)
|
|
|
|
user4 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""}
|
|
ruser, _ = th.App.CreateUser(th.Context, &user4)
|
|
_, appErr = th.App.AddUserToChannel(th.Context, ruser, th.BasicChannel, false)
|
|
require.NotNil(t, appErr, "user should not be able to join the channel without being in the team.")
|
|
|
|
timezones, appErr := th.App.GetChannelMembersTimezones(th.Context, th.BasicChannel.Id)
|
|
require.Nil(t, appErr, "Failed to get the timezones for a channel.")
|
|
|
|
assert.Equal(t, 2, len(timezones))
|
|
}
|
|
|
|
func TestGetChannelsForUser(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
channel := &model.Channel{
|
|
DisplayName: "Public",
|
|
Name: "public",
|
|
Type: model.ChannelTypeOpen,
|
|
CreatorId: th.BasicUser.Id,
|
|
TeamId: th.BasicTeam.Id,
|
|
}
|
|
_, appErr := th.App.CreateChannel(th.Context, channel, true)
|
|
require.Nil(t, appErr)
|
|
defer func() {
|
|
appErr = th.App.PermanentDeleteChannel(th.Context, channel)
|
|
require.Nil(t, appErr)
|
|
}()
|
|
defer th.TearDown()
|
|
|
|
channelList, appErr := th.App.GetChannelsForTeamForUser(th.Context, th.BasicTeam.Id, th.BasicUser.Id, &model.ChannelSearchOpts{
|
|
IncludeDeleted: false,
|
|
LastDeleteAt: 0,
|
|
})
|
|
require.Nil(t, appErr)
|
|
require.Len(t, channelList, 4)
|
|
|
|
appErr = th.App.DeleteChannel(th.Context, channel, th.BasicUser.Id)
|
|
require.Nil(t, appErr)
|
|
|
|
// Now we get all the non-archived channels for the user
|
|
channelList, appErr = th.App.GetChannelsForTeamForUser(th.Context, th.BasicTeam.Id, th.BasicUser.Id, &model.ChannelSearchOpts{
|
|
IncludeDeleted: false,
|
|
LastDeleteAt: 0,
|
|
})
|
|
require.Nil(t, appErr)
|
|
require.Len(t, channelList, 3)
|
|
|
|
// Now we get all the channels, even though are archived, for the user
|
|
channelList, appErr = th.App.GetChannelsForTeamForUser(th.Context, th.BasicTeam.Id, th.BasicUser.Id, &model.ChannelSearchOpts{
|
|
IncludeDeleted: true,
|
|
LastDeleteAt: 0,
|
|
})
|
|
require.Nil(t, appErr)
|
|
require.Len(t, channelList, 4)
|
|
}
|
|
|
|
func TestGetPublicChannelsForTeam(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t)
|
|
team := th.CreateTeam()
|
|
defer th.TearDown()
|
|
|
|
var expectedChannels []*model.Channel
|
|
|
|
townSquare, appErr := th.App.GetChannelByName(th.Context, "town-square", team.Id, false)
|
|
require.Nil(t, appErr)
|
|
require.NotNil(t, townSquare)
|
|
expectedChannels = append(expectedChannels, townSquare)
|
|
|
|
offTopic, appErr := th.App.GetChannelByName(th.Context, "off-topic", team.Id, false)
|
|
require.Nil(t, appErr)
|
|
require.NotNil(t, offTopic)
|
|
expectedChannels = append(expectedChannels, offTopic)
|
|
|
|
for i := range 8 {
|
|
channel := model.Channel{
|
|
DisplayName: fmt.Sprintf("Public %v", i),
|
|
Name: fmt.Sprintf("public_%v", i),
|
|
Type: model.ChannelTypeOpen,
|
|
TeamId: team.Id,
|
|
}
|
|
var rchannel *model.Channel
|
|
rchannel, appErr = th.App.CreateChannel(th.Context, &channel, false)
|
|
require.Nil(t, appErr)
|
|
require.NotNil(t, rchannel)
|
|
defer func() {
|
|
appErr = th.App.PermanentDeleteChannel(th.Context, rchannel)
|
|
require.Nil(t, appErr)
|
|
}()
|
|
|
|
// Store the user ids for comparison later
|
|
expectedChannels = append(expectedChannels, rchannel)
|
|
}
|
|
|
|
// Fetch public channels multiple times
|
|
channelList, appErr := th.App.GetPublicChannelsForTeam(th.Context, team.Id, 0, 5)
|
|
require.Nil(t, appErr)
|
|
channelList2, appErr := th.App.GetPublicChannelsForTeam(th.Context, team.Id, 5, 5)
|
|
require.Nil(t, appErr)
|
|
|
|
channels := append(channelList, channelList2...)
|
|
assert.ElementsMatch(t, expectedChannels, channels)
|
|
}
|
|
|
|
func TestGetPrivateChannelsForTeam(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t)
|
|
team := th.CreateTeam()
|
|
defer th.TearDown()
|
|
|
|
var expectedChannels []*model.Channel
|
|
for i := range 8 {
|
|
channel := model.Channel{
|
|
DisplayName: fmt.Sprintf("Private %v", i),
|
|
Name: fmt.Sprintf("private_%v", i),
|
|
Type: model.ChannelTypePrivate,
|
|
TeamId: team.Id,
|
|
}
|
|
var rchannel *model.Channel
|
|
rchannel, appErr := th.App.CreateChannel(th.Context, &channel, false)
|
|
require.Nil(t, appErr)
|
|
require.NotNil(t, rchannel)
|
|
defer func() {
|
|
appErr := th.App.PermanentDeleteChannel(th.Context, rchannel)
|
|
require.Nil(t, appErr)
|
|
}()
|
|
|
|
// Store the user ids for comparison later
|
|
expectedChannels = append(expectedChannels, rchannel)
|
|
}
|
|
|
|
// Fetch private channels multiple times
|
|
channelList, appErr := th.App.GetPrivateChannelsForTeam(th.Context, team.Id, 0, 5)
|
|
require.Nil(t, appErr)
|
|
channelList2, appErr := th.App.GetPrivateChannelsForTeam(th.Context, team.Id, 5, 5)
|
|
require.Nil(t, appErr)
|
|
|
|
channels := append(channelList, channelList2...)
|
|
assert.ElementsMatch(t, expectedChannels, channels)
|
|
}
|
|
|
|
func TestUpdateChannelMemberRolesChangingGuest(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
t.Run("from guest to user", func(t *testing.T) {
|
|
user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""}
|
|
ruser, _ := th.App.CreateGuest(th.Context, &user)
|
|
|
|
_, _, appErr := th.App.AddUserToTeam(th.Context, th.BasicTeam.Id, ruser.Id, "")
|
|
require.Nil(t, appErr)
|
|
|
|
_, appErr = th.App.AddUserToChannel(th.Context, ruser, th.BasicChannel, false)
|
|
require.Nil(t, appErr)
|
|
|
|
_, appErr = th.App.UpdateChannelMemberRoles(th.Context, th.BasicChannel.Id, ruser.Id, "channel_user")
|
|
require.NotNil(t, appErr, "Should fail when try to modify the guest role")
|
|
})
|
|
|
|
t.Run("from user to guest", func(t *testing.T) {
|
|
user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""}
|
|
ruser, _ := th.App.CreateUser(th.Context, &user)
|
|
|
|
_, _, appErr := th.App.AddUserToTeam(th.Context, th.BasicTeam.Id, ruser.Id, "")
|
|
require.Nil(t, appErr)
|
|
|
|
_, appErr = th.App.AddUserToChannel(th.Context, ruser, th.BasicChannel, false)
|
|
require.Nil(t, appErr)
|
|
|
|
_, appErr = th.App.UpdateChannelMemberRoles(th.Context, th.BasicChannel.Id, ruser.Id, "channel_guest")
|
|
require.NotNil(t, appErr, "Should fail when try to modify the guest role")
|
|
})
|
|
|
|
t.Run("from user to admin", func(t *testing.T) {
|
|
user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""}
|
|
ruser, _ := th.App.CreateUser(th.Context, &user)
|
|
|
|
_, _, appErr := th.App.AddUserToTeam(th.Context, th.BasicTeam.Id, ruser.Id, "")
|
|
require.Nil(t, appErr)
|
|
|
|
_, appErr = th.App.AddUserToChannel(th.Context, ruser, th.BasicChannel, false)
|
|
require.Nil(t, appErr)
|
|
|
|
_, appErr = th.App.UpdateChannelMemberRoles(th.Context, th.BasicChannel.Id, ruser.Id, "channel_user channel_admin")
|
|
require.Nil(t, appErr, "Should work when you not modify guest role")
|
|
})
|
|
|
|
t.Run("from guest to guest plus custom", func(t *testing.T) {
|
|
user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""}
|
|
ruser, _ := th.App.CreateGuest(th.Context, &user)
|
|
|
|
_, _, appErr := th.App.AddUserToTeam(th.Context, th.BasicTeam.Id, ruser.Id, "")
|
|
require.Nil(t, appErr)
|
|
|
|
_, appErr = th.App.AddUserToChannel(th.Context, ruser, th.BasicChannel, false)
|
|
require.Nil(t, appErr)
|
|
|
|
_, appErr = th.App.CreateRole(&model.Role{Name: "custom", DisplayName: "custom", Description: "custom"})
|
|
require.Nil(t, appErr)
|
|
|
|
_, appErr = th.App.UpdateChannelMemberRoles(th.Context, th.BasicChannel.Id, ruser.Id, "channel_guest custom")
|
|
require.Nil(t, appErr, "Should work when you not modify guest role")
|
|
})
|
|
|
|
t.Run("a guest cant have user role", func(t *testing.T) {
|
|
user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""}
|
|
ruser, _ := th.App.CreateGuest(th.Context, &user)
|
|
|
|
_, _, appErr := th.App.AddUserToTeam(th.Context, th.BasicTeam.Id, ruser.Id, "")
|
|
require.Nil(t, appErr)
|
|
|
|
_, appErr = th.App.AddUserToChannel(th.Context, ruser, th.BasicChannel, false)
|
|
require.Nil(t, appErr)
|
|
|
|
_, appErr = th.App.UpdateChannelMemberRoles(th.Context, th.BasicChannel.Id, ruser.Id, "channel_guest channel_user")
|
|
require.NotNil(t, appErr, "Should work when you not modify guest role")
|
|
})
|
|
}
|
|
|
|
func TestDefaultChannelNames(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t)
|
|
defer th.TearDown()
|
|
|
|
actual := th.App.DefaultChannelNames(th.Context)
|
|
expect := []string{"town-square", "off-topic"}
|
|
require.ElementsMatch(t, expect, actual)
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
cfg.TeamSettings.ExperimentalDefaultChannels = []string{"foo", "bar"}
|
|
})
|
|
|
|
actual = th.App.DefaultChannelNames(th.Context)
|
|
expect = []string{"town-square", "foo", "bar"}
|
|
require.ElementsMatch(t, expect, actual)
|
|
}
|
|
|
|
func TestSearchChannelsForUser(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
c1, appErr := th.App.CreateChannel(th.Context, &model.Channel{DisplayName: "test-dev-1", Name: "test-dev-1", Type: model.ChannelTypeOpen, TeamId: th.BasicTeam.Id}, false)
|
|
require.Nil(t, appErr)
|
|
|
|
c2, appErr := th.App.CreateChannel(th.Context, &model.Channel{DisplayName: "test-dev-2", Name: "test-dev-2", Type: model.ChannelTypeOpen, TeamId: th.BasicTeam.Id}, false)
|
|
require.Nil(t, appErr)
|
|
|
|
c3, appErr := th.App.CreateChannel(th.Context, &model.Channel{DisplayName: "dev-3", Name: "dev-3", Type: model.ChannelTypeOpen, TeamId: th.BasicTeam.Id}, false)
|
|
require.Nil(t, appErr)
|
|
defer func() {
|
|
appErr = th.App.PermanentDeleteChannel(th.Context, c1)
|
|
require.Nil(t, appErr)
|
|
|
|
appErr = th.App.PermanentDeleteChannel(th.Context, c2)
|
|
require.Nil(t, appErr)
|
|
|
|
appErr = th.App.PermanentDeleteChannel(th.Context, c3)
|
|
require.Nil(t, appErr)
|
|
}()
|
|
|
|
// add user to test-dev-1 and dev3
|
|
_, appErr = th.App.AddUserToChannel(th.Context, th.BasicUser, c1, false)
|
|
require.Nil(t, appErr)
|
|
_, appErr = th.App.AddUserToChannel(th.Context, th.BasicUser, c3, false)
|
|
require.Nil(t, appErr)
|
|
|
|
searchAndCheck := func(t *testing.T, term string, expectedDisplayNames []string) {
|
|
res, searchErr := th.App.SearchChannelsForUser(th.Context, th.BasicUser.Id, th.BasicTeam.Id, term)
|
|
require.Nil(t, searchErr)
|
|
require.Len(t, res, len(expectedDisplayNames))
|
|
|
|
resultDisplayNames := []string{}
|
|
for _, c := range res {
|
|
resultDisplayNames = append(resultDisplayNames, c.Name)
|
|
}
|
|
require.ElementsMatch(t, expectedDisplayNames, resultDisplayNames)
|
|
}
|
|
|
|
t.Run("Search for test, only test-dev-1 should be returned", func(t *testing.T) {
|
|
searchAndCheck(t, "test", []string{"test-dev-1"})
|
|
})
|
|
|
|
t.Run("Search for dev, both test-dev-1 and dev-3 should be returned", func(t *testing.T) {
|
|
searchAndCheck(t, "dev", []string{"test-dev-1", "dev-3"})
|
|
})
|
|
|
|
t.Run("After adding user to test-dev-2, search for dev, the three channels should be returned", func(t *testing.T) {
|
|
_, appErr = th.App.AddUserToChannel(th.Context, th.BasicUser, c2, false)
|
|
require.Nil(t, appErr)
|
|
|
|
searchAndCheck(t, "dev", []string{"test-dev-1", "test-dev-2", "dev-3"})
|
|
})
|
|
}
|
|
|
|
func TestMarkChannelAsUnreadFromPost(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
u1 := th.BasicUser
|
|
u2 := th.BasicUser2
|
|
c1 := th.BasicChannel
|
|
pc1 := th.CreatePrivateChannel(th.Context, th.BasicTeam)
|
|
th.AddUserToChannel(u2, c1)
|
|
th.AddUserToChannel(u1, pc1)
|
|
th.AddUserToChannel(u2, pc1)
|
|
|
|
p1 := th.CreatePost(c1)
|
|
p2 := th.CreatePost(c1)
|
|
p3 := th.CreatePost(c1)
|
|
|
|
pp1 := th.CreatePost(pc1)
|
|
require.NotNil(t, pp1)
|
|
pp2 := th.CreatePost(pc1)
|
|
|
|
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.MarkChannelsAsViewed(th.Context, []string{c1.Id, pc1.Id}, u1.Id, "", false, false)
|
|
require.Nil(t, appErr)
|
|
_, appErr = th.App.MarkChannelsAsViewed(th.Context, []string{c1.Id, pc1.Id}, u2.Id, "", false, 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 but last one", func(t *testing.T) {
|
|
response, appErr := th.App.MarkChannelAsUnreadFromPost(th.Context, p2.Id, u1.Id, true)
|
|
require.Nil(t, appErr)
|
|
require.NotNil(t, response)
|
|
assert.Equal(t, int64(2), response.MsgCount)
|
|
unread, appErr := th.App.GetChannelUnread(th.Context, c1.Id, u1.Id)
|
|
require.Nil(t, appErr)
|
|
assert.Equal(t, int64(2), unread.MsgCount)
|
|
assert.Equal(t, p2.CreateAt-1, response.LastViewedAt)
|
|
})
|
|
|
|
t.Run("Unread last one", func(t *testing.T) {
|
|
response, appErr := th.App.MarkChannelAsUnreadFromPost(th.Context, p3.Id, u1.Id, true)
|
|
require.Nil(t, appErr)
|
|
require.NotNil(t, response)
|
|
assert.Equal(t, int64(3), response.MsgCount)
|
|
unread, appErr := th.App.GetChannelUnread(th.Context, c1.Id, u1.Id)
|
|
require.Nil(t, appErr)
|
|
assert.Equal(t, int64(1), unread.MsgCount)
|
|
assert.Equal(t, p3.CreateAt-1, response.LastViewedAt)
|
|
})
|
|
|
|
t.Run("Unread first one", func(t *testing.T) {
|
|
response, appErr := th.App.MarkChannelAsUnreadFromPost(th.Context, p1.Id, u1.Id, true)
|
|
require.Nil(t, appErr)
|
|
require.NotNil(t, response)
|
|
assert.Equal(t, int64(1), response.MsgCount)
|
|
unread, appErr := th.App.GetChannelUnread(th.Context, c1.Id, u1.Id)
|
|
require.Nil(t, appErr)
|
|
assert.Equal(t, int64(3), unread.MsgCount)
|
|
assert.Equal(t, p1.CreateAt-1, response.LastViewedAt)
|
|
})
|
|
|
|
t.Run("Other users are unaffected", func(t *testing.T) {
|
|
unread, appErr := th.App.GetChannelUnread(th.Context, c1.Id, u2.Id)
|
|
require.Nil(t, appErr)
|
|
assert.Equal(t, int64(0), unread.MsgCount)
|
|
})
|
|
|
|
t.Run("Unread on a private channel", func(t *testing.T) {
|
|
response, appErr := th.App.MarkChannelAsUnreadFromPost(th.Context, pp1.Id, u1.Id, true)
|
|
require.Nil(t, appErr)
|
|
require.NotNil(t, response)
|
|
assert.Equal(t, int64(0), response.MsgCount)
|
|
unread, appErr := th.App.GetChannelUnread(th.Context, pc1.Id, u1.Id)
|
|
require.Nil(t, appErr)
|
|
assert.Equal(t, int64(2), unread.MsgCount)
|
|
assert.Equal(t, pp1.CreateAt-1, response.LastViewedAt)
|
|
|
|
response, appErr = th.App.MarkChannelAsUnreadFromPost(th.Context, pp2.Id, u1.Id, true)
|
|
assert.Nil(t, appErr)
|
|
assert.Equal(t, int64(1), response.MsgCount)
|
|
unread, appErr = th.App.GetChannelUnread(th.Context, pc1.Id, u1.Id)
|
|
require.Nil(t, appErr)
|
|
assert.Equal(t, int64(1), unread.MsgCount)
|
|
assert.Equal(t, pp2.CreateAt-1, response.LastViewedAt)
|
|
})
|
|
|
|
t.Run("Unread with mentions", func(t *testing.T) {
|
|
c2 := th.CreateChannel(th.Context, th.BasicTeam)
|
|
_, appErr := th.App.AddUserToChannel(th.Context, u2, c2, false)
|
|
require.Nil(t, appErr)
|
|
|
|
p4, appErr := th.App.CreatePost(th.Context, &model.Post{
|
|
UserId: u2.Id,
|
|
ChannelId: c2.Id,
|
|
Message: "@" + u1.Username,
|
|
}, c2, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, appErr)
|
|
th.CreatePost(c2)
|
|
|
|
_, appErr = th.App.CreatePost(th.Context, &model.Post{
|
|
UserId: u2.Id,
|
|
ChannelId: c2.Id,
|
|
RootId: p4.Id,
|
|
Message: "@" + u1.Username,
|
|
}, c2, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, appErr)
|
|
|
|
response, appErr := th.App.MarkChannelAsUnreadFromPost(th.Context, p4.Id, u1.Id, true)
|
|
assert.Nil(t, appErr)
|
|
assert.Equal(t, int64(1), response.MsgCount)
|
|
assert.Equal(t, int64(2), response.MentionCount)
|
|
assert.Equal(t, int64(1), response.MentionCountRoot)
|
|
|
|
unread, appErr := th.App.GetChannelUnread(th.Context, c2.Id, u1.Id)
|
|
require.Nil(t, appErr)
|
|
assert.Equal(t, int64(2), unread.MsgCount)
|
|
assert.Equal(t, int64(2), unread.MentionCount)
|
|
assert.Equal(t, int64(1), unread.MentionCountRoot)
|
|
})
|
|
|
|
t.Run("Unread on a DM channel", func(t *testing.T) {
|
|
dc := th.CreateDmChannel(u2)
|
|
|
|
dm1 := th.CreatePost(dc)
|
|
th.CreatePost(dc)
|
|
th.CreatePost(dc)
|
|
|
|
_, appErr := th.App.CreatePost(th.Context, &model.Post{ChannelId: dc.Id, UserId: th.BasicUser.Id, Message: "testReply", RootId: dm1.Id}, dc, model.CreatePostFlags{})
|
|
assert.Nil(t, appErr)
|
|
|
|
response, appErr := th.App.MarkChannelAsUnreadFromPost(th.Context, dm1.Id, u2.Id, true)
|
|
assert.Nil(t, appErr)
|
|
assert.Equal(t, int64(0), response.MsgCount)
|
|
assert.Equal(t, int64(4), response.MentionCount)
|
|
assert.Equal(t, int64(3), response.MentionCountRoot)
|
|
|
|
unread, appErr := th.App.GetChannelUnread(th.Context, dc.Id, u2.Id)
|
|
require.Nil(t, appErr)
|
|
assert.Equal(t, int64(4), unread.MsgCount)
|
|
assert.Equal(t, int64(4), unread.MentionCount)
|
|
assert.Equal(t, int64(3), unread.MentionCountRoot)
|
|
})
|
|
|
|
t.Run("Can't unread an imaginary post", func(t *testing.T) {
|
|
response, err := th.App.MarkChannelAsUnreadFromPost(th.Context, "invalid4ofngungryquinj976y", u1.Id, true)
|
|
assert.NotNil(t, err)
|
|
assert.Nil(t, response)
|
|
})
|
|
}
|
|
|
|
func TestAddUserToChannel(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
user1 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""}
|
|
ruser1, _ := th.App.CreateUser(th.Context, &user1)
|
|
defer func() {
|
|
appErr := th.App.PermanentDeleteUser(th.Context, &user1)
|
|
require.Nil(t, appErr)
|
|
}()
|
|
bot := th.CreateBot()
|
|
botUser, _ := th.App.GetUser(bot.UserId)
|
|
defer func() {
|
|
appErr := th.App.PermanentDeleteBot(th.Context, botUser.Id)
|
|
require.Nil(t, appErr)
|
|
}()
|
|
|
|
_, appErr := th.App.AddTeamMember(th.Context, th.BasicTeam.Id, ruser1.Id)
|
|
require.Nil(t, appErr)
|
|
_, appErr = th.App.AddTeamMember(th.Context, th.BasicTeam.Id, bot.UserId)
|
|
require.Nil(t, appErr)
|
|
|
|
group := th.CreateGroup()
|
|
|
|
_, appErr = th.App.UpsertGroupMember(group.Id, user1.Id)
|
|
require.Nil(t, appErr)
|
|
|
|
gs, appErr := th.App.UpsertGroupSyncable(&model.GroupSyncable{
|
|
AutoAdd: true,
|
|
SyncableId: th.BasicChannel.Id,
|
|
Type: model.GroupSyncableTypeChannel,
|
|
GroupId: group.Id,
|
|
SchemeAdmin: false,
|
|
})
|
|
require.Nil(t, appErr)
|
|
|
|
appErr = th.App.JoinChannel(th.Context, th.BasicChannel, ruser1.Id)
|
|
require.Nil(t, appErr)
|
|
|
|
// verify user was added as a non-admin
|
|
cm1, appErr := th.App.GetChannelMember(th.Context, th.BasicChannel.Id, ruser1.Id)
|
|
require.Nil(t, appErr)
|
|
require.False(t, cm1.SchemeAdmin)
|
|
|
|
user2 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""}
|
|
ruser2, _ := th.App.CreateUser(th.Context, &user2)
|
|
defer func() {
|
|
appErr = th.App.PermanentDeleteUser(th.Context, &user2)
|
|
require.Nil(t, appErr)
|
|
}()
|
|
|
|
_, appErr = th.App.AddTeamMember(th.Context, th.BasicTeam.Id, ruser2.Id)
|
|
require.Nil(t, appErr)
|
|
|
|
_, appErr = th.App.UpsertGroupMember(group.Id, user2.Id)
|
|
require.Nil(t, appErr)
|
|
|
|
gs.SchemeAdmin = true
|
|
_, appErr = th.App.UpdateGroupSyncable(gs)
|
|
require.Nil(t, appErr)
|
|
|
|
appErr = th.App.JoinChannel(th.Context, th.BasicChannel, ruser2.Id)
|
|
require.Nil(t, appErr)
|
|
|
|
// Should allow a bot to be added to a public group synced channel
|
|
_, appErr = th.App.AddUserToChannel(th.Context, botUser, th.BasicChannel, false)
|
|
require.Nil(t, appErr)
|
|
|
|
// verify user was added as an admin
|
|
cm2, appErr := th.App.GetChannelMember(th.Context, th.BasicChannel.Id, ruser2.Id)
|
|
require.Nil(t, appErr)
|
|
require.True(t, cm2.SchemeAdmin)
|
|
|
|
privateChannel := th.CreatePrivateChannel(th.Context, th.BasicTeam)
|
|
privateChannel.GroupConstrained = model.NewPointer(true)
|
|
_, appErr = th.App.UpdateChannel(th.Context, privateChannel)
|
|
require.Nil(t, appErr)
|
|
|
|
_, appErr = th.App.UpsertGroupSyncable(&model.GroupSyncable{
|
|
GroupId: group.Id,
|
|
SyncableId: privateChannel.Id,
|
|
Type: model.GroupSyncableTypeChannel,
|
|
})
|
|
require.Nil(t, appErr)
|
|
|
|
// Should allow a group synced user to be added to a group synced private channel
|
|
_, appErr = th.App.AddUserToChannel(th.Context, ruser1, privateChannel, false)
|
|
require.Nil(t, appErr)
|
|
|
|
// Should allow a bot to be added to a private group synced channel
|
|
_, appErr = th.App.AddUserToChannel(th.Context, botUser, privateChannel, false)
|
|
require.Nil(t, appErr)
|
|
}
|
|
|
|
func TestRemoveUserFromChannel(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""}
|
|
ruser, _ := th.App.CreateUser(th.Context, &user)
|
|
defer func() {
|
|
appErr := th.App.PermanentDeleteUser(th.Context, ruser)
|
|
require.Nil(t, appErr)
|
|
}()
|
|
|
|
bot := th.CreateBot()
|
|
botUser, _ := th.App.GetUser(bot.UserId)
|
|
defer func() {
|
|
appErr := th.App.PermanentDeleteBot(th.Context, botUser.Id)
|
|
require.Nil(t, appErr)
|
|
}()
|
|
|
|
_, appErr := th.App.AddTeamMember(th.Context, th.BasicTeam.Id, ruser.Id)
|
|
require.Nil(t, appErr)
|
|
|
|
_, appErr = th.App.AddTeamMember(th.Context, th.BasicTeam.Id, bot.UserId)
|
|
require.Nil(t, appErr)
|
|
|
|
privateChannel := th.CreatePrivateChannel(th.Context, th.BasicTeam)
|
|
|
|
_, appErr = th.App.AddUserToChannel(th.Context, ruser, privateChannel, false)
|
|
require.Nil(t, appErr)
|
|
_, appErr = th.App.AddUserToChannel(th.Context, botUser, privateChannel, false)
|
|
require.Nil(t, appErr)
|
|
|
|
group := th.CreateGroup()
|
|
_, appErr = th.App.UpsertGroupMember(group.Id, ruser.Id)
|
|
require.Nil(t, appErr)
|
|
|
|
_, appErr = th.App.UpsertGroupSyncable(&model.GroupSyncable{
|
|
GroupId: group.Id,
|
|
SyncableId: privateChannel.Id,
|
|
Type: model.GroupSyncableTypeChannel,
|
|
})
|
|
require.Nil(t, appErr)
|
|
|
|
privateChannel.GroupConstrained = model.NewPointer(true)
|
|
_, appErr = th.App.UpdateChannel(th.Context, privateChannel)
|
|
require.Nil(t, appErr)
|
|
|
|
// Should not allow a group synced user to be removed from channel
|
|
appErr = th.App.RemoveUserFromChannel(th.Context, ruser.Id, th.SystemAdminUser.Id, privateChannel)
|
|
assert.Equal(t, appErr.Id, "api.channel.remove_members.denied")
|
|
|
|
// Should allow a user to remove themselves from group synced channel
|
|
appErr = th.App.RemoveUserFromChannel(th.Context, ruser.Id, ruser.Id, privateChannel)
|
|
require.Nil(t, appErr)
|
|
|
|
// Should allow a bot to be removed from a group synced channel
|
|
appErr = th.App.RemoveUserFromChannel(th.Context, botUser.Id, th.SystemAdminUser.Id, privateChannel)
|
|
require.Nil(t, appErr)
|
|
}
|
|
|
|
func TestPatchChannelModerationsForChannel(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
err := th.App.SetPhase2PermissionsMigrationStatus(true)
|
|
require.NoError(t, err)
|
|
channel := th.BasicChannel
|
|
|
|
user := th.BasicUser
|
|
th.AddUserToChannel(user, channel)
|
|
|
|
createPosts := model.ChannelModeratedPermissions[0]
|
|
createReactions := model.ChannelModeratedPermissions[1]
|
|
manageMembers := model.ChannelModeratedPermissions[2]
|
|
channelMentions := model.ChannelModeratedPermissions[3]
|
|
manageBookmarks := model.ChannelModeratedPermissions[4]
|
|
|
|
nonChannelModeratedPermission := model.PermissionCreateBot.Id
|
|
|
|
testCases := []struct {
|
|
Name string
|
|
ChannelModerationsPatch []*model.ChannelModerationPatch
|
|
PermissionsModeratedByPatch map[string]*model.ChannelModeratedRoles
|
|
RevertChannelModerationsPatch []*model.ChannelModerationPatch
|
|
HigherScopedMemberPermissions []string
|
|
HigherScopedGuestPermissions []string
|
|
ShouldError bool
|
|
ShouldHaveNoChannelScheme bool
|
|
}{
|
|
{
|
|
Name: "Removing create posts from members role",
|
|
ChannelModerationsPatch: []*model.ChannelModerationPatch{
|
|
{
|
|
Name: &createPosts,
|
|
Roles: &model.ChannelModeratedRolesPatch{Members: model.NewPointer(false)},
|
|
},
|
|
},
|
|
PermissionsModeratedByPatch: map[string]*model.ChannelModeratedRoles{
|
|
createPosts: {
|
|
Members: &model.ChannelModeratedRole{Value: false, Enabled: true},
|
|
},
|
|
},
|
|
RevertChannelModerationsPatch: []*model.ChannelModerationPatch{
|
|
{
|
|
Name: &createPosts,
|
|
Roles: &model.ChannelModeratedRolesPatch{Members: model.NewPointer(true)},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "Removing create reactions from members role",
|
|
ChannelModerationsPatch: []*model.ChannelModerationPatch{
|
|
{
|
|
Name: &createReactions,
|
|
Roles: &model.ChannelModeratedRolesPatch{Members: model.NewPointer(false)},
|
|
},
|
|
},
|
|
PermissionsModeratedByPatch: map[string]*model.ChannelModeratedRoles{
|
|
createReactions: {
|
|
Members: &model.ChannelModeratedRole{Value: false, Enabled: true},
|
|
},
|
|
},
|
|
RevertChannelModerationsPatch: []*model.ChannelModerationPatch{
|
|
{
|
|
Name: &createReactions,
|
|
Roles: &model.ChannelModeratedRolesPatch{Members: model.NewPointer(true)},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "Removing channel mentions from members role",
|
|
ChannelModerationsPatch: []*model.ChannelModerationPatch{
|
|
{
|
|
Name: &channelMentions,
|
|
Roles: &model.ChannelModeratedRolesPatch{Members: model.NewPointer(false)},
|
|
},
|
|
},
|
|
PermissionsModeratedByPatch: map[string]*model.ChannelModeratedRoles{
|
|
channelMentions: {
|
|
Members: &model.ChannelModeratedRole{Value: false, Enabled: true},
|
|
},
|
|
},
|
|
RevertChannelModerationsPatch: []*model.ChannelModerationPatch{
|
|
{
|
|
Name: &channelMentions,
|
|
Roles: &model.ChannelModeratedRolesPatch{Members: model.NewPointer(true)},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "Removing manage members from members role",
|
|
ChannelModerationsPatch: []*model.ChannelModerationPatch{
|
|
{
|
|
Name: &manageMembers,
|
|
Roles: &model.ChannelModeratedRolesPatch{Members: model.NewPointer(false)},
|
|
},
|
|
},
|
|
PermissionsModeratedByPatch: map[string]*model.ChannelModeratedRoles{
|
|
manageMembers: {
|
|
Members: &model.ChannelModeratedRole{Value: false, Enabled: true},
|
|
},
|
|
},
|
|
RevertChannelModerationsPatch: []*model.ChannelModerationPatch{
|
|
{
|
|
Name: &manageMembers,
|
|
Roles: &model.ChannelModeratedRolesPatch{Members: model.NewPointer(true)},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "Removing manage bookmarks from members role",
|
|
ChannelModerationsPatch: []*model.ChannelModerationPatch{
|
|
{
|
|
Name: &manageBookmarks,
|
|
Roles: &model.ChannelModeratedRolesPatch{Members: model.NewPointer(false)},
|
|
},
|
|
},
|
|
PermissionsModeratedByPatch: map[string]*model.ChannelModeratedRoles{
|
|
manageBookmarks: {
|
|
Members: &model.ChannelModeratedRole{Value: false, Enabled: true},
|
|
},
|
|
},
|
|
RevertChannelModerationsPatch: []*model.ChannelModerationPatch{
|
|
{
|
|
Name: &manageBookmarks,
|
|
Roles: &model.ChannelModeratedRolesPatch{Members: model.NewPointer(true)},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "Removing create posts from guests role",
|
|
ChannelModerationsPatch: []*model.ChannelModerationPatch{
|
|
{
|
|
Name: &createPosts,
|
|
Roles: &model.ChannelModeratedRolesPatch{Guests: model.NewPointer(false)},
|
|
},
|
|
},
|
|
PermissionsModeratedByPatch: map[string]*model.ChannelModeratedRoles{
|
|
createPosts: {
|
|
Guests: &model.ChannelModeratedRole{Value: false, Enabled: true},
|
|
},
|
|
},
|
|
RevertChannelModerationsPatch: []*model.ChannelModerationPatch{
|
|
{
|
|
Name: &createPosts,
|
|
Roles: &model.ChannelModeratedRolesPatch{Guests: model.NewPointer(true)},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "Removing create reactions from guests role",
|
|
ChannelModerationsPatch: []*model.ChannelModerationPatch{
|
|
{
|
|
Name: &createReactions,
|
|
Roles: &model.ChannelModeratedRolesPatch{Guests: model.NewPointer(false)},
|
|
},
|
|
},
|
|
PermissionsModeratedByPatch: map[string]*model.ChannelModeratedRoles{
|
|
createReactions: {
|
|
Guests: &model.ChannelModeratedRole{Value: false, Enabled: true},
|
|
},
|
|
},
|
|
RevertChannelModerationsPatch: []*model.ChannelModerationPatch{
|
|
{
|
|
Name: &createReactions,
|
|
Roles: &model.ChannelModeratedRolesPatch{Guests: model.NewPointer(true)},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "Removing channel mentions from guests role",
|
|
ChannelModerationsPatch: []*model.ChannelModerationPatch{
|
|
{
|
|
Name: &channelMentions,
|
|
Roles: &model.ChannelModeratedRolesPatch{Guests: model.NewPointer(false)},
|
|
},
|
|
},
|
|
PermissionsModeratedByPatch: map[string]*model.ChannelModeratedRoles{
|
|
channelMentions: {
|
|
Guests: &model.ChannelModeratedRole{Value: false, Enabled: true},
|
|
},
|
|
},
|
|
RevertChannelModerationsPatch: []*model.ChannelModerationPatch{
|
|
{
|
|
Name: &channelMentions,
|
|
Roles: &model.ChannelModeratedRolesPatch{Guests: model.NewPointer(true)},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "Removing manage members from guests role should not error",
|
|
ChannelModerationsPatch: []*model.ChannelModerationPatch{
|
|
{
|
|
Name: &manageMembers,
|
|
Roles: &model.ChannelModeratedRolesPatch{Guests: model.NewPointer(false)},
|
|
},
|
|
},
|
|
PermissionsModeratedByPatch: map[string]*model.ChannelModeratedRoles{},
|
|
ShouldError: false,
|
|
ShouldHaveNoChannelScheme: true,
|
|
},
|
|
{
|
|
Name: "Removing manage bookmarks from guests role should not error",
|
|
ChannelModerationsPatch: []*model.ChannelModerationPatch{
|
|
{
|
|
Name: &manageBookmarks,
|
|
Roles: &model.ChannelModeratedRolesPatch{Guests: model.NewPointer(false)},
|
|
},
|
|
},
|
|
PermissionsModeratedByPatch: map[string]*model.ChannelModeratedRoles{},
|
|
ShouldError: false,
|
|
ShouldHaveNoChannelScheme: true,
|
|
},
|
|
{
|
|
Name: "Removing a permission that is not channel moderated should not error",
|
|
ChannelModerationsPatch: []*model.ChannelModerationPatch{
|
|
{
|
|
Name: &nonChannelModeratedPermission,
|
|
Roles: &model.ChannelModeratedRolesPatch{
|
|
Members: model.NewPointer(false),
|
|
Guests: model.NewPointer(false),
|
|
},
|
|
},
|
|
},
|
|
PermissionsModeratedByPatch: map[string]*model.ChannelModeratedRoles{},
|
|
ShouldError: false,
|
|
ShouldHaveNoChannelScheme: true,
|
|
},
|
|
{
|
|
Name: "Error when adding a permission that is disabled in the parent member role",
|
|
ChannelModerationsPatch: []*model.ChannelModerationPatch{
|
|
{
|
|
Name: &createPosts,
|
|
Roles: &model.ChannelModeratedRolesPatch{
|
|
Members: model.NewPointer(true),
|
|
Guests: model.NewPointer(false),
|
|
},
|
|
},
|
|
},
|
|
PermissionsModeratedByPatch: map[string]*model.ChannelModeratedRoles{},
|
|
HigherScopedMemberPermissions: []string{},
|
|
ShouldError: true,
|
|
},
|
|
{
|
|
Name: "Error when adding a permission that is disabled in the parent guest role",
|
|
ChannelModerationsPatch: []*model.ChannelModerationPatch{
|
|
{
|
|
Name: &createPosts,
|
|
Roles: &model.ChannelModeratedRolesPatch{
|
|
Members: model.NewPointer(false),
|
|
Guests: model.NewPointer(true),
|
|
},
|
|
},
|
|
},
|
|
PermissionsModeratedByPatch: map[string]*model.ChannelModeratedRoles{},
|
|
HigherScopedGuestPermissions: []string{},
|
|
ShouldError: true,
|
|
},
|
|
{
|
|
Name: "Removing a permission from the member role that is disabled in the parent guest role",
|
|
ChannelModerationsPatch: []*model.ChannelModerationPatch{
|
|
{
|
|
Name: &createPosts,
|
|
Roles: &model.ChannelModeratedRolesPatch{
|
|
Members: model.NewPointer(false),
|
|
},
|
|
},
|
|
},
|
|
PermissionsModeratedByPatch: map[string]*model.ChannelModeratedRoles{
|
|
createPosts: {
|
|
Members: &model.ChannelModeratedRole{Value: false, Enabled: true},
|
|
Guests: &model.ChannelModeratedRole{Value: false, Enabled: false},
|
|
},
|
|
createReactions: {
|
|
Guests: &model.ChannelModeratedRole{Value: false, Enabled: false},
|
|
},
|
|
channelMentions: {
|
|
Guests: &model.ChannelModeratedRole{Value: false, Enabled: false},
|
|
},
|
|
},
|
|
HigherScopedGuestPermissions: []string{},
|
|
ShouldError: false,
|
|
},
|
|
{
|
|
Name: "Channel should have no scheme when all moderated permissions are equivalent to higher scoped role",
|
|
ChannelModerationsPatch: []*model.ChannelModerationPatch{
|
|
{
|
|
Name: &createPosts,
|
|
Roles: &model.ChannelModeratedRolesPatch{
|
|
Members: model.NewPointer(true),
|
|
Guests: model.NewPointer(true),
|
|
},
|
|
},
|
|
{
|
|
Name: &createReactions,
|
|
Roles: &model.ChannelModeratedRolesPatch{
|
|
Members: model.NewPointer(true),
|
|
Guests: model.NewPointer(true),
|
|
},
|
|
},
|
|
{
|
|
Name: &channelMentions,
|
|
Roles: &model.ChannelModeratedRolesPatch{
|
|
Members: model.NewPointer(true),
|
|
Guests: model.NewPointer(true),
|
|
},
|
|
},
|
|
{
|
|
Name: &manageMembers,
|
|
Roles: &model.ChannelModeratedRolesPatch{
|
|
Members: model.NewPointer(true),
|
|
},
|
|
},
|
|
{
|
|
Name: &manageBookmarks,
|
|
Roles: &model.ChannelModeratedRolesPatch{
|
|
Members: model.NewPointer(true),
|
|
},
|
|
},
|
|
},
|
|
PermissionsModeratedByPatch: map[string]*model.ChannelModeratedRoles{},
|
|
ShouldHaveNoChannelScheme: true,
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.Name, func(t *testing.T) {
|
|
higherScopedPermissionsOverridden := tc.HigherScopedMemberPermissions != nil || tc.HigherScopedGuestPermissions != nil
|
|
// If the test case restricts higher scoped permissions.
|
|
if higherScopedPermissionsOverridden {
|
|
higherScopedGuestRoleName, higherScopedMemberRoleName, _, _ := th.App.GetTeamSchemeChannelRoles(th.Context, channel.TeamId)
|
|
if tc.HigherScopedMemberPermissions != nil {
|
|
higherScopedMemberRole, appErr := th.App.GetRoleByName(th.Context, higherScopedMemberRoleName)
|
|
require.Nil(t, appErr)
|
|
originalPermissions := higherScopedMemberRole.Permissions
|
|
|
|
_, appErr = th.App.PatchRole(higherScopedMemberRole, &model.RolePatch{Permissions: &tc.HigherScopedMemberPermissions})
|
|
require.Nil(t, appErr)
|
|
defer func() {
|
|
_, appErr := th.App.PatchRole(higherScopedMemberRole, &model.RolePatch{Permissions: &originalPermissions})
|
|
require.Nil(t, appErr)
|
|
}()
|
|
}
|
|
|
|
if tc.HigherScopedGuestPermissions != nil {
|
|
higherScopedGuestRole, appErr := th.App.GetRoleByName(th.Context, higherScopedGuestRoleName)
|
|
require.Nil(t, appErr)
|
|
originalPermissions := higherScopedGuestRole.Permissions
|
|
|
|
_, appErr = th.App.PatchRole(higherScopedGuestRole, &model.RolePatch{Permissions: &tc.HigherScopedGuestPermissions})
|
|
require.Nil(t, appErr)
|
|
defer func() {
|
|
_, appErr := th.App.PatchRole(higherScopedGuestRole, &model.RolePatch{Permissions: &originalPermissions})
|
|
require.Nil(t, appErr)
|
|
}()
|
|
}
|
|
}
|
|
|
|
moderations, appErr := th.App.PatchChannelModerationsForChannel(th.Context, channel, tc.ChannelModerationsPatch)
|
|
if tc.ShouldError {
|
|
require.NotNil(t, appErr)
|
|
require.Equal(t, http.StatusForbidden, appErr.StatusCode)
|
|
return
|
|
}
|
|
require.Nil(t, appErr)
|
|
|
|
updatedChannel, _ := th.App.GetChannel(th.Context, channel.Id)
|
|
if tc.ShouldHaveNoChannelScheme {
|
|
require.Nil(t, updatedChannel.SchemeId)
|
|
} else {
|
|
require.NotNil(t, updatedChannel.SchemeId)
|
|
}
|
|
|
|
for _, moderation := range moderations {
|
|
// If the permission is not found in the expected modified permissions table then require it to be true
|
|
if permission, found := tc.PermissionsModeratedByPatch[moderation.Name]; found && permission.Members != nil {
|
|
require.Equal(t, moderation.Roles.Members.Value, permission.Members.Value)
|
|
require.Equal(t, moderation.Roles.Members.Enabled, permission.Members.Enabled)
|
|
} else {
|
|
require.Equal(t, moderation.Roles.Members.Value, true)
|
|
require.Equal(t, moderation.Roles.Members.Enabled, true)
|
|
}
|
|
|
|
if permission, found := tc.PermissionsModeratedByPatch[moderation.Name]; found && permission.Guests != nil {
|
|
require.Equal(t, moderation.Roles.Guests.Value, permission.Guests.Value)
|
|
require.Equal(t, moderation.Roles.Guests.Enabled, permission.Guests.Enabled)
|
|
} else if moderation.Name == manageMembers || moderation.Name == "manage_bookmarks" {
|
|
require.Empty(t, moderation.Roles.Guests)
|
|
} else {
|
|
require.Equal(t, moderation.Roles.Guests.Value, true)
|
|
require.Equal(t, moderation.Roles.Guests.Enabled, true)
|
|
}
|
|
}
|
|
|
|
if tc.RevertChannelModerationsPatch != nil {
|
|
_, appErr := th.App.PatchChannelModerationsForChannel(th.Context, channel, tc.RevertChannelModerationsPatch)
|
|
require.Nil(t, appErr)
|
|
}
|
|
})
|
|
}
|
|
|
|
t.Run("Handles concurrent patch requests gracefully", func(t *testing.T) {
|
|
addCreatePosts := []*model.ChannelModerationPatch{
|
|
{
|
|
Name: &createPosts,
|
|
Roles: &model.ChannelModeratedRolesPatch{
|
|
Members: model.NewPointer(false),
|
|
Guests: model.NewPointer(false),
|
|
},
|
|
},
|
|
}
|
|
removeCreatePosts := []*model.ChannelModerationPatch{
|
|
{
|
|
Name: &createPosts,
|
|
Roles: &model.ChannelModeratedRolesPatch{
|
|
Members: model.NewPointer(false),
|
|
Guests: model.NewPointer(false),
|
|
},
|
|
},
|
|
}
|
|
|
|
wg := sync.WaitGroup{}
|
|
wg.Add(20)
|
|
for range 10 {
|
|
go func() {
|
|
_, appErr := th.App.PatchChannelModerationsForChannel(th.Context, channel.DeepCopy(), addCreatePosts)
|
|
require.Nil(t, appErr)
|
|
_, appErr = th.App.PatchChannelModerationsForChannel(th.Context, channel.DeepCopy(), removeCreatePosts)
|
|
require.Nil(t, appErr)
|
|
wg.Done()
|
|
}()
|
|
}
|
|
for range 10 {
|
|
go func() {
|
|
_, appErr := th.App.PatchChannelModerationsForChannel(th.Context, channel.DeepCopy(), addCreatePosts)
|
|
require.Nil(t, appErr)
|
|
_, appErr = th.App.PatchChannelModerationsForChannel(th.Context, channel.DeepCopy(), removeCreatePosts)
|
|
require.Nil(t, appErr)
|
|
wg.Done()
|
|
}()
|
|
}
|
|
wg.Wait()
|
|
|
|
higherScopedGuestRoleName, higherScopedMemberRoleName, _, _ := th.App.GetTeamSchemeChannelRoles(th.Context, channel.TeamId)
|
|
higherScopedMemberRole, _ := th.App.GetRoleByName(th.Context, higherScopedMemberRoleName)
|
|
higherScopedGuestRole, _ := th.App.GetRoleByName(th.Context, higherScopedGuestRoleName)
|
|
assert.Contains(t, higherScopedMemberRole.Permissions, createPosts)
|
|
assert.Contains(t, higherScopedGuestRole.Permissions, createPosts)
|
|
})
|
|
|
|
t.Run("Updates the authorization to create post", func(t *testing.T) {
|
|
addCreatePosts := []*model.ChannelModerationPatch{
|
|
{
|
|
Name: &createPosts,
|
|
Roles: &model.ChannelModeratedRolesPatch{
|
|
Members: model.NewPointer(true),
|
|
},
|
|
},
|
|
}
|
|
removeCreatePosts := []*model.ChannelModerationPatch{
|
|
{
|
|
Name: &createPosts,
|
|
Roles: &model.ChannelModeratedRolesPatch{
|
|
Members: model.NewPointer(false),
|
|
},
|
|
},
|
|
}
|
|
|
|
mockSession := model.Session{UserId: user.Id}
|
|
|
|
_, appErr := th.App.PatchChannelModerationsForChannel(th.Context, channel.DeepCopy(), addCreatePosts)
|
|
require.Nil(t, appErr)
|
|
require.True(t, th.App.SessionHasPermissionToChannel(th.Context, mockSession, channel.Id, model.PermissionCreatePost))
|
|
|
|
_, appErr = th.App.PatchChannelModerationsForChannel(th.Context, channel.DeepCopy(), removeCreatePosts)
|
|
require.Nil(t, appErr)
|
|
require.False(t, th.App.SessionHasPermissionToChannel(th.Context, mockSession, channel.Id, model.PermissionCreatePost))
|
|
})
|
|
}
|
|
|
|
func TestClearChannelMembersCache(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := SetupWithStoreMock(t)
|
|
defer th.TearDown()
|
|
|
|
mockStore := th.App.Srv().Store().(*mocks.Store)
|
|
mockChannelStore := mocks.ChannelStore{}
|
|
cms := model.ChannelMembers{}
|
|
for range 200 {
|
|
cms = append(cms, model.ChannelMember{
|
|
ChannelId: "1",
|
|
})
|
|
}
|
|
mockChannelStore.On("GetMembers", model.ChannelMembersGetOptions{
|
|
ChannelID: "channelID",
|
|
Offset: 0,
|
|
Limit: 100,
|
|
}).Return(cms, nil)
|
|
mockChannelStore.On("GetMembers", model.ChannelMembersGetOptions{
|
|
ChannelID: "channelID",
|
|
Offset: 100,
|
|
Limit: 100,
|
|
}).Return(model.ChannelMembers{
|
|
model.ChannelMember{
|
|
ChannelId: "1",
|
|
},
|
|
}, nil)
|
|
mockStore.On("Channel").Return(&mockChannelStore)
|
|
mockStore.On("GetDBSchemaVersion").Return(1, nil)
|
|
|
|
require.NoError(t, th.App.ClearChannelMembersCache(th.Context, "channelID"))
|
|
}
|
|
|
|
func TestGetMemberCountsByGroup(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := SetupWithStoreMock(t)
|
|
defer th.TearDown()
|
|
|
|
mockStore := th.App.Srv().Store().(*mocks.Store)
|
|
mockChannelStore := mocks.ChannelStore{}
|
|
cmc := []*model.ChannelMemberCountByGroup{}
|
|
for i := range 5 {
|
|
cmc = append(cmc, &model.ChannelMemberCountByGroup{
|
|
GroupId: model.NewId(),
|
|
ChannelMemberCount: int64(i),
|
|
ChannelMemberTimezonesCount: int64(i),
|
|
})
|
|
}
|
|
mockChannelStore.On("GetMemberCountsByGroup", mock.AnythingOfType("*request.Context"), "channelID", true).Return(cmc, nil)
|
|
mockStore.On("Channel").Return(&mockChannelStore)
|
|
mockStore.On("GetDBSchemaVersion").Return(1, nil)
|
|
resp, appErr := th.App.GetMemberCountsByGroup(th.Context, "channelID", true)
|
|
require.Nil(t, appErr)
|
|
require.ElementsMatch(t, cmc, resp)
|
|
}
|
|
|
|
func TestGetChannelsMemberCount(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := SetupWithStoreMock(t)
|
|
defer th.TearDown()
|
|
|
|
mockStore := th.App.Srv().Store().(*mocks.Store)
|
|
mockChannelStore := mocks.ChannelStore{}
|
|
channelsMemberCount := map[string]int64{
|
|
"channel1": int64(10),
|
|
"channel2": int64(20),
|
|
}
|
|
mockChannelStore.On("GetChannelsMemberCount", []string{"channel1", "channel2"}).Return(channelsMemberCount, nil)
|
|
mockStore.On("Channel").Return(&mockChannelStore)
|
|
mockStore.On("GetDBSchemaVersion").Return(1, nil)
|
|
resp, appErr := th.App.GetChannelsMemberCount(th.Context, []string{"channel1", "channel2"})
|
|
require.Nil(t, appErr)
|
|
require.Equal(t, channelsMemberCount, resp)
|
|
}
|
|
|
|
func TestViewChannelCollapsedThreadsTurnedOff(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
u1 := th.BasicUser
|
|
u2 := th.BasicUser2
|
|
c1 := th.BasicChannel
|
|
th.AddUserToChannel(u2, c1)
|
|
|
|
// Enable CRT
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.ServiceSettings.ThreadAutoFollow = true
|
|
*cfg.ServiceSettings.CollapsedThreads = model.CollapsedThreadsDefaultOn
|
|
})
|
|
|
|
// Turn off CRT for user
|
|
preference := model.Preference{
|
|
UserId: u1.Id,
|
|
Category: model.PreferenceCategoryDisplaySettings,
|
|
Name: model.PreferenceNameCollapsedThreadsEnabled,
|
|
Value: "off",
|
|
}
|
|
var preferences model.Preferences
|
|
preferences = append(preferences, preference)
|
|
err := th.App.Srv().Store().Preference().Save(preferences)
|
|
require.NoError(t, err)
|
|
|
|
// mention the user in a root post
|
|
post1 := &model.Post{
|
|
ChannelId: c1.Id,
|
|
Message: "root post @" + u1.Username,
|
|
UserId: u2.Id,
|
|
}
|
|
rpost1, appErr := th.App.CreatePost(th.Context, post1, c1, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, appErr)
|
|
|
|
// mention the user in a reply post
|
|
post2 := &model.Post{
|
|
ChannelId: c1.Id,
|
|
Message: "reply post @" + u1.Username,
|
|
UserId: u2.Id,
|
|
RootId: rpost1.Id,
|
|
}
|
|
_, appErr = th.App.CreatePost(th.Context, post2, c1, model.CreatePostFlags{SetOnline: true})
|
|
require.Nil(t, appErr)
|
|
|
|
// Check we have unread mention in the thread
|
|
threads, appErr := th.App.GetThreadsForUser(th.Context, u1.Id, c1.TeamId, model.GetUserThreadsOpts{})
|
|
require.Nil(t, appErr)
|
|
found := false
|
|
for _, thread := range threads.Threads {
|
|
if thread.PostId == rpost1.Id {
|
|
require.EqualValues(t, int64(1), thread.UnreadMentions)
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
require.Truef(t, found, "did not find created thread in user's threads")
|
|
|
|
// Mark channel as read from a client that supports CRT
|
|
_, appErr = th.App.MarkChannelsAsViewed(th.Context, []string{c1.Id}, u1.Id, th.Context.Session().Id, true, th.App.IsCRTEnabledForUser(th.Context, u1.Id))
|
|
require.Nil(t, appErr)
|
|
|
|
// Thread should be marked as read because CRT has been turned off by user
|
|
threads, appErr = th.App.GetThreadsForUser(th.Context, u1.Id, c1.TeamId, model.GetUserThreadsOpts{})
|
|
require.Nil(t, appErr)
|
|
found = false
|
|
for _, thread := range threads.Threads {
|
|
if thread.PostId == rpost1.Id {
|
|
require.Zero(t, thread.UnreadMentions)
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
require.Truef(t, found, "did not find created thread in user's threads")
|
|
}
|
|
|
|
func TestMarkChannelAsUnreadFromPostCollapsedThreadsTurnedOff(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
// Enable CRT
|
|
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.ServiceSettings.ThreadAutoFollow = true
|
|
*cfg.ServiceSettings.CollapsedThreads = model.CollapsedThreadsDefaultOn
|
|
})
|
|
|
|
th.AddUserToChannel(th.BasicUser2, th.BasicChannel)
|
|
|
|
// Turn off CRT for user
|
|
preference := model.Preference{
|
|
UserId: th.BasicUser.Id,
|
|
Category: model.PreferenceCategoryDisplaySettings,
|
|
Name: model.PreferenceNameCollapsedThreadsEnabled,
|
|
Value: "off",
|
|
}
|
|
var preferences model.Preferences
|
|
preferences = append(preferences, preference)
|
|
err := th.App.Srv().Store().Preference().Save(preferences)
|
|
require.NoError(t, err)
|
|
|
|
// 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) {
|
|
_, appErr := th.App.MarkChannelAsUnreadFromPost(th.Context, replyPost1.Id, th.BasicUser.Id, true)
|
|
require.Nil(t, appErr)
|
|
// Get channel unreads
|
|
// Easier to reason with ChannelUnread now, than channelUnreadAt from the previous call
|
|
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 for a user that has CRT turned off
|
|
require.Equal(t, int64(0), channelUnread.MentionCountRoot)
|
|
|
|
require.Equal(t, int64(5), channelUnread.MsgCount)
|
|
// MentionCountRoot should be zero for a user that has CRT turned off
|
|
require.Equal(t, channelUnread.MsgCountRoot, int64(0))
|
|
|
|
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) {
|
|
_, appErr := th.App.MarkChannelAsUnreadFromPost(th.Context, rootPost1.Id, th.BasicUser.Id, true)
|
|
require.Nil(t, appErr)
|
|
// Get channel unreads
|
|
// Easier to reason with ChannelUnread now, than channelUnreadAt from the previous call
|
|
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 TestMarkUnreadCRTOffUpdatesThreads(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.ServiceSettings.ThreadAutoFollow = true
|
|
*cfg.ServiceSettings.CollapsedThreads = model.CollapsedThreadsDefaultOff
|
|
})
|
|
|
|
t.Run("Mentions counted correctly if post is edited", func(t *testing.T) {
|
|
user3 := th.CreateUser()
|
|
defer func() {
|
|
appErr := th.App.PermanentDeleteUser(th.Context, user3)
|
|
require.Nil(t, appErr)
|
|
}()
|
|
rootPost, appErr := th.App.CreatePost(th.Context, &model.Post{UserId: th.BasicUser.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "root post"}, th.BasicChannel, model.CreatePostFlags{})
|
|
require.Nil(t, appErr)
|
|
r1, appErr := th.App.CreatePost(th.Context, &model.Post{RootId: rootPost.Id, UserId: th.BasicUser2.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "reply 1"}, th.BasicChannel, model.CreatePostFlags{})
|
|
require.Nil(t, appErr)
|
|
_, appErr = th.App.CreatePost(th.Context, &model.Post{RootId: rootPost.Id, UserId: th.BasicUser.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "reply 2 @" + user3.Username}, th.BasicChannel, model.CreatePostFlags{})
|
|
require.Nil(t, appErr)
|
|
_, appErr = th.App.CreatePost(th.Context, &model.Post{RootId: rootPost.Id, UserId: th.BasicUser2.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "reply 3"}, th.BasicChannel, model.CreatePostFlags{})
|
|
require.Nil(t, appErr)
|
|
editedPost := r1.Clone()
|
|
editedPost.Message += " edited"
|
|
_, appErr = th.App.UpdatePost(th.Context, editedPost, &model.UpdatePostOptions{SafeUpdate: false})
|
|
require.Nil(t, appErr)
|
|
|
|
th.LinkUserToTeam(user3, th.BasicTeam)
|
|
th.AddUserToChannel(user3, th.BasicChannel)
|
|
|
|
_, appErr = th.App.MarkChannelAsUnreadFromPost(th.Context, editedPost.Id, user3.Id, false)
|
|
require.Nil(t, appErr)
|
|
threadMembership, appErr := th.App.GetThreadMembershipForUser(user3.Id, rootPost.Id)
|
|
require.Nil(t, appErr)
|
|
require.NotNil(t, threadMembership)
|
|
require.True(t, threadMembership.Following)
|
|
assert.Equal(t, int64(1), threadMembership.UnreadMentions)
|
|
})
|
|
}
|
|
|
|
func TestIsCRTEnabledForUser(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
type preference struct {
|
|
val string
|
|
err error
|
|
}
|
|
|
|
testCases := []struct {
|
|
desc string
|
|
appCRT string
|
|
pref preference
|
|
expected bool
|
|
}{
|
|
{
|
|
desc: "Returns false when system config is disabled",
|
|
appCRT: model.CollapsedThreadsDisabled,
|
|
expected: false,
|
|
},
|
|
{
|
|
desc: "Returns true when system config is always_on",
|
|
appCRT: model.CollapsedThreadsAlwaysOn,
|
|
expected: true,
|
|
},
|
|
{
|
|
desc: "Returns true when system config is default_on and user has no preference",
|
|
appCRT: model.CollapsedThreadsDefaultOn,
|
|
pref: preference{"test", errors.New("err")},
|
|
expected: true,
|
|
},
|
|
{
|
|
desc: "Returns false when system config is default_off and user has no preference",
|
|
appCRT: model.CollapsedThreadsDefaultOff,
|
|
pref: preference{"qwe", errors.New("err")},
|
|
expected: false,
|
|
},
|
|
{
|
|
desc: "Returns true when system config is default_on and user has on preference",
|
|
appCRT: model.CollapsedThreadsDefaultOn,
|
|
pref: preference{"on", nil},
|
|
expected: true,
|
|
},
|
|
{
|
|
desc: "Returns false when system config is default_on and user has off preference",
|
|
appCRT: model.CollapsedThreadsDefaultOn,
|
|
pref: preference{"off", nil},
|
|
expected: false,
|
|
},
|
|
{
|
|
desc: "Returns true when system config is default_off and user has on preference",
|
|
appCRT: model.CollapsedThreadsDefaultOff,
|
|
pref: preference{"on", nil},
|
|
expected: true,
|
|
},
|
|
{
|
|
desc: "Returns false when system config is default_off and user has off preference",
|
|
appCRT: model.CollapsedThreadsDefaultOff,
|
|
pref: preference{"off", nil},
|
|
expected: false,
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.desc, func(t *testing.T) {
|
|
th := SetupWithStoreMock(t)
|
|
defer th.TearDown()
|
|
|
|
th.App.Config().ServiceSettings.CollapsedThreads = &tc.appCRT
|
|
|
|
mockStore := th.App.Srv().Store().(*mocks.Store)
|
|
mockPreferenceStore := mocks.PreferenceStore{}
|
|
mockPreferenceStore.On("Get", mock.Anything, model.PreferenceCategoryDisplaySettings, model.PreferenceNameCollapsedThreadsEnabled).Return(&model.Preference{Value: tc.pref.val}, tc.pref.err)
|
|
mockStore.On("Preference").Return(&mockPreferenceStore)
|
|
|
|
res := th.App.IsCRTEnabledForUser(th.Context, mock.Anything)
|
|
|
|
assert.Equal(t, tc.expected, res)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetDirectOrGroupMessageMembersCommonTeams(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
teamsToCreate := 2
|
|
usersToCreate := 4 // at least 3 users to create a GM channel, last user is not in any team
|
|
teams := make([]string, 0, teamsToCreate)
|
|
for i := 0; i < cap(teams); i++ {
|
|
team := th.CreateTeam()
|
|
defer func(team *model.Team) {
|
|
appErr := th.App.PermanentDeleteTeam(th.Context, team)
|
|
require.Nil(t, appErr)
|
|
}(team)
|
|
teams = append(teams, team.Id)
|
|
}
|
|
|
|
users := make([]string, 0, usersToCreate)
|
|
for i := 0; i < cap(users); i++ {
|
|
user := th.CreateUser()
|
|
defer func(user *model.User) {
|
|
appErr := th.App.PermanentDeleteUser(th.Context, user)
|
|
require.Nil(t, appErr)
|
|
}(user)
|
|
users = append(users, user.Id)
|
|
}
|
|
|
|
for _, teamId := range teams {
|
|
// add first 3 users to each team, last user is not in any team
|
|
for i := range 3 {
|
|
_, _, appErr := th.App.AddUserToTeam(th.Context, teamId, users[i], "")
|
|
require.Nil(t, appErr)
|
|
}
|
|
}
|
|
|
|
// create GM channel with first 3 users who share common teams
|
|
gmChannel, appErr := th.App.createGroupChannel(th.Context, users[:3], users[0])
|
|
require.Nil(t, appErr)
|
|
require.NotNil(t, gmChannel)
|
|
|
|
// normally you can't create a GM channel with users that don't share any teams, but we do it here to test the edge case
|
|
// create GM channel with last 3 users, where last member is not in any team
|
|
otherGMChannel, appErr := th.App.createGroupChannel(th.Context, users[1:], users[0])
|
|
require.Nil(t, appErr)
|
|
require.NotNil(t, otherGMChannel)
|
|
|
|
t.Run("Get teams for GM channel", func(t *testing.T) {
|
|
commonTeams, appErr := th.App.GetDirectOrGroupMessageMembersCommonTeams(th.Context, gmChannel.Id)
|
|
require.Nil(t, appErr)
|
|
require.Equal(t, 2, len(commonTeams))
|
|
})
|
|
|
|
t.Run("No common teams", func(t *testing.T) {
|
|
commonTeams, appErr := th.App.GetDirectOrGroupMessageMembersCommonTeams(th.Context, otherGMChannel.Id)
|
|
require.Nil(t, appErr)
|
|
require.Equal(t, 0, len(commonTeams))
|
|
})
|
|
}
|
|
|
|
func TestConvertGroupMessageToChannel(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := SetupWithStoreMock(t)
|
|
defer th.TearDown()
|
|
|
|
mockStore := th.App.Srv().Store().(*mocks.Store)
|
|
|
|
mockChannelStore := mocks.ChannelStore{}
|
|
mockStore.On("Channel").Return(&mockChannelStore)
|
|
mockChannelStore.On("Get", "channelidchannelidchanneli", true).Return(&model.Channel{
|
|
Id: "channelidchannelidchanneli",
|
|
CreateAt: time.Now().Unix(),
|
|
UpdateAt: time.Now().Unix(),
|
|
Type: model.ChannelTypeGroup,
|
|
}, nil)
|
|
mockChannelStore.On("Update", mock.AnythingOfType("*request.Context"), mock.AnythingOfType("*model.Channel")).Return(&model.Channel{}, nil)
|
|
mockChannelStore.On("InvalidateChannel", "channelidchannelidchanneli")
|
|
mockChannelStore.On("InvalidateChannelByName", "team_id_1", "new_name").Times(1)
|
|
mockChannelStore.On("InvalidateChannelByName", "dm", "")
|
|
mockChannelStore.On("GetMember", mock.AnythingOfType("*request.Context"), "channelidchannelidchanneli", "user_id_1").Return(&model.ChannelMember{}, nil)
|
|
mockChannelStore.On("UpdateMember", mock.AnythingOfType("*request.Context"), mock.AnythingOfType("*model.ChannelMember")).Return(&model.ChannelMember{UserId: "user_id_1"}, nil)
|
|
mockChannelStore.On("InvalidateAllChannelMembersForUser", "user_id_1").Return()
|
|
mockChannelStore.On("InvalidatePinnedPostCount", "channelidchannelidchanneli")
|
|
mockChannelStore.On("GetAllChannelMembersNotifyPropsForChannel", "channelidchannelidchanneli", true).Return(map[string]model.StringMap{}, nil)
|
|
mockChannelStore.On("IncrementMentionCount", "", []string{}, true, false).Return(nil)
|
|
mockChannelStore.On("DeleteAllSidebarChannelForChannel", "channelidchannelidchanneli").Return(nil)
|
|
mockChannelStore.On("GetSidebarCategories", "user_id_1", "team_id_1").Return(
|
|
&model.OrderedSidebarCategories{
|
|
Categories: model.SidebarCategoriesWithChannels{
|
|
{
|
|
SidebarCategory: model.SidebarCategory{
|
|
Type: model.SidebarCategoryChannels,
|
|
},
|
|
},
|
|
},
|
|
}, nil)
|
|
mockChannelStore.On("GetSidebarCategories", "user_id_2", "team_id_1").Return(
|
|
&model.OrderedSidebarCategories{
|
|
Categories: model.SidebarCategoriesWithChannels{
|
|
{
|
|
SidebarCategory: model.SidebarCategory{
|
|
Type: model.SidebarCategoryChannels,
|
|
},
|
|
},
|
|
},
|
|
}, nil)
|
|
mockChannelStore.On("UpdateSidebarCategories", "user_id_1", "team_id_1", mock.Anything).Return(
|
|
[]*model.SidebarCategoryWithChannels{
|
|
{
|
|
SidebarCategory: model.SidebarCategory{
|
|
Type: model.SidebarCategoryChannels,
|
|
},
|
|
},
|
|
},
|
|
[]*model.SidebarCategoryWithChannels{
|
|
{
|
|
SidebarCategory: model.SidebarCategory{
|
|
Type: model.SidebarCategoryChannels,
|
|
},
|
|
},
|
|
},
|
|
nil,
|
|
)
|
|
mockChannelStore.On("UpdateSidebarCategories", "user_id_2", "team_id_1", mock.Anything).Return(
|
|
[]*model.SidebarCategoryWithChannels{
|
|
{
|
|
SidebarCategory: model.SidebarCategory{
|
|
Type: model.SidebarCategoryChannels,
|
|
},
|
|
},
|
|
},
|
|
[]*model.SidebarCategoryWithChannels{
|
|
{
|
|
SidebarCategory: model.SidebarCategory{
|
|
Type: model.SidebarCategoryChannels,
|
|
},
|
|
},
|
|
},
|
|
nil,
|
|
)
|
|
|
|
mockTeamStore := mocks.TeamStore{}
|
|
mockStore.On("Team").Return(&mockTeamStore)
|
|
mockTeamStore.On("GetMember", mock.AnythingOfType("*request.Context"), "team_id_1", "user_id_1").Return(&model.TeamMember{}, nil)
|
|
mockTeamStore.On("GetCommonTeamIDsForMultipleUsers", []string{"user_id_1", "user_id_2"}).Return([]string{"team_id_1", "team_id_2", "team_id_3"}, nil).Times(1)
|
|
mockTeamStore.On("GetMany", []string{"team_id_1", "team_id_2", "team_id_3"}).Return(
|
|
[]*model.Team{
|
|
{Id: "team_id_1", DisplayName: "Team 1"},
|
|
{Id: "team_id_2", DisplayName: "Team 2"},
|
|
{Id: "team_id_3", DisplayName: "Team 3"},
|
|
},
|
|
nil,
|
|
)
|
|
|
|
mockUserStore := mocks.UserStore{}
|
|
mockStore.On("User").Return(&mockUserStore)
|
|
mockUserStore.On("Get", context.Background(), "user_id_1").Return(&model.User{Username: "username_1"}, nil)
|
|
mockUserStore.On("GetProfilesInChannel", mock.AnythingOfType("*model.UserGetOptions")).Return([]*model.User{
|
|
{Id: "user_id_1", Username: "user_id_1"},
|
|
{Id: "user_id_2", Username: "user_id_2"},
|
|
}, nil)
|
|
mockUserStore.On("GetAllProfilesInChannel", mock.Anything, mock.Anything, mock.Anything).Return(map[string]*model.User{}, nil)
|
|
mockUserStore.On("InvalidateProfilesInChannelCacheByUser", "user_id_1").Return()
|
|
mockUserStore.On("InvalidateProfileCacheForUser", "user_id_1").Return()
|
|
|
|
mockPostStore := mocks.PostStore{}
|
|
mockStore.On("Post").Return(&mockPostStore)
|
|
mockPostStore.On("Save", mock.AnythingOfType("*request.Context"), mock.AnythingOfType("*model.Post")).Return(&model.Post{}, nil)
|
|
mockPostStore.On("InvalidateLastPostTimeCache", "channelidchannelidchanneli")
|
|
|
|
mockSystemStore := mocks.SystemStore{}
|
|
mockStore.On("System").Return(&mockSystemStore)
|
|
mockSystemStore.On("GetByName", model.MigrationKeyAdvancedPermissionsPhase2).Return(nil, nil)
|
|
|
|
var err error
|
|
|
|
th.App.ch.srv.userService, err = users.New(users.ServiceConfig{
|
|
UserStore: &mockUserStore,
|
|
ConfigFn: th.App.ch.srv.platform.Config,
|
|
SessionStore: &mocks.SessionStore{},
|
|
OAuthStore: &mocks.OAuthStore{},
|
|
LicenseFn: th.App.ch.srv.License,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
th.App.ch.srv.teamService, err = teams.New(teams.ServiceConfig{
|
|
TeamStore: &mockTeamStore,
|
|
ChannelStore: &mockChannelStore,
|
|
GroupStore: &mocks.GroupStore{},
|
|
Users: th.App.ch.srv.userService,
|
|
WebHub: th.App.ch.srv.platform,
|
|
ConfigFn: th.App.ch.srv.platform.Config,
|
|
LicenseFn: th.App.ch.srv.License,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
conversionRequest := &model.GroupMessageConversionRequestBody{
|
|
ChannelID: "channelidchannelidchanneli",
|
|
TeamID: "team_id_1",
|
|
Name: "new_name",
|
|
DisplayName: "New Display Name",
|
|
}
|
|
|
|
convertedChannel, appErr := th.App.ConvertGroupMessageToChannel(th.Context, "user_id_1", conversionRequest)
|
|
require.Nil(t, appErr)
|
|
require.Equal(t, model.ChannelTypePrivate, convertedChannel.Type)
|
|
}
|
|
|
|
func TestPatchChannelMembersNotifyProps(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
t.Run("should update multiple users' notify props", func(t *testing.T) {
|
|
user1 := th.CreateUser()
|
|
user2 := th.CreateUser()
|
|
|
|
channel1 := th.CreateChannel(th.Context, th.BasicTeam)
|
|
channel2 := th.CreateChannel(th.Context, th.BasicTeam)
|
|
|
|
th.LinkUserToTeam(user1, th.BasicTeam)
|
|
th.LinkUserToTeam(user2, th.BasicTeam)
|
|
th.AddUserToChannel(user1, channel1)
|
|
th.AddUserToChannel(user1, channel2)
|
|
th.AddUserToChannel(user2, channel1)
|
|
th.AddUserToChannel(user2, channel2)
|
|
|
|
result, appErr := th.App.PatchChannelMembersNotifyProps(th.Context, []*model.ChannelMemberIdentifier{
|
|
{UserId: user1.Id, ChannelId: channel1.Id},
|
|
{UserId: user1.Id, ChannelId: channel2.Id},
|
|
{UserId: user2.Id, ChannelId: channel1.Id},
|
|
}, map[string]string{
|
|
model.DesktopNotifyProp: model.ChannelNotifyNone,
|
|
"custom_key": "custom_value",
|
|
})
|
|
|
|
require.Nil(t, appErr)
|
|
|
|
// Confirm specified fields were updated
|
|
assert.Equal(t, model.ChannelNotifyNone, result[0].NotifyProps[model.DesktopNotifyProp])
|
|
assert.Equal(t, "custom_value", result[0].NotifyProps["custom_key"])
|
|
assert.Equal(t, model.ChannelNotifyNone, result[1].NotifyProps[model.DesktopNotifyProp])
|
|
assert.Equal(t, "custom_value", result[1].NotifyProps["custom_key"])
|
|
assert.Equal(t, model.ChannelNotifyNone, result[2].NotifyProps[model.DesktopNotifyProp])
|
|
assert.Equal(t, "custom_value", result[2].NotifyProps["custom_key"])
|
|
|
|
// Confirm unspecified fields were unchanged
|
|
assert.Equal(t, model.ChannelNotifyDefault, result[0].NotifyProps[model.PushNotifyProp])
|
|
assert.Equal(t, model.ChannelNotifyDefault, result[1].NotifyProps[model.PushNotifyProp])
|
|
assert.Equal(t, model.ChannelNotifyDefault, result[2].NotifyProps[model.PushNotifyProp])
|
|
|
|
// Confirm other members were unchanged
|
|
otherMember, appErr := th.App.GetChannelMember(th.Context, channel2.Id, user2.Id)
|
|
|
|
require.Nil(t, appErr)
|
|
|
|
assert.Equal(t, model.ChannelNotifyDefault, otherMember.NotifyProps[model.DesktopNotifyProp])
|
|
assert.Equal(t, "", otherMember.NotifyProps["custom_key"])
|
|
assert.Equal(t, model.ChannelNotifyDefault, otherMember.NotifyProps[model.PushNotifyProp])
|
|
})
|
|
|
|
t.Run("should send WS events for each user", func(t *testing.T) {
|
|
user1 := th.CreateUser()
|
|
user2 := th.CreateUser()
|
|
|
|
channel1 := th.CreateChannel(th.Context, th.BasicTeam)
|
|
channel2 := th.CreateChannel(th.Context, th.BasicTeam)
|
|
|
|
th.LinkUserToTeam(user1, th.BasicTeam)
|
|
th.LinkUserToTeam(user2, th.BasicTeam)
|
|
th.AddUserToChannel(user1, channel1)
|
|
th.AddUserToChannel(user1, channel2)
|
|
th.AddUserToChannel(user2, channel1)
|
|
|
|
eventTypesFilter := []model.WebsocketEventType{model.WebsocketEventChannelMemberUpdated}
|
|
|
|
messages1, closeWS1 := connectFakeWebSocket(t, th, user1.Id, "", eventTypesFilter)
|
|
defer closeWS1()
|
|
messages2, closeWS2 := connectFakeWebSocket(t, th, user2.Id, "", eventTypesFilter)
|
|
defer closeWS2()
|
|
|
|
_, appErr := th.App.PatchChannelMembersNotifyProps(th.Context, []*model.ChannelMemberIdentifier{
|
|
{UserId: user1.Id, ChannelId: channel1.Id},
|
|
{UserId: user1.Id, ChannelId: channel2.Id},
|
|
{UserId: user2.Id, ChannelId: channel1.Id},
|
|
}, map[string]string{
|
|
model.DesktopNotifyProp: model.ChannelNotifyNone,
|
|
"custom_key": "custom_value",
|
|
})
|
|
|
|
require.Nil(t, appErr)
|
|
|
|
// User1, Channel1
|
|
received := <-messages1
|
|
assert.Equal(t, model.WebsocketEventChannelMemberUpdated, received.EventType())
|
|
|
|
member := decodeJSON(received.GetData()["channelMember"], &model.ChannelMember{})
|
|
assert.Equal(t, user1.Id, member.UserId)
|
|
assert.Contains(t, []string{channel1.Id, channel2.Id}, member.ChannelId)
|
|
assert.Equal(t, model.ChannelNotifyNone, member.NotifyProps[model.DesktopNotifyProp])
|
|
assert.Equal(t, "custom_value", member.NotifyProps["custom_key"])
|
|
assert.Equal(t, model.ChannelNotifyDefault, member.NotifyProps[model.PushNotifyProp])
|
|
|
|
// User1, Channel2
|
|
received = <-messages1
|
|
assert.Equal(t, model.WebsocketEventChannelMemberUpdated, received.EventType())
|
|
|
|
member = decodeJSON(received.GetData()["channelMember"], &model.ChannelMember{})
|
|
assert.Equal(t, user1.Id, member.UserId)
|
|
assert.Contains(t, []string{channel1.Id, channel2.Id}, member.ChannelId)
|
|
assert.Equal(t, model.ChannelNotifyNone, member.NotifyProps[model.DesktopNotifyProp])
|
|
assert.Equal(t, "custom_value", member.NotifyProps["custom_key"])
|
|
assert.Equal(t, model.ChannelNotifyDefault, member.NotifyProps[model.PushNotifyProp])
|
|
|
|
// User2, Channel1
|
|
received = <-messages2
|
|
assert.Equal(t, model.WebsocketEventChannelMemberUpdated, received.EventType())
|
|
|
|
member = decodeJSON(received.GetData()["channelMember"], &model.ChannelMember{})
|
|
assert.Equal(t, user2.Id, member.UserId)
|
|
assert.Equal(t, channel1.Id, member.ChannelId)
|
|
assert.Equal(t, model.ChannelNotifyNone, member.NotifyProps[model.DesktopNotifyProp])
|
|
assert.Equal(t, "custom_value", member.NotifyProps["custom_key"])
|
|
assert.Equal(t, model.ChannelNotifyDefault, member.NotifyProps[model.PushNotifyProp])
|
|
})
|
|
|
|
t.Run("should return an error when trying to update too many users at once", func(t *testing.T) {
|
|
identifiers := make([]*model.ChannelMemberIdentifier, 201)
|
|
for i := range identifiers {
|
|
identifiers[i] = &model.ChannelMemberIdentifier{UserId: "fakeuser", ChannelId: "fakechannel"}
|
|
}
|
|
|
|
_, appErr := th.App.PatchChannelMembersNotifyProps(th.Context, identifiers, map[string]string{})
|
|
|
|
assert.NotNil(t, appErr)
|
|
})
|
|
}
|
|
|
|
func TestGetChannelFileCount(t *testing.T) {
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
channel := th.BasicChannel
|
|
|
|
// Create a post with files
|
|
post := &model.Post{
|
|
ChannelId: channel.Id,
|
|
Message: "This is a test post",
|
|
UserId: th.BasicUser.Id,
|
|
}
|
|
post, appErr := th.App.CreatePost(th.Context, post, channel, model.CreatePostFlags{})
|
|
require.Nil(t, appErr)
|
|
|
|
fileInfo1 := &model.FileInfo{
|
|
Name: "file1.txt",
|
|
MimeType: "text/plain",
|
|
ChannelId: channel.Id,
|
|
CreatorId: th.BasicUser.Id,
|
|
PostId: post.Id,
|
|
Path: "/path/to/file1.txt",
|
|
}
|
|
_, err := th.App.Srv().Store().FileInfo().Save(th.Context, fileInfo1)
|
|
require.NoError(t, err)
|
|
|
|
fileInfo2 := &model.FileInfo{
|
|
Name: "file2.txt",
|
|
MimeType: "text/plain",
|
|
ChannelId: channel.Id,
|
|
CreatorId: th.BasicUser.Id,
|
|
PostId: post.Id,
|
|
Path: "/path/to/file2.txt",
|
|
}
|
|
_, err = th.App.Srv().Store().FileInfo().Save(th.Context, fileInfo2)
|
|
require.NoError(t, err)
|
|
|
|
// Create a file without a post
|
|
fileInfo3 := &model.FileInfo{
|
|
Name: "file3.txt",
|
|
MimeType: "text/plain",
|
|
ChannelId: channel.Id,
|
|
CreatorId: th.BasicUser.Id,
|
|
Path: "/path/to/file3.txt",
|
|
}
|
|
_, err = th.App.Srv().Store().FileInfo().Save(th.Context, fileInfo3)
|
|
require.NoError(t, err)
|
|
|
|
count, appErr := th.App.GetChannelFileCount(th.Context, channel.Id)
|
|
require.Nil(t, appErr)
|
|
require.Equal(t, int64(2), count)
|
|
}
|
|
|
|
func TestCheckIfChannelIsRestrictedDM(t *testing.T) {
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
channel := th.CreateDmChannel(th.BasicUser2)
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.TeamSettings.RestrictDirectMessage = model.DirectMessageTeam
|
|
})
|
|
|
|
// Ensure the two users do not share a team
|
|
teams, err := th.App.GetTeamsForUser(th.BasicUser.Id)
|
|
require.Nil(t, err)
|
|
for _, team := range teams {
|
|
teamErr := th.App.RemoveUserFromTeam(th.Context, team.Id, th.BasicUser.Id, th.SystemAdminUser.Id)
|
|
require.Nil(t, teamErr)
|
|
}
|
|
teams, err = th.App.GetTeamsForUser(th.BasicUser2.Id)
|
|
require.Nil(t, err)
|
|
for _, team := range teams {
|
|
teamErr := th.App.RemoveUserFromTeam(th.Context, team.Id, th.BasicUser2.Id, th.SystemAdminUser.Id)
|
|
require.Nil(t, teamErr)
|
|
}
|
|
|
|
team1 := th.CreateTeam()
|
|
team2 := th.CreateTeam()
|
|
th.LinkUserToTeam(th.BasicUser, team1)
|
|
th.LinkUserToTeam(th.BasicUser2, team2)
|
|
|
|
t.Run("should be restricted", func(t *testing.T) {
|
|
restricted, err := th.App.CheckIfChannelIsRestrictedDM(th.Context, channel)
|
|
require.Nil(t, err)
|
|
require.True(t, restricted)
|
|
})
|
|
|
|
t.Run("setting set to any", func(t *testing.T) {
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.TeamSettings.RestrictDirectMessage = model.DirectMessageAny
|
|
})
|
|
|
|
restricted, err := th.App.CheckIfChannelIsRestrictedDM(th.Context, channel)
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.TeamSettings.RestrictDirectMessage = model.DirectMessageTeam
|
|
})
|
|
|
|
require.Nil(t, err)
|
|
require.False(t, restricted)
|
|
})
|
|
|
|
t.Run("channel is not a direct or group channel", func(t *testing.T) {
|
|
openChannel := th.CreateChannel(th.Context, th.BasicTeam)
|
|
restricted, err := th.App.CheckIfChannelIsRestrictedDM(th.Context, openChannel)
|
|
require.Nil(t, err)
|
|
require.False(t, restricted)
|
|
})
|
|
|
|
t.Run("group message where users share a team", func(t *testing.T) {
|
|
team := th.CreateTeam()
|
|
user1 := th.CreateUser()
|
|
user2 := th.CreateUser()
|
|
th.LinkUserToTeam(user1, team)
|
|
th.LinkUserToTeam(user2, team)
|
|
th.LinkUserToTeam(th.BasicUser, team)
|
|
|
|
groupChannel := th.CreateGroupChannel(th.Context, user1, user2)
|
|
|
|
restricted, err := th.App.CheckIfChannelIsRestrictedDM(th.Context, groupChannel)
|
|
require.Nil(t, err)
|
|
require.False(t, restricted)
|
|
})
|
|
}
|
|
|
|
func TestUpdateChannel(t *testing.T) {
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
t.Run("should be able to update banner info", func(t *testing.T) {
|
|
channel := th.createChannel(th.Context, th.BasicTeam, model.ChannelTypeOpen)
|
|
|
|
channel.BannerInfo = &model.ChannelBannerInfo{
|
|
Enabled: model.NewPointer(true),
|
|
Text: model.NewPointer("banner text"),
|
|
BackgroundColor: model.NewPointer("#000000"),
|
|
}
|
|
|
|
updatedChannel, appErr := th.App.UpdateChannel(th.Context, channel)
|
|
require.Nil(t, appErr)
|
|
require.NotNil(t, updatedChannel.BannerInfo)
|
|
require.True(t, *updatedChannel.BannerInfo.Enabled)
|
|
require.Equal(t, "banner text", *updatedChannel.BannerInfo.Text)
|
|
require.Equal(t, "#000000", *updatedChannel.BannerInfo.BackgroundColor)
|
|
|
|
channel.BannerInfo.Enabled = model.NewPointer(false)
|
|
updatedChannel, appErr = th.App.UpdateChannel(th.Context, channel)
|
|
require.Nil(t, appErr)
|
|
require.NotNil(t, updatedChannel.BannerInfo)
|
|
require.False(t, *updatedChannel.BannerInfo.Enabled)
|
|
})
|
|
}
|
|
|
|
func TestPatchChannel(t *testing.T) {
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
t.Run("should be able to patch banner info", func(t *testing.T) {
|
|
channel := th.createChannel(th.Context, th.BasicTeam, model.ChannelTypeOpen)
|
|
|
|
patch := &model.ChannelPatch{
|
|
BannerInfo: &model.ChannelBannerInfo{
|
|
Enabled: model.NewPointer(true),
|
|
Text: model.NewPointer("banner text"),
|
|
BackgroundColor: model.NewPointer("#000000"),
|
|
},
|
|
}
|
|
|
|
patchedChannel, appErr := th.App.PatchChannel(th.Context, channel, patch, channel.CreatorId)
|
|
require.Nil(t, appErr)
|
|
require.NotNil(t, patchedChannel.BannerInfo)
|
|
require.True(t, *patchedChannel.BannerInfo.Enabled)
|
|
require.Equal(t, "banner text", *patchedChannel.BannerInfo.Text)
|
|
require.Equal(t, "#000000", *patchedChannel.BannerInfo.BackgroundColor)
|
|
|
|
patch = &model.ChannelPatch{
|
|
BannerInfo: &model.ChannelBannerInfo{
|
|
Text: model.NewPointer("text 1"),
|
|
},
|
|
}
|
|
|
|
patchedChannel, appErr = th.App.PatchChannel(th.Context, channel, patch, channel.CreatorId)
|
|
require.Nil(t, appErr)
|
|
require.NotNil(t, patchedChannel.BannerInfo)
|
|
require.True(t, *patchedChannel.BannerInfo.Enabled)
|
|
require.Equal(t, "text 1", *patchedChannel.BannerInfo.Text)
|
|
require.Equal(t, "#000000", *patchedChannel.BannerInfo.BackgroundColor)
|
|
|
|
patch = &model.ChannelPatch{
|
|
BannerInfo: &model.ChannelBannerInfo{
|
|
BackgroundColor: model.NewPointer("#FF00FF"),
|
|
},
|
|
}
|
|
|
|
patchedChannel, appErr = th.App.PatchChannel(th.Context, channel, patch, channel.CreatorId)
|
|
require.Nil(t, appErr)
|
|
require.NotNil(t, patchedChannel.BannerInfo)
|
|
require.True(t, *patchedChannel.BannerInfo.Enabled)
|
|
require.Equal(t, "text 1", *patchedChannel.BannerInfo.Text)
|
|
require.Equal(t, "#FF00FF", *patchedChannel.BannerInfo.BackgroundColor)
|
|
|
|
// should be able to unset fields as well
|
|
patch = &model.ChannelPatch{
|
|
BannerInfo: &model.ChannelBannerInfo{
|
|
Enabled: model.NewPointer(false),
|
|
},
|
|
}
|
|
|
|
patchedChannel, appErr = th.App.PatchChannel(th.Context, channel, patch, channel.CreatorId)
|
|
require.Nil(t, appErr)
|
|
require.NotNil(t, patchedChannel.BannerInfo)
|
|
require.False(t, *patchedChannel.BannerInfo.Enabled)
|
|
})
|
|
|
|
t.Run("should not allow saving channel with invalid background info", func(t *testing.T) {
|
|
channel := th.createChannel(th.Context, th.BasicTeam, model.ChannelTypeOpen)
|
|
|
|
// enabling banner without data is invalid
|
|
patch := &model.ChannelPatch{
|
|
BannerInfo: &model.ChannelBannerInfo{
|
|
Enabled: model.NewPointer(true),
|
|
},
|
|
}
|
|
|
|
patchedChannel, appErr := th.App.PatchChannel(th.Context, channel, patch, channel.CreatorId)
|
|
require.Nil(t, patchedChannel)
|
|
require.NotNil(t, appErr)
|
|
require.Equal(t, http.StatusBadRequest, appErr.StatusCode)
|
|
require.Equal(t, "model.channel.is_valid.banner_info.text.empty.app_error", appErr.Id)
|
|
})
|
|
|
|
t.Run("cannot configure channel banner on DMs", func(t *testing.T) {
|
|
dmChannel := th.CreateDmChannel(th.BasicUser2)
|
|
|
|
// enabling banner without data is invalid
|
|
patch := &model.ChannelPatch{
|
|
BannerInfo: &model.ChannelBannerInfo{
|
|
Enabled: model.NewPointer(true),
|
|
},
|
|
}
|
|
|
|
patchedChannel, appErr := th.App.PatchChannel(th.Context, dmChannel, patch, dmChannel.CreatorId)
|
|
require.Nil(t, patchedChannel)
|
|
require.NotNil(t, appErr)
|
|
require.Equal(t, appErr.StatusCode, http.StatusBadRequest)
|
|
require.Equal(t, "model.channel.is_valid.banner_info.channel_type.app_error", appErr.Id)
|
|
})
|
|
|
|
t.Run("cannot configure channel banner on GMs", func(t *testing.T) {
|
|
user3 := th.CreateUser()
|
|
gmChannel := th.CreateGroupChannel(th.Context, th.BasicUser2, user3)
|
|
|
|
// enabling banner without data is invalid
|
|
patch := &model.ChannelPatch{
|
|
BannerInfo: &model.ChannelBannerInfo{
|
|
Enabled: model.NewPointer(true),
|
|
},
|
|
}
|
|
|
|
patchedChannel, appErr := th.App.PatchChannel(th.Context, gmChannel, patch, gmChannel.CreatorId)
|
|
require.Nil(t, patchedChannel)
|
|
require.NotNil(t, appErr)
|
|
require.Equal(t, appErr.StatusCode, http.StatusBadRequest)
|
|
require.Equal(t, "model.channel.is_valid.banner_info.channel_type.app_error", appErr.Id)
|
|
})
|
|
|
|
t.Run("cannot patch restricted DM", func(t *testing.T) {
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.TeamSettings.RestrictDirectMessage = model.DirectMessageTeam
|
|
})
|
|
|
|
// Create a DM channel between two users who don't share a team
|
|
dmChannel := th.CreateDmChannel(th.BasicUser2)
|
|
|
|
// Ensure the two users do not share a team
|
|
teams, err := th.App.GetTeamsForUser(th.BasicUser.Id)
|
|
require.Nil(t, err)
|
|
for _, team := range teams {
|
|
teamErr := th.App.RemoveUserFromTeam(th.Context, team.Id, th.BasicUser.Id, th.SystemAdminUser.Id)
|
|
require.Nil(t, teamErr)
|
|
}
|
|
teams, err = th.App.GetTeamsForUser(th.BasicUser2.Id)
|
|
require.Nil(t, err)
|
|
for _, team := range teams {
|
|
teamErr := th.App.RemoveUserFromTeam(th.Context, team.Id, th.BasicUser2.Id, th.SystemAdminUser.Id)
|
|
require.Nil(t, teamErr)
|
|
}
|
|
|
|
// Create separate teams for each user
|
|
team1 := th.CreateTeam()
|
|
team2 := th.CreateTeam()
|
|
th.LinkUserToTeam(th.BasicUser, team1)
|
|
th.LinkUserToTeam(th.BasicUser2, team2)
|
|
|
|
patch := &model.ChannelPatch{
|
|
DisplayName: model.NewPointer("Updated DM"),
|
|
}
|
|
|
|
_, appErr := th.App.PatchChannel(th.Context, dmChannel, patch, th.BasicUser.Id)
|
|
require.NotNil(t, appErr)
|
|
require.Equal(t, "api.channel.patch_update_channel.restricted_dm.app_error", appErr.Id)
|
|
require.Equal(t, http.StatusBadRequest, appErr.StatusCode)
|
|
|
|
// Reset config
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.TeamSettings.RestrictDirectMessage = model.DirectMessageAny
|
|
})
|
|
})
|
|
}
|
|
|
|
func TestCreateChannelWithCategorySorting(t *testing.T) {
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
// Enable ExperimentalChannelCategorySorting
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.ExperimentalSettings.ExperimentalChannelCategorySorting = true
|
|
})
|
|
|
|
t.Run("should set category when adding user to channel with category and trim white spaces", func(t *testing.T) {
|
|
channel := &model.Channel{
|
|
DisplayName: " Category / Channel Name ",
|
|
Name: "name1",
|
|
Type: model.ChannelTypeOpen,
|
|
TeamId: th.BasicTeam.Id,
|
|
}
|
|
|
|
channel, appErr := th.App.CreateChannelWithUser(th.Context, channel, th.BasicUser.Id)
|
|
require.Nil(t, appErr)
|
|
require.Equal(t, "Channel Name", channel.DisplayName)
|
|
require.Equal(t, "Category", channel.DefaultCategoryName)
|
|
|
|
// Verify channel is in default category
|
|
categories, appErr := th.App.GetSidebarCategoriesForTeamForUser(th.Context, th.BasicUser.Id, th.BasicTeam.Id)
|
|
require.Nil(t, appErr)
|
|
|
|
foundCategory := false
|
|
for _, category := range categories.Categories {
|
|
if category.DisplayName == "Category" {
|
|
foundCategory = true
|
|
assert.Contains(t, category.Channels, channel.Id)
|
|
break
|
|
}
|
|
}
|
|
assert.True(t, foundCategory, "Category 'Category' not found in sidebar categories")
|
|
|
|
// Add user to channel
|
|
_, appErr = th.App.AddUserToChannel(th.Context, th.BasicUser2, channel, false)
|
|
require.Nil(t, appErr)
|
|
|
|
// Verify channel is in default category
|
|
categories2, appErr := th.App.GetSidebarCategoriesForTeamForUser(th.Context, th.BasicUser2.Id, th.BasicTeam.Id)
|
|
require.Nil(t, appErr)
|
|
|
|
foundCategory2 := false
|
|
for _, category := range categories2.Categories {
|
|
if category.DisplayName == "Category" {
|
|
foundCategory2 = true
|
|
assert.Contains(t, category.Channels, channel.Id)
|
|
break
|
|
}
|
|
}
|
|
assert.True(t, foundCategory2, "Category 'Category' not found in sidebar categories")
|
|
})
|
|
|
|
t.Run("should not set category when feature is disabled", func(t *testing.T) {
|
|
channel := &model.Channel{
|
|
DisplayName: "Category2/Channel Name",
|
|
Name: "name2",
|
|
Type: model.ChannelTypeOpen,
|
|
TeamId: th.BasicTeam.Id,
|
|
}
|
|
|
|
channel, appErr := th.App.CreateChannel(th.Context, channel, false)
|
|
require.Nil(t, appErr)
|
|
require.Equal(t, "Channel Name", channel.DisplayName)
|
|
require.Equal(t, "Category2", channel.DefaultCategoryName)
|
|
|
|
// Disable ExperimentalChannelCategorySorting
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.ExperimentalSettings.ExperimentalChannelCategorySorting = false
|
|
})
|
|
|
|
// Add user to channel
|
|
_, appErr = th.App.AddUserToChannel(th.Context, th.BasicUser, channel, false)
|
|
require.Nil(t, appErr)
|
|
|
|
// Verify channel is in default category
|
|
categories, appErr := th.App.GetSidebarCategoriesForTeamForUser(th.Context, th.BasicUser.Id, th.BasicTeam.Id)
|
|
require.Nil(t, appErr)
|
|
|
|
foundCategory := false
|
|
for _, category := range categories.Categories {
|
|
if category.DisplayName == "Category2" {
|
|
foundCategory = true
|
|
break
|
|
}
|
|
}
|
|
assert.False(t, foundCategory, "Category 'Category2' not found in sidebar categories")
|
|
})
|
|
}
|
|
|
|
func TestPatchChannelWithCategorySorting(t *testing.T) {
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
// Enable ExperimentalChannelCategorySorting
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.ExperimentalSettings.ExperimentalChannelCategorySorting = true
|
|
})
|
|
|
|
// Create initial channel
|
|
channel := th.createChannel(th.Context, th.BasicTeam, model.ChannelTypeOpen)
|
|
channel.DisplayName = "Initial Name"
|
|
channel, appErr := th.App.UpdateChannel(th.Context, channel)
|
|
require.Nil(t, appErr)
|
|
|
|
// Add user to channel
|
|
_, appErr = th.App.AddUserToChannel(th.Context, th.BasicUser, channel, false)
|
|
require.Nil(t, appErr)
|
|
|
|
// Patch channel with new display name containing category
|
|
patch := &model.ChannelPatch{
|
|
DisplayName: model.NewPointer(" New Category / New Channel Name "),
|
|
}
|
|
|
|
patchedChannel, appErr := th.App.PatchChannel(th.Context, channel, patch, channel.CreatorId)
|
|
require.Nil(t, appErr)
|
|
require.Equal(t, "New Channel Name", patchedChannel.DisplayName)
|
|
require.Equal(t, "New Category", patchedChannel.DefaultCategoryName)
|
|
|
|
// Test that category is not updated when feature is disabled
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.ExperimentalSettings.ExperimentalChannelCategorySorting = false
|
|
})
|
|
|
|
patch = &model.ChannelPatch{
|
|
DisplayName: model.NewPointer("Disabled Category/Channel Name"),
|
|
}
|
|
|
|
patchedChannel, appErr = th.App.PatchChannel(th.Context, channel, patch, channel.CreatorId)
|
|
require.Nil(t, appErr)
|
|
require.Equal(t, "Disabled Category/Channel Name", patchedChannel.DisplayName)
|
|
require.Equal(t, "New Category", patchedChannel.DefaultCategoryName)
|
|
}
|