mattermost/server/channels/app/syncables_test.go
Jesse Hallam e3fbf8711f
MM-68149: Upgrade to Go 1.26.2 (#36418)
* MM-68149: upgrade to Go 1.26.2

Update go directive in go.mod and .go-version.

* MM-68149: replace pointer helpers with Go 1.26 new()

Go 1.26 extends the built-in new() to accept an initial value expression,
making typed-pointer helpers like model.NewPointer(x), bToP(x), and boolPtr(x)
redundant. Replace every call site with new(x) and remove the now-unused
helper functions and their //go:fix inline directives.

* MM-68149: apply go fix for reflect API and format-string changes

- reflect.Ptr → reflect.Pointer (renamed in Go 1.18, deprecated alias removed in 1.26)
- reflect range-over-struct: for i := 0; i < t.NumField(); i++ → for field := range t.Fields()
  and the equivalent for Methods() and interface types
- Fix format-string concatenation and variadic-arg mismatches flagged by go vet

* MM-68149: update JPEG fixtures and test infrastructure for Go 1.26 encoder

Go 1.26 ships a new image/jpeg encoder that produces slightly different output.
Regenerate all JPEG fixture files and switch the comparison helpers from
byte-equality to pixel-level comparison with a small per-channel tolerance,
so minor encoder drift across patch versions is handled automatically.

Add -update-fixtures flag to make it easy to regenerate fixtures after future
major Go upgrades. Document the update procedure in tests/README.md.

* MM-68149: CI check that go fix ./... produces no changes

* Fix real bugs flagged by CodeRabbit review

- group.go: set newGroup.MemberCount not group.MemberCount (member count
  was populated on the wrong variable and lost before publish/return)
- file_test.go: guard compareImage(GetFilePreview) on the preview slice
  length, not the thumbnail slice length (copy-paste error)
- config_test.go: remove duplicate MinimumLength assignment

* fixup! Fix real bugs flagged by CodeRabbit review
2026-05-12 15:59:12 +00:00

820 lines
27 KiB
Go

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package app
import (
"context"
"fmt"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/mattermost/mattermost/server/public/model"
"github.com/mattermost/mattermost/server/v8/channels/store"
)
//nolint:govet // The setup code leads to a lot of variable shadowing.
func TestCreateDefaultMemberships(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic(t)
singersTeam, err := th.App.CreateTeam(th.Context, &model.Team{
DisplayName: "Singers",
Name: "zz" + model.NewId(),
Email: "singers@test.com",
Type: model.TeamOpen,
})
if err != nil {
t.Errorf("test team not created: %s", err.Error())
}
nerdsTeam, err := th.App.CreateTeam(th.Context, &model.Team{
DisplayName: "Nerds",
Name: "zz" + model.NewId(),
Email: "nerds@test.com",
Type: model.TeamInvite,
})
if err != nil {
t.Errorf("test team not created: %s", err.Error())
}
practiceChannel, err := th.App.CreateChannel(th.Context, &model.Channel{
TeamId: singersTeam.Id,
DisplayName: "Practices",
Name: model.NewId(),
Type: model.ChannelTypeOpen,
}, false)
if err != nil {
t.Errorf("test channel not created: %s", err.Error())
}
experimentsChannel, err := th.App.CreateChannel(th.Context, &model.Channel{
TeamId: singersTeam.Id,
DisplayName: "Experiments",
Name: model.NewId(),
Type: model.ChannelTypePrivate,
}, false)
if err != nil {
t.Errorf("test channel not created: %s", err.Error())
}
gleeGroup, err := th.App.CreateGroup(&model.Group{
Name: new(model.NewId()),
DisplayName: "Glee Club",
RemoteId: new(model.NewId()),
Source: model.GroupSourceLdap,
})
if err != nil {
t.Errorf("test group not created: %s", err.Error())
}
scienceGroup, err := th.App.CreateGroup(&model.Group{
Name: new(model.NewId()),
DisplayName: "Science Club",
RemoteId: new(model.NewId()),
Source: model.GroupSourceLdap,
})
if err != nil {
t.Errorf("test group not created: %s", err.Error())
}
_, err = th.App.UpsertGroupSyncable(model.NewGroupChannel(gleeGroup.Id, practiceChannel.Id, true))
if err != nil {
t.Errorf("test groupchannel not created: %s", err.Error())
}
scienceTeamGroupSyncable, err := th.App.UpsertGroupSyncable(model.NewGroupTeam(scienceGroup.Id, nerdsTeam.Id, false))
if err != nil {
t.Errorf("test groupteam not created: %s", err.Error())
}
scienceChannelGroupSyncable, err := th.App.UpsertGroupSyncable(model.NewGroupChannel(scienceGroup.Id, experimentsChannel.Id, false))
if err != nil {
t.Errorf("test groupchannel not created: %s", err.Error())
}
singer1 := th.BasicUser
scientist1 := th.BasicUser2
_, err = th.App.UpsertGroupMember(gleeGroup.Id, singer1.Id)
if err != nil {
t.Errorf("test groupmember not created: %s", err.Error())
}
scientistGroupMember, err := th.App.UpsertGroupMember(scienceGroup.Id, scientist1.Id)
if err != nil {
t.Errorf("test groupmember not created: %s", err.Error())
}
pErr := th.App.CreateDefaultMemberships(th.Context, model.CreateDefaultMembershipParams{Since: 0, ReAddRemovedMembers: false})
if pErr != nil {
t.Errorf("failed to populate syncables: %s", pErr.Error())
}
// Singer should be in team and channel
_, err = th.App.GetTeamMember(th.Context, singersTeam.Id, singer1.Id)
if err != nil {
t.Errorf("error retrieving team member: %s", err.Error())
}
_, err = th.App.GetChannelMember(th.Context, practiceChannel.Id, singer1.Id)
if err != nil {
t.Errorf("error retrieving channel member: %s", err.Error())
}
tMembers, err := th.App.GetTeamMembers(singersTeam.Id, 0, 999, nil)
if err != nil {
t.Errorf("error retrieving team members: %s", err.Error())
}
expected := 1
actual := len(tMembers)
if actual != expected {
t.Errorf("expected %d team members but got %d", expected, actual)
}
cMembersCount, err := th.App.GetChannelMemberCount(th.Context, practiceChannel.Id)
if err != nil {
t.Errorf("error retrieving team members: %s", err.Error())
}
if cMembersCount != int64(expected) {
t.Errorf("expected %d team member but got %d", expected, cMembersCount)
}
// Scientist should not be in team or channel
_, err = th.App.GetTeamMember(th.Context, nerdsTeam.Id, scientist1.Id)
if err.Id != "app.team.get_member.missing.app_error" {
t.Errorf("wrong error: %s", err.Id)
}
_, err = th.App.GetChannelMember(th.Context, experimentsChannel.Id, scientist1.Id)
if err.Id != "app.channel.get_member.missing.app_error" {
t.Errorf("wrong error: %s", err.Id)
}
tMembers, err = th.App.GetTeamMembers(nerdsTeam.Id, 0, 999, nil)
if err != nil {
t.Errorf("error retrieving team members: %s", err.Error())
}
expected = 0
actual = len(tMembers)
if actual != expected {
t.Errorf("expected %d team members but got %d", expected, actual)
}
cMembersCount, err = th.App.GetChannelMemberCount(th.Context, experimentsChannel.Id)
if err != nil {
t.Errorf("error retrieving team members: %s", err.Error())
}
if cMembersCount != int64(expected) {
t.Errorf("expected %d team members but got %d", expected, cMembersCount)
}
// update AutoAdd to true
scienceTeamGroupSyncable.AutoAdd = true
_, err = th.App.UpdateGroupSyncable(scienceTeamGroupSyncable)
if err != nil {
t.Errorf("error updating group syncable: %s", err.Error())
}
// Sync everything after syncable was created (proving that team updates trigger re-sync)
pErr = th.App.CreateDefaultMemberships(th.Context, model.CreateDefaultMembershipParams{Since: scientistGroupMember.CreateAt + 1, ReAddRemovedMembers: false})
if pErr != nil {
t.Errorf("failed to populate syncables: %s", pErr.Error())
}
// Scientist should be in team but not the channel
_, err = th.App.GetTeamMember(th.Context, nerdsTeam.Id, scientist1.Id)
if err != nil {
t.Errorf("error retrieving team member: %s", err.Error())
}
_, err = th.App.GetChannelMember(th.Context, experimentsChannel.Id, scientist1.Id)
if err.Id != "app.channel.get_member.missing.app_error" {
t.Errorf("wrong error: %s", err.Id)
}
tMembers, err = th.App.GetTeamMembers(nerdsTeam.Id, 0, 999, nil)
if err != nil {
t.Errorf("error retrieving team members: %s", err.Error())
}
expected = 1
actual = len(tMembers)
if actual != expected {
t.Errorf("expected %d team members but got %d", expected, actual)
}
expected = 0
cMembersCount, err = th.App.GetChannelMemberCount(th.Context, experimentsChannel.Id)
if err != nil {
t.Errorf("error retrieving team members: %s", err.Error())
}
if cMembersCount != int64(expected) {
t.Errorf("expected %d team members but got %d", expected, cMembersCount)
}
// Update the channel syncable
scienceChannelGroupSyncable.AutoAdd = true
scienceChannelGroupSyncable, err = th.App.UpdateGroupSyncable(scienceChannelGroupSyncable)
if err != nil {
t.Errorf("error updating group syncable: %s", err.Error())
}
// Sync everything after syncable was created (proving that channel updates trigger re-sync)
pErr = th.App.CreateDefaultMemberships(th.Context, model.CreateDefaultMembershipParams{Since: scientistGroupMember.CreateAt + 1, ReAddRemovedMembers: false})
if pErr != nil {
t.Errorf("failed to populate syncables: %s", pErr.Error())
}
expected = 1
cMembersCount, err = th.App.GetChannelMemberCount(th.Context, experimentsChannel.Id)
if err != nil {
t.Errorf("error retrieving team members: %s", err.Error())
}
if cMembersCount != int64(expected) {
t.Errorf("expected %d team members but got %d", expected, cMembersCount)
}
// singer leaves team and channel
err = th.App.LeaveChannel(th.Context, practiceChannel.Id, singer1.Id)
if err != nil {
t.Errorf("error leaving channel: %s", err.Error())
}
err = th.App.LeaveTeam(th.Context, singersTeam, singer1, "")
if err != nil {
t.Errorf("error leaving team: %s", err.Error())
}
// Even re-syncing from the beginning doesn't re-add to channel or team
pErr = th.App.CreateDefaultMemberships(th.Context, model.CreateDefaultMembershipParams{Since: 0, ReAddRemovedMembers: false})
if pErr != nil {
t.Errorf("failed to populate syncables: %s", pErr.Error())
}
// Singer should not be in team or channel
tMember, err := th.App.GetTeamMember(th.Context, singersTeam.Id, singer1.Id)
if err != nil {
t.Errorf("error retrieving team member: %s", err.Error())
}
if tMember.DeleteAt == 0 {
t.Error("expected team member to remain deleted")
}
_, err = th.App.GetChannelMember(th.Context, practiceChannel.Id, singer1.Id)
if err == nil {
t.Error("Expected channel member to remain deleted")
}
// Ensure members are in channel
_, err = th.App.AddChannelMember(th.Context, scientist1.Id, experimentsChannel, ChannelMemberOpts{})
if err != nil {
t.Errorf("unable to add user to channel: %s", err.Error())
}
// Add other user so that user can leave channel
_, err = th.App.AddTeamMember(th.Context, singersTeam.Id, singer1.Id)
if err != nil {
t.Errorf("unable to add user to team: %s", err.Error())
}
_, err = th.App.AddChannelMember(th.Context, singer1.Id, experimentsChannel, ChannelMemberOpts{})
if err != nil {
t.Errorf("unable to add user to channel: %s", err.Error())
}
// the channel syncable is updated
scienceChannelGroupSyncable, err = th.App.UpdateGroupSyncable(scienceChannelGroupSyncable)
if err != nil {
t.Errorf("error updating group syncable: %s", err.Error())
}
pErr = th.App.CreateDefaultMemberships(th.Context, model.CreateDefaultMembershipParams{Since: 0, ReAddRemovedMembers: false})
if pErr != nil {
t.Errorf("failed to populate syncables: %s", pErr.Error())
}
timeBeforeLeaving := model.GetMillis()
// User leaves channel
err = th.App.LeaveChannel(th.Context, experimentsChannel.Id, scientist1.Id)
if err != nil {
t.Errorf("unable to add user to channel: %s", err.Error())
}
timeAfterLeaving := model.GetMillis() + 1
retentionPolicyBatchConfigs := model.RetentionPolicyBatchConfigs{
Now: 0,
GlobalPolicyEndTime: timeBeforeLeaving,
Limit: 1000,
}
// Purging channelmemberhistory doesn't re-add user to channel
deletedCount, _, nErr := th.App.Srv().Store().ChannelMemberHistory().PermanentDeleteBatchForRetentionPolicies(
retentionPolicyBatchConfigs, model.RetentionPolicyCursor{})
if nErr != nil {
t.Errorf("error permanently deleting channelmemberhistory: %s", nErr.Error())
}
require.Equal(t, int64(1), deletedCount)
pErr = th.App.CreateDefaultMemberships(th.Context, model.CreateDefaultMembershipParams{Since: scienceChannelGroupSyncable.UpdateAt, ReAddRemovedMembers: false})
if pErr != nil {
t.Errorf("failed to populate syncables: %s", pErr.Error())
}
_, err = th.App.GetChannelMember(th.Context, experimentsChannel.Id, scientist1.Id)
if err == nil {
t.Error("Expected channel member to remain deleted")
}
retentionPolicyBatchConfigs = model.RetentionPolicyBatchConfigs{
Now: 0,
GlobalPolicyEndTime: timeAfterLeaving,
Limit: 1000,
}
// Purging channelmemberhistory doesn't re-add user to channel
deletedCount, _, nErr = th.App.Srv().Store().ChannelMemberHistory().PermanentDeleteBatchForRetentionPolicies(
retentionPolicyBatchConfigs, model.RetentionPolicyCursor{})
if nErr != nil {
t.Errorf("error permanently deleting channelmemberhistory: %s", nErr.Error())
}
require.Equal(t, int64(1), deletedCount)
pErr = th.App.CreateDefaultMemberships(th.Context, model.CreateDefaultMembershipParams{Since: scienceChannelGroupSyncable.UpdateAt, ReAddRemovedMembers: false})
if pErr != nil {
t.Errorf("failed to populate syncables: %s", pErr.Error())
}
// Channel member is re-added.
_, err = th.App.GetChannelMember(th.Context, experimentsChannel.Id, scientist1.Id)
if err != nil {
t.Errorf("expected channel member: %s", err.Error())
}
t.Run("Team with restricted domains skips over members that do not match the allowed domains", func(t *testing.T) {
restrictedUser := th.CreateUser(t)
restrictedUser.Email = "restricted@mattermost.org"
_, err = th.App.UpdateUser(th.Context, restrictedUser, false)
require.Nil(t, err)
_, err = th.App.UpsertGroupMember(scienceGroup.Id, restrictedUser.Id)
require.Nil(t, err)
restrictedTeam, err := th.App.CreateTeam(th.Context, &model.Team{
DisplayName: "Restricted",
Name: "restricted" + model.NewId(),
Email: "restricted@mattermost.org",
AllowedDomains: "mattermost.org",
Type: model.TeamOpen,
})
require.Nil(t, err)
_, err = th.App.UpsertGroupSyncable(model.NewGroupTeam(scienceGroup.Id, restrictedTeam.Id, true))
require.Nil(t, err)
restrictedChannel, err := th.App.CreateChannel(th.Context, &model.Channel{
TeamId: restrictedTeam.Id,
DisplayName: "Restricted",
Name: "restricted" + model.NewId(),
Type: model.ChannelTypeOpen,
}, false)
require.Nil(t, err)
_, err = th.App.UpsertGroupSyncable(model.NewGroupChannel(scienceGroup.Id, restrictedChannel.Id, true))
require.Nil(t, err)
pErr = th.App.CreateDefaultMemberships(th.Context, model.CreateDefaultMembershipParams{Since: 0, ReAddRemovedMembers: false})
require.NoError(t, pErr)
// Ensure only the restricted user was added to both the team and channel
cMembersCount, err = th.App.GetChannelMemberCount(th.Context, restrictedChannel.Id)
require.Nil(t, err)
require.Equal(t, cMembersCount, int64(1))
tmembers, err := th.App.GetTeamMembers(restrictedTeam.Id, 0, 100, nil)
require.Nil(t, err)
require.Len(t, tmembers, 1)
require.Equal(t, tmembers[0].UserId, restrictedUser.Id)
})
t.Run("scoped to a single user", func(t *testing.T) {
team1, err := th.App.CreateTeam(th.Context, &model.Team{
DisplayName: "Team 1",
Name: "zz" + model.NewId(),
Email: "team1admin@test.com",
Type: model.TeamOpen,
})
if err != nil {
t.Errorf("test team not created: %s", err.Error())
}
team1Channel1, err := th.App.CreateChannel(th.Context, &model.Channel{
TeamId: team1.Id,
DisplayName: "Team 1 Channel 1",
Name: model.NewId(),
Type: model.ChannelTypeOpen,
}, false)
if err != nil {
t.Errorf("test channel not created: %s", err.Error())
}
group1, err := th.App.CreateGroup(&model.Group{
Name: new(model.NewId()),
DisplayName: "Group 1",
RemoteId: new(model.NewId()),
Source: model.GroupSourceLdap,
})
if err != nil {
t.Errorf("test group not created: %s", err.Error())
}
_, err = th.App.UpsertGroupSyncable(model.NewGroupTeam(group1.Id, team1.Id, true))
if err != nil {
t.Errorf("test groupchannel not created: %s", err.Error())
}
_, err = th.App.UpsertGroupSyncable(model.NewGroupChannel(group1.Id, team1Channel1.Id, true))
if err != nil {
t.Errorf("test groupchannel not created: %s", err.Error())
}
user1 := th.BasicUser
user2 := th.BasicUser2
_, err = th.App.UpsertGroupMember(group1.Id, user1.Id)
if err != nil {
t.Errorf("test groupmember not created: %s", err.Error())
}
_, err = th.App.UpsertGroupMember(group1.Id, user2.Id)
if err != nil {
t.Errorf("test groupmember not created: %s", err.Error())
}
params := model.CreateDefaultMembershipParams{Since: 0, ReAddRemovedMembers: false, ScopedUserID: &user1.Id}
pErr = th.App.CreateDefaultMemberships(th.Context, params)
if pErr != nil {
t.Errorf("failed to populate syncables: %s", pErr.Error())
}
// test that only user 1 is successfully added to the team and channel.
team1Members, err := th.App.GetTeamMembers(team1.Id, 0, 100, nil)
if err != nil {
t.Errorf("failed to get team members: %s", err.Error())
}
if len(team1Members) != 1 {
t.Errorf("expected 1 team member on team1, got %d", len(team1Members))
}
if team1Members[0].UserId != user1.Id {
t.Errorf("expected user1 to be a team member on team1, got %s", team1Members[0].UserId)
}
team1Channel1Members, err := th.App.GetChannelMembersPage(th.Context, team1Channel1.Id, 0, 100)
if err != nil {
t.Errorf("failed to get channel members: %s", err.Error())
}
if len(team1Channel1Members) != 1 {
t.Errorf("expected 1 channel member on team1Channel1, got %d", len(team1Channel1Members))
}
if team1Channel1Members[0].UserId != user1.Id {
t.Errorf("expected user1 to be a channel member on team1Channel1, got %s", team1Channel1Members[0].UserId)
}
// unscoped should add user2 to the team and channel
params = model.CreateDefaultMembershipParams{Since: 0, ReAddRemovedMembers: false}
pErr = th.App.CreateDefaultMemberships(th.Context, params)
if pErr != nil {
t.Errorf("failed to populate syncables: %s", pErr.Error())
}
team1Members, err = th.App.GetTeamMembers(team1.Id, 0, 100, nil)
if err != nil {
t.Errorf("failed to get team members: %s", err.Error())
}
if len(team1Members) != 2 {
t.Errorf("expected 2 team member on team1, got %d", len(team1Members))
}
team1Channel1Members, err = th.App.GetChannelMembersPage(th.Context, team1Channel1.Id, 0, 100)
if err != nil {
t.Errorf("failed to get channel members: %s", err.Error())
}
if len(team1Channel1Members) != 2 {
t.Errorf("expected 2 channel member on team1Channel1, got %d", len(team1Channel1Members))
}
})
t.Run("error should contain a information about all users that failed", func(t *testing.T) {
user1 := th.CreateUser(t)
_, err = th.App.UpsertGroupMember(scienceGroup.Id, user1.Id)
require.Nil(t, err)
user2 := th.CreateUser(t)
_, err = th.App.UpsertGroupMember(scienceGroup.Id, user2.Id)
require.Nil(t, err)
store := &mockStore{
Store: th.App.Srv().Store(),
us: &mokeUserStore{
UserStore: th.App.Srv().Store().User(),
},
}
require.Nil(t, err)
th.App.Srv().platform.Store = store
nErr = th.App.CreateDefaultMemberships(th.Context, model.CreateDefaultMembershipParams{Since: 0, ReAddRemovedMembers: false})
require.Error(t, nErr)
assert.ErrorContains(t, nErr, "failed to add team member for default team membership")
assert.ErrorContains(t, nErr, user1.Id)
assert.ErrorContains(t, nErr, user2.Id)
})
}
type mockStore struct {
store.Store
us store.UserStore
}
func (fk *mockStore) User() store.UserStore { return fk.us }
type mokeUserStore struct {
store.UserStore
}
func (us *mokeUserStore) Get(_ context.Context, id string) (*model.User, error) {
return nil, fmt.Errorf("some error for %s", id)
}
func TestDeleteGroupMemberships(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic(t)
group := th.CreateGroup(t)
userIDs := []string{th.BasicUser.Id, th.BasicUser2.Id, th.SystemAdminUser.Id}
var err *model.AppError
// add users to teams and channels
for _, userID := range userIDs {
_, err = th.App.AddTeamMember(th.Context, th.BasicTeam.Id, userID)
require.Nil(t, err)
_, err = th.App.AddChannelMember(th.Context, userID, th.BasicChannel, ChannelMemberOpts{})
require.Nil(t, err)
}
// make team group-constrained
team := th.BasicTeam
team.GroupConstrained = new(true)
team, err = th.App.UpdateTeam(team)
require.Nil(t, err)
require.True(t, *team.GroupConstrained)
// make channel group-constrained
channel := th.BasicChannel
channel.GroupConstrained = new(true)
channel, err = th.App.UpdateChannel(th.Context, channel)
require.Nil(t, err)
require.True(t, *channel.GroupConstrained)
// create groupteam and groupchannel
_, err = th.App.UpsertGroupSyncable(model.NewGroupTeam(group.Id, team.Id, true))
require.Nil(t, err)
_, err = th.App.UpsertGroupSyncable(model.NewGroupChannel(group.Id, channel.Id, true))
require.Nil(t, err)
// verify the member count
tmembers, err := th.App.GetTeamMembers(th.BasicTeam.Id, 0, 100, nil)
require.Nil(t, err)
require.Len(t, tmembers, 3)
cmemberCount, err := th.App.GetChannelMemberCount(th.Context, th.BasicChannel.Id)
require.Nil(t, err)
require.Equal(t, 3, int(cmemberCount))
// add a user to the group
_, err = th.App.UpsertGroupMember(group.Id, th.SystemAdminUser.Id)
require.Nil(t, err)
// run the delete
appErr := th.App.DeleteGroupConstrainedMemberships(th.Context)
require.NoError(t, appErr)
// verify the new member counts
tmembers, err = th.App.GetTeamMembers(th.BasicTeam.Id, 0, 100, nil)
require.Nil(t, err)
require.Len(t, tmembers, 1)
require.Equal(t, th.SystemAdminUser.Id, tmembers[0].UserId)
cmembers, err := th.App.GetChannelMembersPage(th.Context, channel.Id, 0, 99)
require.Nil(t, err)
require.Len(t, cmembers, 1)
require.Equal(t, th.SystemAdminUser.Id, cmembers[0].UserId)
}
func TestSyncSyncableRoles(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic(t)
team := th.CreateTeam(t)
channel := th.CreateChannel(t, team)
channel.GroupConstrained = new(true)
channel, err := th.App.UpdateChannel(th.Context, channel)
require.Nil(t, err)
user1 := th.CreateUser(t)
user2 := th.CreateUser(t)
group := th.CreateGroup(t)
teamSyncable, err := th.App.UpsertGroupSyncable(&model.GroupSyncable{
SyncableId: team.Id,
Type: model.GroupSyncableTypeTeam,
GroupId: group.Id,
})
require.Nil(t, err)
channelSyncable, err := th.App.UpsertGroupSyncable(&model.GroupSyncable{
SyncableId: channel.Id,
Type: model.GroupSyncableTypeChannel,
GroupId: group.Id,
})
require.Nil(t, err)
for _, user := range []*model.User{user1, user2} {
_, err = th.App.UpsertGroupMember(group.Id, user.Id)
require.Nil(t, err)
var tm *model.TeamMember
tm, err = th.App.AddTeamMember(th.Context, team.Id, user.Id)
require.Nil(t, err)
require.False(t, tm.SchemeAdmin)
cm := th.AddUserToChannel(t, user, channel)
require.False(t, cm.SchemeAdmin)
}
teamSyncable.SchemeAdmin = true
_, err = th.App.UpdateGroupSyncable(teamSyncable)
require.Nil(t, err)
channelSyncable.SchemeAdmin = true
_, err = th.App.UpdateGroupSyncable(channelSyncable)
require.Nil(t, err)
err = th.App.SyncSyncableRoles(th.Context, channel.Id, model.GroupSyncableTypeChannel)
require.Nil(t, err)
err = th.App.SyncSyncableRoles(th.Context, team.Id, model.GroupSyncableTypeTeam)
require.Nil(t, err)
for _, user := range []*model.User{user1, user2} {
tm, err := th.App.GetTeamMember(th.Context, team.Id, user.Id)
require.Nil(t, err)
require.True(t, tm.SchemeAdmin)
cm, err := th.App.GetChannelMember(th.Context, channel.Id, user.Id)
require.Nil(t, err)
require.True(t, cm.SchemeAdmin)
}
}
func TestSyncRolesAndMembership_RoleSyncGate(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic(t)
setup := func(t *testing.T) (*model.Team, *model.Channel, *model.Group, *model.User) {
t.Helper()
team := th.CreateTeam(t)
channel := th.CreateChannel(t, team)
group := th.CreateGroup(t)
_, err := th.App.UpsertGroupSyncable(&model.GroupSyncable{
SyncableId: team.Id,
Type: model.GroupSyncableTypeTeam,
GroupId: group.Id,
AutoAdd: true,
})
require.Nil(t, err)
_, err = th.App.UpsertGroupSyncable(&model.GroupSyncable{
SyncableId: channel.Id,
Type: model.GroupSyncableTypeChannel,
GroupId: group.Id,
AutoAdd: true,
})
require.Nil(t, err)
directAdmin := th.CreateUser(t)
_, appErr := th.App.AddTeamMember(th.Context, team.Id, directAdmin.Id)
require.Nil(t, appErr)
_, appErr = th.App.AddUserToChannel(th.Context, directAdmin, channel, false)
require.Nil(t, appErr)
tm, storeErr := th.App.Srv().Store().Team().GetMember(th.Context, team.Id, directAdmin.Id)
require.NoError(t, storeErr)
tm.SchemeAdmin = true
_, storeErr = th.App.Srv().Store().Team().UpdateMember(th.Context, tm)
require.NoError(t, storeErr)
cm, storeErr := th.App.Srv().Store().Channel().GetMember(th.Context, channel.Id, directAdmin.Id)
require.NoError(t, storeErr)
cm.SchemeAdmin = true
_, storeErr = th.App.Srv().Store().Channel().UpdateMember(th.Context, cm)
require.NoError(t, storeErr)
return team, channel, group, directAdmin
}
t.Run("syncRoles=false preserves the existing SchemeAdmin on team members", func(t *testing.T) {
team, _, group, directAdmin := setup(t)
th.App.SyncRolesAndMembership(th.Context, team.Id, model.GroupSyncableTypeTeam, group.Id, false)
tm, appErr := th.App.GetTeamMember(th.Context, team.Id, directAdmin.Id)
require.Nil(t, appErr)
assert.True(t, tm.SchemeAdmin)
})
t.Run("syncRoles=false preserves the existing SchemeAdmin on channel members", func(t *testing.T) {
_, channel, group, directAdmin := setup(t)
th.App.SyncRolesAndMembership(th.Context, channel.Id, model.GroupSyncableTypeChannel, group.Id, false)
cm, appErr := th.App.GetChannelMember(th.Context, channel.Id, directAdmin.Id)
require.Nil(t, appErr)
assert.True(t, cm.SchemeAdmin)
})
t.Run("syncRoles=true reconciles team SchemeAdmin against PermittedSyncableAdmins", func(t *testing.T) {
team, _, group, directAdmin := setup(t)
th.App.SyncRolesAndMembership(th.Context, team.Id, model.GroupSyncableTypeTeam, group.Id, true)
tm, appErr := th.App.GetTeamMember(th.Context, team.Id, directAdmin.Id)
require.Nil(t, appErr)
assert.False(t, tm.SchemeAdmin)
})
t.Run("syncRoles=true reconciles channel SchemeAdmin against PermittedSyncableAdmins", func(t *testing.T) {
_, channel, group, directAdmin := setup(t)
th.App.SyncRolesAndMembership(th.Context, channel.Id, model.GroupSyncableTypeChannel, group.Id, true)
cm, appErr := th.App.GetChannelMember(th.Context, channel.Id, directAdmin.Id)
require.Nil(t, appErr)
assert.False(t, cm.SchemeAdmin)
})
}
func TestSyncRolesAndMembership_AlwaysSyncsMembership(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic(t)
setup := func(t *testing.T) (*model.Team, *model.Channel, *model.Group, *model.User) {
t.Helper()
team := th.CreateTeam(t)
channel := th.CreateChannel(t, team)
group := th.CreateGroup(t)
_, err := th.App.UpsertGroupSyncable(&model.GroupSyncable{
SyncableId: team.Id,
Type: model.GroupSyncableTypeTeam,
GroupId: group.Id,
AutoAdd: true,
})
require.Nil(t, err)
_, err = th.App.UpsertGroupSyncable(&model.GroupSyncable{
SyncableId: channel.Id,
Type: model.GroupSyncableTypeChannel,
GroupId: group.Id,
AutoAdd: true,
})
require.Nil(t, err)
groupMember := th.CreateUser(t)
_, err = th.App.UpsertGroupMember(group.Id, groupMember.Id)
require.Nil(t, err)
return team, channel, group, groupMember
}
t.Run("syncRoles=false still adds group members to the team", func(t *testing.T) {
team, _, group, groupMember := setup(t)
th.App.SyncRolesAndMembership(th.Context, team.Id, model.GroupSyncableTypeTeam, group.Id, false)
tm, appErr := th.App.GetTeamMember(th.Context, team.Id, groupMember.Id)
require.Nil(t, appErr)
assert.Equal(t, groupMember.Id, tm.UserId)
})
t.Run("syncRoles=false still adds group members to the channel", func(t *testing.T) {
_, channel, group, groupMember := setup(t)
th.App.SyncRolesAndMembership(th.Context, channel.Id, model.GroupSyncableTypeChannel, group.Id, false)
cm, appErr := th.App.GetChannelMember(th.Context, channel.Id, groupMember.Id)
require.Nil(t, appErr)
assert.Equal(t, groupMember.Id, cm.UserId)
})
}