status-go/protocol/messenger_installations_test.go
Pascal Precht 3f987cc565 fix(messenger): don't remove inactive chats when syncing
This commit ensures we're relying on `chat.DeletedAtClockValue` instead
of `chat.Active` to know whether or not we need to remove the chat from
paired devices.

Because we were relying on `Active != true`, we ended up with a serious
but that would result in deactivating all chats on paired devices.

The reason the chats would disappear on paired devices is because, when
setting up a new device by importing a seedphrase, chances are this
device will receive `HandleBackUp` signals (which original from other
devices with the same account that backed up contacts etc).

When backups are handled, we create chats for every contact that's part
of the backup signal. Those chats are set to `Active = false` because
the signal handling shouldn't cause those chats to show up in the UI.

However, because those are set to `Active = false`, the next time the
user tries to sync from this devices, all those chats are considered as
"removed", hence sending "chat removed" signals when syncing (which then
causes those chats to disappear on all paired devices.

We need to rely on `DeletedAtClockValue` to know whether a chat was
indeed removed and only then emit such a signal.
2022-08-25 11:51:01 +02:00

320 lines
9.5 KiB
Go

package protocol
import (
"context"
"crypto/ecdsa"
"errors"
"testing"
"github.com/status-im/status-go/services/browsers"
"github.com/stretchr/testify/suite"
"go.uber.org/zap"
gethbridge "github.com/status-im/status-go/eth-node/bridge/geth"
"github.com/status-im/status-go/eth-node/crypto"
"github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/protocol/encryption/multidevice"
"github.com/status-im/status-go/protocol/requests"
"github.com/status-im/status-go/protocol/tt"
"github.com/status-im/status-go/waku"
)
const statusChatID = "status"
const removedChatID = "deactivated"
func TestMessengerInstallationSuite(t *testing.T) {
suite.Run(t, new(MessengerInstallationSuite))
}
type MessengerInstallationSuite struct {
suite.Suite
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 Waku service should be shared.
shh types.Waku
logger *zap.Logger
}
func (s *MessengerInstallationSuite) SetupTest() {
s.logger = tt.MustCreateTestLogger()
config := waku.DefaultConfig
config.MinimumAcceptedPoW = 0
shh := waku.New(&config, s.logger)
s.shh = gethbridge.NewGethWakuWrapper(shh)
s.Require().NoError(shh.Start())
s.m = s.newMessenger(s.shh)
s.privateKey = s.m.identity
// We start the messenger in order to receive installations
_, err := s.m.Start()
s.Require().NoError(err)
}
func (s *MessengerInstallationSuite) TearDownTest() {
s.Require().NoError(s.m.Shutdown())
}
func (s *MessengerInstallationSuite) newMessenger(shh types.Waku) *Messenger {
privateKey, err := crypto.GenerateKey()
s.Require().NoError(err)
messenger, err := newMessengerWithKey(s.shh, privateKey, s.logger, nil)
s.Require().NoError(err)
return messenger
}
func (s *MessengerInstallationSuite) TestReceiveInstallation() {
theirMessenger, err := newMessengerWithKey(s.shh, s.privateKey, s.logger, nil)
s.Require().NoError(err)
err = theirMessenger.SetInstallationMetadata(theirMessenger.installationID, &multidevice.InstallationMetadata{
Name: "their-name",
DeviceType: "their-device-type",
})
s.Require().NoError(err)
response, err := theirMessenger.SendPairInstallation(context.Background())
s.Require().NoError(err)
s.Require().NotNil(response)
s.Require().Len(response.Chats(), 1)
s.Require().False(response.Chats()[0].Active)
// Wait for the message to reach its destination
response, err = WaitOnMessengerResponse(
s.m,
func(r *MessengerResponse) bool { return len(r.Installations) > 0 },
"installation not received",
)
s.Require().NoError(err)
actualInstallation := response.Installations[0]
s.Require().Equal(theirMessenger.installationID, actualInstallation.ID)
s.Require().NotNil(actualInstallation.InstallationMetadata)
s.Require().Equal("their-name", actualInstallation.InstallationMetadata.Name)
s.Require().Equal("their-device-type", actualInstallation.InstallationMetadata.DeviceType)
err = s.m.EnableInstallation(theirMessenger.installationID)
s.Require().NoError(err)
contactKey, err := crypto.GenerateKey()
s.Require().NoError(err)
contact, err := BuildContactFromPublicKey(&contactKey.PublicKey)
s.Require().NoError(err)
_, err = s.m.AddContact(context.Background(), &requests.AddContact{ID: types.Hex2Bytes(contact.ID)})
s.Require().NoError(err)
// Wait for the message to reach its destination
response, err = WaitOnMessengerResponse(
theirMessenger,
func(r *MessengerResponse) bool { return len(r.Contacts) > 0 && r.Contacts[0].ID == contact.ID },
"contact not received",
)
s.Require().NoError(err)
actualContact := response.Contacts[0]
s.Require().Equal(contact.ID, actualContact.ID)
s.Require().True(actualContact.Added)
chat := CreatePublicChat(statusChatID, s.m.transport)
err = s.m.SaveChat(chat)
s.Require().NoError(err)
response, err = WaitOnMessengerResponse(
theirMessenger,
func(r *MessengerResponse) bool { return len(r.Chats()) > 0 },
"sync chat not received",
)
s.Require().NoError(err)
actualChat := response.Chats()[0]
s.Require().Equal(statusChatID, actualChat.ID)
s.Require().True(actualChat.Active)
s.Require().NoError(theirMessenger.Shutdown())
}
func (s *MessengerInstallationSuite) TestSyncInstallation() {
// add contact
contactKey, err := crypto.GenerateKey()
s.Require().NoError(err)
contact, err := BuildContactFromPublicKey(&contactKey.PublicKey)
s.Require().NoError(err)
contact.LocalNickname = "Test Nickname"
_, err = s.m.AddContact(context.Background(), &requests.AddContact{ID: types.Hex2Bytes(contact.ID)})
s.Require().NoError(err)
_, err = s.m.SetContactLocalNickname(&requests.SetContactLocalNickname{ID: types.Hex2Bytes(contact.ID), Nickname: contact.LocalNickname})
s.Require().NoError(err)
//add bookmark
bookmark := browsers.Bookmark{
Name: "status official site",
URL: "https://status.im",
Removed: false,
}
_, err = s.m.browserDatabase.StoreBookmark(bookmark)
s.Require().NoError(err)
// add chat
chat := CreatePublicChat(statusChatID, s.m.transport)
err = s.m.SaveChat(chat)
s.Require().NoError(err)
// add and deactivate chat
chat2 := CreatePublicChat(removedChatID, s.m.transport)
chat2.DeletedAtClockValue = 1
err = s.m.SaveChat(chat2)
s.Require().NoError(err)
_, err = s.m.deactivateChat(removedChatID, 0, true)
s.Require().NoError(err)
// pair
theirMessenger, err := newMessengerWithKey(s.shh, s.privateKey, s.logger, nil)
s.Require().NoError(err)
err = theirMessenger.SaveChat(chat2)
s.Require().NoError(err)
err = theirMessenger.SetInstallationMetadata(theirMessenger.installationID, &multidevice.InstallationMetadata{
Name: "their-name",
DeviceType: "their-device-type",
})
s.Require().NoError(err)
response, err := theirMessenger.SendPairInstallation(context.Background())
s.Require().NoError(err)
s.Require().NotNil(response)
s.Require().Len(response.Chats(), 1)
s.Require().False(response.Chats()[0].Active)
// Wait for the message to reach its destination
response, err = WaitOnMessengerResponse(
s.m,
func(r *MessengerResponse) bool { return len(r.Installations) > 0 },
"installation not received",
)
s.Require().NoError(err)
actualInstallation := response.Installations[0]
s.Require().Equal(theirMessenger.installationID, actualInstallation.ID)
s.Require().NotNil(actualInstallation.InstallationMetadata)
s.Require().Equal("their-name", actualInstallation.InstallationMetadata.Name)
s.Require().Equal("their-device-type", actualInstallation.InstallationMetadata.DeviceType)
err = s.m.EnableInstallation(theirMessenger.installationID)
s.Require().NoError(err)
// sync
err = s.m.SyncDevices(context.Background(), "ens-name", "profile-image")
s.Require().NoError(err)
var allChats []*Chat
var actualContact *Contact
// Wait for the message to reach its destination
err = tt.RetryWithBackOff(func() error {
var err error
response, err = theirMessenger.RetrieveAll()
if err != nil {
return err
}
allChats = append(allChats, response.Chats()...)
if len(response.Contacts) == 1 {
actualContact = response.Contacts[0]
}
if len(allChats) >= 2 && actualContact != nil {
return nil
}
return errors.New("Not received all chats & contacts")
})
s.Require().NoError(err)
var statusChat *Chat
var removedChat *Chat
for _, c := range allChats {
if c.ID == statusChatID {
statusChat = c
}
if c.ID == removedChatID {
removedChat = c
}
}
s.Require().NotNil(statusChat)
s.Require().True(actualContact.Added)
s.Require().Equal("Test Nickname", actualContact.LocalNickname)
bookmarks, err := theirMessenger.browserDatabase.GetBookmarks()
s.Require().NoError(err)
s.Require().Equal(1, len(bookmarks))
s.Require().NoError(theirMessenger.Shutdown())
s.Require().NotNil(removedChat)
s.Require().False(removedChat.Active)
}
func (s *MessengerInstallationSuite) TestSyncInstallationNewMessages() {
bob1 := s.m
// pair
bob2, err := newMessengerWithKey(s.shh, s.privateKey, s.logger, nil)
s.Require().NoError(err)
alice := s.newMessenger(s.shh)
err = bob2.SetInstallationMetadata(bob2.installationID, &multidevice.InstallationMetadata{
Name: "their-name",
DeviceType: "their-device-type",
})
s.Require().NoError(err)
response, err := bob2.SendPairInstallation(context.Background())
s.Require().NoError(err)
s.Require().NotNil(response)
s.Require().Len(response.Chats(), 1)
s.Require().False(response.Chats()[0].Active)
// Wait for the message to reach its destination
response, err = WaitOnMessengerResponse(
bob1,
func(r *MessengerResponse) bool { return len(r.Installations) > 0 },
"installation not received",
)
s.Require().NoError(err)
actualInstallation := response.Installations[0]
s.Require().Equal(bob2.installationID, actualInstallation.ID)
err = bob1.EnableInstallation(bob2.installationID)
s.Require().NoError(err)
// send a message from bob1 to alice, it should be received on both bob1 and bob2
alicePkString := types.EncodeHex(crypto.FromECDSAPub(&alice.identity.PublicKey))
chat := CreateOneToOneChat(alicePkString, &alice.identity.PublicKey, bob1.transport)
s.Require().NoError(bob1.SaveChat(chat))
inputMessage := buildTestMessage(*chat)
_, err = s.m.SendChatMessage(context.Background(), inputMessage)
s.Require().NoError(err)
// Wait for the message to reach its destination
_, err = WaitOnMessengerResponse(
bob2,
func(r *MessengerResponse) bool { return len(r.Messages()) > 0 },
"message not received",
)
s.Require().NoError(err)
s.Require().NoError(bob2.Shutdown())
s.Require().NoError(alice.Shutdown())
}