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 }