diff options
Diffstat (limited to 'testing/xpcshell/node-ws/test/sender.test.js')
-rw-r--r-- | testing/xpcshell/node-ws/test/sender.test.js | 370 |
1 files changed, 370 insertions, 0 deletions
diff --git a/testing/xpcshell/node-ws/test/sender.test.js b/testing/xpcshell/node-ws/test/sender.test.js new file mode 100644 index 0000000000..532239fa1a --- /dev/null +++ b/testing/xpcshell/node-ws/test/sender.test.js @@ -0,0 +1,370 @@ +'use strict'; + +const assert = require('assert'); + +const extension = require('../lib/extension'); +const PerMessageDeflate = require('../lib/permessage-deflate'); +const Sender = require('../lib/sender'); +const { EMPTY_BUFFER } = require('../lib/constants'); + +class MockSocket { + constructor({ write } = {}) { + this.readable = true; + this.writable = true; + + if (write) this.write = write; + } + + cork() {} + write() {} + uncork() {} +} + +describe('Sender', () => { + describe('.frame', () => { + it('does not mutate the input buffer if data is `readOnly`', () => { + const buf = Buffer.from([1, 2, 3, 4, 5]); + + Sender.frame(buf, { + readOnly: true, + rsv1: false, + mask: true, + opcode: 2, + fin: true + }); + + assert.ok(buf.equals(Buffer.from([1, 2, 3, 4, 5]))); + }); + + it('honors the `rsv1` option', () => { + const list = Sender.frame(EMPTY_BUFFER, { + readOnly: false, + mask: false, + rsv1: true, + opcode: 1, + fin: true + }); + + assert.strictEqual(list[0][0] & 0x40, 0x40); + }); + + it('accepts a string as first argument', () => { + const list = Sender.frame('€', { + readOnly: false, + rsv1: false, + mask: false, + opcode: 1, + fin: true + }); + + assert.deepStrictEqual(list[0], Buffer.from('8103', 'hex')); + assert.deepStrictEqual(list[1], Buffer.from('e282ac', 'hex')); + }); + }); + + describe('#send', () => { + it('compresses data if compress option is enabled', (done) => { + const chunks = []; + const perMessageDeflate = new PerMessageDeflate(); + const mockSocket = new MockSocket({ + write: (chunk) => { + chunks.push(chunk); + if (chunks.length !== 6) return; + + assert.strictEqual(chunks[0].length, 2); + assert.strictEqual(chunks[0][0] & 0x40, 0x40); + + assert.strictEqual(chunks[2].length, 2); + assert.strictEqual(chunks[2][0] & 0x40, 0x40); + + assert.strictEqual(chunks[4].length, 2); + assert.strictEqual(chunks[4][0] & 0x40, 0x40); + done(); + } + }); + const sender = new Sender(mockSocket, { + 'permessage-deflate': perMessageDeflate + }); + + perMessageDeflate.accept([{}]); + + const options = { compress: true, fin: true }; + const array = new Uint8Array([0x68, 0x69]); + + sender.send(array.buffer, options); + sender.send(array, options); + sender.send('hi', options); + }); + + describe('when context takeover is disabled', () => { + it('honors the compression threshold', (done) => { + const chunks = []; + const perMessageDeflate = new PerMessageDeflate(); + const mockSocket = new MockSocket({ + write: (chunk) => { + chunks.push(chunk); + if (chunks.length !== 2) return; + + assert.strictEqual(chunks[0].length, 2); + assert.notStrictEqual(chunk[0][0] & 0x40, 0x40); + assert.strictEqual(chunks[1], 'hi'); + done(); + } + }); + const sender = new Sender(mockSocket, { + 'permessage-deflate': perMessageDeflate + }); + const extensions = extension.parse( + 'permessage-deflate; client_no_context_takeover' + ); + + perMessageDeflate.accept(extensions['permessage-deflate']); + + sender.send('hi', { compress: true, fin: true }); + }); + + it('compresses all fragments of a fragmented message', (done) => { + const chunks = []; + const perMessageDeflate = new PerMessageDeflate({ threshold: 3 }); + const mockSocket = new MockSocket({ + write: (chunk) => { + chunks.push(chunk); + if (chunks.length !== 4) return; + + assert.strictEqual(chunks[0].length, 2); + assert.strictEqual(chunks[0][0] & 0x40, 0x40); + assert.strictEqual(chunks[1].length, 9); + + assert.strictEqual(chunks[2].length, 2); + assert.strictEqual(chunks[2][0] & 0x40, 0x00); + assert.strictEqual(chunks[3].length, 4); + done(); + } + }); + const sender = new Sender(mockSocket, { + 'permessage-deflate': perMessageDeflate + }); + const extensions = extension.parse( + 'permessage-deflate; client_no_context_takeover' + ); + + perMessageDeflate.accept(extensions['permessage-deflate']); + + sender.send('123', { compress: true, fin: false }); + sender.send('12', { compress: true, fin: true }); + }); + + it('does not compress any fragments of a fragmented message', (done) => { + const chunks = []; + const perMessageDeflate = new PerMessageDeflate({ threshold: 3 }); + const mockSocket = new MockSocket({ + write: (chunk) => { + chunks.push(chunk); + if (chunks.length !== 4) return; + + assert.strictEqual(chunks[0].length, 2); + assert.strictEqual(chunks[0][0] & 0x40, 0x00); + assert.strictEqual(chunks[1].length, 2); + + assert.strictEqual(chunks[2].length, 2); + assert.strictEqual(chunks[2][0] & 0x40, 0x00); + assert.strictEqual(chunks[3].length, 3); + done(); + } + }); + const sender = new Sender(mockSocket, { + 'permessage-deflate': perMessageDeflate + }); + const extensions = extension.parse( + 'permessage-deflate; client_no_context_takeover' + ); + + perMessageDeflate.accept(extensions['permessage-deflate']); + + sender.send('12', { compress: true, fin: false }); + sender.send('123', { compress: true, fin: true }); + }); + + it('compresses empty buffer as first fragment', (done) => { + const chunks = []; + const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); + const mockSocket = new MockSocket({ + write: (chunk) => { + chunks.push(chunk); + if (chunks.length !== 4) return; + + assert.strictEqual(chunks[0].length, 2); + assert.strictEqual(chunks[0][0] & 0x40, 0x40); + assert.strictEqual(chunks[1].length, 5); + + assert.strictEqual(chunks[2].length, 2); + assert.strictEqual(chunks[2][0] & 0x40, 0x00); + assert.strictEqual(chunks[3].length, 6); + done(); + } + }); + const sender = new Sender(mockSocket, { + 'permessage-deflate': perMessageDeflate + }); + const extensions = extension.parse( + 'permessage-deflate; client_no_context_takeover' + ); + + perMessageDeflate.accept(extensions['permessage-deflate']); + + sender.send(Buffer.alloc(0), { compress: true, fin: false }); + sender.send('data', { compress: true, fin: true }); + }); + + it('compresses empty buffer as last fragment', (done) => { + const chunks = []; + const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); + const mockSocket = new MockSocket({ + write: (chunk) => { + chunks.push(chunk); + if (chunks.length !== 4) return; + + assert.strictEqual(chunks[0].length, 2); + assert.strictEqual(chunks[0][0] & 0x40, 0x40); + assert.strictEqual(chunks[1].length, 10); + + assert.strictEqual(chunks[2].length, 2); + assert.strictEqual(chunks[2][0] & 0x40, 0x00); + assert.strictEqual(chunks[3].length, 1); + done(); + } + }); + const sender = new Sender(mockSocket, { + 'permessage-deflate': perMessageDeflate + }); + const extensions = extension.parse( + 'permessage-deflate; client_no_context_takeover' + ); + + perMessageDeflate.accept(extensions['permessage-deflate']); + + sender.send('data', { compress: true, fin: false }); + sender.send(Buffer.alloc(0), { compress: true, fin: true }); + }); + }); + }); + + describe('#ping', () => { + it('works with multiple types of data', (done) => { + const perMessageDeflate = new PerMessageDeflate(); + let count = 0; + const mockSocket = new MockSocket({ + write: (data) => { + if (++count < 3) return; + + if (count % 2) { + assert.ok(data.equals(Buffer.from([0x89, 0x02]))); + } else if (count < 8) { + assert.ok(data.equals(Buffer.from([0x68, 0x69]))); + } else { + assert.strictEqual(data, 'hi'); + done(); + } + } + }); + const sender = new Sender(mockSocket, { + 'permessage-deflate': perMessageDeflate + }); + + perMessageDeflate.accept([{}]); + + const array = new Uint8Array([0x68, 0x69]); + + sender.send('foo', { compress: true, fin: true }); + sender.ping(array.buffer, false); + sender.ping(array, false); + sender.ping('hi', false); + }); + }); + + describe('#pong', () => { + it('works with multiple types of data', (done) => { + const perMessageDeflate = new PerMessageDeflate(); + let count = 0; + const mockSocket = new MockSocket({ + write: (data) => { + if (++count < 3) return; + + if (count % 2) { + assert.ok(data.equals(Buffer.from([0x8a, 0x02]))); + } else if (count < 8) { + assert.ok(data.equals(Buffer.from([0x68, 0x69]))); + } else { + assert.strictEqual(data, 'hi'); + done(); + } + } + }); + const sender = new Sender(mockSocket, { + 'permessage-deflate': perMessageDeflate + }); + + perMessageDeflate.accept([{}]); + + const array = new Uint8Array([0x68, 0x69]); + + sender.send('foo', { compress: true, fin: true }); + sender.pong(array.buffer, false); + sender.pong(array, false); + sender.pong('hi', false); + }); + }); + + describe('#close', () => { + it('throws an error if the first argument is invalid', () => { + const mockSocket = new MockSocket(); + const sender = new Sender(mockSocket); + + assert.throws( + () => sender.close('error'), + /^TypeError: First argument must be a valid error code number$/ + ); + + assert.throws( + () => sender.close(1004), + /^TypeError: First argument must be a valid error code number$/ + ); + }); + + it('throws an error if the message is greater than 123 bytes', () => { + const mockSocket = new MockSocket(); + const sender = new Sender(mockSocket); + + assert.throws( + () => sender.close(1000, 'a'.repeat(124)), + /^RangeError: The message must not be greater than 123 bytes$/ + ); + }); + + it('should consume all data before closing', (done) => { + const perMessageDeflate = new PerMessageDeflate(); + + let count = 0; + const mockSocket = new MockSocket({ + write: (data, cb) => { + count++; + if (cb) cb(); + } + }); + const sender = new Sender(mockSocket, { + 'permessage-deflate': perMessageDeflate + }); + + perMessageDeflate.accept([{}]); + + sender.send('foo', { compress: true, fin: true }); + sender.send('bar', { compress: true, fin: true }); + sender.send('baz', { compress: true, fin: true }); + + sender.close(1000, undefined, false, () => { + assert.strictEqual(count, 8); + done(); + }); + }); + }); +}); |