Full Mattermost server source with integrated Community Enterprise features. Includes vendor directory for offline/air-gapped builds. Structure: - enterprise-impl/: Enterprise feature implementations - enterprise-community/: Init files that register implementations - enterprise/: Bridge imports (community_imports.go) - vendor/: All dependencies for offline builds Build (online): go build ./cmd/mattermost Build (offline/air-gapped): go build -mod=vendor ./cmd/mattermost 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1790 lines
64 KiB
Go
1790 lines
64 KiB
Go
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
package app
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/gorilla/mux"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/mock"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/mattermost/mattermost/server/public/model"
|
|
"github.com/mattermost/mattermost/server/public/shared/i18n"
|
|
"github.com/mattermost/mattermost/server/v8/channels/app/platform"
|
|
"github.com/mattermost/mattermost/server/v8/channels/store/storetest/mocks"
|
|
"github.com/mattermost/mattermost/server/v8/channels/testlib"
|
|
"github.com/mattermost/mattermost/server/v8/config"
|
|
fmocks "github.com/mattermost/mattermost/server/v8/platform/shared/filestore/mocks"
|
|
)
|
|
|
|
func TestDoesNotifyPropsAllowPushNotification(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
tt := []struct {
|
|
name string
|
|
userNotifySetting string
|
|
channelNotifySetting string
|
|
withSystemPost bool
|
|
wasMentioned bool
|
|
isMuted bool
|
|
expected model.NotificationReason
|
|
isGM bool
|
|
}{
|
|
{
|
|
name: "When post is a System Message and has no mentions",
|
|
userNotifySetting: model.UserNotifyAll,
|
|
channelNotifySetting: "",
|
|
withSystemPost: true,
|
|
wasMentioned: false,
|
|
isMuted: false,
|
|
expected: model.NotificationReasonSystemMessage,
|
|
isGM: false,
|
|
},
|
|
{
|
|
name: "When post is a System Message and has mentions",
|
|
userNotifySetting: model.UserNotifyAll,
|
|
channelNotifySetting: "",
|
|
withSystemPost: true,
|
|
wasMentioned: true,
|
|
isMuted: false,
|
|
expected: model.NotificationReasonSystemMessage,
|
|
isGM: false,
|
|
},
|
|
{
|
|
name: "When default is ALL, no channel props is set and has no mentions",
|
|
userNotifySetting: model.UserNotifyAll,
|
|
channelNotifySetting: "",
|
|
withSystemPost: false,
|
|
wasMentioned: false,
|
|
isMuted: false,
|
|
expected: "",
|
|
isGM: false,
|
|
},
|
|
{
|
|
name: "When default is ALL, no channel props is set and has mentions",
|
|
userNotifySetting: model.UserNotifyAll,
|
|
channelNotifySetting: "",
|
|
withSystemPost: false,
|
|
wasMentioned: true,
|
|
isMuted: false,
|
|
expected: "",
|
|
isGM: false,
|
|
},
|
|
{
|
|
name: "When default is MENTION, no channel props is set and has no mentions",
|
|
userNotifySetting: model.UserNotifyMention,
|
|
channelNotifySetting: "",
|
|
withSystemPost: false,
|
|
wasMentioned: false,
|
|
isMuted: false,
|
|
expected: model.NotificationReasonNotMentioned,
|
|
isGM: false,
|
|
},
|
|
{
|
|
name: "When default is MENTION, no channel props is set and has mentions",
|
|
userNotifySetting: model.UserNotifyMention,
|
|
channelNotifySetting: "",
|
|
withSystemPost: false,
|
|
wasMentioned: true,
|
|
isMuted: false,
|
|
expected: "",
|
|
isGM: false,
|
|
},
|
|
{
|
|
name: "When default is NONE, no channel props is set and has no mentions",
|
|
userNotifySetting: model.UserNotifyNone,
|
|
channelNotifySetting: "",
|
|
withSystemPost: false,
|
|
wasMentioned: false,
|
|
isMuted: false,
|
|
expected: model.NotificationReasonLevelSetToNone,
|
|
isGM: false,
|
|
},
|
|
{
|
|
name: "When default is NONE, no channel props is set and has mentions",
|
|
userNotifySetting: model.UserNotifyNone,
|
|
channelNotifySetting: "",
|
|
withSystemPost: false,
|
|
wasMentioned: true,
|
|
isMuted: false,
|
|
expected: model.NotificationReasonLevelSetToNone,
|
|
isGM: false,
|
|
},
|
|
{
|
|
name: "When default is ALL, channel is DEFAULT and has no mentions",
|
|
userNotifySetting: model.UserNotifyAll,
|
|
channelNotifySetting: model.ChannelNotifyDefault,
|
|
withSystemPost: false,
|
|
wasMentioned: false,
|
|
isMuted: false,
|
|
expected: "",
|
|
isGM: false,
|
|
},
|
|
{
|
|
name: "When default is ALL, channel is DEFAULT and has mentions",
|
|
userNotifySetting: model.UserNotifyAll,
|
|
channelNotifySetting: model.ChannelNotifyDefault,
|
|
withSystemPost: false,
|
|
wasMentioned: true,
|
|
isMuted: false,
|
|
expected: "",
|
|
isGM: false,
|
|
},
|
|
{
|
|
name: "When default is MENTION, channel is DEFAULT and has no mentions",
|
|
userNotifySetting: model.UserNotifyMention,
|
|
channelNotifySetting: model.ChannelNotifyDefault,
|
|
withSystemPost: false,
|
|
wasMentioned: false,
|
|
isMuted: false,
|
|
expected: model.NotificationReasonNotMentioned,
|
|
isGM: false,
|
|
},
|
|
{
|
|
name: "When default is MENTION, channel is DEFAULT and has mentions",
|
|
userNotifySetting: model.UserNotifyMention,
|
|
channelNotifySetting: model.ChannelNotifyDefault,
|
|
withSystemPost: false,
|
|
wasMentioned: true,
|
|
isMuted: false,
|
|
expected: "",
|
|
isGM: false,
|
|
},
|
|
{
|
|
name: "When default is NONE, channel is DEFAULT and has no mentions",
|
|
userNotifySetting: model.UserNotifyNone,
|
|
channelNotifySetting: model.ChannelNotifyDefault,
|
|
withSystemPost: false,
|
|
wasMentioned: false,
|
|
isMuted: false,
|
|
expected: model.NotificationReasonLevelSetToNone,
|
|
isGM: false,
|
|
},
|
|
{
|
|
name: "When default is NONE, channel is DEFAULT and has mentions",
|
|
userNotifySetting: model.UserNotifyNone,
|
|
channelNotifySetting: model.ChannelNotifyDefault,
|
|
withSystemPost: false,
|
|
wasMentioned: true,
|
|
isMuted: false,
|
|
expected: model.NotificationReasonLevelSetToNone,
|
|
isGM: false,
|
|
},
|
|
{
|
|
name: "When default is ALL, channel is ALL and has no mentions",
|
|
userNotifySetting: model.UserNotifyAll,
|
|
channelNotifySetting: model.ChannelNotifyAll,
|
|
withSystemPost: false,
|
|
wasMentioned: false,
|
|
isMuted: false,
|
|
expected: "",
|
|
isGM: false,
|
|
},
|
|
{
|
|
name: "When default is ALL, channel is ALL and has mentions",
|
|
userNotifySetting: model.UserNotifyAll,
|
|
channelNotifySetting: model.ChannelNotifyAll,
|
|
withSystemPost: false,
|
|
wasMentioned: true,
|
|
isMuted: false,
|
|
expected: "",
|
|
isGM: false,
|
|
},
|
|
{
|
|
name: "When default is MENTION, channel is ALL and has no mentions",
|
|
userNotifySetting: model.UserNotifyMention,
|
|
channelNotifySetting: model.ChannelNotifyAll,
|
|
withSystemPost: false,
|
|
wasMentioned: false,
|
|
isMuted: false,
|
|
expected: "",
|
|
isGM: false,
|
|
},
|
|
{
|
|
name: "When default is MENTION, channel is ALL and has mentions",
|
|
userNotifySetting: model.UserNotifyMention,
|
|
channelNotifySetting: model.ChannelNotifyAll,
|
|
withSystemPost: false,
|
|
wasMentioned: true,
|
|
isMuted: false,
|
|
expected: "",
|
|
isGM: false,
|
|
},
|
|
{
|
|
name: "When default is NONE, channel is ALL and has no mentions",
|
|
userNotifySetting: model.UserNotifyNone,
|
|
channelNotifySetting: model.ChannelNotifyAll,
|
|
withSystemPost: false,
|
|
wasMentioned: false,
|
|
isMuted: false,
|
|
expected: "",
|
|
isGM: false,
|
|
},
|
|
{
|
|
name: "When default is NONE, channel is ALL and has mentions",
|
|
userNotifySetting: model.UserNotifyNone,
|
|
channelNotifySetting: model.ChannelNotifyAll,
|
|
withSystemPost: false,
|
|
wasMentioned: true,
|
|
isMuted: false,
|
|
expected: "",
|
|
isGM: false,
|
|
},
|
|
{
|
|
name: "When default is ALL, channel is MENTION and has no mentions",
|
|
userNotifySetting: model.UserNotifyAll,
|
|
channelNotifySetting: model.ChannelNotifyMention,
|
|
withSystemPost: false,
|
|
wasMentioned: false,
|
|
isMuted: false,
|
|
expected: model.NotificationReasonNotMentioned,
|
|
isGM: false,
|
|
},
|
|
{
|
|
name: "When default is ALL, channel is MENTION and has mentions",
|
|
userNotifySetting: model.UserNotifyAll,
|
|
channelNotifySetting: model.ChannelNotifyMention,
|
|
withSystemPost: false,
|
|
wasMentioned: true,
|
|
isMuted: false,
|
|
expected: "",
|
|
isGM: false,
|
|
},
|
|
{
|
|
name: "When default is MENTION, channel is MENTION and has no mentions",
|
|
userNotifySetting: model.UserNotifyMention,
|
|
channelNotifySetting: model.ChannelNotifyMention,
|
|
withSystemPost: false,
|
|
wasMentioned: false,
|
|
isMuted: false,
|
|
expected: model.NotificationReasonNotMentioned,
|
|
isGM: false,
|
|
},
|
|
{
|
|
name: "When default is MENTION, channel is MENTION and has mentions",
|
|
userNotifySetting: model.UserNotifyMention,
|
|
channelNotifySetting: model.ChannelNotifyMention,
|
|
withSystemPost: false,
|
|
wasMentioned: true,
|
|
isMuted: false,
|
|
expected: "",
|
|
isGM: false,
|
|
},
|
|
{
|
|
name: "When default is NONE, channel is MENTION and has no mentions",
|
|
userNotifySetting: model.UserNotifyNone,
|
|
channelNotifySetting: model.ChannelNotifyMention,
|
|
withSystemPost: false,
|
|
wasMentioned: false,
|
|
isMuted: false,
|
|
expected: model.NotificationReasonNotMentioned,
|
|
isGM: false,
|
|
},
|
|
{
|
|
name: "When default is NONE, channel is MENTION and has mentions",
|
|
userNotifySetting: model.UserNotifyNone,
|
|
channelNotifySetting: model.ChannelNotifyMention,
|
|
withSystemPost: false,
|
|
wasMentioned: true,
|
|
isMuted: false,
|
|
expected: "",
|
|
isGM: false,
|
|
},
|
|
{
|
|
name: "When default is ALL, channel is NONE and has no mentions",
|
|
userNotifySetting: model.UserNotifyAll,
|
|
channelNotifySetting: model.ChannelNotifyNone,
|
|
withSystemPost: false,
|
|
wasMentioned: false,
|
|
isMuted: false,
|
|
expected: model.NotificationReasonLevelSetToNone,
|
|
isGM: false,
|
|
},
|
|
{
|
|
name: "When default is ALL, channel is NONE and has mentions",
|
|
userNotifySetting: model.UserNotifyAll,
|
|
channelNotifySetting: model.ChannelNotifyNone,
|
|
withSystemPost: false,
|
|
wasMentioned: true,
|
|
isMuted: false,
|
|
expected: model.NotificationReasonLevelSetToNone,
|
|
isGM: false,
|
|
},
|
|
{
|
|
name: "When default is MENTION, channel is NONE and has no mentions",
|
|
userNotifySetting: model.UserNotifyMention,
|
|
channelNotifySetting: model.ChannelNotifyNone,
|
|
withSystemPost: false,
|
|
wasMentioned: false,
|
|
isMuted: false,
|
|
expected: model.NotificationReasonLevelSetToNone,
|
|
isGM: false,
|
|
},
|
|
{
|
|
name: "When default is MENTION, channel is NONE and has mentions",
|
|
userNotifySetting: model.UserNotifyMention,
|
|
channelNotifySetting: model.ChannelNotifyNone,
|
|
withSystemPost: false,
|
|
wasMentioned: true,
|
|
isMuted: false,
|
|
expected: model.NotificationReasonLevelSetToNone,
|
|
isGM: false,
|
|
},
|
|
{
|
|
name: "When default is NONE, channel is NONE and has no mentions",
|
|
userNotifySetting: model.UserNotifyNone,
|
|
channelNotifySetting: model.ChannelNotifyNone,
|
|
withSystemPost: false,
|
|
wasMentioned: false,
|
|
isMuted: false,
|
|
expected: model.NotificationReasonLevelSetToNone,
|
|
isGM: false,
|
|
},
|
|
{
|
|
name: "When default is NONE, channel is NONE and has mentions",
|
|
userNotifySetting: model.UserNotifyNone,
|
|
channelNotifySetting: model.ChannelNotifyNone,
|
|
withSystemPost: false,
|
|
wasMentioned: true,
|
|
isMuted: false,
|
|
expected: model.NotificationReasonLevelSetToNone,
|
|
isGM: false,
|
|
},
|
|
{
|
|
name: "When default is ALL, and channel is MUTED",
|
|
userNotifySetting: model.UserNotifyAll,
|
|
channelNotifySetting: "",
|
|
withSystemPost: false,
|
|
wasMentioned: false,
|
|
isMuted: true,
|
|
expected: model.NotificationReasonChannelMuted,
|
|
isGM: false,
|
|
},
|
|
{
|
|
name: "For GM default for NONE is NONE",
|
|
userNotifySetting: model.UserNotifyNone,
|
|
channelNotifySetting: model.ChannelNotifyDefault,
|
|
withSystemPost: false,
|
|
wasMentioned: false,
|
|
isMuted: false,
|
|
expected: model.NotificationReasonLevelSetToNone,
|
|
isGM: true,
|
|
},
|
|
{
|
|
name: "For GM, mentioned is only called if explicitly mentioned",
|
|
userNotifySetting: model.UserNotifyNone,
|
|
channelNotifySetting: model.ChannelNotifyMention,
|
|
withSystemPost: false,
|
|
wasMentioned: true,
|
|
isMuted: false,
|
|
expected: "",
|
|
isGM: true,
|
|
},
|
|
{
|
|
name: "For GM default for MENTION is ALL",
|
|
userNotifySetting: model.UserNotifyMention,
|
|
channelNotifySetting: model.ChannelNotifyDefault,
|
|
withSystemPost: false,
|
|
wasMentioned: false,
|
|
isMuted: false,
|
|
expected: "",
|
|
isGM: true,
|
|
},
|
|
{
|
|
name: "For GM, mentioned is only called if explicitly mentioned",
|
|
userNotifySetting: model.UserNotifyNone,
|
|
channelNotifySetting: model.ChannelNotifyMention,
|
|
withSystemPost: false,
|
|
wasMentioned: false,
|
|
isMuted: false,
|
|
expected: model.NotificationReasonNotMentioned,
|
|
isGM: true,
|
|
},
|
|
}
|
|
|
|
for _, tc := range tt {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
user := &model.User{Id: model.NewId(), Email: "unit@test.com", NotifyProps: make(map[string]string)}
|
|
user.NotifyProps[model.PushNotifyProp] = tc.userNotifySetting
|
|
post := &model.Post{UserId: user.Id, ChannelId: model.NewId()}
|
|
if tc.withSystemPost {
|
|
post.Type = model.PostTypeJoinChannel
|
|
}
|
|
|
|
channelNotifyProps := make(map[string]string)
|
|
if tc.channelNotifySetting != "" {
|
|
channelNotifyProps[model.PushNotifyProp] = tc.channelNotifySetting
|
|
}
|
|
if tc.isMuted {
|
|
channelNotifyProps[model.MarkUnreadNotifyProp] = model.ChannelMarkUnreadMention
|
|
}
|
|
assert.Equal(t, tc.expected, doesNotifyPropsAllowPushNotification(user, channelNotifyProps, post, tc.wasMentioned, tc.isGM))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDoesStatusAllowPushNotification(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
userID := model.NewId()
|
|
channelID := model.NewId()
|
|
|
|
offline := &model.Status{UserId: userID, Status: model.StatusOffline, Manual: false, LastActivityAt: 0, ActiveChannel: ""}
|
|
away := &model.Status{UserId: userID, Status: model.StatusAway, Manual: false, LastActivityAt: 0, ActiveChannel: ""}
|
|
online := &model.Status{UserId: userID, Status: model.StatusOnline, Manual: false, LastActivityAt: model.GetMillis(), ActiveChannel: ""}
|
|
dnd := &model.Status{UserId: userID, Status: model.StatusDnd, Manual: true, LastActivityAt: model.GetMillis(), ActiveChannel: ""}
|
|
activeOnChannel := &model.Status{UserId: userID, Status: model.StatusOnline, Manual: false, LastActivityAt: model.GetMillis(), ActiveChannel: channelID}
|
|
|
|
tt := []struct {
|
|
name string
|
|
userNotifySetting string
|
|
status *model.Status
|
|
channelID string
|
|
isCRT bool
|
|
expected model.NotificationReason
|
|
}{
|
|
{
|
|
name: "WHEN props is ONLINE and user is offline with channel",
|
|
userNotifySetting: model.StatusOnline,
|
|
status: offline,
|
|
channelID: channelID,
|
|
expected: "",
|
|
},
|
|
{
|
|
name: "WHEN props is ONLINE and user is offline without channel",
|
|
userNotifySetting: model.StatusOnline,
|
|
status: offline,
|
|
channelID: "",
|
|
expected: "",
|
|
},
|
|
{
|
|
name: "WHEN props is ONLINE and user is away with channel",
|
|
userNotifySetting: model.StatusOnline,
|
|
status: away,
|
|
channelID: channelID,
|
|
expected: "",
|
|
},
|
|
{
|
|
name: "WHEN props is ONLINE and user is away without channel",
|
|
userNotifySetting: model.StatusOnline,
|
|
status: away,
|
|
channelID: "",
|
|
expected: "",
|
|
},
|
|
{
|
|
name: "WHEN props is ONLINE and user is online with channel",
|
|
userNotifySetting: model.StatusOnline,
|
|
status: online,
|
|
channelID: channelID,
|
|
expected: "",
|
|
},
|
|
{
|
|
name: "WHEN props is ONLINE and user is online without channel",
|
|
userNotifySetting: model.StatusOnline,
|
|
status: online,
|
|
channelID: "",
|
|
expected: model.NotificationReasonUserIsActive,
|
|
},
|
|
{
|
|
name: "WHEN props is ONLINE and user is online and active within the channel",
|
|
userNotifySetting: model.StatusOnline,
|
|
status: activeOnChannel,
|
|
channelID: channelID,
|
|
expected: model.NotificationReasonUserIsActive,
|
|
},
|
|
{
|
|
name: "WHEN props is ONLINE and user is online and active within a thread in the channel",
|
|
userNotifySetting: model.StatusOnline,
|
|
status: activeOnChannel,
|
|
channelID: channelID,
|
|
expected: "",
|
|
isCRT: true,
|
|
},
|
|
{
|
|
name: "WHEN props is ONLINE and user is dnd with channel",
|
|
userNotifySetting: model.StatusOnline,
|
|
status: dnd,
|
|
channelID: channelID,
|
|
expected: model.NotificationReasonUserStatus,
|
|
},
|
|
{
|
|
name: "WHEN props is ONLINE and user is dnd without channel",
|
|
userNotifySetting: model.StatusOnline,
|
|
status: dnd,
|
|
channelID: "",
|
|
expected: model.NotificationReasonUserStatus,
|
|
},
|
|
{
|
|
name: "WHEN props is AWAY and user is offline with channel",
|
|
userNotifySetting: model.StatusAway,
|
|
status: offline,
|
|
channelID: channelID,
|
|
expected: "",
|
|
},
|
|
{
|
|
name: "WHEN props is AWAY and user is offline without channel",
|
|
userNotifySetting: model.StatusAway,
|
|
status: offline,
|
|
channelID: "",
|
|
expected: "",
|
|
},
|
|
{
|
|
name: "WHEN props is AWAY and user is away with channel",
|
|
userNotifySetting: model.StatusAway,
|
|
status: away,
|
|
channelID: channelID,
|
|
expected: "",
|
|
},
|
|
{
|
|
name: "WHEN props is AWAY and user is away without channel",
|
|
userNotifySetting: model.StatusAway,
|
|
status: away,
|
|
channelID: "",
|
|
expected: "",
|
|
},
|
|
{
|
|
name: "WHEN props is AWAY and user is online with channel",
|
|
userNotifySetting: model.StatusAway,
|
|
status: online,
|
|
channelID: channelID,
|
|
expected: model.NotificationReasonUserIsActive,
|
|
},
|
|
{
|
|
name: "WHEN props is AWAY and user is online without channel",
|
|
userNotifySetting: model.StatusAway,
|
|
status: online,
|
|
channelID: "",
|
|
expected: model.NotificationReasonUserIsActive,
|
|
},
|
|
{
|
|
name: "WHEN props is AWAY and user is dnd with channel",
|
|
userNotifySetting: model.StatusAway,
|
|
status: dnd,
|
|
channelID: channelID,
|
|
expected: model.NotificationReasonUserStatus,
|
|
},
|
|
{
|
|
name: "WHEN props is AWAY and user is dnd without channel",
|
|
userNotifySetting: model.StatusAway,
|
|
status: dnd,
|
|
channelID: "",
|
|
expected: model.NotificationReasonUserStatus,
|
|
},
|
|
{
|
|
name: "WHEN props is OFFLINE and user is offline with channel",
|
|
userNotifySetting: model.StatusOffline,
|
|
status: offline,
|
|
channelID: channelID,
|
|
expected: "",
|
|
},
|
|
{
|
|
name: "WHEN props is OFFLINE and user is offline without channel",
|
|
userNotifySetting: model.StatusOffline,
|
|
status: offline,
|
|
channelID: "",
|
|
expected: "",
|
|
},
|
|
{
|
|
name: "WHEN props is OFFLINE and user is away with channel",
|
|
userNotifySetting: model.StatusOffline,
|
|
status: away,
|
|
channelID: channelID,
|
|
expected: model.NotificationReasonUserIsActive,
|
|
},
|
|
{
|
|
name: "WHEN props is OFFLINE and user is away without channel",
|
|
userNotifySetting: model.StatusOffline,
|
|
status: away,
|
|
channelID: "",
|
|
expected: model.NotificationReasonUserIsActive,
|
|
},
|
|
{
|
|
name: "WHEN props is OFFLINE and user is online with channel",
|
|
userNotifySetting: model.StatusOffline,
|
|
status: online,
|
|
channelID: channelID,
|
|
expected: model.NotificationReasonUserIsActive,
|
|
},
|
|
{
|
|
name: "WHEN props is OFFLINE and user is online without channel",
|
|
userNotifySetting: model.StatusOffline,
|
|
status: online,
|
|
channelID: "",
|
|
expected: model.NotificationReasonUserIsActive,
|
|
},
|
|
{
|
|
name: "WHEN props is OFFLINE and user is dnd with channel",
|
|
userNotifySetting: model.StatusOffline,
|
|
status: dnd,
|
|
channelID: channelID,
|
|
expected: model.NotificationReasonUserStatus,
|
|
},
|
|
{
|
|
name: "WHEN props is OFFLINE and user is dnd without channel",
|
|
userNotifySetting: model.StatusOffline,
|
|
status: dnd,
|
|
channelID: "",
|
|
expected: model.NotificationReasonUserStatus,
|
|
},
|
|
}
|
|
|
|
for _, tc := range tt {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
userNotifyProps := make(map[string]string)
|
|
userNotifyProps["push_status"] = tc.userNotifySetting
|
|
assert.Equal(t, tc.expected, doesStatusAllowPushNotification(userNotifyProps, tc.status, tc.channelID, tc.isCRT))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetPushNotificationMessage(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := SetupWithStoreMock(t)
|
|
defer th.TearDown()
|
|
|
|
mockStore := th.App.Srv().Store().(*mocks.Store)
|
|
mockUserStore := mocks.UserStore{}
|
|
mockUserStore.On("Count", mock.Anything).Return(int64(10), nil)
|
|
mockPostStore := mocks.PostStore{}
|
|
mockPostStore.On("GetMaxPostSize").Return(65535, nil)
|
|
mockSystemStore := mocks.SystemStore{}
|
|
mockSystemStore.On("GetByName", "UpgradedFromTE").Return(&model.System{Name: "UpgradedFromTE", Value: "false"}, nil)
|
|
mockSystemStore.On("GetByName", "InstallationDate").Return(&model.System{Name: "InstallationDate", Value: "10"}, nil)
|
|
mockSystemStore.On("GetByName", "FirstServerRunTimestamp").Return(&model.System{Name: "FirstServerRunTimestamp", Value: "10"}, nil)
|
|
|
|
mockStore.On("User").Return(&mockUserStore)
|
|
mockStore.On("Post").Return(&mockPostStore)
|
|
mockStore.On("System").Return(&mockSystemStore)
|
|
mockStore.On("GetDBSchemaVersion").Return(1, nil)
|
|
|
|
for name, tc := range map[string]struct {
|
|
Message string
|
|
explicitMention bool
|
|
channelWideMention bool
|
|
HasFiles bool
|
|
replyToThreadType string
|
|
Locale string
|
|
PushNotificationContents string
|
|
ChannelType model.ChannelType
|
|
|
|
ExpectedMessage string
|
|
}{
|
|
"full message, public channel, no mention": {
|
|
Message: "this is a message",
|
|
ChannelType: model.ChannelTypeOpen,
|
|
ExpectedMessage: "user: this is a message",
|
|
},
|
|
"full message, public channel, mention": {
|
|
Message: "this is a message",
|
|
explicitMention: true,
|
|
ChannelType: model.ChannelTypeOpen,
|
|
ExpectedMessage: "user: this is a message",
|
|
},
|
|
"full message, public channel, channel wide mention": {
|
|
Message: "this is a message",
|
|
channelWideMention: true,
|
|
ChannelType: model.ChannelTypeOpen,
|
|
ExpectedMessage: "user: this is a message",
|
|
},
|
|
"full message, public channel, commented on post": {
|
|
Message: "this is a message",
|
|
replyToThreadType: model.CommentsNotifyRoot,
|
|
ChannelType: model.ChannelTypeOpen,
|
|
ExpectedMessage: "user: this is a message",
|
|
},
|
|
"full message, public channel, commented on thread": {
|
|
Message: "this is a message",
|
|
replyToThreadType: model.CommentsNotifyAny,
|
|
ChannelType: model.ChannelTypeOpen,
|
|
ExpectedMessage: "user: this is a message",
|
|
},
|
|
"full message, private channel, no mention": {
|
|
Message: "this is a message",
|
|
ChannelType: model.ChannelTypePrivate,
|
|
ExpectedMessage: "user: this is a message",
|
|
},
|
|
"full message, private channel, mention": {
|
|
Message: "this is a message",
|
|
explicitMention: true,
|
|
ChannelType: model.ChannelTypePrivate,
|
|
ExpectedMessage: "user: this is a message",
|
|
},
|
|
"full message, private channel, commented on post": {
|
|
Message: "this is a message",
|
|
replyToThreadType: model.CommentsNotifyRoot,
|
|
ChannelType: model.ChannelTypePrivate,
|
|
ExpectedMessage: "user: this is a message",
|
|
},
|
|
"full message, private channel, commented on thread": {
|
|
Message: "this is a message",
|
|
replyToThreadType: model.CommentsNotifyAny,
|
|
ChannelType: model.ChannelTypePrivate,
|
|
ExpectedMessage: "user: this is a message",
|
|
},
|
|
"full message, group message channel, no mention": {
|
|
Message: "this is a message",
|
|
ChannelType: model.ChannelTypeGroup,
|
|
ExpectedMessage: "user: this is a message",
|
|
},
|
|
"full message, group message channel, mention": {
|
|
Message: "this is a message",
|
|
explicitMention: true,
|
|
ChannelType: model.ChannelTypeGroup,
|
|
ExpectedMessage: "user: this is a message",
|
|
},
|
|
"full message, group message channel, commented on post": {
|
|
Message: "this is a message",
|
|
replyToThreadType: model.CommentsNotifyRoot,
|
|
ChannelType: model.ChannelTypeGroup,
|
|
ExpectedMessage: "user: this is a message",
|
|
},
|
|
"full message, group message channel, commented on thread": {
|
|
Message: "this is a message",
|
|
replyToThreadType: model.CommentsNotifyAny,
|
|
ChannelType: model.ChannelTypeGroup,
|
|
ExpectedMessage: "user: this is a message",
|
|
},
|
|
"full message, direct message channel, no mention": {
|
|
Message: "this is a message",
|
|
ChannelType: model.ChannelTypeDirect,
|
|
ExpectedMessage: "this is a message",
|
|
},
|
|
"full message, direct message channel, mention": {
|
|
Message: "this is a message",
|
|
explicitMention: true,
|
|
ChannelType: model.ChannelTypeDirect,
|
|
ExpectedMessage: "this is a message",
|
|
},
|
|
"full message, direct message channel, commented on post": {
|
|
Message: "this is a message",
|
|
replyToThreadType: model.CommentsNotifyRoot,
|
|
ChannelType: model.ChannelTypeDirect,
|
|
ExpectedMessage: "this is a message",
|
|
},
|
|
"full message, direct message channel, commented on thread": {
|
|
Message: "this is a message",
|
|
replyToThreadType: model.CommentsNotifyAny,
|
|
ChannelType: model.ChannelTypeDirect,
|
|
ExpectedMessage: "this is a message",
|
|
},
|
|
"full message, direct message channel, commented on CRT enabled thread": {
|
|
Message: "this is a message",
|
|
replyToThreadType: model.CommentsNotifyCRT,
|
|
ChannelType: model.ChannelTypeDirect,
|
|
ExpectedMessage: "user: this is a message",
|
|
},
|
|
"generic message with channel, public channel, no mention": {
|
|
Message: "this is a message",
|
|
PushNotificationContents: model.GenericNotification,
|
|
ChannelType: model.ChannelTypeOpen,
|
|
ExpectedMessage: "user posted a message.",
|
|
},
|
|
"generic message with channel, public channel, mention": {
|
|
Message: "this is a message",
|
|
explicitMention: true,
|
|
PushNotificationContents: model.GenericNotification,
|
|
ChannelType: model.ChannelTypeOpen,
|
|
ExpectedMessage: "user mentioned you.",
|
|
},
|
|
"generic message with channel, public channel, channel wide mention": {
|
|
Message: "this is a message",
|
|
channelWideMention: true,
|
|
PushNotificationContents: model.GenericNotification,
|
|
ChannelType: model.ChannelTypeOpen,
|
|
ExpectedMessage: "user notified the channel.",
|
|
},
|
|
"generic message, public channel, commented on post": {
|
|
Message: "this is a message",
|
|
replyToThreadType: model.CommentsNotifyRoot,
|
|
PushNotificationContents: model.GenericNotification,
|
|
ChannelType: model.ChannelTypeOpen,
|
|
ExpectedMessage: "user commented on your post.",
|
|
},
|
|
"generic message, public channel, commented on thread": {
|
|
Message: "this is a message",
|
|
replyToThreadType: model.CommentsNotifyAny,
|
|
PushNotificationContents: model.GenericNotification,
|
|
ChannelType: model.ChannelTypeOpen,
|
|
ExpectedMessage: "user commented on a thread you participated in.",
|
|
},
|
|
"generic message with channel, private channel, no mention": {
|
|
Message: "this is a message",
|
|
PushNotificationContents: model.GenericNotification,
|
|
ChannelType: model.ChannelTypePrivate,
|
|
ExpectedMessage: "user posted a message.",
|
|
},
|
|
"generic message with channel, private channel, mention": {
|
|
Message: "this is a message",
|
|
explicitMention: true,
|
|
PushNotificationContents: model.GenericNotification,
|
|
ChannelType: model.ChannelTypePrivate,
|
|
ExpectedMessage: "user mentioned you.",
|
|
},
|
|
"generic message with channel, private channel, channel wide mention": {
|
|
Message: "this is a message",
|
|
channelWideMention: true,
|
|
PushNotificationContents: model.GenericNotification,
|
|
ChannelType: model.ChannelTypePrivate,
|
|
ExpectedMessage: "user notified the channel.",
|
|
},
|
|
"generic message, public private, commented on post": {
|
|
Message: "this is a message",
|
|
replyToThreadType: model.CommentsNotifyRoot,
|
|
PushNotificationContents: model.GenericNotification,
|
|
ChannelType: model.ChannelTypePrivate,
|
|
ExpectedMessage: "user commented on your post.",
|
|
},
|
|
"generic message, public private, commented on thread": {
|
|
Message: "this is a message",
|
|
replyToThreadType: model.CommentsNotifyAny,
|
|
PushNotificationContents: model.GenericNotification,
|
|
ChannelType: model.ChannelTypePrivate,
|
|
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.GenericNotification,
|
|
ChannelType: model.ChannelTypeGroup,
|
|
ExpectedMessage: "user posted a message.",
|
|
},
|
|
"generic message with channel, group message channel, mention": {
|
|
Message: "this is a message",
|
|
explicitMention: true,
|
|
PushNotificationContents: model.GenericNotification,
|
|
ChannelType: model.ChannelTypeGroup,
|
|
ExpectedMessage: "user mentioned you.",
|
|
},
|
|
"generic message with channel, group message channel, channel wide mention": {
|
|
Message: "this is a message",
|
|
channelWideMention: true,
|
|
PushNotificationContents: model.GenericNotification,
|
|
ChannelType: model.ChannelTypeGroup,
|
|
ExpectedMessage: "user notified the channel.",
|
|
},
|
|
"generic message, group message channel, commented on post": {
|
|
Message: "this is a message",
|
|
replyToThreadType: model.CommentsNotifyRoot,
|
|
PushNotificationContents: model.GenericNotification,
|
|
ChannelType: model.ChannelTypeGroup,
|
|
ExpectedMessage: "user commented on your post.",
|
|
},
|
|
"generic message, group message channel, commented on thread": {
|
|
Message: "this is a message",
|
|
replyToThreadType: model.CommentsNotifyAny,
|
|
PushNotificationContents: model.GenericNotification,
|
|
ChannelType: model.ChannelTypeGroup,
|
|
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.GenericNotification,
|
|
ChannelType: model.ChannelTypeDirect,
|
|
ExpectedMessage: "sent you a message.",
|
|
},
|
|
"generic message with channel, direct message channel, mention": {
|
|
Message: "this is a message",
|
|
explicitMention: true,
|
|
PushNotificationContents: model.GenericNotification,
|
|
ChannelType: model.ChannelTypeDirect,
|
|
ExpectedMessage: "sent you a message.",
|
|
},
|
|
"generic message with channel, direct message channel, channel wide mention": {
|
|
Message: "this is a message",
|
|
channelWideMention: true,
|
|
PushNotificationContents: model.GenericNotification,
|
|
ChannelType: model.ChannelTypeDirect,
|
|
ExpectedMessage: "sent you a message.",
|
|
},
|
|
"generic message, direct message channel, commented on post": {
|
|
Message: "this is a message",
|
|
replyToThreadType: model.CommentsNotifyRoot,
|
|
PushNotificationContents: model.GenericNotification,
|
|
ChannelType: model.ChannelTypeDirect,
|
|
ExpectedMessage: "sent you a message.",
|
|
},
|
|
"generic message, direct message channel, commented on thread": {
|
|
Message: "this is a message",
|
|
replyToThreadType: model.CommentsNotifyAny,
|
|
PushNotificationContents: model.GenericNotification,
|
|
ChannelType: model.ChannelTypeDirect,
|
|
ExpectedMessage: "sent you a message.",
|
|
},
|
|
"generic message without channel, public channel, no mention": {
|
|
Message: "this is a message",
|
|
PushNotificationContents: model.GenericNoChannelNotification,
|
|
ChannelType: model.ChannelTypeOpen,
|
|
ExpectedMessage: "user posted a message.",
|
|
},
|
|
"generic message without channel, public channel, mention": {
|
|
Message: "this is a message",
|
|
explicitMention: true,
|
|
PushNotificationContents: model.GenericNoChannelNotification,
|
|
ChannelType: model.ChannelTypeOpen,
|
|
ExpectedMessage: "user mentioned you.",
|
|
},
|
|
"generic message without channel, private channel, no mention": {
|
|
Message: "this is a message",
|
|
PushNotificationContents: model.GenericNoChannelNotification,
|
|
ChannelType: model.ChannelTypePrivate,
|
|
ExpectedMessage: "user posted a message.",
|
|
},
|
|
"generic message without channel, private channel, mention": {
|
|
Message: "this is a message",
|
|
explicitMention: true,
|
|
PushNotificationContents: model.GenericNoChannelNotification,
|
|
ChannelType: model.ChannelTypePrivate,
|
|
ExpectedMessage: "user mentioned you.",
|
|
},
|
|
"generic message without channel, group message channel, no mention": {
|
|
Message: "this is a message",
|
|
PushNotificationContents: model.GenericNoChannelNotification,
|
|
ChannelType: model.ChannelTypeGroup,
|
|
ExpectedMessage: "user posted a message.",
|
|
},
|
|
"generic message without channel, group message channel, mention": {
|
|
Message: "this is a message",
|
|
explicitMention: true,
|
|
PushNotificationContents: model.GenericNoChannelNotification,
|
|
ChannelType: model.ChannelTypeGroup,
|
|
ExpectedMessage: "user mentioned you.",
|
|
},
|
|
"generic message without channel, direct message channel, no mention": {
|
|
Message: "this is a message",
|
|
PushNotificationContents: model.GenericNoChannelNotification,
|
|
ChannelType: model.ChannelTypeDirect,
|
|
ExpectedMessage: "sent you a message.",
|
|
},
|
|
"generic message without channel, direct message channel, mention": {
|
|
Message: "this is a message",
|
|
explicitMention: true,
|
|
PushNotificationContents: model.GenericNoChannelNotification,
|
|
ChannelType: model.ChannelTypeDirect,
|
|
ExpectedMessage: "sent you a message.",
|
|
},
|
|
"only files, public channel": {
|
|
HasFiles: true,
|
|
ChannelType: model.ChannelTypeOpen,
|
|
ExpectedMessage: "user attached a file.",
|
|
},
|
|
"only files, private channel": {
|
|
HasFiles: true,
|
|
ChannelType: model.ChannelTypePrivate,
|
|
ExpectedMessage: "user attached a file.",
|
|
},
|
|
"only files, group message channel": {
|
|
HasFiles: true,
|
|
ChannelType: model.ChannelTypeGroup,
|
|
ExpectedMessage: "user attached a file.",
|
|
},
|
|
"only files, direct message channel": {
|
|
HasFiles: true,
|
|
ChannelType: model.ChannelTypeDirect,
|
|
ExpectedMessage: "attached a file.",
|
|
},
|
|
"only files without channel, public channel": {
|
|
HasFiles: true,
|
|
PushNotificationContents: model.GenericNoChannelNotification,
|
|
ChannelType: model.ChannelTypeOpen,
|
|
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.FullNotification
|
|
}
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.EmailSettings.PushNotificationContents = pushNotificationContents
|
|
})
|
|
|
|
actualMessage := th.App.getPushNotificationMessage(
|
|
pushNotificationContents,
|
|
tc.Message,
|
|
tc.explicitMention,
|
|
tc.channelWideMention,
|
|
tc.HasFiles,
|
|
"user",
|
|
tc.ChannelType,
|
|
tc.replyToThreadType,
|
|
i18n.GetUserTranslations(locale),
|
|
)
|
|
|
|
assert.Equal(t, tc.ExpectedMessage, actualMessage)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestBuildPushNotificationMessageMentions(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
team := th.CreateTeam()
|
|
sender := th.CreateUser()
|
|
receiver := th.CreateUser()
|
|
th.LinkUserToTeam(sender, team)
|
|
th.LinkUserToTeam(receiver, team)
|
|
channel1 := th.CreateChannel(th.Context, team)
|
|
th.AddUserToChannel(sender, channel1)
|
|
th.AddUserToChannel(receiver, channel1)
|
|
|
|
channel2 := th.CreateChannel(th.Context, team)
|
|
th.AddUserToChannel(sender, channel2)
|
|
th.AddUserToChannel(receiver, channel2)
|
|
|
|
// Create three mention posts and two non-mention posts
|
|
th.CreateMessagePost(channel1, "@channel Hello")
|
|
th.CreateMessagePost(channel1, "@all Hello")
|
|
th.CreateMessagePost(channel1, fmt.Sprintf("@%s Hello in channel 1", receiver.Username))
|
|
th.CreateMessagePost(channel2, fmt.Sprintf("@%s Hello in channel 2", receiver.Username))
|
|
th.CreatePost(channel1)
|
|
post := th.CreatePost(channel1)
|
|
|
|
for name, tc := range map[string]struct {
|
|
explicitMention bool
|
|
channelWideMention bool
|
|
replyToThreadType string
|
|
pushNotifyProps string
|
|
expectedBadge int
|
|
}{
|
|
"only mentions included for notify_props=mention": {
|
|
explicitMention: false,
|
|
channelWideMention: true,
|
|
replyToThreadType: "",
|
|
pushNotifyProps: "mention",
|
|
expectedBadge: 4,
|
|
},
|
|
"only mentions included for notify_props=all": {
|
|
explicitMention: false,
|
|
channelWideMention: true,
|
|
replyToThreadType: "",
|
|
pushNotifyProps: "all",
|
|
expectedBadge: 4,
|
|
},
|
|
} {
|
|
t.Run(name, func(t *testing.T) {
|
|
receiver.NotifyProps["push"] = tc.pushNotifyProps
|
|
msg, err := th.App.BuildPushNotificationMessage(th.Context, model.FullNotification, post, receiver, channel1, channel1.Name, sender.Username, tc.explicitMention, tc.channelWideMention, tc.replyToThreadType)
|
|
require.Nil(t, err)
|
|
assert.Equal(t, tc.expectedBadge, msg.Badge)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSendPushNotifications(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
_, err := th.App.CreateSession(th.Context, &model.Session{
|
|
UserId: th.BasicUser.Id,
|
|
DeviceId: "test",
|
|
ExpiresAt: model.GetMillis() + 100000,
|
|
})
|
|
require.Nil(t, err)
|
|
|
|
t.Run("should return error if data is not valid or nil", func(t *testing.T) {
|
|
err := th.App.sendPushNotificationToAllSessions(th.Context, nil, th.BasicUser.Id, "")
|
|
require.NotNil(t, err)
|
|
assert.Equal(t, "api.push_notifications.message.parse.app_error", err.Id)
|
|
// Errors derived of using an empty object are handled internally through the notifications log
|
|
err = th.App.sendPushNotificationToAllSessions(th.Context, &model.PushNotification{}, th.BasicUser.Id, "")
|
|
require.Nil(t, err)
|
|
})
|
|
}
|
|
|
|
func TestShouldSendPushNotifications(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
t.Run("should return true if forced", func(t *testing.T) {
|
|
user := &model.User{Id: model.NewId(), Email: "unit@test.com", NotifyProps: make(map[string]string)}
|
|
user.NotifyProps[model.PushNotifyProp] = model.UserNotifyNone
|
|
|
|
post := &model.Post{UserId: user.Id, ChannelId: model.NewId()}
|
|
post.AddProp(model.PostPropsForceNotification, model.NewId())
|
|
|
|
channelNotifyProps := map[string]string{model.PushNotifyProp: model.ChannelNotifyNone, model.MarkUnreadNotifyProp: model.ChannelMarkUnreadMention}
|
|
|
|
status := &model.Status{UserId: user.Id, Status: model.StatusOnline, Manual: false, LastActivityAt: model.GetMillis(), ActiveChannel: post.ChannelId}
|
|
|
|
result := th.App.ShouldSendPushNotification(th.Context, user, channelNotifyProps, false, status, post, false)
|
|
assert.True(t, result)
|
|
})
|
|
|
|
t.Run("should return false if force undefined", func(t *testing.T) {
|
|
user := &model.User{Id: model.NewId(), Email: "unit@test.com", NotifyProps: make(map[string]string)}
|
|
user.NotifyProps[model.PushNotifyProp] = model.UserNotifyNone
|
|
|
|
post := &model.Post{UserId: user.Id, ChannelId: model.NewId()}
|
|
|
|
channelNotifyProps := map[string]string{model.PushNotifyProp: model.ChannelNotifyNone, model.MarkUnreadNotifyProp: model.ChannelMarkUnreadMention}
|
|
|
|
status := &model.Status{UserId: user.Id, Status: model.StatusOnline, Manual: false, LastActivityAt: model.GetMillis(), ActiveChannel: post.ChannelId}
|
|
|
|
result := th.App.ShouldSendPushNotification(th.Context, user, channelNotifyProps, false, status, post, false)
|
|
assert.False(t, result)
|
|
})
|
|
}
|
|
|
|
// testPushNotificationHandler is an HTTP handler to record push notifications
|
|
// being sent from the client.
|
|
// It records the number of requests sent to it, and stores all the requests
|
|
// to be verified later.
|
|
type testPushNotificationHandler struct {
|
|
t testing.TB
|
|
serialUserMap sync.Map
|
|
mut sync.RWMutex
|
|
behavior string
|
|
_numReqs int
|
|
_notifications []*model.PushNotification
|
|
_notificationAcks []*model.PushNotificationAck
|
|
}
|
|
|
|
// handleReq parses a push notification from the body, and stores it.
|
|
// It also sends an appropriate response depending on the behavior set.
|
|
// If the behavior is simple, it always sends an OK response. Otherwise,
|
|
// it alternates between an OK and a REMOVE response.
|
|
func (h *testPushNotificationHandler) handleReq(w http.ResponseWriter, r *http.Request) {
|
|
switch r.URL.Path {
|
|
case "/api/v1/send_push", "/api/v1/ack":
|
|
h.t.Helper()
|
|
|
|
// Don't do any checking if it's a benchmark
|
|
if _, ok := h.t.(*testing.B); ok {
|
|
h.printResponse(w, model.NewOkPushResponse())
|
|
return
|
|
}
|
|
|
|
var notification model.PushNotification
|
|
var notificationAck model.PushNotificationAck
|
|
var err error
|
|
if r.URL.Path == "/api/v1/send_push" {
|
|
if err = json.NewDecoder(r.Body).Decode(¬ification); err != nil {
|
|
h.printResponse(w, model.NewErrorPushResponse("fail"))
|
|
return
|
|
}
|
|
// We verify that messages are being sent in order per-device.
|
|
if notification.DeviceId != "" {
|
|
if _, ok := h.serialUserMap.Load(notification.DeviceId); ok {
|
|
h.t.Fatalf("device id: %s being sent concurrently", notification.DeviceId)
|
|
}
|
|
h.serialUserMap.LoadOrStore(notification.DeviceId, true)
|
|
defer h.serialUserMap.Delete(notification.DeviceId)
|
|
}
|
|
} else {
|
|
if err = json.NewDecoder(r.Body).Decode(¬ificationAck); err != nil {
|
|
h.printResponse(w, model.NewErrorPushResponse("fail"))
|
|
return
|
|
}
|
|
}
|
|
// Updating internal state.
|
|
h.mut.Lock()
|
|
defer h.mut.Unlock()
|
|
h._numReqs++
|
|
// Little bit of duplicate condition check so that we can check the in-order property
|
|
// first.
|
|
if r.URL.Path == "/api/v1/send_push" {
|
|
h._notifications = append(h._notifications, ¬ification)
|
|
} else {
|
|
h._notificationAcks = append(h._notificationAcks, ¬ificationAck)
|
|
}
|
|
|
|
var resp model.PushResponse
|
|
if h.behavior == "simple" {
|
|
resp = model.NewOkPushResponse()
|
|
} else {
|
|
// alternating between ok and remove response to test both code paths.
|
|
if h._numReqs%2 == 0 {
|
|
resp = model.NewOkPushResponse()
|
|
} else {
|
|
resp = model.NewRemovePushResponse()
|
|
}
|
|
}
|
|
h.printResponse(w, resp)
|
|
}
|
|
}
|
|
|
|
func (h *testPushNotificationHandler) printResponse(w http.ResponseWriter, resp model.PushResponse) {
|
|
jsonData, _ := json.Marshal(&resp)
|
|
fmt.Fprintln(w, string(jsonData))
|
|
}
|
|
|
|
func (h *testPushNotificationHandler) numReqs() int {
|
|
h.mut.RLock()
|
|
defer h.mut.RUnlock()
|
|
return h._numReqs
|
|
}
|
|
|
|
func (h *testPushNotificationHandler) notifications() []*model.PushNotification {
|
|
h.mut.RLock()
|
|
defer h.mut.RUnlock()
|
|
return h._notifications
|
|
}
|
|
|
|
func (h *testPushNotificationHandler) notificationAcks() []*model.PushNotificationAck {
|
|
h.mut.RLock()
|
|
defer h.mut.RUnlock()
|
|
return h._notificationAcks
|
|
}
|
|
|
|
func TestClearPushNotificationSync(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := SetupWithStoreMock(t)
|
|
defer th.TearDown()
|
|
|
|
handler := &testPushNotificationHandler{t: t}
|
|
pushServer := httptest.NewServer(
|
|
http.HandlerFunc(handler.handleReq),
|
|
)
|
|
defer pushServer.Close()
|
|
|
|
sess1 := &model.Session{
|
|
Id: "id1",
|
|
UserId: "user1",
|
|
DeviceId: "test1",
|
|
ExpiresAt: model.GetMillis() + 100000,
|
|
}
|
|
sess2 := &model.Session{
|
|
Id: "id2",
|
|
UserId: "user1",
|
|
DeviceId: "test2",
|
|
ExpiresAt: model.GetMillis() + 100000,
|
|
}
|
|
|
|
mockStore := th.App.Srv().Store().(*mocks.Store)
|
|
mockUserStore := mocks.UserStore{}
|
|
mockUserStore.On("Count", mock.Anything).Return(int64(10), nil)
|
|
mockUserStore.On("GetUnreadCount", mock.AnythingOfType("string"), mock.AnythingOfType("bool")).Return(int64(1), nil)
|
|
mockPostStore := mocks.PostStore{}
|
|
mockPostStore.On("GetMaxPostSize").Return(65535, nil)
|
|
mockSystemStore := mocks.SystemStore{}
|
|
mockSystemStore.On("GetByName", "UpgradedFromTE").Return(&model.System{Name: "UpgradedFromTE", Value: "false"}, nil)
|
|
mockSystemStore.On("GetByName", "InstallationDate").Return(&model.System{Name: "InstallationDate", Value: "10"}, nil)
|
|
mockSystemStore.On("GetByName", "FirstServerRunTimestamp").Return(&model.System{Name: "FirstServerRunTimestamp", Value: "10"}, nil)
|
|
mockSystemStore.On("Get").Return(model.StringMap{model.SystemServerId: model.NewId()}, nil)
|
|
|
|
mockSessionStore := mocks.SessionStore{}
|
|
mockSessionStore.On("GetSessionsWithActiveDeviceIds", mock.AnythingOfType("string")).Return([]*model.Session{sess1, sess2}, nil)
|
|
mockSessionStore.On("UpdateProps", mock.Anything).Return(nil)
|
|
mockStore.On("User").Return(&mockUserStore)
|
|
mockStore.On("Post").Return(&mockPostStore)
|
|
mockStore.On("System").Return(&mockSystemStore)
|
|
mockStore.On("Session").Return(&mockSessionStore)
|
|
mockStore.On("GetDBSchemaVersion").Return(1, nil)
|
|
|
|
// When CRT is disabled
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.EmailSettings.PushNotificationServer = pushServer.URL
|
|
*cfg.ServiceSettings.CollapsedThreads = model.CollapsedThreadsDisabled
|
|
})
|
|
|
|
err := th.App.clearPushNotificationSync(th.Context, sess1.Id, "user1", "channel1", "")
|
|
require.Nil(t, err)
|
|
// Server side verification.
|
|
// We verify that 1 request has been sent, and also check the message contents.
|
|
require.Equal(t, 1, handler.numReqs())
|
|
assert.Equal(t, "channel1", handler.notifications()[0].ChannelId)
|
|
assert.Equal(t, model.PushTypeClear, handler.notifications()[0].Type)
|
|
|
|
// When CRT is enabled, Send badge count adding both "User unreads" + "User thread mentions"
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.ServiceSettings.ThreadAutoFollow = true
|
|
*cfg.ServiceSettings.CollapsedThreads = model.CollapsedThreadsDefaultOn
|
|
})
|
|
|
|
mockPreferenceStore := mocks.PreferenceStore{}
|
|
mockPreferenceStore.On("Get", mock.AnythingOfType("string"), model.PreferenceCategoryDisplaySettings, model.PreferenceNameCollapsedThreadsEnabled).Return(&model.Preference{Value: "on"}, nil)
|
|
mockStore.On("Preference").Return(&mockPreferenceStore)
|
|
|
|
mockThreadStore := mocks.ThreadStore{}
|
|
mockThreadStore.On("GetTotalUnreadMentions", mock.AnythingOfType("string"), mock.AnythingOfType("string"), mock.Anything).Return(int64(3), nil)
|
|
mockStore.On("Thread").Return(&mockThreadStore)
|
|
|
|
err = th.App.clearPushNotificationSync(th.Context, sess1.Id, "user1", "channel1", "")
|
|
require.Nil(t, err)
|
|
assert.Equal(t, handler.notifications()[1].Badge, 4)
|
|
}
|
|
|
|
func TestUpdateMobileAppBadgeSync(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := SetupWithStoreMock(t)
|
|
defer th.TearDown()
|
|
|
|
handler := &testPushNotificationHandler{t: t}
|
|
pushServer := httptest.NewServer(
|
|
http.HandlerFunc(handler.handleReq),
|
|
)
|
|
defer pushServer.Close()
|
|
|
|
sess1 := &model.Session{
|
|
Id: "id1",
|
|
UserId: "user1",
|
|
DeviceId: "test1",
|
|
ExpiresAt: model.GetMillis() + 100000,
|
|
}
|
|
sess2 := &model.Session{
|
|
Id: "id2",
|
|
UserId: "user1",
|
|
DeviceId: "test2",
|
|
ExpiresAt: model.GetMillis() + 100000,
|
|
}
|
|
|
|
mockStore := th.App.Srv().Store().(*mocks.Store)
|
|
mockUserStore := mocks.UserStore{}
|
|
mockUserStore.On("Count", mock.Anything).Return(int64(10), nil)
|
|
mockUserStore.On("GetUnreadCount", mock.AnythingOfType("string"), mock.AnythingOfType("bool")).Return(int64(1), nil)
|
|
mockPostStore := mocks.PostStore{}
|
|
mockPostStore.On("GetMaxPostSize").Return(65535, nil)
|
|
mockSystemStore := mocks.SystemStore{}
|
|
mockSystemStore.On("GetByName", "UpgradedFromTE").Return(&model.System{Name: "UpgradedFromTE", Value: "false"}, nil)
|
|
mockSystemStore.On("GetByName", "InstallationDate").Return(&model.System{Name: "InstallationDate", Value: "10"}, nil)
|
|
mockSystemStore.On("GetByName", "FirstServerRunTimestamp").Return(&model.System{Name: "FirstServerRunTimestamp", Value: "10"}, nil)
|
|
mockSystemStore.On("Get").Return(model.StringMap{model.SystemServerId: model.NewId()}, nil)
|
|
|
|
mockSessionStore := mocks.SessionStore{}
|
|
mockSessionStore.On("GetSessionsWithActiveDeviceIds", mock.AnythingOfType("string")).Return([]*model.Session{sess1, sess2}, nil)
|
|
mockSessionStore.On("UpdateProps", mock.Anything).Return(nil)
|
|
mockStore.On("User").Return(&mockUserStore)
|
|
mockStore.On("Post").Return(&mockPostStore)
|
|
mockStore.On("System").Return(&mockSystemStore)
|
|
mockStore.On("Session").Return(&mockSessionStore)
|
|
mockStore.On("GetDBSchemaVersion").Return(1, nil)
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.EmailSettings.PushNotificationServer = pushServer.URL
|
|
*cfg.ServiceSettings.CollapsedThreads = model.CollapsedThreadsDisabled
|
|
})
|
|
|
|
err := th.App.updateMobileAppBadgeSync(th.Context, "user1")
|
|
require.Nil(t, err)
|
|
// Server side verification.
|
|
// We verify that 2 requests have been sent, and also check the message contents.
|
|
require.Equal(t, 2, handler.numReqs())
|
|
assert.Equal(t, 1, handler.notifications()[0].ContentAvailable)
|
|
assert.Equal(t, model.PushTypeUpdateBadge, handler.notifications()[0].Type)
|
|
assert.Equal(t, 1, handler.notifications()[1].ContentAvailable)
|
|
assert.Equal(t, model.PushTypeUpdateBadge, handler.notifications()[1].Type)
|
|
}
|
|
|
|
func TestSendTestPushNotification(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t)
|
|
defer th.TearDown()
|
|
|
|
handler := &testPushNotificationHandler{t: t}
|
|
pushServer := httptest.NewServer(
|
|
http.HandlerFunc(handler.handleReq),
|
|
)
|
|
defer pushServer.Close()
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.EmailSettings.PushNotificationServer = pushServer.URL
|
|
})
|
|
|
|
// Per mock definition, first time will send remove, second time will send OK
|
|
result := th.App.SendTestPushNotification(th.Context, "platform:id")
|
|
assert.Equal(t, "false", result)
|
|
result = th.App.SendTestPushNotification(th.Context, "platform:id")
|
|
assert.Equal(t, "true", result)
|
|
|
|
// Server side verification.
|
|
// We verify that 2 requests have been sent, and also check the message contents.
|
|
require.Equal(t, 2, handler.numReqs())
|
|
assert.Equal(t, model.PushTypeTest, handler.notifications()[0].Type)
|
|
assert.Equal(t, model.PushTypeTest, handler.notifications()[1].Type)
|
|
}
|
|
|
|
func TestSendAckToPushProxy(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := SetupWithStoreMock(t)
|
|
defer th.TearDown()
|
|
|
|
handler := &testPushNotificationHandler{t: t}
|
|
pushServer := httptest.NewServer(
|
|
http.HandlerFunc(handler.handleReq),
|
|
)
|
|
defer pushServer.Close()
|
|
|
|
mockStore := th.App.Srv().Store().(*mocks.Store)
|
|
mockUserStore := mocks.UserStore{}
|
|
mockUserStore.On("Count", mock.Anything).Return(int64(10), nil)
|
|
mockPostStore := mocks.PostStore{}
|
|
mockPostStore.On("GetMaxPostSize").Return(65535, nil)
|
|
mockSystemStore := mocks.SystemStore{}
|
|
mockSystemStore.On("GetByName", "UpgradedFromTE").Return(&model.System{Name: "UpgradedFromTE", Value: "false"}, nil)
|
|
mockSystemStore.On("GetByName", "InstallationDate").Return(&model.System{Name: "InstallationDate", Value: "10"}, nil)
|
|
mockSystemStore.On("GetByName", "FirstServerRunTimestamp").Return(&model.System{Name: "FirstServerRunTimestamp", Value: "10"}, nil)
|
|
mockSystemStore.On("Get").Return(model.StringMap{model.SystemServerId: model.NewId()}, nil)
|
|
|
|
mockStore.On("User").Return(&mockUserStore)
|
|
mockStore.On("Post").Return(&mockPostStore)
|
|
mockStore.On("System").Return(&mockSystemStore)
|
|
mockStore.On("GetDBSchemaVersion").Return(1, nil)
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.EmailSettings.PushNotificationServer = pushServer.URL
|
|
})
|
|
|
|
ack := &model.PushNotificationAck{
|
|
Id: "testid",
|
|
NotificationType: model.PushTypeMessage,
|
|
}
|
|
err := th.App.SendAckToPushProxy(th.Context, ack)
|
|
require.NoError(t, err)
|
|
// Server side verification.
|
|
// We verify that 1 request has been sent, and also check the message contents.
|
|
require.Equal(t, 1, handler.numReqs())
|
|
assert.Equal(t, ack.Id, handler.notificationAcks()[0].Id)
|
|
assert.Equal(t, ack.NotificationType, handler.notificationAcks()[0].NotificationType)
|
|
}
|
|
|
|
// TestAllPushNotifications is a master test which sends all various types
|
|
// of notifications and verifies they have been properly sent.
|
|
func TestAllPushNotifications(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
if testing.Short() {
|
|
t.Skip("skipping all push notifications test in short mode")
|
|
}
|
|
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
// Create 10 users, each having 2 sessions.
|
|
type userSession struct {
|
|
user *model.User
|
|
session *model.Session
|
|
}
|
|
var testData []userSession
|
|
for range 10 {
|
|
u := th.CreateUser()
|
|
sess, err := th.App.CreateSession(th.Context, &model.Session{
|
|
UserId: u.Id,
|
|
DeviceId: "deviceID" + u.Id,
|
|
ExpiresAt: model.GetMillis() + 100000,
|
|
})
|
|
require.Nil(t, err)
|
|
// We don't need to track the 2nd session.
|
|
_, err = th.App.CreateSession(th.Context, &model.Session{
|
|
UserId: u.Id,
|
|
DeviceId: "deviceID" + u.Id,
|
|
ExpiresAt: model.GetMillis() + 100000,
|
|
})
|
|
require.Nil(t, err)
|
|
_, err = th.App.AddTeamMember(th.Context, th.BasicTeam.Id, u.Id)
|
|
require.Nil(t, err)
|
|
th.AddUserToChannel(u, th.BasicChannel)
|
|
testData = append(testData, userSession{
|
|
user: u,
|
|
session: sess,
|
|
})
|
|
}
|
|
|
|
handler := &testPushNotificationHandler{
|
|
t: t,
|
|
behavior: "simple",
|
|
}
|
|
pushServer := httptest.NewServer(
|
|
http.HandlerFunc(handler.handleReq),
|
|
)
|
|
defer pushServer.Close()
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.EmailSettings.PushNotificationContents = model.GenericNotification
|
|
*cfg.EmailSettings.PushNotificationServer = pushServer.URL
|
|
})
|
|
|
|
var wg sync.WaitGroup
|
|
for i, data := range testData {
|
|
wg.Add(1)
|
|
// Ranging between 3 types of notifications.
|
|
switch i % 3 {
|
|
case 0:
|
|
go func(user model.User) {
|
|
defer wg.Done()
|
|
notification := &PostNotification{
|
|
Post: th.CreatePost(th.BasicChannel),
|
|
Channel: th.BasicChannel,
|
|
ProfileMap: map[string]*model.User{
|
|
user.Id: &user,
|
|
},
|
|
Sender: &user,
|
|
}
|
|
// testing all 3 notification types.
|
|
th.App.sendPushNotification(notification, &user, true, false, model.CommentsNotifyAny)
|
|
}(*data.user)
|
|
case 1:
|
|
go func(id string) {
|
|
defer wg.Done()
|
|
th.App.UpdateMobileAppBadge(id)
|
|
}(data.user.Id)
|
|
case 2:
|
|
go func(sessID, userID string) {
|
|
defer wg.Done()
|
|
th.App.clearPushNotification(sessID, userID, th.BasicChannel.Id, "")
|
|
}(data.session.Id, data.user.Id)
|
|
}
|
|
}
|
|
wg.Wait()
|
|
|
|
// Hack to let the worker goroutines complete.
|
|
time.Sleep(1 * time.Second)
|
|
// Server side verification.
|
|
assert.Equal(t, 17, handler.numReqs())
|
|
var numClears, numMessages, numUpdateBadges int
|
|
for _, n := range handler.notifications() {
|
|
switch n.Type {
|
|
case model.PushTypeClear:
|
|
numClears++
|
|
assert.Equal(t, th.BasicChannel.Id, n.ChannelId)
|
|
case model.PushTypeMessage:
|
|
numMessages++
|
|
assert.Equal(t, th.BasicChannel.Id, n.ChannelId)
|
|
assert.Contains(t, n.Message, "mentioned you")
|
|
case model.PushTypeUpdateBadge:
|
|
numUpdateBadges++
|
|
assert.Equal(t, "none", n.Sound)
|
|
assert.Equal(t, 1, n.ContentAvailable)
|
|
}
|
|
}
|
|
assert.Equal(t, 8, numMessages)
|
|
assert.Equal(t, 3, numClears)
|
|
assert.Equal(t, 6, numUpdateBadges)
|
|
}
|
|
|
|
func TestPushNotificationRace(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t)
|
|
defer th.TearDown()
|
|
|
|
memoryStore := config.NewTestMemoryStore()
|
|
mockStore := testlib.GetMockStoreForSetupFunctions()
|
|
// Playbooks DB job requires a plugin mock
|
|
pluginStore := mocks.PluginStore{}
|
|
pluginStore.On("List", mock.Anything, mock.Anything, mock.Anything).Return([]string{}, nil)
|
|
mockStore.On("Plugin").Return(&pluginStore)
|
|
mockPreferenceStore := mocks.PreferenceStore{}
|
|
mockPreferenceStore.On("Get",
|
|
mock.AnythingOfType("string"),
|
|
mock.AnythingOfType("string"),
|
|
mock.AnythingOfType("string")).
|
|
Return(&model.Preference{Value: "test"}, nil)
|
|
mockStore.On("Preference").Return(&mockPreferenceStore)
|
|
s := &Server{
|
|
Router: mux.NewRouter(),
|
|
}
|
|
var err error
|
|
s.platform, err = platform.New(
|
|
platform.ServiceConfig{},
|
|
platform.ConfigStore(memoryStore),
|
|
platform.SetFileStore(&fmocks.FileBackend{}),
|
|
platform.SetExportFileStore(&fmocks.FileBackend{}),
|
|
platform.StoreOverride(mockStore))
|
|
require.NoError(t, err)
|
|
ch, err := NewChannels(s)
|
|
require.NoError(t, err)
|
|
s.ch = ch
|
|
|
|
app := New(ServerConnector(s.Channels()))
|
|
require.NotPanics(t, func() {
|
|
s.createPushNotificationsHub(th.Context)
|
|
|
|
s.StopPushNotificationsHubWorkers()
|
|
|
|
// Now we start sending messages after the PN hub is shut down.
|
|
// We test all 3 notification types.
|
|
app.clearPushNotification("currentSessionId", "userId", "channelId", "")
|
|
|
|
app.UpdateMobileAppBadge("userId")
|
|
|
|
notification := &PostNotification{
|
|
Post: &model.Post{},
|
|
Channel: &model.Channel{},
|
|
ProfileMap: map[string]*model.User{
|
|
"userId": {},
|
|
},
|
|
Sender: &model.User{},
|
|
}
|
|
app.sendPushNotification(notification, &model.User{}, true, false, model.CommentsNotifyAny)
|
|
})
|
|
}
|
|
|
|
func TestPushNotificationAttachment(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t)
|
|
defer th.TearDown()
|
|
|
|
originalMessage := "hello world"
|
|
post := &model.Post{
|
|
Message: originalMessage,
|
|
Props: map[string]any{
|
|
model.PostPropsAttachments: []*model.SlackAttachment{
|
|
{
|
|
AuthorName: "testuser",
|
|
Text: "test attachment",
|
|
Fallback: "fallback text",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
user := &model.User{}
|
|
ch := &model.Channel{}
|
|
|
|
t.Run("The notification should contain the fallback message from the attachment", func(t *testing.T) {
|
|
pn := th.App.buildFullPushNotificationMessage(th.Context, "full", post, user, ch, ch.Name, "test", false, false, "")
|
|
assert.Equal(t, "test: hello world\nfallback text", pn.Message)
|
|
})
|
|
|
|
t.Run("The original post message should not be modified", func(t *testing.T) {
|
|
assert.Equal(t, originalMessage, post.Message)
|
|
})
|
|
}
|
|
|
|
// Run it with | grep -v '{"level"' to prevent spamming the console.
|
|
func BenchmarkPushNotificationThroughput(b *testing.B) {
|
|
th := SetupWithStoreMock(b)
|
|
defer th.TearDown()
|
|
|
|
handler := &testPushNotificationHandler{
|
|
t: b,
|
|
behavior: "simple",
|
|
}
|
|
pushServer := httptest.NewServer(
|
|
http.HandlerFunc(handler.handleReq),
|
|
)
|
|
defer pushServer.Close()
|
|
|
|
mockStore := th.App.Srv().Store().(*mocks.Store)
|
|
mockUserStore := mocks.UserStore{}
|
|
mockUserStore.On("Count", mock.Anything).Return(int64(10), nil)
|
|
mockUserStore.On("GetUnreadCount", mock.AnythingOfType("string"), mock.AnythingOfType("bool")).Return(int64(1), nil)
|
|
mockPostStore := mocks.PostStore{}
|
|
mockPostStore.On("GetMaxPostSize").Return(65535, nil)
|
|
mockSystemStore := mocks.SystemStore{}
|
|
mockSystemStore.On("GetByName", "UpgradedFromTE").Return(&model.System{Name: "UpgradedFromTE", Value: "false"}, nil)
|
|
mockSystemStore.On("GetByName", "InstallationDate").Return(&model.System{Name: "InstallationDate", Value: "10"}, nil)
|
|
mockSystemStore.On("GetByName", "FirstServerRunTimestamp").Return(&model.System{Name: "FirstServerRunTimestamp", Value: "10"}, nil)
|
|
mockSystemStore.On("Get").Return(model.StringMap{model.SystemServerId: model.NewId()}, nil)
|
|
|
|
mockSessionStore := mocks.SessionStore{}
|
|
mockPreferenceStore := mocks.PreferenceStore{}
|
|
mockPreferenceStore.On("Get", mock.AnythingOfType("string"), mock.AnythingOfType("string"), mock.AnythingOfType("string")).Return(&model.Preference{Value: "test"}, nil)
|
|
mockStore.On("User").Return(&mockUserStore)
|
|
mockStore.On("Post").Return(&mockPostStore)
|
|
mockStore.On("System").Return(&mockSystemStore)
|
|
mockStore.On("Session").Return(&mockSessionStore)
|
|
mockStore.On("Preference").Return(&mockPreferenceStore)
|
|
mockStore.On("GetDBSchemaVersion").Return(1, nil)
|
|
|
|
// create 50 users, each having 2 sessions.
|
|
type userSession struct {
|
|
user *model.User
|
|
session *model.Session
|
|
}
|
|
var testData []userSession
|
|
for range 50 {
|
|
id := model.NewId()
|
|
u := &model.User{
|
|
Id: id,
|
|
Email: "success+" + id + "@simulator.amazonses.com",
|
|
Username: "un_" + id,
|
|
Nickname: "nn_" + id,
|
|
Password: "Password1",
|
|
EmailVerified: true,
|
|
}
|
|
sess1 := &model.Session{
|
|
Id: "id1",
|
|
UserId: u.Id,
|
|
DeviceId: "deviceID" + u.Id,
|
|
ExpiresAt: model.GetMillis() + 100000,
|
|
}
|
|
sess2 := &model.Session{
|
|
Id: "id2",
|
|
UserId: u.Id,
|
|
DeviceId: "deviceID" + u.Id,
|
|
ExpiresAt: model.GetMillis() + 100000,
|
|
}
|
|
mockSessionStore.On("GetSessionsWithActiveDeviceIds", u.Id).Return([]*model.Session{sess1, sess2}, nil)
|
|
mockSessionStore.On("UpdateProps", mock.Anything).Return(nil)
|
|
|
|
testData = append(testData, userSession{
|
|
user: u,
|
|
session: sess1,
|
|
})
|
|
}
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.EmailSettings.PushNotificationServer = pushServer.URL
|
|
*cfg.LogSettings.EnableConsole = false
|
|
})
|
|
|
|
ch := &model.Channel{
|
|
Id: model.NewId(),
|
|
CreateAt: model.GetMillis(),
|
|
Type: model.ChannelTypeOpen,
|
|
Name: "testch",
|
|
}
|
|
|
|
// We have an inner loop which ranges the testdata slice
|
|
// and we just repeat that.
|
|
then := time.Now()
|
|
cnt := 0
|
|
for b.Loop() {
|
|
cnt++
|
|
var wg sync.WaitGroup
|
|
for j, data := range testData {
|
|
wg.Add(1)
|
|
// Ranging between 3 types of notifications.
|
|
switch j % 3 {
|
|
case 0:
|
|
go func(user model.User) {
|
|
defer wg.Done()
|
|
post := &model.Post{
|
|
UserId: user.Id,
|
|
ChannelId: ch.Id,
|
|
Message: "test message",
|
|
CreateAt: model.GetMillis(),
|
|
}
|
|
notification := &PostNotification{
|
|
Post: post,
|
|
Channel: ch,
|
|
ProfileMap: map[string]*model.User{
|
|
user.Id: &user,
|
|
},
|
|
Sender: &user,
|
|
}
|
|
th.App.sendPushNotification(notification, &user, true, false, model.CommentsNotifyAny)
|
|
}(*data.user)
|
|
case 1:
|
|
go func(id string) {
|
|
defer wg.Done()
|
|
th.App.UpdateMobileAppBadge(id)
|
|
}(data.user.Id)
|
|
case 2:
|
|
go func(sessID, userID string) {
|
|
defer wg.Done()
|
|
th.App.clearPushNotification(sessID, userID, ch.Id, "")
|
|
}(data.session.Id, data.user.Id)
|
|
}
|
|
}
|
|
wg.Wait()
|
|
}
|
|
b.Logf("throughput: %f reqs/s", float64(len(testData)*cnt)/time.Since(then).Seconds())
|
|
time.Sleep(2 * time.Second)
|
|
}
|