diff options
Diffstat (limited to '')
-rw-r--r-- | test/websocket/close.js | 130 | ||||
-rw-r--r-- | test/websocket/constructor.js | 48 | ||||
-rw-r--r-- | test/websocket/custom-headers.js | 30 | ||||
-rw-r--r-- | test/websocket/diagnostics-channel.js | 71 | ||||
-rw-r--r-- | test/websocket/events.js | 204 | ||||
-rw-r--r-- | test/websocket/fragments.js | 40 | ||||
-rw-r--r-- | test/websocket/frame.js | 24 | ||||
-rw-r--r-- | test/websocket/opening-handshake.js | 215 | ||||
-rw-r--r-- | test/websocket/ping-pong.js | 46 | ||||
-rw-r--r-- | test/websocket/receive.js | 60 | ||||
-rw-r--r-- | test/websocket/send.js | 216 | ||||
-rw-r--r-- | test/websocket/websocketinit.js | 45 |
12 files changed, 1129 insertions, 0 deletions
diff --git a/test/websocket/close.js b/test/websocket/close.js new file mode 100644 index 0000000..4d314a4 --- /dev/null +++ b/test/websocket/close.js @@ -0,0 +1,130 @@ +'use strict' + +const { test } = require('tap') +const { WebSocketServer } = require('ws') +const { WebSocket } = require('../..') + +test('Close', (t) => { + t.plan(6) + + t.test('Close with code', (t) => { + t.plan(1) + + const server = new WebSocketServer({ port: 0 }) + + server.on('connection', (ws) => { + ws.on('close', (code) => { + t.equal(code, 1000) + }) + }) + + t.teardown(server.close.bind(server)) + + const ws = new WebSocket(`ws://localhost:${server.address().port}`) + ws.addEventListener('open', () => ws.close(1000)) + }) + + t.test('Close with code and reason', (t) => { + t.plan(2) + + const server = new WebSocketServer({ port: 0 }) + + server.on('connection', (ws) => { + ws.on('close', (code, reason) => { + t.equal(code, 1000) + t.same(reason, Buffer.from('Goodbye')) + }) + }) + + t.teardown(server.close.bind(server)) + + const ws = new WebSocket(`ws://localhost:${server.address().port}`) + ws.addEventListener('open', () => ws.close(1000, 'Goodbye')) + }) + + t.test('Close with invalid code', (t) => { + t.plan(2) + + const server = new WebSocketServer({ port: 0 }) + + t.teardown(server.close.bind(server)) + + const ws = new WebSocket(`ws://localhost:${server.address().port}`) + ws.addEventListener('open', () => { + t.throws( + () => ws.close(2999), + { + name: 'InvalidAccessError', + constructor: DOMException + } + ) + + t.throws( + () => ws.close(5000), + { + name: 'InvalidAccessError', + constructor: DOMException + } + ) + + ws.close() + }) + }) + + t.test('Close with invalid reason', (t) => { + t.plan(1) + + const server = new WebSocketServer({ port: 0 }) + + t.teardown(server.close.bind(server)) + const ws = new WebSocket(`ws://localhost:${server.address().port}`) + + ws.addEventListener('open', () => { + t.throws( + () => ws.close(1000, 'a'.repeat(124)), + { + name: 'SyntaxError', + constructor: DOMException + } + ) + + ws.close(1000) + }) + }) + + t.test('Close with no code or reason', (t) => { + t.plan(2) + + const server = new WebSocketServer({ port: 0 }) + + server.on('connection', (ws) => { + ws.on('close', (code, reason) => { + t.equal(code, 1005) + t.same(reason, Buffer.alloc(0)) + }) + }) + + t.teardown(server.close.bind(server)) + + const ws = new WebSocket(`ws://localhost:${server.address().port}`) + ws.addEventListener('open', () => ws.close()) + }) + + t.test('Close with a 3000 status code', (t) => { + t.plan(2) + + const server = new WebSocketServer({ port: 0 }) + + server.on('connection', (ws) => { + ws.on('close', (code, reason) => { + t.equal(code, 3000) + t.same(reason, Buffer.alloc(0)) + }) + }) + + t.teardown(server.close.bind(server)) + + const ws = new WebSocket(`ws://localhost:${server.address().port}`) + ws.addEventListener('open', () => ws.close(3000)) + }) +}) diff --git a/test/websocket/constructor.js b/test/websocket/constructor.js new file mode 100644 index 0000000..dd87dea --- /dev/null +++ b/test/websocket/constructor.js @@ -0,0 +1,48 @@ +'use strict' + +const { test } = require('tap') +const { WebSocket } = require('../..') + +test('Constructor', (t) => { + t.throws( + () => new WebSocket('abc'), + { + name: 'SyntaxError', + constructor: DOMException + } + ) + + t.throws( + () => new WebSocket('wss://echo.websocket.events/#a'), + { + name: 'SyntaxError', + constructor: DOMException + } + ) + + t.throws( + () => new WebSocket('wss://echo.websocket.events', ''), + { + name: 'SyntaxError', + constructor: DOMException + } + ) + + t.throws( + () => new WebSocket('wss://echo.websocket.events', ['chat', 'chat']), + { + name: 'SyntaxError', + constructor: DOMException + } + ) + + t.throws( + () => new WebSocket('wss://echo.websocket.events', ['<>@,;:\\"/[]?={}\t']), + { + name: 'SyntaxError', + constructor: DOMException + } + ) + + t.end() +}) diff --git a/test/websocket/custom-headers.js b/test/websocket/custom-headers.js new file mode 100644 index 0000000..01f1830 --- /dev/null +++ b/test/websocket/custom-headers.js @@ -0,0 +1,30 @@ +'use strict' + +const { test } = require('tap') +const assert = require('assert') +const { Agent, WebSocket } = require('../..') + +test('Setting custom headers', (t) => { + t.plan(1) + + const headers = { + 'x-khafra-hello': 'hi', + Authorization: 'Bearer base64orsomethingitreallydoesntmatter' + } + + class TestAgent extends Agent { + dispatch (options) { + t.match(options.headers, headers) + + return false + } + } + + const ws = new WebSocket('wss://echo.websocket.events', { + headers, + dispatcher: new TestAgent() + }) + + // We don't want to make a request, just ensure the headers are set. + ws.onclose = ws.onerror = ws.onmessage = assert.fail +}) diff --git a/test/websocket/diagnostics-channel.js b/test/websocket/diagnostics-channel.js new file mode 100644 index 0000000..c3bf05a --- /dev/null +++ b/test/websocket/diagnostics-channel.js @@ -0,0 +1,71 @@ +'use strict' + +const t = require('tap') +const dc = require('diagnostics_channel') +const { WebSocketServer } = require('ws') +const { WebSocket } = require('../..') + +t.test('diagnostics channel', { jobs: 1 }, (t) => { + t.plan(2) + + t.test('undici:websocket:open', (t) => { + t.plan(3) + + const server = new WebSocketServer({ port: 0 }) + + server.on('connection', (ws) => { + ws.close(1000, 'goodbye') + }) + + const listener = ({ extensions, protocol }) => { + t.equal(extensions, null) + t.equal(protocol, 'chat') + } + + t.teardown(() => { + dc.channel('undici:websocket:open').unsubscribe(listener) + return server.close() + }) + + const { port } = server.address() + + dc.channel('undici:websocket:open').subscribe(listener) + + const ws = new WebSocket(`ws://localhost:${port}`, 'chat') + + ws.addEventListener('open', () => { + t.pass('Emitted open') + }) + }) + + t.test('undici:websocket:close', (t) => { + t.plan(4) + + const server = new WebSocketServer({ port: 0 }) + + server.on('connection', (ws) => { + ws.close(1000, 'goodbye') + }) + + const listener = ({ websocket, code, reason }) => { + t.type(websocket, WebSocket) + t.equal(code, 1000) + t.equal(reason, 'goodbye') + } + + t.teardown(() => { + dc.channel('undici:websocket:close').unsubscribe(listener) + return server.close() + }) + + const { port } = server.address() + + dc.channel('undici:websocket:close').subscribe(listener) + + const ws = new WebSocket(`ws://localhost:${port}`, 'chat') + + ws.addEventListener('close', () => { + t.pass('Emitted open') + }) + }) +}) diff --git a/test/websocket/events.js b/test/websocket/events.js new file mode 100644 index 0000000..e5b565c --- /dev/null +++ b/test/websocket/events.js @@ -0,0 +1,204 @@ +'use strict' + +const { test } = require('tap') +const { WebSocketServer } = require('ws') +const { MessageEvent, CloseEvent, ErrorEvent } = require('../../lib/websocket/events') +const { WebSocket } = require('../..') + +test('MessageEvent', (t) => { + t.throws(() => new MessageEvent(), TypeError, 'no arguments') + t.throws(() => new MessageEvent('').initMessageEvent(), TypeError) + + const noInitEvent = new MessageEvent('message') + + t.equal(noInitEvent.origin, '') + t.equal(noInitEvent.data, null) + t.equal(noInitEvent.lastEventId, '') + t.equal(noInitEvent.source, null) + t.ok(Array.isArray(noInitEvent.ports)) + t.ok(Object.isFrozen(noInitEvent.ports)) + t.type(new MessageEvent('').initMessageEvent('message'), MessageEvent) + + t.end() +}) + +test('CloseEvent', (t) => { + t.throws(() => new CloseEvent(), TypeError) + + const noInitEvent = new CloseEvent('close') + + t.equal(noInitEvent.wasClean, false) + t.equal(noInitEvent.code, 0) + t.equal(noInitEvent.reason, '') + + t.end() +}) + +test('ErrorEvent', (t) => { + t.throws(() => new ErrorEvent(), TypeError) + + const noInitEvent = new ErrorEvent('error') + + t.equal(noInitEvent.message, '') + t.equal(noInitEvent.filename, '') + t.equal(noInitEvent.lineno, 0) + t.equal(noInitEvent.colno, 0) + t.equal(noInitEvent.error, undefined) + + t.end() +}) + +test('Event handlers', (t) => { + t.plan(4) + + const server = new WebSocketServer({ port: 0 }) + const ws = new WebSocket(`ws://localhost:${server.address().port}`) + + function listen () {} + + t.teardown(server.close.bind(server)) + t.teardown(() => ws.close()) + + t.test('onopen', (t) => { + t.plan(3) + + t.equal(ws.onopen, null) + ws.onopen = 3 + t.equal(ws.onopen, null) + ws.onopen = listen + t.equal(ws.onopen, listen) + }) + + t.test('onerror', (t) => { + t.plan(3) + + t.equal(ws.onerror, null) + ws.onerror = 3 + t.equal(ws.onerror, null) + ws.onerror = listen + t.equal(ws.onerror, listen) + }) + + t.test('onclose', (t) => { + t.plan(3) + + t.equal(ws.onclose, null) + ws.onclose = 3 + t.equal(ws.onclose, null) + ws.onclose = listen + t.equal(ws.onclose, listen) + }) + + t.test('onmessage', (t) => { + t.plan(3) + + t.equal(ws.onmessage, null) + ws.onmessage = 3 + t.equal(ws.onmessage, null) + ws.onmessage = listen + t.equal(ws.onmessage, listen) + }) +}) + +test('CloseEvent WPTs ported', (t) => { + t.test('initCloseEvent', (t) => { + // Taken from websockets/interfaces/CloseEvent/historical.html + t.notOk('initCloseEvent' in CloseEvent.prototype) + t.notOk('initCloseEvent' in new CloseEvent('close')) + + t.end() + }) + + t.test('CloseEvent constructor', (t) => { + // Taken from websockets/interfaces/CloseEvent/constructor.html + + { + const event = new CloseEvent('foo') + + t.ok(event instanceof CloseEvent, 'should be a CloseEvent') + t.equal(event.type, 'foo') + t.notOk(event.bubbles, 'bubbles') + t.notOk(event.cancelable, 'cancelable') + t.notOk(event.wasClean, 'wasClean') + t.equal(event.code, 0) + t.equal(event.reason, '') + } + + { + const event = new CloseEvent('foo', { + bubbles: true, + cancelable: true, + wasClean: true, + code: 7, + reason: 'x' + }) + t.ok(event instanceof CloseEvent, 'should be a CloseEvent') + t.equal(event.type, 'foo') + t.ok(event.bubbles, 'bubbles') + t.ok(event.cancelable, 'cancelable') + t.ok(event.wasClean, 'wasClean') + t.equal(event.code, 7) + t.equal(event.reason, 'x') + } + + t.end() + }) + + t.end() +}) + +test('ErrorEvent WPTs ported', (t) => { + t.test('Synthetic ErrorEvent', (t) => { + // Taken from html/webappapis/scripting/events/event-handler-processing-algorithm-error/document-synthetic-errorevent.html + + { + const e = new ErrorEvent('error') + t.equal(e.message, '') + t.equal(e.filename, '') + t.equal(e.lineno, 0) + t.equal(e.colno, 0) + t.equal(e.error, undefined) + } + + { + const e = new ErrorEvent('error', { error: null }) + t.equal(e.error, null) + } + + { + const e = new ErrorEvent('error', { error: undefined }) + t.equal(e.error, undefined) + } + + { + const e = new ErrorEvent('error', { error: 'foo' }) + t.equal(e.error, 'foo') + } + + t.end() + }) + + t.test('webidl', (t) => { + // Taken from webidl/ecmascript-binding/no-regexp-special-casing.any.js + + const regExp = new RegExp() + regExp.message = 'some message' + + const errorEvent = new ErrorEvent('type', regExp) + + t.equal(errorEvent.message, 'some message') + + t.end() + }) + + t.test('initErrorEvent', (t) => { + // Taken from workers/Worker_dispatchEvent_ErrorEvent.htm + + const e = new ErrorEvent('error') + t.notOk('initErrorEvent' in e, 'should not be supported') + + t.end() + }) + + t.end() +}) diff --git a/test/websocket/fragments.js b/test/websocket/fragments.js new file mode 100644 index 0000000..d51db4b --- /dev/null +++ b/test/websocket/fragments.js @@ -0,0 +1,40 @@ +'use strict' + +const { test } = require('tap') +const { WebSocketServer } = require('ws') +const { WebSocket } = require('../..') +const diagnosticsChannel = require('diagnostics_channel') + +test('Fragmented frame with a ping frame in the middle of it', (t) => { + t.plan(2) + + const server = new WebSocketServer({ port: 0 }) + + server.on('connection', (ws) => { + const socket = ws._socket + + socket.write(Buffer.from([0x01, 0x03, 0x48, 0x65, 0x6c])) // Text frame "Hel" + socket.write(Buffer.from([0x89, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f])) // ping "Hello" + socket.write(Buffer.from([0x80, 0x02, 0x6c, 0x6f])) // Text frame "lo" + }) + + t.teardown(() => { + for (const client of server.clients) { + client.close() + } + + server.close() + }) + + const ws = new WebSocket(`ws://localhost:${server.address().port}`) + + ws.addEventListener('message', ({ data }) => { + t.same(data, 'Hello') + + ws.close() + }) + + diagnosticsChannel.channel('undici:websocket:ping').subscribe( + ({ payload }) => t.same(payload, Buffer.from('Hello')) + ) +}) diff --git a/test/websocket/frame.js b/test/websocket/frame.js new file mode 100644 index 0000000..b4b73b7 --- /dev/null +++ b/test/websocket/frame.js @@ -0,0 +1,24 @@ +'use strict' + +const { test } = require('tap') +const { WebsocketFrameSend } = require('../../lib/websocket/frame') +const { opcodes } = require('../../lib/websocket/constants') + +test('Writing 16-bit frame length value at correct offset when buffer has a non-zero byteOffset', (t) => { + /* + When writing 16-bit frame lengths, a `DataView` was being used without setting a `byteOffset` into the buffer: + i.e. `new DataView(buffer.buffer)` instead of `new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength)`. + Small `Buffers` returned by `allocUnsafe` are usually returned from the buffer pool, and thus have a non-zero `byteOffset`. + Invalid frames were therefore being returned in that case. + */ + t.plan(3) + + const payloadLength = 126 // 126 bytes is the smallest payload to trigger a 16-bit length field + const smallBuffer = Buffer.allocUnsafe(1) // make it very likely that the next buffer returned by allocUnsafe DOESN'T have a zero byteOffset + const payload = Buffer.allocUnsafe(payloadLength).fill(0) + const frame = new WebsocketFrameSend(payload).createFrame(opcodes.BINARY) + + t.equal(frame[2], payloadLength >>> 8) + t.equal(frame[3], payloadLength & 0xff) + t.equal(smallBuffer.length, 1) // ensure smallBuffer can't be garbage-collected too soon +}) diff --git a/test/websocket/opening-handshake.js b/test/websocket/opening-handshake.js new file mode 100644 index 0000000..b9a7989 --- /dev/null +++ b/test/websocket/opening-handshake.js @@ -0,0 +1,215 @@ +'use strict' + +const { test } = require('tap') +const { createServer } = require('http') +const { WebSocketServer } = require('ws') +const { WebSocket } = require('../..') + +test('WebSocket connecting to server that isn\'t a Websocket server', (t) => { + t.plan(5) + + const server = createServer((req, res) => { + t.equal(req.headers.connection, 'upgrade') + t.equal(req.headers.upgrade, 'websocket') + t.ok(req.headers['sec-websocket-key']) + t.equal(req.headers['sec-websocket-version'], '13') + + res.end() + server.unref() + }).listen(0, () => { + const ws = new WebSocket(`ws://localhost:${server.address().port}`) + + // Server isn't a websocket server + ws.onmessage = ws.onopen = t.fail + + ws.addEventListener('error', t.pass) + }) + + t.teardown(server.close.bind(server)) +}) + +test('Open event is emitted', (t) => { + t.plan(1) + + const server = new WebSocketServer({ port: 0 }) + + server.on('connection', (ws) => { + ws.close(1000) + }) + + t.teardown(server.close.bind(server)) + + const ws = new WebSocket(`ws://localhost:${server.address().port}`) + + ws.onmessage = ws.onerror = t.fail + ws.addEventListener('open', t.pass) +}) + +test('Multiple protocols are joined by a comma', (t) => { + t.plan(1) + + const server = new WebSocketServer({ port: 0 }) + + server.on('connection', (ws, req) => { + t.equal(req.headers['sec-websocket-protocol'], 'chat, echo') + + ws.close(1000) + server.close() + }) + + t.teardown(server.close.bind(server)) + + const ws = new WebSocket(`ws://localhost:${server.address().port}`, ['chat', 'echo']) + + ws.addEventListener('open', () => ws.close()) +}) + +test('Server doesn\'t send Sec-WebSocket-Protocol header when protocols are used', (t) => { + t.plan(1) + + const server = createServer((req, res) => { + res.statusCode = 101 + + req.socket.destroy() + }).listen(0, () => { + const ws = new WebSocket(`ws://localhost:${server.address().port}`, 'chat') + + ws.onopen = t.fail + + ws.addEventListener('error', ({ error }) => { + t.ok(error) + }) + }) + + t.teardown(server.close.bind(server)) +}) + +test('Server sends invalid Upgrade header', (t) => { + t.plan(1) + + const server = createServer((req, res) => { + res.setHeader('Upgrade', 'NotWebSocket') + res.statusCode = 101 + + req.socket.destroy() + }).listen(0, () => { + const ws = new WebSocket(`ws://localhost:${server.address().port}`) + + ws.onopen = t.fail + + ws.addEventListener('error', ({ error }) => { + t.ok(error) + }) + }) + + t.teardown(server.close.bind(server)) +}) + +test('Server sends invalid Connection header', (t) => { + t.plan(1) + + const server = createServer((req, res) => { + res.setHeader('Upgrade', 'websocket') + res.setHeader('Connection', 'downgrade') + res.statusCode = 101 + + req.socket.destroy() + }).listen(0, () => { + const ws = new WebSocket(`ws://localhost:${server.address().port}`) + + ws.onopen = t.fail + + ws.addEventListener('error', ({ error }) => { + t.ok(error) + }) + }) + + t.teardown(server.close.bind(server)) +}) + +test('Server sends invalid Sec-WebSocket-Accept header', (t) => { + t.plan(1) + + const server = createServer((req, res) => { + res.setHeader('Upgrade', 'websocket') + res.setHeader('Connection', 'upgrade') + res.setHeader('Sec-WebSocket-Accept', 'abc') + res.statusCode = 101 + + req.socket.destroy() + }).listen(0, () => { + const ws = new WebSocket(`ws://localhost:${server.address().port}`) + + ws.onopen = t.fail + + ws.addEventListener('error', ({ error }) => { + t.ok(error) + }) + }) + + t.teardown(server.close.bind(server)) +}) + +test('Server sends invalid Sec-WebSocket-Extensions header', (t) => { + const uid = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11' + const { createHash } = require('crypto') + + t.plan(2) + + const server = createServer((req, res) => { + const key = req.headers['sec-websocket-key'] + t.ok(key) + + const accept = createHash('sha1').update(key + uid).digest('base64') + + res.setHeader('Upgrade', 'websocket') + res.setHeader('Connection', 'upgrade') + res.setHeader('Sec-WebSocket-Accept', accept) + res.setHeader('Sec-WebSocket-Extensions', 'InvalidExtension') + res.statusCode = 101 + + res.end() + }).listen(0, () => { + const ws = new WebSocket(`ws://localhost:${server.address().port}`) + + ws.onopen = t.fail + + ws.addEventListener('error', ({ error }) => { + t.ok(error) + }) + }) + + t.teardown(server.close.bind(server)) +}) + +test('Server sends invalid Sec-WebSocket-Extensions header', (t) => { + const uid = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11' + const { createHash } = require('crypto') + + t.plan(2) + + const server = createServer((req, res) => { + const key = req.headers['sec-websocket-key'] + t.ok(key) + + const accept = createHash('sha1').update(key + uid).digest('base64') + + res.setHeader('Upgrade', 'websocket') + res.setHeader('Connection', 'upgrade') + res.setHeader('Sec-WebSocket-Accept', accept) + res.setHeader('Sec-WebSocket-Protocol', 'echo') // <-- + res.statusCode = 101 + + res.end() + }).listen(0, () => { + const ws = new WebSocket(`ws://localhost:${server.address().port}`, 'chat') + + ws.onopen = t.fail + + ws.addEventListener('error', ({ error }) => { + t.ok(error) + }) + }) + + t.teardown(server.close.bind(server)) +}) diff --git a/test/websocket/ping-pong.js b/test/websocket/ping-pong.js new file mode 100644 index 0000000..b7c4694 --- /dev/null +++ b/test/websocket/ping-pong.js @@ -0,0 +1,46 @@ +'use strict' + +const { test } = require('tap') +const { WebSocketServer } = require('ws') +const diagnosticsChannel = require('diagnostics_channel') +const { WebSocket } = require('../..') + +test('Receives ping and parses body', (t) => { + t.plan(1) + + const server = new WebSocketServer({ port: 0 }) + + server.on('connection', (ws) => { + ws.ping('Hello, world') + }) + + t.teardown(server.close.bind(server)) + + const ws = new WebSocket(`ws://localhost:${server.address().port}`) + ws.onerror = ws.onmessage = t.fail + + diagnosticsChannel.channel('undici:websocket:ping').subscribe(({ payload }) => { + t.same(payload, Buffer.from('Hello, world')) + ws.close() + }) +}) + +test('Receives pong and parses body', (t) => { + t.plan(1) + + const server = new WebSocketServer({ port: 0 }) + + server.on('connection', (ws) => { + ws.pong('Pong') + }) + + t.teardown(server.close.bind(server)) + + const ws = new WebSocket(`ws://localhost:${server.address().port}`) + ws.onerror = ws.onmessage = t.fail + + diagnosticsChannel.channel('undici:websocket:pong').subscribe(({ payload }) => { + t.same(payload, Buffer.from('Pong')) + ws.close() + }) +}) diff --git a/test/websocket/receive.js b/test/websocket/receive.js new file mode 100644 index 0000000..a669022 --- /dev/null +++ b/test/websocket/receive.js @@ -0,0 +1,60 @@ +'use strict' + +const { test } = require('tap') +const { WebSocketServer } = require('ws') +const { WebSocket } = require('../..') + +test('Receiving a frame with a payload length > 2^31-1 bytes', (t) => { + t.plan(1) + + const server = new WebSocketServer({ port: 0 }) + + server.on('connection', (ws) => { + const socket = ws._socket + + socket.write(Buffer.from([0x81, 0x7F, 0xCA, 0xE5, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00])) + }) + + const ws = new WebSocket(`ws://localhost:${server.address().port}`) + + t.teardown(() => { + ws.close() + server.close() + }) + + ws.onmessage = t.fail + + ws.addEventListener('error', (event) => { + t.type(event.error, Error) // error event is emitted + }) +}) + +test('Receiving an ArrayBuffer', (t) => { + t.plan(3) + + const server = new WebSocketServer({ port: 0 }) + + server.on('connection', (ws) => { + ws.on('message', (data, isBinary) => { + ws.send(data, { binary: true }) + + ws.close(1000) + }) + }) + + t.teardown(server.close.bind(server)) + const ws = new WebSocket(`ws://localhost:${server.address().port}`) + + ws.addEventListener('open', () => { + ws.binaryType = 'what' + t.equal(ws.binaryType, 'blob') + + ws.binaryType = 'arraybuffer' // <-- + ws.send('Hello') + }) + + ws.addEventListener('message', ({ data }) => { + t.type(data, ArrayBuffer) + t.same(Buffer.from(data), Buffer.from('Hello')) + }) +}) diff --git a/test/websocket/send.js b/test/websocket/send.js new file mode 100644 index 0000000..ac295fd --- /dev/null +++ b/test/websocket/send.js @@ -0,0 +1,216 @@ +'use strict' + +const { test } = require('tap') +const { WebSocketServer } = require('ws') +const { Blob } = require('buffer') +const { WebSocket } = require('../..') + +// the following three tests exercise different code paths because of the three +// different ways a payload length may be specified in a WebSocket frame +// (https://datatracker.ietf.org/doc/html/rfc6455#section-5.2) + +test('Sending >= 2^16 bytes', (t) => { + t.plan(3) + + const server = new WebSocketServer({ port: 0 }) + + server.on('connection', (ws) => { + ws.on('message', (m, isBinary) => { + ws.send(m, { binary: isBinary }) + }) + }) + + const payload = Buffer.allocUnsafe(2 ** 16).fill('Hello') + + const ws = new WebSocket(`ws://localhost:${server.address().port}`) + + ws.addEventListener('open', () => { + ws.send(payload) + }) + + ws.addEventListener('message', async ({ data }) => { + t.type(data, Blob) + t.equal(data.size, payload.length) + t.same(Buffer.from(await data.arrayBuffer()), payload) + + ws.close() + server.close() + }) +}) + +test('Sending >= 126, < 2^16 bytes', (t) => { + t.plan(3) + + const server = new WebSocketServer({ port: 0 }) + + server.on('connection', (ws) => { + ws.on('message', (m, isBinary) => { + ws.send(m, { binary: isBinary }) + }) + }) + + const payload = Buffer.allocUnsafe(126).fill('Hello') + + const ws = new WebSocket(`ws://localhost:${server.address().port}`) + + ws.addEventListener('open', () => { + ws.send(payload) + }) + + ws.addEventListener('message', async ({ data }) => { + t.type(data, Blob) + t.equal(data.size, payload.length) + t.same(Buffer.from(await data.arrayBuffer()), payload) + + ws.close() + server.close() + }) +}) + +test('Sending < 126 bytes', (t) => { + t.plan(3) + + const server = new WebSocketServer({ port: 0 }) + + server.on('connection', (ws) => { + ws.on('message', (m, isBinary) => { + ws.send(m, { binary: isBinary }) + }) + }) + + const payload = Buffer.allocUnsafe(125).fill('Hello') + + const ws = new WebSocket(`ws://localhost:${server.address().port}`) + + ws.addEventListener('open', () => { + ws.send(payload) + }) + + ws.addEventListener('message', async ({ data }) => { + t.type(data, Blob) + t.equal(data.size, payload.length) + t.same(Buffer.from(await data.arrayBuffer()), payload) + + ws.close() + server.close() + }) +}) + +test('Sending data after close', (t) => { + t.plan(2) + + const server = new WebSocketServer({ port: 0 }) + + server.on('connection', (ws) => { + t.pass() + + ws.on('message', t.fail) + }) + + t.teardown(server.close.bind(server)) + const ws = new WebSocket(`ws://localhost:${server.address().port}`) + + ws.addEventListener('open', () => { + ws.close() + ws.send('Some message') + + t.pass() + }) + + ws.addEventListener('error', t.fail) +}) + +test('Sending data before connected', (t) => { + t.plan(2) + + const server = new WebSocketServer({ port: 0 }) + + t.teardown(server.close.bind(server)) + const ws = new WebSocket(`ws://localhost:${server.address().port}`) + + t.throws( + () => ws.send('Not sent'), + { + name: 'InvalidStateError', + constructor: DOMException + } + ) + + t.equal(ws.readyState, WebSocket.CONNECTING) +}) + +test('Sending data to a server', (t) => { + t.plan(3) + + t.test('Send with string', (t) => { + t.plan(2) + + const server = new WebSocketServer({ port: 0 }) + + server.on('connection', (ws) => { + ws.on('message', (data, isBinary) => { + t.notOk(isBinary, 'Received text frame') + t.same(data, Buffer.from('message')) + + ws.close(1000) + }) + }) + + t.teardown(server.close.bind(server)) + + const ws = new WebSocket(`ws://localhost:${server.address().port}`) + + ws.addEventListener('open', () => { + ws.send('message') + }) + }) + + t.test('Send with ArrayBuffer', (t) => { + t.plan(2) + + const message = new TextEncoder().encode('message') + const ab = new ArrayBuffer(7) + new Uint8Array(ab).set(message) + + const server = new WebSocketServer({ port: 0 }) + + server.on('connection', (ws) => { + ws.on('message', (data, isBinary) => { + t.ok(isBinary) + t.same(new Uint8Array(data), message) + + ws.close(1000) + }) + }) + + t.teardown(server.close.bind(server)) + const ws = new WebSocket(`ws://localhost:${server.address().port}`) + + ws.addEventListener('open', () => { + ws.send(ab) + }) + }) + + t.test('Send with Blob', (t) => { + t.plan(2) + + const blob = new Blob(['hello']) + const server = new WebSocketServer({ port: 0 }) + + server.on('connection', (ws) => { + ws.on('message', (data, isBinary) => { + t.ok(isBinary) + t.same(data, Buffer.from('hello')) + + ws.close(1000) + }) + }) + + t.teardown(server.close.bind(server)) + const ws = new WebSocket(`ws://localhost:${server.address().port}`) + + ws.addEventListener('open', () => { + ws.send(blob) + }) + }) +}) diff --git a/test/websocket/websocketinit.js b/test/websocket/websocketinit.js new file mode 100644 index 0000000..4dda3b4 --- /dev/null +++ b/test/websocket/websocketinit.js @@ -0,0 +1,45 @@ +'use strict' + +const { test } = require('tap') +const { WebSocketServer } = require('ws') +const { WebSocket, Dispatcher, Agent } = require('../..') + +test('WebSocketInit', (t) => { + t.plan(2) + + class WsDispatcher extends Dispatcher { + constructor () { + super() + this.agent = new Agent() + } + + dispatch () { + t.pass() + return this.agent.dispatch(...arguments) + } + } + + t.test('WebSocketInit as 2nd param', (t) => { + t.plan(1) + + const server = new WebSocketServer({ port: 0 }) + + server.on('connection', (ws) => { + ws.send(Buffer.from('hello, world')) + }) + + t.teardown(server.close.bind(server)) + + const ws = new WebSocket(`ws://localhost:${server.address().port}`, { + dispatcher: new WsDispatcher() + }) + + ws.onerror = t.fail + + ws.addEventListener('message', async (event) => { + t.equal(await event.data.text(), 'hello, world') + server.close() + ws.close() + }) + }) +}) |