diff --git a/lib/server.js b/lib/server.js index 0b43c3c..ee5b583 100644 --- a/lib/server.js +++ b/lib/server.js @@ -119,8 +119,8 @@ WalletService.prototype.createWallet = function(opts, cb) { try { pubKey = new PublicKey.fromString(opts.pubKey); - } catch (e) { - return cb(e.toString()); + } catch (ex) { + return cb(new ClientError('Invalid public key')); }; var wallet = Wallet.create({ @@ -512,6 +512,9 @@ WalletService.prototype.createTx = function(opts, cb) { if (toAddress.network != wallet.getNetworkName()) return cb(new ClientError('INVALIDADDRESS', 'Incorrect address network')); + if (opts.amount <= 0) + return cb(new ClientError('Invalid amount')); + if (opts.amount < Bitcore.Transaction.DUST_AMOUNT) return cb(new ClientError('DUSTAMOUNT', 'Amount below dust threshold')); @@ -538,8 +541,7 @@ WalletService.prototype.createTx = function(opts, cb) { try { self._selectUtxos(txp, utxos); } catch (ex) { - console.log('[server.js.523:ex:]', ex); //TODO - return cb(new ClientError(ex)); + return cb(new ClientError(ex.toString())); } if (!txp.inputs) diff --git a/test/integration/server.js b/test/integration/server.js index 4c55b6a..990704d 100644 --- a/test/integration/server.js +++ b/test/integration/server.js @@ -302,7 +302,7 @@ describe('Copay server', function() { }; server.createWallet(opts, function(err, walletId) { should.not.exist(walletId); - err.should.exist; + should.exist(err); err.message.should.contain('name'); done(); }); @@ -346,7 +346,20 @@ describe('Copay server', function() { }); }); - it.skip('should fail to create wallet with invalid pubKey argument', function(done) {}); + it('should fail to create wallet with invalid pubKey argument', function(done) { + var opts = { + name: 'my wallet', + m: 2, + n: 3, + pubKey: 'dummy', + }; + server.createWallet(opts, function(err, walletId) { + should.not.exist(walletId); + should.exist(err); + err.message.should.contain('Invalid public key'); + done(); + }); + }); }); describe('#joinWallet', function() { @@ -399,7 +412,7 @@ describe('Copay server', function() { }; server.joinWallet(copayerOpts, function(err, result) { should.not.exist(result); - err.should.exist; + should.exist(err); err.message.should.contain('name'); done(); }); @@ -453,7 +466,39 @@ describe('Copay server', function() { }); }); - it.skip('should fail two wallets with same xPubKey', function(done) {}); + it('should fail two wallets with same xPubKey', function(done) { + var copayerOpts = { + walletId: walletId, + name: 'me', + xPubKey: TestData.copayers[0].xPubKey, + xPubKeySignature: TestData.copayers[0].xPubKeySignature, + }; + server.joinWallet(copayerOpts, function(err) { + should.not.exist(err); + + var walletOpts = { + name: 'my other wallet', + m: 1, + n: 1, + pubKey: TestData.keyPair.pub, + }; + server.createWallet(walletOpts, function(err, walletId) { + should.not.exist(err); + copayerOpts = { + walletId: walletId, + name: 'me', + xPubKey: TestData.copayers[0].xPubKey, + xPubKeySignature: TestData.copayers[0].xPubKeySignature, + }; + server.joinWallet(copayerOpts, function(err) { + should.exist(err); + err.code.should.equal('CREGISTERED'); + err.message.should.equal('Copayer ID already registered on server'); + done(); + }); + }); + }); + }); it('should fail to join with bad formated signature', function(done) { var copayerOpts = { @@ -475,7 +520,7 @@ describe('Copay server', function() { xPubKey: TestData.copayers[0].xPubKey[0], }; server.joinWallet(copayerOpts, function(err) { - err.should.exist; + should.exist(err); err.message.should.contain('argument missing'); done(); }); @@ -583,7 +628,7 @@ describe('Copay server', function() { it('should not create address if unable to store it', function(done) { sinon.stub(server.storage, 'storeAddressAndWallet').yields('dummy error'); server.createAddress({}, function(err, address) { - err.should.exist; + should.exist(err); should.not.exist(address); server.getMainAddresses({}, function(err, addresses) { @@ -625,7 +670,7 @@ describe('Copay server', function() { helpers.getAuthServer(result.copayerId, function(server) { server.createAddress({}, function(err, address) { should.not.exist(address); - err.should.exist; + should.exist(err); err.message.should.contain('not complete'); done(); }); @@ -656,7 +701,7 @@ describe('Copay server', function() { var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 80, null, TestData.copayers[0].privKey); server.createTx(txOpts, function(err, tx) { should.not.exist(tx); - err.should.exist; + should.exist(err); err.message.should.contain('not complete'); done(); }); @@ -714,7 +759,7 @@ describe('Copay server', function() { server.createTx(txOpts, function(err, tx) { should.not.exist(tx); - err.should.exist; + should.exist(err); err.message.should.equal('Invalid proposal signature'); done(); }); @@ -727,7 +772,7 @@ describe('Copay server', function() { server.createTx(txOpts, function(err, tx) { should.not.exist(tx); - err.should.exist; + should.exist(err); err.message.should.equal('Invalid proposal signature'); done(); }); @@ -740,7 +785,7 @@ describe('Copay server', function() { server.createTx(txOpts, function(err, tx) { should.not.exist(tx); - err.should.exist; + should.exist(err); err.code.should.equal('INVALIDADDRESS'); err.message.should.equal('Invalid address'); done(); @@ -762,7 +807,15 @@ describe('Copay server', function() { }); }); - it.skip('should fail to create tx for invalid amount', function(done) {}); + it('should fail to create tx for invalid amount', function(done) { + var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 0, null, TestData.copayers[0].privKey); + server.createTx(txOpts, function(err, tx) { + should.not.exist(tx); + should.exist(err); + err.message.should.equal('Invalid amount'); + done(); + }); + }); it('should fail to create tx when insufficient funds', function(done) { helpers.stubUtxos(server, wallet, [100], function() { @@ -809,7 +862,22 @@ describe('Copay server', function() { }); }); - it.skip('should fail gracefully when bitcore throws exception on raw tx creation', function(done) {}); + it('should fail gracefully when bitcore throws exception on raw tx creation', function(done) { + helpers.stubUtxos(server, wallet, [10], function() { + var bitcoreStub = sinon.stub(Bitcore, 'Transaction'); + bitcoreStub.throws({ + name: 'dummy', + message: 'dummy exception' + }); + var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 2, null, TestData.copayers[0].privKey); + server.createTx(txOpts, function(err, tx) { + should.exist(err); + err.message.should.equal('dummy exception'); + bitcoreStub.restore(); + done(); + }); + }); + }); it('should create tx when there is a pending tx and enough UTXOs', function(done) { helpers.stubUtxos(server, wallet, [10.1, 10.2, 10.3], function() { @@ -897,7 +965,7 @@ describe('Copay server', function() { var server, wallet, txid; beforeEach(function(done) { - helpers.createAndJoinWallet(2, 3, function(s, w) { + helpers.createAndJoinWallet(2, 2, function(s, w) { server = s; wallet = w; helpers.stubUtxos(server, wallet, _.range(1, 9), function() { @@ -924,22 +992,63 @@ describe('Copay server', function() { should.not.exist(err); server.getPendingTxs({}, function(err, txs) { should.not.exist(err); - var tx = txs[0]; - tx.id.should.equal(txid); - - var actors = tx.getActors(); - actors.length.should.equal(1); - actors[0].should.equal(wallet.copayers[0].id); - var action = tx.getActionBy(wallet.copayers[0].id); - action.type.should.equal('reject'); - action.comment.should.equal('some reason'); - done(); + txs.should.be.empty; + server.getTx({ + id: txid + }, function(err, tx) { + var actors = tx.getActors(); + actors.length.should.equal(1); + actors[0].should.equal(wallet.copayers[0].id); + var action = tx.getActionBy(wallet.copayers[0].id); + action.type.should.equal('reject'); + action.comment.should.equal('some reason'); + done(); + }); }); }); }); }); - it.skip('should fail to reject non-pending TX', function(done) {}); + it('should fail to reject non-pending TX', function(done) { + async.waterfall([ + + function(next) { + server.getPendingTxs({}, function(err, txs) { + var tx = txs[0]; + tx.id.should.equal(txid); + next(); + }); + }, + function(next) { + server.rejectTx({ + txProposalId: txid, + reason: 'some reason', + }, function(err) { + should.not.exist(err); + next(); + }); + }, + function(next) { + server.getPendingTxs({}, function(err, txs) { + should.not.exist(err); + txs.should.be.empty; + next(); + }); + }, + function(next) { + helpers.getAuthServer(wallet.copayers[1].id, function(server) { + server.rejectTx({ + txProposalId: txid, + reason: 'some other reason', + }, function(err) { + should.exist(err); + err.code.should.equal('TXNOTPENDING'); + done(); + }); + }); + }, + ]); + }); }); describe('#signTx', function() { @@ -1095,6 +1204,57 @@ describe('Copay server', function() { }); }); }); + + it('should fail to sign a non-pending TX', function(done) { + async.waterfall([ + + function(next) { + server.rejectTx({ + txProposalId: txid, + reason: 'some reason', + }, function(err) { + should.not.exist(err); + next(); + }); + }, + function(next) { + helpers.getAuthServer(wallet.copayers[1].id, function(server) { + server.rejectTx({ + txProposalId: txid, + reason: 'some reason', + }, function(err) { + should.not.exist(err); + next(); + }); + }); + }, + function(next) { + server.getPendingTxs({}, function(err, txs) { + should.not.exist(err); + txs.should.be.empty; + next(); + }); + }, + function(next) { + helpers.getAuthServer(wallet.copayers[2].id, function(server) { + server.getTx({ + id: txid + }, function(err, tx) { + should.not.exist(err); + var signatures = helpers.clientSign(tx, TestData.copayers[2].xPrivKey); + server.signTx({ + txProposalId: txid, + signatures: signatures, + }, function(err) { + should.exist(err); + err.code.should.equal('TXNOTPENDING'); + done(); + }); + }); + }); + }, + ]); + }); }); describe.skip('#broadcastTx', function() { @@ -1644,9 +1804,10 @@ describe('Copay server', function() { it('should allow creator to remove an signed TX by himself', function(done) { var signatures = helpers.clientSign(txp, TestData.copayers[0].xPrivKey); server.signTx({ - txProposalId: txp[0], + txProposalId: txp.id, signatures: signatures, }, function(err) { + should.not.exist(err); server.removePendingTx({ txProposalId: txp.id }, function(err) { @@ -1659,7 +1820,57 @@ describe('Copay server', function() { }); }); - it.skip('should fail to remove non-pending TX', function(done) {}); + it('should fail to remove non-pending TX', function(done) { + async.waterfall([ + + function(next) { + var signatures = helpers.clientSign(txp, TestData.copayers[0].xPrivKey); + server.signTx({ + txProposalId: txp.id, + signatures: signatures, + }, function(err) { + should.not.exist(err); + next(); + }); + }, + function(next) { + helpers.getAuthServer(wallet.copayers[1].id, function(server) { + server.rejectTx({ + txProposalId: txp.id, + }, function(err) { + should.not.exist(err); + next(); + }); + }); + }, + function(next) { + helpers.getAuthServer(wallet.copayers[2].id, function(server) { + server.rejectTx({ + txProposalId: txp.id, + }, function(err) { + should.not.exist(err); + next(); + }); + }); + }, + function(next) { + server.getPendingTxs({}, function(err, txs) { + should.not.exist(err); + txs.should.be.empty; + next(); + }); + }, + function(next) { + server.removePendingTx({ + txProposalId: txp.id + }, function(err) { + should.exist(err); + err.code.should.equal('TXNOTPENDING'); + done(); + }); + }, + ]); + }); it('should not allow non-creator copayer to remove an unsigned TX ', function(done) { helpers.getAuthServer(wallet.copayers[1].id, function(server2) {