CoolFace
Apppublic

strong-tie/inbound-calls

sourceHugging Faceupdated 2y agoView on Hugging Face
0likes
reply-error.test.js795 linesDownload Raw Back to test
1'use strict'2 3const t = require('tap')4const test = t.test5const net = require('node:net')6const Fastify = require('..')7const statusCodes = require('node:http').STATUS_CODES8const split = require('split2')9const fs = require('node:fs')10const path = require('node:path')11 12const codes = Object.keys(statusCodes)13codes.forEach(code => {14  if (Number(code) >= 400) helper(code)15})16 17function helper (code) {18  test('Reply error handling - code: ' + code, t => {19    t.plan(4)20    const fastify = Fastify()21    t.teardown(fastify.close.bind(fastify))22    const err = new Error('winter is coming')23 24    fastify.get('/', (req, reply) => {25      reply26        .code(Number(code))27        .send(err)28    })29 30    fastify.inject({31      method: 'GET',32      url: '/'33    }, (error, res) => {34      t.error(error)35      t.equal(res.statusCode, Number(code))36      t.equal(res.headers['content-type'], 'application/json; charset=utf-8')37      t.same(38        {39          error: statusCodes[code],40          message: err.message,41          statusCode: Number(code)42        },43        JSON.parse(res.payload)44      )45    })46  })47}48 49test('preHandler hook error handling with external code', t => {50  t.plan(3)51  const fastify = Fastify()52  t.teardown(fastify.close.bind(fastify))53  const err = new Error('winter is coming')54 55  fastify.addHook('preHandler', (req, reply, done) => {56    reply.code(400)57    done(err)58  })59 60  fastify.get('/', () => {})61 62  fastify.inject({63    method: 'GET',64    url: '/'65  }, (error, res) => {66    t.error(error)67    t.equal(res.statusCode, 400)68    t.same(69      {70        error: statusCodes['400'],71        message: err.message,72        statusCode: 40073      },74      JSON.parse(res.payload)75    )76  })77})78 79test('onRequest hook error handling with external done', t => {80  t.plan(3)81  const fastify = Fastify()82  t.teardown(fastify.close.bind(fastify))83  const err = new Error('winter is coming')84 85  fastify.addHook('onRequest', (req, reply, done) => {86    reply.code(400)87    done(err)88  })89 90  fastify.get('/', () => {})91 92  fastify.inject({93    method: 'GET',94    url: '/'95  }, (error, res) => {96    t.error(error)97    t.equal(res.statusCode, 400)98    t.same(99      {100        error: statusCodes['400'],101        message: err.message,102        statusCode: 400103      },104      JSON.parse(res.payload)105    )106  })107})108 109test('Should reply 400 on client error', t => {110  t.plan(2)111 112  const fastify = Fastify()113  t.teardown(fastify.close.bind(fastify))114  fastify.listen({ port: 0, host: '127.0.0.1' }, err => {115    t.error(err)116 117    const client = net.connect(fastify.server.address().port, '127.0.0.1')118    client.end('oooops!')119 120    let chunks = ''121    client.on('data', chunk => {122      chunks += chunk123    })124 125    client.once('end', () => {126      const body = JSON.stringify({127        error: 'Bad Request',128        message: 'Client Error',129        statusCode: 400130      })131      t.equal(`HTTP/1.1 400 Bad Request\r\nContent-Length: ${body.length}\r\nContent-Type: application/json\r\n\r\n${body}`, chunks)132    })133  })134})135 136test('Should set the response from client error handler', t => {137  t.plan(5)138 139  const responseBody = JSON.stringify({140    error: 'Ended Request',141    message: 'Serious Client Error',142    statusCode: 400143  })144  const response = `HTTP/1.1 400 Bad Request\r\nContent-Length: ${responseBody.length}\r\nContent-Type: application/json; charset=utf-8\r\n\r\n${responseBody}`145 146  function clientErrorHandler (err, socket) {147    t.type(err, Error)148 149    this.log.warn({ err }, 'Handled client error')150    socket.end(response)151  }152 153  const logStream = split(JSON.parse)154  const fastify = Fastify({155    clientErrorHandler,156    logger: {157      stream: logStream,158      level: 'warn'159    }160  })161 162  fastify.listen({ port: 0, host: '127.0.0.1' }, err => {163    t.error(err)164    t.teardown(fastify.close.bind(fastify))165 166    const client = net.connect(fastify.server.address().port, '127.0.0.1')167    client.end('oooops!')168 169    let chunks = ''170    client.on('data', chunk => {171      chunks += chunk172    })173 174    client.once('end', () => {175      t.equal(response, chunks)176    })177  })178 179  logStream.once('data', line => {180    t.equal('Handled client error', line.msg)181    t.equal(40, line.level, 'Log level is not warn')182  })183})184 185test('Error instance sets HTTP status code', t => {186  t.plan(3)187  const fastify = Fastify()188  t.teardown(fastify.close.bind(fastify))189  const err = new Error('winter is coming')190  err.statusCode = 418191 192  fastify.get('/', () => {193    return Promise.reject(err)194  })195 196  fastify.inject({197    method: 'GET',198    url: '/'199  }, (error, res) => {200    t.error(error)201    t.equal(res.statusCode, 418)202    t.same(203      {204        error: statusCodes['418'],205        message: err.message,206        statusCode: 418207      },208      JSON.parse(res.payload)209    )210  })211})212 213test('Error status code below 400 defaults to 500', t => {214  t.plan(3)215  const fastify = Fastify()216  t.teardown(fastify.close.bind(fastify))217  const err = new Error('winter is coming')218  err.statusCode = 399219 220  fastify.get('/', () => {221    return Promise.reject(err)222  })223 224  fastify.inject({225    method: 'GET',226    url: '/'227  }, (error, res) => {228    t.error(error)229    t.equal(res.statusCode, 500)230    t.same(231      {232        error: statusCodes['500'],233        message: err.message,234        statusCode: 500235      },236      JSON.parse(res.payload)237    )238  })239})240 241test('Error.status property support', t => {242  t.plan(3)243  const fastify = Fastify()244  t.teardown(fastify.close.bind(fastify))245  const err = new Error('winter is coming')246  err.status = 418247 248  fastify.get('/', () => {249    return Promise.reject(err)250  })251 252  fastify.inject({253    method: 'GET',254    url: '/'255  }, (error, res) => {256    t.error(error)257    t.equal(res.statusCode, 418)258    t.same(259      {260        error: statusCodes['418'],261        message: err.message,262        statusCode: 418263      },264      JSON.parse(res.payload)265    )266  })267})268 269test('Support rejection with values that are not Error instances', t => {270  const objs = [271    0,272    '',273    [],274    {},275    null,276    undefined,277    123,278    'abc',279    new RegExp(),280    new Date(),281    new Uint8Array()282  ]283  t.plan(objs.length)284  for (const nonErr of objs) {285    t.test('Type: ' + typeof nonErr, t => {286      t.plan(4)287      const fastify = Fastify()288      t.teardown(fastify.close.bind(fastify))289 290      fastify.get('/', () => {291        return Promise.reject(nonErr)292      })293 294      fastify.setErrorHandler((err, request, reply) => {295        if (typeof err === 'object') {296          t.same(err, nonErr)297        } else {298          t.equal(err, nonErr)299        }300        reply.code(500).send('error')301      })302 303      fastify.inject({304        method: 'GET',305        url: '/'306      }, (error, res) => {307        t.error(error)308        t.equal(res.statusCode, 500)309        t.equal(res.payload, 'error')310      })311    })312  }313})314 315test('invalid schema - ajv', t => {316  t.plan(4)317 318  const fastify = Fastify()319  t.teardown(fastify.close.bind(fastify))320  fastify.get('/', {321    schema: {322      querystring: {323        type: 'object',324        properties: {325          id: { type: 'number' }326        }327      }328    }329  }, (req, reply) => {330    t.fail('we should not be here')331  })332 333  fastify.setErrorHandler((err, request, reply) => {334    t.ok(Array.isArray(err.validation))335    reply.code(400).send('error')336  })337 338  fastify.inject({339    url: '/?id=abc',340    method: 'GET'341  }, (err, res) => {342    t.error(err)343    t.equal(res.statusCode, 400)344    t.equal(res.payload, 'error')345  })346})347 348test('should set the status code and the headers from the error object (from route handler) (no custom error handler)', t => {349  t.plan(4)350  const fastify = Fastify()351  t.teardown(fastify.close.bind(fastify))352 353  fastify.get('/', (req, reply) => {354    const error = new Error('kaboom')355    error.headers = { hello: 'world' }356    error.statusCode = 400357    reply.send(error)358  })359 360  fastify.inject({361    url: '/',362    method: 'GET'363  }, (err, res) => {364    t.error(err)365    t.equal(res.statusCode, 400)366    t.equal(res.headers.hello, 'world')367    t.same(JSON.parse(res.payload), {368      error: 'Bad Request',369      message: 'kaboom',370      statusCode: 400371    })372  })373})374 375test('should set the status code and the headers from the error object (from custom error handler)', t => {376  t.plan(6)377  const fastify = Fastify()378  t.teardown(fastify.close.bind(fastify))379 380  fastify.get('/', (req, reply) => {381    const error = new Error('ouch')382    error.statusCode = 401383    reply.send(error)384  })385 386  fastify.setErrorHandler((err, request, reply) => {387    t.equal(err.message, 'ouch')388    t.equal(reply.raw.statusCode, 200)389    const error = new Error('kaboom')390    error.headers = { hello: 'world' }391    error.statusCode = 400392    reply.send(error)393  })394 395  fastify.inject({396    url: '/',397    method: 'GET'398  }, (err, res) => {399    t.error(err)400    t.equal(res.statusCode, 400)401    t.equal(res.headers.hello, 'world')402    t.same(JSON.parse(res.payload), {403      error: 'Bad Request',404      message: 'kaboom',405      statusCode: 400406    })407  })408})409 410// Issue 595 https://github.com/fastify/fastify/issues/595411test('\'*\' should throw an error due to serializer can not handle the payload type', t => {412  t.plan(3)413  const fastify = Fastify()414  t.teardown(fastify.close.bind(fastify))415 416  fastify.get('/', (req, reply) => {417    reply.type('text/html')418    try {419      reply.send({})420    } catch (err) {421      t.type(err, TypeError)422      t.equal(err.code, 'FST_ERR_REP_INVALID_PAYLOAD_TYPE')423      t.equal(err.message, "Attempted to send payload of invalid type 'object'. Expected a string or Buffer.")424    }425  })426 427  fastify.inject({428    url: '/',429    method: 'GET'430  }, (e, res) => {431    t.fail('should not be called')432  })433})434 435test('should throw an error if the custom serializer does not serialize the payload to a valid type', t => {436  t.plan(3)437  const fastify = Fastify()438  t.teardown(fastify.close.bind(fastify))439 440  fastify.get('/', (req, reply) => {441    try {442      reply443        .type('text/html')444        .serializer(payload => payload)445        .send({})446    } catch (err) {447      t.type(err, TypeError)448      t.equal(err.code, 'FST_ERR_REP_INVALID_PAYLOAD_TYPE')449      t.equal(err.message, "Attempted to send payload of invalid type 'object'. Expected a string or Buffer.")450    }451  })452 453  fastify.inject({454    url: '/',455    method: 'GET'456  }, (e, res) => {457    t.fail('should not be called')458  })459})460 461test('should not set headers or status code for custom error handler', t => {462  t.plan(7)463 464  const fastify = Fastify()465  t.teardown(fastify.close.bind(fastify))466  fastify.get('/', function (req, reply) {467    const err = new Error('kaboom')468    err.headers = {469      'fake-random-header': 'abc'470    }471    reply.send(err)472  })473 474  fastify.setErrorHandler(async (err, req, res) => {475    t.equal(res.statusCode, 200)476    t.equal('fake-random-header' in res.headers, false)477    return res.code(500).send(err.message)478  })479 480  fastify.inject({481    method: 'GET',482    url: '/'483  }, (err, res) => {484    t.error(err)485    t.equal(res.statusCode, 500)486    t.equal('fake-random-header' in res.headers, false)487    t.equal(res.headers['content-length'], ('kaboom'.length).toString())488    t.same(res.payload, 'kaboom')489  })490})491 492test('error thrown by custom error handler routes to default error handler', t => {493  t.plan(6)494 495  const fastify = Fastify()496  t.teardown(fastify.close.bind(fastify))497 498  const error = new Error('kaboom')499  error.headers = {500    'fake-random-header': 'abc'501  }502 503  fastify.get('/', function (req, reply) {504    reply.send(error)505  })506 507  const newError = new Error('kabong')508 509  fastify.setErrorHandler(async (err, req, res) => {510    t.equal(res.statusCode, 200)511    t.equal('fake-random-header' in res.headers, false)512    t.same(err.headers, error.headers)513 514    return res.send(newError)515  })516 517  fastify.inject({518    method: 'GET',519    url: '/'520  }, (err, res) => {521    t.error(err)522    t.equal(res.statusCode, 500)523    t.same(JSON.parse(res.payload), {524      error: statusCodes['500'],525      message: newError.message,526      statusCode: 500527    })528  })529})530 531// Refs: https://github.com/fastify/fastify/pull/4484#issuecomment-1367301750532test('allow re-thrown error to default error handler when route handler is async and error handler is sync', t => {533  t.plan(4)534  const fastify = Fastify()535  t.teardown(fastify.close.bind(fastify))536 537  fastify.setErrorHandler(function (error) {538    t.equal(error.message, 'kaboom')539    throw Error('kabong')540  })541 542  fastify.get('/', async function () {543    throw Error('kaboom')544  })545 546  fastify.inject({547    url: '/',548    method: 'GET'549  }, (err, res) => {550    t.error(err)551    t.equal(res.statusCode, 500)552    t.same(JSON.parse(res.payload), {553      error: statusCodes['500'],554      message: 'kabong',555      statusCode: 500556    })557  })558})559 560// Issue 2078 https://github.com/fastify/fastify/issues/2078561// Supported error code list: http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml562const invalidErrorCodes = [563  undefined,564  null,565  'error_code',566 567  // out of the 100-599 range:568  0,569  1,570  99,571  600,572  700573]574invalidErrorCodes.forEach((invalidCode) => {575  test(`should throw error if error code is ${invalidCode}`, t => {576    t.plan(2)577    const fastify = Fastify()578    t.teardown(fastify.close.bind(fastify))579    fastify.get('/', (request, reply) => {580      try {581        return reply.code(invalidCode).send('You should not read this')582      } catch (err) {583        t.equal(err.code, 'FST_ERR_BAD_STATUS_CODE')584        t.equal(err.message, 'Called reply with an invalid status code: ' + invalidCode)585      }586    })587    fastify.inject({588      url: '/',589      method: 'GET'590    }, (e, res) => {591      t.fail('should not be called')592    })593  })594})595 596test('error handler is triggered when a string is thrown from sync handler', t => {597  t.plan(3)598 599  const fastify = Fastify()600  t.teardown(fastify.close.bind(fastify))601 602  const throwable = 'test'603  const payload = 'error'604 605  fastify.get('/', function (req, reply) {606    throw throwable607  })608 609  fastify.setErrorHandler((err, req, res) => {610    t.equal(err, throwable)611 612    res.send(payload)613  })614 615  fastify.inject({616    method: 'GET',617    url: '/'618  }, (err, res) => {619    t.error(err)620    t.equal(res.payload, payload)621  })622})623 624test('status code should be set to 500 and return an error json payload if route handler throws any non Error object expression', async t => {625  t.plan(2)626  const fastify = Fastify()627  t.teardown(fastify.close.bind(fastify))628 629  fastify.get('/', () => {630    /* eslint-disable-next-line */631    throw { foo: 'bar' }632  })633 634  // ----635  const reply = await fastify.inject({ method: 'GET', url: '/' })636  t.equal(reply.statusCode, 500)637  t.equal(JSON.parse(reply.body).foo, 'bar')638})639 640test('should preserve the status code set by the user if an expression is thrown in a sync route', async t => {641  t.plan(2)642  const fastify = Fastify()643  t.teardown(fastify.close.bind(fastify))644 645  fastify.get('/', (_, rep) => {646    rep.status(501)647 648    /* eslint-disable-next-line */649    throw { foo: 'bar' }650  })651 652  // ----653  const reply = await fastify.inject({ method: 'GET', url: '/' })654  t.equal(reply.statusCode, 501)655  t.equal(JSON.parse(reply.body).foo, 'bar')656})657 658test('should trigger error handlers if a sync route throws any non-error object', async t => {659  t.plan(2)660 661  const fastify = Fastify()662  t.teardown(fastify.close.bind(fastify))663 664  const throwable = 'test'665  const payload = 'error'666 667  fastify.get('/', function async (req, reply) {668    throw throwable669  })670 671  fastify.setErrorHandler((err, req, res) => {672    t.equal(err, throwable)673    res.code(500).send(payload)674  })675 676  const reply = await fastify.inject({ method: 'GET', url: '/' })677  t.equal(reply.statusCode, 500)678})679 680test('should trigger error handlers if a sync route throws undefined', async t => {681  t.plan(1)682 683  const fastify = Fastify()684  t.teardown(fastify.close.bind(fastify))685 686  fastify.get('/', function async (req, reply) {687    // eslint-disable-next-line no-throw-literal688    throw undefined689  })690 691  const reply = await fastify.inject({ method: 'GET', url: '/' })692  t.equal(reply.statusCode, 500)693})694 695test('setting content-type on reply object should not hang the server case 1', t => {696  t.plan(2)697  const fastify = Fastify()698  t.teardown(fastify.close.bind(fastify))699 700  fastify.get('/', (req, reply) => {701    reply702      .code(200)703      .headers({ 'content-type': 'text/plain; charset=utf-32' })704      .send(JSON.stringify({ bar: 'foo', baz: 'foobar' }))705  })706 707  fastify.inject({708    url: '/',709    method: 'GET'710  }, (err, res) => {711    t.error(err)712    t.equal(res.statusCode, 200)713  })714})715 716test('setting content-type on reply object should not hang the server case 2', async t => {717  t.plan(1)718  const fastify = Fastify()719  t.teardown(fastify.close.bind(fastify))720 721  fastify.get('/', (req, reply) => {722    reply723      .code(200)724      .headers({ 'content-type': 'text/plain; charset=utf-8' })725      .send({ bar: 'foo', baz: 'foobar' })726  })727 728  try {729    await fastify.ready()730    const res = await fastify.inject({731      url: '/',732      method: 'GET'733    })734    t.same({735      error: 'Internal Server Error',736      message: 'Attempted to send payload of invalid type \'object\'. Expected a string or Buffer.',737      statusCode: 500,738      code: 'FST_ERR_REP_INVALID_PAYLOAD_TYPE'739    },740    res.json())741  } catch (error) {742    t.error(error)743  } finally {744    await fastify.close()745  }746})747 748test('setting content-type on reply object should not hang the server case 3', t => {749  t.plan(2)750  const fastify = Fastify()751  t.teardown(fastify.close.bind(fastify))752 753  fastify.get('/', (req, reply) => {754    reply755      .code(200)756      .headers({ 'content-type': 'application/json' })757      .send({ bar: 'foo', baz: 'foobar' })758  })759 760  fastify.inject({761    url: '/',762    method: 'GET'763  }, (err, res) => {764    t.error(err)765    t.equal(res.statusCode, 200)766  })767})768 769test('pipe stream inside error handler should not cause error', t => {770  t.plan(3)771  const location = path.join(__dirname, '..', 'package.json')772  const json = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json')).toString('utf8'))773 774  const fastify = Fastify()775  t.teardown(fastify.close.bind(fastify))776 777  fastify.setErrorHandler((_error, _request, reply) => {778    const stream = fs.createReadStream(location)779    reply.code(400).type('application/json; charset=utf-8').send(stream)780  })781 782  fastify.get('/', (request, reply) => {783    throw new Error('This is an error.')784  })785 786  fastify.inject({787    url: '/',788    method: 'GET'789  }, (err, res) => {790    t.error(err)791    t.equal(res.statusCode, 400)792    t.same(JSON.parse(res.payload), json)793  })794})795