You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					554 lines
				
				12 KiB
			
		
		
			
		
	
	
					554 lines
				
				12 KiB
			| 
											3 years ago
										 | 'use strict' | ||
|  | 
 | ||
|  | const { Readable } = require('stream') | ||
|  | const test = require('tap').test | ||
|  | const sget = require('simple-get').concat | ||
|  | const Fastify = require('../') | ||
|  | 
 | ||
|  | process.removeAllListeners('warning') | ||
|  | 
 | ||
|  | function endRouteHook (doneOrPayload, done) { | ||
|  |   if (typeof doneOrPayload === 'function') { | ||
|  |     doneOrPayload() | ||
|  |   } else { | ||
|  |     done() | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | function testExecutionHook (hook) { | ||
|  |   test(`${hook}`, t => { | ||
|  |     t.plan(3) | ||
|  |     const fastify = Fastify() | ||
|  | 
 | ||
|  |     fastify.post('/', { | ||
|  |       [hook]: (req, reply, doneOrPayload, done) => { | ||
|  |         t.pass('hook called') | ||
|  |         endRouteHook(doneOrPayload, done) | ||
|  |       } | ||
|  |     }, (req, reply) => { | ||
|  |       reply.send(req.body) | ||
|  |     }) | ||
|  | 
 | ||
|  |     fastify.inject({ | ||
|  |       method: 'POST', | ||
|  |       url: '/', | ||
|  |       payload: { hello: 'world' } | ||
|  |     }, (err, res) => { | ||
|  |       t.error(err) | ||
|  |       const payload = JSON.parse(res.payload) | ||
|  |       t.same(payload, { hello: 'world' }) | ||
|  |     }) | ||
|  |   }) | ||
|  | 
 | ||
|  |   test(`${hook} option should be called after ${hook} hook`, t => { | ||
|  |     t.plan(3) | ||
|  |     const fastify = Fastify() | ||
|  |     const checker = Object.defineProperty({ calledTimes: 0 }, 'check', { | ||
|  |       get: function () { return ++this.calledTimes } | ||
|  |     }) | ||
|  | 
 | ||
|  |     fastify.addHook(hook, (req, reply, doneOrPayload, done) => { | ||
|  |       t.equal(checker.check, 1) | ||
|  |       endRouteHook(doneOrPayload, done) | ||
|  |     }) | ||
|  | 
 | ||
|  |     fastify.post('/', { | ||
|  |       [hook]: (req, reply, doneOrPayload, done) => { | ||
|  |         t.equal(checker.check, 2) | ||
|  |         endRouteHook(doneOrPayload, done) | ||
|  |       } | ||
|  |     }, (req, reply) => { | ||
|  |       reply.send({}) | ||
|  |     }) | ||
|  | 
 | ||
|  |     fastify.inject({ | ||
|  |       method: 'POST', | ||
|  |       url: '/', | ||
|  |       payload: { hello: 'world' } | ||
|  |     }, (err, res) => { | ||
|  |       t.error(err) | ||
|  |     }) | ||
|  |   }) | ||
|  | 
 | ||
|  |   test(`${hook} option could accept an array of functions`, t => { | ||
|  |     t.plan(3) | ||
|  |     const fastify = Fastify() | ||
|  |     const checker = Object.defineProperty({ calledTimes: 0 }, 'check', { | ||
|  |       get: function () { return ++this.calledTimes } | ||
|  |     }) | ||
|  | 
 | ||
|  |     fastify.post('/', { | ||
|  |       [hook]: [ | ||
|  |         (req, reply, doneOrPayload, done) => { | ||
|  |           t.equal(checker.check, 1) | ||
|  |           endRouteHook(doneOrPayload, done) | ||
|  |         }, | ||
|  |         (req, reply, doneOrPayload, done) => { | ||
|  |           t.equal(checker.check, 2) | ||
|  |           endRouteHook(doneOrPayload, done) | ||
|  |         } | ||
|  |       ] | ||
|  |     }, (req, reply) => { | ||
|  |       reply.send({}) | ||
|  |     }) | ||
|  | 
 | ||
|  |     fastify.inject({ | ||
|  |       method: 'POST', | ||
|  |       url: '/', | ||
|  |       payload: { hello: 'world' } | ||
|  |     }, (err, res) => { | ||
|  |       t.error(err) | ||
|  |     }) | ||
|  |   }) | ||
|  | 
 | ||
|  |   test(`${hook} option does not interfere with ${hook} hook`, t => { | ||
|  |     t.plan(7) | ||
|  |     const fastify = Fastify() | ||
|  |     const checker = Object.defineProperty({ calledTimes: 0 }, 'check', { | ||
|  |       get: function () { return ++this.calledTimes } | ||
|  |     }) | ||
|  | 
 | ||
|  |     fastify.addHook(hook, (req, reply, doneOrPayload, done) => { | ||
|  |       t.equal(checker.check, 1) | ||
|  |       endRouteHook(doneOrPayload, done) | ||
|  |     }) | ||
|  | 
 | ||
|  |     fastify.post('/', { | ||
|  |       [hook]: (req, reply, doneOrPayload, done) => { | ||
|  |         t.equal(checker.check, 2) | ||
|  |         endRouteHook(doneOrPayload, done) | ||
|  |       } | ||
|  |     }, handler) | ||
|  | 
 | ||
|  |     fastify.post('/no', handler) | ||
|  | 
 | ||
|  |     function handler (req, reply) { | ||
|  |       reply.send({}) | ||
|  |     } | ||
|  | 
 | ||
|  |     fastify.inject({ | ||
|  |       method: 'post', | ||
|  |       url: '/' | ||
|  |     }, (err, res) => { | ||
|  |       t.error(err) | ||
|  |       t.equal(checker.calledTimes, 2) | ||
|  | 
 | ||
|  |       checker.calledTimes = 0 | ||
|  | 
 | ||
|  |       fastify.inject({ | ||
|  |         method: 'post', | ||
|  |         url: '/no' | ||
|  |       }, (err, res) => { | ||
|  |         t.error(err) | ||
|  |         t.equal(checker.calledTimes, 1) | ||
|  |       }) | ||
|  |     }) | ||
|  |   }) | ||
|  | } | ||
|  | 
 | ||
|  | function testBeforeHandlerHook (hook) { | ||
|  |   test(`${hook} option should be unique per route`, t => { | ||
|  |     t.plan(4) | ||
|  |     const fastify = Fastify() | ||
|  | 
 | ||
|  |     fastify.post('/', { | ||
|  |       [hook]: (req, reply, done) => { | ||
|  |         req.hello = 'earth' | ||
|  |         done() | ||
|  |       } | ||
|  |     }, (req, reply) => { | ||
|  |       reply.send({ hello: req.hello }) | ||
|  |     }) | ||
|  | 
 | ||
|  |     fastify.post('/no', (req, reply) => { | ||
|  |       reply.send(req.body) | ||
|  |     }) | ||
|  | 
 | ||
|  |     fastify.inject({ | ||
|  |       method: 'POST', | ||
|  |       url: '/', | ||
|  |       payload: { hello: 'world' } | ||
|  |     }, (err, res) => { | ||
|  |       t.error(err) | ||
|  |       const payload = JSON.parse(res.payload) | ||
|  |       t.same(payload, { hello: 'earth' }) | ||
|  |     }) | ||
|  | 
 | ||
|  |     fastify.inject({ | ||
|  |       method: 'POST', | ||
|  |       url: '/no', | ||
|  |       payload: { hello: 'world' } | ||
|  |     }, (err, res) => { | ||
|  |       t.error(err) | ||
|  |       const payload = JSON.parse(res.payload) | ||
|  |       t.same(payload, { hello: 'world' }) | ||
|  |     }) | ||
|  |   }) | ||
|  | 
 | ||
|  |   test(`${hook} option should handle errors`, t => { | ||
|  |     t.plan(3) | ||
|  |     const fastify = Fastify() | ||
|  | 
 | ||
|  |     fastify.post('/', { | ||
|  |       [hook]: (req, reply, done) => { | ||
|  |         done(new Error('kaboom')) | ||
|  |       } | ||
|  |     }, (req, reply) => { | ||
|  |       reply.send(req.body) | ||
|  |     }) | ||
|  | 
 | ||
|  |     fastify.inject({ | ||
|  |       method: 'POST', | ||
|  |       url: '/', | ||
|  |       payload: { hello: 'world' } | ||
|  |     }, (err, res) => { | ||
|  |       t.error(err) | ||
|  |       const payload = JSON.parse(res.payload) | ||
|  |       t.equal(res.statusCode, 500) | ||
|  |       t.same(payload, { | ||
|  |         message: 'kaboom', | ||
|  |         error: 'Internal Server Error', | ||
|  |         statusCode: 500 | ||
|  |       }) | ||
|  |     }) | ||
|  |   }) | ||
|  | 
 | ||
|  |   test(`${hook} option should handle throwing objects`, t => { | ||
|  |     t.plan(4) | ||
|  |     const fastify = Fastify() | ||
|  | 
 | ||
|  |     const myError = { myError: 'kaboom' } | ||
|  | 
 | ||
|  |     fastify.setErrorHandler(async (error, request, reply) => { | ||
|  |       t.same(error, myError, 'the error object throws by the user') | ||
|  |       reply.send({ this: 'is', my: 'error' }) | ||
|  |     }) | ||
|  | 
 | ||
|  |     fastify.get('/', { | ||
|  |       [hook]: async () => { | ||
|  |         // eslint-disable-next-line no-throw-literal
 | ||
|  |         throw myError | ||
|  |       } | ||
|  |     }, (req, reply) => { | ||
|  |       t.fail('the handler must not be called') | ||
|  |     }) | ||
|  | 
 | ||
|  |     fastify.inject({ | ||
|  |       url: '/', | ||
|  |       method: 'GET' | ||
|  |     }, (err, res) => { | ||
|  |       t.error(err) | ||
|  |       t.equal(res.statusCode, 500) | ||
|  |       t.same(res.json(), { this: 'is', my: 'error' }) | ||
|  |     }) | ||
|  |   }) | ||
|  | 
 | ||
|  |   test(`${hook} option should handle throwing objects by default`, t => { | ||
|  |     t.plan(3) | ||
|  |     const fastify = Fastify() | ||
|  | 
 | ||
|  |     fastify.get('/', { | ||
|  |       [hook]: async () => { | ||
|  |         // eslint-disable-next-line no-throw-literal
 | ||
|  |         throw { myError: 'kaboom', message: 'i am an error' } | ||
|  |       } | ||
|  |     }, (req, reply) => { | ||
|  |       t.fail('the handler must not be called') | ||
|  |     }) | ||
|  | 
 | ||
|  |     fastify.inject({ | ||
|  |       url: '/', | ||
|  |       method: 'GET' | ||
|  |     }, (err, res) => { | ||
|  |       t.error(err) | ||
|  |       t.equal(res.statusCode, 500) | ||
|  |       t.same(res.json(), { myError: 'kaboom', message: 'i am an error' }) | ||
|  |     }) | ||
|  |   }) | ||
|  | 
 | ||
|  |   test(`${hook} option should handle errors with custom status code`, t => { | ||
|  |     t.plan(3) | ||
|  |     const fastify = Fastify() | ||
|  | 
 | ||
|  |     fastify.post('/', { | ||
|  |       [hook]: (req, reply, done) => { | ||
|  |         reply.code(401) | ||
|  |         done(new Error('go away')) | ||
|  |       } | ||
|  |     }, (req, reply) => { | ||
|  |       reply.send(req.body) | ||
|  |     }) | ||
|  | 
 | ||
|  |     fastify.inject({ | ||
|  |       method: 'POST', | ||
|  |       url: '/', | ||
|  |       payload: { hello: 'world' } | ||
|  |     }, (err, res) => { | ||
|  |       t.error(err) | ||
|  |       const payload = JSON.parse(res.payload) | ||
|  |       t.equal(res.statusCode, 401) | ||
|  |       t.same(payload, { | ||
|  |         message: 'go away', | ||
|  |         error: 'Unauthorized', | ||
|  |         statusCode: 401 | ||
|  |       }) | ||
|  |     }) | ||
|  |   }) | ||
|  | 
 | ||
|  |   test(`${hook} option should keep the context`, t => { | ||
|  |     t.plan(3) | ||
|  |     const fastify = Fastify() | ||
|  | 
 | ||
|  |     fastify.decorate('foo', 42) | ||
|  | 
 | ||
|  |     fastify.post('/', { | ||
|  |       [hook]: function (req, reply, done) { | ||
|  |         t.equal(this.foo, 42) | ||
|  |         this.foo += 1 | ||
|  |         done() | ||
|  |       } | ||
|  |     }, function (req, reply) { | ||
|  |       reply.send({ foo: this.foo }) | ||
|  |     }) | ||
|  | 
 | ||
|  |     fastify.inject({ | ||
|  |       method: 'POST', | ||
|  |       url: '/', | ||
|  |       payload: { hello: 'world' } | ||
|  |     }, (err, res) => { | ||
|  |       t.error(err) | ||
|  |       const payload = JSON.parse(res.payload) | ||
|  |       t.same(payload, { foo: 43 }) | ||
|  |     }) | ||
|  |   }) | ||
|  | 
 | ||
|  |   test(`${hook} option should keep the context (array)`, t => { | ||
|  |     t.plan(3) | ||
|  |     const fastify = Fastify() | ||
|  | 
 | ||
|  |     fastify.decorate('foo', 42) | ||
|  | 
 | ||
|  |     fastify.post('/', { | ||
|  |       [hook]: [function (req, reply, done) { | ||
|  |         t.equal(this.foo, 42) | ||
|  |         this.foo += 1 | ||
|  |         done() | ||
|  |       }] | ||
|  |     }, function (req, reply) { | ||
|  |       reply.send({ foo: this.foo }) | ||
|  |     }) | ||
|  | 
 | ||
|  |     fastify.inject({ | ||
|  |       method: 'POST', | ||
|  |       url: '/', | ||
|  |       payload: { hello: 'world' } | ||
|  |     }, (err, res) => { | ||
|  |       t.error(err) | ||
|  |       const payload = JSON.parse(res.payload) | ||
|  |       t.same(payload, { foo: 43 }) | ||
|  |     }) | ||
|  |   }) | ||
|  | } | ||
|  | 
 | ||
|  | testExecutionHook('preHandler') | ||
|  | testExecutionHook('onSend') | ||
|  | testExecutionHook('onRequest') | ||
|  | testExecutionHook('onResponse') | ||
|  | testExecutionHook('preValidation') | ||
|  | testExecutionHook('preParsing') | ||
|  | // hooks that comes before the handler
 | ||
|  | testBeforeHandlerHook('preHandler') | ||
|  | testBeforeHandlerHook('onRequest') | ||
|  | testBeforeHandlerHook('preValidation') | ||
|  | testBeforeHandlerHook('preParsing') | ||
|  | 
 | ||
|  | test('preValidation option should be called before preHandler hook', t => { | ||
|  |   t.plan(3) | ||
|  |   const fastify = Fastify() | ||
|  | 
 | ||
|  |   fastify.addHook('preHandler', (req, reply, done) => { | ||
|  |     t.ok(req.called) | ||
|  |     done() | ||
|  |   }) | ||
|  | 
 | ||
|  |   fastify.post('/', { | ||
|  |     preValidation: (req, reply, done) => { | ||
|  |       req.called = true | ||
|  |       done() | ||
|  |     } | ||
|  |   }, (req, reply) => { | ||
|  |     reply.send(req.body) | ||
|  |   }) | ||
|  | 
 | ||
|  |   fastify.inject({ | ||
|  |     method: 'POST', | ||
|  |     url: '/', | ||
|  |     payload: { hello: 'world' } | ||
|  |   }, (err, res) => { | ||
|  |     t.error(err) | ||
|  |     const payload = JSON.parse(res.payload) | ||
|  |     t.same(payload, { hello: 'world' }) | ||
|  |   }) | ||
|  | }) | ||
|  | 
 | ||
|  | test('preSerialization option should be able to modify the payload', t => { | ||
|  |   t.plan(3) | ||
|  |   const fastify = Fastify() | ||
|  | 
 | ||
|  |   fastify.get('/only', { | ||
|  |     preSerialization: (req, reply, payload, done) => { | ||
|  |       done(null, { hello: 'another world' }) | ||
|  |     } | ||
|  |   }, (req, reply) => { | ||
|  |     reply.send({ hello: 'world' }) | ||
|  |   }) | ||
|  | 
 | ||
|  |   fastify.inject({ | ||
|  |     method: 'GET', | ||
|  |     url: '/only' | ||
|  |   }, (err, res) => { | ||
|  |     t.error(err) | ||
|  |     t.equal(res.statusCode, 200) | ||
|  |     t.same(JSON.parse(res.payload), { hello: 'another world' }) | ||
|  |   }) | ||
|  | }) | ||
|  | 
 | ||
|  | test('preParsing option should be called before preValidation hook', t => { | ||
|  |   t.plan(3) | ||
|  |   const fastify = Fastify() | ||
|  | 
 | ||
|  |   fastify.addHook('preValidation', (req, reply, done) => { | ||
|  |     t.ok(req.called) | ||
|  |     done() | ||
|  |   }) | ||
|  | 
 | ||
|  |   fastify.post('/', { | ||
|  |     preParsing: (req, reply, done) => { | ||
|  |       req.called = true | ||
|  |       done() | ||
|  |     } | ||
|  |   }, (req, reply) => { | ||
|  |     reply.send(req.body) | ||
|  |   }) | ||
|  | 
 | ||
|  |   fastify.inject({ | ||
|  |     method: 'POST', | ||
|  |     url: '/', | ||
|  |     payload: { hello: 'world' } | ||
|  |   }, (err, res) => { | ||
|  |     t.error(err) | ||
|  |     const payload = JSON.parse(res.payload) | ||
|  |     t.same(payload, { hello: 'world' }) | ||
|  |   }) | ||
|  | }) | ||
|  | 
 | ||
|  | test('preParsing option should be able to modify the payload', t => { | ||
|  |   t.plan(3) | ||
|  |   const fastify = Fastify() | ||
|  | 
 | ||
|  |   fastify.post('/only', { | ||
|  |     preParsing: (req, reply, payload, done) => { | ||
|  |       const stream = new Readable() | ||
|  |       stream.receivedEncodedLength = parseInt(req.headers['content-length'], 10) | ||
|  |       stream.push(JSON.stringify({ hello: 'another world' })) | ||
|  |       stream.push(null) | ||
|  |       done(null, stream) | ||
|  |     } | ||
|  |   }, (req, reply) => { | ||
|  |     reply.send(req.body) | ||
|  |   }) | ||
|  | 
 | ||
|  |   fastify.inject({ | ||
|  |     method: 'POST', | ||
|  |     url: '/only', | ||
|  |     payload: { hello: 'world' } | ||
|  |   }, (err, res) => { | ||
|  |     t.error(err) | ||
|  |     t.equal(res.statusCode, 200) | ||
|  |     t.same(JSON.parse(res.payload), { hello: 'another world' }) | ||
|  |   }) | ||
|  | }) | ||
|  | 
 | ||
|  | test('onRequest option should be called before preParsing', t => { | ||
|  |   t.plan(3) | ||
|  |   const fastify = Fastify() | ||
|  | 
 | ||
|  |   fastify.addHook('preParsing', (req, reply, done) => { | ||
|  |     t.ok(req.called) | ||
|  |     done() | ||
|  |   }) | ||
|  | 
 | ||
|  |   fastify.post('/', { | ||
|  |     onRequest: (req, reply, done) => { | ||
|  |       req.called = true | ||
|  |       done() | ||
|  |     } | ||
|  |   }, (req, reply) => { | ||
|  |     reply.send(req.body) | ||
|  |   }) | ||
|  | 
 | ||
|  |   fastify.inject({ | ||
|  |     method: 'POST', | ||
|  |     url: '/', | ||
|  |     payload: { hello: 'world' } | ||
|  |   }, (err, res) => { | ||
|  |     t.error(err) | ||
|  |     const payload = JSON.parse(res.payload) | ||
|  |     t.same(payload, { hello: 'world' }) | ||
|  |   }) | ||
|  | }) | ||
|  | 
 | ||
|  | test('onTimeout on route', t => { | ||
|  |   t.plan(4) | ||
|  |   const fastify = Fastify({ connectionTimeout: 500 }) | ||
|  | 
 | ||
|  |   fastify.get('/timeout', { | ||
|  |     async  handler (request, reply) { }, | ||
|  |     onTimeout (request, reply, done) { | ||
|  |       t.pass('onTimeout called') | ||
|  |       done() | ||
|  |     } | ||
|  |   }) | ||
|  | 
 | ||
|  |   fastify.listen(0, (err, address) => { | ||
|  |     t.error(err) | ||
|  |     t.teardown(() => fastify.close()) | ||
|  | 
 | ||
|  |     sget({ | ||
|  |       method: 'GET', | ||
|  |       url: `${address}/timeout` | ||
|  |     }, (err, response, body) => { | ||
|  |       t.type(err, Error) | ||
|  |       t.equal(err.message, 'socket hang up') | ||
|  |     }) | ||
|  |   }) | ||
|  | }) | ||
|  | 
 | ||
|  | test('onError on route', t => { | ||
|  |   t.plan(3) | ||
|  | 
 | ||
|  |   const fastify = Fastify() | ||
|  | 
 | ||
|  |   const err = new Error('kaboom') | ||
|  | 
 | ||
|  |   fastify.get('/', | ||
|  |     { | ||
|  |       onError (request, reply, error, done) { | ||
|  |         t.match(error, err) | ||
|  |         done() | ||
|  |       } | ||
|  |     }, | ||
|  |     (req, reply) => { | ||
|  |       reply.send(err) | ||
|  |     }) | ||
|  | 
 | ||
|  |   fastify.inject('/', (err, res) => { | ||
|  |     t.error(err) | ||
|  |     t.same(JSON.parse(res.payload), { | ||
|  |       error: 'Internal Server Error', | ||
|  |       message: 'kaboom', | ||
|  |       statusCode: 500 | ||
|  |     }) | ||
|  |   }) | ||
|  | }) |