github.com/mattermost/mattermost-server/v5@v5.39.3/store/storetest/channel_member_history_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  	"testing"
     8  
     9  	"math"
    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 TestChannelMemberHistoryStore(t *testing.T, ss store.Store) {
    19  	t.Run("TestLogJoinEvent", func(t *testing.T) { testLogJoinEvent(t, ss) })
    20  	t.Run("TestLogLeaveEvent", func(t *testing.T) { testLogLeaveEvent(t, ss) })
    21  	t.Run("TestGetUsersInChannelAtChannelMemberHistory", func(t *testing.T) { testGetUsersInChannelAtChannelMemberHistory(t, ss) })
    22  	t.Run("TestGetUsersInChannelAtChannelMembers", func(t *testing.T) { testGetUsersInChannelAtChannelMembers(t, ss) })
    23  	t.Run("TestPermanentDeleteBatch", func(t *testing.T) { testPermanentDeleteBatch(t, ss) })
    24  	t.Run("TestPermanentDeleteBatchForRetentionPolicies", func(t *testing.T) { testPermanentDeleteBatchForRetentionPolicies(t, ss) })
    25  }
    26  
    27  func testLogJoinEvent(t *testing.T, ss store.Store) {
    28  	// create a test channel
    29  	ch := model.Channel{
    30  		TeamId:      model.NewId(),
    31  		DisplayName: "Display " + model.NewId(),
    32  		Name:        "zz" + model.NewId() + "b",
    33  		Type:        model.CHANNEL_OPEN,
    34  	}
    35  	channel, err := ss.Channel().Save(&ch, -1)
    36  	require.NoError(t, err)
    37  
    38  	// and a test user
    39  	user := model.User{
    40  		Email:    MakeEmail(),
    41  		Nickname: model.NewId(),
    42  		Username: model.NewId(),
    43  	}
    44  	userPtr, err := ss.User().Save(&user)
    45  	require.NoError(t, err)
    46  	user = *userPtr
    47  
    48  	// log a join event
    49  	err = ss.ChannelMemberHistory().LogJoinEvent(user.Id, channel.Id, model.GetMillis())
    50  	assert.NoError(t, err)
    51  }
    52  
    53  func testLogLeaveEvent(t *testing.T, ss store.Store) {
    54  	// create a test channel
    55  	ch := model.Channel{
    56  		TeamId:      model.NewId(),
    57  		DisplayName: "Display " + model.NewId(),
    58  		Name:        "zz" + model.NewId() + "b",
    59  		Type:        model.CHANNEL_OPEN,
    60  	}
    61  	channel, err := ss.Channel().Save(&ch, -1)
    62  	require.NoError(t, err)
    63  
    64  	// and a test user
    65  	user := model.User{
    66  		Email:    MakeEmail(),
    67  		Nickname: model.NewId(),
    68  		Username: model.NewId(),
    69  	}
    70  	userPtr, err := ss.User().Save(&user)
    71  	require.NoError(t, err)
    72  	user = *userPtr
    73  
    74  	// log a join event, followed by a leave event
    75  	err = ss.ChannelMemberHistory().LogJoinEvent(user.Id, channel.Id, model.GetMillis())
    76  	assert.NoError(t, err)
    77  
    78  	err = ss.ChannelMemberHistory().LogLeaveEvent(user.Id, channel.Id, model.GetMillis())
    79  	assert.NoError(t, err)
    80  }
    81  
    82  func testGetUsersInChannelAtChannelMemberHistory(t *testing.T, ss store.Store) {
    83  	// create a test channel
    84  	ch := &model.Channel{
    85  		TeamId:      model.NewId(),
    86  		DisplayName: "Display " + model.NewId(),
    87  		Name:        "zz" + model.NewId() + "b",
    88  		Type:        model.CHANNEL_OPEN,
    89  	}
    90  	channel, err := ss.Channel().Save(ch, -1)
    91  	require.NoError(t, err)
    92  
    93  	// and a test user
    94  	user := model.User{
    95  		Email:    MakeEmail(),
    96  		Nickname: model.NewId(),
    97  		Username: model.NewId(),
    98  	}
    99  	userPtr, err := ss.User().Save(&user)
   100  	require.NoError(t, err)
   101  	user = *userPtr
   102  
   103  	// the user was previously in the channel a long time ago, before the export period starts
   104  	// the existence of this record makes it look like the MessageExport feature has been active for awhile, and prevents
   105  	// us from looking in the ChannelMembers table for data that isn't found in the ChannelMemberHistory table
   106  	leaveTime := model.GetMillis() - 20000
   107  	joinTime := leaveTime - 10000
   108  	err = ss.ChannelMemberHistory().LogJoinEvent(user.Id, channel.Id, joinTime)
   109  	require.NoError(t, err)
   110  	err = ss.ChannelMemberHistory().LogLeaveEvent(user.Id, channel.Id, leaveTime)
   111  	require.NoError(t, err)
   112  
   113  	// log a join event
   114  	leaveTime = model.GetMillis()
   115  	joinTime = leaveTime - 10000
   116  	err = ss.ChannelMemberHistory().LogJoinEvent(user.Id, channel.Id, joinTime)
   117  	require.NoError(t, err)
   118  
   119  	// case 1: user joins and leaves the channel before the export period begins
   120  	channelMembers, err := ss.ChannelMemberHistory().GetUsersInChannelDuring(joinTime-500, joinTime-100, channel.Id)
   121  	require.NoError(t, err)
   122  	assert.Empty(t, channelMembers)
   123  
   124  	// case 2: user joins the channel after the export period begins, but has not yet left the channel when the export period ends
   125  	channelMembers, err = ss.ChannelMemberHistory().GetUsersInChannelDuring(joinTime-100, joinTime+500, channel.Id)
   126  	require.NoError(t, err)
   127  	assert.Len(t, channelMembers, 1)
   128  	assert.Equal(t, channel.Id, channelMembers[0].ChannelId)
   129  	assert.Equal(t, user.Id, channelMembers[0].UserId)
   130  	assert.Equal(t, user.Email, channelMembers[0].UserEmail)
   131  	assert.Equal(t, user.Username, channelMembers[0].Username)
   132  	assert.Equal(t, joinTime, channelMembers[0].JoinTime)
   133  	assert.Nil(t, channelMembers[0].LeaveTime)
   134  
   135  	// case 3: user joins the channel before the export period begins, but has not yet left the channel when the export period ends
   136  	channelMembers, err = ss.ChannelMemberHistory().GetUsersInChannelDuring(joinTime+100, joinTime+500, channel.Id)
   137  	require.NoError(t, err)
   138  	assert.Len(t, channelMembers, 1)
   139  	assert.Equal(t, channel.Id, channelMembers[0].ChannelId)
   140  	assert.Equal(t, user.Id, channelMembers[0].UserId)
   141  	assert.Equal(t, user.Email, channelMembers[0].UserEmail)
   142  	assert.Equal(t, user.Username, channelMembers[0].Username)
   143  	assert.Equal(t, joinTime, channelMembers[0].JoinTime)
   144  	assert.Nil(t, channelMembers[0].LeaveTime)
   145  
   146  	// add a leave time for the user
   147  	err = ss.ChannelMemberHistory().LogLeaveEvent(user.Id, channel.Id, leaveTime)
   148  	require.NoError(t, err)
   149  
   150  	// case 4: user joins the channel before the export period begins, but has not yet left the channel when the export period ends
   151  	channelMembers, err = ss.ChannelMemberHistory().GetUsersInChannelDuring(joinTime+100, leaveTime-100, channel.Id)
   152  	require.NoError(t, err)
   153  	assert.Len(t, channelMembers, 1)
   154  	assert.Equal(t, channel.Id, channelMembers[0].ChannelId)
   155  	assert.Equal(t, user.Id, channelMembers[0].UserId)
   156  	assert.Equal(t, user.Email, channelMembers[0].UserEmail)
   157  	assert.Equal(t, user.Username, channelMembers[0].Username)
   158  	assert.Equal(t, joinTime, channelMembers[0].JoinTime)
   159  	assert.Equal(t, leaveTime, *channelMembers[0].LeaveTime)
   160  
   161  	// case 5: user joins the channel after the export period begins, and leaves the channel before the export period ends
   162  	channelMembers, err = ss.ChannelMemberHistory().GetUsersInChannelDuring(joinTime-100, leaveTime+100, channel.Id)
   163  	require.NoError(t, err)
   164  	assert.Len(t, channelMembers, 1)
   165  	assert.Equal(t, channel.Id, channelMembers[0].ChannelId)
   166  	assert.Equal(t, user.Id, channelMembers[0].UserId)
   167  	assert.Equal(t, user.Email, channelMembers[0].UserEmail)
   168  	assert.Equal(t, user.Username, channelMembers[0].Username)
   169  	assert.Equal(t, joinTime, channelMembers[0].JoinTime)
   170  	assert.Equal(t, leaveTime, *channelMembers[0].LeaveTime)
   171  
   172  	// case 6: user has joined and left the channel long before the export period begins
   173  	channelMembers, err = ss.ChannelMemberHistory().GetUsersInChannelDuring(leaveTime+100, leaveTime+200, channel.Id)
   174  	require.NoError(t, err)
   175  	assert.Empty(t, channelMembers)
   176  }
   177  
   178  func testGetUsersInChannelAtChannelMembers(t *testing.T, ss store.Store) {
   179  	// create a test channel
   180  	channel := &model.Channel{
   181  		TeamId:      model.NewId(),
   182  		DisplayName: "Display " + model.NewId(),
   183  		Name:        "zz" + model.NewId() + "b",
   184  		Type:        model.CHANNEL_OPEN,
   185  	}
   186  	channel, err := ss.Channel().Save(channel, -1)
   187  	require.NoError(t, err)
   188  
   189  	// and a test user
   190  	user := model.User{
   191  		Email:    MakeEmail(),
   192  		Nickname: model.NewId(),
   193  		Username: model.NewId(),
   194  	}
   195  	userPtr, err := ss.User().Save(&user)
   196  	require.NoError(t, err)
   197  	user = *userPtr
   198  
   199  	// clear any existing ChannelMemberHistory data that might interfere with our test
   200  	var tableDataTruncated = false
   201  	for !tableDataTruncated {
   202  		var count int64
   203  		count, _, err = ss.ChannelMemberHistory().PermanentDeleteBatchForRetentionPolicies(
   204  			0, model.GetMillis(), 1000, model.RetentionPolicyCursor{})
   205  		require.NoError(t, err, "Failed to truncate ChannelMemberHistory contents")
   206  		tableDataTruncated = count == int64(0)
   207  	}
   208  
   209  	// in this test, we're pretending that Message Export was not activated during the export period, so there's no data
   210  	// available in the ChannelMemberHistory table. Instead, we'll fall back to the ChannelMembers table for a rough approximation
   211  	joinTime := int64(1000)
   212  	leaveTime := joinTime + 5000
   213  	_, err = ss.Channel().SaveMember(&model.ChannelMember{
   214  		ChannelId:   channel.Id,
   215  		UserId:      user.Id,
   216  		NotifyProps: model.GetDefaultChannelNotifyProps(),
   217  	})
   218  	require.NoError(t, err)
   219  
   220  	// in every single case, the user will be included in the export, because ChannelMembers says they were in the channel at some point in
   221  	// the past, even though the time that they were actually in the channel doesn't necessarily overlap with the export period
   222  
   223  	// case 1: user joins and leaves the channel before the export period begins
   224  	channelMembers, err := ss.ChannelMemberHistory().GetUsersInChannelDuring(joinTime-500, joinTime-100, channel.Id)
   225  	require.NoError(t, err)
   226  	assert.Len(t, channelMembers, 1)
   227  	assert.Equal(t, channel.Id, channelMembers[0].ChannelId)
   228  	assert.Equal(t, user.Id, channelMembers[0].UserId)
   229  	assert.Equal(t, user.Email, channelMembers[0].UserEmail)
   230  	assert.Equal(t, user.Username, channelMembers[0].Username)
   231  	assert.Equal(t, joinTime-500, channelMembers[0].JoinTime)
   232  	assert.Equal(t, joinTime-100, *channelMembers[0].LeaveTime)
   233  
   234  	// case 2: user joins the channel after the export period begins, but has not yet left the channel when the export period ends
   235  	channelMembers, err = ss.ChannelMemberHistory().GetUsersInChannelDuring(joinTime-100, joinTime+500, channel.Id)
   236  	require.NoError(t, err)
   237  	assert.Len(t, channelMembers, 1)
   238  	assert.Equal(t, channel.Id, channelMembers[0].ChannelId)
   239  	assert.Equal(t, user.Id, channelMembers[0].UserId)
   240  	assert.Equal(t, user.Email, channelMembers[0].UserEmail)
   241  	assert.Equal(t, user.Username, channelMembers[0].Username)
   242  	assert.Equal(t, joinTime-100, channelMembers[0].JoinTime)
   243  	assert.Equal(t, joinTime+500, *channelMembers[0].LeaveTime)
   244  
   245  	// case 3: user joins the channel before the export period begins, but has not yet left the channel when the export period ends
   246  	channelMembers, err = ss.ChannelMemberHistory().GetUsersInChannelDuring(joinTime+100, joinTime+500, channel.Id)
   247  	require.NoError(t, err)
   248  	assert.Len(t, channelMembers, 1)
   249  	assert.Equal(t, channel.Id, channelMembers[0].ChannelId)
   250  	assert.Equal(t, user.Id, channelMembers[0].UserId)
   251  	assert.Equal(t, user.Email, channelMembers[0].UserEmail)
   252  	assert.Equal(t, user.Username, channelMembers[0].Username)
   253  	assert.Equal(t, joinTime+100, channelMembers[0].JoinTime)
   254  	assert.Equal(t, joinTime+500, *channelMembers[0].LeaveTime)
   255  
   256  	// case 4: user joins the channel before the export period begins, but has not yet left the channel when the export period ends
   257  	channelMembers, err = ss.ChannelMemberHistory().GetUsersInChannelDuring(joinTime+100, leaveTime-100, channel.Id)
   258  	require.NoError(t, err)
   259  	assert.Len(t, channelMembers, 1)
   260  	assert.Equal(t, channel.Id, channelMembers[0].ChannelId)
   261  	assert.Equal(t, user.Id, channelMembers[0].UserId)
   262  	assert.Equal(t, user.Email, channelMembers[0].UserEmail)
   263  	assert.Equal(t, user.Username, channelMembers[0].Username)
   264  	assert.Equal(t, joinTime+100, channelMembers[0].JoinTime)
   265  	assert.Equal(t, leaveTime-100, *channelMembers[0].LeaveTime)
   266  
   267  	// case 5: user joins the channel after the export period begins, and leaves the channel before the export period ends
   268  	channelMembers, err = ss.ChannelMemberHistory().GetUsersInChannelDuring(joinTime-100, leaveTime+100, channel.Id)
   269  	require.NoError(t, err)
   270  	assert.Len(t, channelMembers, 1)
   271  	assert.Equal(t, channel.Id, channelMembers[0].ChannelId)
   272  	assert.Equal(t, user.Id, channelMembers[0].UserId)
   273  	assert.Equal(t, user.Email, channelMembers[0].UserEmail)
   274  	assert.Equal(t, user.Username, channelMembers[0].Username)
   275  	assert.Equal(t, joinTime-100, channelMembers[0].JoinTime)
   276  	assert.Equal(t, leaveTime+100, *channelMembers[0].LeaveTime)
   277  
   278  	// case 6: user has joined and left the channel long before the export period begins
   279  	channelMembers, err = ss.ChannelMemberHistory().GetUsersInChannelDuring(leaveTime+100, leaveTime+200, channel.Id)
   280  	require.NoError(t, err)
   281  	assert.Len(t, channelMembers, 1)
   282  	assert.Equal(t, channel.Id, channelMembers[0].ChannelId)
   283  	assert.Equal(t, user.Id, channelMembers[0].UserId)
   284  	assert.Equal(t, user.Email, channelMembers[0].UserEmail)
   285  	assert.Equal(t, user.Username, channelMembers[0].Username)
   286  	assert.Equal(t, leaveTime+100, channelMembers[0].JoinTime)
   287  	assert.Equal(t, leaveTime+200, *channelMembers[0].LeaveTime)
   288  }
   289  
   290  func testPermanentDeleteBatch(t *testing.T, ss store.Store) {
   291  	// create a test channel
   292  	channel := &model.Channel{
   293  		TeamId:      model.NewId(),
   294  		DisplayName: "Display " + model.NewId(),
   295  		Name:        "zz" + model.NewId() + "b",
   296  		Type:        model.CHANNEL_OPEN,
   297  	}
   298  	channel, err := ss.Channel().Save(channel, -1)
   299  	require.NoError(t, err)
   300  
   301  	// and two test users
   302  	user := model.User{
   303  		Email:    MakeEmail(),
   304  		Nickname: model.NewId(),
   305  		Username: model.NewId(),
   306  	}
   307  	userPtr, err := ss.User().Save(&user)
   308  	require.NoError(t, err)
   309  	user = *userPtr
   310  
   311  	user2 := model.User{
   312  		Email:    MakeEmail(),
   313  		Nickname: model.NewId(),
   314  		Username: model.NewId(),
   315  	}
   316  	user2Ptr, err := ss.User().Save(&user2)
   317  	require.NoError(t, err)
   318  	user2 = *user2Ptr
   319  
   320  	// user1 joins and leaves the channel
   321  	leaveTime := model.GetMillis()
   322  	joinTime := leaveTime - 10000
   323  	err = ss.ChannelMemberHistory().LogJoinEvent(user.Id, channel.Id, joinTime)
   324  	require.NoError(t, err)
   325  	err = ss.ChannelMemberHistory().LogLeaveEvent(user.Id, channel.Id, leaveTime)
   326  	require.NoError(t, err)
   327  
   328  	// user2 joins the channel but never leaves
   329  	err = ss.ChannelMemberHistory().LogJoinEvent(user2.Id, channel.Id, joinTime)
   330  	require.NoError(t, err)
   331  
   332  	// in between the join time and the leave time, both users were members of the channel
   333  	channelMembers, err := ss.ChannelMemberHistory().GetUsersInChannelDuring(joinTime+10, leaveTime-10, channel.Id)
   334  	require.NoError(t, err)
   335  	assert.Len(t, channelMembers, 2)
   336  
   337  	// the permanent delete should delete at least one record
   338  	rowsDeleted, _, err := ss.ChannelMemberHistory().PermanentDeleteBatchForRetentionPolicies(
   339  		0, leaveTime+1, math.MaxInt64, model.RetentionPolicyCursor{})
   340  	require.NoError(t, err)
   341  	assert.NotEqual(t, int64(0), rowsDeleted)
   342  
   343  	// after the delete, there should be one less member in the channel
   344  	channelMembers, err = ss.ChannelMemberHistory().GetUsersInChannelDuring(joinTime+10, leaveTime-10, channel.Id)
   345  	require.NoError(t, err)
   346  	assert.Len(t, channelMembers, 1)
   347  	assert.Equal(t, user2.Id, channelMembers[0].UserId)
   348  }
   349  
   350  func testPermanentDeleteBatchForRetentionPolicies(t *testing.T, ss store.Store) {
   351  	const limit = 1000
   352  	team, err := ss.Team().Save(&model.Team{
   353  		DisplayName: "DisplayName",
   354  		Name:        "team" + model.NewId(),
   355  		Email:       MakeEmail(),
   356  		Type:        model.TEAM_OPEN,
   357  	})
   358  	require.NoError(t, err)
   359  	channel, err := ss.Channel().Save(&model.Channel{
   360  		TeamId:      team.Id,
   361  		DisplayName: "DisplayName",
   362  		Name:        "channel" + model.NewId(),
   363  		Type:        model.CHANNEL_OPEN,
   364  	}, -1)
   365  	require.NoError(t, err)
   366  	userID := model.NewId()
   367  
   368  	joinTime := int64(1000)
   369  	leaveTime := int64(1500)
   370  	err = ss.ChannelMemberHistory().LogJoinEvent(userID, channel.Id, joinTime)
   371  	require.NoError(t, err)
   372  	err = ss.ChannelMemberHistory().LogLeaveEvent(userID, channel.Id, leaveTime)
   373  	require.NoError(t, err)
   374  
   375  	channelPolicy, err := ss.RetentionPolicy().Save(&model.RetentionPolicyWithTeamAndChannelIDs{
   376  		RetentionPolicy: model.RetentionPolicy{
   377  			DisplayName:  "DisplayName",
   378  			PostDuration: model.NewInt64(30),
   379  		},
   380  		ChannelIDs: []string{channel.Id},
   381  	})
   382  	require.NoError(t, err)
   383  
   384  	nowMillis := leaveTime + *channelPolicy.PostDuration*24*60*60*1000 + 1
   385  	_, _, err = ss.ChannelMemberHistory().PermanentDeleteBatchForRetentionPolicies(
   386  		nowMillis, 0, limit, model.RetentionPolicyCursor{})
   387  	require.NoError(t, err)
   388  	result, err := ss.ChannelMemberHistory().GetUsersInChannelDuring(joinTime, leaveTime, channel.Id)
   389  	require.NoError(t, err)
   390  	require.Empty(t, result, "history should have been deleted by channel policy")
   391  }