strong-tie/inbound-calls
0
1'use strict'2 3const { test } = require('node:test')4const localize = require('ajv-i18n')5const Fastify = require('..')6 7test('Example - URI $id', (t, done) => {8 t.plan(1)9 const fastify = Fastify()10 fastify.addSchema({11 $id: 'http://example.com/',12 type: 'object',13 properties: {14 hello: { type: 'string' }15 }16 })17 18 fastify.post('/', {19 handler () { },20 schema: {21 body: {22 type: 'array',23 items: { $ref: 'http://example.com#/properties/hello' }24 }25 }26 })27 28 fastify.ready(err => {29 t.assert.ifError(err)30 done()31 })32})33 34test('Example - string $id', (t, done) => {35 t.plan(1)36 const fastify = Fastify()37 fastify.addSchema({38 $id: 'commonSchema',39 type: 'object',40 properties: {41 hello: { type: 'string' }42 }43 })44 45 fastify.post('/', {46 handler () { },47 schema: {48 body: { $ref: 'commonSchema#' },49 headers: { $ref: 'commonSchema#' }50 }51 })52 53 fastify.ready(err => {54 t.assert.ifError(err)55 done()56 })57})58 59test('Example - get schema', (t, done) => {60 t.plan(1)61 const fastify = Fastify()62 fastify.addSchema({63 $id: 'schemaId',64 type: 'object',65 properties: {66 hello: { type: 'string' }67 }68 })69 70 const mySchemas = fastify.getSchemas()71 const mySchema = fastify.getSchema('schemaId')72 t.assert.deepStrictEqual(mySchemas.schemaId, mySchema)73 done()74})75 76test('Example - get schema encapsulated', async t => {77 const fastify = Fastify()78 79 fastify.addSchema({ $id: 'one', my: 'hello' })80 // will return only `one` schema81 fastify.get('/', (request, reply) => { reply.send(fastify.getSchemas()) })82 83 fastify.register((instance, opts, done) => {84 instance.addSchema({ $id: 'two', my: 'ciao' })85 // will return `one` and `two` schemas86 instance.get('/sub', (request, reply) => { reply.send(instance.getSchemas()) })87 88 instance.register((subinstance, opts, done) => {89 subinstance.addSchema({ $id: 'three', my: 'hola' })90 // will return `one`, `two` and `three`91 subinstance.get('/deep', (request, reply) => { reply.send(subinstance.getSchemas()) })92 done()93 })94 done()95 })96 97 const r1 = await fastify.inject('/')98 const r2 = await fastify.inject('/sub')99 const r3 = await fastify.inject('/deep')100 101 t.assert.deepStrictEqual(Object.keys(r1.json()), ['one'])102 t.assert.deepStrictEqual(Object.keys(r2.json()), ['one', 'two'])103 t.assert.deepStrictEqual(Object.keys(r3.json()), ['one', 'two', 'three'])104})105 106test('Example - validation', (t, done) => {107 t.plan(1)108 const fastify = Fastify({109 ajv: {110 customOptions: {111 allowUnionTypes: true112 }113 }114 })115 const handler = () => { }116 117 const bodyJsonSchema = {118 type: 'object',119 required: ['requiredKey'],120 properties: {121 someKey: { type: 'string' },122 someOtherKey: { type: 'number' },123 requiredKey: {124 type: 'array',125 maxItems: 3,126 items: { type: 'integer' }127 },128 nullableKey: { type: ['number', 'null'] }, // or { type: 'number', nullable: true }129 multipleTypesKey: { type: ['boolean', 'number'] },130 multipleRestrictedTypesKey: {131 oneOf: [132 { type: 'string', maxLength: 5 },133 { type: 'number', minimum: 10 }134 ]135 },136 enumKey: {137 type: 'string',138 enum: ['John', 'Foo']139 },140 notTypeKey: {141 not: { type: 'array' }142 }143 }144 }145 146 const queryStringJsonSchema = {147 type: 'object',148 properties: {149 name: { type: 'string' },150 excitement: { type: 'integer' }151 }152 }153 154 const paramsJsonSchema = {155 type: 'object',156 properties: {157 par1: { type: 'string' },158 par2: { type: 'number' }159 }160 }161 162 const headersJsonSchema = {163 type: 'object',164 properties: {165 'x-foo': { type: 'string' }166 },167 required: ['x-foo']168 }169 170 const schema = {171 body: bodyJsonSchema,172 querystring: queryStringJsonSchema,173 params: paramsJsonSchema,174 headers: headersJsonSchema175 }176 177 fastify.post('/the/url', { schema }, handler)178 fastify.ready(err => {179 t.assert.ifError(err)180 done()181 })182})183 184test('Example - ajv config', (t, done) => {185 t.plan(1)186 187 const fastify = Fastify({188 ajv: {189 plugins: [190 require('ajv-merge-patch')191 ]192 }193 })194 195 fastify.post('/', {196 handler (req, reply) { reply.send({ ok: 1 }) },197 schema: {198 body: {199 $patch: {200 source: {201 type: 'object',202 properties: {203 q: {204 type: 'string'205 }206 }207 },208 with: [209 {210 op: 'add',211 path: '/properties/q',212 value: { type: 'number' }213 }214 ]215 }216 }217 }218 })219 220 fastify.post('/foo', {221 handler (req, reply) { reply.send({ ok: 1 }) },222 schema: {223 body: {224 $merge: {225 source: {226 type: 'object',227 properties: {228 q: {229 type: 'string'230 }231 }232 },233 with: {234 required: ['q']235 }236 }237 }238 }239 })240 241 fastify.ready(err => {242 t.assert.ifError(err)243 done()244 })245})246 247test('Example Joi', (t, done) => {248 t.plan(1)249 const fastify = Fastify()250 const handler = () => { }251 252 const Joi = require('joi')253 fastify.post('/the/url', {254 schema: {255 body: Joi.object().keys({256 hello: Joi.string().required()257 }).required()258 },259 validatorCompiler: ({ schema, method, url, httpPart }) => {260 return data => schema.validate(data)261 }262 }, handler)263 264 fastify.ready(err => {265 t.assert.ifError(err)266 done()267 })268})269 270test('Example yup', (t, done) => {271 t.plan(1)272 const fastify = Fastify()273 const handler = () => { }274 275 const yup = require('yup')276 // Validation options to match ajv's baseline options used in Fastify277 const yupOptions = {278 strict: false,279 abortEarly: false, // return all errors280 stripUnknown: true, // remove additional properties281 recursive: true282 }283 284 fastify.post('/the/url', {285 schema: {286 body: yup.object({287 age: yup.number().integer().required(),288 sub: yup.object().shape({289 name: yup.string().required()290 }).required()291 })292 },293 validatorCompiler: ({ schema, method, url, httpPart }) => {294 return function (data) {295 // with option strict = false, yup `validateSync` function returns the coerced value if validation was successful, or throws if validation failed296 try {297 const result = schema.validateSync(data, yupOptions)298 return { value: result }299 } catch (e) {300 return { error: e }301 }302 }303 }304 }, handler)305 306 fastify.ready(err => {307 t.assert.ifError(err)308 done()309 })310})311 312test('Example - serialization', (t, done) => {313 t.plan(1)314 const fastify = Fastify()315 const handler = () => { }316 317 const schema = {318 response: {319 200: {320 type: 'object',321 properties: {322 value: { type: 'string' },323 otherValue: { type: 'boolean' }324 }325 }326 }327 }328 329 fastify.post('/the/url', { schema }, handler)330 fastify.ready(err => {331 t.assert.ifError(err)332 done()333 })334})335 336test('Example - serialization 2', (t, done) => {337 t.plan(1)338 const fastify = Fastify()339 const handler = () => { }340 341 const schema = {342 response: {343 '2xx': {344 type: 'object',345 properties: {346 value: { type: 'string' },347 otherValue: { type: 'boolean' }348 }349 },350 201: {351 // the contract syntax352 value: { type: 'string' }353 }354 }355 }356 357 fastify.post('/the/url', { schema }, handler)358 fastify.ready(err => {359 t.assert.ifError(err)360 done()361 })362})363 364test('Example - serializator', (t, done) => {365 t.plan(1)366 const fastify = Fastify()367 368 fastify.setSerializerCompiler(({ schema, method, url, httpStatus }) => {369 return data => JSON.stringify(data)370 })371 372 fastify.get('/user', {373 handler (req, reply) {374 reply.send({ id: 1, name: 'Foo', image: 'BIG IMAGE' })375 },376 schema: {377 response: {378 '2xx': {379 type: 'object',380 properties: {381 id: { type: 'number' },382 name: { type: 'string' }383 }384 }385 }386 }387 })388 389 fastify.ready(err => {390 t.assert.ifError(err)391 done()392 })393})394 395test('Example - schemas examples', (t, done) => {396 t.plan(1)397 const fastify = Fastify()398 const handler = () => { }399 400 fastify.addSchema({401 $id: 'http://foo/common.json',402 type: 'object',403 definitions: {404 foo: {405 $id: '#address',406 type: 'object',407 properties: {408 city: { type: 'string' }409 }410 }411 }412 })413 414 fastify.addSchema({415 $id: 'http://foo/shared.json',416 type: 'object',417 definitions: {418 foo: {419 type: 'object',420 properties: {421 city: { type: 'string' }422 }423 }424 }425 })426 427 const refToId = {428 type: 'object',429 definitions: {430 foo: {431 $id: '#address',432 type: 'object',433 properties: {434 city: { type: 'string' }435 }436 }437 },438 properties: {439 home: { $ref: '#address' },440 work: { $ref: '#address' }441 }442 }443 444 const refToDefinitions = {445 type: 'object',446 definitions: {447 foo: {448 $id: '#address',449 type: 'object',450 properties: {451 city: { type: 'string' }452 }453 }454 },455 properties: {456 home: { $ref: '#/definitions/foo' },457 work: { $ref: '#/definitions/foo' }458 }459 }460 461 const refToSharedSchemaId = {462 type: 'object',463 properties: {464 home: { $ref: 'http://foo/common.json#address' },465 work: { $ref: 'http://foo/common.json#address' }466 }467 }468 469 const refToSharedSchemaDefinitions = {470 type: 'object',471 properties: {472 home: { $ref: 'http://foo/shared.json#/definitions/foo' },473 work: { $ref: 'http://foo/shared.json#/definitions/foo' }474 }475 }476 477 fastify.post('/', {478 handler,479 schema: {480 body: refToId,481 headers: refToDefinitions,482 params: refToSharedSchemaId,483 query: refToSharedSchemaDefinitions484 }485 486 })487 488 fastify.ready(err => {489 t.assert.ifError(err)490 done()491 })492})493 494test('should return custom error messages with ajv-errors', (t, done) => {495 t.plan(3)496 497 const fastify = Fastify({498 ajv: {499 customOptions: { allErrors: true },500 plugins: [501 require('ajv-errors')502 ]503 }504 })505 506 const schema = {507 body: {508 type: 'object',509 properties: {510 name: { type: 'string' },511 work: { type: 'string' },512 age: {513 type: 'number',514 errorMessage: {515 type: 'bad age - should be num'516 }517 }518 },519 required: ['name', 'work'],520 errorMessage: {521 required: {522 name: 'name please',523 work: 'work please',524 age: 'age please'525 }526 }527 }528 }529 530 fastify.post('/', { schema }, function (req, reply) {531 reply.code(200).send(req.body.name)532 })533 534 fastify.inject({535 method: 'POST',536 payload: {537 hello: 'salman',538 age: 'bad'539 },540 url: '/'541 }, (err, res) => {542 t.assert.ifError(err)543 t.assert.deepStrictEqual(JSON.parse(res.payload), {544 statusCode: 400,545 code: 'FST_ERR_VALIDATION',546 error: 'Bad Request',547 message: 'body/age bad age - should be num, body name please, body work please'548 })549 t.assert.strictEqual(res.statusCode, 400)550 done()551 })552})553 554test('should be able to handle formats of ajv-formats when added by plugins option', (t, done) => {555 t.plan(3)556 557 const fastify = Fastify({558 ajv: {559 plugins: [560 require('ajv-formats')561 ]562 }563 })564 565 const schema = {566 body: {567 type: 'object',568 properties: {569 id: { type: 'string', format: 'uuid' },570 email: { type: 'string', format: 'email' }571 },572 required: ['id', 'email']573 }574 }575 576 fastify.post('/', { schema }, function (req, reply) {577 reply.code(200).send(req.body.id)578 })579 580 fastify.inject({581 method: 'POST',582 payload: {583 id: '254381a5-888c-4b41-8116-e3b1a54980bd',584 email: 'info@fastify.dev'585 },586 url: '/'587 }, (_err, res) => {588 t.assert.strictEqual(res.body, '254381a5-888c-4b41-8116-e3b1a54980bd')589 t.assert.strictEqual(res.statusCode, 200)590 })591 592 fastify.inject({593 method: 'POST',594 payload: {595 id: 'invalid',596 email: 'info@fastify.dev'597 },598 url: '/'599 }, (_err, res) => {600 t.assert.deepStrictEqual(JSON.parse(res.payload), {601 statusCode: 400,602 code: 'FST_ERR_VALIDATION',603 error: 'Bad Request',604 message: 'body/id must match format "uuid"'605 })606 done()607 })608})609 610test('should return localized error messages with ajv-i18n', (t, done) => {611 t.plan(3)612 613 const schema = {614 body: {615 type: 'object',616 properties: {617 name: { type: 'string' },618 work: { type: 'string' }619 },620 required: ['name', 'work']621 }622 }623 624 const fastify = Fastify({625 ajv: {626 customOptions: { allErrors: true }627 }628 })629 630 fastify.setErrorHandler(function (error, request, reply) {631 if (error.validation) {632 localize.ru(error.validation)633 reply.status(400).send(error.validation)634 return635 }636 reply.send(error)637 })638 639 fastify.post('/', { schema }, function (req, reply) {640 reply.code(200).send(req.body.name)641 })642 643 fastify.inject({644 method: 'POST',645 payload: {646 name: 'salman'647 },648 url: '/'649 }, (err, res) => {650 t.assert.ifError(err)651 t.assert.deepStrictEqual(JSON.parse(res.payload), [{652 instancePath: '',653 keyword: 'required',654 message: 'должно иметь обязательное поле work',655 params: { missingProperty: 'work' },656 schemaPath: '#/required'657 }])658 t.assert.strictEqual(res.statusCode, 400)659 done()660 })661})662 