// 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) }