saikumaraili
test
23ac194
'use strict'
const net = require('node:net')
const test = require('tap').test
const Fastify = require('fastify')
const fastifyWebsocket = require('..')
const WebSocket = require('ws')
const get = require('node:http').get
test('Should expose a websocket on prefixed route', t => {
t.plan(4)
const fastify = Fastify()
t.teardown(() => fastify.close())
fastify.register(fastifyWebsocket)
fastify.register(
function (instance, _opts, next) {
instance.get('/echo', { websocket: true }, function (socket) {
t.equal(this.prefix, '/baz')
socket.send('hello client')
t.teardown(() => socket.terminate())
socket.once('message', (chunk) => {
t.equal(chunk.toString(), 'hello server')
})
})
next()
},
{ prefix: '/baz' }
)
fastify.listen({ port: 0 }, err => {
t.error(err)
const ws = new WebSocket('ws://localhost:' + (fastify.server.address()).port + '/baz/echo')
const client = WebSocket.createWebSocketStream(ws, { encoding: 'utf8' })
t.teardown(client.destroy.bind(client))
client.setEncoding('utf8')
client.write('hello server')
client.once('data', chunk => {
t.equal(chunk, 'hello client')
client.end()
})
})
})
test('Should expose a websocket on prefixed route with /', t => {
t.plan(3)
const fastify = Fastify()
t.teardown(() => fastify.close())
fastify.register(fastifyWebsocket)
fastify.register(
function (instance, _opts, next) {
instance.get('/', { websocket: true }, (socket) => {
socket.send('hello client')
t.teardown(() => socket.terminate())
socket.once('message', (chunk) => {
t.equal(chunk.toString(), 'hello server')
})
})
next()
},
{ prefix: '/baz' }
)
fastify.listen({ port: 0 }, err => {
t.error(err)
const ws = new WebSocket('ws://localhost:' + (fastify.server.address()).port + '/baz')
const client = WebSocket.createWebSocketStream(ws, { encoding: 'utf8' })
t.teardown(client.destroy.bind(client))
client.setEncoding('utf8')
client.write('hello server')
client.once('data', chunk => {
t.equal(chunk, 'hello client')
client.end()
})
})
})
test('Should expose websocket and http route', t => {
t.plan(4)
const fastify = Fastify()
t.teardown(() => fastify.close())
fastify.register(fastifyWebsocket)
fastify.register(
function (instance, _opts, next) {
instance.route({
method: 'GET',
url: '/echo',
handler: (_request, reply) => {
reply.send({ hello: 'world' })
},
wsHandler: (socket) => {
socket.send('hello client')
t.teardown(() => socket.terminate())
socket.once('message', (chunk) => {
t.equal(chunk.toString(), 'hello server')
})
}
})
next()
},
{ prefix: '/baz' }
)
fastify.listen({ port: 0 }, err => {
t.error(err)
const url = '//localhost:' + (fastify.server.address()).port + '/baz/echo'
const ws = new WebSocket('ws:' + url)
const client = WebSocket.createWebSocketStream(ws, { encoding: 'utf8' })
t.teardown(client.destroy.bind(client))
client.setEncoding('utf8')
client.write('hello server')
client.once('data', chunk => {
t.equal(chunk, 'hello client')
client.end()
})
get('http:' + url, function (response) {
let data = ''
// A chunk of data has been recieved.
response.on('data', (chunk) => {
data += chunk
})
// The whole response has been received. Print out the result.
response.on('end', () => {
t.equal(data, '{"hello":"world"}')
})
})
})
})
test('Should close on unregistered path (with no wildcard route websocket handler defined)', t => {
t.plan(2)
const fastify = Fastify()
t.teardown(() => fastify.close())
fastify
.register(fastifyWebsocket)
.register(async function () {
fastify.get('/*', (_request, reply) => {
reply.send('hello world')
})
fastify.get('/echo', { websocket: true }, (socket) => {
socket.on('message', message => {
try {
socket.send(message)
} catch (err) {
socket.send(err.message)
}
})
t.teardown(() => socket.terminate())
})
})
fastify.listen({ port: 0 }, err => {
t.error(err)
const ws = new WebSocket('ws://localhost:' + (fastify.server.address()).port)
const client = WebSocket.createWebSocketStream(ws, { encoding: 'utf8' })
t.teardown(client.destroy.bind(client))
ws.on('close', () => {
t.pass()
})
})
})
test('Should use wildcard websocket route when (with a normal http wildcard route defined as well)', t => {
t.plan(2)
const fastify = Fastify()
t.teardown(() => fastify.close())
fastify
.register(fastifyWebsocket)
.register(async function (fastify) {
fastify.route({
method: 'GET',
url: '/*',
handler: (_, reply) => {
reply.send({ hello: 'world' })
},
wsHandler: (socket) => {
socket.send('hello client')
t.teardown(() => socket.terminate())
socket.once('message', () => {
socket.close()
})
}
})
})
fastify.listen({ port: 0 }, err => {
t.error(err)
const ws = new WebSocket('ws://localhost:' + (fastify.server.address()).port)
const client = WebSocket.createWebSocketStream(ws, { encoding: 'utf8' })
t.teardown(client.destroy.bind(client))
client.once('data', chunk => {
t.equal(chunk, 'hello client')
client.end()
})
})
})
test('Should call wildcard route handler on unregistered path', t => {
t.plan(3)
const fastify = Fastify()
t.teardown(() => fastify.close())
fastify
.register(fastifyWebsocket)
.register(async function (fastify) {
fastify.get('/*', { websocket: true }, (socket) => {
socket.on('message', () => {
try {
socket.send('hi from wildcard route handler')
} catch (err) {
socket.send(err.message)
}
})
t.teardown(() => socket.terminate())
})
})
fastify.get('/echo', { websocket: true }, (socket) => {
socket.on('message', () => {
try {
socket.send('hi from /echo handler')
} catch (err) {
socket.send(err.message)
}
})
t.teardown(() => socket.terminate())
})
fastify.listen({ port: 0 }, err => {
t.error(err)
const ws = new WebSocket('ws://localhost:' + (fastify.server.address()).port)
const client = WebSocket.createWebSocketStream(ws, { encoding: 'utf8' })
t.teardown(client.destroy.bind(client))
ws.on('open', () => {
ws.send('hi from client')
client.end()
})
ws.on('message', message => {
t.equal(message.toString(), 'hi from wildcard route handler')
})
ws.on('close', () => {
t.pass()
})
})
})
test('Should invoke the correct handler depending on the headers', t => {
t.plan(4)
const fastify = Fastify()
t.teardown(() => fastify.close())
fastify.register(fastifyWebsocket)
fastify.register(async function () {
fastify.route({
method: 'GET',
url: '/',
handler: (_request, reply) => {
reply.send('hi from handler')
},
wsHandler: (socket) => {
socket.send('hi from wsHandler')
t.teardown(() => socket.terminate())
}
})
})
fastify.listen({ port: 0 }, err => {
t.error(err)
const port = fastify.server.address().port
const httpClient = net.createConnection({ port }, () => {
httpClient.write('GET / HTTP/1.1\r\nHOST: localhost\r\n\r\n')
httpClient.once('data', data => {
t.match(data.toString(), /hi from handler/i)
httpClient.end()
})
})
const wsClient = net.createConnection({ port }, () => {
wsClient.write('GET / HTTP/1.1\r\nConnection: upgrade\r\nUpgrade: websocket\r\nSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\nSec-WebSocket-Version: 13\r\n\r\n')
wsClient.once('data', data => {
t.match(data.toString(), /hi from wsHandler/i)
wsClient.end(() => { t.pass() })
})
})
})
})
test('Should call the wildcard handler if a no other non-websocket route with path exists', t => {
const fastify = Fastify()
t.teardown(() => fastify.close())
fastify.register(fastifyWebsocket)
fastify.register(async function (fastify) {
fastify.get('/*', { websocket: true }, (socket) => {
t.ok('called', 'wildcard handler')
socket.close()
t.teardown(() => socket.terminate())
})
fastify.get('/http', (_request, reply) => {
t.fail('Should not call http handler')
reply.send('http route')
})
})
fastify.listen({ port: 0 }, err => {
t.error(err)
const ws = new WebSocket('ws://localhost:' + (fastify.server.address()).port + '/http2')
const client = WebSocket.createWebSocketStream(ws, { encoding: 'utf8' })
t.teardown(client.destroy.bind(client))
client.setEncoding('utf8')
client.end(() => { t.end() })
})
})
test('Should close the connection if a non-websocket route with path exists', t => {
t.plan(2)
const fastify = Fastify()
t.teardown(() => fastify.close())
fastify.register(fastifyWebsocket)
fastify.register(async function (fastify) {
fastify.get('/*', { websocket: true }, (socket) => {
t.fail('called', 'wildcard handler')
t.teardown(() => socket.terminate())
})
fastify.get('/http', (_request, reply) => {
t.fail('Should not call /http handler')
reply.send('http route')
})
})
fastify.listen({ port: 0 }, err => {
t.error(err)
const ws = new WebSocket('ws://localhost:' + (fastify.server.address()).port + '/http')
ws.on('close', (code) => {
t.equal(code, 1005, 'closed websocket')
t.end()
})
})
})
test('Should throw on wrong HTTP method', t => {
t.plan(2)
const fastify = Fastify()
t.teardown(() => fastify.close())
fastify.register(fastifyWebsocket)
fastify.register(async function (fastify) {
fastify.post('/echo', { websocket: true }, (socket) => {
socket.on('message', message => {
try {
socket.send(message)
} catch (err) {
socket.send(err.message)
}
})
t.teardown(() => socket.terminate())
})
fastify.get('/http', (_request, reply) => {
t.fail('Should not call /http handler')
reply.send('http route')
})
})
fastify.listen({ port: 0 }, (err) => {
t.ok(err)
t.equal(err.message, 'websocket handler can only be declared in GET method')
})
})
test('Should throw on invalid wsHandler', async t => {
t.plan(1)
const fastify = Fastify()
t.teardown(() => fastify.close())
await fastify.register(fastifyWebsocket)
try {
fastify.route({
method: 'GET',
url: '/echo',
handler: (_, reply) => {
reply.send({ hello: 'world' })
},
wsHandler: 'hello'
}, { prefix: '/baz' })
} catch (err) {
t.equal(err.message, 'invalid wsHandler function')
}
})
test('Should open on registered path', t => {
t.plan(2)
const fastify = Fastify()
t.teardown(() => fastify.close())
fastify.register(fastifyWebsocket)
fastify.register(async function (fastify) {
fastify.get('/echo', { websocket: true }, (socket) => {
socket.on('message', message => {
try {
socket.send(message)
} catch (err) {
socket.send(err.message)
}
})
t.teardown(() => socket.terminate())
})
})
fastify.listen({ port: 0 }, err => {
t.error(err)
const ws = new WebSocket('ws://localhost:' + (fastify.server.address()).port + '/echo')
ws.on('open', () => {
t.pass()
client.end()
})
const client = WebSocket.createWebSocketStream(ws, { encoding: 'utf8' })
t.teardown(client.destroy.bind(client))
})
})
test('Should send message and close', t => {
t.plan(5)
const fastify = Fastify()
t.teardown(() => fastify.close())
fastify.register(fastifyWebsocket)
fastify.register(async function (fastify) {
fastify.get('/', { websocket: true }, (socket) => {
socket.on('message', message => {
t.equal(message.toString(), 'hi from client')
socket.send('hi from server')
})
socket.on('close', () => {
t.pass()
})
t.teardown(() => socket.terminate())
})
})
fastify.listen({ port: 0 }, err => {
t.error(err)
const ws = new WebSocket('ws://localhost:' + (fastify.server.address()).port + '/')
const client = WebSocket.createWebSocketStream(ws, { encoding: 'utf8' })
t.teardown(client.destroy.bind(client))
ws.on('message', message => {
t.equal(message.toString(), 'hi from server')
})
ws.on('open', () => {
ws.send('hi from client')
client.end()
})
ws.on('close', () => {
t.pass()
})
})
})
test('Should return 404 on http request', t => {
const fastify = Fastify()
t.teardown(() => fastify.close())
fastify.register(fastifyWebsocket)
fastify.register(async function (fastify) {
fastify.get('/', { websocket: true }, (socket) => {
socket.on('message', message => {
t.equal(message.toString(), 'hi from client')
socket.send('hi from server')
})
socket.on('close', () => {
t.pass()
})
t.teardown(() => socket.terminate())
})
})
fastify.inject({
method: 'GET',
url: '/'
}).then((response) => {
t.equal(response.payload, '')
t.equal(response.statusCode, 404)
t.end()
})
})
test('Should pass route params to per-route handlers', t => {
const fastify = Fastify()
t.teardown(() => fastify.close())
fastify.register(fastifyWebsocket)
fastify.register(async function (fastify) {
fastify.get('/ws', { websocket: true }, (socket, request) => {
const params = request.params
t.equal(Object.keys(params).length, 0, 'params are empty')
socket.send('empty')
socket.close()
})
fastify.get('/ws/:id', { websocket: true }, (socket, request) => {
const params = request.params
t.equal(params.id, 'foo', 'params are correct')
socket.send(params.id)
socket.close()
})
})
fastify.listen({ port: 0 }, err => {
let pending = 2
t.error(err)
const ws = new WebSocket(
'ws://localhost:' + (fastify.server.address()).port + '/ws/foo'
)
const client = WebSocket.createWebSocketStream(ws, { encoding: 'utf8' })
const ws2 = new WebSocket(
'ws://localhost:' + (fastify.server.address()).port + '/ws'
)
const client2 = WebSocket.createWebSocketStream(ws2, { encoding: 'utf8' })
t.teardown(client.destroy.bind(client))
t.teardown(client2.destroy.bind(client2))
client.setEncoding('utf8')
client2.setEncoding('utf8')
client.once('data', chunk => {
t.equal(chunk, 'foo')
client.end()
if (--pending === 0) t.end()
})
client2.once('data', chunk => {
t.equal(chunk, 'empty')
client2.end()
if (--pending === 0) t.end()
})
})
})
test('Should not throw error when register empty get with prefix', t => {
const fastify = Fastify()
t.teardown(() => fastify.close())
fastify.register(fastifyWebsocket)
fastify.register(
function (instance, _opts, next) {
instance.get('/', { websocket: true }, (socket) => {
socket.on('message', message => {
t.equal(message.toString(), 'hi from client')
socket.send('hi from server')
})
})
next()
},
{ prefix: '/baz' }
)
fastify.listen({ port: 0 }, err => {
if (err) t.error(err)
const ws = new WebSocket(
'ws://localhost:' + fastify.server.address().port + '/baz/'
)
ws.on('open', () => {
t.pass('Done')
ws.close()
t.end()
})
})
})
test('Should expose fastify instance to websocket per-route handler', t => {
const fastify = Fastify()
t.teardown(() => fastify.close())
fastify.register(fastifyWebsocket)
fastify.register(async function (fastify) {
fastify.get('/ws', { websocket: true }, function wsHandler (socket) {
t.equal(this, fastify, 'this is bound to fastify server')
socket.send('empty')
socket.close()
})
})
fastify.listen({ port: 0 }, err => {
t.error(err)
const ws = new WebSocket(
'ws://localhost:' + (fastify.server.address()).port + '/ws'
)
const client = WebSocket.createWebSocketStream(ws, { encoding: 'utf8' })
t.teardown(client.destroy.bind(client))
client.setEncoding('utf8')
client.once('data', chunk => {
t.equal(chunk, 'empty')
client.end()
t.end()
})
})
})
test('Should have access to decorators in per-route handler', t => {
const fastify = Fastify()
t.teardown(() => fastify.close())
fastify.decorateRequest('str', 'it works!')
fastify.register(fastifyWebsocket)
fastify.register(async function (fastify) {
fastify.get('/ws', { websocket: true }, function wsHandler (socket, request) {
t.equal(request.str, 'it works!', 'decorator is accessible')
socket.send('empty')
socket.close()
})
})
fastify.listen({ port: 0 }, err => {
t.error(err)
const ws = new WebSocket('ws://localhost:' + (fastify.server.address()).port + '/ws')
const client = WebSocket.createWebSocketStream(ws, { encoding: 'utf8' })
t.teardown(client.destroy.bind(client))
client.once('data', chunk => {
t.equal(chunk, 'empty')
client.end()
t.end()
})
})
})
test('should call `destroy` when exception is thrown inside async handler', t => {
t.plan(2)
const fastify = Fastify()
t.teardown(() => fastify.close())
fastify.register(fastifyWebsocket)
fastify.register(async function (fastify) {
fastify.get('/ws', { websocket: true }, async function wsHandler (socket) {
socket.on('close', code => {
t.equal(code, 1006)
t.end()
})
throw new Error('something wrong')
})
})
fastify.listen({ port: 0 }, err => {
t.error(err)
const ws = new WebSocket(
'ws://localhost:' + (fastify.server.address()).port + '/ws'
)
const client = WebSocket.createWebSocketStream(ws, { encoding: 'utf8' })
client.on('error', (_) => { })
t.teardown(client.destroy.bind(client))
})
})
test('should call default non websocket fastify route when no match is found', t => {
t.plan(2)
const fastify = Fastify()
t.teardown(() => fastify.close())
fastify.register(fastifyWebsocket)
fastify.register(async function (fastify) {
fastify.get('/ws', function handler (_request, reply) {
reply.send({ hello: 'world' })
})
})
fastify.listen({ port: 0 }, err => {
t.error(err)
get('http://localhost:' + (fastify.server.address()).port + '/wrong-route', function (response) {
t.equal(response.statusCode, 404)
t.end()
})
})
})
test('register a non websocket route', t => {
t.plan(2)
const fastify = Fastify()
t.teardown(() => fastify.close())
fastify.register(fastifyWebsocket)
fastify.register(async function (fastify) {
fastify.get('/ws', function handler (_request, reply) {
reply.send({ hello: 'world' })
})
})
fastify.listen({ port: 0 }, err => {
t.error(err)
get('http://localhost:' + (fastify.server.address()).port + '/ws', function (response) {
let data = ''
response.on('data', (chunk) => {
data += chunk
})
response.on('end', () => {
t.equal(data, '{"hello":"world"}')
t.end()
})
})
})
})