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