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.
		
		
		
		
		
			
		
			
				
					
					
						
							1283 lines
						
					
					
						
							28 KiB
						
					
					
				
			
		
		
	
	
							1283 lines
						
					
					
						
							28 KiB
						
					
					
				| 'use strict'
 | |
| 
 | |
| const stream = require('stream')
 | |
| const split = require('split2')
 | |
| const t = require('tap')
 | |
| const test = t.test
 | |
| const sget = require('simple-get').concat
 | |
| const joi = require('@hapi/joi')
 | |
| const Fastify = require('..')
 | |
| const proxyquire = require('proxyquire')
 | |
| const { FST_ERR_INVALID_URL } = require('../lib/errors')
 | |
| 
 | |
| test('route', t => {
 | |
|   t.plan(9)
 | |
|   const test = t.test
 | |
|   const fastify = Fastify()
 | |
| 
 | |
|   test('route - get', t => {
 | |
|     t.plan(1)
 | |
|     try {
 | |
|       fastify.route({
 | |
|         method: 'GET',
 | |
|         url: '/',
 | |
|         schema: {
 | |
|           response: {
 | |
|             '2xx': {
 | |
|               type: 'object',
 | |
|               properties: {
 | |
|                 hello: {
 | |
|                   type: 'string'
 | |
|                 }
 | |
|               }
 | |
|             }
 | |
|           }
 | |
|         },
 | |
|         handler: function (req, reply) {
 | |
|           reply.send({ hello: 'world' })
 | |
|         }
 | |
|       })
 | |
|       t.pass()
 | |
|     } catch (e) {
 | |
|       t.fail()
 | |
|     }
 | |
|   })
 | |
| 
 | |
|   test('missing schema - route', t => {
 | |
|     t.plan(1)
 | |
|     try {
 | |
|       fastify.route({
 | |
|         method: 'GET',
 | |
|         url: '/missing',
 | |
|         handler: function (req, reply) {
 | |
|           reply.send({ hello: 'world' })
 | |
|         }
 | |
|       })
 | |
|       t.pass()
 | |
|     } catch (e) {
 | |
|       t.fail()
 | |
|     }
 | |
|   })
 | |
| 
 | |
|   test('invalid handler attribute - route', t => {
 | |
|     t.plan(1)
 | |
|     try {
 | |
|       fastify.get('/', { handler: 'not a function' }, () => {})
 | |
|       t.fail()
 | |
|     } catch (e) {
 | |
|       t.pass()
 | |
|     }
 | |
|   })
 | |
| 
 | |
|   test('Multiple methods', t => {
 | |
|     t.plan(1)
 | |
|     try {
 | |
|       fastify.route({
 | |
|         method: ['GET', 'DELETE'],
 | |
|         url: '/multiple',
 | |
|         handler: function (req, reply) {
 | |
|           reply.send({ hello: 'world' })
 | |
|         }
 | |
|       })
 | |
|       t.pass()
 | |
|     } catch (e) {
 | |
|       t.fail()
 | |
|     }
 | |
|   })
 | |
| 
 | |
|   test('Add multiple methods', t => {
 | |
|     t.plan(1)
 | |
|     try {
 | |
|       fastify.get('/add-multiple', function (req, reply) {
 | |
|         reply.send({ hello: 'Bob!' })
 | |
|       })
 | |
|       fastify.route({
 | |
|         method: ['PUT', 'DELETE'],
 | |
|         url: '/add-multiple',
 | |
|         handler: function (req, reply) {
 | |
|           reply.send({ hello: 'world' })
 | |
|         }
 | |
|       })
 | |
|       t.pass()
 | |
|     } catch (e) {
 | |
|       t.fail()
 | |
|     }
 | |
|   })
 | |
| 
 | |
|   fastify.listen(0, function (err) {
 | |
|     if (err) t.error(err)
 | |
|     fastify.server.unref()
 | |
| 
 | |
|     test('cannot add another route after binding', t => {
 | |
|       t.plan(1)
 | |
|       try {
 | |
|         fastify.route({
 | |
|           method: 'GET',
 | |
|           url: '/another-get-route',
 | |
|           handler: function (req, reply) {
 | |
|             reply.send({ hello: 'world' })
 | |
|           }
 | |
|         })
 | |
|         t.fail()
 | |
|       } catch (e) {
 | |
|         t.pass()
 | |
|       }
 | |
|     })
 | |
| 
 | |
|     test('route - get', t => {
 | |
|       t.plan(3)
 | |
|       sget({
 | |
|         method: 'GET',
 | |
|         url: 'http://localhost:' + fastify.server.address().port
 | |
|       }, (err, response, body) => {
 | |
|         t.error(err)
 | |
|         t.equal(response.statusCode, 200)
 | |
|         t.same(JSON.parse(body), { hello: 'world' })
 | |
|       })
 | |
|     })
 | |
| 
 | |
|     test('route - missing schema', t => {
 | |
|       t.plan(3)
 | |
|       sget({
 | |
|         method: 'GET',
 | |
|         url: 'http://localhost:' + fastify.server.address().port + '/missing'
 | |
|       }, (err, response, body) => {
 | |
|         t.error(err)
 | |
|         t.equal(response.statusCode, 200)
 | |
|         t.same(JSON.parse(body), { hello: 'world' })
 | |
|       })
 | |
|     })
 | |
| 
 | |
|     test('route - multiple methods', t => {
 | |
|       t.plan(6)
 | |
|       sget({
 | |
|         method: 'GET',
 | |
|         url: 'http://localhost:' + fastify.server.address().port + '/multiple'
 | |
|       }, (err, response, body) => {
 | |
|         t.error(err)
 | |
|         t.equal(response.statusCode, 200)
 | |
|         t.same(JSON.parse(body), { hello: 'world' })
 | |
|       })
 | |
| 
 | |
|       sget({
 | |
|         method: 'DELETE',
 | |
|         url: 'http://localhost:' + fastify.server.address().port + '/multiple'
 | |
|       }, (err, response, body) => {
 | |
|         t.error(err)
 | |
|         t.equal(response.statusCode, 200)
 | |
|         t.same(JSON.parse(body), { hello: 'world' })
 | |
|       })
 | |
|     })
 | |
|   })
 | |
| })
 | |
| 
 | |
| test('invalid schema - route', t => {
 | |
|   t.plan(3)
 | |
| 
 | |
|   const fastify = Fastify()
 | |
|   fastify.route({
 | |
|     handler: () => {},
 | |
|     method: 'GET',
 | |
|     url: '/invalid',
 | |
|     schema: {
 | |
|       querystring: {
 | |
|         id: 'string'
 | |
|       }
 | |
|     }
 | |
|   })
 | |
|   fastify.after(err => {
 | |
|     t.notOk(err, 'the error is throw on preReady')
 | |
|   })
 | |
|   fastify.ready(err => {
 | |
|     t.equal(err.code, 'FST_ERR_SCH_VALIDATION_BUILD')
 | |
|     t.match(err.message, /Failed building the validation schema for GET: \/invalid/)
 | |
|   })
 | |
| })
 | |
| 
 | |
| test('same route definition object on multiple prefixes', async t => {
 | |
|   t.plan(2)
 | |
| 
 | |
|   const routeObject = {
 | |
|     handler: () => {},
 | |
|     method: 'GET',
 | |
|     url: '/simple'
 | |
|   }
 | |
| 
 | |
|   const fastify = Fastify()
 | |
| 
 | |
|   fastify.register(async function (f) {
 | |
|     f.addHook('onRoute', (routeOptions) => {
 | |
|       t.equal(routeOptions.url, '/v1/simple')
 | |
|     })
 | |
|     f.route(routeObject)
 | |
|   }, { prefix: '/v1' })
 | |
|   fastify.register(async function (f) {
 | |
|     f.addHook('onRoute', (routeOptions) => {
 | |
|       t.equal(routeOptions.url, '/v2/simple')
 | |
|     })
 | |
|     f.route(routeObject)
 | |
|   }, { prefix: '/v2' })
 | |
| 
 | |
|   await fastify.ready()
 | |
| })
 | |
| 
 | |
| test('path can be specified in place of uri', t => {
 | |
|   t.plan(3)
 | |
|   const fastify = Fastify()
 | |
| 
 | |
|   fastify.route({
 | |
|     method: 'GET',
 | |
|     path: '/path',
 | |
|     handler: function (req, reply) {
 | |
|       reply.send({ hello: 'world' })
 | |
|     }
 | |
|   })
 | |
| 
 | |
|   const reqOpts = {
 | |
|     method: 'GET',
 | |
|     url: '/path'
 | |
|   }
 | |
| 
 | |
|   fastify.inject(reqOpts, (err, res) => {
 | |
|     t.error(err)
 | |
|     t.equal(res.statusCode, 200)
 | |
|     t.same(JSON.parse(res.payload), { hello: 'world' })
 | |
|   })
 | |
| })
 | |
| 
 | |
| test('invalid bodyLimit option - route', t => {
 | |
|   t.plan(2)
 | |
|   const fastify = Fastify()
 | |
| 
 | |
|   try {
 | |
|     fastify.route({
 | |
|       bodyLimit: false,
 | |
|       method: 'PUT',
 | |
|       handler: () => null
 | |
|     })
 | |
|     t.fail('bodyLimit must be an integer')
 | |
|   } catch (err) {
 | |
|     t.equal(err.message, "'bodyLimit' option must be an integer > 0. Got 'false'")
 | |
|   }
 | |
| 
 | |
|   try {
 | |
|     fastify.post('/url', { bodyLimit: 10000.1 }, () => null)
 | |
|     t.fail('bodyLimit must be an integer')
 | |
|   } catch (err) {
 | |
|     t.equal(err.message, "'bodyLimit' option must be an integer > 0. Got '10000.1'")
 | |
|   }
 | |
| })
 | |
| 
 | |
| test('handler function in options of shorthand route should works correctly', t => {
 | |
|   t.plan(3)
 | |
| 
 | |
|   const fastify = Fastify()
 | |
|   fastify.get('/foo', {
 | |
|     handler: (req, reply) => {
 | |
|       reply.send({ hello: 'world' })
 | |
|     }
 | |
|   })
 | |
| 
 | |
|   fastify.inject({
 | |
|     method: 'GET',
 | |
|     url: '/foo'
 | |
|   }, (err, res) => {
 | |
|     t.error(err)
 | |
|     t.equal(res.statusCode, 200)
 | |
|     t.same(JSON.parse(res.payload), { hello: 'world' })
 | |
|   })
 | |
| })
 | |
| 
 | |
| test('does not mutate joi schemas', t => {
 | |
|   t.plan(4)
 | |
| 
 | |
|   const fastify = Fastify()
 | |
|   function validatorCompiler ({ schema, method, url, httpPart }) {
 | |
|     // Needed to extract the params part,
 | |
|     // without the JSON-schema encapsulation
 | |
|     // that is automatically added by the short
 | |
|     // form of params.
 | |
|     schema = joi.object(schema.properties)
 | |
| 
 | |
|     return validateHttpData
 | |
| 
 | |
|     function validateHttpData (data) {
 | |
|       return schema.validate(data)
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   fastify.setValidatorCompiler(validatorCompiler)
 | |
| 
 | |
|   fastify.route({
 | |
|     path: '/foo/:an_id',
 | |
|     method: 'GET',
 | |
|     schema: {
 | |
|       params: { an_id: joi.number() }
 | |
|     },
 | |
|     handler (req, res) {
 | |
|       t.same(req.params, { an_id: 42 })
 | |
|       res.send({ hello: 'world' })
 | |
|     }
 | |
|   })
 | |
| 
 | |
|   fastify.inject({
 | |
|     method: 'GET',
 | |
|     url: '/foo/42'
 | |
|   }, (err, result) => {
 | |
|     t.error(err)
 | |
|     t.equal(result.statusCode, 200)
 | |
|     t.same(JSON.parse(result.payload), { hello: 'world' })
 | |
|   })
 | |
| })
 | |
| 
 | |
| test('multiple routes with one schema', t => {
 | |
|   t.plan(2)
 | |
| 
 | |
|   const fastify = Fastify()
 | |
| 
 | |
|   const schema = {
 | |
|     query: {
 | |
|       id: { type: 'number' }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   fastify.route({
 | |
|     schema,
 | |
|     method: 'GET',
 | |
|     path: '/first/:id',
 | |
|     handler (req, res) {
 | |
|       res.send({ hello: 'world' })
 | |
|     }
 | |
|   })
 | |
| 
 | |
|   fastify.route({
 | |
|     schema,
 | |
|     method: 'GET',
 | |
|     path: '/second/:id',
 | |
|     handler (req, res) {
 | |
|       res.send({ hello: 'world' })
 | |
|     }
 | |
|   })
 | |
| 
 | |
|   fastify.ready(error => {
 | |
|     t.error(error)
 | |
|     t.same(schema, schema)
 | |
|   })
 | |
| })
 | |
| 
 | |
| test('route error handler overrides default error handler', t => {
 | |
|   t.plan(4)
 | |
| 
 | |
|   const fastify = Fastify()
 | |
| 
 | |
|   const customRouteErrorHandler = (error, request, reply) => {
 | |
|     t.equal(error.message, 'Wrong Pot Error')
 | |
| 
 | |
|     reply.code(418).send({
 | |
|       message: 'Make a brew',
 | |
|       statusCode: 418,
 | |
|       error: 'Wrong Pot Error'
 | |
|     })
 | |
|   }
 | |
| 
 | |
|   fastify.route({
 | |
|     method: 'GET',
 | |
|     path: '/coffee',
 | |
|     handler: (req, res) => {
 | |
|       res.send(new Error('Wrong Pot Error'))
 | |
|     },
 | |
|     errorHandler: customRouteErrorHandler
 | |
|   })
 | |
| 
 | |
|   fastify.inject({
 | |
|     method: 'GET',
 | |
|     url: '/coffee'
 | |
|   }, (error, res) => {
 | |
|     t.error(error)
 | |
|     t.equal(res.statusCode, 418)
 | |
|     t.same(JSON.parse(res.payload), {
 | |
|       message: 'Make a brew',
 | |
|       statusCode: 418,
 | |
|       error: 'Wrong Pot Error'
 | |
|     })
 | |
|   })
 | |
| })
 | |
| 
 | |
| test('route error handler does not affect other routes', t => {
 | |
|   t.plan(3)
 | |
| 
 | |
|   const fastify = Fastify()
 | |
| 
 | |
|   const customRouteErrorHandler = (error, request, reply) => {
 | |
|     t.equal(error.message, 'Wrong Pot Error')
 | |
| 
 | |
|     reply.code(418).send({
 | |
|       message: 'Make a brew',
 | |
|       statusCode: 418,
 | |
|       error: 'Wrong Pot Error'
 | |
|     })
 | |
|   }
 | |
| 
 | |
|   fastify.route({
 | |
|     method: 'GET',
 | |
|     path: '/coffee',
 | |
|     handler: (req, res) => {
 | |
|       res.send(new Error('Wrong Pot Error'))
 | |
|     },
 | |
|     errorHandler: customRouteErrorHandler
 | |
|   })
 | |
| 
 | |
|   fastify.route({
 | |
|     method: 'GET',
 | |
|     path: '/tea',
 | |
|     handler: (req, res) => {
 | |
|       res.send(new Error('No tea today'))
 | |
|     }
 | |
|   })
 | |
| 
 | |
|   fastify.inject({
 | |
|     method: 'GET',
 | |
|     url: '/tea'
 | |
|   }, (error, res) => {
 | |
|     t.error(error)
 | |
|     t.equal(res.statusCode, 500)
 | |
|     t.same(JSON.parse(res.payload), {
 | |
|       message: 'No tea today',
 | |
|       statusCode: 500,
 | |
|       error: 'Internal Server Error'
 | |
|     })
 | |
|   })
 | |
| })
 | |
| 
 | |
| test('async error handler for a route', t => {
 | |
|   t.plan(4)
 | |
| 
 | |
|   const fastify = Fastify()
 | |
| 
 | |
|   const customRouteErrorHandler = async (error, request, reply) => {
 | |
|     t.equal(error.message, 'Delayed Pot Error')
 | |
|     reply.code(418)
 | |
|     return {
 | |
|       message: 'Make a brew sometime later',
 | |
|       statusCode: 418,
 | |
|       error: 'Delayed Pot Error'
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   fastify.route({
 | |
|     method: 'GET',
 | |
|     path: '/late-coffee',
 | |
|     handler: (req, res) => {
 | |
|       res.send(new Error('Delayed Pot Error'))
 | |
|     },
 | |
|     errorHandler: customRouteErrorHandler
 | |
|   })
 | |
| 
 | |
|   fastify.inject({
 | |
|     method: 'GET',
 | |
|     url: '/late-coffee'
 | |
|   }, (error, res) => {
 | |
|     t.error(error)
 | |
|     t.equal(res.statusCode, 418)
 | |
|     t.same(JSON.parse(res.payload), {
 | |
|       message: 'Make a brew sometime later',
 | |
|       statusCode: 418,
 | |
|       error: 'Delayed Pot Error'
 | |
|     })
 | |
|   })
 | |
| })
 | |
| 
 | |
| test('route error handler overrides global custom error handler', t => {
 | |
|   t.plan(4)
 | |
| 
 | |
|   const fastify = Fastify()
 | |
| 
 | |
|   const customGlobalErrorHandler = (error, request, reply) => {
 | |
|     t.error(error)
 | |
|     reply.code(429).send({ message: 'Too much coffee' })
 | |
|   }
 | |
| 
 | |
|   const customRouteErrorHandler = (error, request, reply) => {
 | |
|     t.equal(error.message, 'Wrong Pot Error')
 | |
|     reply.code(418).send({
 | |
|       message: 'Make a brew',
 | |
|       statusCode: 418,
 | |
|       error: 'Wrong Pot Error'
 | |
|     })
 | |
|   }
 | |
| 
 | |
|   fastify.setErrorHandler(customGlobalErrorHandler)
 | |
| 
 | |
|   fastify.route({
 | |
|     method: 'GET',
 | |
|     path: '/more-coffee',
 | |
|     handler: (req, res) => {
 | |
|       res.send(new Error('Wrong Pot Error'))
 | |
|     },
 | |
|     errorHandler: customRouteErrorHandler
 | |
|   })
 | |
| 
 | |
|   fastify.inject({
 | |
|     method: 'GET',
 | |
|     url: '/more-coffee'
 | |
|   }, (error, res) => {
 | |
|     t.error(error)
 | |
|     t.equal(res.statusCode, 418)
 | |
|     t.same(JSON.parse(res.payload), {
 | |
|       message: 'Make a brew',
 | |
|       statusCode: 418,
 | |
|       error: 'Wrong Pot Error'
 | |
|     })
 | |
|   })
 | |
| })
 | |
| 
 | |
| test('throws when route with empty url', async t => {
 | |
|   t.plan(1)
 | |
| 
 | |
|   const fastify = Fastify()
 | |
|   try {
 | |
|     await fastify.route({
 | |
|       method: 'GET',
 | |
|       url: '',
 | |
|       handler: (req, res) => {
 | |
|         res.send('hi!')
 | |
|       }
 | |
|     }).ready()
 | |
|   } catch (err) {
 | |
|     t.equal(err.message, 'The first character of a path should be `/` or `*`')
 | |
|   }
 | |
| })
 | |
| 
 | |
| test('throws when route with empty url in shorthand declaration', async t => {
 | |
|   t.plan(1)
 | |
| 
 | |
|   const fastify = Fastify()
 | |
|   try {
 | |
|     await fastify.get(
 | |
|       '',
 | |
|       async function handler () { return {} }
 | |
|     ).ready()
 | |
|   } catch (err) {
 | |
|     t.equal(err.message, 'The path could not be empty')
 | |
|   }
 | |
| })
 | |
| 
 | |
| test('throws when route-level error handler is not a function', t => {
 | |
|   t.plan(1)
 | |
| 
 | |
|   const fastify = Fastify()
 | |
| 
 | |
|   try {
 | |
|     fastify.route({
 | |
|       method: 'GET',
 | |
|       url: '/tea',
 | |
|       handler: (req, res) => {
 | |
|         res.send('hi!')
 | |
|       },
 | |
|       errorHandler: 'teapot'
 | |
|     })
 | |
|   } catch (err) {
 | |
|     t.equal(err.message, 'Error Handler for GET:/tea route, if defined, must be a function')
 | |
|   }
 | |
| })
 | |
| 
 | |
| test('Creates a HEAD route for each GET one', t => {
 | |
|   t.plan(8)
 | |
| 
 | |
|   const fastify = Fastify({ exposeHeadRoutes: true })
 | |
| 
 | |
|   fastify.route({
 | |
|     method: 'GET',
 | |
|     path: '/more-coffee',
 | |
|     handler: (req, reply) => {
 | |
|       reply.send({ here: 'is coffee' })
 | |
|     }
 | |
|   })
 | |
| 
 | |
|   fastify.route({
 | |
|     method: 'GET',
 | |
|     path: '/some-light',
 | |
|     handler: (req, reply) => {
 | |
|       reply.send('Get some light!')
 | |
|     }
 | |
|   })
 | |
| 
 | |
|   fastify.inject({
 | |
|     method: 'HEAD',
 | |
|     url: '/more-coffee'
 | |
|   }, (error, res) => {
 | |
|     t.error(error)
 | |
|     t.equal(res.statusCode, 200)
 | |
|     t.equal(res.headers['content-type'], 'application/json; charset=utf-8')
 | |
|     t.same(res.body, '')
 | |
|   })
 | |
| 
 | |
|   fastify.inject({
 | |
|     method: 'HEAD',
 | |
|     url: '/some-light'
 | |
|   }, (error, res) => {
 | |
|     t.error(error)
 | |
|     t.equal(res.statusCode, 200)
 | |
|     t.equal(res.headers['content-type'], 'text/plain; charset=utf-8')
 | |
|     t.equal(res.body, '')
 | |
|   })
 | |
| })
 | |
| 
 | |
| test('Creates a HEAD route for a GET one with prefixTrailingSlash', async (t) => {
 | |
|   t.plan(1)
 | |
| 
 | |
|   const fastify = Fastify()
 | |
| 
 | |
|   const arr = []
 | |
|   fastify.register((instance, opts, next) => {
 | |
|     instance.addHook('onRoute', (routeOptions) => {
 | |
|       arr.push(`${routeOptions.method} ${routeOptions.url}`)
 | |
|     })
 | |
| 
 | |
|     instance.route({
 | |
|       method: 'GET',
 | |
|       path: '/',
 | |
|       exposeHeadRoute: true,
 | |
|       prefixTrailingSlash: 'both',
 | |
|       handler: (req, reply) => {
 | |
|         reply.send({ here: 'is coffee' })
 | |
|       }
 | |
|     })
 | |
| 
 | |
|     next()
 | |
|   }, { prefix: '/v1' })
 | |
| 
 | |
|   await fastify.ready()
 | |
| 
 | |
|   t.ok(true)
 | |
| })
 | |
| 
 | |
| test('Will not create a HEAD route that is not GET', t => {
 | |
|   t.plan(11)
 | |
| 
 | |
|   const fastify = Fastify({ exposeHeadRoutes: true })
 | |
| 
 | |
|   fastify.route({
 | |
|     method: 'GET',
 | |
|     path: '/more-coffee',
 | |
|     handler: (req, reply) => {
 | |
|       reply.send({ here: 'is coffee' })
 | |
|     }
 | |
|   })
 | |
| 
 | |
|   fastify.route({
 | |
|     method: 'GET',
 | |
|     path: '/some-light',
 | |
|     handler: (req, reply) => {
 | |
|       reply.send()
 | |
|     }
 | |
|   })
 | |
| 
 | |
|   fastify.route({
 | |
|     method: 'POST',
 | |
|     path: '/something',
 | |
|     handler: (req, reply) => {
 | |
|       reply.send({ look: 'It is something!' })
 | |
|     }
 | |
|   })
 | |
| 
 | |
|   fastify.inject({
 | |
|     method: 'HEAD',
 | |
|     url: '/more-coffee'
 | |
|   }, (error, res) => {
 | |
|     t.error(error)
 | |
|     t.equal(res.statusCode, 200)
 | |
|     t.equal(res.headers['content-type'], 'application/json; charset=utf-8')
 | |
|     t.same(res.body, '')
 | |
|   })
 | |
| 
 | |
|   fastify.inject({
 | |
|     method: 'HEAD',
 | |
|     url: '/some-light'
 | |
|   }, (error, res) => {
 | |
|     t.error(error)
 | |
|     t.equal(res.statusCode, 200)
 | |
|     t.equal(res.headers['content-type'], undefined)
 | |
|     t.equal(res.headers['content-length'], '0')
 | |
|     t.equal(res.body, '')
 | |
|   })
 | |
| 
 | |
|   fastify.inject({
 | |
|     method: 'HEAD',
 | |
|     url: '/something'
 | |
|   }, (error, res) => {
 | |
|     t.error(error)
 | |
|     t.equal(res.statusCode, 404)
 | |
|   })
 | |
| })
 | |
| 
 | |
| test('HEAD route should handle properly each response type', t => {
 | |
|   t.plan(25)
 | |
| 
 | |
|   const fastify = Fastify({ exposeHeadRoutes: true })
 | |
|   const resString = 'Found me!'
 | |
|   const resJSON = { here: 'is Johnny' }
 | |
|   const resBuffer = Buffer.from('I am a buffer!')
 | |
|   const resStream = stream.Readable.from('I am a stream!')
 | |
| 
 | |
|   fastify.route({
 | |
|     method: 'GET',
 | |
|     path: '/json',
 | |
|     handler: (req, reply) => {
 | |
|       reply.send(resJSON)
 | |
|     }
 | |
|   })
 | |
| 
 | |
|   fastify.route({
 | |
|     method: 'GET',
 | |
|     path: '/string',
 | |
|     handler: (req, reply) => {
 | |
|       reply.send(resString)
 | |
|     }
 | |
|   })
 | |
| 
 | |
|   fastify.route({
 | |
|     method: 'GET',
 | |
|     path: '/buffer',
 | |
|     handler: (req, reply) => {
 | |
|       reply.send(resBuffer)
 | |
|     }
 | |
|   })
 | |
| 
 | |
|   fastify.route({
 | |
|     method: 'GET',
 | |
|     path: '/buffer-with-content-type',
 | |
|     handler: (req, reply) => {
 | |
|       reply.headers({ 'content-type': 'image/jpeg' })
 | |
|       reply.send(resBuffer)
 | |
|     }
 | |
|   })
 | |
| 
 | |
|   fastify.route({
 | |
|     method: 'GET',
 | |
|     path: '/stream',
 | |
|     handler: (req, reply) => {
 | |
|       return resStream
 | |
|     }
 | |
|   })
 | |
| 
 | |
|   fastify.inject({
 | |
|     method: 'HEAD',
 | |
|     url: '/json'
 | |
|   }, (error, res) => {
 | |
|     t.error(error)
 | |
|     t.equal(res.statusCode, 200)
 | |
|     t.equal(res.headers['content-type'], 'application/json; charset=utf-8')
 | |
|     t.equal(res.headers['content-length'], `${Buffer.byteLength(JSON.stringify(resJSON))}`)
 | |
|     t.same(res.body, '')
 | |
|   })
 | |
| 
 | |
|   fastify.inject({
 | |
|     method: 'HEAD',
 | |
|     url: '/string'
 | |
|   }, (error, res) => {
 | |
|     t.error(error)
 | |
|     t.equal(res.statusCode, 200)
 | |
|     t.equal(res.headers['content-type'], 'text/plain; charset=utf-8')
 | |
|     t.equal(res.headers['content-length'], `${Buffer.byteLength(resString)}`)
 | |
|     t.equal(res.body, '')
 | |
|   })
 | |
| 
 | |
|   fastify.inject({
 | |
|     method: 'HEAD',
 | |
|     url: '/buffer'
 | |
|   }, (error, res) => {
 | |
|     t.error(error)
 | |
|     t.equal(res.statusCode, 200)
 | |
|     t.equal(res.headers['content-type'], 'application/octet-stream')
 | |
|     t.equal(res.headers['content-length'], `${resBuffer.byteLength}`)
 | |
|     t.equal(res.body, '')
 | |
|   })
 | |
| 
 | |
|   fastify.inject({
 | |
|     method: 'HEAD',
 | |
|     url: '/buffer-with-content-type'
 | |
|   }, (error, res) => {
 | |
|     t.error(error)
 | |
|     t.equal(res.statusCode, 200)
 | |
|     t.equal(res.headers['content-type'], 'image/jpeg')
 | |
|     t.equal(res.headers['content-length'], `${resBuffer.byteLength}`)
 | |
|     t.equal(res.body, '')
 | |
|   })
 | |
| 
 | |
|   fastify.inject({
 | |
|     method: 'HEAD',
 | |
|     url: '/stream'
 | |
|   }, (error, res) => {
 | |
|     t.error(error)
 | |
|     t.equal(res.statusCode, 200)
 | |
|     t.equal(res.headers['content-type'], 'application/octet-stream')
 | |
|     t.equal(res.headers['content-length'], undefined)
 | |
|     t.equal(res.body, '')
 | |
|   })
 | |
| })
 | |
| 
 | |
| test('HEAD route should respect custom onSend handlers', t => {
 | |
|   t.plan(6)
 | |
| 
 | |
|   let counter = 0
 | |
|   const resBuffer = Buffer.from('I am a coffee!')
 | |
|   const fastify = Fastify({ exposeHeadRoutes: true })
 | |
|   const customOnSend = (res, reply, payload, done) => {
 | |
|     counter = counter + 1
 | |
|     done(null, payload)
 | |
|   }
 | |
| 
 | |
|   fastify.route({
 | |
|     method: 'GET',
 | |
|     path: '/more-coffee',
 | |
|     handler: (req, reply) => {
 | |
|       reply.send(resBuffer)
 | |
|     },
 | |
|     onSend: [customOnSend, customOnSend]
 | |
|   })
 | |
| 
 | |
|   fastify.inject({
 | |
|     method: 'HEAD',
 | |
|     url: '/more-coffee'
 | |
|   }, (error, res) => {
 | |
|     t.error(error)
 | |
|     t.equal(res.statusCode, 200)
 | |
|     t.equal(res.headers['content-type'], 'application/octet-stream')
 | |
|     t.equal(res.headers['content-length'], `${resBuffer.byteLength}`)
 | |
|     t.equal(res.body, '')
 | |
|     t.equal(counter, 2)
 | |
|   })
 | |
| })
 | |
| 
 | |
| test('route onSend can be function or array of functions', t => {
 | |
|   t.plan(12)
 | |
|   const counters = { single: 0, multiple: 0 }
 | |
| 
 | |
|   const resBuffer = Buffer.from('I am a coffee!')
 | |
|   const fastify = Fastify({ exposeHeadRoutes: true })
 | |
| 
 | |
|   fastify.route({
 | |
|     method: 'GET',
 | |
|     path: '/coffee',
 | |
|     handler: () => resBuffer,
 | |
|     onSend: (res, reply, payload, done) => {
 | |
|       counters.single += 1
 | |
|       done(null, payload)
 | |
|     }
 | |
|   })
 | |
| 
 | |
|   const customOnSend = (res, reply, payload, done) => {
 | |
|     counters.multiple += 1
 | |
|     done(null, payload)
 | |
|   }
 | |
| 
 | |
|   fastify.route({
 | |
|     method: 'GET',
 | |
|     path: '/more-coffee',
 | |
|     handler: () => resBuffer,
 | |
|     onSend: [customOnSend, customOnSend]
 | |
|   })
 | |
| 
 | |
|   fastify.inject({ method: 'HEAD', url: '/coffee' }, (error, res) => {
 | |
|     t.error(error)
 | |
|     t.equal(res.statusCode, 200)
 | |
|     t.equal(res.headers['content-type'], 'application/octet-stream')
 | |
|     t.equal(res.headers['content-length'], `${resBuffer.byteLength}`)
 | |
|     t.equal(res.body, '')
 | |
|     t.equal(counters.single, 1)
 | |
|   })
 | |
| 
 | |
|   fastify.inject({ method: 'HEAD', url: '/more-coffee' }, (error, res) => {
 | |
|     t.error(error)
 | |
|     t.equal(res.statusCode, 200)
 | |
|     t.equal(res.headers['content-type'], 'application/octet-stream')
 | |
|     t.equal(res.headers['content-length'], `${resBuffer.byteLength}`)
 | |
|     t.equal(res.body, '')
 | |
|     t.equal(counters.multiple, 2)
 | |
|   })
 | |
| })
 | |
| 
 | |
| test('no warning for exposeHeadRoute', async t => {
 | |
|   const fastify = Fastify()
 | |
| 
 | |
|   fastify.route({
 | |
|     method: 'GET',
 | |
|     path: '/more-coffee',
 | |
|     exposeHeadRoute: true,
 | |
|     async handler () {
 | |
|       return 'hello world'
 | |
|     }
 | |
|   })
 | |
| 
 | |
|   const listener = (w) => {
 | |
|     t.fail('no warning')
 | |
|   }
 | |
| 
 | |
|   process.on('warning', listener)
 | |
| 
 | |
|   await fastify.listen(0)
 | |
| 
 | |
|   process.removeListener('warning', listener)
 | |
| 
 | |
|   await fastify.close()
 | |
| })
 | |
| 
 | |
| test("HEAD route should handle stream.on('error')", t => {
 | |
|   t.plan(6)
 | |
| 
 | |
|   const resStream = stream.Readable.from('Hello with error!')
 | |
|   const logStream = split(JSON.parse)
 | |
|   const expectedError = new Error('Hello!')
 | |
|   const fastify = Fastify({
 | |
|     logger: {
 | |
|       stream: logStream,
 | |
|       level: 'error'
 | |
|     }
 | |
|   })
 | |
| 
 | |
|   fastify.route({
 | |
|     method: 'GET',
 | |
|     path: '/more-coffee',
 | |
|     exposeHeadRoute: true,
 | |
|     handler: (req, reply) => {
 | |
|       process.nextTick(() => resStream.emit('error', expectedError))
 | |
|       return resStream
 | |
|     }
 | |
|   })
 | |
| 
 | |
|   logStream.once('data', line => {
 | |
|     const { message, stack } = expectedError
 | |
|     t.same(line.err, { type: 'Error', message, stack })
 | |
|     t.equal(line.msg, 'Error on Stream found for HEAD route')
 | |
|     t.equal(line.level, 50)
 | |
|   })
 | |
| 
 | |
|   fastify.inject({
 | |
|     method: 'HEAD',
 | |
|     url: '/more-coffee'
 | |
|   }, (error, res) => {
 | |
|     t.error(error)
 | |
|     t.equal(res.statusCode, 200)
 | |
|     t.equal(res.headers['content-type'], 'application/octet-stream')
 | |
|   })
 | |
| })
 | |
| 
 | |
| test('HEAD route should not be exposed by default', t => {
 | |
|   t.plan(7)
 | |
| 
 | |
|   const resStream = stream.Readable.from('Hello with error!')
 | |
|   const resJson = { hello: 'world' }
 | |
|   const fastify = Fastify()
 | |
| 
 | |
|   fastify.route({
 | |
|     method: 'GET',
 | |
|     path: '/without-flag',
 | |
|     handler: (req, reply) => {
 | |
|       return resStream
 | |
|     }
 | |
|   })
 | |
| 
 | |
|   fastify.route({
 | |
|     exposeHeadRoute: true,
 | |
|     method: 'GET',
 | |
|     path: '/with-flag',
 | |
|     handler: (req, reply) => {
 | |
|       return resJson
 | |
|     }
 | |
|   })
 | |
| 
 | |
|   fastify.inject({
 | |
|     method: 'HEAD',
 | |
|     url: '/without-flag'
 | |
|   }, (error, res) => {
 | |
|     t.error(error)
 | |
|     t.equal(res.statusCode, 404)
 | |
|   })
 | |
| 
 | |
|   fastify.inject({
 | |
|     method: 'HEAD',
 | |
|     url: '/with-flag'
 | |
|   }, (error, res) => {
 | |
|     t.error(error)
 | |
|     t.equal(res.statusCode, 200)
 | |
|     t.equal(res.headers['content-type'], 'application/json; charset=utf-8')
 | |
|     t.equal(res.headers['content-length'], `${Buffer.byteLength(JSON.stringify(resJson))}`)
 | |
|     t.equal(res.body, '')
 | |
|   })
 | |
| })
 | |
| 
 | |
| test('HEAD route should be exposed if route exposeHeadRoute is set', t => {
 | |
|   t.plan(7)
 | |
| 
 | |
|   const resBuffer = Buffer.from('I am a coffee!')
 | |
|   const resJson = { hello: 'world' }
 | |
|   const fastify = Fastify({ exposeHeadRoutes: false })
 | |
| 
 | |
|   fastify.route({
 | |
|     exposeHeadRoute: true,
 | |
|     method: 'GET',
 | |
|     path: '/one',
 | |
|     handler: (req, reply) => {
 | |
|       return resBuffer
 | |
|     }
 | |
|   })
 | |
| 
 | |
|   fastify.route({
 | |
|     method: 'GET',
 | |
|     path: '/two',
 | |
|     handler: (req, reply) => {
 | |
|       return resJson
 | |
|     }
 | |
|   })
 | |
| 
 | |
|   fastify.inject({
 | |
|     method: 'HEAD',
 | |
|     url: '/one'
 | |
|   }, (error, res) => {
 | |
|     t.error(error)
 | |
|     t.equal(res.statusCode, 200)
 | |
|     t.equal(res.headers['content-type'], 'application/octet-stream')
 | |
|     t.equal(res.headers['content-length'], `${resBuffer.byteLength}`)
 | |
|     t.equal(res.body, '')
 | |
|   })
 | |
| 
 | |
|   fastify.inject({
 | |
|     method: 'HEAD',
 | |
|     url: '/two'
 | |
|   }, (error, res) => {
 | |
|     t.error(error)
 | |
|     t.equal(res.statusCode, 404)
 | |
|   })
 | |
| })
 | |
| 
 | |
| test('Set a custom HEAD route before GET one without disabling exposeHeadRoutes (global)', t => {
 | |
|   t.plan(6)
 | |
| 
 | |
|   const resBuffer = Buffer.from('I am a coffee!')
 | |
|   const fastify = Fastify({
 | |
|     exposeHeadRoutes: true
 | |
|   })
 | |
| 
 | |
|   fastify.route({
 | |
|     method: 'HEAD',
 | |
|     path: '/one',
 | |
|     handler: (req, reply) => {
 | |
|       reply.header('content-type', 'application/pdf')
 | |
|       reply.header('content-length', `${resBuffer.byteLength}`)
 | |
|       reply.header('x-custom-header', 'some-custom-header')
 | |
|       reply.send()
 | |
|     }
 | |
|   })
 | |
| 
 | |
|   fastify.route({
 | |
|     method: 'GET',
 | |
|     path: '/one',
 | |
|     handler: (req, reply) => {
 | |
|       return resBuffer
 | |
|     }
 | |
|   })
 | |
| 
 | |
|   fastify.inject({
 | |
|     method: 'HEAD',
 | |
|     url: '/one'
 | |
|   }, (error, res) => {
 | |
|     t.error(error)
 | |
|     t.equal(res.statusCode, 200)
 | |
|     t.equal(res.headers['content-type'], 'application/pdf')
 | |
|     t.equal(res.headers['content-length'], `${resBuffer.byteLength}`)
 | |
|     t.equal(res.headers['x-custom-header'], 'some-custom-header')
 | |
|     t.equal(res.body, '')
 | |
|   })
 | |
| })
 | |
| 
 | |
| test('Set a custom HEAD route before GET one without disabling exposeHeadRoutes (route)', t => {
 | |
|   t.plan(7)
 | |
| 
 | |
|   function onWarning (code) {
 | |
|     t.equal(code, 'FSTDEP007')
 | |
|   }
 | |
|   const warning = {
 | |
|     emit: onWarning
 | |
|   }
 | |
| 
 | |
|   const route = proxyquire('../lib/route', { './warnings': warning })
 | |
|   const fastify = proxyquire('..', { './lib/route.js': route })()
 | |
| 
 | |
|   const resBuffer = Buffer.from('I am a coffee!')
 | |
| 
 | |
|   fastify.route({
 | |
|     method: 'HEAD',
 | |
|     path: '/one',
 | |
|     handler: (req, reply) => {
 | |
|       reply.header('content-type', 'application/pdf')
 | |
|       reply.header('content-length', `${resBuffer.byteLength}`)
 | |
|       reply.header('x-custom-header', 'some-custom-header')
 | |
|       reply.send()
 | |
|     }
 | |
|   })
 | |
| 
 | |
|   fastify.route({
 | |
|     method: 'GET',
 | |
|     exposeHeadRoute: true,
 | |
|     path: '/one',
 | |
|     handler: (req, reply) => {
 | |
|       return resBuffer
 | |
|     }
 | |
|   })
 | |
| 
 | |
|   fastify.inject({
 | |
|     method: 'HEAD',
 | |
|     url: '/one'
 | |
|   }, (error, res) => {
 | |
|     t.error(error)
 | |
|     t.equal(res.statusCode, 200)
 | |
|     t.equal(res.headers['content-type'], 'application/pdf')
 | |
|     t.equal(res.headers['content-length'], `${resBuffer.byteLength}`)
 | |
|     t.equal(res.headers['x-custom-header'], 'some-custom-header')
 | |
|     t.equal(res.body, '')
 | |
|   })
 | |
| })
 | |
| 
 | |
| test('HEAD routes properly auto created for GET routes when prefixTrailingSlash: \'no-slash\'', t => {
 | |
|   t.plan(2)
 | |
| 
 | |
|   const fastify = Fastify()
 | |
| 
 | |
|   fastify.register(function routes (f, opts, next) {
 | |
|     f.route({
 | |
|       method: 'GET',
 | |
|       url: '/',
 | |
|       exposeHeadRoute: true,
 | |
|       prefixTrailingSlash: 'no-slash',
 | |
|       handler: (req, reply) => {
 | |
|         reply.send({ hello: 'world' })
 | |
|       }
 | |
|     })
 | |
| 
 | |
|     next()
 | |
|   }, { prefix: '/prefix' })
 | |
| 
 | |
|   fastify.inject({ url: '/prefix/prefix', method: 'HEAD' }, (err, res) => {
 | |
|     t.error(err)
 | |
|     t.equal(res.statusCode, 404)
 | |
|   })
 | |
| })
 | |
| 
 | |
| test('HEAD routes properly auto created for GET routes when prefixTrailingSlash: \'both\'', async t => {
 | |
|   t.plan(3)
 | |
| 
 | |
|   const fastify = Fastify()
 | |
| 
 | |
|   fastify.register(function routes (f, opts, next) {
 | |
|     f.route({
 | |
|       method: 'GET',
 | |
|       url: '/',
 | |
|       exposeHeadRoute: true,
 | |
|       prefixTrailingSlash: 'both',
 | |
|       handler: (req, reply) => {
 | |
|         reply.send({ hello: 'world' })
 | |
|       }
 | |
|     })
 | |
| 
 | |
|     next()
 | |
|   }, { prefix: '/prefix' })
 | |
| 
 | |
|   const doublePrefixReply = await fastify.inject({ url: '/prefix/prefix', method: 'HEAD' })
 | |
|   const trailingSlashReply = await fastify.inject({ url: '/prefix/', method: 'HEAD' })
 | |
|   const noneTrailingReply = await fastify.inject({ url: '/prefix', method: 'HEAD' })
 | |
| 
 | |
|   t.equal(doublePrefixReply.statusCode, 404)
 | |
|   t.equal(trailingSlashReply.statusCode, 200)
 | |
|   t.equal(noneTrailingReply.statusCode, 200)
 | |
| })
 | |
| 
 | |
| test('Request and Reply share the route config', async t => {
 | |
|   t.plan(3)
 | |
| 
 | |
|   const fastify = Fastify()
 | |
| 
 | |
|   const config = {
 | |
|     this: 'is a string',
 | |
|     thisIs: function aFunction () {}
 | |
|   }
 | |
| 
 | |
|   fastify.route({
 | |
|     method: 'GET',
 | |
|     url: '/',
 | |
|     config,
 | |
|     handler: (req, reply) => {
 | |
|       t.same(req.context, reply.context)
 | |
|       t.same(req.context.config, reply.context.config)
 | |
|       t.match(req.context.config, config, 'there are url and method additional properties')
 | |
| 
 | |
|       reply.send({ hello: 'world' })
 | |
|     }
 | |
|   })
 | |
| 
 | |
|   await fastify.inject('/')
 | |
| })
 | |
| 
 | |
| test('Will not try to re-createprefixed HEAD route if it already exists and exposeHeadRoutes is true', async (t) => {
 | |
|   t.plan(1)
 | |
| 
 | |
|   const fastify = Fastify({ exposeHeadRoutes: true })
 | |
| 
 | |
|   fastify.register((scope, opts, next) => {
 | |
|     scope.route({
 | |
|       method: 'HEAD',
 | |
|       path: '/route',
 | |
|       handler: (req, reply) => {
 | |
|         reply.header('content-type', 'text/plain')
 | |
|         reply.send('custom HEAD response')
 | |
|       }
 | |
|     })
 | |
|     scope.route({
 | |
|       method: 'GET',
 | |
|       path: '/route',
 | |
|       handler: (req, reply) => {
 | |
|         reply.send({ ok: true })
 | |
|       }
 | |
|     })
 | |
| 
 | |
|     next()
 | |
|   }, { prefix: '/prefix' })
 | |
| 
 | |
|   await fastify.ready()
 | |
| 
 | |
|   t.ok(true)
 | |
| })
 | |
| 
 | |
| test('Correct error message is produced if "path" option is used', t => {
 | |
|   t.plan(2)
 | |
| 
 | |
|   const fastify = Fastify()
 | |
| 
 | |
|   t.throws(() => {
 | |
|     fastify.route({
 | |
|       method: 'GET',
 | |
|       path: '/test'
 | |
|     })
 | |
|   }, new Error('Missing handler function for GET:/test route.'))
 | |
| 
 | |
|   t.throws(() => {
 | |
|     fastify.route({
 | |
|       method: 'POST',
 | |
|       url: '/test',
 | |
|       handler: function () {},
 | |
|       errorHandler: ''
 | |
|     })
 | |
|   }, new Error('Error Handler for POST:/test route, if defined, must be a function'))
 | |
| })
 | |
| 
 | |
| test('invalid url attribute - non string URL', t => {
 | |
|   t.plan(1)
 | |
|   const fastify = Fastify()
 | |
| 
 | |
|   try {
 | |
|     fastify.get(/^\/(donations|skills|blogs)/, () => {})
 | |
|   } catch (error) {
 | |
|     t.equal(error.code, FST_ERR_INVALID_URL().code)
 | |
|   }
 | |
| })
 |