'use strict' const t = require('tap') const test = t.test const fp = require('fastify-plugin') const httpErrors = require('http-errors') const sget = require('simple-get').concat const errors = require('http-errors') const split = require('split2') const Fastify = require('..') test('default 404', t => { t.plan(3) const test = t.test const fastify = Fastify() fastify.get('/', function (req, reply) { reply.send({ hello: 'world' }) }) t.tearDown(fastify.close.bind(fastify)) fastify.listen(0, err => { t.error(err) test('unsupported method', t => { t.plan(3) sget({ method: 'PUT', url: 'http://localhost:' + fastify.server.address().port, body: {}, json: true }, (err, response, body) => { t.error(err) t.strictEqual(response.statusCode, 404) t.strictEqual(response.headers['content-type'], 'application/json; charset=utf-8') }) }) test('unsupported route', t => { t.plan(3) sget({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port + '/notSupported', body: {}, json: true }, (err, response, body) => { t.error(err) t.strictEqual(response.statusCode, 404) t.strictEqual(response.headers['content-type'], 'application/json; charset=utf-8') }) }) }) }) test('customized 404', t => { t.plan(5) const test = t.test 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.tearDown(fastify.close.bind(fastify)) fastify.listen(0, err => { t.error(err) test('unsupported method', t => { t.plan(3) sget({ method: 'PUT', url: 'http://localhost:' + fastify.server.address().port, body: JSON.stringify({ hello: 'world' }), headers: { 'Content-Type': 'application/json' } }, (err, response, body) => { t.error(err) t.strictEqual(response.statusCode, 404) t.strictEqual(body.toString(), 'this was not found') }) }) test('unsupported route', t => { t.plan(3) sget({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port + '/notSupported' }, (err, response, body) => { t.error(err) t.strictEqual(response.statusCode, 404) t.strictEqual(body.toString(), 'this was not found') }) }) test('with error object', t => { t.plan(3) sget({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port + '/with-error' }, (err, response, body) => { t.error(err) t.strictEqual(response.statusCode, 404) t.deepEqual(JSON.parse(body), { error: 'Not Found', message: 'Not Found', statusCode: 404 }) }) }) test('error object with headers property', t => { t.plan(4) sget({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port + '/with-error-custom-header' }, (err, response, body) => { t.error(err) t.strictEqual(response.statusCode, 404) t.strictEqual(response.headers['x-foo'], 'bar') t.deepEqual(JSON.parse(body), { error: 'Not Found', message: 'Not Found', statusCode: 404 }) }) }) }) }) test('custom header in notFound handler', t => { t.plan(2) const test = t.test const fastify = Fastify() fastify.setNotFoundHandler(function (req, reply) { reply.code(404).header('x-foo', 'bar').send('this was not found') }) t.tearDown(fastify.close.bind(fastify)) fastify.listen(0, err => { t.error(err) test('not found with custom header', t => { t.plan(4) sget({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port + '/notSupported' }, (err, response, body) => { t.error(err) t.strictEqual(response.statusCode, 404) t.strictEqual(response.headers['x-foo'], 'bar') t.strictEqual(body.toString(), 'this was not found') }) }) }) }) test('setting a custom 404 handler multiple times is an error', t => { t.plan(5) t.test('at the root level', t => { t.plan(2) const fastify = Fastify() fastify.setNotFoundHandler(() => {}) try { fastify.setNotFoundHandler(() => {}) t.fail('setting multiple 404 handlers at the same prefix encapsulation level should throw') } catch (err) { t.type(err, Error) t.strictEqual(err.message, 'Not found handler already set for Fastify instance with prefix: \'/\'') } }) t.test('at the plugin level', t => { t.plan(3) const fastify = Fastify() fastify.register((instance, options, next) => { instance.setNotFoundHandler(() => {}) try { instance.setNotFoundHandler(() => {}) t.fail('setting multiple 404 handlers at the same prefix encapsulation level should throw') } catch (err) { t.type(err, Error) t.strictEqual(err.message, 'Not found handler already set for Fastify instance with prefix: \'/prefix\'') } next() }, { prefix: '/prefix' }) fastify.listen(0, err => { t.error(err) fastify.close() }) }) t.test('at multiple levels', t => { t.plan(3) const fastify = Fastify() fastify.register((instance, options, next) => { try { instance.setNotFoundHandler(() => {}) t.fail('setting multiple 404 handlers at the same prefix encapsulation level should throw') } catch (err) { t.type(err, Error) t.strictEqual(err.message, 'Not found handler already set for Fastify instance with prefix: \'/\'') } next() }) fastify.setNotFoundHandler(() => {}) fastify.listen(0, err => { t.error(err) fastify.close() }) }) t.test('at multiple levels / 2', t => { t.plan(3) const fastify = Fastify() fastify.register((instance, options, next) => { instance.setNotFoundHandler(() => {}) instance.register((instance2, options, next) => { try { instance2.setNotFoundHandler(() => {}) t.fail('setting multiple 404 handlers at the same prefix encapsulation level should throw') } catch (err) { t.type(err, Error) t.strictEqual(err.message, 'Not found handler already set for Fastify instance with prefix: \'/prefix\'') } next() }) next() }, { prefix: '/prefix' }) fastify.setNotFoundHandler(() => {}) fastify.listen(0, err => { t.error(err) fastify.close() }) }) t.test('in separate plugins at the same level', t => { t.plan(3) const fastify = Fastify() fastify.register((instance, options, next) => { instance.register((instance2A, options, next) => { instance2A.setNotFoundHandler(() => {}) next() }) instance.register((instance2B, options, next) => { try { instance2B.setNotFoundHandler(() => {}) t.fail('setting multiple 404 handlers at the same prefix encapsulation level should throw') } catch (err) { t.type(err, Error) t.strictEqual(err.message, 'Not found handler already set for Fastify instance with prefix: \'/prefix\'') } next() }) next() }, { prefix: '/prefix' }) fastify.setNotFoundHandler(() => {}) fastify.listen(0, err => { t.error(err) fastify.close() }) }) }) test('encapsulated 404', t => { t.plan(9) const test = t.test 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, next) { f.setNotFoundHandler(function (req, reply) { reply.code(404).send('this was not found 2') }) next() }, { prefix: '/test' }) fastify.register(function (f, opts, next) { f.setNotFoundHandler(function (req, reply) { reply.code(404).send('this was not found 3') }) next() }, { prefix: '/test2' }) fastify.register(function (f, opts, next) { f.setNotFoundHandler(function (request, reply) { reply.code(404).send('this was not found 4') }) next() }, { prefix: '/test3/' }) t.tearDown(fastify.close.bind(fastify)) fastify.listen(0, err => { t.error(err) test('root unsupported method', t => { t.plan(3) sget({ method: 'PUT', url: 'http://localhost:' + fastify.server.address().port, body: JSON.stringify({ hello: 'world' }), headers: { 'Content-Type': 'application/json' } }, (err, response, body) => { t.error(err) t.strictEqual(response.statusCode, 404) t.strictEqual(body.toString(), 'this was not found') }) }) test('root insupported route', t => { t.plan(3) sget({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port + '/notSupported' }, (err, response, body) => { t.error(err) t.strictEqual(response.statusCode, 404) t.strictEqual(body.toString(), 'this was not found') }) }) test('unsupported method', t => { t.plan(3) sget({ method: 'PUT', url: 'http://localhost:' + fastify.server.address().port + '/test', body: JSON.stringify({ hello: 'world' }), headers: { 'Content-Type': 'application/json' } }, (err, response, body) => { t.error(err) t.strictEqual(response.statusCode, 404) t.strictEqual(body.toString(), 'this was not found 2') }) }) test('unsupported route', t => { t.plan(3) sget({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port + '/test/notSupported' }, (err, response, body) => { t.error(err) t.strictEqual(response.statusCode, 404) t.strictEqual(body.toString(), 'this was not found 2') }) }) test('unsupported method bis', t => { t.plan(3) sget({ method: 'PUT', url: 'http://localhost:' + fastify.server.address().port + '/test2', body: JSON.stringify({ hello: 'world' }), headers: { 'Content-Type': 'application/json' } }, (err, response, body) => { t.error(err) t.strictEqual(response.statusCode, 404) t.strictEqual(body.toString(), 'this was not found 3') }) }) test('unsupported route bis', t => { t.plan(3) sget({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port + '/test2/notSupported' }, (err, response, body) => { t.error(err) t.strictEqual(response.statusCode, 404) t.strictEqual(body.toString(), 'this was not found 3') }) }) test('unsupported method 3', t => { t.plan(3) sget({ method: 'PUT', url: 'http://localhost:' + fastify.server.address().port + '/test3/', body: JSON.stringify({ hello: 'world' }), headers: { 'Content-Type': 'application/json' } }, (err, response, body) => { t.error(err) t.strictEqual(response.statusCode, 404) t.strictEqual(body.toString(), 'this was not found 4') }) }) test('unsupported route 3', t => { t.plan(3) sget({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port + '/test3/notSupported' }, (err, response, body) => { t.error(err) t.strictEqual(response.statusCode, 404) t.strictEqual(body.toString(), 'this was not found 4') }) }) }) }) test('custom 404 hook and handler context', t => { t.plan(21) const fastify = Fastify() fastify.decorate('foo', 42) fastify.addHook('onRequest', function (req, res, next) { t.strictEqual(this.foo, 42) next() }) fastify.addHook('preHandler', function (request, reply, next) { t.strictEqual(this.foo, 42) next() }) fastify.addHook('onSend', function (request, reply, payload, next) { t.strictEqual(this.foo, 42) next() }) fastify.addHook('onResponse', function (request, reply, next) { t.strictEqual(this.foo, 42) next() }) fastify.setNotFoundHandler(function (req, reply) { t.strictEqual(this.foo, 42) reply.code(404).send('this was not found') }) fastify.register(function (instance, opts, next) { instance.decorate('bar', 84) instance.addHook('onRequest', function (req, res, next) { t.strictEqual(this.bar, 84) next() }) instance.addHook('preHandler', function (request, reply, next) { t.strictEqual(this.bar, 84) next() }) instance.addHook('onSend', function (request, reply, payload, next) { t.strictEqual(this.bar, 84) next() }) instance.addHook('onResponse', function (request, reply, next) { t.strictEqual(this.bar, 84) next() }) instance.setNotFoundHandler(function (req, reply) { t.strictEqual(this.foo, 42) t.strictEqual(this.bar, 84) reply.code(404).send('encapsulated was not found') }) next() }, { prefix: '/encapsulated' }) fastify.inject('/not-found', (err, res) => { t.error(err) t.strictEqual(res.statusCode, 404) t.strictEqual(res.payload, 'this was not found') }) fastify.inject('/encapsulated/not-found', (err, res) => { t.error(err) t.strictEqual(res.statusCode, 404) t.strictEqual(res.payload, 'encapsulated was not found') }) }) test('encapsulated custom 404 without - prefix hook and handler context', t => { t.plan(13) const fastify = Fastify() fastify.decorate('foo', 42) fastify.register(function (instance, opts, next) { instance.decorate('bar', 84) instance.addHook('onRequest', function (req, res, next) { t.strictEqual(this.foo, 42) t.strictEqual(this.bar, 84) next() }) instance.addHook('preHandler', function (request, reply, next) { t.strictEqual(this.foo, 42) t.strictEqual(this.bar, 84) next() }) instance.addHook('onSend', function (request, reply, payload, next) { t.strictEqual(this.foo, 42) t.strictEqual(this.bar, 84) next() }) instance.addHook('onResponse', function (request, reply, next) { t.strictEqual(this.foo, 42) t.strictEqual(this.bar, 84) next() }) instance.setNotFoundHandler(function (request, reply) { t.strictEqual(this.foo, 42) t.strictEqual(this.bar, 84) reply.code(404).send('custom not found') }) next() }) fastify.inject('/not-found', (err, res) => { t.error(err) t.strictEqual(res.statusCode, 404) t.strictEqual(res.payload, 'custom not found') }) }) test('run hooks and middleware on default 404', t => { t.plan(8) const fastify = Fastify() fastify.addHook('onRequest', function (req, res, next) { t.pass('onRequest called') next() }) fastify.use(function (req, res, next) { t.pass('middleware called') next() }) fastify.addHook('preHandler', function (request, reply, next) { t.pass('preHandler called') next() }) fastify.addHook('onSend', function (request, reply, payload, next) { t.pass('onSend called') next() }) fastify.addHook('onResponse', function (request, reply, next) { t.pass('onResponse called') next() }) fastify.get('/', function (req, reply) { reply.send({ hello: 'world' }) }) t.tearDown(fastify.close.bind(fastify)) fastify.listen(0, err => { t.error(err) sget({ method: 'PUT', url: 'http://localhost:' + fastify.server.address().port, body: JSON.stringify({ hello: 'world' }), headers: { 'Content-Type': 'application/json' } }, (err, response, body) => { t.error(err) t.strictEqual(response.statusCode, 404) }) }) }) test('run non-encapsulated plugin hooks and middleware on default 404', t => { t.plan(7) const fastify = Fastify() fastify.register(fp(function (instance, options, next) { instance.addHook('onRequest', function (req, res, next) { t.pass('onRequest called') next() }) instance.use(function (req, res, next) { t.pass('middleware called') next() }) instance.addHook('preHandler', function (request, reply, next) { t.pass('preHandler called') next() }) instance.addHook('onSend', function (request, reply, payload, next) { t.pass('onSend called') next() }) instance.addHook('onResponse', function (request, reply, next) { t.pass('onResponse called') next() }) next() })) fastify.get('/', function (req, reply) { reply.send({ hello: 'world' }) }) fastify.inject({ method: 'POST', url: '/', payload: { hello: 'world' } }, (err, res) => { t.error(err) t.strictEqual(res.statusCode, 404) }) }) test('run non-encapsulated plugin hooks and middleware on custom 404', t => { t.plan(13) const fastify = Fastify() const plugin = fp((instance, opts, next) => { instance.addHook('onRequest', function (req, res, next) { t.pass('onRequest called') next() }) instance.use(function (req, res, next) { t.pass('middleware called') next() }) instance.addHook('preHandler', function (request, reply, next) { t.pass('preHandler called') next() }) instance.addHook('onSend', function (request, reply, payload, next) { t.pass('onSend called') next() }) instance.addHook('onResponse', function (request, reply, next) { t.pass('onResponse called') next() }) next() }) 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.error(err) t.strictEqual(res.statusCode, 404) t.strictEqual(res.payload, 'this was not found') }) }) test('run hooks and middleware with encapsulated 404', t => { t.plan(13) const fastify = Fastify() fastify.addHook('onRequest', function (req, res, next) { t.pass('onRequest called') next() }) fastify.use(function (req, res, next) { t.pass('middleware called') next() }) fastify.addHook('preHandler', function (request, reply, next) { t.pass('preHandler called') next() }) fastify.addHook('onSend', function (request, reply, payload, next) { t.pass('onSend called') next() }) fastify.addHook('onResponse', function (request, reply, next) { t.pass('onResponse called') next() }) fastify.register(function (f, opts, next) { f.setNotFoundHandler(function (req, reply) { reply.code(404).send('this was not found 2') }) f.addHook('onRequest', function (req, res, next) { t.pass('onRequest 2 called') next() }) f.use(function (req, res, next) { t.pass('middleware 2 called') next() }) f.addHook('preHandler', function (request, reply, next) { t.pass('preHandler 2 called') next() }) f.addHook('onSend', function (request, reply, payload, next) { t.pass('onSend 2 called') next() }) f.addHook('onResponse', function (request, reply, next) { t.pass('onResponse 2 called') next() }) next() }, { prefix: '/test' }) t.tearDown(fastify.close.bind(fastify)) fastify.listen(0, err => { t.error(err) sget({ method: 'PUT', url: 'http://localhost:' + fastify.server.address().port + '/test', body: JSON.stringify({ hello: 'world' }), headers: { 'Content-Type': 'application/json' } }, (err, response, body) => { t.error(err) t.strictEqual(response.statusCode, 404) }) }) }) test('run middlewares on default 404', t => { t.plan(4) const fastify = Fastify() fastify.use(function (req, res, next) { t.pass('middleware called') next() }) fastify.get('/', function (req, reply) { reply.send({ hello: 'world' }) }) t.tearDown(fastify.close.bind(fastify)) fastify.listen(0, err => { t.error(err) sget({ method: 'PUT', url: 'http://localhost:' + fastify.server.address().port, body: JSON.stringify({ hello: 'world' }), headers: { 'Content-Type': 'application/json' } }, (err, response, body) => { t.error(err) t.strictEqual(response.statusCode, 404) }) }) }) test('run middlewares with encapsulated 404', t => { t.plan(5) const fastify = Fastify() fastify.use(function (req, res, next) { t.pass('middleware called') next() }) fastify.register(function (f, opts, next) { f.setNotFoundHandler(function (req, reply) { reply.code(404).send('this was not found 2') }) f.use(function (req, res, next) { t.pass('middleware 2 called') next() }) next() }, { prefix: '/test' }) t.tearDown(fastify.close.bind(fastify)) fastify.listen(0, err => { t.error(err) sget({ method: 'PUT', url: 'http://localhost:' + fastify.server.address().port + '/test', body: JSON.stringify({ hello: 'world' }), headers: { 'Content-Type': 'application/json' } }, (err, response, body) => { t.error(err) t.strictEqual(response.statusCode, 404) }) }) }) test('hooks check 404', t => { t.plan(13) const fastify = Fastify() fastify.get('/', function (req, reply) { reply.send({ hello: 'world' }) }) fastify.addHook('onSend', (req, reply, payload, next) => { t.deepEqual(req.query, { foo: 'asd' }) t.ok('called', 'onSend') next() }) fastify.addHook('onRequest', (req, res, next) => { t.ok('called', 'onRequest') next() }) fastify.addHook('onResponse', (request, reply, next) => { t.ok('called', 'onResponse') next() }) t.tearDown(fastify.close.bind(fastify)) fastify.listen(0, err => { t.error(err) sget({ method: 'PUT', url: 'http://localhost:' + fastify.server.address().port + '?foo=asd', body: JSON.stringify({ hello: 'world' }), headers: { 'Content-Type': 'application/json' } }, (err, response, body) => { t.error(err) t.strictEqual(response.statusCode, 404) }) sget({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port + '/notSupported?foo=asd' }, (err, response, body) => { t.error(err) t.strictEqual(response.statusCode, 404) }) }) }) test('setNotFoundHandler should not suppress duplicated routes checking', t => { t.plan(1) const fastify = Fastify() 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') }) fastify.listen(0, err => { t.ok(err) }) }) test('log debug for 404', t => { t.plan(1) const Writable = require('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.tearDown(fastify.close.bind(fastify)) t.test('log debug', t => { t.plan(7) fastify.inject({ method: 'GET', url: '/not-found' }, (err, response) => { t.error(err) t.strictEqual(response.statusCode, 404) const INFO_LEVEL = 30 t.strictEqual(JSON.parse(logStream.logs[0]).msg, 'incoming request') t.strictEqual(JSON.parse(logStream.logs[1]).msg, 'Route GET:/not-found not found') t.strictEqual(JSON.parse(logStream.logs[1]).level, INFO_LEVEL) t.strictEqual(JSON.parse(logStream.logs[2]).msg, 'request completed') t.strictEqual(logStream.logs.length, 3) }) }) }) test('Unknown method', t => { t.plan(5) const fastify = Fastify() fastify.get('/', function (req, reply) { reply.send({ hello: 'world' }) }) t.tearDown(fastify.close.bind(fastify)) fastify.listen(0, err => { t.error(err) const handler = () => {} // See https://github.com/fastify/light-my-request/pull/20 t.throws(() => fastify.inject({ method: 'UNKNWON_METHOD', url: '/' }, handler), Error) sget({ method: 'UNKNWON_METHOD', url: 'http://localhost:' + fastify.server.address().port }, (err, response, body) => { t.error(err) t.strictEqual(response.statusCode, 400) t.strictDeepEqual(JSON.parse(body), { error: 'Bad Request', message: 'Client Error', statusCode: 400 }) }) }) }) test('recognizes errors from the http-errors module', t => { t.plan(5) const fastify = Fastify() fastify.get('/', function (req, reply) { reply.send(httpErrors.NotFound()) }) t.tearDown(fastify.close.bind(fastify)) fastify.listen(0, err => { t.error(err) fastify.inject({ method: 'GET', url: '/' }, (err, res) => { t.error(err) t.strictEqual(res.statusCode, 404) sget('http://localhost:' + fastify.server.address().port, (err, response, body) => { t.error(err) const obj = JSON.parse(body.toString()) t.strictDeepEqual(obj, { error: 'Not Found', message: 'Not Found', statusCode: 404 }) }) }) }) }) test('the default 404 handler can be invoked inside a prefixed plugin', t => { t.plan(3) const fastify = Fastify() fastify.register(function (instance, opts, next) { instance.get('/path', function (request, reply) { reply.send(httpErrors.NotFound()) }) next() }, { prefix: '/v1' }) fastify.inject('/v1/path', (err, res) => { t.error(err) t.strictEqual(res.statusCode, 404) t.strictDeepEqual(JSON.parse(res.payload), { error: 'Not Found', message: 'Not Found', statusCode: 404 }) }) }) test('an inherited custom 404 handler can be invoked inside a prefixed plugin', t => { t.plan(3) const fastify = Fastify() fastify.setNotFoundHandler(function (request, reply) { reply.code(404).send('custom handler') }) fastify.register(function (instance, opts, next) { instance.get('/path', function (request, reply) { reply.send(httpErrors.NotFound()) }) next() }, { prefix: '/v1' }) fastify.inject('/v1/path', (err, res) => { t.error(err) t.strictEqual(res.statusCode, 404) t.deepEqual(JSON.parse(res.payload), { error: 'Not Found', message: 'Not Found', statusCode: 404 }) }) }) test('encapsulated custom 404 handler without a prefix is the handler for the entire 404 level', t => { t.plan(6) const fastify = Fastify() fastify.register(function (instance, opts, next) { instance.setNotFoundHandler(function (request, reply) { reply.code(404).send('custom handler') }) next() }) fastify.register(function (instance, opts, next) { instance.register(function (instance2, opts, next) { instance2.setNotFoundHandler(function (request, reply) { reply.code(404).send('custom handler 2') }) next() }) next() }, { prefix: 'prefixed' }) fastify.inject('/not-found', (err, res) => { t.error(err) t.strictEqual(res.statusCode, 404) t.strictEqual(res.payload, 'custom handler') }) fastify.inject('/prefixed/not-found', (err, res) => { t.error(err) t.strictEqual(res.statusCode, 404) t.strictEqual(res.payload, 'custom handler 2') }) }) test('cannot set notFoundHandler after binding', t => { t.plan(2) const fastify = Fastify() t.tearDown(fastify.close.bind(fastify)) fastify.listen(0, err => { t.error(err) try { fastify.setNotFoundHandler(() => { }) t.fail() } catch (e) { t.pass() } }) }) test('404 inside onSend', t => { t.plan(3) const fastify = Fastify() var called = false fastify.get('/', function (req, reply) { reply.send({ hello: 'world' }) }) fastify.addHook('onSend', function (request, reply, payload, next) { if (!called) { called = true next(new errors.NotFound()) } else { next() } }) t.tearDown(fastify.close.bind(fastify)) fastify.listen(0, err => { t.error(err) sget({ method: 'GET', url: 'http://localhost:' + fastify.server.address().port }, (err, response, body) => { t.error(err) t.strictEqual(response.statusCode, 404) }) }) }) test('Not found on supported method (should return a 404)', t => { t.plan(5) const fastify = Fastify() fastify.get('/', function (req, reply) { reply.send({ hello: 'world' }) }) t.tearDown(fastify.close.bind(fastify)) fastify.listen(0, err => { t.error(err) fastify.inject({ method: 'POST', url: '/' }, (err, res) => { t.error(err) t.strictEqual(res.statusCode, 404) sget({ method: 'POST', url: 'http://localhost:' + fastify.server.address().port }, (err, response, body) => { t.error(err) t.strictEqual(response.statusCode, 404) }) }) }) }) // Return 404 instead of 405 see https://github.com/fastify/fastify/pull/862 for discussion test('Not found on unsupported method (should return a 404)', t => { t.plan(5) const fastify = Fastify() fastify.all('/', function (req, reply) { reply.send({ hello: 'world' }) }) t.tearDown(fastify.close.bind(fastify)) fastify.listen(0, err => { t.error(err) fastify.inject({ method: 'PROPFIND', url: '/' }, (err, res) => { t.error(err) t.strictEqual(res.statusCode, 404) sget({ method: 'PROPFIND', url: 'http://localhost:' + fastify.server.address().port }, (err, response, body) => { t.error(err) t.strictEqual(response.statusCode, 404) }) }) }) }) // https://github.com/fastify/fastify/issues/868 test('onSend hooks run when an encapsulated route invokes the notFound handler', t => { t.plan(3) const fastify = Fastify() fastify.register((instance, options, done) => { instance.addHook('onSend', (request, reply, payload, next) => { t.pass('onSend hook called') next() }) instance.get('/', (request, reply) => { reply.send(new errors.NotFound()) }) done() }) fastify.inject('/', (err, res) => { t.error(err) t.strictEqual(res.statusCode, 404) }) }) // https://github.com/fastify/fastify/issues/713 test('preHandler option for setNotFoundHandler', t => { t.plan(10) t.test('preHandler option', t => { 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.error(err) var payload = JSON.parse(res.payload) t.deepEqual(payload, { preHandler: true, hello: 'world' }) }) }) // https://github.com/fastify/fastify/issues/2229 t.test('preHandler hook in setNotFoundHandler should be called when callNotFound', t => { 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.post('/', function (req, reply) { reply.callNotFound() }) fastify.inject({ method: 'POST', url: '/', payload: { hello: 'world' } }, (err, res) => { t.error(err) var payload = JSON.parse(res.payload) t.deepEqual(payload, { preHandler: true, hello: 'world' }) }) }) t.test('preHandler hook in setNotFoundHandler should accept an array of functions and be called when callNotFound', t => { 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.error(err) var payload = JSON.parse(res.payload) t.deepEqual(payload, { preHandler1: true, preHandler2: true, hello: 'world' }) }) }) t.test('preHandler option should be called after preHandler hook', t => { t.plan(2) const fastify = Fastify() fastify.addHook('preHandler', (req, reply, next) => { req.body.check = 'a' next() }) 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.error(err) var payload = JSON.parse(res.payload) t.deepEqual(payload, { check: 'ab', hello: 'world' }) }) }) t.test('preHandler option should be unique per prefix', t => { t.plan(4) 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' }) fastify.inject({ method: 'POST', url: '/not-found', payload: { hello: 'world' } }, (err, res) => { t.error(err) var payload = JSON.parse(res.payload) t.deepEqual(payload, { hello: 'earth' }) }) fastify.inject({ method: 'POST', url: '/no/not-found', payload: { hello: 'world' } }, (err, res) => { t.error(err) var payload = JSON.parse(res.payload) t.deepEqual(payload, { hello: 'world' }) }) }) t.test('preHandler option should handle errors', t => { 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.error(err) var payload = JSON.parse(res.payload) t.equal(res.statusCode, 500) t.deepEqual(payload, { message: 'kaboom', error: 'Internal Server Error', statusCode: 500 }) }) }) t.test('preHandler option should handle errors with custom status code', t => { 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.error(err) var payload = JSON.parse(res.payload) t.equal(res.statusCode, 401) t.deepEqual(payload, { message: 'go away', error: 'Unauthorized', statusCode: 401 }) }) }) t.test('preHandler option could accept an array of functions', t => { 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.error(err) var payload = JSON.parse(res.payload) t.deepEqual(payload, { preHandler: 'ab', hello: 'world' }) }) }) t.test('preHandler option does not interfere with preHandler', t => { t.plan(4) const fastify = Fastify() fastify.addHook('preHandler', (req, reply, next) => { req.body.check = 'a' next() }) 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' }) fastify.inject({ method: 'post', url: '/not-found', payload: { hello: 'world' } }, (err, res) => { t.error(err) var payload = JSON.parse(res.payload) t.deepEqual(payload, { check: 'ab', hello: 'world' }) }) fastify.inject({ method: 'post', url: '/no/not-found', payload: { hello: 'world' } }, (err, res) => { t.error(err) var payload = JSON.parse(res.payload) t.deepEqual(payload, { check: 'a', hello: 'world' }) }) }) t.test('preHandler option should keep the context', t => { t.plan(3) const fastify = Fastify() fastify.decorate('foo', 42) fastify.setNotFoundHandler({ preHandler: function (req, reply, done) { t.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.error(err) var payload = JSON.parse(res.payload) t.deepEqual(payload, { foo: 43, hello: 'world' }) }) }) }) test('reply.notFound invoked the notFound handler', t => { 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.error(err) t.strictEqual(res.statusCode, 404) t.deepEqual(JSON.parse(res.payload), { error: 'Not Found', message: 'kaboom', statusCode: 404 }) }) }) test('The custom error handler should be invoked after the custom not found handler', t => { t.plan(6) const fastify = Fastify() const order = [1, 2] fastify.setErrorHandler((err, req, reply) => { t.is(order.shift(), 2) t.type(err, Error) reply.send(err) }) fastify.setNotFoundHandler((req, reply) => { t.is(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.error(err) t.strictEqual(res.statusCode, 404) t.deepEqual(JSON.parse(res.payload), { error: 'Not Found', message: 'kaboom', statusCode: 404 }) }) }) test('If the custom not found handler does not use an Error, the custom error handler should not be called', t => { t.plan(3) const fastify = Fastify() fastify.setErrorHandler((_err, req, reply) => { t.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.error(err) t.strictEqual(res.statusCode, 404) t.strictEqual(res.payload, 'kaboom') }) }) test('preValidation option', t => { t.plan(3) const fastify = Fastify() fastify.decorate('foo', true) fastify.setNotFoundHandler({ preValidation: function (req, reply, done) { t.true(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.error(err) var payload = JSON.parse(res.payload) t.deepEqual(payload, { hello: 'world' }) }) }) t.test('preValidation option could accept an array of functions', t => { t.plan(4) const fastify = Fastify() fastify.setNotFoundHandler({ preValidation: [ (req, reply, done) => { t.ok('called') done() }, (req, reply, done) => { t.ok('called') done() } ] }, (req, reply) => { reply.send(req.body) }) fastify.inject({ method: 'POST', url: '/not-found', payload: { hello: 'world' } }, (err, res) => { t.error(err) var payload = JSON.parse(res.payload) t.deepEqual(payload, { hello: 'world' }) }) }) test('Should fail to invoke callNotFound inside a 404 handler', t => { t.plan(5) let fastify = null const logStream = split(JSON.parse) try { fastify = Fastify({ logger: { stream: logStream, level: 'warn' } }) } catch (e) { t.fail() } fastify.setNotFoundHandler((req, reply) => { reply.callNotFound() }) fastify.get('/', function (req, reply) { reply.callNotFound() }) logStream.once('data', line => { t.is(line.msg, 'Trying to send a NotFound error inside a 404 handler. Sending basic 404 response.') t.is(line.level, 40) }) fastify.inject({ url: '/', method: 'GET' }, (err, res) => { t.error(err) t.is(res.statusCode, 404) t.is(res.payload, '404 Not Found') }) }) test('400 in case of bad url (pre find-my-way v2.2.0 was a 404)', t => { t.test('Dyamic route', t => { t.plan(3) const fastify = Fastify() fastify.get('/hello/:id', () => t.fail('we should not be here')) fastify.inject({ url: '/hello/%world', method: 'GET' }, (err, response) => { t.error(err) t.strictEqual(response.statusCode, 400) t.deepEqual(JSON.parse(response.payload), { error: 'Bad Request', message: "'%world' is not a valid url component", statusCode: 400 }) }) }) t.test('Wildcard', t => { t.plan(3) const fastify = Fastify() fastify.get('*', () => t.fail('we should not be here')) fastify.inject({ url: '/hello/%world', method: 'GET' }, (err, response) => { t.error(err) t.strictEqual(response.statusCode, 400) t.deepEqual(JSON.parse(response.payload), { error: 'Bad Request', message: "'/hello/%world' is not a valid url component", statusCode: 400 }) }) }) t.end() })