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
			| 
								 
											3 years ago
										 
									 | 
							
								'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')
							 |