status-go/services/wallet/activity/activity_test.go

407 lines
14 KiB
Go

package activity
import (
"database/sql"
"testing"
"github.com/status-im/status-go/appdatabase"
"github.com/status-im/status-go/services/wallet/testutils"
"github.com/status-im/status-go/services/wallet/transfer"
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require"
)
func setupTestActivityDB(t *testing.T) (db *sql.DB, close func()) {
db, err := appdatabase.SetupTestMemorySQLDB("wallet-activity-tests")
require.NoError(t, err)
return db, func() {
require.NoError(t, db.Close())
}
}
func insertTestPendingTransaction(t *testing.T, db *sql.DB, tr *transfer.TestTransaction) {
_, err := db.Exec(`
INSERT INTO pending_transactions (network_id, hash, timestamp, from_address, to_address,
symbol, gas_price, gas_limit, value, data, type, additional_data, multi_transaction_id
) VALUES (?, ?, ?, ?, ?, 'ETH', 0, 0, ?, '', 'test', '', ?)`,
tr.ChainID, tr.Hash, tr.Timestamp, tr.From, tr.To, tr.Value, tr.MultiTransactionID)
require.NoError(t, err)
}
type testData struct {
tr1 transfer.TestTransaction // index 1
pendingTr transfer.TestTransaction // index 2
singletonMTr transfer.TestTransaction // index 3
mTr transfer.TestTransaction // index 4
subTr transfer.TestTransaction // index 5
subPendingTr transfer.TestTransaction // index 6
singletonMTID transfer.MultiTransactionIDType
mTrID transfer.MultiTransactionIDType
}
// Generates and adds to the DB 6 transactions. 2 transactions, 2 pending and 2 multi transactions
// There are only 4 extractable transactions and multi-transaction with timestamps 1-4. The other 2 are associated with a multi-transaction
func fillTestData(t *testing.T, db *sql.DB) (td testData) {
trs := transfer.GenerateTestTransactions(t, db, 1, 6)
td.tr1 = trs[0]
transfer.InsertTestTransfer(t, db, &td.tr1)
td.pendingTr = trs[1]
insertTestPendingTransaction(t, db, &td.pendingTr)
td.singletonMTr = trs[2]
td.singletonMTID = transfer.InsertTestMultiTransaction(t, db, &td.singletonMTr)
td.mTr = trs[3]
td.mTrID = transfer.InsertTestMultiTransaction(t, db, &td.mTr)
td.subTr = trs[4]
td.subTr.MultiTransactionID = td.mTrID
transfer.InsertTestTransfer(t, db, &td.subTr)
td.subPendingTr = trs[5]
td.subPendingTr.MultiTransactionID = td.mTrID
insertTestPendingTransaction(t, db, &td.subPendingTr)
return
}
func TestGetActivityEntriesAll(t *testing.T) {
db, close := setupTestActivityDB(t)
defer close()
td := fillTestData(t, db)
var filter Filter
entries, err := GetActivityEntries(db, []common.Address{}, []uint64{}, filter, 0, 10)
require.NoError(t, err)
require.Equal(t, 4, len(entries))
// Ensure we have the correct order
var curTimestamp int64 = 4
for _, entry := range entries {
require.Equal(t, curTimestamp, entry.timestamp, "entries are sorted by timestamp; expected %d, got %d", curTimestamp, entry.timestamp)
curTimestamp--
}
require.True(t, testutils.StructExistsInSlice(Entry{
transactionType: SimpleTransactionPT,
transaction: &transfer.TransactionIdentity{ChainID: td.tr1.ChainID, Hash: td.tr1.Hash, Address: td.tr1.To},
id: td.tr1.MultiTransactionID,
timestamp: td.tr1.Timestamp,
activityType: SendAT,
}, entries))
require.True(t, testutils.StructExistsInSlice(Entry{
transactionType: PendingTransactionPT,
transaction: &transfer.TransactionIdentity{ChainID: td.pendingTr.ChainID, Hash: td.pendingTr.Hash},
id: td.pendingTr.MultiTransactionID,
timestamp: td.pendingTr.Timestamp,
activityType: SendAT,
}, entries))
require.True(t, testutils.StructExistsInSlice(Entry{
transactionType: MultiTransactionPT,
transaction: nil,
id: td.singletonMTID,
timestamp: td.singletonMTr.Timestamp,
activityType: SendAT,
}, entries))
require.True(t, testutils.StructExistsInSlice(Entry{
transactionType: MultiTransactionPT,
transaction: nil,
id: td.mTrID,
timestamp: td.mTr.Timestamp,
activityType: SendAT,
}, entries))
// Ensure the sub-transactions of the multi-transactions are not returned
require.False(t, testutils.StructExistsInSlice(Entry{
transactionType: SimpleTransactionPT,
transaction: &transfer.TransactionIdentity{ChainID: td.subTr.ChainID, Hash: td.subTr.Hash, Address: td.subTr.To},
id: td.subTr.MultiTransactionID,
timestamp: td.subTr.Timestamp,
activityType: SendAT,
}, entries))
require.False(t, testutils.StructExistsInSlice(Entry{
transactionType: PendingTransactionPT,
transaction: &transfer.TransactionIdentity{ChainID: td.subPendingTr.ChainID, Hash: td.subPendingTr.Hash},
id: td.subPendingTr.MultiTransactionID,
timestamp: td.subPendingTr.Timestamp,
activityType: SendAT,
}, entries))
}
// TestGetActivityEntriesWithSenderFilter covers the issue with returning the same transaction
// twice when the sender and receiver have entries in the transfers table
func TestGetActivityEntriesWithSameTransactionForSenderAndReceiverInDB(t *testing.T) {
db, close := setupTestActivityDB(t)
defer close()
// Add 4 extractable transactions with timestamps 1-4
td := fillTestData(t, db)
// Add another transaction with sender and receiver reversed
receiverTr := td.tr1
prevTo := receiverTr.To
receiverTr.To = td.tr1.From
receiverTr.From = prevTo
transfer.InsertTestTransfer(t, db, &receiverTr)
var filter Filter
entries, err := GetActivityEntries(db, []common.Address{}, []uint64{}, filter, 0, 10)
require.NoError(t, err)
// TODO: decide how should we handle this case filter out or include it in the result
// For now we include both. Can be changed by using UNION instead of UNION ALL in the query or by filtering out
require.Equal(t, 5, len(entries))
}
func TestGetActivityEntriesFilterByTime(t *testing.T) {
db, close := setupTestActivityDB(t)
defer close()
td := fillTestData(t, db)
// Add 6 extractable transactions with timestamps 6-12
trs := transfer.GenerateTestTransactions(t, db, 6, 6)
for i := range trs {
transfer.InsertTestTransfer(t, db, &trs[i])
}
// Test start only
var filter Filter
filter.Period.StartTimestamp = td.singletonMTr.Timestamp
entries, err := GetActivityEntries(db, []common.Address{}, []uint64{}, filter, 0, 15)
require.NoError(t, err)
require.Equal(t, 8, len(entries))
// Check start and end content
require.Equal(t, Entry{
transactionType: SimpleTransactionPT,
transaction: &transfer.TransactionIdentity{ChainID: trs[5].ChainID, Hash: trs[5].Hash, Address: trs[5].To},
id: 0,
timestamp: trs[5].Timestamp,
activityType: SendAT,
}, entries[0])
require.Equal(t, Entry{
transactionType: MultiTransactionPT,
transaction: nil,
id: td.singletonMTID,
timestamp: td.singletonMTr.Timestamp,
activityType: SendAT,
}, entries[7])
// Test complete interval
filter.Period.EndTimestamp = trs[2].Timestamp
entries, err = GetActivityEntries(db, []common.Address{}, []uint64{}, filter, 0, 15)
require.NoError(t, err)
require.Equal(t, 5, len(entries))
// Check start and end content
require.Equal(t, Entry{
transactionType: SimpleTransactionPT,
transaction: &transfer.TransactionIdentity{ChainID: trs[2].ChainID, Hash: trs[2].Hash, Address: trs[2].To},
id: 0,
timestamp: trs[2].Timestamp,
activityType: SendAT,
}, entries[0])
require.Equal(t, Entry{
transactionType: MultiTransactionPT,
transaction: nil,
id: td.singletonMTID,
timestamp: td.singletonMTr.Timestamp,
activityType: SendAT,
}, entries[4])
// Test end only
filter.Period.StartTimestamp = 0
entries, err = GetActivityEntries(db, []common.Address{}, []uint64{}, filter, 0, 15)
require.NoError(t, err)
require.Equal(t, 7, len(entries))
// Check start and end content
require.Equal(t, Entry{
transactionType: SimpleTransactionPT,
transaction: &transfer.TransactionIdentity{ChainID: trs[2].ChainID, Hash: trs[2].Hash, Address: trs[2].To},
id: 0,
timestamp: trs[2].Timestamp,
activityType: SendAT,
}, entries[0])
require.Equal(t, Entry{
transactionType: SimpleTransactionPT,
transaction: &transfer.TransactionIdentity{ChainID: td.tr1.ChainID, Hash: td.tr1.Hash, Address: td.tr1.To},
id: 0,
timestamp: td.tr1.Timestamp,
activityType: SendAT,
}, entries[6])
}
func TestGetActivityEntriesCheckOffsetAndLimit(t *testing.T) {
db, close := setupTestActivityDB(t)
defer close()
// Add 10 extractable transactions with timestamps 1-10
trs := transfer.GenerateTestTransactions(t, db, 1, 10)
for i := range trs {
transfer.InsertTestTransfer(t, db, &trs[i])
}
var filter Filter
// Get all
entries, err := GetActivityEntries(db, []common.Address{}, []uint64{}, filter, 0, 5)
require.NoError(t, err)
require.Equal(t, 5, len(entries))
// Get time based interval
filter.Period.StartTimestamp = trs[2].Timestamp
filter.Period.EndTimestamp = trs[8].Timestamp
entries, err = GetActivityEntries(db, []common.Address{}, []uint64{}, filter, 0, 3)
require.NoError(t, err)
require.Equal(t, 3, len(entries))
// Check start and end content
require.Equal(t, Entry{
transactionType: SimpleTransactionPT,
transaction: &transfer.TransactionIdentity{ChainID: trs[8].ChainID, Hash: trs[8].Hash, Address: trs[8].To},
id: 0,
timestamp: trs[8].Timestamp,
activityType: SendAT,
}, entries[0])
require.Equal(t, Entry{
transactionType: SimpleTransactionPT,
transaction: &transfer.TransactionIdentity{ChainID: trs[6].ChainID, Hash: trs[6].Hash, Address: trs[6].To},
id: 0,
timestamp: trs[6].Timestamp,
activityType: SendAT,
}, entries[2])
// Move window 2 entries forward
entries, err = GetActivityEntries(db, []common.Address{}, []uint64{}, filter, 2, 3)
require.NoError(t, err)
require.Equal(t, 3, len(entries))
// Check start and end content
require.Equal(t, Entry{
transactionType: SimpleTransactionPT,
transaction: &transfer.TransactionIdentity{ChainID: trs[6].ChainID, Hash: trs[6].Hash, Address: trs[6].To},
id: 0,
timestamp: trs[6].Timestamp,
activityType: SendAT,
}, entries[0])
require.Equal(t, Entry{
transactionType: SimpleTransactionPT,
transaction: &transfer.TransactionIdentity{ChainID: trs[4].ChainID, Hash: trs[4].Hash, Address: trs[4].To},
id: 0,
timestamp: trs[4].Timestamp,
activityType: SendAT,
}, entries[2])
// Move window 4 more entries to test filter cap
entries, err = GetActivityEntries(db, []common.Address{}, []uint64{}, filter, 6, 3)
require.NoError(t, err)
require.Equal(t, 1, len(entries))
// Check start and end content
require.Equal(t, Entry{
transactionType: SimpleTransactionPT,
transaction: &transfer.TransactionIdentity{ChainID: trs[2].ChainID, Hash: trs[2].Hash, Address: trs[2].To},
id: 0,
timestamp: trs[2].Timestamp,
activityType: SendAT,
}, entries[0])
}
func TestGetActivityEntriesFilterByType(t *testing.T) {
db, close := setupTestActivityDB(t)
defer close()
// Adds 4 extractable transactions
fillTestData(t, db)
// Add 6 extractable transactions: one MultiTransactionSwap, two MultiTransactionBridge rest Send
trs := transfer.GenerateTestTransactions(t, db, 6, 6)
trs[1].MultiTransactionType = transfer.MultiTransactionBridge
trs[3].MultiTransactionType = transfer.MultiTransactionSwap
trs[5].MultiTransactionType = transfer.MultiTransactionBridge
for i := range trs {
if trs[i].MultiTransactionType != transfer.MultiTransactionSend {
transfer.InsertTestMultiTransaction(t, db, &trs[i])
} else {
transfer.InsertTestTransfer(t, db, &trs[i])
}
}
// Test filtering out without address involved
var filter Filter
// TODO: add more types to cover all cases
filter.Types = []Type{SendAT, SwapAT}
entries, err := GetActivityEntries(db, []common.Address{}, []uint64{}, filter, 0, 15)
require.NoError(t, err)
require.Equal(t, 8, len(entries))
swapCount := 0
sendCount := 0
for _, entry := range entries {
if entry.activityType == SendAT {
sendCount++
}
if entry.activityType == SwapAT {
swapCount++
}
}
require.Equal(t, 7, sendCount)
require.Equal(t, 1, swapCount)
// Test filtering out with address involved
filter.Types = []Type{BridgeAT, ReceiveAT}
// Include one "to" from transfers to be detected as receive
addresses := []common.Address{trs[0].To, trs[1].To, trs[2].From, trs[3].From, trs[5].From}
entries, err = GetActivityEntries(db, addresses, []uint64{}, filter, 0, 15)
require.NoError(t, err)
require.Equal(t, 3, len(entries))
bridgeCount := 0
receiveCount := 0
for _, entry := range entries {
if entry.activityType == BridgeAT {
bridgeCount++
}
if entry.activityType == ReceiveAT {
receiveCount++
}
}
require.Equal(t, 2, bridgeCount)
require.Equal(t, 1, receiveCount)
}
func TestGetActivityEntriesFilterByAddress(t *testing.T) {
db, close := setupTestActivityDB(t)
defer close()
// Adds 4 extractable transactions
td := fillTestData(t, db)
// Add 6 extractable transactions: one MultiTransactionSwap, two MultiTransactionBridge rest Send
trs := transfer.GenerateTestTransactions(t, db, 7, 6)
for i := range trs {
transfer.InsertTestTransfer(t, db, &trs[i])
}
var filter Filter
addressesFilter := []common.Address{td.mTr.To, trs[1].From, trs[4].To}
entries, err := GetActivityEntries(db, addressesFilter, []uint64{}, filter, 0, 15)
require.NoError(t, err)
require.Equal(t, 3, len(entries))
require.Equal(t, Entry{
transactionType: SimpleTransactionPT,
transaction: &transfer.TransactionIdentity{ChainID: trs[4].ChainID, Hash: trs[4].Hash, Address: trs[4].To},
id: 0,
timestamp: trs[4].Timestamp,
activityType: ReceiveAT,
}, entries[0])
require.Equal(t, Entry{
transactionType: SimpleTransactionPT,
transaction: &transfer.TransactionIdentity{ChainID: trs[1].ChainID, Hash: trs[1].Hash, Address: trs[1].To},
id: 0,
timestamp: trs[1].Timestamp,
activityType: SendAT,
}, entries[1])
require.Equal(t, Entry{
transactionType: MultiTransactionPT,
transaction: nil,
id: td.mTrID,
timestamp: td.mTr.Timestamp,
activityType: SendAT,
}, entries[2])
}