email_batching_test.go 12 KB
Newer Older
1
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
2 3
// See License.txt for license information.

4
package app
5 6

import (
7
	"strings"
8 9 10
	"testing"
	"time"

Christopher Speller's avatar
Christopher Speller committed
11 12
	"github.com/mattermost/mattermost-server/model"
	"github.com/mattermost/mattermost-server/store"
13 14 15
)

func TestHandleNewNotifications(t *testing.T) {
16
	th := Setup()
17
	defer th.TearDown()
18 19 20 21 22 23

	id1 := model.NewId()
	id2 := model.NewId()
	id3 := model.NewId()

	// test queueing of received posts by user
24
	job := NewEmailBatchingJob(th.App, 128)
25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77

	job.handleNewNotifications()

	if len(job.pendingNotifications) != 0 {
		t.Fatal("shouldn't have added any pending notifications")
	}

	job.Add(&model.User{Id: id1}, &model.Post{UserId: id1, Message: "test"}, &model.Team{Name: "team"})
	if len(job.pendingNotifications) != 0 {
		t.Fatal("shouldn't have added any pending notifications")
	}

	job.handleNewNotifications()
	if len(job.pendingNotifications) != 1 {
		t.Fatal("should have received posts for 1 user")
	} else if len(job.pendingNotifications[id1]) != 1 {
		t.Fatal("should have received 1 post for user")
	}

	job.Add(&model.User{Id: id1}, &model.Post{UserId: id1, Message: "test"}, &model.Team{Name: "team"})
	job.handleNewNotifications()
	if len(job.pendingNotifications) != 1 {
		t.Fatal("should have received posts for 1 user")
	} else if len(job.pendingNotifications[id1]) != 2 {
		t.Fatal("should have received 2 posts for user1", job.pendingNotifications[id1])
	}

	job.Add(&model.User{Id: id2}, &model.Post{UserId: id1, Message: "test"}, &model.Team{Name: "team"})
	job.handleNewNotifications()
	if len(job.pendingNotifications) != 2 {
		t.Fatal("should have received posts for 2 users")
	} else if len(job.pendingNotifications[id1]) != 2 {
		t.Fatal("should have received 2 posts for user1")
	} else if len(job.pendingNotifications[id2]) != 1 {
		t.Fatal("should have received 1 post for user2")
	}

	job.Add(&model.User{Id: id2}, &model.Post{UserId: id2, Message: "test"}, &model.Team{Name: "team"})
	job.Add(&model.User{Id: id1}, &model.Post{UserId: id3, Message: "test"}, &model.Team{Name: "team"})
	job.Add(&model.User{Id: id3}, &model.Post{UserId: id3, Message: "test"}, &model.Team{Name: "team"})
	job.Add(&model.User{Id: id2}, &model.Post{UserId: id2, Message: "test"}, &model.Team{Name: "team"})
	job.handleNewNotifications()
	if len(job.pendingNotifications) != 3 {
		t.Fatal("should have received posts for 3 users")
	} else if len(job.pendingNotifications[id1]) != 3 {
		t.Fatal("should have received 3 posts for user1")
	} else if len(job.pendingNotifications[id2]) != 3 {
		t.Fatal("should have received 3 posts for user2")
	} else if len(job.pendingNotifications[id3]) != 1 {
		t.Fatal("should have received 1 post for user3")
	}

	// test ordering of received posts
78
	job = NewEmailBatchingJob(th.App, 128)
79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96

	job.Add(&model.User{Id: id1}, &model.Post{UserId: id1, Message: "test1"}, &model.Team{Name: "team"})
	job.Add(&model.User{Id: id1}, &model.Post{UserId: id1, Message: "test2"}, &model.Team{Name: "team"})
	job.Add(&model.User{Id: id2}, &model.Post{UserId: id1, Message: "test3"}, &model.Team{Name: "team"})
	job.Add(&model.User{Id: id1}, &model.Post{UserId: id1, Message: "test4"}, &model.Team{Name: "team"})
	job.Add(&model.User{Id: id2}, &model.Post{UserId: id1, Message: "test5"}, &model.Team{Name: "team"})
	job.handleNewNotifications()
	if job.pendingNotifications[id1][0].post.Message != "test1" ||
		job.pendingNotifications[id1][1].post.Message != "test2" ||
		job.pendingNotifications[id1][2].post.Message != "test4" {
		t.Fatal("incorrect order of received posts for user1")
	} else if job.pendingNotifications[id2][0].post.Message != "test3" ||
		job.pendingNotifications[id2][1].post.Message != "test5" {
		t.Fatal("incorrect order of received posts for user2")
	}
}

func TestCheckPendingNotifications(t *testing.T) {
Chris's avatar
Chris committed
97
	th := Setup().InitBasic()
98
	defer th.TearDown()
99

100
	job := NewEmailBatchingJob(th.App, 128)
101
	job.pendingNotifications[th.BasicUser.Id] = []*batchedNotification{
102 103
		{
			post: &model.Post{
Chris's avatar
Chris committed
104
				UserId:    th.BasicUser.Id,
105
				ChannelId: th.BasicChannel.Id,
Chris's avatar
Chris committed
106
				CreateAt:  10000000,
107
			},
108
			teamName: th.BasicTeam.Name,
109 110 111
		},
	}

Chris's avatar
Chris committed
112
	channelMember := store.Must(th.App.Srv.Store.Channel().GetMember(th.BasicChannel.Id, th.BasicUser.Id)).(*model.ChannelMember)
113
	channelMember.LastViewedAt = 9999999
Chris's avatar
Chris committed
114
	store.Must(th.App.Srv.Store.Channel().UpdateMember(channelMember))
115

Chris's avatar
Chris committed
116
	store.Must(th.App.Srv.Store.Preference().Save(&model.Preferences{{
117
		UserId:   th.BasicUser.Id,
118 119 120 121 122 123 124 125
		Category: model.PREFERENCE_CATEGORY_NOTIFICATIONS,
		Name:     model.PREFERENCE_NAME_EMAIL_INTERVAL,
		Value:    "60",
	}}))

	// test that notifications aren't sent before interval
	job.checkPendingNotifications(time.Unix(10001, 0), func(string, []*batchedNotification) {})

126 127
	if job.pendingNotifications[th.BasicUser.Id] == nil || len(job.pendingNotifications[th.BasicUser.Id]) != 1 {
		t.Fatal("shouldn't have sent queued post")
128 129 130
	}

	// test that notifications are cleared if the user has acted
Chris's avatar
Chris committed
131
	channelMember = store.Must(th.App.Srv.Store.Channel().GetMember(th.BasicChannel.Id, th.BasicUser.Id)).(*model.ChannelMember)
132
	channelMember.LastViewedAt = 10001000
Chris's avatar
Chris committed
133
	store.Must(th.App.Srv.Store.Channel().UpdateMember(channelMember))
134 135 136

	job.checkPendingNotifications(time.Unix(10002, 0), func(string, []*batchedNotification) {})

137
	if job.pendingNotifications[th.BasicUser.Id] != nil && len(job.pendingNotifications[th.BasicUser.Id]) != 0 {
138 139 140 141
		t.Fatal("should've remove queued post since user acted")
	}

	// test that notifications are sent if enough time passes since the first message
142
	job.pendingNotifications[th.BasicUser.Id] = []*batchedNotification{
143 144
		{
			post: &model.Post{
Chris's avatar
Chris committed
145
				UserId:    th.BasicUser.Id,
146
				ChannelId: th.BasicChannel.Id,
Chris's avatar
Chris committed
147 148
				CreateAt:  10060000,
				Message:   "post1",
149
			},
150
			teamName: th.BasicTeam.Name,
151 152 153
		},
		{
			post: &model.Post{
Chris's avatar
Chris committed
154
				UserId:    th.BasicUser.Id,
155
				ChannelId: th.BasicChannel.Id,
Chris's avatar
Chris committed
156 157
				CreateAt:  10090000,
				Message:   "post2",
158
			},
159
			teamName: th.BasicTeam.Name,
160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177
		},
	}

	received := make(chan *model.Post, 2)
	timeout := make(chan bool)

	job.checkPendingNotifications(time.Unix(10130, 0), func(s string, notifications []*batchedNotification) {
		for _, notification := range notifications {
			received <- notification.post
		}
	})

	go func() {
		// start a timeout to make sure that we don't get stuck here on a failed test
		time.Sleep(5 * time.Second)
		timeout <- true
	}()

178
	if job.pendingNotifications[th.BasicUser.Id] != nil && len(job.pendingNotifications[th.BasicUser.Id]) != 0 {
179 180 181 182 183 184 185 186
		t.Fatal("should've remove queued posts when sending messages")
	}

	select {
	case post := <-received:
		if post.Message != "post1" {
			t.Fatal("should've received post1 first")
		}
Chris's avatar
Chris committed
187
	case <-timeout:
188 189 190 191 192 193 194 195
		t.Fatal("timed out waiting for first post notification")
	}

	select {
	case post := <-received:
		if post.Message != "post2" {
			t.Fatal("should've received post2 second")
		}
Chris's avatar
Chris committed
196
	case <-timeout:
197 198 199
		t.Fatal("timed out waiting for second post notification")
	}
}
200 201 202 203 204

/**
 * Ensures that email batch interval defaults to 15 minutes for users that haven't explicitly set this preference
 */
func TestCheckPendingNotificationsDefaultInterval(t *testing.T) {
Chris's avatar
Chris committed
205
	th := Setup().InitBasic()
206 207
	defer th.TearDown()

208
	job := NewEmailBatchingJob(th.App, 128)
209 210

	// bypasses recent user activity check
Chris's avatar
Chris committed
211
	channelMember := store.Must(th.App.Srv.Store.Channel().GetMember(th.BasicChannel.Id, th.BasicUser.Id)).(*model.ChannelMember)
212
	channelMember.LastViewedAt = 9999000
Chris's avatar
Chris committed
213
	store.Must(th.App.Srv.Store.Channel().UpdateMember(channelMember))
214

215
	job.pendingNotifications[th.BasicUser.Id] = []*batchedNotification{
216 217
		{
			post: &model.Post{
Chris's avatar
Chris committed
218
				UserId:    th.BasicUser.Id,
219
				ChannelId: th.BasicChannel.Id,
Chris's avatar
Chris committed
220
				CreateAt:  10000000,
221
			},
222
			teamName: th.BasicTeam.Name,
223 224 225 226 227
		},
	}

	// notifications should not be sent 1s after post was created, because default batch interval is 15mins
	job.checkPendingNotifications(time.Unix(10001, 0), func(string, []*batchedNotification) {})
228
	if job.pendingNotifications[th.BasicUser.Id] == nil || len(job.pendingNotifications[th.BasicUser.Id]) != 1 {
229 230 231 232 233
		t.Fatal("shouldn't have sent queued post")
	}

	// notifications should be sent 901s after post was created, because default batch interval is 15mins
	job.checkPendingNotifications(time.Unix(10901, 0), func(string, []*batchedNotification) {})
234
	if job.pendingNotifications[th.BasicUser.Id] != nil || len(job.pendingNotifications[th.BasicUser.Id]) != 0 {
235 236 237 238 239 240 241 242
		t.Fatal("should have sent queued post")
	}
}

/**
 * Ensures that email batch interval defaults to 15 minutes if user preference is invalid
 */
func TestCheckPendingNotificationsCantParseInterval(t *testing.T) {
Chris's avatar
Chris committed
243
	th := Setup().InitBasic()
244 245
	defer th.TearDown()

246
	job := NewEmailBatchingJob(th.App, 128)
247 248

	// bypasses recent user activity check
Chris's avatar
Chris committed
249
	channelMember := store.Must(th.App.Srv.Store.Channel().GetMember(th.BasicChannel.Id, th.BasicUser.Id)).(*model.ChannelMember)
250
	channelMember.LastViewedAt = 9999000
Chris's avatar
Chris committed
251
	store.Must(th.App.Srv.Store.Channel().UpdateMember(channelMember))
252 253

	// preference value is not an integer, so we'll fall back to the default 15min value
Chris's avatar
Chris committed
254
	store.Must(th.App.Srv.Store.Preference().Save(&model.Preferences{{
255
		UserId:   th.BasicUser.Id,
256 257 258 259 260
		Category: model.PREFERENCE_CATEGORY_NOTIFICATIONS,
		Name:     model.PREFERENCE_NAME_EMAIL_INTERVAL,
		Value:    "notAnIntegerValue",
	}}))

261
	job.pendingNotifications[th.BasicUser.Id] = []*batchedNotification{
262 263
		{
			post: &model.Post{
Chris's avatar
Chris committed
264
				UserId:    th.BasicUser.Id,
265
				ChannelId: th.BasicChannel.Id,
Chris's avatar
Chris committed
266
				CreateAt:  10000000,
267
			},
268
			teamName: th.BasicTeam.Name,
269 270 271 272 273
		},
	}

	// notifications should not be sent 1s after post was created, because default batch interval is 15mins
	job.checkPendingNotifications(time.Unix(10001, 0), func(string, []*batchedNotification) {})
274
	if job.pendingNotifications[th.BasicUser.Id] == nil || len(job.pendingNotifications[th.BasicUser.Id]) != 1 {
275 276 277 278 279
		t.Fatal("shouldn't have sent queued post")
	}

	// notifications should be sent 901s after post was created, because default batch interval is 15mins
	job.checkPendingNotifications(time.Unix(10901, 0), func(string, []*batchedNotification) {})
280
	if job.pendingNotifications[th.BasicUser.Id] != nil || len(job.pendingNotifications[th.BasicUser.Id]) != 0 {
281 282 283
		t.Fatal("should have sent queued post")
	}
}
284 285 286 287 288

/*
 * Ensures that post contents are not included in notification email when email notification content type is set to generic
 */
func TestRenderBatchedPostGeneric(t *testing.T) {
Chris's avatar
Chris committed
289
	th := Setup()
290 291
	defer th.TearDown()

292 293 294 295 296 297 298 299 300 301 302 303 304 305
	var post = &model.Post{}
	post.Message = "This is the message"
	var notification = &batchedNotification{}
	notification.post = post
	var channel = &model.Channel{}
	channel.DisplayName = "Some Test Channel"
	var sender = &model.User{}
	sender.Email = "sender@test.com"

	translateFunc := func(translationID string, args ...interface{}) string {
		// mock translateFunc just returns the translation id - this is good enough for our purposes
		return translationID
	}

Chris's avatar
Chris committed
306
	var rendered = th.App.renderBatchedPost(notification, channel, sender, "http://localhost:8065", "", translateFunc, "en", model.EMAIL_NOTIFICATION_CONTENTS_GENERIC)
307 308 309 310 311 312 313 314 315
	if strings.Contains(rendered, post.Message) {
		t.Fatal("Rendered email should not contain post contents when email notification contents type is set to Generic.")
	}
}

/*
 * Ensures that post contents included in notification email when email notification content type is set to full
 */
func TestRenderBatchedPostFull(t *testing.T) {
Chris's avatar
Chris committed
316
	th := Setup()
317 318
	defer th.TearDown()

319 320 321 322 323 324 325 326 327 328 329 330 331 332
	var post = &model.Post{}
	post.Message = "This is the message"
	var notification = &batchedNotification{}
	notification.post = post
	var channel = &model.Channel{}
	channel.DisplayName = "Some Test Channel"
	var sender = &model.User{}
	sender.Email = "sender@test.com"

	translateFunc := func(translationID string, args ...interface{}) string {
		// mock translateFunc just returns the translation id - this is good enough for our purposes
		return translationID
	}

Chris's avatar
Chris committed
333
	var rendered = th.App.renderBatchedPost(notification, channel, sender, "http://localhost:8065", "", translateFunc, "en", model.EMAIL_NOTIFICATION_CONTENTS_FULL)
334 335 336 337
	if !strings.Contains(rendered, post.Message) {
		t.Fatal("Rendered email should contain post contents when email notification contents type is set to Full.")
	}
}