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  }