Files
mostlymatter/server/channels/app/team_test.go
Maria A Nunez 2ff29e375b [release-10.11] Tighten thread membership cleanup on team membership changes (#37081)
* Tighten thread membership cleanup on team membership changes (#36764)

* Tighten thread membership cleanup on team membership changes

Ensure ThreadMembership rows are cleaned up when a user is removed
from or leaves a team, add a defense-in-depth filter on the thread
retrieval path so memberships referencing channels the user is no
longer a member of are excluded, and add a one-time migration to
clean up stale records on existing deployments.

https://mattermost.atlassian.net/browse/MM-69008

* Centralize per-channel membership removal and tighten tests

Extract the combined channel-member and thread-membership removal into
a shared helper used by both removeUserFromChannel and LeaveTeam, so
future code paths cannot revoke channel access without dropping the
dependent thread state. The channel-leave event is now logged after
the combined removal completes.

Also drop verbose test header comments and rename a test to a
behavior-focused name.

* Backfill channel members in thread storetest setups

The new ChannelMembers predicate on thread read queries filters out
ThreadMembership rows whose user has no ChannelMembers row for the
thread's channel. Several existing storetest setups bypassed the
normal write path and inserted threads/memberships without channel
members. Add the missing channel-member rows so the test data matches
the real-world invariant.

* Retrigger enterprise CI

Pick up enterprise merge e6953d4 (master into
MM-69008-thread-membership-team-leave) in the combined Enterprise
CI/tests lane, which pins the enterprise SHA at mattermost-side
dispatch time.

Co-authored-by: Maria A Nunez <maria.nunez@mattermost.com>

---------

Co-authored-by: Mattermost Build <build@mattermost.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>

* Add MySQL migration for 000195_threadmemberships_cleanup_v2

Co-authored-by: Cursor <cursoragent@cursor.com>

* Apply pre-commit lint fixes

Co-authored-by: Cursor <cursoragent@cursor.com>

* Retrigger CI

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Mattermost Build <build@mattermost.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
2026-06-22 13:52:05 -04:00

2049 строки
71 KiB
Go

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package app
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"math/rand"
"sort"
"strings"
"testing"
"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/public/shared/request"
"github.com/mattermost/mattermost/server/v8/channels/app/email"
emailmocks "github.com/mattermost/mattermost/server/v8/channels/app/email/mocks"
"github.com/mattermost/mattermost/server/v8/channels/app/teams"
"github.com/mattermost/mattermost/server/v8/channels/app/users"
"github.com/mattermost/mattermost/server/v8/channels/store"
"github.com/mattermost/mattermost/server/v8/channels/store/sqlstore"
"github.com/mattermost/mattermost/server/v8/channels/store/storetest/mocks"
"github.com/mattermost/mattermost/server/v8/channels/testlib"
)
func TestCreateTeam(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic()
defer th.TearDown()
id := model.NewId()
team := &model.Team{
DisplayName: "dn_" + id,
Name: "name" + id,
Email: "success+" + id + "@simulator.amazonses.com",
Type: model.TeamOpen,
}
_, err := th.App.CreateTeam(th.Context, team)
require.Nil(t, err, "Should create a new team")
_, err = th.App.CreateTeam(th.Context, th.BasicTeam)
require.NotNil(t, err, "Should not create a new team - team already exist")
}
func TestCreateTeamWithUser(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic()
defer th.TearDown()
id := model.NewId()
team := &model.Team{
DisplayName: "dn_" + id,
Name: "name" + id,
Email: "success+" + id + "@simulator.amazonses.com",
Type: model.TeamOpen,
}
_, err := th.App.CreateTeamWithUser(th.Context, team, th.BasicUser.Id)
require.Nil(t, err, "Should create a new team with existing user")
_, err = th.App.CreateTeamWithUser(th.Context, team, model.NewId())
require.NotNil(t, err, "Should not create a new team - user does not exist")
}
func TestUpdateTeam(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic()
defer th.TearDown()
th.BasicTeam.DisplayName = "Testing 123"
updatedTeam, err := th.App.UpdateTeam(th.BasicTeam)
require.Nil(t, err, "Should update the team")
require.Equal(t, "Testing 123", updatedTeam.DisplayName, "Wrong Team DisplayName")
}
func TestAddUserToTeam(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic()
defer th.TearDown()
t.Run("add 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.CreateUser(th.Context, &user)
defer func() {
appErr := th.App.PermanentDeleteUser(th.Context, &user)
require.Nil(t, appErr)
}()
_, _, err := th.App.AddUserToTeam(th.Context, th.BasicTeam.Id, ruser.Id, "")
require.Nil(t, err, "Should add user to the team")
})
t.Run("allow user by domain", func(t *testing.T) {
th.BasicTeam.AllowedDomains = "example.com"
_, err := th.App.UpdateTeam(th.BasicTeam)
require.Nil(t, err, "Should update the team")
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, &user)
require.Nil(t, appErr)
}()
_, _, err = th.App.AddUserToTeam(th.Context, th.BasicTeam.Id, ruser.Id, "")
require.Nil(t, err, "Should have allowed whitelisted user")
})
t.Run("block user by domain but allow bot", func(t *testing.T) {
th.BasicTeam.AllowedDomains = "example.com"
_, err := th.App.UpdateTeam(th.BasicTeam)
require.Nil(t, err, "Should update the team")
user := model.User{Email: strings.ToLower(model.NewId()) + "test@invalid.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""}
ruser, err := th.App.CreateUser(th.Context, &user)
require.Nil(t, err, "Error creating user: %s", err)
defer func() {
appErr := th.App.PermanentDeleteUser(th.Context, &user)
require.NotNil(t, appErr)
}()
_, _, err = th.App.AddUserToTeam(th.Context, th.BasicTeam.Id, ruser.Id, "")
require.NotNil(t, err, "Should not add restricted user")
require.Equal(t, "JoinUserToTeam", err.Where, "Error should be JoinUserToTeam")
user = model.User{Email: strings.ToLower(model.NewId()) + "test@invalid.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), AuthService: "notnil", AuthData: model.NewPointer("notnil")}
ruser, err = th.App.CreateUser(th.Context, &user)
require.Nil(t, err, "Error creating authservice user: %s", err)
defer func() {
appErr := th.App.PermanentDeleteUser(th.Context, &user)
require.Nil(t, appErr)
}()
_, _, err = th.App.AddUserToTeam(th.Context, th.BasicTeam.Id, ruser.Id, "")
require.NotNil(t, err, "Should not add authservice user")
require.Equal(t, "JoinUserToTeam", err.Where, "Error should be JoinUserToTeam")
bot, err := th.App.CreateBot(th.Context, &model.Bot{
Username: "somebot",
Description: "a bot",
OwnerId: th.BasicUser.Id,
})
require.Nil(t, err)
_, _, err = th.App.AddUserToTeam(th.Context, th.BasicTeam.Id, bot.UserId, "")
assert.Nil(t, err, "should be able to add bot to domain restricted team")
})
t.Run("block user with subdomain", func(t *testing.T) {
th.BasicTeam.AllowedDomains = "example.com"
_, err := th.App.UpdateTeam(th.BasicTeam)
require.Nil(t, err, "Should update the team")
user := model.User{Email: strings.ToLower(model.NewId()) + "test@invalid.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, &user)
require.Nil(t, appErr)
}()
_, _, err = th.App.AddUserToTeam(th.Context, th.BasicTeam.Id, ruser.Id, "")
require.NotNil(t, err, "Should not add restricted user")
require.Equal(t, "JoinUserToTeam", err.Where, "Error should be JoinUserToTeam")
})
t.Run("allow users by multiple domains", func(t *testing.T) {
th.BasicTeam.AllowedDomains = "foo.com, bar.com"
_, err := th.App.UpdateTeam(th.BasicTeam)
require.Nil(t, err, "Should update the team")
user1 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@foo.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""}
ruser1, _ := th.App.CreateUser(th.Context, &user1)
user2 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@bar.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""}
ruser2, _ := th.App.CreateUser(th.Context, &user2)
user3 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@invalid.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""}
ruser3, _ := th.App.CreateUser(th.Context, &user3)
defer func() {
appErr := th.App.PermanentDeleteUser(th.Context, &user1)
require.Nil(t, appErr)
}()
defer func() {
appErr := th.App.PermanentDeleteUser(th.Context, &user2)
require.Nil(t, appErr)
}()
defer func() {
appErr := th.App.PermanentDeleteUser(th.Context, &user3)
require.Nil(t, appErr)
}()
_, _, err = th.App.AddUserToTeam(th.Context, th.BasicTeam.Id, ruser1.Id, "")
require.Nil(t, err, "Should have allowed whitelisted user1")
_, _, err = th.App.AddUserToTeam(th.Context, th.BasicTeam.Id, ruser2.Id, "")
require.Nil(t, err, "Should have allowed whitelisted user2")
_, _, err = th.App.AddUserToTeam(th.Context, th.BasicTeam.Id, ruser3.Id, "")
require.NotNil(t, err, "Should not have allowed restricted user3")
require.Equal(t, "JoinUserToTeam", err.Where, "Error should be JoinUserToTeam")
})
t.Run("should set up initial sidebar categories when joining a team", func(t *testing.T) {
user := th.CreateUser()
team := th.CreateTeam()
_, _, err := th.App.AddUserToTeam(th.Context, team.Id, user.Id, "")
require.Nil(t, err)
res, err := th.App.GetSidebarCategoriesForTeamForUser(th.Context, user.Id, team.Id)
require.Nil(t, err)
assert.Len(t, res.Categories, 3)
assert.Equal(t, model.SidebarCategoryFavorites, res.Categories[0].Type)
assert.Equal(t, model.SidebarCategoryChannels, res.Categories[1].Type)
assert.Equal(t, model.SidebarCategoryDirectMessages, res.Categories[2].Type)
})
}
func TestAddUserToTeamByToken(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)
rguest := th.CreateGuest()
t.Run("invalid token", func(t *testing.T) {
_, _, err := th.App.AddUserToTeamByToken(th.Context, ruser.Id, "123")
require.NotNil(t, err, "Should fail on unexisting token")
})
t.Run("invalid token type", func(t *testing.T) {
token := model.NewToken(
TokenTypeVerifyEmail,
model.MapToJSON(map[string]string{"teamId": th.BasicTeam.Id}),
)
require.NoError(t, th.App.Srv().Store().Token().Save(token))
defer func() {
appErr := th.App.DeleteToken(token)
require.Nil(t, appErr)
}()
_, _, err := th.App.AddUserToTeamByToken(th.Context, ruser.Id, token.Token)
require.NotNil(t, err, "Should fail on bad token type")
})
t.Run("expired token", func(t *testing.T) {
token := model.NewToken(
TokenTypeTeamInvitation,
model.MapToJSON(map[string]string{"teamId": th.BasicTeam.Id}),
)
token.CreateAt = model.GetMillis() - InvitationExpiryTime - 1
require.NoError(t, th.App.Srv().Store().Token().Save(token))
defer func() {
appErr := th.App.DeleteToken(token)
require.Nil(t, appErr)
}()
_, _, err := th.App.AddUserToTeamByToken(th.Context, ruser.Id, token.Token)
require.NotNil(t, err, "Should fail on expired token")
})
t.Run("invalid team id", func(t *testing.T) {
token := model.NewToken(
TokenTypeTeamInvitation,
model.MapToJSON(map[string]string{"teamId": model.NewId()}),
)
require.NoError(t, th.App.Srv().Store().Token().Save(token))
defer func() {
appErr := th.App.DeleteToken(token)
require.Nil(t, appErr)
}()
_, _, err := th.App.AddUserToTeamByToken(th.Context, ruser.Id, token.Token)
require.NotNil(t, err, "Should fail on bad team id")
})
t.Run("invalid user id", func(t *testing.T) {
token := model.NewToken(
TokenTypeTeamInvitation,
model.MapToJSON(map[string]string{"teamId": th.BasicTeam.Id}),
)
require.NoError(t, th.App.Srv().Store().Token().Save(token))
defer func() {
appErr := th.App.DeleteToken(token)
require.Nil(t, appErr)
}()
_, _, err := th.App.AddUserToTeamByToken(th.Context, model.NewId(), token.Token)
require.NotNil(t, err, "Should fail on bad user id")
})
t.Run("valid request", func(t *testing.T) {
token := model.NewToken(
TokenTypeTeamInvitation,
model.MapToJSON(map[string]string{"teamId": th.BasicTeam.Id}),
)
require.NoError(t, th.App.Srv().Store().Token().Save(token))
_, _, err := th.App.AddUserToTeamByToken(th.Context, ruser.Id, token.Token)
require.Nil(t, err, "Should add user to the team")
_, nErr := th.App.Srv().Store().Token().GetByToken(token.Token)
require.Error(t, nErr, "The token must be deleted after be used")
members, err := th.App.GetChannelMembersForUser(th.Context, th.BasicTeam.Id, ruser.Id)
require.Nil(t, err)
assert.Len(t, members, 2)
})
t.Run("invalid add a guest using a regular invite", func(t *testing.T) {
token := model.NewToken(
TokenTypeTeamInvitation,
model.MapToJSON(map[string]string{"teamId": th.BasicTeam.Id}),
)
require.NoError(t, th.App.Srv().Store().Token().Save(token))
_, _, err := th.App.AddUserToTeamByToken(th.Context, rguest.Id, token.Token)
assert.NotNil(t, err)
})
t.Run("invalid add a regular user using a guest invite", func(t *testing.T) {
token := model.NewToken(
TokenTypeGuestInvitation,
model.MapToJSON(map[string]string{"teamId": th.BasicTeam.Id, "channels": th.BasicChannel.Id}),
)
require.NoError(t, th.App.Srv().Store().Token().Save(token))
_, _, err := th.App.AddUserToTeamByToken(th.Context, ruser.Id, token.Token)
assert.NotNil(t, err)
})
t.Run("invalid add a guest user with a non-granted email domain", func(t *testing.T) {
restrictedDomain := *th.App.Config().GuestAccountsSettings.RestrictCreationToDomains
defer func() {
th.App.UpdateConfig(func(cfg *model.Config) { cfg.GuestAccountsSettings.RestrictCreationToDomains = &restrictedDomain })
}()
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.GuestAccountsSettings.RestrictCreationToDomains = "restricted.com" })
token := model.NewToken(
TokenTypeGuestInvitation,
model.MapToJSON(map[string]string{"teamId": th.BasicTeam.Id, "channels": th.BasicChannel.Id}),
)
require.NoError(t, th.App.Srv().Store().Token().Save(token))
_, _, err := th.App.AddUserToTeamByToken(th.Context, rguest.Id, token.Token)
require.NotNil(t, err)
assert.Equal(t, "api.team.join_user_to_team.allowed_domains.app_error", err.Id)
})
t.Run("add a guest user with a granted email domain", func(t *testing.T) {
restrictedDomain := *th.App.Config().GuestAccountsSettings.RestrictCreationToDomains
defer func() {
th.App.UpdateConfig(func(cfg *model.Config) { cfg.GuestAccountsSettings.RestrictCreationToDomains = &restrictedDomain })
}()
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.GuestAccountsSettings.RestrictCreationToDomains = "restricted.com" })
token := model.NewToken(
TokenTypeGuestInvitation,
model.MapToJSON(map[string]string{"teamId": th.BasicTeam.Id, "channels": th.BasicChannel.Id}),
)
guestEmail := rguest.Email
rguest.Email = "test@restricted.com"
_, err := th.App.Srv().Store().User().Update(th.Context, rguest, false)
th.App.InvalidateCacheForUser(rguest.Id)
require.NoError(t, err)
require.NoError(t, th.App.Srv().Store().Token().Save(token))
_, _, appErr := th.App.AddUserToTeamByToken(th.Context, rguest.Id, token.Token)
require.Nil(t, appErr)
rguest.Email = guestEmail
_, err = th.App.Srv().Store().User().Update(th.Context, rguest, false)
require.NoError(t, err)
})
t.Run("add a guest user even though there are team and system domain restrictions", func(t *testing.T) {
th.BasicTeam.AllowedDomains = "restricted-team.com"
_, err := th.Server.Store().Team().Update(th.BasicTeam)
require.NoError(t, err)
restrictedDomain := *th.App.Config().TeamSettings.RestrictCreationToDomains
defer func() {
th.App.UpdateConfig(func(cfg *model.Config) { cfg.TeamSettings.RestrictCreationToDomains = &restrictedDomain })
}()
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictCreationToDomains = "restricted.com" })
token := model.NewToken(
TokenTypeGuestInvitation,
model.MapToJSON(map[string]string{"teamId": th.BasicTeam.Id, "channels": th.BasicChannel.Id}),
)
_, err = th.App.Srv().Store().User().Update(th.Context, rguest, false)
require.NoError(t, err)
require.NoError(t, th.App.Srv().Store().Token().Save(token))
_, _, appErr := th.App.AddUserToTeamByToken(th.Context, rguest.Id, token.Token)
require.Nil(t, appErr)
th.BasicTeam.AllowedDomains = ""
_, err = th.Server.Store().Team().Update(th.BasicTeam)
require.NoError(t, err)
})
t.Run("valid request from guest invite", func(t *testing.T) {
token := model.NewToken(
TokenTypeGuestInvitation,
model.MapToJSON(map[string]string{"teamId": th.BasicTeam.Id, "channels": th.BasicChannel.Id}),
)
require.NoError(t, th.App.Srv().Store().Token().Save(token))
_, _, err := th.App.AddUserToTeamByToken(th.Context, rguest.Id, token.Token)
require.Nil(t, err, "Should add user to the team")
_, nErr := th.App.Srv().Store().Token().GetByToken(token.Token)
require.Error(t, nErr, "The token must be deleted after be used")
members, err := th.App.GetChannelMembersForUser(th.Context, th.BasicTeam.Id, rguest.Id)
require.Nil(t, err)
require.Len(t, members, 1)
assert.Equal(t, members[0].ChannelId, th.BasicChannel.Id)
})
t.Run("group-constrained team", func(t *testing.T) {
th.BasicTeam.GroupConstrained = model.NewPointer(true)
_, err := th.App.UpdateTeam(th.BasicTeam)
require.Nil(t, err, "Should update the team")
token := model.NewToken(
TokenTypeTeamInvitation,
model.MapToJSON(map[string]string{"teamId": th.BasicTeam.Id}),
)
require.NoError(t, th.App.Srv().Store().Token().Save(token))
_, _, err = th.App.AddUserToTeamByToken(th.Context, ruser.Id, token.Token)
require.NotNil(t, err, "Should return an error when trying to join a group-constrained team.")
require.Equal(t, "app.team.invite_token.group_constrained.error", err.Id)
th.BasicTeam.GroupConstrained = model.NewPointer(false)
_, err = th.App.UpdateTeam(th.BasicTeam)
require.Nil(t, err, "Should update the team")
})
t.Run("block user", func(t *testing.T) {
th.BasicTeam.AllowedDomains = "example.com"
_, err := th.App.UpdateTeam(th.BasicTeam)
require.Nil(t, err, "Should update the team")
user := model.User{Email: strings.ToLower(model.NewId()) + "test@invalid.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, &user)
require.Nil(t, appErr)
}()
token := model.NewToken(
TokenTypeTeamInvitation,
model.MapToJSON(map[string]string{"teamId": th.BasicTeam.Id}),
)
require.NoError(t, th.App.Srv().Store().Token().Save(token))
_, _, err = th.App.AddUserToTeamByToken(th.Context, ruser.Id, token.Token)
require.NotNil(t, err, "Should not add restricted user")
require.Equal(t, "JoinUserToTeam", err.Where, "Error should be JoinUserToTeam")
})
t.Run("should set up initial sidebar categories when joining a team by token", func(t *testing.T) {
user := th.CreateUser()
team := th.CreateTeam()
token := model.NewToken(
TokenTypeTeamInvitation,
model.MapToJSON(map[string]string{"teamId": team.Id}),
)
require.NoError(t, th.App.Srv().Store().Token().Save(token))
_, _, err := th.App.AddUserToTeamByToken(th.Context, user.Id, token.Token)
require.Nil(t, err)
res, err := th.App.GetSidebarCategoriesForTeamForUser(th.Context, user.Id, team.Id)
require.Nil(t, err)
assert.Len(t, res.Categories, 3)
assert.Equal(t, model.SidebarCategoryFavorites, res.Categories[0].Type)
assert.Equal(t, model.SidebarCategoryChannels, res.Categories[1].Type)
assert.Equal(t, model.SidebarCategoryDirectMessages, res.Categories[2].Type)
})
}
func TestAddUserToTeamByTeamId(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic()
defer th.TearDown()
t.Run("add 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.CreateUser(th.Context, &user)
err := th.App.AddUserToTeamByTeamId(th.Context, th.BasicTeam.Id, ruser)
require.Nil(t, err, "Should add user to the team")
})
t.Run("block user", func(t *testing.T) {
th.BasicTeam.AllowedDomains = "example.com"
_, err := th.App.UpdateTeam(th.BasicTeam)
require.Nil(t, err, "Should update the team")
user := model.User{Email: strings.ToLower(model.NewId()) + "test@invalid.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, &user)
require.Nil(t, appErr)
}()
err = th.App.AddUserToTeamByTeamId(th.Context, th.BasicTeam.Id, ruser)
require.NotNil(t, err, "Should not add restricted user")
require.Equal(t, "JoinUserToTeam", err.Where, "Error should be JoinUserToTeam")
})
}
func TestAdjustTeamsFromProductLimits(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic()
defer th.TearDown()
teams := []*model.Team{
{
DisplayName: "team-1",
Name: "team-1",
Email: "foo@foo.com",
Type: model.TeamOpen,
},
{
DisplayName: "team-2",
Name: "team-2",
Email: "foo@foo.com",
Type: model.TeamOpen,
},
{
DisplayName: "team-3",
Name: "team-3",
Email: "foo@foo.com",
Type: model.TeamOpen,
},
}
teamIds := []string{}
for _, create := range teams {
team, err := th.App.CreateTeam(th.Context, create)
require.Nil(t, err)
teamIds = append(teamIds, team.Id)
}
t.Run("Should soft delete teams if there are more teams than the limit", func(t *testing.T) {
activeLimit := 1
teamLimits := &model.TeamsLimits{Active: &activeLimit}
err := th.App.AdjustTeamsFromProductLimits(teamLimits)
require.Nil(t, err)
teamsList, err := th.App.GetTeams(teamIds)
require.Nil(t, err)
// Sort the list of teams based on their creation date
sort.Slice(teamsList, func(i, j int) bool {
return teamsList[i].CreateAt < teamsList[j].CreateAt
})
for i := range teamsList {
require.Equal(t, teamsList[i].DisplayName, teams[i].DisplayName)
require.NotEqual(t, 0, teamsList[i].DeleteAt)
require.Equal(t, true, teamsList[i].CloudLimitsArchived)
}
})
t.Run("Should not do anything if the amount of teams is equal to the limit", func(t *testing.T) {
expectedTeamsList, err := th.App.GetAllTeams()
var expectedActiveTeams []*model.Team
var expectedCloudArchivedTeams []*model.Team
for _, team := range expectedTeamsList {
if team.DeleteAt == 0 {
expectedActiveTeams = append(expectedActiveTeams, team)
}
if team.DeleteAt > 0 && team.CloudLimitsArchived {
expectedCloudArchivedTeams = append(expectedCloudArchivedTeams, team)
}
}
require.Nil(t, err)
activeLimit := len(expectedActiveTeams)
teamLimits := &model.TeamsLimits{Active: &activeLimit}
err = th.App.AdjustTeamsFromProductLimits(teamLimits)
require.Nil(t, err)
actualTeamsList, err := th.App.GetAllTeams()
require.Nil(t, err)
var actualActiveTeams []*model.Team
var actualCloudArchivedTeams []*model.Team
for _, team := range actualTeamsList {
if team.DeleteAt == 0 {
actualActiveTeams = append(actualActiveTeams, team)
}
if team.DeleteAt > 0 && team.CloudLimitsArchived {
actualCloudArchivedTeams = append(actualCloudArchivedTeams, team)
}
}
require.Equal(t, len(expectedActiveTeams), len(actualActiveTeams))
require.Equal(t, len(expectedCloudArchivedTeams), len(actualCloudArchivedTeams))
})
t.Run("Should restore archived teams if limit increases", func(t *testing.T) {
activeLimit := 1
teamLimits := &model.TeamsLimits{Active: &activeLimit}
err := th.App.AdjustTeamsFromProductLimits(teamLimits)
require.Nil(t, err)
activeLimit = 10000 // make the limit extremely high so all teams are enabled
teamLimits = &model.TeamsLimits{Active: &activeLimit}
err = th.App.AdjustTeamsFromProductLimits(teamLimits)
require.Nil(t, err)
teamsList, err := th.App.GetTeams(teamIds)
require.Nil(t, err)
// Sort the list of teams based on their creation date
sort.Slice(teamsList, func(i, j int) bool {
return teamsList[i].CreateAt < teamsList[j].CreateAt
})
for i := range teamsList {
require.Equal(t, teamsList[i].DisplayName, teams[i].DisplayName)
require.Equal(t, int64(0), teamsList[i].DeleteAt)
require.Equal(t, false, teamsList[i].CloudLimitsArchived)
}
})
t.Run("Should only restore teams that were archived by cloud limits", func(t *testing.T) {
activeLimit := 1
teamLimits := &model.TeamsLimits{Active: &activeLimit}
err := th.App.AdjustTeamsFromProductLimits(teamLimits)
require.Nil(t, err)
cloudLimitsArchived := false
patch := &model.TeamPatch{CloudLimitsArchived: &cloudLimitsArchived}
team, err := th.App.PatchTeam(teamIds[0], patch)
require.Nil(t, err)
require.Equal(t, false, team.CloudLimitsArchived)
activeLimit = 10000 // make the limit extremely high so all teams are enabled
teamLimits = &model.TeamsLimits{Active: &activeLimit}
err = th.App.AdjustTeamsFromProductLimits(teamLimits)
require.Nil(t, err)
teamsList, err := th.App.GetTeams(teamIds)
require.Nil(t, err)
// Sort the list of teams based on their creation date
sort.Slice(teamsList, func(i, j int) bool {
return teamsList[i].CreateAt < teamsList[j].CreateAt
})
require.NotEqual(t, int64(0), teamsList[0].DeleteAt)
require.Equal(t, int64(0), teamsList[1].DeleteAt)
require.Equal(t, int64(0), teamsList[2].DeleteAt)
})
}
func TestPermanentDeleteTeam(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic()
defer th.TearDown()
team, err := th.App.CreateTeam(th.Context, &model.Team{
DisplayName: "deletion-test",
Name: "deletion-test",
Email: "foo@foo.com",
Type: model.TeamOpen,
})
require.Nil(t, err, "Should create a team")
command, err := th.App.CreateCommand(&model.Command{
CreatorId: th.BasicUser.Id,
TeamId: team.Id,
Trigger: "foo",
URL: "http://foo",
Method: model.CommandMethodPost,
})
require.Nil(t, err, "Should create a command")
command, err = th.App.GetCommand(command.Id)
require.NotNil(t, command, "command should not be nil")
require.Nil(t, err, "unable to get new command")
err = th.App.PermanentDeleteTeam(th.Context, team)
require.Nil(t, err)
command, appErr := th.App.GetCommand(command.Id)
require.Nil(t, command, "command was deleted")
require.NotNil(t, appErr, "unable to get command")
// Test deleting a team with no channels.
team = th.CreateTeam()
defer func() {
appErr := th.App.PermanentDeleteTeam(th.Context, team)
require.Nil(t, appErr)
}()
channels, err := th.App.GetPublicChannelsForTeam(th.Context, team.Id, 0, 1000)
require.Nil(t, err)
for _, channel := range channels {
err2 := th.App.PermanentDeleteChannel(th.Context, channel)
require.Nil(t, err2)
}
}
func TestSanitizeTeam(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t)
defer th.TearDown()
team := &model.Team{
Id: model.NewId(),
Email: th.MakeEmail(),
InviteId: model.NewId(),
AllowedDomains: "example.com",
}
copyTeam := func() *model.Team {
copy := &model.Team{} //nolint:revive
*copy = *team
return copy
}
t.Run("not a user of the team", func(t *testing.T) {
userID := model.NewId()
session := model.Session{
Roles: model.SystemUserRoleId,
TeamMembers: []*model.TeamMember{
{
UserId: userID,
TeamId: model.NewId(),
Roles: model.TeamUserRoleId,
},
},
}
sanitized := th.App.SanitizeTeam(session, copyTeam())
require.Empty(t, sanitized.Email, "should've sanitized team")
require.Empty(t, sanitized.InviteId, "should've sanitized inviteid")
})
t.Run("user of the team", func(t *testing.T) {
userID := model.NewId()
session := model.Session{
Roles: model.SystemUserRoleId,
TeamMembers: []*model.TeamMember{
{
UserId: userID,
TeamId: team.Id,
Roles: model.TeamUserRoleId,
},
},
}
sanitized := th.App.SanitizeTeam(session, copyTeam())
require.Empty(t, sanitized.Email, "should've sanitized team")
require.NotEmpty(t, sanitized.InviteId, "should have not sanitized inviteid")
})
t.Run("team admin", func(t *testing.T) {
userID := model.NewId()
session := model.Session{
Roles: model.SystemUserRoleId,
TeamMembers: []*model.TeamMember{
{
UserId: userID,
TeamId: team.Id,
Roles: model.TeamUserRoleId + " " + model.TeamAdminRoleId,
},
},
}
sanitized := th.App.SanitizeTeam(session, copyTeam())
require.NotEmpty(t, sanitized.Email, "shouldn't have sanitized team")
require.NotEmpty(t, sanitized.InviteId, "shouldn't have sanitized inviteid")
})
t.Run("team admin of another team", func(t *testing.T) {
userID := model.NewId()
session := model.Session{
Roles: model.SystemUserRoleId,
TeamMembers: []*model.TeamMember{
{
UserId: userID,
TeamId: model.NewId(),
Roles: model.TeamUserRoleId + " " + model.TeamAdminRoleId,
},
},
}
sanitized := th.App.SanitizeTeam(session, copyTeam())
require.Empty(t, sanitized.Email, "should've sanitized team")
require.Empty(t, sanitized.InviteId, "should've sanitized inviteid")
})
t.Run("system admin, not a user of team", func(t *testing.T) {
userID := model.NewId()
session := model.Session{
Roles: model.SystemUserRoleId + " " + model.SystemAdminRoleId,
TeamMembers: []*model.TeamMember{
{
UserId: userID,
TeamId: model.NewId(),
Roles: model.TeamUserRoleId,
},
},
}
sanitized := th.App.SanitizeTeam(session, copyTeam())
require.NotEmpty(t, sanitized.Email, "shouldn't have sanitized team")
require.NotEmpty(t, sanitized.InviteId, "shouldn't have sanitized inviteid")
})
t.Run("system admin, user of team", func(t *testing.T) {
userID := model.NewId()
session := model.Session{
Roles: model.SystemUserRoleId + " " + model.SystemAdminRoleId,
TeamMembers: []*model.TeamMember{
{
UserId: userID,
TeamId: team.Id,
Roles: model.TeamUserRoleId,
},
},
}
sanitized := th.App.SanitizeTeam(session, copyTeam())
require.NotEmpty(t, sanitized.Email, "shouldn't have sanitized team")
require.NotEmpty(t, sanitized.InviteId, "shouldn't have sanitized inviteid")
})
}
func TestSanitizeTeams(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t)
defer th.TearDown()
t.Run("not a system admin", func(t *testing.T) {
teams := []*model.Team{
{
Id: model.NewId(),
Email: th.MakeEmail(),
AllowedDomains: "example.com",
},
{
Id: model.NewId(),
Email: th.MakeEmail(),
AllowedDomains: "example.com",
},
}
userID := model.NewId()
session := model.Session{
Roles: model.SystemUserRoleId,
TeamMembers: []*model.TeamMember{
{
UserId: userID,
TeamId: teams[0].Id,
Roles: model.TeamUserRoleId,
},
{
UserId: userID,
TeamId: teams[1].Id,
Roles: model.TeamUserRoleId + " " + model.TeamAdminRoleId,
},
},
}
sanitized := th.App.SanitizeTeams(session, teams)
require.Empty(t, sanitized[0].Email, "should've sanitized first team")
require.NotEmpty(t, sanitized[1].Email, "shouldn't have sanitized second team")
})
t.Run("system admin", func(t *testing.T) {
teams := []*model.Team{
{
Id: model.NewId(),
Email: th.MakeEmail(),
AllowedDomains: "example.com",
},
{
Id: model.NewId(),
Email: th.MakeEmail(),
AllowedDomains: "example.com",
},
}
userID := model.NewId()
session := model.Session{
Roles: model.SystemUserRoleId + " " + model.SystemAdminRoleId,
TeamMembers: []*model.TeamMember{
{
UserId: userID,
TeamId: teams[0].Id,
Roles: model.TeamUserRoleId,
},
},
}
sanitized := th.App.SanitizeTeams(session, teams)
assert.NotEmpty(t, sanitized[0].Email, "shouldn't have sanitized first team")
assert.NotEmpty(t, sanitized[1].Email, "shouldn't have sanitized second team")
})
}
func TestJoinUserToTeam(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t)
defer th.TearDown()
id := model.NewId()
team := &model.Team{
DisplayName: "dn_" + id,
Name: "name" + id,
Email: "success+" + id + "@simulator.amazonses.com",
Type: model.TeamOpen,
}
_, err := th.App.CreateTeam(th.Context, team)
require.Nil(t, err, "Should create a new team")
maxUsersPerTeam := th.App.Config().TeamSettings.MaxUsersPerTeam
defer func() {
th.App.UpdateConfig(func(cfg *model.Config) { cfg.TeamSettings.MaxUsersPerTeam = maxUsersPerTeam })
appErr := th.App.PermanentDeleteTeam(th.Context, team)
require.Nil(t, appErr)
}()
one := 1
th.App.UpdateConfig(func(cfg *model.Config) { cfg.TeamSettings.MaxUsersPerTeam = &one })
t.Run("new join", 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)
defer func() {
appErr := th.App.PermanentDeleteUser(th.Context, &user)
require.Nil(t, appErr)
}()
_, appErr := th.App.JoinUserToTeam(th.Context, team, ruser, "")
require.Nil(t, appErr, "Should return no error")
})
t.Run("new join with limit problem", func(t *testing.T) {
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)
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, &user1)
require.Nil(t, appErr)
}()
defer func() {
appErr := th.App.PermanentDeleteUser(th.Context, &user2)
require.Nil(t, appErr)
}()
_, appErr := th.App.JoinUserToTeam(th.Context, team, ruser1, ruser2.Id)
require.Nil(t, appErr, "Should return no error")
_, appErr = th.App.JoinUserToTeam(th.Context, team, ruser2, ruser1.Id)
require.NotNil(t, appErr, "Should fail")
})
t.Run("re-join after leaving with limit problem", func(t *testing.T) {
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)
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, &user1)
require.Nil(t, appErr)
}()
defer func() {
appErr := th.App.PermanentDeleteUser(th.Context, &user2)
require.Nil(t, appErr)
}()
_, appErr := th.App.JoinUserToTeam(th.Context, team, ruser1, ruser2.Id)
require.Nil(t, appErr, "Should return no error")
appErr = th.App.LeaveTeam(th.Context, team, ruser1, ruser1.Id)
require.Nil(t, appErr, "Should return no error")
_, appErr = th.App.JoinUserToTeam(th.Context, team, ruser2, ruser2.Id)
require.Nil(t, appErr, "Should return no error")
_, appErr = th.App.JoinUserToTeam(th.Context, team, ruser1, ruser2.Id)
require.NotNil(t, appErr, "Should fail")
})
t.Run("new join with correct scheme_admin value from group syncable", func(t *testing.T) {
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)
}()
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: team.Id,
Type: model.GroupSyncableTypeTeam,
GroupId: group.Id,
SchemeAdmin: false,
})
require.Nil(t, err)
th.App.UpdateConfig(func(cfg *model.Config) { cfg.TeamSettings.MaxUsersPerTeam = model.NewPointer(999) })
tm1, appErr := th.App.JoinUserToTeam(th.Context, team, ruser1, "")
require.Nil(t, appErr)
require.False(t, tm1.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)
}()
_, err = th.App.UpsertGroupMember(group.Id, user2.Id)
require.Nil(t, err)
gs.SchemeAdmin = true
_, err = th.App.UpdateGroupSyncable(gs)
require.Nil(t, err)
tm2, appErr := th.App.JoinUserToTeam(th.Context, team, ruser2, "")
require.Nil(t, appErr)
require.True(t, tm2.SchemeAdmin)
})
}
func TestLeaveTeamPanic(t *testing.T) {
mainHelper.Parallel(t)
th := SetupWithStoreMock(t)
defer th.TearDown()
mockStore := th.App.Srv().Store().(*mocks.Store)
mockUserStore := mocks.UserStore{}
mockUserStore.On("Get", context.Background(), "userID").Return(&model.User{Id: "userID"}, nil)
mockUserStore.On("Count", mock.Anything).Return(int64(10), nil)
mockChannelStore := mocks.ChannelStore{}
mockChannelStore.On("Get", "channelID", true).Return(&model.Channel{Id: "channelID"}, nil)
mockChannelStore.On("GetMember", context.Background(), "channelID", "userID").Return(&model.ChannelMember{
NotifyProps: model.StringMap{
model.PushNotifyProp: model.ChannelNotifyDefault,
},
}, nil)
mockChannelStore.On("GetChannels", "myteam", "userID", mock.Anything).Return(model.ChannelList{}, nil)
var err error
th.App.ch.srv.userService, err = users.New(users.ServiceConfig{
UserStore: &mockUserStore,
SessionStore: &mocks.SessionStore{},
OAuthStore: &mocks.OAuthStore{},
ConfigFn: th.App.ch.srv.platform.Config,
LicenseFn: th.App.ch.srv.License,
})
require.NoError(t, err)
mockPreferenceStore := mocks.PreferenceStore{}
mockPreferenceStore.On("Get", "userID", model.PreferenceCategoryDisplaySettings, model.PreferenceNameCollapsedThreadsEnabled).Return(&model.Preference{Value: "on"}, nil)
mockPostStore := mocks.PostStore{}
mockPostStore.On("GetMaxPostSize").Return(65535, nil)
mockSystemStore := mocks.SystemStore{}
mockSystemStore.On("GetByName", "UpgradedFromTE").Return(&model.System{Name: "UpgradedFromTE", Value: "false"}, nil)
mockSystemStore.On("GetByName", "InstallationDate").Return(&model.System{Name: "InstallationDate", Value: "10"}, nil)
mockSystemStore.On("GetByName", "FirstServerRunTimestamp").Return(&model.System{Name: "FirstServerRunTimestamp", Value: "10"}, nil)
mockLicenseStore := mocks.LicenseStore{}
mockLicenseStore.On("Get", "").Return(&model.LicenseRecord{}, nil)
mockTeamStore := mocks.TeamStore{}
mockTeamStore.On("GetMember", mock.AnythingOfType("*request.Context"), "myteam", "userID").Return(&model.TeamMember{TeamId: "myteam", UserId: "userID"}, nil).Run(func(args mock.Arguments) {
c, ok := args[0].(request.CTX)
require.True(t, ok)
sqlstore.HasMaster(c.Context())
})
mockTeamStore.On("UpdateMember", mock.Anything, mock.Anything).Return(nil, errors.New("repro error")) // This is the line that triggers the error
mockStore.On("Channel").Return(&mockChannelStore)
mockStore.On("Preference").Return(&mockPreferenceStore)
mockStore.On("Post").Return(&mockPostStore)
mockStore.On("User").Return(&mockUserStore)
mockStore.On("System").Return(&mockSystemStore)
mockStore.On("License").Return(&mockLicenseStore)
mockStore.On("Team").Return(&mockTeamStore)
mockStore.On("GetDBSchemaVersion").Return(1, nil)
team := &model.Team{Id: "myteam"}
user := &model.User{Id: "userID"}
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.ServiceSettings.ExperimentalEnableDefaultChannelLeaveJoinMessages = false
})
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)
require.NotPanics(t, func() {
appErr := th.App.LeaveTeam(th.Context, team, user, user.Id)
require.NotNil(t, appErr)
}, "unexpected panic from LeaveTeam")
}
func TestLeaveTeamCleansUpThreadMemberships(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic()
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.ServiceSettings.ThreadAutoFollow = true
*cfg.ServiceSettings.CollapsedThreads = model.CollapsedThreadsDefaultOn
})
th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuProfessional))
admin := th.BasicUser
victim := th.BasicUser2
privateChannel := th.CreatePrivateChannel(th.Context, th.BasicTeam)
th.AddUserToChannel(victim, privateChannel)
rootPost, _, appErr := th.App.CreatePost(th.Context, &model.Post{
UserId: admin.Id,
ChannelId: privateChannel.Id,
Message: "private team secret",
}, privateChannel, model.CreatePostFlags{SetOnline: true})
require.Nil(t, appErr)
defer func() {
require.NoError(t, th.App.Srv().Store().Post().PermanentDeleteByUser(th.Context, admin.Id))
}()
_, _, appErr = th.App.CreatePost(th.Context, &model.Post{
UserId: victim.Id,
ChannelId: privateChannel.Id,
RootId: rootPost.Id,
Message: "victim reply",
}, privateChannel, model.CreatePostFlags{SetOnline: true})
require.Nil(t, appErr)
defer func() {
require.NoError(t, th.App.Srv().Store().Post().PermanentDeleteByUser(th.Context, victim.Id))
}()
_, sErr := th.App.Srv().Store().Thread().GetMembershipForUser(victim.Id, rootPost.Id)
require.NoError(t, sErr, "victim should follow the thread after replying")
appErr = th.App.LeaveTeam(th.Context, th.BasicTeam, victim, victim.Id)
require.Nil(t, appErr)
_, gErr := th.App.Srv().Store().Thread().GetMembershipForUser(victim.Id, rootPost.Id)
var errNotFound *store.ErrNotFound
require.ErrorAs(t, gErr, &errNotFound, "thread membership must be deleted when user leaves the team")
}
func TestLeaveTeamCleansUpThreadMembershipsAcrossChannels(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic()
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.ServiceSettings.ThreadAutoFollow = true
*cfg.ServiceSettings.CollapsedThreads = model.CollapsedThreadsDefaultOn
})
th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuProfessional))
admin := th.BasicUser
victim := th.BasicUser2
privateA := th.CreatePrivateChannel(th.Context, th.BasicTeam)
privateB := th.CreatePrivateChannel(th.Context, th.BasicTeam)
openC := th.CreateChannel(th.Context, th.BasicTeam)
th.AddUserToChannel(victim, privateA)
th.AddUserToChannel(victim, privateB)
th.AddUserToChannel(victim, openC)
rootIDs := make([]string, 0, 3)
for _, ch := range []*model.Channel{privateA, privateB, openC} {
root, _, appErr := th.App.CreatePost(th.Context, &model.Post{
UserId: admin.Id,
ChannelId: ch.Id,
Message: "root in " + ch.Id,
}, ch, model.CreatePostFlags{SetOnline: true})
require.Nil(t, appErr)
_, _, appErr = th.App.CreatePost(th.Context, &model.Post{
UserId: victim.Id,
ChannelId: ch.Id,
RootId: root.Id,
Message: "reply",
}, ch, model.CreatePostFlags{SetOnline: true})
require.Nil(t, appErr)
rootIDs = append(rootIDs, root.Id)
}
defer func() {
require.NoError(t, th.App.Srv().Store().Post().PermanentDeleteByUser(th.Context, admin.Id))
require.NoError(t, th.App.Srv().Store().Post().PermanentDeleteByUser(th.Context, victim.Id))
}()
for _, rid := range rootIDs {
_, sErr := th.App.Srv().Store().Thread().GetMembershipForUser(victim.Id, rid)
require.NoError(t, sErr, "sanity: victim should follow each thread")
}
appErr := th.App.LeaveTeam(th.Context, th.BasicTeam, victim, victim.Id)
require.Nil(t, appErr)
var errNotFound *store.ErrNotFound
for _, rid := range rootIDs {
_, gErr := th.App.Srv().Store().Thread().GetMembershipForUser(victim.Id, rid)
require.ErrorAs(t, gErr, &errNotFound, "thread membership for %s must be deleted on team leave", rid)
}
}
func TestLeaveTeamPreservesDMThreadMemberships(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic()
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.ServiceSettings.ThreadAutoFollow = true
*cfg.ServiceSettings.CollapsedThreads = model.CollapsedThreadsDefaultOn
})
th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuProfessional))
admin := th.BasicUser
victim := th.BasicUser2
dmChannel, appErr := th.App.GetOrCreateDirectChannel(th.Context, admin.Id, victim.Id)
require.Nil(t, appErr)
dmRoot, _, appErr := th.App.CreatePost(th.Context, &model.Post{
UserId: admin.Id,
ChannelId: dmChannel.Id,
Message: "dm root",
}, dmChannel, model.CreatePostFlags{SetOnline: true})
require.Nil(t, appErr)
_, _, appErr = th.App.CreatePost(th.Context, &model.Post{
UserId: victim.Id,
ChannelId: dmChannel.Id,
RootId: dmRoot.Id,
Message: "dm reply",
}, dmChannel, model.CreatePostFlags{SetOnline: true})
require.Nil(t, appErr)
defer func() {
require.NoError(t, th.App.Srv().Store().Post().PermanentDeleteByUser(th.Context, admin.Id))
require.NoError(t, th.App.Srv().Store().Post().PermanentDeleteByUser(th.Context, victim.Id))
}()
_, sErr := th.App.Srv().Store().Thread().GetMembershipForUser(victim.Id, dmRoot.Id)
require.NoError(t, sErr, "sanity: victim should follow the DM thread")
appErr = th.App.LeaveTeam(th.Context, th.BasicTeam, victim, victim.Id)
require.Nil(t, appErr)
_, gErr := th.App.Srv().Store().Thread().GetMembershipForUser(victim.Id, dmRoot.Id)
require.NoError(t, gErr, "DM thread membership must survive leaving an unrelated team")
}
func TestGetThreadsForUser_ReadPathRejectsOrphanThreadMembership(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic()
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.ServiceSettings.ThreadAutoFollow = true
*cfg.ServiceSettings.CollapsedThreads = model.CollapsedThreadsDefaultOn
})
th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuProfessional))
admin := th.BasicUser
victim := th.BasicUser2
privateChannel := th.CreatePrivateChannel(th.Context, th.BasicTeam)
th.AddUserToChannel(victim, privateChannel)
rootPost, _, appErr := th.App.CreatePost(th.Context, &model.Post{
UserId: admin.Id,
ChannelId: privateChannel.Id,
Message: "private team secret",
}, privateChannel, model.CreatePostFlags{SetOnline: true})
require.Nil(t, appErr)
defer func() {
require.NoError(t, th.App.Srv().Store().Post().PermanentDeleteByUser(th.Context, admin.Id))
require.NoError(t, th.App.Srv().Store().Post().PermanentDeleteByUser(th.Context, victim.Id))
}()
_, _, appErr = th.App.CreatePost(th.Context, &model.Post{
UserId: victim.Id,
ChannelId: privateChannel.Id,
RootId: rootPost.Id,
Message: "victim reply",
}, privateChannel, model.CreatePostFlags{SetOnline: true})
require.Nil(t, appErr)
_, sErr := th.App.Srv().Store().Thread().GetMembershipForUser(victim.Id, rootPost.Id)
require.NoError(t, sErr, "sanity: victim should follow the thread after replying")
require.NoError(t, th.App.Srv().Store().Channel().RemoveMember(th.Context, privateChannel.Id, victim.Id))
_, sErr2 := th.App.Srv().Store().Thread().GetMembershipForUser(victim.Id, rootPost.Id)
require.NoError(t, sErr2, "sanity: synthetic orphan ThreadMembership must remain")
threads, gErr := th.App.Srv().Store().Thread().GetThreadsForUser(victim.Id, th.BasicTeam.Id, model.GetUserThreadsOpts{})
require.NoError(t, gErr)
for _, thr := range threads {
require.NotEqual(t, rootPost.Id, thr.PostId, "read path must not surface threads from channels the user no longer belongs to")
}
require.Empty(t, threads, "GetThreadsForUser must filter out orphan ThreadMembership rows")
totalThreads, gErr := th.App.Srv().Store().Thread().GetTotalThreads(victim.Id, th.BasicTeam.Id, model.GetUserThreadsOpts{})
require.NoError(t, gErr)
require.Zero(t, totalThreads, "GetTotalThreads must not count orphan ThreadMembership rows")
totalUnread, gErr := th.App.Srv().Store().Thread().GetTotalUnreadThreads(victim.Id, th.BasicTeam.Id, model.GetUserThreadsOpts{})
require.NoError(t, gErr)
require.Zero(t, totalUnread, "GetTotalUnreadThreads must not count orphan ThreadMembership rows")
}
func TestPermanentDeleteChannelRemovesThreadMemberships(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic()
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.ServiceSettings.ThreadAutoFollow = true
*cfg.ServiceSettings.CollapsedThreads = model.CollapsedThreadsDefaultOn
})
th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuProfessional))
admin := th.BasicUser
victim := th.BasicUser2
privateChannel := th.CreatePrivateChannel(th.Context, th.BasicTeam)
th.AddUserToChannel(victim, privateChannel)
rootPost, _, appErr := th.App.CreatePost(th.Context, &model.Post{
UserId: admin.Id,
ChannelId: privateChannel.Id,
Message: "doomed root",
}, privateChannel, model.CreatePostFlags{SetOnline: true})
require.Nil(t, appErr)
_, _, appErr = th.App.CreatePost(th.Context, &model.Post{
UserId: victim.Id,
ChannelId: privateChannel.Id,
RootId: rootPost.Id,
Message: "doomed reply",
}, privateChannel, model.CreatePostFlags{SetOnline: true})
require.Nil(t, appErr)
_, sErr := th.App.Srv().Store().Thread().GetMembershipForUser(victim.Id, rootPost.Id)
require.NoError(t, sErr, "victim should follow the thread after replying")
appErr = th.App.PermanentDeleteChannel(th.Context, privateChannel)
require.Nil(t, appErr)
_, gErr := th.App.Srv().Store().Thread().GetMembershipForUser(victim.Id, rootPost.Id)
var errNotFound *store.ErrNotFound
require.ErrorAs(t, gErr, &errNotFound, "thread membership must be deleted with the channel")
}
func TestAppUpdateTeamScheme(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic()
defer th.TearDown()
team := th.BasicTeam
mockID := model.NewPointer("x")
team.SchemeId = mockID
updatedTeam, appErr := th.App.UpdateTeamScheme(th.BasicTeam)
require.Nil(t, appErr)
require.Equal(t, mockID, updatedTeam.SchemeId, "Wrong Team SchemeId")
// Test that a newly applied team scheme applies the new permissions to a team member
err := th.App.SetPhase2PermissionsMigrationStatus(true)
require.NoError(t, err)
team2Scheme := th.SetupTeamScheme()
channelUser, appErr := th.App.GetRoleByName(context.Background(), team2Scheme.DefaultChannelUserRole)
require.Nil(t, appErr)
channelUser.Permissions = []string{}
_, appErr = th.App.UpdateRole(channelUser) // Remove all permissions from the team user role of the scheme
require.Nil(t, appErr)
channelAdmin, appErr := th.App.GetRoleByName(context.Background(), team2Scheme.DefaultChannelAdminRole)
require.Nil(t, appErr)
channelAdmin.Permissions = []string{}
_, appErr = th.App.UpdateRole(channelAdmin) // Remove all permissions from the team admin role of the scheme
require.Nil(t, appErr)
team2 := th.CreateTeam()
_, _, appErr = th.App.AddUserToTeam(th.Context, team2.Id, th.BasicUser.Id, "")
require.Nil(t, appErr)
channel := th.CreateChannel(th.Context, team2)
_, appErr = th.App.AddUserToChannel(th.Context, th.BasicUser, channel, true)
require.Nil(t, appErr)
session := model.Session{
Roles: model.SystemUserRoleId,
UserId: th.BasicUser.Id,
TeamMembers: []*model.TeamMember{
{
UserId: th.BasicUser.Id,
TeamId: team2.Id,
SchemeUser: true,
},
},
}
// ensure user can update channel properties before applying the scheme
ok, _ := th.App.SessionHasPermissionToChannel(th.Context, session, channel.Id, model.PermissionManagePublicChannelProperties)
require.True(t, ok)
// apply the team scheme
team2.SchemeId = &team2Scheme.Id
_, appErr = th.App.UpdateTeamScheme(team2)
require.Nil(t, appErr)
ok, _ = th.App.SessionHasPermissionToChannel(th.Context, session, channel.Id, model.PermissionManagePublicChannelProperties)
require.False(t, ok)
}
func TestGetTeamMembers(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic()
defer th.TearDown()
var users []model.User
users = append(users, *th.BasicUser)
users = append(users, *th.BasicUser2)
for i := 0; i < 8; i++ {
user := model.User{
Email: strings.ToLower(model.NewId()) + "success+test@example.com",
Username: fmt.Sprintf("user%v", i),
Password: "passwd1",
DeleteAt: int64(rand.Intn(2)),
}
ruser, err := th.App.CreateUser(th.Context, &user)
require.Nil(t, err)
require.NotNil(t, ruser)
defer func() {
appErr := th.App.PermanentDeleteUser(th.Context, &user)
require.Nil(t, appErr)
}()
_, _, err = th.App.AddUserToTeam(th.Context, th.BasicTeam.Id, ruser.Id, "")
require.Nil(t, err)
// Store the users for comparison later
users = append(users, *ruser)
}
t.Run("Ensure Sorted By Username when TeamMemberGet options is passed", func(t *testing.T) {
members, err := th.App.GetTeamMembers(th.BasicTeam.Id, 0, 100, &model.TeamMembersGetOptions{Sort: model.USERNAME})
require.Nil(t, err)
// Sort the users array by username
sort.Slice(users, func(i, j int) bool {
return users[i].Username < users[j].Username
})
// We should have the same number of users in both users and members array as we have not excluded any deleted members
require.Equal(t, len(users), len(members))
for i, member := range members {
assert.Equal(t, users[i].Id, member.UserId)
}
})
t.Run("Ensure ExcludedDeletedUsers when TeamMemberGetOptions is passed", func(t *testing.T) {
members, err := th.App.GetTeamMembers(th.BasicTeam.Id, 0, 100, &model.TeamMembersGetOptions{ExcludeDeletedUsers: true})
require.Nil(t, err)
// Choose all users who aren't deleted from our users array
var usersNotDeletedIDs []string
var membersIDs []string
for _, u := range users {
if u.DeleteAt == 0 {
usersNotDeletedIDs = append(usersNotDeletedIDs, u.Id)
}
}
for _, m := range members {
membersIDs = append(membersIDs, m.UserId)
}
require.Equal(t, len(usersNotDeletedIDs), len(membersIDs))
require.ElementsMatch(t, usersNotDeletedIDs, membersIDs)
})
t.Run("Ensure Sorted By Username and ExcludedDeletedUsers when TeamMemberGetOptions is passed", func(t *testing.T) {
members, err := th.App.GetTeamMembers(th.BasicTeam.Id, 0, 100, &model.TeamMembersGetOptions{Sort: model.USERNAME, ExcludeDeletedUsers: true})
require.Nil(t, err)
var usersNotDeleted []model.User
for _, u := range users {
if u.DeleteAt == 0 {
usersNotDeleted = append(usersNotDeleted, u)
}
}
// Sort our non deleted members by username
sort.Slice(usersNotDeleted, func(i, j int) bool {
return usersNotDeleted[i].Username < usersNotDeleted[j].Username
})
require.Equal(t, len(usersNotDeleted), len(members))
for i, member := range members {
assert.Equal(t, usersNotDeleted[i].Id, member.UserId)
}
})
t.Run("Ensure Sorted By User ID when no TeamMemberGetOptions is passed", func(t *testing.T) {
// Sort them by UserID because the result of GetTeamMembers() is also sorted
sort.Slice(users, func(i, j int) bool {
return users[i].Id < users[j].Id
})
// Fetch team members multiple times
members, err := th.App.GetTeamMembers(th.BasicTeam.Id, 0, 5, nil)
require.Nil(t, err)
// This should return 5 members
members2, err := th.App.GetTeamMembers(th.BasicTeam.Id, 5, 6, nil)
require.Nil(t, err)
members = append(members, members2...)
require.Equal(t, len(users), len(members))
for i, member := range members {
assert.Equal(t, users[i].Id, member.UserId)
}
})
}
func TestGetTeamStats(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic()
defer th.TearDown()
t.Run("without view restrictions", func(t *testing.T) {
teamStats, err := th.App.GetTeamStats(th.BasicTeam.Id, nil)
require.Nil(t, err)
require.NotNil(t, teamStats)
members, err := th.App.GetTeamMembers(th.BasicTeam.Id, 0, 5, nil)
require.Nil(t, err)
assert.Equal(t, int64(len(members)), teamStats.TotalMemberCount)
assert.Equal(t, int64(len(members)), teamStats.ActiveMemberCount)
})
t.Run("with view restrictions by this team", func(t *testing.T) {
restrictions := &model.ViewUsersRestrictions{Teams: []string{th.BasicTeam.Id}}
teamStats, err := th.App.GetTeamStats(th.BasicTeam.Id, restrictions)
require.Nil(t, err)
require.NotNil(t, teamStats)
members, err := th.App.GetTeamMembers(th.BasicTeam.Id, 0, 5, nil)
require.Nil(t, err)
assert.Equal(t, int64(len(members)), teamStats.TotalMemberCount)
assert.Equal(t, int64(len(members)), teamStats.ActiveMemberCount)
})
t.Run("with view restrictions by valid channel", func(t *testing.T) {
restrictions := &model.ViewUsersRestrictions{Teams: []string{}, Channels: []string{th.BasicChannel.Id}}
teamStats, err := th.App.GetTeamStats(th.BasicTeam.Id, restrictions)
require.Nil(t, err)
require.NotNil(t, teamStats)
members, err := th.App.GetChannelMembersPage(th.Context, th.BasicChannel.Id, 0, 5)
require.Nil(t, err)
assert.Equal(t, int64(len(members)), teamStats.TotalMemberCount)
assert.Equal(t, int64(len(members)), teamStats.ActiveMemberCount)
})
t.Run("with view restrictions to not see anything", func(t *testing.T) {
restrictions := &model.ViewUsersRestrictions{Teams: []string{}, Channels: []string{}}
teamStats, err := th.App.GetTeamStats(th.BasicTeam.Id, restrictions)
require.Nil(t, err)
require.NotNil(t, teamStats)
assert.Equal(t, int64(0), teamStats.TotalMemberCount)
assert.Equal(t, int64(0), teamStats.ActiveMemberCount)
})
t.Run("with view restrictions by other team", func(t *testing.T) {
restrictions := &model.ViewUsersRestrictions{Teams: []string{"other-team-id"}}
teamStats, err := th.App.GetTeamStats(th.BasicTeam.Id, restrictions)
require.Nil(t, err)
require.NotNil(t, teamStats)
assert.Equal(t, int64(0), teamStats.TotalMemberCount)
assert.Equal(t, int64(0), teamStats.ActiveMemberCount)
})
t.Run("with view restrictions by not-existing channel", func(t *testing.T) {
restrictions := &model.ViewUsersRestrictions{Teams: []string{}, Channels: []string{"test"}}
teamStats, err := th.App.GetTeamStats(th.BasicTeam.Id, restrictions)
require.Nil(t, err)
require.NotNil(t, teamStats)
assert.Equal(t, int64(0), teamStats.TotalMemberCount)
assert.Equal(t, int64(0), teamStats.ActiveMemberCount)
})
}
func TestUpdateTeamMemberRolesChangingGuest(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)
_, _, err := th.App.AddUserToTeam(th.Context, th.BasicTeam.Id, ruser.Id, "")
require.Nil(t, err)
_, err = th.App.UpdateTeamMemberRoles(th.Context, th.BasicTeam.Id, ruser.Id, "team_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.UpdateTeamMemberRoles(th.Context, th.BasicTeam.Id, ruser.Id, "team_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.UpdateTeamMemberRoles(th.Context, th.BasicTeam.Id, ruser.Id, "team_user team_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.CreateRole(&model.Role{Name: "custom", DisplayName: "custom", Description: "custom"})
require.Nil(t, err)
_, err = th.App.UpdateTeamMemberRoles(th.Context, th.BasicTeam.Id, ruser.Id, "team_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.UpdateTeamMemberRoles(th.Context, th.BasicTeam.Id, ruser.Id, "team_guest team_user")
require.NotNil(t, err, "Should work when you not modify guest role")
})
}
func TestInvalidateAllResendInviteEmailJobs(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t)
defer th.TearDown()
job, err := th.App.Srv().Jobs.CreateJob(th.Context, model.JobTypeResendInvitationEmail, map[string]string{})
require.Nil(t, err)
sysVar := &model.System{Name: job.Id, Value: "0"}
e := th.App.Srv().Store().System().SaveOrUpdate(sysVar)
require.NoError(t, e)
appErr := th.App.InvalidateAllResendInviteEmailJobs(th.Context)
require.Nil(t, appErr)
j, e := th.App.Srv().Store().Job().Get(th.Context, job.Id)
require.NoError(t, e)
require.Equal(t, j.Status, model.JobStatusCanceled)
_, sysValErr := th.App.Srv().Store().System().GetByName(job.Id)
var errNotFound *store.ErrNotFound
require.ErrorAs(t, sysValErr, &errNotFound)
}
func TestInvalidateAllEmailInvites(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t)
defer th.TearDown()
t1 := model.Token{
Token: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
CreateAt: model.GetMillis(),
Type: TokenTypeGuestInvitation,
Extra: "",
}
err := th.App.Srv().Store().Token().Save(&t1)
require.NoError(t, err)
t2 := model.Token{
Token: "yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy",
CreateAt: model.GetMillis(),
Type: TokenTypeTeamInvitation,
Extra: "",
}
err = th.App.Srv().Store().Token().Save(&t2)
require.NoError(t, err)
t3 := model.Token{
Token: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz",
CreateAt: model.GetMillis(),
Type: "other",
Extra: "",
}
err = th.App.Srv().Store().Token().Save(&t3)
require.NoError(t, err)
appErr := th.App.InvalidateAllEmailInvites(th.Context)
require.Nil(t, appErr)
_, err = th.App.Srv().Store().Token().GetByToken(t1.Token)
require.Error(t, err)
_, err = th.App.Srv().Store().Token().GetByToken(t2.Token)
require.Error(t, err)
_, err = th.App.Srv().Store().Token().GetByToken(t3.Token)
require.NoError(t, err)
}
func TestClearTeamMembersCache(t *testing.T) {
mainHelper.Parallel(t)
th := SetupWithStoreMock(t)
defer th.TearDown()
mockStore := th.App.Srv().Store().(*mocks.Store)
mockTeamStore := mocks.TeamStore{}
tms := []*model.TeamMember{}
for i := 0; i < 200; i++ {
tms = append(tms, &model.TeamMember{
TeamId: "1",
})
}
mockTeamStore.On("GetMembers", "teamID", 0, 100, mock.Anything).Return(tms, nil)
mockTeamStore.On("GetMembers", "teamID", 100, 100, mock.Anything).Return([]*model.TeamMember{{
TeamId: "1",
}}, nil)
mockStore.On("Team").Return(&mockTeamStore)
mockStore.On("GetDBSchemaVersion").Return(1, nil)
require.NoError(t, th.App.ClearTeamMembersCache("teamID"))
}
func TestInviteNewUsersToTeamGracefully(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic()
defer th.TearDown()
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.ServiceSettings.EnableEmailInvitations = true
})
t.Run("it return list of email with no error on success", func(t *testing.T) {
emailServiceMock := emailmocks.ServiceInterface{}
memberInvite := &model.MemberInvite{
Emails: []string{"idontexist@mattermost.com"},
}
emailServiceMock.On("SendInviteEmails",
mock.AnythingOfType("*model.Team"),
mock.AnythingOfType("string"),
mock.AnythingOfType("string"),
memberInvite.Emails,
"",
mock.Anything,
true,
false,
false,
).Once().Return(nil)
emailServiceMock.On("Stop").Once().Return()
th.App.Srv().EmailService = &emailServiceMock
res, err := th.App.InviteNewUsersToTeamGracefully(th.Context, memberInvite, th.BasicTeam.Id, th.BasicUser.Id, "")
require.Nil(t, err)
require.Len(t, res, 1)
require.Nil(t, res[0].Error)
})
t.Run("it should assign errors to emails when failing to send", func(t *testing.T) {
emailServiceMock := emailmocks.ServiceInterface{}
memberInvite := &model.MemberInvite{
Emails: []string{"idontexist@mattermost.com"},
}
emailServiceMock.On("SendInviteEmails",
mock.AnythingOfType("*model.Team"),
mock.AnythingOfType("string"),
mock.AnythingOfType("string"),
memberInvite.Emails,
"",
mock.Anything,
true,
false,
false,
).Once().Return(email.SendMailError)
emailServiceMock.On("Stop").Once().Return()
th.App.Srv().EmailService = &emailServiceMock
res, err := th.App.InviteNewUsersToTeamGracefully(th.Context, memberInvite, th.BasicTeam.Id, th.BasicUser.Id, "")
require.Nil(t, err)
require.Len(t, res, 1)
require.NotNil(t, res[0].Error)
})
t.Run("it return list of email with no error when inviting to team and channels using memberInvite struct", func(t *testing.T) {
emailServiceMock := emailmocks.ServiceInterface{}
memberInvite := &model.MemberInvite{
Emails: []string{"idontexist@mattermost.com"},
ChannelIds: []string{th.BasicChannel.Id},
}
emailServiceMock.On("SendInviteEmailsToTeamAndChannels",
mock.AnythingOfType("*model.Team"),
mock.AnythingOfType("[]*model.Channel"),
mock.AnythingOfType("string"),
mock.AnythingOfType("string"),
mock.AnythingOfType("[]uint8"),
memberInvite.Emails,
"",
mock.Anything,
mock.AnythingOfType("string"),
true,
false,
false,
).Once().Return([]*model.EmailInviteWithError{}, nil)
emailServiceMock.On("Stop").Once().Return()
th.App.Srv().EmailService = &emailServiceMock
res, err := th.App.InviteNewUsersToTeamGracefully(th.Context, memberInvite, th.BasicTeam.Id, th.BasicUser.Id, "")
require.Nil(t, err)
require.Len(t, res, 1)
require.Nil(t, res[0].Error)
})
t.Run("it return list of email with no error when inviting to team and channels using plain emails array", func(t *testing.T) {
emailServiceMock := emailmocks.ServiceInterface{}
memberInvite := &model.MemberInvite{
Emails: []string{"idontexist@mattermost.com"},
}
emailServiceMock.On("SendInviteEmails",
mock.AnythingOfType("*model.Team"),
mock.AnythingOfType("string"),
mock.AnythingOfType("string"),
[]string{"idontexist@mattermost.com"},
"",
mock.Anything,
true,
false,
false,
).Once().Return(nil)
emailServiceMock.On("Stop").Once().Return()
th.App.Srv().EmailService = &emailServiceMock
res, err := th.App.InviteNewUsersToTeamGracefully(th.Context, memberInvite, th.BasicTeam.Id, th.BasicUser.Id, "")
require.Nil(t, err)
require.Len(t, res, 1)
require.Nil(t, res[0].Error)
})
}
func TestInviteGuestsToChannelsGracefully(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic()
defer th.TearDown()
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.ServiceSettings.EnableEmailInvitations = true
})
t.Run("it return list of email with no error on success", func(t *testing.T) {
emailServiceMock := emailmocks.ServiceInterface{}
emailServiceMock.On("SendGuestInviteEmails",
mock.AnythingOfType("*model.Team"),
mock.AnythingOfType("[]*model.Channel"),
mock.AnythingOfType("string"),
mock.AnythingOfType("string"),
mock.AnythingOfType("[]uint8"),
[]string{"idontexist@mattermost.com"},
"",
"",
true,
false,
false,
).Once().Return(nil)
emailServiceMock.On("Stop").Once().Return()
th.App.Srv().EmailService = &emailServiceMock
res, err := th.App.InviteGuestsToChannelsGracefully(th.Context, th.BasicTeam.Id, &model.GuestsInvite{
Emails: []string{"idontexist@mattermost.com"},
Channels: []string{th.BasicChannel.Id},
}, th.BasicUser.Id)
require.Nil(t, err)
require.Len(t, res, 1)
require.Nil(t, res[0].Error)
})
t.Run("it should assign errors to emails when failing to send", func(t *testing.T) {
emailServiceMock := emailmocks.ServiceInterface{}
emailServiceMock.On("SendGuestInviteEmails",
mock.AnythingOfType("*model.Team"),
mock.AnythingOfType("[]*model.Channel"),
mock.AnythingOfType("string"),
mock.AnythingOfType("string"),
mock.AnythingOfType("[]uint8"),
[]string{"idontexist@mattermost.com"},
"",
"",
true,
false,
false,
).Once().Return(email.SendMailError)
emailServiceMock.On("Stop").Once().Return()
th.App.Srv().EmailService = &emailServiceMock
res, err := th.App.InviteGuestsToChannelsGracefully(th.Context, th.BasicTeam.Id, &model.GuestsInvite{
Emails: []string{"idontexist@mattermost.com"},
Channels: []string{th.BasicChannel.Id},
}, th.BasicUser.Id)
require.Nil(t, err)
require.Len(t, res, 1)
require.NotNil(t, res[0].Error)
})
}
func TestInviteGuestsToChannelsWithPolicyEnforced(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.ServiceSettings.EnableEmailInvitations = true
})
// Create a private channel
channel := th.CreatePrivateChannel(th.Context, th.BasicTeam)
// Create a policy with the same ID as the channel
channelPolicy := &model.AccessControlPolicy{
Type: model.AccessControlPolicyTypeChannel,
ID: channel.Id, // Use the channel ID directly
Name: "Test Channel Policy",
Revision: 1,
Version: model.AccessControlPolicyVersionV0_1,
Rules: []model.AccessControlPolicyRule{
{
Actions: []string{"view", "join_channel"},
Expression: "user.attributes.program == \"test-program\"",
},
},
}
// Save the channel policy
channelPolicy, err := th.App.Srv().Store().AccessControlPolicy().Save(th.Context, channelPolicy)
require.NoError(t, err)
require.NotNil(t, channelPolicy)
// Attempt to invite guests to the policy-enforced channel
guestsInvite := &model.GuestsInvite{
Emails: []string{"guest@example.com"},
Channels: []string{channel.Id},
Message: "test message",
}
// Call the function we want to test
_, _, _, appErr := th.App.prepareInviteGuestsToChannels(th.BasicTeam.Id, guestsInvite, th.BasicUser.Id)
// Verify that the appropriate error is returned
require.NotNil(t, appErr)
require.Equal(t, "api.team.invite_guests.policy_enforced_channel.app_error", appErr.Id)
}
func TestTeamSendEvents(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic()
defer th.TearDown()
testCluster := &testlib.FakeClusterInterface{}
th.Server.Platform().SetCluster(testCluster)
defer th.Server.Platform().SetCluster(nil)
team := th.CreateTeam()
testCluster.ClearMessages()
wsEvents := []model.WebsocketEventType{model.WebsocketEventUpdateTeam, model.WebsocketEventRestoreTeam, model.WebsocketEventDeleteTeam}
for _, wsEvent := range wsEvents {
appErr := th.App.sendTeamEvent(team, wsEvent)
require.Nil(t, appErr)
}
msgs := testCluster.GetMessages()
require.Len(t, msgs, len(wsEvents))
for _, msg := range msgs {
ev, err := model.WebSocketEventFromJSON(bytes.NewReader(msg.Data))
require.NoError(t, err)
// The event should be a team event.
require.Equal(t, team.Id, ev.GetBroadcast().TeamId)
// Make sure we're hiding the sensitive fields.
var teamFromEvent *model.Team
err = json.Unmarshal([]byte(ev.GetData()["team"].(string)), &teamFromEvent)
require.NoError(t, err)
require.Equal(t, team.Id, teamFromEvent.Id)
require.Equal(t, team.DisplayName, teamFromEvent.DisplayName)
require.Equal(t, team.Name, teamFromEvent.Name)
require.Equal(t, team.Description, teamFromEvent.Description)
require.Equal(t, "", teamFromEvent.Email)
require.Equal(t, "", teamFromEvent.InviteId)
}
}