From a6b797c0921d9e986a956f05dfe30a1410b8fab5 Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Thu, 19 Feb 2015 11:00:14 -0300 Subject: [PATCH 1/7] WIP, supertest testing --- app.js | 250 +-------------------------------- lib/client/api.js | 1 + lib/expressapp.js | 254 ++++++++++++++++++++++++++++++++++ package.json | 3 +- test/integration/clientApi.js | 39 +++++- 5 files changed, 298 insertions(+), 249 deletions(-) create mode 100644 lib/expressapp.js diff --git a/app.js b/app.js index 9519be1..7c73f44 100644 --- a/app.js +++ b/app.js @@ -1,252 +1,10 @@ -'use strict'; - -var _ = require('lodash'); -var async = require('async'); -var log = require('npmlog'); -var express = require('express'); -var querystring = require('querystring'); -var bodyParser = require('body-parser') - -var CopayServer = require('./lib/server'); - -log.debug = log.verbose; -log.level = 'debug'; - - -CopayServer.initialize(); - - -var app = express(); -app.use(function(req, res, next) { - res.setHeader('Access-Control-Allow-Origin', '*'); - res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, DELETE'); - res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,Content-Type,Authorization'); - next(); -}); -var allowCORS = function(req, res, next) { - if ('OPTIONS' == req.method) { - res.send(200); - res.end(); - return; - } - next(); -} -app.use(allowCORS); - -var POST_LIMIT = 1024 * 100 /* Max POST 100 kb */ ; - -app.use(bodyParser.json({ - limit: POST_LIMIT -})); - -app.use(require('morgan')('dev')); +#!/usr/bin/env node +var ExpressApp = require('./lib/expressapp'); var port = process.env.COPAY_PORT || 3001; -var router = express.Router(); - -function returnError(err, res, req) { - if (err instanceof CopayServer.ClientError) { - - var status = (err.code == 'NOTAUTHORIZED') ? 401 : 400; - log.error('Err: ' + status + ':' + req.url + ' :' + err.code + ':' + err.message); - res.status(status).json({ - code: err.code, - error: err.message, - }).end(); - } else { - var code, message; - if (_.isObject(err)) { - code = err.code; - message = err.message; - } - var m = message || err.toString(); - - log.error('Error: ' + req.url + ' :' + code + ':' + m); - res.status(code || 500).json({ - error: m, - }).end(); - } -}; - -function getCredentials(req) { - var identity = req.header('x-identity'); - if (!identity) return; - - return { - copayerId: identity, - signature: req.header('x-signature'), - }; -}; - -function getServerWithAuth(req, res, cb) { - var credentials = getCredentials(req); - var auth = { - copayerId: credentials.copayerId, - message: req.method.toLowerCase() + '|' + req.url + '|' + JSON.stringify(req.body), - signature: credentials.signature, - }; - CopayServer.getInstanceWithAuth(auth, function(err, server) { - if (err) return returnError(err, res, req); - return cb(server); - }); -}; - -router.post('/v1/wallets/', function(req, res) { - var server = CopayServer.getInstance(); - server.createWallet(req.body, function(err, walletId) { - if (err) return returnError(err, res, req); - - res.json({ - walletId: walletId, - }); - }); -}); - -router.post('/v1/wallets/:id/copayers/', function(req, res) { - req.body.walletId = req.params['id']; - var server = CopayServer.getInstance(); - server.joinWallet(req.body, function(err, result) { - if (err) return returnError(err, res, req); - - res.json(result); - }); -}); - -router.get('/v1/wallets/', function(req, res) { - getServerWithAuth(req, res, function(server) { - var result = {}; - async.parallel([ - - function(next) { - server.getWallet({}, function(err, wallet) { - if (err) return next(err); - result.wallet = wallet; - next(); - }); - }, - function(next) { - server.getBalance({}, function(err, balance) { - if (err) return next(err); - result.balance = balance; - next(); - }); - }, - function(next) { - server.getPendingTxs({}, function(err, pendingTxps) { - if (err) return next(err); - result.pendingTxps = pendingTxps; - next(); - }); - }, - ], function(err) { - if (err) return returnError(err, res, req); - res.json(result); - }); - }); -}); - - -router.get('/v1/txproposals/', function(req, res) { - getServerWithAuth(req, res, function(server) { - server.getPendingTxs({}, function(err, pendings) { - if (err) return returnError(err, res, req); - res.json(pendings); - }); - }); -}); - - -router.post('/v1/txproposals/', function(req, res) { - getServerWithAuth(req, res, function(server) { - server.createTx(req.body, function(err, txp) { - if (err) return returnError(err, res, req); - res.json(txp); - }); - }); -}); - - -router.post('/v1/addresses/', function(req, res) { - getServerWithAuth(req, res, function(server) { - server.createAddress(req.body, function(err, address) { - if (err) return returnError(err, res, req); - res.json(address); - }); - }); -}); - -router.get('/v1/addresses/', function(req, res) { - getServerWithAuth(req, res, function(server) { - server.getAddresses({}, function(err, addresses) { - if (err) return returnError(err, res, req); - res.json(addresses); - }); - }); -}); - -router.get('/v1/balance/', function(req, res) { - getServerWithAuth(req, res, function(server) { - server.getBalance({}, function(err, balance) { - if (err) return returnError(err, res, req); - res.json(balance); - }); - }); -}); - -router.post('/v1/txproposals/:id/signatures/', function(req, res) { - getServerWithAuth(req, res, function(server) { - req.body.txProposalId = req.params['id']; - server.signTx(req.body, function(err, txp) { - if (err) return returnError(err, res, req); - res.json(txp); - res.end(); - }); - }); -}); - -// TODO Check HTTP verb and URL name -router.post('/v1/txproposals/:id/broadcast/', function(req, res) { - getServerWithAuth(req, res, function(server) { - req.body.txProposalId = req.params['id']; - server.broadcastTx(req.body, function(err, txp) { - if (err) return returnError(err, res, req); - res.json(txp); - res.end(); - }); - }); -}); - -router.post('/v1/txproposals/:id/rejections', function(req, res) { - getServerWithAuth(req, res, function(server) { - req.body.txProposalId = req.params['id']; - server.rejectTx(req.body, function(err, txp) { - if (err) return returnError(err, res, req); - res.json(txp); - res.end(); - }); - }); -}); - -router.delete('/v1/txproposals/:id/', function(req, res) { - getServerWithAuth(req, res, function(server) { - req.body.txProposalId = req.params['id']; - server.removePendingTx(req.body, function(err) { - if (err) return returnError(err, res, req); - res.end(); - }); - }); -}); - -// TODO: DEBUG only! -router.get('/v1/dump', function(req, res) { - var server = CopayServer.getInstance(); - server.storage._dump(function() { - res.end(); - }); -}); - -app.use('/copay/api', router); +var app = ExpressApp.start(); app.listen(port); + console.log('Copay service running on port ' + port); diff --git a/lib/client/api.js b/lib/client/api.js index f2224ff..9020dbb 100644 --- a/lib/client/api.js +++ b/lib/client/api.js @@ -127,6 +127,7 @@ API.prototype._doRequest = function(method, url, args, data, cb) { var absUrl = this.baseUrl + url; var args = { + relUrl: url, headers: { 'x-identity': data.copayerId, 'x-signature': reqSignature, diff --git a/lib/expressapp.js b/lib/expressapp.js new file mode 100644 index 0000000..b39d61e --- /dev/null +++ b/lib/expressapp.js @@ -0,0 +1,254 @@ +'use strict'; + +var _ = require('lodash'); +var async = require('async'); +var log = require('npmlog'); +var express = require('express'); +var querystring = require('querystring'); +var bodyParser = require('body-parser') + +var CopayServer = require('./server'); + +log.debug = log.verbose; +log.level = 'debug'; + +var ExpressApp = function() {}; + +ExpressApp.start = function(opts) { + opts = opts || {}; + + + CopayServer.initialize(opts.CopayServer); + var app = express(); + app.use(function(req, res, next) { + res.setHeader('Access-Control-Allow-Origin', '*'); + res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, DELETE'); + res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,Content-Type,Authorization'); + next(); + }); + var allowCORS = function(req, res, next) { + if ('OPTIONS' == req.method) { + res.send(200); + res.end(); + return; + } + next(); + } + app.use(allowCORS); + + var POST_LIMIT = 1024 * 100 /* Max POST 100 kb */ ; + + app.use(bodyParser.json({ + limit: POST_LIMIT + })); + + app.use(require('morgan')('dev')); + + + var router = express.Router(); + + function returnError(err, res, req) { + if (err instanceof CopayServer.ClientError) { + + var status = (err.code == 'NOTAUTHORIZED') ? 401 : 400; + log.error('Err: ' + status + ':' + req.url + ' :' + err.code + ':' + err.message); + res.status(status).json({ + code: err.code, + error: err.message, + }).end(); + } else { + var code, message; + if (_.isObject(err)) { + code = err.code; + message = err.message; + } + var m = message || err.toString(); + + log.error('Error: ' + req.url + ' :' + code + ':' + m); + res.status(code || 500).json({ + error: m, + }).end(); + } + }; + + function getCredentials(req) { + var identity = req.header('x-identity'); + if (!identity) return; + + return { + copayerId: identity, + signature: req.header('x-signature'), + }; + }; + + function getServerWithAuth(req, res, cb) { + var credentials = getCredentials(req); + var auth = { + copayerId: credentials.copayerId, + message: req.method.toLowerCase() + '|' + req.url + '|' + JSON.stringify(req.body), + signature: credentials.signature, + }; + CopayServer.getInstanceWithAuth(auth, function(err, server) { + if (err) return returnError(err, res, req); + return cb(server); + }); + }; + + router.post('/v1/wallets/', function(req, res) { + var server = CopayServer.getInstance(); + server.createWallet(req.body, function(err, walletId) { + if (err) return returnError(err, res, req); + + res.json({ + walletId: walletId, + }); + }); + }); + + router.post('/v1/wallets/:id/copayers/', function(req, res) { + req.body.walletId = req.params['id']; + var server = CopayServer.getInstance(); + server.joinWallet(req.body, function(err, result) { + if (err) return returnError(err, res, req); + + res.json(result); + }); + }); + + router.get('/v1/wallets/', function(req, res) { + getServerWithAuth(req, res, function(server) { + var result = {}; + async.parallel([ + + function(next) { + server.getWallet({}, function(err, wallet) { + if (err) return next(err); + result.wallet = wallet; + next(); + }); + }, + function(next) { + server.getBalance({}, function(err, balance) { + if (err) return next(err); + result.balance = balance; + next(); + }); + }, + function(next) { + server.getPendingTxs({}, function(err, pendingTxps) { + if (err) return next(err); + result.pendingTxps = pendingTxps; + next(); + }); + }, + ], function(err) { + if (err) return returnError(err, res, req); + res.json(result); + }); + }); + }); + + + router.get('/v1/txproposals/', function(req, res) { + getServerWithAuth(req, res, function(server) { + server.getPendingTxs({}, function(err, pendings) { + if (err) return returnError(err, res, req); + res.json(pendings); + }); + }); + }); + + + router.post('/v1/txproposals/', function(req, res) { + getServerWithAuth(req, res, function(server) { + server.createTx(req.body, function(err, txp) { + if (err) return returnError(err, res, req); + res.json(txp); + }); + }); + }); + + + router.post('/v1/addresses/', function(req, res) { + getServerWithAuth(req, res, function(server) { + server.createAddress(req.body, function(err, address) { + if (err) return returnError(err, res, req); + res.json(address); + }); + }); + }); + + router.get('/v1/addresses/', function(req, res) { + getServerWithAuth(req, res, function(server) { + server.getAddresses({}, function(err, addresses) { + if (err) return returnError(err, res, req); + res.json(addresses); + }); + }); + }); + + router.get('/v1/balance/', function(req, res) { + getServerWithAuth(req, res, function(server) { + server.getBalance({}, function(err, balance) { + if (err) return returnError(err, res, req); + res.json(balance); + }); + }); + }); + + router.post('/v1/txproposals/:id/signatures/', function(req, res) { + getServerWithAuth(req, res, function(server) { + req.body.txProposalId = req.params['id']; + server.signTx(req.body, function(err, txp) { + if (err) return returnError(err, res, req); + res.json(txp); + res.end(); + }); + }); + }); + + // TODO Check HTTP verb and URL name + router.post('/v1/txproposals/:id/broadcast/', function(req, res) { + getServerWithAuth(req, res, function(server) { + req.body.txProposalId = req.params['id']; + server.broadcastTx(req.body, function(err, txp) { + if (err) return returnError(err, res, req); + res.json(txp); + res.end(); + }); + }); + }); + + router.post('/v1/txproposals/:id/rejections', function(req, res) { + getServerWithAuth(req, res, function(server) { + req.body.txProposalId = req.params['id']; + server.rejectTx(req.body, function(err, txp) { + if (err) return returnError(err, res, req); + res.json(txp); + res.end(); + }); + }); + }); + + router.delete('/v1/txproposals/:id/', function(req, res) { + getServerWithAuth(req, res, function(server) { + req.body.txProposalId = req.params['id']; + server.removePendingTx(req.body, function(err) { + if (err) return returnError(err, res, req); + res.end(); + }); + }); + }); + + // TODO: DEBUG only! + router.get('/v1/dump', function(req, res) { + var server = CopayServer.getInstance(); + server.storage._dump(function() { + res.end(); + }); + }); + app.use(opts.base_path || '/copay/api', router); + return app; +}; + +module.exports = ExpressApp; diff --git a/package.json b/package.json index bcca48e..049b877 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,8 @@ "mocha": "^1.18.2", "sinon": "^1.10.3", "memdown": "^1.0.0", - "jsdoc": "^3.3.0" + "jsdoc": "^3.3.0", + "supertest": "*" }, "scripts": { "start": "node server.js" diff --git a/test/integration/clientApi.js b/test/integration/clientApi.js index dea1aff..6c6e84a 100644 --- a/test/integration/clientApi.js +++ b/test/integration/clientApi.js @@ -4,14 +4,19 @@ var _ = require('lodash'); var chai = require('chai'); var sinon = require('sinon'); var should = chai.should(); +var levelup = require('levelup'); +var memdown = require('memdown'); +var request = require('supertest'); var Client = require('../../lib/client'); var API = Client.API; var Bitcore = require('bitcore'); var TestData = require('./clienttestdata'); var WalletUtils = require('../../lib/walletutils'); +var ExpressApp = require('../../lib/expressapp'); +var Storage = require('../../lib/storage'); describe('client API ', function() { - var client; + var client, app; beforeEach(function() { var fsmock = {};; @@ -24,10 +29,40 @@ describe('client API ', function() { client = new Client({ storage: storage }); + + var db = levelup(memdown, { + valueEncoding: 'json' + }); + var storage = new Storage({ + db: db + }); + app = ExpressApp.start({ + CopayServer: { + storage: storage + } + }); }); + var helpers = {}; + + helpers.request = function(args) { + if (args.method == 'get') { + request(app) + .get(relUrl) + .end(cb); + } else { + request(app) + .post(relUrl) + .send(body) + .end(function(err, res) { + console.log('[clientApi.js.59:err:]', err, res); //TODO + return cb(err, res); + }); + } + }; + describe('#_tryToComplete ', function() { - it('should complete a wallet ', function(done) { + it.only('should complete a wallet ', function(done) { var request = sinon.stub(); // Wallet request From dc446c5f2ba60f70fd19919b79bae43d96fa3d08 Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Thu, 19 Feb 2015 12:32:10 -0300 Subject: [PATCH 2/7] api tests working --- lib/client/api.js | 6 +- lib/expressapp.js | 5 ++ test/integration/clientApi.js | 155 +++++++++++++++++++++++----------- test/integration/server.js | 1 - 4 files changed, 117 insertions(+), 50 deletions(-) diff --git a/lib/client/api.js b/lib/client/api.js index 9020dbb..e964507 100644 --- a/lib/client/api.js +++ b/lib/client/api.js @@ -60,8 +60,11 @@ function API(opts) { this.verbose = !!opts.verbose; this.request = request || opts.request; this.baseUrl = opts.baseUrl || BASE_URL; + this.basePath = this.baseUrl.replace(/http.?:\/\/[a-zA-Z0-9:-]*\//,'/'); if (this.verbose) { log.level = 'debug'; + } else { + log.level = 'info'; } }; @@ -127,7 +130,8 @@ API.prototype._doRequest = function(method, url, args, data, cb) { var absUrl = this.baseUrl + url; var args = { - relUrl: url, + // relUrl: only for testing with `supertest` + relUrl: this.basePath + url, headers: { 'x-identity': data.copayerId, 'x-signature': reqSignature, diff --git a/lib/expressapp.js b/lib/expressapp.js index b39d61e..07f9de3 100644 --- a/lib/expressapp.js +++ b/lib/expressapp.js @@ -83,6 +83,11 @@ ExpressApp.start = function(opts) { function getServerWithAuth(req, res, cb) { var credentials = getCredentials(req); + if (!credentials) + return returnError(new CopayServer.ClientError({ + code: 'NOTAUTHORIZED' + }), res, req); + var auth = { copayerId: credentials.copayerId, message: req.method.toLowerCase() + '|' + req.url + '|' + JSON.stringify(req.body), diff --git a/test/integration/clientApi.js b/test/integration/clientApi.js index 6c6e84a..09a0a1d 100644 --- a/test/integration/clientApi.js +++ b/test/integration/clientApi.js @@ -6,6 +6,7 @@ var sinon = require('sinon'); var should = chai.should(); var levelup = require('levelup'); var memdown = require('memdown'); +var async = require('async'); var request = require('supertest'); var Client = require('../../lib/client'); var API = Client.API; @@ -15,66 +16,124 @@ var WalletUtils = require('../../lib/walletutils'); var ExpressApp = require('../../lib/expressapp'); var Storage = require('../../lib/storage'); + +var helpers = {}; + +helpers.getRequest = function(app) { + return function(args, cb) { + var req = request(app); + var r = req[args.method](args.relUrl); + + if (args.headers) { + _.each(args.headers, function(v, k) { + r.set(k, v); + }) + } + if (!_.isEmpty(args.body)) { + r.send(args.body); + }; + r.end(function(err, res) { + return cb(err, res, res.body); + }); + }; +}; + +helpers.createAndJoinWallet = function(clients, m, n, cb) { + clients[0].createWallet('wallet name', 'creator copayer', m, n, 'testnet', + function(err, secret) { + if (err) return cb(err); + if (n == 1) return cb(); + + should.exist(secret); + async.each(_.range(n-1), function(i, cb) { + clients[i + 1].joinWallet(secret, 'copayer ' + (i + 1), function(err, result) { + should.not.exist(err); + return cb(err); + }); + }, function(err) { + if (err) return new Error('Could not generate wallet'); + return cb(); + }); + }); +}; + +var fsmock = {}; +var content = {}; +fsmock.readFile = function(name, enc, cb) { + if (!content || _.isEmpty(content[name])) + return cb('empty'); + + return cb(null, content[name]); +}; +fsmock.writeFile = function(name, data, cb) { + content[name] = data; + return cb(); +}; + describe('client API ', function() { - var client, app; + var clients; beforeEach(function() { - var fsmock = {};; - fsmock.readFile = sinon.mock().yields(null, JSON.stringify(TestData.storage.wallet11)); - fsmock.writeFile = sinon.mock().yields(); - var storage = new Client.FileStorage({ - filename: 'dummy', - fs: fsmock, - }); - client = new Client({ - storage: storage + clients = []; + // Generates 5 clients + _.each(_.range(5), function(i) { + var storage = new Client.FileStorage({ + filename: 'client' + i, + fs: fsmock, + }); + var client = new Client({ + storage: storage, + }); + var db = levelup(memdown, { + valueEncoding: 'json' + }); + var storage = new Storage({ + db: db + }); + var app = ExpressApp.start({ + CopayServer: { + storage: storage + } + }); + client.request = helpers.getRequest(app); + clients.push(client); }); + content={}; + }); - var db = levelup(memdown, { - valueEncoding: 'json' + describe.only('#getBalance', function() { + it('should check balance in a 1-1 ', function(done) { + helpers.createAndJoinWallet(clients, 1, 1, function(err) { + should.not.exist(err); + clients[0].getBalance(function(err, x) { + should.not.exist(err); + done(); + }) + }); }); - var storage = new Storage({ - db: db - }); - app = ExpressApp.start({ - CopayServer: { - storage: storage - } + it('should be able to check balance in a 2-3 wallet ', function(done) { + helpers.createAndJoinWallet(clients, 2, 3, function(err) { + should.not.exist(err); + clients[0].getBalance(function(err, x) { + should.not.exist(err); + clients[1].getBalance(function(err, x) { + should.not.exist(err); + clients[2].getBalance(function(err, x) { + should.not.exist(err); + done(); + }) + }) + }) + }); }); }); - var helpers = {}; - - helpers.request = function(args) { - if (args.method == 'get') { - request(app) - .get(relUrl) - .end(cb); - } else { - request(app) - .post(relUrl) - .send(body) - .end(function(err, res) { - console.log('[clientApi.js.59:err:]', err, res); //TODO - return cb(err, res); - }); - } - }; - describe('#_tryToComplete ', function() { - it.only('should complete a wallet ', function(done) { - var request = sinon.stub(); + it('should complete a wallet ', function(done) { + client.storage.fs.readFile = + sinon.stub().yields(null, JSON.stringify(TestData.storage.incompleteWallet22)); - // Wallet request - request.onCall(0).yields(null, { - statusCode: 200, - }, TestData.serverResponse.completeWallet); - request.onCall(1).yields(null, { - statusCode: 200, - }, "pepe"); - client.request = request; - client.storage.fs.readFile = sinon.stub().yields(null, JSON.stringify(TestData.storage.incompleteWallet22)); client.getBalance(function(err, x) { should.not.exist(err); done(); diff --git a/test/integration/server.js b/test/integration/server.js index 92490db..cf5e799 100644 --- a/test/integration/server.js +++ b/test/integration/server.js @@ -20,7 +20,6 @@ var Copayer = require('../../lib/model/copayer'); var CopayServer = require('../../lib/server'); var TestData = require('../testdata'); - var helpers = {}; helpers.getAuthServer = function(copayerId, cb) { var signatureStub = sinon.stub(CopayServer.prototype, '_verifySignature'); From 83f59ddfbbbb7d9a55d2aa338fb444bf70edbd70 Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Thu, 19 Feb 2015 12:45:28 -0300 Subject: [PATCH 3/7] rm express client --- test/integration/clientApi.js | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/test/integration/clientApi.js b/test/integration/clientApi.js index 09a0a1d..7231435 100644 --- a/test/integration/clientApi.js +++ b/test/integration/clientApi.js @@ -45,7 +45,7 @@ helpers.createAndJoinWallet = function(clients, m, n, cb) { if (n == 1) return cb(); should.exist(secret); - async.each(_.range(n-1), function(i, cb) { + async.each(_.range(n - 1), function(i, cb) { clients[i + 1].joinWallet(secret, 'copayer ' + (i + 1), function(err, result) { should.not.exist(err); return cb(err); @@ -75,6 +75,17 @@ describe('client API ', function() { beforeEach(function() { clients = []; + var db = levelup(memdown, { + valueEncoding: 'json' + }); + var storage = new Storage({ + db: db + }); + var app = ExpressApp.start({ + CopayServer: { + storage: storage + } + }); // Generates 5 clients _.each(_.range(5), function(i) { var storage = new Client.FileStorage({ @@ -84,21 +95,11 @@ describe('client API ', function() { var client = new Client({ storage: storage, }); - var db = levelup(memdown, { - valueEncoding: 'json' - }); - var storage = new Storage({ - db: db - }); - var app = ExpressApp.start({ - CopayServer: { - storage: storage - } - }); + client.request = helpers.getRequest(app); clients.push(client); }); - content={}; + content = {}; }); describe.only('#getBalance', function() { From ce8aeee3a96d681867fa6b876cc71b176a204200 Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Thu, 19 Feb 2015 17:38:48 -0300 Subject: [PATCH 4/7] add api tests --- lib/client/Verifier.js | 3 +- lib/client/api.js | 2 +- lib/server.js | 13 +- lib/walletutils.js | 6 +- test/integration/clientApi.js | 342 +++++++++++++++++++++++----------- 5 files changed, 254 insertions(+), 112 deletions(-) diff --git a/lib/client/Verifier.js b/lib/client/Verifier.js index 2439497..a653a72 100644 --- a/lib/client/Verifier.js +++ b/lib/client/Verifier.js @@ -35,7 +35,8 @@ Verifier.checkCopayers = function(copayers, walletPrivKey, myXPrivKey, n) { } // Not signed pub keys - if (!WalletUtils.verifyMessage(copayer.xPubKey, copayer.xPubKeySignature, walletPubKey)) { + if (!copayer.xPubKey || !copayer.xPubKeySignature || + !WalletUtils.verifyMessage(copayer.xPubKey, copayer.xPubKeySignature, walletPubKey)) { log.error('Invalid signatures in server response'); error = true; } diff --git a/lib/client/api.js b/lib/client/api.js index e964507..9cb714a 100644 --- a/lib/client/api.js +++ b/lib/client/api.js @@ -420,7 +420,7 @@ API.prototype.import = function(str, cb) { var xPubKey = (new Bitcore.HDPublicKey(data.xPrivKey)).toString(); - data.publicKeyRing.push(xPubKey); + data.publicKeyRing.unshift(xPubKey); data.copayerId = WalletUtils.xPubToCopayerId(xPubKey); data.n = data.publicKeyRing.length; data.signingPrivKey = (new Bitcore.HDPrivateKey(data.xPrivKey)).derive('m/1/0').privateKey.toWIF(); diff --git a/lib/server.js b/lib/server.js index bc3a287..2ae43d0 100644 --- a/lib/server.js +++ b/lib/server.js @@ -26,15 +26,18 @@ var TxProposal = require('./model/txproposal'); var Notification = require('./model/notification'); var initialized = false; -var storage; +var storage, blockExplorer; /** * Creates an instance of the Copay server. * @constructor */ function CopayServer() { - if (!initialized) throw new Error('Server not initialized'); + if (!initialized) + throw new Error('Server not initialized'); + this.storage = storage; + this.blockExplorer = blockExplorer; this.notifyTicker = 0; }; @@ -45,10 +48,12 @@ nodeutil.inherits(CopayServer, events.EventEmitter); * Initializes global settings for all instances. * @param {Object} opts * @param {Storage} [opts.storage] - The storage provider. + * @param {Storage} [opts.blockExplorer] - The blockExporer provider. */ CopayServer.initialize = function(opts) { opts = opts || {}; storage = opts.storage ||  new Storage(); + blockExplorer = opts.blockExplorer; initialized = true; }; @@ -311,6 +316,9 @@ CopayServer.prototype.verifyMessageSignature = function(opts, cb) { CopayServer.prototype._getBlockExplorer = function(provider, network) { var url; + if (this.blockExplorer) + return this.blockExplorer; + switch (provider) { default: case 'insight': @@ -352,7 +360,6 @@ CopayServer.prototype._getUtxos = function(cb) { var utxos = _.map(inutxos, function(i) { return i.toObject(); }); - self.getPendingTxs({}, function(err, txps) { if (err) return cb(err); diff --git a/lib/walletutils.js b/lib/walletutils.js index 5aacd82..9138f83 100644 --- a/lib/walletutils.js +++ b/lib/walletutils.js @@ -1,4 +1,5 @@ var _ = require('lodash'); +var $ = require('preconditions').singleton(); var sjcl = require('sjcl'); var Bitcore = require('bitcore'); @@ -14,6 +15,7 @@ function WalletUtils() {}; /* TODO: It would be nice to be compatible with bitcoind signmessage. How * the hash is calculated there? */ WalletUtils.hashMessage = function(text) { + $.checkArgument(text); var buf = new Buffer(text); var ret = crypto.Hash.sha256sha256(buf); ret = new Bitcore.encoding.BufferReader(ret).readReverse(); @@ -22,6 +24,7 @@ WalletUtils.hashMessage = function(text) { WalletUtils.signMessage = function(text, privKey) { + $.checkArgument(text); var priv = new PrivateKey(privKey); var hash = WalletUtils.hashMessage(text); return crypto.ECDSA.sign(hash, priv, 'little').toString(); @@ -29,6 +32,7 @@ WalletUtils.signMessage = function(text, privKey) { WalletUtils.verifyMessage = function(text, signature, pubKey) { + $.checkArgument(signature, text, pubKey); var pub = new PublicKey(pubKey); var hash = WalletUtils.hashMessage(text); @@ -69,6 +73,7 @@ WalletUtils.toSecret = function(walletId, walletPrivKey, network) { }; WalletUtils.fromSecret = function(secret) { + $.checkArgument(secret); var secretSplit = secret.split(':'); var walletId = secretSplit[0]; var walletPrivKey = Bitcore.PrivateKey.fromString(secretSplit[1]); @@ -105,7 +110,6 @@ WalletUtils.UNITS = { WalletUtils.parseAmount = function(text) { var regex = '^(\\d*(\\.\\d{0,8})?)\\s*(' + _.keys(WalletUtils.UNITS).join('|') + ')?$'; - var match = new RegExp(regex, 'i').exec(text.trim()); if (!match || match.length === 0) throw new Error('Invalid amount'); diff --git a/test/integration/clientApi.js b/test/integration/clientApi.js index 7231435..1d5b438 100644 --- a/test/integration/clientApi.js +++ b/test/integration/clientApi.js @@ -39,7 +39,7 @@ helpers.getRequest = function(app) { }; helpers.createAndJoinWallet = function(clients, m, n, cb) { - clients[0].createWallet('wallet name', 'creator copayer', m, n, 'testnet', + clients[0].createWallet('wallet name', 'creator', m, n, 'testnet', function(err, secret) { if (err) return cb(err); if (n == 1) return cb(); @@ -51,12 +51,17 @@ helpers.createAndJoinWallet = function(clients, m, n, cb) { return cb(err); }); }, function(err) { - if (err) return new Error('Could not generate wallet'); - return cb(); + if (err) return cb(err); + return cb(null, { + m: m, + n: n, + secret: secret, + }); }); }); }; + var fsmock = {}; var content = {}; fsmock.readFile = function(name, enc, cb) { @@ -69,9 +74,47 @@ fsmock.writeFile = function(name, data, cb) { content[name] = data; return cb(); }; +fsmock.reset = function() { + content = {}; +}; + +fsmock._get = function(name) { + return content[name]; +}; + + +var utxos = []; +var blockExplorerMock = {}; + + + + +blockExplorerMock.getUnspentUtxos = function(dummy, cb) { + var ret = _.map(utxos || [], function(x) { + x.toObject = function() { + return this; + }; + return x; + }); + return cb(null, ret); +}; + +blockExplorerMock.setUtxo = function(address, amount, m) { + utxos.push({ + txid: Bitcore.crypto.Hash.sha256(new Buffer(Math.random() * 100000)).toString('hex'), + vout: Math.floor((Math.random() * 10) + 1), + amount: amount, + address: address.address, + scriptPubKey: Bitcore.Script.buildMultisigOut(address.publicKeys, m).toScriptHashOut(), + }); +}; + +blockExplorerMock.reset = function() { + utxos = []; +}; describe('client API ', function() { - var clients; + var clients, app; beforeEach(function() { clients = []; @@ -81,9 +124,10 @@ describe('client API ', function() { var storage = new Storage({ db: db }); - var app = ExpressApp.start({ + app = ExpressApp.start({ CopayServer: { - storage: storage + storage: storage, + blockExplorer: blockExplorerMock, } }); // Generates 5 clients @@ -99,10 +143,11 @@ describe('client API ', function() { client.request = helpers.getRequest(app); clients.push(client); }); - content = {}; + fsmock.reset(); + blockExplorerMock.reset(); }); - describe.only('#getBalance', function() { + describe('Wallet Creation', function() { it('should check balance in a 1-1 ', function(done) { helpers.createAndJoinWallet(clients, 1, 1, function(err) { should.not.exist(err); @@ -112,7 +157,7 @@ describe('client API ', function() { }) }); }); - it('should be able to check balance in a 2-3 wallet ', function(done) { + it('should be able to complete wallets in copayer that joined later', function(done) { helpers.createAndJoinWallet(clients, 2, 3, function(err) { should.not.exist(err); clients[0].getBalance(function(err, x) { @@ -127,105 +172,212 @@ describe('client API ', function() { }) }); }); - }); - describe('#_tryToComplete ', function() { - it('should complete a wallet ', function(done) { - client.storage.fs.readFile = - sinon.stub().yields(null, JSON.stringify(TestData.storage.incompleteWallet22)); - - - client.getBalance(function(err, x) { + it('should not allow to join a full wallet ', function(done) { + helpers.createAndJoinWallet(clients, 2, 2, function(err, w) { should.not.exist(err); + should.exist(w.secret); + clients[4].joinWallet(w.secret, 'copayer', function(err, result) { + err.should.contain('Request error'); + done(); + }); + }); + }); + it('should fail with a unknown secret', function(done) { + var oldSecret = '3f8e5acb-ceeb-4aae-134f-692d934e3b1c:L2gohj8s2fLKqVU5cQutAVGciutUxczFxLxxXHFsjzLh71ZjkFQQ:T'; + clients[0].joinWallet(oldSecret, 'copayer', function(err, result) { + err.should.contain('Request error'); done(); }); }); - - - it('should handle incomple wallets', function(done) { - var request = sinon.stub(); - - // Wallet request - request.onCall(0).yields(null, { - statusCode: 200, - }, TestData.serverResponse.incompleteWallet); - - client.request = request; - client.storage.fs.readFile = sinon.stub().yields(null, JSON.stringify(TestData.storage.incompleteWallet22)); - client.createAddress(function(err, x) { - err.should.contain('Incomplete'); - done(); - }); - }); - it('should reject wallets with bad signatures', function(done) { - var request = sinon.stub(); - // Wallet request - request.onCall(0).yields(null, { - statusCode: 200, - }, TestData.serverResponse.corruptWallet22); + helpers.createAndJoinWallet(clients, 2, 3, function(err) { + should.not.exist(err); - client.request = request; - client.storage.fs.readFile = sinon.stub().yields(null, JSON.stringify(TestData.storage.incompleteWallet22)); - client.createAddress(function(err, x) { - err.should.contain('verified'); - done(); + // Get right response + var data = clients[0]._load(function(err, data) { + var url = '/v1/wallets/'; + clients[0]._doGetRequest(url, data, function(err, x) { + + // Tamper data + x.wallet.copayers[0].xPubKey = x.wallet.copayers[1].xPubKey; + + // Tamper response + clients[1]._doGetRequest = sinon.stub().yields(null, x); + + clients[1].getBalance(function(err, x) { + err.should.contain('verified'); + done(); + }); + }); + }); }); }); - it('should reject wallets with missing signatures ', function(done) { - var request = sinon.stub(); - // Wallet request - request.onCall(0).yields(null, { - statusCode: 200, - }, TestData.serverResponse.corruptWallet222); + it('should reject wallets with missing signatures', function(done) { + helpers.createAndJoinWallet(clients, 2, 3, function(err) { + should.not.exist(err); - client.request = request; - client.storage.fs.readFile = sinon.stub().yields(null, JSON.stringify(TestData.storage.incompleteWallet22)); - client.createAddress(function(err, x) { - err.should.contain('verified'); - done(); + // Get right response + var data = clients[0]._load(function(err, data) { + var url = '/v1/wallets/'; + clients[0]._doGetRequest(url, data, function(err, x) { + + // Tamper data + delete x.wallet.copayers[1].xPubKey; + + // Tamper response + clients[1]._doGetRequest = sinon.stub().yields(null, x); + + clients[1].getBalance(function(err, x) { + err.should.contain('verified'); + done(); + }); + }); + }); }); }); + it('should reject wallets missing caller"s pubkey', function(done) { - var request = sinon.stub(); - // Wallet request - request.onCall(0).yields(null, { - statusCode: 200, - }, TestData.serverResponse.missingMyPubKey); + helpers.createAndJoinWallet(clients, 2, 3, function(err) { + should.not.exist(err); - client.request = request; - client.storage.fs.readFile = sinon.stub().yields(null, JSON.stringify(TestData.storage.incompleteWallet22)); - client.createAddress(function(err, x) { - err.should.contain('verified'); - done(); + // Get right response + var data = clients[0]._load(function(err, data) { + var url = '/v1/wallets/'; + clients[0]._doGetRequest(url, data, function(err, x) { + + // Tamper data. Replace caller's pubkey + x.wallet.copayers[1].xPubKey = (new Bitcore.HDPrivateKey()).publicKey; + // Add a correct signature + x.wallet.copayers[1].xPubKeySignature = WalletUtils.signMessage( + x.wallet.copayers[1].xPubKey, data.walletPrivKey), + + // Tamper response + clients[1]._doGetRequest = sinon.stub().yields(null, x); + + clients[1].getBalance(function(err, x) { + err.should.contain('verified'); + done(); + }); + }); + }); }); }); }); - describe('#createAddress ', function() { - it('should check address ', function(done) { - var response = { - createdOn: 1424105995, - address: '2N3fA6wDtnebzywPkGuNK9KkFaEzgbPRRTq', - path: 'm/2147483647/0/7', - publicKeys: ['03f6a5fe8db51bfbaf26ece22a3e3bc242891a47d3048fc70bc0e8c03a071ad76f'] - }; - var request = sinon.mock().yields(null, { - statusCode: 200 - }, response); - client.request = request; - - - client.createAddress(function(err, x) { + describe('Address Creation', function() { + it('should be able to create address in all copayers in a 2-3 wallet', function(done) { + this.timeout(5000); + helpers.createAndJoinWallet(clients, 2, 3, function(err) { should.not.exist(err); - x.address.should.equal('2N3fA6wDtnebzywPkGuNK9KkFaEzgbPRRTq'); - done(); + clients[0].createAddress(function(err, x0) { + should.not.exist(err); + should.exist(x0.address); + clients[1].createAddress(function(err, x1) { + should.not.exist(err); + should.exist(x1.address); + clients[2].createAddress(function(err, x2) { + should.not.exist(err); + should.exist(x2.address); + done(); + }); + }); + }); }); }); + it('should see balance on address created by others', function(done) { + helpers.createAndJoinWallet(clients, 2, 2, function(err, w) { + should.not.exist(err); + clients[0].createAddress(function(err, x0) { + should.not.exist(err); + should.exist(x0.address); + blockExplorerMock.setUtxo(x0, 10, w.m); + clients[0].getBalance(function(err, bal0) { + should.not.exist(err); + bal0.totalAmount.should.equal(10 * 1e8); + bal0.lockedAmount.should.equal(0); + clients[1].getBalance(function(err, bal1) { + bal1.totalAmount.should.equal(10 * 1e8); + bal1.lockedAmount.should.equal(0); + done(); + }); + }); + }); + }); + }); + }); + + + describe('Wallet Backups and Mobility', function() { + + it('round trip #import #export', function(done) { + helpers.createAndJoinWallet(clients, 2, 2, function(err, w) { + should.not.exist(err); + clients[0].export(function(err, str) { + should.not.exist(err); + var original = JSON.parse(fsmock._get('client0')); + clients[2].import(str, function(err, wallet) { + should.not.exist(err); + var clone = JSON.parse(fsmock._get('client2')); + delete original.walletPrivKey; // no need to persist it. + clone.should.deep.equal(original); + done(); + }); + + }); + }); + }); + it('should recreate a wallet, create addresses and receive money', function(done) { + var backup = '["tprv8ZgxMBicQKsPehCdj4HM1MZbKVXBFt5Dj9nQ44M99EdmdiUfGtQBDTSZsKmzdUrB1vEuP6ipuoa39UXwPS2CvnjE1erk5aUjc5vQZkWvH4B",2,["tpubD6NzVbkrYhZ4XCNDPDtyRWPxvJzvTkvUE2cMPB8jcUr9Dkicv6cYQmA18DBAid6eRK1BGCU9nzgxxVdQUGLYJ34XsPXPW4bxnH4PH6oQBF3"],"sd0kzXmlXBgTGHrKaBW4aA=="]'; + clients[0].import(backup, function(err, wallet) { + should.not.exist(err); + clients[0].reCreateWallet('pepe', function(err, wallet) { + should.not.exist(err); + + clients[0].createAddress(function(err, x0) { + should.not.exist(err); + should.exist(x0.address); + blockExplorerMock.setUtxo(x0, 10, 2); + clients[0].getBalance(function(err, bal0) { + should.not.exist(err); + bal0.totalAmount.should.equal(10 * 1e8); + bal0.lockedAmount.should.equal(0); + done(); + }); + }); + }); + }); + }); + }); + + + describe.only('Send TXs', function() { + it('Send and broadcast in 1-1 wallet', function(done) { + helpers.createAndJoinWallet(clients, 1, 1, function(err, w) { + clients[0].createAddress(function(err, x0) { + should.not.exist(err); + should.exist(x0.address); + blockExplorerMock.setUtxo(x0, 10, 1); + var opts = { + amount: 1000, + toAddress: 'n2TBMPzPECGUfcT2EByiTJ12TPZkhN2mN5', + message: 'hola 1-1', + }; + clients[0].sendTxProposal(opts, function(err, x) { + should.not.exist(err); +console.log('[clientApi.js.372]',x); //TODO + done(); + }); + }); + }); + }); + }); + /* + describe('TODO', function(x) { it('should detect fake addresses ', function(done) { var response = { createdOn: 1424105995, @@ -246,25 +398,6 @@ describe('client API ', function() { }); - describe('#export & #import 2-2 wallet', function() { - it('round trip ', function(done) { - client.storage.fs.readFile = sinon.stub().yields(null, JSON.stringify(TestData.storage.complete22)); - client.export(function(err, str) { - should.not.exist(err); - - client.storage.fs.readFile = sinon.stub().yields(null); - client.import(str, function(err, wallet) { - should.not.exist(err); - var wallet = JSON.parse(client.storage.fs.writeFile.getCall(0).args[1]); - TestData.storage.complete22.should.deep.equal(wallet); - - done(); - }); - }); - }); - }); - - describe('#getTxProposals', function() { it('should return tx proposals and decrypt message', function(done) { client.storage.fs.readFile = sinon.stub().yields(null, JSON.stringify(TestData.storage.complete11)); @@ -283,10 +416,6 @@ describe('client API ', function() { }); }); - describe('#recreate', function() { - it.skip('Should recreate a wallet acording stored data', function(done) {}); - }); - describe('#sendTxProposal ', function() { it('should send tx proposal with encrypted message', function(done) { client.storage.fs.readFile = sinon.stub().yields(null, JSON.stringify(TestData.storage.complete11)); @@ -356,4 +485,5 @@ describe('client API ', function() { }); }); }); + */ }); From f3a152ca30e1fc6c427e2b6f2204f305f6c7fa2a Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Thu, 19 Feb 2015 17:47:51 -0300 Subject: [PATCH 5/7] send TX tests --- lib/client/Verifier.js | 1 - lib/client/api.js | 1 - lib/walletutils.js | 3 +++ test/integration/clientApi.js | 41 +++++++++++++++++++++++++++++++---- 4 files changed, 40 insertions(+), 6 deletions(-) diff --git a/lib/client/Verifier.js b/lib/client/Verifier.js index a653a72..9cc75e4 100644 --- a/lib/client/Verifier.js +++ b/lib/client/Verifier.js @@ -58,7 +58,6 @@ Verifier.checkTxProposal = function(data, txp) { if (WalletUtils.xPubToCopayerId(xPubKey) === txp.creatorId) return true; }); if (!creatorXPubKey) return false; - var creatorSigningPubKey = (new Bitcore.HDPublicKey(creatorXPubKey)).derive('m/1/0').publicKey.toString(); var hash = WalletUtils.getProposalHash(txp.toAddress, txp.amount, txp.message); diff --git a/lib/client/api.js b/lib/client/api.js index 9cb714a..4f3e452 100644 --- a/lib/client/api.js +++ b/lib/client/api.js @@ -457,7 +457,6 @@ API.prototype.signTxProposal = function(txp, cb) { return cb(new ServerCompromisedError('Server sent fake transaction proposal')); } - //Derive proper key to sign, for each input var privs = [], derived = {}; diff --git a/lib/walletutils.js b/lib/walletutils.js index 9138f83..6707a63 100644 --- a/lib/walletutils.js +++ b/lib/walletutils.js @@ -109,6 +109,9 @@ WalletUtils.UNITS = { }; WalletUtils.parseAmount = function(text) { + if (!_.isString(text)) + text = text.toString(); + var regex = '^(\\d*(\\.\\d{0,8})?)\\s*(' + _.keys(WalletUtils.UNITS).join('|') + ')?$'; var match = new RegExp(regex, 'i').exec(text.trim()); diff --git a/test/integration/clientApi.js b/test/integration/clientApi.js index 1d5b438..8739c69 100644 --- a/test/integration/clientApi.js +++ b/test/integration/clientApi.js @@ -355,7 +355,7 @@ describe('client API ', function() { }); - describe.only('Send TXs', function() { + describe.only('Send Transactions', function() { it('Send and broadcast in 1-1 wallet', function(done) { helpers.createAndJoinWallet(clients, 1, 1, function(err, w) { clients[0].createAddress(function(err, x0) { @@ -363,19 +363,52 @@ describe('client API ', function() { should.exist(x0.address); blockExplorerMock.setUtxo(x0, 10, 1); var opts = { - amount: 1000, + amount: 10000, toAddress: 'n2TBMPzPECGUfcT2EByiTJ12TPZkhN2mN5', message: 'hola 1-1', }; clients[0].sendTxProposal(opts, function(err, x) { should.not.exist(err); -console.log('[clientApi.js.372]',x); //TODO - done(); + x.requiredRejections.should.equal(1); + x.requiredSignatures.should.equal(1); + x.status.should.equal('pending'); + x.changeAddress.path.should.equal('m/2147483647/1/0'); + clients[0].signTxProposal(x.id, function(err, res) { + should.not.exist(err, err.message); + done(); + }); }); }); }); }); + it('Send and broadcast in 2-3 wallet', function(done) { + helpers.createAndJoinWallet(clients, 2, 3, function(err, w) { + clients[0].createAddress(function(err, x0) { + should.not.exist(err); + should.exist(x0.address); + blockExplorerMock.setUtxo(x0, 10, 1); + var opts = { + amount: 10000, + toAddress: 'n2TBMPzPECGUfcT2EByiTJ12TPZkhN2mN5', + message: 'hola 1-1', + }; + clients[0].sendTxProposal(opts, function(err, x) { + should.not.exist(err); + x.status.should.equal('pending'); + x.requiredRejections.should.equal(2); + x.requiredSignatures.should.equal(2); + clients[0].signTxProposal(x.id, function(err, res) { + should.not.exist(err, err.message); + done(); + }); + }); + }); + }); + }); + }); + + /* describe('TODO', function(x) { it('should detect fake addresses ', function(done) { From 36ed3682be18999ddd0b39601eb1a6cacdd29e81 Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Thu, 19 Feb 2015 18:11:57 -0300 Subject: [PATCH 6/7] sendTx sign reject tests --- lib/client/Verifier.js | 2 + lib/client/api.js | 2 + lib/server.js | 2 +- test/integration/clientApi.js | 112 +++++++++++++++++++++++++++++----- 4 files changed, 103 insertions(+), 15 deletions(-) diff --git a/lib/client/Verifier.js b/lib/client/Verifier.js index 9cc75e4..639b73e 100644 --- a/lib/client/Verifier.js +++ b/lib/client/Verifier.js @@ -54,6 +54,8 @@ Verifier.checkCopayers = function(copayers, walletPrivKey, myXPrivKey, n) { Verifier.checkTxProposal = function(data, txp) { + $.checkArgument(txp.creatorId); + var creatorXPubKey = _.find(data.publicKeyRing, function(xPubKey) { if (WalletUtils.xPubToCopayerId(xPubKey) === txp.creatorId) return true; }); diff --git a/lib/client/api.js b/lib/client/api.js index 4f3e452..3530582 100644 --- a/lib/client/api.js +++ b/lib/client/api.js @@ -449,6 +449,7 @@ API.prototype.getTxProposals = function(opts, cb) { API.prototype.signTxProposal = function(txp, cb) { var self = this; + $.checkArgument(txp.creatorId); this._loadAndCheck(function(err, data) { if (err) return cb(err); @@ -497,6 +498,7 @@ API.prototype.signTxProposal = function(txp, cb) { API.prototype.rejectTxProposal = function(txp, reason, cb) { var self = this; + $.checkArgument(cb); this._loadAndCheck( function(err, data) { diff --git a/lib/server.js b/lib/server.js index 2ae43d0..486e762 100644 --- a/lib/server.js +++ b/lib/server.js @@ -780,7 +780,7 @@ CopayServer.prototype.rejectTx = function(opts, cb) { }); }; - return cb(); + return cb(null, txp); }); }); }; diff --git a/test/integration/clientApi.js b/test/integration/clientApi.js index 8739c69..16c0288 100644 --- a/test/integration/clientApi.js +++ b/test/integration/clientApi.js @@ -83,14 +83,14 @@ fsmock._get = function(name) { }; -var utxos = []; var blockExplorerMock = {}; +blockExplorerMock.utxos = []; blockExplorerMock.getUnspentUtxos = function(dummy, cb) { - var ret = _.map(utxos || [], function(x) { + var ret = _.map(blockExplorerMock.utxos || [], function(x) { x.toObject = function() { return this; }; @@ -100,17 +100,23 @@ blockExplorerMock.getUnspentUtxos = function(dummy, cb) { }; blockExplorerMock.setUtxo = function(address, amount, m) { - utxos.push({ + blockExplorerMock.utxos.push({ txid: Bitcore.crypto.Hash.sha256(new Buffer(Math.random() * 100000)).toString('hex'), vout: Math.floor((Math.random() * 10) + 1), amount: amount, address: address.address, - scriptPubKey: Bitcore.Script.buildMultisigOut(address.publicKeys, m).toScriptHashOut(), + scriptPubKey: Bitcore.Script.buildMultisigOut(address.publicKeys, m).toScriptHashOut().toString(), }); }; + +blockExplorerMock.broadcast = function(raw, cb) { + blockExplorerMock.lastBroadcasted = raw; + return cb(null, (new Bitcore.Transaction(raw)).id); +}; + blockExplorerMock.reset = function() { - utxos = []; + blockExplorerMock.utxos = []; }; describe('client API ', function() { @@ -355,15 +361,15 @@ describe('client API ', function() { }); - describe.only('Send Transactions', function() { + describe('Send Transactions', function() { it('Send and broadcast in 1-1 wallet', function(done) { helpers.createAndJoinWallet(clients, 1, 1, function(err, w) { clients[0].createAddress(function(err, x0) { should.not.exist(err); should.exist(x0.address); - blockExplorerMock.setUtxo(x0, 10, 1); + blockExplorerMock.setUtxo(x0, 1, 1); var opts = { - amount: 10000, + amount: '0.1btc', toAddress: 'n2TBMPzPECGUfcT2EByiTJ12TPZkhN2mN5', message: 'hola 1-1', }; @@ -373,8 +379,10 @@ describe('client API ', function() { x.requiredSignatures.should.equal(1); x.status.should.equal('pending'); x.changeAddress.path.should.equal('m/2147483647/1/0'); - clients[0].signTxProposal(x.id, function(err, res) { - should.not.exist(err, err.message); + clients[0].signTxProposal(x, function(err, tx) { + should.not.exist(err); + tx.status.should.equal('broadcasted'); + tx.txid.should.equal((new Bitcore.Transaction(blockExplorerMock.lastBroadcasted)).id); done(); }); }); @@ -397,15 +405,91 @@ describe('client API ', function() { x.status.should.equal('pending'); x.requiredRejections.should.equal(2); x.requiredSignatures.should.equal(2); - clients[0].signTxProposal(x.id, function(err, res) { - should.not.exist(err, err.message); - done(); + clients[0].signTxProposal(x, function(err, tx) { + should.not.exist(err, err); + tx.status.should.equal('pending'); + clients[1].signTxProposal(x, function(err, tx) { + should.not.exist(err); + tx.status.should.equal('broadcasted'); + tx.txid.should.equal((new Bitcore.Transaction(blockExplorerMock.lastBroadcasted)).id); + done(); + }); }); }); }); }); }); - + + it('Send, reject, 2 signs and broadcast in 2-3 wallet', function(done) { + helpers.createAndJoinWallet(clients, 2, 3, function(err, w) { + clients[0].createAddress(function(err, x0) { + should.not.exist(err); + should.exist(x0.address); + blockExplorerMock.setUtxo(x0, 10, 1); + var opts = { + amount: 10000, + toAddress: 'n2TBMPzPECGUfcT2EByiTJ12TPZkhN2mN5', + message: 'hola 1-1', + }; + clients[0].sendTxProposal(opts, function(err, x) { + should.not.exist(err); + x.status.should.equal('pending'); + x.requiredRejections.should.equal(2); + x.requiredSignatures.should.equal(2); + clients[0].rejectTxProposal(x, 'no me gusto', function(err, tx) { + should.not.exist(err, err); + tx.status.should.equal('pending'); + clients[1].signTxProposal(x, function(err, tx) { + should.not.exist(err); + clients[2].signTxProposal(x, function(err, tx) { + should.not.exist(err); + tx.status.should.equal('broadcasted'); + tx.txid.should.equal((new Bitcore.Transaction(blockExplorerMock.lastBroadcasted)).id); + done(); + }); + }); + }); + }); + }); + }); + }); + + it('Send, reject in 3-4 wallet', function(done) { + helpers.createAndJoinWallet(clients, 3, 4, function(err, w) { + clients[0].createAddress(function(err, x0) { + should.not.exist(err); + should.exist(x0.address); + blockExplorerMock.setUtxo(x0, 10, 1); + var opts = { + amount: 10000, + toAddress: 'n2TBMPzPECGUfcT2EByiTJ12TPZkhN2mN5', + message: 'hola 1-1', + }; + clients[0].sendTxProposal(opts, function(err, x) { + should.not.exist(err); + x.status.should.equal('pending'); + x.requiredRejections.should.equal(2); + x.requiredSignatures.should.equal(3); + + clients[0].rejectTxProposal(x, 'no me gusto', function(err, tx) { + should.not.exist(err, err); + tx.status.should.equal('pending'); + clients[1].signTxProposal(x, function(err, tx) { + should.not.exist(err); + tx.status.should.equal('pending'); + clients[2].rejectTxProposal(x, 'tampoco me gusto', function(err, tx) { + should.not.exist(err); + tx.status.should.equal('rejected'); + done(); + }); + }); + }); + }); + }); + }); + }); + + }); From 733c22c0faa8473f68f91ad2974ae591cac985c2 Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Thu, 19 Feb 2015 18:15:47 -0300 Subject: [PATCH 7/7] fix tests --- lib/walletutils.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/walletutils.js b/lib/walletutils.js index 6707a63..78f4c71 100644 --- a/lib/walletutils.js +++ b/lib/walletutils.js @@ -32,7 +32,11 @@ WalletUtils.signMessage = function(text, privKey) { WalletUtils.verifyMessage = function(text, signature, pubKey) { - $.checkArgument(signature, text, pubKey); + $.checkArgument(text, pubKey); + + if (!signature) + return false; + var pub = new PublicKey(pubKey); var hash = WalletUtils.hashMessage(text);