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/unit') 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 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/unit') 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 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/unit') 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 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/unit') 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 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/unit') 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 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/unit') 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 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/unit') 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 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/unit') 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 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/unit') 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 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/unit') 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 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/unit') 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 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/unit') 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 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/unit') 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