[MM-59367] export: enable exporting thread followers for CRT (#27623)

Этот коммит содержится в:
Ibrahim Serdar Acikgoz
2024-08-29 14:06:41 +02:00
коммит произвёл GitHub
родитель 3dc0e63c03
Коммит d5cc2eb2f6
17 изменённых файлов: 1430 добавлений и 63 удалений

Просмотреть файл

@@ -259,6 +259,36 @@ func (_m *ThreadStore) GetThreadForUser(threadMembership *model.ThreadMembership
return r0, r1
}
// GetThreadMembershipsForExport provides a mock function with given fields: postID
func (_m *ThreadStore) GetThreadMembershipsForExport(postID string) ([]*model.ThreadMembershipForExport, error) {
ret := _m.Called(postID)
if len(ret) == 0 {
panic("no return value specified for GetThreadMembershipsForExport")
}
var r0 []*model.ThreadMembershipForExport
var r1 error
if rf, ok := ret.Get(0).(func(string) ([]*model.ThreadMembershipForExport, error)); ok {
return rf(postID)
}
if rf, ok := ret.Get(0).(func(string) []*model.ThreadMembershipForExport); ok {
r0 = rf(postID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*model.ThreadMembershipForExport)
}
}
if rf, ok := ret.Get(1).(func(string) error); ok {
r1 = rf(postID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetThreadUnreadReplyCount provides a mock function with given fields: threadMembership
func (_m *ThreadStore) GetThreadUnreadReplyCount(threadMembership *model.ThreadMembership) (int64, error) {
ret := _m.Called(threadMembership)
@@ -459,6 +489,36 @@ func (_m *ThreadStore) MaintainMembership(userID string, postID string, opts sto
return r0, r1
}
// MaintainMultipleFromImport provides a mock function with given fields: memberships
func (_m *ThreadStore) MaintainMultipleFromImport(memberships []*model.ThreadMembership) ([]*model.ThreadMembership, error) {
ret := _m.Called(memberships)
if len(ret) == 0 {
panic("no return value specified for MaintainMultipleFromImport")
}
var r0 []*model.ThreadMembership
var r1 error
if rf, ok := ret.Get(0).(func([]*model.ThreadMembership) ([]*model.ThreadMembership, error)); ok {
return rf(memberships)
}
if rf, ok := ret.Get(0).(func([]*model.ThreadMembership) []*model.ThreadMembership); ok {
r0 = rf(memberships)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*model.ThreadMembership)
}
}
if rf, ok := ret.Get(1).(func([]*model.ThreadMembership) error); ok {
r1 = rf(memberships)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// MarkAllAsRead provides a mock function with given fields: userID, threadIds
func (_m *ThreadStore) MarkAllAsRead(userID string, threadIds []string) error {
ret := _m.Called(userID, threadIds)
@@ -601,6 +661,36 @@ func (_m *ThreadStore) PermanentDeleteBatchThreadMembershipsForRetentionPolicies
return r0, r1, r2
}
// SaveMultipleMemberships provides a mock function with given fields: memberships
func (_m *ThreadStore) SaveMultipleMemberships(memberships []*model.ThreadMembership) ([]*model.ThreadMembership, error) {
ret := _m.Called(memberships)
if len(ret) == 0 {
panic("no return value specified for SaveMultipleMemberships")
}
var r0 []*model.ThreadMembership
var r1 error
if rf, ok := ret.Get(0).(func([]*model.ThreadMembership) ([]*model.ThreadMembership, error)); ok {
return rf(memberships)
}
if rf, ok := ret.Get(0).(func([]*model.ThreadMembership) []*model.ThreadMembership); ok {
r0 = rf(memberships)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*model.ThreadMembership)
}
}
if rf, ok := ret.Get(1).(func([]*model.ThreadMembership) error); ok {
r1 = rf(memberships)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// UpdateMembership provides a mock function with given fields: membership
func (_m *ThreadStore) UpdateMembership(membership *model.ThreadMembership) (*model.ThreadMembership, error) {
ret := _m.Called(membership)

Просмотреть файл

@@ -30,6 +30,8 @@ func TestThreadStore(t *testing.T, rctx request.CTX, ss store.Store, s SqlStore)
t.Run("MarkAllAsReadByChannels", func(t *testing.T) { testMarkAllAsReadByChannels(t, rctx, ss) })
t.Run("MarkAllAsReadByTeam", func(t *testing.T) { testMarkAllAsReadByTeam(t, rctx, ss) })
t.Run("DeleteMembershipsForChannel", func(t *testing.T) { testDeleteMembershipsForChannel(t, rctx, ss) })
t.Run("SaveMultipleMemberships", func(t *testing.T) { testSaveMultipleMemberships(t, ss) })
t.Run("MaintainMultipleFromImport", func(t *testing.T) { testMaintainMultipleFromImport(t, rctx, ss) })
}
func testThreadStorePopulation(t *testing.T, rctx request.CTX, ss store.Store) {
@@ -1201,6 +1203,75 @@ func testVarious(t *testing.T, rctx request.CTX, ss store.Store) {
})
}
})
t.Run(("GetThreadMembershipsForExport"), func(t *testing.T) {
t.Run("Get members for thread, ensure usernames", func(t *testing.T) {
members, err := ss.Thread().GetThreadMembershipsForExport(team1channel1post1.Id)
require.NoError(t, err)
// team1channel1post1 has 1 member
assert.Len(t, members, 1)
userIDs, err := ss.Thread().GetThreadFollowers(team1channel1post1.Id, true)
require.NoError(t, err)
require.Len(t, userIDs, 1)
u, err := ss.User().Get(context.Background(), userIDs[0])
require.NoError(t, err)
assert.Equal(t, u.Username, members[0].Username)
members, err = ss.Thread().GetThreadMembershipsForExport(team1channel1post2.Id)
require.NoError(t, err)
// team1channel1post2 has 2 members
assert.Len(t, members, 2)
userIDs, err = ss.Thread().GetThreadFollowers(team1channel1post2.Id, true)
require.NoError(t, err)
require.Len(t, userIDs, 2)
for i := range userIDs {
u, err := ss.User().Get(context.Background(), userIDs[i])
require.NoError(t, err)
assert.Equal(t, u.Username, members[i].Username)
}
})
t.Run("Get members for a thread, ensure only following members are exported", func(t *testing.T) {
createThreadMembership(user2ID, team1channel1post1.Id, false)
members, err := ss.Thread().GetThreadMembershipsForExport(team1channel1post1.Id)
require.NoError(t, err)
// team1channel1post1 should have 2 members
assert.Len(t, members, 2)
_, err = ss.Thread().MaintainMembership(user2ID, team1channel1post1.Id, store.ThreadMembershipOpts{
Following: false,
UpdateFollowing: true,
UpdateViewedTimestamp: false,
UpdateParticipants: true,
})
require.NoError(t, err)
members, err = ss.Thread().GetThreadMembershipsForExport(team1channel1post1.Id)
require.NoError(t, err)
// team1channel1post1 should have 1 following member
assert.Len(t, members, 1)
userIDs, err := ss.Thread().GetThreadFollowers(team1channel1post1.Id, true)
require.NoError(t, err)
require.Len(t, userIDs, 1)
u, err := ss.User().Get(context.Background(), userIDs[0])
require.NoError(t, err)
assert.Equal(t, u.Username, members[0].Username)
})
})
}
func testMarkAllAsReadByChannels(t *testing.T, rctx request.CTX, ss store.Store) {
@@ -1690,3 +1761,241 @@ func testDeleteMembershipsForChannel(t *testing.T, rctx request.CTX, ss store.St
require.ElementsMatch(t, []*model.ThreadMembership{memB1}, membershipsB)
})
}
func testSaveMultipleMemberships(t *testing.T, ss store.Store) {
t.Run("should save multiple memberships", func(t *testing.T) {
memberships := []*model.ThreadMembership{
{
PostId: model.NewId(),
UserId: model.NewId(),
Following: true,
},
{
PostId: model.NewId(),
UserId: model.NewId(),
Following: true,
},
}
_, err := ss.Thread().SaveMultipleMemberships(memberships)
require.NoError(t, err)
})
t.Run("should return error if any of the memberships is invalid", func(t *testing.T) {
memberships := []*model.ThreadMembership{
{
PostId: model.NewId(),
UserId: "invalid",
Following: true,
},
{
PostId: model.NewId(),
UserId: model.NewId(),
Following: true,
},
}
_, err := ss.Thread().SaveMultipleMemberships(memberships)
require.Error(t, err)
})
t.Run("should not fail if the list is empty", func(t *testing.T) {
_, err := ss.Thread().SaveMultipleMemberships([]*model.ThreadMembership{})
require.NoError(t, err)
})
t.Run("should fail if there is a conflict", func(t *testing.T) {
postID := model.NewId()
userID := model.NewId()
memberships := []*model.ThreadMembership{
{
PostId: postID,
UserId: userID,
Following: true,
},
{
PostId: postID,
UserId: userID,
Following: true,
},
}
_, err := ss.Thread().SaveMultipleMemberships(memberships)
require.Error(t, err)
})
}
func testMaintainMultipleFromImport(t *testing.T, rctx request.CTX, ss store.Store) {
createThreadMembership := func(userID, postID string, following bool) (*model.ThreadMembership, func()) {
t.Helper()
opts := store.ThreadMembershipOpts{
Following: following,
IncrementMentions: false,
UpdateFollowing: true,
UpdateViewedTimestamp: false,
UpdateParticipants: false,
}
mem, err := ss.Thread().MaintainMembership(userID, postID, opts)
require.NoError(t, err)
return mem, func() {
err := ss.Thread().DeleteMembershipForUser(userID, postID)
require.NoError(t, err)
}
}
cleanMembers := func(userIDs []string, postID string) error {
// clean the thread memberships
for _, id := range userIDs {
err := ss.Thread().DeleteMembershipForUser(id, postID)
if err != nil {
return err
}
}
return nil
}
postingUserID := model.NewId()
team, err := ss.Team().Save(&model.Team{
DisplayName: "DisplayName",
Name: "team" + model.NewId(),
Email: MakeEmail(),
Type: model.TeamOpen,
})
require.NoError(t, err)
channel1, err := ss.Channel().Save(rctx, &model.Channel{
TeamId: team.Id,
DisplayName: "DisplayName",
Name: "channel1" + model.NewId(),
Type: model.ChannelTypeOpen,
}, -1)
require.NoError(t, err)
rootPost1, err := ss.Post().Save(rctx, &model.Post{
ChannelId: channel1.Id,
UserId: postingUserID,
Message: model.NewRandomString(10),
})
require.NoError(t, err)
_, err = ss.Post().Save(rctx, &model.Post{
ChannelId: channel1.Id,
UserId: postingUserID,
Message: model.NewRandomString(10),
RootId: rootPost1.Id,
})
require.NoError(t, err)
t.Run("Should create new memberships from new list", func(t *testing.T) {
userAID := model.NewId()
userBID := model.NewId()
_, err := ss.Thread().MaintainMultipleFromImport([]*model.ThreadMembership{
{
UserId: userAID,
PostId: rootPost1.Id,
Following: true,
},
{
UserId: userBID,
PostId: rootPost1.Id,
Following: true,
},
})
require.NoError(t, err)
followers, err := ss.Thread().GetThreadFollowers(rootPost1.Id, true)
require.NoError(t, err)
require.ElementsMatch(t, followers, []string{userAID, userBID})
// clean the thread memberships
err = cleanMembers(followers, rootPost1.Id)
require.NoError(t, err)
})
t.Run("Should add incoming memberships from the list", func(t *testing.T) {
userAID := model.NewId()
userBID := model.NewId()
_, clean := createThreadMembership(userAID, rootPost1.Id, true)
defer clean()
_, err := ss.Thread().MaintainMultipleFromImport([]*model.ThreadMembership{
{
UserId: userBID,
PostId: rootPost1.Id,
Following: true,
},
})
require.NoError(t, err)
followers, err := ss.Thread().GetThreadFollowers(rootPost1.Id, true)
require.NoError(t, err)
require.ElementsMatch(t, followers, []string{userAID, userBID})
// clean the thread memberships
err = cleanMembers(followers, rootPost1.Id)
require.NoError(t, err)
})
t.Run("Should update memberships if they are newer", func(t *testing.T) {
userAID := model.NewId()
old, clean := createThreadMembership(userAID, rootPost1.Id, true)
defer clean()
_, err := ss.Thread().MaintainMultipleFromImport([]*model.ThreadMembership{
{
UserId: userAID,
PostId: rootPost1.Id,
Following: true,
LastViewed: time.Now().Add(time.Minute).UnixMilli(),
},
})
require.NoError(t, err)
followers, err := ss.Thread().GetThreadFollowers(rootPost1.Id, true)
require.NoError(t, err)
require.ElementsMatch(t, followers, []string{userAID})
updated, err := ss.Thread().GetMembershipForUser(userAID, rootPost1.Id)
require.NoError(t, err)
require.Greater(t, updated.LastViewed, old.LastViewed)
// clean the thread memberships
err = cleanMembers(followers, rootPost1.Id)
require.NoError(t, err)
})
t.Run("Should not update membership if incoming is not newer", func(t *testing.T) {
userAID := model.NewId()
_, clean := createThreadMembership(userAID, rootPost1.Id, false)
defer clean()
_, err := ss.Thread().MaintainMultipleFromImport([]*model.ThreadMembership{
{
UserId: userAID,
PostId: rootPost1.Id,
Following: true,
LastViewed: time.Now().Add(-1 * time.Hour).UnixMilli(),
},
})
require.NoError(t, err)
followers, err := ss.Thread().GetThreadFollowers(rootPost1.Id, true)
require.NoError(t, err)
require.Empty(t, followers)
m, err := ss.Thread().GetMembershipForUser(userAID, rootPost1.Id)
require.NoError(t, err)
require.False(t, m.Following)
// clean the thread memberships
err = cleanMembers(followers, rootPost1.Id)
require.NoError(t, err)
})
}