github.com/mattermosttest/mattermost-server/v5@v5.0.0-20200917143240-9dfa12e121f9/model/post_test.go (about) 1 // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. 2 // See LICENSE.txt for license information. 3 4 package model 5 6 import ( 7 "io/ioutil" 8 "strings" 9 "sync" 10 "testing" 11 12 "github.com/stretchr/testify/assert" 13 "github.com/stretchr/testify/require" 14 ) 15 16 func TestPostToJson(t *testing.T) { 17 o := Post{Id: NewId(), Message: NewId()} 18 j := o.ToJson() 19 ro := PostFromJson(strings.NewReader(j)) 20 21 assert.NotNil(t, ro) 22 assert.Equal(t, &o, ro.Clone()) 23 } 24 25 func TestPostFromJsonError(t *testing.T) { 26 ro := PostFromJson(strings.NewReader("")) 27 assert.Nil(t, ro) 28 } 29 30 func TestPostIsValid(t *testing.T) { 31 o := Post{} 32 maxPostSize := 10000 33 34 err := o.IsValid(maxPostSize) 35 require.NotNil(t, err) 36 37 o.Id = NewId() 38 err = o.IsValid(maxPostSize) 39 require.NotNil(t, err) 40 41 o.CreateAt = GetMillis() 42 err = o.IsValid(maxPostSize) 43 require.NotNil(t, err) 44 45 o.UpdateAt = GetMillis() 46 err = o.IsValid(maxPostSize) 47 require.NotNil(t, err) 48 49 o.UserId = NewId() 50 err = o.IsValid(maxPostSize) 51 require.NotNil(t, err) 52 53 o.ChannelId = NewId() 54 o.RootId = "123" 55 err = o.IsValid(maxPostSize) 56 require.NotNil(t, err) 57 58 o.RootId = "" 59 o.ParentId = "123" 60 err = o.IsValid(maxPostSize) 61 require.NotNil(t, err) 62 63 o.ParentId = NewId() 64 o.RootId = "" 65 err = o.IsValid(maxPostSize) 66 require.NotNil(t, err) 67 68 o.ParentId = "" 69 o.Message = strings.Repeat("0", maxPostSize+1) 70 err = o.IsValid(maxPostSize) 71 require.NotNil(t, err) 72 73 o.Message = strings.Repeat("0", maxPostSize) 74 err = o.IsValid(maxPostSize) 75 require.Nil(t, err) 76 77 o.Message = "test" 78 err = o.IsValid(maxPostSize) 79 require.Nil(t, err) 80 o.Type = "junk" 81 err = o.IsValid(maxPostSize) 82 require.NotNil(t, err) 83 84 o.Type = POST_CUSTOM_TYPE_PREFIX + "type" 85 err = o.IsValid(maxPostSize) 86 require.Nil(t, err) 87 } 88 89 func TestPostPreSave(t *testing.T) { 90 o := Post{Message: "test"} 91 o.PreSave() 92 93 require.NotEqual(t, 0, o.CreateAt) 94 95 past := GetMillis() - 1 96 o = Post{Message: "test", CreateAt: past} 97 o.PreSave() 98 99 require.LessOrEqual(t, o.CreateAt, past) 100 101 o.Etag() 102 } 103 104 func TestPostIsSystemMessage(t *testing.T) { 105 post1 := Post{Message: "test_1"} 106 post1.PreSave() 107 108 require.False(t, post1.IsSystemMessage()) 109 110 post2 := Post{Message: "test_2", Type: POST_JOIN_LEAVE} 111 post2.PreSave() 112 113 require.True(t, post2.IsSystemMessage()) 114 } 115 116 func TestPostChannelMentions(t *testing.T) { 117 post := Post{Message: "~a ~b ~b ~c/~d."} 118 assert.Equal(t, []string{"a", "b", "c", "d"}, post.ChannelMentions()) 119 } 120 121 func TestPostSanitizeProps(t *testing.T) { 122 post1 := &Post{ 123 Message: "test", 124 } 125 126 post1.SanitizeProps() 127 128 require.Nil(t, post1.GetProp(PROPS_ADD_CHANNEL_MEMBER)) 129 130 post2 := &Post{ 131 Message: "test", 132 Props: StringInterface{ 133 PROPS_ADD_CHANNEL_MEMBER: "test", 134 }, 135 } 136 137 post2.SanitizeProps() 138 139 require.Nil(t, post2.GetProp(PROPS_ADD_CHANNEL_MEMBER)) 140 141 post3 := &Post{ 142 Message: "test", 143 Props: StringInterface{ 144 PROPS_ADD_CHANNEL_MEMBER: "no good", 145 "attachments": "good", 146 }, 147 } 148 149 post3.SanitizeProps() 150 151 require.Nil(t, post3.GetProp(PROPS_ADD_CHANNEL_MEMBER)) 152 153 require.NotNil(t, post3.GetProp("attachments")) 154 } 155 156 func TestPost_AttachmentsEqual(t *testing.T) { 157 post1 := &Post{} 158 post2 := &Post{} 159 for name, tc := range map[string]struct { 160 Attachments1 []*SlackAttachment 161 Attachments2 []*SlackAttachment 162 Expected bool 163 }{ 164 "Empty": { 165 nil, 166 nil, 167 true, 168 }, 169 "DifferentLength": { 170 []*SlackAttachment{ 171 { 172 Text: "Hello World", 173 }, 174 }, 175 nil, 176 false, 177 }, 178 "EqualText": { 179 []*SlackAttachment{ 180 { 181 Text: "Hello World", 182 }, 183 }, 184 []*SlackAttachment{ 185 { 186 Text: "Hello World", 187 }, 188 }, 189 true, 190 }, 191 "DifferentText": { 192 []*SlackAttachment{ 193 { 194 Text: "Hello World", 195 }, 196 }, 197 []*SlackAttachment{ 198 { 199 Text: "Hello World 2", 200 }, 201 }, 202 false, 203 }, 204 "DifferentColor": { 205 []*SlackAttachment{ 206 { 207 Text: "Hello World", 208 Color: "#152313", 209 }, 210 }, 211 []*SlackAttachment{ 212 { 213 Text: "Hello World 2", 214 }, 215 }, 216 false, 217 }, 218 "EqualFields": { 219 []*SlackAttachment{ 220 { 221 Fields: []*SlackAttachmentField{ 222 { 223 Title: "Hello World", 224 Value: "FooBar", 225 }, 226 { 227 Title: "Hello World2", 228 Value: "FooBar2", 229 }, 230 }, 231 }, 232 }, 233 []*SlackAttachment{ 234 { 235 Fields: []*SlackAttachmentField{ 236 { 237 Title: "Hello World", 238 Value: "FooBar", 239 }, 240 { 241 Title: "Hello World2", 242 Value: "FooBar2", 243 }, 244 }, 245 }, 246 }, 247 true, 248 }, 249 "DifferentFields": { 250 []*SlackAttachment{ 251 { 252 Fields: []*SlackAttachmentField{ 253 { 254 Title: "Hello World", 255 Value: "FooBar", 256 }, 257 }, 258 }, 259 }, 260 []*SlackAttachment{ 261 { 262 Fields: []*SlackAttachmentField{ 263 { 264 Title: "Hello World", 265 Value: "FooBar", 266 Short: false, 267 }, 268 { 269 Title: "Hello World2", 270 Value: "FooBar2", 271 Short: true, 272 }, 273 }, 274 }, 275 }, 276 false, 277 }, 278 "EqualActions": { 279 []*SlackAttachment{ 280 { 281 Actions: []*PostAction{ 282 { 283 Name: "FooBar", 284 Options: []*PostActionOptions{ 285 { 286 Text: "abcdef", 287 Value: "abcdef", 288 }, 289 }, 290 Integration: &PostActionIntegration{ 291 URL: "http://localhost", 292 Context: map[string]interface{}{ 293 "context": "foobar", 294 "test": 123, 295 }, 296 }, 297 }, 298 }, 299 }, 300 }, 301 []*SlackAttachment{ 302 { 303 Actions: []*PostAction{ 304 { 305 Name: "FooBar", 306 Options: []*PostActionOptions{ 307 { 308 Text: "abcdef", 309 Value: "abcdef", 310 }, 311 }, 312 Integration: &PostActionIntegration{ 313 URL: "http://localhost", 314 Context: map[string]interface{}{ 315 "context": "foobar", 316 "test": 123, 317 }, 318 }, 319 }, 320 }, 321 }, 322 }, 323 true, 324 }, 325 "DifferentActions": { 326 []*SlackAttachment{ 327 { 328 Actions: []*PostAction{ 329 { 330 Name: "FooBar", 331 Options: []*PostActionOptions{ 332 { 333 Text: "abcdef", 334 Value: "abcdef", 335 }, 336 }, 337 Integration: &PostActionIntegration{ 338 URL: "http://localhost", 339 Context: map[string]interface{}{ 340 "context": "foobar", 341 "test": "mattermost", 342 }, 343 }, 344 }, 345 }, 346 }, 347 }, 348 []*SlackAttachment{ 349 { 350 Actions: []*PostAction{ 351 { 352 Name: "FooBar", 353 Options: []*PostActionOptions{ 354 { 355 Text: "abcdef", 356 Value: "abcdef", 357 }, 358 }, 359 Integration: &PostActionIntegration{ 360 URL: "http://localhost", 361 Context: map[string]interface{}{ 362 "context": "foobar", 363 "test": 123, 364 }, 365 }, 366 }, 367 }, 368 }, 369 }, 370 false, 371 }, 372 } { 373 t.Run(name, func(t *testing.T) { 374 post1.AddProp("attachments", tc.Attachments1) 375 post2.AddProp("attachments", tc.Attachments2) 376 assert.Equal(t, tc.Expected, post1.AttachmentsEqual(post2)) 377 }) 378 } 379 } 380 381 var markdownSample, markdownSampleWithRewrittenImageURLs string 382 383 func init() { 384 bytes, err := ioutil.ReadFile("testdata/markdown-sample.md") 385 if err != nil { 386 panic(err) 387 } 388 markdownSample = string(bytes) 389 390 bytes, err = ioutil.ReadFile("testdata/markdown-sample-with-rewritten-image-urls.md") 391 if err != nil { 392 panic(err) 393 } 394 markdownSampleWithRewrittenImageURLs = string(bytes) 395 } 396 397 func TestRewriteImageURLs(t *testing.T) { 398 for name, tc := range map[string]struct { 399 Markdown string 400 Expected string 401 }{ 402 "Empty": { 403 Markdown: ``, 404 Expected: ``, 405 }, 406 "NoImages": { 407 Markdown: `foo`, 408 Expected: `foo`, 409 }, 410 "Link": { 411 Markdown: `[foo](/url)`, 412 Expected: `[foo](/url)`, 413 }, 414 "Image": { 415 Markdown: ``, 416 Expected: ``, 417 }, 418 "SpacedURL": { 419 Markdown: ``, 420 Expected: ``, 421 }, 422 "Title": { 423 Markdown: ``, 424 Expected: ``, 425 }, 426 "Parentheses": { 427 Markdown: ` "title")`, 428 Expected: ` "title")`, 429 }, 430 "AngleBrackets": { 431 Markdown: ``, 432 Expected: ``, 433 }, 434 "MultipleLines": { 435 Markdown: ``, 439 Expected: ``, 443 }, 444 "ReferenceLink": { 445 Markdown: `[foo]: </url\<1\>\\> "title" 446 [foo]`, 447 Expected: `[foo]: </url\<1\>\\> "title" 448 [foo]`, 449 }, 450 "ReferenceImage": { 451 Markdown: `[foo]: </url\<1\>\\> "title" 452 ![foo]`, 453 Expected: `[foo]: <rewritten:/url\<1\>\\> "title" 454 ![foo]`, 455 }, 456 "MultipleReferenceImages": { 457 Markdown: `[foo]: </url1> "title" 458 [bar]: </url2> 459 [baz]: /url3 "title" 460 [qux]: /url4 461 ![foo]![qux]`, 462 Expected: `[foo]: <rewritten:/url1> "title" 463 [bar]: </url2> 464 [baz]: /url3 "title" 465 [qux]: rewritten:/url4 466 ![foo]![qux]`, 467 }, 468 "DuplicateReferences": { 469 Markdown: `[foo]: </url1> "title" 470 [foo]: </url2> 471 [foo]: /url3 "title" 472 [foo]: /url4 473 ![foo]![foo]![foo]`, 474 Expected: `[foo]: <rewritten:/url1> "title" 475 [foo]: </url2> 476 [foo]: /url3 "title" 477 [foo]: /url4 478 ![foo]![foo]![foo]`, 479 }, 480 "TrailingURL": { 481 Markdown: "![foo]\n\n[foo]: /url", 482 Expected: "![foo]\n\n[foo]: rewritten:/url", 483 }, 484 "Sample": { 485 Markdown: markdownSample, 486 Expected: markdownSampleWithRewrittenImageURLs, 487 }, 488 } { 489 t.Run(name, func(t *testing.T) { 490 assert.Equal(t, tc.Expected, RewriteImageURLs(tc.Markdown, func(url string) string { 491 return "rewritten:" + url 492 })) 493 }) 494 } 495 } 496 497 var rewriteImageURLsSink string 498 499 func BenchmarkRewriteImageURLs(b *testing.B) { 500 for i := 0; i < b.N; i++ { 501 rewriteImageURLsSink = RewriteImageURLs(markdownSample, func(url string) string { 502 return "rewritten:" + url 503 }) 504 } 505 } 506 507 func TestPostShallowCopy(t *testing.T) { 508 var dst *Post 509 p := &Post{ 510 Id: NewId(), 511 } 512 513 err := p.ShallowCopy(dst) 514 require.Error(t, err) 515 516 dst = &Post{} 517 err = p.ShallowCopy(dst) 518 require.NoError(t, err) 519 require.Equal(t, p, dst) 520 require.Condition(t, func() bool { 521 return p != dst 522 }) 523 } 524 525 func TestPostClone(t *testing.T) { 526 p := &Post{ 527 Id: NewId(), 528 } 529 530 pp := p.Clone() 531 require.Equal(t, p, pp) 532 require.Condition(t, func() bool { 533 return p != pp 534 }) 535 require.Condition(t, func() bool { 536 return &p.propsMu != &pp.propsMu 537 }) 538 } 539 540 func BenchmarkClonePost(b *testing.B) { 541 p := Post{} 542 for i := 0; i < b.N; i++ { 543 _ = p.Clone() 544 } 545 } 546 547 func BenchmarkPostPropsGet_indirect(b *testing.B) { 548 p := Post{ 549 Props: make(StringInterface), 550 } 551 for i := 0; i < b.N; i++ { 552 _ = p.GetProps() 553 } 554 } 555 556 func BenchmarkPostPropsGet_direct(b *testing.B) { 557 p := Post{ 558 Props: make(StringInterface), 559 } 560 for i := 0; i < b.N; i++ { 561 _ = p.Props 562 } 563 } 564 565 func BenchmarkPostPropsAdd_indirect(b *testing.B) { 566 p := Post{ 567 Props: make(StringInterface), 568 } 569 for i := 0; i < b.N; i++ { 570 p.AddProp("test", "somevalue") 571 } 572 } 573 574 func BenchmarkPostPropsAdd_direct(b *testing.B) { 575 p := Post{ 576 Props: make(StringInterface), 577 } 578 for i := 0; i < b.N; i++ { 579 p.Props["test"] = "somevalue" 580 } 581 } 582 583 func BenchmarkPostPropsDel_indirect(b *testing.B) { 584 p := Post{ 585 Props: make(StringInterface), 586 } 587 p.AddProp("test", "somevalue") 588 for i := 0; i < b.N; i++ { 589 p.DelProp("test") 590 } 591 } 592 593 func BenchmarkPostPropsDel_direct(b *testing.B) { 594 p := Post{ 595 Props: make(StringInterface), 596 } 597 for i := 0; i < b.N; i++ { 598 delete(p.Props, "test") 599 } 600 } 601 602 func BenchmarkPostPropGet_direct(b *testing.B) { 603 p := Post{ 604 Props: make(StringInterface), 605 } 606 p.Props["somekey"] = "somevalue" 607 for i := 0; i < b.N; i++ { 608 _ = p.Props["somekey"] 609 } 610 } 611 612 func BenchmarkPostPropGet_indirect(b *testing.B) { 613 p := Post{ 614 Props: make(StringInterface), 615 } 616 p.Props["somekey"] = "somevalue" 617 for i := 0; i < b.N; i++ { 618 _ = p.GetProp("somekey") 619 } 620 } 621 622 // TestPostPropsDataRace tries to trigger data race conditions related to Post.Props. 623 // It's meant to be run with the -race flag. 624 func TestPostPropsDataRace(t *testing.T) { 625 p := Post{Message: "test"} 626 627 wg := sync.WaitGroup{} 628 wg.Add(7) 629 630 go func() { 631 for i := 0; i < 100; i++ { 632 p.AddProp("test", "test") 633 } 634 wg.Done() 635 }() 636 637 go func() { 638 for i := 0; i < 100; i++ { 639 _ = p.GetProp("test") 640 } 641 wg.Done() 642 }() 643 644 go func() { 645 for i := 0; i < 100; i++ { 646 p.AddProp("test", "test2") 647 } 648 wg.Done() 649 }() 650 651 go func() { 652 for i := 0; i < 100; i++ { 653 _ = p.GetProps()["test"] 654 } 655 wg.Done() 656 }() 657 658 go func() { 659 for i := 0; i < 100; i++ { 660 p.DelProp("test") 661 } 662 wg.Done() 663 }() 664 665 go func() { 666 for i := 0; i < 100; i++ { 667 p.SetProps(make(StringInterface)) 668 } 669 wg.Done() 670 }() 671 672 go func() { 673 for i := 0; i < 100; i++ { 674 _ = p.Clone() 675 } 676 wg.Done() 677 }() 678 679 wg.Wait() 680 } 681 682 func Test_findAtChannelMention(t *testing.T) { 683 testCases := []struct { 684 Name string 685 Message string 686 Mention string 687 Found bool 688 }{ 689 { 690 "Returns mention for @here wrapped by spaces", 691 "hi guys @here wrapped by spaces", 692 "@here", 693 true, 694 }, 695 { 696 "Returns mention for @all wrapped by spaces", 697 "hi guys @all wrapped by spaces", 698 "@all", 699 true, 700 }, 701 { 702 "Returns mention for @channel wrapped by spaces", 703 "hi guys @channel wrapped by spaces", 704 "@channel", 705 true, 706 }, 707 { 708 "Returns mention for @here wrapped by dash", 709 "-@here-", 710 "@here", 711 true, 712 }, 713 { 714 "Returns mention for @all wrapped by back tick", 715 "`@all`", 716 "@all", 717 true, 718 }, 719 { 720 "Returns mention for @channel wrapped by tags", 721 "<@channel>", 722 "@channel", 723 true, 724 }, 725 { 726 "Returns mention for @channel wrapped by asterisks", 727 "*@channel*", 728 "@channel", 729 true, 730 }, 731 { 732 "Does not return mention when prefixed by letters", 733 "hi@channel", 734 "", 735 false, 736 }, 737 { 738 "Does not return mention when suffixed by letters", 739 "hi @channelanotherword", 740 "", 741 false, 742 }, 743 { 744 "Returns mention when prefixed by word ending in special character", 745 "hi-@channel", 746 "@channel", 747 true, 748 }, 749 { 750 "Returns mention when suffixed by word starting in special character", 751 "hi @channel-guys", 752 "@channel", 753 true, 754 }, 755 } 756 for _, tc := range testCases { 757 t.Run(tc.Name, func(t *testing.T) { 758 mention, found := findAtChannelMention(tc.Message) 759 assert.Equal(t, tc.Mention, mention) 760 assert.Equal(t, tc.Found, found) 761 }) 762 } 763 } 764 765 func TestPostDisableMentionHighlights(t *testing.T) { 766 post := &Post{} 767 768 testCases := []struct { 769 Name string 770 Message string 771 ExpectedProps StringInterface 772 ExpectedMention string 773 }{ 774 { 775 "Does nothing for post with no mentions", 776 "Sample message with no mentions", 777 StringInterface(nil), 778 "", 779 }, 780 { 781 "Sets POST_PROPS_MENTION_HIGHLIGHT_DISABLED and returns mention", 782 "Sample message with @here", 783 StringInterface{POST_PROPS_MENTION_HIGHLIGHT_DISABLED: true}, 784 "@here", 785 }, 786 { 787 "Sets POST_PROPS_MENTION_HIGHLIGHT_DISABLED and returns mention", 788 "Sample message with @channel", 789 StringInterface{POST_PROPS_MENTION_HIGHLIGHT_DISABLED: true}, 790 "@channel", 791 }, 792 { 793 "Sets POST_PROPS_MENTION_HIGHLIGHT_DISABLED and returns mention", 794 "Sample message with @all", 795 StringInterface{POST_PROPS_MENTION_HIGHLIGHT_DISABLED: true}, 796 "@all", 797 }, 798 } 799 for _, tc := range testCases { 800 t.Run(tc.Name, func(t *testing.T) { 801 post.Message = tc.Message 802 mention := post.DisableMentionHighlights() 803 assert.Equal(t, tc.ExpectedMention, mention) 804 assert.Equal(t, tc.ExpectedProps, post.Props) 805 post.Props = StringInterface{} 806 }) 807 } 808 } 809 810 func TestPostPatchDisableMentionHighlights(t *testing.T) { 811 patch := &PostPatch{} 812 813 testCases := []struct { 814 Name string 815 Message string 816 ExpectedProps *StringInterface 817 }{ 818 { 819 "Does nothing for post with no mentions", 820 "Sample message with no mentions", 821 nil, 822 }, 823 { 824 "Sets POST_PROPS_MENTION_HIGHLIGHT_DISABLED", 825 "Sample message with @here", 826 &StringInterface{POST_PROPS_MENTION_HIGHLIGHT_DISABLED: true}, 827 }, 828 { 829 "Sets POST_PROPS_MENTION_HIGHLIGHT_DISABLED", 830 "Sample message with @channel", 831 &StringInterface{POST_PROPS_MENTION_HIGHLIGHT_DISABLED: true}, 832 }, 833 { 834 "Sets POST_PROPS_MENTION_HIGHLIGHT_DISABLED", 835 "Sample message with @all", 836 &StringInterface{POST_PROPS_MENTION_HIGHLIGHT_DISABLED: true}, 837 }, 838 } 839 for _, tc := range testCases { 840 t.Run(tc.Name, func(t *testing.T) { 841 patch.Message = &tc.Message 842 patch.DisableMentionHighlights() 843 if tc.ExpectedProps == nil { 844 assert.Nil(t, patch.Props) 845 } else { 846 assert.Equal(t, *tc.ExpectedProps, *patch.Props) 847 } 848 patch.Props = nil 849 }) 850 } 851 852 t.Run("TestNilMessage", func(t *testing.T) { 853 patch.Message = nil 854 patch.DisableMentionHighlights() 855 // Useless assertion to prevent compiler elision. 856 assert.Nil(t, patch.Message) 857 }) 858 }