diff --git a/app/helper_test.go b/app/helper_test.go index cd06037dff..b16adea1ef 100644 --- a/app/helper_test.go +++ b/app/helper_test.go @@ -454,6 +454,11 @@ func (me *TestHelper) AddUserToChannel(user *model.User, channel *model.Channel) return member } +func (me *TestHelper) CreateRole(roleName string) *model.Role { + role, _ := me.App.CreateRole(&model.Role{Name: roleName, DisplayName: roleName, Description: roleName, Permissions: []string{}}) + return role +} + func (me *TestHelper) CreateScheme() (*model.Scheme, []*model.Role) { utils.DisableDebugLogForTest() diff --git a/app/import_functions.go b/app/import_functions.go index 2a6bbb2122..0baa6d2395 100644 --- a/app/import_functions.go +++ b/app/import_functions.go @@ -649,16 +649,39 @@ func (a *App) importUserTeams(user *model.User, data *[]UserTeamImportData) *mod return nil } - var teamThemePreferences model.Preferences + teamNames := []string{} for _, tdata := range *data { - team, err := a.GetTeamByName(*tdata.Name) - if err != nil { - return err - } + teamNames = append(teamNames, *tdata.Name) + } + allTeams, err := a.getTeamsByNames(teamNames) + if err != nil { + return err + } + + teamThemePreferencesByID := map[string]model.Preferences{} + channels := map[string][]UserChannelImportData{} + teamsByID := map[string]*model.Team{} + teamMemberByTeamID := map[string]*model.TeamMember{} + newTeamMembers := []*model.TeamMember{} + oldTeamMembers := []*model.TeamMember{} + rolesByTeamId := map[string]string{} + isGuestByTeamId := map[string]bool{} + isUserByTeamId := map[string]bool{} + isAdminByTeamId := map[string]bool{} + existingMemberships, err := a.Srv().Store.Team().GetTeamsForUser(user.Id) + if err != nil { + return err + } + existingMembershipsByTeamId := map[string]*model.TeamMember{} + for _, teamMembership := range existingMemberships { + existingMembershipsByTeamId[teamMembership.TeamId] = teamMembership + } + for _, tdata := range *data { + team := allTeams[*tdata.Name] // Team-specific theme Preferences. if tdata.Theme != nil { - teamThemePreferences = append(teamThemePreferences, model.Preference{ + teamThemePreferencesByID[team.Id] = append(teamThemePreferencesByID[team.Id], model.Preference{ UserId: user.Id, Category: model.PREFERENCE_CATEGORY_THEME, Name: team.Id, @@ -666,63 +689,95 @@ func (a *App) importUserTeams(user *model.User, data *[]UserTeamImportData) *mod }) } - var roles string - isSchemeGuest := false - isSchemeUser := true - isSchemeAdmin := false + isGuestByTeamId[team.Id] = false + isUserByTeamId[team.Id] = true + isAdminByTeamId[team.Id] = false if tdata.Roles == nil { - isSchemeUser = true + isUserByTeamId[team.Id] = true } else { rawRoles := *tdata.Roles explicitRoles := []string{} for _, role := range strings.Fields(rawRoles) { if role == model.TEAM_GUEST_ROLE_ID { - isSchemeGuest = true - isSchemeUser = false + isGuestByTeamId[team.Id] = true + isUserByTeamId[team.Id] = false } else if role == model.TEAM_USER_ROLE_ID { - isSchemeUser = true + isUserByTeamId[team.Id] = true } else if role == model.TEAM_ADMIN_ROLE_ID { - isSchemeAdmin = true + isAdminByTeamId[team.Id] = true } else { explicitRoles = append(explicitRoles, role) } } - roles = strings.Join(explicitRoles, " ") + rolesByTeamId[team.Id] = strings.Join(explicitRoles, " ") } - var member *model.TeamMember - if member, _, err = a.joinUserToTeam(team, user); err != nil { - return err + member := &model.TeamMember{ + TeamId: team.Id, + UserId: user.Id, + SchemeGuest: user.IsGuest(), + SchemeUser: !user.IsGuest(), + SchemeAdmin: team.Email == user.Email && !user.IsGuest(), } - - if member.ExplicitRoles != roles { - if _, err = a.UpdateTeamMemberRoles(team.Id, user.Id, roles); err != nil { + if !user.IsGuest() { + var userShouldBeAdmin bool + userShouldBeAdmin, err = a.UserIsInAdminRoleGroup(user.Id, team.Id, model.GroupSyncableTypeTeam) + if err != nil { return err } + member.SchemeAdmin = userShouldBeAdmin } - if member.SchemeAdmin != isSchemeAdmin || member.SchemeUser != isSchemeUser || member.SchemeGuest != isSchemeGuest { - a.UpdateTeamMemberSchemeRoles(team.Id, user.Id, isSchemeGuest, isSchemeUser, isSchemeAdmin) + if tdata.Channels != nil { + channels[team.Id] = append(channels[team.Id], *tdata.Channels...) + } + if !user.IsGuest() { + channels[team.Id] = append(channels[team.Id], UserChannelImportData{Name: model.NewString(model.DEFAULT_CHANNEL)}) } - defaultChannel, err := a.GetChannelByName(model.DEFAULT_CHANNEL, team.Id, true) + teamsByID[team.Id] = team + teamMemberByTeamID[team.Id] = member + if _, ok := existingMembershipsByTeamId[team.Id]; !ok { + newTeamMembers = append(newTeamMembers, member) + } else { + oldTeamMembers = append(oldTeamMembers, member) + } + } + + oldMembers, err := a.Srv().Store.Team().UpdateMultipleMembers(oldTeamMembers) + if err != nil { + return err + } + + newMembers := []*model.TeamMember{} + if len(newTeamMembers) > 0 { + newMembers, err = a.Srv().Store.Team().SaveMultipleMembers(newTeamMembers, *a.Config().TeamSettings.MaxUsersPerTeam) if err != nil { return err } - - if _, err = a.addUserToChannel(user, defaultChannel, member); err != nil { - return err - } - - if err := a.importUserChannels(user, team, member, tdata.Channels); err != nil { - return err - } } - if len(teamThemePreferences) > 0 { - if err := a.Srv().Store.Preference().Save(&teamThemePreferences); err != nil { - return model.NewAppError("BulkImport", "app.import.import_user_teams.save_preferences.error", nil, err.Error(), http.StatusInternalServerError) + for _, member := range append(newMembers, oldMembers...) { + if member.ExplicitRoles != rolesByTeamId[member.TeamId] { + if _, err = a.UpdateTeamMemberRoles(member.TeamId, user.Id, rolesByTeamId[member.TeamId]); err != nil { + return err + } + } + + a.UpdateTeamMemberSchemeRoles(member.TeamId, user.Id, isGuestByTeamId[member.TeamId], isUserByTeamId[member.TeamId], isAdminByTeamId[member.TeamId]) + } + + for _, team := range allTeams { + if len(teamThemePreferencesByID[team.Id]) > 0 { + pref := teamThemePreferencesByID[team.Id] + if err := a.Srv().Store.Preference().Save(&pref); err != nil { + return model.NewAppError("BulkImport", "app.import.import_user_teams.save_preferences.error", nil, err.Error(), http.StatusInternalServerError) + } + } + channelsToImport := channels[team.Id] + if err := a.importUserChannels(user, team, teamMemberByTeamID[team.Id], &channelsToImport); err != nil { + return err } } @@ -734,92 +789,144 @@ func (a *App) importUserChannels(user *model.User, team *model.Team, teamMember return nil } - var preferences model.Preferences + channelNames := []string{} + for _, tdata := range *data { + channelNames = append(channelNames, *tdata.Name) + } + allChannels, err := a.getChannelsByNames(channelNames, team.Id) + if err != nil { + return err + } - // Loop through all channels. + channelsByID := map[string]*model.Channel{} + channelMemberByChannelID := map[string]*model.ChannelMember{} + newChannelMembers := []*model.ChannelMember{} + oldChannelMembers := []*model.ChannelMember{} + rolesByChannelId := map[string]string{} + channelPreferencesByID := map[string]model.Preferences{} + isGuestByChannelId := map[string]bool{} + isUserByChannelId := map[string]bool{} + isAdminByChannelId := map[string]bool{} + existingMemberships, err := a.Srv().Store.Channel().GetMembersForUser(team.Id, user.Id) + if err != nil { + return err + } + existingMembershipsByChannelId := map[string]model.ChannelMember{} + for _, channelMembership := range *existingMemberships { + existingMembershipsByChannelId[channelMembership.ChannelId] = channelMembership + } for _, cdata := range *data { - channel, err := a.GetChannelByName(*cdata.Name, team.Id, true) - if err != nil { - return err + channel, ok := allChannels[*cdata.Name] + if !ok { + return model.NewAppError("BulkImport", "app.import.import_user_channels.channel_not_found.error", nil, "", http.StatusInternalServerError) + } + if _, ok = channelsByID[channel.Id]; ok && *cdata.Name == model.DEFAULT_CHANNEL { + // town-square membership was in the import and added by the importer (skip the added by the importer) + continue } - var roles string - isSchemeGuest := false - isSchemeUser := true - isSchemeAdmin := false + isGuestByChannelId[channel.Id] = false + isUserByChannelId[channel.Id] = true + isAdminByChannelId[channel.Id] = false if cdata.Roles == nil { - isSchemeUser = true + isUserByChannelId[channel.Id] = true } else { rawRoles := *cdata.Roles explicitRoles := []string{} for _, role := range strings.Fields(rawRoles) { if role == model.CHANNEL_GUEST_ROLE_ID { - isSchemeGuest = true - isSchemeUser = false + isGuestByChannelId[channel.Id] = true + isUserByChannelId[channel.Id] = false } else if role == model.CHANNEL_USER_ROLE_ID { - isSchemeUser = true + isUserByChannelId[channel.Id] = true } else if role == model.CHANNEL_ADMIN_ROLE_ID { - isSchemeAdmin = true + isAdminByChannelId[channel.Id] = true } else { explicitRoles = append(explicitRoles, role) } } - roles = strings.Join(explicitRoles, " ") - } - - var member *model.ChannelMember - member, err = a.GetChannelMember(channel.Id, user.Id) - if err != nil { - member, err = a.addUserToChannel(user, channel, teamMember) - if err != nil { - return err - } - } - - if member.ExplicitRoles != roles { - if _, err := a.UpdateChannelMemberRoles(channel.Id, user.Id, roles); err != nil { - return err - } - } - - if member.SchemeAdmin != isSchemeAdmin || member.SchemeUser != isSchemeUser || member.SchemeGuest != isSchemeGuest { - a.UpdateChannelMemberSchemeRoles(channel.Id, user.Id, isSchemeGuest, isSchemeUser, isSchemeAdmin) - } - - if cdata.NotifyProps != nil { - notifyProps := member.NotifyProps - - if cdata.NotifyProps.Desktop != nil { - notifyProps[model.DESKTOP_NOTIFY_PROP] = *cdata.NotifyProps.Desktop - } - - if cdata.NotifyProps.Mobile != nil { - notifyProps[model.PUSH_NOTIFY_PROP] = *cdata.NotifyProps.Mobile - } - - if cdata.NotifyProps.MarkUnread != nil { - notifyProps[model.MARK_UNREAD_NOTIFY_PROP] = *cdata.NotifyProps.MarkUnread - } - - if _, err := a.UpdateChannelMemberNotifyProps(notifyProps, channel.Id, user.Id); err != nil { - return err - } + rolesByChannelId[channel.Id] = strings.Join(explicitRoles, " ") } if cdata.Favorite != nil && *cdata.Favorite { - preferences = append(preferences, model.Preference{ + channelPreferencesByID[channel.Id] = append(channelPreferencesByID[channel.Id], model.Preference{ UserId: user.Id, Category: model.PREFERENCE_CATEGORY_FAVORITE_CHANNEL, Name: channel.Id, Value: "true", }) } + + member := &model.ChannelMember{ + ChannelId: channel.Id, + UserId: user.Id, + NotifyProps: model.GetDefaultChannelNotifyProps(), + SchemeGuest: user.IsGuest(), + SchemeUser: !user.IsGuest(), + SchemeAdmin: false, + } + if !user.IsGuest() { + var userShouldBeAdmin bool + userShouldBeAdmin, err = a.UserIsInAdminRoleGroup(user.Id, team.Id, model.GroupSyncableTypeTeam) + if err != nil { + return err + } + member.SchemeAdmin = userShouldBeAdmin + } + + if cdata.NotifyProps != nil { + if cdata.NotifyProps.Desktop != nil { + member.NotifyProps[model.DESKTOP_NOTIFY_PROP] = *cdata.NotifyProps.Desktop + } + + if cdata.NotifyProps.Mobile != nil { + member.NotifyProps[model.PUSH_NOTIFY_PROP] = *cdata.NotifyProps.Mobile + } + + if cdata.NotifyProps.MarkUnread != nil { + member.NotifyProps[model.MARK_UNREAD_NOTIFY_PROP] = *cdata.NotifyProps.MarkUnread + } + } + + channelsByID[channel.Id] = channel + channelMemberByChannelID[channel.Id] = member + if _, ok := existingMembershipsByChannelId[channel.Id]; !ok { + newChannelMembers = append(newChannelMembers, member) + } else { + oldChannelMembers = append(oldChannelMembers, member) + } } - if len(preferences) > 0 { - if err := a.Srv().Store.Preference().Save(&preferences); err != nil { - return model.NewAppError("BulkImport", "app.import.import_user_channels.save_preferences.error", nil, err.Error(), http.StatusInternalServerError) + oldMembers, err := a.Srv().Store.Channel().UpdateMultipleMembers(oldChannelMembers) + if err != nil { + return err + } + + newMembers := []*model.ChannelMember{} + if len(newChannelMembers) > 0 { + newMembers, err = a.Srv().Store.Channel().SaveMultipleMembers(newChannelMembers) + if err != nil { + return err + } + } + + for _, member := range append(newMembers, oldMembers...) { + if member.ExplicitRoles != rolesByChannelId[member.ChannelId] { + if _, err = a.UpdateChannelMemberRoles(member.ChannelId, user.Id, rolesByChannelId[member.ChannelId]); err != nil { + return err + } + } + + a.UpdateChannelMemberSchemeRoles(member.ChannelId, user.Id, isGuestByChannelId[member.ChannelId], isUserByChannelId[member.ChannelId], isAdminByChannelId[member.ChannelId]) + } + + for _, channel := range allChannels { + if len(channelPreferencesByID[channel.Id]) > 0 { + pref := channelPreferencesByID[channel.Id] + if err := a.Srv().Store.Preference().Save(&pref); err != nil { + return model.NewAppError("BulkImport", "app.import.import_user_channels.save_preferences.error", nil, err.Error(), http.StatusInternalServerError) + } } } @@ -1022,6 +1129,19 @@ func (a *App) getTeamsByNames(names []string) (map[string]*model.Team, *model.Ap return teams, nil } +func (a *App) getChannelsByNames(names []string, teamId string) (map[string]*model.Channel, *model.AppError) { + allChannels, err := a.Srv().Store.Channel().GetByNames(teamId, names, true) + if err != nil { + return nil, model.NewAppError("BulkImport", "app.import.get_teams_by_names.some_teams_not_found.error", nil, err.Error(), http.StatusBadRequest) + } + + channels := make(map[string]*model.Channel) + for _, channel := range allChannels { + channels[channel.Name] = channel + } + return channels, nil +} + func (a *App) getChannelsForPosts(teams map[string]*model.Team, data []*PostImportData) (map[string]*model.Channel, *model.AppError) { channels := make(map[string]*model.Channel) for _, postData := range data { diff --git a/app/import_functions_test.go b/app/import_functions_test.go index b0c5717cf1..60f02eaeb4 100644 --- a/app/import_functions_test.go +++ b/app/import_functions_test.go @@ -1526,6 +1526,363 @@ func TestImportImportUser(t *testing.T) { assert.Equal(t, "", channelMember.ExplicitRoles) } +func TestImportUserTeams(t *testing.T) { + th := Setup(t).InitBasic() + defer th.TearDown() + team2 := th.CreateTeam() + channel2 := th.CreateChannel(th.BasicTeam) + channel3 := th.CreateChannel(team2) + customRole := th.CreateRole("test_custom_role") + sampleTheme := "{\"test\":\"#abcdef\"}" + + tt := []struct { + name string + data *[]UserTeamImportData + expectedError bool + expectedUserTeams int + expectedUserChannels int + expectedExplicitRoles string + expectedRoles string + expectedTheme string + }{ + { + name: "Not existing team should fail", + data: &[]UserTeamImportData{ + { + Name: model.NewString("not-existing-team-name"), + }, + }, + expectedError: true, + }, + { + name: "nil data shouldn't do anything", + expectedError: false, + expectedUserTeams: 0, + expectedUserChannels: 0, + }, + { + name: "Should fail if one of the roles doesn't exists", + data: &[]UserTeamImportData{ + { + Name: &th.BasicTeam.Name, + Roles: model.NewString("not-existing-role"), + }, + }, + expectedError: true, + expectedUserTeams: 1, + expectedUserChannels: 0, + expectedExplicitRoles: "", + expectedRoles: "team_user", + }, + { + name: "Should success to import explicit role", + data: &[]UserTeamImportData{ + { + Name: &th.BasicTeam.Name, + Roles: &customRole.Name, + }, + }, + expectedError: false, + expectedUserTeams: 1, + expectedUserChannels: 1, + expectedExplicitRoles: customRole.Name, + expectedRoles: customRole.Name + " team_user", + }, + { + name: "Should success to import admin role", + data: &[]UserTeamImportData{ + { + Name: &th.BasicTeam.Name, + Roles: model.NewString(model.TEAM_ADMIN_ROLE_ID), + }, + }, + expectedError: false, + expectedUserTeams: 1, + expectedUserChannels: 1, + expectedExplicitRoles: "", + expectedRoles: "team_user team_admin", + }, + { + name: "Should success to import with theme", + data: &[]UserTeamImportData{ + { + Name: &th.BasicTeam.Name, + Theme: &sampleTheme, + }, + }, + expectedError: false, + expectedUserTeams: 1, + expectedUserChannels: 1, + expectedExplicitRoles: "", + expectedRoles: "team_user", + expectedTheme: sampleTheme, + }, + { + name: "Team without channels must add the default channel", + data: &[]UserTeamImportData{ + { + Name: &th.BasicTeam.Name, + }, + }, + expectedError: false, + expectedUserTeams: 1, + expectedUserChannels: 1, + expectedExplicitRoles: "", + expectedRoles: "team_user", + }, + { + name: "Team with default channel must add only the default channel", + data: &[]UserTeamImportData{ + { + Name: &th.BasicTeam.Name, + Channels: &[]UserChannelImportData{ + { + Name: ptrStr(model.DEFAULT_CHANNEL), + }, + }, + }, + }, + expectedError: false, + expectedUserTeams: 1, + expectedUserChannels: 1, + expectedExplicitRoles: "", + expectedRoles: "team_user", + }, + { + name: "Team with non default channel must add default channel and the other channel", + data: &[]UserTeamImportData{ + { + Name: &th.BasicTeam.Name, + Channels: &[]UserChannelImportData{ + { + Name: &th.BasicChannel.Name, + }, + }, + }, + }, + expectedError: false, + expectedUserTeams: 1, + expectedUserChannels: 2, + expectedExplicitRoles: "", + expectedRoles: "team_user", + }, + { + name: "Multiple teams with multiple channels each", + data: &[]UserTeamImportData{ + { + Name: &th.BasicTeam.Name, + Channels: &[]UserChannelImportData{ + { + Name: &th.BasicChannel.Name, + }, + { + Name: &channel2.Name, + }, + }, + }, + { + Name: &team2.Name, + Channels: &[]UserChannelImportData{ + { + Name: &channel3.Name, + }, + { + Name: model.NewString("town-square"), + }, + }, + }, + }, + expectedError: false, + expectedUserTeams: 2, + expectedUserChannels: 5, + }, + } + + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + user := th.CreateUser() + + // Two times import must end with the same results + for x := 0; x < 2; x++ { + err := th.App.importUserTeams(user, tc.data) + if tc.expectedError { + require.NotNil(t, err) + } else { + require.Nil(t, err) + } + teamMembers, err := th.App.Srv().Store.Team().GetTeamsForUser(user.Id) + require.Nil(t, err) + require.Len(t, teamMembers, tc.expectedUserTeams) + if tc.expectedUserTeams == 1 { + require.Equal(t, tc.expectedExplicitRoles, teamMembers[0].ExplicitRoles, "Not matching expected explicit roles") + require.Equal(t, tc.expectedRoles, teamMembers[0].Roles, "not matching expected roles") + if tc.expectedTheme != "" { + pref, prefErr := th.App.Srv().Store.Preference().Get(user.Id, model.PREFERENCE_CATEGORY_THEME, teamMembers[0].TeamId) + require.Nil(t, prefErr) + require.Equal(t, tc.expectedTheme, pref.Value) + } + } + + totalMembers := 0 + for _, teamMember := range teamMembers { + channelMembers, err := th.App.Srv().Store.Channel().GetMembersForUser(teamMember.TeamId, user.Id) + require.Nil(t, err) + totalMembers += len(*channelMembers) + } + require.Equal(t, tc.expectedUserChannels, totalMembers) + } + }) + } + + t.Run("Should fail if the MaxUserPerTeam is reached", func(t *testing.T) { + user := th.CreateUser() + data := &[]UserTeamImportData{ + { + Name: &th.BasicTeam.Name, + }, + } + th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.MaxUsersPerTeam = 1 }) + defer th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.MaxUsersPerTeam = 100 }) + err := th.App.importUserTeams(user, data) + require.NotNil(t, err) + }) +} + +func TestImportUserChannels(t *testing.T) { + th := Setup(t).InitBasic() + defer th.TearDown() + channel2 := th.CreateChannel(th.BasicTeam) + customRole := th.CreateRole("test_custom_role") + sampleNotifyProps := UserChannelNotifyPropsImportData{ + Desktop: model.NewString("all"), + Mobile: model.NewString("none"), + MarkUnread: model.NewString("all"), + } + + tt := []struct { + name string + data *[]UserChannelImportData + expectedError bool + expectedUserChannels int + expectedExplicitRoles string + expectedRoles string + expectedNotifyProps *UserChannelNotifyPropsImportData + }{ + { + name: "Not existing channel should fail", + data: &[]UserChannelImportData{ + { + Name: model.NewString("not-existing-channel-name"), + }, + }, + expectedError: true, + }, + { + name: "nil data shouldn't do anything", + expectedError: false, + expectedUserChannels: 0, + }, + { + name: "Should fail if one of the roles doesn't exists", + data: &[]UserChannelImportData{ + { + Name: &th.BasicChannel.Name, + Roles: model.NewString("not-existing-role"), + }, + }, + expectedError: true, + expectedUserChannels: 1, + expectedExplicitRoles: "", + expectedRoles: "channel_user", + }, + { + name: "Should success to import explicit role", + data: &[]UserChannelImportData{ + { + Name: &th.BasicChannel.Name, + Roles: &customRole.Name, + }, + }, + expectedError: false, + expectedUserChannels: 1, + expectedExplicitRoles: customRole.Name, + expectedRoles: customRole.Name + " channel_user", + }, + { + name: "Should success to import admin role", + data: &[]UserChannelImportData{ + { + Name: &th.BasicChannel.Name, + Roles: model.NewString(model.CHANNEL_ADMIN_ROLE_ID), + }, + }, + expectedError: false, + expectedUserChannels: 1, + expectedExplicitRoles: "", + expectedRoles: "channel_user channel_admin", + }, + { + name: "Should success to import with notifyProps", + data: &[]UserChannelImportData{ + { + Name: &th.BasicChannel.Name, + NotifyProps: &sampleNotifyProps, + }, + }, + expectedError: false, + expectedUserChannels: 1, + expectedExplicitRoles: "", + expectedRoles: "channel_user", + expectedNotifyProps: &sampleNotifyProps, + }, + { + name: "Should import properly multiple channels", + data: &[]UserChannelImportData{ + { + Name: &th.BasicChannel.Name, + }, + { + Name: &channel2.Name, + }, + }, + expectedError: false, + expectedUserChannels: 2, + }, + } + + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + user := th.CreateUser() + th.App.joinUserToTeam(th.BasicTeam, user) + teamMember, err := th.App.GetTeamMember(th.BasicTeam.Id, user.Id) + require.Nil(t, err) + + // Two times import must end with the same results + for x := 0; x < 2; x++ { + err = th.App.importUserChannels(user, th.BasicTeam, teamMember, tc.data) + if tc.expectedError { + require.NotNil(t, err) + } else { + require.Nil(t, err) + } + channelMembers, err := th.App.Srv().Store.Channel().GetMembersForUser(th.BasicTeam.Id, user.Id) + require.Nil(t, err) + require.Len(t, *channelMembers, tc.expectedUserChannels) + if tc.expectedUserChannels == 1 { + channelMember := (*channelMembers)[0] + require.Equal(t, tc.expectedExplicitRoles, channelMember.ExplicitRoles, "Not matching expected explicit roles") + require.Equal(t, tc.expectedRoles, channelMember.Roles, "not matching expected roles") + if tc.expectedNotifyProps != nil { + require.Equal(t, *tc.expectedNotifyProps.Desktop, channelMember.NotifyProps[model.DESKTOP_NOTIFY_PROP]) + require.Equal(t, *tc.expectedNotifyProps.Mobile, channelMember.NotifyProps[model.PUSH_NOTIFY_PROP]) + require.Equal(t, *tc.expectedNotifyProps.MarkUnread, channelMember.NotifyProps[model.MARK_UNREAD_NOTIFY_PROP]) + } + } + } + }) + } +} + func TestImportUserDefaultNotifyProps(t *testing.T) { th := Setup(t) defer th.TearDown() diff --git a/i18n/en.json b/i18n/en.json index ede7a064e9..7e89b8853d 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -3070,6 +3070,10 @@ "id": "app.import.import_user.save_preferences.error", "translation": "Error importing user preferences. Failed to save preferences." }, + { + "id": "app.import.import_user_channels.channel_not_found.error", + "translation": "Error importing user channels. Channel not found." + }, { "id": "app.import.import_user_channels.save_preferences.error", "translation": "Error importing user channel memberships. Failed to save preferences." @@ -6046,6 +6050,22 @@ "id": "store.sql_channel.save_member.save.app_error", "translation": "Unable to save the channel member." }, + { + "id": "store.sql_channel.save_multimple_members.channel_roles.app_error", + "translation": "Unable to build query to get the channel roles." + }, + { + "id": "store.sql_channel.save_multimple_members.channel_roles_query.app_error", + "translation": "Error fetching the channel roles." + }, + { + "id": "store.sql_channel.save_multimple_members.team_roles.app_error", + "translation": "Unable to build query to get the team roles." + }, + { + "id": "store.sql_channel.save_multimple_members.team_roles_query.app_error", + "translation": "Error fetching the team roles." + }, { "id": "store.sql_channel.search.app_error", "translation": "We encountered an error searching channels." diff --git a/store/localcachelayer/channel_layer.go b/store/localcachelayer/channel_layer.go index 75d751fd73..b7b4e9fd4b 100644 --- a/store/localcachelayer/channel_layer.go +++ b/store/localcachelayer/channel_layer.go @@ -167,3 +167,61 @@ func (s LocalCacheChannelStore) Get(id string, allowFromCache bool) (*model.Chan return ch, err } + +func (s LocalCacheChannelStore) SaveMember(member *model.ChannelMember) (*model.ChannelMember, *model.AppError) { + member, err := s.ChannelStore.SaveMember(member) + if err != nil { + return nil, err + } + s.InvalidateMemberCount(member.ChannelId) + return member, nil +} + +func (s LocalCacheChannelStore) SaveMultipleMembers(members []*model.ChannelMember) ([]*model.ChannelMember, *model.AppError) { + members, err := s.ChannelStore.SaveMultipleMembers(members) + if err != nil { + return nil, err + } + for _, member := range members { + s.InvalidateMemberCount(member.ChannelId) + } + return members, nil +} + +func (s LocalCacheChannelStore) UpdateMember(member *model.ChannelMember) (*model.ChannelMember, *model.AppError) { + member, err := s.ChannelStore.UpdateMember(member) + if err != nil { + return nil, err + } + s.InvalidateMemberCount(member.ChannelId) + return member, nil +} + +func (s LocalCacheChannelStore) UpdateMultipleMembers(members []*model.ChannelMember) ([]*model.ChannelMember, *model.AppError) { + members, err := s.ChannelStore.UpdateMultipleMembers(members) + if err != nil { + return nil, err + } + for _, member := range members { + s.InvalidateMemberCount(member.ChannelId) + } + return members, nil +} + +func (s LocalCacheChannelStore) RemoveMember(channelId, userId string) *model.AppError { + err := s.ChannelStore.RemoveMember(channelId, userId) + if err != nil { + return err + } + s.InvalidateMemberCount(channelId) + return nil +} + +func (s LocalCacheChannelStore) RemoveMembers(channelId string, userIds []string) *model.AppError { + err := s.ChannelStore.RemoveMembers(channelId, userIds) + if err != nil { + return err + } + s.InvalidateMemberCount(channelId) + return nil +} diff --git a/store/opentracing_layer.go b/store/opentracing_layer.go index 8c2d904b0e..28ea32bae6 100644 --- a/store/opentracing_layer.go +++ b/store/opentracing_layer.go @@ -1678,6 +1678,24 @@ func (s *OpenTracingLayerChannelStore) SaveMember(member *model.ChannelMember) ( return resultVar0, resultVar1 } +func (s *OpenTracingLayerChannelStore) SaveMultipleMembers(members []*model.ChannelMember) ([]*model.ChannelMember, *model.AppError) { + origCtx := s.Root.Store.Context() + span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ChannelStore.SaveMultipleMembers") + s.Root.Store.SetContext(newCtx) + defer func() { + s.Root.Store.SetContext(origCtx) + }() + + defer span.Finish() + resultVar0, resultVar1 := s.ChannelStore.SaveMultipleMembers(members) + if resultVar1 != nil { + span.LogFields(spanlog.Error(resultVar1)) + ext.Error.Set(span, true) + } + + return resultVar0, resultVar1 +} + func (s *OpenTracingLayerChannelStore) SearchAllChannels(term string, opts ChannelSearchOpts) (*model.ChannelListWithTeamData, int64, *model.AppError) { origCtx := s.Root.Store.Context() span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ChannelStore.SearchAllChannels") @@ -1894,6 +1912,24 @@ func (s *OpenTracingLayerChannelStore) UpdateMembersRole(channelID string, userI return resultVar0 } +func (s *OpenTracingLayerChannelStore) UpdateMultipleMembers(members []*model.ChannelMember) ([]*model.ChannelMember, *model.AppError) { + origCtx := s.Root.Store.Context() + span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ChannelStore.UpdateMultipleMembers") + s.Root.Store.SetContext(newCtx) + defer func() { + s.Root.Store.SetContext(origCtx) + }() + + defer span.Finish() + resultVar0, resultVar1 := s.ChannelStore.UpdateMultipleMembers(members) + if resultVar1 != nil { + span.LogFields(spanlog.Error(resultVar1)) + ext.Error.Set(span, true) + } + + return resultVar0, resultVar1 +} + func (s *OpenTracingLayerChannelStore) UserBelongsToChannels(userId string, channelIds []string) (bool, *model.AppError) { origCtx := s.Root.Store.Context() span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ChannelStore.UserBelongsToChannels") @@ -4783,6 +4819,24 @@ func (s *OpenTracingLayerPostStore) Overwrite(post *model.Post) (*model.Post, *m return resultVar0, resultVar1 } +func (s *OpenTracingLayerPostStore) OverwriteMultiple(posts []*model.Post) ([]*model.Post, *model.AppError) { + origCtx := s.Root.Store.Context() + span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "PostStore.OverwriteMultiple") + s.Root.Store.SetContext(newCtx) + defer func() { + s.Root.Store.SetContext(origCtx) + }() + + defer span.Finish() + resultVar0, resultVar1 := s.PostStore.OverwriteMultiple(posts) + if resultVar1 != nil { + span.LogFields(spanlog.Error(resultVar1)) + ext.Error.Set(span, true) + } + + return resultVar0, resultVar1 +} + func (s *OpenTracingLayerPostStore) PermanentDeleteBatch(endTime int64, limit int64) (int64, *model.AppError) { origCtx := s.Root.Store.Context() span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "PostStore.PermanentDeleteBatch") @@ -4855,6 +4909,24 @@ func (s *OpenTracingLayerPostStore) Save(post *model.Post) (*model.Post, *model. return resultVar0, resultVar1 } +func (s *OpenTracingLayerPostStore) SaveMultiple(posts []*model.Post) ([]*model.Post, *model.AppError) { + origCtx := s.Root.Store.Context() + span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "PostStore.SaveMultiple") + s.Root.Store.SetContext(newCtx) + defer func() { + s.Root.Store.SetContext(origCtx) + }() + + defer span.Finish() + resultVar0, resultVar1 := s.PostStore.SaveMultiple(posts) + if resultVar1 != nil { + span.LogFields(spanlog.Error(resultVar1)) + ext.Error.Set(span, true) + } + + return resultVar0, resultVar1 +} + func (s *OpenTracingLayerPostStore) Search(teamId string, userId string, params *model.SearchParams) (*model.PostList, *model.AppError) { origCtx := s.Root.Store.Context() span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "PostStore.Search") @@ -6231,6 +6303,24 @@ func (s *OpenTracingLayerTeamStore) GetByName(name string) (*model.Team, *model. return resultVar0, resultVar1 } +func (s *OpenTracingLayerTeamStore) GetByNames(name []string) ([]*model.Team, *model.AppError) { + origCtx := s.Root.Store.Context() + span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "TeamStore.GetByNames") + s.Root.Store.SetContext(newCtx) + defer func() { + s.Root.Store.SetContext(origCtx) + }() + + defer span.Finish() + resultVar0, resultVar1 := s.TeamStore.GetByNames(name) + if resultVar1 != nil { + span.LogFields(spanlog.Error(resultVar1)) + ext.Error.Set(span, true) + } + + return resultVar0, resultVar1 +} + func (s *OpenTracingLayerTeamStore) GetChannelUnreadsForAllTeams(excludeTeamId string, userId string) ([]*model.ChannelUnread, *model.AppError) { origCtx := s.Root.Store.Context() span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "TeamStore.GetChannelUnreadsForAllTeams") @@ -6622,6 +6712,24 @@ func (s *OpenTracingLayerTeamStore) SaveMember(member *model.TeamMember, maxUser return resultVar0, resultVar1 } +func (s *OpenTracingLayerTeamStore) SaveMultipleMembers(members []*model.TeamMember, maxUsersPerTeam int) ([]*model.TeamMember, *model.AppError) { + origCtx := s.Root.Store.Context() + span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "TeamStore.SaveMultipleMembers") + s.Root.Store.SetContext(newCtx) + defer func() { + s.Root.Store.SetContext(origCtx) + }() + + defer span.Finish() + resultVar0, resultVar1 := s.TeamStore.SaveMultipleMembers(members, maxUsersPerTeam) + if resultVar1 != nil { + span.LogFields(spanlog.Error(resultVar1)) + ext.Error.Set(span, true) + } + + return resultVar0, resultVar1 +} + func (s *OpenTracingLayerTeamStore) SearchAll(term string) ([]*model.Team, *model.AppError) { origCtx := s.Root.Store.Context() span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "TeamStore.SearchAll") @@ -6766,6 +6874,24 @@ func (s *OpenTracingLayerTeamStore) UpdateMembersRole(teamID string, userIDs []s return resultVar0 } +func (s *OpenTracingLayerTeamStore) UpdateMultipleMembers(members []*model.TeamMember) ([]*model.TeamMember, *model.AppError) { + origCtx := s.Root.Store.Context() + span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "TeamStore.UpdateMultipleMembers") + s.Root.Store.SetContext(newCtx) + defer func() { + s.Root.Store.SetContext(origCtx) + }() + + defer span.Finish() + resultVar0, resultVar1 := s.TeamStore.UpdateMultipleMembers(members) + if resultVar1 != nil { + span.LogFields(spanlog.Error(resultVar1)) + ext.Error.Set(span, true) + } + + return resultVar0, resultVar1 +} + func (s *OpenTracingLayerTeamStore) UserBelongsToTeams(userId string, teamIds []string) (bool, *model.AppError) { origCtx := s.Root.Store.Context() span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "TeamStore.UserBelongsToTeams") diff --git a/store/sqlstore/channel_store.go b/store/sqlstore/channel_store.go index 2d13ec34ef..47a1a18065 100644 --- a/store/sqlstore/channel_store.go +++ b/store/sqlstore/channel_store.go @@ -87,94 +87,154 @@ type channelMemberWithSchemeRoles struct { ChannelSchemeDefaultAdminRole sql.NullString } +func channelMemberSliceColumns() []string { + return []string{"ChannelId", "UserId", "Roles", "LastViewedAt", "MsgCount", "MentionCount", "NotifyProps", "LastUpdateAt", "SchemeUser", "SchemeAdmin", "SchemeGuest"} +} + +func channelMemberToSlice(member *model.ChannelMember) []interface{} { + resultSlice := []interface{}{} + resultSlice = append(resultSlice, member.ChannelId) + resultSlice = append(resultSlice, member.UserId) + resultSlice = append(resultSlice, member.ExplicitRoles) + resultSlice = append(resultSlice, member.LastViewedAt) + resultSlice = append(resultSlice, member.MsgCount) + resultSlice = append(resultSlice, member.MentionCount) + resultSlice = append(resultSlice, model.MapToJson(member.NotifyProps)) + resultSlice = append(resultSlice, member.LastUpdateAt) + resultSlice = append(resultSlice, member.SchemeUser) + resultSlice = append(resultSlice, member.SchemeAdmin) + resultSlice = append(resultSlice, member.SchemeGuest) + return resultSlice +} + type channelMemberWithSchemeRolesList []channelMemberWithSchemeRoles -func (db channelMemberWithSchemeRoles) ToModel() *model.ChannelMember { - var roles []string - var explicitRoles []string +func getChannelRoles(schemeGuest, schemeUser, schemeAdmin bool, defaultTeamGuestRole, defaultTeamUserRole, defaultTeamAdminRole, defaultChannelGuestRole, defaultChannelUserRole, defaultChannelAdminRole string, roles []string) rolesInfo { + result := rolesInfo{ + roles: []string{}, + explicitRoles: []string{}, + schemeGuest: schemeGuest, + schemeUser: schemeUser, + schemeAdmin: schemeAdmin, + } - // Identify any system-wide scheme derived roles that are in "Roles" field due to not yet being migrated, - // and exclude them from ExplicitRoles field. - schemeGuest := db.SchemeGuest.Valid && db.SchemeGuest.Bool - schemeUser := db.SchemeUser.Valid && db.SchemeUser.Bool - schemeAdmin := db.SchemeAdmin.Valid && db.SchemeAdmin.Bool - for _, role := range strings.Fields(db.Roles) { - isImplicit := false - if role == model.CHANNEL_GUEST_ROLE_ID { - // We have an implicit role via the system scheme. Override the "schemeGuest" field to true. - schemeGuest = true - isImplicit = true - } else if role == model.CHANNEL_USER_ROLE_ID { - // We have an implicit role via the system scheme. Override the "schemeUser" field to true. - schemeUser = true - isImplicit = true - } else if role == model.CHANNEL_ADMIN_ROLE_ID { - // We have an implicit role via the system scheme. - schemeAdmin = true - isImplicit = true + // Identify any scheme derived roles that are in "Roles" field due to not yet being migrated, and exclude + // them from ExplicitRoles field. + for _, role := range roles { + switch role { + case model.CHANNEL_GUEST_ROLE_ID: + result.schemeGuest = true + case model.CHANNEL_USER_ROLE_ID: + result.schemeUser = true + case model.CHANNEL_ADMIN_ROLE_ID: + result.schemeAdmin = true + default: + result.explicitRoles = append(result.explicitRoles, role) + result.roles = append(result.roles, role) } - - if !isImplicit { - explicitRoles = append(explicitRoles, role) - } - roles = append(roles, role) } // Add any scheme derived roles that are not in the Roles field due to being Implicit from the Scheme, and add // them to the Roles field for backwards compatibility reasons. var schemeImpliedRoles []string - if db.SchemeGuest.Valid && db.SchemeGuest.Bool { - if db.ChannelSchemeDefaultGuestRole.Valid && db.ChannelSchemeDefaultGuestRole.String != "" { - schemeImpliedRoles = append(schemeImpliedRoles, db.ChannelSchemeDefaultGuestRole.String) - } else if db.TeamSchemeDefaultGuestRole.Valid && db.TeamSchemeDefaultGuestRole.String != "" { - schemeImpliedRoles = append(schemeImpliedRoles, db.TeamSchemeDefaultGuestRole.String) + if result.schemeGuest { + if defaultChannelGuestRole != "" { + schemeImpliedRoles = append(schemeImpliedRoles, defaultChannelGuestRole) + } else if defaultTeamGuestRole != "" { + schemeImpliedRoles = append(schemeImpliedRoles, defaultTeamGuestRole) } else { schemeImpliedRoles = append(schemeImpliedRoles, model.CHANNEL_GUEST_ROLE_ID) } } - if db.SchemeUser.Valid && db.SchemeUser.Bool { - if db.ChannelSchemeDefaultUserRole.Valid && db.ChannelSchemeDefaultUserRole.String != "" { - schemeImpliedRoles = append(schemeImpliedRoles, db.ChannelSchemeDefaultUserRole.String) - } else if db.TeamSchemeDefaultUserRole.Valid && db.TeamSchemeDefaultUserRole.String != "" { - schemeImpliedRoles = append(schemeImpliedRoles, db.TeamSchemeDefaultUserRole.String) + if result.schemeUser { + if defaultChannelUserRole != "" { + schemeImpliedRoles = append(schemeImpliedRoles, defaultChannelUserRole) + } else if defaultTeamUserRole != "" { + schemeImpliedRoles = append(schemeImpliedRoles, defaultTeamUserRole) } else { schemeImpliedRoles = append(schemeImpliedRoles, model.CHANNEL_USER_ROLE_ID) } } - if db.SchemeAdmin.Valid && db.SchemeAdmin.Bool { - if db.ChannelSchemeDefaultAdminRole.Valid && db.ChannelSchemeDefaultAdminRole.String != "" { - schemeImpliedRoles = append(schemeImpliedRoles, db.ChannelSchemeDefaultAdminRole.String) - } else if db.TeamSchemeDefaultAdminRole.Valid && db.TeamSchemeDefaultAdminRole.String != "" { - schemeImpliedRoles = append(schemeImpliedRoles, db.TeamSchemeDefaultAdminRole.String) + if result.schemeAdmin { + if defaultChannelAdminRole != "" { + schemeImpliedRoles = append(schemeImpliedRoles, defaultChannelAdminRole) + } else if defaultTeamAdminRole != "" { + schemeImpliedRoles = append(schemeImpliedRoles, defaultTeamAdminRole) } else { schemeImpliedRoles = append(schemeImpliedRoles, model.CHANNEL_ADMIN_ROLE_ID) } } for _, impliedRole := range schemeImpliedRoles { alreadyThere := false - for _, role := range roles { + for _, role := range result.roles { if role == impliedRole { alreadyThere = true + break } } if !alreadyThere { - roles = append(roles, impliedRole) + result.roles = append(result.roles, impliedRole) } } + return result +} +func (db channelMemberWithSchemeRoles) ToModel() *model.ChannelMember { + // Identify any system-wide scheme derived roles that are in "Roles" field due to not yet being migrated, + // and exclude them from ExplicitRoles field. + schemeGuest := db.SchemeGuest.Valid && db.SchemeGuest.Bool + schemeUser := db.SchemeUser.Valid && db.SchemeUser.Bool + schemeAdmin := db.SchemeAdmin.Valid && db.SchemeAdmin.Bool + + defaultTeamGuestRole := "" + if db.TeamSchemeDefaultGuestRole.Valid { + defaultTeamGuestRole = db.TeamSchemeDefaultGuestRole.String + } + + defaultTeamUserRole := "" + if db.TeamSchemeDefaultUserRole.Valid { + defaultTeamUserRole = db.TeamSchemeDefaultUserRole.String + } + + defaultTeamAdminRole := "" + if db.TeamSchemeDefaultAdminRole.Valid { + defaultTeamAdminRole = db.TeamSchemeDefaultAdminRole.String + } + + defaultChannelGuestRole := "" + if db.ChannelSchemeDefaultGuestRole.Valid { + defaultChannelGuestRole = db.ChannelSchemeDefaultGuestRole.String + } + + defaultChannelUserRole := "" + if db.ChannelSchemeDefaultUserRole.Valid { + defaultChannelUserRole = db.ChannelSchemeDefaultUserRole.String + } + + defaultChannelAdminRole := "" + if db.ChannelSchemeDefaultAdminRole.Valid { + defaultChannelAdminRole = db.ChannelSchemeDefaultAdminRole.String + } + + rolesResult := getChannelRoles( + schemeGuest, schemeUser, schemeAdmin, + defaultTeamGuestRole, defaultTeamUserRole, defaultTeamAdminRole, + defaultChannelGuestRole, defaultChannelUserRole, defaultChannelAdminRole, + strings.Fields(db.Roles), + ) return &model.ChannelMember{ ChannelId: db.ChannelId, UserId: db.UserId, - Roles: strings.Join(roles, " "), + Roles: strings.Join(rolesResult.roles, " "), LastViewedAt: db.LastViewedAt, MsgCount: db.MsgCount, MentionCount: db.MentionCount, NotifyProps: db.NotifyProps, LastUpdateAt: db.LastUpdateAt, - SchemeAdmin: schemeAdmin, - SchemeUser: schemeUser, - SchemeGuest: schemeGuest, - ExplicitRoles: strings.Join(explicitRoles, " "), + SchemeAdmin: rolesResult.schemeAdmin, + SchemeUser: rolesResult.schemeUser, + SchemeGuest: rolesResult.schemeGuest, + ExplicitRoles: strings.Join(rolesResult.explicitRoles, " "), } } @@ -526,21 +586,15 @@ func (s SqlChannelStore) SaveDirectChannel(directchannel *model.Channel, member1 member1.ChannelId = newChannel.Id member2.ChannelId = newChannel.Id - _, member1SaveErr := s.saveMemberT(transaction, member1) - member2SaveErr := member1SaveErr + var memberSaveErr *model.AppError if member1.UserId != member2.UserId { - _, member2SaveErr = s.saveMemberT(transaction, member2) + _, memberSaveErr = s.saveMultipleMembersT(transaction, []*model.ChannelMember{member1, member2}) + } else { + _, memberSaveErr = s.saveMemberT(transaction, member2) } - if member1SaveErr != nil || member2SaveErr != nil { - details := "" - if member1SaveErr != nil { - details += "Member1Err: " + member1SaveErr.Message - } - if member2SaveErr != nil { - details += "Member2Err: " + member2SaveErr.Message - } - return nil, model.NewAppError("SqlChannelStore.SaveDirectChannel", "store.sql_channel.save_direct_channel.add_members.app_error", nil, details, http.StatusInternalServerError) + if memberSaveErr != nil { + return nil, model.NewAppError("SqlChannelStore.SaveDirectChannel", "store.sql_channel.save_direct_channel.add_members.app_error", nil, memberSaveErr.Error(), http.StatusInternalServerError) } if err := transaction.Commit(); err != nil { @@ -1252,8 +1306,10 @@ var CHANNEL_MEMBERS_WITH_SCHEME_SELECT_QUERY = ` Schemes TeamScheme ON Teams.SchemeId = TeamScheme.Id ` -func (s SqlChannelStore) SaveMember(member *model.ChannelMember) (*model.ChannelMember, *model.AppError) { - defer s.InvalidateAllChannelMembersForUser(member.UserId) +func (s SqlChannelStore) SaveMultipleMembers(members []*model.ChannelMember) ([]*model.ChannelMember, *model.AppError) { + for _, member := range members { + defer s.InvalidateAllChannelMembersForUser(member.UserId) + } transaction, err := s.GetMaster().Begin() if err != nil { @@ -1261,7 +1317,7 @@ func (s SqlChannelStore) SaveMember(member *model.ChannelMember) (*model.Channel } defer finalizeTransaction(transaction) - newMember, appErr := s.saveMemberT(transaction, member) + newMembers, appErr := s.saveMultipleMembersT(transaction, members) if appErr != nil { return nil, appErr } @@ -1270,55 +1326,212 @@ func (s SqlChannelStore) SaveMember(member *model.ChannelMember) (*model.Channel return nil, model.NewAppError("SqlChannelStore.SaveMember", "store.sql_channel.save_member.commit_transaction.app_error", nil, err.Error(), http.StatusInternalServerError) } - return newMember, nil + return newMembers, nil +} + +func (s SqlChannelStore) SaveMember(member *model.ChannelMember) (*model.ChannelMember, *model.AppError) { + newMembers, appErr := s.SaveMultipleMembers([]*model.ChannelMember{member}) + if appErr != nil { + return nil, appErr + } + return newMembers[0], nil +} + +func (s SqlChannelStore) saveMultipleMembersT(transaction *gorp.Transaction, members []*model.ChannelMember) ([]*model.ChannelMember, *model.AppError) { + newChannelMembers := map[string]int{} + users := map[string]bool{} + for _, member := range members { + if val, ok := newChannelMembers[member.ChannelId]; val < 1 || !ok { + newChannelMembers[member.ChannelId] = 1 + } else { + newChannelMembers[member.ChannelId]++ + } + users[member.UserId] = true + + member.PreSave() + if err := member.IsValid(); err != nil { + return nil, err + } + } + + channels := []string{} + for channel := range newChannelMembers { + channels = append(channels, channel) + } + + defaultChannelRolesByChannel := map[string]struct { + Id string + Guest sql.NullString + User sql.NullString + Admin sql.NullString + }{} + + channelRolesQuery := s.getQueryBuilder(). + Select( + "Channels.Id as Id", + "ChannelScheme.DefaultChannelGuestRole as Guest", + "ChannelScheme.DefaultChannelUserRole as User", + "ChannelScheme.DefaultChannelAdminRole as Admin", + ). + From("Channels"). + LeftJoin("Schemes ChannelScheme ON Channels.SchemeId = ChannelScheme.Id"). + Where(sq.Eq{"Channels.Id": channels}) + + channelRolesSql, channelRolesArgs, err := channelRolesQuery.ToSql() + if err != nil { + return nil, model.NewAppError("SqlChannelStore.SaveMultipleMembers", "store.sql_channel.save_multimple_members.channel_roles.app_error", nil, err.Error(), http.StatusInternalServerError) + } + + var defaultChannelsRoles []struct { + Id string + Guest sql.NullString + User sql.NullString + Admin sql.NullString + } + _, err = s.GetMaster().Select(&defaultChannelsRoles, channelRolesSql, channelRolesArgs...) + if err != nil { + return nil, model.NewAppError("SqlChannelStore.SaveMultipleMembers", "store.sql_channel.save_multimple_members.channel_roles_query.app_error", nil, err.Error(), http.StatusInternalServerError) + } + + for _, defaultRoles := range defaultChannelsRoles { + defaultChannelRolesByChannel[defaultRoles.Id] = defaultRoles + } + + defaultTeamRolesByChannel := map[string]struct { + Id string + Guest sql.NullString + User sql.NullString + Admin sql.NullString + }{} + + teamRolesQuery := s.getQueryBuilder(). + Select( + "Channels.Id as Id", + "TeamScheme.DefaultChannelGuestRole as Guest", + "TeamScheme.DefaultChannelUserRole as User", + "TeamScheme.DefaultChannelAdminRole as Admin", + ). + From("Channels"). + LeftJoin("Teams ON Teams.Id = Channels.TeamId"). + LeftJoin("Schemes TeamScheme ON Teams.SchemeId = TeamScheme.Id"). + Where(sq.Eq{"Channels.Id": channels}) + + teamRolesSql, teamRolesArgs, err := teamRolesQuery.ToSql() + if err != nil { + return nil, model.NewAppError("SqlChannelStore.SaveMultipleMembers", "store.sql_channel.save_multimple_members.team_roles.app_error", nil, err.Error(), http.StatusInternalServerError) + } + + var defaultTeamsRoles []struct { + Id string + Guest sql.NullString + User sql.NullString + Admin sql.NullString + } + _, err = s.GetMaster().Select(&defaultTeamsRoles, teamRolesSql, teamRolesArgs...) + if err != nil { + return nil, model.NewAppError("SqlChannelStore.SaveMultipleMembers", "store.sql_channel.save_multimple_members.team_roles_query.app_error", nil, err.Error(), http.StatusInternalServerError) + } + + for _, defaultRoles := range defaultTeamsRoles { + defaultTeamRolesByChannel[defaultRoles.Id] = defaultRoles + } + + query := s.getQueryBuilder().Insert("ChannelMembers").Columns(channelMemberSliceColumns()...) + for _, member := range members { + query = query.Values(channelMemberToSlice(member)...) + } + + sql, args, err := query.ToSql() + if err != nil { + return nil, model.NewAppError("SqlTeamStore.SaveMember", "store.sql_channel.save_member.save.app_error", nil, err.Error(), http.StatusInternalServerError) + } + + if _, err := s.GetMaster().Exec(sql, args...); err != nil { + if IsUniqueConstraintError(err, []string{"ChannelId", "channelmembers_pkey", "PRIMARY"}) { + return nil, model.NewAppError("SqlTeamStore.SaveMember", "store.sql_channel.save_member.exists.app_error", nil, err.Error(), http.StatusBadRequest) + } + return nil, model.NewAppError("SqlTeamStore.SaveMember", "store.sql_channel.save_member.save.app_error", nil, err.Error(), http.StatusInternalServerError) + } + + newMembers := []*model.ChannelMember{} + for _, member := range members { + defaultTeamGuestRole := defaultTeamRolesByChannel[member.ChannelId].Guest.String + defaultTeamUserRole := defaultTeamRolesByChannel[member.ChannelId].User.String + defaultTeamAdminRole := defaultTeamRolesByChannel[member.ChannelId].Admin.String + defaultChannelGuestRole := defaultChannelRolesByChannel[member.ChannelId].Guest.String + defaultChannelUserRole := defaultChannelRolesByChannel[member.ChannelId].User.String + defaultChannelAdminRole := defaultChannelRolesByChannel[member.ChannelId].Admin.String + rolesResult := getChannelRoles( + member.SchemeGuest, member.SchemeUser, member.SchemeAdmin, + defaultTeamGuestRole, defaultTeamUserRole, defaultTeamAdminRole, + defaultChannelGuestRole, defaultChannelUserRole, defaultChannelAdminRole, + strings.Fields(member.ExplicitRoles), + ) + newMember := *member + newMember.SchemeGuest = rolesResult.schemeGuest + newMember.SchemeUser = rolesResult.schemeUser + newMember.SchemeAdmin = rolesResult.schemeAdmin + newMember.Roles = strings.Join(rolesResult.roles, " ") + newMember.ExplicitRoles = strings.Join(rolesResult.explicitRoles, " ") + newMembers = append(newMembers, &newMember) + } + return newMembers, nil } func (s SqlChannelStore) saveMemberT(transaction *gorp.Transaction, member *model.ChannelMember) (*model.ChannelMember, *model.AppError) { - member.PreSave() - if err := member.IsValid(); err != nil { + members, err := s.saveMultipleMembersT(transaction, []*model.ChannelMember{member}) + if err != nil { return nil, err } + return members[0], nil +} - dbMember := NewChannelMemberFromModel(member) +func (s SqlChannelStore) UpdateMultipleMembers(members []*model.ChannelMember) ([]*model.ChannelMember, *model.AppError) { + for _, member := range members { + member.PreUpdate() - if err := transaction.Insert(dbMember); err != nil { - if IsUniqueConstraintError(err, []string{"ChannelId", "channelmembers_pkey"}) { - return nil, model.NewAppError("SqlChannelStore.SaveMember", "store.sql_channel.save_member.exists.app_error", nil, "channel_id="+member.ChannelId+", user_id="+member.UserId+", "+err.Error(), http.StatusBadRequest) + if err := member.IsValid(); err != nil { + return nil, err } - return nil, model.NewAppError("SqlChannelStore.SaveMember", "store.sql_channel.save_member.save.app_error", nil, "channel_id="+member.ChannelId+", user_id="+member.UserId+", "+err.Error(), http.StatusInternalServerError) } - var retrievedMember channelMemberWithSchemeRoles - if err := transaction.SelectOne(&retrievedMember, CHANNEL_MEMBERS_WITH_SCHEME_SELECT_QUERY+"WHERE ChannelMembers.ChannelId = :ChannelId AND ChannelMembers.UserId = :UserId", map[string]interface{}{"ChannelId": dbMember.ChannelId, "UserId": dbMember.UserId}); err != nil { - if err == sql.ErrNoRows { - return nil, model.NewAppError("SqlChannelStore.GetMember", store.MISSING_CHANNEL_MEMBER_ERROR, nil, "channel_id="+dbMember.ChannelId+"user_id="+dbMember.UserId+","+err.Error(), http.StatusNotFound) + var transaction *gorp.Transaction + var err error + + if transaction, err = s.GetMaster().Begin(); err != nil { + return nil, model.NewAppError("SqlChannelStore.MigrateChannelMembers", "store.sql_channel.migrate_channel_members.open_transaction.app_error", nil, err.Error(), http.StatusInternalServerError) + } + defer finalizeTransaction(transaction) + + updatedMembers := []*model.ChannelMember{} + for _, member := range members { + if _, err := transaction.Update(NewChannelMemberFromModel(member)); err != nil { + return nil, model.NewAppError("SqlChannelStore.UpdateMember", "store.sql_channel.update_member.app_error", nil, "channel_id="+member.ChannelId+", "+"user_id="+member.UserId+", "+err.Error(), http.StatusInternalServerError) } - return nil, model.NewAppError("SqlChannelStore.GetMember", "store.sql_channel.get_member.app_error", nil, "channel_id="+dbMember.ChannelId+"user_id="+dbMember.UserId+","+err.Error(), http.StatusInternalServerError) + + // TODO: Get this out of the transaction when is possible + var dbMember channelMemberWithSchemeRoles + if err := transaction.SelectOne(&dbMember, CHANNEL_MEMBERS_WITH_SCHEME_SELECT_QUERY+"WHERE ChannelMembers.ChannelId = :ChannelId AND ChannelMembers.UserId = :UserId", map[string]interface{}{"ChannelId": member.ChannelId, "UserId": member.UserId}); err != nil { + if err == sql.ErrNoRows { + return nil, model.NewAppError("SqlChannelStore.GetMember", store.MISSING_CHANNEL_MEMBER_ERROR, nil, "channel_id="+member.ChannelId+"user_id="+member.UserId+","+err.Error(), http.StatusNotFound) + } + return nil, model.NewAppError("SqlChannelStore.GetMember", "store.sql_channel.get_member.app_error", nil, "channel_id="+member.ChannelId+"user_id="+member.UserId+","+err.Error(), http.StatusInternalServerError) + } + updatedMembers = append(updatedMembers, dbMember.ToModel()) } - return retrievedMember.ToModel(), nil + if err := transaction.Commit(); err != nil { + return nil, model.NewAppError("SqlChannelStore.MigrateChannelMembers", "store.sql_channel.migrate_channel_members.commit_transaction.app_error", nil, err.Error(), http.StatusInternalServerError) + } + return updatedMembers, nil } func (s SqlChannelStore) UpdateMember(member *model.ChannelMember) (*model.ChannelMember, *model.AppError) { - member.PreUpdate() - - if err := member.IsValid(); err != nil { + updatedMembers, err := s.UpdateMultipleMembers([]*model.ChannelMember{member}) + if err != nil { return nil, err } - - if _, err := s.GetMaster().Update(NewChannelMemberFromModel(member)); err != nil { - return nil, model.NewAppError("SqlChannelStore.UpdateMember", "store.sql_channel.update_member.app_error", nil, "channel_id="+member.ChannelId+", "+"user_id="+member.UserId+", "+err.Error(), http.StatusInternalServerError) - } - - var dbMember channelMemberWithSchemeRoles - - if err := s.GetReplica().SelectOne(&dbMember, CHANNEL_MEMBERS_WITH_SCHEME_SELECT_QUERY+"WHERE ChannelMembers.ChannelId = :ChannelId AND ChannelMembers.UserId = :UserId", map[string]interface{}{"ChannelId": member.ChannelId, "UserId": member.UserId}); err != nil { - if err == sql.ErrNoRows { - return nil, model.NewAppError("SqlChannelStore.GetMember", store.MISSING_CHANNEL_MEMBER_ERROR, nil, "channel_id="+member.ChannelId+"user_id="+member.UserId+","+err.Error(), http.StatusNotFound) - } - return nil, model.NewAppError("SqlChannelStore.GetMember", "store.sql_channel.get_member.app_error", nil, "channel_id="+member.ChannelId+"user_id="+member.UserId+","+err.Error(), http.StatusInternalServerError) - } - return dbMember.ToModel(), nil + return updatedMembers[0], nil } func (s SqlChannelStore) GetMembers(channelId string, offset, limit int) (*model.ChannelMembers, *model.AppError) { @@ -1606,14 +1819,26 @@ func (s SqlChannelStore) GetGuestCount(channelId string, allowFromCache bool) (i return count, nil } -func (s SqlChannelStore) RemoveMember(channelId string, userId string) *model.AppError { - _, err := s.GetMaster().Exec("DELETE FROM ChannelMembers WHERE ChannelId = :ChannelId AND UserId = :UserId", map[string]interface{}{"ChannelId": channelId, "UserId": userId}) +func (s SqlChannelStore) RemoveMembers(channelId string, userIds []string) *model.AppError { + query := s.getQueryBuilder(). + Delete("ChannelMembers"). + Where(sq.Eq{"ChannelId": channelId}). + Where(sq.Eq{"UserId": userIds}) + sql, args, err := query.ToSql() if err != nil { - return model.NewAppError("SqlChannelStore.RemoveMember", "store.sql_channel.remove_member.app_error", nil, "channel_id="+channelId+", user_id="+userId+", "+err.Error(), http.StatusInternalServerError) + return model.NewAppError("SqlChannelStore.RemoveMember", "store.sql_channel.remove_member.app_error", nil, "channel_id="+channelId+", "+err.Error(), http.StatusInternalServerError) + } + _, err = s.GetMaster().Exec(sql, args...) + if err != nil { + return model.NewAppError("SqlChannelStore.RemoveMember", "store.sql_channel.remove_member.app_error", nil, "channel_id="+channelId+", "+err.Error(), http.StatusInternalServerError) } return nil } +func (s SqlChannelStore) RemoveMember(channelId string, userId string) *model.AppError { + return s.RemoveMembers(channelId, []string{userId}) +} + func (s SqlChannelStore) RemoveAllDeactivatedMembers(channelId string) *model.AppError { query := ` DELETE diff --git a/store/sqlstore/channel_store_test.go b/store/sqlstore/channel_store_test.go index 99880fa686..7fe4c28240 100644 --- a/store/sqlstore/channel_store_test.go +++ b/store/sqlstore/channel_store_test.go @@ -154,7 +154,7 @@ func testChannelMemberWithSchemeRolesToModel(t *testing.T) { cm := db.ToModel() - assert.Equal(t, "channel_admin channel_user", cm.Roles) + assert.Equal(t, "channel_user channel_admin", cm.Roles) assert.Equal(t, false, cm.SchemeGuest) assert.Equal(t, true, cm.SchemeUser) assert.Equal(t, true, cm.SchemeAdmin) @@ -199,7 +199,7 @@ func testChannelMemberWithSchemeRolesToModel(t *testing.T) { cm := db.ToModel() - assert.Equal(t, "channel_user custom_role", cm.Roles) + assert.Equal(t, "custom_role channel_user", cm.Roles) assert.Equal(t, false, cm.SchemeGuest) assert.Equal(t, true, cm.SchemeUser) assert.Equal(t, false, cm.SchemeAdmin) @@ -222,7 +222,7 @@ func testChannelMemberWithSchemeRolesToModel(t *testing.T) { cm := db.ToModel() - assert.Equal(t, "channel_user channel_admin custom_role", cm.Roles) + assert.Equal(t, "custom_role channel_user channel_admin", cm.Roles) assert.Equal(t, false, cm.SchemeGuest) assert.Equal(t, true, cm.SchemeUser) assert.Equal(t, true, cm.SchemeAdmin) diff --git a/store/sqlstore/team_store.go b/store/sqlstore/team_store.go index 0e8a1c36b2..987a186ac1 100644 --- a/store/sqlstore/team_store.go +++ b/store/sqlstore/team_store.go @@ -61,82 +61,125 @@ type teamMemberWithSchemeRoles struct { type teamMemberWithSchemeRolesList []teamMemberWithSchemeRoles -func (db teamMemberWithSchemeRoles) ToModel() *model.TeamMember { - var roles []string - var explicitRoles []string +func teamMemberSliceColumns() []string { + return []string{"TeamId", "UserId", "Roles", "DeleteAt", "SchemeUser", "SchemeAdmin", "SchemeGuest"} +} +func teamMemberToSlice(member *model.TeamMember) []interface{} { + resultSlice := []interface{}{} + resultSlice = append(resultSlice, member.TeamId) + resultSlice = append(resultSlice, member.UserId) + resultSlice = append(resultSlice, member.ExplicitRoles) + resultSlice = append(resultSlice, member.DeleteAt) + resultSlice = append(resultSlice, member.SchemeUser) + resultSlice = append(resultSlice, member.SchemeAdmin) + resultSlice = append(resultSlice, member.SchemeGuest) + return resultSlice +} + +type rolesInfo struct { + roles []string + explicitRoles []string + schemeGuest bool + schemeUser bool + schemeAdmin bool +} + +func getTeamRoles(schemeGuest, schemeUser, schemeAdmin bool, defaultTeamGuestRole, defaultTeamUserRole, defaultTeamAdminRole string, roles []string) rolesInfo { + result := rolesInfo{ + roles: []string{}, + explicitRoles: []string{}, + schemeGuest: schemeGuest, + schemeUser: schemeUser, + schemeAdmin: schemeAdmin, + } // Identify any scheme derived roles that are in "Roles" field due to not yet being migrated, and exclude // them from ExplicitRoles field. - schemeGuest := db.SchemeGuest.Valid && db.SchemeGuest.Bool - schemeUser := db.SchemeUser.Valid && db.SchemeUser.Bool - schemeAdmin := db.SchemeAdmin.Valid && db.SchemeAdmin.Bool - for _, role := range strings.Fields(db.Roles) { - isImplicit := false - if role == model.TEAM_GUEST_ROLE_ID { - // We have an implicit role via the system scheme. Override the "schemeGuest" field to true. - schemeGuest = true - isImplicit = true - } else if role == model.TEAM_USER_ROLE_ID { - // We have an implicit role via the system scheme. Override the "schemeUser" field to true. - schemeUser = true - isImplicit = true - } else if role == model.TEAM_ADMIN_ROLE_ID { - // We have an implicit role via the system scheme. - schemeAdmin = true - isImplicit = true + for _, role := range roles { + switch role { + case model.TEAM_GUEST_ROLE_ID: + result.schemeGuest = true + case model.TEAM_USER_ROLE_ID: + result.schemeUser = true + case model.TEAM_ADMIN_ROLE_ID: + result.schemeAdmin = true + default: + result.explicitRoles = append(result.explicitRoles, role) + result.roles = append(result.roles, role) } - - if !isImplicit { - explicitRoles = append(explicitRoles, role) - } - roles = append(roles, role) } // Add any scheme derived roles that are not in the Roles field due to being Implicit from the Scheme, and add // them to the Roles field for backwards compatibility reasons. var schemeImpliedRoles []string - if db.SchemeGuest.Valid && db.SchemeGuest.Bool { - if db.TeamSchemeDefaultGuestRole.Valid && db.TeamSchemeDefaultGuestRole.String != "" { - schemeImpliedRoles = append(schemeImpliedRoles, db.TeamSchemeDefaultGuestRole.String) + if result.schemeGuest { + if defaultTeamGuestRole != "" { + schemeImpliedRoles = append(schemeImpliedRoles, defaultTeamGuestRole) } else { schemeImpliedRoles = append(schemeImpliedRoles, model.TEAM_GUEST_ROLE_ID) } } - if db.SchemeUser.Valid && db.SchemeUser.Bool { - if db.TeamSchemeDefaultUserRole.Valid && db.TeamSchemeDefaultUserRole.String != "" { - schemeImpliedRoles = append(schemeImpliedRoles, db.TeamSchemeDefaultUserRole.String) + if result.schemeUser { + if defaultTeamUserRole != "" { + schemeImpliedRoles = append(schemeImpliedRoles, defaultTeamUserRole) } else { schemeImpliedRoles = append(schemeImpliedRoles, model.TEAM_USER_ROLE_ID) } } - if db.SchemeAdmin.Valid && db.SchemeAdmin.Bool { - if db.TeamSchemeDefaultAdminRole.Valid && db.TeamSchemeDefaultAdminRole.String != "" { - schemeImpliedRoles = append(schemeImpliedRoles, db.TeamSchemeDefaultAdminRole.String) + if result.schemeAdmin { + if defaultTeamAdminRole != "" { + schemeImpliedRoles = append(schemeImpliedRoles, defaultTeamAdminRole) } else { schemeImpliedRoles = append(schemeImpliedRoles, model.TEAM_ADMIN_ROLE_ID) } } for _, impliedRole := range schemeImpliedRoles { alreadyThere := false - for _, role := range roles { + for _, role := range result.roles { if role == impliedRole { alreadyThere = true } } if !alreadyThere { - roles = append(roles, impliedRole) + result.roles = append(result.roles, impliedRole) } } + return result +} + +func (db teamMemberWithSchemeRoles) ToModel() *model.TeamMember { + // Identify any scheme derived roles that are in "Roles" field due to not yet being migrated, and exclude + // them from ExplicitRoles field. + schemeGuest := db.SchemeGuest.Valid && db.SchemeGuest.Bool + schemeUser := db.SchemeUser.Valid && db.SchemeUser.Bool + schemeAdmin := db.SchemeAdmin.Valid && db.SchemeAdmin.Bool + + defaultTeamGuestRole := "" + if db.TeamSchemeDefaultGuestRole.Valid { + defaultTeamGuestRole = db.TeamSchemeDefaultGuestRole.String + } + + defaultTeamUserRole := "" + if db.TeamSchemeDefaultUserRole.Valid { + defaultTeamUserRole = db.TeamSchemeDefaultUserRole.String + } + + defaultTeamAdminRole := "" + if db.TeamSchemeDefaultAdminRole.Valid { + defaultTeamAdminRole = db.TeamSchemeDefaultAdminRole.String + } + + rolesResult := getTeamRoles(schemeGuest, schemeUser, schemeAdmin, defaultTeamGuestRole, defaultTeamUserRole, defaultTeamAdminRole, strings.Fields(db.Roles)) tm := &model.TeamMember{ TeamId: db.TeamId, UserId: db.UserId, - Roles: strings.Join(roles, " "), + Roles: strings.Join(rolesResult.roles, " "), DeleteAt: db.DeleteAt, - SchemeGuest: schemeGuest, - SchemeUser: schemeUser, - SchemeAdmin: schemeAdmin, - ExplicitRoles: strings.Join(explicitRoles, " "), + SchemeGuest: rolesResult.schemeGuest, + SchemeUser: rolesResult.schemeUser, + SchemeAdmin: rolesResult.schemeAdmin, + ExplicitRoles: strings.Join(rolesResult.explicitRoles, " "), } return tm } @@ -562,99 +605,224 @@ func (s SqlTeamStore) getTeamMembersWithSchemeSelectQuery() sq.SelectBuilder { LeftJoin("Schemes TeamScheme ON Teams.SchemeId = TeamScheme.Id") } -// SaveMember adds a team member using the team Id of the member -// if the member does not already exist in the database and if the number -// of existing team members are less than the maximum allowed users per team. -// It returns the team member added if the operation is successful. -func (s SqlTeamStore) SaveMember(member *model.TeamMember, maxUsersPerTeam int) (*model.TeamMember, *model.AppError) { - defer s.InvalidateAllTeamIdsForUser(member.UserId) - if err := member.IsValid(); err != nil { - return nil, err +func (s SqlTeamStore) SaveMultipleMembers(members []*model.TeamMember, maxUsersPerTeam int) ([]*model.TeamMember, *model.AppError) { + newTeamMembers := map[string]int{} + users := map[string]bool{} + for _, member := range members { + newTeamMembers[member.TeamId] = 0 + } + + for _, member := range members { + newTeamMembers[member.TeamId]++ + users[member.UserId] = true + + if err := member.IsValid(); err != nil { + return nil, err + } + } + + teams := []string{} + for team := range newTeamMembers { + teams = append(teams, team) + } + + defaultTeamRolesByTeam := map[string]struct { + Id string + Guest sql.NullString + User sql.NullString + Admin sql.NullString + }{} + + queryRoles := s.getQueryBuilder(). + Select( + "Teams.Id as Id", + "TeamScheme.DefaultTeamGuestRole as Guest", + "TeamScheme.DefaultTeamUserRole as User", + "TeamScheme.DefaultTeamAdminRole as Admin", + ). + From("Teams"). + LeftJoin("Schemes TeamScheme ON Teams.SchemeId = TeamScheme.Id"). + Where(sq.Eq{"Teams.Id": teams}) + + sqlRolesQuery, argsRoles, err := queryRoles.ToSql() + if err != nil { + return nil, model.NewAppError("SqlUserStore.Save", "store.sql_user.save.member_count.app_error", nil, err.Error(), http.StatusInternalServerError) + } + var defaultTeamsRoles []struct { + Id string + Guest sql.NullString + User sql.NullString + Admin sql.NullString + } + _, err = s.GetMaster().Select(&defaultTeamsRoles, sqlRolesQuery, argsRoles...) + if err != nil { + return nil, model.NewAppError("SqlUserStore.Save", "store.sql_user.save.member_count.app_error", nil, err.Error(), http.StatusInternalServerError) + } + + for _, defaultRoles := range defaultTeamsRoles { + defaultTeamRolesByTeam[defaultRoles.Id] = defaultRoles } - dbMember := NewTeamMemberFromModel(member) if maxUsersPerTeam >= 0 { - count, err := s.GetMaster().SelectInt( - `SELECT - COUNT(0) - FROM - TeamMembers - INNER JOIN - Users - ON - TeamMembers.UserId = Users.Id - WHERE - TeamId = :TeamId - AND TeamMembers.DeleteAt = 0 - AND Users.DeleteAt = 0`, map[string]interface{}{"TeamId": member.TeamId}) + queryCount := s.getQueryBuilder(). + Select( + "COUNT(0) as Count, TeamMembers.TeamId as TeamId", + ). + From("TeamMembers"). + Join("Users ON TeamMembers.UserId = Users.Id"). + Where(sq.Eq{"TeamMembers.TeamId": teams}). + Where(sq.Eq{"TeamMembers.DeleteAt": 0}). + Where(sq.Eq{"Users.DeleteAt": 0}). + GroupBy("TeamMembers.TeamId") + sqlCountQuery, argsCount, errCount := queryCount.ToSql() + if errCount != nil { + return nil, model.NewAppError("SqlUserStore.Save", "store.sql_user.save.member_count.app_error", nil, errCount.Error(), http.StatusInternalServerError) + } + + var counters []struct { + Count int `db:"Count"` + TeamId string `db:"TeamId"` + } + + _, err = s.GetMaster().Select(&counters, sqlCountQuery, argsCount...) if err != nil { - return nil, model.NewAppError("SqlUserStore.Save", "store.sql_user.save.member_count.app_error", nil, "teamId="+member.TeamId+", "+err.Error(), http.StatusInternalServerError) + return nil, model.NewAppError("SqlUserStore.Save", "store.sql_user.save.member_count.app_error", nil, err.Error(), http.StatusInternalServerError) } - if count >= int64(maxUsersPerTeam) { - return nil, model.NewAppError("SqlUserStore.Save", "store.sql_user.save.max_accounts.app_error", nil, "teamId="+member.TeamId, http.StatusBadRequest) + for teamId, newMembers := range newTeamMembers { + existingMembers := 0 + for _, counter := range counters { + if counter.TeamId == teamId { + existingMembers = counter.Count + } + } + if existingMembers+newMembers > maxUsersPerTeam { + return nil, model.NewAppError("SqlUserStore.Save", "store.sql_user.save.max_accounts.app_error", nil, "", http.StatusBadRequest) + } } } - if err := s.GetMaster().Insert(dbMember); err != nil { - if IsUniqueConstraintError(err, []string{"TeamId", "teammembers_pkey", "PRIMARY"}) { - return nil, model.NewAppError("SqlTeamStore.SaveMember", TEAM_MEMBER_EXISTS_ERROR, nil, "team_id="+member.TeamId+", user_id="+member.UserId+", "+err.Error(), http.StatusBadRequest) - } - return nil, model.NewAppError("SqlTeamStore.SaveMember", "store.sql_team.save_member.save.app_error", nil, "team_id="+member.TeamId+", user_id="+member.UserId+", "+err.Error(), http.StatusInternalServerError) + query := s.getQueryBuilder().Insert("TeamMembers").Columns(teamMemberSliceColumns()...) + for _, member := range members { + query = query.Values(teamMemberToSlice(member)...) } - query := s.getTeamMembersWithSchemeSelectQuery(). - Where(sq.Eq{"TeamMembers.TeamId": dbMember.TeamId}). - Where(sq.Eq{"TeamMembers.UserId": dbMember.UserId}) - - queryString, args, err := query.ToSql() + sql, args, err := query.ToSql() if err != nil { - return nil, model.NewAppError("SqlTeamStore.SaveMember", "store.sql_team.get_member.app_error", nil, err.Error(), http.StatusInternalServerError) + return nil, model.NewAppError("SqlTeamStore.SaveMember", "store.sql_team.save_member.save.app_error", nil, err.Error(), http.StatusInternalServerError) } - var retrievedMember teamMemberWithSchemeRoles - if err := s.GetMaster().SelectOne(&retrievedMember, queryString, args...); err != nil { - if err == sql.ErrNoRows { - return nil, model.NewAppError("SqlTeamStore.SaveMember", "store.sql_team.get_member.missing.app_error", nil, "team_id="+dbMember.TeamId+"user_id="+dbMember.UserId+","+err.Error(), http.StatusNotFound) + if _, err := s.GetMaster().Exec(sql, args...); err != nil { + if IsUniqueConstraintError(err, []string{"TeamId", "teammembers_pkey", "PRIMARY"}) { + return nil, model.NewAppError("SqlTeamStore.SaveMember", TEAM_MEMBER_EXISTS_ERROR, nil, err.Error(), http.StatusBadRequest) } - return nil, model.NewAppError("SqlTeamStore.SaveMember", "store.sql_team.get_member.app_error", nil, "team_id="+dbMember.TeamId+"user_id="+dbMember.UserId+","+err.Error(), http.StatusInternalServerError) + return nil, model.NewAppError("SqlTeamStore.SaveMember", "store.sql_team.save_member.save.app_error", nil, err.Error(), http.StatusInternalServerError) } - return retrievedMember.ToModel(), nil + newMembers := []*model.TeamMember{} + for _, member := range members { + s.InvalidateAllTeamIdsForUser(member.UserId) + defaultTeamGuestRole := defaultTeamRolesByTeam[member.TeamId].Guest.String + defaultTeamUserRole := defaultTeamRolesByTeam[member.TeamId].User.String + defaultTeamAdminRole := defaultTeamRolesByTeam[member.TeamId].Admin.String + rolesResult := getTeamRoles(member.SchemeGuest, member.SchemeUser, member.SchemeAdmin, defaultTeamGuestRole, defaultTeamUserRole, defaultTeamAdminRole, strings.Fields(member.ExplicitRoles)) + newMember := *member + newMember.SchemeGuest = rolesResult.schemeGuest + newMember.SchemeUser = rolesResult.schemeUser + newMember.SchemeAdmin = rolesResult.schemeAdmin + newMember.Roles = strings.Join(rolesResult.roles, " ") + newMember.ExplicitRoles = strings.Join(rolesResult.explicitRoles, " ") + newMembers = append(newMembers, &newMember) + } + + return newMembers, nil } -// UpdateMember updates the team member if the team member exists in the database. -// It returns the updated team member if the operation is successful. -func (s SqlTeamStore) UpdateMember(member *model.TeamMember) (*model.TeamMember, *model.AppError) { - member.PreUpdate() - - if err := member.IsValid(); err != nil { +func (s SqlTeamStore) SaveMember(member *model.TeamMember, maxUsersPerTeam int) (*model.TeamMember, *model.AppError) { + members, err := s.SaveMultipleMembers([]*model.TeamMember{member}, maxUsersPerTeam) + if err != nil { return nil, err } + return members[0], nil +} - if _, err := s.GetMaster().Update(NewTeamMemberFromModel(member)); err != nil { - return nil, model.NewAppError("SqlTeamStore.UpdateMember", "store.sql_team.save_member.save.app_error", nil, err.Error(), http.StatusInternalServerError) - } +func (s SqlTeamStore) UpdateMultipleMembers(members []*model.TeamMember) ([]*model.TeamMember, *model.AppError) { + teams := []string{} + for _, member := range members { + member.PreUpdate() - query := s.getTeamMembersWithSchemeSelectQuery(). - Where(sq.Eq{"TeamMembers.TeamId": member.TeamId}). - Where(sq.Eq{"TeamMembers.UserId": member.UserId}) - - queryString, args, err := query.ToSql() - if err != nil { - return nil, model.NewAppError("SqlTeamStore.UpdateMember", "store.sql_team.get_member.app_error", nil, err.Error(), http.StatusInternalServerError) - } - - var retrievedMember teamMemberWithSchemeRoles - if err := s.GetMaster().SelectOne(&retrievedMember, queryString, args...); err != nil { - if err == sql.ErrNoRows { - return nil, model.NewAppError("SqlTeamStore.UpdateMember", "store.sql_team.get_member.missing.app_error", nil, "team_id="+member.TeamId+"user_id="+member.UserId+","+err.Error(), http.StatusNotFound) + if err := member.IsValid(); err != nil { + return nil, err } - return nil, model.NewAppError("SqlTeamStore.UpdateMember", "store.sql_team.get_member.app_error", nil, "team_id="+member.TeamId+"user_id="+member.UserId+","+err.Error(), http.StatusInternalServerError) + + if _, err := s.GetMaster().Update(NewTeamMemberFromModel(member)); err != nil { + return nil, model.NewAppError("SqlTeamStore.UpdateMember", "store.sql_team.save_member.save.app_error", nil, err.Error(), http.StatusInternalServerError) + } + teams = append(teams, member.TeamId) } - return retrievedMember.ToModel(), nil + query := s.getQueryBuilder(). + Select( + "Teams.Id as Id", + "TeamScheme.DefaultTeamGuestRole as Guest", + "TeamScheme.DefaultTeamUserRole as User", + "TeamScheme.DefaultTeamAdminRole as Admin", + ). + From("Teams"). + LeftJoin("Schemes TeamScheme ON Teams.SchemeId = TeamScheme.Id"). + Where(sq.Eq{"Teams.Id": teams}) + + sqlQuery, args, err := query.ToSql() + if err != nil { + return nil, model.NewAppError("SqlUserStore.Save", "store.sql_user.save.member_count.app_error", nil, err.Error(), http.StatusInternalServerError) + } + var defaultTeamsRoles []struct { + Id string + Guest sql.NullString + User sql.NullString + Admin sql.NullString + } + _, err = s.GetMaster().Select(&defaultTeamsRoles, sqlQuery, args...) + if err != nil { + return nil, model.NewAppError("SqlUserStore.Save", "store.sql_user.save.member_count.app_error", nil, err.Error(), http.StatusInternalServerError) + } + + defaultTeamRolesByTeam := map[string]struct { + Id string + Guest sql.NullString + User sql.NullString + Admin sql.NullString + }{} + for _, defaultRoles := range defaultTeamsRoles { + defaultTeamRolesByTeam[defaultRoles.Id] = defaultRoles + } + + updatedMembers := []*model.TeamMember{} + for _, member := range members { + s.InvalidateAllTeamIdsForUser(member.UserId) + defaultTeamGuestRole := defaultTeamRolesByTeam[member.TeamId].Guest.String + defaultTeamUserRole := defaultTeamRolesByTeam[member.TeamId].User.String + defaultTeamAdminRole := defaultTeamRolesByTeam[member.TeamId].Admin.String + rolesResult := getTeamRoles(member.SchemeGuest, member.SchemeUser, member.SchemeAdmin, defaultTeamGuestRole, defaultTeamUserRole, defaultTeamAdminRole, strings.Fields(member.ExplicitRoles)) + updatedMember := *member + updatedMember.SchemeGuest = rolesResult.schemeGuest + updatedMember.SchemeUser = rolesResult.schemeUser + updatedMember.SchemeAdmin = rolesResult.schemeAdmin + updatedMember.Roles = strings.Join(rolesResult.roles, " ") + updatedMember.ExplicitRoles = strings.Join(rolesResult.explicitRoles, " ") + updatedMembers = append(updatedMembers, &updatedMember) + } + + return updatedMembers, nil +} + +func (s SqlTeamStore) UpdateMember(member *model.TeamMember) (*model.TeamMember, *model.AppError) { + members, err := s.UpdateMultipleMembers([]*model.TeamMember{member}) + if err != nil { + return nil, err + } + return members[0], nil } func (s SqlTeamStore) GetMember(teamId string, userId string) (*model.TeamMember, *model.AppError) { @@ -851,14 +1019,27 @@ func (s SqlTeamStore) GetChannelUnreadsForTeam(teamId, userId string) ([]*model. return channels, nil } -func (s SqlTeamStore) RemoveMember(teamId string, userId string) *model.AppError { - _, err := s.GetMaster().Exec("DELETE FROM TeamMembers WHERE TeamId = :TeamId AND UserId = :UserId", map[string]interface{}{"TeamId": teamId, "UserId": userId}) +func (s SqlTeamStore) RemoveMembers(teamId string, userIds []string) *model.AppError { + query := s.getQueryBuilder(). + Delete("TeamMembers"). + Where(sq.Eq{"TeamId": teamId}). + Where(sq.Eq{"UserId": userIds}) + + sql, args, err := query.ToSql() if err != nil { - return model.NewAppError("SqlTeamStore.RemoveMember", "store.sql_team.remove_member.app_error", nil, "team_id="+teamId+", user_id="+userId+", "+err.Error(), http.StatusInternalServerError) + return model.NewAppError("SqlTeamStore.RemoveMembers", "store.sql_team.remove_member.app_error", nil, "team_id="+teamId+", "+err.Error(), http.StatusInternalServerError) + } + _, err = s.GetMaster().Exec(sql, args...) + if err != nil { + return model.NewAppError("SqlTeamStore.RemoveMembers", "store.sql_team.remove_member.app_error", nil, "team_id="+teamId+", "+err.Error(), http.StatusInternalServerError) } return nil } +func (s SqlTeamStore) RemoveMember(teamId string, userId string) *model.AppError { + return s.RemoveMembers(teamId, []string{userId}) +} + func (s SqlTeamStore) RemoveAllMembersByTeam(teamId string) *model.AppError { _, err := s.GetMaster().Exec("DELETE FROM TeamMembers WHERE TeamId = :TeamId", map[string]interface{}{"TeamId": teamId}) if err != nil { diff --git a/store/sqlstore/team_store_test.go b/store/sqlstore/team_store_test.go index 164a96c1d2..548f1d5879 100644 --- a/store/sqlstore/team_store_test.go +++ b/store/sqlstore/team_store_test.go @@ -150,7 +150,7 @@ func testTeamMemberWithSchemeRolesToModel(t *testing.T) { m := db.ToModel() - assert.Equal(t, "team_user custom_role", m.Roles) + assert.Equal(t, "custom_role team_user", m.Roles) assert.Equal(t, false, m.SchemeGuest) assert.Equal(t, true, m.SchemeUser) assert.Equal(t, false, m.SchemeAdmin) @@ -170,7 +170,7 @@ func testTeamMemberWithSchemeRolesToModel(t *testing.T) { m := db.ToModel() - assert.Equal(t, "team_user team_admin custom_role", m.Roles) + assert.Equal(t, "custom_role team_user team_admin", m.Roles) assert.Equal(t, false, m.SchemeGuest) assert.Equal(t, true, m.SchemeUser) assert.Equal(t, true, m.SchemeAdmin) diff --git a/store/store.go b/store/store.go index 851f9dc66f..1bd183167a 100644 --- a/store/store.go +++ b/store/store.go @@ -85,8 +85,10 @@ type TeamStore interface { AnalyticsTeamCount(includeDeleted bool) (int64, *model.AppError) AnalyticsPublicTeamCount() (int64, *model.AppError) AnalyticsPrivateTeamCount() (int64, *model.AppError) + SaveMultipleMembers(members []*model.TeamMember, maxUsersPerTeam int) ([]*model.TeamMember, *model.AppError) SaveMember(member *model.TeamMember, maxUsersPerTeam int) (*model.TeamMember, *model.AppError) UpdateMember(member *model.TeamMember) (*model.TeamMember, *model.AppError) + UpdateMultipleMembers(members []*model.TeamMember) ([]*model.TeamMember, *model.AppError) GetMember(teamId string, userId string) (*model.TeamMember, *model.AppError) GetMembers(teamId string, offset int, limit int, restrictions *model.ViewUsersRestrictions) ([]*model.TeamMember, *model.AppError) GetMembersByIds(teamId string, userIds []string, restrictions *model.ViewUsersRestrictions) ([]*model.TeamMember, *model.AppError) @@ -97,6 +99,7 @@ type TeamStore interface { GetChannelUnreadsForAllTeams(excludeTeamId, userId string) ([]*model.ChannelUnread, *model.AppError) GetChannelUnreadsForTeam(teamId, userId string) ([]*model.ChannelUnread, *model.AppError) RemoveMember(teamId string, userId string) *model.AppError + RemoveMembers(teamId string, userIds []string) *model.AppError RemoveAllMembersByTeam(teamId string) *model.AppError RemoveAllMembersByUser(userId string) *model.AppError UpdateLastTeamIconUpdate(teamId string, curTime int64) *model.AppError @@ -150,8 +153,10 @@ type ChannelStore interface { GetAll(teamId string) ([]*model.Channel, *model.AppError) GetChannelsByIds(channelIds []string, includeDeleted bool) ([]*model.Channel, *model.AppError) GetForPost(postId string) (*model.Channel, *model.AppError) + SaveMultipleMembers(members []*model.ChannelMember) ([]*model.ChannelMember, *model.AppError) SaveMember(member *model.ChannelMember) (*model.ChannelMember, *model.AppError) UpdateMember(member *model.ChannelMember) (*model.ChannelMember, *model.AppError) + UpdateMultipleMembers(members []*model.ChannelMember) ([]*model.ChannelMember, *model.AppError) GetMembers(channelId string, offset, limit int) (*model.ChannelMembers, *model.AppError) GetMember(channelId string, userId string) (*model.ChannelMember, *model.AppError) GetChannelMembersTimezones(channelId string) ([]model.StringMap, *model.AppError) @@ -170,6 +175,7 @@ type ChannelStore interface { GetGuestCount(channelId string, allowFromCache bool) (int64, *model.AppError) GetPinnedPosts(channelId string) (*model.PostList, *model.AppError) RemoveMember(channelId string, userId string) *model.AppError + RemoveMembers(channelId string, userIds []string) *model.AppError PermanentDeleteMembersByUser(userId string) *model.AppError PermanentDeleteMembersByChannel(channelId string) *model.AppError UpdateLastViewedAt(channelIds []string, userId string) (map[string]int64, *model.AppError) diff --git a/store/storetest/channel_store.go b/store/storetest/channel_store.go index 5f206fbbc2..afe1ab4979 100644 --- a/store/storetest/channel_store.go +++ b/store/storetest/channel_store.go @@ -48,6 +48,12 @@ func TestChannelStore(t *testing.T, ss store.Store, s SqlSupplier) { t.Run("GetDeletedByName", func(t *testing.T) { testChannelStoreGetDeletedByName(t, ss) }) t.Run("GetDeleted", func(t *testing.T) { testChannelStoreGetDeleted(t, ss) }) t.Run("ChannelMemberStore", func(t *testing.T) { testChannelMemberStore(t, ss) }) + t.Run("SaveMember", func(t *testing.T) { testChannelSaveMember(t, ss) }) + t.Run("SaveMultipleMembers", func(t *testing.T) { testChannelSaveMultipleMembers(t, ss) }) + t.Run("UpdateMember", func(t *testing.T) { testChannelUpdateMember(t, ss) }) + t.Run("UpdateMultipleMembers", func(t *testing.T) { testChannelUpdateMultipleMembers(t, ss) }) + t.Run("RemoveMember", func(t *testing.T) { testChannelRemoveMember(t, ss) }) + t.Run("RemoveMembers", func(t *testing.T) { testChannelRemoveMembers(t, ss) }) t.Run("ChannelDeleteMemberStore", func(t *testing.T) { testChannelDeleteMemberStore(t, ss) }) t.Run("GetChannels", func(t *testing.T) { testChannelStoreGetChannels(t, ss) }) t.Run("GetAllChannels", func(t *testing.T) { testChannelStoreGetAllChannels(t, ss, s) }) @@ -877,6 +883,2155 @@ func testChannelMemberStore(t *testing.T, ss store.Store) { assert.EqualValues(t, 0, c1t4.ExtraUpdateAt, "ExtraUpdateAt should be 0") } +func testChannelSaveMember(t *testing.T, ss store.Store) { + u1, err := ss.User().Save(&model.User{Username: model.NewId(), Email: MakeEmail()}) + require.Nil(t, err) + defaultNotifyProps := model.GetDefaultChannelNotifyProps() + + t.Run("not valid channel member", func(t *testing.T) { + member := &model.ChannelMember{ChannelId: "wrong", UserId: u1.Id, NotifyProps: defaultNotifyProps} + _, err = ss.Channel().SaveMember(member) + require.NotNil(t, err) + require.Equal(t, "model.channel_member.is_valid.channel_id.app_error", err.Id) + }) + + t.Run("duplicated entries should fail", func(t *testing.T) { + channelID1 := model.NewId() + m1 := &model.ChannelMember{ChannelId: channelID1, UserId: u1.Id, NotifyProps: defaultNotifyProps} + _, err = ss.Channel().SaveMember(m1) + require.Nil(t, err) + m2 := &model.ChannelMember{ChannelId: channelID1, UserId: u1.Id, NotifyProps: defaultNotifyProps} + _, err = ss.Channel().SaveMember(m2) + require.NotNil(t, err) + require.Equal(t, "store.sql_channel.save_member.exists.app_error", err.Id) + }) + + t.Run("insert member correctly (in channel without channel scheme and team without scheme)", func(t *testing.T) { + team := &model.Team{ + DisplayName: "Name", + Name: "zz" + model.NewId(), + Email: MakeEmail(), + Type: model.TEAM_OPEN, + } + + team, err = ss.Team().Save(team) + require.Nil(t, err) + + channel := &model.Channel{ + DisplayName: "DisplayName", + Name: "z-z-z" + model.NewId() + "b", + Type: model.CHANNEL_OPEN, + TeamId: team.Id, + } + channel, err = ss.Channel().Save(channel, -1) + require.Nil(t, err) + defer func() { ss.Channel().PermanentDelete(channel.Id) }() + + testCases := []struct { + Name string + SchemeGuest bool + SchemeUser bool + SchemeAdmin bool + ExplicitRoles string + ExpectedRoles string + ExpectedExplicitRoles string + ExpectedSchemeGuest bool + ExpectedSchemeUser bool + ExpectedSchemeAdmin bool + }{ + { + Name: "channel user implicit", + SchemeUser: true, + ExpectedRoles: "channel_user", + ExpectedSchemeUser: true, + }, + { + Name: "channel user explicit", + ExplicitRoles: "channel_user", + ExpectedRoles: "channel_user", + ExpectedSchemeUser: true, + }, + { + Name: "channel guest implicit", + SchemeGuest: true, + ExpectedRoles: "channel_guest", + ExpectedSchemeGuest: true, + }, + { + Name: "channel guest explicit", + ExplicitRoles: "channel_guest", + ExpectedRoles: "channel_guest", + ExpectedSchemeGuest: true, + }, + { + Name: "channel admin implicit", + SchemeUser: true, + SchemeAdmin: true, + ExpectedRoles: "channel_user channel_admin", + ExpectedSchemeUser: true, + ExpectedSchemeAdmin: true, + }, + { + Name: "channel admin explicit", + ExplicitRoles: "channel_user channel_admin", + ExpectedRoles: "channel_user channel_admin", + ExpectedSchemeUser: true, + ExpectedSchemeAdmin: true, + }, + { + Name: "channel user implicit and explicit custom role", + SchemeUser: true, + ExplicitRoles: "test", + ExpectedRoles: "test channel_user", + ExpectedExplicitRoles: "test", + ExpectedSchemeUser: true, + }, + { + Name: "channel user explicit and explicit custom role", + ExplicitRoles: "channel_user test", + ExpectedRoles: "test channel_user", + ExpectedExplicitRoles: "test", + ExpectedSchemeUser: true, + }, + { + Name: "channel guest implicit and explicit custom role", + SchemeGuest: true, + ExplicitRoles: "test", + ExpectedRoles: "test channel_guest", + ExpectedExplicitRoles: "test", + ExpectedSchemeGuest: true, + }, + { + Name: "channel guest explicit and explicit custom role", + ExplicitRoles: "channel_guest test", + ExpectedRoles: "test channel_guest", + ExpectedExplicitRoles: "test", + ExpectedSchemeGuest: true, + }, + { + Name: "channel admin implicit and explicit custom role", + SchemeUser: true, + SchemeAdmin: true, + ExplicitRoles: "test", + ExpectedRoles: "test channel_user channel_admin", + ExpectedExplicitRoles: "test", + ExpectedSchemeUser: true, + ExpectedSchemeAdmin: true, + }, + { + Name: "channel admin explicit and explicit custom role", + ExplicitRoles: "channel_user channel_admin test", + ExpectedRoles: "test channel_user channel_admin", + ExpectedExplicitRoles: "test", + ExpectedSchemeUser: true, + ExpectedSchemeAdmin: true, + }, + { + Name: "channel member with only explicit custom roles", + ExplicitRoles: "test test2", + ExpectedRoles: "test test2", + ExpectedExplicitRoles: "test test2", + }, + } + + for _, tc := range testCases { + t.Run(tc.Name, func(t *testing.T) { + member := &model.ChannelMember{ + ChannelId: channel.Id, + UserId: u1.Id, + SchemeGuest: tc.SchemeGuest, + SchemeUser: tc.SchemeUser, + SchemeAdmin: tc.SchemeAdmin, + ExplicitRoles: tc.ExplicitRoles, + NotifyProps: defaultNotifyProps, + } + member, err = ss.Channel().SaveMember(member) + require.Nil(t, err) + defer ss.Channel().RemoveMember(channel.Id, u1.Id) + assert.Equal(t, tc.ExpectedRoles, member.Roles) + assert.Equal(t, tc.ExpectedExplicitRoles, member.ExplicitRoles) + assert.Equal(t, tc.ExpectedSchemeGuest, member.SchemeGuest) + assert.Equal(t, tc.ExpectedSchemeUser, member.SchemeUser) + assert.Equal(t, tc.ExpectedSchemeAdmin, member.SchemeAdmin) + }) + } + }) + + t.Run("insert member correctly (in channel without scheme and team with scheme)", func(t *testing.T) { + ts := &model.Scheme{ + Name: model.NewId(), + DisplayName: model.NewId(), + Description: model.NewId(), + Scope: model.SCHEME_SCOPE_TEAM, + } + ts, err = ss.Scheme().Save(ts) + require.Nil(t, err) + + team := &model.Team{ + DisplayName: "Name", + Name: "zz" + model.NewId(), + Email: MakeEmail(), + Type: model.TEAM_OPEN, + SchemeId: &ts.Id, + } + + team, err = ss.Team().Save(team) + require.Nil(t, err) + + channel := &model.Channel{ + DisplayName: "DisplayName", + Name: "z-z-z" + model.NewId() + "b", + Type: model.CHANNEL_OPEN, + TeamId: team.Id, + } + channel, err = ss.Channel().Save(channel, -1) + require.Nil(t, err) + defer func() { ss.Channel().PermanentDelete(channel.Id) }() + + testCases := []struct { + Name string + SchemeGuest bool + SchemeUser bool + SchemeAdmin bool + ExplicitRoles string + ExpectedRoles string + ExpectedExplicitRoles string + ExpectedSchemeGuest bool + ExpectedSchemeUser bool + ExpectedSchemeAdmin bool + }{ + { + Name: "channel user implicit", + SchemeUser: true, + ExpectedRoles: ts.DefaultChannelUserRole, + ExpectedSchemeUser: true, + }, + { + Name: "channel user explicit", + ExplicitRoles: "channel_user", + ExpectedRoles: ts.DefaultChannelUserRole, + ExpectedSchemeUser: true, + }, + { + Name: "channel guest implicit", + SchemeGuest: true, + ExpectedRoles: ts.DefaultChannelGuestRole, + ExpectedSchemeGuest: true, + }, + { + Name: "channel guest explicit", + ExplicitRoles: "channel_guest", + ExpectedRoles: ts.DefaultChannelGuestRole, + ExpectedSchemeGuest: true, + }, + { + Name: "channel admin implicit", + SchemeUser: true, + SchemeAdmin: true, + ExpectedRoles: ts.DefaultChannelUserRole + " " + ts.DefaultChannelAdminRole, + ExpectedSchemeUser: true, + ExpectedSchemeAdmin: true, + }, + { + Name: "channel admin explicit", + ExplicitRoles: "channel_user channel_admin", + ExpectedRoles: ts.DefaultChannelUserRole + " " + ts.DefaultChannelAdminRole, + ExpectedSchemeUser: true, + ExpectedSchemeAdmin: true, + }, + { + Name: "channel user implicit and explicit custom role", + SchemeUser: true, + ExplicitRoles: "test", + ExpectedRoles: "test " + ts.DefaultChannelUserRole, + ExpectedExplicitRoles: "test", + ExpectedSchemeUser: true, + }, + { + Name: "channel user explicit and explicit custom role", + ExplicitRoles: "channel_user test", + ExpectedRoles: "test " + ts.DefaultChannelUserRole, + ExpectedExplicitRoles: "test", + ExpectedSchemeUser: true, + }, + { + Name: "channel guest implicit and explicit custom role", + SchemeGuest: true, + ExplicitRoles: "test", + ExpectedRoles: "test " + ts.DefaultChannelGuestRole, + ExpectedExplicitRoles: "test", + ExpectedSchemeGuest: true, + }, + { + Name: "channel guest explicit and explicit custom role", + ExplicitRoles: "channel_guest test", + ExpectedRoles: "test " + ts.DefaultChannelGuestRole, + ExpectedExplicitRoles: "test", + ExpectedSchemeGuest: true, + }, + { + Name: "channel admin implicit and explicit custom role", + SchemeUser: true, + SchemeAdmin: true, + ExplicitRoles: "test", + ExpectedRoles: "test " + ts.DefaultChannelUserRole + " " + ts.DefaultChannelAdminRole, + ExpectedExplicitRoles: "test", + ExpectedSchemeUser: true, + ExpectedSchemeAdmin: true, + }, + { + Name: "channel admin explicit and explicit custom role", + ExplicitRoles: "channel_user channel_admin test", + ExpectedRoles: "test " + ts.DefaultChannelUserRole + " " + ts.DefaultChannelAdminRole, + ExpectedExplicitRoles: "test", + ExpectedSchemeUser: true, + ExpectedSchemeAdmin: true, + }, + { + Name: "channel member with only explicit custom roles", + ExplicitRoles: "test test2", + ExpectedRoles: "test test2", + ExpectedExplicitRoles: "test test2", + }, + } + + for _, tc := range testCases { + t.Run(tc.Name, func(t *testing.T) { + member := &model.ChannelMember{ + ChannelId: channel.Id, + UserId: u1.Id, + SchemeGuest: tc.SchemeGuest, + SchemeUser: tc.SchemeUser, + SchemeAdmin: tc.SchemeAdmin, + ExplicitRoles: tc.ExplicitRoles, + NotifyProps: defaultNotifyProps, + } + member, err = ss.Channel().SaveMember(member) + require.Nil(t, err) + defer ss.Channel().RemoveMember(channel.Id, u1.Id) + assert.Equal(t, tc.ExpectedRoles, member.Roles) + assert.Equal(t, tc.ExpectedExplicitRoles, member.ExplicitRoles) + assert.Equal(t, tc.ExpectedSchemeGuest, member.SchemeGuest) + assert.Equal(t, tc.ExpectedSchemeUser, member.SchemeUser) + assert.Equal(t, tc.ExpectedSchemeAdmin, member.SchemeAdmin) + }) + } + }) + + t.Run("insert member correctly (in channel with channel scheme)", func(t *testing.T) { + cs := &model.Scheme{ + Name: model.NewId(), + DisplayName: model.NewId(), + Description: model.NewId(), + Scope: model.SCHEME_SCOPE_CHANNEL, + } + cs, err = ss.Scheme().Save(cs) + require.Nil(t, err) + + team := &model.Team{ + DisplayName: "Name", + Name: "zz" + model.NewId(), + Email: MakeEmail(), + Type: model.TEAM_OPEN, + } + + team, err = ss.Team().Save(team) + require.Nil(t, err) + + channel, err := ss.Channel().Save(&model.Channel{ + DisplayName: "DisplayName", + Name: "z-z-z" + model.NewId() + "b", + Type: model.CHANNEL_OPEN, + TeamId: team.Id, + SchemeId: &cs.Id, + }, -1) + require.Nil(t, err) + defer func() { ss.Channel().PermanentDelete(channel.Id) }() + + testCases := []struct { + Name string + SchemeGuest bool + SchemeUser bool + SchemeAdmin bool + ExplicitRoles string + ExpectedRoles string + ExpectedExplicitRoles string + ExpectedSchemeGuest bool + ExpectedSchemeUser bool + ExpectedSchemeAdmin bool + }{ + { + Name: "channel user implicit", + SchemeUser: true, + ExpectedRoles: cs.DefaultChannelUserRole, + ExpectedSchemeUser: true, + }, + { + Name: "channel user explicit", + ExplicitRoles: "channel_user", + ExpectedRoles: cs.DefaultChannelUserRole, + ExpectedSchemeUser: true, + }, + { + Name: "channel guest implicit", + SchemeGuest: true, + ExpectedRoles: cs.DefaultChannelGuestRole, + ExpectedSchemeGuest: true, + }, + { + Name: "channel guest explicit", + ExplicitRoles: "channel_guest", + ExpectedRoles: cs.DefaultChannelGuestRole, + ExpectedSchemeGuest: true, + }, + { + Name: "channel admin implicit", + SchemeUser: true, + SchemeAdmin: true, + ExpectedRoles: cs.DefaultChannelUserRole + " " + cs.DefaultChannelAdminRole, + ExpectedSchemeUser: true, + ExpectedSchemeAdmin: true, + }, + { + Name: "channel admin explicit", + ExplicitRoles: "channel_user channel_admin", + ExpectedRoles: cs.DefaultChannelUserRole + " " + cs.DefaultChannelAdminRole, + ExpectedSchemeUser: true, + ExpectedSchemeAdmin: true, + }, + { + Name: "channel user implicit and explicit custom role", + SchemeUser: true, + ExplicitRoles: "test", + ExpectedRoles: "test " + cs.DefaultChannelUserRole, + ExpectedExplicitRoles: "test", + ExpectedSchemeUser: true, + }, + { + Name: "channel user explicit and explicit custom role", + ExplicitRoles: "channel_user test", + ExpectedRoles: "test " + cs.DefaultChannelUserRole, + ExpectedExplicitRoles: "test", + ExpectedSchemeUser: true, + }, + { + Name: "channel guest implicit and explicit custom role", + SchemeGuest: true, + ExplicitRoles: "test", + ExpectedRoles: "test " + cs.DefaultChannelGuestRole, + ExpectedExplicitRoles: "test", + ExpectedSchemeGuest: true, + }, + { + Name: "channel guest explicit and explicit custom role", + ExplicitRoles: "channel_guest test", + ExpectedRoles: "test " + cs.DefaultChannelGuestRole, + ExpectedExplicitRoles: "test", + ExpectedSchemeGuest: true, + }, + { + Name: "channel admin implicit and explicit custom role", + SchemeUser: true, + SchemeAdmin: true, + ExplicitRoles: "test", + ExpectedRoles: "test " + cs.DefaultChannelUserRole + " " + cs.DefaultChannelAdminRole, + ExpectedExplicitRoles: "test", + ExpectedSchemeUser: true, + ExpectedSchemeAdmin: true, + }, + { + Name: "channel admin explicit and explicit custom role", + ExplicitRoles: "channel_user channel_admin test", + ExpectedRoles: "test " + cs.DefaultChannelUserRole + " " + cs.DefaultChannelAdminRole, + ExpectedExplicitRoles: "test", + ExpectedSchemeUser: true, + ExpectedSchemeAdmin: true, + }, + { + Name: "channel member with only explicit custom roles", + ExplicitRoles: "test test2", + ExpectedRoles: "test test2", + ExpectedExplicitRoles: "test test2", + }, + } + + for _, tc := range testCases { + t.Run(tc.Name, func(t *testing.T) { + member := &model.ChannelMember{ + ChannelId: channel.Id, + UserId: u1.Id, + SchemeGuest: tc.SchemeGuest, + SchemeUser: tc.SchemeUser, + SchemeAdmin: tc.SchemeAdmin, + ExplicitRoles: tc.ExplicitRoles, + NotifyProps: defaultNotifyProps, + } + member, err = ss.Channel().SaveMember(member) + require.Nil(t, err) + defer ss.Channel().RemoveMember(channel.Id, u1.Id) + assert.Equal(t, tc.ExpectedRoles, member.Roles) + assert.Equal(t, tc.ExpectedExplicitRoles, member.ExplicitRoles) + assert.Equal(t, tc.ExpectedSchemeGuest, member.SchemeGuest) + assert.Equal(t, tc.ExpectedSchemeUser, member.SchemeUser) + assert.Equal(t, tc.ExpectedSchemeAdmin, member.SchemeAdmin) + }) + } + }) +} + +func testChannelSaveMultipleMembers(t *testing.T, ss store.Store) { + u1, err := ss.User().Save(&model.User{Username: model.NewId(), Email: MakeEmail()}) + require.Nil(t, err) + u2, err := ss.User().Save(&model.User{Username: model.NewId(), Email: MakeEmail()}) + require.Nil(t, err) + defaultNotifyProps := model.GetDefaultChannelNotifyProps() + + t.Run("any not valid channel member", func(t *testing.T) { + m1 := &model.ChannelMember{ChannelId: "wrong", UserId: u1.Id, NotifyProps: defaultNotifyProps} + m2 := &model.ChannelMember{ChannelId: model.NewId(), UserId: u2.Id, NotifyProps: defaultNotifyProps} + _, err = ss.Channel().SaveMultipleMembers([]*model.ChannelMember{m1, m2}) + require.NotNil(t, err) + require.Equal(t, "model.channel_member.is_valid.channel_id.app_error", err.Id) + }) + + t.Run("duplicated entries should fail", func(t *testing.T) { + channelID1 := model.NewId() + m1 := &model.ChannelMember{ChannelId: channelID1, UserId: u1.Id, NotifyProps: defaultNotifyProps} + m2 := &model.ChannelMember{ChannelId: channelID1, UserId: u1.Id, NotifyProps: defaultNotifyProps} + _, err = ss.Channel().SaveMultipleMembers([]*model.ChannelMember{m1, m2}) + require.NotNil(t, err) + require.Equal(t, "store.sql_channel.save_member.exists.app_error", err.Id) + }) + + t.Run("insert members correctly (in channel without channel scheme and team without scheme)", func(t *testing.T) { + team := &model.Team{ + DisplayName: "Name", + Name: "zz" + model.NewId(), + Email: MakeEmail(), + Type: model.TEAM_OPEN, + } + + team, err = ss.Team().Save(team) + require.Nil(t, err) + + channel := &model.Channel{ + DisplayName: "DisplayName", + Name: "z-z-z" + model.NewId() + "b", + Type: model.CHANNEL_OPEN, + TeamId: team.Id, + } + channel, err = ss.Channel().Save(channel, -1) + require.Nil(t, err) + defer func() { ss.Channel().PermanentDelete(channel.Id) }() + + testCases := []struct { + Name string + SchemeGuest bool + SchemeUser bool + SchemeAdmin bool + ExplicitRoles string + ExpectedRoles string + ExpectedExplicitRoles string + ExpectedSchemeGuest bool + ExpectedSchemeUser bool + ExpectedSchemeAdmin bool + }{ + { + Name: "channel user implicit", + SchemeUser: true, + ExpectedRoles: "channel_user", + ExpectedSchemeUser: true, + }, + { + Name: "channel user explicit", + ExplicitRoles: "channel_user", + ExpectedRoles: "channel_user", + ExpectedSchemeUser: true, + }, + { + Name: "channel guest implicit", + SchemeGuest: true, + ExpectedRoles: "channel_guest", + ExpectedSchemeGuest: true, + }, + { + Name: "channel guest explicit", + ExplicitRoles: "channel_guest", + ExpectedRoles: "channel_guest", + ExpectedSchemeGuest: true, + }, + { + Name: "channel admin implicit", + SchemeUser: true, + SchemeAdmin: true, + ExpectedRoles: "channel_user channel_admin", + ExpectedSchemeUser: true, + ExpectedSchemeAdmin: true, + }, + { + Name: "channel admin explicit", + ExplicitRoles: "channel_user channel_admin", + ExpectedRoles: "channel_user channel_admin", + ExpectedSchemeUser: true, + ExpectedSchemeAdmin: true, + }, + { + Name: "channel user implicit and explicit custom role", + SchemeUser: true, + ExplicitRoles: "test", + ExpectedRoles: "test channel_user", + ExpectedExplicitRoles: "test", + ExpectedSchemeUser: true, + }, + { + Name: "channel user explicit and explicit custom role", + ExplicitRoles: "channel_user test", + ExpectedRoles: "test channel_user", + ExpectedExplicitRoles: "test", + ExpectedSchemeUser: true, + }, + { + Name: "channel guest implicit and explicit custom role", + SchemeGuest: true, + ExplicitRoles: "test", + ExpectedRoles: "test channel_guest", + ExpectedExplicitRoles: "test", + ExpectedSchemeGuest: true, + }, + { + Name: "channel guest explicit and explicit custom role", + ExplicitRoles: "channel_guest test", + ExpectedRoles: "test channel_guest", + ExpectedExplicitRoles: "test", + ExpectedSchemeGuest: true, + }, + { + Name: "channel admin implicit and explicit custom role", + SchemeUser: true, + SchemeAdmin: true, + ExplicitRoles: "test", + ExpectedRoles: "test channel_user channel_admin", + ExpectedExplicitRoles: "test", + ExpectedSchemeUser: true, + ExpectedSchemeAdmin: true, + }, + { + Name: "channel admin explicit and explicit custom role", + ExplicitRoles: "channel_user channel_admin test", + ExpectedRoles: "test channel_user channel_admin", + ExpectedExplicitRoles: "test", + ExpectedSchemeUser: true, + ExpectedSchemeAdmin: true, + }, + { + Name: "channel member with only explicit custom roles", + ExplicitRoles: "test test2", + ExpectedRoles: "test test2", + ExpectedExplicitRoles: "test test2", + }, + } + + for _, tc := range testCases { + t.Run(tc.Name, func(t *testing.T) { + member := &model.ChannelMember{ + ChannelId: channel.Id, + UserId: u1.Id, + SchemeGuest: tc.SchemeGuest, + SchemeUser: tc.SchemeUser, + SchemeAdmin: tc.SchemeAdmin, + ExplicitRoles: tc.ExplicitRoles, + NotifyProps: defaultNotifyProps, + } + otherMember := &model.ChannelMember{ + ChannelId: channel.Id, + UserId: u2.Id, + SchemeGuest: tc.SchemeGuest, + SchemeUser: tc.SchemeUser, + SchemeAdmin: tc.SchemeAdmin, + ExplicitRoles: tc.ExplicitRoles, + NotifyProps: defaultNotifyProps, + } + var members []*model.ChannelMember + members, err = ss.Channel().SaveMultipleMembers([]*model.ChannelMember{member, otherMember}) + require.Nil(t, err) + require.Len(t, members, 2) + member = members[0] + defer ss.Channel().RemoveMember(channel.Id, u1.Id) + defer ss.Channel().RemoveMember(channel.Id, u2.Id) + + assert.Equal(t, tc.ExpectedRoles, member.Roles) + assert.Equal(t, tc.ExpectedExplicitRoles, member.ExplicitRoles) + assert.Equal(t, tc.ExpectedSchemeGuest, member.SchemeGuest) + assert.Equal(t, tc.ExpectedSchemeUser, member.SchemeUser) + assert.Equal(t, tc.ExpectedSchemeAdmin, member.SchemeAdmin) + }) + } + }) + + t.Run("insert members correctly (in channel without scheme and team with scheme)", func(t *testing.T) { + ts := &model.Scheme{ + Name: model.NewId(), + DisplayName: model.NewId(), + Description: model.NewId(), + Scope: model.SCHEME_SCOPE_TEAM, + } + ts, err = ss.Scheme().Save(ts) + require.Nil(t, err) + + team := &model.Team{ + DisplayName: "Name", + Name: "zz" + model.NewId(), + Email: MakeEmail(), + Type: model.TEAM_OPEN, + SchemeId: &ts.Id, + } + + team, err = ss.Team().Save(team) + require.Nil(t, err) + + channel := &model.Channel{ + DisplayName: "DisplayName", + Name: "z-z-z" + model.NewId() + "b", + Type: model.CHANNEL_OPEN, + TeamId: team.Id, + } + channel, err = ss.Channel().Save(channel, -1) + require.Nil(t, err) + defer func() { ss.Channel().PermanentDelete(channel.Id) }() + + testCases := []struct { + Name string + SchemeGuest bool + SchemeUser bool + SchemeAdmin bool + ExplicitRoles string + ExpectedRoles string + ExpectedExplicitRoles string + ExpectedSchemeGuest bool + ExpectedSchemeUser bool + ExpectedSchemeAdmin bool + }{ + { + Name: "channel user implicit", + SchemeUser: true, + ExpectedRoles: ts.DefaultChannelUserRole, + ExpectedSchemeUser: true, + }, + { + Name: "channel user explicit", + ExplicitRoles: "channel_user", + ExpectedRoles: ts.DefaultChannelUserRole, + ExpectedSchemeUser: true, + }, + { + Name: "channel guest implicit", + SchemeGuest: true, + ExpectedRoles: ts.DefaultChannelGuestRole, + ExpectedSchemeGuest: true, + }, + { + Name: "channel guest explicit", + ExplicitRoles: "channel_guest", + ExpectedRoles: ts.DefaultChannelGuestRole, + ExpectedSchemeGuest: true, + }, + { + Name: "channel admin implicit", + SchemeUser: true, + SchemeAdmin: true, + ExpectedRoles: ts.DefaultChannelUserRole + " " + ts.DefaultChannelAdminRole, + ExpectedSchemeUser: true, + ExpectedSchemeAdmin: true, + }, + { + Name: "channel admin explicit", + ExplicitRoles: "channel_user channel_admin", + ExpectedRoles: ts.DefaultChannelUserRole + " " + ts.DefaultChannelAdminRole, + ExpectedSchemeUser: true, + ExpectedSchemeAdmin: true, + }, + { + Name: "channel user implicit and explicit custom role", + SchemeUser: true, + ExplicitRoles: "test", + ExpectedRoles: "test " + ts.DefaultChannelUserRole, + ExpectedExplicitRoles: "test", + ExpectedSchemeUser: true, + }, + { + Name: "channel user explicit and explicit custom role", + ExplicitRoles: "channel_user test", + ExpectedRoles: "test " + ts.DefaultChannelUserRole, + ExpectedExplicitRoles: "test", + ExpectedSchemeUser: true, + }, + { + Name: "channel guest implicit and explicit custom role", + SchemeGuest: true, + ExplicitRoles: "test", + ExpectedRoles: "test " + ts.DefaultChannelGuestRole, + ExpectedExplicitRoles: "test", + ExpectedSchemeGuest: true, + }, + { + Name: "channel guest explicit and explicit custom role", + ExplicitRoles: "channel_guest test", + ExpectedRoles: "test " + ts.DefaultChannelGuestRole, + ExpectedExplicitRoles: "test", + ExpectedSchemeGuest: true, + }, + { + Name: "channel admin implicit and explicit custom role", + SchemeUser: true, + SchemeAdmin: true, + ExplicitRoles: "test", + ExpectedRoles: "test " + ts.DefaultChannelUserRole + " " + ts.DefaultChannelAdminRole, + ExpectedExplicitRoles: "test", + ExpectedSchemeUser: true, + ExpectedSchemeAdmin: true, + }, + { + Name: "channel admin explicit and explicit custom role", + ExplicitRoles: "channel_user channel_admin test", + ExpectedRoles: "test " + ts.DefaultChannelUserRole + " " + ts.DefaultChannelAdminRole, + ExpectedExplicitRoles: "test", + ExpectedSchemeUser: true, + ExpectedSchemeAdmin: true, + }, + { + Name: "channel member with only explicit custom roles", + ExplicitRoles: "test test2", + ExpectedRoles: "test test2", + ExpectedExplicitRoles: "test test2", + }, + } + + for _, tc := range testCases { + t.Run(tc.Name, func(t *testing.T) { + member := &model.ChannelMember{ + ChannelId: channel.Id, + UserId: u1.Id, + SchemeGuest: tc.SchemeGuest, + SchemeUser: tc.SchemeUser, + SchemeAdmin: tc.SchemeAdmin, + ExplicitRoles: tc.ExplicitRoles, + NotifyProps: defaultNotifyProps, + } + otherMember := &model.ChannelMember{ + ChannelId: channel.Id, + UserId: u2.Id, + SchemeGuest: tc.SchemeGuest, + SchemeUser: tc.SchemeUser, + SchemeAdmin: tc.SchemeAdmin, + ExplicitRoles: tc.ExplicitRoles, + NotifyProps: defaultNotifyProps, + } + var members []*model.ChannelMember + members, err = ss.Channel().SaveMultipleMembers([]*model.ChannelMember{member, otherMember}) + require.Nil(t, err) + require.Len(t, members, 2) + member = members[0] + defer ss.Channel().RemoveMember(channel.Id, u1.Id) + defer ss.Channel().RemoveMember(channel.Id, u2.Id) + + assert.Equal(t, tc.ExpectedRoles, member.Roles) + assert.Equal(t, tc.ExpectedExplicitRoles, member.ExplicitRoles) + assert.Equal(t, tc.ExpectedSchemeGuest, member.SchemeGuest) + assert.Equal(t, tc.ExpectedSchemeUser, member.SchemeUser) + assert.Equal(t, tc.ExpectedSchemeAdmin, member.SchemeAdmin) + }) + } + }) + + t.Run("insert members correctly (in channel with channel scheme)", func(t *testing.T) { + cs := &model.Scheme{ + Name: model.NewId(), + DisplayName: model.NewId(), + Description: model.NewId(), + Scope: model.SCHEME_SCOPE_CHANNEL, + } + cs, err = ss.Scheme().Save(cs) + require.Nil(t, err) + + team := &model.Team{ + DisplayName: "Name", + Name: "zz" + model.NewId(), + Email: MakeEmail(), + Type: model.TEAM_OPEN, + } + + team, err = ss.Team().Save(team) + require.Nil(t, err) + + channel, err := ss.Channel().Save(&model.Channel{ + DisplayName: "DisplayName", + Name: "z-z-z" + model.NewId() + "b", + Type: model.CHANNEL_OPEN, + TeamId: team.Id, + SchemeId: &cs.Id, + }, -1) + require.Nil(t, err) + defer func() { ss.Channel().PermanentDelete(channel.Id) }() + + testCases := []struct { + Name string + SchemeGuest bool + SchemeUser bool + SchemeAdmin bool + ExplicitRoles string + ExpectedRoles string + ExpectedExplicitRoles string + ExpectedSchemeGuest bool + ExpectedSchemeUser bool + ExpectedSchemeAdmin bool + }{ + { + Name: "channel user implicit", + SchemeUser: true, + ExpectedRoles: cs.DefaultChannelUserRole, + ExpectedSchemeUser: true, + }, + { + Name: "channel user explicit", + ExplicitRoles: "channel_user", + ExpectedRoles: cs.DefaultChannelUserRole, + ExpectedSchemeUser: true, + }, + { + Name: "channel guest implicit", + SchemeGuest: true, + ExpectedRoles: cs.DefaultChannelGuestRole, + ExpectedSchemeGuest: true, + }, + { + Name: "channel guest explicit", + ExplicitRoles: "channel_guest", + ExpectedRoles: cs.DefaultChannelGuestRole, + ExpectedSchemeGuest: true, + }, + { + Name: "channel admin implicit", + SchemeUser: true, + SchemeAdmin: true, + ExpectedRoles: cs.DefaultChannelUserRole + " " + cs.DefaultChannelAdminRole, + ExpectedSchemeUser: true, + ExpectedSchemeAdmin: true, + }, + { + Name: "channel admin explicit", + ExplicitRoles: "channel_user channel_admin", + ExpectedRoles: cs.DefaultChannelUserRole + " " + cs.DefaultChannelAdminRole, + ExpectedSchemeUser: true, + ExpectedSchemeAdmin: true, + }, + { + Name: "channel user implicit and explicit custom role", + SchemeUser: true, + ExplicitRoles: "test", + ExpectedRoles: "test " + cs.DefaultChannelUserRole, + ExpectedExplicitRoles: "test", + ExpectedSchemeUser: true, + }, + { + Name: "channel user explicit and explicit custom role", + ExplicitRoles: "channel_user test", + ExpectedRoles: "test " + cs.DefaultChannelUserRole, + ExpectedExplicitRoles: "test", + ExpectedSchemeUser: true, + }, + { + Name: "channel guest implicit and explicit custom role", + SchemeGuest: true, + ExplicitRoles: "test", + ExpectedRoles: "test " + cs.DefaultChannelGuestRole, + ExpectedExplicitRoles: "test", + ExpectedSchemeGuest: true, + }, + { + Name: "channel guest explicit and explicit custom role", + ExplicitRoles: "channel_guest test", + ExpectedRoles: "test " + cs.DefaultChannelGuestRole, + ExpectedExplicitRoles: "test", + ExpectedSchemeGuest: true, + }, + { + Name: "channel admin implicit and explicit custom role", + SchemeUser: true, + SchemeAdmin: true, + ExplicitRoles: "test", + ExpectedRoles: "test " + cs.DefaultChannelUserRole + " " + cs.DefaultChannelAdminRole, + ExpectedExplicitRoles: "test", + ExpectedSchemeUser: true, + ExpectedSchemeAdmin: true, + }, + { + Name: "channel admin explicit and explicit custom role", + ExplicitRoles: "channel_user channel_admin test", + ExpectedRoles: "test " + cs.DefaultChannelUserRole + " " + cs.DefaultChannelAdminRole, + ExpectedExplicitRoles: "test", + ExpectedSchemeUser: true, + ExpectedSchemeAdmin: true, + }, + { + Name: "channel member with only explicit custom roles", + ExplicitRoles: "test test2", + ExpectedRoles: "test test2", + ExpectedExplicitRoles: "test test2", + }, + } + + for _, tc := range testCases { + t.Run(tc.Name, func(t *testing.T) { + member := &model.ChannelMember{ + ChannelId: channel.Id, + UserId: u1.Id, + SchemeGuest: tc.SchemeGuest, + SchemeUser: tc.SchemeUser, + SchemeAdmin: tc.SchemeAdmin, + ExplicitRoles: tc.ExplicitRoles, + NotifyProps: defaultNotifyProps, + } + otherMember := &model.ChannelMember{ + ChannelId: channel.Id, + UserId: u2.Id, + SchemeGuest: tc.SchemeGuest, + SchemeUser: tc.SchemeUser, + SchemeAdmin: tc.SchemeAdmin, + ExplicitRoles: tc.ExplicitRoles, + NotifyProps: defaultNotifyProps, + } + members, err := ss.Channel().SaveMultipleMembers([]*model.ChannelMember{member, otherMember}) + require.Nil(t, err) + require.Len(t, members, 2) + member = members[0] + defer ss.Channel().RemoveMember(channel.Id, u1.Id) + defer ss.Channel().RemoveMember(channel.Id, u2.Id) + + assert.Equal(t, tc.ExpectedRoles, member.Roles) + assert.Equal(t, tc.ExpectedExplicitRoles, member.ExplicitRoles) + assert.Equal(t, tc.ExpectedSchemeGuest, member.SchemeGuest) + assert.Equal(t, tc.ExpectedSchemeUser, member.SchemeUser) + assert.Equal(t, tc.ExpectedSchemeAdmin, member.SchemeAdmin) + }) + } + }) +} + +func testChannelUpdateMember(t *testing.T, ss store.Store) { + u1, err := ss.User().Save(&model.User{Username: model.NewId(), Email: MakeEmail()}) + require.Nil(t, err) + defaultNotifyProps := model.GetDefaultChannelNotifyProps() + + t.Run("not valid channel member", func(t *testing.T) { + member := &model.ChannelMember{ChannelId: "wrong", UserId: u1.Id, NotifyProps: defaultNotifyProps} + _, err = ss.Channel().UpdateMember(member) + require.NotNil(t, err) + require.Equal(t, "model.channel_member.is_valid.channel_id.app_error", err.Id) + }) + + t.Run("insert member correctly (in channel without channel scheme and team without scheme)", func(t *testing.T) { + team := &model.Team{ + DisplayName: "Name", + Name: "zz" + model.NewId(), + Email: MakeEmail(), + Type: model.TEAM_OPEN, + } + + team, err = ss.Team().Save(team) + require.Nil(t, err) + + channel := &model.Channel{ + DisplayName: "DisplayName", + Name: "z-z-z" + model.NewId() + "b", + Type: model.CHANNEL_OPEN, + TeamId: team.Id, + } + channel, err = ss.Channel().Save(channel, -1) + require.Nil(t, err) + defer func() { ss.Channel().PermanentDelete(channel.Id) }() + + member := &model.ChannelMember{ + ChannelId: channel.Id, + UserId: u1.Id, + NotifyProps: defaultNotifyProps, + } + member, err = ss.Channel().SaveMember(member) + require.Nil(t, err) + + testCases := []struct { + Name string + SchemeGuest bool + SchemeUser bool + SchemeAdmin bool + ExplicitRoles string + ExpectedRoles string + ExpectedExplicitRoles string + ExpectedSchemeGuest bool + ExpectedSchemeUser bool + ExpectedSchemeAdmin bool + }{ + { + Name: "channel user implicit", + SchemeUser: true, + ExpectedRoles: "channel_user", + ExpectedSchemeUser: true, + }, + { + Name: "channel user explicit", + ExplicitRoles: "channel_user", + ExpectedRoles: "channel_user", + ExpectedSchemeUser: true, + }, + { + Name: "channel guest implicit", + SchemeGuest: true, + ExpectedRoles: "channel_guest", + ExpectedSchemeGuest: true, + }, + { + Name: "channel guest explicit", + ExplicitRoles: "channel_guest", + ExpectedRoles: "channel_guest", + ExpectedSchemeGuest: true, + }, + { + Name: "channel admin implicit", + SchemeUser: true, + SchemeAdmin: true, + ExpectedRoles: "channel_user channel_admin", + ExpectedSchemeUser: true, + ExpectedSchemeAdmin: true, + }, + { + Name: "channel admin explicit", + ExplicitRoles: "channel_user channel_admin", + ExpectedRoles: "channel_user channel_admin", + ExpectedSchemeUser: true, + ExpectedSchemeAdmin: true, + }, + { + Name: "channel user implicit and explicit custom role", + SchemeUser: true, + ExplicitRoles: "test", + ExpectedRoles: "test channel_user", + ExpectedExplicitRoles: "test", + ExpectedSchemeUser: true, + }, + { + Name: "channel user explicit and explicit custom role", + ExplicitRoles: "channel_user test", + ExpectedRoles: "test channel_user", + ExpectedExplicitRoles: "test", + ExpectedSchemeUser: true, + }, + { + Name: "channel guest implicit and explicit custom role", + SchemeGuest: true, + ExplicitRoles: "test", + ExpectedRoles: "test channel_guest", + ExpectedExplicitRoles: "test", + ExpectedSchemeGuest: true, + }, + { + Name: "channel guest explicit and explicit custom role", + ExplicitRoles: "channel_guest test", + ExpectedRoles: "test channel_guest", + ExpectedExplicitRoles: "test", + ExpectedSchemeGuest: true, + }, + { + Name: "channel admin implicit and explicit custom role", + SchemeUser: true, + SchemeAdmin: true, + ExplicitRoles: "test", + ExpectedRoles: "test channel_user channel_admin", + ExpectedExplicitRoles: "test", + ExpectedSchemeUser: true, + ExpectedSchemeAdmin: true, + }, + { + Name: "channel admin explicit and explicit custom role", + ExplicitRoles: "channel_user channel_admin test", + ExpectedRoles: "test channel_user channel_admin", + ExpectedExplicitRoles: "test", + ExpectedSchemeUser: true, + ExpectedSchemeAdmin: true, + }, + { + Name: "channel member with only explicit custom roles", + ExplicitRoles: "test test2", + ExpectedRoles: "test test2", + ExpectedExplicitRoles: "test test2", + }, + } + + for _, tc := range testCases { + t.Run(tc.Name, func(t *testing.T) { + member.SchemeGuest = tc.SchemeGuest + member.SchemeUser = tc.SchemeUser + member.SchemeAdmin = tc.SchemeAdmin + member.ExplicitRoles = tc.ExplicitRoles + member, err = ss.Channel().UpdateMember(member) + require.Nil(t, err) + assert.Equal(t, tc.ExpectedRoles, member.Roles) + assert.Equal(t, tc.ExpectedExplicitRoles, member.ExplicitRoles) + assert.Equal(t, tc.ExpectedSchemeGuest, member.SchemeGuest) + assert.Equal(t, tc.ExpectedSchemeUser, member.SchemeUser) + assert.Equal(t, tc.ExpectedSchemeAdmin, member.SchemeAdmin) + }) + } + }) + + t.Run("insert member correctly (in channel without scheme and team with scheme)", func(t *testing.T) { + ts := &model.Scheme{ + Name: model.NewId(), + DisplayName: model.NewId(), + Description: model.NewId(), + Scope: model.SCHEME_SCOPE_TEAM, + } + ts, err = ss.Scheme().Save(ts) + require.Nil(t, err) + + team := &model.Team{ + DisplayName: "Name", + Name: "zz" + model.NewId(), + Email: MakeEmail(), + Type: model.TEAM_OPEN, + SchemeId: &ts.Id, + } + + team, err = ss.Team().Save(team) + require.Nil(t, err) + + channel := &model.Channel{ + DisplayName: "DisplayName", + Name: "z-z-z" + model.NewId() + "b", + Type: model.CHANNEL_OPEN, + TeamId: team.Id, + } + channel, err = ss.Channel().Save(channel, -1) + require.Nil(t, err) + defer func() { ss.Channel().PermanentDelete(channel.Id) }() + + member := &model.ChannelMember{ + ChannelId: channel.Id, + UserId: u1.Id, + NotifyProps: defaultNotifyProps, + } + member, err = ss.Channel().SaveMember(member) + require.Nil(t, err) + + testCases := []struct { + Name string + SchemeGuest bool + SchemeUser bool + SchemeAdmin bool + ExplicitRoles string + ExpectedRoles string + ExpectedExplicitRoles string + ExpectedSchemeGuest bool + ExpectedSchemeUser bool + ExpectedSchemeAdmin bool + }{ + { + Name: "channel user implicit", + SchemeUser: true, + ExpectedRoles: ts.DefaultChannelUserRole, + ExpectedSchemeUser: true, + }, + { + Name: "channel user explicit", + ExplicitRoles: "channel_user", + ExpectedRoles: ts.DefaultChannelUserRole, + ExpectedSchemeUser: true, + }, + { + Name: "channel guest implicit", + SchemeGuest: true, + ExpectedRoles: ts.DefaultChannelGuestRole, + ExpectedSchemeGuest: true, + }, + { + Name: "channel guest explicit", + ExplicitRoles: "channel_guest", + ExpectedRoles: ts.DefaultChannelGuestRole, + ExpectedSchemeGuest: true, + }, + { + Name: "channel admin implicit", + SchemeUser: true, + SchemeAdmin: true, + ExpectedRoles: ts.DefaultChannelUserRole + " " + ts.DefaultChannelAdminRole, + ExpectedSchemeUser: true, + ExpectedSchemeAdmin: true, + }, + { + Name: "channel admin explicit", + ExplicitRoles: "channel_user channel_admin", + ExpectedRoles: ts.DefaultChannelUserRole + " " + ts.DefaultChannelAdminRole, + ExpectedSchemeUser: true, + ExpectedSchemeAdmin: true, + }, + { + Name: "channel user implicit and explicit custom role", + SchemeUser: true, + ExplicitRoles: "test", + ExpectedRoles: "test " + ts.DefaultChannelUserRole, + ExpectedExplicitRoles: "test", + ExpectedSchemeUser: true, + }, + { + Name: "channel user explicit and explicit custom role", + ExplicitRoles: "channel_user test", + ExpectedRoles: "test " + ts.DefaultChannelUserRole, + ExpectedExplicitRoles: "test", + ExpectedSchemeUser: true, + }, + { + Name: "channel guest implicit and explicit custom role", + SchemeGuest: true, + ExplicitRoles: "test", + ExpectedRoles: "test " + ts.DefaultChannelGuestRole, + ExpectedExplicitRoles: "test", + ExpectedSchemeGuest: true, + }, + { + Name: "channel guest explicit and explicit custom role", + ExplicitRoles: "channel_guest test", + ExpectedRoles: "test " + ts.DefaultChannelGuestRole, + ExpectedExplicitRoles: "test", + ExpectedSchemeGuest: true, + }, + { + Name: "channel admin implicit and explicit custom role", + SchemeUser: true, + SchemeAdmin: true, + ExplicitRoles: "test", + ExpectedRoles: "test " + ts.DefaultChannelUserRole + " " + ts.DefaultChannelAdminRole, + ExpectedExplicitRoles: "test", + ExpectedSchemeUser: true, + ExpectedSchemeAdmin: true, + }, + { + Name: "channel admin explicit and explicit custom role", + ExplicitRoles: "channel_user channel_admin test", + ExpectedRoles: "test " + ts.DefaultChannelUserRole + " " + ts.DefaultChannelAdminRole, + ExpectedExplicitRoles: "test", + ExpectedSchemeUser: true, + ExpectedSchemeAdmin: true, + }, + { + Name: "channel member with only explicit custom roles", + ExplicitRoles: "test test2", + ExpectedRoles: "test test2", + ExpectedExplicitRoles: "test test2", + }, + } + + for _, tc := range testCases { + t.Run(tc.Name, func(t *testing.T) { + member.SchemeGuest = tc.SchemeGuest + member.SchemeUser = tc.SchemeUser + member.SchemeAdmin = tc.SchemeAdmin + member.ExplicitRoles = tc.ExplicitRoles + member, err = ss.Channel().UpdateMember(member) + require.Nil(t, err) + assert.Equal(t, tc.ExpectedRoles, member.Roles) + assert.Equal(t, tc.ExpectedExplicitRoles, member.ExplicitRoles) + assert.Equal(t, tc.ExpectedSchemeGuest, member.SchemeGuest) + assert.Equal(t, tc.ExpectedSchemeUser, member.SchemeUser) + assert.Equal(t, tc.ExpectedSchemeAdmin, member.SchemeAdmin) + }) + } + }) + + t.Run("insert member correctly (in channel with channel scheme)", func(t *testing.T) { + cs := &model.Scheme{ + Name: model.NewId(), + DisplayName: model.NewId(), + Description: model.NewId(), + Scope: model.SCHEME_SCOPE_CHANNEL, + } + cs, err = ss.Scheme().Save(cs) + require.Nil(t, err) + + team := &model.Team{ + DisplayName: "Name", + Name: "zz" + model.NewId(), + Email: MakeEmail(), + Type: model.TEAM_OPEN, + } + + team, err = ss.Team().Save(team) + require.Nil(t, err) + + channel, err := ss.Channel().Save(&model.Channel{ + DisplayName: "DisplayName", + Name: "z-z-z" + model.NewId() + "b", + Type: model.CHANNEL_OPEN, + TeamId: team.Id, + SchemeId: &cs.Id, + }, -1) + require.Nil(t, err) + defer func() { ss.Channel().PermanentDelete(channel.Id) }() + + member := &model.ChannelMember{ + ChannelId: channel.Id, + UserId: u1.Id, + NotifyProps: defaultNotifyProps, + } + member, err = ss.Channel().SaveMember(member) + require.Nil(t, err) + + testCases := []struct { + Name string + SchemeGuest bool + SchemeUser bool + SchemeAdmin bool + ExplicitRoles string + ExpectedRoles string + ExpectedExplicitRoles string + ExpectedSchemeGuest bool + ExpectedSchemeUser bool + ExpectedSchemeAdmin bool + }{ + { + Name: "channel user implicit", + SchemeUser: true, + ExpectedRoles: cs.DefaultChannelUserRole, + ExpectedSchemeUser: true, + }, + { + Name: "channel user explicit", + ExplicitRoles: "channel_user", + ExpectedRoles: cs.DefaultChannelUserRole, + ExpectedSchemeUser: true, + }, + { + Name: "channel guest implicit", + SchemeGuest: true, + ExpectedRoles: cs.DefaultChannelGuestRole, + ExpectedSchemeGuest: true, + }, + { + Name: "channel guest explicit", + ExplicitRoles: "channel_guest", + ExpectedRoles: cs.DefaultChannelGuestRole, + ExpectedSchemeGuest: true, + }, + { + Name: "channel admin implicit", + SchemeUser: true, + SchemeAdmin: true, + ExpectedRoles: cs.DefaultChannelUserRole + " " + cs.DefaultChannelAdminRole, + ExpectedSchemeUser: true, + ExpectedSchemeAdmin: true, + }, + { + Name: "channel admin explicit", + ExplicitRoles: "channel_user channel_admin", + ExpectedRoles: cs.DefaultChannelUserRole + " " + cs.DefaultChannelAdminRole, + ExpectedSchemeUser: true, + ExpectedSchemeAdmin: true, + }, + { + Name: "channel user implicit and explicit custom role", + SchemeUser: true, + ExplicitRoles: "test", + ExpectedRoles: "test " + cs.DefaultChannelUserRole, + ExpectedExplicitRoles: "test", + ExpectedSchemeUser: true, + }, + { + Name: "channel user explicit and explicit custom role", + ExplicitRoles: "channel_user test", + ExpectedRoles: "test " + cs.DefaultChannelUserRole, + ExpectedExplicitRoles: "test", + ExpectedSchemeUser: true, + }, + { + Name: "channel guest implicit and explicit custom role", + SchemeGuest: true, + ExplicitRoles: "test", + ExpectedRoles: "test " + cs.DefaultChannelGuestRole, + ExpectedExplicitRoles: "test", + ExpectedSchemeGuest: true, + }, + { + Name: "channel guest explicit and explicit custom role", + ExplicitRoles: "channel_guest test", + ExpectedRoles: "test " + cs.DefaultChannelGuestRole, + ExpectedExplicitRoles: "test", + ExpectedSchemeGuest: true, + }, + { + Name: "channel admin implicit and explicit custom role", + SchemeUser: true, + SchemeAdmin: true, + ExplicitRoles: "test", + ExpectedRoles: "test " + cs.DefaultChannelUserRole + " " + cs.DefaultChannelAdminRole, + ExpectedExplicitRoles: "test", + ExpectedSchemeUser: true, + ExpectedSchemeAdmin: true, + }, + { + Name: "channel admin explicit and explicit custom role", + ExplicitRoles: "channel_user channel_admin test", + ExpectedRoles: "test " + cs.DefaultChannelUserRole + " " + cs.DefaultChannelAdminRole, + ExpectedExplicitRoles: "test", + ExpectedSchemeUser: true, + ExpectedSchemeAdmin: true, + }, + { + Name: "channel member with only explicit custom roles", + ExplicitRoles: "test test2", + ExpectedRoles: "test test2", + ExpectedExplicitRoles: "test test2", + }, + } + + for _, tc := range testCases { + t.Run(tc.Name, func(t *testing.T) { + member.SchemeGuest = tc.SchemeGuest + member.SchemeUser = tc.SchemeUser + member.SchemeAdmin = tc.SchemeAdmin + member.ExplicitRoles = tc.ExplicitRoles + member, err = ss.Channel().UpdateMember(member) + require.Nil(t, err) + assert.Equal(t, tc.ExpectedRoles, member.Roles) + assert.Equal(t, tc.ExpectedExplicitRoles, member.ExplicitRoles) + assert.Equal(t, tc.ExpectedSchemeGuest, member.SchemeGuest) + assert.Equal(t, tc.ExpectedSchemeUser, member.SchemeUser) + assert.Equal(t, tc.ExpectedSchemeAdmin, member.SchemeAdmin) + }) + } + }) +} + +func testChannelUpdateMultipleMembers(t *testing.T, ss store.Store) { + u1, err := ss.User().Save(&model.User{Username: model.NewId(), Email: MakeEmail()}) + require.Nil(t, err) + u2, err := ss.User().Save(&model.User{Username: model.NewId(), Email: MakeEmail()}) + require.Nil(t, err) + defaultNotifyProps := model.GetDefaultChannelNotifyProps() + + t.Run("any not valid channel member", func(t *testing.T) { + m1 := &model.ChannelMember{ChannelId: "wrong", UserId: u1.Id, NotifyProps: defaultNotifyProps} + m2 := &model.ChannelMember{ChannelId: model.NewId(), UserId: u2.Id, NotifyProps: defaultNotifyProps} + _, err = ss.Channel().SaveMultipleMembers([]*model.ChannelMember{m1, m2}) + require.NotNil(t, err) + require.Equal(t, "model.channel_member.is_valid.channel_id.app_error", err.Id) + }) + + t.Run("duplicated entries should fail", func(t *testing.T) { + channelID1 := model.NewId() + m1 := &model.ChannelMember{ChannelId: channelID1, UserId: u1.Id, NotifyProps: defaultNotifyProps} + m2 := &model.ChannelMember{ChannelId: channelID1, UserId: u1.Id, NotifyProps: defaultNotifyProps} + _, err = ss.Channel().SaveMultipleMembers([]*model.ChannelMember{m1, m2}) + require.NotNil(t, err) + require.Equal(t, "store.sql_channel.save_member.exists.app_error", err.Id) + }) + + t.Run("insert members correctly (in channel without channel scheme and team without scheme)", func(t *testing.T) { + team := &model.Team{ + DisplayName: "Name", + Name: "zz" + model.NewId(), + Email: MakeEmail(), + Type: model.TEAM_OPEN, + } + + team, err = ss.Team().Save(team) + require.Nil(t, err) + + channel := &model.Channel{ + DisplayName: "DisplayName", + Name: "z-z-z" + model.NewId() + "b", + Type: model.CHANNEL_OPEN, + TeamId: team.Id, + } + channel, err = ss.Channel().Save(channel, -1) + require.Nil(t, err) + defer func() { ss.Channel().PermanentDelete(channel.Id) }() + + member := &model.ChannelMember{ChannelId: channel.Id, UserId: u1.Id, NotifyProps: defaultNotifyProps} + otherMember := &model.ChannelMember{ChannelId: channel.Id, UserId: u2.Id, NotifyProps: defaultNotifyProps} + var members []*model.ChannelMember + members, err = ss.Channel().SaveMultipleMembers([]*model.ChannelMember{member, otherMember}) + require.Nil(t, err) + defer ss.Channel().RemoveMember(channel.Id, u1.Id) + defer ss.Channel().RemoveMember(channel.Id, u2.Id) + require.Len(t, members, 2) + member = members[0] + otherMember = members[1] + + testCases := []struct { + Name string + SchemeGuest bool + SchemeUser bool + SchemeAdmin bool + ExplicitRoles string + ExpectedRoles string + ExpectedExplicitRoles string + ExpectedSchemeGuest bool + ExpectedSchemeUser bool + ExpectedSchemeAdmin bool + }{ + { + Name: "channel user implicit", + SchemeUser: true, + ExpectedRoles: "channel_user", + ExpectedSchemeUser: true, + }, + { + Name: "channel user explicit", + ExplicitRoles: "channel_user", + ExpectedRoles: "channel_user", + ExpectedSchemeUser: true, + }, + { + Name: "channel guest implicit", + SchemeGuest: true, + ExpectedRoles: "channel_guest", + ExpectedSchemeGuest: true, + }, + { + Name: "channel guest explicit", + ExplicitRoles: "channel_guest", + ExpectedRoles: "channel_guest", + ExpectedSchemeGuest: true, + }, + { + Name: "channel admin implicit", + SchemeUser: true, + SchemeAdmin: true, + ExpectedRoles: "channel_user channel_admin", + ExpectedSchemeUser: true, + ExpectedSchemeAdmin: true, + }, + { + Name: "channel admin explicit", + ExplicitRoles: "channel_user channel_admin", + ExpectedRoles: "channel_user channel_admin", + ExpectedSchemeUser: true, + ExpectedSchemeAdmin: true, + }, + { + Name: "channel user implicit and explicit custom role", + SchemeUser: true, + ExplicitRoles: "test", + ExpectedRoles: "test channel_user", + ExpectedExplicitRoles: "test", + ExpectedSchemeUser: true, + }, + { + Name: "channel user explicit and explicit custom role", + ExplicitRoles: "channel_user test", + ExpectedRoles: "test channel_user", + ExpectedExplicitRoles: "test", + ExpectedSchemeUser: true, + }, + { + Name: "channel guest implicit and explicit custom role", + SchemeGuest: true, + ExplicitRoles: "test", + ExpectedRoles: "test channel_guest", + ExpectedExplicitRoles: "test", + ExpectedSchemeGuest: true, + }, + { + Name: "channel guest explicit and explicit custom role", + ExplicitRoles: "channel_guest test", + ExpectedRoles: "test channel_guest", + ExpectedExplicitRoles: "test", + ExpectedSchemeGuest: true, + }, + { + Name: "channel admin implicit and explicit custom role", + SchemeUser: true, + SchemeAdmin: true, + ExplicitRoles: "test", + ExpectedRoles: "test channel_user channel_admin", + ExpectedExplicitRoles: "test", + ExpectedSchemeUser: true, + ExpectedSchemeAdmin: true, + }, + { + Name: "channel admin explicit and explicit custom role", + ExplicitRoles: "channel_user channel_admin test", + ExpectedRoles: "test channel_user channel_admin", + ExpectedExplicitRoles: "test", + ExpectedSchemeUser: true, + ExpectedSchemeAdmin: true, + }, + { + Name: "channel member with only explicit custom roles", + ExplicitRoles: "test test2", + ExpectedRoles: "test test2", + ExpectedExplicitRoles: "test test2", + }, + } + + for _, tc := range testCases { + t.Run(tc.Name, func(t *testing.T) { + member.SchemeGuest = tc.SchemeGuest + member.SchemeUser = tc.SchemeUser + member.SchemeAdmin = tc.SchemeAdmin + member.ExplicitRoles = tc.ExplicitRoles + var members []*model.ChannelMember + members, err = ss.Channel().UpdateMultipleMembers([]*model.ChannelMember{member, otherMember}) + require.Nil(t, err) + require.Len(t, members, 2) + member = members[0] + + assert.Equal(t, tc.ExpectedRoles, member.Roles) + assert.Equal(t, tc.ExpectedExplicitRoles, member.ExplicitRoles) + assert.Equal(t, tc.ExpectedSchemeGuest, member.SchemeGuest) + assert.Equal(t, tc.ExpectedSchemeUser, member.SchemeUser) + assert.Equal(t, tc.ExpectedSchemeAdmin, member.SchemeAdmin) + }) + } + }) + + t.Run("insert members correctly (in channel without scheme and team with scheme)", func(t *testing.T) { + ts := &model.Scheme{ + Name: model.NewId(), + DisplayName: model.NewId(), + Description: model.NewId(), + Scope: model.SCHEME_SCOPE_TEAM, + } + ts, err = ss.Scheme().Save(ts) + require.Nil(t, err) + + team := &model.Team{ + DisplayName: "Name", + Name: "zz" + model.NewId(), + Email: MakeEmail(), + Type: model.TEAM_OPEN, + SchemeId: &ts.Id, + } + + team, err = ss.Team().Save(team) + require.Nil(t, err) + + channel := &model.Channel{ + DisplayName: "DisplayName", + Name: "z-z-z" + model.NewId() + "b", + Type: model.CHANNEL_OPEN, + TeamId: team.Id, + } + channel, err = ss.Channel().Save(channel, -1) + require.Nil(t, err) + defer func() { ss.Channel().PermanentDelete(channel.Id) }() + + member := &model.ChannelMember{ChannelId: channel.Id, UserId: u1.Id, NotifyProps: defaultNotifyProps} + otherMember := &model.ChannelMember{ChannelId: channel.Id, UserId: u2.Id, NotifyProps: defaultNotifyProps} + var members []*model.ChannelMember + members, err = ss.Channel().SaveMultipleMembers([]*model.ChannelMember{member, otherMember}) + require.Nil(t, err) + defer ss.Channel().RemoveMember(channel.Id, u1.Id) + defer ss.Channel().RemoveMember(channel.Id, u2.Id) + require.Len(t, members, 2) + member = members[0] + otherMember = members[1] + + testCases := []struct { + Name string + SchemeGuest bool + SchemeUser bool + SchemeAdmin bool + ExplicitRoles string + ExpectedRoles string + ExpectedExplicitRoles string + ExpectedSchemeGuest bool + ExpectedSchemeUser bool + ExpectedSchemeAdmin bool + }{ + { + Name: "channel user implicit", + SchemeUser: true, + ExpectedRoles: ts.DefaultChannelUserRole, + ExpectedSchemeUser: true, + }, + { + Name: "channel user explicit", + ExplicitRoles: "channel_user", + ExpectedRoles: ts.DefaultChannelUserRole, + ExpectedSchemeUser: true, + }, + { + Name: "channel guest implicit", + SchemeGuest: true, + ExpectedRoles: ts.DefaultChannelGuestRole, + ExpectedSchemeGuest: true, + }, + { + Name: "channel guest explicit", + ExplicitRoles: "channel_guest", + ExpectedRoles: ts.DefaultChannelGuestRole, + ExpectedSchemeGuest: true, + }, + { + Name: "channel admin implicit", + SchemeUser: true, + SchemeAdmin: true, + ExpectedRoles: ts.DefaultChannelUserRole + " " + ts.DefaultChannelAdminRole, + ExpectedSchemeUser: true, + ExpectedSchemeAdmin: true, + }, + { + Name: "channel admin explicit", + ExplicitRoles: "channel_user channel_admin", + ExpectedRoles: ts.DefaultChannelUserRole + " " + ts.DefaultChannelAdminRole, + ExpectedSchemeUser: true, + ExpectedSchemeAdmin: true, + }, + { + Name: "channel user implicit and explicit custom role", + SchemeUser: true, + ExplicitRoles: "test", + ExpectedRoles: "test " + ts.DefaultChannelUserRole, + ExpectedExplicitRoles: "test", + ExpectedSchemeUser: true, + }, + { + Name: "channel user explicit and explicit custom role", + ExplicitRoles: "channel_user test", + ExpectedRoles: "test " + ts.DefaultChannelUserRole, + ExpectedExplicitRoles: "test", + ExpectedSchemeUser: true, + }, + { + Name: "channel guest implicit and explicit custom role", + SchemeGuest: true, + ExplicitRoles: "test", + ExpectedRoles: "test " + ts.DefaultChannelGuestRole, + ExpectedExplicitRoles: "test", + ExpectedSchemeGuest: true, + }, + { + Name: "channel guest explicit and explicit custom role", + ExplicitRoles: "channel_guest test", + ExpectedRoles: "test " + ts.DefaultChannelGuestRole, + ExpectedExplicitRoles: "test", + ExpectedSchemeGuest: true, + }, + { + Name: "channel admin implicit and explicit custom role", + SchemeUser: true, + SchemeAdmin: true, + ExplicitRoles: "test", + ExpectedRoles: "test " + ts.DefaultChannelUserRole + " " + ts.DefaultChannelAdminRole, + ExpectedExplicitRoles: "test", + ExpectedSchemeUser: true, + ExpectedSchemeAdmin: true, + }, + { + Name: "channel admin explicit and explicit custom role", + ExplicitRoles: "channel_user channel_admin test", + ExpectedRoles: "test " + ts.DefaultChannelUserRole + " " + ts.DefaultChannelAdminRole, + ExpectedExplicitRoles: "test", + ExpectedSchemeUser: true, + ExpectedSchemeAdmin: true, + }, + { + Name: "channel member with only explicit custom roles", + ExplicitRoles: "test test2", + ExpectedRoles: "test test2", + ExpectedExplicitRoles: "test test2", + }, + } + + for _, tc := range testCases { + t.Run(tc.Name, func(t *testing.T) { + member.SchemeGuest = tc.SchemeGuest + member.SchemeUser = tc.SchemeUser + member.SchemeAdmin = tc.SchemeAdmin + member.ExplicitRoles = tc.ExplicitRoles + var members []*model.ChannelMember + members, err = ss.Channel().UpdateMultipleMembers([]*model.ChannelMember{member, otherMember}) + require.Nil(t, err) + require.Len(t, members, 2) + member = members[0] + + assert.Equal(t, tc.ExpectedRoles, member.Roles) + assert.Equal(t, tc.ExpectedExplicitRoles, member.ExplicitRoles) + assert.Equal(t, tc.ExpectedSchemeGuest, member.SchemeGuest) + assert.Equal(t, tc.ExpectedSchemeUser, member.SchemeUser) + assert.Equal(t, tc.ExpectedSchemeAdmin, member.SchemeAdmin) + }) + } + }) + + t.Run("insert members correctly (in channel with channel scheme)", func(t *testing.T) { + cs := &model.Scheme{ + Name: model.NewId(), + DisplayName: model.NewId(), + Description: model.NewId(), + Scope: model.SCHEME_SCOPE_CHANNEL, + } + cs, err = ss.Scheme().Save(cs) + require.Nil(t, err) + + team := &model.Team{ + DisplayName: "Name", + Name: "zz" + model.NewId(), + Email: MakeEmail(), + Type: model.TEAM_OPEN, + } + + team, err = ss.Team().Save(team) + require.Nil(t, err) + + channel, err := ss.Channel().Save(&model.Channel{ + DisplayName: "DisplayName", + Name: "z-z-z" + model.NewId() + "b", + Type: model.CHANNEL_OPEN, + TeamId: team.Id, + SchemeId: &cs.Id, + }, -1) + require.Nil(t, err) + defer func() { ss.Channel().PermanentDelete(channel.Id) }() + + member := &model.ChannelMember{ChannelId: channel.Id, UserId: u1.Id, NotifyProps: defaultNotifyProps} + otherMember := &model.ChannelMember{ChannelId: channel.Id, UserId: u2.Id, NotifyProps: defaultNotifyProps} + members, err := ss.Channel().SaveMultipleMembers([]*model.ChannelMember{member, otherMember}) + require.Nil(t, err) + defer ss.Channel().RemoveMember(channel.Id, u1.Id) + defer ss.Channel().RemoveMember(channel.Id, u2.Id) + require.Len(t, members, 2) + member = members[0] + otherMember = members[1] + + testCases := []struct { + Name string + SchemeGuest bool + SchemeUser bool + SchemeAdmin bool + ExplicitRoles string + ExpectedRoles string + ExpectedExplicitRoles string + ExpectedSchemeGuest bool + ExpectedSchemeUser bool + ExpectedSchemeAdmin bool + }{ + { + Name: "channel user implicit", + SchemeUser: true, + ExpectedRoles: cs.DefaultChannelUserRole, + ExpectedSchemeUser: true, + }, + { + Name: "channel user explicit", + ExplicitRoles: "channel_user", + ExpectedRoles: cs.DefaultChannelUserRole, + ExpectedSchemeUser: true, + }, + { + Name: "channel guest implicit", + SchemeGuest: true, + ExpectedRoles: cs.DefaultChannelGuestRole, + ExpectedSchemeGuest: true, + }, + { + Name: "channel guest explicit", + ExplicitRoles: "channel_guest", + ExpectedRoles: cs.DefaultChannelGuestRole, + ExpectedSchemeGuest: true, + }, + { + Name: "channel admin implicit", + SchemeUser: true, + SchemeAdmin: true, + ExpectedRoles: cs.DefaultChannelUserRole + " " + cs.DefaultChannelAdminRole, + ExpectedSchemeUser: true, + ExpectedSchemeAdmin: true, + }, + { + Name: "channel admin explicit", + ExplicitRoles: "channel_user channel_admin", + ExpectedRoles: cs.DefaultChannelUserRole + " " + cs.DefaultChannelAdminRole, + ExpectedSchemeUser: true, + ExpectedSchemeAdmin: true, + }, + { + Name: "channel user implicit and explicit custom role", + SchemeUser: true, + ExplicitRoles: "test", + ExpectedRoles: "test " + cs.DefaultChannelUserRole, + ExpectedExplicitRoles: "test", + ExpectedSchemeUser: true, + }, + { + Name: "channel user explicit and explicit custom role", + ExplicitRoles: "channel_user test", + ExpectedRoles: "test " + cs.DefaultChannelUserRole, + ExpectedExplicitRoles: "test", + ExpectedSchemeUser: true, + }, + { + Name: "channel guest implicit and explicit custom role", + SchemeGuest: true, + ExplicitRoles: "test", + ExpectedRoles: "test " + cs.DefaultChannelGuestRole, + ExpectedExplicitRoles: "test", + ExpectedSchemeGuest: true, + }, + { + Name: "channel guest explicit and explicit custom role", + ExplicitRoles: "channel_guest test", + ExpectedRoles: "test " + cs.DefaultChannelGuestRole, + ExpectedExplicitRoles: "test", + ExpectedSchemeGuest: true, + }, + { + Name: "channel admin implicit and explicit custom role", + SchemeUser: true, + SchemeAdmin: true, + ExplicitRoles: "test", + ExpectedRoles: "test " + cs.DefaultChannelUserRole + " " + cs.DefaultChannelAdminRole, + ExpectedExplicitRoles: "test", + ExpectedSchemeUser: true, + ExpectedSchemeAdmin: true, + }, + { + Name: "channel admin explicit and explicit custom role", + ExplicitRoles: "channel_user channel_admin test", + ExpectedRoles: "test " + cs.DefaultChannelUserRole + " " + cs.DefaultChannelAdminRole, + ExpectedExplicitRoles: "test", + ExpectedSchemeUser: true, + ExpectedSchemeAdmin: true, + }, + { + Name: "channel member with only explicit custom roles", + ExplicitRoles: "test test2", + ExpectedRoles: "test test2", + ExpectedExplicitRoles: "test test2", + }, + } + + for _, tc := range testCases { + t.Run(tc.Name, func(t *testing.T) { + member.SchemeGuest = tc.SchemeGuest + member.SchemeUser = tc.SchemeUser + member.SchemeAdmin = tc.SchemeAdmin + member.ExplicitRoles = tc.ExplicitRoles + members, err := ss.Channel().UpdateMultipleMembers([]*model.ChannelMember{member, otherMember}) + require.Nil(t, err) + require.Len(t, members, 2) + member = members[0] + + assert.Equal(t, tc.ExpectedRoles, member.Roles) + assert.Equal(t, tc.ExpectedExplicitRoles, member.ExplicitRoles) + assert.Equal(t, tc.ExpectedSchemeGuest, member.SchemeGuest) + assert.Equal(t, tc.ExpectedSchemeUser, member.SchemeUser) + assert.Equal(t, tc.ExpectedSchemeAdmin, member.SchemeAdmin) + }) + } + }) +} + +func testChannelRemoveMember(t *testing.T, ss store.Store) { + u1, err := ss.User().Save(&model.User{Username: model.NewId(), Email: MakeEmail()}) + require.Nil(t, err) + u2, err := ss.User().Save(&model.User{Username: model.NewId(), Email: MakeEmail()}) + require.Nil(t, err) + u3, err := ss.User().Save(&model.User{Username: model.NewId(), Email: MakeEmail()}) + require.Nil(t, err) + u4, err := ss.User().Save(&model.User{Username: model.NewId(), Email: MakeEmail()}) + require.Nil(t, err) + channelID := model.NewId() + defaultNotifyProps := model.GetDefaultChannelNotifyProps() + m1 := &model.ChannelMember{ChannelId: channelID, UserId: u1.Id, NotifyProps: defaultNotifyProps} + m2 := &model.ChannelMember{ChannelId: channelID, UserId: u2.Id, NotifyProps: defaultNotifyProps} + m3 := &model.ChannelMember{ChannelId: channelID, UserId: u3.Id, NotifyProps: defaultNotifyProps} + m4 := &model.ChannelMember{ChannelId: channelID, UserId: u4.Id, NotifyProps: defaultNotifyProps} + _, err = ss.Channel().SaveMultipleMembers([]*model.ChannelMember{m1, m2, m3, m4}) + require.Nil(t, err) + + t.Run("remove member from not existing channel", func(t *testing.T) { + err = ss.Channel().RemoveMember("not-existing-channel", u1.Id) + require.Nil(t, err) + var membersCount int64 + membersCount, err = ss.Channel().GetMemberCount(channelID, false) + require.Nil(t, err) + require.Equal(t, int64(4), membersCount) + }) + + t.Run("remove not existing member from an existing channel", func(t *testing.T) { + err = ss.Channel().RemoveMember(channelID, model.NewId()) + require.Nil(t, err) + var membersCount int64 + membersCount, err = ss.Channel().GetMemberCount(channelID, false) + require.Nil(t, err) + require.Equal(t, int64(4), membersCount) + }) + + t.Run("remove existing member from an existing channel", func(t *testing.T) { + err = ss.Channel().RemoveMember(channelID, u1.Id) + require.Nil(t, err) + defer ss.Channel().SaveMember(m1) + var membersCount int64 + membersCount, err = ss.Channel().GetMemberCount(channelID, false) + require.Nil(t, err) + require.Equal(t, int64(3), membersCount) + }) +} + +func testChannelRemoveMembers(t *testing.T, ss store.Store) { + u1, err := ss.User().Save(&model.User{Username: model.NewId(), Email: MakeEmail()}) + require.Nil(t, err) + u2, err := ss.User().Save(&model.User{Username: model.NewId(), Email: MakeEmail()}) + require.Nil(t, err) + u3, err := ss.User().Save(&model.User{Username: model.NewId(), Email: MakeEmail()}) + require.Nil(t, err) + u4, err := ss.User().Save(&model.User{Username: model.NewId(), Email: MakeEmail()}) + require.Nil(t, err) + channelID := model.NewId() + defaultNotifyProps := model.GetDefaultChannelNotifyProps() + m1 := &model.ChannelMember{ChannelId: channelID, UserId: u1.Id, NotifyProps: defaultNotifyProps} + m2 := &model.ChannelMember{ChannelId: channelID, UserId: u2.Id, NotifyProps: defaultNotifyProps} + m3 := &model.ChannelMember{ChannelId: channelID, UserId: u3.Id, NotifyProps: defaultNotifyProps} + m4 := &model.ChannelMember{ChannelId: channelID, UserId: u4.Id, NotifyProps: defaultNotifyProps} + _, err = ss.Channel().SaveMultipleMembers([]*model.ChannelMember{m1, m2, m3, m4}) + require.Nil(t, err) + + t.Run("remove members from not existing channel", func(t *testing.T) { + err = ss.Channel().RemoveMembers("not-existing-channel", []string{u1.Id, u2.Id, u3.Id, u4.Id}) + require.Nil(t, err) + var membersCount int64 + membersCount, err = ss.Channel().GetMemberCount(channelID, false) + require.Nil(t, err) + require.Equal(t, int64(4), membersCount) + }) + + t.Run("remove not existing members from an existing channel", func(t *testing.T) { + err = ss.Channel().RemoveMembers(channelID, []string{model.NewId(), model.NewId()}) + require.Nil(t, err) + var membersCount int64 + membersCount, err = ss.Channel().GetMemberCount(channelID, false) + require.Nil(t, err) + require.Equal(t, int64(4), membersCount) + }) + + t.Run("remove not existing and not existing members from an existing channel", func(t *testing.T) { + err = ss.Channel().RemoveMembers(channelID, []string{u1.Id, u2.Id, model.NewId(), model.NewId()}) + require.Nil(t, err) + defer ss.Channel().SaveMultipleMembers([]*model.ChannelMember{m1, m2}) + var membersCount int64 + membersCount, err = ss.Channel().GetMemberCount(channelID, false) + require.Nil(t, err) + require.Equal(t, int64(2), membersCount) + }) + t.Run("remove existing members from an existing channel", func(t *testing.T) { + err = ss.Channel().RemoveMembers(channelID, []string{u1.Id, u2.Id, u3.Id}) + require.Nil(t, err) + defer ss.Channel().SaveMultipleMembers([]*model.ChannelMember{m1, m2, m3}) + membersCount, err := ss.Channel().GetMemberCount(channelID, false) + require.Nil(t, err) + require.Equal(t, int64(1), membersCount) + }) +} + func testChannelDeleteMemberStore(t *testing.T, ss store.Store) { c1 := &model.Channel{} c1.TeamId = model.NewId() @@ -3633,7 +5788,7 @@ func testChannelStoreClearAllCustomRoleAssignments(t *testing.T, ss store.Store) ChannelId: c.Id, UserId: model.NewId(), NotifyProps: model.GetDefaultChannelNotifyProps(), - ExplicitRoles: "channel_user channel_admin system_user_access_token", + ExplicitRoles: "system_user_access_token channel_user channel_admin", } m2 := &model.ChannelMember{ ChannelId: c.Id, diff --git a/store/storetest/mocks/ChannelStore.go b/store/storetest/mocks/ChannelStore.go index c96e38754b..af6995b6d7 100644 --- a/store/storetest/mocks/ChannelStore.go +++ b/store/storetest/mocks/ChannelStore.go @@ -1350,6 +1350,22 @@ func (_m *ChannelStore) RemoveMember(channelId string, userId string) *model.App return r0 } +// RemoveMembers provides a mock function with given fields: channelId, userIds +func (_m *ChannelStore) RemoveMembers(channelId string, userIds []string) *model.AppError { + ret := _m.Called(channelId, userIds) + + var r0 *model.AppError + if rf, ok := ret.Get(0).(func(string, []string) *model.AppError); ok { + r0 = rf(channelId, userIds) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*model.AppError) + } + } + + return r0 +} + // ResetAllChannelSchemes provides a mock function with given fields: func (_m *ChannelStore) ResetAllChannelSchemes() *model.AppError { ret := _m.Called() @@ -1457,6 +1473,31 @@ func (_m *ChannelStore) SaveMember(member *model.ChannelMember) (*model.ChannelM return r0, r1 } +// SaveMultipleMembers provides a mock function with given fields: members +func (_m *ChannelStore) SaveMultipleMembers(members []*model.ChannelMember) ([]*model.ChannelMember, *model.AppError) { + ret := _m.Called(members) + + var r0 []*model.ChannelMember + if rf, ok := ret.Get(0).(func([]*model.ChannelMember) []*model.ChannelMember); ok { + r0 = rf(members) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*model.ChannelMember) + } + } + + var r1 *model.AppError + if rf, ok := ret.Get(1).(func([]*model.ChannelMember) *model.AppError); ok { + r1 = rf(members) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(*model.AppError) + } + } + + return r0, r1 +} + // SearchAllChannels provides a mock function with given fields: term, opts func (_m *ChannelStore) SearchAllChannels(term string, opts store.ChannelSearchOpts) (*model.ChannelListWithTeamData, int64, *model.AppError) { ret := _m.Called(term, opts) @@ -1746,6 +1787,31 @@ func (_m *ChannelStore) UpdateMembersRole(channelID string, userIDs []string) *m return r0 } +// UpdateMultipleMembers provides a mock function with given fields: members +func (_m *ChannelStore) UpdateMultipleMembers(members []*model.ChannelMember) ([]*model.ChannelMember, *model.AppError) { + ret := _m.Called(members) + + var r0 []*model.ChannelMember + if rf, ok := ret.Get(0).(func([]*model.ChannelMember) []*model.ChannelMember); ok { + r0 = rf(members) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*model.ChannelMember) + } + } + + var r1 *model.AppError + if rf, ok := ret.Get(1).(func([]*model.ChannelMember) *model.AppError); ok { + r1 = rf(members) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(*model.AppError) + } + } + + return r0, r1 +} + // UserBelongsToChannels provides a mock function with given fields: userId, channelIds func (_m *ChannelStore) UserBelongsToChannels(userId string, channelIds []string) (bool, *model.AppError) { ret := _m.Called(userId, channelIds) diff --git a/store/storetest/mocks/TeamStore.go b/store/storetest/mocks/TeamStore.go index 33f4b476b8..f9740366d8 100644 --- a/store/storetest/mocks/TeamStore.go +++ b/store/storetest/mocks/TeamStore.go @@ -865,6 +865,22 @@ func (_m *TeamStore) RemoveMember(teamId string, userId string) *model.AppError return r0 } +// RemoveMembers provides a mock function with given fields: teamId, userIds +func (_m *TeamStore) RemoveMembers(teamId string, userIds []string) *model.AppError { + ret := _m.Called(teamId, userIds) + + var r0 *model.AppError + if rf, ok := ret.Get(0).(func(string, []string) *model.AppError); ok { + r0 = rf(teamId, userIds) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*model.AppError) + } + } + + return r0 +} + // ResetAllTeamSchemes provides a mock function with given fields: func (_m *TeamStore) ResetAllTeamSchemes() *model.AppError { ret := _m.Called() @@ -931,6 +947,31 @@ func (_m *TeamStore) SaveMember(member *model.TeamMember, maxUsersPerTeam int) ( return r0, r1 } +// SaveMultipleMembers provides a mock function with given fields: members, maxUsersPerTeam +func (_m *TeamStore) SaveMultipleMembers(members []*model.TeamMember, maxUsersPerTeam int) ([]*model.TeamMember, *model.AppError) { + ret := _m.Called(members, maxUsersPerTeam) + + var r0 []*model.TeamMember + if rf, ok := ret.Get(0).(func([]*model.TeamMember, int) []*model.TeamMember); ok { + r0 = rf(members, maxUsersPerTeam) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*model.TeamMember) + } + } + + var r1 *model.AppError + if rf, ok := ret.Get(1).(func([]*model.TeamMember, int) *model.AppError); ok { + r1 = rf(members, maxUsersPerTeam) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(*model.AppError) + } + } + + return r0, r1 +} + // SearchAll provides a mock function with given fields: term func (_m *TeamStore) SearchAll(term string) ([]*model.Team, *model.AppError) { ret := _m.Called(term) @@ -1120,6 +1161,31 @@ func (_m *TeamStore) UpdateMembersRole(teamID string, userIDs []string) *model.A return r0 } +// UpdateMultipleMembers provides a mock function with given fields: members +func (_m *TeamStore) UpdateMultipleMembers(members []*model.TeamMember) ([]*model.TeamMember, *model.AppError) { + ret := _m.Called(members) + + var r0 []*model.TeamMember + if rf, ok := ret.Get(0).(func([]*model.TeamMember) []*model.TeamMember); ok { + r0 = rf(members) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*model.TeamMember) + } + } + + var r1 *model.AppError + if rf, ok := ret.Get(1).(func([]*model.TeamMember) *model.AppError); ok { + r1 = rf(members) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(*model.AppError) + } + } + + return r0, r1 +} + // UserBelongsToTeams provides a mock function with given fields: userId, teamIds func (_m *TeamStore) UserBelongsToTeams(userId string, teamIds []string) (bool, *model.AppError) { ret := _m.Called(userId, teamIds) diff --git a/store/storetest/team_store.go b/store/storetest/team_store.go index c269fdfe17..3f8b7661e7 100644 --- a/store/storetest/team_store.go +++ b/store/storetest/team_store.go @@ -46,6 +46,12 @@ func TestTeamStore(t *testing.T, ss store.Store) { t.Run("TeamPublicCount", func(t *testing.T) { testPublicTeamCount(t, ss) }) t.Run("TeamPrivateCount", func(t *testing.T) { testPrivateTeamCount(t, ss) }) t.Run("TeamMembers", func(t *testing.T) { testTeamMembers(t, ss) }) + t.Run("SaveMember", func(t *testing.T) { testTeamSaveMember(t, ss) }) + t.Run("SaveMultipleMembers", func(t *testing.T) { testTeamSaveMultipleMembers(t, ss) }) + t.Run("UpdateMember", func(t *testing.T) { testTeamUpdateMember(t, ss) }) + t.Run("UpdateMultipleMembers", func(t *testing.T) { testTeamUpdateMultipleMembers(t, ss) }) + t.Run("RemoveMember", func(t *testing.T) { testTeamRemoveMember(t, ss) }) + t.Run("RemoveMembers", func(t *testing.T) { testTeamRemoveMembers(t, ss) }) t.Run("GetMembersOrder", func(t *testing.T) { testGetMembersOrder(t, ss) }) t.Run("SaveTeamMemberMaxMembers", func(t *testing.T) { testSaveTeamMemberMaxMembers(t, ss) }) t.Run("GetTeamMember", func(t *testing.T) { testGetTeamMember(t, ss) }) @@ -880,17 +886,7 @@ func testGetMembersOrder(t *testing.T, ss store.Store) { m5 := &model.TeamMember{TeamId: teamId1, UserId: "44444444444444444444444444"} m6 := &model.TeamMember{TeamId: teamId2, UserId: "00000000000000000000000000"} - _, err := ss.Team().SaveMember(m1, -1) - require.Nil(t, err) - _, err = ss.Team().SaveMember(m2, -1) - require.Nil(t, err) - _, err = ss.Team().SaveMember(m3, -1) - require.Nil(t, err) - _, err = ss.Team().SaveMember(m4, -1) - require.Nil(t, err) - _, err = ss.Team().SaveMember(m5, -1) - require.Nil(t, err) - _, err = ss.Team().SaveMember(m6, -1) + _, err := ss.Team().SaveMultipleMembers([]*model.TeamMember{m1, m2, m3, m4, m5, m6}, -1) require.Nil(t, err) ms, err := ss.Team().GetMembers(teamId1, 0, 100, nil) @@ -911,11 +907,7 @@ func testTeamMembers(t *testing.T, ss store.Store) { m2 := &model.TeamMember{TeamId: teamId1, UserId: model.NewId()} m3 := &model.TeamMember{TeamId: teamId2, UserId: model.NewId()} - _, err := ss.Team().SaveMember(m1, -1) - require.Nil(t, err) - _, err = ss.Team().SaveMember(m2, -1) - require.Nil(t, err) - _, err = ss.Team().SaveMember(m3, -1) + _, err := ss.Team().SaveMultipleMembers([]*model.TeamMember{m1, m2, m3}, -1) require.Nil(t, err) ms, err := ss.Team().GetMembers(teamId1, 0, 100, nil) @@ -953,9 +945,7 @@ func testTeamMembers(t *testing.T, ss store.Store) { uid := model.NewId() m4 := &model.TeamMember{TeamId: teamId1, UserId: uid} m5 := &model.TeamMember{TeamId: teamId2, UserId: uid} - _, err = ss.Team().SaveMember(m4, -1) - require.Nil(t, err) - _, err = ss.Team().SaveMember(m5, -1) + _, err = ss.Team().SaveMultipleMembers([]*model.TeamMember{m4, m5}, -1) require.Nil(t, err) ms, err = ss.Team().GetTeamsForUser(uid) @@ -970,6 +960,1446 @@ func testTeamMembers(t *testing.T, ss store.Store) { require.Empty(t, ms) } +func testTeamSaveMember(t *testing.T, ss store.Store) { + u1, err := ss.User().Save(&model.User{Username: model.NewId(), Email: MakeEmail()}) + require.Nil(t, err) + u2, err := ss.User().Save(&model.User{Username: model.NewId(), Email: MakeEmail()}) + require.Nil(t, err) + + t.Run("not valid team member", func(t *testing.T) { + member := &model.TeamMember{TeamId: "wrong", UserId: u1.Id} + _, err = ss.Team().SaveMember(member, -1) + require.NotNil(t, err) + require.Equal(t, "model.team_member.is_valid.team_id.app_error", err.Id) + }) + + t.Run("too many members", func(t *testing.T) { + member := &model.TeamMember{TeamId: model.NewId(), UserId: u1.Id} + _, err = ss.Team().SaveMember(member, 0) + require.NotNil(t, err) + require.Equal(t, "store.sql_user.save.max_accounts.app_error", err.Id) + }) + + t.Run("too many members because previous existing members", func(t *testing.T) { + teamID := model.NewId() + + m1 := &model.TeamMember{TeamId: teamID, UserId: u1.Id} + _, err = ss.Team().SaveMember(m1, 1) + m2 := &model.TeamMember{TeamId: teamID, UserId: u2.Id} + _, err = ss.Team().SaveMember(m2, 1) + require.NotNil(t, err) + require.Equal(t, "store.sql_user.save.max_accounts.app_error", err.Id) + }) + + t.Run("duplicated entries should fail", func(t *testing.T) { + teamID1 := model.NewId() + m1 := &model.TeamMember{TeamId: teamID1, UserId: u1.Id} + _, err = ss.Team().SaveMember(m1, -1) + require.Nil(t, err) + m2 := &model.TeamMember{TeamId: teamID1, UserId: u1.Id} + _, err = ss.Team().SaveMember(m2, -1) + require.NotNil(t, err) + require.Equal(t, "store.sql_team.save_member.exists.app_error", err.Id) + }) + + t.Run("insert member correctly (in team without scheme)", func(t *testing.T) { + team := &model.Team{ + DisplayName: "Name", + Name: "zz" + model.NewId(), + Email: MakeEmail(), + Type: model.TEAM_OPEN, + } + + team, err = ss.Team().Save(team) + require.Nil(t, err) + + testCases := []struct { + Name string + SchemeGuest bool + SchemeUser bool + SchemeAdmin bool + ExplicitRoles string + ExpectedRoles string + ExpectedExplicitRoles string + ExpectedSchemeGuest bool + ExpectedSchemeUser bool + ExpectedSchemeAdmin bool + }{ + { + Name: "team user implicit", + SchemeUser: true, + ExpectedRoles: "team_user", + ExpectedSchemeUser: true, + }, + { + Name: "team user explicit", + ExplicitRoles: "team_user", + ExpectedRoles: "team_user", + ExpectedSchemeUser: true, + }, + { + Name: "team guest implicit", + SchemeGuest: true, + ExpectedRoles: "team_guest", + ExpectedSchemeGuest: true, + }, + { + Name: "team guest explicit", + ExplicitRoles: "team_guest", + ExpectedRoles: "team_guest", + ExpectedSchemeGuest: true, + }, + { + Name: "team admin implicit", + SchemeUser: true, + SchemeAdmin: true, + ExpectedRoles: "team_user team_admin", + ExpectedSchemeUser: true, + ExpectedSchemeAdmin: true, + }, + { + Name: "team admin explicit", + ExplicitRoles: "team_user team_admin", + ExpectedRoles: "team_user team_admin", + ExpectedSchemeUser: true, + ExpectedSchemeAdmin: true, + }, + { + Name: "team user implicit and explicit custom role", + SchemeUser: true, + ExplicitRoles: "test", + ExpectedRoles: "test team_user", + ExpectedExplicitRoles: "test", + ExpectedSchemeUser: true, + }, + { + Name: "team user explicit and explicit custom role", + ExplicitRoles: "team_user test", + ExpectedRoles: "test team_user", + ExpectedExplicitRoles: "test", + ExpectedSchemeUser: true, + }, + { + Name: "team guest implicit and explicit custom role", + SchemeGuest: true, + ExplicitRoles: "test", + ExpectedRoles: "test team_guest", + ExpectedExplicitRoles: "test", + ExpectedSchemeGuest: true, + }, + { + Name: "team guest explicit and explicit custom role", + ExplicitRoles: "team_guest test", + ExpectedRoles: "test team_guest", + ExpectedExplicitRoles: "test", + ExpectedSchemeGuest: true, + }, + { + Name: "team admin implicit and explicit custom role", + SchemeUser: true, + SchemeAdmin: true, + ExplicitRoles: "test", + ExpectedRoles: "test team_user team_admin", + ExpectedExplicitRoles: "test", + ExpectedSchemeUser: true, + ExpectedSchemeAdmin: true, + }, + { + Name: "team admin explicit and explicit custom role", + ExplicitRoles: "team_user team_admin test", + ExpectedRoles: "test team_user team_admin", + ExpectedExplicitRoles: "test", + ExpectedSchemeUser: true, + ExpectedSchemeAdmin: true, + }, + { + Name: "team member with only explicit custom roles", + ExplicitRoles: "test test2", + ExpectedRoles: "test test2", + ExpectedExplicitRoles: "test test2", + }, + } + + for _, tc := range testCases { + t.Run(tc.Name, func(t *testing.T) { + member := &model.TeamMember{ + TeamId: team.Id, + UserId: u1.Id, + SchemeGuest: tc.SchemeGuest, + SchemeUser: tc.SchemeUser, + SchemeAdmin: tc.SchemeAdmin, + ExplicitRoles: tc.ExplicitRoles, + } + member, err = ss.Team().SaveMember(member, -1) + require.Nil(t, err) + defer ss.Team().RemoveMember(team.Id, u1.Id) + + assert.Equal(t, tc.ExpectedRoles, member.Roles) + assert.Equal(t, tc.ExpectedExplicitRoles, member.ExplicitRoles) + assert.Equal(t, tc.ExpectedSchemeGuest, member.SchemeGuest) + assert.Equal(t, tc.ExpectedSchemeUser, member.SchemeUser) + assert.Equal(t, tc.ExpectedSchemeAdmin, member.SchemeAdmin) + }) + } + }) + + t.Run("insert member correctly (in team with scheme)", func(t *testing.T) { + ts := &model.Scheme{ + Name: model.NewId(), + DisplayName: model.NewId(), + Description: model.NewId(), + Scope: model.SCHEME_SCOPE_TEAM, + } + ts, err = ss.Scheme().Save(ts) + require.Nil(t, err) + + team := &model.Team{ + DisplayName: "Name", + Name: "zz" + model.NewId(), + Email: MakeEmail(), + Type: model.TEAM_OPEN, + SchemeId: &ts.Id, + } + + team, err = ss.Team().Save(team) + require.Nil(t, err) + + testCases := []struct { + Name string + SchemeGuest bool + SchemeUser bool + SchemeAdmin bool + ExplicitRoles string + ExpectedRoles string + ExpectedExplicitRoles string + ExpectedSchemeGuest bool + ExpectedSchemeUser bool + ExpectedSchemeAdmin bool + }{ + { + Name: "team user implicit", + SchemeUser: true, + ExpectedRoles: ts.DefaultTeamUserRole, + ExpectedSchemeUser: true, + }, + { + Name: "team user explicit", + ExplicitRoles: "team_user", + ExpectedRoles: ts.DefaultTeamUserRole, + ExpectedSchemeUser: true, + }, + { + Name: "team guest implicit", + SchemeGuest: true, + ExpectedRoles: ts.DefaultTeamGuestRole, + ExpectedSchemeGuest: true, + }, + { + Name: "team guest explicit", + ExplicitRoles: "team_guest", + ExpectedRoles: ts.DefaultTeamGuestRole, + ExpectedSchemeGuest: true, + }, + { + Name: "team admin implicit", + SchemeUser: true, + SchemeAdmin: true, + ExpectedRoles: ts.DefaultTeamUserRole + " " + ts.DefaultTeamAdminRole, + ExpectedSchemeUser: true, + ExpectedSchemeAdmin: true, + }, + { + Name: "team admin explicit", + ExplicitRoles: "team_user team_admin", + ExpectedRoles: ts.DefaultTeamUserRole + " " + ts.DefaultTeamAdminRole, + ExpectedSchemeUser: true, + ExpectedSchemeAdmin: true, + }, + { + Name: "team user implicit and explicit custom role", + SchemeUser: true, + ExplicitRoles: "test", + ExpectedRoles: "test " + ts.DefaultTeamUserRole, + ExpectedExplicitRoles: "test", + ExpectedSchemeUser: true, + }, + { + Name: "team user explicit and explicit custom role", + ExplicitRoles: "team_user test", + ExpectedRoles: "test " + ts.DefaultTeamUserRole, + ExpectedExplicitRoles: "test", + ExpectedSchemeUser: true, + }, + { + Name: "team guest implicit and explicit custom role", + SchemeGuest: true, + ExplicitRoles: "test", + ExpectedRoles: "test " + ts.DefaultTeamGuestRole, + ExpectedExplicitRoles: "test", + ExpectedSchemeGuest: true, + }, + { + Name: "team guest explicit and explicit custom role", + ExplicitRoles: "team_guest test", + ExpectedRoles: "test " + ts.DefaultTeamGuestRole, + ExpectedExplicitRoles: "test", + ExpectedSchemeGuest: true, + }, + { + Name: "team admin implicit and explicit custom role", + SchemeUser: true, + SchemeAdmin: true, + ExplicitRoles: "test", + ExpectedRoles: "test " + ts.DefaultTeamUserRole + " " + ts.DefaultTeamAdminRole, + ExpectedExplicitRoles: "test", + ExpectedSchemeUser: true, + ExpectedSchemeAdmin: true, + }, + { + Name: "team admin explicit and explicit custom role", + ExplicitRoles: "team_user team_admin test", + ExpectedRoles: "test " + ts.DefaultTeamUserRole + " " + ts.DefaultTeamAdminRole, + ExpectedExplicitRoles: "test", + ExpectedSchemeUser: true, + ExpectedSchemeAdmin: true, + }, + { + Name: "team member with only explicit custom roles", + ExplicitRoles: "test test2", + ExpectedRoles: "test test2", + ExpectedExplicitRoles: "test test2", + }, + } + + for _, tc := range testCases { + t.Run(tc.Name, func(t *testing.T) { + member := &model.TeamMember{ + TeamId: team.Id, + UserId: u1.Id, + SchemeGuest: tc.SchemeGuest, + SchemeUser: tc.SchemeUser, + SchemeAdmin: tc.SchemeAdmin, + ExplicitRoles: tc.ExplicitRoles, + } + member, err := ss.Team().SaveMember(member, -1) + require.Nil(t, err) + defer ss.Team().RemoveMember(team.Id, u1.Id) + + assert.Equal(t, tc.ExpectedRoles, member.Roles) + assert.Equal(t, tc.ExpectedExplicitRoles, member.ExplicitRoles) + assert.Equal(t, tc.ExpectedSchemeGuest, member.SchemeGuest) + assert.Equal(t, tc.ExpectedSchemeUser, member.SchemeUser) + assert.Equal(t, tc.ExpectedSchemeAdmin, member.SchemeAdmin) + }) + } + }) +} + +func testTeamSaveMultipleMembers(t *testing.T, ss store.Store) { + u1, err := ss.User().Save(&model.User{Username: model.NewId(), Email: MakeEmail()}) + require.Nil(t, err) + u2, err := ss.User().Save(&model.User{Username: model.NewId(), Email: MakeEmail()}) + require.Nil(t, err) + u3, err := ss.User().Save(&model.User{Username: model.NewId(), Email: MakeEmail()}) + require.Nil(t, err) + u4, err := ss.User().Save(&model.User{Username: model.NewId(), Email: MakeEmail()}) + require.Nil(t, err) + + t.Run("any not valid team member", func(t *testing.T) { + m1 := &model.TeamMember{TeamId: "wrong", UserId: u1.Id} + m2 := &model.TeamMember{TeamId: model.NewId(), UserId: u2.Id} + _, err = ss.Team().SaveMultipleMembers([]*model.TeamMember{m1, m2}, -1) + require.NotNil(t, err) + require.Equal(t, "model.team_member.is_valid.team_id.app_error", err.Id) + }) + + t.Run("too many members in one team", func(t *testing.T) { + teamID := model.NewId() + m1 := &model.TeamMember{TeamId: teamID, UserId: u1.Id} + m2 := &model.TeamMember{TeamId: teamID, UserId: u2.Id} + _, err = ss.Team().SaveMultipleMembers([]*model.TeamMember{m1, m2}, 0) + require.NotNil(t, err) + require.Equal(t, "store.sql_user.save.max_accounts.app_error", err.Id) + }) + + t.Run("too many members in one team because previous existing members", func(t *testing.T) { + teamID := model.NewId() + m1 := &model.TeamMember{TeamId: teamID, UserId: u1.Id} + m2 := &model.TeamMember{TeamId: teamID, UserId: u2.Id} + m3 := &model.TeamMember{TeamId: teamID, UserId: u3.Id} + m4 := &model.TeamMember{TeamId: teamID, UserId: u4.Id} + _, err = ss.Team().SaveMultipleMembers([]*model.TeamMember{m1, m2}, 3) + require.Nil(t, err) + + _, err = ss.Team().SaveMultipleMembers([]*model.TeamMember{m3, m4}, 3) + require.NotNil(t, err) + require.Equal(t, "store.sql_user.save.max_accounts.app_error", err.Id) + }) + + t.Run("too many members, but in different teams", func(t *testing.T) { + teamID1 := model.NewId() + teamID2 := model.NewId() + m1 := &model.TeamMember{TeamId: teamID1, UserId: u1.Id} + m2 := &model.TeamMember{TeamId: teamID1, UserId: u2.Id} + m3 := &model.TeamMember{TeamId: teamID1, UserId: u3.Id} + m4 := &model.TeamMember{TeamId: teamID2, UserId: u1.Id} + m5 := &model.TeamMember{TeamId: teamID2, UserId: u2.Id} + _, err = ss.Team().SaveMultipleMembers([]*model.TeamMember{m1, m2, m3, m4, m5}, 2) + require.NotNil(t, err) + require.Equal(t, "store.sql_user.save.max_accounts.app_error", err.Id) + }) + + t.Run("duplicated entries should fail", func(t *testing.T) { + teamID1 := model.NewId() + m1 := &model.TeamMember{TeamId: teamID1, UserId: u1.Id} + m2 := &model.TeamMember{TeamId: teamID1, UserId: u1.Id} + _, err = ss.Team().SaveMultipleMembers([]*model.TeamMember{m1, m2}, 10) + require.NotNil(t, err) + require.Equal(t, "store.sql_team.save_member.exists.app_error", err.Id) + }) + + t.Run("insert members correctly (in team without scheme)", func(t *testing.T) { + team := &model.Team{ + DisplayName: "Name", + Name: "zz" + model.NewId(), + Email: MakeEmail(), + Type: model.TEAM_OPEN, + } + + team, err = ss.Team().Save(team) + require.Nil(t, err) + + testCases := []struct { + Name string + SchemeGuest bool + SchemeUser bool + SchemeAdmin bool + ExplicitRoles string + ExpectedRoles string + ExpectedExplicitRoles string + ExpectedSchemeGuest bool + ExpectedSchemeUser bool + ExpectedSchemeAdmin bool + }{ + { + Name: "team user implicit", + SchemeUser: true, + ExpectedRoles: "team_user", + ExpectedSchemeUser: true, + }, + { + Name: "team user explicit", + ExplicitRoles: "team_user", + ExpectedRoles: "team_user", + ExpectedSchemeUser: true, + }, + { + Name: "team guest implicit", + SchemeGuest: true, + ExpectedRoles: "team_guest", + ExpectedSchemeGuest: true, + }, + { + Name: "team guest explicit", + ExplicitRoles: "team_guest", + ExpectedRoles: "team_guest", + ExpectedSchemeGuest: true, + }, + { + Name: "team admin implicit", + SchemeUser: true, + SchemeAdmin: true, + ExpectedRoles: "team_user team_admin", + ExpectedSchemeUser: true, + ExpectedSchemeAdmin: true, + }, + { + Name: "team admin explicit", + ExplicitRoles: "team_user team_admin", + ExpectedRoles: "team_user team_admin", + ExpectedSchemeUser: true, + ExpectedSchemeAdmin: true, + }, + { + Name: "team user implicit and explicit custom role", + SchemeUser: true, + ExplicitRoles: "test", + ExpectedRoles: "test team_user", + ExpectedExplicitRoles: "test", + ExpectedSchemeUser: true, + }, + { + Name: "team user explicit and explicit custom role", + ExplicitRoles: "team_user test", + ExpectedRoles: "test team_user", + ExpectedExplicitRoles: "test", + ExpectedSchemeUser: true, + }, + { + Name: "team guest implicit and explicit custom role", + SchemeGuest: true, + ExplicitRoles: "test", + ExpectedRoles: "test team_guest", + ExpectedExplicitRoles: "test", + ExpectedSchemeGuest: true, + }, + { + Name: "team guest explicit and explicit custom role", + ExplicitRoles: "team_guest test", + ExpectedRoles: "test team_guest", + ExpectedExplicitRoles: "test", + ExpectedSchemeGuest: true, + }, + { + Name: "team admin implicit and explicit custom role", + SchemeUser: true, + SchemeAdmin: true, + ExplicitRoles: "test", + ExpectedRoles: "test team_user team_admin", + ExpectedExplicitRoles: "test", + ExpectedSchemeUser: true, + ExpectedSchemeAdmin: true, + }, + { + Name: "team admin explicit and explicit custom role", + ExplicitRoles: "team_user team_admin test", + ExpectedRoles: "test team_user team_admin", + ExpectedExplicitRoles: "test", + ExpectedSchemeUser: true, + ExpectedSchemeAdmin: true, + }, + { + Name: "team member with only explicit custom roles", + ExplicitRoles: "test test2", + ExpectedRoles: "test test2", + ExpectedExplicitRoles: "test test2", + }, + } + + for _, tc := range testCases { + t.Run(tc.Name, func(t *testing.T) { + member := &model.TeamMember{ + TeamId: team.Id, + UserId: u1.Id, + SchemeGuest: tc.SchemeGuest, + SchemeUser: tc.SchemeUser, + SchemeAdmin: tc.SchemeAdmin, + ExplicitRoles: tc.ExplicitRoles, + } + otherMember := &model.TeamMember{ + TeamId: team.Id, + UserId: u2.Id, + SchemeGuest: tc.SchemeGuest, + SchemeUser: tc.SchemeUser, + SchemeAdmin: tc.SchemeAdmin, + ExplicitRoles: tc.ExplicitRoles, + } + var members []*model.TeamMember + members, err = ss.Team().SaveMultipleMembers([]*model.TeamMember{member, otherMember}, -1) + require.Nil(t, err) + require.Len(t, members, 2) + member = members[0] + defer ss.Team().RemoveMember(team.Id, u1.Id) + defer ss.Team().RemoveMember(team.Id, u2.Id) + + assert.Equal(t, tc.ExpectedRoles, member.Roles) + assert.Equal(t, tc.ExpectedExplicitRoles, member.ExplicitRoles) + assert.Equal(t, tc.ExpectedSchemeGuest, member.SchemeGuest) + assert.Equal(t, tc.ExpectedSchemeUser, member.SchemeUser) + assert.Equal(t, tc.ExpectedSchemeAdmin, member.SchemeAdmin) + }) + } + }) + + t.Run("insert members correctly (in team with scheme)", func(t *testing.T) { + ts := &model.Scheme{ + Name: model.NewId(), + DisplayName: model.NewId(), + Description: model.NewId(), + Scope: model.SCHEME_SCOPE_TEAM, + } + ts, err = ss.Scheme().Save(ts) + require.Nil(t, err) + + team := &model.Team{ + DisplayName: "Name", + Name: "zz" + model.NewId(), + Email: MakeEmail(), + Type: model.TEAM_OPEN, + SchemeId: &ts.Id, + } + + team, err = ss.Team().Save(team) + require.Nil(t, err) + + testCases := []struct { + Name string + SchemeGuest bool + SchemeUser bool + SchemeAdmin bool + ExplicitRoles string + ExpectedRoles string + ExpectedExplicitRoles string + ExpectedSchemeGuest bool + ExpectedSchemeUser bool + ExpectedSchemeAdmin bool + }{ + { + Name: "team user implicit", + SchemeUser: true, + ExpectedRoles: ts.DefaultTeamUserRole, + ExpectedSchemeUser: true, + }, + { + Name: "team user explicit", + ExplicitRoles: "team_user", + ExpectedRoles: ts.DefaultTeamUserRole, + ExpectedSchemeUser: true, + }, + { + Name: "team guest implicit", + SchemeGuest: true, + ExpectedRoles: ts.DefaultTeamGuestRole, + ExpectedSchemeGuest: true, + }, + { + Name: "team guest explicit", + ExplicitRoles: "team_guest", + ExpectedRoles: ts.DefaultTeamGuestRole, + ExpectedSchemeGuest: true, + }, + { + Name: "team admin implicit", + SchemeUser: true, + SchemeAdmin: true, + ExpectedRoles: ts.DefaultTeamUserRole + " " + ts.DefaultTeamAdminRole, + ExpectedSchemeUser: true, + ExpectedSchemeAdmin: true, + }, + { + Name: "team admin explicit", + ExplicitRoles: "team_user team_admin", + ExpectedRoles: ts.DefaultTeamUserRole + " " + ts.DefaultTeamAdminRole, + ExpectedSchemeUser: true, + ExpectedSchemeAdmin: true, + }, + { + Name: "team user implicit and explicit custom role", + SchemeUser: true, + ExplicitRoles: "test", + ExpectedRoles: "test " + ts.DefaultTeamUserRole, + ExpectedExplicitRoles: "test", + ExpectedSchemeUser: true, + }, + { + Name: "team user explicit and explicit custom role", + ExplicitRoles: "team_user test", + ExpectedRoles: "test " + ts.DefaultTeamUserRole, + ExpectedExplicitRoles: "test", + ExpectedSchemeUser: true, + }, + { + Name: "team guest implicit and explicit custom role", + SchemeGuest: true, + ExplicitRoles: "test", + ExpectedRoles: "test " + ts.DefaultTeamGuestRole, + ExpectedExplicitRoles: "test", + ExpectedSchemeGuest: true, + }, + { + Name: "team guest explicit and explicit custom role", + ExplicitRoles: "team_guest test", + ExpectedRoles: "test " + ts.DefaultTeamGuestRole, + ExpectedExplicitRoles: "test", + ExpectedSchemeGuest: true, + }, + { + Name: "team admin implicit and explicit custom role", + SchemeUser: true, + SchemeAdmin: true, + ExplicitRoles: "test", + ExpectedRoles: "test " + ts.DefaultTeamUserRole + " " + ts.DefaultTeamAdminRole, + ExpectedExplicitRoles: "test", + ExpectedSchemeUser: true, + ExpectedSchemeAdmin: true, + }, + { + Name: "team admin explicit and explicit custom role", + ExplicitRoles: "team_user team_admin test", + ExpectedRoles: "test " + ts.DefaultTeamUserRole + " " + ts.DefaultTeamAdminRole, + ExpectedExplicitRoles: "test", + ExpectedSchemeUser: true, + ExpectedSchemeAdmin: true, + }, + { + Name: "team member with only explicit custom roles", + ExplicitRoles: "test test2", + ExpectedRoles: "test test2", + ExpectedExplicitRoles: "test test2", + }, + } + + for _, tc := range testCases { + t.Run(tc.Name, func(t *testing.T) { + member := &model.TeamMember{ + TeamId: team.Id, + UserId: u1.Id, + SchemeGuest: tc.SchemeGuest, + SchemeUser: tc.SchemeUser, + SchemeAdmin: tc.SchemeAdmin, + ExplicitRoles: tc.ExplicitRoles, + } + otherMember := &model.TeamMember{ + TeamId: team.Id, + UserId: u2.Id, + SchemeGuest: tc.SchemeGuest, + SchemeUser: tc.SchemeUser, + SchemeAdmin: tc.SchemeAdmin, + ExplicitRoles: tc.ExplicitRoles, + } + members, err := ss.Team().SaveMultipleMembers([]*model.TeamMember{member, otherMember}, -1) + require.Nil(t, err) + require.Len(t, members, 2) + member = members[0] + defer ss.Team().RemoveMember(team.Id, u1.Id) + defer ss.Team().RemoveMember(team.Id, u2.Id) + + assert.Equal(t, tc.ExpectedRoles, member.Roles) + assert.Equal(t, tc.ExpectedExplicitRoles, member.ExplicitRoles) + assert.Equal(t, tc.ExpectedSchemeGuest, member.SchemeGuest) + assert.Equal(t, tc.ExpectedSchemeUser, member.SchemeUser) + assert.Equal(t, tc.ExpectedSchemeAdmin, member.SchemeAdmin) + }) + } + }) +} + +func testTeamUpdateMember(t *testing.T, ss store.Store) { + u1, err := ss.User().Save(&model.User{Username: model.NewId(), Email: MakeEmail()}) + require.Nil(t, err) + + t.Run("not valid team member", func(t *testing.T) { + member := &model.TeamMember{TeamId: "wrong", UserId: u1.Id} + _, err = ss.Team().UpdateMember(member) + require.NotNil(t, err) + require.Equal(t, "model.team_member.is_valid.team_id.app_error", err.Id) + }) + + t.Run("insert member correctly (in team without scheme)", func(t *testing.T) { + team := &model.Team{ + DisplayName: "Name", + Name: "zz" + model.NewId(), + Email: MakeEmail(), + Type: model.TEAM_OPEN, + } + + team, err = ss.Team().Save(team) + require.Nil(t, err) + + member := &model.TeamMember{TeamId: team.Id, UserId: u1.Id} + member, err = ss.Team().SaveMember(member, -1) + require.Nil(t, err) + + testCases := []struct { + Name string + SchemeGuest bool + SchemeUser bool + SchemeAdmin bool + ExplicitRoles string + ExpectedRoles string + ExpectedExplicitRoles string + ExpectedSchemeGuest bool + ExpectedSchemeUser bool + ExpectedSchemeAdmin bool + }{ + { + Name: "team user implicit", + SchemeUser: true, + ExpectedRoles: "team_user", + ExpectedSchemeUser: true, + }, + { + Name: "team user explicit", + ExplicitRoles: "team_user", + ExpectedRoles: "team_user", + ExpectedSchemeUser: true, + }, + { + Name: "team guest implicit", + SchemeGuest: true, + ExpectedRoles: "team_guest", + ExpectedSchemeGuest: true, + }, + { + Name: "team guest explicit", + ExplicitRoles: "team_guest", + ExpectedRoles: "team_guest", + ExpectedSchemeGuest: true, + }, + { + Name: "team admin implicit", + SchemeUser: true, + SchemeAdmin: true, + ExpectedRoles: "team_user team_admin", + ExpectedSchemeUser: true, + ExpectedSchemeAdmin: true, + }, + { + Name: "team admin explicit", + ExplicitRoles: "team_user team_admin", + ExpectedRoles: "team_user team_admin", + ExpectedSchemeUser: true, + ExpectedSchemeAdmin: true, + }, + { + Name: "team user implicit and explicit custom role", + SchemeUser: true, + ExplicitRoles: "test", + ExpectedRoles: "test team_user", + ExpectedExplicitRoles: "test", + ExpectedSchemeUser: true, + }, + { + Name: "team user explicit and explicit custom role", + ExplicitRoles: "team_user test", + ExpectedRoles: "test team_user", + ExpectedExplicitRoles: "test", + ExpectedSchemeUser: true, + }, + { + Name: "team guest implicit and explicit custom role", + SchemeGuest: true, + ExplicitRoles: "test", + ExpectedRoles: "test team_guest", + ExpectedExplicitRoles: "test", + ExpectedSchemeGuest: true, + }, + { + Name: "team guest explicit and explicit custom role", + ExplicitRoles: "team_guest test", + ExpectedRoles: "test team_guest", + ExpectedExplicitRoles: "test", + ExpectedSchemeGuest: true, + }, + { + Name: "team admin implicit and explicit custom role", + SchemeUser: true, + SchemeAdmin: true, + ExplicitRoles: "test", + ExpectedRoles: "test team_user team_admin", + ExpectedExplicitRoles: "test", + ExpectedSchemeUser: true, + ExpectedSchemeAdmin: true, + }, + { + Name: "team admin explicit and explicit custom role", + ExplicitRoles: "team_user team_admin test", + ExpectedRoles: "test team_user team_admin", + ExpectedExplicitRoles: "test", + ExpectedSchemeUser: true, + ExpectedSchemeAdmin: true, + }, + { + Name: "team member with only explicit custom roles", + ExplicitRoles: "test test2", + ExpectedRoles: "test test2", + ExpectedExplicitRoles: "test test2", + }, + } + + for _, tc := range testCases { + t.Run(tc.Name, func(t *testing.T) { + member.SchemeGuest = tc.SchemeGuest + member.SchemeUser = tc.SchemeUser + member.SchemeAdmin = tc.SchemeAdmin + member.ExplicitRoles = tc.ExplicitRoles + + member, err = ss.Team().UpdateMember(member) + require.Nil(t, err) + + assert.Equal(t, tc.ExpectedRoles, member.Roles) + assert.Equal(t, tc.ExpectedExplicitRoles, member.ExplicitRoles) + assert.Equal(t, tc.ExpectedSchemeGuest, member.SchemeGuest) + assert.Equal(t, tc.ExpectedSchemeUser, member.SchemeUser) + assert.Equal(t, tc.ExpectedSchemeAdmin, member.SchemeAdmin) + }) + } + }) + + t.Run("insert member correctly (in team with scheme)", func(t *testing.T) { + ts := &model.Scheme{ + Name: model.NewId(), + DisplayName: model.NewId(), + Description: model.NewId(), + Scope: model.SCHEME_SCOPE_TEAM, + } + ts, err = ss.Scheme().Save(ts) + require.Nil(t, err) + + team := &model.Team{ + DisplayName: "Name", + Name: "zz" + model.NewId(), + Email: MakeEmail(), + Type: model.TEAM_OPEN, + SchemeId: &ts.Id, + } + + team, err = ss.Team().Save(team) + require.Nil(t, err) + + member := &model.TeamMember{TeamId: team.Id, UserId: u1.Id} + member, err := ss.Team().SaveMember(member, -1) + require.Nil(t, err) + + testCases := []struct { + Name string + SchemeGuest bool + SchemeUser bool + SchemeAdmin bool + ExplicitRoles string + ExpectedRoles string + ExpectedExplicitRoles string + ExpectedSchemeGuest bool + ExpectedSchemeUser bool + ExpectedSchemeAdmin bool + }{ + { + Name: "team user implicit", + SchemeUser: true, + ExpectedRoles: ts.DefaultTeamUserRole, + ExpectedSchemeUser: true, + }, + { + Name: "team user explicit", + ExplicitRoles: "team_user", + ExpectedRoles: ts.DefaultTeamUserRole, + ExpectedSchemeUser: true, + }, + { + Name: "team guest implicit", + SchemeGuest: true, + ExpectedRoles: ts.DefaultTeamGuestRole, + ExpectedSchemeGuest: true, + }, + { + Name: "team guest explicit", + ExplicitRoles: "team_guest", + ExpectedRoles: ts.DefaultTeamGuestRole, + ExpectedSchemeGuest: true, + }, + { + Name: "team admin implicit", + SchemeUser: true, + SchemeAdmin: true, + ExpectedRoles: ts.DefaultTeamUserRole + " " + ts.DefaultTeamAdminRole, + ExpectedSchemeUser: true, + ExpectedSchemeAdmin: true, + }, + { + Name: "team admin explicit", + ExplicitRoles: "team_user team_admin", + ExpectedRoles: ts.DefaultTeamUserRole + " " + ts.DefaultTeamAdminRole, + ExpectedSchemeUser: true, + ExpectedSchemeAdmin: true, + }, + { + Name: "team user implicit and explicit custom role", + SchemeUser: true, + ExplicitRoles: "test", + ExpectedRoles: "test " + ts.DefaultTeamUserRole, + ExpectedExplicitRoles: "test", + ExpectedSchemeUser: true, + }, + { + Name: "team user explicit and explicit custom role", + ExplicitRoles: "team_user test", + ExpectedRoles: "test " + ts.DefaultTeamUserRole, + ExpectedExplicitRoles: "test", + ExpectedSchemeUser: true, + }, + { + Name: "team guest implicit and explicit custom role", + SchemeGuest: true, + ExplicitRoles: "test", + ExpectedRoles: "test " + ts.DefaultTeamGuestRole, + ExpectedExplicitRoles: "test", + ExpectedSchemeGuest: true, + }, + { + Name: "team guest explicit and explicit custom role", + ExplicitRoles: "team_guest test", + ExpectedRoles: "test " + ts.DefaultTeamGuestRole, + ExpectedExplicitRoles: "test", + ExpectedSchemeGuest: true, + }, + { + Name: "team admin implicit and explicit custom role", + SchemeUser: true, + SchemeAdmin: true, + ExplicitRoles: "test", + ExpectedRoles: "test " + ts.DefaultTeamUserRole + " " + ts.DefaultTeamAdminRole, + ExpectedExplicitRoles: "test", + ExpectedSchemeUser: true, + ExpectedSchemeAdmin: true, + }, + { + Name: "team admin explicit and explicit custom role", + ExplicitRoles: "team_user team_admin test", + ExpectedRoles: "test " + ts.DefaultTeamUserRole + " " + ts.DefaultTeamAdminRole, + ExpectedExplicitRoles: "test", + ExpectedSchemeUser: true, + ExpectedSchemeAdmin: true, + }, + { + Name: "team member with only explicit custom roles", + ExplicitRoles: "test test2", + ExpectedRoles: "test test2", + ExpectedExplicitRoles: "test test2", + }, + } + + for _, tc := range testCases { + t.Run(tc.Name, func(t *testing.T) { + member.SchemeGuest = tc.SchemeGuest + member.SchemeUser = tc.SchemeUser + member.SchemeAdmin = tc.SchemeAdmin + member.ExplicitRoles = tc.ExplicitRoles + + member, err = ss.Team().UpdateMember(member) + require.Nil(t, err) + + assert.Equal(t, tc.ExpectedRoles, member.Roles) + assert.Equal(t, tc.ExpectedExplicitRoles, member.ExplicitRoles) + assert.Equal(t, tc.ExpectedSchemeGuest, member.SchemeGuest) + assert.Equal(t, tc.ExpectedSchemeUser, member.SchemeUser) + assert.Equal(t, tc.ExpectedSchemeAdmin, member.SchemeAdmin) + }) + } + }) +} + +func testTeamUpdateMultipleMembers(t *testing.T, ss store.Store) { + u1, err := ss.User().Save(&model.User{Username: model.NewId(), Email: MakeEmail()}) + require.Nil(t, err) + u2, err := ss.User().Save(&model.User{Username: model.NewId(), Email: MakeEmail()}) + require.Nil(t, err) + + t.Run("any not valid team member", func(t *testing.T) { + m1 := &model.TeamMember{TeamId: "wrong", UserId: u1.Id} + m2 := &model.TeamMember{TeamId: model.NewId(), UserId: u2.Id} + _, err = ss.Team().UpdateMultipleMembers([]*model.TeamMember{m1, m2}) + require.NotNil(t, err) + require.Equal(t, "model.team_member.is_valid.team_id.app_error", err.Id) + }) + + t.Run("update members correctly (in team without scheme)", func(t *testing.T) { + team := &model.Team{ + DisplayName: "Name", + Name: "zz" + model.NewId(), + Email: MakeEmail(), + Type: model.TEAM_OPEN, + } + + team, err = ss.Team().Save(team) + require.Nil(t, err) + + member := &model.TeamMember{TeamId: team.Id, UserId: u1.Id} + otherMember := &model.TeamMember{TeamId: team.Id, UserId: u2.Id} + var members []*model.TeamMember + members, err = ss.Team().SaveMultipleMembers([]*model.TeamMember{member, otherMember}, -1) + require.Nil(t, err) + require.Len(t, members, 2) + member = members[0] + otherMember = members[1] + + testCases := []struct { + Name string + SchemeGuest bool + SchemeUser bool + SchemeAdmin bool + ExplicitRoles string + ExpectedRoles string + ExpectedExplicitRoles string + ExpectedSchemeGuest bool + ExpectedSchemeUser bool + ExpectedSchemeAdmin bool + }{ + { + Name: "team user implicit", + SchemeUser: true, + ExpectedRoles: "team_user", + ExpectedSchemeUser: true, + }, + { + Name: "team user explicit", + ExplicitRoles: "team_user", + ExpectedRoles: "team_user", + ExpectedSchemeUser: true, + }, + { + Name: "team guest implicit", + SchemeGuest: true, + ExpectedRoles: "team_guest", + ExpectedSchemeGuest: true, + }, + { + Name: "team guest explicit", + ExplicitRoles: "team_guest", + ExpectedRoles: "team_guest", + ExpectedSchemeGuest: true, + }, + { + Name: "team admin implicit", + SchemeUser: true, + SchemeAdmin: true, + ExpectedRoles: "team_user team_admin", + ExpectedSchemeUser: true, + ExpectedSchemeAdmin: true, + }, + { + Name: "team admin explicit", + ExplicitRoles: "team_user team_admin", + ExpectedRoles: "team_user team_admin", + ExpectedSchemeUser: true, + ExpectedSchemeAdmin: true, + }, + { + Name: "team user implicit and explicit custom role", + SchemeUser: true, + ExplicitRoles: "test", + ExpectedRoles: "test team_user", + ExpectedExplicitRoles: "test", + ExpectedSchemeUser: true, + }, + { + Name: "team user explicit and explicit custom role", + ExplicitRoles: "team_user test", + ExpectedRoles: "test team_user", + ExpectedExplicitRoles: "test", + ExpectedSchemeUser: true, + }, + { + Name: "team guest implicit and explicit custom role", + SchemeGuest: true, + ExplicitRoles: "test", + ExpectedRoles: "test team_guest", + ExpectedExplicitRoles: "test", + ExpectedSchemeGuest: true, + }, + { + Name: "team guest explicit and explicit custom role", + ExplicitRoles: "team_guest test", + ExpectedRoles: "test team_guest", + ExpectedExplicitRoles: "test", + ExpectedSchemeGuest: true, + }, + { + Name: "team admin implicit and explicit custom role", + SchemeUser: true, + SchemeAdmin: true, + ExplicitRoles: "test", + ExpectedRoles: "test team_user team_admin", + ExpectedExplicitRoles: "test", + ExpectedSchemeUser: true, + ExpectedSchemeAdmin: true, + }, + { + Name: "team admin explicit and explicit custom role", + ExplicitRoles: "team_user team_admin test", + ExpectedRoles: "test team_user team_admin", + ExpectedExplicitRoles: "test", + ExpectedSchemeUser: true, + ExpectedSchemeAdmin: true, + }, + { + Name: "team member with only explicit custom roles", + ExplicitRoles: "test test2", + ExpectedRoles: "test test2", + ExpectedExplicitRoles: "test test2", + }, + } + + for _, tc := range testCases { + t.Run(tc.Name, func(t *testing.T) { + member.SchemeGuest = tc.SchemeGuest + member.SchemeUser = tc.SchemeUser + member.SchemeAdmin = tc.SchemeAdmin + member.ExplicitRoles = tc.ExplicitRoles + + var members []*model.TeamMember + members, err = ss.Team().UpdateMultipleMembers([]*model.TeamMember{member, otherMember}) + require.Nil(t, err) + require.Len(t, members, 2) + member = members[0] + + assert.Equal(t, tc.ExpectedRoles, member.Roles) + assert.Equal(t, tc.ExpectedExplicitRoles, member.ExplicitRoles) + assert.Equal(t, tc.ExpectedSchemeGuest, member.SchemeGuest) + assert.Equal(t, tc.ExpectedSchemeUser, member.SchemeUser) + assert.Equal(t, tc.ExpectedSchemeAdmin, member.SchemeAdmin) + }) + } + }) + + t.Run("insert members correctly (in team with scheme)", func(t *testing.T) { + ts := &model.Scheme{ + Name: model.NewId(), + DisplayName: model.NewId(), + Description: model.NewId(), + Scope: model.SCHEME_SCOPE_TEAM, + } + ts, err = ss.Scheme().Save(ts) + require.Nil(t, err) + + team := &model.Team{ + DisplayName: "Name", + Name: "zz" + model.NewId(), + Email: MakeEmail(), + Type: model.TEAM_OPEN, + SchemeId: &ts.Id, + } + + team, err = ss.Team().Save(team) + require.Nil(t, err) + + member := &model.TeamMember{TeamId: team.Id, UserId: u1.Id} + otherMember := &model.TeamMember{TeamId: team.Id, UserId: u2.Id} + members, err := ss.Team().SaveMultipleMembers([]*model.TeamMember{member, otherMember}, -1) + require.Nil(t, err) + require.Len(t, members, 2) + member = members[0] + otherMember = members[1] + + testCases := []struct { + Name string + SchemeGuest bool + SchemeUser bool + SchemeAdmin bool + ExplicitRoles string + ExpectedRoles string + ExpectedExplicitRoles string + ExpectedSchemeGuest bool + ExpectedSchemeUser bool + ExpectedSchemeAdmin bool + }{ + { + Name: "team user implicit", + SchemeUser: true, + ExpectedRoles: ts.DefaultTeamUserRole, + ExpectedSchemeUser: true, + }, + { + Name: "team user explicit", + ExplicitRoles: "team_user", + ExpectedRoles: ts.DefaultTeamUserRole, + ExpectedSchemeUser: true, + }, + { + Name: "team guest implicit", + SchemeGuest: true, + ExpectedRoles: ts.DefaultTeamGuestRole, + ExpectedSchemeGuest: true, + }, + { + Name: "team guest explicit", + ExplicitRoles: "team_guest", + ExpectedRoles: ts.DefaultTeamGuestRole, + ExpectedSchemeGuest: true, + }, + { + Name: "team admin implicit", + SchemeUser: true, + SchemeAdmin: true, + ExpectedRoles: ts.DefaultTeamUserRole + " " + ts.DefaultTeamAdminRole, + ExpectedSchemeUser: true, + ExpectedSchemeAdmin: true, + }, + { + Name: "team admin explicit", + ExplicitRoles: "team_user team_admin", + ExpectedRoles: ts.DefaultTeamUserRole + " " + ts.DefaultTeamAdminRole, + ExpectedSchemeUser: true, + ExpectedSchemeAdmin: true, + }, + { + Name: "team user implicit and explicit custom role", + SchemeUser: true, + ExplicitRoles: "test", + ExpectedRoles: "test " + ts.DefaultTeamUserRole, + ExpectedExplicitRoles: "test", + ExpectedSchemeUser: true, + }, + { + Name: "team user explicit and explicit custom role", + ExplicitRoles: "team_user test", + ExpectedRoles: "test " + ts.DefaultTeamUserRole, + ExpectedExplicitRoles: "test", + ExpectedSchemeUser: true, + }, + { + Name: "team guest implicit and explicit custom role", + SchemeGuest: true, + ExplicitRoles: "test", + ExpectedRoles: "test " + ts.DefaultTeamGuestRole, + ExpectedExplicitRoles: "test", + ExpectedSchemeGuest: true, + }, + { + Name: "team guest explicit and explicit custom role", + ExplicitRoles: "team_guest test", + ExpectedRoles: "test " + ts.DefaultTeamGuestRole, + ExpectedExplicitRoles: "test", + ExpectedSchemeGuest: true, + }, + { + Name: "team admin implicit and explicit custom role", + SchemeUser: true, + SchemeAdmin: true, + ExplicitRoles: "test", + ExpectedRoles: "test " + ts.DefaultTeamUserRole + " " + ts.DefaultTeamAdminRole, + ExpectedExplicitRoles: "test", + ExpectedSchemeUser: true, + ExpectedSchemeAdmin: true, + }, + { + Name: "team admin explicit and explicit custom role", + ExplicitRoles: "team_user team_admin test", + ExpectedRoles: "test " + ts.DefaultTeamUserRole + " " + ts.DefaultTeamAdminRole, + ExpectedExplicitRoles: "test", + ExpectedSchemeUser: true, + ExpectedSchemeAdmin: true, + }, + { + Name: "team member with only explicit custom roles", + ExplicitRoles: "test test2", + ExpectedRoles: "test test2", + ExpectedExplicitRoles: "test test2", + }, + } + + for _, tc := range testCases { + t.Run(tc.Name, func(t *testing.T) { + member.SchemeGuest = tc.SchemeGuest + member.SchemeUser = tc.SchemeUser + member.SchemeAdmin = tc.SchemeAdmin + member.ExplicitRoles = tc.ExplicitRoles + + members, err := ss.Team().UpdateMultipleMembers([]*model.TeamMember{member, otherMember}) + require.Nil(t, err) + require.Len(t, members, 2) + member = members[0] + + assert.Equal(t, tc.ExpectedRoles, member.Roles) + assert.Equal(t, tc.ExpectedExplicitRoles, member.ExplicitRoles) + assert.Equal(t, tc.ExpectedSchemeGuest, member.SchemeGuest) + assert.Equal(t, tc.ExpectedSchemeUser, member.SchemeUser) + assert.Equal(t, tc.ExpectedSchemeAdmin, member.SchemeAdmin) + }) + } + }) +} + +func testTeamRemoveMember(t *testing.T, ss store.Store) { + u1, err := ss.User().Save(&model.User{Username: model.NewId(), Email: MakeEmail()}) + require.Nil(t, err) + u2, err := ss.User().Save(&model.User{Username: model.NewId(), Email: MakeEmail()}) + require.Nil(t, err) + u3, err := ss.User().Save(&model.User{Username: model.NewId(), Email: MakeEmail()}) + require.Nil(t, err) + u4, err := ss.User().Save(&model.User{Username: model.NewId(), Email: MakeEmail()}) + require.Nil(t, err) + teamID := model.NewId() + m1 := &model.TeamMember{TeamId: teamID, UserId: u1.Id} + m2 := &model.TeamMember{TeamId: teamID, UserId: u2.Id} + m3 := &model.TeamMember{TeamId: teamID, UserId: u3.Id} + m4 := &model.TeamMember{TeamId: teamID, UserId: u4.Id} + _, err = ss.Team().SaveMultipleMembers([]*model.TeamMember{m1, m2, m3, m4}, -1) + require.Nil(t, err) + + t.Run("remove member from not existing team", func(t *testing.T) { + err = ss.Team().RemoveMember("not-existing-team", u1.Id) + require.Nil(t, err) + var membersOtherTeam []*model.TeamMember + membersOtherTeam, err = ss.Team().GetMembers(teamID, 0, 100, nil) + require.Nil(t, err) + require.Len(t, membersOtherTeam, 4) + }) + + t.Run("remove not existing member from an existing team", func(t *testing.T) { + err = ss.Team().RemoveMember(teamID, model.NewId()) + require.Nil(t, err) + var membersOtherTeam []*model.TeamMember + membersOtherTeam, err = ss.Team().GetMembers(teamID, 0, 100, nil) + require.Nil(t, err) + require.Len(t, membersOtherTeam, 4) + }) + + t.Run("remove existing member from an existing team", func(t *testing.T) { + err = ss.Team().RemoveMember(teamID, u1.Id) + require.Nil(t, err) + defer ss.Team().SaveMember(m1, -1) + var membersOtherTeam []*model.TeamMember + membersOtherTeam, err = ss.Team().GetMembers(teamID, 0, 100, nil) + require.Nil(t, err) + require.Len(t, membersOtherTeam, 3) + }) +} + +func testTeamRemoveMembers(t *testing.T, ss store.Store) { + u1, err := ss.User().Save(&model.User{Username: model.NewId(), Email: MakeEmail()}) + require.Nil(t, err) + u2, err := ss.User().Save(&model.User{Username: model.NewId(), Email: MakeEmail()}) + require.Nil(t, err) + u3, err := ss.User().Save(&model.User{Username: model.NewId(), Email: MakeEmail()}) + require.Nil(t, err) + u4, err := ss.User().Save(&model.User{Username: model.NewId(), Email: MakeEmail()}) + require.Nil(t, err) + teamID := model.NewId() + m1 := &model.TeamMember{TeamId: teamID, UserId: u1.Id} + m2 := &model.TeamMember{TeamId: teamID, UserId: u2.Id} + m3 := &model.TeamMember{TeamId: teamID, UserId: u3.Id} + m4 := &model.TeamMember{TeamId: teamID, UserId: u4.Id} + _, err = ss.Team().SaveMultipleMembers([]*model.TeamMember{m1, m2, m3, m4}, -1) + require.Nil(t, err) + + t.Run("remove members from not existing team", func(t *testing.T) { + err = ss.Team().RemoveMembers("not-existing-team", []string{u1.Id, u2.Id, u3.Id, u4.Id}) + require.Nil(t, err) + var membersOtherTeam []*model.TeamMember + membersOtherTeam, err = ss.Team().GetMembers(teamID, 0, 100, nil) + require.Nil(t, err) + require.Len(t, membersOtherTeam, 4) + }) + + t.Run("remove not existing members from an existing team", func(t *testing.T) { + err = ss.Team().RemoveMembers(teamID, []string{model.NewId(), model.NewId()}) + require.Nil(t, err) + var membersOtherTeam []*model.TeamMember + membersOtherTeam, err = ss.Team().GetMembers(teamID, 0, 100, nil) + require.Nil(t, err) + require.Len(t, membersOtherTeam, 4) + }) + + t.Run("remove not existing and not existing members from an existing team", func(t *testing.T) { + err = ss.Team().RemoveMembers(teamID, []string{u1.Id, u2.Id, model.NewId(), model.NewId()}) + require.Nil(t, err) + defer ss.Team().SaveMultipleMembers([]*model.TeamMember{m1, m2}, -1) + var membersOtherTeam []*model.TeamMember + membersOtherTeam, err = ss.Team().GetMembers(teamID, 0, 100, nil) + require.Nil(t, err) + require.Len(t, membersOtherTeam, 2) + }) + t.Run("remove existing members from an existing team", func(t *testing.T) { + err = ss.Team().RemoveMembers(teamID, []string{u1.Id, u2.Id, u3.Id}) + require.Nil(t, err) + defer ss.Team().SaveMultipleMembers([]*model.TeamMember{m1, m2, m3}, -1) + var membersOtherTeam []*model.TeamMember + membersOtherTeam, err = ss.Team().GetMembers(teamID, 0, 100, nil) + require.Nil(t, err) + require.Len(t, membersOtherTeam, 1) + }) +} + func testTeamMembersWithPagination(t *testing.T, ss store.Store) { teamId1 := model.NewId() teamId2 := model.NewId() @@ -978,12 +2408,7 @@ func testTeamMembersWithPagination(t *testing.T, ss store.Store) { m2 := &model.TeamMember{TeamId: teamId1, UserId: model.NewId()} m3 := &model.TeamMember{TeamId: teamId2, UserId: model.NewId()} - _, err := ss.Team().SaveMember(m1, -1) - require.Nil(t, err) - - _, err = ss.Team().SaveMember(m2, -1) - require.Nil(t, err) - _, err = ss.Team().SaveMember(m3, -1) + _, err := ss.Team().SaveMultipleMembers([]*model.TeamMember{m1, m2, m3}, -1) require.Nil(t, err) ms, errTeam := ss.Team().GetTeamsForUserWithPagination(m1.UserId, 0, 1) @@ -1010,9 +2435,7 @@ func testTeamMembersWithPagination(t *testing.T, ss store.Store) { uid := model.NewId() m4 := &model.TeamMember{TeamId: teamId1, UserId: uid} m5 := &model.TeamMember{TeamId: teamId2, UserId: uid} - _, err = ss.Team().SaveMember(m4, -1) - require.Nil(t, err) - _, err = ss.Team().SaveMember(m5, -1) + _, err = ss.Team().SaveMultipleMembers([]*model.TeamMember{m4, m5}, -1) require.Nil(t, err) result, err := ss.Team().GetTeamsForUserWithPagination(uid, 0, 1) @@ -1477,12 +2900,12 @@ func testTeamStoreMigrateTeamMembers(t *testing.T, ss store.Store) { ExplicitRoles: "something_else", } - tm1, err = ss.Team().SaveMember(tm1, -1) - require.Nil(t, err) - tm2, err = ss.Team().SaveMember(tm2, -1) - require.Nil(t, err) - tm3, err = ss.Team().SaveMember(tm3, -1) + memberships, err := ss.Team().SaveMultipleMembers([]*model.TeamMember{tm1, tm2, tm3}, -1) require.Nil(t, err) + require.Len(t, memberships, 3) + tm1 = memberships[0] + tm2 = memberships[1] + tm3 = memberships[2] lastDoneTeamId := strings.Repeat("0", 26) lastDoneUserId := strings.Repeat("0", 26) @@ -1568,7 +2991,7 @@ func testTeamStoreClearAllCustomRoleAssignments(t *testing.T, ss store.Store) { m1 := &model.TeamMember{ TeamId: model.NewId(), UserId: model.NewId(), - ExplicitRoles: "team_user team_admin team_post_all_public", + ExplicitRoles: "team_post_all_public team_user team_admin", } m2 := &model.TeamMember{ TeamId: model.NewId(), @@ -1586,13 +3009,7 @@ func testTeamStoreClearAllCustomRoleAssignments(t *testing.T, ss store.Store) { ExplicitRoles: "custom_only", } - _, err := ss.Team().SaveMember(m1, -1) - require.Nil(t, err) - _, err = ss.Team().SaveMember(m2, -1) - require.Nil(t, err) - _, err = ss.Team().SaveMember(m3, -1) - require.Nil(t, err) - _, err = ss.Team().SaveMember(m4, -1) + _, err := ss.Team().SaveMultipleMembers([]*model.TeamMember{m1, m2, m3, m4}, -1) require.Nil(t, err) require.Nil(t, (ss.Team().ClearAllCustomRoleAssignments())) @@ -1731,11 +3148,8 @@ func testTeamStoreGetTeamMembersForExport(t *testing.T, ss store.Store) { require.Nil(t, err) m1 := &model.TeamMember{TeamId: t1.Id, UserId: u1.Id} - _, err = ss.Team().SaveMember(m1, -1) - require.Nil(t, err) - m2 := &model.TeamMember{TeamId: t1.Id, UserId: u2.Id} - _, err = ss.Team().SaveMember(m2, -1) + _, err = ss.Team().SaveMultipleMembers([]*model.TeamMember{m1, m2}, -1) require.Nil(t, err) d1, err := ss.Team().GetTeamMembersForExport(u1.Id) diff --git a/store/timer_layer.go b/store/timer_layer.go index ac0abc0928..e026b8920c 100644 --- a/store/timer_layer.go +++ b/store/timer_layer.go @@ -1569,6 +1569,22 @@ func (s *TimerLayerChannelStore) SaveMember(member *model.ChannelMember) (*model return resultVar0, resultVar1 } +func (s *TimerLayerChannelStore) SaveMultipleMembers(members []*model.ChannelMember) ([]*model.ChannelMember, *model.AppError) { + start := timemodule.Now() + + resultVar0, resultVar1 := s.ChannelStore.SaveMultipleMembers(members) + + elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second) + if s.Root.Metrics != nil { + success := "false" + if resultVar1 == nil { + success = "true" + } + s.Root.Metrics.ObserveStoreMethodDuration("ChannelStore.SaveMultipleMembers", success, elapsed) + } + return resultVar0, resultVar1 +} + func (s *TimerLayerChannelStore) SearchAllChannels(term string, opts ChannelSearchOpts) (*model.ChannelListWithTeamData, int64, *model.AppError) { start := timemodule.Now() @@ -1761,6 +1777,22 @@ func (s *TimerLayerChannelStore) UpdateMembersRole(channelID string, userIDs []s return resultVar0 } +func (s *TimerLayerChannelStore) UpdateMultipleMembers(members []*model.ChannelMember) ([]*model.ChannelMember, *model.AppError) { + start := timemodule.Now() + + resultVar0, resultVar1 := s.ChannelStore.UpdateMultipleMembers(members) + + elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second) + if s.Root.Metrics != nil { + success := "false" + if resultVar1 == nil { + success = "true" + } + s.Root.Metrics.ObserveStoreMethodDuration("ChannelStore.UpdateMultipleMembers", success, elapsed) + } + return resultVar0, resultVar1 +} + func (s *TimerLayerChannelStore) UserBelongsToChannels(userId string, channelIds []string) (bool, *model.AppError) { start := timemodule.Now() @@ -4348,6 +4380,22 @@ func (s *TimerLayerPostStore) Overwrite(post *model.Post) (*model.Post, *model.A return resultVar0, resultVar1 } +func (s *TimerLayerPostStore) OverwriteMultiple(posts []*model.Post) ([]*model.Post, *model.AppError) { + start := timemodule.Now() + + resultVar0, resultVar1 := s.PostStore.OverwriteMultiple(posts) + + elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second) + if s.Root.Metrics != nil { + success := "false" + if resultVar1 == nil { + success = "true" + } + s.Root.Metrics.ObserveStoreMethodDuration("PostStore.OverwriteMultiple", success, elapsed) + } + return resultVar0, resultVar1 +} + func (s *TimerLayerPostStore) PermanentDeleteBatch(endTime int64, limit int64) (int64, *model.AppError) { start := timemodule.Now() @@ -5658,6 +5706,22 @@ func (s *TimerLayerTeamStore) GetByName(name string) (*model.Team, *model.AppErr return resultVar0, resultVar1 } +func (s *TimerLayerTeamStore) GetByNames(name []string) ([]*model.Team, *model.AppError) { + start := timemodule.Now() + + resultVar0, resultVar1 := s.TeamStore.GetByNames(name) + + elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second) + if s.Root.Metrics != nil { + success := "false" + if resultVar1 == nil { + success = "true" + } + s.Root.Metrics.ObserveStoreMethodDuration("TeamStore.GetByNames", success, elapsed) + } + return resultVar0, resultVar1 +} + func (s *TimerLayerTeamStore) GetChannelUnreadsForAllTeams(excludeTeamId string, userId string) ([]*model.ChannelUnread, *model.AppError) { start := timemodule.Now() @@ -6009,6 +6073,22 @@ func (s *TimerLayerTeamStore) SaveMember(member *model.TeamMember, maxUsersPerTe return resultVar0, resultVar1 } +func (s *TimerLayerTeamStore) SaveMultipleMembers(members []*model.TeamMember, maxUsersPerTeam int) ([]*model.TeamMember, *model.AppError) { + start := timemodule.Now() + + resultVar0, resultVar1 := s.TeamStore.SaveMultipleMembers(members, maxUsersPerTeam) + + elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second) + if s.Root.Metrics != nil { + success := "false" + if resultVar1 == nil { + success = "true" + } + s.Root.Metrics.ObserveStoreMethodDuration("TeamStore.SaveMultipleMembers", success, elapsed) + } + return resultVar0, resultVar1 +} + func (s *TimerLayerTeamStore) SearchAll(term string) ([]*model.Team, *model.AppError) { start := timemodule.Now() @@ -6137,6 +6217,22 @@ func (s *TimerLayerTeamStore) UpdateMembersRole(teamID string, userIDs []string) return resultVar0 } +func (s *TimerLayerTeamStore) UpdateMultipleMembers(members []*model.TeamMember) ([]*model.TeamMember, *model.AppError) { + start := timemodule.Now() + + resultVar0, resultVar1 := s.TeamStore.UpdateMultipleMembers(members) + + elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second) + if s.Root.Metrics != nil { + success := "false" + if resultVar1 == nil { + success = "true" + } + s.Root.Metrics.ObserveStoreMethodDuration("TeamStore.UpdateMultipleMembers", success, elapsed) + } + return resultVar0, resultVar1 +} + func (s *TimerLayerTeamStore) UserBelongsToTeams(userId string, teamIds []string) (bool, *model.AppError) { start := timemodule.Now()