From 728ae3ec0daa0a617a4bf4097109f41d5cf68077 Mon Sep 17 00:00:00 2001 From: Matias Pando Date: Sat, 20 Dec 2014 08:23:32 -0300 Subject: [PATCH 01/15] Allow older Android versions --- js/routes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/routes.js b/js/routes.js index c39314443..1f443ee76 100644 --- a/js/routes.js +++ b/js/routes.js @@ -145,7 +145,7 @@ angular } $rootScope.$on('$routeChangeStart', function(event, next, current) { - if (unsupported) { + if (unsupported && !isCordova) { $location.path('unsupported'); return; } From 1021cbc4fa31c3893fa8ef3047c81d56544469ce Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Tue, 30 Dec 2014 15:12:01 -0300 Subject: [PATCH 02/15] added checksum methods to profile --- js/models/Identity.js | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/js/models/Identity.js b/js/models/Identity.js index 51d8c4191..23796c78f 100644 --- a/js/models/Identity.js +++ b/js/models/Identity.js @@ -76,6 +76,10 @@ Identity.getKeyForEmail = function(email) { return Identity.getStoragePrefix() + bitcore.util.sha256ripe160(email).toString('hex'); }; +Identity.prototype.getChecksumForStorage = function() { + return JSON.stringify(this.walletIds); +}; + Identity.prototype.getId = function() { return Identity.getKeyForEmail(this.email); }; @@ -148,6 +152,22 @@ Identity.open = function(opts, cb) { }); }; +Identity.prototype.verifyChecksum = function (cb) { + var self = this; + + self.storage.getItem(Identity.getKeyForEmail(self.email), function(err, data, headers) { + var iden; + if (err) return cb(err); + try { + iden = JSON.parse(data); + } catch (e) { + return cb(e); + } + + return cb(null, self.getChecksumForStorage() == self.getChecksumForStorage.call(iden)); +}; + + /** * @param {string} walletId * @returns {Wallet} From f2cda099f96ab5eacc1240701ee264ea30ac8da4 Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Tue, 30 Dec 2014 15:49:19 -0300 Subject: [PATCH 03/15] verify checksum on actions that store the profile --- js/models/Identity.js | 181 +++++++++++++++++++---------------- js/services/backupService.js | 2 +- 2 files changed, 97 insertions(+), 86 deletions(-) diff --git a/js/models/Identity.js b/js/models/Identity.js index 23796c78f..2ad0b22da 100644 --- a/js/models/Identity.js +++ b/js/models/Identity.js @@ -77,7 +77,7 @@ Identity.getKeyForEmail = function(email) { }; Identity.prototype.getChecksumForStorage = function() { - return JSON.stringify(this.walletIds); + return JSON.stringify(_.sortBy(this.walletIds)); }; Identity.prototype.getId = function() { @@ -163,8 +163,8 @@ Identity.prototype.verifyChecksum = function (cb) { } catch (e) { return cb(e); } - return cb(null, self.getChecksumForStorage() == self.getChecksumForStorage.call(iden)); + }); }; @@ -206,18 +206,22 @@ Identity.prototype.deleteWallet = function(walletId, cb) { var self = this; - - var w = this.getWalletById(walletId); - w.close(); - - delete this.wallets[walletId]; - delete this.focusedTimestamps[walletId]; - this.walletIds = _.without(this.walletIds, walletId); - - this.storage.removeItem(Wallet.getStorageKey(walletId), function(err) { + self.verifyChecksum(function (err, match) { if (err) return cb(err); - self.emitAndKeepAlive('walletDeleted', walletId); - self.store(null, cb); + if (!match) return cb('The profile is out of sync'); + + var w = self.getWalletById(walletId); + w.close(); + + delete self.wallets[walletId]; + delete self.focusedTimestamps[walletId]; + self.walletIds = _.without(self.walletIds, walletId); + + self.storage.removeItem(Wallet.getStorageKey(walletId), function(err) { + if (err) return cb(err); + self.emitAndKeepAlive('walletDeleted', walletId); + self.store(null, cb); + }); }); }; @@ -366,18 +370,19 @@ Identity.prototype.exportEncryptedWithWalletInfo = function(opts) { return crypto.encrypt(this.password, this.exportWithWalletInfo(opts)); }; -Identity.prototype.setBackupNeeded = function() { - this.backupNeeded = true; - this.store({ - noWallets: true - }, function() {}); -} +Identity.prototype.setBackupNeeded = function(backupNeeded) { + var self = this; -Identity.prototype.setBackupDone = function() { - this.backupNeeded = false; - this.store({ - noWallets: true - }, function() {}); + self.backupNeeded = !!backupNeeded; + + self.verifyChecksum(function (err, match) { + if (err) return cb(err); + if (!match) return cb('The profile is out of sync'); + + self.store({ + noWallets: true + }, function() {}); + }); } Identity.prototype.exportWithWalletInfo = function(opts) { @@ -618,71 +623,77 @@ Identity.prototype.bindWallet = function(w) { */ Identity.prototype.createWallet = function(opts, cb) { preconditions.checkArgument(cb); - - opts = opts || {}; - opts.networkName = opts.networkName || 'testnet'; - - log.debug('### CREATING NEW WALLET.' + (opts.id ? ' USING ID: ' + opts.id : ' NEW ID') + (opts.privateKey ? ' USING PrivateKey: ' + opts.privateKey.getId() : ' NEW PrivateKey')); - - var privOpts = { - networkName: opts.networkName, - }; - - if (opts.privateKeyHex && opts.privateKeyHex.length > 1) { - privOpts.extendedPrivateKeyString = opts.privateKeyHex; - } - - opts.privateKey = opts.privateKey || new PrivateKey(privOpts); - - var requiredCopayers = opts.requiredCopayers || this.walletDefaults.requiredCopayers; - var totalCopayers = opts.totalCopayers || this.walletDefaults.totalCopayers; - opts.lockTimeoutMin = this.walletDefaults.idleDurationMin; - - opts.publicKeyRing = opts.publicKeyRing || new PublicKeyRing({ - networkName: opts.networkName, - requiredCopayers: requiredCopayers, - totalCopayers: totalCopayers, - }); - opts.publicKeyRing.addCopayer( - opts.privateKey.deriveBIP45Branch().extendedPublicKeyString(), - opts.nickname || this.getName() - ); - log.debug('\t### PublicKeyRing Initialized'); - - opts.txProposals = opts.txProposals || new TxProposals({ - networkName: opts.networkName, - }); - var walletClass = opts.walletClass || Wallet; - - log.debug('\t### TxProposals Initialized'); - - - opts.networkOpts = this.networkOpts; - opts.blockchainOpts = this.blockchainOpts; - - opts.spendUnconfirmed = opts.spendUnconfirmed || this.walletDefaults.spendUnconfirmed; - opts.reconnectDelay = opts.reconnectDelay || this.walletDefaults.reconnectDelay; - opts.requiredCopayers = requiredCopayers; - opts.totalCopayers = totalCopayers; - opts.version = opts.version || this.version; - + var self = this; - var w = new walletClass(opts); - if (self.getWalletById(w.getId())) { - return cb('walletAlreadyExists'); - } - self.addWallet(w); - self.updateFocusedTimestamp(w.getId()); - self.bindWallet(w); - self.storeWallet(w, function(err) { + self.verifyChecksum(function (err, match) { if (err) return cb(err); + if (!match) return cb('The profile is out of sync'); - self.backupNeeded = true; - self.store({ - noWallets: true, - }, function(err) { - return cb(err, w); + opts = opts || {}; + opts.networkName = opts.networkName || 'testnet'; + + log.debug('### CREATING NEW WALLET.' + (opts.id ? ' USING ID: ' + opts.id : ' NEW ID') + (opts.privateKey ? ' USING PrivateKey: ' + opts.privateKey.getId() : ' NEW PrivateKey')); + + var privOpts = { + networkName: opts.networkName, + }; + + if (opts.privateKeyHex && opts.privateKeyHex.length > 1) { + privOpts.extendedPrivateKeyString = opts.privateKeyHex; + } + + opts.privateKey = opts.privateKey || new PrivateKey(privOpts); + + var requiredCopayers = opts.requiredCopayers || self.walletDefaults.requiredCopayers; + var totalCopayers = opts.totalCopayers || self.walletDefaults.totalCopayers; + opts.lockTimeoutMin = self.walletDefaults.idleDurationMin; + + opts.publicKeyRing = opts.publicKeyRing || new PublicKeyRing({ + networkName: opts.networkName, + requiredCopayers: requiredCopayers, + totalCopayers: totalCopayers, + }); + opts.publicKeyRing.addCopayer( + opts.privateKey.deriveBIP45Branch().extendedPublicKeyString(), + opts.nickname || self.getName() + ); + log.debug('\t### PublicKeyRing Initialized'); + + opts.txProposals = opts.txProposals || new TxProposals({ + networkName: opts.networkName, + }); + var walletClass = opts.walletClass || Wallet; + + log.debug('\t### TxProposals Initialized'); + + + opts.networkOpts = self.networkOpts; + opts.blockchainOpts = self.blockchainOpts; + + opts.spendUnconfirmed = opts.spendUnconfirmed || self.walletDefaults.spendUnconfirmed; + opts.reconnectDelay = opts.reconnectDelay || self.walletDefaults.reconnectDelay; + opts.requiredCopayers = requiredCopayers; + opts.totalCopayers = totalCopayers; + opts.version = opts.version || self.version; + + var w = new walletClass(opts); + + if (self.getWalletById(w.getId())) { + return cb('walletAlreadyExists'); + } + self.addWallet(w); + self.updateFocusedTimestamp(w.getId()); + self.bindWallet(w); + self.storeWallet(w, function(err) { + if (err) return cb(err); + + self.backupNeeded = true; + self.store({ + noWallets: true, + }, function(err) { + return cb(err, w); + }); }); }); }; diff --git a/js/services/backupService.js b/js/services/backupService.js index f8a445907..5caf3f3ce 100644 --- a/js/services/backupService.js +++ b/js/services/backupService.js @@ -72,7 +72,7 @@ BackupService.prototype.walletDownload = function(wallet) { }; BackupService.prototype.profileEncrypted = function(iden) { - iden.setBackupDone(); + iden.setBackupNeeded(false); return iden.exportEncryptedWithWalletInfo(iden.password); } From e3aafbf86005dd8aedd7f52b71de02a6805ccf3e Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Tue, 30 Dec 2014 15:50:30 -0300 Subject: [PATCH 04/15] improved error message --- js/models/Identity.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/js/models/Identity.js b/js/models/Identity.js index 2ad0b22da..84868bafd 100644 --- a/js/models/Identity.js +++ b/js/models/Identity.js @@ -208,7 +208,7 @@ Identity.prototype.deleteWallet = function(walletId, cb) { self.verifyChecksum(function (err, match) { if (err) return cb(err); - if (!match) return cb('The profile is out of sync'); + if (!match) return cb('The profile is out of sync. Please re-login to get the latest changes.'); var w = self.getWalletById(walletId); w.close(); @@ -377,7 +377,7 @@ Identity.prototype.setBackupNeeded = function(backupNeeded) { self.verifyChecksum(function (err, match) { if (err) return cb(err); - if (!match) return cb('The profile is out of sync'); + if (!match) return cb('The profile is out of sync. Please re-login to get the latest changes.'); self.store({ noWallets: true @@ -628,7 +628,7 @@ Identity.prototype.createWallet = function(opts, cb) { self.verifyChecksum(function (err, match) { if (err) return cb(err); - if (!match) return cb('The profile is out of sync'); + if (!match) return cb('The profile is out of sync. Please re-login to get the latest changes.'); opts = opts || {}; opts.networkName = opts.networkName || 'testnet'; From 8edcfcf69bf104d627f9b9b6d11e5519126c6401 Mon Sep 17 00:00:00 2001 From: annie246 Date: Tue, 30 Dec 2014 13:53:49 -0500 Subject: [PATCH 05/15] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e9dac7f08..1ee07b9b0 100644 --- a/README.md +++ b/README.md @@ -4,10 +4,10 @@ [![Coverage Status](https://img.shields.io/coveralls/bitpay/copay.svg)](https://coveralls.io/r/bitpay/copay?branch=master) [![Stories in Ready](https://badge.waffle.io/bitpay/copay.svg?label=in progress&title=In progress)](https://waffle.io/bitpay/copay) -Copay is a secure bitcoin wallet for friends and companies. +Copay is an open-source secure bitcoin wallet platform for friends and companies. Easy-to-use multisignature bitcoin wallet, bringing corporate-level security to ordinary people. -When friends or company executives join a Copay wallet, more than one person must sign every transaction. If your computer is compromised and your private keys are stolen, the bitcoins are still safe. This is in addition to state-of-the-art encrypted storage and communication. +When friends or company executives join a Copay wallet, more than one person must sign every transaction. If your computer is compromised and your private keys are stolen, the bitcoins are still safe if you use the multi-signature feature. This is in addition to state-of-the-art encrypted storage and communication. ## Before you start From 094f38c5e9801f32f3fd3ca97e5894e67a4a6f8d Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Tue, 30 Dec 2014 16:20:18 -0300 Subject: [PATCH 06/15] giving proper feedback --- js/controllers/profile.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/js/controllers/profile.js b/js/controllers/profile.js index bc66c2806..8bebdfeae 100644 --- a/js/controllers/profile.js +++ b/js/controllers/profile.js @@ -52,9 +52,9 @@ angular.module('copayApp.controllers').controller('ProfileController', function( return; } $location.path('/'); - setTimeout(function() { + $timeout(function() { notification.error('Success', 'Profile successfully deleted'); - }, 1); + }); }); }; @@ -73,10 +73,10 @@ angular.module('copayApp.controllers').controller('ProfileController', function( identityService.deleteWallet($scope.item, function(err) { if (err) { $scope.loading = null; - $scope.error = err.message; + $scope.error = err.message || err; copay.logger.warn(err); - } - else { + $timeout(function () { $scope.$digest(); }); + } else { $modalInstance.close($scope.item.name || $scope.item.id); } }); From a769ba456a3a158bdd1aa88133a3bcdea4c946e7 Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Tue, 30 Dec 2014 16:30:05 -0300 Subject: [PATCH 07/15] fixed tests --- test/Identity.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/Identity.js b/test/Identity.js index 2b77a5ac8..038ca7d34 100644 --- a/test/Identity.js +++ b/test/Identity.js @@ -327,6 +327,7 @@ describe('Identity model', function() { it('should be able to create wallets with given pk', function(done) { var priv = 'tprv8ZgxMBicQKsPdEqHcA7RjJTayxA3gSSqeRTttS1JjVbgmNDZdSk9EHZK5pc52GY5xFmwcakmUeKWUDzGoMLGAhrfr5b3MovMUZUTPqisL2m'; + args.storage.getItem = sinon.stub().yields(null, JSON.stringify(iden)); args.storage.setItem = sinon.stub(); args.storage.setItem.onFirstCall().callsArg(2); args.storage.setItem.onSecondCall().callsArg(2); @@ -342,6 +343,7 @@ describe('Identity model', function() { }); it('should be able to create wallets with random pk', function(done) { + args.storage.getItem = sinon.stub().yields(null, JSON.stringify(iden)); args.storage.setItem = sinon.stub(); args.storage.setItem.onCall(0).callsArg(2); args.storage.setItem.onCall(1).callsArg(2); @@ -351,7 +353,8 @@ describe('Identity model', function() { walletClass: walletClass, }, function(err, w1) { should.exist(w1); - + + args.storage.getItem = sinon.stub().yields(null, JSON.stringify(iden)); iden.createWallet({ walletClass: walletClass, }, function(err, w2) { @@ -622,6 +625,7 @@ describe('Identity model', function() { it('should delete wallet', function(done) { iden.addWallet(w); iden.getWalletById('32').getName().should.equal('treintaydos'); + iden.storage.getItem = sinon.stub().yields(null, JSON.stringify(iden)); iden.deleteWallet('32', function(err) { should.not.exist(iden.getWalletById('32')); iden.walletIds.should.deep.equal([]); From 863bb98170ba7a2b66876ed935012907c5d3afe5 Mon Sep 17 00:00:00 2001 From: Matias Pando Date: Wed, 31 Dec 2014 09:54:09 -0300 Subject: [PATCH 08/15] Fixing condition for supported browsers --- js/routes.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/routes.js b/js/routes.js index 1f443ee76..a0589ef31 100644 --- a/js/routes.js +++ b/js/routes.js @@ -11,7 +11,7 @@ if (!ls || ls.length < 1) if (window && window.navigator) { var rxaosp = window.navigator.userAgent.match(/Android.*AppleWebKit\/([\d.]+)/); - var isaosp = (rxaosp && rxaosp[1] < 537); + var isaosp = (rxaosp && rxaosp[1] < 534); if (isaosp) unsupported = true; } @@ -145,7 +145,7 @@ angular } $rootScope.$on('$routeChangeStart', function(event, next, current) { - if (unsupported && !isCordova) { + if (unsupported) { $location.path('unsupported'); return; } From 5cd5287baf15b02f20093ddbebd4f89e91b610fd Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Wed, 31 Dec 2014 10:14:29 -0300 Subject: [PATCH 09/15] do not show proposals on 1-of-N wallets --- js/controllers/homeWallet.js | 1 + js/controllers/send.js | 4 ++-- views/homeWallet.html | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/js/controllers/homeWallet.js b/js/controllers/homeWallet.js index 548598e73..8fa9cbceb 100644 --- a/js/controllers/homeWallet.js +++ b/js/controllers/homeWallet.js @@ -5,6 +5,7 @@ angular.module('copayApp.controllers').controller('HomeWalletController', functi $rootScope.title = 'Home'; var w = $rootScope.wallet; $scope.isShared = w.isShared(); + $scope.requiresMultipleSignatures = w.requiresMultipleSignatures(); if ($scope.isShared) $scope.copayers = w.getRegisteredPeerIds(); }; diff --git a/js/controllers/send.js b/js/controllers/send.js index 371123545..05c4d6bfc 100644 --- a/js/controllers/send.js +++ b/js/controllers/send.js @@ -14,7 +14,7 @@ angular.module('copayApp.controllers').controller('SendController', preconditions.checkState(w.settings.unitToSatoshi); $scope.isShared = w.isShared(); - $rootScope.title =$scope.isShared ? 'Send Proposal' : 'Send'; + $rootScope.title = w.requiresMultipleSignatures() ? 'Send Proposal' : 'Send'; $scope.loading = false; $scope.error = $scope.success = null; @@ -127,7 +127,7 @@ angular.module('copayApp.controllers').controller('SendController', if (msg.match('expired')) msg = 'The payment request has expired'; - var message = 'The transaction' + ($scope.isShared ? ' proposal' : '') + + var message = 'The transaction' + (w.requiresMultipleSignatures() ? ' proposal' : '') + ' could not be created: ' + msg; $scope.error = message; diff --git a/views/homeWallet.html b/views/homeWallet.html index 12a83451f..88ce1f7fb 100644 --- a/views/homeWallet.html +++ b/views/homeWallet.html @@ -50,7 +50,7 @@ -
+
From 6a55f176afa793760901b8d171d5b5b9a73a5ef7 Mon Sep 17 00:00:00 2001 From: Matias Pando Date: Wed, 31 Dec 2014 10:21:49 -0300 Subject: [PATCH 10/15] Condition for supported browsers is now considering cordova --- js/routes.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/routes.js b/js/routes.js index a0589ef31..7ad3971fe 100644 --- a/js/routes.js +++ b/js/routes.js @@ -11,8 +11,8 @@ if (!ls || ls.length < 1) if (window && window.navigator) { var rxaosp = window.navigator.userAgent.match(/Android.*AppleWebKit\/([\d.]+)/); - var isaosp = (rxaosp && rxaosp[1] < 534); - if (isaosp) + var isaosp = (rxaosp && rxaosp[1] < 537); + if (!window.cordova && isaosp) unsupported = true; } From 8486fe9b69ec9dd9d8828e4c445e589c6a4dac90 Mon Sep 17 00:00:00 2001 From: Matias Pando Date: Thu, 18 Dec 2014 07:13:39 -0300 Subject: [PATCH 11/15] Adding tests to TxPrpposals --- test/TxProposals.js | 95 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 76 insertions(+), 19 deletions(-) diff --git a/test/TxProposals.js b/test/TxProposals.js index 13a2837e5..f01a084ae 100644 --- a/test/TxProposals.js +++ b/test/TxProposals.js @@ -11,6 +11,7 @@ var networks = bitcore.networks; var TxProposal = copay.TxProposal; var TxProposals = copay.TxProposals; +var moment = moment || require('moment'); var dummyProposal = new TxProposal({ creator: 1, @@ -34,41 +35,98 @@ describe('TxProposals', function() { describe('#fromObj', function() { it('should create an instance from an Object', function() { var txps = TxProposals.fromObj({ - networkName:'livenet', + networkName: 'livenet', walletId: '123a12', txps: [], }); should.exist(txps); txps.network.name.should.equal('livenet'); }); - it('should skip Objects with errors', function() { + it('should skip Objects with errors', function() { var txps = TxProposals.fromObj({ - networkName:'livenet', + networkName: 'livenet', walletId: '123a12', - txps: [ { a: 1 }], + txps: [{ + a: 1 + }], }); should.exist(txps); Object.keys(txps.txps).length.should.equal(0); }); }); + describe('#length', function() { + it('should return length', function() { + var txps = new TxProposals(); + txps.txps = { + a: 1, + b: 2 + }; + txps.length().should.equal(2); + }); + }); + describe('#getNtxidsSince', function() { + it('should throw illegal argument', function() { + var txps = new TxProposals(); + txps.txps = { + a: 1, + b: 2 + }; + (function() { + txps.getNtxidsSince() + }).should.throw('Illegal Argument'); + }); + it('should return keys since a date', function() { + var today = moment().toDate(); + var today_plus_1 = moment().add(1, 'day').toDate(); + var today_plus_2 = moment().add(2, 'day').toDate(); + var today_plus_3 = moment().add(3, 'day').toDate(); + + var txps = new TxProposals(); + txps.txps = [{ + id: 1, + createdTs: today + }, { + id: 2, + createdTs: today_plus_1 + }, { + id: 3, + createdTs: today_plus_2 + }]; + + txps.getNtxidsSince(today).length.should.be.equal(3); + txps.getNtxidsSince(today_plus_1).length.should.be.equal(2); + txps.getNtxidsSince(today_plus_2).length.should.be.equal(1); + txps.getNtxidsSince(today_plus_3).length.should.be.equal(0); + + }); + }); describe('#getNtxids', function() { it('should return keys', function() { var txps = new TxProposals(); - txps.txps = {a:1, b:2}; - txps.getNtxids().should.deep.equal(['a','b']); + txps.txps = { + a: 1, + b: 2 + }; + txps.getNtxids().should.deep.equal(['a', 'b']); }); }); describe('#deleteOne', function() { it('should delete specified ntxid', function() { var txps = new TxProposals(); - txps.txps = {a:1, b:2}; + txps.txps = { + a: 1, + b: 2 + }; txps.deleteOne('a'); txps.getNtxids().should.deep.equal(['b']); }); it('should fail on non-existent ntxid', function() { var txps = new TxProposals(); - txps.txps = {a:1, b:2}; - (function () { + txps.txps = { + a: 1, + b: 2 + }; + (function() { txps.deleteOne('c'); }).should.throw('Unknown TXP: c'); }); @@ -76,7 +134,7 @@ describe('TxProposals', function() { describe('#toObj', function() { it('should an object', function() { var txps = TxProposals.fromObj({ - networkName:'livenet', + networkName: 'livenet', walletId: '123a12', txps: [], }); @@ -86,28 +144,28 @@ describe('TxProposals', function() { }); it('should export txps', function() { var txps = TxProposals.fromObj({ - networkName:'livenet', + networkName: 'livenet', walletId: '123a12', txps: [], }); txps.txps = { - 'hola' : dummyProposal, - 'chau' : dummyProposal, + 'hola': dummyProposal, + 'chau': dummyProposal, }; var o = txps.toObj(); o.txps.length.should.equal(2); }); it('should filter sent txp', function() { var txps = TxProposals.fromObj({ - networkName:'livenet', + networkName: 'livenet', walletId: '123a12', txps: [], }); var d = JSON.parse(JSON.stringify(dummyProposal)); - d.sent=1; + d.sent = 1; txps.txps = { - 'hola' : dummyProposal, - 'chau' : d, + 'hola': dummyProposal, + 'chau': d, }; var o = txps.toObj(); o.txps.length.should.equal(1); @@ -117,8 +175,7 @@ describe('TxProposals', function() { it('should merge', function() { var txps = new TxProposals(); var d = dummyProposal; - txps.merge(d.toObj(),{}); + txps.merge(d.toObj(), {}); }); }); }); - From e7240748a20d27001478b961a2e6f0153fa561be Mon Sep 17 00:00:00 2001 From: Matias Pando Date: Thu, 18 Dec 2014 18:23:17 -0300 Subject: [PATCH 12/15] Adding test for Identity --- js/models/Identity.js | 19 ++++---- test/Identity.js | 106 ++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 112 insertions(+), 13 deletions(-) diff --git a/js/models/Identity.js b/js/models/Identity.js index 84868bafd..fb3aafacc 100644 --- a/js/models/Identity.js +++ b/js/models/Identity.js @@ -109,7 +109,7 @@ Identity.create = function(opts, cb) { }); }; -Identity.prototype.resendVerificationEmail = function (cb) { +Identity.prototype.resendVerificationEmail = function(cb) { var self = this; preconditions.checkArgument(_.isFunction(cb)); @@ -152,7 +152,7 @@ Identity.open = function(opts, cb) { }); }; -Identity.prototype.verifyChecksum = function (cb) { +Identity.prototype.verifyChecksum = function(cb) { var self = this; self.storage.getItem(Identity.getKeyForEmail(self.email), function(err, data, headers) { @@ -202,11 +202,9 @@ Identity.prototype.addWallet = function(w) { */ Identity.prototype.deleteWallet = function(walletId, cb) { preconditions.checkArgument(_.isString(walletId)); - var self = this; - - self.verifyChecksum(function (err, match) { + self.verifyChecksum(function(err, match) { if (err) return cb(err); if (!match) return cb('The profile is out of sync. Please re-login to get the latest changes.'); @@ -222,6 +220,7 @@ Identity.prototype.deleteWallet = function(walletId, cb) { self.emitAndKeepAlive('walletDeleted', walletId); self.store(null, cb); }); + }); }; @@ -375,7 +374,7 @@ Identity.prototype.setBackupNeeded = function(backupNeeded) { self.backupNeeded = !!backupNeeded; - self.verifyChecksum(function (err, match) { + self.verifyChecksum(function(err, match) { if (err) return cb(err); if (!match) return cb('The profile is out of sync. Please re-login to get the latest changes.'); @@ -449,8 +448,11 @@ Identity.prototype.remove = function(opts, cb) { }; Identity.prototype._cleanUp = function() { + var self = this; + _.each(this.getWallets(), function(w) { w.close(); + delete self.wallets[w.getId()]; }); }; @@ -458,7 +460,6 @@ Identity.prototype._cleanUp = function() { * @desc Closes the wallet and disconnects all services */ Identity.prototype.close = function() { - this._cleanUp(); this.emitAndKeepAlive('closed'); }; @@ -623,10 +624,10 @@ Identity.prototype.bindWallet = function(w) { */ Identity.prototype.createWallet = function(opts, cb) { preconditions.checkArgument(cb); - + var self = this; - self.verifyChecksum(function (err, match) { + self.verifyChecksum(function(err, match) { if (err) return cb(err); if (!match) return cb('The profile is out of sync. Please re-login to get the latest changes.'); diff --git a/test/Identity.js b/test/Identity.js index 038ca7d34..8a9ee91bf 100644 --- a/test/Identity.js +++ b/test/Identity.js @@ -168,6 +168,17 @@ describe('Identity model', function() { }); }); + + describe('#openWallets', function(done) { + it('should emit noWallets', function() { + var iden = new Identity(getDefaultParams()); + sinon.spy(iden, 'emitAndKeepAlive'); + iden.openWallets(); + iden.emitAndKeepAlive.calledOnce.should.be.true; + iden.emitAndKeepAlive.getCall(0).args[0].should.equal('noWallets'); + }); + }); + describe('#remove', function(done) { it('should remove empty profile', function(done) { var storage = sinon.stub(); @@ -285,6 +296,48 @@ describe('Identity model', function() { done(); }); }); + + it('should return error because the limit has been reached', function(done) { + storage.setItem = sinon.stub().yields('OVERQUOTA'); + var w = { + toObj: sinon.stub().returns({ + key1: 'val1' + }), + getStorageKey: sinon.stub().returns('storage_key'), + getName: sinon.stub().returns('name'), + setVersion: sinon.spy(), + sizes: sinon.stub().returns(99), + getId: sinon.spy(), + }; + iden.storeWallet(w, function(err) { + should.exist(err); + err.should.be.equal('OVERQUOTA'); + done(); + }); + }); + + it('should return error', function(done) { + storage.setItem = sinon.stub().yields('UNKNOWN'); + var w = { + toObj: sinon.stub().returns({ + key1: 'val1' + }), + getStorageKey: sinon.stub().returns('storage_key'), + getName: sinon.stub().returns('name'), + setVersion: sinon.spy(), + sizes: sinon.stub().returns(99), + getId: sinon.spy(), + }; + iden.storeWallet(w, function(err) { + should.exist(err); + err.should.be.equal('UNKNOWN'); + done(); + }); + }); + + + + it('should change wallet version when storing', function(done) { storage.setItem = sinon.stub().yields(null); var w = { @@ -620,7 +673,7 @@ describe('Identity model', function() { }).should.deep.equal(w); }); - + it('should delete wallet', function(done) { iden.addWallet(w); @@ -676,7 +729,7 @@ describe('Identity model', function() { it('should include wallets', function() { iden.addWallet(w); var obj = iden.toObj(); - _.indexOf(obj.walletIds,'32').should.be.above(-1); + _.indexOf(obj.walletIds, '32').should.be.above(-1); }); it('should set version to actual version', function() { @@ -690,8 +743,53 @@ describe('Identity model', function() { iden.addWallet(w); iden.addWallet(w2); var obj = iden.toObj(); - _.indexOf(obj.walletIds,'32').should.be.above(-1); - _.indexOf(obj.walletIds,'33').should.be.above(-1); + _.indexOf(obj.walletIds, '32').should.be.above(-1); + _.indexOf(obj.walletIds, '33').should.be.above(-1); + }); + }); + + + describe('#_cleanUp', function() { + var iden, w, w2; + beforeEach(function() { + var storage = sinon.stub(); + storage.setCredentials = sinon.stub(); + storage.removeItem = sinon.stub().yields(null); + storage.clear = sinon.stub().yields(); + + var opts = { + email: 'test@test.com', + password: '123', + network: { + testnet: { + url: 'https://test-insight.bitpay.com:443' + }, + livenet: { + url: 'https://insight.bitpay.com:443' + }, + }, + storage: storage, + }; + iden = new Identity(opts); + + w = { + getId: sinon.stub().returns('32'), + getName: sinon.stub().returns('treintaydos'), + close: sinon.stub(), + }; + w2 = { + getId: sinon.stub().returns('33'), + getName: sinon.stub().returns('treintaytres'), + close: sinon.stub(), + }; + iden.addWallet(w); + iden.addWallet(w2); + }); + + it('should close all wallets', function() { + _.size(iden.wallets).should.be.equal(2); + iden._cleanUp(); + _.size(iden.wallets).should.be.equal(0); }); }); }); From 4a6e03d51f8f4434a3a49f525feb921283fd1730 Mon Sep 17 00:00:00 2001 From: Matias Pando Date: Thu, 18 Dec 2014 19:34:30 -0300 Subject: [PATCH 13/15] Adding test for Identity --- test/Identity.js | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/test/Identity.js b/test/Identity.js index 8a9ee91bf..77bf41065 100644 --- a/test/Identity.js +++ b/test/Identity.js @@ -791,5 +791,50 @@ describe('Identity model', function() { iden._cleanUp(); _.size(iden.wallets).should.be.equal(0); }); + + }); + + describe('#getLastFocusedWalletId', function() { + var iden, w, w2; + beforeEach(function() { + var storage = sinon.stub(); + storage.setCredentials = sinon.stub(); + storage.removeItem = sinon.stub().yields(null); + storage.clear = sinon.stub().yields(); + + var opts = { + email: 'test@test.com', + password: '123', + network: { + testnet: { + url: 'https://test-insight.bitpay.com:443' + }, + livenet: { + url: 'https://insight.bitpay.com:443' + }, + }, + storage: storage, + }; + iden = new Identity(opts); + + w = { + getId: sinon.stub().returns('32'), + getName: sinon.stub().returns('treintaydos'), + close: sinon.stub(), + }; + w2 = { + getId: sinon.stub().returns('33'), + getName: sinon.stub().returns('treintaytres'), + close: sinon.stub(), + }; + iden.addWallet(w); + iden.addWallet(w2); + }); + + it.only('should return indefined', function() { + var a = iden.getLastFocusedWalletId(); + console.log('a', a); + }); + }); }); From 80770f9a7885b61d4f9a400fe03e55bc880ef54b Mon Sep 17 00:00:00 2001 From: Matias Pando Date: Fri, 19 Dec 2014 18:25:15 -0300 Subject: [PATCH 14/15] More tests on Identity --- test/Identity.js | 175 +++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 163 insertions(+), 12 deletions(-) diff --git a/test/Identity.js b/test/Identity.js index 77bf41065..995e28ac5 100644 --- a/test/Identity.js +++ b/test/Identity.js @@ -673,8 +673,6 @@ describe('Identity model', function() { }).should.deep.equal(w); }); - - it('should delete wallet', function(done) { iden.addWallet(w); iden.getWalletById('32').getName().should.equal('treintaydos'); @@ -725,20 +723,15 @@ describe('Identity model', function() { close: sinon.stub(), }; }); - it('should include wallets', function() { iden.addWallet(w); var obj = iden.toObj(); _.indexOf(obj.walletIds, '32').should.be.above(-1); }); - it('should set version to actual version', function() { var obj = iden.toObj(); obj.version.should.equal(version); }); - - - it('should include 2 wallets', function() { iden.addWallet(w); iden.addWallet(w2); @@ -795,6 +788,13 @@ describe('Identity model', function() { }); describe('#getLastFocusedWalletId', function() { + var clock; + before(function() { + clock = sinon.useFakeTimers(); + }); + after(function() { + clock.restore(); + }); var iden, w, w2; beforeEach(function() { var storage = sinon.stub(); @@ -827,14 +827,165 @@ describe('Identity model', function() { getName: sinon.stub().returns('treintaytres'), close: sinon.stub(), }; - iden.addWallet(w); - iden.addWallet(w2); + }); - it.only('should return indefined', function() { - var a = iden.getLastFocusedWalletId(); - console.log('a', a); + it('should return indefined', function() { + expect(iden.getLastFocusedWalletId()).to.be.undefined; + }); + + it('should return last focused wallet', function() { + iden.addWallet(w); + iden.addWallet(w2); + iden.updateFocusedTimestamp(w.getId()); + + iden.getLastFocusedWalletId().should.be.equal(w.getId()); + + clock.tick(1000); + + iden.updateFocusedTimestamp(w2.getId()); + iden.getLastFocusedWalletId().should.be.equal(w2.getId()); + + iden.deleteWallet(w2.getId(), function() { + iden.getLastFocusedWalletId().should.be.equal(w.getId()); + }); + }); + }); + + describe('importFromFullJson', function() { + var opts; + beforeEach(function() { + var storage = sinon.stub(); + storage.setCredentials = sinon.stub(); + storage.removeItem = sinon.stub().yields(null); + storage.clear = sinon.stub().yields(); + + opts = { + email: 'test@test.com', + password: '123', + network: { + testnet: { + url: 'https://test-insight.bitpay.com:443' + }, + livenet: { + url: 'https://insight.bitpay.com:443' + }, + }, + storage: storage, + }; + }); + it('should throw error because json is wrong', function() { + Identity.importFromFullJson('asdfg', '1', {}, function(c) { + c.should.be.equal('BADSTR: Unable to retrieve json from string'); + }); + }); + it('should throw error because json does not have required fields', function() { + Identity.importFromFullJson('{"age":23}', '1', {}, function(c) { + c.should.be.equal('BADSTR'); + }); + }); + it('should create a profile', function() { + var json = '{"networkOpts":{"livenet":{"url":"https://insight.bitpay.com:443","transports":["polling"]},"testnet":{"url":"https://test-insight.bitpay.com:443","transports":["polling"]}},"blockchainOpts":{"livenet":{"url":"https://insight.bitpay.com:443","transports":["polling"]},"testnet":{"url":"https://test-insight.bitpay.com:443","transports":["polling"]}},"fullName":"l@l","email":"l@l","password":"1","storage":{"type":"DB","storeUrl":"https://insight.bitpay.com:443/api/email","iterations":1000,"salt":"jBbYTj8zTrOt6V","email":"l@l","password":"1","_cachedKey":"y4a352k6sM15gGag+PgQwXRdFjzi0yX6aLEGttWaeP+kbU7JeSPDUfbhhzonnQRUicJu/1IMWgDZbDJjWmrKgA=="},"walletDefaults":{"requiredCopayers":2,"totalCopayers":3,"spendUnconfirmed":true,"reconnectDelay":5000,"idleDurationMin":4,"settings":{"unitName":"bits","unitToSatoshi":100,"unitDecimals":2,"alternativeName":"US Dollar","alternativeIsoCode":"USD"}},"version":"0.8.2","walletIds":["15a3ecd34dfb7000","59220d2110461861","bfd6adad419078d9","893dc0c0a776648b","e8ee7218c6ea7f93"],"wallets":{},"focusedTimestamps":{"15a3ecd34dfb7000":1418916813711,"bfd6adad419078d9":1418835855887,"e8ee7218c6ea7f93":1418775999995,"59220d2110461861":1418835858871,"893dc0c0a776648b":1418835763680},"backupNeeded":true,"_events":{}}'; + Identity.importFromFullJson(json, '1', opts, function(err, iden) { + expect(err).to.be.null; + iden.should.not.be.null; + }); + }); }); + + describe('#closeWallet', function() { + var iden, w, w2, w3; + beforeEach(function() { + var storage = sinon.stub(); + storage.setCredentials = sinon.stub(); + storage.removeItem = sinon.stub().yields(null); + storage.clear = sinon.stub().yields(); + + var opts = { + email: 'test@test.com', + password: '123', + network: { + testnet: { + url: 'https://test-insight.bitpay.com:443' + }, + livenet: { + url: 'https://insight.bitpay.com:443' + }, + }, + storage: storage, + }; + iden = new Identity(opts); + + w = { + getId: sinon.stub().returns('32'), + getName: sinon.stub().returns('treintaydos'), + close: sinon.stub(), + }; + w2 = { + getId: sinon.stub().returns('33'), + getName: sinon.stub().returns('treintaytres'), + close: sinon.stub(), + }; + + w3 = { + getId: sinon.stub().returns('34'), + getName: sinon.stub().returns('treintaycuatro'), + close: sinon.stub(), + }; + + iden.addWallet(w); + iden.addWallet(w2); + //do not add w3 + }); + + it('should close a Wallet', function() { + iden.closeWallet(w, function(err) { + expect(err).to.be.null; + }); + + iden.closeWallet(w3, function(err) { + expect(err).to.be.not.null; + }); + }); + + }); + + + + describe('#_checkVersion', function() { + var iden; + beforeEach(function() { + var storage = sinon.stub(); + storage.setCredentials = sinon.stub(); + storage.removeItem = sinon.stub().yields(null); + storage.clear = sinon.stub().yields(); + + var opts = { + email: 'test@test.com', + password: '123', + network: { + testnet: { + url: 'https://test-insight.bitpay.com:443' + }, + livenet: { + url: 'https://insight.bitpay.com:443' + }, + }, + storage: storage, + }; + iden = new Identity(opts); + }); + + it('should checkVersion', function() { + + expect(iden._checkVersion()).to.be.undefined; + expect(iden._checkVersion('0.0.0')).to.be.undefined; + (function() { + console.log('b', iden._checkVersion('9.9.9')); + }).should.throw('Major difference'); + }); + }); + }); From cc701d648df8f5613c3efa54bd06c0a9ceabd6cd Mon Sep 17 00:00:00 2001 From: Matias Pando Date: Wed, 31 Dec 2014 10:35:33 -0300 Subject: [PATCH 15/15] Rebased --- test/Identity.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/Identity.js b/test/Identity.js index 995e28ac5..a2a7c066e 100644 --- a/test/Identity.js +++ b/test/Identity.js @@ -406,7 +406,7 @@ describe('Identity model', function() { walletClass: walletClass, }, function(err, w1) { should.exist(w1); - + args.storage.getItem = sinon.stub().yields(null, JSON.stringify(iden)); iden.createWallet({ walletClass: walletClass, @@ -801,6 +801,7 @@ describe('Identity model', function() { storage.setCredentials = sinon.stub(); storage.removeItem = sinon.stub().yields(null); storage.clear = sinon.stub().yields(); + storage.getItem = sinon.stub(); var opts = { email: 'test@test.com',