test.websock.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480
  1. // requires local modules: websock, base64, util
  2. // requires test modules: fake.websocket
  3. /* jshint expr: true */
  4. var assert = chai.assert;
  5. var expect = chai.expect;
  6. describe('Websock', function() {
  7. "use strict";
  8. describe('Queue methods', function () {
  9. var sock;
  10. var RQ_TEMPLATE = [0, 1, 2, 3, 4, 5, 6, 7];
  11. beforeEach(function () {
  12. sock = new Websock();
  13. for (var i = RQ_TEMPLATE.length - 1; i >= 0; i--) {
  14. sock.rQunshift8(RQ_TEMPLATE[i]);
  15. }
  16. });
  17. describe('rQlen', function () {
  18. it('should return the length of the receive queue', function () {
  19. sock.set_rQi(0);
  20. expect(sock.rQlen()).to.equal(RQ_TEMPLATE.length);
  21. });
  22. it("should return the proper length if we read some from the receive queue", function () {
  23. sock.set_rQi(1);
  24. expect(sock.rQlen()).to.equal(RQ_TEMPLATE.length - 1);
  25. });
  26. });
  27. describe('rQpeek8', function () {
  28. it('should peek at the next byte without poping it off the queue', function () {
  29. var bef_len = sock.rQlen();
  30. var peek = sock.rQpeek8();
  31. expect(sock.rQpeek8()).to.equal(peek);
  32. expect(sock.rQlen()).to.equal(bef_len);
  33. });
  34. });
  35. describe('rQshift8', function () {
  36. it('should pop a single byte from the receive queue', function () {
  37. var peek = sock.rQpeek8();
  38. var bef_len = sock.rQlen();
  39. expect(sock.rQshift8()).to.equal(peek);
  40. expect(sock.rQlen()).to.equal(bef_len - 1);
  41. });
  42. });
  43. describe('rQunshift8', function () {
  44. it('should place a byte at the front of the queue', function () {
  45. sock.rQunshift8(255);
  46. expect(sock.rQpeek8()).to.equal(255);
  47. expect(sock.rQlen()).to.equal(RQ_TEMPLATE.length + 1);
  48. });
  49. });
  50. describe('rQshift16', function () {
  51. it('should pop two bytes from the receive queue and return a single number', function () {
  52. var bef_len = sock.rQlen();
  53. var expected = (RQ_TEMPLATE[0] << 8) + RQ_TEMPLATE[1];
  54. expect(sock.rQshift16()).to.equal(expected);
  55. expect(sock.rQlen()).to.equal(bef_len - 2);
  56. });
  57. });
  58. describe('rQshift32', function () {
  59. it('should pop four bytes from the receive queue and return a single number', function () {
  60. var bef_len = sock.rQlen();
  61. var expected = (RQ_TEMPLATE[0] << 24) +
  62. (RQ_TEMPLATE[1] << 16) +
  63. (RQ_TEMPLATE[2] << 8) +
  64. RQ_TEMPLATE[3];
  65. expect(sock.rQshift32()).to.equal(expected);
  66. expect(sock.rQlen()).to.equal(bef_len - 4);
  67. });
  68. });
  69. describe('rQshiftStr', function () {
  70. it('should shift the given number of bytes off of the receive queue and return a string', function () {
  71. var bef_len = sock.rQlen();
  72. var bef_rQi = sock.get_rQi();
  73. var shifted = sock.rQshiftStr(3);
  74. expect(shifted).to.be.a('string');
  75. expect(shifted).to.equal(String.fromCharCode.apply(null, RQ_TEMPLATE.slice(bef_rQi, bef_rQi + 3)));
  76. expect(sock.rQlen()).to.equal(bef_len - 3);
  77. });
  78. it('should shift the entire rest of the queue off if no length is given', function () {
  79. sock.rQshiftStr();
  80. expect(sock.rQlen()).to.equal(0);
  81. });
  82. });
  83. describe('rQshiftBytes', function () {
  84. it('should shift the given number of bytes of the receive queue and return an array', function () {
  85. var bef_len = sock.rQlen();
  86. var bef_rQi = sock.get_rQi();
  87. var shifted = sock.rQshiftBytes(3);
  88. expect(shifted).to.be.an.instanceof(Array);
  89. expect(shifted).to.deep.equal(RQ_TEMPLATE.slice(bef_rQi, bef_rQi + 3));
  90. expect(sock.rQlen()).to.equal(bef_len - 3);
  91. });
  92. it('should shift the entire rest of the queue off if no length is given', function () {
  93. sock.rQshiftBytes();
  94. expect(sock.rQlen()).to.equal(0);
  95. });
  96. });
  97. describe('rQslice', function () {
  98. beforeEach(function () {
  99. sock.set_rQi(0);
  100. });
  101. it('should not modify the receive queue', function () {
  102. var bef_len = sock.rQlen();
  103. sock.rQslice(0, 2);
  104. expect(sock.rQlen()).to.equal(bef_len);
  105. });
  106. it('should return an array containing the given slice of the receive queue', function () {
  107. var sl = sock.rQslice(0, 2);
  108. expect(sl).to.be.an.instanceof(Array);
  109. expect(sl).to.deep.equal(RQ_TEMPLATE.slice(0, 2));
  110. });
  111. it('should use the rest of the receive queue if no end is given', function () {
  112. var sl = sock.rQslice(1);
  113. expect(sl).to.have.length(RQ_TEMPLATE.length - 1);
  114. expect(sl).to.deep.equal(RQ_TEMPLATE.slice(1));
  115. });
  116. it('should take the current rQi in to account', function () {
  117. sock.set_rQi(1);
  118. expect(sock.rQslice(0, 2)).to.deep.equal(RQ_TEMPLATE.slice(1, 3));
  119. });
  120. });
  121. describe('rQwait', function () {
  122. beforeEach(function () {
  123. sock.set_rQi(0);
  124. });
  125. it('should return true if there are not enough bytes in the receive queue', function () {
  126. expect(sock.rQwait('hi', RQ_TEMPLATE.length + 1)).to.be.true;
  127. });
  128. it('should return false if there are enough bytes in the receive queue', function () {
  129. expect(sock.rQwait('hi', RQ_TEMPLATE.length)).to.be.false;
  130. });
  131. it('should return true and reduce rQi by "goback" if there are not enough bytes', function () {
  132. sock.set_rQi(5);
  133. expect(sock.rQwait('hi', RQ_TEMPLATE.length, 4)).to.be.true;
  134. expect(sock.get_rQi()).to.equal(1);
  135. });
  136. it('should raise an error if we try to go back more than possible', function () {
  137. sock.set_rQi(5);
  138. expect(function () { sock.rQwait('hi', RQ_TEMPLATE.length, 6); }).to.throw(Error);
  139. });
  140. it('should not reduce rQi if there are enough bytes', function () {
  141. sock.set_rQi(5);
  142. sock.rQwait('hi', 1, 6);
  143. expect(sock.get_rQi()).to.equal(5);
  144. });
  145. });
  146. describe('flush', function () {
  147. beforeEach(function () {
  148. sock._websocket = {
  149. send: sinon.spy()
  150. };
  151. });
  152. it('should actually send on the websocket if the websocket does not have too much buffered', function () {
  153. sock.maxBufferedAmount = 10;
  154. sock._websocket.bufferedAmount = 8;
  155. sock._sQ = [1, 2, 3];
  156. var encoded = sock._encode_message();
  157. sock.flush();
  158. expect(sock._websocket.send).to.have.been.calledOnce;
  159. expect(sock._websocket.send).to.have.been.calledWith(encoded);
  160. });
  161. it('should return true if the websocket did not have too much buffered', function () {
  162. sock.maxBufferedAmount = 10;
  163. sock._websocket.bufferedAmount = 8;
  164. expect(sock.flush()).to.be.true;
  165. });
  166. it('should not call send if we do not have anything queued up', function () {
  167. sock._sQ = [];
  168. sock.maxBufferedAmount = 10;
  169. sock._websocket.bufferedAmount = 8;
  170. sock.flush();
  171. expect(sock._websocket.send).not.to.have.been.called;
  172. });
  173. it('should not send and return false if the websocket has too much buffered', function () {
  174. sock.maxBufferedAmount = 10;
  175. sock._websocket.bufferedAmount = 12;
  176. expect(sock.flush()).to.be.false;
  177. expect(sock._websocket.send).to.not.have.been.called;
  178. });
  179. });
  180. describe('send', function () {
  181. beforeEach(function () {
  182. sock.flush = sinon.spy();
  183. });
  184. it('should add to the send queue', function () {
  185. sock.send([1, 2, 3]);
  186. var sq = sock.get_sQ();
  187. expect(sock.get_sQ().slice(sq.length - 3)).to.deep.equal([1, 2, 3]);
  188. });
  189. it('should call flush', function () {
  190. sock.send([1, 2, 3]);
  191. expect(sock.flush).to.have.been.calledOnce;
  192. });
  193. });
  194. describe('send_string', function () {
  195. beforeEach(function () {
  196. sock.send = sinon.spy();
  197. });
  198. it('should call send after converting the string to an array', function () {
  199. sock.send_string("\x01\x02\x03");
  200. expect(sock.send).to.have.been.calledWith([1, 2, 3]);
  201. });
  202. });
  203. });
  204. describe('lifecycle methods', function () {
  205. var old_WS;
  206. before(function () {
  207. old_WS = WebSocket;
  208. });
  209. var sock;
  210. beforeEach(function () {
  211. sock = new Websock();
  212. WebSocket = sinon.spy();
  213. WebSocket.OPEN = old_WS.OPEN;
  214. WebSocket.CONNECTING = old_WS.CONNECTING;
  215. WebSocket.CLOSING = old_WS.CLOSING;
  216. WebSocket.CLOSED = old_WS.CLOSED;
  217. });
  218. describe('opening', function () {
  219. it('should pick the correct protocols if none are given' , function () {
  220. });
  221. it('should open the actual websocket', function () {
  222. sock.open('ws://localhost:8675', 'base64');
  223. expect(WebSocket).to.have.been.calledWith('ws://localhost:8675', 'base64');
  224. });
  225. it('should fail if we try to use binary but do not support it', function () {
  226. expect(function () { sock.open('ws:///', 'binary'); }).to.throw(Error);
  227. });
  228. it('should fail if we specified an array with only binary and we do not support it', function () {
  229. expect(function () { sock.open('ws:///', ['binary']); }).to.throw(Error);
  230. });
  231. it('should skip binary if we have multiple options for encoding and do not support binary', function () {
  232. sock.open('ws:///', ['binary', 'base64']);
  233. expect(WebSocket).to.have.been.calledWith('ws:///', ['base64']);
  234. });
  235. // it('should initialize the event handlers')?
  236. });
  237. describe('closing', function () {
  238. beforeEach(function () {
  239. sock.open('ws://');
  240. sock._websocket.close = sinon.spy();
  241. });
  242. it('should close the actual websocket if it is open', function () {
  243. sock._websocket.readyState = WebSocket.OPEN;
  244. sock.close();
  245. expect(sock._websocket.close).to.have.been.calledOnce;
  246. });
  247. it('should close the actual websocket if it is connecting', function () {
  248. sock._websocket.readyState = WebSocket.CONNECTING;
  249. sock.close();
  250. expect(sock._websocket.close).to.have.been.calledOnce;
  251. });
  252. it('should not try to close the actual websocket if closing', function () {
  253. sock._websocket.readyState = WebSocket.CLOSING;
  254. sock.close();
  255. expect(sock._websocket.close).not.to.have.been.called;
  256. });
  257. it('should not try to close the actual websocket if closed', function () {
  258. sock._websocket.readyState = WebSocket.CLOSED;
  259. sock.close();
  260. expect(sock._websocket.close).not.to.have.been.called;
  261. });
  262. it('should reset onmessage to not call _recv_message', function () {
  263. sinon.spy(sock, '_recv_message');
  264. sock.close();
  265. sock._websocket.onmessage(null);
  266. try {
  267. expect(sock._recv_message).not.to.have.been.called;
  268. } finally {
  269. sock._recv_message.restore();
  270. }
  271. });
  272. });
  273. describe('event handlers', function () {
  274. beforeEach(function () {
  275. sock._recv_message = sinon.spy();
  276. sock.on('open', sinon.spy());
  277. sock.on('close', sinon.spy());
  278. sock.on('error', sinon.spy());
  279. sock.open('ws://');
  280. });
  281. it('should call _recv_message on a message', function () {
  282. sock._websocket.onmessage(null);
  283. expect(sock._recv_message).to.have.been.calledOnce;
  284. });
  285. it('should copy the mode over upon opening', function () {
  286. sock._websocket.protocol = 'cheese';
  287. sock._websocket.onopen();
  288. expect(sock._mode).to.equal('cheese');
  289. });
  290. it('should assume base64 if no protocol was available on opening', function () {
  291. sock._websocket.protocol = null;
  292. sock._websocket.onopen();
  293. expect(sock._mode).to.equal('base64');
  294. });
  295. it('should call the open event handler on opening', function () {
  296. sock._websocket.onopen();
  297. expect(sock._eventHandlers.open).to.have.been.calledOnce;
  298. });
  299. it('should call the close event handler on closing', function () {
  300. sock._websocket.onclose();
  301. expect(sock._eventHandlers.close).to.have.been.calledOnce;
  302. });
  303. it('should call the error event handler on error', function () {
  304. sock._websocket.onerror();
  305. expect(sock._eventHandlers.error).to.have.been.calledOnce;
  306. });
  307. });
  308. after(function () {
  309. WebSocket = old_WS;
  310. });
  311. });
  312. describe('WebSocket Receiving', function () {
  313. var sock;
  314. beforeEach(function () {
  315. sock = new Websock();
  316. });
  317. it('should support decoding base64 string data to add it to the receive queue', function () {
  318. var msg = { data: Base64.encode([1, 2, 3]) };
  319. sock._mode = 'base64';
  320. sock._recv_message(msg);
  321. expect(sock.rQshiftStr(3)).to.equal('\x01\x02\x03');
  322. });
  323. it('should support adding binary Uint8Array data to the receive queue', function () {
  324. var msg = { data: new Uint8Array([1, 2, 3]) };
  325. sock._mode = 'binary';
  326. sock._recv_message(msg);
  327. expect(sock.rQshiftStr(3)).to.equal('\x01\x02\x03');
  328. });
  329. it('should call the message event handler if present', function () {
  330. sock._eventHandlers.message = sinon.spy();
  331. var msg = { data: Base64.encode([1, 2, 3]) };
  332. sock._mode = 'base64';
  333. sock._recv_message(msg);
  334. expect(sock._eventHandlers.message).to.have.been.calledOnce;
  335. });
  336. it('should not call the message event handler if there is nothing in the receive queue', function () {
  337. sock._eventHandlers.message = sinon.spy();
  338. var msg = { data: Base64.encode([]) };
  339. sock._mode = 'base64';
  340. sock._recv_message(msg);
  341. expect(sock._eventHandlers.message).not.to.have.been.called;
  342. });
  343. it('should compact the receive queue', function () {
  344. // NB(sross): while this is an internal implementation detail, it's important to
  345. // test, otherwise the receive queue could become very large very quickly
  346. sock._rQ = [0, 1, 2, 3, 4, 5];
  347. sock.set_rQi(6);
  348. sock._rQmax = 3;
  349. var msg = { data: Base64.encode([1, 2, 3]) };
  350. sock._mode = 'base64';
  351. sock._recv_message(msg);
  352. expect(sock._rQ.length).to.equal(3);
  353. expect(sock.get_rQi()).to.equal(0);
  354. });
  355. it('should call the error event handler on an exception', function () {
  356. sock._eventHandlers.error = sinon.spy();
  357. sock._eventHandlers.message = sinon.stub().throws();
  358. var msg = { data: Base64.encode([1, 2, 3]) };
  359. sock._mode = 'base64';
  360. sock._recv_message(msg);
  361. expect(sock._eventHandlers.error).to.have.been.calledOnce;
  362. });
  363. });
  364. describe('Data encoding', function () {
  365. before(function () { FakeWebSocket.replace(); });
  366. after(function () { FakeWebSocket.restore(); });
  367. describe('as binary data', function () {
  368. var sock;
  369. beforeEach(function () {
  370. sock = new Websock();
  371. sock.open('ws://', 'binary');
  372. sock._websocket._open();
  373. });
  374. it('should convert the send queue into an ArrayBuffer', function () {
  375. sock._sQ = [1, 2, 3];
  376. var res = sock._encode_message(); // An ArrayBuffer
  377. expect(new Uint8Array(res)).to.deep.equal(new Uint8Array(res));
  378. });
  379. it('should properly pass the encoded data off to the actual WebSocket', function () {
  380. sock.send([1, 2, 3]);
  381. expect(sock._websocket._get_sent_data()).to.deep.equal([1, 2, 3]);
  382. });
  383. });
  384. describe('as Base64 data', function () {
  385. var sock;
  386. beforeEach(function () {
  387. sock = new Websock();
  388. sock.open('ws://', 'base64');
  389. sock._websocket._open();
  390. });
  391. it('should convert the send queue into a Base64-encoded string', function () {
  392. sock._sQ = [1, 2, 3];
  393. expect(sock._encode_message()).to.equal(Base64.encode([1, 2, 3]));
  394. });
  395. it('should properly pass the encoded data off to the actual WebSocket', function () {
  396. sock.send([1, 2, 3]);
  397. expect(sock._websocket._get_sent_data()).to.deep.equal([1, 2, 3]);
  398. });
  399. });
  400. });
  401. });