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.

481 lines
9.8 KiB

'use strict'
const t = require('tap')
const test = t.test
const Fastify = require('..')
const fp = require('fastify-plugin')
const ajvMergePatch = require('ajv-merge-patch')
const AJV = require('ajv')
const fastClone = require('rfdc')({ circles: false, proto: true })
const schemaUsed = {
$id: 'urn:schema:foo',
definitions: {
foo: { type: 'string' }
},
type: 'object',
properties: {
foo: { $ref: '#/definitions/foo' }
}
}
const schemaParent = {
$id: 'urn:schema:response',
type: 'object',
required: ['foo'],
properties: {
foo: { $ref: 'urn:schema:foo#/definitions/foo' }
}
}
const schemaRequest = {
$id: 'urn:schema:request',
type: 'object',
required: ['foo'],
properties: {
foo: { $ref: 'urn:schema:response#/properties/foo' }
}
}
test('Should use the ref resolver - response', t => {
t.plan(3)
const fastify = Fastify()
const ajv = new AJV()
ajv.addSchema(fastClone(schemaParent))
ajv.addSchema(fastClone(schemaUsed))
fastify.setSchemaCompiler(schema => ajv.compile(schema))
fastify.setSchemaResolver((ref) => {
t.ok(['urn:schema:response', 'urn:schema:foo'].includes(ref))
return ajv.getSchema(ref).schema
})
fastify.route({
method: 'GET',
url: '/',
schema: {
response: {
'2xx': { $ref: 'urn:schema:response#' }
}
},
handler (req, reply) {
reply.send({ foo: 'bar' })
}
})
fastify.ready(t.error)
})
test('Should use the ref resolver - body', t => {
t.plan(4)
const fastify = Fastify()
const ajv = new AJV()
ajv.addSchema(fastClone(schemaParent))
ajv.addSchema(fastClone(schemaUsed))
fastify.setSchemaCompiler(schema => ajv.compile(schema))
fastify.setSchemaResolver((ref) => {
t.ok(['urn:schema:response', 'urn:schema:foo'].includes(ref))
return ajv.getSchema(ref).schema
})
fastify.route({
method: 'POST',
url: '/',
schema: {
body: { $ref: 'urn:schema:response#' }
},
handler (req, reply) {
reply.send({ foo: 'bar' })
}
})
fastify.inject({
method: 'POST',
url: '/',
payload: { foo: 'bar' }
}, (err, res) => {
t.error(err)
t.deepEquals(JSON.parse(res.payload), { foo: 'bar' })
})
})
test('Encapsulation', t => {
t.plan(14)
const fastify = Fastify()
const ajv = new AJV()
ajv.addSchema(fastClone(schemaParent))
ajv.addSchema(fastClone(schemaUsed))
fastify.register((instance, opts, next) => {
instance.setSchemaCompiler(schema => ajv.compile(schema))
instance.setSchemaResolver((ref) => {
return ajv.getSchema(ref).schema
})
instance.route({
method: 'POST',
url: '/',
schema: {
body: { $ref: 'urn:schema:response#' }
},
handler (req, reply) {
reply.send({ foo: 'bar' })
}
})
instance.register((instance, opts, next) => {
instance.route({
method: 'POST',
url: '/two',
schema: {
body: { $ref: 'urn:schema:response#' }
},
handler (req, reply) {
reply.send({ foo: 'bar' })
}
})
next()
})
next()
})
fastify.register((instance, opts, next) => {
instance.route({
method: 'POST',
url: '/clean',
handler (req, reply) {
reply.send({ foo: 'bar' })
}
})
next()
})
fastify.ready(err => {
t.error(err)
fastify.inject({
method: 'POST',
url: '/',
payload: { foo: 'bar' }
}, (err, res) => {
t.error(err)
t.equals(res.statusCode, 200)
t.deepEquals(JSON.parse(res.payload), { foo: 'bar' })
})
fastify.inject({
method: 'POST',
url: '/',
payload: { wrongFoo: 'bar' }
}, (err, res) => {
t.error(err)
t.equals(res.statusCode, 400)
})
fastify.inject({
method: 'POST',
url: '/two',
payload: { foo: 'bar' }
}, (err, res) => {
t.error(err)
t.equals(res.statusCode, 200)
t.deepEquals(JSON.parse(res.payload), { foo: 'bar' })
})
fastify.inject({
method: 'POST',
url: '/two',
payload: { wrongFoo: 'bar' }
}, (err, res) => {
t.error(err)
t.equals(res.statusCode, 400)
})
fastify.inject({
method: 'POST',
url: '/clean',
payload: { wrongFoo: 'bar' }
}, (err, res) => {
t.error(err)
t.equals(res.statusCode, 200)
t.deepEquals(JSON.parse(res.payload), { foo: 'bar' })
})
})
})
test('Schema resolver without schema compiler', t => {
t.plan(2)
const fastify = Fastify()
fastify.setSchemaResolver(() => { t.fail('the schema resolver will never be called') })
fastify.route({
method: 'POST',
url: '/',
schema: {},
handler (req, reply) {
reply.send({ foo: 'bar' })
}
})
fastify.ready(err => {
t.is(err.code, 'FST_ERR_SCH_MISSING_COMPILER')
t.isLike(err.message, /You must provide a schemaCompiler to route POST \/ to use the schemaResolver/)
})
})
test('Triple $ref deep', t => {
t.plan(6)
const fastify = Fastify()
const ajv = new AJV()
ajv.addSchema(fastClone(schemaParent))
ajv.addSchema(fastClone(schemaUsed))
ajv.addSchema(fastClone(schemaRequest))
fastify.setSchemaCompiler(schema => ajv.compile(schema))
fastify.setSchemaResolver((ref) => {
return ajv.getSchema(ref).schema
})
fastify.route({
method: 'POST',
url: '/',
schema: {
body: { $ref: 'urn:schema:request#' },
response: {
'2xx': { $ref: 'urn:schema:response#' }
}
},
handler (req, reply) {
reply.send({ foo: 'bar' })
}
})
fastify.inject({
method: 'POST',
url: '/',
payload: { foo: 'bar' }
}, (err, res) => {
t.error(err)
t.equals(res.statusCode, 200)
t.deepEquals(JSON.parse(res.payload), { foo: 'bar' })
})
fastify.inject({
method: 'POST',
url: '/',
payload: { fool: 'bar' }
}, (err, res) => {
t.error(err)
t.equals(res.statusCode, 400)
t.deepEquals(JSON.parse(res.payload).message, "body should have required property 'foo'")
})
})
test('$ref with a simple $id', t => {
t.plan(6)
const fastify = Fastify()
const ajv = new AJV()
ajv.addSchema(fastClone(schemaUsed))
ajv.addSchema({
$id: 'urn:schema:response',
type: 'object',
required: ['foo'],
properties: {
foo: { $ref: 'urn:schema:foo' }
}
})
ajv.addSchema({
$id: 'urn:schema:request',
type: 'object',
required: ['foo'],
properties: {
foo: { $ref: 'urn:schema:foo' }
}
})
fastify.setSchemaCompiler(schema => ajv.compile(schema))
fastify.setSchemaResolver((ref) => {
t.ok(['urn:schema:request', 'urn:schema:response', 'urn:schema:foo'].includes(ref))
return ajv.getSchema(ref).schema
})
fastify.route({
method: 'POST',
url: '/',
schema: {
body: { $ref: 'urn:schema:request#' },
response: {
'2xx': { $ref: 'urn:schema:response#' }
}
},
handler (req, reply) {
reply.send({ foo: { foo: 'bar', bar: 'foo' } })
}
})
fastify.inject({
method: 'POST',
url: '/',
payload: { foo: { foo: 'bar' } }
}, (err, res) => {
t.error(err)
t.equals(res.statusCode, 200)
t.deepEquals(JSON.parse(res.payload), { foo: { foo: 'bar' } })
})
})
test('Should handle root $merge keywords in header', t => {
t.plan(5)
const fastify = Fastify({
ajv: {
plugins: [
ajvMergePatch
]
}
})
fastify.route({
method: 'GET',
url: '/',
schema: {
headers: {
$merge: {
source: {
type: 'object',
properties: {
q: {
type: 'string'
}
}
},
with: {
required: ['q']
}
}
}
},
handler (req, reply) {
reply.send({ ok: 1 })
}
})
fastify.ready(err => {
t.error(err)
fastify.inject({
method: 'GET',
url: '/'
}, (err, res) => {
t.error(err)
t.equals(res.statusCode, 400)
})
fastify.inject({
method: 'GET',
url: '/',
headers: {
q: 'foo'
}
}, (err, res) => {
t.error(err)
t.equals(res.statusCode, 200)
})
})
})
test('Should handle root $patch keywords in header', t => {
t.plan(5)
const fastify = Fastify({
ajv: {
plugins: [
ajvMergePatch
]
}
})
fastify.route({
method: 'GET',
url: '/',
schema: {
headers: {
$patch: {
source: {
type: 'object',
properties: {
q: {
type: 'string'
}
}
},
with: [
{
op: 'add',
path: '/properties/q',
value: { type: 'number' }
}
]
}
}
},
handler (req, reply) {
reply.send({ ok: 1 })
}
})
fastify.ready(err => {
t.error(err)
fastify.inject({
method: 'GET',
url: '/',
headers: {
q: 'foo'
}
}, (err, res) => {
t.error(err)
t.equals(res.statusCode, 400)
})
fastify.inject({
method: 'GET',
url: '/',
headers: {
q: 10
}
}, (err, res) => {
t.error(err)
t.equals(res.statusCode, 200)
})
})
})
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) })
})