import assert from 'assert'
import sinon from 'sinon'
import proxyquire from 'proxyquire'
const IncomingTransactionsController = proxyquire ( '../../../../app/scripts/controllers/incoming-transactions' , {
'../lib/random-id' : { default : ( ) => 54321 } ,
} ) . default
import { ROPSTEN , RINKEBY , KOVAN , GOERLI , MAINNET } from '../../../../app/scripts/controllers/network/enums'
describe ( 'IncomingTransactionsController' , function ( ) {
const EMPTY _INIT _STATE = {
incomingTransactions : { } ,
incomingTxLastFetchedBlocksByNetwork : {
[ ROPSTEN ] : null ,
[ RINKEBY ] : null ,
[ KOVAN ] : null ,
[ GOERLI ] : null ,
[ MAINNET ] : null ,
} ,
}
const NON _EMPTY _INIT _STATE = {
incomingTransactions : {
'0x123456' : { id : 777 } ,
} ,
incomingTxLastFetchedBlocksByNetwork : {
[ ROPSTEN ] : 1 ,
[ RINKEBY ] : 2 ,
[ KOVAN ] : 3 ,
[ GOERLI ] : 5 ,
[ MAINNET ] : 4 ,
} ,
}
const NON _EMPTY _INIT _STATE _WITH _FAKE _NETWORK _STATE = {
incomingTransactions : {
'0x123456' : { id : 777 } ,
} ,
incomingTxLastFetchedBlocksByNetwork : {
[ ROPSTEN ] : 1 ,
[ RINKEBY ] : 2 ,
[ KOVAN ] : 3 ,
[ GOERLI ] : 5 ,
[ MAINNET ] : 4 ,
FAKE _NETWORK : 1111 ,
} ,
}
const MOCK _BLOCKTRACKER = {
addListener : sinon . spy ( ) ,
removeListener : sinon . spy ( ) ,
testProperty : 'fakeBlockTracker' ,
getCurrentBlock : ( ) => '0xa' ,
}
const MOCK _NETWORK _CONTROLLER = {
getProviderConfig : ( ) => ( { type : 'FAKE_NETWORK' } ) ,
on : sinon . spy ( ) ,
}
const MOCK _PREFERENCES _CONTROLLER = {
getSelectedAddress : sinon . stub ( ) . returns ( '0x0101' ) ,
store : {
getState : sinon . stub ( ) . returns ( {
featureFlags : {
showIncomingTransactions : true ,
} ,
} ) ,
subscribe : sinon . spy ( ) ,
} ,
}
describe ( 'constructor' , function ( ) {
it ( 'should set up correct store, listeners and properties in the constructor' , function ( ) {
const incomingTransactionsController = new IncomingTransactionsController ( {
blockTracker : MOCK _BLOCKTRACKER ,
networkController : MOCK _NETWORK _CONTROLLER ,
preferencesController : MOCK _PREFERENCES _CONTROLLER ,
initState : { } ,
} )
sinon . spy ( incomingTransactionsController , '_update' )
assert . deepEqual ( incomingTransactionsController . blockTracker , MOCK _BLOCKTRACKER )
assert . deepEqual ( incomingTransactionsController . networkController , MOCK _NETWORK _CONTROLLER )
assert . equal ( incomingTransactionsController . preferencesController , MOCK _PREFERENCES _CONTROLLER )
assert . equal ( incomingTransactionsController . getCurrentNetwork ( ) , 'FAKE_NETWORK' )
assert . deepEqual ( incomingTransactionsController . store . getState ( ) , EMPTY _INIT _STATE )
assert ( incomingTransactionsController . networkController . on . calledOnce )
assert . equal ( incomingTransactionsController . networkController . on . getCall ( 0 ) . args [ 0 ] , 'networkDidChange' )
const networkControllerListenerCallback = incomingTransactionsController . networkController . on . getCall ( 0 ) . args [ 1 ]
assert . equal ( incomingTransactionsController . _update . callCount , 0 )
networkControllerListenerCallback ( 'testNetworkType' )
assert . equal ( incomingTransactionsController . _update . callCount , 1 )
assert . deepEqual ( incomingTransactionsController . _update . getCall ( 0 ) . args [ 0 ] , {
address : '0x0101' ,
networkType : 'testNetworkType' ,
} )
incomingTransactionsController . _update . resetHistory ( )
} )
it ( 'should set the store to a provided initial state' , function ( ) {
const incomingTransactionsController = new IncomingTransactionsController ( {
blockTracker : MOCK _BLOCKTRACKER ,
networkController : MOCK _NETWORK _CONTROLLER ,
preferencesController : MOCK _PREFERENCES _CONTROLLER ,
initState : NON _EMPTY _INIT _STATE ,
} )
assert . deepEqual ( incomingTransactionsController . store . getState ( ) , NON _EMPTY _INIT _STATE )
} )
} )
describe ( '#start' , function ( ) {
it ( 'should set up a listener for the latest block' , function ( ) {
const incomingTransactionsController = new IncomingTransactionsController ( {
blockTracker : MOCK _BLOCKTRACKER ,
networkController : MOCK _NETWORK _CONTROLLER ,
preferencesController : MOCK _PREFERENCES _CONTROLLER ,
initState : { } ,
} )
sinon . spy ( incomingTransactionsController , '_update' )
incomingTransactionsController . start ( )
assert ( incomingTransactionsController . blockTracker . addListener . calledOnce )
assert . equal ( incomingTransactionsController . blockTracker . addListener . getCall ( 0 ) . args [ 0 ] , 'latest' )
const blockTrackerListenerCallback = incomingTransactionsController . blockTracker . addListener . getCall ( 0 ) . args [ 1 ]
assert . equal ( incomingTransactionsController . _update . callCount , 0 )
blockTrackerListenerCallback ( '0xabc' )
assert . equal ( incomingTransactionsController . _update . callCount , 1 )
assert . deepEqual ( incomingTransactionsController . _update . getCall ( 0 ) . args [ 0 ] , {
address : '0x0101' ,
newBlockNumberDec : 2748 ,
} )
} )
} )
describe ( '_getDataForUpdate' , function ( ) {
it ( 'should call fetchAll with the correct params when passed a new block number and the current network has no stored block' , async function ( ) {
const incomingTransactionsController = new IncomingTransactionsController ( {
blockTracker : MOCK _BLOCKTRACKER ,
networkController : MOCK _NETWORK _CONTROLLER ,
preferencesController : MOCK _PREFERENCES _CONTROLLER ,
initState : NON _EMPTY _INIT _STATE ,
} )
incomingTransactionsController . _fetchAll = sinon . stub ( ) . returns ( { } )
await incomingTransactionsController . _getDataForUpdate ( { address : 'fakeAddress' , newBlockNumberDec : 999 } )
assert ( incomingTransactionsController . _fetchAll . calledOnce )
assert . deepEqual ( incomingTransactionsController . _fetchAll . getCall ( 0 ) . args , [
'fakeAddress' , 999 , 'FAKE_NETWORK' ,
] )
} )
it ( 'should call fetchAll with the correct params when passed a new block number but the current network has a stored block' , async function ( ) {
const incomingTransactionsController = new IncomingTransactionsController ( {
blockTracker : MOCK _BLOCKTRACKER ,
networkController : MOCK _NETWORK _CONTROLLER ,
preferencesController : MOCK _PREFERENCES _CONTROLLER ,
initState : NON _EMPTY _INIT _STATE _WITH _FAKE _NETWORK _STATE ,
} )
incomingTransactionsController . _fetchAll = sinon . stub ( ) . returns ( { } )
await incomingTransactionsController . _getDataForUpdate ( { address : 'fakeAddress' , newBlockNumberDec : 999 } )
assert ( incomingTransactionsController . _fetchAll . calledOnce )
assert . deepEqual ( incomingTransactionsController . _fetchAll . getCall ( 0 ) . args , [
'fakeAddress' , 1111 , 'FAKE_NETWORK' ,
] )
} )
it ( 'should call fetchAll with the correct params when passed a new network type but no block info exists' , async function ( ) {
const incomingTransactionsController = new IncomingTransactionsController ( {
blockTracker : MOCK _BLOCKTRACKER ,
networkController : MOCK _NETWORK _CONTROLLER ,
preferencesController : MOCK _PREFERENCES _CONTROLLER ,
initState : NON _EMPTY _INIT _STATE _WITH _FAKE _NETWORK _STATE ,
} )
incomingTransactionsController . _fetchAll = sinon . stub ( ) . returns ( { } )
await incomingTransactionsController . _getDataForUpdate ( {
address : 'fakeAddress' ,
networkType : 'NEW_FAKE_NETWORK' ,
} )
assert ( incomingTransactionsController . _fetchAll . calledOnce )
assert . deepEqual ( incomingTransactionsController . _fetchAll . getCall ( 0 ) . args , [
'fakeAddress' , 10 , 'NEW_FAKE_NETWORK' ,
] )
} )
it ( 'should return the expected data' , async function ( ) {
const incomingTransactionsController = new IncomingTransactionsController ( {
blockTracker : MOCK _BLOCKTRACKER ,
networkController : MOCK _NETWORK _CONTROLLER ,
preferencesController : MOCK _PREFERENCES _CONTROLLER ,
initState : NON _EMPTY _INIT _STATE _WITH _FAKE _NETWORK _STATE ,
} )
incomingTransactionsController . _fetchAll = sinon . stub ( ) . returns ( {
latestIncomingTxBlockNumber : 444 ,
txs : [ { id : 555 } ] ,
} )
const result = await incomingTransactionsController . _getDataForUpdate ( {
address : 'fakeAddress' ,
networkType : 'FAKE_NETWORK' ,
} )
assert . deepEqual ( result , {
latestIncomingTxBlockNumber : 444 ,
newTxs : [ { id : 555 } ] ,
currentIncomingTxs : {
'0x123456' : { id : 777 } ,
} ,
currentBlocksByNetwork : {
[ ROPSTEN ] : 1 ,
[ RINKEBY ] : 2 ,
[ KOVAN ] : 3 ,
[ GOERLI ] : 5 ,
[ MAINNET ] : 4 ,
FAKE _NETWORK : 1111 ,
} ,
fetchedBlockNumber : 1111 ,
network : 'FAKE_NETWORK' ,
} )
} )
} )
describe ( '_updateStateWithNewTxData' , function ( ) {
const MOCK _INPUT _WITHOUT _LASTEST = {
newTxs : [ { id : 555 , hash : '0xfff' } ] ,
currentIncomingTxs : {
'0x123456' : { id : 777 , hash : '0x123456' } ,
} ,
currentBlocksByNetwork : {
[ ROPSTEN ] : 1 ,
[ RINKEBY ] : 2 ,
[ KOVAN ] : 3 ,
[ GOERLI ] : 5 ,
[ MAINNET ] : 4 ,
FAKE _NETWORK : 1111 ,
} ,
fetchedBlockNumber : 1111 ,
network : 'FAKE_NETWORK' ,
}
const MOCK _INPUT _WITH _LASTEST = {
... MOCK _INPUT _WITHOUT _LASTEST ,
latestIncomingTxBlockNumber : 444 ,
}
it ( 'should update state with correct blockhash and transactions when passed a truthy latestIncomingTxBlockNumber' , async function ( ) {
const incomingTransactionsController = new IncomingTransactionsController ( {
blockTracker : MOCK _BLOCKTRACKER ,
networkController : MOCK _NETWORK _CONTROLLER ,
preferencesController : MOCK _PREFERENCES _CONTROLLER ,
initState : NON _EMPTY _INIT _STATE ,
} )
sinon . spy ( incomingTransactionsController . store , 'updateState' )
await incomingTransactionsController . _updateStateWithNewTxData ( MOCK _INPUT _WITH _LASTEST )
assert ( incomingTransactionsController . store . updateState . calledOnce )
assert . deepEqual ( incomingTransactionsController . store . updateState . getCall ( 0 ) . args [ 0 ] , {
incomingTxLastFetchedBlocksByNetwork : {
... MOCK _INPUT _WITH _LASTEST . currentBlocksByNetwork ,
'FAKE_NETWORK' : 445 ,
} ,
incomingTransactions : {
'0x123456' : { id : 777 , hash : '0x123456' } ,
'0xfff' : { id : 555 , hash : '0xfff' } ,
} ,
} )
} )
it ( 'should update state with correct blockhash and transactions when passed a falsy latestIncomingTxBlockNumber' , async function ( ) {
const incomingTransactionsController = new IncomingTransactionsController ( {
blockTracker : MOCK _BLOCKTRACKER ,
networkController : MOCK _NETWORK _CONTROLLER ,
preferencesController : MOCK _PREFERENCES _CONTROLLER ,
initState : NON _EMPTY _INIT _STATE ,
} )
sinon . spy ( incomingTransactionsController . store , 'updateState' )
await incomingTransactionsController . _updateStateWithNewTxData ( MOCK _INPUT _WITHOUT _LASTEST )
assert ( incomingTransactionsController . store . updateState . calledOnce )
assert . deepEqual ( incomingTransactionsController . store . updateState . getCall ( 0 ) . args [ 0 ] , {
incomingTxLastFetchedBlocksByNetwork : {
... MOCK _INPUT _WITH _LASTEST . currentBlocksByNetwork ,
'FAKE_NETWORK' : 1112 ,
} ,
incomingTransactions : {
'0x123456' : { id : 777 , hash : '0x123456' } ,
'0xfff' : { id : 555 , hash : '0xfff' } ,
} ,
} )
} )
} )
describe ( '_fetchTxs' , function ( ) {
const mockFetch = sinon . stub ( ) . returns ( Promise . resolve ( {
json : ( ) => Promise . resolve ( { someKey : 'someValue' } ) ,
} ) )
let tempFetch
beforeEach ( function ( ) {
tempFetch = global . fetch
global . fetch = mockFetch
} )
afterEach ( function ( ) {
global . fetch = tempFetch
mockFetch . resetHistory ( )
} )
it ( 'should call fetch with the expected url when passed an address, block number and supported network' , async function ( ) {
const incomingTransactionsController = new IncomingTransactionsController ( {
blockTracker : MOCK _BLOCKTRACKER ,
networkController : MOCK _NETWORK _CONTROLLER ,
preferencesController : MOCK _PREFERENCES _CONTROLLER ,
initState : NON _EMPTY _INIT _STATE ,
} )
await incomingTransactionsController . _fetchTxs ( '0xfakeaddress' , '789' , ROPSTEN )
assert ( mockFetch . calledOnce )
assert . equal ( mockFetch . getCall ( 0 ) . args [ 0 ] , ` https://api- ${ ROPSTEN } .etherscan.io/api?module=account&action=txlist&address=0xfakeaddress&tag=latest&page=1&startBlock=789 ` )
} )
it ( 'should call fetch with the expected url when passed an address, block number and MAINNET' , async function ( ) {
const incomingTransactionsController = new IncomingTransactionsController ( {
blockTracker : MOCK _BLOCKTRACKER ,
networkController : MOCK _NETWORK _CONTROLLER ,
preferencesController : MOCK _PREFERENCES _CONTROLLER ,
initState : NON _EMPTY _INIT _STATE ,
} )
await incomingTransactionsController . _fetchTxs ( '0xfakeaddress' , '789' , MAINNET )
assert ( mockFetch . calledOnce )
assert . equal ( mockFetch . getCall ( 0 ) . args [ 0 ] , ` https://api.etherscan.io/api?module=account&action=txlist&address=0xfakeaddress&tag=latest&page=1&startBlock=789 ` )
} )
it ( 'should call fetch with the expected url when passed an address and supported network, but a falsy block number' , async function ( ) {
const incomingTransactionsController = new IncomingTransactionsController ( {
blockTracker : MOCK _BLOCKTRACKER ,
networkController : MOCK _NETWORK _CONTROLLER ,
preferencesController : MOCK _PREFERENCES _CONTROLLER ,
initState : NON _EMPTY _INIT _STATE ,
} )
await incomingTransactionsController . _fetchTxs ( '0xfakeaddress' , null , ROPSTEN )
assert ( mockFetch . calledOnce )
assert . equal ( mockFetch . getCall ( 0 ) . args [ 0 ] , ` https://api- ${ ROPSTEN } .etherscan.io/api?module=account&action=txlist&address=0xfakeaddress&tag=latest&page=1 ` )
} )
it ( 'should not fetch and return an empty object when passed an unsported network' , async function ( ) {
const incomingTransactionsController = new IncomingTransactionsController ( {
blockTracker : MOCK _BLOCKTRACKER ,
networkController : MOCK _NETWORK _CONTROLLER ,
preferencesController : MOCK _PREFERENCES _CONTROLLER ,
initState : NON _EMPTY _INIT _STATE ,
} )
const result = await incomingTransactionsController . _fetchTxs ( '0xfakeaddress' , null , 'UNSUPPORTED_NETWORK' )
assert ( mockFetch . notCalled )
assert . deepEqual ( result , { } )
} )
it ( 'should return the results from the fetch call, plus the address and currentNetworkID, when passed an address, block number and supported network' , async function ( ) {
const incomingTransactionsController = new IncomingTransactionsController ( {
blockTracker : MOCK _BLOCKTRACKER ,
networkController : MOCK _NETWORK _CONTROLLER ,
preferencesController : MOCK _PREFERENCES _CONTROLLER ,
initState : NON _EMPTY _INIT _STATE ,
} )
const result = await incomingTransactionsController . _fetchTxs ( '0xfakeaddress' , '789' , ROPSTEN )
assert ( mockFetch . calledOnce )
assert . deepEqual ( result , {
someKey : 'someValue' ,
address : '0xfakeaddress' ,
currentNetworkID : 3 ,
} )
} )
} )
describe ( '_processTxFetchResponse' , function ( ) {
it ( 'should return a null block number and empty tx array if status is 0' , function ( ) {
const incomingTransactionsController = new IncomingTransactionsController ( {
blockTracker : MOCK _BLOCKTRACKER ,
networkController : MOCK _NETWORK _CONTROLLER ,
preferencesController : MOCK _PREFERENCES _CONTROLLER ,
initState : NON _EMPTY _INIT _STATE ,
} )
const result = incomingTransactionsController . _processTxFetchResponse ( {
status : '0' ,
result : [ { id : 1 } ] ,
address : '0xfakeaddress' ,
} )
assert . deepEqual ( result , {
latestIncomingTxBlockNumber : null ,
txs : [ ] ,
} )
} )
it ( 'should return a null block number and empty tx array if the passed result array is empty' , function ( ) {
const incomingTransactionsController = new IncomingTransactionsController ( {
blockTracker : MOCK _BLOCKTRACKER ,
networkController : MOCK _NETWORK _CONTROLLER ,
preferencesController : MOCK _PREFERENCES _CONTROLLER ,
initState : NON _EMPTY _INIT _STATE ,
} )
const result = incomingTransactionsController . _processTxFetchResponse ( {
status : '1' ,
result : [ ] ,
address : '0xfakeaddress' ,
} )
assert . deepEqual ( result , {
latestIncomingTxBlockNumber : null ,
txs : [ ] ,
} )
} )
it ( 'should return the expected block number and tx list when passed data from a successful fetch' , function ( ) {
const incomingTransactionsController = new IncomingTransactionsController ( {
blockTracker : MOCK _BLOCKTRACKER ,
networkController : MOCK _NETWORK _CONTROLLER ,
preferencesController : MOCK _PREFERENCES _CONTROLLER ,
initState : NON _EMPTY _INIT _STATE ,
} )
incomingTransactionsController . _normalizeTxFromEtherscan = ( tx , currentNetworkID ) => ( {
... tx ,
currentNetworkID ,
normalized : true ,
} )
const result = incomingTransactionsController . _processTxFetchResponse ( {
status : '1' ,
address : '0xfakeaddress' ,
currentNetworkID : 'FAKE_NETWORK' ,
result : [
{
hash : '0xabc123' ,
txParams : {
to : '0xfakeaddress' ,
} ,
blockNumber : 5000 ,
time : 10 ,
} ,
{
hash : '0xabc123' ,
txParams : {
to : '0xfakeaddress' ,
} ,
blockNumber : 5000 ,
time : 10 ,
} ,
{
hash : '0xabc1234' ,
txParams : {
to : '0xfakeaddress' ,
} ,
blockNumber : 5000 ,
time : 9 ,
} ,
{
hash : '0xabc12345' ,
txParams : {
to : '0xfakeaddress' ,
} ,
blockNumber : 5001 ,
time : 11 ,
} ,
{
hash : '0xabc123456' ,
txParams : {
to : '0xfakeaddress' ,
} ,
blockNumber : 5001 ,
time : 12 ,
} ,
{
hash : '0xabc1234567' ,
txParams : {
to : '0xanotherFakeaddress' ,
} ,
blockNumber : 5002 ,
time : 13 ,
} ,
] ,
} )
assert . deepEqual ( result , {
latestIncomingTxBlockNumber : 5001 ,
txs : [
{
hash : '0xabc1234' ,
txParams : {
to : '0xfakeaddress' ,
} ,
blockNumber : 5000 ,
time : 9 ,
normalized : true ,
currentNetworkID : 'FAKE_NETWORK' ,
} ,
{
hash : '0xabc123' ,
txParams : {
to : '0xfakeaddress' ,
} ,
blockNumber : 5000 ,
time : 10 ,
normalized : true ,
currentNetworkID : 'FAKE_NETWORK' ,
} ,
{
hash : '0xabc12345' ,
txParams : {
to : '0xfakeaddress' ,
} ,
blockNumber : 5001 ,
time : 11 ,
normalized : true ,
currentNetworkID : 'FAKE_NETWORK' ,
} ,
{
hash : '0xabc123456' ,
txParams : {
to : '0xfakeaddress' ,
} ,
blockNumber : 5001 ,
time : 12 ,
normalized : true ,
currentNetworkID : 'FAKE_NETWORK' ,
} ,
] ,
} )
} )
} )
describe ( '_normalizeTxFromEtherscan' , function ( ) {
it ( 'should return the expected data when the tx is in error' , function ( ) {
const incomingTransactionsController = new IncomingTransactionsController ( {
blockTracker : MOCK _BLOCKTRACKER ,
networkController : MOCK _NETWORK _CONTROLLER ,
preferencesController : MOCK _PREFERENCES _CONTROLLER ,
initState : NON _EMPTY _INIT _STATE ,
} )
const result = incomingTransactionsController . _normalizeTxFromEtherscan ( {
timeStamp : '4444' ,
isError : '1' ,
blockNumber : 333 ,
from : '0xa' ,
gas : '11' ,
gasPrice : '12' ,
nonce : '13' ,
to : '0xe' ,
value : '15' ,
hash : '0xg' ,
} , 'FAKE_NETWORK' )
assert . deepEqual ( result , {
blockNumber : 333 ,
id : 54321 ,
metamaskNetworkId : 'FAKE_NETWORK' ,
status : 'failed' ,
time : 4444000 ,
txParams : {
from : '0xa' ,
gas : '0xb' ,
gasPrice : '0xc' ,
nonce : '0xd' ,
to : '0xe' ,
value : '0xf' ,
} ,
hash : '0xg' ,
transactionCategory : 'incoming' ,
} )
} )
it ( 'should return the expected data when the tx is not in error' , function ( ) {
const incomingTransactionsController = new IncomingTransactionsController ( {
blockTracker : MOCK _BLOCKTRACKER ,
networkController : MOCK _NETWORK _CONTROLLER ,
preferencesController : MOCK _PREFERENCES _CONTROLLER ,
initState : NON _EMPTY _INIT _STATE ,
} )
const result = incomingTransactionsController . _normalizeTxFromEtherscan ( {
timeStamp : '4444' ,
isError : '0' ,
blockNumber : 333 ,
from : '0xa' ,
gas : '11' ,
gasPrice : '12' ,
nonce : '13' ,
to : '0xe' ,
value : '15' ,
hash : '0xg' ,
} , 'FAKE_NETWORK' )
assert . deepEqual ( result , {
blockNumber : 333 ,
id : 54321 ,
metamaskNetworkId : 'FAKE_NETWORK' ,
status : 'confirmed' ,
time : 4444000 ,
txParams : {
from : '0xa' ,
gas : '0xb' ,
gasPrice : '0xc' ,
nonce : '0xd' ,
to : '0xe' ,
value : '0xf' ,
} ,
hash : '0xg' ,
transactionCategory : 'incoming' ,
} )
} )
} )
} )