github.com/mattermost/mattermost-server/v5@v5.39.3/store/storetest/thread_store.go (about)

     1  // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
     2  // See LICENSE.txt for license information.
     3  
     4  package storetest
     5  
     6  import (
     7  	"context"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/stretchr/testify/assert"
    12  	"github.com/stretchr/testify/require"
    13  
    14  	"github.com/mattermost/mattermost-server/v5/model"
    15  	"github.com/mattermost/mattermost-server/v5/store"
    16  )
    17  
    18  func TestThreadStore(t *testing.T, ss store.Store, s SqlStore) {
    19  	t.Run("ThreadSQLOperations", func(t *testing.T) { testThreadSQLOperations(t, ss, s) })
    20  	t.Run("ThreadStorePopulation", func(t *testing.T) { testThreadStorePopulation(t, ss) })
    21  	t.Run("ThreadStorePermanentDeleteBatchForRetentionPolicies", func(t *testing.T) {
    22  		testThreadStorePermanentDeleteBatchForRetentionPolicies(t, ss)
    23  	})
    24  	t.Run("ThreadStorePermanentDeleteBatchThreadMembershipsForRetentionPolicies", func(t *testing.T) {
    25  		testThreadStorePermanentDeleteBatchThreadMembershipsForRetentionPolicies(t, ss)
    26  	})
    27  }
    28  
    29  func testThreadStorePopulation(t *testing.T, ss store.Store) {
    30  	makeSomePosts := func() []*model.Post {
    31  
    32  		u1 := model.User{
    33  			Email:    MakeEmail(),
    34  			Username: model.NewId(),
    35  		}
    36  
    37  		u, err := ss.User().Save(&u1)
    38  		require.NoError(t, err)
    39  
    40  		c, err2 := ss.Channel().Save(&model.Channel{
    41  			DisplayName: model.NewId(),
    42  			Type:        model.CHANNEL_OPEN,
    43  			Name:        model.NewId(),
    44  		}, 999)
    45  		require.NoError(t, err2)
    46  
    47  		_, err44 := ss.Channel().SaveMember(&model.ChannelMember{
    48  			ChannelId:   c.Id,
    49  			UserId:      u1.Id,
    50  			NotifyProps: model.GetDefaultChannelNotifyProps(),
    51  			MsgCount:    0,
    52  		})
    53  		require.NoError(t, err44)
    54  		o := model.Post{}
    55  		o.ChannelId = c.Id
    56  		o.UserId = u.Id
    57  		o.Message = "zz" + model.NewId() + "b"
    58  
    59  		otmp, err3 := ss.Post().Save(&o)
    60  		require.NoError(t, err3)
    61  		o2 := model.Post{}
    62  		o2.ChannelId = c.Id
    63  		o2.UserId = model.NewId()
    64  		o2.RootId = otmp.Id
    65  		o2.Message = "zz" + model.NewId() + "b"
    66  
    67  		o3 := model.Post{}
    68  		o3.ChannelId = c.Id
    69  		o3.UserId = u.Id
    70  		o3.RootId = otmp.Id
    71  		o3.Message = "zz" + model.NewId() + "b"
    72  
    73  		o4 := model.Post{}
    74  		o4.ChannelId = c.Id
    75  		o4.UserId = model.NewId()
    76  		o4.Message = "zz" + model.NewId() + "b"
    77  
    78  		newPosts, errIdx, err3 := ss.Post().SaveMultiple([]*model.Post{&o2, &o3, &o4})
    79  
    80  		olist, _ := ss.Post().Get(context.Background(), otmp.Id, true, false, false, "")
    81  		o1 := olist.Posts[olist.Order[0]]
    82  
    83  		newPosts = append([]*model.Post{o1}, newPosts...)
    84  		require.NoError(t, err3, "couldn't save item")
    85  		require.Equal(t, -1, errIdx)
    86  		require.Len(t, newPosts, 4)
    87  		require.Equal(t, int64(2), newPosts[0].ReplyCount)
    88  		require.Equal(t, int64(2), newPosts[1].ReplyCount)
    89  		require.Equal(t, int64(2), newPosts[2].ReplyCount)
    90  		require.Equal(t, int64(0), newPosts[3].ReplyCount)
    91  
    92  		return newPosts
    93  	}
    94  	t.Run("Save replies creates a thread", func(t *testing.T) {
    95  		newPosts := makeSomePosts()
    96  		thread, err := ss.Thread().Get(newPosts[0].Id)
    97  		require.NoError(t, err, "couldn't get thread")
    98  		require.NotNil(t, thread)
    99  		require.Equal(t, int64(2), thread.ReplyCount)
   100  		require.ElementsMatch(t, model.StringArray{newPosts[0].UserId, newPosts[1].UserId}, thread.Participants)
   101  
   102  		o5 := model.Post{}
   103  		o5.ChannelId = model.NewId()
   104  		o5.UserId = model.NewId()
   105  		o5.RootId = newPosts[0].Id
   106  		o5.Message = "zz" + model.NewId() + "b"
   107  
   108  		_, _, err = ss.Post().SaveMultiple([]*model.Post{&o5})
   109  		require.NoError(t, err, "couldn't save item")
   110  
   111  		thread, err = ss.Thread().Get(newPosts[0].Id)
   112  		require.NoError(t, err, "couldn't get thread")
   113  		require.NotNil(t, thread)
   114  		require.Equal(t, int64(3), thread.ReplyCount)
   115  		require.ElementsMatch(t, model.StringArray{newPosts[0].UserId, newPosts[1].UserId, o5.UserId}, thread.Participants)
   116  	})
   117  
   118  	t.Run("Delete a reply updates count on a thread", func(t *testing.T) {
   119  		newPosts := makeSomePosts()
   120  		thread, err := ss.Thread().Get(newPosts[0].Id)
   121  		require.NoError(t, err, "couldn't get thread")
   122  		require.NotNil(t, thread)
   123  		require.Equal(t, int64(2), thread.ReplyCount)
   124  		require.ElementsMatch(t, model.StringArray{newPosts[0].UserId, newPosts[1].UserId}, thread.Participants)
   125  
   126  		err = ss.Post().Delete(newPosts[1].Id, 1234, model.NewId())
   127  		require.NoError(t, err, "couldn't delete post")
   128  
   129  		thread, err = ss.Thread().Get(newPosts[0].Id)
   130  		require.NoError(t, err, "couldn't get thread")
   131  		require.NotNil(t, thread)
   132  		require.Equal(t, int64(1), thread.ReplyCount)
   133  		require.ElementsMatch(t, model.StringArray{newPosts[0].UserId, newPosts[1].UserId}, thread.Participants)
   134  	})
   135  
   136  	t.Run("Update reply should update the UpdateAt of the thread", func(t *testing.T) {
   137  		rootPost := model.Post{}
   138  		rootPost.RootId = model.NewId()
   139  		rootPost.ChannelId = model.NewId()
   140  		rootPost.UserId = model.NewId()
   141  		rootPost.Message = "zz" + model.NewId() + "b"
   142  
   143  		replyPost := model.Post{}
   144  		replyPost.ChannelId = rootPost.ChannelId
   145  		replyPost.UserId = model.NewId()
   146  		replyPost.Message = "zz" + model.NewId() + "b"
   147  		replyPost.RootId = rootPost.RootId
   148  
   149  		newPosts, _, err := ss.Post().SaveMultiple([]*model.Post{&rootPost, &replyPost})
   150  		require.NoError(t, err)
   151  
   152  		thread1, err := ss.Thread().Get(newPosts[0].RootId)
   153  		require.NoError(t, err)
   154  
   155  		rrootPost, err := ss.Post().GetSingle(rootPost.Id, false)
   156  		require.NoError(t, err)
   157  		require.Equal(t, rrootPost.UpdateAt, rootPost.UpdateAt)
   158  
   159  		replyPost2 := model.Post{}
   160  		replyPost2.ChannelId = rootPost.ChannelId
   161  		replyPost2.UserId = model.NewId()
   162  		replyPost2.Message = "zz" + model.NewId() + "b"
   163  		replyPost2.RootId = rootPost.Id
   164  
   165  		replyPost3 := model.Post{}
   166  		replyPost3.ChannelId = rootPost.ChannelId
   167  		replyPost3.UserId = model.NewId()
   168  		replyPost3.Message = "zz" + model.NewId() + "b"
   169  		replyPost3.RootId = rootPost.Id
   170  
   171  		_, _, err = ss.Post().SaveMultiple([]*model.Post{&replyPost2, &replyPost3})
   172  		require.NoError(t, err)
   173  
   174  		rrootPost2, err := ss.Post().GetSingle(rootPost.Id, false)
   175  		require.NoError(t, err)
   176  		require.Greater(t, rrootPost2.UpdateAt, rrootPost.UpdateAt)
   177  
   178  		thread2, err := ss.Thread().Get(rootPost.Id)
   179  		require.NoError(t, err)
   180  		require.Greater(t, thread2.LastReplyAt, thread1.LastReplyAt)
   181  	})
   182  
   183  	t.Run("Deleting reply should update the thread", func(t *testing.T) {
   184  		rootPost := model.Post{}
   185  		rootPost.RootId = model.NewId()
   186  		rootPost.ChannelId = model.NewId()
   187  		rootPost.UserId = model.NewId()
   188  		rootPost.Message = "zz" + model.NewId() + "b"
   189  
   190  		replyPost := model.Post{}
   191  		replyPost.ChannelId = rootPost.ChannelId
   192  		replyPost.UserId = model.NewId()
   193  		replyPost.Message = "zz" + model.NewId() + "b"
   194  		replyPost.RootId = rootPost.RootId
   195  
   196  		newPosts, _, err := ss.Post().SaveMultiple([]*model.Post{&rootPost, &replyPost})
   197  		require.NoError(t, err)
   198  
   199  		thread1, err := ss.Thread().Get(newPosts[0].RootId)
   200  		require.NoError(t, err)
   201  		require.EqualValues(t, thread1.ReplyCount, 2)
   202  		require.Len(t, thread1.Participants, 2)
   203  
   204  		err = ss.Post().Delete(replyPost.Id, 123, model.NewId())
   205  		require.NoError(t, err)
   206  
   207  		thread2, err := ss.Thread().Get(rootPost.RootId)
   208  		require.NoError(t, err)
   209  		require.EqualValues(t, thread2.ReplyCount, 1)
   210  		require.Len(t, thread2.Participants, 2)
   211  	})
   212  
   213  	t.Run("Deleting root post should delete the thread", func(t *testing.T) {
   214  		rootPost := model.Post{}
   215  		rootPost.ChannelId = model.NewId()
   216  		rootPost.UserId = model.NewId()
   217  		rootPost.Message = "zz" + model.NewId() + "b"
   218  
   219  		newPosts1, _, err := ss.Post().SaveMultiple([]*model.Post{&rootPost})
   220  		require.NoError(t, err)
   221  
   222  		replyPost := model.Post{}
   223  		replyPost.ChannelId = rootPost.ChannelId
   224  		replyPost.UserId = model.NewId()
   225  		replyPost.Message = "zz" + model.NewId() + "b"
   226  		replyPost.RootId = newPosts1[0].Id
   227  
   228  		_, _, err = ss.Post().SaveMultiple([]*model.Post{&replyPost})
   229  		require.NoError(t, err)
   230  
   231  		thread1, err := ss.Thread().Get(newPosts1[0].Id)
   232  		require.NoError(t, err)
   233  		require.EqualValues(t, thread1.ReplyCount, 1)
   234  		require.Len(t, thread1.Participants, 2)
   235  
   236  		err = ss.Post().PermanentDeleteByUser(rootPost.UserId)
   237  		require.NoError(t, err)
   238  
   239  		thread2, _ := ss.Thread().Get(rootPost.Id)
   240  		require.Nil(t, thread2)
   241  	})
   242  
   243  	t.Run("Thread last updated is changed when channel is updated after UpdateLastViewedAtPost", func(t *testing.T) {
   244  		newPosts := makeSomePosts()
   245  		opts := store.ThreadMembershipOpts{
   246  			Following:             true,
   247  			IncrementMentions:     false,
   248  			UpdateFollowing:       true,
   249  			UpdateViewedTimestamp: false,
   250  			UpdateParticipants:    false,
   251  		}
   252  		_, e := ss.Thread().MaintainMembership(newPosts[0].UserId, newPosts[0].Id, opts)
   253  		require.NoError(t, e)
   254  		m, err1 := ss.Thread().GetMembershipForUser(newPosts[0].UserId, newPosts[0].Id)
   255  		require.NoError(t, err1)
   256  		m.LastUpdated -= 1000
   257  		_, err := ss.Thread().UpdateMembership(m)
   258  		require.NoError(t, err)
   259  
   260  		_, err = ss.Channel().UpdateLastViewedAtPost(newPosts[0], newPosts[0].UserId, 0, 0, true, true)
   261  		require.NoError(t, err)
   262  
   263  		assert.Eventually(t, func() bool {
   264  			m2, err2 := ss.Thread().GetMembershipForUser(newPosts[0].UserId, newPosts[0].Id)
   265  			require.NoError(t, err2)
   266  			return m2.LastUpdated > m.LastUpdated
   267  		}, time.Second, 10*time.Millisecond)
   268  	})
   269  
   270  	t.Run("Thread last updated is changed when channel is updated after IncrementMentionCount", func(t *testing.T) {
   271  		newPosts := makeSomePosts()
   272  
   273  		opts := store.ThreadMembershipOpts{
   274  			Following:             true,
   275  			IncrementMentions:     false,
   276  			UpdateFollowing:       true,
   277  			UpdateViewedTimestamp: false,
   278  			UpdateParticipants:    false,
   279  		}
   280  		_, e := ss.Thread().MaintainMembership(newPosts[0].UserId, newPosts[0].Id, opts)
   281  		require.NoError(t, e)
   282  		m, err1 := ss.Thread().GetMembershipForUser(newPosts[0].UserId, newPosts[0].Id)
   283  		require.NoError(t, err1)
   284  		m.LastUpdated -= 1000
   285  		_, err := ss.Thread().UpdateMembership(m)
   286  		require.NoError(t, err)
   287  
   288  		err = ss.Channel().IncrementMentionCount(newPosts[0].ChannelId, newPosts[0].UserId, true, false)
   289  		require.NoError(t, err)
   290  
   291  		assert.Eventually(t, func() bool {
   292  			m2, err2 := ss.Thread().GetMembershipForUser(newPosts[0].UserId, newPosts[0].Id)
   293  			require.NoError(t, err2)
   294  			return m2.LastUpdated > m.LastUpdated
   295  		}, time.Second, 10*time.Millisecond)
   296  	})
   297  
   298  	t.Run("Thread last updated is changed when channel is updated after UpdateLastViewedAt", func(t *testing.T) {
   299  		newPosts := makeSomePosts()
   300  		opts := store.ThreadMembershipOpts{
   301  			Following:             true,
   302  			IncrementMentions:     false,
   303  			UpdateFollowing:       true,
   304  			UpdateViewedTimestamp: false,
   305  			UpdateParticipants:    false,
   306  		}
   307  		_, e := ss.Thread().MaintainMembership(newPosts[0].UserId, newPosts[0].Id, opts)
   308  		require.NoError(t, e)
   309  		m, err1 := ss.Thread().GetMembershipForUser(newPosts[0].UserId, newPosts[0].Id)
   310  		require.NoError(t, err1)
   311  		m.LastUpdated -= 1000
   312  		_, err := ss.Thread().UpdateMembership(m)
   313  		require.NoError(t, err)
   314  
   315  		_, err = ss.Channel().UpdateLastViewedAt([]string{newPosts[0].ChannelId}, newPosts[0].UserId, true)
   316  		require.NoError(t, err)
   317  
   318  		assert.Eventually(t, func() bool {
   319  			m2, err2 := ss.Thread().GetMembershipForUser(newPosts[0].UserId, newPosts[0].Id)
   320  			require.NoError(t, err2)
   321  			return m2.LastUpdated > m.LastUpdated
   322  		}, time.Second, 10*time.Millisecond)
   323  	})
   324  
   325  	t.Run("Thread membership 'viewed' timestamp is updated properly", func(t *testing.T) {
   326  		newPosts := makeSomePosts()
   327  
   328  		opts := store.ThreadMembershipOpts{
   329  			Following:             true,
   330  			IncrementMentions:     false,
   331  			UpdateFollowing:       true,
   332  			UpdateViewedTimestamp: false,
   333  			UpdateParticipants:    false,
   334  		}
   335  		tm, e := ss.Thread().MaintainMembership(newPosts[0].UserId, newPosts[0].Id, opts)
   336  		require.NoError(t, e)
   337  		require.Equal(t, int64(0), tm.LastViewed)
   338  
   339  		opts.UpdateViewedTimestamp = true
   340  		_, e = ss.Thread().MaintainMembership(newPosts[0].UserId, newPosts[0].Id, opts)
   341  		require.NoError(t, e)
   342  		m2, err2 := ss.Thread().GetMembershipForUser(newPosts[0].UserId, newPosts[0].Id)
   343  		require.NoError(t, err2)
   344  		require.Greater(t, m2.LastViewed, int64(0))
   345  	})
   346  
   347  	t.Run("Thread membership 'viewed' timestamp is updated properly for new membership", func(t *testing.T) {
   348  		newPosts := makeSomePosts()
   349  
   350  		opts := store.ThreadMembershipOpts{
   351  			Following:             true,
   352  			IncrementMentions:     false,
   353  			UpdateFollowing:       false,
   354  			UpdateViewedTimestamp: true,
   355  			UpdateParticipants:    false,
   356  		}
   357  		tm, e := ss.Thread().MaintainMembership(newPosts[0].UserId, newPosts[0].Id, opts)
   358  		require.NoError(t, e)
   359  		require.NotEqual(t, int64(0), tm.LastViewed)
   360  	})
   361  
   362  	t.Run("Thread last updated is changed when channel is updated after UpdateLastViewedAtPost for mark unread", func(t *testing.T) {
   363  		newPosts := makeSomePosts()
   364  		opts := store.ThreadMembershipOpts{
   365  			Following:             true,
   366  			IncrementMentions:     false,
   367  			UpdateFollowing:       true,
   368  			UpdateViewedTimestamp: false,
   369  			UpdateParticipants:    false,
   370  		}
   371  		_, e := ss.Thread().MaintainMembership(newPosts[0].UserId, newPosts[0].Id, opts)
   372  		require.NoError(t, e)
   373  		m, err1 := ss.Thread().GetMembershipForUser(newPosts[0].UserId, newPosts[0].Id)
   374  		require.NoError(t, err1)
   375  		m.LastUpdated += 1000
   376  		_, err := ss.Thread().UpdateMembership(m)
   377  		require.NoError(t, err)
   378  
   379  		_, err = ss.Channel().UpdateLastViewedAtPost(newPosts[0], newPosts[0].UserId, 0, 0, true, true)
   380  		require.NoError(t, err)
   381  
   382  		assert.Eventually(t, func() bool {
   383  			m2, err2 := ss.Thread().GetMembershipForUser(newPosts[0].UserId, newPosts[0].Id)
   384  			require.NoError(t, err2)
   385  			return m2.LastUpdated < m.LastUpdated
   386  		}, time.Second, 10*time.Millisecond)
   387  	})
   388  
   389  	t.Run("Updating post does not make thread unread", func(t *testing.T) {
   390  		newPosts := makeSomePosts()
   391  		opts := store.ThreadMembershipOpts{
   392  			Following:             true,
   393  			IncrementMentions:     false,
   394  			UpdateFollowing:       true,
   395  			UpdateViewedTimestamp: false,
   396  			UpdateParticipants:    false,
   397  		}
   398  		m, err := ss.Thread().MaintainMembership(newPosts[0].UserId, newPosts[0].Id, opts)
   399  		require.NoError(t, err)
   400  		th, err := ss.Thread().GetThreadForUser("", m, false)
   401  		require.NoError(t, err)
   402  		require.Equal(t, int64(2), th.UnreadReplies)
   403  
   404  		m.LastViewed = newPosts[2].UpdateAt + 1
   405  		_, err = ss.Thread().UpdateMembership(m)
   406  		require.NoError(t, err)
   407  		th, err = ss.Thread().GetThreadForUser("", m, false)
   408  		require.NoError(t, err)
   409  		require.Equal(t, int64(0), th.UnreadReplies)
   410  
   411  		editedPost := newPosts[2].Clone()
   412  		editedPost.Message = "This is an edited post"
   413  		_, err = ss.Post().Update(editedPost, newPosts[2])
   414  		require.NoError(t, err)
   415  
   416  		th, err = ss.Thread().GetThreadForUser("", m, false)
   417  		require.NoError(t, err)
   418  		require.Equal(t, int64(0), th.UnreadReplies)
   419  	})
   420  }
   421  
   422  func testThreadSQLOperations(t *testing.T, ss store.Store, s SqlStore) {
   423  	t.Run("Save", func(t *testing.T) {
   424  		threadToSave := &model.Thread{
   425  			PostId:       model.NewId(),
   426  			ChannelId:    model.NewId(),
   427  			LastReplyAt:  10,
   428  			ReplyCount:   5,
   429  			Participants: model.StringArray{model.NewId(), model.NewId()},
   430  		}
   431  		_, err := ss.Thread().Save(threadToSave)
   432  		require.NoError(t, err)
   433  
   434  		th, err := ss.Thread().Get(threadToSave.PostId)
   435  		require.NoError(t, err)
   436  		require.Equal(t, threadToSave, th)
   437  	})
   438  }
   439  
   440  func threadStoreCreateReply(t *testing.T, ss store.Store, channelID, postID string, createAt int64) *model.Post {
   441  	reply, err := ss.Post().Save(&model.Post{
   442  		ChannelId: channelID,
   443  		UserId:    model.NewId(),
   444  		CreateAt:  createAt,
   445  		RootId:    postID,
   446  		ParentId:  postID,
   447  	})
   448  	require.NoError(t, err)
   449  	return reply
   450  }
   451  
   452  func testThreadStorePermanentDeleteBatchForRetentionPolicies(t *testing.T, ss store.Store) {
   453  	const limit = 1000
   454  	team, err := ss.Team().Save(&model.Team{
   455  		DisplayName: "DisplayName",
   456  		Name:        "team" + model.NewId(),
   457  		Email:       MakeEmail(),
   458  		Type:        model.TEAM_OPEN,
   459  	})
   460  	require.NoError(t, err)
   461  	channel, err := ss.Channel().Save(&model.Channel{
   462  		TeamId:      team.Id,
   463  		DisplayName: "DisplayName",
   464  		Name:        "channel" + model.NewId(),
   465  		Type:        model.CHANNEL_OPEN,
   466  	}, -1)
   467  	require.NoError(t, err)
   468  
   469  	post, err := ss.Post().Save(&model.Post{
   470  		ChannelId: channel.Id,
   471  		UserId:    model.NewId(),
   472  	})
   473  	require.NoError(t, err)
   474  	threadStoreCreateReply(t, ss, channel.Id, post.Id, 2000)
   475  
   476  	thread, err := ss.Thread().Get(post.Id)
   477  	require.NoError(t, err)
   478  
   479  	channelPolicy, err := ss.RetentionPolicy().Save(&model.RetentionPolicyWithTeamAndChannelIDs{
   480  		RetentionPolicy: model.RetentionPolicy{
   481  			DisplayName:  "DisplayName",
   482  			PostDuration: model.NewInt64(30),
   483  		},
   484  		ChannelIDs: []string{channel.Id},
   485  	})
   486  	require.NoError(t, err)
   487  
   488  	nowMillis := thread.LastReplyAt + *channelPolicy.PostDuration*24*60*60*1000 + 1
   489  	_, _, err = ss.Thread().PermanentDeleteBatchForRetentionPolicies(nowMillis, 0, limit, model.RetentionPolicyCursor{})
   490  	require.NoError(t, err)
   491  	thread, err = ss.Thread().Get(post.Id)
   492  	assert.NoError(t, err)
   493  	assert.Nil(t, thread, "thread should have been deleted by channel policy")
   494  
   495  	// create a new thread
   496  	threadStoreCreateReply(t, ss, channel.Id, post.Id, 2000)
   497  	thread, err = ss.Thread().Get(post.Id)
   498  	require.NoError(t, err)
   499  
   500  	// Create a team policy which is stricter than the channel policy
   501  	teamPolicy, err := ss.RetentionPolicy().Save(&model.RetentionPolicyWithTeamAndChannelIDs{
   502  		RetentionPolicy: model.RetentionPolicy{
   503  			DisplayName:  "DisplayName",
   504  			PostDuration: model.NewInt64(20),
   505  		},
   506  		TeamIDs: []string{team.Id},
   507  	})
   508  	require.NoError(t, err)
   509  
   510  	nowMillis = thread.LastReplyAt + *teamPolicy.PostDuration*24*60*60*1000 + 1
   511  	_, _, err = ss.Thread().PermanentDeleteBatchForRetentionPolicies(nowMillis, 0, limit, model.RetentionPolicyCursor{})
   512  	require.NoError(t, err)
   513  	_, err = ss.Thread().Get(post.Id)
   514  	require.NoError(t, err, "channel policy should have overridden team policy")
   515  
   516  	// Delete channel policy and re-run team policy
   517  	err = ss.RetentionPolicy().Delete(channelPolicy.ID)
   518  	require.NoError(t, err)
   519  	_, _, err = ss.Thread().PermanentDeleteBatchForRetentionPolicies(nowMillis, 0, limit, model.RetentionPolicyCursor{})
   520  	require.NoError(t, err)
   521  	thread, err = ss.Thread().Get(post.Id)
   522  	assert.NoError(t, err)
   523  	assert.Nil(t, thread, "thread should have been deleted by team policy")
   524  }
   525  
   526  func testThreadStorePermanentDeleteBatchThreadMembershipsForRetentionPolicies(t *testing.T, ss store.Store) {
   527  	const limit = 1000
   528  	userID := model.NewId()
   529  	createThreadMembership := func(userID, postID string) *model.ThreadMembership {
   530  		opts := store.ThreadMembershipOpts{
   531  			Following:             true,
   532  			IncrementMentions:     false,
   533  			UpdateFollowing:       true,
   534  			UpdateViewedTimestamp: false,
   535  			UpdateParticipants:    false,
   536  		}
   537  		_, err := ss.Thread().MaintainMembership(userID, postID, opts)
   538  		require.NoError(t, err)
   539  		threadMembership, err := ss.Thread().GetMembershipForUser(userID, postID)
   540  		require.NoError(t, err)
   541  		return threadMembership
   542  	}
   543  	team, err := ss.Team().Save(&model.Team{
   544  		DisplayName: "DisplayName",
   545  		Name:        "team" + model.NewId(),
   546  		Email:       MakeEmail(),
   547  		Type:        model.TEAM_OPEN,
   548  	})
   549  	require.NoError(t, err)
   550  	channel, err := ss.Channel().Save(&model.Channel{
   551  		TeamId:      team.Id,
   552  		DisplayName: "DisplayName",
   553  		Name:        "channel" + model.NewId(),
   554  		Type:        model.CHANNEL_OPEN,
   555  	}, -1)
   556  	require.NoError(t, err)
   557  	post, err := ss.Post().Save(&model.Post{
   558  		ChannelId: channel.Id,
   559  		UserId:    model.NewId(),
   560  	})
   561  	require.NoError(t, err)
   562  	threadStoreCreateReply(t, ss, channel.Id, post.Id, 2000)
   563  
   564  	threadMembership := createThreadMembership(userID, post.Id)
   565  
   566  	channelPolicy, err := ss.RetentionPolicy().Save(&model.RetentionPolicyWithTeamAndChannelIDs{
   567  		RetentionPolicy: model.RetentionPolicy{
   568  			DisplayName:  "DisplayName",
   569  			PostDuration: model.NewInt64(30),
   570  		},
   571  		ChannelIDs: []string{channel.Id},
   572  	})
   573  	require.NoError(t, err)
   574  
   575  	nowMillis := threadMembership.LastUpdated + *channelPolicy.PostDuration*24*60*60*1000 + 1
   576  	_, _, err = ss.Thread().PermanentDeleteBatchThreadMembershipsForRetentionPolicies(nowMillis, 0, limit, model.RetentionPolicyCursor{})
   577  	require.NoError(t, err)
   578  	_, err = ss.Thread().GetMembershipForUser(userID, post.Id)
   579  	require.Error(t, err, "thread membership should have been deleted by channel policy")
   580  
   581  	// create a new thread membership
   582  	threadMembership = createThreadMembership(userID, post.Id)
   583  
   584  	// Create a team policy which is stricter than the channel policy
   585  	teamPolicy, err := ss.RetentionPolicy().Save(&model.RetentionPolicyWithTeamAndChannelIDs{
   586  		RetentionPolicy: model.RetentionPolicy{
   587  			DisplayName:  "DisplayName",
   588  			PostDuration: model.NewInt64(20),
   589  		},
   590  		TeamIDs: []string{team.Id},
   591  	})
   592  	require.NoError(t, err)
   593  
   594  	nowMillis = threadMembership.LastUpdated + *teamPolicy.PostDuration*24*60*60*1000 + 1
   595  	_, _, err = ss.Thread().PermanentDeleteBatchThreadMembershipsForRetentionPolicies(nowMillis, 0, limit, model.RetentionPolicyCursor{})
   596  	require.NoError(t, err)
   597  	_, err = ss.Thread().GetMembershipForUser(userID, post.Id)
   598  	require.NoError(t, err, "channel policy should have overridden team policy")
   599  
   600  	// Delete channel policy and re-run team policy
   601  	err = ss.RetentionPolicy().Delete(channelPolicy.ID)
   602  	require.NoError(t, err)
   603  	_, _, err = ss.Thread().PermanentDeleteBatchThreadMembershipsForRetentionPolicies(nowMillis, 0, limit, model.RetentionPolicyCursor{})
   604  	require.NoError(t, err)
   605  	_, err = ss.Thread().GetMembershipForUser(userID, post.Id)
   606  	require.Error(t, err, "thread membership should have been deleted by team policy")
   607  
   608  	// create a new thread membership
   609  	createThreadMembership(userID, post.Id)
   610  
   611  	// Delete team policy and thread
   612  	err = ss.RetentionPolicy().Delete(teamPolicy.ID)
   613  	require.NoError(t, err)
   614  	err = ss.Thread().Delete(post.Id)
   615  	require.NoError(t, err)
   616  
   617  	deleted, err := ss.Thread().DeleteOrphanedRows(1000)
   618  	require.NoError(t, err)
   619  	require.NotZero(t, deleted)
   620  	_, err = ss.Thread().GetMembershipForUser(userID, post.Id)
   621  	require.Error(t, err, "thread membership should have been deleted because thread no longer exists")
   622  }