diff options
Diffstat (limited to 'test')
25 files changed, 1123 insertions, 262 deletions
diff --git a/test/integration/helpers.js b/test/integration/helpers.js index 95c36017a..eede103b4 100644 --- a/test/integration/helpers.js +++ b/test/integration/helpers.js @@ -1,7 +1,7 @@ -function wait() { +function wait(time) { return new Promise(function(resolve, reject) { setTimeout(function() { resolve() - }, 500) + }, time * 3 || 1500) }) } diff --git a/test/integration/index.html b/test/integration/index.html index 6de40b046..8a54cb829 100644 --- a/test/integration/index.html +++ b/test/integration/index.html @@ -12,10 +12,10 @@ <script src="https://code.jquery.com/qunit/qunit-2.0.0.js"></script> <script src="./jquery-3.1.0.min.js"></script> <script src="helpers.js"></script> - <script src="tests.js"></script> + <script src="bundle.js"></script> <script src="/testem.js"></script> - <iframe src="/development/index.html" height="500px" width="360px"> + <iframe src="/development/test.html" height="500px" width="360px"> <p>Your browser does not support iframes</p> </iframe> </body> diff --git a/test/integration/index.js b/test/integration/index.js new file mode 100644 index 000000000..ff6d1baf8 --- /dev/null +++ b/test/integration/index.js @@ -0,0 +1,21 @@ +var fs = require('fs') +var path = require('path') +var browserify = require('browserify'); +var tests = fs.readdirSync(path.join(__dirname, 'lib')) +var bundlePath = path.join(__dirname, 'bundle.js') + +var b = browserify(); + +// Remove old bundle +try { + fs.unlinkSync(bundlePath) +} catch (e) {} + +var writeStream = fs.createWriteStream(bundlePath) + +tests.forEach(function(fileName) { + b.add(path.join(__dirname, 'lib', fileName)) +}) + +b.bundle().pipe(writeStream); + diff --git a/test/integration/lib/first-time.js b/test/integration/lib/first-time.js new file mode 100644 index 000000000..1811ccbd4 --- /dev/null +++ b/test/integration/lib/first-time.js @@ -0,0 +1,90 @@ +const PASSWORD = 'password123' + +QUnit.module('first time usage') + +QUnit.test('agree to terms', function (assert) { + var done = assert.async() + let app + + wait().then(function() { + app = $('iframe').contents().find('#app-content .mock-app-root') + app.find('.markdown').prop('scrollTop', 100000000) + return wait() + + }).then(function() { + + var title = app.find('h1').text() + assert.equal(title, 'MetaMask', 'title screen') + + var pwBox = app.find('#password-box')[0] + var confBox = app.find('#password-box-confirm')[0] + + pwBox.value = PASSWORD + confBox.value = PASSWORD + return wait() + + }).then(function() { + + var createButton = app.find('button.primary')[0] + createButton.click() + + return wait(1500) + }).then(function() { + + var terms = app.find('h3.terms-header')[0] + assert.equal(terms.textContent, 'MetaMask Terms & Conditions', 'Showing TOS') + + // Scroll through terms + var scrollable = app.find('.markdown')[0] + scrollable.scrollTop = scrollable.scrollHeight + + return wait(10) + }).then(function() { + + var button = app.find('button')[0] // Agree button + button.click() + + return wait(1000) + }).then(function() { + + var created = app.find('h3')[0] + assert.equal(created.textContent, 'Vault Created', 'Vault created screen') + + var button = app.find('button')[0] // Agree button + button.click() + + return wait(1000) + }).then(function() { + + var detail = app.find('.account-detail-section')[0] + assert.ok(detail, 'Account detail section loaded.') + + var sandwich = app.find('.sandwich-expando')[0] + sandwich.click() + + return wait() + }).then(function() { + + var sandwich = app.find('.menu-droppo')[0] + var lock = sandwich.children[2] + assert.ok(lock, 'Lock menu item found') + lock.click() + + return wait(1000) + }).then(function() { + + var pwBox = app.find('#password-box')[0] + pwBox.value = PASSWORD + + var createButton = app.find('button.primary')[0] + createButton.click() + + return wait(1000) + }).then(function() { + + var detail = app.find('.account-detail-section')[0] + assert.ok(detail, 'Account detail section loaded again.') + + done() + }) +}) diff --git a/test/integration/lib/idStore-migrator-test.js b/test/integration/lib/idStore-migrator-test.js new file mode 100644 index 000000000..4ae30411d --- /dev/null +++ b/test/integration/lib/idStore-migrator-test.js @@ -0,0 +1,91 @@ +var ConfigManager = require('../../../app/scripts/lib/config-manager') +var IdStoreMigrator = require('../../../app/scripts/lib/idStore-migrator') +var SimpleKeyring = require('../../../app/scripts/keyrings/simple') +var normalize = require('../../../app/scripts/lib/sig-util').normalize + +var oldStyleVault = require('../mocks/oldVault.json') +var badStyleVault = require('../mocks/badVault.json') + +var STORAGE_KEY = 'metamask-config' +var PASSWORD = '12345678' +var FIRST_ADDRESS = '0x4dd5d356c5A016A220bCD69e82e5AF680a430d00'.toLowerCase() +var SEED = 'fringe damage bounce extend tunnel afraid alert sound all soldier all dinner' + +var BAD_STYLE_FIRST_ADDRESS = '0xac39b311dceb2a4b2f5d8461c1cdaf756f4f7ae9' + +QUnit.module('Old Style Vaults', { + beforeEach: function () { + window.localStorage[STORAGE_KEY] = JSON.stringify(oldStyleVault) + + this.configManager = new ConfigManager({ + loadData: () => { return JSON.parse(window.localStorage[STORAGE_KEY]) }, + setData: (data) => { window.localStorage[STORAGE_KEY] = JSON.stringify(data) }, + }) + + this.migrator = new IdStoreMigrator({ + configManager: this.configManager, + }) + } +}) + +QUnit.test('migrator:isInitialized', function (assert) { + assert.ok(this.migrator) +}) + +QUnit.test('migrator:migratedVaultForPassword', function (assert) { + var done = assert.async() + + this.migrator.migratedVaultForPassword(PASSWORD) + .then((result) => { + const { serialized, lostAccounts } = result + assert.equal(serialized.data.mnemonic, SEED, 'seed phrase recovered') + assert.equal(lostAccounts.length, 0, 'no lost accounts') + done() + }) +}) + +QUnit.module('Old Style Vaults with bad HD seed', { + beforeEach: function () { + window.localStorage[STORAGE_KEY] = JSON.stringify(badStyleVault) + + this.configManager = new ConfigManager({ + loadData: () => { return JSON.parse(window.localStorage[STORAGE_KEY]) }, + setData: (data) => { window.localStorage[STORAGE_KEY] = JSON.stringify(data) }, + }) + + this.migrator = new IdStoreMigrator({ + configManager: this.configManager, + }) + } +}) + +QUnit.test('migrator:migratedVaultForPassword', function (assert) { + var done = assert.async() + + this.migrator.migratedVaultForPassword(PASSWORD) + .then((result) => { + const { serialized, lostAccounts } = result + + assert.equal(lostAccounts.length, 1, 'one lost account') + assert.equal(lostAccounts[0].address, '0xe15D894BeCB0354c501AE69429B05143679F39e0'.toLowerCase()) + assert.ok(lostAccounts[0].privateKey, 'private key exported') + + var lostAccount = lostAccounts[0] + var privateKey = lostAccount.privateKey + + var simple = new SimpleKeyring() + simple.deserialize([privateKey]) + .then(() => { + return simple.getAccounts() + }) + .then((accounts) => { + assert.equal(normalize(accounts[0]), lostAccount.address, 'recovered address.') + done() + }) + .catch((reason) => { + assert.ifError(reason) + done(reason) + }) + }) +}) + diff --git a/test/integration/mocks/badVault.json b/test/integration/mocks/badVault.json new file mode 100644 index 000000000..a59e4626a --- /dev/null +++ b/test/integration/mocks/badVault.json @@ -0,0 +1 @@ +{"meta":{"version":4},"data":{"fiatCurrency":"USD","conversionRate":8.34908448,"conversionDate":1481227505,"isConfirmed":true,"wallet":"{\"encSeed\":{\"encStr\":\"Te2KyAGY3S01bgUJ+7d4y3BOvr/8TKrXrkRZ29cGI6dgyedtN+YgTQxElC2td/pzuoXm7KeSfr+yAoFCvMgqFAJwRcX3arHOsMFQie8kp8mL5I65zwdg/HB2QecB4OJHytrxgApv2zZiKEo0kbu2cs8zYIn5wNlCBIHwgylYmHpUDIJcO1B4zg==\",\"nonce\":\"xnxqk4iy70bjt721F+KPLV4PNfBFNyct\"},\"ksData\":{\"m/44'/60'/0'/0\":{\"info\":{\"curve\":\"secp256k1\",\"purpose\":\"sign\"},\"encHdPathPriv\":{\"encStr\":\"vNrSjekRKLmaGFf77Uca9+aAebmDlvrBwtAV8YthpQ4OX/mXtLSycmnLsYdk4schaByfJvrm6/Mf9fxzOSaScJk+XvKw5XqNXedkDHtbWrmNnxFpuT+9tuB8Nupr3D9GZK9PgXhJD99/7Bn6Wk7/ne+PIDmbtdmx/SWmrdo3pg==\",\"nonce\":\"zqWq/gtJ5zfUVRWQQJkP/zoYjer6Rozj\"},\"hdIndex\":1,\"encPrivKeys\":{\"e15d894becb0354c501ae69429b05143679f39e0\":{\"key\":\"jBLQ9v1l5LOEY1C3kI8z7LpbJKHP1vpVfPAlz90MNSfa8Oe+XlxKQAGYs8Zb4fWm\",\"nonce\":\"fJyrSRo1t0RMNqp2MsneoJnYJWHQnSVY\"}},\"addresses\":[\"e15d894becb0354c501ae69429b05143679f39e0\"]}},\"encHdRootPriv\":{\"encStr\":\"mbvwiFBQGbjj4BJLmdeYzfYi8jb7gtFtwiCQOPfvmyz4h2/KMbHNGzumM16qRKpifioQXkhnBulMIQHaYg0Jwv1MoFsqHxHmuIAT+QP5XvJjz0MRl6708pHowmIVG+R8CZNTLqzE7XS8YkZ4ElRpTvLEM8Wngi5Sg287mQMP9w==\",\"nonce\":\"i5Tp2lQe92rXQzNhjZcu9fNNhfux6Wf4\"},\"salt\":\"FQpA8D9R/5qSp9WtQ94FILyfWZHMI6YZw6RmBYqK0N0=\",\"version\":2}","config":{"provider":{"type":"testnet"},"selectedAccount":"0xe15d894becb0354c501ae69429b05143679f39e0"},"isEthConfirmed":true,"transactions":[],"TOSHash":"a4f4e23f823a7ac51783e7ffba7914a911b09acdb97263296b7e14b527f80c5b","gasMultiplier":1}} diff --git a/test/integration/mocks/badVault2.json b/test/integration/mocks/badVault2.json new file mode 100644 index 000000000..4b7aec386 --- /dev/null +++ b/test/integration/mocks/badVault2.json @@ -0,0 +1 @@ +{"meta":{"version":4},"data":{"fiatCurrency":"USD","isConfirmed":true,"TOSHash":"a4f4e23f823a7ac51783e7ffba7914a911b09acdb97263296b7e14b527f80c5b","noticesList":[{"read":true,"date":"Fri Dec 16 2016","title":"Ending Morden Support","body":"Due to [recent events](https://blog.ethereum.org/2016/11/20/from-morden-to-ropsten/), MetaMask is now deprecating support for the Morden Test Network.\n\nUsers will still be able to access Morden through a locally hosted node, but we will no longer be providing hosted access to this network through [Infura](http://infura.io/).\n\nPlease use the new Ropsten Network as your new default test network.\n\nYou can fund your Ropsten account using the buy button on your account page.\n\nBest wishes!\nThe MetaMask Team\n\n","id":0}],"conversionRate":7.07341909,"conversionDate":1482539284,"wallet":"{\"encSeed\":{\"encStr\":\"LZsdN8lJzYkUe1UpmAalnERdgkBFt25gWDdK8kfQUwMAk/27XR+dc+8n5swgoF5qgwhc9LBgliEGNDs1Q/lnuld3aQLabkOeAW4BHS1vS7FxqKrzDS3iyzSuQO6wDQmGno/buuknVgDsKiyjW22hpt7vtVVWA+ZL1P3x6M0+AxGJjeGVrG+E8Q==\",\"nonce\":\"T6O9BmwmTj214XUK3KF0s3iCKo3OlrUD\"},\"ksData\":{\"m/44'/60'/0'/0\":{\"info\":{\"curve\":\"secp256k1\",\"purpose\":\"sign\"},\"encHdPathPriv\":{\"encStr\":\"GNNfZevCMlgMVh9y21y1UwrC9qcmH6XYq7v+9UoqbHnzPQJFlxidN5+x/Sldo72a6+5zJpQkkdZ+Q0lePrzvXfuSd3D/RO7WKFIKo9nAQI5+JWwz4INuCmVcmqCv2J4BTLGjrG8fp5pDJ62Bn0XHqkJo3gx3fpvs3cS66+ZKwg==\",\"nonce\":\"HRTlGj44khQs2veYHEF/GqTI1t0yYvyd\"},\"hdIndex\":3,\"encPrivKeys\":{\"e15d894becb0354c501ae69429b05143679f39e0\":{\"key\":\"ZAeZL9VcRUtiiO4VXOQKBFg787PR5R3iymjUeU5vpDRIqOXbjWN6N4ZNR8YpSXl+\",\"nonce\":\"xLsADagS8uqDYae6cImyhxF7o1kBDbPe\"},\"87658c15aefe7448008a28513a11b6b130ef4cd0\":{\"key\":\"ku0mm5s1agRJNAMYIJO0qeoDe+FqcbqdQI6azXF3GL1OLo6uMlt6I4qS+eeravFi\",\"nonce\":\"xdGfSUPKtkW8ge0SWIbbpahs/NyEMzn5\"},\"aa25854c0379e53c957ac9382e720c577fa31fd5\":{\"key\":\"NjpYC9FbiC95CTx/1kwgOHk5LSN9vl4RULEBbvwfVOjqSH8WixNoP3R6I/QyNIs2\",\"nonce\":\"M/HWpXXA9QvuZxEykkGQPJKKdz33ovQr\"}},\"addresses\":[\"e15d894becb0354c501ae69429b05143679f39e0\",\"87658c15aefe7448008a28513a11b6b130ef4cd0\",\"aa25854c0379e53c957ac9382e720c577fa31fd5\"]}},\"encHdRootPriv\":{\"encStr\":\"f+3prUOzl+95aNAV+ad6lZdsYZz120ZsL67ucjj3tiMXf/CC4X8XB9N2QguhoMy6fW+fATUsTdJe8+CbAAyb79V9HY0Pitzq9Yw/g1g0/Ii2JzsdGBriuMsPdwZSVqz+rvQFw/6Qms1xjW6cqa8S7kM2WA5l8RB1Ck6r5zaqbA==\",\"nonce\":\"oGahxNFekVxH9sg6PUCCHIByvo4WFSqm\"},\"salt\":\"N7xYoEA53yhSweOsEphku1UKkIEuZtX2MwLBhVM6RR8=\",\"version\":2}","config":{"provider":{"type":"testnet"},"selectedAccount":"0xe15d894becb0354c501ae69429b05143679f39e0"},"isDisclaimerConfirmed":true,"walletNicknames":{"0xac39b311dceb2a4b2f5d8461c1cdaf756f4f7ae9":"Account 1","0xd7c0cd9e7d2701c710d64fc492c7086679bdf7b4":"Account 2","0x1acfb961c5a8268eac8e09d6241a26cbeff42241":"Account 3"},"lostAccounts":["0xe15d894becb0354c501ae69429b05143679f39e0","0x87658c15aefe7448008a28513a11b6b130ef4cd0","0xaa25854c0379e53c957ac9382e720c577fa31fd5"]}}
\ No newline at end of file diff --git a/test/integration/mocks/oldVault.json b/test/integration/mocks/oldVault.json new file mode 100644 index 000000000..5861c41d7 --- /dev/null +++ b/test/integration/mocks/oldVault.json @@ -0,0 +1,21 @@ +{ + "meta": { + "version": 4 + }, + "data": { + "fiatCurrency": "USD", + "isConfirmed": true, + "TOSHash": "a4f4e23f823a7ac51783e7ffba7914a911b09acdb97263296b7e14b527f80c5b", + "conversionRate": 9.47316629, + "conversionDate": 1479510994, + "wallet": "{\"encSeed\":{\"encStr\":\"a5tjKtDGlHkua+6Ta5s3wMFWPmsBqaPdMKGmqeI2z1kMbNs3V03HBaCptU7NtMra1DjHKbSNsUToxFUrmrvWBmUejamN16+l1CviwqASsv7kKzpot00/dfyyJgtZwwFP5Je+TAB1V231nRbPidOfeE1cDec5V8KTF8epl6qzsbA25pjeW76Dfw==\",\"nonce\":\"RzID6bAhWfGTSR74xdIh3RaT1+1sLk6F\"},\"ksData\":{\"m/44'/60'/0'/0\":{\"info\":{\"curve\":\"secp256k1\",\"purpose\":\"sign\"},\"encHdPathPriv\":{\"encStr\":\"6nlYAopRbmGcqerRZO08XwgeYaCJg9XRhh4oiYiVVdQtyNPdxvOI9TcE/mqvBiatMwBwA+TmsqTV6eZZe/VDZKYIGajKulQbScd0xQ71JhYfqqmzSG6EH2Pnzwa+aSAsfARgN1JJSaff2+p6wV6Zg5BUDtl72OGEIEfXhcUGwg==\",\"nonce\":\"Ee1KiDqtx7NvYToQUFvjEhKNinNQcXlK\"},\"hdIndex\":1,\"encPrivKeys\":{\"4dd5d356c5a016a220bcd69e82e5af680a430d00\":{\"key\":\"htGRGAH10lGF4M+fvioznmYVIUSWAzwp/yWSIo85psgZZwmCdJY72oyGanYsrFO8\",\"nonce\":\"PkP8XeZ+ok215rzEorvJu9nYTWzkOVr0\"}},\"addresses\":[\"4dd5d356c5a016a220bcd69e82e5af680a430d00\"]}},\"encHdRootPriv\":{\"encStr\":\"TAZAo71a+4IlAaoA66f0w4ts2f+V7ArTSUHRIrMltfAPXz7GfJBmKXNtHPORUYAjRiKqWK6FZnhKLf7Vcng2LG7VnDQwC4xPxzSRZzSEilnoY3V+zRY0HD7Wb/pndb4FliA/buZQmjohO4vezeX0hl70rJlPJEZTyYoWgxbxFA==\",\"nonce\":\"FlJOaLyBEHMaH5fEnYjdHc6nn18+WkRj\"},\"salt\":\"CmuCcWpbqpKUUv+1aE2ZwvQl7EIQ731uFibSq++vwtY=\",\"version\":2}", + "config": { + "provider": { + "type": "testnet" + }, + "selectedAccount": "0x4dd5d356c5a016a220bcd69e82e5af680a430d00" + }, + "showSeedWords": false, + "isEthConfirmed": true + } +} diff --git a/test/integration/tests.js b/test/integration/tests.js deleted file mode 100644 index 92111b05b..000000000 --- a/test/integration/tests.js +++ /dev/null @@ -1,24 +0,0 @@ -QUnit.test('agree to terms', function (assert) { - var done = assert.async() - - // Select the mock app root - var app = $('iframe').contents().find('#app-content .mock-app-root') - - app.find('.markdown').prop('scrollTop', 100000000) - - wait().then(function() { - app.find('button').click() - }).then(function() { - return wait() - }).then(function() { - var title = app.find('h1').text() - assert.equal(title, 'MetaMask', 'title screen') - - var buttons = app.find('button') - assert.equal(buttons.length, 2, 'two buttons: create and restore') - - done() - }) - - // Wait for view to transition: -}) diff --git a/test/lib/mock-config-manager.js b/test/lib/mock-config-manager.js index fe841f455..b79f63090 100644 --- a/test/lib/mock-config-manager.js +++ b/test/lib/mock-config-manager.js @@ -1,5 +1,5 @@ var ConfigManager = require('../../app/scripts/lib/config-manager') -const STORAGE_KEY = 'metamask-persistance-key' +const STORAGE_KEY = 'metamask-config' const extend = require('xtend') module.exports = function() { @@ -9,6 +9,7 @@ module.exports = function() { function loadData () { var oldData = getOldStyleData() var newData + try { newData = JSON.parse(window.localStorage[STORAGE_KEY]) } catch (e) {} diff --git a/test/lib/mock-encryptor.js b/test/lib/mock-encryptor.js new file mode 100644 index 000000000..09bbf7ad5 --- /dev/null +++ b/test/lib/mock-encryptor.js @@ -0,0 +1,32 @@ +var mockHex = '0xabcdef0123456789' +var mockKey = new Buffer(32) +let cacheVal + +module.exports = { + + encrypt(password, dataObj) { + cacheVal = dataObj + return Promise.resolve(mockHex) + }, + + decrypt(password, text) { + return Promise.resolve(cacheVal || {}) + }, + + encryptWithKey(key, dataObj) { + return this.encrypt(key, dataObj) + }, + + decryptWithKey(key, text) { + return this.decrypt(key, text) + }, + + keyFromPassword(password) { + return Promise.resolve(mockKey) + }, + + generateSalt() { + return 'WHADDASALT!' + }, + +} diff --git a/test/lib/mock-simple-keychain.js b/test/lib/mock-simple-keychain.js new file mode 100644 index 000000000..615b3e537 --- /dev/null +++ b/test/lib/mock-simple-keychain.js @@ -0,0 +1,38 @@ +var fakeWallet = { + privKey: '0x123456788890abcdef', + address: '0xfedcba0987654321', +} +const type = 'Simple Key Pair' + +module.exports = class MockSimpleKeychain { + + static type() { return type } + + constructor(opts) { + this.type = type + this.opts = opts || {} + this.wallets = [] + } + + serialize() { + return [ fakeWallet.privKey ] + } + + deserialize(data) { + if (!Array.isArray(data)) { + throw new Error('Simple keychain deserialize requires a privKey array.') + } + this.wallets = [ fakeWallet ] + } + + addAccounts(n = 1) { + for(var i = 0; i < n; i++) { + this.wallets.push(fakeWallet) + } + } + + getAccounts() { + return this.wallets.map(w => w.address) + } + +} diff --git a/test/unit/actions/restore_vault_test.js b/test/unit/actions/restore_vault_test.js deleted file mode 100644 index 609f5429e..000000000 --- a/test/unit/actions/restore_vault_test.js +++ /dev/null @@ -1,60 +0,0 @@ -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')) - -describe('#recoverFromSeed(password, seed)', function() { - - beforeEach(function() { - // sinon allows stubbing methods that are easily verified - this.sinon = sinon.sandbox.create() - }) - - afterEach(function() { - // sinon requires cleanup otherwise it will overwrite context - this.sinon.restore() - }) - - // stub out account manager - actions._setAccountManager({ - recoverFromSeed(pw, seed, cb) { - cb(null, { - identities: { - foo: 'bar' - } - }) - }, - }) - - it('sets metamask.isUnlocked to true', function() { - var initialState = { - metamask: { - isUnlocked: false, - isInitialized: false, - } - } - freeze(initialState) - - const restorePhrase = 'invite heavy among daring outdoor dice jelly coil stable note seat vicious' - const password = 'foo' - const dispatchFunc = actions.recoverFromSeed(password, restorePhrase) - - var dispatchStub = this.sinon.stub() - dispatchStub.withArgs({ TYPE: actions.unlockMetamask() }).onCall(0) - dispatchStub.withArgs({ TYPE: actions.showAccountsPage() }).onCall(1) - - var action - var resultingState = initialState - dispatchFunc((newAction) => { - action = newAction - resultingState = reducers(resultingState, action) - }) - - assert.equal(resultingState.metamask.isUnlocked, true, 'was unlocked') - assert.equal(resultingState.metamask.isInitialized, true, 'was initialized') - }); -}); diff --git a/test/unit/actions/set_selected_account_test.js b/test/unit/actions/set_selected_account_test.js index 69eb11e47..f72ca82e4 100644 --- a/test/unit/actions/set_selected_account_test.js +++ b/test/unit/actions/set_selected_account_test.js @@ -44,6 +44,5 @@ describe('SHOW_ACCOUNT_DETAIL', function() { var resultingState = reducers(initialState, action) assert.equal(resultingState.metamask.selectedAccount, action.value) - assert.equal(resultingState.metamask.selectedAddress, action.value) }) }) diff --git a/test/unit/actions/tx_test.js b/test/unit/actions/tx_test.js index c08a8aa26..1f06b1120 100644 --- a/test/unit/actions/tx_test.js +++ b/test/unit/actions/tx_test.js @@ -46,7 +46,7 @@ describe('tx confirmation screen', function() { describe('cancelTx', function() { before(function(done) { - actions._setAccountManager({ + actions._setBackgroundConnection({ approveTransaction(txId, cb) { cb('An error!') }, cancelTransaction(txId) { /* noop */ }, clearSeedWordCache(cb) { cb() }, @@ -75,7 +75,7 @@ describe('tx confirmation screen', function() { before(function(done) { alert = () => {/* noop */} - actions._setAccountManager({ + actions._setBackgroundConnection({ approveTransaction(txId, cb) { cb({message: 'An error!'}) }, }) @@ -96,7 +96,7 @@ describe('tx confirmation screen', function() { describe('when there is success', function() { it('should complete tx and go home', function() { - actions._setAccountManager({ + actions._setBackgroundConnection({ approveTransaction(txId, cb) { cb() }, }) @@ -135,7 +135,7 @@ describe('tx confirmation screen', function() { } freeze(initialState) - actions._setAccountManager({ + actions._setBackgroundConnection({ approveTransaction(txId, cb) { cb() }, }) diff --git a/test/unit/config-manager-test.js b/test/unit/config-manager-test.js index 26aa35a74..77d431d5f 100644 --- a/test/unit/config-manager-test.js +++ b/test/unit/config-manager-test.js @@ -1,8 +1,10 @@ +// polyfill fetch +global.fetch = global.fetch || require('isomorphic-fetch') const assert = require('assert') const extend = require('xtend') const rp = require('request-promise') const nock = require('nock') -var configManagerGen = require('../lib/mock-config-manager') +const configManagerGen = require('../lib/mock-config-manager') const STORAGE_KEY = 'metamask-persistance-key' describe('config-manager', function() { @@ -100,31 +102,31 @@ describe('config-manager', function() { describe('confirmation', function() { - describe('#getConfirmed', function() { + describe('#getConfirmedDisclaimer', function() { it('should return false if no previous key exists', function() { - var result = configManager.getConfirmed() + var result = configManager.getConfirmedDisclaimer() assert.ok(!result) }) }) - describe('#setConfirmed', function() { - it('should make getConfirmed return true once set', function() { - assert.equal(configManager.getConfirmed(), false) - configManager.setConfirmed(true) - var result = configManager.getConfirmed() + describe('#setConfirmedDisclaimer', function() { + it('should make getConfirmedDisclaimer return true once set', function() { + assert.equal(configManager.getConfirmedDisclaimer(), false) + configManager.setConfirmedDisclaimer(true) + var result = configManager.getConfirmedDisclaimer() assert.equal(result, true) }) it('should be able to set false', function() { - configManager.setConfirmed(false) - var result = configManager.getConfirmed() + configManager.setConfirmedDisclaimer(false) + var result = configManager.getConfirmedDisclaimer() assert.equal(result, false) }) it('should persist to local storage', function() { - configManager.setConfirmed(true) + configManager.setConfirmedDisclaimer(true) var data = configManager.getData() - assert.equal(data.isConfirmed, true) + assert.equal(data.isDisclaimerConfirmed, true) }) }) }) @@ -153,7 +155,7 @@ describe('config-manager', function() { rpcTarget: 'foobar' }, } - configManager.setConfirmed(true) + configManager.setConfirmedDisclaimer(true) configManager.setConfig(testConfig) var testWallet = { @@ -164,7 +166,7 @@ describe('config-manager', function() { var result = configManager.getData() assert.equal(result.wallet.name, testWallet.name, 'wallet name is set') assert.equal(result.config.provider.rpcTarget, testConfig.provider.rpcTarget) - assert.equal(configManager.getConfirmed(), true) + assert.equal(configManager.getConfirmedDisclaimer(), true) testConfig.provider.type = 'something else!' configManager.setConfig(testConfig) @@ -173,7 +175,7 @@ describe('config-manager', function() { assert.equal(result.wallet.name, testWallet.name, 'wallet name is set') assert.equal(result.config.provider.rpcTarget, testConfig.provider.rpcTarget) assert.equal(result.config.provider.type, testConfig.provider.type) - assert.equal(configManager.getConfirmed(), true) + assert.equal(configManager.getConfirmedDisclaimer(), true) }) }) @@ -215,7 +217,7 @@ describe('config-manager', function() { describe('transactions', function() { beforeEach(function() { - configManager._saveTxList([]) + configManager.setTxList([]) }) describe('#getTxList', function() { @@ -226,90 +228,13 @@ describe('config-manager', function() { }) }) - describe('#_saveTxList', function() { + describe('#setTxList', function() { it('saves the submitted data to the tx list', function() { var target = [{ foo: 'bar' }] - configManager._saveTxList(target) + configManager.setTxList(target) var result = configManager.getTxList() assert.equal(result[0].foo, 'bar') }) }) - - describe('#addTx', function() { - it('adds a tx returned in getTxList', function() { - var tx = { id: 1 } - configManager.addTx(tx) - var result = configManager.getTxList() - assert.ok(Array.isArray(result)) - assert.equal(result.length, 1) - assert.equal(result[0].id, 1) - }) - - it('cuts off early txs beyond a limit', function() { - const limit = configManager.txLimit - for (let i = 0; i < limit + 1; i++) { - let tx = { id: i } - configManager.addTx(tx) - } - var result = configManager.getTxList() - assert.equal(result.length, limit, `limit of ${limit} txs enforced`) - assert.equal(result[0].id, 1, 'early txs truncted') - }) - }) - - describe('#confirmTx', function() { - it('sets the tx status to confirmed', function() { - var tx = { id: 1, status: 'unconfirmed' } - configManager.addTx(tx) - configManager.confirmTx(1) - var result = configManager.getTxList() - assert.ok(Array.isArray(result)) - assert.equal(result.length, 1) - assert.equal(result[0].status, 'confirmed') - }) - }) - - describe('#rejectTx', function() { - it('sets the tx status to rejected', function() { - var tx = { id: 1, status: 'unconfirmed' } - configManager.addTx(tx) - configManager.rejectTx(1) - var result = configManager.getTxList() - assert.ok(Array.isArray(result)) - assert.equal(result.length, 1) - assert.equal(result[0].status, 'rejected') - }) - }) - - describe('#updateTx', function() { - it('replaces the tx with the same id', function() { - configManager.addTx({ id: '1', status: 'unconfirmed' }) - configManager.addTx({ id: '2', status: 'confirmed' }) - configManager.updateTx({ id: '1', status: 'blah', hash: 'foo' }) - var result = configManager.getTx('1') - assert.equal(result.hash, 'foo') - }) - }) - - describe('#unconfirmedTxs', function() { - it('returns unconfirmed txs in a hash', function() { - configManager.addTx({ id: '1', status: 'unconfirmed' }) - configManager.addTx({ id: '2', status: 'confirmed' }) - let result = configManager.unconfirmedTxs() - assert.equal(typeof result, 'object') - assert.equal(result['1'].status, 'unconfirmed') - assert.equal(result['0'], undefined) - assert.equal(result['2'], undefined) - }) - }) - - describe('#getTx', function() { - it('returns a tx with the requested id', function() { - configManager.addTx({ id: '1', status: 'unconfirmed' }) - configManager.addTx({ id: '2', status: 'confirmed' }) - assert.equal(configManager.getTx('1').status, 'unconfirmed') - assert.equal(configManager.getTx('2').status, 'confirmed') - }) - }) }) }) diff --git a/test/unit/idStore-migration-test.js b/test/unit/idStore-migration-test.js new file mode 100644 index 000000000..54f38fb2f --- /dev/null +++ b/test/unit/idStore-migration-test.js @@ -0,0 +1,146 @@ +const async = require('async') +const assert = require('assert') +const ethUtil = require('ethereumjs-util') +const BN = ethUtil.BN +const ConfigManager = require('../../app/scripts/lib/config-manager') +const delegateCallCode = require('../lib/example-code.json').delegateCallCode + +// The old way: +const IdentityStore = require('../../app/scripts/lib/idStore') +const STORAGE_KEY = 'metamask-config' +const extend = require('xtend') + +// The new ways: +var KeyringController = require('../../app/scripts/keyring-controller') +const mockEncryptor = require('../lib/mock-encryptor') +const MockSimpleKeychain = require('../lib/mock-simple-keychain') +const sinon = require('sinon') + +const mockVault = { + seed: 'picnic injury awful upper eagle junk alert toss flower renew silly vague', + account: '0x5d8de92c205279c10e5669f797b853ccef4f739a', +} + +const badVault = { + seed: 'radar blur cabbage chef fix engine embark joy scheme fiction master release', +} + +describe('IdentityStore to KeyringController migration', function() { + + // The stars of the show: + let idStore, keyringController, seedWords, configManager + + let password = 'password123' + let entropy = 'entripppppyy duuude' + let accounts = [] + let newAccounts = [] + let originalKeystore + + // This is a lot of setup, I know! + // We have to create an old style vault, populate it, + // and THEN create a new one, before we can run tests on it. + beforeEach(function(done) { + this.sinon = sinon.sandbox.create() + window.localStorage = {} // Hacking localStorage support into JSDom + configManager = new ConfigManager({ + loadData, + setData: (d) => { window.localStorage = d } + }) + + + idStore = new IdentityStore({ + configManager: configManager, + ethStore: { + addAccount(acct) { accounts.push(ethUtil.addHexPrefix(acct)) }, + del(acct) { delete accounts[acct] }, + }, + }) + + idStore._createVault(password, mockVault.seed, (err) => { + assert.ifError(err, 'createNewVault threw error') + originalKeystore = idStore._idmgmt.keyStore + + idStore.setLocked((err) => { + assert.ifError(err, 'createNewVault threw error') + keyringController = new KeyringController({ + configManager, + ethStore: { + addAccount(acct) { newAccounts.push(ethUtil.addHexPrefix(acct)) }, + del(acct) { delete newAccounts[acct] }, + }, + txManager: { + getTxList: () => [], + getUnapprovedTxList: () => [] + }, + }) + + // Stub out the browser crypto for a mock encryptor. + // Browser crypto is tested in the integration test suite. + keyringController.encryptor = mockEncryptor + done() + }) + }) + }) + + describe('entering a password', function() { + it('should identify an old wallet as an initialized keyring', function(done) { + keyringController.configManager.setWallet('something') + keyringController.getState() + .then((state) => { + assert(state.isInitialized, 'old vault counted as initialized.') + assert(!state.lostAccounts, 'no lost accounts') + done() + }) + }) + }) +}) + +function loadData () { + var oldData = getOldStyleData() + var newData + try { + newData = JSON.parse(window.localStorage[STORAGE_KEY]) + } catch (e) {} + + var data = extend({ + meta: { + version: 0, + }, + data: { + config: { + provider: { + type: 'testnet', + }, + }, + }, + }, oldData || null, newData || null) + return data +} + +function setData (data) { + window.localStorage[STORAGE_KEY] = JSON.stringify(data) +} + +function getOldStyleData () { + var config, wallet, seedWords + + var result = { + meta: { version: 0 }, + data: {}, + } + + try { + config = JSON.parse(window.localStorage['config']) + result.data.config = config + } catch (e) {} + try { + wallet = JSON.parse(window.localStorage['lightwallet']) + result.data.wallet = wallet + } catch (e) {} + try { + seedWords = window.localStorage['seedWords'] + result.data.seedWords = seedWords + } catch (e) {} + + return result +} diff --git a/test/unit/idStore-test.js b/test/unit/idStore-test.js index bf8270540..000c58a82 100644 --- a/test/unit/idStore-test.js +++ b/test/unit/idStore-test.js @@ -11,7 +11,6 @@ describe('IdentityStore', function() { describe('#createNewVault', function () { let idStore let password = 'password123' - let entropy = 'entripppppyy duuude' let seedWords let accounts = [] let originalKeystore @@ -26,7 +25,7 @@ describe('IdentityStore', function() { }, }) - idStore.createNewVault(password, entropy, (err, seeds) => { + idStore.createNewVault(password, (err, seeds) => { assert.ifError(err, 'createNewVault threw error') seedWords = seeds originalKeystore = idStore._idmgmt.keyStore @@ -140,54 +139,4 @@ describe('IdentityStore', function() { }) }) }) - - describe('#addGasBuffer', function() { - it('formats the result correctly', function() { - const idStore = new IdentityStore({ - configManager: configManagerGen(), - ethStore: { - addAccount(acct) { accounts.push(ethUtil.addHexPrefix(acct)) }, - }, - }) - - const gas = '0x01' - const bnGas = new BN(gas, 16) - const bnResult = idStore.addGasBuffer(bnGas) - - assert.ok(bnResult.gt(gas), 'added more gas as buffer.') - }) - - it('buffers 20%', function() { - const idStore = new IdentityStore({ - configManager: configManagerGen(), - ethStore: { - addAccount(acct) { accounts.push(ethUtil.addHexPrefix(acct)) }, - }, - }) - - const gas = '0x04ee59' // Actual estimated gas example - const bnGas = new BN(ethUtil.stripHexPrefix(gas), 16) - const five = new BN('5', 10) - const correctBuffer = bnGas.div(five) - const correct = bnGas.add(correctBuffer) - - const bnResult = idStore.addGasBuffer(bnGas) - - assert(bnResult.gt(bnGas), 'Estimate increased in value.') - assert.equal(bnResult.sub(bnGas).toString(10), correctBuffer.toString(10), 'added 20% gas') - }) - }) - - describe('#checkForDelegateCall', function() { - const idStore = new IdentityStore({ - configManager: configManagerGen(), - ethStore: { - addAccount(acct) { accounts.push(ethUtil.addHexPrefix(acct)) }, - }, - }) - - var result = idStore.checkForDelegateCall(delegateCallCode) - assert.equal(result, true, 'no delegate call in provided code') - }) - }) diff --git a/test/unit/keyring-controller-test.js b/test/unit/keyring-controller-test.js new file mode 100644 index 000000000..37fd7175e --- /dev/null +++ b/test/unit/keyring-controller-test.js @@ -0,0 +1,189 @@ +var assert = require('assert') +var KeyringController = require('../../app/scripts/keyring-controller') +var 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() + }) + }) + + afterEach(function() { + // Cleanup mocks + this.sinon.restore() + }) + + describe('#createNewVaultAndKeychain', function () { + this.timeout(10000) + + it('should set a vault on the configManager', function(done) { + keyringController.configManager.setVault(null) + assert(!keyringController.configManager.getVault(), 'no previous vault') + keyringController.createNewVaultAndKeychain(password) + .then(() => { + const vault = keyringController.configManager.getVault() + assert(vault, 'vault created') + done() + }) + .catch((reason) => { + assert.ifError(reason) + done() + }) + }) + }) + + 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) => { + assert.ifError(reason) + done() + }) + }) + }) + + describe('#createNickname', function() { + it('should add the address to the identities hash', function() { + const fakeAddress = '0x12345678' + keyringController.createNickname(fakeAddress) + const identities = keyringController.identities + const identity = identities[fakeAddress] + assert.equal(identity.address, fakeAddress) + + const nick = keyringController.configManager.nicknameForWallet(fakeAddress) + assert.equal(typeof nick, 'string') + }) + }) + + describe('#saveAccountLabel', function() { + it ('sets the nickname', function(done) { + const account = addresses[0] + var nick = 'Test nickname' + keyringController.identities[ethUtil.addHexPrefix(account)] = {} + keyringController.saveAccountLabel(account, nick) + .then((label) => { + assert.equal(label, nick) + const persisted = keyringController.configManager.nicknameForWallet(account) + assert.equal(persisted, nick) + done() + }) + .catch((reason) => { + assert.ifError(reason) + done() + }) + }) + + this.timeout(10000) + it('retrieves the persisted nickname', function(done) { + const account = addresses[0] + var nick = 'Test nickname' + keyringController.configManager.setNicknameForWallet(account, nick) + keyringController.createNewVaultAndRestore(password, seedWords) + .then((state) => { + + const identity = keyringController.identities['0x' + account] + assert.equal(identity.name, nick) + + assert(accounts) + done() + }) + .catch((reason) => { + assert.ifError(reason) + done() + }) + }) + }) + + describe('#getAccounts', function() { + it('returns the result of getAccounts for each keyring', function() { + 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/keyrings/hd-test.js b/test/unit/keyrings/hd-test.js new file mode 100644 index 000000000..dfc0ec908 --- /dev/null +++ b/test/unit/keyrings/hd-test.js @@ -0,0 +1,127 @@ +const assert = require('assert') +const extend = require('xtend') +const HdKeyring = require('../../../app/scripts/keyrings/hd') + +// Sample account: +const privKeyHex = 'b8a9c05beeedb25df85f8d641538cbffedf67216048de9c678ee26260eb91952' + +const sampleMnemonic = 'finish oppose decorate face calm tragic certain desk hour urge dinosaur mango' +const firstAcct = '1c96099350f13d558464ec79b9be4445aa0ef579' +const secondAcct = '1b00aed43a693f3a957f9feb5cc08afa031e37a0' + +describe('hd-keyring', function() { + + let keyring + beforeEach(function() { + keyring = new HdKeyring() + }) + + describe('constructor', function(done) { + keyring = new HdKeyring({ + mnemonic: sampleMnemonic, + numberOfAccounts: 2, + }) + + const accounts = keyring.getAccounts() + .then((accounts) => { + assert.equal(accounts[0], firstAcct) + assert.equal(accounts[1], secondAcct) + done() + }) + }) + + describe('Keyring.type', function() { + it('is a class property that returns the type string.', function() { + const type = HdKeyring.type + assert.equal(typeof type, 'string') + }) + }) + + describe('#type', function() { + it('returns the correct value', function() { + const type = keyring.type + const correct = HdKeyring.type + assert.equal(type, correct) + }) + }) + + describe('#serialize empty wallets.', function() { + it('serializes a new mnemonic', function() { + keyring.serialize() + .then((output) => { + assert.equal(output.numberOfAccounts, 0) + assert.equal(output.mnemonic, null) + }) + }) + }) + + describe('#deserialize a private key', function() { + it('serializes what it deserializes', function(done) { + keyring.deserialize({ + mnemonic: sampleMnemonic, + numberOfAccounts: 1 + }) + .then(() => { + assert.equal(keyring.wallets.length, 1, 'restores two accounts') + return keyring.addAccounts(1) + }).then(() => { + return keyring.getAccounts() + }).then((accounts) => { + assert.equal(accounts[0], firstAcct) + assert.equal(accounts[1], secondAcct) + assert.equal(accounts.length, 2) + + return keyring.serialize() + }).then((serialized) => { + assert.equal(serialized.mnemonic, sampleMnemonic) + done() + }) + }) + }) + + describe('#addAccounts', function() { + describe('with no arguments', function() { + it('creates a single wallet', function(done) { + keyring.addAccounts() + .then(() => { + assert.equal(keyring.wallets.length, 1) + done() + }) + }) + }) + + describe('with a numeric argument', function() { + it('creates that number of wallets', function(done) { + keyring.addAccounts(3) + .then(() => { + assert.equal(keyring.wallets.length, 3) + done() + }) + }) + }) + }) + + describe('#getAccounts', function() { + it('calls getAddress on each wallet', function(done) { + + // Push a mock wallet + const desiredOutput = 'foo' + keyring.wallets.push({ + getAddress() { + return { + toString() { + return desiredOutput + } + } + } + }) + + const output = keyring.getAccounts() + .then((output) => { + assert.equal(output[0], desiredOutput) + assert.equal(output.length, 1) + done() + }) + }) + }) +}) diff --git a/test/unit/keyrings/simple-test.js b/test/unit/keyrings/simple-test.js new file mode 100644 index 000000000..979abdb69 --- /dev/null +++ b/test/unit/keyrings/simple-test.js @@ -0,0 +1,94 @@ +const assert = require('assert') +const extend = require('xtend') +const SimpleKeyring = require('../../../app/scripts/keyrings/simple') +const TYPE_STR = 'Simple Key Pair' + +// Sample account: +const privKeyHex = 'b8a9c05beeedb25df85f8d641538cbffedf67216048de9c678ee26260eb91952' + +describe('simple-keyring', function() { + + let keyring + beforeEach(function() { + keyring = new SimpleKeyring() + }) + + describe('Keyring.type', function() { + it('is a class property that returns the type string.', function() { + const type = SimpleKeyring.type + assert.equal(type, TYPE_STR) + }) + }) + + describe('#type', function() { + it('returns the correct value', function() { + const type = keyring.type + assert.equal(type, TYPE_STR) + }) + }) + + describe('#serialize empty wallets.', function() { + it('serializes an empty array', function(done) { + keyring.serialize() + .then((output) => { + assert.deepEqual(output, []) + done() + }) + }) + }) + + describe('#deserialize a private key', function() { + it('serializes what it deserializes', function() { + keyring.deserialize([privKeyHex]) + .then(() => { + assert.equal(keyring.wallets.length, 1, 'has one wallet') + const serialized = keyring.serialize() + assert.equal(serialized[0], privKeyHex) + }) + }) + }) + + describe('#addAccounts', function() { + describe('with no arguments', function() { + it('creates a single wallet', function() { + keyring.addAccounts() + .then(() => { + assert.equal(keyring.wallets.length, 1) + }) + }) + }) + + describe('with a numeric argument', function() { + it('creates that number of wallets', function() { + keyring.addAccounts(3) + .then(() => { + assert.equal(keyring.wallets.length, 3) + }) + }) + }) + }) + + describe('#getAccounts', function() { + it('calls getAddress on each wallet', function(done) { + + // Push a mock wallet + const desiredOutput = 'foo' + keyring.wallets.push({ + getAddress() { + return { + toString() { + return desiredOutput + } + } + } + }) + + keyring.getAccounts() + .then((output) => { + assert.equal(output[0], desiredOutput) + assert.equal(output.length, 1) + done() + }) + }) + }) +}) diff --git a/test/unit/metamask-controller-test.js b/test/unit/metamask-controller-test.js index b87169ca2..a6164c9a0 100644 --- a/test/unit/metamask-controller-test.js +++ b/test/unit/metamask-controller-test.js @@ -9,7 +9,7 @@ describe('MetaMaskController', function() { let controller = new MetaMaskController({ showUnconfirmedMessage: noop, unlockAccountMessage: noop, - showUnconfirmedTx: noop, + showUnapprovedTx: noop, setData, loadData, }) @@ -25,24 +25,6 @@ describe('MetaMaskController', function() { this.sinon.restore() }) - describe('#enforceTxValidations', function () { - it('returns null for positive values', function() { - var sample = { - value: '0x01' - } - var res = controller.enforceTxValidations(sample) - assert.equal(res, null, 'no error') - }) - - - it('returns error for negative values', function() { - var sample = { - value: '-0x01' - } - var res = controller.enforceTxValidations(sample) - assert.ok(res, 'error') - }) - }) }) diff --git a/test/unit/nodeify-test.js b/test/unit/nodeify-test.js new file mode 100644 index 000000000..a14d34338 --- /dev/null +++ b/test/unit/nodeify-test.js @@ -0,0 +1,22 @@ +const assert = require('assert') +const nodeify = require('../../app/scripts/lib/nodeify') + +describe('nodeify', function() { + + var obj = { + foo: 'bar', + promiseFunc: function (a) { + var solution = this.foo + a + return Promise.resolve(solution) + } + } + + it('should retain original context', function(done) { + var nodified = nodeify(obj.promiseFunc).bind(obj) + nodified('baz', function (err, res) { + assert.equal(res, 'barbaz') + done() + }) + }) + +}) diff --git a/test/unit/notice-controller-test.js b/test/unit/notice-controller-test.js index 4aa4c8e7b..cf00daeba 100644 --- a/test/unit/notice-controller-test.js +++ b/test/unit/notice-controller-test.js @@ -5,13 +5,14 @@ const nock = require('nock') const configManagerGen = require('../lib/mock-config-manager') const NoticeController = require('../../app/scripts/notice-controller') const STORAGE_KEY = 'metamask-persistance-key' -// Hacking localStorage support into JSDom -window.localStorage = {} describe('notice-controller', function() { var noticeController beforeEach(function() { + // simple localStorage polyfill + window.localStorage = {} + if (window.localStorage.clear) window.localStorage.clear() let configManager = configManagerGen() noticeController = new NoticeController({ configManager: configManager, diff --git a/test/unit/tx-manager-test.js b/test/unit/tx-manager-test.js new file mode 100644 index 000000000..a66003f85 --- /dev/null +++ b/test/unit/tx-manager-test.js @@ -0,0 +1,215 @@ +const assert = require('assert') +const extend = require('xtend') +const EventEmitter = require('events') +const STORAGE_KEY = 'metamask-persistance-key' +const TransactionManager = require('../../app/scripts/transaction-manager') + +describe('Transaction Manager', function() { + let txManager + + const onTxDoneCb = () => true + beforeEach(function() { + txManager = new TransactionManager ({ + txList: [], + setTxList: () => {}, + provider: "testnet", + txHistoryLimit: 10, + blockTracker: new EventEmitter(), + getNetwork: function(){ return 'unit test' } + }) + }) + + 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('#_saveTxList', function() { + it('saves the submitted data to the tx list', function() { + var target = [{ foo: 'bar', metamaskNetworkId: 'unit test' }] + txManager._saveTxList(target) + var result = txManager.getTxList() + assert.equal(result[0].foo, 'bar') + }) + }) + + describe('#addTx', function() { + it('adds a tx returned in getTxList', function() { + var tx = { id: 1, status: 'confirmed', metamaskNetworkId: 'unit test' } + txManager.addTx(tx, onTxDoneCb) + var result = txManager.getTxList() + assert.ok(Array.isArray(result)) + assert.equal(result.length, 1) + assert.equal(result[0].id, 1) + }) + + 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: 'unit test' } + txManager.addTx(tx, onTxDoneCb) + } + 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: 'unit test' } + txManager.addTx(tx, onTxDoneCb) + } + 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: 'unit test' } + txManager.addTx(unconfirmedTx, onTxDoneCb) + const limit = txManager.txHistoryLimit + for (let i = 1; i < limit + 1; i++) { + let tx = { id: i, time: new Date(), status: 'confirmed', metamaskNetworkId: 'unit test' } + txManager.addTx(tx, onTxDoneCb) + } + 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: 'unit test' } + txManager.addTx(tx, onTxDoneCb) + 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: 'unit test' } + let onTxDoneCb = function () { + assert(true, 'event listener has been triggered and onTxDoneCb executed') + done() + } + txManager.addTx(tx) + txManager.on('1:signed', onTxDoneCb) + txManager.setTxStatusSigned(1) + }) + }) + + describe('#setTxStatusRejected', function() { + it('sets the tx status to rejected', function() { + var tx = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test' } + 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: 'unit test' } + txManager.addTx(tx) + let onTxDoneCb = function (err, txId) { + assert(true, 'event listener has been triggered and onTxDoneCb executed') + done() + } + txManager.on('1:rejected', onTxDoneCb) + txManager.setTxStatusRejected(1) + }) + + }) + + describe('#updateTx', function() { + it('replaces the tx with the same id', function() { + txManager.addTx({ id: '1', status: 'unapproved', metamaskNetworkId: 'unit test' }, onTxDoneCb) + txManager.addTx({ id: '2', status: 'confirmed', metamaskNetworkId: 'unit test' }, onTxDoneCb) + txManager.updateTx({ id: '1', status: 'blah', hash: 'foo', metamaskNetworkId: 'unit test' }) + 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: 'unit test' }, onTxDoneCb) + txManager.addTx({ id: '2', status: 'confirmed', metamaskNetworkId: 'unit test' }, onTxDoneCb) + 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: 'unit test' }, onTxDoneCb) + txManager.addTx({ id: '2', status: 'confirmed', metamaskNetworkId: 'unit test' }, onTxDoneCb) + 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() { + var foop = 0 + var zoop = 0 + for (let i = 0; i < 10; ++i ){ + let everyOther = i % 2 + txManager.addTx({ id: i, + status: everyOther ? 'unapproved' : 'confirmed', + metamaskNetworkId: 'unit test', + txParams: { + from: everyOther ? 'foop' : 'zoop', + to: everyOther ? 'zoop' : 'foop', + } + }, onTxDoneCb) + everyOther ? ++foop : ++zoop + } + assert.equal(txManager.getFilteredTxList({status: 'confirmed', from: 'zoop'}).length, zoop) + assert.equal(txManager.getFilteredTxList({status: 'confirmed', to: 'foop'}).length, zoop) + assert.equal(txManager.getFilteredTxList({status: 'confirmed', from: 'foop'}).length, 0) + assert.equal(txManager.getFilteredTxList({status: 'confirmed'}).length, zoop) + assert.equal(txManager.getFilteredTxList({from: 'foop'}).length, foop) + assert.equal(txManager.getFilteredTxList({from: 'zoop'}).length, zoop) + }) + }) + +}) |