github.com/status-im/status-go@v1.1.0/protocol/messenger_edit_message_test.go (about) 1 package protocol 2 3 import ( 4 "context" 5 "testing" 6 7 "github.com/stretchr/testify/suite" 8 9 "github.com/status-im/status-go/eth-node/crypto" 10 "github.com/status-im/status-go/eth-node/types" 11 "github.com/status-im/status-go/protocol/common" 12 "github.com/status-im/status-go/protocol/protobuf" 13 "github.com/status-im/status-go/protocol/requests" 14 ) 15 16 func TestMessengerEditMessageSuite(t *testing.T) { 17 suite.Run(t, new(MessengerEditMessageSuite)) 18 } 19 20 type MessengerEditMessageSuite struct { 21 MessengerBaseTestSuite 22 } 23 24 func (s *MessengerEditMessageSuite) TestEditMessage() { 25 theirMessenger := s.newMessenger() 26 defer TearDownMessenger(&s.Suite, theirMessenger) 27 28 theirChat := CreateOneToOneChat("Their 1TO1", &s.privateKey.PublicKey, s.m.transport) 29 err := theirMessenger.SaveChat(theirChat) 30 s.Require().NoError(err) 31 32 ourChat := CreateOneToOneChat("Our 1TO1", &theirMessenger.identity.PublicKey, s.m.transport) 33 err = s.m.SaveChat(ourChat) 34 s.Require().NoError(err) 35 36 inputMessage := buildTestMessage(*theirChat) 37 sendResponse, err := theirMessenger.SendChatMessage(context.Background(), inputMessage) 38 s.NoError(err) 39 s.Require().Len(sendResponse.Messages(), 1) 40 41 response, err := WaitOnMessengerResponse( 42 s.m, 43 func(r *MessengerResponse) bool { return len(r.messages) > 0 }, 44 "no messages", 45 ) 46 s.Require().NoError(err) 47 s.Require().Len(response.Chats(), 1) 48 s.Require().Len(response.Messages(), 1) 49 50 ogMessage := sendResponse.Messages()[0] 51 52 messageID, err := types.DecodeHex(ogMessage.ID) 53 s.Require().NoError(err) 54 55 editedText := "edited text" 56 editedMessage := &requests.EditMessage{ 57 ID: messageID, 58 Text: editedText, 59 } 60 61 sendResponse, err = theirMessenger.EditMessage(context.Background(), editedMessage) 62 63 s.Require().NoError(err) 64 s.Require().Len(sendResponse.Messages(), 1) 65 s.Require().NotEmpty(sendResponse.Messages()[0].EditedAt) 66 s.Require().Equal(sendResponse.Messages()[0].Text, editedText) 67 s.Require().Len(sendResponse.Chats(), 1) 68 s.Require().NotNil(sendResponse.Chats()[0].LastMessage) 69 s.Require().NotEmpty(sendResponse.Chats()[0].LastMessage.EditedAt) 70 71 response, err = WaitOnMessengerResponse( 72 s.m, 73 func(r *MessengerResponse) bool { return len(r.messages) > 0 }, 74 "no messages", 75 ) 76 s.Require().NoError(err) 77 78 s.Require().Len(response.Chats(), 1) 79 s.Require().Len(response.Messages(), 1) 80 s.Require().NotEmpty(response.Messages()[0].EditedAt) 81 s.Require().False(response.Messages()[0].New) 82 83 // Main instance user attempts to edit the message it received from theirMessenger 84 editedMessage = &requests.EditMessage{ 85 ID: messageID, 86 Text: "edited-again text", 87 } 88 _, err = s.m.EditMessage(context.Background(), editedMessage) 89 90 s.Require().Equal(ErrInvalidEditOrDeleteAuthor, err) 91 } 92 93 func (s *MessengerEditMessageSuite) TestEditBridgeMessage() { 94 theirMessenger := s.newMessenger() 95 defer TearDownMessenger(&s.Suite, theirMessenger) 96 97 theirChat := CreateOneToOneChat("Their 1TO1", &s.privateKey.PublicKey, s.m.transport) 98 err := theirMessenger.SaveChat(theirChat) 99 s.Require().NoError(err) 100 101 ourChat := CreateOneToOneChat("Our 1TO1", &theirMessenger.identity.PublicKey, s.m.transport) 102 err = s.m.SaveChat(ourChat) 103 s.Require().NoError(err) 104 105 bridgeMessage := buildTestMessage(*theirChat) 106 bridgeMessage.ContentType = protobuf.ChatMessage_BRIDGE_MESSAGE 107 bridgeMessage.Payload = &protobuf.ChatMessage_BridgeMessage{ 108 BridgeMessage: &protobuf.BridgeMessage{ 109 BridgeName: "discord", 110 UserName: "user1", 111 UserAvatar: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAIAAACRXR/mAAAAjklEQVR4nOzXwQmFMBAAUZXUYh32ZB32ZB02sxYQQSZGsod55/91WFgSS0RM+SyjA56ZRZhFmEWYRRT6h+M6G16zrxv6fdJpmUWYRbxsYr13dKfanpN0WmYRZhGzXz6AWYRZRIfbaX26fT9Jk07LLMIsosPt9I/dTDotswizCG+nhFmEWYRZhFnEHQAA///z1CFkYamgfQAAAABJRU5ErkJggg==", 112 UserID: "123", 113 Content: "text1", 114 MessageID: "456", 115 ParentMessageID: "789", 116 }, 117 } 118 119 sendResponse, err := theirMessenger.SendChatMessage(context.Background(), bridgeMessage) 120 s.NoError(err) 121 s.Require().Len(sendResponse.Messages(), 1) 122 123 response, err := WaitOnMessengerResponse( 124 s.m, 125 func(r *MessengerResponse) bool { return len(r.messages) > 0 }, 126 "no messages", 127 ) 128 s.Require().NoError(err) 129 s.Require().Len(response.Chats(), 1) 130 s.Require().Len(response.Messages(), 1) 131 132 messageToEdit := sendResponse.Messages()[0] 133 134 messageID, err := types.DecodeHex(messageToEdit.ID) 135 s.Require().NoError(err) 136 137 editedText := "edited text" 138 editedMessage := &requests.EditMessage{ 139 ID: messageID, 140 Text: editedText, 141 ContentType: protobuf.ChatMessage_BRIDGE_MESSAGE, 142 } 143 144 sendResponse, err = theirMessenger.EditMessage(context.Background(), editedMessage) 145 s.Require().NoError(err) 146 s.Require().Len(sendResponse.Messages(), 1) 147 s.Require().NotEmpty(sendResponse.Messages()[0].EditedAt) 148 s.Require().Equal(sendResponse.Messages()[0].Text, "text-input-message") 149 s.Require().Equal(sendResponse.Messages()[0].GetBridgeMessage().Content, editedText) 150 s.Require().Len(sendResponse.Chats(), 1) 151 s.Require().NotNil(sendResponse.Chats()[0].LastMessage) 152 s.Require().NotEmpty(sendResponse.Chats()[0].LastMessage.EditedAt) 153 154 response, err = WaitOnMessengerResponse( 155 s.m, 156 func(r *MessengerResponse) bool { return len(r.messages) > 0 }, 157 "no messages", 158 ) 159 s.Require().NoError(err) 160 s.Require().Len(response.Chats(), 1) 161 s.Require().Len(response.Messages(), 1) 162 163 s.Require().NotEmpty(response.Chats()[0].LastMessage.EditedAt) 164 s.Require().Equal(response.Messages()[0].GetBridgeMessage().Content, "edited text") 165 } 166 167 func (s *MessengerEditMessageSuite) TestEditMessageEdgeCases() { 168 theirMessenger := s.newMessenger() 169 defer TearDownMessenger(&s.Suite, theirMessenger) 170 171 theirChat := CreateOneToOneChat("Their 1TO1", &s.privateKey.PublicKey, s.m.transport) 172 err := theirMessenger.SaveChat(theirChat) 173 s.Require().NoError(err) 174 175 ourChat := CreateOneToOneChat("Our 1TO1", &theirMessenger.identity.PublicKey, s.m.transport) 176 err = s.m.SaveChat(ourChat) 177 s.Require().NoError(err) 178 179 inputMessage := buildTestMessage(*theirChat) 180 sendResponse, err := theirMessenger.SendChatMessage(context.Background(), inputMessage) 181 s.NoError(err) 182 s.Require().Len(sendResponse.Messages(), 1) 183 184 response, err := WaitOnMessengerResponse( 185 s.m, 186 func(r *MessengerResponse) bool { return len(r.messages) > 0 }, 187 "no messages", 188 ) 189 s.Require().NoError(err) 190 s.Require().Len(response.Chats(), 1) 191 s.Require().Len(response.Messages(), 1) 192 193 chat := response.Chats()[0] 194 editedMessage := sendResponse.Messages()[0] 195 196 newContactKey, err := crypto.GenerateKey() 197 s.Require().NoError(err) 198 wrongContact, err := BuildContactFromPublicKey(&newContactKey.PublicKey) 199 s.Require().NoError(err) 200 201 editMessage := EditMessage{ 202 EditMessage: &protobuf.EditMessage{ 203 Clock: editedMessage.Clock + 1, 204 Text: "some text", 205 MessageId: editedMessage.ID, 206 ChatId: chat.ID, 207 }, 208 From: wrongContact.ID, 209 } 210 211 state := &ReceivedMessageState{ 212 Response: &MessengerResponse{}, 213 AllChats: &chatMap{}, 214 } 215 state.AllChats.Store(ourChat.ID, ourChat) 216 217 err = s.m.handleEditMessage(state, editMessage) 218 // It should error as the user can't edit this message 219 s.Require().Error(err) 220 221 // Edit with a newer clock value 222 223 contact, err := BuildContactFromPublicKey(&theirMessenger.identity.PublicKey) 224 s.Require().NoError(err) 225 226 editMessage = EditMessage{ 227 EditMessage: &protobuf.EditMessage{ 228 Clock: editedMessage.Clock + 2, 229 Text: "some text", 230 MessageType: protobuf.MessageType_ONE_TO_ONE, 231 MessageId: editedMessage.ID, 232 ChatId: chat.ID, 233 }, 234 From: contact.ID, 235 } 236 237 err = s.m.handleEditMessage(state, editMessage) 238 s.Require().NoError(err) 239 // It save the edit 240 s.Require().Len(state.Response.Messages(), 1) 241 s.Require().Len(state.Response.Chats(), 1) 242 s.Require().NotNil(state.Response.Chats()[0].LastMessage) 243 s.Require().NotEmpty(state.Response.Chats()[0].LastMessage.EditedAt) 244 245 editedMessage = state.Response.Messages()[0] 246 247 // In-between edit 248 editMessage = EditMessage{ 249 EditMessage: &protobuf.EditMessage{ 250 Clock: editedMessage.Clock + 1, 251 Text: "some other text", 252 MessageType: protobuf.MessageType_ONE_TO_ONE, 253 MessageId: editedMessage.ID, 254 ChatId: chat.ID, 255 }, 256 From: contact.ID, 257 } 258 259 state.Response = &MessengerResponse{} 260 261 err = s.m.handleEditMessage(state, editMessage) 262 // It should error as the user can't edit this message 263 s.Require().NoError(err) 264 // It discards the edit 265 s.Require().Len(state.Response.Messages(), 0) 266 } 267 268 func (s *MessengerEditMessageSuite) TestEditMessageFirstEditsThenMessage() { 269 theirMessenger := s.newMessenger() 270 defer TearDownMessenger(&s.Suite, theirMessenger) 271 272 theirChat := CreateOneToOneChat("Their 1TO1", &s.privateKey.PublicKey, s.m.transport) 273 err := theirMessenger.SaveChat(theirChat) 274 s.Require().NoError(err) 275 276 contact, err := BuildContactFromPublicKey(&theirMessenger.identity.PublicKey) 277 s.Require().NoError(err) 278 279 ourChat := CreateOneToOneChat("Our 1TO1", &theirMessenger.identity.PublicKey, s.m.transport) 280 err = s.m.SaveChat(ourChat) 281 s.Require().NoError(err) 282 messageID := "message-id" 283 284 inputMessage := buildTestMessage(*theirChat) 285 inputMessage.Clock = 1 286 editMessage := EditMessage{ 287 EditMessage: &protobuf.EditMessage{ 288 Clock: 2, 289 Text: "some text", 290 MessageType: protobuf.MessageType_ONE_TO_ONE, 291 MessageId: messageID, 292 ChatId: theirChat.ID, 293 }, 294 From: common.PubkeyToHex(&theirMessenger.identity.PublicKey), 295 } 296 state := &ReceivedMessageState{ 297 Response: &MessengerResponse{}, 298 } 299 300 // Handle edit first 301 err = s.m.handleEditMessage(state, editMessage) 302 s.Require().NoError(err) 303 304 // Handle chat message 305 response := &MessengerResponse{} 306 state = &ReceivedMessageState{ 307 Response: response, 308 CurrentMessageState: &CurrentMessageState{ 309 MessageID: messageID, 310 WhisperTimestamp: s.m.getTimesource().GetCurrentTime(), 311 Contact: contact, 312 PublicKey: &theirMessenger.identity.PublicKey, 313 }, 314 } 315 err = s.m.HandleChatMessage(state, inputMessage.ChatMessage, nil, false) 316 s.Require().NoError(err) 317 s.Require().Len(response.Messages(), 1) 318 319 editedMessage := response.Messages()[0] 320 321 s.Require().Equal(uint64(2), editedMessage.EditedAt) 322 } 323 324 // Test editing a message on an existing private group chat 325 func (s *MessengerEditMessageSuite) TestEditGroupChatMessage() { 326 theirMessenger := s.newMessenger() 327 defer TearDownMessenger(&s.Suite, theirMessenger) 328 329 response, err := s.m.CreateGroupChatWithMembers(context.Background(), "id", []string{}) 330 s.NoError(err) 331 s.Require().Len(response.Chats(), 1) 332 333 ourChat := response.Chats()[0] 334 335 err = s.m.SaveChat(ourChat) 336 s.NoError(err) 337 338 s.Require().NoError(makeMutualContact(s.m, &theirMessenger.identity.PublicKey)) 339 340 members := []string{common.PubkeyToHex(&theirMessenger.identity.PublicKey)} 341 _, err = s.m.AddMembersToGroupChat(context.Background(), ourChat.ID, members) 342 s.NoError(err) 343 344 // Retrieve their messages so that the chat is created 345 response, err = WaitOnMessengerResponse( 346 theirMessenger, 347 func(r *MessengerResponse) bool { return len(r.Chats()) > 0 }, 348 "chat invitation not received", 349 ) 350 s.Require().NoError(err) 351 s.Require().Len(response.Chats(), 1) 352 s.Require().Len(response.ActivityCenterNotifications(), 1) 353 s.Require().False(response.Chats()[0].Active) 354 355 _, err = theirMessenger.ConfirmJoiningGroup(context.Background(), ourChat.ID) 356 s.NoError(err) 357 358 // Wait for the message to reach its destination 359 _, err = WaitOnMessengerResponse( 360 s.m, 361 func(r *MessengerResponse) bool { return len(r.Chats()) > 0 }, 362 "no joining group event received", 363 ) 364 s.Require().NoError(err) 365 366 inputMessage := buildTestMessage(*ourChat) 367 368 sendResponse, err := theirMessenger.SendChatMessage(context.Background(), inputMessage) 369 s.NoError(err) 370 s.Require().Len(sendResponse.Messages(), 1) 371 372 sentMessage := sendResponse.Messages()[0] 373 374 _, err = WaitOnMessengerResponse( 375 s.m, 376 func(r *MessengerResponse) bool { return len(r.Messages()) > 0 }, 377 "no messages", 378 ) 379 s.Require().NoError(err) 380 381 // Edit message 382 383 messageID, err := types.DecodeHex(sentMessage.ID) 384 s.Require().NoError(err) 385 386 editedText := "edited text" 387 editedMessage := &requests.EditMessage{ 388 ID: messageID, 389 Text: editedText, 390 } 391 392 _, err = theirMessenger.EditMessage(context.Background(), editedMessage) 393 394 s.Require().NoError(err) 395 396 response, err = WaitOnMessengerResponse( 397 s.m, 398 func(r *MessengerResponse) bool { return len(r.messages) > 0 }, 399 "no messages", 400 ) 401 s.Require().NoError(err) 402 s.Require().Len(response.Chats(), 1) 403 s.Require().Len(response.Messages(), 1) 404 s.Require().NotEmpty(response.Messages()[0].EditedAt) 405 s.Require().False(response.Messages()[0].New) 406 } 407 408 func (s *MessengerEditMessageSuite) TestEditMessageWithMention() { 409 theirMessenger := s.newMessenger() 410 defer TearDownMessenger(&s.Suite, theirMessenger) 411 412 theirChat := CreateOneToOneChat("Their 1TO1", &s.privateKey.PublicKey, s.m.transport) 413 err := theirMessenger.SaveChat(theirChat) 414 s.Require().NoError(err) 415 416 ourChat := CreateOneToOneChat("Our 1TO1", &theirMessenger.identity.PublicKey, s.m.transport) 417 err = s.m.SaveChat(ourChat) 418 s.Require().NoError(err) 419 420 inputMessage := buildTestMessage(*theirChat) 421 // Send first message with no mention 422 sendResponse, err := theirMessenger.SendChatMessage(context.Background(), inputMessage) 423 s.NoError(err) 424 s.Require().Len(sendResponse.Messages(), 1) 425 426 response, err := WaitOnMessengerResponse( 427 s.m, 428 func(r *MessengerResponse) bool { return len(r.messages) == 1 }, 429 "no messages", 430 ) 431 s.Require().NoError(err) 432 s.Require().Len(response.Chats(), 1) 433 s.Require().Len(response.Messages(), 1) 434 // Make sure the message is not marked as Mentioned (chat still counts it because it's 1-1) 435 s.Require().False(response.Messages()[0].Mentioned) 436 s.Require().Equal(int(response.Chats()[0].UnviewedMessagesCount), 1) 437 s.Require().Equal(int(response.Chats()[0].UnviewedMentionsCount), 1) 438 439 ogMessage := sendResponse.Messages()[0] 440 441 messageID, err := types.DecodeHex(ogMessage.ID) 442 s.Require().NoError(err) 443 444 // Edit the message and add a mention 445 editedText := "edited text @" + common.PubkeyToHex(&s.privateKey.PublicKey) 446 editedMessage := &requests.EditMessage{ 447 ID: messageID, 448 Text: editedText, 449 } 450 451 sendResponse, err = theirMessenger.EditMessage(context.Background(), editedMessage) 452 453 s.Require().NoError(err) 454 s.Require().Len(sendResponse.Messages(), 1) 455 s.Require().NotEmpty(sendResponse.Messages()[0].EditedAt) 456 s.Require().Equal(sendResponse.Messages()[0].Text, editedText) 457 s.Require().Len(sendResponse.Chats(), 1) 458 s.Require().NotNil(sendResponse.Chats()[0].LastMessage) 459 s.Require().NotEmpty(sendResponse.Chats()[0].LastMessage.EditedAt) 460 s.Require().False(sendResponse.Messages()[0].Mentioned) // Sender is still not mentioned 461 462 response, err = WaitOnMessengerResponse( 463 s.m, 464 func(r *MessengerResponse) bool { return len(r.messages) == 1 }, 465 "no messages", 466 ) 467 s.Require().NoError(err) 468 469 s.Require().Len(response.Chats(), 1) 470 s.Require().Len(response.Messages(), 1) 471 s.Require().NotEmpty(response.Messages()[0].EditedAt) 472 s.Require().False(response.Messages()[0].New) 473 // Receiver (us) is now mentioned 474 s.Require().True(response.Messages()[0].Mentioned) 475 s.Require().Equal(int(response.Chats()[0].UnviewedMessagesCount), 1) 476 s.Require().Equal(int(response.Chats()[0].UnviewedMentionsCount), 1) 477 478 // Edit the message again but remove the mention 479 editedText = "edited text no mention" 480 editedMessage = &requests.EditMessage{ 481 ID: messageID, 482 Text: editedText, 483 } 484 485 sendResponse, err = theirMessenger.EditMessage(context.Background(), editedMessage) 486 487 s.Require().NoError(err) 488 s.Require().Len(sendResponse.Messages(), 1) 489 s.Require().NotEmpty(sendResponse.Messages()[0].EditedAt) 490 s.Require().Equal(sendResponse.Messages()[0].Text, editedText) 491 s.Require().Len(sendResponse.Chats(), 1) 492 s.Require().NotNil(sendResponse.Chats()[0].LastMessage) 493 s.Require().NotEmpty(sendResponse.Chats()[0].LastMessage.EditedAt) 494 s.Require().False(sendResponse.Messages()[0].Mentioned) // Sender is still not mentioned 495 496 response, err = WaitOnMessengerResponse( 497 s.m, 498 func(r *MessengerResponse) bool { return len(r.messages) == 1 }, 499 "no messages", 500 ) 501 s.Require().NoError(err) 502 503 s.Require().Len(response.Chats(), 1) 504 s.Require().Len(response.Messages(), 1) 505 s.Require().NotEmpty(response.Messages()[0].EditedAt) 506 s.Require().False(response.Messages()[0].New) 507 // Receiver (us) is no longer mentioned 508 s.Require().False(response.Messages()[0].Mentioned) 509 s.Require().Equal(int(response.Chats()[0].UnviewedMessagesCount), 1) // We still have an unread message though 510 s.Require().Equal(int(response.Chats()[0].UnviewedMentionsCount), 1) 511 } 512 513 func (s *MessengerEditMessageSuite) TestEditMessageWithLinkPreviews() { 514 theirMessenger := s.newMessenger() 515 defer TearDownMessenger(&s.Suite, theirMessenger) 516 517 theirChat := CreateOneToOneChat("Their 1TO1", &s.privateKey.PublicKey, s.m.transport) 518 err := theirMessenger.SaveChat(theirChat) 519 s.Require().NoError(err) 520 521 ourChat := CreateOneToOneChat("Our 1TO1", &theirMessenger.identity.PublicKey, s.m.transport) 522 err = s.m.SaveChat(ourChat) 523 s.Require().NoError(err) 524 525 inputMessage := buildTestMessage(*theirChat) 526 527 sendResponse, err := theirMessenger.SendChatMessage(context.Background(), inputMessage) 528 s.NoError(err) 529 s.Require().Len(sendResponse.Messages(), 1) 530 531 response, err := WaitOnMessengerResponse( 532 s.m, 533 func(r *MessengerResponse) bool { return len(r.messages) > 0 }, 534 "no messages", 535 ) 536 s.Require().NoError(err) 537 s.Require().Len(response.Chats(), 1) 538 s.Require().Len(response.Messages(), 1) 539 540 ogMessage := sendResponse.Messages()[0] 541 542 messageID, err := types.DecodeHex(ogMessage.ID) 543 s.Require().NoError(err) 544 545 contactPublicKey, err := crypto.GenerateKey() 546 s.Require().NoError(err) 547 contactID := types.EncodeHex(crypto.FromECDSAPub(&contactPublicKey.PublicKey)) 548 549 editedText := "edited text" 550 editedMessage := &requests.EditMessage{ 551 ID: messageID, 552 Text: editedText, 553 LinkPreviews: []common.LinkPreview{ 554 { 555 Type: protobuf.UnfurledLink_LINK, 556 Description: "GitHub is where people build software.", 557 Hostname: "github.com", 558 Title: "Build software better, together", 559 URL: "https://github.com", 560 Thumbnail: common.LinkPreviewThumbnail{ 561 Width: 100, 562 Height: 200, 563 URL: "http://localhost:9999", 564 DataURI: "data:image/png;base64,iVBORw0KGgoAAAANSUg=", 565 }}, 566 }, 567 StatusLinkPreviews: []common.StatusLinkPreview{ 568 { 569 URL: "https://status.app/u/TestUrl", 570 Contact: &common.StatusContactLinkPreview{ 571 PublicKey: contactID, 572 DisplayName: "TestDisplayName", 573 Description: "Test description", 574 Icon: common.LinkPreviewThumbnail{ 575 Width: 100, 576 Height: 200, 577 DataURI: "data:image/png;base64,iVBORw0KGgoAAAANSUg=", 578 }, 579 }, 580 }, 581 }, 582 } 583 584 sendResponse, err = theirMessenger.EditMessage(context.Background(), editedMessage) 585 586 s.Require().NoError(err) 587 s.Require().Len(sendResponse.Messages(), 1) 588 s.Require().Len(sendResponse.Messages()[0].LinkPreviews, 1) 589 s.Require().NotNil(sendResponse.Messages()[0].UnfurledStatusLinks) 590 s.Require().Len(sendResponse.Messages()[0].UnfurledStatusLinks.UnfurledStatusLinks, 1) 591 response, err = WaitOnMessengerResponse( 592 s.m, 593 func(r *MessengerResponse) bool { return len(r.messages) == 1 }, 594 "no messages", 595 ) 596 s.Require().NoError(err) 597 598 s.Require().Len(response.Chats(), 1) 599 s.Require().Len(response.Messages(), 1) 600 601 responseMessage := response.Messages()[0] 602 s.Require().NotEmpty(responseMessage.EditedAt) 603 s.Require().Len(responseMessage.UnfurledLinks, 1) 604 s.Require().NotNil(responseMessage.UnfurledStatusLinks) 605 s.Require().Len(responseMessage.UnfurledStatusLinks.UnfurledStatusLinks, 1) 606 s.Require().False(responseMessage.New) 607 }