UNPKG

fastify

Version:

Fast and low overhead web framework, for Node.js

1,964 lines (1,649 loc) 52.8 kB
'use strict' const { test } = require('node:test') const fp = require('fastify-plugin') const sget = require('simple-get').concat const errors = require('http-errors') const split = require('split2') const Fastify = require('..') const { getServerUrl } = require('./helper') test('default 404', async t => { t.plan(4) const fastify = Fastify() fastify.get('/', function (req, reply) { reply.send({ hello: 'world' }) }) t.after(() => { fastify.close() }) await fastify.listen({ port: 0 }) await t.test('unsupported method', (t, done) => { t.plan(3) sget({ method: 'PUT', url: getServerUrl(fastify), body: {}, json: true }, (err, response, body) => { t.assert.ifError(err) t.assert.strictEqual(response.statusCode, 404) t.assert.strictEqual(response.headers['content-type'], 'application/json; charset=utf-8') done() }) }) // Return 404 instead of 405 see https://github.com/fastify/fastify/pull/862 for discussion await t.test('framework-unsupported method', (t, done) => { t.plan(3) sget({ method: 'PROPFIND', url: getServerUrl(fastify), body: {}, json: true }, (err, response, body) => { t.assert.ifError(err) t.assert.strictEqual(response.statusCode, 404) t.assert.strictEqual(response.headers['content-type'], 'application/json; charset=utf-8') done() }) }) await t.test('unsupported route', (t, done) => { t.plan(3) sget({ method: 'GET', url: getServerUrl(fastify) + '/notSupported', body: {}, json: true }, (err, response, body) => { t.assert.ifError(err) t.assert.strictEqual(response.statusCode, 404) t.assert.strictEqual(response.headers['content-type'], 'application/json; charset=utf-8') done() }) }) await t.test('using post method and multipart/formdata', async t => { t.plan(3) const form = new FormData() form.set('test-field', 'just some field') const response = await fetch(getServerUrl(fastify) + '/notSupported', { method: 'POST', body: form }) t.assert.strictEqual(response.status, 404) t.assert.strictEqual(response.statusText, 'Not Found') t.assert.strictEqual(response.headers.get('content-type'), 'application/json; charset=utf-8') }) }) test('customized 404', async t => { t.plan(5) const fastify = Fastify() fastify.get('/', function (req, reply) { reply.send({ hello: 'world' }) }) fastify.get('/with-error', function (req, reply) { reply.send(new errors.NotFound()) }) fastify.get('/with-error-custom-header', function (req, reply) { const err = new errors.NotFound() err.headers = { 'x-foo': 'bar' } reply.send(err) }) fastify.setNotFoundHandler(function (req, reply) { reply.code(404).send('this was not found') }) t.after(() => { fastify.close() }) await fastify.listen({ port: 0 }) await t.test('unsupported method', (t, done) => { t.plan(3) sget({ method: 'PUT', url: getServerUrl(fastify), body: JSON.stringify({ hello: 'world' }), headers: { 'Content-Type': 'application/json' } }, (err, response, body) => { t.assert.ifError(err) t.assert.strictEqual(response.statusCode, 404) t.assert.strictEqual(body.toString(), 'this was not found') done() }) }) await t.test('framework-unsupported method', (t, done) => { t.plan(3) sget({ method: 'PROPFIND', url: getServerUrl(fastify), body: JSON.stringify({ hello: 'world' }), headers: { 'Content-Type': 'application/json' } }, (err, response, body) => { t.assert.ifError(err) t.assert.strictEqual(response.statusCode, 404) t.assert.strictEqual(body.toString(), 'this was not found') done() }) }) await t.test('unsupported route', (t, done) => { t.plan(3) sget({ method: 'GET', url: getServerUrl(fastify) + '/notSupported' }, (err, response, body) => { t.assert.ifError(err) t.assert.strictEqual(response.statusCode, 404) t.assert.strictEqual(body.toString(), 'this was not found') done() }) }) await t.test('with error object', (t, done) => { t.plan(3) sget({ method: 'GET', url: getServerUrl(fastify) + '/with-error' }, (err, response, body) => { t.assert.ifError(err) t.assert.strictEqual(response.statusCode, 404) t.assert.deepStrictEqual(JSON.parse(body), { error: 'Not Found', message: 'Not Found', statusCode: 404 }) done() }) }) await t.test('error object with headers property', (t, done) => { t.plan(4) sget({ method: 'GET', url: getServerUrl(fastify) + '/with-error-custom-header' }, (err, response, body) => { t.assert.ifError(err) t.assert.strictEqual(response.statusCode, 404) t.assert.strictEqual(response.headers['x-foo'], 'bar') t.assert.deepStrictEqual(JSON.parse(body), { error: 'Not Found', message: 'Not Found', statusCode: 404 }) done() }) }) }) test('custom header in notFound handler', async t => { t.plan(1) const fastify = Fastify() fastify.setNotFoundHandler(function (req, reply) { reply.code(404).header('x-foo', 'bar').send('this was not found') }) t.after(() => { fastify.close() }) await fastify.listen({ port: 0 }) await t.test('not found with custom header', (t, done) => { t.plan(4) sget({ method: 'GET', url: getServerUrl(fastify) + '/notSupported' }, (err, response, body) => { t.assert.ifError(err) t.assert.strictEqual(response.statusCode, 404) t.assert.strictEqual(response.headers['x-foo'], 'bar') t.assert.strictEqual(body.toString(), 'this was not found') done() }) }) }) test('setting a custom 404 handler multiple times is an error', async t => { t.plan(5) await t.test('at the root level', t => { t.plan(2) const fastify = Fastify() fastify.setNotFoundHandler(() => {}) try { fastify.setNotFoundHandler(() => {}) t.assert.fail('setting multiple 404 handlers at the same prefix encapsulation level should throw') } catch (err) { t.assert.ok(err instanceof Error) t.assert.strictEqual(err.message, 'Not found handler already set for Fastify instance with prefix: \'/\'') } }) await t.test('at the plugin level', (t, done) => { t.plan(3) const fastify = Fastify() fastify.register((instance, options, done) => { instance.setNotFoundHandler(() => {}) try { instance.setNotFoundHandler(() => {}) t.assert.fail('setting multiple 404 handlers at the same prefix encapsulation level should throw') } catch (err) { t.assert.ok(err instanceof Error) t.assert.strictEqual(err.message, 'Not found handler already set for Fastify instance with prefix: \'/prefix\'') } done() }, { prefix: '/prefix' }) fastify.listen({ port: 0 }, err => { t.assert.ifError(err) fastify.close() done() }) }) await t.test('at multiple levels', (t, done) => { t.plan(3) const fastify = Fastify() fastify.register((instance, options, done) => { try { instance.setNotFoundHandler(() => {}) t.assert.fail('setting multiple 404 handlers at the same prefix encapsulation level should throw') } catch (err) { t.assert.ok(err instanceof Error) t.assert.strictEqual(err.message, 'Not found handler already set for Fastify instance with prefix: \'/\'') } done() }) fastify.setNotFoundHandler(() => {}) fastify.listen({ port: 0 }, err => { t.assert.ifError(err) fastify.close() done() }) }) await t.test('at multiple levels / 2', (t, done) => { t.plan(3) const fastify = Fastify() fastify.register((instance, options, done) => { instance.setNotFoundHandler(() => {}) instance.register((instance2, options, done) => { try { instance2.setNotFoundHandler(() => {}) t.assert.fail('setting multiple 404 handlers at the same prefix encapsulation level should throw') } catch (err) { t.assert.ok(err instanceof Error) t.assert.strictEqual(err.message, 'Not found handler already set for Fastify instance with prefix: \'/prefix\'') } done() }) done() }, { prefix: '/prefix' }) fastify.setNotFoundHandler(() => {}) fastify.listen({ port: 0 }, err => { t.assert.ifError(err) fastify.close() done() }) }) await t.test('in separate plugins at the same level', (t, done) => { t.plan(3) const fastify = Fastify() fastify.register((instance, options, done) => { instance.register((instance2A, options, done) => { instance2A.setNotFoundHandler(() => {}) done() }) instance.register((instance2B, options, done) => { try { instance2B.setNotFoundHandler(() => {}) t.assert.fail('setting multiple 404 handlers at the same prefix encapsulation level should throw') } catch (err) { t.assert.ok(err instanceof Error) t.assert.strictEqual(err.message, 'Not found handler already set for Fastify instance with prefix: \'/prefix\'') } done() }) done() }, { prefix: '/prefix' }) fastify.setNotFoundHandler(() => {}) fastify.listen({ port: 0 }, err => { t.assert.ifError(err) fastify.close() done() }) }) }) test('encapsulated 404', async t => { t.plan(12) const fastify = Fastify() fastify.get('/', function (req, reply) { reply.send({ hello: 'world' }) }) fastify.setNotFoundHandler(function (req, reply) { reply.code(404).send('this was not found') }) fastify.register(function (f, opts, done) { f.setNotFoundHandler(function (req, reply) { reply.code(404).send('this was not found 2') }) done() }, { prefix: '/test' }) fastify.register(function (f, opts, done) { f.setNotFoundHandler(function (req, reply) { reply.code(404).send('this was not found 3') }) done() }, { prefix: '/test2' }) fastify.register(function (f, opts, done) { f.setNotFoundHandler(function (request, reply) { reply.code(404).send('this was not found 4') }) done() }, { prefix: '/test3/' }) t.after(() => { fastify.close() }) await fastify.listen({ port: 0 }) await t.test('root unsupported method', (t, done) => { t.plan(3) sget({ method: 'PUT', url: getServerUrl(fastify), body: JSON.stringify({ hello: 'world' }), headers: { 'Content-Type': 'application/json' } }, (err, response, body) => { t.assert.ifError(err) t.assert.strictEqual(response.statusCode, 404) t.assert.strictEqual(body.toString(), 'this was not found') done() }) }) await t.test('root framework-unsupported method', (t, done) => { t.plan(3) sget({ method: 'PROPFIND', url: getServerUrl(fastify), body: JSON.stringify({ hello: 'world' }), headers: { 'Content-Type': 'application/json' } }, (err, response, body) => { t.assert.ifError(err) t.assert.strictEqual(response.statusCode, 404) t.assert.strictEqual(body.toString(), 'this was not found') done() }) }) await t.test('root unsupported route', (t, done) => { t.plan(3) sget({ method: 'GET', url: getServerUrl(fastify) + '/notSupported' }, (err, response, body) => { t.assert.ifError(err) t.assert.strictEqual(response.statusCode, 404) t.assert.strictEqual(body.toString(), 'this was not found') done() }) }) await t.test('unsupported method', (t, done) => { t.plan(3) sget({ method: 'PUT', url: getServerUrl(fastify) + '/test', body: JSON.stringify({ hello: 'world' }), headers: { 'Content-Type': 'application/json' } }, (err, response, body) => { t.assert.ifError(err) t.assert.strictEqual(response.statusCode, 404) t.assert.strictEqual(body.toString(), 'this was not found 2') done() }) }) await t.test('framework-unsupported method', (t, done) => { t.plan(3) sget({ method: 'PROPFIND', url: getServerUrl(fastify) + '/test', body: JSON.stringify({ hello: 'world' }), headers: { 'Content-Type': 'application/json' } }, (err, response, body) => { t.assert.ifError(err) t.assert.strictEqual(response.statusCode, 404) t.assert.strictEqual(body.toString(), 'this was not found 2') done() }) }) await t.test('unsupported route', (t, done) => { t.plan(3) sget({ method: 'GET', url: getServerUrl(fastify) + '/test/notSupported' }, (err, response, body) => { t.assert.ifError(err) t.assert.strictEqual(response.statusCode, 404) t.assert.strictEqual(body.toString(), 'this was not found 2') done() }) }) await t.test('unsupported method 2', (t, done) => { t.plan(3) sget({ method: 'PUT', url: getServerUrl(fastify) + '/test2', body: JSON.stringify({ hello: 'world' }), headers: { 'Content-Type': 'application/json' } }, (err, response, body) => { t.assert.ifError(err) t.assert.strictEqual(response.statusCode, 404) t.assert.strictEqual(body.toString(), 'this was not found 3') done() }) }) await t.test('framework-unsupported method 2', (t, done) => { t.plan(3) sget({ method: 'PROPFIND', url: getServerUrl(fastify) + '/test2', body: JSON.stringify({ hello: 'world' }), headers: { 'Content-Type': 'application/json' } }, (err, response, body) => { t.assert.ifError(err) t.assert.strictEqual(response.statusCode, 404) t.assert.strictEqual(body.toString(), 'this was not found 3') done() }) }) await t.test('unsupported route 2', (t, done) => { t.plan(3) sget({ method: 'GET', url: getServerUrl(fastify) + '/test2/notSupported' }, (err, response, body) => { t.assert.ifError(err) t.assert.strictEqual(response.statusCode, 404) t.assert.strictEqual(body.toString(), 'this was not found 3') done() }) }) await t.test('unsupported method 3', (t, done) => { t.plan(3) sget({ method: 'PUT', url: getServerUrl(fastify) + '/test3/', body: JSON.stringify({ hello: 'world' }), headers: { 'Content-Type': 'application/json' } }, (err, response, body) => { t.assert.ifError(err) t.assert.strictEqual(response.statusCode, 404) t.assert.strictEqual(body.toString(), 'this was not found 4') done() }) }) await t.test('framework-unsupported method 3', (t, done) => { t.plan(3) sget({ method: 'PROPFIND', url: getServerUrl(fastify) + '/test3/', body: JSON.stringify({ hello: 'world' }), headers: { 'Content-Type': 'application/json' } }, (err, response, body) => { t.assert.ifError(err) t.assert.strictEqual(response.statusCode, 404) t.assert.strictEqual(body.toString(), 'this was not found 4') done() }) }) await t.test('unsupported route 3', (t, done) => { t.plan(3) sget({ method: 'GET', url: getServerUrl(fastify) + '/test3/notSupported' }, (err, response, body) => { t.assert.ifError(err) t.assert.strictEqual(response.statusCode, 404) t.assert.strictEqual(body.toString(), 'this was not found 4') done() }) }) }) test('custom 404 hook and handler context', async t => { t.plan(19) const fastify = Fastify() fastify.decorate('foo', 42) fastify.addHook('onRequest', function (req, res, done) { t.assert.strictEqual(this.foo, 42) done() }) fastify.addHook('preHandler', function (request, reply, done) { t.assert.strictEqual(this.foo, 42) done() }) fastify.addHook('onSend', function (request, reply, payload, done) { t.assert.strictEqual(this.foo, 42) done() }) fastify.addHook('onResponse', function (request, reply, done) { t.assert.strictEqual(this.foo, 42) done() }) fastify.setNotFoundHandler(function (req, reply) { t.assert.strictEqual(this.foo, 42) reply.code(404).send('this was not found') }) fastify.register(function (instance, opts, done) { instance.decorate('bar', 84) instance.addHook('onRequest', function (req, res, done) { t.assert.strictEqual(this.bar, 84) done() }) instance.addHook('preHandler', function (request, reply, done) { t.assert.strictEqual(this.bar, 84) done() }) instance.addHook('onSend', function (request, reply, payload, done) { t.assert.strictEqual(this.bar, 84) done() }) instance.addHook('onResponse', function (request, reply, done) { t.assert.strictEqual(this.bar, 84) done() }) instance.setNotFoundHandler(function (req, reply) { t.assert.strictEqual(this.foo, 42) t.assert.strictEqual(this.bar, 84) reply.code(404).send('encapsulated was not found') }) done() }, { prefix: '/encapsulated' }) { const res = await fastify.inject('/not-found') t.assert.strictEqual(res.statusCode, 404) t.assert.strictEqual(res.payload, 'this was not found') } { const res = await fastify.inject('/encapsulated/not-found') t.assert.strictEqual(res.statusCode, 404) t.assert.strictEqual(res.payload, 'encapsulated was not found') } }) test('encapsulated custom 404 without - prefix hook and handler context', (t, done) => { t.plan(13) const fastify = Fastify() fastify.decorate('foo', 42) fastify.register(function (instance, opts, done) { instance.decorate('bar', 84) instance.addHook('onRequest', function (req, res, done) { t.assert.strictEqual(this.foo, 42) t.assert.strictEqual(this.bar, 84) done() }) instance.addHook('preHandler', function (request, reply, done) { t.assert.strictEqual(this.foo, 42) t.assert.strictEqual(this.bar, 84) done() }) instance.addHook('onSend', function (request, reply, payload, done) { t.assert.strictEqual(this.foo, 42) t.assert.strictEqual(this.bar, 84) done() }) instance.addHook('onResponse', function (request, reply, done) { t.assert.strictEqual(this.foo, 42) t.assert.strictEqual(this.bar, 84) done() }) instance.setNotFoundHandler(function (request, reply) { t.assert.strictEqual(this.foo, 42) t.assert.strictEqual(this.bar, 84) reply.code(404).send('custom not found') }) done() }) fastify.inject('/not-found', (err, res) => { t.assert.ifError(err) t.assert.strictEqual(res.statusCode, 404) t.assert.strictEqual(res.payload, 'custom not found') done() }) }) test('run hooks on default 404', (t, done) => { t.plan(7) const fastify = Fastify() fastify.addHook('onRequest', function (req, res, done) { t.assert.ok(true, 'onRequest called') done() }) fastify.addHook('preHandler', function (request, reply, done) { t.assert.ok(true, 'preHandler called') done() }) fastify.addHook('onSend', function (request, reply, payload, done) { t.assert.ok(true, 'onSend called') done() }) fastify.addHook('onResponse', function (request, reply, done) { t.assert.ok(true, 'onResponse called') done() }) fastify.get('/', function (req, reply) { reply.send({ hello: 'world' }) }) t.after(() => { fastify.close() }) fastify.listen({ port: 0 }, err => { t.assert.ifError(err) sget({ method: 'PUT', url: getServerUrl(fastify), body: JSON.stringify({ hello: 'world' }), headers: { 'Content-Type': 'application/json' } }, (err, response, body) => { t.assert.ifError(err) t.assert.strictEqual(response.statusCode, 404) done() }) }) }) test('run non-encapsulated plugin hooks on default 404', (t, done) => { t.plan(6) const fastify = Fastify() fastify.register(fp(function (instance, options, done) { instance.addHook('onRequest', function (req, res, done) { t.assert.ok(true, 'onRequest called') done() }) instance.addHook('preHandler', function (request, reply, done) { t.assert.ok(true, 'preHandler called') done() }) instance.addHook('onSend', function (request, reply, payload, done) { t.assert.ok(true, 'onSend called') done() }) instance.addHook('onResponse', function (request, reply, done) { t.assert.ok(true, 'onResponse called') done() }) done() })) fastify.get('/', function (req, reply) { reply.send({ hello: 'world' }) }) fastify.inject({ method: 'POST', url: '/', payload: { hello: 'world' } }, (err, res) => { t.assert.ifError(err) t.assert.strictEqual(res.statusCode, 404) done() }) }) test('run non-encapsulated plugin hooks on custom 404', (t, done) => { t.plan(11) const fastify = Fastify() const plugin = fp((instance, opts, done) => { instance.addHook('onRequest', function (req, res, done) { t.assert.ok(true, 'onRequest called') done() }) instance.addHook('preHandler', function (request, reply, done) { t.assert.ok(true, 'preHandler called') done() }) instance.addHook('onSend', function (request, reply, payload, done) { t.assert.ok(true, 'onSend called') done() }) instance.addHook('onResponse', function (request, reply, done) { t.assert.ok(true, 'onResponse called') done() }) done() }) fastify.register(plugin) fastify.get('/', function (req, reply) { reply.send({ hello: 'world' }) }) fastify.setNotFoundHandler(function (req, reply) { reply.code(404).send('this was not found') }) fastify.register(plugin) // Registering plugin after handler also works fastify.inject({ url: '/not-found' }, (err, res) => { t.assert.ifError(err) t.assert.strictEqual(res.statusCode, 404) t.assert.strictEqual(res.payload, 'this was not found') done() }) }) test('run hook with encapsulated 404', (t, done) => { t.plan(11) const fastify = Fastify() fastify.addHook('onRequest', function (req, res, done) { t.assert.ok(true, 'onRequest called') done() }) fastify.addHook('preHandler', function (request, reply, done) { t.assert.ok(true, 'preHandler called') done() }) fastify.addHook('onSend', function (request, reply, payload, done) { t.assert.ok(true, 'onSend called') done() }) fastify.addHook('onResponse', function (request, reply, done) { t.assert.ok(true, 'onResponse called') done() }) fastify.register(function (f, opts, done) { f.setNotFoundHandler(function (req, reply) { reply.code(404).send('this was not found 2') }) f.addHook('onRequest', function (req, res, done) { t.assert.ok(true, 'onRequest 2 called') done() }) f.addHook('preHandler', function (request, reply, done) { t.assert.ok(true, 'preHandler 2 called') done() }) f.addHook('onSend', function (request, reply, payload, done) { t.assert.ok(true, 'onSend 2 called') done() }) f.addHook('onResponse', function (request, reply, done) { t.assert.ok(true, 'onResponse 2 called') done() }) done() }, { prefix: '/test' }) t.after(() => { fastify.close() }) fastify.listen({ port: 0 }, err => { t.assert.ifError(err) sget({ method: 'PUT', url: getServerUrl(fastify) + '/test', body: JSON.stringify({ hello: 'world' }), headers: { 'Content-Type': 'application/json' } }, (err, response, body) => { t.assert.ifError(err) t.assert.strictEqual(response.statusCode, 404) done() }) }) }) test('run hook with encapsulated 404 and framework-unsupported method', (t, done) => { t.plan(11) const fastify = Fastify() fastify.addHook('onRequest', function (req, res, done) { t.assert.ok(true, 'onRequest called') done() }) fastify.addHook('preHandler', function (request, reply, done) { t.assert.ok(true, 'preHandler called') done() }) fastify.addHook('onSend', function (request, reply, payload, done) { t.assert.ok(true, 'onSend called') done() }) fastify.addHook('onResponse', function (request, reply, done) { t.assert.ok(true, 'onResponse called') done() }) fastify.register(function (f, opts, done) { f.setNotFoundHandler(function (req, reply) { reply.code(404).send('this was not found 2') }) f.addHook('onRequest', function (req, res, done) { t.assert.ok(true, 'onRequest 2 called') done() }) f.addHook('preHandler', function (request, reply, done) { t.assert.ok(true, 'preHandler 2 called') done() }) f.addHook('onSend', function (request, reply, payload, done) { t.assert.ok(true, 'onSend 2 called') done() }) f.addHook('onResponse', function (request, reply, done) { t.assert.ok(true, 'onResponse 2 called') done() }) done() }, { prefix: '/test' }) t.after(() => { fastify.close() }) fastify.listen({ port: 0 }, err => { t.assert.ifError(err) sget({ method: 'PROPFIND', url: getServerUrl(fastify) + '/test', body: JSON.stringify({ hello: 'world' }), headers: { 'Content-Type': 'application/json' } }, (err, response, body) => { t.assert.ifError(err) t.assert.strictEqual(response.statusCode, 404) done() }) }) }) test('hooks check 404', (t, done) => { t.plan(13) const fastify = Fastify() fastify.get('/', function (req, reply) { reply.send({ hello: 'world' }) }) fastify.addHook('onSend', (req, reply, payload, done) => { t.assert.deepStrictEqual(req.query, { foo: 'asd' }) t.assert.ok(true, 'called onSend') done() }) fastify.addHook('onRequest', (req, res, done) => { t.assert.ok(true, 'called onRequest') done() }) fastify.addHook('onResponse', (request, reply, done) => { t.assert.ok(true, 'calledonResponse') done() }) t.after(() => { fastify.close() }) fastify.listen({ port: 0 }, err => { t.assert.ifError(err) sget({ method: 'PUT', url: getServerUrl(fastify) + '?foo=asd', body: JSON.stringify({ hello: 'world' }), headers: { 'Content-Type': 'application/json' } }, (err, response, body) => { t.assert.ifError(err) t.assert.strictEqual(response.statusCode, 404) }) sget({ method: 'GET', url: getServerUrl(fastify) + '/notSupported?foo=asd' }, (err, response, body) => { t.assert.ifError(err) t.assert.strictEqual(response.statusCode, 404) done() }) }) }) test('setNotFoundHandler should not suppress duplicated routes checking', t => { t.plan(1) const fastify = Fastify() try { fastify.get('/', function (req, reply) { reply.send({ hello: 'world' }) }) fastify.get('/', function (req, reply) { reply.send({ hello: 'world' }) }) fastify.setNotFoundHandler(function (req, reply) { reply.code(404).send('this was not found') }) t.assert.fail('setNotFoundHandler should not interfere duplicated route error') } catch (error) { t.assert.ok(error) } }) test('log debug for 404', async t => { t.plan(1) const Writable = require('node:stream').Writable const logStream = new Writable() logStream.logs = [] logStream._write = function (chunk, encoding, callback) { this.logs.push(chunk.toString()) callback() } const fastify = Fastify({ logger: { level: 'trace', stream: logStream } }) fastify.get('/', function (req, reply) { reply.send({ hello: 'world' }) }) t.after(() => { fastify.close() }) await t.test('log debug', (t, done) => { t.plan(7) fastify.inject({ method: 'GET', url: '/not-found' }, (err, response) => { t.assert.ifError(err) t.assert.strictEqual(response.statusCode, 404) const INFO_LEVEL = 30 t.assert.strictEqual(JSON.parse(logStream.logs[0]).msg, 'incoming request') t.assert.strictEqual(JSON.parse(logStream.logs[1]).msg, 'Route GET:/not-found not found') t.assert.strictEqual(JSON.parse(logStream.logs[1]).level, INFO_LEVEL) t.assert.strictEqual(JSON.parse(logStream.logs[2]).msg, 'request completed') t.assert.strictEqual(logStream.logs.length, 3) done() }) }) }) test('Unknown method', (t, done) => { t.plan(5) const fastify = Fastify() fastify.get('/', function (req, reply) { reply.send({ hello: 'world' }) }) t.after(() => { fastify.close() }) fastify.listen({ port: 0 }, err => { t.assert.ifError(err) const handler = () => {} // See https://github.com/fastify/light-my-request/pull/20 t.assert.throws(() => fastify.inject({ method: 'UNKNOWN_METHOD', url: '/' }, handler), Error) sget({ method: 'UNKNOWN_METHOD', url: getServerUrl(fastify) }, (err, response, body) => { t.assert.ifError(err) t.assert.strictEqual(response.statusCode, 400) t.assert.deepStrictEqual(JSON.parse(body), { error: 'Bad Request', message: 'Client Error', statusCode: 400 }) done() }) }) }) test('recognizes errors from the http-errors module', (t, done) => { t.plan(5) const fastify = Fastify() fastify.get('/', function (req, reply) { reply.send(new errors.NotFound()) }) t.after(() => { fastify.close() }) fastify.listen({ port: 0 }, err => { t.assert.ifError(err) fastify.inject({ method: 'GET', url: '/' }, (err, res) => { t.assert.ifError(err) t.assert.strictEqual(res.statusCode, 404) sget(getServerUrl(fastify), (err, response, body) => { t.assert.ifError(err) const obj = JSON.parse(body.toString()) t.assert.deepStrictEqual(obj, { error: 'Not Found', message: 'Not Found', statusCode: 404 }) done() }) }) }) }) test('the default 404 handler can be invoked inside a prefixed plugin', (t, done) => { t.plan(3) const fastify = Fastify() fastify.register(function (instance, opts, done) { instance.get('/path', function (request, reply) { reply.send(new errors.NotFound()) }) done() }, { prefix: '/v1' }) fastify.inject('/v1/path', (err, res) => { t.assert.ifError(err) t.assert.strictEqual(res.statusCode, 404) t.assert.deepStrictEqual(JSON.parse(res.payload), { error: 'Not Found', message: 'Not Found', statusCode: 404 }) done() }) }) test('an inherited custom 404 handler can be invoked inside a prefixed plugin', (t, done) => { t.plan(3) const fastify = Fastify() fastify.setNotFoundHandler(function (request, reply) { reply.code(404).send('custom handler') }) fastify.register(function (instance, opts, done) { instance.get('/path', function (request, reply) { reply.send(new errors.NotFound()) }) done() }, { prefix: '/v1' }) fastify.inject('/v1/path', (err, res) => { t.assert.ifError(err) t.assert.strictEqual(res.statusCode, 404) t.assert.deepStrictEqual(JSON.parse(res.payload), { error: 'Not Found', message: 'Not Found', statusCode: 404 }) done() }) }) test('encapsulated custom 404 handler without a prefix is the handler for the entire 404 level', async t => { t.plan(4) const fastify = Fastify() fastify.register(function (instance, opts, done) { instance.setNotFoundHandler(function (request, reply) { reply.code(404).send('custom handler') }) done() }) fastify.register(function (instance, opts, done) { instance.register(function (instance2, opts, done) { instance2.setNotFoundHandler(function (request, reply) { reply.code(404).send('custom handler 2') }) done() }) done() }, { prefix: 'prefixed' }) { const res = await fastify.inject('/not-found') t.assert.strictEqual(res.statusCode, 404) t.assert.strictEqual(res.payload, 'custom handler') } { const res = await fastify.inject('/prefixed/not-found') t.assert.strictEqual(res.statusCode, 404) t.assert.strictEqual(res.payload, 'custom handler 2') } }) test('cannot set notFoundHandler after binding', (t, done) => { t.plan(2) const fastify = Fastify() t.after(() => { fastify.close() }) fastify.listen({ port: 0 }, err => { t.assert.ifError(err) try { fastify.setNotFoundHandler(() => { }) t.assert.fail() } catch (e) { t.assert.ok(true) done() } }) }) test('404 inside onSend', (t, done) => { t.plan(3) const fastify = Fastify() let called = false fastify.get('/', function (req, reply) { reply.send({ hello: 'world' }) }) fastify.addHook('onSend', function (request, reply, payload, done) { if (!called) { called = true done(new errors.NotFound()) } else { done() } }) t.after(() => { fastify.close() }) fastify.listen({ port: 0 }, err => { t.assert.ifError(err) sget({ method: 'GET', url: getServerUrl(fastify) }, (err, response, body) => { t.assert.ifError(err) t.assert.strictEqual(response.statusCode, 404) done() }) }) }) // https://github.com/fastify/fastify/issues/868 test('onSend hooks run when an encapsulated route invokes the notFound handler', (t, done) => { t.plan(3) const fastify = Fastify() fastify.register((instance, options, done) => { instance.addHook('onSend', (request, reply, payload, done) => { t.assert.ok(true, 'onSend hook called') done() }) instance.get('/', (request, reply) => { reply.send(new errors.NotFound()) }) done() }) fastify.inject('/', (err, res) => { t.assert.ifError(err) t.assert.strictEqual(res.statusCode, 404) done() }) }) // https://github.com/fastify/fastify/issues/713 test('preHandler option for setNotFoundHandler', async t => { t.plan(10) await t.test('preHandler option', (t, done) => { t.plan(2) const fastify = Fastify() fastify.setNotFoundHandler({ preHandler: (req, reply, done) => { req.body.preHandler = true done() } }, function (req, reply) { reply.code(404).send(req.body) }) fastify.inject({ method: 'POST', url: '/not-found', payload: { hello: 'world' } }, (err, res) => { t.assert.ifError(err) const payload = JSON.parse(res.payload) t.assert.deepStrictEqual(payload, { preHandler: true, hello: 'world' }) done() }) }) // https://github.com/fastify/fastify/issues/2229 await t.test('preHandler hook in setNotFoundHandler should be called when callNotFound', { timeout: 40000 }, (t, done) => { t.plan(3) const fastify = Fastify() fastify.setNotFoundHandler({ preHandler: (req, reply, done) => { req.body.preHandler = true done() } }, function (req, reply) { reply.code(404).send(req.body) }) fastify.post('/', function (req, reply) { t.assert.strictEqual(reply.callNotFound(), reply) }) fastify.inject({ method: 'POST', url: '/', payload: { hello: 'world' } }, (err, res) => { t.assert.ifError(err) const payload = JSON.parse(res.payload) t.assert.deepStrictEqual(payload, { preHandler: true, hello: 'world' }) done() }) }) await t.test('preHandler hook in setNotFoundHandler should accept an array of functions and be called when callNotFound', (t, done) => { t.plan(2) const fastify = Fastify() fastify.setNotFoundHandler({ preHandler: [ (req, reply, done) => { req.body.preHandler1 = true done() }, (req, reply, done) => { req.body.preHandler2 = true done() } ] }, function (req, reply) { reply.code(404).send(req.body) }) fastify.post('/', function (req, reply) { reply.callNotFound() }) fastify.inject({ method: 'POST', url: '/', payload: { hello: 'world' } }, (err, res) => { t.assert.ifError(err) const payload = JSON.parse(res.payload) t.assert.deepStrictEqual(payload, { preHandler1: true, preHandler2: true, hello: 'world' }) done() }) }) await t.test('preHandler option should be called after preHandler hook', (t, done) => { t.plan(2) const fastify = Fastify() fastify.addHook('preHandler', (req, reply, done) => { req.body.check = 'a' done() }) fastify.setNotFoundHandler({ preHandler: (req, reply, done) => { req.body.check += 'b' done() } }, (req, reply) => { reply.send(req.body) }) fastify.inject({ method: 'POST', url: '/', payload: { hello: 'world' } }, (err, res) => { t.assert.ifError(err) const payload = JSON.parse(res.payload) t.assert.deepStrictEqual(payload, { check: 'ab', hello: 'world' }) done() }) }) await t.test('preHandler option should be unique per prefix', async t => { t.plan(2) const fastify = Fastify() fastify.setNotFoundHandler({ preHandler: (req, reply, done) => { req.body.hello = 'earth' done() } }, (req, reply) => { reply.send(req.body) }) fastify.register(function (i, o, n) { i.setNotFoundHandler((req, reply) => { reply.send(req.body) }) n() }, { prefix: '/no' }) { const res = await fastify.inject({ method: 'POST', url: '/not-found', payload: { hello: 'world' } }) const payload = JSON.parse(res.payload) t.assert.deepStrictEqual(payload, { hello: 'earth' }) } { const res = await fastify.inject({ method: 'POST', url: '/no/not-found', payload: { hello: 'world' } }) const payload = JSON.parse(res.payload) t.assert.deepStrictEqual(payload, { hello: 'world' }) } }) await t.test('preHandler option should handle errors', (t, done) => { t.plan(3) const fastify = Fastify() fastify.setNotFoundHandler({ preHandler: (req, reply, done) => { done(new Error('kaboom')) } }, (req, reply) => { reply.send(req.body) }) fastify.inject({ method: 'POST', url: '/not-found', payload: { hello: 'world' } }, (err, res) => { t.assert.ifError(err) const payload = JSON.parse(res.payload) t.assert.strictEqual(res.statusCode, 500) t.assert.deepStrictEqual(payload, { message: 'kaboom', error: 'Internal Server Error', statusCode: 500 }) done() }) }) await t.test('preHandler option should handle errors with custom status code', (t, done) => { t.plan(3) const fastify = Fastify() fastify.setNotFoundHandler({ preHandler: (req, reply, done) => { reply.code(401) done(new Error('go away')) } }, (req, reply) => { reply.send(req.body) }) fastify.inject({ method: 'POST', url: '/not-found', payload: { hello: 'world' } }, (err, res) => { t.assert.ifError(err) const payload = JSON.parse(res.payload) t.assert.strictEqual(res.statusCode, 401) t.assert.deepStrictEqual(payload, { message: 'go away', error: 'Unauthorized', statusCode: 401 }) done() }) }) await t.test('preHandler option could accept an array of functions', (t, done) => { t.plan(2) const fastify = Fastify() fastify.setNotFoundHandler({ preHandler: [ (req, reply, done) => { req.body.preHandler = 'a' done() }, (req, reply, done) => { req.body.preHandler += 'b' done() } ] }, (req, reply) => { reply.send(req.body) }) fastify.inject({ method: 'POST', url: '/not-found', payload: { hello: 'world' } }, (err, res) => { t.assert.ifError(err) const payload = JSON.parse(res.payload) t.assert.deepStrictEqual(payload, { preHandler: 'ab', hello: 'world' }) done() }) }) await t.test('preHandler option does not interfere with preHandler', async t => { t.plan(2) const fastify = Fastify() fastify.addHook('preHandler', (req, reply, done) => { req.body.check = 'a' done() }) fastify.setNotFoundHandler({ preHandler: (req, reply, done) => { req.body.check += 'b' done() } }, (req, reply) => { reply.send(req.body) }) fastify.register(function (i, o, n) { i.setNotFoundHandler((req, reply) => { reply.send(req.body) }) n() }, { prefix: '/no' }) { const res = await fastify.inject({ method: 'post', url: '/not-found', payload: { hello: 'world' } }) const payload = JSON.parse(res.payload) t.assert.deepStrictEqual(payload, { check: 'ab', hello: 'world' }) } { const res = await fastify.inject({ method: 'post', url: '/no/not-found', payload: { hello: 'world' } }) const payload = JSON.parse(res.payload) t.assert.deepStrictEqual(payload, { check: 'a', hello: 'world' }) } }) await t.test('preHandler option should keep the context', (t, done) => { t.plan(3) const fastify = Fastify() fastify.decorate('foo', 42) fastify.setNotFoundHandler({ preHandler: function (req, reply, done) { t.assert.strictEqual(this.foo, 42) this.foo += 1 req.body.foo = this.foo done() } }, (req, reply) => { reply.send(req.body) }) fastify.inject({ method: 'POST', url: '/not-found', payload: { hello: 'world' } }, (err, res) => { t.assert.ifError(err) const payload = JSON.parse(res.payload) t.assert.deepStrictEqual(payload, { foo: 43, hello: 'world' }) done() }) }) }) test('reply.notFound invoked the notFound handler', (t, done) => { t.plan(3) const fastify = Fastify() fastify.setNotFoundHandler((req, reply) => { reply.code(404).send(new Error('kaboom')) }) fastify.get('/', function (req, reply) { reply.callNotFound() }) fastify.inject({ url: '/', method: 'GET' }, (err, res) => { t.assert.ifError(err) t.assert.strictEqual(res.statusCode, 404) t.assert.deepStrictEqual(JSON.parse(res.payload), { error: 'Not Found', message: 'kaboom', statusCode: 404 }) done() }) }) test('The custom error handler should be invoked after the custom not found handler', (t, done) => { t.plan(6) const fastify = Fastify() const order = [1, 2] fastify.setErrorHandler((err, req, reply) => { t.assert.strictEqual(order.shift(), 2) t.assert.ok(err instanceof Error) reply.send(err) }) fastify.setNotFoundHandler((req, reply) => { t.assert.strictEqual(order.shift(), 1) reply.code(404).send(new Error('kaboom')) }) fastify.get('/', function (req, reply) { reply.callNotFound() }) fastify.inject({ url: '/', method: 'GET' }, (err, res) => { t.assert.ifError(err) t.assert.strictEqual(res.statusCode, 404) t.assert.deepStrictEqual(JSON.parse(res.payload), { error: 'Not Found', message: 'kaboom', statusCode: 404 }) done() }) }) test('If the custom not found handler does not use an Error, the custom error handler should not be called', (t, done) => { t.plan(3) const fastify = Fastify() fastify.setErrorHandler((_err, req, reply) => { t.assert.fail('Should not be called') }) fastify.setNotFoundHandler((req, reply) => { reply.code(404).send('kaboom') }) fastify.get('/', function (req, reply) { reply.callNotFound() }) fastify.inject({ url: '/', method: 'GET' }, (err, res) => { t.assert.ifError(err) t.assert.strictEqual(res.statusCode, 404) t.assert.strictEqual(res.payload, 'kaboom') done() }) }) test('preValidation option', (t, done) => { t.plan(3) const fastify = Fastify() fastify.decorate('foo', true) fastify.setNotFoundHandler({ preValidation: function (req, reply, done) { t.assert.ok(this.foo) done() } }, function (req, reply) { reply.code(404).send(req.body) }) fastify.inject({ method: 'POST', url: '/not-found', payload: { hello: 'world' } }, (err, res) => { t.assert.ifError(err) const payload = JSON.parse(res.payload) t.assert.deepStrictEqual(payload, { hello: 'world' }) done() }) }) test('preValidation option could accept an array of functions', (t, done) => { t.plan(4) const fastify = Fastify() fastify.setNotFoundHandler({ preValidation: [ (req, reply, done) => { t.assert.ok('called') done() }, (req, reply, done) => { t.assert.ok('called') done() } ] }, (req, reply) => { reply.send(req.body) }) fastify.inject({ method: 'POST', url: '/not-found', payload: { hello: 'world' } }, (err, res) => { t.assert.ifError(err) const payload = JSON.parse(res.payload) t.assert.deepStrictEqual(payload, { hello: 'world' }) done() }) }) test('Should fail to invoke callNotFound inside a 404 handler', (t, done) => { t.plan(5) let fastify = null const logStream = split(JSON.parse) try { fastify = Fastify({ logger: { stream: logStream, level: 'warn' } }) } catch (e) { t.assert.fail() } fastify.setNotFoundHandler((req, reply) => { reply.callNotFound() }) fastify.get('/', function (req, reply) { reply.callNotFound() }) logStream.once('data', line => { t.assert.strictEqual(line.msg, 'Trying to send a NotFound error inside a 404 handler. Sending basic 404 response.') t.assert.strictEqual(line.level, 40) }) fastify.inject({ url: '/', method: 'GET' }, (err, res) => { t.assert.ifError(err) t.assert.strictEqual(res.statusCode, 404) t.assert.strictEqual(res.payload, '404 Not Found') done() }) }) test('400 in case of bad url (pre find-my-way v2.2.0 was a 404)', async t => { await t.test('Dynamic route', (t, done) => { t.plan(3) const fastify = Fastify() fastify.get('/hello/:id', () => t.assert.fail('we should not be here')) fastify.inject({ url: '/hello/%world', method: 'GET' }, (err, response) => { t.assert.ifError(err) t.assert.strictEqual(response.statusCode, 400) t.assert.deepStrictEqual(JSON.parse(response.payload), { error: 'Bad Request', message: "'/hello/%world' is not a valid url component", statusCode: 400, code: 'FST_ERR_BAD_URL' }) done() }) }) await t.test('Wildcard', (t, done) => { t.plan(3) const fastify = Fastify() fastify.get('*', () => t.assert.fail('we should not be here')) fastify.inject({ url: '/hello/%world', method: 'GET' }, (err, response) => { t.assert.ifError(err) t.assert.strictEqual(response.statusCode, 400) t.assert.deepStrictEqual(JSON.parse(response.payload), { error: 'Bad Request', message: "'/hello/%world' is not a valid url component", statusCode: 400, code: 'FST_ERR_BAD_URL' }) done() }) }) await t.test('No route registered', (t, done) => { t.plan(3) const fastify = Fastify() fastify.inject({ url: '/%c0', method: 'GET' }, (err, response) => { t.assert.ifError(err) t.assert.strictEqual(response.statusCode, 404) t.assert.deepStrictEqual(JSON.parse(response.payload), { error: 'Not Found', message: 'Route GET:/%c0 not found', statusCode: 404 }) done() }) }) await t.test('Only / is registered', (t, done) => { t.plan(3) const fastify = Fastify() fastify.get('/', () => t.assert.fail('we should not be here')) fastify.inject({ url: '/non-existing', method: 'GET' }, (err, response) => { t.assert.ifError(err) t.assert.strictEqual(response.statusCode, 404) t.assert.deepStrictEqual(JSON.parse(response.payload), { error: 'Not Found', message: 'Route GET:/non-existing not found', statusCode: 404 }) done() }) }) await t.test('customized 404', (t, done) => { t.plan(3) const fastify = Fastify({ logger: true }) fastify.setNotFoundHandler(function (req, reply) { reply.code(404).send('this was not found') }) fastify.inject({ url: '/%c0', method: 'GET' }, (err, response) => { t.assert.ifError(err) t.assert.strictEqual(response.statusCode, 404) t.assert.deepStrictEqual(response.payload, 'this was not found') done() }) }) }) test('setNotFoundHandler should be chaining fastify instance', async t => { await t.test('Register route after setNotFoundHandler', async t => { t.plan(4) const fastify = Fastify() fastify.setNotFoundHandler(function (_req, reply) { reply.code(404).send('this was not found') }).get('/valid-route', function (_req, reply) { reply.send('valid route') }) { const response = await fastify.inject({ url: '/invalid-route', method: 'GET' })