github.com/status-im/status-go@v1.1.0/protocol/messenger_installations_test.go (about)

     1  package protocol
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"image"
     7  	"image/png"
     8  	"os"
     9  	"runtime"
    10  	"testing"
    11  
    12  	userimage "github.com/status-im/status-go/images"
    13  	"github.com/status-im/status-go/server"
    14  	"github.com/status-im/status-go/services/browsers"
    15  
    16  	"github.com/stretchr/testify/suite"
    17  
    18  	"github.com/status-im/status-go/eth-node/crypto"
    19  	"github.com/status-im/status-go/eth-node/types"
    20  	multiaccountscommon "github.com/status-im/status-go/multiaccounts/common"
    21  	"github.com/status-im/status-go/protocol/encryption/multidevice"
    22  	"github.com/status-im/status-go/protocol/requests"
    23  	"github.com/status-im/status-go/protocol/tt"
    24  )
    25  
    26  const statusChatID = "status"
    27  const removedChatID = "deactivated"
    28  
    29  func TestMessengerInstallationSuite(t *testing.T) {
    30  	suite.Run(t, new(MessengerInstallationSuite))
    31  }
    32  
    33  type MessengerInstallationSuite struct {
    34  	MessengerBaseTestSuite
    35  }
    36  
    37  func (s *MessengerInstallationSuite) TestReceiveInstallation() {
    38  	theirMessenger, err := newMessengerWithKey(s.shh, s.privateKey, s.logger, nil)
    39  	s.Require().NoError(err)
    40  
    41  	err = theirMessenger.SetInstallationMetadata(theirMessenger.installationID, &multidevice.InstallationMetadata{
    42  		Name:       "their-name",
    43  		DeviceType: "their-device-type",
    44  	})
    45  	s.Require().NoError(err)
    46  	response, err := theirMessenger.SendPairInstallation(context.Background(), nil)
    47  	s.Require().NoError(err)
    48  	s.Require().NotNil(response)
    49  	s.Require().Len(response.Chats(), 1)
    50  	s.Require().False(response.Chats()[0].Active)
    51  
    52  	// Wait for the message to reach its destination
    53  	response, err = WaitOnMessengerResponse(
    54  		s.m,
    55  		func(r *MessengerResponse) bool { return len(r.Installations()) > 0 },
    56  		"installation not received",
    57  	)
    58  
    59  	s.Require().NoError(err)
    60  	actualInstallation := response.Installations()[0]
    61  	s.Require().Equal(theirMessenger.installationID, actualInstallation.ID)
    62  	s.Require().NotNil(actualInstallation.InstallationMetadata)
    63  	s.Require().Equal("their-name", actualInstallation.InstallationMetadata.Name)
    64  	s.Require().Equal("their-device-type", actualInstallation.InstallationMetadata.DeviceType)
    65  
    66  	err = s.m.EnableInstallation(theirMessenger.installationID)
    67  	s.Require().NoError(err)
    68  
    69  	contactKey, err := crypto.GenerateKey()
    70  	s.Require().NoError(err)
    71  
    72  	contact, err := BuildContactFromPublicKey(&contactKey.PublicKey)
    73  	s.Require().NoError(err)
    74  	response, err = s.m.AddContact(context.Background(), &requests.AddContact{ID: contact.ID, CustomizationColor: string(multiaccountscommon.CustomizationColorRed)})
    75  	s.Require().NoError(err)
    76  
    77  	s.Require().Len(response.Contacts, 1)
    78  	s.Require().Equal(response.Contacts[0].ID, contact.ID)
    79  	s.Require().Equal(response.Contacts[0].CustomizationColor, multiaccountscommon.CustomizationColorRed)
    80  
    81  	// Wait for the message to reach its destination
    82  	response, err = WaitOnMessengerResponse(
    83  		theirMessenger,
    84  		func(r *MessengerResponse) bool { return len(r.Contacts) == 1 && r.Contacts[0].ID == contact.ID },
    85  		"contact not received",
    86  	)
    87  	s.Require().NoError(err)
    88  
    89  	actualContact := response.Contacts[0]
    90  	s.Require().Equal(contact.ID, actualContact.ID)
    91  	s.Require().True(actualContact.added())
    92  
    93  	// Simulate update from contact
    94  	contact.LastUpdated = 10
    95  	contact.DisplayName = "display-name"
    96  	contact.CustomizationColor = multiaccountscommon.CustomizationColorRed
    97  
    98  	s.Require().NoError(s.m.persistence.SaveContacts([]*Contact{contact}))
    99  	// Trigger syncing of contact
   100  	err = s.m.syncContact(context.Background(), contact, s.m.dispatchMessage)
   101  	s.Require().NoError(err)
   102  
   103  	// Wait for the message to reach its destination
   104  	_, err = WaitOnMessengerResponse(
   105  		theirMessenger,
   106  		func(r *MessengerResponse) bool {
   107  			return len(r.Contacts) == 1 &&
   108  				r.Contacts[0].ID == contact.ID &&
   109  				// Make sure lastupdated is **not** synced
   110  				actualContact.LastUpdated == 0 &&
   111  				r.Contacts[0].DisplayName == "display-name" &&
   112  				r.Contacts[0].CustomizationColor == multiaccountscommon.CustomizationColorRed
   113  		},
   114  		"contact not received",
   115  	)
   116  	s.Require().NoError(err)
   117  
   118  	chat := CreatePublicChat(statusChatID, s.m.transport)
   119  	err = s.m.SaveChat(chat)
   120  	s.Require().NoError(err)
   121  
   122  	response, err = WaitOnMessengerResponse(
   123  		theirMessenger,
   124  		func(r *MessengerResponse) bool { return len(r.Chats()) > 0 },
   125  		"sync chat not received",
   126  	)
   127  
   128  	s.Require().NoError(err)
   129  
   130  	actualChat := response.Chats()[0]
   131  	s.Require().Equal(statusChatID, actualChat.ID)
   132  	s.Require().True(actualChat.Active)
   133  	s.Require().NoError(theirMessenger.Shutdown())
   134  }
   135  
   136  func (s *MessengerInstallationSuite) TestSyncInstallation() {
   137  	// add contact
   138  	contactKey, err := crypto.GenerateKey()
   139  	s.Require().NoError(err)
   140  
   141  	contact, err := BuildContactFromPublicKey(&contactKey.PublicKey)
   142  	s.Require().NoError(err)
   143  
   144  	// mock added as mutual contact
   145  	contact.LastUpdated = 1
   146  	contact.ContactRequestReceived(1)
   147  	s.m.allContacts.Store(contact.ID, contact)
   148  
   149  	contact.LocalNickname = "Test Nickname"
   150  	_, err = s.m.AddContact(context.Background(), &requests.AddContact{ID: contact.ID, CustomizationColor: string(multiaccountscommon.CustomizationColorRed)})
   151  	s.Require().NoError(err)
   152  	_, err = s.m.SetContactLocalNickname(&requests.SetContactLocalNickname{ID: types.Hex2Bytes(contact.ID), Nickname: contact.LocalNickname})
   153  	s.Require().NoError(err)
   154  
   155  	//add bookmark
   156  	bookmark := browsers.Bookmark{
   157  		Name:    "status official site",
   158  		URL:     "https://status.im",
   159  		Removed: false,
   160  	}
   161  	_, err = s.m.browserDatabase.StoreBookmark(bookmark)
   162  	s.Require().NoError(err)
   163  
   164  	// add chat
   165  	chat := CreatePublicChat(statusChatID, s.m.transport)
   166  	err = s.m.SaveChat(chat)
   167  	s.Require().NoError(err)
   168  
   169  	// Create group chat
   170  	response, err := s.m.CreateGroupChatWithMembers(context.Background(), "group", []string{})
   171  	s.NoError(err)
   172  	s.Require().Len(response.Chats(), 1)
   173  
   174  	ourGroupChat := response.Chats()[0]
   175  
   176  	err = s.m.SaveChat(ourGroupChat)
   177  	s.NoError(err)
   178  
   179  	// Generate test image bigger than BannerDim
   180  	testImage := image.NewRGBA(image.Rect(0, 0, 20, 10))
   181  
   182  	tmpTestFilePath := s.T().TempDir() + "/test.png"
   183  	file, err := os.Create(tmpTestFilePath)
   184  	s.NoError(err)
   185  	defer file.Close()
   186  
   187  	err = png.Encode(file, testImage)
   188  	s.Require().NoError(err)
   189  
   190  	groupImg := userimage.CroppedImage{
   191  		ImagePath: tmpTestFilePath,
   192  		X:         1,
   193  		Y:         1,
   194  		Width:     10,
   195  		Height:    5,
   196  	}
   197  
   198  	// Add image to chat
   199  	response, err = s.m.EditGroupChat(context.Background(), ourGroupChat.ID, "test_admin_group", "#FF00FF", groupImg)
   200  	s.Require().NoError(err)
   201  	s.Require().Len(response.Chats(), 1)
   202  	s.Require().Equal("test_admin_group", response.Chats()[0].Name)
   203  	s.Require().Equal("#FF00FF", response.Chats()[0].Color)
   204  	ourGroupChat = response.Chats()[0]
   205  
   206  	// Create second group chat and deactivate it
   207  	response, err = s.m.CreateGroupChatWithMembers(context.Background(), "deactivated-group", []string{})
   208  	s.NoError(err)
   209  	s.Require().Len(response.Chats(), 1)
   210  
   211  	ourDeactivatedGroupChat := response.Chats()[0]
   212  	err = s.m.SaveChat(ourDeactivatedGroupChat)
   213  	s.NoError(err)
   214  	_, err = s.m.deactivateChat(ourDeactivatedGroupChat.ID, 0, true, true)
   215  	s.NoError(err)
   216  
   217  	// Create Alice for the 1-1 chat
   218  	alice := s.newMessenger()
   219  	defer TearDownMessenger(&s.Suite, alice)
   220  
   221  	// Create 1-1 chat
   222  	ourOneOneChat := CreateOneToOneChat("Our 1TO1", &alice.identity.PublicKey, alice.transport)
   223  	err = s.m.SaveChat(ourOneOneChat)
   224  	s.Require().NoError(err)
   225  
   226  	// add and deactivate chat
   227  	chat2 := CreatePublicChat(removedChatID, s.m.transport)
   228  	chat2.DeletedAtClockValue = 1
   229  	err = s.m.SaveChat(chat2)
   230  	s.Require().NoError(err)
   231  	_, err = s.m.deactivateChat(removedChatID, 0, true, true)
   232  	s.Require().NoError(err)
   233  
   234  	// pair
   235  	theirMessenger, err := newMessengerWithKey(s.shh, s.privateKey, s.logger, nil)
   236  	s.Require().NoError(err)
   237  	err = theirMessenger.SaveChat(chat2)
   238  	s.Require().NoError(err)
   239  
   240  	err = theirMessenger.SetInstallationMetadata(theirMessenger.installationID, &multidevice.InstallationMetadata{
   241  		Name:       "their-name",
   242  		DeviceType: "their-device-type",
   243  	})
   244  	s.Require().NoError(err)
   245  	response, err = theirMessenger.SendPairInstallation(context.Background(), nil)
   246  	s.Require().NoError(err)
   247  	s.Require().NotNil(response)
   248  	s.Require().Len(response.Chats(), 1)
   249  	s.Require().False(response.Chats()[0].Active)
   250  
   251  	// Wait for the message to reach its destination
   252  	response, err = WaitOnMessengerResponse(
   253  		s.m,
   254  		func(r *MessengerResponse) bool { return len(r.Installations()) > 0 },
   255  		"installation not received",
   256  	)
   257  
   258  	s.Require().NoError(err)
   259  	actualInstallation := response.Installations()[0]
   260  	s.Require().Equal(theirMessenger.installationID, actualInstallation.ID)
   261  	s.Require().NotNil(actualInstallation.InstallationMetadata)
   262  	s.Require().Equal("their-name", actualInstallation.InstallationMetadata.Name)
   263  	s.Require().Equal("their-device-type", actualInstallation.InstallationMetadata.DeviceType)
   264  
   265  	err = s.m.EnableInstallation(theirMessenger.installationID)
   266  	s.Require().NoError(err)
   267  
   268  	// sync
   269  	err = s.m.SyncDevices(context.Background(), "ens-name", "profile-image", nil)
   270  	s.Require().NoError(err)
   271  
   272  	var allChats []*Chat
   273  	var actualContact *Contact
   274  	var bookmarks []*browsers.Bookmark
   275  	// Wait for the message to reach its destination
   276  	err = tt.RetryWithBackOff(func() error {
   277  		var err error
   278  		response, err = theirMessenger.RetrieveAll()
   279  		if err != nil {
   280  			return err
   281  		}
   282  
   283  		allChats = append(allChats, response.Chats()...)
   284  		for _, c := range response.Contacts {
   285  			if c.LocalNickname == contact.LocalNickname {
   286  				actualContact = c
   287  				break
   288  			}
   289  		}
   290  		bookmarks = append(bookmarks, response.GetBookmarks()...)
   291  
   292  		if len(allChats) >= 5 && actualContact != nil && len(bookmarks) >= 1 {
   293  			return nil
   294  		}
   295  
   296  		return errors.New("not received all chats & contacts & bookmarks yet")
   297  
   298  	})
   299  
   300  	s.Require().NoError(err)
   301  
   302  	var statusChat *Chat
   303  	var groupChat *Chat
   304  	var removedGroupChat *Chat
   305  	var oneToOneChat *Chat
   306  	var removedChat *Chat
   307  	for _, c := range allChats {
   308  		if c.ID == statusChatID {
   309  			statusChat = c
   310  		}
   311  		if c.ID == ourGroupChat.ID {
   312  			groupChat = c
   313  		}
   314  		if c.ID == ourDeactivatedGroupChat.ID {
   315  			removedGroupChat = c
   316  		}
   317  		if c.ID == ourOneOneChat.ID {
   318  			oneToOneChat = c
   319  		}
   320  		if c.ID == removedChatID {
   321  			removedChat = c
   322  		}
   323  	}
   324  
   325  	s.Require().NotNil(statusChat)
   326  	s.Require().NotNil(groupChat)
   327  	s.Require().NotNil(removedGroupChat)
   328  	s.Require().NotNil(oneToOneChat)
   329  
   330  	s.Require().Equal(ourGroupChat.Name, groupChat.Name)
   331  	s.Require().True(ourGroupChat.Active)
   332  
   333  	s.Require().Equal(ourDeactivatedGroupChat.Name, removedGroupChat.Name)
   334  	s.Require().False(removedGroupChat.Active)
   335  
   336  	s.Require().Equal("", oneToOneChat.Name) // We set 1-1 chat names to "" because the name is not good
   337  	s.Require().True(oneToOneChat.Active)
   338  
   339  	s.Require().True(actualContact.added())
   340  	s.Require().Equal("Test Nickname", actualContact.LocalNickname)
   341  	s.Require().Equal(multiaccountscommon.CustomizationColorRed, actualContact.CustomizationColor)
   342  	s.Require().True(actualContact.hasAddedUs())
   343  	s.Require().True(actualContact.mutual())
   344  
   345  	bookmarks, err = theirMessenger.browserDatabase.GetBookmarks()
   346  	s.Require().NoError(err)
   347  	s.Require().Equal(1, len(bookmarks))
   348  
   349  	s.Require().NoError(theirMessenger.Shutdown())
   350  
   351  	s.Require().NotNil(removedChat)
   352  	s.Require().False(removedChat.Active)
   353  
   354  }
   355  
   356  func (s *MessengerInstallationSuite) TestSyncInstallationNewMessages() {
   357  
   358  	bob1 := s.m
   359  	// pair
   360  	bob2, err := newMessengerWithKey(s.shh, s.privateKey, s.logger, nil)
   361  	s.Require().NoError(err)
   362  	alice := s.newMessenger()
   363  
   364  	err = bob2.SetInstallationMetadata(bob2.installationID, &multidevice.InstallationMetadata{
   365  		Name:       "their-name",
   366  		DeviceType: "their-device-type",
   367  	})
   368  	s.Require().NoError(err)
   369  	response, err := bob2.SendPairInstallation(context.Background(), nil)
   370  	s.Require().NoError(err)
   371  	s.Require().NotNil(response)
   372  	s.Require().Len(response.Chats(), 1)
   373  	s.Require().False(response.Chats()[0].Active)
   374  
   375  	// Wait for the message to reach its destination
   376  	response, err = WaitOnMessengerResponse(
   377  		bob1,
   378  		func(r *MessengerResponse) bool { return len(r.Installations()) > 0 },
   379  		"installation not received",
   380  	)
   381  
   382  	s.Require().NoError(err)
   383  	actualInstallation := response.Installations()[0]
   384  	s.Require().Equal(bob2.installationID, actualInstallation.ID)
   385  	err = bob1.EnableInstallation(bob2.installationID)
   386  	s.Require().NoError(err)
   387  
   388  	// send a message from bob1 to alice, it should be received on both bob1 and bob2
   389  
   390  	alicePkString := types.EncodeHex(crypto.FromECDSAPub(&alice.identity.PublicKey))
   391  	chat := CreateOneToOneChat(alicePkString, &alice.identity.PublicKey, bob1.transport)
   392  	s.Require().NoError(bob1.SaveChat(chat))
   393  
   394  	inputMessage := buildTestMessage(*chat)
   395  	_, err = s.m.SendChatMessage(context.Background(), inputMessage)
   396  	s.Require().NoError(err)
   397  
   398  	// Wait for the message to reach its destination
   399  	_, err = WaitOnMessengerResponse(
   400  		bob2,
   401  		func(r *MessengerResponse) bool { return len(r.Messages()) > 0 },
   402  		"message not received",
   403  	)
   404  	s.Require().NoError(err)
   405  	s.Require().NoError(bob2.Shutdown())
   406  	s.Require().NoError(alice.Shutdown())
   407  }
   408  
   409  func (s *MessengerInstallationSuite) TestInitInstallations() {
   410  	m, err := newMessengerWithKey(s.shh, s.privateKey, s.logger, nil)
   411  	s.Require().NoError(err)
   412  
   413  	// m.InitInstallations is already called when we set-up the messenger for
   414  	// testing, thus this test has no act phase.
   415  	// err = m.InitInstallations()
   416  
   417  	// We get one installation when the messenger initializes installations
   418  	// correctly.
   419  	s.Require().Equal(1, m.allInstallations.Len())
   420  
   421  	deviceName, err := server.GetDeviceName()
   422  	s.Require().NoError(err)
   423  	installation, ok := m.allInstallations.Load(m.installationID)
   424  	s.Require().True(ok)
   425  	s.Require().Equal(deviceName+" ", installation.InstallationMetadata.Name)
   426  	s.Require().Equal(runtime.GOOS, installation.InstallationMetadata.DeviceType)
   427  }