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
664 lines
20 KiB
Go
664 lines
20 KiB
Go
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.enterprise for license information.
|
|
|
|
package common
|
|
|
|
import (
|
|
"encoding/json"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/mattermost/mattermost/server/public/model"
|
|
)
|
|
|
|
func TestElasticsearchBuildPostIndexName(t *testing.T) {
|
|
now := time.Date(2017, 8, 14, 15, 16, 17, 123, time.Local)
|
|
|
|
sixDaysAgo := time.Date(2017, 8, 9, 12, 11, 10, 987, time.Local)
|
|
sevenDaysAgo := time.Date(2017, 8, 8, 11, 10, 9, 876, time.Local)
|
|
eightDaysAgo := time.Date(2017, 8, 7, 6, 5, 4, 321, time.Local)
|
|
|
|
sixMillis := sixDaysAgo.UnixNano() / int64(time.Millisecond)
|
|
sevenMillis := sevenDaysAgo.UnixNano() / int64(time.Millisecond)
|
|
eightMillis := eightDaysAgo.UnixNano() / int64(time.Millisecond)
|
|
|
|
aggregationCutoff := 7 // Aggregate monthly after 7 days.
|
|
|
|
sixName := BuildPostIndexName(aggregationCutoff, IndexBasePosts, IndexBasePosts_MONTH, now, sixMillis)
|
|
sevenName := BuildPostIndexName(aggregationCutoff, IndexBasePosts, IndexBasePosts_MONTH, now, sevenMillis)
|
|
eightName := BuildPostIndexName(aggregationCutoff, IndexBasePosts, IndexBasePosts_MONTH, now, eightMillis)
|
|
|
|
assert.Equal(t, sixName, "posts_2017_08_09")
|
|
assert.Equal(t, sevenName, "posts_2017_08_08")
|
|
assert.Equal(t, eightName, "postsmonth_2017_08")
|
|
}
|
|
|
|
func TestHasAnalysisPlugin(t *testing.T) {
|
|
installed := []string{"analysis-icu", "analysis-kuromoji", "opensearch-analysis-nori"}
|
|
|
|
testCases := []struct {
|
|
Name string
|
|
Plugins []string
|
|
Lookup string
|
|
Expected bool
|
|
}{
|
|
{"bundled name", installed, "analysis-kuromoji", true},
|
|
{"opensearch prefixed name", installed, "analysis-nori", true},
|
|
{"not installed", installed, "analysis-stempel", false},
|
|
{"no plugins reported", nil, "analysis-nori", false},
|
|
{"unrelated prefix is not accepted", []string{"vendor-analysis-nori"}, "analysis-nori", false},
|
|
{"elasticsearch prefix is not accepted", []string{"elasticsearch-analysis-nori"}, "analysis-nori", false},
|
|
{"a longer name is not a match", []string{"analysis-noris"}, "analysis-nori", false},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.Name, func(t *testing.T) {
|
|
assert.Equal(t, tc.Expected, HasAnalysisPlugin(tc.Plugins, tc.Lookup))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestESPostFromPostForIndexing(t *testing.T) {
|
|
t.Run("any form with text only", func(t *testing.T) {
|
|
post := model.PostForIndexing{
|
|
TeamId: model.NewId(),
|
|
ParentCreateAt: nil,
|
|
Post: model.Post{
|
|
Id: model.NewId(),
|
|
ChannelId: model.NewId(),
|
|
UserId: model.NewId(),
|
|
CreateAt: model.GetMillis(),
|
|
Message: "message",
|
|
Type: "",
|
|
Hashtags: "",
|
|
Props: map[string]any{
|
|
model.PostPropsAttachments: []any{
|
|
map[string]any{
|
|
"text": "text 1",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
espost := ESPostFromPostForIndexing(&post, true)
|
|
|
|
assert.Equal(t, post.Id, espost.Id)
|
|
assert.Equal(t, post.TeamId, espost.TeamId)
|
|
assert.Equal(t, post.ChannelId, espost.ChannelId)
|
|
assert.Equal(t, post.UserId, espost.UserId)
|
|
assert.Equal(t, post.CreateAt, espost.CreateAt)
|
|
assert.Equal(t, post.Message, espost.Message)
|
|
assert.Equal(t, "default", espost.Type)
|
|
assert.Empty(t, espost.Hashtags)
|
|
assert.Equal(t, "text 1", espost.Attachments)
|
|
})
|
|
|
|
t.Run("MessageAttachment form with text only", func(t *testing.T) {
|
|
post := model.PostForIndexing{
|
|
TeamId: model.NewId(),
|
|
ParentCreateAt: nil,
|
|
Post: model.Post{
|
|
Id: model.NewId(),
|
|
ChannelId: model.NewId(),
|
|
UserId: model.NewId(),
|
|
CreateAt: model.GetMillis(),
|
|
Message: "message",
|
|
Type: "slack_attachment",
|
|
Hashtags: "#buh #boh",
|
|
Props: map[string]any{
|
|
model.PostPropsAttachments: []*model.MessageAttachment{
|
|
{
|
|
Text: "text 2",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
espost := ESPostFromPostForIndexing(&post, true)
|
|
|
|
assert.Equal(t, post.Id, espost.Id)
|
|
assert.Equal(t, post.TeamId, espost.TeamId)
|
|
assert.Equal(t, post.ChannelId, espost.ChannelId)
|
|
assert.Equal(t, post.UserId, espost.UserId)
|
|
assert.Equal(t, post.CreateAt, espost.CreateAt)
|
|
assert.Equal(t, post.Message, espost.Message)
|
|
assert.Equal(t, "slack_attachment", espost.Type)
|
|
assert.Len(t, espost.Hashtags, 2)
|
|
assert.Equal(t, "text 2", espost.Attachments)
|
|
})
|
|
|
|
t.Run("any form indexes title, pretext, and fields", func(t *testing.T) {
|
|
post := model.PostForIndexing{
|
|
TeamId: model.NewId(),
|
|
Post: model.Post{
|
|
Id: model.NewId(),
|
|
ChannelId: model.NewId(),
|
|
UserId: model.NewId(),
|
|
CreateAt: model.GetMillis(),
|
|
Message: "",
|
|
Type: "slack_attachment",
|
|
Props: map[string]any{
|
|
model.PostPropsAttachments: []any{
|
|
map[string]any{
|
|
"title": "Build Failed",
|
|
"pretext": "CI notification",
|
|
"fallback": "Build Failed on main",
|
|
"text": "Details here",
|
|
"fields": []any{
|
|
map[string]any{
|
|
"title": "Branch",
|
|
"value": "main",
|
|
},
|
|
map[string]any{
|
|
"title": "Status",
|
|
"value": "failed",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
espost := ESPostFromPostForIndexing(&post, true)
|
|
|
|
assert.Contains(t, espost.Attachments, "Details here")
|
|
assert.Contains(t, espost.Attachments, "Build Failed")
|
|
assert.Contains(t, espost.Attachments, "CI notification")
|
|
assert.NotContains(t, espost.Attachments, "Build Failed on main")
|
|
assert.Contains(t, espost.Attachments, "Branch")
|
|
assert.Contains(t, espost.Attachments, "main")
|
|
assert.Contains(t, espost.Attachments, "Status")
|
|
assert.Contains(t, espost.Attachments, "failed")
|
|
})
|
|
|
|
t.Run("MessageAttachment form indexes title, pretext, and fields", func(t *testing.T) {
|
|
post := model.PostForIndexing{
|
|
TeamId: model.NewId(),
|
|
Post: model.Post{
|
|
Id: model.NewId(),
|
|
ChannelId: model.NewId(),
|
|
UserId: model.NewId(),
|
|
CreateAt: model.GetMillis(),
|
|
Message: "",
|
|
Type: "slack_attachment",
|
|
Props: map[string]any{
|
|
model.PostPropsAttachments: []*model.MessageAttachment{
|
|
{
|
|
Title: "Build Failed",
|
|
Pretext: "CI notification",
|
|
Fallback: "Build Failed on main",
|
|
Text: "Details here",
|
|
Fields: []*model.MessageAttachmentField{
|
|
{Title: "Branch", Value: "main"},
|
|
{Title: "Status", Value: "failed"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
espost := ESPostFromPostForIndexing(&post, true)
|
|
|
|
assert.Contains(t, espost.Attachments, "Details here")
|
|
assert.Contains(t, espost.Attachments, "Build Failed")
|
|
assert.Contains(t, espost.Attachments, "CI notification")
|
|
assert.NotContains(t, espost.Attachments, "Build Failed on main")
|
|
assert.Contains(t, espost.Attachments, "Branch")
|
|
assert.Contains(t, espost.Attachments, "main")
|
|
assert.Contains(t, espost.Attachments, "Status")
|
|
assert.Contains(t, espost.Attachments, "failed")
|
|
})
|
|
|
|
t.Run("empty fields are excluded", func(t *testing.T) {
|
|
post := model.PostForIndexing{
|
|
TeamId: model.NewId(),
|
|
Post: model.Post{
|
|
Id: model.NewId(),
|
|
ChannelId: model.NewId(),
|
|
UserId: model.NewId(),
|
|
CreateAt: model.GetMillis(),
|
|
Message: "",
|
|
Type: "slack_attachment",
|
|
Props: map[string]any{
|
|
model.PostPropsAttachments: []*model.MessageAttachment{
|
|
{
|
|
Title: "Only Title",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
espost := ESPostFromPostForIndexing(&post, true)
|
|
|
|
assert.Equal(t, "Only Title", espost.Attachments)
|
|
})
|
|
|
|
t.Run("nil fields and attachments are handled", func(t *testing.T) {
|
|
post := model.PostForIndexing{
|
|
TeamId: model.NewId(),
|
|
Post: model.Post{
|
|
Id: model.NewId(),
|
|
ChannelId: model.NewId(),
|
|
UserId: model.NewId(),
|
|
CreateAt: model.GetMillis(),
|
|
Message: "",
|
|
Type: "slack_attachment",
|
|
Props: map[string]any{
|
|
model.PostPropsAttachments: []*model.MessageAttachment{
|
|
nil,
|
|
{
|
|
Text: "valid",
|
|
Fields: []*model.MessageAttachmentField{
|
|
nil,
|
|
{Title: "field title", Value: "field value"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
espost := ESPostFromPostForIndexing(&post, true)
|
|
|
|
assert.Contains(t, espost.Attachments, "valid")
|
|
assert.Contains(t, espost.Attachments, "field title")
|
|
assert.Contains(t, espost.Attachments, "field value")
|
|
})
|
|
|
|
t.Run("non-string field values are indexed", func(t *testing.T) {
|
|
post := model.PostForIndexing{
|
|
TeamId: model.NewId(),
|
|
Post: model.Post{
|
|
Id: model.NewId(),
|
|
ChannelId: model.NewId(),
|
|
UserId: model.NewId(),
|
|
CreateAt: model.GetMillis(),
|
|
Message: "",
|
|
Type: "slack_attachment",
|
|
Props: map[string]any{
|
|
model.PostPropsAttachments: []any{
|
|
map[string]any{
|
|
"text": "metrics",
|
|
"fields": []any{
|
|
map[string]any{
|
|
"title": "Count",
|
|
"value": 42,
|
|
},
|
|
map[string]any{
|
|
"title": "Rate",
|
|
"value": 99.5,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
espost := ESPostFromPostForIndexing(&post, true)
|
|
|
|
assert.Contains(t, espost.Attachments, "metrics")
|
|
assert.Contains(t, espost.Attachments, "Count")
|
|
assert.Contains(t, espost.Attachments, "42")
|
|
assert.Contains(t, espost.Attachments, "Rate")
|
|
assert.Contains(t, espost.Attachments, "99.5")
|
|
})
|
|
|
|
t.Run("non-string field values in MessageAttachment form are indexed", func(t *testing.T) {
|
|
post := model.PostForIndexing{
|
|
TeamId: model.NewId(),
|
|
Post: model.Post{
|
|
Id: model.NewId(),
|
|
ChannelId: model.NewId(),
|
|
UserId: model.NewId(),
|
|
CreateAt: model.GetMillis(),
|
|
Message: "",
|
|
Type: "slack_attachment",
|
|
Props: map[string]any{
|
|
model.PostPropsAttachments: []*model.MessageAttachment{
|
|
{
|
|
Text: "metrics",
|
|
Fields: []*model.MessageAttachmentField{
|
|
{Title: "Count", Value: 42},
|
|
{Title: "Rate", Value: 99.5},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
espost := ESPostFromPostForIndexing(&post, true)
|
|
|
|
assert.Contains(t, espost.Attachments, "metrics")
|
|
assert.Contains(t, espost.Attachments, "Count")
|
|
assert.Contains(t, espost.Attachments, "42")
|
|
assert.Contains(t, espost.Attachments, "Rate")
|
|
assert.Contains(t, espost.Attachments, "99.5")
|
|
})
|
|
|
|
t.Run("any form indexes footer and author_name", func(t *testing.T) {
|
|
post := model.PostForIndexing{
|
|
TeamId: model.NewId(),
|
|
Post: model.Post{
|
|
Id: model.NewId(),
|
|
ChannelId: model.NewId(),
|
|
UserId: model.NewId(),
|
|
CreateAt: model.GetMillis(),
|
|
Message: "",
|
|
Type: "slack_attachment",
|
|
Props: map[string]any{
|
|
model.PostPropsAttachments: []any{
|
|
map[string]any{
|
|
"text": "body text",
|
|
"footer": "Opportunity #OPP-000035341 • United States",
|
|
"author_name": "Salesforce",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
espost := ESPostFromPostForIndexing(&post, true)
|
|
|
|
assert.Contains(t, espost.Attachments, "body text")
|
|
assert.Contains(t, espost.Attachments, "Opportunity #OPP-000035341 • United States")
|
|
assert.Contains(t, espost.Attachments, "Salesforce")
|
|
})
|
|
|
|
t.Run("MessageAttachment form indexes footer and author_name", func(t *testing.T) {
|
|
post := model.PostForIndexing{
|
|
TeamId: model.NewId(),
|
|
Post: model.Post{
|
|
Id: model.NewId(),
|
|
ChannelId: model.NewId(),
|
|
UserId: model.NewId(),
|
|
CreateAt: model.GetMillis(),
|
|
Message: "",
|
|
Type: "slack_attachment",
|
|
Props: map[string]any{
|
|
model.PostPropsAttachments: []*model.MessageAttachment{
|
|
{
|
|
Text: "body text",
|
|
Footer: "Opportunity #OPP-000035341 • United States",
|
|
AuthorName: "Salesforce",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
espost := ESPostFromPostForIndexing(&post, true)
|
|
|
|
assert.Contains(t, espost.Attachments, "body text")
|
|
assert.Contains(t, espost.Attachments, "Opportunity #OPP-000035341 • United States")
|
|
assert.Contains(t, espost.Attachments, "Salesforce")
|
|
})
|
|
|
|
t.Run("interactive mm_blocks blocks and cards are indexed in attachments field", func(t *testing.T) {
|
|
post := model.PostForIndexing{
|
|
TeamId: model.NewId(),
|
|
Post: model.Post{
|
|
Id: model.NewId(),
|
|
ChannelId: model.NewId(),
|
|
UserId: model.NewId(),
|
|
CreateAt: model.GetMillis(),
|
|
Message: "root",
|
|
Type: model.PostTypeDefault,
|
|
Props: map[string]any{
|
|
model.PostPropsMmBlocks: []any{
|
|
map[string]any{"type": "text", "text": "mm-line"},
|
|
},
|
|
model.PostPropsBlockKitBlocks: []any{
|
|
map[string]any{
|
|
"type": "section",
|
|
"text": map[string]any{
|
|
"type": "mrkdwn",
|
|
"text": "block kit-line",
|
|
},
|
|
},
|
|
},
|
|
model.PostPropsAdaptiveCards: []any{
|
|
map[string]any{
|
|
"type": "AdaptiveCard",
|
|
"body": []any{
|
|
map[string]any{"type": "TextBlock", "text": "card-line"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
espost := ESPostFromPostForIndexing(&post, true)
|
|
|
|
assert.Equal(t, "root", espost.Message)
|
|
assert.Contains(t, espost.Attachments, "mm-line")
|
|
assert.Contains(t, espost.Attachments, "block kit-line")
|
|
assert.Contains(t, espost.Attachments, "card-line")
|
|
})
|
|
|
|
t.Run("interactive content omitted from attachments when mmBlocksEnabled is false", func(t *testing.T) {
|
|
post := model.PostForIndexing{
|
|
TeamId: model.NewId(),
|
|
Post: model.Post{
|
|
Id: model.NewId(),
|
|
ChannelId: model.NewId(),
|
|
UserId: model.NewId(),
|
|
CreateAt: model.GetMillis(),
|
|
Message: "root",
|
|
Type: model.PostTypeDefault,
|
|
Props: map[string]any{
|
|
model.PostPropsMmBlocks: []any{
|
|
map[string]any{"type": "text", "text": "mm-line"},
|
|
},
|
|
model.PostPropsBlockKitBlocks: []any{
|
|
map[string]any{
|
|
"type": "section",
|
|
"text": map[string]any{
|
|
"type": "mrkdwn",
|
|
"text": "block kit-line",
|
|
},
|
|
},
|
|
},
|
|
model.PostPropsAdaptiveCards: []any{
|
|
map[string]any{
|
|
"type": "AdaptiveCard",
|
|
"body": []any{
|
|
map[string]any{"type": "TextBlock", "text": "card-line"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
espost := ESPostFromPostForIndexing(&post, false)
|
|
|
|
assert.Equal(t, "root", espost.Message)
|
|
assert.NotContains(t, espost.Attachments, "mm-line")
|
|
assert.NotContains(t, espost.Attachments, "block kit-line")
|
|
assert.NotContains(t, espost.Attachments, "card-line")
|
|
})
|
|
|
|
t.Run("multiple attachments are combined", func(t *testing.T) {
|
|
post := model.PostForIndexing{
|
|
TeamId: model.NewId(),
|
|
Post: model.Post{
|
|
Id: model.NewId(),
|
|
ChannelId: model.NewId(),
|
|
UserId: model.NewId(),
|
|
CreateAt: model.GetMillis(),
|
|
Message: "",
|
|
Type: "slack_attachment",
|
|
Props: map[string]any{
|
|
model.PostPropsAttachments: []any{
|
|
map[string]any{
|
|
"title": "First",
|
|
"text": "one",
|
|
},
|
|
map[string]any{
|
|
"title": "Second",
|
|
"text": "two",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
espost := ESPostFromPostForIndexing(&post, true)
|
|
|
|
assert.Contains(t, espost.Attachments, "First")
|
|
assert.Contains(t, espost.Attachments, "one")
|
|
assert.Contains(t, espost.Attachments, "Second")
|
|
assert.Contains(t, espost.Attachments, "two")
|
|
})
|
|
}
|
|
|
|
// TestESPostFromPost_CreatePostJSONRoundTrip simulates the exact flow that happens
|
|
// in production: App.CreatePost converts []*MessageAttachment to []any via JSON
|
|
// marshal/unmarshal (Post.Attachments in post.go), then the search layer calls
|
|
// ESPostFromPost which does ShallowCopy → ESPostFromPostForIndexing.
|
|
func TestESPostFromPost_CreatePostJSONRoundTrip(t *testing.T) {
|
|
// Step 1: Start with typed MessageAttachment (as a plugin/webhook would create)
|
|
original := &model.Post{
|
|
Id: model.NewId(),
|
|
ChannelId: model.NewId(),
|
|
UserId: model.NewId(),
|
|
CreateAt: model.GetMillis(),
|
|
Message: "#closedwon #renewal",
|
|
Type: "slack_attachment",
|
|
}
|
|
original.AddProp(model.PostPropsAttachments, []*model.MessageAttachment{
|
|
{
|
|
Title: "Account: Acme Corp / Widget Industries, LLC",
|
|
Text: "Renewal ARR: $49,140.00",
|
|
Pretext: "#closedwon #renewal",
|
|
Footer: "Opportunity #OPP-000035341 • United States",
|
|
AuthorName: "CRM Bot",
|
|
Fields: []*model.MessageAttachmentField{
|
|
{Title: "Sales Rep", Value: "Jane Smith"},
|
|
{Title: "Account Manager", Value: "John Doe"},
|
|
{Title: "Opportunity", Value: "Acme Corp / Widget Industries, LLC - Enterprise - 350 Seats - '26 Renewal"},
|
|
{Title: "Seats", Value: 350.0},
|
|
},
|
|
},
|
|
})
|
|
|
|
// Step 2: Simulate CreatePost JSON round-trip (post.go:305-315)
|
|
if attachments, ok := original.GetProp(model.PostPropsAttachments).([]*model.MessageAttachment); ok {
|
|
jsonAttachments, err := json.Marshal(attachments)
|
|
require.NoError(t, err)
|
|
attachmentsInterface := []any{}
|
|
err = json.Unmarshal(jsonAttachments, &attachmentsInterface)
|
|
require.NoError(t, err)
|
|
original.AddProp(model.PostPropsAttachments, attachmentsInterface)
|
|
} else {
|
|
require.Fail(t, "expected []*MessageAttachment type assertion to succeed")
|
|
}
|
|
|
|
// Step 3: Simulate ESPostFromPost (what the search layer calls)
|
|
teamId := model.NewId()
|
|
esPost, err := ESPostFromPost(original, teamId, "O", true)
|
|
require.NoError(t, err)
|
|
|
|
// Step 4: Verify all attachment fields are indexed
|
|
assert.Contains(t, esPost.Attachments, "Account: Acme Corp / Widget Industries, LLC", "title should be indexed")
|
|
assert.Contains(t, esPost.Attachments, "Renewal ARR: $49,140.00", "text should be indexed")
|
|
assert.Contains(t, esPost.Attachments, "#closedwon #renewal", "pretext should be indexed")
|
|
|
|
// Field titles and values
|
|
assert.Contains(t, esPost.Attachments, "Sales Rep", "field title should be indexed")
|
|
assert.Contains(t, esPost.Attachments, "Jane Smith", "field value should be indexed")
|
|
assert.Contains(t, esPost.Attachments, "Account Manager", "field title should be indexed")
|
|
assert.Contains(t, esPost.Attachments, "John Doe", "field value should be indexed")
|
|
assert.Contains(t, esPost.Attachments, "Opportunity", "field title should be indexed")
|
|
assert.Contains(t, esPost.Attachments, "Acme Corp / Widget Industries, LLC - Enterprise - 350 Seats - '26 Renewal", "field value should be indexed")
|
|
assert.Contains(t, esPost.Attachments, "350", "numeric field value should be indexed")
|
|
|
|
// Footer and AuthorName
|
|
assert.Contains(t, esPost.Attachments, "Opportunity #OPP-000035341 • United States", "footer should be indexed")
|
|
assert.Contains(t, esPost.Attachments, "CRM Bot", "author_name should be indexed")
|
|
|
|
t.Logf("Attachments field content: %s", esPost.Attachments)
|
|
}
|
|
|
|
// TestESPostFromPost_APIJSONUnmarshal simulates a post created via the REST API
|
|
// where Props are deserialized from JSON (attachments arrive as []any directly).
|
|
func TestESPostFromPost_APIJSONUnmarshal(t *testing.T) {
|
|
// Simulate what happens when a bot POSTs JSON to /api/v4/posts
|
|
postJSON := `{
|
|
"channel_id": "` + model.NewId() + `",
|
|
"message": "",
|
|
"props": {
|
|
"attachments": [{
|
|
"title": "Account: Acme Corp",
|
|
"text": "Renewal ARR: $49,140.00",
|
|
"footer": "Opportunity #OPP-000035341",
|
|
"author_name": "CRM Bot",
|
|
"fields": [
|
|
{"title": "Sales Rep", "value": "Jane Smith"},
|
|
{"title": "Seats", "value": 350}
|
|
]
|
|
}]
|
|
}
|
|
}`
|
|
|
|
var post model.Post
|
|
err := json.Unmarshal([]byte(postJSON), &post)
|
|
require.NoError(t, err)
|
|
post.Id = model.NewId()
|
|
post.UserId = model.NewId()
|
|
post.CreateAt = model.GetMillis()
|
|
|
|
// The CreatePost conversion at post.go:305-315 would NOT trigger here
|
|
// because the type is already []any, not []*MessageAttachment
|
|
_, typeOk := post.GetProp(model.PostPropsAttachments).([]*model.MessageAttachment)
|
|
assert.False(t, typeOk, "API-created post should have []any, not []*MessageAttachment")
|
|
|
|
esPost, err := ESPostFromPost(&post, model.NewId(), "O", true)
|
|
require.NoError(t, err)
|
|
|
|
assert.Contains(t, esPost.Attachments, "Account: Acme Corp", "title should be indexed")
|
|
assert.Contains(t, esPost.Attachments, "Renewal ARR: $49,140.00", "text should be indexed")
|
|
assert.Contains(t, esPost.Attachments, "Sales Rep", "field title should be indexed")
|
|
assert.Contains(t, esPost.Attachments, "Jane Smith", "field value should be indexed")
|
|
assert.Contains(t, esPost.Attachments, "350", "numeric field value should be indexed")
|
|
assert.Contains(t, esPost.Attachments, "Opportunity #OPP-000035341", "footer should be indexed")
|
|
assert.Contains(t, esPost.Attachments, "CRM Bot", "author_name should be indexed")
|
|
|
|
t.Logf("Attachments field content: %s", esPost.Attachments)
|
|
}
|
|
|
|
func TestGetMatchesForHit(t *testing.T) {
|
|
snippets := map[string][]string{
|
|
"message": {
|
|
"<em>Apples</em> and oranges and <em>apple</em> and orange",
|
|
"Johnny <em>Appleseed</em>",
|
|
"That doesn't <em>apply</em> to me, and it doesn't <em>apply</em> to you.",
|
|
},
|
|
"hashtags": {
|
|
"This is an <em>#hashtag</em>",
|
|
},
|
|
}
|
|
expected := []string{
|
|
"Apples",
|
|
"apple",
|
|
"Appleseed",
|
|
"apply",
|
|
"#hashtag",
|
|
}
|
|
|
|
actual, err := GetMatchesForHit(snippets)
|
|
require.NoError(t, err)
|
|
require.ElementsMatch(t, expected, actual)
|
|
}
|