github.com/mattermosttest/mattermost-server/v5@v5.0.0-20200917143240-9dfa12e121f9/app/email_batching_test.go (about) 1 // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. 2 // See LICENSE.txt for license information. 3 4 package app 5 6 import ( 7 "sync/atomic" 8 "testing" 9 "time" 10 11 "github.com/stretchr/testify/assert" 12 13 "github.com/mattermost/mattermost-server/v5/model" 14 "github.com/stretchr/testify/require" 15 ) 16 17 func TestHandleNewNotifications(t *testing.T) { 18 th := SetupWithStoreMock(t) 19 defer th.TearDown() 20 21 id1 := model.NewId() 22 id2 := model.NewId() 23 id3 := model.NewId() 24 25 // test queueing of received posts by user 26 job := NewEmailBatchingJob(th.Server.EmailService, 128) 27 28 job.handleNewNotifications() 29 30 require.Empty(t, job.pendingNotifications, "shouldn't have added any pending notifications") 31 32 job.Add(&model.User{Id: id1}, &model.Post{UserId: id1, Message: "test"}, &model.Team{Name: "team"}) 33 require.Empty(t, job.pendingNotifications, "shouldn't have added any pending notifications") 34 35 job.handleNewNotifications() 36 require.Len(t, job.pendingNotifications, 1, "should have received posts for 1 user") 37 require.Len(t, job.pendingNotifications[id1], 1, "should have received 1 post for user") 38 39 job.Add(&model.User{Id: id1}, &model.Post{UserId: id1, Message: "test"}, &model.Team{Name: "team"}) 40 job.handleNewNotifications() 41 require.Len(t, job.pendingNotifications, 1, "should have received posts for 1 user") 42 require.Len(t, job.pendingNotifications[id1], 2, "should have received 2 posts for user1") 43 44 job.Add(&model.User{Id: id2}, &model.Post{UserId: id1, Message: "test"}, &model.Team{Name: "team"}) 45 job.handleNewNotifications() 46 require.Len(t, job.pendingNotifications, 2, "should have received posts for 2 users") 47 require.Len(t, job.pendingNotifications[id1], 2, "should have received 2 posts for user1") 48 require.Len(t, job.pendingNotifications[id2], 1, "should have received 1 post for user2") 49 50 job.Add(&model.User{Id: id2}, &model.Post{UserId: id2, Message: "test"}, &model.Team{Name: "team"}) 51 job.Add(&model.User{Id: id1}, &model.Post{UserId: id3, Message: "test"}, &model.Team{Name: "team"}) 52 job.Add(&model.User{Id: id3}, &model.Post{UserId: id3, Message: "test"}, &model.Team{Name: "team"}) 53 job.Add(&model.User{Id: id2}, &model.Post{UserId: id2, Message: "test"}, &model.Team{Name: "team"}) 54 job.handleNewNotifications() 55 require.Len(t, job.pendingNotifications, 3, "should have received posts for 3 users") 56 require.Len(t, job.pendingNotifications[id1], 3, "should have received 3 posts for user1") 57 require.Len(t, job.pendingNotifications[id2], 3, "should have received 3 posts for user2") 58 require.Len(t, job.pendingNotifications[id3], 1, "should have received 1 post for user3") 59 60 // test ordering of received posts 61 job = NewEmailBatchingJob(th.Server.EmailService, 128) 62 63 job.Add(&model.User{Id: id1}, &model.Post{UserId: id1, Message: "test1"}, &model.Team{Name: "team"}) 64 job.Add(&model.User{Id: id1}, &model.Post{UserId: id1, Message: "test2"}, &model.Team{Name: "team"}) 65 job.Add(&model.User{Id: id2}, &model.Post{UserId: id1, Message: "test3"}, &model.Team{Name: "team"}) 66 job.Add(&model.User{Id: id1}, &model.Post{UserId: id1, Message: "test4"}, &model.Team{Name: "team"}) 67 job.Add(&model.User{Id: id2}, &model.Post{UserId: id1, Message: "test5"}, &model.Team{Name: "team"}) 68 job.handleNewNotifications() 69 assert.Equal(t, job.pendingNotifications[id1][0].post.Message, "test1", "incorrect order of received posts for user1") 70 assert.Equal(t, job.pendingNotifications[id1][1].post.Message, "test2", "incorrect order of received posts for user1") 71 assert.Equal(t, job.pendingNotifications[id1][2].post.Message, "test4", "incorrect order of received posts for user1") 72 assert.Equal(t, job.pendingNotifications[id2][0].post.Message, "test3", "incorrect order of received posts for user2") 73 assert.Equal(t, job.pendingNotifications[id2][1].post.Message, "test5", "incorrect order of received posts for user2") 74 } 75 76 func TestCheckPendingNotifications(t *testing.T) { 77 th := Setup(t).InitBasic() 78 defer th.TearDown() 79 80 job := NewEmailBatchingJob(th.Server.EmailService, 128) 81 job.pendingNotifications[th.BasicUser.Id] = []*batchedNotification{ 82 { 83 post: &model.Post{ 84 UserId: th.BasicUser.Id, 85 ChannelId: th.BasicChannel.Id, 86 CreateAt: 10000000, 87 }, 88 teamName: th.BasicTeam.Name, 89 }, 90 } 91 92 channelMember, err := th.App.Srv().Store.Channel().GetMember(th.BasicChannel.Id, th.BasicUser.Id) 93 require.Nil(t, err) 94 channelMember.LastViewedAt = 9999999 95 _, err = th.App.Srv().Store.Channel().UpdateMember(channelMember) 96 require.Nil(t, err) 97 98 nErr := th.App.Srv().Store.Preference().Save(&model.Preferences{{ 99 UserId: th.BasicUser.Id, 100 Category: model.PREFERENCE_CATEGORY_NOTIFICATIONS, 101 Name: model.PREFERENCE_NAME_EMAIL_INTERVAL, 102 Value: "60", 103 }}) 104 require.Nil(t, nErr) 105 106 // test that notifications aren't sent before interval 107 job.checkPendingNotifications(time.Unix(10001, 0), func(string, []*batchedNotification) {}) 108 109 require.NotNil(t, job.pendingNotifications[th.BasicUser.Id]) 110 require.Len(t, job.pendingNotifications[th.BasicUser.Id], 1, "shouldn't have sent queued post") 111 112 // test that notifications are cleared if the user has acted 113 channelMember, err = th.App.Srv().Store.Channel().GetMember(th.BasicChannel.Id, th.BasicUser.Id) 114 require.Nil(t, err) 115 channelMember.LastViewedAt = 10001000 116 _, err = th.App.Srv().Store.Channel().UpdateMember(channelMember) 117 require.Nil(t, err) 118 119 // We reset the interval to something shorter 120 nErr = th.App.Srv().Store.Preference().Save(&model.Preferences{{ 121 UserId: th.BasicUser.Id, 122 Category: model.PREFERENCE_CATEGORY_NOTIFICATIONS, 123 Name: model.PREFERENCE_NAME_EMAIL_INTERVAL, 124 Value: "10", 125 }}) 126 require.Nil(t, nErr) 127 128 var wasCalled int32 129 job.checkPendingNotifications(time.Unix(10050, 0), func(string, []*batchedNotification) { 130 atomic.StoreInt32(&wasCalled, int32(1)) 131 }) 132 133 // A hack to check whether the handler was called. 134 // It's not straightforward to just wait for it using a channel because the test should 135 // NOT call the handler, and it will be called only if the test fails. 136 time.Sleep(1 * time.Second) 137 // We do a check outside the email handler, because otherwise, failing from 138 // inside the handler doesn't let the .Go() function exit cleanly, and it gets 139 // stuck during server shutdown, trying to wait for the goroutine to exit 140 require.Equal(t, atomic.LoadInt32(&wasCalled), int32(0), "email handler should not have been called") 141 142 require.Nil(t, job.pendingNotifications[th.BasicUser.Id]) 143 require.Empty(t, job.pendingNotifications[th.BasicUser.Id], "should've remove queued post since user acted") 144 145 // test that notifications are sent if enough time passes since the first message 146 job.pendingNotifications[th.BasicUser.Id] = []*batchedNotification{ 147 { 148 post: &model.Post{ 149 UserId: th.BasicUser.Id, 150 ChannelId: th.BasicChannel.Id, 151 CreateAt: 10060000, 152 Message: "post1", 153 }, 154 teamName: th.BasicTeam.Name, 155 }, 156 { 157 post: &model.Post{ 158 UserId: th.BasicUser.Id, 159 ChannelId: th.BasicChannel.Id, 160 CreateAt: 10090000, 161 Message: "post2", 162 }, 163 teamName: th.BasicTeam.Name, 164 }, 165 } 166 167 received := make(chan *model.Post, 2) 168 timeout := make(chan bool) 169 170 job.checkPendingNotifications(time.Unix(10130, 0), func(s string, notifications []*batchedNotification) { 171 for _, notification := range notifications { 172 received <- notification.post 173 } 174 }) 175 176 go func() { 177 // start a timeout to make sure that we don't get stuck here on a failed test 178 time.Sleep(5 * time.Second) 179 timeout <- true 180 }() 181 182 require.Nil(t, job.pendingNotifications[th.BasicUser.Id], "shouldn't have sent queued post") 183 184 select { 185 case post := <-received: 186 require.Equal(t, post.Message, "post1", "should've received post1 first") 187 188 case <-timeout: 189 require.Fail(t, "timed out waiting for first post notification") 190 } 191 192 select { 193 case post := <-received: 194 require.Equal(t, post.Message, "post2", "should've received post2 second") 195 196 case <-timeout: 197 require.Fail(t, "timed out waiting for second post notification") 198 } 199 } 200 201 /** 202 * Ensures that email batch interval defaults to 15 minutes for users that haven't explicitly set this preference 203 */ 204 func TestCheckPendingNotificationsDefaultInterval(t *testing.T) { 205 th := Setup(t).InitBasic() 206 defer th.TearDown() 207 208 job := NewEmailBatchingJob(th.Server.EmailService, 128) 209 210 // bypasses recent user activity check 211 channelMember, err := th.App.Srv().Store.Channel().GetMember(th.BasicChannel.Id, th.BasicUser.Id) 212 require.Nil(t, err) 213 channelMember.LastViewedAt = 9999000 214 _, err = th.App.Srv().Store.Channel().UpdateMember(channelMember) 215 require.Nil(t, err) 216 217 job.pendingNotifications[th.BasicUser.Id] = []*batchedNotification{ 218 { 219 post: &model.Post{ 220 UserId: th.BasicUser.Id, 221 ChannelId: th.BasicChannel.Id, 222 CreateAt: 10000000, 223 }, 224 teamName: th.BasicTeam.Name, 225 }, 226 } 227 228 // notifications should not be sent 1s after post was created, because default batch interval is 15mins 229 job.checkPendingNotifications(time.Unix(10001, 0), func(string, []*batchedNotification) {}) 230 require.NotNil(t, job.pendingNotifications[th.BasicUser.Id]) 231 require.Len(t, job.pendingNotifications[th.BasicUser.Id], 1, "shouldn't have sent queued post") 232 233 // notifications should be sent 901s after post was created, because default batch interval is 15mins 234 job.checkPendingNotifications(time.Unix(10901, 0), func(string, []*batchedNotification) {}) 235 require.Nil(t, job.pendingNotifications[th.BasicUser.Id]) 236 require.Empty(t, job.pendingNotifications[th.BasicUser.Id], "should have sent queued post") 237 } 238 239 /** 240 * Ensures that email batch interval defaults to 15 minutes if user preference is invalid 241 */ 242 func TestCheckPendingNotificationsCantParseInterval(t *testing.T) { 243 th := Setup(t).InitBasic() 244 defer th.TearDown() 245 246 job := NewEmailBatchingJob(th.Server.EmailService, 128) 247 248 // bypasses recent user activity check 249 channelMember, err := th.App.Srv().Store.Channel().GetMember(th.BasicChannel.Id, th.BasicUser.Id) 250 require.Nil(t, err) 251 channelMember.LastViewedAt = 9999000 252 _, err = th.App.Srv().Store.Channel().UpdateMember(channelMember) 253 require.Nil(t, err) 254 255 // preference value is not an integer, so we'll fall back to the default 15min value 256 nErr := th.App.Srv().Store.Preference().Save(&model.Preferences{{ 257 UserId: th.BasicUser.Id, 258 Category: model.PREFERENCE_CATEGORY_NOTIFICATIONS, 259 Name: model.PREFERENCE_NAME_EMAIL_INTERVAL, 260 Value: "notAnIntegerValue", 261 }}) 262 require.Nil(t, nErr) 263 264 job.pendingNotifications[th.BasicUser.Id] = []*batchedNotification{ 265 { 266 post: &model.Post{ 267 UserId: th.BasicUser.Id, 268 ChannelId: th.BasicChannel.Id, 269 CreateAt: 10000000, 270 }, 271 teamName: th.BasicTeam.Name, 272 }, 273 } 274 275 // notifications should not be sent 1s after post was created, because default batch interval is 15mins 276 job.checkPendingNotifications(time.Unix(10001, 0), func(string, []*batchedNotification) {}) 277 require.NotNil(t, job.pendingNotifications[th.BasicUser.Id]) 278 require.Len(t, job.pendingNotifications[th.BasicUser.Id], 1, "shouldn't have sent queued post") 279 280 // notifications should be sent 901s after post was created, because default batch interval is 15mins 281 job.checkPendingNotifications(time.Unix(10901, 0), func(string, []*batchedNotification) {}) 282 283 require.Nil(t, job.pendingNotifications[th.BasicUser.Id], "should have sent queued post") 284 } 285 286 /* 287 * Ensures that post contents are not included in notification email when email notification content type is set to generic 288 */ 289 func TestRenderBatchedPostGeneric(t *testing.T) { 290 th := SetupWithStoreMock(t) 291 defer th.TearDown() 292 293 var post = &model.Post{} 294 post.Message = "This is the message" 295 var notification = &batchedNotification{} 296 notification.post = post 297 var channel = &model.Channel{} 298 channel.DisplayName = "Some Test Channel" 299 var sender = &model.User{} 300 sender.Email = "sender@test.com" 301 302 translateFunc := func(translationID string, args ...interface{}) string { 303 // mock translateFunc just returns the translation id - this is good enough for our purposes 304 return translationID 305 } 306 307 var rendered = th.Server.EmailService.renderBatchedPost(notification, channel, sender, "http://localhost:8065", "", translateFunc, "en", model.EMAIL_NOTIFICATION_CONTENTS_GENERIC) 308 require.NotContains(t, rendered, post.Message, "Rendered email should not contain post contents when email notification contents type is set to Generic.") 309 } 310 311 /* 312 * Ensures that post contents included in notification email when email notification content type is set to full 313 */ 314 func TestRenderBatchedPostFull(t *testing.T) { 315 th := SetupWithStoreMock(t) 316 defer th.TearDown() 317 318 var post = &model.Post{} 319 post.Message = "This is the message" 320 var notification = &batchedNotification{} 321 notification.post = post 322 var channel = &model.Channel{} 323 channel.DisplayName = "Some Test Channel" 324 var sender = &model.User{} 325 sender.Email = "sender@test.com" 326 327 translateFunc := func(translationID string, args ...interface{}) string { 328 // mock translateFunc just returns the translation id - this is good enough for our purposes 329 return translationID 330 } 331 332 var rendered = th.Server.EmailService.renderBatchedPost(notification, channel, sender, "http://localhost:8065", "", translateFunc, "en", model.EMAIL_NOTIFICATION_CONTENTS_FULL) 333 require.Contains(t, rendered, post.Message, "Rendered email should contain post contents when email notification contents type is set to Full.") 334 }