github.com/status-im/status-go@v1.1.0/protocol/messenger_group_chat_test.go (about) 1 package protocol 2 3 import ( 4 "context" 5 "crypto/ecdsa" 6 "fmt" 7 "testing" 8 9 "github.com/golang/protobuf/proto" 10 "github.com/stretchr/testify/suite" 11 12 userimage "github.com/status-im/status-go/images" 13 multiaccountscommon "github.com/status-im/status-go/multiaccounts/common" 14 15 "github.com/status-im/status-go/multiaccounts/settings" 16 "github.com/status-im/status-go/protocol/common" 17 "github.com/status-im/status-go/protocol/protobuf" 18 ) 19 20 func TestGroupChatSuite(t *testing.T) { 21 suite.Run(t, new(MessengerGroupChatSuite)) 22 } 23 24 type MessengerGroupChatSuite struct { 25 MessengerBaseTestSuite 26 } 27 28 func (s *MessengerGroupChatSuite) createGroupChat(creator *Messenger, name string, members []string) *Chat { 29 response, err := creator.CreateGroupChatWithMembers(context.Background(), name, members) 30 s.Require().NoError(err) 31 s.Require().Len(response.Chats(), 1) 32 33 chat := response.Chats()[0] 34 err = creator.SaveChat(chat) 35 s.Require().NoError(err) 36 37 return chat 38 } 39 40 func (s *MessengerGroupChatSuite) createEmptyGroupChat(creator *Messenger, name string) *Chat { 41 return s.createGroupChat(creator, name, []string{}) 42 } 43 44 func (s *MessengerGroupChatSuite) verifyGroupChatCreated(member *Messenger, expectedChatActive bool) { 45 response, err := WaitOnMessengerResponse( 46 member, 47 func(r *MessengerResponse) bool { 48 return len(r.Chats()) == 1 && r.Chats()[0].Active == expectedChatActive 49 }, 50 "chat invitation not received", 51 ) 52 s.Require().NoError(err) 53 s.Require().Len(response.Chats(), 1) 54 s.Require().True(response.Chats()[0].Active == expectedChatActive) 55 } 56 57 func makeMutualContact(origin *Messenger, contactPubkey *ecdsa.PublicKey) error { 58 contact, err := BuildContactFromPublicKey(contactPubkey) 59 if err != nil { 60 return err 61 } 62 contact.ContactRequestLocalState = ContactRequestStateSent 63 contact.ContactRequestRemoteState = ContactRequestStateReceived 64 origin.allContacts.Store(contact.ID, contact) 65 66 return nil 67 } 68 69 func (s *MessengerGroupChatSuite) makeContact(origin *Messenger, toAdd *Messenger) { 70 s.Require().NoError(makeMutualContact(origin, &toAdd.identity.PublicKey)) 71 } 72 73 func (s *MessengerGroupChatSuite) makeMutualContacts(lhs *Messenger, rhs *Messenger) { 74 s.makeContact(lhs, rhs) 75 s.makeContact(rhs, lhs) 76 } 77 78 func (s *MessengerGroupChatSuite) TestGroupChatCreation() { 79 testCases := []struct { 80 name string 81 creatorAddedMemberAsContact bool 82 memberAddedCreatorAsContact bool 83 expectedCreationSuccess bool 84 expectedAddedMemberChatActive bool 85 }{ 86 { 87 name: "not added - not added", 88 creatorAddedMemberAsContact: false, 89 memberAddedCreatorAsContact: false, 90 expectedCreationSuccess: false, 91 expectedAddedMemberChatActive: false, 92 }, 93 { 94 name: "added - not added", 95 creatorAddedMemberAsContact: true, 96 memberAddedCreatorAsContact: false, 97 expectedCreationSuccess: true, 98 expectedAddedMemberChatActive: false, 99 }, 100 { 101 name: "not added - added", 102 creatorAddedMemberAsContact: false, 103 memberAddedCreatorAsContact: true, 104 expectedCreationSuccess: false, 105 expectedAddedMemberChatActive: false, 106 }, 107 { 108 name: "added - added", 109 creatorAddedMemberAsContact: true, 110 memberAddedCreatorAsContact: true, 111 expectedCreationSuccess: true, 112 expectedAddedMemberChatActive: true, 113 }, 114 } 115 116 for i, testCase := range testCases { 117 creator := s.newMessenger() 118 member := s.newMessenger() 119 members := []string{common.PubkeyToHex(&member.identity.PublicKey)} 120 121 if testCase.creatorAddedMemberAsContact { 122 s.makeContact(creator, member) 123 } 124 if testCase.memberAddedCreatorAsContact { 125 s.makeContact(member, creator) 126 } 127 128 _, err := creator.CreateGroupChatWithMembers(context.Background(), fmt.Sprintf("test_group_chat_%d", i), members) 129 if testCase.creatorAddedMemberAsContact { 130 s.Require().NoError(err) 131 s.verifyGroupChatCreated(member, testCase.expectedAddedMemberChatActive) 132 } else { 133 s.Require().EqualError(err, "group-chat: can't add members who are not mutual contacts") 134 } 135 136 defer s.NoError(creator.Shutdown()) 137 defer s.NoError(member.Shutdown()) 138 } 139 } 140 141 func (s *MessengerGroupChatSuite) TestGroupChatMembersAddition() { 142 testCases := []struct { 143 name string 144 inviterAddedMemberAsContact bool 145 memberAddedInviterAsContact bool 146 expectedAdditionSuccess bool 147 expectedAddedMemberChatActive bool 148 }{ 149 { 150 name: "not added - not added", 151 inviterAddedMemberAsContact: false, 152 memberAddedInviterAsContact: false, 153 expectedAdditionSuccess: false, 154 expectedAddedMemberChatActive: false, 155 }, 156 { 157 name: "added - not added", 158 inviterAddedMemberAsContact: true, 159 memberAddedInviterAsContact: false, 160 expectedAdditionSuccess: true, 161 expectedAddedMemberChatActive: false, 162 }, 163 { 164 name: "not added - added", 165 inviterAddedMemberAsContact: false, 166 memberAddedInviterAsContact: true, 167 expectedAdditionSuccess: false, 168 expectedAddedMemberChatActive: false, 169 }, 170 { 171 name: "added - added", 172 inviterAddedMemberAsContact: true, 173 memberAddedInviterAsContact: true, 174 expectedAdditionSuccess: true, 175 expectedAddedMemberChatActive: true, 176 }, 177 } 178 179 for i, testCase := range testCases { 180 admin := s.newMessenger() 181 inviter := s.newMessenger() 182 member := s.newMessenger() 183 members := []string{common.PubkeyToHex(&member.identity.PublicKey)} 184 185 if testCase.inviterAddedMemberAsContact { 186 s.makeContact(inviter, member) 187 } 188 if testCase.memberAddedInviterAsContact { 189 s.makeContact(member, inviter) 190 } 191 192 for j, inviterIsAlsoGroupCreator := range []bool{true, false} { 193 var groupChat *Chat 194 if inviterIsAlsoGroupCreator { 195 groupChat = s.createEmptyGroupChat(inviter, fmt.Sprintf("test_group_chat_%d_%d", i, j)) 196 } else { 197 s.makeContact(admin, inviter) 198 groupChat = s.createGroupChat(admin, fmt.Sprintf("test_group_chat_%d_%d", i, j), []string{common.PubkeyToHex(&inviter.identity.PublicKey)}) 199 err := inviter.SaveChat(groupChat) 200 s.Require().NoError(err) 201 } 202 203 _, err := inviter.AddMembersToGroupChat(context.Background(), groupChat.ID, members) 204 if testCase.inviterAddedMemberAsContact { 205 s.Require().NoError(err) 206 s.verifyGroupChatCreated(member, testCase.expectedAddedMemberChatActive) 207 } else { 208 s.Require().EqualError(err, "group-chat: can't add members who are not mutual contacts") 209 } 210 } 211 212 defer s.NoError(admin.Shutdown()) 213 defer s.NoError(inviter.Shutdown()) 214 defer s.NoError(member.Shutdown()) 215 } 216 } 217 218 func (s *MessengerGroupChatSuite) TestGroupChatMembersRemoval() { 219 admin := s.newMessenger() 220 memberA := s.newMessenger() 221 memberB := s.newMessenger() 222 memberC := s.newMessenger() 223 members := []string{common.PubkeyToHex(&memberA.identity.PublicKey), common.PubkeyToHex(&memberB.identity.PublicKey), 224 common.PubkeyToHex(&memberC.identity.PublicKey)} 225 226 s.makeMutualContacts(admin, memberA) 227 s.makeMutualContacts(admin, memberB) 228 s.makeMutualContacts(admin, memberC) 229 230 groupChat := s.createGroupChat(admin, "test_group_chat", members) 231 s.verifyGroupChatCreated(memberA, true) 232 s.verifyGroupChatCreated(memberB, true) 233 s.verifyGroupChatCreated(memberC, true) 234 235 _, err := memberA.RemoveMembersFromGroupChat(context.Background(), groupChat.ID, []string{common.PubkeyToHex(&memberB.identity.PublicKey), 236 common.PubkeyToHex(&memberC.identity.PublicKey)}) 237 s.Require().Error(err) 238 239 // only admin can remove members from the group 240 _, err = admin.RemoveMembersFromGroupChat(context.Background(), groupChat.ID, []string{common.PubkeyToHex(&memberB.identity.PublicKey), 241 common.PubkeyToHex(&memberC.identity.PublicKey)}) 242 s.Require().NoError(err) 243 244 // ensure removal is propagated to other members 245 response, err := WaitOnMessengerResponse( 246 memberA, 247 func(r *MessengerResponse) bool { return len(r.Chats()) > 0 }, 248 "chat invitation not received", 249 ) 250 s.Require().NoError(err) 251 s.Require().Len(response.Chats(), 1) 252 s.Require().True(response.Chats()[0].Active) 253 s.Require().Len(response.Chats()[0].Members, 2) 254 255 defer s.NoError(admin.Shutdown()) 256 defer s.NoError(memberA.Shutdown()) 257 defer s.NoError(memberB.Shutdown()) 258 defer s.NoError(memberC.Shutdown()) 259 } 260 261 func (s *MessengerGroupChatSuite) TestGroupChatEdit() { 262 admin := s.newMessenger() 263 member := s.newMessenger() 264 s.makeMutualContacts(admin, member) 265 266 groupChat := s.createGroupChat(admin, "test_group_chat", []string{common.PubkeyToHex(&member.identity.PublicKey)}) 267 s.verifyGroupChatCreated(member, true) 268 269 response, err := admin.EditGroupChat(context.Background(), groupChat.ID, "test_admin_group", "#FF00FF", userimage.CroppedImage{}) 270 s.Require().NoError(err) 271 s.Require().Len(response.Chats(), 1) 272 s.Require().Equal("test_admin_group", response.Chats()[0].Name) 273 s.Require().Equal("#FF00FF", response.Chats()[0].Color) 274 // TODO: handle image 275 276 // ensure group edit is propagated to other members 277 response, err = WaitOnMessengerResponse( 278 member, 279 func(r *MessengerResponse) bool { return len(r.Chats()) > 0 }, 280 "chat invitation not received", 281 ) 282 s.Require().NoError(err) 283 s.Require().Len(response.Chats(), 1) 284 s.Require().Equal("test_admin_group", response.Chats()[0].Name) 285 s.Require().Equal("#FF00FF", response.Chats()[0].Color) 286 287 response, err = member.EditGroupChat(context.Background(), groupChat.ID, "test_member_group", "#F0F0F0", userimage.CroppedImage{}) 288 s.Require().NoError(err) 289 s.Require().Len(response.Chats(), 1) 290 s.Require().Equal("test_member_group", response.Chats()[0].Name) 291 s.Require().Equal("#F0F0F0", response.Chats()[0].Color) 292 293 // ensure group edit is propagated to other members 294 response, err = WaitOnMessengerResponse( 295 admin, 296 func(r *MessengerResponse) bool { return len(r.Chats()) > 0 }, 297 "chat invitation not received", 298 ) 299 s.Require().NoError(err) 300 s.Require().Len(response.Chats(), 1) 301 s.Require().Equal("test_member_group", response.Chats()[0].Name) 302 s.Require().Equal("#F0F0F0", response.Chats()[0].Color) 303 304 inputMessage := buildTestMessage(*groupChat) 305 306 _, err = admin.SendChatMessage(context.Background(), inputMessage) 307 s.Require().NoError(err) 308 309 response, err = WaitOnMessengerResponse( 310 member, 311 func(r *MessengerResponse) bool { return len(r.Messages()) > 0 }, 312 "chat invitation not received", 313 ) 314 s.Require().NoError(err) 315 s.Require().Len(response.Messages(), 1) 316 s.Require().Equal(inputMessage.Text, response.Messages()[0].Text) 317 318 defer s.NoError(admin.Shutdown()) 319 defer s.NoError(member.Shutdown()) 320 } 321 322 func (s *MessengerGroupChatSuite) TestGroupChatDeleteMemberMessage() { 323 admin := s.newMessenger() 324 member := s.newMessenger() 325 s.makeMutualContacts(admin, member) 326 327 groupChat := s.createGroupChat(admin, "test_group_chat", []string{common.PubkeyToHex(&member.identity.PublicKey)}) 328 s.verifyGroupChatCreated(member, true) 329 330 ctx := context.Background() 331 inputMessage := buildTestMessage(*groupChat) 332 _, err := member.SendChatMessage(ctx, inputMessage) 333 s.Require().NoError(err) 334 335 response, err := WaitOnMessengerResponse( 336 admin, 337 func(r *MessengerResponse) bool { return len(r.Messages()) > 0 }, 338 "messages not received", 339 ) 340 s.Require().NoError(err) 341 s.Require().Len(response.Messages(), 1) 342 s.Require().Equal(inputMessage.Text, response.Messages()[0].Text) 343 344 message := response.Messages()[0] 345 deleteMessageResponse, err := admin.DeleteMessageAndSend(ctx, message.ID) 346 s.Require().NoError(err) 347 348 _, err = WaitOnMessengerResponse(member, func(response *MessengerResponse) bool { 349 return len(response.RemovedMessages()) > 0 350 }, "removed messages not received") 351 s.Require().Equal(deleteMessageResponse.RemovedMessages()[0].DeletedBy, contactIDFromPublicKey(admin.IdentityPublicKey())) 352 s.Require().NoError(err) 353 message, err = member.MessageByID(message.ID) 354 s.Require().NoError(err) 355 s.Require().True(message.Deleted) 356 357 defer s.NoError(admin.Shutdown()) 358 defer s.NoError(member.Shutdown()) 359 } 360 361 func (s *MessengerGroupChatSuite) TestGroupChatHandleDeleteMemberMessage() { 362 admin := s.newMessenger() 363 member := s.newMessenger() 364 s.makeMutualContacts(admin, member) 365 366 groupChat := s.createGroupChat(admin, "test_group_chat", []string{common.PubkeyToHex(&member.identity.PublicKey)}) 367 s.verifyGroupChatCreated(member, true) 368 369 ctx := context.Background() 370 inputMessage := buildTestMessage(*groupChat) 371 _, err := member.SendChatMessage(ctx, inputMessage) 372 s.Require().NoError(err) 373 374 response, err := WaitOnMessengerResponse( 375 admin, 376 func(r *MessengerResponse) bool { return len(r.Messages()) > 0 }, 377 "messages not received", 378 ) 379 s.Require().NoError(err) 380 s.Require().Len(response.Messages(), 1) 381 s.Require().Equal(inputMessage.Text, response.Messages()[0].Text) 382 383 deleteMessage := &DeleteMessage{ 384 DeleteMessage: &protobuf.DeleteMessage{ 385 Clock: 2, 386 MessageType: protobuf.MessageType_PRIVATE_GROUP, 387 MessageId: inputMessage.ID, 388 ChatId: groupChat.ID, 389 }, 390 From: common.PubkeyToHex(&admin.identity.PublicKey), 391 } 392 393 state := &ReceivedMessageState{ 394 Response: &MessengerResponse{}, 395 } 396 397 err = member.handleDeleteMessage(state, deleteMessage) 398 s.Require().NoError(err) 399 400 removedMessages := state.Response.RemovedMessages() 401 s.Require().Len(removedMessages, 1) 402 s.Require().Equal(removedMessages[0].MessageID, inputMessage.ID) 403 404 defer s.NoError(admin.Shutdown()) 405 defer s.NoError(member.Shutdown()) 406 } 407 408 func (s *MessengerGroupChatSuite) TestGroupChatMembersRemovalOutOfOrder() { 409 admin := s.newMessenger() 410 memberA := s.newMessenger() 411 members := []string{common.PubkeyToHex(&memberA.identity.PublicKey)} 412 413 s.makeMutualContacts(admin, memberA) 414 415 groupChat := s.createGroupChat(admin, "test_group_chat", members) 416 417 removeMembersResponse, err := admin.removeMembersFromGroupChat(context.Background(), groupChat, []string{common.PubkeyToHex(&memberA.identity.PublicKey)}) 418 s.Require().NoError(err) 419 420 encodedMessage := removeMembersResponse.encodedProtobuf 421 422 message := protobuf.MembershipUpdateMessage{} 423 err = proto.Unmarshal(encodedMessage, &message) 424 s.Require().NoError(err) 425 426 response := &MessengerResponse{} 427 428 messageState := &ReceivedMessageState{ 429 ExistingMessagesMap: make(map[string]bool), 430 Response: response, 431 AllChats: new(chatMap), 432 Timesource: memberA.getTimesource(), 433 } 434 435 c, err := buildContact(admin.myHexIdentity(), &admin.identity.PublicKey) 436 s.Require().NoError(err) 437 438 messageState.CurrentMessageState = &CurrentMessageState{ 439 Contact: c, 440 } 441 442 err = memberA.HandleMembershipUpdate(messageState, nil, &message, memberA.systemMessagesTranslations) 443 444 s.Require().NoError(err) 445 s.Require().NotNil(messageState.Response) 446 s.Require().Len(messageState.Response.Chats(), 1) 447 s.Require().Len(messageState.Response.Chats()[0].Members, 1) 448 defer s.NoError(admin.Shutdown()) 449 defer s.NoError(memberA.Shutdown()) 450 } 451 452 func (s *MessengerGroupChatSuite) TestGroupChatMembersInfoSync() { 453 admin, memberA, memberB := s.newMessenger(), s.newMessenger(), s.newMessenger() 454 memberB.account.CustomizationColor = multiaccountscommon.CustomizationColorBlue 455 s.Require().NoError(admin.settings.SaveSettingField(settings.DisplayName, "admin")) 456 s.Require().NoError(memberA.settings.SaveSettingField(settings.DisplayName, "memberA")) 457 s.Require().NoError(memberB.settings.SaveSettingField(settings.DisplayName, "memberB")) 458 459 members := []string{common.PubkeyToHex(&memberA.identity.PublicKey), common.PubkeyToHex(&memberB.identity.PublicKey)} 460 461 s.makeMutualContacts(admin, memberA) 462 s.makeMutualContacts(admin, memberB) 463 464 s.createGroupChat(admin, "test_group_chat", members) 465 s.verifyGroupChatCreated(memberA, true) 466 s.verifyGroupChatCreated(memberB, true) 467 468 response, err := WaitOnMessengerResponse( 469 memberA, 470 func(r *MessengerResponse) bool { return len(r.Chats()) > 0 }, 471 "chat invitation not received", 472 ) 473 s.Require().NoError(err) 474 s.Require().Len(response.Chats(), 1) 475 s.Require().True(response.Chats()[0].Active) 476 s.Require().Len(response.Chats()[0].Members, 3) 477 478 _, err = WaitOnMessengerResponse( 479 memberA, 480 func(r *MessengerResponse) bool { 481 // we republish as we don't have store nodes in tests 482 err := memberB.publishContactCode() 483 if err != nil { 484 return false 485 } 486 contact, ok := memberA.allContacts.Load(common.PubkeyToHex(&memberB.identity.PublicKey)) 487 return ok && contact.DisplayName == "memberB" && contact.CustomizationColor == memberB.account.GetCustomizationColor() 488 }, 489 "DisplayName is not the same", 490 ) 491 s.Require().NoError(err) 492 493 s.NoError(admin.Shutdown()) 494 s.NoError(memberA.Shutdown()) 495 s.NoError(memberB.Shutdown()) 496 }