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.
		
		
		
		
		
			
		
			
				
					
					
						
							323 lines
						
					
					
						
							8.6 KiB
						
					
					
				
			
		
		
	
	
							323 lines
						
					
					
						
							8.6 KiB
						
					
					
				| 'use strict'
 | |
| 
 | |
| const { test } = require('tap')
 | |
| const split = require('split2')
 | |
| const net = require('net')
 | |
| const Fastify = require('../fastify')
 | |
| 
 | |
| process.removeAllListeners('warning')
 | |
| 
 | |
| const lifecycleHooks = [
 | |
|   'onRequest',
 | |
|   'preParsing',
 | |
|   'preValidation',
 | |
|   'preHandler',
 | |
|   'preSerialization',
 | |
|   'onSend',
 | |
|   'onTimeout',
 | |
|   'onResponse',
 | |
|   'onError'
 | |
| ]
 | |
| 
 | |
| test('skip automatic reply.send() with reply.sent = true and a body', (t) => {
 | |
|   const stream = split(JSON.parse)
 | |
|   const app = Fastify({
 | |
|     logger: {
 | |
|       stream
 | |
|     }
 | |
|   })
 | |
| 
 | |
|   stream.on('data', (line) => {
 | |
|     t.not(line.level, 40) // there are no errors
 | |
|     t.not(line.level, 50) // there are no errors
 | |
|   })
 | |
| 
 | |
|   app.get('/', (req, reply) => {
 | |
|     reply.sent = true
 | |
|     reply.raw.end('hello world')
 | |
| 
 | |
|     return Promise.resolve('this will be skipped')
 | |
|   })
 | |
| 
 | |
|   return app.inject({
 | |
|     method: 'GET',
 | |
|     url: '/'
 | |
|   }).then((res) => {
 | |
|     t.equal(res.statusCode, 200)
 | |
|     t.equal(res.body, 'hello world')
 | |
|   })
 | |
| })
 | |
| 
 | |
| test('skip automatic reply.send() with reply.sent = true and no body', (t) => {
 | |
|   const stream = split(JSON.parse)
 | |
|   const app = Fastify({
 | |
|     logger: {
 | |
|       stream
 | |
|     }
 | |
|   })
 | |
| 
 | |
|   stream.on('data', (line) => {
 | |
|     t.not(line.level, 40) // there are no error
 | |
|     t.not(line.level, 50) // there are no error
 | |
|   })
 | |
| 
 | |
|   app.get('/', (req, reply) => {
 | |
|     reply.sent = true
 | |
|     reply.raw.end('hello world')
 | |
| 
 | |
|     return Promise.resolve()
 | |
|   })
 | |
| 
 | |
|   return app.inject({
 | |
|     method: 'GET',
 | |
|     url: '/'
 | |
|   }).then((res) => {
 | |
|     t.equal(res.statusCode, 200)
 | |
|     t.equal(res.body, 'hello world')
 | |
|   })
 | |
| })
 | |
| 
 | |
| test('skip automatic reply.send() with reply.sent = true and an error', (t) => {
 | |
|   const stream = split(JSON.parse)
 | |
|   const app = Fastify({
 | |
|     logger: {
 | |
|       stream
 | |
|     }
 | |
|   })
 | |
| 
 | |
|   let errorSeen = false
 | |
| 
 | |
|   stream.on('data', (line) => {
 | |
|     if (line.level === 50) {
 | |
|       errorSeen = true
 | |
|       t.equal(line.err.message, 'kaboom')
 | |
|       t.equal(line.msg, 'Promise errored, but reply.sent = true was set')
 | |
|     }
 | |
|   })
 | |
| 
 | |
|   app.get('/', (req, reply) => {
 | |
|     reply.sent = true
 | |
|     reply.raw.end('hello world')
 | |
| 
 | |
|     return Promise.reject(new Error('kaboom'))
 | |
|   })
 | |
| 
 | |
|   return app.inject({
 | |
|     method: 'GET',
 | |
|     url: '/'
 | |
|   }).then((res) => {
 | |
|     t.equal(errorSeen, true)
 | |
|     t.equal(res.statusCode, 200)
 | |
|     t.equal(res.body, 'hello world')
 | |
|   })
 | |
| })
 | |
| 
 | |
| function testHandlerOrBeforeHandlerHook (test, hookOrHandler) {
 | |
|   const idx = hookOrHandler === 'handler' ? lifecycleHooks.indexOf('preHandler') : lifecycleHooks.indexOf(hookOrHandler)
 | |
|   const previousHooks = lifecycleHooks.slice(0, idx)
 | |
|   const nextHooks = lifecycleHooks.slice(idx + 1)
 | |
| 
 | |
|   test(`Hijacking inside ${hookOrHandler} skips all the following hooks and handler execution`, t => {
 | |
|     t.plan(4)
 | |
|     const test = t.test
 | |
| 
 | |
|     test('Sending a response using reply.raw => onResponse hook is called', t => {
 | |
|       const stream = split(JSON.parse)
 | |
|       const app = Fastify({
 | |
|         logger: {
 | |
|           stream
 | |
|         }
 | |
|       })
 | |
| 
 | |
|       stream.on('data', (line) => {
 | |
|         t.not(line.level, 40) // there are no errors
 | |
|         t.not(line.level, 50) // there are no errors
 | |
|       })
 | |
| 
 | |
|       previousHooks.forEach(h => app.addHook(h, async (req, reply) => t.pass(`${h} should be called`)))
 | |
| 
 | |
|       if (hookOrHandler === 'handler') {
 | |
|         app.get('/', (req, reply) => {
 | |
|           reply.hijack()
 | |
|           reply.raw.end(`hello from ${hookOrHandler}`)
 | |
|         })
 | |
|       } else {
 | |
|         app.addHook(hookOrHandler, async (req, reply) => {
 | |
|           reply.hijack()
 | |
|           reply.raw.end(`hello from ${hookOrHandler}`)
 | |
|         })
 | |
|         app.get('/', (req, reply) => t.fail('Handler should not be called'))
 | |
|       }
 | |
| 
 | |
|       nextHooks.forEach(h => {
 | |
|         if (h === 'onResponse') {
 | |
|           app.addHook(h, async (req, reply) => t.pass(`${h} should be called`))
 | |
|         } else {
 | |
|           app.addHook(h, async (req, reply) => t.fail(`${h} should not be called`))
 | |
|         }
 | |
|       })
 | |
| 
 | |
|       return app.inject({
 | |
|         method: 'GET',
 | |
|         url: '/'
 | |
|       }).then((res) => {
 | |
|         t.equal(res.statusCode, 200)
 | |
|         t.equal(res.body, `hello from ${hookOrHandler}`)
 | |
|       })
 | |
|     })
 | |
| 
 | |
|     test('Sending a response using req.socket => onResponse not called', t => {
 | |
|       const stream = split(JSON.parse)
 | |
|       const app = Fastify({
 | |
|         logger: {
 | |
|           stream
 | |
|         }
 | |
|       })
 | |
|       t.teardown(() => app.close())
 | |
| 
 | |
|       stream.on('data', (line) => {
 | |
|         t.not(line.level, 40) // there are no errors
 | |
|         t.not(line.level, 50) // there are no errors
 | |
|       })
 | |
| 
 | |
|       previousHooks.forEach(h => app.addHook(h, async (req, reply) => t.pass(`${h} should be called`)))
 | |
| 
 | |
|       if (hookOrHandler === 'handler') {
 | |
|         app.get('/', (req, reply) => {
 | |
|           reply.hijack()
 | |
|           req.socket.write('HTTP/1.1 200 OK\r\n\r\n')
 | |
|           req.socket.write(`hello from ${hookOrHandler}`)
 | |
|           req.socket.end()
 | |
|         })
 | |
|       } else {
 | |
|         app.addHook(hookOrHandler, async (req, reply) => {
 | |
|           reply.hijack()
 | |
|           req.socket.write('HTTP/1.1 200 OK\r\n\r\n')
 | |
|           req.socket.write(`hello from ${hookOrHandler}`)
 | |
|           req.socket.end()
 | |
|         })
 | |
|         app.get('/', (req, reply) => t.fail('Handler should not be called'))
 | |
|       }
 | |
| 
 | |
|       nextHooks.forEach(h => app.addHook(h, async (req, reply) => t.fail(`${h} should not be called`)))
 | |
| 
 | |
|       app.listen(0, err => {
 | |
|         t.error(err)
 | |
|         const client = net.createConnection({ port: (app.server.address()).port }, () => {
 | |
|           client.write('GET / HTTP/1.1\r\n\r\n')
 | |
| 
 | |
|           let chunks = ''
 | |
|           client.setEncoding('utf8')
 | |
|           client.on('data', data => {
 | |
|             chunks += data
 | |
|           })
 | |
| 
 | |
|           client.on('end', function () {
 | |
|             t.match(chunks, new RegExp(`hello from ${hookOrHandler}`, 'i'))
 | |
|             t.end()
 | |
|           })
 | |
|         })
 | |
|       })
 | |
|     })
 | |
| 
 | |
|     test('Throwing an error doesnt trigger any hooks', t => {
 | |
|       const stream = split(JSON.parse)
 | |
|       const app = Fastify({
 | |
|         logger: {
 | |
|           stream
 | |
|         }
 | |
|       })
 | |
|       t.teardown(() => app.close())
 | |
| 
 | |
|       let errorSeen = false
 | |
|       stream.on('data', (line) => {
 | |
|         if (hookOrHandler === 'handler') {
 | |
|           if (line.level === 40) {
 | |
|             errorSeen = true
 | |
|             t.equal(line.err.code, 'FST_ERR_REP_ALREADY_SENT')
 | |
|           }
 | |
|         } else {
 | |
|           t.not(line.level, 40) // there are no errors
 | |
|           t.not(line.level, 50) // there are no errors
 | |
|         }
 | |
|       })
 | |
| 
 | |
|       previousHooks.forEach(h => app.addHook(h, async (req, reply) => t.pass(`${h} should be called`)))
 | |
| 
 | |
|       if (hookOrHandler === 'handler') {
 | |
|         app.get('/', (req, reply) => {
 | |
|           reply.hijack()
 | |
|           throw new Error('This wil be skipped')
 | |
|         })
 | |
|       } else {
 | |
|         app.addHook(hookOrHandler, async (req, reply) => {
 | |
|           reply.hijack()
 | |
|           throw new Error('This wil be skipped')
 | |
|         })
 | |
|         app.get('/', (req, reply) => t.fail('Handler should not be called'))
 | |
|       }
 | |
| 
 | |
|       nextHooks.forEach(h => app.addHook(h, async (req, reply) => t.fail(`${h} should not be called`)))
 | |
| 
 | |
|       return Promise.race([
 | |
|         app.inject({ method: 'GET', url: '/' }),
 | |
|         new Promise((resolve, reject) => setTimeout(resolve, 1000))
 | |
|       ]).then((err, res) => {
 | |
|         t.error(err)
 | |
|         if (hookOrHandler === 'handler') {
 | |
|           t.equal(errorSeen, true)
 | |
|         }
 | |
|       })
 | |
|     })
 | |
| 
 | |
|     test('Calling reply.send() after hijacking logs a warning', t => {
 | |
|       const stream = split(JSON.parse)
 | |
|       const app = Fastify({
 | |
|         logger: {
 | |
|           stream
 | |
|         }
 | |
|       })
 | |
| 
 | |
|       let errorSeen = false
 | |
| 
 | |
|       stream.on('data', (line) => {
 | |
|         if (line.level === 40) {
 | |
|           errorSeen = true
 | |
|           t.equal(line.err.code, 'FST_ERR_REP_ALREADY_SENT')
 | |
|         }
 | |
|       })
 | |
| 
 | |
|       previousHooks.forEach(h => app.addHook(h, async (req, reply) => t.pass(`${h} should be called`)))
 | |
| 
 | |
|       if (hookOrHandler === 'handler') {
 | |
|         app.get('/', (req, reply) => {
 | |
|           reply.hijack()
 | |
|           reply.send('hello from reply.send()')
 | |
|         })
 | |
|       } else {
 | |
|         app.addHook(hookOrHandler, async (req, reply) => {
 | |
|           reply.hijack()
 | |
|           reply.send('hello from reply.send()')
 | |
|         })
 | |
|         app.get('/', (req, reply) => t.fail('Handler should not be called'))
 | |
|       }
 | |
| 
 | |
|       nextHooks.forEach(h => app.addHook(h, async (req, reply) => t.fail(`${h} should not be called`)))
 | |
| 
 | |
|       return Promise.race([
 | |
|         app.inject({ method: 'GET', url: '/' }),
 | |
|         new Promise((resolve, reject) => setTimeout(resolve, 1000))
 | |
|       ]).then((err, res) => {
 | |
|         t.error(err)
 | |
|         t.equal(errorSeen, true)
 | |
|       })
 | |
|     })
 | |
|   })
 | |
| }
 | |
| 
 | |
| testHandlerOrBeforeHandlerHook(test, 'onRequest')
 | |
| testHandlerOrBeforeHandlerHook(test, 'preParsing')
 | |
| testHandlerOrBeforeHandlerHook(test, 'preValidation')
 | |
| testHandlerOrBeforeHandlerHook(test, 'preHandler')
 | |
| testHandlerOrBeforeHandlerHook(test, 'handler')
 |