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  }