[MM-30831] granular data retention wireup (#17417)
* pre-checkout commit * add API endpoints for retention policies * allow deleting multiple teams/channels from a policy in a single request * pre-checkout commit * add auditing in API functions * add permission checks * update the store layers * update storetest * add check constraint on PostDuration column * pre-checkout commit * add query to delete posts under the scope of a granular retention policy * add suggestions from sbishel * allow clients to specify channels/teams when creating a new policy * remove foreign keys referencing Channels and Teams tables * add checks for whether teams and channels exist * pre-checkout commit * remove data referencing the Posts table * pre-checkout commit * write data store tests * sort results of buildGetPoliciesQuery * add missing test cases for teams * pre-checkout commit * add Client4 methods for data retention policy endpoints * add uint and uint64 to app/layer_generators * make granular policies override global policies * fix lint errors * pre-checkout commit * add license to top of files * add tests for data store layer * add missing test cases for store layer * run make i18n-extract * add query to delete ChannelMemberHistory * work in progress * add test for old reply to old post * fix lint error * use COALESCE on each Posts column * begin implementing orphaned rows worker * split PR * pre-checkout commit * use RetentionPolicyWithTeamAndChannelCounts * update app and api layers * run make i18n-extract * add RetentionPolicy to retrylayer_test.go * Revert "split PR" This reverts commit b316f03dd307a30deae931944ca7e4a1cc904605. * fix errors caused by revert * add suggestions from sbishel * fix copy-paste error * fix lint errors * pre-checkout commit * add function to delete orphaned rows * use -1 for infinite retention * remove check constraint * copy i18n entries from master * re-run tests with newer enterprise branch * add team data to channel list * add search for channels and teams in a policy * add store tests for channel and team search * add suggestions from mkraft * run make einterfaces-mocks * fix lint errors * add suggestions from mkraft * move removeOrphanedRows method to wireup branch * Revert "move removeOrphanedRows method to wireup branch" This reverts commit 94605c9b4a5378ffa44a3dec4d3f8e3306b9d33e. * use DeleteOrphanedRows where possible * run make i18n-extract * use COMPLIANCE permissions * run make migrations-bindadta * clean up teams before test * fix tests for TestRetentionPolicyStore * add API endpoints for mobile * fix lint error * fix some of the lint errors * move user/data_retention endpoints to data_retention.go * Revert "fix some of the lint errors" This reverts commit b5b2dc27566c427187db942c5c0afe319e7679c4. * add exclude_policy_constrained parameter for /channels and /teams * fix lint errors * add policy_id field to GET endpoints for channels and teams * use PolicyWithTeamID in RetentionPolicy layer * fix lint errors * run make i18n-extract * update mock call in telemetry_test.go * return status:OK in JSON instead of 204 * pre-checkout commit * add policy_id field on channels/teams * fix lint errors * use sq.Eq instead of '?' * use new subsection permissions * update channels and teams endpoints to use new subsection permissions * add extra search opts for channels in a policy * fix lint errors * allow negative post duration in patch * remove DELETE FROM query in retention policy tests * use *int64 for PostDuration * re-run CI tests * use 3-step deletion strategy for each table * fix lint errors * run make store-layers * re-run CI tests * add test with channel, team and global policies * use common function for SQL queries * add pagination test * use struct for args to common SQL function * fix lint errors * run make i18n-extract * check if Channels.TeamId is "" or nil * use three OR clauses * write separate genericRetentionPoliciesDeletion function * add config setting for BatchSize * add telemetry for BatchSize * use feature flag * add old i18n messages back in * re-run CI tests * update call signature in storetest * MM-30831: Adds constant for retention default batch size. * MM-30831: Removes comment re: optimization. * MM-30831: Converts days to milliseconds. * MM-30831: Reverts change to test. * Revert "MM-30831: Reverts change to test." This reverts commit 6d14275a1ceae682bb9e17ec69b39252b44e0c0c. * Revert "MM-30831: Converts days to milliseconds." This reverts commit a0cb6ec09d854a05194c1daee1c5333f260231c3. * MM-30831: Fixes tests. * MM-30381: Fix for change to method sig. Co-authored-by: Max Erenberg <max.erenberg@mattermost.com> Co-authored-by: Mattermod <mattermod@users.noreply.github.com> Co-authored-by: Martin Kraft <martin@upspin.org>
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
71ef1c5386
Коммит
58d5d51f7a
@@ -21,6 +21,7 @@ func TestChannelMemberHistoryStore(t *testing.T, ss store.Store) {
|
||||
t.Run("TestGetUsersInChannelAtChannelMemberHistory", func(t *testing.T) { testGetUsersInChannelAtChannelMemberHistory(t, ss) })
|
||||
t.Run("TestGetUsersInChannelAtChannelMembers", func(t *testing.T) { testGetUsersInChannelAtChannelMembers(t, ss) })
|
||||
t.Run("TestPermanentDeleteBatch", func(t *testing.T) { testPermanentDeleteBatch(t, ss) })
|
||||
t.Run("TestPermanentDeleteBatchForRetentionPolicies", func(t *testing.T) { testPermanentDeleteBatchForRetentionPolicies(t, ss) })
|
||||
}
|
||||
|
||||
func testLogJoinEvent(t *testing.T, ss store.Store) {
|
||||
@@ -199,7 +200,8 @@ func testGetUsersInChannelAtChannelMembers(t *testing.T, ss store.Store) {
|
||||
var tableDataTruncated = false
|
||||
for !tableDataTruncated {
|
||||
var count int64
|
||||
count, err = ss.ChannelMemberHistory().PermanentDeleteBatch(model.GetMillis(), 1000)
|
||||
count, _, err = ss.ChannelMemberHistory().PermanentDeleteBatchForRetentionPolicies(
|
||||
0, model.GetMillis(), 1000, model.RetentionPolicyCursor{})
|
||||
require.NoError(t, err, "Failed to truncate ChannelMemberHistory contents")
|
||||
tableDataTruncated = count == int64(0)
|
||||
}
|
||||
@@ -333,7 +335,8 @@ func testPermanentDeleteBatch(t *testing.T, ss store.Store) {
|
||||
assert.Len(t, channelMembers, 2)
|
||||
|
||||
// the permanent delete should delete at least one record
|
||||
rowsDeleted, err := ss.ChannelMemberHistory().PermanentDeleteBatch(leaveTime, math.MaxInt64)
|
||||
rowsDeleted, _, err := ss.ChannelMemberHistory().PermanentDeleteBatchForRetentionPolicies(
|
||||
0, leaveTime+1, math.MaxInt64, model.RetentionPolicyCursor{})
|
||||
require.NoError(t, err)
|
||||
assert.NotEqual(t, int64(0), rowsDeleted)
|
||||
|
||||
@@ -343,3 +346,46 @@ func testPermanentDeleteBatch(t *testing.T, ss store.Store) {
|
||||
assert.Len(t, channelMembers, 1)
|
||||
assert.Equal(t, user2.Id, channelMembers[0].UserId)
|
||||
}
|
||||
|
||||
func testPermanentDeleteBatchForRetentionPolicies(t *testing.T, ss store.Store) {
|
||||
const limit = 1000
|
||||
team, err := ss.Team().Save(&model.Team{
|
||||
DisplayName: "DisplayName",
|
||||
Name: "team" + model.NewId(),
|
||||
Email: MakeEmail(),
|
||||
Type: model.TEAM_OPEN,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
channel, err := ss.Channel().Save(&model.Channel{
|
||||
TeamId: team.Id,
|
||||
DisplayName: "DisplayName",
|
||||
Name: "channel" + model.NewId(),
|
||||
Type: model.CHANNEL_OPEN,
|
||||
}, -1)
|
||||
require.NoError(t, err)
|
||||
userID := model.NewId()
|
||||
|
||||
joinTime := int64(1000)
|
||||
leaveTime := int64(1500)
|
||||
err = ss.ChannelMemberHistory().LogJoinEvent(userID, channel.Id, joinTime)
|
||||
require.NoError(t, err)
|
||||
err = ss.ChannelMemberHistory().LogLeaveEvent(userID, channel.Id, leaveTime)
|
||||
require.NoError(t, err)
|
||||
|
||||
channelPolicy, err := ss.RetentionPolicy().Save(&model.RetentionPolicyWithTeamAndChannelIDs{
|
||||
RetentionPolicy: model.RetentionPolicy{
|
||||
DisplayName: "DisplayName",
|
||||
PostDuration: model.NewInt64(30),
|
||||
},
|
||||
ChannelIDs: []string{channel.Id},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
nowMillis := leaveTime + *channelPolicy.PostDuration*24*60*60*1000 + 1
|
||||
_, _, err = ss.ChannelMemberHistory().PermanentDeleteBatchForRetentionPolicies(
|
||||
nowMillis, 0, limit, model.RetentionPolicyCursor{})
|
||||
require.NoError(t, err)
|
||||
result, err := ss.ChannelMemberHistory().GetUsersInChannelDuring(joinTime, leaveTime, channel.Id)
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, result, "history should have been deleted by channel policy")
|
||||
}
|
||||
|
||||
@@ -1757,7 +1757,8 @@ func testChannelMembersToAdd(t *testing.T, ss store.Store) {
|
||||
require.Empty(t, channelMembers)
|
||||
|
||||
// Purging ChannelMemberHistory re-returns the result
|
||||
_, nErr = ss.ChannelMemberHistory().PermanentDeleteBatch(model.GetMillis()+1, 100)
|
||||
_, _, nErr = ss.ChannelMemberHistory().PermanentDeleteBatchForRetentionPolicies(
|
||||
0, model.GetMillis()+1, 100, model.RetentionPolicyCursor{})
|
||||
require.NoError(t, nErr)
|
||||
channelMembers, err = ss.Group().ChannelMembersToAdd(0, nil, false)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -14,6 +14,27 @@ type ChannelMemberHistoryStore struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// DeleteOrphanedRows provides a mock function with given fields: limit
|
||||
func (_m *ChannelMemberHistoryStore) DeleteOrphanedRows(limit int) (int64, error) {
|
||||
ret := _m.Called(limit)
|
||||
|
||||
var r0 int64
|
||||
if rf, ok := ret.Get(0).(func(int) int64); ok {
|
||||
r0 = rf(limit)
|
||||
} else {
|
||||
r0 = ret.Get(0).(int64)
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(int) error); ok {
|
||||
r1 = rf(limit)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetUsersInChannelDuring provides a mock function with given fields: startTime, endTime, channelID
|
||||
func (_m *ChannelMemberHistoryStore) GetUsersInChannelDuring(startTime int64, endTime int64, channelID string) ([]*model.ChannelMemberHistoryResult, error) {
|
||||
ret := _m.Called(startTime, endTime, channelID)
|
||||
@@ -85,3 +106,31 @@ func (_m *ChannelMemberHistoryStore) PermanentDeleteBatch(endTime int64, limit i
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// PermanentDeleteBatchForRetentionPolicies provides a mock function with given fields: now, globalPolicyEndTime, limit, cursor
|
||||
func (_m *ChannelMemberHistoryStore) PermanentDeleteBatchForRetentionPolicies(now int64, globalPolicyEndTime int64, limit int64, cursor model.RetentionPolicyCursor) (int64, model.RetentionPolicyCursor, error) {
|
||||
ret := _m.Called(now, globalPolicyEndTime, limit, cursor)
|
||||
|
||||
var r0 int64
|
||||
if rf, ok := ret.Get(0).(func(int64, int64, int64, model.RetentionPolicyCursor) int64); ok {
|
||||
r0 = rf(now, globalPolicyEndTime, limit, cursor)
|
||||
} else {
|
||||
r0 = ret.Get(0).(int64)
|
||||
}
|
||||
|
||||
var r1 model.RetentionPolicyCursor
|
||||
if rf, ok := ret.Get(1).(func(int64, int64, int64, model.RetentionPolicyCursor) model.RetentionPolicyCursor); ok {
|
||||
r1 = rf(now, globalPolicyEndTime, limit, cursor)
|
||||
} else {
|
||||
r1 = ret.Get(1).(model.RetentionPolicyCursor)
|
||||
}
|
||||
|
||||
var r2 error
|
||||
if rf, ok := ret.Get(2).(func(int64, int64, int64, model.RetentionPolicyCursor) error); ok {
|
||||
r2 = rf(now, globalPolicyEndTime, limit, cursor)
|
||||
} else {
|
||||
r2 = ret.Error(2)
|
||||
}
|
||||
|
||||
return r0, r1, r2
|
||||
}
|
||||
|
||||
@@ -102,6 +102,27 @@ func (_m *PostStore) Delete(postID string, time int64, deleteByID string) error
|
||||
return r0
|
||||
}
|
||||
|
||||
// DeleteOrphanedRows provides a mock function with given fields: limit
|
||||
func (_m *PostStore) DeleteOrphanedRows(limit int) (int64, error) {
|
||||
ret := _m.Called(limit)
|
||||
|
||||
var r0 int64
|
||||
if rf, ok := ret.Get(0).(func(int) int64); ok {
|
||||
r0 = rf(limit)
|
||||
} else {
|
||||
r0 = ret.Get(0).(int64)
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(int) error); ok {
|
||||
r1 = rf(limit)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Get provides a mock function with given fields: ctx, id, skipFetchThreads, collapsedThreads, collapsedThreadsExtended, userID
|
||||
func (_m *PostStore) Get(ctx context.Context, id string, skipFetchThreads bool, collapsedThreads bool, collapsedThreadsExtended bool, userID string) (*model.PostList, error) {
|
||||
ret := _m.Called(ctx, id, skipFetchThreads, collapsedThreads, collapsedThreadsExtended, userID)
|
||||
@@ -714,6 +735,34 @@ func (_m *PostStore) PermanentDeleteBatch(endTime int64, limit int64) (int64, er
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// PermanentDeleteBatchForRetentionPolicies provides a mock function with given fields: now, globalPolicyEndTime, limit, cursor
|
||||
func (_m *PostStore) PermanentDeleteBatchForRetentionPolicies(now int64, globalPolicyEndTime int64, limit int64, cursor model.RetentionPolicyCursor) (int64, model.RetentionPolicyCursor, error) {
|
||||
ret := _m.Called(now, globalPolicyEndTime, limit, cursor)
|
||||
|
||||
var r0 int64
|
||||
if rf, ok := ret.Get(0).(func(int64, int64, int64, model.RetentionPolicyCursor) int64); ok {
|
||||
r0 = rf(now, globalPolicyEndTime, limit, cursor)
|
||||
} else {
|
||||
r0 = ret.Get(0).(int64)
|
||||
}
|
||||
|
||||
var r1 model.RetentionPolicyCursor
|
||||
if rf, ok := ret.Get(1).(func(int64, int64, int64, model.RetentionPolicyCursor) model.RetentionPolicyCursor); ok {
|
||||
r1 = rf(now, globalPolicyEndTime, limit, cursor)
|
||||
} else {
|
||||
r1 = ret.Get(1).(model.RetentionPolicyCursor)
|
||||
}
|
||||
|
||||
var r2 error
|
||||
if rf, ok := ret.Get(2).(func(int64, int64, int64, model.RetentionPolicyCursor) error); ok {
|
||||
r2 = rf(now, globalPolicyEndTime, limit, cursor)
|
||||
} else {
|
||||
r2 = ret.Error(2)
|
||||
}
|
||||
|
||||
return r0, r1, r2
|
||||
}
|
||||
|
||||
// PermanentDeleteByChannel provides a mock function with given fields: channelID
|
||||
func (_m *PostStore) PermanentDeleteByChannel(channelID string) error {
|
||||
ret := _m.Called(channelID)
|
||||
|
||||
@@ -77,6 +77,27 @@ func (_m *PreferenceStore) DeleteCategoryAndName(category string, name string) e
|
||||
return r0
|
||||
}
|
||||
|
||||
// DeleteOrphanedRows provides a mock function with given fields: limit
|
||||
func (_m *PreferenceStore) DeleteOrphanedRows(limit int) (int64, error) {
|
||||
ret := _m.Called(limit)
|
||||
|
||||
var r0 int64
|
||||
if rf, ok := ret.Get(0).(func(int) int64); ok {
|
||||
r0 = rf(limit)
|
||||
} else {
|
||||
r0 = ret.Get(0).(int64)
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(int) error); ok {
|
||||
r1 = rf(limit)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Get provides a mock function with given fields: userID, category, name
|
||||
func (_m *PreferenceStore) Get(userID string, category string, name string) (*model.Preference, error) {
|
||||
ret := _m.Called(userID, category, name)
|
||||
|
||||
@@ -74,6 +74,27 @@ func (_m *ReactionStore) DeleteAllWithEmojiName(emojiName string) error {
|
||||
return r0
|
||||
}
|
||||
|
||||
// DeleteOrphanedRows provides a mock function with given fields: limit
|
||||
func (_m *ReactionStore) DeleteOrphanedRows(limit int) (int64, error) {
|
||||
ret := _m.Called(limit)
|
||||
|
||||
var r0 int64
|
||||
if rf, ok := ret.Get(0).(func(int) int64); ok {
|
||||
r0 = rf(limit)
|
||||
} else {
|
||||
r0 = ret.Get(0).(int64)
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(int) error); ok {
|
||||
r1 = rf(limit)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetForPost provides a mock function with given fields: postID, allowFromCache
|
||||
func (_m *ReactionStore) GetForPost(postID string, allowFromCache bool) ([]*model.Reaction, error) {
|
||||
ret := _m.Called(postID, allowFromCache)
|
||||
|
||||
@@ -56,6 +56,27 @@ func (_m *RetentionPolicyStore) Delete(id string) error {
|
||||
return r0
|
||||
}
|
||||
|
||||
// DeleteOrphanedRows provides a mock function with given fields: limit
|
||||
func (_m *RetentionPolicyStore) DeleteOrphanedRows(limit int) (int64, error) {
|
||||
ret := _m.Called(limit)
|
||||
|
||||
var r0 int64
|
||||
if rf, ok := ret.Get(0).(func(int) int64); ok {
|
||||
r0 = rf(limit)
|
||||
} else {
|
||||
r0 = ret.Get(0).(int64)
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(int) error); ok {
|
||||
r1 = rf(limit)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Get provides a mock function with given fields: id
|
||||
func (_m *RetentionPolicyStore) Get(id string) (*model.RetentionPolicyWithTeamAndChannelCounts, error) {
|
||||
ret := _m.Called(id)
|
||||
|
||||
@@ -66,6 +66,27 @@ func (_m *ThreadStore) DeleteMembershipForUser(userId string, postID string) err
|
||||
return r0
|
||||
}
|
||||
|
||||
// DeleteOrphanedRows provides a mock function with given fields: limit
|
||||
func (_m *ThreadStore) DeleteOrphanedRows(limit int) (int64, error) {
|
||||
ret := _m.Called(limit)
|
||||
|
||||
var r0 int64
|
||||
if rf, ok := ret.Get(0).(func(int) int64); ok {
|
||||
r0 = rf(limit)
|
||||
} else {
|
||||
r0 = ret.Get(0).(int64)
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(int) error); ok {
|
||||
r1 = rf(limit)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Get provides a mock function with given fields: id
|
||||
func (_m *ThreadStore) Get(id string) (*model.Thread, error) {
|
||||
ret := _m.Called(id)
|
||||
@@ -292,6 +313,62 @@ func (_m *ThreadStore) MarkAsRead(userID string, threadID string, timestamp int6
|
||||
return r0
|
||||
}
|
||||
|
||||
// PermanentDeleteBatchForRetentionPolicies provides a mock function with given fields: now, globalPolicyEndTime, limit, cursor
|
||||
func (_m *ThreadStore) PermanentDeleteBatchForRetentionPolicies(now int64, globalPolicyEndTime int64, limit int64, cursor model.RetentionPolicyCursor) (int64, model.RetentionPolicyCursor, error) {
|
||||
ret := _m.Called(now, globalPolicyEndTime, limit, cursor)
|
||||
|
||||
var r0 int64
|
||||
if rf, ok := ret.Get(0).(func(int64, int64, int64, model.RetentionPolicyCursor) int64); ok {
|
||||
r0 = rf(now, globalPolicyEndTime, limit, cursor)
|
||||
} else {
|
||||
r0 = ret.Get(0).(int64)
|
||||
}
|
||||
|
||||
var r1 model.RetentionPolicyCursor
|
||||
if rf, ok := ret.Get(1).(func(int64, int64, int64, model.RetentionPolicyCursor) model.RetentionPolicyCursor); ok {
|
||||
r1 = rf(now, globalPolicyEndTime, limit, cursor)
|
||||
} else {
|
||||
r1 = ret.Get(1).(model.RetentionPolicyCursor)
|
||||
}
|
||||
|
||||
var r2 error
|
||||
if rf, ok := ret.Get(2).(func(int64, int64, int64, model.RetentionPolicyCursor) error); ok {
|
||||
r2 = rf(now, globalPolicyEndTime, limit, cursor)
|
||||
} else {
|
||||
r2 = ret.Error(2)
|
||||
}
|
||||
|
||||
return r0, r1, r2
|
||||
}
|
||||
|
||||
// PermanentDeleteBatchThreadMembershipsForRetentionPolicies provides a mock function with given fields: now, globalPolicyEndTime, limit, cursor
|
||||
func (_m *ThreadStore) PermanentDeleteBatchThreadMembershipsForRetentionPolicies(now int64, globalPolicyEndTime int64, limit int64, cursor model.RetentionPolicyCursor) (int64, model.RetentionPolicyCursor, error) {
|
||||
ret := _m.Called(now, globalPolicyEndTime, limit, cursor)
|
||||
|
||||
var r0 int64
|
||||
if rf, ok := ret.Get(0).(func(int64, int64, int64, model.RetentionPolicyCursor) int64); ok {
|
||||
r0 = rf(now, globalPolicyEndTime, limit, cursor)
|
||||
} else {
|
||||
r0 = ret.Get(0).(int64)
|
||||
}
|
||||
|
||||
var r1 model.RetentionPolicyCursor
|
||||
if rf, ok := ret.Get(1).(func(int64, int64, int64, model.RetentionPolicyCursor) model.RetentionPolicyCursor); ok {
|
||||
r1 = rf(now, globalPolicyEndTime, limit, cursor)
|
||||
} else {
|
||||
r1 = ret.Get(1).(model.RetentionPolicyCursor)
|
||||
}
|
||||
|
||||
var r2 error
|
||||
if rf, ok := ret.Get(2).(func(int64, int64, int64, model.RetentionPolicyCursor) error); ok {
|
||||
r2 = rf(now, globalPolicyEndTime, limit, cursor)
|
||||
} else {
|
||||
r2 = ret.Error(2)
|
||||
}
|
||||
|
||||
return r0, r1, r2
|
||||
}
|
||||
|
||||
// Save provides a mock function with given fields: thread
|
||||
func (_m *ThreadStore) Save(thread *model.Thread) (*model.Thread, error) {
|
||||
ret := _m.Called(thread)
|
||||
|
||||
@@ -2586,16 +2586,31 @@ func testPostStoreGetPostsBatchForIndexing(t *testing.T, ss store.Store) {
|
||||
}
|
||||
|
||||
func testPostStorePermanentDeleteBatch(t *testing.T, ss store.Store) {
|
||||
team, err := ss.Team().Save(&model.Team{
|
||||
DisplayName: "DisplayName",
|
||||
Name: "team" + model.NewId(),
|
||||
Email: MakeEmail(),
|
||||
Type: model.TEAM_OPEN,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
channel, err := ss.Channel().Save(&model.Channel{
|
||||
TeamId: team.Id,
|
||||
DisplayName: "DisplayName",
|
||||
Name: "channel" + model.NewId(),
|
||||
Type: model.CHANNEL_OPEN,
|
||||
}, -1)
|
||||
require.NoError(t, err)
|
||||
|
||||
o1 := &model.Post{}
|
||||
o1.ChannelId = model.NewId()
|
||||
o1.ChannelId = channel.Id
|
||||
o1.UserId = model.NewId()
|
||||
o1.Message = "zz" + model.NewId() + "AAAAAAAAAAA"
|
||||
o1.CreateAt = 1000
|
||||
o1, err := ss.Post().Save(o1)
|
||||
o1, err = ss.Post().Save(o1)
|
||||
require.NoError(t, err)
|
||||
|
||||
o2 := &model.Post{}
|
||||
o2.ChannelId = model.NewId()
|
||||
o2.ChannelId = channel.Id
|
||||
o2.UserId = model.NewId()
|
||||
o2.Message = "zz" + model.NewId() + "AAAAAAAAAAA"
|
||||
o2.CreateAt = 1000
|
||||
@@ -2603,14 +2618,14 @@ func testPostStorePermanentDeleteBatch(t *testing.T, ss store.Store) {
|
||||
require.NoError(t, err)
|
||||
|
||||
o3 := &model.Post{}
|
||||
o3.ChannelId = model.NewId()
|
||||
o3.ChannelId = channel.Id
|
||||
o3.UserId = model.NewId()
|
||||
o3.Message = "zz" + model.NewId() + "AAAAAAAAAAA"
|
||||
o3.CreateAt = 100000
|
||||
o3, err = ss.Post().Save(o3)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = ss.Post().PermanentDeleteBatch(2000, 1000)
|
||||
_, _, err = ss.Post().PermanentDeleteBatchForRetentionPolicies(0, 2000, 1000, model.RetentionPolicyCursor{})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = ss.Post().Get(context.Background(), o1.Id, false, false, false, "")
|
||||
@@ -2620,7 +2635,160 @@ func testPostStorePermanentDeleteBatch(t *testing.T, ss store.Store) {
|
||||
require.Error(t, err, "Should have not found post 2 after purge")
|
||||
|
||||
_, err = ss.Post().Get(context.Background(), o3.Id, false, false, false, "")
|
||||
require.NoError(t, err, "Should have not found post 3 after purge")
|
||||
require.NoError(t, err, "Should have found post 3 after purge")
|
||||
|
||||
t.Run("with pagination", func(t *testing.T) {
|
||||
for i := 0; i < 3; i++ {
|
||||
_, err = ss.Post().Save(&model.Post{
|
||||
ChannelId: channel.Id,
|
||||
UserId: model.NewId(),
|
||||
Message: "message",
|
||||
CreateAt: 1,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
cursor := model.RetentionPolicyCursor{}
|
||||
|
||||
deleted, cursor, err := ss.Post().PermanentDeleteBatchForRetentionPolicies(0, 2, 2, cursor)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, int64(2), deleted)
|
||||
|
||||
deleted, _, err = ss.Post().PermanentDeleteBatchForRetentionPolicies(0, 2, 2, cursor)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, int64(1), deleted)
|
||||
})
|
||||
|
||||
t.Run("with data retention policies", func(t *testing.T) {
|
||||
channelPolicy, err2 := ss.RetentionPolicy().Save(&model.RetentionPolicyWithTeamAndChannelIDs{
|
||||
RetentionPolicy: model.RetentionPolicy{
|
||||
DisplayName: "DisplayName",
|
||||
PostDuration: model.NewInt64(30),
|
||||
},
|
||||
ChannelIDs: []string{channel.Id},
|
||||
})
|
||||
require.NoError(t, err2)
|
||||
post := &model.Post{
|
||||
ChannelId: channel.Id,
|
||||
UserId: model.NewId(),
|
||||
Message: "message",
|
||||
CreateAt: 1,
|
||||
}
|
||||
post, err2 = ss.Post().Save(post)
|
||||
require.NoError(t, err2)
|
||||
|
||||
_, _, err2 = ss.Post().PermanentDeleteBatchForRetentionPolicies(0, 2000, 1000, model.RetentionPolicyCursor{})
|
||||
require.NoError(t, err2)
|
||||
_, err2 = ss.Post().Get(context.Background(), post.Id, false, false, false, "")
|
||||
require.NoError(t, err2, "global policy should have been ignored due to granular policy")
|
||||
|
||||
nowMillis := post.CreateAt + *channelPolicy.PostDuration*24*60*60*1000 + 1
|
||||
_, _, err2 = ss.Post().PermanentDeleteBatchForRetentionPolicies(nowMillis, 0, 1000, model.RetentionPolicyCursor{})
|
||||
require.NoError(t, err2)
|
||||
_, err2 = ss.Post().Get(context.Background(), post.Id, false, false, false, "")
|
||||
require.Error(t, err2, "post should have been deleted by channel policy")
|
||||
|
||||
// Create a team policy which is stricter than the channel policy
|
||||
teamPolicy, err2 := ss.RetentionPolicy().Save(&model.RetentionPolicyWithTeamAndChannelIDs{
|
||||
RetentionPolicy: model.RetentionPolicy{
|
||||
DisplayName: "DisplayName",
|
||||
PostDuration: model.NewInt64(20),
|
||||
},
|
||||
TeamIDs: []string{team.Id},
|
||||
})
|
||||
require.NoError(t, err2)
|
||||
post.Id = ""
|
||||
post, err2 = ss.Post().Save(post)
|
||||
require.NoError(t, err2)
|
||||
|
||||
nowMillis = post.CreateAt + *teamPolicy.PostDuration*24*60*60*1000 + 1
|
||||
_, _, err2 = ss.Post().PermanentDeleteBatchForRetentionPolicies(nowMillis, 0, 1000, model.RetentionPolicyCursor{})
|
||||
require.NoError(t, err2)
|
||||
_, err2 = ss.Post().Get(context.Background(), post.Id, false, false, false, "")
|
||||
require.NoError(t, err2, "channel policy should have overridden team policy")
|
||||
|
||||
// Delete channel policy and re-run team policy
|
||||
err2 = ss.RetentionPolicy().RemoveChannels(channelPolicy.ID, []string{channel.Id})
|
||||
require.NoError(t, err2)
|
||||
|
||||
err2 = ss.RetentionPolicy().Delete(channelPolicy.ID)
|
||||
require.NoError(t, err2)
|
||||
|
||||
_, _, err2 = ss.Post().PermanentDeleteBatchForRetentionPolicies(nowMillis, 0, 1000, model.RetentionPolicyCursor{})
|
||||
require.NoError(t, err2)
|
||||
_, err2 = ss.Post().Get(context.Background(), post.Id, false, false, false, "")
|
||||
require.Error(t, err2, "post should have been deleted by team policy")
|
||||
|
||||
err2 = ss.RetentionPolicy().RemoveTeams(teamPolicy.ID, []string{team.Id})
|
||||
require.NoError(t, err2)
|
||||
|
||||
err2 = ss.RetentionPolicy().Delete(teamPolicy.ID)
|
||||
require.NoError(t, err2)
|
||||
})
|
||||
|
||||
t.Run("with channel, team and global policies", func(t *testing.T) {
|
||||
c1 := &model.Channel{}
|
||||
c1.TeamId = model.NewId()
|
||||
c1.DisplayName = "Channel1"
|
||||
c1.Name = "zz" + model.NewId() + "b"
|
||||
c1.Type = model.CHANNEL_OPEN
|
||||
c1, _ = ss.Channel().Save(c1, -1)
|
||||
|
||||
c2 := &model.Channel{}
|
||||
c2.TeamId = model.NewId()
|
||||
c2.DisplayName = "Channel2"
|
||||
c2.Name = "zz" + model.NewId() + "b"
|
||||
c2.Type = model.CHANNEL_OPEN
|
||||
c2, _ = ss.Channel().Save(c2, -1)
|
||||
|
||||
channelPolicy, err2 := ss.RetentionPolicy().Save(&model.RetentionPolicyWithTeamAndChannelIDs{
|
||||
RetentionPolicy: model.RetentionPolicy{
|
||||
DisplayName: "DisplayName",
|
||||
PostDuration: model.NewInt64(30),
|
||||
},
|
||||
ChannelIDs: []string{c1.Id},
|
||||
})
|
||||
require.NoError(t, err2)
|
||||
defer ss.RetentionPolicy().Delete(channelPolicy.ID)
|
||||
teamPolicy, err2 := ss.RetentionPolicy().Save(&model.RetentionPolicyWithTeamAndChannelIDs{
|
||||
RetentionPolicy: model.RetentionPolicy{
|
||||
DisplayName: "DisplayName",
|
||||
PostDuration: model.NewInt64(30),
|
||||
},
|
||||
TeamIDs: []string{team.Id},
|
||||
})
|
||||
require.NoError(t, err2)
|
||||
defer ss.RetentionPolicy().Delete(teamPolicy.ID)
|
||||
|
||||
// This one should be deleted by the channel policy
|
||||
_, err2 = ss.Post().Save(&model.Post{
|
||||
ChannelId: c1.Id,
|
||||
UserId: model.NewId(),
|
||||
Message: "message",
|
||||
CreateAt: 1,
|
||||
})
|
||||
require.NoError(t, err2)
|
||||
// This one, by the team policy
|
||||
_, err2 = ss.Post().Save(&model.Post{
|
||||
ChannelId: channel.Id,
|
||||
UserId: model.NewId(),
|
||||
Message: "message",
|
||||
CreateAt: 1,
|
||||
})
|
||||
require.NoError(t, err2)
|
||||
// This one, by the global policy
|
||||
_, err2 = ss.Post().Save(&model.Post{
|
||||
ChannelId: c2.Id,
|
||||
UserId: model.NewId(),
|
||||
Message: "message",
|
||||
CreateAt: 1,
|
||||
})
|
||||
require.NoError(t, err2)
|
||||
|
||||
nowMillis := int64(1 + 30*24*60*60*1000 + 1)
|
||||
deleted, _, err2 := ss.Post().PermanentDeleteBatchForRetentionPolicies(nowMillis, 2, 1000, model.RetentionPolicyCursor{})
|
||||
require.NoError(t, err2)
|
||||
require.Equal(t, int64(3), deleted)
|
||||
})
|
||||
}
|
||||
|
||||
func testPostStoreGetOldest(t *testing.T, ss store.Store) {
|
||||
|
||||
@@ -22,7 +22,7 @@ func TestPreferenceStore(t *testing.T, ss store.Store) {
|
||||
t.Run("PreferenceDelete", func(t *testing.T) { testPreferenceDelete(t, ss) })
|
||||
t.Run("PreferenceDeleteCategory", func(t *testing.T) { testPreferenceDeleteCategory(t, ss) })
|
||||
t.Run("PreferenceDeleteCategoryAndName", func(t *testing.T) { testPreferenceDeleteCategoryAndName(t, ss) })
|
||||
t.Run("PreferenceCleanupFlagsBatch", func(t *testing.T) { testPreferenceCleanupFlagsBatch(t, ss) })
|
||||
t.Run("PreferenceDeleteOrphanedRows", func(t *testing.T) { testPreferenceDeleteOrphanedRows(t, ss) })
|
||||
}
|
||||
|
||||
func testPreferenceSave(t *testing.T, ss store.Store) {
|
||||
@@ -330,44 +330,66 @@ func testPreferenceDeleteCategoryAndName(t *testing.T, ss store.Store) {
|
||||
assert.Empty(t, preferences, "should've returned no preference")
|
||||
}
|
||||
|
||||
func testPreferenceCleanupFlagsBatch(t *testing.T, ss store.Store) {
|
||||
func testPreferenceDeleteOrphanedRows(t *testing.T, ss store.Store) {
|
||||
const limit = 1000
|
||||
team, err := ss.Team().Save(&model.Team{
|
||||
DisplayName: "DisplayName",
|
||||
Name: "team" + model.NewId(),
|
||||
Email: MakeEmail(),
|
||||
Type: model.TEAM_OPEN,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
channel, err := ss.Channel().Save(&model.Channel{
|
||||
TeamId: team.Id,
|
||||
DisplayName: "DisplayName",
|
||||
Name: "channel" + model.NewId(),
|
||||
Type: model.CHANNEL_OPEN,
|
||||
}, -1)
|
||||
require.NoError(t, err)
|
||||
category := model.PREFERENCE_CATEGORY_FLAGGED_POST
|
||||
userId := model.NewId()
|
||||
|
||||
o1 := &model.Post{}
|
||||
o1.ChannelId = model.NewId()
|
||||
o1.UserId = userId
|
||||
o1.Message = "zz" + model.NewId() + "AAAAAAAAAAA"
|
||||
o1.CreateAt = 1000
|
||||
o1, err := ss.Post().Save(o1)
|
||||
olderPost, err := ss.Post().Save(&model.Post{
|
||||
ChannelId: channel.Id,
|
||||
UserId: userId,
|
||||
Message: "message",
|
||||
CreateAt: 1000,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
newerPost, err := ss.Post().Save(&model.Post{
|
||||
ChannelId: channel.Id,
|
||||
UserId: userId,
|
||||
Message: "message",
|
||||
CreateAt: 3000,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
preference1 := model.Preference{
|
||||
UserId: userId,
|
||||
Category: category,
|
||||
Name: o1.Id,
|
||||
Name: olderPost.Id,
|
||||
Value: "true",
|
||||
}
|
||||
|
||||
preference2 := model.Preference{
|
||||
UserId: userId,
|
||||
Category: category,
|
||||
Name: model.NewId(),
|
||||
Name: newerPost.Id,
|
||||
Value: "true",
|
||||
}
|
||||
|
||||
nErr := ss.Preference().Save(&model.Preferences{preference1, preference2})
|
||||
require.NoError(t, nErr)
|
||||
|
||||
_, nErr = ss.Preference().CleanupFlagsBatch(-1)
|
||||
require.Error(t, nErr)
|
||||
_, _, nErr = ss.Post().PermanentDeleteBatchForRetentionPolicies(0, 2000, limit, model.RetentionPolicyCursor{})
|
||||
assert.NoError(t, nErr)
|
||||
|
||||
_, nErr = ss.Preference().CleanupFlagsBatch(10000)
|
||||
_, nErr = ss.Preference().DeleteOrphanedRows(limit)
|
||||
assert.NoError(t, nErr)
|
||||
|
||||
_, nErr = ss.Preference().Get(userId, category, preference1.Name)
|
||||
assert.NoError(t, nErr)
|
||||
assert.Error(t, nErr, "older preference should have been deleted")
|
||||
|
||||
_, nErr = ss.Preference().Get(userId, category, preference2.Name)
|
||||
assert.Error(t, nErr)
|
||||
assert.NoError(t, nErr, "newer preference should not have been deleted")
|
||||
}
|
||||
|
||||
@@ -522,61 +522,72 @@ func testReactionDeleteAllWithEmojiName(t *testing.T, ss store.Store, s SqlStore
|
||||
}
|
||||
|
||||
func testReactionStorePermanentDeleteBatch(t *testing.T, ss store.Store) {
|
||||
post, err1 := ss.Post().Save(&model.Post{
|
||||
ChannelId: model.NewId(),
|
||||
UserId: model.NewId(),
|
||||
const limit = 1000
|
||||
team, err := ss.Team().Save(&model.Team{
|
||||
DisplayName: "DisplayName",
|
||||
Name: "team" + model.NewId(),
|
||||
Email: MakeEmail(),
|
||||
Type: model.TEAM_OPEN,
|
||||
})
|
||||
require.NoError(t, err1)
|
||||
require.NoError(t, err)
|
||||
channel, err := ss.Channel().Save(&model.Channel{
|
||||
TeamId: team.Id,
|
||||
DisplayName: "DisplayName",
|
||||
Name: "channel" + model.NewId(),
|
||||
Type: model.CHANNEL_OPEN,
|
||||
}, -1)
|
||||
require.NoError(t, err)
|
||||
olderPost, err := ss.Post().Save(&model.Post{
|
||||
ChannelId: channel.Id,
|
||||
UserId: model.NewId(),
|
||||
CreateAt: 1000,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
newerPost, err := ss.Post().Save(&model.Post{
|
||||
ChannelId: channel.Id,
|
||||
UserId: model.NewId(),
|
||||
CreateAt: 3000,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Reactions will be deleted based on the timestamp of their post. So the time at
|
||||
// which a reaction was created doesn't matter.
|
||||
reactions := []*model.Reaction{
|
||||
{
|
||||
UserId: model.NewId(),
|
||||
PostId: post.Id,
|
||||
PostId: olderPost.Id,
|
||||
EmojiName: "sad",
|
||||
CreateAt: 1000,
|
||||
},
|
||||
{
|
||||
UserId: model.NewId(),
|
||||
PostId: post.Id,
|
||||
PostId: olderPost.Id,
|
||||
EmojiName: "sad",
|
||||
CreateAt: 1500,
|
||||
},
|
||||
{
|
||||
UserId: model.NewId(),
|
||||
PostId: post.Id,
|
||||
EmojiName: "sad",
|
||||
CreateAt: 2000,
|
||||
},
|
||||
{
|
||||
UserId: model.NewId(),
|
||||
PostId: post.Id,
|
||||
EmojiName: "sad",
|
||||
CreateAt: 2000,
|
||||
PostId: newerPost.Id,
|
||||
EmojiName: "smile",
|
||||
},
|
||||
}
|
||||
|
||||
// Need to hang on to a reaction to delete later in order to clear the cache, as "allowFromCache" isn't honoured any more.
|
||||
var lastReaction *model.Reaction
|
||||
for _, reaction := range reactions {
|
||||
var nErr error
|
||||
lastReaction, nErr = ss.Reaction().Save(reaction)
|
||||
require.NoError(t, nErr)
|
||||
_, err = ss.Reaction().Save(reaction)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
returned, err := ss.Reaction().GetForPost(post.Id, false)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, returned, 4, "expected 4 reactions")
|
||||
|
||||
_, err = ss.Reaction().PermanentDeleteBatch(1800, 1000)
|
||||
_, _, err = ss.Post().PermanentDeleteBatchForRetentionPolicies(0, 2000, limit, model.RetentionPolicyCursor{})
|
||||
require.NoError(t, err)
|
||||
|
||||
// This is to force a clear of the cache.
|
||||
_, err = ss.Reaction().Delete(lastReaction)
|
||||
_, err = ss.Reaction().DeleteOrphanedRows(limit)
|
||||
require.NoError(t, err)
|
||||
|
||||
returned, err = ss.Reaction().GetForPost(post.Id, false)
|
||||
returned, err := ss.Reaction().GetForPost(olderPost.Id, false)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, returned, 1, "expected 1 reaction. Got: %v", len(returned))
|
||||
require.Len(t, returned, 0, "reactions for older post should have been deleted")
|
||||
|
||||
returned, err = ss.Reaction().GetForPost(newerPost.Id, false)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, returned, 1, "reactions for newer post should not have been deleted")
|
||||
}
|
||||
|
||||
func testReactionBulkGetForPosts(t *testing.T, ss store.Store) {
|
||||
|
||||
@@ -25,6 +25,7 @@ func TestRetentionPolicyStore(t *testing.T, ss store.Store, s SqlStore) {
|
||||
t.Run("GetTeams", func(t *testing.T) { testRetentionPolicyStoreGetTeams(t, ss, s) })
|
||||
t.Run("AddTeams", func(t *testing.T) { testRetentionPolicyStoreAddTeams(t, ss, s) })
|
||||
t.Run("RemoveTeams", func(t *testing.T) { testRetentionPolicyStoreRemoveTeams(t, ss, s) })
|
||||
t.Run("RemoveOrphanedRows", func(t *testing.T) { testRetentionPolicyStoreRemoveOrphanedRows(t, ss, s) })
|
||||
t.Run("GetPoliciesForUser", func(t *testing.T) { testRetentionPolicyStoreGetPoliciesForUser(t, ss, s) })
|
||||
}
|
||||
|
||||
@@ -656,3 +657,21 @@ func testRetentionPolicyStoreGetPoliciesForUser(t *testing.T, ss store.Store, s
|
||||
require.Equal(t, int64(len(channelIDs)), count)
|
||||
})
|
||||
}
|
||||
|
||||
func testRetentionPolicyStoreRemoveOrphanedRows(t *testing.T, ss store.Store, s SqlStore) {
|
||||
teamID := createTeamsForRetentionPolicy(t, ss, 1)[0]
|
||||
channelID := createChannelsForRetentionPolicy(t, ss, teamID, 1)[0]
|
||||
policy := saveRetentionPolicyWithTeamAndChannelIds(t, ss, "Policy 1",
|
||||
[]string{teamID}, []string{channelID})
|
||||
|
||||
err := ss.Channel().PermanentDelete(channelID)
|
||||
require.NoError(t, err)
|
||||
err = ss.Team().PermanentDelete(teamID)
|
||||
require.NoError(t, err)
|
||||
_, err = ss.RetentionPolicy().DeleteOrphanedRows(1000)
|
||||
require.NoError(t, err)
|
||||
|
||||
policy.ChannelIDs = make([]string, 0)
|
||||
policy.TeamIDs = make([]string, 0)
|
||||
checkRetentionPolicyLikeThisExists(t, ss, policy)
|
||||
}
|
||||
|
||||
@@ -17,6 +17,12 @@ import (
|
||||
|
||||
func TestThreadStore(t *testing.T, ss store.Store, s SqlStore) {
|
||||
t.Run("ThreadStorePopulation", func(t *testing.T) { testThreadStorePopulation(t, ss) })
|
||||
t.Run("ThreadStorePermanentDeleteBatchForRetentionPolicies", func(t *testing.T) {
|
||||
testThreadStorePermanentDeleteBatchForRetentionPolicies(t, ss)
|
||||
})
|
||||
t.Run("ThreadStorePermanentDeleteBatchThreadMembershipsForRetentionPolicies", func(t *testing.T) {
|
||||
testThreadStorePermanentDeleteBatchThreadMembershipsForRetentionPolicies(t, ss)
|
||||
})
|
||||
}
|
||||
|
||||
func testThreadStorePopulation(t *testing.T, ss store.Store) {
|
||||
@@ -411,3 +417,185 @@ func testThreadStorePopulation(t *testing.T, ss store.Store) {
|
||||
require.Equal(t, int64(0), th.UnreadReplies)
|
||||
})
|
||||
}
|
||||
|
||||
func threadStoreCreateReply(t *testing.T, ss store.Store, channelID, postID string, createAt int64) *model.Post {
|
||||
reply, err := ss.Post().Save(&model.Post{
|
||||
ChannelId: channelID,
|
||||
UserId: model.NewId(),
|
||||
CreateAt: createAt,
|
||||
RootId: postID,
|
||||
ParentId: postID,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
return reply
|
||||
}
|
||||
|
||||
func testThreadStorePermanentDeleteBatchForRetentionPolicies(t *testing.T, ss store.Store) {
|
||||
const limit = 1000
|
||||
team, err := ss.Team().Save(&model.Team{
|
||||
DisplayName: "DisplayName",
|
||||
Name: "team" + model.NewId(),
|
||||
Email: MakeEmail(),
|
||||
Type: model.TEAM_OPEN,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
channel, err := ss.Channel().Save(&model.Channel{
|
||||
TeamId: team.Id,
|
||||
DisplayName: "DisplayName",
|
||||
Name: "channel" + model.NewId(),
|
||||
Type: model.CHANNEL_OPEN,
|
||||
}, -1)
|
||||
require.NoError(t, err)
|
||||
|
||||
post, err := ss.Post().Save(&model.Post{
|
||||
ChannelId: channel.Id,
|
||||
UserId: model.NewId(),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
threadStoreCreateReply(t, ss, channel.Id, post.Id, 2000)
|
||||
|
||||
thread, err := ss.Thread().Get(post.Id)
|
||||
require.NoError(t, err)
|
||||
|
||||
channelPolicy, err := ss.RetentionPolicy().Save(&model.RetentionPolicyWithTeamAndChannelIDs{
|
||||
RetentionPolicy: model.RetentionPolicy{
|
||||
DisplayName: "DisplayName",
|
||||
PostDuration: model.NewInt64(30),
|
||||
},
|
||||
ChannelIDs: []string{channel.Id},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
nowMillis := thread.LastReplyAt + *channelPolicy.PostDuration*24*60*60*1000 + 1
|
||||
_, _, err = ss.Thread().PermanentDeleteBatchForRetentionPolicies(nowMillis, 0, limit, model.RetentionPolicyCursor{})
|
||||
require.NoError(t, err)
|
||||
_, err = ss.Thread().Get(post.Id)
|
||||
require.Error(t, err, "thread should have been deleted by channel policy")
|
||||
|
||||
// create a new thread
|
||||
threadStoreCreateReply(t, ss, channel.Id, post.Id, 2000)
|
||||
thread, err = ss.Thread().Get(post.Id)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create a team policy which is stricter than the channel policy
|
||||
teamPolicy, err := ss.RetentionPolicy().Save(&model.RetentionPolicyWithTeamAndChannelIDs{
|
||||
RetentionPolicy: model.RetentionPolicy{
|
||||
DisplayName: "DisplayName",
|
||||
PostDuration: model.NewInt64(20),
|
||||
},
|
||||
TeamIDs: []string{team.Id},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
nowMillis = thread.LastReplyAt + *teamPolicy.PostDuration*24*60*60*1000 + 1
|
||||
_, _, err = ss.Thread().PermanentDeleteBatchForRetentionPolicies(nowMillis, 0, limit, model.RetentionPolicyCursor{})
|
||||
require.NoError(t, err)
|
||||
_, err = ss.Thread().Get(post.Id)
|
||||
require.NoError(t, err, "channel policy should have overridden team policy")
|
||||
|
||||
// Delete channel policy and re-run team policy
|
||||
err = ss.RetentionPolicy().Delete(channelPolicy.ID)
|
||||
require.NoError(t, err)
|
||||
_, _, err = ss.Thread().PermanentDeleteBatchForRetentionPolicies(nowMillis, 0, limit, model.RetentionPolicyCursor{})
|
||||
require.NoError(t, err)
|
||||
_, err = ss.Thread().Get(post.Id)
|
||||
require.Error(t, err, "thread should have been deleted by team policy")
|
||||
}
|
||||
|
||||
func testThreadStorePermanentDeleteBatchThreadMembershipsForRetentionPolicies(t *testing.T, ss store.Store) {
|
||||
const limit = 1000
|
||||
userID := model.NewId()
|
||||
createThreadMembership := func(userID, postID string) *model.ThreadMembership {
|
||||
opts := store.ThreadMembershipOpts{
|
||||
Following: true,
|
||||
IncrementMentions: false,
|
||||
UpdateFollowing: true,
|
||||
UpdateViewedTimestamp: false,
|
||||
UpdateParticipants: false,
|
||||
}
|
||||
_, err := ss.Thread().MaintainMembership(userID, postID, opts)
|
||||
require.NoError(t, err)
|
||||
threadMembership, err := ss.Thread().GetMembershipForUser(userID, postID)
|
||||
require.NoError(t, err)
|
||||
return threadMembership
|
||||
}
|
||||
team, err := ss.Team().Save(&model.Team{
|
||||
DisplayName: "DisplayName",
|
||||
Name: "team" + model.NewId(),
|
||||
Email: MakeEmail(),
|
||||
Type: model.TEAM_OPEN,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
channel, err := ss.Channel().Save(&model.Channel{
|
||||
TeamId: team.Id,
|
||||
DisplayName: "DisplayName",
|
||||
Name: "channel" + model.NewId(),
|
||||
Type: model.CHANNEL_OPEN,
|
||||
}, -1)
|
||||
require.NoError(t, err)
|
||||
post, err := ss.Post().Save(&model.Post{
|
||||
ChannelId: channel.Id,
|
||||
UserId: model.NewId(),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
threadStoreCreateReply(t, ss, channel.Id, post.Id, 2000)
|
||||
|
||||
threadMembership := createThreadMembership(userID, post.Id)
|
||||
|
||||
channelPolicy, err := ss.RetentionPolicy().Save(&model.RetentionPolicyWithTeamAndChannelIDs{
|
||||
RetentionPolicy: model.RetentionPolicy{
|
||||
DisplayName: "DisplayName",
|
||||
PostDuration: model.NewInt64(30),
|
||||
},
|
||||
ChannelIDs: []string{channel.Id},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
nowMillis := threadMembership.LastUpdated + *channelPolicy.PostDuration*24*60*60*1000 + 1
|
||||
_, _, err = ss.Thread().PermanentDeleteBatchThreadMembershipsForRetentionPolicies(nowMillis, 0, limit, model.RetentionPolicyCursor{})
|
||||
require.NoError(t, err)
|
||||
_, err = ss.Thread().GetMembershipForUser(userID, post.Id)
|
||||
require.Error(t, err, "thread membership should have been deleted by channel policy")
|
||||
|
||||
// create a new thread membership
|
||||
threadMembership = createThreadMembership(userID, post.Id)
|
||||
|
||||
// Create a team policy which is stricter than the channel policy
|
||||
teamPolicy, err := ss.RetentionPolicy().Save(&model.RetentionPolicyWithTeamAndChannelIDs{
|
||||
RetentionPolicy: model.RetentionPolicy{
|
||||
DisplayName: "DisplayName",
|
||||
PostDuration: model.NewInt64(20),
|
||||
},
|
||||
TeamIDs: []string{team.Id},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
nowMillis = threadMembership.LastUpdated + *teamPolicy.PostDuration*24*60*60*1000 + 1
|
||||
_, _, err = ss.Thread().PermanentDeleteBatchThreadMembershipsForRetentionPolicies(nowMillis, 0, limit, model.RetentionPolicyCursor{})
|
||||
require.NoError(t, err)
|
||||
_, err = ss.Thread().GetMembershipForUser(userID, post.Id)
|
||||
require.NoError(t, err, "channel policy should have overridden team policy")
|
||||
|
||||
// Delete channel policy and re-run team policy
|
||||
err = ss.RetentionPolicy().Delete(channelPolicy.ID)
|
||||
require.NoError(t, err)
|
||||
_, _, err = ss.Thread().PermanentDeleteBatchThreadMembershipsForRetentionPolicies(nowMillis, 0, limit, model.RetentionPolicyCursor{})
|
||||
require.NoError(t, err)
|
||||
_, err = ss.Thread().GetMembershipForUser(userID, post.Id)
|
||||
require.Error(t, err, "thread membership should have been deleted by team policy")
|
||||
|
||||
// create a new thread membership
|
||||
threadMembership = createThreadMembership(userID, post.Id)
|
||||
|
||||
// Delete team policy and thread
|
||||
err = ss.RetentionPolicy().Delete(teamPolicy.ID)
|
||||
require.NoError(t, err)
|
||||
err = ss.Thread().Delete(post.Id)
|
||||
require.NoError(t, err)
|
||||
|
||||
deleted, err := ss.Thread().DeleteOrphanedRows(1000)
|
||||
require.NoError(t, err)
|
||||
require.NotZero(t, deleted)
|
||||
_, err = ss.Thread().GetMembershipForUser(userID, post.Id)
|
||||
require.Error(t, err, "thread membership should have been deleted because thread no longer exists")
|
||||
}
|
||||
|
||||
Ссылка в новой задаче
Block a user