diff --git a/integration/regtest-node.js b/integration/regtest-node.js index 873801d7..bc4909bb 100644 --- a/integration/regtest-node.js +++ b/integration/regtest-node.js @@ -69,17 +69,17 @@ describe('Node Functionality', function() { { name: 'db', module: DBService, - dependencies: DBService.dependencies + config: {} }, { name: 'bitcoind', module: BitcoinService, - dependencies: BitcoinService.dependencies + config: {} }, { name: 'address', module: AddressService, - dependencies: AddressService.dependencies + config: {} } ] }; diff --git a/integration/regtest.js b/integration/regtest.js index 59ca049b..ff1e47c4 100644 --- a/integration/regtest.js +++ b/integration/regtest.js @@ -227,7 +227,27 @@ describe('Daemon Binding Functionality', function() { work.cmp(expectedWork).should.equal(0); expectedWork = expectedWork.add(new BN(2)); should.exist(blockIndex.prevHash); + blockIndex.hash.should.equal(blockHashes[i]); blockIndex.prevHash.should.equal(blockHashes[i - 1]); + blockIndex.height.should.equal(i + 1); + }); + }); + }); + + describe('get block index by height', function() { + var expectedWork = new BN(6); + [2,3,4,5,6,7,8,9].forEach(function(i) { + it('generate block ' + i, function() { + var blockIndex = bitcoind.getBlockIndex(i); + should.exist(blockIndex); + should.exist(blockIndex.chainWork); + var work = new BN(blockIndex.chainWork, 'hex'); + work.cmp(expectedWork).should.equal(0); + expectedWork = expectedWork.add(new BN(2)); + should.exist(blockIndex.prevHash); + blockIndex.hash.should.equal(blockHashes[i - 1]); + blockIndex.prevHash.should.equal(blockHashes[i - 2]); + blockIndex.height.should.equal(i); }); }); }); diff --git a/lib/node.js b/lib/node.js index be6b8955..d697070b 100644 --- a/lib/node.js +++ b/lib/node.js @@ -6,10 +6,12 @@ var async = require('async'); var bitcore = require('bitcore'); var Networks = bitcore.Networks; var $ = bitcore.util.preconditions; +var _ = bitcore.deps._; var index = require('./'); var log = index.log; var Bus = require('./bus'); var BaseService = require('./service'); +var errors = require('./errors'); function Node(config) { if(!(this instanceof Node)) { @@ -18,6 +20,8 @@ function Node(config) { var self = this; + this.errors = errors; // So services can use errors without having to have bitcore-node as a dependency + this.log = log; this.network = null; this.services = {}; this._unloadedServices = []; @@ -30,6 +34,7 @@ function Node(config) { $.checkState(config.datadir, 'Node config expects "datadir"'); this.datadir = config.datadir; + this.port = config.port; this._setNetwork(config); @@ -116,7 +121,7 @@ Node.prototype.getServiceOrder = function() { $.checkState(service, 'Required dependency "' + name + '" not available.'); // first add the dependencies - addToStack(service.dependencies); + addToStack(service.module.dependencies); // add to the stack if it hasn't been added if(!stackNames[name]) { @@ -134,9 +139,13 @@ Node.prototype.getServiceOrder = function() { Node.prototype._instantiateService = function(service) { var self = this; - var mod = new service.module({ - node: this - }); + + $.checkState(_.isObject(service.config)); + $.checkState(!service.config.node); + + var config = service.config; + config.node = this; + var mod = new service.module(config); $.checkState( mod instanceof BaseService, diff --git a/lib/scaffold/default-config.js b/lib/scaffold/default-config.js index 979d110a..3bf92e58 100644 --- a/lib/scaffold/default-config.js +++ b/lib/scaffold/default-config.js @@ -13,7 +13,7 @@ function getDefaultConfig() { datadir: process.env.BITCORENODE_DIR || path.resolve(process.env.HOME, '.bitcoin'), network: process.env.BITCORENODE_NETWORK || 'livenet', port: process.env.BITCORENODE_PORT || 3001, - services: ['bitcoind', 'db', 'address'] + services: ['bitcoind', 'db', 'address', 'web'] } }; } diff --git a/lib/scaffold/start.js b/lib/scaffold/start.js index 38fca447..704a9ff5 100644 --- a/lib/scaffold/start.js +++ b/lib/scaffold/start.js @@ -6,6 +6,7 @@ var BitcoreNode = require('../node'); var index = require('../'); var bitcore = require('bitcore'); var _ = bitcore.deps._; +var $ = bitcore.util.preconditions; var log = index.log; log.debug = function() {}; @@ -22,37 +23,35 @@ function start(options) { if (config.services) { for (var i = 0; i < config.services.length; i++) { - var serviceName = config.services[i]; - var service; + var service = {}; + service.name = config.services[i]; + service.config = config.servicesConfig && config.servicesConfig[service.name] ? config.servicesConfig[service.name] : {}; + try { // first try in the built-in bitcore-node services directory - service = require(path.resolve(__dirname, '../services/' + serviceName)); + service.module = require(path.resolve(__dirname, '../services/' + service.name)); } catch(e) { // check if the package.json specifies a specific file to use - var servicePackage = require(serviceName + '/package.json'); - var serviceModule = serviceName; + var servicePackage = require(service.name + '/package.json'); + var serviceModule = service.name; if (servicePackage.bitcoreNode) { - serviceModule = serviceName + '/' + servicePackage.bitcoreNode; + serviceModule = service.name + '/' + servicePackage.bitcoreNode; } - service = require(serviceModule); + service.module = require(serviceModule); } // check that the service supports expected methods - if (!service.prototype || - !service.dependencies || - !service.prototype.start || - !service.prototype.stop) { + if (!service.module.prototype || + !service.module.dependencies || + !service.module.prototype.start || + !service.module.prototype.stop) { throw new Error( - 'Could not load service "' + serviceName + '" as it does not support necessary methods.' + 'Could not load service "' + service.name + '" as it does not support necessary methods.' ); } - services.push({ - name: serviceName, - module: service, - dependencies: service.dependencies - }); + services.push(service); } } @@ -82,102 +81,7 @@ function start(options) { }); node.on('ready', function() { - - var io = socketio(fullConfig.port); - - io.on('connection', function(socket) { - - var bus = node.openBus(); - - var methods = node.getAllAPIMethods(); - var methodsMap = {}; - - methods.forEach(function(data) { - var name = data[0]; - var instance = data[1]; - var method = data[2]; - var args = data[3]; - methodsMap[name] = { - fn: function() { - return method.apply(instance, arguments); - }, - args: args - }; - }); - - socket.on('message', function(message, socketCallback) { - if (methodsMap[message.method]) { - var params = message.params; - - if(!params || !params.length) { - params = []; - } - - if(params.length !== methodsMap[message.method].args) { - return socketCallback({ - error: { - message: 'Expected ' + methodsMap[message.method].args + ' parameters' - } - }); - } - - var callback = function(err, result) { - var response = {}; - if(err) { - response.error = { - message: err.toString() - }; - } - - if(result) { - response.result = result; - } - - socketCallback(response); - }; - - params = params.concat(callback); - methodsMap[message.method].fn.apply(this, params); - } else { - socketCallback({ - error: { - message: 'Method Not Found' - } - }); - } - }); - - socket.on('subscribe', function(name, params) { - bus.subscribe(name, params); - }); - - socket.on('unsubscribe', function(name, params) { - bus.unsubscribe(name, params); - }); - - var events = node.getAllPublishEvents(); - - events.forEach(function(event) { - bus.on(event.name, function() { - if(socket.connected) { - var results = []; - - for(var i = 0; i < arguments.length; i++) { - results.push(arguments[i]); - } - - var params = [event.name].concat(results); - socket.emit.apply(socket, params); - } - }); - }); - - socket.on('disconnect', function() { - bus.close(); - }); - - }); - + log.info('Bitcore Node ready'); }); node.on('error', function(err) { @@ -207,7 +111,12 @@ function start(options) { if(err.stack) { console.log(err.stack); } - process.exit(-1); + node.stop(function(err) { + if(err) { + log.error('Failed to stop services: ' + err); + } + process.exit(-1); + }); } if (options.sigint) { node.stop(function(err) { diff --git a/lib/service.js b/lib/service.js index 4407ff4a..97fed380 100644 --- a/lib/service.js +++ b/lib/service.js @@ -24,7 +24,9 @@ Service.dependencies = []; */ Service.prototype.blockHandler = function(block, add, callback) { // implement in the child class - setImmediate(callback); + setImmediate(function() { + callback(null, []); + }); }; /** @@ -57,12 +59,26 @@ Service.prototype.getAPIMethods = function() { // // }; +/** + * Function which is called when module is first initialized + */ Service.prototype.start = function(done) { setImmediate(done); }; +/** + * Function to be called when bitcore-node is stopped + */ Service.prototype.stop = function(done) { setImmediate(done); }; +/** + * Setup express routes + * @param {Express} app + */ +Service.prototype.setupRoutes = function(app) { + // Setup express routes here +}; + module.exports = Service; diff --git a/lib/services/address.js b/lib/services/address.js index a254016b..f168a1d3 100644 --- a/lib/services/address.js +++ b/lib/services/address.js @@ -505,6 +505,7 @@ AddressService.prototype.getAddressHistoryForAddress = function(address, queryMe var confirmations = 0; if(transaction.__height >= 0) { confirmations = self.node.services.db.tip.__height - transaction.__height; + confirmations = self.node.services.db.tip.__height - transaction.__height + 1; } txinfos[transaction.hash] = { diff --git a/lib/services/db.js b/lib/services/db.js index 582b6088..5762038e 100644 --- a/lib/services/db.js +++ b/lib/services/db.js @@ -217,6 +217,7 @@ DB.prototype.getTransactionWithBlockInfo = function(txid, queryMempool, callback } var tx = Transaction().fromBuffer(obj.buffer); + tx.__blockHash = obj.blockHash; tx.__height = obj.height; tx.__timestamp = obj.timestamp; @@ -657,6 +658,7 @@ DB.prototype.sync = function() { self.bitcoindSyncing = false; self.lastSavedMetadataThreshold = 0; + self.saveMetadata(); // If bitcoind is completely synced if (self.node.services.bitcoind.isSynced()) { diff --git a/lib/services/web.js b/lib/services/web.js new file mode 100644 index 00000000..63fac376 --- /dev/null +++ b/lib/services/web.js @@ -0,0 +1,155 @@ +'use strict'; + +var http = require('http'); +var express = require('express'); +var bodyParser = require('body-parser'); +var socketio = require('socket.io'); +var BaseService = require('../service'); +var inherits = require('util').inherits; + +var WebService = function(options) { + var self = this; + this.node = options.node; + this.port = options.port || this.node.port || 3456; + + this.node.on('ready', function() { + self.setupAllRoutes(); + self.server.listen(self.port); + self.createMethodsMap(); + }); +}; + +inherits(WebService, BaseService); + +WebService.dependencies = []; + +WebService.prototype.start = function(callback) { + var self = this; + this.app = express(); + this.app.use(bodyParser.json()); + + this.server = http.createServer(this.app); + + this.io = socketio.listen(this.server); + this.io.on('connection', this.socketHandler.bind(this)); + + setImmediate(callback); +}; + +WebService.prototype.stop = function(callback) { + var self = this; + + setImmediate(function() { + if(self.server) { + self.server.close(); + } + + callback(); + }) +}; + +WebService.prototype.setupAllRoutes = function() { + for(var key in this.node.services) { + this.node.services[key].setupRoutes(this.app); + } +}; + +WebService.prototype.createMethodsMap = function() { + var self = this; + var methods = this.node.getAllAPIMethods(); + this.methodsMap = {}; + + methods.forEach(function(data) { + var name = data[0]; + var instance = data[1]; + var method = data[2]; + var args = data[3]; + self.methodsMap[name] = { + fn: function() { + return method.apply(instance, arguments); + }, + args: args + }; + }); +} + +WebService.prototype.socketHandler = function(socket) { + var self = this; + + var bus = this.node.openBus(); + + socket.on('message', this.socketMessageHandler.bind(this)); + + socket.on('subscribe', function(name, params) { + bus.subscribe(name, params); + }); + + socket.on('unsubscribe', function(name, params) { + bus.unsubscribe(name, params); + }); + + var events = self.node.getAllPublishEvents(); + + events.forEach(function(event) { + bus.on(event.name, function() { + if(socket.connected) { + var results = []; + + for(var i = 0; i < arguments.length; i++) { + results.push(arguments[i]); + } + + var params = [event.name].concat(results); + socket.emit.apply(socket, params); + } + }); + }); + + socket.on('disconnect', function() { + bus.close(); + }); +}; + +WebService.prototype.socketMessageHandler = function(message, socketCallback) { + if (this.methodsMap[message.method]) { + var params = message.params; + + if(!params || !params.length) { + params = []; + } + + if(params.length !== this.methodsMap[message.method].args) { + return socketCallback({ + error: { + message: 'Expected ' + this.methodsMap[message.method].args + ' parameter(s)' + } + }); + } + + var callback = function(err, result) { + var response = {}; + if(err) { + response.error = { + message: err.toString() + }; + } + + if(result) { + response.result = result; + } + + socketCallback(response); + }; + + params = params.concat(callback); + this.methodsMap[message.method].fn.apply(this, params); + } else { + socketCallback({ + error: { + message: 'Method Not Found' + } + }); + } +}; + +module.exports = WebService; \ No newline at end of file diff --git a/package.json b/package.json index 3a0b4253..83c382a9 100644 --- a/package.json +++ b/package.json @@ -48,11 +48,13 @@ "bindings": "^1.2.1", "bitcore": "^0.13.0", "colors": "^1.1.2", + "body-parser": "^1.13.3", "commander": "^2.8.1", "errno": "^0.1.4", "leveldown": "^1.4.1", "levelup": "^1.2.1", "liftoff": "^2.1.0", + "express": "^4.13.3", "memdown": "^1.0.0", "mkdirp": "0.5.0", "nan": "1.3.0", diff --git a/src/libbitcoind.cc b/src/libbitcoind.cc index 045c57e3..1c6fc879 100644 --- a/src/libbitcoind.cc +++ b/src/libbitcoind.cc @@ -165,7 +165,7 @@ struct async_block_data { struct async_tx_data { std::string err_msg; std::string txid; - std::string blockhash; + std::string blockHash; uint32_t nTime; int64_t height; bool queryMempool; @@ -1235,6 +1235,7 @@ async_get_tx_and_info(uv_work_t *req) { // Read header first to get block timestamp and hash file >> blockHeader; blockHash = blockHeader.GetHash(); + data->blockHash = blockHash.GetHex(); data->nTime = blockHeader.nTime; fseek(file.Get(), postx.nTxOffset, SEEK_CUR); file >> ctx; @@ -1284,6 +1285,7 @@ async_get_tx_and_info_after(uv_work_t *req) { std::string stx = ssTx.str(); Local rawNodeBuffer = node::Buffer::New(isolate, stx.c_str(), stx.size()); + obj->Set(NanNew("blockHash"), NanNew(data->blockHash)); obj->Set(NanNew("height"), NanNew(data->height)); obj->Set(NanNew("timestamp"), NanNew(data->nTime)); obj->Set(NanNew("buffer"), rawNodeBuffer); @@ -1349,28 +1351,38 @@ NAN_METHOD(GetBlockIndex) { Isolate* isolate = Isolate::GetCurrent(); HandleScope scope(isolate); - String::Utf8Value hash_(args[0]->ToString()); - std::string hashStr = std::string(*hash_); - uint256 hash = uint256S(hashStr); - CBlockIndex* blockIndex; - if (mapBlockIndex.count(hash) == 0) { - NanReturnValue(Undefined(isolate)); + if (args[0]->IsNumber()) { + int64_t height = args[0]->IntegerValue(); + blockIndex = chainActive[height]; + + if (blockIndex == NULL) { + NanReturnValue(Undefined(isolate)); + } + } else { - blockIndex = mapBlockIndex[hash]; - arith_uint256 cw = blockIndex->nChainWork; - CBlockIndex* prevBlockIndex = blockIndex->pprev; - const uint256* prevHash = prevBlockIndex->phashBlock; - - Local obj = NanNew(); - - obj->Set(NanNew("chainWork"), NanNew(cw.GetHex())); - obj->Set(NanNew("prevHash"), NanNew(prevHash->GetHex())); - - NanReturnValue(obj); + String::Utf8Value hash_(args[0]->ToString()); + std::string hashStr = std::string(*hash_); + uint256 hash = uint256S(hashStr); + if (mapBlockIndex.count(hash) == 0) { + NanReturnValue(Undefined(isolate)); + } else { + blockIndex = mapBlockIndex[hash]; + } } + arith_uint256 cw = blockIndex->nChainWork; + CBlockIndex* prevBlockIndex = blockIndex->pprev; + const uint256* prevHash = prevBlockIndex->phashBlock; + + Local obj = NanNew(); + obj->Set(NanNew("hash"), NanNew(blockIndex->phashBlock->GetHex())); + obj->Set(NanNew("chainWork"), NanNew(cw.GetHex())); + obj->Set(NanNew("prevHash"), NanNew(prevHash->GetHex())); + obj->Set(NanNew("height"), NanNew(blockIndex->nHeight)); + + NanReturnValue(obj); }; /** diff --git a/test/node.unit.js b/test/node.unit.js index 60a346cd..19e1f2e6 100644 --- a/test/node.unit.js +++ b/test/node.unit.js @@ -183,19 +183,27 @@ describe('Bitcore Node', function() { node._unloadedServices = [ { name: 'chain', - dependencies: ['db'] + module: { + dependencies: ['db'] + } }, { name: 'db', + module: { dependencies: ['daemon', 'p2p'] + } }, { name:'daemon', - dependencies: [] + module: { + dependencies: [] + } }, { name: 'p2p', - dependencies: [] + module: { + dependencies: [] + } } ]; var order = node.getServiceOrder(); @@ -219,7 +227,8 @@ describe('Bitcore Node', function() { }; var service = { name: 'testservice', - module: TestService + module: TestService, + config: {} }; node._instantiateService(service); should.exist(node.services.testservice); @@ -254,11 +263,13 @@ describe('Bitcore Node', function() { node.getServiceOrder = sinon.stub().returns([ { name: 'test1', - module: TestService + module: TestService, + config: {} }, { name: 'test2', - module: TestService2 + module: TestService2, + config: {} } ]); node.start(function() { @@ -295,11 +306,13 @@ describe('Bitcore Node', function() { node.getServiceOrder = sinon.stub().returns([ { name: 'test', - module: TestService + module: TestService, + config: {} }, { name: 'conflict', - module: ConflictService + module: ConflictService, + config: {} } ]); diff --git a/test/scaffold/start.integration.js b/test/scaffold/start.integration.js index 228af308..e6985068 100644 --- a/test/scaffold/start.integration.js +++ b/test/scaffold/start.integration.js @@ -9,13 +9,13 @@ describe('#start', function() { describe('will dynamically create a node from a configuration', function() { - it('require each bitcore-node service', function(done) { + it('require each bitcore-node service with default config', function(done) { var node; var TestNode = function(options) { options.services[0].should.deep.equal({ name: 'address', module: AddressService, - dependencies: ['bitcoind', 'db'] + config: {} }); }; TestNode.prototype.on = sinon.stub(); @@ -39,5 +39,43 @@ describe('#start', function() { node.should.be.instanceof(TestNode); done(); }); + + it('require each bitcore-node service with explicit config', function(done) { + var node; + var TestNode = function(options) { + options.services[0].should.deep.equal({ + name: 'address', + module: AddressService, + config: { + param: 'test' + } + }); + }; + TestNode.prototype.on = sinon.stub(); + TestNode.prototype.chain = { + on: sinon.stub() + }; + + var starttest = proxyquire('../../lib/scaffold/start', { + '../node': TestNode + }); + + node = starttest({ + path: __dirname, + config: { + services: [ + 'address' + ], + servicesConfig: { + 'address': { + param: 'test' + } + }, + datadir: './data' + } + }); + node.should.be.instanceof(TestNode); + done(); + }); }); }); diff --git a/test/services/db.unit.js b/test/services/db.unit.js index 340a79ca..4bfd2388 100644 --- a/test/services/db.unit.js +++ b/test/services/db.unit.js @@ -398,6 +398,7 @@ describe('DB Service', function() { it('should give a transaction with height and timestamp', function(done) { var txBuffer = new Buffer('01000000016f95980911e01c2c664b3e78299527a47933aac61a515930a8fe0213d1ac9abe01000000da0047304402200e71cda1f71e087c018759ba3427eb968a9ea0b1decd24147f91544629b17b4f0220555ee111ed0fc0f751ffebf097bdf40da0154466eb044e72b6b3dcd5f06807fa01483045022100c86d6c8b417bff6cc3bbf4854c16bba0aaca957e8f73e19f37216e2b06bb7bf802205a37be2f57a83a1b5a8cc511dc61466c11e9ba053c363302e7b99674be6a49fc0147522102632178d046673c9729d828cfee388e121f497707f810c131e0d3fc0fe0bd66d62103a0951ec7d3a9da9de171617026442fcd30f34d66100fab539853b43f508787d452aeffffffff0240420f000000000017a9148a31d53a448c18996e81ce67811e5fb7da21e4468738c9d6f90000000017a9148ce5408cfeaddb7ccb2545ded41ef478109454848700000000', 'hex'); var info = { + blockHash: '00000000000ec715852ea2ecae4dc8563f62d603c820f81ac284cd5be0a944d6', height: 530482, timestamp: 1439559434000, buffer: txBuffer @@ -412,6 +413,7 @@ describe('DB Service', function() { db.getTransactionWithBlockInfo('2d950d00494caf6bfc5fff2a3f839f0eb50f663ae85ce092bc5f9d45296ae91f', true, function(err, tx) { should.not.exist(err); + tx.__blockHash.should.equal(info.blockHash); tx.__height.should.equal(info.height); tx.__timestamp.should.equal(info.timestamp); done(); diff --git a/test/services/web.unit.js b/test/services/web.unit.js new file mode 100644 index 00000000..2db7aad6 --- /dev/null +++ b/test/services/web.unit.js @@ -0,0 +1,239 @@ +'use strict'; + +var should = require('chai').should(); +var sinon = require('sinon'); +var WebService = require('../../lib/services/web'); +var EventEmitter = require('events').EventEmitter; + +describe('WebService', function() { + var defaultNode = new EventEmitter(); + + describe('#start', function() { + it('should call the callback with no error', function(done) { + var web = new WebService({node: defaultNode}); + web.start(function(err) { + should.not.exist(err); + done(); + }); + }); + }); + + describe('#stop', function() { + it('should close the server if it exists', function(done) { + var web = new WebService({node: defaultNode}); + web.server = { + close: sinon.spy() + }; + + web.stop(function(err) { + should.not.exist(err); + web.server.close.callCount.should.equal(1); + done(); + }); + }); + }); + + describe('#setupAllRoutes', function() { + it('should call setupRoutes on each module', function() { + var node = { + on: sinon.spy(), + services: { + one: { + setupRoutes: sinon.spy() + }, + two: { + setupRoutes: sinon.spy() + } + } + }; + + var web = new WebService({node: node}); + + web.setupAllRoutes(); + node.services.one.setupRoutes.callCount.should.equal(1); + node.services.two.setupRoutes.callCount.should.equal(1); + }); + }); + + describe('#createMethodsMap', function() { + it('should create the methodsMap correctly', function(done) { + var Module1 = function() {}; + Module1.prototype.getAPIMethods = function() { + return [ + ['one', this, this.one, 1], + ['two', this, this.two, 2] + ]; + }; + Module1.prototype.one = function(param1, callback) { + callback(null, param1); + }; + Module1.prototype.two = function(param1, param2, callback) { + callback(null, param1 + param2); + }; + + var module1 = new Module1(); + + var node = { + on: sinon.spy(), + getAllAPIMethods: sinon.stub().returns(module1.getAPIMethods()) + }; + + var web = new WebService({node: node}); + web.createMethodsMap(); + + Object.keys(web.methodsMap).length.should.equal(2); + web.methodsMap.one.args.should.equal(1); + web.methodsMap.two.args.should.equal(2); + web.methodsMap.one.fn(1, function(err, result) { + should.not.exist(err); + result.should.equal(1); + + web.methodsMap.two.fn(1, 2, function(err, result) { + should.not.exist(err); + result.should.equal(3); + done(); + }); + }); + }); + }); + + describe('#socketHandler', function() { + var bus = new EventEmitter(); + + var Module1 = function() {}; + Module1.prototype.getPublishEvents = function() { + return [ + { + name: 'event1' + } + ]; + }; + + var module1 = new Module1(); + var node = { + on: sinon.spy(), + openBus: sinon.stub().returns(bus), + getAllPublishEvents: sinon.stub().returns(module1.getPublishEvents()) + }; + + var web; + var socket; + + it('on message should call socketMessageHandler', function(done) { + web = new WebService({node: node}); + web.socketMessageHandler = function(param1) { + param1.should.equal('data'); + done(); + }; + socket = new EventEmitter(); + web.socketHandler(socket); + socket.emit('message', 'data'); + }); + + it('on subscribe should call bus.subscribe', function(done) { + bus.subscribe = function(param1) { + param1.should.equal('data'); + done(); + }; + + socket.emit('subscribe', 'data'); + }); + + it('on unsubscribe should call bus.unsubscribe', function(done) { + bus.unsubscribe = function(param1) { + param1.should.equal('data'); + done(); + }; + + socket.emit('unsubscribe', 'data'); + }); + + it('publish events from bus should be emitted from socket', function(done) { + socket.once('event1', function(param1, param2) { + param1.should.equal('param1'); + param2.should.equal('param2'); + done(); + }); + socket.connected = true; + bus.emit('event1', 'param1', 'param2'); + }); + + it('on disconnect should close bus', function(done) { + bus.close = function() { + done(); + }; + + socket.emit('disconnect'); + }); + }); + + describe('#socketMessageHandler', function() { + var node = { + on: sinon.spy() + }; + + var web = new WebService({node: node}); + web.methodsMap = { + one: { + fn: function(param1, param2, callback) { + var result = param1 + param2; + if(result > 0) { + return callback(null, result); + } else { + return callback(new Error('error')); + } + }, + args: 2 + } + }; + + it('should give a Method Not Found error if method does not exist', function(done) { + var message = { + method: 'two', + params: [1, 2] + } + web.socketMessageHandler(message, function(response) { + should.exist(response.error); + response.error.message.should.equal('Method Not Found'); + done(); + }); + }); + + it('should call the method and return the result', function(done) { + var message = { + method: 'one', + params: [1, 2] + }; + web.socketMessageHandler(message, function(response) { + should.not.exist(response.error); + response.result.should.equal(3); + done(); + }); + }); + + it('should give an error if there is a param count mismatch', function(done) { + var message = { + method: 'one', + params: [1] + }; + web.socketMessageHandler(message, function(response) { + should.exist(response.error); + response.error.message.should.equal('Expected 2 parameter(s)'); + done(); + }); + }); + + it('should give an error if the method gave an error', function(done) { + var message = { + method: 'one', + params: [-1, -2] + }; + web.socketMessageHandler(message, function(response) { + should.exist(response.error); + response.error.message.should.equal('Error: error'); + done(); + }); + }); + }); + +}); \ No newline at end of file