strong-tie/inbound-calls
0
1'use strict'2 3const t = require('tap')4const test = t.test5const Fastify = require('..')6const { Readable } = require('node:stream')7const { createHash } = require('node:crypto')8const { sleep } = require('./helper')9 10test('send trailers when payload is empty string', t => {11 t.plan(5)12 13 const fastify = Fastify()14 15 fastify.get('/', function (request, reply) {16 reply.trailer('ETag', function (reply, payload, done) {17 done(null, 'custom-etag')18 })19 reply.send('')20 })21 22 fastify.inject({23 method: 'GET',24 url: '/'25 }, (error, res) => {26 t.error(error)27 t.equal(res.statusCode, 200)28 t.equal(res.headers.trailer, 'etag')29 t.equal(res.trailers.etag, 'custom-etag')30 t.notHas(res.headers, 'content-length')31 })32})33 34test('send trailers when payload is empty buffer', t => {35 t.plan(5)36 37 const fastify = Fastify()38 39 fastify.get('/', function (request, reply) {40 reply.trailer('ETag', function (reply, payload, done) {41 done(null, 'custom-etag')42 })43 reply.send(Buffer.alloc(0))44 })45 46 fastify.inject({47 method: 'GET',48 url: '/'49 }, (error, res) => {50 t.error(error)51 t.equal(res.statusCode, 200)52 t.equal(res.headers.trailer, 'etag')53 t.equal(res.trailers.etag, 'custom-etag')54 t.notHas(res.headers, 'content-length')55 })56})57 58test('send trailers when payload is undefined', t => {59 t.plan(5)60 61 const fastify = Fastify()62 63 fastify.get('/', function (request, reply) {64 reply.trailer('ETag', function (reply, payload, done) {65 done(null, 'custom-etag')66 })67 reply.send(undefined)68 })69 70 fastify.inject({71 method: 'GET',72 url: '/'73 }, (error, res) => {74 t.error(error)75 t.equal(res.statusCode, 200)76 t.equal(res.headers.trailer, 'etag')77 t.equal(res.trailers.etag, 'custom-etag')78 t.notHas(res.headers, 'content-length')79 })80})81 82test('send trailers when payload is json', t => {83 t.plan(7)84 85 const fastify = Fastify()86 const data = JSON.stringify({ hello: 'world' })87 const hash = createHash('md5')88 hash.update(data)89 const md5 = hash.digest('hex')90 91 fastify.get('/', function (request, reply) {92 reply.trailer('Content-MD5', function (reply, payload, done) {93 t.equal(data, payload)94 const hash = createHash('md5')95 hash.update(payload)96 done(null, hash.digest('hex'))97 })98 reply.send(data)99 })100 101 fastify.inject({102 method: 'GET',103 url: '/'104 }, (error, res) => {105 t.error(error)106 t.equal(res.statusCode, 200)107 t.equal(res.headers['transfer-encoding'], 'chunked')108 t.equal(res.headers.trailer, 'content-md5')109 t.equal(res.trailers['content-md5'], md5)110 t.notHas(res.headers, 'content-length')111 })112})113 114test('send trailers when payload is stream', t => {115 t.plan(7)116 117 const fastify = Fastify()118 119 fastify.get('/', function (request, reply) {120 reply.trailer('ETag', function (reply, payload, done) {121 t.same(payload, null)122 done(null, 'custom-etag')123 })124 const stream = Readable.from([JSON.stringify({ hello: 'world' })])125 reply.send(stream)126 })127 128 fastify.inject({129 method: 'GET',130 url: '/'131 }, (error, res) => {132 t.error(error)133 t.equal(res.statusCode, 200)134 t.equal(res.headers['transfer-encoding'], 'chunked')135 t.equal(res.headers.trailer, 'etag')136 t.equal(res.trailers.etag, 'custom-etag')137 t.notHas(res.headers, 'content-length')138 })139})140 141test('send trailers when using async-await', t => {142 t.plan(5)143 144 const fastify = Fastify()145 146 fastify.get('/', function (request, reply) {147 reply.trailer('ETag', async function (reply, payload) {148 return 'custom-etag'149 })150 reply.send('')151 })152 153 fastify.inject({154 method: 'GET',155 url: '/'156 }, (error, res) => {157 t.error(error)158 t.equal(res.statusCode, 200)159 t.equal(res.headers.trailer, 'etag')160 t.equal(res.trailers.etag, 'custom-etag')161 t.notHas(res.headers, 'content-length')162 })163})164 165test('error in trailers should be ignored', t => {166 t.plan(5)167 168 const fastify = Fastify()169 170 fastify.get('/', function (request, reply) {171 reply.trailer('ETag', function (reply, payload, done) {172 done('error')173 })174 reply.send('')175 })176 177 fastify.inject({178 method: 'GET',179 url: '/'180 }, (error, res) => {181 t.error(error)182 t.equal(res.statusCode, 200)183 t.equal(res.headers.trailer, 'etag')184 t.notHas(res.trailers, 'etag')185 t.notHas(res.headers, 'content-length')186 })187})188 189test('trailer handler counter', t => {190 t.plan(2)191 192 const data = JSON.stringify({ hello: 'world' })193 const hash = createHash('md5')194 hash.update(data)195 const md5 = hash.digest('hex')196 197 t.test('callback with timeout', t => {198 t.plan(9)199 const fastify = Fastify()200 201 fastify.get('/', function (request, reply) {202 reply.trailer('Return-Early', function (reply, payload, done) {203 t.equal(data, payload)204 done(null, 'return')205 })206 reply.trailer('Content-MD5', function (reply, payload, done) {207 t.equal(data, payload)208 const hash = createHash('md5')209 hash.update(payload)210 setTimeout(() => {211 done(null, hash.digest('hex'))212 }, 500)213 })214 reply.send(data)215 })216 217 fastify.inject({218 method: 'GET',219 url: '/'220 }, (error, res) => {221 t.error(error)222 t.equal(res.statusCode, 200)223 t.equal(res.headers['transfer-encoding'], 'chunked')224 t.equal(res.headers.trailer, 'return-early content-md5')225 t.equal(res.trailers['return-early'], 'return')226 t.equal(res.trailers['content-md5'], md5)227 t.notHas(res.headers, 'content-length')228 })229 })230 231 t.test('async-await', t => {232 t.plan(9)233 const fastify = Fastify()234 235 fastify.get('/', function (request, reply) {236 reply.trailer('Return-Early', async function (reply, payload) {237 t.equal(data, payload)238 return 'return'239 })240 reply.trailer('Content-MD5', async function (reply, payload) {241 t.equal(data, payload)242 const hash = createHash('md5')243 hash.update(payload)244 await sleep(500)245 return hash.digest('hex')246 })247 reply.send(data)248 })249 250 fastify.inject({251 method: 'GET',252 url: '/'253 }, (error, res) => {254 t.error(error)255 t.equal(res.statusCode, 200)256 t.equal(res.headers['transfer-encoding'], 'chunked')257 t.equal(res.headers.trailer, 'return-early content-md5')258 t.equal(res.trailers['return-early'], 'return')259 t.equal(res.trailers['content-md5'], md5)260 t.notHas(res.headers, 'content-length')261 })262 })263})264 265test('removeTrailer', t => {266 t.plan(6)267 268 const fastify = Fastify()269 270 fastify.get('/', function (request, reply) {271 reply.removeTrailer('ETag') // remove nothing272 reply.trailer('ETag', function (reply, payload, done) {273 done(null, 'custom-etag')274 })275 reply.trailer('Should-Not-Call', function (reply, payload, done) {276 t.fail('it should not called as this trailer is removed')277 done(null, 'should-not-call')278 })279 reply.removeTrailer('Should-Not-Call')280 reply.send(undefined)281 })282 283 fastify.inject({284 method: 'GET',285 url: '/'286 }, (error, res) => {287 t.error(error)288 t.equal(res.statusCode, 200)289 t.equal(res.headers.trailer, 'etag')290 t.equal(res.trailers.etag, 'custom-etag')291 t.notOk(res.trailers['should-not-call'])292 t.notHas(res.headers, 'content-length')293 })294})295 296test('remove all trailers', t => {297 t.plan(6)298 299 const fastify = Fastify()300 301 fastify.get('/', function (request, reply) {302 reply.trailer('ETag', function (reply, payload, done) {303 t.fail('it should not called as this trailer is removed')304 done(null, 'custom-etag')305 })306 reply.removeTrailer('ETag')307 reply.trailer('Should-Not-Call', function (reply, payload, done) {308 t.fail('it should not called as this trailer is removed')309 done(null, 'should-not-call')310 })311 reply.removeTrailer('Should-Not-Call')312 reply.send('')313 })314 315 fastify.inject({316 method: 'GET',317 url: '/'318 }, (error, res) => {319 t.error(error)320 t.equal(res.statusCode, 200)321 t.notOk(res.headers.trailer)322 t.notOk(res.trailers.etag)323 t.notOk(res.trailers['should-not-call'])324 t.notHas(res.headers, 'content-length')325 })326})327 328test('hasTrailer', t => {329 t.plan(10)330 331 const fastify = Fastify()332 333 fastify.get('/', function (request, reply) {334 t.equal(reply.hasTrailer('ETag'), false)335 reply.trailer('ETag', function (reply, payload, done) {336 done(null, 'custom-etag')337 })338 t.equal(reply.hasTrailer('ETag'), true)339 reply.trailer('Should-Not-Call', function (reply, payload, done) {340 t.fail('it should not called as this trailer is removed')341 done(null, 'should-not-call')342 })343 t.equal(reply.hasTrailer('Should-Not-Call'), true)344 reply.removeTrailer('Should-Not-Call')345 t.equal(reply.hasTrailer('Should-Not-Call'), false)346 reply.send(undefined)347 })348 349 fastify.inject({350 method: 'GET',351 url: '/'352 }, (error, res) => {353 t.error(error)354 t.equal(res.statusCode, 200)355 t.equal(res.headers.trailer, 'etag')356 t.equal(res.trailers.etag, 'custom-etag')357 t.notOk(res.trailers['should-not-call'])358 t.notHas(res.headers, 'content-length')359 })360})361 362test('throw error when trailer header name is not allowed', t => {363 const INVALID_TRAILERS = [364 'transfer-encoding',365 'content-length',366 'host',367 'cache-control',368 'max-forwards',369 'te',370 'authorization',371 'set-cookie',372 'content-encoding',373 'content-type',374 'content-range',375 'trailer'376 ]377 t.plan(INVALID_TRAILERS.length + 2)378 379 const fastify = Fastify()380 381 fastify.get('/', function (request, reply) {382 for (const key of INVALID_TRAILERS) {383 try {384 reply.trailer(key, () => { })385 } catch (err) {386 t.equal(err.message, `Called reply.trailer with an invalid header name: ${key}`)387 }388 }389 reply.send('')390 })391 392 fastify.inject({393 method: 'GET',394 url: '/'395 }, (error, res) => {396 t.error(error)397 t.equal(res.statusCode, 200)398 })399})400 401test('throw error when trailer header value is not function', t => {402 const INVALID_TRAILERS_VALUE = [403 undefined,404 null,405 true,406 false,407 'invalid',408 [],409 new Date(),410 {}411 ]412 t.plan(INVALID_TRAILERS_VALUE.length + 2)413 414 const fastify = Fastify()415 416 fastify.get('/', function (request, reply) {417 for (const value of INVALID_TRAILERS_VALUE) {418 try {419 reply.trailer('invalid', value)420 } catch (err) {421 t.equal(err.message, `Called reply.trailer('invalid', fn) with an invalid type: ${typeof value}. Expected a function.`)422 }423 }424 reply.send('')425 })426 427 fastify.inject({428 method: 'GET',429 url: '/'430 }, (error, res) => {431 t.error(error)432 t.equal(res.statusCode, 200)433 })434})435 