1403 lines
37 KiB
JavaScript
1403 lines
37 KiB
JavaScript
'use strict'
|
|
|
|
const { test } = require('node:test')
|
|
const Ajv = require('ajv')
|
|
const { kRequestCacheValidateFns, kRouteContext } = require('../../lib/symbols')
|
|
const Fastify = require('../../fastify')
|
|
|
|
const defaultSchema = {
|
|
type: 'object',
|
|
required: ['hello'],
|
|
properties: {
|
|
hello: { type: 'string' },
|
|
world: { type: 'string' }
|
|
}
|
|
}
|
|
|
|
const requestSchema = {
|
|
params: {
|
|
type: 'object',
|
|
properties: {
|
|
id: {
|
|
type: 'integer',
|
|
minimum: 1
|
|
}
|
|
}
|
|
},
|
|
querystring: {
|
|
type: 'object',
|
|
properties: {
|
|
foo: {
|
|
type: 'string',
|
|
enum: ['bar']
|
|
}
|
|
}
|
|
},
|
|
body: defaultSchema,
|
|
headers: {
|
|
type: 'object',
|
|
properties: {
|
|
'x-foo': {
|
|
type: 'string'
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
test('#compileValidationSchema', async subtest => {
|
|
subtest.plan(7)
|
|
|
|
await subtest.test('Should return a function - Route without schema', async t => {
|
|
const fastify = Fastify()
|
|
|
|
t.plan(3)
|
|
|
|
fastify.get('/', (req, reply) => {
|
|
const validate = req.compileValidationSchema(defaultSchema)
|
|
|
|
t.assert.ok(validate instanceof Function)
|
|
t.assert.ok(validate({ hello: 'world' }))
|
|
t.assert.ok(!validate({ world: 'foo' }))
|
|
|
|
reply.send({ hello: 'world' })
|
|
})
|
|
|
|
await fastify.inject({
|
|
path: '/',
|
|
method: 'GET'
|
|
})
|
|
})
|
|
|
|
await subtest.test('Validate function errors property should be null after validation when input is valid', async t => {
|
|
const fastify = Fastify()
|
|
|
|
t.plan(3)
|
|
|
|
fastify.get('/', (req, reply) => {
|
|
const validate = req.compileValidationSchema(defaultSchema)
|
|
|
|
t.assert.ok(validate({ hello: 'world' }))
|
|
t.assert.ok(Object.hasOwn(validate, 'errors'))
|
|
t.assert.strictEqual(validate.errors, null)
|
|
|
|
reply.send({ hello: 'world' })
|
|
})
|
|
|
|
await fastify.inject({
|
|
path: '/',
|
|
method: 'GET'
|
|
})
|
|
})
|
|
|
|
await subtest.test('Validate function errors property should be an array of errors after validation when input is valid', async t => {
|
|
const fastify = Fastify()
|
|
|
|
t.plan(4)
|
|
|
|
fastify.get('/', (req, reply) => {
|
|
const validate = req.compileValidationSchema(defaultSchema)
|
|
|
|
t.assert.ok(!validate({ world: 'foo' }))
|
|
t.assert.ok(Object.hasOwn(validate, 'errors'))
|
|
t.assert.ok(Array.isArray(validate.errors))
|
|
t.assert.ok(validate.errors.length > 0)
|
|
|
|
reply.send({ hello: 'world' })
|
|
})
|
|
|
|
await fastify.inject({
|
|
path: '/',
|
|
method: 'GET'
|
|
})
|
|
})
|
|
|
|
await subtest.test(
|
|
'Should reuse the validate fn across multiple invocations - Route without schema',
|
|
async t => {
|
|
const fastify = Fastify()
|
|
let validate = null
|
|
let counter = 0
|
|
|
|
t.plan(16)
|
|
|
|
fastify.get('/', (req, reply) => {
|
|
counter++
|
|
if (counter > 1) {
|
|
const newValidate = req.compileValidationSchema(defaultSchema)
|
|
t.assert.strictEqual(validate, newValidate, 'Are the same validate function')
|
|
validate = newValidate
|
|
} else {
|
|
validate = req.compileValidationSchema(defaultSchema)
|
|
}
|
|
|
|
t.assert.ok(validate instanceof Function)
|
|
t.assert.ok(validate({ hello: 'world' }))
|
|
t.assert.ok(!validate({ world: 'foo' }))
|
|
|
|
reply.send({ hello: 'world' })
|
|
})
|
|
|
|
await Promise.all([
|
|
fastify.inject({
|
|
path: '/',
|
|
method: 'GET'
|
|
}),
|
|
fastify.inject({
|
|
path: '/',
|
|
method: 'GET'
|
|
}),
|
|
fastify.inject({
|
|
path: '/',
|
|
method: 'GET'
|
|
}),
|
|
fastify.inject({
|
|
path: '/',
|
|
method: 'GET'
|
|
})
|
|
])
|
|
|
|
t.assert.strictEqual(counter, 4)
|
|
}
|
|
)
|
|
|
|
await subtest.test('Should return a function - Route with schema', async t => {
|
|
const fastify = Fastify()
|
|
|
|
t.plan(3)
|
|
|
|
fastify.post(
|
|
'/',
|
|
{
|
|
schema: {
|
|
body: defaultSchema
|
|
}
|
|
},
|
|
(req, reply) => {
|
|
const validate = req.compileValidationSchema(defaultSchema)
|
|
|
|
t.assert.ok(validate instanceof Function)
|
|
t.assert.ok(validate({ hello: 'world' }))
|
|
t.assert.ok(!validate({ world: 'foo' }))
|
|
|
|
reply.send({ hello: 'world' })
|
|
}
|
|
)
|
|
|
|
await fastify.inject({
|
|
path: '/',
|
|
method: 'POST',
|
|
payload: {
|
|
hello: 'world',
|
|
world: 'foo'
|
|
}
|
|
})
|
|
})
|
|
|
|
await subtest.test(
|
|
'Should use the custom validator compiler for the route',
|
|
async t => {
|
|
const fastify = Fastify()
|
|
let called = 0
|
|
const custom = ({ schema, httpPart, url, method }) => {
|
|
t.assert.strictEqual(schema, defaultSchema)
|
|
t.assert.strictEqual(url, '/')
|
|
t.assert.strictEqual(method, 'GET')
|
|
t.assert.strictEqual(httpPart, 'querystring')
|
|
|
|
return input => {
|
|
called++
|
|
t.assert.deepStrictEqual(input, { hello: 'world' })
|
|
return true
|
|
}
|
|
}
|
|
|
|
t.plan(10)
|
|
|
|
fastify.get('/', { validatorCompiler: custom }, (req, reply) => {
|
|
const first = req.compileValidationSchema(defaultSchema, 'querystring')
|
|
const second = req.compileValidationSchema(defaultSchema, 'querystring')
|
|
|
|
t.assert.strictEqual(first, second)
|
|
t.assert.ok(first({ hello: 'world' }))
|
|
t.assert.ok(second({ hello: 'world' }))
|
|
t.assert.strictEqual(called, 2)
|
|
|
|
reply.send({ hello: 'world' })
|
|
})
|
|
|
|
await fastify.inject({
|
|
path: '/',
|
|
method: 'GET'
|
|
})
|
|
}
|
|
)
|
|
|
|
await subtest.test(
|
|
'Should instantiate a WeakMap when executed for first time',
|
|
async t => {
|
|
const fastify = Fastify()
|
|
|
|
t.plan(5)
|
|
|
|
fastify.get('/', (req, reply) => {
|
|
t.assert.strictEqual(req[kRouteContext][kRequestCacheValidateFns], null)
|
|
t.assert.ok(req.compileValidationSchema(defaultSchema) instanceof Function)
|
|
t.assert.ok(req[kRouteContext][kRequestCacheValidateFns] instanceof WeakMap)
|
|
t.assert.ok(req.compileValidationSchema(Object.assign({}, defaultSchema)) instanceof Function)
|
|
t.assert.ok(req[kRouteContext][kRequestCacheValidateFns] instanceof WeakMap)
|
|
|
|
reply.send({ hello: 'world' })
|
|
})
|
|
|
|
await fastify.inject({
|
|
path: '/',
|
|
method: 'GET'
|
|
})
|
|
}
|
|
)
|
|
})
|
|
|
|
test('#getValidationFunction', async subtest => {
|
|
subtest.plan(6)
|
|
|
|
await subtest.test('Should return a validation function', async t => {
|
|
const fastify = Fastify()
|
|
|
|
t.plan(1)
|
|
|
|
fastify.get('/', (req, reply) => {
|
|
const original = req.compileValidationSchema(defaultSchema)
|
|
const referenced = req.getValidationFunction(defaultSchema)
|
|
|
|
t.assert.strictEqual(original, referenced)
|
|
|
|
reply.send({ hello: 'world' })
|
|
})
|
|
|
|
await fastify.inject({
|
|
path: '/',
|
|
method: 'GET'
|
|
})
|
|
})
|
|
|
|
await subtest.test('Validate function errors property should be null after validation when input is valid', async t => {
|
|
const fastify = Fastify()
|
|
|
|
t.plan(3)
|
|
|
|
fastify.get('/', (req, reply) => {
|
|
req.compileValidationSchema(defaultSchema)
|
|
const validate = req.getValidationFunction(defaultSchema)
|
|
|
|
t.assert.ok(validate({ hello: 'world' }))
|
|
t.assert.ok(Object.hasOwn(validate, 'errors'))
|
|
t.assert.strictEqual(validate.errors, null)
|
|
|
|
reply.send({ hello: 'world' })
|
|
})
|
|
|
|
await fastify.inject({
|
|
path: '/',
|
|
method: 'GET'
|
|
})
|
|
})
|
|
|
|
await subtest.test('Validate function errors property should be an array of errors after validation when input is valid', async t => {
|
|
const fastify = Fastify()
|
|
|
|
t.plan(4)
|
|
|
|
fastify.get('/', (req, reply) => {
|
|
req.compileValidationSchema(defaultSchema)
|
|
const validate = req.getValidationFunction(defaultSchema)
|
|
|
|
t.assert.ok(!validate({ world: 'foo' }))
|
|
t.assert.ok(Object.hasOwn(validate, 'errors'))
|
|
t.assert.ok(Array.isArray(validate.errors))
|
|
t.assert.ok(validate.errors.length > 0)
|
|
|
|
reply.send({ hello: 'world' })
|
|
})
|
|
|
|
await fastify.inject({
|
|
path: '/',
|
|
method: 'GET'
|
|
})
|
|
})
|
|
|
|
await subtest.test('Should return undefined if no schema compiled', async t => {
|
|
const fastify = Fastify()
|
|
|
|
t.plan(2)
|
|
|
|
fastify.get('/', (req, reply) => {
|
|
const validate = req.getValidationFunction(defaultSchema)
|
|
t.assert.ok(!validate)
|
|
|
|
const validateFn = req.getValidationFunction(42)
|
|
t.assert.ok(!validateFn)
|
|
|
|
reply.send({ hello: 'world' })
|
|
})
|
|
|
|
await fastify.inject('/')
|
|
})
|
|
|
|
await subtest.test(
|
|
'Should return the validation function from each HTTP part',
|
|
async t => {
|
|
const fastify = Fastify()
|
|
let headerValidation = null
|
|
let customValidation = null
|
|
|
|
t.plan(15)
|
|
|
|
fastify.post(
|
|
'/:id',
|
|
{
|
|
schema: requestSchema
|
|
},
|
|
(req, reply) => {
|
|
const { params } = req
|
|
|
|
switch (params.id) {
|
|
case 1:
|
|
customValidation = req.compileValidationSchema(defaultSchema)
|
|
t.assert.ok(req.getValidationFunction('body'))
|
|
t.assert.ok(req.getValidationFunction('body')({ hello: 'world' }))
|
|
t.assert.ok(!req.getValidationFunction('body')({ world: 'hello' }))
|
|
break
|
|
case 2:
|
|
headerValidation = req.getValidationFunction('headers')
|
|
t.assert.ok(headerValidation)
|
|
t.assert.ok(headerValidation({ 'x-foo': 'world' }))
|
|
t.assert.ok(!headerValidation({ 'x-foo': [] }))
|
|
break
|
|
case 3:
|
|
t.assert.ok(req.getValidationFunction('params'))
|
|
t.assert.ok(req.getValidationFunction('params')({ id: 123 }))
|
|
t.assert.ok(!req.getValidationFunction('params'({ id: 1.2 })))
|
|
break
|
|
case 4:
|
|
t.assert.ok(req.getValidationFunction('querystring'))
|
|
t.assert.ok(req.getValidationFunction('querystring')({ foo: 'bar' }))
|
|
t.assert.ok(!req.getValidationFunction('querystring')({ foo: 'not-bar' })
|
|
)
|
|
break
|
|
case 5:
|
|
t.assert.strictEqual(
|
|
customValidation,
|
|
req.getValidationFunction(defaultSchema)
|
|
)
|
|
t.assert.ok(customValidation({ hello: 'world' }))
|
|
t.assert.ok(!customValidation({}))
|
|
t.assert.strictEqual(headerValidation, req.getValidationFunction('headers'))
|
|
break
|
|
default:
|
|
t.assert.fail('Invalid id')
|
|
}
|
|
|
|
reply.send({ hello: 'world' })
|
|
}
|
|
)
|
|
|
|
const promises = []
|
|
|
|
for (let i = 1; i < 6; i++) {
|
|
promises.push(
|
|
fastify.inject({
|
|
path: `/${i}`,
|
|
method: 'post',
|
|
query: { foo: 'bar' },
|
|
payload: {
|
|
hello: 'world'
|
|
},
|
|
headers: {
|
|
'x-foo': 'x-bar'
|
|
}
|
|
})
|
|
)
|
|
}
|
|
|
|
await Promise.all(promises)
|
|
}
|
|
)
|
|
|
|
await subtest.test('Should not set a WeakMap if there is no schema', async t => {
|
|
const fastify = Fastify()
|
|
|
|
t.plan(1)
|
|
|
|
fastify.get('/', (req, reply) => {
|
|
req.getValidationFunction(defaultSchema)
|
|
req.getValidationFunction('body')
|
|
|
|
t.assert.strictEqual(req[kRouteContext][kRequestCacheValidateFns], null)
|
|
reply.send({ hello: 'world' })
|
|
})
|
|
|
|
await fastify.inject({
|
|
path: '/',
|
|
method: 'GET'
|
|
})
|
|
})
|
|
})
|
|
|
|
test('#validate', async subtest => {
|
|
subtest.plan(7)
|
|
|
|
await subtest.test(
|
|
'Should return true/false if input valid - Route without schema',
|
|
async t => {
|
|
const fastify = Fastify()
|
|
|
|
t.plan(2)
|
|
|
|
fastify.get('/', (req, reply) => {
|
|
const isNotValid = req.validateInput({ world: 'string' }, defaultSchema)
|
|
const isValid = req.validateInput({ hello: 'string' }, defaultSchema)
|
|
|
|
t.assert.ok(!isNotValid)
|
|
t.assert.ok(isValid)
|
|
|
|
reply.send({ hello: 'world' })
|
|
})
|
|
|
|
await fastify.inject({
|
|
path: '/',
|
|
method: 'GET'
|
|
})
|
|
}
|
|
)
|
|
|
|
await subtest.test(
|
|
'Should use the custom validator compiler for the route',
|
|
async t => {
|
|
const fastify = Fastify()
|
|
let called = 0
|
|
const custom = ({ schema, httpPart, url, method }) => {
|
|
t.assert.strictEqual(schema, defaultSchema)
|
|
t.assert.strictEqual(url, '/')
|
|
t.assert.strictEqual(method, 'GET')
|
|
t.assert.strictEqual(httpPart, 'querystring')
|
|
|
|
return input => {
|
|
called++
|
|
t.assert.deepStrictEqual(input, { hello: 'world' })
|
|
return true
|
|
}
|
|
}
|
|
|
|
t.plan(9)
|
|
|
|
fastify.get('/', { validatorCompiler: custom }, (req, reply) => {
|
|
const ok = req.validateInput(
|
|
{ hello: 'world' },
|
|
defaultSchema,
|
|
'querystring'
|
|
)
|
|
const ok2 = req.validateInput({ hello: 'world' }, defaultSchema)
|
|
|
|
t.assert.ok(ok)
|
|
t.assert.ok(ok2)
|
|
t.assert.strictEqual(called, 2)
|
|
|
|
reply.send({ hello: 'world' })
|
|
})
|
|
|
|
await fastify.inject({
|
|
path: '/',
|
|
method: 'GET'
|
|
})
|
|
}
|
|
)
|
|
|
|
await subtest.test(
|
|
'Should return true/false if input valid - With Schema for Route defined',
|
|
async t => {
|
|
const fastify = Fastify()
|
|
|
|
t.plan(8)
|
|
|
|
fastify.post(
|
|
'/:id',
|
|
{
|
|
schema: requestSchema
|
|
},
|
|
(req, reply) => {
|
|
const { params } = req
|
|
|
|
switch (params.id) {
|
|
case 1:
|
|
t.assert.ok(req.validateInput({ hello: 'world' }, 'body'))
|
|
t.assert.ok(!req.validateInput({ hello: [], world: 'foo' }, 'body'))
|
|
break
|
|
case 2:
|
|
t.assert.ok(!req.validateInput({ foo: 'something' }, 'querystring'))
|
|
t.assert.ok(req.validateInput({ foo: 'bar' }, 'querystring'))
|
|
break
|
|
case 3:
|
|
t.assert.ok(!req.validateInput({ 'x-foo': [] }, 'headers'))
|
|
t.assert.ok(req.validateInput({ 'x-foo': 'something' }, 'headers'))
|
|
break
|
|
case 4:
|
|
t.assert.ok(req.validateInput({ id: params.id }, 'params'))
|
|
t.assert.ok(!req.validateInput({ id: 0 }, 'params'))
|
|
break
|
|
default:
|
|
t.assert.fail('Invalid id')
|
|
}
|
|
|
|
reply.send({ hello: 'world' })
|
|
}
|
|
)
|
|
|
|
const promises = []
|
|
|
|
for (let i = 1; i < 5; i++) {
|
|
promises.push(
|
|
fastify.inject({
|
|
path: `/${i}`,
|
|
method: 'post',
|
|
query: { foo: 'bar' },
|
|
payload: {
|
|
hello: 'world'
|
|
},
|
|
headers: {
|
|
'x-foo': 'x-bar'
|
|
}
|
|
})
|
|
)
|
|
}
|
|
|
|
await Promise.all(promises)
|
|
}
|
|
)
|
|
|
|
await subtest.test(
|
|
'Should throw if missing validation fn for HTTP part and not schema provided',
|
|
async t => {
|
|
const fastify = Fastify()
|
|
|
|
t.plan(10)
|
|
|
|
fastify.get('/:id', (req, reply) => {
|
|
const { params } = req
|
|
|
|
switch (parseInt(params.id)) {
|
|
case 1:
|
|
req.validateInput({}, 'body')
|
|
break
|
|
case 2:
|
|
req.validateInput({}, 'querystring')
|
|
break
|
|
case 3:
|
|
req.validateInput({}, 'query')
|
|
break
|
|
case 4:
|
|
req.validateInput({ 'x-foo': [] }, 'headers')
|
|
break
|
|
case 5:
|
|
req.validateInput({ id: 0 }, 'params')
|
|
break
|
|
default:
|
|
t.assert.fail('Invalid id')
|
|
}
|
|
})
|
|
|
|
const promises = []
|
|
|
|
for (let i = 1; i < 6; i++) {
|
|
promises.push(
|
|
(async j => {
|
|
const response = await fastify.inject(`/${j}`)
|
|
|
|
const result = response.json()
|
|
t.assert.strictEqual(result.statusCode, 500)
|
|
t.assert.strictEqual(result.code, 'FST_ERR_REQ_INVALID_VALIDATION_INVOCATION')
|
|
})(i)
|
|
)
|
|
}
|
|
|
|
await Promise.all(promises)
|
|
}
|
|
)
|
|
|
|
await subtest.test(
|
|
'Should throw if missing validation fn for HTTP part and not valid schema provided',
|
|
async t => {
|
|
const fastify = Fastify()
|
|
|
|
t.plan(10)
|
|
|
|
fastify.get('/:id', (req, reply) => {
|
|
const { params } = req
|
|
|
|
switch (parseInt(params.id)) {
|
|
case 1:
|
|
req.validateInput({}, 1, 'body')
|
|
break
|
|
case 2:
|
|
req.validateInput({}, [], 'querystring')
|
|
break
|
|
case 3:
|
|
req.validateInput({}, '', 'query')
|
|
break
|
|
case 4:
|
|
req.validateInput({ 'x-foo': [] }, null, 'headers')
|
|
break
|
|
case 5:
|
|
req.validateInput({ id: 0 }, () => {}, 'params')
|
|
break
|
|
default:
|
|
t.assert.fail('Invalid id')
|
|
}
|
|
})
|
|
|
|
const promises = []
|
|
|
|
for (let i = 1; i < 6; i++) {
|
|
promises.push(
|
|
(async j => {
|
|
const response = await fastify.inject({
|
|
path: `/${j}`,
|
|
method: 'GET'
|
|
})
|
|
|
|
const result = response.json()
|
|
t.assert.strictEqual(result.statusCode, 500)
|
|
t.assert.strictEqual(result.code, 'FST_ERR_REQ_INVALID_VALIDATION_INVOCATION')
|
|
})(i)
|
|
)
|
|
}
|
|
|
|
await Promise.all(promises)
|
|
}
|
|
)
|
|
|
|
await subtest.test('Should throw if invalid schema passed', async t => {
|
|
const fastify = Fastify()
|
|
|
|
t.plan(10)
|
|
|
|
fastify.get('/:id', (req, reply) => {
|
|
const { params } = req
|
|
|
|
switch (parseInt(params.id)) {
|
|
case 1:
|
|
req.validateInput({}, 1)
|
|
break
|
|
case 2:
|
|
req.validateInput({}, '')
|
|
break
|
|
case 3:
|
|
req.validateInput({}, [])
|
|
break
|
|
case 4:
|
|
req.validateInput({ 'x-foo': [] }, null)
|
|
break
|
|
case 5:
|
|
req.validateInput({ id: 0 }, () => {})
|
|
break
|
|
default:
|
|
t.assert.fail('Invalid id')
|
|
}
|
|
})
|
|
|
|
const promises = []
|
|
|
|
for (let i = 1; i < 6; i++) {
|
|
promises.push(
|
|
(async j => {
|
|
const response = await fastify.inject({
|
|
path: `/${j}`,
|
|
method: 'GET'
|
|
})
|
|
|
|
const result = response.json()
|
|
t.assert.strictEqual(result.statusCode, 500)
|
|
t.assert.strictEqual(result.code, 'FST_ERR_REQ_INVALID_VALIDATION_INVOCATION')
|
|
})(i)
|
|
)
|
|
}
|
|
|
|
await Promise.all(promises)
|
|
})
|
|
|
|
await subtest.test(
|
|
'Should set a WeakMap if compiling the very first schema',
|
|
async t => {
|
|
const fastify = Fastify()
|
|
|
|
t.plan(3)
|
|
|
|
fastify.get('/', (req, reply) => {
|
|
t.assert.strictEqual(req[kRouteContext][kRequestCacheValidateFns], null)
|
|
t.assert.strictEqual(req.validateInput({ hello: 'world' }, defaultSchema), true)
|
|
t.assert.ok(req[kRouteContext][kRequestCacheValidateFns] instanceof WeakMap)
|
|
|
|
reply.send({ hello: 'world' })
|
|
})
|
|
|
|
await fastify.inject({
|
|
path: '/',
|
|
method: 'GET'
|
|
})
|
|
}
|
|
)
|
|
})
|
|
|
|
test('Nested Context', async subtest => {
|
|
subtest.plan(1)
|
|
|
|
await subtest.test('Level_1', async tst => {
|
|
tst.plan(3)
|
|
await tst.test('#compileValidationSchema', async ntst => {
|
|
ntst.plan(5)
|
|
|
|
await ntst.test('Should return a function - Route without schema', async t => {
|
|
const fastify = Fastify()
|
|
|
|
fastify.register((instance, opts, next) => {
|
|
instance.get('/', (req, reply) => {
|
|
const validate = req.compileValidationSchema(defaultSchema)
|
|
|
|
t.assert.ok(validate, Function)
|
|
t.assert.ok(validate({ hello: 'world' }))
|
|
t.assert.ok(!validate({ world: 'foo' }))
|
|
|
|
reply.send({ hello: 'world' })
|
|
})
|
|
|
|
next()
|
|
})
|
|
|
|
t.plan(3)
|
|
|
|
await fastify.inject({
|
|
path: '/',
|
|
method: 'GET'
|
|
})
|
|
})
|
|
|
|
await ntst.test(
|
|
'Should reuse the validate fn across multiple invocations - Route without schema',
|
|
async t => {
|
|
const fastify = Fastify()
|
|
let validate = null
|
|
let counter = 0
|
|
|
|
t.plan(16)
|
|
|
|
fastify.register((instance, opts, next) => {
|
|
instance.get('/', (req, reply) => {
|
|
counter++
|
|
if (counter > 1) {
|
|
const newValidate = req.compileValidationSchema(defaultSchema)
|
|
t.assert.strictEqual(validate, newValidate, 'Are the same validate function')
|
|
validate = newValidate
|
|
} else {
|
|
validate = req.compileValidationSchema(defaultSchema)
|
|
}
|
|
|
|
t.assert.ok(validate, Function)
|
|
t.assert.ok(validate({ hello: 'world' }))
|
|
t.assert.ok(!validate({ world: 'foo' }))
|
|
|
|
reply.send({ hello: 'world' })
|
|
})
|
|
|
|
next()
|
|
})
|
|
|
|
await Promise.all([
|
|
fastify.inject('/'),
|
|
fastify.inject('/'),
|
|
fastify.inject('/'),
|
|
fastify.inject('/')
|
|
])
|
|
|
|
t.assert.strictEqual(counter, 4)
|
|
}
|
|
)
|
|
|
|
await ntst.test('Should return a function - Route with schema', async t => {
|
|
const fastify = Fastify()
|
|
|
|
t.plan(3)
|
|
|
|
fastify.register((instance, opts, next) => {
|
|
instance.post(
|
|
'/',
|
|
{
|
|
schema: {
|
|
body: defaultSchema
|
|
}
|
|
},
|
|
(req, reply) => {
|
|
const validate = req.compileValidationSchema(defaultSchema)
|
|
|
|
t.assert.ok(validate, Function)
|
|
t.assert.ok(validate({ hello: 'world' }))
|
|
t.assert.ok(!validate({ world: 'foo' }))
|
|
|
|
reply.send({ hello: 'world' })
|
|
}
|
|
)
|
|
|
|
next()
|
|
})
|
|
|
|
await fastify.inject({
|
|
path: '/',
|
|
method: 'POST',
|
|
payload: {
|
|
hello: 'world',
|
|
world: 'foo'
|
|
}
|
|
})
|
|
})
|
|
|
|
await ntst.test(
|
|
'Should use the custom validator compiler for the route',
|
|
async t => {
|
|
const fastify = Fastify()
|
|
let called = 0
|
|
|
|
t.plan(10)
|
|
|
|
fastify.register((instance, opts, next) => {
|
|
const custom = ({ schema, httpPart, url, method }) => {
|
|
t.assert.strictEqual(schema, defaultSchema)
|
|
t.assert.strictEqual(url, '/')
|
|
t.assert.strictEqual(method, 'GET')
|
|
t.assert.strictEqual(httpPart, 'querystring')
|
|
|
|
return input => {
|
|
called++
|
|
t.assert.deepStrictEqual(input, { hello: 'world' })
|
|
return true
|
|
}
|
|
}
|
|
|
|
fastify.get('/', { validatorCompiler: custom }, (req, reply) => {
|
|
const first = req.compileValidationSchema(
|
|
defaultSchema,
|
|
'querystring'
|
|
)
|
|
const second = req.compileValidationSchema(
|
|
defaultSchema,
|
|
'querystring'
|
|
)
|
|
|
|
t.assert.strictEqual(first, second)
|
|
t.assert.ok(first({ hello: 'world' }))
|
|
t.assert.ok(second({ hello: 'world' }))
|
|
t.assert.strictEqual(called, 2)
|
|
|
|
reply.send({ hello: 'world' })
|
|
})
|
|
|
|
next()
|
|
})
|
|
|
|
await fastify.inject('/')
|
|
}
|
|
)
|
|
|
|
await ntst.test('Should compile the custom validation - nested with schema.headers', async t => {
|
|
const fastify = Fastify()
|
|
let called = false
|
|
|
|
const schemaWithHeaders = {
|
|
headers: {
|
|
'x-foo': {
|
|
type: 'string'
|
|
}
|
|
}
|
|
}
|
|
|
|
const custom = ({ schema, httpPart, url, method }) => {
|
|
if (called) return () => true
|
|
// only custom validators keep the same headers object
|
|
t.assert.strictEqual(schema, schemaWithHeaders.headers)
|
|
t.assert.strictEqual(url, '/')
|
|
t.assert.strictEqual(httpPart, 'headers')
|
|
called = true
|
|
return () => true
|
|
}
|
|
|
|
t.plan(4)
|
|
|
|
fastify.setValidatorCompiler(custom)
|
|
|
|
fastify.register((instance, opts, next) => {
|
|
instance.get('/', { schema: schemaWithHeaders }, (req, reply) => {
|
|
t.assert.strictEqual(called, true)
|
|
|
|
reply.send({ hello: 'world' })
|
|
})
|
|
|
|
next()
|
|
})
|
|
|
|
await fastify.inject('/')
|
|
})
|
|
})
|
|
|
|
await tst.test('#getValidationFunction', async ntst => {
|
|
ntst.plan(6)
|
|
|
|
await ntst.test('Should return a validation function', async t => {
|
|
const fastify = Fastify()
|
|
|
|
t.plan(1)
|
|
|
|
fastify.register((instance, opts, next) => {
|
|
instance.get('/', (req, reply) => {
|
|
const original = req.compileValidationSchema(defaultSchema)
|
|
const referenced = req.getValidationFunction(defaultSchema)
|
|
|
|
t.assert.strictEqual(original, referenced)
|
|
|
|
reply.send({ hello: 'world' })
|
|
})
|
|
|
|
next()
|
|
})
|
|
|
|
await fastify.inject('/')
|
|
})
|
|
|
|
await ntst.test('Should return undefined if no schema compiled', async t => {
|
|
const fastify = Fastify()
|
|
|
|
t.plan(1)
|
|
|
|
fastify.register((instance, opts, next) => {
|
|
instance.get('/', (req, reply) => {
|
|
const validate = req.getValidationFunction(defaultSchema)
|
|
|
|
t.assert.ok(!validate)
|
|
|
|
reply.send({ hello: 'world' })
|
|
})
|
|
|
|
next()
|
|
})
|
|
|
|
await fastify.inject('/')
|
|
})
|
|
|
|
await ntst.test(
|
|
'Should return the validation function from each HTTP part',
|
|
async t => {
|
|
const fastify = Fastify()
|
|
let headerValidation = null
|
|
let customValidation = null
|
|
|
|
t.plan(15)
|
|
|
|
fastify.register((instance, opts, next) => {
|
|
instance.post(
|
|
'/:id',
|
|
{
|
|
schema: requestSchema
|
|
},
|
|
(req, reply) => {
|
|
const { params } = req
|
|
|
|
switch (params.id) {
|
|
case 1:
|
|
customValidation = req.compileValidationSchema(
|
|
defaultSchema
|
|
)
|
|
t.assert.ok(req.getValidationFunction('body'))
|
|
t.assert.ok(req.getValidationFunction('body')({ hello: 'world' }))
|
|
t.assert.ok(!req.getValidationFunction('body')({ world: 'hello' })
|
|
)
|
|
break
|
|
case 2:
|
|
headerValidation = req.getValidationFunction('headers')
|
|
t.assert.ok(headerValidation)
|
|
t.assert.ok(headerValidation({ 'x-foo': 'world' }))
|
|
t.assert.ok(!headerValidation({ 'x-foo': [] }))
|
|
break
|
|
case 3:
|
|
t.assert.ok(req.getValidationFunction('params'))
|
|
t.assert.ok(req.getValidationFunction('params')({ id: 123 }))
|
|
t.assert.ok(!req.getValidationFunction('params'({ id: 1.2 })))
|
|
break
|
|
case 4:
|
|
t.assert.ok(req.getValidationFunction('querystring'))
|
|
t.assert.ok(
|
|
req.getValidationFunction('querystring')({ foo: 'bar' })
|
|
)
|
|
t.assert.ok(!req.getValidationFunction('querystring')({
|
|
foo: 'not-bar'
|
|
})
|
|
)
|
|
break
|
|
case 5:
|
|
t.assert.strictEqual(
|
|
customValidation,
|
|
req.getValidationFunction(defaultSchema)
|
|
)
|
|
t.assert.ok(customValidation({ hello: 'world' }))
|
|
t.assert.ok(!customValidation({}))
|
|
t.assert.strictEqual(
|
|
headerValidation,
|
|
req.getValidationFunction('headers')
|
|
)
|
|
break
|
|
default:
|
|
t.assert.fail('Invalid id')
|
|
}
|
|
|
|
reply.send({ hello: 'world' })
|
|
}
|
|
)
|
|
|
|
next()
|
|
})
|
|
const promises = []
|
|
|
|
for (let i = 1; i < 6; i++) {
|
|
promises.push(
|
|
fastify.inject({
|
|
path: `/${i}`,
|
|
method: 'post',
|
|
query: { foo: 'bar' },
|
|
payload: {
|
|
hello: 'world'
|
|
},
|
|
headers: {
|
|
'x-foo': 'x-bar'
|
|
}
|
|
})
|
|
)
|
|
}
|
|
|
|
await Promise.all(promises)
|
|
}
|
|
)
|
|
|
|
await ntst.test('Should return a validation function - nested', async t => {
|
|
const fastify = Fastify()
|
|
let called = false
|
|
const custom = ({ schema, httpPart, url, method }) => {
|
|
t.assert.strictEqual(schema, defaultSchema)
|
|
t.assert.strictEqual(url, '/')
|
|
t.assert.strictEqual(method, 'GET')
|
|
t.assert.ok(!httpPart)
|
|
|
|
called = true
|
|
return () => true
|
|
}
|
|
|
|
t.plan(6)
|
|
|
|
fastify.setValidatorCompiler(custom)
|
|
|
|
fastify.register((instance, opts, next) => {
|
|
instance.get('/', (req, reply) => {
|
|
const original = req.compileValidationSchema(defaultSchema)
|
|
const referenced = req.getValidationFunction(defaultSchema)
|
|
|
|
t.assert.strictEqual(original, referenced)
|
|
t.assert.strictEqual(called, true)
|
|
|
|
reply.send({ hello: 'world' })
|
|
})
|
|
|
|
next()
|
|
})
|
|
|
|
await fastify.inject('/')
|
|
})
|
|
|
|
await ntst.test(
|
|
'Should return undefined if no schema compiled - nested',
|
|
async t => {
|
|
const fastify = Fastify()
|
|
let called = 0
|
|
const custom = ({ schema, httpPart, url, method }) => {
|
|
called++
|
|
return () => true
|
|
}
|
|
|
|
t.plan(3)
|
|
|
|
fastify.setValidatorCompiler(custom)
|
|
|
|
fastify.get('/', (req, reply) => {
|
|
const validate = req.compileValidationSchema(defaultSchema)
|
|
|
|
t.assert.strictEqual(typeof validate, 'function')
|
|
|
|
reply.send({ hello: 'world' })
|
|
})
|
|
|
|
fastify.register(
|
|
(instance, opts, next) => {
|
|
instance.get('/', (req, reply) => {
|
|
const validate = req.getValidationFunction(defaultSchema)
|
|
|
|
t.assert.ok(!validate)
|
|
t.assert.strictEqual(called, 1)
|
|
|
|
reply.send({ hello: 'world' })
|
|
})
|
|
|
|
next()
|
|
},
|
|
{ prefix: '/nested' }
|
|
)
|
|
|
|
await fastify.inject('/')
|
|
await fastify.inject('/nested')
|
|
}
|
|
)
|
|
|
|
await ntst.test('Should per-route defined validation compiler', async t => {
|
|
const fastify = Fastify()
|
|
let validateParent
|
|
let validateChild
|
|
let calledParent = 0
|
|
let calledChild = 0
|
|
const customParent = ({ schema, httpPart, url, method }) => {
|
|
calledParent++
|
|
return () => true
|
|
}
|
|
|
|
const customChild = ({ schema, httpPart, url, method }) => {
|
|
calledChild++
|
|
return () => true
|
|
}
|
|
|
|
t.plan(5)
|
|
|
|
fastify.setValidatorCompiler(customParent)
|
|
|
|
fastify.get('/', (req, reply) => {
|
|
validateParent = req.compileValidationSchema(defaultSchema)
|
|
|
|
t.assert.strictEqual(typeof validateParent, 'function')
|
|
|
|
reply.send({ hello: 'world' })
|
|
})
|
|
|
|
fastify.register(
|
|
(instance, opts, next) => {
|
|
instance.get(
|
|
'/',
|
|
{
|
|
validatorCompiler: customChild
|
|
},
|
|
(req, reply) => {
|
|
const validate1 = req.compileValidationSchema(defaultSchema)
|
|
validateChild = req.getValidationFunction(defaultSchema)
|
|
|
|
t.assert.strictEqual(validate1, validateChild)
|
|
t.assert.notStrictEqual(validateParent, validateChild)
|
|
t.assert.strictEqual(calledParent, 1)
|
|
t.assert.strictEqual(calledChild, 1)
|
|
|
|
reply.send({ hello: 'world' })
|
|
}
|
|
)
|
|
|
|
next()
|
|
},
|
|
{ prefix: '/nested' }
|
|
)
|
|
|
|
await fastify.inject('/')
|
|
await fastify.inject('/nested')
|
|
})
|
|
})
|
|
|
|
await tst.test('#validate', async ntst => {
|
|
ntst.plan(3)
|
|
|
|
await ntst.test(
|
|
'Should return true/false if input valid - Route without schema',
|
|
async t => {
|
|
const fastify = Fastify()
|
|
|
|
t.plan(2)
|
|
|
|
fastify.register((instance, opts, next) => {
|
|
instance.get('/', (req, reply) => {
|
|
const isNotValid = req.validateInput(
|
|
{ world: 'string' },
|
|
defaultSchema
|
|
)
|
|
const isValid = req.validateInput({ hello: 'string' }, defaultSchema)
|
|
|
|
t.assert.ok(!isNotValid)
|
|
t.assert.ok(isValid)
|
|
|
|
reply.send({ hello: 'world' })
|
|
})
|
|
|
|
next()
|
|
})
|
|
|
|
await fastify.inject('/')
|
|
}
|
|
)
|
|
|
|
await ntst.test(
|
|
'Should use the custom validator compiler for the route',
|
|
async t => {
|
|
const fastify = Fastify()
|
|
let parentCalled = 0
|
|
let childCalled = 0
|
|
const customParent = () => {
|
|
parentCalled++
|
|
|
|
return () => true
|
|
}
|
|
|
|
const customChild = ({ schema, httpPart, url, method }) => {
|
|
t.assert.strictEqual(schema, defaultSchema)
|
|
t.assert.strictEqual(url, '/')
|
|
t.assert.strictEqual(method, 'GET')
|
|
t.assert.strictEqual(httpPart, 'querystring')
|
|
|
|
return input => {
|
|
childCalled++
|
|
t.assert.deepStrictEqual(input, { hello: 'world' })
|
|
return true
|
|
}
|
|
}
|
|
|
|
t.plan(10)
|
|
|
|
fastify.setValidatorCompiler(customParent)
|
|
|
|
fastify.register((instance, opts, next) => {
|
|
instance.get(
|
|
'/',
|
|
{ validatorCompiler: customChild },
|
|
(req, reply) => {
|
|
const ok = req.validateInput(
|
|
{ hello: 'world' },
|
|
defaultSchema,
|
|
'querystring'
|
|
)
|
|
const ok2 = req.validateInput({ hello: 'world' }, defaultSchema)
|
|
|
|
t.assert.ok(ok)
|
|
t.assert.ok(ok2)
|
|
t.assert.strictEqual(childCalled, 2)
|
|
t.assert.strictEqual(parentCalled, 0)
|
|
|
|
reply.send({ hello: 'world' })
|
|
}
|
|
)
|
|
|
|
next()
|
|
})
|
|
|
|
await fastify.inject('/')
|
|
}
|
|
)
|
|
|
|
await ntst.test(
|
|
'Should return true/false if input valid - With Schema for Route defined and scoped validator compiler',
|
|
async t => {
|
|
const validator = new Ajv()
|
|
const fastify = Fastify()
|
|
const childCounter = {
|
|
query: 0,
|
|
body: 0,
|
|
params: 0,
|
|
headers: 0
|
|
}
|
|
let parentCalled = 0
|
|
|
|
const parent = () => {
|
|
parentCalled++
|
|
return () => true
|
|
}
|
|
const child = ({ schema, httpPart, url, method }) => {
|
|
httpPart = httpPart === 'querystring' ? 'query' : httpPart
|
|
const validate = validator.compile(schema)
|
|
|
|
return input => {
|
|
childCounter[httpPart]++
|
|
return validate(input)
|
|
}
|
|
}
|
|
|
|
t.plan(13)
|
|
|
|
fastify.setValidatorCompiler(parent)
|
|
fastify.register((instance, opts, next) => {
|
|
instance.setValidatorCompiler(child)
|
|
instance.post(
|
|
'/:id',
|
|
{
|
|
schema: requestSchema
|
|
},
|
|
(req, reply) => {
|
|
const { params } = req
|
|
|
|
switch (parseInt(params.id)) {
|
|
case 1:
|
|
t.assert.ok(req.validateInput({ hello: 'world' }, 'body'))
|
|
t.assert.ok(!req.validateInput({ hello: [], world: 'foo' }, 'body'))
|
|
break
|
|
case 2:
|
|
t.assert.ok(!req.validateInput({ foo: 'something' }, 'querystring'))
|
|
t.assert.ok(req.validateInput({ foo: 'bar' }, 'querystring'))
|
|
break
|
|
case 3:
|
|
t.assert.ok(!req.validateInput({ 'x-foo': [] }, 'headers'))
|
|
t.assert.ok(req.validateInput({ 'x-foo': 'something' }, 'headers'))
|
|
break
|
|
case 4:
|
|
t.assert.ok(req.validateInput({ id: 1 }, 'params'))
|
|
t.assert.ok(!req.validateInput({ id: params.id }, 'params'))
|
|
break
|
|
default:
|
|
t.assert.fail('Invalid id')
|
|
}
|
|
|
|
reply.send({ hello: 'world' })
|
|
}
|
|
)
|
|
|
|
next()
|
|
})
|
|
|
|
const promises = []
|
|
|
|
for (let i = 1; i < 5; i++) {
|
|
promises.push(
|
|
fastify.inject({
|
|
path: `/${i}`,
|
|
method: 'post',
|
|
query: {},
|
|
payload: {
|
|
hello: 'world'
|
|
}
|
|
})
|
|
)
|
|
}
|
|
|
|
await Promise.all(promises)
|
|
|
|
t.assert.strictEqual(childCounter.query, 6) // 4 calls made + 2 custom validations
|
|
t.assert.strictEqual(childCounter.headers, 6) // 4 calls made + 2 custom validations
|
|
t.assert.strictEqual(childCounter.body, 6) // 4 calls made + 2 custom validations
|
|
t.assert.strictEqual(childCounter.params, 6) // 4 calls made + 2 custom validations
|
|
t.assert.strictEqual(parentCalled, 0)
|
|
}
|
|
)
|
|
})
|
|
})
|
|
})
|