diff --git a/.gitignore b/.gitignore index f7555ecd4..8aa3d8515 100644 --- a/.gitignore +++ b/.gitignore @@ -77,5 +77,8 @@ dist/windows dist/*.dmg dist/*.tar.gz dist/*.exe +<<<<<<< HEAD doc/ +/node_modules +/*-cov diff --git a/js/app.js b/js/app.js index 216553f20..a9e3c39f0 100644 --- a/js/app.js +++ b/js/app.js @@ -21,8 +21,6 @@ if (localConfig) { var log = function() { if (config.verbose) console.log(arguments); } -var copayApp = window.copayApp = angular.module('copayApp', [ - var modules = [ 'ngRoute', diff --git a/js/models/Storage.js b/js/models/Storage.js index 4d842e431..5750eb428 100644 --- a/js/models/Storage.js +++ b/js/models/Storage.js @@ -12,7 +12,7 @@ function Storage(opts) { this.__uniqueid = ++id; if (opts.password) - this._setPassphrase(opts.password); + this.setPassphrase(opts.password); try { this.storage = opts.storage || localStorage; @@ -33,7 +33,7 @@ Storage.prototype._getPassphrase = function() { return pps[this.__uniqueid]; } -Storage.prototype._setPassphrase = function(password) { +Storage.prototype.setPassphrase = function(password) { pps[this.__uniqueid] = password; } @@ -231,6 +231,7 @@ Storage.prototype.getWallets = function(cb) { Storage.prototype.deleteWallet = function(walletId, cb) { preconditions.checkArgument(walletId); preconditions.checkArgument(cb); + var err; var toDelete = {}; toDelete['nameFor::' + walletId] = 1; @@ -245,11 +246,15 @@ Storage.prototype.deleteWallet = function(walletId, cb) { }); var l = toDelete.length, - i = 0; + j = 0; + + if (!l) + return cb(new Error('WNOTFOUND: Wallet not found')); + for (var i in toDelete) { this.removeGlobal(i, function() { - if (++i == l) - return cb(); + if (++j == l) + return cb(err); }); } }; diff --git a/js/models/core/PluginManager.js b/js/models/core/PluginManager.js index bce94e464..7aced3b49 100644 --- a/js/models/core/PluginManager.js +++ b/js/models/core/PluginManager.js @@ -1,5 +1,6 @@ 'use strict'; var preconditions = require('preconditions').singleton(); +var log = require('../../log'); function PluginManager(config) { this.registered = {}; @@ -11,7 +12,7 @@ function PluginManager(config) { if (!config.plugins[pluginName]) continue; - console.log('Loading plugin: ' + pluginName); + log.info('Loading plugin: ' + pluginName); var pluginClass = require('../plugins/' + pluginName); var pluginObj = new pluginClass(config[pluginName]); pluginObj.init(); diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 6ab010c5a..c14a568fe 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -64,7 +64,7 @@ function Wallet(opts) { 'publicKeyRing', 'txProposals', 'privateKey', 'version', 'reconnectDelay' ].forEach(function(k) { - preconditions.checkArgument(!_.isUndefined(opts[k]), 'missing required option for Wallet: ' + k); + preconditions.checkArgument(!_.isUndefined(opts[k]), 'MISSOPT: missing required option for Wallet: ' + k); self[k] = opts[k]; }); preconditions.checkArgument(!copayConfig.forceNetwork || this.getNetworkName() === copayConfig.networkName, diff --git a/js/models/core/WalletFactory.js b/js/models/core/WalletFactory.js index eca5e656b..15e4eee16 100644 --- a/js/models/core/WalletFactory.js +++ b/js/models/core/WalletFactory.js @@ -120,7 +120,7 @@ WalletFactory.prototype.fromObj = function(obj, skipFields) { * @return {Wallet} */ WalletFactory.prototype.fromEncryptedObj = function(base64, password, skipFields) { - this.storage._setPassphrase(password); + this.storage.setPassphrase(password); var walletObj = this.storage.import(base64); if (!walletObj) return false; var w = this.fromObj(walletObj, skipFields); @@ -148,18 +148,34 @@ WalletFactory.prototype.import = function(base64, password, skipFields) { * @desc Retrieve a wallet from storage * @param {string} walletId - the wallet id * @param {string[]} skipFields - parameters to ignore when importing - * @return {Wallet} + * @param {function} callback - {err, Wallet} */ WalletFactory.prototype.read = function(walletId, skipFields, cb) { - var self = this, err; + var self = this, + err; var obj = {}; - obj.id = walletId; this.storage.getMany(walletId, Wallet.PERSISTED_PROPERTIES, function(ret) { for (var ii in ret) { obj[ii] = ret[ii]; } - return cb(err, self.fromObj(obj, skipFields)); + + if (!_.any(_.values(obj))) + return cb(new Error('Wallet not found')); + + var w, err; + obj.id = walletId; + try { + w = self.fromObj(obj, skipFields); + } catch (e) { + if (e && e.message && e.message.indexOf('MISSOPTS')) { + err = new Error('Could not read: ' + walletId); + } else { + err = e; + } + w = null; + } + return cb(err, w); }); }; @@ -221,7 +237,7 @@ WalletFactory.prototype.create = function(opts, cb) { }); log.debug('\t### TxProposals Initialized'); - this.storage._setPassphrase(opts.passphrase); + this.storage.setPassphrase(opts.passphrase); opts.storage = this.storage; opts.network = this.networks[opts.networkName]; @@ -272,8 +288,8 @@ WalletFactory.prototype._checkVersion = function(inVersion) { WalletFactory.prototype.open = function(walletId, passphrase, cb) { preconditions.checkArgument(cb); var self = this; - self.storage._setPassphrase(passphrase); - self.read(err, walletId, null, function(w) { + self.storage.setPassphrase(passphrase); + self.read(walletId, null, function(err, w) { if (err) return cb(err); w.store(function(err) { @@ -285,11 +301,11 @@ WalletFactory.prototype.open = function(walletId, passphrase, cb) { }; WalletFactory.prototype.getWallets = function(cb) { - var ret = this.storage.getWallets(function(ret) { + this.storage.getWallets(function(ret) { ret.forEach(function(i) { i.show = i.name ? ((i.name + ' <' + i.id + '>')) : i.id; }); - return cb(ret); + return cb(null, ret); }); }; @@ -303,9 +319,12 @@ WalletFactory.prototype.getWallets = function(cb) { */ WalletFactory.prototype.delete = function(walletId, cb) { var s = this.storage; - s.deleteWallet(walletId); - s.setLastOpened(undefined); - return cb(); + s.deleteWallet(walletId, function(err) { + if (err) return cb(err); + s.setLastOpened(null, function(err) { + return cb(err); + }); + }); }; /** diff --git a/package.json b/package.json index 7248c5c0f..248cac974 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "dependencies": { "browser-request": "^0.3.2", "inherits": "^2.0.1", - "mocha": "^1.18.2", + "mocha": "^1.21.4", "mocha-lcov-reporter": "0.0.1", "optimist": "^0.6.1", "preconditions": "^1.0.7", @@ -27,12 +27,14 @@ "chrome": "source browser-extensions/chrome/build.sh", "setup-shell": "node shell/scripts/download-atom-shell.js", "start": "node server.js", - "coverage": "./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha -- --reporter spec test", - "test": "sh test/run.sh", + "coverage": "mochacoverage", + "test": "mocha --ui exports --reporter spec", "dist": "node shell/scripts/dist.js", "sign": "gpg -u 1112CFA1 --output browser-extensions/firefox/copay.xpi.sig --detach-sig browser-extensions/firefox/copay.xpi; gpg -u 1112CFA1 --output browser-extensions/chrome/copay-chrome-extension.zip.sig --detach-sig browser-extensions/chrome/copay-chrome-extension.zip", "verify": "gpg --verify browser-extensions/firefox/copay.xpi.sig browser-extensions/firefox/copay.xpi; gpg --verify browser-extensions/chrome/copay-chrome-extension.zip.sig browser-extensions/chrome/copay-chrome-extension.zip", - "postinstall": "./node_modules/.bin/grunt" + "postinstall": "./node_modules/.bin/grunt", + "monitor": "mocha --ui exports --reporter min --watch", + "debugtest": "mocha --debug-brk --ui exports --reporter spec" }, "keywords": [ "wallet", @@ -81,7 +83,8 @@ "shelljs": "0.3.0", "socket.io-client": "1.0.6", "travis-cov": "0.2.5", - "uglifyify": "1.2.3" + "uglifyify": "1.2.3", + "mochawrapper": "" }, "main": "app.js", "homepage": "https://github.com/bitpay/copay", diff --git a/test/mocks/FakeStorage.js b/test/mocks/FakeStorage.js index ef8b33b3c..ba64b382a 100644 --- a/test/mocks/FakeStorage.js +++ b/test/mocks/FakeStorage.js @@ -21,9 +21,9 @@ FakeStorage.prototype.getGlobal = function(id, cb) { }; FakeStorage.prototype.getMany = function(wid, fields, cb) { - var self= this; + var self = this; var ret = []; - for(var ii in fields){ + for (var ii in fields) { var k = fields[ii]; ret[k] = this.storage[wid + '::' + k]; } @@ -113,14 +113,23 @@ FakeStorage.prototype.deleteWallet = function(walletId, cb) { toDelete[key] = 1; } } + var l = toDelete.length, + j = 0; + + if (!l) + return cb(new Error('WNOTFOUND: Wallet not found')); + for (var i in toDelete) { - this.removeGlobal(i); + this.removeGlobal(i, cb); + if (++j == l) + return cb(err); + } }; FakeStorage.prototype.getName = function(walletId, cb) { - return this.getGlobal('nameFor::' + walletId, cb); + this.getGlobal('nameFor::' + walletId, cb); }; @@ -131,15 +140,16 @@ FakeStorage.prototype.setName = function(walletId, name, cb) { FakeStorage.prototype.getWallets = function(cb) { var wallets = []; - var ids = this.getWalletIds(); - - for (var i in ids) { - wallets.push({ - id: ids[i], - name: this.getName(ids[i]), - }); - } - return cb(wallets); + var self= this; + this.getWalletIds(function(ids) { + for (var i in ids) { + wallets.push({ + id: ids[i], + name: self.storage['nameFor::' + ids[i]], + }); + } + return cb(wallets); + }); }; FakeStorage.prototype.setFromObj = function(walletId, obj, cb) { diff --git a/test/test.WalletFactory.js b/test/test.WalletFactory.js index 36c032b80..be09931d6 100644 --- a/test/test.WalletFactory.js +++ b/test/test.WalletFactory.js @@ -155,8 +155,8 @@ describe('WalletFactory model', function() { it('should be able to get wallets', function(done) { var wf = new WalletFactory(config, '0.0.1'); - wf.create(null, function(err,w){ - wf.read(w.id, [], function(err, w2){ + wf.create(null, function(err, w) { + wf.read(w.id, [], function(err, w2) { should.exist(w2); w2.id.should.equal(w.id); done(); @@ -312,107 +312,177 @@ describe('WalletFactory model', function() { 'requiredCopayers': 2, 'totalCopayers': 3 }; - wf.create(opts, function(err,w){ + wf.create(opts, function(err, w) { should.not.exist(err); should.exist(w); done(); }); }); - it('should be able to get current wallets', function() { + it('should be able to get current wallets', function(done) { var wf = new WalletFactory(config, '0.0.1'); - var ws = wf.getWallets(); - - var w = wf.create({ + wf.create({ name: 'test wallet' - }); + }, function(err, ws) { + should.not.exist(err); - ws = wf.getWallets(); - ws.length.should.equal(1); - ws[0].name.should.equal('test wallet'); - }); - - it('should be able to delete wallet', function(done) { - var wf = new WalletFactory(config, '0.0.1'); - var w = wf.create({ - name: 'test wallet' - }); - var ws = wf.getWallets(); - ws.length.should.equal(1); - wf.delete(ws[0].id, function() { - ws = wf.getWallets(); - ws.length.should.equal(0); - done(); + wf.getWallets(function(err, ws) { + should.not.exist(err); + ws.length.should.equal(1); + ws[0].name.should.equal('test wallet'); + done(); + }); }); }); - it('should clean lastOpened on delete wallet', function(done) { - var wf = new WalletFactory(config, '0.0.1'); - var w = wf.create({ - name: 'test wallet' + describe('#delete', function() { + it('should call deleteWallet', function(done) { + var wf = new WalletFactory(config, '0.0.1'); + var s1 = sinon.spy(wf.storage, 'deleteWallet'); + + wf.delete('xxx', function() { + s1.getCall(0).args[0].should.equal('xxx'); + done(); + }); }); - wf.storage.setLastOpened(w.id); - wf.delete(w.id, function() { - var last = wf.storage.getLastOpened(); - should.equal(last, undefined); - done(); + it('should call lastOpened', function(done) { + var wf = new WalletFactory(config, '0.0.1'); + var s1 = sinon.stub(wf.storage, 'deleteWallet').yields(null); + var s2 = sinon.stub(wf.storage, 'setLastOpened').yields(null); + wf.delete('xxx', function() { + s2.calledOnce.should.equal(true); + should.not.exist(s2.getCall(0).args[0]); + done(); + }); }); }); - it('should return false if wallet does not exist', function() { + + describe('#read', function() { + it('should fail to read unexisting wallet', function(done) { + var wf = new WalletFactory(config, '0.0.1'); + wf.read('id', [], function(err, w) { + should.not.exist(w); + should.exist(err); + should.exist(err.message); + var m = err.message.toString(); + m.should.to.have.string('Wallet not found'); + done(); + }); + }); + it('should fail to read broken wallet', function(done) { + var wf = new WalletFactory(config, '0.0.1'); + wf.storage.getMany = sinon.stub().yields({ + 'opts': 1 + }); + wf.read('id', [], function(err, w) { + should.not.exist(w); + should.exist(err); + should.exist(err.message); + var m = err.message.toString(); + m.should.to.have.string('Could not read'); + done(); + }); + }); + it('should read existing wallet', function(done) { + var wf = new WalletFactory(config, '0.0.1'); + wf.storage.getMany = sinon.stub().yields({ + 'opts': 1 + }); + wf.fromObj = sinon.stub().returns('ok'); + wf.read('id', [], function(err, w) { + should.not.exist(err); + should.exist(w); + done(); + }); + }); + }); + + + describe('#open', function() { var opts = { - 'requiredCopayers': 2, - 'totalCopayers': 3 + 'requiredcopayers': 2, + 'totalcopayers': 3 }; - var wf = new WalletFactory(config, '0.0.1'); - var w = wf.open('dummy', opts); - should.exist(w); + it('should call setPassphrase', function(done) { + var wf = new WalletFactory(config, '0.0.1'); + wf.storage.setPassphrase = sinon.spy(); + + var s1 = sinon.stub(); + s1.store = sinon.stub().yields(null); + wf.read = sinon.stub().yields(null, s1); + wf.storage.setLastOpened = sinon.stub().yields(null); + + wf.open('dummy', 'xxx', function(err, w) { + wf.storage.setPassphrase.calledOnce.should.equal(true); + wf.storage.setPassphrase.getCall(0).args[0].should.equal('xxx'); + done(); + }); + }); + + it('should call return wallet', function(done) { + var wf = new WalletFactory(config, '0.0.1'); + wf.storage.setPassphrase = sinon.spy(); + + var s1 = sinon.stub(); + s1.store = sinon.stub().yields(null); + wf.read = sinon.stub().yields(null, s1); + wf.storage.setLastOpened = sinon.stub().yields(null); + + wf.open('dummy', 'xxx', function(err, w) { + w.should.equal(s1); + s1.store.calledOnce.should.equal(true); + done(); + }); + }); + + + it('should call #store', function(done) { + var wf = new WalletFactory(config, '0.0.1'); + wf.storage.setPassphrase = sinon.spy(); + + var s1 = sinon.stub(); + s1.store = sinon.stub().yields(null); + wf.read = sinon.stub().yields(null, s1); + wf.storage.setLastOpened = sinon.stub().yields(null); + + wf.open('dummy', 'xxx', function(err, w) { + s1.store.calledOnce.should.equal(true); + done(); + }); + }); + + it('should call #setLastOpened', function(done) { + var wf = new WalletFactory(config, '0.0.1'); + wf.storage.setPassphrase = sinon.spy(); + + var s1 = sinon.stub(); + s1.store = sinon.stub().yields(null); + wf.read = sinon.stub().yields(null, s1); + wf.storage.setLastOpened = sinon.stub().yields(null); + + wf.open('dummy', 'xxx', function(err, w) { + wf.storage.setLastOpened.calledOnce.should.equal(true); + wf.storage.setLastOpened.getCall(0).args[0].should.equal('dummy'); + done(); + }); + }); }); - it('should open a wallet', function() { - var opts = { - 'requiredCopayers': 2, - 'totalCopayers': 3 - }; - var wf = new WalletFactory(config, '0.0.1'); - var w = wf.create(opts); - var walletId = w.id; - - wf.read = sinon.stub().withArgs(walletId).returns(w); - var wo = wf.open(walletId, opts); - should.exist(wo); - wf.read.calledWith(walletId).should.be.true; - }); - - it('should save lastOpened on create/open a wallet', function() { - var opts = { - 'requiredCopayers': 2, - 'totalCopayers': 3 - }; - var wf = new WalletFactory(config, '0.0.1'); - var w = wf.create(opts); - var last = wf.storage.getLastOpened(); - should.equal(last, w.id); - - wf.storage.setLastOpened('other_id'); - - var wo = wf.open(w.id, opts); - last = wf.storage.getLastOpened(); - should.equal(last, w.id); - }); - - it('should return error if network are differents', function() { - var opts = { - 'requiredCopayers': 2, - 'totalCopayers': 3 - }; - var wf = new WalletFactory(config, '0.0.1'); - var w = wf.create(opts); - (function() { - wf._checkNetwork('livenet'); - }).should.throw(); + describe.only('#create', function() { + it('should return error if network are differents', function() { + var opts = { + 'requiredCopayers': 2, + 'totalCopayers': 3 + }; + var wf = new WalletFactory(config, '0.0.1'); + var w = wf.create(opts, function(err, w) { + err.should.to.have.string('Wallet not found'); + should.not.exist(w); + }); + }); }); describe('#joinCreateSession', function() {