Files
mattermost/app/channel_test.go
Agniva De Sarker f0ecdcc5f5 Move Channels into App (#18623)
* Move Channels into App

In this PR, we make Channels as part of App
instead of Server. This is part of the transition period
of moving fields from Server to Channels.

For now, Channels contains Server. So the hierarchy is

App -> Channels -> Server.

And as a first step, we also move httpService to Channels.

```release-note
NONE
```

* Fixing another test

```release-note
NONE
```

* new method

```release-note
NONE
```
2021-10-12 11:39:49 +05:30

2349 lines
83 KiB
Go

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package app
import (
"context"
"fmt"
"net/http"
"os"
"sort"
"strings"
"sync"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"github.com/mattermost/mattermost-server/v6/app/users"
"github.com/mattermost/mattermost-server/v6/model"
"github.com/mattermost/mattermost-server/v6/store/storetest/mocks"
)
func TestPermanentDeleteChannel(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.ServiceSettings.EnableIncomingWebhooks = true
*cfg.ServiceSettings.EnableOutgoingWebhooks = true
})
channel, err := 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, err)
defer func() {
th.App.PermanentDeleteChannel(channel)
}()
incoming, err := 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, err, "Unable to create Incoming Webhook for Channel")
defer th.App.DeleteIncomingWebhook(incoming.Id)
incoming, err = th.App.GetIncomingWebhook(incoming.Id)
require.NotNil(t, incoming, "incoming webhook should not be nil")
require.Nil(t, err, "Unable to get new incoming webhook")
outgoing, err := th.App.CreateOutgoingWebhook(&model.OutgoingWebhook{
ChannelId: channel.Id,
TeamId: channel.TeamId,
CreatorId: th.BasicUser.Id,
CallbackURLs: []string{"http://foo"},
})
require.Nil(t, err)
defer th.App.DeleteOutgoingWebhook(outgoing.Id)
outgoing, err = th.App.GetOutgoingWebhook(outgoing.Id)
require.NotNil(t, outgoing, "Outgoing webhook should not be nil")
require.Nil(t, err, "Unable to get new outgoing webhook")
err = th.App.PermanentDeleteChannel(channel)
require.Nil(t, err)
incoming, err = th.App.GetIncomingWebhook(incoming.Id)
require.Nil(t, incoming, "Incoming webhook should be nil")
require.NotNil(t, err, "Incoming webhook wasn't deleted")
outgoing, err = th.App.GetOutgoingWebhook(outgoing.Id)
require.Nil(t, outgoing, "Outgoing webhook should be nil")
require.NotNil(t, err, "Outgoing webhook wasn't deleted")
}
func TestRemoveAllDeactivatedMembersFromChannel(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
var err *model.AppError
team := th.CreateTeam()
channel := th.CreateChannel(team)
defer func() {
th.App.PermanentDeleteChannel(channel)
th.App.PermanentDeleteTeam(team)
}()
_, _, err = th.App.AddUserToTeam(th.Context, team.Id, th.BasicUser.Id, "")
require.Nil(t, err)
deacivatedUser := th.CreateUser()
_, _, err = th.App.AddUserToTeam(th.Context, team.Id, deacivatedUser.Id, "")
require.Nil(t, err)
_, err = th.App.AddUserToChannel(deacivatedUser, channel, false)
require.Nil(t, err)
channelMembers, err := th.App.GetChannelMembersPage(channel.Id, 0, 10000000)
require.Nil(t, err)
require.Len(t, channelMembers, 2)
_, err = th.App.UpdateActive(th.Context, deacivatedUser, false)
require.Nil(t, err)
err = th.App.RemoveAllDeactivatedMembersFromChannel(channel)
require.Nil(t, err)
channelMembers, err = th.App.GetChannelMembersPage(channel.Id, 0, 10000000)
require.Nil(t, err)
require.Len(t, channelMembers, 1)
}
func TestMoveChannel(t *testing.T) {
t.Run("should move channels between teams", func(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
var err *model.AppError
sourceTeam := th.CreateTeam()
targetTeam := th.CreateTeam()
channel1 := th.CreateChannel(sourceTeam)
defer func() {
th.App.PermanentDeleteChannel(channel1)
th.App.PermanentDeleteTeam(sourceTeam)
th.App.PermanentDeleteTeam(targetTeam)
}()
_, _, err = th.App.AddUserToTeam(th.Context, sourceTeam.Id, th.BasicUser.Id, "")
require.Nil(t, err)
_, _, err = th.App.AddUserToTeam(th.Context, sourceTeam.Id, th.BasicUser2.Id, "")
require.Nil(t, err)
_, _, err = th.App.AddUserToTeam(th.Context, targetTeam.Id, th.BasicUser.Id, "")
require.Nil(t, err)
_, err = th.App.AddUserToChannel(th.BasicUser, channel1, false)
require.Nil(t, err)
_, err = th.App.AddUserToChannel(th.BasicUser2, channel1, false)
require.Nil(t, err)
err = th.App.MoveChannel(th.Context, targetTeam, channel1, th.BasicUser)
require.NotNil(t, err, "Should have failed due to mismatched members.")
_, _, err = th.App.AddUserToTeam(th.Context, targetTeam.Id, th.BasicUser2.Id, "")
require.Nil(t, err)
err = th.App.MoveChannel(th.Context, targetTeam, channel1, th.BasicUser)
require.Nil(t, err)
// Test moving a channel with a deactivated user who isn't in the destination team.
// It should fail, unless removeDeactivatedMembers is true.
deacivatedUser := th.CreateUser()
channel2 := th.CreateChannel(sourceTeam)
defer th.App.PermanentDeleteChannel(channel2)
_, _, err = th.App.AddUserToTeam(th.Context, sourceTeam.Id, deacivatedUser.Id, "")
require.Nil(t, err)
_, err = th.App.AddUserToChannel(th.BasicUser, channel2, false)
require.Nil(t, err)
_, err = th.App.AddUserToChannel(deacivatedUser, channel2, false)
require.Nil(t, err)
_, err = th.App.UpdateActive(th.Context, deacivatedUser, false)
require.Nil(t, err)
err = th.App.MoveChannel(th.Context, targetTeam, channel2, th.BasicUser)
require.NotNil(t, err, "Should have failed due to mismatched deacivated 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, err = th.App.CreateChannel(th.Context, channel3, false)
require.Nil(t, err)
defer th.App.PermanentDeleteChannel(channel3)
err = th.App.MoveChannel(th.Context, targetTeam, channel3, th.BasicUser)
assert.Nil(t, err)
})
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(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, err := th.App.CreateSidebarCategory(th.BasicUser.Id, sourceTeam.Id, &model.SidebarCategoryWithChannels{
SidebarCategory: model.SidebarCategory{
DisplayName: "new category",
},
Channels: []string{channel.Id},
})
require.Nil(t, err)
require.Equal(t, []string{channel.Id}, category.Channels)
err = th.App.MoveChannel(th.Context, targetTeam, channel, th.BasicUser)
require.Nil(t, err)
moved, err := th.App.GetChannel(channel.Id)
require.Nil(t, err)
require.Equal(t, targetTeam.Id, moved.TeamId)
// The channel should no longer be on the old team
updatedCategory, err := th.App.GetSidebarCategory(category.Id)
require.Nil(t, err)
assert.Equal(t, []string{}, updatedCategory.Channels)
// And it should be on the new team instead
categories, err := th.App.GetSidebarCategories(th.BasicUser.Id, targetTeam.Id)
require.Nil(t, err)
require.Equal(t, model.SidebarCategoryChannels, categories.Categories[1].Type)
assert.Contains(t, categories.Categories[1].Channels, channel.Id)
})
}
func TestRemoveUsersFromChannelNotMemberOfTeam(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
team := th.CreateTeam()
team2 := th.CreateTeam()
channel1 := th.CreateChannel(team)
defer func() {
th.App.PermanentDeleteChannel(channel1)
th.App.PermanentDeleteTeam(team)
th.App.PermanentDeleteTeam(team2)
}()
_, _, err := th.App.AddUserToTeam(th.Context, team.Id, th.BasicUser.Id, "")
require.Nil(t, err)
_, _, err = th.App.AddUserToTeam(th.Context, team2.Id, th.BasicUser.Id, "")
require.Nil(t, err)
_, _, err = th.App.AddUserToTeam(th.Context, team.Id, th.BasicUser2.Id, "")
require.Nil(t, err)
_, err = th.App.AddUserToChannel(th.BasicUser, channel1, false)
require.Nil(t, err)
_, err = th.App.AddUserToChannel(th.BasicUser2, channel1, false)
require.Nil(t, err)
err = th.App.RemoveUsersFromChannelNotMemberOfTeam(th.Context, th.SystemAdminUser, channel1, team2)
require.Nil(t, err)
channelMembers, err := th.App.GetChannelMembersPage(channel1.Id, 0, 10000000)
require.Nil(t, err)
require.Len(t, channelMembers, 1)
members := make([]model.ChannelMember, len(channelMembers))
for i, m := range channelMembers {
members[i] = m
}
require.Equal(t, members[0].UserId, th.BasicUser.Id)
}
func TestJoinDefaultChannelsCreatesChannelMemberHistoryRecordTownSquare(t *testing.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, townSquareChannelId)
require.NoError(t, nErr)
initialNumTownSquareUsers := len(users)
// create a new user that joins the default channels
user := th.CreateUser()
th.App.JoinDefaultChannels(th.Context, th.BasicTeam.Id, user, false, "")
// there should be a ChannelMemberHistory record for the user
histories, nErr := th.App.Srv().Store.ChannelMemberHistory().GetUsersInChannelDuring(model.GetMillis()-100, model.GetMillis()+100, 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) {
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, offTopicChannelId)
require.NoError(t, nErr)
initialNumTownSquareUsers := len(users)
// create a new user that joins the default channels
user := th.CreateUser()
th.App.JoinDefaultChannels(th.Context, th.BasicTeam.Id, user, false, "")
// there should be a ChannelMemberHistory record for the user
histories, nErr := th.App.Srv().Store.ChannelMemberHistory().GetUsersInChannelDuring(model.GetMillis()-100, model.GetMillis()+100, 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) {
th := Setup(t).InitBasic()
defer th.TearDown()
basicChannel2 := th.CreateChannel(th.BasicTeam)
defer th.App.PermanentDeleteChannel(basicChannel2)
defaultChannelList := []string{th.BasicChannel.Name, basicChannel2.Name, basicChannel2.Name}
th.App.Config().TeamSettings.ExperimentalDefaultChannels = defaultChannelList
user := th.CreateUser()
th.App.JoinDefaultChannels(th.Context, th.BasicTeam.Id, user, false, "")
for _, channelName := range defaultChannelList {
channel, err := th.App.GetChannelByName(channelName, th.BasicTeam.Id, false)
require.Nil(t, err, "Expected nil, didn't receive nil")
member, err := th.App.GetChannelMember(context.Background(), channel.Id, user.Id)
require.NotNil(t, member, "Expected member object, got nil")
require.Nil(t, err, "Expected nil object, didn't receive nil")
}
}
func TestCreateChannelPublicCreatesChannelMemberHistoryRecord(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
// creates a public channel and adds basic user to it
publicChannel := th.createChannel(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, 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) {
th := Setup(t).InitBasic()
defer th.TearDown()
// creates a private channel and adds basic user to it
privateChannel := th.createChannel(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, 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) {
th := Setup(t).InitBasic()
defer th.TearDown()
channel, err := th.App.CreateChannel(th.Context, &model.Channel{DisplayName: " Public 1 ", Name: "public1", Type: model.ChannelTypeOpen, TeamId: th.BasicTeam.Id}, false)
defer th.App.PermanentDeleteChannel(channel)
require.Nil(t, err)
require.Equal(t, channel.DisplayName, "Public 1")
}
func TestUpdateChannelPrivacy(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
privateChannel := th.createChannel(th.BasicTeam, model.ChannelTypePrivate)
privateChannel.Type = model.ChannelTypeOpen
publicChannel, err := th.App.UpdateChannelPrivacy(th.Context, privateChannel, th.BasicUser)
require.Nil(t, err, "Failed to update channel privacy.")
assert.Equal(t, publicChannel.Id, privateChannel.Id)
assert.Equal(t, publicChannel.Type, model.ChannelTypeOpen)
}
func TestCreateGroupChannelCreatesChannelMemberHistoryRecord(t *testing.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, err := th.App.CreateGroupChannel(groupUserIds, th.BasicUser.Id)
require.Nil(t, err, "Failed to create group channel.")
histories, nErr := th.App.Srv().Store.ChannelMemberHistory().GetUsersInChannelDuring(model.GetMillis()-100, model.GetMillis()+100, 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) {
th := Setup(t)
defer th.TearDown()
user1 := th.CreateUser()
user2 := th.CreateUser()
channel, err := th.App.GetOrCreateDirectChannel(th.Context, user1.Id, user2.Id)
require.Nil(t, err, "Failed to create direct channel.")
histories, nErr := th.App.Srv().Store.ChannelMemberHistory().GetUsersInChannelDuring(model.GetMillis()-100, model.GetMillis()+100, 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) {
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, err := th.App.GetOrCreateDirectChannel(th.Context, user1.Id, user2.Id)
require.Nil(t, err, "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, 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) {
th := Setup(t).InitBasic()
defer th.TearDown()
// create a user and add it to a channel
user := th.CreateUser()
_, err := th.App.AddTeamMember(th.Context, th.BasicTeam.Id, user.Id)
require.Nil(t, err, "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.BasicTeam, model.ChannelTypeOpen)
_, err = th.App.AddUserToChannel(user, channel, false)
require.Nil(t, err, "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, 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 TestLeaveDefaultChannel(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
guest := th.CreateGuest()
th.LinkUserToTeam(guest, th.BasicTeam)
townSquare, err := th.App.GetChannelByName("town-square", th.BasicTeam.Id, false)
require.Nil(t, err)
th.AddUserToChannel(guest, townSquare)
th.AddUserToChannel(th.BasicUser, townSquare)
t.Run("User tries to leave the default channel", func(t *testing.T) {
err = th.App.LeaveChannel(th.Context, townSquare.Id, th.BasicUser.Id)
assert.NotNil(t, err, "It should fail to remove a regular user from the default channel")
assert.Equal(t, err.Id, "api.channel.remove.default.app_error")
_, err = th.App.GetChannelMember(context.Background(), townSquare.Id, th.BasicUser.Id)
assert.Nil(t, err)
})
t.Run("Guest leaves the default channel", func(t *testing.T) {
err = th.App.LeaveChannel(th.Context, townSquare.Id, guest.Id)
assert.Nil(t, err, "It should allow to remove a guest user from the default channel")
_, err = th.App.GetChannelMember(context.Background(), townSquare.Id, guest.Id)
assert.NotNil(t, err)
})
}
func TestLeaveLastChannel(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
guest := th.CreateGuest()
th.LinkUserToTeam(guest, th.BasicTeam)
townSquare, err := th.App.GetChannelByName("town-square", th.BasicTeam.Id, false)
require.Nil(t, err)
th.AddUserToChannel(guest, townSquare)
th.AddUserToChannel(guest, th.BasicChannel)
t.Run("Guest leaves not last channel", func(t *testing.T) {
err = th.App.LeaveChannel(th.Context, townSquare.Id, guest.Id)
require.Nil(t, err)
_, err = th.App.GetTeamMember(th.BasicTeam.Id, guest.Id)
assert.Nil(t, err, "It should maintain the team membership")
})
t.Run("Guest leaves last channel", func(t *testing.T) {
err = th.App.LeaveChannel(th.Context, th.BasicChannel.Id, guest.Id)
assert.Nil(t, err, "It should allow to remove a guest user from the default channel")
_, err = th.App.GetChannelMember(context.Background(), th.BasicChannel.Id, guest.Id)
assert.NotNil(t, err)
_, err = th.App.GetTeamMember(th.BasicTeam.Id, guest.Id)
assert.Nil(t, err, "It should remove the team membership")
})
}
func TestAddChannelMemberNoUserRequestor(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
// create a user and add it to a channel
user := th.CreateUser()
_, err := th.App.AddTeamMember(th.Context, th.BasicTeam.Id, user.Id)
require.Nil(t, err)
groupUserIds := make([]string, 0)
groupUserIds = append(groupUserIds, th.BasicUser.Id)
groupUserIds = append(groupUserIds, user.Id)
channel := th.createChannel(th.BasicTeam, model.ChannelTypeOpen)
_, err = th.App.AddChannelMember(th.Context, user.Id, channel, ChannelMemberOpts{})
require.Nil(t, err, "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, 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(model.GetPostsOptions{ChannelId: channel.Id, Page: 0, PerPage: 1}, false)
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 TestAppUpdateChannelScheme(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
channel := th.BasicChannel
mockID := model.NewString("x")
channel.SchemeId = mockID
updatedChannel, err := th.App.UpdateChannelScheme(channel)
require.Nil(t, err)
if updatedChannel.SchemeId != mockID {
require.Fail(t, "Wrong Channel SchemeId")
}
}
func TestSetChannelsMuted(t *testing.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.BasicTeam)
th.AddUserToChannel(th.BasicUser, channel2)
// Ensure that both channels start unmuted
member1, err := th.App.GetChannelMember(context.Background(), channel1.Id, th.BasicUser.Id)
require.Nil(t, err)
require.False(t, member1.IsChannelMuted())
member2, err := th.App.GetChannelMember(context.Background(), channel2.Id, th.BasicUser.Id)
require.Nil(t, err)
require.False(t, member2.IsChannelMuted())
// Mute both channels
updated, err := th.App.setChannelsMuted([]string{channel1.Id, channel2.Id}, th.BasicUser.Id, true)
require.Nil(t, err)
assert.True(t, updated[0].IsChannelMuted())
assert.True(t, updated[1].IsChannelMuted())
// Verify that the channels are muted in the database
member1, err = th.App.GetChannelMember(context.Background(), channel1.Id, th.BasicUser.Id)
require.Nil(t, err)
require.True(t, member1.IsChannelMuted())
member2, err = th.App.GetChannelMember(context.Background(), channel2.Id, th.BasicUser.Id)
require.Nil(t, err)
require.True(t, member2.IsChannelMuted())
// Unm both channels
updated, err = th.App.setChannelsMuted([]string{channel1.Id, channel2.Id}, th.BasicUser.Id, false)
require.Nil(t, err)
assert.False(t, updated[0].IsChannelMuted())
assert.False(t, updated[1].IsChannelMuted())
// Verify that the channels are muted in the database
member1, err = th.App.GetChannelMember(context.Background(), channel1.Id, th.BasicUser.Id)
require.Nil(t, err)
require.False(t, member1.IsChannelMuted())
member2, err = th.App.GetChannelMember(context.Background(), channel2.Id, th.BasicUser.Id)
require.Nil(t, err)
require.False(t, member2.IsChannelMuted())
})
}
func TestFillInChannelProps(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
channelPublic1, err := th.App.CreateChannel(th.Context, &model.Channel{DisplayName: "Public 1", Name: "public1", Type: model.ChannelTypeOpen, TeamId: th.BasicTeam.Id}, false)
require.Nil(t, err)
defer th.App.PermanentDeleteChannel(channelPublic1)
channelPublic2, err := th.App.CreateChannel(th.Context, &model.Channel{DisplayName: "Public 2", Name: "public2", Type: model.ChannelTypeOpen, TeamId: th.BasicTeam.Id}, false)
require.Nil(t, err)
defer th.App.PermanentDeleteChannel(channelPublic2)
channelPrivate, err := th.App.CreateChannel(th.Context, &model.Channel{DisplayName: "Private", Name: "private", Type: model.ChannelTypePrivate, TeamId: th.BasicTeam.Id}, false)
require.Nil(t, err)
defer th.App.PermanentDeleteChannel(channelPrivate)
otherTeamId := model.NewId()
otherTeam := &model.Team{
DisplayName: "dn_" + otherTeamId,
Name: "name" + otherTeamId,
Email: "success+" + otherTeamId + "@simulator.amazonses.com",
Type: model.TeamOpen,
}
otherTeam, err = th.App.CreateTeam(th.Context, otherTeam)
require.Nil(t, err)
defer th.App.PermanentDeleteTeam(otherTeam)
channelOtherTeam, err := th.App.CreateChannel(th.Context, &model.Channel{DisplayName: "Other Team Channel", Name: "other-team", Type: model.ChannelTypeOpen, TeamId: otherTeam.Id}, false)
require.Nil(t, err)
defer th.App.PermanentDeleteChannel(channelOtherTeam)
// 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]interface{}
}{
{
"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]interface{}{
"channel_mentions": map[string]interface{}{
"public1": map[string]interface{}{
"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]interface{}{
"channel_mentions": map[string]interface{}{
"other-team": map[string]interface{}{
"display_name": "Other Team Channel",
},
},
},
},
}
for _, testCase := range testCases {
t.Run(testCase.Description, func(t *testing.T) {
err = th.App.FillInChannelProps(testCase.Channel)
require.Nil(t, err)
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]interface{}
}{
{
"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]interface{}{
"test": map[string]interface{}{
"channel_mentions": map[string]interface{}{
"public1": map[string]interface{}{
"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]interface{}{
"test": map[string]interface{}{
"channel_mentions": map[string]interface{}{
"public1": map[string]interface{}{
"display_name": "Public 1",
},
},
},
"test2": map[string]interface{}(nil),
"test3": map[string]interface{}(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]interface{}{
"test": map[string]interface{}{
"channel_mentions": map[string]interface{}{
"public1": map[string]interface{}{
"display_name": "Public 1",
},
},
},
"test2": map[string]interface{}{
"channel_mentions": map[string]interface{}{
"other-team": map[string]interface{}{
"display_name": "Other Team Channel",
},
},
},
"test3": map[string]interface{}(nil),
},
},
}
for _, testCase := range testCases {
t.Run(testCase.Description, func(t *testing.T) {
err = th.App.FillInChannelsProps(testCase.Channels)
require.Nil(t, err)
for _, channel := range testCase.Channels {
assert.Equal(t, testCase.ExpectedChannelProps[channel.Name], channel.Props)
}
})
}
})
}
func TestRenameChannel(t *testing.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.BasicTeam, model.ChannelTypeOpen),
false,
"newchannelname",
"newchannelname",
"New Display Name",
},
{
"Fail on rename open channel with bad name",
th.createChannel(th.BasicTeam, model.ChannelTypeOpen),
true,
"6zii9a9g6pruzj451x3esok54h__wr4j4g8zqtnhmkw771pfpynqwo",
"",
"",
},
{
"Success on rename open channel with consecutive underscores in name",
th.createChannel(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.BasicUser2, th.CreateUser()),
true,
"newchannelname",
"",
"",
},
}
for _, tc := range testCases {
t.Run(tc.Name, func(t *testing.T) {
channel, err := th.App.RenameChannel(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) {
th := Setup(t).InitBasic()
defer th.TearDown()
_, err := th.App.AddChannelMember(th.Context, th.BasicUser2.Id, th.BasicChannel, ChannelMemberOpts{})
require.Nil(t, err, "Failed to add user to channel.")
user := th.BasicUser
user.Timezone["useAutomaticTimezone"] = "false"
user.Timezone["manualTimezone"] = "XOXO/BLABLA"
th.App.UpdateUser(user, false)
user2 := th.BasicUser2
user2.Timezone["automaticTimezone"] = "NoWhere/Island"
th.App.UpdateUser(user2, false)
user3 := 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, &user3)
th.App.AddUserToChannel(ruser, th.BasicChannel, false)
ruser.Timezone["automaticTimezone"] = "NoWhere/Island"
th.App.UpdateUser(ruser, false)
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)
th.App.AddUserToChannel(ruser, th.BasicChannel, false)
timezones, err := th.App.GetChannelMembersTimezones(th.BasicChannel.Id)
require.Nil(t, err, "Failed to get the timezones for a channel.")
assert.Equal(t, 2, len(timezones))
}
func TestGetChannelsForUser(t *testing.T) {
th := Setup(t).InitBasic()
channel := &model.Channel{
DisplayName: "Public",
Name: "public",
Type: model.ChannelTypeOpen,
CreatorId: th.BasicUser.Id,
TeamId: th.BasicTeam.Id,
}
th.App.CreateChannel(th.Context, channel, true)
defer th.App.PermanentDeleteChannel(channel)
defer th.TearDown()
channelList, err := th.App.GetChannelsForUser(th.BasicTeam.Id, th.BasicUser.Id, false, 0)
require.Nil(t, err)
require.Len(t, channelList, 4)
th.App.DeleteChannel(th.Context, channel, th.BasicUser.Id)
// Now we get all the non-archived channels for the user
channelList, err = th.App.GetChannelsForUser(th.BasicTeam.Id, th.BasicUser.Id, false, 0)
require.Nil(t, err)
require.Len(t, channelList, 3)
// Now we get all the channels, even though are archived, for the user
channelList, err = th.App.GetChannelsForUser(th.BasicTeam.Id, th.BasicUser.Id, true, 0)
require.Nil(t, err)
require.Len(t, channelList, 4)
}
func TestGetPublicChannelsForTeam(t *testing.T) {
th := Setup(t)
team := th.CreateTeam()
defer th.TearDown()
var expectedChannels []*model.Channel
townSquare, err := th.App.GetChannelByName("town-square", team.Id, false)
require.Nil(t, err)
require.NotNil(t, townSquare)
expectedChannels = append(expectedChannels, townSquare)
offTopic, err := th.App.GetChannelByName("off-topic", team.Id, false)
require.Nil(t, err)
require.NotNil(t, offTopic)
expectedChannels = append(expectedChannels, offTopic)
for i := 0; i < 8; i++ {
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, err = th.App.CreateChannel(th.Context, &channel, false)
require.Nil(t, err)
require.NotNil(t, rchannel)
defer th.App.PermanentDeleteChannel(rchannel)
// Store the user ids for comparison later
expectedChannels = append(expectedChannels, rchannel)
}
// Fetch public channels multipile times
channelList, err := th.App.GetPublicChannelsForTeam(team.Id, 0, 5)
require.Nil(t, err)
channelList2, err := th.App.GetPublicChannelsForTeam(team.Id, 5, 5)
require.Nil(t, err)
channels := append(channelList, channelList2...)
assert.ElementsMatch(t, expectedChannels, channels)
}
func TestGetPrivateChannelsForTeam(t *testing.T) {
th := Setup(t)
team := th.CreateTeam()
defer th.TearDown()
var expectedChannels []*model.Channel
for i := 0; i < 8; i++ {
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, err := th.App.CreateChannel(th.Context, &channel, false)
require.Nil(t, err)
require.NotNil(t, rchannel)
defer th.App.PermanentDeleteChannel(rchannel)
// Store the user ids for comparison later
expectedChannels = append(expectedChannels, rchannel)
}
// Fetch private channels multipile times
channelList, err := th.App.GetPrivateChannelsForTeam(team.Id, 0, 5)
require.Nil(t, err)
channelList2, err := th.App.GetPrivateChannelsForTeam(team.Id, 5, 5)
require.Nil(t, err)
channels := append(channelList, channelList2...)
assert.ElementsMatch(t, expectedChannels, channels)
}
func TestUpdateChannelMemberRolesChangingGuest(t *testing.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)
_, _, err := th.App.AddUserToTeam(th.Context, th.BasicTeam.Id, ruser.Id, "")
require.Nil(t, err)
_, err = th.App.AddUserToChannel(ruser, th.BasicChannel, false)
require.Nil(t, err)
_, err = th.App.UpdateChannelMemberRoles(th.BasicChannel.Id, ruser.Id, "channel_user")
require.NotNil(t, err, "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)
_, _, err := th.App.AddUserToTeam(th.Context, th.BasicTeam.Id, ruser.Id, "")
require.Nil(t, err)
_, err = th.App.AddUserToChannel(ruser, th.BasicChannel, false)
require.Nil(t, err)
_, err = th.App.UpdateChannelMemberRoles(th.BasicChannel.Id, ruser.Id, "channel_guest")
require.NotNil(t, err, "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)
_, _, err := th.App.AddUserToTeam(th.Context, th.BasicTeam.Id, ruser.Id, "")
require.Nil(t, err)
_, err = th.App.AddUserToChannel(ruser, th.BasicChannel, false)
require.Nil(t, err)
_, err = th.App.UpdateChannelMemberRoles(th.BasicChannel.Id, ruser.Id, "channel_user channel_admin")
require.Nil(t, err, "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)
_, _, err := th.App.AddUserToTeam(th.Context, th.BasicTeam.Id, ruser.Id, "")
require.Nil(t, err)
_, err = th.App.AddUserToChannel(ruser, th.BasicChannel, false)
require.Nil(t, err)
_, err = th.App.CreateRole(&model.Role{Name: "custom", DisplayName: "custom", Description: "custom"})
require.Nil(t, err)
_, err = th.App.UpdateChannelMemberRoles(th.BasicChannel.Id, ruser.Id, "channel_guest custom")
require.Nil(t, err, "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)
_, _, err := th.App.AddUserToTeam(th.Context, th.BasicTeam.Id, ruser.Id, "")
require.Nil(t, err)
_, err = th.App.AddUserToChannel(ruser, th.BasicChannel, false)
require.Nil(t, err)
_, err = th.App.UpdateChannelMemberRoles(th.BasicChannel.Id, ruser.Id, "channel_guest channel_user")
require.NotNil(t, err, "Should work when you not modify guest role")
})
}
func TestDefaultChannelNames(t *testing.T) {
th := Setup(t)
defer th.TearDown()
actual := th.App.DefaultChannelNames()
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()
expect = []string{"town-square", "foo", "bar"}
require.ElementsMatch(t, expect, actual)
}
func TestSearchChannelsForUser(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
c1, err := 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, err)
c2, err := 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, err)
c3, err := th.App.CreateChannel(th.Context, &model.Channel{DisplayName: "dev-3", Name: "dev-3", Type: model.ChannelTypeOpen, TeamId: th.BasicTeam.Id}, false)
require.Nil(t, err)
defer func() {
th.App.PermanentDeleteChannel(c1)
th.App.PermanentDeleteChannel(c2)
th.App.PermanentDeleteChannel(c3)
}()
// add user to test-dev-1 and dev3
_, err = th.App.AddUserToChannel(th.BasicUser, c1, false)
require.Nil(t, err)
_, err = th.App.AddUserToChannel(th.BasicUser, c3, false)
require.Nil(t, err)
searchAndCheck := func(t *testing.T, term string, expectedDisplayNames []string) {
res, searchErr := th.App.SearchChannelsForUser(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) {
_, err = th.App.AddUserToChannel(th.BasicUser, c2, false)
require.Nil(t, err)
searchAndCheck(t, "dev", []string{"test-dev-1", "test-dev-2", "dev-3"})
})
}
func TestMarkChannelAsUnreadFromPost(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
u1 := th.BasicUser
u2 := th.BasicUser2
c1 := th.BasicChannel
pc1 := th.CreatePrivateChannel(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, err := th.App.GetChannelUnread(c1.Id, u1.Id)
require.Nil(t, err)
require.Equal(t, int64(4), unread.MsgCount)
unread, err = th.App.GetChannelUnread(c1.Id, u2.Id)
require.Nil(t, err)
require.Equal(t, int64(4), unread.MsgCount)
err = th.App.UpdateChannelLastViewedAt([]string{c1.Id, pc1.Id}, u1.Id)
require.Nil(t, err)
err = th.App.UpdateChannelLastViewedAt([]string{c1.Id, pc1.Id}, u2.Id)
require.Nil(t, err)
unread, err = th.App.GetChannelUnread(c1.Id, u2.Id)
require.Nil(t, err)
require.Equal(t, int64(0), unread.MsgCount)
t.Run("Unread but last one", func(t *testing.T) {
response, err := th.App.MarkChannelAsUnreadFromPost(p2.Id, u1.Id, true, true)
require.Nil(t, err)
require.NotNil(t, response)
assert.Equal(t, int64(2), response.MsgCount)
unread, err := th.App.GetChannelUnread(c1.Id, u1.Id)
require.Nil(t, err)
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, err := th.App.MarkChannelAsUnreadFromPost(p3.Id, u1.Id, true, true)
require.Nil(t, err)
require.NotNil(t, response)
assert.Equal(t, int64(3), response.MsgCount)
unread, err := th.App.GetChannelUnread(c1.Id, u1.Id)
require.Nil(t, err)
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, err := th.App.MarkChannelAsUnreadFromPost(p1.Id, u1.Id, true, true)
require.Nil(t, err)
require.NotNil(t, response)
assert.Equal(t, int64(1), response.MsgCount)
unread, err := th.App.GetChannelUnread(c1.Id, u1.Id)
require.Nil(t, err)
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, err := th.App.GetChannelUnread(c1.Id, u2.Id)
require.Nil(t, err)
assert.Equal(t, int64(0), unread.MsgCount)
})
t.Run("Unread on a private channel", func(t *testing.T) {
response, err := th.App.MarkChannelAsUnreadFromPost(pp1.Id, u1.Id, true, true)
require.Nil(t, err)
require.NotNil(t, response)
assert.Equal(t, int64(0), response.MsgCount)
unread, err := th.App.GetChannelUnread(pc1.Id, u1.Id)
require.Nil(t, err)
assert.Equal(t, int64(2), unread.MsgCount)
assert.Equal(t, pp1.CreateAt-1, response.LastViewedAt)
response, err = th.App.MarkChannelAsUnreadFromPost(pp2.Id, u1.Id, true, true)
assert.Nil(t, err)
assert.Equal(t, int64(1), response.MsgCount)
unread, err = th.App.GetChannelUnread(pc1.Id, u1.Id)
require.Nil(t, err)
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.BasicTeam)
_, err := th.App.AddUserToChannel(u2, c2, false)
require.Nil(t, err)
p4, err := th.App.CreatePost(th.Context, &model.Post{
UserId: u2.Id,
ChannelId: c2.Id,
Message: "@" + u1.Username,
}, c2, false, true)
require.Nil(t, err)
th.CreatePost(c2)
th.App.CreatePost(th.Context, &model.Post{
UserId: u2.Id,
ChannelId: c2.Id,
RootId: p4.Id,
Message: "@" + u1.Username,
}, c2, false, true)
response, err := th.App.MarkChannelAsUnreadFromPost(p4.Id, u1.Id, true, true)
assert.Nil(t, err)
assert.Equal(t, int64(1), response.MsgCount)
assert.Equal(t, int64(2), response.MentionCount)
assert.Equal(t, int64(1), response.MentionCountRoot)
unread, err := th.App.GetChannelUnread(c2.Id, u1.Id)
require.Nil(t, err)
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)
_, err := th.App.CreatePost(th.Context, &model.Post{ChannelId: dc.Id, UserId: th.BasicUser.Id, Message: "testReply", RootId: dm1.Id}, dc, false, false)
assert.Nil(t, err)
response, err := th.App.MarkChannelAsUnreadFromPost(dm1.Id, u2.Id, true, true)
assert.Nil(t, err)
assert.Equal(t, int64(0), response.MsgCount)
assert.Equal(t, int64(4), response.MentionCount)
assert.Equal(t, int64(3), response.MentionCountRoot)
unread, err := th.App.GetChannelUnread(dc.Id, u2.Id)
require.Nil(t, err)
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("invalid4ofngungryquinj976y", u1.Id, true, true)
assert.NotNil(t, err)
assert.Nil(t, response)
})
}
func TestAddUserToChannel(t *testing.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 th.App.PermanentDeleteUser(th.Context, &user1)
bot := th.CreateBot()
botUser, _ := th.App.GetUser(bot.UserId)
defer th.App.PermanentDeleteBot(botUser.Id)
th.App.AddTeamMember(th.Context, th.BasicTeam.Id, ruser1.Id)
th.App.AddTeamMember(th.Context, th.BasicTeam.Id, bot.UserId)
group := th.CreateGroup()
_, err := th.App.UpsertGroupMember(group.Id, user1.Id)
require.Nil(t, err)
gs, err := th.App.UpsertGroupSyncable(&model.GroupSyncable{
AutoAdd: true,
SyncableId: th.BasicChannel.Id,
Type: model.GroupSyncableTypeChannel,
GroupId: group.Id,
SchemeAdmin: false,
})
require.Nil(t, err)
err = th.App.JoinChannel(th.Context, th.BasicChannel, ruser1.Id)
require.Nil(t, err)
// verify user was added as a non-admin
cm1, err := th.App.GetChannelMember(context.Background(), th.BasicChannel.Id, ruser1.Id)
require.Nil(t, err)
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 th.App.PermanentDeleteUser(th.Context, &user2)
th.App.AddTeamMember(th.Context, th.BasicTeam.Id, ruser2.Id)
_, err = th.App.UpsertGroupMember(group.Id, user2.Id)
require.Nil(t, err)
gs.SchemeAdmin = true
_, err = th.App.UpdateGroupSyncable(gs)
require.Nil(t, err)
err = th.App.JoinChannel(th.Context, th.BasicChannel, ruser2.Id)
require.Nil(t, err)
// Should allow a bot to be added to a public group synced channel
_, err = th.App.AddUserToChannel(botUser, th.BasicChannel, false)
require.Nil(t, err)
// verify user was added as an admin
cm2, err := th.App.GetChannelMember(context.Background(), th.BasicChannel.Id, ruser2.Id)
require.Nil(t, err)
require.True(t, cm2.SchemeAdmin)
privateChannel := th.CreatePrivateChannel(th.BasicTeam)
privateChannel.GroupConstrained = model.NewBool(true)
_, err = th.App.UpdateChannel(privateChannel)
require.Nil(t, err)
_, err = th.App.UpsertGroupSyncable(&model.GroupSyncable{
GroupId: group.Id,
SyncableId: privateChannel.Id,
Type: model.GroupSyncableTypeChannel,
})
require.Nil(t, err)
// Should allow a group synced user to be added to a group synced private channel
_, err = th.App.AddUserToChannel(ruser1, privateChannel, false)
require.Nil(t, err)
// Should allow a bot to be added to a private group synced channel
_, err = th.App.AddUserToChannel(botUser, privateChannel, false)
require.Nil(t, err)
}
func TestRemoveUserFromChannel(t *testing.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 th.App.PermanentDeleteUser(th.Context, ruser)
bot := th.CreateBot()
botUser, _ := th.App.GetUser(bot.UserId)
defer th.App.PermanentDeleteBot(botUser.Id)
th.App.AddTeamMember(th.Context, th.BasicTeam.Id, ruser.Id)
th.App.AddTeamMember(th.Context, th.BasicTeam.Id, bot.UserId)
privateChannel := th.CreatePrivateChannel(th.BasicTeam)
_, err := th.App.AddUserToChannel(ruser, privateChannel, false)
require.Nil(t, err)
_, err = th.App.AddUserToChannel(botUser, privateChannel, false)
require.Nil(t, err)
group := th.CreateGroup()
_, err = th.App.UpsertGroupMember(group.Id, ruser.Id)
require.Nil(t, err)
_, err = th.App.UpsertGroupSyncable(&model.GroupSyncable{
GroupId: group.Id,
SyncableId: privateChannel.Id,
Type: model.GroupSyncableTypeChannel,
})
require.Nil(t, err)
privateChannel.GroupConstrained = model.NewBool(true)
_, err = th.App.UpdateChannel(privateChannel)
require.Nil(t, err)
// Should not allow a group synced user to be removed from channel
err = th.App.RemoveUserFromChannel(th.Context, ruser.Id, th.SystemAdminUser.Id, privateChannel)
assert.Equal(t, err.Id, "api.channel.remove_members.denied")
// Should allow a user to remove themselves from group synced channel
err = th.App.RemoveUserFromChannel(th.Context, ruser.Id, ruser.Id, privateChannel)
require.Nil(t, err)
// Should allow a bot to be removed from a group synced channel
err = th.App.RemoveUserFromChannel(th.Context, botUser.Id, th.SystemAdminUser.Id, privateChannel)
require.Nil(t, err)
}
func TestPatchChannelModerationsForChannel(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
th.App.SetPhase2PermissionsMigrationStatus(true)
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]
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.NewBool(false)},
},
},
PermissionsModeratedByPatch: map[string]*model.ChannelModeratedRoles{
createPosts: {
Members: &model.ChannelModeratedRole{Value: false, Enabled: true},
},
},
RevertChannelModerationsPatch: []*model.ChannelModerationPatch{
{
Name: &createPosts,
Roles: &model.ChannelModeratedRolesPatch{Members: model.NewBool(true)},
},
},
},
{
Name: "Removing create reactions from members role",
ChannelModerationsPatch: []*model.ChannelModerationPatch{
{
Name: &createReactions,
Roles: &model.ChannelModeratedRolesPatch{Members: model.NewBool(false)},
},
},
PermissionsModeratedByPatch: map[string]*model.ChannelModeratedRoles{
createReactions: {
Members: &model.ChannelModeratedRole{Value: false, Enabled: true},
},
},
RevertChannelModerationsPatch: []*model.ChannelModerationPatch{
{
Name: &createReactions,
Roles: &model.ChannelModeratedRolesPatch{Members: model.NewBool(true)},
},
},
},
{
Name: "Removing channel mentions from members role",
ChannelModerationsPatch: []*model.ChannelModerationPatch{
{
Name: &channelMentions,
Roles: &model.ChannelModeratedRolesPatch{Members: model.NewBool(false)},
},
},
PermissionsModeratedByPatch: map[string]*model.ChannelModeratedRoles{
channelMentions: {
Members: &model.ChannelModeratedRole{Value: false, Enabled: true},
},
},
RevertChannelModerationsPatch: []*model.ChannelModerationPatch{
{
Name: &channelMentions,
Roles: &model.ChannelModeratedRolesPatch{Members: model.NewBool(true)},
},
},
},
{
Name: "Removing manage members from members role",
ChannelModerationsPatch: []*model.ChannelModerationPatch{
{
Name: &manageMembers,
Roles: &model.ChannelModeratedRolesPatch{Members: model.NewBool(false)},
},
},
PermissionsModeratedByPatch: map[string]*model.ChannelModeratedRoles{
manageMembers: {
Members: &model.ChannelModeratedRole{Value: false, Enabled: true},
},
},
RevertChannelModerationsPatch: []*model.ChannelModerationPatch{
{
Name: &manageMembers,
Roles: &model.ChannelModeratedRolesPatch{Members: model.NewBool(true)},
},
},
},
{
Name: "Removing create posts from guests role",
ChannelModerationsPatch: []*model.ChannelModerationPatch{
{
Name: &createPosts,
Roles: &model.ChannelModeratedRolesPatch{Guests: model.NewBool(false)},
},
},
PermissionsModeratedByPatch: map[string]*model.ChannelModeratedRoles{
createPosts: {
Guests: &model.ChannelModeratedRole{Value: false, Enabled: true},
},
},
RevertChannelModerationsPatch: []*model.ChannelModerationPatch{
{
Name: &createPosts,
Roles: &model.ChannelModeratedRolesPatch{Guests: model.NewBool(true)},
},
},
},
{
Name: "Removing create reactions from guests role",
ChannelModerationsPatch: []*model.ChannelModerationPatch{
{
Name: &createReactions,
Roles: &model.ChannelModeratedRolesPatch{Guests: model.NewBool(false)},
},
},
PermissionsModeratedByPatch: map[string]*model.ChannelModeratedRoles{
createReactions: {
Guests: &model.ChannelModeratedRole{Value: false, Enabled: true},
},
},
RevertChannelModerationsPatch: []*model.ChannelModerationPatch{
{
Name: &createReactions,
Roles: &model.ChannelModeratedRolesPatch{Guests: model.NewBool(true)},
},
},
},
{
Name: "Removing channel mentions from guests role",
ChannelModerationsPatch: []*model.ChannelModerationPatch{
{
Name: &channelMentions,
Roles: &model.ChannelModeratedRolesPatch{Guests: model.NewBool(false)},
},
},
PermissionsModeratedByPatch: map[string]*model.ChannelModeratedRoles{
channelMentions: {
Guests: &model.ChannelModeratedRole{Value: false, Enabled: true},
},
},
RevertChannelModerationsPatch: []*model.ChannelModerationPatch{
{
Name: &channelMentions,
Roles: &model.ChannelModeratedRolesPatch{Guests: model.NewBool(true)},
},
},
},
{
Name: "Removing manage members from guests role should not error",
ChannelModerationsPatch: []*model.ChannelModerationPatch{
{
Name: &manageMembers,
Roles: &model.ChannelModeratedRolesPatch{Guests: model.NewBool(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.NewBool(false),
Guests: model.NewBool(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.NewBool(true),
Guests: model.NewBool(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.NewBool(false),
Guests: model.NewBool(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.NewBool(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.NewBool(true),
Guests: model.NewBool(true),
},
},
{
Name: &createReactions,
Roles: &model.ChannelModeratedRolesPatch{
Members: model.NewBool(true),
Guests: model.NewBool(true),
},
},
{
Name: &channelMentions,
Roles: &model.ChannelModeratedRolesPatch{
Members: model.NewBool(true),
Guests: model.NewBool(true),
},
},
{
Name: &manageMembers,
Roles: &model.ChannelModeratedRolesPatch{
Members: model.NewBool(true),
},
},
},
PermissionsModeratedByPatch: map[string]*model.ChannelModeratedRoles{},
ShouldHaveNoChannelScheme: true,
},
}
for _, tc := range testCases {
t.Run(tc.Name, func(t *testing.T) {
higherScopedPermissionsOverriden := tc.HigherScopedMemberPermissions != nil || tc.HigherScopedGuestPermissions != nil
// If the test case restricts higher scoped permissions.
if higherScopedPermissionsOverriden {
higherScopedGuestRoleName, higherScopedMemberRoleName, _, _ := th.App.GetTeamSchemeChannelRoles(channel.TeamId)
if tc.HigherScopedMemberPermissions != nil {
higherScopedMemberRole, err := th.App.GetRoleByName(context.Background(), higherScopedMemberRoleName)
require.Nil(t, err)
originalPermissions := higherScopedMemberRole.Permissions
th.App.PatchRole(higherScopedMemberRole, &model.RolePatch{Permissions: &tc.HigherScopedMemberPermissions})
defer th.App.PatchRole(higherScopedMemberRole, &model.RolePatch{Permissions: &originalPermissions})
}
if tc.HigherScopedGuestPermissions != nil {
higherScopedGuestRole, err := th.App.GetRoleByName(context.Background(), higherScopedGuestRoleName)
require.Nil(t, err)
originalPermissions := higherScopedGuestRole.Permissions
th.App.PatchRole(higherScopedGuestRole, &model.RolePatch{Permissions: &tc.HigherScopedGuestPermissions})
defer th.App.PatchRole(higherScopedGuestRole, &model.RolePatch{Permissions: &originalPermissions})
}
}
moderations, appErr := th.App.PatchChannelModerationsForChannel(channel, tc.ChannelModerationsPatch)
if tc.ShouldError {
require.NotNil(t, appErr)
return
}
require.Nil(t, appErr)
updatedChannel, _ := th.App.GetChannel(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 {
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 {
th.App.PatchChannelModerationsForChannel(channel, tc.RevertChannelModerationsPatch)
}
})
}
t.Run("Handles concurrent patch requests gracefully", func(t *testing.T) {
addCreatePosts := []*model.ChannelModerationPatch{
{
Name: &createPosts,
Roles: &model.ChannelModeratedRolesPatch{
Members: model.NewBool(false),
Guests: model.NewBool(false),
},
},
}
removeCreatePosts := []*model.ChannelModerationPatch{
{
Name: &createPosts,
Roles: &model.ChannelModeratedRolesPatch{
Members: model.NewBool(false),
Guests: model.NewBool(false),
},
},
}
wg := sync.WaitGroup{}
wg.Add(20)
for i := 0; i < 10; i++ {
go func() {
th.App.PatchChannelModerationsForChannel(channel.DeepCopy(), addCreatePosts)
th.App.PatchChannelModerationsForChannel(channel.DeepCopy(), removeCreatePosts)
wg.Done()
}()
}
for i := 0; i < 10; i++ {
go func() {
th.App.PatchChannelModerationsForChannel(channel.DeepCopy(), addCreatePosts)
th.App.PatchChannelModerationsForChannel(channel.DeepCopy(), removeCreatePosts)
wg.Done()
}()
}
wg.Wait()
higherScopedGuestRoleName, higherScopedMemberRoleName, _, _ := th.App.GetTeamSchemeChannelRoles(channel.TeamId)
higherScopedMemberRole, _ := th.App.GetRoleByName(context.Background(), higherScopedMemberRoleName)
higherScopedGuestRole, _ := th.App.GetRoleByName(context.Background(), 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.NewBool(true),
},
},
}
removeCreatePosts := []*model.ChannelModerationPatch{
{
Name: &createPosts,
Roles: &model.ChannelModeratedRolesPatch{
Members: model.NewBool(false),
},
},
}
mockSession := model.Session{UserId: user.Id}
_, err := th.App.PatchChannelModerationsForChannel(channel.DeepCopy(), addCreatePosts)
require.Nil(t, err)
require.True(t, th.App.SessionHasPermissionToChannel(mockSession, channel.Id, model.PermissionCreatePost))
_, err = th.App.PatchChannelModerationsForChannel(channel.DeepCopy(), removeCreatePosts)
require.Nil(t, err)
require.False(t, th.App.SessionHasPermissionToChannel(mockSession, channel.Id, model.PermissionCreatePost))
})
}
// TestMarkChannelsAsViewedPanic verifies that returning an error from a.GetUser
// does not cause a panic.
func TestMarkChannelsAsViewedPanic(t *testing.T) {
th := SetupWithStoreMock(t)
defer th.TearDown()
mockStore := th.App.Srv().Store.(*mocks.Store)
mockUserStore := mocks.UserStore{}
mockUserStore.On("Get", context.Background(), "userID").Return(nil, model.NewAppError("SqlUserStore.Get", "app.user.get.app_error", nil, "user_id=userID", http.StatusInternalServerError))
mockChannelStore := mocks.ChannelStore{}
mockChannelStore.On("Get", "channelID", true).Return(&model.Channel{}, nil)
mockChannelStore.On("GetMember", context.Background(), "channelID", "userID").Return(&model.ChannelMember{
NotifyProps: model.StringMap{
model.PushNotifyProp: model.ChannelNotifyDefault,
}}, nil)
times := map[string]int64{
"userID": 1,
}
mockChannelStore.On("UpdateLastViewedAt", []string{"channelID"}, "userID", false).Return(times, nil)
mockSessionStore := mocks.SessionStore{}
mockOAuthStore := mocks.OAuthStore{}
var err error
th.App.ch.srv.userService, err = users.New(users.ServiceConfig{
UserStore: &mockUserStore,
SessionStore: &mockSessionStore,
OAuthStore: &mockOAuthStore,
ConfigFn: th.App.ch.srv.Config,
LicenseFn: th.App.ch.srv.License,
})
require.NoError(t, err)
mockPreferenceStore := mocks.PreferenceStore{}
mockPreferenceStore.On("Get", mock.AnythingOfType("string"), mock.AnythingOfType("string"), mock.AnythingOfType("string")).Return(&model.Preference{Value: "test"}, nil)
mockThreadStore := mocks.ThreadStore{}
mockThreadStore.On("MarkAllAsReadInChannels", "userID", []string{"channelID"}).Return(nil)
mockStore.On("Channel").Return(&mockChannelStore)
mockStore.On("Preference").Return(&mockPreferenceStore)
mockStore.On("Thread").Return(&mockThreadStore)
_, appErr := th.App.MarkChannelsAsViewed([]string{"channelID"}, "userID", th.Context.Session().Id, false)
require.Nil(t, appErr)
}
func TestClearChannelMembersCache(t *testing.T) {
th := SetupWithStoreMock(t)
defer th.TearDown()
mockStore := th.App.Srv().Store.(*mocks.Store)
mockChannelStore := mocks.ChannelStore{}
cms := model.ChannelMembers{}
for i := 0; i < 200; i++ {
cms = append(cms, model.ChannelMember{
ChannelId: "1",
})
}
mockChannelStore.On("GetMembers", "channelID", 0, 100).Return(cms, nil)
mockChannelStore.On("GetMembers", "channelID", 100, 100).Return(model.ChannelMembers{
model.ChannelMember{
ChannelId: "1",
}}, nil)
mockStore.On("Channel").Return(&mockChannelStore)
th.App.ClearChannelMembersCache("channelID")
}
func TestGetMemberCountsByGroup(t *testing.T) {
th := SetupWithStoreMock(t)
defer th.TearDown()
mockStore := th.App.Srv().Store.(*mocks.Store)
mockChannelStore := mocks.ChannelStore{}
cmc := []*model.ChannelMemberCountByGroup{}
for i := 0; i < 5; i++ {
cmc = append(cmc, &model.ChannelMemberCountByGroup{
GroupId: model.NewId(),
ChannelMemberCount: int64(i),
ChannelMemberTimezonesCount: int64(i),
})
}
mockChannelStore.On("GetMemberCountsByGroup", context.Background(), "channelID", true).Return(cmc, nil)
mockStore.On("Channel").Return(&mockChannelStore)
resp, err := th.App.GetMemberCountsByGroup(context.Background(), "channelID", true)
require.Nil(t, err)
require.ElementsMatch(t, cmc, resp)
}
func TestViewChannelCollapsedThreadsTurnedOff(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
u1 := th.BasicUser
u2 := th.BasicUser2
c1 := th.BasicChannel
th.AddUserToChannel(u2, c1)
// Enable CRT
os.Setenv("MM_FEATUREFLAGS_COLLAPSEDTHREADS", "true")
defer os.Unsetenv("MM_FEATUREFLAGS_COLLAPSEDTHREADS")
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, false, 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, false, true)
require.Nil(t, appErr)
// Check we have unread mention in the thread
threads, appErr := th.App.GetThreadsForUser(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([]string{c1.Id}, u1.Id, th.Context.Session().Id, true)
require.Nil(t, appErr)
// Thread should be marked as read because CRT has been turned off by user
threads, appErr = th.App.GetThreadsForUser(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) {
// Enable CRT
os.Setenv("MM_FEATUREFLAGS_COLLAPSEDTHREADS", "true")
defer os.Unsetenv("MM_FEATUREFLAGS_COLLAPSEDTHREADS")
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 repoy
// - 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, false, false)
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, false, false)
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, false, false)
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, false, false)
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, false, false)
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, false, false)
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, false, false)
require.Nil(t, appErr)
t.Run("Mark reply post as unread", func(t *testing.T) {
_, err := th.App.MarkChannelAsUnreadFromPost(replyPost1.Id, th.BasicUser.Id, true, true)
require.Nil(t, err)
// Get channel unreads
// Easier to reason with ChannelUnread now, than channelUnreadAt from the previous call
channelUnread, err := th.App.GetChannelUnread(th.BasicChannel.Id, th.BasicUser.Id)
require.Nil(t, err)
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, err := th.App.GetThreadMembershipForUser(th.BasicUser.Id, rootPost1.Id)
require.Nil(t, err)
thread, err := th.App.GetThreadForUser(th.BasicTeam.Id, threadMembership, false)
require.Nil(t, err)
require.Equal(t, int64(2), thread.UnreadMentions)
require.Equal(t, int64(3), thread.UnreadReplies)
})
t.Run("Mark root post as unread", func(t *testing.T) {
_, err := th.App.MarkChannelAsUnreadFromPost(rootPost1.Id, th.BasicUser.Id, true, true)
require.Nil(t, err)
// Get channel unreads
// Easier to reason with ChannelUnread now, than channelUnreadAt from the previous call
channelUnread, err := th.App.GetChannelUnread(th.BasicChannel.Id, th.BasicUser.Id)
require.Nil(t, err)
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 TestMarkUnreadWithThreads(t *testing.T) {
os.Setenv("MM_FEATUREFLAGS_COLLAPSEDTHREADS", "true")
defer os.Unsetenv("MM_FEATUREFLAGS_COLLAPSEDTHREADS")
th := Setup(t).InitBasic()
defer th.TearDown()
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.ServiceSettings.ThreadAutoFollow = true
*cfg.ServiceSettings.CollapsedThreads = model.CollapsedThreadsDefaultOn
})
t.Run("Follow threads only if specified", func(t *testing.T) {
rootPost, appErr := th.App.CreatePost(th.Context, &model.Post{UserId: th.BasicUser2.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "hi"}, th.BasicChannel, false, false)
require.Nil(t, appErr)
replyPost, appErr := th.App.CreatePost(th.Context, &model.Post{RootId: rootPost.Id, UserId: th.BasicUser2.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "hi"}, th.BasicChannel, false, false)
require.Nil(t, appErr)
threads, appErr := th.App.GetThreadsForUser(th.BasicUser.Id, th.BasicTeam.Id, model.GetUserThreadsOpts{})
require.Nil(t, appErr)
require.Zero(t, threads.Total)
_, appErr = th.App.MarkChannelAsUnreadFromPost(replyPost.Id, th.BasicUser.Id, true, true)
require.Nil(t, appErr)
threads, appErr = th.App.GetThreadsForUser(th.BasicUser.Id, th.BasicTeam.Id, model.GetUserThreadsOpts{})
require.Nil(t, appErr)
require.NotZero(t, threads.Total)
threadMembership, appErr := th.App.GetThreadMembershipForUser(th.BasicUser.Id, replyPost.RootId)
require.Nil(t, appErr)
require.NotNil(t, threadMembership)
assert.True(t, threadMembership.Following)
// Create a new thread
rootPost, appErr = th.App.CreatePost(th.Context, &model.Post{UserId: th.BasicUser2.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "hi2"}, th.BasicChannel, false, false)
require.Nil(t, appErr)
replyPost, appErr = th.App.CreatePost(th.Context, &model.Post{RootId: rootPost.Id, UserId: th.BasicUser2.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "hi2"}, th.BasicChannel, false, false)
require.Nil(t, appErr)
_, appErr = th.App.MarkChannelAsUnreadFromPost(replyPost.Id, th.BasicUser.Id, true, false)
require.Nil(t, appErr)
threadMembership, appErr = th.App.GetThreadMembershipForUser(th.BasicUser.Id, replyPost.RootId)
require.Nil(t, appErr)
require.NotNil(t, threadMembership)
assert.False(t, threadMembership.Following)
})
t.Run("Set unread mentions correctly", func(t *testing.T) {
t.Run("Never followed root post with no replies or mentions", func(t *testing.T) {
rootPost, appErr := th.App.CreatePost(th.Context, &model.Post{UserId: th.BasicUser2.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "hi"}, th.BasicChannel, false, false)
require.Nil(t, appErr)
_, appErr = th.App.MarkChannelAsUnreadFromPost(rootPost.Id, th.BasicUser.Id, true, true)
require.Nil(t, appErr)
threadMembership, appErr := th.App.GetThreadMembershipForUser(th.BasicUser.Id, rootPost.Id)
require.Nil(t, appErr)
require.NotNil(t, threadMembership)
assert.Zero(t, threadMembership.UnreadMentions)
})
t.Run("Never followed root post with replies and no mentions", func(t *testing.T) {
rootPost, appErr := th.App.CreatePost(th.Context, &model.Post{UserId: th.BasicUser2.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "hi"}, th.BasicChannel, false, false)
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: "hi"}, th.BasicChannel, false, false)
require.Nil(t, appErr)
_, appErr = th.App.MarkChannelAsUnreadFromPost(rootPost.Id, th.BasicUser.Id, true, true)
require.Nil(t, appErr)
threadMembership, appErr := th.App.GetThreadMembershipForUser(th.BasicUser.Id, rootPost.Id)
require.Nil(t, appErr)
require.NotNil(t, threadMembership)
assert.Zero(t, threadMembership.UnreadMentions)
})
t.Run("Never followed root post with replies and mentions", func(t *testing.T) {
rootPost, appErr := th.App.CreatePost(th.Context, &model.Post{UserId: th.BasicUser2.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "hi"}, th.BasicChannel, false, false)
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: "hi @" + th.BasicUser.Username}, th.BasicChannel, false, false)
require.Nil(t, appErr)
_, appErr = th.App.MarkChannelAsUnreadFromPost(rootPost.Id, th.BasicUser.Id, true, true)
require.Nil(t, appErr)
threadMembership, appErr := th.App.GetThreadMembershipForUser(th.BasicUser.Id, rootPost.Id)
require.Nil(t, appErr)
require.NotNil(t, threadMembership)
assert.Equal(t, int64(1), threadMembership.UnreadMentions)
})
t.Run("Previously followed root post with no replies or mentions", func(t *testing.T) {
rootPost, appErr := th.App.CreatePost(th.Context, &model.Post{UserId: th.BasicUser2.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "hi"}, th.BasicChannel, false, false)
require.Nil(t, appErr)
appErr = th.App.UpdateThreadFollowForUser(th.BasicUser.Id, th.BasicTeam.Id, rootPost.Id, true)
require.Nil(t, appErr)
appErr = th.App.UpdateThreadFollowForUser(th.BasicUser.Id, th.BasicTeam.Id, rootPost.Id, false)
require.Nil(t, appErr)
_, appErr = th.App.MarkChannelAsUnreadFromPost(rootPost.Id, th.BasicUser.Id, true, true)
require.Nil(t, appErr)
threadMembership, appErr := th.App.GetThreadMembershipForUser(th.BasicUser.Id, rootPost.Id)
require.Nil(t, appErr)
require.NotNil(t, threadMembership)
assert.Zero(t, threadMembership.UnreadMentions)
})
t.Run("Previously followed root post with replies and no mentions", func(t *testing.T) {
rootPost, appErr := th.App.CreatePost(th.Context, &model.Post{UserId: th.BasicUser2.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "hi"}, th.BasicChannel, false, false)
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: "hi"}, th.BasicChannel, false, false)
require.Nil(t, appErr)
appErr = th.App.UpdateThreadFollowForUser(th.BasicUser.Id, th.BasicTeam.Id, rootPost.Id, true)
require.Nil(t, appErr)
appErr = th.App.UpdateThreadFollowForUser(th.BasicUser.Id, th.BasicTeam.Id, rootPost.Id, false)
require.Nil(t, appErr)
_, appErr = th.App.MarkChannelAsUnreadFromPost(rootPost.Id, th.BasicUser.Id, true, true)
require.Nil(t, appErr)
threadMembership, appErr := th.App.GetThreadMembershipForUser(th.BasicUser.Id, rootPost.Id)
require.Nil(t, appErr)
require.NotNil(t, threadMembership)
assert.Zero(t, threadMembership.UnreadMentions)
})
t.Run("Previously followed root post with replies and mentions", func(t *testing.T) {
rootPost, appErr := th.App.CreatePost(th.Context, &model.Post{UserId: th.BasicUser2.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "hi"}, th.BasicChannel, false, false)
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: "hi @" + th.BasicUser.Username}, th.BasicChannel, false, false)
require.Nil(t, appErr)
appErr = th.App.UpdateThreadFollowForUser(th.BasicUser.Id, th.BasicTeam.Id, rootPost.Id, true)
require.Nil(t, appErr)
appErr = th.App.UpdateThreadFollowForUser(th.BasicUser.Id, th.BasicTeam.Id, rootPost.Id, false)
require.Nil(t, appErr)
_, appErr = th.App.MarkChannelAsUnreadFromPost(rootPost.Id, th.BasicUser.Id, true, true)
require.Nil(t, appErr)
threadMembership, appErr := th.App.GetThreadMembershipForUser(th.BasicUser.Id, rootPost.Id)
require.Nil(t, appErr)
require.NotNil(t, threadMembership)
assert.Equal(t, int64(1), threadMembership.UnreadMentions)
})
})
}