UNPKG

fastify

Version:

Fast and low overhead web framework, for Node.js

1,953 lines (1,649 loc) 96.4 kB
'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