UNPKG

zmq-service-suite-service

Version:
930 lines (797 loc) 26.4 kB
describe("ZSSService", function(){ var ZSSService = require('../../service'); var zmq = require('zeromq'); var uuid = require('uuid'); var _ = require('lodash'); var msgpack = require('msgpack-js'); var Message = require('zmq-service-suite-message'); var Logger = require('logger-facade-nodejs'); var errors = require('../../config/errors'); var config = { sid: 'TEST-ZMQ', broker: "ipc://tmp/test/service-js", heartbeat: 10000 }; var IDENTITY_FRAME = 0, PROTOCOL_FRAME = 1, TYPE_FRAME = 2, RID_FRAME = 3, ADDRESS_FRAME = 4, HEADERS_FRAME = 5, STATUS_FRAME = 6, PAYLOAD_FRAME = 7; var log; beforeEach(function(){ spyOn(uuid, 'v1').andReturn("uuid"); log = Logger.getLogger('ZSSService'); spyOn(Logger, 'getLogger').andReturn(log); }); describe('Constructor', function() { it('throws an error if no sid is passed', function() { var err = new Error("A sid must be passed!"); expect(function() { new ZSSService({}); }).toThrow(err); }); it('converts the sid to its uppercase version', function(done) { spyOn(zmq, 'socket').andReturn({ send: Function.apply(), on: Function.apply(), connect: function(uri) { expect(this.identity).toEqual("TEST#uuid"); done(); } }); new ZSSService({sid: 'test'}).run(); }); }); describe("#run", function(){ describe('connects the zmq socket', function(){ it('invoking socket connect', function(done) { spyOn(zmq, 'socket').andReturn({ send: Function.apply(), on: Function.apply(), connect: function(uri) { done(); } }); new ZSSService(config).run(); }); it('with linger options set to 0', function(done) { spyOn(zmq, 'socket').andReturn({ send: Function.apply(), on: Function.apply(), connect: function(uri) { expect(this.linger).toEqual(0); done(); } }); new ZSSService(config).run(); }); it('with setted identity', function(done) { spyOn(zmq, 'socket').andReturn({ send: Function.apply(), on: Function.apply(), connect: function(uri) { expect(this.identity).toEqual("TEST-ZMQ#uuid"); done(); } }); new ZSSService(config).run(); }); it('to the broker instance', function(done) { spyOn(zmq, 'socket').andReturn({ send: Function.apply(), on: Function.apply(), connect: function(uri) { expect(uri).toEqual("ipc://tmp/test/service-js"); done(); } }); new ZSSService(config).run(); }); }); describe('register callback', function(){ it('on "message"', function(done) { spyOn(zmq, 'socket').andReturn({ connect: Function.apply(), send: Function.apply(), on: function(type) { if (type === 'message') { done(); } } }); new ZSSService(config).run(); }); it('on "error"', function(done) { spyOn(zmq, 'socket').andReturn({ connect: Function.apply(), send: Function.apply(), on: function(type) { if (type === 'error') { done(); } } }); new ZSSService(config).run(); }); }); describe("send announcement message to broker", function(){ it("with service identity filled",function(done){ spyOn(zmq, 'socket').andReturn({ connect: Function.apply(), on: Function.apply(), send: function(frames) { expect(frames[IDENTITY_FRAME]).toEqual("TEST-ZMQ#uuid"); done(); } }); new ZSSService(config).run(); }); it("with correct SMI address", function(done){ var address = { sid: "SMI", sversion: "*", verb: "UP" }; spyOn(zmq, 'socket').andReturn({ connect: Function.apply(), on: Function.apply(), send: function(frames) { expect(msgpack.decode(frames[ADDRESS_FRAME])).toEqual(address); done(); } }); new ZSSService(config).run(); }); it("with type request", function(done){ spyOn(zmq, 'socket').andReturn({ connect: Function.apply(), on: Function.apply(), send: function(frames) { expect(frames[TYPE_FRAME]).toEqual(Message.Type.REQ); done(); } }); new ZSSService(config).run(); }); it("with generated request id", function(done){ spyOn(zmq, 'socket').andReturn({ connect: Function.apply(), on: Function.apply(), send: function(frames) { expect(frames[RID_FRAME]).toEqual("uuid"); done(); } }); new ZSSService(config).run(); }); it("with sid on payload", function(done){ spyOn(zmq, 'socket').andReturn({ connect: Function.apply(), on: Function.apply(), send: function(frames) { expect(msgpack.decode(frames[PAYLOAD_FRAME])).toBe(config.sid); done(); } }); new ZSSService(config).run(); }); }); describe("send async heartbeat messages", function(){ var isHeartbeat = function(frames){ var to = msgpack.decode(frames[ADDRESS_FRAME]); var address = { sid: "SMI", sversion: "*", verb: "HEARTBEAT" }; return to.sid === address.sid && to.verb === address.verb; }; beforeEach(function(){ config.heartbeat = 100; }); afterEach(function(){ // reset to previous state config.heartbeat = 10000; }); it('on intervale basis', function(done){ var times = 0; spyOn(zmq, 'socket').andReturn({ connect: Function.apply(), on: Function.apply(), send: function(frames) { // discard announcement message if (!isHeartbeat(frames)){ return; } ++times; if(times === 2){ done(); } } }); new ZSSService(config).run(); }); it("with service identity filled",function(done){ spyOn(zmq, 'socket').andReturn({ connect: Function.apply(), on: Function.apply(), send: function(frames) { // discard announcement message if (!isHeartbeat(frames)){ return; } expect(frames[IDENTITY_FRAME]).toEqual("TEST-ZMQ#uuid"); done(); } }); new ZSSService(config).run(); }); it("with correct SMI address", function(done){ var address = { sid: "SMI", sversion: "*", verb: "HEARTBEAT" }; spyOn(zmq, 'socket').andReturn({ connect: Function.apply(), on: Function.apply(), send: function(frames) { // discard announcement message if (!isHeartbeat(frames)){ return; } expect(msgpack.decode(frames[ADDRESS_FRAME])).toEqual(address); done(); } }); new ZSSService(config).run(); }); it("with type request", function(done){ spyOn(zmq, 'socket').andReturn({ connect: Function.apply(), on: Function.apply(), send: function(frames) { // discard announcement message if (!isHeartbeat(frames)){ return; } expect(frames[TYPE_FRAME]).toEqual(Message.Type.REQ); done(); } }); new ZSSService(config).run(); }); it("with generated request id", function(done){ spyOn(zmq, 'socket').andReturn({ connect: Function.apply(), on: Function.apply(), send: function(frames) { // discard announcement message if (!isHeartbeat(frames)){ return; } expect(frames[RID_FRAME]).toEqual("uuid"); done(); } }); new ZSSService(config).run(); }); it("with sid on payload", function(done){ spyOn(zmq, 'socket').andReturn({ connect: Function.apply(), on: Function.apply(), send: function(frames) { // discard announcement message if (!isHeartbeat(frames)){ return; } expect(msgpack.decode(frames[PAYLOAD_FRAME])).toBe(config.sid); done(); } }); new ZSSService(config).run(); }); }); }); describe("#stop", function(){ var isDownMessage = function(frames){ var to = msgpack.decode(frames[ADDRESS_FRAME]); var address = { sid: "SMI", sversion: "*", verb: "DOWN" }; return to.sid === address.sid && to.verb === address.verb; }; it('close the zmq socket', function(done) { var onMessage; spyOn(zmq, 'socket').andReturn({ send: function(frames){ if (isDownMessage(frames)) { var msg = new Message("SMI", "DOWN"); msg.status = 200; msg.type = Message.Type.REP; onMessage.apply(null, msg.toFrames()); } }, on: function(type, callback) { if (type === 'message') { onMessage = callback; } }, connect: Function.apply(), close: function() { done(); } }); var target = new ZSSService(config); target.run(); target.stop(); }); describe("sends down message to broker", function(){ it('on stoping', function(done) { var address = { sid: "SMI", sversion: "*", verb: "DOWN" }; spyOn(zmq, 'socket').andReturn({ send: function(frames){ if(isDownMessage(frames)){ expect(msgpack.decode(frames[ADDRESS_FRAME])).toEqual(address); done(); } }, on: Function.apply(), connect: Function.apply(), close: Function.apply() }); var target = new ZSSService(config); target.run(); target.stop(); }); it('before closing socket', function(done) { var downSended = false; var onMessage; spyOn(zmq, 'socket').andReturn({ send: function(frames){ if (isDownMessage(frames)) { downSended = true; var msg = new Message("SMI", "DOWN"); msg.status = 200; msg.type = Message.Type.REP; onMessage.apply(null, msg.toFrames()); } }, on: function(type, callback) { if (type === 'message') { onMessage = callback; } }, connect: Function.apply(), close: function(){ expect(downSended).toBeTruthy(); done(); } }); var target = new ZSSService(config); target.run(); target.stop(); }); it("with sid on payload", function(done){ spyOn(zmq, 'socket').andReturn({ send: function(frames){ if(isDownMessage(frames)){ expect(msgpack.decode(frames[PAYLOAD_FRAME])).toBe(config.sid); done(); } }, on: Function.apply(), connect: Function.apply(), close: Function.apply() }); var target = new ZSSService(config); target.run(); target.stop(); }); }); }); describe("#onMessage", function(){ var address, frames, expectedMsg; beforeEach(function(){ address = { sid: "TEST-ZMQ", sversion: "*", verb: "ping" }; frames = [ "identity", "ZSS:0.0", "REQ", "RID", msgpack.encode(address), msgpack.encode({something: true}), null, msgpack.encode("data") ]; expectedMsg = Message.parse(frames); }); it('executes service verb with payload and message', function(done) { spyOn(zmq, 'socket').andReturn({ connect: Function.apply(), send: Function.apply(), on: function(type, callback) { if (type === 'message') { callback.apply(null, frames); } } }); var target = new ZSSService(config); target.addVerb('ping', function(payload, message, callback){ expect(payload).toEqual(expectedMsg.payload); expect(message).toBeDefined(); expect(message.identity).toEqual(expectedMsg.identity); expect(message.protocol).toEqual(expectedMsg.protocol); expect(message.type).toEqual(expectedMsg.type); expect(message.address).toEqual(expectedMsg.address); expect(message.headers.something).toEqual(expectedMsg.headers.something); expect(message.status).toEqual(expectedMsg.status); expect(message.payload).toEqual(expectedMsg.payload); done(); }); target.run(); }); it('logs reply information', function(){ spyOn(zmq, 'socket').andReturn({ send: Function.apply(), on: function(type, callback) { if (type === 'message') { var msg = new Message("SMI", "DOWN"); msg.status = 200; msg.type = Message.Type.REP; callback.apply(null, msg.toFrames()); } }, connect: Function.apply(), close: Function.apply() }); var target = new ZSSService(config); spyOn(log, 'info'); target.run(); expect(log.info).toHaveBeenCalledWith("Reply received from %s:%s with status %s", "SMI", "DOWN", 200); }); it('logs heartbeat reply information in trace level', function(){ spyOn(zmq, 'socket').andReturn({ send: Function.apply(), on: function(type, callback) { if (type === 'message') { var msg = new Message("SMI", "HEARTBEAT"); msg.status = 200; msg.type = Message.Type.REP; callback.apply(null, msg.toFrames()); } }, connect: Function.apply(), close: Function.apply() }); var target = new ZSSService(config); spyOn(log, 'trace'); target.run(); expect(log.trace).toHaveBeenCalledWith("Reply received from %s:%s with status %s", "SMI", "HEARTBEAT", 200); }); describe("with error on execution", function(){ it('returns an error message for invalid sid', function(done) { spyOn(zmq, 'socket').andReturn({ connect: Function.apply(), send: function(frames){ if(frames[TYPE_FRAME] === Message.Type.REP) { expect(frames[STATUS_FRAME]).toEqual(404); done(); } }, on: function(type, callback) { if (type === 'message') { address.sid = "other-sid"; frames[ADDRESS_FRAME] = msgpack.encode(address); callback.apply(null, frames); } } }); var target = new ZSSService(config); target.run(); }); it('returns an error message for invalid verb', function(done) { spyOn(zmq, 'socket').andReturn({ connect: Function.apply(), send: function(frames){ if(frames[TYPE_FRAME] === Message.Type.REP) { expect(frames[STATUS_FRAME]).toEqual(404); done(); } }, on: function(type, callback) { if (type === 'message') { address.verb = "missing"; frames[ADDRESS_FRAME] = msgpack.encode(address); callback.apply(null, frames); } } }); var target = new ZSSService(config); target.run(); }); describe("on service unkown error", function(){ it('returns an error message', function(done) { spyOn(zmq, 'socket').andReturn({ connect: Function.apply(), send: function(frames){ if(frames[TYPE_FRAME] === Message.Type.REP) { expect(frames[STATUS_FRAME]).toEqual(500); done(); } }, on: function(type, callback) { if (type === 'message') { callback.apply(null, frames); } } }); var target = new ZSSService(config); target.addVerb('ping', function(payload, message, callback){ callback(new Error("blow up"), null); }); target.run(); }); it('returns a error message payload', function(done) { spyOn(zmq, 'socket').andReturn({ connect: Function.apply(), send: function(frames){ if(frames[TYPE_FRAME] === Message.Type.REP) { expect(msgpack.decode(frames[PAYLOAD_FRAME])).toEqual(errors["500"]); done(); } }, on: function(type, callback) { if (type === 'message') { callback.apply(null, frames); } } }); var target = new ZSSService(config); target.addVerb('ping', function(payload, message, callback){ callback(new Error("blow up"), null); }); target.run(); }); }); describe("on service handled error", function(){ var error; beforeEach(function(){ error = { developerMessage: "Connection timeout, the connection to the datasource seems to be down.", userMessage: "We are having troubles accessing our datasource.", code: 599 }; }); it('returns an error message', function(done) { spyOn(zmq, 'socket').andReturn({ connect: Function.apply(), send: function(frames){ if(frames[TYPE_FRAME] === Message.Type.REP) { expect(frames[STATUS_FRAME]).toEqual(599); done(); } }, on: function(type, callback) { if (type === 'message') { callback.apply(null, frames); } } }); var target = new ZSSService(config); target.addVerb('ping', function(payload, message, callback){ callback(error, null); }); target.run(); }); it('returns a error message payload', function(done) { spyOn(zmq, 'socket').andReturn({ connect: Function.apply(), send: function(frames){ if(frames[TYPE_FRAME] === Message.Type.REP) { expect(msgpack.decode(frames[PAYLOAD_FRAME])).toEqual(error); done(); } }, on: function(type, callback) { if (type === 'message') { callback.apply(null, frames); } } }); var target = new ZSSService(config); target.addVerb('ping', function(payload, message, callback){ callback(error, null); }); target.run(); }); }); describe("on service exception", function(){ it('returns an error message', function(done) { spyOn(zmq, 'socket').andReturn({ connect: Function.apply(), send: function(frames){ if(frames[TYPE_FRAME] === Message.Type.REP) { expect(frames[STATUS_FRAME]).toEqual(500); done(); } }, on: function(type, callback) { if (type === 'message') { callback.apply(null, frames); } } }); var target = new ZSSService(config); target.addVerb('ping', function(payload, message, callback){ throw new Error("blow up"); }); target.run(); }); it('returns a error message payload', function(done) { spyOn(zmq, 'socket').andReturn({ connect: Function.apply(), send: function(frames){ if(frames[TYPE_FRAME] === Message.Type.REP) { expect(msgpack.decode(frames[PAYLOAD_FRAME])).toEqual(errors["500"]); done(); } }, on: function(type, callback) { if (type === 'message') { callback.apply(null, frames); } } }); var target = new ZSSService(config); target.addVerb('ping', function(payload, message, callback){ throw new Error("blow up"); }); target.run(); }); }); describe("logs an error", function(){ it('on zmq error', function() { var error = new Error("BLOWS UP!"); spyOn(zmq, 'socket').andReturn({ connect: Function.apply(), send: Function.apply(), on: function(type, callback) { if (type === 'error') { callback(error); } } }); var target = new ZSSService(config); spyOn(log, 'error'); target.run(); expect(log.error).toHaveBeenCalledWith(error, "Received zmq error: %s with stack: %s", error, error.stack); }); }); }); describe('#addVerbs', function() { it('calls #addVerb for each tuple in an array', function() { var service = new ZSSService({sid: 'test'}); var verbs = [['one', Function.apply()], ['two', Function.apply()]]; var spy = spyOn(service, 'addVerb'); service.addVerbs(verbs); expect(spy.calls[0].args).toEqual(['one', jasmine.any(Function)]); expect(spy.calls[1].args).toEqual(['two', jasmine.any(Function)]); }); }); describe("with successfull execution", function(){ var socketMock; beforeEach(function(){ socketMock = { connect: Function.apply(), on: function(type, callback) { if (type === 'message') { callback.apply(null, frames); } } }; }); it('returns status code 200 when not specified', function(done) { socketMock.send = function(frames) { if(frames[TYPE_FRAME] === Message.Type.REP) { expect(frames[STATUS_FRAME]).toEqual(200); done(); } }; spyOn(zmq, 'socket').andReturn(socketMock); var target = new ZSSService(config); target.addVerb('ping', function(payload, message, callback){ callback(null, "PONG"); }); target.run(); }); it('returns the status code specified by the service', function(done) { socketMock.send = function(frames) { if(frames[TYPE_FRAME] === Message.Type.REP) { expect(frames[STATUS_FRAME]).toEqual(204); done(); } }; spyOn(zmq, 'socket').andReturn(socketMock); var target = new ZSSService(config); target.addVerb('ping', function(payload, message, callback){ message.status = 204; callback(null, null); }); target.run(); }); it('returns 204 when no content and no status code specified', function(done) { socketMock.send = function(frames) { if(frames[TYPE_FRAME] === Message.Type.REP) { expect(frames[STATUS_FRAME]).toEqual(204); done(); } }; spyOn(zmq, 'socket').andReturn(socketMock); var target = new ZSSService(config); target.addVerb('ping', function(payload, message, callback){ callback(null, null); }); target.run(); }); it('returns 500 when the status code specified by the service is not valid', function(done) { socketMock.send = function(frames) { if(frames[TYPE_FRAME] === Message.Type.REP) { expect(frames[STATUS_FRAME]).toEqual(500); done(); } }; spyOn(zmq, 'socket').andReturn(socketMock); var target = new ZSSService(config); target.addVerb('ping', function(payload, message, callback){ message.status = 400; callback(null, null); }); target.run(); }); it('returns payload on successfull service execution', function(done) { socketMock.send = function(frames) { if(frames[TYPE_FRAME] === Message.Type.REP) { expect(msgpack.decode(frames[PAYLOAD_FRAME])).toEqual("PONG"); done(); } }; spyOn(zmq, 'socket').andReturn(socketMock); var target = new ZSSService(config); target.addVerb('ping', function(payload, message, callback){ callback(null, "PONG"); }); target.run(); }); it('returns response-time (ms) on headers', function(done) { spyOn(process, 'hrtime').andCallFake(function(start){ return (start) ? [1, 0] : 1000000; }); socketMock.send = function(frames) { if(frames[TYPE_FRAME] === Message.Type.REP) { var headers = msgpack.decode(frames[HEADERS_FRAME]); expect(headers["response-time"]).toEqual(1000); done(); } }; spyOn(zmq, 'socket').andReturn(socketMock); var target = new ZSSService(config); target.addVerb('ping', function(payload, message, callback){ callback(null, "PONG"); }); target.run(); }); }); }); });