strong-tie/inbound-calls
0
1'use strict'2 3const { test } = require('tap')4const split = require('split2')5const net = require('node:net')6const Fastify = require('../fastify')7 8process.removeAllListeners('warning')9 10const lifecycleHooks = [11 'onRequest',12 'preParsing',13 'preValidation',14 'preHandler',15 'preSerialization',16 'onSend',17 'onTimeout',18 'onResponse',19 'onError'20]21 22test('skip automatic reply.send() with reply.hijack and a body', (t) => {23 const stream = split(JSON.parse)24 const app = Fastify({25 logger: {26 stream27 }28 })29 30 stream.on('data', (line) => {31 t.not(line.level, 40) // there are no errors32 t.not(line.level, 50) // there are no errors33 })34 35 app.get('/', (req, reply) => {36 reply.hijack()37 reply.raw.end('hello world')38 39 return Promise.resolve('this will be skipped')40 })41 42 return app.inject({43 method: 'GET',44 url: '/'45 }).then((res) => {46 t.equal(res.statusCode, 200)47 t.equal(res.body, 'hello world')48 })49})50 51test('skip automatic reply.send() with reply.hijack and no body', (t) => {52 const stream = split(JSON.parse)53 const app = Fastify({54 logger: {55 stream56 }57 })58 59 stream.on('data', (line) => {60 t.not(line.level, 40) // there are no error61 t.not(line.level, 50) // there are no error62 })63 64 app.get('/', (req, reply) => {65 reply.hijack()66 reply.raw.end('hello world')67 68 return Promise.resolve()69 })70 71 return app.inject({72 method: 'GET',73 url: '/'74 }).then((res) => {75 t.equal(res.statusCode, 200)76 t.equal(res.body, 'hello world')77 })78})79 80test('skip automatic reply.send() with reply.hijack and an error', (t) => {81 const stream = split(JSON.parse)82 const app = Fastify({83 logger: {84 stream85 }86 })87 88 let errorSeen = false89 90 stream.on('data', (line) => {91 if (line.level === 50) {92 errorSeen = true93 t.equal(line.err.message, 'kaboom')94 t.equal(line.msg, 'Promise errored, but reply.sent = true was set')95 }96 })97 98 app.get('/', (req, reply) => {99 reply.hijack()100 reply.raw.end('hello world')101 102 return Promise.reject(new Error('kaboom'))103 })104 105 return app.inject({106 method: 'GET',107 url: '/'108 }).then((res) => {109 t.equal(errorSeen, true)110 t.equal(res.statusCode, 200)111 t.equal(res.body, 'hello world')112 })113})114 115function testHandlerOrBeforeHandlerHook (test, hookOrHandler) {116 const idx = hookOrHandler === 'handler' ? lifecycleHooks.indexOf('preHandler') : lifecycleHooks.indexOf(hookOrHandler)117 const previousHooks = lifecycleHooks.slice(0, idx)118 const nextHooks = lifecycleHooks.slice(idx + 1)119 120 test(`Hijacking inside ${hookOrHandler} skips all the following hooks and handler execution`, t => {121 t.plan(4)122 const test = t.test123 124 test('Sending a response using reply.raw => onResponse hook is called', t => {125 const stream = split(JSON.parse)126 const app = Fastify({127 logger: {128 stream129 }130 })131 132 stream.on('data', (line) => {133 t.not(line.level, 40) // there are no errors134 t.not(line.level, 50) // there are no errors135 })136 137 previousHooks.forEach(h => app.addHook(h, async (req, reply) => t.pass(`${h} should be called`)))138 139 if (hookOrHandler === 'handler') {140 app.get('/', (req, reply) => {141 reply.hijack()142 reply.raw.end(`hello from ${hookOrHandler}`)143 })144 } else {145 app.addHook(hookOrHandler, async (req, reply) => {146 reply.hijack()147 reply.raw.end(`hello from ${hookOrHandler}`)148 })149 app.get('/', (req, reply) => t.fail('Handler should not be called'))150 }151 152 nextHooks.forEach(h => {153 if (h === 'onResponse') {154 app.addHook(h, async (req, reply) => t.pass(`${h} should be called`))155 } else {156 app.addHook(h, async (req, reply) => t.fail(`${h} should not be called`))157 }158 })159 160 return app.inject({161 method: 'GET',162 url: '/'163 }).then((res) => {164 t.equal(res.statusCode, 200)165 t.equal(res.body, `hello from ${hookOrHandler}`)166 })167 })168 169 test('Sending a response using req.socket => onResponse not called', t => {170 const stream = split(JSON.parse)171 const app = Fastify({172 logger: {173 stream174 }175 })176 t.teardown(() => app.close())177 178 stream.on('data', (line) => {179 t.not(line.level, 40) // there are no errors180 t.not(line.level, 50) // there are no errors181 })182 183 previousHooks.forEach(h => app.addHook(h, async (req, reply) => t.pass(`${h} should be called`)))184 185 if (hookOrHandler === 'handler') {186 app.get('/', (req, reply) => {187 reply.hijack()188 req.socket.write('HTTP/1.1 200 OK\r\n\r\n')189 req.socket.write(`hello from ${hookOrHandler}`)190 req.socket.end()191 })192 } else {193 app.addHook(hookOrHandler, async (req, reply) => {194 reply.hijack()195 req.socket.write('HTTP/1.1 200 OK\r\n\r\n')196 req.socket.write(`hello from ${hookOrHandler}`)197 req.socket.end()198 })199 app.get('/', (req, reply) => t.fail('Handler should not be called'))200 }201 202 nextHooks.forEach(h => app.addHook(h, async (req, reply) => t.fail(`${h} should not be called`)))203 204 app.listen({ port: 0 }, err => {205 t.error(err)206 const client = net.createConnection({ port: (app.server.address()).port }, () => {207 client.write('GET / HTTP/1.1\r\nHost: example.com\r\n\r\n')208 209 let chunks = ''210 client.setEncoding('utf8')211 client.on('data', data => {212 chunks += data213 })214 215 client.on('end', function () {216 t.match(chunks, new RegExp(`hello from ${hookOrHandler}`, 'i'))217 t.end()218 })219 })220 })221 })222 223 test('Throwing an error does not trigger any hooks', t => {224 const stream = split(JSON.parse)225 const app = Fastify({226 logger: {227 stream228 }229 })230 t.teardown(() => app.close())231 232 let errorSeen = false233 stream.on('data', (line) => {234 if (hookOrHandler === 'handler') {235 if (line.level === 40) {236 errorSeen = true237 t.equal(line.err.code, 'FST_ERR_REP_ALREADY_SENT')238 }239 } else {240 t.not(line.level, 40) // there are no errors241 t.not(line.level, 50) // there are no errors242 }243 })244 245 previousHooks.forEach(h => app.addHook(h, async (req, reply) => t.pass(`${h} should be called`)))246 247 if (hookOrHandler === 'handler') {248 app.get('/', (req, reply) => {249 reply.hijack()250 throw new Error('This wil be skipped')251 })252 } else {253 app.addHook(hookOrHandler, async (req, reply) => {254 reply.hijack()255 throw new Error('This wil be skipped')256 })257 app.get('/', (req, reply) => t.fail('Handler should not be called'))258 }259 260 nextHooks.forEach(h => app.addHook(h, async (req, reply) => t.fail(`${h} should not be called`)))261 262 return Promise.race([263 app.inject({ method: 'GET', url: '/' }),264 new Promise((resolve, reject) => setTimeout(resolve, 1000))265 ]).then((err, res) => {266 t.error(err)267 if (hookOrHandler === 'handler') {268 t.equal(errorSeen, true)269 }270 })271 })272 273 test('Calling reply.send() after hijacking logs a warning', t => {274 const stream = split(JSON.parse)275 const app = Fastify({276 logger: {277 stream278 }279 })280 281 let errorSeen = false282 283 stream.on('data', (line) => {284 if (line.level === 40) {285 errorSeen = true286 t.equal(line.err.code, 'FST_ERR_REP_ALREADY_SENT')287 }288 })289 290 previousHooks.forEach(h => app.addHook(h, async (req, reply) => t.pass(`${h} should be called`)))291 292 if (hookOrHandler === 'handler') {293 app.get('/', (req, reply) => {294 reply.hijack()295 reply.send('hello from reply.send()')296 })297 } else {298 app.addHook(hookOrHandler, async (req, reply) => {299 reply.hijack()300 return reply.send('hello from reply.send()')301 })302 app.get('/', (req, reply) => t.fail('Handler should not be called'))303 }304 305 nextHooks.forEach(h => app.addHook(h, async (req, reply) => t.fail(`${h} should not be called`)))306 307 return Promise.race([308 app.inject({ method: 'GET', url: '/' }),309 new Promise((resolve, reject) => setTimeout(resolve, 1000))310 ]).then((err, res) => {311 t.error(err)312 t.equal(errorSeen, true)313 })314 })315 })316}317 318testHandlerOrBeforeHandlerHook(test, 'onRequest')319testHandlerOrBeforeHandlerHook(test, 'preParsing')320testHandlerOrBeforeHandlerHook(test, 'preValidation')321testHandlerOrBeforeHandlerHook(test, 'preHandler')322testHandlerOrBeforeHandlerHook(test, 'handler')323 