Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix possible race condition that can lead to duplicated messages #627

Merged
merged 10 commits into from
May 17, 2024
9 changes: 9 additions & 0 deletions server/handlers/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,10 @@ func (ah *ActivityHandler) handleCreatedActivity(msg *clientmodels.Message, subs
return metrics.DiscardedReasonNotUserEvent
}

if strings.HasSuffix(msg.Text, "<abbr title=\"generated-from-mattermost\"></abbr>") {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be a constant?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably, let me move it to a constants

return metrics.DiscardedReasonGeneratedFromMattermost
}

isDirectMessage := IsDirectMessage(activityIds.ChatID)

// Avoid possible duplication
Expand Down Expand Up @@ -352,6 +356,11 @@ func (ah *ActivityHandler) handleCreatedActivity(msg *clientmodels.Message, subs

post, skippedFileAttachments, errorFound := ah.msgToPost(channelID, senderID, msg, chat, []string{})

// Last second check to avoid possible duplication
if postInfo, _ = ah.plugin.GetStore().GetPostInfoByMSTeamsID(msg.ChatID+msg.ChannelID, msg.ID); postInfo != nil {
return metrics.DiscardedReasonDuplicatedPost
}

newPost, appErr := ah.plugin.GetAPI().CreatePost(post)
if appErr != nil {
ah.plugin.GetAPI().LogWarn("Unable to create post", "error", appErr)
Expand Down
50 changes: 46 additions & 4 deletions server/handlers/handlers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,7 @@ func TestHandleCreatedActivity(t *testing.T) {
mockAPI.On("CreatePost", testutils.GetPostFromTeamsMessage(mmCreateAtTime)).Return(nil, testutils.GetInternalServerAppError("unable to create the post")).Times(1)
},
setupStore: func(store *mocksStore.Store) {
store.On("GetPostInfoByMSTeamsID", testutils.GetChatID(), testutils.GetMessageID()).Return(nil, nil).Times(1)
store.On("GetPostInfoByMSTeamsID", testutils.GetChatID(), testutils.GetMessageID()).Return(nil, nil).Times(2)
store.On("MattermostToTeamsUserID", "mock-BotUserID").Return(testutils.GetTeamsUserID(), nil).Times(1)
store.On("TeamsToMattermostUserID", "mockUserID-1").Return("mockUserID-1", nil).Times(1)
store.On("TeamsToMattermostUserID", "mockUserID-2").Return("mockUserID-2", nil).Times(1)
Expand Down Expand Up @@ -377,7 +377,7 @@ func TestHandleCreatedActivity(t *testing.T) {
store.On("TeamsToMattermostUserID", "mockUserID-1").Return("mockUserID-1", nil).Times(1)
store.On("TeamsToMattermostUserID", "mockUserID-2").Return("mockUserID-2", nil).Times(1)
store.On("TeamsToMattermostUserID", testutils.GetSenderID()).Return(testutils.GetUserID(), nil).Times(1)
store.On("GetPostInfoByMSTeamsID", testutils.GetChatID(), testutils.GetMessageID()).Return(nil, nil).Times(1)
store.On("GetPostInfoByMSTeamsID", testutils.GetChatID(), testutils.GetMessageID()).Return(nil, nil).Times(2)
store.On("LinkPosts", storemodels.PostInfo{
MattermostID: testutils.GetID(),
MSTeamsID: testutils.GetMessageID(),
Expand All @@ -390,6 +390,48 @@ func TestHandleCreatedActivity(t *testing.T) {
mockmetrics.On("ObserveMessageDelay", metrics.ActionCreated, metrics.ActionSourceMSTeams, true, mock.AnythingOfType("time.Duration")).Times(1)
},
},
{
description: "Message generated from Mattermost",
activityIds: clientmodels.ActivityIds{
ChatID: testutils.GetChatID(),
MessageID: testutils.GetMessageID(),
},
setupPlugin: func(p *mocksPlugin.PluginIface, client *mocksClient.Client, mockAPI *plugintest.API, store *mocksStore.Store, mockmetrics *mocksMetrics.Metrics) {
p.On("GetClientForApp").Return(client).Maybe()
p.On("GetClientForTeamsUser", "mockUserID-1").Return(client, nil).Times(1)
p.On("GetAPI").Return(mockAPI).Maybe()
p.On("GetStore").Return(store).Maybe()
p.On("GetMetrics").Return(mockmetrics).Maybe()
},
setupClient: func(client *mocksClient.Client) {
client.On("GetChat", testutils.GetChatID()).Return(&clientmodels.Chat{
ID: testutils.GetChatID(),
Members: []clientmodels.ChatMember{
{UserID: "mockUserID-1"},
{UserID: "mockUserID-2"},
},
Type: "D",
}, nil).Times(1)
client.On("GetChatMessage", testutils.GetChatID(), testutils.GetMessageID()).Return(&clientmodels.Message{
ID: testutils.GetMessageID(),
UserID: testutils.GetSenderID(),
ChatID: testutils.GetChatID(),
UserDisplayName: "mockUserDisplayName",
Text: "mockText<abbr title=\"generated-from-mattermost\"></abbr>",
CreateAt: msteamsCreateAtTime,
LastUpdateAt: msteamsCreateAtTime,
}, nil).Times(1)
},
setupAPI: func(mockAPI *plugintest.API) {
mockAPI.On("GetDirectChannel", "mockUserID-1", "mockUserID-2").Return(&model.Channel{Id: testutils.GetChannelID()}, nil).Times(1)
mockAPI.On("GetUser", testutils.GetUserID()).Return(testutils.GetUser(model.ChannelAdminRoleId, "test@test.com"), nil).Once()
mockAPI.On("CreatePost", testutils.GetPostFromTeamsMessage(mmCreateAtTime)).Return(testutils.GetPost(testutils.GetChannelID(), testutils.GetUserID(), mmCreateAtTime), nil).Times(1)
},
setupStore: func(store *mocksStore.Store) {
},
setupMetrics: func(mockmetrics *mocksMetrics.Metrics) {
},
},
{
description: "Valid: chat message",
activityIds: clientmodels.ActivityIds{
Expand Down Expand Up @@ -437,7 +479,7 @@ func TestHandleCreatedActivity(t *testing.T) {
store.On("TeamsToMattermostUserID", "mockUserID-1").Return("mockUserID-1", nil).Times(1)
store.On("TeamsToMattermostUserID", "mockUserID-2").Return("mockUserID-2", nil).Times(1)
store.On("TeamsToMattermostUserID", testutils.GetSenderID()).Return(testutils.GetUserID(), nil).Times(1)
store.On("GetPostInfoByMSTeamsID", testutils.GetChatID(), testutils.GetMessageID()).Return(nil, nil).Times(1)
store.On("GetPostInfoByMSTeamsID", testutils.GetChatID(), testutils.GetMessageID()).Return(nil, nil).Times(2)
store.On("LinkPosts", storemodels.PostInfo{
MattermostID: testutils.GetID(),
MSTeamsID: testutils.GetMessageID(),
Expand Down Expand Up @@ -528,7 +570,7 @@ func TestHandleCreatedActivity(t *testing.T) {
Creator: "mockCreator",
MattermostChannelID: testutils.GetChannelID(),
}, nil).Times(1)
store.On("GetPostInfoByMSTeamsID", testutils.GetChannelID(), testutils.GetMessageID()).Return(nil, nil).Times(1)
store.On("GetPostInfoByMSTeamsID", testutils.GetChannelID(), testutils.GetMessageID()).Return(nil, nil).Times(2)
store.On("LinkPosts", storemodels.PostInfo{
MattermostID: testutils.GetID(),
MSTeamsID: testutils.GetMessageID(),
Expand Down
4 changes: 4 additions & 0 deletions server/message_hooks.go
Original file line number Diff line number Diff line change
Expand Up @@ -595,6 +595,8 @@ func (p *Plugin) SendChat(srcUser string, usersIDs []string, post *model.Post, c
}
}

content += "<abbr title=\"generated-from-mattermost\"></abbr>"

newMessage, err := client.SendChat(chat.ID, content, parentMessage, attachments, mentions)
if err != nil {
p.API.LogWarn("Error creating post on MS Teams", "error", err.Error())
Expand Down Expand Up @@ -676,6 +678,8 @@ func (p *Plugin) Send(teamID, channelID string, user *model.User, post *model.Po

content, mentions := p.getMentionsData(content, teamID, channelID, "", client)

content += "<abbr title=\"generated-from-mattermost\"></abbr>"

newMessage, err := client.SendMessageWithAttachments(teamID, channelID, parentID, content, attachments, mentions)
if err != nil {
p.API.LogWarn("Error creating post on MS Teams", "error", err.Error())
Expand Down
28 changes: 14 additions & 14 deletions server/message_hooks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1362,7 +1362,7 @@ func TestSendChat(t *testing.T) {
uclient.On("UploadFile", "", "", "mockFile.Name"+"_"+testutils.GetID()+".txt", 1, "mockMimeType", bytes.NewReader([]byte("mockData")), mockChat).Return(&clientmodels.Attachment{
ID: testutils.GetID(),
}, nil).Times(1)
uclient.On("SendChat", testutils.GetChatID(), "<p>mockMessage??????????</p>\n", (*clientmodels.Message)(nil), []*clientmodels.Attachment{{
uclient.On("SendChat", testutils.GetChatID(), "<p>mockMessage??????????</p>\n<abbr title=\"generated-from-mattermost\"></abbr>", (*clientmodels.Message)(nil), []*clientmodels.Attachment{{
ID: testutils.GetID(),
}}, []models.ChatMessageMentionable{}).Return(nil, errors.New("unable to send the chat")).Times(1)
},
Expand Down Expand Up @@ -1397,7 +1397,7 @@ func TestSendChat(t *testing.T) {
SetupClient: func(client *clientmocks.Client, uclient *clientmocks.Client) {
uclient.On("CreateOrGetChatForUsers", mock.AnythingOfType("[]string")).Return(mockChat, nil).Times(1)
uclient.On("GetChat", testutils.GetChatID()).Return(mockChat, nil).Times(1)
uclient.On("SendChat", testutils.GetChatID(), "<p>mockMessage??????????</p>\n", (*clientmodels.Message)(nil), []*clientmodels.Attachment{{
uclient.On("SendChat", testutils.GetChatID(), "<p>mockMessage??????????</p>\n<abbr title=\"generated-from-mattermost\"></abbr>", (*clientmodels.Message)(nil), []*clientmodels.Attachment{{
ID: testutils.GetID(),
}}, []models.ChatMessageMentionable{}).Return(&clientmodels.Message{
ID: "mockMessageID",
Expand Down Expand Up @@ -1441,7 +1441,7 @@ func TestSendChat(t *testing.T) {
SetupClient: func(client *clientmocks.Client, uclient *clientmocks.Client) {
uclient.On("CreateOrGetChatForUsers", mock.AnythingOfType("[]string")).Return(mockChat, nil).Once()
uclient.On("GetChat", testutils.GetChatID()).Return(mockChat, nil).Times(1)
uclient.On("SendChat", testutils.GetChatID(), "<p>mockMessage??????????</p>\n", (*clientmodels.Message)(nil), []*clientmodels.Attachment{{
uclient.On("SendChat", testutils.GetChatID(), "<p>mockMessage??????????</p>\n<abbr title=\"generated-from-mattermost\"></abbr>", (*clientmodels.Message)(nil), []*clientmodels.Attachment{{
ID: testutils.GetID(),
}}, []models.ChatMessageMentionable{}).Return(&clientmodels.Message{
ID: "mockMessageID",
Expand Down Expand Up @@ -1486,7 +1486,7 @@ func TestSendChat(t *testing.T) {
SetupClient: func(client *clientmocks.Client, uclient *clientmocks.Client) {
uclient.On("CreateOrGetChatForUsers", mock.AnythingOfType("[]string")).Return(mockChat, nil).Times(1)
uclient.On("GetChat", testutils.GetChatID()).Return(mockChat, nil).Times(1)
uclient.On("SendChat", testutils.GetChatID(), "<p>mockMessage??????????</p>\n", (*clientmodels.Message)(nil), ([]*clientmodels.Attachment)(nil), []models.ChatMessageMentionable{}).Return(&clientmodels.Message{
uclient.On("SendChat", testutils.GetChatID(), "<p>mockMessage??????????</p>\n<abbr title=\"generated-from-mattermost\"></abbr>", (*clientmodels.Message)(nil), ([]*clientmodels.Attachment)(nil), []models.ChatMessageMentionable{}).Return(&clientmodels.Message{
ID: "mockMessageID",
}, nil).Times(1)
},
Expand Down Expand Up @@ -1520,7 +1520,7 @@ func TestSendChat(t *testing.T) {
SetupClient: func(client *clientmocks.Client, uclient *clientmocks.Client) {
uclient.On("CreateOrGetChatForUsers", mock.AnythingOfType("[]string")).Return(mockChat, nil).Times(1)
uclient.On("GetChat", testutils.GetChatID()).Return(mockChat, nil).Times(1)
uclient.On("SendChat", testutils.GetChatID(), "<p>mockMessage??????????</p>\n", (*clientmodels.Message)(nil), ([]*clientmodels.Attachment)(nil), []models.ChatMessageMentionable{}).Return(&clientmodels.Message{
uclient.On("SendChat", testutils.GetChatID(), "<p>mockMessage??????????</p>\n<abbr title=\"generated-from-mattermost\"></abbr>", (*clientmodels.Message)(nil), ([]*clientmodels.Attachment)(nil), []models.ChatMessageMentionable{}).Return(&clientmodels.Message{
ID: "mockMessageID",
}, nil).Times(1)
},
Expand Down Expand Up @@ -1556,7 +1556,7 @@ func TestSendChat(t *testing.T) {
SetupClient: func(client *clientmocks.Client, uclient *clientmocks.Client) {
uclient.On("CreateOrGetChatForUsers", mock.AnythingOfType("[]string")).Return(mockChat, nil).Times(1)
uclient.On("GetChat", testutils.GetChatID()).Return(mockChat, nil).Times(1)
uclient.On("SendChat", testutils.GetChatID(), "<p>mockMessage??????????</p>\n", (*clientmodels.Message)(nil), ([]*clientmodels.Attachment)(nil), []models.ChatMessageMentionable{}).Return(&clientmodels.Message{
uclient.On("SendChat", testutils.GetChatID(), "<p>mockMessage??????????</p>\n<abbr title=\"generated-from-mattermost\"></abbr>", (*clientmodels.Message)(nil), ([]*clientmodels.Attachment)(nil), []models.ChatMessageMentionable{}).Return(&clientmodels.Message{
ID: "mockMessageID",
}, nil).Times(1)
},
Expand Down Expand Up @@ -1601,7 +1601,7 @@ func TestSendChat(t *testing.T) {
Text: "mockText",
UserDisplayName: "mockUserDisplayName",
}, nil).Once()
uclient.On("SendChat", testutils.GetChatID(), "<p>mockMessage??????????</p>\n", &clientmodels.Message{
uclient.On("SendChat", testutils.GetChatID(), "<p>mockMessage??????????</p>\n<abbr title=\"generated-from-mattermost\"></abbr>", &clientmodels.Message{
ID: "mockParentMessageID",
UserID: "mockUserID",
Text: "mockText",
Expand Down Expand Up @@ -1647,7 +1647,7 @@ func TestSendChat(t *testing.T) {
uclient.On("UploadFile", "", "", "mockFile.Name"+"_"+testutils.GetID()+".txt", 1, "mockMimeType", bytes.NewReader([]byte("mockData")), mockChat).Return(&clientmodels.Attachment{
ID: testutils.GetID(),
}, nil).Times(1)
uclient.On("SendChat", testutils.GetChatID(), "<p>mockMessage??????????</p>\n", (*clientmodels.Message)(nil), []*clientmodels.Attachment{{
uclient.On("SendChat", testutils.GetChatID(), "<p>mockMessage??????????</p>\n<abbr title=\"generated-from-mattermost\"></abbr>", (*clientmodels.Message)(nil), []*clientmodels.Attachment{{
ID: testutils.GetID(),
}}, []models.ChatMessageMentionable{}).Return(&clientmodels.Message{
ID: "mockMessageID",
Expand Down Expand Up @@ -1738,7 +1738,7 @@ func TestSend(t *testing.T) {
}).Return(nil).Times(1)
},
SetupClient: func(client *clientmocks.Client, uclient *clientmocks.Client) {
uclient.On("SendMessageWithAttachments", testutils.GetID(), testutils.GetChannelID(), "", "<p>mockMessage??????????</p>\n", ([]*clientmodels.Attachment)(nil), []models.ChatMessageMentionable{}).Return(&clientmodels.Message{
uclient.On("SendMessageWithAttachments", testutils.GetID(), testutils.GetChannelID(), "", "<p>mockMessage??????????</p>\n<abbr title=\"generated-from-mattermost\"></abbr>", ([]*clientmodels.Attachment)(nil), []models.ChatMessageMentionable{}).Return(&clientmodels.Message{
ID: "mockMessageID",
}, nil).Times(1)
},
Expand Down Expand Up @@ -1766,7 +1766,7 @@ func TestSend(t *testing.T) {
}).Return(nil).Times(1)
},
SetupClient: func(client *clientmocks.Client, uclient *clientmocks.Client) {
uclient.On("SendMessageWithAttachments", testutils.GetID(), testutils.GetChannelID(), "", "<p>mockMessage??????????</p>\n", ([]*clientmodels.Attachment)(nil), []models.ChatMessageMentionable{}).Return(&clientmodels.Message{
uclient.On("SendMessageWithAttachments", testutils.GetID(), testutils.GetChannelID(), "", "<p>mockMessage??????????</p>\n<abbr title=\"generated-from-mattermost\"></abbr>", ([]*clientmodels.Attachment)(nil), []models.ChatMessageMentionable{}).Return(&clientmodels.Message{
ID: "mockMessageID",
}, nil).Times(1)
},
Expand Down Expand Up @@ -1796,7 +1796,7 @@ func TestSend(t *testing.T) {
}).Return(nil).Times(1)
},
SetupClient: func(client *clientmocks.Client, uclient *clientmocks.Client) {
uclient.On("SendMessageWithAttachments", testutils.GetID(), testutils.GetChannelID(), "", "<p>mockMessage??????????</p>\n", ([]*clientmodels.Attachment)(nil), []models.ChatMessageMentionable{}).Return(&clientmodels.Message{
uclient.On("SendMessageWithAttachments", testutils.GetID(), testutils.GetChannelID(), "", "<p>mockMessage??????????</p>\n<abbr title=\"generated-from-mattermost\"></abbr>", ([]*clientmodels.Attachment)(nil), []models.ChatMessageMentionable{}).Return(&clientmodels.Message{
ID: "mockMessageID",
}, nil).Times(1)
},
Expand Down Expand Up @@ -1824,7 +1824,7 @@ func TestSend(t *testing.T) {
uclient.On("UploadFile", testutils.GetID(), testutils.GetChannelID(), "mockFile.Name"+"_"+testutils.GetID()+".txt", 1, "mockMimeType", bytes.NewReader([]byte("mockData")), (*clientmodels.Chat)(nil)).Return(&clientmodels.Attachment{
ID: testutils.GetID(),
}, nil).Times(1)
uclient.On("SendMessageWithAttachments", testutils.GetID(), testutils.GetChannelID(), "", "<p>mockMessage??????????</p>\n", []*clientmodels.Attachment{
uclient.On("SendMessageWithAttachments", testutils.GetID(), testutils.GetChannelID(), "", "<p>mockMessage??????????</p>\n<abbr title=\"generated-from-mattermost\"></abbr>", []*clientmodels.Attachment{
{
ID: testutils.GetID(),
},
Expand Down Expand Up @@ -1860,7 +1860,7 @@ func TestSend(t *testing.T) {
uclient.On("UploadFile", testutils.GetID(), testutils.GetChannelID(), "mockFile.Name"+"_"+testutils.GetID()+".txt", 1, "mockMimeType", bytes.NewReader([]byte("mockData")), (*clientmodels.Chat)(nil)).Return(&clientmodels.Attachment{
ID: testutils.GetID(),
}, nil).Times(1)
uclient.On("SendMessageWithAttachments", testutils.GetID(), testutils.GetChannelID(), "", "<p>mockMessage??????????</p>\n", []*clientmodels.Attachment{
uclient.On("SendMessageWithAttachments", testutils.GetID(), testutils.GetChannelID(), "", "<p>mockMessage??????????</p>\n<abbr title=\"generated-from-mattermost\"></abbr>", []*clientmodels.Attachment{
{
ID: testutils.GetID(),
},
Expand Down Expand Up @@ -1898,7 +1898,7 @@ func TestSend(t *testing.T) {
uclient.On("UploadFile", testutils.GetID(), testutils.GetChannelID(), "mockFile.Name"+"_"+testutils.GetID()+".txt", 1, "mockMimeType", bytes.NewReader([]byte("mockData")), (*clientmodels.Chat)(nil)).Return(&clientmodels.Attachment{
ID: testutils.GetID(),
}, nil).Times(1)
uclient.On("SendMessageWithAttachments", testutils.GetID(), testutils.GetChannelID(), "", "<p>mockMessage??????????</p>\n", []*clientmodels.Attachment{
uclient.On("SendMessageWithAttachments", testutils.GetID(), testutils.GetChannelID(), "", "<p>mockMessage??????????</p>\n<abbr title=\"generated-from-mattermost\"></abbr>", []*clientmodels.Attachment{
{
ID: testutils.GetID(),
},
Expand Down
1 change: 1 addition & 0 deletions server/metrics/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ const (
DiscardedReasonFailedSubscriptionCheck = "failed_subscription_check"
DiscardedReasonFailedToRefresh = "failed_to_refresh"
DiscardedReasonSelectiveSync = "selective_sync"
DiscardedReasonGeneratedFromMattermost = "generated_from_mattermost"

WorkerMonitor = "monitor"
WorkerSyncUsers = "sync_users"
Expand Down
Loading