github.com/status-im/status-go@v1.1.0/protocol/messenger_linkpreview_test.go (about) 1 package protocol 2 3 import ( 4 "bytes" 5 "fmt" 6 "io/ioutil" 7 "math" 8 "net/http" 9 "net/url" 10 "regexp" 11 "strings" 12 "testing" 13 "time" 14 15 "github.com/stretchr/testify/suite" 16 17 "github.com/status-im/status-go/eth-node/crypto" 18 "github.com/status-im/status-go/images" 19 "github.com/status-im/status-go/multiaccounts/accounts" 20 "github.com/status-im/status-go/multiaccounts/settings" 21 "github.com/status-im/status-go/protocol/common" 22 "github.com/status-im/status-go/protocol/protobuf" 23 "github.com/status-im/status-go/protocol/requests" 24 ) 25 26 const ( 27 exampleIdenticonURI = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixA" + 28 "AAAiklEQVR4nOzWwQmFQAwG4ffEXmzLIizDImzLarQBhSwSGH7mO+9hh0DI9AthCI0hNIbQGEJjCI0hNIbQxITM1YfHfl69X3m2bsu/8i5mI" + 29 "obQGEJjCI0hNIbQlG+tUW83UtfNFjMRQ2gMofm8tUa3U9c2i5mIITSGqEnMRAyhMYTGEBpDaO4AAAD//5POEGncqtj1AAAAAElFTkSuQmCC" 30 ) 31 32 func TestMessengerLinkPreviews(t *testing.T) { 33 suite.Run(t, new(MessengerLinkPreviewsTestSuite)) 34 } 35 36 type MessengerLinkPreviewsTestSuite struct { 37 MessengerBaseTestSuite 38 } 39 40 // StubMatcher should either return an http.Response or nil in case the request 41 // doesn't match. 42 type StubMatcher func(req *http.Request) *http.Response 43 44 type StubTransport struct { 45 // fallbackToDefaultTransport when true will make the transport use 46 // http.DefaultTransport in case no matcher is found. 47 fallbackToDefaultTransport bool 48 // disabledStubs when true, will skip all matchers and use 49 // http.DefaultTransport. 50 // 51 // Useful while testing to toggle between the original and stubbed responses. 52 disabledStubs bool 53 // matchers are http.RoundTripper functions. 54 matchers []StubMatcher 55 } 56 57 // RoundTrip returns a stubbed response if any matcher returns a non-nil 58 // http.Response. If no matcher is found and fallbackToDefaultTransport is true, 59 // then it executes the HTTP request using the default http transport. 60 // 61 // If StubTransport#disabledStubs is true, the default http transport is used. 62 func (t *StubTransport) RoundTrip(req *http.Request) (*http.Response, error) { 63 if t.disabledStubs { 64 return http.DefaultTransport.RoundTrip(req) 65 } 66 67 for _, matcher := range t.matchers { 68 res := matcher(req) 69 if res != nil { 70 return res, nil 71 } 72 } 73 74 if t.fallbackToDefaultTransport { 75 return http.DefaultTransport.RoundTrip(req) 76 } 77 78 return nil, fmt.Errorf("no HTTP matcher found") 79 } 80 81 func (t *StubTransport) AddURLMatcherRoundTrip(urlRegexp string, roundTrip func(r *http.Request) *http.Response) { 82 matcher := func(req *http.Request) *http.Response { 83 rx, err := regexp.Compile(regexp.QuoteMeta(urlRegexp)) 84 if err != nil { 85 return nil 86 } 87 if !rx.MatchString(req.URL.String()) { 88 return nil 89 } 90 return roundTrip(req) 91 } 92 t.matchers = append(t.matchers, matcher) 93 } 94 95 // Add a matcher based on a URL regexp. If a given request URL matches the 96 // regexp, then responseBody will be returned with a hardcoded 200 status code. 97 // If headers is non-nil, use it as the value of http.Response.Header. 98 func (t *StubTransport) AddURLMatcher(urlRegexp string, responseBody []byte, headers map[string]string) { 99 matcher := func(req *http.Request) *http.Response { 100 res := &http.Response{ 101 StatusCode: http.StatusOK, 102 Body: ioutil.NopCloser(bytes.NewBuffer(responseBody)), 103 } 104 105 if headers != nil { 106 res.Header = http.Header{} 107 for k, v := range headers { 108 res.Header.Set(k, v) 109 } 110 } 111 112 return res 113 } 114 t.AddURLMatcherRoundTrip(urlRegexp, matcher) 115 } 116 117 // assertContainsLongString verifies if actual contains a slice of expected and 118 // correctly prints the cause of the failure. The default behavior of 119 // require.Contains with long strings is to not print the formatted message 120 // (varargs to require.Contains). 121 func (s *MessengerLinkPreviewsTestSuite) assertContainsLongString(expected string, actual string, maxLength int) { 122 var safeIdx float64 123 var actualShort string 124 var expectedShort string 125 126 if len(actual) > 0 { 127 safeIdx = math.Min(float64(maxLength), float64(len(actual)-1)) 128 actualShort = actual[:int(safeIdx)] 129 } 130 131 if len(expected) > 0 { 132 safeIdx = math.Min(float64(maxLength), float64(len(expected)-1)) 133 expectedShort = expected[:int(safeIdx)] 134 } 135 136 s.Require().Contains( 137 actual, expected, 138 "'%s' should contain '%s'", 139 actualShort, 140 expectedShort, 141 ) 142 } 143 144 func (s *MessengerLinkPreviewsTestSuite) Test_GetLinks() { 145 examples := []struct { 146 args string 147 expected []string 148 }{ 149 // Invalid URLs are not taken in consideration. 150 {args: "", expected: []string{}}, 151 {args: " ", expected: []string{}}, 152 {args: "https", expected: []string{}}, 153 {args: "https://", expected: []string{}}, 154 {args: "https://status", expected: []string{}}, 155 {args: "https://status.", expected: []string{}}, 156 // URLs must include the sheme. 157 {args: "status.com", expected: []string{}}, 158 159 {args: "https://status.im", expected: []string{"https://status.im"}}, 160 161 // Only the host should be lowercased. 162 {args: "HTTPS://STATUS.IM/path/to?Q=AbCdE", expected: []string{"https://status.im/path/to?Q=AbCdE"}}, 163 164 // Remove trailing forward slash. 165 {args: "https://github.com/", expected: []string{"https://github.com"}}, 166 {args: "https://www.youtube.com/watch?v=mzOyYtfXkb0/", expected: []string{"https://www.youtube.com/watch?v=mzOyYtfXkb0"}}, 167 168 // Valid URL. 169 {args: "https://status.c", expected: []string{"https://status.c"}}, 170 {args: "https://status.im/test", expected: []string{"https://status.im/test"}}, 171 {args: "https://192.168.0.100:9999/xyz", expected: []string{"https://192.168.0.100:9999/xyz"}}, 172 173 // There is a bug in the code that builds the AST from markdown text, 174 // because it removes the closing parenthesis, which means it won't be 175 // possible to unfurl this URL. 176 {args: "https://en.wikipedia.org/wiki/Status_message_(instant_messaging)", expected: []string{"https://en.wikipedia.org/wiki/Status_message_(instant_messaging"}}, 177 178 // Multiple URLs. 179 { 180 args: "https://status.im/test https://www.youtube.com/watch?v=mzOyYtfXkb0", 181 expected: []string{"https://status.im/test", "https://www.youtube.com/watch?v=mzOyYtfXkb0"}, 182 }, 183 { 184 args: "status.im https://www.youtube.com/watch?v=mzOyYtfXkb0", 185 expected: []string{"https://www.youtube.com/watch?v=mzOyYtfXkb0"}, 186 }, 187 } 188 189 for _, ex := range examples { 190 links := s.m.GetURLs(ex.args) 191 s.Require().Equal(ex.expected, links, "Failed for args: '%s'", ex.args) 192 } 193 } 194 195 func (s *MessengerLinkPreviewsTestSuite) readAsset(filename string) []byte { 196 b, err := ioutil.ReadFile("../_assets/tests/" + filename) 197 s.Require().NoError(err) 198 return b 199 } 200 201 func (s *MessengerLinkPreviewsTestSuite) Test_GetFavicon() { 202 goodHTMLPNG := []byte( 203 ` 204 <html> 205 <head> 206 <link rel="shortcut icon" href="https://www.somehost.com/favicon.png"> 207 </head> 208 </html>`) 209 210 goodHTMLSVG := []byte( 211 ` 212 <html> 213 <head> 214 <link rel="shortcut icon" href="https://www.somehost.com/favicon.svg"> 215 </head> 216 </html>`) 217 218 goodHTMLICO := []byte( 219 ` 220 <html> 221 <head> 222 <link rel="shortcut icon" href="https://www.somehost.com/favicon.ico"> 223 </head> 224 </html>`) 225 226 badHTMLNoRelAttr := []byte( 227 ` 228 <html> 229 <head> 230 <link href="https://www.somehost.com/favicon.png"> 231 </head> 232 </html>`) 233 234 GoodHTMLRelAttributeIcon := []byte( 235 ` 236 <html> 237 <head> 238 <link rel="icon" href="https://www.somehost.com/favicon.png"> 239 </head> 240 </html>`) 241 242 faviconPath := GetFavicon(goodHTMLPNG) 243 s.Require().Equal("https://www.somehost.com/favicon.png", faviconPath) 244 245 faviconPath = GetFavicon(goodHTMLSVG) 246 s.Require().Equal("https://www.somehost.com/favicon.svg", faviconPath) 247 248 faviconPath = GetFavicon(goodHTMLICO) 249 s.Require().Equal("https://www.somehost.com/favicon.ico", faviconPath) 250 251 faviconPath = GetFavicon(GoodHTMLRelAttributeIcon) 252 s.Require().Equal("https://www.somehost.com/favicon.png", faviconPath) 253 254 faviconPath = GetFavicon(badHTMLNoRelAttr) 255 s.Require().Equal("", faviconPath) 256 } 257 258 func (s *MessengerLinkPreviewsTestSuite) Test_UnfurlURLs_YouTube() { 259 u := "https://www.youtube.com/watch?v=lE4UXdJSJM4" 260 thumbnailURL := "https://i.ytimg.com/vi/lE4UXdJSJM4/maxresdefault.jpg" 261 expected := common.LinkPreview{ 262 Type: protobuf.UnfurledLink_LINK, 263 URL: u, 264 Hostname: "www.youtube.com", 265 Title: "Interview with a GNU/Linux user - Partition 1", 266 Description: "GNU/Linux Operating SystemInterview with a GNU/Linux user with Richie Guix - aired on © The GNU Linux.Programmer humorLinux humorProgramming jokesProgramming...", 267 Thumbnail: common.LinkPreviewThumbnail{ 268 Width: 1, 269 Height: 1, 270 DataURI: "data:image/webp;base64,UklGRiQAAABXRUJQVlA4IBgAAAAwAQCdASoBAAEAAQAaJaQAA3AA/vpMgAA", 271 }, 272 } 273 favicon := "https://www.youtube.com/s/desktop/87423d78/img/favicon.ico" 274 transport := StubTransport{} 275 transport.AddURLMatcher( 276 u, 277 []byte(fmt.Sprintf(` 278 <html> 279 <head> 280 <meta property="og:title" content="%s"> 281 <meta property="og:description" content="%s"> 282 <meta property="og:image" content="%s"> 283 <link rel="shortcut icon" href="%s"> 284 </head> 285 </html> 286 `, expected.Title, expected.Description, thumbnailURL, favicon)), 287 nil, 288 ) 289 transport.AddURLMatcher(thumbnailURL, s.readAsset("1.jpg"), nil) 290 stubbedClient := http.Client{Transport: &transport} 291 292 response, err := s.m.UnfurlURLs(&stubbedClient, []string{u}) 293 s.Require().NoError(err) 294 s.Require().Len(response.StatusLinkPreviews, 0) 295 s.Require().Len(response.LinkPreviews, 1) 296 preview := response.LinkPreviews[0] 297 298 s.Require().Equal(expected.Type, preview.Type) 299 s.Require().Equal(expected.URL, preview.URL) 300 s.Require().Equal(expected.Hostname, preview.Hostname) 301 s.Require().Equal(expected.Title, preview.Title) 302 s.Require().Equal(expected.Description, preview.Description) 303 s.Require().Equal(expected.Thumbnail.Width, preview.Thumbnail.Width) 304 s.Require().Equal(expected.Thumbnail.Height, preview.Thumbnail.Height) 305 s.Require().Equal(expected.Thumbnail.URL, preview.Thumbnail.URL) 306 s.Require().NotNil(preview.Favicon) 307 s.assertContainsLongString(expected.Thumbnail.DataURI, preview.Thumbnail.DataURI, 100) 308 } 309 310 func (s *MessengerLinkPreviewsTestSuite) Test_UnfurlURLs_Reddit() { 311 u := "https://www.reddit.com/r/Bitcoin/comments/13j0tzr/the_best_bitcoin_explanation_of_all_times/?utm_source=share" 312 expected := common.LinkPreview{ 313 Type: protobuf.UnfurledLink_LINK, 314 URL: u, 315 Hostname: "www.reddit.com", 316 Title: "The best bitcoin explanation of all times.", 317 Description: "", 318 Thumbnail: common.LinkPreviewThumbnail{}, 319 } 320 321 transport := StubTransport{} 322 transport.AddURLMatcher( 323 "https://www.reddit.com/oembed", 324 []byte(` 325 { 326 "provider_url": "https://www.reddit.com/", 327 "version": "1.0", 328 "title": "The best bitcoin explanation of all times.", 329 "provider_name": "reddit", 330 "type": "rich", 331 "author_name": "DTheDev" 332 } 333 `), 334 nil, 335 ) 336 stubbedClient := http.Client{Transport: &transport} 337 338 response, err := s.m.UnfurlURLs(&stubbedClient, []string{u}) 339 s.Require().NoError(err) 340 s.Require().Len(response.StatusLinkPreviews, 0) 341 s.Require().Len(response.LinkPreviews, 1) 342 preview := response.LinkPreviews[0] 343 344 s.Require().Equal(expected.Type, preview.Type) 345 s.Require().Equal(expected.URL, preview.URL) 346 s.Require().Equal(expected.Hostname, preview.Hostname) 347 s.Require().Equal(expected.Title, preview.Title) 348 s.Require().Equal(expected.Description, preview.Description) 349 s.Require().Equal(expected.Thumbnail, preview.Thumbnail) 350 } 351 352 func (s *MessengerLinkPreviewsTestSuite) Test_UnfurlURLs_Timeout() { 353 httpClient := http.Client{Timeout: time.Nanosecond} 354 response, err := s.m.UnfurlURLs(&httpClient, []string{"https://status.im"}) 355 s.Require().NoError(err) 356 s.Require().Len(response.StatusLinkPreviews, 0) 357 s.Require().Empty(response.LinkPreviews) 358 } 359 360 func (s *MessengerLinkPreviewsTestSuite) Test_UnfurlURLs_CommonFailures() { 361 httpClient := http.Client{} 362 363 // Test URL that doesn't return any OpenGraph title. 364 transport := StubTransport{} 365 transport.AddURLMatcher( 366 "https://wikipedia.org", 367 []byte("<html><head></head></html>"), 368 nil, 369 ) 370 stubbedClient := http.Client{Transport: &transport} 371 response, err := s.m.UnfurlURLs(&stubbedClient, []string{"https://wikipedia.org"}) 372 s.Require().NoError(err) 373 s.Require().Len(response.StatusLinkPreviews, 0) 374 s.Require().Empty(response.LinkPreviews) 375 376 // Test 404. 377 response, err = s.m.UnfurlURLs(&httpClient, []string{"https://github.com/status-im/i_do_not_exist"}) 378 s.Require().NoError(err) 379 s.Require().Len(response.StatusLinkPreviews, 0) 380 s.Require().Empty(response.LinkPreviews) 381 382 // Test no response when trying to get OpenGraph metadata. 383 response, err = s.m.UnfurlURLs(&httpClient, []string{"https://wikipedia.o"}) 384 s.Require().NoError(err) 385 s.Require().Len(response.StatusLinkPreviews, 0) 386 s.Require().Empty(response.LinkPreviews) 387 } 388 389 func (s *MessengerLinkPreviewsTestSuite) Test_isSupportedImageURL() { 390 examples := []struct { 391 url string 392 expected bool 393 }{ 394 {url: "https://placehold.co/600x400@2x.png", expected: true}, 395 {url: "https://placehold.co/600x400@2x.PNG", expected: true}, 396 {url: "https://placehold.co/600x400@2x.jpg", expected: true}, 397 {url: "https://placehold.co/600x400@2x.JPG", expected: true}, 398 {url: "https://placehold.co/600x400@2x.jpeg", expected: true}, 399 {url: "https://placehold.co/600x400@2x.Jpeg", expected: true}, 400 {url: "https://placehold.co/600x400@2x.webp", expected: true}, 401 {url: "https://placehold.co/600x400@2x.WebP", expected: true}, 402 {url: "https://placehold.co/600x400@2x.PnGs", expected: false}, 403 {url: "https://placehold.co/600x400@2x.tiff", expected: false}, 404 } 405 406 for _, e := range examples { 407 parsedURL, err := url.Parse(e.url) 408 s.Require().NoError(err, e) 409 s.Require().Equal(e.expected, IsSupportedImageURL(parsedURL), e.url) 410 } 411 } 412 413 func (s *MessengerLinkPreviewsTestSuite) Test_UnfurlURLs_Image() { 414 u := "https://placehold.co/600x400@3x.png" 415 expected := common.LinkPreview{ 416 Type: protobuf.UnfurledLink_IMAGE, 417 URL: u, 418 Hostname: "placehold.co", 419 Title: "600x400@3x.png", 420 Description: "", 421 Thumbnail: common.LinkPreviewThumbnail{ 422 Width: 1293, 423 Height: 1900, 424 DataURI: "data:image/jpeg;base64,/9j/2wCEABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ", 425 }, 426 } 427 428 transport := StubTransport{} 429 // Use a larger image to verify Thumbnail.DataURI is compressed. 430 transport.AddURLMatcher(u, s.readAsset("IMG_1205.HEIC.jpg"), nil) 431 stubbedClient := http.Client{Transport: &transport} 432 433 response, err := s.m.UnfurlURLs(&stubbedClient, []string{u}) 434 s.Require().NoError(err) 435 s.Require().Len(response.StatusLinkPreviews, 0) 436 s.Require().Len(response.LinkPreviews, 1) 437 preview := response.LinkPreviews[0] 438 439 s.Require().Equal(expected.Type, preview.Type) 440 s.Require().Equal(expected.URL, preview.URL) 441 s.Require().Equal(expected.Hostname, preview.Hostname) 442 s.Require().Equal(expected.Title, preview.Title) 443 s.Require().Equal(expected.Description, preview.Description) 444 s.Require().Equal(expected.Thumbnail.Width, preview.Thumbnail.Width) 445 s.Require().Equal(expected.Thumbnail.Height, preview.Thumbnail.Height) 446 s.Require().Equal(expected.Thumbnail.URL, preview.Thumbnail.URL) 447 s.assertContainsLongString(expected.Thumbnail.DataURI, preview.Thumbnail.DataURI, 100) 448 } 449 450 func (s *MessengerLinkPreviewsTestSuite) Test_UnfurlURLs_StatusContactAdded() { 451 identity, err := crypto.GenerateKey() 452 s.Require().NoError(err) 453 454 c, err := BuildContactFromPublicKey(&identity.PublicKey) 455 s.Require().NoError(err) 456 s.Require().NotNil(c) 457 458 payload, err := images.GetPayloadFromURI(exampleIdenticonURI) 459 s.Require().NoError(err) 460 461 icon := images.IdentityImage{ 462 Width: 50, 463 Height: 50, 464 Payload: payload, 465 } 466 467 c.Bio = "TestBio_1" 468 c.DisplayName = "TestDisplayName_1" 469 c.Images = map[string]images.IdentityImage{} 470 c.Images[images.SmallDimName] = icon 471 s.m.allContacts.Store(c.ID, c) 472 473 // Generate a shared URL 474 u, err := s.m.ShareUserURLWithData(c.ID) 475 s.Require().NoError(err) 476 477 // Update contact info locally after creating the shared URL 478 // This is required to test that URL-decoded data is not used in the preview. 479 c.Bio = "TestBio_2" 480 c.DisplayName = "TestDisplayName_2" 481 s.m.allContacts.Store(c.ID, c) 482 483 r, err := s.m.UnfurlURLs(nil, []string{u}) 484 s.Require().NoError(err) 485 s.Require().Len(r.StatusLinkPreviews, 1) 486 s.Require().Len(r.LinkPreviews, 0) 487 488 preview := r.StatusLinkPreviews[0] 489 s.Require().Equal(u, preview.URL) 490 s.Require().Nil(preview.Community) 491 s.Require().Nil(preview.Channel) 492 s.Require().NotNil(preview.Contact) 493 s.Require().Equal(c.ID, preview.Contact.PublicKey) 494 s.Require().Equal(c.DisplayName, preview.Contact.DisplayName) 495 s.Require().Equal(c.Bio, preview.Contact.Description) 496 s.Require().Equal(icon.Width, preview.Contact.Icon.Width) 497 s.Require().Equal(icon.Height, preview.Contact.Icon.Height) 498 s.Require().Equal("", preview.Contact.Icon.URL) 499 500 expectedDataURI, err := images.GetPayloadDataURI(icon.Payload) 501 s.Require().NoError(err) 502 s.Require().Equal(expectedDataURI, preview.Contact.Icon.DataURI) 503 } 504 505 func (s *MessengerLinkPreviewsTestSuite) setProfileParameters(messenger *Messenger, displayName string, bio string, identityImages []images.IdentityImage) { 506 const timeout = 1 * time.Second 507 508 changes := SelfContactChangeEvent{} 509 510 SetSettingsAndWaitForChange(&s.Suite, messenger, timeout, func() { 511 err := messenger.SetDisplayName(displayName) 512 s.Require().NoError(err) 513 err = messenger.SetBio(bio) 514 s.Require().NoError(err) 515 }, func(event *SelfContactChangeEvent) bool { 516 if event.DisplayNameChanged { 517 changes.DisplayNameChanged = true 518 } 519 if event.BioChanged { 520 changes.BioChanged = true 521 } 522 return changes.DisplayNameChanged && changes.BioChanged 523 }) 524 525 SetIdentityImagesAndWaitForChange(&s.Suite, messenger, timeout, func() { 526 err := messenger.multiAccounts.StoreIdentityImages(messenger.account.KeyUID, identityImages, false) 527 s.Require().NoError(err) 528 }) 529 530 selfContact := messenger.GetSelfContact() 531 s.Require().Equal(selfContact.DisplayName, displayName) 532 s.Require().Equal(selfContact.Bio, bio) 533 534 for _, image := range identityImages { 535 saved, ok := selfContact.Images[image.Name] 536 s.Require().True(ok) 537 s.Require().Equal(saved, image) 538 } 539 s.Require().Equal(selfContact.DisplayName, displayName) 540 } 541 542 func (s *MessengerLinkPreviewsTestSuite) Test_UnfurlURLs_SelfLink() { 543 profileKp := accounts.GetProfileKeypairForTest(true, false, false) 544 profileKp.KeyUID = s.m.account.KeyUID 545 profileKp.Accounts[0].KeyUID = s.m.account.KeyUID 546 547 err := s.m.settings.SaveOrUpdateKeypair(profileKp) 548 s.Require().NoError(err) 549 550 // Set initial profile parameters 551 identityImages := images.SampleIdentityImages() 552 s.setProfileParameters(s.m, "TestDisplayName_3", "TestBio_3", identityImages) 553 554 // Generate a shared URL 555 u, err := s.m.ShareUserURLWithData(s.m.IdentityPublicKeyString()) 556 s.Require().NoError(err) 557 558 // Update contact info locally after creating the shared URL 559 // This is required to test that URL-decoded data is not used in the preview. 560 iconPayload, err := images.GetPayloadFromURI(exampleIdenticonURI) 561 s.Require().NoError(err) 562 icon := images.IdentityImage{ 563 Name: images.SmallDimName, 564 Width: 50, 565 Height: 50, 566 Payload: iconPayload, 567 } 568 s.setProfileParameters(s.m, "TestDisplayName_4", "TestBio_4", []images.IdentityImage{icon}) 569 570 r, err := s.m.UnfurlURLs(nil, []string{u}) 571 s.Require().NoError(err) 572 s.Require().Len(r.StatusLinkPreviews, 1) 573 s.Require().Len(r.LinkPreviews, 0) 574 575 userSettings, err := s.m.getSettings() 576 s.Require().NoError(err) 577 578 preview := r.StatusLinkPreviews[0] 579 s.Require().Equal(u, preview.URL) 580 s.Require().Nil(preview.Community) 581 s.Require().Nil(preview.Channel) 582 s.Require().NotNil(preview.Contact) 583 s.Require().Equal(s.m.IdentityPublicKeyString(), preview.Contact.PublicKey) 584 s.Require().Equal(userSettings.DisplayName, preview.Contact.DisplayName) 585 s.Require().Equal(userSettings.Bio, preview.Contact.Description) 586 587 s.Require().Equal(icon.Width, preview.Contact.Icon.Width) 588 s.Require().Equal(icon.Height, preview.Contact.Icon.Height) 589 s.Require().Equal("", preview.Contact.Icon.URL) 590 591 expectedDataURI, err := images.GetPayloadDataURI(icon.Payload) 592 s.Require().NoError(err) 593 s.Require().Equal(expectedDataURI, preview.Contact.Icon.DataURI) 594 } 595 596 func (s *MessengerLinkPreviewsTestSuite) Test_UnfurlURLs_StatusCommunityJoined() { 597 598 description := &requests.CreateCommunity{ 599 Membership: protobuf.CommunityPermissions_AUTO_ACCEPT, 600 Name: "status", 601 Description: "status community description", 602 Color: "#123456", 603 Image: "../_assets/tests/status.png", // 256*256 px 604 ImageAx: 0, 605 ImageAy: 0, 606 ImageBx: 256, 607 ImageBy: 256, 608 Banner: images.CroppedImage{ 609 ImagePath: "../_assets/tests/IMG_1205.HEIC.jpg", // 2282*3352 px 610 X: 0, 611 Y: 0, 612 Width: 160, 613 Height: 90, 614 }, 615 } 616 617 response, err := s.m.CreateCommunity(description, false) 618 s.Require().NoError(err) 619 s.Require().NotNil(response) 620 621 community := response.Communities()[0] 622 communityImages := community.Images() 623 s.Require().Len(communityImages, 3) 624 625 // Get icon data 626 icon, ok := communityImages[images.SmallDimName] 627 s.Require().True(ok) 628 iconWidth, iconHeight, err := images.GetImageDimensions(icon.Payload) 629 s.Require().NoError(err) 630 iconDataURI, err := images.GetPayloadDataURI(icon.Payload) 631 s.Require().NoError(err) 632 633 // Get banner data 634 banner, ok := communityImages[images.BannerIdentityName] 635 s.Require().True(ok) 636 bannerWidth, bannerHeight, err := images.GetImageDimensions(banner.Payload) 637 s.Require().NoError(err) 638 bannerDataURI, err := images.GetPayloadDataURI(banner.Payload) 639 s.Require().NoError(err) 640 641 // Create shared URL 642 u, err := s.m.ShareCommunityURLWithData(community.ID()) 643 s.Require().NoError(err) 644 645 // Unfurl community shared URL 646 r, err := s.m.UnfurlURLs(nil, []string{u}) 647 s.Require().NoError(err) 648 s.Require().Len(r.StatusLinkPreviews, 1) 649 s.Require().Len(r.LinkPreviews, 0) 650 651 preview := r.StatusLinkPreviews[0] 652 s.Require().Equal(u, preview.URL) 653 s.Require().NotNil(preview.Community) 654 s.Require().Nil(preview.Channel) 655 s.Require().Nil(preview.Contact) 656 657 s.Require().Equal(community.IDString(), preview.Community.CommunityID) 658 s.Require().Equal(community.Name(), preview.Community.DisplayName) 659 s.Require().Equal(community.Identity().Description, preview.Community.Description) 660 s.Require().Equal(iconWidth, preview.Community.Icon.Width) 661 s.Require().Equal(iconHeight, preview.Community.Icon.Height) 662 s.Require().Equal(iconDataURI, preview.Community.Icon.DataURI) 663 s.Require().Equal(bannerWidth, preview.Community.Banner.Width) 664 s.Require().Equal(bannerHeight, preview.Community.Banner.Height) 665 s.Require().Equal(bannerDataURI, preview.Community.Banner.DataURI) 666 } 667 668 func (s *MessengerLinkPreviewsTestSuite) Test_UnfurlURLs_Settings() { 669 // Create website stub 670 const ogLink = "https://github.com" 671 const statusUserLink = "https://status.app/c#zQ3shYSHp7GoiXaauJMnDcjwU2yNjdzpXLosAWapPS4CFxc11" 672 const gifLink = "https://media1.giphy.com/media/lcG3qwtTKSNI2i5vst/giphy.gif" 673 674 linksToUnfurl := []string{ogLink, statusUserLink, gifLink} 675 text := strings.Join(linksToUnfurl, " ") 676 677 // Test `AlwaysAsk` 678 679 err := s.m.settings.SaveSettingField(settings.URLUnfurlingMode, settings.URLUnfurlingAlwaysAsk) 680 s.Require().NoError(err) 681 682 plan := s.m.GetTextURLsToUnfurl(text) 683 s.Require().Len(plan.URLs, len(linksToUnfurl)) 684 685 s.Require().Equal(plan.URLs[0].URL, ogLink) 686 s.Require().Equal(plan.URLs[0].IsStatusSharedURL, false) 687 s.Require().Equal(plan.URLs[0].Permission, URLUnfurlingAskUser) 688 689 s.Require().Equal(plan.URLs[1].URL, statusUserLink) 690 s.Require().Equal(plan.URLs[1].IsStatusSharedURL, true) 691 s.Require().Equal(plan.URLs[1].Permission, URLUnfurlingAllowed) 692 693 s.Require().Equal(plan.URLs[2].URL, gifLink) 694 s.Require().Equal(plan.URLs[2].IsStatusSharedURL, false) 695 s.Require().Equal(plan.URLs[2].Permission, URLUnfurlingNotSupported) 696 697 // Test `EnableAll` 698 err = s.m.settings.SaveSettingField(settings.URLUnfurlingMode, settings.URLUnfurlingEnableAll) 699 s.Require().NoError(err) 700 701 plan = s.m.GetTextURLsToUnfurl(text) 702 s.Require().Len(plan.URLs, len(linksToUnfurl)) 703 704 s.Require().Equal(plan.URLs[0].URL, ogLink) 705 s.Require().Equal(plan.URLs[0].IsStatusSharedURL, false) 706 s.Require().Equal(plan.URLs[0].Permission, URLUnfurlingAllowed) 707 708 s.Require().Equal(plan.URLs[1].URL, statusUserLink) 709 s.Require().Equal(plan.URLs[1].IsStatusSharedURL, true) 710 s.Require().Equal(plan.URLs[1].Permission, URLUnfurlingAllowed) 711 712 s.Require().Equal(plan.URLs[2].URL, gifLink) 713 s.Require().Equal(plan.URLs[2].IsStatusSharedURL, false) 714 s.Require().Equal(plan.URLs[2].Permission, URLUnfurlingNotSupported) 715 716 // Test `DisableAll` 717 err = s.m.settings.SaveSettingField(settings.URLUnfurlingMode, settings.URLUnfurlingDisableAll) 718 s.Require().NoError(err) 719 720 plan = s.m.GetTextURLsToUnfurl(text) 721 s.Require().Len(plan.URLs, len(linksToUnfurl)) 722 723 s.Require().Equal(plan.URLs[0].URL, ogLink) 724 s.Require().Equal(plan.URLs[0].IsStatusSharedURL, false) 725 s.Require().Equal(plan.URLs[0].Permission, URLUnfurlingForbiddenBySettings) 726 727 s.Require().Equal(plan.URLs[1].URL, statusUserLink) 728 s.Require().Equal(plan.URLs[1].IsStatusSharedURL, true) 729 s.Require().Equal(plan.URLs[1].Permission, URLUnfurlingAllowed) 730 731 s.Require().Equal(plan.URLs[2].URL, gifLink) 732 s.Require().Equal(plan.URLs[2].IsStatusSharedURL, false) 733 s.Require().Equal(plan.URLs[2].Permission, URLUnfurlingNotSupported) 734 } 735 736 func (s *MessengerLinkPreviewsTestSuite) Test_UnfurlURLs_Limit() { 737 text := "https://www.youtube.com/watch?v=6dkDepLX0rk " + 738 "https://www.youtube.com/watch?v=ferZnZ0_rSM " + 739 "https://www.youtube.com/watch?v=bdneye4pzMw " + 740 "https://www.youtube.com/watch?v=pRERgcQe-fQ " + 741 "https://www.youtube.com/watch?v=j82L3pLjb_0 " + 742 "https://www.youtube.com/watch?v=hxsJvKYyVyg " + 743 "https://www.youtube.com/watch?v=jIIuzB11dsA " 744 745 urls := s.m.GetURLs(text) 746 s.Require().Equal(UnfurledLinksPerMessageLimit, len(urls)) 747 }