diff options
Diffstat (limited to 'test')
34 files changed, 1780 insertions, 399 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..430814a8a 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="800px" width="500px"> <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..55a16ef38 --- /dev/null +++ b/test/integration/lib/first-time.js @@ -0,0 +1,103 @@ +const PASSWORD = 'password123' + +QUnit.module('first time usage') + +QUnit.test('render init screen', function (assert) { + var done = assert.async() + let app + + wait().then(function() { + app = $('iframe').contents().find('#app-content .mock-app-root') + + // Scroll through terms + var title = app.find('h1').text() + assert.equal(title, 'MetaMask', 'title screen') + + // enter password + 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() { + + // create vault + var createButton = app.find('button.primary')[0] + createButton.click() + + return wait(1500) + }).then(function() { + + var created = app.find('h3')[0] + assert.equal(created.textContent, 'Vault Created', 'Vault created screen') + + // Agree button + var button = app.find('button')[0] + assert.ok(button, 'button present') + 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 children = sandwich.children + var lock = children[children.length - 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.') + + return wait() + }).then(function (){ + + var qrButton = app.find('.fa.fa-qrcode')[0] + qrButton.click() + + return wait(1000) + }).then(function (){ + + var qrHeader = app.find('.qr-header')[0] + var qrContainer = app.find('#qr-container')[0] + assert.equal(qrHeader.textContent, 'Account 1', 'Should show account label.') + assert.ok(qrContainer, 'QR Container found') + + return wait() + }).then(function (){ + + var networkMenu = app.find('.network-indicator')[0] + networkMenu.click() + + return wait() + }).then(function (){ + + var networkMenu = app.find('.network-indicator')[0] + var children = networkMenu.children + children.length[3] + assert.ok(children, 'All network options present') + + 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..f2a437a7c --- /dev/null +++ b/test/integration/lib/idStore-migrator-test.js @@ -0,0 +1,92 @@ +const ObservableStore = require('obs-store') +const ConfigManager = require('../../../app/scripts/lib/config-manager') +const IdStoreMigrator = require('../../../app/scripts/lib/idStore-migrator') +const SimpleKeyring = require('../../../app/scripts/keyrings/simple') +const normalize = require('../../../app/scripts/lib/sig-util').normalize + +const oldStyleVault = require('../mocks/oldVault.json').data +const badStyleVault = require('../mocks/badVault.json').data + +const PASSWORD = '12345678' +const FIRST_ADDRESS = '0x4dd5d356c5A016A220bCD69e82e5AF680a430d00'.toLowerCase() +const BAD_STYLE_FIRST_ADDRESS = '0xac39b311dceb2a4b2f5d8461c1cdaf756f4f7ae9' +const SEED = 'fringe damage bounce extend tunnel afraid alert sound all soldier all dinner' + +QUnit.module('Old Style Vaults', { + beforeEach: function () { + let managers = managersFromInitState(oldStyleVault) + + this.configManager = managers.configManager + this.migrator = managers.migrator + } +}) + +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) => { + assert.ok(result, 'migratedVaultForPassword returned 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 () { + let managers = managersFromInitState(badStyleVault) + + this.configManager = managers.configManager + this.migrator = managers.migrator + } +}) + +QUnit.test('migrator:migratedVaultForPassword', function (assert) { + var done = assert.async() + + this.migrator.migratedVaultForPassword(PASSWORD) + .then((result) => { + assert.ok(result, 'migratedVaultForPassword returned 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) + }) + }) +}) + +function managersFromInitState(initState){ + + let configManager = new ConfigManager({ + store: new ObservableStore(initState), + }) + + let migrator = new IdStoreMigrator({ + configManager: configManager, + }) + + return { configManager, migrator } +}
\ No newline at end of file diff --git a/test/integration/mocks/badVault.json b/test/integration/mocks/badVault.json new file mode 100644 index 000000000..83b4f6298 --- /dev/null +++ b/test/integration/mocks/badVault.json @@ -0,0 +1 @@ +{"meta":{"version":4},"data":{"fiatCurrency":"USD","conversionRate":8.34908448,"conversionDate":1481227505,"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":[],"gasMultiplier":1}} diff --git a/test/integration/mocks/badVault2.json b/test/integration/mocks/badVault2.json new file mode 100644 index 000000000..e849ca62a --- /dev/null +++ b/test/integration/mocks/badVault2.json @@ -0,0 +1 @@ +{"meta":{"version":4},"data":{"fiatCurrency":"USD","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"},"walletNicknames":{"0xac39b311dceb2a4b2f5d8461c1cdaf756f4f7ae9":"Account 1","0xd7c0cd9e7d2701c710d64fc492c7086679bdf7b4":"Account 2","0x1acfb961c5a8268eac8e09d6241a26cbeff42241":"Account 3"},"lostAccounts":["0xe15d894becb0354c501ae69429b05143679f39e0","0x87658c15aefe7448008a28513a11b6b130ef4cd0","0xaa25854c0379e53c957ac9382e720c577fa31fd5"]}} diff --git a/test/integration/mocks/oldVault.json b/test/integration/mocks/oldVault.json new file mode 100644 index 000000000..dbcd91575 --- /dev/null +++ b/test/integration/mocks/oldVault.json @@ -0,0 +1,19 @@ +{ + "meta": { + "version": 4 + }, + "data": { + "fiatCurrency": "USD", + "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" + }, + "selectedAddress": "0x4dd5d356c5a016a220bcd69e82e5af680a430d00" + }, + "showSeedWords": false, + "isEthConfirmed": true + } +} diff --git a/test/integration/tests.js b/test/integration/tests.js deleted file mode 100644 index a3f3cd294..000000000 --- a/test/integration/tests.js +++ /dev/null @@ -1,21 +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') - - // Agree to terms - app.find('button').click() - - // Wait for view to transition: - 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() - }) -}) diff --git a/test/lib/example-code.json b/test/lib/example-code.json new file mode 100644 index 000000000..b76d37a4c --- /dev/null +++ b/test/lib/example-code.json @@ -0,0 +1,3 @@ +{ + "delegateCallCode": "0x606060405260e060020a60003504637bd703e8811461003157806390b98a111461005c578063f8b2cb4f1461008e575b005b6100b4600435600073f28c53067227848f8145355c455da5cfdd20e3136396e4ee3d6100da84610095565b6100c660043560243533600160a060020a03166000908152602081905260408120548290101561011f57506000610189565b6100b46004355b600160a060020a0381166000908152602081905260409020545b919050565b60408051918252519081900360200190f35b604080519115158252519081900360200190f35b60026040518360e060020a02815260040180838152602001828152602001925050506020604051808303818660325a03f4156100025750506040515191506100af9050565b33600160a060020a0390811660008181526020818152604080832080548890039055938716808352918490208054870190558351868152935191937fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef929081900390910190a35060015b9291505056" +} diff --git a/test/lib/migrations/004.json b/test/lib/migrations/004.json new file mode 100644 index 000000000..0e2075c46 --- /dev/null +++ b/test/lib/migrations/004.json @@ -0,0 +1,138 @@ +{ + "meta":{ + "version":4 + }, + "data":{ + "seedWords":null, + "fiatCurrency":"USD", + "isDisclaimerConfirmed":true, + "TOSHash":"a4f4e23f823a7ac51783e7ffba7914a911b09acdb97263296b7e14b527f80c5b", + "shapeShiftTxList":[ + { + "depositAddress": "1L8BJCR6KHkCiVceDqibt7zJscqPpH7pFw", + "depositType": "BTC", + "key": "shapeshift", + "time": 1471564825772, + "response": { + "status": "complete", + "outgoingCoin": "100.00", + "incomingCoin": "1.000", + "transaction": "0x3701e0ac344a12a1fc5417cf251109a7c41f3edab922310202630d9c012414c8" + } + }, + { + "depositAddress": "1L8BJCR6KHkCiVceDqibt7zJscqPpH7pFw", + "depositType": "BTC", + "key": "shapeshift", + "time": 1471566579224, + "response": { + "status": "no_deposits", + "depositAddress": "1L8BJCR6KHkCiVceDqibt7zJscqPpH7pFw" + } + }, + { + "depositAddress": "1L8BJCR6KHkCiVceDqibt7zJscqPpH7pFw", + "depositType": "BTC", + "key": "shapeshift", + "time": 1471566565378, + "response": { + "status": "received", + "depositAddress": "1L8BJCR6KHkCiVceDqibt7zJscqPpH7pFw" + } + } + ], + "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":12.66441492, + "conversionDate":1487184182, + "vault":"{\"data\":\"Z5UFCeI/Tg9F9No0dC7eIhe4evCG91m6qeXhGpSeX48HHCQ/BepyNONKrh05YjB9hXCAd3Jy93judD+pcXNy7WS9zLujjmMI6sI90ToSrzThnMrOE6ixcH7HGS+TCcqvwBhZEsAQqUcQeHhT9CcdCQAxkKwBk8CK8W290MVeZoQVGK88hB2R8kL3mo/uayS5AnHPwWOS0rocgSfd/ioiucClpw==\",\"iv\":\"AYufeEPwp9f2Rdrfq7yS8g==\",\"salt\":\"g7BQIEx8tosH3IxWhPnrgZFu1XRkQn1Pp7l1ehTQQCo=\"}", + "config":{ + "provider":{ + "type":"testnet" + }, + "selectedAccount":"0x0beb674745816b125fbc07285d39fd373e64895c" + }, + "walletNicknames":{ + "0x0beb674745816b125fbc07285d39fd373e64895c":"Account 1", + "0x433eb37d2e4895815b90f555425dfa123ddaed40":"Account 2" + }, + "transactions":[ + { + "id":3922064325443430, + "time":1487184358262, + "status":"confirmed", + "gasMultiplier":1, + "metamaskNetworkId":"3", + "txParams":{ + "from":"0x0beb674745816b125fbc07285d39fd373e64895c", + "to":"0x18a3462427bcc9133bb46e88bcbe39cd7ef0e761", + "value":"0xde0b6b3a7640000", + "metamaskId":3922064325443430, + "metamaskNetworkId":"3", + "gas":"0x5209", + "gasPrice":"0x04a817c800", + "nonce":"0x0", + "gasLimit":"0x5209" + }, + "gasLimitSpecified":false, + "estimatedGas":"0x5209", + "txFee":"17e0186e60800", + "txValue":"de0b6b3a7640000", + "maxCost":"de234b52e4a0800", + "hash":"0x0b36c5bb31528044e6a71e45a64e9872f5f365a14ac42ee1bea49e7766216c12" + }, + { + "id":3922064325443431, + "time":1487184373172, + "status":"confirmed", + "gasMultiplier":1, + "metamaskNetworkId":"3", + "txParams":{ + "from":"0x0beb674745816b125fbc07285d39fd373e64895c", + "to":"0x433eb37d2e4895815b90f555425dfa123ddaed40", + "value":"0xde0b6b3a7640000", + "metamaskId":3922064325443431, + "metamaskNetworkId":"3", + "gas":"0x5209", + "nonce":"0x01", + "gasPrice":"0x04a817c800", + "gasLimit":"0x5209" + }, + "gasLimitSpecified":false, + "estimatedGas":"0x5209", + "txFee":"17e0186e60800", + "txValue":"de0b6b3a7640000", + "maxCost":"de234b52e4a0800", + "hash":"0x305548a8b8bb72de0ca8cf77df45e4fe2b29383e58c4da6b7eac7e9bd59e85e9" + }, + { + "id":3922064325443432, + "time":1487184391226, + "status":"unapproved", + "gasMultiplier":1, + "metamaskNetworkId":"3", + "txParams":{ + "from":"0x0beb674745816b125fbc07285d39fd373e64895c", + "to":"0x18a3462427bcc9133bb46e88bcbe39cd7ef0e761", + "value":"0xde0b6b3a7640000", + "metamaskId":3922064325443432, + "metamaskNetworkId":"3", + "gas":"0x5209" + }, + "gasLimitSpecified":false, + "estimatedGas":"0x5209", + "txFee":"17e0186e60800", + "txValue":"de0b6b3a7640000", + "maxCost":"de234b52e4a0800" + } + ], + "gasMultiplier":1 + } +} diff --git a/test/lib/mock-config-manager.js b/test/lib/mock-config-manager.js index fe841f455..72be86ed1 100644 --- a/test/lib/mock-config-manager.js +++ b/test/lib/mock-config-manager.js @@ -1,57 +1,10 @@ -var ConfigManager = require('../../app/scripts/lib/config-manager') -const STORAGE_KEY = 'metamask-persistance-key' -const extend = require('xtend') +const ObservableStore = require('obs-store') +const clone = require('clone') +const ConfigManager = require('../../app/scripts/lib/config-manager') +const firstTimeState = require('../../app/scripts/first-time-state') +const STORAGE_KEY = 'metamask-config' module.exports = function() { - return new ConfigManager({ loadData, setData }) -} - -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 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 -} - -function setData (data) { - window.localStorage[STORAGE_KEY] = JSON.stringify(data) -} + let store = new ObservableStore(clone(firstTimeState)) + return new ConfigManager({ store }) +}
\ No newline at end of file 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/account-link-test.js b/test/unit/account-link-test.js index 39889b6be..4ea12e002 100644 --- a/test/unit/account-link-test.js +++ b/test/unit/account-link-test.js @@ -3,9 +3,15 @@ var linkGen = require('../../ui/lib/account-link') describe('account-link', function() { - it('adds testnet prefix to morden test network', function() { + it('adds morden prefix to morden test network', function() { var result = linkGen('account', '2') - assert.notEqual(result.indexOf('testnet'), -1, 'testnet injected') + assert.notEqual(result.indexOf('morden'), -1, 'testnet included') + assert.notEqual(result.indexOf('account'), -1, 'account included') + }) + + it('adds testnet prefix to ropsten test network', function() { + var result = linkGen('account', '3') + assert.notEqual(result.indexOf('testnet'), -1, 'testnet included') assert.notEqual(result.indexOf('account'), -1, 'account included') }) 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..2dc42d2ec 100644 --- a/test/unit/actions/set_selected_account_test.js +++ b/test/unit/actions/set_selected_account_test.js @@ -31,7 +31,7 @@ describe('SHOW_ACCOUNT_DETAIL', function() { it('updates metamask state', function() { var initialState = { metamask: { - selectedAccount: 'foo' + selectedAddress: 'foo' } } freeze(initialState) @@ -43,7 +43,6 @@ describe('SHOW_ACCOUNT_DETAIL', function() { freeze(action) 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..7ded5b1ef 100644 --- a/test/unit/actions/tx_test.js +++ b/test/unit/actions/tx_test.js @@ -31,7 +31,7 @@ describe('tx confirmation screen', function() { }, }, metamask: { - unconfTxs: { + unapprovedTxs: { '1457634084250832': { id: 1457634084250832, status: "unconfirmed", @@ -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() }, }) @@ -119,7 +119,7 @@ describe('tx confirmation screen', function() { }, }, metamask: { - unconfTxs: { + unapprovedTxs: { '1457634084250832': { id: 1457634084250832, status: "unconfirmed", @@ -135,7 +135,7 @@ describe('tx confirmation screen', function() { } freeze(initialState) - actions._setAccountManager({ + actions._setBackgroundConnection({ approveTransaction(txId, cb) { cb() }, }) @@ -162,7 +162,7 @@ describe('tx confirmation screen', function() { }); function getUnconfirmedTxCount(state) { - var txs = state.metamask.unconfTxs + var txs = state.metamask.unapprovedTxs var count = Object.keys(txs).length return count } diff --git a/test/unit/config-manager-test.js b/test/unit/config-manager-test.js index 6aa7146f0..05324e741 100644 --- a/test/unit/config-manager-test.js +++ b/test/unit/config-manager-test.js @@ -1,136 +1,20 @@ +// polyfill fetch +global.fetch = global.fetch || require('isomorphic-fetch') + const assert = require('assert') const extend = require('xtend') -const STORAGE_KEY = 'metamask-persistance-key' -var configManagerGen = require('../lib/mock-config-manager') -var configManager const rp = require('request-promise') const nock = require('nock') +const configManagerGen = require('../lib/mock-config-manager') describe('config-manager', function() { + var configManager beforeEach(function() { - window.localStorage = {} // Hacking localStorage support into JSDom configManager = configManagerGen() }) - describe('currency conversions', function() { - - describe('#getCurrentFiat', function() { - it('should return false if no previous key exists', function() { - var result = configManager.getCurrentFiat() - assert.ok(!result) - }) - }) - - describe('#setCurrentFiat', function() { - it('should make getCurrentFiat return true once set', function() { - assert.equal(configManager.getCurrentFiat(), false) - configManager.setCurrentFiat('USD') - var result = configManager.getCurrentFiat() - assert.equal(result, 'USD') - }) - - it('should work with other currencies as well', function() { - assert.equal(configManager.getCurrentFiat(), false) - configManager.setCurrentFiat('JPY') - var result = configManager.getCurrentFiat() - assert.equal(result, 'JPY') - }) - }) - - describe('#getConversionRate', function() { - it('should return false if non-existent', function() { - var result = configManager.getConversionRate() - assert.ok(!result) - }) - }) - - describe('#updateConversionRate', function() { - it('should retrieve an update for ETH to USD and set it in memory', function(done) { - this.timeout(15000) - 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":""}') - - assert.equal(configManager.getConversionRate(), false) - var promise = new Promise( - function (resolve, reject) { - configManager.setCurrentFiat('USD') - configManager.updateConversionRate().then(function() { - resolve() - }) - }) - - promise.then(function() { - var result = configManager.getConversionRate() - assert.equal(typeof result, 'number') - done() - }).catch(function(err) { - console.log(err) - }) - - }) - - it('should work for JPY as well.', function() { - this.timeout(15000) - assert.equal(configManager.getConversionRate(), false) - - 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":""}') - - - var promise = new Promise( - function (resolve, reject) { - configManager.setCurrentFiat('JPY') - configManager.updateConversionRate().then(function() { - resolve() - }) - }) - - promise.then(function() { - var result = configManager.getConversionRate() - assert.equal(typeof result, 'number') - }).catch(function(err) { - console.log(err) - }) - }) - }) - }) - - describe('confirmation', function() { - - describe('#getConfirmed', function() { - it('should return false if no previous key exists', function() { - var result = configManager.getConfirmed() - 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() - assert.equal(result, true) - }) - - it('should be able to set false', function() { - configManager.setConfirmed(false) - var result = configManager.getConfirmed() - assert.equal(result, false) - }) - - it('should persist to local storage', function() { - configManager.setConfirmed(true) - var data = configManager.getData() - assert.equal(data.isConfirmed, true) - }) - }) - }) - describe('#setConfig', function() { - window.localStorage = {} // Hacking localStorage support into JSDom it('should set the config key', function () { var testConfig = { @@ -153,7 +37,6 @@ describe('config-manager', function() { rpcTarget: 'foobar' }, } - configManager.setConfirmed(true) configManager.setConfig(testConfig) var testWallet = { @@ -164,7 +47,6 @@ 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) testConfig.provider.type = 'something else!' configManager.setConfig(testConfig) @@ -173,7 +55,6 @@ 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) }) }) @@ -215,7 +96,7 @@ describe('config-manager', function() { describe('transactions', function() { beforeEach(function() { - configManager._saveTxList([]) + configManager.setTxList([]) }) describe('#getTxList', function() { @@ -226,90 +107,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/currency-controller-test.js b/test/unit/currency-controller-test.js new file mode 100644 index 000000000..c57b522c7 --- /dev/null +++ b/test/unit/currency-controller-test.js @@ -0,0 +1,87 @@ +// 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') +const CurrencyController = require('../../app/scripts/lib/controllers/currency') + +describe('config-manager', function() { + var currencyController + + beforeEach(function() { + currencyController = new CurrencyController() + }) + + describe('currency conversions', function() { + + describe('#setCurrentCurrency', function() { + it('should return USD as default', function() { + assert.equal(currencyController.getCurrentCurrency(), 'USD') + }) + + it('should be able to set to other currency', function() { + assert.equal(currencyController.getCurrentCurrency(), 'USD') + currencyController.setCurrentCurrency('JPY') + var result = currencyController.getCurrentCurrency() + assert.equal(result, 'JPY') + }) + }) + + describe('#getConversionRate', function() { + it('should return undefined if non-existent', function() { + var result = currencyController.getConversionRate() + assert.ok(!result) + }) + }) + + describe('#updateConversionRate', function() { + it('should retrieve an update for ETH to USD and set it in memory', function(done) { + this.timeout(15000) + 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":""}') + + assert.equal(currencyController.getConversionRate(), 0) + currencyController.setCurrentCurrency('USD') + currencyController.updateConversionRate() + .then(function() { + var result = currencyController.getConversionRate() + console.log('currencyController.getConversionRate:', result) + assert.equal(typeof result, 'number') + done() + }).catch(function(err) { + done(err) + }) + + }) + + it('should work for JPY as well.', function() { + this.timeout(15000) + assert.equal(currencyController.getConversionRate(), 0) + + 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":""}') + + + var promise = new Promise( + function (resolve, reject) { + currencyController.setCurrentCurrency('JPY') + currencyController.updateConversionRate().then(function() { + resolve() + }) + }) + + promise.then(function() { + var result = currencyController.getConversionRate() + assert.equal(typeof result, 'number') + }).catch(function(err) { + done(err) + }) + }) + }) + }) + +}) diff --git a/test/unit/explorer-link-test.js b/test/unit/explorer-link-test.js index 961b400fd..8aa58bff9 100644 --- a/test/unit/explorer-link-test.js +++ b/test/unit/explorer-link-test.js @@ -4,7 +4,7 @@ var linkGen = require('../../ui/lib/explorer-link') describe('explorer-link', function() { it('adds testnet prefix to morden test network', function() { - var result = linkGen('hash', '2') + var result = linkGen('hash', '3') assert.notEqual(result.indexOf('testnet'), -1, 'testnet injected') }) diff --git a/test/unit/extension-test.js b/test/unit/extension-test.js index 6b695e835..8f259f05c 100644 --- a/test/unit/extension-test.js +++ b/test/unit/extension-test.js @@ -1,19 +1,47 @@ var assert = require('assert') var sinon = require('sinon') const ethUtil = require('ethereumjs-util') -GLOBAL.chrome = {} -GLOBAL.browser = {} +global.chrome = {} +global.browser = {} var path = require('path') var Extension = require(path.join(__dirname, '..', '..', 'app', 'scripts', 'lib', 'extension-instance.js')) describe('extension', function() { + describe('extension.getURL', function() { + const desiredResult = 'http://the-desired-result.io' + + describe('in Chrome or Firefox', function() { + global.chrome.extension = { + getURL: () => desiredResult + } + + it('returns the desired result', function() { + const extension = new Extension() + const result = extension.extension.getURL() + assert.equal(result, desiredResult) + }) + }) + + describe('in Microsoft Edge', function() { + global.browser.extension = { + getURL: () => desiredResult + } + + it('returns the desired result', function() { + const extension = new Extension() + const result = extension.extension.getURL() + assert.equal(result, desiredResult) + }) + }) + }) + describe('with chrome global', function() { let extension beforeEach(function() { - GLOBAL.chrome = { + global.chrome = { alarms: 'foo' } extension = new Extension() @@ -30,9 +58,9 @@ describe('extension', function() { beforeEach(function() { realWindow = window - window = GLOBAL - GLOBAL.chrome = undefined - GLOBAL.alarms = 'foo' + window = global + global.chrome = undefined + global.alarms = 'foo' extension = new Extension() }) @@ -45,4 +73,5 @@ describe('extension', function() { }) }) + }) diff --git a/test/unit/idStore-migration-test.js b/test/unit/idStore-migration-test.js new file mode 100644 index 000000000..81a99ef63 --- /dev/null +++ b/test/unit/idStore-migration-test.js @@ -0,0 +1,83 @@ +const async = require('async') +const assert = require('assert') +const ObservableStore = require('obs-store') +const ethUtil = require('ethereumjs-util') +const BN = ethUtil.BN +const ConfigManager = require('../../app/scripts/lib/config-manager') +const firstTimeState = require('../../app/scripts/first-time-state') +const delegateCallCode = require('../lib/example-code.json').delegateCallCode +const clone = require('clone') + +// The old way: +const IdentityStore = require('../../app/scripts/lib/idStore') +const STORAGE_KEY = 'metamask-config' + +// 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() + let store = new ObservableStore(clone(firstTimeState)) + configManager = new ConfigManager({ store }) + + 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() + }) + }) + }) + +}) diff --git a/test/unit/idStore-test.js b/test/unit/idStore-test.js index ee4613236..000c58a82 100644 --- a/test/unit/idStore-test.js +++ b/test/unit/idStore-test.js @@ -1,13 +1,16 @@ -var assert = require('assert') -var IdentityStore = require('../../app/scripts/lib/idStore') -var configManagerGen = require('../lib/mock-config-manager') +const async = require('async') +const assert = require('assert') +const ethUtil = require('ethereumjs-util') +const BN = ethUtil.BN +const configManagerGen = require('../lib/mock-config-manager') +const delegateCallCode = require('../lib/example-code.json').delegateCallCode +const IdentityStore = require('../../app/scripts/lib/idStore') describe('IdentityStore', function() { describe('#createNewVault', function () { let idStore let password = 'password123' - let entropy = 'entripppppyy duuude' let seedWords let accounts = [] let originalKeystore @@ -18,11 +21,12 @@ describe('IdentityStore', function() { idStore = new IdentityStore({ configManager: configManagerGen(), ethStore: { - addAccount(acct) { accounts.push(acct) }, + addAccount(acct) { accounts.push(ethUtil.addHexPrefix(acct)) }, }, }) - idStore.createNewVault(password, entropy, (err, seeds) => { + idStore.createNewVault(password, (err, seeds) => { + assert.ifError(err, 'createNewVault threw error') seedWords = seeds originalKeystore = idStore._idmgmt.keyStore done() @@ -38,7 +42,7 @@ describe('IdentityStore', function() { idStore = new IdentityStore({ configManager: configManagerGen(), ethStore: { - addAccount(acct) { newAccounts.push(acct) }, + addAccount(acct) { newAccounts.push(ethUtil.addHexPrefix(acct)) }, }, }) }) @@ -57,31 +61,80 @@ describe('IdentityStore', function() { }) describe('#recoverFromSeed BIP44 compliance', function() { - let seedWords = 'picnic injury awful upper eagle junk alert toss flower renew silly vague' - let firstAccount = '0x5d8de92c205279c10e5669f797b853ccef4f739a' + const salt = 'lightwalletSalt' let password = 'secret!' - let accounts = [] + let accounts = {} let idStore + var assertions = [ + { + seed: 'picnic injury awful upper eagle junk alert toss flower renew silly vague', + account: '0x5d8de92c205279c10e5669f797b853ccef4f739a', + }, + { + seed: 'radar blur cabbage chef fix engine embark joy scheme fiction master release', + account: '0xe15d894becb0354c501ae69429b05143679f39e0', + }, + { + seed: 'phone coyote caught pattern found table wedding list tumble broccoli chief swing', + account: '0xb0e868f24bc7fec2bce2efc2b1c344d7569cd9d2', + }, + { + seed: 'recycle tag bird palace blue village anxiety census cook soldier example music', + account: '0xab34a45920afe4af212b96ec51232aaa6a33f663', + }, + { + seed: 'half glimpse tape cute harvest sweet bike voyage actual floor poet lazy', + account: '0x28e9044597b625ac4beda7250011670223de43b2', + }, + { + seed: 'flavor tiger carpet motor angry hungry document inquiry large critic usage liar', + account: '0xb571be96558940c4e9292e1999461aa7499fb6cd', + }, + ] + before(function() { window.localStorage = {} // Hacking localStorage support into JSDom idStore = new IdentityStore({ configManager: configManagerGen(), ethStore: { - addAccount(acct) { accounts.push(acct) }, + addAccount(acct) { accounts[acct] = acct}, + del(acct) { delete accounts[acct] }, }, }) }) - it('should return the expected first account', function (done) { + it('should enforce seed compliance with TestRPC', function (done) { + this.timeout(10000) + const tests = assertions.map((assertion) => { + return function (cb) { - idStore.recoverFromSeed(password, seedWords, (err) => { - assert.ifError(err) + idStore.recoverFromSeed(password, assertion.seed, (err) => { + assert.ifError(err) + + var expected = assertion.account.toLowerCase() + var received = accounts[expected].toLowerCase() + assert.equal(received, expected) + + idStore.tryPassword(password, function (err) { - let newKeystore = idStore._idmgmt.keyStore - assert.equal(accounts[0], firstAccount) + assert.ok(idStore._isUnlocked(), 'should unlock the id store') + + idStore.submitPassword(password, function(err, account) { + assert.ifError(err) + assert.equal(account, expected) + assert.equal(Object.keys(idStore._getAddresses()).length, 1, 'only one account on restore') + cb() + }) + }) + }) + } + }) + + async.series(tests, function(err, results) { + assert.ifError(err) done() }) }) diff --git a/test/unit/keyring-controller-test.js b/test/unit/keyring-controller-test.js new file mode 100644 index 000000000..aae4cdfd6 --- /dev/null +++ b/test/unit/keyring-controller-test.js @@ -0,0 +1,172 @@ +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() { + 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..ba7dd448a --- /dev/null +++ b/test/unit/keyrings/simple-test.js @@ -0,0 +1,149 @@ +const assert = require('assert') +const extend = require('xtend') +const Web3 = require('web3') +const web3 = new Web3() +const ethUtil = require('ethereumjs-util') +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('#signMessage', function() { + const address = '0x9858e7d8b79fc3e6d989636721584498926da38a' + const message = '0x879a053d4800c6354e76c7985a865d2922c82fb5b3f4577b2fe08b998954f2e0' + const privateKey = '0x7dd98753d7b4394095de7d176c58128e2ed6ee600abe97c9f6d9fd65015d9b18' + const expectedResult = '0x28fcb6768e5110144a55b2e6ce9d1ea5a58103033632d272d2b5cf506906f7941a00b539383fd872109633d8c71c404e13dba87bc84166ee31b0e36061a69e161c' + + it('passes the dennis test', function(done) { + keyring.deserialize([ privateKey ]) + .then(() => { + return keyring.signMessage(address, message) + }) + .then((result) => { + assert.equal(result, expectedResult) + done() + }) + }) + + it('reliably can decode messages it signs', function (done) { + + const message = 'hello there!' + const msgHashHex = web3.sha3(message) + let address + let addresses = [] + + keyring.deserialize([ privateKey ]) + .then(() => { + keyring.addAccounts(9) + }) + .then(() => { + return keyring.getAccounts() + }) + .then((addrs) => { + addresses = addrs + return Promise.all(addresses.map((address) => { + return keyring.signMessage(address, msgHashHex) + })) + }) + .then((signatures) => { + + signatures.forEach((sgn, index) => { + const address = addresses[index] + + var r = ethUtil.toBuffer(sgn.slice(0,66)) + var s = ethUtil.toBuffer('0x' + sgn.slice(66,130)) + var v = ethUtil.bufferToInt(ethUtil.toBuffer('0x' + sgn.slice(130,132))) + var m = ethUtil.toBuffer(msgHashHex) + var pub = ethUtil.ecrecover(m, v, r, s) + var adr = '0x' + ethUtil.pubToAddress(pub).toString('hex') + + assert.equal(adr, address, 'recovers address from signature correctly') + }) + done() + }) + }) + }) + + 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 = '0x18a3462427bcc9133bb46e88bcbe39cd7ef0e761' + keyring.wallets.push({ + getAddress() { + return ethUtil.toBuffer(desiredOutput) + } + }) + + keyring.getAccounts() + .then((output) => { + assert.equal(output[0], desiredOutput) + assert.equal(output.length, 1) + done() + }) + }) + }) +}) diff --git a/test/unit/message-manager-test.js b/test/unit/message-manager-test.js new file mode 100644 index 000000000..faf7429d4 --- /dev/null +++ b/test/unit/message-manager-test.js @@ -0,0 +1,89 @@ +const assert = require('assert') +const extend = require('xtend') +const EventEmitter = require('events') + +const MessageManger = require('../../app/scripts/lib/message-manager') + +describe('Transaction Manager', function() { + let messageManager + + beforeEach(function() { + messageManager = new MessageManger () + }) + + describe('#getMsgList', function() { + it('when new should return empty array', function() { + var result = messageManager.messages + assert.ok(Array.isArray(result)) + assert.equal(result.length, 0) + }) + it('should also return transactions from local storage if any', function() { + + }) + }) + + describe('#addMsg', function() { + it('adds a Msg returned in getMsgList', function() { + var Msg = { id: 1, status: 'approved', metamaskNetworkId: 'unit test' } + messageManager.addMsg(Msg) + var result = messageManager.messages + assert.ok(Array.isArray(result)) + assert.equal(result.length, 1) + assert.equal(result[0].id, 1) + }) + }) + + describe('#setMsgStatusApproved', function() { + it('sets the Msg status to approved', function() { + var Msg = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test' } + messageManager.addMsg(Msg) + messageManager.setMsgStatusApproved(1) + var result = messageManager.messages + assert.ok(Array.isArray(result)) + assert.equal(result.length, 1) + assert.equal(result[0].status, 'approved') + }) + }) + + describe('#rejectMsg', function() { + it('sets the Msg status to rejected', function() { + var Msg = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test' } + messageManager.addMsg(Msg) + messageManager.rejectMsg(1) + var result = messageManager.messages + assert.ok(Array.isArray(result)) + assert.equal(result.length, 1) + assert.equal(result[0].status, 'rejected') + }) + }) + + describe('#_updateMsg', function() { + it('replaces the Msg with the same id', function() { + messageManager.addMsg({ id: '1', status: 'unapproved', metamaskNetworkId: 'unit test' }) + messageManager.addMsg({ id: '2', status: 'approved', metamaskNetworkId: 'unit test' }) + messageManager._updateMsg({ id: '1', status: 'blah', hash: 'foo', metamaskNetworkId: 'unit test' }) + var result = messageManager.getMsg('1') + assert.equal(result.hash, 'foo') + }) + }) + + describe('#getUnapprovedMsgs', function() { + it('returns unapproved Msgs in a hash', function() { + messageManager.addMsg({ id: '1', status: 'unapproved', metamaskNetworkId: 'unit test' }) + messageManager.addMsg({ id: '2', status: 'approved', metamaskNetworkId: 'unit test' }) + let result = messageManager.getUnapprovedMsgs() + assert.equal(typeof result, 'object') + assert.equal(result['1'].status, 'unapproved') + assert.equal(result['2'], undefined) + }) + }) + + describe('#getMsg', function() { + it('returns a Msg with the requested id', function() { + messageManager.addMsg({ id: '1', status: 'unapproved', metamaskNetworkId: 'unit test' }) + messageManager.addMsg({ id: '2', status: 'approved', metamaskNetworkId: 'unit test' }) + assert.equal(messageManager.getMsg('1').status, 'unapproved') + assert.equal(messageManager.getMsg('2').status, 'approved') + }) + }) +}) diff --git a/test/unit/metamask-controller-test.js b/test/unit/metamask-controller-test.js new file mode 100644 index 000000000..78b9e9df7 --- /dev/null +++ b/test/unit/metamask-controller-test.js @@ -0,0 +1,29 @@ +const assert = require('assert') +const sinon = require('sinon') +const clone = require('clone') +const MetaMaskController = require('../../app/scripts/metamask-controller') +const firstTimeState = require('../../app/scripts/first-time-state') + +const STORAGE_KEY = 'metamask-config' + +describe('MetaMaskController', function() { + const noop = () => {} + let controller = new MetaMaskController({ + showUnconfirmedMessage: noop, + unlockAccountMessage: noop, + showUnapprovedTx: noop, + // initial state + initState: clone(firstTimeState), + }) + + 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() + }) + +})
\ No newline at end of file diff --git a/test/unit/migrations-test.js b/test/unit/migrations-test.js index 9ea8d5c5a..d2a83be77 100644 --- a/test/unit/migrations-test.js +++ b/test/unit/migrations-test.js @@ -1,34 +1,97 @@ -var assert = require('assert') -var path = require('path') +const assert = require('assert') +const path = require('path') -var wallet1 = require(path.join('..', 'lib', 'migrations', '001.json')) +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 -var migration2 = require(path.join('..', '..', 'app', 'scripts', 'migrations', '002')) -var migration3 = require(path.join('..', '..', 'app', 'scripts', 'migrations', '003')) -var migration4 = require(path.join('..', '..', 'app', 'scripts', 'migrations', '004')) +const migration2 = require(path.join('..', '..', 'app', 'scripts', 'migrations', '002')) +const migration3 = require(path.join('..', '..', 'app', 'scripts', 'migrations', '003')) +const migration4 = require(path.join('..', '..', 'app', 'scripts', 'migrations', '004')) +const migration5 = require(path.join('..', '..', 'app', 'scripts', 'migrations', '005')) +const migration6 = require(path.join('..', '..', 'app', 'scripts', 'migrations', '006')) +const migration7 = require(path.join('..', '..', 'app', 'scripts', 'migrations', '007')) +const migration8 = require(path.join('..', '..', 'app', 'scripts', 'migrations', '008')) +const migration9 = require(path.join('..', '..', 'app', 'scripts', 'migrations', '009')) +const migration10 = require(path.join('..', '..', 'app', 'scripts', 'migrations', '010')) +const migration11 = require(path.join('..', '..', 'app', 'scripts', 'migrations', '011')) -describe('wallet1 is migrated successfully', function() { +const oldTestRpc = 'https://rawtestrpc.metamask.io/' +const newTestRpc = 'https://testrpc.metamask.io/' - it('should convert providers', function(done) { +describe('wallet1 is migrated successfully', () => { + it('should convert providers', () => { wallet1.data.config.provider = { type: 'etherscan', rpcTarget: null } - var firstResult = migration2.migrate(wallet1.data) - assert.equal(firstResult.config.provider.type, 'rpc', 'provider should be rpc') - assert.equal(firstResult.config.provider.rpcTarget, 'https://rpc.metamask.io/', 'main provider should be our rpc') + return migration2.migrate(wallet1) + .then((secondResult) => { + const secondData = secondResult.data + assert.equal(secondData.config.provider.type, 'rpc', 'provider should be rpc') + assert.equal(secondData.config.provider.rpcTarget, 'https://rpc.metamask.io/', 'main provider should be our rpc') + secondResult.data.config.provider.rpcTarget = oldTestRpc + return migration3.migrate(secondResult) + }).then((thirdResult) => { + assert.equal(thirdResult.data.config.provider.rpcTarget, newTestRpc, 'config.provider.rpcTarget should be set to the proper testrpc url.') + return migration4.migrate(thirdResult) + }).then((fourthResult) => { + const fourthData = fourthResult.data + assert.equal(fourthData.config.provider.rpcTarget, null, 'old rpcTarget should not exist.') + assert.equal(fourthData.config.provider.type, 'testnet', 'config.provider should be set to testnet.') - var oldTestRpc = 'https://rawtestrpc.metamask.io/' - var newTestRpc = 'https://testrpc.metamask.io/' - firstResult.config.provider.rpcTarget = oldTestRpc + return migration5.migrate(vault4) + }).then((fifthResult) => { + const fifthData = fifthResult.data + assert.equal(fifthData.vault, null, 'old vault should not exist') + assert.equal(fifthData.walletNicknames, null, 'old walletNicknames should not exist') + assert.equal(fifthData.config.selectedAccount, null, 'old config.selectedAccount should not exist') + assert.equal(fifthData.KeyringController.vault, vault4.data.vault, 'KeyringController.vault should exist') + assert.equal(fifthData.KeyringController.selectedAccount, vault4.data.config.selectedAccount, 'KeyringController.selectedAccount should have moved') + assert.equal(fifthData.KeyringController.walletNicknames['0x0beb674745816b125fbc07285d39fd373e64895c'], vault4.data.walletNicknames['0x0beb674745816b125fbc07285d39fd373e64895c'], 'KeyringController.walletNicknames should have moved') - var secondResult = migration3.migrate(firstResult) - assert.equal(secondResult.config.provider.rpcTarget, newTestRpc) + vault5 = fifthResult + return migration6.migrate(fifthResult) + }).then((sixthResult) => { + assert.equal(sixthResult.data.KeyringController.selectedAccount, null, 'old selectedAccount should not exist') + assert.equal(sixthResult.data.PreferencesController.selectedAddress, vault5.data.KeyringController.selectedAccount, 'selectedAccount should have moved') - var thirdResult = migration4.migrate(secondResult) - assert.equal(secondResult.config.provider.rpcTarget, null) - assert.equal(secondResult.config.provider.type, 'testnet') + vault6 = sixthResult + return migration7.migrate(sixthResult) + }).then((seventhResult) => { + assert.equal(seventhResult.data.transactions, null, 'old transactions should not exist') + assert.equal(seventhResult.data.gasMultiplier, null, 'old gasMultiplier should not exist') + assert.equal(seventhResult.data.TransactionManager.transactions[0].id, vault6.data.transactions[0].id, 'transactions should have moved') + assert.equal(seventhResult.data.TransactionManager.gasMultiplier, vault6.data.gasMultiplier, 'gasMultiplier should have moved') + + vault7 = seventhResult + return migration8.migrate(seventhResult) + }).then((eighthResult) => { + assert.equal(eighthResult.data.noticesList, null, 'old noticesList should not exist') + assert.equal(eighthResult.data.NoticeController.noticesList[0].title, vault7.data.noticesList[0].title, 'noticesList should have moved') + + vault8 = eighthResult + return migration9.migrate(eighthResult) + }).then((ninthResult) => { + assert.equal(ninthResult.data.currentFiat, null, 'old currentFiat should not exist') + assert.equal(ninthResult.data.fiatCurrency, null, 'old fiatCurrency should not exist') + assert.equal(ninthResult.data.conversionRate, null, 'old conversionRate should not exist') + assert.equal(ninthResult.data.conversionDate, null, 'old conversionDate should not exist') + + assert.equal(ninthResult.data.CurrencyController.currentCurrency, vault8.data.fiatCurrency, 'currentFiat should have moved') + assert.equal(ninthResult.data.CurrencyController.conversionRate, vault8.data.conversionRate, 'conversionRate should have moved') + assert.equal(ninthResult.data.CurrencyController.conversionDate, vault8.data.conversionDate, 'conversionDate should have moved') + + vault9 = ninthResult + return migration10.migrate(ninthResult) + }).then((tenthResult) => { + assert.equal(tenthResult.data.shapeShiftTxList, null, 'old shapeShiftTxList should not exist') + assert.equal(tenthResult.data.ShapeShiftController.shapeShiftTxList[0].transaction, vault9.data.shapeShiftTxList[0].transaction) + + return migration11.migrate(tenthResult) + }).then((eleventhResult) => { + assert.equal(eleventhResult.data.isDisclaimerConfirmed, null, 'isDisclaimerConfirmed should not exist') + assert.equal(eleventhResult.data.TOSHash, null, 'TOSHash should not exist') + }) - done() }) }) - 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 new file mode 100644 index 000000000..cf00daeba --- /dev/null +++ b/test/unit/notice-controller-test.js @@ -0,0 +1,116 @@ +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-persistance-key' + +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, + }) + }) + + describe('notices', function() { + describe('#getNoticesList', function() { + it('should return an empty array when new', function() { + var testList = [{ + id:0, + read:false, + title:"Futuristic Notice" + }] + var result = noticeController.getNoticesList() + assert.equal(result.length, 0) + }) + }) + + describe('#setNoticesList', function() { + it('should set data appropriately', function () { + var testList = [{ + id:0, + read:false, + title:"Futuristic Notice" + }] + noticeController.setNoticesList(testList) + var testListId = noticeController.getNoticesList()[0].id + assert.equal(testListId, 0) + }) + }) + + describe('#updateNoticeslist', function() { + it('should integrate the latest changes from the source', function() { + var testList = [{ + id:55, + read:false, + title:"Futuristic Notice" + }] + noticeController.setNoticesList(testList) + noticeController.updateNoticesList().then(() => { + var newList = noticeController.getNoticesList() + assert.ok(newList[0].id === 55) + assert.ok(newList[1]) + }) + }) + it('should not overwrite any existing fields', function () { + var testList = [{ + id:0, + read:false, + title:"Futuristic Notice" + }] + noticeController.setNoticesList(testList) + noticeController.updateNoticesList().then(() => { + var newList = noticeController.getNoticesList() + assert.equal(newList[0].id, 0) + assert.equal(newList[0].title, "Futuristic Notice") + assert.equal(newList.length, 1) + }) + }) + }) + + describe('#markNoticeRead', function () { + it('should mark a notice as read', function () { + var testList = [{ + id:0, + read:false, + title:"Futuristic Notice" + }] + noticeController.setNoticesList(testList) + noticeController.markNoticeRead(testList[0]) + var newList = noticeController.getNoticesList() + assert.ok(newList[0].read) + }) + }) + + describe('#getLatestUnreadNotice', function () { + it('should retrieve the latest unread notice', function () { + var testList = [ + {id:0,read:true,title:"Past Notice"}, + {id:1,read:false,title:"Current Notice"}, + {id:2,read:false,title:"Future Notice"}, + ] + noticeController.setNoticesList(testList) + var latestUnread = noticeController.getLatestUnreadNotice() + assert.equal(latestUnread.id, 2) + }) + it('should return undefined if no unread notices exist.', function () { + var testList = [ + {id:0,read:true,title:"Past Notice"}, + {id:1,read:true,title:"Current Notice"}, + {id:2,read:true,title:"Future Notice"}, + ] + noticeController.setNoticesList(testList) + var latestUnread = noticeController.getLatestUnreadNotice() + assert.ok(!latestUnread) + }) + }) + }) + +}) diff --git a/test/unit/tx-manager-test.js b/test/unit/tx-manager-test.js new file mode 100644 index 000000000..f64f048e3 --- /dev/null +++ b/test/unit/tx-manager-test.js @@ -0,0 +1,211 @@ +const assert = require('assert') +const extend = require('xtend') +const EventEmitter = require('events') +const ObservableStore = require('obs-store') +const STORAGE_KEY = 'metamask-persistance-key' +const TransactionManager = require('../../app/scripts/transaction-manager') +const noop = () => true + +describe('Transaction Manager', function() { + let txManager + + beforeEach(function() { + txManager = new TransactionManager({ + networkStore: new ObservableStore({ network: 'unit test' }), + txHistoryLimit: 10, + blockTracker: new EventEmitter(), + }) + }) + + 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: 'unit test', 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('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', 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: 'unit test', 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: 'unit test', 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: 'unit test', 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: 'unit test', 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: 'unit test', 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: 'unit test', 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: 'unit test', 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: 'unit test', txParams: {} }, noop) + txManager.addTx({ id: '2', status: 'confirmed', metamaskNetworkId: 'unit test', txParams: {} }, noop) + txManager.updateTx({ id: '1', status: 'blah', hash: 'foo', metamaskNetworkId: 'unit test', 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: 'unit test', txParams: {} }, noop) + txManager.addTx({ id: '2', status: 'confirmed', metamaskNetworkId: 'unit test', 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: 'unit test', txParams: {} }, noop) + txManager.addTx({ id: '2', status: 'confirmed', metamaskNetworkId: 'unit test', 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: 'unit test' }, + { id: 1, status: 'unapproved', txParams: { from: '0xaa', to: '0xbb' }, metamaskNetworkId: 'unit test' }, + { id: 2, status: 'unapproved', txParams: { from: '0xaa', to: '0xbb' }, metamaskNetworkId: 'unit test' }, + { id: 3, status: 'unapproved', txParams: { from: '0xbb', to: '0xaa' }, metamaskNetworkId: 'unit test' }, + { id: 4, status: 'unapproved', txParams: { from: '0xbb', to: '0xaa' }, metamaskNetworkId: 'unit test' }, + { id: 5, status: 'confirmed', txParams: { from: '0xaa', to: '0xbb' }, metamaskNetworkId: 'unit test' }, + { id: 6, status: 'confirmed', txParams: { from: '0xaa', to: '0xbb' }, metamaskNetworkId: 'unit test' }, + { id: 7, status: 'confirmed', txParams: { from: '0xbb', to: '0xaa' }, metamaskNetworkId: 'unit test' }, + { id: 8, status: 'confirmed', txParams: { from: '0xbb', to: '0xaa' }, metamaskNetworkId: 'unit test' }, + { id: 9, status: 'confirmed', txParams: { from: '0xbb', to: '0xaa' }, metamaskNetworkId: 'unit test' }, + ] + 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)}`) + }) + }) + +}) diff --git a/test/unit/util_test.js b/test/unit/util_test.js index 45e545e8e..00528b905 100644 --- a/test/unit/util_test.js +++ b/test/unit/util_test.js @@ -227,5 +227,27 @@ describe('util', function() { assert.equal(result.toString(10), '1111000000000000000', 'accepts decimals') }) }) + 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() { + var result = util.isHex('c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714imnotreal') + assert(!result) + }) + + 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() { + var result = util.isHex('0xc3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2') + assert(result) + }) + + }) }) }) |