import assert from 'assert' import sinon from 'sinon' import { cloneDeep } from 'lodash' import configureStore from 'redux-mock-store' import thunk from 'redux-thunk' import EthQuery from 'eth-query' import Eth from 'ethjs' import KeyringController from 'eth-keyring-controller' import { createTestProviderTools } from '../../../stub/provider' import enLocale from '../../../../app/_locales/en/messages.json' import * as actions from '../../../../ui/app/store/actions' import MetaMaskController from '../../../../app/scripts/metamask-controller' import firstTimeState from '../../localhostState' const { provider } = createTestProviderTools({ scaffold: {} }) const middleware = [thunk] const defaultState = { metamask: {} } const mockStore = (state = defaultState) => configureStore(middleware)(state) describe('Actions', function () { const noop = () => undefined const currentNetworkId = '42' let background, metamaskController const TEST_SEED = 'debris dizzy just program just float decrease vacant alarm reduce speak stadium' const password = 'a-fake-password' const importPrivkey = '4cfd3e90fc78b0f86bf7524722150bb8da9c60cd532564d7ff43f5716514f553' beforeEach(async function () { metamaskController = new MetaMaskController({ platform: { getVersion: () => 'foo' }, provider, keyringController: new KeyringController({}), showUnapprovedTx: noop, showUnconfirmedMessage: noop, encryptor: { encrypt (_, object) { this.object = object return Promise.resolve('mock-encrypted') }, decrypt () { return Promise.resolve(this.object) }, }, initState: cloneDeep(firstTimeState), }) metamaskController.threeBoxController = { new3Box: sinon.spy(), getThreeBoxSyncingState: sinon.spy(), } await metamaskController.createNewVaultAndRestore(password, TEST_SEED) await metamaskController.importAccountWithStrategy('Private Key', [importPrivkey]) background = metamaskController.getApi() actions._setBackgroundConnection(background) global.ethQuery = new EthQuery(provider) }) describe('#tryUnlockMetamask', function () { let submitPasswordSpy, verifySeedPhraseSpy afterEach(function () { submitPasswordSpy.restore() verifySeedPhraseSpy.restore() }) it('calls submitPassword and verifySeedPhrase', async function () { const store = mockStore() submitPasswordSpy = sinon.spy(background, 'submitPassword') verifySeedPhraseSpy = sinon.spy(background, 'verifySeedPhrase') await store.dispatch(actions.tryUnlockMetamask()) assert(submitPasswordSpy.calledOnce) assert(verifySeedPhraseSpy.calledOnce) }) it('errors on submitPassword will fail', async function () { const store = mockStore() const expectedActions = [ { type: 'SHOW_LOADING_INDICATION', value: undefined }, { type: 'UNLOCK_IN_PROGRESS' }, { type: 'UNLOCK_FAILED', value: 'error in submitPassword' }, { type: 'HIDE_LOADING_INDICATION' }, ] submitPasswordSpy = sinon.stub(background, 'submitPassword') submitPasswordSpy.callsFake((_, callback) => { callback(new Error('error in submitPassword')) }) try { await store.dispatch(actions.tryUnlockMetamask('test')) assert.fail('Should have thrown error') } catch (_) { assert.deepEqual(store.getActions(), expectedActions) } }) it('displays warning error and unlock failed when verifySeed fails', async function () { const store = mockStore() const displayWarningError = [{ type: 'DISPLAY_WARNING', value: 'error' }] const unlockFailedError = [{ type: 'UNLOCK_FAILED', value: 'error' }] verifySeedPhraseSpy = sinon.stub(background, 'verifySeedPhrase') verifySeedPhraseSpy.callsFake((callback) => { callback(new Error('error')) }) try { await store.dispatch(actions.tryUnlockMetamask('test')) assert.fail('Should have thrown error') } catch (_) { const actions1 = store.getActions() const warning = actions1.filter((action) => action.type === 'DISPLAY_WARNING') const unlockFailed = actions1.filter((action) => action.type === 'UNLOCK_FAILED') assert.deepEqual(warning, displayWarningError) assert.deepEqual(unlockFailed, unlockFailedError) } }) }) describe('#createNewVaultAndRestore', function () { let createNewVaultAndRestoreSpy afterEach(function () { createNewVaultAndRestoreSpy.restore() }) it('restores new vault', async function () { const store = mockStore() createNewVaultAndRestoreSpy = sinon.spy(background, 'createNewVaultAndRestore') try { await store.dispatch(actions.createNewVaultAndRestore()) assert.fail('Should have thrown error') } catch (_) { assert(createNewVaultAndRestoreSpy.calledOnce) } }) it('errors when callback in createNewVaultAndRestore throws', async function () { const store = mockStore() const expectedActions = [ { type: 'SHOW_LOADING_INDICATION', value: undefined }, { type: 'DISPLAY_WARNING', value: 'error' }, { type: 'HIDE_LOADING_INDICATION' }, ] createNewVaultAndRestoreSpy = sinon.stub(background, 'createNewVaultAndRestore') createNewVaultAndRestoreSpy.callsFake((_, __, callback) => { callback(new Error('error')) }) try { await store.dispatch(actions.createNewVaultAndRestore()) assert.fail('Should have thrown error') } catch (_) { assert.deepEqual(store.getActions(), expectedActions) } }) }) describe('#requestRevealSeedWords', function () { let submitPasswordSpy afterEach(function () { submitPasswordSpy.restore() }) it('calls submitPassword in background', async function () { const store = mockStore() submitPasswordSpy = sinon.spy(background, 'verifySeedPhrase') await store.dispatch(actions.requestRevealSeedWords()) assert(submitPasswordSpy.calledOnce) }) it('displays warning error message then callback in background errors', async function () { const store = mockStore() const expectedActions = [ { type: 'SHOW_LOADING_INDICATION', value: undefined }, { type: 'HIDE_LOADING_INDICATION' }, { type: 'DISPLAY_WARNING', value: 'error' }, ] submitPasswordSpy = sinon.stub(background, 'verifySeedPhrase') submitPasswordSpy.callsFake((callback) => { callback(new Error('error')) }) try { await store.dispatch(actions.requestRevealSeedWords()) assert.fail('Should have thrown error') } catch (_) { assert.deepEqual(store.getActions(), expectedActions) } }) }) describe('#removeAccount', function () { let removeAccountStub afterEach(function () { removeAccountStub.restore() }) it('calls removeAccount in background and expect actions to show account', async function () { const store = mockStore() const expectedActions = [ 'SHOW_LOADING_INDICATION', 'SELECTED_ADDRESS_CHANGED', 'UPDATE_METAMASK_STATE', 'HIDE_LOADING_INDICATION', 'SHOW_ACCOUNTS_PAGE', ] removeAccountStub = sinon.stub(background, 'removeAccount') removeAccountStub.callsFake((_, callback) => callback()) await store.dispatch(actions.removeAccount('0xe18035bf8712672935fdb4e5e431b1a0183d2dfc')) assert(removeAccountStub.calledOnce) const actionTypes = store .getActions() .map((action) => action.type) assert.deepEqual(actionTypes, expectedActions) }) it('displays warning error message when removeAccount callback errors', async function () { const store = mockStore() const expectedActions = [ 'SHOW_LOADING_INDICATION', 'DISPLAY_WARNING', 'HIDE_LOADING_INDICATION', ] removeAccountStub = sinon.stub(background, 'removeAccount') removeAccountStub.callsFake((_, callback) => { callback(new Error('error')) }) try { await store.dispatch(actions.removeAccount('0xe18035bf8712672935fdb4e5e431b1a0183d2dfc')) assert.fail('Should have thrown error') } catch (_) { const actionTypes = store .getActions() .map((action) => action.type) assert.deepEqual(actionTypes, expectedActions) } }) }) describe('#resetAccount', function () { let resetAccountSpy afterEach(function () { resetAccountSpy.restore() }) it('resets account', async function () { const store = mockStore() const expectedActions = [ { type: 'SHOW_LOADING_INDICATION', value: undefined }, { type: 'HIDE_LOADING_INDICATION' }, { type: 'SHOW_ACCOUNTS_PAGE' }, ] resetAccountSpy = sinon.spy(background, 'resetAccount') await store.dispatch(actions.resetAccount()) assert(resetAccountSpy.calledOnce) assert.deepEqual(store.getActions(), expectedActions) }) it('throws if resetAccount throws', async function () { const store = mockStore() const expectedActions = [ { type: 'SHOW_LOADING_INDICATION', value: undefined }, { type: 'HIDE_LOADING_INDICATION' }, { type: 'DISPLAY_WARNING', value: 'error' }, ] resetAccountSpy = sinon.stub(background, 'resetAccount') resetAccountSpy.callsFake((callback) => { callback(new Error('error')) }) try { await store.dispatch(actions.resetAccount()) assert.fail('Should have thrown error') } catch (_) { assert.deepEqual(store.getActions(), expectedActions) } }) }) describe('#importNewAccount', function () { let importAccountWithStrategySpy afterEach(function () { importAccountWithStrategySpy.restore() }) it('calls importAccountWithStrategies in background', async function () { const store = mockStore() importAccountWithStrategySpy = sinon.spy(background, 'importAccountWithStrategy') await store.dispatch(actions.importNewAccount('Private Key', [ 'c87509a1c067bbde78beb793e6fa76530b6382a4c0241e5e4a9ec0a0f44dc0d3', ])) assert(importAccountWithStrategySpy.calledOnce) }) it('displays warning error message when importAccount in background callback errors', async function () { const store = mockStore() const expectedActions = [ { type: 'SHOW_LOADING_INDICATION', value: 'This may take a while, please be patient.' }, { type: 'HIDE_LOADING_INDICATION' }, { type: 'DISPLAY_WARNING', value: 'error' }, ] importAccountWithStrategySpy = sinon.stub(background, 'importAccountWithStrategy') importAccountWithStrategySpy.callsFake((_, __, callback) => { callback(new Error('error')) }) try { await store.dispatch(actions.importNewAccount()) assert.fail('Should have thrown error') } catch (_) { assert.deepEqual(store.getActions(), expectedActions) } }) }) describe('#addNewAccount', function () { it('Adds a new account', async function () { const store = mockStore({ metamask: { identities: {} } }) const addNewAccountSpy = sinon.spy(background, 'addNewAccount') await store.dispatch(actions.addNewAccount()) assert(addNewAccountSpy.calledOnce) }) }) describe('#checkHardwareStatus', function () { let checkHardwareStatusSpy afterEach(function () { checkHardwareStatusSpy.restore() }) it('calls checkHardwareStatus in background', async function () { checkHardwareStatusSpy = sinon.stub(background, 'checkHardwareStatus') .callsArgWith(2, null) const store = mockStore() await store.dispatch(actions.checkHardwareStatus('ledger', `m/44'/60'/0'/0`)) assert.equal(checkHardwareStatusSpy.calledOnce, true) }) it('shows loading indicator and displays error', async function () { checkHardwareStatusSpy = sinon.stub(background, 'checkHardwareStatus') .callsArgWith(2, new Error('error')) const store = mockStore() const expectedActions = [ { type: 'SHOW_LOADING_INDICATION', value: undefined }, { type: 'DISPLAY_WARNING', value: 'error' }, ] try { await store.dispatch(actions.checkHardwareStatus()) assert.fail('Should have thrown error') } catch (_) { assert.deepEqual(store.getActions(), expectedActions) } }) }) describe('#forgetDevice', function () { let forgetDeviceSpy afterEach(function () { forgetDeviceSpy.restore() }) it('calls forgetDevice in background', async function () { forgetDeviceSpy = sinon.stub(background, 'forgetDevice') .callsArgWith(1, null) const store = mockStore() await store.dispatch(actions.forgetDevice('ledger')) assert.equal(forgetDeviceSpy.calledOnce, true) }) it('shows loading indicator and displays error', async function () { forgetDeviceSpy = sinon.stub(background, 'forgetDevice') .callsArgWith(1, new Error('error')) const store = mockStore() const expectedActions = [ { type: 'SHOW_LOADING_INDICATION', value: undefined }, { type: 'DISPLAY_WARNING', value: 'error' }, ] try { await store.dispatch(actions.forgetDevice()) assert.fail('Should have thrown error') } catch (_) { assert.deepEqual(store.getActions(), expectedActions) } }) }) describe('#connectHardware', function () { let connectHardwareSpy afterEach(function () { connectHardwareSpy.restore() }) it('calls connectHardware in background', async function () { connectHardwareSpy = sinon.stub(background, 'connectHardware') .callsArgWith(3, null) const store = mockStore() await store.dispatch(actions.connectHardware('ledger', 0, `m/44'/60'/0'/0`)) assert.equal(connectHardwareSpy.calledOnce, true) }) it('shows loading indicator and displays error', async function () { connectHardwareSpy = sinon.stub(background, 'connectHardware') .callsArgWith(3, new Error('error')) const store = mockStore() const expectedActions = [ { type: 'SHOW_LOADING_INDICATION', value: 'Looking for your Ledger...' }, { type: 'DISPLAY_WARNING', value: 'error' }, ] try { await store.dispatch(actions.connectHardware('ledger')) assert.fail('Should have thrown error') } catch (_) { assert.deepEqual(store.getActions(), expectedActions) } }) }) describe('#unlockHardwareWalletAccount', function () { let unlockHardwareWalletAccountSpy afterEach(function () { unlockHardwareWalletAccountSpy.restore() }) it('calls unlockHardwareWalletAccount in background', async function () { unlockHardwareWalletAccountSpy = sinon.stub(background, 'unlockHardwareWalletAccount') .callsArgWith(3, null) const store = mockStore() await store.dispatch(actions.unlockHardwareWalletAccount('ledger', 0, `m/44'/60'/0'/0`)) assert.equal(unlockHardwareWalletAccountSpy.calledOnce, true) }) it('shows loading indicator and displays error', async function () { unlockHardwareWalletAccountSpy = sinon.stub(background, 'unlockHardwareWalletAccount') .callsArgWith(3, new Error('error')) const store = mockStore() const expectedActions = [ { type: 'SHOW_LOADING_INDICATION', value: undefined }, { type: 'DISPLAY_WARNING', value: 'error' }, ] try { await store.dispatch(actions.unlockHardwareWalletAccount()) assert.fail('Should have thrown error') } catch (error) { assert.deepEqual(store.getActions(), expectedActions) } }) }) describe('#setCurrentCurrency', function () { let setCurrentCurrencySpy afterEach(function () { setCurrentCurrencySpy.restore() }) it('calls setCurrentCurrency', async function () { setCurrentCurrencySpy = sinon.stub(background, 'setCurrentCurrency') .callsArgWith(1, null, {}) const store = mockStore() await store.dispatch(actions.setCurrentCurrency('jpy')) assert(setCurrentCurrencySpy.calledOnce) }) it('throws if setCurrentCurrency throws', async function () { setCurrentCurrencySpy = sinon.stub(background, 'setCurrentCurrency') .callsArgWith(1, new Error('error')) const store = mockStore() const expectedActions = [ { type: 'SHOW_LOADING_INDICATION', value: undefined }, { type: 'HIDE_LOADING_INDICATION' }, { type: 'DISPLAY_WARNING', value: 'error' }, ] await store.dispatch(actions.setCurrentCurrency()) assert.deepEqual(store.getActions(), expectedActions) }) }) describe('#signMsg', function () { let signMessageSpy, metamaskMsgs, msgId, messages const msgParams = { from: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc', data: '0x879a053d4800c6354e76c7985a865d2922c82fb5b3f4577b2fe08b998954f2e0', } beforeEach(function () { metamaskController.newUnsignedMessage(msgParams, noop) metamaskMsgs = metamaskController.messageManager.getUnapprovedMsgs() messages = metamaskController.messageManager.messages msgId = Object.keys(metamaskMsgs)[0] messages[0].msgParams.metamaskId = parseInt(msgId, 10) }) afterEach(function () { signMessageSpy.restore() }) it('calls signMsg in background', async function () { const store = mockStore() signMessageSpy = sinon.spy(background, 'signMessage') await store.dispatch(actions.signMsg(msgParams)) assert(signMessageSpy.calledOnce) }) it('errors when signMessage in background throws', async function () { const store = mockStore() const expectedActions = [ { type: 'SHOW_LOADING_INDICATION', value: undefined }, { type: 'HIDE_LOADING_INDICATION' }, { type: 'DISPLAY_WARNING', value: 'error' }, ] signMessageSpy = sinon.stub(background, 'signMessage') signMessageSpy.callsFake((_, callback) => { callback(new Error('error')) }) try { await store.dispatch(actions.signMsg()) assert.fail('Should have thrown error') } catch (_) { assert.deepEqual(store.getActions(), expectedActions) } }) }) describe('#signPersonalMsg', function () { let signPersonalMessageSpy, metamaskMsgs, msgId, personalMessages const msgParams = { from: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc', data: '0x879a053d4800c6354e76c7985a865d2922c82fb5b3f4577b2fe08b998954f2e0', } beforeEach(function () { metamaskController.newUnsignedPersonalMessage(msgParams, noop) metamaskMsgs = metamaskController.personalMessageManager.getUnapprovedMsgs() personalMessages = metamaskController.personalMessageManager.messages msgId = Object.keys(metamaskMsgs)[0] personalMessages[0].msgParams.metamaskId = parseInt(msgId, 10) }) afterEach(function () { signPersonalMessageSpy.restore() }) it('calls signPersonalMessage', async function () { const store = mockStore() signPersonalMessageSpy = sinon.spy(background, 'signPersonalMessage') await store.dispatch(actions.signPersonalMsg(msgParams)) assert(signPersonalMessageSpy.calledOnce) }) it('throws if signPersonalMessage throws', async function () { const store = mockStore() const expectedActions = [ { type: 'SHOW_LOADING_INDICATION', value: undefined }, { type: 'HIDE_LOADING_INDICATION' }, { type: 'DISPLAY_WARNING', value: 'error' }, ] signPersonalMessageSpy = sinon.stub(background, 'signPersonalMessage') signPersonalMessageSpy.callsFake((_, callback) => { callback(new Error('error')) }) try { await store.dispatch(actions.signPersonalMsg(msgParams)) assert.fail('Should have thrown error') } catch (_) { assert.deepEqual(store.getActions(), expectedActions) } }) }) describe('#signTypedMsg', function () { let signTypedMsgSpy, messages, typedMessages, msgId const msgParamsV3 = { from: '0x0DCD5D886577d5081B0c52e242Ef29E70Be3E7bc', data: JSON.stringify({ 'types': { 'EIP712Domain': [ { 'name': 'name', 'type': 'string' }, { 'name': 'version', 'type': 'string' }, { 'name': 'chainId', 'type': 'uint256' }, { 'name': 'verifyingContract', 'type': 'address' }, ], 'Person': [ { 'name': 'name', 'type': 'string' }, { 'name': 'wallet', 'type': 'address' }, ], 'Mail': [ { 'name': 'from', 'type': 'Person' }, { 'name': 'to', 'type': 'Person' }, { 'name': 'contents', 'type': 'string' }, ], }, 'primaryType': 'Mail', 'domain': { 'name': 'Ether Mainl', 'version': '1', 'verifyingContract': '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', }, 'message': { 'from': { 'name': 'Cow', 'wallet': '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', }, 'to': { 'name': 'Bob', 'wallet': '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', }, 'contents': 'Hello, Bob!', }, }), } beforeEach(function () { metamaskController.newUnsignedTypedMessage(msgParamsV3, null, 'V3') messages = metamaskController.typedMessageManager.getUnapprovedMsgs() typedMessages = metamaskController.typedMessageManager.messages msgId = Object.keys(messages)[0] typedMessages[0].msgParams.metamaskId = parseInt(msgId, 10) }) afterEach(function () { signTypedMsgSpy.restore() }) it('calls signTypedMsg in background with no error', async function () { signTypedMsgSpy = sinon.stub(background, 'signTypedMessage') .callsArgWith(1, null, defaultState) const store = mockStore() await store.dispatch(actions.signTypedMsg(msgParamsV3)) assert(signTypedMsgSpy.calledOnce) }) it('returns expected actions with error', async function () { signTypedMsgSpy = sinon.stub(background, 'signTypedMessage') .callsArgWith(1, new Error('error')) const store = mockStore() const expectedActions = [ { type: 'SHOW_LOADING_INDICATION', value: undefined }, { type: 'HIDE_LOADING_INDICATION' }, { type: 'DISPLAY_WARNING', value: 'error' }, ] try { await store.dispatch(actions.signTypedMsg()) assert.fail('Should have thrown error') } catch (_) { assert.deepEqual(store.getActions(), expectedActions) } }) }) describe('#signTx', function () { let sendTransactionSpy beforeEach(function () { sendTransactionSpy = sinon.stub(global.ethQuery, 'sendTransaction') }) afterEach(function () { sendTransactionSpy.restore() }) it('calls sendTransaction in global ethQuery', function () { const store = mockStore() store.dispatch(actions.signTx()) assert(sendTransactionSpy.calledOnce) }) it('errors in when sendTransaction throws', function () { const store = mockStore() const expectedActions = [ { type: 'DISPLAY_WARNING', value: 'error' }, { type: 'SHOW_CONF_TX_PAGE', id: undefined }, ] sendTransactionSpy.callsFake((_, callback) => { callback(new Error('error')) }) store.dispatch(actions.signTx()) assert.deepEqual(store.getActions(), expectedActions) }) }) describe('#updatedGasData', function () { it('errors when get code does not return', async function () { const store = mockStore() const expectedActions = [ { type: 'GAS_LOADING_STARTED' }, { type: 'UPDATE_SEND_ERRORS', value: { gasLoadingError: 'gasLoadingError' } }, { type: 'GAS_LOADING_FINISHED' }, ] const mockData = { gasPrice: '0x3b9aca00', // blockGasLimit: '0x6ad79a', // 7002010 selectedAddress: '0x0DCD5D886577d5081B0c52e242Ef29E70Be3E7bc', to: '0xEC1Adf982415D2Ef5ec55899b9Bfb8BC0f29251B', value: '0xde0b6b3a7640000', // 1000000000000000000 } try { await store.dispatch(actions.updateGasData(mockData)) assert.fail('Should have thrown error') } catch (error) { assert.deepEqual(store.getActions(), expectedActions) } }) it('returns default gas limit for basic eth transaction', async function () { const mockData = { gasPrice: '0x3b9aca00', blockGasLimit: '0x6ad79a', // 7002010 selectedAddress: '0x0DCD5D886577d5081B0c52e242Ef29E70Be3E7bc', to: '0xEC1Adf982415D2Ef5ec55899b9Bfb8BC0f29251B', value: '0xde0b6b3a7640000', // 1000000000000000000 } global.eth = { getCode: sinon.stub().returns('0x'), } const store = mockStore() const expectedActions = [ { type: 'GAS_LOADING_STARTED' }, { type: 'UPDATE_GAS_LIMIT', value: '0x5208' }, { type: 'metamask/gas/SET_CUSTOM_GAS_LIMIT', value: '0x5208' }, { type: 'UPDATE_SEND_ERRORS', value: { gasLoadingError: null } }, { type: 'GAS_LOADING_FINISHED' }, ] await store.dispatch(actions.updateGasData(mockData)) assert.deepEqual(store.getActions(), expectedActions) global.eth.getCode.reset() }) }) describe('#signTokenTx', function () { it('calls eth.contract', function () { global.eth = new Eth(provider) const tokenSpy = sinon.spy(global.eth, 'contract') const store = mockStore() store.dispatch(actions.signTokenTx()) assert(tokenSpy.calledOnce) tokenSpy.restore() }) }) describe('#updateTransaction', function () { let updateTransactionSpy const txParams = { 'from': '0x1', 'gas': '0x5208', 'gasPrice': '0x3b9aca00', 'to': '0x2', 'value': '0x0', } const txData = { id: '1', status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams } beforeEach(async function () { await metamaskController.txController.txStateManager.addTx(txData) }) afterEach(function () { updateTransactionSpy.restore() }) it('updates transaction', async function () { const store = mockStore() updateTransactionSpy = sinon.spy(background, 'updateTransaction') await store.dispatch(actions.updateTransaction(txData)) const resultantActions = store.getActions() assert.ok(updateTransactionSpy.calledOnce) assert.deepEqual(resultantActions[1], { type: 'UPDATE_TRANSACTION_PARAMS', id: txData.id, value: txParams }) }) it('rejects with error message', async function () { const store = mockStore() updateTransactionSpy = sinon.stub(background, 'updateTransaction') updateTransactionSpy.callsFake((_, callback) => { callback(new Error('error')) }) try { await store.dispatch(actions.updateTransaction(txData)) assert.fail('Should have thrown error') } catch (error) { assert.equal(error.message, 'error') } }) }) describe('#lockMetamask', function () { let backgroundSetLockedSpy afterEach(function () { backgroundSetLockedSpy.restore() }) it('calls setLocked', async function () { const store = mockStore() backgroundSetLockedSpy = sinon.spy(background, 'setLocked') await store.dispatch(actions.lockMetamask()) assert(backgroundSetLockedSpy.calledOnce) }) it('returns display warning error with value when setLocked in background callback errors', async function () { const store = mockStore() const expectedActions = [ { type: 'SHOW_LOADING_INDICATION', value: undefined }, { type: 'DISPLAY_WARNING', value: 'error' }, { type: 'HIDE_LOADING_INDICATION' }, { type: 'LOCK_METAMASK' }, ] backgroundSetLockedSpy = sinon.stub(background, 'setLocked') backgroundSetLockedSpy.callsFake((callback) => { callback(new Error('error')) }) try { await store.dispatch(actions.lockMetamask()) assert.fail('Should have thrown error') } catch (error) { assert.deepEqual(store.getActions(), expectedActions) } }) }) describe('#setSelectedAddress', function () { let setSelectedAddressSpy afterEach(function () { setSelectedAddressSpy.restore() }) it('calls setSelectedAddress in background', async function () { setSelectedAddressSpy = sinon.stub(background, 'setSelectedAddress') .callsArgWith(1, null) const store = mockStore() await store.dispatch(actions.setSelectedAddress('0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc')) assert(setSelectedAddressSpy.calledOnce) }) it('errors when setSelectedAddress throws', async function () { setSelectedAddressSpy = sinon.stub(background, 'setSelectedAddress') .callsArgWith(1, new Error('error')) const store = mockStore() const expectedActions = [ { type: 'SHOW_LOADING_INDICATION', value: undefined }, { type: 'HIDE_LOADING_INDICATION' }, { type: 'DISPLAY_WARNING', value: 'error' }, ] await store.dispatch(actions.setSelectedAddress()) assert.deepEqual(store.getActions(), expectedActions) }) }) describe('#showAccountDetail', function () { let setSelectedAddressSpy afterEach(function () { setSelectedAddressSpy.restore() }) it('#showAccountDetail', async function () { setSelectedAddressSpy = sinon.stub(background, 'setSelectedAddress') .callsArgWith(1, null) const store = mockStore({ activeTab: {}, metamask: { alertEnabledness: {}, selectedAddress: '0x123' } }) await store.dispatch(actions.showAccountDetail()) assert(setSelectedAddressSpy.calledOnce) }) it('displays warning if setSelectedAddress throws', async function () { setSelectedAddressSpy = sinon.stub(background, 'setSelectedAddress') .callsArgWith(1, new Error('error')) const store = mockStore({ activeTab: {}, metamask: { alertEnabledness: {}, selectedAddress: '0x123' } }) const expectedActions = [ { type: 'SHOW_LOADING_INDICATION', value: undefined }, { type: 'HIDE_LOADING_INDICATION' }, { type: 'DISPLAY_WARNING', value: 'error' }, ] await store.dispatch(actions.showAccountDetail()) assert.deepEqual(store.getActions(), expectedActions) }) }) describe('#addToken', function () { let addTokenSpy afterEach(function () { addTokenSpy.restore() }) it('calls addToken in background', async function () { addTokenSpy = sinon.stub(background, 'addToken') .callsArgWith(4, null) const store = mockStore() await store.dispatch(actions.addToken()) assert(addTokenSpy.calledOnce) }) it('errors when addToken in background throws', async function () { addTokenSpy = sinon.stub(background, 'addToken') .callsArgWith(4, new Error('error')) const store = mockStore() const expectedActions = [ { type: 'SHOW_LOADING_INDICATION', value: undefined }, { type: 'HIDE_LOADING_INDICATION' }, { type: 'DISPLAY_WARNING', value: 'error' }, ] try { await store.dispatch(actions.addToken()) assert.fail('Should have thrown error') } catch (_) { assert.deepEqual(store.getActions(), expectedActions) } }) }) describe('#removeToken', function () { let removeTokenSpy afterEach(function () { removeTokenSpy.restore() }) it('calls removeToken in background', async function () { removeTokenSpy = sinon.stub(background, 'removeToken') .callsArgWith(1, null) const store = mockStore() await store.dispatch(actions.removeToken()) assert(removeTokenSpy.calledOnce) }) it('errors when removeToken in background fails', async function () { removeTokenSpy = sinon.stub(background, 'removeToken') .callsArgWith(1, new Error('error')) const store = mockStore() const expectedActions = [ { type: 'SHOW_LOADING_INDICATION', value: undefined }, { type: 'HIDE_LOADING_INDICATION' }, { type: 'DISPLAY_WARNING', value: 'error' }, ] try { await store.dispatch(actions.removeToken()) assert.fail('Should have thrown error') } catch (_) { assert.deepEqual(store.getActions(), expectedActions) } }) }) describe('#setProviderType', function () { let setProviderTypeSpy let store beforeEach(function () { store = mockStore({ metamask: { provider: {} } }) }) afterEach(function () { setProviderTypeSpy.restore() }) it('calls setProviderType', async function () { setProviderTypeSpy = sinon.stub(background, 'setProviderType') .callsArgWith(1, null) await store.dispatch(actions.setProviderType()) assert(setProviderTypeSpy.calledOnce) }) it('displays warning when setProviderType throws', async function () { setProviderTypeSpy = sinon.stub(background, 'setProviderType') .callsArgWith(1, new Error('error')) const expectedActions = [ { type: 'DISPLAY_WARNING', value: 'Had a problem changing networks!' }, ] await store.dispatch(actions.setProviderType()) assert(setProviderTypeSpy.calledOnce) assert.deepEqual(store.getActions(), expectedActions) }) }) describe('#setRpcTarget', function () { let setRpcTargetSpy afterEach(function () { setRpcTargetSpy.restore() }) it('calls setRpcTarget', async function () { setRpcTargetSpy = sinon.stub(background, 'setCustomRpc') .callsArgWith(4, null) const store = mockStore() await store.dispatch(actions.setRpcTarget('http://localhost:8545')) assert(setRpcTargetSpy.calledOnce) }) it('displays warning when setRpcTarget throws', async function () { setRpcTargetSpy = sinon.stub(background, 'setCustomRpc') .callsArgWith(4, new Error('error')) const store = mockStore() const expectedActions = [ { type: 'DISPLAY_WARNING', value: 'Had a problem changing networks!' }, ] await store.dispatch(actions.setRpcTarget()) assert.deepEqual(store.getActions(), expectedActions) }) }) describe('#addToAddressBook', function () { it('calls setAddressBook', async function () { const addToAddressBookSpy = sinon.stub(background, 'setAddressBook') .callsArgWith(4, null, true) const store = mockStore() await store.dispatch(actions.addToAddressBook('test')) assert(addToAddressBookSpy.calledOnce) addToAddressBookSpy.restore() }) }) describe('#exportAccount', function () { let verifyPasswordSpy, exportAccountSpy afterEach(function () { verifyPasswordSpy.restore() exportAccountSpy.restore() }) it('returns expected actions for successful action', async function () { const store = mockStore() const expectedActions = [ { type: 'SHOW_LOADING_INDICATION', value: undefined }, { type: 'HIDE_LOADING_INDICATION' }, { type: 'SHOW_PRIVATE_KEY', value: '7ec73b91bb20f209a7ff2d32f542c3420b4fccf14abcc7840d2eff0ebcb18505' }, ] verifyPasswordSpy = sinon.spy(background, 'verifyPassword') exportAccountSpy = sinon.spy(background, 'exportAccount') await store.dispatch(actions.exportAccount(password, '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc')) assert(verifyPasswordSpy.calledOnce) assert(exportAccountSpy.calledOnce) assert.deepEqual(store.getActions(), expectedActions) }) it('returns action errors when first func callback errors', async function () { const store = mockStore() const expectedActions = [ { type: 'SHOW_LOADING_INDICATION', value: undefined }, { type: 'HIDE_LOADING_INDICATION' }, { type: 'DISPLAY_WARNING', value: 'Incorrect Password.' }, ] verifyPasswordSpy = sinon.stub(background, 'verifyPassword') verifyPasswordSpy.callsFake((_, callback) => { callback(new Error('error')) }) try { await store.dispatch(actions.exportAccount(password, '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc')) assert.fail('Should have thrown error') } catch (_) { assert.deepEqual(store.getActions(), expectedActions) } }) it('returns action errors when second func callback errors', async function () { const store = mockStore() const expectedActions = [ { type: 'SHOW_LOADING_INDICATION', value: undefined }, { type: 'HIDE_LOADING_INDICATION' }, { type: 'DISPLAY_WARNING', value: 'Had a problem exporting the account.' }, ] exportAccountSpy = sinon.stub(background, 'exportAccount') exportAccountSpy.callsFake((_, callback) => { callback(new Error('error')) }) try { await store.dispatch(actions.exportAccount(password, '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc')) assert.fail('Should have thrown error') } catch (_) { assert.deepEqual(store.getActions(), expectedActions) } }) }) describe('#setAccountLabel', function () { it('calls setAccountLabel', async function () { const setAccountLabelSpy = sinon.stub(background, 'setAccountLabel') .callsArgWith(2, null) const store = mockStore() await store.dispatch(actions.setAccountLabel('0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc', 'test')) assert(setAccountLabelSpy.calledOnce) }) }) describe('#setFeatureFlag', function () { let setFeatureFlagSpy afterEach(function () { setFeatureFlagSpy.restore() }) it('calls setFeatureFlag in the background', async function () { setFeatureFlagSpy = sinon.stub(background, 'setFeatureFlag') .callsArgWith(2, null) const store = mockStore() await store.dispatch(actions.setFeatureFlag()) assert(setFeatureFlagSpy.calledOnce) }) it('errors when setFeatureFlag in background throws', async function () { setFeatureFlagSpy = sinon.stub(background, 'setFeatureFlag') .callsArgWith(2, new Error('error')) const store = mockStore() const expectedActions = [ { type: 'SHOW_LOADING_INDICATION', value: undefined }, { type: 'HIDE_LOADING_INDICATION' }, { type: 'DISPLAY_WARNING', value: 'error' }, ] try { await store.dispatch(actions.setFeatureFlag()) assert.fail('Should have thrown error') } catch (_) { assert.deepEqual(store.getActions(), expectedActions) } }) }) describe('#setCompletedOnboarding', function () { it('completes onboarding', async function () { const completeOnboardingSpy = sinon.stub(background, 'completeOnboarding') completeOnboardingSpy.callsFake((cb) => cb()) const store = mockStore() await store.dispatch(actions.setCompletedOnboarding()) assert.equal(completeOnboardingSpy.callCount, 1) completeOnboardingSpy.restore() }) }) describe('#setUseBlockie', function () { let setUseBlockieSpy beforeEach(function () { setUseBlockieSpy = sinon.stub(background, 'setUseBlockie') }) afterEach(function () { setUseBlockieSpy.restore() }) it('calls setUseBlockie in background', function () { const store = mockStore() store.dispatch(actions.setUseBlockie()) assert(setUseBlockieSpy.calledOnce) }) it('errors when setUseBlockie in background throws', function () { const store = mockStore() const expectedActions = [ { type: 'SHOW_LOADING_INDICATION', value: undefined }, { type: 'HIDE_LOADING_INDICATION' }, { type: 'DISPLAY_WARNING', value: 'error' }, { type: 'SET_USE_BLOCKIE', value: undefined }, ] setUseBlockieSpy.callsFake((_, callback) => { callback(new Error('error')) }) store.dispatch(actions.setUseBlockie()) assert.deepEqual(store.getActions(), expectedActions) }) }) describe('#updateCurrentLocale', function () { let setCurrentLocaleSpy beforeEach(function () { sinon.stub(window, 'fetch') .resolves({ json: async () => enLocale, }) }) afterEach(function () { setCurrentLocaleSpy.restore() window.fetch.restore() }) it('calls expected actions', async function () { const store = mockStore() setCurrentLocaleSpy = sinon.spy(background, 'setCurrentLocale') const expectedActions = [ { type: 'SHOW_LOADING_INDICATION', value: undefined }, { type: 'SET_CURRENT_LOCALE', value: { locale: 'en', messages: enLocale } }, { type: 'HIDE_LOADING_INDICATION' }, ] await store.dispatch(actions.updateCurrentLocale('en')) assert(setCurrentLocaleSpy.calledOnce) assert.deepEqual(store.getActions(), expectedActions) }) it('errors when setCurrentLocale throws', async function () { const store = mockStore() const expectedActions = [ { type: 'SHOW_LOADING_INDICATION', value: undefined }, { type: 'HIDE_LOADING_INDICATION' }, { type: 'DISPLAY_WARNING', value: 'error' }, ] setCurrentLocaleSpy = sinon.stub(background, 'setCurrentLocale') setCurrentLocaleSpy.callsFake((_, callback) => { callback(new Error('error')) }) try { await store.dispatch(actions.updateCurrentLocale('en')) assert.fail('Should have thrown error') } catch (_) { assert.deepEqual(store.getActions(), expectedActions) } }) }) describe('#markPasswordForgotten', function () { it('calls markPasswordForgotten', async function () { const store = mockStore() const markPasswordForgottenSpy = sinon.stub(background, 'markPasswordForgotten').callsArg(0) await store.dispatch(actions.markPasswordForgotten()) const resultantActions = store.getActions() assert.deepEqual(resultantActions[1], { type: 'FORGOT_PASSWORD', value: true }) assert.ok(markPasswordForgottenSpy.calledOnce) markPasswordForgottenSpy.restore() }) }) describe('#unMarkPasswordForgotten', function () { it('calls unMarkPasswordForgotten', async function () { const store = mockStore() const unMarkPasswordForgottenSpy = sinon.stub(background, 'unMarkPasswordForgotten').callsArg(0) await store.dispatch(actions.unMarkPasswordForgotten()) const resultantActions = store.getActions() assert.deepEqual(resultantActions[0], { type: 'FORGOT_PASSWORD', value: false }) assert.ok(unMarkPasswordForgottenSpy.calledOnce) unMarkPasswordForgottenSpy.restore() }) }) })