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.

443 lines
9.8 KiB

4 years ago
'use strict'
const t = require('tap')
const Joi = require('joi')
const localize = require('ajv-i18n')
const Fastify = require('..')
const test = t.test
const schema = {
body: {
type: 'object',
properties: {
name: { type: 'string' },
work: { type: 'string' }
},
required: ['name', 'work']
}
}
test('should work with valid payload', t => {
t.plan(3)
const fastify = Fastify()
fastify.post('/', { schema }, function (req, reply) {
reply.code(200).send(req.body.name)
})
fastify.inject({
method: 'POST',
payload: {
name: 'michelangelo',
work: 'sculptor, painter, architect and poet'
},
url: '/'
}, (err, res) => {
t.error(err)
t.deepEqual(res.payload, 'michelangelo')
t.strictEqual(res.statusCode, 200)
})
})
test('should fail immediately with invalid payload', t => {
t.plan(3)
const fastify = Fastify()
fastify.post('/', { schema }, function (req, reply) {
reply.code(200).send(req.body.name)
})
fastify.inject({
method: 'POST',
payload: {
hello: 'michelangelo'
},
url: '/'
}, (err, res) => {
t.error(err)
t.deepEqual(JSON.parse(res.payload), {
statusCode: 400,
error: 'Bad Request',
message: "body should have required property 'name', body should have required property 'work'"
})
t.strictEqual(res.statusCode, 400)
})
})
test('should return custom error messages with ajv-errors', t => {
t.plan(3)
const fastify = Fastify({
ajv: {
customOptions: { allErrors: true, jsonPointers: true },
plugins: [
require('ajv-errors')
]
}
})
const schema = {
body: {
type: 'object',
properties: {
name: { type: 'string' },
work: { type: 'string' },
age: {
type: 'number',
errorMessage: {
type: 'bad age - should be num'
}
}
},
required: ['name', 'work'],
errorMessage: {
required: {
name: 'name please',
work: 'work please',
age: 'age please'
}
}
}
}
fastify.post('/', { schema }, function (req, reply) {
reply.code(200).send(req.body.name)
})
fastify.inject({
method: 'POST',
payload: {
hello: 'salman',
age: 'bad'
},
url: '/'
}, (err, res) => {
t.error(err)
t.deepEqual(JSON.parse(res.payload), {
statusCode: 400,
error: 'Bad Request',
message: 'body/age bad age - should be num, body name please, body work please'
})
t.strictEqual(res.statusCode, 400)
})
})
test('should return localized error messages with ajv-i18n', t => {
t.plan(3)
const fastify = Fastify({
ajv: {
customOptions: { allErrors: true }
}
})
fastify.setErrorHandler(function (error, request, reply) {
if (error.validation) {
localize.ru(error.validation)
reply.status(400).send(error.validation)
return
}
reply.send(error)
})
fastify.post('/', { schema }, function (req, reply) {
reply.code(200).send(req.body.name)
})
fastify.inject({
method: 'POST',
payload: {
name: 'salman'
},
url: '/'
}, (err, res) => {
t.error(err)
t.deepEqual(JSON.parse(res.payload), [{
dataPath: '',
keyword: 'required',
message: 'должно иметь обязательное поле work',
params: { missingProperty: 'work' },
schemaPath: '#/required'
}])
t.strictEqual(res.statusCode, 400)
})
})
test('should be able to use setErrorHandler specify custom validation error', t => {
t.plan(3)
const fastify = Fastify()
fastify.post('/', { schema }, function (req, reply) {
t.fail('should not be here')
reply.code(200).send(req.body.name)
})
fastify.setErrorHandler(function (error, request, reply) {
if (error.validation) {
reply.status(422).send(new Error('validation failed'))
}
})
fastify.inject({
method: 'POST',
payload: {
hello: 'michelangelo'
},
url: '/'
}, (err, res) => {
t.error(err)
t.deepEqual(JSON.parse(res.payload), {
statusCode: 422,
error: 'Unprocessable Entity',
message: 'validation failed'
})
t.strictEqual(res.statusCode, 422)
})
})
test('should be able to attach validation to request', t => {
t.plan(3)
const fastify = Fastify()
fastify.post('/', { schema, attachValidation: true }, function (req, reply) {
reply.code(400).send(req.validationError.validation)
})
fastify.inject({
method: 'POST',
payload: {
hello: 'michelangelo'
},
url: '/'
}, (err, res) => {
t.error(err)
t.deepEqual(JSON.parse(res.payload), [{
keyword: 'required',
dataPath: '',
schemaPath: '#/required',
params: { missingProperty: 'name' },
message: 'should have required property \'name\''
},
{
keyword: 'required',
dataPath: '',
schemaPath: '#/required',
params: { missingProperty: 'work' },
message: 'should have required property \'work\''
}])
t.strictEqual(res.statusCode, 400)
})
})
test('should respect when attachValidation is explicitly set to false', t => {
t.plan(3)
const fastify = Fastify()
fastify.post('/', { schema, attachValidation: false }, function (req, reply) {
t.fail('should not be here')
reply.code(200).send(req.validationError.validation)
})
fastify.inject({
method: 'POST',
payload: {
hello: 'michelangelo'
},
url: '/'
}, (err, res) => {
t.error(err)
t.deepEqual(JSON.parse(res.payload), {
statusCode: 400,
error: 'Bad Request',
message: "body should have required property 'name', body should have required property 'work'"
})
t.strictEqual(res.statusCode, 400)
})
})
test('Attached validation error should take precendence over setErrorHandler', t => {
t.plan(3)
const fastify = Fastify()
fastify.post('/', { schema, attachValidation: true }, function (req, reply) {
reply.code(400).send('Attached: ' + req.validationError)
})
fastify.setErrorHandler(function (error, request, reply) {
t.fail('should not be here')
if (error.validation) {
reply.status(422).send(new Error('validation failed'))
}
})
fastify.inject({
method: 'POST',
payload: {
hello: 'michelangelo'
},
url: '/'
}, (err, res) => {
t.error(err)
t.deepEqual(res.payload, "Attached: Error: body should have required property 'name', body should have required property 'work'")
t.strictEqual(res.statusCode, 400)
})
})
test('should handle response validation error', t => {
t.plan(2)
const response = {
200: {
type: 'object',
required: ['name', 'work'],
properties: {
name: { type: 'string' },
work: { type: 'string' }
}
}
}
const fastify = Fastify()
fastify.get('/', { schema: { response } }, function (req, reply) {
try {
reply.code(200).send({ work: 'actor' })
} catch (error) {
reply.code(500).send(error)
}
})
fastify.inject({
method: 'GET',
payload: { },
url: '/'
}, (err, res) => {
t.error(err)
t.strictEqual(res.payload, '{"statusCode":500,"error":"Internal Server Error","message":"name is required!"}')
})
})
test('should handle response validation error with promises', t => {
t.plan(2)
const response = {
200: {
type: 'object',
required: ['name', 'work'],
properties: {
name: { type: 'string' },
work: { type: 'string' }
}
}
}
const fastify = Fastify()
fastify.get('/', { schema: { response } }, function (req, reply) {
return Promise.resolve({ work: 'actor' })
})
fastify.inject({
method: 'GET',
payload: { },
url: '/'
}, (err, res) => {
t.error(err)
t.strictEqual(res.payload, '{"statusCode":500,"error":"Internal Server Error","message":"name is required!"}')
})
})
test('should return a defined output message parsing AJV errors', t => {
t.plan(2)
const body = {
type: 'object',
required: ['name', 'work'],
properties: {
name: { type: 'string' },
work: { type: 'string' }
}
}
const fastify = Fastify()
fastify.post('/', { schema: { body } }, function (req, reply) {
t.fail()
})
fastify.inject({
method: 'POST',
payload: { },
url: '/'
}, (err, res) => {
t.error(err)
t.strictEqual(res.payload, '{"statusCode":400,"error":"Bad Request","message":"body should have required property \'name\', body should have required property \'work\'"}')
})
})
test('should return a defined output message parsing JOI errors', t => {
t.plan(2)
const body = Joi.object().keys({
name: Joi.string().required(),
work: Joi.string().required()
}).required()
const fastify = Fastify()
fastify.post('/', {
schema: { body },
schemaCompiler: schema => data => Joi.validate(data, schema)
},
function (req, reply) {
t.fail()
})
fastify.inject({
method: 'POST',
payload: {},
url: '/'
}, (err, res) => {
t.error(err)
t.strictEqual(res.payload, '{"statusCode":400,"error":"Bad Request","message":"child \\"name\\" fails because [\\"name\\" is required]"}')
})
})
test('should return a defined output message parsing JOI error details', t => {
t.plan(2)
const body = Joi.object().keys({
name: Joi.string().required(),
work: Joi.string().required()
}).required()
const fastify = Fastify()
fastify.post('/', {
schema: { body },
schemaCompiler: schema => data => {
const validation = Joi.validate(data, schema)
return { error: validation.error.details }
}
},
function (req, reply) {
t.fail()
})
fastify.inject({
method: 'POST',
payload: {},
url: '/'
}, (err, res) => {
t.error(err)
t.strictEqual(res.payload, '{"statusCode":400,"error":"Bad Request","message":"body \\"name\\" is required"}')
})
})