github.com/lologarithm/mattermost-server@v5.3.2-0.20181002060438-c82a84ed765b+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 "fmt" 8 "net/http" 9 "net/http/httptest" 10 "strings" 11 "sync/atomic" 12 "testing" 13 "time" 14 15 "github.com/dyatlov/go-opengraph/opengraph" 16 "github.com/stretchr/testify/assert" 17 "github.com/stretchr/testify/require" 18 19 "github.com/mattermost/mattermost-server/model" 20 "github.com/mattermost/mattermost-server/store" 21 "github.com/mattermost/mattermost-server/store/storetest" 22 ) 23 24 func TestUpdatePostEditAt(t *testing.T) { 25 th := Setup().InitBasic() 26 defer th.TearDown() 27 28 post := &model.Post{} 29 *post = *th.BasicPost 30 31 post.IsPinned = true 32 if saved, err := th.App.UpdatePost(post, true); err != nil { 33 t.Fatal(err) 34 } else if saved.EditAt != post.EditAt { 35 t.Fatal("shouldn't have updated post.EditAt when pinning post") 36 37 *post = *saved 38 } 39 40 time.Sleep(time.Millisecond * 100) 41 42 post.Message = model.NewId() 43 if saved, err := th.App.UpdatePost(post, true); err != nil { 44 t.Fatal(err) 45 } else if saved.EditAt == post.EditAt { 46 t.Fatal("should have updated post.EditAt when updating post message") 47 } 48 49 time.Sleep(time.Millisecond * 200) 50 } 51 52 func TestUpdatePostTimeLimit(t *testing.T) { 53 th := Setup().InitBasic() 54 defer th.TearDown() 55 56 post := &model.Post{} 57 *post = *th.BasicPost 58 59 th.App.SetLicense(model.NewTestLicense()) 60 61 th.App.UpdateConfig(func(cfg *model.Config) { 62 *cfg.ServiceSettings.PostEditTimeLimit = -1 63 }) 64 if _, err := th.App.UpdatePost(post, true); err != nil { 65 t.Fatal(err) 66 } 67 68 th.App.UpdateConfig(func(cfg *model.Config) { 69 *cfg.ServiceSettings.PostEditTimeLimit = 1000000000 70 }) 71 post.Message = model.NewId() 72 if _, err := th.App.UpdatePost(post, true); err != nil { 73 t.Fatal("should allow you to edit the post") 74 } 75 76 th.App.UpdateConfig(func(cfg *model.Config) { 77 *cfg.ServiceSettings.PostEditTimeLimit = 1 78 }) 79 post.Message = model.NewId() 80 if _, err := th.App.UpdatePost(post, true); err == nil { 81 t.Fatal("should fail on update old post") 82 } 83 84 th.App.UpdateConfig(func(cfg *model.Config) { 85 *cfg.ServiceSettings.PostEditTimeLimit = -1 86 }) 87 } 88 89 func TestPostReplyToPostWhereRootPosterLeftChannel(t *testing.T) { 90 // This test ensures that when replying to a root post made by a user who has since left the channel, the reply 91 // post completes successfully. This is a regression test for PLT-6523. 92 th := Setup().InitBasic() 93 defer th.TearDown() 94 95 channel := th.BasicChannel 96 userInChannel := th.BasicUser2 97 userNotInChannel := th.BasicUser 98 rootPost := th.BasicPost 99 100 if _, err := th.App.AddUserToChannel(userInChannel, channel); err != nil { 101 t.Fatal(err) 102 } 103 104 if err := th.App.RemoveUserFromChannel(userNotInChannel.Id, "", channel); err != nil { 105 t.Fatal(err) 106 } 107 108 replyPost := model.Post{ 109 Message: "asd", 110 ChannelId: channel.Id, 111 RootId: rootPost.Id, 112 ParentId: rootPost.Id, 113 PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()), 114 UserId: userInChannel.Id, 115 CreateAt: 0, 116 } 117 118 if _, err := th.App.CreatePostAsUser(&replyPost, false); err != nil { 119 t.Fatal(err) 120 } 121 } 122 123 func TestPostAction(t *testing.T) { 124 th := Setup().InitBasic() 125 defer th.TearDown() 126 127 th.App.UpdateConfig(func(cfg *model.Config) { 128 *cfg.ServiceSettings.AllowedUntrustedInternalConnections = "localhost 127.0.0.1" 129 }) 130 131 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 132 request := model.PostActionIntegrationRequesteFromJson(r.Body) 133 assert.NotNil(t, request) 134 135 assert.Equal(t, request.UserId, th.BasicUser.Id) 136 assert.Equal(t, request.ChannelId, th.BasicChannel.Id) 137 assert.Equal(t, request.TeamId, th.BasicTeam.Id) 138 if request.Type == model.POST_ACTION_TYPE_SELECT { 139 assert.Equal(t, request.DataSource, "some_source") 140 assert.Equal(t, request.Context["selected_option"], "selected") 141 } else { 142 assert.Equal(t, request.DataSource, "") 143 } 144 assert.Equal(t, "foo", request.Context["s"]) 145 assert.EqualValues(t, 3, request.Context["n"]) 146 fmt.Fprintf(w, `{"post": {"message": "updated"}, "ephemeral_text": "foo"}`) 147 })) 148 defer ts.Close() 149 150 interactivePost := model.Post{ 151 Message: "Interactive post", 152 ChannelId: th.BasicChannel.Id, 153 PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()), 154 UserId: th.BasicUser.Id, 155 Props: model.StringInterface{ 156 "attachments": []*model.SlackAttachment{ 157 { 158 Text: "hello", 159 Actions: []*model.PostAction{ 160 { 161 Integration: &model.PostActionIntegration{ 162 Context: model.StringInterface{ 163 "s": "foo", 164 "n": 3, 165 }, 166 URL: ts.URL, 167 }, 168 Name: "action", 169 Type: "some_type", 170 DataSource: "some_source", 171 }, 172 }, 173 }, 174 }, 175 }, 176 } 177 178 post, err := th.App.CreatePostAsUser(&interactivePost, false) 179 require.Nil(t, err) 180 181 attachments, ok := post.Props["attachments"].([]*model.SlackAttachment) 182 require.True(t, ok) 183 184 require.NotEmpty(t, attachments[0].Actions) 185 require.NotEmpty(t, attachments[0].Actions[0].Id) 186 187 menuPost := model.Post{ 188 Message: "Interactive post", 189 ChannelId: th.BasicChannel.Id, 190 PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()), 191 UserId: th.BasicUser.Id, 192 Props: model.StringInterface{ 193 "attachments": []*model.SlackAttachment{ 194 { 195 Text: "hello", 196 Actions: []*model.PostAction{ 197 { 198 Integration: &model.PostActionIntegration{ 199 Context: model.StringInterface{ 200 "s": "foo", 201 "n": 3, 202 }, 203 URL: ts.URL, 204 }, 205 Name: "action", 206 Type: model.POST_ACTION_TYPE_SELECT, 207 DataSource: "some_source", 208 }, 209 }, 210 }, 211 }, 212 }, 213 } 214 215 post2, err := th.App.CreatePostAsUser(&menuPost, false) 216 require.Nil(t, err) 217 218 attachments2, ok := post2.Props["attachments"].([]*model.SlackAttachment) 219 require.True(t, ok) 220 221 require.NotEmpty(t, attachments2[0].Actions) 222 require.NotEmpty(t, attachments2[0].Actions[0].Id) 223 224 err = th.App.DoPostAction(post.Id, "notavalidid", th.BasicUser.Id, "") 225 require.NotNil(t, err) 226 assert.Equal(t, http.StatusNotFound, err.StatusCode) 227 228 err = th.App.DoPostAction(post.Id, attachments[0].Actions[0].Id, th.BasicUser.Id, "") 229 require.Nil(t, err) 230 231 err = th.App.DoPostAction(post2.Id, attachments2[0].Actions[0].Id, th.BasicUser.Id, "selected") 232 require.Nil(t, err) 233 234 th.App.UpdateConfig(func(cfg *model.Config) { 235 *cfg.ServiceSettings.AllowedUntrustedInternalConnections = "" 236 }) 237 238 err = th.App.DoPostAction(post.Id, attachments[0].Actions[0].Id, th.BasicUser.Id, "") 239 require.NotNil(t, err) 240 require.True(t, strings.Contains(err.Error(), "address forbidden")) 241 242 interactivePostPlugin := model.Post{ 243 Message: "Interactive post", 244 ChannelId: th.BasicChannel.Id, 245 PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()), 246 UserId: th.BasicUser.Id, 247 Props: model.StringInterface{ 248 "attachments": []*model.SlackAttachment{ 249 { 250 Text: "hello", 251 Actions: []*model.PostAction{ 252 { 253 Integration: &model.PostActionIntegration{ 254 Context: model.StringInterface{ 255 "s": "foo", 256 "n": 3, 257 }, 258 URL: ts.URL + "/plugins/myplugin/myaction", 259 }, 260 Name: "action", 261 Type: "some_type", 262 DataSource: "some_source", 263 }, 264 }, 265 }, 266 }, 267 }, 268 } 269 270 postplugin, err := th.App.CreatePostAsUser(&interactivePostPlugin, false) 271 require.Nil(t, err) 272 273 attachmentsPlugin, ok := postplugin.Props["attachments"].([]*model.SlackAttachment) 274 require.True(t, ok) 275 276 err = th.App.DoPostAction(postplugin.Id, attachmentsPlugin[0].Actions[0].Id, th.BasicUser.Id, "") 277 require.Nil(t, err) 278 279 th.App.UpdateConfig(func(cfg *model.Config) { 280 *cfg.ServiceSettings.SiteURL = "http://127.1.1.1" 281 }) 282 283 interactivePostSiteURL := model.Post{ 284 Message: "Interactive post", 285 ChannelId: th.BasicChannel.Id, 286 PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()), 287 UserId: th.BasicUser.Id, 288 Props: model.StringInterface{ 289 "attachments": []*model.SlackAttachment{ 290 { 291 Text: "hello", 292 Actions: []*model.PostAction{ 293 { 294 Integration: &model.PostActionIntegration{ 295 Context: model.StringInterface{ 296 "s": "foo", 297 "n": 3, 298 }, 299 URL: "http://127.1.1.1/plugins/myplugin/myaction", 300 }, 301 Name: "action", 302 Type: "some_type", 303 DataSource: "some_source", 304 }, 305 }, 306 }, 307 }, 308 }, 309 } 310 311 postSiteURL, err := th.App.CreatePostAsUser(&interactivePostSiteURL, false) 312 require.Nil(t, err) 313 314 attachmentsSiteURL, ok := postSiteURL.Props["attachments"].([]*model.SlackAttachment) 315 require.True(t, ok) 316 317 err = th.App.DoPostAction(postSiteURL.Id, attachmentsSiteURL[0].Actions[0].Id, th.BasicUser.Id, "") 318 require.NotNil(t, err) 319 require.False(t, strings.Contains(err.Error(), "address forbidden")) 320 321 th.App.UpdateConfig(func(cfg *model.Config) { 322 *cfg.ServiceSettings.SiteURL = ts.URL + "/subpath" 323 }) 324 325 interactivePostSubpath := model.Post{ 326 Message: "Interactive post", 327 ChannelId: th.BasicChannel.Id, 328 PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()), 329 UserId: th.BasicUser.Id, 330 Props: model.StringInterface{ 331 "attachments": []*model.SlackAttachment{ 332 { 333 Text: "hello", 334 Actions: []*model.PostAction{ 335 { 336 Integration: &model.PostActionIntegration{ 337 Context: model.StringInterface{ 338 "s": "foo", 339 "n": 3, 340 }, 341 URL: ts.URL + "/subpath/plugins/myplugin/myaction", 342 }, 343 Name: "action", 344 Type: "some_type", 345 DataSource: "some_source", 346 }, 347 }, 348 }, 349 }, 350 }, 351 } 352 353 postSubpath, err := th.App.CreatePostAsUser(&interactivePostSubpath, false) 354 require.Nil(t, err) 355 356 attachmentsSubpath, ok := postSubpath.Props["attachments"].([]*model.SlackAttachment) 357 require.True(t, ok) 358 359 err = th.App.DoPostAction(postSubpath.Id, attachmentsSubpath[0].Actions[0].Id, th.BasicUser.Id, "") 360 require.Nil(t, err) 361 } 362 363 func TestPostChannelMentions(t *testing.T) { 364 th := Setup().InitBasic() 365 defer th.TearDown() 366 367 channel := th.BasicChannel 368 user := th.BasicUser 369 370 channelToMention, err := th.App.CreateChannel(&model.Channel{ 371 DisplayName: "Mention Test", 372 Name: "mention-test", 373 Type: model.CHANNEL_OPEN, 374 TeamId: th.BasicTeam.Id, 375 }, false) 376 if err != nil { 377 t.Fatal(err.Error()) 378 } 379 defer th.App.PermanentDeleteChannel(channelToMention) 380 381 _, err = th.App.AddUserToChannel(user, channel) 382 require.Nil(t, err) 383 384 post := &model.Post{ 385 Message: fmt.Sprintf("hello, ~%v!", channelToMention.Name), 386 ChannelId: channel.Id, 387 PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()), 388 UserId: user.Id, 389 CreateAt: 0, 390 } 391 392 result, err := th.App.CreatePostAsUser(post, false) 393 require.Nil(t, err) 394 assert.Equal(t, map[string]interface{}{ 395 "mention-test": map[string]interface{}{ 396 "display_name": "Mention Test", 397 }, 398 }, result.Props["channel_mentions"]) 399 400 post.Message = fmt.Sprintf("goodbye, ~%v!", channelToMention.Name) 401 result, err = th.App.UpdatePost(post, false) 402 require.Nil(t, err) 403 assert.Equal(t, map[string]interface{}{ 404 "mention-test": map[string]interface{}{ 405 "display_name": "Mention Test", 406 }, 407 }, result.Props["channel_mentions"]) 408 } 409 410 func TestImageProxy(t *testing.T) { 411 th := Setup().InitBasic() 412 defer th.TearDown() 413 414 th.App.UpdateConfig(func(cfg *model.Config) { 415 *cfg.ServiceSettings.SiteURL = "http://mymattermost.com" 416 }) 417 418 for name, tc := range map[string]struct { 419 ProxyType string 420 ProxyURL string 421 ProxyOptions string 422 ImageURL string 423 ProxiedImageURL string 424 }{ 425 "atmos/camo": { 426 ProxyType: "atmos/camo", 427 ProxyURL: "https://127.0.0.1", 428 ProxyOptions: "foo", 429 ImageURL: "http://mydomain.com/myimage", 430 ProxiedImageURL: "https://127.0.0.1/f8dace906d23689e8d5b12c3cefbedbf7b9b72f5/687474703a2f2f6d79646f6d61696e2e636f6d2f6d79696d616765", 431 }, 432 "atmos/camo_SameSite": { 433 ProxyType: "atmos/camo", 434 ProxyURL: "https://127.0.0.1", 435 ProxyOptions: "foo", 436 ImageURL: "http://mymattermost.com/myimage", 437 ProxiedImageURL: "http://mymattermost.com/myimage", 438 }, 439 "atmos/camo_PathOnly": { 440 ProxyType: "atmos/camo", 441 ProxyURL: "https://127.0.0.1", 442 ProxyOptions: "foo", 443 ImageURL: "/myimage", 444 ProxiedImageURL: "/myimage", 445 }, 446 "atmos/camo_EmptyImageURL": { 447 ProxyType: "atmos/camo", 448 ProxyURL: "https://127.0.0.1", 449 ProxyOptions: "foo", 450 ImageURL: "", 451 ProxiedImageURL: "", 452 }, 453 } { 454 t.Run(name, func(t *testing.T) { 455 th.App.UpdateConfig(func(cfg *model.Config) { 456 cfg.ServiceSettings.ImageProxyType = model.NewString(tc.ProxyType) 457 cfg.ServiceSettings.ImageProxyOptions = model.NewString(tc.ProxyOptions) 458 cfg.ServiceSettings.ImageProxyURL = model.NewString(tc.ProxyURL) 459 }) 460 461 post := &model.Post{ 462 Id: model.NewId(), 463 Message: "![foo](" + tc.ImageURL + ")", 464 } 465 466 list := model.NewPostList() 467 list.Posts[post.Id] = post 468 469 assert.Equal(t, "![foo]("+tc.ProxiedImageURL+")", th.App.PostListWithProxyAddedToImageURLs(list).Posts[post.Id].Message) 470 assert.Equal(t, "![foo]("+tc.ProxiedImageURL+")", th.App.PostWithProxyAddedToImageURLs(post).Message) 471 472 assert.Equal(t, "![foo]("+tc.ImageURL+")", th.App.PostWithProxyRemovedFromImageURLs(post).Message) 473 post.Message = "![foo](" + tc.ProxiedImageURL + ")" 474 assert.Equal(t, "![foo]("+tc.ImageURL+")", th.App.PostWithProxyRemovedFromImageURLs(post).Message) 475 476 if tc.ImageURL != "" { 477 post.Message = "![foo](" + tc.ImageURL + " =500x200)" 478 assert.Equal(t, "![foo]("+tc.ProxiedImageURL+" =500x200)", th.App.PostWithProxyAddedToImageURLs(post).Message) 479 assert.Equal(t, "![foo]("+tc.ImageURL+" =500x200)", th.App.PostWithProxyRemovedFromImageURLs(post).Message) 480 post.Message = "![foo](" + tc.ProxiedImageURL + " =500x200)" 481 assert.Equal(t, "![foo]("+tc.ImageURL+" =500x200)", th.App.PostWithProxyRemovedFromImageURLs(post).Message) 482 } 483 }) 484 } 485 } 486 487 func BenchmarkForceHTMLEncodingToUTF8(b *testing.B) { 488 HTML := ` 489 <html> 490 <head> 491 <meta property="og:url" content="https://example.com/apps/mattermost"> 492 <meta property="og:image" content="https://images.example.com/image.png"> 493 </head> 494 </html> 495 ` 496 ContentType := "text/html; utf-8" 497 498 b.Run("with converting", func(b *testing.B) { 499 for i := 0; i < b.N; i++ { 500 r := forceHTMLEncodingToUTF8(strings.NewReader(HTML), ContentType) 501 502 og := opengraph.NewOpenGraph() 503 og.ProcessHTML(r) 504 } 505 }) 506 507 b.Run("without converting", func(b *testing.B) { 508 for i := 0; i < b.N; i++ { 509 og := opengraph.NewOpenGraph() 510 og.ProcessHTML(strings.NewReader(HTML)) 511 } 512 }) 513 } 514 515 func TestMakeOpenGraphURLsAbsolute(t *testing.T) { 516 for name, tc := range map[string]struct { 517 HTML string 518 RequestURL string 519 URL string 520 ImageURL string 521 }{ 522 "absolute URLs": { 523 HTML: ` 524 <html> 525 <head> 526 <meta property="og:url" content="https://example.com/apps/mattermost"> 527 <meta property="og:image" content="https://images.example.com/image.png"> 528 </head> 529 </html>`, 530 RequestURL: "https://example.com", 531 URL: "https://example.com/apps/mattermost", 532 ImageURL: "https://images.example.com/image.png", 533 }, 534 "URLs starting with /": { 535 HTML: ` 536 <html> 537 <head> 538 <meta property="og:url" content="/apps/mattermost"> 539 <meta property="og:image" content="/image.png"> 540 </head> 541 </html>`, 542 RequestURL: "http://example.com", 543 URL: "http://example.com/apps/mattermost", 544 ImageURL: "http://example.com/image.png", 545 }, 546 "HTTPS URLs starting with /": { 547 HTML: ` 548 <html> 549 <head> 550 <meta property="og:url" content="/apps/mattermost"> 551 <meta property="og:image" content="/image.png"> 552 </head> 553 </html>`, 554 RequestURL: "https://example.com", 555 URL: "https://example.com/apps/mattermost", 556 ImageURL: "https://example.com/image.png", 557 }, 558 "missing image URL": { 559 HTML: ` 560 <html> 561 <head> 562 <meta property="og:url" content="/apps/mattermost"> 563 </head> 564 </html>`, 565 RequestURL: "http://example.com", 566 URL: "http://example.com/apps/mattermost", 567 ImageURL: "", 568 }, 569 "relative URLs": { 570 HTML: ` 571 <html> 572 <head> 573 <meta property="og:url" content="index.html"> 574 <meta property="og:image" content="../resources/image.png"> 575 </head> 576 </html>`, 577 RequestURL: "http://example.com/content/index.html", 578 URL: "http://example.com/content/index.html", 579 ImageURL: "http://example.com/resources/image.png", 580 }, 581 } { 582 t.Run(name, func(t *testing.T) { 583 og := opengraph.NewOpenGraph() 584 if err := og.ProcessHTML(strings.NewReader(tc.HTML)); err != nil { 585 t.Fatal(err) 586 } 587 588 makeOpenGraphURLsAbsolute(og, tc.RequestURL) 589 590 if og.URL != tc.URL { 591 t.Fatalf("incorrect url, expected %v, got %v", tc.URL, og.URL) 592 } 593 594 if len(og.Images) > 0 { 595 if og.Images[0].URL != tc.ImageURL { 596 t.Fatalf("incorrect image url, expected %v, got %v", tc.ImageURL, og.Images[0].URL) 597 } 598 } else if tc.ImageURL != "" { 599 t.Fatalf("missing image url, expected %v, got nothing", tc.ImageURL) 600 } 601 }) 602 } 603 } 604 605 func TestMaxPostSize(t *testing.T) { 606 t.Parallel() 607 608 testCases := []struct { 609 Description string 610 StoreMaxPostSize int 611 ExpectedMaxPostSize int 612 ExpectedError *model.AppError 613 }{ 614 { 615 "error fetching max post size", 616 0, 617 model.POST_MESSAGE_MAX_RUNES_V1, 618 model.NewAppError("TestMaxPostSize", "this is an error", nil, "", http.StatusBadRequest), 619 }, 620 { 621 "4000 rune limit", 622 4000, 623 4000, 624 nil, 625 }, 626 { 627 "16383 rune limit", 628 16383, 629 16383, 630 nil, 631 }, 632 } 633 634 for _, testCase := range testCases { 635 testCase := testCase 636 t.Run(testCase.Description, func(t *testing.T) { 637 t.Parallel() 638 639 mockStore := &storetest.Store{} 640 defer mockStore.AssertExpectations(t) 641 642 mockStore.PostStore.On("GetMaxPostSize").Return( 643 storetest.NewStoreChannel(store.StoreResult{ 644 Data: testCase.StoreMaxPostSize, 645 Err: testCase.ExpectedError, 646 }), 647 ) 648 649 app := App{ 650 Srv: &Server{ 651 Store: mockStore, 652 }, 653 config: atomic.Value{}, 654 } 655 656 assert.Equal(t, testCase.ExpectedMaxPostSize, app.MaxPostSize()) 657 }) 658 } 659 }