From 08e54ed8244e2ec9a278d16f80d5ed2a8e2964f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Espino?= Date: Sat, 28 Jul 2018 19:20:44 +0200 Subject: Split notifications file into different files (#9164) --- app/notification.go | 596 -------------------- app/notification_email.go | 348 ++++++++++++ app/notification_email_test.go | 500 +++++++++++++++++ app/notification_push.go | 276 ++++++++++ app/notification_push_test.go | 699 +++++++++++++++++++++++ app/notification_test.go | 1188 +--------------------------------------- 6 files changed, 1828 insertions(+), 1779 deletions(-) create mode 100644 app/notification_email.go create mode 100644 app/notification_email_test.go create mode 100644 app/notification_push.go create mode 100644 app/notification_push_test.go diff --git a/app/notification.go b/app/notification.go index 477f73a27..3a6d4696d 100644 --- a/app/notification.go +++ b/app/notification.go @@ -5,13 +5,8 @@ package app import ( "fmt" - "html" - "net/http" - "net/url" - "path/filepath" "sort" "strings" - "time" "unicode" "github.com/mattermost/mattermost-server/mlog" @@ -19,7 +14,6 @@ import ( "github.com/mattermost/mattermost-server/store" "github.com/mattermost/mattermost-server/utils" "github.com/mattermost/mattermost-server/utils/markdown" - "github.com/nicksnyder/go-i18n/i18n" ) const ( @@ -387,533 +381,6 @@ func (a *App) SendNotifications(post *model.Post, team *model.Team, channel *mod return mentionedUsersList, nil } -func (a *App) sendNotificationEmail(post *model.Post, user *model.User, channel *model.Channel, team *model.Team, channelName string, senderName string, sender *model.User) *model.AppError { - if channel.IsGroupOrDirect() { - if result := <-a.Srv.Store.Team().GetTeamsByUserId(user.Id); result.Err != nil { - return result.Err - } else { - // if the recipient isn't in the current user's team, just pick one - teams := result.Data.([]*model.Team) - found := false - - for i := range teams { - if teams[i].Id == team.Id { - found = true - break - } - } - - if !found && len(teams) > 0 { - team = teams[0] - } else { - // in case the user hasn't joined any teams we send them to the select_team page - team = &model.Team{Name: "select_team", DisplayName: a.Config().TeamSettings.SiteName} - } - } - } - if *a.Config().EmailSettings.EnableEmailBatching { - var sendBatched bool - if result := <-a.Srv.Store.Preference().Get(user.Id, model.PREFERENCE_CATEGORY_NOTIFICATIONS, model.PREFERENCE_NAME_EMAIL_INTERVAL); result.Err != nil { - // if the call fails, assume that the interval has not been explicitly set and batch the notifications - sendBatched = true - } else { - // if the user has chosen to receive notifications immediately, don't batch them - sendBatched = result.Data.(model.Preference).Value != model.PREFERENCE_EMAIL_INTERVAL_NO_BATCHING_SECONDS - } - - if sendBatched { - if err := a.AddNotificationEmailToBatch(user, post, team); err == nil { - return nil - } - } - - // fall back to sending a single email if we can't batch it for some reason - } - - var useMilitaryTime bool - translateFunc := utils.GetUserTranslations(user.Locale) - if result := <-a.Srv.Store.Preference().Get(user.Id, model.PREFERENCE_CATEGORY_DISPLAY_SETTINGS, "use_military_time"); result.Err != nil { - useMilitaryTime = true - } else { - useMilitaryTime = result.Data.(model.Preference).Value == "true" - } - - emailNotificationContentsType := model.EMAIL_NOTIFICATION_CONTENTS_FULL - if license := a.License(); license != nil && *license.Features.EmailNotificationContents { - emailNotificationContentsType = *a.Config().EmailSettings.EmailNotificationContentsType - } - - var subjectText string - if channel.Type == model.CHANNEL_DIRECT { - subjectText = getDirectMessageNotificationEmailSubject(user, post, translateFunc, a.Config().TeamSettings.SiteName, senderName, useMilitaryTime) - } else if channel.Type == model.CHANNEL_GROUP { - subjectText = getGroupMessageNotificationEmailSubject(user, post, translateFunc, a.Config().TeamSettings.SiteName, channelName, emailNotificationContentsType, useMilitaryTime) - } else if *a.Config().EmailSettings.UseChannelInEmailNotifications { - subjectText = getNotificationEmailSubject(user, post, translateFunc, a.Config().TeamSettings.SiteName, team.DisplayName+" ("+channel.DisplayName+")", useMilitaryTime) - } else { - subjectText = getNotificationEmailSubject(user, post, translateFunc, a.Config().TeamSettings.SiteName, team.DisplayName, useMilitaryTime) - } - - teamURL := a.GetSiteURL() + "/" + team.Name - var bodyText = a.getNotificationEmailBody(user, post, channel, channelName, senderName, team.Name, teamURL, emailNotificationContentsType, useMilitaryTime, translateFunc) - - a.Go(func() { - if err := a.SendMail(user.Email, html.UnescapeString(subjectText), bodyText); err != nil { - mlog.Error(fmt.Sprint("api.post.send_notifications_and_forget.send.error FIXME: NOT FOUND IN TRANSLATIONS FILE", user.Email, err)) - } - }) - - if a.Metrics != nil { - a.Metrics.IncrementPostSentEmail() - } - - return nil -} - -/** - * Computes the subject line for direct notification email messages - */ -func getDirectMessageNotificationEmailSubject(user *model.User, post *model.Post, translateFunc i18n.TranslateFunc, siteName string, senderName string, useMilitaryTime bool) string { - t := getFormattedPostTime(user, post, useMilitaryTime, translateFunc) - var subjectParameters = map[string]interface{}{ - "SiteName": siteName, - "SenderDisplayName": senderName, - "Month": t.Month, - "Day": t.Day, - "Year": t.Year, - } - return translateFunc("app.notification.subject.direct.full", subjectParameters) -} - -/** - * Computes the subject line for group, public, and private email messages - */ -func getNotificationEmailSubject(user *model.User, post *model.Post, translateFunc i18n.TranslateFunc, siteName string, teamName string, useMilitaryTime bool) string { - t := getFormattedPostTime(user, post, useMilitaryTime, translateFunc) - var subjectParameters = map[string]interface{}{ - "SiteName": siteName, - "TeamName": teamName, - "Month": t.Month, - "Day": t.Day, - "Year": t.Year, - } - return translateFunc("app.notification.subject.notification.full", subjectParameters) -} - -/** - * Computes the subject line for group email messages - */ -func getGroupMessageNotificationEmailSubject(user *model.User, post *model.Post, translateFunc i18n.TranslateFunc, siteName string, channelName string, emailNotificationContentsType string, useMilitaryTime bool) string { - t := getFormattedPostTime(user, post, useMilitaryTime, translateFunc) - var subjectText string - if emailNotificationContentsType == model.EMAIL_NOTIFICATION_CONTENTS_FULL { - var subjectParameters = map[string]interface{}{ - "SiteName": siteName, - "ChannelName": channelName, - "Month": t.Month, - "Day": t.Day, - "Year": t.Year, - } - subjectText = translateFunc("app.notification.subject.group_message.full", subjectParameters) - } else { - var subjectParameters = map[string]interface{}{ - "SiteName": siteName, - "Month": t.Month, - "Day": t.Day, - "Year": t.Year, - } - subjectText = translateFunc("app.notification.subject.group_message.generic", subjectParameters) - } - return subjectText -} - -/** - * Computes the email body for notification messages - */ -func (a *App) getNotificationEmailBody(recipient *model.User, post *model.Post, channel *model.Channel, channelName string, senderName string, teamName string, teamURL string, emailNotificationContentsType string, useMilitaryTime bool, translateFunc i18n.TranslateFunc) string { - // only include message contents in notification email if email notification contents type is set to full - var bodyPage *utils.HTMLTemplate - if emailNotificationContentsType == model.EMAIL_NOTIFICATION_CONTENTS_FULL { - bodyPage = a.NewEmailTemplate("post_body_full", recipient.Locale) - bodyPage.Props["PostMessage"] = a.GetMessageForNotification(post, translateFunc) - } else { - bodyPage = a.NewEmailTemplate("post_body_generic", recipient.Locale) - } - - bodyPage.Props["SiteURL"] = a.GetSiteURL() - if teamName != "select_team" { - bodyPage.Props["TeamLink"] = teamURL + "/pl/" + post.Id - } else { - bodyPage.Props["TeamLink"] = teamURL - } - - t := getFormattedPostTime(recipient, post, useMilitaryTime, translateFunc) - - if channel.Type == model.CHANNEL_DIRECT { - if emailNotificationContentsType == model.EMAIL_NOTIFICATION_CONTENTS_FULL { - bodyPage.Props["BodyText"] = translateFunc("app.notification.body.intro.direct.full") - bodyPage.Props["Info1"] = "" - bodyPage.Props["Info2"] = translateFunc("app.notification.body.text.direct.full", - map[string]interface{}{ - "SenderName": senderName, - "Hour": t.Hour, - "Minute": t.Minute, - "TimeZone": t.TimeZone, - "Month": t.Month, - "Day": t.Day, - }) - } else { - bodyPage.Props["BodyText"] = translateFunc("app.notification.body.intro.direct.generic", map[string]interface{}{ - "SenderName": senderName, - }) - bodyPage.Props["Info"] = translateFunc("app.notification.body.text.direct.generic", - map[string]interface{}{ - "Hour": t.Hour, - "Minute": t.Minute, - "TimeZone": t.TimeZone, - "Month": t.Month, - "Day": t.Day, - }) - } - } else if channel.Type == model.CHANNEL_GROUP { - if emailNotificationContentsType == model.EMAIL_NOTIFICATION_CONTENTS_FULL { - bodyPage.Props["BodyText"] = translateFunc("app.notification.body.intro.group_message.full") - bodyPage.Props["Info1"] = translateFunc("app.notification.body.text.group_message.full", - map[string]interface{}{ - "ChannelName": channelName, - }) - bodyPage.Props["Info2"] = translateFunc("app.notification.body.text.group_message.full2", - map[string]interface{}{ - "SenderName": senderName, - "Hour": t.Hour, - "Minute": t.Minute, - "TimeZone": t.TimeZone, - "Month": t.Month, - "Day": t.Day, - }) - } else { - bodyPage.Props["BodyText"] = translateFunc("app.notification.body.intro.group_message.generic", map[string]interface{}{ - "SenderName": senderName, - }) - bodyPage.Props["Info"] = translateFunc("app.notification.body.text.group_message.generic", - map[string]interface{}{ - "Hour": t.Hour, - "Minute": t.Minute, - "TimeZone": t.TimeZone, - "Month": t.Month, - "Day": t.Day, - }) - } - } else { - if emailNotificationContentsType == model.EMAIL_NOTIFICATION_CONTENTS_FULL { - bodyPage.Props["BodyText"] = translateFunc("app.notification.body.intro.notification.full") - bodyPage.Props["Info1"] = translateFunc("app.notification.body.text.notification.full", - map[string]interface{}{ - "ChannelName": channelName, - }) - bodyPage.Props["Info2"] = translateFunc("app.notification.body.text.notification.full2", - map[string]interface{}{ - "SenderName": senderName, - "Hour": t.Hour, - "Minute": t.Minute, - "TimeZone": t.TimeZone, - "Month": t.Month, - "Day": t.Day, - }) - } else { - bodyPage.Props["BodyText"] = translateFunc("app.notification.body.intro.notification.generic", map[string]interface{}{ - "SenderName": senderName, - }) - bodyPage.Props["Info"] = translateFunc("app.notification.body.text.notification.generic", - map[string]interface{}{ - "Hour": t.Hour, - "Minute": t.Minute, - "TimeZone": t.TimeZone, - "Month": t.Month, - "Day": t.Day, - }) - } - } - - bodyPage.Props["Button"] = translateFunc("api.templates.post_body.button") - - return bodyPage.Render() -} - -type formattedPostTime struct { - Time time.Time - Year string - Month string - Day string - Hour string - Minute string - TimeZone string -} - -func getFormattedPostTime(user *model.User, post *model.Post, useMilitaryTime bool, translateFunc i18n.TranslateFunc) formattedPostTime { - preferredTimezone := user.GetPreferredTimezone() - postTime := time.Unix(post.CreateAt/1000, 0) - zone, _ := postTime.Zone() - - localTime := postTime - if preferredTimezone != "" { - loc, _ := time.LoadLocation(preferredTimezone) - if loc != nil { - localTime = postTime.In(loc) - zone, _ = localTime.Zone() - } - } - - hour := localTime.Format("15") - period := "" - if !useMilitaryTime { - hour = localTime.Format("3") - period = " " + localTime.Format("PM") - } - - return formattedPostTime{ - Time: localTime, - Year: fmt.Sprintf("%d", localTime.Year()), - Month: translateFunc(localTime.Month().String()), - Day: fmt.Sprintf("%d", localTime.Day()), - Hour: fmt.Sprintf("%s", hour), - Minute: fmt.Sprintf("%02d"+period, localTime.Minute()), - TimeZone: zone, - } -} - -func (a *App) GetMessageForNotification(post *model.Post, translateFunc i18n.TranslateFunc) string { - if len(strings.TrimSpace(post.Message)) != 0 || len(post.FileIds) == 0 { - return post.Message - } - - // extract the filenames from their paths and determine what type of files are attached - var infos []*model.FileInfo - if result := <-a.Srv.Store.FileInfo().GetForPost(post.Id, true, true); result.Err != nil { - mlog.Warn(fmt.Sprintf("Encountered error when getting files for notification message, post_id=%v, err=%v", post.Id, result.Err), mlog.String("post_id", post.Id)) - } else { - infos = result.Data.([]*model.FileInfo) - } - - filenames := make([]string, len(infos)) - onlyImages := true - for i, info := range infos { - if escaped, err := url.QueryUnescape(filepath.Base(info.Name)); err != nil { - // this should never error since filepath was escaped using url.QueryEscape - filenames[i] = escaped - } else { - filenames[i] = info.Name - } - - onlyImages = onlyImages && info.IsImage() - } - - props := map[string]interface{}{"Filenames": strings.Join(filenames, ", ")} - - if onlyImages { - return translateFunc("api.post.get_message_for_notification.images_sent", len(filenames), props) - } else { - return translateFunc("api.post.get_message_for_notification.files_sent", len(filenames), props) - } -} - -func (a *App) sendPushNotification(post *model.Post, user *model.User, channel *model.Channel, channelName string, sender *model.User, senderName string, - explicitMention, channelWideMention bool, replyToThreadType string) *model.AppError { - cfg := a.Config() - contentsConfig := *cfg.EmailSettings.PushNotificationContents - teammateNameConfig := *cfg.TeamSettings.TeammateNameDisplay - sessions, err := a.getMobileAppSessions(user.Id) - sentBySystem := senderName == utils.T("system.message.name") - if err != nil { - return err - } - - msg := model.PushNotification{} - if badge := <-a.Srv.Store.User().GetUnreadCount(user.Id); badge.Err != nil { - msg.Badge = 1 - mlog.Error(fmt.Sprint("We could not get the unread message count for the user", user.Id, badge.Err), mlog.String("user_id", user.Id)) - } else { - msg.Badge = int(badge.Data.(int64)) - } - - msg.Category = model.CATEGORY_CAN_REPLY - msg.Version = model.PUSH_MESSAGE_V2 - msg.Type = model.PUSH_TYPE_MESSAGE - msg.TeamId = channel.TeamId - msg.ChannelId = channel.Id - msg.PostId = post.Id - msg.RootId = post.RootId - msg.SenderId = post.UserId - - if !sentBySystem { - senderName = sender.GetDisplayName(teammateNameConfig) - preference, prefError := a.GetPreferenceByCategoryAndNameForUser(user.Id, model.PREFERENCE_CATEGORY_DISPLAY_SETTINGS, "name_format") - if prefError == nil && preference.Value != teammateNameConfig { - senderName = sender.GetDisplayName(preference.Value) - } - } - - if channel.Type == model.CHANNEL_DIRECT { - channelName = fmt.Sprintf("@%v", senderName) - } - - if contentsConfig != model.GENERIC_NO_CHANNEL_NOTIFICATION || channel.Type == model.CHANNEL_DIRECT { - msg.ChannelName = channelName - } - - if ou, ok := post.Props["override_username"].(string); ok && cfg.ServiceSettings.EnablePostUsernameOverride { - msg.OverrideUsername = ou - senderName = ou - } - - if oi, ok := post.Props["override_icon_url"].(string); ok && cfg.ServiceSettings.EnablePostIconOverride { - msg.OverrideIconUrl = oi - } - - if fw, ok := post.Props["from_webhook"].(string); ok { - msg.FromWebhook = fw - } - - userLocale := utils.GetUserTranslations(user.Locale) - hasFiles := post.FileIds != nil && len(post.FileIds) > 0 - - msg.Message = a.getPushNotificationMessage(post.Message, explicitMention, channelWideMention, hasFiles, senderName, channelName, channel.Type, replyToThreadType, userLocale) - - for _, session := range sessions { - - if session.IsExpired() { - continue - } - - tmpMessage := *model.PushNotificationFromJson(strings.NewReader(msg.ToJson())) - tmpMessage.SetDeviceIdAndPlatform(session.DeviceId) - - mlog.Debug(fmt.Sprintf("Sending push notification to device %v for user %v with msg of '%v'", tmpMessage.DeviceId, user.Id, msg.Message), mlog.String("user_id", user.Id)) - - a.Go(func(session *model.Session) func() { - return func() { - a.sendToPushProxy(tmpMessage, session) - } - }(session)) - - if a.Metrics != nil { - a.Metrics.IncrementPostSentPush() - } - } - - return nil -} - -func (a *App) getPushNotificationMessage(postMessage string, explicitMention, channelWideMention, hasFiles bool, - senderName, channelName, channelType, replyToThreadType string, userLocale i18n.TranslateFunc) string { - message := "" - - contentsConfig := *a.Config().EmailSettings.PushNotificationContents - - if contentsConfig == model.FULL_NOTIFICATION { - if channelType == model.CHANNEL_DIRECT { - message = model.ClearMentionTags(postMessage) - } else { - message = "@" + senderName + ": " + model.ClearMentionTags(postMessage) - } - } else { - if channelType == model.CHANNEL_DIRECT { - message = userLocale("api.post.send_notifications_and_forget.push_message") - } else if channelWideMention { - message = "@" + senderName + userLocale("api.post.send_notification_and_forget.push_channel_mention") - } else if explicitMention { - message = "@" + senderName + userLocale("api.post.send_notifications_and_forget.push_explicit_mention") - } else if replyToThreadType == THREAD_ROOT { - message = "@" + senderName + userLocale("api.post.send_notification_and_forget.push_comment_on_post") - } else if replyToThreadType == THREAD_ANY { - message = "@" + senderName + userLocale("api.post.send_notification_and_forget.push_comment_on_thread") - } else { - message = "@" + senderName + userLocale("api.post.send_notifications_and_forget.push_general_message") - } - } - - // If the post only has images then push an appropriate message - if len(postMessage) == 0 && hasFiles { - if channelType == model.CHANNEL_DIRECT { - message = strings.Trim(userLocale("api.post.send_notifications_and_forget.push_image_only"), " ") - } else { - message = "@" + senderName + userLocale("api.post.send_notifications_and_forget.push_image_only") - } - } - - return message -} - -func (a *App) ClearPushNotification(userId string, channelId string) { - a.Go(func() { - // Sleep is to allow the read replicas a chance to fully sync - // the unread count for sending an accurate count. - // Delaying a little doesn't hurt anything and is cheaper than - // attempting to read from master. - time.Sleep(time.Second * 5) - - sessions, err := a.getMobileAppSessions(userId) - if err != nil { - mlog.Error(err.Error()) - return - } - - msg := model.PushNotification{} - msg.Type = model.PUSH_TYPE_CLEAR - msg.ChannelId = channelId - msg.ContentAvailable = 0 - if badge := <-a.Srv.Store.User().GetUnreadCount(userId); badge.Err != nil { - msg.Badge = 0 - mlog.Error(fmt.Sprint("We could not get the unread message count for the user", userId, badge.Err), mlog.String("user_id", userId)) - } else { - msg.Badge = int(badge.Data.(int64)) - } - - mlog.Debug(fmt.Sprintf("Clearing push notification to %v with channel_id %v", msg.DeviceId, msg.ChannelId)) - - for _, session := range sessions { - tmpMessage := *model.PushNotificationFromJson(strings.NewReader(msg.ToJson())) - tmpMessage.SetDeviceIdAndPlatform(session.DeviceId) - a.Go(func() { - a.sendToPushProxy(tmpMessage, session) - }) - } - }) -} - -func (a *App) sendToPushProxy(msg model.PushNotification, session *model.Session) { - msg.ServerId = a.DiagnosticId() - - request, _ := http.NewRequest("POST", strings.TrimRight(*a.Config().EmailSettings.PushNotificationServer, "/")+model.API_URL_SUFFIX_V1+"/send_push", strings.NewReader(msg.ToJson())) - - if resp, err := a.HTTPClient(true).Do(request); err != nil { - mlog.Error(fmt.Sprintf("Device push reported as error for UserId=%v SessionId=%v message=%v", session.UserId, session.Id, err.Error()), mlog.String("user_id", session.UserId)) - } else { - pushResponse := model.PushResponseFromJson(resp.Body) - if resp.Body != nil { - consumeAndClose(resp) - } - - if pushResponse[model.PUSH_STATUS] == model.PUSH_STATUS_REMOVE { - mlog.Info(fmt.Sprintf("Device was reported as removed for UserId=%v SessionId=%v removing push for this session", session.UserId, session.Id), mlog.String("user_id", session.UserId)) - a.AttachDeviceId(session.Id, "", session.ExpiresAt) - a.ClearSessionCacheForUser(session.UserId) - } - - if pushResponse[model.PUSH_STATUS] == model.PUSH_STATUS_FAIL { - mlog.Error(fmt.Sprintf("Device push reported as error for UserId=%v SessionId=%v message=%v", session.UserId, session.Id, pushResponse[model.PUSH_STATUS_ERROR_MSG]), mlog.String("user_id", session.UserId)) - } - } -} - -func (a *App) getMobileAppSessions(userId string) ([]*model.Session, *model.AppError) { - if result := <-a.Srv.Store.Session().GetSessionsWithActiveDeviceIds(userId); result.Err != nil { - return nil, result.Err - } else { - return result.Data.([]*model.Session), nil - } -} - func (a *App) sendOutOfChannelMentions(sender *model.User, post *model.Post, users []*model.User) *model.AppError { if len(users) == 0 { return nil @@ -1156,66 +623,3 @@ func (a *App) GetMentionKeywordsInChannel(profiles map[string]*model.User, lookF return keywords } - -func ShouldSendPushNotification(user *model.User, channelNotifyProps model.StringMap, wasMentioned bool, status *model.Status, post *model.Post) bool { - return DoesNotifyPropsAllowPushNotification(user, channelNotifyProps, post, wasMentioned) && - DoesStatusAllowPushNotification(user.NotifyProps, status, post.ChannelId) -} - -func DoesNotifyPropsAllowPushNotification(user *model.User, channelNotifyProps model.StringMap, post *model.Post, wasMentioned bool) bool { - userNotifyProps := user.NotifyProps - userNotify := userNotifyProps[model.PUSH_NOTIFY_PROP] - channelNotify, ok := channelNotifyProps[model.PUSH_NOTIFY_PROP] - - // If the channel is muted do not send push notifications - if channelMuted, ok := channelNotifyProps[model.MARK_UNREAD_NOTIFY_PROP]; ok { - if channelMuted == model.CHANNEL_MARK_UNREAD_MENTION { - return false - } - } - - if post.IsSystemMessage() { - return false - } - - if channelNotify == model.USER_NOTIFY_NONE { - return false - } - - if channelNotify == model.CHANNEL_NOTIFY_MENTION && !wasMentioned { - return false - } - - if userNotify == model.USER_NOTIFY_MENTION && (!ok || channelNotify == model.CHANNEL_NOTIFY_DEFAULT) && !wasMentioned { - return false - } - - if (userNotify == model.USER_NOTIFY_ALL || channelNotify == model.CHANNEL_NOTIFY_ALL) && - (post.UserId != user.Id || post.Props["from_webhook"] == "true") { - return true - } - - if userNotify == model.USER_NOTIFY_NONE && - (!ok || channelNotify == model.CHANNEL_NOTIFY_DEFAULT) { - return false - } - - return true -} - -func DoesStatusAllowPushNotification(userNotifyProps model.StringMap, status *model.Status, channelId string) bool { - // If User status is DND or OOO return false right away - if status.Status == model.STATUS_DND || status.Status == model.STATUS_OUT_OF_OFFICE { - return false - } - - if pushStatus, ok := userNotifyProps["push_status"]; (pushStatus == model.STATUS_ONLINE || !ok) && (status.ActiveChannel != channelId || model.GetMillis()-status.LastActivityAt > model.STATUS_CHANNEL_TIMEOUT) { - return true - } else if pushStatus == model.STATUS_AWAY && (status.Status == model.STATUS_AWAY || status.Status == model.STATUS_OFFLINE) { - return true - } else if pushStatus == model.STATUS_OFFLINE && status.Status == model.STATUS_OFFLINE { - return true - } - - return false -} diff --git a/app/notification_email.go b/app/notification_email.go new file mode 100644 index 000000000..cccd02eba --- /dev/null +++ b/app/notification_email.go @@ -0,0 +1,348 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package app + +import ( + "fmt" + "html" + "net/url" + "path/filepath" + "strings" + "time" + + "github.com/mattermost/mattermost-server/mlog" + "github.com/mattermost/mattermost-server/model" + "github.com/mattermost/mattermost-server/utils" + "github.com/nicksnyder/go-i18n/i18n" +) + +func (a *App) sendNotificationEmail(post *model.Post, user *model.User, channel *model.Channel, team *model.Team, channelName string, senderName string, sender *model.User) *model.AppError { + if channel.IsGroupOrDirect() { + if result := <-a.Srv.Store.Team().GetTeamsByUserId(user.Id); result.Err != nil { + return result.Err + } else { + // if the recipient isn't in the current user's team, just pick one + teams := result.Data.([]*model.Team) + found := false + + for i := range teams { + if teams[i].Id == team.Id { + found = true + break + } + } + + if !found && len(teams) > 0 { + team = teams[0] + } else { + // in case the user hasn't joined any teams we send them to the select_team page + team = &model.Team{Name: "select_team", DisplayName: a.Config().TeamSettings.SiteName} + } + } + } + if *a.Config().EmailSettings.EnableEmailBatching { + var sendBatched bool + if result := <-a.Srv.Store.Preference().Get(user.Id, model.PREFERENCE_CATEGORY_NOTIFICATIONS, model.PREFERENCE_NAME_EMAIL_INTERVAL); result.Err != nil { + // if the call fails, assume that the interval has not been explicitly set and batch the notifications + sendBatched = true + } else { + // if the user has chosen to receive notifications immediately, don't batch them + sendBatched = result.Data.(model.Preference).Value != model.PREFERENCE_EMAIL_INTERVAL_NO_BATCHING_SECONDS + } + + if sendBatched { + if err := a.AddNotificationEmailToBatch(user, post, team); err == nil { + return nil + } + } + + // fall back to sending a single email if we can't batch it for some reason + } + + var useMilitaryTime bool + translateFunc := utils.GetUserTranslations(user.Locale) + if result := <-a.Srv.Store.Preference().Get(user.Id, model.PREFERENCE_CATEGORY_DISPLAY_SETTINGS, "use_military_time"); result.Err != nil { + useMilitaryTime = true + } else { + useMilitaryTime = result.Data.(model.Preference).Value == "true" + } + + emailNotificationContentsType := model.EMAIL_NOTIFICATION_CONTENTS_FULL + if license := a.License(); license != nil && *license.Features.EmailNotificationContents { + emailNotificationContentsType = *a.Config().EmailSettings.EmailNotificationContentsType + } + + var subjectText string + if channel.Type == model.CHANNEL_DIRECT { + subjectText = getDirectMessageNotificationEmailSubject(user, post, translateFunc, a.Config().TeamSettings.SiteName, senderName, useMilitaryTime) + } else if channel.Type == model.CHANNEL_GROUP { + subjectText = getGroupMessageNotificationEmailSubject(user, post, translateFunc, a.Config().TeamSettings.SiteName, channelName, emailNotificationContentsType, useMilitaryTime) + } else if *a.Config().EmailSettings.UseChannelInEmailNotifications { + subjectText = getNotificationEmailSubject(user, post, translateFunc, a.Config().TeamSettings.SiteName, team.DisplayName+" ("+channel.DisplayName+")", useMilitaryTime) + } else { + subjectText = getNotificationEmailSubject(user, post, translateFunc, a.Config().TeamSettings.SiteName, team.DisplayName, useMilitaryTime) + } + + teamURL := a.GetSiteURL() + "/" + team.Name + var bodyText = a.getNotificationEmailBody(user, post, channel, channelName, senderName, team.Name, teamURL, emailNotificationContentsType, useMilitaryTime, translateFunc) + + a.Go(func() { + if err := a.SendMail(user.Email, html.UnescapeString(subjectText), bodyText); err != nil { + mlog.Error(fmt.Sprint("api.post.send_notifications_and_forget.send.error FIXME: NOT FOUND IN TRANSLATIONS FILE", user.Email, err)) + } + }) + + if a.Metrics != nil { + a.Metrics.IncrementPostSentEmail() + } + + return nil +} + +/** + * Computes the subject line for direct notification email messages + */ +func getDirectMessageNotificationEmailSubject(user *model.User, post *model.Post, translateFunc i18n.TranslateFunc, siteName string, senderName string, useMilitaryTime bool) string { + t := getFormattedPostTime(user, post, useMilitaryTime, translateFunc) + var subjectParameters = map[string]interface{}{ + "SiteName": siteName, + "SenderDisplayName": senderName, + "Month": t.Month, + "Day": t.Day, + "Year": t.Year, + } + return translateFunc("app.notification.subject.direct.full", subjectParameters) +} + +/** + * Computes the subject line for group, public, and private email messages + */ +func getNotificationEmailSubject(user *model.User, post *model.Post, translateFunc i18n.TranslateFunc, siteName string, teamName string, useMilitaryTime bool) string { + t := getFormattedPostTime(user, post, useMilitaryTime, translateFunc) + var subjectParameters = map[string]interface{}{ + "SiteName": siteName, + "TeamName": teamName, + "Month": t.Month, + "Day": t.Day, + "Year": t.Year, + } + return translateFunc("app.notification.subject.notification.full", subjectParameters) +} + +/** + * Computes the subject line for group email messages + */ +func getGroupMessageNotificationEmailSubject(user *model.User, post *model.Post, translateFunc i18n.TranslateFunc, siteName string, channelName string, emailNotificationContentsType string, useMilitaryTime bool) string { + t := getFormattedPostTime(user, post, useMilitaryTime, translateFunc) + var subjectText string + if emailNotificationContentsType == model.EMAIL_NOTIFICATION_CONTENTS_FULL { + var subjectParameters = map[string]interface{}{ + "SiteName": siteName, + "ChannelName": channelName, + "Month": t.Month, + "Day": t.Day, + "Year": t.Year, + } + subjectText = translateFunc("app.notification.subject.group_message.full", subjectParameters) + } else { + var subjectParameters = map[string]interface{}{ + "SiteName": siteName, + "Month": t.Month, + "Day": t.Day, + "Year": t.Year, + } + subjectText = translateFunc("app.notification.subject.group_message.generic", subjectParameters) + } + return subjectText +} + +/** + * Computes the email body for notification messages + */ +func (a *App) getNotificationEmailBody(recipient *model.User, post *model.Post, channel *model.Channel, channelName string, senderName string, teamName string, teamURL string, emailNotificationContentsType string, useMilitaryTime bool, translateFunc i18n.TranslateFunc) string { + // only include message contents in notification email if email notification contents type is set to full + var bodyPage *utils.HTMLTemplate + if emailNotificationContentsType == model.EMAIL_NOTIFICATION_CONTENTS_FULL { + bodyPage = a.NewEmailTemplate("post_body_full", recipient.Locale) + bodyPage.Props["PostMessage"] = a.GetMessageForNotification(post, translateFunc) + } else { + bodyPage = a.NewEmailTemplate("post_body_generic", recipient.Locale) + } + + bodyPage.Props["SiteURL"] = a.GetSiteURL() + if teamName != "select_team" { + bodyPage.Props["TeamLink"] = teamURL + "/pl/" + post.Id + } else { + bodyPage.Props["TeamLink"] = teamURL + } + + t := getFormattedPostTime(recipient, post, useMilitaryTime, translateFunc) + + if channel.Type == model.CHANNEL_DIRECT { + if emailNotificationContentsType == model.EMAIL_NOTIFICATION_CONTENTS_FULL { + bodyPage.Props["BodyText"] = translateFunc("app.notification.body.intro.direct.full") + bodyPage.Props["Info1"] = "" + bodyPage.Props["Info2"] = translateFunc("app.notification.body.text.direct.full", + map[string]interface{}{ + "SenderName": senderName, + "Hour": t.Hour, + "Minute": t.Minute, + "TimeZone": t.TimeZone, + "Month": t.Month, + "Day": t.Day, + }) + } else { + bodyPage.Props["BodyText"] = translateFunc("app.notification.body.intro.direct.generic", map[string]interface{}{ + "SenderName": senderName, + }) + bodyPage.Props["Info"] = translateFunc("app.notification.body.text.direct.generic", + map[string]interface{}{ + "Hour": t.Hour, + "Minute": t.Minute, + "TimeZone": t.TimeZone, + "Month": t.Month, + "Day": t.Day, + }) + } + } else if channel.Type == model.CHANNEL_GROUP { + if emailNotificationContentsType == model.EMAIL_NOTIFICATION_CONTENTS_FULL { + bodyPage.Props["BodyText"] = translateFunc("app.notification.body.intro.group_message.full") + bodyPage.Props["Info1"] = translateFunc("app.notification.body.text.group_message.full", + map[string]interface{}{ + "ChannelName": channelName, + }) + bodyPage.Props["Info2"] = translateFunc("app.notification.body.text.group_message.full2", + map[string]interface{}{ + "SenderName": senderName, + "Hour": t.Hour, + "Minute": t.Minute, + "TimeZone": t.TimeZone, + "Month": t.Month, + "Day": t.Day, + }) + } else { + bodyPage.Props["BodyText"] = translateFunc("app.notification.body.intro.group_message.generic", map[string]interface{}{ + "SenderName": senderName, + }) + bodyPage.Props["Info"] = translateFunc("app.notification.body.text.group_message.generic", + map[string]interface{}{ + "Hour": t.Hour, + "Minute": t.Minute, + "TimeZone": t.TimeZone, + "Month": t.Month, + "Day": t.Day, + }) + } + } else { + if emailNotificationContentsType == model.EMAIL_NOTIFICATION_CONTENTS_FULL { + bodyPage.Props["BodyText"] = translateFunc("app.notification.body.intro.notification.full") + bodyPage.Props["Info1"] = translateFunc("app.notification.body.text.notification.full", + map[string]interface{}{ + "ChannelName": channelName, + }) + bodyPage.Props["Info2"] = translateFunc("app.notification.body.text.notification.full2", + map[string]interface{}{ + "SenderName": senderName, + "Hour": t.Hour, + "Minute": t.Minute, + "TimeZone": t.TimeZone, + "Month": t.Month, + "Day": t.Day, + }) + } else { + bodyPage.Props["BodyText"] = translateFunc("app.notification.body.intro.notification.generic", map[string]interface{}{ + "SenderName": senderName, + }) + bodyPage.Props["Info"] = translateFunc("app.notification.body.text.notification.generic", + map[string]interface{}{ + "Hour": t.Hour, + "Minute": t.Minute, + "TimeZone": t.TimeZone, + "Month": t.Month, + "Day": t.Day, + }) + } + } + + bodyPage.Props["Button"] = translateFunc("api.templates.post_body.button") + + return bodyPage.Render() +} + +type formattedPostTime struct { + Time time.Time + Year string + Month string + Day string + Hour string + Minute string + TimeZone string +} + +func getFormattedPostTime(user *model.User, post *model.Post, useMilitaryTime bool, translateFunc i18n.TranslateFunc) formattedPostTime { + preferredTimezone := user.GetPreferredTimezone() + postTime := time.Unix(post.CreateAt/1000, 0) + zone, _ := postTime.Zone() + + localTime := postTime + if preferredTimezone != "" { + loc, _ := time.LoadLocation(preferredTimezone) + if loc != nil { + localTime = postTime.In(loc) + zone, _ = localTime.Zone() + } + } + + hour := localTime.Format("15") + period := "" + if !useMilitaryTime { + hour = localTime.Format("3") + period = " " + localTime.Format("PM") + } + + return formattedPostTime{ + Time: localTime, + Year: fmt.Sprintf("%d", localTime.Year()), + Month: translateFunc(localTime.Month().String()), + Day: fmt.Sprintf("%d", localTime.Day()), + Hour: fmt.Sprintf("%s", hour), + Minute: fmt.Sprintf("%02d"+period, localTime.Minute()), + TimeZone: zone, + } +} + +func (a *App) GetMessageForNotification(post *model.Post, translateFunc i18n.TranslateFunc) string { + if len(strings.TrimSpace(post.Message)) != 0 || len(post.FileIds) == 0 { + return post.Message + } + + // extract the filenames from their paths and determine what type of files are attached + var infos []*model.FileInfo + if result := <-a.Srv.Store.FileInfo().GetForPost(post.Id, true, true); result.Err != nil { + mlog.Warn(fmt.Sprintf("Encountered error when getting files for notification message, post_id=%v, err=%v", post.Id, result.Err), mlog.String("post_id", post.Id)) + } else { + infos = result.Data.([]*model.FileInfo) + } + + filenames := make([]string, len(infos)) + onlyImages := true + for i, info := range infos { + if escaped, err := url.QueryUnescape(filepath.Base(info.Name)); err != nil { + // this should never error since filepath was escaped using url.QueryEscape + filenames[i] = escaped + } else { + filenames[i] = info.Name + } + + onlyImages = onlyImages && info.IsImage() + } + + props := map[string]interface{}{"Filenames": strings.Join(filenames, ", ")} + + if onlyImages { + return translateFunc("api.post.get_message_for_notification.images_sent", len(filenames), props) + } else { + return translateFunc("api.post.get_message_for_notification.files_sent", len(filenames), props) + } +} diff --git a/app/notification_email_test.go b/app/notification_email_test.go new file mode 100644 index 000000000..d45ca424a --- /dev/null +++ b/app/notification_email_test.go @@ -0,0 +1,500 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package app + +import ( + "fmt" + "regexp" + "strings" + "testing" + "time" + + "github.com/mattermost/mattermost-server/model" + "github.com/mattermost/mattermost-server/utils" +) + +func TestGetDirectMessageNotificationEmailSubject(t *testing.T) { + th := Setup() + defer th.TearDown() + + expectedPrefix := "[http://localhost:8065] New Direct Message from @sender on" + user := &model.User{} + post := &model.Post{ + CreateAt: 1501804801000, + } + translateFunc := utils.GetUserTranslations("en") + subject := getDirectMessageNotificationEmailSubject(user, post, translateFunc, "http://localhost:8065", "sender", true) + if !strings.HasPrefix(subject, expectedPrefix) { + t.Fatal("Expected subject line prefix '" + expectedPrefix + "', got " + subject) + } +} + +func TestGetGroupMessageNotificationEmailSubjectFull(t *testing.T) { + th := Setup() + defer th.TearDown() + + expectedPrefix := "[http://localhost:8065] New Group Message in sender on" + user := &model.User{} + post := &model.Post{ + CreateAt: 1501804801000, + } + translateFunc := utils.GetUserTranslations("en") + emailNotificationContentsType := model.EMAIL_NOTIFICATION_CONTENTS_FULL + subject := getGroupMessageNotificationEmailSubject(user, post, translateFunc, "http://localhost:8065", "sender", emailNotificationContentsType, true) + if !strings.HasPrefix(subject, expectedPrefix) { + t.Fatal("Expected subject line prefix '" + expectedPrefix + "', got " + subject) + } +} + +func TestGetGroupMessageNotificationEmailSubjectGeneric(t *testing.T) { + th := Setup() + defer th.TearDown() + + expectedPrefix := "[http://localhost:8065] New Group Message on" + user := &model.User{} + post := &model.Post{ + CreateAt: 1501804801000, + } + translateFunc := utils.GetUserTranslations("en") + emailNotificationContentsType := model.EMAIL_NOTIFICATION_CONTENTS_GENERIC + subject := getGroupMessageNotificationEmailSubject(user, post, translateFunc, "http://localhost:8065", "sender", emailNotificationContentsType, true) + if !strings.HasPrefix(subject, expectedPrefix) { + t.Fatal("Expected subject line prefix '" + expectedPrefix + "', got " + subject) + } +} + +func TestGetNotificationEmailSubject(t *testing.T) { + th := Setup() + defer th.TearDown() + + expectedPrefix := "[http://localhost:8065] Notification in team on" + user := &model.User{} + post := &model.Post{ + CreateAt: 1501804801000, + } + translateFunc := utils.GetUserTranslations("en") + subject := getNotificationEmailSubject(user, post, translateFunc, "http://localhost:8065", "team", true) + if !strings.HasPrefix(subject, expectedPrefix) { + t.Fatal("Expected subject line prefix '" + expectedPrefix + "', got " + subject) + } +} + +func TestGetNotificationEmailBodyFullNotificationPublicChannel(t *testing.T) { + th := Setup() + defer th.TearDown() + + recipient := &model.User{} + post := &model.Post{ + Message: "This is the message", + } + channel := &model.Channel{ + DisplayName: "ChannelName", + Type: model.CHANNEL_OPEN, + } + channelName := "ChannelName" + senderName := "sender" + teamName := "team" + teamURL := "http://localhost:8065/" + teamName + emailNotificationContentsType := model.EMAIL_NOTIFICATION_CONTENTS_FULL + translateFunc := utils.GetUserTranslations("en") + + body := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc) + if !strings.Contains(body, "You have a new notification.") { + t.Fatal("Expected email text 'You have a new notification. Got " + body) + } + if !strings.Contains(body, "Channel: "+channel.DisplayName) { + t.Fatal("Expected email text 'Channel: " + channel.DisplayName + "'. Got " + body) + } + if !strings.Contains(body, "@"+senderName+" - ") { + t.Fatal("Expected email text '@" + senderName + " - '. Got " + body) + } + if !strings.Contains(body, post.Message) { + t.Fatal("Expected email text '" + post.Message + "'. Got " + body) + } + if !strings.Contains(body, teamURL) { + t.Fatal("Expected email text '" + teamURL + "'. Got " + body) + } +} + +func TestGetNotificationEmailBodyFullNotificationGroupChannel(t *testing.T) { + th := Setup() + defer th.TearDown() + + recipient := &model.User{} + post := &model.Post{ + Message: "This is the message", + } + channel := &model.Channel{ + DisplayName: "ChannelName", + Type: model.CHANNEL_GROUP, + } + channelName := "ChannelName" + senderName := "sender" + teamName := "team" + teamURL := "http://localhost:8065/" + teamName + emailNotificationContentsType := model.EMAIL_NOTIFICATION_CONTENTS_FULL + translateFunc := utils.GetUserTranslations("en") + + body := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc) + if !strings.Contains(body, "You have a new Group Message.") { + t.Fatal("Expected email text 'You have a new Group Message. Got " + body) + } + if !strings.Contains(body, "Channel: ChannelName") { + t.Fatal("Expected email text 'Channel: ChannelName'. Got " + body) + } + if !strings.Contains(body, "@"+senderName+" - ") { + t.Fatal("Expected email text '@" + senderName + " - '. Got " + body) + } + if !strings.Contains(body, post.Message) { + t.Fatal("Expected email text '" + post.Message + "'. Got " + body) + } + if !strings.Contains(body, teamURL) { + t.Fatal("Expected email text '" + teamURL + "'. Got " + body) + } +} + +func TestGetNotificationEmailBodyFullNotificationPrivateChannel(t *testing.T) { + th := Setup() + defer th.TearDown() + + recipient := &model.User{} + post := &model.Post{ + Message: "This is the message", + } + channel := &model.Channel{ + DisplayName: "ChannelName", + Type: model.CHANNEL_PRIVATE, + } + channelName := "ChannelName" + senderName := "sender" + teamName := "team" + teamURL := "http://localhost:8065/" + teamName + emailNotificationContentsType := model.EMAIL_NOTIFICATION_CONTENTS_FULL + translateFunc := utils.GetUserTranslations("en") + + body := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc) + if !strings.Contains(body, "You have a new notification.") { + t.Fatal("Expected email text 'You have a new notification. Got " + body) + } + if !strings.Contains(body, "Channel: "+channel.DisplayName) { + t.Fatal("Expected email text 'Channel: " + channel.DisplayName + "'. Got " + body) + } + if !strings.Contains(body, "@"+senderName+" - ") { + t.Fatal("Expected email text '@" + senderName + " - '. Got " + body) + } + if !strings.Contains(body, post.Message) { + t.Fatal("Expected email text '" + post.Message + "'. Got " + body) + } + if !strings.Contains(body, teamURL) { + t.Fatal("Expected email text '" + teamURL + "'. Got " + body) + } +} + +func TestGetNotificationEmailBodyFullNotificationDirectChannel(t *testing.T) { + th := Setup() + defer th.TearDown() + + recipient := &model.User{} + post := &model.Post{ + Message: "This is the message", + } + channel := &model.Channel{ + DisplayName: "ChannelName", + Type: model.CHANNEL_DIRECT, + } + channelName := "ChannelName" + senderName := "sender" + teamName := "team" + teamURL := "http://localhost:8065/" + teamName + emailNotificationContentsType := model.EMAIL_NOTIFICATION_CONTENTS_FULL + translateFunc := utils.GetUserTranslations("en") + + body := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc) + if !strings.Contains(body, "You have a new Direct Message.") { + t.Fatal("Expected email text 'You have a new Direct Message. Got " + body) + } + if !strings.Contains(body, "@"+senderName+" - ") { + t.Fatal("Expected email text '@" + senderName + " - '. Got " + body) + } + if !strings.Contains(body, post.Message) { + t.Fatal("Expected email text '" + post.Message + "'. Got " + body) + } + if !strings.Contains(body, teamURL) { + t.Fatal("Expected email text '" + teamURL + "'. Got " + body) + } +} + +func TestGetNotificationEmailBodyFullNotificationLocaleTimeWithTimezone(t *testing.T) { + th := Setup() + defer th.TearDown() + + recipient := &model.User{ + Timezone: model.DefaultUserTimezone(), + } + recipient.Timezone["automaticTimezone"] = "America/New_York" + post := &model.Post{ + CreateAt: 1524663790000, + Message: "This is the message", + } + channel := &model.Channel{ + DisplayName: "ChannelName", + Type: model.CHANNEL_DIRECT, + } + channelName := "ChannelName" + senderName := "sender" + teamName := "team" + teamURL := "http://localhost:8065/" + teamName + emailNotificationContentsType := model.EMAIL_NOTIFICATION_CONTENTS_FULL + translateFunc := utils.GetUserTranslations("en") + + body := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, false, translateFunc) + r, _ := regexp.Compile("E([S|D]+)T") + zone := r.FindString(body) + if !strings.Contains(body, "sender - 9:43 AM "+zone+", April 25") { + t.Fatal("Expected email text 'sender - 9:43 AM " + zone + ", April 25'. Got " + body) + } +} + +func TestGetNotificationEmailBodyFullNotificationLocaleTimeNoTimezone(t *testing.T) { + th := Setup() + defer th.TearDown() + + recipient := &model.User{ + Timezone: model.DefaultUserTimezone(), + } + post := &model.Post{ + CreateAt: 1524681000000, + Message: "This is the message", + } + channel := &model.Channel{ + DisplayName: "ChannelName", + Type: model.CHANNEL_DIRECT, + } + channelName := "ChannelName" + senderName := "sender" + teamName := "team" + teamURL := "http://localhost:8065/" + teamName + emailNotificationContentsType := model.EMAIL_NOTIFICATION_CONTENTS_FULL + translateFunc := utils.GetUserTranslations("en") + + tm := time.Unix(post.CreateAt/1000, 0) + zone, _ := tm.Zone() + + formattedTime := formattedPostTime{ + Time: tm, + Year: fmt.Sprintf("%d", tm.Year()), + Month: translateFunc(tm.Month().String()), + Day: fmt.Sprintf("%d", tm.Day()), + Hour: fmt.Sprintf("%02d", tm.Hour()), + Minute: fmt.Sprintf("%02d", tm.Minute()), + TimeZone: zone, + } + + body := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc) + postTimeLine := fmt.Sprintf("sender - %s:%s %s, %s %s", formattedTime.Hour, formattedTime.Minute, formattedTime.TimeZone, formattedTime.Month, formattedTime.Day) + if !strings.Contains(body, postTimeLine) { + t.Fatal("Expected email text '" + postTimeLine + " '. Got " + body) + } +} + +func TestGetNotificationEmailBodyFullNotificationLocaleTime12Hour(t *testing.T) { + th := Setup() + defer th.TearDown() + + recipient := &model.User{ + Timezone: model.DefaultUserTimezone(), + } + recipient.Timezone["automaticTimezone"] = "America/New_York" + post := &model.Post{ + CreateAt: 1524681000000, // 1524681000 // 1524681000000 + Message: "This is the message", + } + channel := &model.Channel{ + DisplayName: "ChannelName", + Type: model.CHANNEL_DIRECT, + } + channelName := "ChannelName" + senderName := "sender" + teamName := "team" + teamURL := "http://localhost:8065/" + teamName + emailNotificationContentsType := model.EMAIL_NOTIFICATION_CONTENTS_FULL + translateFunc := utils.GetUserTranslations("en") + + body := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, false, translateFunc) + if !strings.Contains(body, "sender - 2:30 PM") { + t.Fatal("Expected email text 'sender - 2:30 PM'. Got " + body) + } + if !strings.Contains(body, "April 25") { + t.Fatal("Expected email text 'April 25'. Got " + body) + } +} + +func TestGetNotificationEmailBodyFullNotificationLocaleTime24Hour(t *testing.T) { + th := Setup() + defer th.TearDown() + + recipient := &model.User{ + Timezone: model.DefaultUserTimezone(), + } + recipient.Timezone["automaticTimezone"] = "America/New_York" + post := &model.Post{ + CreateAt: 1524681000000, + Message: "This is the message", + } + channel := &model.Channel{ + DisplayName: "ChannelName", + Type: model.CHANNEL_DIRECT, + } + channelName := "ChannelName" + senderName := "sender" + teamName := "team" + teamURL := "http://localhost:8065/" + teamName + emailNotificationContentsType := model.EMAIL_NOTIFICATION_CONTENTS_FULL + translateFunc := utils.GetUserTranslations("en") + + body := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc) + if !strings.Contains(body, "sender - 14:30") { + t.Fatal("Expected email text 'sender - 14:30'. Got " + body) + } + if !strings.Contains(body, "April 25") { + t.Fatal("Expected email text 'April 25'. Got " + body) + } +} + +// from here +func TestGetNotificationEmailBodyGenericNotificationPublicChannel(t *testing.T) { + th := Setup() + defer th.TearDown() + + recipient := &model.User{} + post := &model.Post{ + Message: "This is the message", + } + channel := &model.Channel{ + DisplayName: "ChannelName", + Type: model.CHANNEL_OPEN, + } + channelName := "ChannelName" + senderName := "sender" + teamName := "team" + teamURL := "http://localhost:8065/" + teamName + emailNotificationContentsType := model.EMAIL_NOTIFICATION_CONTENTS_GENERIC + translateFunc := utils.GetUserTranslations("en") + + body := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc) + if !strings.Contains(body, "You have a new notification from @"+senderName) { + t.Fatal("Expected email text 'You have a new notification from @" + senderName + "'. Got " + body) + } + if strings.Contains(body, "Channel: "+channel.DisplayName) { + t.Fatal("Did not expect email text 'Channel: " + channel.DisplayName + "'. Got " + body) + } + if strings.Contains(body, post.Message) { + t.Fatal("Did not expect email text '" + post.Message + "'. Got " + body) + } + if !strings.Contains(body, teamURL) { + t.Fatal("Expected email text '" + teamURL + "'. Got " + body) + } +} + +func TestGetNotificationEmailBodyGenericNotificationGroupChannel(t *testing.T) { + th := Setup() + defer th.TearDown() + + recipient := &model.User{} + post := &model.Post{ + Message: "This is the message", + } + channel := &model.Channel{ + DisplayName: "ChannelName", + Type: model.CHANNEL_GROUP, + } + channelName := "ChannelName" + senderName := "sender" + teamName := "team" + teamURL := "http://localhost:8065/" + teamName + emailNotificationContentsType := model.EMAIL_NOTIFICATION_CONTENTS_GENERIC + translateFunc := utils.GetUserTranslations("en") + + body := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc) + if !strings.Contains(body, "You have a new Group Message from @"+senderName) { + t.Fatal("Expected email text 'You have a new Group Message from @" + senderName + "'. Got " + body) + } + if strings.Contains(body, "CHANNEL: "+channel.DisplayName) { + t.Fatal("Did not expect email text 'CHANNEL: " + channel.DisplayName + "'. Got " + body) + } + if strings.Contains(body, post.Message) { + t.Fatal("Did not expect email text '" + post.Message + "'. Got " + body) + } + if !strings.Contains(body, teamURL) { + t.Fatal("Expected email text '" + teamURL + "'. Got " + body) + } +} + +func TestGetNotificationEmailBodyGenericNotificationPrivateChannel(t *testing.T) { + th := Setup() + defer th.TearDown() + + recipient := &model.User{} + post := &model.Post{ + Message: "This is the message", + } + channel := &model.Channel{ + DisplayName: "ChannelName", + Type: model.CHANNEL_PRIVATE, + } + channelName := "ChannelName" + senderName := "sender" + teamName := "team" + teamURL := "http://localhost:8065/" + teamName + emailNotificationContentsType := model.EMAIL_NOTIFICATION_CONTENTS_GENERIC + translateFunc := utils.GetUserTranslations("en") + + body := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc) + if !strings.Contains(body, "You have a new notification from @"+senderName) { + t.Fatal("Expected email text 'You have a new notification from @" + senderName + "'. Got " + body) + } + if strings.Contains(body, "CHANNEL: "+channel.DisplayName) { + t.Fatal("Did not expect email text 'CHANNEL: " + channel.DisplayName + "'. Got " + body) + } + if strings.Contains(body, post.Message) { + t.Fatal("Did not expect email text '" + post.Message + "'. Got " + body) + } + if !strings.Contains(body, teamURL) { + t.Fatal("Expected email text '" + teamURL + "'. Got " + body) + } +} + +func TestGetNotificationEmailBodyGenericNotificationDirectChannel(t *testing.T) { + th := Setup() + defer th.TearDown() + + recipient := &model.User{} + post := &model.Post{ + Message: "This is the message", + } + channel := &model.Channel{ + DisplayName: "ChannelName", + Type: model.CHANNEL_DIRECT, + } + channelName := "ChannelName" + senderName := "sender" + teamName := "team" + teamURL := "http://localhost:8065/" + teamName + emailNotificationContentsType := model.EMAIL_NOTIFICATION_CONTENTS_GENERIC + translateFunc := utils.GetUserTranslations("en") + + body := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc) + if !strings.Contains(body, "You have a new Direct Message from @"+senderName) { + t.Fatal("Expected email text 'You have a new Direct Message from @" + senderName + "'. Got " + body) + } + if strings.Contains(body, "CHANNEL: "+channel.DisplayName) { + t.Fatal("Did not expect email text 'CHANNEL: " + channel.DisplayName + "'. Got " + body) + } + if strings.Contains(body, post.Message) { + t.Fatal("Did not expect email text '" + post.Message + "'. Got " + body) + } + if !strings.Contains(body, teamURL) { + t.Fatal("Expected email text '" + teamURL + "'. Got " + body) + } +} diff --git a/app/notification_push.go b/app/notification_push.go new file mode 100644 index 000000000..12d9f5258 --- /dev/null +++ b/app/notification_push.go @@ -0,0 +1,276 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package app + +import ( + "fmt" + "net/http" + "strings" + "time" + + "github.com/mattermost/mattermost-server/mlog" + "github.com/mattermost/mattermost-server/model" + "github.com/mattermost/mattermost-server/utils" + "github.com/nicksnyder/go-i18n/i18n" +) + +func (a *App) sendPushNotification(post *model.Post, user *model.User, channel *model.Channel, channelName string, sender *model.User, senderName string, + explicitMention, channelWideMention bool, replyToThreadType string) *model.AppError { + cfg := a.Config() + contentsConfig := *cfg.EmailSettings.PushNotificationContents + teammateNameConfig := *cfg.TeamSettings.TeammateNameDisplay + sessions, err := a.getMobileAppSessions(user.Id) + sentBySystem := senderName == utils.T("system.message.name") + if err != nil { + return err + } + + msg := model.PushNotification{} + if badge := <-a.Srv.Store.User().GetUnreadCount(user.Id); badge.Err != nil { + msg.Badge = 1 + mlog.Error(fmt.Sprint("We could not get the unread message count for the user", user.Id, badge.Err), mlog.String("user_id", user.Id)) + } else { + msg.Badge = int(badge.Data.(int64)) + } + + msg.Category = model.CATEGORY_CAN_REPLY + msg.Version = model.PUSH_MESSAGE_V2 + msg.Type = model.PUSH_TYPE_MESSAGE + msg.TeamId = channel.TeamId + msg.ChannelId = channel.Id + msg.PostId = post.Id + msg.RootId = post.RootId + msg.SenderId = post.UserId + + if !sentBySystem { + senderName = sender.GetDisplayName(teammateNameConfig) + preference, prefError := a.GetPreferenceByCategoryAndNameForUser(user.Id, model.PREFERENCE_CATEGORY_DISPLAY_SETTINGS, "name_format") + if prefError == nil && preference.Value != teammateNameConfig { + senderName = sender.GetDisplayName(preference.Value) + } + } + + if channel.Type == model.CHANNEL_DIRECT { + channelName = fmt.Sprintf("@%v", senderName) + } + + if contentsConfig != model.GENERIC_NO_CHANNEL_NOTIFICATION || channel.Type == model.CHANNEL_DIRECT { + msg.ChannelName = channelName + } + + if ou, ok := post.Props["override_username"].(string); ok && cfg.ServiceSettings.EnablePostUsernameOverride { + msg.OverrideUsername = ou + senderName = ou + } + + if oi, ok := post.Props["override_icon_url"].(string); ok && cfg.ServiceSettings.EnablePostIconOverride { + msg.OverrideIconUrl = oi + } + + if fw, ok := post.Props["from_webhook"].(string); ok { + msg.FromWebhook = fw + } + + userLocale := utils.GetUserTranslations(user.Locale) + hasFiles := post.FileIds != nil && len(post.FileIds) > 0 + + msg.Message = a.getPushNotificationMessage(post.Message, explicitMention, channelWideMention, hasFiles, senderName, channelName, channel.Type, replyToThreadType, userLocale) + + for _, session := range sessions { + + if session.IsExpired() { + continue + } + + tmpMessage := *model.PushNotificationFromJson(strings.NewReader(msg.ToJson())) + tmpMessage.SetDeviceIdAndPlatform(session.DeviceId) + + mlog.Debug(fmt.Sprintf("Sending push notification to device %v for user %v with msg of '%v'", tmpMessage.DeviceId, user.Id, msg.Message), mlog.String("user_id", user.Id)) + + a.Go(func(session *model.Session) func() { + return func() { + a.sendToPushProxy(tmpMessage, session) + } + }(session)) + + if a.Metrics != nil { + a.Metrics.IncrementPostSentPush() + } + } + + return nil +} + +func (a *App) getPushNotificationMessage(postMessage string, explicitMention, channelWideMention, hasFiles bool, + senderName, channelName, channelType, replyToThreadType string, userLocale i18n.TranslateFunc) string { + message := "" + + contentsConfig := *a.Config().EmailSettings.PushNotificationContents + + if contentsConfig == model.FULL_NOTIFICATION { + if channelType == model.CHANNEL_DIRECT { + message = model.ClearMentionTags(postMessage) + } else { + message = "@" + senderName + ": " + model.ClearMentionTags(postMessage) + } + } else { + if channelType == model.CHANNEL_DIRECT { + message = userLocale("api.post.send_notifications_and_forget.push_message") + } else if channelWideMention { + message = "@" + senderName + userLocale("api.post.send_notification_and_forget.push_channel_mention") + } else if explicitMention { + message = "@" + senderName + userLocale("api.post.send_notifications_and_forget.push_explicit_mention") + } else if replyToThreadType == THREAD_ROOT { + message = "@" + senderName + userLocale("api.post.send_notification_and_forget.push_comment_on_post") + } else if replyToThreadType == THREAD_ANY { + message = "@" + senderName + userLocale("api.post.send_notification_and_forget.push_comment_on_thread") + } else { + message = "@" + senderName + userLocale("api.post.send_notifications_and_forget.push_general_message") + } + } + + // If the post only has images then push an appropriate message + if len(postMessage) == 0 && hasFiles { + if channelType == model.CHANNEL_DIRECT { + message = strings.Trim(userLocale("api.post.send_notifications_and_forget.push_image_only"), " ") + } else { + message = "@" + senderName + userLocale("api.post.send_notifications_and_forget.push_image_only") + } + } + + return message +} + +func (a *App) ClearPushNotification(userId string, channelId string) { + a.Go(func() { + // Sleep is to allow the read replicas a chance to fully sync + // the unread count for sending an accurate count. + // Delaying a little doesn't hurt anything and is cheaper than + // attempting to read from master. + time.Sleep(time.Second * 5) + + sessions, err := a.getMobileAppSessions(userId) + if err != nil { + mlog.Error(err.Error()) + return + } + + msg := model.PushNotification{} + msg.Type = model.PUSH_TYPE_CLEAR + msg.ChannelId = channelId + msg.ContentAvailable = 0 + if badge := <-a.Srv.Store.User().GetUnreadCount(userId); badge.Err != nil { + msg.Badge = 0 + mlog.Error(fmt.Sprint("We could not get the unread message count for the user", userId, badge.Err), mlog.String("user_id", userId)) + } else { + msg.Badge = int(badge.Data.(int64)) + } + + mlog.Debug(fmt.Sprintf("Clearing push notification to %v with channel_id %v", msg.DeviceId, msg.ChannelId)) + + for _, session := range sessions { + tmpMessage := *model.PushNotificationFromJson(strings.NewReader(msg.ToJson())) + tmpMessage.SetDeviceIdAndPlatform(session.DeviceId) + a.Go(func() { + a.sendToPushProxy(tmpMessage, session) + }) + } + }) +} + +func (a *App) sendToPushProxy(msg model.PushNotification, session *model.Session) { + msg.ServerId = a.DiagnosticId() + + request, _ := http.NewRequest("POST", strings.TrimRight(*a.Config().EmailSettings.PushNotificationServer, "/")+model.API_URL_SUFFIX_V1+"/send_push", strings.NewReader(msg.ToJson())) + + if resp, err := a.HTTPClient(true).Do(request); err != nil { + mlog.Error(fmt.Sprintf("Device push reported as error for UserId=%v SessionId=%v message=%v", session.UserId, session.Id, err.Error()), mlog.String("user_id", session.UserId)) + } else { + pushResponse := model.PushResponseFromJson(resp.Body) + if resp.Body != nil { + consumeAndClose(resp) + } + + if pushResponse[model.PUSH_STATUS] == model.PUSH_STATUS_REMOVE { + mlog.Info(fmt.Sprintf("Device was reported as removed for UserId=%v SessionId=%v removing push for this session", session.UserId, session.Id), mlog.String("user_id", session.UserId)) + a.AttachDeviceId(session.Id, "", session.ExpiresAt) + a.ClearSessionCacheForUser(session.UserId) + } + + if pushResponse[model.PUSH_STATUS] == model.PUSH_STATUS_FAIL { + mlog.Error(fmt.Sprintf("Device push reported as error for UserId=%v SessionId=%v message=%v", session.UserId, session.Id, pushResponse[model.PUSH_STATUS_ERROR_MSG]), mlog.String("user_id", session.UserId)) + } + } +} + +func (a *App) getMobileAppSessions(userId string) ([]*model.Session, *model.AppError) { + if result := <-a.Srv.Store.Session().GetSessionsWithActiveDeviceIds(userId); result.Err != nil { + return nil, result.Err + } else { + return result.Data.([]*model.Session), nil + } +} + +func ShouldSendPushNotification(user *model.User, channelNotifyProps model.StringMap, wasMentioned bool, status *model.Status, post *model.Post) bool { + return DoesNotifyPropsAllowPushNotification(user, channelNotifyProps, post, wasMentioned) && + DoesStatusAllowPushNotification(user.NotifyProps, status, post.ChannelId) +} + +func DoesNotifyPropsAllowPushNotification(user *model.User, channelNotifyProps model.StringMap, post *model.Post, wasMentioned bool) bool { + userNotifyProps := user.NotifyProps + userNotify := userNotifyProps[model.PUSH_NOTIFY_PROP] + channelNotify, ok := channelNotifyProps[model.PUSH_NOTIFY_PROP] + + // If the channel is muted do not send push notifications + if channelMuted, ok := channelNotifyProps[model.MARK_UNREAD_NOTIFY_PROP]; ok { + if channelMuted == model.CHANNEL_MARK_UNREAD_MENTION { + return false + } + } + + if post.IsSystemMessage() { + return false + } + + if channelNotify == model.USER_NOTIFY_NONE { + return false + } + + if channelNotify == model.CHANNEL_NOTIFY_MENTION && !wasMentioned { + return false + } + + if userNotify == model.USER_NOTIFY_MENTION && (!ok || channelNotify == model.CHANNEL_NOTIFY_DEFAULT) && !wasMentioned { + return false + } + + if (userNotify == model.USER_NOTIFY_ALL || channelNotify == model.CHANNEL_NOTIFY_ALL) && + (post.UserId != user.Id || post.Props["from_webhook"] == "true") { + return true + } + + if userNotify == model.USER_NOTIFY_NONE && + (!ok || channelNotify == model.CHANNEL_NOTIFY_DEFAULT) { + return false + } + + return true +} + +func DoesStatusAllowPushNotification(userNotifyProps model.StringMap, status *model.Status, channelId string) bool { + // If User status is DND or OOO return false right away + if status.Status == model.STATUS_DND || status.Status == model.STATUS_OUT_OF_OFFICE { + return false + } + + if pushStatus, ok := userNotifyProps["push_status"]; (pushStatus == model.STATUS_ONLINE || !ok) && (status.ActiveChannel != channelId || model.GetMillis()-status.LastActivityAt > model.STATUS_CHANNEL_TIMEOUT) { + return true + } else if pushStatus == model.STATUS_AWAY && (status.Status == model.STATUS_AWAY || status.Status == model.STATUS_OFFLINE) { + return true + } else if pushStatus == model.STATUS_OFFLINE && status.Status == model.STATUS_OFFLINE { + return true + } + + return false +} diff --git a/app/notification_push_test.go b/app/notification_push_test.go new file mode 100644 index 000000000..aec0406b1 --- /dev/null +++ b/app/notification_push_test.go @@ -0,0 +1,699 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package app + +import ( + "testing" + + "github.com/mattermost/mattermost-server/model" + "github.com/mattermost/mattermost-server/utils" +) + +func TestDoesNotifyPropsAllowPushNotification(t *testing.T) { + userNotifyProps := make(map[string]string) + channelNotifyProps := make(map[string]string) + + user := &model.User{Id: model.NewId(), Email: "unit@test.com"} + + post := &model.Post{UserId: user.Id, ChannelId: model.NewId()} + + // When the post is a System Message + systemPost := &model.Post{UserId: user.Id, Type: model.POST_JOIN_CHANNEL} + userNotifyProps[model.PUSH_NOTIFY_PROP] = model.USER_NOTIFY_ALL + user.NotifyProps = userNotifyProps + if DoesNotifyPropsAllowPushNotification(user, channelNotifyProps, systemPost, false) { + t.Fatal("Should have returned false") + } + + if DoesNotifyPropsAllowPushNotification(user, channelNotifyProps, systemPost, true) { + t.Fatal("Should have returned false") + } + + // When default is ALL and no channel props is set + if !DoesNotifyPropsAllowPushNotification(user, channelNotifyProps, post, false) { + t.Fatal("Should have returned true") + } + + if !DoesNotifyPropsAllowPushNotification(user, channelNotifyProps, post, true) { + t.Fatal("Should have returned true") + } + + // When default is MENTION and no channel props is set + userNotifyProps[model.PUSH_NOTIFY_PROP] = model.USER_NOTIFY_MENTION + user.NotifyProps = userNotifyProps + if DoesNotifyPropsAllowPushNotification(user, channelNotifyProps, post, false) { + t.Fatal("Should have returned false") + } + + if !DoesNotifyPropsAllowPushNotification(user, channelNotifyProps, post, true) { + t.Fatal("Should have returned true") + } + + // When default is NONE and no channel props is set + userNotifyProps[model.PUSH_NOTIFY_PROP] = model.USER_NOTIFY_NONE + user.NotifyProps = userNotifyProps + if DoesNotifyPropsAllowPushNotification(user, channelNotifyProps, post, false) { + t.Fatal("Should have returned false") + } + + if DoesNotifyPropsAllowPushNotification(user, channelNotifyProps, post, true) { + t.Fatal("Should have returned false") + } + + // WHEN default is ALL and channel is DEFAULT + userNotifyProps[model.PUSH_NOTIFY_PROP] = model.USER_NOTIFY_ALL + user.NotifyProps = userNotifyProps + channelNotifyProps[model.PUSH_NOTIFY_PROP] = model.CHANNEL_NOTIFY_DEFAULT + if !DoesNotifyPropsAllowPushNotification(user, channelNotifyProps, post, false) { + t.Fatal("Should have returned true") + } + + if !DoesNotifyPropsAllowPushNotification(user, channelNotifyProps, post, true) { + t.Fatal("Should have returned true") + } + + // WHEN default is MENTION and channel is DEFAULT + userNotifyProps[model.PUSH_NOTIFY_PROP] = model.USER_NOTIFY_MENTION + user.NotifyProps = userNotifyProps + channelNotifyProps[model.PUSH_NOTIFY_PROP] = model.CHANNEL_NOTIFY_DEFAULT + if DoesNotifyPropsAllowPushNotification(user, channelNotifyProps, post, false) { + t.Fatal("Should have returned false") + } + + if !DoesNotifyPropsAllowPushNotification(user, channelNotifyProps, post, true) { + t.Fatal("Should have returned true") + } + + // WHEN default is NONE and channel is DEFAULT + userNotifyProps[model.PUSH_NOTIFY_PROP] = model.USER_NOTIFY_NONE + user.NotifyProps = userNotifyProps + channelNotifyProps[model.PUSH_NOTIFY_PROP] = model.CHANNEL_NOTIFY_DEFAULT + if DoesNotifyPropsAllowPushNotification(user, channelNotifyProps, post, false) { + t.Fatal("Should have returned false") + } + + if DoesNotifyPropsAllowPushNotification(user, channelNotifyProps, post, true) { + t.Fatal("Should have returned false") + } + + // WHEN default is ALL and channel is ALL + userNotifyProps[model.PUSH_NOTIFY_PROP] = model.USER_NOTIFY_ALL + user.NotifyProps = userNotifyProps + channelNotifyProps[model.PUSH_NOTIFY_PROP] = model.CHANNEL_NOTIFY_ALL + if !DoesNotifyPropsAllowPushNotification(user, channelNotifyProps, post, false) { + t.Fatal("Should have returned true") + } + + if !DoesNotifyPropsAllowPushNotification(user, channelNotifyProps, post, true) { + t.Fatal("Should have returned true") + } + + // WHEN default is MENTION and channel is ALL + userNotifyProps[model.PUSH_NOTIFY_PROP] = model.USER_NOTIFY_MENTION + user.NotifyProps = userNotifyProps + channelNotifyProps[model.PUSH_NOTIFY_PROP] = model.CHANNEL_NOTIFY_ALL + if !DoesNotifyPropsAllowPushNotification(user, channelNotifyProps, post, false) { + t.Fatal("Should have returned true") + } + + if !DoesNotifyPropsAllowPushNotification(user, channelNotifyProps, post, true) { + t.Fatal("Should have returned true") + } + + // WHEN default is NONE and channel is ALL + userNotifyProps[model.PUSH_NOTIFY_PROP] = model.USER_NOTIFY_NONE + user.NotifyProps = userNotifyProps + channelNotifyProps[model.PUSH_NOTIFY_PROP] = model.CHANNEL_NOTIFY_ALL + if !DoesNotifyPropsAllowPushNotification(user, channelNotifyProps, post, false) { + t.Fatal("Should have returned true") + } + + if !DoesNotifyPropsAllowPushNotification(user, channelNotifyProps, post, true) { + t.Fatal("Should have returned true") + } + + // WHEN default is ALL and channel is MENTION + userNotifyProps[model.PUSH_NOTIFY_PROP] = model.USER_NOTIFY_ALL + user.NotifyProps = userNotifyProps + channelNotifyProps[model.PUSH_NOTIFY_PROP] = model.CHANNEL_NOTIFY_MENTION + if DoesNotifyPropsAllowPushNotification(user, channelNotifyProps, post, false) { + t.Fatal("Should have returned false") + } + + if !DoesNotifyPropsAllowPushNotification(user, channelNotifyProps, post, true) { + t.Fatal("Should have returned true") + } + + // WHEN default is MENTION and channel is MENTION + userNotifyProps[model.PUSH_NOTIFY_PROP] = model.USER_NOTIFY_MENTION + user.NotifyProps = userNotifyProps + channelNotifyProps[model.PUSH_NOTIFY_PROP] = model.CHANNEL_NOTIFY_MENTION + if DoesNotifyPropsAllowPushNotification(user, channelNotifyProps, post, false) { + t.Fatal("Should have returned false") + } + + if !DoesNotifyPropsAllowPushNotification(user, channelNotifyProps, post, true) { + t.Fatal("Should have returned true") + } + + // WHEN default is NONE and channel is MENTION + userNotifyProps[model.PUSH_NOTIFY_PROP] = model.USER_NOTIFY_NONE + user.NotifyProps = userNotifyProps + channelNotifyProps[model.PUSH_NOTIFY_PROP] = model.CHANNEL_NOTIFY_MENTION + if DoesNotifyPropsAllowPushNotification(user, channelNotifyProps, post, false) { + t.Fatal("Should have returned false") + } + + if !DoesNotifyPropsAllowPushNotification(user, channelNotifyProps, post, true) { + t.Fatal("Should have returned true") + } + + // WHEN default is ALL and channel is NONE + userNotifyProps[model.PUSH_NOTIFY_PROP] = model.USER_NOTIFY_ALL + user.NotifyProps = userNotifyProps + channelNotifyProps[model.PUSH_NOTIFY_PROP] = model.CHANNEL_NOTIFY_NONE + if DoesNotifyPropsAllowPushNotification(user, channelNotifyProps, post, false) { + t.Fatal("Should have returned false") + } + + if DoesNotifyPropsAllowPushNotification(user, channelNotifyProps, post, true) { + t.Fatal("Should have returned false") + } + + // WHEN default is MENTION and channel is NONE + userNotifyProps[model.PUSH_NOTIFY_PROP] = model.USER_NOTIFY_MENTION + user.NotifyProps = userNotifyProps + channelNotifyProps[model.PUSH_NOTIFY_PROP] = model.CHANNEL_NOTIFY_NONE + if DoesNotifyPropsAllowPushNotification(user, channelNotifyProps, post, false) { + t.Fatal("Should have returned false") + } + + if DoesNotifyPropsAllowPushNotification(user, channelNotifyProps, post, true) { + t.Fatal("Should have returned false") + } + + // WHEN default is NONE and channel is NONE + userNotifyProps[model.PUSH_NOTIFY_PROP] = model.USER_NOTIFY_NONE + user.NotifyProps = userNotifyProps + channelNotifyProps[model.PUSH_NOTIFY_PROP] = model.CHANNEL_NOTIFY_NONE + if DoesNotifyPropsAllowPushNotification(user, channelNotifyProps, post, false) { + t.Fatal("Should have returned false") + } + + if DoesNotifyPropsAllowPushNotification(user, channelNotifyProps, post, true) { + t.Fatal("Should have returned false") + } + + // WHEN default is ALL and channel is MUTED + userNotifyProps[model.PUSH_NOTIFY_PROP] = model.USER_NOTIFY_ALL + user.NotifyProps = userNotifyProps + channelNotifyProps[model.MARK_UNREAD_NOTIFY_PROP] = model.CHANNEL_MARK_UNREAD_MENTION + if DoesNotifyPropsAllowPushNotification(user, channelNotifyProps, post, false) { + t.Fatal("Should have returned false") + } +} + +func TestDoesStatusAllowPushNotification(t *testing.T) { + userNotifyProps := make(map[string]string) + userId := model.NewId() + channelId := model.NewId() + + offline := &model.Status{UserId: userId, Status: model.STATUS_OFFLINE, Manual: false, LastActivityAt: 0, ActiveChannel: ""} + away := &model.Status{UserId: userId, Status: model.STATUS_AWAY, Manual: false, LastActivityAt: 0, ActiveChannel: ""} + online := &model.Status{UserId: userId, Status: model.STATUS_ONLINE, Manual: false, LastActivityAt: model.GetMillis(), ActiveChannel: ""} + dnd := &model.Status{UserId: userId, Status: model.STATUS_DND, Manual: true, LastActivityAt: model.GetMillis(), ActiveChannel: ""} + + userNotifyProps["push_status"] = model.STATUS_ONLINE + // WHEN props is ONLINE and user is offline + if !DoesStatusAllowPushNotification(userNotifyProps, offline, channelId) { + t.Fatal("Should have been true") + } + + if !DoesStatusAllowPushNotification(userNotifyProps, offline, "") { + t.Fatal("Should have been true") + } + + // WHEN props is ONLINE and user is away + if !DoesStatusAllowPushNotification(userNotifyProps, away, channelId) { + t.Fatal("Should have been true") + } + + if !DoesStatusAllowPushNotification(userNotifyProps, away, "") { + t.Fatal("Should have been true") + } + + // WHEN props is ONLINE and user is online + if !DoesStatusAllowPushNotification(userNotifyProps, online, channelId) { + t.Fatal("Should have been true") + } + + if DoesStatusAllowPushNotification(userNotifyProps, online, "") { + t.Fatal("Should have been false") + } + + // WHEN props is ONLINE and user is dnd + if DoesStatusAllowPushNotification(userNotifyProps, dnd, channelId) { + t.Fatal("Should have been false") + } + + if DoesStatusAllowPushNotification(userNotifyProps, dnd, "") { + t.Fatal("Should have been false") + } + + userNotifyProps["push_status"] = model.STATUS_AWAY + // WHEN props is AWAY and user is offline + if !DoesStatusAllowPushNotification(userNotifyProps, offline, channelId) { + t.Fatal("Should have been true") + } + + if !DoesStatusAllowPushNotification(userNotifyProps, offline, "") { + t.Fatal("Should have been true") + } + + // WHEN props is AWAY and user is away + if !DoesStatusAllowPushNotification(userNotifyProps, away, channelId) { + t.Fatal("Should have been true") + } + + if !DoesStatusAllowPushNotification(userNotifyProps, away, "") { + t.Fatal("Should have been true") + } + + // WHEN props is AWAY and user is online + if DoesStatusAllowPushNotification(userNotifyProps, online, channelId) { + t.Fatal("Should have been false") + } + + if DoesStatusAllowPushNotification(userNotifyProps, online, "") { + t.Fatal("Should have been false") + } + + // WHEN props is AWAY and user is dnd + if DoesStatusAllowPushNotification(userNotifyProps, dnd, channelId) { + t.Fatal("Should have been false") + } + + if DoesStatusAllowPushNotification(userNotifyProps, dnd, "") { + t.Fatal("Should have been false") + } + + userNotifyProps["push_status"] = model.STATUS_OFFLINE + // WHEN props is OFFLINE and user is offline + if !DoesStatusAllowPushNotification(userNotifyProps, offline, channelId) { + t.Fatal("Should have been true") + } + + if !DoesStatusAllowPushNotification(userNotifyProps, offline, "") { + t.Fatal("Should have been true") + } + + // WHEN props is OFFLINE and user is away + if DoesStatusAllowPushNotification(userNotifyProps, away, channelId) { + t.Fatal("Should have been false") + } + + if DoesStatusAllowPushNotification(userNotifyProps, away, "") { + t.Fatal("Should have been false") + } + + // WHEN props is OFFLINE and user is online + if DoesStatusAllowPushNotification(userNotifyProps, online, channelId) { + t.Fatal("Should have been false") + } + + if DoesStatusAllowPushNotification(userNotifyProps, online, "") { + t.Fatal("Should have been false") + } + + // WHEN props is OFFLINE and user is dnd + if DoesStatusAllowPushNotification(userNotifyProps, dnd, channelId) { + t.Fatal("Should have been false") + } + + if DoesStatusAllowPushNotification(userNotifyProps, dnd, "") { + t.Fatal("Should have been false") + } + +} + +func TestGetPushNotificationMessage(t *testing.T) { + th := Setup() + defer th.TearDown() + + for name, tc := range map[string]struct { + Message string + explicitMention bool + channelWideMention bool + HasFiles bool + replyToThreadType string + Locale string + PushNotificationContents string + ChannelType string + + ExpectedMessage string + }{ + "full message, public channel, no mention": { + Message: "this is a message", + ChannelType: model.CHANNEL_OPEN, + ExpectedMessage: "@user: this is a message", + }, + "full message, public channel, mention": { + Message: "this is a message", + explicitMention: true, + ChannelType: model.CHANNEL_OPEN, + ExpectedMessage: "@user: this is a message", + }, + "full message, public channel, channel wide mention": { + Message: "this is a message", + channelWideMention: true, + ChannelType: model.CHANNEL_OPEN, + ExpectedMessage: "@user: this is a message", + }, + "full message, public channel, commented on post": { + Message: "this is a message", + replyToThreadType: THREAD_ROOT, + ChannelType: model.CHANNEL_OPEN, + ExpectedMessage: "@user: this is a message", + }, + "full message, public channel, commented on thread": { + Message: "this is a message", + replyToThreadType: THREAD_ANY, + ChannelType: model.CHANNEL_OPEN, + ExpectedMessage: "@user: this is a message", + }, + "full message, private channel, no mention": { + Message: "this is a message", + ChannelType: model.CHANNEL_PRIVATE, + ExpectedMessage: "@user: this is a message", + }, + "full message, private channel, mention": { + Message: "this is a message", + explicitMention: true, + ChannelType: model.CHANNEL_PRIVATE, + ExpectedMessage: "@user: this is a message", + }, + "full message, private channel, commented on post": { + Message: "this is a message", + replyToThreadType: THREAD_ROOT, + ChannelType: model.CHANNEL_PRIVATE, + ExpectedMessage: "@user: this is a message", + }, + "full message, private channel, commented on thread": { + Message: "this is a message", + replyToThreadType: THREAD_ANY, + ChannelType: model.CHANNEL_PRIVATE, + ExpectedMessage: "@user: this is a message", + }, + "full message, group message channel, no mention": { + Message: "this is a message", + ChannelType: model.CHANNEL_GROUP, + ExpectedMessage: "@user: this is a message", + }, + "full message, group message channel, mention": { + Message: "this is a message", + explicitMention: true, + ChannelType: model.CHANNEL_GROUP, + ExpectedMessage: "@user: this is a message", + }, + "full message, group message channel, commented on post": { + Message: "this is a message", + replyToThreadType: THREAD_ROOT, + ChannelType: model.CHANNEL_GROUP, + ExpectedMessage: "@user: this is a message", + }, + "full message, group message channel, commented on thread": { + Message: "this is a message", + replyToThreadType: THREAD_ANY, + ChannelType: model.CHANNEL_GROUP, + ExpectedMessage: "@user: this is a message", + }, + "full message, direct message channel, no mention": { + Message: "this is a message", + ChannelType: model.CHANNEL_DIRECT, + ExpectedMessage: "this is a message", + }, + "full message, direct message channel, mention": { + Message: "this is a message", + explicitMention: true, + ChannelType: model.CHANNEL_DIRECT, + ExpectedMessage: "this is a message", + }, + "full message, direct message channel, commented on post": { + Message: "this is a message", + replyToThreadType: THREAD_ROOT, + ChannelType: model.CHANNEL_DIRECT, + ExpectedMessage: "this is a message", + }, + "full message, direct message channel, commented on thread": { + Message: "this is a message", + replyToThreadType: THREAD_ANY, + ChannelType: model.CHANNEL_DIRECT, + ExpectedMessage: "this is a message", + }, + "generic message with channel, public channel, no mention": { + Message: "this is a message", + PushNotificationContents: model.GENERIC_NOTIFICATION, + ChannelType: model.CHANNEL_OPEN, + ExpectedMessage: "@user posted a message.", + }, + "generic message with channel, public channel, mention": { + Message: "this is a message", + explicitMention: true, + PushNotificationContents: model.GENERIC_NOTIFICATION, + ChannelType: model.CHANNEL_OPEN, + ExpectedMessage: "@user mentioned you.", + }, + "generic message with channel, public channel, channel wide mention": { + Message: "this is a message", + channelWideMention: true, + PushNotificationContents: model.GENERIC_NOTIFICATION, + ChannelType: model.CHANNEL_OPEN, + ExpectedMessage: "@user notified the channel.", + }, + "generic message, public channel, commented on post": { + Message: "this is a message", + replyToThreadType: THREAD_ROOT, + PushNotificationContents: model.GENERIC_NOTIFICATION, + ChannelType: model.CHANNEL_OPEN, + ExpectedMessage: "@user commented on your post.", + }, + "generic message, public channel, commented on thread": { + Message: "this is a message", + replyToThreadType: THREAD_ANY, + PushNotificationContents: model.GENERIC_NOTIFICATION, + ChannelType: model.CHANNEL_OPEN, + ExpectedMessage: "@user commented on a thread you participated in.", + }, + "generic message with channel, private channel, no mention": { + Message: "this is a message", + PushNotificationContents: model.GENERIC_NOTIFICATION, + ChannelType: model.CHANNEL_PRIVATE, + ExpectedMessage: "@user posted a message.", + }, + "generic message with channel, private channel, mention": { + Message: "this is a message", + explicitMention: true, + PushNotificationContents: model.GENERIC_NOTIFICATION, + ChannelType: model.CHANNEL_PRIVATE, + ExpectedMessage: "@user mentioned you.", + }, + "generic message with channel, private channel, channel wide mention": { + Message: "this is a message", + channelWideMention: true, + PushNotificationContents: model.GENERIC_NOTIFICATION, + ChannelType: model.CHANNEL_PRIVATE, + ExpectedMessage: "@user notified the channel.", + }, + "generic message, public private, commented on post": { + Message: "this is a message", + replyToThreadType: THREAD_ROOT, + PushNotificationContents: model.GENERIC_NOTIFICATION, + ChannelType: model.CHANNEL_PRIVATE, + ExpectedMessage: "@user commented on your post.", + }, + "generic message, public private, commented on thread": { + Message: "this is a message", + replyToThreadType: THREAD_ANY, + PushNotificationContents: model.GENERIC_NOTIFICATION, + ChannelType: model.CHANNEL_PRIVATE, + ExpectedMessage: "@user commented on a thread you participated in.", + }, + "generic message with channel, group message channel, no mention": { + Message: "this is a message", + PushNotificationContents: model.GENERIC_NOTIFICATION, + ChannelType: model.CHANNEL_GROUP, + ExpectedMessage: "@user posted a message.", + }, + "generic message with channel, group message channel, mention": { + Message: "this is a message", + explicitMention: true, + PushNotificationContents: model.GENERIC_NOTIFICATION, + ChannelType: model.CHANNEL_GROUP, + ExpectedMessage: "@user mentioned you.", + }, + "generic message with channel, group message channel, channel wide mention": { + Message: "this is a message", + channelWideMention: true, + PushNotificationContents: model.GENERIC_NOTIFICATION, + ChannelType: model.CHANNEL_GROUP, + ExpectedMessage: "@user notified the channel.", + }, + "generic message, group message channel, commented on post": { + Message: "this is a message", + replyToThreadType: THREAD_ROOT, + PushNotificationContents: model.GENERIC_NOTIFICATION, + ChannelType: model.CHANNEL_GROUP, + ExpectedMessage: "@user commented on your post.", + }, + "generic message, group message channel, commented on thread": { + Message: "this is a message", + replyToThreadType: THREAD_ANY, + PushNotificationContents: model.GENERIC_NOTIFICATION, + ChannelType: model.CHANNEL_GROUP, + ExpectedMessage: "@user commented on a thread you participated in.", + }, + "generic message with channel, direct message channel, no mention": { + Message: "this is a message", + PushNotificationContents: model.GENERIC_NOTIFICATION, + ChannelType: model.CHANNEL_DIRECT, + ExpectedMessage: "sent you a message.", + }, + "generic message with channel, direct message channel, mention": { + Message: "this is a message", + explicitMention: true, + PushNotificationContents: model.GENERIC_NOTIFICATION, + ChannelType: model.CHANNEL_DIRECT, + ExpectedMessage: "sent you a message.", + }, + "generic message with channel, direct message channel, channel wide mention": { + Message: "this is a message", + channelWideMention: true, + PushNotificationContents: model.GENERIC_NOTIFICATION, + ChannelType: model.CHANNEL_DIRECT, + ExpectedMessage: "sent you a message.", + }, + "generic message, direct message channel, commented on post": { + Message: "this is a message", + replyToThreadType: THREAD_ROOT, + PushNotificationContents: model.GENERIC_NOTIFICATION, + ChannelType: model.CHANNEL_DIRECT, + ExpectedMessage: "sent you a message.", + }, + "generic message, direct message channel, commented on thread": { + Message: "this is a message", + replyToThreadType: THREAD_ANY, + PushNotificationContents: model.GENERIC_NOTIFICATION, + ChannelType: model.CHANNEL_DIRECT, + ExpectedMessage: "sent you a message.", + }, + "generic message without channel, public channel, no mention": { + Message: "this is a message", + PushNotificationContents: model.GENERIC_NO_CHANNEL_NOTIFICATION, + ChannelType: model.CHANNEL_OPEN, + ExpectedMessage: "@user posted a message.", + }, + "generic message without channel, public channel, mention": { + Message: "this is a message", + explicitMention: true, + PushNotificationContents: model.GENERIC_NO_CHANNEL_NOTIFICATION, + ChannelType: model.CHANNEL_OPEN, + ExpectedMessage: "@user mentioned you.", + }, + "generic message without channel, private channel, no mention": { + Message: "this is a message", + PushNotificationContents: model.GENERIC_NO_CHANNEL_NOTIFICATION, + ChannelType: model.CHANNEL_PRIVATE, + ExpectedMessage: "@user posted a message.", + }, + "generic message without channel, private channel, mention": { + Message: "this is a message", + explicitMention: true, + PushNotificationContents: model.GENERIC_NO_CHANNEL_NOTIFICATION, + ChannelType: model.CHANNEL_PRIVATE, + ExpectedMessage: "@user mentioned you.", + }, + "generic message without channel, group message channel, no mention": { + Message: "this is a message", + PushNotificationContents: model.GENERIC_NO_CHANNEL_NOTIFICATION, + ChannelType: model.CHANNEL_GROUP, + ExpectedMessage: "@user posted a message.", + }, + "generic message without channel, group message channel, mention": { + Message: "this is a message", + explicitMention: true, + PushNotificationContents: model.GENERIC_NO_CHANNEL_NOTIFICATION, + ChannelType: model.CHANNEL_GROUP, + ExpectedMessage: "@user mentioned you.", + }, + "generic message without channel, direct message channel, no mention": { + Message: "this is a message", + PushNotificationContents: model.GENERIC_NO_CHANNEL_NOTIFICATION, + ChannelType: model.CHANNEL_DIRECT, + ExpectedMessage: "sent you a message.", + }, + "generic message without channel, direct message channel, mention": { + Message: "this is a message", + explicitMention: true, + PushNotificationContents: model.GENERIC_NO_CHANNEL_NOTIFICATION, + ChannelType: model.CHANNEL_DIRECT, + ExpectedMessage: "sent you a message.", + }, + "only files, public channel": { + HasFiles: true, + ChannelType: model.CHANNEL_OPEN, + ExpectedMessage: "@user attached a file.", + }, + "only files, private channel": { + HasFiles: true, + ChannelType: model.CHANNEL_PRIVATE, + ExpectedMessage: "@user attached a file.", + }, + "only files, group message channel": { + HasFiles: true, + ChannelType: model.CHANNEL_GROUP, + ExpectedMessage: "@user attached a file.", + }, + "only files, direct message channel": { + HasFiles: true, + ChannelType: model.CHANNEL_DIRECT, + ExpectedMessage: "attached a file.", + }, + "only files without channel, public channel": { + HasFiles: true, + PushNotificationContents: model.GENERIC_NO_CHANNEL_NOTIFICATION, + ChannelType: model.CHANNEL_OPEN, + ExpectedMessage: "@user attached a file.", + }, + } { + t.Run(name, func(t *testing.T) { + locale := tc.Locale + if locale == "" { + locale = "en" + } + + pushNotificationContents := tc.PushNotificationContents + if pushNotificationContents == "" { + pushNotificationContents = model.FULL_NOTIFICATION + } + + th.App.UpdateConfig(func(cfg *model.Config) { + *cfg.EmailSettings.PushNotificationContents = pushNotificationContents + }) + + if actualMessage := th.App.getPushNotificationMessage( + tc.Message, + tc.explicitMention, + tc.channelWideMention, + tc.HasFiles, + "user", + "channel", + tc.ChannelType, + tc.replyToThreadType, + utils.GetUserTranslations(locale), + ); actualMessage != tc.ExpectedMessage { + t.Fatalf("Received incorrect push notification message `%v`, expected `%v`", actualMessage, tc.ExpectedMessage) + } + }) + } +} diff --git a/app/notification_test.go b/app/notification_test.go index 8694f9f2d..d9f81dccb 100644 --- a/app/notification_test.go +++ b/app/notification_test.go @@ -4,16 +4,12 @@ package app import ( - "strings" "testing" "github.com/stretchr/testify/assert" - "fmt" "github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/utils" - "regexp" - "time" ) func TestSendNotifications(t *testing.T) { @@ -90,10 +86,10 @@ func TestGetExplicitMentions(t *testing.T) { id3 := model.NewId() for name, tc := range map[string]struct { - Message string + Message string Attachments []*model.SlackAttachment - Keywords map[string][]string - Expected *ExplicitMentions + Keywords map[string][]string + Expected *ExplicitMentions }{ "Nobody": { Message: "this is a message", @@ -534,7 +530,7 @@ func TestGetExplicitMentions(t *testing.T) { post := &model.Post{Message: tc.Message, Props: model.StringInterface{ "attachments": tc.Attachments, - }, + }, } m := GetExplicitMentions(post, tc.Keywords) @@ -784,1185 +780,11 @@ func TestGetMentionKeywords(t *testing.T) { } } -func TestDoesNotifyPropsAllowPushNotification(t *testing.T) { - userNotifyProps := make(map[string]string) - channelNotifyProps := make(map[string]string) - - user := &model.User{Id: model.NewId(), Email: "unit@test.com"} - - post := &model.Post{UserId: user.Id, ChannelId: model.NewId()} - - // When the post is a System Message - systemPost := &model.Post{UserId: user.Id, Type: model.POST_JOIN_CHANNEL} - userNotifyProps[model.PUSH_NOTIFY_PROP] = model.USER_NOTIFY_ALL - user.NotifyProps = userNotifyProps - if DoesNotifyPropsAllowPushNotification(user, channelNotifyProps, systemPost, false) { - t.Fatal("Should have returned false") - } - - if DoesNotifyPropsAllowPushNotification(user, channelNotifyProps, systemPost, true) { - t.Fatal("Should have returned false") - } - - // When default is ALL and no channel props is set - if !DoesNotifyPropsAllowPushNotification(user, channelNotifyProps, post, false) { - t.Fatal("Should have returned true") - } - - if !DoesNotifyPropsAllowPushNotification(user, channelNotifyProps, post, true) { - t.Fatal("Should have returned true") - } - - // When default is MENTION and no channel props is set - userNotifyProps[model.PUSH_NOTIFY_PROP] = model.USER_NOTIFY_MENTION - user.NotifyProps = userNotifyProps - if DoesNotifyPropsAllowPushNotification(user, channelNotifyProps, post, false) { - t.Fatal("Should have returned false") - } - - if !DoesNotifyPropsAllowPushNotification(user, channelNotifyProps, post, true) { - t.Fatal("Should have returned true") - } - - // When default is NONE and no channel props is set - userNotifyProps[model.PUSH_NOTIFY_PROP] = model.USER_NOTIFY_NONE - user.NotifyProps = userNotifyProps - if DoesNotifyPropsAllowPushNotification(user, channelNotifyProps, post, false) { - t.Fatal("Should have returned false") - } - - if DoesNotifyPropsAllowPushNotification(user, channelNotifyProps, post, true) { - t.Fatal("Should have returned false") - } - - // WHEN default is ALL and channel is DEFAULT - userNotifyProps[model.PUSH_NOTIFY_PROP] = model.USER_NOTIFY_ALL - user.NotifyProps = userNotifyProps - channelNotifyProps[model.PUSH_NOTIFY_PROP] = model.CHANNEL_NOTIFY_DEFAULT - if !DoesNotifyPropsAllowPushNotification(user, channelNotifyProps, post, false) { - t.Fatal("Should have returned true") - } - - if !DoesNotifyPropsAllowPushNotification(user, channelNotifyProps, post, true) { - t.Fatal("Should have returned true") - } - - // WHEN default is MENTION and channel is DEFAULT - userNotifyProps[model.PUSH_NOTIFY_PROP] = model.USER_NOTIFY_MENTION - user.NotifyProps = userNotifyProps - channelNotifyProps[model.PUSH_NOTIFY_PROP] = model.CHANNEL_NOTIFY_DEFAULT - if DoesNotifyPropsAllowPushNotification(user, channelNotifyProps, post, false) { - t.Fatal("Should have returned false") - } - - if !DoesNotifyPropsAllowPushNotification(user, channelNotifyProps, post, true) { - t.Fatal("Should have returned true") - } - - // WHEN default is NONE and channel is DEFAULT - userNotifyProps[model.PUSH_NOTIFY_PROP] = model.USER_NOTIFY_NONE - user.NotifyProps = userNotifyProps - channelNotifyProps[model.PUSH_NOTIFY_PROP] = model.CHANNEL_NOTIFY_DEFAULT - if DoesNotifyPropsAllowPushNotification(user, channelNotifyProps, post, false) { - t.Fatal("Should have returned false") - } - - if DoesNotifyPropsAllowPushNotification(user, channelNotifyProps, post, true) { - t.Fatal("Should have returned false") - } - - // WHEN default is ALL and channel is ALL - userNotifyProps[model.PUSH_NOTIFY_PROP] = model.USER_NOTIFY_ALL - user.NotifyProps = userNotifyProps - channelNotifyProps[model.PUSH_NOTIFY_PROP] = model.CHANNEL_NOTIFY_ALL - if !DoesNotifyPropsAllowPushNotification(user, channelNotifyProps, post, false) { - t.Fatal("Should have returned true") - } - - if !DoesNotifyPropsAllowPushNotification(user, channelNotifyProps, post, true) { - t.Fatal("Should have returned true") - } - - // WHEN default is MENTION and channel is ALL - userNotifyProps[model.PUSH_NOTIFY_PROP] = model.USER_NOTIFY_MENTION - user.NotifyProps = userNotifyProps - channelNotifyProps[model.PUSH_NOTIFY_PROP] = model.CHANNEL_NOTIFY_ALL - if !DoesNotifyPropsAllowPushNotification(user, channelNotifyProps, post, false) { - t.Fatal("Should have returned true") - } - - if !DoesNotifyPropsAllowPushNotification(user, channelNotifyProps, post, true) { - t.Fatal("Should have returned true") - } - - // WHEN default is NONE and channel is ALL - userNotifyProps[model.PUSH_NOTIFY_PROP] = model.USER_NOTIFY_NONE - user.NotifyProps = userNotifyProps - channelNotifyProps[model.PUSH_NOTIFY_PROP] = model.CHANNEL_NOTIFY_ALL - if !DoesNotifyPropsAllowPushNotification(user, channelNotifyProps, post, false) { - t.Fatal("Should have returned true") - } - - if !DoesNotifyPropsAllowPushNotification(user, channelNotifyProps, post, true) { - t.Fatal("Should have returned true") - } - - // WHEN default is ALL and channel is MENTION - userNotifyProps[model.PUSH_NOTIFY_PROP] = model.USER_NOTIFY_ALL - user.NotifyProps = userNotifyProps - channelNotifyProps[model.PUSH_NOTIFY_PROP] = model.CHANNEL_NOTIFY_MENTION - if DoesNotifyPropsAllowPushNotification(user, channelNotifyProps, post, false) { - t.Fatal("Should have returned false") - } - - if !DoesNotifyPropsAllowPushNotification(user, channelNotifyProps, post, true) { - t.Fatal("Should have returned true") - } - - // WHEN default is MENTION and channel is MENTION - userNotifyProps[model.PUSH_NOTIFY_PROP] = model.USER_NOTIFY_MENTION - user.NotifyProps = userNotifyProps - channelNotifyProps[model.PUSH_NOTIFY_PROP] = model.CHANNEL_NOTIFY_MENTION - if DoesNotifyPropsAllowPushNotification(user, channelNotifyProps, post, false) { - t.Fatal("Should have returned false") - } - - if !DoesNotifyPropsAllowPushNotification(user, channelNotifyProps, post, true) { - t.Fatal("Should have returned true") - } - - // WHEN default is NONE and channel is MENTION - userNotifyProps[model.PUSH_NOTIFY_PROP] = model.USER_NOTIFY_NONE - user.NotifyProps = userNotifyProps - channelNotifyProps[model.PUSH_NOTIFY_PROP] = model.CHANNEL_NOTIFY_MENTION - if DoesNotifyPropsAllowPushNotification(user, channelNotifyProps, post, false) { - t.Fatal("Should have returned false") - } - - if !DoesNotifyPropsAllowPushNotification(user, channelNotifyProps, post, true) { - t.Fatal("Should have returned true") - } - - // WHEN default is ALL and channel is NONE - userNotifyProps[model.PUSH_NOTIFY_PROP] = model.USER_NOTIFY_ALL - user.NotifyProps = userNotifyProps - channelNotifyProps[model.PUSH_NOTIFY_PROP] = model.CHANNEL_NOTIFY_NONE - if DoesNotifyPropsAllowPushNotification(user, channelNotifyProps, post, false) { - t.Fatal("Should have returned false") - } - - if DoesNotifyPropsAllowPushNotification(user, channelNotifyProps, post, true) { - t.Fatal("Should have returned false") - } - - // WHEN default is MENTION and channel is NONE - userNotifyProps[model.PUSH_NOTIFY_PROP] = model.USER_NOTIFY_MENTION - user.NotifyProps = userNotifyProps - channelNotifyProps[model.PUSH_NOTIFY_PROP] = model.CHANNEL_NOTIFY_NONE - if DoesNotifyPropsAllowPushNotification(user, channelNotifyProps, post, false) { - t.Fatal("Should have returned false") - } - - if DoesNotifyPropsAllowPushNotification(user, channelNotifyProps, post, true) { - t.Fatal("Should have returned false") - } - - // WHEN default is NONE and channel is NONE - userNotifyProps[model.PUSH_NOTIFY_PROP] = model.USER_NOTIFY_NONE - user.NotifyProps = userNotifyProps - channelNotifyProps[model.PUSH_NOTIFY_PROP] = model.CHANNEL_NOTIFY_NONE - if DoesNotifyPropsAllowPushNotification(user, channelNotifyProps, post, false) { - t.Fatal("Should have returned false") - } - - if DoesNotifyPropsAllowPushNotification(user, channelNotifyProps, post, true) { - t.Fatal("Should have returned false") - } - - // WHEN default is ALL and channel is MUTED - userNotifyProps[model.PUSH_NOTIFY_PROP] = model.USER_NOTIFY_ALL - user.NotifyProps = userNotifyProps - channelNotifyProps[model.MARK_UNREAD_NOTIFY_PROP] = model.CHANNEL_MARK_UNREAD_MENTION - if DoesNotifyPropsAllowPushNotification(user, channelNotifyProps, post, false) { - t.Fatal("Should have returned false") - } -} - -func TestDoesStatusAllowPushNotification(t *testing.T) { - userNotifyProps := make(map[string]string) - userId := model.NewId() - channelId := model.NewId() - - offline := &model.Status{UserId: userId, Status: model.STATUS_OFFLINE, Manual: false, LastActivityAt: 0, ActiveChannel: ""} - away := &model.Status{UserId: userId, Status: model.STATUS_AWAY, Manual: false, LastActivityAt: 0, ActiveChannel: ""} - online := &model.Status{UserId: userId, Status: model.STATUS_ONLINE, Manual: false, LastActivityAt: model.GetMillis(), ActiveChannel: ""} - dnd := &model.Status{UserId: userId, Status: model.STATUS_DND, Manual: true, LastActivityAt: model.GetMillis(), ActiveChannel: ""} - - userNotifyProps["push_status"] = model.STATUS_ONLINE - // WHEN props is ONLINE and user is offline - if !DoesStatusAllowPushNotification(userNotifyProps, offline, channelId) { - t.Fatal("Should have been true") - } - - if !DoesStatusAllowPushNotification(userNotifyProps, offline, "") { - t.Fatal("Should have been true") - } - - // WHEN props is ONLINE and user is away - if !DoesStatusAllowPushNotification(userNotifyProps, away, channelId) { - t.Fatal("Should have been true") - } - - if !DoesStatusAllowPushNotification(userNotifyProps, away, "") { - t.Fatal("Should have been true") - } - - // WHEN props is ONLINE and user is online - if !DoesStatusAllowPushNotification(userNotifyProps, online, channelId) { - t.Fatal("Should have been true") - } - - if DoesStatusAllowPushNotification(userNotifyProps, online, "") { - t.Fatal("Should have been false") - } - - // WHEN props is ONLINE and user is dnd - if DoesStatusAllowPushNotification(userNotifyProps, dnd, channelId) { - t.Fatal("Should have been false") - } - - if DoesStatusAllowPushNotification(userNotifyProps, dnd, "") { - t.Fatal("Should have been false") - } - - userNotifyProps["push_status"] = model.STATUS_AWAY - // WHEN props is AWAY and user is offline - if !DoesStatusAllowPushNotification(userNotifyProps, offline, channelId) { - t.Fatal("Should have been true") - } - - if !DoesStatusAllowPushNotification(userNotifyProps, offline, "") { - t.Fatal("Should have been true") - } - - // WHEN props is AWAY and user is away - if !DoesStatusAllowPushNotification(userNotifyProps, away, channelId) { - t.Fatal("Should have been true") - } - - if !DoesStatusAllowPushNotification(userNotifyProps, away, "") { - t.Fatal("Should have been true") - } - - // WHEN props is AWAY and user is online - if DoesStatusAllowPushNotification(userNotifyProps, online, channelId) { - t.Fatal("Should have been false") - } - - if DoesStatusAllowPushNotification(userNotifyProps, online, "") { - t.Fatal("Should have been false") - } - - // WHEN props is AWAY and user is dnd - if DoesStatusAllowPushNotification(userNotifyProps, dnd, channelId) { - t.Fatal("Should have been false") - } - - if DoesStatusAllowPushNotification(userNotifyProps, dnd, "") { - t.Fatal("Should have been false") - } - - userNotifyProps["push_status"] = model.STATUS_OFFLINE - // WHEN props is OFFLINE and user is offline - if !DoesStatusAllowPushNotification(userNotifyProps, offline, channelId) { - t.Fatal("Should have been true") - } - - if !DoesStatusAllowPushNotification(userNotifyProps, offline, "") { - t.Fatal("Should have been true") - } - - // WHEN props is OFFLINE and user is away - if DoesStatusAllowPushNotification(userNotifyProps, away, channelId) { - t.Fatal("Should have been false") - } - - if DoesStatusAllowPushNotification(userNotifyProps, away, "") { - t.Fatal("Should have been false") - } - - // WHEN props is OFFLINE and user is online - if DoesStatusAllowPushNotification(userNotifyProps, online, channelId) { - t.Fatal("Should have been false") - } - - if DoesStatusAllowPushNotification(userNotifyProps, online, "") { - t.Fatal("Should have been false") - } - - // WHEN props is OFFLINE and user is dnd - if DoesStatusAllowPushNotification(userNotifyProps, dnd, channelId) { - t.Fatal("Should have been false") - } - - if DoesStatusAllowPushNotification(userNotifyProps, dnd, "") { - t.Fatal("Should have been false") - } - -} - -func TestGetDirectMessageNotificationEmailSubject(t *testing.T) { - th := Setup() - defer th.TearDown() - - expectedPrefix := "[http://localhost:8065] New Direct Message from @sender on" - user := &model.User{} - post := &model.Post{ - CreateAt: 1501804801000, - } - translateFunc := utils.GetUserTranslations("en") - subject := getDirectMessageNotificationEmailSubject(user, post, translateFunc, "http://localhost:8065", "sender", true) - if !strings.HasPrefix(subject, expectedPrefix) { - t.Fatal("Expected subject line prefix '" + expectedPrefix + "', got " + subject) - } -} - -func TestGetGroupMessageNotificationEmailSubjectFull(t *testing.T) { - th := Setup() - defer th.TearDown() - - expectedPrefix := "[http://localhost:8065] New Group Message in sender on" - user := &model.User{} - post := &model.Post{ - CreateAt: 1501804801000, - } - translateFunc := utils.GetUserTranslations("en") - emailNotificationContentsType := model.EMAIL_NOTIFICATION_CONTENTS_FULL - subject := getGroupMessageNotificationEmailSubject(user, post, translateFunc, "http://localhost:8065", "sender", emailNotificationContentsType, true) - if !strings.HasPrefix(subject, expectedPrefix) { - t.Fatal("Expected subject line prefix '" + expectedPrefix + "', got " + subject) - } -} - -func TestGetGroupMessageNotificationEmailSubjectGeneric(t *testing.T) { - th := Setup() - defer th.TearDown() - - expectedPrefix := "[http://localhost:8065] New Group Message on" - user := &model.User{} - post := &model.Post{ - CreateAt: 1501804801000, - } - translateFunc := utils.GetUserTranslations("en") - emailNotificationContentsType := model.EMAIL_NOTIFICATION_CONTENTS_GENERIC - subject := getGroupMessageNotificationEmailSubject(user, post, translateFunc, "http://localhost:8065", "sender", emailNotificationContentsType, true) - if !strings.HasPrefix(subject, expectedPrefix) { - t.Fatal("Expected subject line prefix '" + expectedPrefix + "', got " + subject) - } -} - -func TestGetNotificationEmailSubject(t *testing.T) { - th := Setup() - defer th.TearDown() - - expectedPrefix := "[http://localhost:8065] Notification in team on" - user := &model.User{} - post := &model.Post{ - CreateAt: 1501804801000, - } - translateFunc := utils.GetUserTranslations("en") - subject := getNotificationEmailSubject(user, post, translateFunc, "http://localhost:8065", "team", true) - if !strings.HasPrefix(subject, expectedPrefix) { - t.Fatal("Expected subject line prefix '" + expectedPrefix + "', got " + subject) - } -} - -func TestGetNotificationEmailBodyFullNotificationPublicChannel(t *testing.T) { - th := Setup() - defer th.TearDown() - - recipient := &model.User{} - post := &model.Post{ - Message: "This is the message", - } - channel := &model.Channel{ - DisplayName: "ChannelName", - Type: model.CHANNEL_OPEN, - } - channelName := "ChannelName" - senderName := "sender" - teamName := "team" - teamURL := "http://localhost:8065/" + teamName - emailNotificationContentsType := model.EMAIL_NOTIFICATION_CONTENTS_FULL - translateFunc := utils.GetUserTranslations("en") - - body := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc) - if !strings.Contains(body, "You have a new notification.") { - t.Fatal("Expected email text 'You have a new notification. Got " + body) - } - if !strings.Contains(body, "Channel: "+channel.DisplayName) { - t.Fatal("Expected email text 'Channel: " + channel.DisplayName + "'. Got " + body) - } - if !strings.Contains(body, "@"+senderName+" - ") { - t.Fatal("Expected email text '@" + senderName + " - '. Got " + body) - } - if !strings.Contains(body, post.Message) { - t.Fatal("Expected email text '" + post.Message + "'. Got " + body) - } - if !strings.Contains(body, teamURL) { - t.Fatal("Expected email text '" + teamURL + "'. Got " + body) - } -} - -func TestGetNotificationEmailBodyFullNotificationGroupChannel(t *testing.T) { - th := Setup() - defer th.TearDown() - - recipient := &model.User{} - post := &model.Post{ - Message: "This is the message", - } - channel := &model.Channel{ - DisplayName: "ChannelName", - Type: model.CHANNEL_GROUP, - } - channelName := "ChannelName" - senderName := "sender" - teamName := "team" - teamURL := "http://localhost:8065/" + teamName - emailNotificationContentsType := model.EMAIL_NOTIFICATION_CONTENTS_FULL - translateFunc := utils.GetUserTranslations("en") - - body := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc) - if !strings.Contains(body, "You have a new Group Message.") { - t.Fatal("Expected email text 'You have a new Group Message. Got " + body) - } - if !strings.Contains(body, "Channel: ChannelName") { - t.Fatal("Expected email text 'Channel: ChannelName'. Got " + body) - } - if !strings.Contains(body, "@"+senderName+" - ") { - t.Fatal("Expected email text '@" + senderName + " - '. Got " + body) - } - if !strings.Contains(body, post.Message) { - t.Fatal("Expected email text '" + post.Message + "'. Got " + body) - } - if !strings.Contains(body, teamURL) { - t.Fatal("Expected email text '" + teamURL + "'. Got " + body) - } -} - -func TestGetNotificationEmailBodyFullNotificationPrivateChannel(t *testing.T) { - th := Setup() - defer th.TearDown() - - recipient := &model.User{} - post := &model.Post{ - Message: "This is the message", - } - channel := &model.Channel{ - DisplayName: "ChannelName", - Type: model.CHANNEL_PRIVATE, - } - channelName := "ChannelName" - senderName := "sender" - teamName := "team" - teamURL := "http://localhost:8065/" + teamName - emailNotificationContentsType := model.EMAIL_NOTIFICATION_CONTENTS_FULL - translateFunc := utils.GetUserTranslations("en") - - body := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc) - if !strings.Contains(body, "You have a new notification.") { - t.Fatal("Expected email text 'You have a new notification. Got " + body) - } - if !strings.Contains(body, "Channel: "+channel.DisplayName) { - t.Fatal("Expected email text 'Channel: " + channel.DisplayName + "'. Got " + body) - } - if !strings.Contains(body, "@"+senderName+" - ") { - t.Fatal("Expected email text '@" + senderName + " - '. Got " + body) - } - if !strings.Contains(body, post.Message) { - t.Fatal("Expected email text '" + post.Message + "'. Got " + body) - } - if !strings.Contains(body, teamURL) { - t.Fatal("Expected email text '" + teamURL + "'. Got " + body) - } -} - -func TestGetNotificationEmailBodyFullNotificationDirectChannel(t *testing.T) { - th := Setup() - defer th.TearDown() - - recipient := &model.User{} - post := &model.Post{ - Message: "This is the message", - } - channel := &model.Channel{ - DisplayName: "ChannelName", - Type: model.CHANNEL_DIRECT, - } - channelName := "ChannelName" - senderName := "sender" - teamName := "team" - teamURL := "http://localhost:8065/" + teamName - emailNotificationContentsType := model.EMAIL_NOTIFICATION_CONTENTS_FULL - translateFunc := utils.GetUserTranslations("en") - - body := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc) - if !strings.Contains(body, "You have a new Direct Message.") { - t.Fatal("Expected email text 'You have a new Direct Message. Got " + body) - } - if !strings.Contains(body, "@"+senderName+" - ") { - t.Fatal("Expected email text '@" + senderName + " - '. Got " + body) - } - if !strings.Contains(body, post.Message) { - t.Fatal("Expected email text '" + post.Message + "'. Got " + body) - } - if !strings.Contains(body, teamURL) { - t.Fatal("Expected email text '" + teamURL + "'. Got " + body) - } -} - -func TestGetNotificationEmailBodyFullNotificationLocaleTimeWithTimezone(t *testing.T) { - th := Setup() - defer th.TearDown() - - recipient := &model.User{ - Timezone: model.DefaultUserTimezone(), - } - recipient.Timezone["automaticTimezone"] = "America/New_York" - post := &model.Post{ - CreateAt: 1524663790000, - Message: "This is the message", - } - channel := &model.Channel{ - DisplayName: "ChannelName", - Type: model.CHANNEL_DIRECT, - } - channelName := "ChannelName" - senderName := "sender" - teamName := "team" - teamURL := "http://localhost:8065/" + teamName - emailNotificationContentsType := model.EMAIL_NOTIFICATION_CONTENTS_FULL - translateFunc := utils.GetUserTranslations("en") - - body := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, false, translateFunc) - r, _ := regexp.Compile("E([S|D]+)T") - zone := r.FindString(body) - if !strings.Contains(body, "sender - 9:43 AM "+zone+", April 25") { - t.Fatal("Expected email text 'sender - 9:43 AM " + zone + ", April 25'. Got " + body) - } -} - -func TestGetNotificationEmailBodyFullNotificationLocaleTimeNoTimezone(t *testing.T) { - th := Setup() - defer th.TearDown() - - recipient := &model.User{ - Timezone: model.DefaultUserTimezone(), - } - post := &model.Post{ - CreateAt: 1524681000000, - Message: "This is the message", - } - channel := &model.Channel{ - DisplayName: "ChannelName", - Type: model.CHANNEL_DIRECT, - } - channelName := "ChannelName" - senderName := "sender" - teamName := "team" - teamURL := "http://localhost:8065/" + teamName - emailNotificationContentsType := model.EMAIL_NOTIFICATION_CONTENTS_FULL - translateFunc := utils.GetUserTranslations("en") - - tm := time.Unix(post.CreateAt/1000, 0) - zone, _ := tm.Zone() - - formattedTime := formattedPostTime{ - Time: tm, - Year: fmt.Sprintf("%d", tm.Year()), - Month: translateFunc(tm.Month().String()), - Day: fmt.Sprintf("%d", tm.Day()), - Hour: fmt.Sprintf("%02d", tm.Hour()), - Minute: fmt.Sprintf("%02d", tm.Minute()), - TimeZone: zone, - } - - body := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc) - postTimeLine := fmt.Sprintf("sender - %s:%s %s, %s %s", formattedTime.Hour, formattedTime.Minute, formattedTime.TimeZone, formattedTime.Month, formattedTime.Day) - if !strings.Contains(body, postTimeLine) { - t.Fatal("Expected email text '" + postTimeLine + " '. Got " + body) - } -} - -func TestGetNotificationEmailBodyFullNotificationLocaleTime12Hour(t *testing.T) { - th := Setup() - defer th.TearDown() - - recipient := &model.User{ - Timezone: model.DefaultUserTimezone(), - } - recipient.Timezone["automaticTimezone"] = "America/New_York" - post := &model.Post{ - CreateAt: 1524681000000, // 1524681000 // 1524681000000 - Message: "This is the message", - } - channel := &model.Channel{ - DisplayName: "ChannelName", - Type: model.CHANNEL_DIRECT, - } - channelName := "ChannelName" - senderName := "sender" - teamName := "team" - teamURL := "http://localhost:8065/" + teamName - emailNotificationContentsType := model.EMAIL_NOTIFICATION_CONTENTS_FULL - translateFunc := utils.GetUserTranslations("en") - - body := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, false, translateFunc) - if !strings.Contains(body, "sender - 2:30 PM") { - t.Fatal("Expected email text 'sender - 2:30 PM'. Got " + body) - } - if !strings.Contains(body, "April 25") { - t.Fatal("Expected email text 'April 25'. Got " + body) - } -} - -func TestGetNotificationEmailBodyFullNotificationLocaleTime24Hour(t *testing.T) { - th := Setup() - defer th.TearDown() - - recipient := &model.User{ - Timezone: model.DefaultUserTimezone(), - } - recipient.Timezone["automaticTimezone"] = "America/New_York" - post := &model.Post{ - CreateAt: 1524681000000, - Message: "This is the message", - } - channel := &model.Channel{ - DisplayName: "ChannelName", - Type: model.CHANNEL_DIRECT, - } - channelName := "ChannelName" - senderName := "sender" - teamName := "team" - teamURL := "http://localhost:8065/" + teamName - emailNotificationContentsType := model.EMAIL_NOTIFICATION_CONTENTS_FULL - translateFunc := utils.GetUserTranslations("en") - - body := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc) - if !strings.Contains(body, "sender - 14:30") { - t.Fatal("Expected email text 'sender - 14:30'. Got " + body) - } - if !strings.Contains(body, "April 25") { - t.Fatal("Expected email text 'April 25'. Got " + body) - } -} - -// from here -func TestGetNotificationEmailBodyGenericNotificationPublicChannel(t *testing.T) { - th := Setup() - defer th.TearDown() - - recipient := &model.User{} - post := &model.Post{ - Message: "This is the message", - } - channel := &model.Channel{ - DisplayName: "ChannelName", - Type: model.CHANNEL_OPEN, - } - channelName := "ChannelName" - senderName := "sender" - teamName := "team" - teamURL := "http://localhost:8065/" + teamName - emailNotificationContentsType := model.EMAIL_NOTIFICATION_CONTENTS_GENERIC - translateFunc := utils.GetUserTranslations("en") - - body := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc) - if !strings.Contains(body, "You have a new notification from @"+senderName) { - t.Fatal("Expected email text 'You have a new notification from @" + senderName + "'. Got " + body) - } - if strings.Contains(body, "Channel: "+channel.DisplayName) { - t.Fatal("Did not expect email text 'Channel: " + channel.DisplayName + "'. Got " + body) - } - if strings.Contains(body, post.Message) { - t.Fatal("Did not expect email text '" + post.Message + "'. Got " + body) - } - if !strings.Contains(body, teamURL) { - t.Fatal("Expected email text '" + teamURL + "'. Got " + body) - } -} - -func TestGetNotificationEmailBodyGenericNotificationGroupChannel(t *testing.T) { - th := Setup() - defer th.TearDown() - - recipient := &model.User{} - post := &model.Post{ - Message: "This is the message", - } - channel := &model.Channel{ - DisplayName: "ChannelName", - Type: model.CHANNEL_GROUP, - } - channelName := "ChannelName" - senderName := "sender" - teamName := "team" - teamURL := "http://localhost:8065/" + teamName - emailNotificationContentsType := model.EMAIL_NOTIFICATION_CONTENTS_GENERIC - translateFunc := utils.GetUserTranslations("en") - - body := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc) - if !strings.Contains(body, "You have a new Group Message from @"+senderName) { - t.Fatal("Expected email text 'You have a new Group Message from @" + senderName + "'. Got " + body) - } - if strings.Contains(body, "CHANNEL: "+channel.DisplayName) { - t.Fatal("Did not expect email text 'CHANNEL: " + channel.DisplayName + "'. Got " + body) - } - if strings.Contains(body, post.Message) { - t.Fatal("Did not expect email text '" + post.Message + "'. Got " + body) - } - if !strings.Contains(body, teamURL) { - t.Fatal("Expected email text '" + teamURL + "'. Got " + body) - } -} - -func TestGetNotificationEmailBodyGenericNotificationPrivateChannel(t *testing.T) { - th := Setup() - defer th.TearDown() - - recipient := &model.User{} - post := &model.Post{ - Message: "This is the message", - } - channel := &model.Channel{ - DisplayName: "ChannelName", - Type: model.CHANNEL_PRIVATE, - } - channelName := "ChannelName" - senderName := "sender" - teamName := "team" - teamURL := "http://localhost:8065/" + teamName - emailNotificationContentsType := model.EMAIL_NOTIFICATION_CONTENTS_GENERIC - translateFunc := utils.GetUserTranslations("en") - - body := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc) - if !strings.Contains(body, "You have a new notification from @"+senderName) { - t.Fatal("Expected email text 'You have a new notification from @" + senderName + "'. Got " + body) - } - if strings.Contains(body, "CHANNEL: "+channel.DisplayName) { - t.Fatal("Did not expect email text 'CHANNEL: " + channel.DisplayName + "'. Got " + body) - } - if strings.Contains(body, post.Message) { - t.Fatal("Did not expect email text '" + post.Message + "'. Got " + body) - } - if !strings.Contains(body, teamURL) { - t.Fatal("Expected email text '" + teamURL + "'. Got " + body) - } -} - -func TestGetNotificationEmailBodyGenericNotificationDirectChannel(t *testing.T) { - th := Setup() - defer th.TearDown() - - recipient := &model.User{} - post := &model.Post{ - Message: "This is the message", - } - channel := &model.Channel{ - DisplayName: "ChannelName", - Type: model.CHANNEL_DIRECT, - } - channelName := "ChannelName" - senderName := "sender" - teamName := "team" - teamURL := "http://localhost:8065/" + teamName - emailNotificationContentsType := model.EMAIL_NOTIFICATION_CONTENTS_GENERIC - translateFunc := utils.GetUserTranslations("en") - - body := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc) - if !strings.Contains(body, "You have a new Direct Message from @"+senderName) { - t.Fatal("Expected email text 'You have a new Direct Message from @" + senderName + "'. Got " + body) - } - if strings.Contains(body, "CHANNEL: "+channel.DisplayName) { - t.Fatal("Did not expect email text 'CHANNEL: " + channel.DisplayName + "'. Got " + body) - } - if strings.Contains(body, post.Message) { - t.Fatal("Did not expect email text '" + post.Message + "'. Got " + body) - } - if !strings.Contains(body, teamURL) { - t.Fatal("Expected email text '" + teamURL + "'. Got " + body) - } -} - -func TestGetPushNotificationMessage(t *testing.T) { - th := Setup() - defer th.TearDown() - - for name, tc := range map[string]struct { - Message string - explicitMention bool - channelWideMention bool - HasFiles bool - replyToThreadType string - Locale string - PushNotificationContents string - ChannelType string - - ExpectedMessage string - }{ - "full message, public channel, no mention": { - Message: "this is a message", - ChannelType: model.CHANNEL_OPEN, - ExpectedMessage: "@user: this is a message", - }, - "full message, public channel, mention": { - Message: "this is a message", - explicitMention: true, - ChannelType: model.CHANNEL_OPEN, - ExpectedMessage: "@user: this is a message", - }, - "full message, public channel, channel wide mention": { - Message: "this is a message", - channelWideMention: true, - ChannelType: model.CHANNEL_OPEN, - ExpectedMessage: "@user: this is a message", - }, - "full message, public channel, commented on post": { - Message: "this is a message", - replyToThreadType: THREAD_ROOT, - ChannelType: model.CHANNEL_OPEN, - ExpectedMessage: "@user: this is a message", - }, - "full message, public channel, commented on thread": { - Message: "this is a message", - replyToThreadType: THREAD_ANY, - ChannelType: model.CHANNEL_OPEN, - ExpectedMessage: "@user: this is a message", - }, - "full message, private channel, no mention": { - Message: "this is a message", - ChannelType: model.CHANNEL_PRIVATE, - ExpectedMessage: "@user: this is a message", - }, - "full message, private channel, mention": { - Message: "this is a message", - explicitMention: true, - ChannelType: model.CHANNEL_PRIVATE, - ExpectedMessage: "@user: this is a message", - }, - "full message, private channel, commented on post": { - Message: "this is a message", - replyToThreadType: THREAD_ROOT, - ChannelType: model.CHANNEL_PRIVATE, - ExpectedMessage: "@user: this is a message", - }, - "full message, private channel, commented on thread": { - Message: "this is a message", - replyToThreadType: THREAD_ANY, - ChannelType: model.CHANNEL_PRIVATE, - ExpectedMessage: "@user: this is a message", - }, - "full message, group message channel, no mention": { - Message: "this is a message", - ChannelType: model.CHANNEL_GROUP, - ExpectedMessage: "@user: this is a message", - }, - "full message, group message channel, mention": { - Message: "this is a message", - explicitMention: true, - ChannelType: model.CHANNEL_GROUP, - ExpectedMessage: "@user: this is a message", - }, - "full message, group message channel, commented on post": { - Message: "this is a message", - replyToThreadType: THREAD_ROOT, - ChannelType: model.CHANNEL_GROUP, - ExpectedMessage: "@user: this is a message", - }, - "full message, group message channel, commented on thread": { - Message: "this is a message", - replyToThreadType: THREAD_ANY, - ChannelType: model.CHANNEL_GROUP, - ExpectedMessage: "@user: this is a message", - }, - "full message, direct message channel, no mention": { - Message: "this is a message", - ChannelType: model.CHANNEL_DIRECT, - ExpectedMessage: "this is a message", - }, - "full message, direct message channel, mention": { - Message: "this is a message", - explicitMention: true, - ChannelType: model.CHANNEL_DIRECT, - ExpectedMessage: "this is a message", - }, - "full message, direct message channel, commented on post": { - Message: "this is a message", - replyToThreadType: THREAD_ROOT, - ChannelType: model.CHANNEL_DIRECT, - ExpectedMessage: "this is a message", - }, - "full message, direct message channel, commented on thread": { - Message: "this is a message", - replyToThreadType: THREAD_ANY, - ChannelType: model.CHANNEL_DIRECT, - ExpectedMessage: "this is a message", - }, - "generic message with channel, public channel, no mention": { - Message: "this is a message", - PushNotificationContents: model.GENERIC_NOTIFICATION, - ChannelType: model.CHANNEL_OPEN, - ExpectedMessage: "@user posted a message.", - }, - "generic message with channel, public channel, mention": { - Message: "this is a message", - explicitMention: true, - PushNotificationContents: model.GENERIC_NOTIFICATION, - ChannelType: model.CHANNEL_OPEN, - ExpectedMessage: "@user mentioned you.", - }, - "generic message with channel, public channel, channel wide mention": { - Message: "this is a message", - channelWideMention: true, - PushNotificationContents: model.GENERIC_NOTIFICATION, - ChannelType: model.CHANNEL_OPEN, - ExpectedMessage: "@user notified the channel.", - }, - "generic message, public channel, commented on post": { - Message: "this is a message", - replyToThreadType: THREAD_ROOT, - PushNotificationContents: model.GENERIC_NOTIFICATION, - ChannelType: model.CHANNEL_OPEN, - ExpectedMessage: "@user commented on your post.", - }, - "generic message, public channel, commented on thread": { - Message: "this is a message", - replyToThreadType: THREAD_ANY, - PushNotificationContents: model.GENERIC_NOTIFICATION, - ChannelType: model.CHANNEL_OPEN, - ExpectedMessage: "@user commented on a thread you participated in.", - }, - "generic message with channel, private channel, no mention": { - Message: "this is a message", - PushNotificationContents: model.GENERIC_NOTIFICATION, - ChannelType: model.CHANNEL_PRIVATE, - ExpectedMessage: "@user posted a message.", - }, - "generic message with channel, private channel, mention": { - Message: "this is a message", - explicitMention: true, - PushNotificationContents: model.GENERIC_NOTIFICATION, - ChannelType: model.CHANNEL_PRIVATE, - ExpectedMessage: "@user mentioned you.", - }, - "generic message with channel, private channel, channel wide mention": { - Message: "this is a message", - channelWideMention: true, - PushNotificationContents: model.GENERIC_NOTIFICATION, - ChannelType: model.CHANNEL_PRIVATE, - ExpectedMessage: "@user notified the channel.", - }, - "generic message, public private, commented on post": { - Message: "this is a message", - replyToThreadType: THREAD_ROOT, - PushNotificationContents: model.GENERIC_NOTIFICATION, - ChannelType: model.CHANNEL_PRIVATE, - ExpectedMessage: "@user commented on your post.", - }, - "generic message, public private, commented on thread": { - Message: "this is a message", - replyToThreadType: THREAD_ANY, - PushNotificationContents: model.GENERIC_NOTIFICATION, - ChannelType: model.CHANNEL_PRIVATE, - ExpectedMessage: "@user commented on a thread you participated in.", - }, - "generic message with channel, group message channel, no mention": { - Message: "this is a message", - PushNotificationContents: model.GENERIC_NOTIFICATION, - ChannelType: model.CHANNEL_GROUP, - ExpectedMessage: "@user posted a message.", - }, - "generic message with channel, group message channel, mention": { - Message: "this is a message", - explicitMention: true, - PushNotificationContents: model.GENERIC_NOTIFICATION, - ChannelType: model.CHANNEL_GROUP, - ExpectedMessage: "@user mentioned you.", - }, - "generic message with channel, group message channel, channel wide mention": { - Message: "this is a message", - channelWideMention: true, - PushNotificationContents: model.GENERIC_NOTIFICATION, - ChannelType: model.CHANNEL_GROUP, - ExpectedMessage: "@user notified the channel.", - }, - "generic message, group message channel, commented on post": { - Message: "this is a message", - replyToThreadType: THREAD_ROOT, - PushNotificationContents: model.GENERIC_NOTIFICATION, - ChannelType: model.CHANNEL_GROUP, - ExpectedMessage: "@user commented on your post.", - }, - "generic message, group message channel, commented on thread": { - Message: "this is a message", - replyToThreadType: THREAD_ANY, - PushNotificationContents: model.GENERIC_NOTIFICATION, - ChannelType: model.CHANNEL_GROUP, - ExpectedMessage: "@user commented on a thread you participated in.", - }, - "generic message with channel, direct message channel, no mention": { - Message: "this is a message", - PushNotificationContents: model.GENERIC_NOTIFICATION, - ChannelType: model.CHANNEL_DIRECT, - ExpectedMessage: "sent you a message.", - }, - "generic message with channel, direct message channel, mention": { - Message: "this is a message", - explicitMention: true, - PushNotificationContents: model.GENERIC_NOTIFICATION, - ChannelType: model.CHANNEL_DIRECT, - ExpectedMessage: "sent you a message.", - }, - "generic message with channel, direct message channel, channel wide mention": { - Message: "this is a message", - channelWideMention: true, - PushNotificationContents: model.GENERIC_NOTIFICATION, - ChannelType: model.CHANNEL_DIRECT, - ExpectedMessage: "sent you a message.", - }, - "generic message, direct message channel, commented on post": { - Message: "this is a message", - replyToThreadType: THREAD_ROOT, - PushNotificationContents: model.GENERIC_NOTIFICATION, - ChannelType: model.CHANNEL_DIRECT, - ExpectedMessage: "sent you a message.", - }, - "generic message, direct message channel, commented on thread": { - Message: "this is a message", - replyToThreadType: THREAD_ANY, - PushNotificationContents: model.GENERIC_NOTIFICATION, - ChannelType: model.CHANNEL_DIRECT, - ExpectedMessage: "sent you a message.", - }, - "generic message without channel, public channel, no mention": { - Message: "this is a message", - PushNotificationContents: model.GENERIC_NO_CHANNEL_NOTIFICATION, - ChannelType: model.CHANNEL_OPEN, - ExpectedMessage: "@user posted a message.", - }, - "generic message without channel, public channel, mention": { - Message: "this is a message", - explicitMention: true, - PushNotificationContents: model.GENERIC_NO_CHANNEL_NOTIFICATION, - ChannelType: model.CHANNEL_OPEN, - ExpectedMessage: "@user mentioned you.", - }, - "generic message without channel, private channel, no mention": { - Message: "this is a message", - PushNotificationContents: model.GENERIC_NO_CHANNEL_NOTIFICATION, - ChannelType: model.CHANNEL_PRIVATE, - ExpectedMessage: "@user posted a message.", - }, - "generic message without channel, private channel, mention": { - Message: "this is a message", - explicitMention: true, - PushNotificationContents: model.GENERIC_NO_CHANNEL_NOTIFICATION, - ChannelType: model.CHANNEL_PRIVATE, - ExpectedMessage: "@user mentioned you.", - }, - "generic message without channel, group message channel, no mention": { - Message: "this is a message", - PushNotificationContents: model.GENERIC_NO_CHANNEL_NOTIFICATION, - ChannelType: model.CHANNEL_GROUP, - ExpectedMessage: "@user posted a message.", - }, - "generic message without channel, group message channel, mention": { - Message: "this is a message", - explicitMention: true, - PushNotificationContents: model.GENERIC_NO_CHANNEL_NOTIFICATION, - ChannelType: model.CHANNEL_GROUP, - ExpectedMessage: "@user mentioned you.", - }, - "generic message without channel, direct message channel, no mention": { - Message: "this is a message", - PushNotificationContents: model.GENERIC_NO_CHANNEL_NOTIFICATION, - ChannelType: model.CHANNEL_DIRECT, - ExpectedMessage: "sent you a message.", - }, - "generic message without channel, direct message channel, mention": { - Message: "this is a message", - explicitMention: true, - PushNotificationContents: model.GENERIC_NO_CHANNEL_NOTIFICATION, - ChannelType: model.CHANNEL_DIRECT, - ExpectedMessage: "sent you a message.", - }, - "only files, public channel": { - HasFiles: true, - ChannelType: model.CHANNEL_OPEN, - ExpectedMessage: "@user attached a file.", - }, - "only files, private channel": { - HasFiles: true, - ChannelType: model.CHANNEL_PRIVATE, - ExpectedMessage: "@user attached a file.", - }, - "only files, group message channel": { - HasFiles: true, - ChannelType: model.CHANNEL_GROUP, - ExpectedMessage: "@user attached a file.", - }, - "only files, direct message channel": { - HasFiles: true, - ChannelType: model.CHANNEL_DIRECT, - ExpectedMessage: "attached a file.", - }, - "only files without channel, public channel": { - HasFiles: true, - PushNotificationContents: model.GENERIC_NO_CHANNEL_NOTIFICATION, - ChannelType: model.CHANNEL_OPEN, - ExpectedMessage: "@user attached a file.", - }, - } { - t.Run(name, func(t *testing.T) { - locale := tc.Locale - if locale == "" { - locale = "en" - } - - pushNotificationContents := tc.PushNotificationContents - if pushNotificationContents == "" { - pushNotificationContents = model.FULL_NOTIFICATION - } - - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.EmailSettings.PushNotificationContents = pushNotificationContents - }) - - if actualMessage := th.App.getPushNotificationMessage( - tc.Message, - tc.explicitMention, - tc.channelWideMention, - tc.HasFiles, - "user", - "channel", - tc.ChannelType, - tc.replyToThreadType, - utils.GetUserTranslations(locale), - ); actualMessage != tc.ExpectedMessage { - t.Fatalf("Received incorrect push notification message `%v`, expected `%v`", actualMessage, tc.ExpectedMessage) - } - }) - } -} - func TestGetMentionsEnabledFields(t *testing.T) { attachmentWithTextAndPreText := model.SlackAttachment{ - Text: "@here with mentions", + Text: "@here with mentions", Pretext: "@Channel some comment for the channel", - } attachmentWithOutPreText := model.SlackAttachment{ -- cgit v1.2.3-1-g7c22