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

     1  package protocol
     2  
     3  import (
     4  	"context"
     5  	"crypto/rand"
     6  	"errors"
     7  	"fmt"
     8  	"math/big"
     9  	mathRand "math/rand"
    10  	"sync"
    11  	"time"
    12  
    13  	"github.com/libp2p/go-libp2p/core/peer"
    14  
    15  	"github.com/status-im/status-go/protocol/wakusync"
    16  
    17  	"github.com/status-im/status-go/protocol/identity"
    18  
    19  	"github.com/status-im/status-go/eth-node/types"
    20  	waku2 "github.com/status-im/status-go/wakuv2"
    21  
    22  	"github.com/stretchr/testify/suite"
    23  
    24  	"github.com/status-im/status-go/protocol/common"
    25  	"github.com/status-im/status-go/protocol/communities"
    26  	"github.com/status-im/status-go/protocol/protobuf"
    27  	"github.com/status-im/status-go/protocol/requests"
    28  	"github.com/status-im/status-go/protocol/tt"
    29  )
    30  
    31  var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
    32  var hexRunes = []rune("0123456789abcdef")
    33  
    34  // WaitOnMessengerResponse Wait until the condition is true or the timeout is reached.
    35  func WaitOnMessengerResponse(m *Messenger, condition func(*MessengerResponse) bool, errorMessage string) (*MessengerResponse, error) {
    36  	response := &MessengerResponse{}
    37  	err := tt.RetryWithBackOff(func() error {
    38  		var err error
    39  		r, err := m.RetrieveAll()
    40  		if err != nil {
    41  			panic(err)
    42  		}
    43  		if err := response.Merge(r); err != nil {
    44  			panic(err)
    45  		}
    46  
    47  		if err == nil && !condition(response) {
    48  			err = errors.New(errorMessage)
    49  		}
    50  		return err
    51  	})
    52  	return response, err
    53  }
    54  
    55  type MessengerSignalsHandlerMock struct {
    56  	MessengerSignalsHandler
    57  
    58  	responseChan                 chan *MessengerResponse
    59  	communityFoundChan           chan *communities.Community
    60  	wakuBackedUpDataResponseChan chan *wakusync.WakuBackedUpDataResponse
    61  }
    62  
    63  func (m *MessengerSignalsHandlerMock) SendWakuFetchingBackupProgress(response *wakusync.WakuBackedUpDataResponse) {
    64  	m.wakuBackedUpDataResponseChan <- response
    65  }
    66  func (m *MessengerSignalsHandlerMock) SendWakuBackedUpProfile(*wakusync.WakuBackedUpDataResponse)  {}
    67  func (m *MessengerSignalsHandlerMock) SendWakuBackedUpSettings(*wakusync.WakuBackedUpDataResponse) {}
    68  func (m *MessengerSignalsHandlerMock) SendWakuBackedUpKeypair(*wakusync.WakuBackedUpDataResponse)  {}
    69  func (m *MessengerSignalsHandlerMock) SendWakuBackedUpWatchOnlyAccount(*wakusync.WakuBackedUpDataResponse) {
    70  }
    71  
    72  func (m *MessengerSignalsHandlerMock) BackupPerformed(uint64)                    {}
    73  func (m *MessengerSignalsHandlerMock) HistoryArchivesProtocolEnabled()           {}
    74  func (m *MessengerSignalsHandlerMock) HistoryArchivesProtocolDisabled()          {}
    75  func (m *MessengerSignalsHandlerMock) CreatingHistoryArchives(string)            {}
    76  func (m *MessengerSignalsHandlerMock) NoHistoryArchivesCreated(string, int, int) {}
    77  func (m *MessengerSignalsHandlerMock) HistoryArchivesCreated(string, int, int)   {}
    78  func (m *MessengerSignalsHandlerMock) HistoryArchivesSeeding(string)             {}
    79  func (m *MessengerSignalsHandlerMock) HistoryArchivesUnseeded(string)            {}
    80  func (m *MessengerSignalsHandlerMock) HistoryArchiveDownloaded(string, int, int) {}
    81  func (m *MessengerSignalsHandlerMock) DownloadingHistoryArchivesStarted(string)  {}
    82  func (m *MessengerSignalsHandlerMock) DownloadingHistoryArchivesFinished(string) {}
    83  func (m *MessengerSignalsHandlerMock) ImportingHistoryArchiveMessages(string)    {}
    84  
    85  func (m *MessengerSignalsHandlerMock) MessengerResponse(response *MessengerResponse) {
    86  	// Non-blocking send
    87  	select {
    88  	case m.responseChan <- response:
    89  	default:
    90  	}
    91  }
    92  
    93  func (m *MessengerSignalsHandlerMock) MessageDelivered(chatID string, messageID string) {}
    94  
    95  func (m *MessengerSignalsHandlerMock) CommunityInfoFound(community *communities.Community) {
    96  	select {
    97  	case m.communityFoundChan <- community:
    98  	default:
    99  	}
   100  }
   101  
   102  func WaitOnSignaledSendWakuFetchingBackupProgress(m *Messenger, condition func(*wakusync.WakuBackedUpDataResponse) bool, errorMessage string) (*wakusync.WakuBackedUpDataResponse, error) {
   103  	interval := 500 * time.Millisecond
   104  	timeoutChan := time.After(10 * time.Second)
   105  
   106  	if m.config.messengerSignalsHandler != nil {
   107  		return nil, errors.New("messengerSignalsHandler already provided/mocked")
   108  	}
   109  
   110  	responseChan := make(chan *wakusync.WakuBackedUpDataResponse, 1000)
   111  	m.config.messengerSignalsHandler = &MessengerSignalsHandlerMock{
   112  		wakuBackedUpDataResponseChan: responseChan,
   113  	}
   114  
   115  	defer func() {
   116  		m.config.messengerSignalsHandler = nil
   117  	}()
   118  
   119  	for {
   120  		_, err := m.RetrieveAll()
   121  		if err != nil {
   122  			return nil, err
   123  		}
   124  
   125  		select {
   126  		case r := <-responseChan:
   127  			if condition(r) {
   128  				return r, nil
   129  			}
   130  		case <-timeoutChan:
   131  			return nil, errors.New("timed out: " + errorMessage)
   132  		default: // No immediate response, rest & loop back to retrieve again
   133  			time.Sleep(interval)
   134  		}
   135  	}
   136  }
   137  
   138  func WaitOnSignaledMessengerResponse(m *Messenger, condition func(*MessengerResponse) bool, errorMessage string) (*MessengerResponse, error) {
   139  	interval := 500 * time.Millisecond
   140  	timeoutChan := time.After(10 * time.Second)
   141  
   142  	if m.config.messengerSignalsHandler != nil {
   143  		return nil, errors.New("messengerSignalsHandler already provided/mocked")
   144  	}
   145  
   146  	responseChan := make(chan *MessengerResponse, 64)
   147  	m.config.messengerSignalsHandler = &MessengerSignalsHandlerMock{
   148  		responseChan: responseChan,
   149  	}
   150  
   151  	defer func() {
   152  		m.config.messengerSignalsHandler = nil
   153  	}()
   154  
   155  	for {
   156  		_, err := m.RetrieveAll()
   157  		if err != nil {
   158  			return nil, err
   159  		}
   160  
   161  		select {
   162  		case r := <-responseChan:
   163  			if condition(r) {
   164  				return r, nil
   165  			}
   166  
   167  		case <-timeoutChan:
   168  			return nil, errors.New(errorMessage)
   169  
   170  		default: // No immediate response, rest & loop back to retrieve again
   171  			time.Sleep(interval)
   172  		}
   173  	}
   174  }
   175  
   176  func WaitOnSignaledCommunityFound(m *Messenger, action func(), condition func(community *communities.Community) bool, timeout time.Duration, errorMessage string) error {
   177  	timeoutChan := time.After(timeout)
   178  
   179  	if m.config.messengerSignalsHandler != nil {
   180  		return errors.New("messengerSignalsHandler already provided/mocked")
   181  	}
   182  
   183  	communityFoundChan := make(chan *communities.Community, 1)
   184  	m.config.messengerSignalsHandler = &MessengerSignalsHandlerMock{
   185  		communityFoundChan: communityFoundChan,
   186  	}
   187  
   188  	defer func() {
   189  		m.config.messengerSignalsHandler = nil
   190  	}()
   191  
   192  	// Call the action after setting up the mock
   193  	action()
   194  
   195  	// Wait for condition after
   196  	for {
   197  		select {
   198  		case c := <-communityFoundChan:
   199  			if condition(c) {
   200  				return nil
   201  			}
   202  		case <-timeoutChan:
   203  			return errors.New("timed out: " + errorMessage)
   204  		}
   205  	}
   206  }
   207  
   208  func WaitForConnectionStatus(s *suite.Suite, waku *waku2.Waku, action func() bool) {
   209  	subscription := waku.SubscribeToConnStatusChanges()
   210  	defer subscription.Unsubscribe()
   211  
   212  	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
   213  	defer cancel()
   214  
   215  	// Action should return the desired online status
   216  	wantedOnline := action()
   217  
   218  	for {
   219  		select {
   220  		case status := <-subscription.C:
   221  			if status.IsOnline == wantedOnline {
   222  				return
   223  			}
   224  		case <-ctx.Done():
   225  			s.Require().Fail(fmt.Sprintf("timeout waiting for waku connection status '%t'", wantedOnline))
   226  			return
   227  		}
   228  	}
   229  }
   230  
   231  func hasAllPeers(m map[peer.ID]types.WakuV2Peer, checkSlice peer.IDSlice) bool {
   232  	for _, check := range checkSlice {
   233  		if _, ok := m[check]; !ok {
   234  			return false
   235  		}
   236  	}
   237  	return true
   238  }
   239  
   240  func WaitForPeersConnected(s *suite.Suite, waku *waku2.Waku, action func() peer.IDSlice) {
   241  	subscription := waku.SubscribeToConnStatusChanges()
   242  	defer subscription.Unsubscribe()
   243  
   244  	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
   245  	defer cancel()
   246  
   247  	// Action should return the desired peer ID
   248  	peerIDs := action()
   249  	if hasAllPeers(waku.Peers(), peerIDs) {
   250  		return
   251  	}
   252  
   253  	for {
   254  		select {
   255  		case status := <-subscription.C:
   256  			if hasAllPeers(status.Peers, peerIDs) {
   257  				// Give some time for p2p events, otherwise might look like peer is available, but fail to send a message.
   258  				time.Sleep(100 * time.Millisecond)
   259  				return
   260  			}
   261  		case <-ctx.Done():
   262  			s.Require().Fail(fmt.Sprintf("timeout waiting for peers connected '%+v'", peerIDs))
   263  			return
   264  		}
   265  	}
   266  }
   267  
   268  func FindFirstByContentType(messages []*common.Message, contentType protobuf.ChatMessage_ContentType) *common.Message {
   269  	for _, message := range messages {
   270  		if message.ContentType == contentType {
   271  			return message
   272  		}
   273  	}
   274  	return nil
   275  }
   276  
   277  func PairDevices(s *suite.Suite, device1, device2 *Messenger) {
   278  	// Send pairing data
   279  	response, err := device1.SendPairInstallation(context.Background(), nil)
   280  	s.Require().NoError(err)
   281  	s.Require().NotNil(response)
   282  	s.Len(response.Chats(), 1)
   283  	s.False(response.Chats()[0].Active)
   284  
   285  	i, ok := device1.allInstallations.Load(device1.installationID)
   286  	s.Require().True(ok)
   287  
   288  	// Wait for the message to reach its destination
   289  	response, err = WaitOnMessengerResponse(
   290  		device2,
   291  		func(r *MessengerResponse) bool {
   292  			for _, installation := range r.Installations() {
   293  				if installation.ID == device1.installationID {
   294  					return installation.InstallationMetadata != nil &&
   295  						i.InstallationMetadata.Name == installation.InstallationMetadata.Name &&
   296  						i.InstallationMetadata.DeviceType == installation.InstallationMetadata.DeviceType
   297  				}
   298  			}
   299  			return false
   300  
   301  		},
   302  		"installation not received",
   303  	)
   304  	s.Require().NoError(err)
   305  	s.Require().NotNil(response)
   306  
   307  	// Ensure installation is enabled
   308  	err = device2.EnableInstallation(device1.installationID)
   309  	s.Require().NoError(err)
   310  }
   311  
   312  func SetSettingsAndWaitForChange(s *suite.Suite, messenger *Messenger, timeout time.Duration,
   313  	actionCallback func(), eventCallback func(*SelfContactChangeEvent) bool) {
   314  
   315  	allEventsReceived := false
   316  	channel := messenger.SubscribeToSelfContactChanges()
   317  	wg := sync.WaitGroup{}
   318  	wg.Add(1)
   319  
   320  	go func() {
   321  		defer wg.Done()
   322  		for !allEventsReceived {
   323  			select {
   324  			case event := <-channel:
   325  				allEventsReceived = eventCallback(event)
   326  			case <-time.After(timeout):
   327  				return
   328  			}
   329  		}
   330  	}()
   331  
   332  	actionCallback()
   333  
   334  	wg.Wait()
   335  
   336  	s.Require().True(allEventsReceived)
   337  }
   338  
   339  func SetIdentityImagesAndWaitForChange(s *suite.Suite, messenger *Messenger, timeout time.Duration, actionCallback func()) {
   340  	channel := messenger.SubscribeToSelfContactChanges()
   341  	ok := false
   342  	wg := sync.WaitGroup{}
   343  	wg.Add(1)
   344  
   345  	go func() {
   346  		defer wg.Done()
   347  		select {
   348  		case event := <-channel:
   349  			if event.ImagesChanged {
   350  				ok = true
   351  			}
   352  		case <-time.After(timeout):
   353  			return
   354  		}
   355  	}()
   356  
   357  	actionCallback()
   358  
   359  	wg.Wait()
   360  
   361  	s.Require().True(ok)
   362  }
   363  
   364  func WaitForAvailableStoreNode(s *suite.Suite, m *Messenger, timeout time.Duration) {
   365  	available := m.waitForAvailableStoreNode(timeout)
   366  	s.Require().True(available)
   367  }
   368  
   369  func TearDownMessenger(s *suite.Suite, m *Messenger) {
   370  	if m == nil {
   371  		return
   372  	}
   373  	s.Require().NoError(m.Shutdown())
   374  	if m.database != nil {
   375  		s.Require().NoError(m.database.Close())
   376  	}
   377  	if m.multiAccounts != nil {
   378  		s.Require().NoError(m.multiAccounts.Close())
   379  	}
   380  }
   381  
   382  func randomInt(length int) int {
   383  	max := big.NewInt(int64(length))
   384  	value, err := rand.Int(rand.Reader, max)
   385  	if err != nil {
   386  		panic(err)
   387  	}
   388  	return int(value.Int64())
   389  }
   390  
   391  func randomString(length int, runes []rune) string {
   392  	out := make([]rune, length)
   393  	for i := range out {
   394  		out[i] = runes[randomInt(len(runes))] // nolint: gosec
   395  	}
   396  	return string(out)
   397  }
   398  
   399  func RandomLettersString(length int) string {
   400  	return randomString(length, letterRunes)
   401  }
   402  
   403  func RandomColor() string {
   404  	return "#" + randomString(6, hexRunes)
   405  }
   406  
   407  func RandomCommunityTags(count int) []string {
   408  	availableTagsCount := requests.AvailableTagsCount()
   409  
   410  	if count > availableTagsCount {
   411  		count = availableTagsCount
   412  	}
   413  
   414  	//source := mathRand.New(mathRand.NewSource(time.Now().UnixNano()))
   415  	indices := mathRand.Perm(availableTagsCount)
   416  	shuffled := make([]string, count)
   417  	for i := 0; i < count; i++ {
   418  		shuffled[i] = requests.TagByIndex(uint32(indices[i]))
   419  	}
   420  
   421  	return shuffled
   422  }
   423  
   424  func RandomBytes(length int) []byte {
   425  	out := make([]byte, length)
   426  	_, err := rand.Read(out)
   427  	if err != nil {
   428  		panic(err)
   429  	}
   430  	return out
   431  }
   432  
   433  func DummyProfileShowcasePreferences(withCollectibles bool) *identity.ProfileShowcasePreferences {
   434  	preferences := &identity.ProfileShowcasePreferences{
   435  		Communities: []*identity.ProfileShowcaseCommunityPreference{
   436  			{
   437  				CommunityID:        "0x254254546768764565565",
   438  				ShowcaseVisibility: identity.ProfileShowcaseVisibilityEveryone,
   439  			},
   440  			{
   441  				CommunityID:        "0x865241434343432412343",
   442  				ShowcaseVisibility: identity.ProfileShowcaseVisibilityContacts,
   443  			},
   444  		},
   445  		Accounts: []*identity.ProfileShowcaseAccountPreference{
   446  			{
   447  				Address:            "0x0000000000000000000000000033433445133423",
   448  				ShowcaseVisibility: identity.ProfileShowcaseVisibilityEveryone,
   449  				Order:              0,
   450  			},
   451  			{
   452  				Address:            "0x0000000000000000000000000032433445133424",
   453  				ShowcaseVisibility: identity.ProfileShowcaseVisibilityContacts,
   454  				Order:              1,
   455  			},
   456  		},
   457  		VerifiedTokens: []*identity.ProfileShowcaseVerifiedTokenPreference{
   458  			{
   459  				Symbol:             "ETH",
   460  				ShowcaseVisibility: identity.ProfileShowcaseVisibilityEveryone,
   461  				Order:              1,
   462  			},
   463  			{
   464  				Symbol:             "DAI",
   465  				ShowcaseVisibility: identity.ProfileShowcaseVisibilityIDVerifiedContacts,
   466  				Order:              2,
   467  			},
   468  			{
   469  				Symbol:             "SNT",
   470  				ShowcaseVisibility: identity.ProfileShowcaseVisibilityNoOne,
   471  				Order:              3,
   472  			},
   473  		},
   474  		UnverifiedTokens: []*identity.ProfileShowcaseUnverifiedTokenPreference{
   475  			{
   476  				ContractAddress:    "0x454525452023452",
   477  				ChainID:            11155111,
   478  				ShowcaseVisibility: identity.ProfileShowcaseVisibilityEveryone,
   479  				Order:              0,
   480  			},
   481  			{
   482  				ContractAddress:    "0x12312323323233",
   483  				ChainID:            1,
   484  				ShowcaseVisibility: identity.ProfileShowcaseVisibilityContacts,
   485  				Order:              1,
   486  			},
   487  		},
   488  		SocialLinks: []*identity.ProfileShowcaseSocialLinkPreference{
   489  			&identity.ProfileShowcaseSocialLinkPreference{
   490  				Text:               identity.TwitterID,
   491  				URL:                "https://twitter.com/ethstatus",
   492  				ShowcaseVisibility: identity.ProfileShowcaseVisibilityEveryone,
   493  				Order:              1,
   494  			},
   495  			&identity.ProfileShowcaseSocialLinkPreference{
   496  				Text:               identity.TwitterID,
   497  				URL:                "https://twitter.com/StatusIMBlog",
   498  				ShowcaseVisibility: identity.ProfileShowcaseVisibilityIDVerifiedContacts,
   499  				Order:              2,
   500  			},
   501  			&identity.ProfileShowcaseSocialLinkPreference{
   502  				Text:               identity.GithubID,
   503  				URL:                "https://github.com/status-im",
   504  				ShowcaseVisibility: identity.ProfileShowcaseVisibilityContacts,
   505  				Order:              3,
   506  			},
   507  		},
   508  	}
   509  
   510  	if withCollectibles {
   511  		preferences.Collectibles = []*identity.ProfileShowcaseCollectiblePreference{
   512  			{
   513  				ContractAddress:    "0x12378534257568678487683576",
   514  				ChainID:            1,
   515  				TokenID:            "12321389592999903",
   516  				ShowcaseVisibility: identity.ProfileShowcaseVisibilityEveryone,
   517  				Order:              0,
   518  			},
   519  		}
   520  	} else {
   521  		preferences.Collectibles = []*identity.ProfileShowcaseCollectiblePreference{}
   522  	}
   523  
   524  	return preferences
   525  }