From 25eb917c600eeb6af46ff262e16fa54d5df9134d Mon Sep 17 00:00:00 2001 From: Thomas Date: Tue, 27 Mar 2018 13:59:37 -0700 Subject: Update blacklist --- test/stub/blacklist.json | 325 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 320 insertions(+), 5 deletions(-) (limited to 'test') diff --git a/test/stub/blacklist.json b/test/stub/blacklist.json index 6a3230b2f..14a890fec 100644 --- a/test/stub/blacklist.json +++ b/test/stub/blacklist.json @@ -5,9 +5,68 @@ "metamask.io", "myetherwallet.com", "cryptokitties.co", - "mycrypto.com" + "mycrypto.com", + "localethereum.com", + "dfinity.org" ], "whitelist": [ + "ncrypto.com", + "mrcrypto.dk", + "nedfinity.com", + "affinity.me", + "crypto.cat", + "winity.io", + "gfinity.net", + "p-crypto.ru", + "mycryptons.com", + "aicrypto.ai", + "azcrypto.net", + "crypto.com", + "affinity.photography", + "abcrypto.info", + "ucrypto.net", + "vscrypto.com", + "affinity.com", + "affinity.co", + "crypto.news", + "dbcrypto.com", + "ezcrypto.guide", + "gocrypto.me", + "finity.ai", + "weffkemining.com", + "nwcrypto.com", + "cobinhood.com", + "affinity.vc", + "trinity.tech", + "metahusk.com", + "mrcrypto.fr", + "affinity.store", + "affinity.serif.com", + "xfinity.com", + "dfinity.org", + "ggcrypto.net", + "mycrypto.website", + "pqcrypto.org", + "z-crypto.com", + "mpcrypo.com", + "localethereum.com", + "localbitcoins.com", + "huobipro.com", + "ncrypto.tech", + "9crypto.co", + "netmask.hu", + "cryptokittens.club", + "rucrypto.com", + "cryptocities.world", + "mqtcrypto.com", + "crypto.press", + "becrypto.xyz", + "hicrypto.io", + "crypto.nl", + "zycrypto.com", + "mmcrypto.io", + "mycrypter.com", + "crypto.tickets", "crypto.pro", "ocrypto.org", "wecrypto.net", @@ -84,7 +143,6 @@ "megamas2.ru", "metamask.io", "myetherwallet.com", - "myethlerwallet.com", "ethereum.org", "myetheroll.com", "myetherapi.com", @@ -128,9 +186,249 @@ "metabase.one", "cryptokitties.co", "remme.io", - "jibrel.network" + "jibrel.network", + "twinity.com" ], "blacklist": [ + "binance-promo.net", + "trust-cryptopayment.com", + "ethereum-transfer.online", + "icotonsale.com", + "etherbonuses.com", + "ethfreeget.com", + "tongram.me", + "funfaircoin.org", + "coinbase-secure-a2k34j.bitballoon.com", + "ethereumtrans.com", + "xn--condes-8bb1661d.com", + "ethergiveawaytweet.tk", + "eth-giveaway.gq", + "nnyettiervwallat.com", + "nnyettiervwallet.com", + "eth.vote", + "eth-promo.bitballoon.com", + "ethfreepay.com", + "promo-trx.tk", + "paymentaddress.tumblr.com", + "cryptongram.org", + "campaigntoken.blogspot.de", + "coin-promos.com", + "xn--ytherwallet-iw8ex8c.com", + "smartexchangecenter.tk", + "xn--oindesk-15a.com", + "eth-transaction.secure.kissr.com", + "otoeveryday.info", + "ethfreecandy.bitballoon.com", + "etherscanio.github.io", + "ico-gram.net", + "gramtoken.network", + "ethfreegive.com", + "ether-promo.bitballoon.com", + "xn--cindesk-cx4c.com", + "theadsleader.000webhostapp.com", + "myetherwalletico.blogspot.de", + "eth-giveaway.info", + "syncfab.eu", + "eth-giveaway.com", + "icoeth.tk", + "eth-giveaway-706.htmlcomponentservice.com", + "htmlcomponentservice.com", + "mybinance.info", + "xn--myethewallet-kjc.com", + "polyswamr.io", + "polyswarm.me", + "polyswarms.io", + "eth4free.ml", + "ico-telegram-ton.com", + "telegram.tw", + "gramton.info", + "gramsap.enterprises", + "ico-telegram.club", + "telegram.one", + "etharscan.io", + "xn--coindes-jhb.com", + "blocikchair.info", + "eth-cz-319.htmlcomponentservice.com", + "xn--myetherwaet-0t8ea.com", + "eth-giveaway-514.htmlcomponentservice.com", + "xn--mtherwallet-z19ex1a.com", + "xn--eo-e9a.com", + "cosss.io", + "callistonetwork.tech", + "xn--metherwallt-819eo1a.com", + "ethfreepays.com", + "nnyettiervwailet.com", + "xn--hapehift-oh0de.com", + "gram.vc", + "bonus.etherpayout.com", + "etherpayout.com", + "ethereum-giveaway.info", + "xn--bnanc-fsax.com", + "xn--binnce-y0a.com", + "geocities.ws", + "eth-giveaway-209.statichtmlapp.com", + "binance.bitballoon.com", + "binance-give.com", + "ilil.pw", + "myetherwallet.com.send-transaction_8hlg6z8mgt6zyg.ilil.pw", + "telegram.site", + "telegram.auction", + "miroskii.com", + "eth-transactions.neocities.org", + "online-eos.org", + "data-token.org", + "eth953883.htmlcomponentservice.com", + "reeveclancy.wixsite.com", + "transfer-eth.neocities.org", + "eth.secure-transactionssecure.com", + "secure-transactionssecure.com", + "nnyiictehervvailiat.com", + "odachi.neocities.org", + "bittrex-give.com", + "policypal.info", + "changlley.org", + "rnyertherwallet.com", + "xn--thabyss-u8a.com", + "callisto-airdrop.com", + "telegramfoundation.org", + "polymathnetwork.org", + "ethereum-promo.bitballoon.com", + "dfintty.org", + "dftnity.org", + "usermd.net", + "xn--gemn-nzab.com", + "xn--bnance-3va.com", + "xn--binnce-yc8b.com", + "xn--bnance-p9a.com", + "xn--binane-0ua.com", + "binalce.com", + "binince.com", + "xn--conbase-pza.com", + "xn--conbase-sfb.com", + "xn--coinbas-xya.com", + "xn--coinbas-z8a.com", + "xn--coinbse-9wa.com", + "xn--coinbse-lwa.com", + "xn--polonex-vfb.com", + "xn--ploniex-l0a.com", + "xn--plniex-bxab.com", + "xn--havvn-9za.io", + "havvenio.com", + "nucelus.vision", + "metronometoken.io", + "meganfoxloveyou.com", + "xn--yetherwalle-iw8e1s.com", + "tron-online.org", + "myenthereumwallet.com", + "myetherwallet-team.com", + "myetherwallet.usermd.net", + "xn--yetherwalle-tv8ezt.com", + "xn--coindek-873c.com", + "dflinity.org", + "ipsx.promo", + "havven-sale.com", + "tokensale-adhive.com", + "ataritoken.ltd", + "transfer-address-confirmation.droppages.com", + "droppages.com", + "dfinlty.org", + "poloniex.work", + "tokensale-havven.in", + "ico-havven.org", + "secure.poloniex.work", + "vechain-foundation.org", + "telegram.tokyo", + "forkdelta.io", + "ton-sale.com", + "ico-telegram.online", + "telegramtoken.io", + "gonetwork-airdrop.co", + "secure-transaction-confirmation.droppages.com", + "neonexchanges.org", + "eth-transact.secure.droppages.com", + "6khb.payment-checker-ethereum-id-bslyftexjuwcgv8.filesusr.com", + "akasha-world.com", + "address-transfer-confirmation.droppages.com", + "b5z.net", + "p.b5z.net", + "tokensale.xn--havve-7l1b.com", + "xn--coindes-bx3c.com", + "xn--coindek-s73c.com", + "locaeltherum.com", + "myetherwallet.tokenimport.com", + "tokenimport.com", + "leadscoin.network", + "localethereom.com", + "havvens.win", + "xn--metherwalle-jb9ejq.com", + "xn--myetheralle-jb9exm.com", + "tefood.me", + "havventoken.com", + "havven.cc", + "havven.eu", + "havven.top", + "mywalletether.com", + "myetherwallett.site", + "xn--myethewalle-w48erh.com", + "el-petro-coin.com", + "xn--yeterwallet-bm8eym.com", + "sale-orchid.com", + "electrify-asia.info", + "tron-foundation.org", + "dflnlty.org", + "polyswarm.tech", + "tokensale.polyswarm.tech", + "polyswarm.ru", + "dock.click", + "leadcoins.network", + "dock-tokensale.eu", + "polyswarm.pw", + "dock-io.org", + "heavven.io", + "havven.live", + "havven.sale", + "havven.info", + "dock.network", + "blocklancer.me", + "tokensale-havven.io", + "havven.pro", + "tokentelegram.com", + "ico-telegram.me", + "myetherzero.com", + "seele-token.pro", + "tokensale-havven.com", + "polyswarm.in", + "sale-dock.com", + "polyswarm.co", + "polyswarm.bid", + "ton.fund", + "forkdeita.github.io", + "xn--binnce-5nf.com", + "xn--biace-4l1bb.com", + "jiocoins.io", + "xn--polonx-0va26t.com", + "myetlherewallet.org", + "myeithereiuwallet.com", + "fin-trux.com", + "finetrux.com", + "telcoin.pro", + "xn--es-8bb.com", + "xn--myetherallt-pl9elw.com", + "ico-dock.org", + "dock.io-bonus.online", + "docks.site", + "dock-ico.pro", + "dock-ico.eu", + "dock.su", + "tokensale-dock.io", + "dock-ico.site", + "openplatform.tech", + "rightmesh.in", + "waxtoken.in", + "metamaskgiveaway.xyz", + "nebulastoken.in", + "vechain-foundation.com", + "icotoken-current.top", "xn--myethrwalle-jb9e19a.com", "xn--myetheralle-7b9ezl.com", "iconfoundation.co", @@ -162,6 +460,7 @@ "sale-earn.com", "bankera.live", "originprotocol.io", + "originprotocol.online", "trx.foundation", "tokensale.adhive.net", "adhive.net", @@ -1369,6 +1668,22 @@ "ethzero-wallet.org", "zeepln.io", "wepowers.network", - "wepower.vision" + "wepower.vision", + "seele.promo", + "seele-ico.eu", + "seele-ico.pro", + "syncfab.tech", + "tokensale.syncfab.tech", + "havven.promo", + "havven-ico.com", + "havven-ico.eu", + "hawen.io", + "havven.xyz", + "tomo-coin.io", + "tomocoln.io", + "docai.science", + "tomocoin.network", + "celsius-ico.network", + "celsiuss.network" ] -} +} \ No newline at end of file -- cgit v1.2.3 From ecbb59495af61d03187e10852be610cd71c076fb Mon Sep 17 00:00:00 2001 From: Thomas Date: Tue, 27 Mar 2018 13:59:54 -0700 Subject: Remove console.log --- test/unit/currency-controller-test.js | 1 - 1 file changed, 1 deletion(-) (limited to 'test') diff --git a/test/unit/currency-controller-test.js b/test/unit/currency-controller-test.js index 63ab60f9e..73a97d120 100644 --- a/test/unit/currency-controller-test.js +++ b/test/unit/currency-controller-test.js @@ -45,7 +45,6 @@ describe('currency-controller', function () { currencyController.updateConversionRate() .then(function () { var result = currencyController.getConversionRate() - console.log('currencyController.getConversionRate:', result) assert.equal(typeof result, 'number') done() }).catch(function (err) { -- cgit v1.2.3 From f66dbec0a477aaeab1d94365bcc5cba61ce19004 Mon Sep 17 00:00:00 2001 From: Thomas Date: Tue, 27 Mar 2018 14:02:00 -0700 Subject: Metamask controller methods --- test/unit/metamask-controller-test.js | 147 +++++++++++++++++++++++++++++++++- 1 file changed, 146 insertions(+), 1 deletion(-) (limited to 'test') diff --git a/test/unit/metamask-controller-test.js b/test/unit/metamask-controller-test.js index adeca9b5f..3e696a9c3 100644 --- a/test/unit/metamask-controller-test.js +++ b/test/unit/metamask-controller-test.js @@ -9,7 +9,7 @@ const firstTimeState = require('../../app/scripts/first-time-state') describe('MetaMaskController', function () { let metamaskController const sandbox = sinon.sandbox.create() - const noop = () => { } + const noop = () => {} beforeEach(function () { @@ -18,6 +18,14 @@ describe('MetaMaskController', function () { .get('/v2/blacklist') .reply(200, blacklistJSON) + nock('https://api.infura.io') + .get('/v1/ticker/ethusd') + .reply(200, '{"base": "ETH", "quote": "USD", "bid": 288.45, "ask": 288.46, "volume": 112888.17569277, "exchange": "bitfinex", "total_volume": 272175.00106721005, "num_exchanges": 8, "timestamp": 1506444677}') + + nock('https://api.infura.io') + .get('/v1/ticker/ethjpy') + .reply(200, '{"base": "ETH", "quote": "JPY", "bid": 32300.0, "ask": 32400.0, "volume": 247.4616071, "exchange": "kraken", "total_volume": 247.4616071, "num_exchanges": 1, "timestamp": 1506444676}') + nock('https://api.infura.io') .persist() .get(/.*/) @@ -46,6 +54,7 @@ describe('MetaMaskController', function () { }) describe('#getGasPrice', function () { + it('gives the 50th percentile lowest accepted gas price from recentBlocksController', async function () { const realRecentBlocksController = metamaskController.recentBlocksController metamaskController.recentBlocksController = { @@ -100,4 +109,140 @@ describe('MetaMaskController', function () { assert(metamaskController.keyringController.createNewVaultAndRestore.calledTwice) }) }) + + describe('#getApi', function () { + let getApi, state + + beforeEach(function () { + getApi = metamaskController.getApi() + }) + + it('getState', function (done) { + getApi.getState((err, res) => { + if (err) { + done(err) + } else { + state = res + } + }) + assert.deepEqual(state, metamaskController.getState()) + done() + }) + + }) + + describe('preferencesController', function () { + + it('defaults useBlockie to false', function () { + assert.equal(metamaskController.preferencesController.store.getState().useBlockie, false) + }) + + it('setUseBlockie to true', async function () { + metamaskController.setUseBlockie(true, noop) + assert.equal(metamaskController.preferencesController.store.getState().useBlockie, true) + }) + + }) + + describe('#selectFirstIdentity', function () { + let identities, address + + beforeEach(function () { + address = '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc' + identities = { + identities: { + '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc': { + 'address': '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc', + 'name': 'Account 1', + }, + }, + } + metamaskController.selectFirstIdentity(identities) + }) + + it('changes preferences controller select address', function () { + const preferenceControllerState = metamaskController.preferencesController.store.getState() + assert.equal(preferenceControllerState.selectedAddress, address) + }) + + it('changes metamask controller selected address', function () { + const metamaskState = metamaskController.getState() + assert.equal(metamaskState.selectedAddress, address) + }) + }) + + describe('#setCustomRpc', function () { + const customRPC = 'https://custom.rpc/' + let rpcTarget + + beforeEach(function () { + + nock('https://custom.rpc') + .post('/') + .reply(200) + + rpcTarget = metamaskController.setCustomRpc(customRPC) + }) + + it('returns custom RPC that when called', async function () { + assert.equal(await rpcTarget, customRPC) + }) + + it('changes the network controller rpc', function () { + const networkControllerState = metamaskController.networkController.store.getState() + assert.equal(networkControllerState.provider.rpcTarget, customRPC) + }) + }) + + describe('#setCurrentCurrency', function () { + let defaultMetaMaskCurrency + + beforeEach(function () { + defaultMetaMaskCurrency = metamaskController.currencyController.getCurrentCurrency() + }) + + it('defaults to usd', function () { + assert.equal(defaultMetaMaskCurrency, 'usd') + }) + + it('sets currency to JPY', function () { + metamaskController.setCurrentCurrency('JPY', noop) + assert.equal(metamaskController.currencyController.getCurrentCurrency(), 'JPY') + }) + }) + + describe('#createShapeshifttx', function () { + let depositAddress, depositType, shapeShiftTxList + beforeEach(function () { + nock('https://shapeshift.io') + .get('/txStat/3EevLFfB4H4XMWQwYCgjLie1qCAGpd2WBc') + .reply(200, '{"status": "no_deposits", "address": "3EevLFfB4H4XMWQwYCgjLie1qCAGpd2WBc"}') + + depositAddress = '3EevLFfB4H4XMWQwYCgjLie1qCAGpd2WBc' + depositType = 'ETH' + shapeShiftTxList = metamaskController.shapeshiftController.store.getState().shapeShiftTxList + }) + + it('creates', async function () { + metamaskController.createShapeShiftTx(depositAddress, depositType) + assert.equal(shapeShiftTxList[0].depositAddress, depositAddress) + }) + }) + + describe('#addNewAccount', function () { + let addNewAccount + + beforeEach(function () { + addNewAccount = metamaskController.addNewAccount() + }) + + it('errors when an primary keyring is does not exist', async function () { + try { + await addNewAccount + assert.equal(1 === 0) + } catch (e) { + assert.equal(e.message, 'MetamaskController - No HD Key Tree found') + } + }) + }) }) -- cgit v1.2.3 From 7aa54916d8508aababd202eef2e57bc90c6c842d Mon Sep 17 00:00:00 2001 From: Thomas Date: Wed, 4 Apr 2018 14:26:18 -0700 Subject: Update Sinon methods. sinon.sandbox.create() -> sandbox.createSandbox() --- test/unit/actions/tx_test.js | 2 +- test/unit/infura-controller-test.js | 2 +- test/unit/metamask-controller-test.js | 4 ++-- test/unit/nameForAccount_test.js | 2 +- test/unit/pending-tx-test.js | 2 +- test/unit/reducers/unlock_vault_test.js | 2 +- test/unit/util_test.js | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) (limited to 'test') diff --git a/test/unit/actions/tx_test.js b/test/unit/actions/tx_test.js index b6a691860..c110f71fc 100644 --- a/test/unit/actions/tx_test.js +++ b/test/unit/actions/tx_test.js @@ -9,7 +9,7 @@ var reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'redu describe('tx confirmation screen', function () { beforeEach(function () { - this.sinon = sinon.sandbox.create() + this.sinon = sinon.createSandbox() }) afterEach(function () { diff --git a/test/unit/infura-controller-test.js b/test/unit/infura-controller-test.js index 605305efa..c9f0e7587 100644 --- a/test/unit/infura-controller-test.js +++ b/test/unit/infura-controller-test.js @@ -8,7 +8,7 @@ describe('infura-controller', function () { before(async function () { infuraController = new InfuraController() - sandbox = sinon.sandbox.create() + sandbox = sinon.createSandbox() sinon.stub(infuraController, 'checkInfuraNetworkStatus').resolves(response) networkStatus = await infuraController.checkInfuraNetworkStatus() }) diff --git a/test/unit/metamask-controller-test.js b/test/unit/metamask-controller-test.js index 3e696a9c3..b65e9184d 100644 --- a/test/unit/metamask-controller-test.js +++ b/test/unit/metamask-controller-test.js @@ -8,7 +8,7 @@ const firstTimeState = require('../../app/scripts/first-time-state') describe('MetaMaskController', function () { let metamaskController - const sandbox = sinon.sandbox.create() + const sandbox = sinon.createSandbox() const noop = () => {} beforeEach(function () { @@ -36,7 +36,7 @@ describe('MetaMaskController', function () { encryptor: { encrypt: function (password, object) { this.object = object - return Promise.resolve() + return Promise.resolve()g }, decrypt: function () { return Promise.resolve(this.object) diff --git a/test/unit/nameForAccount_test.js b/test/unit/nameForAccount_test.js index 32af49e9d..9bb02c6bc 100644 --- a/test/unit/nameForAccount_test.js +++ b/test/unit/nameForAccount_test.js @@ -6,7 +6,7 @@ var contractNamer = require(path.join(__dirname, '..', '..', 'old-ui', 'lib', 'c describe('contractNamer', function () { beforeEach(function () { - this.sinon = sinon.sandbox.create() + this.sinon = sinon.createSandbox() }) afterEach(function () { diff --git a/test/unit/pending-tx-test.js b/test/unit/pending-tx-test.js index f0b4e3bfc..62f4f28a8 100644 --- a/test/unit/pending-tx-test.js +++ b/test/unit/pending-tx-test.js @@ -294,7 +294,7 @@ describe('PendingTransactionTracker', function () { }) afterEach(() => { - pendingTxTracker.publishTransaction.reset() + pendingTxTracker.publishTransaction.restore() }) it('should publish the transaction', function (done) { diff --git a/test/unit/reducers/unlock_vault_test.js b/test/unit/reducers/unlock_vault_test.js index 2b7d70b2c..d66e8edbb 100644 --- a/test/unit/reducers/unlock_vault_test.js +++ b/test/unit/reducers/unlock_vault_test.js @@ -10,7 +10,7 @@ var reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'redu describe('#unlockMetamask(selectedAccount)', function () { beforeEach(function () { // sinon allows stubbing methods that are easily verified - this.sinon = sinon.sandbox.create() + this.sinon = sinon.createSandbox() }) afterEach(function () { diff --git a/test/unit/util_test.js b/test/unit/util_test.js index 59048975a..39473854f 100644 --- a/test/unit/util_test.js +++ b/test/unit/util_test.js @@ -10,7 +10,7 @@ describe('util', function () { for (var i = 0; i < 18; i++) { ethInWei += '0' } beforeEach(function () { - this.sinon = sinon.sandbox.create() + this.sinon = sinon.createSandbox() }) afterEach(function () { -- cgit v1.2.3 From 30474ccd35d5d7f30ffb8dff0acc8fbc77f44731 Mon Sep 17 00:00:00 2001 From: Thomas Date: Wed, 11 Apr 2018 08:34:35 -0700 Subject: MM-controlller tests --- test/unit/metamask-controller-test.js | 57 +++++++++++++++++++++++++++++++++-- 1 file changed, 55 insertions(+), 2 deletions(-) (limited to 'test') diff --git a/test/unit/metamask-controller-test.js b/test/unit/metamask-controller-test.js index b65e9184d..454b0ae44 100644 --- a/test/unit/metamask-controller-test.js +++ b/test/unit/metamask-controller-test.js @@ -36,7 +36,7 @@ describe('MetaMaskController', function () { encryptor: { encrypt: function (password, object) { this.object = object - return Promise.resolve()g + return Promise.resolve() }, decrypt: function () { return Promise.resolve(this.object) @@ -213,6 +213,7 @@ describe('MetaMaskController', function () { describe('#createShapeshifttx', function () { let depositAddress, depositType, shapeShiftTxList + beforeEach(function () { nock('https://shapeshift.io') .get('/txStat/3EevLFfB4H4XMWQwYCgjLie1qCAGpd2WBc') @@ -223,10 +224,11 @@ describe('MetaMaskController', function () { shapeShiftTxList = metamaskController.shapeshiftController.store.getState().shapeShiftTxList }) - it('creates', async function () { + it('creates a shapeshift tx', async function () { metamaskController.createShapeShiftTx(depositAddress, depositType) assert.equal(shapeShiftTxList[0].depositAddress, depositAddress) }) + }) describe('#addNewAccount', function () { @@ -245,4 +247,55 @@ describe('MetaMaskController', function () { } }) }) + + describe('#verifyseedPhrase', function () { + let seedPhrase, getConfigSeed + + it('errors when no keying is provided', async function () { + try { + await metamaskController.verifySeedPhrase() + } catch (error) { + assert.equal(error.message, 'MetamaskController - No HD Key Tree found') + } + }) + + beforeEach(async function () { + await metamaskController.createNewVaultAndKeychain('password') + seedPhrase = await metamaskController.verifySeedPhrase() + }) + + it('#placeSeedWords should match the initially created vault seed', function () { + + metamaskController.placeSeedWords((err, result) => { + if (err) { + console.log(err) + } else { + getConfigSeed = metamaskController.configManager.getSeedWords() + assert.equal(result, seedPhrase) + assert.equal(result, getConfigSeed) + } + }) + assert.equal(getConfigSeed, undefined) + }) + }) + + describe('#clearSeedWordCache', function () { + + it('should have set seed words', function () { + metamaskController.configManager.setSeedWords('test words') + const getConfigSeed = metamaskController.configManager.getSeedWords() + assert.equal(getConfigSeed, 'test words') + }) + + it('should clear config seed phrase', function () { + metamaskController.configManager.setSeedWords('test words') + metamaskController.clearSeedWordCache((err, result) => { + if (err) console.log(err) + }) + const getConfigSeed = metamaskController.configManager.getSeedWords() + assert.equal(getConfigSeed, null) + }) + + }) + }) -- cgit v1.2.3 From 30df8b7995c61ce1ede06f9caef5c507fb678ddd Mon Sep 17 00:00:00 2001 From: Thomas Date: Wed, 18 Apr 2018 11:26:49 -0700 Subject: newUnsignedMessage and newUnsignedPersonalMessage --- test/unit/metamask-controller-test.js | 102 ++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) (limited to 'test') diff --git a/test/unit/metamask-controller-test.js b/test/unit/metamask-controller-test.js index 454b0ae44..52b67fdf6 100644 --- a/test/unit/metamask-controller-test.js +++ b/test/unit/metamask-controller-test.js @@ -33,6 +33,7 @@ describe('MetaMaskController', function () { metamaskController = new MetaMaskController({ showUnapprovedTx: noop, + showUnconfirmedMessage: noop, encryptor: { encrypt: function (password, object) { this.object = object @@ -298,4 +299,105 @@ describe('MetaMaskController', function () { }) + describe('#setCurrentLocale', function () { + + it('checks the default currentLocale', function () { + const preferenceCurrentLocale = metamaskController.preferencesController.store.getState().currentLocale + assert.equal(preferenceCurrentLocale, undefined) + }) + + it('sets current locale in preferences controller', function () { + metamaskController.setCurrentLocale('ja', noop) + const preferenceCurrentLocale = metamaskController.preferencesController.store.getState().currentLocale + assert.equal(preferenceCurrentLocale, 'ja') + }) + + }) + + describe('#newUnsignedMessage', function () { + + let msgParams, metamaskMsgs, messages, msgId + + const address = '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc' + const data = '0x43727970746f6b697474696573' + + beforeEach(function () { + + msgParams = { + 'from': address, + 'data': data, + } + + metamaskController.newUnsignedMessage(msgParams, noop) + metamaskMsgs = metamaskController.messageManager.getUnapprovedMsgs() + messages = metamaskController.messageManager.messages + msgId = Object.keys(metamaskMsgs)[0] + }) + + it('persists address from msg params', function () { + assert.equal(metamaskMsgs[msgId].msgParams.from, address) + }) + + it('persists data from msg params', function () { + assert.equal(metamaskMsgs[msgId].msgParams.data, data) + }) + + it('sets the status to unapproved', function () { + assert.equal(metamaskMsgs[msgId].status, 'unapproved') + }) + + it('sets the type to eth_sign', function () { + assert.equal(metamaskMsgs[msgId].type, 'eth_sign') + }) + + it('rejects the message', function () { + const msgIdInt = parseInt(msgId) + metamaskController.cancelMessage(msgIdInt, noop) + assert.equal(messages[0].status, 'rejected') + }) + }) + + describe('#newUnsignedPersonalMessage', function () { + + let msgParams, metamaskMsgs, messages, msgId + + const address = '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc' + const data = '0x43727970746f6b697474696573' + + beforeEach(function () { + + msgParams = { + 'from': address, + 'data': data, + } + + metamaskController.newUnsignedPersonalMessage(msgParams, noop) + metamaskMsgs = metamaskController.personalMessageManager.getUnapprovedMsgs() + messages = metamaskController.personalMessageManager.messages + msgId = Object.keys(metamaskMsgs)[0] + }) + + it('persists address from msg params', function () { + assert.equal(metamaskMsgs[msgId].msgParams.from, address) + }) + + it('persists data from msg params', function () { + assert.equal(metamaskMsgs[msgId].msgParams.data, data) + }) + + it('sets the status to unapproved', function () { + assert.equal(metamaskMsgs[msgId].status, 'unapproved') + }) + + it('sets the type to personal_sign', function () { + assert.equal(metamaskMsgs[msgId].type, 'personal_sign') + }) + + it('rejects the message', function () { + const msgIdInt = parseInt(msgId) + metamaskController.cancelPersonalMessage(msgIdInt, noop) + assert.equal(messages[0].status, 'rejected') + }) + }) + }) -- cgit v1.2.3 From 2a1912642c55fe16e180a80a0b8fa00ac13722df Mon Sep 17 00:00:00 2001 From: Thomas Date: Thu, 19 Apr 2018 14:36:33 -0700 Subject: resetAccount and addNewAccount --- test/unit/metamask-controller-test.js | 45 +++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) (limited to 'test') diff --git a/test/unit/metamask-controller-test.js b/test/unit/metamask-controller-test.js index 52b67fdf6..63b8ecf56 100644 --- a/test/unit/metamask-controller-test.js +++ b/test/unit/metamask-controller-test.js @@ -2,13 +2,21 @@ const assert = require('assert') const sinon = require('sinon') const clone = require('clone') const nock = require('nock') +const ObservableStore = require('obs-store') const MetaMaskController = require('../../app/scripts/metamask-controller') +const TransactionController = require('../../app/scripts/controllers/transactions') const blacklistJSON = require('../stub/blacklist') const firstTimeState = require('../../app/scripts/first-time-state') +const { createTestProviderTools } = require('../stub/provider') + +const currentNetworkId = 42 describe('MetaMaskController', function () { let metamaskController const sandbox = sinon.createSandbox() + const providerResultStub = {} + const providerTools = createTestProviderTools({ scaffold: providerResultStub }) + const provider = providerTools.provider const noop = () => {} beforeEach(function () { @@ -278,6 +286,34 @@ describe('MetaMaskController', function () { }) assert.equal(getConfigSeed, undefined) }) + + it('#addNewAccount', async function () { + await metamaskController.addNewAccount() + const getAccounts = await metamaskController.keyringController.getAccounts() + assert.equal(getAccounts.length, 2) + }) + }) + + describe('#resetAccount', function () { + + beforeEach(function () { + const selectedAddressStub = sinon.stub(metamaskController.preferencesController, 'getSelectedAddress') + const getNetworkstub = sinon.stub(metamaskController.txController.txStateManager, 'getNetwork') + + selectedAddressStub.returns('0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc') + getNetworkstub.returns(42) + + metamaskController.txController.txStateManager._saveTxList([ + { id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {from: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc'} }, + { id: 2, status: 'rejected', metamaskNetworkId: 32, txParams: {} }, + { id: 3, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams: {from: '0xB09d8505E1F4EF1CeA089D47094f5DD3464083d4'} }, + ]) + }) + + it('wipes transactions from only the correct network id and with the selected address', async function () { + await metamaskController.resetAccount() + assert.equal(metamaskController.txController.txStateManager.getTx(1), undefined) + }) }) describe('#clearSeedWordCache', function () { @@ -359,6 +395,15 @@ describe('MetaMaskController', function () { describe('#newUnsignedPersonalMessage', function () { + it('errors with no from in msgParams', function () { + const msgParams = { + 'data': data, + } + metamaskController.newUnsignedPersonalMessage(msgParams, function (error) { + assert.equal(error.message, 'MetaMask Message Signature: from field is required.') + }) + }) + let msgParams, metamaskMsgs, messages, msgId const address = '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc' -- cgit v1.2.3 From ddece0cc11215b323df96287f39e6758aa559f77 Mon Sep 17 00:00:00 2001 From: Thomas Date: Mon, 23 Apr 2018 11:21:48 -0700 Subject: ConfigManager markAccountsFound, markPasswordForgotten, unMarkPasswordForgotten --- test/unit/metamask-controller-test.js | 77 +++++++++++++++++++++++++++++++---- 1 file changed, 70 insertions(+), 7 deletions(-) (limited to 'test') diff --git a/test/unit/metamask-controller-test.js b/test/unit/metamask-controller-test.js index 63b8ecf56..c9d041f01 100644 --- a/test/unit/metamask-controller-test.js +++ b/test/unit/metamask-controller-test.js @@ -2,21 +2,15 @@ const assert = require('assert') const sinon = require('sinon') const clone = require('clone') const nock = require('nock') -const ObservableStore = require('obs-store') const MetaMaskController = require('../../app/scripts/metamask-controller') -const TransactionController = require('../../app/scripts/controllers/transactions') const blacklistJSON = require('../stub/blacklist') const firstTimeState = require('../../app/scripts/first-time-state') -const { createTestProviderTools } = require('../stub/provider') const currentNetworkId = 42 describe('MetaMaskController', function () { let metamaskController const sandbox = sinon.createSandbox() - const providerResultStub = {} - const providerTools = createTestProviderTools({ scaffold: providerResultStub }) - const provider = providerTools.provider const noop = () => {} beforeEach(function () { @@ -146,7 +140,7 @@ describe('MetaMaskController', function () { assert.equal(metamaskController.preferencesController.store.getState().useBlockie, false) }) - it('setUseBlockie to true', async function () { + it('setUseBlockie to true', function () { metamaskController.setUseBlockie(true, noop) assert.equal(metamaskController.preferencesController.store.getState().useBlockie, true) }) @@ -445,4 +439,73 @@ describe('MetaMaskController', function () { }) }) + describe('#approvePersonalMessage', function () { + + it('errors with no from in msgParams', function () { + const msgParams = { + 'data': data, + } + metamaskController.approvePersonalMessage(msgParams, function (error) { + assert.equal(error.message, 'MetaMask Message Signature: from field is required.') + }) + }) + + let msgParams, metamaskMsgs, msgId + + const address = '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc' + const data = '0x43727970746f6b697474696573' + + beforeEach(function () { + + msgParams = { + 'from': address, + 'data': data, + } + + metamaskController.approvePersonalMessage(msgParams, noop) + metamaskMsgs = metamaskController.personalMessageManager.getUnapprovedMsgs() + msgId = Object.keys(metamaskMsgs)[0] + }) + + it('persists address from msg params', function () { + assert.equal(metamaskMsgs[msgId].msgParams.from, address) + }) + + it('persists data from msg params', function () { + assert.equal(metamaskMsgs[msgId].msgParams.data, data) + }) + + it('sets the status to unapproved', function () { + assert.equal(metamaskMsgs[msgId].status, 'unapproved') + }) + + it('sets the type to personal_sign', function () { + assert.equal(metamaskMsgs[msgId].type, 'personal_sign') + }) + }) + + describe('#markAccountsFound', function () { + it('adds lost accounts to config manager data', function () { + metamaskController.markAccountsFound(noop) + const configManagerData = metamaskController.configManager.getData() + assert.deepEqual(configManagerData.lostAccounts, []) + }) + }) + + describe('#markPasswordForgotten', function () { + it('adds and sets forgottenPassword to config data to true', function () { + metamaskController.markPasswordForgotten(noop) + const configManagerData = metamaskController.configManager.getData() + assert.equal(configManagerData.forgottenPassword, true) + }) + }) + + describe('#unMarkPasswordForgotten', function () { + it('adds and sets forgottenPassword to config data to false', function () { + metamaskController.unMarkPasswordForgotten(noop) + const configManagerData = metamaskController.configManager.getData() + assert.equal(configManagerData.forgottenPassword, false) + }) + }) + }) -- cgit v1.2.3 From 629457ea5460a1841d4682a234d6a6fb330517f9 Mon Sep 17 00:00:00 2001 From: Thomas Date: Tue, 24 Apr 2018 13:33:31 -0700 Subject: setupUntrustedCommunication and setupTrustedCommunication --- test/unit/metamask-controller-test.js | 45 +++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) (limited to 'test') diff --git a/test/unit/metamask-controller-test.js b/test/unit/metamask-controller-test.js index c9d041f01..acf105d4d 100644 --- a/test/unit/metamask-controller-test.js +++ b/test/unit/metamask-controller-test.js @@ -2,6 +2,7 @@ const assert = require('assert') const sinon = require('sinon') const clone = require('clone') const nock = require('nock') +const createThoughStream = require('through2').obj const MetaMaskController = require('../../app/scripts/metamask-controller') const blacklistJSON = require('../stub/blacklist') const firstTimeState = require('../../app/scripts/first-time-state') @@ -187,6 +188,10 @@ describe('MetaMaskController', function () { rpcTarget = metamaskController.setCustomRpc(customRPC) }) + afterEach(function () { + nock.cleanAll() + }) + it('returns custom RPC that when called', async function () { assert.equal(await rpcTarget, customRPC) }) @@ -484,6 +489,46 @@ describe('MetaMaskController', function () { }) }) + describe('#setupUntrustedCommunication', function () { + let streamTest + + const phishingUrl = 'decentral.market' + + afterEach(function () { + streamTest.end() + }) + + it('sets up phishing stream for untrusted communication ', async function () { + await metamaskController.blacklistController.updatePhishingList() + + streamTest = createThoughStream((chunk, enc, cb) => { + assert.equal(chunk.name, 'phishing') + assert.equal(chunk.data.hostname, phishingUrl) + cb() + }) + // console.log(streamTest) + metamaskController.setupUntrustedCommunication(streamTest, phishingUrl) + }) + }) + + describe('#setupTrustedCommunication', function () { + let streamTest + + afterEach(function () { + streamTest.end() + }) + + it('sets up controller dnode api for trusted communication', function (done) { + streamTest = createThoughStream((chunk, enc, cb) => { + assert.equal(chunk.name, 'controller') + cb() + done() + }) + + metamaskController.setupTrustedCommunication(streamTest, 'mycrypto.com') + }) + }) + describe('#markAccountsFound', function () { it('adds lost accounts to config manager data', function () { metamaskController.markAccountsFound(noop) -- cgit v1.2.3 From f2e1cb9302320c9abf2814bfdaea98aee7add92e Mon Sep 17 00:00:00 2001 From: Thomas Date: Mon, 7 May 2018 14:56:33 -0700 Subject: Remove approvePersonalMessage test --- test/unit/metamask-controller-test.js | 45 ----------------------------------- 1 file changed, 45 deletions(-) (limited to 'test') diff --git a/test/unit/metamask-controller-test.js b/test/unit/metamask-controller-test.js index ae9f549ad..332ccfdb8 100644 --- a/test/unit/metamask-controller-test.js +++ b/test/unit/metamask-controller-test.js @@ -460,51 +460,6 @@ describe('MetaMaskController', function () { }) }) - describe('#approvePersonalMessage', function () { - - it('errors with no from in msgParams', function () { - const msgParams = { - 'data': data, - } - metamaskController.approvePersonalMessage(msgParams, function (error) { - assert.equal(error.message, 'MetaMask Message Signature: from field is required.') - }) - }) - - let msgParams, metamaskMsgs, msgId - - const address = '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc' - const data = '0x43727970746f6b697474696573' - - beforeEach(function () { - - msgParams = { - 'from': address, - 'data': data, - } - - metamaskController.approvePersonalMessage(msgParams, noop) - metamaskMsgs = metamaskController.personalMessageManager.getUnapprovedMsgs() - msgId = Object.keys(metamaskMsgs)[0] - }) - - it('persists address from msg params', function () { - assert.equal(metamaskMsgs[msgId].msgParams.from, address) - }) - - it('persists data from msg params', function () { - assert.equal(metamaskMsgs[msgId].msgParams.data, data) - }) - - it('sets the status to unapproved', function () { - assert.equal(metamaskMsgs[msgId].status, 'unapproved') - }) - - it('sets the type to personal_sign', function () { - assert.equal(metamaskMsgs[msgId].type, 'personal_sign') - }) - }) - describe('#setupUntrustedCommunication', function () { let streamTest -- cgit v1.2.3 From 9a1661918f19a258c22d2c61b7ea972f9640667d Mon Sep 17 00:00:00 2001 From: Thomas Date: Wed, 9 May 2018 01:51:55 -0700 Subject: ImportAccountWithStrategies Json and Keystore --- test/unit/account-import-strategies.spec.js | 30 +++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 test/unit/account-import-strategies.spec.js (limited to 'test') diff --git a/test/unit/account-import-strategies.spec.js b/test/unit/account-import-strategies.spec.js new file mode 100644 index 000000000..e0417299a --- /dev/null +++ b/test/unit/account-import-strategies.spec.js @@ -0,0 +1,30 @@ +const assert = require('assert') +const accountImporter = require('../../app/scripts/account-import-strategies/index') +const ethUtil = require('ethereumjs-util') + +describe('Account Import Strategies', function () { + const privkey = '0x4cfd3e90fc78b0f86bf7524722150bb8da9c60cd532564d7ff43f5716514f553' + const json = '{"version":3,"id":"dbb54385-0a99-437f-83c0-647de9f244c3","address":"a7f92ce3fba24196cf6f4bd2e1eb3db282ba998c","Crypto":{"ciphertext":"bde13d9ade5c82df80281ca363320ce254a8a3a06535bbf6ffdeaf0726b1312c","cipherparams":{"iv":"fbf93718a57f26051b292f072f2e5b41"},"cipher":"aes-128-ctr","kdf":"scrypt","kdfparams":{"dklen":32,"salt":"7ffe00488319dec48e4c49a120ca49c6afbde9272854c64d9541c83fc6acdffe","n":8192,"r":8,"p":1},"mac":"2adfd9c4bc1cdac4c85bddfb31d9e21a684e0e050247a70c5698facf6b7d4681"}}' + + it('imports a private key and strips 0x prefix', async function () { + const importPrivKey = await accountImporter.importAccount('Private Key', [ privkey ]) + assert.equal(importPrivKey, ethUtil.stripHexPrefix(privkey)) + }) + + it('fails when password is incorrect for keystore', async function () { + const wrongPassword = 'password2' + + try { + await accountImporter.importAccount('JSON File', [ json, wrongPassword]) + } catch (error) { + assert.equal(error.message, 'Key derivation failed - possibly wrong passphrase') + } + }) + + it('imports json string and password to return a private key', async function () { + const fileContentsPassword = 'password1' + const importJson = await accountImporter.importAccount('JSON File', [ json, fileContentsPassword]) + console.log(importJson) + }) + +}) -- cgit v1.2.3 From f441153211c7920573f8bfb699bfda1b6de7efe9 Mon Sep 17 00:00:00 2001 From: Thomas Date: Wed, 9 May 2018 01:58:43 -0700 Subject: console.log -> assert.equal --- test/unit/account-import-strategies.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'test') diff --git a/test/unit/account-import-strategies.spec.js b/test/unit/account-import-strategies.spec.js index e0417299a..acc39c88c 100644 --- a/test/unit/account-import-strategies.spec.js +++ b/test/unit/account-import-strategies.spec.js @@ -24,7 +24,7 @@ describe('Account Import Strategies', function () { it('imports json string and password to return a private key', async function () { const fileContentsPassword = 'password1' const importJson = await accountImporter.importAccount('JSON File', [ json, fileContentsPassword]) - console.log(importJson) + assert.equal(importJson, '0x5733876abe94146069ce8bcbabbde2677f2e35fa33e875e92041ed2ac87e5bc7') }) }) -- cgit v1.2.3 From cd42d1820318739668774af25e9b1318cd2256dd Mon Sep 17 00:00:00 2001 From: Thomas Date: Mon, 14 May 2018 17:17:36 -0700 Subject: Merge e2e tests into one --- test/e2e/chrome/metamask.spec.js | 314 ------------------------------------- test/e2e/firefox/metamask.spec.js | 322 -------------------------------------- test/e2e/func.js | 42 ++++- test/e2e/metamask.spec.js | 320 +++++++++++++++++++++++++++++++++++++ 4 files changed, 358 insertions(+), 640 deletions(-) delete mode 100644 test/e2e/chrome/metamask.spec.js delete mode 100644 test/e2e/firefox/metamask.spec.js create mode 100644 test/e2e/metamask.spec.js (limited to 'test') diff --git a/test/e2e/chrome/metamask.spec.js b/test/e2e/chrome/metamask.spec.js deleted file mode 100644 index d72ebe1a9..000000000 --- a/test/e2e/chrome/metamask.spec.js +++ /dev/null @@ -1,314 +0,0 @@ -const fs = require('fs') -const mkdirp = require('mkdirp') -const path = require('path') -const assert = require('assert') -const pify = require('pify') -const webdriver = require('selenium-webdriver') -const until = require('selenium-webdriver/lib/until') -const By = webdriver.By -const { delay, buildChromeWebDriver } = require('../func') - -describe('Metamask popup page', function () { - let driver, accountAddress, tokenAddress, extensionId - - this.timeout(0) - - before(async function () { - const extPath = path.resolve('dist/chrome') - driver = buildChromeWebDriver(extPath) - await driver.get('chrome://extensions') - await delay(500) - }) - - afterEach(async function () { - if (this.currentTest.state === 'failed') { - await verboseReportOnFailure(this.currentTest) - } - }) - - after(async function () { - await driver.quit() - }) - - describe('Setup', function () { - - it('switches to Chrome extensions list', async function () { - const tabs = await driver.getAllWindowHandles() - await driver.switchTo().window(tabs[0]) - await delay(300) - }) - - it(`selects MetaMask's extension id and opens it in the current tab`, async function () { - extensionId = await getExtensionId() - await driver.get(`chrome-extension://${extensionId}/popup.html`) - await delay(500) - }) - - it('sets provider type to localhost', async function () { - await driver.wait(until.elementLocated(By.css('#app-content')), 300) - await setProviderType('localhost') - }) - }) - - describe('Account Creation', () => { - - it('matches MetaMask title', async () => { - const title = await driver.getTitle() - assert.equal(title, 'MetaMask', 'title matches MetaMask') - }) - - it('shows privacy notice', async () => { - await driver.wait(async () => { - const privacyHeader = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div.flex-column.flex-center.flex-grow > h3')).getText() - assert.equal(privacyHeader, 'PRIVACY NOTICE', 'shows privacy notice') - return privacyHeader === 'PRIVACY NOTICE' - }, 300) - await driver.findElement(By.css('button')).click() - }) - - it('show terms of use', async () => { - await driver.wait(async () => { - const terms = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div.flex-column.flex-center.flex-grow > h3')).getText() - assert.equal(terms, 'TERMS OF USE', 'shows terms of use') - return terms === 'TERMS OF USE' - }) - }) - - it('checks if the TOU button is disabled', async () => { - const button = await driver.findElement(By.css('button')).isEnabled() - assert.equal(button, false, 'disabled continue button') - const element = await driver.findElement(By.linkText('Attributions')) - await driver.executeScript('arguments[0].scrollIntoView(true)', element) - await delay(300) - }) - - it('allows the button to be clicked when scrolled to the bottom of TOU', async () => { - const button = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div.flex-column.flex-center.flex-grow > button')) - const buttonEnabled = await button.isEnabled() - assert.equal(buttonEnabled, true, 'enabled continue button') - await button.click() - }) - - it('accepts password with length of eight', async () => { - const passwordBox = await driver.findElement(By.id('password-box')) - const passwordBoxConfirm = await driver.findElement(By.id('password-box-confirm')) - const button = await driver.findElements(By.css('button')) - - await passwordBox.sendKeys('123456789') - await passwordBoxConfirm.sendKeys('123456789') - await button[0].click() - await delay(500) - }) - - it('shows value was created and seed phrase', async () => { - await delay(300) - const seedPhrase = await driver.findElement(By.css('.twelve-word-phrase')).getText() - assert.equal(seedPhrase.split(' ').length, 12) - const continueAfterSeedPhrase = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > button:nth-child(4)')) - assert.equal(await continueAfterSeedPhrase.getText(), `I'VE COPIED IT SOMEWHERE SAFE`) - await continueAfterSeedPhrase.click() - await delay(300) - }) - - it('shows account address', async function () { - accountAddress = await driver.findElement(By.css('#app-content > div > div.app-primary.from-left > div > div > div:nth-child(1) > flex-column > div.flex-row > div')).getText() - }) - - it('logs out of the vault', async () => { - await driver.findElement(By.css('.sandwich-expando')).click() - await delay(500) - const logoutButton = await driver.findElement(By.css('#app-content > div > div:nth-child(3) > span > div > li:nth-child(3)')) - assert.equal(await logoutButton.getText(), 'Log Out') - await logoutButton.click() - }) - - it('accepts account password after lock', async () => { - await delay(500) - await driver.findElement(By.id('password-box')).sendKeys('123456789') - await driver.findElement(By.css('button')).click() - await delay(500) - }) - - it('shows QR code option', async () => { - await delay(300) - await driver.findElement(By.css('.fa-ellipsis-h')).click() - await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div > div:nth-child(1) > flex-column > div.name-label > div > span > i > div > div > li:nth-child(3)')).click() - await delay(300) - }) - - it('checks QR code address is the same as account details address', async () => { - const QRaccountAddress = await driver.findElement(By.css('.ellip-address')).getText() - assert.equal(accountAddress.toLowerCase(), QRaccountAddress) - await driver.findElement(By.css('.fa-arrow-left')).click() - await delay(500) - }) - }) - - describe('Import Ganache seed phrase', function () { - it('logs out', async function () { - await driver.findElement(By.css('.sandwich-expando')).click() - await delay(200) - const logOut = await driver.findElement(By.css('#app-content > div > div:nth-child(3) > span > div > li:nth-child(3)')) - assert.equal(await logOut.getText(), 'Log Out') - await logOut.click() - await delay(300) - }) - - it('restores from seed phrase', async function () { - const restoreSeedLink = await driver.findElement(By.css('#app-content > div > div.app-primary.from-left > div > div.flex-row.flex-center.flex-grow > p')) - assert.equal(await restoreSeedLink.getText(), 'Restore from seed phrase') - await restoreSeedLink.click() - await delay(100) - }) - - it('adds seed phrase', async function () { - const testSeedPhrase = 'phrase upgrade clock rough situate wedding elder clever doctor stamp excess tent' - const seedTextArea = await driver.findElement(By.css('#app-content > div > div.app-primary.from-left > div > textarea')) - await seedTextArea.sendKeys(testSeedPhrase) - - await driver.findElement(By.id('password-box')).sendKeys('123456789') - await driver.findElement(By.id('password-box-confirm')).sendKeys('123456789') - await driver.findElement(By.css('#app-content > div > div.app-primary.from-left > div > div > button:nth-child(2)')).click() - await delay(500) - }) - - it('balance renders', async function () { - await delay(200) - const balance = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div > div.flex-row > div.ether-balance.ether-balance-amount > div > div > div:nth-child(1) > div:nth-child(1)')) - assert.equal(await balance.getText(), '100.000') - await delay(200) - }) - - it('sends transaction', async function () { - const sendButton = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div > div.flex-row > button:nth-child(4)')) - assert.equal(await sendButton.getText(), 'SEND') - await sendButton.click() - await delay(200) - }) - - it('adds recipient address and amount', async function () { - const sendTranscationScreen = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > h3:nth-child(2)')).getText() - assert.equal(sendTranscationScreen, 'SEND TRANSACTION') - const inputAddress = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > section:nth-child(3) > div > input')) - const inputAmmount = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > section:nth-child(4) > input')) - await inputAddress.sendKeys('0x2f318C334780961FB129D2a6c30D0763d9a5C970') - await inputAmmount.sendKeys('10') - await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > section:nth-child(4) > button')).click() - await delay(300) - }) - - it('confirms transaction', async function () { - await delay(300) - await driver.findElement(By.css('#pending-tx-form > div.flex-row.flex-space-around.conf-buttons > input')).click() - await delay(500) - }) - - it('finds the transaction in the transactions list', async function () { - const tranasactionAmount = await driver.findElement(By.css('#app-content > div > div.app-primary.from-left > div > section > section > div > div > div > div.ether-balance.ether-balance-amount > div > div > div > div:nth-child(1)')) - assert.equal(await tranasactionAmount.getText(), '10.0') - }) - }) - - describe('Token Factory', function () { - - it('navigates to token factory', async function () { - await driver.get('http://tokenfactory.surge.sh/') - }) - - it('navigates to create token contract link', async function () { - const createToken = await driver.findElement(By.css('#bs-example-navbar-collapse-1 > ul > li:nth-child(3) > a')) - await createToken.click() - }) - - it('adds input for token', async function () { - const totalSupply = await driver.findElement(By.css('#main > div > div > div > div:nth-child(2) > div > div:nth-child(5) > input')) - const tokenName = await driver.findElement(By.css('#main > div > div > div > div:nth-child(2) > div > div:nth-child(6) > input')) - const tokenDecimal = await driver.findElement(By.css('#main > div > div > div > div:nth-child(2) > div > div:nth-child(7) > input')) - const tokenSymbol = await driver.findElement(By.css('#main > div > div > div > div:nth-child(2) > div > div:nth-child(8) > input')) - const createToken = await driver.findElement(By.css('#main > div > div > div > div:nth-child(2) > div > button')) - - await totalSupply.sendKeys('100') - await tokenName.sendKeys('Test') - await tokenDecimal.sendKeys('0') - await tokenSymbol.sendKeys('TST') - await createToken.click() - await delay(1000) - }) - - it('confirms transaction in MetaMask popup', async function () { - const windowHandles = await driver.getAllWindowHandles() - await driver.switchTo().window(windowHandles[2]) - const metamaskSubmit = await driver.findElement(By.css('#pending-tx-form > div.flex-row.flex-space-around.conf-buttons > input')) - await metamaskSubmit.click() - await delay(1000) - }) - - it('switches back to Token Factory to grab the token contract address', async function () { - const windowHandles = await driver.getAllWindowHandles() - await driver.switchTo().window(windowHandles[0]) - const tokenContactAddress = await driver.findElement(By.css('#main > div > div > div > div:nth-child(2) > span:nth-child(3)')) - tokenAddress = await tokenContactAddress.getText() - await delay(500) - }) - - it('navigates back to MetaMask popup in the tab', async function () { - await driver.get(`chrome-extension://${extensionId}/popup.html`) - await delay(700) - }) - }) - - describe('Add Token', function () { - it('switches to the add token screen', async function () { - const tokensTab = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > section > div > div.inactiveForm.pointer')) - assert.equal(await tokensTab.getText(), 'TOKENS') - await tokensTab.click() - await delay(300) - }) - - it('navigates to the add token screen', async function () { - const addTokenButton = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > section > div.full-flex-height > div > button')) - assert.equal(await addTokenButton.getText(), 'ADD TOKEN') - await addTokenButton.click() - }) - - it('checks add token screen rendered', async function () { - const addTokenScreen = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div.section-title.flex-row.flex-center > h2')) - assert.equal(await addTokenScreen.getText(), 'ADD TOKEN') - }) - - it('adds token parameters', async function () { - const tokenContractAddress = await driver.findElement(By.css('#token-address')) - await tokenContractAddress.sendKeys(tokenAddress) - await delay(300) - await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div.flex-column.flex-justify-center.flex-grow.select-none > div > button')).click() - await delay(100) - }) - - it('checks the token balance', async function () { - const tokenBalance = await driver.findElement(By.css('#app-content > div > div.app-primary.from-left > div > section > div.full-flex-height > ol > li:nth-child(2) > h3')) - assert.equal(await tokenBalance.getText(), '100 TST') - }) - }) - - async function getExtensionId () { - const extension = await driver.executeScript('return document.querySelector("extensions-manager").shadowRoot.querySelector("extensions-view-manager extensions-item-list").shadowRoot.querySelector("#container > div.items-container > extensions-item:nth-child(2)").getAttribute("id")') - return extension - } - - async function setProviderType (type) { - await driver.executeScript('window.metamask.setProviderType(arguments[0])', type) - } - - async function verboseReportOnFailure (test) { - const artifactDir = `./test-artifacts/chrome/${test.title}` - const filepathBase = `${artifactDir}/test-failure` - await pify(mkdirp)(artifactDir) - // capture screenshot - const screenshot = await driver.takeScreenshot() - await pify(fs.writeFile)(`${filepathBase}-screenshot.png`, screenshot, { encoding: 'base64' }) - // capture dom source - const htmlSource = await driver.getPageSource() - await pify(fs.writeFile)(`${filepathBase}-dom.html`, htmlSource) - } - -}) diff --git a/test/e2e/firefox/metamask.spec.js b/test/e2e/firefox/metamask.spec.js deleted file mode 100644 index 20b8a5092..000000000 --- a/test/e2e/firefox/metamask.spec.js +++ /dev/null @@ -1,322 +0,0 @@ -const fs = require('fs') -const mkdirp = require('mkdirp') -const path = require('path') -const assert = require('assert') -const pify = require('pify') -const webdriver = require('selenium-webdriver') -const Command = require('selenium-webdriver/lib/command').Command -const By = webdriver.By -const { delay, buildFirefoxWebdriver } = require('../func') - -describe('', function () { - let driver, accountAddress, tokenAddress, extensionId - - this.timeout(0) - - before(async function () { - const extPath = path.resolve('dist/firefox') - driver = buildFirefoxWebdriver() - installWebExt(driver, extPath) - await delay(700) - }) - - afterEach(async function () { - if (this.currentTest.state === 'failed') { - await verboseReportOnFailure(this.currentTest) - } - }) - - after(async function () { - await driver.quit() - }) - - describe('Setup', function () { - - it('switches to Firefox addon list', async function () { - await driver.get('about:debugging#addons') - await delay(1000) - }) - - it(`selects MetaMask's extension id and opens it in the current tab`, async function () { - const tabs = await driver.getAllWindowHandles() - await driver.switchTo().window(tabs[0]) - extensionId = await driver.findElement(By.css('dd.addon-target-info-content:nth-child(6) > span:nth-child(1)')).getText() - await driver.get(`moz-extension://${extensionId}/popup.html`) - await delay(500) - }) - - it('sets provider type to localhost', async function () { - await setProviderType('localhost') - await delay(300) - }) - }) - - describe('Account Creation', () => { - - it('matches MetaMask title', async () => { - const title = await driver.getTitle() - assert.equal(title, 'MetaMask', 'title matches MetaMask') - }) - - it('shows privacy notice', async () => { - const privacy = await driver.findElement(By.css('.terms-header')).getText() - assert.equal(privacy, 'PRIVACY NOTICE', 'shows privacy notice') - await driver.findElement(By.css('button')).click() - await delay(300) - }) - - it('show terms of use', async () => { - await delay(300) - const terms = await driver.findElement(By.css('.terms-header')).getText() - assert.equal(terms, 'TERMS OF USE', 'shows terms of use') - await delay(300) - }) - - it('checks if the TOU button is disabled', async () => { - const button = await driver.findElement(By.css('button')).isEnabled() - assert.equal(button, false, 'disabled continue button') - const element = await driver.findElement(By.linkText('Attributions')) - await driver.executeScript('arguments[0].scrollIntoView(true)', element) - await delay(300) - }) - - it('allows the button to be clicked when scrolled to the bottom of TOU', async () => { - const button = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div.flex-column.flex-center.flex-grow > button')) - await delay(300) - const buttonEnabled = await button.isEnabled() - assert.equal(buttonEnabled, true, 'enabled continue button') - await delay(200) - await button.click() - }) - - it('accepts password with length of eight', async () => { - const passwordBox = await driver.findElement(By.id('password-box')) - const passwordBoxConfirm = await driver.findElement(By.id('password-box-confirm')) - const button = await driver.findElements(By.css('button')) - - await passwordBox.sendKeys('123456789') - await passwordBoxConfirm.sendKeys('123456789') - await button[0].click() - await delay(500) - }) - - it('shows value was created and seed phrase', async () => { - await delay(300) - const seedPhrase = await driver.findElement(By.css('.twelve-word-phrase')).getText() - assert.equal(seedPhrase.split(' ').length, 12) - const continueAfterSeedPhrase = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > button:nth-child(4)')) - assert.equal(await continueAfterSeedPhrase.getText(), `I'VE COPIED IT SOMEWHERE SAFE`) - await continueAfterSeedPhrase.click() - await delay(300) - }) - - it('shows account address', async function () { - accountAddress = await driver.findElement(By.css('#app-content > div > div.app-primary.from-left > div > div > div:nth-child(1) > flex-column > div.flex-row > div')).getText() - }) - - it('logs out of the vault', async () => { - await driver.findElement(By.css('.sandwich-expando')).click() - await delay(500) - const logoutButton = await driver.findElement(By.css('#app-content > div > div:nth-child(3) > span > div > li:nth-child(3)')) - assert.equal(await logoutButton.getText(), 'Log Out') - await logoutButton.click() - }) - - it('accepts account password after lock', async () => { - await delay(500) - await driver.findElement(By.id('password-box')).sendKeys('123456789') - await driver.findElement(By.css('button')).click() - await delay(500) - }) - - it('shows QR code option', async () => { - await delay(300) - await driver.findElement(By.css('.fa-ellipsis-h')).click() - await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div > div:nth-child(1) > flex-column > div.name-label > div > span > i > div > div > li:nth-child(3)')).click() - await delay(300) - }) - - it('checks QR code address is the same as account details address', async () => { - const QRaccountAddress = await driver.findElement(By.css('.ellip-address')).getText() - assert.equal(accountAddress.toLowerCase(), QRaccountAddress) - await driver.findElement(By.css('.fa-arrow-left')).click() - await delay(500) - }) - }) - - describe('Import Ganache seed phrase', function () { - it('logs out', async function () { - await driver.findElement(By.css('.sandwich-expando')).click() - await delay(200) - const logOut = await driver.findElement(By.css('#app-content > div > div:nth-child(3) > span > div > li:nth-child(3)')) - assert.equal(await logOut.getText(), 'Log Out') - await logOut.click() - await delay(300) - }) - - it('restores from seed phrase', async function () { - const restoreSeedLink = await driver.findElement(By.css('#app-content > div > div.app-primary.from-left > div > div.flex-row.flex-center.flex-grow > p')) - assert.equal(await restoreSeedLink.getText(), 'Restore from seed phrase') - await restoreSeedLink.click() - await delay(100) - }) - - it('adds seed phrase', async function () { - const testSeedPhrase = 'phrase upgrade clock rough situate wedding elder clever doctor stamp excess tent' - const seedTextArea = await driver.findElement(By.css('#app-content > div > div.app-primary.from-left > div > textarea')) - await seedTextArea.sendKeys(testSeedPhrase) - - await driver.findElement(By.id('password-box')).sendKeys('123456789') - await driver.findElement(By.id('password-box-confirm')).sendKeys('123456789') - await driver.findElement(By.css('#app-content > div > div.app-primary.from-left > div > div > button:nth-child(2)')).click() - await delay(500) - }) - - it('balance renders', async function () { - await delay(200) - const balance = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div > div.flex-row > div.ether-balance.ether-balance-amount > div > div > div:nth-child(1) > div:nth-child(1)')) - assert.equal(await balance.getText(), '100.000') - await delay(200) - }) - - it('sends transaction', async function () { - const sendButton = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div > div.flex-row > button:nth-child(4)')) - assert.equal(await sendButton.getText(), 'SEND') - await sendButton.click() - await delay(200) - }) - - it('adds recipient address and amount', async function () { - const sendTranscationScreen = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > h3:nth-child(2)')).getText() - assert.equal(sendTranscationScreen, 'SEND TRANSACTION') - const inputAddress = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > section:nth-child(3) > div > input')) - const inputAmmount = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > section:nth-child(4) > input')) - await inputAddress.sendKeys('0x2f318C334780961FB129D2a6c30D0763d9a5C970') - await inputAmmount.sendKeys('10') - await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > section:nth-child(4) > button')).click() - await delay(300) - }) - - it('confirms transaction', async function () { - await delay(300) - await driver.findElement(By.css('#pending-tx-form > div.flex-row.flex-space-around.conf-buttons > input')).click() - await delay(500) - }) - - it('finds the transaction in the transactions list', async function () { - const tranasactionAmount = await driver.findElement(By.css('#app-content > div > div.app-primary.from-left > div > section > section > div > div > div > div.ether-balance.ether-balance-amount > div > div > div > div:nth-child(1)')) - assert.equal(await tranasactionAmount.getText(), '10.0') - }) - }) - - describe('Token Factory', function () { - - it('navigates to token factory', async function () { - await driver.get('http://tokenfactory.surge.sh/') - }) - - it('navigates to create token contract link', async function () { - const createToken = await driver.findElement(By.css('#bs-example-navbar-collapse-1 > ul > li:nth-child(3) > a')) - await createToken.click() - }) - - it('adds input for token', async function () { - const totalSupply = await driver.findElement(By.css('#main > div > div > div > div:nth-child(2) > div > div:nth-child(5) > input')) - const tokenName = await driver.findElement(By.css('#main > div > div > div > div:nth-child(2) > div > div:nth-child(6) > input')) - const tokenDecimal = await driver.findElement(By.css('#main > div > div > div > div:nth-child(2) > div > div:nth-child(7) > input')) - const tokenSymbol = await driver.findElement(By.css('#main > div > div > div > div:nth-child(2) > div > div:nth-child(8) > input')) - const createToken = await driver.findElement(By.css('#main > div > div > div > div:nth-child(2) > div > button')) - - await totalSupply.sendKeys('100') - await tokenName.sendKeys('Test') - await tokenDecimal.sendKeys('0') - await tokenSymbol.sendKeys('TST') - await createToken.click() - await delay(1000) - }) - - // There is an issue with blank confirmation window, but the button is still there and the driver is able to clicked (?.?) - it('confirms transaction in MetaMask popup', async function () { - const windowHandles = await driver.getAllWindowHandles() - await driver.switchTo().window(windowHandles[2]) - const metamaskSubmit = await driver.findElement(By.css('#pending-tx-form > div.flex-row.flex-space-around.conf-buttons > input')) - await metamaskSubmit.click() - await delay(1000) - }) - - it('switches back to Token Factory to grab the token contract address', async function () { - const windowHandles = await driver.getAllWindowHandles() - await driver.switchTo().window(windowHandles[0]) - const tokenContactAddress = await driver.findElement(By.css('#main > div > div > div > div:nth-child(2) > span:nth-child(3)')) - tokenAddress = await tokenContactAddress.getText() - await delay(500) - }) - - it('navigates back to MetaMask popup in the tab', async function () { - await driver.get(`moz-extension://${extensionId}/popup.html`) - await delay(700) - }) - }) - - describe('Add Token', function () { - it('switches to the add token screen', async function () { - const tokensTab = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > section > div > div.inactiveForm.pointer')) - assert.equal(await tokensTab.getText(), 'TOKENS') - await tokensTab.click() - await delay(300) - }) - - it('navigates to the add token screen', async function () { - const addTokenButton = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > section > div.full-flex-height > div > button')) - assert.equal(await addTokenButton.getText(), 'ADD TOKEN') - await addTokenButton.click() - }) - - it('checks add token screen rendered', async function () { - const addTokenScreen = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div.section-title.flex-row.flex-center > h2')) - assert.equal(await addTokenScreen.getText(), 'ADD TOKEN') - }) - - it('adds token parameters', async function () { - const tokenContractAddress = await driver.findElement(By.css('#token-address')) - await tokenContractAddress.sendKeys(tokenAddress) - await delay(300) - await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div.flex-column.flex-justify-center.flex-grow.select-none > div > button')).click() - await delay(100) - }) - - it('checks the token balance', async function () { - const tokenBalance = await driver.findElement(By.css('#app-content > div > div.app-primary.from-left > div > section > div.full-flex-height > ol > li:nth-child(2) > h3')) - assert.equal(await tokenBalance.getText(), '100 TST') - }) - }) - - async function setProviderType(type) { - await driver.executeScript('window.metamask.setProviderType(arguments[0])', type) - } - - async function verboseReportOnFailure(test) { - const artifactDir = `./test-artifacts/firefox/${test.title}` - const filepathBase = `${artifactDir}/test-failure` - await pify(mkdirp)(artifactDir) - // capture screenshot - const screenshot = await driver.takeScreenshot() - await pify(fs.writeFile)(`${filepathBase}-screenshot.png`, screenshot, { encoding: 'base64' }) - // capture dom source - const htmlSource = await driver.getPageSource() - await pify(fs.writeFile)(`${filepathBase}-dom.html`, htmlSource) - } - -}) - -async function installWebExt (driver, extension) { - const cmd = await new Command('moz-install-web-ext') - .setParameter('path', path.resolve(extension)) - .setParameter('temporary', true) - - await driver.getExecutor() - .defineCommand(cmd.getName(), 'POST', '/session/:sessionId/moz/addon/install') - - return await driver.schedule(cmd, 'installWebExt(' + extension + ')') -} - diff --git a/test/e2e/func.js b/test/e2e/func.js index 4ad0ea615..8b221ce47 100644 --- a/test/e2e/func.js +++ b/test/e2e/func.js @@ -1,13 +1,24 @@ require('chromedriver') require('geckodriver') +const path = require('path') const webdriver = require('selenium-webdriver') +const Command = require('selenium-webdriver/lib/command').Command +const By = webdriver.By -exports.delay = function delay (time) { - return new Promise(resolve => setTimeout(resolve, time)) +module.exports = { + delay, + buildChromeWebDriver, + buildFirefoxWebdriver, + installWebExt, + getExtensionIdChrome, + getExtensionIdFirefox, } +function delay (time) { + return new Promise(resolve => setTimeout(resolve, time)) +} -exports.buildChromeWebDriver = function buildChromeWebDriver (extPath) { +function buildChromeWebDriver (extPath) { return new webdriver.Builder() .withCapabilities({ chromeOptions: { @@ -17,6 +28,29 @@ exports.buildChromeWebDriver = function buildChromeWebDriver (extPath) { .build() } -exports.buildFirefoxWebdriver = function buildFirefoxWebdriver (extPath) { +function buildFirefoxWebdriver () { return new webdriver.Builder().build() } + +async function getExtensionIdChrome (driver) { + await driver.get('chrome://extensions') + const extensionId = await driver.executeScript('return document.querySelector("extensions-manager").shadowRoot.querySelector("extensions-view-manager extensions-item-list").shadowRoot.querySelector("extensions-item:nth-child(2)").getAttribute("id")') + return extensionId +} + +async function getExtensionIdFirefox (driver) { + await driver.get('about:debugging#addons') + const extensionId = await driver.findElement(By.css('dd.addon-target-info-content:nth-child(6) > span:nth-child(1)')).getText() + return extensionId +} + +async function installWebExt (driver, extension) { + const cmd = await new Command('moz-install-web-ext') + .setParameter('path', path.resolve(extension)) + .setParameter('temporary', true) + + await driver.getExecutor() + .defineCommand(cmd.getName(), 'POST', '/session/:sessionId/moz/addon/install') + + return await driver.schedule(cmd, 'installWebExt(' + extension + ')') +} \ No newline at end of file diff --git a/test/e2e/metamask.spec.js b/test/e2e/metamask.spec.js new file mode 100644 index 000000000..836e99754 --- /dev/null +++ b/test/e2e/metamask.spec.js @@ -0,0 +1,320 @@ +const fs = require('fs') +const mkdirp = require('mkdirp') +const path = require('path') +const assert = require('assert') +const pify = require('pify') +const webdriver = require('selenium-webdriver') +const until = require('selenium-webdriver/lib/until') +const By = webdriver.By +const { delay, buildChromeWebDriver, buildFirefoxWebdriver, installWebExt, getExtensionIdChrome, getExtensionIdFirefox } = require('./func') + +describe('Metamask popup page', function () { + let driver, accountAddress, tokenAddress, extensionId + + this.timeout(0) + + before(async function () { + if (process.env.SELENIUM_BROWSER === 'chrome') { + const extPath = path.resolve('dist/chrome') + driver = buildChromeWebDriver(extPath) + extensionId = await getExtensionIdChrome(driver) + await driver.get(`chrome-extension://${extensionId}/popup.html`) + + } else if (process.env.SELENIUM_BROWSER === 'firefox') { + const extPath = path.resolve('dist/firefox') + driver = buildFirefoxWebdriver() + await installWebExt(driver, extPath) + await delay(500) + extensionId = await getExtensionIdFirefox(driver) + await driver.get(`moz-extension://${extensionId}/popup.html`) + } + }) + + afterEach(async function () { + if (this.currentTest.state === 'failed') { + await verboseReportOnFailure(this.currentTest) + } + }) + + after(async function () { + await driver.quit() + }) + + describe('Setup', function () { + + it('switches to extension/addon list', async function () { + await driver.wait(async () => { + await until.urlContains('#how-it-works') + const tabs = await driver.getAllWindowHandles() + await driver.switchTo().window(tabs[0]) + return true + }, 300) + }) + + it('sets provider type to localhost', async function () { + await delay(300) + await setProviderType('localhost') + }) + }) + + describe('Account Creation', () => { + + it('matches MetaMask title', async () => { + const title = await driver.getTitle() + assert.equal(title, 'MetaMask', 'title matches MetaMask') + }) + + it('shows privacy notice', async () => { + await delay(300) + const privacy = await driver.findElement(By.css('.terms-header')).getText() + assert.equal(privacy, 'PRIVACY NOTICE', 'shows privacy notice') + await driver.findElement(By.css('button')).click() + await delay(300) + }) + + it('show terms of use', async () => { + await driver.wait(async () => { + const terms = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div.flex-column.flex-center.flex-grow > h3')).getText() + assert.equal(terms, 'TERMS OF USE', 'shows terms of use') + return terms === 'TERMS OF USE' + }) + }) + + it('checks if the TOU button is disabled', async () => { + const button = await driver.findElement(By.css('button')).isEnabled() + assert.equal(button, false, 'disabled continue button') + const element = await driver.findElement(By.linkText('Attributions')) + await driver.executeScript('arguments[0].scrollIntoView(true)', element) + await delay(300) + }) + + it('allows the button to be clicked when scrolled to the bottom of TOU', async () => { + const button = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div.flex-column.flex-center.flex-grow > button')) + const buttonEnabled = await driver.wait(until.elementIsEnabled(button)) + await buttonEnabled.click() + }) + + it('accepts password with length of eight', async () => { + const passwordBox = await driver.findElement(By.id('password-box')) + const passwordBoxConfirm = await driver.findElement(By.id('password-box-confirm')) + const button = await driver.findElements(By.css('button')) + + await passwordBox.sendKeys('123456789') + await passwordBoxConfirm.sendKeys('123456789') + await button[0].click() + await delay(500) + }) + + it('shows value was created and seed phrase', async () => { + await delay(300) + const seedPhrase = await driver.findElement(By.css('.twelve-word-phrase')).getText() + assert.equal(seedPhrase.split(' ').length, 12) + const continueAfterSeedPhrase = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > button:nth-child(4)')) + assert.equal(await continueAfterSeedPhrase.getText(), `I'VE COPIED IT SOMEWHERE SAFE`) + await continueAfterSeedPhrase.click() + await delay(300) + }) + + it('shows account address', async function () { + accountAddress = await driver.findElement(By.css('#app-content > div > div.app-primary.from-left > div > div > div:nth-child(1) > flex-column > div.flex-row > div')).getText() + }) + + it('logs out of the vault', async () => { + await driver.findElement(By.css('.sandwich-expando')).click() + await delay(500) + const logoutButton = await driver.findElement(By.css('#app-content > div > div:nth-child(3) > span > div > li:nth-child(3)')) + assert.equal(await logoutButton.getText(), 'Log Out') + await logoutButton.click() + }) + + it('accepts account password after lock', async () => { + await delay(500) + await driver.findElement(By.id('password-box')).sendKeys('123456789') + await driver.findElement(By.css('button')).sendKeys(webdriver.Key.ENTER) + await delay(500) + }) + + it('shows QR code option', async () => { + await delay(300) + await driver.findElement(By.css('.fa-ellipsis-h')).click() + await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div > div:nth-child(1) > flex-column > div.name-label > div > span > i > div > div > li:nth-child(3)')).click() + await delay(300) + }) + + it('checks QR code address is the same as account details address', async () => { + const QRaccountAddress = await driver.findElement(By.css('.ellip-address')).getText() + assert.equal(accountAddress.toLowerCase(), QRaccountAddress) + await driver.findElement(By.css('.fa-arrow-left')).click() + await delay(500) + }) + }) + + describe('Import Ganache seed phrase', function () { + + it('logs out', async function () { + await driver.findElement(By.css('.sandwich-expando')).click() + await delay(200) + const logOut = await driver.findElement(By.css('#app-content > div > div:nth-child(3) > span > div > li:nth-child(3)')) + assert.equal(await logOut.getText(), 'Log Out') + await logOut.click() + await delay(300) + }) + + it('restores from seed phrase', async function () { + const restoreSeedLink = await driver.findElement(By.css('#app-content > div > div.app-primary.from-left > div > div.flex-row.flex-center.flex-grow > p')) + assert.equal(await restoreSeedLink.getText(), 'Restore from seed phrase') + await restoreSeedLink.click() + await delay(100) + }) + + it('adds seed phrase', async function () { + const testSeedPhrase = 'phrase upgrade clock rough situate wedding elder clever doctor stamp excess tent' + const seedTextArea = await driver.findElement(By.css('#app-content > div > div.app-primary.from-left > div > textarea')) + await seedTextArea.sendKeys(testSeedPhrase) + + await driver.findElement(By.id('password-box')).sendKeys('123456789') + await driver.findElement(By.id('password-box-confirm')).sendKeys('123456789') + await driver.findElement(By.css('#app-content > div > div.app-primary.from-left > div > div > button:nth-child(2)')).click() + await delay(500) + }) + + it('balance renders', async function () { + await delay(200) + const balance = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div > div.flex-row > div.ether-balance.ether-balance-amount > div > div > div:nth-child(1) > div:nth-child(1)')) + assert.equal(await balance.getText(), '100.000') + await delay(200) + }) + + it('sends transaction', async function () { + const sendButton = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div > div.flex-row > button:nth-child(4)')) + assert.equal(await sendButton.getText(), 'SEND') + await sendButton.click() + await delay(200) + }) + + it('adds recipient address and amount', async function () { + const sendTranscationScreen = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > h3:nth-child(2)')).getText() + assert.equal(sendTranscationScreen, 'SEND TRANSACTION') + const inputAddress = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > section:nth-child(3) > div > input')) + const inputAmmount = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > section:nth-child(4) > input')) + await inputAddress.sendKeys('0x2f318C334780961FB129D2a6c30D0763d9a5C970') + await inputAmmount.sendKeys('10') + await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > section:nth-child(4) > button')).click() + await delay(300) + }) + + it('confirms transaction', async function () { + await delay(300) + await driver.findElement(By.css('#pending-tx-form > div.flex-row.flex-space-around.conf-buttons > input')).click() + await delay(500) + }) + + it('finds the transaction in the transactions list', async function () { + const tranasactionAmount = await driver.findElement(By.css('#app-content > div > div.app-primary.from-left > div > section > section > div > div > div > div.ether-balance.ether-balance-amount > div > div > div > div:nth-child(1)')) + assert.equal(await tranasactionAmount.getText(), '10.0') + }) + }) + + describe('Token Factory', function () { + + it('navigates to token factory', async function () { + await driver.get('http://tokenfactory.surge.sh/') + }) + + it('navigates to create token contract link', async function () { + const createToken = await driver.findElement(By.css('#bs-example-navbar-collapse-1 > ul > li:nth-child(3) > a')) + await createToken.click() + }) + + it('adds input for token', async function () { + const totalSupply = await driver.findElement(By.css('#main > div > div > div > div:nth-child(2) > div > div:nth-child(5) > input')) + const tokenName = await driver.findElement(By.css('#main > div > div > div > div:nth-child(2) > div > div:nth-child(6) > input')) + const tokenDecimal = await driver.findElement(By.css('#main > div > div > div > div:nth-child(2) > div > div:nth-child(7) > input')) + const tokenSymbol = await driver.findElement(By.css('#main > div > div > div > div:nth-child(2) > div > div:nth-child(8) > input')) + const createToken = await driver.findElement(By.css('#main > div > div > div > div:nth-child(2) > div > button')) + + await totalSupply.sendKeys('100') + await tokenName.sendKeys('Test') + await tokenDecimal.sendKeys('0') + await tokenSymbol.sendKeys('TST') + await createToken.click() + await delay(1000) + }) + + // // There is an issue with blank confirmation window in Firefox, but the button is still there and the driver is able to clicked (?.?) + it('confirms transaction in MetaMask popup', async function () { + const windowHandles = await driver.getAllWindowHandles() + await driver.switchTo().window(windowHandles[windowHandles.length - 1]) + const metamaskSubmit = await driver.findElement(By.css('#pending-tx-form > div.flex-row.flex-space-around.conf-buttons > input')) + await metamaskSubmit.click() + await delay(1000) + }) + + it('switches back to Token Factory to grab the token contract address', async function () { + const windowHandles = await driver.getAllWindowHandles() + await driver.switchTo().window(windowHandles[0]) + const tokenContactAddress = await driver.findElement(By.css('#main > div > div > div > div:nth-child(2) > span:nth-child(3)')) + tokenAddress = await tokenContactAddress.getText() + await delay(500) + }) + + it('navigates back to MetaMask popup in the tab', async function () { + if (process.env.SELENIUM_BROWSER === 'chrome') { + await driver.get(`chrome-extension://${extensionId}/popup.html`) + } else if (process.env.SELENIUM_BROWSER === 'firefox') { + await driver.get(`moz-extension://${extensionId}/popup.html`) + } + await delay(700) + }) + }) + + describe('Add Token', function () { + it('switches to the add token screen', async function () { + const tokensTab = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > section > div > div.inactiveForm.pointer')) + assert.equal(await tokensTab.getText(), 'TOKENS') + await tokensTab.click() + await delay(300) + }) + + it('navigates to the add token screen', async function () { + const addTokenButton = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > section > div.full-flex-height > div > button')) + assert.equal(await addTokenButton.getText(), 'ADD TOKEN') + await addTokenButton.click() + }) + + it('checks add token screen rendered', async function () { + const addTokenScreen = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div.section-title.flex-row.flex-center > h2')) + assert.equal(await addTokenScreen.getText(), 'ADD TOKEN') + }) + + it('adds token parameters', async function () { + const tokenContractAddress = await driver.findElement(By.css('#token-address')) + await tokenContractAddress.sendKeys(tokenAddress) + await delay(300) + await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div.flex-column.flex-justify-center.flex-grow.select-none > div > button')).click() + await delay(200) + }) + + it('checks the token balance', async function () { + const tokenBalance = await driver.findElement(By.css('#app-content > div > div.app-primary.from-left > div > section > div.full-flex-height > ol > li:nth-child(2) > h3')) + assert.equal(await tokenBalance.getText(), '100 TST') + }) + }) + + async function setProviderType (type) { + await driver.executeScript('window.metamask.setProviderType(arguments[0])', type) + } + + async function verboseReportOnFailure (test) { + const artifactDir = `./test-artifacts/chrome/${test.title}` + const filepathBase = `${artifactDir}/test-failure` + await pify(mkdirp)(artifactDir) + // capture screenshot + const screenshot = await driver.takeScreenshot() + await pify(fs.writeFile)(`${filepathBase}-screenshot.png`, screenshot, { encoding: 'base64' }) + // capture dom source + const htmlSource = await driver.getPageSource() + await pify(fs.writeFile)(`${filepathBase}-dom.html`, htmlSource) + } + +}) -- cgit v1.2.3 From 77e13640b4dba89f870cfa35e7834ce3d61ed730 Mon Sep 17 00:00:00 2001 From: Thomas Date: Wed, 16 May 2018 15:03:53 -0700 Subject: Testing timeouts --- test/e2e/metamask.spec.js | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) (limited to 'test') diff --git a/test/e2e/metamask.spec.js b/test/e2e/metamask.spec.js index 836e99754..1fc557768 100644 --- a/test/e2e/metamask.spec.js +++ b/test/e2e/metamask.spec.js @@ -4,7 +4,6 @@ const path = require('path') const assert = require('assert') const pify = require('pify') const webdriver = require('selenium-webdriver') -const until = require('selenium-webdriver/lib/until') const By = webdriver.By const { delay, buildChromeWebDriver, buildFirefoxWebdriver, installWebExt, getExtensionIdChrome, getExtensionIdFirefox } = require('./func') @@ -24,7 +23,7 @@ describe('Metamask popup page', function () { const extPath = path.resolve('dist/firefox') driver = buildFirefoxWebdriver() await installWebExt(driver, extPath) - await delay(500) + await delay(700) extensionId = await getExtensionIdFirefox(driver) await driver.get(`moz-extension://${extensionId}/popup.html`) } @@ -42,19 +41,17 @@ describe('Metamask popup page', function () { describe('Setup', function () { - it('switches to extension/addon list', async function () { - await driver.wait(async () => { - await until.urlContains('#how-it-works') - const tabs = await driver.getAllWindowHandles() - await driver.switchTo().window(tabs[0]) - return true - }, 300) + it('switches to Chrome extensions list', async function () { + await delay(300) + const windowHandles = await driver.getAllWindowHandles() + await driver.switchTo().window(windowHandles[0]) }) it('sets provider type to localhost', async function () { await delay(300) await setProviderType('localhost') }) + }) describe('Account Creation', () => { @@ -73,11 +70,9 @@ describe('Metamask popup page', function () { }) it('show terms of use', async () => { - await driver.wait(async () => { - const terms = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div.flex-column.flex-center.flex-grow > h3')).getText() - assert.equal(terms, 'TERMS OF USE', 'shows terms of use') - return terms === 'TERMS OF USE' - }) + const terms = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div.flex-column.flex-center.flex-grow > h3')).getText() + assert.equal(terms, 'TERMS OF USE', 'shows terms of use') + delay(300) }) it('checks if the TOU button is disabled', async () => { @@ -85,13 +80,12 @@ describe('Metamask popup page', function () { assert.equal(button, false, 'disabled continue button') const element = await driver.findElement(By.linkText('Attributions')) await driver.executeScript('arguments[0].scrollIntoView(true)', element) - await delay(300) + await delay(700) }) it('allows the button to be clicked when scrolled to the bottom of TOU', async () => { const button = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div.flex-column.flex-center.flex-grow > button')) - const buttonEnabled = await driver.wait(until.elementIsEnabled(button)) - await buttonEnabled.click() + await button.click() }) it('accepts password with length of eight', async () => { @@ -269,6 +263,7 @@ describe('Metamask popup page', function () { }) describe('Add Token', function () { + it('switches to the add token screen', async function () { const tokensTab = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > section > div > div.inactiveForm.pointer')) assert.equal(await tokensTab.getText(), 'TOKENS') @@ -306,7 +301,12 @@ describe('Metamask popup page', function () { } async function verboseReportOnFailure (test) { - const artifactDir = `./test-artifacts/chrome/${test.title}` + let artifactDir + if (process.env.SELENIUM_BROWSER === 'chrome') { + artifactDir = `./test-artifacts/chrome/${test.title}` + } else if (process.env.SELENIUM_BROWSER === 'firefox') { + artifactDir = `./test-artifacts/firefox/${test.title}` + } const filepathBase = `${artifactDir}/test-failure` await pify(mkdirp)(artifactDir) // capture screenshot -- cgit v1.2.3 From f2815d3121d5edcbc5cc0411b5b52092f3072ad1 Mon Sep 17 00:00:00 2001 From: Thomas Date: Wed, 16 May 2018 19:12:03 -0700 Subject: Adjustments for previous PR --- test/e2e/metamask.spec.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'test') diff --git a/test/e2e/metamask.spec.js b/test/e2e/metamask.spec.js index 1fc557768..707ca2560 100644 --- a/test/e2e/metamask.spec.js +++ b/test/e2e/metamask.spec.js @@ -4,7 +4,7 @@ const path = require('path') const assert = require('assert') const pify = require('pify') const webdriver = require('selenium-webdriver') -const By = webdriver.By +const { By, Key } = webdriver const { delay, buildChromeWebDriver, buildFirefoxWebdriver, installWebExt, getExtensionIdChrome, getExtensionIdFirefox } = require('./func') describe('Metamask popup page', function () { @@ -70,7 +70,7 @@ describe('Metamask popup page', function () { }) it('show terms of use', async () => { - const terms = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div.flex-column.flex-center.flex-grow > h3')).getText() + const terms = await driver.findElement(By.css('.terms-header')).getText() assert.equal(terms, 'TERMS OF USE', 'shows terms of use') delay(300) }) @@ -124,7 +124,7 @@ describe('Metamask popup page', function () { it('accepts account password after lock', async () => { await delay(500) await driver.findElement(By.id('password-box')).sendKeys('123456789') - await driver.findElement(By.css('button')).sendKeys(webdriver.Key.ENTER) + await driver.findElement(By.id('password-box')).sendKeys(Key.ENTER) await delay(500) }) @@ -235,7 +235,7 @@ describe('Metamask popup page', function () { await delay(1000) }) - // // There is an issue with blank confirmation window in Firefox, but the button is still there and the driver is able to clicked (?.?) + // There is an issue with blank confirmation window in Firefox, but the button is still there and the driver is able to clicked (?.?) it('confirms transaction in MetaMask popup', async function () { const windowHandles = await driver.getAllWindowHandles() await driver.switchTo().window(windowHandles[windowHandles.length - 1]) -- cgit v1.2.3 From 924cc1fcf7de1896ac09bbe7a400d5ff5df0b50d Mon Sep 17 00:00:00 2001 From: Whymarrh Whitby Date: Thu, 19 Apr 2018 01:03:51 -0230 Subject: Move setAccountLabel into PreferencesController --- test/unit/actions/save_account_label_test.js | 35 ------------------- test/unit/actions/set_account_label_test.js | 34 +++++++++++++++++++ test/unit/preferences-controller-test.js | 51 +++++++++++++++++++++++++++- 3 files changed, 84 insertions(+), 36 deletions(-) delete mode 100644 test/unit/actions/save_account_label_test.js create mode 100644 test/unit/actions/set_account_label_test.js (limited to 'test') diff --git a/test/unit/actions/save_account_label_test.js b/test/unit/actions/save_account_label_test.js deleted file mode 100644 index c5ffd6cbf..000000000 --- a/test/unit/actions/save_account_label_test.js +++ /dev/null @@ -1,35 +0,0 @@ -// var jsdom = require('mocha-jsdom') -var assert = require('assert') -var freeze = require('deep-freeze-strict') -var path = require('path') - -var actions = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'actions.js')) -var reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'reducers.js')) - -describe('SAVE_ACCOUNT_LABEL', function () { - it('updates the state.metamask.identities[:i].name property of the state to the action.value.label', function () { - var initialState = { - metamask: { - identities: { - foo: { - name: 'bar', - }, - }, - }, - } - freeze(initialState) - - const action = { - type: actions.SAVE_ACCOUNT_LABEL, - value: { - account: 'foo', - label: 'baz', - }, - } - freeze(action) - - var resultingState = reducers(initialState, action) - assert.equal(resultingState.metamask.identities.foo.name, action.value.label) - }) -}) - diff --git a/test/unit/actions/set_account_label_test.js b/test/unit/actions/set_account_label_test.js new file mode 100644 index 000000000..53ea1d130 --- /dev/null +++ b/test/unit/actions/set_account_label_test.js @@ -0,0 +1,34 @@ +const assert = require('assert') +const freeze = require('deep-freeze-strict') +const path = require('path') + +const actions = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'actions.js')) +const reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'reducers.js')) + +describe('SET_ACCOUNT_LABEL', function () { + it('updates the state.metamask.identities[:i].name property of the state to the action.value.label', function () { + const initialState = { + metamask: { + identities: { + foo: { + name: 'bar', + }, + }, + }, + } + freeze(initialState) + + const action = { + type: actions.SET_ACCOUNT_LABEL, + value: { + account: 'foo', + label: 'baz', + }, + } + freeze(action) + + const resultingState = reducers(initialState, action) + assert.equal(resultingState.metamask.identities.foo.name, action.value.label) + }) +}) + diff --git a/test/unit/preferences-controller-test.js b/test/unit/preferences-controller-test.js index 9fb5e4251..8ff1c8648 100644 --- a/test/unit/preferences-controller-test.js +++ b/test/unit/preferences-controller-test.js @@ -4,16 +4,26 @@ const PreferencesController = require('../../app/scripts/controllers/preferences describe('preferences controller', function () { let preferencesController - before(() => { + beforeEach(() => { preferencesController = new PreferencesController() }) + describe('getTokens', function () { + it('should return an empty list initially', async function () { + await preferencesController.setSelectedAddress('0x7e57e2') + + const tokens = preferencesController.getTokens() + assert.equal(tokens.length, 0, 'empty list of tokens') + }) + }) + describe('addToken', function () { it('should add that token to its state', async function () { const address = '0xabcdef1234567' const symbol = 'ABBR' const decimals = 5 + await preferencesController.setSelectedAddress('0x7e57e2') await preferencesController.addToken(address, symbol, decimals) const tokens = preferencesController.getTokens() @@ -30,6 +40,7 @@ describe('preferences controller', function () { const symbol = 'ABBR' const decimals = 5 + await preferencesController.setSelectedAddress('0x7e57e2') await preferencesController.addToken(address, symbol, decimals) const newDecimals = 6 @@ -43,6 +54,44 @@ describe('preferences controller', function () { assert.equal(added.symbol, symbol, 'set symbol correctly') assert.equal(added.decimals, newDecimals, 'updated decimals correctly') }) + + it('should allow adding tokens to two separate addresses', async function () { + const address = '0xabcdef1234567' + const symbol = 'ABBR' + const decimals = 5 + + await preferencesController.setSelectedAddress('0x7e57e2') + await preferencesController.addToken(address, symbol, decimals) + assert.equal(preferencesController.getTokens().length, 1, 'one token added for 1st address') + + await preferencesController.setSelectedAddress('0xda22le') + await preferencesController.addToken(address, symbol, decimals) + assert.equal(preferencesController.getTokens().length, 1, 'one token added for 2nd address') + }) + }) + + describe('removeToken', function () { + it('should remove the only token from its state', async function () { + await preferencesController.setSelectedAddress('0x7e57e2') + await preferencesController.addToken('0xa', 'A', 5) + await preferencesController.removeToken('0xa') + + const tokens = preferencesController.getTokens() + assert.equal(tokens.length, 0, 'one token removed') + }) + + it('should remove a token from its state', async function () { + await preferencesController.setSelectedAddress('0x7e57e2') + await preferencesController.addToken('0xa', 'A', 4) + await preferencesController.addToken('0xb', 'B', 5) + await preferencesController.removeToken('0xa') + + const tokens = preferencesController.getTokens() + assert.equal(tokens.length, 1, 'one token removed') + + const [token1] = tokens + assert.deepEqual(token1, {address: '0xb', symbol: 'B', decimals: 5}) + }) }) }) -- cgit v1.2.3 From cbe4d0d88c83ee1d8dd8efde537ee753bf19596a Mon Sep 17 00:00:00 2001 From: Whymarrh Whitby Date: Thu, 19 Apr 2018 01:03:51 -0230 Subject: Update AddressBookController to read from preferences store --- test/unit/address-book-controller.js | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) (limited to 'test') diff --git a/test/unit/address-book-controller.js b/test/unit/address-book-controller.js index 655c9022c..e48e69d98 100644 --- a/test/unit/address-book-controller.js +++ b/test/unit/address-book-controller.js @@ -1,26 +1,26 @@ const assert = require('assert') const AddressBookController = require('../../app/scripts/controllers/address-book') -const mockKeyringController = { - memStore: { - getState: function () { - return { - identities: { - '0x0aaa': { - address: '0x0aaa', - name: 'owned', - }, +const stubPreferencesStore = { + getState: function () { + return { + identities: { + '0x0aaa': { + address: '0x0aaa', + name: 'owned', }, - } - }, + }, + } }, -} +}; describe('address-book-controller', function () { var addressBookController beforeEach(function () { - addressBookController = new AddressBookController({}, mockKeyringController) + addressBookController = new AddressBookController({ + preferencesStore: stubPreferencesStore, + }) }) describe('addres book management', function () { -- cgit v1.2.3 From c54e4c719110c2033b7cc3757676f97ad3329d42 Mon Sep 17 00:00:00 2001 From: Whymarrh Whitby Date: Thu, 19 Apr 2018 01:03:51 -0230 Subject: Add PreferencesController#setAddresses to update ids --- test/unit/preferences-controller-test.js | 44 ++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) (limited to 'test') diff --git a/test/unit/preferences-controller-test.js b/test/unit/preferences-controller-test.js index 8ff1c8648..4b40b1159 100644 --- a/test/unit/preferences-controller-test.js +++ b/test/unit/preferences-controller-test.js @@ -8,6 +8,50 @@ describe('preferences controller', function () { preferencesController = new PreferencesController() }) + describe('setAddresses', function () { + it('should keep a map of addresses to names and addresses in the store', function () { + preferencesController.setAddresses([ + '0xda22le', + '0x7e57e2', + ]) + + const {identities} = preferencesController.store.getState() + assert.deepEqual(identities, { + '0xda22le': { + name: 'Account 1', + address: '0xda22le', + }, + '0x7e57e2': { + name: 'Account 2', + address: '0x7e57e2', + }, + }) + }) + + it('should replace its list of addresses', function () { + preferencesController.setAddresses([ + '0xda22le', + '0x7e57e2', + ]) + preferencesController.setAddresses([ + '0xda22le77', + '0x7e57e277', + ]) + + const {identities} = preferencesController.store.getState() + assert.deepEqual(identities, { + '0xda22le77': { + name: 'Account 1', + address: '0xda22le77', + }, + '0x7e57e277': { + name: 'Account 2', + address: '0x7e57e277', + }, + }) + }) + }) + describe('getTokens', function () { it('should return an empty list initially', async function () { await preferencesController.setSelectedAddress('0x7e57e2') -- cgit v1.2.3 From 50af02e74bf8967232b97a43db4f3befb6939566 Mon Sep 17 00:00:00 2001 From: Whymarrh Whitby Date: Thu, 19 Apr 2018 01:03:51 -0230 Subject: Add test case for PreferencesController#setAccountLabel --- test/unit/preferences-controller-test.js | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) (limited to 'test') diff --git a/test/unit/preferences-controller-test.js b/test/unit/preferences-controller-test.js index 4b40b1159..c613c68f9 100644 --- a/test/unit/preferences-controller-test.js +++ b/test/unit/preferences-controller-test.js @@ -52,6 +52,27 @@ describe('preferences controller', function () { }) }) + describe('setAccountLabel', function () { + it('should update a label for the given account', function () { + preferencesController.setAddresses([ + '0xda22le', + '0x7e57e2', + ]) + + assert.deepEqual(preferencesController.store.getState().identities['0xda22le'], { + name: 'Account 1', + address: '0xda22le', + }) + + + preferencesController.setAccountLabel('0xda22le', 'Dazzle') + assert.deepEqual(preferencesController.store.getState().identities['0xda22le'], { + name: 'Dazzle', + address: '0xda22le', + }) + }) + }) + describe('getTokens', function () { it('should return an empty list initially', async function () { await preferencesController.setSelectedAddress('0x7e57e2') -- cgit v1.2.3 From 2d13fac476cfbb7b0140611dca00fa95ee8d91ab Mon Sep 17 00:00:00 2001 From: Whymarrh Whitby Date: Thu, 19 Apr 2018 01:03:51 -0230 Subject: Add migration to move identities from KeyringController --- test/unit/migrations/026-test.js | 41 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 test/unit/migrations/026-test.js (limited to 'test') diff --git a/test/unit/migrations/026-test.js b/test/unit/migrations/026-test.js new file mode 100644 index 000000000..ac0da3fb2 --- /dev/null +++ b/test/unit/migrations/026-test.js @@ -0,0 +1,41 @@ +const assert = require('assert') +const migration26 = require('../../../app/scripts/migrations/026') +const oldStorage = { + 'meta': {'version': 25}, + 'data': { + 'PreferencesController': {}, + 'KeyringController': { + 'walletNicknames': { + '0x1e77e2': 'Test Account 1', + '0x7e57e2': 'Test Account 2', + }, + }, + }, +} + +describe('migration #26', () => { + it('should move the identities from KeyringController', (done) => { + migration26.migrate(oldStorage) + .then((newStorage) => { + const identities = newStorage.data.PreferencesController.identities + assert.deepEqual(identities, { + '0x1e77e2': 'Test Account 1', + '0x7e57e2': 'Test Account 2', + }) + assert.strictEqual(newStorage.data.KeyringController.walletNicknames, undefined) + done() + }) + .catch(done) + }) + + it('should successfully migrate first time state', (done) => { + migration26.migrate({ + meta: {}, + data: require('../../../app/scripts/first-time-state'), + }) + .then((migratedData) => { + assert.equal(migratedData.meta.version, migration26.version) + done() + }).catch(done) + }) +}) -- cgit v1.2.3 From e546f4b9041c03deb74ed115b8bcfe3869670b6e Mon Sep 17 00:00:00 2001 From: Whymarrh Whitby Date: Mon, 14 May 2018 17:16:24 -0230 Subject: Update MetaMaskController vault restoration test for new KC api --- test/unit/metamask-controller-test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'test') diff --git a/test/unit/metamask-controller-test.js b/test/unit/metamask-controller-test.js index 18c3f9ab9..649b26e1e 100644 --- a/test/unit/metamask-controller-test.js +++ b/test/unit/metamask-controller-test.js @@ -106,7 +106,7 @@ describe('MetaMaskController', function () { [TEST_ADDRESS]: { address: TEST_ADDRESS, name: DEFAULT_LABEL }, }) - await metamaskController.keyringController.saveAccountLabel(TEST_ADDRESS, 'Account Foo') + await metamaskController.preferencesController.setAccountLabel(TEST_ADDRESS, 'Account Foo') assert.deepEqual(metamaskController.getState().identities, { [TEST_ADDRESS]: { address: TEST_ADDRESS, name: 'Account Foo' }, }) -- cgit v1.2.3 From 67310e151ea75c7aa4fc00bfd63bf41cd4f2db51 Mon Sep 17 00:00:00 2001 From: Whymarrh Whitby Date: Thu, 17 May 2018 13:35:38 -0230 Subject: Fix migration 026 to produce the correct shape for state.identities --- test/unit/migrations/026-test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'test') diff --git a/test/unit/migrations/026-test.js b/test/unit/migrations/026-test.js index ac0da3fb2..b3f5470cf 100644 --- a/test/unit/migrations/026-test.js +++ b/test/unit/migrations/026-test.js @@ -19,8 +19,8 @@ describe('migration #26', () => { .then((newStorage) => { const identities = newStorage.data.PreferencesController.identities assert.deepEqual(identities, { - '0x1e77e2': 'Test Account 1', - '0x7e57e2': 'Test Account 2', + '0x1e77e2': {name: 'Test Account 1', address: '0x1e77e2'}, + '0x7e57e2': {name: 'Test Account 2', address: '0x7e57e2'}, }) assert.strictEqual(newStorage.data.KeyringController.walletNicknames, undefined) done() -- cgit v1.2.3 From f279a8e61a3f50326fe9f26b0b860af47cd662fb Mon Sep 17 00:00:00 2001 From: Thomas Date: Thu, 17 May 2018 16:26:08 -0700 Subject: signMessage and signPersonalMessage --- test/unit/metamask-controller-test.js | 46 +++++++++++++++++++++++++---------- 1 file changed, 33 insertions(+), 13 deletions(-) (limited to 'test') diff --git a/test/unit/metamask-controller-test.js b/test/unit/metamask-controller-test.js index 332ccfdb8..23372d823 100644 --- a/test/unit/metamask-controller-test.js +++ b/test/unit/metamask-controller-test.js @@ -369,10 +369,12 @@ describe('MetaMaskController', function () { let msgParams, metamaskMsgs, messages, msgId - const address = '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc' + const address = '0xc42edfcc21ed14dda456aa0756c153f7985d8813' const data = '0x43727970746f6b697474696573' - beforeEach(function () { + beforeEach(async function () { + + await metamaskController.createNewVaultAndRestore('foobar1337', TEST_SEED_ALT) msgParams = { 'from': address, @@ -383,6 +385,7 @@ describe('MetaMaskController', function () { metamaskMsgs = metamaskController.messageManager.getUnapprovedMsgs() messages = metamaskController.messageManager.messages msgId = Object.keys(metamaskMsgs)[0] + messages[0].msgParams.metamaskId = parseInt(msgId) }) it('persists address from msg params', function () { @@ -406,6 +409,14 @@ describe('MetaMaskController', function () { metamaskController.cancelMessage(msgIdInt, noop) assert.equal(messages[0].status, 'rejected') }) + + it('errors when signing a message', async function () { + try { + await metamaskController.signMessage(messages[0].msgParams) + } catch (error) { + assert.equal(error.message, 'message length is invalid') + } + }) }) describe('#newUnsignedPersonalMessage', function () { @@ -419,12 +430,14 @@ describe('MetaMaskController', function () { }) }) - let msgParams, metamaskMsgs, messages, msgId + let msgParams, metamaskPersonalMsgs, personalMessages, msgId - const address = '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc' + const address = '0xc42edfcc21ed14dda456aa0756c153f7985d8813' const data = '0x43727970746f6b697474696573' - beforeEach(function () { + beforeEach(async function () { + + await metamaskController.createNewVaultAndRestore('foobar1337', TEST_SEED_ALT) msgParams = { 'from': address, @@ -432,31 +445,38 @@ describe('MetaMaskController', function () { } metamaskController.newUnsignedPersonalMessage(msgParams, noop) - metamaskMsgs = metamaskController.personalMessageManager.getUnapprovedMsgs() - messages = metamaskController.personalMessageManager.messages - msgId = Object.keys(metamaskMsgs)[0] + metamaskPersonalMsgs = metamaskController.personalMessageManager.getUnapprovedMsgs() + personalMessages = metamaskController.personalMessageManager.messages + msgId = Object.keys(metamaskPersonalMsgs)[0] + personalMessages[0].msgParams.metamaskId = parseInt(msgId) }) it('persists address from msg params', function () { - assert.equal(metamaskMsgs[msgId].msgParams.from, address) + assert.equal(metamaskPersonalMsgs[msgId].msgParams.from, address) }) it('persists data from msg params', function () { - assert.equal(metamaskMsgs[msgId].msgParams.data, data) + assert.equal(metamaskPersonalMsgs[msgId].msgParams.data, data) }) it('sets the status to unapproved', function () { - assert.equal(metamaskMsgs[msgId].status, 'unapproved') + assert.equal(metamaskPersonalMsgs[msgId].status, 'unapproved') }) it('sets the type to personal_sign', function () { - assert.equal(metamaskMsgs[msgId].type, 'personal_sign') + assert.equal(metamaskPersonalMsgs[msgId].type, 'personal_sign') }) it('rejects the message', function () { const msgIdInt = parseInt(msgId) metamaskController.cancelPersonalMessage(msgIdInt, noop) - assert.equal(messages[0].status, 'rejected') + assert.equal(personalMessages[0].status, 'rejected') + }) + + it('errors when signing a message', async function () { + await metamaskController.signPersonalMessage(personalMessages[0].msgParams) + assert.equal(metamaskPersonalMsgs[msgId].status, 'signed') + assert.equal(metamaskPersonalMsgs[msgId].rawSig, '0x6a1b65e2b8ed53cf398a769fad24738f9fbe29841fe6854e226953542c4b6a173473cb152b6b1ae5f06d601d45dd699a129b0a8ca84e78b423031db5baa734741b') }) }) -- cgit v1.2.3 From c4e75a7075a2a7d4726475a37d11acb4b1eec5fa Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Sun, 20 May 2018 14:08:45 -0700 Subject: Fix tests --- test/base.conf.js | 3 ++ test/integration/lib/add-token.js | 71 ++++++++++++++++++++++----------------- 2 files changed, 44 insertions(+), 30 deletions(-) (limited to 'test') diff --git a/test/base.conf.js b/test/base.conf.js index e2e9d44ba..956dce011 100644 --- a/test/base.conf.js +++ b/test/base.conf.js @@ -6,6 +6,9 @@ module.exports = function(config) { // base path that will be used to resolve all patterns (eg. files, exclude) basePath: process.cwd(), + // Uncomment to allow for longer timeouts + // browserNoActivityTimeout: 100000000, + browserConsoleLogOptions: { terminal: false, }, diff --git a/test/integration/lib/add-token.js b/test/integration/lib/add-token.js index 1840bdd39..e51c854d2 100644 --- a/test/integration/lib/add-token.js +++ b/test/integration/lib/add-token.js @@ -22,6 +22,11 @@ async function runAddTokenFlowTest (assert, done) { selectState.val('add token') reactTriggerChange(selectState[0]) + // Used to set values on TextField input component + const nativeInputValueSetter = Object.getOwnPropertyDescriptor( + window.HTMLInputElement.prototype, 'value' + ).set + // Check that no tokens have been added assert.ok($('.token-list-item').length === 0, 'no tokens added') @@ -31,14 +36,14 @@ async function runAddTokenFlowTest (assert, done) { addTokenButton[0].click() // Verify Add Token screen - let addTokenWrapper = await queryAsync($, '.add-token__wrapper') + let addTokenWrapper = await queryAsync($, '.page-container') assert.ok(addTokenWrapper[0], 'add token wrapper renders') - let addTokenTitle = await queryAsync($, '.add-token__header__title') + let addTokenTitle = await queryAsync($, '.page-container__title') assert.equal(addTokenTitle[0].textContent, 'Add Tokens', 'add token title is correct') // Cancel Add Token - const cancelAddTokenButton = await queryAsync($, 'button.btn-secondary--lg.add-token__cancel-button') + const cancelAddTokenButton = await queryAsync($, 'button.btn-secondary--lg.page-container__footer-button') assert.ok(cancelAddTokenButton[0], 'cancel add token button present') cancelAddTokenButton.click() @@ -50,20 +55,22 @@ async function runAddTokenFlowTest (assert, done) { addTokenButton[0].click() // Verify Add Token Screen - addTokenWrapper = await queryAsync($, '.add-token__wrapper') - addTokenTitle = await queryAsync($, '.add-token__header__title') + addTokenWrapper = await queryAsync($, '.page-container') + addTokenTitle = await queryAsync($, '.page-container__title') assert.ok(addTokenWrapper[0], 'add token wrapper renders') assert.equal(addTokenTitle[0].textContent, 'Add Tokens', 'add token title is correct') // Search for token - const searchInput = await queryAsync($, 'input.add-token__input') - searchInput.val('a') - reactTriggerChange(searchInput[0]) + const searchInput = (await findAsync(addTokenWrapper, '#search-tokens'))[0] + searchInput.focus() + await timeout(1000) + nativeInputValueSetter.call(searchInput, 'a') + searchInput.dispatchEvent(new Event('input', { bubbles: true})) // Click token to add - const tokenWrapper = await queryAsync($, 'div.add-token__token-wrapper') + const tokenWrapper = await queryAsync($, 'div.token-list__token') assert.ok(tokenWrapper[0], 'token found') - const tokenImageProp = tokenWrapper.find('.add-token__token-icon').css('background-image') + const tokenImageProp = tokenWrapper.find('.token-list__token-icon').css('background-image') const tokenImageUrl = tokenImageProp.slice(5, -2) tokenWrapper[0].click() @@ -73,11 +80,8 @@ async function runAddTokenFlowTest (assert, done) { nextButton[0].click() // Confirm Add token - assert.equal( - $('.add-token__description')[0].textContent, - 'Token balance(s)', - 'confirm add token rendered' - ) + const confirmAddToken = await queryAsync($, '.confirm-add-token') + assert.ok(confirmAddToken[0], 'confirm add token rendered') assert.ok($('button.btn-primary--lg')[0], 'confirm add token button found') $('button.btn-primary--lg')[0].click() @@ -91,39 +95,46 @@ async function runAddTokenFlowTest (assert, done) { assert.ok(addTokenButton[0], 'add token button present') addTokenButton[0].click() - const addTokenTabs = await queryAsync($, '.add-token__header__tabs__tab') + addTokenWrapper = await queryAsync($, '.page-container') + const addTokenTabs = await queryAsync($, '.page-container__tab') assert.equal(addTokenTabs.length, 2, 'expected number of tabs') assert.equal(addTokenTabs[1].textContent, 'Custom Token', 'Custom Token tab present') assert.ok(addTokenTabs[1], 'add custom token tab present') addTokenTabs[1].click() + await timeout(1000) // Input token contract address - const customInput = await queryAsync($, 'input.add-token__add-custom-input') - customInput.val('0x177af043D3A1Aed7cc5f2397C70248Fc6cDC056c') - reactTriggerChange(customInput[0]) + const customInput = (await findAsync(addTokenWrapper, '#custom-address'))[0] + customInput.focus() + await timeout(1000) + nativeInputValueSetter.call(customInput, '0x177af043D3A1Aed7cc5f2397C70248Fc6cDC056c') + customInput.dispatchEvent(new Event('input', { bubbles: true})) + // Click Next button - nextButton = await queryAsync($, 'button.btn-primary--lg') - assert.equal(nextButton[0].textContent, 'Next', 'next button rendered') - nextButton[0].click() + // nextButton = await queryAsync($, 'button.btn-primary--lg') + // assert.equal(nextButton[0].textContent, 'Next', 'next button rendered') + // nextButton[0].click() - // Verify symbol length error since contract address won't return symbol - const errorMessage = await queryAsync($, '.add-token__add-custom-error-message') + // // Verify symbol length error since contract address won't return symbol + const errorMessage = await queryAsync($, '#custom-symbol-helper-text') assert.ok(errorMessage[0], 'error rendered') $('button.btn-secondary--lg')[0].click() - // // Confirm Add token + // await timeout(100000) + + // Confirm Add token // assert.equal( - // $('.add-token__description')[0].textContent, + // $('.page-container__subtitle')[0].textContent, // 'Would you like to add these tokens?', // 'confirm add token rendered' // ) // assert.ok($('button.btn-primary--lg')[0], 'confirm add token button found') // $('button.btn-primary--lg')[0].click() - // // Verify added token image - // heroBalance = await queryAsync($, '.hero-balance') - // assert.ok(heroBalance, 'rendered hero balance') - // assert.ok(heroBalance.find('.identicon')[0], 'token added') + // Verify added token image + heroBalance = await queryAsync($, '.hero-balance') + assert.ok(heroBalance, 'rendered hero balance') + assert.ok(heroBalance.find('.identicon')[0], 'token added') } -- cgit v1.2.3 From 13ebb0b455bc775a53b6bb30e675a39d02d8f6f5 Mon Sep 17 00:00:00 2001 From: tmashuang Date: Mon, 21 May 2018 05:59:26 -0700 Subject: Moved loose some loose test files to sub folders --- test/unit/ComposableObservableStore.js | 35 -- test/unit/account-import-strategies.spec.js | 30 -- test/unit/address-book-controller.js | 54 -- test/unit/app/ComposableObservableStore.js | 35 ++ test/unit/app/account-import-strategies.spec.js | 31 ++ test/unit/app/buy-eth-url.spec.js | 48 ++ .../app/controllers/address-book-controller.js | 54 ++ .../app/controllers/blacklist-controller-test.js | 41 ++ .../app/controllers/currency-controller-test.js | 81 +++ .../unit/app/controllers/infura-controller-test.js | 62 +++ .../app/controllers/metamask-controller-test.js | 547 +++++++++++++++++++++ .../unit/app/controllers/network-contoller-test.js | 102 ++++ .../unit/app/controllers/notice-controller-test.js | 114 +++++ .../app/controllers/preferences-controller-test.js | 48 ++ .../unit/app/controllers/token-rates-controller.js | 29 ++ .../controllers/transactions/nonce-tracker-test.js | 239 +++++++++ .../controllers/transactions/pending-tx-test.js | 402 +++++++++++++++ .../controllers/transactions/tx-controller-test.js | 463 +++++++++++++++++ .../controllers/transactions/tx-gas-util-test.js | 77 +++ .../app/controllers/transactions/tx-helper-test.js | 17 + .../transactions/tx-state-history-helper-test.js | 129 +++++ .../transactions/tx-state-manager-test.js | 291 +++++++++++ .../app/controllers/transactions/tx-utils-test.js | 98 ++++ test/unit/app/edge-encryptor-test.js | 101 ++++ test/unit/app/message-manager-test.js | 86 ++++ test/unit/app/nodeify-test.js | 30 ++ test/unit/app/pending-balance-test.js | 93 ++++ test/unit/app/personal-message-manager-test.js | 107 ++++ test/unit/app/seed-phrase-verifier-test.js | 133 +++++ test/unit/app/util-test.js | 41 ++ test/unit/blacklist-controller-test.js | 41 -- test/unit/currency-controller-test.js | 81 --- test/unit/edge-encryptor-test.js | 101 ---- test/unit/infura-controller-test.js | 62 --- test/unit/message-manager-test.js | 86 ---- test/unit/metamask-controller-test.js | 547 --------------------- test/unit/migrations-test.js | 108 ---- test/unit/migrations/migrations-test.js | 108 ++++ test/unit/migrations/migrator-test.js | 68 +++ test/unit/migrator-test.js | 68 --- test/unit/network-contoller-test.js | 102 ---- test/unit/nodeify-test.js | 30 -- test/unit/nonce-tracker-test.js | 239 --------- test/unit/notice-controller-test.js | 114 ----- test/unit/pending-balance-test.js | 93 ---- test/unit/pending-tx-test.js | 402 --------------- test/unit/personal-message-manager-test.js | 107 ---- test/unit/preferences-controller-test.js | 48 -- test/unit/seed-phrase-verifier-test.js | 133 ----- test/unit/token-rates-controller.js | 29 -- test/unit/tx-controller-test.js | 463 ----------------- test/unit/tx-gas-util-test.js | 77 --- test/unit/tx-helper-test.js | 17 - test/unit/tx-state-history-helper-test.js | 129 ----- test/unit/tx-state-manager-test.js | 291 ----------- test/unit/tx-utils-test.js | 98 ---- test/unit/util-test.js | 41 -- 57 files changed, 3675 insertions(+), 3626 deletions(-) delete mode 100644 test/unit/ComposableObservableStore.js delete mode 100644 test/unit/account-import-strategies.spec.js delete mode 100644 test/unit/address-book-controller.js create mode 100644 test/unit/app/ComposableObservableStore.js create mode 100644 test/unit/app/account-import-strategies.spec.js create mode 100644 test/unit/app/buy-eth-url.spec.js create mode 100644 test/unit/app/controllers/address-book-controller.js create mode 100644 test/unit/app/controllers/blacklist-controller-test.js create mode 100644 test/unit/app/controllers/currency-controller-test.js create mode 100644 test/unit/app/controllers/infura-controller-test.js create mode 100644 test/unit/app/controllers/metamask-controller-test.js create mode 100644 test/unit/app/controllers/network-contoller-test.js create mode 100644 test/unit/app/controllers/notice-controller-test.js create mode 100644 test/unit/app/controllers/preferences-controller-test.js create mode 100644 test/unit/app/controllers/token-rates-controller.js create mode 100644 test/unit/app/controllers/transactions/nonce-tracker-test.js create mode 100644 test/unit/app/controllers/transactions/pending-tx-test.js create mode 100644 test/unit/app/controllers/transactions/tx-controller-test.js create mode 100644 test/unit/app/controllers/transactions/tx-gas-util-test.js create mode 100644 test/unit/app/controllers/transactions/tx-helper-test.js create mode 100644 test/unit/app/controllers/transactions/tx-state-history-helper-test.js create mode 100644 test/unit/app/controllers/transactions/tx-state-manager-test.js create mode 100644 test/unit/app/controllers/transactions/tx-utils-test.js create mode 100644 test/unit/app/edge-encryptor-test.js create mode 100644 test/unit/app/message-manager-test.js create mode 100644 test/unit/app/nodeify-test.js create mode 100644 test/unit/app/pending-balance-test.js create mode 100644 test/unit/app/personal-message-manager-test.js create mode 100644 test/unit/app/seed-phrase-verifier-test.js create mode 100644 test/unit/app/util-test.js delete mode 100644 test/unit/blacklist-controller-test.js delete mode 100644 test/unit/currency-controller-test.js delete mode 100644 test/unit/edge-encryptor-test.js delete mode 100644 test/unit/infura-controller-test.js delete mode 100644 test/unit/message-manager-test.js delete mode 100644 test/unit/metamask-controller-test.js delete mode 100644 test/unit/migrations-test.js create mode 100644 test/unit/migrations/migrations-test.js create mode 100644 test/unit/migrations/migrator-test.js delete mode 100644 test/unit/migrator-test.js delete mode 100644 test/unit/network-contoller-test.js delete mode 100644 test/unit/nodeify-test.js delete mode 100644 test/unit/nonce-tracker-test.js delete mode 100644 test/unit/notice-controller-test.js delete mode 100644 test/unit/pending-balance-test.js delete mode 100644 test/unit/pending-tx-test.js delete mode 100644 test/unit/personal-message-manager-test.js delete mode 100644 test/unit/preferences-controller-test.js delete mode 100644 test/unit/seed-phrase-verifier-test.js delete mode 100644 test/unit/token-rates-controller.js delete mode 100644 test/unit/tx-controller-test.js delete mode 100644 test/unit/tx-gas-util-test.js delete mode 100644 test/unit/tx-helper-test.js delete mode 100644 test/unit/tx-state-history-helper-test.js delete mode 100644 test/unit/tx-state-manager-test.js delete mode 100644 test/unit/tx-utils-test.js delete mode 100644 test/unit/util-test.js (limited to 'test') diff --git a/test/unit/ComposableObservableStore.js b/test/unit/ComposableObservableStore.js deleted file mode 100644 index 3fba200c1..000000000 --- a/test/unit/ComposableObservableStore.js +++ /dev/null @@ -1,35 +0,0 @@ -const assert = require('assert') -const ComposableObservableStore = require('../../app/scripts/lib/ComposableObservableStore') -const ObservableStore = require('obs-store') - -describe('ComposableObservableStore', () => { - it('should register initial state', () => { - const store = new ComposableObservableStore('state') - assert.strictEqual(store.getState(), 'state') - }) - - it('should register initial structure', () => { - const testStore = new ObservableStore() - const store = new ComposableObservableStore(null, { TestStore: testStore }) - testStore.putState('state') - assert.deepEqual(store.getState(), { TestStore: 'state' }) - }) - - it('should update structure', () => { - const testStore = new ObservableStore() - const store = new ComposableObservableStore() - store.updateStructure({ TestStore: testStore }) - testStore.putState('state') - assert.deepEqual(store.getState(), { TestStore: 'state' }) - }) - - it('should return flattened state', () => { - const fooStore = new ObservableStore({ foo: 'foo' }) - const barStore = new ObservableStore({ bar: 'bar' }) - const store = new ComposableObservableStore(null, { - FooStore: fooStore, - BarStore: barStore, - }) - assert.deepEqual(store.getFlatState(), { foo: 'foo', bar: 'bar' }) - }) -}) diff --git a/test/unit/account-import-strategies.spec.js b/test/unit/account-import-strategies.spec.js deleted file mode 100644 index acc39c88c..000000000 --- a/test/unit/account-import-strategies.spec.js +++ /dev/null @@ -1,30 +0,0 @@ -const assert = require('assert') -const accountImporter = require('../../app/scripts/account-import-strategies/index') -const ethUtil = require('ethereumjs-util') - -describe('Account Import Strategies', function () { - const privkey = '0x4cfd3e90fc78b0f86bf7524722150bb8da9c60cd532564d7ff43f5716514f553' - const json = '{"version":3,"id":"dbb54385-0a99-437f-83c0-647de9f244c3","address":"a7f92ce3fba24196cf6f4bd2e1eb3db282ba998c","Crypto":{"ciphertext":"bde13d9ade5c82df80281ca363320ce254a8a3a06535bbf6ffdeaf0726b1312c","cipherparams":{"iv":"fbf93718a57f26051b292f072f2e5b41"},"cipher":"aes-128-ctr","kdf":"scrypt","kdfparams":{"dklen":32,"salt":"7ffe00488319dec48e4c49a120ca49c6afbde9272854c64d9541c83fc6acdffe","n":8192,"r":8,"p":1},"mac":"2adfd9c4bc1cdac4c85bddfb31d9e21a684e0e050247a70c5698facf6b7d4681"}}' - - it('imports a private key and strips 0x prefix', async function () { - const importPrivKey = await accountImporter.importAccount('Private Key', [ privkey ]) - assert.equal(importPrivKey, ethUtil.stripHexPrefix(privkey)) - }) - - it('fails when password is incorrect for keystore', async function () { - const wrongPassword = 'password2' - - try { - await accountImporter.importAccount('JSON File', [ json, wrongPassword]) - } catch (error) { - assert.equal(error.message, 'Key derivation failed - possibly wrong passphrase') - } - }) - - it('imports json string and password to return a private key', async function () { - const fileContentsPassword = 'password1' - const importJson = await accountImporter.importAccount('JSON File', [ json, fileContentsPassword]) - assert.equal(importJson, '0x5733876abe94146069ce8bcbabbde2677f2e35fa33e875e92041ed2ac87e5bc7') - }) - -}) diff --git a/test/unit/address-book-controller.js b/test/unit/address-book-controller.js deleted file mode 100644 index 655c9022c..000000000 --- a/test/unit/address-book-controller.js +++ /dev/null @@ -1,54 +0,0 @@ -const assert = require('assert') -const AddressBookController = require('../../app/scripts/controllers/address-book') - -const mockKeyringController = { - memStore: { - getState: function () { - return { - identities: { - '0x0aaa': { - address: '0x0aaa', - name: 'owned', - }, - }, - } - }, - }, -} - -describe('address-book-controller', function () { - var addressBookController - - beforeEach(function () { - addressBookController = new AddressBookController({}, mockKeyringController) - }) - - describe('addres book management', function () { - describe('#_getAddressBook', function () { - it('should be empty by default.', function () { - assert.equal(addressBookController._getAddressBook().length, 0) - }) - }) - describe('#setAddressBook', function () { - it('should properly set a new address.', function () { - addressBookController.setAddressBook('0x01234', 'test') - var addressBook = addressBookController._getAddressBook() - assert.equal(addressBook.length, 1, 'incorrect address book length.') - assert.equal(addressBook[0].address, '0x01234', 'incorrect addresss') - assert.equal(addressBook[0].name, 'test', 'incorrect nickname') - }) - - it('should reject duplicates.', function () { - addressBookController.setAddressBook('0x01234', 'test') - addressBookController.setAddressBook('0x01234', 'test') - var addressBook = addressBookController._getAddressBook() - assert.equal(addressBook.length, 1, 'incorrect address book length.') - }) - it('should not add any identities that are under user control', function () { - addressBookController.setAddressBook('0x0aaa', ' ') - var addressBook = addressBookController._getAddressBook() - assert.equal(addressBook.length, 0, 'incorrect address book length.') - }) - }) - }) -}) diff --git a/test/unit/app/ComposableObservableStore.js b/test/unit/app/ComposableObservableStore.js new file mode 100644 index 000000000..aa8abd463 --- /dev/null +++ b/test/unit/app/ComposableObservableStore.js @@ -0,0 +1,35 @@ +const assert = require('assert') +const ComposableObservableStore = require('../../../app/scripts/lib/ComposableObservableStore') +const ObservableStore = require('obs-store') + +describe('ComposableObservableStore', () => { + it('should register initial state', () => { + const store = new ComposableObservableStore('state') + assert.strictEqual(store.getState(), 'state') + }) + + it('should register initial structure', () => { + const testStore = new ObservableStore() + const store = new ComposableObservableStore(null, { TestStore: testStore }) + testStore.putState('state') + assert.deepEqual(store.getState(), { TestStore: 'state' }) + }) + + it('should update structure', () => { + const testStore = new ObservableStore() + const store = new ComposableObservableStore() + store.updateStructure({ TestStore: testStore }) + testStore.putState('state') + assert.deepEqual(store.getState(), { TestStore: 'state' }) + }) + + it('should return flattened state', () => { + const fooStore = new ObservableStore({ foo: 'foo' }) + const barStore = new ObservableStore({ bar: 'bar' }) + const store = new ComposableObservableStore(null, { + FooStore: fooStore, + BarStore: barStore, + }) + assert.deepEqual(store.getFlatState(), { foo: 'foo', bar: 'bar' }) + }) +}) diff --git a/test/unit/app/account-import-strategies.spec.js b/test/unit/app/account-import-strategies.spec.js new file mode 100644 index 000000000..83cfaeb3e --- /dev/null +++ b/test/unit/app/account-import-strategies.spec.js @@ -0,0 +1,31 @@ +const assert = require('assert') +const path = require('path') +const accountImporter = require('../../../app/scripts/account-import-strategies/index') +const ethUtil = require('ethereumjs-util') + +describe('Account Import Strategies', function () { + const privkey = '0x4cfd3e90fc78b0f86bf7524722150bb8da9c60cd532564d7ff43f5716514f553' + const json = '{"version":3,"id":"dbb54385-0a99-437f-83c0-647de9f244c3","address":"a7f92ce3fba24196cf6f4bd2e1eb3db282ba998c","Crypto":{"ciphertext":"bde13d9ade5c82df80281ca363320ce254a8a3a06535bbf6ffdeaf0726b1312c","cipherparams":{"iv":"fbf93718a57f26051b292f072f2e5b41"},"cipher":"aes-128-ctr","kdf":"scrypt","kdfparams":{"dklen":32,"salt":"7ffe00488319dec48e4c49a120ca49c6afbde9272854c64d9541c83fc6acdffe","n":8192,"r":8,"p":1},"mac":"2adfd9c4bc1cdac4c85bddfb31d9e21a684e0e050247a70c5698facf6b7d4681"}}' + + it('imports a private key and strips 0x prefix', async function () { + const importPrivKey = await accountImporter.importAccount('Private Key', [ privkey ]) + assert.equal(importPrivKey, ethUtil.stripHexPrefix(privkey)) + }) + + it('fails when password is incorrect for keystore', async function () { + const wrongPassword = 'password2' + + try { + await accountImporter.importAccount('JSON File', [ json, wrongPassword]) + } catch (error) { + assert.equal(error.message, 'Key derivation failed - possibly wrong passphrase') + } + }) + + it('imports json string and password to return a private key', async function () { + const fileContentsPassword = 'password1' + const importJson = await accountImporter.importAccount('JSON File', [ json, fileContentsPassword]) + assert.equal(importJson, '0x5733876abe94146069ce8bcbabbde2677f2e35fa33e875e92041ed2ac87e5bc7') + }) + +}) diff --git a/test/unit/app/buy-eth-url.spec.js b/test/unit/app/buy-eth-url.spec.js new file mode 100644 index 000000000..36646fa68 --- /dev/null +++ b/test/unit/app/buy-eth-url.spec.js @@ -0,0 +1,48 @@ +const assert = require('assert') +const getBuyEthUrl = require('../../../app/scripts/lib/buy-eth-url') + +describe('', function () { + const mainnet = { + network: '1', + amount: 5, + address: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc', + } + const ropsten = { + network: '3', + } + const rinkeby = { + network: '4', + } + const kovan = { + network: '42', + } + + it('returns coinbase url with amount and address for network 1', function () { + const coinbaseUrl = getBuyEthUrl(mainnet) + const coinbase = coinbaseUrl.match(/(https:\/\/buy.coinbase.com)/) + const amount = coinbaseUrl.match(/(amount)\D\d/) + const address = coinbaseUrl.match(/(address)(.*)(?=&)/) + + assert.equal(coinbase[0], 'https://buy.coinbase.com') + assert.equal(amount[0], 'amount=5') + assert.equal(address[0], 'address=0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc') + + }) + + it('returns metamask ropsten faucet for network 3', function () { + const ropstenUrl = getBuyEthUrl(ropsten) + assert.equal(ropstenUrl, 'https://faucet.metamask.io/') + }) + + it('returns rinkeby dapp for network 4', function () { + const rinkebyUrl = getBuyEthUrl(rinkeby) + assert.equal(rinkebyUrl, 'https://www.rinkeby.io/') + }) + + it('returns kovan github test faucet for network 42', function () { + const kovanUrl = getBuyEthUrl(kovan) + assert.equal(kovanUrl, 'https://github.com/kovan-testnet/faucet') + }) + +}) + diff --git a/test/unit/app/controllers/address-book-controller.js b/test/unit/app/controllers/address-book-controller.js new file mode 100644 index 000000000..1836dea94 --- /dev/null +++ b/test/unit/app/controllers/address-book-controller.js @@ -0,0 +1,54 @@ +const assert = require('assert') +const AddressBookController = require('../../../../app/scripts/controllers/address-book') + +const mockKeyringController = { + memStore: { + getState: function () { + return { + identities: { + '0x0aaa': { + address: '0x0aaa', + name: 'owned', + }, + }, + } + }, + }, +} + +describe('address-book-controller', function () { + var addressBookController + + beforeEach(function () { + addressBookController = new AddressBookController({}, mockKeyringController) + }) + + describe('addres book management', function () { + describe('#_getAddressBook', function () { + it('should be empty by default.', function () { + assert.equal(addressBookController._getAddressBook().length, 0) + }) + }) + describe('#setAddressBook', function () { + it('should properly set a new address.', function () { + addressBookController.setAddressBook('0x01234', 'test') + var addressBook = addressBookController._getAddressBook() + assert.equal(addressBook.length, 1, 'incorrect address book length.') + assert.equal(addressBook[0].address, '0x01234', 'incorrect addresss') + assert.equal(addressBook[0].name, 'test', 'incorrect nickname') + }) + + it('should reject duplicates.', function () { + addressBookController.setAddressBook('0x01234', 'test') + addressBookController.setAddressBook('0x01234', 'test') + var addressBook = addressBookController._getAddressBook() + assert.equal(addressBook.length, 1, 'incorrect address book length.') + }) + it('should not add any identities that are under user control', function () { + addressBookController.setAddressBook('0x0aaa', ' ') + var addressBook = addressBookController._getAddressBook() + assert.equal(addressBook.length, 0, 'incorrect address book length.') + }) + }) + }) +}) diff --git a/test/unit/app/controllers/blacklist-controller-test.js b/test/unit/app/controllers/blacklist-controller-test.js new file mode 100644 index 000000000..085641777 --- /dev/null +++ b/test/unit/app/controllers/blacklist-controller-test.js @@ -0,0 +1,41 @@ +const assert = require('assert') +const BlacklistController = require('../../../../app/scripts/controllers/blacklist') + +describe('blacklist controller', function () { + let blacklistController + + before(() => { + blacklistController = new BlacklistController() + }) + + describe('checkForPhishing', function () { + it('should not flag whitelisted values', function () { + const result = blacklistController.checkForPhishing('www.metamask.io') + assert.equal(result, false) + }) + it('should flag explicit values', function () { + const result = blacklistController.checkForPhishing('metamask.com') + assert.equal(result, true) + }) + it('should flag levenshtein values', function () { + const result = blacklistController.checkForPhishing('metmask.io') + assert.equal(result, true) + }) + it('should not flag not-even-close values', function () { + const result = blacklistController.checkForPhishing('example.com') + assert.equal(result, false) + }) + it('should not flag the ropsten faucet domains', function () { + const result = blacklistController.checkForPhishing('faucet.metamask.io') + assert.equal(result, false) + }) + it('should not flag the mascara domain', function () { + const result = blacklistController.checkForPhishing('zero.metamask.io') + assert.equal(result, false) + }) + it('should not flag the mascara-faucet domain', function () { + const result = blacklistController.checkForPhishing('zero-faucet.metamask.io') + assert.equal(result, false) + }) + }) +}) diff --git a/test/unit/app/controllers/currency-controller-test.js b/test/unit/app/controllers/currency-controller-test.js new file mode 100644 index 000000000..1941d1c43 --- /dev/null +++ b/test/unit/app/controllers/currency-controller-test.js @@ -0,0 +1,81 @@ +// polyfill fetch +global.fetch = global.fetch || require('isomorphic-fetch') + +const assert = require('assert') +const nock = require('nock') +const CurrencyController = require('../../../../app/scripts/controllers/currency') + +describe('currency-controller', function () { + var currencyController + + beforeEach(function () { + currencyController = new CurrencyController() + }) + + describe('currency conversions', function () { + describe('#setCurrentCurrency', function () { + it('should return USD as default', function () { + assert.equal(currencyController.getCurrentCurrency(), 'usd') + }) + + it('should be able to set to other currency', function () { + assert.equal(currencyController.getCurrentCurrency(), 'usd') + currencyController.setCurrentCurrency('JPY') + var result = currencyController.getCurrentCurrency() + assert.equal(result, 'JPY') + }) + }) + + describe('#getConversionRate', function () { + it('should return undefined if non-existent', function () { + var result = currencyController.getConversionRate() + assert.ok(!result) + }) + }) + + describe('#updateConversionRate', function () { + it('should retrieve an update for ETH to USD and set it in memory', function (done) { + this.timeout(15000) + nock('https://api.infura.io') + .get('/v1/ticker/ethusd') + .reply(200, '{"base": "ETH", "quote": "USD", "bid": 288.45, "ask": 288.46, "volume": 112888.17569277, "exchange": "bitfinex", "total_volume": 272175.00106721005, "num_exchanges": 8, "timestamp": 1506444677}') + + assert.equal(currencyController.getConversionRate(), 0) + currencyController.setCurrentCurrency('usd') + currencyController.updateConversionRate() + .then(function () { + var result = currencyController.getConversionRate() + assert.equal(typeof result, 'number') + done() + }).catch(function (err) { + done(err) + }) + }) + + it('should work for JPY as well.', function () { + this.timeout(15000) + assert.equal(currencyController.getConversionRate(), 0) + + nock('https://api.infura.io') + .get('/v1/ticker/ethjpy') + .reply(200, '{"base": "ETH", "quote": "JPY", "bid": 32300.0, "ask": 32400.0, "volume": 247.4616071, "exchange": "kraken", "total_volume": 247.4616071, "num_exchanges": 1, "timestamp": 1506444676}') + + + var promise = new Promise( + function (resolve, reject) { + currencyController.setCurrentCurrency('jpy') + currencyController.updateConversionRate().then(function () { + resolve() + }) + }) + + promise.then(function () { + var result = currencyController.getConversionRate() + assert.equal(typeof result, 'number') + }).catch(function (done, err) { + done(err) + }) + }) + }) + }) +}) diff --git a/test/unit/app/controllers/infura-controller-test.js b/test/unit/app/controllers/infura-controller-test.js new file mode 100644 index 000000000..7bd95dd4b --- /dev/null +++ b/test/unit/app/controllers/infura-controller-test.js @@ -0,0 +1,62 @@ +const assert = require('assert') +const sinon = require('sinon') +const InfuraController = require('../../../../app/scripts/controllers/infura') + +describe('infura-controller', function () { + let infuraController, sandbox, networkStatus + const response = {'mainnet': 'degraded', 'ropsten': 'ok', 'kovan': 'ok', 'rinkeby': 'down'} + + before(async function () { + infuraController = new InfuraController() + sandbox = sinon.createSandbox() + sinon.stub(infuraController, 'checkInfuraNetworkStatus').resolves(response) + networkStatus = await infuraController.checkInfuraNetworkStatus() + }) + + after(function () { + sandbox.restore() + }) + + describe('Network status queries', function () { + + describe('Mainnet', function () { + it('should have Mainnet', function () { + assert.equal(Object.keys(networkStatus)[0], 'mainnet') + }) + + it('should have a value for Mainnet status', function () { + assert.equal(networkStatus.mainnet, 'degraded') + }) + }) + + describe('Ropsten', function () { + it('should have Ropsten', function () { + assert.equal(Object.keys(networkStatus)[1], 'ropsten') + }) + + it('should have a value for Ropsten status', function () { + assert.equal(networkStatus.ropsten, 'ok') + }) + }) + + describe('Kovan', function () { + it('should have Kovan', function () { + assert.equal(Object.keys(networkStatus)[2], 'kovan') + }) + + it('should have a value for Kovan status', function () { + assert.equal(networkStatus.kovan, 'ok') + }) + }) + + describe('Rinkeby', function () { + it('should have Rinkeby', function () { + assert.equal(Object.keys(networkStatus)[3], 'rinkeby') + }) + + it('should have a value for Rinkeby status', function () { + assert.equal(networkStatus.rinkeby, 'down') + }) + }) + }) +}) diff --git a/test/unit/app/controllers/metamask-controller-test.js b/test/unit/app/controllers/metamask-controller-test.js new file mode 100644 index 000000000..65a6fea09 --- /dev/null +++ b/test/unit/app/controllers/metamask-controller-test.js @@ -0,0 +1,547 @@ +const assert = require('assert') +const sinon = require('sinon') +const clone = require('clone') +const nock = require('nock') +const createThoughStream = require('through2').obj +const MetaMaskController = require('../../../../app/scripts/metamask-controller') +const blacklistJSON = require('../../../stub/blacklist') +const firstTimeState = require('../../../../app/scripts/first-time-state') + +const currentNetworkId = 42 +const DEFAULT_LABEL = 'Account 1' +const TEST_SEED = 'debris dizzy just program just float decrease vacant alarm reduce speak stadium' +const TEST_ADDRESS = '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc' +const TEST_SEED_ALT = 'setup olympic issue mobile velvet surge alcohol burger horse view reopen gentle' +const TEST_ADDRESS_ALT = '0xc42edfcc21ed14dda456aa0756c153f7985d8813' + +describe('MetaMaskController', function () { + let metamaskController + const sandbox = sinon.createSandbox() + const noop = () => {} + + beforeEach(function () { + + nock('https://api.infura.io') + .persist() + .get('/v2/blacklist') + .reply(200, blacklistJSON) + + nock('https://api.infura.io') + .get('/v1/ticker/ethusd') + .reply(200, '{"base": "ETH", "quote": "USD", "bid": 288.45, "ask": 288.46, "volume": 112888.17569277, "exchange": "bitfinex", "total_volume": 272175.00106721005, "num_exchanges": 8, "timestamp": 1506444677}') + + nock('https://api.infura.io') + .get('/v1/ticker/ethjpy') + .reply(200, '{"base": "ETH", "quote": "JPY", "bid": 32300.0, "ask": 32400.0, "volume": 247.4616071, "exchange": "kraken", "total_volume": 247.4616071, "num_exchanges": 1, "timestamp": 1506444676}') + + nock('https://api.infura.io') + .persist() + .get(/.*/) + .reply(200) + + metamaskController = new MetaMaskController({ + showUnapprovedTx: noop, + showUnconfirmedMessage: noop, + encryptor: { + encrypt: function (password, object) { + this.object = object + return Promise.resolve() + }, + decrypt: function () { + return Promise.resolve(this.object) + }, + }, + initState: clone(firstTimeState), + }) + sandbox.spy(metamaskController.keyringController, 'createNewVaultAndKeychain') + sandbox.spy(metamaskController.keyringController, 'createNewVaultAndRestore') + }) + + afterEach(function () { + nock.cleanAll() + sandbox.restore() + }) + + describe('#getGasPrice', function () { + + it('gives the 50th percentile lowest accepted gas price from recentBlocksController', async function () { + const realRecentBlocksController = metamaskController.recentBlocksController + metamaskController.recentBlocksController = { + store: { + getState: () => { + return { + recentBlocks: [ + { gasPrices: [ '0x3b9aca00', '0x174876e800'] }, + { gasPrices: [ '0x3b9aca00', '0x174876e800'] }, + { gasPrices: [ '0x174876e800', '0x174876e800' ]}, + { gasPrices: [ '0x174876e800', '0x174876e800' ]}, + ], + } + }, + }, + } + + const gasPrice = metamaskController.getGasPrice() + assert.equal(gasPrice, '0x3b9aca00', 'accurately estimates 50th percentile accepted gas price') + + metamaskController.recentBlocksController = realRecentBlocksController + }) + }) + + describe('#createNewVaultAndKeychain', function () { + it('can only create new vault on keyringController once', async function () { + const selectStub = sandbox.stub(metamaskController, 'selectFirstIdentity') + + const password = 'a-fake-password' + + await metamaskController.createNewVaultAndKeychain(password) + await metamaskController.createNewVaultAndKeychain(password) + + assert(metamaskController.keyringController.createNewVaultAndKeychain.calledOnce) + + selectStub.reset() + }) + }) + + describe('#createNewVaultAndRestore', function () { + it('should be able to call newVaultAndRestore despite a mistake.', async function () { + const password = 'what-what-what' + await metamaskController.createNewVaultAndRestore(password, TEST_SEED.slice(0, -1)).catch((e) => null) + await metamaskController.createNewVaultAndRestore(password, TEST_SEED) + + assert(metamaskController.keyringController.createNewVaultAndRestore.calledTwice) + }) + + it('should clear previous identities after vault restoration', async () => { + await metamaskController.createNewVaultAndRestore('foobar1337', TEST_SEED) + assert.deepEqual(metamaskController.getState().identities, { + [TEST_ADDRESS]: { address: TEST_ADDRESS, name: DEFAULT_LABEL }, + }) + + await metamaskController.keyringController.saveAccountLabel(TEST_ADDRESS, 'Account Foo') + assert.deepEqual(metamaskController.getState().identities, { + [TEST_ADDRESS]: { address: TEST_ADDRESS, name: 'Account Foo' }, + }) + + await metamaskController.createNewVaultAndRestore('foobar1337', TEST_SEED_ALT) + assert.deepEqual(metamaskController.getState().identities, { + [TEST_ADDRESS_ALT]: { address: TEST_ADDRESS_ALT, name: DEFAULT_LABEL }, + }) + }) + }) + + describe('#getApi', function () { + let getApi, state + + beforeEach(function () { + getApi = metamaskController.getApi() + }) + + it('getState', function (done) { + getApi.getState((err, res) => { + if (err) { + done(err) + } else { + state = res + } + }) + assert.deepEqual(state, metamaskController.getState()) + done() + }) + + }) + + describe('preferencesController', function () { + + it('defaults useBlockie to false', function () { + assert.equal(metamaskController.preferencesController.store.getState().useBlockie, false) + }) + + it('setUseBlockie to true', function () { + metamaskController.setUseBlockie(true, noop) + assert.equal(metamaskController.preferencesController.store.getState().useBlockie, true) + }) + + }) + + describe('#selectFirstIdentity', function () { + let identities, address + + beforeEach(function () { + address = '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc' + identities = { + identities: { + '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc': { + 'address': '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc', + 'name': 'Account 1', + }, + }, + } + metamaskController.selectFirstIdentity(identities) + }) + + it('changes preferences controller select address', function () { + const preferenceControllerState = metamaskController.preferencesController.store.getState() + assert.equal(preferenceControllerState.selectedAddress, address) + }) + + it('changes metamask controller selected address', function () { + const metamaskState = metamaskController.getState() + assert.equal(metamaskState.selectedAddress, address) + }) + }) + + describe('#setCustomRpc', function () { + const customRPC = 'https://custom.rpc/' + let rpcTarget + + beforeEach(function () { + + nock('https://custom.rpc') + .post('/') + .reply(200) + + rpcTarget = metamaskController.setCustomRpc(customRPC) + }) + + afterEach(function () { + nock.cleanAll() + }) + + it('returns custom RPC that when called', async function () { + assert.equal(await rpcTarget, customRPC) + }) + + it('changes the network controller rpc', function () { + const networkControllerState = metamaskController.networkController.store.getState() + assert.equal(networkControllerState.provider.rpcTarget, customRPC) + }) + }) + + describe('#setCurrentCurrency', function () { + let defaultMetaMaskCurrency + + beforeEach(function () { + defaultMetaMaskCurrency = metamaskController.currencyController.getCurrentCurrency() + }) + + it('defaults to usd', function () { + assert.equal(defaultMetaMaskCurrency, 'usd') + }) + + it('sets currency to JPY', function () { + metamaskController.setCurrentCurrency('JPY', noop) + assert.equal(metamaskController.currencyController.getCurrentCurrency(), 'JPY') + }) + }) + + describe('#createShapeshifttx', function () { + let depositAddress, depositType, shapeShiftTxList + + beforeEach(function () { + nock('https://shapeshift.io') + .get('/txStat/3EevLFfB4H4XMWQwYCgjLie1qCAGpd2WBc') + .reply(200, '{"status": "no_deposits", "address": "3EevLFfB4H4XMWQwYCgjLie1qCAGpd2WBc"}') + + depositAddress = '3EevLFfB4H4XMWQwYCgjLie1qCAGpd2WBc' + depositType = 'ETH' + shapeShiftTxList = metamaskController.shapeshiftController.store.getState().shapeShiftTxList + }) + + it('creates a shapeshift tx', async function () { + metamaskController.createShapeShiftTx(depositAddress, depositType) + assert.equal(shapeShiftTxList[0].depositAddress, depositAddress) + }) + + }) + + describe('#addNewAccount', function () { + let addNewAccount + + beforeEach(function () { + addNewAccount = metamaskController.addNewAccount() + }) + + it('errors when an primary keyring is does not exist', async function () { + try { + await addNewAccount + assert.equal(1 === 0) + } catch (e) { + assert.equal(e.message, 'MetamaskController - No HD Key Tree found') + } + }) + }) + + describe('#verifyseedPhrase', function () { + let seedPhrase, getConfigSeed + + it('errors when no keying is provided', async function () { + try { + await metamaskController.verifySeedPhrase() + } catch (error) { + assert.equal(error.message, 'MetamaskController - No HD Key Tree found') + } + }) + + beforeEach(async function () { + await metamaskController.createNewVaultAndKeychain('password') + seedPhrase = await metamaskController.verifySeedPhrase() + }) + + it('#placeSeedWords should match the initially created vault seed', function () { + + metamaskController.placeSeedWords((err, result) => { + if (err) { + console.log(err) + } else { + getConfigSeed = metamaskController.configManager.getSeedWords() + assert.equal(result, seedPhrase) + assert.equal(result, getConfigSeed) + } + }) + assert.equal(getConfigSeed, undefined) + }) + + it('#addNewAccount', async function () { + await metamaskController.addNewAccount() + const getAccounts = await metamaskController.keyringController.getAccounts() + assert.equal(getAccounts.length, 2) + }) + }) + + describe('#resetAccount', function () { + + beforeEach(function () { + const selectedAddressStub = sinon.stub(metamaskController.preferencesController, 'getSelectedAddress') + const getNetworkstub = sinon.stub(metamaskController.txController.txStateManager, 'getNetwork') + + selectedAddressStub.returns('0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc') + getNetworkstub.returns(42) + + metamaskController.txController.txStateManager._saveTxList([ + { id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {from: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc'} }, + { id: 2, status: 'rejected', metamaskNetworkId: 32, txParams: {} }, + { id: 3, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams: {from: '0xB09d8505E1F4EF1CeA089D47094f5DD3464083d4'} }, + ]) + }) + + it('wipes transactions from only the correct network id and with the selected address', async function () { + await metamaskController.resetAccount() + assert.equal(metamaskController.txController.txStateManager.getTx(1), undefined) + }) + }) + + describe('#clearSeedWordCache', function () { + + it('should have set seed words', function () { + metamaskController.configManager.setSeedWords('test words') + const getConfigSeed = metamaskController.configManager.getSeedWords() + assert.equal(getConfigSeed, 'test words') + }) + + it('should clear config seed phrase', function () { + metamaskController.configManager.setSeedWords('test words') + metamaskController.clearSeedWordCache((err, result) => { + if (err) console.log(err) + }) + const getConfigSeed = metamaskController.configManager.getSeedWords() + assert.equal(getConfigSeed, null) + }) + + }) + + describe('#setCurrentLocale', function () { + + it('checks the default currentLocale', function () { + const preferenceCurrentLocale = metamaskController.preferencesController.store.getState().currentLocale + assert.equal(preferenceCurrentLocale, undefined) + }) + + it('sets current locale in preferences controller', function () { + metamaskController.setCurrentLocale('ja', noop) + const preferenceCurrentLocale = metamaskController.preferencesController.store.getState().currentLocale + assert.equal(preferenceCurrentLocale, 'ja') + }) + + }) + + describe('#newUnsignedMessage', function () { + + let msgParams, metamaskMsgs, messages, msgId + + const address = '0xc42edfcc21ed14dda456aa0756c153f7985d8813' + const data = '0x43727970746f6b697474696573' + + beforeEach(async function () { + + await metamaskController.createNewVaultAndRestore('foobar1337', TEST_SEED_ALT) + + msgParams = { + 'from': address, + 'data': data, + } + + metamaskController.newUnsignedMessage(msgParams, noop) + metamaskMsgs = metamaskController.messageManager.getUnapprovedMsgs() + messages = metamaskController.messageManager.messages + msgId = Object.keys(metamaskMsgs)[0] + messages[0].msgParams.metamaskId = parseInt(msgId) + }) + + it('persists address from msg params', function () { + assert.equal(metamaskMsgs[msgId].msgParams.from, address) + }) + + it('persists data from msg params', function () { + assert.equal(metamaskMsgs[msgId].msgParams.data, data) + }) + + it('sets the status to unapproved', function () { + assert.equal(metamaskMsgs[msgId].status, 'unapproved') + }) + + it('sets the type to eth_sign', function () { + assert.equal(metamaskMsgs[msgId].type, 'eth_sign') + }) + + it('rejects the message', function () { + const msgIdInt = parseInt(msgId) + metamaskController.cancelMessage(msgIdInt, noop) + assert.equal(messages[0].status, 'rejected') + }) + + it('errors when signing a message', async function () { + try { + await metamaskController.signMessage(messages[0].msgParams) + } catch (error) { + assert.equal(error.message, 'message length is invalid') + } + }) + }) + + describe('#newUnsignedPersonalMessage', function () { + + it('errors with no from in msgParams', function () { + const msgParams = { + 'data': data, + } + metamaskController.newUnsignedPersonalMessage(msgParams, function (error) { + assert.equal(error.message, 'MetaMask Message Signature: from field is required.') + }) + }) + + let msgParams, metamaskPersonalMsgs, personalMessages, msgId + + const address = '0xc42edfcc21ed14dda456aa0756c153f7985d8813' + const data = '0x43727970746f6b697474696573' + + beforeEach(async function () { + + await metamaskController.createNewVaultAndRestore('foobar1337', TEST_SEED_ALT) + + msgParams = { + 'from': address, + 'data': data, + } + + metamaskController.newUnsignedPersonalMessage(msgParams, noop) + metamaskPersonalMsgs = metamaskController.personalMessageManager.getUnapprovedMsgs() + personalMessages = metamaskController.personalMessageManager.messages + msgId = Object.keys(metamaskPersonalMsgs)[0] + personalMessages[0].msgParams.metamaskId = parseInt(msgId) + }) + + it('persists address from msg params', function () { + assert.equal(metamaskPersonalMsgs[msgId].msgParams.from, address) + }) + + it('persists data from msg params', function () { + assert.equal(metamaskPersonalMsgs[msgId].msgParams.data, data) + }) + + it('sets the status to unapproved', function () { + assert.equal(metamaskPersonalMsgs[msgId].status, 'unapproved') + }) + + it('sets the type to personal_sign', function () { + assert.equal(metamaskPersonalMsgs[msgId].type, 'personal_sign') + }) + + it('rejects the message', function () { + const msgIdInt = parseInt(msgId) + metamaskController.cancelPersonalMessage(msgIdInt, noop) + assert.equal(personalMessages[0].status, 'rejected') + }) + + it('errors when signing a message', async function () { + await metamaskController.signPersonalMessage(personalMessages[0].msgParams) + assert.equal(metamaskPersonalMsgs[msgId].status, 'signed') + assert.equal(metamaskPersonalMsgs[msgId].rawSig, '0x6a1b65e2b8ed53cf398a769fad24738f9fbe29841fe6854e226953542c4b6a173473cb152b6b1ae5f06d601d45dd699a129b0a8ca84e78b423031db5baa734741b') + }) + }) + + describe('#setupUntrustedCommunication', function () { + let streamTest + + const phishingUrl = 'decentral.market' + + afterEach(function () { + streamTest.end() + }) + + it('sets up phishing stream for untrusted communication ', async function () { + await metamaskController.blacklistController.updatePhishingList() + + streamTest = createThoughStream((chunk, enc, cb) => { + assert.equal(chunk.name, 'phishing') + assert.equal(chunk.data.hostname, phishingUrl) + cb() + }) + // console.log(streamTest) + metamaskController.setupUntrustedCommunication(streamTest, phishingUrl) + }) + }) + + describe('#setupTrustedCommunication', function () { + let streamTest + + afterEach(function () { + streamTest.end() + }) + + it('sets up controller dnode api for trusted communication', function (done) { + streamTest = createThoughStream((chunk, enc, cb) => { + assert.equal(chunk.name, 'controller') + cb() + done() + }) + + metamaskController.setupTrustedCommunication(streamTest, 'mycrypto.com') + }) + }) + + describe('#markAccountsFound', function () { + it('adds lost accounts to config manager data', function () { + metamaskController.markAccountsFound(noop) + const configManagerData = metamaskController.configManager.getData() + assert.deepEqual(configManagerData.lostAccounts, []) + }) + }) + + describe('#markPasswordForgotten', function () { + it('adds and sets forgottenPassword to config data to true', function () { + metamaskController.markPasswordForgotten(noop) + const configManagerData = metamaskController.configManager.getData() + assert.equal(configManagerData.forgottenPassword, true) + }) + }) + + describe('#unMarkPasswordForgotten', function () { + it('adds and sets forgottenPassword to config data to false', function () { + metamaskController.unMarkPasswordForgotten(noop) + const configManagerData = metamaskController.configManager.getData() + assert.equal(configManagerData.forgottenPassword, false) + }) + }) + +}) diff --git a/test/unit/app/controllers/network-contoller-test.js b/test/unit/app/controllers/network-contoller-test.js new file mode 100644 index 000000000..789850ef3 --- /dev/null +++ b/test/unit/app/controllers/network-contoller-test.js @@ -0,0 +1,102 @@ +const assert = require('assert') +const nock = require('nock') +const NetworkController = require('../../../../app/scripts/controllers/network') +const { + getNetworkDisplayName, +} = require('../../../../app/scripts/controllers/network/util') + +const { createTestProviderTools } = require('../../../stub/provider') +const providerResultStub = {} + +describe('# Network Controller', function () { + let networkController + const noop = () => {} + const networkControllerProviderConfig = { + getAccounts: noop, + } + + beforeEach(function () { + + nock('https://rinkeby.infura.io') + .persist() + .post('/metamask') + .reply(200) + + networkController = new NetworkController() + + networkController.initializeProvider(networkControllerProviderConfig) + }) + + afterEach(function () { + nock.cleanAll() + }) + + describe('network', function () { + describe('#provider', function () { + it('provider should be updatable without reassignment', function () { + networkController.initializeProvider(networkControllerProviderConfig) + const proxy = networkController._proxy + proxy.setTarget({ test: true, on: () => {} }) + assert.ok(proxy.test) + }) + }) + describe('#getNetworkState', function () { + it('should return loading when new', function () { + const networkState = networkController.getNetworkState() + assert.equal(networkState, 'loading', 'network is loading') + }) + }) + + describe('#setNetworkState', function () { + it('should update the network', function () { + networkController.setNetworkState(1) + const networkState = networkController.getNetworkState() + assert.equal(networkState, 1, 'network is 1') + }) + }) + + describe('#setProviderType', function () { + it('should update provider.type', function () { + networkController.setProviderType('mainnet') + const type = networkController.getProviderConfig().type + assert.equal(type, 'mainnet', 'provider type is updated') + }) + it('should set the network to loading', function () { + networkController.setProviderType('mainnet') + const loading = networkController.isNetworkLoading() + assert.ok(loading, 'network is loading') + }) + }) + }) +}) + +describe('Network utils', () => { + it('getNetworkDisplayName should return the correct network name', () => { + const tests = [ + { + input: 3, + expected: 'Ropsten', + }, { + input: 4, + expected: 'Rinkeby', + }, { + input: 42, + expected: 'Kovan', + }, { + input: 'ropsten', + expected: 'Ropsten', + }, { + input: 'rinkeby', + expected: 'Rinkeby', + }, { + input: 'kovan', + expected: 'Kovan', + }, { + input: 'mainnet', + expected: 'Main Ethereum Network', + }, + ] + + tests.forEach(({ input, expected }) => assert.equal(getNetworkDisplayName(input), expected)) + }) +}) diff --git a/test/unit/app/controllers/notice-controller-test.js b/test/unit/app/controllers/notice-controller-test.js new file mode 100644 index 000000000..e78b69623 --- /dev/null +++ b/test/unit/app/controllers/notice-controller-test.js @@ -0,0 +1,114 @@ +const assert = require('assert') +const configManagerGen = require('../../../lib/mock-config-manager') +const NoticeController = require('../../../../app/scripts/notice-controller') + +describe('notice-controller', function () { + var noticeController + + beforeEach(function () { + // simple localStorage polyfill + const configManager = configManagerGen() + noticeController = new NoticeController({ + configManager: configManager, + }) + }) + + describe('notices', function () { + describe('#getNoticesList', function () { + it('should return an empty array when new', function (done) { + // const testList = [{ + // id: 0, + // read: false, + // title: 'Futuristic Notice', + // }] + var result = noticeController.getNoticesList() + assert.equal(result.length, 0) + done() + }) + }) + + describe('#setNoticesList', function () { + it('should set data appropriately', function (done) { + var testList = [{ + id: 0, + read: false, + title: 'Futuristic Notice', + }] + noticeController.setNoticesList(testList) + var testListId = noticeController.getNoticesList()[0].id + assert.equal(testListId, 0) + done() + }) + }) + + describe('#updateNoticeslist', function () { + it('should integrate the latest changes from the source', function (done) { + var testList = [{ + id: 55, + read: false, + title: 'Futuristic Notice', + }] + noticeController.setNoticesList(testList) + noticeController.updateNoticesList().then(() => { + var newList = noticeController.getNoticesList() + assert.ok(newList[0].id === 55) + assert.ok(newList[1]) + done() + }) + }) + it('should not overwrite any existing fields', function (done) { + var testList = [{ + id: 0, + read: false, + title: 'Futuristic Notice', + }] + noticeController.setNoticesList(testList) + var newList = noticeController.getNoticesList() + assert.equal(newList[0].id, 0) + assert.equal(newList[0].title, 'Futuristic Notice') + assert.equal(newList.length, 1) + done() + }) + }) + + describe('#markNoticeRead', function () { + it('should mark a notice as read', function (done) { + var testList = [{ + id: 0, + read: false, + title: 'Futuristic Notice', + }] + noticeController.setNoticesList(testList) + noticeController.markNoticeRead(testList[0]) + var newList = noticeController.getNoticesList() + assert.ok(newList[0].read) + done() + }) + }) + + describe('#getLatestUnreadNotice', function () { + it('should retrieve the latest unread notice', function (done) { + var testList = [ + {id: 0, read: true, title: 'Past Notice'}, + {id: 1, read: false, title: 'Current Notice'}, + {id: 2, read: false, title: 'Future Notice'}, + ] + noticeController.setNoticesList(testList) + var latestUnread = noticeController.getLatestUnreadNotice() + assert.equal(latestUnread.id, 2) + done() + }) + it('should return undefined if no unread notices exist.', function (done) { + var testList = [ + {id: 0, read: true, title: 'Past Notice'}, + {id: 1, read: true, title: 'Current Notice'}, + {id: 2, read: true, title: 'Future Notice'}, + ] + noticeController.setNoticesList(testList) + var latestUnread = noticeController.getLatestUnreadNotice() + assert.ok(!latestUnread) + done() + }) + }) + }) +}) diff --git a/test/unit/app/controllers/preferences-controller-test.js b/test/unit/app/controllers/preferences-controller-test.js new file mode 100644 index 000000000..3d903bd5b --- /dev/null +++ b/test/unit/app/controllers/preferences-controller-test.js @@ -0,0 +1,48 @@ +const assert = require('assert') +const PreferencesController = require('../../../../app/scripts/controllers/preferences') + +describe('preferences controller', function () { + let preferencesController + + before(() => { + preferencesController = new PreferencesController() + }) + + describe('addToken', function () { + it('should add that token to its state', async function () { + const address = '0xabcdef1234567' + const symbol = 'ABBR' + const decimals = 5 + + await preferencesController.addToken(address, symbol, decimals) + + const tokens = preferencesController.getTokens() + assert.equal(tokens.length, 1, 'one token added') + + const added = tokens[0] + assert.equal(added.address, address, 'set address correctly') + assert.equal(added.symbol, symbol, 'set symbol correctly') + assert.equal(added.decimals, decimals, 'set decimals correctly') + }) + + it('should allow updating a token value', async function () { + const address = '0xabcdef1234567' + const symbol = 'ABBR' + const decimals = 5 + + await preferencesController.addToken(address, symbol, decimals) + + const newDecimals = 6 + await preferencesController.addToken(address, symbol, newDecimals) + + const tokens = preferencesController.getTokens() + assert.equal(tokens.length, 1, 'one token added') + + const added = tokens[0] + assert.equal(added.address, address, 'set address correctly') + assert.equal(added.symbol, symbol, 'set symbol correctly') + assert.equal(added.decimals, newDecimals, 'updated decimals correctly') + }) + }) +}) + diff --git a/test/unit/app/controllers/token-rates-controller.js b/test/unit/app/controllers/token-rates-controller.js new file mode 100644 index 000000000..28e583d8d --- /dev/null +++ b/test/unit/app/controllers/token-rates-controller.js @@ -0,0 +1,29 @@ +const assert = require('assert') +const sinon = require('sinon') +const TokenRatesController = require('../../../../app/scripts/controllers/token-rates') +const ObservableStore = require('obs-store') + +describe('TokenRatesController', () => { + it('should listen for preferences store updates', () => { + const preferences = new ObservableStore({ tokens: [] }) + const controller = new TokenRatesController({ preferences }) + preferences.putState({ tokens: ['foo'] }) + assert.deepEqual(controller._tokens, ['foo']) + }) + + it('should poll on correct interval', async () => { + const stub = sinon.stub(global, 'setInterval') + new TokenRatesController({ interval: 1337 }) // eslint-disable-line no-new + assert.strictEqual(stub.getCall(0).args[1], 1337) + stub.restore() + }) + + it('should fetch each token rate based on address', async () => { + const controller = new TokenRatesController() + controller.isActive = true + controller.fetchExchangeRate = address => address + controller.tokens = [{ address: 'foo' }, { address: 'bar' }] + await controller.updateExchangeRates() + assert.deepEqual(controller.store.getState().contractExchangeRates, { foo: 'foo', bar: 'bar' }) + }) +}) diff --git a/test/unit/app/controllers/transactions/nonce-tracker-test.js b/test/unit/app/controllers/transactions/nonce-tracker-test.js new file mode 100644 index 000000000..fc852458c --- /dev/null +++ b/test/unit/app/controllers/transactions/nonce-tracker-test.js @@ -0,0 +1,239 @@ +const assert = require('assert') +const NonceTracker = require('../../../../../app/scripts/controllers/transactions/nonce-tracker') +const MockTxGen = require('../../../../lib/mock-tx-gen') +let providerResultStub = {} + +describe('Nonce Tracker', function () { + let nonceTracker, provider + let getPendingTransactions, pendingTxs + let getConfirmedTransactions, confirmedTxs + + describe('#getNonceLock', function () { + + describe('with 3 confirmed and 1 pending', function () { + beforeEach(function () { + const txGen = new MockTxGen() + confirmedTxs = txGen.generate({ status: 'confirmed' }, { count: 3 }) + pendingTxs = txGen.generate({ status: 'submitted' }, { count: 1 }) + nonceTracker = generateNonceTrackerWith(pendingTxs, confirmedTxs, '0x1') + }) + + it('should return 4', async function () { + this.timeout(15000) + const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926') + assert.equal(nonceLock.nextNonce, '4', `nonce should be 4 got ${nonceLock.nextNonce}`) + await nonceLock.releaseLock() + }) + + it('should use localNonce if network returns a nonce lower then a confirmed tx in state', async function () { + this.timeout(15000) + const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926') + assert.equal(nonceLock.nextNonce, '4', 'nonce should be 4') + await nonceLock.releaseLock() + }) + }) + + describe('sentry issue 476304902', function () { + beforeEach(function () { + const txGen = new MockTxGen() + pendingTxs = txGen.generate({ status: 'submitted' }, { + fromNonce: 3, + count: 29, + }) + nonceTracker = generateNonceTrackerWith(pendingTxs, [], '0x3') + }) + + it('should return 9', async function () { + this.timeout(15000) + const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926') + assert.equal(nonceLock.nextNonce, '32', `nonce should be 32 got ${nonceLock.nextNonce}`) + await nonceLock.releaseLock() + }) + }) + + describe('issue 3670', function () { + beforeEach(function () { + const txGen = new MockTxGen() + pendingTxs = txGen.generate({ status: 'submitted' }, { + fromNonce: 6, + count: 3, + }) + nonceTracker = generateNonceTrackerWith(pendingTxs, [], '0x6') + }) + + it('should return 9', async function () { + this.timeout(15000) + const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926') + assert.equal(nonceLock.nextNonce, '9', `nonce should be 9 got ${nonceLock.nextNonce}`) + await nonceLock.releaseLock() + }) + }) + + describe('with no previous txs', function () { + beforeEach(function () { + nonceTracker = generateNonceTrackerWith([], []) + }) + + it('should return 0', async function () { + this.timeout(15000) + const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926') + assert.equal(nonceLock.nextNonce, '0', `nonce should be 0 returned ${nonceLock.nextNonce}`) + await nonceLock.releaseLock() + }) + }) + + describe('with multiple previous txs with same nonce', function () { + beforeEach(function () { + const txGen = new MockTxGen() + confirmedTxs = txGen.generate({ status: 'confirmed' }, { count: 1 }) + pendingTxs = txGen.generate({ + status: 'submitted', + txParams: { nonce: '0x01' }, + }, { count: 5 }) + + nonceTracker = generateNonceTrackerWith(pendingTxs, confirmedTxs, '0x0') + }) + + it('should return nonce after those', async function () { + this.timeout(15000) + const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926') + assert.equal(nonceLock.nextNonce, '2', `nonce should be 2 got ${nonceLock.nextNonce}`) + await nonceLock.releaseLock() + }) + }) + + describe('when local confirmed count is higher than network nonce', function () { + beforeEach(function () { + const txGen = new MockTxGen() + confirmedTxs = txGen.generate({ status: 'confirmed' }, { count: 3 }) + nonceTracker = generateNonceTrackerWith([], confirmedTxs, '0x1') + }) + + it('should return nonce after those', async function () { + this.timeout(15000) + const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926') + assert.equal(nonceLock.nextNonce, '3', `nonce should be 3 got ${nonceLock.nextNonce}`) + await nonceLock.releaseLock() + }) + }) + + describe('when local pending count is higher than other metrics', function () { + beforeEach(function () { + const txGen = new MockTxGen() + pendingTxs = txGen.generate({ status: 'submitted' }, { count: 2 }) + nonceTracker = generateNonceTrackerWith(pendingTxs, []) + }) + + it('should return nonce after those', async function () { + this.timeout(15000) + const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926') + assert.equal(nonceLock.nextNonce, '2', `nonce should be 2 got ${nonceLock.nextNonce}`) + await nonceLock.releaseLock() + }) + }) + + describe('when provider nonce is higher than other metrics', function () { + beforeEach(function () { + const txGen = new MockTxGen() + pendingTxs = txGen.generate({ status: 'submitted' }, { count: 2 }) + nonceTracker = generateNonceTrackerWith(pendingTxs, [], '0x05') + }) + + it('should return nonce after those', async function () { + this.timeout(15000) + const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926') + assert.equal(nonceLock.nextNonce, '5', `nonce should be 5 got ${nonceLock.nextNonce}`) + await nonceLock.releaseLock() + }) + }) + + describe('when there are some pending nonces below the remote one and some over.', function () { + beforeEach(function () { + const txGen = new MockTxGen() + pendingTxs = txGen.generate({ status: 'submitted' }, { count: 5 }) + nonceTracker = generateNonceTrackerWith(pendingTxs, [], '0x03') + }) + + it('should return nonce after those', async function () { + this.timeout(15000) + const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926') + assert.equal(nonceLock.nextNonce, '5', `nonce should be 5 got ${nonceLock.nextNonce}`) + await nonceLock.releaseLock() + }) + }) + + describe('when there are pending nonces non sequentially over the network nonce.', function () { + beforeEach(function () { + const txGen = new MockTxGen() + txGen.generate({ status: 'submitted' }, { count: 5 }) + // 5 over that number + pendingTxs = txGen.generate({ status: 'submitted' }, { count: 5 }) + nonceTracker = generateNonceTrackerWith(pendingTxs, [], '0x00') + }) + + it('should return nonce after network nonce', async function () { + this.timeout(15000) + const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926') + assert.equal(nonceLock.nextNonce, '0', `nonce should be 0 got ${nonceLock.nextNonce}`) + await nonceLock.releaseLock() + }) + }) + + describe('When all three return different values', function () { + beforeEach(function () { + const txGen = new MockTxGen() + const confirmedTxs = txGen.generate({ status: 'confirmed' }, { count: 10 }) + const pendingTxs = txGen.generate({ + status: 'submitted', + nonce: 100, + }, { count: 1 }) + // 0x32 is 50 in hex: + nonceTracker = generateNonceTrackerWith(pendingTxs, confirmedTxs, '0x32') + }) + + it('should return nonce after network nonce', async function () { + this.timeout(15000) + const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926') + assert.equal(nonceLock.nextNonce, '50', `nonce should be 50 got ${nonceLock.nextNonce}`) + await nonceLock.releaseLock() + }) + }) + + describe('Faq issue 67', function () { + beforeEach(function () { + const txGen = new MockTxGen() + const confirmedTxs = txGen.generate({ status: 'confirmed' }, { count: 64 }) + const pendingTxs = txGen.generate({ + status: 'submitted', + }, { count: 10 }) + // 0x40 is 64 in hex: + nonceTracker = generateNonceTrackerWith(pendingTxs, [], '0x40') + }) + + it('should return nonce after network nonce', async function () { + this.timeout(15000) + const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926') + assert.equal(nonceLock.nextNonce, '74', `nonce should be 74 got ${nonceLock.nextNonce}`) + await nonceLock.releaseLock() + }) + }) + }) +}) + +function generateNonceTrackerWith (pending, confirmed, providerStub = '0x0') { + const getPendingTransactions = () => pending + const getConfirmedTransactions = () => confirmed + providerResultStub.result = providerStub + const provider = { + sendAsync: (_, cb) => { cb(undefined, providerResultStub) }, + _blockTracker: { + getCurrentBlock: () => '0x11b568', + }, + } + return new NonceTracker({ + provider, + getPendingTransactions, + getConfirmedTransactions, + }) +} + diff --git a/test/unit/app/controllers/transactions/pending-tx-test.js b/test/unit/app/controllers/transactions/pending-tx-test.js new file mode 100644 index 000000000..e7705c594 --- /dev/null +++ b/test/unit/app/controllers/transactions/pending-tx-test.js @@ -0,0 +1,402 @@ +const assert = require('assert') +const ethUtil = require('ethereumjs-util') +const EthTx = require('ethereumjs-tx') +const ObservableStore = require('obs-store') +const clone = require('clone') +const { createTestProviderTools } = require('../../../../stub/provider') +const PendingTransactionTracker = require('../../../../../app/scripts/controllers/transactions/pending-tx-tracker') +const MockTxGen = require('../../../../lib/mock-tx-gen') +const sinon = require('sinon') +const noop = () => true +const currentNetworkId = 42 +const otherNetworkId = 36 +const privKey = new Buffer('8718b9618a37d1fc78c436511fc6df3c8258d3250635bba617f33003270ec03e', 'hex') + + +describe('PendingTransactionTracker', function () { + let pendingTxTracker, txMeta, txMetaNoHash, txMetaNoRawTx, providerResultStub, + provider, txMeta3, txList, knownErrors + this.timeout(10000) + beforeEach(function () { + txMeta = { + id: 1, + hash: '0x0593ee121b92e10d63150ad08b4b8f9c7857d1bd160195ee648fb9a0f8d00eeb', + status: 'signed', + txParams: { + from: '0x1678a085c290ebd122dc42cba69373b5953b831d', + nonce: '0x1', + value: '0xfffff', + }, + rawTx: '0xf86c808504a817c800827b0d940c62bb85faa3311a998d3aba8098c1235c564966880de0b6b3a7640000802aa08ff665feb887a25d4099e40e11f0fef93ee9608f404bd3f853dd9e84ed3317a6a02ec9d3d1d6e176d4d2593dd760e74ccac753e6a0ea0d00cc9789d0d7ff1f471d', + } + txMetaNoHash = { + id: 2, + status: 'signed', + txParams: { from: '0x1678a085c290ebd122dc42cba69373b5953b831d'}, + } + txMetaNoRawTx = { + hash: '0x0593ee121b92e10d63150ad08b4b8f9c7857d1bd160195ee648fb9a0f8d00eeb', + status: 'signed', + txParams: { from: '0x1678a085c290ebd122dc42cba69373b5953b831d'}, + } + providerResultStub = {} + provider = createTestProviderTools({ scaffold: providerResultStub }).provider + + pendingTxTracker = new PendingTransactionTracker({ + provider, + nonceTracker: { + getGlobalLock: async () => { + return { releaseLock: () => {} } + } + }, + getPendingTransactions: () => {return []}, + getCompletedTransactions: () => {return []}, + publishTransaction: () => {}, + }) + }) + + describe('_checkPendingTx state management', function () { + let stub + + afterEach(function () { + if (stub) { + stub.restore() + } + }) + + it('should become failed if another tx with the same nonce succeeds', async function () { + + // SETUP + const txGen = new MockTxGen() + + txGen.generate({ + id: '456', + value: '0x01', + hash: '0xbad', + status: 'confirmed', + nonce: '0x01', + }, { count: 1 }) + + const pending = txGen.generate({ + id: '123', + value: '0x02', + hash: '0xfad', + status: 'submitted', + nonce: '0x01', + }, { count: 1 })[0] + + stub = sinon.stub(pendingTxTracker, 'getCompletedTransactions') + .returns(txGen.txs) + + // THE EXPECTATION + const spy = sinon.spy() + pendingTxTracker.on('tx:failed', (txId, err) => { + assert.equal(txId, pending.id, 'should fail the pending tx') + assert.equal(err.name, 'NonceTakenErr', 'should emit a nonce taken error.') + spy(txId, err) + }) + + // THE METHOD + await pendingTxTracker._checkPendingTx(pending) + + // THE ASSERTION + assert.ok(spy.calledWith(pending.id), 'tx failed should be emitted') + }) + }) + + describe('#checkForTxInBlock', function () { + it('should return if no pending transactions', function () { + // throw a type error if it trys to do anything on the block + // thus failing the test + const block = Proxy.revocable({}, {}).revoke() + pendingTxTracker.checkForTxInBlock(block) + }) + it('should emit \'tx:failed\' if the txMeta does not have a hash', function (done) { + const block = Proxy.revocable({}, {}).revoke() + pendingTxTracker.getPendingTransactions = () => [txMetaNoHash] + pendingTxTracker.once('tx:failed', (txId, err) => { + assert(txId, txMetaNoHash.id, 'should pass txId') + done() + }) + pendingTxTracker.checkForTxInBlock(block) + }) + it('should emit \'txConfirmed\' if the tx is in the block', function (done) { + const block = { transactions: [txMeta]} + pendingTxTracker.getPendingTransactions = () => [txMeta] + pendingTxTracker.once('tx:confirmed', (txId) => { + assert(txId, txMeta.id, 'should pass txId') + done() + }) + pendingTxTracker.once('tx:failed', (_, err) => { done(err) }) + pendingTxTracker.checkForTxInBlock(block) + }) + }) + describe('#queryPendingTxs', function () { + it('should call #_checkPendingTxs if their is no oldBlock', function (done) { + let newBlock, oldBlock + newBlock = { number: '0x01' } + pendingTxTracker._checkPendingTxs = done + pendingTxTracker.queryPendingTxs({ oldBlock, newBlock }) + }) + it('should call #_checkPendingTxs if oldBlock and the newBlock have a diff of greater then 1', function (done) { + let newBlock, oldBlock + oldBlock = { number: '0x01' } + newBlock = { number: '0x03' } + pendingTxTracker._checkPendingTxs = done + pendingTxTracker.queryPendingTxs({ oldBlock, newBlock }) + }) + it('should not call #_checkPendingTxs if oldBlock and the newBlock have a diff of 1 or less', function (done) { + let newBlock, oldBlock + oldBlock = { number: '0x1' } + newBlock = { number: '0x2' } + pendingTxTracker._checkPendingTxs = () => { + const err = new Error('should not call #_checkPendingTxs if oldBlock and the newBlock have a diff of 1 or less') + done(err) + } + pendingTxTracker.queryPendingTxs({ oldBlock, newBlock }) + done() + }) + }) + + describe('#_checkPendingTx', function () { + it('should emit \'tx:failed\' if the txMeta does not have a hash', function (done) { + pendingTxTracker.once('tx:failed', (txId, err) => { + assert(txId, txMetaNoHash.id, 'should pass txId') + done() + }) + pendingTxTracker._checkPendingTx(txMetaNoHash) + }) + + it('should should return if query does not return txParams', function () { + providerResultStub.eth_getTransactionByHash = null + pendingTxTracker._checkPendingTx(txMeta) + }) + + it('should emit \'txConfirmed\'', function (done) { + providerResultStub.eth_getTransactionByHash = {blockNumber: '0x01'} + pendingTxTracker.once('tx:confirmed', (txId) => { + assert(txId, txMeta.id, 'should pass txId') + done() + }) + pendingTxTracker.once('tx:failed', (_, err) => { done(err) }) + pendingTxTracker._checkPendingTx(txMeta) + }) + }) + + describe('#_checkPendingTxs', function () { + beforeEach(function () { + const txMeta2 = txMeta3 = txMeta + txMeta2.id = 2 + txMeta3.id = 3 + txList = [txMeta, txMeta2, txMeta3].map((tx) => { + tx.processed = new Promise ((resolve) => { tx.resolve = resolve }) + return tx + }) + }) + + it('should warp all txMeta\'s in #_checkPendingTx', function (done) { + pendingTxTracker.getPendingTransactions = () => txList + pendingTxTracker._checkPendingTx = (tx) => { tx.resolve(tx) } + const list = txList.map + Promise.all(txList.map((tx) => tx.processed)) + .then((txCompletedList) => done()) + .catch(done) + + pendingTxTracker._checkPendingTxs() + }) + }) + + describe('#resubmitPendingTxs', function () { + const blockStub = { number: '0x0' }; + beforeEach(function () { + const txMeta2 = txMeta3 = txMeta + txList = [txMeta, txMeta2, txMeta3].map((tx) => { + tx.processed = new Promise ((resolve) => { tx.resolve = resolve }) + return tx + }) + }) + + it('should return if no pending transactions', function () { + pendingTxTracker.resubmitPendingTxs() + }) + it('should call #_resubmitTx for all pending tx\'s', function (done) { + pendingTxTracker.getPendingTransactions = () => txList + pendingTxTracker._resubmitTx = async (tx) => { tx.resolve(tx) } + Promise.all(txList.map((tx) => tx.processed)) + .then((txCompletedList) => done()) + .catch(done) + pendingTxTracker.resubmitPendingTxs(blockStub) + }) + it('should not emit \'tx:failed\' if the txMeta throws a known txError', function (done) { + knownErrors =[ + // geth + ' Replacement transaction Underpriced ', + ' known transaction', + // parity + 'Gas price too low to replace ', + ' transaction with the sAme hash was already imported', + // other + ' gateway timeout', + ' noncE too low ', + ] + const enoughForAllErrors = txList.concat(txList) + + pendingTxTracker.on('tx:failed', (_, err) => done(err)) + + pendingTxTracker.getPendingTransactions = () => enoughForAllErrors + pendingTxTracker._resubmitTx = async (tx) => { + tx.resolve() + throw new Error(knownErrors.pop()) + } + Promise.all(txList.map((tx) => tx.processed)) + .then((txCompletedList) => done()) + .catch(done) + + pendingTxTracker.resubmitPendingTxs(blockStub) + }) + it('should emit \'tx:warning\' if it encountered a real error', function (done) { + pendingTxTracker.once('tx:warning', (txMeta, err) => { + if (err.message === 'im some real error') { + const matchingTx = txList.find(tx => tx.id === txMeta.id) + matchingTx.resolve() + } else { + done(err) + } + }) + + pendingTxTracker.getPendingTransactions = () => txList + pendingTxTracker._resubmitTx = async (tx) => { throw new TypeError('im some real error') } + Promise.all(txList.map((tx) => tx.processed)) + .then((txCompletedList) => done()) + .catch(done) + + pendingTxTracker.resubmitPendingTxs(blockStub) + }) + }) + describe('#_resubmitTx', function () { + const mockFirstRetryBlockNumber = '0x1' + let txMetaToTestExponentialBackoff + + beforeEach(() => { + pendingTxTracker.getBalance = (address) => { + assert.equal(address, txMeta.txParams.from, 'Should pass the address') + return enoughBalance + } + pendingTxTracker.publishTransaction = async (rawTx) => { + assert.equal(rawTx, txMeta.rawTx, 'Should pass the rawTx') + } + sinon.spy(pendingTxTracker, 'publishTransaction') + + txMetaToTestExponentialBackoff = Object.assign({}, txMeta, { + retryCount: 4, + firstRetryBlockNumber: mockFirstRetryBlockNumber, + }) + }) + + afterEach(() => { + pendingTxTracker.publishTransaction.restore() + }) + + it('should publish the transaction', function (done) { + const enoughBalance = '0x100000' + + // Stubbing out current account state: + // Adding the fake tx: + pendingTxTracker._resubmitTx(txMeta) + .then(() => done()) + .catch((err) => { + assert.ifError(err, 'should not throw an error') + done(err) + }) + + assert.equal(pendingTxTracker.publishTransaction.callCount, 1, 'Should call publish transaction') + }) + + it('should not publish the transaction if the limit of retries has been exceeded', function (done) { + const enoughBalance = '0x100000' + const mockLatestBlockNumber = '0x5' + + pendingTxTracker._resubmitTx(txMetaToTestExponentialBackoff, mockLatestBlockNumber) + .then(() => done()) + .catch((err) => { + assert.ifError(err, 'should not throw an error') + done(err) + }) + + assert.equal(pendingTxTracker.publishTransaction.callCount, 0, 'Should NOT call publish transaction') + }) + + it('should publish the transaction if the number of blocks since last retry exceeds the last set limit', function (done) { + const enoughBalance = '0x100000' + const mockLatestBlockNumber = '0x11' + + pendingTxTracker._resubmitTx(txMetaToTestExponentialBackoff, mockLatestBlockNumber) + .then(() => done()) + .catch((err) => { + assert.ifError(err, 'should not throw an error') + done(err) + }) + + assert.equal(pendingTxTracker.publishTransaction.callCount, 1, 'Should call publish transaction') + }) + }) + + describe('#_checkIfNonceIsTaken', function () { + beforeEach ( function () { + let confirmedTxList = [{ + id: 1, + hash: '0x0593ee121b92e10d63150ad08b4b8f9c7857d1bd160195ee648fb9a0f8d00eeb', + status: 'confirmed', + txParams: { + from: '0x1678a085c290ebd122dc42cba69373b5953b831d', + nonce: '0x1', + value: '0xfffff', + }, + rawTx: '0xf86c808504a817c800827b0d940c62bb85faa3311a998d3aba8098c1235c564966880de0b6b3a7640000802aa08ff665feb887a25d4099e40e11f0fef93ee9608f404bd3f853dd9e84ed3317a6a02ec9d3d1d6e176d4d2593dd760e74ccac753e6a0ea0d00cc9789d0d7ff1f471d', + }, { + id: 2, + hash: '0x0593ee121b92e10d63150ad08b4b8f9c7857d1bd160195ee648fb9a0f8d00eeb', + status: 'confirmed', + txParams: { + from: '0x1678a085c290ebd122dc42cba69373b5953b831d', + nonce: '0x2', + value: '0xfffff', + }, + rawTx: '0xf86c808504a817c800827b0d940c62bb85faa3311a998d3aba8098c1235c564966880de0b6b3a7640000802aa08ff665feb887a25d4099e40e11f0fef93ee9608f404bd3f853dd9e84ed3317a6a02ec9d3d1d6e176d4d2593dd760e74ccac753e6a0ea0d00cc9789d0d7ff1f471d', + }] + pendingTxTracker.getCompletedTransactions = (address) => { + if (!address) throw new Error('unless behavior has changed #_checkIfNonceIsTaken needs a filtered list of transactions to see if the nonce is taken') + return confirmedTxList + } + }) + + it('should return false if nonce has not been taken', function (done) { + pendingTxTracker._checkIfNonceIsTaken({ + txParams: { + from: '0x1678a085c290ebd122dc42cba69373b5953b831d', + nonce: '0x3', + value: '0xfffff', + }, + }) + .then((taken) => { + assert.ok(!taken) + done() + }) + .catch(done) + }) + + it('should return true if nonce has been taken', function (done) { + pendingTxTracker._checkIfNonceIsTaken({ + txParams: { + from: '0x1678a085c290ebd122dc42cba69373b5953b831d', + nonce: '0x2', + value: '0xfffff', + }, + }).then((taken) => { + assert.ok(taken) + done() + }) + .catch(done) + }) + }) +}) diff --git a/test/unit/app/controllers/transactions/tx-controller-test.js b/test/unit/app/controllers/transactions/tx-controller-test.js new file mode 100644 index 000000000..f1d67f64e --- /dev/null +++ b/test/unit/app/controllers/transactions/tx-controller-test.js @@ -0,0 +1,463 @@ +const assert = require('assert') +const ethUtil = require('ethereumjs-util') +const EthTx = require('ethereumjs-tx') +const EthjsQuery = require('ethjs-query') +const ObservableStore = require('obs-store') +const sinon = require('sinon') +const TransactionController = require('../../../../../app/scripts/controllers/transactions') +const TxGasUtils = require('../../../../../app/scripts/controllers/transactions/tx-gas-utils') +const { createTestProviderTools, getTestAccounts } = require('../../../../stub/provider') + +const noop = () => true +const currentNetworkId = 42 +const otherNetworkId = 36 + + +describe('Transaction Controller', function () { + let txController, provider, providerResultStub, query, fromAccount + + beforeEach(function () { + providerResultStub = { + // 1 gwei + eth_gasPrice: '0x0de0b6b3a7640000', + // by default, all accounts are external accounts (not contracts) + eth_getCode: '0x', + } + provider = createTestProviderTools({ scaffold: providerResultStub }).provider + query = new EthjsQuery(provider) + fromAccount = getTestAccounts()[0] + + txController = new TransactionController({ + provider, + networkStore: new ObservableStore(currentNetworkId), + txHistoryLimit: 10, + blockTracker: { getCurrentBlock: noop, on: noop, once: noop }, + signTransaction: (ethTx) => new Promise((resolve) => { + ethTx.sign(fromAccount.key) + resolve() + }), + }) + txController.nonceTracker.getNonceLock = () => Promise.resolve({ nextNonce: 0, releaseLock: noop }) + }) + + describe('#isNonceTaken', function () { + it('should return true', function (done) { + txController.txStateManager._saveTxList([ + { id: 1, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams: {nonce: 0, from: '0x8ACCE2391C0d510a6C5E5D8f819A678F79B7E675'} }, + { id: 2, status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {nonce: 0, from: '0x8ACCE2391C0d510a6C5E5D8f819A678F79B7E675'} }, + { id: 3, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams: {nonce: 0, from: '0x8ACCE2391C0d510a6C5E5D8f819A678F79B7E675'} }, + ]) + txController.isNonceTaken({txParams: {nonce:0, from:'0x8ACCE2391C0d510a6C5E5D8f819A678F79B7E675'}}) + .then((isNonceTaken) => { + assert(isNonceTaken) + done() + }).catch(done) + + }) + it('should return false', function (done) { + txController.txStateManager._saveTxList([ + { id: 1, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams: {nonce: 0, from: '0x8ACCE2391C0d510a6C5E5D8f819A678F79B7E675'} }, + { id: 2, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams: {nonce: 0, from: '0x8ACCE2391C0d510a6C5E5D8f819A678F79B7E675'} }, + { id: 3, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams: {nonce: 0, from: '0x8ACCE2391C0d510a6C5E5D8f819A678F79B7E675'} }, + ]) + + txController.isNonceTaken({txParams: {nonce:0, from:'0x8ACCE2391C0d510a6C5E5D8f819A678F79B7E675'}}) + .then((isNonceTaken) => { + assert(!isNonceTaken) + done() + }).catch(done) + + }) + }) + + describe('#getState', function () { + it('should return a state object with the right keys and datat types', function () { + const exposedState = txController.getState() + assert('unapprovedTxs' in exposedState, 'state should have the key unapprovedTxs') + assert('selectedAddressTxList' in exposedState, 'state should have the key selectedAddressTxList') + assert(typeof exposedState.unapprovedTxs === 'object', 'should be an object') + assert(Array.isArray(exposedState.selectedAddressTxList), 'should be an array') + }) + }) + + describe('#getUnapprovedTxCount', function () { + it('should return the number of unapproved txs', function () { + txController.txStateManager._saveTxList([ + { id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }, + { id: 2, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }, + { id: 3, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }, + ]) + const unapprovedTxCount = txController.getUnapprovedTxCount() + assert.equal(unapprovedTxCount, 3, 'should be 3') + }) + }) + + describe('#getPendingTxCount', function () { + it('should return the number of pending txs', function () { + txController.txStateManager._saveTxList([ + { id: 1, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams: {} }, + { id: 2, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams: {} }, + { id: 3, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams: {} }, + ]) + const pendingTxCount = txController.getPendingTxCount() + assert.equal(pendingTxCount, 3, 'should be 3') + }) + }) + + describe('#getConfirmedTransactions', function () { + let address + beforeEach(function () { + address = '0xc684832530fcbddae4b4230a47e991ddcec2831d' + const txParams = { + 'from': address, + 'to': '0xc684832530fcbddae4b4230a47e991ddcec2831d', + } + txController.txStateManager._saveTxList([ + {id: 0, status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams}, + {id: 1, status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams}, + {id: 2, status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams}, + {id: 3, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams}, + {id: 4, status: 'rejected', metamaskNetworkId: currentNetworkId, txParams}, + {id: 5, status: 'approved', metamaskNetworkId: currentNetworkId, txParams}, + {id: 6, status: 'signed', metamaskNetworkId: currentNetworkId, txParams}, + {id: 7, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams}, + {id: 8, status: 'failed', metamaskNetworkId: currentNetworkId, txParams}, + ]) + }) + + it('should return the number of confirmed txs', function () { + assert.equal(txController.nonceTracker.getConfirmedTransactions(address).length, 3) + }) + }) + + + describe('#newUnapprovedTransaction', function () { + let stub, txMeta, txParams + beforeEach(function () { + txParams = { + 'from': '0xc684832530fcbddae4b4230a47e991ddcec2831d', + 'to': '0xc684832530fcbddae4b4230a47e991ddcec2831d', + } + txMeta = { + status: 'unapproved', + id: 1, + metamaskNetworkId: currentNetworkId, + txParams, + history: [], + } + txController.txStateManager._saveTxList([txMeta]) + stub = sinon.stub(txController, 'addUnapprovedTransaction').callsFake(() => { + txController.emit('newUnapprovedTx', txMeta) + return Promise.resolve(txController.txStateManager.addTx(txMeta)) + }) + + afterEach(function () { + txController.txStateManager._saveTxList([]) + stub.restore() + }) + }) + + it('should resolve when finished and status is submitted and resolve with the hash', function (done) { + txController.once('newUnapprovedTx', (txMetaFromEmit) => { + setTimeout(() => { + txController.setTxHash(txMetaFromEmit.id, '0x0') + txController.txStateManager.setTxStatusSubmitted(txMetaFromEmit.id) + }, 10) + }) + + txController.newUnapprovedTransaction(txParams) + .then((hash) => { + assert(hash, 'newUnapprovedTransaction needs to return the hash') + done() + }) + .catch(done) + }) + + it('should reject when finished and status is rejected', function (done) { + txController.once('newUnapprovedTx', (txMetaFromEmit) => { + setTimeout(() => { + txController.txStateManager.setTxStatusRejected(txMetaFromEmit.id) + }, 10) + }) + + txController.newUnapprovedTransaction(txParams) + .catch((err) => { + if (err.message === 'MetaMask Tx Signature: User denied transaction signature.') done() + else done(err) + }) + }) + }) + + describe('#addUnapprovedTransaction', function () { + + it('should add an unapproved transaction and return a valid txMeta', function (done) { + txController.addUnapprovedTransaction({ from: '0x1678a085c290ebd122dc42cba69373b5953b831d' }) + .then((txMeta) => { + assert(('id' in txMeta), 'should have a id') + assert(('time' in txMeta), 'should have a time stamp') + assert(('metamaskNetworkId' in txMeta), 'should have a metamaskNetworkId') + assert(('txParams' in txMeta), 'should have a txParams') + assert(('history' in txMeta), 'should have a history') + + const memTxMeta = txController.txStateManager.getTx(txMeta.id) + assert.deepEqual(txMeta, memTxMeta, `txMeta should be stored in txController after adding it\n expected: ${txMeta} \n got: ${memTxMeta}`) + done() + }).catch(done) + }) + + it('should emit newUnapprovedTx event and pass txMeta as the first argument', function (done) { + providerResultStub.eth_gasPrice = '4a817c800' + txController.once('newUnapprovedTx', (txMetaFromEmit) => { + assert(txMetaFromEmit, 'txMeta is falsey') + done() + }) + txController.addUnapprovedTransaction({ from: '0x1678a085c290ebd122dc42cba69373b5953b831d' }) + .catch(done) + }) + + }) + + describe('#addTxGasDefaults', function () { + it('should add the tx defaults if their are none', function (done) { + const txMeta = { + 'txParams': { + 'from': '0xc684832530fcbddae4b4230a47e991ddcec2831d', + 'to': '0xc684832530fcbddae4b4230a47e991ddcec2831d', + }, + } + providerResultStub.eth_gasPrice = '4a817c800' + providerResultStub.eth_getBlockByNumber = { gasLimit: '47b784' } + providerResultStub.eth_estimateGas = '5209' + txController.addTxGasDefaults(txMeta) + .then((txMetaWithDefaults) => { + assert(txMetaWithDefaults.txParams.value, '0x0', 'should have added 0x0 as the value') + assert(txMetaWithDefaults.txParams.gasPrice, 'should have added the gas price') + assert(txMetaWithDefaults.txParams.gas, 'should have added the gas field') + done() + }) + .catch(done) + }) + }) + + describe('#addTx', function () { + it('should emit updates', function (done) { + const txMeta = { + id: '1', + status: 'unapproved', + metamaskNetworkId: currentNetworkId, + txParams: {}, + } + + const eventNames = ['update:badge', '1:unapproved'] + const listeners = [] + eventNames.forEach((eventName) => { + listeners.push(new Promise((resolve) => { + txController.once(eventName, (arg) => { + resolve(arg) + }) + })) + }) + Promise.all(listeners) + .then((returnValues) => { + assert.deepEqual(returnValues.pop(), txMeta, 'last event 1:unapproved should return txMeta') + done() + }) + .catch(done) + txController.addTx(txMeta) + }) + }) + + describe('#approveTransaction', function () { + let txMeta, originalValue + + beforeEach(function () { + originalValue = '0x01' + txMeta = { + id: '1', + status: 'unapproved', + metamaskNetworkId: currentNetworkId, + txParams: { + nonce: originalValue, + gas: originalValue, + gasPrice: originalValue, + }, + } + }) + + + it('does not overwrite set values', function (done) { + this.timeout(15000) + const wrongValue = '0x05' + + txController.addTx(txMeta) + providerResultStub.eth_gasPrice = wrongValue + providerResultStub.eth_estimateGas = '0x5209' + + const signStub = sinon.stub(txController, 'signTransaction').callsFake(() => Promise.resolve()) + + const pubStub = sinon.stub(txController, 'publishTransaction').callsFake(() => { + txController.setTxHash('1', originalValue) + txController.txStateManager.setTxStatusSubmitted('1') + }) + + txController.approveTransaction(txMeta.id).then(() => { + const result = txController.txStateManager.getTx(txMeta.id) + const params = result.txParams + + assert.equal(params.gas, originalValue, 'gas unmodified') + assert.equal(params.gasPrice, originalValue, 'gas price unmodified') + assert.equal(result.hash, originalValue, `hash was set \n got: ${result.hash} \n expected: ${originalValue}`) + signStub.restore() + pubStub.restore() + done() + }).catch(done) + }) + }) + + describe('#sign replay-protected tx', function () { + it('prepares a tx with the chainId set', function (done) { + txController.addTx({ id: '1', status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }, noop) + txController.signTransaction('1').then((rawTx) => { + const ethTx = new EthTx(ethUtil.toBuffer(rawTx)) + assert.equal(ethTx.getChainId(), currentNetworkId) + done() + }).catch(done) + }) + }) + + describe('#updateAndApproveTransaction', function () { + let txMeta + beforeEach(() => { + txMeta = { + id: 1, + status: 'unapproved', + txParams: { + from: fromAccount.address, + to: '0x1678a085c290ebd122dc42cba69373b5953b831d', + gasPrice: '0x77359400', + gas: '0x7b0d', + nonce: '0x4b', + }, + metamaskNetworkId: currentNetworkId, + } + }) + it('should update and approve transactions', async () => { + txController.txStateManager.addTx(txMeta) + const approvalPromise = txController.updateAndApproveTransaction(txMeta) + const tx = txController.txStateManager.getTx(1) + assert.equal(tx.status, 'approved') + await approvalPromise + }) + }) + + describe('#getChainId', function () { + it('returns 0 when the chainId is NaN', function () { + txController.networkStore = new ObservableStore(NaN) + assert.equal(txController.getChainId(), 0) + }) + }) + + describe('#cancelTransaction', function () { + beforeEach(function () { + txController.txStateManager._saveTxList([ + { id: 0, status: 'unapproved', txParams: {}, metamaskNetworkId: currentNetworkId, history: [{}] }, + { id: 1, status: 'rejected', txParams: {}, metamaskNetworkId: currentNetworkId, history: [{}] }, + { id: 2, status: 'approved', txParams: {}, metamaskNetworkId: currentNetworkId, history: [{}] }, + { id: 3, status: 'signed', txParams: {}, metamaskNetworkId: currentNetworkId, history: [{}] }, + { id: 4, status: 'submitted', txParams: {}, metamaskNetworkId: currentNetworkId, history: [{}] }, + { id: 5, status: 'confirmed', txParams: {}, metamaskNetworkId: currentNetworkId, history: [{}] }, + { id: 6, status: 'failed', txParams: {}, metamaskNetworkId: currentNetworkId, history: [{}] }, + ]) + }) + + it('should set the transaction to rejected from unapproved', async function () { + await txController.cancelTransaction(0) + assert.equal(txController.txStateManager.getTx(0).status, 'rejected') + }) + + }) + + describe('#publishTransaction', function () { + let hash, txMeta + beforeEach(function () { + hash = '0x2a5523c6fa98b47b7d9b6c8320179785150b42a16bcff36b398c5062b65657e8' + txMeta = { + id: 1, + status: 'unapproved', + txParams: {}, + metamaskNetworkId: currentNetworkId, + } + providerResultStub.eth_sendRawTransaction = hash + }) + + it('should publish a tx, updates the rawTx when provided a one', async function () { + txController.txStateManager.addTx(txMeta) + await txController.publishTransaction(txMeta.id) + const publishedTx = txController.txStateManager.getTx(1) + assert.equal(publishedTx.hash, hash) + assert.equal(publishedTx.status, 'submitted') + }) + }) + + describe('#retryTransaction', function () { + it('should create a new txMeta with the same txParams as the original one', function (done) { + let txParams = { + nonce: '0x00', + from: '0xB09d8505E1F4EF1CeA089D47094f5DD3464083d4', + to: '0xB09d8505E1F4EF1CeA089D47094f5DD3464083d4', + data: '0x0', + } + txController.txStateManager._saveTxList([ + { id: 1, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams }, + ]) + txController.retryTransaction(1) + .then((txMeta) => { + assert.equal(txMeta.txParams.nonce, txParams.nonce, 'nonce should be the same') + assert.equal(txMeta.txParams.from, txParams.from, 'from should be the same') + assert.equal(txMeta.txParams.to, txParams.to, 'to should be the same') + assert.equal(txMeta.txParams.data, txParams.data, 'data should be the same') + assert.ok(('lastGasPrice' in txMeta), 'should have the key `lastGasPrice`') + assert.equal(txController.txStateManager.getTxList().length, 2) + done() + }).catch(done) + }) + }) + + describe('#_markNonceDuplicatesDropped', function () { + it('should mark all nonce duplicates as dropped without marking the confirmed transaction as dropped', function () { + txController.txStateManager._saveTxList([ + { id: 1, status: 'confirmed', metamaskNetworkId: currentNetworkId, history: [{}], txParams: { nonce: '0x01' } }, + { id: 2, status: 'submitted', metamaskNetworkId: currentNetworkId, history: [{}], txParams: { nonce: '0x01' } }, + { id: 3, status: 'submitted', metamaskNetworkId: currentNetworkId, history: [{}], txParams: { nonce: '0x01' } }, + { id: 4, status: 'submitted', metamaskNetworkId: currentNetworkId, history: [{}], txParams: { nonce: '0x01' } }, + { id: 5, status: 'submitted', metamaskNetworkId: currentNetworkId, history: [{}], txParams: { nonce: '0x01' } }, + { id: 6, status: 'submitted', metamaskNetworkId: currentNetworkId, history: [{}], txParams: { nonce: '0x01' } }, + { id: 7, status: 'submitted', metamaskNetworkId: currentNetworkId, history: [{}], txParams: { nonce: '0x01' } }, + ]) + txController._markNonceDuplicatesDropped(1) + const confirmedTx = txController.txStateManager.getTx(1) + const droppedTxs = txController.txStateManager.getFilteredTxList({ nonce: '0x01', status: 'dropped' }) + assert.equal(confirmedTx.status, 'confirmed', 'the confirmedTx should remain confirmed') + assert.equal(droppedTxs.length, 6, 'their should be 6 dropped txs') + + }) + }) + + describe('#getPendingTransactions', function () { + beforeEach(function () { + txController.txStateManager._saveTxList([ + { id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }, + { id: 2, status: 'rejected', metamaskNetworkId: currentNetworkId, txParams: {} }, + { id: 3, status: 'approved', metamaskNetworkId: currentNetworkId, txParams: {} }, + { id: 4, status: 'signed', metamaskNetworkId: currentNetworkId, txParams: {} }, + { id: 5, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams: {} }, + { id: 6, status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} }, + { id: 7, status: 'failed', metamaskNetworkId: currentNetworkId, txParams: {} }, + ]) + }) + it('should show only submitted transactions as pending transasction', function () { + assert(txController.pendingTxTracker.getPendingTransactions().length, 1) + assert(txController.pendingTxTracker.getPendingTransactions()[0].status, 'submitted') + }) + }) +}) diff --git a/test/unit/app/controllers/transactions/tx-gas-util-test.js b/test/unit/app/controllers/transactions/tx-gas-util-test.js new file mode 100644 index 000000000..d1ee86033 --- /dev/null +++ b/test/unit/app/controllers/transactions/tx-gas-util-test.js @@ -0,0 +1,77 @@ +const assert = require('assert') +const Transaction = require('ethereumjs-tx') +const BN = require('bn.js') + + +const { hexToBn, bnToHex } = require('../../../../../app/scripts/lib/util') +const TxUtils = require('../../../../../app/scripts/controllers/transactions/tx-gas-utils') + + +describe('txUtils', function () { + let txUtils + + before(function () { + txUtils = new TxUtils(new Proxy({}, { + get: (obj, name) => { + return () => {} + }, + })) + }) + + describe('chain Id', function () { + it('prepares a transaction with the provided chainId', function () { + const txParams = { + to: '0x70ad465e0bab6504002ad58c744ed89c7da38524', + from: '0x69ad465e0bab6504002ad58c744ed89c7da38525', + value: '0x0', + gas: '0x7b0c', + gasPrice: '0x199c82cc00', + data: '0x', + nonce: '0x3', + chainId: 42, + } + const ethTx = new Transaction(txParams) + assert.equal(ethTx.getChainId(), 42, 'chainId is set from tx params') + }) + }) + + describe('addGasBuffer', function () { + it('multiplies by 1.5, when within block gas limit', function () { + // naive estimatedGas: 0x16e360 (1.5 mil) + const inputHex = '0x16e360' + // dummy gas limit: 0x3d4c52 (4 mil) + const blockGasLimitHex = '0x3d4c52' + const output = txUtils.addGasBuffer(inputHex, blockGasLimitHex) + const inputBn = hexToBn(inputHex) + const outputBn = hexToBn(output) + const expectedBn = inputBn.muln(1.5) + assert(outputBn.eq(expectedBn), 'returns 1.5 the input value') + }) + + it('uses original estimatedGas, when above block gas limit', function () { + // naive estimatedGas: 0x16e360 (1.5 mil) + const inputHex = '0x16e360' + // dummy gas limit: 0x0f4240 (1 mil) + const blockGasLimitHex = '0x0f4240' + const output = txUtils.addGasBuffer(inputHex, blockGasLimitHex) + // const inputBn = hexToBn(inputHex) + const outputBn = hexToBn(output) + const expectedBn = hexToBn(inputHex) + assert(outputBn.eq(expectedBn), 'returns the original estimatedGas value') + }) + + it('buffers up to recommend gas limit recommended ceiling', function () { + // naive estimatedGas: 0x16e360 (1.5 mil) + const inputHex = '0x16e360' + // dummy gas limit: 0x1e8480 (2 mil) + const blockGasLimitHex = '0x1e8480' + const blockGasLimitBn = hexToBn(blockGasLimitHex) + const ceilGasLimitBn = blockGasLimitBn.muln(0.9) + const output = txUtils.addGasBuffer(inputHex, blockGasLimitHex) + // const inputBn = hexToBn(inputHex) + // const outputBn = hexToBn(output) + const expectedHex = bnToHex(ceilGasLimitBn) + assert.equal(output, expectedHex, 'returns the gas limit recommended ceiling value') + }) + }) +}) diff --git a/test/unit/app/controllers/transactions/tx-helper-test.js b/test/unit/app/controllers/transactions/tx-helper-test.js new file mode 100644 index 000000000..ce54ef483 --- /dev/null +++ b/test/unit/app/controllers/transactions/tx-helper-test.js @@ -0,0 +1,17 @@ +const assert = require('assert') +const txHelper = require('../../../../../ui/lib/tx-helper') + +describe('txHelper', function () { + it('always shows the oldest tx first', function () { + const metamaskNetworkId = 1 + const txs = { + a: { metamaskNetworkId, time: 3 }, + b: { metamaskNetworkId, time: 1 }, + c: { metamaskNetworkId, time: 2 }, + } + + const sorted = txHelper(txs, null, null, metamaskNetworkId) + assert.equal(sorted[0].time, 1, 'oldest tx first') + assert.equal(sorted[2].time, 3, 'newest tx last') + }) +}) diff --git a/test/unit/app/controllers/transactions/tx-state-history-helper-test.js b/test/unit/app/controllers/transactions/tx-state-history-helper-test.js new file mode 100644 index 000000000..f4c3a6be1 --- /dev/null +++ b/test/unit/app/controllers/transactions/tx-state-history-helper-test.js @@ -0,0 +1,129 @@ +const assert = require('assert') +const txStateHistoryHelper = require('../../../../../app/scripts/controllers/transactions/lib/tx-state-history-helper') +const testVault = require('../../../../data/v17-long-history.json') + +describe ('Transaction state history helper', function () { + + describe('#snapshotFromTxMeta', function () { + it('should clone deep', function () { + const input = { + foo: { + bar: { + bam: 'baz' + } + } + } + const output = txStateHistoryHelper.snapshotFromTxMeta(input) + assert('foo' in output, 'has a foo key') + assert('bar' in output.foo, 'has a bar key') + assert('bam' in output.foo.bar, 'has a bar key') + assert.equal(output.foo.bar.bam, 'baz', 'has a baz value') + }) + + it('should remove the history key', function () { + const input = { foo: 'bar', history: 'remembered' } + const output = txStateHistoryHelper.snapshotFromTxMeta(input) + assert(typeof output.history, 'undefined', 'should remove history') + }) + }) + + describe('#migrateFromSnapshotsToDiffs', function () { + it('migrates history to diffs and can recover original values', function () { + testVault.data.TransactionController.transactions.forEach((tx, index) => { + const newHistory = txStateHistoryHelper.migrateFromSnapshotsToDiffs(tx.history) + newHistory.forEach((newEntry, index) => { + if (index === 0) { + assert.equal(Array.isArray(newEntry), false, 'initial history item IS NOT a json patch obj') + } else { + assert.equal(Array.isArray(newEntry), true, 'non-initial history entry IS a json patch obj') + } + const oldEntry = tx.history[index] + const historySubset = newHistory.slice(0, index + 1) + const reconstructedValue = txStateHistoryHelper.replayHistory(historySubset) + assert.deepEqual(oldEntry, reconstructedValue, 'was able to reconstruct old entry from diffs') + }) + }) + }) + }) + + describe('#replayHistory', function () { + it('replaying history does not mutate the original obj', function () { + const initialState = { test: true, message: 'hello', value: 1 } + const diff1 = [{ + "op": "replace", + "path": "/message", + "value": "haay", + }] + const diff2 = [{ + "op": "replace", + "path": "/value", + "value": 2, + }] + const history = [initialState, diff1, diff2] + + const beforeStateSnapshot = JSON.stringify(initialState) + const latestState = txStateHistoryHelper.replayHistory(history) + const afterStateSnapshot = JSON.stringify(initialState) + + assert.notEqual(initialState, latestState, 'initial state is not the same obj as the latest state') + assert.equal(beforeStateSnapshot, afterStateSnapshot, 'initial state is not modified during run') + }) + }) + + describe('#generateHistoryEntry', function () { + + function generateHistoryEntryTest(note) { + + const prevState = { + someValue: 'value 1', + foo: { + bar: { + bam: 'baz' + } + } + } + + const nextState = { + newPropRoot: 'new property - root', + someValue: 'value 2', + foo: { + newPropFirstLevel: 'new property - first level', + bar: { + bam: 'baz' + } + } + } + + const before = new Date().getTime() + const result = txStateHistoryHelper.generateHistoryEntry(prevState, nextState, note) + const after = new Date().getTime() + + assert.ok(Array.isArray(result)) + assert.equal(result.length, 3) + + const expectedEntry1 = { op: 'add', path: '/foo/newPropFirstLevel', value: 'new property - first level' } + assert.equal(result[0].op, expectedEntry1.op) + assert.equal(result[0].path, expectedEntry1.path) + assert.equal(result[0].value, expectedEntry1.value) + assert.equal(result[0].value, expectedEntry1.value) + if (note) + assert.equal(result[0].note, note) + + assert.ok(result[0].timestamp >= before && result[0].timestamp <= after) + + const expectedEntry2 = { op: 'replace', path: '/someValue', value: 'value 2' } + assert.deepEqual(result[1], expectedEntry2) + + const expectedEntry3 = { op: 'add', path: '/newPropRoot', value: 'new property - root' } + assert.deepEqual(result[2], expectedEntry3) + } + + it('should generate history entries', function () { + generateHistoryEntryTest() + }) + + it('should add note to first entry', function () { + generateHistoryEntryTest('custom note') + }) + }) +}) \ No newline at end of file diff --git a/test/unit/app/controllers/transactions/tx-state-manager-test.js b/test/unit/app/controllers/transactions/tx-state-manager-test.js new file mode 100644 index 000000000..20bc08b94 --- /dev/null +++ b/test/unit/app/controllers/transactions/tx-state-manager-test.js @@ -0,0 +1,291 @@ +const assert = require('assert') +const clone = require('clone') +const ObservableStore = require('obs-store') +const TxStateManager = require('../../../../../app/scripts/controllers/transactions/tx-state-manager') +const txStateHistoryHelper = require('../../../../../app/scripts/controllers/transactions/lib/tx-state-history-helper') +const noop = () => true + +describe('TransactionStateManager', function () { + let txStateManager + const currentNetworkId = 42 + const otherNetworkId = 2 + + beforeEach(function () { + txStateManager = new TxStateManager({ + initState: { + transactions: [], + }, + txHistoryLimit: 10, + getNetwork: () => currentNetworkId + }) + }) + + describe('#setTxStatusSigned', function () { + it('sets the tx status to signed', function () { + let tx = { id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} } + txStateManager.addTx(tx, noop) + txStateManager.setTxStatusSigned(1) + let result = txStateManager.getTxList() + assert.ok(Array.isArray(result)) + assert.equal(result.length, 1) + assert.equal(result[0].status, 'signed') + }) + + it('should emit a signed event to signal the exciton of callback', (done) => { + let tx = { id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} } + const noop = function () { + assert(true, 'event listener has been triggered and noop executed') + done() + } + txStateManager.addTx(tx) + txStateManager.on('1:signed', noop) + txStateManager.setTxStatusSigned(1) + + }) + }) + + describe('#setTxStatusRejected', function () { + it('sets the tx status to rejected', function () { + let tx = { id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} } + txStateManager.addTx(tx) + txStateManager.setTxStatusRejected(1) + let result = txStateManager.getTxList() + assert.ok(Array.isArray(result)) + assert.equal(result.length, 1) + assert.equal(result[0].status, 'rejected') + }) + + it('should emit a rejected event to signal the exciton of callback', (done) => { + let tx = { id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} } + txStateManager.addTx(tx) + const noop = function (err, txId) { + assert(true, 'event listener has been triggered and noop executed') + done() + } + txStateManager.on('1:rejected', noop) + txStateManager.setTxStatusRejected(1) + }) + }) + + describe('#getFullTxList', function () { + it('when new should return empty array', function () { + let result = txStateManager.getTxList() + assert.ok(Array.isArray(result)) + assert.equal(result.length, 0) + }) + }) + + describe('#getTxList', function () { + it('when new should return empty array', function () { + let result = txStateManager.getTxList() + assert.ok(Array.isArray(result)) + assert.equal(result.length, 0) + }) + }) + + describe('#addTx', function () { + it('adds a tx returned in getTxList', function () { + let tx = { id: 1, status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} } + txStateManager.addTx(tx, noop) + let result = txStateManager.getTxList() + assert.ok(Array.isArray(result)) + assert.equal(result.length, 1) + assert.equal(result[0].id, 1) + }) + + it('does not override txs from other networks', function () { + let tx = { id: 1, status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} } + let tx2 = { id: 2, status: 'confirmed', metamaskNetworkId: otherNetworkId, txParams: {} } + txStateManager.addTx(tx, noop) + txStateManager.addTx(tx2, noop) + let result = txStateManager.getFullTxList() + let result2 = txStateManager.getTxList() + assert.equal(result.length, 2, 'txs were deleted') + assert.equal(result2.length, 1, 'incorrect number of txs on network.') + }) + + it('cuts off early txs beyond a limit', function () { + const limit = txStateManager.txHistoryLimit + for (let i = 0; i < limit + 1; i++) { + const tx = { id: i, time: new Date(), status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} } + txStateManager.addTx(tx, noop) + } + let result = txStateManager.getTxList() + assert.equal(result.length, limit, `limit of ${limit} txs enforced`) + assert.equal(result[0].id, 1, 'early txs truncted') + }) + + it('cuts off early txs beyond a limit whether or not it is confirmed or rejected', function () { + const limit = txStateManager.txHistoryLimit + for (let i = 0; i < limit + 1; i++) { + const tx = { id: i, time: new Date(), status: 'rejected', metamaskNetworkId: currentNetworkId, txParams: {} } + txStateManager.addTx(tx, noop) + } + let result = txStateManager.getTxList() + assert.equal(result.length, limit, `limit of ${limit} txs enforced`) + assert.equal(result[0].id, 1, 'early txs truncted') + }) + + it('cuts off early txs beyond a limit but does not cut unapproved txs', function () { + let unconfirmedTx = { id: 0, time: new Date(), status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} } + txStateManager.addTx(unconfirmedTx, noop) + const limit = txStateManager.txHistoryLimit + for (let i = 1; i < limit + 1; i++) { + const tx = { id: i, time: new Date(), status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} } + txStateManager.addTx(tx, noop) + } + let result = txStateManager.getTxList() + assert.equal(result.length, limit, `limit of ${limit} txs enforced`) + assert.equal(result[0].id, 0, 'first tx should still be there') + assert.equal(result[0].status, 'unapproved', 'first tx should be unapproved') + assert.equal(result[1].id, 2, 'early txs truncted') + }) + }) + + describe('#updateTx', function () { + it('replaces the tx with the same id', function () { + txStateManager.addTx({ id: '1', status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }, noop) + txStateManager.addTx({ id: '2', status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} }, noop) + const txMeta = txStateManager.getTx('1') + txMeta.hash = 'foo' + txStateManager.updateTx(txMeta) + let result = txStateManager.getTx('1') + assert.equal(result.hash, 'foo') + }) + + it('updates gas price and adds history items', function () { + const originalGasPrice = '0x01' + const desiredGasPrice = '0x02' + + const txMeta = { + id: '1', + status: 'unapproved', + metamaskNetworkId: currentNetworkId, + txParams: { + gasPrice: originalGasPrice, + }, + } + + const updatedMeta = clone(txMeta) + + txStateManager.addTx(txMeta) + const updatedTx = txStateManager.getTx('1') + // verify tx was initialized correctly + assert.equal(updatedTx.history.length, 1, 'one history item (initial)') + assert.equal(Array.isArray(updatedTx.history[0]), false, 'first history item is initial state') + assert.deepEqual(updatedTx.history[0], txStateHistoryHelper.snapshotFromTxMeta(updatedTx), 'first history item is initial state') + // modify value and updateTx + updatedTx.txParams.gasPrice = desiredGasPrice + const before = new Date().getTime() + txStateManager.updateTx(updatedTx) + const after = new Date().getTime() + // check updated value + const result = txStateManager.getTx('1') + assert.equal(result.txParams.gasPrice, desiredGasPrice, 'gas price updated') + // validate history was updated + assert.equal(result.history.length, 2, 'two history items (initial + diff)') + assert.equal(result.history[1].length, 1, 'two history state items (initial + diff)') + + const expectedEntry = { op: 'replace', path: '/txParams/gasPrice', value: desiredGasPrice } + assert.deepEqual(result.history[1][0].op, expectedEntry.op, 'two history items (initial + diff) operation') + assert.deepEqual(result.history[1][0].path, expectedEntry.path, 'two history items (initial + diff) path') + assert.deepEqual(result.history[1][0].value, expectedEntry.value, 'two history items (initial + diff) value') + assert.ok(result.history[1][0].timestamp >= before && result.history[1][0].timestamp <= after) + }) + }) + + describe('#getUnapprovedTxList', function () { + it('returns unapproved txs in a hash', function () { + txStateManager.addTx({ id: '1', status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }, noop) + txStateManager.addTx({ id: '2', status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} }, noop) + const result = txStateManager.getUnapprovedTxList() + assert.equal(typeof result, 'object') + assert.equal(result['1'].status, 'unapproved') + assert.equal(result['2'], undefined) + }) + }) + + describe('#getTx', function () { + it('returns a tx with the requested id', function () { + txStateManager.addTx({ id: '1', status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }, noop) + txStateManager.addTx({ id: '2', status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} }, noop) + assert.equal(txStateManager.getTx('1').status, 'unapproved') + assert.equal(txStateManager.getTx('2').status, 'confirmed') + }) + }) + + describe('#getFilteredTxList', function () { + it('returns a tx with the requested data', function () { + const txMetas = [ + { id: 0, status: 'unapproved', txParams: { from: '0xaa', to: '0xbb' }, metamaskNetworkId: currentNetworkId }, + { id: 1, status: 'unapproved', txParams: { from: '0xaa', to: '0xbb' }, metamaskNetworkId: currentNetworkId }, + { id: 2, status: 'unapproved', txParams: { from: '0xaa', to: '0xbb' }, metamaskNetworkId: currentNetworkId }, + { id: 3, status: 'unapproved', txParams: { from: '0xbb', to: '0xaa' }, metamaskNetworkId: currentNetworkId }, + { id: 4, status: 'unapproved', txParams: { from: '0xbb', to: '0xaa' }, metamaskNetworkId: currentNetworkId }, + { id: 5, status: 'confirmed', txParams: { from: '0xaa', to: '0xbb' }, metamaskNetworkId: currentNetworkId }, + { id: 6, status: 'confirmed', txParams: { from: '0xaa', to: '0xbb' }, metamaskNetworkId: currentNetworkId }, + { id: 7, status: 'confirmed', txParams: { from: '0xbb', to: '0xaa' }, metamaskNetworkId: currentNetworkId }, + { id: 8, status: 'confirmed', txParams: { from: '0xbb', to: '0xaa' }, metamaskNetworkId: currentNetworkId }, + { id: 9, status: 'confirmed', txParams: { from: '0xbb', to: '0xaa' }, metamaskNetworkId: currentNetworkId }, + ] + txMetas.forEach((txMeta) => txStateManager.addTx(txMeta, noop)) + let filterParams + + filterParams = { status: 'unapproved', from: '0xaa' } + assert.equal(txStateManager.getFilteredTxList(filterParams).length, 3, `getFilteredTxList - ${JSON.stringify(filterParams)}`) + filterParams = { status: 'unapproved', to: '0xaa' } + assert.equal(txStateManager.getFilteredTxList(filterParams).length, 2, `getFilteredTxList - ${JSON.stringify(filterParams)}`) + filterParams = { status: 'confirmed', from: '0xbb' } + assert.equal(txStateManager.getFilteredTxList(filterParams).length, 3, `getFilteredTxList - ${JSON.stringify(filterParams)}`) + filterParams = { status: 'confirmed' } + assert.equal(txStateManager.getFilteredTxList(filterParams).length, 5, `getFilteredTxList - ${JSON.stringify(filterParams)}`) + filterParams = { from: '0xaa' } + assert.equal(txStateManager.getFilteredTxList(filterParams).length, 5, `getFilteredTxList - ${JSON.stringify(filterParams)}`) + filterParams = { to: '0xaa' } + assert.equal(txStateManager.getFilteredTxList(filterParams).length, 5, `getFilteredTxList - ${JSON.stringify(filterParams)}`) + }) + }) + + describe('#wipeTransactions', function () { + + const specificAddress = '0xaa' + const otherAddress = '0xbb' + + it('should remove only the transactions from a specific address', function () { + + const txMetas = [ + { id: 0, status: 'unapproved', txParams: { from: specificAddress, to: otherAddress }, metamaskNetworkId: currentNetworkId }, + { id: 1, status: 'confirmed', txParams: { from: otherAddress, to: specificAddress }, metamaskNetworkId: currentNetworkId }, + { id: 2, status: 'confirmed', txParams: { from: otherAddress, to: specificAddress }, metamaskNetworkId: currentNetworkId }, + ] + txMetas.forEach((txMeta) => txStateManager.addTx(txMeta, noop)) + + txStateManager.wipeTransactions(specificAddress) + + const transactionsFromCurrentAddress = txStateManager.getTxList().filter((txMeta) => txMeta.txParams.from === specificAddress) + const transactionsFromOtherAddresses = txStateManager.getTxList().filter((txMeta) => txMeta.txParams.from !== specificAddress) + + assert.equal(transactionsFromCurrentAddress.length, 0) + assert.equal(transactionsFromOtherAddresses.length, 2) + }) + + it('should not remove the transactions from other networks', function () { + const txMetas = [ + { id: 0, status: 'unapproved', txParams: { from: specificAddress, to: otherAddress }, metamaskNetworkId: currentNetworkId }, + { id: 1, status: 'confirmed', txParams: { from: specificAddress, to: otherAddress }, metamaskNetworkId: otherNetworkId }, + { id: 2, status: 'confirmed', txParams: { from: specificAddress, to: otherAddress }, metamaskNetworkId: otherNetworkId }, + ] + + txMetas.forEach((txMeta) => txStateManager.addTx(txMeta, noop)) + + txStateManager.wipeTransactions(specificAddress) + + const txsFromCurrentNetworkAndAddress = txStateManager.getTxList().filter((txMeta) => txMeta.txParams.from === specificAddress) + const txFromOtherNetworks = txStateManager.getFullTxList().filter((txMeta) => txMeta.metamaskNetworkId === otherNetworkId) + + assert.equal(txsFromCurrentNetworkAndAddress.length, 0) + assert.equal(txFromOtherNetworks.length, 2) + + }) + }) +}) diff --git a/test/unit/app/controllers/transactions/tx-utils-test.js b/test/unit/app/controllers/transactions/tx-utils-test.js new file mode 100644 index 000000000..115127f85 --- /dev/null +++ b/test/unit/app/controllers/transactions/tx-utils-test.js @@ -0,0 +1,98 @@ +const assert = require('assert') +const txUtils = require('../../../../../app/scripts/controllers/transactions/lib/util') + + +describe('txUtils', function () { + describe('#validateTxParams', function () { + it('does not throw for positive values', function () { + var sample = { + from: '0x1678a085c290ebd122dc42cba69373b5953b831d', + value: '0x01', + } + txUtils.validateTxParams(sample) + }) + + it('returns error for negative values', function () { + var sample = { + from: '0x1678a085c290ebd122dc42cba69373b5953b831d', + value: '-0x01', + } + try { + txUtils.validateTxParams(sample) + } catch (err) { + assert.ok(err, 'error') + } + }) + }) + + describe('#normalizeTxParams', () => { + it('should normalize txParams', () => { + let txParams = { + chainId: '0x1', + from: 'a7df1beDBF813f57096dF77FCd515f0B3900e402', + to: null, + data: '68656c6c6f20776f726c64', + random: 'hello world', + } + + let normalizedTxParams = txUtils.normalizeTxParams(txParams) + + assert(!normalizedTxParams.chainId, 'their should be no chainId') + assert(!normalizedTxParams.to, 'their should be no to address if null') + assert.equal(normalizedTxParams.from.slice(0, 2), '0x', 'from should be hexPrefixd') + assert.equal(normalizedTxParams.data.slice(0, 2), '0x', 'data should be hexPrefixd') + assert(!('random' in normalizedTxParams), 'their should be no random key in normalizedTxParams') + + txParams.to = 'a7df1beDBF813f57096dF77FCd515f0B3900e402' + normalizedTxParams = txUtils.normalizeTxParams(txParams) + assert.equal(normalizedTxParams.to.slice(0, 2), '0x', 'to should be hexPrefixd') + + }) + }) + + describe('#validateRecipient', () => { + it('removes recipient for txParams with 0x when contract data is provided', function () { + const zeroRecipientandDataTxParams = { + from: '0x1678a085c290ebd122dc42cba69373b5953b831d', + to: '0x', + data: 'bytecode', + } + const sanitizedTxParams = txUtils.validateRecipient(zeroRecipientandDataTxParams) + assert.deepEqual(sanitizedTxParams, { from: '0x1678a085c290ebd122dc42cba69373b5953b831d', data: 'bytecode' }, 'no recipient with 0x') + }) + + it('should error when recipient is 0x', function () { + const zeroRecipientTxParams = { + from: '0x1678a085c290ebd122dc42cba69373b5953b831d', + to: '0x', + } + assert.throws(() => { txUtils.validateRecipient(zeroRecipientTxParams) }, Error, 'Invalid recipient address') + }) + }) + + + describe('#validateFrom', () => { + it('should error when from is not a hex string', function () { + + // where from is undefined + const txParams = {} + assert.throws(() => { txUtils.validateFrom(txParams) }, Error, `Invalid from address ${txParams.from} not a string`) + + // where from is array + txParams.from = [] + assert.throws(() => { txUtils.validateFrom(txParams) }, Error, `Invalid from address ${txParams.from} not a string`) + + // where from is a object + txParams.from = {} + assert.throws(() => { txUtils.validateFrom(txParams) }, Error, `Invalid from address ${txParams.from} not a string`) + + // where from is a invalid address + txParams.from = 'im going to fail' + assert.throws(() => { txUtils.validateFrom(txParams) }, Error, `Invalid from address`) + + // should run + txParams.from ='0x1678a085c290ebd122dc42cba69373b5953b831d' + txUtils.validateFrom(txParams) + }) + }) +}) diff --git a/test/unit/app/edge-encryptor-test.js b/test/unit/app/edge-encryptor-test.js new file mode 100644 index 000000000..cc9777389 --- /dev/null +++ b/test/unit/app/edge-encryptor-test.js @@ -0,0 +1,101 @@ +const assert = require('assert') + +const EdgeEncryptor = require('../../../app/scripts/edge-encryptor') + +var password = 'passw0rd1' +var data = 'some random data' + +global.crypto = global.crypto || { + getRandomValues: function (array) { + for (let i = 0; i < array.length; i++) { + array[i] = Math.random() * 100 + } + return array + } +} + +describe('EdgeEncryptor', function () { + + const edgeEncryptor = new EdgeEncryptor() + describe('encrypt', function () { + + it('should encrypt the data.', function (done) { + edgeEncryptor.encrypt(password, data) + .then(function (encryptedData) { + assert.notEqual(data, encryptedData) + assert.notEqual(encryptedData.length, 0) + done() + }).catch(function (err) { + done(err) + }) + }) + + it('should return proper format.', function (done) { + edgeEncryptor.encrypt(password, data) + .then(function (encryptedData) { + let encryptedObject = JSON.parse(encryptedData) + assert.ok(encryptedObject.data, 'there is no data') + assert.ok(encryptedObject.iv && encryptedObject.iv.length != 0, 'there is no iv') + assert.ok(encryptedObject.salt && encryptedObject.salt.length != 0, 'there is no salt') + done() + }).catch(function (err) { + done(err) + }) + }) + + it('should not return the same twice.', function (done) { + + const encryptPromises = [] + encryptPromises.push(edgeEncryptor.encrypt(password, data)) + encryptPromises.push(edgeEncryptor.encrypt(password, data)) + + Promise.all(encryptPromises).then((encryptedData) => { + assert.equal(encryptedData.length, 2) + assert.notEqual(encryptedData[0], encryptedData[1]) + assert.notEqual(encryptedData[0].length, 0) + assert.notEqual(encryptedData[1].length, 0) + done() + }) + }) + }) + + describe('decrypt', function () { + it('should be able to decrypt the encrypted data.', function (done) { + + edgeEncryptor.encrypt(password, data) + .then(function (encryptedData) { + edgeEncryptor.decrypt(password, encryptedData) + .then(function (decryptedData) { + assert.equal(decryptedData, data) + done() + }) + .catch(function (err) { + done(err) + }) + }) + .catch(function (err) { + done(err) + }) + }) + + it('cannot decrypt the encrypted data with wrong password.', function (done) { + + edgeEncryptor.encrypt(password, data) + .then(function (encryptedData) { + edgeEncryptor.decrypt('wrong password', encryptedData) + .then(function (decryptedData) { + assert.fail('could decrypt with wrong password') + done() + }) + .catch(function (err) { + assert.ok(err instanceof Error) + assert.equal(err.message, 'Incorrect password') + done() + }) + }) + .catch(function (err) { + done(err) + }) + }) + }) +}) diff --git a/test/unit/app/message-manager-test.js b/test/unit/app/message-manager-test.js new file mode 100644 index 000000000..36ef6c29f --- /dev/null +++ b/test/unit/app/message-manager-test.js @@ -0,0 +1,86 @@ +const assert = require('assert') +const MessageManager = require('../../../app/scripts/lib/message-manager') + +describe('Message Manager', function () { + let messageManager + + beforeEach(function () { + messageManager = new MessageManager() + }) + + describe('#getMsgList', function () { + it('when new should return empty array', function () { + var result = messageManager.messages + assert.ok(Array.isArray(result)) + assert.equal(result.length, 0) + }) + it('should also return transactions from local storage if any', function () { + + }) + }) + + describe('#addMsg', function () { + it('adds a Msg returned in getMsgList', function () { + var Msg = { id: 1, status: 'approved', metamaskNetworkId: 'unit test' } + messageManager.addMsg(Msg) + var result = messageManager.messages + assert.ok(Array.isArray(result)) + assert.equal(result.length, 1) + assert.equal(result[0].id, 1) + }) + }) + + describe('#setMsgStatusApproved', function () { + it('sets the Msg status to approved', function () { + var Msg = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test' } + messageManager.addMsg(Msg) + messageManager.setMsgStatusApproved(1) + var result = messageManager.messages + assert.ok(Array.isArray(result)) + assert.equal(result.length, 1) + assert.equal(result[0].status, 'approved') + }) + }) + + describe('#rejectMsg', function () { + it('sets the Msg status to rejected', function () { + var Msg = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test' } + messageManager.addMsg(Msg) + messageManager.rejectMsg(1) + var result = messageManager.messages + assert.ok(Array.isArray(result)) + assert.equal(result.length, 1) + assert.equal(result[0].status, 'rejected') + }) + }) + + describe('#_updateMsg', function () { + it('replaces the Msg with the same id', function () { + messageManager.addMsg({ id: '1', status: 'unapproved', metamaskNetworkId: 'unit test' }) + messageManager.addMsg({ id: '2', status: 'approved', metamaskNetworkId: 'unit test' }) + messageManager._updateMsg({ id: '1', status: 'blah', hash: 'foo', metamaskNetworkId: 'unit test' }) + var result = messageManager.getMsg('1') + assert.equal(result.hash, 'foo') + }) + }) + + describe('#getUnapprovedMsgs', function () { + it('returns unapproved Msgs in a hash', function () { + messageManager.addMsg({ id: '1', status: 'unapproved', metamaskNetworkId: 'unit test' }) + messageManager.addMsg({ id: '2', status: 'approved', metamaskNetworkId: 'unit test' }) + const result = messageManager.getUnapprovedMsgs() + assert.equal(typeof result, 'object') + assert.equal(result['1'].status, 'unapproved') + assert.equal(result['2'], undefined) + }) + }) + + describe('#getMsg', function () { + it('returns a Msg with the requested id', function () { + messageManager.addMsg({ id: '1', status: 'unapproved', metamaskNetworkId: 'unit test' }) + messageManager.addMsg({ id: '2', status: 'approved', metamaskNetworkId: 'unit test' }) + assert.equal(messageManager.getMsg('1').status, 'unapproved') + assert.equal(messageManager.getMsg('2').status, 'approved') + }) + }) +}) diff --git a/test/unit/app/nodeify-test.js b/test/unit/app/nodeify-test.js new file mode 100644 index 000000000..901603c8b --- /dev/null +++ b/test/unit/app/nodeify-test.js @@ -0,0 +1,30 @@ +const assert = require('assert') +const nodeify = require('../../../app/scripts/lib/nodeify') + +describe('nodeify', function () { + var obj = { + foo: 'bar', + promiseFunc: function (a) { + var solution = this.foo + a + return Promise.resolve(solution) + }, + } + + it('should retain original context', function (done) { + var nodified = nodeify(obj.promiseFunc, obj) + nodified('baz', function (err, res) { + assert.equal(res, 'barbaz') + done() + }) + }) + + it('should allow the last argument to not be a function', function (done) { + const nodified = nodeify(obj.promiseFunc, obj) + try { + nodified('baz') + done() + } catch (err) { + done(new Error('should not have thrown if the last argument is not a function')) + } + }) +}) diff --git a/test/unit/app/pending-balance-test.js b/test/unit/app/pending-balance-test.js new file mode 100644 index 000000000..1418e4a4e --- /dev/null +++ b/test/unit/app/pending-balance-test.js @@ -0,0 +1,93 @@ +const assert = require('assert') +const PendingBalanceCalculator = require('../../../app/scripts/lib/pending-balance-calculator') +const MockTxGen = require('../../lib/mock-tx-gen') +const BN = require('ethereumjs-util').BN +let providerResultStub = {} + +const zeroBn = new BN(0) +const etherBn = new BN(String(1e18)) +const ether = '0x' + etherBn.toString(16) + +describe('PendingBalanceCalculator', function () { + let balanceCalculator, pendingTxs + + describe('#calculateMaxCost(tx)', function () { + it('returns a BN for a given tx value', function () { + const txGen = new MockTxGen() + pendingTxs = txGen.generate({ + status: 'submitted', + txParams: { + value: ether, + gasPrice: '0x0', + gas: '0x0', + } + }, { count: 1 }) + + const balanceCalculator = generateBalanceCalcWith([], zeroBn) + const result = balanceCalculator.calculateMaxCost(pendingTxs[0]) + assert.equal(result.toString(), etherBn.toString(), 'computes one ether') + }) + + it('calculates gas costs as well', function () { + const txGen = new MockTxGen() + pendingTxs = txGen.generate({ + status: 'submitted', + txParams: { + value: '0x0', + gasPrice: '0x2', + gas: '0x3', + } + }, { count: 1 }) + + const balanceCalculator = generateBalanceCalcWith([], zeroBn) + const result = balanceCalculator.calculateMaxCost(pendingTxs[0]) + assert.equal(result.toString(), '6', 'computes 6 wei of gas') + }) + }) + + describe('if you have no pending txs and one ether', function () { + + beforeEach(function () { + balanceCalculator = generateBalanceCalcWith([], etherBn) + }) + + it('returns the network balance', async function () { + const result = await balanceCalculator.getBalance() + assert.equal(result, ether, `gave ${result} needed ${ether}`) + }) + }) + + describe('if you have a one ether pending tx and one ether', function () { + beforeEach(function () { + const txGen = new MockTxGen() + pendingTxs = txGen.generate({ + status: 'submitted', + txParams: { + value: ether, + gasPrice: '0x0', + gas: '0x0', + } + }, { count: 1 }) + + balanceCalculator = generateBalanceCalcWith(pendingTxs, etherBn) + }) + + it('returns the subtracted result', async function () { + const result = await balanceCalculator.getBalance() + assert.equal(result, '0x0', `gave ${result} needed '0x0'`) + return true + }) + + }) +}) + +function generateBalanceCalcWith (transactions, providerStub = zeroBn) { + const getPendingTransactions = async () => transactions + const getBalance = async () => providerStub + + return new PendingBalanceCalculator({ + getBalance, + getPendingTransactions, + }) +} + diff --git a/test/unit/app/personal-message-manager-test.js b/test/unit/app/personal-message-manager-test.js new file mode 100644 index 000000000..b07167bff --- /dev/null +++ b/test/unit/app/personal-message-manager-test.js @@ -0,0 +1,107 @@ +const assert = require('assert') + +const PersonalMessageManager = require('../../../app/scripts/lib/personal-message-manager') + +describe('Personal Message Manager', function () { + let messageManager + + beforeEach(function () { + messageManager = new PersonalMessageManager() + }) + + describe('#getMsgList', function () { + it('when new should return empty array', function () { + var result = messageManager.messages + assert.ok(Array.isArray(result)) + assert.equal(result.length, 0) + }) + it('should also return transactions from local storage if any', function () { + + }) + }) + + describe('#addMsg', function () { + it('adds a Msg returned in getMsgList', function () { + var Msg = { id: 1, status: 'approved', metamaskNetworkId: 'unit test' } + messageManager.addMsg(Msg) + var result = messageManager.messages + assert.ok(Array.isArray(result)) + assert.equal(result.length, 1) + assert.equal(result[0].id, 1) + }) + }) + + describe('#setMsgStatusApproved', function () { + it('sets the Msg status to approved', function () { + var Msg = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test' } + messageManager.addMsg(Msg) + messageManager.setMsgStatusApproved(1) + var result = messageManager.messages + assert.ok(Array.isArray(result)) + assert.equal(result.length, 1) + assert.equal(result[0].status, 'approved') + }) + }) + + describe('#rejectMsg', function () { + it('sets the Msg status to rejected', function () { + var Msg = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test' } + messageManager.addMsg(Msg) + messageManager.rejectMsg(1) + var result = messageManager.messages + assert.ok(Array.isArray(result)) + assert.equal(result.length, 1) + assert.equal(result[0].status, 'rejected') + }) + }) + + describe('#_updateMsg', function () { + it('replaces the Msg with the same id', function () { + messageManager.addMsg({ id: '1', status: 'unapproved', metamaskNetworkId: 'unit test' }) + messageManager.addMsg({ id: '2', status: 'approved', metamaskNetworkId: 'unit test' }) + messageManager._updateMsg({ id: '1', status: 'blah', hash: 'foo', metamaskNetworkId: 'unit test' }) + var result = messageManager.getMsg('1') + assert.equal(result.hash, 'foo') + }) + }) + + describe('#getUnapprovedMsgs', function () { + it('returns unapproved Msgs in a hash', function () { + messageManager.addMsg({ id: '1', status: 'unapproved', metamaskNetworkId: 'unit test' }) + messageManager.addMsg({ id: '2', status: 'approved', metamaskNetworkId: 'unit test' }) + const result = messageManager.getUnapprovedMsgs() + assert.equal(typeof result, 'object') + assert.equal(result['1'].status, 'unapproved') + assert.equal(result['2'], undefined) + }) + }) + + describe('#getMsg', function () { + it('returns a Msg with the requested id', function () { + messageManager.addMsg({ id: '1', status: 'unapproved', metamaskNetworkId: 'unit test' }) + messageManager.addMsg({ id: '2', status: 'approved', metamaskNetworkId: 'unit test' }) + assert.equal(messageManager.getMsg('1').status, 'unapproved') + assert.equal(messageManager.getMsg('2').status, 'approved') + }) + }) + + describe('#normalizeMsgData', function () { + it('converts text to a utf8 hex string', function () { + var input = 'hello' + var output = messageManager.normalizeMsgData(input) + assert.equal(output, '0x68656c6c6f', 'predictably hex encoded') + }) + + it('tolerates a hex prefix', function () { + var input = '0x12' + var output = messageManager.normalizeMsgData(input) + assert.equal(output, '0x12', 'un modified') + }) + + it('tolerates normal hex', function () { + var input = '12' + var output = messageManager.normalizeMsgData(input) + assert.equal(output, '0x12', 'adds prefix') + }) + }) +}) diff --git a/test/unit/app/seed-phrase-verifier-test.js b/test/unit/app/seed-phrase-verifier-test.js new file mode 100644 index 000000000..b0da534da --- /dev/null +++ b/test/unit/app/seed-phrase-verifier-test.js @@ -0,0 +1,133 @@ +const assert = require('assert') +const clone = require('clone') +const KeyringController = require('eth-keyring-controller') +const firstTimeState = require('../../../app/scripts/first-time-state') +const seedPhraseVerifier = require('../../../app/scripts/lib/seed-phrase-verifier') +const mockEncryptor = require('../../lib/mock-encryptor') + +describe('SeedPhraseVerifier', function () { + + describe('verifyAccounts', function () { + + let password = 'passw0rd1' + let hdKeyTree = 'HD Key Tree' + + let keyringController + let vault + let primaryKeyring + + beforeEach(async function () { + keyringController = new KeyringController({ + initState: clone(firstTimeState), + encryptor: mockEncryptor, + }) + + assert(keyringController) + + vault = await keyringController.createNewVaultAndKeychain(password) + primaryKeyring = keyringController.getKeyringsByType(hdKeyTree)[0] + }) + + it('should be able to verify created account with seed words', async function () { + + let createdAccounts = await primaryKeyring.getAccounts() + assert.equal(createdAccounts.length, 1) + + let serialized = await primaryKeyring.serialize() + let seedWords = serialized.mnemonic + assert.notEqual(seedWords.length, 0) + + let result = await seedPhraseVerifier.verifyAccounts(createdAccounts, seedWords) + }) + + it('should be able to verify created account (upper case) with seed words', async function () { + + let createdAccounts = await primaryKeyring.getAccounts() + assert.equal(createdAccounts.length, 1) + + let upperCaseAccounts = [createdAccounts[0].toUpperCase()] + + let serialized = await primaryKeyring.serialize() + let seedWords = serialized.mnemonic + assert.notEqual(seedWords.length, 0) + + let result = await seedPhraseVerifier.verifyAccounts(upperCaseAccounts, seedWords) + }) + + it('should be able to verify created account (lower case) with seed words', async function () { + + let createdAccounts = await primaryKeyring.getAccounts() + assert.equal(createdAccounts.length, 1) + let lowerCaseAccounts = [createdAccounts[0].toLowerCase()] + + let serialized = await primaryKeyring.serialize() + let seedWords = serialized.mnemonic + assert.notEqual(seedWords.length, 0) + + let result = await seedPhraseVerifier.verifyAccounts(lowerCaseAccounts, seedWords) + }) + + it('should return error with good but different seed words', async function () { + + let createdAccounts = await primaryKeyring.getAccounts() + assert.equal(createdAccounts.length, 1) + + let serialized = await primaryKeyring.serialize() + let seedWords = 'debris dizzy just program just float decrease vacant alarm reduce speak stadium' + + try { + let result = await seedPhraseVerifier.verifyAccounts(createdAccounts, seedWords) + assert.fail("Should reject") + } catch (err) { + assert.ok(err.message.indexOf('Not identical accounts!') >= 0, 'Wrong error message') + } + }) + + it('should return error with undefined existing accounts', async function () { + + let createdAccounts = await primaryKeyring.getAccounts() + assert.equal(createdAccounts.length, 1) + + let serialized = await primaryKeyring.serialize() + let seedWords = 'debris dizzy just program just float decrease vacant alarm reduce speak stadium' + + try { + let result = await seedPhraseVerifier.verifyAccounts(undefined, seedWords) + assert.fail("Should reject") + } catch (err) { + assert.equal(err.message, 'No created accounts defined.') + } + }) + + it('should return error with empty accounts array', async function () { + + let createdAccounts = await primaryKeyring.getAccounts() + assert.equal(createdAccounts.length, 1) + + let serialized = await primaryKeyring.serialize() + let seedWords = 'debris dizzy just program just float decrease vacant alarm reduce speak stadium' + + try { + let result = await seedPhraseVerifier.verifyAccounts([], seedWords) + assert.fail("Should reject") + } catch (err) { + assert.equal(err.message, 'No created accounts defined.') + } + }) + + it('should be able to verify more than one created account with seed words', async function () { + + const keyState = await keyringController.addNewAccount(primaryKeyring) + const keyState2 = await keyringController.addNewAccount(primaryKeyring) + + let createdAccounts = await primaryKeyring.getAccounts() + assert.equal(createdAccounts.length, 3) + + let serialized = await primaryKeyring.serialize() + let seedWords = serialized.mnemonic + assert.notEqual(seedWords.length, 0) + + let result = await seedPhraseVerifier.verifyAccounts(createdAccounts, seedWords) + }) + }) +}) diff --git a/test/unit/app/util-test.js b/test/unit/app/util-test.js new file mode 100644 index 000000000..670bc4d22 --- /dev/null +++ b/test/unit/app/util-test.js @@ -0,0 +1,41 @@ +const assert = require('assert') +const { sufficientBalance } = require('../../../app/scripts/lib/util') + + +describe('SufficientBalance', function () { + it('returns true if max tx cost is equal to balance.', function () { + const tx = { + 'value': '0x1', + 'gas': '0x2', + 'gasPrice': '0x3', + } + const balance = '0x8' + + const result = sufficientBalance(tx, balance) + assert.ok(result, 'sufficient balance found.') + }) + + it('returns true if max tx cost is less than balance.', function () { + const tx = { + 'value': '0x1', + 'gas': '0x2', + 'gasPrice': '0x3', + } + const balance = '0x9' + + const result = sufficientBalance(tx, balance) + assert.ok(result, 'sufficient balance found.') + }) + + it('returns false if max tx cost is more than balance.', function () { + const tx = { + 'value': '0x1', + 'gas': '0x2', + 'gasPrice': '0x3', + } + const balance = '0x6' + + const result = sufficientBalance(tx, balance) + assert.ok(!result, 'insufficient balance found.') + }) +}) \ No newline at end of file diff --git a/test/unit/blacklist-controller-test.js b/test/unit/blacklist-controller-test.js deleted file mode 100644 index cbf73d3e5..000000000 --- a/test/unit/blacklist-controller-test.js +++ /dev/null @@ -1,41 +0,0 @@ -const assert = require('assert') -const BlacklistController = require('../../app/scripts/controllers/blacklist') - -describe('blacklist controller', function () { - let blacklistController - - before(() => { - blacklistController = new BlacklistController() - }) - - describe('checkForPhishing', function () { - it('should not flag whitelisted values', function () { - const result = blacklistController.checkForPhishing('www.metamask.io') - assert.equal(result, false) - }) - it('should flag explicit values', function () { - const result = blacklistController.checkForPhishing('metamask.com') - assert.equal(result, true) - }) - it('should flag levenshtein values', function () { - const result = blacklistController.checkForPhishing('metmask.io') - assert.equal(result, true) - }) - it('should not flag not-even-close values', function () { - const result = blacklistController.checkForPhishing('example.com') - assert.equal(result, false) - }) - it('should not flag the ropsten faucet domains', function () { - const result = blacklistController.checkForPhishing('faucet.metamask.io') - assert.equal(result, false) - }) - it('should not flag the mascara domain', function () { - const result = blacklistController.checkForPhishing('zero.metamask.io') - assert.equal(result, false) - }) - it('should not flag the mascara-faucet domain', function () { - const result = blacklistController.checkForPhishing('zero-faucet.metamask.io') - assert.equal(result, false) - }) - }) -}) diff --git a/test/unit/currency-controller-test.js b/test/unit/currency-controller-test.js deleted file mode 100644 index 73a97d120..000000000 --- a/test/unit/currency-controller-test.js +++ /dev/null @@ -1,81 +0,0 @@ -// polyfill fetch -global.fetch = global.fetch || require('isomorphic-fetch') - -const assert = require('assert') -const nock = require('nock') -const CurrencyController = require('../../app/scripts/controllers/currency') - -describe('currency-controller', function () { - var currencyController - - beforeEach(function () { - currencyController = new CurrencyController() - }) - - describe('currency conversions', function () { - describe('#setCurrentCurrency', function () { - it('should return USD as default', function () { - assert.equal(currencyController.getCurrentCurrency(), 'usd') - }) - - it('should be able to set to other currency', function () { - assert.equal(currencyController.getCurrentCurrency(), 'usd') - currencyController.setCurrentCurrency('JPY') - var result = currencyController.getCurrentCurrency() - assert.equal(result, 'JPY') - }) - }) - - describe('#getConversionRate', function () { - it('should return undefined if non-existent', function () { - var result = currencyController.getConversionRate() - assert.ok(!result) - }) - }) - - describe('#updateConversionRate', function () { - it('should retrieve an update for ETH to USD and set it in memory', function (done) { - this.timeout(15000) - nock('https://api.infura.io') - .get('/v1/ticker/ethusd') - .reply(200, '{"base": "ETH", "quote": "USD", "bid": 288.45, "ask": 288.46, "volume": 112888.17569277, "exchange": "bitfinex", "total_volume": 272175.00106721005, "num_exchanges": 8, "timestamp": 1506444677}') - - assert.equal(currencyController.getConversionRate(), 0) - currencyController.setCurrentCurrency('usd') - currencyController.updateConversionRate() - .then(function () { - var result = currencyController.getConversionRate() - assert.equal(typeof result, 'number') - done() - }).catch(function (err) { - done(err) - }) - }) - - it('should work for JPY as well.', function () { - this.timeout(15000) - assert.equal(currencyController.getConversionRate(), 0) - - nock('https://api.infura.io') - .get('/v1/ticker/ethjpy') - .reply(200, '{"base": "ETH", "quote": "JPY", "bid": 32300.0, "ask": 32400.0, "volume": 247.4616071, "exchange": "kraken", "total_volume": 247.4616071, "num_exchanges": 1, "timestamp": 1506444676}') - - - var promise = new Promise( - function (resolve, reject) { - currencyController.setCurrentCurrency('jpy') - currencyController.updateConversionRate().then(function () { - resolve() - }) - }) - - promise.then(function () { - var result = currencyController.getConversionRate() - assert.equal(typeof result, 'number') - }).catch(function (done, err) { - done(err) - }) - }) - }) - }) -}) diff --git a/test/unit/edge-encryptor-test.js b/test/unit/edge-encryptor-test.js deleted file mode 100644 index d3f014d74..000000000 --- a/test/unit/edge-encryptor-test.js +++ /dev/null @@ -1,101 +0,0 @@ -const assert = require('assert') - -const EdgeEncryptor = require('../../app/scripts/edge-encryptor') - -var password = 'passw0rd1' -var data = 'some random data' - -global.crypto = global.crypto || { - getRandomValues: function (array) { - for (let i = 0; i < array.length; i++) { - array[i] = Math.random() * 100 - } - return array - } -} - -describe('EdgeEncryptor', function () { - - const edgeEncryptor = new EdgeEncryptor() - describe('encrypt', function () { - - it('should encrypt the data.', function (done) { - edgeEncryptor.encrypt(password, data) - .then(function (encryptedData) { - assert.notEqual(data, encryptedData) - assert.notEqual(encryptedData.length, 0) - done() - }).catch(function (err) { - done(err) - }) - }) - - it('should return proper format.', function (done) { - edgeEncryptor.encrypt(password, data) - .then(function (encryptedData) { - let encryptedObject = JSON.parse(encryptedData) - assert.ok(encryptedObject.data, 'there is no data') - assert.ok(encryptedObject.iv && encryptedObject.iv.length != 0, 'there is no iv') - assert.ok(encryptedObject.salt && encryptedObject.salt.length != 0, 'there is no salt') - done() - }).catch(function (err) { - done(err) - }) - }) - - it('should not return the same twice.', function (done) { - - const encryptPromises = [] - encryptPromises.push(edgeEncryptor.encrypt(password, data)) - encryptPromises.push(edgeEncryptor.encrypt(password, data)) - - Promise.all(encryptPromises).then((encryptedData) => { - assert.equal(encryptedData.length, 2) - assert.notEqual(encryptedData[0], encryptedData[1]) - assert.notEqual(encryptedData[0].length, 0) - assert.notEqual(encryptedData[1].length, 0) - done() - }) - }) - }) - - describe('decrypt', function () { - it('should be able to decrypt the encrypted data.', function (done) { - - edgeEncryptor.encrypt(password, data) - .then(function (encryptedData) { - edgeEncryptor.decrypt(password, encryptedData) - .then(function (decryptedData) { - assert.equal(decryptedData, data) - done() - }) - .catch(function (err) { - done(err) - }) - }) - .catch(function (err) { - done(err) - }) - }) - - it('cannot decrypt the encrypted data with wrong password.', function (done) { - - edgeEncryptor.encrypt(password, data) - .then(function (encryptedData) { - edgeEncryptor.decrypt('wrong password', encryptedData) - .then(function (decryptedData) { - assert.fail('could decrypt with wrong password') - done() - }) - .catch(function (err) { - assert.ok(err instanceof Error) - assert.equal(err.message, 'Incorrect password') - done() - }) - }) - .catch(function (err) { - done(err) - }) - }) - }) -}) diff --git a/test/unit/infura-controller-test.js b/test/unit/infura-controller-test.js deleted file mode 100644 index c9f0e7587..000000000 --- a/test/unit/infura-controller-test.js +++ /dev/null @@ -1,62 +0,0 @@ -const assert = require('assert') -const sinon = require('sinon') -const InfuraController = require('../../app/scripts/controllers/infura') - -describe('infura-controller', function () { - let infuraController, sandbox, networkStatus - const response = {'mainnet': 'degraded', 'ropsten': 'ok', 'kovan': 'ok', 'rinkeby': 'down'} - - before(async function () { - infuraController = new InfuraController() - sandbox = sinon.createSandbox() - sinon.stub(infuraController, 'checkInfuraNetworkStatus').resolves(response) - networkStatus = await infuraController.checkInfuraNetworkStatus() - }) - - after(function () { - sandbox.restore() - }) - - describe('Network status queries', function () { - - describe('Mainnet', function () { - it('should have Mainnet', function () { - assert.equal(Object.keys(networkStatus)[0], 'mainnet') - }) - - it('should have a value for Mainnet status', function () { - assert.equal(networkStatus.mainnet, 'degraded') - }) - }) - - describe('Ropsten', function () { - it('should have Ropsten', function () { - assert.equal(Object.keys(networkStatus)[1], 'ropsten') - }) - - it('should have a value for Ropsten status', function () { - assert.equal(networkStatus.ropsten, 'ok') - }) - }) - - describe('Kovan', function () { - it('should have Kovan', function () { - assert.equal(Object.keys(networkStatus)[2], 'kovan') - }) - - it('should have a value for Kovan status', function () { - assert.equal(networkStatus.kovan, 'ok') - }) - }) - - describe('Rinkeby', function () { - it('should have Rinkeby', function () { - assert.equal(Object.keys(networkStatus)[3], 'rinkeby') - }) - - it('should have a value for Rinkeby status', function () { - assert.equal(networkStatus.rinkeby, 'down') - }) - }) - }) -}) diff --git a/test/unit/message-manager-test.js b/test/unit/message-manager-test.js deleted file mode 100644 index 5e7039841..000000000 --- a/test/unit/message-manager-test.js +++ /dev/null @@ -1,86 +0,0 @@ -const assert = require('assert') -const MessageManager = require('../../app/scripts/lib/message-manager') - -describe('Message Manager', function () { - let messageManager - - beforeEach(function () { - messageManager = new MessageManager() - }) - - describe('#getMsgList', function () { - it('when new should return empty array', function () { - var result = messageManager.messages - assert.ok(Array.isArray(result)) - assert.equal(result.length, 0) - }) - it('should also return transactions from local storage if any', function () { - - }) - }) - - describe('#addMsg', function () { - it('adds a Msg returned in getMsgList', function () { - var Msg = { id: 1, status: 'approved', metamaskNetworkId: 'unit test' } - messageManager.addMsg(Msg) - var result = messageManager.messages - assert.ok(Array.isArray(result)) - assert.equal(result.length, 1) - assert.equal(result[0].id, 1) - }) - }) - - describe('#setMsgStatusApproved', function () { - it('sets the Msg status to approved', function () { - var Msg = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test' } - messageManager.addMsg(Msg) - messageManager.setMsgStatusApproved(1) - var result = messageManager.messages - assert.ok(Array.isArray(result)) - assert.equal(result.length, 1) - assert.equal(result[0].status, 'approved') - }) - }) - - describe('#rejectMsg', function () { - it('sets the Msg status to rejected', function () { - var Msg = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test' } - messageManager.addMsg(Msg) - messageManager.rejectMsg(1) - var result = messageManager.messages - assert.ok(Array.isArray(result)) - assert.equal(result.length, 1) - assert.equal(result[0].status, 'rejected') - }) - }) - - describe('#_updateMsg', function () { - it('replaces the Msg with the same id', function () { - messageManager.addMsg({ id: '1', status: 'unapproved', metamaskNetworkId: 'unit test' }) - messageManager.addMsg({ id: '2', status: 'approved', metamaskNetworkId: 'unit test' }) - messageManager._updateMsg({ id: '1', status: 'blah', hash: 'foo', metamaskNetworkId: 'unit test' }) - var result = messageManager.getMsg('1') - assert.equal(result.hash, 'foo') - }) - }) - - describe('#getUnapprovedMsgs', function () { - it('returns unapproved Msgs in a hash', function () { - messageManager.addMsg({ id: '1', status: 'unapproved', metamaskNetworkId: 'unit test' }) - messageManager.addMsg({ id: '2', status: 'approved', metamaskNetworkId: 'unit test' }) - const result = messageManager.getUnapprovedMsgs() - assert.equal(typeof result, 'object') - assert.equal(result['1'].status, 'unapproved') - assert.equal(result['2'], undefined) - }) - }) - - describe('#getMsg', function () { - it('returns a Msg with the requested id', function () { - messageManager.addMsg({ id: '1', status: 'unapproved', metamaskNetworkId: 'unit test' }) - messageManager.addMsg({ id: '2', status: 'approved', metamaskNetworkId: 'unit test' }) - assert.equal(messageManager.getMsg('1').status, 'unapproved') - assert.equal(messageManager.getMsg('2').status, 'approved') - }) - }) -}) diff --git a/test/unit/metamask-controller-test.js b/test/unit/metamask-controller-test.js deleted file mode 100644 index 23372d823..000000000 --- a/test/unit/metamask-controller-test.js +++ /dev/null @@ -1,547 +0,0 @@ -const assert = require('assert') -const sinon = require('sinon') -const clone = require('clone') -const nock = require('nock') -const createThoughStream = require('through2').obj -const MetaMaskController = require('../../app/scripts/metamask-controller') -const blacklistJSON = require('../stub/blacklist') -const firstTimeState = require('../../app/scripts/first-time-state') - -const currentNetworkId = 42 -const DEFAULT_LABEL = 'Account 1' -const TEST_SEED = 'debris dizzy just program just float decrease vacant alarm reduce speak stadium' -const TEST_ADDRESS = '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc' -const TEST_SEED_ALT = 'setup olympic issue mobile velvet surge alcohol burger horse view reopen gentle' -const TEST_ADDRESS_ALT = '0xc42edfcc21ed14dda456aa0756c153f7985d8813' - -describe('MetaMaskController', function () { - let metamaskController - const sandbox = sinon.createSandbox() - const noop = () => {} - - beforeEach(function () { - - nock('https://api.infura.io') - .persist() - .get('/v2/blacklist') - .reply(200, blacklistJSON) - - nock('https://api.infura.io') - .get('/v1/ticker/ethusd') - .reply(200, '{"base": "ETH", "quote": "USD", "bid": 288.45, "ask": 288.46, "volume": 112888.17569277, "exchange": "bitfinex", "total_volume": 272175.00106721005, "num_exchanges": 8, "timestamp": 1506444677}') - - nock('https://api.infura.io') - .get('/v1/ticker/ethjpy') - .reply(200, '{"base": "ETH", "quote": "JPY", "bid": 32300.0, "ask": 32400.0, "volume": 247.4616071, "exchange": "kraken", "total_volume": 247.4616071, "num_exchanges": 1, "timestamp": 1506444676}') - - nock('https://api.infura.io') - .persist() - .get(/.*/) - .reply(200) - - metamaskController = new MetaMaskController({ - showUnapprovedTx: noop, - showUnconfirmedMessage: noop, - encryptor: { - encrypt: function (password, object) { - this.object = object - return Promise.resolve() - }, - decrypt: function () { - return Promise.resolve(this.object) - }, - }, - initState: clone(firstTimeState), - }) - sandbox.spy(metamaskController.keyringController, 'createNewVaultAndKeychain') - sandbox.spy(metamaskController.keyringController, 'createNewVaultAndRestore') - }) - - afterEach(function () { - nock.cleanAll() - sandbox.restore() - }) - - describe('#getGasPrice', function () { - - it('gives the 50th percentile lowest accepted gas price from recentBlocksController', async function () { - const realRecentBlocksController = metamaskController.recentBlocksController - metamaskController.recentBlocksController = { - store: { - getState: () => { - return { - recentBlocks: [ - { gasPrices: [ '0x3b9aca00', '0x174876e800'] }, - { gasPrices: [ '0x3b9aca00', '0x174876e800'] }, - { gasPrices: [ '0x174876e800', '0x174876e800' ]}, - { gasPrices: [ '0x174876e800', '0x174876e800' ]}, - ], - } - }, - }, - } - - const gasPrice = metamaskController.getGasPrice() - assert.equal(gasPrice, '0x3b9aca00', 'accurately estimates 50th percentile accepted gas price') - - metamaskController.recentBlocksController = realRecentBlocksController - }) - }) - - describe('#createNewVaultAndKeychain', function () { - it('can only create new vault on keyringController once', async function () { - const selectStub = sandbox.stub(metamaskController, 'selectFirstIdentity') - - const password = 'a-fake-password' - - await metamaskController.createNewVaultAndKeychain(password) - await metamaskController.createNewVaultAndKeychain(password) - - assert(metamaskController.keyringController.createNewVaultAndKeychain.calledOnce) - - selectStub.reset() - }) - }) - - describe('#createNewVaultAndRestore', function () { - it('should be able to call newVaultAndRestore despite a mistake.', async function () { - const password = 'what-what-what' - await metamaskController.createNewVaultAndRestore(password, TEST_SEED.slice(0, -1)).catch((e) => null) - await metamaskController.createNewVaultAndRestore(password, TEST_SEED) - - assert(metamaskController.keyringController.createNewVaultAndRestore.calledTwice) - }) - - it('should clear previous identities after vault restoration', async () => { - await metamaskController.createNewVaultAndRestore('foobar1337', TEST_SEED) - assert.deepEqual(metamaskController.getState().identities, { - [TEST_ADDRESS]: { address: TEST_ADDRESS, name: DEFAULT_LABEL }, - }) - - await metamaskController.keyringController.saveAccountLabel(TEST_ADDRESS, 'Account Foo') - assert.deepEqual(metamaskController.getState().identities, { - [TEST_ADDRESS]: { address: TEST_ADDRESS, name: 'Account Foo' }, - }) - - await metamaskController.createNewVaultAndRestore('foobar1337', TEST_SEED_ALT) - assert.deepEqual(metamaskController.getState().identities, { - [TEST_ADDRESS_ALT]: { address: TEST_ADDRESS_ALT, name: DEFAULT_LABEL }, - }) - }) - }) - - describe('#getApi', function () { - let getApi, state - - beforeEach(function () { - getApi = metamaskController.getApi() - }) - - it('getState', function (done) { - getApi.getState((err, res) => { - if (err) { - done(err) - } else { - state = res - } - }) - assert.deepEqual(state, metamaskController.getState()) - done() - }) - - }) - - describe('preferencesController', function () { - - it('defaults useBlockie to false', function () { - assert.equal(metamaskController.preferencesController.store.getState().useBlockie, false) - }) - - it('setUseBlockie to true', function () { - metamaskController.setUseBlockie(true, noop) - assert.equal(metamaskController.preferencesController.store.getState().useBlockie, true) - }) - - }) - - describe('#selectFirstIdentity', function () { - let identities, address - - beforeEach(function () { - address = '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc' - identities = { - identities: { - '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc': { - 'address': '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc', - 'name': 'Account 1', - }, - }, - } - metamaskController.selectFirstIdentity(identities) - }) - - it('changes preferences controller select address', function () { - const preferenceControllerState = metamaskController.preferencesController.store.getState() - assert.equal(preferenceControllerState.selectedAddress, address) - }) - - it('changes metamask controller selected address', function () { - const metamaskState = metamaskController.getState() - assert.equal(metamaskState.selectedAddress, address) - }) - }) - - describe('#setCustomRpc', function () { - const customRPC = 'https://custom.rpc/' - let rpcTarget - - beforeEach(function () { - - nock('https://custom.rpc') - .post('/') - .reply(200) - - rpcTarget = metamaskController.setCustomRpc(customRPC) - }) - - afterEach(function () { - nock.cleanAll() - }) - - it('returns custom RPC that when called', async function () { - assert.equal(await rpcTarget, customRPC) - }) - - it('changes the network controller rpc', function () { - const networkControllerState = metamaskController.networkController.store.getState() - assert.equal(networkControllerState.provider.rpcTarget, customRPC) - }) - }) - - describe('#setCurrentCurrency', function () { - let defaultMetaMaskCurrency - - beforeEach(function () { - defaultMetaMaskCurrency = metamaskController.currencyController.getCurrentCurrency() - }) - - it('defaults to usd', function () { - assert.equal(defaultMetaMaskCurrency, 'usd') - }) - - it('sets currency to JPY', function () { - metamaskController.setCurrentCurrency('JPY', noop) - assert.equal(metamaskController.currencyController.getCurrentCurrency(), 'JPY') - }) - }) - - describe('#createShapeshifttx', function () { - let depositAddress, depositType, shapeShiftTxList - - beforeEach(function () { - nock('https://shapeshift.io') - .get('/txStat/3EevLFfB4H4XMWQwYCgjLie1qCAGpd2WBc') - .reply(200, '{"status": "no_deposits", "address": "3EevLFfB4H4XMWQwYCgjLie1qCAGpd2WBc"}') - - depositAddress = '3EevLFfB4H4XMWQwYCgjLie1qCAGpd2WBc' - depositType = 'ETH' - shapeShiftTxList = metamaskController.shapeshiftController.store.getState().shapeShiftTxList - }) - - it('creates a shapeshift tx', async function () { - metamaskController.createShapeShiftTx(depositAddress, depositType) - assert.equal(shapeShiftTxList[0].depositAddress, depositAddress) - }) - - }) - - describe('#addNewAccount', function () { - let addNewAccount - - beforeEach(function () { - addNewAccount = metamaskController.addNewAccount() - }) - - it('errors when an primary keyring is does not exist', async function () { - try { - await addNewAccount - assert.equal(1 === 0) - } catch (e) { - assert.equal(e.message, 'MetamaskController - No HD Key Tree found') - } - }) - }) - - describe('#verifyseedPhrase', function () { - let seedPhrase, getConfigSeed - - it('errors when no keying is provided', async function () { - try { - await metamaskController.verifySeedPhrase() - } catch (error) { - assert.equal(error.message, 'MetamaskController - No HD Key Tree found') - } - }) - - beforeEach(async function () { - await metamaskController.createNewVaultAndKeychain('password') - seedPhrase = await metamaskController.verifySeedPhrase() - }) - - it('#placeSeedWords should match the initially created vault seed', function () { - - metamaskController.placeSeedWords((err, result) => { - if (err) { - console.log(err) - } else { - getConfigSeed = metamaskController.configManager.getSeedWords() - assert.equal(result, seedPhrase) - assert.equal(result, getConfigSeed) - } - }) - assert.equal(getConfigSeed, undefined) - }) - - it('#addNewAccount', async function () { - await metamaskController.addNewAccount() - const getAccounts = await metamaskController.keyringController.getAccounts() - assert.equal(getAccounts.length, 2) - }) - }) - - describe('#resetAccount', function () { - - beforeEach(function () { - const selectedAddressStub = sinon.stub(metamaskController.preferencesController, 'getSelectedAddress') - const getNetworkstub = sinon.stub(metamaskController.txController.txStateManager, 'getNetwork') - - selectedAddressStub.returns('0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc') - getNetworkstub.returns(42) - - metamaskController.txController.txStateManager._saveTxList([ - { id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {from: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc'} }, - { id: 2, status: 'rejected', metamaskNetworkId: 32, txParams: {} }, - { id: 3, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams: {from: '0xB09d8505E1F4EF1CeA089D47094f5DD3464083d4'} }, - ]) - }) - - it('wipes transactions from only the correct network id and with the selected address', async function () { - await metamaskController.resetAccount() - assert.equal(metamaskController.txController.txStateManager.getTx(1), undefined) - }) - }) - - describe('#clearSeedWordCache', function () { - - it('should have set seed words', function () { - metamaskController.configManager.setSeedWords('test words') - const getConfigSeed = metamaskController.configManager.getSeedWords() - assert.equal(getConfigSeed, 'test words') - }) - - it('should clear config seed phrase', function () { - metamaskController.configManager.setSeedWords('test words') - metamaskController.clearSeedWordCache((err, result) => { - if (err) console.log(err) - }) - const getConfigSeed = metamaskController.configManager.getSeedWords() - assert.equal(getConfigSeed, null) - }) - - }) - - describe('#setCurrentLocale', function () { - - it('checks the default currentLocale', function () { - const preferenceCurrentLocale = metamaskController.preferencesController.store.getState().currentLocale - assert.equal(preferenceCurrentLocale, undefined) - }) - - it('sets current locale in preferences controller', function () { - metamaskController.setCurrentLocale('ja', noop) - const preferenceCurrentLocale = metamaskController.preferencesController.store.getState().currentLocale - assert.equal(preferenceCurrentLocale, 'ja') - }) - - }) - - describe('#newUnsignedMessage', function () { - - let msgParams, metamaskMsgs, messages, msgId - - const address = '0xc42edfcc21ed14dda456aa0756c153f7985d8813' - const data = '0x43727970746f6b697474696573' - - beforeEach(async function () { - - await metamaskController.createNewVaultAndRestore('foobar1337', TEST_SEED_ALT) - - msgParams = { - 'from': address, - 'data': data, - } - - metamaskController.newUnsignedMessage(msgParams, noop) - metamaskMsgs = metamaskController.messageManager.getUnapprovedMsgs() - messages = metamaskController.messageManager.messages - msgId = Object.keys(metamaskMsgs)[0] - messages[0].msgParams.metamaskId = parseInt(msgId) - }) - - it('persists address from msg params', function () { - assert.equal(metamaskMsgs[msgId].msgParams.from, address) - }) - - it('persists data from msg params', function () { - assert.equal(metamaskMsgs[msgId].msgParams.data, data) - }) - - it('sets the status to unapproved', function () { - assert.equal(metamaskMsgs[msgId].status, 'unapproved') - }) - - it('sets the type to eth_sign', function () { - assert.equal(metamaskMsgs[msgId].type, 'eth_sign') - }) - - it('rejects the message', function () { - const msgIdInt = parseInt(msgId) - metamaskController.cancelMessage(msgIdInt, noop) - assert.equal(messages[0].status, 'rejected') - }) - - it('errors when signing a message', async function () { - try { - await metamaskController.signMessage(messages[0].msgParams) - } catch (error) { - assert.equal(error.message, 'message length is invalid') - } - }) - }) - - describe('#newUnsignedPersonalMessage', function () { - - it('errors with no from in msgParams', function () { - const msgParams = { - 'data': data, - } - metamaskController.newUnsignedPersonalMessage(msgParams, function (error) { - assert.equal(error.message, 'MetaMask Message Signature: from field is required.') - }) - }) - - let msgParams, metamaskPersonalMsgs, personalMessages, msgId - - const address = '0xc42edfcc21ed14dda456aa0756c153f7985d8813' - const data = '0x43727970746f6b697474696573' - - beforeEach(async function () { - - await metamaskController.createNewVaultAndRestore('foobar1337', TEST_SEED_ALT) - - msgParams = { - 'from': address, - 'data': data, - } - - metamaskController.newUnsignedPersonalMessage(msgParams, noop) - metamaskPersonalMsgs = metamaskController.personalMessageManager.getUnapprovedMsgs() - personalMessages = metamaskController.personalMessageManager.messages - msgId = Object.keys(metamaskPersonalMsgs)[0] - personalMessages[0].msgParams.metamaskId = parseInt(msgId) - }) - - it('persists address from msg params', function () { - assert.equal(metamaskPersonalMsgs[msgId].msgParams.from, address) - }) - - it('persists data from msg params', function () { - assert.equal(metamaskPersonalMsgs[msgId].msgParams.data, data) - }) - - it('sets the status to unapproved', function () { - assert.equal(metamaskPersonalMsgs[msgId].status, 'unapproved') - }) - - it('sets the type to personal_sign', function () { - assert.equal(metamaskPersonalMsgs[msgId].type, 'personal_sign') - }) - - it('rejects the message', function () { - const msgIdInt = parseInt(msgId) - metamaskController.cancelPersonalMessage(msgIdInt, noop) - assert.equal(personalMessages[0].status, 'rejected') - }) - - it('errors when signing a message', async function () { - await metamaskController.signPersonalMessage(personalMessages[0].msgParams) - assert.equal(metamaskPersonalMsgs[msgId].status, 'signed') - assert.equal(metamaskPersonalMsgs[msgId].rawSig, '0x6a1b65e2b8ed53cf398a769fad24738f9fbe29841fe6854e226953542c4b6a173473cb152b6b1ae5f06d601d45dd699a129b0a8ca84e78b423031db5baa734741b') - }) - }) - - describe('#setupUntrustedCommunication', function () { - let streamTest - - const phishingUrl = 'decentral.market' - - afterEach(function () { - streamTest.end() - }) - - it('sets up phishing stream for untrusted communication ', async function () { - await metamaskController.blacklistController.updatePhishingList() - - streamTest = createThoughStream((chunk, enc, cb) => { - assert.equal(chunk.name, 'phishing') - assert.equal(chunk.data.hostname, phishingUrl) - cb() - }) - // console.log(streamTest) - metamaskController.setupUntrustedCommunication(streamTest, phishingUrl) - }) - }) - - describe('#setupTrustedCommunication', function () { - let streamTest - - afterEach(function () { - streamTest.end() - }) - - it('sets up controller dnode api for trusted communication', function (done) { - streamTest = createThoughStream((chunk, enc, cb) => { - assert.equal(chunk.name, 'controller') - cb() - done() - }) - - metamaskController.setupTrustedCommunication(streamTest, 'mycrypto.com') - }) - }) - - describe('#markAccountsFound', function () { - it('adds lost accounts to config manager data', function () { - metamaskController.markAccountsFound(noop) - const configManagerData = metamaskController.configManager.getData() - assert.deepEqual(configManagerData.lostAccounts, []) - }) - }) - - describe('#markPasswordForgotten', function () { - it('adds and sets forgottenPassword to config data to true', function () { - metamaskController.markPasswordForgotten(noop) - const configManagerData = metamaskController.configManager.getData() - assert.equal(configManagerData.forgottenPassword, true) - }) - }) - - describe('#unMarkPasswordForgotten', function () { - it('adds and sets forgottenPassword to config data to false', function () { - metamaskController.unMarkPasswordForgotten(noop) - const configManagerData = metamaskController.configManager.getData() - assert.equal(configManagerData.forgottenPassword, false) - }) - }) - -}) diff --git a/test/unit/migrations-test.js b/test/unit/migrations-test.js deleted file mode 100644 index 5bad25a45..000000000 --- a/test/unit/migrations-test.js +++ /dev/null @@ -1,108 +0,0 @@ -const assert = require('assert') -const path = require('path') - -const wallet1 = require(path.join('..', 'lib', 'migrations', '001.json')) -const vault4 = require(path.join('..', 'lib', 'migrations', '004.json')) -let vault5, vault6, vault7, vault8, vault9 // vault10, vault11 - -const migration2 = require(path.join('..', '..', 'app', 'scripts', 'migrations', '002')) -const migration3 = require(path.join('..', '..', 'app', 'scripts', 'migrations', '003')) -const migration4 = require(path.join('..', '..', 'app', 'scripts', 'migrations', '004')) -const migration5 = require(path.join('..', '..', 'app', 'scripts', 'migrations', '005')) -const migration6 = require(path.join('..', '..', 'app', 'scripts', 'migrations', '006')) -const migration7 = require(path.join('..', '..', 'app', 'scripts', 'migrations', '007')) -const migration8 = require(path.join('..', '..', 'app', 'scripts', 'migrations', '008')) -const migration9 = require(path.join('..', '..', 'app', 'scripts', 'migrations', '009')) -const migration10 = require(path.join('..', '..', 'app', 'scripts', 'migrations', '010')) -const migration11 = require(path.join('..', '..', 'app', 'scripts', 'migrations', '011')) -const migration12 = require(path.join('..', '..', 'app', 'scripts', 'migrations', '012')) -const migration13 = require(path.join('..', '..', 'app', 'scripts', 'migrations', '013')) - - -const oldTestRpc = 'https://rawtestrpc.metamask.io/' -const newTestRpc = 'https://testrpc.metamask.io/' - -describe('wallet1 is migrated successfully', () => { - it('should convert providers', () => { - wallet1.data.config.provider = { type: 'etherscan', rpcTarget: null } - - return migration2.migrate(wallet1) - .then((secondResult) => { - const secondData = secondResult.data - assert.equal(secondData.config.provider.type, 'rpc', 'provider should be rpc') - assert.equal(secondData.config.provider.rpcTarget, 'https://rpc.metamask.io/', 'main provider should be our rpc') - secondResult.data.config.provider.rpcTarget = oldTestRpc - return migration3.migrate(secondResult) - }).then((thirdResult) => { - assert.equal(thirdResult.data.config.provider.rpcTarget, newTestRpc, 'config.provider.rpcTarget should be set to the proper testrpc url.') - return migration4.migrate(thirdResult) - }).then((fourthResult) => { - const fourthData = fourthResult.data - assert.equal(fourthData.config.provider.rpcTarget, null, 'old rpcTarget should not exist.') - assert.equal(fourthData.config.provider.type, 'testnet', 'config.provider should be set to testnet.') - - return migration5.migrate(vault4) - }).then((fifthResult) => { - const fifthData = fifthResult.data - assert.equal(fifthData.vault, null, 'old vault should not exist') - assert.equal(fifthData.walletNicknames, null, 'old walletNicknames should not exist') - assert.equal(fifthData.config.selectedAccount, null, 'old config.selectedAccount should not exist') - assert.equal(fifthData.KeyringController.vault, vault4.data.vault, 'KeyringController.vault should exist') - assert.equal(fifthData.KeyringController.selectedAccount, vault4.data.config.selectedAccount, 'KeyringController.selectedAccount should have moved') - assert.equal(fifthData.KeyringController.walletNicknames['0x0beb674745816b125fbc07285d39fd373e64895c'], vault4.data.walletNicknames['0x0beb674745816b125fbc07285d39fd373e64895c'], 'KeyringController.walletNicknames should have moved') - - vault5 = fifthResult - return migration6.migrate(fifthResult) - }).then((sixthResult) => { - assert.equal(sixthResult.data.KeyringController.selectedAccount, null, 'old selectedAccount should not exist') - assert.equal(sixthResult.data.PreferencesController.selectedAddress, vault5.data.KeyringController.selectedAccount, 'selectedAccount should have moved') - - vault6 = sixthResult - return migration7.migrate(sixthResult) - }).then((seventhResult) => { - assert.equal(seventhResult.data.transactions, null, 'old transactions should not exist') - assert.equal(seventhResult.data.gasMultiplier, null, 'old gasMultiplier should not exist') - assert.equal(seventhResult.data.TransactionManager.transactions[0].id, vault6.data.transactions[0].id, 'transactions should have moved') - assert.equal(seventhResult.data.TransactionManager.gasMultiplier, vault6.data.gasMultiplier, 'gasMultiplier should have moved') - - vault7 = seventhResult - return migration8.migrate(seventhResult) - }).then((eighthResult) => { - assert.equal(eighthResult.data.noticesList, null, 'old noticesList should not exist') - assert.equal(eighthResult.data.NoticeController.noticesList[0].title, vault7.data.noticesList[0].title, 'noticesList should have moved') - - vault8 = eighthResult - return migration9.migrate(eighthResult) - }).then((ninthResult) => { - assert.equal(ninthResult.data.currentFiat, null, 'old currentFiat should not exist') - assert.equal(ninthResult.data.fiatCurrency, null, 'old fiatCurrency should not exist') - assert.equal(ninthResult.data.conversionRate, null, 'old conversionRate should not exist') - assert.equal(ninthResult.data.conversionDate, null, 'old conversionDate should not exist') - - assert.equal(ninthResult.data.CurrencyController.currentCurrency, vault8.data.fiatCurrency, 'currentFiat should have moved') - assert.equal(ninthResult.data.CurrencyController.conversionRate, vault8.data.conversionRate, 'conversionRate should have moved') - assert.equal(ninthResult.data.CurrencyController.conversionDate, vault8.data.conversionDate, 'conversionDate should have moved') - - vault9 = ninthResult - return migration10.migrate(ninthResult) - }).then((tenthResult) => { - assert.equal(tenthResult.data.shapeShiftTxList, null, 'old shapeShiftTxList should not exist') - assert.equal(tenthResult.data.ShapeShiftController.shapeShiftTxList[0].transaction, vault9.data.shapeShiftTxList[0].transaction) - - return migration11.migrate(tenthResult) - }).then((eleventhResult) => { - assert.equal(eleventhResult.data.isDisclaimerConfirmed, null, 'isDisclaimerConfirmed should not exist') - assert.equal(eleventhResult.data.TOSHash, null, 'TOSHash should not exist') - - return migration12.migrate(eleventhResult) - }).then((twelfthResult) => { - assert.equal(twelfthResult.data.NoticeController.noticesList[0].body, '', 'notices that have been read should have an empty body.') - assert.equal(twelfthResult.data.NoticeController.noticesList[1].body, 'nonempty', 'notices that have not been read should not have an empty body.') - - assert.equal(twelfthResult.data.config.provider.type, 'testnet', 'network is originally testnet.') - return migration13.migrate(twelfthResult) - }).then((thirteenthResult) => { - assert.equal(thirteenthResult.data.config.provider.type, 'ropsten', 'network has been changed to ropsten.') - }) - }) -}) diff --git a/test/unit/migrations/migrations-test.js b/test/unit/migrations/migrations-test.js new file mode 100644 index 000000000..50afd9c2e --- /dev/null +++ b/test/unit/migrations/migrations-test.js @@ -0,0 +1,108 @@ +const assert = require('assert') +const path = require('path') + +const wallet1 = require(path.join('..', '..', 'lib', 'migrations', '001.json')) +const vault4 = require(path.join('..', '..', 'lib', 'migrations', '004.json')) +let vault5, vault6, vault7, vault8, vault9 // vault10, vault11 + +const migration2 = require(path.join('..', '..', '..', 'app', 'scripts', 'migrations', '002')) +const migration3 = require(path.join('..', '..', '..', 'app', 'scripts', 'migrations', '003')) +const migration4 = require(path.join('..', '..', '..', 'app', 'scripts', 'migrations', '004')) +const migration5 = require(path.join('..', '..', '..', 'app', 'scripts', 'migrations', '005')) +const migration6 = require(path.join('..', '..', '..', 'app', 'scripts', 'migrations', '006')) +const migration7 = require(path.join('..', '..', '..', 'app', 'scripts', 'migrations', '007')) +const migration8 = require(path.join('..', '..', '..', 'app', 'scripts', 'migrations', '008')) +const migration9 = require(path.join('..', '..', '..', 'app', 'scripts', 'migrations', '009')) +const migration10 = require(path.join('..', '..', '..', 'app', 'scripts', 'migrations', '010')) +const migration11 = require(path.join('..', '..', '..', 'app', 'scripts', 'migrations', '011')) +const migration12 = require(path.join('..', '..', '..', 'app', 'scripts', 'migrations', '012')) +const migration13 = require(path.join('..', '..', '..', 'app', 'scripts', 'migrations', '013')) + + +const oldTestRpc = 'https://rawtestrpc.metamask.io/' +const newTestRpc = 'https://testrpc.metamask.io/' + +describe('wallet1 is migrated successfully', () => { + it('should convert providers', () => { + wallet1.data.config.provider = { type: 'etherscan', rpcTarget: null } + + return migration2.migrate(wallet1) + .then((secondResult) => { + const secondData = secondResult.data + assert.equal(secondData.config.provider.type, 'rpc', 'provider should be rpc') + assert.equal(secondData.config.provider.rpcTarget, 'https://rpc.metamask.io/', 'main provider should be our rpc') + secondResult.data.config.provider.rpcTarget = oldTestRpc + return migration3.migrate(secondResult) + }).then((thirdResult) => { + assert.equal(thirdResult.data.config.provider.rpcTarget, newTestRpc, 'config.provider.rpcTarget should be set to the proper testrpc url.') + return migration4.migrate(thirdResult) + }).then((fourthResult) => { + const fourthData = fourthResult.data + assert.equal(fourthData.config.provider.rpcTarget, null, 'old rpcTarget should not exist.') + assert.equal(fourthData.config.provider.type, 'testnet', 'config.provider should be set to testnet.') + + return migration5.migrate(vault4) + }).then((fifthResult) => { + const fifthData = fifthResult.data + assert.equal(fifthData.vault, null, 'old vault should not exist') + assert.equal(fifthData.walletNicknames, null, 'old walletNicknames should not exist') + assert.equal(fifthData.config.selectedAccount, null, 'old config.selectedAccount should not exist') + assert.equal(fifthData.KeyringController.vault, vault4.data.vault, 'KeyringController.vault should exist') + assert.equal(fifthData.KeyringController.selectedAccount, vault4.data.config.selectedAccount, 'KeyringController.selectedAccount should have moved') + assert.equal(fifthData.KeyringController.walletNicknames['0x0beb674745816b125fbc07285d39fd373e64895c'], vault4.data.walletNicknames['0x0beb674745816b125fbc07285d39fd373e64895c'], 'KeyringController.walletNicknames should have moved') + + vault5 = fifthResult + return migration6.migrate(fifthResult) + }).then((sixthResult) => { + assert.equal(sixthResult.data.KeyringController.selectedAccount, null, 'old selectedAccount should not exist') + assert.equal(sixthResult.data.PreferencesController.selectedAddress, vault5.data.KeyringController.selectedAccount, 'selectedAccount should have moved') + + vault6 = sixthResult + return migration7.migrate(sixthResult) + }).then((seventhResult) => { + assert.equal(seventhResult.data.transactions, null, 'old transactions should not exist') + assert.equal(seventhResult.data.gasMultiplier, null, 'old gasMultiplier should not exist') + assert.equal(seventhResult.data.TransactionManager.transactions[0].id, vault6.data.transactions[0].id, 'transactions should have moved') + assert.equal(seventhResult.data.TransactionManager.gasMultiplier, vault6.data.gasMultiplier, 'gasMultiplier should have moved') + + vault7 = seventhResult + return migration8.migrate(seventhResult) + }).then((eighthResult) => { + assert.equal(eighthResult.data.noticesList, null, 'old noticesList should not exist') + assert.equal(eighthResult.data.NoticeController.noticesList[0].title, vault7.data.noticesList[0].title, 'noticesList should have moved') + + vault8 = eighthResult + return migration9.migrate(eighthResult) + }).then((ninthResult) => { + assert.equal(ninthResult.data.currentFiat, null, 'old currentFiat should not exist') + assert.equal(ninthResult.data.fiatCurrency, null, 'old fiatCurrency should not exist') + assert.equal(ninthResult.data.conversionRate, null, 'old conversionRate should not exist') + assert.equal(ninthResult.data.conversionDate, null, 'old conversionDate should not exist') + + assert.equal(ninthResult.data.CurrencyController.currentCurrency, vault8.data.fiatCurrency, 'currentFiat should have moved') + assert.equal(ninthResult.data.CurrencyController.conversionRate, vault8.data.conversionRate, 'conversionRate should have moved') + assert.equal(ninthResult.data.CurrencyController.conversionDate, vault8.data.conversionDate, 'conversionDate should have moved') + + vault9 = ninthResult + return migration10.migrate(ninthResult) + }).then((tenthResult) => { + assert.equal(tenthResult.data.shapeShiftTxList, null, 'old shapeShiftTxList should not exist') + assert.equal(tenthResult.data.ShapeShiftController.shapeShiftTxList[0].transaction, vault9.data.shapeShiftTxList[0].transaction) + + return migration11.migrate(tenthResult) + }).then((eleventhResult) => { + assert.equal(eleventhResult.data.isDisclaimerConfirmed, null, 'isDisclaimerConfirmed should not exist') + assert.equal(eleventhResult.data.TOSHash, null, 'TOSHash should not exist') + + return migration12.migrate(eleventhResult) + }).then((twelfthResult) => { + assert.equal(twelfthResult.data.NoticeController.noticesList[0].body, '', 'notices that have been read should have an empty body.') + assert.equal(twelfthResult.data.NoticeController.noticesList[1].body, 'nonempty', 'notices that have not been read should not have an empty body.') + + assert.equal(twelfthResult.data.config.provider.type, 'testnet', 'network is originally testnet.') + return migration13.migrate(twelfthResult) + }).then((thirteenthResult) => { + assert.equal(thirteenthResult.data.config.provider.type, 'ropsten', 'network has been changed to ropsten.') + }) + }) +}) diff --git a/test/unit/migrations/migrator-test.js b/test/unit/migrations/migrator-test.js new file mode 100644 index 000000000..a9374dff1 --- /dev/null +++ b/test/unit/migrations/migrator-test.js @@ -0,0 +1,68 @@ +const assert = require('assert') +const clone = require('clone') +const Migrator = require('../../../app/scripts/lib/migrator/') +const liveMigrations = require('../../../app/scripts/migrations/') +const stubMigrations = [ + { + version: 1, + migrate: (data) => { + // clone the data just like we do in migrations + const clonedData = clone(data) + clonedData.meta.version = 1 + return Promise.resolve(clonedData) + }, + }, + { + version: 2, + migrate: (data) => { + const clonedData = clone(data) + clonedData.meta.version = 2 + return Promise.resolve(clonedData) + }, + }, + { + version: 3, + migrate: (data) => { + const clonedData = clone(data) + clonedData.meta.version = 3 + return Promise.resolve(clonedData) + }, + }, +] +const versionedData = {meta: {version: 0}, data: {hello: 'world'}} + +const firstTimeState = { + meta: { version: 0 }, + data: require('../../../app/scripts/first-time-state'), +} + +describe('Migrator', () => { + const migrator = new Migrator({ migrations: stubMigrations }) + it('migratedData version should be version 3', (done) => { + migrator.migrateData(versionedData) + .then((migratedData) => { + assert.equal(migratedData.meta.version, stubMigrations[2].version) + done() + }).catch(done) + }) + + it('should match the last version in live migrations', (done) => { + const migrator = new Migrator({ migrations: liveMigrations }) + migrator.migrateData(firstTimeState) + .then((migratedData) => { + const last = liveMigrations.length - 1 + assert.equal(migratedData.meta.version, liveMigrations[last].version) + done() + }).catch(done) + }) + + it('should emit an error', function (done) { + this.timeout(15000) + const migrator = new Migrator({ migrations: [{ version: 1, migrate: async () => { throw new Error('test') } } ] }) + migrator.on('error', () => done()) + migrator.migrateData({ meta: {version: 0} }) + .then((migratedData) => { + }).catch(done) + }) + +}) diff --git a/test/unit/migrator-test.js b/test/unit/migrator-test.js deleted file mode 100644 index 4404e1dc4..000000000 --- a/test/unit/migrator-test.js +++ /dev/null @@ -1,68 +0,0 @@ -const assert = require('assert') -const clone = require('clone') -const Migrator = require('../../app/scripts/lib/migrator/') -const liveMigrations = require('../../app/scripts/migrations/') -const stubMigrations = [ - { - version: 1, - migrate: (data) => { - // clone the data just like we do in migrations - const clonedData = clone(data) - clonedData.meta.version = 1 - return Promise.resolve(clonedData) - }, - }, - { - version: 2, - migrate: (data) => { - const clonedData = clone(data) - clonedData.meta.version = 2 - return Promise.resolve(clonedData) - }, - }, - { - version: 3, - migrate: (data) => { - const clonedData = clone(data) - clonedData.meta.version = 3 - return Promise.resolve(clonedData) - }, - }, -] -const versionedData = {meta: {version: 0}, data: {hello: 'world'}} - -const firstTimeState = { - meta: { version: 0 }, - data: require('../../app/scripts/first-time-state'), -} - -describe('Migrator', () => { - const migrator = new Migrator({ migrations: stubMigrations }) - it('migratedData version should be version 3', (done) => { - migrator.migrateData(versionedData) - .then((migratedData) => { - assert.equal(migratedData.meta.version, stubMigrations[2].version) - done() - }).catch(done) - }) - - it('should match the last version in live migrations', (done) => { - const migrator = new Migrator({ migrations: liveMigrations }) - migrator.migrateData(firstTimeState) - .then((migratedData) => { - const last = liveMigrations.length - 1 - assert.equal(migratedData.meta.version, liveMigrations[last].version) - done() - }).catch(done) - }) - - it('should emit an error', function (done) { - this.timeout(15000) - const migrator = new Migrator({ migrations: [{ version: 1, migrate: async () => { throw new Error('test') } } ] }) - migrator.on('error', () => done()) - migrator.migrateData({ meta: {version: 0} }) - .then((migratedData) => { - }).catch(done) - }) - -}) diff --git a/test/unit/network-contoller-test.js b/test/unit/network-contoller-test.js deleted file mode 100644 index 2d590a3f6..000000000 --- a/test/unit/network-contoller-test.js +++ /dev/null @@ -1,102 +0,0 @@ -const assert = require('assert') -const nock = require('nock') -const NetworkController = require('../../app/scripts/controllers/network') -const { - getNetworkDisplayName, -} = require('../../app/scripts/controllers/network/util') - -const { createTestProviderTools } = require('../stub/provider') -const providerResultStub = {} - -describe('# Network Controller', function () { - let networkController - const noop = () => {} - const networkControllerProviderConfig = { - getAccounts: noop, - } - - beforeEach(function () { - - nock('https://rinkeby.infura.io') - .persist() - .post('/metamask') - .reply(200) - - networkController = new NetworkController() - - networkController.initializeProvider(networkControllerProviderConfig) - }) - - afterEach(function () { - nock.cleanAll() - }) - - describe('network', function () { - describe('#provider', function () { - it('provider should be updatable without reassignment', function () { - networkController.initializeProvider(networkControllerProviderConfig) - const proxy = networkController._proxy - proxy.setTarget({ test: true, on: () => {} }) - assert.ok(proxy.test) - }) - }) - describe('#getNetworkState', function () { - it('should return loading when new', function () { - const networkState = networkController.getNetworkState() - assert.equal(networkState, 'loading', 'network is loading') - }) - }) - - describe('#setNetworkState', function () { - it('should update the network', function () { - networkController.setNetworkState(1) - const networkState = networkController.getNetworkState() - assert.equal(networkState, 1, 'network is 1') - }) - }) - - describe('#setProviderType', function () { - it('should update provider.type', function () { - networkController.setProviderType('mainnet') - const type = networkController.getProviderConfig().type - assert.equal(type, 'mainnet', 'provider type is updated') - }) - it('should set the network to loading', function () { - networkController.setProviderType('mainnet') - const loading = networkController.isNetworkLoading() - assert.ok(loading, 'network is loading') - }) - }) - }) -}) - -describe('Network utils', () => { - it('getNetworkDisplayName should return the correct network name', () => { - const tests = [ - { - input: 3, - expected: 'Ropsten', - }, { - input: 4, - expected: 'Rinkeby', - }, { - input: 42, - expected: 'Kovan', - }, { - input: 'ropsten', - expected: 'Ropsten', - }, { - input: 'rinkeby', - expected: 'Rinkeby', - }, { - input: 'kovan', - expected: 'Kovan', - }, { - input: 'mainnet', - expected: 'Main Ethereum Network', - }, - ] - - tests.forEach(({ input, expected }) => assert.equal(getNetworkDisplayName(input), expected)) - }) -}) diff --git a/test/unit/nodeify-test.js b/test/unit/nodeify-test.js deleted file mode 100644 index c7b127889..000000000 --- a/test/unit/nodeify-test.js +++ /dev/null @@ -1,30 +0,0 @@ -const assert = require('assert') -const nodeify = require('../../app/scripts/lib/nodeify') - -describe('nodeify', function () { - var obj = { - foo: 'bar', - promiseFunc: function (a) { - var solution = this.foo + a - return Promise.resolve(solution) - }, - } - - it('should retain original context', function (done) { - var nodified = nodeify(obj.promiseFunc, obj) - nodified('baz', function (err, res) { - assert.equal(res, 'barbaz') - done() - }) - }) - - it('should allow the last argument to not be a function', function (done) { - const nodified = nodeify(obj.promiseFunc, obj) - try { - nodified('baz') - done() - } catch (err) { - done(new Error('should not have thrown if the last argument is not a function')) - } - }) -}) diff --git a/test/unit/nonce-tracker-test.js b/test/unit/nonce-tracker-test.js deleted file mode 100644 index cf26945d3..000000000 --- a/test/unit/nonce-tracker-test.js +++ /dev/null @@ -1,239 +0,0 @@ -const assert = require('assert') -const NonceTracker = require('../../app/scripts/controllers/transactions/nonce-tracker') -const MockTxGen = require('../lib/mock-tx-gen') -let providerResultStub = {} - -describe('Nonce Tracker', function () { - let nonceTracker, provider - let getPendingTransactions, pendingTxs - let getConfirmedTransactions, confirmedTxs - - describe('#getNonceLock', function () { - - describe('with 3 confirmed and 1 pending', function () { - beforeEach(function () { - const txGen = new MockTxGen() - confirmedTxs = txGen.generate({ status: 'confirmed' }, { count: 3 }) - pendingTxs = txGen.generate({ status: 'submitted' }, { count: 1 }) - nonceTracker = generateNonceTrackerWith(pendingTxs, confirmedTxs, '0x1') - }) - - it('should return 4', async function () { - this.timeout(15000) - const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926') - assert.equal(nonceLock.nextNonce, '4', `nonce should be 4 got ${nonceLock.nextNonce}`) - await nonceLock.releaseLock() - }) - - it('should use localNonce if network returns a nonce lower then a confirmed tx in state', async function () { - this.timeout(15000) - const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926') - assert.equal(nonceLock.nextNonce, '4', 'nonce should be 4') - await nonceLock.releaseLock() - }) - }) - - describe('sentry issue 476304902', function () { - beforeEach(function () { - const txGen = new MockTxGen() - pendingTxs = txGen.generate({ status: 'submitted' }, { - fromNonce: 3, - count: 29, - }) - nonceTracker = generateNonceTrackerWith(pendingTxs, [], '0x3') - }) - - it('should return 9', async function () { - this.timeout(15000) - const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926') - assert.equal(nonceLock.nextNonce, '32', `nonce should be 32 got ${nonceLock.nextNonce}`) - await nonceLock.releaseLock() - }) - }) - - describe('issue 3670', function () { - beforeEach(function () { - const txGen = new MockTxGen() - pendingTxs = txGen.generate({ status: 'submitted' }, { - fromNonce: 6, - count: 3, - }) - nonceTracker = generateNonceTrackerWith(pendingTxs, [], '0x6') - }) - - it('should return 9', async function () { - this.timeout(15000) - const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926') - assert.equal(nonceLock.nextNonce, '9', `nonce should be 9 got ${nonceLock.nextNonce}`) - await nonceLock.releaseLock() - }) - }) - - describe('with no previous txs', function () { - beforeEach(function () { - nonceTracker = generateNonceTrackerWith([], []) - }) - - it('should return 0', async function () { - this.timeout(15000) - const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926') - assert.equal(nonceLock.nextNonce, '0', `nonce should be 0 returned ${nonceLock.nextNonce}`) - await nonceLock.releaseLock() - }) - }) - - describe('with multiple previous txs with same nonce', function () { - beforeEach(function () { - const txGen = new MockTxGen() - confirmedTxs = txGen.generate({ status: 'confirmed' }, { count: 1 }) - pendingTxs = txGen.generate({ - status: 'submitted', - txParams: { nonce: '0x01' }, - }, { count: 5 }) - - nonceTracker = generateNonceTrackerWith(pendingTxs, confirmedTxs, '0x0') - }) - - it('should return nonce after those', async function () { - this.timeout(15000) - const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926') - assert.equal(nonceLock.nextNonce, '2', `nonce should be 2 got ${nonceLock.nextNonce}`) - await nonceLock.releaseLock() - }) - }) - - describe('when local confirmed count is higher than network nonce', function () { - beforeEach(function () { - const txGen = new MockTxGen() - confirmedTxs = txGen.generate({ status: 'confirmed' }, { count: 3 }) - nonceTracker = generateNonceTrackerWith([], confirmedTxs, '0x1') - }) - - it('should return nonce after those', async function () { - this.timeout(15000) - const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926') - assert.equal(nonceLock.nextNonce, '3', `nonce should be 3 got ${nonceLock.nextNonce}`) - await nonceLock.releaseLock() - }) - }) - - describe('when local pending count is higher than other metrics', function () { - beforeEach(function () { - const txGen = new MockTxGen() - pendingTxs = txGen.generate({ status: 'submitted' }, { count: 2 }) - nonceTracker = generateNonceTrackerWith(pendingTxs, []) - }) - - it('should return nonce after those', async function () { - this.timeout(15000) - const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926') - assert.equal(nonceLock.nextNonce, '2', `nonce should be 2 got ${nonceLock.nextNonce}`) - await nonceLock.releaseLock() - }) - }) - - describe('when provider nonce is higher than other metrics', function () { - beforeEach(function () { - const txGen = new MockTxGen() - pendingTxs = txGen.generate({ status: 'submitted' }, { count: 2 }) - nonceTracker = generateNonceTrackerWith(pendingTxs, [], '0x05') - }) - - it('should return nonce after those', async function () { - this.timeout(15000) - const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926') - assert.equal(nonceLock.nextNonce, '5', `nonce should be 5 got ${nonceLock.nextNonce}`) - await nonceLock.releaseLock() - }) - }) - - describe('when there are some pending nonces below the remote one and some over.', function () { - beforeEach(function () { - const txGen = new MockTxGen() - pendingTxs = txGen.generate({ status: 'submitted' }, { count: 5 }) - nonceTracker = generateNonceTrackerWith(pendingTxs, [], '0x03') - }) - - it('should return nonce after those', async function () { - this.timeout(15000) - const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926') - assert.equal(nonceLock.nextNonce, '5', `nonce should be 5 got ${nonceLock.nextNonce}`) - await nonceLock.releaseLock() - }) - }) - - describe('when there are pending nonces non sequentially over the network nonce.', function () { - beforeEach(function () { - const txGen = new MockTxGen() - txGen.generate({ status: 'submitted' }, { count: 5 }) - // 5 over that number - pendingTxs = txGen.generate({ status: 'submitted' }, { count: 5 }) - nonceTracker = generateNonceTrackerWith(pendingTxs, [], '0x00') - }) - - it('should return nonce after network nonce', async function () { - this.timeout(15000) - const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926') - assert.equal(nonceLock.nextNonce, '0', `nonce should be 0 got ${nonceLock.nextNonce}`) - await nonceLock.releaseLock() - }) - }) - - describe('When all three return different values', function () { - beforeEach(function () { - const txGen = new MockTxGen() - const confirmedTxs = txGen.generate({ status: 'confirmed' }, { count: 10 }) - const pendingTxs = txGen.generate({ - status: 'submitted', - nonce: 100, - }, { count: 1 }) - // 0x32 is 50 in hex: - nonceTracker = generateNonceTrackerWith(pendingTxs, confirmedTxs, '0x32') - }) - - it('should return nonce after network nonce', async function () { - this.timeout(15000) - const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926') - assert.equal(nonceLock.nextNonce, '50', `nonce should be 50 got ${nonceLock.nextNonce}`) - await nonceLock.releaseLock() - }) - }) - - describe('Faq issue 67', function () { - beforeEach(function () { - const txGen = new MockTxGen() - const confirmedTxs = txGen.generate({ status: 'confirmed' }, { count: 64 }) - const pendingTxs = txGen.generate({ - status: 'submitted', - }, { count: 10 }) - // 0x40 is 64 in hex: - nonceTracker = generateNonceTrackerWith(pendingTxs, [], '0x40') - }) - - it('should return nonce after network nonce', async function () { - this.timeout(15000) - const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926') - assert.equal(nonceLock.nextNonce, '74', `nonce should be 74 got ${nonceLock.nextNonce}`) - await nonceLock.releaseLock() - }) - }) - }) -}) - -function generateNonceTrackerWith (pending, confirmed, providerStub = '0x0') { - const getPendingTransactions = () => pending - const getConfirmedTransactions = () => confirmed - providerResultStub.result = providerStub - const provider = { - sendAsync: (_, cb) => { cb(undefined, providerResultStub) }, - _blockTracker: { - getCurrentBlock: () => '0x11b568', - }, - } - return new NonceTracker({ - provider, - getPendingTransactions, - getConfirmedTransactions, - }) -} - diff --git a/test/unit/notice-controller-test.js b/test/unit/notice-controller-test.js deleted file mode 100644 index 09eeda15c..000000000 --- a/test/unit/notice-controller-test.js +++ /dev/null @@ -1,114 +0,0 @@ -const assert = require('assert') -const configManagerGen = require('../lib/mock-config-manager') -const NoticeController = require('../../app/scripts/notice-controller') - -describe('notice-controller', function () { - var noticeController - - beforeEach(function () { - // simple localStorage polyfill - const configManager = configManagerGen() - noticeController = new NoticeController({ - configManager: configManager, - }) - }) - - describe('notices', function () { - describe('#getNoticesList', function () { - it('should return an empty array when new', function (done) { - // const testList = [{ - // id: 0, - // read: false, - // title: 'Futuristic Notice', - // }] - var result = noticeController.getNoticesList() - assert.equal(result.length, 0) - done() - }) - }) - - describe('#setNoticesList', function () { - it('should set data appropriately', function (done) { - var testList = [{ - id: 0, - read: false, - title: 'Futuristic Notice', - }] - noticeController.setNoticesList(testList) - var testListId = noticeController.getNoticesList()[0].id - assert.equal(testListId, 0) - done() - }) - }) - - describe('#updateNoticeslist', function () { - it('should integrate the latest changes from the source', function (done) { - var testList = [{ - id: 55, - read: false, - title: 'Futuristic Notice', - }] - noticeController.setNoticesList(testList) - noticeController.updateNoticesList().then(() => { - var newList = noticeController.getNoticesList() - assert.ok(newList[0].id === 55) - assert.ok(newList[1]) - done() - }) - }) - it('should not overwrite any existing fields', function (done) { - var testList = [{ - id: 0, - read: false, - title: 'Futuristic Notice', - }] - noticeController.setNoticesList(testList) - var newList = noticeController.getNoticesList() - assert.equal(newList[0].id, 0) - assert.equal(newList[0].title, 'Futuristic Notice') - assert.equal(newList.length, 1) - done() - }) - }) - - describe('#markNoticeRead', function () { - it('should mark a notice as read', function (done) { - var testList = [{ - id: 0, - read: false, - title: 'Futuristic Notice', - }] - noticeController.setNoticesList(testList) - noticeController.markNoticeRead(testList[0]) - var newList = noticeController.getNoticesList() - assert.ok(newList[0].read) - done() - }) - }) - - describe('#getLatestUnreadNotice', function () { - it('should retrieve the latest unread notice', function (done) { - var testList = [ - {id: 0, read: true, title: 'Past Notice'}, - {id: 1, read: false, title: 'Current Notice'}, - {id: 2, read: false, title: 'Future Notice'}, - ] - noticeController.setNoticesList(testList) - var latestUnread = noticeController.getLatestUnreadNotice() - assert.equal(latestUnread.id, 2) - done() - }) - it('should return undefined if no unread notices exist.', function (done) { - var testList = [ - {id: 0, read: true, title: 'Past Notice'}, - {id: 1, read: true, title: 'Current Notice'}, - {id: 2, read: true, title: 'Future Notice'}, - ] - noticeController.setNoticesList(testList) - var latestUnread = noticeController.getLatestUnreadNotice() - assert.ok(!latestUnread) - done() - }) - }) - }) -}) diff --git a/test/unit/pending-balance-test.js b/test/unit/pending-balance-test.js deleted file mode 100644 index dc4c1c3e4..000000000 --- a/test/unit/pending-balance-test.js +++ /dev/null @@ -1,93 +0,0 @@ -const assert = require('assert') -const PendingBalanceCalculator = require('../../app/scripts/lib/pending-balance-calculator') -const MockTxGen = require('../lib/mock-tx-gen') -const BN = require('ethereumjs-util').BN -let providerResultStub = {} - -const zeroBn = new BN(0) -const etherBn = new BN(String(1e18)) -const ether = '0x' + etherBn.toString(16) - -describe('PendingBalanceCalculator', function () { - let balanceCalculator, pendingTxs - - describe('#calculateMaxCost(tx)', function () { - it('returns a BN for a given tx value', function () { - const txGen = new MockTxGen() - pendingTxs = txGen.generate({ - status: 'submitted', - txParams: { - value: ether, - gasPrice: '0x0', - gas: '0x0', - } - }, { count: 1 }) - - const balanceCalculator = generateBalanceCalcWith([], zeroBn) - const result = balanceCalculator.calculateMaxCost(pendingTxs[0]) - assert.equal(result.toString(), etherBn.toString(), 'computes one ether') - }) - - it('calculates gas costs as well', function () { - const txGen = new MockTxGen() - pendingTxs = txGen.generate({ - status: 'submitted', - txParams: { - value: '0x0', - gasPrice: '0x2', - gas: '0x3', - } - }, { count: 1 }) - - const balanceCalculator = generateBalanceCalcWith([], zeroBn) - const result = balanceCalculator.calculateMaxCost(pendingTxs[0]) - assert.equal(result.toString(), '6', 'computes 6 wei of gas') - }) - }) - - describe('if you have no pending txs and one ether', function () { - - beforeEach(function () { - balanceCalculator = generateBalanceCalcWith([], etherBn) - }) - - it('returns the network balance', async function () { - const result = await balanceCalculator.getBalance() - assert.equal(result, ether, `gave ${result} needed ${ether}`) - }) - }) - - describe('if you have a one ether pending tx and one ether', function () { - beforeEach(function () { - const txGen = new MockTxGen() - pendingTxs = txGen.generate({ - status: 'submitted', - txParams: { - value: ether, - gasPrice: '0x0', - gas: '0x0', - } - }, { count: 1 }) - - balanceCalculator = generateBalanceCalcWith(pendingTxs, etherBn) - }) - - it('returns the subtracted result', async function () { - const result = await balanceCalculator.getBalance() - assert.equal(result, '0x0', `gave ${result} needed '0x0'`) - return true - }) - - }) -}) - -function generateBalanceCalcWith (transactions, providerStub = zeroBn) { - const getPendingTransactions = async () => transactions - const getBalance = async () => providerStub - - return new PendingBalanceCalculator({ - getBalance, - getPendingTransactions, - }) -} - diff --git a/test/unit/pending-tx-test.js b/test/unit/pending-tx-test.js deleted file mode 100644 index 97ac8524b..000000000 --- a/test/unit/pending-tx-test.js +++ /dev/null @@ -1,402 +0,0 @@ -const assert = require('assert') -const ethUtil = require('ethereumjs-util') -const EthTx = require('ethereumjs-tx') -const ObservableStore = require('obs-store') -const clone = require('clone') -const { createTestProviderTools } = require('../stub/provider') -const PendingTransactionTracker = require('../../app/scripts/controllers/transactions/pending-tx-tracker') -const MockTxGen = require('../lib/mock-tx-gen') -const sinon = require('sinon') -const noop = () => true -const currentNetworkId = 42 -const otherNetworkId = 36 -const privKey = new Buffer('8718b9618a37d1fc78c436511fc6df3c8258d3250635bba617f33003270ec03e', 'hex') - - -describe('PendingTransactionTracker', function () { - let pendingTxTracker, txMeta, txMetaNoHash, txMetaNoRawTx, providerResultStub, - provider, txMeta3, txList, knownErrors - this.timeout(10000) - beforeEach(function () { - txMeta = { - id: 1, - hash: '0x0593ee121b92e10d63150ad08b4b8f9c7857d1bd160195ee648fb9a0f8d00eeb', - status: 'signed', - txParams: { - from: '0x1678a085c290ebd122dc42cba69373b5953b831d', - nonce: '0x1', - value: '0xfffff', - }, - rawTx: '0xf86c808504a817c800827b0d940c62bb85faa3311a998d3aba8098c1235c564966880de0b6b3a7640000802aa08ff665feb887a25d4099e40e11f0fef93ee9608f404bd3f853dd9e84ed3317a6a02ec9d3d1d6e176d4d2593dd760e74ccac753e6a0ea0d00cc9789d0d7ff1f471d', - } - txMetaNoHash = { - id: 2, - status: 'signed', - txParams: { from: '0x1678a085c290ebd122dc42cba69373b5953b831d'}, - } - txMetaNoRawTx = { - hash: '0x0593ee121b92e10d63150ad08b4b8f9c7857d1bd160195ee648fb9a0f8d00eeb', - status: 'signed', - txParams: { from: '0x1678a085c290ebd122dc42cba69373b5953b831d'}, - } - providerResultStub = {} - provider = createTestProviderTools({ scaffold: providerResultStub }).provider - - pendingTxTracker = new PendingTransactionTracker({ - provider, - nonceTracker: { - getGlobalLock: async () => { - return { releaseLock: () => {} } - } - }, - getPendingTransactions: () => {return []}, - getCompletedTransactions: () => {return []}, - publishTransaction: () => {}, - }) - }) - - describe('_checkPendingTx state management', function () { - let stub - - afterEach(function () { - if (stub) { - stub.restore() - } - }) - - it('should become failed if another tx with the same nonce succeeds', async function () { - - // SETUP - const txGen = new MockTxGen() - - txGen.generate({ - id: '456', - value: '0x01', - hash: '0xbad', - status: 'confirmed', - nonce: '0x01', - }, { count: 1 }) - - const pending = txGen.generate({ - id: '123', - value: '0x02', - hash: '0xfad', - status: 'submitted', - nonce: '0x01', - }, { count: 1 })[0] - - stub = sinon.stub(pendingTxTracker, 'getCompletedTransactions') - .returns(txGen.txs) - - // THE EXPECTATION - const spy = sinon.spy() - pendingTxTracker.on('tx:failed', (txId, err) => { - assert.equal(txId, pending.id, 'should fail the pending tx') - assert.equal(err.name, 'NonceTakenErr', 'should emit a nonce taken error.') - spy(txId, err) - }) - - // THE METHOD - await pendingTxTracker._checkPendingTx(pending) - - // THE ASSERTION - assert.ok(spy.calledWith(pending.id), 'tx failed should be emitted') - }) - }) - - describe('#checkForTxInBlock', function () { - it('should return if no pending transactions', function () { - // throw a type error if it trys to do anything on the block - // thus failing the test - const block = Proxy.revocable({}, {}).revoke() - pendingTxTracker.checkForTxInBlock(block) - }) - it('should emit \'tx:failed\' if the txMeta does not have a hash', function (done) { - const block = Proxy.revocable({}, {}).revoke() - pendingTxTracker.getPendingTransactions = () => [txMetaNoHash] - pendingTxTracker.once('tx:failed', (txId, err) => { - assert(txId, txMetaNoHash.id, 'should pass txId') - done() - }) - pendingTxTracker.checkForTxInBlock(block) - }) - it('should emit \'txConfirmed\' if the tx is in the block', function (done) { - const block = { transactions: [txMeta]} - pendingTxTracker.getPendingTransactions = () => [txMeta] - pendingTxTracker.once('tx:confirmed', (txId) => { - assert(txId, txMeta.id, 'should pass txId') - done() - }) - pendingTxTracker.once('tx:failed', (_, err) => { done(err) }) - pendingTxTracker.checkForTxInBlock(block) - }) - }) - describe('#queryPendingTxs', function () { - it('should call #_checkPendingTxs if their is no oldBlock', function (done) { - let newBlock, oldBlock - newBlock = { number: '0x01' } - pendingTxTracker._checkPendingTxs = done - pendingTxTracker.queryPendingTxs({ oldBlock, newBlock }) - }) - it('should call #_checkPendingTxs if oldBlock and the newBlock have a diff of greater then 1', function (done) { - let newBlock, oldBlock - oldBlock = { number: '0x01' } - newBlock = { number: '0x03' } - pendingTxTracker._checkPendingTxs = done - pendingTxTracker.queryPendingTxs({ oldBlock, newBlock }) - }) - it('should not call #_checkPendingTxs if oldBlock and the newBlock have a diff of 1 or less', function (done) { - let newBlock, oldBlock - oldBlock = { number: '0x1' } - newBlock = { number: '0x2' } - pendingTxTracker._checkPendingTxs = () => { - const err = new Error('should not call #_checkPendingTxs if oldBlock and the newBlock have a diff of 1 or less') - done(err) - } - pendingTxTracker.queryPendingTxs({ oldBlock, newBlock }) - done() - }) - }) - - describe('#_checkPendingTx', function () { - it('should emit \'tx:failed\' if the txMeta does not have a hash', function (done) { - pendingTxTracker.once('tx:failed', (txId, err) => { - assert(txId, txMetaNoHash.id, 'should pass txId') - done() - }) - pendingTxTracker._checkPendingTx(txMetaNoHash) - }) - - it('should should return if query does not return txParams', function () { - providerResultStub.eth_getTransactionByHash = null - pendingTxTracker._checkPendingTx(txMeta) - }) - - it('should emit \'txConfirmed\'', function (done) { - providerResultStub.eth_getTransactionByHash = {blockNumber: '0x01'} - pendingTxTracker.once('tx:confirmed', (txId) => { - assert(txId, txMeta.id, 'should pass txId') - done() - }) - pendingTxTracker.once('tx:failed', (_, err) => { done(err) }) - pendingTxTracker._checkPendingTx(txMeta) - }) - }) - - describe('#_checkPendingTxs', function () { - beforeEach(function () { - const txMeta2 = txMeta3 = txMeta - txMeta2.id = 2 - txMeta3.id = 3 - txList = [txMeta, txMeta2, txMeta3].map((tx) => { - tx.processed = new Promise ((resolve) => { tx.resolve = resolve }) - return tx - }) - }) - - it('should warp all txMeta\'s in #_checkPendingTx', function (done) { - pendingTxTracker.getPendingTransactions = () => txList - pendingTxTracker._checkPendingTx = (tx) => { tx.resolve(tx) } - const list = txList.map - Promise.all(txList.map((tx) => tx.processed)) - .then((txCompletedList) => done()) - .catch(done) - - pendingTxTracker._checkPendingTxs() - }) - }) - - describe('#resubmitPendingTxs', function () { - const blockStub = { number: '0x0' }; - beforeEach(function () { - const txMeta2 = txMeta3 = txMeta - txList = [txMeta, txMeta2, txMeta3].map((tx) => { - tx.processed = new Promise ((resolve) => { tx.resolve = resolve }) - return tx - }) - }) - - it('should return if no pending transactions', function () { - pendingTxTracker.resubmitPendingTxs() - }) - it('should call #_resubmitTx for all pending tx\'s', function (done) { - pendingTxTracker.getPendingTransactions = () => txList - pendingTxTracker._resubmitTx = async (tx) => { tx.resolve(tx) } - Promise.all(txList.map((tx) => tx.processed)) - .then((txCompletedList) => done()) - .catch(done) - pendingTxTracker.resubmitPendingTxs(blockStub) - }) - it('should not emit \'tx:failed\' if the txMeta throws a known txError', function (done) { - knownErrors =[ - // geth - ' Replacement transaction Underpriced ', - ' known transaction', - // parity - 'Gas price too low to replace ', - ' transaction with the sAme hash was already imported', - // other - ' gateway timeout', - ' noncE too low ', - ] - const enoughForAllErrors = txList.concat(txList) - - pendingTxTracker.on('tx:failed', (_, err) => done(err)) - - pendingTxTracker.getPendingTransactions = () => enoughForAllErrors - pendingTxTracker._resubmitTx = async (tx) => { - tx.resolve() - throw new Error(knownErrors.pop()) - } - Promise.all(txList.map((tx) => tx.processed)) - .then((txCompletedList) => done()) - .catch(done) - - pendingTxTracker.resubmitPendingTxs(blockStub) - }) - it('should emit \'tx:warning\' if it encountered a real error', function (done) { - pendingTxTracker.once('tx:warning', (txMeta, err) => { - if (err.message === 'im some real error') { - const matchingTx = txList.find(tx => tx.id === txMeta.id) - matchingTx.resolve() - } else { - done(err) - } - }) - - pendingTxTracker.getPendingTransactions = () => txList - pendingTxTracker._resubmitTx = async (tx) => { throw new TypeError('im some real error') } - Promise.all(txList.map((tx) => tx.processed)) - .then((txCompletedList) => done()) - .catch(done) - - pendingTxTracker.resubmitPendingTxs(blockStub) - }) - }) - describe('#_resubmitTx', function () { - const mockFirstRetryBlockNumber = '0x1' - let txMetaToTestExponentialBackoff - - beforeEach(() => { - pendingTxTracker.getBalance = (address) => { - assert.equal(address, txMeta.txParams.from, 'Should pass the address') - return enoughBalance - } - pendingTxTracker.publishTransaction = async (rawTx) => { - assert.equal(rawTx, txMeta.rawTx, 'Should pass the rawTx') - } - sinon.spy(pendingTxTracker, 'publishTransaction') - - txMetaToTestExponentialBackoff = Object.assign({}, txMeta, { - retryCount: 4, - firstRetryBlockNumber: mockFirstRetryBlockNumber, - }) - }) - - afterEach(() => { - pendingTxTracker.publishTransaction.restore() - }) - - it('should publish the transaction', function (done) { - const enoughBalance = '0x100000' - - // Stubbing out current account state: - // Adding the fake tx: - pendingTxTracker._resubmitTx(txMeta) - .then(() => done()) - .catch((err) => { - assert.ifError(err, 'should not throw an error') - done(err) - }) - - assert.equal(pendingTxTracker.publishTransaction.callCount, 1, 'Should call publish transaction') - }) - - it('should not publish the transaction if the limit of retries has been exceeded', function (done) { - const enoughBalance = '0x100000' - const mockLatestBlockNumber = '0x5' - - pendingTxTracker._resubmitTx(txMetaToTestExponentialBackoff, mockLatestBlockNumber) - .then(() => done()) - .catch((err) => { - assert.ifError(err, 'should not throw an error') - done(err) - }) - - assert.equal(pendingTxTracker.publishTransaction.callCount, 0, 'Should NOT call publish transaction') - }) - - it('should publish the transaction if the number of blocks since last retry exceeds the last set limit', function (done) { - const enoughBalance = '0x100000' - const mockLatestBlockNumber = '0x11' - - pendingTxTracker._resubmitTx(txMetaToTestExponentialBackoff, mockLatestBlockNumber) - .then(() => done()) - .catch((err) => { - assert.ifError(err, 'should not throw an error') - done(err) - }) - - assert.equal(pendingTxTracker.publishTransaction.callCount, 1, 'Should call publish transaction') - }) - }) - - describe('#_checkIfNonceIsTaken', function () { - beforeEach ( function () { - let confirmedTxList = [{ - id: 1, - hash: '0x0593ee121b92e10d63150ad08b4b8f9c7857d1bd160195ee648fb9a0f8d00eeb', - status: 'confirmed', - txParams: { - from: '0x1678a085c290ebd122dc42cba69373b5953b831d', - nonce: '0x1', - value: '0xfffff', - }, - rawTx: '0xf86c808504a817c800827b0d940c62bb85faa3311a998d3aba8098c1235c564966880de0b6b3a7640000802aa08ff665feb887a25d4099e40e11f0fef93ee9608f404bd3f853dd9e84ed3317a6a02ec9d3d1d6e176d4d2593dd760e74ccac753e6a0ea0d00cc9789d0d7ff1f471d', - }, { - id: 2, - hash: '0x0593ee121b92e10d63150ad08b4b8f9c7857d1bd160195ee648fb9a0f8d00eeb', - status: 'confirmed', - txParams: { - from: '0x1678a085c290ebd122dc42cba69373b5953b831d', - nonce: '0x2', - value: '0xfffff', - }, - rawTx: '0xf86c808504a817c800827b0d940c62bb85faa3311a998d3aba8098c1235c564966880de0b6b3a7640000802aa08ff665feb887a25d4099e40e11f0fef93ee9608f404bd3f853dd9e84ed3317a6a02ec9d3d1d6e176d4d2593dd760e74ccac753e6a0ea0d00cc9789d0d7ff1f471d', - }] - pendingTxTracker.getCompletedTransactions = (address) => { - if (!address) throw new Error('unless behavior has changed #_checkIfNonceIsTaken needs a filtered list of transactions to see if the nonce is taken') - return confirmedTxList - } - }) - - it('should return false if nonce has not been taken', function (done) { - pendingTxTracker._checkIfNonceIsTaken({ - txParams: { - from: '0x1678a085c290ebd122dc42cba69373b5953b831d', - nonce: '0x3', - value: '0xfffff', - }, - }) - .then((taken) => { - assert.ok(!taken) - done() - }) - .catch(done) - }) - - it('should return true if nonce has been taken', function (done) { - pendingTxTracker._checkIfNonceIsTaken({ - txParams: { - from: '0x1678a085c290ebd122dc42cba69373b5953b831d', - nonce: '0x2', - value: '0xfffff', - }, - }).then((taken) => { - assert.ok(taken) - done() - }) - .catch(done) - }) - }) -}) diff --git a/test/unit/personal-message-manager-test.js b/test/unit/personal-message-manager-test.js deleted file mode 100644 index ec2f9a4d1..000000000 --- a/test/unit/personal-message-manager-test.js +++ /dev/null @@ -1,107 +0,0 @@ -const assert = require('assert') - -const PersonalMessageManager = require('../../app/scripts/lib/personal-message-manager') - -describe('Personal Message Manager', function () { - let messageManager - - beforeEach(function () { - messageManager = new PersonalMessageManager() - }) - - describe('#getMsgList', function () { - it('when new should return empty array', function () { - var result = messageManager.messages - assert.ok(Array.isArray(result)) - assert.equal(result.length, 0) - }) - it('should also return transactions from local storage if any', function () { - - }) - }) - - describe('#addMsg', function () { - it('adds a Msg returned in getMsgList', function () { - var Msg = { id: 1, status: 'approved', metamaskNetworkId: 'unit test' } - messageManager.addMsg(Msg) - var result = messageManager.messages - assert.ok(Array.isArray(result)) - assert.equal(result.length, 1) - assert.equal(result[0].id, 1) - }) - }) - - describe('#setMsgStatusApproved', function () { - it('sets the Msg status to approved', function () { - var Msg = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test' } - messageManager.addMsg(Msg) - messageManager.setMsgStatusApproved(1) - var result = messageManager.messages - assert.ok(Array.isArray(result)) - assert.equal(result.length, 1) - assert.equal(result[0].status, 'approved') - }) - }) - - describe('#rejectMsg', function () { - it('sets the Msg status to rejected', function () { - var Msg = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test' } - messageManager.addMsg(Msg) - messageManager.rejectMsg(1) - var result = messageManager.messages - assert.ok(Array.isArray(result)) - assert.equal(result.length, 1) - assert.equal(result[0].status, 'rejected') - }) - }) - - describe('#_updateMsg', function () { - it('replaces the Msg with the same id', function () { - messageManager.addMsg({ id: '1', status: 'unapproved', metamaskNetworkId: 'unit test' }) - messageManager.addMsg({ id: '2', status: 'approved', metamaskNetworkId: 'unit test' }) - messageManager._updateMsg({ id: '1', status: 'blah', hash: 'foo', metamaskNetworkId: 'unit test' }) - var result = messageManager.getMsg('1') - assert.equal(result.hash, 'foo') - }) - }) - - describe('#getUnapprovedMsgs', function () { - it('returns unapproved Msgs in a hash', function () { - messageManager.addMsg({ id: '1', status: 'unapproved', metamaskNetworkId: 'unit test' }) - messageManager.addMsg({ id: '2', status: 'approved', metamaskNetworkId: 'unit test' }) - const result = messageManager.getUnapprovedMsgs() - assert.equal(typeof result, 'object') - assert.equal(result['1'].status, 'unapproved') - assert.equal(result['2'], undefined) - }) - }) - - describe('#getMsg', function () { - it('returns a Msg with the requested id', function () { - messageManager.addMsg({ id: '1', status: 'unapproved', metamaskNetworkId: 'unit test' }) - messageManager.addMsg({ id: '2', status: 'approved', metamaskNetworkId: 'unit test' }) - assert.equal(messageManager.getMsg('1').status, 'unapproved') - assert.equal(messageManager.getMsg('2').status, 'approved') - }) - }) - - describe('#normalizeMsgData', function () { - it('converts text to a utf8 hex string', function () { - var input = 'hello' - var output = messageManager.normalizeMsgData(input) - assert.equal(output, '0x68656c6c6f', 'predictably hex encoded') - }) - - it('tolerates a hex prefix', function () { - var input = '0x12' - var output = messageManager.normalizeMsgData(input) - assert.equal(output, '0x12', 'un modified') - }) - - it('tolerates normal hex', function () { - var input = '12' - var output = messageManager.normalizeMsgData(input) - assert.equal(output, '0x12', 'adds prefix') - }) - }) -}) diff --git a/test/unit/preferences-controller-test.js b/test/unit/preferences-controller-test.js deleted file mode 100644 index 9fb5e4251..000000000 --- a/test/unit/preferences-controller-test.js +++ /dev/null @@ -1,48 +0,0 @@ -const assert = require('assert') -const PreferencesController = require('../../app/scripts/controllers/preferences') - -describe('preferences controller', function () { - let preferencesController - - before(() => { - preferencesController = new PreferencesController() - }) - - describe('addToken', function () { - it('should add that token to its state', async function () { - const address = '0xabcdef1234567' - const symbol = 'ABBR' - const decimals = 5 - - await preferencesController.addToken(address, symbol, decimals) - - const tokens = preferencesController.getTokens() - assert.equal(tokens.length, 1, 'one token added') - - const added = tokens[0] - assert.equal(added.address, address, 'set address correctly') - assert.equal(added.symbol, symbol, 'set symbol correctly') - assert.equal(added.decimals, decimals, 'set decimals correctly') - }) - - it('should allow updating a token value', async function () { - const address = '0xabcdef1234567' - const symbol = 'ABBR' - const decimals = 5 - - await preferencesController.addToken(address, symbol, decimals) - - const newDecimals = 6 - await preferencesController.addToken(address, symbol, newDecimals) - - const tokens = preferencesController.getTokens() - assert.equal(tokens.length, 1, 'one token added') - - const added = tokens[0] - assert.equal(added.address, address, 'set address correctly') - assert.equal(added.symbol, symbol, 'set symbol correctly') - assert.equal(added.decimals, newDecimals, 'updated decimals correctly') - }) - }) -}) - diff --git a/test/unit/seed-phrase-verifier-test.js b/test/unit/seed-phrase-verifier-test.js deleted file mode 100644 index 4e314806b..000000000 --- a/test/unit/seed-phrase-verifier-test.js +++ /dev/null @@ -1,133 +0,0 @@ -const assert = require('assert') -const clone = require('clone') -const KeyringController = require('eth-keyring-controller') -const firstTimeState = require('../../app/scripts/first-time-state') -const seedPhraseVerifier = require('../../app/scripts/lib/seed-phrase-verifier') -const mockEncryptor = require('../lib/mock-encryptor') - -describe('SeedPhraseVerifier', function () { - - describe('verifyAccounts', function () { - - let password = 'passw0rd1' - let hdKeyTree = 'HD Key Tree' - - let keyringController - let vault - let primaryKeyring - - beforeEach(async function () { - keyringController = new KeyringController({ - initState: clone(firstTimeState), - encryptor: mockEncryptor, - }) - - assert(keyringController) - - vault = await keyringController.createNewVaultAndKeychain(password) - primaryKeyring = keyringController.getKeyringsByType(hdKeyTree)[0] - }) - - it('should be able to verify created account with seed words', async function () { - - let createdAccounts = await primaryKeyring.getAccounts() - assert.equal(createdAccounts.length, 1) - - let serialized = await primaryKeyring.serialize() - let seedWords = serialized.mnemonic - assert.notEqual(seedWords.length, 0) - - let result = await seedPhraseVerifier.verifyAccounts(createdAccounts, seedWords) - }) - - it('should be able to verify created account (upper case) with seed words', async function () { - - let createdAccounts = await primaryKeyring.getAccounts() - assert.equal(createdAccounts.length, 1) - - let upperCaseAccounts = [createdAccounts[0].toUpperCase()] - - let serialized = await primaryKeyring.serialize() - let seedWords = serialized.mnemonic - assert.notEqual(seedWords.length, 0) - - let result = await seedPhraseVerifier.verifyAccounts(upperCaseAccounts, seedWords) - }) - - it('should be able to verify created account (lower case) with seed words', async function () { - - let createdAccounts = await primaryKeyring.getAccounts() - assert.equal(createdAccounts.length, 1) - let lowerCaseAccounts = [createdAccounts[0].toLowerCase()] - - let serialized = await primaryKeyring.serialize() - let seedWords = serialized.mnemonic - assert.notEqual(seedWords.length, 0) - - let result = await seedPhraseVerifier.verifyAccounts(lowerCaseAccounts, seedWords) - }) - - it('should return error with good but different seed words', async function () { - - let createdAccounts = await primaryKeyring.getAccounts() - assert.equal(createdAccounts.length, 1) - - let serialized = await primaryKeyring.serialize() - let seedWords = 'debris dizzy just program just float decrease vacant alarm reduce speak stadium' - - try { - let result = await seedPhraseVerifier.verifyAccounts(createdAccounts, seedWords) - assert.fail("Should reject") - } catch (err) { - assert.ok(err.message.indexOf('Not identical accounts!') >= 0, 'Wrong error message') - } - }) - - it('should return error with undefined existing accounts', async function () { - - let createdAccounts = await primaryKeyring.getAccounts() - assert.equal(createdAccounts.length, 1) - - let serialized = await primaryKeyring.serialize() - let seedWords = 'debris dizzy just program just float decrease vacant alarm reduce speak stadium' - - try { - let result = await seedPhraseVerifier.verifyAccounts(undefined, seedWords) - assert.fail("Should reject") - } catch (err) { - assert.equal(err.message, 'No created accounts defined.') - } - }) - - it('should return error with empty accounts array', async function () { - - let createdAccounts = await primaryKeyring.getAccounts() - assert.equal(createdAccounts.length, 1) - - let serialized = await primaryKeyring.serialize() - let seedWords = 'debris dizzy just program just float decrease vacant alarm reduce speak stadium' - - try { - let result = await seedPhraseVerifier.verifyAccounts([], seedWords) - assert.fail("Should reject") - } catch (err) { - assert.equal(err.message, 'No created accounts defined.') - } - }) - - it('should be able to verify more than one created account with seed words', async function () { - - const keyState = await keyringController.addNewAccount(primaryKeyring) - const keyState2 = await keyringController.addNewAccount(primaryKeyring) - - let createdAccounts = await primaryKeyring.getAccounts() - assert.equal(createdAccounts.length, 3) - - let serialized = await primaryKeyring.serialize() - let seedWords = serialized.mnemonic - assert.notEqual(seedWords.length, 0) - - let result = await seedPhraseVerifier.verifyAccounts(createdAccounts, seedWords) - }) - }) -}) diff --git a/test/unit/token-rates-controller.js b/test/unit/token-rates-controller.js deleted file mode 100644 index a49547313..000000000 --- a/test/unit/token-rates-controller.js +++ /dev/null @@ -1,29 +0,0 @@ -const assert = require('assert') -const sinon = require('sinon') -const TokenRatesController = require('../../app/scripts/controllers/token-rates') -const ObservableStore = require('obs-store') - -describe('TokenRatesController', () => { - it('should listen for preferences store updates', () => { - const preferences = new ObservableStore({ tokens: [] }) - const controller = new TokenRatesController({ preferences }) - preferences.putState({ tokens: ['foo'] }) - assert.deepEqual(controller._tokens, ['foo']) - }) - - it('should poll on correct interval', async () => { - const stub = sinon.stub(global, 'setInterval') - new TokenRatesController({ interval: 1337 }) // eslint-disable-line no-new - assert.strictEqual(stub.getCall(0).args[1], 1337) - stub.restore() - }) - - it('should fetch each token rate based on address', async () => { - const controller = new TokenRatesController() - controller.isActive = true - controller.fetchExchangeRate = address => address - controller.tokens = [{ address: 'foo' }, { address: 'bar' }] - await controller.updateExchangeRates() - assert.deepEqual(controller.store.getState().contractExchangeRates, { foo: 'foo', bar: 'bar' }) - }) -}) diff --git a/test/unit/tx-controller-test.js b/test/unit/tx-controller-test.js deleted file mode 100644 index ddd921652..000000000 --- a/test/unit/tx-controller-test.js +++ /dev/null @@ -1,463 +0,0 @@ -const assert = require('assert') -const ethUtil = require('ethereumjs-util') -const EthTx = require('ethereumjs-tx') -const EthjsQuery = require('ethjs-query') -const ObservableStore = require('obs-store') -const sinon = require('sinon') -const TransactionController = require('../../app/scripts/controllers/transactions') -const TxGasUtils = require('../../app/scripts/controllers/transactions/tx-gas-utils') -const { createTestProviderTools, getTestAccounts } = require('../stub/provider') - -const noop = () => true -const currentNetworkId = 42 -const otherNetworkId = 36 - - -describe('Transaction Controller', function () { - let txController, provider, providerResultStub, query, fromAccount - - beforeEach(function () { - providerResultStub = { - // 1 gwei - eth_gasPrice: '0x0de0b6b3a7640000', - // by default, all accounts are external accounts (not contracts) - eth_getCode: '0x', - } - provider = createTestProviderTools({ scaffold: providerResultStub }).provider - query = new EthjsQuery(provider) - fromAccount = getTestAccounts()[0] - - txController = new TransactionController({ - provider, - networkStore: new ObservableStore(currentNetworkId), - txHistoryLimit: 10, - blockTracker: { getCurrentBlock: noop, on: noop, once: noop }, - signTransaction: (ethTx) => new Promise((resolve) => { - ethTx.sign(fromAccount.key) - resolve() - }), - }) - txController.nonceTracker.getNonceLock = () => Promise.resolve({ nextNonce: 0, releaseLock: noop }) - }) - - describe('#isNonceTaken', function () { - it('should return true', function (done) { - txController.txStateManager._saveTxList([ - { id: 1, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams: {nonce: 0, from: '0x8ACCE2391C0d510a6C5E5D8f819A678F79B7E675'} }, - { id: 2, status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {nonce: 0, from: '0x8ACCE2391C0d510a6C5E5D8f819A678F79B7E675'} }, - { id: 3, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams: {nonce: 0, from: '0x8ACCE2391C0d510a6C5E5D8f819A678F79B7E675'} }, - ]) - txController.isNonceTaken({txParams: {nonce:0, from:'0x8ACCE2391C0d510a6C5E5D8f819A678F79B7E675'}}) - .then((isNonceTaken) => { - assert(isNonceTaken) - done() - }).catch(done) - - }) - it('should return false', function (done) { - txController.txStateManager._saveTxList([ - { id: 1, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams: {nonce: 0, from: '0x8ACCE2391C0d510a6C5E5D8f819A678F79B7E675'} }, - { id: 2, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams: {nonce: 0, from: '0x8ACCE2391C0d510a6C5E5D8f819A678F79B7E675'} }, - { id: 3, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams: {nonce: 0, from: '0x8ACCE2391C0d510a6C5E5D8f819A678F79B7E675'} }, - ]) - - txController.isNonceTaken({txParams: {nonce:0, from:'0x8ACCE2391C0d510a6C5E5D8f819A678F79B7E675'}}) - .then((isNonceTaken) => { - assert(!isNonceTaken) - done() - }).catch(done) - - }) - }) - - describe('#getState', function () { - it('should return a state object with the right keys and datat types', function () { - const exposedState = txController.getState() - assert('unapprovedTxs' in exposedState, 'state should have the key unapprovedTxs') - assert('selectedAddressTxList' in exposedState, 'state should have the key selectedAddressTxList') - assert(typeof exposedState.unapprovedTxs === 'object', 'should be an object') - assert(Array.isArray(exposedState.selectedAddressTxList), 'should be an array') - }) - }) - - describe('#getUnapprovedTxCount', function () { - it('should return the number of unapproved txs', function () { - txController.txStateManager._saveTxList([ - { id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }, - { id: 2, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }, - { id: 3, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }, - ]) - const unapprovedTxCount = txController.getUnapprovedTxCount() - assert.equal(unapprovedTxCount, 3, 'should be 3') - }) - }) - - describe('#getPendingTxCount', function () { - it('should return the number of pending txs', function () { - txController.txStateManager._saveTxList([ - { id: 1, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams: {} }, - { id: 2, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams: {} }, - { id: 3, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams: {} }, - ]) - const pendingTxCount = txController.getPendingTxCount() - assert.equal(pendingTxCount, 3, 'should be 3') - }) - }) - - describe('#getConfirmedTransactions', function () { - let address - beforeEach(function () { - address = '0xc684832530fcbddae4b4230a47e991ddcec2831d' - const txParams = { - 'from': address, - 'to': '0xc684832530fcbddae4b4230a47e991ddcec2831d', - } - txController.txStateManager._saveTxList([ - {id: 0, status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams}, - {id: 1, status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams}, - {id: 2, status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams}, - {id: 3, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams}, - {id: 4, status: 'rejected', metamaskNetworkId: currentNetworkId, txParams}, - {id: 5, status: 'approved', metamaskNetworkId: currentNetworkId, txParams}, - {id: 6, status: 'signed', metamaskNetworkId: currentNetworkId, txParams}, - {id: 7, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams}, - {id: 8, status: 'failed', metamaskNetworkId: currentNetworkId, txParams}, - ]) - }) - - it('should return the number of confirmed txs', function () { - assert.equal(txController.nonceTracker.getConfirmedTransactions(address).length, 3) - }) - }) - - - describe('#newUnapprovedTransaction', function () { - let stub, txMeta, txParams - beforeEach(function () { - txParams = { - 'from': '0xc684832530fcbddae4b4230a47e991ddcec2831d', - 'to': '0xc684832530fcbddae4b4230a47e991ddcec2831d', - } - txMeta = { - status: 'unapproved', - id: 1, - metamaskNetworkId: currentNetworkId, - txParams, - history: [], - } - txController.txStateManager._saveTxList([txMeta]) - stub = sinon.stub(txController, 'addUnapprovedTransaction').callsFake(() => { - txController.emit('newUnapprovedTx', txMeta) - return Promise.resolve(txController.txStateManager.addTx(txMeta)) - }) - - afterEach(function () { - txController.txStateManager._saveTxList([]) - stub.restore() - }) - }) - - it('should resolve when finished and status is submitted and resolve with the hash', function (done) { - txController.once('newUnapprovedTx', (txMetaFromEmit) => { - setTimeout(() => { - txController.setTxHash(txMetaFromEmit.id, '0x0') - txController.txStateManager.setTxStatusSubmitted(txMetaFromEmit.id) - }, 10) - }) - - txController.newUnapprovedTransaction(txParams) - .then((hash) => { - assert(hash, 'newUnapprovedTransaction needs to return the hash') - done() - }) - .catch(done) - }) - - it('should reject when finished and status is rejected', function (done) { - txController.once('newUnapprovedTx', (txMetaFromEmit) => { - setTimeout(() => { - txController.txStateManager.setTxStatusRejected(txMetaFromEmit.id) - }, 10) - }) - - txController.newUnapprovedTransaction(txParams) - .catch((err) => { - if (err.message === 'MetaMask Tx Signature: User denied transaction signature.') done() - else done(err) - }) - }) - }) - - describe('#addUnapprovedTransaction', function () { - - it('should add an unapproved transaction and return a valid txMeta', function (done) { - txController.addUnapprovedTransaction({ from: '0x1678a085c290ebd122dc42cba69373b5953b831d' }) - .then((txMeta) => { - assert(('id' in txMeta), 'should have a id') - assert(('time' in txMeta), 'should have a time stamp') - assert(('metamaskNetworkId' in txMeta), 'should have a metamaskNetworkId') - assert(('txParams' in txMeta), 'should have a txParams') - assert(('history' in txMeta), 'should have a history') - - const memTxMeta = txController.txStateManager.getTx(txMeta.id) - assert.deepEqual(txMeta, memTxMeta, `txMeta should be stored in txController after adding it\n expected: ${txMeta} \n got: ${memTxMeta}`) - done() - }).catch(done) - }) - - it('should emit newUnapprovedTx event and pass txMeta as the first argument', function (done) { - providerResultStub.eth_gasPrice = '4a817c800' - txController.once('newUnapprovedTx', (txMetaFromEmit) => { - assert(txMetaFromEmit, 'txMeta is falsey') - done() - }) - txController.addUnapprovedTransaction({ from: '0x1678a085c290ebd122dc42cba69373b5953b831d' }) - .catch(done) - }) - - }) - - describe('#addTxGasDefaults', function () { - it('should add the tx defaults if their are none', function (done) { - const txMeta = { - 'txParams': { - 'from': '0xc684832530fcbddae4b4230a47e991ddcec2831d', - 'to': '0xc684832530fcbddae4b4230a47e991ddcec2831d', - }, - } - providerResultStub.eth_gasPrice = '4a817c800' - providerResultStub.eth_getBlockByNumber = { gasLimit: '47b784' } - providerResultStub.eth_estimateGas = '5209' - txController.addTxGasDefaults(txMeta) - .then((txMetaWithDefaults) => { - assert(txMetaWithDefaults.txParams.value, '0x0', 'should have added 0x0 as the value') - assert(txMetaWithDefaults.txParams.gasPrice, 'should have added the gas price') - assert(txMetaWithDefaults.txParams.gas, 'should have added the gas field') - done() - }) - .catch(done) - }) - }) - - describe('#addTx', function () { - it('should emit updates', function (done) { - const txMeta = { - id: '1', - status: 'unapproved', - metamaskNetworkId: currentNetworkId, - txParams: {}, - } - - const eventNames = ['update:badge', '1:unapproved'] - const listeners = [] - eventNames.forEach((eventName) => { - listeners.push(new Promise((resolve) => { - txController.once(eventName, (arg) => { - resolve(arg) - }) - })) - }) - Promise.all(listeners) - .then((returnValues) => { - assert.deepEqual(returnValues.pop(), txMeta, 'last event 1:unapproved should return txMeta') - done() - }) - .catch(done) - txController.addTx(txMeta) - }) - }) - - describe('#approveTransaction', function () { - let txMeta, originalValue - - beforeEach(function () { - originalValue = '0x01' - txMeta = { - id: '1', - status: 'unapproved', - metamaskNetworkId: currentNetworkId, - txParams: { - nonce: originalValue, - gas: originalValue, - gasPrice: originalValue, - }, - } - }) - - - it('does not overwrite set values', function (done) { - this.timeout(15000) - const wrongValue = '0x05' - - txController.addTx(txMeta) - providerResultStub.eth_gasPrice = wrongValue - providerResultStub.eth_estimateGas = '0x5209' - - const signStub = sinon.stub(txController, 'signTransaction').callsFake(() => Promise.resolve()) - - const pubStub = sinon.stub(txController, 'publishTransaction').callsFake(() => { - txController.setTxHash('1', originalValue) - txController.txStateManager.setTxStatusSubmitted('1') - }) - - txController.approveTransaction(txMeta.id).then(() => { - const result = txController.txStateManager.getTx(txMeta.id) - const params = result.txParams - - assert.equal(params.gas, originalValue, 'gas unmodified') - assert.equal(params.gasPrice, originalValue, 'gas price unmodified') - assert.equal(result.hash, originalValue, `hash was set \n got: ${result.hash} \n expected: ${originalValue}`) - signStub.restore() - pubStub.restore() - done() - }).catch(done) - }) - }) - - describe('#sign replay-protected tx', function () { - it('prepares a tx with the chainId set', function (done) { - txController.addTx({ id: '1', status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }, noop) - txController.signTransaction('1').then((rawTx) => { - const ethTx = new EthTx(ethUtil.toBuffer(rawTx)) - assert.equal(ethTx.getChainId(), currentNetworkId) - done() - }).catch(done) - }) - }) - - describe('#updateAndApproveTransaction', function () { - let txMeta - beforeEach(() => { - txMeta = { - id: 1, - status: 'unapproved', - txParams: { - from: fromAccount.address, - to: '0x1678a085c290ebd122dc42cba69373b5953b831d', - gasPrice: '0x77359400', - gas: '0x7b0d', - nonce: '0x4b', - }, - metamaskNetworkId: currentNetworkId, - } - }) - it('should update and approve transactions', async () => { - txController.txStateManager.addTx(txMeta) - const approvalPromise = txController.updateAndApproveTransaction(txMeta) - const tx = txController.txStateManager.getTx(1) - assert.equal(tx.status, 'approved') - await approvalPromise - }) - }) - - describe('#getChainId', function () { - it('returns 0 when the chainId is NaN', function () { - txController.networkStore = new ObservableStore(NaN) - assert.equal(txController.getChainId(), 0) - }) - }) - - describe('#cancelTransaction', function () { - beforeEach(function () { - txController.txStateManager._saveTxList([ - { id: 0, status: 'unapproved', txParams: {}, metamaskNetworkId: currentNetworkId, history: [{}] }, - { id: 1, status: 'rejected', txParams: {}, metamaskNetworkId: currentNetworkId, history: [{}] }, - { id: 2, status: 'approved', txParams: {}, metamaskNetworkId: currentNetworkId, history: [{}] }, - { id: 3, status: 'signed', txParams: {}, metamaskNetworkId: currentNetworkId, history: [{}] }, - { id: 4, status: 'submitted', txParams: {}, metamaskNetworkId: currentNetworkId, history: [{}] }, - { id: 5, status: 'confirmed', txParams: {}, metamaskNetworkId: currentNetworkId, history: [{}] }, - { id: 6, status: 'failed', txParams: {}, metamaskNetworkId: currentNetworkId, history: [{}] }, - ]) - }) - - it('should set the transaction to rejected from unapproved', async function () { - await txController.cancelTransaction(0) - assert.equal(txController.txStateManager.getTx(0).status, 'rejected') - }) - - }) - - describe('#publishTransaction', function () { - let hash, txMeta - beforeEach(function () { - hash = '0x2a5523c6fa98b47b7d9b6c8320179785150b42a16bcff36b398c5062b65657e8' - txMeta = { - id: 1, - status: 'unapproved', - txParams: {}, - metamaskNetworkId: currentNetworkId, - } - providerResultStub.eth_sendRawTransaction = hash - }) - - it('should publish a tx, updates the rawTx when provided a one', async function () { - txController.txStateManager.addTx(txMeta) - await txController.publishTransaction(txMeta.id) - const publishedTx = txController.txStateManager.getTx(1) - assert.equal(publishedTx.hash, hash) - assert.equal(publishedTx.status, 'submitted') - }) - }) - - describe('#retryTransaction', function () { - it('should create a new txMeta with the same txParams as the original one', function (done) { - let txParams = { - nonce: '0x00', - from: '0xB09d8505E1F4EF1CeA089D47094f5DD3464083d4', - to: '0xB09d8505E1F4EF1CeA089D47094f5DD3464083d4', - data: '0x0', - } - txController.txStateManager._saveTxList([ - { id: 1, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams }, - ]) - txController.retryTransaction(1) - .then((txMeta) => { - assert.equal(txMeta.txParams.nonce, txParams.nonce, 'nonce should be the same') - assert.equal(txMeta.txParams.from, txParams.from, 'from should be the same') - assert.equal(txMeta.txParams.to, txParams.to, 'to should be the same') - assert.equal(txMeta.txParams.data, txParams.data, 'data should be the same') - assert.ok(('lastGasPrice' in txMeta), 'should have the key `lastGasPrice`') - assert.equal(txController.txStateManager.getTxList().length, 2) - done() - }).catch(done) - }) - }) - - describe('#_markNonceDuplicatesDropped', function () { - it('should mark all nonce duplicates as dropped without marking the confirmed transaction as dropped', function () { - txController.txStateManager._saveTxList([ - { id: 1, status: 'confirmed', metamaskNetworkId: currentNetworkId, history: [{}], txParams: { nonce: '0x01' } }, - { id: 2, status: 'submitted', metamaskNetworkId: currentNetworkId, history: [{}], txParams: { nonce: '0x01' } }, - { id: 3, status: 'submitted', metamaskNetworkId: currentNetworkId, history: [{}], txParams: { nonce: '0x01' } }, - { id: 4, status: 'submitted', metamaskNetworkId: currentNetworkId, history: [{}], txParams: { nonce: '0x01' } }, - { id: 5, status: 'submitted', metamaskNetworkId: currentNetworkId, history: [{}], txParams: { nonce: '0x01' } }, - { id: 6, status: 'submitted', metamaskNetworkId: currentNetworkId, history: [{}], txParams: { nonce: '0x01' } }, - { id: 7, status: 'submitted', metamaskNetworkId: currentNetworkId, history: [{}], txParams: { nonce: '0x01' } }, - ]) - txController._markNonceDuplicatesDropped(1) - const confirmedTx = txController.txStateManager.getTx(1) - const droppedTxs = txController.txStateManager.getFilteredTxList({ nonce: '0x01', status: 'dropped' }) - assert.equal(confirmedTx.status, 'confirmed', 'the confirmedTx should remain confirmed') - assert.equal(droppedTxs.length, 6, 'their should be 6 dropped txs') - - }) - }) - - describe('#getPendingTransactions', function () { - beforeEach(function () { - txController.txStateManager._saveTxList([ - { id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }, - { id: 2, status: 'rejected', metamaskNetworkId: currentNetworkId, txParams: {} }, - { id: 3, status: 'approved', metamaskNetworkId: currentNetworkId, txParams: {} }, - { id: 4, status: 'signed', metamaskNetworkId: currentNetworkId, txParams: {} }, - { id: 5, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams: {} }, - { id: 6, status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} }, - { id: 7, status: 'failed', metamaskNetworkId: currentNetworkId, txParams: {} }, - ]) - }) - it('should show only submitted transactions as pending transasction', function () { - assert(txController.pendingTxTracker.getPendingTransactions().length, 1) - assert(txController.pendingTxTracker.getPendingTransactions()[0].status, 'submitted') - }) - }) -}) diff --git a/test/unit/tx-gas-util-test.js b/test/unit/tx-gas-util-test.js deleted file mode 100644 index c1d5966da..000000000 --- a/test/unit/tx-gas-util-test.js +++ /dev/null @@ -1,77 +0,0 @@ -const assert = require('assert') -const Transaction = require('ethereumjs-tx') -const BN = require('bn.js') - - -const { hexToBn, bnToHex } = require('../../app/scripts/lib/util') -const TxUtils = require('../../app/scripts/controllers/transactions/tx-gas-utils') - - -describe('txUtils', function () { - let txUtils - - before(function () { - txUtils = new TxUtils(new Proxy({}, { - get: (obj, name) => { - return () => {} - }, - })) - }) - - describe('chain Id', function () { - it('prepares a transaction with the provided chainId', function () { - const txParams = { - to: '0x70ad465e0bab6504002ad58c744ed89c7da38524', - from: '0x69ad465e0bab6504002ad58c744ed89c7da38525', - value: '0x0', - gas: '0x7b0c', - gasPrice: '0x199c82cc00', - data: '0x', - nonce: '0x3', - chainId: 42, - } - const ethTx = new Transaction(txParams) - assert.equal(ethTx.getChainId(), 42, 'chainId is set from tx params') - }) - }) - - describe('addGasBuffer', function () { - it('multiplies by 1.5, when within block gas limit', function () { - // naive estimatedGas: 0x16e360 (1.5 mil) - const inputHex = '0x16e360' - // dummy gas limit: 0x3d4c52 (4 mil) - const blockGasLimitHex = '0x3d4c52' - const output = txUtils.addGasBuffer(inputHex, blockGasLimitHex) - const inputBn = hexToBn(inputHex) - const outputBn = hexToBn(output) - const expectedBn = inputBn.muln(1.5) - assert(outputBn.eq(expectedBn), 'returns 1.5 the input value') - }) - - it('uses original estimatedGas, when above block gas limit', function () { - // naive estimatedGas: 0x16e360 (1.5 mil) - const inputHex = '0x16e360' - // dummy gas limit: 0x0f4240 (1 mil) - const blockGasLimitHex = '0x0f4240' - const output = txUtils.addGasBuffer(inputHex, blockGasLimitHex) - // const inputBn = hexToBn(inputHex) - const outputBn = hexToBn(output) - const expectedBn = hexToBn(inputHex) - assert(outputBn.eq(expectedBn), 'returns the original estimatedGas value') - }) - - it('buffers up to recommend gas limit recommended ceiling', function () { - // naive estimatedGas: 0x16e360 (1.5 mil) - const inputHex = '0x16e360' - // dummy gas limit: 0x1e8480 (2 mil) - const blockGasLimitHex = '0x1e8480' - const blockGasLimitBn = hexToBn(blockGasLimitHex) - const ceilGasLimitBn = blockGasLimitBn.muln(0.9) - const output = txUtils.addGasBuffer(inputHex, blockGasLimitHex) - // const inputBn = hexToBn(inputHex) - // const outputBn = hexToBn(output) - const expectedHex = bnToHex(ceilGasLimitBn) - assert.equal(output, expectedHex, 'returns the gas limit recommended ceiling value') - }) - }) -}) diff --git a/test/unit/tx-helper-test.js b/test/unit/tx-helper-test.js deleted file mode 100644 index cc6543c30..000000000 --- a/test/unit/tx-helper-test.js +++ /dev/null @@ -1,17 +0,0 @@ -const assert = require('assert') -const txHelper = require('../../ui/lib/tx-helper') - -describe('txHelper', function () { - it('always shows the oldest tx first', function () { - const metamaskNetworkId = 1 - const txs = { - a: { metamaskNetworkId, time: 3 }, - b: { metamaskNetworkId, time: 1 }, - c: { metamaskNetworkId, time: 2 }, - } - - const sorted = txHelper(txs, null, null, metamaskNetworkId) - assert.equal(sorted[0].time, 1, 'oldest tx first') - assert.equal(sorted[2].time, 3, 'newest tx last') - }) -}) diff --git a/test/unit/tx-state-history-helper-test.js b/test/unit/tx-state-history-helper-test.js deleted file mode 100644 index 5ad014dbb..000000000 --- a/test/unit/tx-state-history-helper-test.js +++ /dev/null @@ -1,129 +0,0 @@ -const assert = require('assert') -const txStateHistoryHelper = require('../../app/scripts/controllers/transactions/lib/tx-state-history-helper') -const testVault = require('../data/v17-long-history.json') - -describe ('Transaction state history helper', function () { - - describe('#snapshotFromTxMeta', function () { - it('should clone deep', function () { - const input = { - foo: { - bar: { - bam: 'baz' - } - } - } - const output = txStateHistoryHelper.snapshotFromTxMeta(input) - assert('foo' in output, 'has a foo key') - assert('bar' in output.foo, 'has a bar key') - assert('bam' in output.foo.bar, 'has a bar key') - assert.equal(output.foo.bar.bam, 'baz', 'has a baz value') - }) - - it('should remove the history key', function () { - const input = { foo: 'bar', history: 'remembered' } - const output = txStateHistoryHelper.snapshotFromTxMeta(input) - assert(typeof output.history, 'undefined', 'should remove history') - }) - }) - - describe('#migrateFromSnapshotsToDiffs', function () { - it('migrates history to diffs and can recover original values', function () { - testVault.data.TransactionController.transactions.forEach((tx, index) => { - const newHistory = txStateHistoryHelper.migrateFromSnapshotsToDiffs(tx.history) - newHistory.forEach((newEntry, index) => { - if (index === 0) { - assert.equal(Array.isArray(newEntry), false, 'initial history item IS NOT a json patch obj') - } else { - assert.equal(Array.isArray(newEntry), true, 'non-initial history entry IS a json patch obj') - } - const oldEntry = tx.history[index] - const historySubset = newHistory.slice(0, index + 1) - const reconstructedValue = txStateHistoryHelper.replayHistory(historySubset) - assert.deepEqual(oldEntry, reconstructedValue, 'was able to reconstruct old entry from diffs') - }) - }) - }) - }) - - describe('#replayHistory', function () { - it('replaying history does not mutate the original obj', function () { - const initialState = { test: true, message: 'hello', value: 1 } - const diff1 = [{ - "op": "replace", - "path": "/message", - "value": "haay", - }] - const diff2 = [{ - "op": "replace", - "path": "/value", - "value": 2, - }] - const history = [initialState, diff1, diff2] - - const beforeStateSnapshot = JSON.stringify(initialState) - const latestState = txStateHistoryHelper.replayHistory(history) - const afterStateSnapshot = JSON.stringify(initialState) - - assert.notEqual(initialState, latestState, 'initial state is not the same obj as the latest state') - assert.equal(beforeStateSnapshot, afterStateSnapshot, 'initial state is not modified during run') - }) - }) - - describe('#generateHistoryEntry', function () { - - function generateHistoryEntryTest(note) { - - const prevState = { - someValue: 'value 1', - foo: { - bar: { - bam: 'baz' - } - } - } - - const nextState = { - newPropRoot: 'new property - root', - someValue: 'value 2', - foo: { - newPropFirstLevel: 'new property - first level', - bar: { - bam: 'baz' - } - } - } - - const before = new Date().getTime() - const result = txStateHistoryHelper.generateHistoryEntry(prevState, nextState, note) - const after = new Date().getTime() - - assert.ok(Array.isArray(result)) - assert.equal(result.length, 3) - - const expectedEntry1 = { op: 'add', path: '/foo/newPropFirstLevel', value: 'new property - first level' } - assert.equal(result[0].op, expectedEntry1.op) - assert.equal(result[0].path, expectedEntry1.path) - assert.equal(result[0].value, expectedEntry1.value) - assert.equal(result[0].value, expectedEntry1.value) - if (note) - assert.equal(result[0].note, note) - - assert.ok(result[0].timestamp >= before && result[0].timestamp <= after) - - const expectedEntry2 = { op: 'replace', path: '/someValue', value: 'value 2' } - assert.deepEqual(result[1], expectedEntry2) - - const expectedEntry3 = { op: 'add', path: '/newPropRoot', value: 'new property - root' } - assert.deepEqual(result[2], expectedEntry3) - } - - it('should generate history entries', function () { - generateHistoryEntryTest() - }) - - it('should add note to first entry', function () { - generateHistoryEntryTest('custom note') - }) - }) -}) \ No newline at end of file diff --git a/test/unit/tx-state-manager-test.js b/test/unit/tx-state-manager-test.js deleted file mode 100644 index 179542f90..000000000 --- a/test/unit/tx-state-manager-test.js +++ /dev/null @@ -1,291 +0,0 @@ -const assert = require('assert') -const clone = require('clone') -const ObservableStore = require('obs-store') -const TxStateManager = require('../../app/scripts/controllers/transactions/tx-state-manager') -const txStateHistoryHelper = require('../../app/scripts/controllers/transactions/lib/tx-state-history-helper') -const noop = () => true - -describe('TransactionStateManager', function () { - let txStateManager - const currentNetworkId = 42 - const otherNetworkId = 2 - - beforeEach(function () { - txStateManager = new TxStateManager({ - initState: { - transactions: [], - }, - txHistoryLimit: 10, - getNetwork: () => currentNetworkId - }) - }) - - describe('#setTxStatusSigned', function () { - it('sets the tx status to signed', function () { - let tx = { id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} } - txStateManager.addTx(tx, noop) - txStateManager.setTxStatusSigned(1) - let result = txStateManager.getTxList() - assert.ok(Array.isArray(result)) - assert.equal(result.length, 1) - assert.equal(result[0].status, 'signed') - }) - - it('should emit a signed event to signal the exciton of callback', (done) => { - let tx = { id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} } - const noop = function () { - assert(true, 'event listener has been triggered and noop executed') - done() - } - txStateManager.addTx(tx) - txStateManager.on('1:signed', noop) - txStateManager.setTxStatusSigned(1) - - }) - }) - - describe('#setTxStatusRejected', function () { - it('sets the tx status to rejected', function () { - let tx = { id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} } - txStateManager.addTx(tx) - txStateManager.setTxStatusRejected(1) - let result = txStateManager.getTxList() - assert.ok(Array.isArray(result)) - assert.equal(result.length, 1) - assert.equal(result[0].status, 'rejected') - }) - - it('should emit a rejected event to signal the exciton of callback', (done) => { - let tx = { id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} } - txStateManager.addTx(tx) - const noop = function (err, txId) { - assert(true, 'event listener has been triggered and noop executed') - done() - } - txStateManager.on('1:rejected', noop) - txStateManager.setTxStatusRejected(1) - }) - }) - - describe('#getFullTxList', function () { - it('when new should return empty array', function () { - let result = txStateManager.getTxList() - assert.ok(Array.isArray(result)) - assert.equal(result.length, 0) - }) - }) - - describe('#getTxList', function () { - it('when new should return empty array', function () { - let result = txStateManager.getTxList() - assert.ok(Array.isArray(result)) - assert.equal(result.length, 0) - }) - }) - - describe('#addTx', function () { - it('adds a tx returned in getTxList', function () { - let tx = { id: 1, status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} } - txStateManager.addTx(tx, noop) - let result = txStateManager.getTxList() - assert.ok(Array.isArray(result)) - assert.equal(result.length, 1) - assert.equal(result[0].id, 1) - }) - - it('does not override txs from other networks', function () { - let tx = { id: 1, status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} } - let tx2 = { id: 2, status: 'confirmed', metamaskNetworkId: otherNetworkId, txParams: {} } - txStateManager.addTx(tx, noop) - txStateManager.addTx(tx2, noop) - let result = txStateManager.getFullTxList() - let result2 = txStateManager.getTxList() - assert.equal(result.length, 2, 'txs were deleted') - assert.equal(result2.length, 1, 'incorrect number of txs on network.') - }) - - it('cuts off early txs beyond a limit', function () { - const limit = txStateManager.txHistoryLimit - for (let i = 0; i < limit + 1; i++) { - const tx = { id: i, time: new Date(), status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} } - txStateManager.addTx(tx, noop) - } - let result = txStateManager.getTxList() - assert.equal(result.length, limit, `limit of ${limit} txs enforced`) - assert.equal(result[0].id, 1, 'early txs truncted') - }) - - it('cuts off early txs beyond a limit whether or not it is confirmed or rejected', function () { - const limit = txStateManager.txHistoryLimit - for (let i = 0; i < limit + 1; i++) { - const tx = { id: i, time: new Date(), status: 'rejected', metamaskNetworkId: currentNetworkId, txParams: {} } - txStateManager.addTx(tx, noop) - } - let result = txStateManager.getTxList() - assert.equal(result.length, limit, `limit of ${limit} txs enforced`) - assert.equal(result[0].id, 1, 'early txs truncted') - }) - - it('cuts off early txs beyond a limit but does not cut unapproved txs', function () { - let unconfirmedTx = { id: 0, time: new Date(), status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} } - txStateManager.addTx(unconfirmedTx, noop) - const limit = txStateManager.txHistoryLimit - for (let i = 1; i < limit + 1; i++) { - const tx = { id: i, time: new Date(), status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} } - txStateManager.addTx(tx, noop) - } - let result = txStateManager.getTxList() - assert.equal(result.length, limit, `limit of ${limit} txs enforced`) - assert.equal(result[0].id, 0, 'first tx should still be there') - assert.equal(result[0].status, 'unapproved', 'first tx should be unapproved') - assert.equal(result[1].id, 2, 'early txs truncted') - }) - }) - - describe('#updateTx', function () { - it('replaces the tx with the same id', function () { - txStateManager.addTx({ id: '1', status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }, noop) - txStateManager.addTx({ id: '2', status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} }, noop) - const txMeta = txStateManager.getTx('1') - txMeta.hash = 'foo' - txStateManager.updateTx(txMeta) - let result = txStateManager.getTx('1') - assert.equal(result.hash, 'foo') - }) - - it('updates gas price and adds history items', function () { - const originalGasPrice = '0x01' - const desiredGasPrice = '0x02' - - const txMeta = { - id: '1', - status: 'unapproved', - metamaskNetworkId: currentNetworkId, - txParams: { - gasPrice: originalGasPrice, - }, - } - - const updatedMeta = clone(txMeta) - - txStateManager.addTx(txMeta) - const updatedTx = txStateManager.getTx('1') - // verify tx was initialized correctly - assert.equal(updatedTx.history.length, 1, 'one history item (initial)') - assert.equal(Array.isArray(updatedTx.history[0]), false, 'first history item is initial state') - assert.deepEqual(updatedTx.history[0], txStateHistoryHelper.snapshotFromTxMeta(updatedTx), 'first history item is initial state') - // modify value and updateTx - updatedTx.txParams.gasPrice = desiredGasPrice - const before = new Date().getTime() - txStateManager.updateTx(updatedTx) - const after = new Date().getTime() - // check updated value - const result = txStateManager.getTx('1') - assert.equal(result.txParams.gasPrice, desiredGasPrice, 'gas price updated') - // validate history was updated - assert.equal(result.history.length, 2, 'two history items (initial + diff)') - assert.equal(result.history[1].length, 1, 'two history state items (initial + diff)') - - const expectedEntry = { op: 'replace', path: '/txParams/gasPrice', value: desiredGasPrice } - assert.deepEqual(result.history[1][0].op, expectedEntry.op, 'two history items (initial + diff) operation') - assert.deepEqual(result.history[1][0].path, expectedEntry.path, 'two history items (initial + diff) path') - assert.deepEqual(result.history[1][0].value, expectedEntry.value, 'two history items (initial + diff) value') - assert.ok(result.history[1][0].timestamp >= before && result.history[1][0].timestamp <= after) - }) - }) - - describe('#getUnapprovedTxList', function () { - it('returns unapproved txs in a hash', function () { - txStateManager.addTx({ id: '1', status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }, noop) - txStateManager.addTx({ id: '2', status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} }, noop) - const result = txStateManager.getUnapprovedTxList() - assert.equal(typeof result, 'object') - assert.equal(result['1'].status, 'unapproved') - assert.equal(result['2'], undefined) - }) - }) - - describe('#getTx', function () { - it('returns a tx with the requested id', function () { - txStateManager.addTx({ id: '1', status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }, noop) - txStateManager.addTx({ id: '2', status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} }, noop) - assert.equal(txStateManager.getTx('1').status, 'unapproved') - assert.equal(txStateManager.getTx('2').status, 'confirmed') - }) - }) - - describe('#getFilteredTxList', function () { - it('returns a tx with the requested data', function () { - const txMetas = [ - { id: 0, status: 'unapproved', txParams: { from: '0xaa', to: '0xbb' }, metamaskNetworkId: currentNetworkId }, - { id: 1, status: 'unapproved', txParams: { from: '0xaa', to: '0xbb' }, metamaskNetworkId: currentNetworkId }, - { id: 2, status: 'unapproved', txParams: { from: '0xaa', to: '0xbb' }, metamaskNetworkId: currentNetworkId }, - { id: 3, status: 'unapproved', txParams: { from: '0xbb', to: '0xaa' }, metamaskNetworkId: currentNetworkId }, - { id: 4, status: 'unapproved', txParams: { from: '0xbb', to: '0xaa' }, metamaskNetworkId: currentNetworkId }, - { id: 5, status: 'confirmed', txParams: { from: '0xaa', to: '0xbb' }, metamaskNetworkId: currentNetworkId }, - { id: 6, status: 'confirmed', txParams: { from: '0xaa', to: '0xbb' }, metamaskNetworkId: currentNetworkId }, - { id: 7, status: 'confirmed', txParams: { from: '0xbb', to: '0xaa' }, metamaskNetworkId: currentNetworkId }, - { id: 8, status: 'confirmed', txParams: { from: '0xbb', to: '0xaa' }, metamaskNetworkId: currentNetworkId }, - { id: 9, status: 'confirmed', txParams: { from: '0xbb', to: '0xaa' }, metamaskNetworkId: currentNetworkId }, - ] - txMetas.forEach((txMeta) => txStateManager.addTx(txMeta, noop)) - let filterParams - - filterParams = { status: 'unapproved', from: '0xaa' } - assert.equal(txStateManager.getFilteredTxList(filterParams).length, 3, `getFilteredTxList - ${JSON.stringify(filterParams)}`) - filterParams = { status: 'unapproved', to: '0xaa' } - assert.equal(txStateManager.getFilteredTxList(filterParams).length, 2, `getFilteredTxList - ${JSON.stringify(filterParams)}`) - filterParams = { status: 'confirmed', from: '0xbb' } - assert.equal(txStateManager.getFilteredTxList(filterParams).length, 3, `getFilteredTxList - ${JSON.stringify(filterParams)}`) - filterParams = { status: 'confirmed' } - assert.equal(txStateManager.getFilteredTxList(filterParams).length, 5, `getFilteredTxList - ${JSON.stringify(filterParams)}`) - filterParams = { from: '0xaa' } - assert.equal(txStateManager.getFilteredTxList(filterParams).length, 5, `getFilteredTxList - ${JSON.stringify(filterParams)}`) - filterParams = { to: '0xaa' } - assert.equal(txStateManager.getFilteredTxList(filterParams).length, 5, `getFilteredTxList - ${JSON.stringify(filterParams)}`) - }) - }) - - describe('#wipeTransactions', function () { - - const specificAddress = '0xaa' - const otherAddress = '0xbb' - - it('should remove only the transactions from a specific address', function () { - - const txMetas = [ - { id: 0, status: 'unapproved', txParams: { from: specificAddress, to: otherAddress }, metamaskNetworkId: currentNetworkId }, - { id: 1, status: 'confirmed', txParams: { from: otherAddress, to: specificAddress }, metamaskNetworkId: currentNetworkId }, - { id: 2, status: 'confirmed', txParams: { from: otherAddress, to: specificAddress }, metamaskNetworkId: currentNetworkId }, - ] - txMetas.forEach((txMeta) => txStateManager.addTx(txMeta, noop)) - - txStateManager.wipeTransactions(specificAddress) - - const transactionsFromCurrentAddress = txStateManager.getTxList().filter((txMeta) => txMeta.txParams.from === specificAddress) - const transactionsFromOtherAddresses = txStateManager.getTxList().filter((txMeta) => txMeta.txParams.from !== specificAddress) - - assert.equal(transactionsFromCurrentAddress.length, 0) - assert.equal(transactionsFromOtherAddresses.length, 2) - }) - - it('should not remove the transactions from other networks', function () { - const txMetas = [ - { id: 0, status: 'unapproved', txParams: { from: specificAddress, to: otherAddress }, metamaskNetworkId: currentNetworkId }, - { id: 1, status: 'confirmed', txParams: { from: specificAddress, to: otherAddress }, metamaskNetworkId: otherNetworkId }, - { id: 2, status: 'confirmed', txParams: { from: specificAddress, to: otherAddress }, metamaskNetworkId: otherNetworkId }, - ] - - txMetas.forEach((txMeta) => txStateManager.addTx(txMeta, noop)) - - txStateManager.wipeTransactions(specificAddress) - - const txsFromCurrentNetworkAndAddress = txStateManager.getTxList().filter((txMeta) => txMeta.txParams.from === specificAddress) - const txFromOtherNetworks = txStateManager.getFullTxList().filter((txMeta) => txMeta.metamaskNetworkId === otherNetworkId) - - assert.equal(txsFromCurrentNetworkAndAddress.length, 0) - assert.equal(txFromOtherNetworks.length, 2) - - }) - }) -}) diff --git a/test/unit/tx-utils-test.js b/test/unit/tx-utils-test.js deleted file mode 100644 index be16225ba..000000000 --- a/test/unit/tx-utils-test.js +++ /dev/null @@ -1,98 +0,0 @@ -const assert = require('assert') -const txUtils = require('../../app/scripts/controllers/transactions/lib/util') - - -describe('txUtils', function () { - describe('#validateTxParams', function () { - it('does not throw for positive values', function () { - var sample = { - from: '0x1678a085c290ebd122dc42cba69373b5953b831d', - value: '0x01', - } - txUtils.validateTxParams(sample) - }) - - it('returns error for negative values', function () { - var sample = { - from: '0x1678a085c290ebd122dc42cba69373b5953b831d', - value: '-0x01', - } - try { - txUtils.validateTxParams(sample) - } catch (err) { - assert.ok(err, 'error') - } - }) - }) - - describe('#normalizeTxParams', () => { - it('should normalize txParams', () => { - let txParams = { - chainId: '0x1', - from: 'a7df1beDBF813f57096dF77FCd515f0B3900e402', - to: null, - data: '68656c6c6f20776f726c64', - random: 'hello world', - } - - let normalizedTxParams = txUtils.normalizeTxParams(txParams) - - assert(!normalizedTxParams.chainId, 'their should be no chainId') - assert(!normalizedTxParams.to, 'their should be no to address if null') - assert.equal(normalizedTxParams.from.slice(0, 2), '0x', 'from should be hexPrefixd') - assert.equal(normalizedTxParams.data.slice(0, 2), '0x', 'data should be hexPrefixd') - assert(!('random' in normalizedTxParams), 'their should be no random key in normalizedTxParams') - - txParams.to = 'a7df1beDBF813f57096dF77FCd515f0B3900e402' - normalizedTxParams = txUtils.normalizeTxParams(txParams) - assert.equal(normalizedTxParams.to.slice(0, 2), '0x', 'to should be hexPrefixd') - - }) - }) - - describe('#validateRecipient', () => { - it('removes recipient for txParams with 0x when contract data is provided', function () { - const zeroRecipientandDataTxParams = { - from: '0x1678a085c290ebd122dc42cba69373b5953b831d', - to: '0x', - data: 'bytecode', - } - const sanitizedTxParams = txUtils.validateRecipient(zeroRecipientandDataTxParams) - assert.deepEqual(sanitizedTxParams, { from: '0x1678a085c290ebd122dc42cba69373b5953b831d', data: 'bytecode' }, 'no recipient with 0x') - }) - - it('should error when recipient is 0x', function () { - const zeroRecipientTxParams = { - from: '0x1678a085c290ebd122dc42cba69373b5953b831d', - to: '0x', - } - assert.throws(() => { txUtils.validateRecipient(zeroRecipientTxParams) }, Error, 'Invalid recipient address') - }) - }) - - - describe('#validateFrom', () => { - it('should error when from is not a hex string', function () { - - // where from is undefined - const txParams = {} - assert.throws(() => { txUtils.validateFrom(txParams) }, Error, `Invalid from address ${txParams.from} not a string`) - - // where from is array - txParams.from = [] - assert.throws(() => { txUtils.validateFrom(txParams) }, Error, `Invalid from address ${txParams.from} not a string`) - - // where from is a object - txParams.from = {} - assert.throws(() => { txUtils.validateFrom(txParams) }, Error, `Invalid from address ${txParams.from} not a string`) - - // where from is a invalid address - txParams.from = 'im going to fail' - assert.throws(() => { txUtils.validateFrom(txParams) }, Error, `Invalid from address`) - - // should run - txParams.from ='0x1678a085c290ebd122dc42cba69373b5953b831d' - txUtils.validateFrom(txParams) - }) - }) -}) diff --git a/test/unit/util-test.js b/test/unit/util-test.js deleted file mode 100644 index 6da185b2c..000000000 --- a/test/unit/util-test.js +++ /dev/null @@ -1,41 +0,0 @@ -const assert = require('assert') -const { sufficientBalance } = require('../../app/scripts/lib/util') - - -describe('SufficientBalance', function () { - it('returns true if max tx cost is equal to balance.', function () { - const tx = { - 'value': '0x1', - 'gas': '0x2', - 'gasPrice': '0x3', - } - const balance = '0x8' - - const result = sufficientBalance(tx, balance) - assert.ok(result, 'sufficient balance found.') - }) - - it('returns true if max tx cost is less than balance.', function () { - const tx = { - 'value': '0x1', - 'gas': '0x2', - 'gasPrice': '0x3', - } - const balance = '0x9' - - const result = sufficientBalance(tx, balance) - assert.ok(result, 'sufficient balance found.') - }) - - it('returns false if max tx cost is more than balance.', function () { - const tx = { - 'value': '0x1', - 'gas': '0x2', - 'gasPrice': '0x3', - } - const balance = '0x6' - - const result = sufficientBalance(tx, balance) - assert.ok(!result, 'insufficient balance found.') - }) -}) \ No newline at end of file -- cgit v1.2.3 From e8b2e11c5624d80f535c1344d9c9be48627b1319 Mon Sep 17 00:00:00 2001 From: Frankie Date: Mon, 21 May 2018 16:00:44 -0700 Subject: Reveal get filtered tx list (#4332) * add getFilteredTxList from txController to getApi * transactions - remove dead code (isNonceTaken) --- test/unit/tx-controller-test.js | 30 ------------------------------ 1 file changed, 30 deletions(-) (limited to 'test') diff --git a/test/unit/tx-controller-test.js b/test/unit/tx-controller-test.js index ddd921652..0b5c7226a 100644 --- a/test/unit/tx-controller-test.js +++ b/test/unit/tx-controller-test.js @@ -40,36 +40,6 @@ describe('Transaction Controller', function () { txController.nonceTracker.getNonceLock = () => Promise.resolve({ nextNonce: 0, releaseLock: noop }) }) - describe('#isNonceTaken', function () { - it('should return true', function (done) { - txController.txStateManager._saveTxList([ - { id: 1, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams: {nonce: 0, from: '0x8ACCE2391C0d510a6C5E5D8f819A678F79B7E675'} }, - { id: 2, status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {nonce: 0, from: '0x8ACCE2391C0d510a6C5E5D8f819A678F79B7E675'} }, - { id: 3, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams: {nonce: 0, from: '0x8ACCE2391C0d510a6C5E5D8f819A678F79B7E675'} }, - ]) - txController.isNonceTaken({txParams: {nonce:0, from:'0x8ACCE2391C0d510a6C5E5D8f819A678F79B7E675'}}) - .then((isNonceTaken) => { - assert(isNonceTaken) - done() - }).catch(done) - - }) - it('should return false', function (done) { - txController.txStateManager._saveTxList([ - { id: 1, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams: {nonce: 0, from: '0x8ACCE2391C0d510a6C5E5D8f819A678F79B7E675'} }, - { id: 2, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams: {nonce: 0, from: '0x8ACCE2391C0d510a6C5E5D8f819A678F79B7E675'} }, - { id: 3, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams: {nonce: 0, from: '0x8ACCE2391C0d510a6C5E5D8f819A678F79B7E675'} }, - ]) - - txController.isNonceTaken({txParams: {nonce:0, from:'0x8ACCE2391C0d510a6C5E5D8f819A678F79B7E675'}}) - .then((isNonceTaken) => { - assert(!isNonceTaken) - done() - }).catch(done) - - }) - }) - describe('#getState', function () { it('should return a state object with the right keys and datat types', function () { const exposedState = txController.getState() -- cgit v1.2.3 From 9c6e2ec8ef4944172f6e771b216beaf00ed21575 Mon Sep 17 00:00:00 2001 From: kumavis Date: Mon, 21 May 2018 16:26:38 -0700 Subject: test - e2e - check for console errors after each test --- test/e2e/metamask.spec.js | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) (limited to 'test') diff --git a/test/e2e/metamask.spec.js b/test/e2e/metamask.spec.js index 707ca2560..fac560176 100644 --- a/test/e2e/metamask.spec.js +++ b/test/e2e/metamask.spec.js @@ -30,6 +30,14 @@ describe('Metamask popup page', function () { }) afterEach(async function () { + // check for console errors + const errors = await checkBrowserForConsoleErrors() + if (errors.length) { + const errorReports = errors.map(err => err.message) + const errorMessage = `Errors found in browser console:\n${errorReports.join('\n')}` + this.test.error(new Error(errorMessage)) + } + // gather extra data if test failed if (this.currentTest.state === 'failed') { await verboseReportOnFailure(this.currentTest) } @@ -300,13 +308,21 @@ describe('Metamask popup page', function () { await driver.executeScript('window.metamask.setProviderType(arguments[0])', type) } + async function checkBrowserForConsoleErrors() { + const ignoredLogTypes = ['WARNING'] + const browserLogs = await driver.manage().logs().get('browser') + const errorEntries = browserLogs.filter(entry => !ignoredLogTypes.includes(entry.level.toString())) + const errorEntryObjects = errorEntries.map(entry => entry.toJSON()) + return errorEntryObjects + } + async function verboseReportOnFailure (test) { let artifactDir if (process.env.SELENIUM_BROWSER === 'chrome') { artifactDir = `./test-artifacts/chrome/${test.title}` } else if (process.env.SELENIUM_BROWSER === 'firefox') { artifactDir = `./test-artifacts/firefox/${test.title}` - } + } const filepathBase = `${artifactDir}/test-failure` await pify(mkdirp)(artifactDir) // capture screenshot -- cgit v1.2.3 From db08d8d43eef26005c9c022445f4530caba71ac3 Mon Sep 17 00:00:00 2001 From: kumavis Date: Mon, 21 May 2018 17:15:05 -0700 Subject: test - e2e - ignore react 'unknown props dataset' warnings --- test/e2e/metamask.spec.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'test') diff --git a/test/e2e/metamask.spec.js b/test/e2e/metamask.spec.js index fac560176..72861adc4 100644 --- a/test/e2e/metamask.spec.js +++ b/test/e2e/metamask.spec.js @@ -310,10 +310,13 @@ describe('Metamask popup page', function () { async function checkBrowserForConsoleErrors() { const ignoredLogTypes = ['WARNING'] + const ignoredErrorMessages = ['Warning: Unknown prop `dataset` on '] const browserLogs = await driver.manage().logs().get('browser') const errorEntries = browserLogs.filter(entry => !ignoredLogTypes.includes(entry.level.toString())) - const errorEntryObjects = errorEntries.map(entry => entry.toJSON()) - return errorEntryObjects + const errorObjects = errorEntries.map(entry => entry.toJSON()) + // ignore all errors that contain a message in `ignoredErrorMessages` + const matchedErrorObjects = errorObjects.filter(entry => !ignoredErrorMessages.some(message => entry.message.includes(message))) + return matchedErrorObjects } async function verboseReportOnFailure (test) { -- cgit v1.2.3 From 81ff04ec279c5642a5e0a8dbc45724d2b8774850 Mon Sep 17 00:00:00 2001 From: kumavis Date: Mon, 21 May 2018 18:00:22 -0700 Subject: test - e2e - ignore favicon 404s --- test/e2e/metamask.spec.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'test') diff --git a/test/e2e/metamask.spec.js b/test/e2e/metamask.spec.js index 72861adc4..4ce51a91a 100644 --- a/test/e2e/metamask.spec.js +++ b/test/e2e/metamask.spec.js @@ -310,7 +310,12 @@ describe('Metamask popup page', function () { async function checkBrowserForConsoleErrors() { const ignoredLogTypes = ['WARNING'] - const ignoredErrorMessages = ['Warning: Unknown prop `dataset` on '] + const ignoredErrorMessages = [ + // React throws error warnings on "dataset", but still sets the data-* properties correctly + 'Warning: Unknown prop `dataset` on ', + // Third-party Favicon 404s show up as errors + 'favicon.ico - Failed to load resource: the server responded with a status of 404 (Not Found)', + ] const browserLogs = await driver.manage().logs().get('browser') const errorEntries = browserLogs.filter(entry => !ignoredLogTypes.includes(entry.level.toString())) const errorObjects = errorEntries.map(entry => entry.toJSON()) -- cgit v1.2.3 From 4856c0803fb512373107453c60781d3f0d5813b3 Mon Sep 17 00:00:00 2001 From: kumavis Date: Mon, 21 May 2018 19:57:13 -0700 Subject: test - e2e - ignore 'development build of React' warning --- test/e2e/metamask.spec.js | 2 ++ 1 file changed, 2 insertions(+) (limited to 'test') diff --git a/test/e2e/metamask.spec.js b/test/e2e/metamask.spec.js index 4ce51a91a..e966517db 100644 --- a/test/e2e/metamask.spec.js +++ b/test/e2e/metamask.spec.js @@ -315,6 +315,8 @@ describe('Metamask popup page', function () { 'Warning: Unknown prop `dataset` on ', // Third-party Favicon 404s show up as errors 'favicon.ico - Failed to load resource: the server responded with a status of 404 (Not Found)', + // React Minified build - known issue blocked by test build sys + 'Warning: It looks like you\'re using a minified copy of the development build of React.', ] const browserLogs = await driver.manage().logs().get('browser') const errorEntries = browserLogs.filter(entry => !ignoredLogTypes.includes(entry.level.toString())) -- cgit v1.2.3 From a3ca64d3210077cb1da67cfd105e8e22e978ff49 Mon Sep 17 00:00:00 2001 From: kumavis Date: Mon, 21 May 2018 19:59:21 -0700 Subject: test - e2e - ignore 'development build of Redux' warning --- test/e2e/metamask.spec.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'test') diff --git a/test/e2e/metamask.spec.js b/test/e2e/metamask.spec.js index e966517db..482259ee8 100644 --- a/test/e2e/metamask.spec.js +++ b/test/e2e/metamask.spec.js @@ -315,8 +315,10 @@ describe('Metamask popup page', function () { 'Warning: Unknown prop `dataset` on ', // Third-party Favicon 404s show up as errors 'favicon.ico - Failed to load resource: the server responded with a status of 404 (Not Found)', - // React Minified build - known issue blocked by test build sys + // React Development build - known issue blocked by test build sys 'Warning: It looks like you\'re using a minified copy of the development build of React.', + // Redux Development build - known issue blocked by test build sys + 'This means that you are running a slower development build of Redux.', ] const browserLogs = await driver.manage().logs().get('browser') const errorEntries = browserLogs.filter(entry => !ignoredLogTypes.includes(entry.level.toString())) -- cgit v1.2.3 From 83782421bb16f809e3679d51e598b0348f17cf3b Mon Sep 17 00:00:00 2001 From: kumavis Date: Mon, 21 May 2018 20:17:46 -0700 Subject: test - e2e - only inspect console for errors in chrome --- test/e2e/metamask.spec.js | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) (limited to 'test') diff --git a/test/e2e/metamask.spec.js b/test/e2e/metamask.spec.js index 482259ee8..8ec7de16c 100644 --- a/test/e2e/metamask.spec.js +++ b/test/e2e/metamask.spec.js @@ -30,12 +30,16 @@ describe('Metamask popup page', function () { }) afterEach(async function () { - // check for console errors - const errors = await checkBrowserForConsoleErrors() - if (errors.length) { - const errorReports = errors.map(err => err.message) - const errorMessage = `Errors found in browser console:\n${errorReports.join('\n')}` - this.test.error(new Error(errorMessage)) + // logs command not supported in firefox + // https://github.com/SeleniumHQ/selenium/issues/2910 + if (process.env.SELENIUM_BROWSER === 'chrome') { + // check for console errors + const errors = await checkBrowserForConsoleErrors() + if (errors.length) { + const errorReports = errors.map(err => err.message) + const errorMessage = `Errors found in browser console:\n${errorReports.join('\n')}` + this.test.error(new Error(errorMessage)) + } } // gather extra data if test failed if (this.currentTest.state === 'failed') { -- cgit v1.2.3 From 1fbf59cdd3db916d7753ffe058beee789dbe6220 Mon Sep 17 00:00:00 2001 From: kumavis Date: Tue, 22 May 2018 01:56:52 -0700 Subject: ci - screens - continue screenshots through account creation + export account --- test/screens/new-ui.js | 225 ++++++++++++++++++++++++++++--------------------- 1 file changed, 130 insertions(+), 95 deletions(-) (limited to 'test') diff --git a/test/screens/new-ui.js b/test/screens/new-ui.js index 6a8822eb3..6b873ac85 100644 --- a/test/screens/new-ui.js +++ b/test/screens/new-ui.js @@ -5,29 +5,44 @@ const mkdirp = require('mkdirp') const rimraf = require('rimraf') const webdriver = require('selenium-webdriver') const endOfStream = require('end-of-stream') +const clipboardy = require('clipboardy') +const Ethjs = require('ethjs') const GIFEncoder = require('gifencoder') const pngFileStream = require('png-file-stream') const sizeOfPng = require('image-size/lib/types/png') const By = webdriver.By const { delay, buildWebDriver } = require('./func') const localesIndex = require('../../app/_locales/index.json') +// const localesIndex = [] + +const eth = new Ethjs(new Ethjs.HttpProvider('http://localhost:8545')) let driver +let screenshotCount = 0 + +captureAllScreens() +.then(async () => { + // build screenshots into gif + console.log('building gif...') + await generateGif() -captureAllScreens().catch((err) => { + await driver.quit() + process.exit() +}) +.catch(async (err) => { try { console.error(err) - verboseReportOnFailure() - driver.quit() + verboseReportOnFailure({ title: 'something broke' }) } catch (err) { console.error(err) } + + await driver.quit() process.exit(1) }) -async function captureAllScreens() { - let screenshotCount = 0 +async function captureAllScreens() { // common names let button let tabs @@ -74,10 +89,11 @@ async function captureAllScreens() { await driver.findElement(By.css('button')).click() await captureLanguageScreenShots('create password') + const password = '123456789' const passwordBox = await driver.findElement(By.css('input#create-password')) const passwordBoxConfirm = await driver.findElement(By.css('input#confirm-password')) - passwordBox.sendKeys('123456789') - passwordBoxConfirm.sendKeys('123456789') + passwordBox.sendKeys(password) + passwordBoxConfirm.sendKeys(password) await delay(500) await captureLanguageScreenShots('choose-password-filled') @@ -111,109 +127,123 @@ async function captureAllScreens() { await delay(300) await captureLanguageScreenShots('secret backup phrase - reveal') + const seedPhrase = await driver.findElement(By.css('.backup-phrase__secret-words')).getText() + const seedPhraseWords = seedPhrase.split(' ') await driver.findElement(By.css('button')).click() await delay(300) await captureLanguageScreenShots('confirm secret backup phrase') - // finish up - console.log('building gif...') - await generateGif() - await driver.quit() - return - - // - // await button.click() - // await delay(700) - // this.seedPhase = await driver.findElement(By.css('.twelve-word-phrase')).getText() - // await captureScreenShot('seed phrase') - // - // const continueAfterSeedPhrase = await driver.findElement(By.css('button')) - // await continueAfterSeedPhrase.click() - // await delay(300) - // await captureScreenShot('main screen') - // - // await driver.findElement(By.css('.sandwich-expando')).click() - // await delay(500) - // await captureScreenShot('menu') - - // await driver.findElement(By.css('#app-content > div > div:nth-child(3) > span > div > li:nth-child(3)')).click() - // await captureScreenShot('main screen') - // it('should accept account password after lock', async () => { - // await delay(500) - // await driver.findElement(By.id('password-box')).sendKeys('123456789') - // await driver.findElement(By.css('button')).click() - // await delay(500) - // }) - // - // it('should show QR code option', async () => { - // await delay(300) - // await driver.findElement(By.css('.fa-ellipsis-h')).click() - // await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div > div:nth-child(1) > flex-column > div.name-label > div > span > i > div > div > li:nth-child(3)')).click() - // await delay(300) - // }) - // - // it('should show the account address', async () => { - // this.accountAddress = await driver.findElement(By.css('.ellip-address')).getText() - // await driver.findElement(By.css('.fa-arrow-left')).click() - // await delay(500) - // }) - - async function captureLanguageScreenShots(label) { - const nonEnglishLocales = localesIndex.filter(localeMeta => localeMeta.code !== 'en') - // take english shot - await captureScreenShot(`${label} (en)`) - for (let localeMeta of nonEnglishLocales) { - // set locale and take shot - await setLocale(localeMeta.code) - await delay(300) - await captureScreenShot(`${label} (${localeMeta.code})`) - } - // return locale to english - await setLocale('en') - await delay(300) + // enter seed phrase + const seedPhraseButtons = await driver.findElements(By.css('.backup-phrase__confirm-seed-options > button')) + const seedPhraseButtonWords = await Promise.all(seedPhraseButtons.map(button => button.getText())) + for (let targetWord of seedPhraseWords) { + const wordIndex = seedPhraseButtonWords.indexOf(targetWord) + if (wordIndex === -1) throw new Error(`Captured seed phrase word "${targetWord}" not in found seed phrase button options ${seedPhraseButtonWords.join(' ')}`) + await driver.findElement(By.css(`.backup-phrase__confirm-seed-options > button:nth-child(${wordIndex+1})`)).click() + await delay(100) } + await captureLanguageScreenShots('confirm secret backup phrase - words selected correctly') - async function setLocale(code) { - await driver.executeScript('window.metamask.updateCurrentLocale(arguments[0])', code) - } + await driver.findElement(By.css('.backup-phrase__content-wrapper .first-time-flow__button')).click() + await delay(300) + await captureLanguageScreenShots('metamask post-initialize greeter screen deposit ether') - async function setProviderType(type) { - await driver.executeScript('window.metamask.setProviderType(arguments[0])', type) - } + await driver.findElement(By.css('.page-container__header-close')).click() + await delay(300) + await captureLanguageScreenShots('metamask account main screen') - // cleanup - await driver.quit() + // account details + export private key + await driver.findElement(By.css('.wallet-view__name-container > .wallet-view__details-button')).click() + await delay(300) + await captureLanguageScreenShots('metamask account detail screen') - async function cleanScreenShotDir() { - await pify(rimraf)(`./test-artifacts/screens/`) - } + await driver.findElement(By.css('.account-modal__button:nth-of-type(2)')).click() + await delay(300) + await captureLanguageScreenShots('metamask account detail export private key screen - initial') - async function captureScreenShot(label) { - const shotIndex = screenshotCount.toString().padStart(4, '0') - screenshotCount++ - const artifactDir = `./test-artifacts/screens/` - await pify(mkdirp)(artifactDir) - // capture screenshot - const screenshot = await driver.takeScreenshot() - await pify(fs.writeFile)(`${artifactDir}/${shotIndex} - ${label}.png`, screenshot, { encoding: 'base64' }) - } + await driver.findElement(By.css('.private-key-password > input')).sendKeys(password) + await delay(300) + await captureLanguageScreenShots('metamask account detail export private key screen - password entered') - async function generateGif(){ - // calculate screenshot size - const screenshot = await driver.takeScreenshot() - const pngBuffer = Buffer.from(screenshot, 'base64') - const size = sizeOfPng.calculate(pngBuffer) + await driver.findElement(By.css('.btn-primary--lg.export-private-key__button')).click() + await delay(300) + await captureLanguageScreenShots('metamask account detail export private key screen - reveal key') - // read only the english pngs into gif - const encoder = new GIFEncoder(size.width, size.height) - const stream = pngFileStream('./test-artifacts/screens/* (en).png') - .pipe(encoder.createWriteStream({ repeat: 0, delay: 1000, quality: 10 })) - .pipe(fs.createWriteStream('./test-artifacts/screens/walkthrough (en).gif')) + await driver.findElement(By.css('.export-private-key__button')).click() + await delay(300) + await captureLanguageScreenShots('metamask account detail export private key screen - done') - // wait for end - await pify(endOfStream)(stream) + // get eth from Ganache + // const viewAddressButton = await driver.findElement(By.css('.wallet-view__address')) + // await driver.actions({ bridge: true }).move({ origin: viewAddressButton }).perform() + // console.log('driver.actions', driver.actions({ bridge: true })) + // await delay(300) + // await captureLanguageScreenShots('metamask home - hover copy address') + + await driver.findElement(By.css('.wallet-view__address')).click() + await delay(100) + await captureLanguageScreenShots('metamask home - hover copy address') + + const primaryAddress = clipboardy.readSync() + await requestEther(primaryAddress) + // wait for block polling + await delay(10000) + await captureLanguageScreenShots('metamask home - has ether') + +} + + +async function captureLanguageScreenShots(label) { + const nonEnglishLocales = localesIndex.filter(localeMeta => localeMeta.code !== 'en') + // take english shot + await captureScreenShot(`${label} (en)`) + for (let localeMeta of nonEnglishLocales) { + // set locale and take shot + await setLocale(localeMeta.code) + await delay(300) + await captureScreenShot(`${label} (${localeMeta.code})`) } + // return locale to english + await setLocale('en') + await delay(300) +} + +async function setLocale(code) { + await driver.executeScript('window.metamask.updateCurrentLocale(arguments[0])', code) +} + +async function setProviderType(type) { + await driver.executeScript('window.metamask.setProviderType(arguments[0])', type) +} +async function cleanScreenShotDir() { + await pify(rimraf)(`./test-artifacts/screens/`) +} + +async function captureScreenShot(label) { + const shotIndex = screenshotCount.toString().padStart(4, '0') + screenshotCount++ + const artifactDir = `./test-artifacts/screens/` + await pify(mkdirp)(artifactDir) + // capture screenshot + const screenshot = await driver.takeScreenshot() + await pify(fs.writeFile)(`${artifactDir}/${shotIndex} - ${label}.png`, screenshot, { encoding: 'base64' }) +} + +async function generateGif(){ + // calculate screenshot size + const screenshot = await driver.takeScreenshot() + const pngBuffer = Buffer.from(screenshot, 'base64') + const size = sizeOfPng.calculate(pngBuffer) + + // read only the english pngs into gif + const encoder = new GIFEncoder(size.width, size.height) + const stream = pngFileStream('./test-artifacts/screens/* (en).png') + .pipe(encoder.createWriteStream({ repeat: 0, delay: 1000, quality: 10 })) + .pipe(fs.createWriteStream('./test-artifacts/screens/walkthrough (en).gif')) + + // wait for end + await pify(endOfStream)(stream) } async function verboseReportOnFailure(test) { @@ -227,3 +257,8 @@ async function verboseReportOnFailure(test) { const htmlSource = await driver.getPageSource() await pify(fs.writeFile)(`${filepathBase}-dom.html`, htmlSource) } + +async function requestEther(address) { + const accounts = await eth.accounts() + await eth.sendTransaction({ from: accounts[0], to: address, value: 1 * 1e18, data: '0x0' }) +} -- cgit v1.2.3