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

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