package protocol import ( "context" "crypto/ecdsa" "encoding/hex" "encoding/json" "errors" "io/ioutil" "math/big" "os" "strconv" "strings" "testing" "time" "github.com/google/uuid" _ "github.com/mutecomm/go-sqlcipher" // require go-sqlcipher that overrides default implementation "github.com/stretchr/testify/suite" "go.uber.org/zap" gethbridge "github.com/status-im/status-go/eth-node/bridge/geth" coretypes "github.com/status-im/status-go/eth-node/core/types" "github.com/status-im/status-go/eth-node/crypto" "github.com/status-im/status-go/eth-node/types" enstypes "github.com/status-im/status-go/eth-node/types/ens" "github.com/status-im/status-go/protocol/protobuf" "github.com/status-im/status-go/protocol/tt" v1protocol "github.com/status-im/status-go/protocol/v1" "github.com/status-im/status-go/whisper/v6" ) const ( testPK = "0x0424a68f89ba5fcd5e0640c1e1f591d561fa4125ca4e2a43592bc4123eca10ce064e522c254bb83079ba404327f6eafc01ec90a1444331fe769d3f3a7f90b0dde1" testPublicChatID = "super-chat" testContract = "0x314159265dd8dbb310642f98f50c066173c1259b" testValue = "2000" testTransactionHash = "0x412a851ac2ae51cad34a56c8a9cfee55d577ac5e1ac71cf488a2f2093a373799" testIdenticon = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAIAAACRXR/mAAAAnElEQVR4nOzXQaqDMBRG4bZkLR10e12H23PgZuJUjJAcE8kdnG/44IXDhZ9iyjm/4vnMDrhmFmEWYRZhFpH6n1jW7fSX/+/b+WbQa5lFmEVUljhqZfSdoNcyizCLeNMvn3JTLeh+g17LLMIsorLElt2VK7v3X0dBr2UWYRaBfxNLfifOZhYRNGvAEp8Q9FpmEWYRZhFmEXsAAAD//5K5JFhu0M0nAAAAAElFTkSuQmCC" testAlias = "Concrete Lavender Xiphias" ) func TestMessengerSuite(t *testing.T) { suite.Run(t, new(MessengerSuite)) } func TestMessengerWithDataSyncEnabledSuite(t *testing.T) { suite.Run(t, &MessengerSuite{enableDataSync: true}) } func TestMessageHandlerSuite(t *testing.T) { suite.Run(t, new(MessageHandlerSuite)) } type MessengerSuite struct { suite.Suite enableDataSync bool m *Messenger // main instance of Messenger privateKey *ecdsa.PrivateKey // private key for the main instance of Messenger // If one wants to send messages between different instances of Messenger, // a single Whisper service should be shared. shh types.Whisper tmpFiles []*os.File // files to clean up logger *zap.Logger } type testNode struct { shh types.Whisper } func (n *testNode) NewENSVerifier(_ *zap.Logger) enstypes.ENSVerifier { panic("not implemented") } func (n *testNode) AddPeer(_ string) error { panic("not implemented") } func (n *testNode) RemovePeer(_ string) error { panic("not implemented") } func (n *testNode) GetWaku(_ interface{}) (types.Waku, error) { panic("not implemented") } func (n *testNode) GetWhisper(_ interface{}) (types.Whisper, error) { return n.shh, nil } func (s *MessengerSuite) SetupTest() { s.logger = tt.MustCreateTestLogger() config := whisper.DefaultConfig config.MinimumAcceptedPOW = 0 shh := whisper.New(&config) s.shh = gethbridge.NewGethWhisperWrapper(shh) s.Require().NoError(shh.Start(nil)) s.m = s.newMessenger(s.shh) s.privateKey = s.m.identity } func (s *MessengerSuite) newMessengerWithKey(shh types.Whisper, privateKey *ecdsa.PrivateKey) *Messenger { tmpFile, err := ioutil.TempFile("", "") s.Require().NoError(err) options := []Option{ WithCustomLogger(s.logger), WithMessagesPersistenceEnabled(), WithDatabaseConfig(tmpFile.Name(), "some-key"), } if s.enableDataSync { options = append(options, WithDatasync()) } m, err := NewMessenger( privateKey, &testNode{shh: shh}, uuid.New().String(), options..., ) s.Require().NoError(err) err = m.Init() s.Require().NoError(err) s.tmpFiles = append(s.tmpFiles, tmpFile) return m } func (s *MessengerSuite) newMessenger(shh types.Whisper) *Messenger { privateKey, err := crypto.GenerateKey() s.Require().NoError(err) return s.newMessengerWithKey(shh, privateKey) } func (s *MessengerSuite) TearDownTest() { s.Require().NoError(s.m.Shutdown()) for _, f := range s.tmpFiles { _ = os.Remove(f.Name()) } _ = s.logger.Sync() } func (s *MessengerSuite) TestInit() { testCases := []struct { Name string Prep func() AddedFilters int }{ { Name: "no chats and contacts", Prep: func() {}, AddedFilters: 3, }, { Name: "active public chat", Prep: func() { publicChat := Chat{ ChatType: ChatTypePublic, ID: "some-public-chat", Active: true, } err := s.m.SaveChat(&publicChat) s.Require().NoError(err) }, AddedFilters: 1, }, { Name: "active one-to-one chat", Prep: func() { key, err := crypto.GenerateKey() s.Require().NoError(err) privateChat := Chat{ ID: types.EncodeHex(crypto.FromECDSAPub(&key.PublicKey)), ChatType: ChatTypeOneToOne, Active: true, } err = s.m.SaveChat(&privateChat) s.Require().NoError(err) }, AddedFilters: 1, }, { Name: "active group chat", Prep: func() { key1, err := crypto.GenerateKey() s.Require().NoError(err) key2, err := crypto.GenerateKey() s.Require().NoError(err) groupChat := Chat{ ID: "some-id", ChatType: ChatTypePrivateGroupChat, Active: true, Members: []ChatMember{ { ID: types.EncodeHex(crypto.FromECDSAPub(&key1.PublicKey)), }, { ID: types.EncodeHex(crypto.FromECDSAPub(&key2.PublicKey)), }, }, } err = s.m.SaveChat(&groupChat) s.Require().NoError(err) }, AddedFilters: 2, }, { Name: "inactive chat", Prep: func() { publicChat := Chat{ ChatType: ChatTypePublic, ID: "some-public-chat-2", Active: false, } err := s.m.SaveChat(&publicChat) s.Require().NoError(err) }, AddedFilters: 0, }, { Name: "added contact", Prep: func() { key, err := crypto.GenerateKey() s.Require().NoError(err) contact := Contact{ ID: types.EncodeHex(crypto.FromECDSAPub(&key.PublicKey)), Name: "Some Contact", SystemTags: []string{contactAdded}, } err = s.m.SaveContact(&contact) s.Require().NoError(err) }, AddedFilters: 1, }, { Name: "added and blocked contact", Prep: func() { key, err := crypto.GenerateKey() s.Require().NoError(err) contact := Contact{ ID: types.EncodeHex(crypto.FromECDSAPub(&key.PublicKey)), Name: "Some Contact", SystemTags: []string{contactAdded, contactBlocked}, } err = s.m.SaveContact(&contact) s.Require().NoError(err) }, AddedFilters: 0, }, { Name: "added by them contact", Prep: func() { key, err := crypto.GenerateKey() s.Require().NoError(err) contact := Contact{ ID: types.EncodeHex(crypto.FromECDSAPub(&key.PublicKey)), Name: "Some Contact", SystemTags: []string{contactRequestReceived}, } err = s.m.SaveContact(&contact) s.Require().NoError(err) }, AddedFilters: 0, }, } expectedFilters := 0 for _, tc := range testCases { s.Run(tc.Name, func() { tc.Prep() err := s.m.Init() s.Require().NoError(err) filters := s.m.transport.Filters() expectedFilters += tc.AddedFilters s.Equal(expectedFilters, len(filters)) }) } } func buildTestMessage(chat Chat) *Message { clock, timestamp := chat.NextClockAndTimestamp(&testTimeSource{}) message := &Message{} message.Text = "text-input-message" message.ChatId = chat.ID message.Clock = clock message.Timestamp = timestamp message.WhisperTimestamp = clock message.LocalChatID = chat.ID message.ContentType = protobuf.ChatMessage_TEXT_PLAIN switch chat.ChatType { case ChatTypePublic: message.MessageType = protobuf.ChatMessage_PUBLIC_GROUP case ChatTypeOneToOne: message.MessageType = protobuf.ChatMessage_ONE_TO_ONE case ChatTypePrivateGroupChat: message.MessageType = protobuf.ChatMessage_PRIVATE_GROUP } return message } func (s *MessengerSuite) TestMarkMessagesSeen() { chat := CreatePublicChat("test-chat", s.m.transport) chat.UnviewedMessagesCount = 2 err := s.m.SaveChat(&chat) s.Require().NoError(err) inputMessage1 := buildTestMessage(chat) inputMessage1.ID = "1" inputMessage1.Seen = false inputMessage2 := buildTestMessage(chat) inputMessage2.ID = "2" inputMessage2.Seen = false err = s.m.SaveMessages([]*Message{inputMessage1, inputMessage2}) s.Require().NoError(err) count, err := s.m.MarkMessagesSeen(chat.ID, []string{inputMessage1.ID}) s.Require().NoError(err) s.Require().Equal(uint64(1), count) chats := s.m.Chats() s.Require().Len(chats, 1) s.Require().Equal(uint(1), chats[0].UnviewedMessagesCount) } func (s *MessengerSuite) TestMarkAllRead() { chat := CreatePublicChat("test-chat", s.m.transport) chat.UnviewedMessagesCount = 2 err := s.m.SaveChat(&chat) s.Require().NoError(err) inputMessage1 := buildTestMessage(chat) inputMessage1.ID = "1" inputMessage1.Seen = false inputMessage2 := buildTestMessage(chat) inputMessage2.ID = "2" inputMessage2.Seen = false err = s.m.SaveMessages([]*Message{inputMessage1, inputMessage2}) s.Require().NoError(err) err = s.m.MarkAllRead(chat.ID) s.Require().NoError(err) chats := s.m.Chats() s.Require().Len(chats, 1) s.Require().Equal(uint(0), chats[0].UnviewedMessagesCount) } func (s *MessengerSuite) TestSendPublic() { chat := CreatePublicChat("test-chat", s.m.transport) chat.LastClockValue = uint64(100000000000000) err := s.m.SaveChat(&chat) s.NoError(err) inputMessage := buildTestMessage(chat) response, err := s.m.SendChatMessage(context.Background(), inputMessage) s.NoError(err) s.Require().Equal(1, len(response.Messages), "it returns the message") outputMessage := response.Messages[0] s.Require().Equal(uint64(100000000000001), outputMessage.Clock, "it correctly sets the clock") s.Require().Equal(uint64(100000000000001), chat.LastClockValue, "it correctly sets the last-clock-value") s.Require().NotEqual(uint64(0), chat.Timestamp, "it sets the timestamp") s.Require().Equal("0x"+hex.EncodeToString(crypto.FromECDSAPub(&s.privateKey.PublicKey)), outputMessage.From, "it sets the From field") s.Require().True(outputMessage.Seen, "it marks the message as seen") s.Require().Equal(outputMessage.OutgoingStatus, OutgoingStatusSending, "it marks the message as sending") s.Require().NotEmpty(outputMessage.ID, "it sets the ID field") s.Require().Equal(protobuf.ChatMessage_PUBLIC_GROUP, outputMessage.MessageType) savedMessages, _, err := s.m.MessageByChatID(chat.ID, "", 10) s.Require().NoError(err) s.Require().Equal(1, len(savedMessages), "it saves the message") } func (s *MessengerSuite) TestSendPrivateOneToOne() { recipientKey, err := crypto.GenerateKey() s.NoError(err) pkString := hex.EncodeToString(crypto.FromECDSAPub(&recipientKey.PublicKey)) chat := CreateOneToOneChat(pkString, &recipientKey.PublicKey, s.m.transport) inputMessage := &Message{} inputMessage.ChatId = chat.ID chat.LastClockValue = uint64(100000000000000) err = s.m.SaveChat(&chat) s.NoError(err) response, err := s.m.SendChatMessage(context.Background(), inputMessage) s.NoError(err) s.Require().Equal(1, len(response.Messages), "it returns the message") outputMessage := response.Messages[0] s.Require().Equal(uint64(100000000000001), outputMessage.Clock, "it correctly sets the clock") s.Require().Equal(uint64(100000000000001), chat.LastClockValue, "it correctly sets the last-clock-value") s.Require().NotEqual(uint64(0), chat.Timestamp, "it sets the timestamp") s.Require().Equal("0x"+hex.EncodeToString(crypto.FromECDSAPub(&s.privateKey.PublicKey)), outputMessage.From, "it sets the From field") s.Require().True(outputMessage.Seen, "it marks the message as seen") s.Require().Equal(outputMessage.OutgoingStatus, OutgoingStatusSending, "it marks the message as sending") s.Require().NotEmpty(outputMessage.ID, "it sets the ID field") s.Require().Equal(protobuf.ChatMessage_ONE_TO_ONE, outputMessage.MessageType) } func (s *MessengerSuite) TestSendPrivateGroup() { response, err := s.m.CreateGroupChatWithMembers(context.Background(), "test", []string{}) s.NoError(err) s.Require().Len(response.Chats, 1) chat := response.Chats[0] key, err := crypto.GenerateKey() s.NoError(err) members := []string{"0x" + hex.EncodeToString(crypto.FromECDSAPub(&key.PublicKey))} _, err = s.m.AddMembersToGroupChat(context.Background(), chat.ID, members) s.NoError(err) inputMessage := &Message{} inputMessage.ChatId = chat.ID chat.LastClockValue = uint64(100000000000000) err = s.m.SaveChat(chat) s.NoError(err) response, err = s.m.SendChatMessage(context.Background(), inputMessage) s.NoError(err) s.Require().Equal(1, len(response.Messages), "it returns the message") outputMessage := response.Messages[0] s.Require().Equal(uint64(100000000000001), outputMessage.Clock, "it correctly sets the clock") s.Require().Equal(uint64(100000000000001), chat.LastClockValue, "it correctly sets the last-clock-value") s.Require().NotEqual(uint64(0), chat.Timestamp, "it sets the timestamp") s.Require().Equal("0x"+hex.EncodeToString(crypto.FromECDSAPub(&s.privateKey.PublicKey)), outputMessage.From, "it sets the From field") s.Require().True(outputMessage.Seen, "it marks the message as seen") s.Require().Equal(outputMessage.OutgoingStatus, OutgoingStatusSending, "it marks the message as sending") s.Require().NotEmpty(outputMessage.ID, "it sets the ID field") s.Require().Equal(protobuf.ChatMessage_PRIVATE_GROUP, outputMessage.MessageType) } func (s *MessengerSuite) TestSendPrivateEmptyGroup() { response, err := s.m.CreateGroupChatWithMembers(context.Background(), "test", []string{}) s.NoError(err) s.Require().Len(response.Chats, 1) chat := response.Chats[0] inputMessage := &Message{} inputMessage.ChatId = chat.ID chat.LastClockValue = uint64(100000000000000) err = s.m.SaveChat(chat) s.NoError(err) response, err = s.m.SendChatMessage(context.Background(), inputMessage) s.NoError(err) s.Require().Equal(1, len(response.Messages), "it returns the message") outputMessage := response.Messages[0] s.Require().Equal(uint64(100000000000001), outputMessage.Clock, "it correctly sets the clock") s.Require().Equal(uint64(100000000000001), chat.LastClockValue, "it correctly sets the last-clock-value") s.Require().NotEqual(uint64(0), chat.Timestamp, "it sets the timestamp") s.Require().Equal("0x"+hex.EncodeToString(crypto.FromECDSAPub(&s.privateKey.PublicKey)), outputMessage.From, "it sets the From field") s.Require().True(outputMessage.Seen, "it marks the message as seen") s.Require().Equal(outputMessage.OutgoingStatus, OutgoingStatusSending, "it marks the message as sending") s.Require().NotEmpty(outputMessage.ID, "it sets the ID field") s.Require().Equal(protobuf.ChatMessage_PRIVATE_GROUP, outputMessage.MessageType) } // Make sure public messages sent by us are not func (s *MessengerSuite) TestRetrieveOwnPublic() { chat := CreatePublicChat("status", s.m.transport) err := s.m.SaveChat(&chat) s.NoError(err) // Right-to-left text text := "پيل اندر خانه يي تاريک بود عرضه را آورده بودندش هنود i\nاز براي ديدنش مردم بسي اندر آن ظلمت همي شد هر کسي" inputMessage := buildTestMessage(chat) inputMessage.ChatId = chat.ID inputMessage.Text = text response, err := s.m.SendChatMessage(context.Background(), inputMessage) s.NoError(err) s.Require().Len(response.Messages, 1) textMessage := response.Messages[0] s.Equal(textMessage.Text, text) s.NotNil(textMessage.ParsedText) s.True(textMessage.RTL) s.Equal(1, textMessage.LineCount) s.Require().Len(response.Chats, 1) actualChat := response.Chats[0] // It does not set the unviewed messages count s.Require().Equal(uint(0), actualChat.UnviewedMessagesCount) // It updates the last message clock value s.Require().Equal(textMessage.Clock, actualChat.LastClockValue) // It sets the last message s.Require().NotNil(actualChat.LastMessage) } // Retrieve their public message func (s *MessengerSuite) TestRetrieveTheirPublic() { theirMessenger := s.newMessenger(s.shh) theirChat := CreatePublicChat("status", s.m.transport) err := theirMessenger.SaveChat(&theirChat) s.Require().NoError(err) chat := CreatePublicChat("status", s.m.transport) err = s.m.SaveChat(&chat) s.Require().NoError(err) err = s.m.Join(chat) s.Require().NoError(err) inputMessage := buildTestMessage(chat) sendResponse, err := theirMessenger.SendChatMessage(context.Background(), inputMessage) s.NoError(err) sentMessage := sendResponse.Messages[0] // Wait for the message to reach its destination var response *MessengerResponse err = tt.RetryWithBackOff(func() error { var err error response, err = s.m.RetrieveAll() if err == nil && len(response.Messages) == 0 { err = errors.New("no messages") } return err }) s.Require().NoError(err) s.Require().Len(response.Messages, 1) s.Require().Len(response.Chats, 1) actualChat := response.Chats[0] // It sets the unviewed messages count s.Require().Equal(uint(1), actualChat.UnviewedMessagesCount) // It updates the last message clock value s.Require().Equal(sentMessage.Clock, actualChat.LastClockValue) // It sets the last message s.Require().NotNil(actualChat.LastMessage) } func (s *MessengerSuite) TestDeletedAtClockValue() { theirMessenger := s.newMessenger(s.shh) theirChat := CreatePublicChat("status", s.m.transport) err := theirMessenger.SaveChat(&theirChat) s.Require().NoError(err) chat := CreatePublicChat("status", s.m.transport) err = s.m.SaveChat(&chat) s.Require().NoError(err) err = s.m.Join(chat) s.Require().NoError(err) inputMessage := buildTestMessage(chat) sentResponse, err := theirMessenger.SendChatMessage(context.Background(), inputMessage) s.NoError(err) chat.DeletedAtClockValue = sentResponse.Messages[0].Clock err = s.m.SaveChat(&chat) s.Require().NoError(err) // Wait for the message to reach its destination time.Sleep(100 * time.Millisecond) response, err := s.m.RetrieveAll() s.Require().NoError(err) s.Require().Len(response.Messages, 0) } func (s *MessengerSuite) TestRetrieveBlockedContact() { theirMessenger := s.newMessenger(s.shh) theirChat := CreatePublicChat("status", s.m.transport) err := theirMessenger.SaveChat(&theirChat) s.Require().NoError(err) chat := CreatePublicChat("status", s.m.transport) err = s.m.SaveChat(&chat) s.Require().NoError(err) err = s.m.Join(chat) s.Require().NoError(err) publicKeyHex := "0x" + hex.EncodeToString(crypto.FromECDSAPub(&theirMessenger.identity.PublicKey)) blockedContact := Contact{ ID: publicKeyHex, Name: "contact-name", Photo: "contact-photo", LastUpdated: 20, SystemTags: []string{contactBlocked}, TributeToTalk: "talk", } s.Require().NoError(s.m.SaveContact(&blockedContact)) inputMessage := buildTestMessage(chat) _, err = theirMessenger.SendChatMessage(context.Background(), inputMessage) s.NoError(err) // Wait for the message to reach its destination time.Sleep(100 * time.Millisecond) response, err := s.m.RetrieveAll() s.Require().NoError(err) s.Require().Len(response.Messages, 0) } // Resend their public message, receive only once func (s *MessengerSuite) TestResendPublicMessage() { theirMessenger := s.newMessenger(s.shh) theirChat := CreatePublicChat("status", s.m.transport) err := theirMessenger.SaveChat(&theirChat) s.Require().NoError(err) chat := CreatePublicChat("status", s.m.transport) err = s.m.SaveChat(&chat) s.Require().NoError(err) err = s.m.Join(chat) s.Require().NoError(err) inputMessage := buildTestMessage(chat) sendResponse1, err := theirMessenger.SendChatMessage(context.Background(), inputMessage) s.Require().NoError(err) sentMessage := sendResponse1.Messages[0] err = theirMessenger.ReSendChatMessage(context.Background(), sentMessage.ID) s.Require().NoError(err) // Wait for the message to reach its destination var response *MessengerResponse err = tt.RetryWithBackOff(func() error { var err error response, err = s.m.RetrieveAll() if err == nil && len(response.Messages) == 0 { err = errors.New("no messages") } return err }) s.Require().NoError(err) s.Require().Len(response.Messages, 1) s.Require().Len(response.Chats, 1) actualChat := response.Chats[0] // It sets the unviewed messages count s.Require().Equal(uint(1), actualChat.UnviewedMessagesCount) // It updates the last message clock value s.Require().Equal(sentMessage.Clock, actualChat.LastClockValue) // It sets the last message s.Require().NotNil(actualChat.LastMessage) // We send the messag again err = theirMessenger.ReSendChatMessage(context.Background(), sentMessage.ID) s.Require().NoError(err) // It should not be retrieved anymore time.Sleep(100 * time.Millisecond) response, err = s.m.RetrieveAll() s.Require().NoError(err) s.Require().Len(response.Messages, 0) } // Test receiving a message on an existing private chat func (s *MessengerSuite) TestRetrieveTheirPrivateChatExisting() { theirMessenger := s.newMessenger(s.shh) theirChat := CreateOneToOneChat("XXX", &s.privateKey.PublicKey, s.m.transport) err := theirMessenger.SaveChat(&theirChat) s.Require().NoError(err) ourChat := CreateOneToOneChat("our-chat", &theirMessenger.identity.PublicKey, s.m.transport) ourChat.UnviewedMessagesCount = 1 // Make chat inactive ourChat.Active = false err = s.m.SaveChat(&ourChat) s.Require().NoError(err) inputMessage := buildTestMessage(theirChat) sendResponse, err := theirMessenger.SendChatMessage(context.Background(), inputMessage) s.NoError(err) s.Require().Len(sendResponse.Messages, 1) sentMessage := sendResponse.Messages[0] var response *MessengerResponse err = tt.RetryWithBackOff(func() error { var err error response, err = s.m.RetrieveAll() if err == nil && len(response.Messages) == 0 { err = errors.New("no messages") } return err }) s.Require().NoError(err) s.Require().Equal(len(response.Chats), 1) actualChat := response.Chats[0] // It updates the unviewed messages count s.Require().Equal(uint(2), actualChat.UnviewedMessagesCount) // It updates the last message clock value s.Require().Equal(sentMessage.Clock, actualChat.LastClockValue) // It sets the last message s.Require().NotNil(actualChat.LastMessage) s.Require().True(actualChat.Active) } // Test receiving a message on an non-existing private chat func (s *MessengerSuite) TestRetrieveTheirPrivateChatNonExisting() { theirMessenger := s.newMessenger(s.shh) chat := CreateOneToOneChat("XXX", &s.privateKey.PublicKey, s.m.transport) err := theirMessenger.SaveChat(&chat) s.NoError(err) inputMessage := buildTestMessage(chat) sendResponse, err := theirMessenger.SendChatMessage(context.Background(), inputMessage) s.NoError(err) s.Require().Len(sendResponse.Messages, 1) sentMessage := sendResponse.Messages[0] // Wait for the message to reach its destination var response *MessengerResponse err = tt.RetryWithBackOff(func() error { var err error response, err = s.m.RetrieveAll() if err == nil && len(response.Messages) == 0 { err = errors.New("no messages") } return err }) s.Require().NoError(err) s.Require().Len(response.Chats, 1) actualChat := response.Chats[0] // It updates the unviewed messages count s.Require().Equal(uint(1), actualChat.UnviewedMessagesCount) // It updates the last message clock value s.Require().Equal(sentMessage.Clock, actualChat.LastClockValue) // It sets the last message s.Require().NotNil(actualChat.LastMessage) // It sets the chat as active s.Require().True(actualChat.Active) } // Test receiving a message on an non-existing public chat func (s *MessengerSuite) TestRetrieveTheirPublicChatNonExisting() { theirMessenger := s.newMessenger(s.shh) chat := CreatePublicChat("test-chat", s.m.transport) err := theirMessenger.SaveChat(&chat) s.NoError(err) inputMessage := buildTestMessage(chat) sendResponse, err := theirMessenger.SendChatMessage(context.Background(), inputMessage) s.NoError(err) s.Require().Len(sendResponse.Messages, 1) // Wait for the message to reach its destination time.Sleep(100 * time.Millisecond) response, err := s.m.RetrieveAll() s.NoError(err) s.Require().Equal(len(response.Messages), 0) s.Require().Equal(len(response.Chats), 0) } // Test receiving a message on an non-existing private public chat func (s *MessengerSuite) TestRetrieveTheirGroupChatNonExisting() { theirMessenger := s.newMessenger(s.shh) response, err := s.m.CreateGroupChatWithMembers(context.Background(), "test", []string{}) s.NoError(err) s.Require().Len(response.Chats, 1) chat := response.Chats[0] err = theirMessenger.SaveChat(chat) s.NoError(err) inputMessage := buildTestMessage(*chat) sendResponse, err := theirMessenger.SendChatMessage(context.Background(), inputMessage) s.NoError(err) s.Require().Len(sendResponse.Messages, 1) // Retrieve their messages so that the chat is created err = tt.RetryWithBackOff(func() error { var err error response, err = s.m.RetrieveAll() if err == nil && len(response.Chats) == 1 { err = errors.New("chat membership update not received") } return err }) s.Require().NoError(err) // The message is discarded s.Require().Equal(0, len(response.Messages)) s.Require().Equal(0, len(response.Chats)) } // Test receiving a message on an existing private group chat func (s *MessengerSuite) TestRetrieveTheirPrivateGroupChat() { var response *MessengerResponse theirMessenger := s.newMessenger(s.shh) response, err := s.m.CreateGroupChatWithMembers(context.Background(), "id", []string{}) s.NoError(err) s.Require().Len(response.Chats, 1) ourChat := response.Chats[0] err = s.m.SaveChat(ourChat) s.NoError(err) members := []string{"0x" + hex.EncodeToString(crypto.FromECDSAPub(&theirMessenger.identity.PublicKey))} _, err = s.m.AddMembersToGroupChat(context.Background(), ourChat.ID, members) s.NoError(err) // Retrieve their messages so that the chat is created err = tt.RetryWithBackOff(func() error { var err error response, err = theirMessenger.RetrieveAll() if err == nil && len(response.Chats) == 0 { err = errors.New("chat invitation not received") } return err }) s.Require().NoError(err) _, err = theirMessenger.ConfirmJoiningGroup(context.Background(), ourChat.ID) s.NoError(err) err = tt.RetryWithBackOff(func() error { var err error response, err = s.m.RetrieveAll() if err == nil && len(response.Chats) == 0 { err = errors.New("no joining group event received") } return err }) s.Require().NoError(err) inputMessage := buildTestMessage(*ourChat) sendResponse, err := theirMessenger.SendChatMessage(context.Background(), inputMessage) s.NoError(err) s.Require().Len(sendResponse.Messages, 1) sentMessage := sendResponse.Messages[0] err = tt.RetryWithBackOff(func() error { var err error response, err = s.m.RetrieveAll() if err == nil && len(response.Messages) == 0 { err = errors.New("no messages") } return err }) s.Require().NoError(err) s.Require().Len(response.Chats, 1) actualChat := response.Chats[0] // It updates the unviewed messages count s.Require().Equal(uint(1), actualChat.UnviewedMessagesCount) // It updates the last message clock value s.Require().Equal(sentMessage.Clock, actualChat.LastClockValue) // It sets the last message s.Require().NotNil(actualChat.LastMessage) } // Test receiving a message on an existing private group chat, if messages // are not wrapped this will fail as they'll likely come out of order // NOTE: Disabling this as too flaky /* func (s *MessengerSuite) testRetrieveTheirPrivateGroupWrappedMessageChat() { var response *MessengerResponse theirMessenger := s.newMessenger(s.shh) response, err := s.m.CreateGroupChatWithMembers(context.Background(), "id", []string{}) s.NoError(err) s.Require().Len(response.Chats, 1) ourChat := response.Chats[0] err = s.m.SaveChat(ourChat) s.NoError(err) members := []string{"0x" + hex.EncodeToString(crypto.FromECDSAPub(&theirMessenger.identity.PublicKey))} _, err = s.m.AddMembersToGroupChat(context.Background(), ourChat.ID, members) s.NoError(err) // Retrieve their messages so that the chat is created err = tt.RetryWithBackOff(func() error { var err error response, err = theirMessenger.RetrieveAll() if err == nil && len(response.Chats) == 0 { err = errors.New("chat invitation not received") } return err }) s.Require().NoError(err) _, err = theirMessenger.ConfirmJoiningGroup(context.Background(), ourChat.ID) s.NoError(err) inputMessage := buildTestMessage(*ourChat) sendResponse, err := theirMessenger.SendChatMessage(context.Background(), inputMessage) s.NoError(err) s.Require().Len(sendResponse.Messages, 1) sentMessage := sendResponse.Messages[0] err = tt.RetryWithBackOff(func() error { var err error response, err = s.m.RetrieveAll() if err == nil && len(response.Chats) == 0 { err = errors.New("no chats") } return err }) s.Require().NoError(err) s.Require().Len(response.Chats, 1) actualChat := response.Chats[0] // It updates the unviewed messages count s.Require().Equal(uint(1), actualChat.UnviewedMessagesCount) // It updates the last message clock value s.Require().Equal(sentMessage.Clock, actualChat.LastClockValue) // It sets the last message s.Require().NotNil(actualChat.LastMessage) } */ func (s *MessengerSuite) TestChatPersistencePublic() { chat := Chat{ ID: "chat-name", Name: "chat-name", Color: "#fffff", Active: true, ChatType: ChatTypePublic, Timestamp: 10, LastClockValue: 20, DeletedAtClockValue: 30, UnviewedMessagesCount: 40, LastMessage: []byte("test"), } s.Require().NoError(s.m.SaveChat(&chat)) savedChats := s.m.Chats() s.Require().Equal(1, len(savedChats)) actualChat := savedChats[0] expectedChat := &chat s.Require().Equal(actualChat, expectedChat) } func (s *MessengerSuite) TestDeleteChat() { chatID := "chatid" chat := Chat{ ID: chatID, Name: "chat-name", Color: "#fffff", Active: true, ChatType: ChatTypePublic, Timestamp: 10, LastClockValue: 20, DeletedAtClockValue: 30, UnviewedMessagesCount: 40, LastMessage: []byte("test"), } s.Require().NoError(s.m.SaveChat(&chat)) savedChats := s.m.Chats() s.Require().Equal(1, len(savedChats)) s.Require().NoError(s.m.DeleteChat(chatID)) savedChats = s.m.Chats() s.Require().Equal(0, len(savedChats)) } func (s *MessengerSuite) TestChatPersistenceUpdate() { chat := Chat{ ID: "chat-name", Name: "chat-name", Color: "#fffff", Active: true, ChatType: ChatTypePublic, Timestamp: 10, LastClockValue: 20, DeletedAtClockValue: 30, UnviewedMessagesCount: 40, LastMessage: []byte("test"), } s.Require().NoError(s.m.SaveChat(&chat)) savedChats := s.m.Chats() s.Require().Equal(1, len(savedChats)) actualChat := savedChats[0] expectedChat := &chat s.Require().Equal(expectedChat, actualChat) chat.Name = "updated-name-1" s.Require().NoError(s.m.SaveChat(&chat)) updatedChats := s.m.Chats() s.Require().Equal(1, len(updatedChats)) actualUpdatedChat := updatedChats[0] expectedUpdatedChat := &chat s.Require().Equal(expectedUpdatedChat, actualUpdatedChat) } func (s *MessengerSuite) TestChatPersistenceOneToOne() { chat := Chat{ ID: testPK, Name: testPK, Color: "#fffff", Active: true, ChatType: ChatTypeOneToOne, Timestamp: 10, LastClockValue: 20, DeletedAtClockValue: 30, UnviewedMessagesCount: 40, LastMessage: []byte("test"), } publicKeyBytes, err := hex.DecodeString(testPK[2:]) s.Require().NoError(err) pk, err := crypto.UnmarshalPubkey(publicKeyBytes) s.Require().NoError(err) s.Require().NoError(s.m.SaveChat(&chat)) savedChats := s.m.Chats() s.Require().Equal(1, len(savedChats)) actualChat := savedChats[0] expectedChat := &chat actualPk, err := actualChat.PublicKey() s.Require().NoError(err) s.Require().Equal(pk, actualPk) s.Require().Equal(expectedChat, actualChat) } func (s *MessengerSuite) TestChatPersistencePrivateGroupChat() { member1Key, err := crypto.GenerateKey() s.Require().NoError(err) member1ID := types.EncodeHex(crypto.FromECDSAPub(&member1Key.PublicKey)) member2Key, err := crypto.GenerateKey() s.Require().NoError(err) member2ID := types.EncodeHex(crypto.FromECDSAPub(&member2Key.PublicKey)) member3Key, err := crypto.GenerateKey() s.Require().NoError(err) member3ID := types.EncodeHex(crypto.FromECDSAPub(&member3Key.PublicKey)) chat := Chat{ ID: "chat-id", Name: "chat-id", Color: "#fffff", Active: true, ChatType: ChatTypePrivateGroupChat, Timestamp: 10, Members: []ChatMember{ { ID: member1ID, Admin: false, Joined: true, }, { ID: member2ID, Admin: true, Joined: false, }, { ID: member3ID, Admin: true, Joined: true, }, }, MembershipUpdates: []v1protocol.MembershipUpdateEvent{ { Type: protobuf.MembershipUpdateEvent_CHAT_CREATED, Name: "name-1", ClockValue: 1, Signature: []byte("signature-1"), From: "from-1", Members: []string{"member-1", "member-2"}, }, { Type: protobuf.MembershipUpdateEvent_MEMBERS_ADDED, Name: "name-2", ClockValue: 2, Signature: []byte("signature-2"), From: "from-2", Members: []string{"member-2", "member-3"}, }, }, LastClockValue: 20, DeletedAtClockValue: 30, UnviewedMessagesCount: 40, LastMessage: []byte("test"), } s.Require().NoError(s.m.SaveChat(&chat)) savedChats := s.m.Chats() s.Require().Equal(1, len(savedChats)) actualChat := savedChats[0] expectedChat := &chat s.Require().Equal(expectedChat, actualChat) } func (s *MessengerSuite) TestBlockContact() { contact := Contact{ ID: testPK, Name: "contact-name", Photo: "contact-photo", LastUpdated: 20, SystemTags: []string{"1", "2"}, DeviceInfo: []ContactDeviceInfo{ { InstallationID: "1", Timestamp: 2, FCMToken: "token", }, { InstallationID: "2", Timestamp: 3, FCMToken: "token-2", }, }, TributeToTalk: "talk", } chat1 := Chat{ ID: contact.ID, Name: "chat-name", Color: "#fffff", Active: true, ChatType: ChatTypeOneToOne, Timestamp: 1, LastClockValue: 20, DeletedAtClockValue: 30, UnviewedMessagesCount: 40, } chat2 := Chat{ ID: "chat-2", Name: "chat-name", Color: "#fffff", Active: true, ChatType: ChatTypePublic, Timestamp: 2, LastClockValue: 20, DeletedAtClockValue: 30, UnviewedMessagesCount: 40, } chat3 := Chat{ ID: "chat-3", Name: "chat-name", Color: "#fffff", Active: true, ChatType: ChatTypePublic, Timestamp: 3, LastClockValue: 20, DeletedAtClockValue: 30, UnviewedMessagesCount: 40, } s.Require().NoError(s.m.SaveChat(&chat1)) s.Require().NoError(s.m.SaveChat(&chat2)) s.Require().NoError(s.m.SaveChat(&chat3)) s.Require().NoError(s.m.SaveContact(&contact)) contact.Name = "blocked" messages := []*Message{ { ID: "test-1", LocalChatID: chat2.ID, ChatMessage: protobuf.ChatMessage{ ContentType: 1, Text: "test-1", Clock: 1, }, From: contact.ID, }, { ID: "test-2", LocalChatID: chat2.ID, ChatMessage: protobuf.ChatMessage{ ContentType: 2, Text: "test-2", Clock: 2, }, From: contact.ID, }, { ID: "test-3", LocalChatID: chat2.ID, ChatMessage: protobuf.ChatMessage{ ContentType: 3, Text: "test-3", Clock: 3, }, Seen: false, From: "test", }, { ID: "test-4", LocalChatID: chat2.ID, ChatMessage: protobuf.ChatMessage{ ContentType: 4, Text: "test-4", Clock: 4, }, Seen: false, From: "test", }, { ID: "test-5", LocalChatID: chat2.ID, ChatMessage: protobuf.ChatMessage{ ContentType: 5, Text: "test-5", Clock: 5, }, Seen: true, From: "test", }, { ID: "test-6", LocalChatID: chat3.ID, ChatMessage: protobuf.ChatMessage{ ContentType: 6, Text: "test-6", Clock: 6, }, Seen: false, From: contact.ID, }, { ID: "test-7", LocalChatID: chat3.ID, ChatMessage: protobuf.ChatMessage{ ContentType: 7, Text: "test-7", Clock: 7, }, Seen: false, From: "test", }, } err := s.m.SaveMessages(messages) s.Require().NoError(err) response, err := s.m.BlockContact(&contact) s.Require().NoError(err) // The new unviewed count is updated s.Require().Equal(uint(1), response[0].UnviewedMessagesCount) s.Require().Equal(uint(2), response[1].UnviewedMessagesCount) // The new message content is updated decodedMessage := &Message{} s.Require().NotNil(response[0].LastMessage) s.Require().NoError(json.Unmarshal(response[0].LastMessage, decodedMessage)) s.Require().Equal("test-7", decodedMessage.ID) decodedMessage = &Message{} s.Require().NotNil(response[1].LastMessage) s.Require().NoError(json.Unmarshal(response[1].LastMessage, decodedMessage)) s.Require().Equal("test-5", decodedMessage.ID) // The contact is updated savedContacts := s.m.Contacts() s.Require().Equal(1, len(savedContacts)) s.Require().Equal("blocked", savedContacts[0].Name) // The chat is deleted actualChats := s.m.Chats() s.Require().Equal(2, len(actualChats)) // The messages have been deleted chat2Messages, _, err := s.m.MessageByChatID(chat2.ID, "", 20) s.Require().NoError(err) s.Require().Equal(3, len(chat2Messages)) chat3Messages, _, err := s.m.MessageByChatID(chat3.ID, "", 20) s.Require().NoError(err) s.Require().Equal(1, len(chat3Messages)) } func (s *MessengerSuite) TestContactPersistence() { contact := Contact{ ID: testPK, Name: "contact-name", Photo: "contact-photo", LastUpdated: 20, SystemTags: []string{"1", "2"}, DeviceInfo: []ContactDeviceInfo{ { InstallationID: "1", Timestamp: 2, FCMToken: "token", }, { InstallationID: "2", Timestamp: 3, FCMToken: "token-2", }, }, TributeToTalk: "talk", } s.Require().NoError(s.m.SaveContact(&contact)) savedContacts := s.m.Contacts() s.Require().Equal(1, len(savedContacts)) actualContact := savedContacts[0] expectedContact := &contact expectedContact.Alias = testAlias expectedContact.Identicon = testIdenticon s.Require().Equal(expectedContact, actualContact) } func (s *MessengerSuite) TestContactPersistenceUpdate() { contactID := testPK contact := Contact{ ID: contactID, Name: "contact-name", Photo: "contact-photo", LastUpdated: 20, SystemTags: []string{"1", "2"}, DeviceInfo: []ContactDeviceInfo{ { InstallationID: "1", Timestamp: 2, FCMToken: "token", }, { InstallationID: "2", Timestamp: 3, FCMToken: "token-2", }, }, TributeToTalk: "talk", } s.Require().NoError(s.m.SaveContact(&contact)) savedContacts := s.m.Contacts() s.Require().Equal(1, len(savedContacts)) actualContact := savedContacts[0] expectedContact := &contact expectedContact.Alias = testAlias expectedContact.Identicon = testIdenticon s.Require().Equal(expectedContact, actualContact) contact.Name = "updated-name-2" s.Require().NoError(s.m.SaveContact(&contact)) updatedContact := s.m.Contacts() s.Require().Equal(1, len(updatedContact)) actualUpdatedContact := updatedContact[0] expectedUpdatedContact := &contact s.Require().Equal(expectedUpdatedContact, actualUpdatedContact) } func (s *MessengerSuite) TestSharedSecretHandler() { _, err := s.m.handleSharedSecrets(nil) s.NoError(err) } func (s *MessengerSuite) TestCreateGroupChatWithMembers() { members := []string{testPK} response, err := s.m.CreateGroupChatWithMembers(context.Background(), "test", members) s.NoError(err) s.Require().Len(response.Chats, 1) chat := response.Chats[0] s.Require().Equal("test", chat.Name) publicKeyHex := "0x" + hex.EncodeToString(crypto.FromECDSAPub(&s.m.identity.PublicKey)) s.Require().Contains(chat.ID, publicKeyHex) s.EqualValues([]string{publicKeyHex}, []string{chat.Members[0].ID}) s.Equal(members[0], chat.Members[1].ID) } func (s *MessengerSuite) TestAddMembersToChat() { response, err := s.m.CreateGroupChatWithMembers(context.Background(), "test", []string{}) s.Require().NoError(err) s.Require().Len(response.Chats, 1) chat := response.Chats[0] key, err := crypto.GenerateKey() s.Require().NoError(err) members := []string{"0x" + hex.EncodeToString(crypto.FromECDSAPub(&key.PublicKey))} response, err = s.m.AddMembersToGroupChat(context.Background(), chat.ID, members) s.Require().NoError(err) s.Require().Len(response.Chats, 1) s.Require().Len(response.Messages, 1) chat = response.Chats[0] publicKeyHex := "0x" + hex.EncodeToString(crypto.FromECDSAPub(&s.m.identity.PublicKey)) keyHex := "0x" + hex.EncodeToString(crypto.FromECDSAPub(&key.PublicKey)) s.EqualValues([]string{publicKeyHex, keyHex}, []string{chat.Members[0].ID, chat.Members[1].ID}) } func (s *MessengerSuite) TestDeclineRequestAddressForTransaction() { value := testValue contract := testContract theirMessenger := s.newMessenger(s.shh) theirPkString := types.EncodeHex(crypto.FromECDSAPub(&theirMessenger.identity.PublicKey)) chat := CreateOneToOneChat(theirPkString, &theirMessenger.identity.PublicKey, s.m.transport) err := s.m.SaveChat(&chat) s.Require().NoError(err) myAddress := crypto.PubkeyToAddress(s.m.identity.PublicKey) response, err := s.m.RequestAddressForTransaction(context.Background(), theirPkString, myAddress.Hex(), value, contract) s.Require().NoError(err) s.Require().NotNil(response) s.Require().Len(response.Chats, 1) s.Require().Len(response.Messages, 1) senderMessage := response.Messages[0] s.Require().Equal(protobuf.ChatMessage_TRANSACTION_COMMAND, senderMessage.ContentType) initialCommandID := senderMessage.ID s.Require().Equal("Request address for transaction", senderMessage.Text) s.Require().NotNil(senderMessage.CommandParameters) s.Require().Equal(value, senderMessage.CommandParameters.Value) s.Require().Equal(contract, senderMessage.CommandParameters.Contract) s.Require().Equal(initialCommandID, senderMessage.CommandParameters.ID) s.Require().Equal(CommandStateRequestAddressForTransaction, senderMessage.CommandParameters.CommandState) // Wait for the message to reach its destination err = tt.RetryWithBackOff(func() error { var err error response, err = theirMessenger.RetrieveAll() if err == nil && len(response.Messages) == 0 { err = errors.New("no messages") } return err }) s.Require().NoError(err) s.Require().NotNil(response) s.Require().Len(response.Chats, 1) s.Require().Len(response.Messages, 1) receiverMessage := response.Messages[0] s.Require().Equal(protobuf.ChatMessage_TRANSACTION_COMMAND, receiverMessage.ContentType) s.Require().Equal("Request address for transaction", receiverMessage.Text) s.Require().NotNil(receiverMessage.CommandParameters) s.Require().Equal(value, receiverMessage.CommandParameters.Value) s.Require().Equal(contract, receiverMessage.CommandParameters.Contract) s.Require().Equal(initialCommandID, receiverMessage.CommandParameters.ID) s.Require().Equal(CommandStateRequestAddressForTransaction, receiverMessage.CommandParameters.CommandState) // We decline the request response, err = theirMessenger.DeclineRequestAddressForTransaction(context.Background(), receiverMessage.ID) s.Require().NoError(err) s.Require().Len(response.Chats, 1) s.Require().Len(response.Messages, 1) senderMessage = response.Messages[0] s.Require().Equal(protobuf.ChatMessage_TRANSACTION_COMMAND, senderMessage.ContentType) s.Require().Equal("Request address for transaction declined", senderMessage.Text) s.Require().NotNil(senderMessage.CommandParameters) s.Require().Equal(value, senderMessage.CommandParameters.Value) s.Require().Equal(contract, senderMessage.CommandParameters.Contract) s.Require().Equal(CommandStateRequestAddressForTransactionDeclined, senderMessage.CommandParameters.CommandState) s.Require().Equal(initialCommandID, senderMessage.CommandParameters.ID) s.Require().Equal(receiverMessage.ID, senderMessage.Replace) // Wait for the message to reach its destination err = tt.RetryWithBackOff(func() error { var err error response, err = s.m.RetrieveAll() if err == nil && len(response.Messages) == 0 { err = errors.New("no messages") } return err }) s.Require().NoError(err) s.Require().Len(response.Chats, 1) s.Require().Len(response.Messages, 1) receiverMessage = response.Messages[0] s.Require().Equal(protobuf.ChatMessage_TRANSACTION_COMMAND, receiverMessage.ContentType) s.Require().Equal("Request address for transaction declined", receiverMessage.Text) s.Require().NotNil(receiverMessage.CommandParameters) s.Require().Equal(value, receiverMessage.CommandParameters.Value) s.Require().Equal(contract, receiverMessage.CommandParameters.Contract) s.Require().Equal(CommandStateRequestAddressForTransactionDeclined, receiverMessage.CommandParameters.CommandState) s.Require().Equal(initialCommandID, receiverMessage.CommandParameters.ID) s.Require().Equal(initialCommandID, receiverMessage.Replace) } func (s *MessengerSuite) TestSendEthTransaction() { value := testValue contract := testContract theirMessenger := s.newMessenger(s.shh) theirPkString := types.EncodeHex(crypto.FromECDSAPub(&theirMessenger.identity.PublicKey)) receiverAddress := crypto.PubkeyToAddress(theirMessenger.identity.PublicKey) receiverAddressString := strings.ToLower(receiverAddress.Hex()) chat := CreateOneToOneChat(theirPkString, &theirMessenger.identity.PublicKey, s.m.transport) err := s.m.SaveChat(&chat) s.Require().NoError(err) transactionHash := testTransactionHash signature, err := buildSignature(s.m.identity, &s.m.identity.PublicKey, transactionHash) s.Require().NoError(err) response, err := s.m.SendTransaction(context.Background(), theirPkString, value, contract, transactionHash, signature) s.Require().NoError(err) s.Require().NotNil(response) s.Require().Len(response.Chats, 1) s.Require().Len(response.Messages, 1) senderMessage := response.Messages[0] s.Require().Equal(protobuf.ChatMessage_TRANSACTION_COMMAND, senderMessage.ContentType) s.Require().Equal("Transaction sent", senderMessage.Text) s.Require().NotNil(senderMessage.CommandParameters) s.Require().Equal(transactionHash, senderMessage.CommandParameters.TransactionHash) s.Require().Equal(contract, senderMessage.CommandParameters.Contract) s.Require().Equal(value, senderMessage.CommandParameters.Value) s.Require().Equal(signature, senderMessage.CommandParameters.Signature) s.Require().Equal(CommandStateTransactionSent, senderMessage.CommandParameters.CommandState) s.Require().NotEmpty(senderMessage.ID) s.Require().Equal("", senderMessage.Replace) var transactions []*TransactionToValidate // Wait for the message to reach its destination err = tt.RetryWithBackOff(func() error { var err error _, err = theirMessenger.RetrieveAll() if err != nil { return err } transactions, err = theirMessenger.persistence.TransactionsToValidate() if err == nil && len(transactions) == 0 { err = errors.New("no transactions") } return err }) s.Require().NoError(err) actualTransaction := transactions[0] s.Require().Equal(&s.m.identity.PublicKey, actualTransaction.From) s.Require().Equal(transactionHash, actualTransaction.TransactionHash) s.Require().True(actualTransaction.Validate) senderAddress := crypto.PubkeyToAddress(s.m.identity.PublicKey) client := MockEthClient{} valueBig, ok := big.NewInt(0).SetString(value, 10) s.Require().True(ok) client.messages = make(map[string]MockTransaction) client.messages[transactionHash] = MockTransaction{ Status: coretypes.TransactionStatusSuccess, Message: coretypes.NewMessage( senderAddress, &receiverAddress, 1, valueBig, 0, nil, nil, false, ), } theirMessenger.verifyTransactionClient = client response, err = theirMessenger.ValidateTransactions(context.Background(), []types.Address{receiverAddress}) s.Require().NoError(err) s.Require().NotNil(response) s.Require().Len(response.Chats, 1) s.Require().Len(response.Messages, 1) receiverMessage := response.Messages[0] s.Require().Equal(protobuf.ChatMessage_TRANSACTION_COMMAND, receiverMessage.ContentType) s.Require().Equal("Transaction received", receiverMessage.Text) s.Require().NotNil(receiverMessage.CommandParameters) s.Require().Equal(value, receiverMessage.CommandParameters.Value) s.Require().Equal(strings.ToLower(receiverAddress.Hex()), receiverMessage.CommandParameters.Address) s.Require().Equal(transactionHash, receiverMessage.CommandParameters.TransactionHash) s.Require().Equal(receiverAddressString, receiverMessage.CommandParameters.Address) s.Require().Equal("", receiverMessage.CommandParameters.ID) s.Require().Equal(CommandStateTransactionSent, receiverMessage.CommandParameters.CommandState) s.Require().Equal(senderMessage.ID, receiverMessage.ID) s.Require().Equal("", receiverMessage.Replace) } func (s *MessengerSuite) TestSendTokenTransaction() { value := testValue contract := testContract theirMessenger := s.newMessenger(s.shh) theirPkString := types.EncodeHex(crypto.FromECDSAPub(&theirMessenger.identity.PublicKey)) receiverAddress := crypto.PubkeyToAddress(theirMessenger.identity.PublicKey) receiverAddressString := strings.ToLower(receiverAddress.Hex()) chat := CreateOneToOneChat(theirPkString, &theirMessenger.identity.PublicKey, s.m.transport) err := s.m.SaveChat(&chat) s.Require().NoError(err) transactionHash := testTransactionHash signature, err := buildSignature(s.m.identity, &s.m.identity.PublicKey, transactionHash) s.Require().NoError(err) response, err := s.m.SendTransaction(context.Background(), theirPkString, value, contract, transactionHash, signature) s.Require().NoError(err) s.Require().NotNil(response) s.Require().Len(response.Chats, 1) s.Require().Len(response.Messages, 1) senderMessage := response.Messages[0] s.Require().Equal(protobuf.ChatMessage_TRANSACTION_COMMAND, senderMessage.ContentType) s.Require().Equal("Transaction sent", senderMessage.Text) s.Require().NotNil(senderMessage.CommandParameters) s.Require().Equal(transactionHash, senderMessage.CommandParameters.TransactionHash) s.Require().Equal(value, senderMessage.CommandParameters.Value) s.Require().Equal(contract, senderMessage.CommandParameters.Contract) s.Require().Equal(signature, senderMessage.CommandParameters.Signature) s.Require().Equal(CommandStateTransactionSent, senderMessage.CommandParameters.CommandState) s.Require().NotEmpty(senderMessage.ID) var transactions []*TransactionToValidate // Wait for the message to reach its destination err = tt.RetryWithBackOff(func() error { var err error _, err = theirMessenger.RetrieveAll() if err != nil { return err } transactions, err = theirMessenger.persistence.TransactionsToValidate() if err == nil && len(transactions) == 0 { err = errors.New("no transactions") } return err }) s.Require().NoError(err) actualTransaction := transactions[0] s.Require().Equal(&s.m.identity.PublicKey, actualTransaction.From) s.Require().Equal(transactionHash, actualTransaction.TransactionHash) s.Require().True(actualTransaction.Validate) senderAddress := crypto.PubkeyToAddress(s.m.identity.PublicKey) contractAddress := types.HexToAddress(contract) client := MockEthClient{} valueBig, ok := big.NewInt(0).SetString(value, 10) s.Require().True(ok) client.messages = make(map[string]MockTransaction) client.messages[transactionHash] = MockTransaction{ Status: coretypes.TransactionStatusSuccess, Message: coretypes.NewMessage( senderAddress, &contractAddress, 1, nil, 0, nil, buildData(transferFunction, receiverAddress, valueBig), false, ), } theirMessenger.verifyTransactionClient = client response, err = theirMessenger.ValidateTransactions(context.Background(), []types.Address{receiverAddress}) s.Require().NoError(err) s.Require().NotNil(response) s.Require().Len(response.Chats, 1) s.Require().Len(response.Messages, 1) receiverMessage := response.Messages[0] s.Require().Equal(protobuf.ChatMessage_TRANSACTION_COMMAND, receiverMessage.ContentType) s.Require().Equal("Transaction received", receiverMessage.Text) s.Require().NotNil(receiverMessage.CommandParameters) s.Require().Equal(value, receiverMessage.CommandParameters.Value) s.Require().Equal(contract, receiverMessage.CommandParameters.Contract) s.Require().Equal(transactionHash, receiverMessage.CommandParameters.TransactionHash) s.Require().Equal(receiverAddressString, receiverMessage.CommandParameters.Address) s.Require().Equal("", receiverMessage.CommandParameters.ID) s.Require().Equal(CommandStateTransactionSent, receiverMessage.CommandParameters.CommandState) s.Require().Equal(senderMessage.ID, receiverMessage.ID) s.Require().Equal(senderMessage.Replace, senderMessage.Replace) } func (s *MessengerSuite) TestAcceptRequestAddressForTransaction() { value := testValue contract := testContract theirMessenger := s.newMessenger(s.shh) theirPkString := types.EncodeHex(crypto.FromECDSAPub(&theirMessenger.identity.PublicKey)) myAddress := crypto.PubkeyToAddress(s.m.identity.PublicKey) chat := CreateOneToOneChat(theirPkString, &theirMessenger.identity.PublicKey, s.m.transport) err := s.m.SaveChat(&chat) s.Require().NoError(err) response, err := s.m.RequestAddressForTransaction(context.Background(), theirPkString, myAddress.Hex(), value, contract) s.Require().NoError(err) s.Require().NotNil(response) s.Require().Len(response.Chats, 1) s.Require().Len(response.Messages, 1) senderMessage := response.Messages[0] s.Require().Equal(protobuf.ChatMessage_TRANSACTION_COMMAND, senderMessage.ContentType) initialCommandID := senderMessage.ID s.Require().Equal("Request address for transaction", senderMessage.Text) s.Require().NotNil(senderMessage.CommandParameters) s.Require().Equal(value, senderMessage.CommandParameters.Value) s.Require().Equal(contract, senderMessage.CommandParameters.Contract) s.Require().Equal(initialCommandID, senderMessage.CommandParameters.ID) s.Require().Equal(CommandStateRequestAddressForTransaction, senderMessage.CommandParameters.CommandState) // Wait for the message to reach its destination err = tt.RetryWithBackOff(func() error { var err error response, err = theirMessenger.RetrieveAll() if err == nil && len(response.Messages) == 0 { err = errors.New("no messages") } return err }) s.Require().NoError(err) s.Require().NotNil(response) s.Require().Len(response.Chats, 1) s.Require().Len(response.Messages, 1) receiverMessage := response.Messages[0] s.Require().Equal(protobuf.ChatMessage_TRANSACTION_COMMAND, receiverMessage.ContentType) s.Require().Equal("Request address for transaction", receiverMessage.Text) s.Require().NotNil(receiverMessage.CommandParameters) s.Require().Equal(value, receiverMessage.CommandParameters.Value) s.Require().Equal(contract, receiverMessage.CommandParameters.Contract) s.Require().Equal(initialCommandID, receiverMessage.CommandParameters.ID) s.Require().Equal(CommandStateRequestAddressForTransaction, receiverMessage.CommandParameters.CommandState) // We accept the request response, err = theirMessenger.AcceptRequestAddressForTransaction(context.Background(), receiverMessage.ID, "some-address") s.Require().NoError(err) s.Require().Len(response.Chats, 1) s.Require().Len(response.Messages, 1) senderMessage = response.Messages[0] s.Require().Equal(protobuf.ChatMessage_TRANSACTION_COMMAND, senderMessage.ContentType) s.Require().Equal("Request address for transaction accepted", senderMessage.Text) s.Require().NotNil(senderMessage.CommandParameters) s.Require().Equal(value, senderMessage.CommandParameters.Value) s.Require().Equal(contract, senderMessage.CommandParameters.Contract) s.Require().Equal(CommandStateRequestAddressForTransactionAccepted, senderMessage.CommandParameters.CommandState) s.Require().Equal(initialCommandID, senderMessage.CommandParameters.ID) s.Require().Equal("some-address", senderMessage.CommandParameters.Address) s.Require().Equal(receiverMessage.ID, senderMessage.Replace) // Wait for the message to reach its destination err = tt.RetryWithBackOff(func() error { var err error response, err = s.m.RetrieveAll() if err == nil && len(response.Messages) == 0 { err = errors.New("no messages") } return err }) s.Require().NoError(err) s.Require().Len(response.Chats, 1) s.Require().Len(response.Messages, 1) receiverMessage = response.Messages[0] s.Require().Equal(protobuf.ChatMessage_TRANSACTION_COMMAND, receiverMessage.ContentType) s.Require().Equal("Request address for transaction accepted", receiverMessage.Text) s.Require().NotNil(receiverMessage.CommandParameters) s.Require().Equal(value, receiverMessage.CommandParameters.Value) s.Require().Equal(contract, receiverMessage.CommandParameters.Contract) s.Require().Equal(CommandStateRequestAddressForTransactionAccepted, receiverMessage.CommandParameters.CommandState) s.Require().Equal(initialCommandID, receiverMessage.CommandParameters.ID) s.Require().Equal("some-address", receiverMessage.CommandParameters.Address) s.Require().Equal(initialCommandID, receiverMessage.Replace) } func (s *MessengerSuite) TestDeclineRequestTransaction() { value := testValue contract := testContract receiverAddress := crypto.PubkeyToAddress(s.m.identity.PublicKey) receiverAddressString := strings.ToLower(receiverAddress.Hex()) theirMessenger := s.newMessenger(s.shh) theirPkString := types.EncodeHex(crypto.FromECDSAPub(&theirMessenger.identity.PublicKey)) chat := CreateOneToOneChat(theirPkString, &theirMessenger.identity.PublicKey, s.m.transport) err := s.m.SaveChat(&chat) s.Require().NoError(err) response, err := s.m.RequestTransaction(context.Background(), theirPkString, value, contract, receiverAddressString) s.Require().NoError(err) s.Require().NotNil(response) s.Require().Len(response.Chats, 1) s.Require().Len(response.Messages, 1) senderMessage := response.Messages[0] s.Require().Equal(protobuf.ChatMessage_TRANSACTION_COMMAND, senderMessage.ContentType) initialCommandID := senderMessage.ID s.Require().Equal("Request transaction", senderMessage.Text) s.Require().NotNil(senderMessage.CommandParameters) s.Require().Equal(value, senderMessage.CommandParameters.Value) s.Require().Equal(contract, senderMessage.CommandParameters.Contract) s.Require().Equal(receiverAddressString, senderMessage.CommandParameters.Address) s.Require().Equal(initialCommandID, senderMessage.CommandParameters.ID) s.Require().Equal(CommandStateRequestTransaction, senderMessage.CommandParameters.CommandState) // Wait for the message to reach its destination err = tt.RetryWithBackOff(func() error { var err error response, err = theirMessenger.RetrieveAll() if err == nil && len(response.Messages) == 0 { err = errors.New("no messages") } return err }) s.Require().NoError(err) s.Require().NotNil(response) s.Require().Len(response.Chats, 1) s.Require().Len(response.Messages, 1) receiverMessage := response.Messages[0] s.Require().Equal(protobuf.ChatMessage_TRANSACTION_COMMAND, receiverMessage.ContentType) s.Require().Equal("Request transaction", receiverMessage.Text) s.Require().NotNil(receiverMessage.CommandParameters) s.Require().Equal(value, receiverMessage.CommandParameters.Value) s.Require().Equal(contract, receiverMessage.CommandParameters.Contract) s.Require().Equal(receiverAddressString, receiverMessage.CommandParameters.Address) s.Require().Equal(initialCommandID, receiverMessage.CommandParameters.ID) s.Require().Equal(CommandStateRequestTransaction, receiverMessage.CommandParameters.CommandState) response, err = theirMessenger.DeclineRequestTransaction(context.Background(), initialCommandID) s.Require().NoError(err) s.Require().NotNil(response) s.Require().Len(response.Chats, 1) s.Require().Len(response.Messages, 1) senderMessage = response.Messages[0] s.Require().Equal(protobuf.ChatMessage_TRANSACTION_COMMAND, senderMessage.ContentType) s.Require().Equal("Transaction request declined", senderMessage.Text) s.Require().Equal(initialCommandID, senderMessage.CommandParameters.ID) s.Require().Equal(receiverMessage.ID, senderMessage.Replace) s.Require().Equal(CommandStateRequestTransactionDeclined, senderMessage.CommandParameters.CommandState) // Wait for the message to reach its destination err = tt.RetryWithBackOff(func() error { var err error response, err = s.m.RetrieveAll() if err == nil && len(response.Messages) == 0 { err = errors.New("no messages") } return err }) s.Require().NoError(err) s.Require().NotNil(response) s.Require().Len(response.Chats, 1) s.Require().Len(response.Messages, 1) receiverMessage = response.Messages[0] s.Require().Equal(protobuf.ChatMessage_TRANSACTION_COMMAND, receiverMessage.ContentType) s.Require().Equal("Transaction request declined", receiverMessage.Text) s.Require().Equal(initialCommandID, receiverMessage.CommandParameters.ID) s.Require().Equal(initialCommandID, receiverMessage.Replace) s.Require().Equal(CommandStateRequestTransactionDeclined, receiverMessage.CommandParameters.CommandState) } func (s *MessengerSuite) TestRequestTransaction() { value := testValue contract := testContract receiverAddress := crypto.PubkeyToAddress(s.m.identity.PublicKey) receiverAddressString := strings.ToLower(receiverAddress.Hex()) theirMessenger := s.newMessenger(s.shh) theirPkString := types.EncodeHex(crypto.FromECDSAPub(&theirMessenger.identity.PublicKey)) chat := CreateOneToOneChat(theirPkString, &theirMessenger.identity.PublicKey, s.m.transport) err := s.m.SaveChat(&chat) s.Require().NoError(err) response, err := s.m.RequestTransaction(context.Background(), theirPkString, value, contract, receiverAddressString) s.Require().NoError(err) s.Require().NotNil(response) s.Require().Len(response.Chats, 1) s.Require().Len(response.Messages, 1) senderMessage := response.Messages[0] s.Require().Equal(protobuf.ChatMessage_TRANSACTION_COMMAND, senderMessage.ContentType) initialCommandID := senderMessage.ID s.Require().Equal("Request transaction", senderMessage.Text) s.Require().NotNil(senderMessage.CommandParameters) s.Require().Equal(value, senderMessage.CommandParameters.Value) s.Require().Equal(contract, senderMessage.CommandParameters.Contract) s.Require().Equal(receiverAddressString, senderMessage.CommandParameters.Address) s.Require().Equal(initialCommandID, senderMessage.CommandParameters.ID) s.Require().Equal(CommandStateRequestTransaction, senderMessage.CommandParameters.CommandState) // Wait for the message to reach its destination err = tt.RetryWithBackOff(func() error { var err error response, err = theirMessenger.RetrieveAll() if err == nil && len(response.Messages) == 0 { err = errors.New("no messages") } return err }) s.Require().NoError(err) s.Require().NotNil(response) s.Require().Len(response.Chats, 1) s.Require().Len(response.Messages, 1) receiverMessage := response.Messages[0] s.Require().Equal(protobuf.ChatMessage_TRANSACTION_COMMAND, receiverMessage.ContentType) s.Require().Equal("Request transaction", receiverMessage.Text) s.Require().NotNil(receiverMessage.CommandParameters) s.Require().Equal(value, receiverMessage.CommandParameters.Value) s.Require().Equal(contract, receiverMessage.CommandParameters.Contract) s.Require().Equal(receiverAddressString, receiverMessage.CommandParameters.Address) s.Require().Equal(initialCommandID, receiverMessage.CommandParameters.ID) s.Require().Equal(CommandStateRequestTransaction, receiverMessage.CommandParameters.CommandState) transactionHash := "0x412a851ac2ae51cad34a56c8a9cfee55d577ac5e1ac71cf488a2f2093a373799" signature, err := buildSignature(theirMessenger.identity, &theirMessenger.identity.PublicKey, transactionHash) s.Require().NoError(err) response, err = theirMessenger.AcceptRequestTransaction(context.Background(), transactionHash, initialCommandID, signature) s.Require().NoError(err) s.Require().NotNil(response) s.Require().Len(response.Chats, 1) s.Require().Len(response.Messages, 1) senderMessage = response.Messages[0] s.Require().Equal(protobuf.ChatMessage_TRANSACTION_COMMAND, senderMessage.ContentType) s.Require().Equal("Transaction sent", senderMessage.Text) s.Require().NotNil(senderMessage.CommandParameters) s.Require().Equal(value, senderMessage.CommandParameters.Value) s.Require().Equal(contract, senderMessage.CommandParameters.Contract) s.Require().Equal(transactionHash, senderMessage.CommandParameters.TransactionHash) s.Require().Equal(receiverAddressString, senderMessage.CommandParameters.Address) s.Require().Equal(initialCommandID, senderMessage.CommandParameters.ID) s.Require().Equal(signature, senderMessage.CommandParameters.Signature) s.Require().NotEmpty(senderMessage.ID) s.Require().Equal(receiverMessage.ID, senderMessage.Replace) s.Require().Equal(CommandStateTransactionSent, senderMessage.CommandParameters.CommandState) var transactions []*TransactionToValidate // Wait for the message to reach its destination err = tt.RetryWithBackOff(func() error { var err error _, err = s.m.RetrieveAll() if err != nil { return err } transactions, err = s.m.persistence.TransactionsToValidate() if err == nil && len(transactions) == 0 { err = errors.New("no transactions") } return err }) s.Require().NoError(err) actualTransaction := transactions[0] s.Require().Equal(&theirMessenger.identity.PublicKey, actualTransaction.From) s.Require().Equal(transactionHash, actualTransaction.TransactionHash) s.Require().True(actualTransaction.Validate) s.Require().Equal(initialCommandID, actualTransaction.CommandID) senderAddress := crypto.PubkeyToAddress(theirMessenger.identity.PublicKey) contractAddress := types.HexToAddress(contract) client := MockEthClient{} valueBig, ok := big.NewInt(0).SetString(value, 10) s.Require().True(ok) client.messages = make(map[string]MockTransaction) client.messages[transactionHash] = MockTransaction{ Status: coretypes.TransactionStatusSuccess, Message: coretypes.NewMessage( senderAddress, &contractAddress, 1, nil, 0, nil, buildData(transferFunction, receiverAddress, valueBig), false, ), } s.m.verifyTransactionClient = client response, err = s.m.ValidateTransactions(context.Background(), []types.Address{receiverAddress}) s.Require().NoError(err) s.Require().NotNil(response) s.Require().Len(response.Chats, 1) s.Require().Len(response.Messages, 1) receiverMessage = response.Messages[0] s.Require().Equal(protobuf.ChatMessage_TRANSACTION_COMMAND, receiverMessage.ContentType) s.Require().Equal("Transaction received", receiverMessage.Text) s.Require().NotNil(receiverMessage.CommandParameters) s.Require().Equal(value, receiverMessage.CommandParameters.Value) s.Require().Equal(contract, receiverMessage.CommandParameters.Contract) s.Require().Equal(transactionHash, receiverMessage.CommandParameters.TransactionHash) s.Require().Equal(receiverAddressString, receiverMessage.CommandParameters.Address) s.Require().Equal(initialCommandID, receiverMessage.CommandParameters.ID) s.Require().Equal(signature, receiverMessage.CommandParameters.Signature) s.Require().Equal(CommandStateTransactionSent, receiverMessage.CommandParameters.CommandState) s.Require().Equal(senderMessage.ID, receiverMessage.ID) s.Require().Equal(senderMessage.Replace, senderMessage.Replace) } type MockTransaction struct { Status coretypes.TransactionStatus Message coretypes.Message } type MockEthClient struct { messages map[string]MockTransaction } type mockSendMessagesRequest struct { types.Whisper req types.MessagesRequest } func (m MockEthClient) TransactionByHash(ctx context.Context, hash types.Hash) (coretypes.Message, coretypes.TransactionStatus, error) { mockTransaction, ok := m.messages[hash.Hex()] if !ok { return coretypes.Message{}, coretypes.TransactionStatusFailed, nil } return mockTransaction.Message, mockTransaction.Status, nil } func (m *mockSendMessagesRequest) SendMessagesRequest(peerID []byte, request types.MessagesRequest) error { m.req = request return nil } func (s *MessengerSuite) TestMessageJSON() { message := &Message{ ID: "test-1", LocalChatID: "local-chat-id", Alias: "alias", ChatMessage: protobuf.ChatMessage{ ChatId: "remote-chat-id", ContentType: 0, Text: "test-1", Clock: 1, }, From: "from-field", } expectedJSON := `{"id":"test-1","whisperTimestamp":0,"from":"from-field","alias":"alias","identicon":"","seen":false,"quotedMessage":null,"rtl":false,"parsedText":null,"lineCount":0,"text":"test-1","chatId":"remote-chat-id","localChatId":"local-chat-id","clock":1,"replace":"","responseTo":"","ensName":"","sticker":null,"commandParameters":null,"timestamp":0,"contentType":0,"messageType":0}` messageJSON, err := json.Marshal(message) s.Require().NoError(err) s.Require().Equal(expectedJSON, string(messageJSON)) decodedMessage := &Message{} err = json.Unmarshal([]byte(expectedJSON), decodedMessage) s.Require().NoError(err) s.Require().Equal(message, decodedMessage) } func (s *MessengerSuite) TestRequestHistoricMessagesRequest() { shh := &mockSendMessagesRequest{ Whisper: s.shh, } m := s.newMessenger(shh) ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond) defer cancel() cursor, err := m.RequestHistoricMessages(ctx, nil, 10, 20, []byte{0x01}) s.EqualError(err, ctx.Err().Error()) s.Empty(cursor) // verify request is correct s.NotEmpty(shh.req.ID) s.EqualValues(10, shh.req.From) s.EqualValues(20, shh.req.To) s.EqualValues(100, shh.req.Limit) s.Equal([]byte{0x01}, shh.req.Cursor) s.NotEmpty(shh.req.Bloom) } type MessageHandlerSuite struct { suite.Suite messageHandler *MessageHandler logger *zap.Logger } func (s *MessageHandlerSuite) SetupTest() { s.logger = tt.MustCreateTestLogger() privateKey, err := crypto.GenerateKey() s.Require().NoError(err) s.messageHandler = &MessageHandler{ identity: privateKey, logger: s.logger, } } func (s *MessageHandlerSuite) TearDownTest() { _ = s.logger.Sync() } type testTimeSource struct{} func (t *testTimeSource) GetCurrentTime() uint64 { return uint64(time.Now().Unix()) } func (s *MessageHandlerSuite) TestRun() { key1, err := crypto.GenerateKey() s.Require().NoError(err) key2, err := crypto.GenerateKey() s.Require().NoError(err) testCases := []struct { Name string Error bool Chat Chat // Chat to create Message Message SigPubKey *ecdsa.PublicKey ExpectedChatID string }{ { Name: "Public chat", Chat: CreatePublicChat("test-chat", &testTimeSource{}), Message: Message{ ChatMessage: protobuf.ChatMessage{ ChatId: "test-chat", MessageType: protobuf.ChatMessage_PUBLIC_GROUP, Text: "test-text"}, }, SigPubKey: &key1.PublicKey, ExpectedChatID: "test-chat", }, { Name: "Private message from myself with existing chat", Chat: CreateOneToOneChat("test-private-chat", &key1.PublicKey, &testTimeSource{}), Message: Message{ ChatMessage: protobuf.ChatMessage{ ChatId: "test-chat", MessageType: protobuf.ChatMessage_ONE_TO_ONE, Text: "test-text"}, }, SigPubKey: &key1.PublicKey, ExpectedChatID: oneToOneChatID(&key1.PublicKey), }, { Name: "Private message from other with existing chat", Chat: CreateOneToOneChat("test-private-chat", &key2.PublicKey, &testTimeSource{}), Message: Message{ ChatMessage: protobuf.ChatMessage{ ChatId: "test-chat", MessageType: protobuf.ChatMessage_ONE_TO_ONE, Text: "test-text"}, }, SigPubKey: &key2.PublicKey, ExpectedChatID: oneToOneChatID(&key2.PublicKey), }, { Name: "Private message from myself without chat", Message: Message{ ChatMessage: protobuf.ChatMessage{ ChatId: "test-chat", MessageType: protobuf.ChatMessage_ONE_TO_ONE, Text: "test-text"}, }, SigPubKey: &key1.PublicKey, ExpectedChatID: oneToOneChatID(&key1.PublicKey), }, { Name: "Private message from other without chat", Message: Message{ ChatMessage: protobuf.ChatMessage{ ChatId: "test-chat", MessageType: protobuf.ChatMessage_ONE_TO_ONE, Text: "test-text"}, }, SigPubKey: &key2.PublicKey, ExpectedChatID: oneToOneChatID(&key2.PublicKey), }, { Name: "Private message without public key", SigPubKey: nil, Error: true, }, { Name: "Private group message", Message: Message{ ChatMessage: protobuf.ChatMessage{ ChatId: "non-existing-chat", MessageType: protobuf.ChatMessage_PRIVATE_GROUP, Text: "test-text"}, }, Error: true, SigPubKey: &key2.PublicKey, }, } for idx, tc := range testCases { s.Run(tc.Name, func() { chatsMap := make(map[string]*Chat) if tc.Chat.ID != "" { chatsMap[tc.Chat.ID] = &tc.Chat } message := tc.Message message.SigPubKey = tc.SigPubKey // ChatID is not set at the beginning. s.Empty(message.LocalChatID) message.ID = strconv.Itoa(idx) // manually set the ID because messages does not go through messageProcessor chat, err := s.messageHandler.matchMessage(&message, chatsMap, &testTimeSource{}) if tc.Error { s.Require().Error(err) } else { s.Require().NoError(err) if tc.ExpectedChatID != "" { s.Require().NotNil(chat) s.Require().Equal(tc.ExpectedChatID, chat.ID) } } }) } }