github.com/gigforks/mattermost-server@v4.9.1-0.20180619094218-800d97fa55d0+incompatible/app/post_test.go (about)

     1  // Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
     2  // See License.txt for license information.
     3  
     4  package app
     5  
     6  import (
     7  	"encoding/json"
     8  	"fmt"
     9  	"net/http"
    10  	"net/http/httptest"
    11  	"strings"
    12  	"sync/atomic"
    13  	"testing"
    14  	"time"
    15  
    16  	"github.com/dyatlov/go-opengraph/opengraph"
    17  	"github.com/stretchr/testify/assert"
    18  	"github.com/stretchr/testify/require"
    19  
    20  	"github.com/mattermost/mattermost-server/model"
    21  	"github.com/mattermost/mattermost-server/store"
    22  	"github.com/mattermost/mattermost-server/store/storetest"
    23  )
    24  
    25  func TestUpdatePostEditAt(t *testing.T) {
    26  	th := Setup().InitBasic()
    27  	defer th.TearDown()
    28  
    29  	post := &model.Post{}
    30  	*post = *th.BasicPost
    31  
    32  	post.IsPinned = true
    33  	if saved, err := th.App.UpdatePost(post, true); err != nil {
    34  		t.Fatal(err)
    35  	} else if saved.EditAt != post.EditAt {
    36  		t.Fatal("shouldn't have updated post.EditAt when pinning post")
    37  
    38  		*post = *saved
    39  	}
    40  
    41  	time.Sleep(time.Millisecond * 100)
    42  
    43  	post.Message = model.NewId()
    44  	if saved, err := th.App.UpdatePost(post, true); err != nil {
    45  		t.Fatal(err)
    46  	} else if saved.EditAt == post.EditAt {
    47  		t.Fatal("should have updated post.EditAt when updating post message")
    48  	}
    49  }
    50  
    51  func TestUpdatePostTimeLimit(t *testing.T) {
    52  	th := Setup().InitBasic()
    53  	defer th.TearDown()
    54  
    55  	post := &model.Post{}
    56  	*post = *th.BasicPost
    57  
    58  	th.App.SetLicense(model.NewTestLicense())
    59  
    60  	th.App.UpdateConfig(func(cfg *model.Config) {
    61  		*cfg.ServiceSettings.PostEditTimeLimit = -1
    62  	})
    63  	if _, err := th.App.UpdatePost(post, true); err != nil {
    64  		t.Fatal(err)
    65  	}
    66  
    67  	th.App.UpdateConfig(func(cfg *model.Config) {
    68  		*cfg.ServiceSettings.PostEditTimeLimit = 1000000000
    69  	})
    70  	post.Message = model.NewId()
    71  	if _, err := th.App.UpdatePost(post, true); err != nil {
    72  		t.Fatal("should allow you to edit the post")
    73  	}
    74  
    75  	th.App.UpdateConfig(func(cfg *model.Config) {
    76  		*cfg.ServiceSettings.PostEditTimeLimit = 1
    77  	})
    78  	post.Message = model.NewId()
    79  	if _, err := th.App.UpdatePost(post, true); err == nil {
    80  		t.Fatal("should fail on update old post")
    81  	}
    82  
    83  	th.App.UpdateConfig(func(cfg *model.Config) {
    84  		*cfg.ServiceSettings.PostEditTimeLimit = -1
    85  	})
    86  }
    87  
    88  func TestPostReplyToPostWhereRootPosterLeftChannel(t *testing.T) {
    89  	// This test ensures that when replying to a root post made by a user who has since left the channel, the reply
    90  	// post completes successfully. This is a regression test for PLT-6523.
    91  	th := Setup().InitBasic()
    92  	defer th.TearDown()
    93  
    94  	channel := th.BasicChannel
    95  	userInChannel := th.BasicUser2
    96  	userNotInChannel := th.BasicUser
    97  	rootPost := th.BasicPost
    98  
    99  	if _, err := th.App.AddUserToChannel(userInChannel, channel); err != nil {
   100  		t.Fatal(err)
   101  	}
   102  
   103  	if err := th.App.RemoveUserFromChannel(userNotInChannel.Id, "", channel); err != nil {
   104  		t.Fatal(err)
   105  	}
   106  
   107  	replyPost := model.Post{
   108  		Message:       "asd",
   109  		ChannelId:     channel.Id,
   110  		RootId:        rootPost.Id,
   111  		ParentId:      rootPost.Id,
   112  		PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()),
   113  		UserId:        userInChannel.Id,
   114  		CreateAt:      0,
   115  	}
   116  
   117  	if _, err := th.App.CreatePostAsUser(&replyPost); err != nil {
   118  		t.Fatal(err)
   119  	}
   120  }
   121  
   122  func TestPostAction(t *testing.T) {
   123  	th := Setup().InitBasic()
   124  	defer th.TearDown()
   125  
   126  	th.App.UpdateConfig(func(cfg *model.Config) {
   127  		*cfg.ServiceSettings.AllowedUntrustedInternalConnections = "localhost 127.0.0.1"
   128  	})
   129  
   130  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   131  		var request model.PostActionIntegrationRequest
   132  		err := json.NewDecoder(r.Body).Decode(&request)
   133  		assert.NoError(t, err)
   134  		assert.Equal(t, request.UserId, th.BasicUser.Id)
   135  		assert.Equal(t, "foo", request.Context["s"])
   136  		assert.EqualValues(t, 3, request.Context["n"])
   137  		fmt.Fprintf(w, `{"update": {"message": "updated"}, "ephemeral_text": "foo"}`)
   138  	}))
   139  	defer ts.Close()
   140  
   141  	interactivePost := model.Post{
   142  		Message:       "Interactive post",
   143  		ChannelId:     th.BasicChannel.Id,
   144  		PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()),
   145  		UserId:        th.BasicUser.Id,
   146  		Props: model.StringInterface{
   147  			"attachments": []*model.SlackAttachment{
   148  				{
   149  					Text: "hello",
   150  					Actions: []*model.PostAction{
   151  						{
   152  							Integration: &model.PostActionIntegration{
   153  								Context: model.StringInterface{
   154  									"s": "foo",
   155  									"n": 3,
   156  								},
   157  								URL: ts.URL,
   158  							},
   159  							Name: "action",
   160  						},
   161  					},
   162  				},
   163  			},
   164  		},
   165  	}
   166  
   167  	post, err := th.App.CreatePostAsUser(&interactivePost)
   168  	require.Nil(t, err)
   169  
   170  	attachments, ok := post.Props["attachments"].([]*model.SlackAttachment)
   171  	require.True(t, ok)
   172  
   173  	require.NotEmpty(t, attachments[0].Actions)
   174  	require.NotEmpty(t, attachments[0].Actions[0].Id)
   175  
   176  	err = th.App.DoPostAction(post.Id, "notavalidid", th.BasicUser.Id)
   177  	require.NotNil(t, err)
   178  	assert.Equal(t, http.StatusNotFound, err.StatusCode)
   179  
   180  	err = th.App.DoPostAction(post.Id, attachments[0].Actions[0].Id, th.BasicUser.Id)
   181  	require.Nil(t, err)
   182  }
   183  
   184  func TestPostChannelMentions(t *testing.T) {
   185  	th := Setup().InitBasic()
   186  	defer th.TearDown()
   187  
   188  	channel := th.BasicChannel
   189  	user := th.BasicUser
   190  
   191  	channelToMention, err := th.App.CreateChannel(&model.Channel{
   192  		DisplayName: "Mention Test",
   193  		Name:        "mention-test",
   194  		Type:        model.CHANNEL_OPEN,
   195  		TeamId:      th.BasicTeam.Id,
   196  	}, false)
   197  	if err != nil {
   198  		t.Fatal(err.Error())
   199  	}
   200  	defer th.App.PermanentDeleteChannel(channelToMention)
   201  
   202  	_, err = th.App.AddUserToChannel(user, channel)
   203  	require.Nil(t, err)
   204  
   205  	post := &model.Post{
   206  		Message:       fmt.Sprintf("hello, ~%v!", channelToMention.Name),
   207  		ChannelId:     channel.Id,
   208  		PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()),
   209  		UserId:        user.Id,
   210  		CreateAt:      0,
   211  	}
   212  
   213  	result, err := th.App.CreatePostAsUser(post)
   214  	require.Nil(t, err)
   215  	assert.Equal(t, map[string]interface{}{
   216  		"mention-test": map[string]interface{}{
   217  			"display_name": "Mention Test",
   218  		},
   219  	}, result.Props["channel_mentions"])
   220  
   221  	post.Message = fmt.Sprintf("goodbye, ~%v!", channelToMention.Name)
   222  	result, err = th.App.UpdatePost(post, false)
   223  	require.Nil(t, err)
   224  	assert.Equal(t, map[string]interface{}{
   225  		"mention-test": map[string]interface{}{
   226  			"display_name": "Mention Test",
   227  		},
   228  	}, result.Props["channel_mentions"])
   229  }
   230  
   231  func TestImageProxy(t *testing.T) {
   232  	th := Setup().InitBasic()
   233  	defer th.TearDown()
   234  
   235  	th.App.UpdateConfig(func(cfg *model.Config) {
   236  		*cfg.ServiceSettings.SiteURL = "http://mymattermost.com"
   237  	})
   238  
   239  	for name, tc := range map[string]struct {
   240  		ProxyType       string
   241  		ProxyURL        string
   242  		ProxyOptions    string
   243  		ImageURL        string
   244  		ProxiedImageURL string
   245  	}{
   246  		"atmos/camo": {
   247  			ProxyType:       "atmos/camo",
   248  			ProxyURL:        "https://127.0.0.1",
   249  			ProxyOptions:    "foo",
   250  			ImageURL:        "http://mydomain.com/myimage",
   251  			ProxiedImageURL: "https://127.0.0.1/f8dace906d23689e8d5b12c3cefbedbf7b9b72f5/687474703a2f2f6d79646f6d61696e2e636f6d2f6d79696d616765",
   252  		},
   253  		"atmos/camo_SameSite": {
   254  			ProxyType:       "atmos/camo",
   255  			ProxyURL:        "https://127.0.0.1",
   256  			ProxyOptions:    "foo",
   257  			ImageURL:        "http://mymattermost.com/myimage",
   258  			ProxiedImageURL: "http://mymattermost.com/myimage",
   259  		},
   260  		"atmos/camo_PathOnly": {
   261  			ProxyType:       "atmos/camo",
   262  			ProxyURL:        "https://127.0.0.1",
   263  			ProxyOptions:    "foo",
   264  			ImageURL:        "/myimage",
   265  			ProxiedImageURL: "/myimage",
   266  		},
   267  		"atmos/camo_EmptyImageURL": {
   268  			ProxyType:       "atmos/camo",
   269  			ProxyURL:        "https://127.0.0.1",
   270  			ProxyOptions:    "foo",
   271  			ImageURL:        "",
   272  			ProxiedImageURL: "",
   273  		},
   274  	} {
   275  		t.Run(name, func(t *testing.T) {
   276  			th.App.UpdateConfig(func(cfg *model.Config) {
   277  				cfg.ServiceSettings.ImageProxyType = model.NewString(tc.ProxyType)
   278  				cfg.ServiceSettings.ImageProxyOptions = model.NewString(tc.ProxyOptions)
   279  				cfg.ServiceSettings.ImageProxyURL = model.NewString(tc.ProxyURL)
   280  			})
   281  
   282  			post := &model.Post{
   283  				Id:      model.NewId(),
   284  				Message: "![foo](" + tc.ImageURL + ")",
   285  			}
   286  
   287  			list := model.NewPostList()
   288  			list.Posts[post.Id] = post
   289  
   290  			assert.Equal(t, "![foo]("+tc.ProxiedImageURL+")", th.App.PostListWithProxyAddedToImageURLs(list).Posts[post.Id].Message)
   291  			assert.Equal(t, "![foo]("+tc.ProxiedImageURL+")", th.App.PostWithProxyAddedToImageURLs(post).Message)
   292  
   293  			assert.Equal(t, "![foo]("+tc.ImageURL+")", th.App.PostWithProxyRemovedFromImageURLs(post).Message)
   294  			post.Message = "![foo](" + tc.ProxiedImageURL + ")"
   295  			assert.Equal(t, "![foo]("+tc.ImageURL+")", th.App.PostWithProxyRemovedFromImageURLs(post).Message)
   296  		})
   297  	}
   298  }
   299  
   300  func BenchmarkForceHTMLEncodingToUTF8(b *testing.B) {
   301  	HTML := `
   302  		<html>
   303  			<head>
   304  				<meta property="og:url" content="https://example.com/apps/mattermost">
   305  				<meta property="og:image" content="https://images.example.com/image.png">
   306  			</head>
   307  		</html>
   308  	`
   309  	ContentType := "text/html; utf-8"
   310  
   311  	b.Run("with converting", func(b *testing.B) {
   312  		for i := 0; i < b.N; i++ {
   313  			r := forceHTMLEncodingToUTF8(strings.NewReader(HTML), ContentType)
   314  
   315  			og := opengraph.NewOpenGraph()
   316  			og.ProcessHTML(r)
   317  		}
   318  	})
   319  
   320  	b.Run("without converting", func(b *testing.B) {
   321  		for i := 0; i < b.N; i++ {
   322  			og := opengraph.NewOpenGraph()
   323  			og.ProcessHTML(strings.NewReader(HTML))
   324  		}
   325  	})
   326  }
   327  
   328  func TestMakeOpenGraphURLsAbsolute(t *testing.T) {
   329  	for name, tc := range map[string]struct {
   330  		HTML       string
   331  		RequestURL string
   332  		URL        string
   333  		ImageURL   string
   334  	}{
   335  		"absolute URLs": {
   336  			HTML: `
   337  				<html>
   338  					<head>
   339  						<meta property="og:url" content="https://example.com/apps/mattermost">
   340  						<meta property="og:image" content="https://images.example.com/image.png">
   341  					</head>
   342  				</html>`,
   343  			RequestURL: "https://example.com",
   344  			URL:        "https://example.com/apps/mattermost",
   345  			ImageURL:   "https://images.example.com/image.png",
   346  		},
   347  		"URLs starting with /": {
   348  			HTML: `
   349  				<html>
   350  					<head>
   351  						<meta property="og:url" content="/apps/mattermost">
   352  						<meta property="og:image" content="/image.png">
   353  					</head>
   354  				</html>`,
   355  			RequestURL: "http://example.com",
   356  			URL:        "http://example.com/apps/mattermost",
   357  			ImageURL:   "http://example.com/image.png",
   358  		},
   359  		"HTTPS URLs starting with /": {
   360  			HTML: `
   361  				<html>
   362  					<head>
   363  						<meta property="og:url" content="/apps/mattermost">
   364  						<meta property="og:image" content="/image.png">
   365  					</head>
   366  				</html>`,
   367  			RequestURL: "https://example.com",
   368  			URL:        "https://example.com/apps/mattermost",
   369  			ImageURL:   "https://example.com/image.png",
   370  		},
   371  		"missing image URL": {
   372  			HTML: `
   373  				<html>
   374  					<head>
   375  						<meta property="og:url" content="/apps/mattermost">
   376  					</head>
   377  				</html>`,
   378  			RequestURL: "http://example.com",
   379  			URL:        "http://example.com/apps/mattermost",
   380  			ImageURL:   "",
   381  		},
   382  		"relative URLs": {
   383  			HTML: `
   384  				<html>
   385  					<head>
   386  						<meta property="og:url" content="index.html">
   387  						<meta property="og:image" content="../resources/image.png">
   388  					</head>
   389  				</html>`,
   390  			RequestURL: "http://example.com/content/index.html",
   391  			URL:        "http://example.com/content/index.html",
   392  			ImageURL:   "http://example.com/resources/image.png",
   393  		},
   394  	} {
   395  		t.Run(name, func(t *testing.T) {
   396  			og := opengraph.NewOpenGraph()
   397  			if err := og.ProcessHTML(strings.NewReader(tc.HTML)); err != nil {
   398  				t.Fatal(err)
   399  			}
   400  
   401  			makeOpenGraphURLsAbsolute(og, tc.RequestURL)
   402  
   403  			if og.URL != tc.URL {
   404  				t.Fatalf("incorrect url, expected %v, got %v", tc.URL, og.URL)
   405  			}
   406  
   407  			if len(og.Images) > 0 {
   408  				if og.Images[0].URL != tc.ImageURL {
   409  					t.Fatalf("incorrect image url, expected %v, got %v", tc.ImageURL, og.Images[0].URL)
   410  				}
   411  			} else if tc.ImageURL != "" {
   412  				t.Fatalf("missing image url, expected %v, got nothing", tc.ImageURL)
   413  			}
   414  		})
   415  	}
   416  }
   417  
   418  func TestMaxPostSize(t *testing.T) {
   419  	t.Parallel()
   420  
   421  	testCases := []struct {
   422  		Description         string
   423  		StoreMaxPostSize    int
   424  		ExpectedMaxPostSize int
   425  		ExpectedError       *model.AppError
   426  	}{
   427  		{
   428  			"error fetching max post size",
   429  			0,
   430  			model.POST_MESSAGE_MAX_RUNES_V1,
   431  			model.NewAppError("TestMaxPostSize", "this is an error", nil, "", http.StatusBadRequest),
   432  		},
   433  		{
   434  			"4000 rune limit",
   435  			4000,
   436  			4000,
   437  			nil,
   438  		},
   439  		{
   440  			"16383 rune limit",
   441  			16383,
   442  			16383,
   443  			nil,
   444  		},
   445  	}
   446  
   447  	for _, testCase := range testCases {
   448  		testCase := testCase
   449  		t.Run(testCase.Description, func(t *testing.T) {
   450  			t.Parallel()
   451  
   452  			mockStore := &storetest.Store{}
   453  			defer mockStore.AssertExpectations(t)
   454  
   455  			mockStore.PostStore.On("GetMaxPostSize").Return(
   456  				storetest.NewStoreChannel(store.StoreResult{
   457  					Data: testCase.StoreMaxPostSize,
   458  					Err:  testCase.ExpectedError,
   459  				}),
   460  			)
   461  
   462  			app := App{
   463  				Srv: &Server{
   464  					Store: mockStore,
   465  				},
   466  				config: atomic.Value{},
   467  			}
   468  
   469  			assert.Equal(t, testCase.ExpectedMaxPostSize, app.MaxPostSize())
   470  		})
   471  	}
   472  }