status-im/status-go

View on GitHub
protocol/push_notification_test.go

Summary

Maintainability
B
5 hrs
Test Coverage
package protocol

import (
    "context"
    "crypto/ecdsa"
    "encoding/hex"
    "errors"
    "testing"

    "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/common"
    "github.com/status-im/status-go/protocol/communities"
    "github.com/status-im/status-go/protocol/protobuf"
    "github.com/status-im/status-go/protocol/pushnotificationclient"
    "github.com/status-im/status-go/protocol/pushnotificationserver"
    "github.com/status-im/status-go/protocol/requests"
    "github.com/status-im/status-go/protocol/tt"
    "github.com/status-im/status-go/waku"
)

const (
    bob1DeviceToken = "token-1"
    bob2DeviceToken = "token-2"
    testAPNTopic    = "topic"
)

func TestMessengerPushNotificationSuite(t *testing.T) {
    suite.Run(t, new(MessengerPushNotificationSuite))
}

type MessengerPushNotificationSuite 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 *MessengerPushNotificationSuite) 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
}

func (s *MessengerPushNotificationSuite) TearDownTest() {
    TearDownMessenger(&s.Suite, s.m)
    _ = s.logger.Sync()
}

func (s *MessengerPushNotificationSuite) newMessenger(shh types.Waku) *Messenger {
    privateKey, err := crypto.GenerateKey()
    s.Require().NoError(err)

    messenger, err := newMessengerWithKey(s.shh, privateKey, s.logger, []Option{WithPushNotifications()})
    s.Require().NoError(err)
    return messenger
}

func (s *MessengerPushNotificationSuite) newPushNotificationServer(shh types.Waku, privateKey *ecdsa.PrivateKey) *Messenger {

    serverConfig := &pushnotificationserver.Config{
        Enabled:  true,
        Logger:   s.logger,
        Identity: privateKey,
    }

    options := []Option{
        WithPushNotificationServerConfig(serverConfig),
    }
    messenger, err := newMessengerWithKey(shh, privateKey, s.logger, options)
    s.Require().NoError(err)
    return messenger
}

func (s *MessengerPushNotificationSuite) TestReceivePushNotification() {

    bob1 := s.m
    bob2, err := newMessengerWithKey(s.shh, s.m.identity, s.logger, []Option{WithPushNotifications()})
    s.Require().NoError(err)
    defer TearDownMessenger(&s.Suite, bob2)

    serverKey, err := crypto.GenerateKey()
    s.Require().NoError(err)
    server := s.newPushNotificationServer(s.shh, serverKey)
    defer TearDownMessenger(&s.Suite, server)

    alice := s.newMessenger(s.shh)
    defer TearDownMessenger(&s.Suite, alice)
    s.Require().NoError(alice.EnableSendingPushNotifications())
    bobInstallationIDs := []string{bob1.installationID, bob2.installationID}

    // Register bob1
    err = bob1.AddPushNotificationsServer(context.Background(), &server.identity.PublicKey, pushnotificationclient.ServerTypeCustom)
    s.Require().NoError(err)

    err = bob1.RegisterForPushNotifications(context.Background(), bob1DeviceToken, testAPNTopic, protobuf.PushNotificationRegistration_APN_TOKEN)

    // Pull servers  and check we registered
    err = tt.RetryWithBackOff(func() error {
        _, err = server.RetrieveAll()
        if err != nil {
            return err
        }
        _, err = bob1.RetrieveAll()
        if err != nil {
            return err
        }
        registered, err := bob1.RegisteredForPushNotifications()
        if err != nil {
            return err
        }
        if !registered {
            return errors.New("not registered")
        }
        bobServers, err := bob1.GetPushNotificationsServers()
        if err != nil {
            return err
        }

        if len(bobServers) == 0 {
            return errors.New("not registered")
        }

        return nil
    })
    // Make sure we receive it
    s.Require().NoError(err)
    bob1Servers, err := bob1.GetPushNotificationsServers()
    s.Require().NoError(err)

    // Register bob2
    err = bob2.AddPushNotificationsServer(context.Background(), &server.identity.PublicKey, pushnotificationclient.ServerTypeCustom)
    s.Require().NoError(err)

    err = bob2.RegisterForPushNotifications(context.Background(), bob2DeviceToken, testAPNTopic, protobuf.PushNotificationRegistration_APN_TOKEN)
    s.Require().NoError(err)

    err = tt.RetryWithBackOff(func() error {
        _, err = server.RetrieveAll()
        if err != nil {
            return err
        }
        _, err = bob2.RetrieveAll()
        if err != nil {
            return err
        }

        registered, err := bob2.RegisteredForPushNotifications()
        if err != nil {
            return err
        }
        if !registered {
            return errors.New("not registered")
        }
        bobServers, err := bob2.GetPushNotificationsServers()
        if err != nil {
            return err
        }

        if len(bobServers) == 0 {
            return errors.New("not registered")
        }

        return nil
    })
    // Make sure we receive it
    s.Require().NoError(err)
    bob2Servers, err := bob2.GetPushNotificationsServers()
    s.Require().NoError(err)

    // Create one to one chat & send message
    pkString := hex.EncodeToString(crypto.FromECDSAPub(&s.m.identity.PublicKey))
    chat := CreateOneToOneChat(pkString, &s.m.identity.PublicKey, alice.transport)
    s.Require().NoError(alice.SaveChat(chat))
    inputMessage := buildTestMessage(*chat)
    response, err := alice.SendChatMessage(context.Background(), inputMessage)
    s.Require().NoError(err)
    messageIDString := response.Messages()[0].ID
    messageID, err := hex.DecodeString(messageIDString[2:])
    s.Require().NoError(err)

    infoMap := make(map[string]*pushnotificationclient.PushNotificationInfo)
    err = tt.RetryWithBackOff(func() error {
        _, err = server.RetrieveAll()
        if err != nil {
            return err
        }
        _, err = alice.RetrieveAll()
        if err != nil {
            return err
        }

        info, err := alice.pushNotificationClient.GetPushNotificationInfo(&bob1.identity.PublicKey, bobInstallationIDs)
        if err != nil {
            return err
        }
        for _, i := range info {
            infoMap[i.AccessToken] = i
        }

        // Check we have replies for both bob1 and bob2
        if len(infoMap) != 2 {
            return errors.New("info not fetched")
        }
        return nil

    })

    s.Require().Len(infoMap, 2)

    // Check we have replies for both bob1 and bob2
    var bob1Info, bob2Info *pushnotificationclient.PushNotificationInfo

    bob1Info = infoMap[bob1Servers[0].AccessToken]
    bob2Info = infoMap[bob2Servers[0].AccessToken]

    s.Require().NotNil(bob1Info)
    s.Require().Equal(bob1.installationID, bob1Info.InstallationID)
    s.Require().Equal(bob1Servers[0].AccessToken, bob1Info.AccessToken)
    s.Require().Equal(&bob1.identity.PublicKey, bob1Info.PublicKey)

    s.Require().NotNil(bob2Info)
    s.Require().Equal(bob2.installationID, bob2Info.InstallationID)
    s.Require().Equal(bob2Servers[0].AccessToken, bob2Info.AccessToken)
    s.Require().Equal(&bob2.identity.PublicKey, bob2Info.PublicKey)

    retrievedNotificationInfo, err := alice.pushNotificationClient.GetPushNotificationInfo(&bob1.identity.PublicKey, bobInstallationIDs)

    s.Require().NoError(err)
    s.Require().NotNil(retrievedNotificationInfo)
    s.Require().Len(retrievedNotificationInfo, 2)

    var sentNotification *pushnotificationclient.SentNotification
    err = tt.RetryWithBackOff(func() error {
        _, err = server.RetrieveAll()
        if err != nil {
            return err
        }
        _, err = alice.RetrieveAll()
        if err != nil {
            return err
        }
        sentNotification, err = alice.pushNotificationClient.GetSentNotification(common.HashPublicKey(&bob1.identity.PublicKey), bob1.installationID, messageID)
        if err != nil {
            return err
        }
        if sentNotification == nil {
            return errors.New("sent notification not found")
        }
        if !sentNotification.Success {
            return errors.New("sent notification not successul")
        }
        return nil
    })
    s.Require().NoError(err)
}

func (s *MessengerPushNotificationSuite) TestReceivePushNotificationFromContactOnly() {

    bob := s.m

    serverKey, err := crypto.GenerateKey()
    s.Require().NoError(err)
    server := s.newPushNotificationServer(s.shh, serverKey)
    defer TearDownMessenger(&s.Suite, server)

    alice := s.newMessenger(s.shh)
    defer TearDownMessenger(&s.Suite, alice)
    s.Require().NoError(alice.EnableSendingPushNotifications())
    bobInstallationIDs := []string{bob.installationID}

    // Register bob
    err = bob.AddPushNotificationsServer(context.Background(), &server.identity.PublicKey, pushnotificationclient.ServerTypeCustom)
    s.Require().NoError(err)

    // Add alice has a contact
    aliceContact := &Contact{
        ID:                       types.EncodeHex(crypto.FromECDSAPub(&alice.identity.PublicKey)),
        EnsName:                  "Some Contact",
        ContactRequestLocalState: ContactRequestStateSent,
    }

    _, err = bob.AddContact(context.Background(), &requests.AddContact{ID: aliceContact.ID})
    s.Require().NoError(err)

    // Enable from contacts only
    err = bob.EnablePushNotificationsFromContactsOnly()
    s.Require().NoError(err)

    err = bob.RegisterForPushNotifications(context.Background(), bob1DeviceToken, testAPNTopic, protobuf.PushNotificationRegistration_APN_TOKEN)
    s.Require().NoError(err)

    // Pull servers  and check we registered
    err = tt.RetryWithBackOff(func() error {
        _, err = server.RetrieveAll()
        if err != nil {
            return err
        }
        _, err = bob.RetrieveAll()
        if err != nil {
            return err
        }
        registered, err := bob.RegisteredForPushNotifications()
        if err != nil {
            return err
        }
        if !registered {
            return errors.New("not registered")
        }
        bobServers, err := bob.GetPushNotificationsServers()
        if err != nil {
            return err
        }

        if len(bobServers) == 0 {
            return errors.New("not registered")
        }

        return nil
    })
    // Make sure we receive it
    s.Require().NoError(err)
    bobServers, err := bob.GetPushNotificationsServers()
    s.Require().NoError(err)

    // Create one to one chat & send message
    pkString := hex.EncodeToString(crypto.FromECDSAPub(&s.m.identity.PublicKey))
    chat := CreateOneToOneChat(pkString, &s.m.identity.PublicKey, alice.transport)
    s.Require().NoError(alice.SaveChat(chat))
    inputMessage := buildTestMessage(*chat)
    response, err := alice.SendChatMessage(context.Background(), inputMessage)
    s.Require().NoError(err)
    messageIDString := response.Messages()[0].ID
    messageID, err := hex.DecodeString(messageIDString[2:])
    s.Require().NoError(err)

    var info []*pushnotificationclient.PushNotificationInfo
    err = tt.RetryWithBackOff(func() error {
        _, err = server.RetrieveAll()
        if err != nil {
            return err
        }
        _, err = alice.RetrieveAll()
        if err != nil {
            return err
        }

        info, err = alice.pushNotificationClient.GetPushNotificationInfo(&bob.identity.PublicKey, bobInstallationIDs)
        if err != nil {
            return err
        }
        // Check we have replies for bob
        if len(info) != 1 {
            return errors.New("info not fetched")
        }
        return nil

    })
    s.Require().NoError(err)

    s.Require().NotNil(info)
    s.Require().Equal(bob.installationID, info[0].InstallationID)
    s.Require().Equal(bobServers[0].AccessToken, info[0].AccessToken)
    s.Require().Equal(&bob.identity.PublicKey, info[0].PublicKey)

    retrievedNotificationInfo, err := alice.pushNotificationClient.GetPushNotificationInfo(&bob.identity.PublicKey, bobInstallationIDs)
    s.Require().NoError(err)
    s.Require().NotNil(retrievedNotificationInfo)
    s.Require().Len(retrievedNotificationInfo, 1)

    var sentNotification *pushnotificationclient.SentNotification
    err = tt.RetryWithBackOff(func() error {
        _, err = server.RetrieveAll()
        if err != nil {
            return err
        }
        _, err = alice.RetrieveAll()
        if err != nil {
            return err
        }
        sentNotification, err = alice.pushNotificationClient.GetSentNotification(common.HashPublicKey(&bob.identity.PublicKey), bob.installationID, messageID)
        if err != nil {
            return err
        }
        if sentNotification == nil {
            return errors.New("sent notification not found")
        }
        if !sentNotification.Success {
            return errors.New("sent notification not successul")
        }
        return nil
    })

    s.Require().NoError(err)
}

func (s *MessengerPushNotificationSuite) TestReceivePushNotificationRetries() {

    bob := s.m

    serverKey, err := crypto.GenerateKey()
    s.Require().NoError(err)
    server := s.newPushNotificationServer(s.shh, serverKey)
    defer TearDownMessenger(&s.Suite, server)

    alice := s.newMessenger(s.shh)
    // another contact to invalidate the token
    frank := s.newMessenger(s.shh)
    defer TearDownMessenger(&s.Suite, frank)
    defer TearDownMessenger(&s.Suite, alice)

    s.Require().NoError(alice.EnableSendingPushNotifications())
    bobInstallationIDs := []string{bob.installationID}

    // Register bob
    err = bob.AddPushNotificationsServer(context.Background(), &server.identity.PublicKey, pushnotificationclient.ServerTypeCustom)
    s.Require().NoError(err)

    // Add alice has a contact
    aliceContact := &Contact{
        ID:                       types.EncodeHex(crypto.FromECDSAPub(&alice.identity.PublicKey)),
        EnsName:                  "Some Contact",
        ContactRequestLocalState: ContactRequestStateSent,
    }

    _, err = bob.AddContact(context.Background(), &requests.AddContact{ID: aliceContact.ID})
    s.Require().NoError(err)

    // Add frank has a contact
    frankContact := &Contact{
        ID:                       types.EncodeHex(crypto.FromECDSAPub(&frank.identity.PublicKey)),
        EnsName:                  "Some Contact",
        ContactRequestLocalState: ContactRequestStateSent,
    }

    _, err = bob.AddContact(context.Background(), &requests.AddContact{ID: frankContact.ID})
    s.Require().NoError(err)

    // Enable from contacts only
    err = bob.EnablePushNotificationsFromContactsOnly()
    s.Require().NoError(err)

    err = bob.RegisterForPushNotifications(context.Background(), bob1DeviceToken, testAPNTopic, protobuf.PushNotificationRegistration_APN_TOKEN)
    s.Require().NoError(err)

    // Pull servers  and check we registered
    err = tt.RetryWithBackOff(func() error {
        _, err = server.RetrieveAll()
        if err != nil {
            return err
        }
        _, err = bob.RetrieveAll()
        if err != nil {
            return err
        }
        registered, err := bob.RegisteredForPushNotifications()
        if err != nil {
            return err
        }
        if !registered {
            return errors.New("not registered")
        }
        bobServers, err := bob.GetPushNotificationsServers()
        if err != nil {
            return err
        }

        if len(bobServers) == 0 {
            return errors.New("not registered")
        }

        return nil
    })
    // Make sure we receive it
    s.Require().NoError(err)
    bobServers, err := bob.GetPushNotificationsServers()
    s.Require().NoError(err)

    // Create one to one chat & send message
    pkString := hex.EncodeToString(crypto.FromECDSAPub(&s.m.identity.PublicKey))
    chat := CreateOneToOneChat(pkString, &s.m.identity.PublicKey, alice.transport)
    s.Require().NoError(alice.SaveChat(chat))
    inputMessage := buildTestMessage(*chat)
    _, err = alice.SendChatMessage(context.Background(), inputMessage)
    s.Require().NoError(err)

    // We check that alice retrieves the info from the server
    var info []*pushnotificationclient.PushNotificationInfo
    err = tt.RetryWithBackOff(func() error {
        _, err = server.RetrieveAll()
        if err != nil {
            return err
        }
        _, err = alice.RetrieveAll()
        if err != nil {
            return err
        }

        info, err = alice.pushNotificationClient.GetPushNotificationInfo(&bob.identity.PublicKey, bobInstallationIDs)
        if err != nil {
            return err
        }
        // Check we have replies for bob
        if len(info) != 1 {
            return errors.New("info not fetched")
        }
        return nil

    })
    s.Require().NoError(err)

    s.Require().NotNil(info)
    s.Require().Equal(bob.installationID, info[0].InstallationID)
    s.Require().Equal(bobServers[0].AccessToken, info[0].AccessToken)
    s.Require().Equal(&bob.identity.PublicKey, info[0].PublicKey)

    // The message has been sent, but not received, now we remove a contact so that the token is invalidated
    frankContact = &Contact{
        ID:      types.EncodeHex(crypto.FromECDSAPub(&frank.identity.PublicKey)),
        EnsName: "Some Contact",
    }
    _, err = bob.RemoveContact(context.Background(), frankContact.ID)
    s.Require().NoError(err)

    // Re-registration should be triggered, pull from server and bob to check we are correctly registered
    // Pull servers  and check we registered
    err = tt.RetryWithBackOff(func() error {
        _, err = server.RetrieveAll()
        if err != nil {
            return err
        }
        _, err = bob.RetrieveAll()
        if err != nil {
            return err
        }
        registered, err := bob.RegisteredForPushNotifications()
        if err != nil {
            return err
        }
        if !registered {
            return errors.New("not registered")
        }
        return nil
    })

    newBobServers, err := bob.GetPushNotificationsServers()
    s.Require().NoError(err)
    // Make sure access token is not the same
    s.Require().NotEqual(newBobServers[0].AccessToken, bobServers[0].AccessToken)

    // Send another message, here the token will not be valid
    inputMessage = buildTestMessage(*chat)
    response, err := alice.SendChatMessage(context.Background(), inputMessage)
    s.Require().NoError(err)
    messageIDString := response.Messages()[0].ID
    messageID, err := hex.DecodeString(messageIDString[2:])
    s.Require().NoError(err)

    err = tt.RetryWithBackOff(func() error {
        _, err = server.RetrieveAll()
        if err != nil {
            return err
        }
        _, err = alice.RetrieveAll()
        if err != nil {
            return err
        }

        info, err = alice.pushNotificationClient.GetPushNotificationInfo(&bob.identity.PublicKey, bobInstallationIDs)
        if err != nil {
            return err
        }
        // Check we have replies for bob
        if len(info) != 1 {
            return errors.New("info not fetched")
        }
        if newBobServers[0].AccessToken != info[0].AccessToken {
            return errors.New("still using the old access token")
        }
        return nil

    })
    s.Require().NoError(err)

    s.Require().NotNil(info)
    s.Require().Equal(bob.installationID, info[0].InstallationID)
    s.Require().Equal(newBobServers[0].AccessToken, info[0].AccessToken)
    s.Require().Equal(&bob.identity.PublicKey, info[0].PublicKey)

    retrievedNotificationInfo, err := alice.pushNotificationClient.GetPushNotificationInfo(&bob.identity.PublicKey, bobInstallationIDs)
    s.Require().NoError(err)
    s.Require().NotNil(retrievedNotificationInfo)
    s.Require().Len(retrievedNotificationInfo, 1)

    var sentNotification *pushnotificationclient.SentNotification
    err = tt.RetryWithBackOff(func() error {
        _, err = server.RetrieveAll()
        if err != nil {
            return err
        }
        _, err = alice.RetrieveAll()
        if err != nil {
            return err
        }
        sentNotification, err = alice.pushNotificationClient.GetSentNotification(common.HashPublicKey(&bob.identity.PublicKey), bob.installationID, messageID)
        if err != nil {
            return err
        }
        if sentNotification == nil {
            return errors.New("sent notification not found")
        }
        if !sentNotification.Success {
            return errors.New("sent notification not successul")
        }
        return nil
    })

    s.Require().NoError(err)
}

func (s *MessengerPushNotificationSuite) TestContactCode() {

    bob1 := s.m

    serverKey, err := crypto.GenerateKey()
    s.Require().NoError(err)
    server := s.newPushNotificationServer(s.shh, serverKey)
    defer TearDownMessenger(&s.Suite, server)

    alice := s.newMessenger(s.shh)
    s.Require().NoError(err)
    defer TearDownMessenger(&s.Suite, alice)
    s.Require().NoError(alice.EnableSendingPushNotifications())

    // Register bob1
    err = bob1.AddPushNotificationsServer(context.Background(), &server.identity.PublicKey, pushnotificationclient.ServerTypeCustom)
    s.Require().NoError(err)

    err = bob1.RegisterForPushNotifications(context.Background(), bob1DeviceToken, testAPNTopic, protobuf.PushNotificationRegistration_APN_TOKEN)

    // Pull servers  and check we registered
    err = tt.RetryWithBackOff(func() error {
        _, err = server.RetrieveAll()
        if err != nil {
            return err
        }
        _, err = bob1.RetrieveAll()
        if err != nil {
            return err
        }
        registered, err := bob1.RegisteredForPushNotifications()
        if err != nil {
            return err
        }
        if !registered {
            return errors.New("not registered")
        }
        bobServers, err := bob1.GetPushNotificationsServers()
        if err != nil {
            return err
        }

        if len(bobServers) == 0 {
            return errors.New("not registered")
        }

        return nil
    })
    // Make sure we receive it
    s.Require().NoError(err)

    contactCodeAdvertisement, err := bob1.buildContactCodeAdvertisement()
    s.Require().NoError(err)
    s.Require().NotNil(contactCodeAdvertisement)

    s.Require().NoError(alice.pushNotificationClient.HandleContactCodeAdvertisement(&bob1.identity.PublicKey, contactCodeAdvertisement))

}

func (s *MessengerPushNotificationSuite) TestReceivePushNotificationMention() {

    bob := s.m

    serverKey, err := crypto.GenerateKey()
    s.Require().NoError(err)
    server := s.newPushNotificationServer(s.shh, serverKey)
    defer TearDownMessenger(&s.Suite, server)

    alice := s.newMessenger(s.shh)
    s.Require().NoError(err)
    defer TearDownMessenger(&s.Suite, alice)
    s.Require().NoError(alice.EnableSendingPushNotifications())
    bobInstallationIDs := []string{bob.installationID}

    // Create public chat and join for both alice and bob
    chat := CreatePublicChat("status", s.m.transport)
    err = bob.SaveChat(chat)
    s.Require().NoError(err)

    _, err = bob.Join(chat)
    s.Require().NoError(err)

    err = alice.SaveChat(chat)
    s.Require().NoError(err)

    _, err = alice.Join(chat)
    s.Require().NoError(err)

    // Register bob
    err = bob.AddPushNotificationsServer(context.Background(), &server.identity.PublicKey, pushnotificationclient.ServerTypeCustom)
    s.Require().NoError(err)

    err = bob.RegisterForPushNotifications(context.Background(), bob1DeviceToken, testAPNTopic, protobuf.PushNotificationRegistration_APN_TOKEN)

    // Pull servers  and check we registered
    err = tt.RetryWithBackOff(func() error {
        _, err = server.RetrieveAll()
        if err != nil {
            return err
        }
        _, err = bob.RetrieveAll()
        if err != nil {
            return err
        }
        registered, err := bob.RegisteredForPushNotifications()
        if err != nil {
            return err
        }
        if !registered {
            return errors.New("not registered")
        }

        bobServers, err := bob.GetPushNotificationsServers()
        if err != nil {
            return err
        }

        if len(bobServers) == 0 {
            return errors.New("not registered")
        }

        return nil
    })
    // Make sure we receive it
    s.Require().NoError(err)
    bobServers, err := bob.GetPushNotificationsServers()
    s.Require().NoError(err)

    inputMessage := buildTestMessage(*chat)
    // message contains a mention
    inputMessage.Text = "Hey @" + types.EncodeHex(crypto.FromECDSAPub(&bob.identity.PublicKey))
    response, err := alice.SendChatMessage(context.Background(), inputMessage)
    s.Require().NoError(err)
    messageIDString := response.Messages()[0].ID
    messageID, err := hex.DecodeString(messageIDString[2:])
    s.Require().NoError(err)

    var bobInfo []*pushnotificationclient.PushNotificationInfo
    err = tt.RetryWithBackOff(func() error {
        _, err = server.RetrieveAll()
        if err != nil {
            return err
        }
        _, err = alice.RetrieveAll()
        if err != nil {
            return err
        }

        bobInfo, err = alice.pushNotificationClient.GetPushNotificationInfo(&bob.identity.PublicKey, bobInstallationIDs)
        if err != nil {
            return err
        }
        // Check we have replies for bob
        if len(bobInfo) != 1 {
            return errors.New("info not fetched")
        }
        return nil

    })

    s.Require().NoError(err)

    s.Require().NotEmpty(bobInfo)
    s.Require().Equal(bob.installationID, bobInfo[0].InstallationID)
    s.Require().Equal(bobServers[0].AccessToken, bobInfo[0].AccessToken)
    s.Require().Equal(&bob.identity.PublicKey, bobInfo[0].PublicKey)

    retrievedNotificationInfo, err := alice.pushNotificationClient.GetPushNotificationInfo(&bob.identity.PublicKey, bobInstallationIDs)

    s.Require().NoError(err)
    s.Require().NotNil(retrievedNotificationInfo)
    s.Require().Len(retrievedNotificationInfo, 1)

    var sentNotification *pushnotificationclient.SentNotification
    err = tt.RetryWithBackOff(func() error {
        _, err = server.RetrieveAll()
        if err != nil {
            return err
        }
        _, err = alice.RetrieveAll()
        if err != nil {
            return err
        }
        sentNotification, err = alice.pushNotificationClient.GetSentNotification(common.HashPublicKey(&bob.identity.PublicKey), bob.installationID, messageID)
        if err != nil {
            return err
        }
        if sentNotification == nil {
            return errors.New("sent notification not found")
        }
        if !sentNotification.Success {
            return errors.New("sent notification not successul")
        }
        return nil
    })
    s.Require().NoError(err)
}

func (s *MessengerPushNotificationSuite) TestReceivePushNotificationCommunityRequest() {

    bob := s.m

    serverKey, err := crypto.GenerateKey()
    s.Require().NoError(err)
    server := s.newPushNotificationServer(s.shh, serverKey)
    defer TearDownMessenger(&s.Suite, server)

    alice := s.newMessenger(s.shh)
    s.Require().NoError(err)
    defer TearDownMessenger(&s.Suite, alice)
    s.Require().NoError(alice.EnableSendingPushNotifications())

    // Register bob
    err = bob.AddPushNotificationsServer(context.Background(), &server.identity.PublicKey, pushnotificationclient.ServerTypeCustom)
    s.Require().NoError(err)

    err = bob.RegisterForPushNotifications(context.Background(), bob1DeviceToken, testAPNTopic, protobuf.PushNotificationRegistration_APN_TOKEN)

    // Pull servers  and check we registered
    err = tt.RetryWithBackOff(func() error {
        _, err = server.RetrieveAll()
        if err != nil {
            return err
        }
        _, err = bob.RetrieveAll()
        if err != nil {
            return err
        }
        registered, err := bob.RegisteredForPushNotifications()
        if err != nil {
            return err
        }
        if !registered {
            return errors.New("not registered")
        }

        bobServers, err := bob.GetPushNotificationsServers()
        if err != nil {
            return err
        }

        if len(bobServers) == 0 {
            return errors.New("not registered")
        }

        return nil
    })
    // Make sure we receive it
    s.Require().NoError(err)
    _, err = bob.GetPushNotificationsServers()
    s.Require().NoError(err)

    description := &requests.CreateCommunity{
        Membership:  protobuf.CommunityPermissions_MANUAL_ACCEPT,
        Name:        "status",
        Color:       "#ffffff",
        Description: "status community description",
    }

    response, err := bob.CreateCommunity(description, true)
    s.Require().NoError(err)
    s.Require().NotNil(response)
    s.Require().Len(response.Communities(), 1)
    community := response.Communities()[0]

    // Send a community message
    chat := CreateOneToOneChat(common.PubkeyToHex(&alice.identity.PublicKey), &alice.identity.PublicKey, alice.transport)

    inputMessage := common.NewMessage()
    inputMessage.ChatId = chat.ID
    inputMessage.Text = "some text"
    inputMessage.CommunityID = community.IDString()

    err = bob.SaveChat(chat)
    s.NoError(err)
    _, err = bob.SendChatMessage(context.Background(), inputMessage)
    s.NoError(err)

    // Pull message and make sure org is received
    err = tt.RetryWithBackOff(func() error {
        response, err = alice.RetrieveAll()
        if err != nil {
            return err
        }
        if len(response.Communities()) == 0 {
            return errors.New("community not received")
        }
        return nil
    })

    request := &requests.RequestToJoinCommunity{CommunityID: community.ID()}

    // We try to join the org
    response, err = alice.RequestToJoinCommunity(request)
    s.Require().NoError(err)
    s.Require().NotNil(response)
    s.Require().Len(response.RequestsToJoinCommunity(), 1)

    requestToJoin1 := response.RequestsToJoinCommunity()[0]
    s.Require().NotNil(requestToJoin1)
    s.Require().Equal(community.ID(), requestToJoin1.CommunityID)
    s.Require().True(requestToJoin1.Our)
    s.Require().NotEmpty(requestToJoin1.ID)
    s.Require().NotEmpty(requestToJoin1.Clock)
    s.Require().Equal(requestToJoin1.PublicKey, common.PubkeyToHex(&alice.identity.PublicKey))
    s.Require().Equal(communities.RequestToJoinStatePending, requestToJoin1.State)

    err = tt.RetryWithBackOff(func() error {
        _, err = server.RetrieveAll()
        if err != nil {
            return err
        }
        _, err = alice.RetrieveAll()
        if err != nil {
            return err
        }

        if server.pushNotificationServer.SentRequests != 1 {
            return errors.New("request not sent")
        }

        return nil

    })

    s.Require().NoError(err)

}

func (s *MessengerPushNotificationSuite) TestReceivePushNotificationPairedDevices() {

    bob1 := s.m
    bob2, err := newMessengerWithKey(s.shh, s.m.identity, s.logger, []Option{WithPushNotifications()})
    s.Require().NoError(err)
    defer TearDownMessenger(&s.Suite, bob2)

    serverKey, err := crypto.GenerateKey()
    s.Require().NoError(err)
    server := s.newPushNotificationServer(s.shh, serverKey)
    defer TearDownMessenger(&s.Suite, server)

    alice := s.newMessenger(s.shh)
    s.Require().NoError(err)
    defer TearDownMessenger(&s.Suite, alice)
    s.Require().NoError(alice.EnableSendingPushNotifications())
    bobInstallationIDs := []string{bob1.installationID, bob2.installationID}

    // Register bob1
    err = bob1.AddPushNotificationsServer(context.Background(), &server.identity.PublicKey, pushnotificationclient.ServerTypeCustom)
    s.Require().NoError(err)

    err = bob1.RegisterForPushNotifications(context.Background(), bob1DeviceToken, testAPNTopic, protobuf.PushNotificationRegistration_APN_TOKEN)

    // Pull servers  and check we registered
    err = tt.RetryWithBackOff(func() error {
        _, err = server.RetrieveAll()
        if err != nil {
            return err
        }
        _, err = bob1.RetrieveAll()
        if err != nil {
            return err
        }
        registered, err := bob1.RegisteredForPushNotifications()
        if err != nil {
            return err
        }
        if !registered {
            return errors.New("not registered")
        }
        bobServers, err := bob1.GetPushNotificationsServers()
        if err != nil {
            return err
        }

        if len(bobServers) == 0 {
            return errors.New("not registered")
        }

        return nil
    })
    // Make sure we receive it
    s.Require().NoError(err)
    bob1Servers, err := bob1.GetPushNotificationsServers()
    s.Require().NoError(err)

    // Register bob2
    err = bob2.AddPushNotificationsServer(context.Background(), &server.identity.PublicKey, pushnotificationclient.ServerTypeCustom)
    s.Require().NoError(err)

    err = bob2.RegisterForPushNotifications(context.Background(), bob2DeviceToken, testAPNTopic, protobuf.PushNotificationRegistration_APN_TOKEN)
    s.Require().NoError(err)

    err = tt.RetryWithBackOff(func() error {
        _, err = server.RetrieveAll()
        if err != nil {
            return err
        }
        _, err = bob2.RetrieveAll()
        if err != nil {
            return err
        }

        registered, err := bob2.RegisteredForPushNotifications()
        if err != nil {
            return err
        }
        if !registered {
            return errors.New("not registered")
        }
        bobServers, err := bob2.GetPushNotificationsServers()
        if err != nil {
            return err
        }

        if len(bobServers) == 0 {
            return errors.New("not registered")
        }

        return nil
    })
    // Make sure we receive it
    s.Require().NoError(err)
    bob2Servers, err := bob2.GetPushNotificationsServers()
    s.Require().NoError(err)

    // Create one to one chat & send message
    pkString := hex.EncodeToString(crypto.FromECDSAPub(&s.m.identity.PublicKey))
    chat := CreateOneToOneChat(pkString, &s.m.identity.PublicKey, alice.transport)
    s.Require().NoError(alice.SaveChat(chat))
    inputMessage := buildTestMessage(*chat)
    response, err := alice.SendChatMessage(context.Background(), inputMessage)
    s.Require().NoError(err)
    messageIDString := response.Messages()[0].ID
    messageID, err := hex.DecodeString(messageIDString[2:])
    s.Require().NoError(err)

    infoMap := make(map[string]*pushnotificationclient.PushNotificationInfo)
    err = tt.RetryWithBackOff(func() error {
        _, err = server.RetrieveAll()
        if err != nil {
            return err
        }
        _, err = alice.RetrieveAll()
        if err != nil {
            return err
        }

        info, err := alice.pushNotificationClient.GetPushNotificationInfo(&bob1.identity.PublicKey, bobInstallationIDs)
        if err != nil {
            return err
        }
        for _, i := range info {
            infoMap[i.AccessToken] = i
        }

        // Check we have replies for both bob1 and bob2
        if len(infoMap) != 2 {
            return errors.New("info not fetched")
        }
        return nil

    })

    s.Require().Len(infoMap, 2)

    // Check we have replies for both bob1 and bob2
    var bob1Info, bob2Info *pushnotificationclient.PushNotificationInfo

    bob1Info = infoMap[bob1Servers[0].AccessToken]
    bob2Info = infoMap[bob2Servers[0].AccessToken]

    s.Require().NotNil(bob1Info)
    s.Require().Equal(bob1.installationID, bob1Info.InstallationID)
    s.Require().Equal(bob1Servers[0].AccessToken, bob1Info.AccessToken)
    s.Require().Equal(&bob1.identity.PublicKey, bob1Info.PublicKey)

    s.Require().NotNil(bob2Info)
    s.Require().Equal(bob2.installationID, bob2Info.InstallationID)
    s.Require().Equal(bob2Servers[0].AccessToken, bob2Info.AccessToken)
    s.Require().Equal(&bob2.identity.PublicKey, bob2Info.PublicKey)

    retrievedNotificationInfo, err := alice.pushNotificationClient.GetPushNotificationInfo(&bob1.identity.PublicKey, bobInstallationIDs)

    s.Require().NoError(err)
    s.Require().NotNil(retrievedNotificationInfo)
    s.Require().Len(retrievedNotificationInfo, 2)

    var sentNotification *pushnotificationclient.SentNotification
    err = tt.RetryWithBackOff(func() error {
        _, err = server.RetrieveAll()
        if err != nil {
            return err
        }
        _, err = alice.RetrieveAll()
        if err != nil {
            return err
        }
        sentNotification, err = alice.pushNotificationClient.GetSentNotification(common.HashPublicKey(&bob1.identity.PublicKey), bob1.installationID, messageID)
        if err != nil {
            return err
        }
        if sentNotification == nil {
            return errors.New("sent notification not found")
        }
        if !sentNotification.Success {
            return errors.New("sent notification not successul")
        }
        return nil
    })
    s.Require().NoError(err)
}