Files
zeekay 8feb24ea1f One name for this fork, and it is ours
The server module named github.com/hanzoai/team/server/v8 — a path that
resolves to the retired workspace's repository, not this one — while
server/public still published github.com/mattermost/mattermost/server/public.
Both are github.com/hanzoteam/server/... now, which is where this code
actually lives.

That second half is what made the binary unbootable. Upstream's public path
stayed claimed by upstream, mattermost-plugin-ai imported it, and the build
linked public/model twice; public/plugin's gob.Register then ran twice under
one name and panicked at init, before main. Reverting the rename traded the
brand for a working binary. Rebranding the plugin alongside it needs no such
trade: the agents fork is github.com/hanzoteam/agents/v2 against our public
module, so no module named mattermost/mattermost is in the graph at all.

Measured: `go list -deps ./cmd/mattermost` names exactly one public/model, and
it is ours.
2026-08-28 23:35:36 -07:00

2660 lines
98 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/hanzoteam/server/server/public/model"
"github.com/hanzoteam/server/server/public/shared/request"
"github.com/hanzoteam/server/server/v8/channels/app/email"
emailmocks "github.com/hanzoteam/server/server/v8/channels/app/email/mocks"
"github.com/hanzoteam/server/server/v8/channels/app/teams"
"github.com/hanzoteam/server/server/v8/channels/app/users"
"github.com/hanzoteam/server/server/v8/channels/store"
"github.com/hanzoteam/server/server/v8/channels/store/sqlstore"
"github.com/hanzoteam/server/server/v8/channels/store/storetest/mocks"
"github.com/hanzoteam/server/server/v8/channels/testlib"
)
func TestCreateTeam(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic(t)
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(t)
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(t)
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")
updatedTeam.Name = "renamed-" + model.NewRandomTeamName()
updatedTeam.Description = "Updated description"
leakedEmail := "leak+" + model.NewId() + "@simulator.amazonses.com"
updatedTeam.Email = leakedEmail
updatedTeam.Type = model.TeamInvite
updatedTeam, err = th.App.UpdateTeam(updatedTeam)
require.Nil(t, err, "Should update the team name and sanitized fields")
require.Equal(t, "Updated description", updatedTeam.Description, "Wrong Team Description")
fetchedTeam, err := th.App.GetTeam(updatedTeam.Id)
require.Nil(t, err)
require.Equal(t, updatedTeam.Name, fetchedTeam.Name, "Wrong Team Name")
require.Equal(t, "Updated description", fetchedTeam.Description, "Wrong Team Description")
require.NotEqual(t, leakedEmail, fetchedTeam.Email, "Should not update email")
require.Equal(t, model.TeamOpen, fetchedTeam.Type, "Should not update type")
}
func TestAddUserToTeam(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic(t)
t.Run("add user", func(t *testing.T) {
user := model.User{Email: strings.ToLower(model.NewId()) + "[email protected]", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: model.NewTestPassword(), 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()) + "[email protected]", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: model.NewTestPassword(), 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()) + "[email protected]", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: model.NewTestPassword(), 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()) + "[email protected]", Nickname: "Darth Vader", Username: "vader" + model.NewId(), AuthService: "notnil", AuthData: new("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()) + "[email protected]", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: model.NewTestPassword(), 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()) + "[email protected]", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: model.NewTestPassword(), AuthService: ""}
ruser1, _ := th.App.CreateUser(th.Context, &user1)
user2 := model.User{Email: strings.ToLower(model.NewId()) + "[email protected]", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: model.NewTestPassword(), AuthService: ""}
ruser2, _ := th.App.CreateUser(th.Context, &user2)
user3 := model.User{Email: strings.ToLower(model.NewId()) + "[email protected]", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: model.NewTestPassword(), 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(t)
team := th.CreateTeam(t)
_, _, 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(t)
user := model.User{Email: strings.ToLower(model.NewId()) + "[email protected]", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: model.NewTestPassword(), AuthService: ""}
ruser, _ := th.App.CreateUser(th.Context, &user)
rguest := th.CreateGuest(t)
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(
model.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(
model.TokenTypeTeamInvitation,
model.MapToJSON(map[string]string{"teamId": th.BasicTeam.Id}),
)
token.CreateAt = model.GetMillis() - model.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(
model.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(
model.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(
model.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(
model.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(
model.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(
model.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(
model.TokenTypeGuestInvitation,
model.MapToJSON(map[string]string{"teamId": th.BasicTeam.Id, "channels": th.BasicChannel.Id}),
)
guestEmail := rguest.Email
rguest.Email = "[email protected]"
_, 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(
model.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(
model.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("team invitation token with mismatched email fails", func(t *testing.T) {
otherUser := th.CreateUser(t)
token := model.NewToken(
model.TokenTypeTeamInvitation,
model.MapToJSON(map[string]string{"teamId": th.BasicTeam.Id, "email": otherUser.Email}),
)
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 when the redeeming user's email does not match the token's email")
assert.Equal(t, "api.user.create_user.bad_token_email_data.app_error", err.Id)
})
t.Run("team invitation token with matching email succeeds", func(t *testing.T) {
matchingUser := th.CreateUser(t)
token := model.NewToken(
model.TokenTypeTeamInvitation,
model.MapToJSON(map[string]string{"teamId": th.BasicTeam.Id, "email": strings.ToUpper(matchingUser.Email)}),
)
require.NoError(t, th.App.Srv().Store().Token().Save(token))
_, _, err := th.App.AddUserToTeamByToken(th.Context, matchingUser.Id, token.Token)
require.Nil(t, err, "Should add user to the team when the email matches")
_, nErr := th.App.Srv().Store().Token().GetByToken(token.Token)
require.Error(t, nErr, "The token must be deleted after be used")
})
t.Run("guest invitation token with mismatched email fails", func(t *testing.T) {
otherGuest := th.CreateGuest(t)
token := model.NewToken(
model.TokenTypeGuestInvitation,
model.MapToJSON(map[string]string{"teamId": th.BasicTeam.Id, "channels": th.BasicChannel.Id, "email": otherGuest.Email}),
)
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, rguest.Id, token.Token)
require.NotNil(t, err, "Should fail when the redeeming guest's email does not match the token's email")
assert.Equal(t, "api.user.create_user.bad_token_email_data.app_error", err.Id)
})
t.Run("guest invitation token with matching email succeeds", func(t *testing.T) {
matchingGuest := th.CreateGuest(t)
token := model.NewToken(
model.TokenTypeGuestInvitation,
model.MapToJSON(map[string]string{"teamId": th.BasicTeam.Id, "channels": th.BasicChannel.Id, "email": matchingGuest.Email}),
)
require.NoError(t, th.App.Srv().Store().Token().Save(token))
_, _, err := th.App.AddUserToTeamByToken(th.Context, matchingGuest.Id, token.Token)
require.Nil(t, err, "Should add guest to the team when the email matches")
_, 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, matchingGuest.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 = new(true)
_, err := th.App.UpdateTeam(th.BasicTeam)
require.Nil(t, err, "Should update the team")
token := model.NewToken(
model.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 = new(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")
defer func() {
th.BasicTeam.AllowedDomains = ""
_, _ = th.App.UpdateTeam(th.BasicTeam)
}()
user := model.User{Email: strings.ToLower(model.NewId()) + "[email protected]", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: model.NewTestPassword(), AuthService: ""}
ruser, _ := th.App.CreateUser(th.Context, &user)
defer func() {
appErr := th.App.PermanentDeleteUser(th.Context, &user)
require.Nil(t, appErr)
}()
token := model.NewToken(
model.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(t)
team := th.CreateTeam(t)
token := model.NewToken(
model.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)
})
// Tests for Magic link Invitation tokens
t.Run("valid magic link invitation token adds guest to team and channels", func(t *testing.T) {
guest := th.CreateGuest(t)
channel1 := th.CreateChannel(t, th.BasicTeam)
channel2 := th.CreateChannel(t, th.BasicTeam)
tokenData := map[string]string{
"teamId": th.BasicTeam.Id,
"channels": channel1.Id + " " + channel2.Id,
"email": guest.Email,
"guest": "true",
"senderId": th.BasicUser.Id,
}
token := model.NewToken(
model.TokenTypeGuestMagicLinkInvitation,
model.MapToJSON(tokenData),
)
require.NoError(t, th.App.Srv().Store().Token().Save(token))
_, _, err := th.App.AddUserToTeamByToken(th.Context, guest.Id, token.Token)
require.Nil(t, err, "Should add guest to team successfully")
// Verify token was deleted
_, nErr := th.App.Srv().Store().Token().GetByToken(token.Token)
require.Error(t, nErr, "Token should be deleted after use")
// Verify guest was added to specified channels
members, chanErr := th.App.GetChannelMembersForUser(th.Context, th.BasicTeam.Id, guest.Id)
require.Nil(t, chanErr)
channelIds := make(map[string]bool)
for _, member := range members {
channelIds[member.ChannelId] = true
}
assert.True(t, channelIds[channel1.Id], "Guest should be in channel1")
assert.True(t, channelIds[channel2.Id], "Guest should be in channel2")
})
t.Run("Guest magic link invitation token with expired timestamp fails", func(t *testing.T) {
guest := th.CreateGuest(t)
tokenData := map[string]string{
"teamId": th.BasicTeam.Id,
"channels": th.BasicChannel.Id,
"email": guest.Email,
"guest": "true",
"senderId": th.BasicUser.Id,
}
token := model.NewToken(
model.TokenTypeGuestMagicLinkInvitation,
model.MapToJSON(tokenData),
)
// Make token expired (48 hours + 1ms)
token.CreateAt = model.GetMillis() - model.InvitationExpiryTime - 1
require.NoError(t, th.App.Srv().Store().Token().Save(token))
_, _, err := th.App.AddUserToTeamByToken(th.Context, guest.Id, token.Token)
require.NotNil(t, err, "Should fail on expired guest magic link token")
assert.Equal(t, "api.user.create_user.signup_link_expired.app_error", err.Id)
})
t.Run("Guest magic link invitation token should not add regular user", func(t *testing.T) {
regularUser := th.CreateUser(t)
tokenData := map[string]string{
"teamId": th.BasicTeam.Id,
"channels": th.BasicChannel.Id,
"email": regularUser.Email,
"guest": "true",
"senderId": th.BasicUser.Id,
}
token := model.NewToken(
model.TokenTypeGuestMagicLinkInvitation,
model.MapToJSON(tokenData),
)
require.NoError(t, th.App.Srv().Store().Token().Save(token))
// Regular users cannot use guest magic link tokens (they're guest-only)
_, _, err := th.App.AddUserToTeamByToken(th.Context, regularUser.Id, token.Token)
require.NotNil(t, err, "Should fail when adding regular user with guest magic link token")
})
t.Run("guest magic link invitation token with mismatched email fails", func(t *testing.T) {
otherGuest := th.CreateGuest(t)
tokenData := map[string]string{
"teamId": th.BasicTeam.Id,
"channels": th.BasicChannel.Id,
"email": otherGuest.Email,
"guest": "true",
"senderId": th.BasicUser.Id,
}
token := model.NewToken(
model.TokenTypeGuestMagicLinkInvitation,
model.MapToJSON(tokenData),
)
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, rguest.Id, token.Token)
require.NotNil(t, err, "Should fail when the redeeming guest's email does not match the token's email")
assert.Equal(t, "api.user.create_user.bad_token_email_data.app_error", err.Id)
})
}
func TestAddUserToTeamByTeamId(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic(t)
t.Run("add user", func(t *testing.T) {
user := model.User{Email: strings.ToLower(model.NewId()) + "[email protected]", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: model.NewTestPassword(), 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")
defer func() {
th.BasicTeam.AllowedDomains = ""
_, _ = th.App.UpdateTeam(th.BasicTeam)
}()
user := model.User{Email: strings.ToLower(model.NewId()) + "[email protected]", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: model.NewTestPassword(), 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(t)
teams := []*model.Team{
{
DisplayName: "team-1",
Name: "team-1",
Email: "[email protected]",
Type: model.TeamOpen,
},
{
DisplayName: "team-2",
Name: "team-2",
Email: "[email protected]",
Type: model.TeamOpen,
},
{
DisplayName: "team-3",
Name: "team-3",
Email: "[email protected]",
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(t)
team, err := th.App.CreateTeam(th.Context, &model.Team{
DisplayName: "deletion-test",
Name: "deletion-test",
Email: "[email protected]",
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(t)
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 TestPermanentDeleteTeamRemovesSpaceChannels(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic(t)
team := th.CreateTeam(t)
// Space backing channels are excluded from GetTeamChannels, so team teardown must clean them
// up through the dedicated path or they orphan with a dead TeamId.
space, nErr := th.App.Srv().Store().Channel().Save(th.Context, &model.Channel{
TeamId: team.Id,
DisplayName: "Space",
Name: "space-" + model.NewId(),
Type: model.ChannelTypeSpace,
}, -1)
require.NoError(t, nErr)
_, nErr = th.App.Srv().Store().Channel().SaveMember(th.Context, &model.ChannelMember{
ChannelId: space.Id,
UserId: th.BasicUser.Id,
NotifyProps: model.GetDefaultChannelNotifyProps(),
SchemeUser: true,
})
require.NoError(t, nErr)
// Sanity: the space backing channel resolves through the typed getter before deletion.
_, appErr := th.App.GetChannelOfType(th.Context, space.Id, model.ChannelTypeSpace)
require.Nil(t, appErr)
appErr = th.App.PermanentDeleteTeam(th.Context, team)
require.Nil(t, appErr)
// Assert through the typed getter: generic Get already excludes spaces, so it returns
// not-found whether or not the row was deleted and would pass vacuously.
_, getErr := th.App.GetChannelOfType(th.Context, space.Id, model.ChannelTypeSpace)
require.NotNil(t, getErr, "space backing channel should be permanently deleted with its team")
_, memErr := th.App.Srv().Store().Channel().GetMember(th.Context, space.Id, th.BasicUser.Id)
require.Error(t, memErr, "space channel membership should be removed with its team")
}
func TestLeaveTeamRemovesSpaceMemberships(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic(t)
// Space backing channels are excluded from GetChannels, so a plain team leave leaves their
// membership rows behind unless LeaveTeam removes them explicitly.
space, nErr := th.App.Srv().Store().Channel().Save(th.Context, &model.Channel{
TeamId: th.BasicTeam.Id,
DisplayName: "Space",
Name: "space-" + model.NewId(),
Type: model.ChannelTypeSpace,
}, -1)
require.NoError(t, nErr)
_, nErr = th.App.Srv().Store().Channel().SaveMember(th.Context, &model.ChannelMember{
ChannelId: space.Id,
UserId: th.BasicUser.Id,
NotifyProps: model.GetDefaultChannelNotifyProps(),
SchemeUser: true,
})
require.NoError(t, nErr)
appErr := th.App.LeaveTeam(th.Context, th.BasicTeam, th.BasicUser, th.BasicUser.Id)
require.Nil(t, appErr)
_, memErr := th.App.Srv().Store().Channel().GetMember(th.Context, space.Id, th.BasicUser.Id)
require.Error(t, memErr, "space channel membership should be removed when the user leaves the team")
}
func TestSanitizeTeam(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t)
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)
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)
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()) + "[email protected]", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: model.NewTestPassword(), 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()) + "[email protected]", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: model.NewTestPassword(), AuthService: ""}
ruser1, _ := th.App.CreateUser(th.Context, &user1)
user2 := model.User{Email: strings.ToLower(model.NewId()) + "[email protected]", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: model.NewTestPassword(), 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()) + "[email protected]", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: model.NewTestPassword(), AuthService: ""}
ruser1, _ := th.App.CreateUser(th.Context, &user1)
user2 := model.User{Email: strings.ToLower(model.NewId()) + "[email protected]", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: model.NewTestPassword(), 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()) + "[email protected]", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: model.NewTestPassword(), AuthService: ""}
ruser1, _ := th.App.CreateUser(th.Context, &user1)
defer func() {
appErr := th.App.PermanentDeleteUser(th.Context, &user1)
require.Nil(t, appErr)
}()
group := th.CreateGroup(t)
_, 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 = new(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()) + "[email protected]", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: model.NewTestPassword(), 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)
mockStore := th.App.Srv().Store().(*mocks.Store)
mockUserStore := mocks.UserStore{}
mockUserStore.On("Get", mock.Anything, "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)
mockChannelStore.On("GetTeamSpaceChannelsForUser", "myteam", "userID").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(t)
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(t, th.BasicTeam)
th.AddUserToChannel(t, 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(t)
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(t, th.BasicTeam)
privateB := th.CreatePrivateChannel(t, th.BasicTeam)
openC := th.CreateChannel(t, th.BasicTeam)
th.AddUserToChannel(t, victim, privateA)
th.AddUserToChannel(t, victim, privateB)
th.AddUserToChannel(t, 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(t)
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(t)
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(t, th.BasicTeam)
th.AddUserToChannel(t, 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(th.Context, 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(t)
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(t, th.BasicTeam)
th.AddUserToChannel(t, 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(t)
team := th.BasicTeam
mockID := new("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(t)
channelUser, appErr := th.App.GetRoleByName(th.Context, 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(th.Context, 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(t)
_, _, appErr = th.App.AddUserToTeam(th.Context, team2.Id, th.BasicUser.Id, "")
require.Nil(t, appErr)
channel := th.CreateChannel(t, 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(t)
var users []model.User
users = append(users, *th.BasicUser)
users = append(users, *th.BasicUser2)
for i := range 8 {
user := model.User{
Email: strings.ToLower(model.NewId()) + "[email protected]",
Username: fmt.Sprintf("user%v", i),
Password: model.NewTestPassword(),
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(t)
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(t)
t.Run("from guest to user", func(t *testing.T) {
user := model.User{Email: strings.ToLower(model.NewId()) + "[email protected]", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: model.NewTestPassword(), 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()) + "[email protected]", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: model.NewTestPassword(), 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()) + "[email protected]", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: model.NewTestPassword(), 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()) + "[email protected]", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: model.NewTestPassword(), 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()) + "[email protected]", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: model.NewTestPassword(), 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 TestUpdateTeamMemberRolesRequireUser(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic(t)
t.Run("empty roles string requires user or guest scheme role", func(t *testing.T) {
user := model.User{Email: strings.ToLower(model.NewId()) + "[email protected]", Nickname: "Tester", Username: "tester" + model.NewId(), Password: model.NewTestPassword(), AuthService: ""}
ruser, _ := th.App.CreateUser(th.Context, &user)
_, _, err := th.App.AddUserToTeam(th.Context, th.BasicTeam.Id, ruser.Id, "")
require.Nil(t, err)
member, err := th.App.GetTeamMember(th.Context, th.BasicTeam.Id, ruser.Id)
require.Nil(t, err)
require.True(t, member.SchemeUser)
require.False(t, member.SchemeGuest)
_, err = th.App.UpdateTeamMemberRoles(th.Context, th.BasicTeam.Id, ruser.Id, "")
require.NotNil(t, err)
require.Equal(t, "api.team.update_team_member_roles.unset_user_scheme.app_error", err.Id)
})
t.Run("admin role requires user or guest scheme role", func(t *testing.T) {
user := model.User{Email: strings.ToLower(model.NewId()) + "[email protected]", Nickname: "Tester", Username: "tester" + model.NewId(), Password: model.NewTestPassword(), 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_admin")
require.NotNil(t, err)
require.Equal(t, "api.team.update_team_member_roles.unset_user_scheme.app_error", err.Id)
})
t.Run("valid user and admin roles update succeeds", func(t *testing.T) {
user := model.User{Email: strings.ToLower(model.NewId()) + "[email protected]", Nickname: "Tester", Username: "tester" + model.NewId(), Password: model.NewTestPassword(), AuthService: ""}
ruser, _ := th.App.CreateUser(th.Context, &user)
_, _, err := th.App.AddUserToTeam(th.Context, th.BasicTeam.Id, ruser.Id, "")
require.Nil(t, err)
updatedMember, err := th.App.UpdateTeamMemberRoles(th.Context, th.BasicTeam.Id, ruser.Id, "team_user team_admin")
require.Nil(t, err)
require.True(t, updatedMember.SchemeUser)
require.True(t, updatedMember.SchemeAdmin)
require.False(t, updatedMember.SchemeGuest)
})
t.Run("removing admin role while keeping user role succeeds", func(t *testing.T) {
user := model.User{Email: strings.ToLower(model.NewId()) + "[email protected]", Nickname: "Tester", Username: "tester" + model.NewId(), Password: model.NewTestPassword(), 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)
updatedMember, err := th.App.UpdateTeamMemberRoles(th.Context, th.BasicTeam.Id, ruser.Id, "team_user")
require.Nil(t, err)
require.True(t, updatedMember.SchemeUser)
require.False(t, updatedMember.SchemeAdmin)
})
t.Run("team_post_all alone should fail", func(t *testing.T) {
user := model.User{Email: strings.ToLower(model.NewId()) + "[email protected]", Nickname: "Tester", Username: "tester" + model.NewId(), Password: model.NewTestPassword(), 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_post_all")
require.NotNil(t, err)
require.Equal(t, "api.team.update_team_member_roles.unset_user_scheme.app_error", err.Id)
})
t.Run("team_post_all_public alone should fail", func(t *testing.T) {
user := model.User{Email: strings.ToLower(model.NewId()) + "[email protected]", Nickname: "Tester", Username: "tester" + model.NewId(), Password: model.NewTestPassword(), 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_post_all_public")
require.NotNil(t, err)
require.Equal(t, "api.team.update_team_member_roles.unset_user_scheme.app_error", err.Id)
})
t.Run("system_post_all alone should fail", func(t *testing.T) {
user := model.User{Email: strings.ToLower(model.NewId()) + "[email protected]", Nickname: "Tester", Username: "tester" + model.NewId(), Password: model.NewTestPassword(), 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, "system_post_all")
require.NotNil(t, err)
require.Equal(t, "api.team.update_team_member_roles.unset_user_scheme.app_error", err.Id)
})
t.Run("system_user_manager alone should fail", func(t *testing.T) {
user := model.User{Email: strings.ToLower(model.NewId()) + "[email protected]", Nickname: "Tester", Username: "tester" + model.NewId(), Password: model.NewTestPassword(), 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, "system_user_manager")
require.NotNil(t, err)
require.Equal(t, "api.team.update_team_member_roles.unset_user_scheme.app_error", err.Id)
})
t.Run("multiple non-scheme-managed roles without user scheme should fail", func(t *testing.T) {
user := model.User{Email: strings.ToLower(model.NewId()) + "[email protected]", Nickname: "Tester", Username: "tester" + model.NewId(), Password: model.NewTestPassword(), 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_post_all team_post_all_public system_post_all")
require.NotNil(t, err)
require.Equal(t, "api.team.update_team_member_roles.unset_user_scheme.app_error", err.Id)
})
t.Run("team_post_all with team_user should succeed", func(t *testing.T) {
user := model.User{Email: strings.ToLower(model.NewId()) + "[email protected]", Nickname: "Tester", Username: "tester" + model.NewId(), Password: model.NewTestPassword(), AuthService: ""}
ruser, _ := th.App.CreateUser(th.Context, &user)
_, _, err := th.App.AddUserToTeam(th.Context, th.BasicTeam.Id, ruser.Id, "")
require.Nil(t, err)
updatedMember, err := th.App.UpdateTeamMemberRoles(th.Context, th.BasicTeam.Id, ruser.Id, "team_user team_post_all")
require.Nil(t, err)
require.True(t, updatedMember.SchemeUser)
require.Contains(t, updatedMember.ExplicitRoles, "team_post_all")
})
t.Run("team_post_all_public with team_user should succeed", func(t *testing.T) {
user := model.User{Email: strings.ToLower(model.NewId()) + "[email protected]", Nickname: "Tester", Username: "tester" + model.NewId(), Password: model.NewTestPassword(), AuthService: ""}
ruser, _ := th.App.CreateUser(th.Context, &user)
_, _, err := th.App.AddUserToTeam(th.Context, th.BasicTeam.Id, ruser.Id, "")
require.Nil(t, err)
updatedMember, err := th.App.UpdateTeamMemberRoles(th.Context, th.BasicTeam.Id, ruser.Id, "team_user team_post_all_public")
require.Nil(t, err)
require.True(t, updatedMember.SchemeUser)
require.Contains(t, updatedMember.ExplicitRoles, "team_post_all_public")
})
t.Run("multiple explicit roles with team_user should succeed", func(t *testing.T) {
user := model.User{Email: strings.ToLower(model.NewId()) + "[email protected]", Nickname: "Tester", Username: "tester" + model.NewId(), Password: model.NewTestPassword(), AuthService: ""}
ruser, _ := th.App.CreateUser(th.Context, &user)
_, _, err := th.App.AddUserToTeam(th.Context, th.BasicTeam.Id, ruser.Id, "")
require.Nil(t, err)
updatedMember, err := th.App.UpdateTeamMemberRoles(th.Context, th.BasicTeam.Id, ruser.Id, "team_user team_post_all team_post_all_public")
require.Nil(t, err)
require.True(t, updatedMember.SchemeUser)
require.Contains(t, updatedMember.ExplicitRoles, "team_post_all")
require.Contains(t, updatedMember.ExplicitRoles, "team_post_all_public")
})
t.Run("explicit role with admin should succeed", func(t *testing.T) {
user := model.User{Email: strings.ToLower(model.NewId()) + "[email protected]", Nickname: "Tester", Username: "tester" + model.NewId(), Password: model.NewTestPassword(), AuthService: ""}
ruser, _ := th.App.CreateUser(th.Context, &user)
_, _, err := th.App.AddUserToTeam(th.Context, th.BasicTeam.Id, ruser.Id, "")
require.Nil(t, err)
updatedMember, err := th.App.UpdateTeamMemberRoles(th.Context, th.BasicTeam.Id, ruser.Id, "team_user team_admin team_post_all")
require.Nil(t, err)
require.True(t, updatedMember.SchemeUser)
require.True(t, updatedMember.SchemeAdmin)
require.Contains(t, updatedMember.ExplicitRoles, "team_post_all")
})
}
func TestInvalidateAllResendInviteEmailJobs(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t)
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)
t1 := model.Token{
Token: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
CreateAt: model.GetMillis(),
Type: model.TokenTypeGuestInvitation,
Extra: "",
}
err := th.App.Srv().Store().Token().Save(&t1)
require.NoError(t, err)
t2 := model.Token{
Token: "yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy",
CreateAt: model.GetMillis(),
Type: model.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)
mockStore := th.App.Srv().Store().(*mocks.Store)
mockTeamStore := mocks.TeamStore{}
tms := []*model.TeamMember{}
for range 200 {
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(t)
th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterprise))
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.ServiceSettings.EnableEmailInvitations = true
*cfg.TeamSettings.LockProfileFieldsForEmailUsers = model.TeamSettingsLockProfileFieldsNameAndUsername
})
inviteDataMatches := func(memberInvite *model.MemberInvite) any {
return mock.MatchedBy(func(inviteData email.InviteEmailData) bool {
if inviteData.Team.Id != th.BasicTeam.Id ||
inviteData.ErrorWhenNotSent != true ||
len(inviteData.Invites) != len(memberInvite.Emails) ||
len(inviteData.Channels) != len(memberInvite.ChannelIds) ||
len(inviteData.Profiles) != len(memberInvite.Profiles) {
return false
}
for i, invite := range inviteData.Invites {
if invite != memberInvite.Emails[i] {
return false
}
}
for _, profile := range memberInvite.Profiles {
if inviteData.Profiles[profile.Email] != profile {
return false
}
}
return 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{"[email protected]"},
}
emailServiceMock.On("SendInviteEmails",
mock.Anything,
inviteDataMatches(memberInvite),
).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{"[email protected]"},
}
emailServiceMock.On("SendInviteEmails",
mock.Anything,
inviteDataMatches(memberInvite),
).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{"[email protected]"},
ChannelIds: []string{th.BasicChannel.Id},
}
emailServiceMock.On("SendInviteEmailsToTeamAndChannels",
mock.Anything,
inviteDataMatches(memberInvite),
).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{"[email protected]"},
}
emailServiceMock.On("SendInviteEmails",
mock.Anything,
inviteDataMatches(memberInvite),
).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 passes the invite profiles keyed by email to the email service", func(t *testing.T) {
emailServiceMock := emailmocks.ServiceInterface{}
memberInvite := &model.MemberInvite{
Emails: []string{"[email protected]"},
Profiles: []*model.MemberInviteProfile{{
Email: "[email protected]",
Username: "un_" + model.NewId(),
FirstName: "Pre",
LastName: "Set",
}},
}
emailServiceMock.On("SendInviteEmails",
mock.Anything,
inviteDataMatches(memberInvite),
).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 fails the email gracefully when the pre-set username is taken", func(t *testing.T) {
emailServiceMock := emailmocks.ServiceInterface{}
memberInvite := &model.MemberInvite{
Emails: []string{"[email protected]"},
Profiles: []*model.MemberInviteProfile{{
Email: "[email protected]",
Username: th.BasicUser2.Username,
}},
}
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)
require.Equal(t, "api.team.invite_members.username_taken.app_error", res[0].Error.Id)
emailServiceMock.AssertNotCalled(t, "SendInviteEmails")
})
t.Run("it returns error for deactivated user without sending email", func(t *testing.T) {
emailServiceMock := emailmocks.ServiceInterface{}
emailServiceMock.On("Stop").Once().Return()
th.App.Srv().EmailService = &emailServiceMock
_, appErr := th.App.UpdateActive(th.Context, th.BasicUser2, false)
require.Nil(t, appErr)
t.Cleanup(func() {
_, _ = th.App.UpdateActive(th.Context, th.BasicUser2, true)
})
memberInvite := &model.MemberInvite{
Emails: []string{th.BasicUser2.Email},
}
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)
require.Equal(t, "api.team.invite_members.account_deactivated.app_error", res[0].Error.Id)
emailServiceMock.AssertNotCalled(t, "SendInviteEmails")
})
}
func TestInviteNewUsersToTeam(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic(t)
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.ServiceSettings.EnableEmailInvitations = true
})
t.Run("it returns error for deactivated user without sending email", func(t *testing.T) {
emailServiceMock := emailmocks.ServiceInterface{}
emailServiceMock.On("Stop").Once().Return()
th.App.Srv().EmailService = &emailServiceMock
_, appErr := th.App.UpdateActive(th.Context, th.BasicUser2, false)
require.Nil(t, appErr)
t.Cleanup(func() {
_, _ = th.App.UpdateActive(th.Context, th.BasicUser2, true)
})
appErr = th.App.InviteNewUsersToTeam(th.Context, []string{th.BasicUser2.Email}, th.BasicTeam.Id, th.BasicUser.Id)
require.NotNil(t, appErr)
require.Equal(t, "api.team.invite_members.account_deactivated.app_error", appErr.Id)
emailServiceMock.AssertNotCalled(t, "SendInviteEmails")
})
}
func TestInviteGuestsToChannelsGracefully(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic(t)
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.Anything,
mock.AnythingOfType("*model.Team"),
mock.AnythingOfType("[]*model.Channel"),
mock.AnythingOfType("string"),
mock.AnythingOfType("string"),
mock.AnythingOfType("[]uint8"),
[]string{"[email protected]"},
"",
"",
true,
false,
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{"[email protected]"},
Channels: []string{th.BasicChannel.Id},
}, th.BasicUser.Id, false)
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.Anything,
mock.AnythingOfType("*model.Team"),
mock.AnythingOfType("[]*model.Channel"),
mock.AnythingOfType("string"),
mock.AnythingOfType("string"),
mock.AnythingOfType("[]uint8"),
[]string{"[email protected]"},
"",
"",
true,
false,
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{"[email protected]"},
Channels: []string{th.BasicChannel.Id},
}, th.BasicUser.Id, false)
require.Nil(t, err)
require.Len(t, res, 1)
require.NotNil(t, res[0].Error)
})
t.Run("it returns error for deactivated user without sending guest invite email", func(t *testing.T) {
emailServiceMock := emailmocks.ServiceInterface{}
emailServiceMock.On("Stop").Once().Return()
th.App.Srv().EmailService = &emailServiceMock
_, appErr := th.App.UpdateActive(th.Context, th.BasicUser2, false)
require.Nil(t, appErr)
t.Cleanup(func() {
_, _ = th.App.UpdateActive(th.Context, th.BasicUser2, true)
})
res, err := th.App.InviteGuestsToChannelsGracefully(th.Context, th.BasicTeam.Id, &model.GuestsInvite{
Emails: []string{th.BasicUser2.Email},
Channels: []string{th.BasicChannel.Id},
}, th.BasicUser.Id, false)
require.Nil(t, err)
require.Len(t, res, 1)
require.NotNil(t, res[0].Error)
require.Equal(t, "api.team.invite_members.account_deactivated.app_error", res[0].Error.Id)
emailServiceMock.AssertNotCalled(t, "SendGuestInviteEmails", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything)
})
}
func TestInviteGuestsToChannels(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic(t)
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.ServiceSettings.EnableEmailInvitations = true
})
t.Run("it returns error for deactivated user without sending guest invite email", func(t *testing.T) {
emailServiceMock := emailmocks.ServiceInterface{}
emailServiceMock.On("Stop").Once().Return()
th.App.Srv().EmailService = &emailServiceMock
_, appErr := th.App.UpdateActive(th.Context, th.BasicUser2, false)
require.Nil(t, appErr)
t.Cleanup(func() {
_, _ = th.App.UpdateActive(th.Context, th.BasicUser2, true)
})
appErr = th.App.InviteGuestsToChannels(th.Context, th.BasicTeam.Id, &model.GuestsInvite{
Emails: []string{th.BasicUser2.Email},
Channels: []string{th.BasicChannel.Id},
}, th.BasicUser.Id, false)
require.NotNil(t, appErr)
require.Equal(t, "api.team.invite_members.account_deactivated.app_error", appErr.Id)
emailServiceMock.AssertNotCalled(t, "SendGuestInviteEmails", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything)
})
}
func TestInviteGuestsToChannelsWithPolicyEnforced(t *testing.T) {
th := Setup(t).InitBasic(t)
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.ServiceSettings.EnableEmailInvitations = true
})
t.Run("membership-policy channel is rejected", func(t *testing.T) {
channel := th.CreatePrivateChannel(t, th.BasicTeam)
channelPolicy := &model.AccessControlPolicy{
Type: model.AccessControlPolicyTypeChannel,
ID: channel.Id,
Name: "Test Channel Policy",
Revision: 1,
Version: model.AccessControlPolicyVersionV0_2,
Rules: []model.AccessControlPolicyRule{
{
Actions: []string{model.AccessControlPolicyActionMembership},
Expression: "user.attributes.program == \"test-program\"",
},
},
}
channelPolicy, err := th.App.Srv().Store().AccessControlPolicy().Save(th.Context, channelPolicy)
require.NoError(t, err)
require.NotNil(t, channelPolicy)
t.Cleanup(func() {
_ = th.App.Srv().Store().AccessControlPolicy().Delete(th.Context, channel.Id)
})
guestsInvite := &model.GuestsInvite{
Emails: []string{"[email protected]"},
Channels: []string{channel.Id},
Message: "test message",
}
_, _, _, appErr := th.App.prepareInviteGuestsToChannels(th.Context, th.BasicTeam.Id, guestsInvite, th.BasicUser.Id)
require.NotNil(t, appErr)
require.Equal(t, "api.team.invite_guests.policy_enforced_channel.app_error", appErr.Id)
})
t.Run("permission-only-policy channel is NOT rejected (bug fix)", func(t *testing.T) {
// Channel carrying ONLY a permission policy (e.g.
// upload_file_attachment) reports policy_enforced=true but has no
// membership action. The guest-invite gate now consults
// PolicyActions[membership] specifically and must accept the
// invite — failing this assertion means the bug has regressed.
channel := th.CreatePrivateChannel(t, th.BasicTeam)
channelPolicy := &model.AccessControlPolicy{
Type: model.AccessControlPolicyTypeChannel,
ID: channel.Id,
Name: "Permission Only Policy",
Revision: 1,
Version: model.AccessControlPolicyVersionV0_2,
Rules: []model.AccessControlPolicyRule{
{
Actions: []string{model.AccessControlPolicyActionUploadFileAttachment},
Expression: "user.attributes.program == \"test-program\"",
},
},
}
_, err := th.App.Srv().Store().AccessControlPolicy().Save(th.Context, channelPolicy)
require.NoError(t, err)
t.Cleanup(func() {
_ = th.App.Srv().Store().AccessControlPolicy().Delete(th.Context, channel.Id)
})
guestsInvite := &model.GuestsInvite{
Emails: []string{"[email protected]"},
Channels: []string{channel.Id},
Message: "test message",
}
_, _, _, appErr := th.App.prepareInviteGuestsToChannels(th.Context, th.BasicTeam.Id, guestsInvite, th.BasicUser.Id)
require.Nil(t, appErr, "guest invite must succeed for a permission-only-policy channel")
})
}
func TestTeamSendEvents(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic(t)
testCluster := &testlib.FakeClusterInterface{}
th.Server.Platform().SetCluster(testCluster)
defer th.Server.Platform().SetCluster(nil)
team := th.CreateTeam(t)
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)
}
}