mirror of
https://github.com/mattermost/mattermost.git
synced 2025-02-25 18:55:24 -06:00
475 lines
12 KiB
Go
475 lines
12 KiB
Go
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
|
// See License.txt for license information.
|
|
|
|
package app
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/dyatlov/go-opengraph/opengraph"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/mattermost/mattermost-server/model"
|
|
"github.com/mattermost/mattermost-server/store"
|
|
"github.com/mattermost/mattermost-server/store/storetest"
|
|
)
|
|
|
|
func TestUpdatePostEditAt(t *testing.T) {
|
|
th := Setup().InitBasic()
|
|
defer th.TearDown()
|
|
|
|
post := &model.Post{}
|
|
*post = *th.BasicPost
|
|
|
|
post.IsPinned = true
|
|
if saved, err := th.App.UpdatePost(post, true); err != nil {
|
|
t.Fatal(err)
|
|
} else if saved.EditAt != post.EditAt {
|
|
t.Fatal("shouldn't have updated post.EditAt when pinning post")
|
|
|
|
*post = *saved
|
|
}
|
|
|
|
time.Sleep(time.Millisecond * 100)
|
|
|
|
post.Message = model.NewId()
|
|
if saved, err := th.App.UpdatePost(post, true); err != nil {
|
|
t.Fatal(err)
|
|
} else if saved.EditAt == post.EditAt {
|
|
t.Fatal("should have updated post.EditAt when updating post message")
|
|
}
|
|
|
|
time.Sleep(time.Millisecond * 200)
|
|
}
|
|
|
|
func TestUpdatePostTimeLimit(t *testing.T) {
|
|
th := Setup().InitBasic()
|
|
defer th.TearDown()
|
|
|
|
post := &model.Post{}
|
|
*post = *th.BasicPost
|
|
|
|
th.App.SetLicense(model.NewTestLicense())
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.ServiceSettings.PostEditTimeLimit = -1
|
|
})
|
|
if _, err := th.App.UpdatePost(post, true); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.ServiceSettings.PostEditTimeLimit = 1000000000
|
|
})
|
|
post.Message = model.NewId()
|
|
if _, err := th.App.UpdatePost(post, true); err != nil {
|
|
t.Fatal("should allow you to edit the post")
|
|
}
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.ServiceSettings.PostEditTimeLimit = 1
|
|
})
|
|
post.Message = model.NewId()
|
|
if _, err := th.App.UpdatePost(post, true); err == nil {
|
|
t.Fatal("should fail on update old post")
|
|
}
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.ServiceSettings.PostEditTimeLimit = -1
|
|
})
|
|
}
|
|
|
|
func TestPostReplyToPostWhereRootPosterLeftChannel(t *testing.T) {
|
|
// This test ensures that when replying to a root post made by a user who has since left the channel, the reply
|
|
// post completes successfully. This is a regression test for PLT-6523.
|
|
th := Setup().InitBasic()
|
|
defer th.TearDown()
|
|
|
|
channel := th.BasicChannel
|
|
userInChannel := th.BasicUser2
|
|
userNotInChannel := th.BasicUser
|
|
rootPost := th.BasicPost
|
|
|
|
if _, err := th.App.AddUserToChannel(userInChannel, channel); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if err := th.App.RemoveUserFromChannel(userNotInChannel.Id, "", channel); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
replyPost := model.Post{
|
|
Message: "asd",
|
|
ChannelId: channel.Id,
|
|
RootId: rootPost.Id,
|
|
ParentId: rootPost.Id,
|
|
PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()),
|
|
UserId: userInChannel.Id,
|
|
CreateAt: 0,
|
|
}
|
|
|
|
if _, err := th.App.CreatePostAsUser(&replyPost); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestPostAction(t *testing.T) {
|
|
th := Setup().InitBasic()
|
|
defer th.TearDown()
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.ServiceSettings.AllowedUntrustedInternalConnections = "localhost 127.0.0.1"
|
|
})
|
|
|
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
var request model.PostActionIntegrationRequest
|
|
err := json.NewDecoder(r.Body).Decode(&request)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, request.UserId, th.BasicUser.Id)
|
|
assert.Equal(t, "foo", request.Context["s"])
|
|
assert.EqualValues(t, 3, request.Context["n"])
|
|
fmt.Fprintf(w, `{"update": {"message": "updated"}, "ephemeral_text": "foo"}`)
|
|
}))
|
|
defer ts.Close()
|
|
|
|
interactivePost := model.Post{
|
|
Message: "Interactive post",
|
|
ChannelId: th.BasicChannel.Id,
|
|
PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()),
|
|
UserId: th.BasicUser.Id,
|
|
Props: model.StringInterface{
|
|
"attachments": []*model.SlackAttachment{
|
|
{
|
|
Text: "hello",
|
|
Actions: []*model.PostAction{
|
|
{
|
|
Integration: &model.PostActionIntegration{
|
|
Context: model.StringInterface{
|
|
"s": "foo",
|
|
"n": 3,
|
|
},
|
|
URL: ts.URL,
|
|
},
|
|
Name: "action",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
post, err := th.App.CreatePostAsUser(&interactivePost)
|
|
require.Nil(t, err)
|
|
|
|
attachments, ok := post.Props["attachments"].([]*model.SlackAttachment)
|
|
require.True(t, ok)
|
|
|
|
require.NotEmpty(t, attachments[0].Actions)
|
|
require.NotEmpty(t, attachments[0].Actions[0].Id)
|
|
|
|
err = th.App.DoPostAction(post.Id, "notavalidid", th.BasicUser.Id)
|
|
require.NotNil(t, err)
|
|
assert.Equal(t, http.StatusNotFound, err.StatusCode)
|
|
|
|
err = th.App.DoPostAction(post.Id, attachments[0].Actions[0].Id, th.BasicUser.Id)
|
|
require.Nil(t, err)
|
|
}
|
|
|
|
func TestPostChannelMentions(t *testing.T) {
|
|
th := Setup().InitBasic()
|
|
defer th.TearDown()
|
|
|
|
channel := th.BasicChannel
|
|
user := th.BasicUser
|
|
|
|
channelToMention, err := th.App.CreateChannel(&model.Channel{
|
|
DisplayName: "Mention Test",
|
|
Name: "mention-test",
|
|
Type: model.CHANNEL_OPEN,
|
|
TeamId: th.BasicTeam.Id,
|
|
}, false)
|
|
if err != nil {
|
|
t.Fatal(err.Error())
|
|
}
|
|
defer th.App.PermanentDeleteChannel(channelToMention)
|
|
|
|
_, err = th.App.AddUserToChannel(user, channel)
|
|
require.Nil(t, err)
|
|
|
|
post := &model.Post{
|
|
Message: fmt.Sprintf("hello, ~%v!", channelToMention.Name),
|
|
ChannelId: channel.Id,
|
|
PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()),
|
|
UserId: user.Id,
|
|
CreateAt: 0,
|
|
}
|
|
|
|
result, err := th.App.CreatePostAsUser(post)
|
|
require.Nil(t, err)
|
|
assert.Equal(t, map[string]interface{}{
|
|
"mention-test": map[string]interface{}{
|
|
"display_name": "Mention Test",
|
|
},
|
|
}, result.Props["channel_mentions"])
|
|
|
|
post.Message = fmt.Sprintf("goodbye, ~%v!", channelToMention.Name)
|
|
result, err = th.App.UpdatePost(post, false)
|
|
require.Nil(t, err)
|
|
assert.Equal(t, map[string]interface{}{
|
|
"mention-test": map[string]interface{}{
|
|
"display_name": "Mention Test",
|
|
},
|
|
}, result.Props["channel_mentions"])
|
|
}
|
|
|
|
func TestImageProxy(t *testing.T) {
|
|
th := Setup().InitBasic()
|
|
defer th.TearDown()
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.ServiceSettings.SiteURL = "http://mymattermost.com"
|
|
})
|
|
|
|
for name, tc := range map[string]struct {
|
|
ProxyType string
|
|
ProxyURL string
|
|
ProxyOptions string
|
|
ImageURL string
|
|
ProxiedImageURL string
|
|
}{
|
|
"atmos/camo": {
|
|
ProxyType: "atmos/camo",
|
|
ProxyURL: "https://127.0.0.1",
|
|
ProxyOptions: "foo",
|
|
ImageURL: "http://mydomain.com/myimage",
|
|
ProxiedImageURL: "https://127.0.0.1/f8dace906d23689e8d5b12c3cefbedbf7b9b72f5/687474703a2f2f6d79646f6d61696e2e636f6d2f6d79696d616765",
|
|
},
|
|
"atmos/camo_SameSite": {
|
|
ProxyType: "atmos/camo",
|
|
ProxyURL: "https://127.0.0.1",
|
|
ProxyOptions: "foo",
|
|
ImageURL: "http://mymattermost.com/myimage",
|
|
ProxiedImageURL: "http://mymattermost.com/myimage",
|
|
},
|
|
"atmos/camo_PathOnly": {
|
|
ProxyType: "atmos/camo",
|
|
ProxyURL: "https://127.0.0.1",
|
|
ProxyOptions: "foo",
|
|
ImageURL: "/myimage",
|
|
ProxiedImageURL: "/myimage",
|
|
},
|
|
"atmos/camo_EmptyImageURL": {
|
|
ProxyType: "atmos/camo",
|
|
ProxyURL: "https://127.0.0.1",
|
|
ProxyOptions: "foo",
|
|
ImageURL: "",
|
|
ProxiedImageURL: "",
|
|
},
|
|
} {
|
|
t.Run(name, func(t *testing.T) {
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
cfg.ServiceSettings.ImageProxyType = model.NewString(tc.ProxyType)
|
|
cfg.ServiceSettings.ImageProxyOptions = model.NewString(tc.ProxyOptions)
|
|
cfg.ServiceSettings.ImageProxyURL = model.NewString(tc.ProxyURL)
|
|
})
|
|
|
|
post := &model.Post{
|
|
Id: model.NewId(),
|
|
Message: "",
|
|
}
|
|
|
|
list := model.NewPostList()
|
|
list.Posts[post.Id] = post
|
|
|
|
assert.Equal(t, "", th.App.PostListWithProxyAddedToImageURLs(list).Posts[post.Id].Message)
|
|
assert.Equal(t, "", th.App.PostWithProxyAddedToImageURLs(post).Message)
|
|
|
|
assert.Equal(t, "", th.App.PostWithProxyRemovedFromImageURLs(post).Message)
|
|
post.Message = ""
|
|
assert.Equal(t, "", th.App.PostWithProxyRemovedFromImageURLs(post).Message)
|
|
})
|
|
}
|
|
}
|
|
|
|
func BenchmarkForceHTMLEncodingToUTF8(b *testing.B) {
|
|
HTML := `
|
|
<html>
|
|
<head>
|
|
<meta property="og:url" content="https://example.com/apps/mattermost">
|
|
<meta property="og:image" content="https://images.example.com/image.png">
|
|
</head>
|
|
</html>
|
|
`
|
|
ContentType := "text/html; utf-8"
|
|
|
|
b.Run("with converting", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
r := forceHTMLEncodingToUTF8(strings.NewReader(HTML), ContentType)
|
|
|
|
og := opengraph.NewOpenGraph()
|
|
og.ProcessHTML(r)
|
|
}
|
|
})
|
|
|
|
b.Run("without converting", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
og := opengraph.NewOpenGraph()
|
|
og.ProcessHTML(strings.NewReader(HTML))
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestMakeOpenGraphURLsAbsolute(t *testing.T) {
|
|
for name, tc := range map[string]struct {
|
|
HTML string
|
|
RequestURL string
|
|
URL string
|
|
ImageURL string
|
|
}{
|
|
"absolute URLs": {
|
|
HTML: `
|
|
<html>
|
|
<head>
|
|
<meta property="og:url" content="https://example.com/apps/mattermost">
|
|
<meta property="og:image" content="https://images.example.com/image.png">
|
|
</head>
|
|
</html>`,
|
|
RequestURL: "https://example.com",
|
|
URL: "https://example.com/apps/mattermost",
|
|
ImageURL: "https://images.example.com/image.png",
|
|
},
|
|
"URLs starting with /": {
|
|
HTML: `
|
|
<html>
|
|
<head>
|
|
<meta property="og:url" content="/apps/mattermost">
|
|
<meta property="og:image" content="/image.png">
|
|
</head>
|
|
</html>`,
|
|
RequestURL: "http://example.com",
|
|
URL: "http://example.com/apps/mattermost",
|
|
ImageURL: "http://example.com/image.png",
|
|
},
|
|
"HTTPS URLs starting with /": {
|
|
HTML: `
|
|
<html>
|
|
<head>
|
|
<meta property="og:url" content="/apps/mattermost">
|
|
<meta property="og:image" content="/image.png">
|
|
</head>
|
|
</html>`,
|
|
RequestURL: "https://example.com",
|
|
URL: "https://example.com/apps/mattermost",
|
|
ImageURL: "https://example.com/image.png",
|
|
},
|
|
"missing image URL": {
|
|
HTML: `
|
|
<html>
|
|
<head>
|
|
<meta property="og:url" content="/apps/mattermost">
|
|
</head>
|
|
</html>`,
|
|
RequestURL: "http://example.com",
|
|
URL: "http://example.com/apps/mattermost",
|
|
ImageURL: "",
|
|
},
|
|
"relative URLs": {
|
|
HTML: `
|
|
<html>
|
|
<head>
|
|
<meta property="og:url" content="index.html">
|
|
<meta property="og:image" content="../resources/image.png">
|
|
</head>
|
|
</html>`,
|
|
RequestURL: "http://example.com/content/index.html",
|
|
URL: "http://example.com/content/index.html",
|
|
ImageURL: "http://example.com/resources/image.png",
|
|
},
|
|
} {
|
|
t.Run(name, func(t *testing.T) {
|
|
og := opengraph.NewOpenGraph()
|
|
if err := og.ProcessHTML(strings.NewReader(tc.HTML)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
makeOpenGraphURLsAbsolute(og, tc.RequestURL)
|
|
|
|
if og.URL != tc.URL {
|
|
t.Fatalf("incorrect url, expected %v, got %v", tc.URL, og.URL)
|
|
}
|
|
|
|
if len(og.Images) > 0 {
|
|
if og.Images[0].URL != tc.ImageURL {
|
|
t.Fatalf("incorrect image url, expected %v, got %v", tc.ImageURL, og.Images[0].URL)
|
|
}
|
|
} else if tc.ImageURL != "" {
|
|
t.Fatalf("missing image url, expected %v, got nothing", tc.ImageURL)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMaxPostSize(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
testCases := []struct {
|
|
Description string
|
|
StoreMaxPostSize int
|
|
ExpectedMaxPostSize int
|
|
ExpectedError *model.AppError
|
|
}{
|
|
{
|
|
"error fetching max post size",
|
|
0,
|
|
model.POST_MESSAGE_MAX_RUNES_V1,
|
|
model.NewAppError("TestMaxPostSize", "this is an error", nil, "", http.StatusBadRequest),
|
|
},
|
|
{
|
|
"4000 rune limit",
|
|
4000,
|
|
4000,
|
|
nil,
|
|
},
|
|
{
|
|
"16383 rune limit",
|
|
16383,
|
|
16383,
|
|
nil,
|
|
},
|
|
}
|
|
|
|
for _, testCase := range testCases {
|
|
testCase := testCase
|
|
t.Run(testCase.Description, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
mockStore := &storetest.Store{}
|
|
defer mockStore.AssertExpectations(t)
|
|
|
|
mockStore.PostStore.On("GetMaxPostSize").Return(
|
|
storetest.NewStoreChannel(store.StoreResult{
|
|
Data: testCase.StoreMaxPostSize,
|
|
Err: testCase.ExpectedError,
|
|
}),
|
|
)
|
|
|
|
app := App{
|
|
Srv: &Server{
|
|
Store: mockStore,
|
|
},
|
|
config: atomic.Value{},
|
|
}
|
|
|
|
assert.Equal(t, testCase.ExpectedMaxPostSize, app.MaxPostSize())
|
|
})
|
|
}
|
|
}
|