package activity import ( "context" "database/sql" "encoding/json" "math/big" "testing" "time" eth "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/event" "github.com/status-im/status-go/services/wallet/bigint" "github.com/status-im/status-go/services/wallet/common" "github.com/status-im/status-go/services/wallet/thirdparty" "github.com/status-im/status-go/services/wallet/token" "github.com/status-im/status-go/services/wallet/transfer" "github.com/status-im/status-go/services/wallet/walletevent" "github.com/status-im/status-go/t/helpers" "github.com/status-im/status-go/walletdatabase" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" ) // mockCollectiblesManager implements the collectibles.ManagerInterface type mockCollectiblesManager struct { mock.Mock } func (m *mockCollectiblesManager) FetchAssetsByCollectibleUniqueID(ctx context.Context, uniqueIDs []thirdparty.CollectibleUniqueID, asyncFetch bool) ([]thirdparty.FullCollectibleData, error) { args := m.Called(uniqueIDs) res := args.Get(0) if res == nil { return nil, args.Error(1) } return res.([]thirdparty.FullCollectibleData), args.Error(1) } // mockTokenManager implements the token.ManagerInterface type mockTokenManager struct { mock.Mock } func (m *mockTokenManager) LookupTokenIdentity(chainID uint64, address eth.Address, native bool) *token.Token { args := m.Called(chainID, address, native) res := args.Get(0) if res == nil { return nil } return res.(*token.Token) } func (m *mockTokenManager) LookupToken(chainID *uint64, tokenSymbol string) (tkn *token.Token, isNative bool) { args := m.Called(chainID, tokenSymbol) return args.Get(0).(*token.Token), args.Bool(1) } func setupTestService(tb testing.TB) (service *Service, eventFeed *event.Feed, tokenMock *mockTokenManager, collectiblesMock *mockCollectiblesManager, close func()) { db, err := helpers.SetupTestMemorySQLDB(walletdatabase.DbInitializer{}) require.NoError(tb, err) eventFeed = new(event.Feed) tokenMock = &mockTokenManager{} collectiblesMock = &mockCollectiblesManager{} service = NewService(db, tokenMock, collectiblesMock, eventFeed) return service, eventFeed, tokenMock, collectiblesMock, func() { require.NoError(tb, db.Close()) } } type arg struct { chainID common.ChainID tokenAddressStr string tokenIDStr string tokenID *big.Int tokenAddress *eth.Address } // insertStubTransfersWithCollectibles will insert nil if tokenIDStr is empty func insertStubTransfersWithCollectibles(t *testing.T, db *sql.DB, args []arg) (fromAddresses, toAddresses []eth.Address) { trs, fromAddresses, toAddresses := transfer.GenerateTestTransfers(t, db, 0, len(args)) for i := range args { trs[i].ChainID = args[i].chainID if args[i].tokenIDStr == "" { args[i].tokenID = nil } else { args[i].tokenID = new(big.Int) args[i].tokenID.SetString(args[i].tokenIDStr, 0) } args[i].tokenAddress = new(eth.Address) *args[i].tokenAddress = eth.HexToAddress(args[i].tokenAddressStr) transfer.InsertTestTransferWithOptions(t, db, trs[i].To, &trs[i], &transfer.TestTransferOptions{ TokenAddress: *args[i].tokenAddress, TokenID: args[i].tokenID, }) } return fromAddresses, toAddresses } func TestService_UpdateCollectibleInfo(t *testing.T) { s, e, tM, c, close := setupTestService(t) defer close() args := []arg{ {5, "0xA2838FDA19EB6EED3F8B9EFF411D4CD7D2DE0313", "0x0D", nil, nil}, {5, "0xA2838FDA19EB6EED3F8B9EFF411D4CD7D2DE0313", "0x762AD3E4934E687F8701F24C7274E5209213FD6208FF952ACEB325D028866949", nil, nil}, {5, "0x3d6afaa395c31fcd391fe3d562e75fe9e8ec7e6a", "", nil, nil}, {5, "0xA2838FDA19EB6EED3F8B9EFF411D4CD7D2DE0313", "0x0F", nil, nil}, } fromAddresses, toAddresses := insertStubTransfersWithCollectibles(t, s.db, args) ch := make(chan walletevent.Event) sub := e.Subscribe(ch) // Expect one call for the fungible token tM.On("LookupTokenIdentity", uint64(5), eth.HexToAddress("0x3d6afaa395c31fcd391fe3d562e75fe9e8ec7e6a"), false).Return( &token.Token{ ChainID: 5, Address: eth.HexToAddress("0x3d6afaa395c31fcd391fe3d562e75fe9e8ec7e6a"), Symbol: "STT", }, false, ).Once() c.On("FetchAssetsByCollectibleUniqueID", []thirdparty.CollectibleUniqueID{ { ContractID: thirdparty.ContractID{ ChainID: args[3].chainID, Address: *args[3].tokenAddress}, TokenID: &bigint.BigInt{Int: args[3].tokenID}, }, { ContractID: thirdparty.ContractID{ ChainID: args[1].chainID, Address: *args[1].tokenAddress}, TokenID: &bigint.BigInt{Int: args[1].tokenID}, }, }).Return([]thirdparty.FullCollectibleData{ { CollectibleData: thirdparty.CollectibleData{ Name: "Test 2", ImageURL: "test://url/2"}, CollectionData: nil, }, { CollectibleData: thirdparty.CollectibleData{ Name: "Test 1", ImageURL: "test://url/1"}, CollectionData: nil, }, }, nil).Once() s.FilterActivityAsync(0, append(fromAddresses, toAddresses...), true, allNetworksFilter(), Filter{}, 0, 3) filterResponseCount := 0 var updates []EntryData for i := 0; i < 2; i++ { select { case res := <-ch: switch res.Type { case EventActivityFilteringDone: var payload FilterResponse err := json.Unmarshal([]byte(res.Message), &payload) require.NoError(t, err) require.Equal(t, ErrorCodeSuccess, payload.ErrorCode) require.Equal(t, 3, len(payload.Activities)) filterResponseCount++ case EventActivityFilteringUpdate: err := json.Unmarshal([]byte(res.Message), &updates) require.NoError(t, err) } case <-time.NewTimer(1 * time.Second).C: require.Fail(t, "timeout while waiting for event") } } require.Equal(t, 1, filterResponseCount) require.Equal(t, 2, len(updates)) require.Equal(t, "Test 2", *updates[0].NftName) require.Equal(t, "test://url/2", *updates[0].NftURL) require.Equal(t, "Test 1", *updates[1].NftName) require.Equal(t, "test://url/1", *updates[1].NftURL) sub.Unsubscribe() } func TestService_UpdateCollectibleInfo_Error(t *testing.T) { s, e, _, c, close := setupTestService(t) defer close() args := []arg{ {5, "0xA2838FDA19EB6EED3F8B9EFF411D4CD7D2DE0313", "0x762AD3E4934E687F8701F24C7274E5209213FD6208FF952ACEB325D028866949", nil, nil}, {5, "0xA2838FDA19EB6EED3F8B9EFF411D4CD7D2DE0313", "0x0D", nil, nil}, } ch := make(chan walletevent.Event, 4) sub := e.Subscribe(ch) fromAddresses, toAddresses := insertStubTransfersWithCollectibles(t, s.db, args) c.On("FetchAssetsByCollectibleUniqueID", mock.Anything).Return(nil, thirdparty.ErrChainIDNotSupported).Once() s.FilterActivityAsync(0, append(fromAddresses, toAddresses...), true, allNetworksFilter(), Filter{}, 0, 5) filterResponseCount := 0 updatesCount := 0 for i := 0; i < 2; i++ { select { case res := <-ch: switch res.Type { case EventActivityFilteringDone: var payload FilterResponse err := json.Unmarshal([]byte(res.Message), &payload) require.NoError(t, err) require.Equal(t, ErrorCodeSuccess, payload.ErrorCode) require.Equal(t, 2, len(payload.Activities)) filterResponseCount++ case EventActivityFilteringUpdate: updatesCount++ } case <-time.NewTimer(20 * time.Millisecond).C: // We wait to ensure the EventActivityFilteringUpdate is never sent } } require.Equal(t, 1, filterResponseCount) require.Equal(t, 0, updatesCount) sub.Unsubscribe() }