status-im/status-go

View on GitHub
services/wallet/saved_addresses_test.go

Summary

Maintainability
A
0 mins
Test Coverage
package wallet

import (
    "strconv"
    "testing"
    "time"

    "github.com/stretchr/testify/require"

    "github.com/ethereum/go-ethereum/common"
    multiAccCommon "github.com/status-im/status-go/multiaccounts/common"
    "github.com/status-im/status-go/t/helpers"
    "github.com/status-im/status-go/walletdatabase"
)

const (
    ensMember int = iota
    isTestMember
    addressMember
)

func setupTestSavedAddressesDB(t *testing.T) (*SavedAddressesManager, func()) {
    db, err := helpers.SetupTestMemorySQLDB(walletdatabase.DbInitializer{})
    require.NoError(t, err)

    return &SavedAddressesManager{db}, func() {
        require.NoError(t, db.Close())
    }
}

func TestSavedAddressesAdd(t *testing.T) {
    manager, stop := setupTestSavedAddressesDB(t)
    defer stop()

    rst, err := manager.GetRawSavedAddresses()
    require.NoError(t, err)
    require.Nil(t, rst)

    sa := SavedAddress{
        Address:         common.Address{1},
        Name:            "Zilliqa",
        ChainShortNames: "eth:arb1:",
        ENSName:         "test.stateofus.eth",
        ColorID:         multiAccCommon.CustomizationColorGreen,
        IsTest:          false,
    }

    err = manager.UpdateMetadataAndUpsertSavedAddress(sa)
    require.NoError(t, err)

    rst, err = manager.GetRawSavedAddresses()
    require.NoError(t, err)
    require.Equal(t, 1, len(rst))
    require.Equal(t, sa.Address, rst[0].Address)
    require.Equal(t, sa.Name, rst[0].Name)
    require.Equal(t, sa.ChainShortNames, rst[0].ChainShortNames)
    require.Equal(t, sa.ENSName, rst[0].ENSName)
    require.Equal(t, sa.ColorID, rst[0].ColorID)
    require.Equal(t, sa.IsTest, rst[0].IsTest)
}

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 {
    for _, v := range a {
        if !contains(b, v, isEqual) {
            return false
        }
    }
    return true
}

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

func TestSavedAddressesMetadata(t *testing.T) {
    manager, stop := setupTestSavedAddressesDB(t)
    defer stop()

    savedAddresses, err := manager.GetRawSavedAddresses()
    require.NoError(t, err)
    require.Nil(t, savedAddresses)

    // Add raw saved addresses
    sa1 := SavedAddress{
        Address: common.Address{1},
        Name:    "Raw",
        savedAddressMeta: savedAddressMeta{
            UpdateClock: 234,
        },
        ChainShortNames: "eth:arb1:",
        ENSName:         "test.stateofus.eth",
        ColorID:         multiAccCommon.CustomizationColorGreen,
        IsTest:          false,
    }

    err = manager.upsertSavedAddress(sa1, nil)
    require.NoError(t, err)

    dbSavedAddresses, err := manager.GetRawSavedAddresses()
    require.NoError(t, err)
    require.Equal(t, 1, len(dbSavedAddresses))
    require.Equal(t, sa1.Address, dbSavedAddresses[0].Address)

    // Add simple saved address without sync metadata
    sa2 := SavedAddress{
        Address: common.Address{2},
        Name:    "Simple",
        ColorID: multiAccCommon.CustomizationColorBlue,
        IsTest:  false,
        savedAddressMeta: savedAddressMeta{
            UpdateClock: 42,
        },
    }

    err = manager.UpdateMetadataAndUpsertSavedAddress(sa2)
    require.NoError(t, err)

    dbSavedAddresses, err = manager.GetRawSavedAddresses()
    require.NoError(t, err)
    require.Equal(t, 2, len(dbSavedAddresses))
    // The order is not guaranteed check raw entry to decide
    rawIndex := 0
    simpleIndex := 1
    if dbSavedAddresses[0].Address == sa1.Address {
        rawIndex = 1
        simpleIndex = 0
    }
    require.Equal(t, sa1.Address, dbSavedAddresses[simpleIndex].Address)
    require.Equal(t, sa2.Address, dbSavedAddresses[rawIndex].Address)
    require.Equal(t, sa2.Name, dbSavedAddresses[rawIndex].Name)
    require.Equal(t, sa2.ColorID, dbSavedAddresses[rawIndex].ColorID)
    require.Equal(t, sa2.IsTest, dbSavedAddresses[rawIndex].IsTest)

    // Check the default values
    require.False(t, dbSavedAddresses[rawIndex].Removed)
    require.Equal(t, dbSavedAddresses[rawIndex].UpdateClock, sa2.UpdateClock)
    require.Greater(t, dbSavedAddresses[rawIndex].UpdateClock, uint64(0))

    sa2Older := sa2
    sa2Older.IsTest = false
    sa2Older.UpdateClock = dbSavedAddresses[rawIndex].UpdateClock - 1

    sa2Newer := sa2
    sa2Newer.IsTest = false
    sa2Newer.UpdateClock = dbSavedAddresses[rawIndex].UpdateClock + 1

    // Try to add an older entry
    updated := false
    updated, err = manager.AddSavedAddressIfNewerUpdate(sa2Older)
    require.NoError(t, err)
    require.False(t, updated)

    dbSavedAddresses, err = manager.GetRawSavedAddresses()
    require.NoError(t, err)

    rawIndex = 0
    simpleIndex = 1
    if dbSavedAddresses[0].Address == sa1.Address {
        rawIndex = 1
        simpleIndex = 0
    }

    require.Equal(t, 2, len(dbSavedAddresses))
    require.True(t, haveSameElements([]*SavedAddress{&sa1, &sa2}, dbSavedAddresses, savedAddressDataIsEqual))
    require.Equal(t, sa1.savedAddressMeta, dbSavedAddresses[simpleIndex].savedAddressMeta)

    // Try to update sa2 with a newer entry
    updatedClock := dbSavedAddresses[rawIndex].UpdateClock + 1
    updated, err = manager.AddSavedAddressIfNewerUpdate(sa2Newer)
    require.NoError(t, err)
    require.True(t, updated)

    dbSavedAddresses, err = manager.GetRawSavedAddresses()
    require.NoError(t, err)

    require.Equal(t, 2, len(dbSavedAddresses))
    require.True(t, haveSameElements([]*SavedAddress{&sa1, &sa2Newer}, dbSavedAddresses, savedAddressDataIsEqual))
    require.Equal(t, updatedClock, dbSavedAddresses[rawIndex].UpdateClock)

    // Try to delete the sa2 newer entry
    updatedDeleteClock := updatedClock + 1
    updated, err = manager.DeleteSavedAddress(sa2Newer.Address, sa2Newer.IsTest, updatedDeleteClock)
    require.NoError(t, err)
    require.True(t, updated)

    dbSavedAddresses, err = manager.GetRawSavedAddresses()
    require.NoError(t, err)

    require.Equal(t, 2, len(dbSavedAddresses))
    require.True(t, dbSavedAddresses[rawIndex].Removed)

    // Check that deleted entry is not returned with the regular API (non-raw)
    dbSavedAddresses, err = manager.GetSavedAddresses()
    require.NoError(t, err)
    require.Equal(t, 1, len(dbSavedAddresses))
}

func TestSavedAddressesCleanSoftDeletes(t *testing.T) {
    manager, stop := setupTestSavedAddressesDB(t)
    defer stop()

    firstTimestamp := 10
    for i := 0; i < 5; i++ {
        sa := SavedAddress{
            Address: common.Address{byte(i)},
            Name:    "Test" + strconv.Itoa(i),
            ColorID: multiAccCommon.CustomizationColorGreen,
            Removed: true,
            savedAddressMeta: savedAddressMeta{
                UpdateClock: uint64(firstTimestamp + i),
            },
        }

        err := manager.upsertSavedAddress(sa, nil)
        require.NoError(t, err)
    }

    err := manager.DeleteSoftRemovedSavedAddresses(uint64(firstTimestamp + 3))
    require.NoError(t, err)

    dbSavedAddresses, err := manager.GetRawSavedAddresses()
    require.NoError(t, err)
    require.Equal(t, 2, len(dbSavedAddresses))
    require.True(t, haveSameElements([]uint64{dbSavedAddresses[0].UpdateClock,
        dbSavedAddresses[1].UpdateClock}, []uint64{uint64(firstTimestamp + 3), uint64(firstTimestamp + 4)},
        func(a, b uint64) bool {
            return a == b
        },
    ))
}

func TestSavedAddressesGet(t *testing.T) {
    manager, stop := setupTestSavedAddressesDB(t)
    defer stop()

    sa := SavedAddress{
        Address: common.Address{1},
        ENSName: "test.ens.eth",
        ColorID: multiAccCommon.CustomizationColorGreen,
        IsTest:  false,
        Removed: true,
    }

    err := manager.upsertSavedAddress(sa, nil)
    require.NoError(t, err)

    dbSavedAddresses, err := manager.GetRawSavedAddresses()
    require.NoError(t, err)
    require.Equal(t, 1, len(dbSavedAddresses))

    require.True(t, savedAddressDataIsEqual(&sa, dbSavedAddresses[0]))

    dbSavedAddresses, err = manager.GetSavedAddresses()
    require.NoError(t, err)
    require.Equal(t, 0, len(dbSavedAddresses))
}

func TestSavedAddressesDelete(t *testing.T) {
    manager, stop := setupTestSavedAddressesDB(t)
    defer stop()

    sa0 := SavedAddress{
        Address: common.Address{1},
        IsTest:  false,
    }

    err := manager.upsertSavedAddress(sa0, nil)
    require.NoError(t, err)

    rst, err := manager.GetRawSavedAddresses()
    require.NoError(t, err)
    require.Equal(t, 1, len(rst))

    require.True(t, savedAddressDataIsEqual(&sa0, rst[0]))

    // Modify IsTest flag, insert
    sa1 := sa0
    sa1.IsTest = !sa1.IsTest

    err = manager.upsertSavedAddress(sa1, nil)
    require.NoError(t, err)

    // Delete s0, test that only s1 is left
    updateClock := uint64(time.Now().Unix())
    _, err = manager.DeleteSavedAddress(sa0.Address, sa0.IsTest, updateClock)
    require.NoError(t, err)

    rst, err = manager.GetSavedAddresses()
    require.NoError(t, err)
    require.Equal(t, 1, len(rst))
    require.True(t, savedAddressDataIsEqual(&sa1, rst[0]))

    // Test that we still have both addresses
    rst, err = manager.GetRawSavedAddresses()
    require.NoError(t, err)
    require.Equal(t, 2, len(rst))

    // Delete s0 one more time with the same timestamp
    deleted, err := manager.DeleteSavedAddress(sa0.Address, sa0.IsTest, updateClock)
    require.NoError(t, err)
    require.False(t, deleted)
}

func testInsertSameAddressWithOneChange(t *testing.T, member int) {
    manager, stop := setupTestSavedAddressesDB(t)
    defer stop()

    // Insert one address
    sa := SavedAddress{
        Address: common.Address{1},
        ENSName: "test.ens.eth",
        IsTest:  true,
    }

    err := manager.upsertSavedAddress(sa, nil)
    require.NoError(t, err)

    rst, err := manager.GetRawSavedAddresses()
    require.NoError(t, err)
    require.Equal(t, 1, len(rst))

    require.True(t, savedAddressDataIsEqual(&sa, rst[0]))

    sa2 := sa

    if member == isTestMember {
        sa2.IsTest = !sa2.IsTest
    } else if member == addressMember {
        sa2.Address = common.Address{7}
    } else if member == ensMember {
        sa2.ENSName += "_"
    } else {
        t.Error("Unsupported member change. Please add it to the list")
    }

    err = manager.upsertSavedAddress(sa2, nil)
    require.NoError(t, err)

    rst, err = manager.GetRawSavedAddresses()
    require.NoError(t, err)
    require.Equal(t, 2, len(rst))

    // The order of records returned by GetRawSavedAddresses is not
    // guaranteed to be the same as insertions, so swap indices if first record does not match
    firstIndex := 0
    secondIndex := 1
    if rst[firstIndex].Address == sa.Address {
        firstIndex = 1
        secondIndex = 0
    }
    require.True(t, savedAddressDataIsEqual(&sa, rst[firstIndex]))
    require.True(t, savedAddressDataIsEqual(&sa2, rst[secondIndex]))
}

func TestSavedAddressesAddDifferentIsTest(t *testing.T) {
    testInsertSameAddressWithOneChange(t, isTestMember)
}

func TestSavedAddressesAddSame(t *testing.T) {
    manager, stop := setupTestSavedAddressesDB(t)
    defer stop()

    // Insert one address
    sa := SavedAddress{
        Address: common.Address{1},
        ENSName: "test.ens.eth",
        IsTest:  true,
    }

    err := manager.upsertSavedAddress(sa, nil)
    require.NoError(t, err)

    rst, err := manager.GetRawSavedAddresses()
    require.NoError(t, err)
    require.Equal(t, 1, len(rst))

    require.True(t, savedAddressDataIsEqual(&sa, rst[0]))

    sa2 := sa
    err = manager.upsertSavedAddress(sa2, nil)
    require.NoError(t, err)

    rst, err = manager.GetRawSavedAddresses()
    require.NoError(t, err)
    require.Equal(t, 1, len(rst))
}