[MM-62427] Add message attachments validation (#30180)
* Add message attachments validation * Add props validation * Validate slack attachment fields * Update tests and library usage * Improve interactive dialog error for length checks * Allow predefined colors for slack attachments * Fix TestPostAction * Use const for data source * Add tests * Cleanup unused props * Add happy path tests * lint fixes * Add validation for PostActionOptions
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
5609489e86
Коммит
9b5d8d52bf
@@ -39,7 +39,7 @@ func (th *testHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
assert.NotEmpty(th.t, poir.TeamName)
|
||||
assert.NotEmpty(th.t, poir.PostId)
|
||||
assert.NotEmpty(th.t, poir.TriggerId)
|
||||
assert.Equal(th.t, "button", poir.Type)
|
||||
assert.Equal(th.t, model.PostActionTypeButton, poir.Type)
|
||||
assert.Equal(th.t, "test-value", poir.Context["test-key"])
|
||||
_, err = w.Write([]byte("{}"))
|
||||
require.NoError(th.t, err)
|
||||
@@ -118,7 +118,7 @@ func TestPostActionCookies(t *testing.T) {
|
||||
CreateAt: model.GetMillis(),
|
||||
UpdateAt: model.GetMillis(),
|
||||
Props: map[string]any{
|
||||
"attachments": []*model.SlackAttachment{
|
||||
model.PostPropsAttachments: []*model.SlackAttachment{
|
||||
{
|
||||
Title: "some-title",
|
||||
TitleLink: "https://some-url.com",
|
||||
|
||||
@@ -1420,7 +1420,7 @@ func TestUpdatePost(t *testing.T) {
|
||||
ChannelId: channel.Id,
|
||||
Message: "zz" + model.NewId() + " update post 3",
|
||||
}
|
||||
up4.AddProp("attachments", []model.SlackAttachment{
|
||||
up4.AddProp(model.PostPropsAttachments, []model.SlackAttachment{
|
||||
{
|
||||
Text: "Hello World",
|
||||
},
|
||||
@@ -1772,12 +1772,12 @@ func TestPatchPost(t *testing.T) {
|
||||
Text: "Hello World",
|
||||
},
|
||||
}
|
||||
patch2.Props = &model.StringInterface{"attachments": attachments}
|
||||
patch2.Props = &model.StringInterface{model.PostPropsAttachments: attachments}
|
||||
|
||||
var rpost2 *model.Post
|
||||
rpost2, _, err = client.PatchPost(context.Background(), post.Id, patch2)
|
||||
require.NoError(t, err)
|
||||
assert.NotEmpty(t, rpost2.GetProp("attachments"))
|
||||
assert.NotEmpty(t, rpost2.GetProp(model.PostPropsAttachments))
|
||||
assert.NotEqual(t, rpost.EditAt, rpost2.EditAt)
|
||||
})
|
||||
|
||||
@@ -4682,7 +4682,7 @@ func TestGetPostStripActionIntegrations(t *testing.T) {
|
||||
ChannelId: th.BasicChannel.Id,
|
||||
Message: "with slack attachment action",
|
||||
}
|
||||
post.AddProp("attachments", []*model.SlackAttachment{
|
||||
post.AddProp(model.PostPropsAttachments, []*model.SlackAttachment{
|
||||
{
|
||||
Text: "Slack Attachment Text",
|
||||
Fields: []*model.SlackAttachmentField{
|
||||
@@ -4694,7 +4694,7 @@ func TestGetPostStripActionIntegrations(t *testing.T) {
|
||||
},
|
||||
Actions: []*model.PostAction{
|
||||
{
|
||||
Type: "button",
|
||||
Type: model.PostActionTypeButton,
|
||||
Name: "test-name",
|
||||
Integration: &model.PostActionIntegration{
|
||||
URL: "https://test.test/action",
|
||||
@@ -4713,7 +4713,7 @@ func TestGetPostStripActionIntegrations(t *testing.T) {
|
||||
|
||||
actualPost, _, err := client.GetPost(context.Background(), rpost.Id, "")
|
||||
require.NoError(t, err)
|
||||
attachments, _ := actualPost.Props["attachments"].([]any)
|
||||
attachments, _ := actualPost.Props[model.PostPropsAttachments].([]any)
|
||||
require.Equal(t, 1, len(attachments))
|
||||
att, _ := attachments[0].(map[string]any)
|
||||
require.NotNil(t, att)
|
||||
|
||||
@@ -618,28 +618,28 @@ func (a *App) HandleCommandResponsePost(c request.CTX, command *model.Command, a
|
||||
|
||||
if *a.Config().ServiceSettings.EnablePostUsernameOverride {
|
||||
if command.Username != "" {
|
||||
post.AddProp("override_username", command.Username)
|
||||
post.AddProp(model.PostPropsOverrideUsername, command.Username)
|
||||
isBotPost = true
|
||||
} else if response.Username != "" {
|
||||
post.AddProp("override_username", response.Username)
|
||||
post.AddProp(model.PostPropsOverrideUsername, response.Username)
|
||||
isBotPost = true
|
||||
}
|
||||
}
|
||||
|
||||
if *a.Config().ServiceSettings.EnablePostIconOverride {
|
||||
if command.IconURL != "" {
|
||||
post.AddProp("override_icon_url", command.IconURL)
|
||||
post.AddProp(model.PostPropsOverrideIconURL, command.IconURL)
|
||||
isBotPost = true
|
||||
} else if response.IconURL != "" {
|
||||
post.AddProp("override_icon_url", response.IconURL)
|
||||
post.AddProp(model.PostPropsOverrideIconURL, response.IconURL)
|
||||
isBotPost = true
|
||||
} else {
|
||||
post.AddProp("override_icon_url", "")
|
||||
post.AddProp(model.PostPropsOverrideIconURL, "")
|
||||
}
|
||||
}
|
||||
|
||||
if isBotPost {
|
||||
post.AddProp("from_webhook", "true")
|
||||
post.AddProp(model.PostPropsFromWebhook, "true")
|
||||
}
|
||||
|
||||
// Process Slack text replacements if the response does not contain "skip_slack_parsing": true.
|
||||
|
||||
@@ -623,7 +623,7 @@ func TestExportPostWithProps(t *testing.T) {
|
||||
ChannelId: dmChannel.Id,
|
||||
Message: "aa" + model.NewId() + "a",
|
||||
Props: map[string]any{
|
||||
"attachments": attachments,
|
||||
model.PostPropsAttachments: attachments,
|
||||
},
|
||||
UserId: th1.BasicUser.Id,
|
||||
}
|
||||
@@ -634,7 +634,7 @@ func TestExportPostWithProps(t *testing.T) {
|
||||
ChannelId: gmChannel.Id,
|
||||
Message: "dd" + model.NewId() + "a",
|
||||
Props: map[string]any{
|
||||
"attachments": attachments,
|
||||
model.PostPropsAttachments: attachments,
|
||||
},
|
||||
UserId: th1.BasicUser.Id,
|
||||
}
|
||||
@@ -673,8 +673,8 @@ func TestExportPostWithProps(t *testing.T) {
|
||||
assert.Len(t, posts, 2)
|
||||
assert.ElementsMatch(t, gmMembers, *posts[0].ChannelMembers)
|
||||
assert.ElementsMatch(t, dmMembers, *posts[1].ChannelMembers)
|
||||
assert.Contains(t, posts[0].Props["attachments"].([]any)[0], "footer")
|
||||
assert.Contains(t, posts[1].Props["attachments"].([]any)[0], "footer")
|
||||
assert.Contains(t, posts[0].Props[model.PostPropsAttachments].([]any)[0], "footer")
|
||||
assert.Contains(t, posts[1].Props[model.PostPropsAttachments].([]any)[0], "footer")
|
||||
}
|
||||
|
||||
func TestExportUserCustomStatus(t *testing.T) {
|
||||
|
||||
@@ -1455,7 +1455,7 @@ func (a *App) importReplies(rctx request.CTX, data []imports.ReplyImportData, po
|
||||
}
|
||||
|
||||
if len(postsForCreateList) > 0 {
|
||||
postsCreated, _, err := a.Srv().Store().Post().SaveMultiple(postsForCreateList)
|
||||
postsCreated, _, err := a.Srv().Store().Post().SaveMultiple(rctx, postsForCreateList)
|
||||
if err != nil {
|
||||
var appErr *model.AppError
|
||||
var invErr *store.ErrInvalidInput
|
||||
@@ -1478,7 +1478,7 @@ func (a *App) importReplies(rctx request.CTX, data []imports.ReplyImportData, po
|
||||
}
|
||||
}
|
||||
|
||||
if _, _, nErr := a.Srv().Store().Post().OverwriteMultiple(postsForOverwriteList); nErr != nil {
|
||||
if _, _, nErr := a.Srv().Store().Post().OverwriteMultiple(rctx, postsForOverwriteList); nErr != nil {
|
||||
return model.NewAppError("importReplies", "app.post.overwrite.app_error", nil, "", http.StatusInternalServerError).Wrap(nErr)
|
||||
}
|
||||
|
||||
@@ -1892,7 +1892,7 @@ func (a *App) importMultiplePostLines(rctx request.CTX, lines []imports.LineImpo
|
||||
}
|
||||
|
||||
if len(postsForCreateList) > 0 {
|
||||
_, idx, nErr := a.Srv().Store().Post().SaveMultiple(postsForCreateList)
|
||||
_, idx, nErr := a.Srv().Store().Post().SaveMultiple(rctx, postsForCreateList)
|
||||
if nErr != nil {
|
||||
var appErr *model.AppError
|
||||
var invErr *store.ErrInvalidInput
|
||||
@@ -1946,7 +1946,7 @@ func (a *App) importMultiplePostLines(rctx request.CTX, lines []imports.LineImpo
|
||||
}
|
||||
}
|
||||
|
||||
if _, idx, err := a.Srv().Store().Post().OverwriteMultiple(postsForOverwriteList); err != nil {
|
||||
if _, idx, err := a.Srv().Store().Post().OverwriteMultiple(rctx, postsForOverwriteList); err != nil {
|
||||
if idx != -1 && idx < len(postsForOverwriteList) {
|
||||
post := postsForOverwriteList[idx]
|
||||
if lineNumber, ok := postsForOverwriteMap[getPostStrID(post)]; ok {
|
||||
@@ -2411,7 +2411,7 @@ func (a *App) importMultipleDirectPostLines(rctx request.CTX, lines []imports.Li
|
||||
}
|
||||
|
||||
if len(postsForCreateList) > 0 {
|
||||
if _, idx, err := a.Srv().Store().Post().SaveMultiple(postsForCreateList); err != nil {
|
||||
if _, idx, err := a.Srv().Store().Post().SaveMultiple(rctx, postsForCreateList); err != nil {
|
||||
var appErr *model.AppError
|
||||
var invErr *store.ErrInvalidInput
|
||||
var retErr *model.AppError
|
||||
@@ -2459,7 +2459,7 @@ func (a *App) importMultipleDirectPostLines(rctx request.CTX, lines []imports.Li
|
||||
}
|
||||
}
|
||||
|
||||
if _, idx, err := a.Srv().Store().Post().OverwriteMultiple(postsForOverwriteList); err != nil {
|
||||
if _, idx, err := a.Srv().Store().Post().OverwriteMultiple(rctx, postsForOverwriteList); err != nil {
|
||||
if idx != -1 && idx < len(postsForOverwriteList) {
|
||||
post := postsForOverwriteList[idx]
|
||||
if lineNumber, ok := postsForOverwriteMap[getPostStrID(post)]; ok {
|
||||
|
||||
@@ -42,16 +42,16 @@ func TestPostActionInvalidURL(t *testing.T) {
|
||||
PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()),
|
||||
UserId: th.BasicUser.Id,
|
||||
Props: model.StringInterface{
|
||||
"attachments": []*model.SlackAttachment{
|
||||
model.PostPropsAttachments: []*model.SlackAttachment{
|
||||
{
|
||||
Text: "hello",
|
||||
Actions: []*model.PostAction{
|
||||
{
|
||||
Type: model.PostActionTypeButton,
|
||||
Name: "action",
|
||||
Integration: &model.PostActionIntegration{
|
||||
URL: ":test",
|
||||
},
|
||||
Name: "action",
|
||||
Type: "some_type",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -61,7 +61,7 @@ func TestPostActionInvalidURL(t *testing.T) {
|
||||
|
||||
post, err := th.App.CreatePostAsUser(th.Context, &interactivePost, "", true)
|
||||
require.Nil(t, err)
|
||||
attachments, ok := post.GetProp("attachments").([]*model.SlackAttachment)
|
||||
attachments, ok := post.GetProp(model.PostPropsAttachments).([]*model.SlackAttachment)
|
||||
require.True(t, ok)
|
||||
require.NotEmpty(t, attachments[0].Actions)
|
||||
require.NotEmpty(t, attachments[0].Actions[0].Id)
|
||||
@@ -90,11 +90,14 @@ func TestPostActionEmptyResponse(t *testing.T) {
|
||||
PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()),
|
||||
UserId: th.BasicUser.Id,
|
||||
Props: model.StringInterface{
|
||||
"attachments": []*model.SlackAttachment{
|
||||
model.PostPropsAttachments: []*model.SlackAttachment{
|
||||
{
|
||||
Text: "hello",
|
||||
Actions: []*model.PostAction{
|
||||
{
|
||||
Type: model.PostActionTypeSelect,
|
||||
Name: "action",
|
||||
DataSource: model.PostActionDataSourceUsers,
|
||||
Integration: &model.PostActionIntegration{
|
||||
Context: model.StringInterface{
|
||||
"s": "foo",
|
||||
@@ -102,9 +105,6 @@ func TestPostActionEmptyResponse(t *testing.T) {
|
||||
},
|
||||
URL: ts.URL,
|
||||
},
|
||||
Name: "action",
|
||||
Type: "some_type",
|
||||
DataSource: "some_source",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -115,7 +115,7 @@ func TestPostActionEmptyResponse(t *testing.T) {
|
||||
post, err := th.App.CreatePostAsUser(th.Context, &interactivePost, "", true)
|
||||
require.Nil(t, err)
|
||||
|
||||
attachments, ok := post.GetProp("attachments").([]*model.SlackAttachment)
|
||||
attachments, ok := post.GetProp(model.PostPropsAttachments).([]*model.SlackAttachment)
|
||||
require.True(t, ok)
|
||||
|
||||
_, err = th.App.DoPostActionWithCookie(th.Context, post.Id, attachments[0].Actions[0].Id, th.BasicUser.Id, "", nil)
|
||||
@@ -134,11 +134,14 @@ func TestPostActionEmptyResponse(t *testing.T) {
|
||||
PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()),
|
||||
UserId: th.BasicUser.Id,
|
||||
Props: model.StringInterface{
|
||||
"attachments": []*model.SlackAttachment{
|
||||
model.PostPropsAttachments: []*model.SlackAttachment{
|
||||
{
|
||||
Text: "hello",
|
||||
Actions: []*model.PostAction{
|
||||
{
|
||||
Type: model.PostActionTypeSelect,
|
||||
Name: "action",
|
||||
DataSource: model.PostActionDataSourceUsers,
|
||||
Integration: &model.PostActionIntegration{
|
||||
Context: model.StringInterface{
|
||||
"s": "foo",
|
||||
@@ -146,9 +149,6 @@ func TestPostActionEmptyResponse(t *testing.T) {
|
||||
},
|
||||
URL: ts.URL,
|
||||
},
|
||||
Name: "action",
|
||||
Type: "some_type",
|
||||
DataSource: "some_source",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -159,7 +159,7 @@ func TestPostActionEmptyResponse(t *testing.T) {
|
||||
post, err := th.App.CreatePostAsUser(th.Context, &interactivePost, "", true)
|
||||
require.Nil(t, err)
|
||||
|
||||
attachments, ok := post.GetProp("attachments").([]*model.SlackAttachment)
|
||||
attachments, ok := post.GetProp(model.PostPropsAttachments).([]*model.SlackAttachment)
|
||||
require.True(t, ok)
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
@@ -209,23 +209,28 @@ func TestPostAction(t *testing.T) {
|
||||
jsonErr := json.NewDecoder(r.Body).Decode(&request)
|
||||
assert.NoError(t, jsonErr)
|
||||
|
||||
assert.Equal(t, request.UserId, th.BasicUser.Id)
|
||||
assert.Equal(t, request.UserName, th.BasicUser.Username)
|
||||
assert.Equal(t, request.ChannelId, channel.Id)
|
||||
assert.Equal(t, request.ChannelName, channel.Name)
|
||||
assert.Equal(t, th.BasicUser.Id, request.UserId)
|
||||
assert.Equal(t, th.BasicUser.Username, request.UserName)
|
||||
assert.Equal(t, channel.Id, request.ChannelId)
|
||||
assert.Equal(t, channel.Name, request.ChannelName)
|
||||
if channel.Type == model.ChannelTypeDirect || channel.Type == model.ChannelTypeGroup {
|
||||
assert.Empty(t, request.TeamId)
|
||||
assert.Empty(t, request.TeamName)
|
||||
} else {
|
||||
assert.Equal(t, request.TeamId, th.BasicTeam.Id)
|
||||
assert.Equal(t, request.TeamName, th.BasicTeam.Name)
|
||||
assert.Equal(t, th.BasicTeam.Id, request.TeamId)
|
||||
assert.Equal(t, th.BasicTeam.Name, request.TeamName)
|
||||
}
|
||||
assert.True(t, request.TriggerId != "")
|
||||
if request.Type == model.PostActionTypeSelect {
|
||||
assert.Equal(t, request.DataSource, "some_source")
|
||||
assert.Equal(t, request.Context["selected_option"], "selected")
|
||||
if selectedOption, ok := request.Context["selected_option"]; ok {
|
||||
// If something was selected, confirm that the data source and selected option are present
|
||||
assert.Equal(t, model.PostActionDataSourceUsers, request.DataSource)
|
||||
assert.Equal(t, "selected", selectedOption)
|
||||
} else {
|
||||
assert.Empty(t, request.DataSource)
|
||||
}
|
||||
} else {
|
||||
assert.Equal(t, request.DataSource, "")
|
||||
assert.Equal(t, "", request.DataSource)
|
||||
}
|
||||
assert.Equal(t, "foo", request.Context["s"])
|
||||
assert.EqualValues(t, 3, request.Context["n"])
|
||||
@@ -239,11 +244,14 @@ func TestPostAction(t *testing.T) {
|
||||
PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()),
|
||||
UserId: th.BasicUser.Id,
|
||||
Props: model.StringInterface{
|
||||
"attachments": []*model.SlackAttachment{
|
||||
model.PostPropsAttachments: []*model.SlackAttachment{
|
||||
{
|
||||
Text: "hello",
|
||||
Actions: []*model.PostAction{
|
||||
{
|
||||
Type: model.PostActionTypeSelect,
|
||||
Name: "action",
|
||||
DataSource: model.PostActionDataSourceUsers,
|
||||
Integration: &model.PostActionIntegration{
|
||||
Context: model.StringInterface{
|
||||
"s": "foo",
|
||||
@@ -251,9 +259,6 @@ func TestPostAction(t *testing.T) {
|
||||
},
|
||||
URL: ts.URL,
|
||||
},
|
||||
Name: "action",
|
||||
Type: "some_type",
|
||||
DataSource: "some_source",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -264,7 +269,7 @@ func TestPostAction(t *testing.T) {
|
||||
post, err := th.App.CreatePostAsUser(th.Context, &interactivePost, "", true)
|
||||
require.Nil(t, err)
|
||||
|
||||
attachments, ok := post.GetProp("attachments").([]*model.SlackAttachment)
|
||||
attachments, ok := post.GetProp(model.PostPropsAttachments).([]*model.SlackAttachment)
|
||||
require.True(t, ok)
|
||||
|
||||
require.NotEmpty(t, attachments[0].Actions)
|
||||
@@ -276,11 +281,14 @@ func TestPostAction(t *testing.T) {
|
||||
PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()),
|
||||
UserId: th.BasicUser.Id,
|
||||
Props: model.StringInterface{
|
||||
"attachments": []*model.SlackAttachment{
|
||||
model.PostPropsAttachments: []*model.SlackAttachment{
|
||||
{
|
||||
Text: "hello",
|
||||
Actions: []*model.PostAction{
|
||||
{
|
||||
Type: model.PostActionTypeSelect,
|
||||
Name: "action",
|
||||
DataSource: model.PostActionDataSourceUsers,
|
||||
Integration: &model.PostActionIntegration{
|
||||
Context: model.StringInterface{
|
||||
"s": "foo",
|
||||
@@ -288,9 +296,6 @@ func TestPostAction(t *testing.T) {
|
||||
},
|
||||
URL: ts.URL,
|
||||
},
|
||||
Name: "action",
|
||||
Type: model.PostActionTypeSelect,
|
||||
DataSource: "some_source",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -301,7 +306,7 @@ func TestPostAction(t *testing.T) {
|
||||
post2, err := th.App.CreatePostAsUser(th.Context, &menuPost, "", true)
|
||||
require.Nil(t, err)
|
||||
|
||||
attachments2, ok := post2.GetProp("attachments").([]*model.SlackAttachment)
|
||||
attachments2, ok := post2.GetProp(model.PostPropsAttachments).([]*model.SlackAttachment)
|
||||
require.True(t, ok)
|
||||
|
||||
require.NotEmpty(t, attachments2[0].Actions)
|
||||
@@ -310,15 +315,15 @@ func TestPostAction(t *testing.T) {
|
||||
clientTriggerID, err := th.App.DoPostActionWithCookie(th.Context, post.Id, "notavalidid", th.BasicUser.Id, "", nil)
|
||||
require.NotNil(t, err)
|
||||
assert.Equal(t, http.StatusNotFound, err.StatusCode)
|
||||
assert.True(t, clientTriggerID == "")
|
||||
assert.Len(t, clientTriggerID, 0)
|
||||
|
||||
clientTriggerID, err = th.App.DoPostActionWithCookie(th.Context, post.Id, attachments[0].Actions[0].Id, th.BasicUser.Id, "", nil)
|
||||
require.Nil(t, err)
|
||||
assert.True(t, len(clientTriggerID) == 26)
|
||||
assert.Len(t, clientTriggerID, 26)
|
||||
|
||||
clientTriggerID, err = th.App.DoPostActionWithCookie(th.Context, post2.Id, attachments2[0].Actions[0].Id, th.BasicUser.Id, "selected", nil)
|
||||
require.Nil(t, err)
|
||||
assert.True(t, len(clientTriggerID) == 26)
|
||||
assert.Len(t, clientTriggerID, 26)
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.ServiceSettings.AllowedUntrustedInternalConnections = ""
|
||||
@@ -334,11 +339,14 @@ func TestPostAction(t *testing.T) {
|
||||
PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()),
|
||||
UserId: th.BasicUser.Id,
|
||||
Props: model.StringInterface{
|
||||
"attachments": []*model.SlackAttachment{
|
||||
model.PostPropsAttachments: []*model.SlackAttachment{
|
||||
{
|
||||
Text: "hello",
|
||||
Actions: []*model.PostAction{
|
||||
{
|
||||
Type: model.PostActionTypeSelect,
|
||||
Name: "action",
|
||||
DataSource: model.PostActionDataSourceUsers,
|
||||
Integration: &model.PostActionIntegration{
|
||||
Context: model.StringInterface{
|
||||
"s": "foo",
|
||||
@@ -346,9 +354,6 @@ func TestPostAction(t *testing.T) {
|
||||
},
|
||||
URL: ts.URL + "/plugins/myplugin/myaction",
|
||||
},
|
||||
Name: "action",
|
||||
Type: "some_type",
|
||||
DataSource: "some_source",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -359,7 +364,7 @@ func TestPostAction(t *testing.T) {
|
||||
postplugin, err := th.App.CreatePostAsUser(th.Context, &interactivePostPlugin, "", true)
|
||||
require.Nil(t, err)
|
||||
|
||||
attachmentsPlugin, ok := postplugin.GetProp("attachments").([]*model.SlackAttachment)
|
||||
attachmentsPlugin, ok := postplugin.GetProp(model.PostPropsAttachments).([]*model.SlackAttachment)
|
||||
require.True(t, ok)
|
||||
|
||||
_, err = th.App.DoPostActionWithCookie(th.Context, postplugin.Id, attachmentsPlugin[0].Actions[0].Id, th.BasicUser.Id, "", nil)
|
||||
@@ -382,11 +387,14 @@ func TestPostAction(t *testing.T) {
|
||||
PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()),
|
||||
UserId: th.BasicUser.Id,
|
||||
Props: model.StringInterface{
|
||||
"attachments": []*model.SlackAttachment{
|
||||
model.PostPropsAttachments: []*model.SlackAttachment{
|
||||
{
|
||||
Text: "hello",
|
||||
Actions: []*model.PostAction{
|
||||
{
|
||||
Type: model.PostActionTypeSelect,
|
||||
Name: "action",
|
||||
DataSource: model.PostActionDataSourceUsers,
|
||||
Integration: &model.PostActionIntegration{
|
||||
Context: model.StringInterface{
|
||||
"s": "foo",
|
||||
@@ -394,9 +402,6 @@ func TestPostAction(t *testing.T) {
|
||||
},
|
||||
URL: "http://127.1.1.1/plugins/myplugin/myaction",
|
||||
},
|
||||
Name: "action",
|
||||
Type: "some_type",
|
||||
DataSource: "some_source",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -407,7 +412,7 @@ func TestPostAction(t *testing.T) {
|
||||
postSiteURL, err := th.App.CreatePostAsUser(th.Context, &interactivePostSiteURL, "", true)
|
||||
require.Nil(t, err)
|
||||
|
||||
attachmentsSiteURL, ok := postSiteURL.GetProp("attachments").([]*model.SlackAttachment)
|
||||
attachmentsSiteURL, ok := postSiteURL.GetProp(model.PostPropsAttachments).([]*model.SlackAttachment)
|
||||
require.True(t, ok)
|
||||
|
||||
_, err = th.App.DoPostActionWithCookie(th.Context, postSiteURL.Id, attachmentsSiteURL[0].Actions[0].Id, th.BasicUser.Id, "", nil)
|
||||
@@ -424,11 +429,14 @@ func TestPostAction(t *testing.T) {
|
||||
PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()),
|
||||
UserId: th.BasicUser.Id,
|
||||
Props: model.StringInterface{
|
||||
"attachments": []*model.SlackAttachment{
|
||||
model.PostPropsAttachments: []*model.SlackAttachment{
|
||||
{
|
||||
Text: "hello",
|
||||
Actions: []*model.PostAction{
|
||||
{
|
||||
Type: model.PostActionTypeSelect,
|
||||
Name: "action",
|
||||
DataSource: model.PostActionDataSourceUsers,
|
||||
Integration: &model.PostActionIntegration{
|
||||
Context: model.StringInterface{
|
||||
"s": "foo",
|
||||
@@ -436,9 +444,6 @@ func TestPostAction(t *testing.T) {
|
||||
},
|
||||
URL: ts.URL + "/subpath/plugins/myplugin/myaction",
|
||||
},
|
||||
Name: "action",
|
||||
Type: "some_type",
|
||||
DataSource: "some_source",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -449,7 +454,7 @@ func TestPostAction(t *testing.T) {
|
||||
postSubpath, err := th.App.CreatePostAsUser(th.Context, &interactivePostSubpath, "", true)
|
||||
require.Nil(t, err)
|
||||
|
||||
attachmentsSubpath, ok := postSubpath.GetProp("attachments").([]*model.SlackAttachment)
|
||||
attachmentsSubpath, ok := postSubpath.GetProp(model.PostPropsAttachments).([]*model.SlackAttachment)
|
||||
require.True(t, ok)
|
||||
|
||||
_, err = th.App.DoPostActionWithCookie(th.Context, postSubpath.Id, attachmentsSubpath[0].Actions[0].Id, th.BasicUser.Id, "", nil)
|
||||
@@ -477,7 +482,7 @@ func TestPostActionProps(t *testing.T) {
|
||||
"has_reactions": true,
|
||||
"is_pinned": false,
|
||||
"props": {
|
||||
"from_webhook":true,
|
||||
"from_webhook":"true",
|
||||
"override_username":"new_override_user",
|
||||
"override_icon_url":"new_override_icon",
|
||||
"A":"AA"
|
||||
@@ -496,11 +501,14 @@ func TestPostActionProps(t *testing.T) {
|
||||
HasReactions: false,
|
||||
IsPinned: true,
|
||||
Props: model.StringInterface{
|
||||
"attachments": []*model.SlackAttachment{
|
||||
model.PostPropsAttachments: []*model.SlackAttachment{
|
||||
{
|
||||
Text: "hello",
|
||||
Actions: []*model.PostAction{
|
||||
{
|
||||
Type: model.PostActionTypeSelect,
|
||||
Name: "action",
|
||||
DataSource: model.PostActionDataSourceUsers,
|
||||
Integration: &model.PostActionIntegration{
|
||||
Context: model.StringInterface{
|
||||
"s": "foo",
|
||||
@@ -508,27 +516,24 @@ func TestPostActionProps(t *testing.T) {
|
||||
},
|
||||
URL: ts.URL,
|
||||
},
|
||||
Name: "action",
|
||||
Type: "some_type",
|
||||
DataSource: "some_source",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"override_icon_url": "old_override_icon",
|
||||
"from_webhook": false,
|
||||
"B": "BB",
|
||||
model.PostPropsOverrideIconURL: "old_override_icon",
|
||||
model.PostPropsFromWebhook: "false",
|
||||
"B": "BB",
|
||||
},
|
||||
}
|
||||
|
||||
post, err := th.App.CreatePostAsUser(th.Context, &interactivePost, "", true)
|
||||
require.Nil(t, err)
|
||||
attachments, ok := post.GetProp("attachments").([]*model.SlackAttachment)
|
||||
attachments, ok := post.GetProp(model.PostPropsAttachments).([]*model.SlackAttachment)
|
||||
require.True(t, ok)
|
||||
|
||||
clientTriggerId, err := th.App.DoPostActionWithCookie(th.Context, post.Id, attachments[0].Actions[0].Id, th.BasicUser.Id, "", nil)
|
||||
require.Nil(t, err)
|
||||
assert.True(t, len(clientTriggerId) == 26)
|
||||
assert.Len(t, clientTriggerId, 26)
|
||||
|
||||
newPost, nErr := th.App.Srv().Store().Post().GetSingle(th.Context, post.Id, false)
|
||||
require.NoError(t, nErr)
|
||||
@@ -536,10 +541,10 @@ func TestPostActionProps(t *testing.T) {
|
||||
assert.True(t, newPost.IsPinned)
|
||||
assert.False(t, newPost.HasReactions)
|
||||
assert.Nil(t, newPost.GetProp("B"))
|
||||
assert.Nil(t, newPost.GetProp("override_username"))
|
||||
assert.Nil(t, newPost.GetProp(model.PostPropsOverrideUsername))
|
||||
assert.Equal(t, "AA", newPost.GetProp("A"))
|
||||
assert.Equal(t, "old_override_icon", newPost.GetProp("override_icon_url"))
|
||||
assert.Equal(t, false, newPost.GetProp("from_webhook"))
|
||||
assert.Equal(t, "old_override_icon", newPost.GetProp(model.PostPropsOverrideIconURL))
|
||||
assert.Equal(t, "false", newPost.GetProp(model.PostPropsFromWebhook))
|
||||
}
|
||||
|
||||
func TestSubmitInteractiveDialog(t *testing.T) {
|
||||
@@ -688,16 +693,16 @@ func TestPostActionRelativeURL(t *testing.T) {
|
||||
PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()),
|
||||
UserId: th.BasicUser.Id,
|
||||
Props: model.StringInterface{
|
||||
"attachments": []*model.SlackAttachment{
|
||||
model.PostPropsAttachments: []*model.SlackAttachment{
|
||||
{
|
||||
Text: "hello",
|
||||
Actions: []*model.PostAction{
|
||||
{
|
||||
Type: model.PostActionTypeButton,
|
||||
Name: "action",
|
||||
Integration: &model.PostActionIntegration{
|
||||
URL: "/notaplugin/some/path",
|
||||
},
|
||||
Name: "action",
|
||||
Type: "some_type",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -707,7 +712,7 @@ func TestPostActionRelativeURL(t *testing.T) {
|
||||
|
||||
post, err := th.App.CreatePostAsUser(th.Context, &interactivePost, "", true)
|
||||
require.Nil(t, err)
|
||||
attachments, ok := post.GetProp("attachments").([]*model.SlackAttachment)
|
||||
attachments, ok := post.GetProp(model.PostPropsAttachments).([]*model.SlackAttachment)
|
||||
require.True(t, ok)
|
||||
require.NotEmpty(t, attachments[0].Actions)
|
||||
require.NotEmpty(t, attachments[0].Actions[0].Id)
|
||||
@@ -728,16 +733,16 @@ func TestPostActionRelativeURL(t *testing.T) {
|
||||
PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()),
|
||||
UserId: th.BasicUser.Id,
|
||||
Props: model.StringInterface{
|
||||
"attachments": []*model.SlackAttachment{
|
||||
model.PostPropsAttachments: []*model.SlackAttachment{
|
||||
{
|
||||
Text: "hello",
|
||||
Actions: []*model.PostAction{
|
||||
{
|
||||
Type: model.PostActionTypeButton,
|
||||
Name: "action",
|
||||
Integration: &model.PostActionIntegration{
|
||||
URL: "/plugins/myplugin/myaction",
|
||||
},
|
||||
Name: "action",
|
||||
Type: "some_type",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -747,7 +752,7 @@ func TestPostActionRelativeURL(t *testing.T) {
|
||||
|
||||
post, err := th.App.CreatePostAsUser(th.Context, &interactivePost, "", true)
|
||||
require.Nil(t, err)
|
||||
attachments, ok := post.GetProp("attachments").([]*model.SlackAttachment)
|
||||
attachments, ok := post.GetProp(model.PostPropsAttachments).([]*model.SlackAttachment)
|
||||
require.True(t, ok)
|
||||
require.NotEmpty(t, attachments[0].Actions)
|
||||
require.NotEmpty(t, attachments[0].Actions[0].Id)
|
||||
@@ -768,16 +773,16 @@ func TestPostActionRelativeURL(t *testing.T) {
|
||||
PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()),
|
||||
UserId: th.BasicUser.Id,
|
||||
Props: model.StringInterface{
|
||||
"attachments": []*model.SlackAttachment{
|
||||
model.PostPropsAttachments: []*model.SlackAttachment{
|
||||
{
|
||||
Text: "hello",
|
||||
Actions: []*model.PostAction{
|
||||
{
|
||||
Type: model.PostActionTypeButton,
|
||||
Name: "action",
|
||||
Integration: &model.PostActionIntegration{
|
||||
URL: "/plugins/myplugin/myaction",
|
||||
},
|
||||
Name: "action",
|
||||
Type: "some_type",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -787,7 +792,7 @@ func TestPostActionRelativeURL(t *testing.T) {
|
||||
|
||||
post, err := th.App.CreatePostAsUser(th.Context, &interactivePost, "", true)
|
||||
require.Nil(t, err)
|
||||
attachments, ok := post.GetProp("attachments").([]*model.SlackAttachment)
|
||||
attachments, ok := post.GetProp(model.PostPropsAttachments).([]*model.SlackAttachment)
|
||||
require.True(t, ok)
|
||||
require.NotEmpty(t, attachments[0].Actions)
|
||||
require.NotEmpty(t, attachments[0].Actions[0].Id)
|
||||
@@ -808,16 +813,16 @@ func TestPostActionRelativeURL(t *testing.T) {
|
||||
PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()),
|
||||
UserId: th.BasicUser.Id,
|
||||
Props: model.StringInterface{
|
||||
"attachments": []*model.SlackAttachment{
|
||||
model.PostPropsAttachments: []*model.SlackAttachment{
|
||||
{
|
||||
Text: "hello",
|
||||
Actions: []*model.PostAction{
|
||||
{
|
||||
Type: model.PostActionTypeButton,
|
||||
Name: "action",
|
||||
Integration: &model.PostActionIntegration{
|
||||
URL: "//plugins/myplugin///myaction",
|
||||
},
|
||||
Name: "action",
|
||||
Type: "some_type",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -827,7 +832,7 @@ func TestPostActionRelativeURL(t *testing.T) {
|
||||
|
||||
post, err := th.App.CreatePostAsUser(th.Context, &interactivePost, "", true)
|
||||
require.Nil(t, err)
|
||||
attachments, ok := post.GetProp("attachments").([]*model.SlackAttachment)
|
||||
attachments, ok := post.GetProp(model.PostPropsAttachments).([]*model.SlackAttachment)
|
||||
require.True(t, ok)
|
||||
require.NotEmpty(t, attachments[0].Actions)
|
||||
require.NotEmpty(t, attachments[0].Actions[0].Id)
|
||||
@@ -848,16 +853,16 @@ func TestPostActionRelativeURL(t *testing.T) {
|
||||
PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()),
|
||||
UserId: th.BasicUser.Id,
|
||||
Props: model.StringInterface{
|
||||
"attachments": []*model.SlackAttachment{
|
||||
model.PostPropsAttachments: []*model.SlackAttachment{
|
||||
{
|
||||
Text: "hello",
|
||||
Actions: []*model.PostAction{
|
||||
{
|
||||
Type: model.PostActionTypeButton,
|
||||
Name: "action",
|
||||
Integration: &model.PostActionIntegration{
|
||||
URL: "plugins/myplugin/myaction",
|
||||
},
|
||||
Name: "action",
|
||||
Type: "some_type",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -867,7 +872,7 @@ func TestPostActionRelativeURL(t *testing.T) {
|
||||
|
||||
post, err := th.App.CreatePostAsUser(th.Context, &interactivePost, "", true)
|
||||
require.Nil(t, err)
|
||||
attachments, ok := post.GetProp("attachments").([]*model.SlackAttachment)
|
||||
attachments, ok := post.GetProp(model.PostPropsAttachments).([]*model.SlackAttachment)
|
||||
require.True(t, ok)
|
||||
require.NotEmpty(t, attachments[0].Actions)
|
||||
require.NotEmpty(t, attachments[0].Actions[0].Id)
|
||||
@@ -925,16 +930,16 @@ func TestPostActionRelativePluginURL(t *testing.T) {
|
||||
PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()),
|
||||
UserId: th.BasicUser.Id,
|
||||
Props: model.StringInterface{
|
||||
"attachments": []*model.SlackAttachment{
|
||||
model.PostPropsAttachments: []*model.SlackAttachment{
|
||||
{
|
||||
Text: "hello",
|
||||
Actions: []*model.PostAction{
|
||||
{
|
||||
Type: model.PostActionTypeButton,
|
||||
Name: "action",
|
||||
Integration: &model.PostActionIntegration{
|
||||
URL: "/notaplugin/some/path",
|
||||
},
|
||||
Name: "action",
|
||||
Type: "some_type",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -944,7 +949,7 @@ func TestPostActionRelativePluginURL(t *testing.T) {
|
||||
|
||||
post, err := th.App.CreatePostAsUser(th.Context, &interactivePost, "", true)
|
||||
require.Nil(t, err)
|
||||
attachments, ok := post.GetProp("attachments").([]*model.SlackAttachment)
|
||||
attachments, ok := post.GetProp(model.PostPropsAttachments).([]*model.SlackAttachment)
|
||||
require.True(t, ok)
|
||||
require.NotEmpty(t, attachments[0].Actions)
|
||||
require.NotEmpty(t, attachments[0].Actions[0].Id)
|
||||
@@ -965,16 +970,16 @@ func TestPostActionRelativePluginURL(t *testing.T) {
|
||||
PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()),
|
||||
UserId: th.BasicUser.Id,
|
||||
Props: model.StringInterface{
|
||||
"attachments": []*model.SlackAttachment{
|
||||
model.PostPropsAttachments: []*model.SlackAttachment{
|
||||
{
|
||||
Text: "hello",
|
||||
Actions: []*model.PostAction{
|
||||
{
|
||||
Type: model.PostActionTypeButton,
|
||||
Name: "action",
|
||||
Integration: &model.PostActionIntegration{
|
||||
URL: "/plugins/myplugin/myaction",
|
||||
},
|
||||
Name: "action",
|
||||
Type: "some_type",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -984,7 +989,7 @@ func TestPostActionRelativePluginURL(t *testing.T) {
|
||||
|
||||
post, err := th.App.CreatePostAsUser(th.Context, &interactivePost, "", true)
|
||||
require.Nil(t, err)
|
||||
attachments, ok := post.GetProp("attachments").([]*model.SlackAttachment)
|
||||
attachments, ok := post.GetProp(model.PostPropsAttachments).([]*model.SlackAttachment)
|
||||
require.True(t, ok)
|
||||
require.NotEmpty(t, attachments[0].Actions)
|
||||
require.NotEmpty(t, attachments[0].Actions[0].Id)
|
||||
@@ -1005,16 +1010,16 @@ func TestPostActionRelativePluginURL(t *testing.T) {
|
||||
PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()),
|
||||
UserId: th.BasicUser.Id,
|
||||
Props: model.StringInterface{
|
||||
"attachments": []*model.SlackAttachment{
|
||||
model.PostPropsAttachments: []*model.SlackAttachment{
|
||||
{
|
||||
Text: "hello",
|
||||
Actions: []*model.PostAction{
|
||||
{
|
||||
Type: model.PostActionTypeButton,
|
||||
Name: "action",
|
||||
Integration: &model.PostActionIntegration{
|
||||
URL: "//plugins/myplugin///myaction",
|
||||
},
|
||||
Name: "action",
|
||||
Type: "some_type",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -1024,7 +1029,7 @@ func TestPostActionRelativePluginURL(t *testing.T) {
|
||||
|
||||
post, err := th.App.CreatePostAsUser(th.Context, &interactivePost, "", true)
|
||||
require.Nil(t, err)
|
||||
attachments, ok := post.GetProp("attachments").([]*model.SlackAttachment)
|
||||
attachments, ok := post.GetProp(model.PostPropsAttachments).([]*model.SlackAttachment)
|
||||
require.True(t, ok)
|
||||
require.NotEmpty(t, attachments[0].Actions)
|
||||
require.NotEmpty(t, attachments[0].Actions[0].Id)
|
||||
@@ -1045,16 +1050,16 @@ func TestPostActionRelativePluginURL(t *testing.T) {
|
||||
PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()),
|
||||
UserId: th.BasicUser.Id,
|
||||
Props: model.StringInterface{
|
||||
"attachments": []*model.SlackAttachment{
|
||||
model.PostPropsAttachments: []*model.SlackAttachment{
|
||||
{
|
||||
Text: "hello",
|
||||
Actions: []*model.PostAction{
|
||||
{
|
||||
Type: model.PostActionTypeButton,
|
||||
Name: "action",
|
||||
Integration: &model.PostActionIntegration{
|
||||
URL: "plugins/myplugin/myaction",
|
||||
},
|
||||
Name: "action",
|
||||
Type: "some_type",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -1064,7 +1069,7 @@ func TestPostActionRelativePluginURL(t *testing.T) {
|
||||
|
||||
post, err := th.App.CreatePostAsUser(th.Context, &interactivePost, "", true)
|
||||
require.Nil(t, err)
|
||||
attachments, ok := post.GetProp("attachments").([]*model.SlackAttachment)
|
||||
attachments, ok := post.GetProp(model.PostPropsAttachments).([]*model.SlackAttachment)
|
||||
require.True(t, ok)
|
||||
require.NotEmpty(t, attachments[0].Actions)
|
||||
require.NotEmpty(t, attachments[0].Actions[0].Id)
|
||||
|
||||
@@ -105,7 +105,7 @@ func TestIsKeywordMultibyte(t *testing.T) {
|
||||
post := &model.Post{
|
||||
Message: tc.Message,
|
||||
Props: model.StringInterface{
|
||||
"attachments": tc.Attachments,
|
||||
model.PostPropsAttachments: tc.Attachments,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -209,7 +209,7 @@ func (a *App) SendNotifications(c request.CTX, post *model.Post, team *model.Tea
|
||||
for _, profile := range profileMap {
|
||||
if (profile.NotifyProps[model.PushNotifyProp] == model.UserNotifyAll ||
|
||||
channelMemberNotifyPropsMap[profile.Id][model.PushNotifyProp] == model.ChannelNotifyAll) &&
|
||||
(post.UserId != profile.Id || post.GetProp("from_webhook") == "true") &&
|
||||
(post.UserId != profile.Id || post.GetProp(model.PostPropsFromWebhook) == "true") &&
|
||||
!post.IsSystemMessage() &&
|
||||
!(a.IsCRTEnabledForUser(c, profile.Id) && post.RootId != "") {
|
||||
allActivityPushUserIds = append(allActivityPushUserIds, profile.Id)
|
||||
@@ -228,7 +228,7 @@ func (a *App) SendNotifications(c request.CTX, post *model.Post, team *model.Tea
|
||||
var rootMentions *MentionResults
|
||||
if parentPostList != nil {
|
||||
rootPost := parentPostList.Posts[parentPostList.Order[0]]
|
||||
if rootPost.GetProp("from_webhook") != "true" {
|
||||
if rootPost.GetProp(model.PostPropsFromWebhook) != "true" {
|
||||
if _, ok := profileMap[rootPost.UserId]; ok {
|
||||
threadParticipants[rootPost.UserId] = true
|
||||
}
|
||||
@@ -358,7 +358,7 @@ func (a *App) SendNotifications(c request.CTX, post *model.Post, team *model.Tea
|
||||
continue
|
||||
}
|
||||
|
||||
if post.GetProp("from_webhook") != "true" && uid == post.UserId {
|
||||
if post.GetProp(model.PostPropsFromWebhook) != "true" && uid == post.UserId {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -1038,7 +1038,7 @@ func (a *App) getExplicitMentionsAndKeywords(c request.CTX, post *model.Post, ch
|
||||
var keywords MentionKeywords
|
||||
|
||||
if channel.Type == model.ChannelTypeDirect {
|
||||
isWebhook := post.GetProp("from_webhook") == "true"
|
||||
isWebhook := post.GetProp(model.PostPropsFromWebhook) == "true"
|
||||
|
||||
// A bot can post in a DM where it doesn't belong to.
|
||||
// Therefore, we cannot "guess" who is the other user,
|
||||
@@ -1119,7 +1119,7 @@ func (a *App) getExplicitMentionsAndKeywords(c request.CTX, post *model.Post, ch
|
||||
}
|
||||
|
||||
// Prevent the user from mentioning themselves
|
||||
if post.GetProp("from_webhook") != "true" {
|
||||
if post.GetProp(model.PostPropsFromWebhook) != "true" {
|
||||
mentions.removeMention(post.UserId)
|
||||
}
|
||||
}
|
||||
@@ -1639,7 +1639,7 @@ func (n *PostNotification) GetSenderName(userNameFormat string, overridesAllowed
|
||||
}
|
||||
|
||||
if overridesAllowed && n.Channel.Type != model.ChannelTypeDirect {
|
||||
if value := n.Post.GetProps()["override_username"]; value != nil && n.Post.GetProp("from_webhook") == "true" {
|
||||
if value := n.Post.GetProp(model.PostPropsOverrideUsername); value != nil && n.Post.GetProp(model.PostPropsFromWebhook) == "true" {
|
||||
if s, ok := value.(string); ok {
|
||||
return s
|
||||
}
|
||||
|
||||
@@ -668,7 +668,7 @@ func doesNotifyPropsAllowPushNotification(user *model.User, channelNotifyProps m
|
||||
}
|
||||
|
||||
if (notify == model.ChannelNotifyAll) &&
|
||||
(post.UserId != user.Id || post.GetProp("from_webhook") == "true") {
|
||||
(post.UserId != user.Id || post.GetProp(model.PostPropsFromWebhook) == "true") {
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -833,16 +833,16 @@ func (a *App) buildFullPushNotificationMessage(c request.CTX, contentsConfig str
|
||||
}
|
||||
|
||||
msg.SenderName = senderName
|
||||
if ou, ok := post.GetProp("override_username").(string); ok && *cfg.ServiceSettings.EnablePostUsernameOverride {
|
||||
if ou, ok := post.GetProp(model.PostPropsOverrideUsername).(string); ok && *cfg.ServiceSettings.EnablePostUsernameOverride {
|
||||
msg.OverrideUsername = ou
|
||||
msg.SenderName = ou
|
||||
}
|
||||
|
||||
if oi, ok := post.GetProp("override_icon_url").(string); ok && *cfg.ServiceSettings.EnablePostIconOverride {
|
||||
if oi, ok := post.GetProp(model.PostPropsOverrideIconURL).(string); ok && *cfg.ServiceSettings.EnablePostIconOverride {
|
||||
msg.OverrideIconURL = oi
|
||||
}
|
||||
|
||||
if fw, ok := post.GetProp("from_webhook").(string); ok {
|
||||
if fw, ok := post.GetProp(model.PostPropsFromWebhook).(string); ok {
|
||||
msg.FromWebhook = fw
|
||||
}
|
||||
|
||||
|
||||
@@ -1617,7 +1617,7 @@ func TestPushNotificationAttachment(t *testing.T) {
|
||||
post := &model.Post{
|
||||
Message: originalMessage,
|
||||
Props: map[string]any{
|
||||
"attachments": []*model.SlackAttachment{
|
||||
model.PostPropsAttachments: []*model.SlackAttachment{
|
||||
{
|
||||
AuthorName: "testuser",
|
||||
Text: "test attachment",
|
||||
|
||||
@@ -175,7 +175,7 @@ func TestSendNotifications(t *testing.T) {
|
||||
UserId: user.Id,
|
||||
ChannelId: th.BasicChannel.Id,
|
||||
Message: "a message",
|
||||
Props: model.StringInterface{"from_webhook": "true", "override_username": "a bot"},
|
||||
Props: model.StringInterface{model.PostPropsFromWebhook: "true", model.PostPropsOverrideUsername: "a bot"},
|
||||
}
|
||||
|
||||
rootPost, appErr := th.App.CreatePostMissingChannel(th.Context, rootPost, false, true)
|
||||
@@ -1428,7 +1428,7 @@ func TestGetExplicitMentions(t *testing.T) {
|
||||
post := &model.Post{
|
||||
Message: tc.Message,
|
||||
Props: model.StringInterface{
|
||||
"attachments": tc.Attachments,
|
||||
model.PostPropsAttachments: tc.Attachments,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -2208,7 +2208,7 @@ func TestGetMentionsEnabledFields(t *testing.T) {
|
||||
post := &model.Post{
|
||||
Message: "This is the message",
|
||||
Props: model.StringInterface{
|
||||
"attachments": attachments,
|
||||
model.PostPropsAttachments: attachments,
|
||||
},
|
||||
}
|
||||
expectedFields := []string{
|
||||
@@ -2316,22 +2316,22 @@ func TestPostNotificationGetSenderName(t *testing.T) {
|
||||
|
||||
overriddenPost := &model.Post{
|
||||
Props: model.StringInterface{
|
||||
"override_username": "Overridden",
|
||||
"from_webhook": "true",
|
||||
model.PostPropsOverrideUsername: "Overridden",
|
||||
model.PostPropsFromWebhook: "true",
|
||||
},
|
||||
}
|
||||
|
||||
overriddenPost2 := &model.Post{
|
||||
Props: model.StringInterface{
|
||||
"override_username": nil,
|
||||
"from_webhook": "true",
|
||||
model.PostPropsOverrideUsername: nil,
|
||||
model.PostPropsFromWebhook: "true",
|
||||
},
|
||||
}
|
||||
|
||||
overriddenPost3 := &model.Post{
|
||||
Props: model.StringInterface{
|
||||
"override_username": 10,
|
||||
"from_webhook": "true",
|
||||
model.PostPropsOverrideUsername: 10,
|
||||
model.PostPropsFromWebhook: "true",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -2364,7 +2364,7 @@ func TestPostNotificationGetSenderName(t *testing.T) {
|
||||
"overridden username": {
|
||||
post: overriddenPost,
|
||||
allowOverrides: true,
|
||||
expected: overriddenPost.GetProp("override_username").(string),
|
||||
expected: overriddenPost.GetProp(model.PostPropsOverrideUsername).(string),
|
||||
},
|
||||
"overridden username, direct channel": {
|
||||
channel: &model.Channel{Type: model.ChannelTypeDirect},
|
||||
@@ -2887,7 +2887,7 @@ func TestReplyPostNotificationsWithCRT(t *testing.T) {
|
||||
UserId: user.Id,
|
||||
ChannelId: th.BasicChannel.Id,
|
||||
Message: "a message",
|
||||
Props: model.StringInterface{"from_webhook": "true", "override_username": "a bot"},
|
||||
Props: model.StringInterface{model.PostPropsFromWebhook: "true", model.PostPropsOverrideUsername: "a bot"},
|
||||
}
|
||||
|
||||
rootPost, appErr := th.App.CreatePostMissingChannel(th.Context, rootPost, false, true)
|
||||
|
||||
@@ -756,7 +756,7 @@ func (api *PluginAPI) DeleteGroupSyncable(groupID string, syncableID string, syn
|
||||
}
|
||||
|
||||
func (api *PluginAPI) CreatePost(post *model.Post) (*model.Post, *model.AppError) {
|
||||
post.AddProp("from_plugin", "true")
|
||||
post.AddProp(model.PostPropsFromPlugin, "true")
|
||||
|
||||
post, appErr := api.app.CreatePostMissingChannel(api.ctx, post, true, true)
|
||||
if post != nil {
|
||||
|
||||
@@ -1520,7 +1520,7 @@ func TestPluginCreatePostAddsFromPluginProp(t *testing.T) {
|
||||
|
||||
actualPost, err := api.GetPost(post.Id)
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, "true", actualPost.GetProp("from_plugin"))
|
||||
assert.Equal(t, "true", actualPost.GetProp(model.PostPropsFromPlugin))
|
||||
}
|
||||
|
||||
func TestPluginAPIGetConfig(t *testing.T) {
|
||||
|
||||
@@ -297,12 +297,12 @@ func (a *App) CreatePost(c request.CTX, post *model.Post, channel *model.Channel
|
||||
}
|
||||
|
||||
// Temporary fix so old plugins don't clobber new fields in SlackAttachment struct, see MM-13088
|
||||
if attachments, ok := post.GetProp("attachments").([]*model.SlackAttachment); ok {
|
||||
if attachments, ok := post.GetProp(model.PostPropsAttachments).([]*model.SlackAttachment); ok {
|
||||
jsonAttachments, err := json.Marshal(attachments)
|
||||
if err == nil {
|
||||
attachmentsInterface := []any{}
|
||||
err = json.Unmarshal(jsonAttachments, &attachmentsInterface)
|
||||
post.AddProp("attachments", attachmentsInterface)
|
||||
post.AddProp(model.PostPropsAttachments, attachmentsInterface)
|
||||
}
|
||||
if err != nil {
|
||||
c.Logger().Warn("Could not convert post attachments to map interface.", mlog.Err(err))
|
||||
@@ -524,9 +524,9 @@ func (a *App) FillInPostProps(c request.CTX, post *model.Post, channel *model.Ch
|
||||
}
|
||||
|
||||
if len(channelMentionsProp) > 0 {
|
||||
post.AddProp("channel_mentions", channelMentionsProp)
|
||||
post.AddProp(model.PostPropsChannelMentions, channelMentionsProp)
|
||||
} else if post.GetProps() != nil {
|
||||
post.DelProp("channel_mentions")
|
||||
post.DelProp(model.PostPropsChannelMentions)
|
||||
}
|
||||
|
||||
matched := atMentionPattern.MatchString(post.Message)
|
||||
@@ -2077,7 +2077,7 @@ func isCommentMention(user *model.User, post *model.Post, otherPosts map[string]
|
||||
|
||||
func isPostMention(user *model.User, post *model.Post, keywords MentionKeywords, otherPosts map[string]*model.Post, mentionedByThread map[string]bool, checkForCommentMentions bool) bool {
|
||||
// Prevent the user from mentioning themselves
|
||||
if post.UserId == user.Id && post.GetProp("from_webhook") != "true" {
|
||||
if post.UserId == user.Id && post.GetProp(model.PostPropsFromWebhook) != "true" {
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
@@ -38,8 +38,6 @@ type linkMetadataCache struct {
|
||||
|
||||
const MaxMetadataImageSize = MaxOpenGraphResponseSize
|
||||
|
||||
const UnsafeLinksPostProp = "unsafe_links"
|
||||
|
||||
func (s *Server) initPostMetadata() {
|
||||
// Dump any cached links if the proxy settings have changed so image URLs can be updated
|
||||
s.platform.AddConfigListener(func(before, after *model.Config) {
|
||||
@@ -183,7 +181,7 @@ func (a *App) getEmbedsAndImages(c request.CTX, post *model.Post, isNewPost bool
|
||||
// Embeds and image dimensions
|
||||
firstLink, images := a.getFirstLinkAndImages(c, post.Message)
|
||||
|
||||
if unsafeLinksProp := post.GetProp(UnsafeLinksPostProp); unsafeLinksProp != nil {
|
||||
if unsafeLinksProp := post.GetProp(model.PostPropsUnsafeLinks); unsafeLinksProp != nil {
|
||||
if prop, ok := unsafeLinksProp.(string); ok && prop == "true" {
|
||||
images = []string{}
|
||||
if !looksLikeAPermalink(firstLink, *a.Config().ServiceSettings.SiteURL) {
|
||||
@@ -302,7 +300,7 @@ func (a *App) getEmojisAndReactionsForPost(c request.CTX, post *model.Post) ([]*
|
||||
}
|
||||
|
||||
func (a *App) getEmbedForPost(c request.CTX, post *model.Post, firstLink string, isNewPost bool) (*model.PostEmbed, error) {
|
||||
if _, ok := post.GetProps()["attachments"]; ok {
|
||||
if _, ok := post.GetProps()[model.PostPropsAttachments]; ok {
|
||||
return &model.PostEmbed{
|
||||
Type: model.PostEmbedMessageAttachment,
|
||||
}, nil
|
||||
|
||||
@@ -223,7 +223,7 @@ func TestPreparePostForClient(t *testing.T) {
|
||||
ChannelId: th.BasicChannel.Id,
|
||||
Message: ":" + emoji.Name + ": :taco:",
|
||||
Props: map[string]any{
|
||||
"attachments": []*model.SlackAttachment{
|
||||
model.PostPropsAttachments: []*model.SlackAttachment{
|
||||
{
|
||||
Text: ":" + emoji.Name + ":",
|
||||
},
|
||||
@@ -267,7 +267,7 @@ func TestPreparePostForClient(t *testing.T) {
|
||||
ChannelId: th.BasicChannel.Id,
|
||||
Message: ":" + emoji3.Name + ": :taco:",
|
||||
Props: map[string]any{
|
||||
"attachments": []*model.SlackAttachment{
|
||||
model.PostPropsAttachments: []*model.SlackAttachment{
|
||||
{
|
||||
Text: ":" + emoji4.Name + ":",
|
||||
},
|
||||
@@ -531,7 +531,7 @@ func TestPreparePostForClient(t *testing.T) {
|
||||
ChannelId: th.BasicChannel.Id,
|
||||
Message: `Bla bla bla: ` + fmt.Sprintf(tc.link, noAccessServer.URL),
|
||||
}
|
||||
prepost.AddProp(UnsafeLinksPostProp, "true")
|
||||
prepost.AddProp(model.PostPropsUnsafeLinks, "true")
|
||||
|
||||
post, err := th.App.CreatePost(th.Context, prepost, th.BasicChannel, model.CreatePostFlags{SetOnline: true})
|
||||
require.Nil(t, err)
|
||||
@@ -569,7 +569,7 @@ func TestPreparePostForClient(t *testing.T) {
|
||||
UserId: th.BasicUser.Id,
|
||||
ChannelId: th.BasicChannel.Id,
|
||||
Props: map[string]any{
|
||||
"attachments": []any{
|
||||
model.PostPropsAttachments: []any{
|
||||
map[string]any{
|
||||
"text": "",
|
||||
},
|
||||
@@ -1039,7 +1039,7 @@ func TestGetEmbedForPost(t *testing.T) {
|
||||
t.Run("should return a message attachment when the post has one", func(t *testing.T) {
|
||||
embed, err := th.App.getEmbedForPost(th.Context, &model.Post{
|
||||
Props: model.StringInterface{
|
||||
"attachments": []*model.SlackAttachment{
|
||||
model.PostPropsAttachments: []*model.SlackAttachment{
|
||||
{
|
||||
Text: "test",
|
||||
},
|
||||
@@ -1117,7 +1117,7 @@ func TestGetEmbedForPost(t *testing.T) {
|
||||
t.Run("should return an embedded message attachment", func(t *testing.T) {
|
||||
embed, err := th.App.getEmbedForPost(th.Context, &model.Post{
|
||||
Props: model.StringInterface{
|
||||
"attachments": []*model.SlackAttachment{
|
||||
model.PostPropsAttachments: []*model.SlackAttachment{
|
||||
{
|
||||
Text: "test",
|
||||
},
|
||||
@@ -1532,7 +1532,7 @@ func TestGetEmojiNamesForPost(t *testing.T) {
|
||||
Post: &model.Post{
|
||||
Message: "this is a post",
|
||||
Props: map[string]any{
|
||||
"attachments": []*model.SlackAttachment{
|
||||
model.PostPropsAttachments: []*model.SlackAttachment{
|
||||
{
|
||||
Text: ":emoji1:",
|
||||
Pretext: ":emoji2:",
|
||||
@@ -1560,7 +1560,7 @@ func TestGetEmojiNamesForPost(t *testing.T) {
|
||||
Post: &model.Post{
|
||||
Message: "this is :emoji1",
|
||||
Props: map[string]any{
|
||||
"attachments": []*model.SlackAttachment{
|
||||
model.PostPropsAttachments: []*model.SlackAttachment{
|
||||
{
|
||||
Text: ":emoji2:",
|
||||
Pretext: ":emoji2:",
|
||||
@@ -1616,7 +1616,7 @@ func TestGetCustomEmojisForPost(t *testing.T) {
|
||||
post := &model.Post{
|
||||
Message: ":" + emojis[1].Name + ":",
|
||||
Props: map[string]any{
|
||||
"attachments": []*model.SlackAttachment{
|
||||
model.PostPropsAttachments: []*model.SlackAttachment{
|
||||
{
|
||||
Pretext: ":" + emojis[2].Name + ":",
|
||||
Text: ":" + emojis[3].Name + ":",
|
||||
@@ -1642,7 +1642,7 @@ func TestGetCustomEmojisForPost(t *testing.T) {
|
||||
post := &model.Post{
|
||||
Message: ":secret: :" + emojis[0].Name + ":",
|
||||
Props: map[string]any{
|
||||
"attachments": []*model.SlackAttachment{
|
||||
model.PostPropsAttachments: []*model.SlackAttachment{
|
||||
{
|
||||
Text: ":imaginary:",
|
||||
},
|
||||
@@ -1838,7 +1838,7 @@ func TestGetImagesInMessageAttachments(t *testing.T) {
|
||||
Name: "empty attachments",
|
||||
Post: &model.Post{
|
||||
Props: map[string]any{
|
||||
"attachments": []*model.SlackAttachment{},
|
||||
model.PostPropsAttachments: []*model.SlackAttachment{},
|
||||
},
|
||||
},
|
||||
Expected: []string{},
|
||||
@@ -1847,7 +1847,7 @@ func TestGetImagesInMessageAttachments(t *testing.T) {
|
||||
Name: "attachment with no fields that can contain images",
|
||||
Post: &model.Post{
|
||||
Props: map[string]any{
|
||||
"attachments": []*model.SlackAttachment{
|
||||
model.PostPropsAttachments: []*model.SlackAttachment{
|
||||
{
|
||||
Title: "This is the title",
|
||||
},
|
||||
@@ -1860,7 +1860,7 @@ func TestGetImagesInMessageAttachments(t *testing.T) {
|
||||
Name: "images in text",
|
||||
Post: &model.Post{
|
||||
Props: map[string]any{
|
||||
"attachments": []*model.SlackAttachment{
|
||||
model.PostPropsAttachments: []*model.SlackAttachment{
|
||||
{
|
||||
Text: " and ",
|
||||
},
|
||||
@@ -1873,7 +1873,7 @@ func TestGetImagesInMessageAttachments(t *testing.T) {
|
||||
Name: "images in pretext",
|
||||
Post: &model.Post{
|
||||
Props: map[string]any{
|
||||
"attachments": []*model.SlackAttachment{
|
||||
model.PostPropsAttachments: []*model.SlackAttachment{
|
||||
{
|
||||
Pretext: " and ",
|
||||
},
|
||||
@@ -1886,7 +1886,7 @@ func TestGetImagesInMessageAttachments(t *testing.T) {
|
||||
Name: "images in fields",
|
||||
Post: &model.Post{
|
||||
Props: map[string]any{
|
||||
"attachments": []*model.SlackAttachment{
|
||||
model.PostPropsAttachments: []*model.SlackAttachment{
|
||||
{
|
||||
Fields: []*model.SlackAttachmentField{
|
||||
{
|
||||
@@ -1903,7 +1903,7 @@ func TestGetImagesInMessageAttachments(t *testing.T) {
|
||||
Name: "image in author_icon",
|
||||
Post: &model.Post{
|
||||
Props: map[string]any{
|
||||
"attachments": []*model.SlackAttachment{
|
||||
model.PostPropsAttachments: []*model.SlackAttachment{
|
||||
{
|
||||
AuthorIcon: "https://example.com/icon2",
|
||||
},
|
||||
@@ -1916,7 +1916,7 @@ func TestGetImagesInMessageAttachments(t *testing.T) {
|
||||
Name: "image in image_url",
|
||||
Post: &model.Post{
|
||||
Props: map[string]any{
|
||||
"attachments": []*model.SlackAttachment{
|
||||
model.PostPropsAttachments: []*model.SlackAttachment{
|
||||
{
|
||||
ImageURL: "https://example.com/image",
|
||||
},
|
||||
@@ -1929,7 +1929,7 @@ func TestGetImagesInMessageAttachments(t *testing.T) {
|
||||
Name: "image in thumb_url",
|
||||
Post: &model.Post{
|
||||
Props: map[string]any{
|
||||
"attachments": []*model.SlackAttachment{
|
||||
model.PostPropsAttachments: []*model.SlackAttachment{
|
||||
{
|
||||
ThumbURL: "https://example.com/image",
|
||||
},
|
||||
@@ -1942,7 +1942,7 @@ func TestGetImagesInMessageAttachments(t *testing.T) {
|
||||
Name: "image in footer_icon",
|
||||
Post: &model.Post{
|
||||
Props: map[string]any{
|
||||
"attachments": []*model.SlackAttachment{
|
||||
model.PostPropsAttachments: []*model.SlackAttachment{
|
||||
{
|
||||
FooterIcon: "https://example.com/image",
|
||||
},
|
||||
@@ -1955,7 +1955,7 @@ func TestGetImagesInMessageAttachments(t *testing.T) {
|
||||
Name: "images in multiple fields",
|
||||
Post: &model.Post{
|
||||
Props: map[string]any{
|
||||
"attachments": []*model.SlackAttachment{
|
||||
model.PostPropsAttachments: []*model.SlackAttachment{
|
||||
{
|
||||
Fields: []*model.SlackAttachmentField{
|
||||
{
|
||||
@@ -1975,7 +1975,7 @@ func TestGetImagesInMessageAttachments(t *testing.T) {
|
||||
Name: "non-string field",
|
||||
Post: &model.Post{
|
||||
Props: map[string]any{
|
||||
"attachments": []*model.SlackAttachment{
|
||||
model.PostPropsAttachments: []*model.SlackAttachment{
|
||||
{
|
||||
Fields: []*model.SlackAttachmentField{
|
||||
{
|
||||
@@ -1992,7 +1992,7 @@ func TestGetImagesInMessageAttachments(t *testing.T) {
|
||||
Name: "images in multiple locations",
|
||||
Post: &model.Post{
|
||||
Props: map[string]any{
|
||||
"attachments": []*model.SlackAttachment{
|
||||
model.PostPropsAttachments: []*model.SlackAttachment{
|
||||
{
|
||||
Text: "",
|
||||
Pretext: "",
|
||||
@@ -2014,7 +2014,7 @@ func TestGetImagesInMessageAttachments(t *testing.T) {
|
||||
Name: "multiple attachments",
|
||||
Post: &model.Post{
|
||||
Props: map[string]any{
|
||||
"attachments": []*model.SlackAttachment{
|
||||
model.PostPropsAttachments: []*model.SlackAttachment{
|
||||
{
|
||||
Text: "",
|
||||
},
|
||||
|
||||
@@ -588,7 +588,7 @@ func TestPostChannelMentions(t *testing.T) {
|
||||
"display_name": "Mention Test",
|
||||
"team_name": th.BasicTeam.Name,
|
||||
},
|
||||
}, post.GetProp("channel_mentions"))
|
||||
}, post.GetProp(model.PostPropsChannelMentions))
|
||||
|
||||
post.Message = fmt.Sprintf("goodbye, ~%v!", channelToMention2.Name)
|
||||
result, err := th.App.UpdatePost(th.Context, post, nil)
|
||||
@@ -598,12 +598,12 @@ func TestPostChannelMentions(t *testing.T) {
|
||||
"display_name": "Mention Test2",
|
||||
"team_name": th.BasicTeam.Name,
|
||||
},
|
||||
}, result.GetProp("channel_mentions"))
|
||||
}, result.GetProp(model.PostPropsChannelMentions))
|
||||
|
||||
result.Message = "no more mentions!"
|
||||
result, err = th.App.UpdatePost(th.Context, result, nil)
|
||||
require.Nil(t, err)
|
||||
assert.Nil(t, result.GetProp("channel_mentions"))
|
||||
assert.Nil(t, result.GetProp(model.PostPropsChannelMentions))
|
||||
}
|
||||
|
||||
func TestImageProxy(t *testing.T) {
|
||||
@@ -1282,7 +1282,7 @@ func TestCreatePostAsUser(t *testing.T) {
|
||||
Message: "test",
|
||||
UserId: th.BasicUser.Id,
|
||||
}
|
||||
post.AddProp("from_webhook", "true")
|
||||
post.AddProp(model.PostPropsFromWebhook, "true")
|
||||
|
||||
channelMemberBefore, err := th.App.Srv().Store().Channel().GetMember(context.Background(), th.BasicChannel.Id, th.BasicUser.Id)
|
||||
require.NoError(t, err)
|
||||
@@ -2502,7 +2502,7 @@ func TestCountMentionsFromPost(t *testing.T) {
|
||||
ChannelId: channel.Id,
|
||||
Message: fmt.Sprintf("@%s", user2.Username),
|
||||
Props: map[string]any{
|
||||
"from_webhook": "true",
|
||||
model.PostPropsFromWebhook: "true",
|
||||
},
|
||||
}, channel, model.CreatePostFlags{SetOnline: true})
|
||||
require.Nil(t, err)
|
||||
|
||||
@@ -165,15 +165,15 @@ func TestHandleCommandResponsePost(t *testing.T) {
|
||||
assert.Equal(t, resp.Type, post.Type)
|
||||
assert.Equal(t, resp.Props, post.GetProps())
|
||||
assert.Equal(t, resp.Text, post.Message)
|
||||
assert.Nil(t, post.GetProp("override_icon_url"))
|
||||
assert.Nil(t, post.GetProp("override_username"))
|
||||
assert.Nil(t, post.GetProp("from_webhook"))
|
||||
assert.Nil(t, post.GetProp(model.PostPropsOverrideIconURL))
|
||||
assert.Nil(t, post.GetProp(model.PostPropsOverrideUsername))
|
||||
assert.Nil(t, post.GetProp(model.PostPropsFromWebhook))
|
||||
|
||||
// Command is not built in, so it is a bot command.
|
||||
builtIn = false
|
||||
post, err = th.App.HandleCommandResponsePost(th.Context, command, args, resp, builtIn)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "true", post.GetProp("from_webhook"))
|
||||
assert.Equal(t, "true", post.GetProp(model.PostPropsFromWebhook))
|
||||
|
||||
builtIn = true
|
||||
|
||||
@@ -195,23 +195,23 @@ func TestHandleCommandResponsePost(t *testing.T) {
|
||||
|
||||
post, err = th.App.HandleCommandResponsePost(th.Context, command, args, resp, builtIn)
|
||||
assert.Nil(t, err)
|
||||
assert.Nil(t, post.GetProp("override_username"))
|
||||
assert.Nil(t, post.GetProp(model.PostPropsOverrideUsername))
|
||||
|
||||
*th.App.Config().ServiceSettings.EnablePostUsernameOverride = true
|
||||
|
||||
// Override username config is turned on. Override username through command property.
|
||||
post, err = th.App.HandleCommandResponsePost(th.Context, command, args, resp, builtIn)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, command.Username, post.GetProp("override_username"))
|
||||
assert.Equal(t, "true", post.GetProp("from_webhook"))
|
||||
assert.Equal(t, command.Username, post.GetProp(model.PostPropsOverrideUsername))
|
||||
assert.Equal(t, "true", post.GetProp(model.PostPropsFromWebhook))
|
||||
|
||||
command.Username = ""
|
||||
|
||||
// Override username through response property.
|
||||
post, err = th.App.HandleCommandResponsePost(th.Context, command, args, resp, builtIn)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, resp.Username, post.GetProp("override_username"))
|
||||
assert.Equal(t, "true", post.GetProp("from_webhook"))
|
||||
assert.Equal(t, resp.Username, post.GetProp(model.PostPropsOverrideUsername))
|
||||
assert.Equal(t, "true", post.GetProp(model.PostPropsFromWebhook))
|
||||
|
||||
*th.App.Config().ServiceSettings.EnablePostUsernameOverride = false
|
||||
|
||||
@@ -222,23 +222,23 @@ func TestHandleCommandResponsePost(t *testing.T) {
|
||||
|
||||
post, err = th.App.HandleCommandResponsePost(th.Context, command, args, resp, builtIn)
|
||||
assert.Nil(t, err)
|
||||
assert.Nil(t, post.GetProp("override_icon_url"))
|
||||
assert.Nil(t, post.GetProp(model.PostPropsOverrideIconURL))
|
||||
|
||||
*th.App.Config().ServiceSettings.EnablePostIconOverride = true
|
||||
|
||||
// Override icon url config is turned on. Override icon url through command property.
|
||||
post, err = th.App.HandleCommandResponsePost(th.Context, command, args, resp, builtIn)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, command.IconURL, post.GetProp("override_icon_url"))
|
||||
assert.Equal(t, "true", post.GetProp("from_webhook"))
|
||||
assert.Equal(t, command.IconURL, post.GetProp(model.PostPropsOverrideIconURL))
|
||||
assert.Equal(t, "true", post.GetProp(model.PostPropsFromWebhook))
|
||||
|
||||
command.IconURL = ""
|
||||
|
||||
// Override icon url through response property.
|
||||
post, err = th.App.HandleCommandResponsePost(th.Context, command, args, resp, builtIn)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, resp.IconURL, post.GetProp("override_icon_url"))
|
||||
assert.Equal(t, "true", post.GetProp("from_webhook"))
|
||||
assert.Equal(t, resp.IconURL, post.GetProp(model.PostPropsOverrideIconURL))
|
||||
assert.Equal(t, "true", post.GetProp(model.PostPropsFromWebhook))
|
||||
|
||||
// Test Slack text conversion.
|
||||
resp.Text = "<!channel>"
|
||||
@@ -246,7 +246,7 @@ func TestHandleCommandResponsePost(t *testing.T) {
|
||||
post, err = th.App.HandleCommandResponsePost(th.Context, command, args, resp, builtIn)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "@channel", post.Message)
|
||||
assert.Equal(t, "true", post.GetProp("from_webhook"))
|
||||
assert.Equal(t, "true", post.GetProp(model.PostPropsFromWebhook))
|
||||
|
||||
// Test Slack attachments text conversion.
|
||||
resp.Attachments = []*model.SlackAttachment{
|
||||
@@ -261,7 +261,7 @@ func TestHandleCommandResponsePost(t *testing.T) {
|
||||
if assert.Len(t, post.Attachments(), 1) {
|
||||
assert.Equal(t, "@here", post.Attachments()[0].Text)
|
||||
}
|
||||
assert.Equal(t, "true", post.GetProp("from_webhook"))
|
||||
assert.Equal(t, "true", post.GetProp(model.PostPropsFromWebhook))
|
||||
|
||||
channel = th.createPrivateChannel(th.BasicTeam)
|
||||
resp.ChannelId = channel.Id
|
||||
|
||||
@@ -163,7 +163,7 @@ func (a *App) TriggerWebhook(c request.CTX, payload *model.OutgoingWebhookPayloa
|
||||
if len(webhookResp.Props) == 0 {
|
||||
webhookResp.Props = make(model.StringInterface)
|
||||
}
|
||||
webhookResp.Props["webhook_display_name"] = hook.DisplayName
|
||||
webhookResp.Props[model.PostPropsWebhookDisplayName] = hook.DisplayName
|
||||
|
||||
text := ""
|
||||
if webhookResp.Text != nil {
|
||||
@@ -172,7 +172,7 @@ func (a *App) TriggerWebhook(c request.CTX, payload *model.OutgoingWebhookPayloa
|
||||
webhookResp.Attachments = a.ProcessSlackAttachments(webhookResp.Attachments)
|
||||
// attachments is in here for slack compatibility
|
||||
if len(webhookResp.Attachments) > 0 {
|
||||
webhookResp.Props["attachments"] = webhookResp.Attachments
|
||||
webhookResp.Props[model.PostPropsAttachments] = webhookResp.Attachments
|
||||
}
|
||||
if *a.Config().ServiceSettings.EnablePostUsernameOverride && hook.Username != "" && webhookResp.Username == "" {
|
||||
webhookResp.Username = hook.Username
|
||||
@@ -232,7 +232,7 @@ func splitWebhookPost(post *model.Post, maxPostSize int) ([]*model.Post, *model.
|
||||
base.Message = ""
|
||||
base.SetProps(make(map[string]any))
|
||||
for k, v := range post.GetProps() {
|
||||
if k != "attachments" {
|
||||
if k != model.PostPropsAttachments {
|
||||
base.AddProp(k, v)
|
||||
}
|
||||
}
|
||||
@@ -259,7 +259,7 @@ func splitWebhookPost(post *model.Post, maxPostSize int) ([]*model.Post, *model.
|
||||
split.Message = remainingText
|
||||
splits = append(splits, split)
|
||||
|
||||
attachments, _ := post.GetProp("attachments").([]*model.SlackAttachment)
|
||||
attachments, _ := post.GetProp(model.PostPropsAttachments).([]*model.SlackAttachment)
|
||||
for _, attachment := range attachments {
|
||||
newAttachment := *attachment
|
||||
for {
|
||||
@@ -268,8 +268,8 @@ func splitWebhookPost(post *model.Post, maxPostSize int) ([]*model.Post, *model.
|
||||
for k, v := range lastSplit.GetProps() {
|
||||
newProps[k] = v
|
||||
}
|
||||
origAttachments, _ := newProps["attachments"].([]*model.SlackAttachment)
|
||||
newProps["attachments"] = append(origAttachments, &newAttachment)
|
||||
origAttachments, _ := newProps[model.PostPropsAttachments].([]*model.SlackAttachment)
|
||||
newProps[model.PostPropsAttachments] = append(origAttachments, &newAttachment)
|
||||
newPropsString := model.StringInterfaceToJSON(newProps)
|
||||
runeCount := utf8.RuneCountInString(newPropsString)
|
||||
|
||||
@@ -310,7 +310,7 @@ func (a *App) CreateWebhookPost(c request.CTX, userID string, channel *model.Cha
|
||||
text = linkWithTextRegex.ReplaceAllString(text, "[${2}](${1})")
|
||||
|
||||
post := &model.Post{UserId: userID, ChannelId: channel.Id, Message: text, Type: postType, RootId: postRootId}
|
||||
post.AddProp("from_webhook", "true")
|
||||
post.AddProp(model.PostPropsFromWebhook, "true")
|
||||
|
||||
if priority != nil {
|
||||
if priority.Priority == nil {
|
||||
@@ -333,28 +333,33 @@ func (a *App) CreateWebhookPost(c request.CTX, userID string, channel *model.Cha
|
||||
|
||||
if *a.Config().ServiceSettings.EnablePostUsernameOverride {
|
||||
if overrideUsername != "" {
|
||||
post.AddProp("override_username", overrideUsername)
|
||||
post.AddProp(model.PostPropsOverrideUsername, overrideUsername)
|
||||
} else {
|
||||
post.AddProp("override_username", model.DefaultWebhookUsername)
|
||||
post.AddProp(model.PostPropsOverrideUsername, model.DefaultWebhookUsername)
|
||||
}
|
||||
}
|
||||
|
||||
if *a.Config().ServiceSettings.EnablePostIconOverride {
|
||||
if overrideIconURL != "" {
|
||||
post.AddProp("override_icon_url", overrideIconURL)
|
||||
post.AddProp(model.PostPropsOverrideIconURL, overrideIconURL)
|
||||
}
|
||||
if overrideIconEmoji != "" {
|
||||
post.AddProp("override_icon_emoji", overrideIconEmoji)
|
||||
post.AddProp(model.PostPropsOverrideIconURL, overrideIconEmoji)
|
||||
}
|
||||
}
|
||||
|
||||
if len(props) > 0 {
|
||||
for key, val := range props {
|
||||
if key == "attachments" {
|
||||
switch key {
|
||||
case model.PostPropsAttachments:
|
||||
if attachments, success := val.([]*model.SlackAttachment); success {
|
||||
model.ParseSlackAttachment(post, attachments)
|
||||
}
|
||||
} else if key != "override_icon_url" && key != "override_username" && key != "from_webhook" {
|
||||
case model.PostPropsOverrideIconURL,
|
||||
model.PostPropsOverrideUsername,
|
||||
model.PostPropsFromWebhook:
|
||||
// Do nothing
|
||||
default:
|
||||
post.AddProp(key, val)
|
||||
}
|
||||
}
|
||||
@@ -765,13 +770,13 @@ func (a *App) HandleIncomingWebhook(c request.CTX, hookID string, req *model.Inc
|
||||
req.Props = make(model.StringInterface)
|
||||
}
|
||||
|
||||
req.Props["webhook_display_name"] = hook.DisplayName
|
||||
req.Props[model.PostPropsWebhookDisplayName] = hook.DisplayName
|
||||
|
||||
text = a.ProcessSlackText(text)
|
||||
req.Attachments = a.ProcessSlackAttachments(req.Attachments)
|
||||
// attachments is in here for slack compatibility
|
||||
if len(req.Attachments) > 0 {
|
||||
req.Props["attachments"] = req.Attachments
|
||||
req.Props[model.PostPropsAttachments] = req.Attachments
|
||||
webhookType = model.PostTypeSlackAttachment
|
||||
}
|
||||
|
||||
|
||||
@@ -298,44 +298,44 @@ func TestCreateWebhookPost(t *testing.T) {
|
||||
|
||||
post, err := th.App.CreateWebhookPost(th.Context, hook.UserId, th.BasicChannel, "foo", "user", "http://iconurl", "",
|
||||
model.StringInterface{
|
||||
"attachments": []*model.SlackAttachment{
|
||||
model.PostPropsAttachments: []*model.SlackAttachment{
|
||||
{
|
||||
Text: "text",
|
||||
},
|
||||
},
|
||||
"webhook_display_name": hook.DisplayName,
|
||||
model.PostPropsWebhookDisplayName: hook.DisplayName,
|
||||
},
|
||||
model.PostTypeSlackAttachment,
|
||||
"", nil)
|
||||
require.Nil(t, err)
|
||||
|
||||
assert.Contains(t, post.GetProps(), "from_webhook", "missing from_webhook prop")
|
||||
assert.Contains(t, post.GetProps(), "attachments", "missing attachments prop")
|
||||
assert.Contains(t, post.GetProps(), "webhook_display_name", "missing webhook_display_name prop")
|
||||
assert.Contains(t, post.GetProps(), model.PostPropsFromWebhook, "missing from_webhook prop")
|
||||
assert.Contains(t, post.GetProps(), model.PostPropsAttachments, "missing attachments prop")
|
||||
assert.Contains(t, post.GetProps(), model.PostPropsWebhookDisplayName, "missing webhook_display_name prop")
|
||||
|
||||
_, err = th.App.CreateWebhookPost(th.Context, hook.UserId, th.BasicChannel, "foo", "user", "http://iconurl", "", nil, model.PostTypeSystemGeneric, "", nil)
|
||||
require.NotNil(t, err, "Should have failed - bad post type")
|
||||
|
||||
expectedText := "`<>|<>|`"
|
||||
post, err = th.App.CreateWebhookPost(th.Context, hook.UserId, th.BasicChannel, expectedText, "user", "http://iconurl", "", model.StringInterface{
|
||||
"attachments": []*model.SlackAttachment{
|
||||
model.PostPropsAttachments: []*model.SlackAttachment{
|
||||
{
|
||||
Text: "text",
|
||||
},
|
||||
},
|
||||
"webhook_display_name": hook.DisplayName,
|
||||
model.PostPropsWebhookDisplayName: hook.DisplayName,
|
||||
}, model.PostTypeSlackAttachment, "", nil)
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, expectedText, post.Message)
|
||||
|
||||
expectedText = "< | \n|\n>"
|
||||
post, err = th.App.CreateWebhookPost(th.Context, hook.UserId, th.BasicChannel, expectedText, "user", "http://iconurl", "", model.StringInterface{
|
||||
"attachments": []*model.SlackAttachment{
|
||||
model.PostPropsAttachments: []*model.SlackAttachment{
|
||||
{
|
||||
Text: "text",
|
||||
},
|
||||
},
|
||||
"webhook_display_name": hook.DisplayName,
|
||||
model.PostPropsWebhookDisplayName: hook.DisplayName,
|
||||
}, model.PostTypeSlackAttachment, "", nil)
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, expectedText, post.Message)
|
||||
@@ -358,12 +358,12 @@ Date: Thu Mar 1 19:46:48 2018 +0300
|
||||
test | 3 +++
|
||||
1 file changed, 3 insertions(+)`
|
||||
post, err = th.App.CreateWebhookPost(th.Context, hook.UserId, th.BasicChannel, expectedText, "user", "http://iconurl", "", model.StringInterface{
|
||||
"attachments": []*model.SlackAttachment{
|
||||
model.PostPropsAttachments: []*model.SlackAttachment{
|
||||
{
|
||||
Text: "text",
|
||||
},
|
||||
},
|
||||
"webhook_display_name": hook.DisplayName,
|
||||
model.PostPropsWebhookDisplayName: hook.DisplayName,
|
||||
}, model.PostTypeSlackAttachment, "", nil)
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, expectedText, post.Message)
|
||||
@@ -415,7 +415,7 @@ func TestCreateWebhookPostWithPriority(t *testing.T) {
|
||||
|
||||
for _, conditions := range testConditions {
|
||||
post, err := th.App.CreateWebhookPost(th.Context, hook.UserId, th.BasicChannel, "foo @"+th.BasicUser.Username, "user", "http://iconurl", "",
|
||||
model.StringInterface{"webhook_display_name": hook.DisplayName},
|
||||
model.StringInterface{model.PostPropsWebhookDisplayName: hook.DisplayName},
|
||||
model.PostTypeSlackAttachment,
|
||||
"",
|
||||
&conditions,
|
||||
@@ -424,7 +424,7 @@ func TestCreateWebhookPostWithPriority(t *testing.T) {
|
||||
require.Nil(t, err)
|
||||
|
||||
assert.Equal(t, post.Message, "foo @"+th.BasicUser.Username)
|
||||
assert.Contains(t, post.GetProps(), "from_webhook", "missing from_webhook prop")
|
||||
assert.Contains(t, post.GetProps(), model.PostPropsFromWebhook, "missing from_webhook prop")
|
||||
assert.Equal(t, *conditions.Priority, *post.GetPriority().Priority)
|
||||
assert.Equal(t, *conditions.RequestedAck, *post.GetPriority().RequestedAck)
|
||||
assert.Equal(t, *conditions.PersistentNotifications, *post.GetPriority().PersistentNotifications)
|
||||
@@ -488,7 +488,7 @@ func TestSplitWebhookPost(t *testing.T) {
|
||||
Post: &model.Post{
|
||||
Message: strings.Repeat("本", maxPostSize*3/2),
|
||||
Props: map[string]any{
|
||||
"attachments": []*model.SlackAttachment{
|
||||
model.PostPropsAttachments: []*model.SlackAttachment{
|
||||
{
|
||||
Text: strings.Repeat("本", 1000),
|
||||
},
|
||||
@@ -508,7 +508,7 @@ func TestSplitWebhookPost(t *testing.T) {
|
||||
{
|
||||
Message: strings.Repeat("本", maxPostSize/2),
|
||||
Props: map[string]any{
|
||||
"attachments": []*model.SlackAttachment{
|
||||
model.PostPropsAttachments: []*model.SlackAttachment{
|
||||
{
|
||||
Text: strings.Repeat("本", 1000),
|
||||
},
|
||||
@@ -520,7 +520,7 @@ func TestSplitWebhookPost(t *testing.T) {
|
||||
},
|
||||
{
|
||||
Props: map[string]any{
|
||||
"attachments": []*model.SlackAttachment{
|
||||
model.PostPropsAttachments: []*model.SlackAttachment{
|
||||
{
|
||||
Text: strings.Repeat("本", model.PostPropsMaxUserRunes-1000),
|
||||
},
|
||||
@@ -549,7 +549,7 @@ func TestSplitWebhookPost(t *testing.T) {
|
||||
for i, split := range splits {
|
||||
if i < len(tc.Expected) {
|
||||
assert.Equal(t, tc.Expected[i].Message, split.Message)
|
||||
assert.Equal(t, tc.Expected[i].GetProp("attachments"), split.GetProp("attachments"))
|
||||
assert.Equal(t, tc.Expected[i].GetProp(model.PostPropsAttachments), split.GetProp(model.PostPropsAttachments))
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -566,7 +566,7 @@ func makePost(message int, attachments []int) *model.Post {
|
||||
}
|
||||
sa = append(sa, attach)
|
||||
}
|
||||
props = map[string]any{"attachments": sa}
|
||||
props = map[string]any{model.PostPropsAttachments: sa}
|
||||
}
|
||||
post := &model.Post{
|
||||
Message: strings.Repeat("那", message),
|
||||
@@ -628,7 +628,7 @@ func TestSplitWebhookPostAttachments(t *testing.T) {
|
||||
for i, split := range splits {
|
||||
if i < len(tc.expected) {
|
||||
assert.Equal(t, tc.expected[i].Message, split.Message, i)
|
||||
assert.Equal(t, tc.expected[i].GetProp("attachments"), split.GetProp("attachments"), i)
|
||||
assert.Equal(t, tc.expected[i].GetProp(model.PostPropsAttachments), split.GetProp(model.PostPropsAttachments), i)
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -786,17 +786,17 @@ func TestTriggerOutGoingWebhookWithUsernameAndIconURL(t *testing.T) {
|
||||
select {
|
||||
case webhookPost := <-createdPost:
|
||||
assert.Equal(t, webhookPost.Message, "sample response text from test server")
|
||||
assert.Equal(t, webhookPost.GetProp("from_webhook"), "true")
|
||||
assert.Equal(t, webhookPost.GetProp(model.PostPropsFromWebhook), "true")
|
||||
if testCase.ExpectedIconURL != "" {
|
||||
assert.Equal(t, webhookPost.GetProp("override_icon_url"), testCase.ExpectedIconURL)
|
||||
assert.Equal(t, webhookPost.GetProp(model.PostPropsOverrideIconURL), testCase.ExpectedIconURL)
|
||||
} else {
|
||||
assert.Nil(t, webhookPost.GetProp("override_icon_url"))
|
||||
assert.Nil(t, webhookPost.GetProp(model.PostPropsOverrideIconURL))
|
||||
}
|
||||
|
||||
if testCase.ExpectedUsername != "" {
|
||||
assert.Equal(t, webhookPost.GetProp("override_username"), testCase.ExpectedUsername)
|
||||
assert.Equal(t, webhookPost.GetProp(model.PostPropsOverrideUsername), testCase.ExpectedUsername)
|
||||
} else {
|
||||
assert.Nil(t, webhookPost.GetProp("override_username"))
|
||||
assert.Nil(t, webhookPost.GetProp(model.PostPropsOverrideUsername))
|
||||
}
|
||||
case <-time.After(5 * time.Second):
|
||||
require.Fail(t, "Timeout, webhook response not created as post")
|
||||
|
||||
@@ -8067,11 +8067,11 @@ func (s *RetryLayerPostStore) Overwrite(rctx request.CTX, post *model.Post) (*mo
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerPostStore) OverwriteMultiple(posts []*model.Post) ([]*model.Post, int, error) {
|
||||
func (s *RetryLayerPostStore) OverwriteMultiple(rctx request.CTX, posts []*model.Post) ([]*model.Post, int, error) {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
result, resultVar1, err := s.PostStore.OverwriteMultiple(posts)
|
||||
result, resultVar1, err := s.PostStore.OverwriteMultiple(rctx, posts)
|
||||
if err == nil {
|
||||
return result, resultVar1, nil
|
||||
}
|
||||
@@ -8235,11 +8235,11 @@ func (s *RetryLayerPostStore) Save(rctx request.CTX, post *model.Post) (*model.P
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerPostStore) SaveMultiple(posts []*model.Post) ([]*model.Post, int, error) {
|
||||
func (s *RetryLayerPostStore) SaveMultiple(rctx request.CTX, posts []*model.Post) ([]*model.Post, int, error) {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
result, resultVar1, err := s.PostStore.SaveMultiple(posts)
|
||||
result, resultVar1, err := s.PostStore.SaveMultiple(rctx, posts)
|
||||
if err == nil {
|
||||
return result, resultVar1, nil
|
||||
}
|
||||
|
||||
@@ -147,7 +147,7 @@ func newSqlPostStore(sqlStore *SqlStore, metrics einterfaces.MetricsInterface) s
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SqlPostStore) SaveMultiple(posts []*model.Post) ([]*model.Post, int, error) {
|
||||
func (s *SqlPostStore) SaveMultiple(rctx request.CTX, posts []*model.Post) ([]*model.Post, int, error) {
|
||||
channelNewPosts := make(map[string]int)
|
||||
channelNewRootPosts := make(map[string]int)
|
||||
maxDateNewPosts := make(map[string]int64)
|
||||
@@ -160,9 +160,11 @@ func (s *SqlPostStore) SaveMultiple(posts []*model.Post) ([]*model.Post, int, er
|
||||
}
|
||||
post.PreSave()
|
||||
maxPostSize := s.GetMaxPostSize()
|
||||
|
||||
if err := post.IsValid(maxPostSize); err != nil {
|
||||
return nil, idx, err
|
||||
}
|
||||
post.ValidateProps(rctx.Logger())
|
||||
|
||||
if currentChannelCount, ok := channelNewPosts[post.ChannelId]; !ok {
|
||||
if post.IsJoinLeaveMessage() {
|
||||
@@ -293,7 +295,7 @@ func (s *SqlPostStore) SaveMultiple(posts []*model.Post) ([]*model.Post, int, er
|
||||
}
|
||||
|
||||
func (s *SqlPostStore) Save(rctx request.CTX, post *model.Post) (*model.Post, error) {
|
||||
posts, _, err := s.SaveMultiple([]*model.Post{post})
|
||||
posts, _, err := s.SaveMultiple(rctx, []*model.Post{post})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -356,6 +358,7 @@ func (s *SqlPostStore) Update(rctx request.CTX, newPost *model.Post, oldPost *mo
|
||||
if err := newPost.IsValid(maxPostSize); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newPost.ValidateProps(rctx.Logger())
|
||||
|
||||
if _, err := s.GetMaster().NamedExec(`UPDATE Posts
|
||||
SET CreateAt=:CreateAt,
|
||||
@@ -409,14 +412,16 @@ func (s *SqlPostStore) Update(rctx request.CTX, newPost *model.Post, oldPost *mo
|
||||
return newPost, nil
|
||||
}
|
||||
|
||||
func (s *SqlPostStore) OverwriteMultiple(posts []*model.Post) (_ []*model.Post, _ int, err error) {
|
||||
func (s *SqlPostStore) OverwriteMultiple(rctx request.CTX, posts []*model.Post) (_ []*model.Post, _ int, err error) {
|
||||
updateAt := model.GetMillis()
|
||||
maxPostSize := s.GetMaxPostSize()
|
||||
for idx, post := range posts {
|
||||
post.UpdateAt = updateAt
|
||||
|
||||
if appErr := post.IsValid(maxPostSize); appErr != nil {
|
||||
return nil, idx, appErr
|
||||
}
|
||||
post.ValidateProps(rctx.Logger())
|
||||
}
|
||||
|
||||
tx, err := s.GetMaster().Beginx()
|
||||
@@ -464,7 +469,7 @@ func (s *SqlPostStore) OverwriteMultiple(posts []*model.Post) (_ []*model.Post,
|
||||
}
|
||||
|
||||
func (s *SqlPostStore) Overwrite(rctx request.CTX, post *model.Post) (*model.Post, error) {
|
||||
posts, _, err := s.OverwriteMultiple([]*model.Post{post})
|
||||
posts, _, err := s.OverwriteMultiple(rctx, []*model.Post{post})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -364,7 +364,7 @@ type ThreadStore interface {
|
||||
}
|
||||
|
||||
type PostStore interface {
|
||||
SaveMultiple(posts []*model.Post) ([]*model.Post, int, error)
|
||||
SaveMultiple(rctx request.CTX, posts []*model.Post) ([]*model.Post, int, error)
|
||||
Save(rctx request.CTX, post *model.Post) (*model.Post, error)
|
||||
Update(rctx request.CTX, newPost *model.Post, oldPost *model.Post) (*model.Post, error)
|
||||
Get(ctx context.Context, id string, opts model.GetPostsOptions, userID string, sanitizeOptions map[string]bool) (*model.PostList, error)
|
||||
@@ -394,7 +394,7 @@ type PostStore interface {
|
||||
InvalidateLastPostTimeCache(channelID string)
|
||||
GetPostsCreatedAt(channelID string, timestamp int64) ([]*model.Post, error)
|
||||
Overwrite(rctx request.CTX, post *model.Post) (*model.Post, error)
|
||||
OverwriteMultiple(posts []*model.Post) ([]*model.Post, int, error)
|
||||
OverwriteMultiple(rctx request.CTX, posts []*model.Post) ([]*model.Post, int, error)
|
||||
GetPostsByIds(postIds []string) ([]*model.Post, error)
|
||||
GetEditHistoryForPost(postID string) ([]*model.Post, error)
|
||||
GetPostsBatchForIndexing(startTime int64, startPostID string, limit int) ([]*model.PostForIndexing, error)
|
||||
|
||||
@@ -1037,9 +1037,9 @@ func (_m *PostStore) Overwrite(rctx request.CTX, post *model.Post) (*model.Post,
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// OverwriteMultiple provides a mock function with given fields: posts
|
||||
func (_m *PostStore) OverwriteMultiple(posts []*model.Post) ([]*model.Post, int, error) {
|
||||
ret := _m.Called(posts)
|
||||
// OverwriteMultiple provides a mock function with given fields: rctx, posts
|
||||
func (_m *PostStore) OverwriteMultiple(rctx request.CTX, posts []*model.Post) ([]*model.Post, int, error) {
|
||||
ret := _m.Called(rctx, posts)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for OverwriteMultiple")
|
||||
@@ -1048,25 +1048,25 @@ func (_m *PostStore) OverwriteMultiple(posts []*model.Post) ([]*model.Post, int,
|
||||
var r0 []*model.Post
|
||||
var r1 int
|
||||
var r2 error
|
||||
if rf, ok := ret.Get(0).(func([]*model.Post) ([]*model.Post, int, error)); ok {
|
||||
return rf(posts)
|
||||
if rf, ok := ret.Get(0).(func(request.CTX, []*model.Post) ([]*model.Post, int, error)); ok {
|
||||
return rf(rctx, posts)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func([]*model.Post) []*model.Post); ok {
|
||||
r0 = rf(posts)
|
||||
if rf, ok := ret.Get(0).(func(request.CTX, []*model.Post) []*model.Post); ok {
|
||||
r0 = rf(rctx, posts)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]*model.Post)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func([]*model.Post) int); ok {
|
||||
r1 = rf(posts)
|
||||
if rf, ok := ret.Get(1).(func(request.CTX, []*model.Post) int); ok {
|
||||
r1 = rf(rctx, posts)
|
||||
} else {
|
||||
r1 = ret.Get(1).(int)
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(2).(func([]*model.Post) error); ok {
|
||||
r2 = rf(posts)
|
||||
if rf, ok := ret.Get(2).(func(request.CTX, []*model.Post) error); ok {
|
||||
r2 = rf(rctx, posts)
|
||||
} else {
|
||||
r2 = ret.Error(2)
|
||||
}
|
||||
@@ -1239,9 +1239,9 @@ func (_m *PostStore) Save(rctx request.CTX, post *model.Post) (*model.Post, erro
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// SaveMultiple provides a mock function with given fields: posts
|
||||
func (_m *PostStore) SaveMultiple(posts []*model.Post) ([]*model.Post, int, error) {
|
||||
ret := _m.Called(posts)
|
||||
// SaveMultiple provides a mock function with given fields: rctx, posts
|
||||
func (_m *PostStore) SaveMultiple(rctx request.CTX, posts []*model.Post) ([]*model.Post, int, error) {
|
||||
ret := _m.Called(rctx, posts)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for SaveMultiple")
|
||||
@@ -1250,25 +1250,25 @@ func (_m *PostStore) SaveMultiple(posts []*model.Post) ([]*model.Post, int, erro
|
||||
var r0 []*model.Post
|
||||
var r1 int
|
||||
var r2 error
|
||||
if rf, ok := ret.Get(0).(func([]*model.Post) ([]*model.Post, int, error)); ok {
|
||||
return rf(posts)
|
||||
if rf, ok := ret.Get(0).(func(request.CTX, []*model.Post) ([]*model.Post, int, error)); ok {
|
||||
return rf(rctx, posts)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func([]*model.Post) []*model.Post); ok {
|
||||
r0 = rf(posts)
|
||||
if rf, ok := ret.Get(0).(func(request.CTX, []*model.Post) []*model.Post); ok {
|
||||
r0 = rf(rctx, posts)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]*model.Post)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func([]*model.Post) int); ok {
|
||||
r1 = rf(posts)
|
||||
if rf, ok := ret.Get(1).(func(request.CTX, []*model.Post) int); ok {
|
||||
r1 = rf(rctx, posts)
|
||||
} else {
|
||||
r1 = ret.Get(1).(int)
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(2).(func([]*model.Post) error); ok {
|
||||
r2 = rf(posts)
|
||||
if rf, ok := ret.Get(2).(func(request.CTX, []*model.Post) error); ok {
|
||||
r2 = rf(rctx, posts)
|
||||
} else {
|
||||
r2 = ret.Error(2)
|
||||
}
|
||||
|
||||
@@ -140,7 +140,7 @@ func testPostAcknowledgementsStoreGetForPosts(t *testing.T, rctx request.CTX, ss
|
||||
PersistentNotifications: model.NewPointer(false),
|
||||
},
|
||||
}
|
||||
_, errIdx, err := ss.Post().SaveMultiple([]*model.Post{&p1, &p2})
|
||||
_, errIdx, err := ss.Post().SaveMultiple(rctx, []*model.Post{&p1, &p2})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, -1, errIdx)
|
||||
|
||||
|
||||
@@ -81,7 +81,7 @@ func testPostPersistentNotificationStoreGet(t *testing.T, rctx request.CTX, ss s
|
||||
},
|
||||
}
|
||||
|
||||
_, errIdx, err := ss.Post().SaveMultiple([]*model.Post{&p1, &p2, &p3, &p4, &p5})
|
||||
_, errIdx, err := ss.Post().SaveMultiple(rctx, []*model.Post{&p1, &p2, &p3, &p4, &p5})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, -1, errIdx)
|
||||
|
||||
@@ -156,7 +156,7 @@ func testPostPersistentNotificationStoreUpdateLastSentAt(t *testing.T, rctx requ
|
||||
},
|
||||
}
|
||||
|
||||
_, errIdx, err := ss.Post().SaveMultiple([]*model.Post{&p1})
|
||||
_, errIdx, err := ss.Post().SaveMultiple(rctx, []*model.Post{&p1})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, -1, errIdx)
|
||||
|
||||
@@ -235,7 +235,7 @@ func testPostPersistentNotificationStoreDelete(t *testing.T, rctx request.CTX, s
|
||||
},
|
||||
}
|
||||
|
||||
_, errIdx, err := ss.Post().SaveMultiple([]*model.Post{&p1, &p2, &p3})
|
||||
_, errIdx, err := ss.Post().SaveMultiple(rctx, []*model.Post{&p1, &p2, &p3})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, -1, errIdx)
|
||||
|
||||
@@ -321,7 +321,7 @@ func testPostPersistentNotificationStoreDelete(t *testing.T, rctx request.CTX, s
|
||||
},
|
||||
}
|
||||
|
||||
_, errIdx, err := ss.Post().SaveMultiple([]*model.Post{&p1, &p2, &p3, &p4, &p5})
|
||||
_, errIdx, err := ss.Post().SaveMultiple(rctx, []*model.Post{&p1, &p2, &p3, &p4, &p5})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, -1, errIdx)
|
||||
|
||||
@@ -425,7 +425,7 @@ func testPostPersistentNotificationStoreDelete(t *testing.T, rctx request.CTX, s
|
||||
},
|
||||
}
|
||||
|
||||
_, errIdx, err := ss.Post().SaveMultiple([]*model.Post{&p1, &p2, &p3, &p4, &p5})
|
||||
_, errIdx, err := ss.Post().SaveMultiple(rctx, []*model.Post{&p1, &p2, &p3, &p4, &p5})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, -1, errIdx)
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ func testPostPriorityStoreGetForPost(t *testing.T, rctx request.CTX, ss store.St
|
||||
p3.UserId = model.NewId()
|
||||
p3.Message = NewTestID()
|
||||
|
||||
_, errIdx, err := ss.Post().SaveMultiple([]*model.Post{&p1, &p2, &p3})
|
||||
_, errIdx, err := ss.Post().SaveMultiple(rctx, []*model.Post{&p1, &p2, &p3})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, -1, errIdx)
|
||||
|
||||
|
||||
@@ -290,7 +290,7 @@ func testPostStoreSaveMultiple(t *testing.T, rctx request.CTX, ss store.Store) {
|
||||
p4.Message = NewTestID()
|
||||
|
||||
t.Run("Save correctly a new set of posts", func(t *testing.T) {
|
||||
newPosts, errIdx, err := ss.Post().SaveMultiple([]*model.Post{&p1, &p2, &p3})
|
||||
newPosts, errIdx, err := ss.Post().SaveMultiple(rctx, []*model.Post{&p1, &p2, &p3})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, -1, errIdx)
|
||||
for _, post := range newPosts {
|
||||
@@ -358,7 +358,7 @@ func testPostStoreSaveMultiple(t *testing.T, rctx request.CTX, ss store.Store) {
|
||||
o4.UserId = model.NewId()
|
||||
o4.Message = NewTestID()
|
||||
|
||||
newPosts, errIdx, err := ss.Post().SaveMultiple([]*model.Post{&o1, &o2, &o3, &o4})
|
||||
newPosts, errIdx, err := ss.Post().SaveMultiple(rctx, []*model.Post{&o1, &o2, &o3, &o4})
|
||||
require.NoError(t, err, "couldn't save item")
|
||||
require.Equal(t, -1, errIdx)
|
||||
assert.Len(t, newPosts, 4)
|
||||
@@ -369,7 +369,7 @@ func testPostStoreSaveMultiple(t *testing.T, rctx request.CTX, ss store.Store) {
|
||||
})
|
||||
|
||||
t.Run("Try to save mixed, already saved and not saved posts", func(t *testing.T) {
|
||||
newPosts, errIdx, err := ss.Post().SaveMultiple([]*model.Post{&p4, &p3})
|
||||
newPosts, errIdx, err := ss.Post().SaveMultiple(rctx, []*model.Post{&p4, &p3})
|
||||
require.Error(t, err)
|
||||
require.Equal(t, 1, errIdx)
|
||||
require.Nil(t, newPosts)
|
||||
@@ -405,7 +405,7 @@ func testPostStoreSaveMultiple(t *testing.T, rctx request.CTX, ss store.Store) {
|
||||
replyPost.Message = NewTestID()
|
||||
replyPost.RootId = rootPost.Id
|
||||
|
||||
_, _, err = ss.Post().SaveMultiple([]*model.Post{&rootPost, &replyPost})
|
||||
_, _, err = ss.Post().SaveMultiple(rctx, []*model.Post{&rootPost, &replyPost})
|
||||
require.NoError(t, err)
|
||||
|
||||
rrootPost, err := ss.Post().GetSingle(rctx, rootPost.Id, false)
|
||||
@@ -427,7 +427,7 @@ func testPostStoreSaveMultiple(t *testing.T, rctx request.CTX, ss store.Store) {
|
||||
// Ensure update does not occur in the same timestamp as creation
|
||||
time.Sleep(time.Millisecond)
|
||||
|
||||
_, _, err = ss.Post().SaveMultiple([]*model.Post{&replyPost2, &replyPost3})
|
||||
_, _, err = ss.Post().SaveMultiple(rctx, []*model.Post{&replyPost2, &replyPost3})
|
||||
require.NoError(t, err)
|
||||
|
||||
rrootPost2, err := ss.Post().GetSingle(rctx, rootPost.Id, false)
|
||||
@@ -460,7 +460,7 @@ func testPostStoreSaveMultiple(t *testing.T, rctx request.CTX, ss store.Store) {
|
||||
post3.UserId = model.NewId()
|
||||
post3.Message = NewTestID()
|
||||
|
||||
_, _, err = ss.Post().SaveMultiple([]*model.Post{&post1, &post2, &post3})
|
||||
_, _, err = ss.Post().SaveMultiple(rctx, []*model.Post{&post1, &post2, &post3})
|
||||
require.NoError(t, err)
|
||||
|
||||
rchannel, err := ss.Channel().Get(channel.Id, false)
|
||||
@@ -3772,7 +3772,7 @@ func testPostStoreOverwriteMultiple(t *testing.T, rctx request.CTX, ss store.Sto
|
||||
o3a := ro3.Clone()
|
||||
o3a.Message = ro3.Message + "WWWWWWW"
|
||||
|
||||
_, errIdx, err := ss.Post().OverwriteMultiple([]*model.Post{o1a, o2a, o3a})
|
||||
_, errIdx, err := ss.Post().OverwriteMultiple(rctx, []*model.Post{o1a, o2a, o3a})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, -1, errIdx)
|
||||
|
||||
@@ -3802,7 +3802,7 @@ func testPostStoreOverwriteMultiple(t *testing.T, rctx request.CTX, ss store.Sto
|
||||
o5a.Filenames = []string{}
|
||||
o5a.FileIds = []string{}
|
||||
|
||||
_, errIdx, err := ss.Post().OverwriteMultiple([]*model.Post{o4a, o5a})
|
||||
_, errIdx, err := ss.Post().OverwriteMultiple(rctx, []*model.Post{o4a, o5a})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, -1, errIdx)
|
||||
|
||||
|
||||
@@ -93,7 +93,7 @@ func testThreadStorePopulation(t *testing.T, rctx request.CTX, ss store.Store) {
|
||||
o4.UserId = model.NewId()
|
||||
o4.Message = NewTestID()
|
||||
|
||||
newPosts, errIdx, err3 := ss.Post().SaveMultiple([]*model.Post{&o2, &o3, &o4})
|
||||
newPosts, errIdx, err3 := ss.Post().SaveMultiple(rctx, []*model.Post{&o2, &o3, &o4})
|
||||
|
||||
opts := model.GetPostsOptions{
|
||||
SkipFetchThreads: true,
|
||||
@@ -135,7 +135,7 @@ func testThreadStorePopulation(t *testing.T, rctx request.CTX, ss store.Store) {
|
||||
o5.RootId = newPosts[0].Id
|
||||
o5.Message = NewTestID()
|
||||
|
||||
_, _, err = ss.Post().SaveMultiple([]*model.Post{&o5})
|
||||
_, _, err = ss.Post().SaveMultiple(rctx, []*model.Post{&o5})
|
||||
require.NoError(t, err, "couldn't save item")
|
||||
|
||||
thread, err = ss.Thread().Get(newPosts[0].Id)
|
||||
@@ -185,7 +185,7 @@ func testThreadStorePopulation(t *testing.T, rctx request.CTX, ss store.Store) {
|
||||
replyPost.Message = NewTestID()
|
||||
replyPost.RootId = rootPost.RootId
|
||||
|
||||
newPosts, _, err := ss.Post().SaveMultiple([]*model.Post{&rootPost, &replyPost})
|
||||
newPosts, _, err := ss.Post().SaveMultiple(rctx, []*model.Post{&rootPost, &replyPost})
|
||||
require.NoError(t, err)
|
||||
|
||||
thread1, err := ss.Thread().Get(newPosts[0].RootId)
|
||||
@@ -207,7 +207,7 @@ func testThreadStorePopulation(t *testing.T, rctx request.CTX, ss store.Store) {
|
||||
replyPost3.Message = NewTestID()
|
||||
replyPost3.RootId = rootPost.Id
|
||||
|
||||
_, _, err = ss.Post().SaveMultiple([]*model.Post{&replyPost2, &replyPost3})
|
||||
_, _, err = ss.Post().SaveMultiple(rctx, []*model.Post{&replyPost2, &replyPost3})
|
||||
require.NoError(t, err)
|
||||
|
||||
rrootPost2, err := ss.Post().GetSingle(rctx, rootPost.Id, false)
|
||||
@@ -295,7 +295,7 @@ func testThreadStorePopulation(t *testing.T, rctx request.CTX, ss store.Store) {
|
||||
rootPost.UserId = model.NewId()
|
||||
rootPost.Message = NewTestID()
|
||||
|
||||
newPosts1, _, err := ss.Post().SaveMultiple([]*model.Post{&rootPost})
|
||||
newPosts1, _, err := ss.Post().SaveMultiple(rctx, []*model.Post{&rootPost})
|
||||
require.NoError(t, err)
|
||||
|
||||
replyPost := model.Post{}
|
||||
@@ -304,7 +304,7 @@ func testThreadStorePopulation(t *testing.T, rctx request.CTX, ss store.Store) {
|
||||
replyPost.Message = NewTestID()
|
||||
replyPost.RootId = newPosts1[0].Id
|
||||
|
||||
_, _, err = ss.Post().SaveMultiple([]*model.Post{&replyPost})
|
||||
_, _, err = ss.Post().SaveMultiple(rctx, []*model.Post{&replyPost})
|
||||
require.NoError(t, err)
|
||||
|
||||
thread1, err := ss.Thread().Get(newPosts1[0].Id)
|
||||
|
||||
@@ -6433,10 +6433,10 @@ func (s *TimerLayerPostStore) Overwrite(rctx request.CTX, post *model.Post) (*mo
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *TimerLayerPostStore) OverwriteMultiple(posts []*model.Post) ([]*model.Post, int, error) {
|
||||
func (s *TimerLayerPostStore) OverwriteMultiple(rctx request.CTX, posts []*model.Post) ([]*model.Post, int, error) {
|
||||
start := time.Now()
|
||||
|
||||
result, resultVar1, err := s.PostStore.OverwriteMultiple(posts)
|
||||
result, resultVar1, err := s.PostStore.OverwriteMultiple(rctx, posts)
|
||||
|
||||
elapsed := float64(time.Since(start)) / float64(time.Second)
|
||||
if s.Root.Metrics != nil {
|
||||
@@ -6561,10 +6561,10 @@ func (s *TimerLayerPostStore) Save(rctx request.CTX, post *model.Post) (*model.P
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *TimerLayerPostStore) SaveMultiple(posts []*model.Post) ([]*model.Post, int, error) {
|
||||
func (s *TimerLayerPostStore) SaveMultiple(rctx request.CTX, posts []*model.Post) ([]*model.Post, int, error) {
|
||||
start := time.Now()
|
||||
|
||||
result, resultVar1, err := s.PostStore.SaveMultiple(posts)
|
||||
result, resultVar1, err := s.PostStore.SaveMultiple(rctx, posts)
|
||||
|
||||
elapsed := float64(time.Since(start)) / float64(time.Second)
|
||||
if s.Root.Metrics != nil {
|
||||
|
||||
@@ -110,7 +110,7 @@ func ESPostFromPostForIndexing(post *model.PostForIndexing) *ESPost {
|
||||
|
||||
var searchAttachments []string
|
||||
|
||||
if attachments := post.GetProp("attachments"); attachments != nil {
|
||||
if attachments := post.GetProp(model.PostPropsAttachments); attachments != nil {
|
||||
attachmentsInterfaceArray, ok := attachments.([]any)
|
||||
if ok {
|
||||
for _, attachment := range attachmentsInterfaceArray {
|
||||
|
||||
@@ -351,12 +351,12 @@ func addPostToChannelExport(rctx request.CTX, channelExport *ChannelExport, post
|
||||
if err != nil {
|
||||
rctx.Logger().Warn("Failed to unmarshal post Props into JSON. Ignoring username override.", mlog.Err(err))
|
||||
} else {
|
||||
if overrideUsername, ok := postPropsLocal["override_username"]; ok {
|
||||
if overrideUsername, ok := postPropsLocal[model.PostPropsOverrideUsername]; ok {
|
||||
postUserName = overrideUsername.(string)
|
||||
}
|
||||
|
||||
if postUserName == originalUsername {
|
||||
if overrideUsername, ok := postPropsLocal["webhook_display_name"]; ok {
|
||||
if overrideUsername, ok := postPropsLocal[model.PostPropsWebhookDisplayName]; ok {
|
||||
postUserName = overrideUsername.(string)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -392,9 +392,9 @@ func (si *SlackImporter) slackAddPosts(rctx request.CTX, teamId string, channel
|
||||
}
|
||||
|
||||
props := make(model.StringInterface)
|
||||
props["override_username"] = sPost.BotUsername
|
||||
props[model.PostPropsOverrideUsername] = sPost.BotUsername
|
||||
if len(sPost.Attachments) > 0 {
|
||||
props["attachments"] = sPost.Attachments
|
||||
props[model.PostPropsAttachments] = sPost.Attachments
|
||||
}
|
||||
|
||||
post := &model.Post{
|
||||
@@ -817,19 +817,19 @@ func (si *SlackImporter) oldImportIncomingWebhookPost(rctx request.CTX, post *mo
|
||||
linkWithTextRegex := regexp.MustCompile(`<([^<\|]+)\|([^>]+)>`)
|
||||
post.Message = linkWithTextRegex.ReplaceAllString(post.Message, "[${2}](${1})")
|
||||
|
||||
post.AddProp("from_webhook", "true")
|
||||
post.AddProp(model.PostPropsFromWebhook, "true")
|
||||
|
||||
if _, ok := props["override_username"]; !ok {
|
||||
post.AddProp("override_username", model.DefaultWebhookUsername)
|
||||
if _, ok := props[model.PostPropsOverrideUsername]; !ok {
|
||||
post.AddProp(model.PostPropsOverrideUsername, model.DefaultWebhookUsername)
|
||||
}
|
||||
|
||||
if len(props) > 0 {
|
||||
for key, val := range props {
|
||||
if key == "attachments" {
|
||||
if key == model.PostPropsAttachments {
|
||||
if attachments, success := val.([]*model.SlackAttachment); success {
|
||||
model.ParseSlackAttachment(post, attachments)
|
||||
}
|
||||
} else if key != "from_webhook" {
|
||||
} else if key != model.PostPropsFromWebhook {
|
||||
post.AddProp(key, val)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,10 +36,11 @@ func TestClient4TrimTrailingSlash(t *testing.T) {
|
||||
func TestClient4CreatePost(t *testing.T) {
|
||||
post := &model.Post{
|
||||
Props: map[string]any{
|
||||
"attachments": []*model.SlackAttachment{
|
||||
model.PostPropsAttachments: []*model.SlackAttachment{
|
||||
{
|
||||
Actions: []*model.PostAction{
|
||||
{
|
||||
Type: model.PostActionTypeButton,
|
||||
Integration: &model.PostActionIntegration{
|
||||
Context: map[string]any{
|
||||
"foo": "bar",
|
||||
@@ -63,6 +64,7 @@ func TestClient4CreatePost(t *testing.T) {
|
||||
{
|
||||
Actions: []*model.PostAction{
|
||||
{
|
||||
Type: model.PostActionTypeButton,
|
||||
Integration: &model.PostActionIntegration{
|
||||
Context: map[string]any{
|
||||
"foo": "bar",
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"math/big"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -38,13 +39,18 @@ const (
|
||||
DialogElementBoolMaxLength = 150
|
||||
)
|
||||
|
||||
var PostActionRetainPropKeys = []string{"from_webhook", "override_username", "override_icon_url"}
|
||||
var PostActionRetainPropKeys = []string{PostPropsFromWebhook, PostPropsOverrideUsername, PostPropsOverrideIconURL}
|
||||
|
||||
type DoPostActionRequest struct {
|
||||
SelectedOption string `json:"selected_option,omitempty"`
|
||||
Cookie string `json:"cookie,omitempty"`
|
||||
}
|
||||
|
||||
const (
|
||||
PostActionDataSourceUsers = "users"
|
||||
PostActionDataSourceChannels = "channels"
|
||||
)
|
||||
|
||||
type PostAction struct {
|
||||
// A unique Action ID. If not set, generated automatically.
|
||||
Id string `json:"id,omitempty"`
|
||||
@@ -85,6 +91,73 @@ type PostAction struct {
|
||||
Cookie string `json:"cookie,omitempty" db:"-"`
|
||||
}
|
||||
|
||||
// IsValid validates the action and returns an error if it is invalid.
|
||||
func (p *PostAction) IsValid() error {
|
||||
var multiErr *multierror.Error
|
||||
|
||||
if p.Name == "" {
|
||||
multiErr = multierror.Append(multiErr, fmt.Errorf("action must have a name"))
|
||||
}
|
||||
|
||||
if p.Style != "" {
|
||||
validStyles := []string{"default", "primary", "success", "good", "warning", "danger"}
|
||||
// If not a predefined style, check if it's a hex color
|
||||
if !slices.Contains(validStyles, p.Style) && !hexColorRegex.MatchString(p.Style) {
|
||||
multiErr = multierror.Append(multiErr, fmt.Errorf("invalid style '%s' - must be one of [default, primary, success, good, warning, danger] or a hex color", p.Style))
|
||||
}
|
||||
}
|
||||
|
||||
switch p.Type {
|
||||
case PostActionTypeButton:
|
||||
if len(p.Options) > 0 {
|
||||
multiErr = multierror.Append(multiErr, fmt.Errorf("button action must not have options"))
|
||||
}
|
||||
if p.DataSource != "" {
|
||||
multiErr = multierror.Append(multiErr, fmt.Errorf("button action must not have a data source"))
|
||||
}
|
||||
case PostActionTypeSelect:
|
||||
if p.DataSource != "" {
|
||||
validSources := []string{PostActionDataSourceUsers, PostActionDataSourceChannels}
|
||||
if !slices.Contains(validSources, p.DataSource) {
|
||||
multiErr = multierror.Append(multiErr, fmt.Errorf("invalid data_source '%s' for select action", p.DataSource))
|
||||
}
|
||||
|
||||
if len(p.Options) > 0 {
|
||||
multiErr = multierror.Append(multiErr, fmt.Errorf("select action cannot have both DataSource and Options set"))
|
||||
}
|
||||
} else {
|
||||
if len(p.Options) == 0 {
|
||||
multiErr = multierror.Append(multiErr, fmt.Errorf("select action must have either DataSource or Options set"))
|
||||
} else {
|
||||
for i, opt := range p.Options {
|
||||
if opt == nil {
|
||||
multiErr = multierror.Append(multiErr, fmt.Errorf("select action contains nil option"))
|
||||
continue
|
||||
}
|
||||
if err := opt.IsValid(); err != nil {
|
||||
multiErr = multierror.Append(multiErr, multierror.Prefix(err, fmt.Sprintf("option at index %d is invalid:", i)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
multiErr = multierror.Append(multiErr, fmt.Errorf("invalid action type: must be '%s' or '%s'", PostActionTypeButton, PostActionTypeSelect))
|
||||
}
|
||||
|
||||
if p.Integration == nil {
|
||||
multiErr = multierror.Append(multiErr, fmt.Errorf("action must have integration settings"))
|
||||
} else {
|
||||
if p.Integration.URL == "" {
|
||||
multiErr = multierror.Append(multiErr, fmt.Errorf("action must have an integration URL"))
|
||||
}
|
||||
if !(strings.HasPrefix(p.Integration.URL, "/plugins/") || strings.HasPrefix(p.Integration.URL, "plugins/") || IsValidHTTPURL(p.Integration.URL)) {
|
||||
multiErr = multierror.Append(multiErr, fmt.Errorf("action must have an valid integration URL"))
|
||||
}
|
||||
}
|
||||
|
||||
return multiErr.ErrorOrNil()
|
||||
}
|
||||
|
||||
func (p *PostAction) Equals(input *PostAction) bool {
|
||||
if p.Id != input.Id {
|
||||
return false
|
||||
@@ -189,7 +262,22 @@ type PostActionOptions struct {
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
func (o *PostActionOptions) IsValid() error {
|
||||
var multiErr *multierror.Error
|
||||
|
||||
if o.Text == "" {
|
||||
multiErr = multierror.Append(multiErr, fmt.Errorf("text is required"))
|
||||
}
|
||||
if o.Value == "" {
|
||||
multiErr = multierror.Append(multiErr, fmt.Errorf("value is required"))
|
||||
}
|
||||
|
||||
return multiErr.ErrorOrNil()
|
||||
}
|
||||
|
||||
type PostActionIntegration struct {
|
||||
// URL is the endpoint that the action will be sent to.
|
||||
// It can be a relative path to a plugin.
|
||||
URL string `json:"url,omitempty"`
|
||||
Context map[string]any `json:"context,omitempty"`
|
||||
}
|
||||
@@ -474,24 +562,25 @@ func isDefaultInOptions(defaultValue string, options []*PostActionOptions) bool
|
||||
return false
|
||||
}
|
||||
|
||||
func checkMaxLength(fieldName string, field string, length int) error {
|
||||
var valid bool
|
||||
func checkMaxLength(fieldName string, field string, maxLength int) error {
|
||||
// DisplayName and Name are required fields
|
||||
if fieldName == "DisplayName" || fieldName == "Name" {
|
||||
valid = len(field) > 0 && len(field) > length
|
||||
} else {
|
||||
valid = len(field) > length
|
||||
if len(field) == 0 {
|
||||
return errors.Errorf("%v cannot be empty", fieldName)
|
||||
}
|
||||
}
|
||||
if valid {
|
||||
return errors.Errorf("%v cannot be longer than %d characters", fieldName, length)
|
||||
|
||||
if len(field) > maxLength {
|
||||
return errors.Errorf("%v cannot be longer than %d characters, got %d", fieldName, maxLength, len(field))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *Post) StripActionIntegrations() {
|
||||
attachments := o.Attachments()
|
||||
if o.GetProp("attachments") != nil {
|
||||
o.AddProp("attachments", attachments)
|
||||
if o.GetProp(PostPropsAttachments) != nil {
|
||||
o.AddProp(PostPropsAttachments, attachments)
|
||||
}
|
||||
for _, attachment := range attachments {
|
||||
for _, action := range attachment.Actions {
|
||||
@@ -512,10 +601,10 @@ func (o *Post) GetAction(id string) *PostAction {
|
||||
}
|
||||
|
||||
func (o *Post) GenerateActionIds() {
|
||||
if o.GetProp("attachments") != nil {
|
||||
o.AddProp("attachments", o.Attachments())
|
||||
if o.GetProp(PostPropsAttachments) != nil {
|
||||
o.AddProp(PostPropsAttachments, o.Attachments())
|
||||
}
|
||||
if attachments, ok := o.GetProp("attachments").([]*SlackAttachment); ok {
|
||||
if attachments, ok := o.GetProp(PostPropsAttachments).([]*SlackAttachment); ok {
|
||||
for _, attachment := range attachments {
|
||||
for _, action := range attachment.Actions {
|
||||
if action != nil && action.Id == "" {
|
||||
|
||||
@@ -16,6 +16,246 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestPostAction_IsValid(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
action *PostAction
|
||||
wantErr string
|
||||
}{
|
||||
"valid button action with http URL": {
|
||||
action: &PostAction{
|
||||
Id: "validid",
|
||||
Name: "Test Button",
|
||||
Type: PostActionTypeButton,
|
||||
Integration: &PostActionIntegration{
|
||||
URL: "http://localhost:8065",
|
||||
},
|
||||
},
|
||||
wantErr: "",
|
||||
},
|
||||
"valid button action with http URL without Id": {
|
||||
action: &PostAction{
|
||||
Name: "Test Button",
|
||||
Type: PostActionTypeButton,
|
||||
Integration: &PostActionIntegration{
|
||||
URL: "http://localhost:8065",
|
||||
},
|
||||
},
|
||||
wantErr: "",
|
||||
},
|
||||
"valid button action with plugin path": {
|
||||
action: &PostAction{
|
||||
Id: "validid",
|
||||
Name: "Test Button",
|
||||
Type: PostActionTypeButton,
|
||||
Integration: &PostActionIntegration{
|
||||
URL: "/plugins/myplugin/action",
|
||||
},
|
||||
},
|
||||
wantErr: "",
|
||||
},
|
||||
"valid button action with relative plugin path": {
|
||||
action: &PostAction{
|
||||
Id: "validid",
|
||||
Name: "Test Button",
|
||||
Type: PostActionTypeButton,
|
||||
Integration: &PostActionIntegration{
|
||||
URL: "plugins/myplugin/action",
|
||||
},
|
||||
},
|
||||
wantErr: "",
|
||||
},
|
||||
"invalid integration URL": {
|
||||
action: &PostAction{
|
||||
Id: "validid",
|
||||
Name: "Test Button",
|
||||
Type: PostActionTypeButton,
|
||||
Integration: &PostActionIntegration{
|
||||
URL: "invalid-url",
|
||||
},
|
||||
},
|
||||
wantErr: "action must have an valid integration URL",
|
||||
},
|
||||
"valid select action with datasource": {
|
||||
action: &PostAction{
|
||||
Id: "validid",
|
||||
Name: "Test Select",
|
||||
Type: PostActionTypeSelect,
|
||||
DataSource: PostActionDataSourceUsers,
|
||||
Integration: &PostActionIntegration{
|
||||
URL: "http://localhost:8065",
|
||||
},
|
||||
},
|
||||
wantErr: "",
|
||||
},
|
||||
"valid select action with options": {
|
||||
action: &PostAction{
|
||||
Id: "validid",
|
||||
Name: "Test Select",
|
||||
Type: PostActionTypeSelect,
|
||||
Options: []*PostActionOptions{
|
||||
{Text: "Opt1", Value: "opt1"},
|
||||
},
|
||||
Integration: &PostActionIntegration{
|
||||
URL: "http://localhost:8065",
|
||||
},
|
||||
},
|
||||
wantErr: "",
|
||||
},
|
||||
"select action with nil option": {
|
||||
action: &PostAction{
|
||||
Id: "validid",
|
||||
Name: "Test Select",
|
||||
Type: PostActionTypeSelect,
|
||||
Options: []*PostActionOptions{
|
||||
nil,
|
||||
{Text: "Opt1", Value: "opt1"},
|
||||
},
|
||||
Integration: &PostActionIntegration{
|
||||
URL: "http://localhost:8065",
|
||||
},
|
||||
},
|
||||
wantErr: "select action contains nil option",
|
||||
},
|
||||
"missing name": {
|
||||
action: &PostAction{
|
||||
Id: "validid",
|
||||
Type: PostActionTypeButton,
|
||||
Integration: &PostActionIntegration{
|
||||
URL: "http://localhost:8065",
|
||||
},
|
||||
},
|
||||
wantErr: "action must have a name",
|
||||
},
|
||||
"invalid style": {
|
||||
action: &PostAction{
|
||||
Id: "validid",
|
||||
Name: "Test Button",
|
||||
Type: PostActionTypeButton,
|
||||
Style: "invalid",
|
||||
Integration: &PostActionIntegration{
|
||||
URL: "http://localhost:8065",
|
||||
},
|
||||
},
|
||||
wantErr: "invalid style 'invalid' - must be one of [default, primary, success, good, warning, danger] or a hex color",
|
||||
},
|
||||
"valid style": {
|
||||
action: &PostAction{
|
||||
Id: "validid",
|
||||
Name: "Test Button",
|
||||
Type: PostActionTypeButton,
|
||||
Style: "primary",
|
||||
Integration: &PostActionIntegration{
|
||||
URL: "http://localhost:8065",
|
||||
},
|
||||
},
|
||||
wantErr: "",
|
||||
},
|
||||
"button with options": {
|
||||
action: &PostAction{
|
||||
Id: "validid",
|
||||
Name: "Test Button",
|
||||
Type: PostActionTypeButton,
|
||||
Options: []*PostActionOptions{
|
||||
{Text: "Opt1", Value: "opt1"},
|
||||
},
|
||||
Integration: &PostActionIntegration{
|
||||
URL: "http://localhost:8065",
|
||||
},
|
||||
},
|
||||
wantErr: "button action must not have options",
|
||||
},
|
||||
"button with datasource": {
|
||||
action: &PostAction{
|
||||
Id: "validid",
|
||||
Name: "Test Button",
|
||||
Type: PostActionTypeButton,
|
||||
DataSource: PostActionDataSourceUsers,
|
||||
Integration: &PostActionIntegration{
|
||||
URL: "http://localhost:8065",
|
||||
},
|
||||
},
|
||||
wantErr: "button action must not have a data source",
|
||||
},
|
||||
"select without datasource or options": {
|
||||
action: &PostAction{
|
||||
Id: "validid",
|
||||
Name: "Test Select",
|
||||
Type: PostActionTypeSelect,
|
||||
Integration: &PostActionIntegration{
|
||||
URL: "http://localhost:8065",
|
||||
},
|
||||
},
|
||||
wantErr: "select action must have either DataSource or Options set",
|
||||
},
|
||||
"select with both datasource and options": {
|
||||
action: &PostAction{
|
||||
Id: "validid",
|
||||
Name: "Test Select",
|
||||
Type: PostActionTypeSelect,
|
||||
DataSource: PostActionDataSourceUsers,
|
||||
Options: []*PostActionOptions{
|
||||
{Text: "Opt1", Value: "opt1"},
|
||||
},
|
||||
Integration: &PostActionIntegration{
|
||||
URL: "http://localhost:8065",
|
||||
},
|
||||
},
|
||||
wantErr: "select action cannot have both DataSource and Options set",
|
||||
},
|
||||
"invalid datasource": {
|
||||
action: &PostAction{
|
||||
Id: "validid",
|
||||
Name: "Test Select",
|
||||
Type: PostActionTypeSelect,
|
||||
DataSource: "invalid",
|
||||
Integration: &PostActionIntegration{
|
||||
URL: "http://localhost:8065",
|
||||
},
|
||||
},
|
||||
wantErr: "invalid data_source 'invalid' for select action",
|
||||
},
|
||||
"missing integration": {
|
||||
action: &PostAction{
|
||||
Id: "validid",
|
||||
Name: "Test Button",
|
||||
Type: PostActionTypeButton,
|
||||
},
|
||||
wantErr: "action must have integration settings",
|
||||
},
|
||||
"missing integration URL": {
|
||||
action: &PostAction{
|
||||
Id: "validid",
|
||||
Name: "Test Button",
|
||||
Type: PostActionTypeButton,
|
||||
Integration: &PostActionIntegration{},
|
||||
},
|
||||
wantErr: "action must have an integration URL",
|
||||
},
|
||||
"invalid type": {
|
||||
action: &PostAction{
|
||||
Id: "validid",
|
||||
Name: "Test Action",
|
||||
Type: "invalid",
|
||||
Integration: &PostActionIntegration{
|
||||
URL: "http://localhost:8065",
|
||||
},
|
||||
},
|
||||
wantErr: "invalid action type: must be 'button' or 'select'",
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
err := tc.action.IsValid()
|
||||
if tc.wantErr == "" {
|
||||
assert.NoError(t, err, name)
|
||||
} else {
|
||||
assert.ErrorContains(t, err, tc.wantErr, name)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTriggerIdDecodeAndVerification(t *testing.T) {
|
||||
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
require.NoError(t, err)
|
||||
@@ -182,6 +422,44 @@ func TestPostActionIntegrationEquals(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestPostActionOptions_IsValid(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
options *PostActionOptions
|
||||
wantErr string
|
||||
}{
|
||||
"valid options": {
|
||||
options: &PostActionOptions{
|
||||
Text: "Option 1",
|
||||
Value: "opt1",
|
||||
},
|
||||
wantErr: "",
|
||||
},
|
||||
"missing text": {
|
||||
options: &PostActionOptions{
|
||||
Value: "opt1",
|
||||
},
|
||||
wantErr: "text is required",
|
||||
},
|
||||
"missing value": {
|
||||
options: &PostActionOptions{
|
||||
Text: "Option 1",
|
||||
},
|
||||
wantErr: "value is required",
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
err := tc.options.IsValid()
|
||||
if tc.wantErr == "" {
|
||||
assert.NoError(t, err)
|
||||
} else {
|
||||
assert.ErrorContains(t, err, tc.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestOpenDialogRequestIsValid(t *testing.T) {
|
||||
getBaseOpenDialogRequest := func() OpenDialogRequest {
|
||||
return OpenDialogRequest{
|
||||
|
||||
@@ -15,7 +15,9 @@ import (
|
||||
"sync"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/mattermost/mattermost/server/public/shared/markdown"
|
||||
"github.com/mattermost/mattermost/server/public/shared/mlog"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -72,14 +74,16 @@ const (
|
||||
PostPropsFromBot = "from_bot"
|
||||
PostPropsFromOAuthApp = "from_oauth_app"
|
||||
PostPropsWebhookDisplayName = "webhook_display_name"
|
||||
PostPropsAttachments = "attachments"
|
||||
PostPropsFromPlugin = "from_plugin"
|
||||
PostPropsMentionHighlightDisabled = "mentionHighlightDisabled"
|
||||
PostPropsGroupHighlightDisabled = "disable_group_highlight"
|
||||
PostPropsPreviewedPost = "previewed_post"
|
||||
PostPropsForceNotification = "force_notification"
|
||||
PostPropsChannelMentions = "channel_mentions"
|
||||
PostPropsUnsafeLinks = "unsafe_links"
|
||||
|
||||
PostPriorityUrgent = "urgent"
|
||||
PostPropsRequestedAck = "requested_ack"
|
||||
PostPropsPersistentNotifications = "persistent_notifications"
|
||||
PostPriorityUrgent = "urgent"
|
||||
)
|
||||
|
||||
type Post struct {
|
||||
@@ -657,6 +661,137 @@ func (o *Post) GetProp(key string) any {
|
||||
return o.Props[key]
|
||||
}
|
||||
|
||||
// ValidateProps checks all known props for validity.
|
||||
// Currently, it logs warnings for invalid props rather than returning an error.
|
||||
// In a future version, this will be updated to return errors for invalid props.
|
||||
func (o *Post) ValidateProps(logger mlog.LoggerIFace) {
|
||||
if err := o.propsIsValid(); err != nil {
|
||||
logger.Warn(
|
||||
"Invalid post props. In a future version this will result in an error. Please update your integration to be compliant.",
|
||||
mlog.String("post_id", o.Id),
|
||||
mlog.Err(err),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func (o *Post) propsIsValid() error {
|
||||
var multiErr *multierror.Error
|
||||
|
||||
props := o.GetProps()
|
||||
|
||||
// Check basic props validity
|
||||
if props == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if props[PostPropsAddedUserId] != nil {
|
||||
if addedUserID, ok := props[PostPropsAddedUserId].(string); !ok {
|
||||
multiErr = multierror.Append(multiErr, fmt.Errorf("added_user_id prop must be a string"))
|
||||
} else if !IsValidId(addedUserID) {
|
||||
multiErr = multierror.Append(multiErr, fmt.Errorf("added_user_id prop must be a valid user ID"))
|
||||
}
|
||||
}
|
||||
if props[PostPropsDeleteBy] != nil {
|
||||
if deleteByID, ok := props[PostPropsDeleteBy].(string); !ok {
|
||||
multiErr = multierror.Append(multiErr, fmt.Errorf("delete_by prop must be a string"))
|
||||
} else if !IsValidId(deleteByID) {
|
||||
multiErr = multierror.Append(multiErr, fmt.Errorf("delete_by prop must be a valid user ID"))
|
||||
}
|
||||
}
|
||||
|
||||
// Validate integration props
|
||||
if props[PostPropsOverrideIconURL] != nil {
|
||||
if iconURL, ok := props[PostPropsOverrideIconURL].(string); !ok {
|
||||
multiErr = multierror.Append(multiErr, fmt.Errorf("override_icon_url prop must be a string"))
|
||||
} else if iconURL == "" || !IsValidHTTPURL(iconURL) {
|
||||
multiErr = multierror.Append(multiErr, fmt.Errorf("override_icon_url prop must be a valid URL"))
|
||||
}
|
||||
}
|
||||
if props[PostPropsOverrideIconEmoji] != nil {
|
||||
if _, ok := props[PostPropsOverrideIconEmoji].(string); !ok {
|
||||
multiErr = multierror.Append(multiErr, fmt.Errorf("override_icon_emoji prop must be a string"))
|
||||
}
|
||||
}
|
||||
if props[PostPropsOverrideUsername] != nil {
|
||||
if _, ok := props[PostPropsOverrideUsername].(string); !ok {
|
||||
multiErr = multierror.Append(multiErr, fmt.Errorf("override_username prop must be a string"))
|
||||
}
|
||||
}
|
||||
if props[PostPropsFromWebhook] != nil {
|
||||
if fromWebhook, ok := props[PostPropsFromWebhook].(string); !ok {
|
||||
multiErr = multierror.Append(multiErr, fmt.Errorf("from_webhook prop must be a string"))
|
||||
} else if fromWebhook != "true" {
|
||||
multiErr = multierror.Append(multiErr, fmt.Errorf("from_webhook prop must be \"true\""))
|
||||
}
|
||||
}
|
||||
if props[PostPropsFromBot] != nil {
|
||||
if fromBot, ok := props[PostPropsFromBot].(string); !ok {
|
||||
multiErr = multierror.Append(multiErr, fmt.Errorf("from_bot prop must be a string"))
|
||||
} else if fromBot != "true" {
|
||||
multiErr = multierror.Append(multiErr, fmt.Errorf("from_bot prop must be \"true\""))
|
||||
}
|
||||
}
|
||||
if props[PostPropsFromOAuthApp] != nil {
|
||||
if fromOAuthApp, ok := props[PostPropsFromOAuthApp].(string); !ok {
|
||||
multiErr = multierror.Append(multiErr, fmt.Errorf("from_oauth_app prop must be a string"))
|
||||
} else if fromOAuthApp != "true" {
|
||||
multiErr = multierror.Append(multiErr, fmt.Errorf("from_oauth_app prop must be \"true\""))
|
||||
}
|
||||
}
|
||||
if props[PostPropsFromPlugin] != nil {
|
||||
if fromPlugin, ok := props[PostPropsFromPlugin].(string); !ok {
|
||||
multiErr = multierror.Append(multiErr, fmt.Errorf("from_plugin prop must be a string"))
|
||||
} else if fromPlugin != "true" {
|
||||
multiErr = multierror.Append(multiErr, fmt.Errorf("from_plugin prop must be \"true\""))
|
||||
}
|
||||
}
|
||||
if props[PostPropsUnsafeLinks] != nil {
|
||||
if unsafeLinks, ok := props[PostPropsUnsafeLinks].(string); !ok {
|
||||
multiErr = multierror.Append(multiErr, fmt.Errorf("unsafe_links prop must be a string"))
|
||||
} else if unsafeLinks != "true" {
|
||||
multiErr = multierror.Append(multiErr, fmt.Errorf("unsafe_links prop must be \"true\""))
|
||||
}
|
||||
}
|
||||
if props[PostPropsWebhookDisplayName] != nil {
|
||||
if _, ok := props[PostPropsWebhookDisplayName].(string); !ok {
|
||||
multiErr = multierror.Append(multiErr, fmt.Errorf("webhook_display_name prop must be a string"))
|
||||
}
|
||||
}
|
||||
|
||||
if props[PostPropsMentionHighlightDisabled] != nil {
|
||||
if _, ok := props[PostPropsMentionHighlightDisabled].(bool); !ok {
|
||||
multiErr = multierror.Append(multiErr, fmt.Errorf("mention_highlight_disabled prop must be a boolean"))
|
||||
}
|
||||
}
|
||||
if props[PostPropsGroupHighlightDisabled] != nil {
|
||||
if _, ok := props[PostPropsGroupHighlightDisabled].(bool); !ok {
|
||||
multiErr = multierror.Append(multiErr, fmt.Errorf("disable_group_highlight prop must be a boolean"))
|
||||
}
|
||||
}
|
||||
|
||||
if props[PostPropsPreviewedPost] != nil {
|
||||
if previewedPostID, ok := props[PostPropsPreviewedPost].(string); !ok {
|
||||
multiErr = multierror.Append(multiErr, fmt.Errorf("previewed_post prop must be a string"))
|
||||
} else if !IsValidId(previewedPostID) {
|
||||
multiErr = multierror.Append(multiErr, fmt.Errorf("previewed_post prop must be a valid post ID"))
|
||||
}
|
||||
}
|
||||
|
||||
if props[PostPropsForceNotification] != nil {
|
||||
if _, ok := props[PostPropsForceNotification].(bool); !ok {
|
||||
multiErr = multierror.Append(multiErr, fmt.Errorf("force_notification prop must be a boolean"))
|
||||
}
|
||||
}
|
||||
|
||||
for i, a := range o.Attachments() {
|
||||
if err := a.IsValid(); err != nil {
|
||||
multiErr = multierror.Append(multiErr, multierror.Prefix(err, fmt.Sprintf("message attachtment at index %d is invalid:", i)))
|
||||
}
|
||||
}
|
||||
|
||||
return multiErr.ErrorOrNil()
|
||||
}
|
||||
|
||||
func (o *Post) IsSystemMessage() bool {
|
||||
return len(o.Type) >= len(PostSystemMessagePrefix) && o.Type[:len(PostSystemMessagePrefix)] == PostSystemMessagePrefix
|
||||
}
|
||||
@@ -746,11 +881,11 @@ func findAtChannelMention(message string) (mention string, found bool) {
|
||||
}
|
||||
|
||||
func (o *Post) Attachments() []*SlackAttachment {
|
||||
if attachments, ok := o.GetProp("attachments").([]*SlackAttachment); ok {
|
||||
if attachments, ok := o.GetProp(PostPropsAttachments).([]*SlackAttachment); ok {
|
||||
return attachments
|
||||
}
|
||||
var ret []*SlackAttachment
|
||||
if attachments, ok := o.GetProp("attachments").([]any); ok {
|
||||
if attachments, ok := o.GetProp(PostPropsAttachments).([]any); ok {
|
||||
for _, attachment := range attachments {
|
||||
if enc, err := json.Marshal(attachment); err == nil {
|
||||
var decoded SlackAttachment
|
||||
@@ -885,7 +1020,7 @@ func RewriteImageURLs(message string, f func(string) string) string {
|
||||
|
||||
func (o *Post) IsFromOAuthBot() bool {
|
||||
props := o.GetProps()
|
||||
return props["from_webhook"] == "true" && props["override_username"] != ""
|
||||
return props[PostPropsFromWebhook] == "true" && props[PostPropsOverrideUsername] != ""
|
||||
}
|
||||
|
||||
func (o *Post) ToNilIfInvalid() *Post {
|
||||
|
||||
@@ -140,7 +140,7 @@ func TestPostSanitizeProps(t *testing.T) {
|
||||
Props: StringInterface{
|
||||
PropsAddChannelMember: "no good",
|
||||
PostPropsForceNotification: "no good",
|
||||
"attachments": "good",
|
||||
PostPropsAttachments: "good",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -149,7 +149,7 @@ func TestPostSanitizeProps(t *testing.T) {
|
||||
require.Nil(t, post3.GetProp(PropsAddChannelMember))
|
||||
require.Nil(t, post3.GetProp(PostPropsForceNotification))
|
||||
|
||||
require.NotNil(t, post3.GetProp("attachments"))
|
||||
require.NotNil(t, post3.GetProp(PostPropsAttachments))
|
||||
}
|
||||
|
||||
func TestPost_ContainsIntegrationsReservedProps(t *testing.T) {
|
||||
@@ -162,11 +162,11 @@ func TestPost_ContainsIntegrationsReservedProps(t *testing.T) {
|
||||
post2 := &Post{
|
||||
Message: "test",
|
||||
Props: StringInterface{
|
||||
"from_webhook": "true",
|
||||
"webhook_display_name": "overridden_display_name",
|
||||
"override_username": "overridden_username",
|
||||
"override_icon_url": "a-custom-url",
|
||||
"override_icon_emoji": ":custom_emoji_name:",
|
||||
PostPropsFromWebhook: "true",
|
||||
PostPropsWebhookDisplayName: "overridden_display_name",
|
||||
PostPropsOverrideUsername: "overridden_username",
|
||||
PostPropsOverrideIconURL: "a-custom-url",
|
||||
PostPropsOverrideIconEmoji: ":custom_emoji_name:",
|
||||
},
|
||||
}
|
||||
keys2 := post2.ContainsIntegrationsReservedProps()
|
||||
@@ -176,7 +176,7 @@ func TestPost_ContainsIntegrationsReservedProps(t *testing.T) {
|
||||
func TestPostPatch_ContainsIntegrationsReservedProps(t *testing.T) {
|
||||
postPatch1 := &PostPatch{
|
||||
Props: &StringInterface{
|
||||
"from_webhook": "true",
|
||||
PostPropsFromWebhook: "true",
|
||||
},
|
||||
}
|
||||
keys1 := postPatch1.ContainsIntegrationsReservedProps()
|
||||
@@ -405,8 +405,8 @@ func TestPost_AttachmentsEqual(t *testing.T) {
|
||||
},
|
||||
} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
post1.AddProp("attachments", tc.Attachments1)
|
||||
post2.AddProp("attachments", tc.Attachments2)
|
||||
post1.AddProp(PostPropsAttachments, tc.Attachments1)
|
||||
post2.AddProp(PostPropsAttachments, tc.Attachments2)
|
||||
assert.Equal(t, tc.Expected, post1.AttachmentsEqual(post2))
|
||||
})
|
||||
}
|
||||
@@ -895,7 +895,7 @@ func TestPostPatchDisableMentionHighlights(t *testing.T) {
|
||||
func TestPostAttachments(t *testing.T) {
|
||||
p := &Post{
|
||||
Props: map[string]any{
|
||||
"attachments": []byte(`[{
|
||||
PostPropsAttachments: []byte(`[{
|
||||
"actions" : {null}
|
||||
}]
|
||||
`),
|
||||
@@ -903,7 +903,7 @@ func TestPostAttachments(t *testing.T) {
|
||||
}
|
||||
|
||||
t.Run("empty actions", func(t *testing.T) {
|
||||
p.Props["attachments"] = []any{
|
||||
p.Props[PostPropsAttachments] = []any{
|
||||
map[string]any{"actions": []any{}},
|
||||
}
|
||||
attachments := p.Attachments()
|
||||
@@ -911,7 +911,7 @@ func TestPostAttachments(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("a couple of actions", func(t *testing.T) {
|
||||
p.Props["attachments"] = []any{
|
||||
p.Props[PostPropsAttachments] = []any{
|
||||
map[string]any{"actions": []any{
|
||||
map[string]any{"id": "test1"}, map[string]any{"id": "test2"}},
|
||||
},
|
||||
@@ -924,7 +924,7 @@ func TestPostAttachments(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("should ignore null actions", func(t *testing.T) {
|
||||
p.Props["attachments"] = []any{
|
||||
p.Props[PostPropsAttachments] = []any{
|
||||
map[string]any{"actions": []any{
|
||||
map[string]any{"id": "test1"}, nil, map[string]any{"id": "test2"}, nil, nil},
|
||||
},
|
||||
@@ -937,7 +937,7 @@ func TestPostAttachments(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("nil fields", func(t *testing.T) {
|
||||
p.Props["attachments"] = []any{
|
||||
p.Props[PostPropsAttachments] = []any{
|
||||
map[string]any{"fields": []any{
|
||||
map[string]any{"value": ":emoji1:"},
|
||||
nil,
|
||||
@@ -995,3 +995,278 @@ func TestPostPriority(t *testing.T) {
|
||||
p.Metadata.Priority.Priority = NewPointer(PostPriorityUrgent)
|
||||
require.True(t, p.IsUrgent())
|
||||
}
|
||||
|
||||
func TestPost_PropsIsValid(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
props StringInterface
|
||||
wantErr string
|
||||
}{
|
||||
"valid empty props": {
|
||||
props: nil,
|
||||
wantErr: "",
|
||||
},
|
||||
"valid props": {
|
||||
props: StringInterface{
|
||||
"key": "value",
|
||||
},
|
||||
wantErr: "",
|
||||
},
|
||||
"valid added_user_id": {
|
||||
props: StringInterface{
|
||||
PostPropsAddedUserId: NewId(),
|
||||
},
|
||||
wantErr: "",
|
||||
},
|
||||
"valid delete_by": {
|
||||
props: StringInterface{
|
||||
PostPropsDeleteBy: NewId(),
|
||||
},
|
||||
wantErr: "",
|
||||
},
|
||||
"valid override_icon_url": {
|
||||
props: StringInterface{
|
||||
PostPropsOverrideIconURL: "https://example.com/icon.png",
|
||||
},
|
||||
wantErr: "",
|
||||
},
|
||||
"valid override_icon_emoji": {
|
||||
props: StringInterface{
|
||||
PostPropsOverrideIconEmoji: ":smile:",
|
||||
},
|
||||
wantErr: "",
|
||||
},
|
||||
"valid override_username": {
|
||||
props: StringInterface{
|
||||
PostPropsOverrideUsername: "testuser",
|
||||
},
|
||||
wantErr: "",
|
||||
},
|
||||
"valid from_webhook": {
|
||||
props: StringInterface{
|
||||
PostPropsFromWebhook: "true",
|
||||
},
|
||||
wantErr: "",
|
||||
},
|
||||
"valid from_bot": {
|
||||
props: StringInterface{
|
||||
PostPropsFromBot: "true",
|
||||
},
|
||||
wantErr: "",
|
||||
},
|
||||
"valid from_oauth_app": {
|
||||
props: StringInterface{
|
||||
PostPropsFromOAuthApp: "true",
|
||||
},
|
||||
wantErr: "",
|
||||
},
|
||||
"valid from_plugin": {
|
||||
props: StringInterface{
|
||||
PostPropsFromPlugin: "true",
|
||||
},
|
||||
wantErr: "",
|
||||
},
|
||||
"valid unsafe_links": {
|
||||
props: StringInterface{
|
||||
PostPropsUnsafeLinks: "true",
|
||||
},
|
||||
wantErr: "",
|
||||
},
|
||||
"valid webhook_display_name": {
|
||||
props: StringInterface{
|
||||
PostPropsWebhookDisplayName: "My Webhook",
|
||||
},
|
||||
wantErr: "",
|
||||
},
|
||||
"valid mention_highlight_disabled": {
|
||||
props: StringInterface{
|
||||
PostPropsMentionHighlightDisabled: true,
|
||||
},
|
||||
wantErr: "",
|
||||
},
|
||||
"valid disable_group_highlight": {
|
||||
props: StringInterface{
|
||||
PostPropsGroupHighlightDisabled: true,
|
||||
},
|
||||
wantErr: "",
|
||||
},
|
||||
"valid previewed_post": {
|
||||
props: StringInterface{
|
||||
PostPropsPreviewedPost: NewId(),
|
||||
},
|
||||
wantErr: "",
|
||||
},
|
||||
"valid force_notification": {
|
||||
props: StringInterface{
|
||||
PostPropsForceNotification: true,
|
||||
},
|
||||
wantErr: "",
|
||||
},
|
||||
"valid multiple props": {
|
||||
props: StringInterface{
|
||||
PostPropsFromWebhook: "true",
|
||||
PostPropsOverrideUsername: "webhook-user",
|
||||
PostPropsOverrideIconURL: "https://example.com/icon.png",
|
||||
PostPropsWebhookDisplayName: "My Webhook",
|
||||
PostPropsMentionHighlightDisabled: true,
|
||||
},
|
||||
wantErr: "",
|
||||
},
|
||||
"invalid added_user_id type": {
|
||||
props: StringInterface{
|
||||
PostPropsAddedUserId: 123,
|
||||
},
|
||||
wantErr: "added_user_id prop must be a string",
|
||||
},
|
||||
"invalid added_user_id value": {
|
||||
props: StringInterface{
|
||||
PostPropsAddedUserId: "invalid-id",
|
||||
},
|
||||
wantErr: "added_user_id prop must be a valid user ID",
|
||||
},
|
||||
"invalid delete_by type": {
|
||||
props: StringInterface{
|
||||
PostPropsDeleteBy: 123,
|
||||
},
|
||||
wantErr: "delete_by prop must be a string",
|
||||
},
|
||||
"invalid delete_by value": {
|
||||
props: StringInterface{
|
||||
PostPropsDeleteBy: "invalid-id",
|
||||
},
|
||||
wantErr: "delete_by prop must be a valid user ID",
|
||||
},
|
||||
"invalid override_icon_url type": {
|
||||
props: StringInterface{
|
||||
PostPropsOverrideIconURL: 123,
|
||||
},
|
||||
wantErr: "override_icon_url prop must be a string",
|
||||
},
|
||||
"invalid override_icon_url value": {
|
||||
props: StringInterface{
|
||||
PostPropsOverrideIconURL: "not-a-url",
|
||||
},
|
||||
wantErr: "override_icon_url prop must be a valid URL",
|
||||
},
|
||||
"invalid override_icon_emoji type": {
|
||||
props: StringInterface{
|
||||
PostPropsOverrideIconEmoji: 123,
|
||||
},
|
||||
wantErr: "override_icon_emoji prop must be a string",
|
||||
},
|
||||
"invalid override_username type": {
|
||||
props: StringInterface{
|
||||
PostPropsOverrideUsername: 123,
|
||||
},
|
||||
wantErr: "override_username prop must be a string",
|
||||
},
|
||||
"invalid from_webhook type": {
|
||||
props: StringInterface{
|
||||
PostPropsFromWebhook: 123,
|
||||
},
|
||||
wantErr: "from_webhook prop must be a string",
|
||||
},
|
||||
"invalid from_webhook value": {
|
||||
props: StringInterface{
|
||||
PostPropsFromWebhook: "false",
|
||||
},
|
||||
wantErr: "from_webhook prop must be \"true\"",
|
||||
},
|
||||
"invalid from_bot type": {
|
||||
props: StringInterface{
|
||||
PostPropsFromBot: 123,
|
||||
},
|
||||
wantErr: "from_bot prop must be a string",
|
||||
},
|
||||
"invalid from_bot value": {
|
||||
props: StringInterface{
|
||||
PostPropsFromBot: "false",
|
||||
},
|
||||
wantErr: "from_bot prop must be \"true\"",
|
||||
},
|
||||
"invalid from_oauth_app type": {
|
||||
props: StringInterface{
|
||||
PostPropsFromOAuthApp: 123,
|
||||
},
|
||||
wantErr: "from_oauth_app prop must be a string",
|
||||
},
|
||||
"invalid from_oauth_app value": {
|
||||
props: StringInterface{
|
||||
PostPropsFromOAuthApp: "false",
|
||||
},
|
||||
wantErr: "from_oauth_app prop must be \"true\"",
|
||||
},
|
||||
"invalid from_plugin type": {
|
||||
props: StringInterface{
|
||||
PostPropsFromPlugin: 123,
|
||||
},
|
||||
wantErr: "from_plugin prop must be a string",
|
||||
},
|
||||
"invalid from_plugin value": {
|
||||
props: StringInterface{
|
||||
PostPropsFromPlugin: "false",
|
||||
},
|
||||
wantErr: "from_plugin prop must be \"true\"",
|
||||
},
|
||||
"invalid unsafe_links type": {
|
||||
props: StringInterface{
|
||||
PostPropsUnsafeLinks: 123,
|
||||
},
|
||||
wantErr: "unsafe_links prop must be a string",
|
||||
},
|
||||
"invalid unsafe_links value": {
|
||||
props: StringInterface{
|
||||
PostPropsUnsafeLinks: "false",
|
||||
},
|
||||
wantErr: "unsafe_links prop must be \"true\"",
|
||||
},
|
||||
"invalid webhook_display_name type": {
|
||||
props: StringInterface{
|
||||
PostPropsWebhookDisplayName: 123,
|
||||
},
|
||||
wantErr: "webhook_display_name prop must be a string",
|
||||
},
|
||||
"invalid mention_highlight_disabled type": {
|
||||
props: StringInterface{
|
||||
PostPropsMentionHighlightDisabled: "true",
|
||||
},
|
||||
wantErr: "mention_highlight_disabled prop must be a boolean",
|
||||
},
|
||||
"invalid disable_group_highlight type": {
|
||||
props: StringInterface{
|
||||
PostPropsGroupHighlightDisabled: "true",
|
||||
},
|
||||
wantErr: "disable_group_highlight prop must be a boolean",
|
||||
},
|
||||
"invalid previewed_post type": {
|
||||
props: StringInterface{
|
||||
PostPropsPreviewedPost: 123,
|
||||
},
|
||||
wantErr: "previewed_post prop must be a string",
|
||||
},
|
||||
"invalid previewed_post value": {
|
||||
props: StringInterface{
|
||||
PostPropsPreviewedPost: "invalid-id",
|
||||
},
|
||||
wantErr: "previewed_post prop must be a valid post ID",
|
||||
},
|
||||
"invalid force_notification type": {
|
||||
props: StringInterface{
|
||||
PostPropsForceNotification: "true",
|
||||
},
|
||||
wantErr: "force_notification prop must be a boolean",
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
p := &Post{}
|
||||
p.SetProps(tc.props)
|
||||
err := p.propsIsValid()
|
||||
if tc.wantErr == "" {
|
||||
assert.NoError(t, err)
|
||||
} else {
|
||||
assert.ErrorContains(t, err, tc.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,9 +6,15 @@ package model
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"slices"
|
||||
|
||||
"github.com/hashicorp/go-multierror"
|
||||
)
|
||||
|
||||
var linkWithTextRegex = regexp.MustCompile(`<([^<\|]+)\|([^>]+)>`)
|
||||
var (
|
||||
linkWithTextRegex = regexp.MustCompile(`<([^<\|]+)\|([^>]+)>`)
|
||||
hexColorRegex = regexp.MustCompile(`^#[0-9a-fA-F]{6}$`)
|
||||
)
|
||||
|
||||
type SlackAttachment struct {
|
||||
Id int64 `json:"id"`
|
||||
@@ -30,6 +36,78 @@ type SlackAttachment struct {
|
||||
Actions []*PostAction `json:"actions,omitempty"`
|
||||
}
|
||||
|
||||
func (s *SlackAttachment) IsValid() error {
|
||||
var multiErr *multierror.Error
|
||||
|
||||
if s.Color != "" {
|
||||
validStyles := []string{"good", "warning", "danger"}
|
||||
// If not a predefined style, check if it's a hex color
|
||||
if !slices.Contains(validStyles, s.Color) && !hexColorRegex.MatchString(s.Color) {
|
||||
multiErr = multierror.Append(multiErr, fmt.Errorf("invalid style '%s' - must be one of [good, warning, danger] or a hex color", s.Color))
|
||||
}
|
||||
}
|
||||
|
||||
if s.AuthorLink != "" {
|
||||
if s.AuthorName == "" {
|
||||
multiErr = multierror.Append(multiErr, fmt.Errorf("author link cannot be set without author name"))
|
||||
}
|
||||
|
||||
if !IsValidHTTPURL(s.AuthorLink) {
|
||||
multiErr = multierror.Append(multiErr, fmt.Errorf("invalid author link URL"))
|
||||
}
|
||||
}
|
||||
|
||||
if s.AuthorIcon != "" && !IsValidHTTPURL(s.AuthorIcon) {
|
||||
multiErr = multierror.Append(multiErr, fmt.Errorf("invalid author icon URL"))
|
||||
}
|
||||
|
||||
if s.TitleLink != "" {
|
||||
if s.Title == "" {
|
||||
multiErr = multierror.Append(multiErr, fmt.Errorf("title link cannot be set without title"))
|
||||
}
|
||||
|
||||
if !IsValidHTTPURL(s.TitleLink) {
|
||||
multiErr = multierror.Append(multiErr, fmt.Errorf("invalid title link URL"))
|
||||
}
|
||||
}
|
||||
|
||||
for _, field := range s.Fields {
|
||||
if err := field.IsValid(); err != nil {
|
||||
multiErr = multierror.Append(multiErr, err)
|
||||
}
|
||||
}
|
||||
|
||||
if s.ImageURL != "" && !IsValidHTTPURL(s.ImageURL) {
|
||||
multiErr = multierror.Append(multiErr, fmt.Errorf("invalid image URL"))
|
||||
}
|
||||
|
||||
if s.ThumbURL != "" && !IsValidHTTPURL(s.ThumbURL) {
|
||||
multiErr = multierror.Append(multiErr, fmt.Errorf("invalid thumb URL"))
|
||||
}
|
||||
|
||||
if s.FooterIcon != "" && !IsValidHTTPURL(s.FooterIcon) {
|
||||
multiErr = multierror.Append(multiErr, fmt.Errorf("invalid footer icon URL"))
|
||||
}
|
||||
|
||||
// Validate timestamp is either string or int64
|
||||
if s.Timestamp != nil {
|
||||
switch s.Timestamp.(type) {
|
||||
case string, int64:
|
||||
// Valid types
|
||||
default:
|
||||
multiErr = multierror.Append(multiErr, fmt.Errorf("timestamp must be either a string or int64"))
|
||||
}
|
||||
}
|
||||
|
||||
for i, action := range s.Actions {
|
||||
if err := action.IsValid(); err != nil {
|
||||
multiErr = multierror.Append(multiErr, multierror.Prefix(err, fmt.Sprintf("action at index %d is invalid:", i)))
|
||||
}
|
||||
}
|
||||
|
||||
return multiErr.ErrorOrNil()
|
||||
}
|
||||
|
||||
func (s *SlackAttachment) Equals(input *SlackAttachment) bool {
|
||||
// Direct comparison of simple types
|
||||
|
||||
@@ -120,6 +198,21 @@ type SlackAttachmentField struct {
|
||||
Short SlackCompatibleBool `json:"short"`
|
||||
}
|
||||
|
||||
func (s *SlackAttachmentField) IsValid() error {
|
||||
var multiErr *multierror.Error
|
||||
|
||||
if s.Value != nil {
|
||||
switch s.Value.(type) {
|
||||
case string, int:
|
||||
// Valid types
|
||||
default:
|
||||
multiErr = multierror.Append(multiErr, fmt.Errorf("value must be either a string or int"))
|
||||
}
|
||||
}
|
||||
|
||||
return multiErr.ErrorOrNil()
|
||||
}
|
||||
|
||||
func (s *SlackAttachmentField) Equals(input *SlackAttachmentField) bool {
|
||||
if s.Title != input.Title {
|
||||
return false
|
||||
@@ -188,7 +281,7 @@ func ParseSlackAttachment(post *Post, attachments []*SlackAttachment) {
|
||||
}
|
||||
postAttachments = append(postAttachments, attachment)
|
||||
}
|
||||
post.AddProp("attachments", postAttachments)
|
||||
post.AddProp(PostPropsAttachments, postAttachments)
|
||||
}
|
||||
|
||||
func ParseSlackLinksToMarkdown(text string) string {
|
||||
|
||||
@@ -9,6 +9,165 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestSlackAttachment_IsValid(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
attachment *SlackAttachment
|
||||
wantErr string
|
||||
}{
|
||||
"valid attachment": {
|
||||
attachment: &SlackAttachment{
|
||||
Text: "This is a test",
|
||||
},
|
||||
wantErr: "",
|
||||
},
|
||||
"invalid color": {
|
||||
attachment: &SlackAttachment{
|
||||
Color: "invalid",
|
||||
},
|
||||
wantErr: "invalid style 'invalid' - must be one of [good, warning, danger] or a hex color",
|
||||
},
|
||||
"valid predefined color": {
|
||||
attachment: &SlackAttachment{
|
||||
Color: "good",
|
||||
},
|
||||
wantErr: "",
|
||||
},
|
||||
"valid warning color": {
|
||||
attachment: &SlackAttachment{
|
||||
Color: "warning",
|
||||
},
|
||||
wantErr: "",
|
||||
},
|
||||
"valid danger color": {
|
||||
attachment: &SlackAttachment{
|
||||
Color: "danger",
|
||||
},
|
||||
wantErr: "",
|
||||
},
|
||||
"valid hex color": {
|
||||
attachment: &SlackAttachment{
|
||||
Color: "#FF0000",
|
||||
},
|
||||
wantErr: "",
|
||||
},
|
||||
"author link without name": {
|
||||
attachment: &SlackAttachment{
|
||||
AuthorLink: "http://example.com",
|
||||
},
|
||||
wantErr: "author link cannot be set without author name",
|
||||
},
|
||||
"invalid author link": {
|
||||
attachment: &SlackAttachment{
|
||||
AuthorName: "Author",
|
||||
AuthorLink: "invalid-url",
|
||||
},
|
||||
wantErr: "invalid author link URL",
|
||||
},
|
||||
"invalid author icon": {
|
||||
attachment: &SlackAttachment{
|
||||
AuthorIcon: "invalid-url",
|
||||
},
|
||||
wantErr: "invalid author icon URL",
|
||||
},
|
||||
"title link without title": {
|
||||
attachment: &SlackAttachment{
|
||||
TitleLink: "http://example.com",
|
||||
},
|
||||
wantErr: "title link cannot be set without title",
|
||||
},
|
||||
"invalid title link": {
|
||||
attachment: &SlackAttachment{
|
||||
Title: "Title",
|
||||
TitleLink: "invalid-url",
|
||||
},
|
||||
wantErr: "invalid title link URL",
|
||||
},
|
||||
"invalid image URL": {
|
||||
attachment: &SlackAttachment{
|
||||
ImageURL: "invalid-url",
|
||||
},
|
||||
wantErr: "invalid image URL",
|
||||
},
|
||||
"invalid thumb URL": {
|
||||
attachment: &SlackAttachment{
|
||||
ThumbURL: "invalid-url",
|
||||
},
|
||||
wantErr: "invalid thumb URL",
|
||||
},
|
||||
"invalid footer icon": {
|
||||
attachment: &SlackAttachment{
|
||||
FooterIcon: "invalid-url",
|
||||
},
|
||||
wantErr: "invalid footer icon URL",
|
||||
},
|
||||
"invalid timestamp type": {
|
||||
attachment: &SlackAttachment{
|
||||
Timestamp: []string{"invalid"},
|
||||
},
|
||||
wantErr: "timestamp must be either a string or int64",
|
||||
},
|
||||
"valid timestamp string": {
|
||||
attachment: &SlackAttachment{
|
||||
Timestamp: "1234567890",
|
||||
},
|
||||
wantErr: "",
|
||||
},
|
||||
"valid timestamp int64": {
|
||||
attachment: &SlackAttachment{
|
||||
Timestamp: int64(1234567890),
|
||||
},
|
||||
wantErr: "",
|
||||
},
|
||||
"invalid action": {
|
||||
attachment: &SlackAttachment{
|
||||
Actions: []*PostAction{
|
||||
{
|
||||
Name: "", // Invalid - missing name
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: "action must have a name",
|
||||
},
|
||||
"invalid field value type": {
|
||||
attachment: &SlackAttachment{
|
||||
Fields: []*SlackAttachmentField{
|
||||
{
|
||||
Title: "Title",
|
||||
Value: []string{"invalid"},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: "value must be either a string or int",
|
||||
},
|
||||
"valid fields": {
|
||||
attachment: &SlackAttachment{
|
||||
Fields: []*SlackAttachmentField{
|
||||
{
|
||||
Title: "Title",
|
||||
Value: "string value",
|
||||
},
|
||||
{
|
||||
Title: "Number",
|
||||
Value: 42,
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: "",
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
err := tc.attachment.IsValid()
|
||||
if tc.wantErr == "" {
|
||||
assert.NoError(t, err, name)
|
||||
} else {
|
||||
assert.ErrorContains(t, err, tc.wantErr, name)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseSlackAttachment(t *testing.T) {
|
||||
t.Run("empty list", func(t *testing.T) {
|
||||
post := &Post{}
|
||||
@@ -19,7 +178,7 @@ func TestParseSlackAttachment(t *testing.T) {
|
||||
expectedPost := &Post{
|
||||
Type: PostTypeSlackAttachment,
|
||||
Props: map[string]any{
|
||||
"attachments": []*SlackAttachment{},
|
||||
PostPropsAttachments: []*SlackAttachment{},
|
||||
},
|
||||
}
|
||||
assert.Equal(t, expectedPost, post)
|
||||
@@ -36,7 +195,7 @@ func TestParseSlackAttachment(t *testing.T) {
|
||||
expectedPost := &Post{
|
||||
Type: PostTypeSlackAttachment,
|
||||
Props: map[string]any{
|
||||
"attachments": []*SlackAttachment{},
|
||||
PostPropsAttachments: []*SlackAttachment{},
|
||||
},
|
||||
}
|
||||
assert.Equal(t, expectedPost, post)
|
||||
|
||||
@@ -106,7 +106,7 @@ func TestDMWithAttachments(t *testing.T) {
|
||||
ChannelId: dmChannelID,
|
||||
Type: model.PostTypeSlackAttachment,
|
||||
Props: model.StringInterface{
|
||||
"attachments": attachments,
|
||||
model.PostPropsAttachments: attachments,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -196,7 +196,7 @@ func (f *Flow) handle(
|
||||
}
|
||||
|
||||
func (f *Flow) processButtonPostActions(post *model.Post) {
|
||||
attachments, ok := post.GetProp("attachments").([]*model.SlackAttachment)
|
||||
attachments, ok := post.GetProp(model.PostPropsAttachments).([]*model.SlackAttachment)
|
||||
if !ok || len(attachments) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -145,7 +145,7 @@ func (s Step) render(f *Flow, done bool, selectedButton int) (*model.Post, bool,
|
||||
|
||||
buttons := processButtons(s.buttons, f.state.AppState)
|
||||
|
||||
attachments, ok := post.GetProp("attachments").([]*model.SlackAttachment)
|
||||
attachments, ok := post.GetProp(model.PostPropsAttachments).([]*model.SlackAttachment)
|
||||
if !ok || len(attachments) != 1 {
|
||||
return nil, false, errors.New("expected 1 slack attachment")
|
||||
}
|
||||
@@ -224,6 +224,7 @@ func processDialog(in *model.Dialog, state State) model.Dialog {
|
||||
|
||||
func renderButton(b Button, stepName Name, i int, state State) *model.PostAction {
|
||||
return &model.PostAction{
|
||||
Type: model.PostActionTypeButton,
|
||||
Name: formatState(b.Name, state),
|
||||
Disabled: b.Disabled,
|
||||
Style: string(b.Color),
|
||||
|
||||
@@ -75,6 +75,7 @@ func (s *boolSetting) GetSlackAttachments(userID, settingHandler string, disable
|
||||
currentValueMessage = fmt.Sprintf("Current value: %s", currentTextValue)
|
||||
|
||||
actionTrue := model.PostAction{
|
||||
Type: model.PostActionTypeButton,
|
||||
Name: "Yes",
|
||||
Integration: &model.PostActionIntegration{
|
||||
URL: settingHandler,
|
||||
@@ -86,6 +87,7 @@ func (s *boolSetting) GetSlackAttachments(userID, settingHandler string, disable
|
||||
}
|
||||
|
||||
actionFalse := model.PostAction{
|
||||
Type: model.PostActionTypeButton,
|
||||
Name: "No",
|
||||
Integration: &model.PostActionIntegration{
|
||||
URL: settingHandler,
|
||||
|
||||
@@ -62,6 +62,7 @@ func (s *optionSetting) GetSlackAttachments(userID, settingHandler string, disab
|
||||
currentValueMessage = fmt.Sprintf("Current value: %s", currentTextValue)
|
||||
|
||||
actionOptions := model.PostAction{
|
||||
Type: model.PostActionTypeSelect,
|
||||
Name: "Select an option:",
|
||||
Integration: &model.PostActionIntegration{
|
||||
URL: settingHandler + "?" + s.id + "=true",
|
||||
@@ -69,7 +70,6 @@ func (s *optionSetting) GetSlackAttachments(userID, settingHandler string, disab
|
||||
ContextIDKey: s.id,
|
||||
},
|
||||
},
|
||||
Type: "select",
|
||||
Options: stringsToOptions(s.options),
|
||||
}
|
||||
|
||||
|
||||
@@ -297,7 +297,7 @@ func (p *PostService) ShouldProcessMessage(post *model.Post, options ...ShouldPr
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if !messageProcessOptions.AllowWebhook && post.GetProp("from_webhook") == "true" {
|
||||
if !messageProcessOptions.AllowWebhook && post.GetProp(model.PostPropsFromWebhook) == "true" {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -660,7 +660,7 @@ func TestShouldProcessMessage(t *testing.T) {
|
||||
|
||||
client := pluginapi.NewClient(api, &plugintest.Driver{})
|
||||
shouldProcessMessage, err := client.Post.ShouldProcessMessage(
|
||||
&model.Post{ChannelId: channelID, Props: model.StringInterface{"from_webhook": "true"}},
|
||||
&model.Post{ChannelId: channelID, Props: model.StringInterface{model.PostPropsFromWebhook: "true"}},
|
||||
pluginapi.AllowBots(),
|
||||
)
|
||||
|
||||
@@ -677,7 +677,7 @@ func TestShouldProcessMessage(t *testing.T) {
|
||||
client := pluginapi.NewClient(api, &plugintest.Driver{})
|
||||
|
||||
shouldProcessMessage, err := client.Post.ShouldProcessMessage(
|
||||
&model.Post{ChannelId: channelID, Props: model.StringInterface{"from_webhook": "true"}},
|
||||
&model.Post{ChannelId: channelID, Props: model.StringInterface{model.PostPropsFromWebhook: "true"}},
|
||||
pluginapi.AllowBots(),
|
||||
pluginapi.AllowWebhook(),
|
||||
)
|
||||
@@ -712,7 +712,7 @@ func TestShouldProcessMessage(t *testing.T) {
|
||||
client := pluginapi.NewClient(api, &plugintest.Driver{})
|
||||
|
||||
shouldProcessMessage, err := client.Post.ShouldProcessMessage(
|
||||
&model.Post{ChannelId: channelID, Props: model.StringInterface{"from_webhook": "false"}},
|
||||
&model.Post{ChannelId: channelID, Props: model.StringInterface{model.PostPropsFromWebhook: "false"}},
|
||||
pluginapi.AllowBots(),
|
||||
)
|
||||
|
||||
|
||||
Ссылка в новой задаче
Block a user