Files
fastify-beginner-guide/node_modules/fastify/test/internals/reply.test.js

1902 lines
53 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use strict'
const { test } = require('node:test')
const http = require('node:http')
const NotFound = require('http-errors').NotFound
const Request = require('../../lib/request')
const Reply = require('../../lib/reply')
const Fastify = require('../..')
const { Readable, Writable } = require('node:stream')
const {
kReplyErrorHandlerCalled,
kReplyHeaders,
kReplySerializer,
kReplyIsError,
kReplySerializerDefault,
kRouteContext
} = require('../../lib/symbols')
const fs = require('node:fs')
const path = require('node:path')
const doGet = async function (url) {
const result = await fetch(url, {
method: 'GET',
redirect: 'manual',
keepAlive: false
})
return {
response: result,
body: await result.json().catch(() => undefined)
}
}
test('Once called, Reply should return an object with methods', t => {
t.plan(15)
const response = { res: 'res' }
const context = { config: { onSend: [] }, schema: {}, _parserOptions: {}, server: { hasConstraintStrategy: () => false, initialConfig: {} } }
const request = new Request(null, null, null, null, null, context)
const reply = new Reply(response, request)
t.assert.strictEqual(typeof reply, 'object')
t.assert.strictEqual(typeof reply[kReplyIsError], 'boolean')
t.assert.strictEqual(typeof reply[kReplyErrorHandlerCalled], 'boolean')
t.assert.strictEqual(typeof reply.send, 'function')
t.assert.strictEqual(typeof reply.code, 'function')
t.assert.strictEqual(typeof reply.status, 'function')
t.assert.strictEqual(typeof reply.header, 'function')
t.assert.strictEqual(typeof reply.serialize, 'function')
t.assert.strictEqual(typeof reply[kReplyHeaders], 'object')
t.assert.deepStrictEqual(reply.raw, response)
t.assert.strictEqual(reply[kRouteContext], context)
t.assert.strictEqual(reply.routeOptions.config, context.config)
t.assert.strictEqual(reply.routeOptions.schema, context.schema)
t.assert.strictEqual(reply.request, request)
// Aim to not bad property keys (including Symbols)
t.assert.ok(!('undefined' in reply))
})
test('reply.send will logStream error and destroy the stream', t => {
t.plan(1)
let destroyCalled
const payload = new Readable({
read () { },
destroy (err, cb) {
destroyCalled = true
cb(err)
}
})
const response = new Writable()
Object.assign(response, {
setHeader: () => { },
hasHeader: () => false,
getHeader: () => undefined,
writeHead: () => { },
write: () => { },
headersSent: true
})
const log = {
warn: () => { }
}
const reply = new Reply(response, { [kRouteContext]: { onSend: null } }, log)
reply.send(payload)
payload.destroy(new Error('stream error'))
t.assert.strictEqual(destroyCalled, true, 'Error not logged and not streamed')
})
test('reply.send throw with circular JSON', t => {
t.plan(1)
const response = {
setHeader: () => { },
hasHeader: () => false,
getHeader: () => undefined,
writeHead: () => { },
write: () => { },
end: () => { }
}
const reply = new Reply(response, { [kRouteContext]: { onSend: [] } })
t.assert.throws(() => {
const obj = {}
obj.obj = obj
reply.send(JSON.stringify(obj))
}, 'Converting circular structure to JSON')
})
test('reply.send returns itself', t => {
t.plan(1)
const response = {
setHeader: () => { },
hasHeader: () => false,
getHeader: () => undefined,
writeHead: () => { },
write: () => { },
end: () => { }
}
const reply = new Reply(response, { [kRouteContext]: { onSend: [] } })
t.assert.strictEqual(reply.send('hello'), reply)
})
test('reply.serializer should set a custom serializer', t => {
t.plan(2)
const reply = new Reply(null, null, null)
t.assert.strictEqual(reply[kReplySerializer], null)
reply.serializer('serializer')
t.assert.strictEqual(reply[kReplySerializer], 'serializer')
})
test('reply.serializer should support running preSerialization hooks', (t, done) => {
t.plan(3)
const fastify = Fastify()
t.after(() => fastify.close())
fastify.addHook('preSerialization', async (request, reply, payload) => { t.assert.ok('called', 'preSerialization') })
fastify.route({
method: 'GET',
url: '/',
handler: (req, reply) => {
reply
.type('application/json')
.serializer(JSON.stringify)
.send({ foo: 'bar' })
}
})
fastify.inject({
method: 'GET',
url: '/'
}, (err, res) => {
t.assert.ifError(err)
t.assert.strictEqual(res.payload, '{"foo":"bar"}')
done()
})
})
test('reply.serialize should serialize payload', t => {
t.plan(1)
const response = { statusCode: 200 }
const context = {}
const reply = new Reply(response, { [kRouteContext]: context })
t.assert.strictEqual(reply.serialize({ foo: 'bar' }), '{"foo":"bar"}')
})
test('reply.serialize should serialize payload with a custom serializer', t => {
t.plan(2)
let customSerializerCalled = false
const response = { statusCode: 200 }
const context = {}
const reply = new Reply(response, { [kRouteContext]: context })
reply.serializer((x) => (customSerializerCalled = true) && JSON.stringify(x))
t.assert.strictEqual(reply.serialize({ foo: 'bar' }), '{"foo":"bar"}')
t.assert.strictEqual(customSerializerCalled, true, 'custom serializer not called')
})
test('reply.serialize should serialize payload with a context default serializer', t => {
t.plan(2)
let customSerializerCalled = false
const response = { statusCode: 200 }
const context = { [kReplySerializerDefault]: (x) => (customSerializerCalled = true) && JSON.stringify(x) }
const reply = new Reply(response, { [kRouteContext]: context })
t.assert.strictEqual(reply.serialize({ foo: 'bar' }), '{"foo":"bar"}')
t.assert.strictEqual(customSerializerCalled, true, 'custom serializer not called')
})
test('reply.serialize should serialize payload with Fastify instance', (t, done) => {
t.plan(2)
const fastify = Fastify()
t.after(() => fastify.close())
fastify.route({
method: 'GET',
url: '/',
schema: {
response: {
200: {
type: 'object',
properties: {
foo: { type: 'string' }
}
}
}
},
handler: (req, reply) => {
reply.send(
reply.serialize({ foo: 'bar' })
)
}
})
fastify.inject({
method: 'GET',
url: '/'
}, (err, res) => {
t.assert.ifError(err)
t.assert.strictEqual(res.payload, '{"foo":"bar"}')
done()
})
})
test('within an instance', async t => {
const fastify = Fastify()
t.after(() => fastify.close())
fastify.get('/', function (req, reply) {
reply.code(200)
reply.header('Content-Type', 'text/plain')
reply.send('hello world!')
})
fastify.get('/auto-type', function (req, reply) {
reply.code(200)
reply.type('text/plain')
reply.send('hello world!')
})
fastify.get('/auto-status-code', function (req, reply) {
reply.send('hello world!')
})
fastify.get('/redirect', function (req, reply) {
reply.redirect('/')
})
fastify.get('/redirect-async', async function (req, reply) {
return reply.redirect('/')
})
fastify.get('/redirect-code', function (req, reply) {
reply.redirect('/', 301)
})
fastify.get('/redirect-code-before-call', function (req, reply) {
reply.code(307).redirect('/')
})
fastify.get('/redirect-code-before-call-overwrite', function (req, reply) {
reply.code(307).redirect('/', 302)
})
fastify.get('/custom-serializer', function (req, reply) {
reply.code(200)
reply.type('text/plain')
reply.serializer(function (body) {
return require('node:querystring').stringify(body)
})
reply.send({ hello: 'world!' })
})
fastify.register(function (instance, options, done) {
fastify.addHook('onSend', function (req, reply, payload, done) {
reply.header('x-onsend', 'yes')
done()
})
fastify.get('/redirect-onsend', function (req, reply) {
reply.redirect('/')
})
done()
})
const fastifyServer = await fastify.listen({ port: 0 })
await t.test('custom serializer should be used', async t => {
t.plan(3)
const result = await fetch(fastifyServer + '/custom-serializer')
t.assert.ok(result.ok)
t.assert.strictEqual(result.headers.get('content-type'), 'text/plain')
t.assert.deepStrictEqual(await result.text(), 'hello=world!')
})
await t.test('status code and content-type should be correct', async t => {
t.plan(3)
const result = await fetch(fastifyServer)
t.assert.ok(result.ok)
t.assert.strictEqual(result.headers.get('content-type'), 'text/plain')
t.assert.deepStrictEqual(await result.text(), 'hello world!')
})
await t.test('auto status code should be 200', async t => {
t.plan(3)
const result = await fetch(fastifyServer + '/auto-status-code')
t.assert.ok(result.ok)
t.assert.strictEqual(result.status, 200)
t.assert.deepStrictEqual(await result.text(), 'hello world!')
})
await t.test('auto type should be text/plain', async t => {
t.plan(3)
const result = await fetch(fastifyServer + '/auto-type')
t.assert.ok(result.ok)
t.assert.strictEqual(result.headers.get('content-type'), 'text/plain')
t.assert.deepStrictEqual(await result.text(), 'hello world!')
})
await t.test('redirect to `/` - 1', (t, done) => {
t.plan(1)
http.get(fastifyServer + '/redirect', function (response) {
t.assert.strictEqual(response.statusCode, 302)
done()
})
})
await t.test('redirect to `/` - 2', (t, done) => {
t.plan(1)
http.get(fastifyServer + '/redirect-code', function (response) {
t.assert.strictEqual(response.statusCode, 301)
done()
})
})
await t.test('redirect to `/` - 3', async t => {
t.plan(4)
const result = await fetch(fastifyServer + '/redirect')
t.assert.ok(result.ok)
t.assert.strictEqual(result.status, 200)
t.assert.strictEqual(result.headers.get('content-type'), 'text/plain')
t.assert.deepStrictEqual(await result.text(), 'hello world!')
})
await t.test('redirect to `/` - 4', async t => {
t.plan(4)
const result = await fetch(fastifyServer + '/redirect-code')
t.assert.ok(result.ok)
t.assert.strictEqual(result.status, 200)
t.assert.strictEqual(result.headers.get('content-type'), 'text/plain')
t.assert.deepStrictEqual(await result.text(), 'hello world!')
})
await t.test('redirect to `/` - 5', (t, done) => {
t.plan(3)
const url = fastifyServer + '/redirect-onsend'
http.get(url, (response) => {
t.assert.strictEqual(response.headers['x-onsend'], 'yes')
t.assert.strictEqual(response.headers['content-length'], '0')
t.assert.strictEqual(response.headers.location, '/')
done()
})
})
await t.test('redirect to `/` - 6', async t => {
t.plan(4)
const result = await fetch(fastifyServer + '/redirect-code-before-call')
t.assert.ok(result.ok)
t.assert.strictEqual(result.status, 200)
t.assert.strictEqual(result.headers.get('content-type'), 'text/plain')
t.assert.deepStrictEqual(await result.text(), 'hello world!')
})
await t.test('redirect to `/` - 7', async t => {
t.plan(4)
const result = await fetch(fastifyServer + '/redirect-code-before-call-overwrite')
t.assert.ok(result.ok)
t.assert.strictEqual(result.status, 200)
t.assert.strictEqual(result.headers.get('content-type'), 'text/plain')
t.assert.deepStrictEqual(await result.text(), 'hello world!')
})
await t.test('redirect to `/` - 8', (t, done) => {
t.plan(1)
http.get(fastifyServer + '/redirect-code-before-call', function (response) {
t.assert.strictEqual(response.statusCode, 307)
done()
})
})
await t.test('redirect to `/` - 9', (t, done) => {
t.plan(1)
http.get(fastifyServer + '/redirect-code-before-call-overwrite', function (response) {
t.assert.strictEqual(response.statusCode, 302)
done()
})
})
await t.test('redirect with async function to `/` - 10', (t, done) => {
t.plan(1)
http.get(fastifyServer + '/redirect-async', function (response) {
t.assert.strictEqual(response.statusCode, 302)
done()
})
})
})
test('buffer without content type should send a application/octet-stream and raw buffer', async t => {
t.plan(3)
const fastify = Fastify()
fastify.get('/', function (req, reply) {
reply.send(Buffer.alloc(1024))
})
const fastifyServer = await fastify.listen({ port: 0 })
t.after(() => fastify.close())
const result = await fetch(fastifyServer)
t.assert.ok(result.ok)
t.assert.strictEqual(result.headers.get('content-type'), 'application/octet-stream')
t.assert.deepStrictEqual(Buffer.from(await result.arrayBuffer()), Buffer.alloc(1024))
})
test('Uint8Array without content type should send a application/octet-stream and raw buffer', (t, done) => {
t.plan(4)
const fastify = Fastify()
fastify.get('/', function (req, reply) {
reply.send(new Uint8Array(1024).fill(0xff))
})
fastify.listen({ port: 0 }, err => {
t.assert.ifError(err)
t.after(() => fastify.close())
fastify.inject({
method: 'GET',
url: '/'
}, (err, response) => {
t.assert.ifError(err)
t.assert.strictEqual(response.headers['content-type'], 'application/octet-stream')
t.assert.deepStrictEqual(new Uint8Array(response.rawPayload), new Uint8Array(1024).fill(0xff))
done()
})
})
})
test('Uint16Array without content type should send a application/octet-stream and raw buffer', (t, done) => {
t.plan(4)
const fastify = Fastify()
fastify.get('/', function (req, reply) {
reply.send(new Uint16Array(50).fill(0xffffffff))
})
fastify.listen({ port: 0 }, err => {
t.assert.ifError(err)
t.after(() => fastify.close())
fastify.inject({
method: 'GET',
url: '/'
}, (err, res) => {
t.assert.ifError(err)
t.assert.strictEqual(res.headers['content-type'], 'application/octet-stream')
t.assert.deepStrictEqual(new Uint16Array(res.rawPayload.buffer, res.rawPayload.byteOffset, res.rawPayload.byteLength / Uint16Array.BYTES_PER_ELEMENT), new Uint16Array(50).fill(0xffffffff))
done()
})
})
})
test('TypedArray with content type should not send application/octet-stream', (t, done) => {
t.plan(4)
const fastify = Fastify()
fastify.get('/', function (req, reply) {
reply.header('Content-Type', 'text/plain')
reply.send(new Uint16Array(1024).fill(0xffffffff))
})
fastify.listen({ port: 0 }, err => {
t.assert.ifError(err)
t.after(() => fastify.close())
fastify.inject({
method: 'GET',
url: '/'
}, (err, res) => {
t.assert.ifError(err)
t.assert.strictEqual(res.headers['content-type'], 'text/plain')
t.assert.deepStrictEqual(new Uint16Array(res.rawPayload.buffer, res.rawPayload.byteOffset, res.rawPayload.byteLength / Uint16Array.BYTES_PER_ELEMENT), new Uint16Array(1024).fill(0xffffffff))
done()
})
})
})
test('buffer with content type should not send application/octet-stream', async t => {
t.plan(3)
const fastify = Fastify()
fastify.get('/', function (req, reply) {
reply.header('Content-Type', 'text/plain')
reply.send(Buffer.alloc(1024))
})
const fastifyServer = await fastify.listen({ port: 0 })
t.after(() => fastify.close())
const result = await fetch(fastifyServer)
t.assert.ok(result.ok)
t.assert.strictEqual(result.headers.get('content-type'), 'text/plain')
t.assert.deepStrictEqual(Buffer.from(await result.arrayBuffer()), Buffer.alloc(1024))
})
test('stream with content type should not send application/octet-stream', async t => {
t.plan(3)
const fastify = Fastify()
const streamPath = path.join(__dirname, '..', '..', 'package.json')
const stream = fs.createReadStream(streamPath)
const buf = fs.readFileSync(streamPath)
fastify.get('/', function (req, reply) {
reply.header('Content-Type', 'text/plain').send(stream)
})
const fastifyServer = await fastify.listen({ port: 0 })
t.after(() => fastify.close())
const result = await fetch(fastifyServer)
t.assert.ok(result.ok)
t.assert.strictEqual(result.headers.get('content-type'), 'text/plain')
t.assert.deepStrictEqual(Buffer.from(await result.arrayBuffer()), buf)
})
test('stream without content type should not send application/octet-stream', async t => {
t.plan(3)
const fastify = Fastify()
const stream = fs.createReadStream(__filename)
const buf = fs.readFileSync(__filename)
fastify.get('/', function (req, reply) {
reply.send(stream)
})
const fastifyServer = await fastify.listen({ port: 0 })
t.after(() => fastify.close())
const result = await fetch(fastifyServer)
t.assert.ok(result.ok)
t.assert.strictEqual(result.headers.get('content-type'), null)
t.assert.deepStrictEqual(Buffer.from(await result.arrayBuffer()), buf)
})
test('stream using reply.raw.writeHead should return customize headers', async t => {
t.plan(5)
const fastify = Fastify()
const fs = require('node:fs')
const path = require('node:path')
const streamPath = path.join(__dirname, '..', '..', 'package.json')
const stream = fs.createReadStream(streamPath)
const buf = fs.readFileSync(streamPath)
fastify.get('/', function (req, reply) {
reply.log.warn = function mockWarn (message) {
t.assert.strictEqual(message, 'response will send, but you shouldn\'t use res.writeHead in stream mode')
}
reply.raw.writeHead(200, {
location: '/'
})
reply.send(stream)
})
const fastifyServer = await fastify.listen({ port: 0 })
t.after(() => fastify.close())
const result = await fetch(fastifyServer)
t.assert.ok(result.ok)
t.assert.strictEqual(result.headers.get('location'), '/')
t.assert.strictEqual(result.headers.get('content-type'), null)
t.assert.deepStrictEqual(Buffer.from(await result.arrayBuffer()), buf)
})
test('plain string without content type should send a text/plain', async t => {
t.plan(3)
const fastify = Fastify()
fastify.get('/', function (req, reply) {
reply.send('hello world!')
})
const fastifyServer = await fastify.listen({ port: 0 })
t.after(() => fastify.close())
const result = await fetch(fastifyServer)
t.assert.ok(result.ok)
t.assert.strictEqual(result.headers.get('content-type'), 'text/plain; charset=utf-8')
t.assert.deepStrictEqual(await result.text(), 'hello world!')
})
test('plain string with content type should be sent unmodified', async t => {
t.plan(3)
const fastify = Fastify()
fastify.get('/', function (req, reply) {
reply.type('text/css').send('hello world!')
})
const fastifyServer = await fastify.listen({ port: 0 })
t.after(() => fastify.close())
const result = await fetch(fastifyServer)
t.assert.ok(result.ok)
t.assert.strictEqual(result.headers.get('content-type'), 'text/css')
t.assert.deepStrictEqual(await result.text(), 'hello world!')
})
test('plain string with content type and custom serializer should be serialized', async t => {
t.plan(3)
const fastify = Fastify()
fastify.get('/', function (req, reply) {
reply
.serializer(() => 'serialized')
.type('text/css')
.send('hello world!')
})
const fastifyServer = await fastify.listen({ port: 0 })
t.after(() => fastify.close())
const result = await fetch(fastifyServer)
t.assert.ok(result.ok)
t.assert.strictEqual(result.headers.get('content-type'), 'text/css')
t.assert.deepStrictEqual(await result.text(), 'serialized')
})
test('plain string with content type application/json should NOT be serialized as json', async t => {
t.plan(3)
const fastify = Fastify()
fastify.get('/', function (req, reply) {
reply.type('application/json').send('{"key": "hello world!"}')
})
const fastifyServer = await fastify.listen({ port: 0 })
t.after(() => fastify.close())
const result = await fetch(fastifyServer)
t.assert.ok(result.ok)
t.assert.strictEqual(result.headers.get('content-type'), 'application/json; charset=utf-8')
t.assert.deepStrictEqual(await result.text(), '{"key": "hello world!"}')
})
test('plain string with custom json content type should NOT be serialized as json', async t => {
t.plan(18)
const fastify = Fastify()
t.after(() => fastify.close())
const customSamples = {
collectionjson: {
mimeType: 'application/vnd.collection+json',
sample: '{"collection":{"version":"1.0","href":"http://api.example.com/people/"}}'
},
hal: {
mimeType: 'application/hal+json',
sample: '{"_links":{"self":{"href":"https://api.example.com/people/1"}},"name":"John Doe"}'
},
jsonapi: {
mimeType: 'application/vnd.api+json',
sample: '{"data":{"type":"people","id":"1"}}'
},
jsonld: {
mimeType: 'application/ld+json',
sample: '{"@context":"https://json-ld.org/contexts/person.jsonld","name":"John Doe"}'
},
ndjson: {
mimeType: 'application/x-ndjson',
sample: '{"a":"apple","b":{"bb":"bubble"}}\n{"c":"croissant","bd":{"dd":"dribble"}}'
},
siren: {
mimeType: 'application/vnd.siren+json',
sample: '{"class":"person","properties":{"name":"John Doe"}}'
}
}
Object.keys(customSamples).forEach((path) => {
fastify.get(`/${path}`, function (req, reply) {
reply.type(customSamples[path].mimeType).send(customSamples[path].sample)
})
})
const fastifyServer = await fastify.listen({ port: 0 })
await Promise.all(Object.keys(customSamples).map(async path => {
const result = await fetch(fastifyServer + '/' + path)
t.assert.ok(result.ok)
t.assert.strictEqual(result.headers.get('content-type'), customSamples[path].mimeType + '; charset=utf-8')
t.assert.deepStrictEqual(await result.text(), customSamples[path].sample)
}))
})
test('non-string with content type application/json SHOULD be serialized as json', async t => {
t.plan(3)
const fastify = Fastify()
fastify.get('/', function (req, reply) {
reply.type('application/json').send({ key: 'hello world!' })
})
const fastifyServer = await fastify.listen({ port: 0 })
t.after(() => fastify.close())
const result = await fetch(fastifyServer)
t.assert.ok(result.ok)
t.assert.strictEqual(result.headers.get('content-type'), 'application/json; charset=utf-8')
t.assert.deepStrictEqual(await result.text(), JSON.stringify({ key: 'hello world!' }))
})
test('non-string with custom json\'s content-type SHOULD be serialized as json', async t => {
t.plan(3)
const fastify = Fastify()
fastify.get('/', function (req, reply) {
reply.type('application/json; version=2; ').send({ key: 'hello world!' })
})
const fastifyServer = await fastify.listen({ port: 0 })
t.after(() => fastify.close())
const result = await fetch(fastifyServer)
t.assert.ok(result.ok)
t.assert.strictEqual(result.headers.get('content-type'), 'application/json; version=2; charset=utf-8')
t.assert.deepStrictEqual(await result.text(), JSON.stringify({ key: 'hello world!' }))
})
test('non-string with custom json content type SHOULD be serialized as json', async t => {
t.plan(15)
const fastify = Fastify()
t.after(() => fastify.close())
const customSamples = {
collectionjson: {
mimeType: 'application/vnd.collection+json',
sample: JSON.parse('{"collection":{"version":"1.0","href":"http://api.example.com/people/"}}')
},
hal: {
mimeType: 'application/hal+json',
sample: JSON.parse('{"_links":{"self":{"href":"https://api.example.com/people/1"}},"name":"John Doe"}')
},
jsonapi: {
mimeType: 'application/vnd.api+json',
sample: JSON.parse('{"data":{"type":"people","id":"1"}}')
},
jsonld: {
mimeType: 'application/ld+json',
sample: JSON.parse('{"@context":"https://json-ld.org/contexts/person.jsonld","name":"John Doe"}')
},
siren: {
mimeType: 'application/vnd.siren+json',
sample: JSON.parse('{"class":"person","properties":{"name":"John Doe"}}')
}
}
Object.keys(customSamples).forEach((path) => {
fastify.get(`/${path}`, function (req, reply) {
reply.type(customSamples[path].mimeType).send(customSamples[path].sample)
})
})
const fastifyServer = await fastify.listen({ port: 0 })
await Promise.all(Object.keys(customSamples).map(async path => {
const result = await fetch(fastifyServer + '/' + path)
t.assert.ok(result.ok)
t.assert.strictEqual(result.headers.get('content-type'), customSamples[path].mimeType + '; charset=utf-8')
t.assert.deepStrictEqual(await result.text(), JSON.stringify(customSamples[path].sample))
}))
})
test('error object with a content type that is not application/json should work', async t => {
t.plan(4)
const fastify = Fastify()
fastify.get('/text', function (req, reply) {
reply.type('text/plain')
reply.send(new Error('some application error'))
})
fastify.get('/html', function (req, reply) {
reply.type('text/html')
reply.send(new Error('some application error'))
})
{
const res = await fastify.inject({
method: 'GET',
url: '/text'
})
t.assert.strictEqual(res.statusCode, 500)
t.assert.strictEqual(JSON.parse(res.payload).message, 'some application error')
}
{
const res = await fastify.inject({
method: 'GET',
url: '/html'
})
t.assert.strictEqual(res.statusCode, 500)
t.assert.strictEqual(JSON.parse(res.payload).message, 'some application error')
}
})
test('undefined payload should be sent as-is', async t => {
t.plan(5)
const fastify = Fastify()
fastify.addHook('onSend', function (request, reply, payload, done) {
t.assert.strictEqual(payload, undefined)
done()
})
fastify.get('/', function (req, reply) {
reply.code(204).send()
})
const fastifyServer = await fastify.listen({ port: 0 })
t.after(() => fastify.close())
const result = await fetch(fastifyServer)
t.assert.ok(result.ok)
t.assert.strictEqual(result.headers.get('content-type'), null)
t.assert.strictEqual(result.headers.get('content-length'), null)
const body = await result.text()
t.assert.strictEqual(body.length, 0)
})
test('for HEAD method, no body should be sent but content-length should be', async t => {
t.plan(10)
const fastify = Fastify()
t.after(() => fastify.close())
const contentType = 'application/json; charset=utf-8'
const bodySize = JSON.stringify({ foo: 'bar' }).length
fastify.head('/', {
onSend: function (request, reply, payload, done) {
t.assert.strictEqual(payload, undefined)
done()
}
}, function (req, reply) {
reply.header('content-length', bodySize)
reply.header('content-type', contentType)
reply.code(200).send()
})
fastify.head('/with/null', {
onSend: function (request, reply, payload, done) {
t.assert.strictEqual(payload, 'null')
done()
}
}, function (req, reply) {
reply.header('content-length', bodySize)
reply.header('content-type', contentType)
reply.code(200).send(null)
})
const fastifyServer = await fastify.listen({ port: 0 })
const promise1 = (async () => {
const result = await fetch(fastifyServer, { method: 'HEAD' })
t.assert.ok(result.ok)
t.assert.strictEqual(result.headers.get('content-type'), contentType)
t.assert.strictEqual(result.headers.get('content-length'), bodySize.toString())
t.assert.strictEqual((await result.text()).length, 0)
})()
const promise2 = (async () => {
const result = await fetch(fastifyServer + '/with/null', { method: 'HEAD' })
t.assert.ok(result.ok)
t.assert.strictEqual(result.headers.get('content-type'), contentType)
t.assert.strictEqual(result.headers.get('content-length'), bodySize.toString())
t.assert.strictEqual((await result.text()).length, 0)
})()
await Promise.all([promise1, promise2])
})
test('reply.send(new NotFound()) should not invoke the 404 handler', async t => {
t.plan(6)
const fastify = Fastify()
t.after(() => fastify.close())
fastify.setNotFoundHandler((req, reply) => {
t.fail('Should not be called')
})
fastify.get('/not-found', function (req, reply) {
reply.send(new NotFound())
})
fastify.register(function (instance, options, done) {
instance.get('/not-found', function (req, reply) {
reply.send(new NotFound())
})
done()
}, { prefix: '/prefixed' })
const fastifyServer = await fastify.listen({ port: 0 })
const promise1 = (async () => {
const result = await fetch(`${fastifyServer}/not-found`)
t.assert.strictEqual(result.status, 404)
t.assert.strictEqual(result.headers.get('content-type'), 'application/json; charset=utf-8')
t.assert.deepStrictEqual(JSON.parse(await result.text()), {
statusCode: 404,
error: 'Not Found',
message: 'Not Found'
})
})()
const promise2 = (async () => {
const result = await fetch(`${fastifyServer}/prefixed/not-found`)
t.assert.strictEqual(result.status, 404)
t.assert.strictEqual(result.headers.get('content-type'), 'application/json; charset=utf-8')
t.assert.deepStrictEqual(JSON.parse(await result.text()), {
statusCode: 404,
error: 'Not Found',
message: 'Not Found'
})
})()
await Promise.all([promise1, promise2])
})
test('reply can set multiple instances of same header', async t => {
t.plan(3)
const fastify = require('../../')()
fastify.get('/headers', function (req, reply) {
reply
.header('set-cookie', 'one')
.header('set-cookie', 'two')
.send({})
})
const fastifyServer = await fastify.listen({ port: 0 })
t.after(() => fastify.close())
const result = await fetch(`${fastifyServer}/headers`)
t.assert.ok(result.ok)
t.assert.ok(result.headers.get('set-cookie'))
t.assert.deepStrictEqual(result.headers.getSetCookie(), ['one', 'two'])
})
test('reply.hasHeader returns correct values', async t => {
t.plan(2)
const fastify = require('../../')()
fastify.get('/headers', function (req, reply) {
reply.header('x-foo', 'foo')
t.assert.strictEqual(reply.hasHeader('x-foo'), true)
t.assert.strictEqual(reply.hasHeader('x-bar'), false)
reply.send()
})
const fastifyServer = await fastify.listen({ port: 0 })
t.after(() => fastify.close())
await fetch(`${fastifyServer}/headers`)
})
test('reply.getHeader returns correct values', async t => {
t.plan(4)
const fastify = require('../../')()
fastify.get('/headers', function (req, reply) {
reply.header('x-foo', 'foo')
t.assert.strictEqual(reply.getHeader('x-foo'), 'foo')
reply.header('x-foo', 'bar')
t.assert.deepStrictEqual(reply.getHeader('x-foo'), 'bar')
reply.header('x-foo', 42)
t.assert.deepStrictEqual(reply.getHeader('x-foo'), 42)
reply.header('set-cookie', 'one')
reply.header('set-cookie', 'two')
t.assert.deepStrictEqual(reply.getHeader('set-cookie'), ['one', 'two'])
reply.send()
})
const fastifyServer = await fastify.listen({ port: 0 })
t.after(() => fastify.close())
await fetch(`${fastifyServer}/headers`)
})
test('reply.getHeader returns raw header if there is not in the reply headers', (t) => {
t.plan(1)
const response = {
setHeader: () => { },
hasHeader: () => true,
getHeader: () => 'bar',
writeHead: () => { },
end: () => { }
}
const reply = new Reply(response, { onSend: [] }, null)
t.assert.strictEqual(reply.getHeader('foo'), 'bar')
})
test('reply.getHeaders returns correct values', (t, done) => {
t.plan(3)
const fastify = require('../../')()
fastify.get('/headers', function (req, reply) {
reply.header('x-foo', 'foo')
t.assert.deepStrictEqual(reply.getHeaders(), {
'x-foo': 'foo'
})
reply.header('x-bar', 'bar')
reply.raw.setHeader('x-foo', 'foo2')
reply.raw.setHeader('x-baz', 'baz')
t.assert.deepStrictEqual(reply.getHeaders(), {
'x-foo': 'foo',
'x-bar': 'bar',
'x-baz': 'baz'
})
reply.send()
})
fastify.inject('/headers', (err) => {
t.assert.ifError(err)
done()
})
})
test('reply.removeHeader can remove the value', async t => {
t.plan(3)
const fastify = require('../../')()
t.after(() => fastify.close())
fastify.get('/headers', function (req, reply) {
reply.header('x-foo', 'foo')
t.assert.strictEqual(reply.getHeader('x-foo'), 'foo')
t.assert.strictEqual(reply.removeHeader('x-foo'), reply)
t.assert.deepStrictEqual(reply.getHeader('x-foo'), undefined)
reply.send()
})
const fastifyServer = await fastify.listen({ port: 0 })
await fetch(`${fastifyServer}/headers`)
})
test('reply.header can reset the value', async t => {
t.plan(1)
const fastify = require('../../')()
t.after(() => fastify.close())
fastify.get('/headers', function (req, reply) {
reply.header('x-foo', 'foo')
reply.header('x-foo', undefined)
t.assert.deepStrictEqual(reply.getHeader('x-foo'), '')
reply.send()
})
const fastifyServer = await fastify.listen({ port: 0 })
await fetch(`${fastifyServer}/headers`)
})
// https://github.com/fastify/fastify/issues/3030
test('reply.hasHeader computes raw and fastify headers', async t => {
t.plan(2)
const fastify = require('../../')()
t.after(() => fastify.close())
fastify.get('/headers', function (req, reply) {
reply.header('x-foo', 'foo')
reply.raw.setHeader('x-bar', 'bar')
t.assert.ok(reply.hasHeader('x-foo'))
t.assert.ok(reply.hasHeader('x-bar'))
reply.send()
})
const fastifyServer = await fastify.listen({ port: 0 })
t.after(() => fastify.close())
await fetch(`${fastifyServer}/headers`)
})
test('Reply should handle JSON content type with a charset', async t => {
t.plan(8)
const fastify = require('../../')()
fastify.get('/default', function (req, reply) {
reply.send({ hello: 'world' })
})
fastify.get('/utf8', function (req, reply) {
reply
.header('content-type', 'application/json; charset=utf-8')
.send({ hello: 'world' })
})
fastify.get('/utf16', function (req, reply) {
reply
.header('content-type', 'application/json; charset=utf-16')
.send({ hello: 'world' })
})
fastify.get('/utf32', function (req, reply) {
reply
.header('content-type', 'application/json; charset=utf-32')
.send({ hello: 'world' })
})
fastify.get('/type-utf8', function (req, reply) {
reply
.type('application/json; charset=utf-8')
.send({ hello: 'world' })
})
fastify.get('/type-utf16', function (req, reply) {
reply
.type('application/json; charset=utf-16')
.send({ hello: 'world' })
})
fastify.get('/type-utf32', function (req, reply) {
reply
.type('application/json; charset=utf-32')
.send({ hello: 'world' })
})
fastify.get('/no-space-type-utf32', function (req, reply) {
reply
.type('application/json;charset=utf-32')
.send({ hello: 'world' })
})
{
const res = await fastify.inject('/default')
t.assert.strictEqual(res.headers['content-type'], 'application/json; charset=utf-8')
}
{
const res = await fastify.inject('/utf8')
t.assert.strictEqual(res.headers['content-type'], 'application/json; charset=utf-8')
}
{
const res = await fastify.inject('/utf16')
t.assert.strictEqual(res.headers['content-type'], 'application/json; charset=utf-16')
}
{
const res = await fastify.inject('/utf32')
t.assert.strictEqual(res.headers['content-type'], 'application/json; charset=utf-32')
}
{
const res = await fastify.inject('/type-utf8')
t.assert.strictEqual(res.headers['content-type'], 'application/json; charset=utf-8')
}
{
const res = await fastify.inject('/type-utf16')
t.assert.strictEqual(res.headers['content-type'], 'application/json; charset=utf-16')
}
{
const res = await fastify.inject('/type-utf32')
t.assert.strictEqual(res.headers['content-type'], 'application/json; charset=utf-32')
}
{
const res = await fastify.inject('/no-space-type-utf32')
t.assert.strictEqual(res.headers['content-type'], 'application/json;charset=utf-32')
}
})
test('Content type and charset set previously', (t, done) => {
t.plan(2)
const fastify = require('../../')()
fastify.addHook('onRequest', function (req, reply, done) {
reply.header('content-type', 'application/json; charset=utf-16')
done()
})
fastify.get('/', function (req, reply) {
reply.send({ hello: 'world' })
})
fastify.inject('/', (err, res) => {
t.assert.ifError(err)
t.assert.strictEqual(res.headers['content-type'], 'application/json; charset=utf-16')
done()
})
})
test('.status() is an alias for .code()', (t, done) => {
t.plan(2)
const fastify = Fastify()
fastify.get('/', function (req, reply) {
reply.status(418).send()
})
fastify.inject('/', (err, res) => {
t.assert.ifError(err)
t.assert.strictEqual(res.statusCode, 418)
done()
})
})
test('.statusCode is getter and setter', (t, done) => {
t.plan(4)
const fastify = Fastify()
fastify.get('/', function (req, reply) {
t.assert.strictEqual(reply.statusCode, 200, 'default status value')
reply.statusCode = 418
t.assert.strictEqual(reply.statusCode, 418)
reply.send()
})
fastify.inject('/', (err, res) => {
t.assert.ifError(err)
t.assert.strictEqual(res.statusCode, 418)
done()
})
})
test('reply.header setting multiple cookies as multiple Set-Cookie headers', async t => {
t.plan(5)
const fastify = require('../../')()
t.after(() => fastify.close())
fastify.get('/headers', function (req, reply) {
reply
.header('set-cookie', 'one')
.header('set-cookie', 'two')
.header('set-cookie', 'three')
.header('set-cookie', ['four', 'five', 'six'])
.send({})
})
const fastifyServer = await fastify.listen({ port: 0 })
const result = await fetch(`${fastifyServer}/headers`)
t.assert.ok(result.ok)
t.assert.ok(result.headers.get('set-cookie'))
t.assert.deepStrictEqual(result.headers.getSetCookie(), ['one', 'two', 'three', 'four', 'five', 'six'])
const response = await fastify.inject('/headers')
t.assert.ok(response.headers['set-cookie'])
t.assert.deepStrictEqual(response.headers['set-cookie'], ['one', 'two', 'three', 'four', 'five', 'six'])
})
test('should throw when trying to modify the reply.sent property', (t, done) => {
t.plan(3)
const fastify = Fastify()
fastify.get('/', function (req, reply) {
try {
reply.sent = true
} catch (err) {
t.assert.ok(err)
reply.send()
}
})
fastify.inject('/', (err, res) => {
t.assert.ifError(err)
t.assert.ok(true)
done()
})
})
test('reply.elapsedTime should return 0 before the timer is initialised on the reply by setting up response listeners', t => {
t.plan(1)
const response = { statusCode: 200 }
const reply = new Reply(response, null)
t.assert.strictEqual(reply.elapsedTime, 0)
})
test('reply.elapsedTime should return a number greater than 0 after the timer is initialised on the reply by setting up response listeners', async t => {
t.plan(1)
const fastify = Fastify()
fastify.route({
method: 'GET',
url: '/',
handler: (req, reply) => {
reply.send('hello world')
}
})
fastify.addHook('onResponse', (req, reply) => {
t.assert.ok(reply.elapsedTime > 0)
})
await fastify.inject({ method: 'GET', url: '/' })
})
test('reply.elapsedTime should return the time since a request started while inflight', async t => {
t.plan(1)
const fastify = Fastify()
fastify.route({
method: 'GET',
url: '/',
handler: (req, reply) => {
reply.send('hello world')
}
})
let preValidationElapsedTime
fastify.addHook('preValidation', (req, reply, done) => {
preValidationElapsedTime = reply.elapsedTime
done()
})
fastify.addHook('onResponse', (req, reply) => {
t.assert.ok(reply.elapsedTime > preValidationElapsedTime)
})
await fastify.inject({ method: 'GET', url: '/' })
})
test('reply.elapsedTime should return the same value after a request is finished', async t => {
t.plan(1)
const fastify = Fastify()
fastify.route({
method: 'GET',
url: '/',
handler: (req, reply) => {
reply.send('hello world')
}
})
fastify.addHook('onResponse', (req, reply) => {
t.assert.strictEqual(reply.elapsedTime, reply.elapsedTime)
})
await fastify.inject({ method: 'GET', url: '/' })
})
test('reply should use the custom serializer', (t, done) => {
t.plan(4)
const fastify = Fastify()
fastify.setReplySerializer((payload, statusCode) => {
t.assert.deepStrictEqual(payload, { foo: 'bar' })
t.assert.strictEqual(statusCode, 200)
payload.foo = 'bar bar'
return JSON.stringify(payload)
})
fastify.route({
method: 'GET',
url: '/',
handler: (req, reply) => {
reply.send({ foo: 'bar' })
}
})
fastify.inject({
method: 'GET',
url: '/'
}, (err, res) => {
t.assert.ifError(err)
t.assert.strictEqual(res.payload, '{"foo":"bar bar"}')
done()
})
})
test('reply should use the right serializer in encapsulated context', async t => {
t.plan(6)
const fastify = Fastify()
fastify.setReplySerializer((payload) => {
t.assert.deepStrictEqual(payload, { foo: 'bar' })
payload.foo = 'bar bar'
return JSON.stringify(payload)
})
fastify.route({
method: 'GET',
url: '/',
handler: (req, reply) => { reply.send({ foo: 'bar' }) }
})
fastify.register(function (instance, opts, done) {
instance.route({
method: 'GET',
url: '/sub',
handler: (req, reply) => { reply.send({ john: 'doo' }) }
})
instance.setReplySerializer((payload) => {
t.assert.deepStrictEqual(payload, { john: 'doo' })
payload.john = 'too too'
return JSON.stringify(payload)
})
done()
})
fastify.register(function (instance, opts, done) {
instance.route({
method: 'GET',
url: '/sub',
handler: (req, reply) => { reply.send({ sweet: 'potato' }) }
})
instance.setReplySerializer((payload) => {
t.assert.deepStrictEqual(payload, { sweet: 'potato' })
payload.sweet = 'potato potato'
return JSON.stringify(payload)
})
done()
}, { prefix: 'sub' })
{
const res = await fastify.inject('/')
t.assert.strictEqual(res.payload, '{"foo":"bar bar"}')
}
{
const res = await fastify.inject('/sub')
t.assert.strictEqual(res.payload, '{"john":"too too"}')
}
{
const res = await fastify.inject('/sub/sub')
t.assert.strictEqual(res.payload, '{"sweet":"potato potato"}')
}
})
test('reply should use the right serializer in deep encapsulated context', async t => {
t.plan(5)
const fastify = Fastify()
fastify.route({
method: 'GET',
url: '/',
handler: (req, reply) => { reply.send({ foo: 'bar' }) }
})
fastify.register(function (instance, opts, done) {
instance.route({
method: 'GET',
url: '/sub',
handler: (req, reply) => { reply.send({ john: 'doo' }) }
})
instance.setReplySerializer((payload) => {
t.assert.deepStrictEqual(payload, { john: 'doo' })
payload.john = 'too too'
return JSON.stringify(payload)
})
instance.register(function (subInstance, opts, done) {
subInstance.route({
method: 'GET',
url: '/deep',
handler: (req, reply) => { reply.send({ john: 'deep' }) }
})
subInstance.setReplySerializer((payload) => {
t.assert.deepStrictEqual(payload, { john: 'deep' })
payload.john = 'deep deep'
return JSON.stringify(payload)
})
done()
})
done()
})
{
const res = await fastify.inject('/')
t.assert.strictEqual(res.payload, '{"foo":"bar"}')
}
{
const res = await fastify.inject('/sub')
t.assert.strictEqual(res.payload, '{"john":"too too"}')
}
{
const res = await fastify.inject('/deep')
t.assert.strictEqual(res.payload, '{"john":"deep deep"}')
}
})
test('reply should use the route serializer', (t, done) => {
t.plan(3)
const fastify = Fastify()
fastify.setReplySerializer(() => {
t.fail('this serializer should not be executed')
})
fastify.route({
method: 'GET',
url: '/',
handler: (req, reply) => {
reply
.serializer((payload) => {
t.assert.deepStrictEqual(payload, { john: 'doo' })
payload.john = 'too too'
return JSON.stringify(payload)
})
.send({ john: 'doo' })
}
})
fastify.inject({
method: 'GET',
url: '/'
}, (err, res) => {
t.assert.ifError(err)
t.assert.strictEqual(res.payload, '{"john":"too too"}')
done()
})
})
test('cannot set the replySerializer when the server is running', (t, done) => {
t.plan(2)
const fastify = Fastify()
t.after(() => fastify.close())
fastify.listen({ port: 0 }, err => {
t.assert.ifError(err)
try {
fastify.setReplySerializer(() => { })
t.assert.fail('this serializer should not be setup')
} catch (e) {
t.assert.strictEqual(e.code, 'FST_ERR_INSTANCE_ALREADY_LISTENING')
} finally {
done()
}
})
})
test('reply should not call the custom serializer for errors and not found', async t => {
t.plan(6)
const fastify = Fastify()
fastify.setReplySerializer((payload, statusCode) => {
t.assert.deepStrictEqual(payload, { foo: 'bar' })
t.assert.strictEqual(statusCode, 200)
return JSON.stringify(payload)
})
fastify.get('/', (req, reply) => { reply.send({ foo: 'bar' }) })
fastify.get('/err', (req, reply) => { reply.send(new Error('an error')) })
{
const res = await fastify.inject('/')
t.assert.strictEqual(res.statusCode, 200)
t.assert.strictEqual(res.payload, '{"foo":"bar"}')
}
{
const res = await fastify.inject('/err')
t.assert.strictEqual(res.statusCode, 500)
}
{
const res = await fastify.inject('/not-existing')
t.assert.strictEqual(res.statusCode, 404)
}
})
test('reply.then', async t => {
t.plan(4)
function request () { }
await t.test('without an error', (t, done) => {
t.plan(1)
const response = new Writable()
const reply = new Reply(response, request)
reply.then(function () {
t.assert.ok(true)
done()
})
response.destroy()
})
await t.test('with an error', (t, done) => {
t.plan(1)
const response = new Writable()
const reply = new Reply(response, request)
const _err = new Error('kaboom')
reply.then(function () {
t.assert.fail('fulfilled called')
}, function (err) {
t.assert.strictEqual(err, _err)
done()
})
response.destroy(_err)
})
await t.test('with error but without reject callback', t => {
t.plan(1)
const response = new Writable()
const reply = new Reply(response, request)
const _err = new Error('kaboom')
reply.then(function () {
t.assert.fail('fulfilled called')
})
t.assert.ok(true)
response.destroy(_err)
})
await t.test('with error, without reject callback, with logger', (t, done) => {
t.plan(1)
const response = new Writable()
const reply = new Reply(response, request)
// spy logger
reply.log = {
warn: (message) => {
t.assert.strictEqual(message, 'unhandled rejection on reply.then')
done()
}
}
const _err = new Error('kaboom')
reply.then(function () {
t.assert.fail('fulfilled called')
})
response.destroy(_err)
})
})
test('reply.sent should read from response.writableEnded if it is defined', t => {
t.plan(1)
const reply = new Reply({ writableEnded: true }, {}, {})
t.assert.strictEqual(reply.sent, true)
})
test('redirect to an invalid URL should not crash the server', async t => {
const fastify = Fastify()
fastify.route({
method: 'GET',
url: '/redirect',
handler: (req, reply) => {
reply.log.warn = function mockWarn (obj, message) {
t.assert.strictEqual(message, 'Invalid character in header content ["location"]')
}
switch (req.query.useCase) {
case '1':
reply.redirect('/?key=ab')
break
case '2':
reply.redirect(encodeURI('/?key=ab'))
break
default:
reply.redirect('/?key=ab')
break
}
}
})
const fastifyServer = await fastify.listen({ port: 0 })
{
const { response, body } = await doGet(`${fastifyServer}/redirect?useCase=1`)
t.assert.strictEqual(response.status, 500)
t.assert.deepStrictEqual(body, {
statusCode: 500,
code: 'ERR_INVALID_CHAR',
error: 'Internal Server Error',
message: 'Invalid character in header content ["location"]'
})
}
{
const { response } = await doGet(`${fastifyServer}/redirect?useCase=2`)
t.assert.strictEqual(response.status, 302)
t.assert.strictEqual(response.headers.get('location'), '/?key=a%E2%80%99b')
}
{
const { response } = await doGet(`${fastifyServer}/redirect?useCase=3`)
t.assert.strictEqual(response.status, 302)
t.assert.strictEqual(response.headers.get('location'), '/?key=ab')
}
await fastify.close()
})
test('invalid response headers should not crash the server', async t => {
const fastify = Fastify()
fastify.route({
method: 'GET',
url: '/bad-headers',
handler: (req, reply) => {
reply.log.warn = function mockWarn (obj, message) {
t.assert.strictEqual(message, 'Invalid character in header content ["smile-encoded"]', 'only the first invalid header is logged')
}
reply.header('foo', '$')
reply.header('smile-encoded', '\uD83D\uDE00')
reply.header('smile', '😄')
reply.header('bar', 'ƒ∂å')
reply.send({})
}
})
const fastifyServer = await fastify.listen({ port: 0 })
const { response, body } = await doGet(`${fastifyServer}/bad-headers`)
t.assert.strictEqual(response.status, 500)
t.assert.deepStrictEqual(body, {
statusCode: 500,
code: 'ERR_INVALID_CHAR',
error: 'Internal Server Error',
message: 'Invalid character in header content ["smile-encoded"]'
})
await fastify.close()
})
test('invalid response headers when sending back an error', async t => {
const fastify = Fastify()
fastify.route({
method: 'GET',
url: '/bad-headers',
handler: (req, reply) => {
reply.log.warn = function mockWarn (obj, message) {
t.assert.strictEqual(message, 'Invalid character in header content ["smile"]', 'only the first invalid header is logged')
}
reply.header('smile', '😄')
reply.send(new Error('user land error'))
}
})
const fastifyServer = await fastify.listen({ port: 0 })
const { response, body } = await doGet(`${fastifyServer}/bad-headers`)
t.assert.strictEqual(response.status, 500)
t.assert.deepStrictEqual(body, {
statusCode: 500,
code: 'ERR_INVALID_CHAR',
error: 'Internal Server Error',
message: 'Invalid character in header content ["smile"]'
})
await fastify.close()
})
test('invalid response headers and custom error handler', async t => {
const fastify = Fastify()
fastify.route({
method: 'GET',
url: '/bad-headers',
handler: (req, reply) => {
reply.log.warn = function mockWarn (obj, message) {
t.assert.strictEqual(message, 'Invalid character in header content ["smile"]', 'only the first invalid header is logged')
}
reply.header('smile', '😄')
reply.send(new Error('user land error'))
}
})
fastify.setErrorHandler(function (error, request, reply) {
t.assert.strictEqual(error.message, 'user land error', 'custom error handler receives the error')
reply.status(500).send({ ops: true })
})
const fastifyServer = await fastify.listen({ port: 0 })
const { response, body } = await doGet(`${fastifyServer}/bad-headers`)
t.assert.strictEqual(response.status, 500)
t.assert.deepStrictEqual(body, {
statusCode: 500,
code: 'ERR_INVALID_CHAR',
error: 'Internal Server Error',
message: 'Invalid character in header content ["smile"]'
})
await fastify.close()
})
test('reply.send will intercept ERR_HTTP_HEADERS_SENT and log an error message', t => {
t.plan(2)
const response = new Writable()
Object.assign(response, {
setHeader: () => { },
hasHeader: () => false,
getHeader: () => undefined,
writeHead: () => {
const err = new Error('kaboom')
err.code = 'ERR_HTTP_HEADERS_SENT'
throw err
},
write: () => { },
headersSent: true
})
const log = {
warn: (msg) => {
t.assert.strictEqual(msg, 'Reply was already sent, did you forget to "return reply" in the "/hello" (GET) route?')
}
}
const reply = new Reply(response, { [kRouteContext]: { onSend: null }, raw: { url: '/hello', method: 'GET' } }, log)
try {
reply.send('')
} catch (err) {
t.assert.strictEqual(err.code, 'ERR_HTTP_HEADERS_SENT')
}
})
test('Uint8Array view of ArrayBuffer returns correct byteLength', (t, done) => {
t.plan(5)
const fastify = Fastify()
const arrBuf = new ArrayBuffer(100)
const arrView = new Uint8Array(arrBuf, 0, 10)
fastify.get('/', function (req, reply) {
return reply.send(arrView)
})
fastify.listen({ port: 0 }, err => {
t.assert.ifError(err)
t.after(() => fastify.close())
fastify.inject({
method: 'GET',
url: '/'
}, (err, response) => {
t.assert.ifError(err)
t.assert.strictEqual(response.headers['content-type'], 'application/octet-stream')
t.assert.strictEqual(response.headers['content-length'], '10')
t.assert.deepStrictEqual(response.rawPayload.byteLength, arrView.byteLength)
done()
})
})
})