diff --git a/bitcorenode/index.js b/bitcorenode/index.js index 6073022..5747c6f 100644 --- a/bitcorenode/index.js +++ b/bitcorenode/index.js @@ -9,7 +9,6 @@ var async = require('async'); var path = require('path'); var bitcore = require('bitcore'); var Networks = bitcore.Networks; -var mkdirp = require('mkdirp'); var Locker = require('locker-server'); var BlockchainMonitor = require('../lib/blockchainmonitor'); var EmailService = require('../lib/emailservice'); @@ -20,6 +19,15 @@ var spawn = child_process.spawn; var EventEmitter = require('events').EventEmitter; var baseConfig = require('../config'); +/** + * A Bitcore Node Service module + * @param {Object} options + * @param {Node} options.node - A reference to the Bitcore Node instance +-* @param {Boolean} options.https - Enable https for this module, defaults to node settings. + * @param {Number} options.bwsPort - Port for Bitcore Wallet Service API + * @param {Number} options.messageBrokerPort - Port for BWS message broker + * @param {Number} options.lockerPort - Port for BWS locker port + */ var Service = function(options) { EventEmitter.call(this); @@ -42,14 +50,15 @@ Service.dependencies = ['insight-api']; /** * This method will read `key` and `cert` files from disk based on `httpsOptions` and * return `serverOpts` with the read files. + * @returns {Object} */ -Service.prototype.readHttpsOptions = function() { +Service.prototype._readHttpsOptions = function() { if(!this.httpsOptions || !this.httpsOptions.key || !this.httpsOptions.cert) { throw new Error('Missing https options'); } var serverOpts = {}; - serverOpts.key = fs.readFileSync(this.httpOptions.key); + serverOpts.key = fs.readFileSync(this.httpsOptions.key); serverOpts.cert = fs.readFileSync(this.httpsOptions.cert); // This sets the intermediate CA certs only if they have all been designated in the config.js @@ -64,11 +73,13 @@ Service.prototype.readHttpsOptions = function() { }; /** - * Called by the node to start the service + * Will get the configuration with settings for the locally + * running Insight API. + * @returns {Object} */ -Service.prototype.start = function(done) { - +Service.prototype._getConfiguration = function() { var self = this; + var providerOptions = { provider: 'insight', url: 'http://localhost:' + self.node.port, @@ -87,61 +98,85 @@ Service.prototype.start = function(done) { testnet: providerOptions }; } else { - return done(new Error('Unknown network')); + throw new Error('Unknown network'); } + return baseConfig; + +}; + +/** + * Will start the HTTP web server and socket.io for the wallet service. + */ +Service.prototype._startWalletService = function(config, next) { + var self = this; + var expressApp = new ExpressApp(); + var wsApp = new WsApp(); + + if (self.https) { + var serverOpts = self._readHttpsOptions(); + self.server = https.createServer(serverOpts, expressApp.app); + } else { + self.server = http.Server(expressApp.app); + } + + async.parallel([ + function(done) { + expressApp.start(config, done); + }, + function(done) { + wsApp.start(self.server, config, done); + }, + ], function(err) { + if (err) { + return next(err); + } + self.server.listen(self.bwsPort, next); + }); +}; + +/** + * Called by the node to start the service + */ +Service.prototype.start = function(done) { + + var self = this; + var config; + try { + config = self._getConfiguration(); + } catch(err) { + return done(err); + } + + // Locker Server + var locker = new Locker(); + locker.listen(self.lockerPort); + + // Message Broker + var messageServer = io(self.messageBrokerPort); + messageServer.on('connection', function(s) { + s.on('msg', function(d) { + messageServer.emit('msg', d); + }); + }); + async.series([ function(next) { - // Locker Server - var locker = new Locker(); - locker.listen(self.lockerPort); - - // Message Broker - var messageServer = io(self.messageBrokerPort); - messageServer.on('connection', function(s) { - s.on('msg', function(d) { - messageServer.emit('msg', d); - }); - }); - // Blockchain Monitor var blockChainMonitor = new BlockchainMonitor(); - blockChainMonitor.start(baseConfig, next); + blockChainMonitor.start(config, next); }, function(next) { - if (baseConfig.emailOpts) { + // Email Service + if (config.emailOpts) { var emailService = new EmailService(); - emailService.start(baseConfig, next); + emailService.start(config, next); } else { setImmediate(next); } }, function(next) { - - var expressApp = new ExpressApp(); - var wsApp = new WsApp(); - - if (self.https) { - var serverOpts = self.readHttpsOptions(); - self.server = https.createServer(serverOpts, expressApp.app); - } else { - self.server = http.Server(expressApp.app); - } - - async.parallel([ - function(done) { - expressApp.start(baseConfig, done); - }, - function(done) { - wsApp.start(self.server, baseConfig, done); - }, - ], function(err) { - if (err) { - return next(err); - } - self.server.listen(self.bwsPort, next); - }); - + self._startWalletService(config, next); } ], done); diff --git a/test/bitcorenode.js b/test/bitcorenode.js new file mode 100644 index 0000000..4bff6cb --- /dev/null +++ b/test/bitcorenode.js @@ -0,0 +1,380 @@ +'use strict'; + +var should = require('chai').should(); +var proxyquire = require('proxyquire'); +var bitcore = require('bitcore'); +var sinon = require('sinon'); +var Service = require('../bitcorenode'); + +describe('Bitcore Node Service', function() { + describe('#constructor', function() { + it('https settings from node', function() { + var node = { + https: true, + httpsOptions: { + key: 'key', + cert: 'cert' + } + }; + var options = { + node: node + }; + var service = new Service(options); + service.node.should.equal(node); + service.https.should.equal(true); + service.httpsOptions.should.deep.equal({ + key: 'key', + cert: 'cert' + }); + service.bwsPort.should.equal(Service.BWS_PORT); + service.messageBrokerPort.should.equal(Service.MESSAGE_BROKER_PORT); + service.lockerPort.should.equal(Service.LOCKER_PORT); + }); + it('direct https options', function() { + var node = {}; + var options = { + node: node, + https: true, + httpsOptions: { + key: 'key', + cert: 'cert' + } + }; + var service = new Service(options); + service.https.should.equal(true); + service.httpsOptions.should.deep.equal({ + key: 'key', + cert: 'cert' + }); + service.bwsPort.should.equal(Service.BWS_PORT); + service.messageBrokerPort.should.equal(Service.MESSAGE_BROKER_PORT); + service.lockerPort.should.equal(Service.LOCKER_PORT); + }); + it('can set custom ports', function() { + var node = {}; + var options = { + node: node, + bwsPort: 1000, + messageBrokerPort: 1001, + lockerPort: 1002 + }; + var service = new Service(options); + service.bwsPort.should.equal(1000); + service.messageBrokerPort.should.equal(1001); + service.lockerPort.should.equal(1002); + }); + }); + describe('#readHttpsOptions', function() { + var TestService = proxyquire('../bitcorenode', { + fs: { + readFileSync: function(arg) { + return arg; + } + } + }); + it('will create server options from httpsOptions', function() { + var options = { + node: { + https: true, + httpsOptions: { + key: 'key', + cert: 'cert', + CAinter1: 'CAinter1', + CAinter2: 'CAinter2', + CAroot: 'CAroot' + } + } + }; + var service = new TestService(options); + var serverOptions = service._readHttpsOptions(); + serverOptions.key.should.equal('key'); + serverOptions.cert.should.equal('cert'); + serverOptions.ca[0].should.equal('CAinter1'); + serverOptions.ca[1].should.equal('CAinter2'); + serverOptions.ca[2].should.equal('CAroot'); + }); + }); + describe('#_getConfiguration', function() { + it('will throw with an unknown network', function() { + var options = { + node: { + network: 'unknown' + } + }; + var service = new Service(options); + (function() { + service._getConfiguration(); + }).should.throw('Unknown network'); + }); + it('livenet local insight', function() { + var options = { + node: { + network: bitcore.Networks.livenet, + port: 3001 + } + }; + var service = new Service(options); + var config = service._getConfiguration(); + config.blockchainExplorerOpts.livenet.should.deep.equal({ + 'apiPrefix': '/insight-api', + 'provider': 'insight', + 'url': 'http://localhost:3001' + }); + }); + it('testnet local insight', function() { + var options = { + node: { + network: bitcore.Networks.testnet, + port: 3001 + } + }; + var service = new Service(options); + var config = service._getConfiguration(); + config.blockchainExplorerOpts.testnet.should.deep.equal({ + 'apiPrefix': '/insight-api', + 'provider': 'insight', + 'url': 'http://localhost:3001' + }); + }); + }); + describe('#_startWalletService', function() { + it('will start express and web socket servers', function(done) { + function TestExpressApp() {} + TestExpressApp.prototype.start = sinon.stub().callsArg(1); + function TestWSApp() {} + TestWSApp.prototype.start = sinon.stub().callsArg(2); + var listen = sinon.stub().callsArg(1); + var TestService = proxyquire('../bitcorenode', { + '../lib/expressapp': TestExpressApp, + '../lib/wsapp': TestWSApp, + 'http': { + Server: sinon.stub().returns({ + listen: listen + }) + } + }); + var options = { + node: { + bwsPort: 3232 + } + }; + var service = new TestService(options); + var config = {}; + service._startWalletService(config, function(err) { + if (err) { + throw err; + } + TestExpressApp.prototype.start.callCount.should.equal(1); + TestExpressApp.prototype.start.args[0][0].should.equal(config); + TestExpressApp.prototype.start.args[0][1].should.be.a('function'); + TestWSApp.prototype.start.callCount.should.equal(1); + TestWSApp.prototype.start.args[0][0].should.equal(service.server); + TestWSApp.prototype.start.args[0][1].should.equal(config); + TestWSApp.prototype.start.args[0][2].should.be.a('function'); + listen.callCount.should.equal(1); + listen.args[0][0].should.equal(3232); + listen.args[0][1].should.be.a('function'); + done(); + }); + }); + it('error from express', function(done) { + function TestExpressApp() {} + TestExpressApp.prototype.start = sinon.stub().callsArgWith(1, new Error('test')); + function TestWSApp() {} + TestWSApp.prototype.start = sinon.stub().callsArg(2); + var listen = sinon.stub().callsArg(1); + var TestService = proxyquire('../bitcorenode', { + '../lib/expressapp': TestExpressApp, + '../lib/wsapp': TestWSApp, + 'http': { + Server: sinon.stub().returns({ + listen: listen + }) + } + }); + var options = { + node: { + bwsPort: 3232 + } + }; + var service = new TestService(options); + var config = {}; + service._startWalletService(config, function(err) { + err.message.should.equal('test'); + done(); + }); + }); + it('error from web socket', function(done) { + function TestExpressApp() {} + TestExpressApp.prototype.start = sinon.stub().callsArg(1); + function TestWSApp() {} + TestWSApp.prototype.start = sinon.stub().callsArgWith(2, new Error('test')); + var listen = sinon.stub().callsArg(1); + var TestService = proxyquire('../bitcorenode', { + '../lib/expressapp': TestExpressApp, + '../lib/wsapp': TestWSApp, + 'http': { + Server: sinon.stub().returns({ + listen: listen + }) + } + }); + var options = { + node: { + bwsPort: 3232 + } + }; + var service = new TestService(options); + var config = {}; + service._startWalletService(config, function(err) { + err.message.should.equal('test'); + done(); + }); + }); + it('error from server.listen', function(done) { + var app = {}; + function TestExpressApp() { + this.app = app; + } + TestExpressApp.prototype.start = sinon.stub().callsArg(1); + function TestWSApp() {} + TestWSApp.prototype.start = sinon.stub().callsArg(2); + var listen = sinon.stub().callsArgWith(1, new Error('test')); + var TestService = proxyquire('../bitcorenode', { + '../lib/expressapp': TestExpressApp, + '../lib/wsapp': TestWSApp, + 'http': { + Server: function() { + arguments[0].should.equal(app); + return { + listen: listen + }; + } + } + }); + var options = { + node: { + bwsPort: 3232 + } + }; + var service = new TestService(options); + var config = {}; + service._startWalletService(config, function(err) { + err.message.should.equal('test'); + done(); + }); + }); + it('will enable https', function(done) { + var app = {}; + function TestExpressApp() { + this.app = app; + } + TestExpressApp.prototype.start = sinon.stub().callsArg(1); + function TestWSApp() {} + TestWSApp.prototype.start = sinon.stub().callsArg(2); + var listen = sinon.stub().callsArg(1); + var httpsOptions = {}; + var createServer = function() { + arguments[0].should.equal(httpsOptions); + arguments[1].should.equal(app); + return { + listen: listen + }; + }; + var TestService = proxyquire('../bitcorenode', { + '../lib/expressapp': TestExpressApp, + '../lib/wsapp': TestWSApp, + 'https': { + createServer: createServer + } + }); + var options = { + node: { + https: true, + bwsPort: 3232 + } + }; + var service = new TestService(options); + service._readHttpsOptions = sinon.stub().returns(httpsOptions); + var config = {}; + service._startWalletService(config, function(err) { + service._readHttpsOptions.callCount.should.equal(1); + listen.callCount.should.equal(1); + done(); + }); + }); + }); + describe('#start', function(done) { + it('error from configuration', function(done) { + var options = { + node: {} + }; + var service = new Service(options); + service._getConfiguration = function() { + throw new Error('test'); + }; + service.start(function(err) { + err.message.should.equal('test'); + done(); + }); + }); + it('error from blockchain monitor', function(done) { + var app = {}; + function TestBlockchainMonitor() {} + TestBlockchainMonitor.prototype.start = sinon.stub().callsArgWith(1, new Error('test')); + function TestLocker() {} + TestLocker.prototype.listen = sinon.stub(); + function TestEmailService() {} + TestEmailService.prototype.start = sinon.stub(); + var TestService = proxyquire('../bitcorenode', { + '../lib/blockchainmonitor': TestBlockchainMonitor, + '../lib/emailservice': TestEmailService, + 'socket.io': sinon.stub().returns({ + on: sinon.stub() + }), + 'locker-server': TestLocker, + }); + var options = { + node: {} + }; + var service = new TestService(options); + var config = {}; + service._getConfiguration = sinon.stub().returns(config); + service._startWalletService = sinon.stub().callsArg(1); + service.start(function(err) { + err.message.should.equal('test'); + done(); + }); + }); + it('error from email service', function(done) { + var app = {}; + function TestBlockchainMonitor() {} + TestBlockchainMonitor.prototype.start = sinon.stub().callsArg(1); + function TestLocker() {} + TestLocker.prototype.listen = sinon.stub(); + function TestEmailService() {} + TestEmailService.prototype.start = sinon.stub().callsArgWith(1, new Error('test')); + var TestService = proxyquire('../bitcorenode', { + '../lib/blockchainmonitor': TestBlockchainMonitor, + '../lib/emailservice': TestEmailService, + 'socket.io': sinon.stub().returns({ + on: sinon.stub() + }), + 'locker-server': TestLocker, + }); + var options = { + node: {} + }; + var service = new TestService(options); + service._getConfiguration = sinon.stub().returns({ + emailOpts: {} + }); + var config = {}; + service._startWalletService = sinon.stub().callsArg(1); + service.start(function(err) { + err.message.should.equal('test'); + done(); + }); + }); + }); +});