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 }