MM-43598: Bulk export not exporting non-thread DMs from deactivated users (#30427)
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
0c8e30da4d
Коммит
e7e80634d6
@@ -1586,3 +1586,152 @@ func TestExportSchemes(t *testing.T) {
|
|||||||
require.Equal(t, customTeamGuestRole.BuiltIn, importedTeamGuestRole.BuiltIn)
|
require.Equal(t, customTeamGuestRole.BuiltIn, importedTeamGuestRole.BuiltIn)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestExportDeactivatedUserDMs specifically tests the MM-43598 bug
|
||||||
|
// by validating that direct messages from deactivated users are exported correctly
|
||||||
|
func TestExportDeactivatedUserDMs(t *testing.T) {
|
||||||
|
th1 := Setup(t).InitBasic()
|
||||||
|
defer th1.TearDown()
|
||||||
|
|
||||||
|
// Create a DM Channel
|
||||||
|
user2 := th1.BasicUser2
|
||||||
|
dmChannel := th1.CreateDmChannel(user2)
|
||||||
|
|
||||||
|
// 1. First basic user (active) sends a message to user2 (who will later be deactivated)
|
||||||
|
initialMessage := "initial_message_from_basic_user"
|
||||||
|
initialPost := &model.Post{
|
||||||
|
ChannelId: dmChannel.Id,
|
||||||
|
Message: initialMessage,
|
||||||
|
UserId: th1.BasicUser.Id,
|
||||||
|
}
|
||||||
|
initialPostCreated, appErr := th1.App.CreatePost(th1.Context, initialPost, dmChannel, model.CreatePostFlags{SetOnline: true})
|
||||||
|
require.Nil(t, appErr)
|
||||||
|
|
||||||
|
// 2. Have user2 reply with TWO types of replies:
|
||||||
|
|
||||||
|
// 2a. User2 replies in a thread (to the initial message)
|
||||||
|
threadedReplyMessage := "threaded_reply_from_user2"
|
||||||
|
threadedReply := &model.Post{
|
||||||
|
ChannelId: dmChannel.Id,
|
||||||
|
Message: threadedReplyMessage,
|
||||||
|
UserId: user2.Id,
|
||||||
|
RootId: initialPostCreated.Id, // This makes it a threaded reply
|
||||||
|
}
|
||||||
|
_, appErr = th1.App.CreatePost(th1.Context, threadedReply, dmChannel, model.CreatePostFlags{SetOnline: true})
|
||||||
|
require.Nil(t, appErr)
|
||||||
|
|
||||||
|
// 2b. User2 sends a standalone reply (NOT in a thread)
|
||||||
|
nonThreadedReplyMessage := "non_threaded_reply_from_user2"
|
||||||
|
nonThreadedReply := &model.Post{
|
||||||
|
ChannelId: dmChannel.Id,
|
||||||
|
Message: nonThreadedReplyMessage,
|
||||||
|
UserId: user2.Id,
|
||||||
|
// No RootId, making it a standalone message, not a thread reply
|
||||||
|
}
|
||||||
|
_, appErr = th1.App.CreatePost(th1.Context, nonThreadedReply, dmChannel, model.CreatePostFlags{SetOnline: true})
|
||||||
|
require.Nil(t, appErr)
|
||||||
|
|
||||||
|
// 3. Now deactivate user2
|
||||||
|
_, err := th1.App.UpdateActive(th1.Context, user2, false)
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
// 4. Export data
|
||||||
|
var b bytes.Buffer
|
||||||
|
appErr = th1.App.BulkExport(th1.Context, &b, "somePath", nil, model.BulkExportOpts{})
|
||||||
|
require.Nil(t, appErr)
|
||||||
|
|
||||||
|
// 5. Make a copy of the buffer for export validation
|
||||||
|
var exportDataCopy bytes.Buffer
|
||||||
|
_, nErr := exportDataCopy.Write(b.Bytes())
|
||||||
|
require.NoError(t, nErr)
|
||||||
|
|
||||||
|
// 6. Validate export data directly to ensure both types of replies are present
|
||||||
|
scanner := bufio.NewScanner(&exportDataCopy)
|
||||||
|
foundThreadedReply := false
|
||||||
|
foundNonThreadedReply := false
|
||||||
|
|
||||||
|
for scanner.Scan() {
|
||||||
|
var line imports.LineImportData
|
||||||
|
err := json.Unmarshal(scanner.Bytes(), &line)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for direct posts
|
||||||
|
if line.Type == "direct_post" && line.DirectPost != nil {
|
||||||
|
// Check for the non-threaded reply (the standalone message)
|
||||||
|
if line.DirectPost.Message != nil && *line.DirectPost.Message == nonThreadedReplyMessage {
|
||||||
|
foundNonThreadedReply = true
|
||||||
|
// Verify username is correctly preserved in export
|
||||||
|
require.Equal(t, user2.Username, *line.DirectPost.User,
|
||||||
|
"Deactivated user's username should be preserved in export for non-threaded reply")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for the thread starter and its replies
|
||||||
|
if line.DirectPost.Message != nil && *line.DirectPost.Message == initialMessage {
|
||||||
|
// Check if the threaded reply is in the replies array
|
||||||
|
if line.DirectPost.Replies != nil {
|
||||||
|
for _, reply := range *line.DirectPost.Replies {
|
||||||
|
if reply.Message != nil && *reply.Message == threadedReplyMessage {
|
||||||
|
foundThreadedReply = true
|
||||||
|
require.Equal(t, user2.Username, *reply.User,
|
||||||
|
"Deactivated user's username should be preserved in export for threaded reply")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is key for testing MM-43598
|
||||||
|
require.True(t, foundNonThreadedReply,
|
||||||
|
"Non-threaded reply from deactivated user must be present in export data")
|
||||||
|
require.True(t, foundThreadedReply,
|
||||||
|
"Threaded reply from deactivated user must be present in export data")
|
||||||
|
|
||||||
|
// 7. Import data into a new instance
|
||||||
|
th2 := Setup(t)
|
||||||
|
defer th2.TearDown()
|
||||||
|
|
||||||
|
i, appErr := th2.App.BulkImport(th2.Context, &b, nil, false, 5)
|
||||||
|
require.Nil(t, appErr)
|
||||||
|
require.Equal(t, 0, i)
|
||||||
|
|
||||||
|
// 8. Verify the DM channel was imported
|
||||||
|
channels, nErr := th2.App.Srv().Store().Channel().GetAllDirectChannelsForExportAfter(1000, "00000000", false)
|
||||||
|
require.NoError(t, nErr)
|
||||||
|
require.Equal(t, 1, len(channels), "Direct channel should be imported")
|
||||||
|
|
||||||
|
// 9. Verify all posts were imported
|
||||||
|
posts, nErr := th2.App.Srv().Store().Post().GetPosts(model.GetPostsOptions{
|
||||||
|
ChannelId: channels[0].Id,
|
||||||
|
PerPage: 1000,
|
||||||
|
}, false, nil)
|
||||||
|
require.NoError(t, nErr)
|
||||||
|
|
||||||
|
// We should have exactly 3 posts
|
||||||
|
require.Equal(t, 3, len(posts.Posts), "Should have imported exactly 3 posts")
|
||||||
|
|
||||||
|
// 10. Specifically check that both types of replies are present
|
||||||
|
foundThreadedReplyInImport := false
|
||||||
|
foundNonThreadedReplyInImport := false
|
||||||
|
|
||||||
|
for _, post := range posts.Posts {
|
||||||
|
if post.Message == threadedReplyMessage {
|
||||||
|
foundThreadedReplyInImport = true
|
||||||
|
// Verify this is a reply in a thread
|
||||||
|
require.NotEmpty(t, post.RootId, "Threaded reply should have a RootId")
|
||||||
|
}
|
||||||
|
if post.Message == nonThreadedReplyMessage {
|
||||||
|
foundNonThreadedReplyInImport = true
|
||||||
|
// Verify this is NOT a reply in a thread
|
||||||
|
require.Empty(t, post.RootId, "Non-threaded reply should not have a RootId")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This directly tests the issue in MM-43598
|
||||||
|
require.True(t, foundNonThreadedReplyInImport,
|
||||||
|
"Non-threaded reply from deactivated user should be imported")
|
||||||
|
require.True(t, foundThreadedReplyInImport,
|
||||||
|
"Threaded reply from deactivated user should be imported")
|
||||||
|
}
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user