fastify
Version:
Fast and low overhead web framework, for Node.js
1,953 lines (1,649 loc) • 96.4 kB
JavaScript
'use strict'
const { test } = require('node:test')
const sget = require('simple-get').concat
const stream = require('node:stream')
const Fastify = require('..')
const fp = require('fastify-plugin')
const fs = require('node:fs')
const split = require('split2')
const symbols = require('../lib/symbols.js')
const payload = { hello: 'world' }
const proxyquire = require('proxyquire')
const { connect } = require('node:net')
const { sleep, getServerUrl } = require('./helper')
const { waitForCb } = require('./toolkit.js')
process.removeAllListeners('warning')
test('hooks', (t, testDone) => {
t.plan(49)
const fastify = Fastify({ exposeHeadRoutes: false })
try {
fastify.addHook('preHandler', function (request, reply, done) {
t.assert.strictEqual(request.test, 'the request is coming')
t.assert.strictEqual(reply.test, 'the reply has come')
if (request.raw.method === 'HEAD') {
done(new Error('some error'))
} else {
done()
}
})
t.assert.ok('should pass')
} catch (e) {
t.assert.fail()
}
try {
fastify.addHook('preHandler', null)
} catch (e) {
t.assert.strictEqual(e.code, 'FST_ERR_HOOK_INVALID_HANDLER')
t.assert.strictEqual(e.message, 'preHandler hook should be a function, instead got null')
t.assert.ok('should pass')
}
try {
fastify.addHook('preParsing')
} catch (e) {
t.assert.strictEqual(e.code, 'FST_ERR_HOOK_INVALID_HANDLER')
t.assert.strictEqual(e.message, 'preParsing hook should be a function, instead got undefined')
t.assert.ok('should pass')
}
try {
fastify.addHook('preParsing', function (request, reply, payload, done) {
request.preParsing = true
t.assert.strictEqual(request.test, 'the request is coming')
t.assert.strictEqual(reply.test, 'the reply has come')
done()
})
t.assert.ok('should pass')
} catch (e) {
t.assert.fail()
}
try {
fastify.addHook('preParsing', function (request, reply, payload, done) {
request.preParsing = true
t.assert.strictEqual(request.test, 'the request is coming')
t.assert.strictEqual(reply.test, 'the reply has come')
done()
})
t.assert.ok('should pass')
} catch (e) {
t.assert.fail()
}
try {
fastify.addHook('preValidation', function (request, reply, done) {
t.assert.strictEqual(request.preParsing, true)
t.assert.strictEqual(request.test, 'the request is coming')
t.assert.strictEqual(reply.test, 'the reply has come')
done()
})
t.assert.ok('should pass')
} catch (e) {
t.assert.fail()
}
try {
fastify.addHook('preSerialization', function (request, reply, payload, done) {
t.assert.ok('preSerialization called')
done()
})
t.assert.ok('should pass')
} catch (e) {
t.assert.fail()
}
try {
fastify.addHook('onRequest', function (request, reply, done) {
request.test = 'the request is coming'
reply.test = 'the reply has come'
if (request.raw.method === 'DELETE') {
done(new Error('some error'))
} else {
done()
}
})
t.assert.ok('should pass')
} catch (e) {
t.assert.fail()
}
fastify.addHook('onResponse', function (request, reply, done) {
t.assert.ok('onResponse called')
done()
})
fastify.addHook('onSend', function (req, reply, thePayload, done) {
t.assert.ok('onSend called')
done()
})
fastify.route({
method: 'GET',
url: '/',
handler: function (req, reply) {
t.assert.strictEqual(req.test, 'the request is coming')
t.assert.strictEqual(reply.test, 'the reply has come')
reply.code(200).send(payload)
},
onResponse: function (req, reply, done) {
t.assert.ok('onResponse inside hook')
},
response: {
200: {
type: 'object'
}
}
})
fastify.head('/', function (req, reply) {
reply.code(200).send(payload)
})
fastify.delete('/', function (req, reply) {
reply.code(200).send(payload)
})
fastify.listen({ port: 0 }, err => {
t.assert.ifError(err)
t.after(() => { fastify.close() })
const completion = waitForCb({ steps: 3 })
sget({
method: 'GET',
url: 'http://127.0.0.1:' + fastify.server.address().port
}, (err, response, body) => {
t.assert.ifError(err)
t.assert.strictEqual(response.statusCode, 200)
t.assert.strictEqual(response.headers['content-length'], '' + body.length)
t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' })
completion.stepIn()
})
sget({
method: 'HEAD',
url: 'http://127.0.0.1:' + fastify.server.address().port
}, (err, response, body) => {
t.assert.ifError(err)
t.assert.strictEqual(response.statusCode, 500)
completion.stepIn()
})
sget({
method: 'DELETE',
url: 'http://127.0.0.1:' + fastify.server.address().port
}, (err, response, body) => {
t.assert.ifError(err)
t.assert.strictEqual(response.statusCode, 500)
completion.stepIn()
})
completion.patience.then(testDone)
})
})
test('onRequest hook should support encapsulation / 1', (t, testDone) => {
t.plan(5)
const fastify = Fastify()
fastify.register((instance, opts, done) => {
instance.addHook('onRequest', (req, reply, done) => {
t.assert.strictEqual(req.raw.url, '/plugin')
done()
})
instance.get('/plugin', (request, reply) => {
reply.send()
})
done()
})
fastify.get('/root', (request, reply) => {
reply.send()
})
fastify.inject('/root', (err, res) => {
t.assert.ifError(err)
t.assert.strictEqual(res.statusCode, 200)
fastify.inject('/plugin', (err, res) => {
t.assert.ifError(err)
t.assert.strictEqual(res.statusCode, 200)
testDone()
})
})
})
test('onRequest hook should support encapsulation / 2', (t, testDone) => {
t.plan(3)
const fastify = Fastify()
let pluginInstance
fastify.addHook('onRequest', () => {})
fastify.register((instance, opts, done) => {
instance.addHook('onRequest', () => {})
pluginInstance = instance
done()
})
fastify.ready(err => {
t.assert.ifError(err)
t.assert.strictEqual(fastify[symbols.kHooks].onRequest.length, 1)
t.assert.strictEqual(pluginInstance[symbols.kHooks].onRequest.length, 2)
testDone()
})
})
test('onRequest hook should support encapsulation / 3', (t, testDone) => {
t.plan(20)
const fastify = Fastify()
fastify.decorate('hello', 'world')
fastify.addHook('onRequest', function (req, reply, done) {
t.assert.ok(this.hello)
t.assert.ok(this.hello2)
req.first = true
done()
})
fastify.decorate('hello2', 'world')
fastify.get('/first', (req, reply) => {
t.assert.ok(req.first)
t.assert.ok(!req.second)
reply.send({ hello: 'world' })
})
fastify.register((instance, opts, done) => {
instance.decorate('hello3', 'world')
instance.addHook('onRequest', function (req, reply, done) {
t.assert.ok(this.hello)
t.assert.ok(this.hello2)
t.assert.ok(this.hello3)
req.second = true
done()
})
instance.get('/second', (req, reply) => {
t.assert.ok(req.first)
t.assert.ok(req.second)
reply.send({ hello: 'world' })
})
done()
})
fastify.listen({ port: 0 }, err => {
t.assert.ifError(err)
t.after(() => { fastify.close() })
const completion = waitForCb({ steps: 2 })
sget({
method: 'GET',
url: 'http://127.0.0.1:' + fastify.server.address().port + '/first'
}, (err, response, body) => {
t.assert.ifError(err)
t.assert.strictEqual(response.statusCode, 200)
t.assert.strictEqual(response.headers['content-length'], '' + body.length)
t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' })
completion.stepIn()
})
sget({
method: 'GET',
url: 'http://127.0.0.1:' + fastify.server.address().port + '/second'
}, (err, response, body) => {
t.assert.ifError(err)
t.assert.strictEqual(response.statusCode, 200)
t.assert.strictEqual(response.headers['content-length'], '' + body.length)
t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' })
completion.stepIn()
})
completion.patience.then(testDone)
})
})
test('preHandler hook should support encapsulation / 5', (t, testDone) => {
t.plan(17)
const fastify = Fastify()
t.after(() => { fastify.close() })
fastify.decorate('hello', 'world')
fastify.addHook('preHandler', function (req, res, done) {
t.assert.ok(this.hello)
req.first = true
done()
})
fastify.get('/first', (req, reply) => {
t.assert.ok(req.first)
t.assert.ok(!req.second)
reply.send({ hello: 'world' })
})
fastify.register((instance, opts, done) => {
instance.decorate('hello2', 'world')
instance.addHook('preHandler', function (req, res, done) {
t.assert.ok(this.hello)
t.assert.ok(this.hello2)
req.second = true
done()
})
instance.get('/second', (req, reply) => {
t.assert.ok(req.first)
t.assert.ok(req.second)
reply.send({ hello: 'world' })
})
done()
})
fastify.listen({ port: 0 }, err => {
t.assert.ifError(err)
const completion = waitForCb({ steps: 2 })
sget({
method: 'GET',
url: 'http://127.0.0.1:' + fastify.server.address().port + '/first'
}, (err, response, body) => {
t.assert.ifError(err)
t.assert.strictEqual(response.statusCode, 200)
t.assert.strictEqual(response.headers['content-length'], '' + body.length)
t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' })
completion.stepIn()
})
sget({
method: 'GET',
url: 'http://127.0.0.1:' + fastify.server.address().port + '/second'
}, (err, response, body) => {
t.assert.ifError(err)
t.assert.strictEqual(response.statusCode, 200)
t.assert.strictEqual(response.headers['content-length'], '' + body.length)
t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' })
completion.stepIn()
})
completion.patience.then(testDone)
})
})
test('onRoute hook should be called / 1', (t, testDone) => {
t.plan(2)
const fastify = Fastify({ exposeHeadRoutes: false })
fastify.register((instance, opts, done) => {
instance.addHook('onRoute', () => {
t.assert.ok('should pass')
})
instance.get('/', opts, function (req, reply) {
reply.send()
})
done()
})
fastify.ready(err => {
t.assert.ifError(err)
testDone()
})
})
test('onRoute hook should be called / 2', (t, testDone) => {
t.plan(5)
let firstHandler = 0
let secondHandler = 0
const fastify = Fastify({ exposeHeadRoutes: false })
fastify.addHook('onRoute', (route) => {
t.assert.ok('should pass')
firstHandler++
})
fastify.register((instance, opts, done) => {
instance.addHook('onRoute', (route) => {
t.assert.ok('should pass')
secondHandler++
})
instance.get('/', opts, function (req, reply) {
reply.send()
})
done()
})
.after(() => {
t.assert.strictEqual(firstHandler, 1)
t.assert.strictEqual(secondHandler, 1)
})
fastify.ready(err => {
t.assert.ifError(err)
testDone()
})
})
test('onRoute hook should be called / 3', (t, testDone) => {
t.plan(5)
const fastify = Fastify({ exposeHeadRoutes: false })
function handler (req, reply) {
reply.send()
}
fastify.addHook('onRoute', (route) => {
t.assert.ok('should pass')
})
fastify.register((instance, opts, done) => {
instance.addHook('onRoute', (route) => {
t.assert.ok('should pass')
})
instance.get('/a', handler)
done()
})
.after((err, done) => {
t.assert.ifError(err)
setTimeout(() => {
fastify.get('/b', handler)
done()
}, 10)
})
fastify.ready(err => {
t.assert.ifError(err)
testDone()
})
})
test('onRoute hook should be called (encapsulation support) / 4', (t, testDone) => {
t.plan(4)
const fastify = Fastify({ exposeHeadRoutes: false })
fastify.addHook('onRoute', () => {
t.assert.ok('should pass')
})
fastify.register((instance, opts, done) => {
instance.addHook('onRoute', () => {
t.assert.ok('should pass')
})
instance.get('/nested', opts, function (req, reply) {
reply.send()
})
done()
})
fastify.get('/', function (req, reply) {
reply.send()
})
fastify.ready(err => {
t.assert.ifError(err)
testDone()
})
})
test('onRoute hook should be called (encapsulation support) / 5', (t, testDone) => {
t.plan(2)
const fastify = Fastify({ exposeHeadRoutes: false })
fastify.get('/first', function (req, reply) {
reply.send()
})
fastify.register((instance, opts, done) => {
instance.addHook('onRoute', () => {
t.assert.ok('should pass')
})
instance.get('/nested', opts, function (req, reply) {
reply.send()
})
done()
})
fastify.get('/second', function (req, reply) {
reply.send()
})
fastify.ready(err => {
t.assert.ifError(err)
testDone()
})
})
test('onRoute hook should be called (encapsulation support) / 6', (t, testDone) => {
t.plan(1)
const fastify = Fastify({ exposeHeadRoutes: false })
fastify.get('/first', function (req, reply) {
reply.send()
})
fastify.addHook('onRoute', () => {
t.assert.fail('This should not be called')
})
fastify.ready(err => {
t.assert.ifError(err)
testDone()
})
})
test('onRoute should keep the context', (t, testDone) => {
t.plan(4)
const fastify = Fastify({ exposeHeadRoutes: false })
fastify.register((instance, opts, done) => {
instance.decorate('test', true)
instance.addHook('onRoute', onRoute)
t.assert.ok(instance.prototype === fastify.prototype)
function onRoute (route) {
t.assert.ok(this.test)
t.assert.strictEqual(this, instance)
}
instance.get('/', opts, function (req, reply) {
reply.send()
})
done()
})
fastify.close((err) => {
t.assert.ifError(err)
testDone()
})
})
test('onRoute hook should pass correct route', (t, testDone) => {
t.plan(9)
const fastify = Fastify({ exposeHeadRoutes: false })
fastify.addHook('onRoute', (route) => {
t.assert.strictEqual(route.method, 'GET')
t.assert.strictEqual(route.url, '/')
t.assert.strictEqual(route.path, '/')
t.assert.strictEqual(route.routePath, '/')
})
fastify.register((instance, opts, done) => {
instance.addHook('onRoute', (route) => {
t.assert.strictEqual(route.method, 'GET')
t.assert.strictEqual(route.url, '/')
t.assert.strictEqual(route.path, '/')
t.assert.strictEqual(route.routePath, '/')
})
instance.get('/', opts, function (req, reply) {
reply.send()
})
done()
})
fastify.ready(err => {
t.assert.ifError(err)
testDone()
})
})
test('onRoute hook should pass correct route with custom prefix', (t, testDone) => {
t.plan(11)
const fastify = Fastify({ exposeHeadRoutes: false })
fastify.addHook('onRoute', function (route) {
t.assert.strictEqual(route.method, 'GET')
t.assert.strictEqual(route.url, '/v1/foo')
t.assert.strictEqual(route.path, '/v1/foo')
t.assert.strictEqual(route.routePath, '/foo')
t.assert.strictEqual(route.prefix, '/v1')
})
fastify.register((instance, opts, done) => {
instance.addHook('onRoute', function (route) {
t.assert.strictEqual(route.method, 'GET')
t.assert.strictEqual(route.url, '/v1/foo')
t.assert.strictEqual(route.path, '/v1/foo')
t.assert.strictEqual(route.routePath, '/foo')
t.assert.strictEqual(route.prefix, '/v1')
})
instance.get('/foo', opts, function (req, reply) {
reply.send()
})
done()
}, { prefix: '/v1' })
fastify.ready(err => {
t.assert.ifError(err)
testDone()
})
})
test('onRoute hook should pass correct route with custom options', (t, testDone) => {
t.plan(6)
const fastify = Fastify({ exposeHeadRoutes: false })
fastify.register((instance, opts, done) => {
instance.addHook('onRoute', function (route) {
t.assert.strictEqual(route.method, 'GET')
t.assert.strictEqual(route.url, '/foo')
t.assert.strictEqual(route.logLevel, 'info')
t.assert.strictEqual(route.bodyLimit, 100)
t.assert.ok(typeof route.logSerializers.test === 'function')
})
instance.get('/foo', {
logLevel: 'info',
bodyLimit: 100,
logSerializers: {
test: value => value
}
}, function (req, reply) {
reply.send()
})
done()
})
fastify.ready(err => {
t.assert.ifError(err)
testDone()
})
})
test('onRoute hook should receive any route option', (t, testDone) => {
t.plan(5)
const fastify = Fastify({ exposeHeadRoutes: false })
fastify.register((instance, opts, done) => {
instance.addHook('onRoute', function (route) {
t.assert.strictEqual(route.method, 'GET')
t.assert.strictEqual(route.url, '/foo')
t.assert.strictEqual(route.routePath, '/foo')
t.assert.strictEqual(route.auth, 'basic')
})
instance.get('/foo', { auth: 'basic' }, function (req, reply) {
reply.send()
})
done()
})
fastify.ready(err => {
t.assert.ifError(err)
testDone()
})
})
test('onRoute hook should preserve system route configuration', (t, testDone) => {
t.plan(5)
const fastify = Fastify({ exposeHeadRoutes: false })
fastify.register((instance, opts, done) => {
instance.addHook('onRoute', function (route) {
t.assert.strictEqual(route.method, 'GET')
t.assert.strictEqual(route.url, '/foo')
t.assert.strictEqual(route.routePath, '/foo')
t.assert.strictEqual(route.handler.length, 2)
})
instance.get('/foo', { url: '/bar', method: 'POST' }, function (req, reply) {
reply.send()
})
done()
})
fastify.ready(err => {
t.assert.ifError(err)
testDone()
})
})
test('onRoute hook should preserve handler function in options of shorthand route system configuration', (t, testDone) => {
t.plan(2)
const handler = (req, reply) => {}
const fastify = Fastify({ exposeHeadRoutes: false })
fastify.register((instance, opts, done) => {
instance.addHook('onRoute', function (route) {
t.assert.strictEqual(route.handler, handler)
})
instance.get('/foo', { handler })
done()
})
fastify.ready(err => {
t.assert.ifError(err)
testDone()
})
})
// issue ref https://github.com/fastify/fastify-compress/issues/140
test('onRoute hook should be called once when prefixTrailingSlash', (t, testDone) => {
t.plan(3)
let onRouteCalled = 0
let routePatched = 0
const fastify = Fastify({ ignoreTrailingSlash: false, exposeHeadRoutes: false })
// a plugin that patches route options, similar to fastify-compress
fastify.register(fp(function myPlugin (instance, opts, next) {
function patchTheRoute () {
routePatched++
}
instance.addHook('onRoute', function (routeOptions) {
onRouteCalled++
patchTheRoute(routeOptions)
})
next()
}))
fastify.register(function routes (instance, opts, next) {
instance.route({
method: 'GET',
url: '/',
prefixTrailingSlash: 'both',
handler: (req, reply) => {
reply.send({ hello: 'world' })
}
})
next()
}, { prefix: '/prefix' })
fastify.ready(err => {
t.assert.ifError(err)
t.assert.strictEqual(onRouteCalled, 1) // onRoute hook was called once
t.assert.strictEqual(routePatched, 1) // and plugin acted once and avoided redundant route patching
testDone()
})
})
test('onRoute hook should able to change the route url', (t, testDone) => {
t.plan(5)
const fastify = Fastify({ exposeHeadRoutes: false })
t.after(() => { fastify.close() })
fastify.register((instance, opts, done) => {
instance.addHook('onRoute', (route) => {
t.assert.strictEqual(route.url, '/foo')
route.url = encodeURI(route.url)
})
instance.get('/foo', (request, reply) => {
reply.send('here /foo')
})
done()
})
fastify.listen({ port: 0 }, err => {
t.assert.ifError(err)
sget({
method: 'GET',
url: getServerUrl(fastify) + encodeURI('/foo')
}, (err, response, body) => {
t.assert.ifError(err)
t.assert.strictEqual(response.statusCode, 200)
t.assert.strictEqual(body.toString(), 'here /foo')
testDone()
})
})
})
test('onRoute hook that throws should be caught', (t, testDone) => {
t.plan(1)
const fastify = Fastify({ exposeHeadRoutes: false })
fastify.register((instance, opts, done) => {
instance.addHook('onRoute', () => {
throw new Error('snap')
})
try {
instance.get('/', opts, function (req, reply) {
reply.send()
})
t.assert.fail('onRoute should throw sync if error')
} catch (error) {
t.assert.ok(error)
}
done()
})
fastify.ready(testDone)
})
test('onRoute hook with many prefix', (t, testDone) => {
t.plan(3)
const fastify = Fastify({ exposeHeadRoutes: false })
const handler = (req, reply) => { reply.send({}) }
const onRouteChecks = [
{ routePath: '/anotherPath', prefix: '/one/two', url: '/one/two/anotherPath' },
{ routePath: '/aPath', prefix: '/one', url: '/one/aPath' }
]
fastify.register((instance, opts, done) => {
instance.addHook('onRoute', ({ routePath, prefix, url }) => {
t.assert.deepStrictEqual({ routePath, prefix, url }, onRouteChecks.pop())
})
instance.route({ method: 'GET', url: '/aPath', handler })
instance.register((instance, opts, done) => {
instance.route({ method: 'GET', path: '/anotherPath', handler })
done()
}, { prefix: '/two' })
done()
}, { prefix: '/one' })
fastify.ready(err => {
t.assert.ifError(err)
testDone()
})
})
test('onRoute hook should not be called when it registered after route', (t, testDone) => {
t.plan(3)
const fastify = Fastify()
fastify.addHook('onRoute', () => {
t.assert.ok('should pass')
})
fastify.get('/', function (req, reply) {
reply.send()
})
fastify.addHook('onRoute', () => {
t.assert.fail('should not be called')
})
fastify.ready(err => {
t.assert.ifError(err)
testDone()
})
})
test('onResponse hook should log request error', (t, testDone) => {
t.plan(4)
let fastify = null
const logStream = split(JSON.parse)
try {
fastify = Fastify({
logger: {
stream: logStream,
level: 'error'
}
})
} catch (e) {
t.assert.fail()
}
logStream.once('data', line => {
t.assert.strictEqual(line.msg, 'request errored')
t.assert.strictEqual(line.level, 50)
})
fastify.addHook('onResponse', (request, reply, done) => {
done(new Error('kaboom'))
})
fastify.get('/root', (request, reply) => {
reply.send()
})
fastify.inject('/root', (err, res) => {
t.assert.ifError(err)
t.assert.strictEqual(res.statusCode, 200)
testDone()
})
})
test('onResponse hook should support encapsulation / 1', (t, testDone) => {
t.plan(5)
const fastify = Fastify()
fastify.register((instance, opts, done) => {
instance.addHook('onResponse', (request, reply, done) => {
t.assert.strictEqual(reply.plugin, true)
done()
})
instance.get('/plugin', (request, reply) => {
reply.plugin = true
reply.send()
})
done()
})
fastify.get('/root', (request, reply) => {
reply.send()
})
fastify.inject('/root', (err, res) => {
t.assert.ifError(err)
t.assert.strictEqual(res.statusCode, 200)
})
fastify.inject('/plugin', (err, res) => {
t.assert.ifError(err)
t.assert.strictEqual(res.statusCode, 200)
testDone()
})
})
test('onResponse hook should support encapsulation / 2', (t, testDone) => {
t.plan(3)
const fastify = Fastify()
let pluginInstance
fastify.addHook('onResponse', () => {})
fastify.register((instance, opts, done) => {
instance.addHook('onResponse', () => {})
pluginInstance = instance
done()
})
fastify.ready(err => {
t.assert.ifError(err)
t.assert.strictEqual(fastify[symbols.kHooks].onResponse.length, 1)
t.assert.strictEqual(pluginInstance[symbols.kHooks].onResponse.length, 2)
testDone()
})
})
test('onResponse hook should support encapsulation / 3', (t, testDone) => {
t.plan(16)
const fastify = Fastify()
t.after(() => { fastify.close() })
fastify.decorate('hello', 'world')
fastify.addHook('onResponse', function (request, reply, done) {
t.assert.ok(this.hello)
t.assert.ok('onResponse called')
done()
})
fastify.get('/first', (req, reply) => {
reply.send({ hello: 'world' })
})
fastify.register((instance, opts, done) => {
instance.decorate('hello2', 'world')
instance.addHook('onResponse', function (request, reply, done) {
t.assert.ok(this.hello)
t.assert.ok(this.hello2)
t.assert.ok('onResponse called')
done()
})
instance.get('/second', (req, reply) => {
reply.send({ hello: 'world' })
})
done()
})
fastify.listen({ port: 0 }, err => {
t.assert.ifError(err)
const completion = waitForCb({ steps: 2 })
sget({
method: 'GET',
url: 'http://127.0.0.1:' + fastify.server.address().port + '/first'
}, (err, response, body) => {
t.assert.ifError(err)
t.assert.strictEqual(response.statusCode, 200)
t.assert.strictEqual(response.headers['content-length'], '' + body.length)
t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' })
completion.stepIn()
})
sget({
method: 'GET',
url: 'http://127.0.0.1:' + fastify.server.address().port + '/second'
}, (err, response, body) => {
t.assert.ifError(err)
t.assert.strictEqual(response.statusCode, 200)
t.assert.strictEqual(response.headers['content-length'], '' + body.length)
t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' })
completion.stepIn()
})
completion.patience.then(testDone)
})
})
test('onSend hook should support encapsulation / 1', (t, testDone) => {
t.plan(3)
const fastify = Fastify()
let pluginInstance
fastify.addHook('onSend', () => {})
fastify.register((instance, opts, done) => {
instance.addHook('onSend', () => {})
pluginInstance = instance
done()
})
fastify.ready(err => {
t.assert.ifError(err)
t.assert.strictEqual(fastify[symbols.kHooks].onSend.length, 1)
t.assert.strictEqual(pluginInstance[symbols.kHooks].onSend.length, 2)
testDone()
})
})
test('onSend hook should support encapsulation / 2', (t, testDone) => {
t.plan(16)
const fastify = Fastify()
t.after(() => { fastify.close() })
fastify.decorate('hello', 'world')
fastify.addHook('onSend', function (request, reply, thePayload, done) {
t.assert.ok(this.hello)
t.assert.ok('onSend called')
done()
})
fastify.get('/first', (req, reply) => {
reply.send({ hello: 'world' })
})
fastify.register((instance, opts, done) => {
instance.decorate('hello2', 'world')
instance.addHook('onSend', function (request, reply, thePayload, done) {
t.assert.ok(this.hello)
t.assert.ok(this.hello2)
t.assert.ok('onSend called')
done()
})
instance.get('/second', (req, reply) => {
reply.send({ hello: 'world' })
})
done()
})
fastify.listen({ port: 0 }, err => {
t.assert.ifError(err)
const completion = waitForCb({ steps: 2 })
sget({
method: 'GET',
url: 'http://127.0.0.1:' + fastify.server.address().port + '/first'
}, (err, response, body) => {
t.assert.ifError(err)
t.assert.strictEqual(response.statusCode, 200)
t.assert.strictEqual(response.headers['content-length'], '' + body.length)
t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' })
completion.stepIn()
})
sget({
method: 'GET',
url: 'http://127.0.0.1:' + fastify.server.address().port + '/second'
}, (err, response, body) => {
t.assert.ifError(err)
t.assert.strictEqual(response.statusCode, 200)
t.assert.strictEqual(response.headers['content-length'], '' + body.length)
t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' })
completion.stepIn()
})
completion.patience.then(testDone)
})
})
test('onSend hook is called after payload is serialized and headers are set', (t, testDone) => {
t.plan(30)
const fastify = Fastify()
fastify.register((instance, opts, done) => {
const thePayload = { hello: 'world' }
instance.addHook('onSend', function (request, reply, payload, done) {
t.assert.deepStrictEqual(JSON.parse(payload), thePayload)
t.assert.strictEqual(reply[symbols.kReplyHeaders]['content-type'], 'application/json; charset=utf-8')
done()
})
instance.get('/json', (request, reply) => {
reply.send(thePayload)
})
done()
})
fastify.register((instance, opts, done) => {
instance.addHook('onSend', function (request, reply, payload, done) {
t.assert.strictEqual(payload, 'some text')
t.assert.strictEqual(reply[symbols.kReplyHeaders]['content-type'], 'text/plain; charset=utf-8')
done()
})
instance.get('/text', (request, reply) => {
reply.send('some text')
})
done()
})
fastify.register((instance, opts, done) => {
const thePayload = Buffer.from('buffer payload')
instance.addHook('onSend', function (request, reply, payload, done) {
t.assert.strictEqual(payload, thePayload)
t.assert.strictEqual(reply[symbols.kReplyHeaders]['content-type'], 'application/octet-stream')
done()
})
instance.get('/buffer', (request, reply) => {
reply.send(thePayload)
})
done()
})
fastify.register((instance, opts, done) => {
let chunk = 'stream payload'
const thePayload = new stream.Readable({
read () {
this.push(chunk)
chunk = null
}
})
instance.addHook('onSend', function (request, reply, payload, done) {
t.assert.strictEqual(payload, thePayload)
t.assert.strictEqual(reply[symbols.kReplyHeaders]['content-type'], 'application/octet-stream')
done()
})
instance.get('/stream', (request, reply) => {
reply.header('content-type', 'application/octet-stream')
reply.send(thePayload)
})
done()
})
fastify.register((instance, opts, done) => {
const serializedPayload = 'serialized'
instance.addHook('onSend', function (request, reply, payload, done) {
t.assert.strictEqual(payload, serializedPayload)
t.assert.strictEqual(reply[symbols.kReplyHeaders]['content-type'], 'text/custom')
done()
})
instance.get('/custom-serializer', (request, reply) => {
reply
.serializer(() => serializedPayload)
.type('text/custom')
.send('needs to be serialized')
})
done()
})
const completion = waitForCb({ steps: 5 })
fastify.inject({
method: 'GET',
url: '/json'
}, (err, res) => {
t.assert.ifError(err)
t.assert.strictEqual(res.statusCode, 200)
t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' })
t.assert.strictEqual(res.headers['content-length'], '17')
completion.stepIn()
})
fastify.inject({
method: 'GET',
url: '/text'
}, (err, res) => {
t.assert.ifError(err)
t.assert.strictEqual(res.statusCode, 200)
t.assert.deepStrictEqual(res.payload, 'some text')
t.assert.strictEqual(res.headers['content-length'], '9')
completion.stepIn()
})
fastify.inject({
method: 'GET',
url: '/buffer'
}, (err, res) => {
t.assert.ifError(err)
t.assert.strictEqual(res.statusCode, 200)
t.assert.deepStrictEqual(res.payload, 'buffer payload')
t.assert.strictEqual(res.headers['content-length'], '14')
completion.stepIn()
})
fastify.inject({
method: 'GET',
url: '/stream'
}, (err, res) => {
t.assert.ifError(err)
t.assert.strictEqual(res.statusCode, 200)
t.assert.deepStrictEqual(res.payload, 'stream payload')
t.assert.strictEqual(res.headers['transfer-encoding'], 'chunked')
completion.stepIn()
})
fastify.inject({
method: 'GET',
url: '/custom-serializer'
}, (err, res) => {
t.assert.ifError(err)
t.assert.strictEqual(res.statusCode, 200)
t.assert.deepStrictEqual(res.payload, 'serialized')
t.assert.strictEqual(res.headers['content-type'], 'text/custom')
completion.stepIn()
})
completion.patience.then(testDone)
})
test('modify payload', (t, testDone) => {
t.plan(10)
const fastify = Fastify()
const payload = { hello: 'world' }
const modifiedPayload = { hello: 'modified' }
const anotherPayload = '"winter is coming"'
fastify.addHook('onSend', function (request, reply, thePayload, done) {
t.assert.ok('onSend called')
t.assert.deepStrictEqual(JSON.parse(thePayload), payload)
thePayload = thePayload.replace('world', 'modified')
done(null, thePayload)
})
fastify.addHook('onSend', function (request, reply, thePayload, done) {
t.assert.ok('onSend called')
t.assert.deepStrictEqual(JSON.parse(thePayload), modifiedPayload)
done(null, anotherPayload)
})
fastify.addHook('onSend', function (request, reply, thePayload, done) {
t.assert.ok('onSend called')
t.assert.strictEqual(thePayload, anotherPayload)
done()
})
fastify.get('/', (req, reply) => {
reply.send(payload)
})
fastify.inject({
method: 'GET',
url: '/'
}, (err, res) => {
t.assert.ifError(err)
t.assert.strictEqual(res.payload, anotherPayload)
t.assert.strictEqual(res.statusCode, 200)
t.assert.strictEqual(res.headers['content-length'], '18')
testDone()
})
})
test('clear payload', (t, testDone) => {
t.plan(6)
const fastify = Fastify()
fastify.addHook('onSend', function (request, reply, payload, done) {
t.assert.ok('onSend called')
reply.code(304)
done(null, null)
})
fastify.get('/', (req, reply) => {
reply.send({ hello: 'world' })
})
fastify.inject({
method: 'GET',
url: '/'
}, (err, res) => {
t.assert.ifError(err)
t.assert.strictEqual(res.statusCode, 304)
t.assert.strictEqual(res.payload, '')
t.assert.strictEqual(res.headers['content-length'], undefined)
t.assert.strictEqual(res.headers['content-type'], 'application/json; charset=utf-8')
testDone()
})
})
test('onSend hook throws', (t, testDone) => {
t.plan(11)
const Fastify = proxyquire('..', {
'./lib/schemas.js': {
getSchemaSerializer: (param1, param2, param3) => {
t.assert.strictEqual(param3, 'application/json; charset=utf-8', 'param3 should be "application/json; charset=utf-8"')
}
}
})
const fastify = Fastify()
t.after(() => { fastify.close() })
fastify.addHook('onSend', function (request, reply, payload, done) {
if (request.raw.method === 'DELETE') {
done(new Error('some error'))
return
}
if (request.raw.method === 'PUT') {
throw new Error('some error')
}
if (request.raw.method === 'POST') {
throw new Error('some error')
}
done()
})
fastify.get('/', (req, reply) => {
reply.send({ hello: 'world' })
})
fastify.post('/', {
schema: {
response: {
200: {
content: {
'application/json': {
schema: {
name: { type: 'string' },
image: { type: 'string' },
address: { type: 'string' }
}
}
}
}
}
}
}, (req, reply) => {
reply.send({ hello: 'world' })
})
fastify.delete('/', (req, reply) => {
reply.send({ hello: 'world' })
})
fastify.put('/', (req, reply) => {
reply.send({ hello: 'world' })
})
fastify.listen({ port: 0 }, err => {
t.assert.ifError(err)
const completion = waitForCb({ steps: 4 })
sget({
method: 'GET',
url: 'http://127.0.0.1:' + fastify.server.address().port
}, (err, response, body) => {
t.assert.ifError(err)
t.assert.strictEqual(response.statusCode, 200)
t.assert.strictEqual(response.headers['content-length'], '' + body.length)
t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' })
completion.stepIn()
})
sget({
method: 'POST',
url: 'http://127.0.0.1:' + fastify.server.address().port
}, (err, response, body) => {
t.assert.ifError(err)
t.assert.strictEqual(response.statusCode, 500)
completion.stepIn()
})
sget({
method: 'DELETE',
url: 'http://127.0.0.1:' + fastify.server.address().port
}, (err, response, body) => {
t.assert.ifError(err)
t.assert.strictEqual(response.statusCode, 500)
completion.stepIn()
})
sget({
method: 'PUT',
url: 'http://127.0.0.1:' + fastify.server.address().port
}, (err, response, body) => {
t.assert.ifError(err)
t.assert.strictEqual(response.statusCode, 500)
completion.stepIn()
})
completion.patience.then(testDone)
})
})
test('onSend hook should receive valid request and reply objects if onRequest hook fails', (t, testDone) => {
t.plan(4)
const fastify = Fastify()
fastify.decorateRequest('testDecorator', 'testDecoratorVal')
fastify.decorateReply('testDecorator', 'testDecoratorVal')
fastify.addHook('onRequest', function (req, reply, done) {
done(new Error('onRequest hook failed'))
})
fastify.addHook('onSend', function (request, reply, payload, done) {
t.assert.strictEqual(request.testDecorator, 'testDecoratorVal')
t.assert.strictEqual(reply.testDecorator, 'testDecoratorVal')
done()
})
fastify.get('/', (req, reply) => {
reply.send('hello')
})
fastify.inject({
method: 'GET',
url: '/'
}, (err, res) => {
t.assert.ifError(err)
t.assert.strictEqual(res.statusCode, 500)
testDone()
})
})
test('onSend hook should receive valid request and reply objects if a custom content type parser fails', (t, testDone) => {
t.plan(4)
const fastify = Fastify()
fastify.decorateRequest('testDecorator', 'testDecoratorVal')
fastify.decorateReply('testDecorator', 'testDecoratorVal')
fastify.addContentTypeParser('*', function (req, payload, done) {
done(new Error('content type parser failed'))
})
fastify.addHook('onSend', function (request, reply, payload, done) {
t.assert.strictEqual(request.testDecorator, 'testDecoratorVal')
t.assert.strictEqual(reply.testDecorator, 'testDecoratorVal')
done()
})
fastify.get('/', (req, reply) => {
reply.send('hello')
})
fastify.inject({
method: 'POST',
url: '/',
payload: 'body'
}, (err, res) => {
t.assert.ifError(err)
t.assert.strictEqual(res.statusCode, 500)
testDone()
})
})
test('Content-Length header should be updated if onSend hook modifies the payload', (t, testDone) => {
t.plan(2)
const instance = Fastify()
instance.get('/', async (_, rep) => {
rep.header('content-length', 3)
return 'foo'
})
instance.addHook('onSend', async () => 'bar12233000')
instance.inject({
method: 'GET',
url: '/'
}, (err, res) => {
t.assert.ifError(err)
const payloadLength = Buffer.byteLength(res.body)
const contentLength = Number(res.headers['content-length'])
t.assert.strictEqual(payloadLength, contentLength)
testDone()
})
})
test('cannot add hook after binding', (t, testDone) => {
t.plan(1)
const instance = Fastify()
t.after(() => instance.close())
instance.get('/', function (request, reply) {
reply.send({ hello: 'world' })
})
instance.listen({ port: 0 }, err => {
t.assert.ifError(err)
try {
instance.addHook('onRequest', () => {})
t.assert.fail()
} catch (e) {
testDone()
}
})
})
test('onRequest hooks should be able to block a request', (t, testDone) => {
t.plan(5)
const fastify = Fastify()
fastify.addHook('onRequest', (req, reply, done) => {
reply.send('hello')
done()
})
fastify.addHook('onRequest', (req, reply, done) => {
t.assert.fail('this should not be called')
})
fastify.addHook('preHandler', (req, reply, done) => {
t.assert.fail('this should not be called')
})
fastify.addHook('onSend', (req, reply, payload, done) => {
t.assert.ok('called')
done()
})
fastify.addHook('onResponse', (request, reply, done) => {
t.assert.ok('called')
done()
})
fastify.get('/', function (request, reply) {
t.assert.fail('we should not be here')
})
fastify.inject({
url: '/',
method: 'GET'
}, (err, res) => {
t.assert.ifError(err)
t.assert.strictEqual(res.statusCode, 200)
t.assert.strictEqual(res.payload, 'hello')
testDone()
})
})
test('preValidation hooks should be able to block a request', (t, testDone) => {
t.plan(5)
const fastify = Fastify()
fastify.addHook('preValidation', (req, reply, done) => {
reply.send('hello')
done()
})
fastify.addHook('preValidation', (req, reply, done) => {
t.assert.fail('this should not be called')
})
fastify.addHook('preHandler', (req, reply, done) => {
t.assert.fail('this should not be called')
})
fastify.addHook('onSend', (req, reply, payload, done) => {
t.assert.ok('called')
done()
})
fastify.addHook('onResponse', (request, reply, done) => {
t.assert.ok('called')
done()
})
fastify.get('/', function (request, reply) {
t.assert.fail('we should not be here')
})
fastify.inject({
url: '/',
method: 'GET'
}, (err, res) => {
t.assert.ifError(err)
t.assert.strictEqual(res.statusCode, 200)
t.assert.strictEqual(res.payload, 'hello')
testDone()
})
})
test('preValidation hooks should be able to change request body before validation', (t, testDone) => {
t.plan(4)
const fastify = Fastify()
fastify.addHook('preValidation', (req, _reply, done) => {
const buff = Buffer.from(req.body.message, 'base64')
req.body = JSON.parse(buff.toString('utf-8'))
done()
})
fastify.post(
'/',
{
schema: {
body: {
type: 'object',
properties: {
foo: {
type: 'string'
},
bar: {
type: 'number'
}
},
required: ['foo', 'bar']
}
}
},
(req, reply) => {
t.assert.ok('should pass')
reply.status(200).send('hello')
}
)
fastify.inject({
url: '/',
method: 'POST',
payload: {
message: Buffer.from(JSON.stringify({ foo: 'example', bar: 1 })).toString('base64')
}
}, (err, res) => {
t.assert.ifError(err)
t.assert.strictEqual(res.statusCode, 200)
t.assert.strictEqual(res.payload, 'hello')
testDone()
})
})
test('preParsing hooks should be able to block a request', (t, testDone) => {
t.plan(5)
const fastify = Fastify()
fastify.addHook('preParsing', (req, reply, payload, done) => {
reply.send('hello')
done()
})
fastify.addHook('preParsing', (req, reply, payload, done) => {
t.assert.fail('this should not be called')
})
fastify.addHook('preHandler', (req, reply, done) => {
t.assert.fail('this should not be called')
})
fastify.addHook('onSend', (req, reply, payload, done) => {
t.assert.ok('called')
done()
})
fastify.addHook('onResponse', (request, reply, done) => {
t.assert.ok('called')
done()
})
fastify.get('/', function (request, reply) {
t.assert.fail('we should not be here')
})
fastify.inject({
url: '/',
method: 'GET'
}, (err, res) => {
t.assert.ifError(err)
t.assert.strictEqual(res.statusCode, 200)
t.assert.strictEqual(res.payload, 'hello')
testDone()
})
})
test('preHandler hooks should be able to block a request', (t, testDone) => {
t.plan(5)
const fastify = Fastify()
fastify.addHook('preHandler', (req, reply, done) => {
reply.send('hello')
done()
})
fastify.addHook('preHandler', (req, reply, done) => {
t.assert.fail('this should not be called')
})
fastify.addHook('onSend', (req, reply, payload, done) => {
t.assert.strictEqual(payload, 'hello')
done()
})
fastify.addHook('onResponse', (request, reply, done) => {
t.assert.ok('called')
done()
})
fastify.get('/', function (request, reply) {
t.assert.fail('we should not be here')
})
fastify.inject({
url: '/',
method: 'GET'
}, (err, res) => {
t.assert.ifError(err)
t.assert.strictEqual(res.statusCode, 200)
t.assert.strictEqual(res.payload, 'hello')
testDone()
})
})
test('onRequest hooks should be able to block a request (last hook)', (t, testDone) => {
t.plan(5)
const fastify = Fastify()
fastify.addHook('onRequest', (req, reply, done) => {
reply.send('hello')
done()
})
fastify.addHook('preHandler', (req, reply, done) => {
t.assert.fail('this should not be called')
})
fastify.addHook('onSend', (req, reply, payload, done) => {
t.assert.ok('called')
done()
})
fastify.addHook('onResponse', (request, reply, done) => {
t.assert.ok('called')
done()
})
fastify.get('/', function (request, reply) {
t.assert.fail('we should not be here')
})
fastify.inject({
url: '/',
method: 'GET'
}, (err, res) => {
t.assert.ifError(err)
t.assert.strictEqual(res.statusCode, 200)
t.assert.strictEqual(res.payload, 'hello')
testDone()
})
})
test('preHandler hooks should be able to block a request (last hook)', (t, testDone) => {
t.plan(5)
const fastify = Fastify()
fastify.addHook('preHandler', (req, reply, done) => {
reply.send('hello')
done()
})
fastify.addHook('onSend', (req, reply, payload, done) => {
t.assert.strictEqual(payload, 'hello')
done()
})
fastify.addHook('onResponse', (request, reply, done) => {
t.assert.ok('called')
done()
})
fastify.get('/', function (request, reply) {
t.assert.fail('we should not be here')
})
fastify.inject({
url: '/',
method: 'GET'
}, (err, res) => {
t.assert.ifError(err)
t.assert.strictEqual(res.statusCode, 200)
t.assert.strictEqual(res.payload, 'hello')
testDone()
})
})
test('preParsing hooks should handle errors', (t, testDone) => {
t.plan(3)
const fastify = Fastify()
fastify.addHook('preParsing', (req, reply, payload, done) => {
const e = new Error('kaboom')
e.statusCode = 501
throw e
})
fastify.post('/', function (request, reply) {
reply.send(request.body)
})
fastify.inject({
method: 'POST',
url: '/',
payload: { hello: 'world' }
}, (err, res) => {
t.assert.ifError(err)
t.assert.strictEqual(res.statusCode, 501)
t.assert.deepStrictEqual(JSON.parse(res.payload), { error: 'Not Implemented', message: 'kaboom', statusCode: 501 })
testDone()
})
})
test('onRequest respond with a stream', (t, testDone) => {
t.plan(4)
const fastify = Fastify()
fastify.addHook('onRequest', (req, reply, done) => {
const stream = fs.createReadStream(__filename, 'utf8')
// stream.pipe(res)
// res.once('finish', done)
reply.send(stream)
})
fastify.addHook('onRequest', (req, res, done) => {
t.assert.fail('this should not be called')
})
fastify.addHook('preHandler', (req, reply, done) => {
t.assert.fail('this should not be called')
})
fastify.addHook('onSend', (req, reply, payload, done) => {
t.assert.ok('called')
done()
})
fastify.addHook('onResponse', (request, reply, done) => {
t.assert.ok('called')
done()
})
fastify.get('/', function (request, reply) {
t.assert.fail('we should not be here')
})
fastify.inject({
url: '/',
method: 'GET'
}, (err, res) => {
t.assert.ifError(err)
t.assert.strictEqual(res.statusCode, 200)
testDone()
})
})
test('preHandler respond with a stream', (t, testDone) => {
t.plan(7)
const fastify = Fastify()
fastify.addHook('onRequest', (req, reply, done) => {
t.assert.ok('called')
done()
})
// we are calling `reply.send` inside the `preHandler` hook with a stream,
// this triggers the `onSend` hook event if `preHandler` has not yet finished
const order = [1, 2]
fastify.addHook('preHandler', (req, reply, done) => {
const stream = fs.createReadStream(__filename, 'utf8')
reply.send(stream)
reply.raw.once('finish', () => {
t.assert.strictEqual(order.shift(), 2)
done()
})
})
fastify.addHook('preHandler', (req, reply, done) => {
t.assert.fail('this should not be called')
})
fastify.addHook('onSend', (req, reply, payload, done) => {
t.assert.strictEqual(order.shift(), 1)
t.assert.strictEqual(typeof payload.pipe, 'function')
done()
})
fastify.addHook('onResponse', (request, reply, done) => {
t.assert.ok('called')
done()
})
fastify.get('/', function (request, reply) {
t.assert.fail('we should not be here')
})
fastify.inject({
url: '/',
method: 'GET'
}, (err, res) => {
t.assert.ifError(err)
t.assert.strictEqual(res.statusCode, 200)
testDone()
})
})
test('Register an hook after a plugin inside a plugin', (t, testDone) => {
t.plan(6)
const fastify = Fastify()
fastify.register(fp(function (instance, opts, done) {
instance.addHook('preHandler', function (req, reply, done) {
t.assert.ok('called')
done()
})
instance.get('/', function (request, reply) {
reply.send({ hello: 'world' })
})
done()
}))
fastify.register(fp(function (instance, opts, done) {
instance.addHook('preHandler', function (req, reply, done) {
t.assert.ok('called')
done()
})
instance.addHook('preHandler', function (req, re