status-im/status-go

View on GitHub
protocol/messenger_sync_saved_addresses_test.go

Summary

Maintainability
A
0 mins
Test Coverage
package protocol

import (
    "context"
    "crypto/ecdsa"
    "reflect"
    "sort"
    "testing"

    "github.com/stretchr/testify/suite"
    "go.uber.org/zap"

    "github.com/ethereum/go-ethereum/common"
    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/multiaccounts/settings"
    "github.com/status-im/status-go/protocol/encryption/multidevice"
    "github.com/status-im/status-go/protocol/tt"
    "github.com/status-im/status-go/services/wallet"
    "github.com/status-im/status-go/waku"
)

func TestMessengerSyncSavedAddressesSuite(t *testing.T) {
    suite.Run(t, new(MessengerSyncSavedAddressesSuite))
}

type MessengerSyncSavedAddressesSuite struct {
    suite.Suite
    main       *Messenger // main instance of Messenger paired with `other`
    other      *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 *MessengerSyncSavedAddressesSuite) 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.main = s.newMessenger(s.logger.Named("main"))
    s.privateKey = s.main.identity

    var err error
    // Create new device and add main account to
    s.other, err = newMessengerWithKey(s.shh, s.main.identity, s.logger.Named("other"), nil)
    s.Require().NoError(err)

    // Pair devices (main and other)
    imOther := &multidevice.InstallationMetadata{
        Name:       "other-device",
        DeviceType: "other-device-type",
    }
    err = s.other.SetInstallationMetadata(s.other.installationID, imOther)
    s.Require().NoError(err)
    response, err := s.other.SendPairInstallation(context.Background(), nil)
    s.Require().NoError(err)
    s.Require().NotNil(response)

    // Wait for the message to reach its destination
    _, err = WaitOnMessengerResponse(
        s.main,
        func(r *MessengerResponse) bool { return len(r.Installations()) > 0 },
        "installation not received",
    )
    s.Require().NoError(err)

    err = s.main.EnableInstallation(s.other.installationID)
    s.Require().NoError(err)
}

func (s *MessengerSyncSavedAddressesSuite) TearDownTest() {
    TearDownMessenger(&s.Suite, s.main)
}

func (s *MessengerSyncSavedAddressesSuite) newMessenger(logger *zap.Logger) *Messenger {
    privateKey, err := crypto.GenerateKey()
    s.Require().NoError(err)

    messenger, err := newMessengerWithKey(s.shh, privateKey, logger, nil)
    s.Require().NoError(err)

    return messenger
}

// Helpers duplicate of wallet test. Could not import it from saved_addresses_test.go

func contains[T comparable](container []T, element T, isEqual func(T, T) bool) bool {
    for _, e := range container {
        if isEqual(e, element) {
            return true
        }
    }
    return false
}

func haveSameElements[T comparable](a []T, b []T, isEqual func(T, T) bool) bool {
    if len(a) != len(b) {
        return false
    }
    for _, v := range a {
        if !contains(b, v, isEqual) {
            return false
        }
    }
    return true
}

func savedAddressDataIsEqual(a, b *wallet.SavedAddress) bool {
    return a.Address == b.Address && a.IsTest == b.IsTest && a.Name == b.Name &&
        a.ENSName == b.ENSName && a.ChainShortNames == b.ChainShortNames && a.ColorID == b.ColorID
}

func (s *MessengerSyncSavedAddressesSuite) TestSyncExistingSavedAddresses() {
    var isTestChain1 bool = false
    var isTestChain2 bool = true
    var testAddress1 = common.Address{1}

    // Add saved addresses to main device
    sa1 := wallet.SavedAddress{
        Address: testAddress1,
        Name:    "TestC1A1",
        IsTest:  isTestChain1,
    }
    sa2 := wallet.SavedAddress{
        ENSName: "test.ens.eth",
        Name:    "TestC2A1",
        IsTest:  isTestChain2,
    }

    err := s.main.UpsertSavedAddress(context.Background(), sa1)
    s.Require().NoError(err)
    err = s.main.UpsertSavedAddress(context.Background(), sa2)
    s.Require().NoError(err)

    // Wait and check that saved addresses are synced
    _, err = WaitOnMessengerResponse(
        s.other,
        func(r *MessengerResponse) bool {
            if len(r.SavedAddresses()) == 2 {
                sas := r.SavedAddresses()
                s.Require().True(haveSameElements([]*wallet.SavedAddress{&sa1, &sa2}, []*wallet.SavedAddress{sas[0], sas[1]}, savedAddressDataIsEqual))
                return true
            }
            return false
        },
        "expected to receive two changes",
    )
    s.Require().NoError(err)

    savedAddresses, err := s.other.savedAddressesManager.GetSavedAddresses()
    s.Require().NoError(err)
    s.Require().Equal(2, len(savedAddresses))
    s.Require().True(haveSameElements([]*wallet.SavedAddress{&sa1, &sa2}, savedAddresses, savedAddressDataIsEqual))
}

func (s *MessengerSyncSavedAddressesSuite) TestSyncSavedAddresses() {
    var isTestChain1 bool = true
    var testAddress1 = common.Address{1}

    // Add saved addresses to main device
    sa1 := wallet.SavedAddress{
        Address: testAddress1,
        Name:    "TestC1A1",
        IsTest:  isTestChain1,
    }
    sa2 := wallet.SavedAddress{
        ENSName: "test.ens.eth",
        Name:    "TestC1A2",
        IsTest:  isTestChain1,
    }

    err := s.main.UpsertSavedAddress(context.Background(), sa1)
    s.Require().NoError(err)
    err = s.main.UpsertSavedAddress(context.Background(), sa2)
    s.Require().NoError(err)

    // Wait and check that saved addresses are synced
    _, err = WaitOnMessengerResponse(
        s.other,
        func(r *MessengerResponse) bool {
            if len(r.SavedAddresses()) == 2 {
                sas := r.SavedAddresses()
                s.Require().True(haveSameElements([]*wallet.SavedAddress{&sa1, &sa2}, []*wallet.SavedAddress{sas[0], sas[1]}, savedAddressDataIsEqual))
                return true
            }
            return false
        },
        "expected to receive two changes",
    )
    s.Require().NoError(err)

    savedAddresses, err := s.other.savedAddressesManager.GetSavedAddresses()
    s.Require().NoError(err)
    s.Require().Equal(2, len(savedAddresses))
    s.Require().True(haveSameElements([]*wallet.SavedAddress{&sa1, &sa2}, savedAddresses, savedAddressDataIsEqual))
}

func (s *MessengerSyncSavedAddressesSuite) requireSavedAddressesEqual(a, b []*wallet.SavedAddress) {
    sort.Slice(a, func(i, j int) bool {
        return a[i].Address.Hex() < a[j].Address.Hex()
    })
    sort.Slice(b, func(i, j int) bool {
        return b[i].Address.Hex() < b[j].Address.Hex()
    })
    s.Require().True(reflect.DeepEqual(a, b))
}

func (s *MessengerSyncSavedAddressesSuite) testSyncDeletesOfSavedAddresses(testModeMain bool, testModeOther bool) {

    sa1 := &wallet.SavedAddress{
        Address: common.Address{1},
        Name:    "TestC1A1",
        IsTest:  true,
    }
    sa2 := &wallet.SavedAddress{
        Address: common.Address{2},
        Name:    "TestC1A2",
        IsTest:  false,
    }

    err := s.main.settings.SaveSettingField(settings.TestNetworksEnabled, testModeMain)
    s.Require().NoError(err)
    err = s.other.settings.SaveSettingField(settings.TestNetworksEnabled, testModeOther)
    s.Require().NoError(err)

    // Add saved addresses to main device
    err = s.main.UpsertSavedAddress(context.Background(), *sa1)
    s.Require().NoError(err)
    err = s.main.UpsertSavedAddress(context.Background(), *sa2)
    s.Require().NoError(err)

    // Wait and check that saved addresses are synced
    {
        response, err := WaitOnMessengerResponse(
            s.other,
            func(r *MessengerResponse) bool {
                return len(r.SavedAddresses()) == 2
            },
            "expected to receive two changes",
        )
        s.Require().NoError(err)

        otherSavedAddresses := response.SavedAddresses()
        s.Require().Len(otherSavedAddresses, 2)

        // Check that the UpdateClock was bumped
        s.Require().GreaterOrEqual(otherSavedAddresses[0].CreatedAt, int64(0))
        s.Require().GreaterOrEqual(otherSavedAddresses[1].CreatedAt, int64(0))
        s.Require().Greater(otherSavedAddresses[0].UpdateClock, uint64(0))
        s.Require().Greater(otherSavedAddresses[1].UpdateClock, uint64(0))

        // Reset the UpdateClock to 0 for comparison
        otherSavedAddresses[0].CreatedAt = 0
        otherSavedAddresses[1].CreatedAt = 0
        otherSavedAddresses[0].UpdateClock = 0
        otherSavedAddresses[1].UpdateClock = 0
        s.requireSavedAddressesEqual([]*wallet.SavedAddress{sa1, sa2}, otherSavedAddresses)

        // Ensure the messenger actually has the saved addresses, not just the response
        savedAddresses, err := s.other.savedAddressesManager.GetSavedAddresses()
        s.Require().NoError(err)
        s.Require().Len(savedAddresses, 2)

        // Reset the UpdateClock to 0 for comparison
        savedAddresses[0].CreatedAt = 0
        savedAddresses[1].CreatedAt = 0
        savedAddresses[0].UpdateClock = 0
        savedAddresses[1].UpdateClock = 0
        s.requireSavedAddressesEqual([]*wallet.SavedAddress{sa1, sa2}, savedAddresses)
    }

    // Delete saved address 1 (test mode = true) and sync with the other device
    {
        err = s.main.DeleteSavedAddress(context.Background(), sa1.Address, sa1.IsTest)
        s.Require().NoError(err)

        // Ensure the removal
        savedAddresses, err := s.main.savedAddressesManager.GetSavedAddresses()
        s.Require().NoError(err)
        s.Require().Len(savedAddresses, 1)
        sa2.CreatedAt = savedAddresses[0].CreatedAt     // force same value
        sa2.UpdateClock = savedAddresses[0].UpdateClock // force same value
        s.Require().Equal(sa2, savedAddresses[0])

        // Wait other device to receive the change
        response, err := WaitOnMessengerResponse(
            s.other,
            func(r *MessengerResponse) bool {
                return len(r.SavedAddresses()) == 1
            },
            "saved address removal wasn't received",
        )
        s.Require().NoError(err)

        // We expect the delete event to report address, ens, isTest
        sa := response.SavedAddresses()[0]
        s.Require().Equal(sa1.Address, sa.Address)
        s.Require().Equal(sa1.IsTest, sa.IsTest)
        s.Require().Equal("", sa.Name)

        // Ensure the messenger doesn't return the removed address
        savedAddresses, err = s.other.savedAddressesManager.GetSavedAddresses()
        s.Require().NoError(err)
        s.Require().Len(savedAddresses, 1)
        savedAddresses[0].CreatedAt = sa2.CreatedAt // force same value
        s.Require().Equal(sa2, savedAddresses[0])
    }

    // Delete saved address 2 (test mode = false) and sync with the other device
    {
        err = s.main.DeleteSavedAddress(context.Background(), sa2.Address, sa2.IsTest)
        s.Require().NoError(err)

        // Ensure the removal
        savedAddresses, err := s.main.savedAddressesManager.GetSavedAddresses()
        s.Require().NoError(err)
        s.Require().Len(savedAddresses, 0)

        // Wait other device to receive the change
        response, err := WaitOnMessengerResponse(
            s.other,
            func(r *MessengerResponse) bool {
                return len(r.SavedAddresses()) == 1
            },
            "expected to receive one change",
        )
        s.Require().NoError(err)

        sa := response.SavedAddresses()[0]
        // We expect the deleted event to report address, ens, isTest
        s.Require().Equal(sa2.Address, sa.Address)
        s.Require().Equal(sa2.IsTest, sa.IsTest)
        s.Require().Equal("", sa.Name)

        savedAddresses, err = s.other.savedAddressesManager.GetSavedAddresses()
        s.Require().NoError(err)
        s.Require().Len(savedAddresses, 0)
    }
}

func (s *MessengerSyncSavedAddressesSuite) TestSyncDeletesOfSavedAddresses() {
    testCases := []struct {
        Name          string
        TestModeMain  bool
        TestModeOther bool
    }{
        {
            Name:          "same test mode on both devices",
            TestModeMain:  true,
            TestModeOther: true,
        },
        {
            Name:          "different test mode on devices",
            TestModeMain:  true,
            TestModeOther: false,
        },
    }

    for _, tc := range testCases {
        s.Run(tc.Name, func() {
            s.testSyncDeletesOfSavedAddresses(tc.TestModeMain, tc.TestModeOther)
        })
    }
}