CoolFace
Apppublic

strong-tie/inbound-calls

sourceHugging Faceupdated 2y agoView on Hugging Face
0likes
validation-error-handling.test.js834 linesDownload Raw Back to test
1'use strict'2 3const { test } = require('node:test')4const Joi = require('joi')5const Fastify = require('..')6 7const schema = {8  body: {9    type: 'object',10    properties: {11      name: { type: 'string' },12      work: { type: 'string' }13    },14    required: ['name', 'work']15  }16}17 18function echoBody (req, reply) {19  reply.code(200).send(req.body.name)20}21 22test('should work with valid payload', async (t) => {23  t.plan(2)24 25  const fastify = Fastify()26 27  fastify.post('/', { schema }, echoBody)28 29  const response = await fastify.inject({30    method: 'POST',31    payload: {32      name: 'michelangelo',33      work: 'sculptor, painter, architect and poet'34    },35    url: '/'36  })37  t.assert.deepStrictEqual(response.payload, 'michelangelo')38  t.assert.strictEqual(response.statusCode, 200)39})40 41test('should fail immediately with invalid payload', async (t) => {42  t.plan(2)43 44  const fastify = Fastify()45 46  fastify.post('/', { schema }, echoBody)47 48  const response = await fastify.inject({49    method: 'POST',50    payload: {51      hello: 'michelangelo'52    },53    url: '/'54  })55 56  t.assert.deepStrictEqual(response.json(), {57    statusCode: 400,58    code: 'FST_ERR_VALIDATION',59    error: 'Bad Request',60    message: "body must have required property 'name'"61  })62  t.assert.strictEqual(response.statusCode, 400)63})64 65test('should be able to use setErrorHandler specify custom validation error', async (t) => {66  t.plan(2)67 68  const fastify = Fastify()69 70  fastify.post('/', { schema }, function (req, reply) {71    t.assert.fail('should not be here')72    reply.code(200).send(req.body.name)73  })74 75  fastify.setErrorHandler(function (error, request, reply) {76    if (error.validation) {77      reply.status(422).send(new Error('validation failed'))78    }79  })80 81  const response = await fastify.inject({82    method: 'POST',83    payload: {84      hello: 'michelangelo'85    },86    url: '/'87  })88 89  t.assert.deepStrictEqual(JSON.parse(response.payload), {90    statusCode: 422,91    error: 'Unprocessable Entity',92    message: 'validation failed'93  })94  t.assert.strictEqual(response.statusCode, 422)95})96 97test('validation error has 400 statusCode set', async (t) => {98  t.plan(2)99 100  const fastify = Fastify()101 102  fastify.setErrorHandler((error, request, reply) => {103    const errorResponse = {104      message: error.message,105      statusCode: error.statusCode || 500106    }107 108    reply.code(errorResponse.statusCode).send(errorResponse)109  })110 111  fastify.post('/', { schema }, echoBody)112 113  const response = await fastify.inject({114    method: 'POST',115    payload: {116      hello: 'michelangelo'117    },118    url: '/'119  })120 121  t.assert.deepStrictEqual(response.json(), {122    statusCode: 400,123    message: "body must have required property 'name'"124  })125  t.assert.strictEqual(response.statusCode, 400)126})127 128test('error inside custom error handler should have validationContext', async (t) => {129  t.plan(1)130 131  const fastify = Fastify()132 133  fastify.post('/', {134    schema,135    validatorCompiler: ({ schema, method, url, httpPart }) => {136      return function (data) {137        return { error: new Error('this failed') }138      }139    }140  }, function (req, reply) {141    t.assert.fail('should not be here')142    reply.code(200).send(req.body.name)143  })144 145  fastify.setErrorHandler(function (error, request, reply) {146    t.assert.strictEqual(error.validationContext, 'body')147    reply.status(500).send(error)148  })149 150  await fastify.inject({151    method: 'POST',152    payload: {153      name: 'michelangelo',154      work: 'artist'155    },156    url: '/'157  })158})159 160test('error inside custom error handler should have validationContext if specified by custom error handler', async (t) => {161  t.plan(1)162 163  const fastify = Fastify()164 165  fastify.post('/', {166    schema,167    validatorCompiler: ({ schema, method, url, httpPart }) => {168      return function (data) {169        const error = new Error('this failed')170        error.validationContext = 'customContext'171        return { error }172      }173    }174  }, function (req, reply) {175    t.assert.fail('should not be here')176    reply.code(200).send(req.body.name)177  })178 179  fastify.setErrorHandler(function (error, request, reply) {180    t.assert.strictEqual(error.validationContext, 'customContext')181    reply.status(500).send(error)182  })183 184  await fastify.inject({185    method: 'POST',186    payload: {187      name: 'michelangelo',188      work: 'artist'189    },190    url: '/'191  })192})193 194test('should be able to attach validation to request', async (t) => {195  t.plan(2)196 197  const fastify = Fastify()198 199  fastify.post('/', { schema, attachValidation: true }, function (req, reply) {200    reply.code(400).send(req.validationError.validation)201  })202 203  const response = await fastify.inject({204    method: 'POST',205    payload: {206      hello: 'michelangelo'207    },208    url: '/'209  })210 211  t.assert.deepStrictEqual(response.json(), [{212    keyword: 'required',213    instancePath: '',214    schemaPath: '#/required',215    params: { missingProperty: 'name' },216    message: 'must have required property \'name\''217  }])218  t.assert.strictEqual(response.statusCode, 400)219})220 221test('should respect when attachValidation is explicitly set to false', async (t) => {222  t.plan(2)223 224  const fastify = Fastify()225 226  fastify.post('/', { schema, attachValidation: false }, function (req, reply) {227    t.assert.fail('should not be here')228    reply.code(200).send(req.validationError.validation)229  })230 231  const response = await fastify.inject({232    method: 'POST',233    payload: {234      hello: 'michelangelo'235    },236    url: '/'237  })238 239  t.assert.deepStrictEqual(JSON.parse(response.payload), {240    statusCode: 400,241    code: 'FST_ERR_VALIDATION',242    error: 'Bad Request',243    message: "body must have required property 'name'"244  })245  t.assert.strictEqual(response.statusCode, 400)246})247 248test('Attached validation error should take precedence over setErrorHandler', async (t) => {249  t.plan(2)250 251  const fastify = Fastify()252 253  fastify.post('/', { schema, attachValidation: true }, function (req, reply) {254    reply.code(400).send('Attached: ' + req.validationError)255  })256 257  fastify.setErrorHandler(function (error, request, reply) {258    t.assert.fail('should not be here')259    if (error.validation) {260      reply.status(422).send(new Error('validation failed'))261    }262  })263 264  const response = await fastify.inject({265    method: 'POST',266    payload: {267      hello: 'michelangelo'268    },269    url: '/'270  })271 272  t.assert.deepStrictEqual(response.payload, "Attached: Error: body must have required property 'name'")273  t.assert.strictEqual(response.statusCode, 400)274})275 276test('should handle response validation error', async (t) => {277  t.plan(2)278 279  const response = {280    200: {281      type: 'object',282      required: ['name', 'work'],283      properties: {284        name: { type: 'string' },285        work: { type: 'string' }286      }287    }288  }289 290  const fastify = Fastify()291 292  fastify.get('/', { schema: { response } }, function (req, reply) {293    try {294      reply.code(200).send({ work: 'actor' })295    } catch (error) {296      reply.code(500).send(error)297    }298  })299 300  const injectResponse = await fastify.inject({301    method: 'GET',302    payload: { },303    url: '/'304  })305 306  t.assert.strictEqual(injectResponse.statusCode, 500)307  t.assert.strictEqual(injectResponse.payload, '{"statusCode":500,"error":"Internal Server Error","message":"\\"name\\" is required!"}')308})309 310test('should handle response validation error with promises', async (t) => {311  t.plan(2)312 313  const response = {314    200: {315      type: 'object',316      required: ['name', 'work'],317      properties: {318        name: { type: 'string' },319        work: { type: 'string' }320      }321    }322  }323 324  const fastify = Fastify()325 326  fastify.get('/', { schema: { response } }, function (req, reply) {327    return Promise.resolve({ work: 'actor' })328  })329 330  const injectResponse = await fastify.inject({331    method: 'GET',332    payload: { },333    url: '/'334  })335 336  t.assert.strictEqual(injectResponse.statusCode, 500)337  t.assert.strictEqual(injectResponse.payload, '{"statusCode":500,"error":"Internal Server Error","message":"\\"name\\" is required!"}')338})339 340test('should return a defined output message parsing AJV errors', async (t) => {341  t.plan(2)342 343  const body = {344    type: 'object',345    required: ['name', 'work'],346    properties: {347      name: { type: 'string' },348      work: { type: 'string' }349    }350  }351 352  const fastify = Fastify()353 354  fastify.post('/', { schema: { body } }, function (req, reply) {355    t.assert.fail()356  })357 358  const response = await fastify.inject({359    method: 'POST',360    payload: { },361    url: '/'362  })363 364  t.assert.strictEqual(response.statusCode, 400)365  t.assert.strictEqual(response.payload, '{"statusCode":400,"code":"FST_ERR_VALIDATION","error":"Bad Request","message":"body must have required property \'name\'"}')366})367 368test('should return a defined output message parsing JOI errors', async (t) => {369  t.plan(2)370 371  const body = Joi.object().keys({372    name: Joi.string().required(),373    work: Joi.string().required()374  }).required()375 376  const fastify = Fastify()377 378  fastify.post('/', {379    schema: { body },380    validatorCompiler: ({ schema, method, url, httpPart }) => {381      return data => schema.validate(data)382    }383  },384  function (req, reply) {385    t.assert.fail()386  })387 388  const response = await fastify.inject({389    method: 'POST',390    payload: {},391    url: '/'392  })393 394  t.assert.strictEqual(response.statusCode, 400)395  t.assert.strictEqual(response.payload, '{"statusCode":400,"code":"FST_ERR_VALIDATION","error":"Bad Request","message":"\\"name\\" is required"}')396})397 398test('should return a defined output message parsing JOI error details', async (t) => {399  t.plan(2)400 401  const body = Joi.object().keys({402    name: Joi.string().required(),403    work: Joi.string().required()404  }).required()405 406  const fastify = Fastify()407 408  fastify.post('/', {409    schema: { body },410    validatorCompiler: ({ schema, method, url, httpPart }) => {411      return data => {412        const validation = schema.validate(data)413        return { error: validation.error.details }414      }415    }416  },417  function (req, reply) {418    t.assert.fail()419  })420 421  const response = await fastify.inject({422    method: 'POST',423    payload: {},424    url: '/'425  })426 427  t.assert.strictEqual(response.statusCode, 400)428  t.assert.strictEqual(response.payload, '{"statusCode":400,"code":"FST_ERR_VALIDATION","error":"Bad Request","message":"body \\"name\\" is required"}')429})430 431test('the custom error formatter context must be the server instance', async (t) => {432  t.plan(3)433 434  const fastify = Fastify()435 436  fastify.setSchemaErrorFormatter(function (errors, dataVar) {437    t.assert.deepStrictEqual(this, fastify)438    return new Error('my error')439  })440 441  fastify.post('/', { schema }, echoBody)442 443  const response = await fastify.inject({444    method: 'POST',445    payload: {446      hello: 'michelangelo'447    },448    url: '/'449  })450 451  t.assert.deepStrictEqual(response.json(), {452    statusCode: 400,453    code: 'FST_ERR_VALIDATION',454    error: 'Bad Request',455    message: 'my error'456  })457  t.assert.strictEqual(response.statusCode, 400)458})459 460test('the custom error formatter context must be the server instance in options', async (t) => {461  t.plan(3)462 463  const fastify = Fastify({464    schemaErrorFormatter: function (errors, dataVar) {465      t.assert.deepStrictEqual(this, fastify)466      return new Error('my error')467    }468  })469 470  fastify.post('/', { schema }, echoBody)471 472  const response = await fastify.inject({473    method: 'POST',474    payload: {475      hello: 'michelangelo'476    },477    url: '/'478  })479 480  t.assert.deepStrictEqual(response.json(), {481    statusCode: 400,482    code: 'FST_ERR_VALIDATION',483    error: 'Bad Request',484    message: 'my error'485  })486  t.assert.strictEqual(response.statusCode, 400)487})488 489test('should call custom error formatter', async (t) => {490  t.plan(8)491 492  const fastify = Fastify({493    schemaErrorFormatter: (errors, dataVar) => {494      t.assert.strictEqual(errors.length, 1)495      t.assert.strictEqual(errors[0].message, "must have required property 'name'")496      t.assert.strictEqual(errors[0].keyword, 'required')497      t.assert.strictEqual(errors[0].schemaPath, '#/required')498      t.assert.deepStrictEqual(errors[0].params, {499        missingProperty: 'name'500      })501      t.assert.strictEqual(dataVar, 'body')502      return new Error('my error')503    }504  })505 506  fastify.post('/', { schema }, echoBody)507 508  const response = await fastify.inject({509    method: 'POST',510    payload: {511      hello: 'michelangelo'512    },513    url: '/'514  })515 516  t.assert.deepStrictEqual(response.json(), {517    statusCode: 400,518    code: 'FST_ERR_VALIDATION',519    error: 'Bad Request',520    message: 'my error'521  })522  t.assert.strictEqual(response.statusCode, 400)523})524 525test('should catch error inside formatter and return message', async (t) => {526  t.plan(2)527 528  const fastify = Fastify({529    schemaErrorFormatter: (errors, dataVar) => {530      throw new Error('abc')531    }532  })533 534  fastify.post('/', { schema }, echoBody)535 536  const response = await fastify.inject({537    method: 'POST',538    payload: {539      hello: 'michelangelo'540    },541    url: '/'542  })543 544  t.assert.deepStrictEqual(response.json(), {545    statusCode: 500,546    error: 'Internal Server Error',547    message: 'abc'548  })549  t.assert.strictEqual(response.statusCode, 500)550})551 552test('cannot create a fastify instance with wrong type of errorFormatter', async (t) => {553  t.plan(3)554 555  try {556    Fastify({557      schemaErrorFormatter: async (errors, dataVar) => {558        return new Error('should not execute')559      }560    })561  } catch (err) {562    t.assert.strictEqual(err.code, 'FST_ERR_SCHEMA_ERROR_FORMATTER_NOT_FN')563  }564 565  try {566    Fastify({567      schemaErrorFormatter: 500568    })569  } catch (err) {570    t.assert.strictEqual(err.code, 'FST_ERR_SCHEMA_ERROR_FORMATTER_NOT_FN')571  }572 573  try {574    const fastify = Fastify()575    fastify.setSchemaErrorFormatter(500)576  } catch (err) {577    t.assert.strictEqual(err.code, 'FST_ERR_SCHEMA_ERROR_FORMATTER_NOT_FN')578  }579})580 581test('should register a route based schema error formatter', async (t) => {582  t.plan(2)583 584  const fastify = Fastify()585 586  fastify.post('/', {587    schema,588    schemaErrorFormatter: (errors, dataVar) => {589      return new Error('abc')590    }591  }, echoBody)592 593  const response = await fastify.inject({594    method: 'POST',595    payload: {596      hello: 'michelangelo'597    },598    url: '/'599  })600 601  t.assert.deepStrictEqual(response.json(), {602    statusCode: 400,603    code: 'FST_ERR_VALIDATION',604    error: 'Bad Request',605    message: 'abc'606  })607  t.assert.strictEqual(response.statusCode, 400)608})609 610test('prefer route based error formatter over global one', async (t) => {611  t.plan(6)612 613  const fastify = Fastify({614    schemaErrorFormatter: (errors, dataVar) => {615      return new Error('abc123')616    }617  })618 619  fastify.post('/', {620    schema,621    schemaErrorFormatter: (errors, dataVar) => {622      return new Error('123')623    }624  }, echoBody)625 626  fastify.post('/abc', {627    schema,628    schemaErrorFormatter: (errors, dataVar) => {629      return new Error('abc')630    }631  }, echoBody)632 633  fastify.post('/test', { schema }, echoBody)634 635  const response1 = await fastify.inject({636    method: 'POST',637    payload: {638      hello: 'michelangelo'639    },640    url: '/'641  })642 643  t.assert.deepStrictEqual(response1.json(), {644    statusCode: 400,645    code: 'FST_ERR_VALIDATION',646    error: 'Bad Request',647    message: '123'648  })649  t.assert.strictEqual(response1.statusCode, 400)650 651  const response2 = await fastify.inject({652    method: 'POST',653    payload: {654      hello: 'michelangelo'655    },656    url: '/abc'657  })658 659  t.assert.deepStrictEqual(response2.json(), {660    statusCode: 400,661    code: 'FST_ERR_VALIDATION',662    error: 'Bad Request',663    message: 'abc'664  })665  t.assert.strictEqual(response2.statusCode, 400)666 667  const response3 = await fastify.inject({668    method: 'POST',669    payload: {670      hello: 'michelangelo'671    },672    url: '/test'673  })674 675  t.assert.deepStrictEqual(response3.json(), {676    statusCode: 400,677    code: 'FST_ERR_VALIDATION',678    error: 'Bad Request',679    message: 'abc123'680  })681  t.assert.strictEqual(response3.statusCode, 400)682})683 684test('adding schemaErrorFormatter', async (t) => {685  t.plan(2)686 687  const fastify = Fastify()688 689  fastify.setSchemaErrorFormatter((errors, dataVar) => {690    return new Error('abc')691  })692 693  fastify.post('/', { schema }, echoBody)694 695  const response = await fastify.inject({696    method: 'POST',697    payload: {698      hello: 'michelangelo'699    },700    url: '/'701  })702 703  t.assert.deepStrictEqual(response.json(), {704    statusCode: 400,705    code: 'FST_ERR_VALIDATION',706    error: 'Bad Request',707    message: 'abc'708  })709  t.assert.strictEqual(response.statusCode, 400)710})711 712test('plugin override', async (t) => {713  t.plan(10)714 715  const fastify = Fastify({716    schemaErrorFormatter: (errors, dataVar) => {717      return new Error('B')718    }719  })720 721  fastify.register((instance, opts, done) => {722    instance.setSchemaErrorFormatter((errors, dataVar) => {723      return new Error('C')724    })725 726    instance.post('/d', {727      schema,728      schemaErrorFormatter: (errors, dataVar) => {729        return new Error('D')730      }731    }, function (req, reply) {732      reply.code(200).send(req.body.name)733    })734 735    instance.post('/c', { schema }, echoBody)736 737    instance.register((subinstance, opts, done) => {738      subinstance.post('/stillC', { schema }, echoBody)739      done()740    })741 742    done()743  })744 745  fastify.post('/b', { schema }, echoBody)746 747  fastify.post('/', {748    schema,749    schemaErrorFormatter: (errors, dataVar) => {750      return new Error('A')751    }752  }, echoBody)753 754  const response1 = await fastify.inject({755    method: 'POST',756    payload: {757      hello: 'michelangelo'758    },759    url: '/'760  })761 762  t.assert.deepStrictEqual(response1.json(), {763    statusCode: 400,764    code: 'FST_ERR_VALIDATION',765    error: 'Bad Request',766    message: 'A'767  })768  t.assert.strictEqual(response1.statusCode, 400)769 770  const response2 = await fastify.inject({771    method: 'POST',772    payload: {773      hello: 'michelangelo'774    },775    url: '/b'776  })777 778  t.assert.deepStrictEqual(response2.json(), {779    statusCode: 400,780    code: 'FST_ERR_VALIDATION',781    error: 'Bad Request',782    message: 'B'783  })784  t.assert.strictEqual(response2.statusCode, 400)785 786  const response3 = await fastify.inject({787    method: 'POST',788    payload: {789      hello: 'michelangelo'790    },791    url: '/c'792  })793 794  t.assert.deepStrictEqual(response3.json(), {795    statusCode: 400,796    code: 'FST_ERR_VALIDATION',797    error: 'Bad Request',798    message: 'C'799  })800  t.assert.strictEqual(response3.statusCode, 400)801 802  const response4 = await fastify.inject({803    method: 'POST',804    payload: {805      hello: 'michelangelo'806    },807    url: '/d'808  })809 810  t.assert.deepStrictEqual(response4.json(), {811    statusCode: 400,812    code: 'FST_ERR_VALIDATION',813    error: 'Bad Request',814    message: 'D'815  })816  t.assert.strictEqual(response4.statusCode, 400)817 818  const response5 = await fastify.inject({819    method: 'POST',820    payload: {821      hello: 'michelangelo'822    },823    url: '/stillC'824  })825 826  t.assert.deepStrictEqual(response5.json(), {827    statusCode: 400,828    code: 'FST_ERR_VALIDATION',829    error: 'Bad Request',830    message: 'C'831  })832  t.assert.strictEqual(response5.statusCode, 400)833})834