497 lines
16 KiB
Go
497 lines
16 KiB
Go
package protocol
|
|
|
|
import (
|
|
"context"
|
|
"crypto/ecdsa"
|
|
"fmt"
|
|
"testing"
|
|
|
|
"github.com/golang/protobuf/proto"
|
|
"github.com/stretchr/testify/suite"
|
|
|
|
userimage "github.com/status-im/status-go/images"
|
|
multiaccountscommon "github.com/status-im/status-go/multiaccounts/common"
|
|
|
|
"github.com/status-im/status-go/multiaccounts/settings"
|
|
"github.com/status-im/status-go/protocol/common"
|
|
"github.com/status-im/status-go/protocol/protobuf"
|
|
)
|
|
|
|
func TestGroupChatSuite(t *testing.T) {
|
|
suite.Run(t, new(MessengerGroupChatSuite))
|
|
}
|
|
|
|
type MessengerGroupChatSuite struct {
|
|
MessengerBaseTestSuite
|
|
}
|
|
|
|
func (s *MessengerGroupChatSuite) createGroupChat(creator *Messenger, name string, members []string) *Chat {
|
|
response, err := creator.CreateGroupChatWithMembers(context.Background(), name, members)
|
|
s.Require().NoError(err)
|
|
s.Require().Len(response.Chats(), 1)
|
|
|
|
chat := response.Chats()[0]
|
|
err = creator.SaveChat(chat)
|
|
s.Require().NoError(err)
|
|
|
|
return chat
|
|
}
|
|
|
|
func (s *MessengerGroupChatSuite) createEmptyGroupChat(creator *Messenger, name string) *Chat {
|
|
return s.createGroupChat(creator, name, []string{})
|
|
}
|
|
|
|
func (s *MessengerGroupChatSuite) verifyGroupChatCreated(member *Messenger, expectedChatActive bool) {
|
|
response, err := WaitOnMessengerResponse(
|
|
member,
|
|
func(r *MessengerResponse) bool {
|
|
return len(r.Chats()) == 1 && r.Chats()[0].Active == expectedChatActive
|
|
},
|
|
"chat invitation not received",
|
|
)
|
|
s.Require().NoError(err)
|
|
s.Require().Len(response.Chats(), 1)
|
|
s.Require().True(response.Chats()[0].Active == expectedChatActive)
|
|
}
|
|
|
|
func makeMutualContact(origin *Messenger, contactPubkey *ecdsa.PublicKey) error {
|
|
contact, err := BuildContactFromPublicKey(contactPubkey)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
contact.ContactRequestLocalState = ContactRequestStateSent
|
|
contact.ContactRequestRemoteState = ContactRequestStateReceived
|
|
origin.allContacts.Store(contact.ID, contact)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *MessengerGroupChatSuite) makeContact(origin *Messenger, toAdd *Messenger) {
|
|
s.Require().NoError(makeMutualContact(origin, &toAdd.identity.PublicKey))
|
|
}
|
|
|
|
func (s *MessengerGroupChatSuite) makeMutualContacts(lhs *Messenger, rhs *Messenger) {
|
|
s.makeContact(lhs, rhs)
|
|
s.makeContact(rhs, lhs)
|
|
}
|
|
|
|
func (s *MessengerGroupChatSuite) TestGroupChatCreation() {
|
|
testCases := []struct {
|
|
name string
|
|
creatorAddedMemberAsContact bool
|
|
memberAddedCreatorAsContact bool
|
|
expectedCreationSuccess bool
|
|
expectedAddedMemberChatActive bool
|
|
}{
|
|
{
|
|
name: "not added - not added",
|
|
creatorAddedMemberAsContact: false,
|
|
memberAddedCreatorAsContact: false,
|
|
expectedCreationSuccess: false,
|
|
expectedAddedMemberChatActive: false,
|
|
},
|
|
{
|
|
name: "added - not added",
|
|
creatorAddedMemberAsContact: true,
|
|
memberAddedCreatorAsContact: false,
|
|
expectedCreationSuccess: true,
|
|
expectedAddedMemberChatActive: false,
|
|
},
|
|
{
|
|
name: "not added - added",
|
|
creatorAddedMemberAsContact: false,
|
|
memberAddedCreatorAsContact: true,
|
|
expectedCreationSuccess: false,
|
|
expectedAddedMemberChatActive: false,
|
|
},
|
|
{
|
|
name: "added - added",
|
|
creatorAddedMemberAsContact: true,
|
|
memberAddedCreatorAsContact: true,
|
|
expectedCreationSuccess: true,
|
|
expectedAddedMemberChatActive: true,
|
|
},
|
|
}
|
|
|
|
for i, testCase := range testCases {
|
|
creator := s.newMessenger()
|
|
member := s.newMessenger()
|
|
members := []string{common.PubkeyToHex(&member.identity.PublicKey)}
|
|
|
|
if testCase.creatorAddedMemberAsContact {
|
|
s.makeContact(creator, member)
|
|
}
|
|
if testCase.memberAddedCreatorAsContact {
|
|
s.makeContact(member, creator)
|
|
}
|
|
|
|
_, err := creator.CreateGroupChatWithMembers(context.Background(), fmt.Sprintf("test_group_chat_%d", i), members)
|
|
if testCase.creatorAddedMemberAsContact {
|
|
s.Require().NoError(err)
|
|
s.verifyGroupChatCreated(member, testCase.expectedAddedMemberChatActive)
|
|
} else {
|
|
s.Require().EqualError(err, "group-chat: can't add members who are not mutual contacts")
|
|
}
|
|
|
|
defer s.NoError(creator.Shutdown())
|
|
defer s.NoError(member.Shutdown())
|
|
}
|
|
}
|
|
|
|
func (s *MessengerGroupChatSuite) TestGroupChatMembersAddition() {
|
|
testCases := []struct {
|
|
name string
|
|
inviterAddedMemberAsContact bool
|
|
memberAddedInviterAsContact bool
|
|
expectedAdditionSuccess bool
|
|
expectedAddedMemberChatActive bool
|
|
}{
|
|
{
|
|
name: "not added - not added",
|
|
inviterAddedMemberAsContact: false,
|
|
memberAddedInviterAsContact: false,
|
|
expectedAdditionSuccess: false,
|
|
expectedAddedMemberChatActive: false,
|
|
},
|
|
{
|
|
name: "added - not added",
|
|
inviterAddedMemberAsContact: true,
|
|
memberAddedInviterAsContact: false,
|
|
expectedAdditionSuccess: true,
|
|
expectedAddedMemberChatActive: false,
|
|
},
|
|
{
|
|
name: "not added - added",
|
|
inviterAddedMemberAsContact: false,
|
|
memberAddedInviterAsContact: true,
|
|
expectedAdditionSuccess: false,
|
|
expectedAddedMemberChatActive: false,
|
|
},
|
|
{
|
|
name: "added - added",
|
|
inviterAddedMemberAsContact: true,
|
|
memberAddedInviterAsContact: true,
|
|
expectedAdditionSuccess: true,
|
|
expectedAddedMemberChatActive: true,
|
|
},
|
|
}
|
|
|
|
for i, testCase := range testCases {
|
|
admin := s.newMessenger()
|
|
inviter := s.newMessenger()
|
|
member := s.newMessenger()
|
|
members := []string{common.PubkeyToHex(&member.identity.PublicKey)}
|
|
|
|
if testCase.inviterAddedMemberAsContact {
|
|
s.makeContact(inviter, member)
|
|
}
|
|
if testCase.memberAddedInviterAsContact {
|
|
s.makeContact(member, inviter)
|
|
}
|
|
|
|
for j, inviterIsAlsoGroupCreator := range []bool{true, false} {
|
|
var groupChat *Chat
|
|
if inviterIsAlsoGroupCreator {
|
|
groupChat = s.createEmptyGroupChat(inviter, fmt.Sprintf("test_group_chat_%d_%d", i, j))
|
|
} else {
|
|
s.makeContact(admin, inviter)
|
|
groupChat = s.createGroupChat(admin, fmt.Sprintf("test_group_chat_%d_%d", i, j), []string{common.PubkeyToHex(&inviter.identity.PublicKey)})
|
|
err := inviter.SaveChat(groupChat)
|
|
s.Require().NoError(err)
|
|
}
|
|
|
|
_, err := inviter.AddMembersToGroupChat(context.Background(), groupChat.ID, members)
|
|
if testCase.inviterAddedMemberAsContact {
|
|
s.Require().NoError(err)
|
|
s.verifyGroupChatCreated(member, testCase.expectedAddedMemberChatActive)
|
|
} else {
|
|
s.Require().EqualError(err, "group-chat: can't add members who are not mutual contacts")
|
|
}
|
|
}
|
|
|
|
defer s.NoError(admin.Shutdown())
|
|
defer s.NoError(inviter.Shutdown())
|
|
defer s.NoError(member.Shutdown())
|
|
}
|
|
}
|
|
|
|
func (s *MessengerGroupChatSuite) TestGroupChatMembersRemoval() {
|
|
admin := s.newMessenger()
|
|
memberA := s.newMessenger()
|
|
memberB := s.newMessenger()
|
|
memberC := s.newMessenger()
|
|
members := []string{common.PubkeyToHex(&memberA.identity.PublicKey), common.PubkeyToHex(&memberB.identity.PublicKey),
|
|
common.PubkeyToHex(&memberC.identity.PublicKey)}
|
|
|
|
s.makeMutualContacts(admin, memberA)
|
|
s.makeMutualContacts(admin, memberB)
|
|
s.makeMutualContacts(admin, memberC)
|
|
|
|
groupChat := s.createGroupChat(admin, "test_group_chat", members)
|
|
s.verifyGroupChatCreated(memberA, true)
|
|
s.verifyGroupChatCreated(memberB, true)
|
|
s.verifyGroupChatCreated(memberC, true)
|
|
|
|
_, err := memberA.RemoveMembersFromGroupChat(context.Background(), groupChat.ID, []string{common.PubkeyToHex(&memberB.identity.PublicKey),
|
|
common.PubkeyToHex(&memberC.identity.PublicKey)})
|
|
s.Require().Error(err)
|
|
|
|
// only admin can remove members from the group
|
|
_, err = admin.RemoveMembersFromGroupChat(context.Background(), groupChat.ID, []string{common.PubkeyToHex(&memberB.identity.PublicKey),
|
|
common.PubkeyToHex(&memberC.identity.PublicKey)})
|
|
s.Require().NoError(err)
|
|
|
|
// ensure removal is propagated to other members
|
|
response, err := WaitOnMessengerResponse(
|
|
memberA,
|
|
func(r *MessengerResponse) bool { return len(r.Chats()) > 0 },
|
|
"chat invitation not received",
|
|
)
|
|
s.Require().NoError(err)
|
|
s.Require().Len(response.Chats(), 1)
|
|
s.Require().True(response.Chats()[0].Active)
|
|
s.Require().Len(response.Chats()[0].Members, 2)
|
|
|
|
defer s.NoError(admin.Shutdown())
|
|
defer s.NoError(memberA.Shutdown())
|
|
defer s.NoError(memberB.Shutdown())
|
|
defer s.NoError(memberC.Shutdown())
|
|
}
|
|
|
|
func (s *MessengerGroupChatSuite) TestGroupChatEdit() {
|
|
admin := s.newMessenger()
|
|
member := s.newMessenger()
|
|
s.makeMutualContacts(admin, member)
|
|
|
|
groupChat := s.createGroupChat(admin, "test_group_chat", []string{common.PubkeyToHex(&member.identity.PublicKey)})
|
|
s.verifyGroupChatCreated(member, true)
|
|
|
|
response, err := admin.EditGroupChat(context.Background(), groupChat.ID, "test_admin_group", "#FF00FF", userimage.CroppedImage{})
|
|
s.Require().NoError(err)
|
|
s.Require().Len(response.Chats(), 1)
|
|
s.Require().Equal("test_admin_group", response.Chats()[0].Name)
|
|
s.Require().Equal("#FF00FF", response.Chats()[0].Color)
|
|
// TODO: handle image
|
|
|
|
// ensure group edit is propagated to other members
|
|
response, err = WaitOnMessengerResponse(
|
|
member,
|
|
func(r *MessengerResponse) bool { return len(r.Chats()) > 0 },
|
|
"chat invitation not received",
|
|
)
|
|
s.Require().NoError(err)
|
|
s.Require().Len(response.Chats(), 1)
|
|
s.Require().Equal("test_admin_group", response.Chats()[0].Name)
|
|
s.Require().Equal("#FF00FF", response.Chats()[0].Color)
|
|
|
|
response, err = member.EditGroupChat(context.Background(), groupChat.ID, "test_member_group", "#F0F0F0", userimage.CroppedImage{})
|
|
s.Require().NoError(err)
|
|
s.Require().Len(response.Chats(), 1)
|
|
s.Require().Equal("test_member_group", response.Chats()[0].Name)
|
|
s.Require().Equal("#F0F0F0", response.Chats()[0].Color)
|
|
|
|
// ensure group edit is propagated to other members
|
|
response, err = WaitOnMessengerResponse(
|
|
admin,
|
|
func(r *MessengerResponse) bool { return len(r.Chats()) > 0 },
|
|
"chat invitation not received",
|
|
)
|
|
s.Require().NoError(err)
|
|
s.Require().Len(response.Chats(), 1)
|
|
s.Require().Equal("test_member_group", response.Chats()[0].Name)
|
|
s.Require().Equal("#F0F0F0", response.Chats()[0].Color)
|
|
|
|
inputMessage := buildTestMessage(*groupChat)
|
|
|
|
_, err = admin.SendChatMessage(context.Background(), inputMessage)
|
|
s.Require().NoError(err)
|
|
|
|
response, err = WaitOnMessengerResponse(
|
|
member,
|
|
func(r *MessengerResponse) bool { return len(r.Messages()) > 0 },
|
|
"chat invitation not received",
|
|
)
|
|
s.Require().NoError(err)
|
|
s.Require().Len(response.Messages(), 1)
|
|
s.Require().Equal(inputMessage.Text, response.Messages()[0].Text)
|
|
|
|
defer s.NoError(admin.Shutdown())
|
|
defer s.NoError(member.Shutdown())
|
|
}
|
|
|
|
func (s *MessengerGroupChatSuite) TestGroupChatDeleteMemberMessage() {
|
|
admin := s.newMessenger()
|
|
member := s.newMessenger()
|
|
s.makeMutualContacts(admin, member)
|
|
|
|
groupChat := s.createGroupChat(admin, "test_group_chat", []string{common.PubkeyToHex(&member.identity.PublicKey)})
|
|
s.verifyGroupChatCreated(member, true)
|
|
|
|
ctx := context.Background()
|
|
inputMessage := buildTestMessage(*groupChat)
|
|
_, err := member.SendChatMessage(ctx, inputMessage)
|
|
s.Require().NoError(err)
|
|
|
|
response, err := WaitOnMessengerResponse(
|
|
admin,
|
|
func(r *MessengerResponse) bool { return len(r.Messages()) > 0 },
|
|
"messages not received",
|
|
)
|
|
s.Require().NoError(err)
|
|
s.Require().Len(response.Messages(), 1)
|
|
s.Require().Equal(inputMessage.Text, response.Messages()[0].Text)
|
|
|
|
message := response.Messages()[0]
|
|
deleteMessageResponse, err := admin.DeleteMessageAndSend(ctx, message.ID)
|
|
s.Require().NoError(err)
|
|
|
|
_, err = WaitOnMessengerResponse(member, func(response *MessengerResponse) bool {
|
|
return len(response.RemovedMessages()) > 0
|
|
}, "removed messages not received")
|
|
s.Require().Equal(deleteMessageResponse.RemovedMessages()[0].DeletedBy, contactIDFromPublicKey(admin.IdentityPublicKey()))
|
|
s.Require().NoError(err)
|
|
message, err = member.MessageByID(message.ID)
|
|
s.Require().NoError(err)
|
|
s.Require().True(message.Deleted)
|
|
|
|
defer s.NoError(admin.Shutdown())
|
|
defer s.NoError(member.Shutdown())
|
|
}
|
|
|
|
func (s *MessengerGroupChatSuite) TestGroupChatHandleDeleteMemberMessage() {
|
|
admin := s.newMessenger()
|
|
member := s.newMessenger()
|
|
s.makeMutualContacts(admin, member)
|
|
|
|
groupChat := s.createGroupChat(admin, "test_group_chat", []string{common.PubkeyToHex(&member.identity.PublicKey)})
|
|
s.verifyGroupChatCreated(member, true)
|
|
|
|
ctx := context.Background()
|
|
inputMessage := buildTestMessage(*groupChat)
|
|
_, err := member.SendChatMessage(ctx, inputMessage)
|
|
s.Require().NoError(err)
|
|
|
|
response, err := WaitOnMessengerResponse(
|
|
admin,
|
|
func(r *MessengerResponse) bool { return len(r.Messages()) > 0 },
|
|
"messages not received",
|
|
)
|
|
s.Require().NoError(err)
|
|
s.Require().Len(response.Messages(), 1)
|
|
s.Require().Equal(inputMessage.Text, response.Messages()[0].Text)
|
|
|
|
deleteMessage := &DeleteMessage{
|
|
DeleteMessage: &protobuf.DeleteMessage{
|
|
Clock: 2,
|
|
MessageType: protobuf.MessageType_PRIVATE_GROUP,
|
|
MessageId: inputMessage.ID,
|
|
ChatId: groupChat.ID,
|
|
},
|
|
From: common.PubkeyToHex(&admin.identity.PublicKey),
|
|
}
|
|
|
|
state := &ReceivedMessageState{
|
|
Response: &MessengerResponse{},
|
|
}
|
|
|
|
err = member.handleDeleteMessage(state, deleteMessage)
|
|
s.Require().NoError(err)
|
|
|
|
removedMessages := state.Response.RemovedMessages()
|
|
s.Require().Len(removedMessages, 1)
|
|
s.Require().Equal(removedMessages[0].MessageID, inputMessage.ID)
|
|
|
|
defer s.NoError(admin.Shutdown())
|
|
defer s.NoError(member.Shutdown())
|
|
}
|
|
|
|
func (s *MessengerGroupChatSuite) TestGroupChatMembersRemovalOutOfOrder() {
|
|
admin := s.newMessenger()
|
|
memberA := s.newMessenger()
|
|
members := []string{common.PubkeyToHex(&memberA.identity.PublicKey)}
|
|
|
|
s.makeMutualContacts(admin, memberA)
|
|
|
|
groupChat := s.createGroupChat(admin, "test_group_chat", members)
|
|
|
|
removeMembersResponse, err := admin.removeMembersFromGroupChat(context.Background(), groupChat, []string{common.PubkeyToHex(&memberA.identity.PublicKey)})
|
|
s.Require().NoError(err)
|
|
|
|
encodedMessage := removeMembersResponse.encodedProtobuf
|
|
|
|
message := protobuf.MembershipUpdateMessage{}
|
|
err = proto.Unmarshal(encodedMessage, &message)
|
|
s.Require().NoError(err)
|
|
|
|
response := &MessengerResponse{}
|
|
|
|
messageState := &ReceivedMessageState{
|
|
ExistingMessagesMap: make(map[string]bool),
|
|
Response: response,
|
|
AllChats: new(chatMap),
|
|
Timesource: memberA.getTimesource(),
|
|
}
|
|
|
|
c, err := buildContact(admin.myHexIdentity(), &admin.identity.PublicKey)
|
|
s.Require().NoError(err)
|
|
|
|
messageState.CurrentMessageState = &CurrentMessageState{
|
|
Contact: c,
|
|
}
|
|
|
|
err = memberA.HandleMembershipUpdate(messageState, nil, &message, memberA.systemMessagesTranslations)
|
|
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(messageState.Response)
|
|
s.Require().Len(messageState.Response.Chats(), 1)
|
|
s.Require().Len(messageState.Response.Chats()[0].Members, 1)
|
|
defer s.NoError(admin.Shutdown())
|
|
defer s.NoError(memberA.Shutdown())
|
|
}
|
|
|
|
func (s *MessengerGroupChatSuite) TestGroupChatMembersInfoSync() {
|
|
admin, memberA, memberB := s.newMessenger(), s.newMessenger(), s.newMessenger()
|
|
memberB.account.CustomizationColor = multiaccountscommon.CustomizationColorBlue
|
|
s.Require().NoError(admin.settings.SaveSettingField(settings.DisplayName, "admin"))
|
|
s.Require().NoError(memberA.settings.SaveSettingField(settings.DisplayName, "memberA"))
|
|
s.Require().NoError(memberB.settings.SaveSettingField(settings.DisplayName, "memberB"))
|
|
|
|
members := []string{common.PubkeyToHex(&memberA.identity.PublicKey), common.PubkeyToHex(&memberB.identity.PublicKey)}
|
|
|
|
s.makeMutualContacts(admin, memberA)
|
|
s.makeMutualContacts(admin, memberB)
|
|
|
|
s.createGroupChat(admin, "test_group_chat", members)
|
|
s.verifyGroupChatCreated(memberA, true)
|
|
s.verifyGroupChatCreated(memberB, true)
|
|
|
|
response, err := WaitOnMessengerResponse(
|
|
memberA,
|
|
func(r *MessengerResponse) bool { return len(r.Chats()) > 0 },
|
|
"chat invitation not received",
|
|
)
|
|
s.Require().NoError(err)
|
|
s.Require().Len(response.Chats(), 1)
|
|
s.Require().True(response.Chats()[0].Active)
|
|
s.Require().Len(response.Chats()[0].Members, 3)
|
|
|
|
_, err = WaitOnMessengerResponse(
|
|
memberA,
|
|
func(r *MessengerResponse) bool {
|
|
// we republish as we don't have store nodes in tests
|
|
err := memberB.publishContactCode()
|
|
if err != nil {
|
|
return false
|
|
}
|
|
contact, ok := memberA.allContacts.Load(common.PubkeyToHex(&memberB.identity.PublicKey))
|
|
return ok && contact.DisplayName == "memberB" && contact.CustomizationColor == memberB.account.GetCustomizationColor()
|
|
},
|
|
"DisplayName is not the same",
|
|
)
|
|
s.Require().NoError(err)
|
|
|
|
s.NoError(admin.Shutdown())
|
|
s.NoError(memberA.Shutdown())
|
|
s.NoError(memberB.Shutdown())
|
|
}
|