fastify
Version:
Fast and low overhead web framework, for Node.js
1,292 lines (1,125 loc) • 30.3 kB
JavaScript
'use strict'
const { test } = require('tap')
const Fastify = require('..')
const fp = require('fastify-plugin')
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.is(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.deepEqual(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.deepEqual(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.deepEqual(fastify.getSchema('abc'), schemas[1])
t.deepEqual(fastify.getSchema('id'), schemas[0])
t.deepEqual(fastify.getSchema('foo'), undefined)
fastify.register((instance, opts, done) => {
const pluginSchema = { $id: 'cde', my: 'schema' }
instance.addSchema(pluginSchema)
t.deepEqual(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.is(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.is(err.code, 'FST_ERR_SCH_ALREADY_PRESENT')
t.is(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.is(err.code, 'FST_ERR_SCH_DUPLICATE')
t.is(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.is(err.code, 'FST_ERR_SCH_VALIDATION_BUILD')
t.is(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.is(err.code, 'FST_ERR_SCH_SERIALIZATION_BUILD')
t.is(err.message, "Failed building the serialization schema for GET: /:id, due to error Cannot read property 'type' of undefined") // 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.deepEqual(res.json(), { name: 'Foo' })
t.ok(theSchema.$id, 'the $id is not removed')
t.deepEqual(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.deepEqual(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.equals(method, 'POST') // run 4 times
t.equals(url, '/:id') // run 4 times
switch (httpPart) {
case 'body':
t.pass('body evaluated')
return body => {
t.deepEqual(body, { foo: ['bar', 'BAR'] })
return true
}
case 'params':
t.pass('params evaluated')
return params => {
t.deepEqual(params, { id: 1234 })
return true
}
case 'querystring':
t.pass('querystring evaluated')
return query => {
t.deepEqual(query, { lang: 'en' })
return true
}
case 'headers':
t.pass('headers evaluated')
return headers => {
t.like(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.equals(method, 'GET') // run 3 times (params, headers, query)
t.equals(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.deepEqual(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.deepEqual(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.strictEqual(res.payload, 'number')
})
fastify.inject({
method: 'GET',
url: '/second/123'
}, (err, res) => {
t.error(err)
t.strictEqual(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.is(err.code, 'FST_ERR_SCH_VALIDATION_BUILD')
t.is(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.is(err.code, 'FST_ERR_SCH_ALREADY_PRESENT')
t.is(err.message, 'Schema with id \'test\' already declared!')
}
done()
})
fastify.inject({
method: 'GET',
url: '/4242'
}, (err, res) => {
t.error(err)
t.equals(res.statusCode, 200)
t.deepEqual(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.deepEqual(fastify.getSchemas(), { z: schemas.z })
t.deepEqual(pluginDeepOneSide.getSchemas(), { z: schemas.z, a: schemas.a })
t.deepEqual(pluginDeepOne.getSchemas(), { z: schemas.z, b: schemas.b })
t.deepEqual(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.deepEqual(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.deepEqual(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.deepEqual(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.equals(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.equals(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.equals(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.equals(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.is(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.deepEqual(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.equals(instance.getSchemas().size, 2)
done()
})
instance.register(async (subinstance) => {
subinstance.addSchema({ $id: 'c', type: 'string' })
subinstance.addHook('onReady', function (done) {
t.equals(subinstance.getSchemas().size, 3)
done()
})
})
})
fastify.register(async (instance) => {
instance.addHook('onReady', function (done) {
t.equals(instance.getSchemas().size, 1)
done()
})
})
fastify.addSchema({ $id: 'a', type: 'string' })
fastify.ready(err => {
t.error(err)
t.equals(added, 3, 'three schema added')
t.equals(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.equals(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.deepEquals(instance2.getSchema('b'), bSchema, 'the schema are loaded')
done()
})
})
fastify.ready(err => { t.error(err) })
})