snapshot of https://github.com/mattermost/mattermost.git (master, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Server CI / Postgres (shard 0) (push) Has been skipped
Server CI / Postgres (shard 3) (push) Has been skipped
BuildEnv Docker Image / build-image (push) Failing after 4s
BuildEnv Docker Image / build-image-fips (push) Failing after 4s
Migration Review & Release Notes / Generate migration review & release notes (push) Failing after 3s
Server CI / Build Environment (push) Failing after 3s
Server CI / Postgres (shard 1) (push) Has been skipped
Tools CI / check-style (mattermost-govet) (push) Failing after 4s
Server CI / Postgres (shard 2) (push) Has been skipped
Tools CI / check-style (mmgotool) (push) Failing after 3s
Server CI / Elasticsearch v8 Compatibility (push) Has been skipped
Server CI / OpenSearch v2 Compatibility (push) Has been skipped
Server CI / Postgres FIPS (shard 0) (push) Has been skipped
Tools CI / Test (mattermost-govet) (push) Failing after 3s
Server CI / Postgres FIPS (shard 1) (push) Has been skipped
Server CI / Postgres FIPS (shard 2) (push) Has been skipped
Tools CI / Test (mmgotool) (push) Failing after 3s
Server CI / Postgres FIPS (shard 3) (push) Has been skipped
Server CI / Run mmctl tests (push) Has been skipped
Server CI / Run mmctl tests (FIPS) (push) Has been skipped
Server CI / Merge Postgres Test Results (push) Has been skipped
Server CI / Merge Postgres FIPS Test Results (push) Has been skipped
Server CI / Check backport migrations (push) Has been skipped
Server CI / Check generated files (push) Has been skipped
Server CI / check-style (push) Has been skipped
Server CI / Vet API (push) Has been skipped
Server CI / Build mattermost server app (push) Has been skipped
Server CI / Server CI Complete (push) Failing after 2s
Server CI / Server CI Artifacts (push) Has been skipped
Server CI / Sentry Upload (push) Has been skipped
Server CI / Server CI Report (push) Failing after 5m1s
Server CI Nightly Race / Race Detector (push) Has been skipped
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Webapp CI Cache Warm / Warm Webapp node_modules Cache (push) Has been cancelled
E2E CI Cache Warm / Warm E2E npm Registry Cache (push) Has been cancelled
API / build (push) Has been cancelled
Web App CI / check-types (push) Has been cancelled
Web App CI / check-lint (push) Has been cancelled
Web App CI / check-i18n (push) Has been cancelled
Web App CI / check-external-links (push) Has been cancelled
Web App CI / test (platform) (push) Has been cancelled
Web App CI / test (mattermost-redux) (push) Has been cancelled
Web App CI / test (channels shard 1/4) (push) Has been cancelled
Web App CI / test (channels shard 2/4) (push) Has been cancelled
Web App CI / test (channels shard 3/4) (push) Has been cancelled
Web App CI / test (channels shard 4/4) (push) Has been cancelled
Web App CI / upload-coverage (push) Has been cancelled
Web App CI / build (push) Has been cancelled
YAML Lint / yamllint (push) Has been cancelled
Push mirrored docker images / cd/Push mirrored docker images (push) Has been cancelled
docs-cd / Build and deploy docs (push) Has been cancelled
Docs CI / build (push) Has been cancelled
E2E Cache Warm: Cypress deps / Warm Cypress deps cache (push) Has been cancelled
Scorecards supply-chain security / Scorecard analysis (push) Has been cancelled
Server CI Weekly / Postgres FIPS (push) Has been skipped
Server CI Weekly / Run mmctl tests (FIPS) (push) Has been skipped
Server CI Weekly / Postgres with binary parameters (push) Has been skipped
Server CI Weekly / Build Environment (push) Failing after 1s
Server CI Cache Warm / Warm Go Module and Build Cache (push) Failing after 2s
Server CI Nightly Race / Build Environment (push) Failing after 1s
Some checks failed
Server CI / Postgres (shard 0) (push) Has been skipped
Server CI / Postgres (shard 3) (push) Has been skipped
BuildEnv Docker Image / build-image (push) Failing after 4s
BuildEnv Docker Image / build-image-fips (push) Failing after 4s
Migration Review & Release Notes / Generate migration review & release notes (push) Failing after 3s
Server CI / Build Environment (push) Failing after 3s
Server CI / Postgres (shard 1) (push) Has been skipped
Tools CI / check-style (mattermost-govet) (push) Failing after 4s
Server CI / Postgres (shard 2) (push) Has been skipped
Tools CI / check-style (mmgotool) (push) Failing after 3s
Server CI / Elasticsearch v8 Compatibility (push) Has been skipped
Server CI / OpenSearch v2 Compatibility (push) Has been skipped
Server CI / Postgres FIPS (shard 0) (push) Has been skipped
Tools CI / Test (mattermost-govet) (push) Failing after 3s
Server CI / Postgres FIPS (shard 1) (push) Has been skipped
Server CI / Postgres FIPS (shard 2) (push) Has been skipped
Tools CI / Test (mmgotool) (push) Failing after 3s
Server CI / Postgres FIPS (shard 3) (push) Has been skipped
Server CI / Run mmctl tests (push) Has been skipped
Server CI / Run mmctl tests (FIPS) (push) Has been skipped
Server CI / Merge Postgres Test Results (push) Has been skipped
Server CI / Merge Postgres FIPS Test Results (push) Has been skipped
Server CI / Check backport migrations (push) Has been skipped
Server CI / Check generated files (push) Has been skipped
Server CI / check-style (push) Has been skipped
Server CI / Vet API (push) Has been skipped
Server CI / Build mattermost server app (push) Has been skipped
Server CI / Server CI Complete (push) Failing after 2s
Server CI / Server CI Artifacts (push) Has been skipped
Server CI / Sentry Upload (push) Has been skipped
Server CI / Server CI Report (push) Failing after 5m1s
Server CI Nightly Race / Race Detector (push) Has been skipped
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Webapp CI Cache Warm / Warm Webapp node_modules Cache (push) Has been cancelled
E2E CI Cache Warm / Warm E2E npm Registry Cache (push) Has been cancelled
API / build (push) Has been cancelled
Web App CI / check-types (push) Has been cancelled
Web App CI / check-lint (push) Has been cancelled
Web App CI / check-i18n (push) Has been cancelled
Web App CI / check-external-links (push) Has been cancelled
Web App CI / test (platform) (push) Has been cancelled
Web App CI / test (mattermost-redux) (push) Has been cancelled
Web App CI / test (channels shard 1/4) (push) Has been cancelled
Web App CI / test (channels shard 2/4) (push) Has been cancelled
Web App CI / test (channels shard 3/4) (push) Has been cancelled
Web App CI / test (channels shard 4/4) (push) Has been cancelled
Web App CI / upload-coverage (push) Has been cancelled
Web App CI / build (push) Has been cancelled
YAML Lint / yamllint (push) Has been cancelled
Push mirrored docker images / cd/Push mirrored docker images (push) Has been cancelled
docs-cd / Build and deploy docs (push) Has been cancelled
Docs CI / build (push) Has been cancelled
E2E Cache Warm: Cypress deps / Warm Cypress deps cache (push) Has been cancelled
Scorecards supply-chain security / Scorecard analysis (push) Has been cancelled
Server CI Weekly / Postgres FIPS (push) Has been skipped
Server CI Weekly / Run mmctl tests (FIPS) (push) Has been skipped
Server CI Weekly / Postgres with binary parameters (push) Has been skipped
Server CI Weekly / Build Environment (push) Failing after 1s
Server CI Cache Warm / Warm Go Module and Build Cache (push) Failing after 2s
Server CI Nightly Race / Build Environment (push) Failing after 1s
This commit is contained in:
351
server/public/model/post_interactive_blocks_test.go
Normal file
351
server/public/model/post_interactive_blocks_test.go
Normal file
@@ -0,0 +1,351 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestCollectMmBlockActionIDs(t *testing.T) {
|
||||
t.Run("nested container blocks", func(t *testing.T) {
|
||||
blocks := []any{
|
||||
map[string]any{"type": "text", "text": "hi"},
|
||||
map[string]any{
|
||||
"type": "container",
|
||||
"content": []any{
|
||||
map[string]any{"type": "button", "text": "A", "action_id": "a1"},
|
||||
map[string]any{"type": "static_select", "action_id": "s1", "placeholder": "pick"},
|
||||
},
|
||||
},
|
||||
}
|
||||
ids := CollectMmBlockActionIDs(blocks)
|
||||
assert.Equal(t, map[string]struct{}{"a1": {}, "s1": {}}, ids)
|
||||
})
|
||||
|
||||
t.Run("columnSet", func(t *testing.T) {
|
||||
blocks := []any{
|
||||
map[string]any{
|
||||
"type": "column_set",
|
||||
"columns": []any{
|
||||
map[string]any{
|
||||
"type": "column",
|
||||
"items": []any{
|
||||
map[string]any{"type": "button", "text": "A", "action_id": "inset"},
|
||||
},
|
||||
},
|
||||
map[string]any{
|
||||
"type": "text",
|
||||
"text": "not a column",
|
||||
},
|
||||
},
|
||||
},
|
||||
map[string]any{
|
||||
"type": "column",
|
||||
"items": []any{
|
||||
map[string]any{"type": "button", "text": "B", "action_id": "orphan"},
|
||||
},
|
||||
},
|
||||
}
|
||||
ids := CollectMmBlockActionIDs(blocks)
|
||||
assert.Equal(t, map[string]struct{}{"inset": {}}, ids)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSubsetMmBlocksActions(t *testing.T) {
|
||||
all := map[string]any{
|
||||
"a1": map[string]any{"type": "external", "url": "http://example.com/a"},
|
||||
"b2": map[string]any{"type": "external", "url": "http://example.com/b"},
|
||||
}
|
||||
subset := SubsetMmBlocksActions(all, map[string]struct{}{"a1": {}})
|
||||
require.Len(t, subset, 1)
|
||||
assert.Contains(t, subset, "a1")
|
||||
}
|
||||
|
||||
func TestValidateMmBlocksActionsForWebhook(t *testing.T) {
|
||||
blocks := []any{
|
||||
map[string]any{"type": "button", "text": "Go", "action_id": "act"},
|
||||
}
|
||||
require.Error(t, ValidateMmBlocksActionsForWebhook(blocks, nil))
|
||||
require.Error(t, ValidateMmBlocksActionsForWebhook(nil, map[string]any{"act": map[string]any{}}))
|
||||
require.NoError(t, ValidateMmBlocksActionsForWebhook(blocks, map[string]any{
|
||||
"act": map[string]any{"type": "external", "url": "http://example.com"},
|
||||
}))
|
||||
}
|
||||
|
||||
func TestCollectMmactionIDsFromMmBlockText(t *testing.T) {
|
||||
blocks := []any{
|
||||
map[string]any{
|
||||
"type": "text",
|
||||
"text": "Choose [one](mmaction://pick1) or [two](mmaction://pick2)",
|
||||
},
|
||||
}
|
||||
ids := CollectMmBlockActionIDs(blocks)
|
||||
assert.Equal(t, map[string]struct{}{"pick1": {}, "pick2": {}}, ids)
|
||||
}
|
||||
|
||||
func TestCollectMmactionIDsFromText(t *testing.T) {
|
||||
t.Run("skipsCode", func(t *testing.T) {
|
||||
ids := CollectMmactionIDsFromText("Use [real](mmaction://real1) not `mmaction://inline`")
|
||||
assert.Equal(t, map[string]struct{}{"real1": {}}, ids)
|
||||
|
||||
ids = CollectMmactionIDsFromText("```\n[mmaction://fence](mmaction://fence)\n```\n[ok](mmaction://ok1)")
|
||||
assert.Equal(t, map[string]struct{}{"ok1": {}}, ids)
|
||||
})
|
||||
}
|
||||
|
||||
func TestCollectBlockKitActionIDs(t *testing.T) {
|
||||
blocks := []any{
|
||||
map[string]any{
|
||||
"type": "section",
|
||||
"text": map[string]any{"type": "mrkdwn", "text": "[Go](mmaction://md1)"},
|
||||
"accessory": map[string]any{
|
||||
"type": "button", "text": map[string]any{"type": "plain_text", "text": "Btn"},
|
||||
"action_id": "acc1",
|
||||
},
|
||||
},
|
||||
map[string]any{
|
||||
"type": "actions",
|
||||
"elements": []any{
|
||||
map[string]any{
|
||||
"type": "button",
|
||||
"text": map[string]any{"type": "plain_text", "text": "OK"},
|
||||
"action_id": "row1",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
ids := CollectBlockKitActionIDs(blocks)
|
||||
assert.Equal(t, map[string]struct{}{"md1": {}, "acc1": {}, "row1": {}}, ids)
|
||||
}
|
||||
|
||||
func TestCollectAdaptiveCardActionIDs(t *testing.T) {
|
||||
cards := []any{
|
||||
map[string]any{
|
||||
"type": "AdaptiveCard",
|
||||
"body": []any{
|
||||
map[string]any{"type": "TextBlock", "text": "See [here](mmaction://cardmd)"},
|
||||
map[string]any{
|
||||
"type": "ActionSet",
|
||||
"actions": []any{
|
||||
map[string]any{"type": "Action.Submit", "title": "OK", "id": "submit1"},
|
||||
},
|
||||
},
|
||||
},
|
||||
"actions": []any{
|
||||
map[string]any{"type": "Action.Submit", "title": "Footer", "id": "footer1"},
|
||||
},
|
||||
},
|
||||
}
|
||||
ids := CollectAdaptiveCardActionIDs(cards)
|
||||
assert.Equal(t, map[string]struct{}{"cardmd": {}, "submit1": {}, "footer1": {}}, ids)
|
||||
}
|
||||
|
||||
func TestValidateMmBlocksActionsOnPost(t *testing.T) {
|
||||
t.Run("pairing", func(t *testing.T) {
|
||||
post := &Post{
|
||||
Props: map[string]any{
|
||||
PostPropsMmBlocks: []any{
|
||||
map[string]any{"type": "button", "text": "Go", "action_id": "act"},
|
||||
},
|
||||
PostPropsMmBlocksActions: map[string]any{
|
||||
"act": map[string]any{"type": "external", "url": "http://example.com"},
|
||||
},
|
||||
},
|
||||
}
|
||||
require.NoError(t, ValidateMmBlocksActions(post))
|
||||
|
||||
extra := post.Clone()
|
||||
extraProps := extra.GetProps()
|
||||
extraProps[PostPropsMmBlocksActions] = map[string]any{
|
||||
"act": map[string]any{"type": "external", "url": "http://example.com"},
|
||||
"extra": map[string]any{"type": "external", "url": "http://example.com/2"},
|
||||
}
|
||||
extra.SetProps(extraProps)
|
||||
require.Error(t, ValidateMmBlocksActions(extra))
|
||||
})
|
||||
|
||||
t.Run("disabledControlSkipsActionRegistry", func(t *testing.T) {
|
||||
post := &Post{
|
||||
Props: map[string]any{
|
||||
PostPropsMmBlocks: []any{
|
||||
map[string]any{
|
||||
"type": "button",
|
||||
"text": "Disabled",
|
||||
"action_id": "disabled_only",
|
||||
"disabled": true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
require.NoError(t, ValidateMmBlocksActions(post))
|
||||
})
|
||||
}
|
||||
|
||||
func TestValidateInteractiveActionsForWebhook(t *testing.T) {
|
||||
t.Run("messageMmaction", func(t *testing.T) {
|
||||
post := &Post{
|
||||
Message: "Click [go](mmaction://go1)",
|
||||
Props: map[string]any{
|
||||
PostPropsMmBlocksActions: map[string]any{
|
||||
"go1": map[string]any{"type": "external", "url": "http://example.com"},
|
||||
},
|
||||
},
|
||||
}
|
||||
require.NoError(t, ValidateInteractiveActionsForWebhook(post))
|
||||
|
||||
postMissing := &Post{Message: "Click [go](mmaction://go1)"}
|
||||
require.Error(t, ValidateInteractiveActionsForWebhook(postMissing))
|
||||
})
|
||||
}
|
||||
|
||||
func TestApplyMmBlocksWithActionsToProps(t *testing.T) {
|
||||
t.Run("nilProps", func(t *testing.T) {
|
||||
blocks := []any{
|
||||
map[string]any{"type": "button", "text": "Go", "action_id": "act1"},
|
||||
}
|
||||
actions := map[string]any{
|
||||
"act1": map[string]any{"type": "external", "url": "http://example.com"},
|
||||
}
|
||||
|
||||
props := ApplyMmBlocksWithActionsToProps(nil, blocks, actions)
|
||||
require.NotNil(t, props)
|
||||
require.Equal(t, blocks, props[PostPropsMmBlocks])
|
||||
require.Equal(t, actions, props[PostPropsMmBlocksActions])
|
||||
})
|
||||
}
|
||||
|
||||
func TestPost_InteractiveBlocksImageURLs(t *testing.T) {
|
||||
t.Run("empty post", func(t *testing.T) {
|
||||
assert.Nil(t, (&Post{}).InteractiveBlocksImageURLs(true))
|
||||
})
|
||||
|
||||
t.Run("collects URLs from all interactive sources when enabled", func(t *testing.T) {
|
||||
post := &Post{
|
||||
Props: StringInterface{
|
||||
PostPropsMmBlocks: []any{
|
||||
map[string]any{
|
||||
"type": "text",
|
||||
"text": "x ",
|
||||
},
|
||||
map[string]any{
|
||||
"type": "image",
|
||||
"url": "https://example.com/mm_direct1.png",
|
||||
},
|
||||
map[string]any{
|
||||
"type": "image",
|
||||
"url": "https://example.com/mm_direct2.png",
|
||||
},
|
||||
map[string]any{
|
||||
"type": "container",
|
||||
"content": []any{
|
||||
map[string]any{
|
||||
"type": "image",
|
||||
"url": "https://example.com/mm_nested.png",
|
||||
},
|
||||
map[string]any{
|
||||
"type": "image",
|
||||
"url": "https://example.com/mm_nested2.png",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
PostPropsBlockKitBlocks: []any{
|
||||
map[string]any{
|
||||
"type": "image",
|
||||
"image_url": "https://example.com/bk_top1.png",
|
||||
},
|
||||
map[string]any{
|
||||
"type": "image",
|
||||
"image_url": "https://example.com/bk_top2.png",
|
||||
},
|
||||
map[string]any{
|
||||
"type": "section",
|
||||
"text": map[string]any{
|
||||
"type": "plain_text",
|
||||
"text": "caption",
|
||||
},
|
||||
"accessory": map[string]any{
|
||||
"type": "image",
|
||||
"image_url": "https://example.com/bk_accessory.png",
|
||||
},
|
||||
},
|
||||
},
|
||||
PostPropsAdaptiveCards: []any{
|
||||
map[string]any{
|
||||
"type": "AdaptiveCard",
|
||||
"body": []any{
|
||||
map[string]any{
|
||||
"type": "Image",
|
||||
"url": "https://example.com/ac_card1_a.png",
|
||||
},
|
||||
map[string]any{
|
||||
"type": "Image",
|
||||
"url": "https://example.com/ac_card1_b.png",
|
||||
},
|
||||
},
|
||||
},
|
||||
map[string]any{
|
||||
"type": "AdaptiveCard",
|
||||
"body": []any{
|
||||
map[string]any{
|
||||
"type": "Image",
|
||||
"url": "https://example.com/ac_card2.png",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
PostPropsAttachments: []*MessageAttachment{
|
||||
{
|
||||
ImageURL: "https://example.com/attach_main.png",
|
||||
ThumbURL: "https://example.com/attach_thumb.png",
|
||||
AuthorIcon: "https://example.com/attach_author.png",
|
||||
FooterIcon: "https://example.com/attach_footer.png",
|
||||
},
|
||||
{ImageURL: "https://example.com/attach_second.png"},
|
||||
nil,
|
||||
{Text: "no images"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
urls := post.InteractiveBlocksImageURLs(true)
|
||||
|
||||
// Markdown images inside text blocks are not returned here; they are gathered with Post.AllStrings in getEmbedsAndImages.
|
||||
assert.ElementsMatch(t, []string{
|
||||
"https://example.com/mm_direct1.png",
|
||||
"https://example.com/mm_direct2.png",
|
||||
"https://example.com/mm_nested.png",
|
||||
"https://example.com/mm_nested2.png",
|
||||
"https://example.com/bk_top1.png",
|
||||
"https://example.com/bk_top2.png",
|
||||
"https://example.com/bk_accessory.png",
|
||||
"https://example.com/ac_card1_a.png",
|
||||
"https://example.com/ac_card1_b.png",
|
||||
"https://example.com/ac_card2.png",
|
||||
"https://example.com/attach_main.png",
|
||||
"https://example.com/attach_thumb.png",
|
||||
"https://example.com/attach_author.png",
|
||||
"https://example.com/attach_footer.png",
|
||||
"https://example.com/attach_second.png",
|
||||
}, urls)
|
||||
})
|
||||
|
||||
t.Run("attachment URLs only when mmBlocksEnabled is false", func(t *testing.T) {
|
||||
post := &Post{
|
||||
Props: StringInterface{
|
||||
PostPropsMmBlocks: []any{
|
||||
map[string]any{"type": "image", "url": "https://example.com/mm.png"},
|
||||
},
|
||||
PostPropsAttachments: []*MessageAttachment{
|
||||
{ImageURL: "https://example.com/attach_main.png"},
|
||||
},
|
||||
},
|
||||
}
|
||||
urls := post.InteractiveBlocksImageURLs(false)
|
||||
assert.ElementsMatch(t, []string{"https://example.com/attach_main.png"}, urls)
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user