Browse Source

Merge pull request #80 from isocolsky/coverage

Increase coverage
activeAddress
Matias Alejo Garcia 10 years ago
parent
commit
35c48066b3
  1. 11
      lib/expressapp.js
  2. 4
      lib/model/txproposal.js
  3. 32
      lib/server.js
  4. 42
      lib/storage.js
  5. 88
      test/integration/clientApi.js
  6. 422
      test/integration/server.js
  7. 2
      test/models/addressmanager.js
  8. 2
      test/models/txproposal.js
  9. 2
      test/models/wallet.js
  10. 51
      test/utils.js

11
lib/expressapp.js

@ -64,7 +64,7 @@ ExpressApp.start = function(opts) {
var status = (err.code == 'NOTAUTHORIZED') ? 401 : 400;
if (!opts.disableLogs)
log.info('Client Err: ' + status + ' ' + req.url + ' ' + err);
log.info('Client Err: ' + status + ' ' + req.url + ' ' + err);
res.status(status).json({
code: err.code,
@ -273,15 +273,6 @@ ExpressApp.start = function(opts) {
});
});
// TODO: DEBUG only!
router.get('/v1/dump', function(req, res) {
var server = WalletService.getInstance();
server.storage._dump(function() {
res.end();
});
});
app.use(opts.basePath || '/copay/api', router);
return app;
};

4
lib/model/txproposal.js

@ -52,6 +52,7 @@ TxProposal.fromObj = function(obj) {
x.requiredRejections = obj.requiredRejections;
x.status = obj.status;
x.txid = obj.txid;
x.broadcastedOn = obj.broadcastedOn;
x.inputPaths = obj.inputPaths;
x.actions = _.map(obj.actions, function(action) {
return TxProposalAction.fromObj(action);
@ -172,7 +173,7 @@ TxProposal.prototype._addSignaturesToBitcoreTx = function(t, signatures, xpub) {
sigtype: Bitcore.crypto.Signature.SIGHASH_ALL,
publicKey: pub,
};
t.inputs[i].addSignature(t,s);
t.inputs[i].addSignature(t, s);
i++;
} catch (e) {};
});
@ -220,6 +221,7 @@ TxProposal.prototype.isBroadcasted = function() {
TxProposal.prototype.setBroadcasted = function(txid) {
this.txid = txid;
this.status = 'broadcasted';
this.broadcastedOn = Math.floor(Date.now() / 1000);
};
module.exports = TxProposal;

32
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)
@ -567,13 +569,13 @@ WalletService.prototype.createTx = function(opts, cb) {
/**
* Retrieves a tx from storage.
* @param {Object} opts
* @param {string} opts.id - The tx id.
* @param {string} opts.txProposalId - The tx id.
* @returns {Object} txProposal
*/
WalletService.prototype.getTx = function(opts, cb) {
var self = this;
self.storage.fetchTx(self.walletId, opts.id, function(err, txp) {
self.storage.fetchTx(self.walletId, opts.txProposalId, function(err, txp) {
if (err) return cb(err);
if (!txp) return cb(new ClientError('Transaction proposal not found'));
return cb(null, txp);
@ -612,12 +614,12 @@ WalletService.prototype.removePendingTx = function(opts, cb) {
Utils.runLocked(self.walletId, cb, function(cb) {
self.getTx({
id: opts.txProposalId,
txProposalId: opts.txProposalId,
}, function(err, txp) {
if (err) return cb(err);
if (!txp.isPending())
return cb(new ClientError('Transaction proposal not pending'));
return cb(new ClientError('TXNOTPENDING', 'Transaction proposal not pending'));
if (txp.creatorId !== self.copayerId)
@ -626,7 +628,7 @@ WalletService.prototype.removePendingTx = function(opts, cb) {
var actors = txp.getActors();
if (actors.length > 1 || (actors.length == 1 && actors[0] !== self.copayerId))
return cb(new ClientError('Cannot remove a proposal signed/rejected by other copayers'));
return cb(new ClientError('TXACTIONED', 'Cannot remove a proposal signed/rejected by other copayers'));
self._notify('transactionProposalRemoved');
self.storage.removeTx(self.walletId, txp.id, cb);
@ -664,7 +666,7 @@ WalletService.prototype.signTx = function(opts, cb) {
if (err) return cb(err);
self.getTx({
id: opts.txProposalId
txProposalId: opts.txProposalId
}, function(err, txp) {
if (err) return cb(err);
@ -673,7 +675,7 @@ WalletService.prototype.signTx = function(opts, cb) {
});
if (action)
return cb(new ClientError('CVOTED', 'Copayer already voted on this transaction proposal'));
if (txp.status != 'pending')
if (!txp.isPending())
return cb(new ClientError('TXNOTPENDING', 'The transaction proposal is not pending'));
var copayer = wallet.getCopayer(self.copayerId);
@ -689,9 +691,7 @@ WalletService.prototype.signTx = function(opts, cb) {
copayerId: self.copayerId,
});
// TODO: replace with .isAccepted()
if (txp.status == 'accepted') {
if (txp.isAccepted()) {
self._notify('TxProposalFinallyAccepted', {
txProposalId: opts.txProposalId,
});
@ -719,7 +719,7 @@ WalletService.prototype.broadcastTx = function(opts, cb) {
if (err) return cb(err);
self.getTx({
id: opts.txProposalId
txProposalId: opts.txProposalId
}, function(err, txp) {
if (err) return cb(err);
@ -761,7 +761,7 @@ WalletService.prototype.rejectTx = function(opts, cb) {
return cb(new ClientError('Required argument missing'));
self.getTx({
id: opts.txProposalId
txProposalId: opts.txProposalId
}, function(err, txp) {
if (err) return cb(err);

42
lib/storage.js

@ -22,7 +22,7 @@ var Storage = function(opts) {
};
var zeroPad = function(x, length) {
return (Array(length).join('0') + parseInt(x)).slice(-length);
return _.padLeft(parseInt(x), length, '0');
};
var walletPrefix = function(id) {
@ -33,10 +33,7 @@ var opKey = function(key) {
return key ? '!' + key : '';
};
var MAX_TS = Array(14).join('9');
var opKeyTs = function(key) {
return key ? '!' + zeroPad(key, 14) : '';
};
var MAX_TS = _.repeat('9', 14);
var KEY = {
@ -106,16 +103,6 @@ Storage.prototype.fetchCopayerLookup = function(copayerId, cb) {
});
};
Storage.prototype.fetchNotification = function(walletId, notificationId, cb) {
this.db.get(KEY.NOTIFICATION(walletId, notificationId), function(err, data) {
if (err) {
if (err.notFound) return cb();
return cb(err);
}
return cb(null, Notification.fromObj(data));
});
};
Storage.prototype._completeTxData = function(walletId, txs, cb) {
var txList = [].concat(txs);
this.fetchWallet(walletId, function(err, wallet) {
@ -293,8 +280,6 @@ Storage.prototype._delByKey = function(key, cb) {
})
.on('error', function(err) {
if (err.notFound) return cb();
console.log('[storage.js.252]'); //TODO
return cb(err);
})
.on('end', function(err) {
@ -309,15 +294,6 @@ Storage.prototype._delByKey = function(key, cb) {
});
};
Storage.prototype.removeAllPendingTxs = function(walletId, cb) {
this._delByKey(KEY.PENDING_TXP(walletId), cb);
};
Storage.prototype.removeAllTxs = function(walletId, cb) {
this._delByKey(KEY.TXP(walletId), cb);
};
Storage.prototype._removeCopayers = function(walletId, cb) {
var self = this;
@ -333,15 +309,6 @@ Storage.prototype._removeCopayers = function(walletId, cb) {
});
};
Storage.prototype._removeAllNotifications = function(walletId, cb) {
this._delByKey(KEY.NOTIFICATION(walletId), cb);
};
Storage.prototype._removeAllAddresses = function(walletId, cb) {
this._delByKey(KEY.ADDRESS(walletId), cb);
};
Storage.prototype.removeWallet = function(walletId, cb) {
var self = this;
@ -390,11 +357,6 @@ Storage.prototype.storeAddressAndWallet = function(wallet, address, cb) {
this.db.batch(ops, cb);
};
Storage.prototype.removeAddress = function(walletId, address, cb) {
this.db.del(KEY.ADDRESS(walletId, address.address), cb);
};
Storage.prototype._dump = function(cb, fn) {
fn = fn || console.log;

88
test/integration/clientApi.js

@ -14,6 +14,7 @@ var Bitcore = require('bitcore');
var WalletUtils = require('../../lib/walletutils');
var ExpressApp = require('../../lib/expressapp');
var Storage = require('../../lib/storage');
var TestData = require('../testdata');
var helpers = {};
@ -86,10 +87,6 @@ fsmock._set = function(name, data) {
var blockExplorerMock = {};
blockExplorerMock.utxos = [];
blockExplorerMock.getUnspentUtxos = function(dummy, cb) {
var ret = _.map(blockExplorerMock.utxos || [], function(x) {
@ -112,16 +109,26 @@ blockExplorerMock.setUtxo = function(address, amount, m) {
});
};
blockExplorerMock.broadcast = function(raw, cb) {
blockExplorerMock.lastBroadcasted = raw;
return cb(null, (new Bitcore.Transaction(raw)).id);
};
blockExplorerMock.setHistory = function(txs) {
blockExplorerMock.txHistory = txs;
};
blockExplorerMock.getTransactions = function(addresses, cb) {
return cb(null, blockExplorerMock.txHistory || []);
};
blockExplorerMock.reset = function() {
blockExplorerMock.utxos = [];
blockExplorerMock.txHistory = [];
};
describe('client API ', function() {
var clients, app;
@ -217,10 +224,8 @@ describe('client API ', function() {
done();
});
});
});
describe('Storage Encryption', function() {
beforeEach(function() {
_.each(_.range(3), function(i) {
@ -326,7 +331,6 @@ describe('client API ', function() {
it.skip('should not ask for password if not needed (readwrite)', function(done) {});
});
describe('Wallet Creation', function() {
it('should check balance in a 1-1 ', function(done) {
helpers.createAndJoinWallet(clients, 1, 1, function(err) {
@ -563,6 +567,7 @@ describe('client API ', function() {
});
});
});
describe('Air gapped related flows', function() {
it('should be able get Tx proposals from a file', function(done) {
helpers.createAndJoinWallet(clients, 1, 2, function(err, w) {
@ -823,10 +828,8 @@ describe('client API ', function() {
});
});
});
});
describe('Wallet Backups and Mobility', function() {
it('round trip #import #export', function(done) {
@ -869,9 +872,8 @@ describe('client API ', function() {
});
});
describe('Transaction Proposals Creation and Locked funds', function() {
it('Should lock and release funds', function(done) {
it('Should lock and release funds through rejection', function(done) {
helpers.createAndJoinWallet(clients, 2, 2, function(err, w) {
clients[0].createAddress(function(err, x0) {
should.not.exist(err);
@ -902,6 +904,37 @@ describe('client API ', function() {
});
});
});
it('Should lock and release funds through removal', function(done) {
helpers.createAndJoinWallet(clients, 2, 2, function(err, w) {
clients[0].createAddress(function(err, x0) {
should.not.exist(err);
should.exist(x0.address);
blockExplorerMock.setUtxo(x0, 1, 2);
blockExplorerMock.setUtxo(x0, 1, 2);
var opts = {
amount: 120000000,
toAddress: 'n2TBMPzPECGUfcT2EByiTJ12TPZkhN2mN5',
message: 'hello 1-1',
};
clients[0].sendTxProposal(opts, function(err, x) {
should.not.exist(err);
clients[0].sendTxProposal(opts, function(err, y) {
err.code.should.contain('INSUFFICIENTFUNDS');
clients[0].removeTxProposal(x, function(err) {
should.not.exist(err);
clients[0].sendTxProposal(opts, function(err, x) {
should.not.exist(err);
done();
});
});
});
});
});
});
});
it('Should keep message and refusal texts', function(done) {
helpers.createAndJoinWallet(clients, 2, 3, function(err, w) {
clients[0].createAddress(function(err, x0) {
@ -1315,4 +1348,35 @@ describe('client API ', function() {
});
});
});
describe('Transaction history', function() {
it('should get transaction history', function(done) {
blockExplorerMock.setHistory(TestData.history);
helpers.createAndJoinWallet(clients, 1, 1, function(err, w) {
clients[0].createAddress(function(err, x0) {
should.not.exist(err);
should.exist(x0.address);
clients[0].getTxHistory({}, function(err, txs) {
should.not.exist(err);
should.exist(txs);
txs.length.should.equal(2);
done();
});
});
});
});
it('should get empty transaction history when there are no addresses', function(done) {
blockExplorerMock.setHistory(TestData.history);
helpers.createAndJoinWallet(clients, 1, 1, function(err, w) {
clients[0].getTxHistory({}, function(err, txs) {
should.not.exist(err);
should.exist(txs);
txs.length.should.equal(0);
done();
});
});
});
it.skip('should get transaction history decorated with proposal', function(done) {});
it.skip('should get paginated transaction history', function(done) {});
});
});

422
test/integration/server.js

@ -228,12 +228,11 @@ describe('Copay server', function() {
.privateKey
.toString();
var message = 'hola';
var sig = WalletUtils.signMessage(message, priv);
var sig = WalletUtils.signMessage('hello world', priv);
WalletService.getInstanceWithAuth({
copayerId: wallet.copayers[0].id,
message: message,
message: 'hello world',
signature: sig,
}, function(err, server) {
should.not.exist(err);
@ -302,7 +301,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();
});
@ -345,6 +344,21 @@ describe('Copay server', 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() {
@ -397,7 +411,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();
});
@ -451,6 +465,40 @@ describe('Copay server', function() {
});
});
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 = {
walletId: walletId,
@ -471,7 +519,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();
});
@ -579,7 +627,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) {
@ -621,7 +669,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();
});
@ -652,7 +700,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();
});
@ -710,7 +758,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();
});
@ -723,7 +771,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();
});
@ -736,7 +784,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();
@ -758,6 +806,16 @@ describe('Copay server', function() {
});
});
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() {
var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 120, null, TestData.copayers[0].privKey);
@ -803,6 +861,23 @@ describe('Copay server', function() {
});
});
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() {
var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 12, null, TestData.copayers[0].privKey);
@ -889,7 +964,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() {
@ -916,20 +991,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({
txProposalId: 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('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() {
@ -1085,8 +1203,160 @@ 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({
txProposalId: 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('#broadcastTx', function() {
var server, wallet, txpid;
beforeEach(function(done) {
helpers.createAndJoinWallet(1, 1, function(s, w) {
server = s;
wallet = w;
helpers.stubUtxos(server, wallet, [10, 10], function() {
var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 9, 'some message', TestData.copayers[0].privKey);
server.createTx(txOpts, function(err, txp) {
should.not.exist(err);
should.exist(txp);
var signatures = helpers.clientSign(txp, TestData.copayers[0].xPrivKey);
server.signTx({
txProposalId: txp.id,
signatures: signatures,
}, function(err, txp) {
should.not.exist(err);
should.exist(txp);
txp.isAccepted().should.be.true;
txp.isBroadcasted().should.be.false;
txpid = txp.id;
done();
});
});
});
});
});
it('should brodcast a tx', function(done) {
var clock = sinon.useFakeTimers(1234000);
helpers.stubBroadcast('999');
server.broadcastTx({
txProposalId: txpid
}, function(err) {
should.not.exist(err);
server.getTx({
txProposalId: txpid
}, function(err, txp) {
should.not.exist(err);
txp.txid.should.equal('999');
txp.isBroadcasted().should.be.true;
txp.broadcastedOn.should.equal(1234);
clock.restore();
done();
});
});
});
it('should fail to brodcast an already broadcasted tx', function(done) {
helpers.stubBroadcast('999');
server.broadcastTx({
txProposalId: txpid
}, function(err) {
should.not.exist(err);
server.broadcastTx({
txProposalId: txpid
}, function(err) {
should.exist(err);
err.code.should.equal('TXALREADYBROADCASTED');
done();
});
});
});
it('should fail to brodcast a not yet accepted tx', function(done) {
helpers.stubBroadcast('999');
var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 9, 'some other message', TestData.copayers[0].privKey);
server.createTx(txOpts, function(err, txp) {
should.not.exist(err);
should.exist(txp);
server.broadcastTx({
txProposalId: txp.id
}, function(err) {
should.exist(err);
err.code.should.equal('TXNOTACCEPTED');
done();
});
});
});
it('should keep tx as accepted if unable to broadcast it', function(done) {
helpers.stubBroadcastFail();
server.broadcastTx({
txProposalId: txpid
}, function(err) {
should.exist(err);
server.getTx({
txProposalId: txpid
}, function(err, txp) {
should.not.exist(err);
should.not.exist(txp.txid);
txp.isBroadcasted().should.be.false;
should.not.exist(txp.broadcastedOn);
txp.isAccepted().should.be.true;
done();
});
});
});
});
describe('Tx proposal workflow', function() {
var server, wallet;
beforeEach(function(done) {
@ -1104,7 +1374,7 @@ describe('Copay server', function() {
var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 10, 'some message', TestData.copayers[0].privKey);
server.createTx(txOpts, function(err, txp) {
should.not.exist(err);
should.exist.txp;
should.exist(txp);
helpers.getAuthServer(wallet.copayers[1].id, function(server2, wallet) {
server2.getPendingTxs({}, function(err, txps) {
should.not.exist(err);
@ -1126,7 +1396,7 @@ describe('Copay server', function() {
server.createTx(txOpts, function(err, txp) {
txpId = txp.id;
should.not.exist(err);
should.exist.txp;
should.exist(txp);
next();
});
},
@ -1212,7 +1482,7 @@ describe('Copay server', function() {
server.createTx(txOpts, function(err, txp) {
txpId = txp.id;
should.not.exist(err);
should.exist.txp;
should.exist(txp);
next();
});
},
@ -1269,7 +1539,7 @@ describe('Copay server', function() {
},
function(next) {
server.getTx({
id: txpId
txProposalId: txpId
}, function(err, txp) {
should.not.exist(err);
txp.isPending().should.be.false;
@ -1283,6 +1553,48 @@ describe('Copay server', function() {
});
});
describe('#getTx', function() {
var server, wallet, txpid;
beforeEach(function(done) {
helpers.createAndJoinWallet(2, 3, function(s, w) {
server = s;
wallet = w;
helpers.stubUtxos(server, wallet, 10, function() {
var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 9, 'some message', TestData.copayers[0].privKey);
server.createTx(txOpts, function(err, txp) {
should.not.exist(err);
should.exist(txp);
txpid = txp.id;
done();
});
});
});
});
it('should get own transaction proposal', function(done) {
server.getTx({
txProposalId: txpid
}, function(err, txp) {
should.not.exist(err);
should.exist(txp);
txp.id.should.equal(txpid);
done();
});
});
it.skip('should get someone elses transaction proposal', function(done) {});
it('should fail to get non-existent transaction proposal', function(done) {
server.getTx({
txProposalId: 'dummy'
}, function(err, txp) {
should.exist(err);
should.not.exist(txp);
err.message.should.contain('not found');
done();
});
});
it.skip('should get accepted/rejected transaction proposal', function(done) {});
it.skip('should get broadcasted transaction proposal', function(done) {});
});
describe('#getTxs', function() {
var server, wallet, clock;
@ -1623,9 +1935,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) {
@ -1638,6 +1951,58 @@ describe('Copay server', function() {
});
});
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) {
server2.removePendingTx({
@ -1663,6 +2028,7 @@ describe('Copay server', function() {
server.removePendingTx({
txProposalId: txp.id
}, function(err) {
err.code.should.equal('TXACTIONED');
err.message.should.contain('other copayers');
done();
});

2
test/addressmanager.js → test/models/addressmanager.js

@ -4,7 +4,7 @@ var _ = require('lodash');
var chai = require('chai');
var sinon = require('sinon');
var should = chai.should();
var AddressManager = require('../lib/model/addressmanager');
var AddressManager = require('../../lib/model/addressmanager');
describe('AddressManager', function() {

2
test/txproposal.js → test/models/txproposal.js

@ -4,7 +4,7 @@ var _ = require('lodash');
var chai = require('chai');
var sinon = require('sinon');
var should = chai.should();
var TXP = require('../lib/model/txproposal');
var TXP = require('../../lib/model/txproposal');
var Bitcore = require('bitcore');

2
test/wallet.js → test/models/wallet.js

@ -4,7 +4,7 @@ var _ = require('lodash');
var chai = require('chai');
var sinon = require('sinon');
var should = chai.should();
var Wallet = require('../lib/model/wallet');
var Wallet = require('../../lib/model/wallet');
describe('Wallet', function() {

51
test/utils.js

@ -0,0 +1,51 @@
'use strict';
var _ = require('lodash');
var chai = require('chai');
var sinon = require('sinon');
var should = chai.should();
var Utils = require('../lib/utils');
describe('Utils', function() {
describe('#checkRequired', function() {
it('should check required fields', function() {
var obj = {
id: 'id',
name: 'name',
array: ['a', 'b'],
};
var fixtures = [{
args: 'id',
check: true
}, {
args: ['id'],
check: true
}, {
args: ['id, name'],
check: false
}, {
args: ['id', 'name'],
check: true
}, {
args: 'array',
check: true
}, {
args: 'dummy',
check: false
}, {
args: ['dummy1', 'dummy2'],
check: false
}, {
args: ['id', 'dummy'],
check: false
}, ];
_.each(fixtures, function(f) {
Utils.checkRequired(obj, f.args).should.equal(f.check);
});
});
it('should fail to check required fields on non-object', function() {
var obj = 'dummy';
Utils.checkRequired(obj, 'name').should.be.false;
});
});
});
Loading…
Cancel
Save