client.test.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631
  1. var util = require('util'),
  2. Events = require('events').EventEmitter,
  3. nodeunit = require('nodeunit'),
  4. testCase = require('nodeunit').testCase;
  5. var StompClient = require('../lib/client').StompClient;
  6. var connectionObserver;
  7. // surpress logs for the test
  8. util.log = function() {};
  9. // check message headers are properties of Error object
  10. function checkError(test, er, expectedHeaders, msg)
  11. {
  12. var headers = {}
  13. for (var key in expectedHeaders) {
  14. headers[key] = er[key];
  15. }
  16. test.deepEqual(headers, expectedHeaders, msg);
  17. }
  18. // net mockage
  19. var net = require('net');
  20. var StompFrame = require('../lib/frame').StompFrame;
  21. // Override StompFrame send function to allow inspection of frame data inside a test
  22. var oldSend;
  23. var oldCreateConnection;
  24. var sendHook = function() {};
  25. module.exports = testCase({
  26. setUp: function(callback) {
  27. // Mock net object so we never try to send any real data
  28. connectionObserver = new Events();
  29. connectionObserver.destroy = function() {};
  30. this.stompClient = new StompClient('127.0.0.1', 2098, 'user', 'pass', '1.0');
  31. oldCreateConnection = net.createConnection;
  32. net.createConnection = function() {
  33. return connectionObserver;
  34. };
  35. oldSend = StompFrame.prototype.send;
  36. StompFrame.prototype.send = function(stream) {
  37. var self = this;
  38. process.nextTick(function () {
  39. sendHook(self);
  40. });
  41. };
  42. callback();
  43. },
  44. tearDown: function(callback) {
  45. delete this.stompClient;
  46. sendHook = function() {};
  47. net.createConnection = oldCreateConnection;
  48. StompFrame.prototype.send = oldSend;
  49. callback();
  50. },
  51. 'check default properties are correctly set on a basic StompClient': function(test) {
  52. var stompClient = new StompClient();
  53. test.equal(stompClient.user, '');
  54. test.equal(stompClient.pass, '');
  55. test.equal(stompClient.address, '127.0.0.1');
  56. test.equal(stompClient.port, 61613);
  57. test.equal(stompClient.version, '1.0');
  58. test.done();
  59. },
  60. 'check StompClient construction from paremeters': function(test) {
  61. var stompClient = new StompClient(
  62. 'test.host.net',1234,'uname','pw', '1.1', 'q1.host.net',
  63. { retries: 10, delay: 1000 });
  64. test.equal(stompClient.user, 'uname');
  65. test.equal(stompClient.pass, 'pw');
  66. test.equal(stompClient.address, 'test.host.net');
  67. test.equal(stompClient.port, 1234);
  68. test.equal(stompClient.version, '1.1');
  69. test.equal(stompClient.vhost, 'q1.host.net');
  70. test.equal(stompClient.reconnectOpts.retries, 10);
  71. test.equal(stompClient.reconnectOpts.delay, 1000);
  72. test.done();
  73. },
  74. 'check StompClient construction from options': function(test) {
  75. var stompClient = new StompClient( {
  76. address: 'test.host.net',
  77. port: 1234,
  78. user: 'uname',
  79. pass: 'pw',
  80. protocolVersion: '1.1',
  81. vhost: 'q1.host.net',
  82. reconnectOpts: { retries: 10, delay: 1000 }});
  83. test.equal(stompClient.user, 'uname');
  84. test.equal(stompClient.pass, 'pw');
  85. test.equal(stompClient.address, 'test.host.net');
  86. test.equal(stompClient.port, 1234);
  87. test.equal(stompClient.version, '1.1');
  88. test.equal(stompClient.vhost, 'q1.host.net');
  89. test.equal(stompClient.reconnectOpts.retries, 10);
  90. test.equal(stompClient.reconnectOpts.delay, 1000);
  91. test.done();
  92. },
  93. 'check StompClient TLS construction': function(test) {
  94. var stompClient = new StompClient(
  95. 'test.host.net',1234,'uname','pw', null, null, null, true);
  96. test.deepEqual(stompClient.tls, {}, 'TLS not set by parameter');
  97. var stompClient = new StompClient(
  98. 'test.host.net',1234,'uname','pw', null, null, null, false);
  99. test.ok(!stompClient.tls, 'TLS incorrectly set by parameter');
  100. var stompClient = new StompClient({
  101. host: 'secure.host.net',
  102. tls: true,
  103. cert: 'dummy'
  104. });
  105. test.equal(stompClient.address, 'secure.host.net');
  106. test.deepEqual(stompClient.tls.cert, 'dummy', 'TLS not set by option');
  107. var stompClient = new StompClient({
  108. host: 'secure.host.net',
  109. tls: false,
  110. cert: 'dummy'
  111. });
  112. test.equal(stompClient.address, 'secure.host.net');
  113. test.ok(!stompClient.tls, 'TLS incorrectly set by option');
  114. var stompClient = new StompClient({
  115. host: 'secure.host.net',
  116. tls: {
  117. cert: 'dummy'
  118. }});
  119. test.equal(stompClient.address, 'secure.host.net');
  120. test.deepEqual(stompClient.tls.cert, 'dummy',
  121. 'TLS not set by nested option');
  122. test.done();
  123. },
  124. 'check outbound CONNECT frame correctly follows protocol specification': function(test) {
  125. var self = this;
  126. test.expect(4);
  127. sendHook = function(stompFrame) {
  128. test.equal(stompFrame.command, 'CONNECT');
  129. test.deepEqual(stompFrame.headers, {
  130. login: 'user',
  131. passcode: 'pass'
  132. });
  133. test.equal(stompFrame.body, '');
  134. test.equal(stompFrame.contentLength, -1);
  135. test.done();
  136. };
  137. //start the test
  138. this.stompClient.connect();
  139. connectionObserver.emit('connect');
  140. },
  141. 'check inbound CONNECTED frame parses correctly': function(test) {
  142. var self = this;
  143. var testId = '1234';
  144. test.expect(2);
  145. sendHook = function() {
  146. self.stompClient.stream.emit('data', 'CONNECTED\nsession:' + testId + '\n\n\0');
  147. };
  148. this.stompClient._stompFrameEmitter.on('CONNECTED', function (stompFrame) {
  149. test.equal(stompFrame.command, 'CONNECTED');
  150. test.equal(testId, stompFrame.headers.session);
  151. test.done();
  152. });
  153. //start the test
  154. this.stompClient.connect(function() {});
  155. connectionObserver.emit('connect');
  156. },
  157. 'check the ERROR callback fires when we receive an error frame on connection': function (test) {
  158. var self = this,
  159. expectedHeaders = {
  160. message: 'some test error',
  161. 'content-length' : 18
  162. },
  163. expectedBody = 'Error message body';
  164. test.expect(2);
  165. // mock that we received a CONNECTED from the stomp server in our send hook
  166. sendHook = function (stompFrame) {
  167. self.stompClient.stream.emit('data', 'ERROR\nmessage:' + expectedHeaders.message + '\ncontent-length:' + expectedHeaders['content-length'] + '\n\n' + expectedBody + '\0');
  168. };
  169. this.stompClient.connect(function () {
  170. test.ok(false, 'Success callback of connect() should not be called');
  171. }, function (headers, body) {
  172. checkError(test, headers, expectedHeaders, 'passed ERROR frame headers should be as expected');
  173. test.equal(body, expectedBody, 'passed ERROR frame body should be as expected');
  174. test.done();
  175. });
  176. connectionObserver.emit('connect');
  177. },
  178. 'check outbound SUBSCRIBE frame correctly follows protocol specification': function(test) {
  179. var self = this;
  180. var testId = '1234';
  181. var destination = '/queue/someQueue';
  182. test.expect(10);
  183. //mock that we received a CONNECTED from the stomp server in our send hook
  184. sendHook = function(stompFrame) {
  185. self.stompClient.stream.emit('data', 'CONNECTED\nsession:' + testId + '\n\n\0');
  186. };
  187. // Once connected - subscribe to a fake queue
  188. this.stompClient._stompFrameEmitter.on('CONNECTED', function (stompFrame) {
  189. function unsubscribe() {
  190. sendHook = function (){};
  191. self.stompClient.unsubscribe(destination);
  192. }
  193. // Synchronous hooking of the .send(), vastly simplifying the tests below
  194. StompFrame.prototype.send = function(stream) {
  195. var self = this;
  196. sendHook(self);
  197. };
  198. //override the sendHook so we can test the latest stompframe to be sent
  199. sendHook = function(stompFrame) {
  200. test.equal(stompFrame.command, 'SUBSCRIBE');
  201. test.equal(stompFrame.headers.destination, destination);
  202. test.equal(stompFrame.headers.id, 'blah');
  203. };
  204. // note the use of additional id header (optional in spec) below :)
  205. self.stompClient.subscribe(destination, function(){}, { id: 'blah' });
  206. unsubscribe();
  207. sendHook = function(stompFrame) {
  208. test.equal(stompFrame.command, 'SUBSCRIBE');
  209. test.equal(stompFrame.headers.destination, destination);
  210. test.equal(stompFrame.headers.id, 'shucks');
  211. };
  212. // Note the natural argument order is used, and destination is ignored, it
  213. // gets overwritten by the real destination.
  214. self.stompClient.subscribe(destination, { id: 'shucks', destination: 'D' }, function(){});
  215. unsubscribe();
  216. // Subscribe without headers is valid
  217. sendHook = function(stompFrame) {
  218. test.equal(stompFrame.command, 'SUBSCRIBE');
  219. test.equal(stompFrame.headers.destination, destination);
  220. };
  221. self.stompClient.subscribe(destination, function(){});
  222. unsubscribe();
  223. // Subscribe without a callback is invalid, with or without headers
  224. try {
  225. self.stompClient.subscribe(destination, {});
  226. } catch(er) {
  227. test.ok(true);
  228. }
  229. unsubscribe();
  230. try {
  231. self.stompClient.subscribe(destination, {});
  232. } catch(er) {
  233. test.ok(true);
  234. }
  235. unsubscribe();
  236. test.done();
  237. });
  238. this.stompClient.connect(function() {});
  239. connectionObserver.emit('connect');
  240. },
  241. 'check the SUBSCRIBE callback fires when we receive data down the destination queue': function(test) {
  242. var self = this;
  243. var testId = '1234';
  244. var destination = '/queue/someQueue';
  245. var messageId = 'ID:SomeID:1';
  246. var messageToBeSent = 'oh herrow!';
  247. test.expect(5);
  248. //mock that we received a CONNECTED from the stomp server in our send hook
  249. sendHook = function(stompFrame) {
  250. self.stompClient.stream.emit('data', 'CONNECTED\nsession:' + testId + '\n\n\0');
  251. };
  252. this.stompClient.connect(function() {
  253. // Mock inbound MESSAGE frame
  254. sendHook = function (stompFrame) {
  255. self.stompClient.stream.emit('data', 'MESSAGE\ndestination:' + destination + '\nmessage-id:' + messageId + '\n\n' + messageToBeSent + '\0');
  256. };
  257. // Subscribe to a queue, and upon receipt of message (wired above) test that body/headers correctly propogate to callback
  258. self.stompClient.subscribe(destination, function (body, headers) {
  259. test.equal(body, messageToBeSent, 'Received message matches the sent one');
  260. test.equal(headers['message-id'], messageId);
  261. test.equal(headers.destination, destination);
  262. test.equal(self.stompClient.subscriptions[destination].listeners.length, 1, 'ensure callback was added to subscription stack');
  263. // Unsubscribe and ensure queue is cleared of the subscription (and related callback)
  264. self.stompClient.unsubscribe(destination, {});
  265. test.equal(typeof self.stompClient.subscriptions[destination], 'undefined', 'ensure queue is cleared of the subscription');
  266. test.done();
  267. });
  268. });
  269. connectionObserver.emit('connect');
  270. },
  271. 'check the MESSAGE callback fires when we receive a message': function(test) {
  272. var self = this;
  273. var testId = '1234';
  274. var destination = '/queue/someQueue';
  275. var messageId = 'ID:SomeID:1';
  276. var messageToBeSent = 'oh herrow!';
  277. test.expect(5);
  278. //mock that we received a CONNECTED from the stomp server in our send hook
  279. sendHook = function(stompFrame) {
  280. self.stompClient.stream.emit('data', 'CONNECTED\nsession:' + testId + '\n\n\0');
  281. };
  282. this.stompClient.connect(function() {
  283. // Mock inbound MESSAGE frame
  284. sendHook = function (stompFrame) {
  285. self.stompClient.stream.emit('data', 'MESSAGE\ndestination:' + destination + '\nmessage-id:' + messageId + '\n\n' + messageToBeSent + '\0');
  286. };
  287. // Subscribe to the queue, but don't do anything with the response
  288. self.stompClient.subscribe(destination, function () {});
  289. // Subscribe to the MESSAGE hook, and upon receipt of message (wired above) test that body/headers correctly propogate to callback
  290. self.stompClient.on('message', function (body, headers) {
  291. test.equal(body, messageToBeSent, 'Received message matches the sent one');
  292. test.equal(headers['message-id'], messageId);
  293. test.equal(headers.destination, destination);
  294. // Check the subscription has also been created
  295. test.equal(self.stompClient.subscriptions[destination].listeners.length, 1, 'ensure callback was added to subscription stack');
  296. // Unsubscribe and ensure queue is cleared of the subscription (and related callback)
  297. self.stompClient.unsubscribe(destination, {});
  298. test.equal(typeof self.stompClient.subscriptions[destination], 'undefined', 'ensure queue is cleared of the subscription');
  299. test.done();
  300. });
  301. });
  302. connectionObserver.emit('connect');
  303. },
  304. 'check the ERROR callback fires when we receive an error frame on subscription': function (test) {
  305. var self = this,
  306. testId = '1234',
  307. destination = '/queue/someQueue',
  308. expectedHeaders = {
  309. message: 'some test error',
  310. 'content-length' : 18
  311. },
  312. expectedBody = 'Error message body',
  313. errorCallbackCalled = false;
  314. test.expect(3);
  315. //mock that we received a CONNECTED from the stomp server in our send hook
  316. sendHook = function (stompFrame) {
  317. self.stompClient.stream.emit('data', 'CONNECTED\nsession:' + testId + '\n\n\0');
  318. };
  319. this.stompClient.connect(function () {
  320. // Mock inbound ERROR frame
  321. sendHook = function (stompFrame) {
  322. self.stompClient.stream.emit('data', 'ERROR\nmessage:' + expectedHeaders.message + '\ncontent-length:' + expectedHeaders['content-length'] + '\n\n' + expectedBody + '\0');
  323. };
  324. // make sure the error callback hasn't been called yet
  325. test.equal(errorCallbackCalled, false, 'ERROR callback should not have been called yet');
  326. // Subscribe to a queue, and upon receipt of message (wired above) test that body/headers correctly propogate to callback
  327. self.stompClient.subscribe(destination, function () {
  328. test.ok(false, 'Success callback of subscribe() should not be called');
  329. });
  330. }, function (headers, body) {
  331. errorCallbackCalled = true;
  332. checkError(test, headers, expectedHeaders, 'passed ERROR frame headers should be as expected');
  333. test.equal(body, expectedBody, 'passed ERROR frame body should be as expected');
  334. test.done();
  335. });
  336. connectionObserver.emit('connect');
  337. },
  338. 'check outbound UNSUBSCRIBE frame correctly follows protocol specification': function (test) {
  339. var self = this;
  340. var testId = '1234';
  341. var destination = '/queue/someQueue';
  342. test.expect(4);
  343. //mock that we received a CONNECTED from the stomp server in our send hook
  344. sendHook = function(stompFrame) {
  345. self.stompClient.stream.emit('data', 'CONNECTED\nsession:' + testId + '\n\n\0');
  346. };
  347. // Once connected - unsubscribe to a fake queue
  348. this.stompClient._stompFrameEmitter.on('CONNECTED', function (stompFrame) {
  349. //override the sendHook so we can test the latest stompframe to be sent
  350. sendHook = function(stompFrame) {
  351. test.equal(stompFrame.command, 'UNSUBSCRIBE');
  352. test.equal(stompFrame.headers.destination, destination);
  353. test.equal(stompFrame.headers.id, 'specialid');
  354. test.done();
  355. };
  356. var before = { id: 'specialid' };
  357. var after = { id: 'specialid' };
  358. self.stompClient.unsubscribe(destination, after);
  359. test.deepEqual(before, after, "methods shouldn't modify their arguments");
  360. });
  361. this.stompClient.connect(function(){});
  362. connectionObserver.emit('connect');
  363. },
  364. 'check the ERROR callback fires when we receive an error frame when unsubscribing': function (test) {
  365. var self = this,
  366. testId = '1234',
  367. destination = '/queue/someQueue',
  368. expectedHeaders = {
  369. message: 'some test error',
  370. 'content-length' : 18
  371. },
  372. expectedBody = 'Error message body',
  373. errorCallbackCalled = false;
  374. test.expect(4);
  375. //mock that we received a CONNECTED from the stomp server in our send hook
  376. sendHook = function (stompFrame) {
  377. self.stompClient.stream.emit('data', 'CONNECTED\nsession:' + testId + '\n\n\0');
  378. };
  379. this.stompClient.connect(function () {
  380. // Mock inbound MESSAGE frame
  381. sendHook = function (stompFrame) {
  382. self.stompClient.stream.emit('data', 'MESSAGE\ndestination:' + destination + '\nmessage-id:some message id\n\nsome message body\0');
  383. };
  384. // make sure the error callback hasn't been called yet
  385. test.equal(errorCallbackCalled, false, 'ERROR callback should not have been called yet');
  386. // Subscribe to a queue, and upon receipt of message (wired above) test that body/headers correctly propogate to callback
  387. self.stompClient.subscribe(destination, function () {
  388. // Mock inbound ERROR frame
  389. sendHook = function (stompFrame) {
  390. self.stompClient.stream.emit('data', 'ERROR\nmessage:' + expectedHeaders.message + '\ncontent-length:' + expectedHeaders['content-length'] + '\n\n' + expectedBody + '\0');
  391. };
  392. test.equal(errorCallbackCalled, false, 'ERROR callback should not have been called yet');
  393. self.stompClient.unsubscribe(destination, { id: 'specialid' });
  394. });
  395. }, function (headers, body) {
  396. errorCallbackCalled = true;
  397. checkError(test, headers, expectedHeaders, 'passed ERROR frame headers should be as expected');
  398. test.equal(body, expectedBody, 'passed ERROR frame body should be as expected');
  399. test.done();
  400. });
  401. connectionObserver.emit('connect');
  402. },
  403. 'check outbound SEND frame correctly follows protocol specification': function (test) {
  404. var self = this;
  405. var testId = '1234';
  406. var destination = '/queue/someQueue';
  407. var messageToBeSent = 'oh herrow!';
  408. test.expect(3);
  409. //mock that we received a CONNECTED from the stomp server in our send hook
  410. sendHook = function (stompFrame) {
  411. self.stompClient.stream.emit('data', 'CONNECTED\nsession:' + testId + '\n\n\0');
  412. };
  413. this.stompClient.connect(function() {
  414. sendHook = function(stompFrame) {
  415. test.equal(stompFrame.command, 'SEND');
  416. test.deepEqual(stompFrame.headers, { destination: destination });
  417. test.equal(stompFrame.body, messageToBeSent);
  418. test.done();
  419. };
  420. self.stompClient.publish(destination, messageToBeSent);
  421. });
  422. connectionObserver.emit('connect');
  423. },
  424. 'check outbound SEND header correctly follows protocol specification': function (test) {
  425. var self = this;
  426. var testId = '1234';
  427. var destination = '/queue/someQueue';
  428. var messageToBeSent = 'oh herrow!';
  429. var headers = {
  430. destination: 'TO BE OVERWRITTEN',
  431. 'content-type': 'text/plain'
  432. };
  433. test.expect(3);
  434. //mock that we received a CONNECTED from the stomp server in our send hook
  435. sendHook = function (stompFrame) {
  436. self.stompClient.stream.emit('data', 'CONNECTED\nsession:' + testId + '\n\n\0');
  437. };
  438. this.stompClient.connect(function() {
  439. sendHook = function(stompFrame) {
  440. test.equal(stompFrame.command, 'SEND');
  441. headers.destination = destination;
  442. test.deepEqual(stompFrame.headers, headers);
  443. test.equal(stompFrame.body, messageToBeSent);
  444. test.done();
  445. };
  446. self.stompClient.publish(destination, messageToBeSent, headers);
  447. });
  448. connectionObserver.emit('connect');
  449. },
  450. 'check parseError event fires when malformed frame is received': function(test) {
  451. var self = this;
  452. test.expect(2);
  453. //mock that we received a CONNECTED from the stomp server in our send hook
  454. sendHook = function (stompFrame) {
  455. self.stompClient.stream.emit('data', 'CONNECTED\n\n\n\0');
  456. };
  457. this.stompClient.on('error', function (err) {
  458. test.equal(err.message, 'Header "session" is required for CONNECTED');
  459. test.equal(err.details, 'Frame: {"command":"CONNECTED","headers":{},"body":"\\n"}');
  460. test.done();
  461. });
  462. this.stompClient.connect(function() {});
  463. connectionObserver.emit('connect');
  464. },
  465. 'check disconnect method correctly sends DISCONNECT frame, disconnects TCP stream, and fires callback': function (test) {
  466. var self = this;
  467. test.expect(5);
  468. //mock that we received a CONNECTED from the stomp server in our send hook
  469. sendHook = function (stompFrame) {
  470. self.stompClient.stream.emit('data', 'CONNECTED\nsession:blah\n\n\0');
  471. };
  472. self.stompClient.connect(function() {
  473. // Assert next outbound STOMP frame is a DISCONNECT
  474. sendHook = function (stompFrame) {
  475. test.equal(stompFrame.command, 'DISCONNECT');
  476. test.deepEqual(stompFrame.headers, {});
  477. test.equal(stompFrame.body, '');
  478. };
  479. // Set disconnection callback to ensure it is called appropriately
  480. self.stompClient.disconnect(function () {
  481. test.ok(true, 'disconnect callback executed');
  482. test.done();
  483. });
  484. });
  485. // Mock the TCP end call
  486. connectionObserver.end = function() {
  487. test.ok(true, 'TCP end call made');
  488. connectionObserver.end = function(){};
  489. process.nextTick(function() { connectionObserver.emit('end'); });
  490. };
  491. connectionObserver.emit('connect');
  492. }
  493. });