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)
 | 
						|
  }
 | 
						|
})
 |