CoolFace
Apppublic

strong-tie/inbound-calls

sourceHugging Faceupdated 2y agoView on Hugging Face
0likes
close.test.js690 linesDownload Raw Back to test
1'use strict'2 3const net = require('node:net')4const http = require('node:http')5const { test } = require('tap')6const Fastify = require('..')7const { Client } = require('undici')8const split = require('split2')9const { sleep } = require('./helper')10 11test('close callback', t => {12  t.plan(7)13  const fastify = Fastify()14  fastify.addHook('onClose', onClose)15  function onClose (instance, done) {16    t.type(fastify, this)17    t.type(fastify, instance)18    t.equal(fastify, this)19    t.equal(fastify, instance)20    done()21  }22 23  fastify.listen({ port: 0 }, err => {24    t.error(err)25 26    fastify.close((err) => {27      t.error(err)28      t.ok('close callback')29    })30  })31})32 33test('inside register', t => {34  t.plan(5)35  const fastify = Fastify()36  fastify.register(function (f, opts, done) {37    f.addHook('onClose', onClose)38    function onClose (instance, done) {39      t.ok(instance.prototype === fastify.prototype)40      t.equal(instance, f)41      done()42    }43 44    done()45  })46 47  fastify.listen({ port: 0 }, err => {48    t.error(err)49 50    fastify.close((err) => {51      t.error(err)52      t.ok('close callback')53    })54  })55})56 57test('close order', t => {58  t.plan(5)59  const fastify = Fastify()60  const order = [1, 2, 3]61 62  fastify.register(function (f, opts, done) {63    f.addHook('onClose', (instance, done) => {64      t.equal(order.shift(), 1)65      done()66    })67 68    done()69  })70 71  fastify.addHook('onClose', (instance, done) => {72    t.equal(order.shift(), 2)73    done()74  })75 76  fastify.listen({ port: 0 }, err => {77    t.error(err)78 79    fastify.close((err) => {80      t.error(err)81      t.equal(order.shift(), 3)82    })83  })84})85 86test('close order - async', async t => {87  t.plan(3)88  const fastify = Fastify()89  const order = [1, 2, 3]90 91  fastify.register(function (f, opts, done) {92    f.addHook('onClose', async instance => {93      t.equal(order.shift(), 1)94    })95 96    done()97  })98 99  fastify.addHook('onClose', () => {100    t.equal(order.shift(), 2)101  })102 103  await fastify.listen({ port: 0 })104  await fastify.close()105 106  t.equal(order.shift(), 3)107})108 109test('should not throw an error if the server is not listening', t => {110  t.plan(2)111  const fastify = Fastify()112  fastify.addHook('onClose', onClose)113  function onClose (instance, done) {114    t.type(fastify, instance)115    done()116  }117 118  fastify.close((err) => {119    t.error(err)120  })121})122 123test('onClose should keep the context', t => {124  t.plan(4)125  const fastify = Fastify()126  fastify.register(plugin)127 128  function plugin (instance, opts, done) {129    instance.decorate('test', true)130    instance.addHook('onClose', onClose)131    t.ok(instance.prototype === fastify.prototype)132 133    function onClose (i, done) {134      t.ok(i.test)135      t.equal(i, instance)136      done()137    }138 139    done()140  }141 142  fastify.close((err) => {143    t.error(err)144  })145})146 147test('Should return error while closing (promise) - injection', t => {148  t.plan(4)149  const fastify = Fastify()150 151  fastify.addHook('onClose', (instance, done) => { done() })152 153  fastify.get('/', (req, reply) => {154    reply.send({ hello: 'world' })155  })156 157  fastify.inject({158    method: 'GET',159    url: '/'160  }, (err, res) => {161    t.error(err)162    t.equal(res.statusCode, 200)163    fastify.close()164 165    process.nextTick(() => {166      fastify.inject({167        method: 'GET',168        url: '/'169      }).catch(err => {170        t.ok(err)171        t.equal(err.code, 'FST_ERR_REOPENED_CLOSE_SERVER')172      })173    }, 100)174  })175})176 177test('Should return error while closing (callback) - injection', t => {178  t.plan(4)179  const fastify = Fastify()180 181  fastify.addHook('onClose', (instance, done) => {182    setTimeout(done, 150)183  })184 185  fastify.get('/', (req, reply) => {186    reply.send({ hello: 'world' })187  })188 189  fastify.inject({190    method: 'GET',191    url: '/'192  }, (err, res) => {193    t.error(err)194    t.equal(res.statusCode, 200)195    fastify.close()196 197    setTimeout(() => {198      fastify.inject({199        method: 'GET',200        url: '/'201      }, (err, res) => {202        t.ok(err)203        t.equal(err.code, 'FST_ERR_REOPENED_CLOSE_SERVER')204      })205    }, 100)206  })207})208 209test('Current opened connection should NOT continue to work after closing and return "connection: close" header - return503OnClosing: false', t => {210  t.plan(4)211  const fastify = Fastify({212    return503OnClosing: false,213    forceCloseConnections: false214  })215 216  fastify.get('/', (req, reply) => {217    fastify.close()218    reply.send({ hello: 'world' })219  })220 221  fastify.listen({ port: 0 }, err => {222    t.error(err)223 224    const port = fastify.server.address().port225    const client = net.createConnection({ port }, () => {226      client.write('GET / HTTP/1.1\r\nHost: example.com\r\n\r\n')227 228      client.on('error', function () {229        // Depending on the Operating System230        // the socket could error or not.231        // However, it will always be closed.232      })233 234      client.on('close', function () {235        t.pass('close')236      })237 238      client.once('data', data => {239        t.match(data.toString(), /Connection:\s*keep-alive/i)240        t.match(data.toString(), /200 OK/i)241 242        client.write('GET / HTTP/1.1\r\nHost: example.com\r\n\r\n')243      })244    })245  })246})247 248test('Current opened connection should not accept new incoming connections', t => {249  t.plan(3)250  const fastify = Fastify({ forceCloseConnections: false })251  fastify.get('/', (req, reply) => {252    fastify.close()253    setTimeout(() => {254      reply.send({ hello: 'world' })255    }, 250)256  })257 258  fastify.listen({ port: 0 }, err => {259    t.error(err)260    const instance = new Client('http://localhost:' + fastify.server.address().port)261    instance.request({ path: '/', method: 'GET' }).then(data => {262      t.equal(data.statusCode, 200)263    })264    instance.request({ path: '/', method: 'GET' }).then(data => {265      t.equal(data.statusCode, 503)266    })267  })268})269 270test('rejected incoming connections should be logged', t => {271  t.plan(2)272  const stream = split(JSON.parse)273  const fastify = Fastify({274    forceCloseConnections: false,275    logger: {276      stream,277      level: 'info'278    }279  })280 281  const messages = []282  stream.on('data', message => {283    messages.push(message)284  })285  fastify.get('/', (req, reply) => {286    fastify.close()287    setTimeout(() => {288      reply.send({ hello: 'world' })289    }, 250)290  })291 292  fastify.listen({ port: 0 }, err => {293    t.error(err)294    const instance = new Client('http://localhost:' + fastify.server.address().port)295    // initial request to trigger close296    instance.request({ path: '/', method: 'GET' })297    // subsequent request should be rejected298    instance.request({ path: '/', method: 'GET' }).then(() => {299      t.ok(messages.find(message => message.msg.includes('request aborted')))300    })301  })302})303 304test('Cannot be reopened the closed server without listen callback', async t => {305  t.plan(2)306  const fastify = Fastify()307 308  await fastify.listen({ port: 0 })309  await fastify.close()310 311  try {312    await fastify.listen({ port: 0 })313  } catch (err) {314    t.ok(err)315    t.equal(err.code, 'FST_ERR_REOPENED_CLOSE_SERVER')316  }317})318 319test('Cannot be reopened the closed server has listen callback', async t => {320  t.plan(2)321  const fastify = Fastify()322 323  await fastify.listen({ port: 0 })324  await fastify.close()325 326  await new Promise((resolve, reject) => {327    fastify.listen({ port: 0 }, err => {328      reject(err)329    })330  }).catch(err => {331    t.equal(err.code, 'FST_ERR_REOPENED_CLOSE_SERVER')332    t.ok(err)333  })334})335 336const server = http.createServer()337const noSupport = typeof server.closeAllConnections !== 'function'338 339test('shutsdown while keep-alive connections are active (non-async, native)', { skip: noSupport }, t => {340  t.plan(5)341 342  const timeoutTime = 2 * 60 * 1000343  const fastify = Fastify({ forceCloseConnections: true })344 345  fastify.server.setTimeout(timeoutTime)346  fastify.server.keepAliveTimeout = timeoutTime347 348  fastify.get('/', (req, reply) => {349    reply.send({ hello: 'world' })350  })351 352  fastify.listen({ port: 0 }, (err, address) => {353    t.error(err)354 355    const client = new Client(356      'http://localhost:' + fastify.server.address().port,357      { keepAliveTimeout: 1 * 60 * 1000 }358    )359    client.request({ path: '/', method: 'GET' }, (err, response) => {360      t.error(err)361      t.equal(client.closed, false)362 363      fastify.close((err) => {364        t.error(err)365 366        // Due to the nature of the way we reap these keep-alive connections,367        // there hasn't been enough time before the server fully closed in order368        // for the client to have seen the socket get destroyed. The mere fact369        // that we have reached this callback is enough indication that the370        // feature being tested works as designed.371        t.equal(client.closed, false)372      })373    })374  })375})376 377test('shutsdown while keep-alive connections are active (non-async, idle, native)', { skip: noSupport }, t => {378  t.plan(5)379 380  const timeoutTime = 2 * 60 * 1000381  const fastify = Fastify({ forceCloseConnections: 'idle' })382 383  fastify.server.setTimeout(timeoutTime)384  fastify.server.keepAliveTimeout = timeoutTime385 386  fastify.get('/', (req, reply) => {387    reply.send({ hello: 'world' })388  })389 390  fastify.listen({ port: 0 }, (err, address) => {391    t.error(err)392 393    const client = new Client(394      'http://localhost:' + fastify.server.address().port,395      { keepAliveTimeout: 1 * 60 * 1000 }396    )397    client.request({ path: '/', method: 'GET' }, (err, response) => {398      t.error(err)399      t.equal(client.closed, false)400 401      fastify.close((err) => {402        t.error(err)403 404        // Due to the nature of the way we reap these keep-alive connections,405        // there hasn't been enough time before the server fully closed in order406        // for the client to have seen the socket get destroyed. The mere fact407        // that we have reached this callback is enough indication that the408        // feature being tested works as designed.409        t.equal(client.closed, false)410      })411    })412  })413})414 415test('triggers on-close hook in the right order with multiple bindings', async t => {416  const expectedOrder = [1, 2, 3]417  const order = []418  const fastify = Fastify()419 420  t.plan(1)421 422  // Follows LIFO423  fastify.addHook('onClose', () => {424    order.push(2)425  })426 427  fastify.addHook('onClose', () => {428    order.push(1)429  })430 431  await fastify.listen({ port: 0 })432 433  await new Promise((resolve, reject) => {434    setTimeout(() => {435      fastify.close(err => {436        order.push(3)437        t.match(order, expectedOrder)438 439        if (err) t.error(err)440        else resolve()441      })442    }, 2000)443  })444})445 446test('triggers on-close hook in the right order with multiple bindings (forceCloseConnections - idle)', { skip: noSupport }, async t => {447  const expectedPayload = { hello: 'world' }448  const timeoutTime = 2 * 60 * 1000449  const expectedOrder = [1, 2]450  const order = []451  const fastify = Fastify({ forceCloseConnections: 'idle' })452 453  fastify.server.setTimeout(timeoutTime)454  fastify.server.keepAliveTimeout = timeoutTime455 456  fastify.get('/', async (req, reply) => {457    await new Promise((resolve) => {458      setTimeout(resolve, 1000)459    })460 461    return expectedPayload462  })463 464  fastify.addHook('onClose', () => {465    order.push(1)466  })467 468  await fastify.listen({ port: 0 })469  const addresses = fastify.addresses()470  const testPlan = (addresses.length * 2) + 1471 472  t.plan(testPlan)473 474  for (const addr of addresses) {475    const { family, address, port } = addr476    const host = family === 'IPv6' ? `[${address}]` : address477    const client = new Client(`http://${host}:${port}`, {478      keepAliveTimeout: 1 * 60 * 1000479    })480 481    client.request({ path: '/', method: 'GET' })482      .then((res) => res.body.json(), err => t.error(err))483      .then(json => {484        t.match(json, expectedPayload, 'should payload match')485        t.notOk(client.closed, 'should client not be closed')486      }, err => t.error(err))487  }488 489  await new Promise((resolve, reject) => {490    setTimeout(() => {491      fastify.close(err => {492        order.push(2)493        t.match(order, expectedOrder)494 495        if (err) t.error(err)496        else resolve()497      })498    }, 2000)499  })500})501 502test('triggers on-close hook in the right order with multiple bindings (forceCloseConnections - true)', { skip: noSupport }, async t => {503  const expectedPayload = { hello: 'world' }504  const timeoutTime = 2 * 60 * 1000505  const expectedOrder = [1, 2]506  const order = []507  const fastify = Fastify({ forceCloseConnections: true })508 509  fastify.server.setTimeout(timeoutTime)510  fastify.server.keepAliveTimeout = timeoutTime511 512  fastify.get('/', async (req, reply) => {513    await new Promise((resolve) => {514      setTimeout(resolve, 1000)515    })516 517    return expectedPayload518  })519 520  fastify.addHook('onClose', () => {521    order.push(1)522  })523 524  await fastify.listen({ port: 0 })525  const addresses = fastify.addresses()526  const testPlan = (addresses.length * 2) + 1527 528  t.plan(testPlan)529 530  for (const addr of addresses) {531    const { family, address, port } = addr532    const host = family === 'IPv6' ? `[${address}]` : address533    const client = new Client(`http://${host}:${port}`, {534      keepAliveTimeout: 1 * 60 * 1000535    })536 537    client.request({ path: '/', method: 'GET' })538      .then((res) => res.body.json(), err => t.error(err))539      .then(json => {540        t.match(json, expectedPayload, 'should payload match')541        t.notOk(client.closed, 'should client not be closed')542      }, err => t.error(err))543  }544 545  await new Promise((resolve, reject) => {546    setTimeout(() => {547      fastify.close(err => {548        order.push(2)549        t.match(order, expectedOrder)550 551        if (err) t.error(err)552        else resolve()553      })554    }, 2000)555  })556})557 558test('shutsdown while keep-alive connections are active (non-async, custom)', t => {559  t.plan(5)560 561  const timeoutTime = 2 * 60 * 1000562  const fastify = Fastify({563    forceCloseConnections: true,564    serverFactory (handler) {565      const server = http.createServer(handler)566 567      server.closeAllConnections = null568 569      return server570    }571  })572 573  fastify.server.setTimeout(timeoutTime)574  fastify.server.keepAliveTimeout = timeoutTime575 576  fastify.get('/', (req, reply) => {577    reply.send({ hello: 'world' })578  })579 580  fastify.listen({ port: 0 }, (err, address) => {581    t.error(err)582 583    const client = new Client(584      'http://localhost:' + fastify.server.address().port,585      { keepAliveTimeout: 1 * 60 * 1000 }586    )587    client.request({ path: '/', method: 'GET' }, (err, response) => {588      t.error(err)589      t.equal(client.closed, false)590 591      fastify.close((err) => {592        t.error(err)593 594        // Due to the nature of the way we reap these keep-alive connections,595        // there hasn't been enough time before the server fully closed in order596        // for the client to have seen the socket get destroyed. The mere fact597        // that we have reached this callback is enough indication that the598        // feature being tested works as designed.599        t.equal(client.closed, false)600      })601    })602  })603})604 605test('preClose callback', t => {606  t.plan(5)607  const fastify = Fastify()608  fastify.addHook('onClose', onClose)609  let preCloseCalled = false610  function onClose (instance, done) {611    t.equal(preCloseCalled, true)612    done()613  }614  fastify.addHook('preClose', preClose)615 616  function preClose (done) {617    t.type(this, fastify)618    preCloseCalled = true619    done()620  }621 622  fastify.listen({ port: 0 }, err => {623    t.error(err)624 625    fastify.close((err) => {626      t.error(err)627      t.ok('close callback')628    })629  })630})631 632test('preClose async', async t => {633  t.plan(2)634  const fastify = Fastify()635  fastify.addHook('onClose', onClose)636  let preCloseCalled = false637  async function onClose () {638    t.equal(preCloseCalled, true)639  }640  fastify.addHook('preClose', preClose)641 642  async function preClose () {643    preCloseCalled = true644    t.type(this, fastify)645  }646 647  await fastify.listen({ port: 0 })648 649  await fastify.close()650})651 652test('preClose execution order', t => {653  t.plan(4)654  const fastify = Fastify()655  const order = []656  fastify.addHook('onClose', onClose)657  function onClose (instance, done) {658    t.same(order, [1, 2, 3])659    done()660  }661 662  fastify.addHook('preClose', (done) => {663    setTimeout(function () {664      order.push(1)665      done()666    }, 200)667  })668 669  fastify.addHook('preClose', async () => {670    await sleep(100)671    order.push(2)672  })673 674  fastify.addHook('preClose', (done) => {675    setTimeout(function () {676      order.push(3)677      done()678    }, 100)679  })680 681  fastify.listen({ port: 0 }, err => {682    t.error(err)683 684    fastify.close((err) => {685      t.error(err)686      t.ok('close callback')687    })688  })689})690