diff options
author | kumavis <kumavis@users.noreply.github.com> | 2018-02-28 03:25:29 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-02-28 03:25:29 +0800 |
commit | 3fefccd37219c9b4b513fc8d929723e07022b9c4 (patch) | |
tree | 22865ecd672570a6162ac3c9402ec9d63ad3f7ef /test/unit | |
parent | 6a7ea00cd34f83b257f6b4280a5f4e20aa5d34ee (diff) | |
parent | ced62ac551a095c8f94f550f0c01a9d4fd04ce5b (diff) | |
download | tangerine-wallet-browser-3fefccd37219c9b4b513fc8d929723e07022b9c4.tar.gz tangerine-wallet-browser-3fefccd37219c9b4b513fc8d929723e07022b9c4.tar.zst tangerine-wallet-browser-3fefccd37219c9b4b513fc8d929723e07022b9c4.zip |
Merge branch 'master' into mascara-deploy
Diffstat (limited to 'test/unit')
46 files changed, 2613 insertions, 907 deletions
diff --git a/test/unit/account-link-test.js b/test/unit/account-link-test.js deleted file mode 100644 index 803a70f37..000000000 --- a/test/unit/account-link-test.js +++ /dev/null @@ -1,18 +0,0 @@ -var assert = require('assert') -var linkGen = require('../../ui/lib/account-link') - -describe('account-link', function() { - - it('adds ropsten prefix to ropsten test network', function() { - var result = linkGen('account', '3') - assert.notEqual(result.indexOf('ropsten'), -1, 'ropsten included') - assert.notEqual(result.indexOf('account'), -1, 'account included') - }) - - it('adds kovan prefix to kovan test network', function() { - var result = linkGen('account', '42') - assert.notEqual(result.indexOf('kovan'), -1, 'kovan included') - assert.notEqual(result.indexOf('account'), -1, 'account included') - }) - -}) diff --git a/test/unit/actions/config_test.js b/test/unit/actions/config_test.js index 14198fa8a..648f456fb 100644 --- a/test/unit/actions/config_test.js +++ b/test/unit/actions/config_test.js @@ -1,36 +1,34 @@ -var jsdom = require('mocha-jsdom') +// var jsdom = require('mocha-jsdom') var assert = require('assert') var freeze = require('deep-freeze-strict') var path = require('path') var actions = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'actions.js')) -var reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'reducers.js')) - -describe ('config view actions', function() { +var reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'reducers.js')) +describe('config view actions', function () { var initialState = { metamask: { rpcTarget: 'foo', - frequentRpcList: [] + frequentRpcList: [], }, appState: { currentView: { name: 'accounts', - } - } + }, + }, } freeze(initialState) - describe('SHOW_CONFIG_PAGE', function() { - it('should set appState.currentView.name to config', function() { + describe('SHOW_CONFIG_PAGE', function () { + it('should set appState.currentView.name to config', function () { var result = reducers(initialState, actions.showConfigPage()) assert.equal(result.appState.currentView.name, 'config') }) }) - describe('SET_RPC_TARGET', function() { - - it('sets the state.metamask.rpcTarget property of the state to the action.value', function() { + describe('SET_RPC_TARGET', function () { + it('sets the state.metamask.rpcTarget property of the state to the action.value', function () { const action = { type: actions.SET_RPC_TARGET, value: 'foo', @@ -41,5 +39,4 @@ describe ('config view actions', function() { assert.equal(result.metamask.provider.rpcTarget, 'foo') }) }) - }) diff --git a/test/unit/actions/save_account_label_test.js b/test/unit/actions/save_account_label_test.js index 1df428b1d..c5ffd6cbf 100644 --- a/test/unit/actions/save_account_label_test.js +++ b/test/unit/actions/save_account_label_test.js @@ -1,22 +1,21 @@ -var jsdom = require('mocha-jsdom') +// var jsdom = require('mocha-jsdom') var assert = require('assert') var freeze = require('deep-freeze-strict') var path = require('path') var actions = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'actions.js')) -var reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'reducers.js')) +var reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'reducers.js')) -describe('SAVE_ACCOUNT_LABEL', function() { - - it('updates the state.metamask.identities[:i].name property of the state to the action.value.label', function() { +describe('SAVE_ACCOUNT_LABEL', function () { + it('updates the state.metamask.identities[:i].name property of the state to the action.value.label', function () { var initialState = { metamask: { identities: { foo: { - name: 'bar' - } + name: 'bar', + }, }, - } + }, } freeze(initialState) @@ -24,13 +23,13 @@ describe('SAVE_ACCOUNT_LABEL', function() { type: actions.SAVE_ACCOUNT_LABEL, value: { account: 'foo', - label: 'baz' + label: 'baz', }, } freeze(action) var resultingState = reducers(initialState, action) assert.equal(resultingState.metamask.identities.foo.name, action.value.label) - }); -}); + }) +}) diff --git a/test/unit/actions/set_selected_account_test.js b/test/unit/actions/set_selected_account_test.js index 2dc42d2ec..28b47d09d 100644 --- a/test/unit/actions/set_selected_account_test.js +++ b/test/unit/actions/set_selected_account_test.js @@ -1,18 +1,17 @@ -var jsdom = require('mocha-jsdom') +// var jsdom = require('mocha-jsdom') var assert = require('assert') var freeze = require('deep-freeze-strict') var path = require('path') var actions = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'actions.js')) -var reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'reducers.js')) +var reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'reducers.js')) -describe('SET_SELECTED_ACCOUNT', function() { - - it('sets the state.appState.activeAddress property of the state to the action.value', function() { +describe('SET_SELECTED_ACCOUNT', function () { + it('sets the state.appState.activeAddress property of the state to the action.value', function () { var initialState = { appState: { activeAddress: 'foo', - } + }, } freeze(initialState) @@ -24,15 +23,15 @@ describe('SET_SELECTED_ACCOUNT', function() { var resultingState = reducers(initialState, action) assert.equal(resultingState.appState.activeAddress, action.value) - }); -}); + }) +}) -describe('SHOW_ACCOUNT_DETAIL', function() { - it('updates metamask state', function() { +describe('SHOW_ACCOUNT_DETAIL', function () { + it('updates metamask state', function () { var initialState = { metamask: { - selectedAddress: 'foo' - } + selectedAddress: 'foo', + }, } freeze(initialState) diff --git a/test/unit/actions/tx_test.js b/test/unit/actions/tx_test.js index bd72a666e..b6a691860 100644 --- a/test/unit/actions/tx_test.js +++ b/test/unit/actions/tx_test.js @@ -1,29 +1,27 @@ -var jsdom = require('mocha-jsdom') +// var jsdom = require('mocha-jsdom') var assert = require('assert') var freeze = require('deep-freeze-strict') var path = require('path') var sinon = require('sinon') var actions = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'actions.js')) -var reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'reducers.js')) +var reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'reducers.js')) -describe('tx confirmation screen', function() { - - beforeEach(function() { - this.sinon = sinon.sandbox.create(); - }); +describe('tx confirmation screen', function () { + beforeEach(function () { + this.sinon = sinon.sandbox.create() + }) - afterEach(function(){ - this.sinon.restore(); - }); + afterEach(function () { + this.sinon.restore() + }) var initialState, result - describe('when there is only one tx', function() { + describe('when there is only one tx', function () { var firstTxId = 1457634084250832 - beforeEach(function() { - + beforeEach(function () { initialState = { appState: { currentView: { @@ -34,130 +32,42 @@ describe('tx confirmation screen', function() { unapprovedTxs: { '1457634084250832': { id: 1457634084250832, - status: "unconfirmed", + status: 'unconfirmed', time: 1457634084250, - } + }, }, - } + }, } freeze(initialState) }) - describe('cancelTx', function() { - - before(function(done) { + describe('cancelTx', function () { + before(function (done) { actions._setBackgroundConnection({ - approveTransaction(txId, cb) { cb('An error!') }, - cancelTransaction(txId) { /* noop */ }, - clearSeedWordCache(cb) { cb() }, + approveTransaction (txId, cb) { cb('An error!') }, + cancelTransaction (txId, cb) { cb() }, + clearSeedWordCache (cb) { cb() }, }) - let action = actions.cancelTx({value: firstTxId}) - result = reducers(initialState, action) + actions.cancelTx({value: firstTxId})((action) => { + result = reducers(initialState, action) + }) done() }) - it('should transition to the account detail view', function() { + it('should transition to the account detail view', function () { assert.equal(result.appState.currentView.name, 'accountDetail') }) - it('should have no unconfirmed txs remaining', function() { + it('should have no unconfirmed txs remaining', function () { var count = getUnconfirmedTxCount(result) assert.equal(count, 0) }) }) - - describe('sendTx', function() { - var result - - describe('when there is an error', function() { - - before(function(done) { - alert = () => {/* noop */} - - actions._setBackgroundConnection({ - approveTransaction(txId, cb) { cb({message: 'An error!'}) }, - }) - - actions.sendTx({id: firstTxId})(function(action) { - result = reducers(initialState, action) - done() - }) - }) - - it('should stay on the page', function() { - assert.equal(result.appState.currentView.name, 'confTx') - }) - - it('should set errorMessage on the currentView', function() { - assert(result.appState.currentView.errorMessage) - }) - }) - - describe('when there is success', function() { - it('should complete tx and go home', function() { - actions._setBackgroundConnection({ - approveTransaction(txId, cb) { cb() }, - }) - - var dispatchExpect = sinon.mock() - dispatchExpect.twice() - - actions.sendTx({id: firstTxId})(dispatchExpect) - }) - }) - }) - - describe('when there are two pending txs', function() { - var firstTxId = 1457634084250832 - var result, initialState - before(function(done) { - initialState = { - appState: { - currentView: { - name: 'confTx', - }, - }, - metamask: { - unapprovedTxs: { - '1457634084250832': { - id: firstTxId, - status: "unconfirmed", - time: 1457634084250, - }, - '1457634084250833': { - id: 1457634084250833, - status: "unconfirmed", - time: 1457634084255, - }, - }, - } - } - freeze(initialState) - - // Mocking a background connection: - actions._setBackgroundConnection({ - approveTransaction(firstTxId, cb) { cb() }, - }) - - let action = actions.sendTx({id: firstTxId})(function(action) { - result = reducers(initialState, action) - }) - done() - }) - - it('should stay on the confTx view', function() { - assert.equal(result.appState.currentView.name, 'confTx') - }) - - it('should transition to the first tx', function() { - assert.equal(result.appState.currentView.context, 0) - }) - }) }) -}); +}) -function getUnconfirmedTxCount(state) { +function getUnconfirmedTxCount (state) { var txs = state.metamask.unapprovedTxs var count = Object.keys(txs).length return count diff --git a/test/unit/actions/view_info_test.js b/test/unit/actions/view_info_test.js index 0558c6e42..69895d801 100644 --- a/test/unit/actions/view_info_test.js +++ b/test/unit/actions/view_info_test.js @@ -1,23 +1,22 @@ -var jsdom = require('mocha-jsdom') +// var jsdom = require('mocha-jsdom') var assert = require('assert') var freeze = require('deep-freeze-strict') var path = require('path') var actions = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'actions.js')) -var reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'reducers.js')) +var reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'reducers.js')) -describe('SHOW_INFO_PAGE', function() { - - it('sets the state.appState.currentView.name property to info', function() { +describe('SHOW_INFO_PAGE', function () { + it('sets the state.appState.currentView.name property to info', function () { var initialState = { appState: { activeAddress: 'foo', - } + }, } freeze(initialState) const action = actions.showInfoPage() var resultingState = reducers(initialState, action) assert.equal(resultingState.appState.currentView.name, 'info') - }); -}); + }) +}) diff --git a/test/unit/actions/warning_test.js b/test/unit/actions/warning_test.js index 37be9ee85..28b565499 100644 --- a/test/unit/actions/warning_test.js +++ b/test/unit/actions/warning_test.js @@ -1,14 +1,13 @@ -var jsdom = require('mocha-jsdom') +// var jsdom = require('mocha-jsdom') var assert = require('assert') var freeze = require('deep-freeze-strict') var path = require('path') var actions = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'actions.js')) -var reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'reducers.js')) +var reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'reducers.js')) -describe('action DISPLAY_WARNING', function() { - - it('sets appState.warning to provided value', function() { +describe('action DISPLAY_WARNING', function () { + it('sets appState.warning to provided value', function () { var initialState = { appState: {}, } @@ -20,5 +19,5 @@ describe('action DISPLAY_WARNING', function() { const resultingState = reducers(initialState, action) assert.equal(resultingState.appState.warning, warningText, 'warning text set') - }); -}); + }) +}) diff --git a/test/unit/address-book-controller.js b/test/unit/address-book-controller.js index f345b0328..655c9022c 100644 --- a/test/unit/address-book-controller.js +++ b/test/unit/address-book-controller.js @@ -1,5 +1,4 @@ const assert = require('assert') -const extend = require('xtend') const AddressBookController = require('../../app/scripts/controllers/address-book') const mockKeyringController = { @@ -7,21 +6,20 @@ const mockKeyringController = { getState: function () { return { identities: { - '0x0aaa' : { + '0x0aaa': { address: '0x0aaa', name: 'owned', - } - } + }, + }, } - } - } + }, + }, } - -describe('address-book-controller', function() { +describe('address-book-controller', function () { var addressBookController - beforeEach(function() { + beforeEach(function () { addressBookController = new AddressBookController({}, mockKeyringController) }) diff --git a/test/unit/blacklist-controller-test.js b/test/unit/blacklist-controller-test.js new file mode 100644 index 000000000..a9260466f --- /dev/null +++ b/test/unit/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) + }) + }) +})
\ No newline at end of file diff --git a/test/unit/components/balance-component-test.js b/test/unit/components/balance-component-test.js new file mode 100644 index 000000000..9b1e82acf --- /dev/null +++ b/test/unit/components/balance-component-test.js @@ -0,0 +1,45 @@ +const assert = require('assert') +const h = require('react-hyperscript') +const { createMockStore } = require('redux-test-utils') +const { shallowWithStore } = require('../../lib/shallow-with-store') +const BalanceComponent = require('../../../ui/app/components/balance-component') +const mockState = { + metamask: { + accounts: { abc: {} }, + network: 1, + selectedAddress: 'abc', + } +} + +describe('BalanceComponent', function () { + let balanceComponent + let store + let component + beforeEach(function () { + store = createMockStore(mockState) + component = shallowWithStore(h(BalanceComponent), store) + balanceComponent = component.dive() + }) + + it('shows token balance and convert to fiat value based on conversion rate', function () { + const formattedBalance = '1.23 ETH' + + const tokenBalance = balanceComponent.instance().getTokenBalance(formattedBalance, false) + const fiatDisplayNumber = balanceComponent.instance().getFiatDisplayNumber(formattedBalance, 2) + + assert.equal('1.23 ETH', tokenBalance) + assert.equal(2.46, fiatDisplayNumber) + }) + + it('shows only the token balance when conversion rate is not available', function () { + const formattedBalance = '1.23 ETH' + + const tokenBalance = balanceComponent.instance().getTokenBalance(formattedBalance, false) + const fiatDisplayNumber = balanceComponent.instance().getFiatDisplayNumber(formattedBalance, 0) + + assert.equal('1.23 ETH', tokenBalance) + assert.equal('N/A', fiatDisplayNumber) + }) + +}) + diff --git a/test/unit/components/binary-renderer-test.js b/test/unit/components/binary-renderer-test.js index 3264faddc..ee2fa8b60 100644 --- a/test/unit/components/binary-renderer-test.js +++ b/test/unit/components/binary-renderer-test.js @@ -1,24 +1,22 @@ var assert = require('assert') var BinaryRenderer = require('../../../ui/app/components/binary-renderer') -describe('BinaryRenderer', function() { - +describe('BinaryRenderer', function () { let binaryRenderer const message = 'Hello, world!' const buffer = new Buffer(message, 'utf8') const hex = buffer.toString('hex') - beforeEach(function() { + beforeEach(function () { binaryRenderer = new BinaryRenderer() }) - it('recovers message', function() { + it('recovers message', function () { const result = binaryRenderer.hexToText(hex) assert.equal(result, message) }) - - it('recovers message with hex prefix', function() { + it('recovers message with hex prefix', function () { const result = binaryRenderer.hexToText('0x' + hex) assert.equal(result, message) }) diff --git a/test/unit/components/bn-as-decimal-input-test.js b/test/unit/components/bn-as-decimal-input-test.js new file mode 100644 index 000000000..58ecc9c89 --- /dev/null +++ b/test/unit/components/bn-as-decimal-input-test.js @@ -0,0 +1,89 @@ +var assert = require('assert') + +const additions = require('react-testutils-additions') +const h = require('react-hyperscript') +const ReactTestUtils = require('react-addons-test-utils') +const ethUtil = require('ethereumjs-util') +const BN = ethUtil.BN + +var BnInput = require('../../../ui/app/components/bn-as-decimal-input') + +describe('BnInput', function () { + it('can tolerate a gas decimal number at a high precision', function (done) { + const renderer = ReactTestUtils.createRenderer() + + let valueStr = '20' + while (valueStr.length < 20) { + valueStr += '0' + } + const value = new BN(valueStr, 10) + + const inputStr = '2.3' + + let targetStr = '23' + while (targetStr.length < 19) { + targetStr += '0' + } + const target = new BN(targetStr, 10) + + const precision = 18 // ether precision + const scale = 18 + + const props = { + value, + scale, + precision, + onChange: (newBn) => { + assert.equal(newBn.toString(), target.toString(), 'should tolerate increase') + done() + }, + } + + const inputComponent = h(BnInput, props) + const component = additions.renderIntoDocument(inputComponent) + renderer.render(inputComponent) + const input = additions.find(component, 'input.hex-input')[0] + ReactTestUtils.Simulate.change(input, { preventDefault () {}, target: { + value: inputStr, + checkValidity () { return true } }, + }) + }) + + it('can tolerate wei precision', function (done) { + const renderer = ReactTestUtils.createRenderer() + + let valueStr = '1000000000' + + const value = new BN(valueStr, 10) + const inputStr = '1.000000001' + + + let targetStr = '1000000001' + + const target = new BN(targetStr, 10) + + const precision = 9 // gwei precision + const scale = 9 + + const props = { + value, + scale, + precision, + onChange: (newBn) => { + assert.equal(newBn.toString(), target.toString(), 'should tolerate increase') + const reInput = BnInput.prototype.downsize(newBn.toString(), 9, 9) + assert.equal(reInput.toString(), inputStr, 'should tolerate increase') + done() + }, + } + + const inputComponent = h(BnInput, props) + const component = additions.renderIntoDocument(inputComponent) + renderer.render(inputComponent) + const input = additions.find(component, 'input.hex-input')[0] + ReactTestUtils.Simulate.change(input, { preventDefault () {}, target: { + value: inputStr, + checkValidity () { return true } }, + }) + }) +}) diff --git a/test/unit/components/pending-tx-test.js b/test/unit/components/pending-tx-test.js new file mode 100644 index 000000000..c6c588e1c --- /dev/null +++ b/test/unit/components/pending-tx-test.js @@ -0,0 +1,67 @@ +const assert = require('assert') +const h = require('react-hyperscript') +const PendingTx = require('../../../ui/app/components/pending-tx') +const ethUtil = require('ethereumjs-util') + +const { createMockStore } = require('redux-test-utils') +const { shallowWithStore } = require('../../lib/shallow-with-store') + +const identities = { abc: {}, def: {} } +const mockState = { + metamask: { + accounts: { abc: {} }, + identities, + conversionRate: 10, + selectedAddress: 'abc', + } +} + +describe('PendingTx', function () { + const gasPrice = '0x4A817C800' // 20 Gwei + const txData = { + 'id': 5021615666270214, + 'time': 1494458763011, + 'status': 'unapproved', + 'metamaskNetworkId': '1494442339676', + 'txParams': { + 'from': '0xfdea65c8e26263f6d9a1b5de9555d2931a33b826', + 'to': '0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb', + 'value': '0xde0b6b3a7640000', + gasPrice, + 'gas': '0x7b0c', + }, + 'gasLimitSpecified': false, + 'estimatedGas': '0x5208', + } + const newGasPrice = '0x77359400' + + const computedBalances = {} + computedBalances[Object.keys(identities)[0]] = { + ethBalance: '0x00000000000000056bc75e2d63100000', + } + const props = { + txData, + computedBalances, + sendTransaction: (txMeta, event) => { + // Assert changes: + const result = ethUtil.addHexPrefix(txMeta.txParams.gasPrice) + assert.notEqual(result, gasPrice, 'gas price should change') + assert.equal(result, newGasPrice, 'gas price assigned.') + }, + } + + let pendingTxComponent + let store + let component + beforeEach(function () { + store = createMockStore(mockState) + component = shallowWithStore(h(PendingTx, props), store) + pendingTxComponent = component + }) + + it('should render correctly', function (done) { + assert.equal(pendingTxComponent.props().identities, identities) + done() + }) +}) + diff --git a/test/unit/config-manager-test.js b/test/unit/config-manager-test.js index 05324e741..b710e2dfb 100644 --- a/test/unit/config-manager-test.js +++ b/test/unit/config-manager-test.js @@ -2,26 +2,22 @@ global.fetch = global.fetch || require('isomorphic-fetch') const assert = require('assert') -const extend = require('xtend') -const rp = require('request-promise') -const nock = require('nock') const configManagerGen = require('../lib/mock-config-manager') -describe('config-manager', function() { +describe('config-manager', function () { var configManager - beforeEach(function() { + beforeEach(function () { configManager = configManagerGen() }) - describe('#setConfig', function() { - + describe('#setConfig', function () { it('should set the config key', function () { var testConfig = { provider: { type: 'rpc', - rpcTarget: 'foobar' - } + rpcTarget: 'foobar', + }, } configManager.setConfig(testConfig) var result = configManager.getData() @@ -30,17 +26,17 @@ describe('config-manager', function() { assert.equal(result.config.provider.rpcTarget, testConfig.provider.rpcTarget) }) - it('setting wallet should not overwrite config', function() { + it('setting wallet should not overwrite config', function () { var testConfig = { provider: { type: 'rpc', - rpcTarget: 'foobar' + rpcTarget: 'foobar', }, } configManager.setConfig(testConfig) var testWallet = { - name: 'this is my fake wallet' + name: 'this is my fake wallet', } configManager.setWallet(testWallet) @@ -58,13 +54,13 @@ describe('config-manager', function() { }) }) - describe('wallet nicknames', function() { - it('should return null when no nicknames are saved', function() { + describe('wallet nicknames', function () { + it('should return null when no nicknames are saved', function () { var nick = configManager.nicknameForWallet('0x0') assert.equal(nick, null, 'no nickname returned') }) - it('should persist nicknames', function() { + it('should persist nicknames', function () { var account = '0x0' var nick1 = 'foo' var nick2 = 'bar' @@ -79,8 +75,8 @@ describe('config-manager', function() { }) }) - describe('rpc manipulations', function() { - it('changing rpc should return a different rpc', function() { + describe('rpc manipulations', function () { + it('changing rpc should return a different rpc', function () { var firstRpc = 'first' var secondRpc = 'second' @@ -94,21 +90,21 @@ describe('config-manager', function() { }) }) - describe('transactions', function() { - beforeEach(function() { + describe('transactions', function () { + beforeEach(function () { configManager.setTxList([]) }) - describe('#getTxList', function() { - it('when new should return empty array', function() { + describe('#getTxList', function () { + it('when new should return empty array', function () { var result = configManager.getTxList() assert.ok(Array.isArray(result)) assert.equal(result.length, 0) }) }) - describe('#setTxList', function() { - it('saves the submitted data to the tx list', function() { + describe('#setTxList', function () { + it('saves the submitted data to the tx list', function () { var target = [{ foo: 'bar' }] configManager.setTxList(target) var result = configManager.getTxList() diff --git a/test/unit/currency-controller-test.js b/test/unit/currency-controller-test.js index 079f8b488..63ab60f9e 100644 --- a/test/unit/currency-controller-test.js +++ b/test/unit/currency-controller-test.js @@ -2,86 +2,81 @@ global.fetch = global.fetch || require('isomorphic-fetch') const assert = require('assert') -const extend = require('xtend') -const rp = require('request-promise') const nock = require('nock') const CurrencyController = require('../../app/scripts/controllers/currency') -describe('currency-controller', function() { +describe('currency-controller', function () { var currencyController - beforeEach(function() { + beforeEach(function () { currencyController = new CurrencyController() }) - describe('currency conversions', function() { - - describe('#setCurrentCurrency', function() { - it('should return USD as default', function() { - assert.equal(currencyController.getCurrentCurrency(), 'USD') + 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') + 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() { + 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) { + describe('#updateConversionRate', function () { + it('should retrieve an update for ETH to USD and set it in memory', function (done) { this.timeout(15000) - var usdMock = nock('https://www.cryptonator.com') - .get('/api/ticker/eth-USD') - .reply(200, '{"ticker":{"base":"ETH","target":"USD","price":"11.02456145","volume":"44948.91745289","change":"-0.01472534"},"timestamp":1472072136,"success":true,"error":""}') + 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.setCurrentCurrency('usd') currencyController.updateConversionRate() - .then(function() { + .then(function () { var result = currencyController.getConversionRate() console.log('currencyController.getConversionRate:', result) assert.equal(typeof result, 'number') done() - }).catch(function(err) { + }).catch(function (err) { done(err) }) - }) - it('should work for JPY as well.', function() { + it('should work for JPY as well.', function () { this.timeout(15000) assert.equal(currencyController.getConversionRate(), 0) - var jpyMock = nock('https://www.cryptonator.com') - .get('/api/ticker/eth-JPY') - .reply(200, '{"ticker":{"base":"ETH","target":"JPY","price":"11.02456145","volume":"44948.91745289","change":"-0.01472534"},"timestamp":1472072136,"success":true,"error":""}') + 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() { + currencyController.setCurrentCurrency('jpy') + currencyController.updateConversionRate().then(function () { resolve() }) - }) + }) - promise.then(function() { + promise.then(function () { var result = currencyController.getConversionRate() assert.equal(typeof result, 'number') - }).catch(function(err) { + }).catch(function (done, err) { done(err) }) }) }) }) - }) diff --git a/test/unit/explorer-link-test.js b/test/unit/explorer-link-test.js deleted file mode 100644 index 4f0230c2c..000000000 --- a/test/unit/explorer-link-test.js +++ /dev/null @@ -1,16 +0,0 @@ -var assert = require('assert') -var linkGen = require('../../ui/lib/explorer-link') - -describe('explorer-link', function() { - - it('adds ropsten prefix to ropsten test network', function() { - var result = linkGen('hash', '3') - assert.notEqual(result.indexOf('ropsten'), -1, 'ropsten injected') - }) - - it('adds kovan prefix to kovan test network', function() { - var result = linkGen('hash', '42') - assert.notEqual(result.indexOf('kovan'), -1, 'kovan injected') - }) - -}) diff --git a/test/unit/infura-controller-test.js b/test/unit/infura-controller-test.js new file mode 100644 index 000000000..605305efa --- /dev/null +++ b/test/unit/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.sandbox.create() + 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/keyring-controller-test.js b/test/unit/keyring-controller-test.js deleted file mode 100644 index efd0a3546..000000000 --- a/test/unit/keyring-controller-test.js +++ /dev/null @@ -1,172 +0,0 @@ -const assert = require('assert') -const KeyringController = require('../../app/scripts/keyring-controller') -const configManagerGen = require('../lib/mock-config-manager') -const ethUtil = require('ethereumjs-util') -const BN = ethUtil.BN -const async = require('async') -const mockEncryptor = require('../lib/mock-encryptor') -const MockSimpleKeychain = require('../lib/mock-simple-keychain') -const sinon = require('sinon') - -describe('KeyringController', function() { - - let keyringController, state - let password = 'password123' - let seedWords = 'puzzle seed penalty soldier say clay field arctic metal hen cage runway' - let addresses = ['eF35cA8EbB9669A35c31b5F6f249A9941a812AC1'.toLowerCase()] - let accounts = [] - let originalKeystore - - beforeEach(function(done) { - this.sinon = sinon.sandbox.create() - window.localStorage = {} // Hacking localStorage support into JSDom - - keyringController = new KeyringController({ - configManager: configManagerGen(), - txManager: { - getTxList: () => [], - getUnapprovedTxList: () => [] - }, - ethStore: { - addAccount(acct) { accounts.push(ethUtil.addHexPrefix(acct)) }, - }, - }) - - // Stub out the browser crypto for a mock encryptor. - // Browser crypto is tested in the integration test suite. - keyringController.encryptor = mockEncryptor - - keyringController.createNewVaultAndKeychain(password) - .then(function (newState) { - state = newState - done() - }) - .catch((err) => { - done(err) - }) - }) - - afterEach(function() { - // Cleanup mocks - this.sinon.restore() - }) - - describe('#createNewVaultAndKeychain', function () { - this.timeout(10000) - - it('should set a vault on the configManager', function(done) { - keyringController.store.updateState({ vault: null }) - assert(!keyringController.store.getState().vault, 'no previous vault') - keyringController.createNewVaultAndKeychain(password) - .then(() => { - const vault = keyringController.store.getState().vault - assert(vault, 'vault created') - done() - }) - .catch((reason) => { - done(reason) - }) - }) - }) - - describe('#restoreKeyring', function() { - - it(`should pass a keyring's serialized data back to the correct type.`, function(done) { - const mockSerialized = { - type: 'HD Key Tree', - data: { - mnemonic: seedWords, - numberOfAccounts: 1, - } - } - const mock = this.sinon.mock(keyringController) - - mock.expects('getBalanceAndNickname') - .exactly(1) - - keyringController.restoreKeyring(mockSerialized) - .then((keyring) => { - assert.equal(keyring.wallets.length, 1, 'one wallet restored') - return keyring.getAccounts() - }) - .then((accounts) => { - assert.equal(accounts[0], addresses[0]) - mock.verify() - done() - }) - .catch((reason) => { - done(reason) - }) - }) - }) - - describe('#createNickname', function() { - it('should add the address to the identities hash', function() { - const fakeAddress = '0x12345678' - keyringController.createNickname(fakeAddress) - const identities = keyringController.memStore.getState().identities - const identity = identities[fakeAddress] - assert.equal(identity.address, fakeAddress) - }) - }) - - describe('#saveAccountLabel', function() { - it ('sets the nickname', function(done) { - const account = addresses[0] - var nick = 'Test nickname' - const identities = keyringController.memStore.getState().identities - identities[ethUtil.addHexPrefix(account)] = {} - keyringController.memStore.updateState({ identities }) - keyringController.saveAccountLabel(account, nick) - .then((label) => { - try { - assert.equal(label, nick) - const persisted = keyringController.store.getState().walletNicknames[account] - assert.equal(persisted, nick) - done() - } catch (err) { - done() - } - }) - .catch((reason) => { - done(reason) - }) - }) - }) - - describe('#getAccounts', function() { - it('returns the result of getAccounts for each keyring', function(done) { - keyringController.keyrings = [ - { getAccounts() { return Promise.resolve([1,2,3]) } }, - { getAccounts() { return Promise.resolve([4,5,6]) } }, - ] - - keyringController.getAccounts() - .then((result) => { - assert.deepEqual(result, [1,2,3,4,5,6]) - done() - }) - }) - }) - - describe('#addGasBuffer', function() { - it('adds 100k gas buffer to estimates', function() { - - const gas = '0x04ee59' // Actual estimated gas example - const tooBigOutput = '0x80674f9' // Actual bad output - const bnGas = new BN(ethUtil.stripHexPrefix(gas), 16) - const correctBuffer = new BN('100000', 10) - const correct = bnGas.add(correctBuffer) - - const tooBig = new BN(tooBigOutput, 16) - const result = keyringController.addGasBuffer(gas) - const bnResult = new BN(ethUtil.stripHexPrefix(result), 16) - - assert.equal(result.indexOf('0x'), 0, 'included hex prefix') - assert(bnResult.gt(bnGas), 'Estimate increased in value.') - assert.equal(bnResult.sub(bnGas).toString(10), '100000', 'added 100k gas') - assert.equal(result, '0x' + correct.toString(16), 'Added the right amount') - assert.notEqual(result, tooBigOutput, 'not that bad estimate') - }) - }) -}) diff --git a/test/unit/linting_test.js b/test/unit/linting_test.js deleted file mode 100644 index 75d90652d..000000000 --- a/test/unit/linting_test.js +++ /dev/null @@ -1,9 +0,0 @@ -// LINTING: -const lint = require('mocha-eslint'); -const lintPaths = ['app/**/*.js', 'ui/**/*.js', '!node_modules/**', '!dist/**', '!docs/**', '!app/scripts/chromereload.js'] - -const lintOptions = { - strict: false, -} - -lint(lintPaths, lintOptions)
\ No newline at end of file diff --git a/test/unit/message-manager-test.js b/test/unit/message-manager-test.js index faf7429d4..9b76241ed 100644 --- a/test/unit/message-manager-test.js +++ b/test/unit/message-manager-test.js @@ -1,29 +1,26 @@ const assert = require('assert') -const extend = require('xtend') -const EventEmitter = require('events') - const MessageManger = require('../../app/scripts/lib/message-manager') -describe('Transaction Manager', function() { +describe('Message Manager', function () { let messageManager - beforeEach(function() { - messageManager = new MessageManger () + beforeEach(function () { + messageManager = new MessageManger() }) - describe('#getMsgList', function() { - it('when new should return empty array', function() { + 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() { + it('should also return transactions from local storage if any', function () { }) }) - describe('#addMsg', function() { - it('adds a Msg returned in getMsgList', 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 @@ -33,8 +30,8 @@ describe('Transaction Manager', function() { }) }) - describe('#setMsgStatusApproved', function() { - it('sets the Msg status to approved', function() { + 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) @@ -45,8 +42,8 @@ describe('Transaction Manager', function() { }) }) - describe('#rejectMsg', function() { - it('sets the Msg status to rejected', function() { + 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) @@ -57,8 +54,8 @@ describe('Transaction Manager', function() { }) }) - describe('#_updateMsg', function() { - it('replaces the Msg with the same id', function() { + 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' }) @@ -67,19 +64,19 @@ describe('Transaction Manager', function() { }) }) - describe('#getUnapprovedMsgs', function() { - it('returns unapproved Msgs in a hash', function() { + 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' }) - let result = messageManager.getUnapprovedMsgs() + 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() { + 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') diff --git a/test/unit/metamask-controller-test.js b/test/unit/metamask-controller-test.js index 78b9e9df7..3fc7f9a98 100644 --- a/test/unit/metamask-controller-test.js +++ b/test/unit/metamask-controller-test.js @@ -3,27 +3,127 @@ const sinon = require('sinon') const clone = require('clone') const MetaMaskController = require('../../app/scripts/metamask-controller') const firstTimeState = require('../../app/scripts/first-time-state') +const BN = require('ethereumjs-util').BN +const GWEI_BN = new BN('1000000000') -const STORAGE_KEY = 'metamask-config' - -describe('MetaMaskController', function() { +describe('MetaMaskController', function () { const noop = () => {} - let controller = new MetaMaskController({ + const metamaskController = new MetaMaskController({ showUnconfirmedMessage: noop, unlockAccountMessage: noop, showUnapprovedTx: noop, + platform: {}, + encryptor: { + encrypt: function(password, object) { + this.object = object + return Promise.resolve() + }, + decrypt: function () { + return Promise.resolve(this.object) + } + }, // initial state initState: clone(firstTimeState), }) - beforeEach(function() { + beforeEach(function () { // sinon allows stubbing methods that are easily verified this.sinon = sinon.sandbox.create() }) - afterEach(function() { + afterEach(function () { // sinon requires cleanup otherwise it will overwrite context this.sinon.restore() }) -})
\ No newline at end of file + describe('Metamask Controller', function () { + assert(metamaskController) + + beforeEach(function () { + sinon.spy(metamaskController.keyringController, 'createNewVaultAndKeychain') + sinon.spy(metamaskController.keyringController, 'createNewVaultAndRestore') + }) + + afterEach(function () { + metamaskController.keyringController.createNewVaultAndKeychain.restore() + metamaskController.keyringController.createNewVaultAndRestore.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 + }) + + it('gives the 1 gwei price if no blocks have been seen.', async function () { + const realRecentBlocksController = metamaskController.recentBlocksController + metamaskController.recentBlocksController = { + store: { + getState: () => { + return { + recentBlocks: [] + } + } + } + } + + const gasPrice = metamaskController.getGasPrice() + assert.equal(gasPrice, '0x' + GWEI_BN.toString(16), 'defaults to 1 gwei') + + metamaskController.recentBlocksController = realRecentBlocksController + }) + + }) + + describe('#createNewVaultAndKeychain', function () { + it('can only create new vault on keyringController once', async function () { + const selectStub = sinon.stub(metamaskController, 'selectFirstIdentity') + + + const password = 'a-fake-password' + + const first = await metamaskController.createNewVaultAndKeychain(password) + const second = 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 selectStub = sinon.stub(metamaskController, 'selectFirstIdentity') + + const password = 'what-what-what' + const wrongSeed = 'debris dizzy just program just float decrease vacant alarm reduce speak stadiu' + const rightSeed = 'debris dizzy just program just float decrease vacant alarm reduce speak stadium' + const first = await metamaskController.createNewVaultAndRestore(password, wrongSeed) + .catch((e) => { + return + }) + const second = await metamaskController.createNewVaultAndRestore(password, rightSeed) + + assert(metamaskController.keyringController.createNewVaultAndRestore.calledTwice) + }) + }) + }) +}) diff --git a/test/unit/migrations-test.js b/test/unit/migrations-test.js index ccd1477b0..5bad25a45 100644 --- a/test/unit/migrations-test.js +++ b/test/unit/migrations-test.js @@ -3,7 +3,7 @@ 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 +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')) @@ -16,6 +16,7 @@ const migration9 = require(path.join('..', '..', 'app', 'scripts', 'migrations', 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/' @@ -23,7 +24,6 @@ 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) @@ -98,7 +98,11 @@ describe('wallet1 is migrated successfully', () => { }).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/021-test.js b/test/unit/migrations/021-test.js new file mode 100644 index 000000000..458e9b4b5 --- /dev/null +++ b/test/unit/migrations/021-test.js @@ -0,0 +1,16 @@ +const assert = require('assert') + +const wallet2 = require('../../lib/migrations/002.json') +const migration21 = require('../../../app/scripts/migrations/021') + +describe('wallet2 is migrated successfully with out the BlacklistController', () => { + it('should delete BlacklistController key', (done) => { + migration21.migrate(wallet2) + .then((migratedData) => { + assert.equal(migratedData.meta.version, 21) + assert(!migratedData.data.BlacklistController) + assert(!migratedData.data.RecentBlocks) + done() + }).catch(done) + }) +}) diff --git a/test/unit/migrator-test.js b/test/unit/migrator-test.js new file mode 100644 index 000000000..16066fefe --- /dev/null +++ b/test/unit/migrator-test.js @@ -0,0 +1,41 @@ +const assert = require('assert') +const clone = require('clone') +const Migrator = require('../../app/scripts/lib/migrator/') +const migrations = [ + { + 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'}} +describe('Migrator', () => { + const migrator = new Migrator({ migrations }) + it('migratedData version should be version 3', (done) => { + migrator.migrateData(versionedData) + .then((migratedData) => { + assert.equal(migratedData.meta.version, migrations[2].version) + done() + }).catch(done) + }) +}) diff --git a/test/unit/nameForAccount_test.js b/test/unit/nameForAccount_test.js index 6839d40f8..e7c0b18b4 100644 --- a/test/unit/nameForAccount_test.js +++ b/test/unit/nameForAccount_test.js @@ -4,25 +4,23 @@ var sinon = require('sinon') var path = require('path') var contractNamer = require(path.join(__dirname, '..', '..', 'ui', 'lib', 'contract-namer.js')) -describe('contractNamer', function() { - - beforeEach(function() { +describe('contractNamer', function () { + beforeEach(function () { this.sinon = sinon.sandbox.create() }) - afterEach(function() { + afterEach(function () { this.sinon.restore() }) - describe('naming a contract', function() { - - it('should return nothing for an unknown random account', function() { + describe('naming a contract', function () { + it('should return nothing for an unknown random account', function () { const input = '0x2386F26FC10000' const output = contractNamer(input) assert.deepEqual(output, null) }) - it('should accept identities as an optional second parameter', function() { + it('should accept identities as an optional second parameter', function () { const input = '0x2386F26FC10000'.toLowerCase() const expected = 'bar' const identities = {} @@ -31,7 +29,7 @@ describe('contractNamer', function() { assert.deepEqual(output, expected) }) - it('should check for identities case insensitively', function() { + it('should check for identities case insensitively', function () { const input = '0x2386F26FC10000'.toLowerCase() const expected = 'bar' const identities = {} @@ -39,6 +37,5 @@ describe('contractNamer', function() { const output = contractNamer(input.toUpperCase(), identities) assert.deepEqual(output, expected) }) - }) }) diff --git a/test/unit/network-contoller-test.js b/test/unit/network-contoller-test.js new file mode 100644 index 000000000..0b3b5adeb --- /dev/null +++ b/test/unit/network-contoller-test.js @@ -0,0 +1,84 @@ +const assert = require('assert') +const NetworkController = require('../../app/scripts/controllers/network') + +describe('# Network Controller', function () { + let networkController + const networkControllerProviderInit = { + getAccounts: () => {}, + } + + beforeEach(function () { + networkController = new NetworkController({ + provider: { + type: 'rinkeby', + }, + }) + + networkController.initializeProvider(networkControllerProviderInit, dummyProviderConstructor) + }) + describe('network', function () { + describe('#provider', function () { + it('provider should be updatable without reassignment', function () { + networkController.initializeProvider(networkControllerProviderInit, dummyProviderConstructor) + 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('#getRpcAddressForType', function () { + it('should return the right rpc address', function () { + const rpcTarget = networkController.getRpcAddressForType('mainnet') + assert.equal(rpcTarget, 'https://mainnet.infura.io/metamask', 'returns the right rpcAddress') + }) + }) + 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') + }) + it('should set the right rpcTarget', function () { + networkController.setProviderType('mainnet') + const rpcTarget = networkController.getProviderConfig().rpcTarget + assert.equal(rpcTarget, 'https://mainnet.infura.io/metamask', 'returns the right rpcAddress') + }) + }) + }) +}) + +function dummyProviderConstructor() { + return { + // provider + sendAsync: noop, + // block tracker + _blockTracker: {}, + start: noop, + stop: noop, + on: noop, + addListener: noop, + once: noop, + removeAllListeners: noop, + } +} + +function noop() {}
\ No newline at end of file diff --git a/test/unit/nodeify-test.js b/test/unit/nodeify-test.js index a14d34338..c7b127889 100644 --- a/test/unit/nodeify-test.js +++ b/test/unit/nodeify-test.js @@ -1,22 +1,30 @@ const assert = require('assert') const nodeify = require('../../app/scripts/lib/nodeify') -describe('nodeify', function() { - +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).bind(obj) + 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 new file mode 100644 index 000000000..8970cf84d --- /dev/null +++ b/test/unit/nonce-tracker-test.js @@ -0,0 +1,203 @@ +const assert = require('assert') +const NonceTracker = require('../../app/scripts/lib/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('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 index ea37108bb..09eeda15c 100644 --- a/test/unit/notice-controller-test.js +++ b/test/unit/notice-controller-test.js @@ -1,42 +1,38 @@ const assert = require('assert') -const extend = require('xtend') -const rp = require('request-promise') -const nock = require('nock') const configManagerGen = require('../lib/mock-config-manager') const NoticeController = require('../../app/scripts/notice-controller') -const STORAGE_KEY = 'metamask-persistence-key' -describe('notice-controller', function() { +describe('notice-controller', function () { var noticeController - beforeEach(function() { + beforeEach(function () { // simple localStorage polyfill - let configManager = configManagerGen() + const configManager = configManagerGen() noticeController = new NoticeController({ configManager: configManager, }) }) - describe('notices', function() { - describe('#getNoticesList', function() { - it('should return an empty array when new', function(done) { - var testList = [{ - id:0, - read:false, - title:"Futuristic Notice" - }] + 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() { + describe('#setNoticesList', function () { it('should set data appropriately', function (done) { var testList = [{ - id:0, - read:false, - title:"Futuristic Notice" + id: 0, + read: false, + title: 'Futuristic Notice', }] noticeController.setNoticesList(testList) var testListId = noticeController.getNoticesList()[0].id @@ -45,12 +41,12 @@ describe('notice-controller', function() { }) }) - describe('#updateNoticeslist', function() { - it('should integrate the latest changes from the source', function(done) { + describe('#updateNoticeslist', function () { + it('should integrate the latest changes from the source', function (done) { var testList = [{ - id:55, - read:false, - title:"Futuristic Notice" + id: 55, + read: false, + title: 'Futuristic Notice', }] noticeController.setNoticesList(testList) noticeController.updateNoticesList().then(() => { @@ -62,14 +58,14 @@ describe('notice-controller', function() { }) it('should not overwrite any existing fields', function (done) { var testList = [{ - id:0, - read:false, - title:"Futuristic Notice" + 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[0].title, 'Futuristic Notice') assert.equal(newList.length, 1) done() }) @@ -78,9 +74,9 @@ describe('notice-controller', function() { describe('#markNoticeRead', function () { it('should mark a notice as read', function (done) { var testList = [{ - id:0, - read:false, - title:"Futuristic Notice" + id: 0, + read: false, + title: 'Futuristic Notice', }] noticeController.setNoticesList(testList) noticeController.markNoticeRead(testList[0]) @@ -93,9 +89,9 @@ describe('notice-controller', function() { 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"}, + {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() @@ -104,9 +100,9 @@ describe('notice-controller', function() { }) 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"}, + {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() @@ -115,5 +111,4 @@ describe('notice-controller', function() { }) }) }) - }) diff --git a/test/unit/pending-balance-test.js b/test/unit/pending-balance-test.js new file mode 100644 index 000000000..dc4c1c3e4 --- /dev/null +++ b/test/unit/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/pending-tx-test.js b/test/unit/pending-tx-test.js new file mode 100644 index 000000000..f0b4e3bfc --- /dev/null +++ b/test/unit/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/lib/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.reset() + }) + + 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 index f2c01392c..ec2f9a4d1 100644 --- a/test/unit/personal-message-manager-test.js +++ b/test/unit/personal-message-manager-test.js @@ -1,29 +1,27 @@ const assert = require('assert') -const extend = require('xtend') -const EventEmitter = require('events') const PersonalMessageManager = require('../../app/scripts/lib/personal-message-manager') -describe('Personal Message Manager', function() { +describe('Personal Message Manager', function () { let messageManager - beforeEach(function() { + beforeEach(function () { messageManager = new PersonalMessageManager() }) - describe('#getMsgList', function() { - it('when new should return empty array', function() { + 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() { + it('should also return transactions from local storage if any', function () { }) }) - describe('#addMsg', function() { - it('adds a Msg returned in getMsgList', 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 @@ -33,8 +31,8 @@ describe('Personal Message Manager', function() { }) }) - describe('#setMsgStatusApproved', function() { - it('sets the Msg status to approved', function() { + 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) @@ -45,8 +43,8 @@ describe('Personal Message Manager', function() { }) }) - describe('#rejectMsg', function() { - it('sets the Msg status to rejected', function() { + 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) @@ -57,8 +55,8 @@ describe('Personal Message Manager', function() { }) }) - describe('#_updateMsg', function() { - it('replaces the Msg with the same id', function() { + 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' }) @@ -67,19 +65,19 @@ describe('Personal Message Manager', function() { }) }) - describe('#getUnapprovedMsgs', function() { - it('returns unapproved Msgs in a hash', function() { + 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' }) - let result = messageManager.getUnapprovedMsgs() + 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() { + 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') @@ -87,24 +85,23 @@ describe('Personal Message Manager', function() { }) }) - describe('#normalizeMsgData', function() { - it('converts text to a utf8 hex string', function() { + 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() { + 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() { + 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 new file mode 100644 index 000000000..9fb5e4251 --- /dev/null +++ b/test/unit/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/reducers/unlock_vault_test.js b/test/unit/reducers/unlock_vault_test.js index b7540af08..2b7d70b2c 100644 --- a/test/unit/reducers/unlock_vault_test.js +++ b/test/unit/reducers/unlock_vault_test.js @@ -1,32 +1,31 @@ -var jsdom = require('mocha-jsdom') +// var jsdom = require('mocha-jsdom') var assert = require('assert') -var freeze = require('deep-freeze-strict') +// var freeze = require('deep-freeze-strict') var path = require('path') var sinon = require('sinon') var actions = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'actions.js')) -var reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'reducers.js')) +var reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'reducers.js')) -describe('#unlockMetamask(selectedAccount)', function() { - - beforeEach(function() { +describe('#unlockMetamask(selectedAccount)', function () { + beforeEach(function () { // sinon allows stubbing methods that are easily verified this.sinon = sinon.sandbox.create() }) - afterEach(function() { + afterEach(function () { // sinon requires cleanup otherwise it will overwrite context this.sinon.restore() }) - describe('after an error', function() { - it('clears warning', function() { + describe('after an error', function () { + it('clears warning', function () { const warning = 'this is the wrong warning' const account = 'foo_account' const initialState = { appState: { warning: warning, - } + }, } const resultState = reducers(initialState, actions.unlockMetamask(account)) @@ -34,14 +33,14 @@ describe('#unlockMetamask(selectedAccount)', function() { }) }) - describe('going home after an error', function() { - it('clears warning', function() { + describe('going home after an error', function () { + it('clears warning', function () { const warning = 'this is the wrong warning' - const account = 'foo_account' + // const account = 'foo_account' const initialState = { appState: { warning: warning, - } + }, } const resultState = reducers(initialState, actions.goHome()) diff --git a/test/unit/responsive/components/dropdown-test.js b/test/unit/responsive/components/dropdown-test.js new file mode 100644 index 000000000..982d8c6ec --- /dev/null +++ b/test/unit/responsive/components/dropdown-test.js @@ -0,0 +1,81 @@ +const assert = require('assert'); + +const h = require('react-hyperscript'); +const sinon = require('sinon'); +const path = require('path'); +const Dropdown = require(path.join(__dirname, '..', '..', '..', '..', 'ui', 'app', 'components', 'dropdowns', 'index.js')).Dropdown; + +const { createMockStore } = require('redux-test-utils') +const { mountWithStore } = require('../../../lib/shallow-with-store') + +const mockState = { + metamask: { + } +} + +describe('Dropdown components', function () { + let onClickOutside; + let closeMenu; + let onClick; + + let dropdownComponentProps = { + isOpen: true, + zIndex: 11, + onClickOutside, + style: { + position: 'absolute', + right: 0, + top: '36px', + }, + innerStyle: {}, + } + + let dropdownComponent + let store + let component + beforeEach(function () { + onClickOutside = sinon.spy(); + closeMenu = sinon.spy(); + onClick = sinon.spy(); + + store = createMockStore(mockState) + component = mountWithStore(h( + Dropdown, + dropdownComponentProps, + [ + h('style', ` + .drop-menu-item:hover { background:rgb(235, 235, 235); } + .drop-menu-item i { margin: 11px; } + `), + h('li', { + closeMenu, + onClick, + }, 'Item 1'), + h('li', { + closeMenu, + onClick, + }, 'Item 2'), + ] + ), store) + dropdownComponent = component + }) + + it('can render two items', function () { + const items = dropdownComponent.find('li'); + assert.equal(items.length, 2); + }); + + it('closes when item clicked', function() { + const items = dropdownComponent.find('li'); + const node = items.at(0); + node.simulate('click'); + assert.equal(node.props().closeMenu, closeMenu); + }); + + it('invokes click handler when item clicked', function() { + const items = dropdownComponent.find('li'); + const node = items.at(0); + node.simulate('click'); + assert.equal(onClick.calledOnce, true); + }); +}); diff --git a/test/unit/tx-controller-test.js b/test/unit/tx-controller-test.js new file mode 100644 index 000000000..cc99afee4 --- /dev/null +++ b/test/unit/tx-controller-test.js @@ -0,0 +1,413 @@ +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/lib/tx-gas-utils') +const { createTestProviderTools } = require('../stub/provider') + +const noop = () => true +const currentNetworkId = 42 +const otherNetworkId = 36 +const privKey = new Buffer('8718b9618a37d1fc78c436511fc6df3c8258d3250635bba617f33003270ec03e', 'hex') + + +describe('Transaction Controller', function () { + let txController, provider, providerResultStub, testBlockchain + + beforeEach(function () { + providerResultStub = { + // 1 gwei + eth_gasPrice: '0x0de0b6b3a7640000', + // by default, all accounts are external accounts (not contracts) + eth_getCode: '0x', + } + const providerTools = createTestProviderTools({ scaffold: providerResultStub }) + provider = providerTools.provider + testBlockchain = providerTools.testBlockchain + + txController = new TransactionController({ + provider, + networkStore: new ObservableStore(currentNetworkId), + txHistoryLimit: 10, + blockTracker: { getCurrentBlock: noop, on: noop, once: noop }, + signTransaction: (ethTx) => new Promise((resolve) => { + ethTx.sign(privKey) + resolve() + }), + }) + txController.nonceTracker.getNonceLock = () => Promise.resolve({ nextNonce: 0, releaseLock: noop }) + }) + + 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({}) + .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({}) + .catch(done) + }) + + }) + + describe('#addTxDefaults', 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.addTxDefaults(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('#validateTxParams', function () { + it('does not throw for positive values', function (done) { + var sample = { + value: '0x01', + } + txController.txGasUtil.validateTxParams(sample).then(() => { + done() + }).catch(done) + }) + + it('returns error for negative values', function (done) { + var sample = { + value: '-0x01', + } + txController.txGasUtil.validateTxParams(sample) + .then(() => done('expected to thrown on negativity values but didn\'t')) + .catch((err) => { + assert.ok(err, 'error') + 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(function () { + txMeta = { + id: 1, + status: 'unapproved', + txParams: { + from: '0xc684832530fcbddae4b4230a47e991ddcec2831d', + to: '0x1678a085c290ebd122dc42cba69373b5953b831d', + gasPrice: '0x77359400', + gas: '0x7b0d', + nonce: '0x4b', + }, + metamaskNetworkId: currentNetworkId, + } + }) + it('should update and approve transactions', function () { + txController.txStateManager.addTx(txMeta) + txController.updateAndApproveTransaction(txMeta) + const tx = txController.txStateManager.getTx(1) + assert.equal(tx.status, 'approved') + }) + }) + + 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('#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: 'confimed', 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 new file mode 100644 index 000000000..d9a12d1c3 --- /dev/null +++ b/test/unit/tx-gas-util-test.js @@ -0,0 +1,32 @@ +const assert = require('assert') +const TxGasUtils = require('../../app/scripts/lib/tx-gas-utils') +const { createTestProviderTools } = require('../stub/provider') + +describe('Tx Gas Util', function () { + let txGasUtil, provider, providerResultStub + beforeEach(function () { + providerResultStub = {} + provider = createTestProviderTools({ scaffold: providerResultStub }).provider + txGasUtil = new TxGasUtils({ + provider, + }) + }) + + it('removes recipient for txParams with 0x when contract data is provided', function () { + const zeroRecipientandDataTxParams = { + from: '0x1678a085c290ebd122dc42cba69373b5953b831d', + to: '0x', + data: 'bytecode', + } + const sanitizedTxParams = txGasUtil.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(() => { txGasUtil.validateRecipient(zeroRecipientTxParams) }, Error, 'Invalid recipient address') + }) +}) diff --git a/test/unit/tx-helper-test.js b/test/unit/tx-helper-test.js new file mode 100644 index 000000000..cc6543c30 --- /dev/null +++ b/test/unit/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/tx-manager-test.js b/test/unit/tx-manager-test.js deleted file mode 100644 index 21e94357b..000000000 --- a/test/unit/tx-manager-test.js +++ /dev/null @@ -1,241 +0,0 @@ -const assert = require('assert') -const extend = require('xtend') -const EventEmitter = require('events') -const ethUtil = require('ethereumjs-util') -const EthTx = require('ethereumjs-tx') -const ObservableStore = require('obs-store') -const STORAGE_KEY = 'metamask-persistance-key' -const TransactionManager = require('../../app/scripts/transaction-manager') -const noop = () => true -const currentNetworkId = 42 -const otherNetworkId = 36 -const privKey = new Buffer('8718b9618a37d1fc78c436511fc6df3c8258d3250635bba617f33003270ec03e', 'hex') - -describe('Transaction Manager', function() { - let txManager - - beforeEach(function() { - txManager = new TransactionManager({ - networkStore: new ObservableStore({ network: currentNetworkId }), - txHistoryLimit: 10, - blockTracker: new EventEmitter(), - signTransaction: (ethTx) => new Promise((resolve) => { - ethTx.sign(privKey) - resolve() - }) - }) - }) - - describe('#validateTxParams', function () { - it('returns null for positive values', function() { - var sample = { - value: '0x01' - } - var res = txManager.txProviderUtils.validateTxParams(sample, (err) => { - assert.equal(err, null, 'no error') - }) - }) - - it('returns error for negative values', function() { - var sample = { - value: '-0x01' - } - var res = txManager.txProviderUtils.validateTxParams(sample, (err) => { - assert.ok(err, 'error') - }) - }) - }) - - describe('#getTxList', function() { - it('when new should return empty array', function() { - var result = txManager.getTxList() - assert.ok(Array.isArray(result)) - assert.equal(result.length, 0) - }) - it('should also return transactions from local storage if any', function() { - - }) - }) - - describe('#addTx', function() { - it('adds a tx returned in getTxList', function() { - var tx = { id: 1, status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} } - txManager.addTx(tx, noop) - var result = txManager.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() { - var tx = { id: 1, status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} } - var tx2 = { id: 2, status: 'confirmed', metamaskNetworkId: otherNetworkId, txParams: {} } - txManager.addTx(tx, noop) - txManager.addTx(tx2, noop) - var result = txManager.getFullTxList() - var result2 = txManager.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 = txManager.txHistoryLimit - for (let i = 0; i < limit + 1; i++) { - let tx = { id: i, time: new Date(), status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} } - txManager.addTx(tx, noop) - } - var result = txManager.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 = txManager.txHistoryLimit - for (let i = 0; i < limit + 1; i++) { - let tx = { id: i, time: new Date(), status: 'rejected', metamaskNetworkId: currentNetworkId, txParams: {} } - txManager.addTx(tx, noop) - } - var result = txManager.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() { - var unconfirmedTx = { id: 0, time: new Date(), status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} } - txManager.addTx(unconfirmedTx, noop) - const limit = txManager.txHistoryLimit - for (let i = 1; i < limit + 1; i++) { - let tx = { id: i, time: new Date(), status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} } - txManager.addTx(tx, noop) - } - var result = txManager.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('#setTxStatusSigned', function() { - it('sets the tx status to signed', function() { - var tx = { id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} } - txManager.addTx(tx, noop) - txManager.setTxStatusSigned(1) - var result = txManager.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) => { - this.timeout(10000) - var tx = { id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} } - let noop = function () { - assert(true, 'event listener has been triggered and noop executed') - done() - } - txManager.addTx(tx) - txManager.on('1:signed', noop) - txManager.setTxStatusSigned(1) - }) - }) - - describe('#setTxStatusRejected', function() { - it('sets the tx status to rejected', function() { - var tx = { id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} } - txManager.addTx(tx) - txManager.setTxStatusRejected(1) - var result = txManager.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) => { - this.timeout(10000) - var tx = { id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} } - txManager.addTx(tx) - let noop = function (err, txId) { - assert(true, 'event listener has been triggered and noop executed') - done() - } - txManager.on('1:rejected', noop) - txManager.setTxStatusRejected(1) - }) - - }) - - describe('#updateTx', function() { - it('replaces the tx with the same id', function() { - txManager.addTx({ id: '1', status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }, noop) - txManager.addTx({ id: '2', status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} }, noop) - txManager.updateTx({ id: '1', status: 'blah', hash: 'foo', metamaskNetworkId: currentNetworkId, txParams: {} }) - var result = txManager.getTx('1') - assert.equal(result.hash, 'foo') - }) - }) - - describe('#getUnapprovedTxList', function() { - it('returns unapproved txs in a hash', function() { - txManager.addTx({ id: '1', status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }, noop) - txManager.addTx({ id: '2', status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} }, noop) - let result = txManager.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() { - txManager.addTx({ id: '1', status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }, noop) - txManager.addTx({ id: '2', status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} }, noop) - assert.equal(txManager.getTx('1').status, 'unapproved') - assert.equal(txManager.getTx('2').status, 'confirmed') - }) - }) - - describe('#getFilteredTxList', function() { - it('returns a tx with the requested data', function() { - let 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) => txManager.addTx(txMeta, noop)) - let filterParams - - filterParams = { status: 'unapproved', from: '0xaa' } - assert.equal(txManager.getFilteredTxList(filterParams).length, 3, `getFilteredTxList - ${JSON.stringify(filterParams)}`) - filterParams = { status: 'unapproved', to: '0xaa' } - assert.equal(txManager.getFilteredTxList(filterParams).length, 2, `getFilteredTxList - ${JSON.stringify(filterParams)}`) - filterParams = { status: 'confirmed', from: '0xbb' } - assert.equal(txManager.getFilteredTxList(filterParams).length, 3, `getFilteredTxList - ${JSON.stringify(filterParams)}`) - filterParams = { status: 'confirmed' } - assert.equal(txManager.getFilteredTxList(filterParams).length, 5, `getFilteredTxList - ${JSON.stringify(filterParams)}`) - filterParams = { from: '0xaa' } - assert.equal(txManager.getFilteredTxList(filterParams).length, 5, `getFilteredTxList - ${JSON.stringify(filterParams)}`) - filterParams = { to: '0xaa' } - assert.equal(txManager.getFilteredTxList(filterParams).length, 5, `getFilteredTxList - ${JSON.stringify(filterParams)}`) - }) - }) - - describe('#sign replay-protected tx', function() { - it('prepares a tx with the chainId set', function() { - txManager.addTx({ id: '1', status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }, noop) - txManager.signTransaction('1', (err, rawTx) => { - if (err) return assert.fail('it should not fail') - const ethTx = new EthTx(ethUtil.toBuffer(rawTx)) - assert.equal(ethTx.getChainId(), currentNetworkId) - }) - }) - }) - -}) diff --git a/test/unit/tx-state-history-helper-test.js b/test/unit/tx-state-history-helper-test.js new file mode 100644 index 000000000..90cb10713 --- /dev/null +++ b/test/unit/tx-state-history-helper-test.js @@ -0,0 +1,26 @@ +const assert = require('assert') +const clone = require('clone') +const txStateHistoryHelper = require('../../app/scripts/lib/tx-state-history-helper') + +describe('deepCloneFromTxMeta', 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') + }) +}) diff --git a/test/unit/tx-state-history-helper.js b/test/unit/tx-state-history-helper.js new file mode 100644 index 000000000..79ee26d6e --- /dev/null +++ b/test/unit/tx-state-history-helper.js @@ -0,0 +1,46 @@ +const assert = require('assert') +const txStateHistoryHelper = require('../../app/scripts/lib/tx-state-history-helper') +const testVault = require('../data/v17-long-history.json') + + +describe('tx-state-history-helper', 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') + }) + }) + }) + + 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') + }) + +}) diff --git a/test/unit/tx-state-manager-test.js b/test/unit/tx-state-manager-test.js new file mode 100644 index 000000000..02dc52967 --- /dev/null +++ b/test/unit/tx-state-manager-test.js @@ -0,0 +1,284 @@ +const assert = require('assert') +const clone = require('clone') +const ObservableStore = require('obs-store') +const TxStateManager = require('../../app/scripts/lib/tx-state-manager') +const txStateHistoryHelper = require('../../app/scripts/lib/tx-state-history-helper') +const noop = () => true + +describe('TransactionStateManger', 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 + txStateManager.updateTx(updatedTx) + // 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)') + const expectedEntry = { op: 'replace', path: '/txParams/gasPrice', value: desiredGasPrice } + assert.deepEqual(result.history[1], [expectedEntry], 'two history items (initial + diff)') + }) + }) + + 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) + + }) + }) +})
\ No newline at end of file diff --git a/test/unit/tx-utils-test.js b/test/unit/tx-utils-test.js index 93e9e4134..8ca13412e 100644 --- a/test/unit/tx-utils-test.js +++ b/test/unit/tx-utils-test.js @@ -1,19 +1,25 @@ const assert = require('assert') -const ethUtil = require('ethereumjs-util') -const BN = ethUtil.BN +const Transaction = require('ethereumjs-tx') +const BN = require('bn.js') -const TxUtils = require('../../app/scripts/lib/tx-utils') +const { hexToBn, bnToHex } = require('../../app/scripts/lib/util') +const TxUtils = require('../../app/scripts/lib/tx-gas-utils') -describe('txUtils', function() { + +describe('txUtils', function () { let txUtils - before(function() { - txUtils = new 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() { + describe('chain Id', function () { + it('prepares a transaction with the provided chainId', function () { const txParams = { to: '0x70ad465e0bab6504002ad58c744ed89c7da38524', from: '0x69ad465e0bab6504002ad58c744ed89c7da38525', @@ -24,13 +30,13 @@ describe('txUtils', function() { nonce: '0x3', chainId: 42, } - const ethTx = txUtils.buildEthTxFromParams(txParams) + 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() { + 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) @@ -41,20 +47,20 @@ describe('txUtils', function() { 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() { + + 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 inputBn = hexToBn(inputHex) const outputBn = hexToBn(output) const expectedBn = hexToBn(inputHex) assert(outputBn.eq(expectedBn), 'returns the original estimatedGas value') }) - it('buffers up to reccomend gas limit reccomended ceiling', function() { + 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) @@ -65,17 +71,7 @@ describe('txUtils', function() { // const inputBn = hexToBn(inputHex) // const outputBn = hexToBn(output) const expectedHex = bnToHex(ceilGasLimitBn) - assert.equal(output, expectedHex, 'returns the gas limit reccomended ceiling value') + assert.equal(output, expectedHex, 'returns the gas limit recommended ceiling value') }) }) -}) - -// util - -function hexToBn(inputHex) { - return new BN(ethUtil.stripHexPrefix(inputHex), 16) -} - -function bnToHex(inputBn) { - return ethUtil.addHexPrefix(inputBn.toString(16)) -}
\ No newline at end of file +})
\ No newline at end of file diff --git a/test/unit/ui/add-token.spec.js b/test/unit/ui/add-token.spec.js new file mode 100644 index 000000000..69b7fb620 --- /dev/null +++ b/test/unit/ui/add-token.spec.js @@ -0,0 +1,43 @@ +const assert = require('assert') +const { createMockStore } = require('redux-test-utils') +const h = require('react-hyperscript') +const { shallowWithStore } = require('../../lib/shallow-with-store') +const AddTokenScreen = require('../../../old-ui/app/add-token') + +describe('Add Token Screen', function () { + let addTokenComponent, store, component + const mockState = { + metamask: { + identities: { + '0x7d3517b0d011698406d6e0aed8453f0be2697926': { + 'address': '0x7d3517b0d011698406d6e0aed8453f0be2697926', + 'name': 'Add Token Name', + }, + }, + }, + } + beforeEach(function () { + store = createMockStore(mockState) + component = shallowWithStore(h(AddTokenScreen), store) + addTokenComponent = component.dive() + }) + + describe('#ValidateInputs', function () { + + it('Default State', function () { + addTokenComponent.instance().validateInputs() + const state = addTokenComponent.state() + assert.equal(state.warning, 'Address is invalid.') + }) + + it('Address is a Metamask Identity', function () { + addTokenComponent.setState({ + address: '0x7d3517b0d011698406d6e0aed8453f0be2697926', + }) + addTokenComponent.instance().validateInputs() + const state = addTokenComponent.state() + assert.equal(state.warning, 'Personal address detected. Input the token contract address.') + }) + + }) +}) diff --git a/test/unit/util-test.js b/test/unit/util-test.js new file mode 100644 index 000000000..6da185b2c --- /dev/null +++ b/test/unit/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/util_test.js b/test/unit/util_test.js index 00528b905..59048975a 100644 --- a/test/unit/util_test.js +++ b/test/unit/util_test.js @@ -5,96 +5,96 @@ const ethUtil = require('ethereumjs-util') var path = require('path') var util = require(path.join(__dirname, '..', '..', 'ui', 'app', 'util.js')) -describe('util', function() { +describe('util', function () { var ethInWei = '1' - for (var i = 0; i < 18; i++ ) { ethInWei += '0' } + for (var i = 0; i < 18; i++) { ethInWei += '0' } - beforeEach(function() { + beforeEach(function () { this.sinon = sinon.sandbox.create() }) - afterEach(function() { + afterEach(function () { this.sinon.restore() }) - describe('#parseBalance', function() { - it('should render 0.01 eth correctly', function() { + describe('#parseBalance', function () { + it('should render 0.01 eth correctly', function () { const input = '0x2386F26FC10000' const output = util.parseBalance(input) assert.deepEqual(output, ['0', '01']) }) - it('should render 12.023 eth correctly', function() { + it('should render 12.023 eth correctly', function () { const input = 'A6DA46CCA6858000' const output = util.parseBalance(input) assert.deepEqual(output, ['12', '023']) }) - it('should render 0.0000000342422 eth correctly', function() { + it('should render 0.0000000342422 eth correctly', function () { const input = '0x7F8FE81C0' const output = util.parseBalance(input) assert.deepEqual(output, ['0', '0000000342422']) }) - it('should render 0 eth correctly', function() { + it('should render 0 eth correctly', function () { const input = '0x0' const output = util.parseBalance(input) assert.deepEqual(output, ['0', '0']) }) }) - describe('#addressSummary', function() { - it('should add case-sensitive checksum', function() { + describe('#addressSummary', function () { + it('should add case-sensitive checksum', function () { var address = '0xfdea65c8e26263f6d9a1b5de9555d2931a33b825' var result = util.addressSummary(address) assert.equal(result, '0xFDEa65C8...b825') }) - it('should accept arguments for firstseg, lastseg, and keepPrefix', function() { + it('should accept arguments for firstseg, lastseg, and keepPrefix', function () { var address = '0xfdea65c8e26263f6d9a1b5de9555d2931a33b825' var result = util.addressSummary(address, 4, 4, false) assert.equal(result, 'FDEa...b825') }) }) - describe('#isValidAddress', function() { - it('should allow 40-char non-prefixed hex', function() { + describe('#isValidAddress', function () { + it('should allow 40-char non-prefixed hex', function () { var address = 'fdea65c8e26263f6d9a1b5de9555d2931a33b825' var result = util.isValidAddress(address) assert.ok(result) }) - it('should allow 42-char non-prefixed hex', function() { + it('should allow 42-char non-prefixed hex', function () { var address = '0xfdea65c8e26263f6d9a1b5de9555d2931a33b825' var result = util.isValidAddress(address) assert.ok(result) }) - it('should not allow less non hex-prefixed', function() { + it('should not allow less non hex-prefixed', function () { var address = 'fdea65c8e26263f6d9a1b5de9555d2931a33b85' var result = util.isValidAddress(address) assert.ok(!result) }) - it('should not allow less hex-prefixed', function() { + it('should not allow less hex-prefixed', function () { var address = '0xfdea65ce26263f6d9a1b5de9555d2931a33b85' var result = util.isValidAddress(address) assert.ok(!result) }) - it('should recognize correct capitalized checksum', function() { + it('should recognize correct capitalized checksum', function () { var address = '0xFDEa65C8e26263F6d9A1B5de9555D2931A33b825' var result = util.isValidAddress(address) assert.ok(result) }) - it('should recognize incorrect capitalized checksum', function() { + it('should recognize incorrect capitalized checksum', function () { var address = '0xFDea65C8e26263F6d9A1B5de9555D2931A33b825' var result = util.isValidAddress(address) assert.ok(!result) }) - it('should recognize this sample hashed address', function() { + it('should recognize this sample hashed address', function () { const address = '0x5Fda30Bb72B8Dfe20e48A00dFc108d0915BE9Bb0' const result = util.isValidAddress(address) const hashed = ethUtil.toChecksumAddress(address.toLowerCase()) @@ -103,60 +103,57 @@ describe('util', function() { }) }) - describe('#numericBalance', function() { - - it('should return a BN 0 if given nothing', function() { + describe('#numericBalance', function () { + it('should return a BN 0 if given nothing', function () { var result = util.numericBalance() assert.equal(result.toString(10), 0) }) - it('should work with hex prefix', function() { + it('should work with hex prefix', function () { var result = util.numericBalance('0x012') assert.equal(result.toString(10), '18') }) - it('should work with no hex prefix', function() { + it('should work with no hex prefix', function () { var result = util.numericBalance('012') assert.equal(result.toString(10), '18') }) - }) - describe('#formatBalance', function() { - - it('when given nothing', function() { + describe('#formatBalance', function () { + it('when given nothing', function () { var result = util.formatBalance() assert.equal(result, 'None', 'should return "None"') }) - it('should return eth as string followed by ETH', function() { + it('should return eth as string followed by ETH', function () { var input = new ethUtil.BN(ethInWei, 10).toJSON() var result = util.formatBalance(input, 4) assert.equal(result, '1.0000 ETH') }) - it('should return eth as string followed by ETH', function() { + it('should return eth as string followed by ETH', function () { var input = new ethUtil.BN(ethInWei, 10).div(new ethUtil.BN('2', 10)).toJSON() var result = util.formatBalance(input, 3) assert.equal(result, '0.500 ETH') }) - it('should display specified decimal points', function() { - var input = "0x128dfa6a90b28000" + it('should display specified decimal points', function () { + var input = '0x128dfa6a90b28000' var result = util.formatBalance(input, 2) assert.equal(result, '1.33 ETH') }) - it('should default to 3 decimal points', function() { - var input = "0x128dfa6a90b28000" + it('should default to 3 decimal points', function () { + var input = '0x128dfa6a90b28000' var result = util.formatBalance(input) assert.equal(result, '1.337 ETH') }) - it('should show 2 significant digits for tiny balances', function() { - var input = "0x1230fa6a90b28" + it('should show 2 significant digits for tiny balances', function () { + var input = '0x1230fa6a90b28' var result = util.formatBalance(input) assert.equal(result, '0.00032 ETH') }) - it('should not parse the balance and return value with 2 decimal points with ETH at the end', function() { + it('should not parse the balance and return value with 2 decimal points with ETH at the end', function () { var value = '1.2456789' var needsParse = false var result = util.formatBalance(value, 2, needsParse) @@ -164,17 +161,16 @@ describe('util', function() { }) }) - describe('normalizing values', function() { - - describe('#normalizeToWei', function() { - it('should convert an eth to the appropriate equivalent values', function() { + describe('normalizing values', function () { + describe('#normalizeToWei', function () { + it('should convert an eth to the appropriate equivalent values', function () { var valueTable = { - wei: '1000000000000000000', - kwei: '1000000000000000', - mwei: '1000000000000', - gwei: '1000000000', + wei: '1000000000000000000', + kwei: '1000000000000000', + mwei: '1000000000000', + gwei: '1000000000', szabo: '1000000', - finney:'1000', + finney: '1000', ether: '1', // kether:'0.001', // mether:'0.000001', @@ -185,8 +181,7 @@ describe('util', function() { } var oneEthBn = new ethUtil.BN(ethInWei, 10) - for(var currency in valueTable) { - + for (var currency in valueTable) { var value = new ethUtil.BN(valueTable[currency], 10) var output = util.normalizeToWei(value, currency) assert.equal(output.toString(10), valueTable.wei, `value of ${output.toString(10)} ${currency} should convert to ${oneEthBn}`) @@ -194,60 +189,70 @@ describe('util', function() { }) }) - describe('#normalizeEthStringToWei', function() { - it('should convert decimal eth to pure wei BN', function() { + describe('#normalizeEthStringToWei', function () { + it('should convert decimal eth to pure wei BN', function () { var input = '1.23456789' var output = util.normalizeEthStringToWei(input) assert.equal(output.toString(10), '1234567890000000000') }) - it('should convert 1 to expected wei', function() { + it('should convert 1 to expected wei', function () { var input = '1' var output = util.normalizeEthStringToWei(input) assert.equal(output.toString(10), ethInWei) }) - }) - describe('#normalizeNumberToWei', function() { + it('should account for overflow numbers gracefully by dropping extra precision.', function () { + var input = '1.11111111111111111111' + var output = util.normalizeEthStringToWei(input) + assert.equal(output.toString(10), '1111111111111111111') + }) + + it('should not truncate very exact wei values that do not have extra precision.', function () { + var input = '1.100000000000000001' + var output = util.normalizeEthStringToWei(input) + assert.equal(output.toString(10), '1100000000000000001') + }) + }) - it('should handle a simple use case', function() { + describe('#normalizeNumberToWei', function () { + it('should handle a simple use case', function () { var input = 0.0002 var output = util.normalizeNumberToWei(input, 'ether') var str = output.toString(10) assert.equal(str, '200000000000000') }) - it('should convert a kwei number to the appropriate equivalent wei', function() { + it('should convert a kwei number to the appropriate equivalent wei', function () { var result = util.normalizeNumberToWei(1.111, 'kwei') assert.equal(result.toString(10), '1111', 'accepts decimals') }) - it('should convert a ether number to the appropriate equivalent wei', function() { + it('should convert a ether number to the appropriate equivalent wei', function () { var result = util.normalizeNumberToWei(1.111, 'ether') assert.equal(result.toString(10), '1111000000000000000', 'accepts decimals') }) }) - describe('#isHex', function(){ - it('should return true when given a hex string', function() { + describe('#isHex', function () { + it('should return true when given a hex string', function () { var result = util.isHex('c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2') assert(result) }) - it('should return false when given a non-hex string', function() { + it('should return false when given a non-hex string', function () { var result = util.isHex('c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714imnotreal') assert(!result) }) - it('should return false when given a string containing a non letter/number character', function() { + it('should return false when given a string containing a non letter/number character', function () { var result = util.isHex('c3ab8ff13720!8ad9047dd39466b3c%8974e592c2fa383d4a396071imnotreal') assert(!result) }) - it('should return true when given a hex string with hex-prefix', function() { + it('should return true when given a hex string with hex-prefix', function () { var result = util.isHex('0xc3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2') assert(result) }) - }) }) }) |