You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							1753 lines
						
					
					
						
							40 KiB
						
					
					
				
			
		
		
	
	
							1753 lines
						
					
					
						
							40 KiB
						
					
					
				| 'use strict'
 | |
| 
 | |
| const { test } = require('tap')
 | |
| const Fastify = require('..')
 | |
| const fp = require('fastify-plugin')
 | |
| const Ajv = require('ajv')
 | |
| const { kSchemaController } = require('../lib/symbols.js')
 | |
| 
 | |
| const echoParams = (req, reply) => { reply.send(req.params) }
 | |
| const echoBody = (req, reply) => { reply.send(req.body) }
 | |
| 
 | |
| ;['addSchema', 'getSchema', 'getSchemas', 'setValidatorCompiler', 'setSerializerCompiler'].forEach(f => {
 | |
|   test(`Should expose ${f} function`, t => {
 | |
|     t.plan(1)
 | |
|     const fastify = Fastify()
 | |
|     t.equal(typeof fastify[f], 'function')
 | |
|   })
 | |
| })
 | |
| 
 | |
| ;['setValidatorCompiler', 'setSerializerCompiler'].forEach(f => {
 | |
|   test(`cannot call ${f} after binding`, t => {
 | |
|     t.plan(2)
 | |
|     const fastify = Fastify()
 | |
|     t.teardown(fastify.close.bind(fastify))
 | |
|     fastify.listen(0, err => {
 | |
|       t.error(err)
 | |
|       try {
 | |
|         fastify[f](() => { })
 | |
|         t.fail()
 | |
|       } catch (e) {
 | |
|         t.pass()
 | |
|       }
 | |
|     })
 | |
|   })
 | |
| })
 | |
| 
 | |
| test('The schemas should be added to an internal storage', t => {
 | |
|   t.plan(1)
 | |
|   const fastify = Fastify()
 | |
|   const schema = { $id: 'id', my: 'schema' }
 | |
|   fastify.addSchema(schema)
 | |
|   t.same(fastify[kSchemaController].schemaBucket.store, { id: schema })
 | |
| })
 | |
| 
 | |
| test('The schemas should be accessible via getSchemas', t => {
 | |
|   t.plan(1)
 | |
|   const fastify = Fastify()
 | |
| 
 | |
|   const schemas = {
 | |
|     id: { $id: 'id', my: 'schema' },
 | |
|     abc: { $id: 'abc', my: 'schema' },
 | |
|     bcd: { $id: 'bcd', my: 'schema', properties: { a: 'a', b: 1 } }
 | |
|   }
 | |
| 
 | |
|   Object.values(schemas).forEach(schema => { fastify.addSchema(schema) })
 | |
|   t.same(fastify.getSchemas(), schemas)
 | |
| })
 | |
| 
 | |
| test('The schema should be accessible by id via getSchema', t => {
 | |
|   t.plan(5)
 | |
|   const fastify = Fastify()
 | |
| 
 | |
|   const schemas = [
 | |
|     { $id: 'id', my: 'schema' },
 | |
|     { $id: 'abc', my: 'schema' },
 | |
|     { $id: 'bcd', my: 'schema', properties: { a: 'a', b: 1 } }
 | |
|   ]
 | |
|   schemas.forEach(schema => { fastify.addSchema(schema) })
 | |
|   t.same(fastify.getSchema('abc'), schemas[1])
 | |
|   t.same(fastify.getSchema('id'), schemas[0])
 | |
|   t.same(fastify.getSchema('foo'), undefined)
 | |
| 
 | |
|   fastify.register((instance, opts, done) => {
 | |
|     const pluginSchema = { $id: 'cde', my: 'schema' }
 | |
|     instance.addSchema(pluginSchema)
 | |
|     t.same(instance.getSchema('cde'), pluginSchema)
 | |
|     done()
 | |
|   })
 | |
| 
 | |
|   fastify.ready(err => t.error(err))
 | |
| })
 | |
| 
 | |
| test('Get validatorCompiler after setValidatorCompiler', t => {
 | |
|   t.plan(2)
 | |
|   const myCompiler = () => { }
 | |
|   const fastify = Fastify()
 | |
|   fastify.setValidatorCompiler(myCompiler)
 | |
|   const sc = fastify.validatorCompiler
 | |
|   t.ok(Object.is(myCompiler, sc))
 | |
|   fastify.ready(err => t.error(err))
 | |
| })
 | |
| 
 | |
| test('Get serializerCompiler after setSerializerCompiler', t => {
 | |
|   t.plan(2)
 | |
|   const myCompiler = () => { }
 | |
|   const fastify = Fastify()
 | |
|   fastify.setSerializerCompiler(myCompiler)
 | |
|   const sc = fastify.serializerCompiler
 | |
|   t.ok(Object.is(myCompiler, sc))
 | |
|   fastify.ready(err => t.error(err))
 | |
| })
 | |
| 
 | |
| test('Get compilers is empty when settle on routes', t => {
 | |
|   t.plan(3)
 | |
| 
 | |
|   const fastify = Fastify()
 | |
| 
 | |
|   fastify.post('/', {
 | |
|     schema: {
 | |
|       body: { type: 'object', properties: { hello: { type: 'string' } } },
 | |
|       response: { '2xx': { foo: { type: 'array', items: { type: 'string' } } } }
 | |
|     },
 | |
|     validatorCompiler: ({ schema, method, url, httpPart }) => {},
 | |
|     serializerCompiler: ({ schema, method, url, httpPart }) => {}
 | |
|   }, function (req, reply) {
 | |
|     reply.send('ok')
 | |
|   })
 | |
| 
 | |
|   fastify.inject({
 | |
|     method: 'POST',
 | |
|     payload: {},
 | |
|     url: '/'
 | |
|   }, (err, res) => {
 | |
|     t.error(err)
 | |
|     t.equal(fastify.validatorCompiler, undefined)
 | |
|     t.equal(fastify.serializerCompiler, undefined)
 | |
|   })
 | |
| })
 | |
| 
 | |
| test('Should throw if the $id property is missing', t => {
 | |
|   t.plan(1)
 | |
|   const fastify = Fastify()
 | |
|   try {
 | |
|     fastify.addSchema({ type: 'string' })
 | |
|     t.fail()
 | |
|   } catch (err) {
 | |
|     t.equal(err.code, 'FST_ERR_SCH_MISSING_ID')
 | |
|   }
 | |
| })
 | |
| 
 | |
| test('Cannot add multiple times the same id', t => {
 | |
|   t.plan(2)
 | |
|   const fastify = Fastify()
 | |
| 
 | |
|   fastify.addSchema({ $id: 'id' })
 | |
|   try {
 | |
|     fastify.addSchema({ $id: 'id' })
 | |
|   } catch (err) {
 | |
|     t.equal(err.code, 'FST_ERR_SCH_ALREADY_PRESENT')
 | |
|     t.equal(err.message, 'Schema with id \'id\' already declared!')
 | |
|   }
 | |
| })
 | |
| 
 | |
| test('Cannot add schema for query and querystring', t => {
 | |
|   t.plan(2)
 | |
|   const fastify = Fastify()
 | |
| 
 | |
|   fastify.get('/', {
 | |
|     handler: () => {},
 | |
|     schema: {
 | |
|       query: { foo: { type: 'string' } },
 | |
|       querystring: { foo: { type: 'string' } }
 | |
|     }
 | |
|   })
 | |
| 
 | |
|   fastify.ready(err => {
 | |
|     t.equal(err.code, 'FST_ERR_SCH_DUPLICATE')
 | |
|     t.equal(err.message, 'Schema with \'querystring\' already present!')
 | |
|   })
 | |
| })
 | |
| 
 | |
| test('Should throw of the schema does not exists in input', t => {
 | |
|   t.plan(2)
 | |
|   const fastify = Fastify()
 | |
| 
 | |
|   fastify.get('/:id', {
 | |
|     handler: echoParams,
 | |
|     schema: {
 | |
|       params: {
 | |
|         name: { $ref: '#notExist' }
 | |
|       }
 | |
|     }
 | |
|   })
 | |
| 
 | |
|   fastify.ready(err => {
 | |
|     t.equal(err.code, 'FST_ERR_SCH_VALIDATION_BUILD')
 | |
|     t.equal(err.message, "Failed building the validation schema for GET: /:id, due to error can't resolve reference #notExist from id #")
 | |
|   })
 | |
| })
 | |
| 
 | |
| test('Should throw of the schema does not exists in output', t => {
 | |
|   t.plan(2)
 | |
|   const fastify = Fastify()
 | |
| 
 | |
|   fastify.get('/:id', {
 | |
|     handler: echoParams,
 | |
|     schema: {
 | |
|       response: {
 | |
|         '2xx': {
 | |
|           name: { $ref: '#notExist' }
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   })
 | |
| 
 | |
|   fastify.ready(err => {
 | |
|     t.equal(err.code, 'FST_ERR_SCH_SERIALIZATION_BUILD')
 | |
|     t.match(err.message, /^Failed building the serialization schema for GET: \/:id, due to error Cannot read propert.*/) // error from fast-json-strinfigy
 | |
|   })
 | |
| })
 | |
| 
 | |
| test('Should not change the input schemas', t => {
 | |
|   t.plan(4)
 | |
| 
 | |
|   const theSchema = {
 | |
|     $id: 'helloSchema',
 | |
|     type: 'object',
 | |
|     definitions: {
 | |
|       hello: { type: 'string' }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   const fastify = Fastify()
 | |
|   fastify.post('/', {
 | |
|     handler: echoBody,
 | |
|     schema: {
 | |
|       body: {
 | |
|         type: 'object',
 | |
|         additionalProperties: false,
 | |
|         properties: {
 | |
|           name: { $ref: 'helloSchema#/definitions/hello' }
 | |
|         }
 | |
|       },
 | |
|       response: {
 | |
|         '2xx': {
 | |
|           name: { $ref: 'helloSchema#/definitions/hello' }
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   })
 | |
|   fastify.addSchema(theSchema)
 | |
| 
 | |
|   fastify.inject({
 | |
|     url: '/',
 | |
|     method: 'POST',
 | |
|     payload: { name: 'Foo', surname: 'Bar' }
 | |
|   }, (err, res) => {
 | |
|     t.error(err)
 | |
|     t.same(res.json(), { name: 'Foo' })
 | |
|     t.ok(theSchema.$id, 'the $id is not removed')
 | |
|     t.same(fastify.getSchema('helloSchema'), theSchema)
 | |
|   })
 | |
| })
 | |
| 
 | |
| test('First level $ref', t => {
 | |
|   t.plan(2)
 | |
|   const fastify = Fastify()
 | |
| 
 | |
|   fastify.addSchema({
 | |
|     $id: 'test',
 | |
|     type: 'object',
 | |
|     properties: {
 | |
|       id: { type: 'number' }
 | |
|     }
 | |
|   })
 | |
| 
 | |
|   fastify.get('/:id', {
 | |
|     handler: (req, reply) => {
 | |
|       reply.send({ id: req.params.id * 2, ignore: 'it' })
 | |
|     },
 | |
|     schema: {
 | |
|       params: { $ref: 'test#' },
 | |
|       response: {
 | |
|         200: { $ref: 'test#' }
 | |
|       }
 | |
|     }
 | |
|   })
 | |
| 
 | |
|   fastify.inject({
 | |
|     method: 'GET',
 | |
|     url: '/123'
 | |
|   }, (err, res) => {
 | |
|     t.error(err)
 | |
|     t.same(res.json(), { id: 246 })
 | |
|   })
 | |
| })
 | |
| 
 | |
| test('Customize validator compiler in instance and route', t => {
 | |
|   t.plan(28)
 | |
|   const fastify = Fastify()
 | |
| 
 | |
|   fastify.setValidatorCompiler(({ schema, method, url, httpPart }) => {
 | |
|     t.equal(method, 'POST') // run 4 times
 | |
|     t.equal(url, '/:id') // run 4 times
 | |
|     switch (httpPart) {
 | |
|       case 'body':
 | |
|         t.pass('body evaluated')
 | |
|         return body => {
 | |
|           t.same(body, { foo: ['bar', 'BAR'] })
 | |
|           return true
 | |
|         }
 | |
|       case 'params':
 | |
|         t.pass('params evaluated')
 | |
|         return params => {
 | |
|           t.same(params, { id: 1234 })
 | |
|           return true
 | |
|         }
 | |
|       case 'querystring':
 | |
|         t.pass('querystring evaluated')
 | |
|         return query => {
 | |
|           t.same(query, { lang: 'en' })
 | |
|           return true
 | |
|         }
 | |
|       case 'headers':
 | |
|         t.pass('headers evaluated')
 | |
|         return headers => {
 | |
|           t.match(headers, { x: 'hello' })
 | |
|           return true
 | |
|         }
 | |
|       case '2xx':
 | |
|         t.fail('the validator doesn\'t process the response')
 | |
|         break
 | |
|       default:
 | |
|         t.fail(`unknown httpPart ${httpPart}`)
 | |
|     }
 | |
|   })
 | |
| 
 | |
|   fastify.post('/:id', {
 | |
|     handler: echoBody,
 | |
|     schema: {
 | |
|       query: { lang: { type: 'string', enum: ['it', 'en'] } },
 | |
|       headers: { x: { type: 'string' } },
 | |
|       params: { id: { type: 'number' } },
 | |
|       body: { foo: { type: 'array' } },
 | |
|       response: {
 | |
|         '2xx': { foo: { type: 'array', items: { type: 'string' } } }
 | |
|       }
 | |
|     }
 | |
|   })
 | |
| 
 | |
|   fastify.get('/wow/:id', {
 | |
|     handler: echoParams,
 | |
|     validatorCompiler: ({ schema, method, url, httpPart }) => {
 | |
|       t.equal(method, 'GET') // run 3 times (params, headers, query)
 | |
|       t.equal(url, '/wow/:id') // run 4 times
 | |
|       return () => { return true } // ignore the validation
 | |
|     },
 | |
|     schema: {
 | |
|       query: { lang: { type: 'string', enum: ['it', 'en'] } },
 | |
|       headers: { x: { type: 'string' } },
 | |
|       params: { id: { type: 'number' } },
 | |
|       response: { '2xx': { foo: { type: 'array', items: { type: 'string' } } } }
 | |
|     }
 | |
|   })
 | |
| 
 | |
|   fastify.inject({
 | |
|     url: '/1234',
 | |
|     method: 'POST',
 | |
|     headers: { x: 'hello' },
 | |
|     query: { lang: 'en' },
 | |
|     payload: { foo: ['bar', 'BAR'] }
 | |
|   }, (err, res) => {
 | |
|     t.error(err)
 | |
|     t.equal(res.statusCode, 200)
 | |
|     t.same(res.json(), { foo: ['bar', 'BAR'] })
 | |
|   })
 | |
| 
 | |
|   fastify.inject({
 | |
|     url: '/wow/should-be-a-num',
 | |
|     method: 'GET',
 | |
|     headers: { x: 'hello' },
 | |
|     query: { lang: 'jp' } // not in the enum
 | |
|   }, (err, res) => {
 | |
|     t.error(err)
 | |
|     t.equal(res.statusCode, 200) // the validation is always true
 | |
|     t.same(res.json(), {})
 | |
|   })
 | |
| })
 | |
| 
 | |
| test('Use the same schema across multiple routes', t => {
 | |
|   t.plan(4)
 | |
|   const fastify = Fastify()
 | |
| 
 | |
|   fastify.addSchema({
 | |
|     $id: 'test',
 | |
|     type: 'object',
 | |
|     properties: {
 | |
|       id: { type: 'number' }
 | |
|     }
 | |
|   })
 | |
| 
 | |
|   fastify.get('/first/:id', {
 | |
|     schema: {
 | |
|       params: { id: { $ref: 'test#/properties/id' } }
 | |
|     },
 | |
|     handler: (req, reply) => {
 | |
|       reply.send(typeof req.params.id)
 | |
|     }
 | |
|   })
 | |
| 
 | |
|   fastify.get('/second/:id', {
 | |
|     schema: {
 | |
|       params: { id: { $ref: 'test#/properties/id' } }
 | |
|     },
 | |
|     handler: (req, reply) => {
 | |
|       reply.send(typeof req.params.id)
 | |
|     }
 | |
|   })
 | |
| 
 | |
|   fastify.inject({
 | |
|     method: 'GET',
 | |
|     url: '/first/123'
 | |
|   }, (err, res) => {
 | |
|     t.error(err)
 | |
|     t.equal(res.payload, 'number')
 | |
|   })
 | |
| 
 | |
|   fastify.inject({
 | |
|     method: 'GET',
 | |
|     url: '/second/123'
 | |
|   }, (err, res) => {
 | |
|     t.error(err)
 | |
|     t.equal(res.payload, 'number')
 | |
|   })
 | |
| })
 | |
| 
 | |
| test('Encapsulation should intervene', t => {
 | |
|   t.plan(2)
 | |
|   const fastify = Fastify()
 | |
| 
 | |
|   fastify.register((instance, opts, done) => {
 | |
|     instance.addSchema({
 | |
|       $id: 'encapsulation',
 | |
|       type: 'object',
 | |
|       properties: {
 | |
|         id: { type: 'number' }
 | |
|       }
 | |
|     })
 | |
|     done()
 | |
|   })
 | |
| 
 | |
|   fastify.register((instance, opts, done) => {
 | |
|     instance.get('/:id', {
 | |
|       handler: echoParams,
 | |
|       schema: {
 | |
|         params: { id: { $ref: 'encapsulation#/properties/id' } }
 | |
|       }
 | |
|     })
 | |
|     done()
 | |
|   })
 | |
| 
 | |
|   fastify.ready(err => {
 | |
|     t.equal(err.code, 'FST_ERR_SCH_VALIDATION_BUILD')
 | |
|     t.equal(err.message, "Failed building the validation schema for GET: /:id, due to error can't resolve reference encapsulation#/properties/id from id #")
 | |
|   })
 | |
| })
 | |
| 
 | |
| test('Encapsulation isolation', t => {
 | |
|   t.plan(1)
 | |
|   const fastify = Fastify()
 | |
| 
 | |
|   fastify.register((instance, opts, done) => {
 | |
|     instance.addSchema({ $id: 'id' })
 | |
|     done()
 | |
|   })
 | |
| 
 | |
|   fastify.register((instance, opts, done) => {
 | |
|     instance.addSchema({ $id: 'id' })
 | |
|     done()
 | |
|   })
 | |
| 
 | |
|   fastify.ready(err => t.error(err))
 | |
| })
 | |
| 
 | |
| test('Add schema after register', t => {
 | |
|   t.plan(5)
 | |
| 
 | |
|   const fastify = Fastify()
 | |
|   fastify.register((instance, opts, done) => {
 | |
|     instance.get('/:id', {
 | |
|       handler: echoParams,
 | |
|       schema: {
 | |
|         params: { $ref: 'test#' }
 | |
|       }
 | |
|     })
 | |
| 
 | |
|     // add it to the parent instance
 | |
|     fastify.addSchema({
 | |
|       $id: 'test',
 | |
|       type: 'object',
 | |
|       properties: {
 | |
|         id: { type: 'number' }
 | |
|       }
 | |
|     })
 | |
| 
 | |
|     try {
 | |
|       instance.addSchema({ $id: 'test' })
 | |
|     } catch (err) {
 | |
|       t.equal(err.code, 'FST_ERR_SCH_ALREADY_PRESENT')
 | |
|       t.equal(err.message, 'Schema with id \'test\' already declared!')
 | |
|     }
 | |
|     done()
 | |
|   })
 | |
| 
 | |
|   fastify.inject({
 | |
|     method: 'GET',
 | |
|     url: '/4242'
 | |
|   }, (err, res) => {
 | |
|     t.error(err)
 | |
|     t.equal(res.statusCode, 200)
 | |
|     t.same(res.json(), { id: 4242 })
 | |
|   })
 | |
| })
 | |
| 
 | |
| test('Encapsulation isolation for getSchemas', t => {
 | |
|   t.plan(5)
 | |
|   const fastify = Fastify()
 | |
| 
 | |
|   let pluginDeepOneSide
 | |
|   let pluginDeepOne
 | |
|   let pluginDeepTwo
 | |
| 
 | |
|   const schemas = {
 | |
|     z: { $id: 'z', my: 'schema' },
 | |
|     a: { $id: 'a', my: 'schema' },
 | |
|     b: { $id: 'b', my: 'schema' },
 | |
|     c: { $id: 'c', my: 'schema', properties: { a: 'a', b: 1 } }
 | |
|   }
 | |
| 
 | |
|   fastify.addSchema(schemas.z)
 | |
| 
 | |
|   fastify.register((instance, opts, done) => {
 | |
|     instance.addSchema(schemas.a)
 | |
|     pluginDeepOneSide = instance
 | |
|     done()
 | |
|   })
 | |
| 
 | |
|   fastify.register((instance, opts, done) => {
 | |
|     instance.addSchema(schemas.b)
 | |
|     instance.register((subinstance, opts, done) => {
 | |
|       subinstance.addSchema(schemas.c)
 | |
|       pluginDeepTwo = subinstance
 | |
|       done()
 | |
|     })
 | |
|     pluginDeepOne = instance
 | |
|     done()
 | |
|   })
 | |
| 
 | |
|   fastify.ready(err => {
 | |
|     t.error(err)
 | |
|     t.same(fastify.getSchemas(), { z: schemas.z })
 | |
|     t.same(pluginDeepOneSide.getSchemas(), { z: schemas.z, a: schemas.a })
 | |
|     t.same(pluginDeepOne.getSchemas(), { z: schemas.z, b: schemas.b })
 | |
|     t.same(pluginDeepTwo.getSchemas(), { z: schemas.z, b: schemas.b, c: schemas.c })
 | |
|   })
 | |
| })
 | |
| 
 | |
| test('Use the same schema id in different places', t => {
 | |
|   t.plan(1)
 | |
|   const fastify = Fastify()
 | |
| 
 | |
|   fastify.addSchema({
 | |
|     $id: 'test',
 | |
|     type: 'object',
 | |
|     properties: {
 | |
|       id: { type: 'number' }
 | |
|     }
 | |
|   })
 | |
| 
 | |
|   fastify.get('/:id', {
 | |
|     handler: echoParams,
 | |
|     schema: {
 | |
|       response: {
 | |
|         200: {
 | |
|           type: 'array',
 | |
|           items: { $ref: 'test#/properties/id' }
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   })
 | |
| 
 | |
|   fastify.post('/:id', {
 | |
|     handler: echoBody,
 | |
|     schema: {
 | |
|       body: { id: { $ref: 'test#/properties/id' } },
 | |
|       response: {
 | |
|         200: { id: { $ref: 'test#/properties/id' } }
 | |
|       }
 | |
|     }
 | |
|   })
 | |
| 
 | |
|   fastify.ready(err => t.error(err))
 | |
| })
 | |
| 
 | |
| test('Get schema anyway should not add `properties` if allOf is present', t => {
 | |
|   t.plan(1)
 | |
|   const fastify = Fastify()
 | |
| 
 | |
|   fastify.addSchema({
 | |
|     $id: 'first',
 | |
|     type: 'object',
 | |
|     properties: {
 | |
|       first: { type: 'number' }
 | |
|     }
 | |
|   })
 | |
| 
 | |
|   fastify.addSchema({
 | |
|     $id: 'second',
 | |
|     type: 'object',
 | |
|     allOf: [
 | |
|       {
 | |
|         type: 'object',
 | |
|         properties: {
 | |
|           second: { type: 'number' }
 | |
|         }
 | |
|       },
 | |
|       fastify.getSchema('first')
 | |
|     ]
 | |
|   })
 | |
| 
 | |
|   fastify.get('/', {
 | |
|     handler: () => {},
 | |
|     schema: {
 | |
|       querystring: fastify.getSchema('second'),
 | |
|       response: { 200: fastify.getSchema('second') }
 | |
|     }
 | |
|   })
 | |
| 
 | |
|   fastify.ready(err => t.error(err))
 | |
| })
 | |
| 
 | |
| test('Get schema anyway should not add `properties` if oneOf is present', t => {
 | |
|   t.plan(1)
 | |
|   const fastify = Fastify()
 | |
| 
 | |
|   fastify.addSchema({
 | |
|     $id: 'first',
 | |
|     type: 'object',
 | |
|     properties: {
 | |
|       first: { type: 'number' }
 | |
|     }
 | |
|   })
 | |
| 
 | |
|   fastify.addSchema({
 | |
|     $id: 'second',
 | |
|     type: 'object',
 | |
|     oneOf: [
 | |
|       {
 | |
|         type: 'object',
 | |
|         properties: {
 | |
|           second: { type: 'number' }
 | |
|         }
 | |
|       },
 | |
|       fastify.getSchema('first')
 | |
|     ]
 | |
|   })
 | |
| 
 | |
|   fastify.get('/', {
 | |
|     handler: () => {},
 | |
|     schema: {
 | |
|       querystring: fastify.getSchema('second'),
 | |
|       response: { 200: fastify.getSchema('second') }
 | |
|     }
 | |
|   })
 | |
| 
 | |
|   fastify.ready(err => t.error(err))
 | |
| })
 | |
| 
 | |
| test('Get schema anyway should not add `properties` if anyOf is present', t => {
 | |
|   t.plan(1)
 | |
|   const fastify = Fastify()
 | |
| 
 | |
|   fastify.addSchema({
 | |
|     $id: 'first',
 | |
|     type: 'object',
 | |
|     properties: {
 | |
|       first: { type: 'number' }
 | |
|     }
 | |
|   })
 | |
| 
 | |
|   fastify.addSchema({
 | |
|     $id: 'second',
 | |
|     type: 'object',
 | |
|     anyOf: [
 | |
|       {
 | |
|         type: 'object',
 | |
|         properties: {
 | |
|           second: { type: 'number' }
 | |
|         }
 | |
|       },
 | |
|       fastify.getSchema('first')
 | |
|     ]
 | |
|   })
 | |
| 
 | |
|   fastify.get('/', {
 | |
|     handler: () => {},
 | |
|     schema: {
 | |
|       querystring: fastify.getSchema('second'),
 | |
|       response: { 200: fastify.getSchema('second') }
 | |
|     }
 | |
|   })
 | |
| 
 | |
|   fastify.ready(err => t.error(err))
 | |
| })
 | |
| 
 | |
| test('Shared schema should be ignored in string enum', t => {
 | |
|   t.plan(2)
 | |
|   const fastify = Fastify()
 | |
| 
 | |
|   fastify.get('/:lang', {
 | |
|     handler: echoParams,
 | |
|     schema: {
 | |
|       params: {
 | |
|         type: 'object',
 | |
|         properties: {
 | |
|           lang: {
 | |
|             type: 'string',
 | |
|             enum: ['Javascript', 'C++', 'C#']
 | |
|           }
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   })
 | |
| 
 | |
|   fastify.inject('/C%23', (err, res) => {
 | |
|     t.error(err)
 | |
|     t.same(res.json(), { lang: 'C#' })
 | |
|   })
 | |
| })
 | |
| 
 | |
| test('Shared schema should NOT be ignored in != string enum', t => {
 | |
|   t.plan(2)
 | |
|   const fastify = Fastify()
 | |
| 
 | |
|   fastify.addSchema({
 | |
|     $id: 'C',
 | |
|     type: 'object',
 | |
|     properties: {
 | |
|       lang: {
 | |
|         type: 'string',
 | |
|         enum: ['Javascript', 'C++', 'C#']
 | |
|       }
 | |
|     }
 | |
|   })
 | |
| 
 | |
|   fastify.post('/:lang', {
 | |
|     handler: echoBody,
 | |
|     schema: {
 | |
|       body: fastify.getSchema('C')
 | |
|     }
 | |
|   })
 | |
| 
 | |
|   fastify.inject({
 | |
|     url: '/',
 | |
|     method: 'POST',
 | |
|     payload: { lang: 'C#' }
 | |
|   }, (err, res) => {
 | |
|     t.error(err)
 | |
|     t.same(res.json(), { lang: 'C#' })
 | |
|   })
 | |
| })
 | |
| 
 | |
| test('Case insensitive header validation', t => {
 | |
|   t.plan(2)
 | |
|   const fastify = Fastify()
 | |
|   fastify.get('/', {
 | |
|     handler: (req, reply) => {
 | |
|       reply.code(200).send(req.headers.foobar)
 | |
|     },
 | |
|     schema: {
 | |
|       headers: {
 | |
|         type: 'object',
 | |
|         required: ['FooBar'],
 | |
|         properties: {
 | |
|           FooBar: { type: 'string' }
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   })
 | |
|   fastify.inject({
 | |
|     url: '/',
 | |
|     method: 'GET',
 | |
|     headers: {
 | |
|       FooBar: 'Baz'
 | |
|     }
 | |
|   }, (err, res) => {
 | |
|     t.error(err)
 | |
|     t.equal(res.payload, 'Baz')
 | |
|   })
 | |
| })
 | |
| 
 | |
| test('Not evaluate json-schema $schema keyword', t => {
 | |
|   t.plan(2)
 | |
|   const fastify = Fastify()
 | |
|   fastify.post('/', {
 | |
|     handler: echoBody,
 | |
|     schema: {
 | |
|       body: {
 | |
|         $schema: 'http://json-schema.org/draft-07/schema#',
 | |
|         type: 'object',
 | |
|         additionalProperties: false,
 | |
|         properties: {
 | |
|           hello: {
 | |
|             type: 'string'
 | |
|           }
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   })
 | |
|   fastify.inject({
 | |
|     url: '/',
 | |
|     method: 'POST',
 | |
|     body: { hello: 'world', foo: 'bar' }
 | |
|   }, (err, res) => {
 | |
|     t.error(err)
 | |
|     t.same(res.json(), { hello: 'world' })
 | |
|   })
 | |
| })
 | |
| 
 | |
| test('Validation context in validation result', t => {
 | |
|   t.plan(5)
 | |
|   const fastify = Fastify()
 | |
|   // custom error handler to expose validation context in response, so we can test it later
 | |
|   fastify.setErrorHandler((err, request, reply) => {
 | |
|     t.equal(err instanceof Error, true)
 | |
|     t.ok(err.validation, 'detailed errors')
 | |
|     t.equal(err.validationContext, 'body')
 | |
|     reply.send()
 | |
|   })
 | |
|   fastify.get('/', {
 | |
|     handler: echoParams,
 | |
|     schema: {
 | |
|       body: {
 | |
|         type: 'object',
 | |
|         required: ['hello'],
 | |
|         properties: {
 | |
|           hello: { type: 'string' }
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   })
 | |
|   fastify.inject({
 | |
|     method: 'GET',
 | |
|     url: '/',
 | |
|     payload: {} // body lacks required field, will fail validation
 | |
|   }, (err, res) => {
 | |
|     t.error(err)
 | |
|     t.equal(res.statusCode, 400)
 | |
|   })
 | |
| })
 | |
| 
 | |
| test('The schema build should not modify the input', t => {
 | |
|   t.plan(3)
 | |
|   const fastify = Fastify()
 | |
| 
 | |
|   const first = {
 | |
|     $id: 'first',
 | |
|     type: 'object',
 | |
|     properties: {
 | |
|       first: {
 | |
|         type: 'number'
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   fastify.addSchema(first)
 | |
| 
 | |
|   fastify.addSchema({
 | |
|     $id: 'second',
 | |
|     type: 'object',
 | |
|     allOf: [
 | |
|       {
 | |
|         type: 'object',
 | |
|         properties: {
 | |
|           second: {
 | |
|             type: 'number'
 | |
|           }
 | |
|         }
 | |
|       },
 | |
|       { $ref: 'first#' }
 | |
|     ]
 | |
|   })
 | |
| 
 | |
|   fastify.get('/', {
 | |
|     schema: {
 | |
|       description: 'get',
 | |
|       body: { $ref: 'second#' },
 | |
|       response: {
 | |
|         200: { $ref: 'second#' }
 | |
|       }
 | |
|     },
 | |
|     handler: (request, reply) => {
 | |
|       reply.send({ hello: 'world' })
 | |
|     }
 | |
|   })
 | |
| 
 | |
|   fastify.patch('/', {
 | |
|     schema: {
 | |
|       description: 'patch',
 | |
|       body: { $ref: 'first#' },
 | |
|       response: {
 | |
|         200: { $ref: 'first#' }
 | |
|       }
 | |
|     },
 | |
|     handler: (request, reply) => {
 | |
|       reply.send({ hello: 'world' })
 | |
|     }
 | |
|   })
 | |
| 
 | |
|   t.ok(first.$id)
 | |
|   fastify.ready(err => {
 | |
|     t.error(err)
 | |
|     t.ok(first.$id)
 | |
|   })
 | |
| })
 | |
| 
 | |
| test('Cross schema reference with encapsulation references', t => {
 | |
|   t.plan(1)
 | |
| 
 | |
|   const fastify = Fastify()
 | |
|   fastify.addSchema({ $id: 'http://foo/item', type: 'object', properties: { foo: { type: 'string' } } })
 | |
| 
 | |
|   const refItem = { $ref: 'http://foo/item#' }
 | |
| 
 | |
|   fastify.addSchema({
 | |
|     $id: 'itemList',
 | |
|     type: 'array',
 | |
|     items: refItem
 | |
|   })
 | |
| 
 | |
|   fastify.register((instance, opts, done) => {
 | |
|     instance.addSchema({
 | |
|       $id: 'encapsulation',
 | |
|       type: 'object',
 | |
|       properties: {
 | |
|         id: { type: 'number' },
 | |
|         item: refItem,
 | |
|         secondItem: refItem
 | |
|       }
 | |
|     })
 | |
| 
 | |
|     const multipleRef = {
 | |
|       type: 'object',
 | |
|       properties: {
 | |
|         a: { $ref: 'itemList#' },
 | |
|         b: refItem,
 | |
|         c: refItem,
 | |
|         d: refItem
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     instance.get('/get', { schema: { response: { 200: multipleRef } } }, () => { })
 | |
|     instance.get('/double-get', { schema: { body: multipleRef, response: { 200: multipleRef } } }, () => { })
 | |
|     instance.post('/post', { schema: { body: multipleRef, response: { 200: multipleRef } } }, () => { })
 | |
|     instance.post('/double', { schema: { response: { 200: { $ref: 'encapsulation' } } } }, () => { })
 | |
|     done()
 | |
|   }, { prefix: '/foo' })
 | |
| 
 | |
|   fastify.post('/post', { schema: { body: refItem, response: { 200: refItem } } }, () => { })
 | |
|   fastify.get('/get', { schema: { body: refItem, response: { 200: refItem } } }, () => { })
 | |
| 
 | |
|   fastify.ready(err => {
 | |
|     t.error(err)
 | |
|   })
 | |
| })
 | |
| 
 | |
| test('Check how many AJV instances are built #1', t => {
 | |
|   t.plan(12)
 | |
|   const fastify = Fastify()
 | |
|   addRandomRoute(fastify) // this trigger the schema validation creation
 | |
|   t.notOk(fastify.validatorCompiler, 'validator not initialized')
 | |
| 
 | |
|   const instances = []
 | |
|   fastify.register((instance, opts, done) => {
 | |
|     t.notOk(fastify.validatorCompiler, 'validator not initialized')
 | |
|     instances.push(instance)
 | |
|     done()
 | |
|   })
 | |
|   fastify.register((instance, opts, done) => {
 | |
|     t.notOk(fastify.validatorCompiler, 'validator not initialized')
 | |
|     addRandomRoute(instance)
 | |
|     instances.push(instance)
 | |
|     done()
 | |
|     instance.register((instance, opts, done) => {
 | |
|       t.notOk(fastify.validatorCompiler, 'validator not initialized')
 | |
|       addRandomRoute(instance)
 | |
|       instances.push(instance)
 | |
|       done()
 | |
|     })
 | |
|   })
 | |
| 
 | |
|   fastify.ready(err => {
 | |
|     t.error(err)
 | |
| 
 | |
|     t.ok(fastify.validatorCompiler, 'validator initialized on preReady')
 | |
|     fastify.validatorCompiler.checkPointer = true
 | |
|     instances.forEach(i => {
 | |
|       t.ok(i.validatorCompiler, 'validator initialized on preReady')
 | |
|       t.equal(i.validatorCompiler.checkPointer, true, 'validator is only one for all the instances')
 | |
|     })
 | |
|   })
 | |
| })
 | |
| 
 | |
| test('onReady hook has the compilers ready', t => {
 | |
|   t.plan(6)
 | |
| 
 | |
|   const fastify = Fastify()
 | |
| 
 | |
|   fastify.get(`/${Math.random()}`, {
 | |
|     handler: (req, reply) => reply.send(),
 | |
|     schema: {
 | |
|       body: { type: 'object' },
 | |
|       response: { 200: { type: 'object' } }
 | |
|     }
 | |
|   })
 | |
| 
 | |
|   fastify.addHook('onReady', function (done) {
 | |
|     t.ok(this.validatorCompiler)
 | |
|     t.ok(this.serializerCompiler)
 | |
|     done()
 | |
|   })
 | |
| 
 | |
|   let hookCallCounter = 0
 | |
|   fastify.register(async (i, o) => {
 | |
|     i.addHook('onReady', function (done) {
 | |
|       t.ok(this.validatorCompiler)
 | |
|       t.ok(this.serializerCompiler)
 | |
|       done()
 | |
|     })
 | |
| 
 | |
|     i.register(async (i, o) => {})
 | |
| 
 | |
|     i.addHook('onReady', function (done) {
 | |
|       hookCallCounter++
 | |
|       done()
 | |
|     })
 | |
|   })
 | |
| 
 | |
|   fastify.ready(err => {
 | |
|     t.error(err)
 | |
|     t.equal(hookCallCounter, 1, 'it is called once')
 | |
|   })
 | |
| })
 | |
| 
 | |
| test('Check how many AJV instances are built #2 - verify validatorPool', t => {
 | |
|   t.plan(13)
 | |
|   const fastify = Fastify()
 | |
|   t.notOk(fastify.validatorCompiler, 'validator not initialized')
 | |
| 
 | |
|   fastify.register(function sibling1 (instance, opts, done) {
 | |
|     addRandomRoute(instance)
 | |
|     t.notOk(instance.validatorCompiler, 'validator not initialized')
 | |
|     instance.ready(() => {
 | |
|       t.ok(instance.validatorCompiler, 'validator is initialized')
 | |
|       instance.validatorCompiler.sharedPool = 1
 | |
|     })
 | |
|     instance.after(() => {
 | |
|       t.notOk(instance.validatorCompiler, 'validator not initialized')
 | |
|     })
 | |
|     done()
 | |
|   })
 | |
| 
 | |
|   fastify.register(function sibling2 (instance, opts, done) {
 | |
|     addRandomRoute(instance)
 | |
|     t.notOk(instance.validatorCompiler, 'validator not initialized')
 | |
|     instance.ready(() => {
 | |
|       t.equal(instance.validatorCompiler.sharedPool, 1, 'this context must share the validator with the same schemas')
 | |
|       instance.validatorCompiler.sharedPool = 2
 | |
|     })
 | |
|     instance.after(() => {
 | |
|       t.notOk(instance.validatorCompiler, 'validator not initialized')
 | |
|     })
 | |
| 
 | |
|     instance.register((instance, opts, done) => {
 | |
|       t.notOk(instance.validatorCompiler, 'validator not initialized')
 | |
|       instance.ready(() => {
 | |
|         t.equal(instance.validatorCompiler.sharedPool, 2, 'this context must share the validator of the parent')
 | |
|       })
 | |
|       done()
 | |
|     })
 | |
|     done()
 | |
|   })
 | |
| 
 | |
|   fastify.register(function sibling3 (instance, opts, done) {
 | |
|     addRandomRoute(instance)
 | |
| 
 | |
|     // this trigger to dont't reuse the same compiler pool
 | |
|     instance.addSchema({ $id: 'diff', type: 'object' })
 | |
| 
 | |
|     t.notOk(instance.validatorCompiler, 'validator not initialized')
 | |
|     instance.ready(() => {
 | |
|       t.ok(instance.validatorCompiler, 'validator is initialized')
 | |
|       t.notOk(instance.validatorCompiler.sharedPool, 'this context has its own compiler')
 | |
|     })
 | |
|     done()
 | |
|   })
 | |
| 
 | |
|   fastify.ready(err => { t.error(err) })
 | |
| })
 | |
| 
 | |
| function addRandomRoute (server) {
 | |
|   server.get(`/${Math.random()}`,
 | |
|     { schema: { body: { type: 'object' } } },
 | |
|     (req, reply) => reply.send()
 | |
|   )
 | |
| }
 | |
| 
 | |
| test('Add schema order should not break the startup', t => {
 | |
|   t.plan(1)
 | |
|   const fastify = Fastify()
 | |
| 
 | |
|   fastify.get('/', { schema: { random: 'options' } }, () => {})
 | |
| 
 | |
|   fastify.register(fp((f, opts) => {
 | |
|     f.addSchema({
 | |
|       $id: 'https://example.com/bson/objectId',
 | |
|       type: 'string',
 | |
|       pattern: '\\b[0-9A-Fa-f]{24}\\b'
 | |
|     })
 | |
|     return Promise.resolve() // avoid async for node 6
 | |
|   }))
 | |
| 
 | |
|   fastify.get('/:id', {
 | |
|     schema: {
 | |
|       params: {
 | |
|         type: 'object',
 | |
|         properties: {
 | |
|           id: { $ref: 'https://example.com/bson/objectId#' }
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }, () => {})
 | |
| 
 | |
|   fastify.ready(err => { t.error(err) })
 | |
| })
 | |
| 
 | |
| test('The schema compiler recreate itself if needed', t => {
 | |
|   t.plan(1)
 | |
|   const fastify = Fastify()
 | |
| 
 | |
|   fastify.options('/', { schema: { hide: true } }, echoBody)
 | |
| 
 | |
|   fastify.register(function (fastify, options, done) {
 | |
|     fastify.addSchema({
 | |
|       $id: 'identifier',
 | |
|       type: 'string',
 | |
|       format: 'uuid'
 | |
|     })
 | |
| 
 | |
|     fastify.get('/:foobarId', {
 | |
|       schema: {
 | |
|         params: {
 | |
|           foobarId: { $ref: 'identifier#' }
 | |
|         }
 | |
|       }
 | |
|     }, echoBody)
 | |
| 
 | |
|     done()
 | |
|   })
 | |
| 
 | |
|   fastify.ready(err => { t.error(err) })
 | |
| })
 | |
| 
 | |
| test('Schema controller setter', t => {
 | |
|   t.plan(2)
 | |
|   Fastify({ schemaController: {} })
 | |
|   t.pass('allow empty object')
 | |
| 
 | |
|   try {
 | |
|     Fastify({ schemaController: { bucket: {} } })
 | |
|     t.fail('the bucket option must be a function')
 | |
|   } catch (err) {
 | |
|     t.equal(err.message, "schemaController.bucket option should be a function, instead got 'object'")
 | |
|   }
 | |
| })
 | |
| 
 | |
| test('Schema controller bucket', t => {
 | |
|   t.plan(10)
 | |
| 
 | |
|   let added = 0
 | |
|   let builtBucket = 0
 | |
| 
 | |
|   const initStoreQueue = []
 | |
| 
 | |
|   function factoryBucket (storeInit) {
 | |
|     builtBucket++
 | |
|     t.same(initStoreQueue.pop(), storeInit)
 | |
|     const store = new Map(storeInit)
 | |
|     return {
 | |
|       add (schema) {
 | |
|         added++
 | |
|         store.set(schema.$id, schema)
 | |
|       },
 | |
|       getSchema (id) {
 | |
|         return store.get(id)
 | |
|       },
 | |
|       getSchemas () {
 | |
|         // what is returned by this function, will be the `storeInit` parameter
 | |
|         initStoreQueue.push(store)
 | |
|         return store
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   const fastify = Fastify({
 | |
|     schemaController: {
 | |
|       bucket: factoryBucket
 | |
|     }
 | |
|   })
 | |
| 
 | |
|   fastify.register(async (instance) => {
 | |
|     instance.addSchema({ $id: 'b', type: 'string' })
 | |
|     instance.addHook('onReady', function (done) {
 | |
|       t.equal(instance.getSchemas().size, 2)
 | |
|       done()
 | |
|     })
 | |
|     instance.register(async (subinstance) => {
 | |
|       subinstance.addSchema({ $id: 'c', type: 'string' })
 | |
|       subinstance.addHook('onReady', function (done) {
 | |
|         t.equal(subinstance.getSchemas().size, 3)
 | |
|         done()
 | |
|       })
 | |
|     })
 | |
|   })
 | |
| 
 | |
|   fastify.register(async (instance) => {
 | |
|     instance.addHook('onReady', function (done) {
 | |
|       t.equal(instance.getSchemas().size, 1)
 | |
|       done()
 | |
|     })
 | |
|   })
 | |
| 
 | |
|   fastify.addSchema({ $id: 'a', type: 'string' })
 | |
| 
 | |
|   fastify.ready(err => {
 | |
|     t.error(err)
 | |
|     t.equal(added, 3, 'three schema added')
 | |
|     t.equal(builtBucket, 4, 'one bucket built for every register call + 1 for the root instance')
 | |
|   })
 | |
| })
 | |
| 
 | |
| test('setSchemaController per instance', t => {
 | |
|   t.plan(7)
 | |
|   const fastify = Fastify({})
 | |
| 
 | |
|   fastify.register(async (instance1) => {
 | |
|     instance1.setSchemaController({
 | |
|       bucket: function factoryBucket (storeInit) {
 | |
|         t.pass('instance1 has created the bucket')
 | |
|         return {
 | |
|           add (schema) { t.fail('add is not called') },
 | |
|           getSchema (id) { t.fail('getSchema is not called') },
 | |
|           getSchemas () { t.fail('getSchemas is not called') }
 | |
|         }
 | |
|       }
 | |
|     })
 | |
|   })
 | |
| 
 | |
|   fastify.register(async (instance2) => {
 | |
|     const bSchema = { $id: 'b', type: 'string' }
 | |
| 
 | |
|     instance2.setSchemaController({
 | |
|       bucket: function factoryBucket (storeInit) {
 | |
|         t.pass('instance2 has created the bucket')
 | |
|         const map = {}
 | |
|         return {
 | |
|           add (schema) {
 | |
|             t.equal(schema.$id, bSchema.$id, 'add is called')
 | |
|             map[schema.$id] = schema
 | |
|           },
 | |
|           getSchema (id) {
 | |
|             t.pass('getSchema is called')
 | |
|             return map[id]
 | |
|           },
 | |
|           getSchemas () {
 | |
|             t.pass('getSchemas is called')
 | |
|           }
 | |
|         }
 | |
|       }
 | |
|     })
 | |
| 
 | |
|     instance2.addSchema(bSchema)
 | |
| 
 | |
|     instance2.addHook('onReady', function (done) {
 | |
|       instance2.getSchemas()
 | |
|       t.same(instance2.getSchema('b'), bSchema, 'the schema are loaded')
 | |
|       done()
 | |
|     })
 | |
|   })
 | |
| 
 | |
|   fastify.ready(err => { t.error(err) })
 | |
| })
 | |
| 
 | |
| test('setSchemaController: Inherits correctly parent schemas with a customized validator instance', async t => {
 | |
|   t.plan(5)
 | |
|   const customAjv = new Ajv({ coerceTypes: false })
 | |
|   const server = Fastify()
 | |
|   const someSchema = {
 | |
|     $id: 'some',
 | |
|     type: 'array',
 | |
|     items: {
 | |
|       type: 'string'
 | |
|     }
 | |
|   }
 | |
|   const errorResponseSchema = {
 | |
|     $id: 'error_response',
 | |
|     type: 'object',
 | |
|     properties: {
 | |
|       statusCode: {
 | |
|         type: 'integer'
 | |
|       },
 | |
|       message: {
 | |
|         type: 'string'
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   server.addSchema(someSchema)
 | |
|   server.addSchema(errorResponseSchema)
 | |
| 
 | |
|   server.register((instance, _, done) => {
 | |
|     instance.setSchemaController({
 | |
|       compilersFactory: {
 | |
|         buildValidator: function (externalSchemas) {
 | |
|           const schemaKeys = Object.keys(externalSchemas)
 | |
|           t.equal(schemaKeys.length, 2, 'Contains same number of schemas')
 | |
|           t.hasStrict([someSchema, errorResponseSchema], Object.values(externalSchemas), 'Contains expected schemas')
 | |
|           for (const key of schemaKeys) {
 | |
|             if (customAjv.getSchema(key) == null) {
 | |
|               customAjv.addSchema(externalSchemas[key], key)
 | |
|             }
 | |
|           }
 | |
|           return function validatorCompiler ({ schema }) {
 | |
|             return customAjv.compile(schema)
 | |
|           }
 | |
|         }
 | |
|       }
 | |
|     })
 | |
| 
 | |
|     instance.get(
 | |
|       '/',
 | |
|       {
 | |
|         schema: {
 | |
|           querystring: {
 | |
|             msg: {
 | |
|               $ref: 'some#'
 | |
|             }
 | |
|           },
 | |
|           response: {
 | |
|             '4xx': {
 | |
|               $ref: 'error_response#'
 | |
|             }
 | |
|           }
 | |
|         }
 | |
|       },
 | |
|       (req, reply) => {
 | |
|         reply.send({ noop: 'noop' })
 | |
|       }
 | |
|     )
 | |
| 
 | |
|     done()
 | |
|   })
 | |
| 
 | |
|   const res = await server.inject({
 | |
|     method: 'GET',
 | |
|     url: '/',
 | |
|     query: {
 | |
|       msg: 'string'
 | |
|     }
 | |
|   })
 | |
|   const json = res.json()
 | |
| 
 | |
|   t.equal(json.message, 'querystring.msg should be array')
 | |
|   t.equal(json.statusCode, 400)
 | |
|   t.equal(res.statusCode, 400, 'Should not coearce the string into array')
 | |
| })
 | |
| 
 | |
| test('setSchemaController: Inherits buildSerializer from parent if not present within the instance', async t => {
 | |
|   t.plan(6)
 | |
|   const customAjv = new Ajv({ coerceTypes: false })
 | |
|   const someSchema = {
 | |
|     $id: 'some',
 | |
|     type: 'array',
 | |
|     items: {
 | |
|       type: 'string'
 | |
|     }
 | |
|   }
 | |
|   const errorResponseSchema = {
 | |
|     $id: 'error_response',
 | |
|     type: 'object',
 | |
|     properties: {
 | |
|       statusCode: {
 | |
|         type: 'integer'
 | |
|       },
 | |
|       message: {
 | |
|         type: 'string'
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   let rootSerializerCalled = 0
 | |
|   let rootValidatorCalled = 0
 | |
|   let childValidatorCalled = 0
 | |
|   const rootBuildSerializer = function (externalSchemas) {
 | |
|     rootSerializerCalled++
 | |
|     return function serializer () {
 | |
|       return data => {
 | |
|         return JSON.stringify({
 | |
|           statusCode: data.statusCode,
 | |
|           message: data.message
 | |
|         })
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   const rootBuildValidator = function (externalSchemas) {
 | |
|     rootValidatorCalled++
 | |
|     return function validatorCompiler ({ schema }) {
 | |
|       return customAjv.compile(schema)
 | |
|     }
 | |
|   }
 | |
|   const server = Fastify({
 | |
|     schemaController: {
 | |
|       compilersFactory: {
 | |
|         buildValidator: rootBuildValidator,
 | |
|         buildSerializer: rootBuildSerializer
 | |
|       }
 | |
|     }
 | |
|   })
 | |
| 
 | |
|   server.addSchema(someSchema)
 | |
|   server.addSchema(errorResponseSchema)
 | |
| 
 | |
|   server.register((instance, _, done) => {
 | |
|     instance.setSchemaController({
 | |
|       compilersFactory: {
 | |
|         buildValidator: function (externalSchemas) {
 | |
|           childValidatorCalled++
 | |
|           const schemaKeys = Object.keys(externalSchemas)
 | |
|           for (const key of schemaKeys) {
 | |
|             if (customAjv.getSchema(key) == null) {
 | |
|               customAjv.addSchema(externalSchemas[key], key)
 | |
|             }
 | |
|           }
 | |
|           return function validatorCompiler ({ schema }) {
 | |
|             return customAjv.compile(schema)
 | |
|           }
 | |
|         }
 | |
|       }
 | |
|     })
 | |
| 
 | |
|     instance.get(
 | |
|       '/',
 | |
|       {
 | |
|         schema: {
 | |
|           querystring: {
 | |
|             msg: {
 | |
|               $ref: 'some#'
 | |
|             }
 | |
|           },
 | |
|           response: {
 | |
|             '4xx': {
 | |
|               $ref: 'error_response#'
 | |
|             }
 | |
|           }
 | |
|         }
 | |
|       },
 | |
|       (req, reply) => {
 | |
|         reply.send({ noop: 'noop' })
 | |
|       }
 | |
|     )
 | |
| 
 | |
|     done()
 | |
|   })
 | |
| 
 | |
|   const res = await server.inject({
 | |
|     method: 'GET',
 | |
|     url: '/',
 | |
|     query: {
 | |
|       msg: 'string'
 | |
|     }
 | |
|   })
 | |
|   const json = res.json()
 | |
| 
 | |
|   t.equal(json.statusCode, 400)
 | |
|   t.equal(json.message, 'querystring.msg should be array')
 | |
|   t.equal(rootSerializerCalled, 1, 'Should be called from the child')
 | |
|   t.equal(rootValidatorCalled, 0, 'Should not be called from the child')
 | |
|   t.equal(childValidatorCalled, 1, 'Should be called from the child')
 | |
|   t.equal(res.statusCode, 400, 'Should not coerce the string into array')
 | |
| })
 | |
| 
 | |
| test('setSchemaController: Inherits buildValidator from parent if not present within the instance', async t => {
 | |
|   t.plan(6)
 | |
|   const customAjv = new Ajv({ coerceTypes: false })
 | |
|   const someSchema = {
 | |
|     $id: 'some',
 | |
|     type: 'array',
 | |
|     items: {
 | |
|       type: 'string'
 | |
|     }
 | |
|   }
 | |
|   const errorResponseSchema = {
 | |
|     $id: 'error_response',
 | |
|     type: 'object',
 | |
|     properties: {
 | |
|       statusCode: {
 | |
|         type: 'integer'
 | |
|       },
 | |
|       message: {
 | |
|         type: 'string'
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   let rootSerializerCalled = 0
 | |
|   let rootValidatorCalled = 0
 | |
|   let childSerializerCalled = 0
 | |
|   const rootBuildSerializer = function (externalSchemas) {
 | |
|     rootSerializerCalled++
 | |
|     return function serializer () {
 | |
|       return data => JSON.stringify(data)
 | |
|     }
 | |
|   }
 | |
|   const rootBuildValidator = function (externalSchemas) {
 | |
|     rootValidatorCalled++
 | |
|     const schemaKeys = Object.keys(externalSchemas)
 | |
|     for (const key of schemaKeys) {
 | |
|       if (customAjv.getSchema(key) == null) {
 | |
|         customAjv.addSchema(externalSchemas[key], key)
 | |
|       }
 | |
|     }
 | |
|     return function validatorCompiler ({ schema }) {
 | |
|       return customAjv.compile(schema)
 | |
|     }
 | |
|   }
 | |
|   const server = Fastify({
 | |
|     schemaController: {
 | |
|       compilersFactory: {
 | |
|         buildValidator: rootBuildValidator,
 | |
|         buildSerializer: rootBuildSerializer
 | |
|       }
 | |
|     }
 | |
|   })
 | |
| 
 | |
|   server.register((instance, _, done) => {
 | |
|     instance.register((subInstance, _, subDone) => {
 | |
|       subInstance.setSchemaController({
 | |
|         compilersFactory: {
 | |
|           buildSerializer: function (externalSchemas) {
 | |
|             childSerializerCalled++
 | |
|             return function serializerCompiler () {
 | |
|               return data => {
 | |
|                 return JSON.stringify({
 | |
|                   statusCode: data.statusCode,
 | |
|                   message: data.message
 | |
|                 })
 | |
|               }
 | |
|             }
 | |
|           }
 | |
|         }
 | |
|       })
 | |
| 
 | |
|       subInstance.get(
 | |
|         '/',
 | |
|         {
 | |
|           schema: {
 | |
|             querystring: {
 | |
|               msg: {
 | |
|                 $ref: 'some#'
 | |
|               }
 | |
|             },
 | |
|             response: {
 | |
|               '4xx': {
 | |
|                 $ref: 'error_response#'
 | |
|               }
 | |
|             }
 | |
|           }
 | |
|         },
 | |
|         (req, reply) => {
 | |
|           reply.send({ noop: 'noop' })
 | |
|         }
 | |
|       )
 | |
| 
 | |
|       subDone()
 | |
|     })
 | |
| 
 | |
|     done()
 | |
|   })
 | |
| 
 | |
|   server.addSchema(someSchema)
 | |
|   server.addSchema(errorResponseSchema)
 | |
| 
 | |
|   const res = await server.inject({
 | |
|     method: 'GET',
 | |
|     url: '/',
 | |
|     query: {
 | |
|       msg: ['string']
 | |
|     }
 | |
|   })
 | |
|   const json = res.json()
 | |
| 
 | |
|   t.equal(json.statusCode, 400)
 | |
|   t.equal(json.message, 'querystring.msg should be array')
 | |
|   t.equal(rootSerializerCalled, 0, 'Should be called from the child')
 | |
|   t.equal(rootValidatorCalled, 1, 'Should not be called from the child')
 | |
|   t.equal(childSerializerCalled, 1, 'Should be called from the child')
 | |
|   t.equal(res.statusCode, 400, 'Should not coearce the string into array')
 | |
| })
 | |
| 
 | |
| test('Should throw if not default validator passed', async t => {
 | |
|   t.plan(4)
 | |
|   const customAjv = new Ajv({ coerceTypes: false })
 | |
|   const someSchema = {
 | |
|     $id: 'some',
 | |
|     type: 'array',
 | |
|     items: {
 | |
|       type: 'string'
 | |
|     }
 | |
|   }
 | |
|   const anotherSchema = {
 | |
|     $id: 'another',
 | |
|     type: 'integer'
 | |
|   }
 | |
|   const plugin = fp(function (pluginInstance, _, pluginDone) {
 | |
|     pluginInstance.setSchemaController({
 | |
|       compilersFactory: {
 | |
|         buildValidator: function (externalSchemas) {
 | |
|           const schemaKeys = Object.keys(externalSchemas)
 | |
|           t.equal(schemaKeys.length, 2)
 | |
|           t.same(schemaKeys, ['some', 'another'])
 | |
| 
 | |
|           for (const key of schemaKeys) {
 | |
|             if (customAjv.getSchema(key) == null) {
 | |
|               customAjv.addSchema(externalSchemas[key], key)
 | |
|             }
 | |
|           }
 | |
|           return function validatorCompiler ({ schema }) {
 | |
|             return customAjv.compile(schema)
 | |
|           }
 | |
|         }
 | |
|       }
 | |
|     })
 | |
| 
 | |
|     pluginDone()
 | |
|   })
 | |
|   const server = Fastify()
 | |
| 
 | |
|   server.addSchema(someSchema)
 | |
| 
 | |
|   server.register((instance, opts, done) => {
 | |
|     instance.addSchema(anotherSchema)
 | |
| 
 | |
|     instance.register(plugin, {})
 | |
| 
 | |
|     instance.post(
 | |
|       '/',
 | |
|       {
 | |
|         schema: {
 | |
|           query: {
 | |
|             msg: {
 | |
|               $ref: 'some#'
 | |
|             }
 | |
|           },
 | |
|           headers: {
 | |
|             'x-another': {
 | |
|               $ref: 'another#'
 | |
|             }
 | |
|           }
 | |
|         }
 | |
|       },
 | |
|       (req, reply) => {
 | |
|         reply.send({ noop: 'noop' })
 | |
|       }
 | |
|     )
 | |
| 
 | |
|     done()
 | |
|   })
 | |
| 
 | |
|   try {
 | |
|     const res = await server.inject({
 | |
|       method: 'POST',
 | |
|       url: '/',
 | |
|       query: {
 | |
|         msg: ['string']
 | |
|       }
 | |
|     })
 | |
| 
 | |
|     t.equal(res.json().message, 'querystring.msg should be array')
 | |
|     t.equal(res.statusCode, 400, 'Should not coearce the string into array')
 | |
|   } catch (err) {
 | |
|     t.error(err)
 | |
|   }
 | |
| })
 | |
| 
 | |
| test('Should throw if not default validator passed', async t => {
 | |
|   t.plan(2)
 | |
|   const someSchema = {
 | |
|     $id: 'some',
 | |
|     type: 'array',
 | |
|     items: {
 | |
|       type: 'string'
 | |
|     }
 | |
|   }
 | |
|   const anotherSchema = {
 | |
|     $id: 'another',
 | |
|     type: 'integer'
 | |
|   }
 | |
| 
 | |
|   const server = Fastify()
 | |
| 
 | |
|   server.addSchema(someSchema)
 | |
| 
 | |
|   server.register((instance, opts, done) => {
 | |
|     instance.addSchema(anotherSchema)
 | |
| 
 | |
|     instance.post(
 | |
|       '/',
 | |
|       {
 | |
|         schema: {
 | |
|           query: {
 | |
|             msg: {
 | |
|               $ref: 'some#'
 | |
|             }
 | |
|           },
 | |
|           headers: {
 | |
|             'x-another': {
 | |
|               $ref: 'another#'
 | |
|             }
 | |
|           }
 | |
|         }
 | |
|       },
 | |
|       (req, reply) => {
 | |
|         reply.send({ noop: 'noop' })
 | |
|       }
 | |
|     )
 | |
| 
 | |
|     done()
 | |
|   })
 | |
| 
 | |
|   try {
 | |
|     const res = await server.inject({
 | |
|       method: 'POST',
 | |
|       url: '/',
 | |
|       query: {
 | |
|         msg: ['string']
 | |
|       }
 | |
|     })
 | |
| 
 | |
|     t.equal(res.json().message, 'querystring.msg should be array')
 | |
|     t.equal(res.statusCode, 400, 'Should not coearce the string into array')
 | |
|   } catch (err) {
 | |
|     t.error(err)
 | |
|   }
 | |
| })
 |