From 1f168263a2ff73ddee1193cccdeea533f6d501fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Espino?= Date: Wed, 1 Aug 2018 15:45:26 +0200 Subject: Split app/import.go in multiple files (#9179) --- app/import.go | 2023 --------------------- app/import_functions.go | 1218 +++++++++++++ app/import_functions_test.go | 2501 ++++++++++++++++++++++++++ app/import_test.go | 3857 +---------------------------------------- app/import_types.go | 186 ++ app/import_validators.go | 529 ++++++ app/import_validators_test.go | 1372 +++++++++++++++ app/slackimport.go | 118 ++ 8 files changed, 5930 insertions(+), 5874 deletions(-) create mode 100644 app/import_functions.go create mode 100644 app/import_functions_test.go create mode 100644 app/import_types.go create mode 100644 app/import_validators.go create mode 100644 app/import_validators_test.go (limited to 'app') diff --git a/app/import.go b/app/import.go index 50973d504..078198dd4 100644 --- a/app/import.go +++ b/app/import.go @@ -5,210 +5,15 @@ package app import ( "bufio" - "bytes" "encoding/json" - "fmt" "io" "net/http" - "os" - "regexp" "strings" "sync" - "time" - "unicode/utf8" - "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" - "github.com/mattermost/mattermost-server/store" - "github.com/mattermost/mattermost-server/utils" ) -// Import Data Models - -type LineImportData struct { - Type string `json:"type"` - Scheme *SchemeImportData `json:"scheme"` - Team *TeamImportData `json:"team"` - Channel *ChannelImportData `json:"channel"` - User *UserImportData `json:"user"` - Post *PostImportData `json:"post"` - DirectChannel *DirectChannelImportData `json:"direct_channel"` - DirectPost *DirectPostImportData `json:"direct_post"` - Emoji *EmojiImportData `json:"emoji"` - Version *int `json:"version"` -} - -type TeamImportData struct { - Name *string `json:"name"` - DisplayName *string `json:"display_name"` - Type *string `json:"type"` - Description *string `json:"description"` - AllowOpenInvite *bool `json:"allow_open_invite"` - Scheme *string `json:"scheme"` -} - -type ChannelImportData struct { - Team *string `json:"team"` - Name *string `json:"name"` - DisplayName *string `json:"display_name"` - Type *string `json:"type"` - Header *string `json:"header"` - Purpose *string `json:"purpose"` - Scheme *string `json:"scheme"` -} - -type UserImportData struct { - ProfileImage *string `json:"profile_image"` - Username *string `json:"username"` - Email *string `json:"email"` - AuthService *string `json:"auth_service"` - AuthData *string `json:"auth_data"` - Password *string `json:"password"` - Nickname *string `json:"nickname"` - FirstName *string `json:"first_name"` - LastName *string `json:"last_name"` - Position *string `json:"position"` - Roles *string `json:"roles"` - Locale *string `json:"locale"` - - Teams *[]UserTeamImportData `json:"teams"` - - Theme *string `json:"theme"` - UseMilitaryTime *string `json:"military_time"` - CollapsePreviews *string `json:"link_previews"` - MessageDisplay *string `json:"message_display"` - ChannelDisplayMode *string `json:"channel_display_mode"` - TutorialStep *string `json:"tutorial_step"` - - NotifyProps *UserNotifyPropsImportData `json:"notify_props"` -} - -type UserNotifyPropsImportData struct { - Desktop *string `json:"desktop"` - DesktopSound *string `json:"desktop_sound"` - - Email *string `json:"email"` - - Mobile *string `json:"mobile"` - MobilePushStatus *string `json:"mobile_push_status"` - - ChannelTrigger *string `json:"channel"` - CommentsTrigger *string `json:"comments"` - MentionKeys *string `json:"mention_keys"` -} - -type UserTeamImportData struct { - Name *string `json:"name"` - Roles *string `json:"roles"` - Channels *[]UserChannelImportData `json:"channels"` -} - -type UserChannelImportData struct { - Name *string `json:"name"` - Roles *string `json:"roles"` - NotifyProps *UserChannelNotifyPropsImportData `json:"notify_props"` - Favorite *bool `json:"favorite"` -} - -type UserChannelNotifyPropsImportData struct { - Desktop *string `json:"desktop"` - Mobile *string `json:"mobile"` - MarkUnread *string `json:"mark_unread"` -} - -type EmojiImportData struct { - Name *string `json:"name"` - Image *string `json:"image"` -} - -type ReactionImportData struct { - User *string `json:"user"` - CreateAt *int64 `json:"create_at"` - EmojiName *string `json:"emoji_name"` -} - -type ReplyImportData struct { - User *string `json:"user"` - - Message *string `json:"message"` - CreateAt *int64 `json:"create_at"` - - FlaggedBy *[]string `json:"flagged_by"` - Reactions *[]ReactionImportData `json:"reactions"` - Attachments *[]AttachmentImportData `json:"attachments"` -} - -type PostImportData struct { - Team *string `json:"team"` - Channel *string `json:"channel"` - User *string `json:"user"` - - Message *string `json:"message"` - CreateAt *int64 `json:"create_at"` - - FlaggedBy *[]string `json:"flagged_by"` - Reactions *[]ReactionImportData `json:"reactions"` - Replies *[]ReplyImportData `json:"replies"` - Attachments *[]AttachmentImportData `json:"attachments"` -} - -type DirectChannelImportData struct { - Members *[]string `json:"members"` - FavoritedBy *[]string `json:"favorited_by"` - - Header *string `json:"header"` -} - -type DirectPostImportData struct { - ChannelMembers *[]string `json:"channel_members"` - User *string `json:"user"` - - Message *string `json:"message"` - CreateAt *int64 `json:"create_at"` - - FlaggedBy *[]string `json:"flagged_by"` - Reactions *[]ReactionImportData `json:"reactions"` - Replies *[]ReplyImportData `json:"replies"` -} - -type SchemeImportData struct { - Name *string `json:"name"` - DisplayName *string `json:"display_name"` - Description *string `json:"description"` - Scope *string `json:"scope"` - DefaultTeamAdminRole *RoleImportData `json:"default_team_admin_role"` - DefaultTeamUserRole *RoleImportData `json:"default_team_user_role"` - DefaultChannelAdminRole *RoleImportData `json:"default_channel_admin_role"` - DefaultChannelUserRole *RoleImportData `json:"default_channel_user_role"` -} - -type RoleImportData struct { - Name *string `json:"name"` - DisplayName *string `json:"display_name"` - Description *string `json:"description"` - Permissions *[]string `json:"permissions"` -} - -type LineImportWorkerData struct { - LineImportData - LineNumber int -} - -type LineImportWorkerError struct { - Error *model.AppError - LineNumber int -} - -type AttachmentImportData struct { - Path *string `json:"path"` -} - -// -// -- Bulk Import Functions -- -// These functions import data directly into the database. Security and permission checks are bypassed but validity is -// still enforced. -// - func (a *App) bulkImportWorker(dryRun bool, wg *sync.WaitGroup, lines <-chan LineImportWorkerData, errors chan<- LineImportWorkerError) { for line := range lines { if err := a.ImportLine(line.LineImportData, dryRun); err != nil { @@ -360,1831 +165,3 @@ func (a *App) ImportLine(line LineImportData, dryRun bool) *model.AppError { return model.NewAppError("BulkImport", "app.import.import_line.unknown_line_type.error", map[string]interface{}{"Type": line.Type}, "", http.StatusBadRequest) } } - -func (a *App) ImportScheme(data *SchemeImportData, dryRun bool) *model.AppError { - if err := validateSchemeImportData(data); err != nil { - return err - } - - // If this is a Dry Run, do not continue any further. - if dryRun { - return nil - } - - scheme, err := a.GetSchemeByName(*data.Name) - if err != nil { - scheme = new(model.Scheme) - } else if scheme.Scope != *data.Scope { - return model.NewAppError("BulkImport", "app.import.import_scheme.scope_change.error", map[string]interface{}{"SchemeName": scheme.Name}, "", http.StatusBadRequest) - } - - scheme.Name = *data.Name - scheme.DisplayName = *data.DisplayName - scheme.Scope = *data.Scope - - if data.Description != nil { - scheme.Description = *data.Description - } - - if len(scheme.Id) == 0 { - scheme, err = a.CreateScheme(scheme) - } else { - scheme, err = a.UpdateScheme(scheme) - } - - if err != nil { - return err - } - - if scheme.Scope == model.SCHEME_SCOPE_TEAM { - data.DefaultTeamAdminRole.Name = &scheme.DefaultTeamAdminRole - if err := a.ImportRole(data.DefaultTeamAdminRole, dryRun, true); err != nil { - return err - } - - data.DefaultTeamUserRole.Name = &scheme.DefaultTeamUserRole - if err := a.ImportRole(data.DefaultTeamUserRole, dryRun, true); err != nil { - return err - } - } - - if scheme.Scope == model.SCHEME_SCOPE_TEAM || scheme.Scope == model.SCHEME_SCOPE_CHANNEL { - data.DefaultChannelAdminRole.Name = &scheme.DefaultChannelAdminRole - if err := a.ImportRole(data.DefaultChannelAdminRole, dryRun, true); err != nil { - return err - } - - data.DefaultChannelUserRole.Name = &scheme.DefaultChannelUserRole - if err := a.ImportRole(data.DefaultChannelUserRole, dryRun, true); err != nil { - return err - } - } - - return nil -} - -func (a *App) ImportRole(data *RoleImportData, dryRun bool, isSchemeRole bool) *model.AppError { - if !isSchemeRole { - if err := validateRoleImportData(data); err != nil { - return err - } - } - - // If this is a Dry Run, do not continue any further. - if dryRun { - return nil - } - - role, err := a.GetRoleByName(*data.Name) - if err != nil { - role = new(model.Role) - } - - role.Name = *data.Name - - if data.DisplayName != nil { - role.DisplayName = *data.DisplayName - } - - if data.Description != nil { - role.Description = *data.Description - } - - if data.Permissions != nil { - role.Permissions = *data.Permissions - } - - if isSchemeRole { - role.SchemeManaged = true - } else { - role.SchemeManaged = false - } - - if len(role.Id) == 0 { - role, err = a.CreateRole(role) - } else { - role, err = a.UpdateRole(role) - } - - return err -} - -func validateSchemeImportData(data *SchemeImportData) *model.AppError { - - if data.Scope == nil { - return model.NewAppError("BulkImport", "app.import.validate_scheme_import_data.null_scope.error", nil, "", http.StatusBadRequest) - } - - switch *data.Scope { - case model.SCHEME_SCOPE_TEAM: - if data.DefaultTeamAdminRole == nil || data.DefaultTeamUserRole == nil || data.DefaultChannelAdminRole == nil || data.DefaultChannelUserRole == nil { - return model.NewAppError("BulkImport", "app.import.validate_scheme_import_data.wrong_roles_for_scope.error", nil, "", http.StatusBadRequest) - } - case model.SCHEME_SCOPE_CHANNEL: - if data.DefaultTeamAdminRole != nil || data.DefaultTeamUserRole != nil || data.DefaultChannelAdminRole == nil || data.DefaultChannelUserRole == nil { - return model.NewAppError("BulkImport", "app.import.validate_scheme_import_data.wrong_roles_for_scope.error", nil, "", http.StatusBadRequest) - } - default: - return model.NewAppError("BulkImport", "app.import.validate_scheme_import_data.unknown_scheme.error", nil, "", http.StatusBadRequest) - } - - if data.Name == nil || !model.IsValidSchemeName(*data.Name) { - return model.NewAppError("BulkImport", "app.import.validate_scheme_import_data.name_invalid.error", nil, "", http.StatusBadRequest) - } - - if data.DisplayName == nil || len(*data.DisplayName) == 0 || len(*data.DisplayName) > model.SCHEME_DISPLAY_NAME_MAX_LENGTH { - return model.NewAppError("BulkImport", "app.import.validate_scheme_import_data.display_name_invalid.error", nil, "", http.StatusBadRequest) - } - - if data.Description != nil && len(*data.Description) > model.SCHEME_DESCRIPTION_MAX_LENGTH { - return model.NewAppError("BulkImport", "app.import.validate_scheme_import_data.description_invalid.error", nil, "", http.StatusBadRequest) - } - - if data.DefaultTeamAdminRole != nil { - if err := validateRoleImportData(data.DefaultTeamAdminRole); err != nil { - return err - } - } - - if data.DefaultTeamUserRole != nil { - if err := validateRoleImportData(data.DefaultTeamUserRole); err != nil { - return err - } - } - - if data.DefaultChannelAdminRole != nil { - if err := validateRoleImportData(data.DefaultChannelAdminRole); err != nil { - return err - } - } - - if data.DefaultChannelUserRole != nil { - if err := validateRoleImportData(data.DefaultChannelUserRole); err != nil { - return err - } - } - - return nil -} - -func validateRoleImportData(data *RoleImportData) *model.AppError { - - if data.Name == nil || !model.IsValidRoleName(*data.Name) { - return model.NewAppError("BulkImport", "app.import.validate_role_import_data.name_invalid.error", nil, "", http.StatusBadRequest) - } - - if data.DisplayName == nil || len(*data.DisplayName) == 0 || len(*data.DisplayName) > model.ROLE_DISPLAY_NAME_MAX_LENGTH { - return model.NewAppError("BulkImport", "app.import.validate_role_import_data.display_name_invalid.error", nil, "", http.StatusBadRequest) - } - - if data.Description != nil && len(*data.Description) > model.ROLE_DESCRIPTION_MAX_LENGTH { - return model.NewAppError("BulkImport", "app.import.validate_role_import_data.description_invalid.error", nil, "", http.StatusBadRequest) - } - - if data.Permissions != nil { - for _, permission := range *data.Permissions { - permissionValidated := false - for _, p := range model.ALL_PERMISSIONS { - if permission == p.Id { - permissionValidated = true - break - } - } - - if !permissionValidated { - return model.NewAppError("BulkImport", "app.import.validate_role_import_data.invalid_permission.error", nil, "permission"+permission, http.StatusBadRequest) - } - } - } - - return nil -} - -func (a *App) ImportTeam(data *TeamImportData, dryRun bool) *model.AppError { - if err := validateTeamImportData(data); err != nil { - return err - } - - // If this is a Dry Run, do not continue any further. - if dryRun { - return nil - } - - var team *model.Team - if result := <-a.Srv.Store.Team().GetByName(*data.Name); result.Err == nil { - team = result.Data.(*model.Team) - } else { - team = &model.Team{} - } - - team.Name = *data.Name - team.DisplayName = *data.DisplayName - team.Type = *data.Type - - if data.Description != nil { - team.Description = *data.Description - } - - if data.AllowOpenInvite != nil { - team.AllowOpenInvite = *data.AllowOpenInvite - } - - if data.Scheme != nil { - scheme, err := a.GetSchemeByName(*data.Scheme) - if err != nil { - return err - } - - if scheme.DeleteAt != 0 { - return model.NewAppError("BulkImport", "app.import.import_team.scheme_deleted.error", nil, "", http.StatusBadRequest) - } - - if scheme.Scope != model.SCHEME_SCOPE_TEAM { - return model.NewAppError("BulkImport", "app.import.import_team.scheme_wrong_scope.error", nil, "", http.StatusBadRequest) - } - - team.SchemeId = &scheme.Id - } - - if team.Id == "" { - if _, err := a.CreateTeam(team); err != nil { - return err - } - } else { - if _, err := a.updateTeamUnsanitized(team); err != nil { - return err - } - } - - return nil -} - -func validateTeamImportData(data *TeamImportData) *model.AppError { - - if data.Name == nil { - return model.NewAppError("BulkImport", "app.import.validate_team_import_data.name_missing.error", nil, "", http.StatusBadRequest) - } else if len(*data.Name) > model.TEAM_NAME_MAX_LENGTH { - return model.NewAppError("BulkImport", "app.import.validate_team_import_data.name_length.error", nil, "", http.StatusBadRequest) - } else if model.IsReservedTeamName(*data.Name) { - return model.NewAppError("BulkImport", "app.import.validate_team_import_data.name_reserved.error", nil, "", http.StatusBadRequest) - } else if !model.IsValidTeamName(*data.Name) { - return model.NewAppError("BulkImport", "app.import.validate_team_import_data.name_characters.error", nil, "", http.StatusBadRequest) - } - - if data.DisplayName == nil { - return model.NewAppError("BulkImport", "app.import.validate_team_import_data.display_name_missing.error", nil, "", http.StatusBadRequest) - } else if utf8.RuneCountInString(*data.DisplayName) == 0 || utf8.RuneCountInString(*data.DisplayName) > model.TEAM_DISPLAY_NAME_MAX_RUNES { - return model.NewAppError("BulkImport", "app.import.validate_team_import_data.display_name_length.error", nil, "", http.StatusBadRequest) - } - - if data.Type == nil { - return model.NewAppError("BulkImport", "app.import.validate_team_import_data.type_missing.error", nil, "", http.StatusBadRequest) - } else if *data.Type != model.TEAM_OPEN && *data.Type != model.TEAM_INVITE { - return model.NewAppError("BulkImport", "app.import.validate_team_import_data.type_invalid.error", nil, "", http.StatusBadRequest) - } - - if data.Description != nil && len(*data.Description) > model.TEAM_DESCRIPTION_MAX_LENGTH { - return model.NewAppError("BulkImport", "app.import.validate_team_import_data.description_length.error", nil, "", http.StatusBadRequest) - } - - if data.Scheme != nil && !model.IsValidSchemeName(*data.Scheme) { - return model.NewAppError("BulkImport", "app.import.validate_team_import_data.scheme_invalid.error", nil, "", http.StatusBadRequest) - } - - return nil -} - -func (a *App) ImportChannel(data *ChannelImportData, dryRun bool) *model.AppError { - if err := validateChannelImportData(data); err != nil { - return err - } - - // If this is a Dry Run, do not continue any further. - if dryRun { - return nil - } - - var team *model.Team - if result := <-a.Srv.Store.Team().GetByName(*data.Team); result.Err != nil { - return model.NewAppError("BulkImport", "app.import.import_channel.team_not_found.error", map[string]interface{}{"TeamName": *data.Team}, result.Err.Error(), http.StatusBadRequest) - } else { - team = result.Data.(*model.Team) - } - - var channel *model.Channel - if result := <-a.Srv.Store.Channel().GetByNameIncludeDeleted(team.Id, *data.Name, true); result.Err == nil { - channel = result.Data.(*model.Channel) - } else { - channel = &model.Channel{} - } - - channel.TeamId = team.Id - channel.Name = *data.Name - channel.DisplayName = *data.DisplayName - channel.Type = *data.Type - - if data.Header != nil { - channel.Header = *data.Header - } - - if data.Purpose != nil { - channel.Purpose = *data.Purpose - } - - if data.Scheme != nil { - scheme, err := a.GetSchemeByName(*data.Scheme) - if err != nil { - return err - } - - if scheme.DeleteAt != 0 { - return model.NewAppError("BulkImport", "app.import.import_channel.scheme_deleted.error", nil, "", http.StatusBadRequest) - } - - if scheme.Scope != model.SCHEME_SCOPE_CHANNEL { - return model.NewAppError("BulkImport", "app.import.import_channel.scheme_wrong_scope.error", nil, "", http.StatusBadRequest) - } - - channel.SchemeId = &scheme.Id - } - - if channel.Id == "" { - if _, err := a.CreateChannel(channel, false); err != nil { - return err - } - } else { - if _, err := a.UpdateChannel(channel); err != nil { - return err - } - } - - return nil -} - -func validateChannelImportData(data *ChannelImportData) *model.AppError { - - if data.Team == nil { - return model.NewAppError("BulkImport", "app.import.validate_channel_import_data.team_missing.error", nil, "", http.StatusBadRequest) - } - - if data.Name == nil { - return model.NewAppError("BulkImport", "app.import.validate_channel_import_data.name_missing.error", nil, "", http.StatusBadRequest) - } else if len(*data.Name) > model.CHANNEL_NAME_MAX_LENGTH { - return model.NewAppError("BulkImport", "app.import.validate_channel_import_data.name_length.error", nil, "", http.StatusBadRequest) - } else if !model.IsValidChannelIdentifier(*data.Name) { - return model.NewAppError("BulkImport", "app.import.validate_channel_import_data.name_characters.error", nil, "", http.StatusBadRequest) - } - - if data.DisplayName == nil { - return model.NewAppError("BulkImport", "app.import.validate_channel_import_data.display_name_missing.error", nil, "", http.StatusBadRequest) - } else if utf8.RuneCountInString(*data.DisplayName) == 0 || utf8.RuneCountInString(*data.DisplayName) > model.CHANNEL_DISPLAY_NAME_MAX_RUNES { - return model.NewAppError("BulkImport", "app.import.validate_channel_import_data.display_name_length.error", nil, "", http.StatusBadRequest) - } - - if data.Type == nil { - return model.NewAppError("BulkImport", "app.import.validate_channel_import_data.type_missing.error", nil, "", http.StatusBadRequest) - } else if *data.Type != model.CHANNEL_OPEN && *data.Type != model.CHANNEL_PRIVATE { - return model.NewAppError("BulkImport", "app.import.validate_channel_import_data.type_invalid.error", nil, "", http.StatusBadRequest) - } - - if data.Header != nil && utf8.RuneCountInString(*data.Header) > model.CHANNEL_HEADER_MAX_RUNES { - return model.NewAppError("BulkImport", "app.import.validate_channel_import_data.header_length.error", nil, "", http.StatusBadRequest) - } - - if data.Purpose != nil && utf8.RuneCountInString(*data.Purpose) > model.CHANNEL_PURPOSE_MAX_RUNES { - return model.NewAppError("BulkImport", "app.import.validate_channel_import_data.purpose_length.error", nil, "", http.StatusBadRequest) - } - - if data.Scheme != nil && !model.IsValidSchemeName(*data.Scheme) { - return model.NewAppError("BulkImport", "app.import.validate_channel_import_data.scheme_invalid.error", nil, "", http.StatusBadRequest) - } - - return nil -} - -func (a *App) ImportUser(data *UserImportData, dryRun bool) *model.AppError { - if err := validateUserImportData(data); err != nil { - return err - } - - // If this is a Dry Run, do not continue any further. - if dryRun { - return nil - } - - // We want to avoid database writes if nothing has changed. - hasUserChanged := false - hasNotifyPropsChanged := false - hasUserRolesChanged := false - hasUserAuthDataChanged := false - hasUserEmailVerifiedChanged := false - - var user *model.User - if result := <-a.Srv.Store.User().GetByUsername(*data.Username); result.Err == nil { - user = result.Data.(*model.User) - } else { - user = &model.User{} - user.MakeNonNil() - hasUserChanged = true - } - - user.Username = *data.Username - - if user.Email != *data.Email { - hasUserChanged = true - hasUserEmailVerifiedChanged = true // Changing the email resets email verified to false by default. - user.Email = *data.Email - } - - var password string - var authService string - var authData *string - - if data.AuthService != nil { - if user.AuthService != *data.AuthService { - hasUserAuthDataChanged = true - } - authService = *data.AuthService - } - - // AuthData and Password are mutually exclusive. - if data.AuthData != nil { - if user.AuthData == nil || *user.AuthData != *data.AuthData { - hasUserAuthDataChanged = true - } - authData = data.AuthData - password = "" - } else if data.Password != nil { - password = *data.Password - authData = nil - } else { - // If no AuthData or Password is specified, we must generate a password. - password = model.NewId() - authData = nil - } - - user.Password = password - user.AuthService = authService - user.AuthData = authData - - // Automatically assume all emails are verified. - emailVerified := true - if user.EmailVerified != emailVerified { - user.EmailVerified = emailVerified - hasUserEmailVerifiedChanged = true - } - - if data.Nickname != nil { - if user.Nickname != *data.Nickname { - user.Nickname = *data.Nickname - hasUserChanged = true - } - } - - if data.FirstName != nil { - if user.FirstName != *data.FirstName { - user.FirstName = *data.FirstName - hasUserChanged = true - } - } - - if data.LastName != nil { - if user.LastName != *data.LastName { - user.LastName = *data.LastName - hasUserChanged = true - } - } - - if data.Position != nil { - if user.Position != *data.Position { - user.Position = *data.Position - hasUserChanged = true - } - } - - if data.Locale != nil { - if user.Locale != *data.Locale { - user.Locale = *data.Locale - hasUserChanged = true - } - } else { - if user.Locale != *a.Config().LocalizationSettings.DefaultClientLocale { - user.Locale = *a.Config().LocalizationSettings.DefaultClientLocale - hasUserChanged = true - } - } - - var roles string - if data.Roles != nil { - if user.Roles != *data.Roles { - roles = *data.Roles - hasUserRolesChanged = true - } - } else if len(user.Roles) == 0 { - // Set SYSTEM_USER roles on newly created users by default. - if user.Roles != model.SYSTEM_USER_ROLE_ID { - roles = model.SYSTEM_USER_ROLE_ID - hasUserRolesChanged = true - } - } - user.Roles = roles - - if data.NotifyProps != nil { - if data.NotifyProps.Desktop != nil { - if value, ok := user.NotifyProps[model.DESKTOP_NOTIFY_PROP]; !ok || value != *data.NotifyProps.Desktop { - user.AddNotifyProp(model.DESKTOP_NOTIFY_PROP, *data.NotifyProps.Desktop) - hasNotifyPropsChanged = true - } - } - - if data.NotifyProps.DesktopSound != nil { - if value, ok := user.NotifyProps[model.DESKTOP_SOUND_NOTIFY_PROP]; !ok || value != *data.NotifyProps.DesktopSound { - user.AddNotifyProp(model.DESKTOP_SOUND_NOTIFY_PROP, *data.NotifyProps.DesktopSound) - hasNotifyPropsChanged = true - } - } - - if data.NotifyProps.Email != nil { - if value, ok := user.NotifyProps[model.EMAIL_NOTIFY_PROP]; !ok || value != *data.NotifyProps.Email { - user.AddNotifyProp(model.EMAIL_NOTIFY_PROP, *data.NotifyProps.Email) - hasNotifyPropsChanged = true - } - } - - if data.NotifyProps.Mobile != nil { - if value, ok := user.NotifyProps[model.PUSH_NOTIFY_PROP]; !ok || value != *data.NotifyProps.Mobile { - user.AddNotifyProp(model.PUSH_NOTIFY_PROP, *data.NotifyProps.Mobile) - hasNotifyPropsChanged = true - } - } - - if data.NotifyProps.MobilePushStatus != nil { - if value, ok := user.NotifyProps[model.PUSH_STATUS_NOTIFY_PROP]; !ok || value != *data.NotifyProps.MobilePushStatus { - user.AddNotifyProp(model.PUSH_STATUS_NOTIFY_PROP, *data.NotifyProps.MobilePushStatus) - hasNotifyPropsChanged = true - } - } - - if data.NotifyProps.ChannelTrigger != nil { - if value, ok := user.NotifyProps[model.CHANNEL_MENTIONS_NOTIFY_PROP]; !ok || value != *data.NotifyProps.ChannelTrigger { - user.AddNotifyProp(model.CHANNEL_MENTIONS_NOTIFY_PROP, *data.NotifyProps.ChannelTrigger) - hasNotifyPropsChanged = true - } - } - - if data.NotifyProps.CommentsTrigger != nil { - if value, ok := user.NotifyProps[model.COMMENTS_NOTIFY_PROP]; !ok || value != *data.NotifyProps.CommentsTrigger { - user.AddNotifyProp(model.COMMENTS_NOTIFY_PROP, *data.NotifyProps.CommentsTrigger) - hasNotifyPropsChanged = true - } - } - - if data.NotifyProps.MentionKeys != nil { - if value, ok := user.NotifyProps[model.MENTION_KEYS_NOTIFY_PROP]; !ok || value != *data.NotifyProps.MentionKeys { - user.AddNotifyProp(model.MENTION_KEYS_NOTIFY_PROP, *data.NotifyProps.MentionKeys) - hasNotifyPropsChanged = true - } - } - } - - var err *model.AppError - var savedUser *model.User - if user.Id == "" { - if savedUser, err = a.createUser(user); err != nil { - return err - } - } else { - if hasUserChanged { - if savedUser, err = a.UpdateUser(user, false); err != nil { - return err - } - } - if hasUserRolesChanged { - if savedUser, err = a.UpdateUserRoles(user.Id, roles, false); err != nil { - return err - } - } - if hasNotifyPropsChanged { - if savedUser, err = a.UpdateUserNotifyProps(user.Id, user.NotifyProps); err != nil { - return err - } - } - if len(password) > 0 { - if err = a.UpdatePassword(user, password); err != nil { - return err - } - } else { - if hasUserAuthDataChanged { - if res := <-a.Srv.Store.User().UpdateAuthData(user.Id, authService, authData, user.Email, false); res.Err != nil { - return res.Err - } - } - } - if emailVerified { - if hasUserEmailVerifiedChanged { - if err := a.VerifyUserEmail(user.Id); err != nil { - return err - } - } - } - } - - if savedUser == nil { - savedUser = user - } - - if data.ProfileImage != nil { - file, err := os.Open(*data.ProfileImage) - if err != nil { - mlog.Error(fmt.Sprint("api.import.import_user.profile_image.error FIXME: NOT FOUND IN TRANSLATIONS FILE", err)) - } - if err := a.SetProfileImageFromFile(savedUser.Id, file); err != nil { - mlog.Error(fmt.Sprint("api.import.import_user.profile_image.error FIXME: NOT FOUND IN TRANSLATIONS FILE", err)) - } - } - - // Preferences. - var preferences model.Preferences - - if data.Theme != nil { - preferences = append(preferences, model.Preference{ - UserId: savedUser.Id, - Category: model.PREFERENCE_CATEGORY_THEME, - Name: "", - Value: *data.Theme, - }) - } - - if data.UseMilitaryTime != nil { - preferences = append(preferences, model.Preference{ - UserId: savedUser.Id, - Category: model.PREFERENCE_CATEGORY_DISPLAY_SETTINGS, - Name: "use_military_time", - Value: *data.UseMilitaryTime, - }) - } - - if data.CollapsePreviews != nil { - preferences = append(preferences, model.Preference{ - UserId: savedUser.Id, - Category: model.PREFERENCE_CATEGORY_DISPLAY_SETTINGS, - Name: "collapse_previews", - Value: *data.CollapsePreviews, - }) - } - - if data.MessageDisplay != nil { - preferences = append(preferences, model.Preference{ - UserId: savedUser.Id, - Category: model.PREFERENCE_CATEGORY_DISPLAY_SETTINGS, - Name: "message_display", - Value: *data.MessageDisplay, - }) - } - - if data.ChannelDisplayMode != nil { - preferences = append(preferences, model.Preference{ - UserId: savedUser.Id, - Category: model.PREFERENCE_CATEGORY_DISPLAY_SETTINGS, - Name: "channel_display_mode", - Value: *data.ChannelDisplayMode, - }) - } - - if data.TutorialStep != nil { - preferences = append(preferences, model.Preference{ - UserId: savedUser.Id, - Category: model.PREFERENCE_CATEGORY_TUTORIAL_STEPS, - Name: savedUser.Id, - Value: *data.TutorialStep, - }) - } - - if len(preferences) > 0 { - if result := <-a.Srv.Store.Preference().Save(&preferences); result.Err != nil { - return model.NewAppError("BulkImport", "app.import.import_user.save_preferences.error", nil, result.Err.Error(), http.StatusInternalServerError) - } - } - - return a.ImportUserTeams(savedUser, data.Teams) -} - -func (a *App) ImportUserTeams(user *model.User, data *[]UserTeamImportData) *model.AppError { - if data == nil { - return nil - } - - for _, tdata := range *data { - team, err := a.GetTeamByName(*tdata.Name) - if err != nil { - return err - } - - var roles string - isSchemeUser := true - isSchemeAdmin := false - - if tdata.Roles == nil { - isSchemeUser = true - } else { - rawRoles := *tdata.Roles - explicitRoles := []string{} - for _, role := range strings.Fields(rawRoles) { - if role == model.TEAM_USER_ROLE_ID { - isSchemeUser = true - } else if role == model.TEAM_ADMIN_ROLE_ID { - isSchemeAdmin = true - } else { - explicitRoles = append(explicitRoles, role) - } - } - roles = strings.Join(explicitRoles, " ") - } - - var member *model.TeamMember - if member, _, err = a.joinUserToTeam(team, user); err != nil { - return err - } - - if member.ExplicitRoles != roles { - if _, err := a.UpdateTeamMemberRoles(team.Id, user.Id, roles); err != nil { - return err - } - } - - if member.SchemeAdmin != isSchemeAdmin || member.SchemeUser != isSchemeUser { - a.UpdateTeamMemberSchemeRoles(team.Id, user.Id, isSchemeUser, isSchemeAdmin) - } - - if defaultChannel, err := a.GetChannelByName(model.DEFAULT_CHANNEL, team.Id, true); err != nil { - return err - } else if _, err = a.addUserToChannel(user, defaultChannel, member); err != nil { - return err - } - - if err := a.ImportUserChannels(user, team, member, tdata.Channels); err != nil { - return err - } - } - - return nil -} - -func (a *App) ImportUserChannels(user *model.User, team *model.Team, teamMember *model.TeamMember, data *[]UserChannelImportData) *model.AppError { - if data == nil { - return nil - } - - var preferences model.Preferences - - // Loop through all channels. - for _, cdata := range *data { - channel, err := a.GetChannelByName(*cdata.Name, team.Id, true) - if err != nil { - return err - } - - var roles string - isSchemeUser := true - isSchemeAdmin := false - - if cdata.Roles == nil { - isSchemeUser = true - } else { - rawRoles := *cdata.Roles - explicitRoles := []string{} - for _, role := range strings.Fields(rawRoles) { - if role == model.CHANNEL_USER_ROLE_ID { - isSchemeUser = true - } else if role == model.CHANNEL_ADMIN_ROLE_ID { - isSchemeAdmin = 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 { - a.UpdateChannelMemberSchemeRoles(channel.Id, user.Id, 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 - } - } - - if cdata.Favorite != nil && *cdata.Favorite { - preferences = append(preferences, model.Preference{ - UserId: user.Id, - Category: model.PREFERENCE_CATEGORY_FAVORITE_CHANNEL, - Name: channel.Id, - Value: "true", - }) - } - } - - if len(preferences) > 0 { - if result := <-a.Srv.Store.Preference().Save(&preferences); result.Err != nil { - return model.NewAppError("BulkImport", "app.import.import_user_channels.save_preferences.error", nil, result.Err.Error(), http.StatusInternalServerError) - } - } - - return nil -} - -func validateUserImportData(data *UserImportData) *model.AppError { - if data.ProfileImage != nil { - if _, err := os.Stat(*data.ProfileImage); os.IsNotExist(err) { - return model.NewAppError("BulkImport", "app.import.validate_user_import_data.profile_image.error", nil, "", http.StatusBadRequest) - } - } - - if data.Username == nil { - return model.NewAppError("BulkImport", "app.import.validate_user_import_data.username_missing.error", nil, "", http.StatusBadRequest) - } else if !model.IsValidUsername(*data.Username) { - return model.NewAppError("BulkImport", "app.import.validate_user_import_data.username_invalid.error", nil, "", http.StatusBadRequest) - } - - if data.Email == nil { - return model.NewAppError("BulkImport", "app.import.validate_user_import_data.email_missing.error", nil, "", http.StatusBadRequest) - } else if len(*data.Email) == 0 || len(*data.Email) > model.USER_EMAIL_MAX_LENGTH { - return model.NewAppError("BulkImport", "app.import.validate_user_import_data.email_length.error", nil, "", http.StatusBadRequest) - } - - if data.AuthService != nil && len(*data.AuthService) == 0 { - return model.NewAppError("BulkImport", "app.import.validate_user_import_data.auth_service_length.error", nil, "", http.StatusBadRequest) - } - - if data.AuthData != nil && data.Password != nil { - return model.NewAppError("BulkImport", "app.import.validate_user_import_data.auth_data_and_password.error", nil, "", http.StatusBadRequest) - } - - if data.AuthData != nil && len(*data.AuthData) > model.USER_AUTH_DATA_MAX_LENGTH { - return model.NewAppError("BulkImport", "app.import.validate_user_import_data.auth_data_length.error", nil, "", http.StatusBadRequest) - } - - if data.Password != nil && len(*data.Password) == 0 { - return model.NewAppError("BulkImport", "app.import.validate_user_import_data.password_length.error", nil, "", http.StatusBadRequest) - } - - if data.Password != nil && len(*data.Password) > model.USER_PASSWORD_MAX_LENGTH { - return model.NewAppError("BulkImport", "app.import.validate_user_import_data.password_length.error", nil, "", http.StatusBadRequest) - } - - if data.Nickname != nil && utf8.RuneCountInString(*data.Nickname) > model.USER_NICKNAME_MAX_RUNES { - return model.NewAppError("BulkImport", "app.import.validate_user_import_data.nickname_length.error", nil, "", http.StatusBadRequest) - } - - if data.FirstName != nil && utf8.RuneCountInString(*data.FirstName) > model.USER_FIRST_NAME_MAX_RUNES { - return model.NewAppError("BulkImport", "app.import.validate_user_import_data.first_name_length.error", nil, "", http.StatusBadRequest) - } - - if data.LastName != nil && utf8.RuneCountInString(*data.LastName) > model.USER_LAST_NAME_MAX_RUNES { - return model.NewAppError("BulkImport", "app.import.validate_user_import_data.last_name_length.error", nil, "", http.StatusBadRequest) - } - - if data.Position != nil && utf8.RuneCountInString(*data.Position) > model.USER_POSITION_MAX_RUNES { - return model.NewAppError("BulkImport", "app.import.validate_user_import_data.position_length.error", nil, "", http.StatusBadRequest) - } - - if data.Roles != nil && !model.IsValidUserRoles(*data.Roles) { - return model.NewAppError("BulkImport", "app.import.validate_user_import_data.roles_invalid.error", nil, "", http.StatusBadRequest) - } - - if data.NotifyProps != nil { - if data.NotifyProps.Desktop != nil && !model.IsValidUserNotifyLevel(*data.NotifyProps.Desktop) { - return model.NewAppError("BulkImport", "app.import.validate_user_import_data.notify_props_desktop_invalid.error", nil, "", http.StatusBadRequest) - } - - if data.NotifyProps.DesktopSound != nil && !model.IsValidTrueOrFalseString(*data.NotifyProps.DesktopSound) { - return model.NewAppError("BulkImport", "app.import.validate_user_import_data.notify_props_desktop_sound_invalid.error", nil, "", http.StatusBadRequest) - } - - if data.NotifyProps.Email != nil && !model.IsValidTrueOrFalseString(*data.NotifyProps.Email) { - return model.NewAppError("BulkImport", "app.import.validate_user_import_data.notify_props_email_invalid.error", nil, "", http.StatusBadRequest) - } - - if data.NotifyProps.Mobile != nil && !model.IsValidUserNotifyLevel(*data.NotifyProps.Mobile) { - return model.NewAppError("BulkImport", "app.import.validate_user_import_data.notify_props_mobile_invalid.error", nil, "", http.StatusBadRequest) - } - - if data.NotifyProps.MobilePushStatus != nil && !model.IsValidPushStatusNotifyLevel(*data.NotifyProps.MobilePushStatus) { - return model.NewAppError("BulkImport", "app.import.validate_user_import_data.notify_props_mobile_push_status_invalid.error", nil, "", http.StatusBadRequest) - } - - if data.NotifyProps.ChannelTrigger != nil && !model.IsValidTrueOrFalseString(*data.NotifyProps.ChannelTrigger) { - return model.NewAppError("BulkImport", "app.import.validate_user_import_data.notify_props_channel_trigger_invalid.error", nil, "", http.StatusBadRequest) - } - - if data.NotifyProps.CommentsTrigger != nil && !model.IsValidCommentsNotifyLevel(*data.NotifyProps.CommentsTrigger) { - return model.NewAppError("BulkImport", "app.import.validate_user_import_data.notify_props_comments_trigger_invalid.error", nil, "", http.StatusBadRequest) - } - } - - if data.Teams != nil { - return validateUserTeamsImportData(data.Teams) - } else { - return nil - } -} - -func validateUserTeamsImportData(data *[]UserTeamImportData) *model.AppError { - if data == nil { - return nil - } - - for _, tdata := range *data { - if tdata.Name == nil { - return model.NewAppError("BulkImport", "app.import.validate_user_teams_import_data.team_name_missing.error", nil, "", http.StatusBadRequest) - } - - if tdata.Roles != nil && !model.IsValidUserRoles(*tdata.Roles) { - return model.NewAppError("BulkImport", "app.import.validate_user_teams_import_data.invalid_roles.error", nil, "", http.StatusBadRequest) - } - - if tdata.Channels != nil { - if err := validateUserChannelsImportData(tdata.Channels); err != nil { - return err - } - } - } - - return nil -} - -func validateUserChannelsImportData(data *[]UserChannelImportData) *model.AppError { - if data == nil { - return nil - } - - for _, cdata := range *data { - if cdata.Name == nil { - return model.NewAppError("BulkImport", "app.import.validate_user_channels_import_data.channel_name_missing.error", nil, "", http.StatusBadRequest) - } - - if cdata.Roles != nil && !model.IsValidUserRoles(*cdata.Roles) { - return model.NewAppError("BulkImport", "app.import.validate_user_channels_import_data.invalid_roles.error", nil, "", http.StatusBadRequest) - } - - if cdata.NotifyProps != nil { - if cdata.NotifyProps.Desktop != nil && !model.IsChannelNotifyLevelValid(*cdata.NotifyProps.Desktop) { - return model.NewAppError("BulkImport", "app.import.validate_user_channels_import_data.invalid_notify_props_desktop.error", nil, "", http.StatusBadRequest) - } - - if cdata.NotifyProps.Mobile != nil && !model.IsChannelNotifyLevelValid(*cdata.NotifyProps.Mobile) { - return model.NewAppError("BulkImport", "app.import.validate_user_channels_import_data.invalid_notify_props_mobile.error", nil, "", http.StatusBadRequest) - } - - if cdata.NotifyProps.MarkUnread != nil && !model.IsChannelMarkUnreadLevelValid(*cdata.NotifyProps.MarkUnread) { - return model.NewAppError("BulkImport", "app.import.validate_user_channels_import_data.invalid_notify_props_mark_unread.error", nil, "", http.StatusBadRequest) - } - } - } - - return nil -} - -func (a *App) ImportReaction(data *ReactionImportData, post *model.Post, dryRun bool) *model.AppError { - if err := validateReactionImportData(data, post.CreateAt); err != nil { - return err - } - - var user *model.User - if result := <-a.Srv.Store.User().GetByUsername(*data.User); result.Err != nil { - return model.NewAppError("BulkImport", "app.import.import_post.user_not_found.error", map[string]interface{}{"Username": data.User}, result.Err.Error(), http.StatusBadRequest) - } else { - user = result.Data.(*model.User) - } - reaction := &model.Reaction{ - UserId: user.Id, - PostId: post.Id, - EmojiName: *data.EmojiName, - CreateAt: *data.CreateAt, - } - if result := <-a.Srv.Store.Reaction().Save(reaction); result.Err != nil { - return result.Err - } - return nil -} - -func (a *App) ImportReply(data *ReplyImportData, post *model.Post, teamId string, dryRun bool) *model.AppError { - if err := validateReplyImportData(data, post.CreateAt, a.MaxPostSize()); err != nil { - return err - } - - var user *model.User - if result := <-a.Srv.Store.User().GetByUsername(*data.User); result.Err != nil { - return model.NewAppError("BulkImport", "app.import.import_post.user_not_found.error", map[string]interface{}{"Username": data.User}, result.Err.Error(), http.StatusBadRequest) - } else { - user = result.Data.(*model.User) - } - - // Check if this post already exists. - var replies []*model.Post - if result := <-a.Srv.Store.Post().GetPostsCreatedAt(post.ChannelId, *data.CreateAt); result.Err != nil { - return result.Err - } else { - replies = result.Data.([]*model.Post) - } - - var reply *model.Post - for _, r := range replies { - if r.Message == *data.Message { - reply = r - break - } - } - - if reply == nil { - reply = &model.Post{} - } - reply.UserId = user.Id - reply.ChannelId = post.ChannelId - reply.ParentId = post.Id - reply.RootId = post.Id - reply.Message = *data.Message - reply.CreateAt = *data.CreateAt - - if data.Attachments != nil { - fileIds, err := a.uploadAttachments(data.Attachments, reply, teamId, dryRun) - if err != nil { - return err - } - reply.FileIds = fileIds - } - - if reply.Id == "" { - if result := <-a.Srv.Store.Post().Save(reply); result.Err != nil { - return result.Err - } - } else { - if result := <-a.Srv.Store.Post().Overwrite(reply); result.Err != nil { - return result.Err - } - } - - a.UpdateFileInfoWithPostId(reply) - - return nil -} - -func (a *App) ImportAttachment(data *AttachmentImportData, post *model.Post, teamId string, dryRun bool) (*model.FileInfo, *model.AppError) { - fileUploadError := model.NewAppError("BulkImport", "app.import.attachment.file_upload.error", map[string]interface{}{"FilePath": *data.Path}, "", http.StatusBadRequest) - file, err := os.Open(*data.Path) - if err != nil { - return nil, model.NewAppError("BulkImport", "app.import.attachment.bad_file.error", map[string]interface{}{"FilePath": *data.Path}, "", http.StatusBadRequest) - } - if file != nil { - timestamp := utils.TimeFromMillis(post.CreateAt) - buf := bytes.NewBuffer(nil) - io.Copy(buf, file) - - fileInfo, err := a.DoUploadFile(timestamp, teamId, post.ChannelId, post.UserId, file.Name(), buf.Bytes()) - - if err != nil { - fmt.Print(err) - return nil, fileUploadError - } - - mlog.Info(fmt.Sprintf("uploading file with name %s", file.Name())) - return fileInfo, nil - } - return nil, fileUploadError -} - -func (a *App) ImportPost(data *PostImportData, dryRun bool) *model.AppError { - if err := validatePostImportData(data, a.MaxPostSize()); err != nil { - return err - } - - // If this is a Dry Run, do not continue any further. - if dryRun { - return nil - } - - var team *model.Team - if result := <-a.Srv.Store.Team().GetByName(*data.Team); result.Err != nil { - return model.NewAppError("BulkImport", "app.import.import_post.team_not_found.error", map[string]interface{}{"TeamName": *data.Team}, result.Err.Error(), http.StatusBadRequest) - } else { - team = result.Data.(*model.Team) - } - - var channel *model.Channel - if result := <-a.Srv.Store.Channel().GetByName(team.Id, *data.Channel, false); result.Err != nil { - return model.NewAppError("BulkImport", "app.import.import_post.channel_not_found.error", map[string]interface{}{"ChannelName": *data.Channel}, result.Err.Error(), http.StatusBadRequest) - } else { - channel = result.Data.(*model.Channel) - } - - var user *model.User - if result := <-a.Srv.Store.User().GetByUsername(*data.User); result.Err != nil { - return model.NewAppError("BulkImport", "app.import.import_post.user_not_found.error", map[string]interface{}{"Username": *data.User}, result.Err.Error(), http.StatusBadRequest) - } else { - user = result.Data.(*model.User) - } - - // Check if this post already exists. - var posts []*model.Post - if result := <-a.Srv.Store.Post().GetPostsCreatedAt(channel.Id, *data.CreateAt); result.Err != nil { - return result.Err - } else { - posts = result.Data.([]*model.Post) - } - - var post *model.Post - for _, p := range posts { - if p.Message == *data.Message { - post = p - break - } - } - - if post == nil { - post = &model.Post{} - } - - post.ChannelId = channel.Id - post.Message = *data.Message - post.UserId = user.Id - post.CreateAt = *data.CreateAt - - post.Hashtags, _ = model.ParseHashtags(post.Message) - - if data.Attachments != nil { - fileIds, err := a.uploadAttachments(data.Attachments, post, team.Id, dryRun) - if err != nil { - return err - } - post.FileIds = fileIds - } - - if post.Id == "" { - if result := <-a.Srv.Store.Post().Save(post); result.Err != nil { - return result.Err - } - } else { - if result := <-a.Srv.Store.Post().Overwrite(post); result.Err != nil { - return result.Err - } - } - - if data.FlaggedBy != nil { - var preferences model.Preferences - - for _, username := range *data.FlaggedBy { - var user *model.User - - if result := <-a.Srv.Store.User().GetByUsername(username); result.Err != nil { - return model.NewAppError("BulkImport", "app.import.import_post.user_not_found.error", map[string]interface{}{"Username": username}, result.Err.Error(), http.StatusBadRequest) - } else { - user = result.Data.(*model.User) - } - - preferences = append(preferences, model.Preference{ - UserId: user.Id, - Category: model.PREFERENCE_CATEGORY_FLAGGED_POST, - Name: post.Id, - Value: "true", - }) - } - - if len(preferences) > 0 { - if result := <-a.Srv.Store.Preference().Save(&preferences); result.Err != nil { - return model.NewAppError("BulkImport", "app.import.import_post.save_preferences.error", nil, result.Err.Error(), http.StatusInternalServerError) - } - } - } - - if data.Reactions != nil { - for _, reaction := range *data.Reactions { - if err := a.ImportReaction(&reaction, post, dryRun); err != nil { - return err - } - } - } - - if data.Replies != nil { - for _, reply := range *data.Replies { - if err := a.ImportReply(&reply, post, team.Id, dryRun); err != nil { - return err - } - } - } - - a.UpdateFileInfoWithPostId(post) - return nil -} - -func (a *App) uploadAttachments(attachments *[]AttachmentImportData, post *model.Post, teamId string, dryRun bool) ([]string, *model.AppError) { - fileIds := []string{} - for _, attachment := range *attachments { - fileInfo, err := a.ImportAttachment(&attachment, post, teamId, dryRun) - if err != nil { - return nil, err - } - fileIds = append(fileIds, fileInfo.Id) - } - return fileIds, nil -} - -func (a *App) UpdateFileInfoWithPostId(post *model.Post) { - for _, fileId := range post.FileIds { - if result := <-a.Srv.Store.FileInfo().AttachToPost(fileId, post.Id); result.Err != nil { - mlog.Error(fmt.Sprintf("Error attaching files to post. postId=%v, fileIds=%v, message=%v", post.Id, post.FileIds, result.Err), mlog.String("post_id", post.Id)) - } - } -} -func validateReactionImportData(data *ReactionImportData, parentCreateAt int64) *model.AppError { - if data.User == nil { - return model.NewAppError("BulkImport", "app.import.validate_reaction_import_data.user_missing.error", nil, "", http.StatusBadRequest) - } - - if data.EmojiName == nil { - return model.NewAppError("BulkImport", "app.import.validate_reaction_import_data.emoji_name_missing.error", nil, "", http.StatusBadRequest) - } else if utf8.RuneCountInString(*data.EmojiName) > model.EMOJI_NAME_MAX_LENGTH { - return model.NewAppError("BulkImport", "app.import.validate_reaction_import_data.emoji_name_length.error", nil, "", http.StatusBadRequest) - } - - if data.CreateAt == nil { - return model.NewAppError("BulkImport", "app.import.validate_reaction_import_data.create_at_missing.error", nil, "", http.StatusBadRequest) - } else if *data.CreateAt == 0 { - return model.NewAppError("BulkImport", "app.import.validate_reaction_import_data.create_at_zero.error", nil, "", http.StatusBadRequest) - } else if *data.CreateAt < parentCreateAt { - return model.NewAppError("BulkImport", "app.import.validate_reaction_import_data.create_at_before_parent.error", nil, "", http.StatusBadRequest) - } - - return nil -} - -func validateReplyImportData(data *ReplyImportData, parentCreateAt int64, maxPostSize int) *model.AppError { - if data.User == nil { - return model.NewAppError("BulkImport", "app.import.validate_reply_import_data.user_missing.error", nil, "", http.StatusBadRequest) - } - - if data.Message == nil { - return model.NewAppError("BulkImport", "app.import.validate_reply_import_data.message_missing.error", nil, "", http.StatusBadRequest) - } else if utf8.RuneCountInString(*data.Message) > maxPostSize { - return model.NewAppError("BulkImport", "app.import.validate_reply_import_data.message_length.error", nil, "", http.StatusBadRequest) - } - - if data.CreateAt == nil { - return model.NewAppError("BulkImport", "app.import.validate_reply_import_data.create_at_missing.error", nil, "", http.StatusBadRequest) - } else if *data.CreateAt == 0 { - return model.NewAppError("BulkImport", "app.import.validate_reply_import_data.create_at_zero.error", nil, "", http.StatusBadRequest) - } else if *data.CreateAt < parentCreateAt { - return model.NewAppError("BulkImport", "app.import.validate_reply_import_data.create_at_before_parent.error", nil, "", http.StatusBadRequest) - } - - return nil -} - -func validatePostImportData(data *PostImportData, maxPostSize int) *model.AppError { - if data.Team == nil { - return model.NewAppError("BulkImport", "app.import.validate_post_import_data.team_missing.error", nil, "", http.StatusBadRequest) - } - - if data.Channel == nil { - return model.NewAppError("BulkImport", "app.import.validate_post_import_data.channel_missing.error", nil, "", http.StatusBadRequest) - } - - if data.User == nil { - return model.NewAppError("BulkImport", "app.import.validate_post_import_data.user_missing.error", nil, "", http.StatusBadRequest) - } - - if data.Message == nil { - return model.NewAppError("BulkImport", "app.import.validate_post_import_data.message_missing.error", nil, "", http.StatusBadRequest) - } else if utf8.RuneCountInString(*data.Message) > maxPostSize { - return model.NewAppError("BulkImport", "app.import.validate_post_import_data.message_length.error", nil, "", http.StatusBadRequest) - } - - if data.CreateAt == nil { - return model.NewAppError("BulkImport", "app.import.validate_post_import_data.create_at_missing.error", nil, "", http.StatusBadRequest) - } else if *data.CreateAt == 0 { - return model.NewAppError("BulkImport", "app.import.validate_post_import_data.create_at_zero.error", nil, "", http.StatusBadRequest) - } - - if data.Reactions != nil { - for _, reaction := range *data.Reactions { - validateReactionImportData(&reaction, *data.CreateAt) - } - } - - if data.Replies != nil { - for _, reply := range *data.Replies { - validateReplyImportData(&reply, *data.CreateAt, maxPostSize) - } - } - - return nil -} - -func (a *App) ImportDirectChannel(data *DirectChannelImportData, dryRun bool) *model.AppError { - if err := validateDirectChannelImportData(data); err != nil { - return err - } - - // If this is a Dry Run, do not continue any further. - if dryRun { - return nil - } - - var userIds []string - userMap := make(map[string]string) - for _, username := range *data.Members { - if result := <-a.Srv.Store.User().GetByUsername(username); result.Err == nil { - user := result.Data.(*model.User) - userIds = append(userIds, user.Id) - userMap[username] = user.Id - } else { - return model.NewAppError("BulkImport", "app.import.import_direct_channel.member_not_found.error", nil, result.Err.Error(), http.StatusBadRequest) - } - } - - var channel *model.Channel - - if len(userIds) == 2 { - ch, err := a.createDirectChannel(userIds[0], userIds[1]) - if err != nil && err.Id != store.CHANNEL_EXISTS_ERROR { - return model.NewAppError("BulkImport", "app.import.import_direct_channel.create_direct_channel.error", nil, err.Error(), http.StatusBadRequest) - } else { - channel = ch - } - } else { - ch, err := a.createGroupChannel(userIds, userIds[0]) - if err != nil && err.Id != store.CHANNEL_EXISTS_ERROR { - return model.NewAppError("BulkImport", "app.import.import_direct_channel.create_group_channel.error", nil, err.Error(), http.StatusBadRequest) - } else { - channel = ch - } - } - - var preferences model.Preferences - - for _, userId := range userIds { - preferences = append(preferences, model.Preference{ - UserId: userId, - Category: model.PREFERENCE_CATEGORY_DIRECT_CHANNEL_SHOW, - Name: channel.Id, - Value: "true", - }) - } - - if data.FavoritedBy != nil { - for _, favoriter := range *data.FavoritedBy { - preferences = append(preferences, model.Preference{ - UserId: userMap[favoriter], - Category: model.PREFERENCE_CATEGORY_FAVORITE_CHANNEL, - Name: channel.Id, - Value: "true", - }) - } - } - - if result := <-a.Srv.Store.Preference().Save(&preferences); result.Err != nil { - result.Err.StatusCode = http.StatusBadRequest - return result.Err - } - - if data.Header != nil { - channel.Header = *data.Header - if result := <-a.Srv.Store.Channel().Update(channel); result.Err != nil { - return model.NewAppError("BulkImport", "app.import.import_direct_channel.update_header_failed.error", nil, result.Err.Error(), http.StatusBadRequest) - } - } - - return nil -} - -func validateDirectChannelImportData(data *DirectChannelImportData) *model.AppError { - if data.Members == nil { - return model.NewAppError("BulkImport", "app.import.validate_direct_channel_import_data.members_required.error", nil, "", http.StatusBadRequest) - } - - if len(*data.Members) != 2 { - if len(*data.Members) < model.CHANNEL_GROUP_MIN_USERS { - return model.NewAppError("BulkImport", "app.import.validate_direct_channel_import_data.members_too_few.error", nil, "", http.StatusBadRequest) - } else if len(*data.Members) > model.CHANNEL_GROUP_MAX_USERS { - return model.NewAppError("BulkImport", "app.import.validate_direct_channel_import_data.members_too_many.error", nil, "", http.StatusBadRequest) - } - } - - if data.Header != nil && utf8.RuneCountInString(*data.Header) > model.CHANNEL_HEADER_MAX_RUNES { - return model.NewAppError("BulkImport", "app.import.validate_direct_channel_import_data.header_length.error", nil, "", http.StatusBadRequest) - } - - if data.FavoritedBy != nil { - for _, favoriter := range *data.FavoritedBy { - found := false - for _, member := range *data.Members { - if favoriter == member { - found = true - break - } - } - if !found { - return model.NewAppError("BulkImport", "app.import.validate_direct_channel_import_data.unknown_favoriter.error", map[string]interface{}{"Username": favoriter}, "", http.StatusBadRequest) - } - } - } - - return nil -} - -func (a *App) ImportDirectPost(data *DirectPostImportData, dryRun bool) *model.AppError { - if err := validateDirectPostImportData(data, a.MaxPostSize()); err != nil { - return err - } - - // If this is a Dry Run, do not continue any further. - if dryRun { - return nil - } - - var userIds []string - for _, username := range *data.ChannelMembers { - if result := <-a.Srv.Store.User().GetByUsername(username); result.Err == nil { - user := result.Data.(*model.User) - userIds = append(userIds, user.Id) - } else { - return model.NewAppError("BulkImport", "app.import.import_direct_post.channel_member_not_found.error", nil, result.Err.Error(), http.StatusBadRequest) - } - } - - var channel *model.Channel - if len(userIds) == 2 { - ch, err := a.createDirectChannel(userIds[0], userIds[1]) - if err != nil && err.Id != store.CHANNEL_EXISTS_ERROR { - return model.NewAppError("BulkImport", "app.import.import_direct_post.create_direct_channel.error", nil, err.Error(), http.StatusBadRequest) - } else { - channel = ch - } - } else { - ch, err := a.createGroupChannel(userIds, userIds[0]) - if err != nil && err.Id != store.CHANNEL_EXISTS_ERROR { - return model.NewAppError("BulkImport", "app.import.import_direct_post.create_group_channel.error", nil, err.Error(), http.StatusBadRequest) - } else { - channel = ch - } - } - - var user *model.User - if result := <-a.Srv.Store.User().GetByUsername(*data.User); result.Err != nil { - return model.NewAppError("BulkImport", "app.import.import_direct_post.user_not_found.error", map[string]interface{}{"Username": *data.User}, "", http.StatusBadRequest) - } else { - user = result.Data.(*model.User) - } - - // Check if this post already exists. - var posts []*model.Post - if result := <-a.Srv.Store.Post().GetPostsCreatedAt(channel.Id, *data.CreateAt); result.Err != nil { - return result.Err - } else { - posts = result.Data.([]*model.Post) - } - - var post *model.Post - for _, p := range posts { - if p.Message == *data.Message { - post = p - break - } - } - - if post == nil { - post = &model.Post{} - } - - post.ChannelId = channel.Id - post.Message = *data.Message - post.UserId = user.Id - post.CreateAt = *data.CreateAt - - post.Hashtags, _ = model.ParseHashtags(post.Message) - - if post.Id == "" { - if result := <-a.Srv.Store.Post().Save(post); result.Err != nil { - return result.Err - } - } else { - if result := <-a.Srv.Store.Post().Overwrite(post); result.Err != nil { - return result.Err - } - } - - if data.FlaggedBy != nil { - var preferences model.Preferences - - for _, username := range *data.FlaggedBy { - var user *model.User - - if result := <-a.Srv.Store.User().GetByUsername(username); result.Err != nil { - return model.NewAppError("BulkImport", "app.import.import_direct_post.user_not_found.error", map[string]interface{}{"Username": username}, "", http.StatusBadRequest) - } else { - user = result.Data.(*model.User) - } - - preferences = append(preferences, model.Preference{ - UserId: user.Id, - Category: model.PREFERENCE_CATEGORY_FLAGGED_POST, - Name: post.Id, - Value: "true", - }) - } - - if len(preferences) > 0 { - if result := <-a.Srv.Store.Preference().Save(&preferences); result.Err != nil { - return model.NewAppError("BulkImport", "app.import.import_direct_post.save_preferences.error", nil, result.Err.Error(), http.StatusInternalServerError) - } - } - } - - if data.Reactions != nil { - for _, reaction := range *data.Reactions { - if err := a.ImportReaction(&reaction, post, dryRun); err != nil { - return err - } - } - } - - if data.Replies != nil { - for _, reply := range *data.Replies { - if err := a.ImportReply(&reply, post, "noteam", dryRun); err != nil { - return err - } - } - } - - return nil -} - -func validateDirectPostImportData(data *DirectPostImportData, maxPostSize int) *model.AppError { - if data.ChannelMembers == nil { - return model.NewAppError("BulkImport", "app.import.validate_direct_post_import_data.channel_members_required.error", nil, "", http.StatusBadRequest) - } - - if len(*data.ChannelMembers) != 2 { - if len(*data.ChannelMembers) < model.CHANNEL_GROUP_MIN_USERS { - return model.NewAppError("BulkImport", "app.import.validate_direct_post_import_data.channel_members_too_few.error", nil, "", http.StatusBadRequest) - } else if len(*data.ChannelMembers) > model.CHANNEL_GROUP_MAX_USERS { - return model.NewAppError("BulkImport", "app.import.validate_direct_post_import_data.channel_members_too_many.error", nil, "", http.StatusBadRequest) - } - } - - if data.User == nil { - return model.NewAppError("BulkImport", "app.import.validate_direct_post_import_data.user_missing.error", nil, "", http.StatusBadRequest) - } - - if data.Message == nil { - return model.NewAppError("BulkImport", "app.import.validate_direct_post_import_data.message_missing.error", nil, "", http.StatusBadRequest) - } else if utf8.RuneCountInString(*data.Message) > maxPostSize { - return model.NewAppError("BulkImport", "app.import.validate_direct_post_import_data.message_length.error", nil, "", http.StatusBadRequest) - } - - if data.CreateAt == nil { - return model.NewAppError("BulkImport", "app.import.validate_direct_post_import_data.create_at_missing.error", nil, "", http.StatusBadRequest) - } else if *data.CreateAt == 0 { - return model.NewAppError("BulkImport", "app.import.validate_direct_post_import_data.create_at_zero.error", nil, "", http.StatusBadRequest) - } - - if data.FlaggedBy != nil { - for _, flagger := range *data.FlaggedBy { - found := false - for _, member := range *data.ChannelMembers { - if flagger == member { - found = true - break - } - } - if !found { - return model.NewAppError("BulkImport", "app.import.validate_direct_post_import_data.unknown_flagger.error", map[string]interface{}{"Username": flagger}, "", http.StatusBadRequest) - } - } - } - - if data.Reactions != nil { - for _, reaction := range *data.Reactions { - validateReactionImportData(&reaction, *data.CreateAt) - } - } - - if data.Replies != nil { - for _, reply := range *data.Replies { - validateReplyImportData(&reply, *data.CreateAt, maxPostSize) - } - } - - return nil -} - -func (a *App) ImportEmoji(data *EmojiImportData, dryRun bool) *model.AppError { - if err := validateEmojiImportData(data); err != nil { - return err - } - - // If this is a Dry Run, do not continue any further. - if dryRun { - return nil - } - - var emoji *model.Emoji - - if result := <-a.Srv.Store.Emoji().GetByName(*data.Name); result.Err != nil && result.Err.StatusCode != http.StatusNotFound { - return result.Err - } else if result.Data != nil { - emoji = result.Data.(*model.Emoji) - } - - alreadyExists := emoji != nil - - if !alreadyExists { - emoji = &model.Emoji{ - Name: *data.Name, - } - emoji.PreSave() - } - - file, err := os.Open(*data.Image) - if err != nil { - return model.NewAppError("BulkImport", "app.import.emoji.bad_file.error", map[string]interface{}{"EmojiName": *data.Name}, "", http.StatusBadRequest) - } - - if _, err := a.WriteFile(file, getEmojiImagePath(emoji.Id)); err != nil { - return err - } - - if !alreadyExists { - if result := <-a.Srv.Store.Emoji().Save(emoji); result.Err != nil { - return result.Err - } - } - - return nil -} - -func validateEmojiImportData(data *EmojiImportData) *model.AppError { - if data == nil { - return model.NewAppError("BulkImport", "app.import.validate_emoji_import_data.empty.error", nil, "", http.StatusBadRequest) - } - - if data.Name == nil || len(*data.Name) == 0 { - return model.NewAppError("BulkImport", "app.import.validate_emoji_import_data.name_missing.error", nil, "", http.StatusBadRequest) - } - - if err := model.IsValidEmojiName(*data.Name); err != nil { - return err - } - - if data.Image == nil || len(*data.Image) == 0 { - return model.NewAppError("BulkImport", "app.import.validate_emoji_import_data.image_missing.error", nil, "", http.StatusBadRequest) - } - - return nil -} - -// -// -- Old SlackImport Functions -- -// Import functions are sutible for entering posts and users into the database without -// some of the usual checks. (IsValid is still run) -// - -func (a *App) OldImportPost(post *model.Post) { - // Workaround for empty messages, which may be the case if they are webhook posts. - firstIteration := true - maxPostSize := a.MaxPostSize() - for messageRuneCount := utf8.RuneCountInString(post.Message); messageRuneCount > 0 || firstIteration; messageRuneCount = utf8.RuneCountInString(post.Message) { - firstIteration = false - var remainder string - if messageRuneCount > maxPostSize { - remainder = string(([]rune(post.Message))[maxPostSize:]) - post.Message = truncateRunes(post.Message, maxPostSize) - } else { - remainder = "" - } - - post.Hashtags, _ = model.ParseHashtags(post.Message) - - if result := <-a.Srv.Store.Post().Save(post); result.Err != nil { - mlog.Debug(fmt.Sprintf("Error saving post. user=%v, message=%v", post.UserId, post.Message)) - } - - for _, fileId := range post.FileIds { - if result := <-a.Srv.Store.FileInfo().AttachToPost(fileId, post.Id); result.Err != nil { - mlog.Error(fmt.Sprintf("Error attaching files to post. postId=%v, fileIds=%v, message=%v", post.Id, post.FileIds, result.Err), mlog.String("post_id", post.Id)) - } - } - - post.Id = "" - post.CreateAt++ - post.Message = remainder - } -} - -func (a *App) OldImportUser(team *model.Team, user *model.User) *model.User { - user.MakeNonNil() - - user.Roles = model.SYSTEM_USER_ROLE_ID - - if result := <-a.Srv.Store.User().Save(user); result.Err != nil { - mlog.Error(fmt.Sprintf("Error saving user. err=%v", result.Err)) - return nil - } else { - ruser := result.Data.(*model.User) - - if cresult := <-a.Srv.Store.User().VerifyEmail(ruser.Id); cresult.Err != nil { - mlog.Error(fmt.Sprintf("Failed to set email verified err=%v", cresult.Err)) - } - - if err := a.JoinUserToTeam(team, user, ""); err != nil { - mlog.Error(fmt.Sprintf("Failed to join team when importing err=%v", err)) - } - - return ruser - } -} - -func (a *App) OldImportChannel(channel *model.Channel) *model.Channel { - if result := <-a.Srv.Store.Channel().Save(channel, *a.Config().TeamSettings.MaxChannelsPerTeam); result.Err != nil { - return nil - } else { - sc := result.Data.(*model.Channel) - - return sc - } -} - -func (a *App) OldImportFile(timestamp time.Time, file io.Reader, teamId string, channelId string, userId string, fileName string) (*model.FileInfo, error) { - buf := bytes.NewBuffer(nil) - io.Copy(buf, file) - data := buf.Bytes() - - fileInfo, err := a.DoUploadFile(timestamp, teamId, channelId, userId, fileName, data) - if err != nil { - return nil, err - } - - if fileInfo.IsImage() && fileInfo.MimeType != "image/svg+xml" { - img, width, height := prepareImage(data) - if img != nil { - a.generateThumbnailImage(*img, fileInfo.ThumbnailPath, width, height) - a.generatePreviewImage(*img, fileInfo.PreviewPath, width) - } - } - - return fileInfo, nil -} - -func (a *App) OldImportIncomingWebhookPost(post *model.Post, props model.StringInterface) { - linkWithTextRegex := regexp.MustCompile(`<([^<\|]+)\|([^>]+)>`) - post.Message = linkWithTextRegex.ReplaceAllString(post.Message, "[${2}](${1})") - - post.AddProp("from_webhook", "true") - - if _, ok := props["override_username"]; !ok { - post.AddProp("override_username", model.DEFAULT_WEBHOOK_USERNAME) - } - - if len(props) > 0 { - for key, val := range props { - if key == "attachments" { - if attachments, success := val.([]*model.SlackAttachment); success { - parseSlackAttachment(post, attachments) - } - } else if key != "from_webhook" { - post.AddProp(key, val) - } - } - } - - a.OldImportPost(post) -} diff --git a/app/import_functions.go b/app/import_functions.go new file mode 100644 index 000000000..f4ff5607f --- /dev/null +++ b/app/import_functions.go @@ -0,0 +1,1218 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package app + +import ( + "bytes" + "fmt" + "io" + "net/http" + "os" + "strings" + + "github.com/mattermost/mattermost-server/mlog" + "github.com/mattermost/mattermost-server/model" + "github.com/mattermost/mattermost-server/store" + "github.com/mattermost/mattermost-server/utils" +) + +// +// -- Bulk Import Functions -- +// These functions import data directly into the database. Security and permission checks are bypassed but validity is +// still enforced. +// + +func (a *App) ImportScheme(data *SchemeImportData, dryRun bool) *model.AppError { + if err := validateSchemeImportData(data); err != nil { + return err + } + + // If this is a Dry Run, do not continue any further. + if dryRun { + return nil + } + + scheme, err := a.GetSchemeByName(*data.Name) + if err != nil { + scheme = new(model.Scheme) + } else if scheme.Scope != *data.Scope { + return model.NewAppError("BulkImport", "app.import.import_scheme.scope_change.error", map[string]interface{}{"SchemeName": scheme.Name}, "", http.StatusBadRequest) + } + + scheme.Name = *data.Name + scheme.DisplayName = *data.DisplayName + scheme.Scope = *data.Scope + + if data.Description != nil { + scheme.Description = *data.Description + } + + if len(scheme.Id) == 0 { + scheme, err = a.CreateScheme(scheme) + } else { + scheme, err = a.UpdateScheme(scheme) + } + + if err != nil { + return err + } + + if scheme.Scope == model.SCHEME_SCOPE_TEAM { + data.DefaultTeamAdminRole.Name = &scheme.DefaultTeamAdminRole + if err := a.ImportRole(data.DefaultTeamAdminRole, dryRun, true); err != nil { + return err + } + + data.DefaultTeamUserRole.Name = &scheme.DefaultTeamUserRole + if err := a.ImportRole(data.DefaultTeamUserRole, dryRun, true); err != nil { + return err + } + } + + if scheme.Scope == model.SCHEME_SCOPE_TEAM || scheme.Scope == model.SCHEME_SCOPE_CHANNEL { + data.DefaultChannelAdminRole.Name = &scheme.DefaultChannelAdminRole + if err := a.ImportRole(data.DefaultChannelAdminRole, dryRun, true); err != nil { + return err + } + + data.DefaultChannelUserRole.Name = &scheme.DefaultChannelUserRole + if err := a.ImportRole(data.DefaultChannelUserRole, dryRun, true); err != nil { + return err + } + } + + return nil +} + +func (a *App) ImportRole(data *RoleImportData, dryRun bool, isSchemeRole bool) *model.AppError { + if !isSchemeRole { + if err := validateRoleImportData(data); err != nil { + return err + } + } + + // If this is a Dry Run, do not continue any further. + if dryRun { + return nil + } + + role, err := a.GetRoleByName(*data.Name) + if err != nil { + role = new(model.Role) + } + + role.Name = *data.Name + + if data.DisplayName != nil { + role.DisplayName = *data.DisplayName + } + + if data.Description != nil { + role.Description = *data.Description + } + + if data.Permissions != nil { + role.Permissions = *data.Permissions + } + + if isSchemeRole { + role.SchemeManaged = true + } else { + role.SchemeManaged = false + } + + if len(role.Id) == 0 { + role, err = a.CreateRole(role) + } else { + role, err = a.UpdateRole(role) + } + + return err +} + +func (a *App) ImportTeam(data *TeamImportData, dryRun bool) *model.AppError { + if err := validateTeamImportData(data); err != nil { + return err + } + + // If this is a Dry Run, do not continue any further. + if dryRun { + return nil + } + + var team *model.Team + if result := <-a.Srv.Store.Team().GetByName(*data.Name); result.Err == nil { + team = result.Data.(*model.Team) + } else { + team = &model.Team{} + } + + team.Name = *data.Name + team.DisplayName = *data.DisplayName + team.Type = *data.Type + + if data.Description != nil { + team.Description = *data.Description + } + + if data.AllowOpenInvite != nil { + team.AllowOpenInvite = *data.AllowOpenInvite + } + + if data.Scheme != nil { + scheme, err := a.GetSchemeByName(*data.Scheme) + if err != nil { + return err + } + + if scheme.DeleteAt != 0 { + return model.NewAppError("BulkImport", "app.import.import_team.scheme_deleted.error", nil, "", http.StatusBadRequest) + } + + if scheme.Scope != model.SCHEME_SCOPE_TEAM { + return model.NewAppError("BulkImport", "app.import.import_team.scheme_wrong_scope.error", nil, "", http.StatusBadRequest) + } + + team.SchemeId = &scheme.Id + } + + if team.Id == "" { + if _, err := a.CreateTeam(team); err != nil { + return err + } + } else { + if _, err := a.updateTeamUnsanitized(team); err != nil { + return err + } + } + + return nil +} + +func (a *App) ImportChannel(data *ChannelImportData, dryRun bool) *model.AppError { + if err := validateChannelImportData(data); err != nil { + return err + } + + // If this is a Dry Run, do not continue any further. + if dryRun { + return nil + } + + var team *model.Team + if result := <-a.Srv.Store.Team().GetByName(*data.Team); result.Err != nil { + return model.NewAppError("BulkImport", "app.import.import_channel.team_not_found.error", map[string]interface{}{"TeamName": *data.Team}, result.Err.Error(), http.StatusBadRequest) + } else { + team = result.Data.(*model.Team) + } + + var channel *model.Channel + if result := <-a.Srv.Store.Channel().GetByNameIncludeDeleted(team.Id, *data.Name, true); result.Err == nil { + channel = result.Data.(*model.Channel) + } else { + channel = &model.Channel{} + } + + channel.TeamId = team.Id + channel.Name = *data.Name + channel.DisplayName = *data.DisplayName + channel.Type = *data.Type + + if data.Header != nil { + channel.Header = *data.Header + } + + if data.Purpose != nil { + channel.Purpose = *data.Purpose + } + + if data.Scheme != nil { + scheme, err := a.GetSchemeByName(*data.Scheme) + if err != nil { + return err + } + + if scheme.DeleteAt != 0 { + return model.NewAppError("BulkImport", "app.import.import_channel.scheme_deleted.error", nil, "", http.StatusBadRequest) + } + + if scheme.Scope != model.SCHEME_SCOPE_CHANNEL { + return model.NewAppError("BulkImport", "app.import.import_channel.scheme_wrong_scope.error", nil, "", http.StatusBadRequest) + } + + channel.SchemeId = &scheme.Id + } + + if channel.Id == "" { + if _, err := a.CreateChannel(channel, false); err != nil { + return err + } + } else { + if _, err := a.UpdateChannel(channel); err != nil { + return err + } + } + + return nil +} + +func (a *App) ImportUser(data *UserImportData, dryRun bool) *model.AppError { + if err := validateUserImportData(data); err != nil { + return err + } + + // If this is a Dry Run, do not continue any further. + if dryRun { + return nil + } + + // We want to avoid database writes if nothing has changed. + hasUserChanged := false + hasNotifyPropsChanged := false + hasUserRolesChanged := false + hasUserAuthDataChanged := false + hasUserEmailVerifiedChanged := false + + var user *model.User + if result := <-a.Srv.Store.User().GetByUsername(*data.Username); result.Err == nil { + user = result.Data.(*model.User) + } else { + user = &model.User{} + user.MakeNonNil() + hasUserChanged = true + } + + user.Username = *data.Username + + if user.Email != *data.Email { + hasUserChanged = true + hasUserEmailVerifiedChanged = true // Changing the email resets email verified to false by default. + user.Email = *data.Email + } + + var password string + var authService string + var authData *string + + if data.AuthService != nil { + if user.AuthService != *data.AuthService { + hasUserAuthDataChanged = true + } + authService = *data.AuthService + } + + // AuthData and Password are mutually exclusive. + if data.AuthData != nil { + if user.AuthData == nil || *user.AuthData != *data.AuthData { + hasUserAuthDataChanged = true + } + authData = data.AuthData + password = "" + } else if data.Password != nil { + password = *data.Password + authData = nil + } else { + // If no AuthData or Password is specified, we must generate a password. + password = model.NewId() + authData = nil + } + + user.Password = password + user.AuthService = authService + user.AuthData = authData + + // Automatically assume all emails are verified. + emailVerified := true + if user.EmailVerified != emailVerified { + user.EmailVerified = emailVerified + hasUserEmailVerifiedChanged = true + } + + if data.Nickname != nil { + if user.Nickname != *data.Nickname { + user.Nickname = *data.Nickname + hasUserChanged = true + } + } + + if data.FirstName != nil { + if user.FirstName != *data.FirstName { + user.FirstName = *data.FirstName + hasUserChanged = true + } + } + + if data.LastName != nil { + if user.LastName != *data.LastName { + user.LastName = *data.LastName + hasUserChanged = true + } + } + + if data.Position != nil { + if user.Position != *data.Position { + user.Position = *data.Position + hasUserChanged = true + } + } + + if data.Locale != nil { + if user.Locale != *data.Locale { + user.Locale = *data.Locale + hasUserChanged = true + } + } else { + if user.Locale != *a.Config().LocalizationSettings.DefaultClientLocale { + user.Locale = *a.Config().LocalizationSettings.DefaultClientLocale + hasUserChanged = true + } + } + + var roles string + if data.Roles != nil { + if user.Roles != *data.Roles { + roles = *data.Roles + hasUserRolesChanged = true + } + } else if len(user.Roles) == 0 { + // Set SYSTEM_USER roles on newly created users by default. + if user.Roles != model.SYSTEM_USER_ROLE_ID { + roles = model.SYSTEM_USER_ROLE_ID + hasUserRolesChanged = true + } + } + user.Roles = roles + + if data.NotifyProps != nil { + if data.NotifyProps.Desktop != nil { + if value, ok := user.NotifyProps[model.DESKTOP_NOTIFY_PROP]; !ok || value != *data.NotifyProps.Desktop { + user.AddNotifyProp(model.DESKTOP_NOTIFY_PROP, *data.NotifyProps.Desktop) + hasNotifyPropsChanged = true + } + } + + if data.NotifyProps.DesktopSound != nil { + if value, ok := user.NotifyProps[model.DESKTOP_SOUND_NOTIFY_PROP]; !ok || value != *data.NotifyProps.DesktopSound { + user.AddNotifyProp(model.DESKTOP_SOUND_NOTIFY_PROP, *data.NotifyProps.DesktopSound) + hasNotifyPropsChanged = true + } + } + + if data.NotifyProps.Email != nil { + if value, ok := user.NotifyProps[model.EMAIL_NOTIFY_PROP]; !ok || value != *data.NotifyProps.Email { + user.AddNotifyProp(model.EMAIL_NOTIFY_PROP, *data.NotifyProps.Email) + hasNotifyPropsChanged = true + } + } + + if data.NotifyProps.Mobile != nil { + if value, ok := user.NotifyProps[model.PUSH_NOTIFY_PROP]; !ok || value != *data.NotifyProps.Mobile { + user.AddNotifyProp(model.PUSH_NOTIFY_PROP, *data.NotifyProps.Mobile) + hasNotifyPropsChanged = true + } + } + + if data.NotifyProps.MobilePushStatus != nil { + if value, ok := user.NotifyProps[model.PUSH_STATUS_NOTIFY_PROP]; !ok || value != *data.NotifyProps.MobilePushStatus { + user.AddNotifyProp(model.PUSH_STATUS_NOTIFY_PROP, *data.NotifyProps.MobilePushStatus) + hasNotifyPropsChanged = true + } + } + + if data.NotifyProps.ChannelTrigger != nil { + if value, ok := user.NotifyProps[model.CHANNEL_MENTIONS_NOTIFY_PROP]; !ok || value != *data.NotifyProps.ChannelTrigger { + user.AddNotifyProp(model.CHANNEL_MENTIONS_NOTIFY_PROP, *data.NotifyProps.ChannelTrigger) + hasNotifyPropsChanged = true + } + } + + if data.NotifyProps.CommentsTrigger != nil { + if value, ok := user.NotifyProps[model.COMMENTS_NOTIFY_PROP]; !ok || value != *data.NotifyProps.CommentsTrigger { + user.AddNotifyProp(model.COMMENTS_NOTIFY_PROP, *data.NotifyProps.CommentsTrigger) + hasNotifyPropsChanged = true + } + } + + if data.NotifyProps.MentionKeys != nil { + if value, ok := user.NotifyProps[model.MENTION_KEYS_NOTIFY_PROP]; !ok || value != *data.NotifyProps.MentionKeys { + user.AddNotifyProp(model.MENTION_KEYS_NOTIFY_PROP, *data.NotifyProps.MentionKeys) + hasNotifyPropsChanged = true + } + } + } + + var err *model.AppError + var savedUser *model.User + if user.Id == "" { + if savedUser, err = a.createUser(user); err != nil { + return err + } + } else { + if hasUserChanged { + if savedUser, err = a.UpdateUser(user, false); err != nil { + return err + } + } + if hasUserRolesChanged { + if savedUser, err = a.UpdateUserRoles(user.Id, roles, false); err != nil { + return err + } + } + if hasNotifyPropsChanged { + if savedUser, err = a.UpdateUserNotifyProps(user.Id, user.NotifyProps); err != nil { + return err + } + } + if len(password) > 0 { + if err = a.UpdatePassword(user, password); err != nil { + return err + } + } else { + if hasUserAuthDataChanged { + if res := <-a.Srv.Store.User().UpdateAuthData(user.Id, authService, authData, user.Email, false); res.Err != nil { + return res.Err + } + } + } + if emailVerified { + if hasUserEmailVerifiedChanged { + if err := a.VerifyUserEmail(user.Id); err != nil { + return err + } + } + } + } + + if savedUser == nil { + savedUser = user + } + + if data.ProfileImage != nil { + file, err := os.Open(*data.ProfileImage) + if err != nil { + mlog.Error(fmt.Sprint("api.import.import_user.profile_image.error FIXME: NOT FOUND IN TRANSLATIONS FILE", err)) + } + if err := a.SetProfileImageFromFile(savedUser.Id, file); err != nil { + mlog.Error(fmt.Sprint("api.import.import_user.profile_image.error FIXME: NOT FOUND IN TRANSLATIONS FILE", err)) + } + } + + // Preferences. + var preferences model.Preferences + + if data.Theme != nil { + preferences = append(preferences, model.Preference{ + UserId: savedUser.Id, + Category: model.PREFERENCE_CATEGORY_THEME, + Name: "", + Value: *data.Theme, + }) + } + + if data.UseMilitaryTime != nil { + preferences = append(preferences, model.Preference{ + UserId: savedUser.Id, + Category: model.PREFERENCE_CATEGORY_DISPLAY_SETTINGS, + Name: "use_military_time", + Value: *data.UseMilitaryTime, + }) + } + + if data.CollapsePreviews != nil { + preferences = append(preferences, model.Preference{ + UserId: savedUser.Id, + Category: model.PREFERENCE_CATEGORY_DISPLAY_SETTINGS, + Name: "collapse_previews", + Value: *data.CollapsePreviews, + }) + } + + if data.MessageDisplay != nil { + preferences = append(preferences, model.Preference{ + UserId: savedUser.Id, + Category: model.PREFERENCE_CATEGORY_DISPLAY_SETTINGS, + Name: "message_display", + Value: *data.MessageDisplay, + }) + } + + if data.ChannelDisplayMode != nil { + preferences = append(preferences, model.Preference{ + UserId: savedUser.Id, + Category: model.PREFERENCE_CATEGORY_DISPLAY_SETTINGS, + Name: "channel_display_mode", + Value: *data.ChannelDisplayMode, + }) + } + + if data.TutorialStep != nil { + preferences = append(preferences, model.Preference{ + UserId: savedUser.Id, + Category: model.PREFERENCE_CATEGORY_TUTORIAL_STEPS, + Name: savedUser.Id, + Value: *data.TutorialStep, + }) + } + + if len(preferences) > 0 { + if result := <-a.Srv.Store.Preference().Save(&preferences); result.Err != nil { + return model.NewAppError("BulkImport", "app.import.import_user.save_preferences.error", nil, result.Err.Error(), http.StatusInternalServerError) + } + } + + return a.ImportUserTeams(savedUser, data.Teams) +} + +func (a *App) ImportUserTeams(user *model.User, data *[]UserTeamImportData) *model.AppError { + if data == nil { + return nil + } + + for _, tdata := range *data { + team, err := a.GetTeamByName(*tdata.Name) + if err != nil { + return err + } + + var roles string + isSchemeUser := true + isSchemeAdmin := false + + if tdata.Roles == nil { + isSchemeUser = true + } else { + rawRoles := *tdata.Roles + explicitRoles := []string{} + for _, role := range strings.Fields(rawRoles) { + if role == model.TEAM_USER_ROLE_ID { + isSchemeUser = true + } else if role == model.TEAM_ADMIN_ROLE_ID { + isSchemeAdmin = true + } else { + explicitRoles = append(explicitRoles, role) + } + } + roles = strings.Join(explicitRoles, " ") + } + + var member *model.TeamMember + if member, _, err = a.joinUserToTeam(team, user); err != nil { + return err + } + + if member.ExplicitRoles != roles { + if _, err := a.UpdateTeamMemberRoles(team.Id, user.Id, roles); err != nil { + return err + } + } + + if member.SchemeAdmin != isSchemeAdmin || member.SchemeUser != isSchemeUser { + a.UpdateTeamMemberSchemeRoles(team.Id, user.Id, isSchemeUser, isSchemeAdmin) + } + + if defaultChannel, err := a.GetChannelByName(model.DEFAULT_CHANNEL, team.Id, true); err != nil { + return err + } else if _, err = a.addUserToChannel(user, defaultChannel, member); err != nil { + return err + } + + if err := a.ImportUserChannels(user, team, member, tdata.Channels); err != nil { + return err + } + } + + return nil +} + +func (a *App) ImportUserChannels(user *model.User, team *model.Team, teamMember *model.TeamMember, data *[]UserChannelImportData) *model.AppError { + if data == nil { + return nil + } + + var preferences model.Preferences + + // Loop through all channels. + for _, cdata := range *data { + channel, err := a.GetChannelByName(*cdata.Name, team.Id, true) + if err != nil { + return err + } + + var roles string + isSchemeUser := true + isSchemeAdmin := false + + if cdata.Roles == nil { + isSchemeUser = true + } else { + rawRoles := *cdata.Roles + explicitRoles := []string{} + for _, role := range strings.Fields(rawRoles) { + if role == model.CHANNEL_USER_ROLE_ID { + isSchemeUser = true + } else if role == model.CHANNEL_ADMIN_ROLE_ID { + isSchemeAdmin = 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 { + a.UpdateChannelMemberSchemeRoles(channel.Id, user.Id, 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 + } + } + + if cdata.Favorite != nil && *cdata.Favorite { + preferences = append(preferences, model.Preference{ + UserId: user.Id, + Category: model.PREFERENCE_CATEGORY_FAVORITE_CHANNEL, + Name: channel.Id, + Value: "true", + }) + } + } + + if len(preferences) > 0 { + if result := <-a.Srv.Store.Preference().Save(&preferences); result.Err != nil { + return model.NewAppError("BulkImport", "app.import.import_user_channels.save_preferences.error", nil, result.Err.Error(), http.StatusInternalServerError) + } + } + + return nil +} + +func (a *App) ImportReaction(data *ReactionImportData, post *model.Post, dryRun bool) *model.AppError { + if err := validateReactionImportData(data, post.CreateAt); err != nil { + return err + } + + var user *model.User + if result := <-a.Srv.Store.User().GetByUsername(*data.User); result.Err != nil { + return model.NewAppError("BulkImport", "app.import.import_post.user_not_found.error", map[string]interface{}{"Username": data.User}, result.Err.Error(), http.StatusBadRequest) + } else { + user = result.Data.(*model.User) + } + reaction := &model.Reaction{ + UserId: user.Id, + PostId: post.Id, + EmojiName: *data.EmojiName, + CreateAt: *data.CreateAt, + } + if result := <-a.Srv.Store.Reaction().Save(reaction); result.Err != nil { + return result.Err + } + return nil +} + +func (a *App) ImportReply(data *ReplyImportData, post *model.Post, teamId string, dryRun bool) *model.AppError { + if err := validateReplyImportData(data, post.CreateAt, a.MaxPostSize()); err != nil { + return err + } + + var user *model.User + if result := <-a.Srv.Store.User().GetByUsername(*data.User); result.Err != nil { + return model.NewAppError("BulkImport", "app.import.import_post.user_not_found.error", map[string]interface{}{"Username": data.User}, result.Err.Error(), http.StatusBadRequest) + } else { + user = result.Data.(*model.User) + } + + // Check if this post already exists. + var replies []*model.Post + if result := <-a.Srv.Store.Post().GetPostsCreatedAt(post.ChannelId, *data.CreateAt); result.Err != nil { + return result.Err + } else { + replies = result.Data.([]*model.Post) + } + + var reply *model.Post + for _, r := range replies { + if r.Message == *data.Message { + reply = r + break + } + } + + if reply == nil { + reply = &model.Post{} + } + reply.UserId = user.Id + reply.ChannelId = post.ChannelId + reply.ParentId = post.Id + reply.RootId = post.Id + reply.Message = *data.Message + reply.CreateAt = *data.CreateAt + + if data.Attachments != nil { + fileIds, err := a.uploadAttachments(data.Attachments, reply, teamId, dryRun) + if err != nil { + return err + } + reply.FileIds = fileIds + } + + if reply.Id == "" { + if result := <-a.Srv.Store.Post().Save(reply); result.Err != nil { + return result.Err + } + } else { + if result := <-a.Srv.Store.Post().Overwrite(reply); result.Err != nil { + return result.Err + } + } + + a.UpdateFileInfoWithPostId(reply) + + return nil +} + +func (a *App) ImportAttachment(data *AttachmentImportData, post *model.Post, teamId string, dryRun bool) (*model.FileInfo, *model.AppError) { + fileUploadError := model.NewAppError("BulkImport", "app.import.attachment.file_upload.error", map[string]interface{}{"FilePath": *data.Path}, "", http.StatusBadRequest) + file, err := os.Open(*data.Path) + if err != nil { + return nil, model.NewAppError("BulkImport", "app.import.attachment.bad_file.error", map[string]interface{}{"FilePath": *data.Path}, "", http.StatusBadRequest) + } + if file != nil { + timestamp := utils.TimeFromMillis(post.CreateAt) + buf := bytes.NewBuffer(nil) + io.Copy(buf, file) + + fileInfo, err := a.DoUploadFile(timestamp, teamId, post.ChannelId, post.UserId, file.Name(), buf.Bytes()) + + if err != nil { + fmt.Print(err) + return nil, fileUploadError + } + + mlog.Info(fmt.Sprintf("uploading file with name %s", file.Name())) + return fileInfo, nil + } + return nil, fileUploadError +} + +func (a *App) ImportPost(data *PostImportData, dryRun bool) *model.AppError { + if err := validatePostImportData(data, a.MaxPostSize()); err != nil { + return err + } + + // If this is a Dry Run, do not continue any further. + if dryRun { + return nil + } + + var team *model.Team + if result := <-a.Srv.Store.Team().GetByName(*data.Team); result.Err != nil { + return model.NewAppError("BulkImport", "app.import.import_post.team_not_found.error", map[string]interface{}{"TeamName": *data.Team}, result.Err.Error(), http.StatusBadRequest) + } else { + team = result.Data.(*model.Team) + } + + var channel *model.Channel + if result := <-a.Srv.Store.Channel().GetByName(team.Id, *data.Channel, false); result.Err != nil { + return model.NewAppError("BulkImport", "app.import.import_post.channel_not_found.error", map[string]interface{}{"ChannelName": *data.Channel}, result.Err.Error(), http.StatusBadRequest) + } else { + channel = result.Data.(*model.Channel) + } + + var user *model.User + if result := <-a.Srv.Store.User().GetByUsername(*data.User); result.Err != nil { + return model.NewAppError("BulkImport", "app.import.import_post.user_not_found.error", map[string]interface{}{"Username": *data.User}, result.Err.Error(), http.StatusBadRequest) + } else { + user = result.Data.(*model.User) + } + + // Check if this post already exists. + var posts []*model.Post + if result := <-a.Srv.Store.Post().GetPostsCreatedAt(channel.Id, *data.CreateAt); result.Err != nil { + return result.Err + } else { + posts = result.Data.([]*model.Post) + } + + var post *model.Post + for _, p := range posts { + if p.Message == *data.Message { + post = p + break + } + } + + if post == nil { + post = &model.Post{} + } + + post.ChannelId = channel.Id + post.Message = *data.Message + post.UserId = user.Id + post.CreateAt = *data.CreateAt + + post.Hashtags, _ = model.ParseHashtags(post.Message) + + if data.Attachments != nil { + fileIds, err := a.uploadAttachments(data.Attachments, post, team.Id, dryRun) + if err != nil { + return err + } + post.FileIds = fileIds + } + + if post.Id == "" { + if result := <-a.Srv.Store.Post().Save(post); result.Err != nil { + return result.Err + } + } else { + if result := <-a.Srv.Store.Post().Overwrite(post); result.Err != nil { + return result.Err + } + } + + if data.FlaggedBy != nil { + var preferences model.Preferences + + for _, username := range *data.FlaggedBy { + var user *model.User + + if result := <-a.Srv.Store.User().GetByUsername(username); result.Err != nil { + return model.NewAppError("BulkImport", "app.import.import_post.user_not_found.error", map[string]interface{}{"Username": username}, result.Err.Error(), http.StatusBadRequest) + } else { + user = result.Data.(*model.User) + } + + preferences = append(preferences, model.Preference{ + UserId: user.Id, + Category: model.PREFERENCE_CATEGORY_FLAGGED_POST, + Name: post.Id, + Value: "true", + }) + } + + if len(preferences) > 0 { + if result := <-a.Srv.Store.Preference().Save(&preferences); result.Err != nil { + return model.NewAppError("BulkImport", "app.import.import_post.save_preferences.error", nil, result.Err.Error(), http.StatusInternalServerError) + } + } + } + + if data.Reactions != nil { + for _, reaction := range *data.Reactions { + if err := a.ImportReaction(&reaction, post, dryRun); err != nil { + return err + } + } + } + + if data.Replies != nil { + for _, reply := range *data.Replies { + if err := a.ImportReply(&reply, post, team.Id, dryRun); err != nil { + return err + } + } + } + + a.UpdateFileInfoWithPostId(post) + return nil +} + +func (a *App) uploadAttachments(attachments *[]AttachmentImportData, post *model.Post, teamId string, dryRun bool) ([]string, *model.AppError) { + fileIds := []string{} + for _, attachment := range *attachments { + fileInfo, err := a.ImportAttachment(&attachment, post, teamId, dryRun) + if err != nil { + return nil, err + } + fileIds = append(fileIds, fileInfo.Id) + } + return fileIds, nil +} + +func (a *App) UpdateFileInfoWithPostId(post *model.Post) { + for _, fileId := range post.FileIds { + if result := <-a.Srv.Store.FileInfo().AttachToPost(fileId, post.Id); result.Err != nil { + mlog.Error(fmt.Sprintf("Error attaching files to post. postId=%v, fileIds=%v, message=%v", post.Id, post.FileIds, result.Err), mlog.String("post_id", post.Id)) + } + } +} +func (a *App) ImportDirectChannel(data *DirectChannelImportData, dryRun bool) *model.AppError { + if err := validateDirectChannelImportData(data); err != nil { + return err + } + + // If this is a Dry Run, do not continue any further. + if dryRun { + return nil + } + + var userIds []string + userMap := make(map[string]string) + for _, username := range *data.Members { + if result := <-a.Srv.Store.User().GetByUsername(username); result.Err == nil { + user := result.Data.(*model.User) + userIds = append(userIds, user.Id) + userMap[username] = user.Id + } else { + return model.NewAppError("BulkImport", "app.import.import_direct_channel.member_not_found.error", nil, result.Err.Error(), http.StatusBadRequest) + } + } + + var channel *model.Channel + + if len(userIds) == 2 { + ch, err := a.createDirectChannel(userIds[0], userIds[1]) + if err != nil && err.Id != store.CHANNEL_EXISTS_ERROR { + return model.NewAppError("BulkImport", "app.import.import_direct_channel.create_direct_channel.error", nil, err.Error(), http.StatusBadRequest) + } else { + channel = ch + } + } else { + ch, err := a.createGroupChannel(userIds, userIds[0]) + if err != nil && err.Id != store.CHANNEL_EXISTS_ERROR { + return model.NewAppError("BulkImport", "app.import.import_direct_channel.create_group_channel.error", nil, err.Error(), http.StatusBadRequest) + } else { + channel = ch + } + } + + var preferences model.Preferences + + for _, userId := range userIds { + preferences = append(preferences, model.Preference{ + UserId: userId, + Category: model.PREFERENCE_CATEGORY_DIRECT_CHANNEL_SHOW, + Name: channel.Id, + Value: "true", + }) + } + + if data.FavoritedBy != nil { + for _, favoriter := range *data.FavoritedBy { + preferences = append(preferences, model.Preference{ + UserId: userMap[favoriter], + Category: model.PREFERENCE_CATEGORY_FAVORITE_CHANNEL, + Name: channel.Id, + Value: "true", + }) + } + } + + if result := <-a.Srv.Store.Preference().Save(&preferences); result.Err != nil { + result.Err.StatusCode = http.StatusBadRequest + return result.Err + } + + if data.Header != nil { + channel.Header = *data.Header + if result := <-a.Srv.Store.Channel().Update(channel); result.Err != nil { + return model.NewAppError("BulkImport", "app.import.import_direct_channel.update_header_failed.error", nil, result.Err.Error(), http.StatusBadRequest) + } + } + + return nil +} + +func (a *App) ImportDirectPost(data *DirectPostImportData, dryRun bool) *model.AppError { + if err := validateDirectPostImportData(data, a.MaxPostSize()); err != nil { + return err + } + + // If this is a Dry Run, do not continue any further. + if dryRun { + return nil + } + + var userIds []string + for _, username := range *data.ChannelMembers { + if result := <-a.Srv.Store.User().GetByUsername(username); result.Err == nil { + user := result.Data.(*model.User) + userIds = append(userIds, user.Id) + } else { + return model.NewAppError("BulkImport", "app.import.import_direct_post.channel_member_not_found.error", nil, result.Err.Error(), http.StatusBadRequest) + } + } + + var channel *model.Channel + if len(userIds) == 2 { + ch, err := a.createDirectChannel(userIds[0], userIds[1]) + if err != nil && err.Id != store.CHANNEL_EXISTS_ERROR { + return model.NewAppError("BulkImport", "app.import.import_direct_post.create_direct_channel.error", nil, err.Error(), http.StatusBadRequest) + } else { + channel = ch + } + } else { + ch, err := a.createGroupChannel(userIds, userIds[0]) + if err != nil && err.Id != store.CHANNEL_EXISTS_ERROR { + return model.NewAppError("BulkImport", "app.import.import_direct_post.create_group_channel.error", nil, err.Error(), http.StatusBadRequest) + } else { + channel = ch + } + } + + var user *model.User + if result := <-a.Srv.Store.User().GetByUsername(*data.User); result.Err != nil { + return model.NewAppError("BulkImport", "app.import.import_direct_post.user_not_found.error", map[string]interface{}{"Username": *data.User}, "", http.StatusBadRequest) + } else { + user = result.Data.(*model.User) + } + + // Check if this post already exists. + var posts []*model.Post + if result := <-a.Srv.Store.Post().GetPostsCreatedAt(channel.Id, *data.CreateAt); result.Err != nil { + return result.Err + } else { + posts = result.Data.([]*model.Post) + } + + var post *model.Post + for _, p := range posts { + if p.Message == *data.Message { + post = p + break + } + } + + if post == nil { + post = &model.Post{} + } + + post.ChannelId = channel.Id + post.Message = *data.Message + post.UserId = user.Id + post.CreateAt = *data.CreateAt + + post.Hashtags, _ = model.ParseHashtags(post.Message) + + if post.Id == "" { + if result := <-a.Srv.Store.Post().Save(post); result.Err != nil { + return result.Err + } + } else { + if result := <-a.Srv.Store.Post().Overwrite(post); result.Err != nil { + return result.Err + } + } + + if data.FlaggedBy != nil { + var preferences model.Preferences + + for _, username := range *data.FlaggedBy { + var user *model.User + + if result := <-a.Srv.Store.User().GetByUsername(username); result.Err != nil { + return model.NewAppError("BulkImport", "app.import.import_direct_post.user_not_found.error", map[string]interface{}{"Username": username}, "", http.StatusBadRequest) + } else { + user = result.Data.(*model.User) + } + + preferences = append(preferences, model.Preference{ + UserId: user.Id, + Category: model.PREFERENCE_CATEGORY_FLAGGED_POST, + Name: post.Id, + Value: "true", + }) + } + + if len(preferences) > 0 { + if result := <-a.Srv.Store.Preference().Save(&preferences); result.Err != nil { + return model.NewAppError("BulkImport", "app.import.import_direct_post.save_preferences.error", nil, result.Err.Error(), http.StatusInternalServerError) + } + } + } + + if data.Reactions != nil { + for _, reaction := range *data.Reactions { + if err := a.ImportReaction(&reaction, post, dryRun); err != nil { + return err + } + } + } + + if data.Replies != nil { + for _, reply := range *data.Replies { + if err := a.ImportReply(&reply, post, "noteam", dryRun); err != nil { + return err + } + } + } + + return nil +} + +func (a *App) ImportEmoji(data *EmojiImportData, dryRun bool) *model.AppError { + if err := validateEmojiImportData(data); err != nil { + return err + } + + // If this is a Dry Run, do not continue any further. + if dryRun { + return nil + } + + var emoji *model.Emoji + + if result := <-a.Srv.Store.Emoji().GetByName(*data.Name); result.Err != nil && result.Err.StatusCode != http.StatusNotFound { + return result.Err + } else if result.Data != nil { + emoji = result.Data.(*model.Emoji) + } + + alreadyExists := emoji != nil + + if !alreadyExists { + emoji = &model.Emoji{ + Name: *data.Name, + } + emoji.PreSave() + } + + file, err := os.Open(*data.Image) + if err != nil { + return model.NewAppError("BulkImport", "app.import.emoji.bad_file.error", map[string]interface{}{"EmojiName": *data.Name}, "", http.StatusBadRequest) + } + + if _, err := a.WriteFile(file, getEmojiImagePath(emoji.Id)); err != nil { + return err + } + + if !alreadyExists { + if result := <-a.Srv.Store.Emoji().Save(emoji); result.Err != nil { + return result.Err + } + } + + return nil +} diff --git a/app/import_functions_test.go b/app/import_functions_test.go new file mode 100644 index 000000000..e0859af45 --- /dev/null +++ b/app/import_functions_test.go @@ -0,0 +1,2501 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package app + +import ( + "path/filepath" + "strings" + "testing" + + "github.com/mattermost/mattermost-server/model" + "github.com/mattermost/mattermost-server/store" + "github.com/mattermost/mattermost-server/utils" + "github.com/stretchr/testify/assert" +) + +func TestImportImportScheme(t *testing.T) { + th := Setup() + defer th.TearDown() + + // Mark the phase 2 permissions migration as completed. + <-th.App.Srv.Store.System().Save(&model.System{Name: model.MIGRATION_KEY_ADVANCED_PERMISSIONS_PHASE_2, Value: "true"}) + + defer func() { + <-th.App.Srv.Store.System().PermanentDeleteByName(model.MIGRATION_KEY_ADVANCED_PERMISSIONS_PHASE_2) + }() + + // Try importing an invalid scheme in dryRun mode. + data := SchemeImportData{ + Name: ptrStr(model.NewId()), + Scope: ptrStr("team"), + DefaultTeamUserRole: &RoleImportData{ + Name: ptrStr(model.NewId()), + DisplayName: ptrStr(model.NewId()), + }, + DefaultTeamAdminRole: &RoleImportData{ + Name: ptrStr(model.NewId()), + DisplayName: ptrStr(model.NewId()), + }, + DefaultChannelUserRole: &RoleImportData{ + Name: ptrStr(model.NewId()), + DisplayName: ptrStr(model.NewId()), + }, + DefaultChannelAdminRole: &RoleImportData{ + Name: ptrStr(model.NewId()), + DisplayName: ptrStr(model.NewId()), + }, + Description: ptrStr("description"), + } + + if err := th.App.ImportScheme(&data, true); err == nil { + t.Fatalf("Should have failed to import.") + } + + if res := <-th.App.Srv.Store.Scheme().GetByName(*data.Name); res.Err == nil { + t.Fatalf("Scheme should not have imported.") + } + + // Try importing a valid scheme in dryRun mode. + data.DisplayName = ptrStr("display name") + + if err := th.App.ImportScheme(&data, true); err != nil { + t.Fatalf("Should have succeeded.") + } + + if res := <-th.App.Srv.Store.Scheme().GetByName(*data.Name); res.Err == nil { + t.Fatalf("Scheme should not have imported.") + } + + // Try importing an invalid scheme. + data.DisplayName = nil + + if err := th.App.ImportScheme(&data, false); err == nil { + t.Fatalf("Should have failed to import.") + } + + if res := <-th.App.Srv.Store.Scheme().GetByName(*data.Name); res.Err == nil { + t.Fatalf("Scheme should not have imported.") + } + + // Try importing a valid scheme with all params set. + data.DisplayName = ptrStr("display name") + + if err := th.App.ImportScheme(&data, false); err != nil { + t.Fatalf("Should have succeeded.") + } + + if res := <-th.App.Srv.Store.Scheme().GetByName(*data.Name); res.Err != nil { + t.Fatalf("Failed to import scheme: %v", res.Err) + } else { + scheme := res.Data.(*model.Scheme) + assert.Equal(t, *data.Name, scheme.Name) + assert.Equal(t, *data.DisplayName, scheme.DisplayName) + assert.Equal(t, *data.Description, scheme.Description) + assert.Equal(t, *data.Scope, scheme.Scope) + + if res := <-th.App.Srv.Store.Role().GetByName(scheme.DefaultTeamAdminRole); res.Err != nil { + t.Fatalf("Should have found the imported role.") + } else { + role := res.Data.(*model.Role) + assert.Equal(t, *data.DefaultTeamAdminRole.DisplayName, role.DisplayName) + assert.False(t, role.BuiltIn) + assert.True(t, role.SchemeManaged) + } + + if res := <-th.App.Srv.Store.Role().GetByName(scheme.DefaultTeamUserRole); res.Err != nil { + t.Fatalf("Should have found the imported role.") + } else { + role := res.Data.(*model.Role) + assert.Equal(t, *data.DefaultTeamUserRole.DisplayName, role.DisplayName) + assert.False(t, role.BuiltIn) + assert.True(t, role.SchemeManaged) + } + + if res := <-th.App.Srv.Store.Role().GetByName(scheme.DefaultChannelAdminRole); res.Err != nil { + t.Fatalf("Should have found the imported role.") + } else { + role := res.Data.(*model.Role) + assert.Equal(t, *data.DefaultChannelAdminRole.DisplayName, role.DisplayName) + assert.False(t, role.BuiltIn) + assert.True(t, role.SchemeManaged) + } + + if res := <-th.App.Srv.Store.Role().GetByName(scheme.DefaultChannelUserRole); res.Err != nil { + t.Fatalf("Should have found the imported role.") + } else { + role := res.Data.(*model.Role) + assert.Equal(t, *data.DefaultChannelUserRole.DisplayName, role.DisplayName) + assert.False(t, role.BuiltIn) + assert.True(t, role.SchemeManaged) + } + } + + // Try modifying all the fields and re-importing. + data.DisplayName = ptrStr("new display name") + data.Description = ptrStr("new description") + + if err := th.App.ImportScheme(&data, false); err != nil { + t.Fatalf("Should have succeeded: %v", err) + } + + if res := <-th.App.Srv.Store.Scheme().GetByName(*data.Name); res.Err != nil { + t.Fatalf("Failed to import scheme: %v", res.Err) + } else { + scheme := res.Data.(*model.Scheme) + assert.Equal(t, *data.Name, scheme.Name) + assert.Equal(t, *data.DisplayName, scheme.DisplayName) + assert.Equal(t, *data.Description, scheme.Description) + assert.Equal(t, *data.Scope, scheme.Scope) + + if res := <-th.App.Srv.Store.Role().GetByName(scheme.DefaultTeamAdminRole); res.Err != nil { + t.Fatalf("Should have found the imported role.") + } else { + role := res.Data.(*model.Role) + assert.Equal(t, *data.DefaultTeamAdminRole.DisplayName, role.DisplayName) + assert.False(t, role.BuiltIn) + assert.True(t, role.SchemeManaged) + } + + if res := <-th.App.Srv.Store.Role().GetByName(scheme.DefaultTeamUserRole); res.Err != nil { + t.Fatalf("Should have found the imported role.") + } else { + role := res.Data.(*model.Role) + assert.Equal(t, *data.DefaultTeamUserRole.DisplayName, role.DisplayName) + assert.False(t, role.BuiltIn) + assert.True(t, role.SchemeManaged) + } + + if res := <-th.App.Srv.Store.Role().GetByName(scheme.DefaultChannelAdminRole); res.Err != nil { + t.Fatalf("Should have found the imported role.") + } else { + role := res.Data.(*model.Role) + assert.Equal(t, *data.DefaultChannelAdminRole.DisplayName, role.DisplayName) + assert.False(t, role.BuiltIn) + assert.True(t, role.SchemeManaged) + } + + if res := <-th.App.Srv.Store.Role().GetByName(scheme.DefaultChannelUserRole); res.Err != nil { + t.Fatalf("Should have found the imported role.") + } else { + role := res.Data.(*model.Role) + assert.Equal(t, *data.DefaultChannelUserRole.DisplayName, role.DisplayName) + assert.False(t, role.BuiltIn) + assert.True(t, role.SchemeManaged) + } + } + + // Try changing the scope of the scheme and reimporting. + data.Scope = ptrStr("channel") + + if err := th.App.ImportScheme(&data, false); err == nil { + t.Fatalf("Should have failed to import.") + } + + if res := <-th.App.Srv.Store.Scheme().GetByName(*data.Name); res.Err != nil { + t.Fatalf("Failed to import scheme: %v", res.Err) + } else { + scheme := res.Data.(*model.Scheme) + assert.Equal(t, *data.Name, scheme.Name) + assert.Equal(t, *data.DisplayName, scheme.DisplayName) + assert.Equal(t, *data.Description, scheme.Description) + assert.Equal(t, "team", scheme.Scope) + } +} + +func TestImportImportRole(t *testing.T) { + th := Setup() + defer th.TearDown() + + // Try importing an invalid role in dryRun mode. + rid1 := model.NewId() + data := RoleImportData{ + Name: &rid1, + } + + if err := th.App.ImportRole(&data, true, false); err == nil { + t.Fatalf("Should have failed to import.") + } + + if res := <-th.App.Srv.Store.Role().GetByName(rid1); res.Err == nil { + t.Fatalf("Role should not have imported.") + } + + // Try importing the valid role in dryRun mode. + data.DisplayName = ptrStr("display name") + + if err := th.App.ImportRole(&data, true, false); err != nil { + t.Fatalf("Should have succeeded.") + } + + if res := <-th.App.Srv.Store.Role().GetByName(rid1); res.Err == nil { + t.Fatalf("Role should not have imported as we are in dry run mode.") + } + + // Try importing an invalid role. + data.DisplayName = nil + + if err := th.App.ImportRole(&data, false, false); err == nil { + t.Fatalf("Should have failed to import.") + } + + if res := <-th.App.Srv.Store.Role().GetByName(rid1); res.Err == nil { + t.Fatalf("Role should not have imported.") + } + + // Try importing a valid role with all params set. + data.DisplayName = ptrStr("display name") + data.Description = ptrStr("description") + data.Permissions = &[]string{"invite_user", "add_user_to_team"} + + if err := th.App.ImportRole(&data, false, false); err != nil { + t.Fatalf("Should have succeeded.") + } + + if res := <-th.App.Srv.Store.Role().GetByName(rid1); res.Err != nil { + t.Fatalf("Should have found the imported role.") + } else { + role := res.Data.(*model.Role) + assert.Equal(t, *data.Name, role.Name) + assert.Equal(t, *data.DisplayName, role.DisplayName) + assert.Equal(t, *data.Description, role.Description) + assert.Equal(t, *data.Permissions, role.Permissions) + assert.False(t, role.BuiltIn) + assert.False(t, role.SchemeManaged) + } + + // Try changing all the params and reimporting. + data.DisplayName = ptrStr("new display name") + data.Description = ptrStr("description") + data.Permissions = &[]string{"use_slash_commands"} + + if err := th.App.ImportRole(&data, false, true); err != nil { + t.Fatalf("Should have succeeded. %v", err) + } + + if res := <-th.App.Srv.Store.Role().GetByName(rid1); res.Err != nil { + t.Fatalf("Should have found the imported role.") + } else { + role := res.Data.(*model.Role) + assert.Equal(t, *data.Name, role.Name) + assert.Equal(t, *data.DisplayName, role.DisplayName) + assert.Equal(t, *data.Description, role.Description) + assert.Equal(t, *data.Permissions, role.Permissions) + assert.False(t, role.BuiltIn) + assert.True(t, role.SchemeManaged) + } + + // Check that re-importing with only required fields doesn't update the others. + data2 := RoleImportData{ + Name: &rid1, + DisplayName: ptrStr("new display name again"), + } + + if err := th.App.ImportRole(&data2, false, false); err != nil { + t.Fatalf("Should have succeeded.") + } + + if res := <-th.App.Srv.Store.Role().GetByName(rid1); res.Err != nil { + t.Fatalf("Should have found the imported role.") + } else { + role := res.Data.(*model.Role) + assert.Equal(t, *data2.Name, role.Name) + assert.Equal(t, *data2.DisplayName, role.DisplayName) + assert.Equal(t, *data.Description, role.Description) + assert.Equal(t, *data.Permissions, role.Permissions) + assert.False(t, role.BuiltIn) + assert.False(t, role.SchemeManaged) + } +} + +func TestImportImportTeam(t *testing.T) { + th := Setup() + defer th.TearDown() + + // Mark the phase 2 permissions migration as completed. + <-th.App.Srv.Store.System().Save(&model.System{Name: model.MIGRATION_KEY_ADVANCED_PERMISSIONS_PHASE_2, Value: "true"}) + + defer func() { + <-th.App.Srv.Store.System().PermanentDeleteByName(model.MIGRATION_KEY_ADVANCED_PERMISSIONS_PHASE_2) + }() + + scheme1 := th.SetupTeamScheme() + scheme2 := th.SetupTeamScheme() + + // Check how many teams are in the database. + var teamsCount int64 + if r := <-th.App.Srv.Store.Team().AnalyticsTeamCount(); r.Err == nil { + teamsCount = r.Data.(int64) + } else { + t.Fatalf("Failed to get team count.") + } + + data := TeamImportData{ + Name: ptrStr(model.NewId()), + DisplayName: ptrStr("Display Name"), + Type: ptrStr("XYZ"), + Description: ptrStr("The team description."), + AllowOpenInvite: ptrBool(true), + Scheme: &scheme1.Name, + } + + // Try importing an invalid team in dryRun mode. + if err := th.App.ImportTeam(&data, true); err == nil { + t.Fatalf("Should have received an error importing an invalid team.") + } + + // Do a valid team in dry-run mode. + data.Type = ptrStr("O") + if err := th.App.ImportTeam(&data, true); err != nil { + t.Fatalf("Received an error validating valid team.") + } + + // Check that no more teams are in the DB. + th.CheckTeamCount(t, teamsCount) + + // Do an invalid team in apply mode, check db changes. + data.Type = ptrStr("XYZ") + if err := th.App.ImportTeam(&data, false); err == nil { + t.Fatalf("Import should have failed on invalid team.") + } + + // Check that no more teams are in the DB. + th.CheckTeamCount(t, teamsCount) + + // Do a valid team in apply mode, check db changes. + data.Type = ptrStr("O") + if err := th.App.ImportTeam(&data, false); err != nil { + t.Fatalf("Received an error importing valid team: %v", err) + } + + // Check that one more team is in the DB. + th.CheckTeamCount(t, teamsCount+1) + + // Get the team and check that all the fields are correct. + if team, err := th.App.GetTeamByName(*data.Name); err != nil { + t.Fatalf("Failed to get team from database.") + } else { + assert.Equal(t, *data.DisplayName, team.DisplayName) + assert.Equal(t, *data.Type, team.Type) + assert.Equal(t, *data.Description, team.Description) + assert.Equal(t, *data.AllowOpenInvite, team.AllowOpenInvite) + assert.Equal(t, scheme1.Id, *team.SchemeId) + } + + // Alter all the fields of that team (apart from unique identifier) and import again. + data.DisplayName = ptrStr("Display Name 2") + data.Type = ptrStr("P") + data.Description = ptrStr("The new description") + data.AllowOpenInvite = ptrBool(false) + data.Scheme = &scheme2.Name + + // Check that the original number of teams are again in the DB (because this query doesn't include deleted). + data.Type = ptrStr("O") + if err := th.App.ImportTeam(&data, false); err != nil { + t.Fatalf("Received an error importing updated valid team.") + } + + th.CheckTeamCount(t, teamsCount+1) + + // Get the team and check that all fields are correct. + if team, err := th.App.GetTeamByName(*data.Name); err != nil { + t.Fatalf("Failed to get team from database.") + } else { + assert.Equal(t, *data.DisplayName, team.DisplayName) + assert.Equal(t, *data.Type, team.Type) + assert.Equal(t, *data.Description, team.Description) + assert.Equal(t, *data.AllowOpenInvite, team.AllowOpenInvite) + assert.Equal(t, scheme2.Id, *team.SchemeId) + } +} + +func TestImportImportChannel(t *testing.T) { + th := Setup() + defer th.TearDown() + + // Mark the phase 2 permissions migration as completed. + <-th.App.Srv.Store.System().Save(&model.System{Name: model.MIGRATION_KEY_ADVANCED_PERMISSIONS_PHASE_2, Value: "true"}) + + defer func() { + <-th.App.Srv.Store.System().PermanentDeleteByName(model.MIGRATION_KEY_ADVANCED_PERMISSIONS_PHASE_2) + }() + + scheme1 := th.SetupChannelScheme() + scheme2 := th.SetupChannelScheme() + + // Import a Team. + teamName := model.NewId() + th.App.ImportTeam(&TeamImportData{ + Name: &teamName, + DisplayName: ptrStr("Display Name"), + Type: ptrStr("O"), + }, false) + team, err := th.App.GetTeamByName(teamName) + if err != nil { + t.Fatalf("Failed to get team from database.") + } + + // Check how many channels are in the database. + var channelCount int64 + if r := <-th.App.Srv.Store.Channel().AnalyticsTypeCount("", model.CHANNEL_OPEN); r.Err == nil { + channelCount = r.Data.(int64) + } else { + t.Fatalf("Failed to get team count.") + } + + // Do an invalid channel in dry-run mode. + data := ChannelImportData{ + Team: &teamName, + DisplayName: ptrStr("Display Name"), + Type: ptrStr("O"), + Header: ptrStr("Channe Header"), + Purpose: ptrStr("Channel Purpose"), + Scheme: &scheme1.Name, + } + if err := th.App.ImportChannel(&data, true); err == nil { + t.Fatalf("Expected error due to invalid name.") + } + + // Check that no more channels are in the DB. + th.CheckChannelsCount(t, channelCount) + + // Do a valid channel with a nonexistent team in dry-run mode. + data.Name = ptrStr("channelname") + data.Team = ptrStr(model.NewId()) + if err := th.App.ImportChannel(&data, true); err != nil { + t.Fatalf("Expected success as cannot validate channel name in dry run mode.") + } + + // Check that no more channels are in the DB. + th.CheckChannelsCount(t, channelCount) + + // Do a valid channel in dry-run mode. + data.Team = &teamName + if err := th.App.ImportChannel(&data, true); err != nil { + t.Fatalf("Expected success as valid team.") + } + + // Check that no more channels are in the DB. + th.CheckChannelsCount(t, channelCount) + + // Do an invalid channel in apply mode. + data.Name = nil + if err := th.App.ImportChannel(&data, false); err == nil { + t.Fatalf("Expected error due to invalid name (apply mode).") + } + + // Check that no more channels are in the DB. + th.CheckChannelsCount(t, channelCount) + + // Do a valid channel in apply mode with a non-existent team. + data.Name = ptrStr("channelname") + data.Team = ptrStr(model.NewId()) + if err := th.App.ImportChannel(&data, false); err == nil { + t.Fatalf("Expected error due to non-existent team (apply mode).") + } + + // Check that no more channels are in the DB. + th.CheckChannelsCount(t, channelCount) + + // Do a valid channel in apply mode. + data.Team = &teamName + if err := th.App.ImportChannel(&data, false); err != nil { + t.Fatalf("Expected success in apply mode: %v", err.Error()) + } + + // Check that 1 more channel is in the DB. + th.CheckChannelsCount(t, channelCount+1) + + // Get the Channel and check all the fields are correct. + if channel, err := th.App.GetChannelByName(*data.Name, team.Id, false); err != nil { + t.Fatalf("Failed to get channel from database.") + } else { + assert.Equal(t, *data.Name, channel.Name) + assert.Equal(t, *data.DisplayName, channel.DisplayName) + assert.Equal(t, *data.Type, channel.Type) + assert.Equal(t, *data.Header, channel.Header) + assert.Equal(t, *data.Purpose, channel.Purpose) + assert.Equal(t, scheme1.Id, *channel.SchemeId) + } + + // Alter all the fields of that channel. + data.DisplayName = ptrStr("Chaned Disp Name") + data.Type = ptrStr(model.CHANNEL_PRIVATE) + data.Header = ptrStr("New Header") + data.Purpose = ptrStr("New Purpose") + data.Scheme = &scheme2.Name + if err := th.App.ImportChannel(&data, false); err != nil { + t.Fatalf("Expected success in apply mode: %v", err.Error()) + } + + // Check channel count the same. + th.CheckChannelsCount(t, channelCount) + + // Get the Channel and check all the fields are correct. + if channel, err := th.App.GetChannelByName(*data.Name, team.Id, false); err != nil { + t.Fatalf("Failed to get channel from database.") + } else { + assert.Equal(t, *data.Name, channel.Name) + assert.Equal(t, *data.DisplayName, channel.DisplayName) + assert.Equal(t, *data.Type, channel.Type) + assert.Equal(t, *data.Header, channel.Header) + assert.Equal(t, *data.Purpose, channel.Purpose) + assert.Equal(t, scheme2.Id, *channel.SchemeId) + } + +} + +func TestImportImportUser(t *testing.T) { + th := Setup() + defer th.TearDown() + + // Check how many users are in the database. + var userCount int64 + if r := <-th.App.Srv.Store.User().GetTotalUsersCount(); r.Err == nil { + userCount = r.Data.(int64) + } else { + t.Fatalf("Failed to get user count.") + } + + // Do an invalid user in dry-run mode. + data := UserImportData{ + Username: ptrStr(model.NewId()), + } + if err := th.App.ImportUser(&data, true); err == nil { + t.Fatalf("Should have failed to import invalid user.") + } + + // Check that no more users are in the DB. + if r := <-th.App.Srv.Store.User().GetTotalUsersCount(); r.Err == nil { + if r.Data.(int64) != userCount { + t.Fatalf("Unexpected number of users") + } + } else { + t.Fatalf("Failed to get user count.") + } + + // Do a valid user in dry-run mode. + data = UserImportData{ + Username: ptrStr(model.NewId()), + Email: ptrStr(model.NewId() + "@example.com"), + } + if err := th.App.ImportUser(&data, true); err != nil { + t.Fatalf("Should have succeeded to import valid user.") + } + + // Check that no more users are in the DB. + if r := <-th.App.Srv.Store.User().GetTotalUsersCount(); r.Err == nil { + if r.Data.(int64) != userCount { + t.Fatalf("Unexpected number of users") + } + } else { + t.Fatalf("Failed to get user count.") + } + + // Do an invalid user in apply mode. + data = UserImportData{ + Username: ptrStr(model.NewId()), + } + if err := th.App.ImportUser(&data, false); err == nil { + t.Fatalf("Should have failed to import invalid user.") + } + + // Check that no more users are in the DB. + if r := <-th.App.Srv.Store.User().GetTotalUsersCount(); r.Err == nil { + if r.Data.(int64) != userCount { + t.Fatalf("Unexpected number of users") + } + } else { + t.Fatalf("Failed to get user count.") + } + + // Do a valid user in apply mode. + username := model.NewId() + testsDir, _ := utils.FindDir("tests") + data = UserImportData{ + ProfileImage: ptrStr(filepath.Join(testsDir, "test.png")), + Username: &username, + Email: ptrStr(model.NewId() + "@example.com"), + Nickname: ptrStr(model.NewId()), + FirstName: ptrStr(model.NewId()), + LastName: ptrStr(model.NewId()), + Position: ptrStr(model.NewId()), + } + if err := th.App.ImportUser(&data, false); err != nil { + t.Fatalf("Should have succeeded to import valid user.") + } + + // Check that one more user is in the DB. + if r := <-th.App.Srv.Store.User().GetTotalUsersCount(); r.Err == nil { + if r.Data.(int64) != userCount+1 { + t.Fatalf("Unexpected number of users") + } + } else { + t.Fatalf("Failed to get user count.") + } + + // Get the user and check all the fields are correct. + if user, err := th.App.GetUserByUsername(username); err != nil { + t.Fatalf("Failed to get user from database.") + } else { + if user.Email != *data.Email || user.Nickname != *data.Nickname || user.FirstName != *data.FirstName || user.LastName != *data.LastName || user.Position != *data.Position { + t.Fatalf("User properties do not match Import Data.") + } + // Check calculated properties. + if user.AuthService != "" { + t.Fatalf("Expected Auth Service to be empty.") + } + + if !(user.AuthData == nil || *user.AuthData == "") { + t.Fatalf("Expected AuthData to be empty.") + } + + if len(user.Password) == 0 { + t.Fatalf("Expected password to be set.") + } + + if !user.EmailVerified { + t.Fatalf("Expected EmailVerified to be true.") + } + + if user.Locale != *th.App.Config().LocalizationSettings.DefaultClientLocale { + t.Fatalf("Expected Locale to be the default.") + } + + if user.Roles != "system_user" { + t.Fatalf("Expected roles to be system_user") + } + } + + // Alter all the fields of that user. + data.Email = ptrStr(model.NewId() + "@example.com") + data.ProfileImage = ptrStr(filepath.Join(testsDir, "testgif.gif")) + data.AuthService = ptrStr("ldap") + data.AuthData = &username + data.Nickname = ptrStr(model.NewId()) + data.FirstName = ptrStr(model.NewId()) + data.LastName = ptrStr(model.NewId()) + data.Position = ptrStr(model.NewId()) + data.Roles = ptrStr("system_admin system_user") + data.Locale = ptrStr("zh_CN") + if err := th.App.ImportUser(&data, false); err != nil { + t.Fatalf("Should have succeeded to update valid user %v", err) + } + + // Check user count the same. + if r := <-th.App.Srv.Store.User().GetTotalUsersCount(); r.Err == nil { + if r.Data.(int64) != userCount+1 { + t.Fatalf("Unexpected number of users") + } + } else { + t.Fatalf("Failed to get user count.") + } + + // Get the user and check all the fields are correct. + if user, err := th.App.GetUserByUsername(username); err != nil { + t.Fatalf("Failed to get user from database.") + } else { + if user.Email != *data.Email || user.Nickname != *data.Nickname || user.FirstName != *data.FirstName || user.LastName != *data.LastName || user.Position != *data.Position { + t.Fatalf("Updated User properties do not match Import Data.") + } + // Check calculated properties. + if user.AuthService != "ldap" { + t.Fatalf("Expected Auth Service to be ldap \"%v\"", user.AuthService) + } + + if !(user.AuthData == data.AuthData || *user.AuthData == *data.AuthData) { + t.Fatalf("Expected AuthData to be set.") + } + + if len(user.Password) != 0 { + t.Fatalf("Expected password to be empty.") + } + + if !user.EmailVerified { + t.Fatalf("Expected EmailVerified to be true.") + } + + if user.Locale != *data.Locale { + t.Fatalf("Expected Locale to be the set.") + } + + if user.Roles != *data.Roles { + t.Fatalf("Expected roles to be set: %v", user.Roles) + } + } + + // Check Password and AuthData together. + data.Password = ptrStr("PasswordTest") + if err := th.App.ImportUser(&data, false); err == nil { + t.Fatalf("Should have failed to import invalid user.") + } + + data.AuthData = nil + if err := th.App.ImportUser(&data, false); err != nil { + t.Fatalf("Should have succeeded to update valid user %v", err) + } + + data.Password = ptrStr("") + if err := th.App.ImportUser(&data, false); err == nil { + t.Fatalf("Should have failed to import invalid user.") + } + + data.Password = ptrStr(strings.Repeat("0123456789", 10)) + if err := th.App.ImportUser(&data, false); err == nil { + t.Fatalf("Should have failed to import invalid user.") + } + + data.Password = ptrStr("TestPassword") + + // Test team and channel memberships + teamName := model.NewId() + th.App.ImportTeam(&TeamImportData{ + Name: &teamName, + DisplayName: ptrStr("Display Name"), + Type: ptrStr("O"), + }, false) + team, err := th.App.GetTeamByName(teamName) + if err != nil { + t.Fatalf("Failed to get team from database.") + } + + channelName := model.NewId() + th.App.ImportChannel(&ChannelImportData{ + Team: &teamName, + Name: &channelName, + DisplayName: ptrStr("Display Name"), + Type: ptrStr("O"), + }, false) + channel, err := th.App.GetChannelByName(channelName, team.Id, false) + if err != nil { + t.Fatalf("Failed to get channel from database.") + } + + username = model.NewId() + data = UserImportData{ + Username: &username, + Email: ptrStr(model.NewId() + "@example.com"), + Nickname: ptrStr(model.NewId()), + FirstName: ptrStr(model.NewId()), + LastName: ptrStr(model.NewId()), + Position: ptrStr(model.NewId()), + } + + teamMembers, err := th.App.GetTeamMembers(team.Id, 0, 1000) + if err != nil { + t.Fatalf("Failed to get team member count") + } + teamMemberCount := len(teamMembers) + + channelMemberCount, err := th.App.GetChannelMemberCount(channel.Id) + if err != nil { + t.Fatalf("Failed to get channel member count") + } + + // Test with an invalid team & channel membership in dry-run mode. + data.Teams = &[]UserTeamImportData{ + { + Roles: ptrStr("invalid"), + Channels: &[]UserChannelImportData{ + { + Roles: ptrStr("invalid"), + }, + }, + }, + } + if err := th.App.ImportUser(&data, true); err == nil { + t.Fatalf("Should have failed.") + } + + // Test with an unknown team name & invalid channel membership in dry-run mode. + data.Teams = &[]UserTeamImportData{ + { + Name: ptrStr(model.NewId()), + Channels: &[]UserChannelImportData{ + { + Roles: ptrStr("invalid"), + }, + }, + }, + } + if err := th.App.ImportUser(&data, true); err == nil { + t.Fatalf("Should have failed.") + } + + // Test with a valid team & invalid channel membership in dry-run mode. + data.Teams = &[]UserTeamImportData{ + { + Name: &teamName, + Channels: &[]UserChannelImportData{ + { + Roles: ptrStr("invalid"), + }, + }, + }, + } + if err := th.App.ImportUser(&data, true); err == nil { + t.Fatalf("Should have failed.") + } + + // Test with a valid team & unknown channel name in dry-run mode. + data.Teams = &[]UserTeamImportData{ + { + Name: &teamName, + Channels: &[]UserChannelImportData{ + { + Name: ptrStr(model.NewId()), + }, + }, + }, + } + if err := th.App.ImportUser(&data, true); err != nil { + t.Fatalf("Should have succeeded.") + } + + // Test with a valid team & valid channel name in dry-run mode. + data.Teams = &[]UserTeamImportData{ + { + Name: &teamName, + Channels: &[]UserChannelImportData{ + { + Name: &channelName, + }, + }, + }, + } + if err := th.App.ImportUser(&data, true); err != nil { + t.Fatalf("Should have succeeded.") + } + + // Check no new member objects were created because dry run mode. + if tmc, err := th.App.GetTeamMembers(team.Id, 0, 1000); err != nil { + t.Fatalf("Failed to get Team Member Count") + } else if len(tmc) != teamMemberCount { + t.Fatalf("Number of team members not as expected") + } + + if cmc, err := th.App.GetChannelMemberCount(channel.Id); err != nil { + t.Fatalf("Failed to get Channel Member Count") + } else if cmc != channelMemberCount { + t.Fatalf("Number of channel members not as expected") + } + + // Test with an invalid team & channel membership in apply mode. + data.Teams = &[]UserTeamImportData{ + { + Roles: ptrStr("invalid"), + Channels: &[]UserChannelImportData{ + { + Roles: ptrStr("invalid"), + }, + }, + }, + } + if err := th.App.ImportUser(&data, false); err == nil { + t.Fatalf("Should have failed.") + } + + // Test with an unknown team name & invalid channel membership in apply mode. + data.Teams = &[]UserTeamImportData{ + { + Name: ptrStr(model.NewId()), + Channels: &[]UserChannelImportData{ + { + Roles: ptrStr("invalid"), + }, + }, + }, + } + if err := th.App.ImportUser(&data, false); err == nil { + t.Fatalf("Should have failed.") + } + + // Test with a valid team & invalid channel membership in apply mode. + data.Teams = &[]UserTeamImportData{ + { + Name: &teamName, + Channels: &[]UserChannelImportData{ + { + Roles: ptrStr("invalid"), + }, + }, + }, + } + if err := th.App.ImportUser(&data, false); err == nil { + t.Fatalf("Should have failed.") + } + + // Check no new member objects were created because all tests should have failed so far. + if tmc, err := th.App.GetTeamMembers(team.Id, 0, 1000); err != nil { + t.Fatalf("Failed to get Team Member Count") + } else if len(tmc) != teamMemberCount { + t.Fatalf("Number of team members not as expected") + } + + if cmc, err := th.App.GetChannelMemberCount(channel.Id); err != nil { + t.Fatalf("Failed to get Channel Member Count") + } else if cmc != channelMemberCount { + t.Fatalf("Number of channel members not as expected") + } + + // Test with a valid team & unknown channel name in apply mode. + data.Teams = &[]UserTeamImportData{ + { + Name: &teamName, + Channels: &[]UserChannelImportData{ + { + Name: ptrStr(model.NewId()), + }, + }, + }, + } + if err := th.App.ImportUser(&data, false); err == nil { + t.Fatalf("Should have failed.") + } + + // Check only new team member object created because dry run mode. + if tmc, err := th.App.GetTeamMembers(team.Id, 0, 1000); err != nil { + t.Fatalf("Failed to get Team Member Count") + } else if len(tmc) != teamMemberCount+1 { + t.Fatalf("Number of team members not as expected") + } + + if cmc, err := th.App.GetChannelMemberCount(channel.Id); err != nil { + t.Fatalf("Failed to get Channel Member Count") + } else if cmc != channelMemberCount { + t.Fatalf("Number of channel members not as expected") + } + + // Check team member properties. + user, err := th.App.GetUserByUsername(username) + if err != nil { + t.Fatalf("Failed to get user from database.") + } + if teamMember, err := th.App.GetTeamMember(team.Id, user.Id); err != nil { + t.Fatalf("Failed to get team member from database.") + } else if teamMember.Roles != "team_user" { + t.Fatalf("Team member properties not as expected") + } + + // Test with a valid team & valid channel name in apply mode. + data.Teams = &[]UserTeamImportData{ + { + Name: &teamName, + Channels: &[]UserChannelImportData{ + { + Name: &channelName, + }, + }, + }, + } + if err := th.App.ImportUser(&data, false); err != nil { + t.Fatalf("Should have succeeded.") + } + + // Check only new channel member object created because dry run mode. + if tmc, err := th.App.GetTeamMembers(team.Id, 0, 1000); err != nil { + t.Fatalf("Failed to get Team Member Count") + } else if len(tmc) != teamMemberCount+1 { + t.Fatalf("Number of team members not as expected") + } + + if cmc, err := th.App.GetChannelMemberCount(channel.Id); err != nil { + t.Fatalf("Failed to get Channel Member Count") + } else if cmc != channelMemberCount+1 { + t.Fatalf("Number of channel members not as expected") + } + + // Check channel member properties. + if channelMember, err := th.App.GetChannelMember(channel.Id, user.Id); err != nil { + t.Fatalf("Failed to get channel member from database.") + } else if channelMember.Roles != "channel_user" || channelMember.NotifyProps[model.DESKTOP_NOTIFY_PROP] != "default" || channelMember.NotifyProps[model.PUSH_NOTIFY_PROP] != "default" || channelMember.NotifyProps[model.MARK_UNREAD_NOTIFY_PROP] != "all" { + t.Fatalf("Channel member properties not as expected") + } + + // Test with the properties of the team and channel membership changed. + data.Teams = &[]UserTeamImportData{ + { + Name: &teamName, + Roles: ptrStr("team_user team_admin"), + Channels: &[]UserChannelImportData{ + { + Name: &channelName, + Roles: ptrStr("channel_user channel_admin"), + NotifyProps: &UserChannelNotifyPropsImportData{ + Desktop: ptrStr(model.USER_NOTIFY_MENTION), + Mobile: ptrStr(model.USER_NOTIFY_MENTION), + MarkUnread: ptrStr(model.USER_NOTIFY_MENTION), + }, + Favorite: ptrBool(true), + }, + }, + }, + } + if err := th.App.ImportUser(&data, false); err != nil { + t.Fatalf("Should have succeeded.") + } + + // Check both member properties. + if teamMember, err := th.App.GetTeamMember(team.Id, user.Id); err != nil { + t.Fatalf("Failed to get team member from database.") + } else if teamMember.Roles != "team_user team_admin" { + t.Fatalf("Team member properties not as expected: %v", teamMember.Roles) + } + + if channelMember, err := th.App.GetChannelMember(channel.Id, user.Id); err != nil { + t.Fatalf("Failed to get channel member Desktop from database.") + } else if channelMember.Roles != "channel_user channel_admin" || channelMember.NotifyProps[model.DESKTOP_NOTIFY_PROP] != model.USER_NOTIFY_MENTION || channelMember.NotifyProps[model.PUSH_NOTIFY_PROP] != model.USER_NOTIFY_MENTION || channelMember.NotifyProps[model.MARK_UNREAD_NOTIFY_PROP] != model.USER_NOTIFY_MENTION { + t.Fatalf("Channel member properties not as expected") + } + + checkPreference(t, th.App, user.Id, model.PREFERENCE_CATEGORY_FAVORITE_CHANNEL, channel.Id, "true") + + // No more new member objects. + if tmc, err := th.App.GetTeamMembers(team.Id, 0, 1000); err != nil { + t.Fatalf("Failed to get Team Member Count") + } else if len(tmc) != teamMemberCount+1 { + t.Fatalf("Number of team members not as expected") + } + + if cmc, err := th.App.GetChannelMemberCount(channel.Id); err != nil { + t.Fatalf("Failed to get Channel Member Count") + } else if cmc != channelMemberCount+1 { + t.Fatalf("Number of channel members not as expected") + } + + // Add a user with some preferences. + username = model.NewId() + data = UserImportData{ + Username: &username, + Email: ptrStr(model.NewId() + "@example.com"), + Theme: ptrStr(`{"awayIndicator":"#DCBD4E","buttonBg":"#23A2FF","buttonColor":"#FFFFFF","centerChannelBg":"#ffffff","centerChannelColor":"#333333","codeTheme":"github","image":"/static/files/a4a388b38b32678e83823ef1b3e17766.png","linkColor":"#2389d7","mentionBg":"#2389d7","mentionColor":"#ffffff","mentionHighlightBg":"#fff2bb","mentionHighlightLink":"#2f81b7","newMessageSeparator":"#FF8800","onlineIndicator":"#7DBE00","sidebarBg":"#fafafa","sidebarHeaderBg":"#3481B9","sidebarHeaderTextColor":"#ffffff","sidebarText":"#333333","sidebarTextActiveBorder":"#378FD2","sidebarTextActiveColor":"#111111","sidebarTextHoverBg":"#e6f2fa","sidebarUnreadText":"#333333","type":"Mattermost"}`), + UseMilitaryTime: ptrStr("true"), + CollapsePreviews: ptrStr("true"), + MessageDisplay: ptrStr("compact"), + ChannelDisplayMode: ptrStr("centered"), + TutorialStep: ptrStr("3"), + } + if err := th.App.ImportUser(&data, false); err != nil { + t.Fatalf("Should have succeeded.") + } + + // Check their values. + user, err = th.App.GetUserByUsername(username) + if err != nil { + t.Fatalf("Failed to get user from database.") + } + + checkPreference(t, th.App, user.Id, model.PREFERENCE_CATEGORY_THEME, "", *data.Theme) + checkPreference(t, th.App, user.Id, model.PREFERENCE_CATEGORY_DISPLAY_SETTINGS, "use_military_time", *data.UseMilitaryTime) + checkPreference(t, th.App, user.Id, model.PREFERENCE_CATEGORY_DISPLAY_SETTINGS, "collapse_previews", *data.CollapsePreviews) + checkPreference(t, th.App, user.Id, model.PREFERENCE_CATEGORY_DISPLAY_SETTINGS, "message_display", *data.MessageDisplay) + checkPreference(t, th.App, user.Id, model.PREFERENCE_CATEGORY_DISPLAY_SETTINGS, "channel_display_mode", *data.ChannelDisplayMode) + checkPreference(t, th.App, user.Id, model.PREFERENCE_CATEGORY_TUTORIAL_STEPS, user.Id, *data.TutorialStep) + + // Change those preferences. + data = UserImportData{ + Username: &username, + Email: ptrStr(model.NewId() + "@example.com"), + Theme: ptrStr(`{"awayIndicator":"#123456","buttonBg":"#23A2FF","buttonColor":"#FFFFFF","centerChannelBg":"#ffffff","centerChannelColor":"#333333","codeTheme":"github","image":"/static/files/a4a388b38b32678e83823ef1b3e17766.png","linkColor":"#2389d7","mentionBg":"#2389d7","mentionColor":"#ffffff","mentionHighlightBg":"#fff2bb","mentionHighlightLink":"#2f81b7","newMessageSeparator":"#FF8800","onlineIndicator":"#7DBE00","sidebarBg":"#fafafa","sidebarHeaderBg":"#3481B9","sidebarHeaderTextColor":"#ffffff","sidebarText":"#333333","sidebarTextActiveBorder":"#378FD2","sidebarTextActiveColor":"#111111","sidebarTextHoverBg":"#e6f2fa","sidebarUnreadText":"#333333","type":"Mattermost"}`), + UseMilitaryTime: ptrStr("false"), + CollapsePreviews: ptrStr("false"), + MessageDisplay: ptrStr("clean"), + ChannelDisplayMode: ptrStr("full"), + TutorialStep: ptrStr("2"), + } + if err := th.App.ImportUser(&data, false); err != nil { + t.Fatalf("Should have succeeded.") + } + + // Check their values again. + checkPreference(t, th.App, user.Id, model.PREFERENCE_CATEGORY_THEME, "", *data.Theme) + checkPreference(t, th.App, user.Id, model.PREFERENCE_CATEGORY_DISPLAY_SETTINGS, "use_military_time", *data.UseMilitaryTime) + checkPreference(t, th.App, user.Id, model.PREFERENCE_CATEGORY_DISPLAY_SETTINGS, "collapse_previews", *data.CollapsePreviews) + checkPreference(t, th.App, user.Id, model.PREFERENCE_CATEGORY_DISPLAY_SETTINGS, "message_display", *data.MessageDisplay) + checkPreference(t, th.App, user.Id, model.PREFERENCE_CATEGORY_DISPLAY_SETTINGS, "channel_display_mode", *data.ChannelDisplayMode) + checkPreference(t, th.App, user.Id, model.PREFERENCE_CATEGORY_TUTORIAL_STEPS, user.Id, *data.TutorialStep) + + // Set Notify Props + data.NotifyProps = &UserNotifyPropsImportData{ + Desktop: ptrStr(model.USER_NOTIFY_ALL), + DesktopSound: ptrStr("true"), + Email: ptrStr("true"), + Mobile: ptrStr(model.USER_NOTIFY_ALL), + MobilePushStatus: ptrStr(model.STATUS_ONLINE), + ChannelTrigger: ptrStr("true"), + CommentsTrigger: ptrStr(model.COMMENTS_NOTIFY_ROOT), + MentionKeys: ptrStr("valid,misc"), + } + if err := th.App.ImportUser(&data, false); err != nil { + t.Fatalf("Should have succeeded.") + } + + user, err = th.App.GetUserByUsername(username) + if err != nil { + t.Fatalf("Failed to get user from database.") + } + + checkNotifyProp(t, user, model.DESKTOP_NOTIFY_PROP, model.USER_NOTIFY_ALL) + checkNotifyProp(t, user, model.DESKTOP_SOUND_NOTIFY_PROP, "true") + checkNotifyProp(t, user, model.EMAIL_NOTIFY_PROP, "true") + checkNotifyProp(t, user, model.PUSH_NOTIFY_PROP, model.USER_NOTIFY_ALL) + checkNotifyProp(t, user, model.PUSH_STATUS_NOTIFY_PROP, model.STATUS_ONLINE) + checkNotifyProp(t, user, model.CHANNEL_MENTIONS_NOTIFY_PROP, "true") + checkNotifyProp(t, user, model.COMMENTS_NOTIFY_PROP, model.COMMENTS_NOTIFY_ROOT) + checkNotifyProp(t, user, model.MENTION_KEYS_NOTIFY_PROP, "valid,misc") + + // Change Notify Props + data.NotifyProps = &UserNotifyPropsImportData{ + Desktop: ptrStr(model.USER_NOTIFY_MENTION), + DesktopSound: ptrStr("false"), + Email: ptrStr("false"), + Mobile: ptrStr(model.USER_NOTIFY_NONE), + MobilePushStatus: ptrStr(model.STATUS_AWAY), + ChannelTrigger: ptrStr("false"), + CommentsTrigger: ptrStr(model.COMMENTS_NOTIFY_ANY), + MentionKeys: ptrStr("misc"), + } + if err := th.App.ImportUser(&data, false); err != nil { + t.Fatalf("Should have succeeded.") + } + + user, err = th.App.GetUserByUsername(username) + if err != nil { + t.Fatalf("Failed to get user from database.") + } + + checkNotifyProp(t, user, model.DESKTOP_NOTIFY_PROP, model.USER_NOTIFY_MENTION) + checkNotifyProp(t, user, model.DESKTOP_SOUND_NOTIFY_PROP, "false") + checkNotifyProp(t, user, model.EMAIL_NOTIFY_PROP, "false") + checkNotifyProp(t, user, model.PUSH_NOTIFY_PROP, model.USER_NOTIFY_NONE) + checkNotifyProp(t, user, model.PUSH_STATUS_NOTIFY_PROP, model.STATUS_AWAY) + checkNotifyProp(t, user, model.CHANNEL_MENTIONS_NOTIFY_PROP, "false") + checkNotifyProp(t, user, model.COMMENTS_NOTIFY_PROP, model.COMMENTS_NOTIFY_ANY) + checkNotifyProp(t, user, model.MENTION_KEYS_NOTIFY_PROP, "misc") + + // Check Notify Props get set on *create* user. + username = model.NewId() + data = UserImportData{ + Username: &username, + Email: ptrStr(model.NewId() + "@example.com"), + } + data.NotifyProps = &UserNotifyPropsImportData{ + Desktop: ptrStr(model.USER_NOTIFY_MENTION), + DesktopSound: ptrStr("false"), + Email: ptrStr("false"), + Mobile: ptrStr(model.USER_NOTIFY_NONE), + MobilePushStatus: ptrStr(model.STATUS_AWAY), + ChannelTrigger: ptrStr("false"), + CommentsTrigger: ptrStr(model.COMMENTS_NOTIFY_ANY), + MentionKeys: ptrStr("misc"), + } + + if err := th.App.ImportUser(&data, false); err != nil { + t.Fatalf("Should have succeeded.") + } + + user, err = th.App.GetUserByUsername(username) + if err != nil { + t.Fatalf("Failed to get user from database.") + } + + checkNotifyProp(t, user, model.DESKTOP_NOTIFY_PROP, model.USER_NOTIFY_MENTION) + checkNotifyProp(t, user, model.DESKTOP_SOUND_NOTIFY_PROP, "false") + checkNotifyProp(t, user, model.EMAIL_NOTIFY_PROP, "false") + checkNotifyProp(t, user, model.PUSH_NOTIFY_PROP, model.USER_NOTIFY_NONE) + checkNotifyProp(t, user, model.PUSH_STATUS_NOTIFY_PROP, model.STATUS_AWAY) + checkNotifyProp(t, user, model.CHANNEL_MENTIONS_NOTIFY_PROP, "false") + checkNotifyProp(t, user, model.COMMENTS_NOTIFY_PROP, model.COMMENTS_NOTIFY_ANY) + checkNotifyProp(t, user, model.MENTION_KEYS_NOTIFY_PROP, "misc") + + // Test importing a user with roles set to a team and a channel which are affected by an override scheme. + // The import subsystem should translate `channel_admin/channel_user/team_admin/team_user` + // to the appropriate scheme-managed-role booleans. + + // Mark the phase 2 permissions migration as completed. + <-th.App.Srv.Store.System().Save(&model.System{Name: model.MIGRATION_KEY_ADVANCED_PERMISSIONS_PHASE_2, Value: "true"}) + + defer func() { + <-th.App.Srv.Store.System().PermanentDeleteByName(model.MIGRATION_KEY_ADVANCED_PERMISSIONS_PHASE_2) + }() + + teamSchemeData := &SchemeImportData{ + Name: ptrStr(model.NewId()), + DisplayName: ptrStr(model.NewId()), + Scope: ptrStr("team"), + DefaultTeamUserRole: &RoleImportData{ + Name: ptrStr(model.NewId()), + DisplayName: ptrStr(model.NewId()), + }, + DefaultTeamAdminRole: &RoleImportData{ + Name: ptrStr(model.NewId()), + DisplayName: ptrStr(model.NewId()), + }, + DefaultChannelUserRole: &RoleImportData{ + Name: ptrStr(model.NewId()), + DisplayName: ptrStr(model.NewId()), + }, + DefaultChannelAdminRole: &RoleImportData{ + Name: ptrStr(model.NewId()), + DisplayName: ptrStr(model.NewId()), + }, + Description: ptrStr("description"), + } + + if err := th.App.ImportScheme(teamSchemeData, false); err != nil { + t.Fatalf("Should have succeeded.") + } + + var teamScheme *model.Scheme + if res := <-th.App.Srv.Store.Scheme().GetByName(*teamSchemeData.Name); res.Err != nil { + t.Fatalf("Failed to import scheme: %v", res.Err) + } else { + teamScheme = res.Data.(*model.Scheme) + } + + teamData := &TeamImportData{ + Name: ptrStr(model.NewId()), + DisplayName: ptrStr("Display Name"), + Type: ptrStr("O"), + Description: ptrStr("The team description."), + AllowOpenInvite: ptrBool(true), + Scheme: &teamScheme.Name, + } + if err := th.App.ImportTeam(teamData, false); err != nil { + t.Fatalf("Import should have succeeded: %v", err.Error()) + } + team, err = th.App.GetTeamByName(teamName) + if err != nil { + t.Fatalf("Failed to get team from database.") + } + + channelData := &ChannelImportData{ + Team: &teamName, + Name: ptrStr(model.NewId()), + DisplayName: ptrStr("Display Name"), + Type: ptrStr("O"), + Header: ptrStr("Channe Header"), + Purpose: ptrStr("Channel Purpose"), + } + if err := th.App.ImportChannel(channelData, false); err != nil { + t.Fatalf("Import should have succeeded.") + } + channel, err = th.App.GetChannelByName(*channelData.Name, team.Id, false) + if err != nil { + t.Fatalf("Failed to get channel from database: %v", err.Error()) + } + + // Test with a valid team & valid channel name in apply mode. + userData := &UserImportData{ + Username: &username, + Email: ptrStr(model.NewId() + "@example.com"), + Teams: &[]UserTeamImportData{ + { + Name: &team.Name, + Roles: ptrStr("team_user team_admin"), + Channels: &[]UserChannelImportData{ + { + Name: &channel.Name, + Roles: ptrStr("channel_admin channel_user"), + }, + }, + }, + }, + } + if err := th.App.ImportUser(userData, false); err != nil { + t.Fatalf("Should have succeeded.") + } + + user, err = th.App.GetUserByUsername(*userData.Username) + if err != nil { + t.Fatalf("Failed to get user from database.") + } + + teamMember, err := th.App.GetTeamMember(team.Id, user.Id) + if err != nil { + t.Fatalf("Failed to get the team member") + } + assert.True(t, teamMember.SchemeAdmin) + assert.True(t, teamMember.SchemeUser) + assert.Equal(t, "", teamMember.ExplicitRoles) + + channelMember, err := th.App.GetChannelMember(channel.Id, user.Id) + if err != nil { + t.Fatalf("Failed to get the channel member") + } + assert.True(t, channelMember.SchemeAdmin) + assert.True(t, channelMember.SchemeUser) + assert.Equal(t, "", channelMember.ExplicitRoles) + +} + +func TestImportImportPost(t *testing.T) { + th := Setup() + defer th.TearDown() + + // Create a Team. + teamName := model.NewId() + th.App.ImportTeam(&TeamImportData{ + Name: &teamName, + DisplayName: ptrStr("Display Name"), + Type: ptrStr("O"), + }, false) + team, err := th.App.GetTeamByName(teamName) + if err != nil { + t.Fatalf("Failed to get team from database.") + } + + // Create a Channel. + channelName := model.NewId() + th.App.ImportChannel(&ChannelImportData{ + Team: &teamName, + Name: &channelName, + DisplayName: ptrStr("Display Name"), + Type: ptrStr("O"), + }, false) + channel, err := th.App.GetChannelByName(channelName, team.Id, false) + if err != nil { + t.Fatalf("Failed to get channel from database.") + } + + // Create a user. + username := model.NewId() + th.App.ImportUser(&UserImportData{ + Username: &username, + Email: ptrStr(model.NewId() + "@example.com"), + }, false) + user, err := th.App.GetUserByUsername(username) + if err != nil { + t.Fatalf("Failed to get user from database.") + } + + // Count the number of posts in the testing team. + var initialPostCount int64 + if result := <-th.App.Srv.Store.Post().AnalyticsPostCount(team.Id, false, false); result.Err != nil { + t.Fatal(result.Err) + } else { + initialPostCount = result.Data.(int64) + } + + // Try adding an invalid post in dry run mode. + data := &PostImportData{ + Team: &teamName, + Channel: &channelName, + User: &username, + } + if err := th.App.ImportPost(data, true); err == nil { + t.Fatalf("Expected error.") + } + AssertAllPostsCount(t, th.App, initialPostCount, 0, team.Id) + + // Try adding a valid post in dry run mode. + data = &PostImportData{ + Team: &teamName, + Channel: &channelName, + User: &username, + Message: ptrStr("Hello"), + CreateAt: ptrInt64(model.GetMillis()), + } + if err := th.App.ImportPost(data, true); err != nil { + t.Fatalf("Expected success.") + } + AssertAllPostsCount(t, th.App, initialPostCount, 0, team.Id) + + // Try adding an invalid post in apply mode. + data = &PostImportData{ + Team: &teamName, + Channel: &channelName, + User: &username, + CreateAt: ptrInt64(model.GetMillis()), + } + if err := th.App.ImportPost(data, false); err == nil { + t.Fatalf("Expected error.") + } + AssertAllPostsCount(t, th.App, initialPostCount, 0, team.Id) + + // Try adding a valid post with invalid team in apply mode. + data = &PostImportData{ + Team: ptrStr(model.NewId()), + Channel: &channelName, + User: &username, + Message: ptrStr("Message"), + CreateAt: ptrInt64(model.GetMillis()), + } + if err := th.App.ImportPost(data, false); err == nil { + t.Fatalf("Expected error.") + } + AssertAllPostsCount(t, th.App, initialPostCount, 0, team.Id) + + // Try adding a valid post with invalid channel in apply mode. + data = &PostImportData{ + Team: &teamName, + Channel: ptrStr(model.NewId()), + User: &username, + Message: ptrStr("Message"), + CreateAt: ptrInt64(model.GetMillis()), + } + if err := th.App.ImportPost(data, false); err == nil { + t.Fatalf("Expected error.") + } + AssertAllPostsCount(t, th.App, initialPostCount, 0, team.Id) + + // Try adding a valid post with invalid user in apply mode. + data = &PostImportData{ + Team: &teamName, + Channel: &channelName, + User: ptrStr(model.NewId()), + Message: ptrStr("Message"), + CreateAt: ptrInt64(model.GetMillis()), + } + if err := th.App.ImportPost(data, false); err == nil { + t.Fatalf("Expected error.") + } + AssertAllPostsCount(t, th.App, initialPostCount, 0, team.Id) + + // Try adding a valid post in apply mode. + time := model.GetMillis() + data = &PostImportData{ + Team: &teamName, + Channel: &channelName, + User: &username, + Message: ptrStr("Message"), + CreateAt: &time, + } + if err := th.App.ImportPost(data, false); err != nil { + t.Fatalf("Expected success.") + } + AssertAllPostsCount(t, th.App, initialPostCount, 1, team.Id) + + // Check the post values. + if result := <-th.App.Srv.Store.Post().GetPostsCreatedAt(channel.Id, time); result.Err != nil { + t.Fatal(result.Err.Error()) + } else { + posts := result.Data.([]*model.Post) + if len(posts) != 1 { + t.Fatal("Unexpected number of posts found.") + } + post := posts[0] + if post.Message != *data.Message || post.CreateAt != *data.CreateAt || post.UserId != user.Id { + t.Fatal("Post properties not as expected") + } + } + + // Update the post. + data = &PostImportData{ + Team: &teamName, + Channel: &channelName, + User: &username, + Message: ptrStr("Message"), + CreateAt: &time, + } + if err := th.App.ImportPost(data, false); err != nil { + t.Fatalf("Expected success.") + } + AssertAllPostsCount(t, th.App, initialPostCount, 1, team.Id) + + // Check the post values. + if result := <-th.App.Srv.Store.Post().GetPostsCreatedAt(channel.Id, time); result.Err != nil { + t.Fatal(result.Err.Error()) + } else { + posts := result.Data.([]*model.Post) + if len(posts) != 1 { + t.Fatal("Unexpected number of posts found.") + } + post := posts[0] + if post.Message != *data.Message || post.CreateAt != *data.CreateAt || post.UserId != user.Id { + t.Fatal("Post properties not as expected") + } + } + + // Save the post with a different time. + newTime := time + 1 + data = &PostImportData{ + Team: &teamName, + Channel: &channelName, + User: &username, + Message: ptrStr("Message"), + CreateAt: &newTime, + } + if err := th.App.ImportPost(data, false); err != nil { + t.Fatalf("Expected success.") + } + AssertAllPostsCount(t, th.App, initialPostCount, 2, team.Id) + + // Save the post with a different message. + data = &PostImportData{ + Team: &teamName, + Channel: &channelName, + User: &username, + Message: ptrStr("Message 2"), + CreateAt: &time, + } + if err := th.App.ImportPost(data, false); err != nil { + t.Fatalf("Expected success.") + } + AssertAllPostsCount(t, th.App, initialPostCount, 3, team.Id) + + // Test with hashtags + hashtagTime := time + 2 + data = &PostImportData{ + Team: &teamName, + Channel: &channelName, + User: &username, + Message: ptrStr("Message 2 #hashtagmashupcity"), + CreateAt: &hashtagTime, + } + if err := th.App.ImportPost(data, false); err != nil { + t.Fatalf("Expected success.") + } + AssertAllPostsCount(t, th.App, initialPostCount, 4, team.Id) + + if result := <-th.App.Srv.Store.Post().GetPostsCreatedAt(channel.Id, hashtagTime); result.Err != nil { + t.Fatal(result.Err.Error()) + } else { + posts := result.Data.([]*model.Post) + if len(posts) != 1 { + t.Fatal("Unexpected number of posts found.") + } + post := posts[0] + if post.Message != *data.Message || post.CreateAt != *data.CreateAt || post.UserId != user.Id { + t.Fatal("Post properties not as expected") + } + if post.Hashtags != "#hashtagmashupcity" { + t.Fatalf("Hashtags not as expected: %s", post.Hashtags) + } + } + + // Post with flags. + username2 := model.NewId() + th.App.ImportUser(&UserImportData{ + Username: &username2, + Email: ptrStr(model.NewId() + "@example.com"), + }, false) + user2, err := th.App.GetUserByUsername(username2) + if err != nil { + t.Fatalf("Failed to get user from database.") + } + + flagsTime := hashtagTime + 1 + data = &PostImportData{ + Team: &teamName, + Channel: &channelName, + User: &username, + Message: ptrStr("Message with Favorites"), + CreateAt: &flagsTime, + FlaggedBy: &[]string{ + username, + username2, + }, + } + if err := th.App.ImportPost(data, false); err != nil { + t.Fatalf("Expected success.") + } + AssertAllPostsCount(t, th.App, initialPostCount, 5, team.Id) + + // Check the post values. + if result := <-th.App.Srv.Store.Post().GetPostsCreatedAt(channel.Id, flagsTime); result.Err != nil { + t.Fatal(result.Err.Error()) + } else { + posts := result.Data.([]*model.Post) + if len(posts) != 1 { + t.Fatal("Unexpected number of posts found.") + } + post := posts[0] + if post.Message != *data.Message || post.CreateAt != *data.CreateAt || post.UserId != user.Id { + t.Fatal("Post properties not as expected") + } + + checkPreference(t, th.App, user.Id, model.PREFERENCE_CATEGORY_FLAGGED_POST, post.Id, "true") + checkPreference(t, th.App, user2.Id, model.PREFERENCE_CATEGORY_FLAGGED_POST, post.Id, "true") + } + + // Post with reaction. + reactionPostTime := hashtagTime + 2 + reactionTime := hashtagTime + 3 + data = &PostImportData{ + Team: &teamName, + Channel: &channelName, + User: &username, + Message: ptrStr("Message with reaction"), + CreateAt: &reactionPostTime, + Reactions: &[]ReactionImportData{{ + User: &user2.Username, + EmojiName: ptrStr("+1"), + CreateAt: &reactionTime, + }}, + } + if err := th.App.ImportPost(data, false); err != nil { + t.Fatalf("Expected success.") + } + AssertAllPostsCount(t, th.App, initialPostCount, 6, team.Id) + + // Check the post values. + if result := <-th.App.Srv.Store.Post().GetPostsCreatedAt(channel.Id, reactionPostTime); result.Err != nil { + t.Fatal(result.Err.Error()) + } else { + posts := result.Data.([]*model.Post) + if len(posts) != 1 { + t.Fatal("Unexpected number of posts found.") + } + post := posts[0] + if post.Message != *data.Message || post.CreateAt != *data.CreateAt || post.UserId != user.Id || !post.HasReactions { + t.Fatal("Post properties not as expected") + } + + if result := <-th.App.Srv.Store.Reaction().GetForPost(post.Id, false); result.Err != nil { + t.Fatal("Can't get reaction") + } else if len(result.Data.([]*model.Reaction)) != 1 { + t.Fatal("Invalid number of reactions") + } + } + + // Post with reply. + replyPostTime := hashtagTime + 4 + replyTime := hashtagTime + 5 + data = &PostImportData{ + Team: &teamName, + Channel: &channelName, + User: &username, + Message: ptrStr("Message with reply"), + CreateAt: &replyPostTime, + Replies: &[]ReplyImportData{{ + User: &user2.Username, + Message: ptrStr("Message reply"), + CreateAt: &replyTime, + }}, + } + if err := th.App.ImportPost(data, false); err != nil { + t.Fatalf("Expected success.") + } + AssertAllPostsCount(t, th.App, initialPostCount, 8, team.Id) + + // Check the post values. + if result := <-th.App.Srv.Store.Post().GetPostsCreatedAt(channel.Id, replyPostTime); result.Err != nil { + t.Fatal(result.Err.Error()) + } else { + posts := result.Data.([]*model.Post) + if len(posts) != 1 { + t.Fatal("Unexpected number of posts found.") + } + post := posts[0] + if post.Message != *data.Message || post.CreateAt != *data.CreateAt || post.UserId != user.Id { + t.Fatal("Post properties not as expected") + } + + // Check the reply values. + if result := <-th.App.Srv.Store.Post().GetPostsCreatedAt(channel.Id, replyTime); result.Err != nil { + t.Fatal(result.Err.Error()) + } else { + replies := result.Data.([]*model.Post) + if len(replies) != 1 { + t.Fatal("Unexpected number of posts found.") + } + reply := replies[0] + if reply.Message != *(*data.Replies)[0].Message || reply.CreateAt != *(*data.Replies)[0].CreateAt || reply.UserId != user2.Id { + t.Fatal("Post properties not as expected") + } + + if reply.RootId != post.Id { + t.Fatal("Unexpected reply RootId") + } + } + } +} + +func TestImportImportDirectChannel(t *testing.T) { + th := Setup().InitBasic() + defer th.TearDown() + + // Check how many channels are in the database. + var directChannelCount int64 + if r := <-th.App.Srv.Store.Channel().AnalyticsTypeCount("", model.CHANNEL_DIRECT); r.Err == nil { + directChannelCount = r.Data.(int64) + } else { + t.Fatalf("Failed to get direct channel count.") + } + + var groupChannelCount int64 + if r := <-th.App.Srv.Store.Channel().AnalyticsTypeCount("", model.CHANNEL_GROUP); r.Err == nil { + groupChannelCount = r.Data.(int64) + } else { + t.Fatalf("Failed to get group channel count.") + } + + // Do an invalid channel in dry-run mode. + data := DirectChannelImportData{ + Members: &[]string{ + model.NewId(), + }, + Header: ptrStr("Channel Header"), + } + if err := th.App.ImportDirectChannel(&data, true); err == nil { + t.Fatalf("Expected error due to invalid name.") + } + + // Check that no more channels are in the DB. + AssertChannelCount(t, th.App, model.CHANNEL_DIRECT, directChannelCount) + AssertChannelCount(t, th.App, model.CHANNEL_GROUP, groupChannelCount) + + // Do a valid DIRECT channel with a nonexistent member in dry-run mode. + data.Members = &[]string{ + model.NewId(), + model.NewId(), + } + if err := th.App.ImportDirectChannel(&data, true); err != nil { + t.Fatalf("Expected success as cannot validate existence of channel members in dry run mode.") + } + + // Check that no more channels are in the DB. + AssertChannelCount(t, th.App, model.CHANNEL_DIRECT, directChannelCount) + AssertChannelCount(t, th.App, model.CHANNEL_GROUP, groupChannelCount) + + // Do a valid GROUP channel with a nonexistent member in dry-run mode. + data.Members = &[]string{ + model.NewId(), + model.NewId(), + model.NewId(), + } + if err := th.App.ImportDirectChannel(&data, true); err != nil { + t.Fatalf("Expected success as cannot validate existence of channel members in dry run mode.") + } + + // Check that no more channels are in the DB. + AssertChannelCount(t, th.App, model.CHANNEL_DIRECT, directChannelCount) + AssertChannelCount(t, th.App, model.CHANNEL_GROUP, groupChannelCount) + + // Do an invalid channel in apply mode. + data.Members = &[]string{ + model.NewId(), + } + if err := th.App.ImportDirectChannel(&data, false); err == nil { + t.Fatalf("Expected error due to invalid member (apply mode).") + } + + // Check that no more channels are in the DB. + AssertChannelCount(t, th.App, model.CHANNEL_DIRECT, directChannelCount) + AssertChannelCount(t, th.App, model.CHANNEL_GROUP, groupChannelCount) + + // Do a valid DIRECT channel. + data.Members = &[]string{ + th.BasicUser.Username, + th.BasicUser2.Username, + } + if err := th.App.ImportDirectChannel(&data, false); err != nil { + t.Fatalf("Expected success: %v", err.Error()) + } + + // Check that one more DIRECT channel is in the DB. + AssertChannelCount(t, th.App, model.CHANNEL_DIRECT, directChannelCount+1) + AssertChannelCount(t, th.App, model.CHANNEL_GROUP, groupChannelCount) + + // Do the same DIRECT channel again. + if err := th.App.ImportDirectChannel(&data, false); err != nil { + t.Fatalf("Expected success.") + } + + // Check that no more channels are in the DB. + AssertChannelCount(t, th.App, model.CHANNEL_DIRECT, directChannelCount+1) + AssertChannelCount(t, th.App, model.CHANNEL_GROUP, groupChannelCount) + + // Update the channel's HEADER + data.Header = ptrStr("New Channel Header 2") + if err := th.App.ImportDirectChannel(&data, false); err != nil { + t.Fatalf("Expected success.") + } + + // Check that no more channels are in the DB. + AssertChannelCount(t, th.App, model.CHANNEL_DIRECT, directChannelCount+1) + AssertChannelCount(t, th.App, model.CHANNEL_GROUP, groupChannelCount) + + // Get the channel to check that the header was updated. + if channel, err := th.App.createDirectChannel(th.BasicUser.Id, th.BasicUser2.Id); err == nil || err.Id != store.CHANNEL_EXISTS_ERROR { + t.Fatal("Should have got store.CHANNEL_EXISTS_ERROR") + } else { + if channel.Header != *data.Header { + t.Fatal("Channel header has not been updated successfully.") + } + } + + // Do a GROUP channel with an extra invalid member. + user3 := th.CreateUser() + data.Members = &[]string{ + th.BasicUser.Username, + th.BasicUser2.Username, + user3.Username, + model.NewId(), + } + if err := th.App.ImportDirectChannel(&data, false); err == nil { + t.Fatalf("Should have failed due to invalid member in list.") + } + + // Check that no more channels are in the DB. + AssertChannelCount(t, th.App, model.CHANNEL_DIRECT, directChannelCount+1) + AssertChannelCount(t, th.App, model.CHANNEL_GROUP, groupChannelCount) + + // Do a valid GROUP channel. + data.Members = &[]string{ + th.BasicUser.Username, + th.BasicUser2.Username, + user3.Username, + } + if err := th.App.ImportDirectChannel(&data, false); err != nil { + t.Fatalf("Expected success.") + } + + // Check that one more GROUP channel is in the DB. + AssertChannelCount(t, th.App, model.CHANNEL_DIRECT, directChannelCount+1) + AssertChannelCount(t, th.App, model.CHANNEL_GROUP, groupChannelCount+1) + + // Do the same DIRECT channel again. + if err := th.App.ImportDirectChannel(&data, false); err != nil { + t.Fatalf("Expected success.") + } + + // Check that no more channels are in the DB. + AssertChannelCount(t, th.App, model.CHANNEL_DIRECT, directChannelCount+1) + AssertChannelCount(t, th.App, model.CHANNEL_GROUP, groupChannelCount+1) + + // Update the channel's HEADER + data.Header = ptrStr("New Channel Header 3") + if err := th.App.ImportDirectChannel(&data, false); err != nil { + t.Fatalf("Expected success.") + } + + // Check that no more channels are in the DB. + AssertChannelCount(t, th.App, model.CHANNEL_DIRECT, directChannelCount+1) + AssertChannelCount(t, th.App, model.CHANNEL_GROUP, groupChannelCount+1) + + // Get the channel to check that the header was updated. + userIds := []string{ + th.BasicUser.Id, + th.BasicUser2.Id, + user3.Id, + } + if channel, err := th.App.createGroupChannel(userIds, th.BasicUser.Id); err.Id != store.CHANNEL_EXISTS_ERROR { + t.Fatal("Should have got store.CHANNEL_EXISTS_ERROR") + } else { + if channel.Header != *data.Header { + t.Fatal("Channel header has not been updated successfully.") + } + } + + // Import a channel with some favorites. + data.Members = &[]string{ + th.BasicUser.Username, + th.BasicUser2.Username, + } + data.FavoritedBy = &[]string{ + th.BasicUser.Username, + th.BasicUser2.Username, + } + if err := th.App.ImportDirectChannel(&data, false); err != nil { + t.Fatal(err) + } + + if channel, err := th.App.createDirectChannel(th.BasicUser.Id, th.BasicUser2.Id); err == nil || err.Id != store.CHANNEL_EXISTS_ERROR { + t.Fatal("Should have got store.CHANNEL_EXISTS_ERROR") + } else { + checkPreference(t, th.App, th.BasicUser.Id, model.PREFERENCE_CATEGORY_FAVORITE_CHANNEL, channel.Id, "true") + checkPreference(t, th.App, th.BasicUser2.Id, model.PREFERENCE_CATEGORY_FAVORITE_CHANNEL, channel.Id, "true") + } +} + +func TestImportImportDirectPost(t *testing.T) { + th := Setup().InitBasic() + defer th.TearDown() + + // Create the DIRECT channel. + channelData := DirectChannelImportData{ + Members: &[]string{ + th.BasicUser.Username, + th.BasicUser2.Username, + }, + } + if err := th.App.ImportDirectChannel(&channelData, false); err != nil { + t.Fatalf("Expected success: %v", err.Error()) + } + + // Get the channel. + var directChannel *model.Channel + if channel, err := th.App.createDirectChannel(th.BasicUser.Id, th.BasicUser2.Id); err.Id != store.CHANNEL_EXISTS_ERROR { + t.Fatal("Should have got store.CHANNEL_EXISTS_ERROR") + } else { + directChannel = channel + } + + // Get the number of posts in the system. + var initialPostCount int64 + if result := <-th.App.Srv.Store.Post().AnalyticsPostCount("", false, false); result.Err != nil { + t.Fatal(result.Err) + } else { + initialPostCount = result.Data.(int64) + } + + // Try adding an invalid post in dry run mode. + data := &DirectPostImportData{ + ChannelMembers: &[]string{ + th.BasicUser.Username, + th.BasicUser2.Username, + }, + User: ptrStr(th.BasicUser.Username), + CreateAt: ptrInt64(model.GetMillis()), + } + if err := th.App.ImportDirectPost(data, true); err == nil { + t.Fatalf("Expected error.") + } + AssertAllPostsCount(t, th.App, initialPostCount, 0, "") + + // Try adding a valid post in dry run mode. + data = &DirectPostImportData{ + ChannelMembers: &[]string{ + th.BasicUser.Username, + th.BasicUser2.Username, + }, + User: ptrStr(th.BasicUser.Username), + Message: ptrStr("Message"), + CreateAt: ptrInt64(model.GetMillis()), + } + if err := th.App.ImportDirectPost(data, true); err != nil { + t.Fatalf("Expected success.") + } + AssertAllPostsCount(t, th.App, initialPostCount, 0, "") + + // Try adding an invalid post in apply mode. + data = &DirectPostImportData{ + ChannelMembers: &[]string{ + th.BasicUser.Username, + model.NewId(), + }, + User: ptrStr(th.BasicUser.Username), + Message: ptrStr("Message"), + CreateAt: ptrInt64(model.GetMillis()), + } + if err := th.App.ImportDirectPost(data, false); err == nil { + t.Fatalf("Expected error.") + } + AssertAllPostsCount(t, th.App, initialPostCount, 0, "") + + // Try adding a valid post in apply mode. + data = &DirectPostImportData{ + ChannelMembers: &[]string{ + th.BasicUser.Username, + th.BasicUser2.Username, + }, + User: ptrStr(th.BasicUser.Username), + Message: ptrStr("Message"), + CreateAt: ptrInt64(model.GetMillis()), + } + if err := th.App.ImportDirectPost(data, false); err != nil { + t.Fatalf("Expected success: %v", err.Error()) + } + AssertAllPostsCount(t, th.App, initialPostCount, 1, "") + + // Check the post values. + if result := <-th.App.Srv.Store.Post().GetPostsCreatedAt(directChannel.Id, *data.CreateAt); result.Err != nil { + t.Fatal(result.Err.Error()) + } else { + posts := result.Data.([]*model.Post) + if len(posts) != 1 { + t.Fatal("Unexpected number of posts found.") + } + post := posts[0] + if post.Message != *data.Message || post.CreateAt != *data.CreateAt || post.UserId != th.BasicUser.Id { + t.Fatal("Post properties not as expected") + } + } + + // Import the post again. + if err := th.App.ImportDirectPost(data, false); err != nil { + t.Fatalf("Expected success.") + } + AssertAllPostsCount(t, th.App, initialPostCount, 1, "") + + // Check the post values. + if result := <-th.App.Srv.Store.Post().GetPostsCreatedAt(directChannel.Id, *data.CreateAt); result.Err != nil { + t.Fatal(result.Err.Error()) + } else { + posts := result.Data.([]*model.Post) + if len(posts) != 1 { + t.Fatal("Unexpected number of posts found.") + } + post := posts[0] + if post.Message != *data.Message || post.CreateAt != *data.CreateAt || post.UserId != th.BasicUser.Id { + t.Fatal("Post properties not as expected") + } + } + + // Save the post with a different time. + data.CreateAt = ptrInt64(*data.CreateAt + 1) + if err := th.App.ImportDirectPost(data, false); err != nil { + t.Fatalf("Expected success.") + } + AssertAllPostsCount(t, th.App, initialPostCount, 2, "") + + // Save the post with a different message. + data.Message = ptrStr("Message 2") + if err := th.App.ImportDirectPost(data, false); err != nil { + t.Fatalf("Expected success.") + } + AssertAllPostsCount(t, th.App, initialPostCount, 3, "") + + // Test with hashtags + data.Message = ptrStr("Message 2 #hashtagmashupcity") + data.CreateAt = ptrInt64(*data.CreateAt + 1) + if err := th.App.ImportDirectPost(data, false); err != nil { + t.Fatalf("Expected success.") + } + AssertAllPostsCount(t, th.App, initialPostCount, 4, "") + + if result := <-th.App.Srv.Store.Post().GetPostsCreatedAt(directChannel.Id, *data.CreateAt); result.Err != nil { + t.Fatal(result.Err.Error()) + } else { + posts := result.Data.([]*model.Post) + if len(posts) != 1 { + t.Fatal("Unexpected number of posts found.") + } + post := posts[0] + if post.Message != *data.Message || post.CreateAt != *data.CreateAt || post.UserId != th.BasicUser.Id { + t.Fatal("Post properties not as expected") + } + if post.Hashtags != "#hashtagmashupcity" { + t.Fatalf("Hashtags not as expected: %s", post.Hashtags) + } + } + + // Test with some flags. + data = &DirectPostImportData{ + ChannelMembers: &[]string{ + th.BasicUser.Username, + th.BasicUser2.Username, + }, + FlaggedBy: &[]string{ + th.BasicUser.Username, + th.BasicUser2.Username, + }, + User: ptrStr(th.BasicUser.Username), + Message: ptrStr("Message"), + CreateAt: ptrInt64(model.GetMillis()), + } + + if err := th.App.ImportDirectPost(data, false); err != nil { + t.Fatalf("Expected success: %v", err.Error()) + } + + // Check the post values. + if result := <-th.App.Srv.Store.Post().GetPostsCreatedAt(directChannel.Id, *data.CreateAt); result.Err != nil { + t.Fatal(result.Err.Error()) + } else { + posts := result.Data.([]*model.Post) + if len(posts) != 1 { + t.Fatal("Unexpected number of posts found.") + } + post := posts[0] + checkPreference(t, th.App, th.BasicUser.Id, model.PREFERENCE_CATEGORY_FLAGGED_POST, post.Id, "true") + checkPreference(t, th.App, th.BasicUser2.Id, model.PREFERENCE_CATEGORY_FLAGGED_POST, post.Id, "true") + } + + // ------------------ Group Channel ------------------------- + + // Create the GROUP channel. + user3 := th.CreateUser() + channelData = DirectChannelImportData{ + Members: &[]string{ + th.BasicUser.Username, + th.BasicUser2.Username, + user3.Username, + }, + } + if err := th.App.ImportDirectChannel(&channelData, false); err != nil { + t.Fatalf("Expected success: %v", err.Error()) + } + + // Get the channel. + var groupChannel *model.Channel + userIds := []string{ + th.BasicUser.Id, + th.BasicUser2.Id, + user3.Id, + } + if channel, err := th.App.createGroupChannel(userIds, th.BasicUser.Id); err.Id != store.CHANNEL_EXISTS_ERROR { + t.Fatal("Should have got store.CHANNEL_EXISTS_ERROR") + } else { + groupChannel = channel + } + + // Get the number of posts in the system. + if result := <-th.App.Srv.Store.Post().AnalyticsPostCount("", false, false); result.Err != nil { + t.Fatal(result.Err) + } else { + initialPostCount = result.Data.(int64) + } + + // Try adding an invalid post in dry run mode. + data = &DirectPostImportData{ + ChannelMembers: &[]string{ + th.BasicUser.Username, + th.BasicUser2.Username, + user3.Username, + }, + User: ptrStr(th.BasicUser.Username), + CreateAt: ptrInt64(model.GetMillis()), + } + if err := th.App.ImportDirectPost(data, true); err == nil { + t.Fatalf("Expected error.") + } + AssertAllPostsCount(t, th.App, initialPostCount, 0, "") + + // Try adding a valid post in dry run mode. + data = &DirectPostImportData{ + ChannelMembers: &[]string{ + th.BasicUser.Username, + th.BasicUser2.Username, + user3.Username, + }, + User: ptrStr(th.BasicUser.Username), + Message: ptrStr("Message"), + CreateAt: ptrInt64(model.GetMillis()), + } + if err := th.App.ImportDirectPost(data, true); err != nil { + t.Fatalf("Expected success.") + } + AssertAllPostsCount(t, th.App, initialPostCount, 0, "") + + // Try adding an invalid post in apply mode. + data = &DirectPostImportData{ + ChannelMembers: &[]string{ + th.BasicUser.Username, + th.BasicUser2.Username, + user3.Username, + model.NewId(), + }, + User: ptrStr(th.BasicUser.Username), + Message: ptrStr("Message"), + CreateAt: ptrInt64(model.GetMillis()), + } + if err := th.App.ImportDirectPost(data, false); err == nil { + t.Fatalf("Expected error.") + } + AssertAllPostsCount(t, th.App, initialPostCount, 0, "") + + // Try adding a valid post in apply mode. + data = &DirectPostImportData{ + ChannelMembers: &[]string{ + th.BasicUser.Username, + th.BasicUser2.Username, + user3.Username, + }, + User: ptrStr(th.BasicUser.Username), + Message: ptrStr("Message"), + CreateAt: ptrInt64(model.GetMillis()), + } + if err := th.App.ImportDirectPost(data, false); err != nil { + t.Fatalf("Expected success: %v", err.Error()) + } + AssertAllPostsCount(t, th.App, initialPostCount, 1, "") + + // Check the post values. + if result := <-th.App.Srv.Store.Post().GetPostsCreatedAt(groupChannel.Id, *data.CreateAt); result.Err != nil { + t.Fatal(result.Err.Error()) + } else { + posts := result.Data.([]*model.Post) + if len(posts) != 1 { + t.Fatal("Unexpected number of posts found.") + } + post := posts[0] + if post.Message != *data.Message || post.CreateAt != *data.CreateAt || post.UserId != th.BasicUser.Id { + t.Fatal("Post properties not as expected") + } + } + + // Import the post again. + if err := th.App.ImportDirectPost(data, false); err != nil { + t.Fatalf("Expected success.") + } + AssertAllPostsCount(t, th.App, initialPostCount, 1, "") + + // Check the post values. + if result := <-th.App.Srv.Store.Post().GetPostsCreatedAt(groupChannel.Id, *data.CreateAt); result.Err != nil { + t.Fatal(result.Err.Error()) + } else { + posts := result.Data.([]*model.Post) + if len(posts) != 1 { + t.Fatal("Unexpected number of posts found.") + } + post := posts[0] + if post.Message != *data.Message || post.CreateAt != *data.CreateAt || post.UserId != th.BasicUser.Id { + t.Fatal("Post properties not as expected") + } + } + + // Save the post with a different time. + data.CreateAt = ptrInt64(*data.CreateAt + 1) + if err := th.App.ImportDirectPost(data, false); err != nil { + t.Fatalf("Expected success.") + } + AssertAllPostsCount(t, th.App, initialPostCount, 2, "") + + // Save the post with a different message. + data.Message = ptrStr("Message 2") + if err := th.App.ImportDirectPost(data, false); err != nil { + t.Fatalf("Expected success.") + } + AssertAllPostsCount(t, th.App, initialPostCount, 3, "") + + // Test with hashtags + data.Message = ptrStr("Message 2 #hashtagmashupcity") + data.CreateAt = ptrInt64(*data.CreateAt + 1) + if err := th.App.ImportDirectPost(data, false); err != nil { + t.Fatalf("Expected success.") + } + AssertAllPostsCount(t, th.App, initialPostCount, 4, "") + + if result := <-th.App.Srv.Store.Post().GetPostsCreatedAt(groupChannel.Id, *data.CreateAt); result.Err != nil { + t.Fatal(result.Err.Error()) + } else { + posts := result.Data.([]*model.Post) + if len(posts) != 1 { + t.Fatal("Unexpected number of posts found.") + } + post := posts[0] + if post.Message != *data.Message || post.CreateAt != *data.CreateAt || post.UserId != th.BasicUser.Id { + t.Fatal("Post properties not as expected") + } + if post.Hashtags != "#hashtagmashupcity" { + t.Fatalf("Hashtags not as expected: %s", post.Hashtags) + } + } + + // Test with some flags. + data = &DirectPostImportData{ + ChannelMembers: &[]string{ + th.BasicUser.Username, + th.BasicUser2.Username, + user3.Username, + }, + FlaggedBy: &[]string{ + th.BasicUser.Username, + th.BasicUser2.Username, + }, + User: ptrStr(th.BasicUser.Username), + Message: ptrStr("Message"), + CreateAt: ptrInt64(model.GetMillis()), + } + + if err := th.App.ImportDirectPost(data, false); err != nil { + t.Fatalf("Expected success: %v", err.Error()) + } + + // Check the post values. + if result := <-th.App.Srv.Store.Post().GetPostsCreatedAt(groupChannel.Id, *data.CreateAt); result.Err != nil { + t.Fatal(result.Err.Error()) + } else { + posts := result.Data.([]*model.Post) + if len(posts) != 1 { + t.Fatal("Unexpected number of posts found.") + } + post := posts[0] + checkPreference(t, th.App, th.BasicUser.Id, model.PREFERENCE_CATEGORY_FLAGGED_POST, post.Id, "true") + checkPreference(t, th.App, th.BasicUser2.Id, model.PREFERENCE_CATEGORY_FLAGGED_POST, post.Id, "true") + } +} + +func TestImportImportEmoji(t *testing.T) { + th := Setup() + defer th.TearDown() + + th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableCustomEmoji = true }) + + testsDir, _ := utils.FindDir("tests") + testImage := filepath.Join(testsDir, "test.png") + + data := EmojiImportData{Name: ptrStr(model.NewId())} + err := th.App.ImportEmoji(&data, true) + assert.NotNil(t, err, "Invalid emoji should have failed dry run") + + result := <-th.App.Srv.Store.Emoji().GetByName(*data.Name) + assert.Nil(t, result.Data, "Emoji should not have been imported") + + data.Image = ptrStr(testImage) + err = th.App.ImportEmoji(&data, true) + assert.Nil(t, err, "Valid emoji should have passed dry run") + + data = EmojiImportData{Name: ptrStr(model.NewId())} + err = th.App.ImportEmoji(&data, false) + assert.NotNil(t, err, "Invalid emoji should have failed apply mode") + + data.Image = ptrStr("non-existent-file") + err = th.App.ImportEmoji(&data, false) + assert.NotNil(t, err, "Emoji with bad image file should have failed apply mode") + + data.Image = ptrStr(testImage) + err = th.App.ImportEmoji(&data, false) + assert.Nil(t, err, "Valid emoji should have succeeded apply mode") + + result = <-th.App.Srv.Store.Emoji().GetByName(*data.Name) + assert.NotNil(t, result.Data, "Emoji should have been imported") + + err = th.App.ImportEmoji(&data, false) + assert.Nil(t, err, "Second run should have succeeded apply mode") +} + +func TestImportAttachment(t *testing.T) { + th := Setup() + defer th.TearDown() + + testsDir, _ := utils.FindDir("tests") + testImage := filepath.Join(testsDir, "test.png") + invalidPath := "some-invalid-path" + + userId := model.NewId() + data := AttachmentImportData{Path: &testImage} + _, err := th.App.ImportAttachment(&data, &model.Post{UserId: userId, ChannelId: "some-channel"}, "some-team", true) + assert.Nil(t, err, "sample run without errors") + + attachments := GetAttachments(userId, th, t) + assert.Equal(t, len(attachments), 1) + + data = AttachmentImportData{Path: &invalidPath} + _, err = th.App.ImportAttachment(&data, &model.Post{UserId: model.NewId(), ChannelId: "some-channel"}, "some-team", true) + assert.NotNil(t, err, "should have failed when opening the file") + assert.Equal(t, err.Id, "app.import.attachment.bad_file.error") +} + +func TestImportPostAndRepliesWithAttachments(t *testing.T) { + + th := Setup() + defer th.TearDown() + + // Create a Team. + teamName := model.NewId() + th.App.ImportTeam(&TeamImportData{ + Name: &teamName, + DisplayName: ptrStr("Display Name"), + Type: ptrStr("O"), + }, false) + team, err := th.App.GetTeamByName(teamName) + if err != nil { + t.Fatalf("Failed to get team from database.") + } + + // Create a Channel. + channelName := model.NewId() + th.App.ImportChannel(&ChannelImportData{ + Team: &teamName, + Name: &channelName, + DisplayName: ptrStr("Display Name"), + Type: ptrStr("O"), + }, false) + _, err = th.App.GetChannelByName(channelName, team.Id, false) + if err != nil { + t.Fatalf("Failed to get channel from database.") + } + + // Create a user3. + username := model.NewId() + th.App.ImportUser(&UserImportData{ + Username: &username, + Email: ptrStr(model.NewId() + "@example.com"), + }, false) + user3, err := th.App.GetUserByUsername(username) + if err != nil { + t.Fatalf("Failed to get user3 from database.") + } + + username2 := model.NewId() + th.App.ImportUser(&UserImportData{ + Username: &username2, + Email: ptrStr(model.NewId() + "@example.com"), + }, false) + user4, err := th.App.GetUserByUsername(username2) + if err != nil { + t.Fatalf("Failed to get user3 from database.") + } + + // Post with attachments. + time := model.GetMillis() + attachmentsPostTime := time + attachmentsReplyTime := time + 1 + testsDir, _ := utils.FindDir("tests") + testImage := filepath.Join(testsDir, "test.png") + testMarkDown := filepath.Join(testsDir, "test-attachments.md") + data := &PostImportData{ + Team: &teamName, + Channel: &channelName, + User: &username, + Message: ptrStr("Message with reply"), + CreateAt: &attachmentsPostTime, + Attachments: &[]AttachmentImportData{{Path: &testImage}, {Path: &testMarkDown}}, + Replies: &[]ReplyImportData{{ + User: &user4.Username, + Message: ptrStr("Message reply"), + CreateAt: &attachmentsReplyTime, + Attachments: &[]AttachmentImportData{{Path: &testImage}}, + }}, + } + + if err := th.App.ImportPost(data, false); err != nil { + t.Fatalf("Expected success.") + } + + attachments := GetAttachments(user3.Id, th, t) + assert.Equal(t, len(attachments), 2) + assert.Contains(t, attachments[0].Path, team.Id) + assert.Contains(t, attachments[1].Path, team.Id) + AssertFileIdsInPost(attachments, th, t) + + attachments = GetAttachments(user4.Id, th, t) + assert.Equal(t, len(attachments), 1) + assert.Contains(t, attachments[0].Path, team.Id) + AssertFileIdsInPost(attachments, th, t) + + // Reply with Attachments in Direct Post + + // Create direct post users. + + username3 := model.NewId() + th.App.ImportUser(&UserImportData{ + Username: &username3, + Email: ptrStr(model.NewId() + "@example.com"), + }, false) + user3, err = th.App.GetUserByUsername(username3) + if err != nil { + t.Fatalf("Failed to get user3 from database.") + } + + username4 := model.NewId() + th.App.ImportUser(&UserImportData{ + Username: &username4, + Email: ptrStr(model.NewId() + "@example.com"), + }, false) + + user4, err = th.App.GetUserByUsername(username4) + if err != nil { + t.Fatalf("Failed to get user3 from database.") + } + + directImportData := &DirectPostImportData{ + ChannelMembers: &[]string{ + user3.Username, + user4.Username, + }, + User: &user3.Username, + Message: ptrStr("Message with Replies"), + CreateAt: ptrInt64(model.GetMillis()), + Replies: &[]ReplyImportData{{ + User: &user4.Username, + Message: ptrStr("Message reply with attachment"), + CreateAt: ptrInt64(model.GetMillis()), + Attachments: &[]AttachmentImportData{{Path: &testImage}}, + }}, + } + + if err := th.App.ImportDirectPost(directImportData, false); err != nil { + t.Fatalf("Expected success.") + } + + attachments = GetAttachments(user4.Id, th, t) + assert.Equal(t, len(attachments), 1) + assert.Contains(t, attachments[0].Path, "noteam") + AssertFileIdsInPost(attachments, th, t) + +} diff --git a/app/import_test.go b/app/import_test.go index a8e5a65a8..839c46bb8 100644 --- a/app/import_test.go +++ b/app/import_test.go @@ -12,7 +12,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/mattermost/mattermost-server/model" - "github.com/mattermost/mattermost-server/store" "github.com/mattermost/mattermost-server/utils" ) @@ -80,3226 +79,15 @@ func checkNoError(t *testing.T, err *model.AppError) { } } -func TestImportValidateSchemeImportData(t *testing.T) { - // Test with minimum required valid properties and team scope. - data := SchemeImportData{ - Name: ptrStr("name"), - DisplayName: ptrStr("display name"), - Scope: ptrStr("team"), - DefaultTeamAdminRole: &RoleImportData{ - Name: ptrStr("name"), - DisplayName: ptrStr("display name"), - Permissions: &[]string{"invite_user"}, - }, - DefaultTeamUserRole: &RoleImportData{ - Name: ptrStr("name"), - DisplayName: ptrStr("display name"), - Permissions: &[]string{"invite_user"}, - }, - DefaultChannelAdminRole: &RoleImportData{ - Name: ptrStr("name"), - DisplayName: ptrStr("display name"), - Permissions: &[]string{"invite_user"}, - }, - DefaultChannelUserRole: &RoleImportData{ - Name: ptrStr("name"), - DisplayName: ptrStr("display name"), - Permissions: &[]string{"invite_user"}, - }, - } - if err := validateSchemeImportData(&data); err != nil { - t.Fatal("Validation failed but should have been valid.", err) - } - - // Test with various invalid names. - data.Name = nil - if err := validateSchemeImportData(&data); err == nil { - t.Fatal("Should have failed due to invalid name.") - } - - data.Name = ptrStr("") - if err := validateSchemeImportData(&data); err == nil { - t.Fatal("Should have failed due to invalid name.") - } - - data.Name = ptrStr(strings.Repeat("1234567890", 100)) - if err := validateSchemeImportData(&data); err == nil { - t.Fatal("Should have failed due to invalid name.") - } - - data.Name = ptrStr("name") - // Test with invalid display name. - data.DisplayName = nil - if err := validateSchemeImportData(&data); err == nil { - t.Fatal("Should have failed due to invalid display name.") - } - - data.DisplayName = ptrStr("") - if err := validateSchemeImportData(&data); err == nil { - t.Fatal("Should have failed due to invalid display name.") - } - - data.DisplayName = ptrStr(strings.Repeat("1234567890", 100)) - if err := validateSchemeImportData(&data); err == nil { - t.Fatal("Should have failed due to invalid display name.") - } - - data.DisplayName = ptrStr("display name") - - // Test with various missing roles. - data.DefaultTeamAdminRole = nil - if err := validateSchemeImportData(&data); err == nil { - t.Fatal("Should have failed due to missing role.") - } - - data.DefaultTeamAdminRole = &RoleImportData{ - Name: ptrStr("name"), - DisplayName: ptrStr("display name"), - Permissions: &[]string{"invite_user"}, - } - data.DefaultTeamUserRole = nil - if err := validateSchemeImportData(&data); err == nil { - t.Fatal("Should have failed due to missing role.") - } - - data.DefaultTeamUserRole = &RoleImportData{ - Name: ptrStr("name"), - DisplayName: ptrStr("display name"), - Permissions: &[]string{"invite_user"}, - } - data.DefaultChannelAdminRole = nil - if err := validateSchemeImportData(&data); err == nil { - t.Fatal("Should have failed due to missing role.") - } - - data.DefaultChannelAdminRole = &RoleImportData{ - Name: ptrStr("name"), - DisplayName: ptrStr("display name"), - Permissions: &[]string{"invite_user"}, - } - data.DefaultChannelUserRole = nil - if err := validateSchemeImportData(&data); err == nil { - t.Fatal("Should have failed due to missing role.") - } - - data.DefaultChannelUserRole = &RoleImportData{ - Name: ptrStr("name"), - DisplayName: ptrStr("display name"), - Permissions: &[]string{"invite_user"}, - } - - // Test with various invalid roles. - data.DefaultTeamAdminRole.Name = nil - if err := validateSchemeImportData(&data); err == nil { - t.Fatal("Should have failed due to invalid role.") - } - - data.DefaultTeamAdminRole.Name = ptrStr("name") - data.DefaultTeamUserRole.Name = nil - if err := validateSchemeImportData(&data); err == nil { - t.Fatal("Should have failed due to invalid role.") - } - - data.DefaultTeamUserRole.Name = ptrStr("name") - data.DefaultChannelAdminRole.Name = nil - if err := validateSchemeImportData(&data); err == nil { - t.Fatal("Should have failed due to invalid role.") - } - - data.DefaultChannelAdminRole.Name = ptrStr("name") - data.DefaultChannelUserRole.Name = nil - if err := validateSchemeImportData(&data); err == nil { - t.Fatal("Should have failed due to invalid role.") - } - - data.DefaultChannelUserRole.Name = ptrStr("name") - - // Change to a Channel scope role, and check with missing or extra roles again. - data.Scope = ptrStr("channel") - data.DefaultTeamAdminRole = nil - if err := validateSchemeImportData(&data); err == nil { - t.Fatal("Should have failed due to spurious role.") - } - - data.DefaultTeamAdminRole = &RoleImportData{ - Name: ptrStr("name"), - DisplayName: ptrStr("display name"), - Permissions: &[]string{"invite_user"}, - } - data.DefaultTeamUserRole = nil - if err := validateSchemeImportData(&data); err == nil { - t.Fatal("Should have failed due to spurious role.") - } - - data.DefaultTeamAdminRole = nil - if err := validateSchemeImportData(&data); err != nil { - t.Fatal("Should have succeeded.") - } - - // Test with all combinations of optional parameters. - data.Description = ptrStr(strings.Repeat("1234567890", 1024)) - if err := validateSchemeImportData(&data); err == nil { - t.Fatal("Should have failed due to invalid description.") - } - - data.Description = ptrStr("description") - if err := validateSchemeImportData(&data); err != nil { - t.Fatal("Should have succeeded.") - } -} - -func TestImportValidateRoleImportData(t *testing.T) { - // Test with minimum required valid properties. - data := RoleImportData{ - Name: ptrStr("name"), - DisplayName: ptrStr("display name"), - } - if err := validateRoleImportData(&data); err != nil { - t.Fatal("Validation failed but should have been valid.", err) - } - - // Test with various invalid names. - data.Name = nil - if err := validateRoleImportData(&data); err == nil { - t.Fatal("Should have failed due to invalid name.") - } - - data.Name = ptrStr("") - if err := validateRoleImportData(&data); err == nil { - t.Fatal("Should have failed due to invalid name.") - } - - data.Name = ptrStr(strings.Repeat("1234567890", 100)) - if err := validateRoleImportData(&data); err == nil { - t.Fatal("Should have failed due to invalid name.") - } - - data.Name = ptrStr("name") - // Test with invalid display name. - data.DisplayName = nil - if err := validateRoleImportData(&data); err == nil { - t.Fatal("Should have failed due to invalid display name.") - } - - data.DisplayName = ptrStr("") - if err := validateRoleImportData(&data); err == nil { - t.Fatal("Should have failed due to invalid display name.") - } - - data.DisplayName = ptrStr(strings.Repeat("1234567890", 100)) - if err := validateRoleImportData(&data); err == nil { - t.Fatal("Should have failed due to invalid display name.") - } - - data.DisplayName = ptrStr("display name") - - // Test with various valid/invalid permissions. - data.Permissions = &[]string{} - if err := validateRoleImportData(&data); err != nil { - t.Fatal("Validation failed but should have been valid.", err) - } - - data.Permissions = &[]string{"invite_user", "add_user_to_team"} - if err := validateRoleImportData(&data); err != nil { - t.Fatal("Validation failed but should have been valid.", err) - } - - data.Permissions = &[]string{"invite_user", "add_user_to_team", "derp"} - if err := validateRoleImportData(&data); err == nil { - t.Fatal("Validation should have failed due to invalid permission.", err) - } - - data.Permissions = &[]string{"invite_user", "add_user_to_team"} - - // Test with various valid/invalid descriptions. - data.Description = ptrStr(strings.Repeat("1234567890", 1024)) - if err := validateRoleImportData(&data); err == nil { - t.Fatal("Validation should have failed due to invalid description.", err) - } - - data.Description = ptrStr("description") - if err := validateRoleImportData(&data); err != nil { - t.Fatal("Validation failed but should have been valid.", err) - } -} - -func TestImportValidateTeamImportData(t *testing.T) { - - // Test with minimum required valid properties. - data := TeamImportData{ - Name: ptrStr("teamname"), - DisplayName: ptrStr("Display Name"), - Type: ptrStr("O"), - } - if err := validateTeamImportData(&data); err != nil { - t.Fatal("Validation failed but should have been valid.") - } - - // Test with various invalid names. - data = TeamImportData{ - DisplayName: ptrStr("Display Name"), - Type: ptrStr("O"), - } - if err := validateTeamImportData(&data); err == nil { - t.Fatal("Should have failed due to missing name.") - } - - data.Name = ptrStr(strings.Repeat("abcdefghij", 7)) - if err := validateTeamImportData(&data); err == nil { - t.Fatal("Should have failed due to too long name.") - } - - data.Name = ptrStr("login") - if err := validateTeamImportData(&data); err == nil { - t.Fatal("Should have failed due to reserved word in name.") - } - - data.Name = ptrStr("Test::''ASD") - if err := validateTeamImportData(&data); err == nil { - t.Fatal("Should have failed due to non alphanum characters in name.") - } - - data.Name = ptrStr("A") - if err := validateTeamImportData(&data); err == nil { - t.Fatal("Should have failed due to short name.") - } - - // Test team various invalid display names. - data = TeamImportData{ - Name: ptrStr("teamname"), - Type: ptrStr("O"), - } - if err := validateTeamImportData(&data); err == nil { - t.Fatal("Should have failed due to missing display_name.") - } - - data.DisplayName = ptrStr("") - if err := validateTeamImportData(&data); err == nil { - t.Fatal("Should have failed due to empty display_name.") - } - - data.DisplayName = ptrStr(strings.Repeat("abcdefghij", 7)) - if err := validateTeamImportData(&data); err == nil { - t.Fatal("Should have failed due to too long display_name.") - } - - // Test with various valid and invalid types. - data = TeamImportData{ - Name: ptrStr("teamname"), - DisplayName: ptrStr("Display Name"), - } - if err := validateTeamImportData(&data); err == nil { - t.Fatal("Should have failed due to missing type.") - } - - data.Type = ptrStr("A") - if err := validateTeamImportData(&data); err == nil { - t.Fatal("Should have failed due to invalid type.") - } - - data.Type = ptrStr("I") - if err := validateTeamImportData(&data); err != nil { - t.Fatal("Should have succeeded with valid type.") - } - - // Test with all the combinations of optional parameters. - data = TeamImportData{ - Name: ptrStr("teamname"), - DisplayName: ptrStr("Display Name"), - Type: ptrStr("O"), - Description: ptrStr("The team description."), - AllowOpenInvite: ptrBool(true), - } - if err := validateTeamImportData(&data); err != nil { - t.Fatal("Should have succeeded with valid optional properties.") - } - - data.AllowOpenInvite = ptrBool(false) - if err := validateTeamImportData(&data); err != nil { - t.Fatal("Should have succeeded with allow open invites false.") - } - - data.Description = ptrStr(strings.Repeat("abcdefghij ", 26)) - if err := validateTeamImportData(&data); err == nil { - t.Fatal("Should have failed due to too long description.") - } - - // Test with an empty scheme name. - data.Description = ptrStr("abcdefg") - data.Scheme = ptrStr("") - if err := validateTeamImportData(&data); err == nil { - t.Fatal("Should have failed due to empty scheme name.") - } - - // Test with a valid scheme name. - data.Scheme = ptrStr("abcdefg") - if err := validateTeamImportData(&data); err != nil { - t.Fatal("Should have succeeded with valid scheme name.") - } -} - -func TestImportValidateChannelImportData(t *testing.T) { - - // Test with minimum required valid properties. - data := ChannelImportData{ - Team: ptrStr("teamname"), - Name: ptrStr("channelname"), - DisplayName: ptrStr("Display Name"), - Type: ptrStr("O"), - } - if err := validateChannelImportData(&data); err != nil { - t.Fatal("Validation failed but should have been valid.") - } - - // Test with missing team. - data = ChannelImportData{ - Name: ptrStr("channelname"), - DisplayName: ptrStr("Display Name"), - Type: ptrStr("O"), - } - if err := validateChannelImportData(&data); err == nil { - t.Fatal("Should have failed due to missing team.") - } - - // Test with various invalid names. - data = ChannelImportData{ - Team: ptrStr("teamname"), - DisplayName: ptrStr("Display Name"), - Type: ptrStr("O"), - } - if err := validateChannelImportData(&data); err == nil { - t.Fatal("Should have failed due to missing name.") - } - - data.Name = ptrStr(strings.Repeat("abcdefghij", 7)) - if err := validateChannelImportData(&data); err == nil { - t.Fatal("Should have failed due to too long name.") - } - - data.Name = ptrStr("Test::''ASD") - if err := validateChannelImportData(&data); err == nil { - t.Fatal("Should have failed due to non alphanum characters in name.") - } - - data.Name = ptrStr("A") - if err := validateChannelImportData(&data); err == nil { - t.Fatal("Should have failed due to short name.") - } - - // Test team various invalid display names. - data = ChannelImportData{ - Team: ptrStr("teamname"), - Name: ptrStr("channelname"), - Type: ptrStr("O"), - } - if err := validateChannelImportData(&data); err == nil { - t.Fatal("Should have failed due to missing display_name.") - } - - data.DisplayName = ptrStr("") - if err := validateChannelImportData(&data); err == nil { - t.Fatal("Should have failed due to empty display_name.") - } - - data.DisplayName = ptrStr(strings.Repeat("abcdefghij", 7)) - if err := validateChannelImportData(&data); err == nil { - t.Fatal("Should have failed due to too long display_name.") - } - - // Test with various valid and invalid types. - data = ChannelImportData{ - Team: ptrStr("teamname"), - Name: ptrStr("channelname"), - DisplayName: ptrStr("Display Name"), - } - if err := validateChannelImportData(&data); err == nil { - t.Fatal("Should have failed due to missing type.") - } - - data.Type = ptrStr("A") - if err := validateChannelImportData(&data); err == nil { - t.Fatal("Should have failed due to invalid type.") - } - - data.Type = ptrStr("P") - if err := validateChannelImportData(&data); err != nil { - t.Fatal("Should have succeeded with valid type.") - } - - // Test with all the combinations of optional parameters. - data = ChannelImportData{ - Team: ptrStr("teamname"), - Name: ptrStr("channelname"), - DisplayName: ptrStr("Display Name"), - Type: ptrStr("O"), - Header: ptrStr("Channel Header Here"), - Purpose: ptrStr("Channel Purpose Here"), - } - if err := validateChannelImportData(&data); err != nil { - t.Fatal("Should have succeeded with valid optional properties.") - } - - data.Header = ptrStr(strings.Repeat("abcdefghij ", 103)) - if err := validateChannelImportData(&data); err == nil { - t.Fatal("Should have failed due to too long header.") - } - - data.Header = ptrStr("Channel Header Here") - data.Purpose = ptrStr(strings.Repeat("abcdefghij ", 26)) - if err := validateChannelImportData(&data); err == nil { - t.Fatal("Should have failed due to too long purpose.") - } - - // Test with an empty scheme name. - data.Purpose = ptrStr("abcdefg") - data.Scheme = ptrStr("") - if err := validateChannelImportData(&data); err == nil { - t.Fatal("Should have failed due to empty scheme name.") - } - - // Test with a valid scheme name. - data.Scheme = ptrStr("abcdefg") - if err := validateChannelImportData(&data); err != nil { - t.Fatal("Should have succeeded with valid scheme name.") - } -} - -func TestImportValidateUserImportData(t *testing.T) { - - // Test with minimum required valid properties. - data := UserImportData{ - Username: ptrStr("bob"), - Email: ptrStr("bob@example.com"), - } - if err := validateUserImportData(&data); err != nil { - t.Fatal("Validation failed but should have been valid.") - } - - // Invalid Usernames. - data.Username = nil - if err := validateUserImportData(&data); err == nil { - t.Fatal("Validation should have failed due to nil Username.") - } - - data.Username = ptrStr("") - if err := validateUserImportData(&data); err == nil { - t.Fatal("Validation should have failed due to 0 length Username.") - } - - data.Username = ptrStr(strings.Repeat("abcdefghij", 7)) - if err := validateUserImportData(&data); err == nil { - t.Fatal("Validation should have failed due to too long Username.") - } - - data.Username = ptrStr("i am a username with spaces and !!!") - if err := validateUserImportData(&data); err == nil { - t.Fatal("Validation should have failed due to invalid characters in Username.") - } - - data.Username = ptrStr("bob") - - // Unexisting Picture Image - data.ProfileImage = ptrStr("not-existing-file") - if err := validateUserImportData(&data); err == nil { - t.Fatal("Validation should have failed due to not existing profile image file.") - } - data.ProfileImage = nil - - // Invalid Emails - data.Email = nil - if err := validateUserImportData(&data); err == nil { - t.Fatal("Validation should have failed due to nil Email.") - } - - data.Email = ptrStr("") - if err := validateUserImportData(&data); err == nil { - t.Fatal("Validation should have failed due to 0 length Email.") - } - - data.Email = ptrStr(strings.Repeat("abcdefghij", 13)) - if err := validateUserImportData(&data); err == nil { - t.Fatal("Validation should have failed due to too long Email.") - } - - data.Email = ptrStr("bob@example.com") - - data.AuthService = ptrStr("") - if err := validateUserImportData(&data); err == nil { - t.Fatal("Validation should have failed due to 0-length auth service.") - } - - data.AuthService = ptrStr("saml") - data.AuthData = ptrStr(strings.Repeat("abcdefghij", 15)) - if err := validateUserImportData(&data); err == nil { - t.Fatal("Validation should have failed due to too long auth data.") - } - - data.AuthData = ptrStr("bobbytables") - if err := validateUserImportData(&data); err != nil { - t.Fatal("Validation should have succeeded with valid auth service and auth data.") - } - - // Test a valid User with all fields populated. - testsDir, _ := utils.FindDir("tests") - data = UserImportData{ - ProfileImage: ptrStr(filepath.Join(testsDir, "test.png")), - Username: ptrStr("bob"), - Email: ptrStr("bob@example.com"), - AuthService: ptrStr("ldap"), - AuthData: ptrStr("bob"), - Nickname: ptrStr("BobNick"), - FirstName: ptrStr("Bob"), - LastName: ptrStr("Blob"), - Position: ptrStr("The Boss"), - Roles: ptrStr("system_user"), - Locale: ptrStr("en"), - } - if err := validateUserImportData(&data); err != nil { - t.Fatal("Validation failed but should have been valid.") - } - - // Test various invalid optional field values. - data.Nickname = ptrStr(strings.Repeat("abcdefghij", 7)) - if err := validateUserImportData(&data); err == nil { - t.Fatal("Validation should have failed due to too long Nickname.") - } - data.Nickname = ptrStr("BobNick") - - data.FirstName = ptrStr(strings.Repeat("abcdefghij", 7)) - if err := validateUserImportData(&data); err == nil { - t.Fatal("Validation should have failed due to too long First Name.") - } - data.FirstName = ptrStr("Bob") - - data.LastName = ptrStr(strings.Repeat("abcdefghij", 7)) - if err := validateUserImportData(&data); err == nil { - t.Fatal("Validation should have failed due to too long Last name.") - } - data.LastName = ptrStr("Blob") - - data.Position = ptrStr(strings.Repeat("abcdefghij", 13)) - if err := validateUserImportData(&data); err == nil { - t.Fatal("Validation should have failed due to too long Position.") - } - data.Position = ptrStr("The Boss") - - data.Roles = nil - if err := validateUserImportData(&data); err != nil { - t.Fatal("Validation failed but should have been valid.") - } - - data.Roles = ptrStr("") - if err := validateUserImportData(&data); err != nil { - t.Fatal("Validation failed but should have been valid.") - } - data.Roles = ptrStr("system_user") - - // Try various valid/invalid notify props. - data.NotifyProps = &UserNotifyPropsImportData{} - - data.NotifyProps.Desktop = ptrStr("invalid") - checkError(t, validateUserImportData(&data)) - - data.NotifyProps.Desktop = ptrStr(model.USER_NOTIFY_ALL) - data.NotifyProps.DesktopSound = ptrStr("invalid") - checkError(t, validateUserImportData(&data)) - - data.NotifyProps.DesktopSound = ptrStr("true") - data.NotifyProps.Email = ptrStr("invalid") - checkError(t, validateUserImportData(&data)) - - data.NotifyProps.Email = ptrStr("true") - data.NotifyProps.Mobile = ptrStr("invalid") - checkError(t, validateUserImportData(&data)) - - data.NotifyProps.Mobile = ptrStr(model.USER_NOTIFY_ALL) - data.NotifyProps.MobilePushStatus = ptrStr("invalid") - checkError(t, validateUserImportData(&data)) - - data.NotifyProps.MobilePushStatus = ptrStr(model.STATUS_ONLINE) - data.NotifyProps.ChannelTrigger = ptrStr("invalid") - checkError(t, validateUserImportData(&data)) - - data.NotifyProps.ChannelTrigger = ptrStr("true") - data.NotifyProps.CommentsTrigger = ptrStr("invalid") - checkError(t, validateUserImportData(&data)) - - data.NotifyProps.CommentsTrigger = ptrStr(model.COMMENTS_NOTIFY_ROOT) - data.NotifyProps.MentionKeys = ptrStr("valid") - checkNoError(t, validateUserImportData(&data)) -} - -func TestImportValidateUserTeamsImportData(t *testing.T) { - - // Invalid Name. - data := []UserTeamImportData{ - { - Roles: ptrStr("team_admin team_user"), - }, - } - if err := validateUserTeamsImportData(&data); err == nil { - t.Fatal("Should have failed due to invalid name.") - } - data[0].Name = ptrStr("teamname") - - // Valid (nil roles) - data[0].Roles = nil - if err := validateUserTeamsImportData(&data); err != nil { - t.Fatal("Should have succeeded with empty roles.") - } - - // Valid (empty roles) - data[0].Roles = ptrStr("") - if err := validateUserTeamsImportData(&data); err != nil { - t.Fatal("Should have succeeded with empty roles.") - } - - // Valid (with roles) - data[0].Roles = ptrStr("team_admin team_user") - if err := validateUserTeamsImportData(&data); err != nil { - t.Fatal("Should have succeeded with valid roles.") - } -} - -func TestImportValidateUserChannelsImportData(t *testing.T) { - - // Invalid Name. - data := []UserChannelImportData{ - { - Roles: ptrStr("channel_admin channel_user"), - }, - } - if err := validateUserChannelsImportData(&data); err == nil { - t.Fatal("Should have failed due to invalid name.") - } - data[0].Name = ptrStr("channelname") - - // Valid (nil roles) - data[0].Roles = nil - if err := validateUserChannelsImportData(&data); err != nil { - t.Fatal("Should have succeeded with empty roles.") - } - - // Valid (empty roles) - data[0].Roles = ptrStr("") - if err := validateUserChannelsImportData(&data); err != nil { - t.Fatal("Should have succeeded with empty roles.") - } - - // Valid (with roles) - data[0].Roles = ptrStr("channel_admin channel_user") - if err := validateUserChannelsImportData(&data); err != nil { - t.Fatal("Should have succeeded with valid roles.") - } - - // Empty notify props. - data[0].NotifyProps = &UserChannelNotifyPropsImportData{} - if err := validateUserChannelsImportData(&data); err != nil { - t.Fatal("Should have succeeded with empty notify props.") - } - - // Invalid desktop notify props. - data[0].NotifyProps.Desktop = ptrStr("invalid") - if err := validateUserChannelsImportData(&data); err == nil { - t.Fatal("Should have failed with invalid desktop notify props.") - } - - // Invalid mobile notify props. - data[0].NotifyProps.Desktop = ptrStr("mention") - data[0].NotifyProps.Mobile = ptrStr("invalid") - if err := validateUserChannelsImportData(&data); err == nil { - t.Fatal("Should have failed with invalid mobile notify props.") - } - - // Invalid mark_unread notify props. - data[0].NotifyProps.Mobile = ptrStr("mention") - data[0].NotifyProps.MarkUnread = ptrStr("invalid") - if err := validateUserChannelsImportData(&data); err == nil { - t.Fatal("Should have failed with invalid mark_unread notify props.") - } - - // Valid notify props. - data[0].NotifyProps.MarkUnread = ptrStr("mention") - if err := validateUserChannelsImportData(&data); err != nil { - t.Fatal("Should have succeeded with valid notify props.") - } -} - -func TestImportValidateReactionImportData(t *testing.T) { - // Test with minimum required valid properties. - parentCreateAt := model.GetMillis() - 100 - data := ReactionImportData{ - User: ptrStr("username"), - EmojiName: ptrStr("emoji"), - CreateAt: ptrInt64(model.GetMillis()), - } - if err := validateReactionImportData(&data, parentCreateAt); err != nil { - t.Fatal("Validation failed but should have been valid.") - } - - // Test with missing required properties. - data = ReactionImportData{ - EmojiName: ptrStr("emoji"), - CreateAt: ptrInt64(model.GetMillis()), - } - if err := validateReactionImportData(&data, parentCreateAt); err == nil { - t.Fatal("Should have failed due to missing required property.") - } - - data = ReactionImportData{ - User: ptrStr("username"), - CreateAt: ptrInt64(model.GetMillis()), - } - if err := validateReactionImportData(&data, parentCreateAt); err == nil { - t.Fatal("Should have failed due to missing required property.") - } - - data = ReactionImportData{ - User: ptrStr("username"), - EmojiName: ptrStr("emoji"), - } - if err := validateReactionImportData(&data, parentCreateAt); err == nil { - t.Fatal("Should have failed due to missing required property.") - } - - // Test with invalid emoji name. - data = ReactionImportData{ - User: ptrStr("username"), - EmojiName: ptrStr(strings.Repeat("1234567890", 500)), - CreateAt: ptrInt64(model.GetMillis()), - } - if err := validateReactionImportData(&data, parentCreateAt); err == nil { - t.Fatal("Should have failed due to too long emoji name.") - } - - // Test with invalid CreateAt - data = ReactionImportData{ - User: ptrStr("username"), - EmojiName: ptrStr("emoji"), - CreateAt: ptrInt64(0), - } - if err := validateReactionImportData(&data, parentCreateAt); err == nil { - t.Fatal("Should have failed due to 0 create-at value.") - } - - data = ReactionImportData{ - User: ptrStr("username"), - EmojiName: ptrStr("emoji"), - CreateAt: ptrInt64(parentCreateAt - 100), - } - if err := validateReactionImportData(&data, parentCreateAt); err == nil { - t.Fatal("Should have failed due parent with newer create-at value.") - } -} - -func TestImportValidateReplyImportData(t *testing.T) { - // Test with minimum required valid properties. - parentCreateAt := model.GetMillis() - 100 - maxPostSize := 10000 - data := ReplyImportData{ - User: ptrStr("username"), - Message: ptrStr("message"), - CreateAt: ptrInt64(model.GetMillis()), - } - if err := validateReplyImportData(&data, parentCreateAt, maxPostSize); err != nil { - t.Fatal("Validation failed but should have been valid.") - } - - // Test with missing required properties. - data = ReplyImportData{ - Message: ptrStr("message"), - CreateAt: ptrInt64(model.GetMillis()), - } - if err := validateReplyImportData(&data, parentCreateAt, maxPostSize); err == nil { - t.Fatal("Should have failed due to missing required property.") - } - - data = ReplyImportData{ - User: ptrStr("username"), - CreateAt: ptrInt64(model.GetMillis()), - } - if err := validateReplyImportData(&data, parentCreateAt, maxPostSize); err == nil { - t.Fatal("Should have failed due to missing required property.") - } - - data = ReplyImportData{ - User: ptrStr("username"), - Message: ptrStr("message"), - } - if err := validateReplyImportData(&data, parentCreateAt, maxPostSize); err == nil { - t.Fatal("Should have failed due to missing required property.") - } - - // Test with invalid message. - data = ReplyImportData{ - User: ptrStr("username"), - Message: ptrStr(strings.Repeat("0", maxPostSize+1)), - CreateAt: ptrInt64(model.GetMillis()), - } - if err := validateReplyImportData(&data, parentCreateAt, maxPostSize); err == nil { - t.Fatal("Should have failed due to too long message.") - } - - // Test with invalid CreateAt - data = ReplyImportData{ - User: ptrStr("username"), - Message: ptrStr("message"), - CreateAt: ptrInt64(0), - } - if err := validateReplyImportData(&data, parentCreateAt, maxPostSize); err == nil { - t.Fatal("Should have failed due to 0 create-at value.") - } - - data = ReplyImportData{ - User: ptrStr("username"), - Message: ptrStr("message"), - CreateAt: ptrInt64(parentCreateAt - 100), - } - if err := validateReplyImportData(&data, parentCreateAt, maxPostSize); err == nil { - t.Fatal("Should have failed due parent with newer create-at value.") - } -} - -func TestImportValidatePostImportData(t *testing.T) { - maxPostSize := 10000 - - // Test with minimum required valid properties. - data := PostImportData{ - Team: ptrStr("teamname"), - Channel: ptrStr("channelname"), - User: ptrStr("username"), - Message: ptrStr("message"), - CreateAt: ptrInt64(model.GetMillis()), - } - if err := validatePostImportData(&data, maxPostSize); err != nil { - t.Fatal("Validation failed but should have been valid.") - } - - // Test with missing required properties. - data = PostImportData{ - Channel: ptrStr("channelname"), - User: ptrStr("username"), - Message: ptrStr("message"), - CreateAt: ptrInt64(model.GetMillis()), - } - if err := validatePostImportData(&data, maxPostSize); err == nil { - t.Fatal("Should have failed due to missing required property.") - } - - data = PostImportData{ - Team: ptrStr("teamname"), - User: ptrStr("username"), - Message: ptrStr("message"), - CreateAt: ptrInt64(model.GetMillis()), - } - if err := validatePostImportData(&data, maxPostSize); err == nil { - t.Fatal("Should have failed due to missing required property.") - } - - data = PostImportData{ - Team: ptrStr("teamname"), - Channel: ptrStr("channelname"), - Message: ptrStr("message"), - CreateAt: ptrInt64(model.GetMillis()), - } - if err := validatePostImportData(&data, maxPostSize); err == nil { - t.Fatal("Should have failed due to missing required property.") - } - - data = PostImportData{ - Team: ptrStr("teamname"), - Channel: ptrStr("channelname"), - User: ptrStr("username"), - CreateAt: ptrInt64(model.GetMillis()), - } - if err := validatePostImportData(&data, maxPostSize); err == nil { - t.Fatal("Should have failed due to missing required property.") - } - - data = PostImportData{ - Team: ptrStr("teamname"), - Channel: ptrStr("channelname"), - User: ptrStr("username"), - Message: ptrStr("message"), - } - if err := validatePostImportData(&data, maxPostSize); err == nil { - t.Fatal("Should have failed due to missing required property.") - } - - // Test with invalid message. - data = PostImportData{ - Team: ptrStr("teamname"), - Channel: ptrStr("channelname"), - User: ptrStr("username"), - Message: ptrStr(strings.Repeat("0", maxPostSize+1)), - CreateAt: ptrInt64(model.GetMillis()), - } - if err := validatePostImportData(&data, maxPostSize); err == nil { - t.Fatal("Should have failed due to too long message.") - } - - // Test with invalid CreateAt - data = PostImportData{ - Team: ptrStr("teamname"), - Channel: ptrStr("channelname"), - User: ptrStr("username"), - Message: ptrStr("message"), - CreateAt: ptrInt64(0), - } - if err := validatePostImportData(&data, maxPostSize); err == nil { - t.Fatal("Should have failed due to 0 create-at value.") - } - - // Test with valid all optional parameters. - reactions := []ReactionImportData{ReactionImportData{ - User: ptrStr("username"), - EmojiName: ptrStr("emoji"), - CreateAt: ptrInt64(model.GetMillis()), - }} - replies := []ReplyImportData{ReplyImportData{ - User: ptrStr("username"), - Message: ptrStr("message"), - CreateAt: ptrInt64(model.GetMillis()), - }} - data = PostImportData{ - Team: ptrStr("teamname"), - Channel: ptrStr("channelname"), - User: ptrStr("username"), - Message: ptrStr("message"), - CreateAt: ptrInt64(model.GetMillis()), - Reactions: &reactions, - Replies: &replies, - } - if err := validatePostImportData(&data, maxPostSize); err != nil { - t.Fatal("Should have succeeded.") - } -} - -func TestImportValidateDirectChannelImportData(t *testing.T) { - - // Test with valid number of members for direct message. - data := DirectChannelImportData{ - Members: &[]string{ - model.NewId(), - model.NewId(), - }, - } - if err := validateDirectChannelImportData(&data); err != nil { - t.Fatal("Validation failed but should have been valid.") - } - - // Test with valid number of members for group message. - data = DirectChannelImportData{ - Members: &[]string{ - model.NewId(), - model.NewId(), - model.NewId(), - }, - } - if err := validateDirectChannelImportData(&data); err != nil { - t.Fatal("Validation failed but should have been valid.") - } - - // Test with all the combinations of optional parameters. - data = DirectChannelImportData{ - Members: &[]string{ - model.NewId(), - model.NewId(), - }, - Header: ptrStr("Channel Header Here"), - } - if err := validateDirectChannelImportData(&data); err != nil { - t.Fatal("Should have succeeded with valid optional properties.") - } - - // Test with invalid Header. - data.Header = ptrStr(strings.Repeat("abcdefghij ", 103)) - if err := validateDirectChannelImportData(&data); err == nil { - t.Fatal("Should have failed due to too long header.") - } - - // Test with different combinations of invalid member counts. - data = DirectChannelImportData{ - Members: &[]string{}, - } - if err := validateDirectChannelImportData(&data); err == nil { - t.Fatal("Validation should have failed due to invalid number of members.") - } - - data = DirectChannelImportData{ - Members: &[]string{ - model.NewId(), - }, - } - if err := validateDirectChannelImportData(&data); err == nil { - t.Fatal("Validation should have failed due to invalid number of members.") - } - - data = DirectChannelImportData{ - Members: &[]string{ - model.NewId(), - model.NewId(), - model.NewId(), - model.NewId(), - model.NewId(), - model.NewId(), - model.NewId(), - model.NewId(), - model.NewId(), - }, - } - if err := validateDirectChannelImportData(&data); err == nil { - t.Fatal("Validation should have failed due to invalid number of members.") - } - - // Test with invalid FavoritedBy - member1 := model.NewId() - member2 := model.NewId() - data = DirectChannelImportData{ - Members: &[]string{ - member1, - member2, - }, - FavoritedBy: &[]string{ - member1, - model.NewId(), - }, - } - if err := validateDirectChannelImportData(&data); err == nil { - t.Fatal("Validation should have failed due to non-member favorited.") - } - - // Test with valid FavoritedBy - data = DirectChannelImportData{ - Members: &[]string{ - member1, - member2, - }, - FavoritedBy: &[]string{ - member1, - member2, - }, - } - if err := validateDirectChannelImportData(&data); err != nil { - t.Fatal(err) - } -} - -func TestImportValidateDirectPostImportData(t *testing.T) { - maxPostSize := 10000 - - // Test with minimum required valid properties. - data := DirectPostImportData{ - ChannelMembers: &[]string{ - model.NewId(), - model.NewId(), - }, - User: ptrStr("username"), - Message: ptrStr("message"), - CreateAt: ptrInt64(model.GetMillis()), - } - if err := validateDirectPostImportData(&data, maxPostSize); err != nil { - t.Fatal("Validation failed but should have been valid.") - } - - // Test with missing required properties. - data = DirectPostImportData{ - User: ptrStr("username"), - Message: ptrStr("message"), - CreateAt: ptrInt64(model.GetMillis()), - } - if err := validateDirectPostImportData(&data, maxPostSize); err == nil { - t.Fatal("Should have failed due to missing required property.") - } - - data = DirectPostImportData{ - ChannelMembers: &[]string{ - model.NewId(), - model.NewId(), - }, - Message: ptrStr("message"), - CreateAt: ptrInt64(model.GetMillis()), - } - if err := validateDirectPostImportData(&data, maxPostSize); err == nil { - t.Fatal("Should have failed due to missing required property.") - } - - data = DirectPostImportData{ - ChannelMembers: &[]string{ - model.NewId(), - model.NewId(), - }, - User: ptrStr("username"), - CreateAt: ptrInt64(model.GetMillis()), - } - if err := validateDirectPostImportData(&data, maxPostSize); err == nil { - t.Fatal("Should have failed due to missing required property.") - } - - data = DirectPostImportData{ - ChannelMembers: &[]string{ - model.NewId(), - model.NewId(), - }, - User: ptrStr("username"), - Message: ptrStr("message"), - } - if err := validateDirectPostImportData(&data, maxPostSize); err == nil { - t.Fatal("Should have failed due to missing required property.") - } - - // Test with invalid numbers of channel members. - data = DirectPostImportData{ - ChannelMembers: &[]string{}, - User: ptrStr("username"), - Message: ptrStr("message"), - CreateAt: ptrInt64(model.GetMillis()), - } - if err := validateDirectPostImportData(&data, maxPostSize); err == nil { - t.Fatal("Should have failed due to unsuitable number of members.") - } - - data = DirectPostImportData{ - ChannelMembers: &[]string{ - model.NewId(), - }, - User: ptrStr("username"), - Message: ptrStr("message"), - CreateAt: ptrInt64(model.GetMillis()), - } - if err := validateDirectPostImportData(&data, maxPostSize); err == nil { - t.Fatal("Should have failed due to unsuitable number of members.") - } - - data = DirectPostImportData{ - ChannelMembers: &[]string{ - model.NewId(), - model.NewId(), - model.NewId(), - model.NewId(), - model.NewId(), - model.NewId(), - model.NewId(), - model.NewId(), - model.NewId(), - model.NewId(), - }, - User: ptrStr("username"), - Message: ptrStr("message"), - CreateAt: ptrInt64(model.GetMillis()), - } - if err := validateDirectPostImportData(&data, maxPostSize); err == nil { - t.Fatal("Should have failed due to unsuitable number of members.") - } - - // Test with group message number of members. - data = DirectPostImportData{ - ChannelMembers: &[]string{ - model.NewId(), - model.NewId(), - model.NewId(), - }, - User: ptrStr("username"), - Message: ptrStr("message"), - CreateAt: ptrInt64(model.GetMillis()), - } - if err := validateDirectPostImportData(&data, maxPostSize); err != nil { - t.Fatal("Validation failed but should have been valid.") - } - - // Test with invalid message. - data = DirectPostImportData{ - ChannelMembers: &[]string{ - model.NewId(), - model.NewId(), - }, - User: ptrStr("username"), - Message: ptrStr(strings.Repeat("0", maxPostSize+1)), - CreateAt: ptrInt64(model.GetMillis()), - } - if err := validateDirectPostImportData(&data, maxPostSize); err == nil { - t.Fatal("Should have failed due to too long message.") - } - - // Test with invalid CreateAt - data = DirectPostImportData{ - ChannelMembers: &[]string{ - model.NewId(), - model.NewId(), - }, - User: ptrStr("username"), - Message: ptrStr("message"), - CreateAt: ptrInt64(0), - } - if err := validateDirectPostImportData(&data, maxPostSize); err == nil { - t.Fatal("Should have failed due to 0 create-at value.") - } - - // Test with invalid FlaggedBy - member1 := model.NewId() - member2 := model.NewId() - data = DirectPostImportData{ - ChannelMembers: &[]string{ - member1, - member2, - }, - FlaggedBy: &[]string{ - member1, - model.NewId(), - }, - User: ptrStr("username"), - Message: ptrStr("message"), - CreateAt: ptrInt64(model.GetMillis()), - } - if err := validateDirectPostImportData(&data, maxPostSize); err == nil { - t.Fatal("Validation should have failed due to non-member flagged.") - } - - // Test with valid FlaggedBy - data = DirectPostImportData{ - ChannelMembers: &[]string{ - member1, - member2, - }, - FlaggedBy: &[]string{ - member1, - member2, - }, - User: ptrStr("username"), - Message: ptrStr("message"), - CreateAt: ptrInt64(model.GetMillis()), - } - if err := validateDirectPostImportData(&data, maxPostSize); err != nil { - t.Fatal(err) - } - - // Test with valid all optional parameters. - reactions := []ReactionImportData{ReactionImportData{ - User: ptrStr("username"), - EmojiName: ptrStr("emoji"), - CreateAt: ptrInt64(model.GetMillis()), - }} - replies := []ReplyImportData{ReplyImportData{ - User: ptrStr("username"), - Message: ptrStr("message"), - CreateAt: ptrInt64(model.GetMillis()), - }} - data = DirectPostImportData{ - ChannelMembers: &[]string{ - member1, - member2, - }, - FlaggedBy: &[]string{ - member1, - member2, - }, - User: ptrStr("username"), - Message: ptrStr("message"), - CreateAt: ptrInt64(model.GetMillis()), - Reactions: &reactions, - Replies: &replies, - } - - if err := validateDirectPostImportData(&data, maxPostSize); err != nil { - t.Fatal(err) - } -} - -func TestImportImportScheme(t *testing.T) { - th := Setup() - defer th.TearDown() - - // Mark the phase 2 permissions migration as completed. - <-th.App.Srv.Store.System().Save(&model.System{Name: model.MIGRATION_KEY_ADVANCED_PERMISSIONS_PHASE_2, Value: "true"}) - - defer func() { - <-th.App.Srv.Store.System().PermanentDeleteByName(model.MIGRATION_KEY_ADVANCED_PERMISSIONS_PHASE_2) - }() - - // Try importing an invalid scheme in dryRun mode. - data := SchemeImportData{ - Name: ptrStr(model.NewId()), - Scope: ptrStr("team"), - DefaultTeamUserRole: &RoleImportData{ - Name: ptrStr(model.NewId()), - DisplayName: ptrStr(model.NewId()), - }, - DefaultTeamAdminRole: &RoleImportData{ - Name: ptrStr(model.NewId()), - DisplayName: ptrStr(model.NewId()), - }, - DefaultChannelUserRole: &RoleImportData{ - Name: ptrStr(model.NewId()), - DisplayName: ptrStr(model.NewId()), - }, - DefaultChannelAdminRole: &RoleImportData{ - Name: ptrStr(model.NewId()), - DisplayName: ptrStr(model.NewId()), - }, - Description: ptrStr("description"), - } - - if err := th.App.ImportScheme(&data, true); err == nil { - t.Fatalf("Should have failed to import.") - } - - if res := <-th.App.Srv.Store.Scheme().GetByName(*data.Name); res.Err == nil { - t.Fatalf("Scheme should not have imported.") - } - - // Try importing a valid scheme in dryRun mode. - data.DisplayName = ptrStr("display name") - - if err := th.App.ImportScheme(&data, true); err != nil { - t.Fatalf("Should have succeeded.") - } - - if res := <-th.App.Srv.Store.Scheme().GetByName(*data.Name); res.Err == nil { - t.Fatalf("Scheme should not have imported.") - } - - // Try importing an invalid scheme. - data.DisplayName = nil - - if err := th.App.ImportScheme(&data, false); err == nil { - t.Fatalf("Should have failed to import.") - } - - if res := <-th.App.Srv.Store.Scheme().GetByName(*data.Name); res.Err == nil { - t.Fatalf("Scheme should not have imported.") - } - - // Try importing a valid scheme with all params set. - data.DisplayName = ptrStr("display name") - - if err := th.App.ImportScheme(&data, false); err != nil { - t.Fatalf("Should have succeeded.") - } - - if res := <-th.App.Srv.Store.Scheme().GetByName(*data.Name); res.Err != nil { - t.Fatalf("Failed to import scheme: %v", res.Err) - } else { - scheme := res.Data.(*model.Scheme) - assert.Equal(t, *data.Name, scheme.Name) - assert.Equal(t, *data.DisplayName, scheme.DisplayName) - assert.Equal(t, *data.Description, scheme.Description) - assert.Equal(t, *data.Scope, scheme.Scope) - - if res := <-th.App.Srv.Store.Role().GetByName(scheme.DefaultTeamAdminRole); res.Err != nil { - t.Fatalf("Should have found the imported role.") - } else { - role := res.Data.(*model.Role) - assert.Equal(t, *data.DefaultTeamAdminRole.DisplayName, role.DisplayName) - assert.False(t, role.BuiltIn) - assert.True(t, role.SchemeManaged) - } - - if res := <-th.App.Srv.Store.Role().GetByName(scheme.DefaultTeamUserRole); res.Err != nil { - t.Fatalf("Should have found the imported role.") - } else { - role := res.Data.(*model.Role) - assert.Equal(t, *data.DefaultTeamUserRole.DisplayName, role.DisplayName) - assert.False(t, role.BuiltIn) - assert.True(t, role.SchemeManaged) - } - - if res := <-th.App.Srv.Store.Role().GetByName(scheme.DefaultChannelAdminRole); res.Err != nil { - t.Fatalf("Should have found the imported role.") - } else { - role := res.Data.(*model.Role) - assert.Equal(t, *data.DefaultChannelAdminRole.DisplayName, role.DisplayName) - assert.False(t, role.BuiltIn) - assert.True(t, role.SchemeManaged) - } - - if res := <-th.App.Srv.Store.Role().GetByName(scheme.DefaultChannelUserRole); res.Err != nil { - t.Fatalf("Should have found the imported role.") - } else { - role := res.Data.(*model.Role) - assert.Equal(t, *data.DefaultChannelUserRole.DisplayName, role.DisplayName) - assert.False(t, role.BuiltIn) - assert.True(t, role.SchemeManaged) - } - } - - // Try modifying all the fields and re-importing. - data.DisplayName = ptrStr("new display name") - data.Description = ptrStr("new description") - - if err := th.App.ImportScheme(&data, false); err != nil { - t.Fatalf("Should have succeeded: %v", err) - } - - if res := <-th.App.Srv.Store.Scheme().GetByName(*data.Name); res.Err != nil { - t.Fatalf("Failed to import scheme: %v", res.Err) - } else { - scheme := res.Data.(*model.Scheme) - assert.Equal(t, *data.Name, scheme.Name) - assert.Equal(t, *data.DisplayName, scheme.DisplayName) - assert.Equal(t, *data.Description, scheme.Description) - assert.Equal(t, *data.Scope, scheme.Scope) - - if res := <-th.App.Srv.Store.Role().GetByName(scheme.DefaultTeamAdminRole); res.Err != nil { - t.Fatalf("Should have found the imported role.") - } else { - role := res.Data.(*model.Role) - assert.Equal(t, *data.DefaultTeamAdminRole.DisplayName, role.DisplayName) - assert.False(t, role.BuiltIn) - assert.True(t, role.SchemeManaged) - } - - if res := <-th.App.Srv.Store.Role().GetByName(scheme.DefaultTeamUserRole); res.Err != nil { - t.Fatalf("Should have found the imported role.") - } else { - role := res.Data.(*model.Role) - assert.Equal(t, *data.DefaultTeamUserRole.DisplayName, role.DisplayName) - assert.False(t, role.BuiltIn) - assert.True(t, role.SchemeManaged) - } - - if res := <-th.App.Srv.Store.Role().GetByName(scheme.DefaultChannelAdminRole); res.Err != nil { - t.Fatalf("Should have found the imported role.") - } else { - role := res.Data.(*model.Role) - assert.Equal(t, *data.DefaultChannelAdminRole.DisplayName, role.DisplayName) - assert.False(t, role.BuiltIn) - assert.True(t, role.SchemeManaged) - } - - if res := <-th.App.Srv.Store.Role().GetByName(scheme.DefaultChannelUserRole); res.Err != nil { - t.Fatalf("Should have found the imported role.") - } else { - role := res.Data.(*model.Role) - assert.Equal(t, *data.DefaultChannelUserRole.DisplayName, role.DisplayName) - assert.False(t, role.BuiltIn) - assert.True(t, role.SchemeManaged) - } - } - - // Try changing the scope of the scheme and reimporting. - data.Scope = ptrStr("channel") - - if err := th.App.ImportScheme(&data, false); err == nil { - t.Fatalf("Should have failed to import.") - } - - if res := <-th.App.Srv.Store.Scheme().GetByName(*data.Name); res.Err != nil { - t.Fatalf("Failed to import scheme: %v", res.Err) - } else { - scheme := res.Data.(*model.Scheme) - assert.Equal(t, *data.Name, scheme.Name) - assert.Equal(t, *data.DisplayName, scheme.DisplayName) - assert.Equal(t, *data.Description, scheme.Description) - assert.Equal(t, "team", scheme.Scope) - } -} - -func TestImportImportRole(t *testing.T) { - th := Setup() - defer th.TearDown() - - // Try importing an invalid role in dryRun mode. - rid1 := model.NewId() - data := RoleImportData{ - Name: &rid1, - } - - if err := th.App.ImportRole(&data, true, false); err == nil { - t.Fatalf("Should have failed to import.") - } - - if res := <-th.App.Srv.Store.Role().GetByName(rid1); res.Err == nil { - t.Fatalf("Role should not have imported.") - } - - // Try importing the valid role in dryRun mode. - data.DisplayName = ptrStr("display name") - - if err := th.App.ImportRole(&data, true, false); err != nil { - t.Fatalf("Should have succeeded.") - } - - if res := <-th.App.Srv.Store.Role().GetByName(rid1); res.Err == nil { - t.Fatalf("Role should not have imported as we are in dry run mode.") - } - - // Try importing an invalid role. - data.DisplayName = nil - - if err := th.App.ImportRole(&data, false, false); err == nil { - t.Fatalf("Should have failed to import.") - } - - if res := <-th.App.Srv.Store.Role().GetByName(rid1); res.Err == nil { - t.Fatalf("Role should not have imported.") - } - - // Try importing a valid role with all params set. - data.DisplayName = ptrStr("display name") - data.Description = ptrStr("description") - data.Permissions = &[]string{"invite_user", "add_user_to_team"} - - if err := th.App.ImportRole(&data, false, false); err != nil { - t.Fatalf("Should have succeeded.") - } - - if res := <-th.App.Srv.Store.Role().GetByName(rid1); res.Err != nil { - t.Fatalf("Should have found the imported role.") - } else { - role := res.Data.(*model.Role) - assert.Equal(t, *data.Name, role.Name) - assert.Equal(t, *data.DisplayName, role.DisplayName) - assert.Equal(t, *data.Description, role.Description) - assert.Equal(t, *data.Permissions, role.Permissions) - assert.False(t, role.BuiltIn) - assert.False(t, role.SchemeManaged) - } - - // Try changing all the params and reimporting. - data.DisplayName = ptrStr("new display name") - data.Description = ptrStr("description") - data.Permissions = &[]string{"use_slash_commands"} - - if err := th.App.ImportRole(&data, false, true); err != nil { - t.Fatalf("Should have succeeded. %v", err) - } - - if res := <-th.App.Srv.Store.Role().GetByName(rid1); res.Err != nil { - t.Fatalf("Should have found the imported role.") - } else { - role := res.Data.(*model.Role) - assert.Equal(t, *data.Name, role.Name) - assert.Equal(t, *data.DisplayName, role.DisplayName) - assert.Equal(t, *data.Description, role.Description) - assert.Equal(t, *data.Permissions, role.Permissions) - assert.False(t, role.BuiltIn) - assert.True(t, role.SchemeManaged) - } - - // Check that re-importing with only required fields doesn't update the others. - data2 := RoleImportData{ - Name: &rid1, - DisplayName: ptrStr("new display name again"), - } - - if err := th.App.ImportRole(&data2, false, false); err != nil { - t.Fatalf("Should have succeeded.") - } - - if res := <-th.App.Srv.Store.Role().GetByName(rid1); res.Err != nil { - t.Fatalf("Should have found the imported role.") - } else { - role := res.Data.(*model.Role) - assert.Equal(t, *data2.Name, role.Name) - assert.Equal(t, *data2.DisplayName, role.DisplayName) - assert.Equal(t, *data.Description, role.Description) - assert.Equal(t, *data.Permissions, role.Permissions) - assert.False(t, role.BuiltIn) - assert.False(t, role.SchemeManaged) - } -} - -func TestImportImportTeam(t *testing.T) { - th := Setup() - defer th.TearDown() - - // Mark the phase 2 permissions migration as completed. - <-th.App.Srv.Store.System().Save(&model.System{Name: model.MIGRATION_KEY_ADVANCED_PERMISSIONS_PHASE_2, Value: "true"}) - - defer func() { - <-th.App.Srv.Store.System().PermanentDeleteByName(model.MIGRATION_KEY_ADVANCED_PERMISSIONS_PHASE_2) - }() - - scheme1 := th.SetupTeamScheme() - scheme2 := th.SetupTeamScheme() - - // Check how many teams are in the database. - var teamsCount int64 - if r := <-th.App.Srv.Store.Team().AnalyticsTeamCount(); r.Err == nil { - teamsCount = r.Data.(int64) - } else { - t.Fatalf("Failed to get team count.") - } - - data := TeamImportData{ - Name: ptrStr(model.NewId()), - DisplayName: ptrStr("Display Name"), - Type: ptrStr("XYZ"), - Description: ptrStr("The team description."), - AllowOpenInvite: ptrBool(true), - Scheme: &scheme1.Name, - } - - // Try importing an invalid team in dryRun mode. - if err := th.App.ImportTeam(&data, true); err == nil { - t.Fatalf("Should have received an error importing an invalid team.") - } - - // Do a valid team in dry-run mode. - data.Type = ptrStr("O") - if err := th.App.ImportTeam(&data, true); err != nil { - t.Fatalf("Received an error validating valid team.") - } - - // Check that no more teams are in the DB. - th.CheckTeamCount(t, teamsCount) - - // Do an invalid team in apply mode, check db changes. - data.Type = ptrStr("XYZ") - if err := th.App.ImportTeam(&data, false); err == nil { - t.Fatalf("Import should have failed on invalid team.") - } - - // Check that no more teams are in the DB. - th.CheckTeamCount(t, teamsCount) - - // Do a valid team in apply mode, check db changes. - data.Type = ptrStr("O") - if err := th.App.ImportTeam(&data, false); err != nil { - t.Fatalf("Received an error importing valid team: %v", err) - } - - // Check that one more team is in the DB. - th.CheckTeamCount(t, teamsCount+1) - - // Get the team and check that all the fields are correct. - if team, err := th.App.GetTeamByName(*data.Name); err != nil { - t.Fatalf("Failed to get team from database.") - } else { - assert.Equal(t, *data.DisplayName, team.DisplayName) - assert.Equal(t, *data.Type, team.Type) - assert.Equal(t, *data.Description, team.Description) - assert.Equal(t, *data.AllowOpenInvite, team.AllowOpenInvite) - assert.Equal(t, scheme1.Id, *team.SchemeId) - } - - // Alter all the fields of that team (apart from unique identifier) and import again. - data.DisplayName = ptrStr("Display Name 2") - data.Type = ptrStr("P") - data.Description = ptrStr("The new description") - data.AllowOpenInvite = ptrBool(false) - data.Scheme = &scheme2.Name - - // Check that the original number of teams are again in the DB (because this query doesn't include deleted). - data.Type = ptrStr("O") - if err := th.App.ImportTeam(&data, false); err != nil { - t.Fatalf("Received an error importing updated valid team.") - } - - th.CheckTeamCount(t, teamsCount+1) - - // Get the team and check that all fields are correct. - if team, err := th.App.GetTeamByName(*data.Name); err != nil { - t.Fatalf("Failed to get team from database.") - } else { - assert.Equal(t, *data.DisplayName, team.DisplayName) - assert.Equal(t, *data.Type, team.Type) - assert.Equal(t, *data.Description, team.Description) - assert.Equal(t, *data.AllowOpenInvite, team.AllowOpenInvite) - assert.Equal(t, scheme2.Id, *team.SchemeId) - } -} - -func TestImportImportChannel(t *testing.T) { - th := Setup() - defer th.TearDown() - - // Mark the phase 2 permissions migration as completed. - <-th.App.Srv.Store.System().Save(&model.System{Name: model.MIGRATION_KEY_ADVANCED_PERMISSIONS_PHASE_2, Value: "true"}) - - defer func() { - <-th.App.Srv.Store.System().PermanentDeleteByName(model.MIGRATION_KEY_ADVANCED_PERMISSIONS_PHASE_2) - }() - - scheme1 := th.SetupChannelScheme() - scheme2 := th.SetupChannelScheme() - - // Import a Team. - teamName := model.NewId() - th.App.ImportTeam(&TeamImportData{ - Name: &teamName, - DisplayName: ptrStr("Display Name"), - Type: ptrStr("O"), - }, false) - team, err := th.App.GetTeamByName(teamName) - if err != nil { - t.Fatalf("Failed to get team from database.") - } - - // Check how many channels are in the database. - var channelCount int64 - if r := <-th.App.Srv.Store.Channel().AnalyticsTypeCount("", model.CHANNEL_OPEN); r.Err == nil { - channelCount = r.Data.(int64) - } else { - t.Fatalf("Failed to get team count.") - } - - // Do an invalid channel in dry-run mode. - data := ChannelImportData{ - Team: &teamName, - DisplayName: ptrStr("Display Name"), - Type: ptrStr("O"), - Header: ptrStr("Channe Header"), - Purpose: ptrStr("Channel Purpose"), - Scheme: &scheme1.Name, - } - if err := th.App.ImportChannel(&data, true); err == nil { - t.Fatalf("Expected error due to invalid name.") - } - - // Check that no more channels are in the DB. - th.CheckChannelsCount(t, channelCount) - - // Do a valid channel with a nonexistent team in dry-run mode. - data.Name = ptrStr("channelname") - data.Team = ptrStr(model.NewId()) - if err := th.App.ImportChannel(&data, true); err != nil { - t.Fatalf("Expected success as cannot validate channel name in dry run mode.") - } - - // Check that no more channels are in the DB. - th.CheckChannelsCount(t, channelCount) - - // Do a valid channel in dry-run mode. - data.Team = &teamName - if err := th.App.ImportChannel(&data, true); err != nil { - t.Fatalf("Expected success as valid team.") - } - - // Check that no more channels are in the DB. - th.CheckChannelsCount(t, channelCount) - - // Do an invalid channel in apply mode. - data.Name = nil - if err := th.App.ImportChannel(&data, false); err == nil { - t.Fatalf("Expected error due to invalid name (apply mode).") - } - - // Check that no more channels are in the DB. - th.CheckChannelsCount(t, channelCount) - - // Do a valid channel in apply mode with a non-existent team. - data.Name = ptrStr("channelname") - data.Team = ptrStr(model.NewId()) - if err := th.App.ImportChannel(&data, false); err == nil { - t.Fatalf("Expected error due to non-existent team (apply mode).") - } - - // Check that no more channels are in the DB. - th.CheckChannelsCount(t, channelCount) - - // Do a valid channel in apply mode. - data.Team = &teamName - if err := th.App.ImportChannel(&data, false); err != nil { - t.Fatalf("Expected success in apply mode: %v", err.Error()) - } - - // Check that 1 more channel is in the DB. - th.CheckChannelsCount(t, channelCount+1) - - // Get the Channel and check all the fields are correct. - if channel, err := th.App.GetChannelByName(*data.Name, team.Id, false); err != nil { - t.Fatalf("Failed to get channel from database.") - } else { - assert.Equal(t, *data.Name, channel.Name) - assert.Equal(t, *data.DisplayName, channel.DisplayName) - assert.Equal(t, *data.Type, channel.Type) - assert.Equal(t, *data.Header, channel.Header) - assert.Equal(t, *data.Purpose, channel.Purpose) - assert.Equal(t, scheme1.Id, *channel.SchemeId) - } - - // Alter all the fields of that channel. - data.DisplayName = ptrStr("Chaned Disp Name") - data.Type = ptrStr(model.CHANNEL_PRIVATE) - data.Header = ptrStr("New Header") - data.Purpose = ptrStr("New Purpose") - data.Scheme = &scheme2.Name - if err := th.App.ImportChannel(&data, false); err != nil { - t.Fatalf("Expected success in apply mode: %v", err.Error()) - } - - // Check channel count the same. - th.CheckChannelsCount(t, channelCount) - - // Get the Channel and check all the fields are correct. - if channel, err := th.App.GetChannelByName(*data.Name, team.Id, false); err != nil { - t.Fatalf("Failed to get channel from database.") - } else { - assert.Equal(t, *data.Name, channel.Name) - assert.Equal(t, *data.DisplayName, channel.DisplayName) - assert.Equal(t, *data.Type, channel.Type) - assert.Equal(t, *data.Header, channel.Header) - assert.Equal(t, *data.Purpose, channel.Purpose) - assert.Equal(t, scheme2.Id, *channel.SchemeId) - } - -} - -func TestImportImportUser(t *testing.T) { - th := Setup() - defer th.TearDown() - - // Check how many users are in the database. - var userCount int64 - if r := <-th.App.Srv.Store.User().GetTotalUsersCount(); r.Err == nil { - userCount = r.Data.(int64) - } else { - t.Fatalf("Failed to get user count.") - } - - // Do an invalid user in dry-run mode. - data := UserImportData{ - Username: ptrStr(model.NewId()), - } - if err := th.App.ImportUser(&data, true); err == nil { - t.Fatalf("Should have failed to import invalid user.") - } - - // Check that no more users are in the DB. - if r := <-th.App.Srv.Store.User().GetTotalUsersCount(); r.Err == nil { - if r.Data.(int64) != userCount { - t.Fatalf("Unexpected number of users") - } - } else { - t.Fatalf("Failed to get user count.") - } - - // Do a valid user in dry-run mode. - data = UserImportData{ - Username: ptrStr(model.NewId()), - Email: ptrStr(model.NewId() + "@example.com"), - } - if err := th.App.ImportUser(&data, true); err != nil { - t.Fatalf("Should have succeeded to import valid user.") - } - - // Check that no more users are in the DB. - if r := <-th.App.Srv.Store.User().GetTotalUsersCount(); r.Err == nil { - if r.Data.(int64) != userCount { - t.Fatalf("Unexpected number of users") - } - } else { - t.Fatalf("Failed to get user count.") - } - - // Do an invalid user in apply mode. - data = UserImportData{ - Username: ptrStr(model.NewId()), - } - if err := th.App.ImportUser(&data, false); err == nil { - t.Fatalf("Should have failed to import invalid user.") - } - - // Check that no more users are in the DB. - if r := <-th.App.Srv.Store.User().GetTotalUsersCount(); r.Err == nil { - if r.Data.(int64) != userCount { - t.Fatalf("Unexpected number of users") - } - } else { - t.Fatalf("Failed to get user count.") - } - - // Do a valid user in apply mode. - username := model.NewId() - testsDir, _ := utils.FindDir("tests") - data = UserImportData{ - ProfileImage: ptrStr(filepath.Join(testsDir, "test.png")), - Username: &username, - Email: ptrStr(model.NewId() + "@example.com"), - Nickname: ptrStr(model.NewId()), - FirstName: ptrStr(model.NewId()), - LastName: ptrStr(model.NewId()), - Position: ptrStr(model.NewId()), - } - if err := th.App.ImportUser(&data, false); err != nil { - t.Fatalf("Should have succeeded to import valid user.") - } - - // Check that one more user is in the DB. - if r := <-th.App.Srv.Store.User().GetTotalUsersCount(); r.Err == nil { - if r.Data.(int64) != userCount+1 { - t.Fatalf("Unexpected number of users") - } - } else { - t.Fatalf("Failed to get user count.") - } - - // Get the user and check all the fields are correct. - if user, err := th.App.GetUserByUsername(username); err != nil { - t.Fatalf("Failed to get user from database.") - } else { - if user.Email != *data.Email || user.Nickname != *data.Nickname || user.FirstName != *data.FirstName || user.LastName != *data.LastName || user.Position != *data.Position { - t.Fatalf("User properties do not match Import Data.") - } - // Check calculated properties. - if user.AuthService != "" { - t.Fatalf("Expected Auth Service to be empty.") - } - - if !(user.AuthData == nil || *user.AuthData == "") { - t.Fatalf("Expected AuthData to be empty.") - } - - if len(user.Password) == 0 { - t.Fatalf("Expected password to be set.") - } - - if !user.EmailVerified { - t.Fatalf("Expected EmailVerified to be true.") - } - - if user.Locale != *th.App.Config().LocalizationSettings.DefaultClientLocale { - t.Fatalf("Expected Locale to be the default.") - } - - if user.Roles != "system_user" { - t.Fatalf("Expected roles to be system_user") - } - } - - // Alter all the fields of that user. - data.Email = ptrStr(model.NewId() + "@example.com") - data.ProfileImage = ptrStr(filepath.Join(testsDir, "testgif.gif")) - data.AuthService = ptrStr("ldap") - data.AuthData = &username - data.Nickname = ptrStr(model.NewId()) - data.FirstName = ptrStr(model.NewId()) - data.LastName = ptrStr(model.NewId()) - data.Position = ptrStr(model.NewId()) - data.Roles = ptrStr("system_admin system_user") - data.Locale = ptrStr("zh_CN") - if err := th.App.ImportUser(&data, false); err != nil { - t.Fatalf("Should have succeeded to update valid user %v", err) - } - - // Check user count the same. - if r := <-th.App.Srv.Store.User().GetTotalUsersCount(); r.Err == nil { - if r.Data.(int64) != userCount+1 { - t.Fatalf("Unexpected number of users") - } - } else { - t.Fatalf("Failed to get user count.") - } - - // Get the user and check all the fields are correct. - if user, err := th.App.GetUserByUsername(username); err != nil { - t.Fatalf("Failed to get user from database.") - } else { - if user.Email != *data.Email || user.Nickname != *data.Nickname || user.FirstName != *data.FirstName || user.LastName != *data.LastName || user.Position != *data.Position { - t.Fatalf("Updated User properties do not match Import Data.") - } - // Check calculated properties. - if user.AuthService != "ldap" { - t.Fatalf("Expected Auth Service to be ldap \"%v\"", user.AuthService) - } - - if !(user.AuthData == data.AuthData || *user.AuthData == *data.AuthData) { - t.Fatalf("Expected AuthData to be set.") - } - - if len(user.Password) != 0 { - t.Fatalf("Expected password to be empty.") - } - - if !user.EmailVerified { - t.Fatalf("Expected EmailVerified to be true.") - } - - if user.Locale != *data.Locale { - t.Fatalf("Expected Locale to be the set.") - } - - if user.Roles != *data.Roles { - t.Fatalf("Expected roles to be set: %v", user.Roles) - } - } - - // Check Password and AuthData together. - data.Password = ptrStr("PasswordTest") - if err := th.App.ImportUser(&data, false); err == nil { - t.Fatalf("Should have failed to import invalid user.") - } - - data.AuthData = nil - if err := th.App.ImportUser(&data, false); err != nil { - t.Fatalf("Should have succeeded to update valid user %v", err) - } - - data.Password = ptrStr("") - if err := th.App.ImportUser(&data, false); err == nil { - t.Fatalf("Should have failed to import invalid user.") - } - - data.Password = ptrStr(strings.Repeat("0123456789", 10)) - if err := th.App.ImportUser(&data, false); err == nil { - t.Fatalf("Should have failed to import invalid user.") - } - - data.Password = ptrStr("TestPassword") - - // Test team and channel memberships - teamName := model.NewId() - th.App.ImportTeam(&TeamImportData{ - Name: &teamName, - DisplayName: ptrStr("Display Name"), - Type: ptrStr("O"), - }, false) - team, err := th.App.GetTeamByName(teamName) - if err != nil { - t.Fatalf("Failed to get team from database.") - } - - channelName := model.NewId() - th.App.ImportChannel(&ChannelImportData{ - Team: &teamName, - Name: &channelName, - DisplayName: ptrStr("Display Name"), - Type: ptrStr("O"), - }, false) - channel, err := th.App.GetChannelByName(channelName, team.Id, false) - if err != nil { - t.Fatalf("Failed to get channel from database.") - } - - username = model.NewId() - data = UserImportData{ - Username: &username, - Email: ptrStr(model.NewId() + "@example.com"), - Nickname: ptrStr(model.NewId()), - FirstName: ptrStr(model.NewId()), - LastName: ptrStr(model.NewId()), - Position: ptrStr(model.NewId()), - } - - teamMembers, err := th.App.GetTeamMembers(team.Id, 0, 1000) - if err != nil { - t.Fatalf("Failed to get team member count") - } - teamMemberCount := len(teamMembers) - - channelMemberCount, err := th.App.GetChannelMemberCount(channel.Id) - if err != nil { - t.Fatalf("Failed to get channel member count") - } - - // Test with an invalid team & channel membership in dry-run mode. - data.Teams = &[]UserTeamImportData{ - { - Roles: ptrStr("invalid"), - Channels: &[]UserChannelImportData{ - { - Roles: ptrStr("invalid"), - }, - }, - }, - } - if err := th.App.ImportUser(&data, true); err == nil { - t.Fatalf("Should have failed.") - } - - // Test with an unknown team name & invalid channel membership in dry-run mode. - data.Teams = &[]UserTeamImportData{ - { - Name: ptrStr(model.NewId()), - Channels: &[]UserChannelImportData{ - { - Roles: ptrStr("invalid"), - }, - }, - }, - } - if err := th.App.ImportUser(&data, true); err == nil { - t.Fatalf("Should have failed.") - } - - // Test with a valid team & invalid channel membership in dry-run mode. - data.Teams = &[]UserTeamImportData{ - { - Name: &teamName, - Channels: &[]UserChannelImportData{ - { - Roles: ptrStr("invalid"), - }, - }, - }, - } - if err := th.App.ImportUser(&data, true); err == nil { - t.Fatalf("Should have failed.") - } - - // Test with a valid team & unknown channel name in dry-run mode. - data.Teams = &[]UserTeamImportData{ - { - Name: &teamName, - Channels: &[]UserChannelImportData{ - { - Name: ptrStr(model.NewId()), - }, - }, - }, - } - if err := th.App.ImportUser(&data, true); err != nil { - t.Fatalf("Should have succeeded.") - } - - // Test with a valid team & valid channel name in dry-run mode. - data.Teams = &[]UserTeamImportData{ - { - Name: &teamName, - Channels: &[]UserChannelImportData{ - { - Name: &channelName, - }, - }, - }, - } - if err := th.App.ImportUser(&data, true); err != nil { - t.Fatalf("Should have succeeded.") - } - - // Check no new member objects were created because dry run mode. - if tmc, err := th.App.GetTeamMembers(team.Id, 0, 1000); err != nil { - t.Fatalf("Failed to get Team Member Count") - } else if len(tmc) != teamMemberCount { - t.Fatalf("Number of team members not as expected") - } - - if cmc, err := th.App.GetChannelMemberCount(channel.Id); err != nil { - t.Fatalf("Failed to get Channel Member Count") - } else if cmc != channelMemberCount { - t.Fatalf("Number of channel members not as expected") - } - - // Test with an invalid team & channel membership in apply mode. - data.Teams = &[]UserTeamImportData{ - { - Roles: ptrStr("invalid"), - Channels: &[]UserChannelImportData{ - { - Roles: ptrStr("invalid"), - }, - }, - }, - } - if err := th.App.ImportUser(&data, false); err == nil { - t.Fatalf("Should have failed.") - } - - // Test with an unknown team name & invalid channel membership in apply mode. - data.Teams = &[]UserTeamImportData{ - { - Name: ptrStr(model.NewId()), - Channels: &[]UserChannelImportData{ - { - Roles: ptrStr("invalid"), - }, - }, - }, - } - if err := th.App.ImportUser(&data, false); err == nil { - t.Fatalf("Should have failed.") - } - - // Test with a valid team & invalid channel membership in apply mode. - data.Teams = &[]UserTeamImportData{ - { - Name: &teamName, - Channels: &[]UserChannelImportData{ - { - Roles: ptrStr("invalid"), - }, - }, - }, - } - if err := th.App.ImportUser(&data, false); err == nil { - t.Fatalf("Should have failed.") - } - - // Check no new member objects were created because all tests should have failed so far. - if tmc, err := th.App.GetTeamMembers(team.Id, 0, 1000); err != nil { - t.Fatalf("Failed to get Team Member Count") - } else if len(tmc) != teamMemberCount { - t.Fatalf("Number of team members not as expected") - } - - if cmc, err := th.App.GetChannelMemberCount(channel.Id); err != nil { - t.Fatalf("Failed to get Channel Member Count") - } else if cmc != channelMemberCount { - t.Fatalf("Number of channel members not as expected") - } - - // Test with a valid team & unknown channel name in apply mode. - data.Teams = &[]UserTeamImportData{ - { - Name: &teamName, - Channels: &[]UserChannelImportData{ - { - Name: ptrStr(model.NewId()), - }, - }, - }, - } - if err := th.App.ImportUser(&data, false); err == nil { - t.Fatalf("Should have failed.") - } - - // Check only new team member object created because dry run mode. - if tmc, err := th.App.GetTeamMembers(team.Id, 0, 1000); err != nil { - t.Fatalf("Failed to get Team Member Count") - } else if len(tmc) != teamMemberCount+1 { - t.Fatalf("Number of team members not as expected") - } - - if cmc, err := th.App.GetChannelMemberCount(channel.Id); err != nil { - t.Fatalf("Failed to get Channel Member Count") - } else if cmc != channelMemberCount { - t.Fatalf("Number of channel members not as expected") - } - - // Check team member properties. - user, err := th.App.GetUserByUsername(username) - if err != nil { - t.Fatalf("Failed to get user from database.") - } - if teamMember, err := th.App.GetTeamMember(team.Id, user.Id); err != nil { - t.Fatalf("Failed to get team member from database.") - } else if teamMember.Roles != "team_user" { - t.Fatalf("Team member properties not as expected") - } - - // Test with a valid team & valid channel name in apply mode. - data.Teams = &[]UserTeamImportData{ - { - Name: &teamName, - Channels: &[]UserChannelImportData{ - { - Name: &channelName, - }, - }, - }, - } - if err := th.App.ImportUser(&data, false); err != nil { - t.Fatalf("Should have succeeded.") - } - - // Check only new channel member object created because dry run mode. - if tmc, err := th.App.GetTeamMembers(team.Id, 0, 1000); err != nil { - t.Fatalf("Failed to get Team Member Count") - } else if len(tmc) != teamMemberCount+1 { - t.Fatalf("Number of team members not as expected") - } - - if cmc, err := th.App.GetChannelMemberCount(channel.Id); err != nil { - t.Fatalf("Failed to get Channel Member Count") - } else if cmc != channelMemberCount+1 { - t.Fatalf("Number of channel members not as expected") - } - - // Check channel member properties. - if channelMember, err := th.App.GetChannelMember(channel.Id, user.Id); err != nil { - t.Fatalf("Failed to get channel member from database.") - } else if channelMember.Roles != "channel_user" || channelMember.NotifyProps[model.DESKTOP_NOTIFY_PROP] != "default" || channelMember.NotifyProps[model.PUSH_NOTIFY_PROP] != "default" || channelMember.NotifyProps[model.MARK_UNREAD_NOTIFY_PROP] != "all" { - t.Fatalf("Channel member properties not as expected") - } - - // Test with the properties of the team and channel membership changed. - data.Teams = &[]UserTeamImportData{ - { - Name: &teamName, - Roles: ptrStr("team_user team_admin"), - Channels: &[]UserChannelImportData{ - { - Name: &channelName, - Roles: ptrStr("channel_user channel_admin"), - NotifyProps: &UserChannelNotifyPropsImportData{ - Desktop: ptrStr(model.USER_NOTIFY_MENTION), - Mobile: ptrStr(model.USER_NOTIFY_MENTION), - MarkUnread: ptrStr(model.USER_NOTIFY_MENTION), - }, - Favorite: ptrBool(true), - }, - }, - }, - } - if err := th.App.ImportUser(&data, false); err != nil { - t.Fatalf("Should have succeeded.") - } - - // Check both member properties. - if teamMember, err := th.App.GetTeamMember(team.Id, user.Id); err != nil { - t.Fatalf("Failed to get team member from database.") - } else if teamMember.Roles != "team_user team_admin" { - t.Fatalf("Team member properties not as expected: %v", teamMember.Roles) - } - - if channelMember, err := th.App.GetChannelMember(channel.Id, user.Id); err != nil { - t.Fatalf("Failed to get channel member Desktop from database.") - } else if channelMember.Roles != "channel_user channel_admin" || channelMember.NotifyProps[model.DESKTOP_NOTIFY_PROP] != model.USER_NOTIFY_MENTION || channelMember.NotifyProps[model.PUSH_NOTIFY_PROP] != model.USER_NOTIFY_MENTION || channelMember.NotifyProps[model.MARK_UNREAD_NOTIFY_PROP] != model.USER_NOTIFY_MENTION { - t.Fatalf("Channel member properties not as expected") - } - - checkPreference(t, th.App, user.Id, model.PREFERENCE_CATEGORY_FAVORITE_CHANNEL, channel.Id, "true") - - // No more new member objects. - if tmc, err := th.App.GetTeamMembers(team.Id, 0, 1000); err != nil { - t.Fatalf("Failed to get Team Member Count") - } else if len(tmc) != teamMemberCount+1 { - t.Fatalf("Number of team members not as expected") - } - - if cmc, err := th.App.GetChannelMemberCount(channel.Id); err != nil { - t.Fatalf("Failed to get Channel Member Count") - } else if cmc != channelMemberCount+1 { - t.Fatalf("Number of channel members not as expected") - } - - // Add a user with some preferences. - username = model.NewId() - data = UserImportData{ - Username: &username, - Email: ptrStr(model.NewId() + "@example.com"), - Theme: ptrStr(`{"awayIndicator":"#DCBD4E","buttonBg":"#23A2FF","buttonColor":"#FFFFFF","centerChannelBg":"#ffffff","centerChannelColor":"#333333","codeTheme":"github","image":"/static/files/a4a388b38b32678e83823ef1b3e17766.png","linkColor":"#2389d7","mentionBg":"#2389d7","mentionColor":"#ffffff","mentionHighlightBg":"#fff2bb","mentionHighlightLink":"#2f81b7","newMessageSeparator":"#FF8800","onlineIndicator":"#7DBE00","sidebarBg":"#fafafa","sidebarHeaderBg":"#3481B9","sidebarHeaderTextColor":"#ffffff","sidebarText":"#333333","sidebarTextActiveBorder":"#378FD2","sidebarTextActiveColor":"#111111","sidebarTextHoverBg":"#e6f2fa","sidebarUnreadText":"#333333","type":"Mattermost"}`), - UseMilitaryTime: ptrStr("true"), - CollapsePreviews: ptrStr("true"), - MessageDisplay: ptrStr("compact"), - ChannelDisplayMode: ptrStr("centered"), - TutorialStep: ptrStr("3"), - } - if err := th.App.ImportUser(&data, false); err != nil { - t.Fatalf("Should have succeeded.") - } - - // Check their values. - user, err = th.App.GetUserByUsername(username) - if err != nil { - t.Fatalf("Failed to get user from database.") - } - - checkPreference(t, th.App, user.Id, model.PREFERENCE_CATEGORY_THEME, "", *data.Theme) - checkPreference(t, th.App, user.Id, model.PREFERENCE_CATEGORY_DISPLAY_SETTINGS, "use_military_time", *data.UseMilitaryTime) - checkPreference(t, th.App, user.Id, model.PREFERENCE_CATEGORY_DISPLAY_SETTINGS, "collapse_previews", *data.CollapsePreviews) - checkPreference(t, th.App, user.Id, model.PREFERENCE_CATEGORY_DISPLAY_SETTINGS, "message_display", *data.MessageDisplay) - checkPreference(t, th.App, user.Id, model.PREFERENCE_CATEGORY_DISPLAY_SETTINGS, "channel_display_mode", *data.ChannelDisplayMode) - checkPreference(t, th.App, user.Id, model.PREFERENCE_CATEGORY_TUTORIAL_STEPS, user.Id, *data.TutorialStep) - - // Change those preferences. - data = UserImportData{ - Username: &username, - Email: ptrStr(model.NewId() + "@example.com"), - Theme: ptrStr(`{"awayIndicator":"#123456","buttonBg":"#23A2FF","buttonColor":"#FFFFFF","centerChannelBg":"#ffffff","centerChannelColor":"#333333","codeTheme":"github","image":"/static/files/a4a388b38b32678e83823ef1b3e17766.png","linkColor":"#2389d7","mentionBg":"#2389d7","mentionColor":"#ffffff","mentionHighlightBg":"#fff2bb","mentionHighlightLink":"#2f81b7","newMessageSeparator":"#FF8800","onlineIndicator":"#7DBE00","sidebarBg":"#fafafa","sidebarHeaderBg":"#3481B9","sidebarHeaderTextColor":"#ffffff","sidebarText":"#333333","sidebarTextActiveBorder":"#378FD2","sidebarTextActiveColor":"#111111","sidebarTextHoverBg":"#e6f2fa","sidebarUnreadText":"#333333","type":"Mattermost"}`), - UseMilitaryTime: ptrStr("false"), - CollapsePreviews: ptrStr("false"), - MessageDisplay: ptrStr("clean"), - ChannelDisplayMode: ptrStr("full"), - TutorialStep: ptrStr("2"), - } - if err := th.App.ImportUser(&data, false); err != nil { - t.Fatalf("Should have succeeded.") - } - - // Check their values again. - checkPreference(t, th.App, user.Id, model.PREFERENCE_CATEGORY_THEME, "", *data.Theme) - checkPreference(t, th.App, user.Id, model.PREFERENCE_CATEGORY_DISPLAY_SETTINGS, "use_military_time", *data.UseMilitaryTime) - checkPreference(t, th.App, user.Id, model.PREFERENCE_CATEGORY_DISPLAY_SETTINGS, "collapse_previews", *data.CollapsePreviews) - checkPreference(t, th.App, user.Id, model.PREFERENCE_CATEGORY_DISPLAY_SETTINGS, "message_display", *data.MessageDisplay) - checkPreference(t, th.App, user.Id, model.PREFERENCE_CATEGORY_DISPLAY_SETTINGS, "channel_display_mode", *data.ChannelDisplayMode) - checkPreference(t, th.App, user.Id, model.PREFERENCE_CATEGORY_TUTORIAL_STEPS, user.Id, *data.TutorialStep) - - // Set Notify Props - data.NotifyProps = &UserNotifyPropsImportData{ - Desktop: ptrStr(model.USER_NOTIFY_ALL), - DesktopSound: ptrStr("true"), - Email: ptrStr("true"), - Mobile: ptrStr(model.USER_NOTIFY_ALL), - MobilePushStatus: ptrStr(model.STATUS_ONLINE), - ChannelTrigger: ptrStr("true"), - CommentsTrigger: ptrStr(model.COMMENTS_NOTIFY_ROOT), - MentionKeys: ptrStr("valid,misc"), - } - if err := th.App.ImportUser(&data, false); err != nil { - t.Fatalf("Should have succeeded.") - } - - user, err = th.App.GetUserByUsername(username) - if err != nil { - t.Fatalf("Failed to get user from database.") - } - - checkNotifyProp(t, user, model.DESKTOP_NOTIFY_PROP, model.USER_NOTIFY_ALL) - checkNotifyProp(t, user, model.DESKTOP_SOUND_NOTIFY_PROP, "true") - checkNotifyProp(t, user, model.EMAIL_NOTIFY_PROP, "true") - checkNotifyProp(t, user, model.PUSH_NOTIFY_PROP, model.USER_NOTIFY_ALL) - checkNotifyProp(t, user, model.PUSH_STATUS_NOTIFY_PROP, model.STATUS_ONLINE) - checkNotifyProp(t, user, model.CHANNEL_MENTIONS_NOTIFY_PROP, "true") - checkNotifyProp(t, user, model.COMMENTS_NOTIFY_PROP, model.COMMENTS_NOTIFY_ROOT) - checkNotifyProp(t, user, model.MENTION_KEYS_NOTIFY_PROP, "valid,misc") - - // Change Notify Props - data.NotifyProps = &UserNotifyPropsImportData{ - Desktop: ptrStr(model.USER_NOTIFY_MENTION), - DesktopSound: ptrStr("false"), - Email: ptrStr("false"), - Mobile: ptrStr(model.USER_NOTIFY_NONE), - MobilePushStatus: ptrStr(model.STATUS_AWAY), - ChannelTrigger: ptrStr("false"), - CommentsTrigger: ptrStr(model.COMMENTS_NOTIFY_ANY), - MentionKeys: ptrStr("misc"), - } - if err := th.App.ImportUser(&data, false); err != nil { - t.Fatalf("Should have succeeded.") - } - - user, err = th.App.GetUserByUsername(username) - if err != nil { - t.Fatalf("Failed to get user from database.") - } - - checkNotifyProp(t, user, model.DESKTOP_NOTIFY_PROP, model.USER_NOTIFY_MENTION) - checkNotifyProp(t, user, model.DESKTOP_SOUND_NOTIFY_PROP, "false") - checkNotifyProp(t, user, model.EMAIL_NOTIFY_PROP, "false") - checkNotifyProp(t, user, model.PUSH_NOTIFY_PROP, model.USER_NOTIFY_NONE) - checkNotifyProp(t, user, model.PUSH_STATUS_NOTIFY_PROP, model.STATUS_AWAY) - checkNotifyProp(t, user, model.CHANNEL_MENTIONS_NOTIFY_PROP, "false") - checkNotifyProp(t, user, model.COMMENTS_NOTIFY_PROP, model.COMMENTS_NOTIFY_ANY) - checkNotifyProp(t, user, model.MENTION_KEYS_NOTIFY_PROP, "misc") - - // Check Notify Props get set on *create* user. - username = model.NewId() - data = UserImportData{ - Username: &username, - Email: ptrStr(model.NewId() + "@example.com"), - } - data.NotifyProps = &UserNotifyPropsImportData{ - Desktop: ptrStr(model.USER_NOTIFY_MENTION), - DesktopSound: ptrStr("false"), - Email: ptrStr("false"), - Mobile: ptrStr(model.USER_NOTIFY_NONE), - MobilePushStatus: ptrStr(model.STATUS_AWAY), - ChannelTrigger: ptrStr("false"), - CommentsTrigger: ptrStr(model.COMMENTS_NOTIFY_ANY), - MentionKeys: ptrStr("misc"), - } - - if err := th.App.ImportUser(&data, false); err != nil { - t.Fatalf("Should have succeeded.") - } - - user, err = th.App.GetUserByUsername(username) - if err != nil { - t.Fatalf("Failed to get user from database.") - } - - checkNotifyProp(t, user, model.DESKTOP_NOTIFY_PROP, model.USER_NOTIFY_MENTION) - checkNotifyProp(t, user, model.DESKTOP_SOUND_NOTIFY_PROP, "false") - checkNotifyProp(t, user, model.EMAIL_NOTIFY_PROP, "false") - checkNotifyProp(t, user, model.PUSH_NOTIFY_PROP, model.USER_NOTIFY_NONE) - checkNotifyProp(t, user, model.PUSH_STATUS_NOTIFY_PROP, model.STATUS_AWAY) - checkNotifyProp(t, user, model.CHANNEL_MENTIONS_NOTIFY_PROP, "false") - checkNotifyProp(t, user, model.COMMENTS_NOTIFY_PROP, model.COMMENTS_NOTIFY_ANY) - checkNotifyProp(t, user, model.MENTION_KEYS_NOTIFY_PROP, "misc") - - // Test importing a user with roles set to a team and a channel which are affected by an override scheme. - // The import subsystem should translate `channel_admin/channel_user/team_admin/team_user` - // to the appropriate scheme-managed-role booleans. - - // Mark the phase 2 permissions migration as completed. - <-th.App.Srv.Store.System().Save(&model.System{Name: model.MIGRATION_KEY_ADVANCED_PERMISSIONS_PHASE_2, Value: "true"}) - - defer func() { - <-th.App.Srv.Store.System().PermanentDeleteByName(model.MIGRATION_KEY_ADVANCED_PERMISSIONS_PHASE_2) - }() - - teamSchemeData := &SchemeImportData{ - Name: ptrStr(model.NewId()), - DisplayName: ptrStr(model.NewId()), - Scope: ptrStr("team"), - DefaultTeamUserRole: &RoleImportData{ - Name: ptrStr(model.NewId()), - DisplayName: ptrStr(model.NewId()), - }, - DefaultTeamAdminRole: &RoleImportData{ - Name: ptrStr(model.NewId()), - DisplayName: ptrStr(model.NewId()), - }, - DefaultChannelUserRole: &RoleImportData{ - Name: ptrStr(model.NewId()), - DisplayName: ptrStr(model.NewId()), - }, - DefaultChannelAdminRole: &RoleImportData{ - Name: ptrStr(model.NewId()), - DisplayName: ptrStr(model.NewId()), - }, - Description: ptrStr("description"), - } - - if err := th.App.ImportScheme(teamSchemeData, false); err != nil { - t.Fatalf("Should have succeeded.") - } - - var teamScheme *model.Scheme - if res := <-th.App.Srv.Store.Scheme().GetByName(*teamSchemeData.Name); res.Err != nil { - t.Fatalf("Failed to import scheme: %v", res.Err) - } else { - teamScheme = res.Data.(*model.Scheme) - } - - teamData := &TeamImportData{ - Name: ptrStr(model.NewId()), - DisplayName: ptrStr("Display Name"), - Type: ptrStr("O"), - Description: ptrStr("The team description."), - AllowOpenInvite: ptrBool(true), - Scheme: &teamScheme.Name, - } - if err := th.App.ImportTeam(teamData, false); err != nil { - t.Fatalf("Import should have succeeded: %v", err.Error()) - } - team, err = th.App.GetTeamByName(teamName) - if err != nil { - t.Fatalf("Failed to get team from database.") - } - - channelData := &ChannelImportData{ - Team: &teamName, - Name: ptrStr(model.NewId()), - DisplayName: ptrStr("Display Name"), - Type: ptrStr("O"), - Header: ptrStr("Channe Header"), - Purpose: ptrStr("Channel Purpose"), - } - if err := th.App.ImportChannel(channelData, false); err != nil { - t.Fatalf("Import should have succeeded.") - } - channel, err = th.App.GetChannelByName(*channelData.Name, team.Id, false) - if err != nil { - t.Fatalf("Failed to get channel from database: %v", err.Error()) - } - - // Test with a valid team & valid channel name in apply mode. - userData := &UserImportData{ - Username: &username, - Email: ptrStr(model.NewId() + "@example.com"), - Teams: &[]UserTeamImportData{ - { - Name: &team.Name, - Roles: ptrStr("team_user team_admin"), - Channels: &[]UserChannelImportData{ - { - Name: &channel.Name, - Roles: ptrStr("channel_admin channel_user"), - }, - }, - }, - }, - } - if err := th.App.ImportUser(userData, false); err != nil { - t.Fatalf("Should have succeeded.") - } - - user, err = th.App.GetUserByUsername(*userData.Username) - if err != nil { - t.Fatalf("Failed to get user from database.") - } - - teamMember, err := th.App.GetTeamMember(team.Id, user.Id) - if err != nil { - t.Fatalf("Failed to get the team member") - } - assert.True(t, teamMember.SchemeAdmin) - assert.True(t, teamMember.SchemeUser) - assert.Equal(t, "", teamMember.ExplicitRoles) - - channelMember, err := th.App.GetChannelMember(channel.Id, user.Id) - if err != nil { - t.Fatalf("Failed to get the channel member") - } - assert.True(t, channelMember.SchemeAdmin) - assert.True(t, channelMember.SchemeUser) - assert.Equal(t, "", channelMember.ExplicitRoles) - -} - -func AssertAllPostsCount(t *testing.T, a *App, initialCount int64, change int64, teamName string) { - if result := <-a.Srv.Store.Post().AnalyticsPostCount(teamName, false, false); result.Err != nil { - t.Fatal(result.Err) - } else { - if initialCount+change != result.Data.(int64) { - debug.PrintStack() - t.Fatalf("Did not find the expected number of posts.") - } - } -} - -func TestImportImportPost(t *testing.T) { - th := Setup() - defer th.TearDown() - - // Create a Team. - teamName := model.NewId() - th.App.ImportTeam(&TeamImportData{ - Name: &teamName, - DisplayName: ptrStr("Display Name"), - Type: ptrStr("O"), - }, false) - team, err := th.App.GetTeamByName(teamName) - if err != nil { - t.Fatalf("Failed to get team from database.") - } - - // Create a Channel. - channelName := model.NewId() - th.App.ImportChannel(&ChannelImportData{ - Team: &teamName, - Name: &channelName, - DisplayName: ptrStr("Display Name"), - Type: ptrStr("O"), - }, false) - channel, err := th.App.GetChannelByName(channelName, team.Id, false) - if err != nil { - t.Fatalf("Failed to get channel from database.") - } - - // Create a user. - username := model.NewId() - th.App.ImportUser(&UserImportData{ - Username: &username, - Email: ptrStr(model.NewId() + "@example.com"), - }, false) - user, err := th.App.GetUserByUsername(username) - if err != nil { - t.Fatalf("Failed to get user from database.") - } - - // Count the number of posts in the testing team. - var initialPostCount int64 - if result := <-th.App.Srv.Store.Post().AnalyticsPostCount(team.Id, false, false); result.Err != nil { - t.Fatal(result.Err) - } else { - initialPostCount = result.Data.(int64) - } - - // Try adding an invalid post in dry run mode. - data := &PostImportData{ - Team: &teamName, - Channel: &channelName, - User: &username, - } - if err := th.App.ImportPost(data, true); err == nil { - t.Fatalf("Expected error.") - } - AssertAllPostsCount(t, th.App, initialPostCount, 0, team.Id) - - // Try adding a valid post in dry run mode. - data = &PostImportData{ - Team: &teamName, - Channel: &channelName, - User: &username, - Message: ptrStr("Hello"), - CreateAt: ptrInt64(model.GetMillis()), - } - if err := th.App.ImportPost(data, true); err != nil { - t.Fatalf("Expected success.") - } - AssertAllPostsCount(t, th.App, initialPostCount, 0, team.Id) - - // Try adding an invalid post in apply mode. - data = &PostImportData{ - Team: &teamName, - Channel: &channelName, - User: &username, - CreateAt: ptrInt64(model.GetMillis()), - } - if err := th.App.ImportPost(data, false); err == nil { - t.Fatalf("Expected error.") - } - AssertAllPostsCount(t, th.App, initialPostCount, 0, team.Id) - - // Try adding a valid post with invalid team in apply mode. - data = &PostImportData{ - Team: ptrStr(model.NewId()), - Channel: &channelName, - User: &username, - Message: ptrStr("Message"), - CreateAt: ptrInt64(model.GetMillis()), - } - if err := th.App.ImportPost(data, false); err == nil { - t.Fatalf("Expected error.") - } - AssertAllPostsCount(t, th.App, initialPostCount, 0, team.Id) - - // Try adding a valid post with invalid channel in apply mode. - data = &PostImportData{ - Team: &teamName, - Channel: ptrStr(model.NewId()), - User: &username, - Message: ptrStr("Message"), - CreateAt: ptrInt64(model.GetMillis()), - } - if err := th.App.ImportPost(data, false); err == nil { - t.Fatalf("Expected error.") - } - AssertAllPostsCount(t, th.App, initialPostCount, 0, team.Id) - - // Try adding a valid post with invalid user in apply mode. - data = &PostImportData{ - Team: &teamName, - Channel: &channelName, - User: ptrStr(model.NewId()), - Message: ptrStr("Message"), - CreateAt: ptrInt64(model.GetMillis()), - } - if err := th.App.ImportPost(data, false); err == nil { - t.Fatalf("Expected error.") - } - AssertAllPostsCount(t, th.App, initialPostCount, 0, team.Id) - - // Try adding a valid post in apply mode. - time := model.GetMillis() - data = &PostImportData{ - Team: &teamName, - Channel: &channelName, - User: &username, - Message: ptrStr("Message"), - CreateAt: &time, - } - if err := th.App.ImportPost(data, false); err != nil { - t.Fatalf("Expected success.") - } - AssertAllPostsCount(t, th.App, initialPostCount, 1, team.Id) - - // Check the post values. - if result := <-th.App.Srv.Store.Post().GetPostsCreatedAt(channel.Id, time); result.Err != nil { - t.Fatal(result.Err.Error()) - } else { - posts := result.Data.([]*model.Post) - if len(posts) != 1 { - t.Fatal("Unexpected number of posts found.") - } - post := posts[0] - if post.Message != *data.Message || post.CreateAt != *data.CreateAt || post.UserId != user.Id { - t.Fatal("Post properties not as expected") - } - } - - // Update the post. - data = &PostImportData{ - Team: &teamName, - Channel: &channelName, - User: &username, - Message: ptrStr("Message"), - CreateAt: &time, - } - if err := th.App.ImportPost(data, false); err != nil { - t.Fatalf("Expected success.") - } - AssertAllPostsCount(t, th.App, initialPostCount, 1, team.Id) - - // Check the post values. - if result := <-th.App.Srv.Store.Post().GetPostsCreatedAt(channel.Id, time); result.Err != nil { - t.Fatal(result.Err.Error()) - } else { - posts := result.Data.([]*model.Post) - if len(posts) != 1 { - t.Fatal("Unexpected number of posts found.") - } - post := posts[0] - if post.Message != *data.Message || post.CreateAt != *data.CreateAt || post.UserId != user.Id { - t.Fatal("Post properties not as expected") - } - } - - // Save the post with a different time. - newTime := time + 1 - data = &PostImportData{ - Team: &teamName, - Channel: &channelName, - User: &username, - Message: ptrStr("Message"), - CreateAt: &newTime, - } - if err := th.App.ImportPost(data, false); err != nil { - t.Fatalf("Expected success.") - } - AssertAllPostsCount(t, th.App, initialPostCount, 2, team.Id) - - // Save the post with a different message. - data = &PostImportData{ - Team: &teamName, - Channel: &channelName, - User: &username, - Message: ptrStr("Message 2"), - CreateAt: &time, - } - if err := th.App.ImportPost(data, false); err != nil { - t.Fatalf("Expected success.") - } - AssertAllPostsCount(t, th.App, initialPostCount, 3, team.Id) - - // Test with hashtags - hashtagTime := time + 2 - data = &PostImportData{ - Team: &teamName, - Channel: &channelName, - User: &username, - Message: ptrStr("Message 2 #hashtagmashupcity"), - CreateAt: &hashtagTime, - } - if err := th.App.ImportPost(data, false); err != nil { - t.Fatalf("Expected success.") - } - AssertAllPostsCount(t, th.App, initialPostCount, 4, team.Id) - - if result := <-th.App.Srv.Store.Post().GetPostsCreatedAt(channel.Id, hashtagTime); result.Err != nil { - t.Fatal(result.Err.Error()) - } else { - posts := result.Data.([]*model.Post) - if len(posts) != 1 { - t.Fatal("Unexpected number of posts found.") - } - post := posts[0] - if post.Message != *data.Message || post.CreateAt != *data.CreateAt || post.UserId != user.Id { - t.Fatal("Post properties not as expected") - } - if post.Hashtags != "#hashtagmashupcity" { - t.Fatalf("Hashtags not as expected: %s", post.Hashtags) - } - } - - // Post with flags. - username2 := model.NewId() - th.App.ImportUser(&UserImportData{ - Username: &username2, - Email: ptrStr(model.NewId() + "@example.com"), - }, false) - user2, err := th.App.GetUserByUsername(username2) - if err != nil { - t.Fatalf("Failed to get user from database.") - } - - flagsTime := hashtagTime + 1 - data = &PostImportData{ - Team: &teamName, - Channel: &channelName, - User: &username, - Message: ptrStr("Message with Favorites"), - CreateAt: &flagsTime, - FlaggedBy: &[]string{ - username, - username2, - }, - } - if err := th.App.ImportPost(data, false); err != nil { - t.Fatalf("Expected success.") - } - AssertAllPostsCount(t, th.App, initialPostCount, 5, team.Id) - - // Check the post values. - if result := <-th.App.Srv.Store.Post().GetPostsCreatedAt(channel.Id, flagsTime); result.Err != nil { - t.Fatal(result.Err.Error()) - } else { - posts := result.Data.([]*model.Post) - if len(posts) != 1 { - t.Fatal("Unexpected number of posts found.") - } - post := posts[0] - if post.Message != *data.Message || post.CreateAt != *data.CreateAt || post.UserId != user.Id { - t.Fatal("Post properties not as expected") - } - - checkPreference(t, th.App, user.Id, model.PREFERENCE_CATEGORY_FLAGGED_POST, post.Id, "true") - checkPreference(t, th.App, user2.Id, model.PREFERENCE_CATEGORY_FLAGGED_POST, post.Id, "true") - } - - // Post with reaction. - reactionPostTime := hashtagTime + 2 - reactionTime := hashtagTime + 3 - data = &PostImportData{ - Team: &teamName, - Channel: &channelName, - User: &username, - Message: ptrStr("Message with reaction"), - CreateAt: &reactionPostTime, - Reactions: &[]ReactionImportData{{ - User: &user2.Username, - EmojiName: ptrStr("+1"), - CreateAt: &reactionTime, - }}, - } - if err := th.App.ImportPost(data, false); err != nil { - t.Fatalf("Expected success.") - } - AssertAllPostsCount(t, th.App, initialPostCount, 6, team.Id) - - // Check the post values. - if result := <-th.App.Srv.Store.Post().GetPostsCreatedAt(channel.Id, reactionPostTime); result.Err != nil { - t.Fatal(result.Err.Error()) - } else { - posts := result.Data.([]*model.Post) - if len(posts) != 1 { - t.Fatal("Unexpected number of posts found.") - } - post := posts[0] - if post.Message != *data.Message || post.CreateAt != *data.CreateAt || post.UserId != user.Id || !post.HasReactions { - t.Fatal("Post properties not as expected") - } - - if result := <-th.App.Srv.Store.Reaction().GetForPost(post.Id, false); result.Err != nil { - t.Fatal("Can't get reaction") - } else if len(result.Data.([]*model.Reaction)) != 1 { - t.Fatal("Invalid number of reactions") - } - } - - // Post with reply. - replyPostTime := hashtagTime + 4 - replyTime := hashtagTime + 5 - data = &PostImportData{ - Team: &teamName, - Channel: &channelName, - User: &username, - Message: ptrStr("Message with reply"), - CreateAt: &replyPostTime, - Replies: &[]ReplyImportData{{ - User: &user2.Username, - Message: ptrStr("Message reply"), - CreateAt: &replyTime, - }}, - } - if err := th.App.ImportPost(data, false); err != nil { - t.Fatalf("Expected success.") - } - AssertAllPostsCount(t, th.App, initialPostCount, 8, team.Id) - - // Check the post values. - if result := <-th.App.Srv.Store.Post().GetPostsCreatedAt(channel.Id, replyPostTime); result.Err != nil { - t.Fatal(result.Err.Error()) - } else { - posts := result.Data.([]*model.Post) - if len(posts) != 1 { - t.Fatal("Unexpected number of posts found.") - } - post := posts[0] - if post.Message != *data.Message || post.CreateAt != *data.CreateAt || post.UserId != user.Id { - t.Fatal("Post properties not as expected") - } - - // Check the reply values. - if result := <-th.App.Srv.Store.Post().GetPostsCreatedAt(channel.Id, replyTime); result.Err != nil { - t.Fatal(result.Err.Error()) - } else { - replies := result.Data.([]*model.Post) - if len(replies) != 1 { - t.Fatal("Unexpected number of posts found.") - } - reply := replies[0] - if reply.Message != *(*data.Replies)[0].Message || reply.CreateAt != *(*data.Replies)[0].CreateAt || reply.UserId != user2.Id { - t.Fatal("Post properties not as expected") - } - - if reply.RootId != post.Id { - t.Fatal("Unexpected reply RootId") - } - } - } -} - -func TestImportImportDirectChannel(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - // Check how many channels are in the database. - var directChannelCount int64 - if r := <-th.App.Srv.Store.Channel().AnalyticsTypeCount("", model.CHANNEL_DIRECT); r.Err == nil { - directChannelCount = r.Data.(int64) - } else { - t.Fatalf("Failed to get direct channel count.") - } - - var groupChannelCount int64 - if r := <-th.App.Srv.Store.Channel().AnalyticsTypeCount("", model.CHANNEL_GROUP); r.Err == nil { - groupChannelCount = r.Data.(int64) - } else { - t.Fatalf("Failed to get group channel count.") - } - - // Do an invalid channel in dry-run mode. - data := DirectChannelImportData{ - Members: &[]string{ - model.NewId(), - }, - Header: ptrStr("Channel Header"), - } - if err := th.App.ImportDirectChannel(&data, true); err == nil { - t.Fatalf("Expected error due to invalid name.") - } - - // Check that no more channels are in the DB. - AssertChannelCount(t, th.App, model.CHANNEL_DIRECT, directChannelCount) - AssertChannelCount(t, th.App, model.CHANNEL_GROUP, groupChannelCount) - - // Do a valid DIRECT channel with a nonexistent member in dry-run mode. - data.Members = &[]string{ - model.NewId(), - model.NewId(), - } - if err := th.App.ImportDirectChannel(&data, true); err != nil { - t.Fatalf("Expected success as cannot validate existence of channel members in dry run mode.") - } - - // Check that no more channels are in the DB. - AssertChannelCount(t, th.App, model.CHANNEL_DIRECT, directChannelCount) - AssertChannelCount(t, th.App, model.CHANNEL_GROUP, groupChannelCount) - - // Do a valid GROUP channel with a nonexistent member in dry-run mode. - data.Members = &[]string{ - model.NewId(), - model.NewId(), - model.NewId(), - } - if err := th.App.ImportDirectChannel(&data, true); err != nil { - t.Fatalf("Expected success as cannot validate existence of channel members in dry run mode.") - } - - // Check that no more channels are in the DB. - AssertChannelCount(t, th.App, model.CHANNEL_DIRECT, directChannelCount) - AssertChannelCount(t, th.App, model.CHANNEL_GROUP, groupChannelCount) - - // Do an invalid channel in apply mode. - data.Members = &[]string{ - model.NewId(), - } - if err := th.App.ImportDirectChannel(&data, false); err == nil { - t.Fatalf("Expected error due to invalid member (apply mode).") - } - - // Check that no more channels are in the DB. - AssertChannelCount(t, th.App, model.CHANNEL_DIRECT, directChannelCount) - AssertChannelCount(t, th.App, model.CHANNEL_GROUP, groupChannelCount) - - // Do a valid DIRECT channel. - data.Members = &[]string{ - th.BasicUser.Username, - th.BasicUser2.Username, - } - if err := th.App.ImportDirectChannel(&data, false); err != nil { - t.Fatalf("Expected success: %v", err.Error()) - } - - // Check that one more DIRECT channel is in the DB. - AssertChannelCount(t, th.App, model.CHANNEL_DIRECT, directChannelCount+1) - AssertChannelCount(t, th.App, model.CHANNEL_GROUP, groupChannelCount) - - // Do the same DIRECT channel again. - if err := th.App.ImportDirectChannel(&data, false); err != nil { - t.Fatalf("Expected success.") - } - - // Check that no more channels are in the DB. - AssertChannelCount(t, th.App, model.CHANNEL_DIRECT, directChannelCount+1) - AssertChannelCount(t, th.App, model.CHANNEL_GROUP, groupChannelCount) - - // Update the channel's HEADER - data.Header = ptrStr("New Channel Header 2") - if err := th.App.ImportDirectChannel(&data, false); err != nil { - t.Fatalf("Expected success.") - } - - // Check that no more channels are in the DB. - AssertChannelCount(t, th.App, model.CHANNEL_DIRECT, directChannelCount+1) - AssertChannelCount(t, th.App, model.CHANNEL_GROUP, groupChannelCount) - - // Get the channel to check that the header was updated. - if channel, err := th.App.createDirectChannel(th.BasicUser.Id, th.BasicUser2.Id); err == nil || err.Id != store.CHANNEL_EXISTS_ERROR { - t.Fatal("Should have got store.CHANNEL_EXISTS_ERROR") - } else { - if channel.Header != *data.Header { - t.Fatal("Channel header has not been updated successfully.") - } - } - - // Do a GROUP channel with an extra invalid member. - user3 := th.CreateUser() - data.Members = &[]string{ - th.BasicUser.Username, - th.BasicUser2.Username, - user3.Username, - model.NewId(), - } - if err := th.App.ImportDirectChannel(&data, false); err == nil { - t.Fatalf("Should have failed due to invalid member in list.") - } - - // Check that no more channels are in the DB. - AssertChannelCount(t, th.App, model.CHANNEL_DIRECT, directChannelCount+1) - AssertChannelCount(t, th.App, model.CHANNEL_GROUP, groupChannelCount) - - // Do a valid GROUP channel. - data.Members = &[]string{ - th.BasicUser.Username, - th.BasicUser2.Username, - user3.Username, - } - if err := th.App.ImportDirectChannel(&data, false); err != nil { - t.Fatalf("Expected success.") - } - - // Check that one more GROUP channel is in the DB. - AssertChannelCount(t, th.App, model.CHANNEL_DIRECT, directChannelCount+1) - AssertChannelCount(t, th.App, model.CHANNEL_GROUP, groupChannelCount+1) - - // Do the same DIRECT channel again. - if err := th.App.ImportDirectChannel(&data, false); err != nil { - t.Fatalf("Expected success.") - } - - // Check that no more channels are in the DB. - AssertChannelCount(t, th.App, model.CHANNEL_DIRECT, directChannelCount+1) - AssertChannelCount(t, th.App, model.CHANNEL_GROUP, groupChannelCount+1) - - // Update the channel's HEADER - data.Header = ptrStr("New Channel Header 3") - if err := th.App.ImportDirectChannel(&data, false); err != nil { - t.Fatalf("Expected success.") - } - - // Check that no more channels are in the DB. - AssertChannelCount(t, th.App, model.CHANNEL_DIRECT, directChannelCount+1) - AssertChannelCount(t, th.App, model.CHANNEL_GROUP, groupChannelCount+1) - - // Get the channel to check that the header was updated. - userIds := []string{ - th.BasicUser.Id, - th.BasicUser2.Id, - user3.Id, - } - if channel, err := th.App.createGroupChannel(userIds, th.BasicUser.Id); err.Id != store.CHANNEL_EXISTS_ERROR { - t.Fatal("Should have got store.CHANNEL_EXISTS_ERROR") +func AssertAllPostsCount(t *testing.T, a *App, initialCount int64, change int64, teamName string) { + if result := <-a.Srv.Store.Post().AnalyticsPostCount(teamName, false, false); result.Err != nil { + t.Fatal(result.Err) } else { - if channel.Header != *data.Header { - t.Fatal("Channel header has not been updated successfully.") + if initialCount+change != result.Data.(int64) { + debug.PrintStack() + t.Fatalf("Did not find the expected number of posts.") } } - - // Import a channel with some favorites. - data.Members = &[]string{ - th.BasicUser.Username, - th.BasicUser2.Username, - } - data.FavoritedBy = &[]string{ - th.BasicUser.Username, - th.BasicUser2.Username, - } - if err := th.App.ImportDirectChannel(&data, false); err != nil { - t.Fatal(err) - } - - if channel, err := th.App.createDirectChannel(th.BasicUser.Id, th.BasicUser2.Id); err == nil || err.Id != store.CHANNEL_EXISTS_ERROR { - t.Fatal("Should have got store.CHANNEL_EXISTS_ERROR") - } else { - checkPreference(t, th.App, th.BasicUser.Id, model.PREFERENCE_CATEGORY_FAVORITE_CHANNEL, channel.Id, "true") - checkPreference(t, th.App, th.BasicUser2.Id, model.PREFERENCE_CATEGORY_FAVORITE_CHANNEL, channel.Id, "true") - } } func AssertChannelCount(t *testing.T, a *App, channelType string, expectedCount int64) { @@ -3315,405 +103,6 @@ func AssertChannelCount(t *testing.T, a *App, channelType string, expectedCount } } -func TestImportImportDirectPost(t *testing.T) { - th := Setup().InitBasic() - defer th.TearDown() - - // Create the DIRECT channel. - channelData := DirectChannelImportData{ - Members: &[]string{ - th.BasicUser.Username, - th.BasicUser2.Username, - }, - } - if err := th.App.ImportDirectChannel(&channelData, false); err != nil { - t.Fatalf("Expected success: %v", err.Error()) - } - - // Get the channel. - var directChannel *model.Channel - if channel, err := th.App.createDirectChannel(th.BasicUser.Id, th.BasicUser2.Id); err.Id != store.CHANNEL_EXISTS_ERROR { - t.Fatal("Should have got store.CHANNEL_EXISTS_ERROR") - } else { - directChannel = channel - } - - // Get the number of posts in the system. - var initialPostCount int64 - if result := <-th.App.Srv.Store.Post().AnalyticsPostCount("", false, false); result.Err != nil { - t.Fatal(result.Err) - } else { - initialPostCount = result.Data.(int64) - } - - // Try adding an invalid post in dry run mode. - data := &DirectPostImportData{ - ChannelMembers: &[]string{ - th.BasicUser.Username, - th.BasicUser2.Username, - }, - User: ptrStr(th.BasicUser.Username), - CreateAt: ptrInt64(model.GetMillis()), - } - if err := th.App.ImportDirectPost(data, true); err == nil { - t.Fatalf("Expected error.") - } - AssertAllPostsCount(t, th.App, initialPostCount, 0, "") - - // Try adding a valid post in dry run mode. - data = &DirectPostImportData{ - ChannelMembers: &[]string{ - th.BasicUser.Username, - th.BasicUser2.Username, - }, - User: ptrStr(th.BasicUser.Username), - Message: ptrStr("Message"), - CreateAt: ptrInt64(model.GetMillis()), - } - if err := th.App.ImportDirectPost(data, true); err != nil { - t.Fatalf("Expected success.") - } - AssertAllPostsCount(t, th.App, initialPostCount, 0, "") - - // Try adding an invalid post in apply mode. - data = &DirectPostImportData{ - ChannelMembers: &[]string{ - th.BasicUser.Username, - model.NewId(), - }, - User: ptrStr(th.BasicUser.Username), - Message: ptrStr("Message"), - CreateAt: ptrInt64(model.GetMillis()), - } - if err := th.App.ImportDirectPost(data, false); err == nil { - t.Fatalf("Expected error.") - } - AssertAllPostsCount(t, th.App, initialPostCount, 0, "") - - // Try adding a valid post in apply mode. - data = &DirectPostImportData{ - ChannelMembers: &[]string{ - th.BasicUser.Username, - th.BasicUser2.Username, - }, - User: ptrStr(th.BasicUser.Username), - Message: ptrStr("Message"), - CreateAt: ptrInt64(model.GetMillis()), - } - if err := th.App.ImportDirectPost(data, false); err != nil { - t.Fatalf("Expected success: %v", err.Error()) - } - AssertAllPostsCount(t, th.App, initialPostCount, 1, "") - - // Check the post values. - if result := <-th.App.Srv.Store.Post().GetPostsCreatedAt(directChannel.Id, *data.CreateAt); result.Err != nil { - t.Fatal(result.Err.Error()) - } else { - posts := result.Data.([]*model.Post) - if len(posts) != 1 { - t.Fatal("Unexpected number of posts found.") - } - post := posts[0] - if post.Message != *data.Message || post.CreateAt != *data.CreateAt || post.UserId != th.BasicUser.Id { - t.Fatal("Post properties not as expected") - } - } - - // Import the post again. - if err := th.App.ImportDirectPost(data, false); err != nil { - t.Fatalf("Expected success.") - } - AssertAllPostsCount(t, th.App, initialPostCount, 1, "") - - // Check the post values. - if result := <-th.App.Srv.Store.Post().GetPostsCreatedAt(directChannel.Id, *data.CreateAt); result.Err != nil { - t.Fatal(result.Err.Error()) - } else { - posts := result.Data.([]*model.Post) - if len(posts) != 1 { - t.Fatal("Unexpected number of posts found.") - } - post := posts[0] - if post.Message != *data.Message || post.CreateAt != *data.CreateAt || post.UserId != th.BasicUser.Id { - t.Fatal("Post properties not as expected") - } - } - - // Save the post with a different time. - data.CreateAt = ptrInt64(*data.CreateAt + 1) - if err := th.App.ImportDirectPost(data, false); err != nil { - t.Fatalf("Expected success.") - } - AssertAllPostsCount(t, th.App, initialPostCount, 2, "") - - // Save the post with a different message. - data.Message = ptrStr("Message 2") - if err := th.App.ImportDirectPost(data, false); err != nil { - t.Fatalf("Expected success.") - } - AssertAllPostsCount(t, th.App, initialPostCount, 3, "") - - // Test with hashtags - data.Message = ptrStr("Message 2 #hashtagmashupcity") - data.CreateAt = ptrInt64(*data.CreateAt + 1) - if err := th.App.ImportDirectPost(data, false); err != nil { - t.Fatalf("Expected success.") - } - AssertAllPostsCount(t, th.App, initialPostCount, 4, "") - - if result := <-th.App.Srv.Store.Post().GetPostsCreatedAt(directChannel.Id, *data.CreateAt); result.Err != nil { - t.Fatal(result.Err.Error()) - } else { - posts := result.Data.([]*model.Post) - if len(posts) != 1 { - t.Fatal("Unexpected number of posts found.") - } - post := posts[0] - if post.Message != *data.Message || post.CreateAt != *data.CreateAt || post.UserId != th.BasicUser.Id { - t.Fatal("Post properties not as expected") - } - if post.Hashtags != "#hashtagmashupcity" { - t.Fatalf("Hashtags not as expected: %s", post.Hashtags) - } - } - - // Test with some flags. - data = &DirectPostImportData{ - ChannelMembers: &[]string{ - th.BasicUser.Username, - th.BasicUser2.Username, - }, - FlaggedBy: &[]string{ - th.BasicUser.Username, - th.BasicUser2.Username, - }, - User: ptrStr(th.BasicUser.Username), - Message: ptrStr("Message"), - CreateAt: ptrInt64(model.GetMillis()), - } - - if err := th.App.ImportDirectPost(data, false); err != nil { - t.Fatalf("Expected success: %v", err.Error()) - } - - // Check the post values. - if result := <-th.App.Srv.Store.Post().GetPostsCreatedAt(directChannel.Id, *data.CreateAt); result.Err != nil { - t.Fatal(result.Err.Error()) - } else { - posts := result.Data.([]*model.Post) - if len(posts) != 1 { - t.Fatal("Unexpected number of posts found.") - } - post := posts[0] - checkPreference(t, th.App, th.BasicUser.Id, model.PREFERENCE_CATEGORY_FLAGGED_POST, post.Id, "true") - checkPreference(t, th.App, th.BasicUser2.Id, model.PREFERENCE_CATEGORY_FLAGGED_POST, post.Id, "true") - } - - // ------------------ Group Channel ------------------------- - - // Create the GROUP channel. - user3 := th.CreateUser() - channelData = DirectChannelImportData{ - Members: &[]string{ - th.BasicUser.Username, - th.BasicUser2.Username, - user3.Username, - }, - } - if err := th.App.ImportDirectChannel(&channelData, false); err != nil { - t.Fatalf("Expected success: %v", err.Error()) - } - - // Get the channel. - var groupChannel *model.Channel - userIds := []string{ - th.BasicUser.Id, - th.BasicUser2.Id, - user3.Id, - } - if channel, err := th.App.createGroupChannel(userIds, th.BasicUser.Id); err.Id != store.CHANNEL_EXISTS_ERROR { - t.Fatal("Should have got store.CHANNEL_EXISTS_ERROR") - } else { - groupChannel = channel - } - - // Get the number of posts in the system. - if result := <-th.App.Srv.Store.Post().AnalyticsPostCount("", false, false); result.Err != nil { - t.Fatal(result.Err) - } else { - initialPostCount = result.Data.(int64) - } - - // Try adding an invalid post in dry run mode. - data = &DirectPostImportData{ - ChannelMembers: &[]string{ - th.BasicUser.Username, - th.BasicUser2.Username, - user3.Username, - }, - User: ptrStr(th.BasicUser.Username), - CreateAt: ptrInt64(model.GetMillis()), - } - if err := th.App.ImportDirectPost(data, true); err == nil { - t.Fatalf("Expected error.") - } - AssertAllPostsCount(t, th.App, initialPostCount, 0, "") - - // Try adding a valid post in dry run mode. - data = &DirectPostImportData{ - ChannelMembers: &[]string{ - th.BasicUser.Username, - th.BasicUser2.Username, - user3.Username, - }, - User: ptrStr(th.BasicUser.Username), - Message: ptrStr("Message"), - CreateAt: ptrInt64(model.GetMillis()), - } - if err := th.App.ImportDirectPost(data, true); err != nil { - t.Fatalf("Expected success.") - } - AssertAllPostsCount(t, th.App, initialPostCount, 0, "") - - // Try adding an invalid post in apply mode. - data = &DirectPostImportData{ - ChannelMembers: &[]string{ - th.BasicUser.Username, - th.BasicUser2.Username, - user3.Username, - model.NewId(), - }, - User: ptrStr(th.BasicUser.Username), - Message: ptrStr("Message"), - CreateAt: ptrInt64(model.GetMillis()), - } - if err := th.App.ImportDirectPost(data, false); err == nil { - t.Fatalf("Expected error.") - } - AssertAllPostsCount(t, th.App, initialPostCount, 0, "") - - // Try adding a valid post in apply mode. - data = &DirectPostImportData{ - ChannelMembers: &[]string{ - th.BasicUser.Username, - th.BasicUser2.Username, - user3.Username, - }, - User: ptrStr(th.BasicUser.Username), - Message: ptrStr("Message"), - CreateAt: ptrInt64(model.GetMillis()), - } - if err := th.App.ImportDirectPost(data, false); err != nil { - t.Fatalf("Expected success: %v", err.Error()) - } - AssertAllPostsCount(t, th.App, initialPostCount, 1, "") - - // Check the post values. - if result := <-th.App.Srv.Store.Post().GetPostsCreatedAt(groupChannel.Id, *data.CreateAt); result.Err != nil { - t.Fatal(result.Err.Error()) - } else { - posts := result.Data.([]*model.Post) - if len(posts) != 1 { - t.Fatal("Unexpected number of posts found.") - } - post := posts[0] - if post.Message != *data.Message || post.CreateAt != *data.CreateAt || post.UserId != th.BasicUser.Id { - t.Fatal("Post properties not as expected") - } - } - - // Import the post again. - if err := th.App.ImportDirectPost(data, false); err != nil { - t.Fatalf("Expected success.") - } - AssertAllPostsCount(t, th.App, initialPostCount, 1, "") - - // Check the post values. - if result := <-th.App.Srv.Store.Post().GetPostsCreatedAt(groupChannel.Id, *data.CreateAt); result.Err != nil { - t.Fatal(result.Err.Error()) - } else { - posts := result.Data.([]*model.Post) - if len(posts) != 1 { - t.Fatal("Unexpected number of posts found.") - } - post := posts[0] - if post.Message != *data.Message || post.CreateAt != *data.CreateAt || post.UserId != th.BasicUser.Id { - t.Fatal("Post properties not as expected") - } - } - - // Save the post with a different time. - data.CreateAt = ptrInt64(*data.CreateAt + 1) - if err := th.App.ImportDirectPost(data, false); err != nil { - t.Fatalf("Expected success.") - } - AssertAllPostsCount(t, th.App, initialPostCount, 2, "") - - // Save the post with a different message. - data.Message = ptrStr("Message 2") - if err := th.App.ImportDirectPost(data, false); err != nil { - t.Fatalf("Expected success.") - } - AssertAllPostsCount(t, th.App, initialPostCount, 3, "") - - // Test with hashtags - data.Message = ptrStr("Message 2 #hashtagmashupcity") - data.CreateAt = ptrInt64(*data.CreateAt + 1) - if err := th.App.ImportDirectPost(data, false); err != nil { - t.Fatalf("Expected success.") - } - AssertAllPostsCount(t, th.App, initialPostCount, 4, "") - - if result := <-th.App.Srv.Store.Post().GetPostsCreatedAt(groupChannel.Id, *data.CreateAt); result.Err != nil { - t.Fatal(result.Err.Error()) - } else { - posts := result.Data.([]*model.Post) - if len(posts) != 1 { - t.Fatal("Unexpected number of posts found.") - } - post := posts[0] - if post.Message != *data.Message || post.CreateAt != *data.CreateAt || post.UserId != th.BasicUser.Id { - t.Fatal("Post properties not as expected") - } - if post.Hashtags != "#hashtagmashupcity" { - t.Fatalf("Hashtags not as expected: %s", post.Hashtags) - } - } - - // Test with some flags. - data = &DirectPostImportData{ - ChannelMembers: &[]string{ - th.BasicUser.Username, - th.BasicUser2.Username, - user3.Username, - }, - FlaggedBy: &[]string{ - th.BasicUser.Username, - th.BasicUser2.Username, - }, - User: ptrStr(th.BasicUser.Username), - Message: ptrStr("Message"), - CreateAt: ptrInt64(model.GetMillis()), - } - - if err := th.App.ImportDirectPost(data, false); err != nil { - t.Fatalf("Expected success: %v", err.Error()) - } - - // Check the post values. - if result := <-th.App.Srv.Store.Post().GetPostsCreatedAt(groupChannel.Id, *data.CreateAt); result.Err != nil { - t.Fatal(result.Err.Error()) - } else { - posts := result.Data.([]*model.Post) - if len(posts) != 1 { - t.Fatal("Unexpected number of posts found.") - } - post := posts[0] - checkPreference(t, th.App, th.BasicUser.Id, model.PREFERENCE_CATEGORY_FLAGGED_POST, post.Id, "true") - checkPreference(t, th.App, th.BasicUser2.Id, model.PREFERENCE_CATEGORY_FLAGGED_POST, post.Id, "true") - } -} - func TestImportImportLine(t *testing.T) { th := Setup() defer th.TearDown() @@ -3840,240 +229,6 @@ func TestImportProcessImportDataFileVersionLine(t *testing.T) { } } -func TestImportValidateEmojiImportData(t *testing.T) { - data := EmojiImportData{ - Name: ptrStr("parrot"), - Image: ptrStr("/path/to/image"), - } - - err := validateEmojiImportData(&data) - assert.Nil(t, err, "Validation should succeed") - - *data.Name = "smiley" - err = validateEmojiImportData(&data) - assert.NotNil(t, err) - - *data.Name = "" - err = validateEmojiImportData(&data) - assert.NotNil(t, err) - - *data.Name = "" - *data.Image = "" - err = validateEmojiImportData(&data) - assert.NotNil(t, err) - - *data.Image = "/path/to/image" - data.Name = nil - err = validateEmojiImportData(&data) - assert.NotNil(t, err) - - data.Name = ptrStr("parrot") - data.Image = nil - err = validateEmojiImportData(&data) - assert.NotNil(t, err) -} - -func TestImportImportEmoji(t *testing.T) { - th := Setup() - defer th.TearDown() - - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableCustomEmoji = true }) - - testsDir, _ := utils.FindDir("tests") - testImage := filepath.Join(testsDir, "test.png") - - data := EmojiImportData{Name: ptrStr(model.NewId())} - err := th.App.ImportEmoji(&data, true) - assert.NotNil(t, err, "Invalid emoji should have failed dry run") - - result := <-th.App.Srv.Store.Emoji().GetByName(*data.Name) - assert.Nil(t, result.Data, "Emoji should not have been imported") - - data.Image = ptrStr(testImage) - err = th.App.ImportEmoji(&data, true) - assert.Nil(t, err, "Valid emoji should have passed dry run") - - data = EmojiImportData{Name: ptrStr(model.NewId())} - err = th.App.ImportEmoji(&data, false) - assert.NotNil(t, err, "Invalid emoji should have failed apply mode") - - data.Image = ptrStr("non-existent-file") - err = th.App.ImportEmoji(&data, false) - assert.NotNil(t, err, "Emoji with bad image file should have failed apply mode") - - data.Image = ptrStr(testImage) - err = th.App.ImportEmoji(&data, false) - assert.Nil(t, err, "Valid emoji should have succeeded apply mode") - - result = <-th.App.Srv.Store.Emoji().GetByName(*data.Name) - assert.NotNil(t, result.Data, "Emoji should have been imported") - - err = th.App.ImportEmoji(&data, false) - assert.Nil(t, err, "Second run should have succeeded apply mode") -} - -func TestImportAttachment(t *testing.T) { - th := Setup() - defer th.TearDown() - - testsDir, _ := utils.FindDir("tests") - testImage := filepath.Join(testsDir, "test.png") - invalidPath := "some-invalid-path" - - userId := model.NewId() - data := AttachmentImportData{Path: &testImage} - _, err := th.App.ImportAttachment(&data, &model.Post{UserId: userId, ChannelId: "some-channel"}, "some-team", true) - assert.Nil(t, err, "sample run without errors") - - attachments := GetAttachments(userId, th, t) - assert.Equal(t, len(attachments), 1) - - data = AttachmentImportData{Path: &invalidPath} - _, err = th.App.ImportAttachment(&data, &model.Post{UserId: model.NewId(), ChannelId: "some-channel"}, "some-team", true) - assert.NotNil(t, err, "should have failed when opening the file") - assert.Equal(t, err.Id, "app.import.attachment.bad_file.error") -} - -func TestImportPostAndRepliesWithAttachments(t *testing.T) { - - th := Setup() - defer th.TearDown() - - // Create a Team. - teamName := model.NewId() - th.App.ImportTeam(&TeamImportData{ - Name: &teamName, - DisplayName: ptrStr("Display Name"), - Type: ptrStr("O"), - }, false) - team, err := th.App.GetTeamByName(teamName) - if err != nil { - t.Fatalf("Failed to get team from database.") - } - - // Create a Channel. - channelName := model.NewId() - th.App.ImportChannel(&ChannelImportData{ - Team: &teamName, - Name: &channelName, - DisplayName: ptrStr("Display Name"), - Type: ptrStr("O"), - }, false) - _, err = th.App.GetChannelByName(channelName, team.Id, false) - if err != nil { - t.Fatalf("Failed to get channel from database.") - } - - // Create a user3. - username := model.NewId() - th.App.ImportUser(&UserImportData{ - Username: &username, - Email: ptrStr(model.NewId() + "@example.com"), - }, false) - user3, err := th.App.GetUserByUsername(username) - if err != nil { - t.Fatalf("Failed to get user3 from database.") - } - - username2 := model.NewId() - th.App.ImportUser(&UserImportData{ - Username: &username2, - Email: ptrStr(model.NewId() + "@example.com"), - }, false) - user4, err := th.App.GetUserByUsername(username2) - if err != nil { - t.Fatalf("Failed to get user3 from database.") - } - - // Post with attachments. - time := model.GetMillis() - attachmentsPostTime := time - attachmentsReplyTime := time + 1 - testsDir, _ := utils.FindDir("tests") - testImage := filepath.Join(testsDir, "test.png") - testMarkDown := filepath.Join(testsDir, "test-attachments.md") - data := &PostImportData{ - Team: &teamName, - Channel: &channelName, - User: &username, - Message: ptrStr("Message with reply"), - CreateAt: &attachmentsPostTime, - Attachments: &[]AttachmentImportData{{Path: &testImage}, {Path: &testMarkDown}}, - Replies: &[]ReplyImportData{{ - User: &user4.Username, - Message: ptrStr("Message reply"), - CreateAt: &attachmentsReplyTime, - Attachments: &[]AttachmentImportData{{Path: &testImage}}, - }}, - } - - if err := th.App.ImportPost(data, false); err != nil { - t.Fatalf("Expected success.") - } - - attachments := GetAttachments(user3.Id, th, t) - assert.Equal(t, len(attachments), 2) - assert.Contains(t, attachments[0].Path, team.Id) - assert.Contains(t, attachments[1].Path, team.Id) - AssertFileIdsInPost(attachments, th, t) - - attachments = GetAttachments(user4.Id, th, t) - assert.Equal(t, len(attachments), 1) - assert.Contains(t, attachments[0].Path, team.Id) - AssertFileIdsInPost(attachments, th, t) - - // Reply with Attachments in Direct Post - - // Create direct post users. - - username3 := model.NewId() - th.App.ImportUser(&UserImportData{ - Username: &username3, - Email: ptrStr(model.NewId() + "@example.com"), - }, false) - user3, err = th.App.GetUserByUsername(username3) - if err != nil { - t.Fatalf("Failed to get user3 from database.") - } - - username4 := model.NewId() - th.App.ImportUser(&UserImportData{ - Username: &username4, - Email: ptrStr(model.NewId() + "@example.com"), - }, false) - - user4, err = th.App.GetUserByUsername(username4) - if err != nil { - t.Fatalf("Failed to get user3 from database.") - } - - directImportData := &DirectPostImportData{ - ChannelMembers: &[]string{ - user3.Username, - user4.Username, - }, - User: &user3.Username, - Message: ptrStr("Message with Replies"), - CreateAt: ptrInt64(model.GetMillis()), - Replies: &[]ReplyImportData{{ - User: &user4.Username, - Message: ptrStr("Message reply with attachment"), - CreateAt: ptrInt64(model.GetMillis()), - Attachments: &[]AttachmentImportData{{Path: &testImage}}, - }}, - } - - if err := th.App.ImportDirectPost(directImportData, false); err != nil { - t.Fatalf("Expected success.") - } - - attachments = GetAttachments(user4.Id, th, t) - assert.Equal(t, len(attachments), 1) - assert.Contains(t, attachments[0].Path, "noteam") - AssertFileIdsInPost(attachments, th, t) - -} - func GetAttachments(userId string, th *TestHelper, t *testing.T) []*model.FileInfo { if result := <-th.App.Srv.Store.FileInfo().GetForUser(userId); result.Err != nil { t.Fatal(result.Err.Error()) diff --git a/app/import_types.go b/app/import_types.go new file mode 100644 index 000000000..45119bffa --- /dev/null +++ b/app/import_types.go @@ -0,0 +1,186 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package app + +import "github.com/mattermost/mattermost-server/model" + +// Import Data Models + +type LineImportData struct { + Type string `json:"type"` + Scheme *SchemeImportData `json:"scheme"` + Team *TeamImportData `json:"team"` + Channel *ChannelImportData `json:"channel"` + User *UserImportData `json:"user"` + Post *PostImportData `json:"post"` + DirectChannel *DirectChannelImportData `json:"direct_channel"` + DirectPost *DirectPostImportData `json:"direct_post"` + Emoji *EmojiImportData `json:"emoji"` + Version *int `json:"version"` +} + +type TeamImportData struct { + Name *string `json:"name"` + DisplayName *string `json:"display_name"` + Type *string `json:"type"` + Description *string `json:"description"` + AllowOpenInvite *bool `json:"allow_open_invite"` + Scheme *string `json:"scheme"` +} + +type ChannelImportData struct { + Team *string `json:"team"` + Name *string `json:"name"` + DisplayName *string `json:"display_name"` + Type *string `json:"type"` + Header *string `json:"header"` + Purpose *string `json:"purpose"` + Scheme *string `json:"scheme"` +} + +type UserImportData struct { + ProfileImage *string `json:"profile_image"` + Username *string `json:"username"` + Email *string `json:"email"` + AuthService *string `json:"auth_service"` + AuthData *string `json:"auth_data"` + Password *string `json:"password"` + Nickname *string `json:"nickname"` + FirstName *string `json:"first_name"` + LastName *string `json:"last_name"` + Position *string `json:"position"` + Roles *string `json:"roles"` + Locale *string `json:"locale"` + + Teams *[]UserTeamImportData `json:"teams"` + + Theme *string `json:"theme"` + UseMilitaryTime *string `json:"military_time"` + CollapsePreviews *string `json:"link_previews"` + MessageDisplay *string `json:"message_display"` + ChannelDisplayMode *string `json:"channel_display_mode"` + TutorialStep *string `json:"tutorial_step"` + + NotifyProps *UserNotifyPropsImportData `json:"notify_props"` +} + +type UserNotifyPropsImportData struct { + Desktop *string `json:"desktop"` + DesktopSound *string `json:"desktop_sound"` + + Email *string `json:"email"` + + Mobile *string `json:"mobile"` + MobilePushStatus *string `json:"mobile_push_status"` + + ChannelTrigger *string `json:"channel"` + CommentsTrigger *string `json:"comments"` + MentionKeys *string `json:"mention_keys"` +} + +type UserTeamImportData struct { + Name *string `json:"name"` + Roles *string `json:"roles"` + Channels *[]UserChannelImportData `json:"channels"` +} + +type UserChannelImportData struct { + Name *string `json:"name"` + Roles *string `json:"roles"` + NotifyProps *UserChannelNotifyPropsImportData `json:"notify_props"` + Favorite *bool `json:"favorite"` +} + +type UserChannelNotifyPropsImportData struct { + Desktop *string `json:"desktop"` + Mobile *string `json:"mobile"` + MarkUnread *string `json:"mark_unread"` +} + +type EmojiImportData struct { + Name *string `json:"name"` + Image *string `json:"image"` +} + +type ReactionImportData struct { + User *string `json:"user"` + CreateAt *int64 `json:"create_at"` + EmojiName *string `json:"emoji_name"` +} + +type ReplyImportData struct { + User *string `json:"user"` + + Message *string `json:"message"` + CreateAt *int64 `json:"create_at"` + + FlaggedBy *[]string `json:"flagged_by"` + Reactions *[]ReactionImportData `json:"reactions"` + Attachments *[]AttachmentImportData `json:"attachments"` +} + +type PostImportData struct { + Team *string `json:"team"` + Channel *string `json:"channel"` + User *string `json:"user"` + + Message *string `json:"message"` + CreateAt *int64 `json:"create_at"` + + FlaggedBy *[]string `json:"flagged_by"` + Reactions *[]ReactionImportData `json:"reactions"` + Replies *[]ReplyImportData `json:"replies"` + Attachments *[]AttachmentImportData `json:"attachments"` +} + +type DirectChannelImportData struct { + Members *[]string `json:"members"` + FavoritedBy *[]string `json:"favorited_by"` + + Header *string `json:"header"` +} + +type DirectPostImportData struct { + ChannelMembers *[]string `json:"channel_members"` + User *string `json:"user"` + + Message *string `json:"message"` + CreateAt *int64 `json:"create_at"` + + FlaggedBy *[]string `json:"flagged_by"` + Reactions *[]ReactionImportData `json:"reactions"` + Replies *[]ReplyImportData `json:"replies"` +} + +type SchemeImportData struct { + Name *string `json:"name"` + DisplayName *string `json:"display_name"` + Description *string `json:"description"` + Scope *string `json:"scope"` + DefaultTeamAdminRole *RoleImportData `json:"default_team_admin_role"` + DefaultTeamUserRole *RoleImportData `json:"default_team_user_role"` + DefaultChannelAdminRole *RoleImportData `json:"default_channel_admin_role"` + DefaultChannelUserRole *RoleImportData `json:"default_channel_user_role"` +} + +type RoleImportData struct { + Name *string `json:"name"` + DisplayName *string `json:"display_name"` + Description *string `json:"description"` + Permissions *[]string `json:"permissions"` +} + +type LineImportWorkerData struct { + LineImportData + LineNumber int +} + +type LineImportWorkerError struct { + Error *model.AppError + LineNumber int +} + +type AttachmentImportData struct { + Path *string `json:"path"` +} diff --git a/app/import_validators.go b/app/import_validators.go new file mode 100644 index 000000000..9b19ae45e --- /dev/null +++ b/app/import_validators.go @@ -0,0 +1,529 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package app + +import ( + "net/http" + "os" + "unicode/utf8" + + "github.com/mattermost/mattermost-server/model" +) + +func validateSchemeImportData(data *SchemeImportData) *model.AppError { + + if data.Scope == nil { + return model.NewAppError("BulkImport", "app.import.validate_scheme_import_data.null_scope.error", nil, "", http.StatusBadRequest) + } + + switch *data.Scope { + case model.SCHEME_SCOPE_TEAM: + if data.DefaultTeamAdminRole == nil || data.DefaultTeamUserRole == nil || data.DefaultChannelAdminRole == nil || data.DefaultChannelUserRole == nil { + return model.NewAppError("BulkImport", "app.import.validate_scheme_import_data.wrong_roles_for_scope.error", nil, "", http.StatusBadRequest) + } + case model.SCHEME_SCOPE_CHANNEL: + if data.DefaultTeamAdminRole != nil || data.DefaultTeamUserRole != nil || data.DefaultChannelAdminRole == nil || data.DefaultChannelUserRole == nil { + return model.NewAppError("BulkImport", "app.import.validate_scheme_import_data.wrong_roles_for_scope.error", nil, "", http.StatusBadRequest) + } + default: + return model.NewAppError("BulkImport", "app.import.validate_scheme_import_data.unknown_scheme.error", nil, "", http.StatusBadRequest) + } + + if data.Name == nil || !model.IsValidSchemeName(*data.Name) { + return model.NewAppError("BulkImport", "app.import.validate_scheme_import_data.name_invalid.error", nil, "", http.StatusBadRequest) + } + + if data.DisplayName == nil || len(*data.DisplayName) == 0 || len(*data.DisplayName) > model.SCHEME_DISPLAY_NAME_MAX_LENGTH { + return model.NewAppError("BulkImport", "app.import.validate_scheme_import_data.display_name_invalid.error", nil, "", http.StatusBadRequest) + } + + if data.Description != nil && len(*data.Description) > model.SCHEME_DESCRIPTION_MAX_LENGTH { + return model.NewAppError("BulkImport", "app.import.validate_scheme_import_data.description_invalid.error", nil, "", http.StatusBadRequest) + } + + if data.DefaultTeamAdminRole != nil { + if err := validateRoleImportData(data.DefaultTeamAdminRole); err != nil { + return err + } + } + + if data.DefaultTeamUserRole != nil { + if err := validateRoleImportData(data.DefaultTeamUserRole); err != nil { + return err + } + } + + if data.DefaultChannelAdminRole != nil { + if err := validateRoleImportData(data.DefaultChannelAdminRole); err != nil { + return err + } + } + + if data.DefaultChannelUserRole != nil { + if err := validateRoleImportData(data.DefaultChannelUserRole); err != nil { + return err + } + } + + return nil +} + +func validateRoleImportData(data *RoleImportData) *model.AppError { + + if data.Name == nil || !model.IsValidRoleName(*data.Name) { + return model.NewAppError("BulkImport", "app.import.validate_role_import_data.name_invalid.error", nil, "", http.StatusBadRequest) + } + + if data.DisplayName == nil || len(*data.DisplayName) == 0 || len(*data.DisplayName) > model.ROLE_DISPLAY_NAME_MAX_LENGTH { + return model.NewAppError("BulkImport", "app.import.validate_role_import_data.display_name_invalid.error", nil, "", http.StatusBadRequest) + } + + if data.Description != nil && len(*data.Description) > model.ROLE_DESCRIPTION_MAX_LENGTH { + return model.NewAppError("BulkImport", "app.import.validate_role_import_data.description_invalid.error", nil, "", http.StatusBadRequest) + } + + if data.Permissions != nil { + for _, permission := range *data.Permissions { + permissionValidated := false + for _, p := range model.ALL_PERMISSIONS { + if permission == p.Id { + permissionValidated = true + break + } + } + + if !permissionValidated { + return model.NewAppError("BulkImport", "app.import.validate_role_import_data.invalid_permission.error", nil, "permission"+permission, http.StatusBadRequest) + } + } + } + + return nil +} + +func validateTeamImportData(data *TeamImportData) *model.AppError { + + if data.Name == nil { + return model.NewAppError("BulkImport", "app.import.validate_team_import_data.name_missing.error", nil, "", http.StatusBadRequest) + } else if len(*data.Name) > model.TEAM_NAME_MAX_LENGTH { + return model.NewAppError("BulkImport", "app.import.validate_team_import_data.name_length.error", nil, "", http.StatusBadRequest) + } else if model.IsReservedTeamName(*data.Name) { + return model.NewAppError("BulkImport", "app.import.validate_team_import_data.name_reserved.error", nil, "", http.StatusBadRequest) + } else if !model.IsValidTeamName(*data.Name) { + return model.NewAppError("BulkImport", "app.import.validate_team_import_data.name_characters.error", nil, "", http.StatusBadRequest) + } + + if data.DisplayName == nil { + return model.NewAppError("BulkImport", "app.import.validate_team_import_data.display_name_missing.error", nil, "", http.StatusBadRequest) + } else if utf8.RuneCountInString(*data.DisplayName) == 0 || utf8.RuneCountInString(*data.DisplayName) > model.TEAM_DISPLAY_NAME_MAX_RUNES { + return model.NewAppError("BulkImport", "app.import.validate_team_import_data.display_name_length.error", nil, "", http.StatusBadRequest) + } + + if data.Type == nil { + return model.NewAppError("BulkImport", "app.import.validate_team_import_data.type_missing.error", nil, "", http.StatusBadRequest) + } else if *data.Type != model.TEAM_OPEN && *data.Type != model.TEAM_INVITE { + return model.NewAppError("BulkImport", "app.import.validate_team_import_data.type_invalid.error", nil, "", http.StatusBadRequest) + } + + if data.Description != nil && len(*data.Description) > model.TEAM_DESCRIPTION_MAX_LENGTH { + return model.NewAppError("BulkImport", "app.import.validate_team_import_data.description_length.error", nil, "", http.StatusBadRequest) + } + + if data.Scheme != nil && !model.IsValidSchemeName(*data.Scheme) { + return model.NewAppError("BulkImport", "app.import.validate_team_import_data.scheme_invalid.error", nil, "", http.StatusBadRequest) + } + + return nil +} + +func validateChannelImportData(data *ChannelImportData) *model.AppError { + + if data.Team == nil { + return model.NewAppError("BulkImport", "app.import.validate_channel_import_data.team_missing.error", nil, "", http.StatusBadRequest) + } + + if data.Name == nil { + return model.NewAppError("BulkImport", "app.import.validate_channel_import_data.name_missing.error", nil, "", http.StatusBadRequest) + } else if len(*data.Name) > model.CHANNEL_NAME_MAX_LENGTH { + return model.NewAppError("BulkImport", "app.import.validate_channel_import_data.name_length.error", nil, "", http.StatusBadRequest) + } else if !model.IsValidChannelIdentifier(*data.Name) { + return model.NewAppError("BulkImport", "app.import.validate_channel_import_data.name_characters.error", nil, "", http.StatusBadRequest) + } + + if data.DisplayName == nil { + return model.NewAppError("BulkImport", "app.import.validate_channel_import_data.display_name_missing.error", nil, "", http.StatusBadRequest) + } else if utf8.RuneCountInString(*data.DisplayName) == 0 || utf8.RuneCountInString(*data.DisplayName) > model.CHANNEL_DISPLAY_NAME_MAX_RUNES { + return model.NewAppError("BulkImport", "app.import.validate_channel_import_data.display_name_length.error", nil, "", http.StatusBadRequest) + } + + if data.Type == nil { + return model.NewAppError("BulkImport", "app.import.validate_channel_import_data.type_missing.error", nil, "", http.StatusBadRequest) + } else if *data.Type != model.CHANNEL_OPEN && *data.Type != model.CHANNEL_PRIVATE { + return model.NewAppError("BulkImport", "app.import.validate_channel_import_data.type_invalid.error", nil, "", http.StatusBadRequest) + } + + if data.Header != nil && utf8.RuneCountInString(*data.Header) > model.CHANNEL_HEADER_MAX_RUNES { + return model.NewAppError("BulkImport", "app.import.validate_channel_import_data.header_length.error", nil, "", http.StatusBadRequest) + } + + if data.Purpose != nil && utf8.RuneCountInString(*data.Purpose) > model.CHANNEL_PURPOSE_MAX_RUNES { + return model.NewAppError("BulkImport", "app.import.validate_channel_import_data.purpose_length.error", nil, "", http.StatusBadRequest) + } + + if data.Scheme != nil && !model.IsValidSchemeName(*data.Scheme) { + return model.NewAppError("BulkImport", "app.import.validate_channel_import_data.scheme_invalid.error", nil, "", http.StatusBadRequest) + } + + return nil +} + +func validateUserImportData(data *UserImportData) *model.AppError { + if data.ProfileImage != nil { + if _, err := os.Stat(*data.ProfileImage); os.IsNotExist(err) { + return model.NewAppError("BulkImport", "app.import.validate_user_import_data.profile_image.error", nil, "", http.StatusBadRequest) + } + } + + if data.Username == nil { + return model.NewAppError("BulkImport", "app.import.validate_user_import_data.username_missing.error", nil, "", http.StatusBadRequest) + } else if !model.IsValidUsername(*data.Username) { + return model.NewAppError("BulkImport", "app.import.validate_user_import_data.username_invalid.error", nil, "", http.StatusBadRequest) + } + + if data.Email == nil { + return model.NewAppError("BulkImport", "app.import.validate_user_import_data.email_missing.error", nil, "", http.StatusBadRequest) + } else if len(*data.Email) == 0 || len(*data.Email) > model.USER_EMAIL_MAX_LENGTH { + return model.NewAppError("BulkImport", "app.import.validate_user_import_data.email_length.error", nil, "", http.StatusBadRequest) + } + + if data.AuthService != nil && len(*data.AuthService) == 0 { + return model.NewAppError("BulkImport", "app.import.validate_user_import_data.auth_service_length.error", nil, "", http.StatusBadRequest) + } + + if data.AuthData != nil && data.Password != nil { + return model.NewAppError("BulkImport", "app.import.validate_user_import_data.auth_data_and_password.error", nil, "", http.StatusBadRequest) + } + + if data.AuthData != nil && len(*data.AuthData) > model.USER_AUTH_DATA_MAX_LENGTH { + return model.NewAppError("BulkImport", "app.import.validate_user_import_data.auth_data_length.error", nil, "", http.StatusBadRequest) + } + + if data.Password != nil && len(*data.Password) == 0 { + return model.NewAppError("BulkImport", "app.import.validate_user_import_data.password_length.error", nil, "", http.StatusBadRequest) + } + + if data.Password != nil && len(*data.Password) > model.USER_PASSWORD_MAX_LENGTH { + return model.NewAppError("BulkImport", "app.import.validate_user_import_data.password_length.error", nil, "", http.StatusBadRequest) + } + + if data.Nickname != nil && utf8.RuneCountInString(*data.Nickname) > model.USER_NICKNAME_MAX_RUNES { + return model.NewAppError("BulkImport", "app.import.validate_user_import_data.nickname_length.error", nil, "", http.StatusBadRequest) + } + + if data.FirstName != nil && utf8.RuneCountInString(*data.FirstName) > model.USER_FIRST_NAME_MAX_RUNES { + return model.NewAppError("BulkImport", "app.import.validate_user_import_data.first_name_length.error", nil, "", http.StatusBadRequest) + } + + if data.LastName != nil && utf8.RuneCountInString(*data.LastName) > model.USER_LAST_NAME_MAX_RUNES { + return model.NewAppError("BulkImport", "app.import.validate_user_import_data.last_name_length.error", nil, "", http.StatusBadRequest) + } + + if data.Position != nil && utf8.RuneCountInString(*data.Position) > model.USER_POSITION_MAX_RUNES { + return model.NewAppError("BulkImport", "app.import.validate_user_import_data.position_length.error", nil, "", http.StatusBadRequest) + } + + if data.Roles != nil && !model.IsValidUserRoles(*data.Roles) { + return model.NewAppError("BulkImport", "app.import.validate_user_import_data.roles_invalid.error", nil, "", http.StatusBadRequest) + } + + if data.NotifyProps != nil { + if data.NotifyProps.Desktop != nil && !model.IsValidUserNotifyLevel(*data.NotifyProps.Desktop) { + return model.NewAppError("BulkImport", "app.import.validate_user_import_data.notify_props_desktop_invalid.error", nil, "", http.StatusBadRequest) + } + + if data.NotifyProps.DesktopSound != nil && !model.IsValidTrueOrFalseString(*data.NotifyProps.DesktopSound) { + return model.NewAppError("BulkImport", "app.import.validate_user_import_data.notify_props_desktop_sound_invalid.error", nil, "", http.StatusBadRequest) + } + + if data.NotifyProps.Email != nil && !model.IsValidTrueOrFalseString(*data.NotifyProps.Email) { + return model.NewAppError("BulkImport", "app.import.validate_user_import_data.notify_props_email_invalid.error", nil, "", http.StatusBadRequest) + } + + if data.NotifyProps.Mobile != nil && !model.IsValidUserNotifyLevel(*data.NotifyProps.Mobile) { + return model.NewAppError("BulkImport", "app.import.validate_user_import_data.notify_props_mobile_invalid.error", nil, "", http.StatusBadRequest) + } + + if data.NotifyProps.MobilePushStatus != nil && !model.IsValidPushStatusNotifyLevel(*data.NotifyProps.MobilePushStatus) { + return model.NewAppError("BulkImport", "app.import.validate_user_import_data.notify_props_mobile_push_status_invalid.error", nil, "", http.StatusBadRequest) + } + + if data.NotifyProps.ChannelTrigger != nil && !model.IsValidTrueOrFalseString(*data.NotifyProps.ChannelTrigger) { + return model.NewAppError("BulkImport", "app.import.validate_user_import_data.notify_props_channel_trigger_invalid.error", nil, "", http.StatusBadRequest) + } + + if data.NotifyProps.CommentsTrigger != nil && !model.IsValidCommentsNotifyLevel(*data.NotifyProps.CommentsTrigger) { + return model.NewAppError("BulkImport", "app.import.validate_user_import_data.notify_props_comments_trigger_invalid.error", nil, "", http.StatusBadRequest) + } + } + + if data.Teams != nil { + return validateUserTeamsImportData(data.Teams) + } else { + return nil + } +} + +func validateUserTeamsImportData(data *[]UserTeamImportData) *model.AppError { + if data == nil { + return nil + } + + for _, tdata := range *data { + if tdata.Name == nil { + return model.NewAppError("BulkImport", "app.import.validate_user_teams_import_data.team_name_missing.error", nil, "", http.StatusBadRequest) + } + + if tdata.Roles != nil && !model.IsValidUserRoles(*tdata.Roles) { + return model.NewAppError("BulkImport", "app.import.validate_user_teams_import_data.invalid_roles.error", nil, "", http.StatusBadRequest) + } + + if tdata.Channels != nil { + if err := validateUserChannelsImportData(tdata.Channels); err != nil { + return err + } + } + } + + return nil +} + +func validateUserChannelsImportData(data *[]UserChannelImportData) *model.AppError { + if data == nil { + return nil + } + + for _, cdata := range *data { + if cdata.Name == nil { + return model.NewAppError("BulkImport", "app.import.validate_user_channels_import_data.channel_name_missing.error", nil, "", http.StatusBadRequest) + } + + if cdata.Roles != nil && !model.IsValidUserRoles(*cdata.Roles) { + return model.NewAppError("BulkImport", "app.import.validate_user_channels_import_data.invalid_roles.error", nil, "", http.StatusBadRequest) + } + + if cdata.NotifyProps != nil { + if cdata.NotifyProps.Desktop != nil && !model.IsChannelNotifyLevelValid(*cdata.NotifyProps.Desktop) { + return model.NewAppError("BulkImport", "app.import.validate_user_channels_import_data.invalid_notify_props_desktop.error", nil, "", http.StatusBadRequest) + } + + if cdata.NotifyProps.Mobile != nil && !model.IsChannelNotifyLevelValid(*cdata.NotifyProps.Mobile) { + return model.NewAppError("BulkImport", "app.import.validate_user_channels_import_data.invalid_notify_props_mobile.error", nil, "", http.StatusBadRequest) + } + + if cdata.NotifyProps.MarkUnread != nil && !model.IsChannelMarkUnreadLevelValid(*cdata.NotifyProps.MarkUnread) { + return model.NewAppError("BulkImport", "app.import.validate_user_channels_import_data.invalid_notify_props_mark_unread.error", nil, "", http.StatusBadRequest) + } + } + } + + return nil +} + +func validateReactionImportData(data *ReactionImportData, parentCreateAt int64) *model.AppError { + if data.User == nil { + return model.NewAppError("BulkImport", "app.import.validate_reaction_import_data.user_missing.error", nil, "", http.StatusBadRequest) + } + + if data.EmojiName == nil { + return model.NewAppError("BulkImport", "app.import.validate_reaction_import_data.emoji_name_missing.error", nil, "", http.StatusBadRequest) + } else if utf8.RuneCountInString(*data.EmojiName) > model.EMOJI_NAME_MAX_LENGTH { + return model.NewAppError("BulkImport", "app.import.validate_reaction_import_data.emoji_name_length.error", nil, "", http.StatusBadRequest) + } + + if data.CreateAt == nil { + return model.NewAppError("BulkImport", "app.import.validate_reaction_import_data.create_at_missing.error", nil, "", http.StatusBadRequest) + } else if *data.CreateAt == 0 { + return model.NewAppError("BulkImport", "app.import.validate_reaction_import_data.create_at_zero.error", nil, "", http.StatusBadRequest) + } else if *data.CreateAt < parentCreateAt { + return model.NewAppError("BulkImport", "app.import.validate_reaction_import_data.create_at_before_parent.error", nil, "", http.StatusBadRequest) + } + + return nil +} + +func validateReplyImportData(data *ReplyImportData, parentCreateAt int64, maxPostSize int) *model.AppError { + if data.User == nil { + return model.NewAppError("BulkImport", "app.import.validate_reply_import_data.user_missing.error", nil, "", http.StatusBadRequest) + } + + if data.Message == nil { + return model.NewAppError("BulkImport", "app.import.validate_reply_import_data.message_missing.error", nil, "", http.StatusBadRequest) + } else if utf8.RuneCountInString(*data.Message) > maxPostSize { + return model.NewAppError("BulkImport", "app.import.validate_reply_import_data.message_length.error", nil, "", http.StatusBadRequest) + } + + if data.CreateAt == nil { + return model.NewAppError("BulkImport", "app.import.validate_reply_import_data.create_at_missing.error", nil, "", http.StatusBadRequest) + } else if *data.CreateAt == 0 { + return model.NewAppError("BulkImport", "app.import.validate_reply_import_data.create_at_zero.error", nil, "", http.StatusBadRequest) + } else if *data.CreateAt < parentCreateAt { + return model.NewAppError("BulkImport", "app.import.validate_reply_import_data.create_at_before_parent.error", nil, "", http.StatusBadRequest) + } + + return nil +} + +func validatePostImportData(data *PostImportData, maxPostSize int) *model.AppError { + if data.Team == nil { + return model.NewAppError("BulkImport", "app.import.validate_post_import_data.team_missing.error", nil, "", http.StatusBadRequest) + } + + if data.Channel == nil { + return model.NewAppError("BulkImport", "app.import.validate_post_import_data.channel_missing.error", nil, "", http.StatusBadRequest) + } + + if data.User == nil { + return model.NewAppError("BulkImport", "app.import.validate_post_import_data.user_missing.error", nil, "", http.StatusBadRequest) + } + + if data.Message == nil { + return model.NewAppError("BulkImport", "app.import.validate_post_import_data.message_missing.error", nil, "", http.StatusBadRequest) + } else if utf8.RuneCountInString(*data.Message) > maxPostSize { + return model.NewAppError("BulkImport", "app.import.validate_post_import_data.message_length.error", nil, "", http.StatusBadRequest) + } + + if data.CreateAt == nil { + return model.NewAppError("BulkImport", "app.import.validate_post_import_data.create_at_missing.error", nil, "", http.StatusBadRequest) + } else if *data.CreateAt == 0 { + return model.NewAppError("BulkImport", "app.import.validate_post_import_data.create_at_zero.error", nil, "", http.StatusBadRequest) + } + + if data.Reactions != nil { + for _, reaction := range *data.Reactions { + validateReactionImportData(&reaction, *data.CreateAt) + } + } + + if data.Replies != nil { + for _, reply := range *data.Replies { + validateReplyImportData(&reply, *data.CreateAt, maxPostSize) + } + } + + return nil +} + +func validateDirectChannelImportData(data *DirectChannelImportData) *model.AppError { + if data.Members == nil { + return model.NewAppError("BulkImport", "app.import.validate_direct_channel_import_data.members_required.error", nil, "", http.StatusBadRequest) + } + + if len(*data.Members) != 2 { + if len(*data.Members) < model.CHANNEL_GROUP_MIN_USERS { + return model.NewAppError("BulkImport", "app.import.validate_direct_channel_import_data.members_too_few.error", nil, "", http.StatusBadRequest) + } else if len(*data.Members) > model.CHANNEL_GROUP_MAX_USERS { + return model.NewAppError("BulkImport", "app.import.validate_direct_channel_import_data.members_too_many.error", nil, "", http.StatusBadRequest) + } + } + + if data.Header != nil && utf8.RuneCountInString(*data.Header) > model.CHANNEL_HEADER_MAX_RUNES { + return model.NewAppError("BulkImport", "app.import.validate_direct_channel_import_data.header_length.error", nil, "", http.StatusBadRequest) + } + + if data.FavoritedBy != nil { + for _, favoriter := range *data.FavoritedBy { + found := false + for _, member := range *data.Members { + if favoriter == member { + found = true + break + } + } + if !found { + return model.NewAppError("BulkImport", "app.import.validate_direct_channel_import_data.unknown_favoriter.error", map[string]interface{}{"Username": favoriter}, "", http.StatusBadRequest) + } + } + } + + return nil +} + +func validateDirectPostImportData(data *DirectPostImportData, maxPostSize int) *model.AppError { + if data.ChannelMembers == nil { + return model.NewAppError("BulkImport", "app.import.validate_direct_post_import_data.channel_members_required.error", nil, "", http.StatusBadRequest) + } + + if len(*data.ChannelMembers) != 2 { + if len(*data.ChannelMembers) < model.CHANNEL_GROUP_MIN_USERS { + return model.NewAppError("BulkImport", "app.import.validate_direct_post_import_data.channel_members_too_few.error", nil, "", http.StatusBadRequest) + } else if len(*data.ChannelMembers) > model.CHANNEL_GROUP_MAX_USERS { + return model.NewAppError("BulkImport", "app.import.validate_direct_post_import_data.channel_members_too_many.error", nil, "", http.StatusBadRequest) + } + } + + if data.User == nil { + return model.NewAppError("BulkImport", "app.import.validate_direct_post_import_data.user_missing.error", nil, "", http.StatusBadRequest) + } + + if data.Message == nil { + return model.NewAppError("BulkImport", "app.import.validate_direct_post_import_data.message_missing.error", nil, "", http.StatusBadRequest) + } else if utf8.RuneCountInString(*data.Message) > maxPostSize { + return model.NewAppError("BulkImport", "app.import.validate_direct_post_import_data.message_length.error", nil, "", http.StatusBadRequest) + } + + if data.CreateAt == nil { + return model.NewAppError("BulkImport", "app.import.validate_direct_post_import_data.create_at_missing.error", nil, "", http.StatusBadRequest) + } else if *data.CreateAt == 0 { + return model.NewAppError("BulkImport", "app.import.validate_direct_post_import_data.create_at_zero.error", nil, "", http.StatusBadRequest) + } + + if data.FlaggedBy != nil { + for _, flagger := range *data.FlaggedBy { + found := false + for _, member := range *data.ChannelMembers { + if flagger == member { + found = true + break + } + } + if !found { + return model.NewAppError("BulkImport", "app.import.validate_direct_post_import_data.unknown_flagger.error", map[string]interface{}{"Username": flagger}, "", http.StatusBadRequest) + } + } + } + + if data.Reactions != nil { + for _, reaction := range *data.Reactions { + validateReactionImportData(&reaction, *data.CreateAt) + } + } + + if data.Replies != nil { + for _, reply := range *data.Replies { + validateReplyImportData(&reply, *data.CreateAt, maxPostSize) + } + } + + return nil +} + +func validateEmojiImportData(data *EmojiImportData) *model.AppError { + if data == nil { + return model.NewAppError("BulkImport", "app.import.validate_emoji_import_data.empty.error", nil, "", http.StatusBadRequest) + } + + if data.Name == nil || len(*data.Name) == 0 { + return model.NewAppError("BulkImport", "app.import.validate_emoji_import_data.name_missing.error", nil, "", http.StatusBadRequest) + } + + if err := model.IsValidEmojiName(*data.Name); err != nil { + return err + } + + if data.Image == nil || len(*data.Image) == 0 { + return model.NewAppError("BulkImport", "app.import.validate_emoji_import_data.image_missing.error", nil, "", http.StatusBadRequest) + } + + return nil +} diff --git a/app/import_validators_test.go b/app/import_validators_test.go new file mode 100644 index 000000000..5fb0518f7 --- /dev/null +++ b/app/import_validators_test.go @@ -0,0 +1,1372 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package app + +import ( + "path/filepath" + "strings" + "testing" + + "github.com/mattermost/mattermost-server/model" + "github.com/mattermost/mattermost-server/utils" + "github.com/stretchr/testify/assert" +) + +func TestImportValidateSchemeImportData(t *testing.T) { + // Test with minimum required valid properties and team scope. + data := SchemeImportData{ + Name: ptrStr("name"), + DisplayName: ptrStr("display name"), + Scope: ptrStr("team"), + DefaultTeamAdminRole: &RoleImportData{ + Name: ptrStr("name"), + DisplayName: ptrStr("display name"), + Permissions: &[]string{"invite_user"}, + }, + DefaultTeamUserRole: &RoleImportData{ + Name: ptrStr("name"), + DisplayName: ptrStr("display name"), + Permissions: &[]string{"invite_user"}, + }, + DefaultChannelAdminRole: &RoleImportData{ + Name: ptrStr("name"), + DisplayName: ptrStr("display name"), + Permissions: &[]string{"invite_user"}, + }, + DefaultChannelUserRole: &RoleImportData{ + Name: ptrStr("name"), + DisplayName: ptrStr("display name"), + Permissions: &[]string{"invite_user"}, + }, + } + if err := validateSchemeImportData(&data); err != nil { + t.Fatal("Validation failed but should have been valid.", err) + } + + // Test with various invalid names. + data.Name = nil + if err := validateSchemeImportData(&data); err == nil { + t.Fatal("Should have failed due to invalid name.") + } + + data.Name = ptrStr("") + if err := validateSchemeImportData(&data); err == nil { + t.Fatal("Should have failed due to invalid name.") + } + + data.Name = ptrStr(strings.Repeat("1234567890", 100)) + if err := validateSchemeImportData(&data); err == nil { + t.Fatal("Should have failed due to invalid name.") + } + + data.Name = ptrStr("name") + // Test with invalid display name. + data.DisplayName = nil + if err := validateSchemeImportData(&data); err == nil { + t.Fatal("Should have failed due to invalid display name.") + } + + data.DisplayName = ptrStr("") + if err := validateSchemeImportData(&data); err == nil { + t.Fatal("Should have failed due to invalid display name.") + } + + data.DisplayName = ptrStr(strings.Repeat("1234567890", 100)) + if err := validateSchemeImportData(&data); err == nil { + t.Fatal("Should have failed due to invalid display name.") + } + + data.DisplayName = ptrStr("display name") + + // Test with various missing roles. + data.DefaultTeamAdminRole = nil + if err := validateSchemeImportData(&data); err == nil { + t.Fatal("Should have failed due to missing role.") + } + + data.DefaultTeamAdminRole = &RoleImportData{ + Name: ptrStr("name"), + DisplayName: ptrStr("display name"), + Permissions: &[]string{"invite_user"}, + } + data.DefaultTeamUserRole = nil + if err := validateSchemeImportData(&data); err == nil { + t.Fatal("Should have failed due to missing role.") + } + + data.DefaultTeamUserRole = &RoleImportData{ + Name: ptrStr("name"), + DisplayName: ptrStr("display name"), + Permissions: &[]string{"invite_user"}, + } + data.DefaultChannelAdminRole = nil + if err := validateSchemeImportData(&data); err == nil { + t.Fatal("Should have failed due to missing role.") + } + + data.DefaultChannelAdminRole = &RoleImportData{ + Name: ptrStr("name"), + DisplayName: ptrStr("display name"), + Permissions: &[]string{"invite_user"}, + } + data.DefaultChannelUserRole = nil + if err := validateSchemeImportData(&data); err == nil { + t.Fatal("Should have failed due to missing role.") + } + + data.DefaultChannelUserRole = &RoleImportData{ + Name: ptrStr("name"), + DisplayName: ptrStr("display name"), + Permissions: &[]string{"invite_user"}, + } + + // Test with various invalid roles. + data.DefaultTeamAdminRole.Name = nil + if err := validateSchemeImportData(&data); err == nil { + t.Fatal("Should have failed due to invalid role.") + } + + data.DefaultTeamAdminRole.Name = ptrStr("name") + data.DefaultTeamUserRole.Name = nil + if err := validateSchemeImportData(&data); err == nil { + t.Fatal("Should have failed due to invalid role.") + } + + data.DefaultTeamUserRole.Name = ptrStr("name") + data.DefaultChannelAdminRole.Name = nil + if err := validateSchemeImportData(&data); err == nil { + t.Fatal("Should have failed due to invalid role.") + } + + data.DefaultChannelAdminRole.Name = ptrStr("name") + data.DefaultChannelUserRole.Name = nil + if err := validateSchemeImportData(&data); err == nil { + t.Fatal("Should have failed due to invalid role.") + } + + data.DefaultChannelUserRole.Name = ptrStr("name") + + // Change to a Channel scope role, and check with missing or extra roles again. + data.Scope = ptrStr("channel") + data.DefaultTeamAdminRole = nil + if err := validateSchemeImportData(&data); err == nil { + t.Fatal("Should have failed due to spurious role.") + } + + data.DefaultTeamAdminRole = &RoleImportData{ + Name: ptrStr("name"), + DisplayName: ptrStr("display name"), + Permissions: &[]string{"invite_user"}, + } + data.DefaultTeamUserRole = nil + if err := validateSchemeImportData(&data); err == nil { + t.Fatal("Should have failed due to spurious role.") + } + + data.DefaultTeamAdminRole = nil + if err := validateSchemeImportData(&data); err != nil { + t.Fatal("Should have succeeded.") + } + + // Test with all combinations of optional parameters. + data.Description = ptrStr(strings.Repeat("1234567890", 1024)) + if err := validateSchemeImportData(&data); err == nil { + t.Fatal("Should have failed due to invalid description.") + } + + data.Description = ptrStr("description") + if err := validateSchemeImportData(&data); err != nil { + t.Fatal("Should have succeeded.") + } +} + +func TestImportValidateRoleImportData(t *testing.T) { + // Test with minimum required valid properties. + data := RoleImportData{ + Name: ptrStr("name"), + DisplayName: ptrStr("display name"), + } + if err := validateRoleImportData(&data); err != nil { + t.Fatal("Validation failed but should have been valid.", err) + } + + // Test with various invalid names. + data.Name = nil + if err := validateRoleImportData(&data); err == nil { + t.Fatal("Should have failed due to invalid name.") + } + + data.Name = ptrStr("") + if err := validateRoleImportData(&data); err == nil { + t.Fatal("Should have failed due to invalid name.") + } + + data.Name = ptrStr(strings.Repeat("1234567890", 100)) + if err := validateRoleImportData(&data); err == nil { + t.Fatal("Should have failed due to invalid name.") + } + + data.Name = ptrStr("name") + // Test with invalid display name. + data.DisplayName = nil + if err := validateRoleImportData(&data); err == nil { + t.Fatal("Should have failed due to invalid display name.") + } + + data.DisplayName = ptrStr("") + if err := validateRoleImportData(&data); err == nil { + t.Fatal("Should have failed due to invalid display name.") + } + + data.DisplayName = ptrStr(strings.Repeat("1234567890", 100)) + if err := validateRoleImportData(&data); err == nil { + t.Fatal("Should have failed due to invalid display name.") + } + + data.DisplayName = ptrStr("display name") + + // Test with various valid/invalid permissions. + data.Permissions = &[]string{} + if err := validateRoleImportData(&data); err != nil { + t.Fatal("Validation failed but should have been valid.", err) + } + + data.Permissions = &[]string{"invite_user", "add_user_to_team"} + if err := validateRoleImportData(&data); err != nil { + t.Fatal("Validation failed but should have been valid.", err) + } + + data.Permissions = &[]string{"invite_user", "add_user_to_team", "derp"} + if err := validateRoleImportData(&data); err == nil { + t.Fatal("Validation should have failed due to invalid permission.", err) + } + + data.Permissions = &[]string{"invite_user", "add_user_to_team"} + + // Test with various valid/invalid descriptions. + data.Description = ptrStr(strings.Repeat("1234567890", 1024)) + if err := validateRoleImportData(&data); err == nil { + t.Fatal("Validation should have failed due to invalid description.", err) + } + + data.Description = ptrStr("description") + if err := validateRoleImportData(&data); err != nil { + t.Fatal("Validation failed but should have been valid.", err) + } +} + +func TestImportValidateTeamImportData(t *testing.T) { + + // Test with minimum required valid properties. + data := TeamImportData{ + Name: ptrStr("teamname"), + DisplayName: ptrStr("Display Name"), + Type: ptrStr("O"), + } + if err := validateTeamImportData(&data); err != nil { + t.Fatal("Validation failed but should have been valid.") + } + + // Test with various invalid names. + data = TeamImportData{ + DisplayName: ptrStr("Display Name"), + Type: ptrStr("O"), + } + if err := validateTeamImportData(&data); err == nil { + t.Fatal("Should have failed due to missing name.") + } + + data.Name = ptrStr(strings.Repeat("abcdefghij", 7)) + if err := validateTeamImportData(&data); err == nil { + t.Fatal("Should have failed due to too long name.") + } + + data.Name = ptrStr("login") + if err := validateTeamImportData(&data); err == nil { + t.Fatal("Should have failed due to reserved word in name.") + } + + data.Name = ptrStr("Test::''ASD") + if err := validateTeamImportData(&data); err == nil { + t.Fatal("Should have failed due to non alphanum characters in name.") + } + + data.Name = ptrStr("A") + if err := validateTeamImportData(&data); err == nil { + t.Fatal("Should have failed due to short name.") + } + + // Test team various invalid display names. + data = TeamImportData{ + Name: ptrStr("teamname"), + Type: ptrStr("O"), + } + if err := validateTeamImportData(&data); err == nil { + t.Fatal("Should have failed due to missing display_name.") + } + + data.DisplayName = ptrStr("") + if err := validateTeamImportData(&data); err == nil { + t.Fatal("Should have failed due to empty display_name.") + } + + data.DisplayName = ptrStr(strings.Repeat("abcdefghij", 7)) + if err := validateTeamImportData(&data); err == nil { + t.Fatal("Should have failed due to too long display_name.") + } + + // Test with various valid and invalid types. + data = TeamImportData{ + Name: ptrStr("teamname"), + DisplayName: ptrStr("Display Name"), + } + if err := validateTeamImportData(&data); err == nil { + t.Fatal("Should have failed due to missing type.") + } + + data.Type = ptrStr("A") + if err := validateTeamImportData(&data); err == nil { + t.Fatal("Should have failed due to invalid type.") + } + + data.Type = ptrStr("I") + if err := validateTeamImportData(&data); err != nil { + t.Fatal("Should have succeeded with valid type.") + } + + // Test with all the combinations of optional parameters. + data = TeamImportData{ + Name: ptrStr("teamname"), + DisplayName: ptrStr("Display Name"), + Type: ptrStr("O"), + Description: ptrStr("The team description."), + AllowOpenInvite: ptrBool(true), + } + if err := validateTeamImportData(&data); err != nil { + t.Fatal("Should have succeeded with valid optional properties.") + } + + data.AllowOpenInvite = ptrBool(false) + if err := validateTeamImportData(&data); err != nil { + t.Fatal("Should have succeeded with allow open invites false.") + } + + data.Description = ptrStr(strings.Repeat("abcdefghij ", 26)) + if err := validateTeamImportData(&data); err == nil { + t.Fatal("Should have failed due to too long description.") + } + + // Test with an empty scheme name. + data.Description = ptrStr("abcdefg") + data.Scheme = ptrStr("") + if err := validateTeamImportData(&data); err == nil { + t.Fatal("Should have failed due to empty scheme name.") + } + + // Test with a valid scheme name. + data.Scheme = ptrStr("abcdefg") + if err := validateTeamImportData(&data); err != nil { + t.Fatal("Should have succeeded with valid scheme name.") + } +} + +func TestImportValidateChannelImportData(t *testing.T) { + + // Test with minimum required valid properties. + data := ChannelImportData{ + Team: ptrStr("teamname"), + Name: ptrStr("channelname"), + DisplayName: ptrStr("Display Name"), + Type: ptrStr("O"), + } + if err := validateChannelImportData(&data); err != nil { + t.Fatal("Validation failed but should have been valid.") + } + + // Test with missing team. + data = ChannelImportData{ + Name: ptrStr("channelname"), + DisplayName: ptrStr("Display Name"), + Type: ptrStr("O"), + } + if err := validateChannelImportData(&data); err == nil { + t.Fatal("Should have failed due to missing team.") + } + + // Test with various invalid names. + data = ChannelImportData{ + Team: ptrStr("teamname"), + DisplayName: ptrStr("Display Name"), + Type: ptrStr("O"), + } + if err := validateChannelImportData(&data); err == nil { + t.Fatal("Should have failed due to missing name.") + } + + data.Name = ptrStr(strings.Repeat("abcdefghij", 7)) + if err := validateChannelImportData(&data); err == nil { + t.Fatal("Should have failed due to too long name.") + } + + data.Name = ptrStr("Test::''ASD") + if err := validateChannelImportData(&data); err == nil { + t.Fatal("Should have failed due to non alphanum characters in name.") + } + + data.Name = ptrStr("A") + if err := validateChannelImportData(&data); err == nil { + t.Fatal("Should have failed due to short name.") + } + + // Test team various invalid display names. + data = ChannelImportData{ + Team: ptrStr("teamname"), + Name: ptrStr("channelname"), + Type: ptrStr("O"), + } + if err := validateChannelImportData(&data); err == nil { + t.Fatal("Should have failed due to missing display_name.") + } + + data.DisplayName = ptrStr("") + if err := validateChannelImportData(&data); err == nil { + t.Fatal("Should have failed due to empty display_name.") + } + + data.DisplayName = ptrStr(strings.Repeat("abcdefghij", 7)) + if err := validateChannelImportData(&data); err == nil { + t.Fatal("Should have failed due to too long display_name.") + } + + // Test with various valid and invalid types. + data = ChannelImportData{ + Team: ptrStr("teamname"), + Name: ptrStr("channelname"), + DisplayName: ptrStr("Display Name"), + } + if err := validateChannelImportData(&data); err == nil { + t.Fatal("Should have failed due to missing type.") + } + + data.Type = ptrStr("A") + if err := validateChannelImportData(&data); err == nil { + t.Fatal("Should have failed due to invalid type.") + } + + data.Type = ptrStr("P") + if err := validateChannelImportData(&data); err != nil { + t.Fatal("Should have succeeded with valid type.") + } + + // Test with all the combinations of optional parameters. + data = ChannelImportData{ + Team: ptrStr("teamname"), + Name: ptrStr("channelname"), + DisplayName: ptrStr("Display Name"), + Type: ptrStr("O"), + Header: ptrStr("Channel Header Here"), + Purpose: ptrStr("Channel Purpose Here"), + } + if err := validateChannelImportData(&data); err != nil { + t.Fatal("Should have succeeded with valid optional properties.") + } + + data.Header = ptrStr(strings.Repeat("abcdefghij ", 103)) + if err := validateChannelImportData(&data); err == nil { + t.Fatal("Should have failed due to too long header.") + } + + data.Header = ptrStr("Channel Header Here") + data.Purpose = ptrStr(strings.Repeat("abcdefghij ", 26)) + if err := validateChannelImportData(&data); err == nil { + t.Fatal("Should have failed due to too long purpose.") + } + + // Test with an empty scheme name. + data.Purpose = ptrStr("abcdefg") + data.Scheme = ptrStr("") + if err := validateChannelImportData(&data); err == nil { + t.Fatal("Should have failed due to empty scheme name.") + } + + // Test with a valid scheme name. + data.Scheme = ptrStr("abcdefg") + if err := validateChannelImportData(&data); err != nil { + t.Fatal("Should have succeeded with valid scheme name.") + } +} + +func TestImportValidateUserImportData(t *testing.T) { + + // Test with minimum required valid properties. + data := UserImportData{ + Username: ptrStr("bob"), + Email: ptrStr("bob@example.com"), + } + if err := validateUserImportData(&data); err != nil { + t.Fatal("Validation failed but should have been valid.") + } + + // Invalid Usernames. + data.Username = nil + if err := validateUserImportData(&data); err == nil { + t.Fatal("Validation should have failed due to nil Username.") + } + + data.Username = ptrStr("") + if err := validateUserImportData(&data); err == nil { + t.Fatal("Validation should have failed due to 0 length Username.") + } + + data.Username = ptrStr(strings.Repeat("abcdefghij", 7)) + if err := validateUserImportData(&data); err == nil { + t.Fatal("Validation should have failed due to too long Username.") + } + + data.Username = ptrStr("i am a username with spaces and !!!") + if err := validateUserImportData(&data); err == nil { + t.Fatal("Validation should have failed due to invalid characters in Username.") + } + + data.Username = ptrStr("bob") + + // Unexisting Picture Image + data.ProfileImage = ptrStr("not-existing-file") + if err := validateUserImportData(&data); err == nil { + t.Fatal("Validation should have failed due to not existing profile image file.") + } + data.ProfileImage = nil + + // Invalid Emails + data.Email = nil + if err := validateUserImportData(&data); err == nil { + t.Fatal("Validation should have failed due to nil Email.") + } + + data.Email = ptrStr("") + if err := validateUserImportData(&data); err == nil { + t.Fatal("Validation should have failed due to 0 length Email.") + } + + data.Email = ptrStr(strings.Repeat("abcdefghij", 13)) + if err := validateUserImportData(&data); err == nil { + t.Fatal("Validation should have failed due to too long Email.") + } + + data.Email = ptrStr("bob@example.com") + + data.AuthService = ptrStr("") + if err := validateUserImportData(&data); err == nil { + t.Fatal("Validation should have failed due to 0-length auth service.") + } + + data.AuthService = ptrStr("saml") + data.AuthData = ptrStr(strings.Repeat("abcdefghij", 15)) + if err := validateUserImportData(&data); err == nil { + t.Fatal("Validation should have failed due to too long auth data.") + } + + data.AuthData = ptrStr("bobbytables") + if err := validateUserImportData(&data); err != nil { + t.Fatal("Validation should have succeeded with valid auth service and auth data.") + } + + // Test a valid User with all fields populated. + testsDir, _ := utils.FindDir("tests") + data = UserImportData{ + ProfileImage: ptrStr(filepath.Join(testsDir, "test.png")), + Username: ptrStr("bob"), + Email: ptrStr("bob@example.com"), + AuthService: ptrStr("ldap"), + AuthData: ptrStr("bob"), + Nickname: ptrStr("BobNick"), + FirstName: ptrStr("Bob"), + LastName: ptrStr("Blob"), + Position: ptrStr("The Boss"), + Roles: ptrStr("system_user"), + Locale: ptrStr("en"), + } + if err := validateUserImportData(&data); err != nil { + t.Fatal("Validation failed but should have been valid.") + } + + // Test various invalid optional field values. + data.Nickname = ptrStr(strings.Repeat("abcdefghij", 7)) + if err := validateUserImportData(&data); err == nil { + t.Fatal("Validation should have failed due to too long Nickname.") + } + data.Nickname = ptrStr("BobNick") + + data.FirstName = ptrStr(strings.Repeat("abcdefghij", 7)) + if err := validateUserImportData(&data); err == nil { + t.Fatal("Validation should have failed due to too long First Name.") + } + data.FirstName = ptrStr("Bob") + + data.LastName = ptrStr(strings.Repeat("abcdefghij", 7)) + if err := validateUserImportData(&data); err == nil { + t.Fatal("Validation should have failed due to too long Last name.") + } + data.LastName = ptrStr("Blob") + + data.Position = ptrStr(strings.Repeat("abcdefghij", 13)) + if err := validateUserImportData(&data); err == nil { + t.Fatal("Validation should have failed due to too long Position.") + } + data.Position = ptrStr("The Boss") + + data.Roles = nil + if err := validateUserImportData(&data); err != nil { + t.Fatal("Validation failed but should have been valid.") + } + + data.Roles = ptrStr("") + if err := validateUserImportData(&data); err != nil { + t.Fatal("Validation failed but should have been valid.") + } + data.Roles = ptrStr("system_user") + + // Try various valid/invalid notify props. + data.NotifyProps = &UserNotifyPropsImportData{} + + data.NotifyProps.Desktop = ptrStr("invalid") + checkError(t, validateUserImportData(&data)) + + data.NotifyProps.Desktop = ptrStr(model.USER_NOTIFY_ALL) + data.NotifyProps.DesktopSound = ptrStr("invalid") + checkError(t, validateUserImportData(&data)) + + data.NotifyProps.DesktopSound = ptrStr("true") + data.NotifyProps.Email = ptrStr("invalid") + checkError(t, validateUserImportData(&data)) + + data.NotifyProps.Email = ptrStr("true") + data.NotifyProps.Mobile = ptrStr("invalid") + checkError(t, validateUserImportData(&data)) + + data.NotifyProps.Mobile = ptrStr(model.USER_NOTIFY_ALL) + data.NotifyProps.MobilePushStatus = ptrStr("invalid") + checkError(t, validateUserImportData(&data)) + + data.NotifyProps.MobilePushStatus = ptrStr(model.STATUS_ONLINE) + data.NotifyProps.ChannelTrigger = ptrStr("invalid") + checkError(t, validateUserImportData(&data)) + + data.NotifyProps.ChannelTrigger = ptrStr("true") + data.NotifyProps.CommentsTrigger = ptrStr("invalid") + checkError(t, validateUserImportData(&data)) + + data.NotifyProps.CommentsTrigger = ptrStr(model.COMMENTS_NOTIFY_ROOT) + data.NotifyProps.MentionKeys = ptrStr("valid") + checkNoError(t, validateUserImportData(&data)) +} + +func TestImportValidateUserTeamsImportData(t *testing.T) { + + // Invalid Name. + data := []UserTeamImportData{ + { + Roles: ptrStr("team_admin team_user"), + }, + } + if err := validateUserTeamsImportData(&data); err == nil { + t.Fatal("Should have failed due to invalid name.") + } + data[0].Name = ptrStr("teamname") + + // Valid (nil roles) + data[0].Roles = nil + if err := validateUserTeamsImportData(&data); err != nil { + t.Fatal("Should have succeeded with empty roles.") + } + + // Valid (empty roles) + data[0].Roles = ptrStr("") + if err := validateUserTeamsImportData(&data); err != nil { + t.Fatal("Should have succeeded with empty roles.") + } + + // Valid (with roles) + data[0].Roles = ptrStr("team_admin team_user") + if err := validateUserTeamsImportData(&data); err != nil { + t.Fatal("Should have succeeded with valid roles.") + } +} + +func TestImportValidateUserChannelsImportData(t *testing.T) { + + // Invalid Name. + data := []UserChannelImportData{ + { + Roles: ptrStr("channel_admin channel_user"), + }, + } + if err := validateUserChannelsImportData(&data); err == nil { + t.Fatal("Should have failed due to invalid name.") + } + data[0].Name = ptrStr("channelname") + + // Valid (nil roles) + data[0].Roles = nil + if err := validateUserChannelsImportData(&data); err != nil { + t.Fatal("Should have succeeded with empty roles.") + } + + // Valid (empty roles) + data[0].Roles = ptrStr("") + if err := validateUserChannelsImportData(&data); err != nil { + t.Fatal("Should have succeeded with empty roles.") + } + + // Valid (with roles) + data[0].Roles = ptrStr("channel_admin channel_user") + if err := validateUserChannelsImportData(&data); err != nil { + t.Fatal("Should have succeeded with valid roles.") + } + + // Empty notify props. + data[0].NotifyProps = &UserChannelNotifyPropsImportData{} + if err := validateUserChannelsImportData(&data); err != nil { + t.Fatal("Should have succeeded with empty notify props.") + } + + // Invalid desktop notify props. + data[0].NotifyProps.Desktop = ptrStr("invalid") + if err := validateUserChannelsImportData(&data); err == nil { + t.Fatal("Should have failed with invalid desktop notify props.") + } + + // Invalid mobile notify props. + data[0].NotifyProps.Desktop = ptrStr("mention") + data[0].NotifyProps.Mobile = ptrStr("invalid") + if err := validateUserChannelsImportData(&data); err == nil { + t.Fatal("Should have failed with invalid mobile notify props.") + } + + // Invalid mark_unread notify props. + data[0].NotifyProps.Mobile = ptrStr("mention") + data[0].NotifyProps.MarkUnread = ptrStr("invalid") + if err := validateUserChannelsImportData(&data); err == nil { + t.Fatal("Should have failed with invalid mark_unread notify props.") + } + + // Valid notify props. + data[0].NotifyProps.MarkUnread = ptrStr("mention") + if err := validateUserChannelsImportData(&data); err != nil { + t.Fatal("Should have succeeded with valid notify props.") + } +} + +func TestImportValidateReactionImportData(t *testing.T) { + // Test with minimum required valid properties. + parentCreateAt := model.GetMillis() - 100 + data := ReactionImportData{ + User: ptrStr("username"), + EmojiName: ptrStr("emoji"), + CreateAt: ptrInt64(model.GetMillis()), + } + if err := validateReactionImportData(&data, parentCreateAt); err != nil { + t.Fatal("Validation failed but should have been valid.") + } + + // Test with missing required properties. + data = ReactionImportData{ + EmojiName: ptrStr("emoji"), + CreateAt: ptrInt64(model.GetMillis()), + } + if err := validateReactionImportData(&data, parentCreateAt); err == nil { + t.Fatal("Should have failed due to missing required property.") + } + + data = ReactionImportData{ + User: ptrStr("username"), + CreateAt: ptrInt64(model.GetMillis()), + } + if err := validateReactionImportData(&data, parentCreateAt); err == nil { + t.Fatal("Should have failed due to missing required property.") + } + + data = ReactionImportData{ + User: ptrStr("username"), + EmojiName: ptrStr("emoji"), + } + if err := validateReactionImportData(&data, parentCreateAt); err == nil { + t.Fatal("Should have failed due to missing required property.") + } + + // Test with invalid emoji name. + data = ReactionImportData{ + User: ptrStr("username"), + EmojiName: ptrStr(strings.Repeat("1234567890", 500)), + CreateAt: ptrInt64(model.GetMillis()), + } + if err := validateReactionImportData(&data, parentCreateAt); err == nil { + t.Fatal("Should have failed due to too long emoji name.") + } + + // Test with invalid CreateAt + data = ReactionImportData{ + User: ptrStr("username"), + EmojiName: ptrStr("emoji"), + CreateAt: ptrInt64(0), + } + if err := validateReactionImportData(&data, parentCreateAt); err == nil { + t.Fatal("Should have failed due to 0 create-at value.") + } + + data = ReactionImportData{ + User: ptrStr("username"), + EmojiName: ptrStr("emoji"), + CreateAt: ptrInt64(parentCreateAt - 100), + } + if err := validateReactionImportData(&data, parentCreateAt); err == nil { + t.Fatal("Should have failed due parent with newer create-at value.") + } +} + +func TestImportValidateReplyImportData(t *testing.T) { + // Test with minimum required valid properties. + parentCreateAt := model.GetMillis() - 100 + maxPostSize := 10000 + data := ReplyImportData{ + User: ptrStr("username"), + Message: ptrStr("message"), + CreateAt: ptrInt64(model.GetMillis()), + } + if err := validateReplyImportData(&data, parentCreateAt, maxPostSize); err != nil { + t.Fatal("Validation failed but should have been valid.") + } + + // Test with missing required properties. + data = ReplyImportData{ + Message: ptrStr("message"), + CreateAt: ptrInt64(model.GetMillis()), + } + if err := validateReplyImportData(&data, parentCreateAt, maxPostSize); err == nil { + t.Fatal("Should have failed due to missing required property.") + } + + data = ReplyImportData{ + User: ptrStr("username"), + CreateAt: ptrInt64(model.GetMillis()), + } + if err := validateReplyImportData(&data, parentCreateAt, maxPostSize); err == nil { + t.Fatal("Should have failed due to missing required property.") + } + + data = ReplyImportData{ + User: ptrStr("username"), + Message: ptrStr("message"), + } + if err := validateReplyImportData(&data, parentCreateAt, maxPostSize); err == nil { + t.Fatal("Should have failed due to missing required property.") + } + + // Test with invalid message. + data = ReplyImportData{ + User: ptrStr("username"), + Message: ptrStr(strings.Repeat("0", maxPostSize+1)), + CreateAt: ptrInt64(model.GetMillis()), + } + if err := validateReplyImportData(&data, parentCreateAt, maxPostSize); err == nil { + t.Fatal("Should have failed due to too long message.") + } + + // Test with invalid CreateAt + data = ReplyImportData{ + User: ptrStr("username"), + Message: ptrStr("message"), + CreateAt: ptrInt64(0), + } + if err := validateReplyImportData(&data, parentCreateAt, maxPostSize); err == nil { + t.Fatal("Should have failed due to 0 create-at value.") + } + + data = ReplyImportData{ + User: ptrStr("username"), + Message: ptrStr("message"), + CreateAt: ptrInt64(parentCreateAt - 100), + } + if err := validateReplyImportData(&data, parentCreateAt, maxPostSize); err == nil { + t.Fatal("Should have failed due parent with newer create-at value.") + } +} + +func TestImportValidatePostImportData(t *testing.T) { + maxPostSize := 10000 + + // Test with minimum required valid properties. + data := PostImportData{ + Team: ptrStr("teamname"), + Channel: ptrStr("channelname"), + User: ptrStr("username"), + Message: ptrStr("message"), + CreateAt: ptrInt64(model.GetMillis()), + } + if err := validatePostImportData(&data, maxPostSize); err != nil { + t.Fatal("Validation failed but should have been valid.") + } + + // Test with missing required properties. + data = PostImportData{ + Channel: ptrStr("channelname"), + User: ptrStr("username"), + Message: ptrStr("message"), + CreateAt: ptrInt64(model.GetMillis()), + } + if err := validatePostImportData(&data, maxPostSize); err == nil { + t.Fatal("Should have failed due to missing required property.") + } + + data = PostImportData{ + Team: ptrStr("teamname"), + User: ptrStr("username"), + Message: ptrStr("message"), + CreateAt: ptrInt64(model.GetMillis()), + } + if err := validatePostImportData(&data, maxPostSize); err == nil { + t.Fatal("Should have failed due to missing required property.") + } + + data = PostImportData{ + Team: ptrStr("teamname"), + Channel: ptrStr("channelname"), + Message: ptrStr("message"), + CreateAt: ptrInt64(model.GetMillis()), + } + if err := validatePostImportData(&data, maxPostSize); err == nil { + t.Fatal("Should have failed due to missing required property.") + } + + data = PostImportData{ + Team: ptrStr("teamname"), + Channel: ptrStr("channelname"), + User: ptrStr("username"), + CreateAt: ptrInt64(model.GetMillis()), + } + if err := validatePostImportData(&data, maxPostSize); err == nil { + t.Fatal("Should have failed due to missing required property.") + } + + data = PostImportData{ + Team: ptrStr("teamname"), + Channel: ptrStr("channelname"), + User: ptrStr("username"), + Message: ptrStr("message"), + } + if err := validatePostImportData(&data, maxPostSize); err == nil { + t.Fatal("Should have failed due to missing required property.") + } + + // Test with invalid message. + data = PostImportData{ + Team: ptrStr("teamname"), + Channel: ptrStr("channelname"), + User: ptrStr("username"), + Message: ptrStr(strings.Repeat("0", maxPostSize+1)), + CreateAt: ptrInt64(model.GetMillis()), + } + if err := validatePostImportData(&data, maxPostSize); err == nil { + t.Fatal("Should have failed due to too long message.") + } + + // Test with invalid CreateAt + data = PostImportData{ + Team: ptrStr("teamname"), + Channel: ptrStr("channelname"), + User: ptrStr("username"), + Message: ptrStr("message"), + CreateAt: ptrInt64(0), + } + if err := validatePostImportData(&data, maxPostSize); err == nil { + t.Fatal("Should have failed due to 0 create-at value.") + } + + // Test with valid all optional parameters. + reactions := []ReactionImportData{ReactionImportData{ + User: ptrStr("username"), + EmojiName: ptrStr("emoji"), + CreateAt: ptrInt64(model.GetMillis()), + }} + replies := []ReplyImportData{ReplyImportData{ + User: ptrStr("username"), + Message: ptrStr("message"), + CreateAt: ptrInt64(model.GetMillis()), + }} + data = PostImportData{ + Team: ptrStr("teamname"), + Channel: ptrStr("channelname"), + User: ptrStr("username"), + Message: ptrStr("message"), + CreateAt: ptrInt64(model.GetMillis()), + Reactions: &reactions, + Replies: &replies, + } + if err := validatePostImportData(&data, maxPostSize); err != nil { + t.Fatal("Should have succeeded.") + } +} + +func TestImportValidateDirectChannelImportData(t *testing.T) { + + // Test with valid number of members for direct message. + data := DirectChannelImportData{ + Members: &[]string{ + model.NewId(), + model.NewId(), + }, + } + if err := validateDirectChannelImportData(&data); err != nil { + t.Fatal("Validation failed but should have been valid.") + } + + // Test with valid number of members for group message. + data = DirectChannelImportData{ + Members: &[]string{ + model.NewId(), + model.NewId(), + model.NewId(), + }, + } + if err := validateDirectChannelImportData(&data); err != nil { + t.Fatal("Validation failed but should have been valid.") + } + + // Test with all the combinations of optional parameters. + data = DirectChannelImportData{ + Members: &[]string{ + model.NewId(), + model.NewId(), + }, + Header: ptrStr("Channel Header Here"), + } + if err := validateDirectChannelImportData(&data); err != nil { + t.Fatal("Should have succeeded with valid optional properties.") + } + + // Test with invalid Header. + data.Header = ptrStr(strings.Repeat("abcdefghij ", 103)) + if err := validateDirectChannelImportData(&data); err == nil { + t.Fatal("Should have failed due to too long header.") + } + + // Test with different combinations of invalid member counts. + data = DirectChannelImportData{ + Members: &[]string{}, + } + if err := validateDirectChannelImportData(&data); err == nil { + t.Fatal("Validation should have failed due to invalid number of members.") + } + + data = DirectChannelImportData{ + Members: &[]string{ + model.NewId(), + }, + } + if err := validateDirectChannelImportData(&data); err == nil { + t.Fatal("Validation should have failed due to invalid number of members.") + } + + data = DirectChannelImportData{ + Members: &[]string{ + model.NewId(), + model.NewId(), + model.NewId(), + model.NewId(), + model.NewId(), + model.NewId(), + model.NewId(), + model.NewId(), + model.NewId(), + }, + } + if err := validateDirectChannelImportData(&data); err == nil { + t.Fatal("Validation should have failed due to invalid number of members.") + } + + // Test with invalid FavoritedBy + member1 := model.NewId() + member2 := model.NewId() + data = DirectChannelImportData{ + Members: &[]string{ + member1, + member2, + }, + FavoritedBy: &[]string{ + member1, + model.NewId(), + }, + } + if err := validateDirectChannelImportData(&data); err == nil { + t.Fatal("Validation should have failed due to non-member favorited.") + } + + // Test with valid FavoritedBy + data = DirectChannelImportData{ + Members: &[]string{ + member1, + member2, + }, + FavoritedBy: &[]string{ + member1, + member2, + }, + } + if err := validateDirectChannelImportData(&data); err != nil { + t.Fatal(err) + } +} + +func TestImportValidateDirectPostImportData(t *testing.T) { + maxPostSize := 10000 + + // Test with minimum required valid properties. + data := DirectPostImportData{ + ChannelMembers: &[]string{ + model.NewId(), + model.NewId(), + }, + User: ptrStr("username"), + Message: ptrStr("message"), + CreateAt: ptrInt64(model.GetMillis()), + } + if err := validateDirectPostImportData(&data, maxPostSize); err != nil { + t.Fatal("Validation failed but should have been valid.") + } + + // Test with missing required properties. + data = DirectPostImportData{ + User: ptrStr("username"), + Message: ptrStr("message"), + CreateAt: ptrInt64(model.GetMillis()), + } + if err := validateDirectPostImportData(&data, maxPostSize); err == nil { + t.Fatal("Should have failed due to missing required property.") + } + + data = DirectPostImportData{ + ChannelMembers: &[]string{ + model.NewId(), + model.NewId(), + }, + Message: ptrStr("message"), + CreateAt: ptrInt64(model.GetMillis()), + } + if err := validateDirectPostImportData(&data, maxPostSize); err == nil { + t.Fatal("Should have failed due to missing required property.") + } + + data = DirectPostImportData{ + ChannelMembers: &[]string{ + model.NewId(), + model.NewId(), + }, + User: ptrStr("username"), + CreateAt: ptrInt64(model.GetMillis()), + } + if err := validateDirectPostImportData(&data, maxPostSize); err == nil { + t.Fatal("Should have failed due to missing required property.") + } + + data = DirectPostImportData{ + ChannelMembers: &[]string{ + model.NewId(), + model.NewId(), + }, + User: ptrStr("username"), + Message: ptrStr("message"), + } + if err := validateDirectPostImportData(&data, maxPostSize); err == nil { + t.Fatal("Should have failed due to missing required property.") + } + + // Test with invalid numbers of channel members. + data = DirectPostImportData{ + ChannelMembers: &[]string{}, + User: ptrStr("username"), + Message: ptrStr("message"), + CreateAt: ptrInt64(model.GetMillis()), + } + if err := validateDirectPostImportData(&data, maxPostSize); err == nil { + t.Fatal("Should have failed due to unsuitable number of members.") + } + + data = DirectPostImportData{ + ChannelMembers: &[]string{ + model.NewId(), + }, + User: ptrStr("username"), + Message: ptrStr("message"), + CreateAt: ptrInt64(model.GetMillis()), + } + if err := validateDirectPostImportData(&data, maxPostSize); err == nil { + t.Fatal("Should have failed due to unsuitable number of members.") + } + + data = DirectPostImportData{ + ChannelMembers: &[]string{ + model.NewId(), + model.NewId(), + model.NewId(), + model.NewId(), + model.NewId(), + model.NewId(), + model.NewId(), + model.NewId(), + model.NewId(), + model.NewId(), + }, + User: ptrStr("username"), + Message: ptrStr("message"), + CreateAt: ptrInt64(model.GetMillis()), + } + if err := validateDirectPostImportData(&data, maxPostSize); err == nil { + t.Fatal("Should have failed due to unsuitable number of members.") + } + + // Test with group message number of members. + data = DirectPostImportData{ + ChannelMembers: &[]string{ + model.NewId(), + model.NewId(), + model.NewId(), + }, + User: ptrStr("username"), + Message: ptrStr("message"), + CreateAt: ptrInt64(model.GetMillis()), + } + if err := validateDirectPostImportData(&data, maxPostSize); err != nil { + t.Fatal("Validation failed but should have been valid.") + } + + // Test with invalid message. + data = DirectPostImportData{ + ChannelMembers: &[]string{ + model.NewId(), + model.NewId(), + }, + User: ptrStr("username"), + Message: ptrStr(strings.Repeat("0", maxPostSize+1)), + CreateAt: ptrInt64(model.GetMillis()), + } + if err := validateDirectPostImportData(&data, maxPostSize); err == nil { + t.Fatal("Should have failed due to too long message.") + } + + // Test with invalid CreateAt + data = DirectPostImportData{ + ChannelMembers: &[]string{ + model.NewId(), + model.NewId(), + }, + User: ptrStr("username"), + Message: ptrStr("message"), + CreateAt: ptrInt64(0), + } + if err := validateDirectPostImportData(&data, maxPostSize); err == nil { + t.Fatal("Should have failed due to 0 create-at value.") + } + + // Test with invalid FlaggedBy + member1 := model.NewId() + member2 := model.NewId() + data = DirectPostImportData{ + ChannelMembers: &[]string{ + member1, + member2, + }, + FlaggedBy: &[]string{ + member1, + model.NewId(), + }, + User: ptrStr("username"), + Message: ptrStr("message"), + CreateAt: ptrInt64(model.GetMillis()), + } + if err := validateDirectPostImportData(&data, maxPostSize); err == nil { + t.Fatal("Validation should have failed due to non-member flagged.") + } + + // Test with valid FlaggedBy + data = DirectPostImportData{ + ChannelMembers: &[]string{ + member1, + member2, + }, + FlaggedBy: &[]string{ + member1, + member2, + }, + User: ptrStr("username"), + Message: ptrStr("message"), + CreateAt: ptrInt64(model.GetMillis()), + } + if err := validateDirectPostImportData(&data, maxPostSize); err != nil { + t.Fatal(err) + } + + // Test with valid all optional parameters. + reactions := []ReactionImportData{ReactionImportData{ + User: ptrStr("username"), + EmojiName: ptrStr("emoji"), + CreateAt: ptrInt64(model.GetMillis()), + }} + replies := []ReplyImportData{ReplyImportData{ + User: ptrStr("username"), + Message: ptrStr("message"), + CreateAt: ptrInt64(model.GetMillis()), + }} + data = DirectPostImportData{ + ChannelMembers: &[]string{ + member1, + member2, + }, + FlaggedBy: &[]string{ + member1, + member2, + }, + User: ptrStr("username"), + Message: ptrStr("message"), + CreateAt: ptrInt64(model.GetMillis()), + Reactions: &reactions, + Replies: &replies, + } + + if err := validateDirectPostImportData(&data, maxPostSize); err != nil { + t.Fatal(err) + } +} + +func TestImportValidateEmojiImportData(t *testing.T) { + data := EmojiImportData{ + Name: ptrStr("parrot"), + Image: ptrStr("/path/to/image"), + } + + err := validateEmojiImportData(&data) + assert.Nil(t, err, "Validation should succeed") + + *data.Name = "smiley" + err = validateEmojiImportData(&data) + assert.NotNil(t, err) + + *data.Name = "" + err = validateEmojiImportData(&data) + assert.NotNil(t, err) + + *data.Name = "" + *data.Image = "" + err = validateEmojiImportData(&data) + assert.NotNil(t, err) + + *data.Image = "/path/to/image" + data.Name = nil + err = validateEmojiImportData(&data) + assert.NotNil(t, err) + + data.Name = ptrStr("parrot") + data.Image = nil + err = validateEmojiImportData(&data) + assert.NotNil(t, err) +} diff --git a/app/slackimport.go b/app/slackimport.go index f9e2ac4ab..5f1f73632 100644 --- a/app/slackimport.go +++ b/app/slackimport.go @@ -14,6 +14,7 @@ import ( "regexp" "strconv" "strings" + "time" "unicode/utf8" "net/http" @@ -688,3 +689,120 @@ func (a *App) SlackImport(fileData multipart.File, fileSize int64, teamID string return nil, log } + +// +// -- Old SlackImport Functions -- +// Import functions are sutible for entering posts and users into the database without +// some of the usual checks. (IsValid is still run) +// + +func (a *App) OldImportPost(post *model.Post) { + // Workaround for empty messages, which may be the case if they are webhook posts. + firstIteration := true + maxPostSize := a.MaxPostSize() + for messageRuneCount := utf8.RuneCountInString(post.Message); messageRuneCount > 0 || firstIteration; messageRuneCount = utf8.RuneCountInString(post.Message) { + firstIteration = false + var remainder string + if messageRuneCount > maxPostSize { + remainder = string(([]rune(post.Message))[maxPostSize:]) + post.Message = truncateRunes(post.Message, maxPostSize) + } else { + remainder = "" + } + + post.Hashtags, _ = model.ParseHashtags(post.Message) + + if result := <-a.Srv.Store.Post().Save(post); result.Err != nil { + mlog.Debug(fmt.Sprintf("Error saving post. user=%v, message=%v", post.UserId, post.Message)) + } + + for _, fileId := range post.FileIds { + if result := <-a.Srv.Store.FileInfo().AttachToPost(fileId, post.Id); result.Err != nil { + mlog.Error(fmt.Sprintf("Error attaching files to post. postId=%v, fileIds=%v, message=%v", post.Id, post.FileIds, result.Err), mlog.String("post_id", post.Id)) + } + } + + post.Id = "" + post.CreateAt++ + post.Message = remainder + } +} + +func (a *App) OldImportUser(team *model.Team, user *model.User) *model.User { + user.MakeNonNil() + + user.Roles = model.SYSTEM_USER_ROLE_ID + + if result := <-a.Srv.Store.User().Save(user); result.Err != nil { + mlog.Error(fmt.Sprintf("Error saving user. err=%v", result.Err)) + return nil + } else { + ruser := result.Data.(*model.User) + + if cresult := <-a.Srv.Store.User().VerifyEmail(ruser.Id); cresult.Err != nil { + mlog.Error(fmt.Sprintf("Failed to set email verified err=%v", cresult.Err)) + } + + if err := a.JoinUserToTeam(team, user, ""); err != nil { + mlog.Error(fmt.Sprintf("Failed to join team when importing err=%v", err)) + } + + return ruser + } +} + +func (a *App) OldImportChannel(channel *model.Channel) *model.Channel { + if result := <-a.Srv.Store.Channel().Save(channel, *a.Config().TeamSettings.MaxChannelsPerTeam); result.Err != nil { + return nil + } else { + sc := result.Data.(*model.Channel) + + return sc + } +} + +func (a *App) OldImportFile(timestamp time.Time, file io.Reader, teamId string, channelId string, userId string, fileName string) (*model.FileInfo, error) { + buf := bytes.NewBuffer(nil) + io.Copy(buf, file) + data := buf.Bytes() + + fileInfo, err := a.DoUploadFile(timestamp, teamId, channelId, userId, fileName, data) + if err != nil { + return nil, err + } + + if fileInfo.IsImage() && fileInfo.MimeType != "image/svg+xml" { + img, width, height := prepareImage(data) + if img != nil { + a.generateThumbnailImage(*img, fileInfo.ThumbnailPath, width, height) + a.generatePreviewImage(*img, fileInfo.PreviewPath, width) + } + } + + return fileInfo, nil +} + +func (a *App) OldImportIncomingWebhookPost(post *model.Post, props model.StringInterface) { + linkWithTextRegex := regexp.MustCompile(`<([^<\|]+)\|([^>]+)>`) + post.Message = linkWithTextRegex.ReplaceAllString(post.Message, "[${2}](${1})") + + post.AddProp("from_webhook", "true") + + if _, ok := props["override_username"]; !ok { + post.AddProp("override_username", model.DEFAULT_WEBHOOK_USERNAME) + } + + if len(props) > 0 { + for key, val := range props { + if key == "attachments" { + if attachments, success := val.([]*model.SlackAttachment); success { + parseSlackAttachment(post, attachments) + } + } else if key != "from_webhook" { + post.AddProp(key, val) + } + } + } + + a.OldImportPost(post) +} -- cgit v1.2.3-1-g7c22