Aktueller Stand

This commit is contained in:
2026-01-22 19:05:45 +01:00
parent 85dee61a4d
commit e280e4eadb
1967 changed files with 397327 additions and 74093 deletions

View File

@@ -1,6 +1,6 @@
'use strict'
const { test } = require('tap')
const { test } = require('node:test')
const Joi = require('joi')
const yup = require('yup')
const AJV = require('ajv')
@@ -8,8 +8,10 @@ const S = require('fluent-json-schema')
const Fastify = require('..')
const ajvMergePatch = require('ajv-merge-patch')
const ajvErrors = require('ajv-errors')
const proxyquire = require('proxyquire')
const { waitForCb } = require('./toolkit')
test('Ajv plugins array parameter', t => {
test('Ajv plugins array parameter', (t, testDone) => {
t.plan(3)
const fastify = Fastify({
ajv: {
@@ -50,13 +52,14 @@ test('Ajv plugins array parameter', t => {
url: '/',
payload: { foo: 99 }
}, (err, res) => {
t.error(err)
t.equal(res.statusCode, 400)
t.equal(res.json().message, 'body/foo should be <= 10@@@@should be multipleOf 2')
t.assert.ifError(err)
t.assert.strictEqual(res.statusCode, 400)
t.assert.strictEqual(res.json().message, 'body/foo should be <= 10@@@@should be multipleOf 2')
testDone()
})
})
test('Should handle root $merge keywords in header', t => {
test('Should handle root $merge keywords in header', (t, testDone) => {
t.plan(5)
const fastify = Fastify({
ajv: {
@@ -86,14 +89,14 @@ test('Should handle root $merge keywords in header', t => {
})
fastify.ready(err => {
t.error(err)
t.assert.ifError(err)
fastify.inject({
method: 'GET',
url: '/'
}, (err, res) => {
t.error(err)
t.equal(res.statusCode, 400)
t.assert.ifError(err)
t.assert.strictEqual(res.statusCode, 400)
})
fastify.inject({
@@ -101,13 +104,14 @@ test('Should handle root $merge keywords in header', t => {
url: '/',
headers: { q: 'foo' }
}, (err, res) => {
t.error(err)
t.equal(res.statusCode, 200)
t.assert.ifError(err)
t.assert.strictEqual(res.statusCode, 200)
testDone()
})
})
})
test('Should handle root $patch keywords in header', t => {
test('Should handle root $patch keywords in header', (t, testDone) => {
t.plan(5)
const fastify = Fastify({
ajv: {
@@ -143,7 +147,7 @@ test('Should handle root $patch keywords in header', t => {
})
fastify.ready(err => {
t.error(err)
t.assert.ifError(err)
fastify.inject({
method: 'GET',
@@ -152,8 +156,8 @@ test('Should handle root $patch keywords in header', t => {
q: 'foo'
}
}, (err, res) => {
t.error(err)
t.equal(res.statusCode, 400)
t.assert.ifError(err)
t.assert.strictEqual(res.statusCode, 400)
})
fastify.inject({
@@ -161,13 +165,14 @@ test('Should handle root $patch keywords in header', t => {
url: '/',
headers: { q: 10 }
}, (err, res) => {
t.error(err)
t.equal(res.statusCode, 200)
t.assert.ifError(err)
t.assert.strictEqual(res.statusCode, 200)
testDone()
})
})
})
test('Should handle $merge keywords in body', t => {
test('Should handle $merge keywords in body', (t, testDone) => {
t.plan(5)
const fastify = Fastify({
ajv: {
@@ -197,14 +202,14 @@ test('Should handle $merge keywords in body', t => {
})
fastify.ready(err => {
t.error(err)
t.assert.ifError(err)
fastify.inject({
method: 'POST',
url: '/'
}, (err, res) => {
t.error(err)
t.equal(res.statusCode, 400)
t.assert.ifError(err)
t.assert.strictEqual(res.statusCode, 400)
})
fastify.inject({
@@ -212,13 +217,14 @@ test('Should handle $merge keywords in body', t => {
url: '/',
payload: { q: 'foo' }
}, (err, res) => {
t.error(err)
t.equal(res.statusCode, 200)
t.assert.ifError(err)
t.assert.strictEqual(res.statusCode, 200)
testDone()
})
})
})
test('Should handle $patch keywords in body', t => {
test('Should handle $patch keywords in body', (t, testDone) => {
t.plan(5)
const fastify = Fastify({
ajv: {
@@ -252,29 +258,32 @@ test('Should handle $patch keywords in body', t => {
})
fastify.ready(err => {
t.error(err)
t.assert.ifError(err)
const completion = waitForCb({ steps: 2 })
fastify.inject({
method: 'POST',
url: '/',
payload: { q: 'foo' }
}, (err, res) => {
t.error(err)
t.equal(res.statusCode, 400)
t.assert.ifError(err)
t.assert.strictEqual(res.statusCode, 400)
completion.stepIn()
})
fastify.inject({
method: 'POST',
url: '/',
payload: { q: 10 }
}, (err, res) => {
t.error(err)
t.equal(res.statusCode, 200)
t.assert.ifError(err)
t.assert.strictEqual(res.statusCode, 200)
completion.stepIn()
})
completion.patience.then(testDone)
})
})
test("serializer read validator's schemas", t => {
test("serializer read validator's schemas", (t, testDone) => {
t.plan(4)
const ajvInstance = new AJV()
@@ -303,7 +312,7 @@ test("serializer read validator's schemas", t => {
const fastify = Fastify({
schemaController: {
bucket: function factory (storeInit) {
t.notOk(storeInit, 'is always empty because fastify.addSchema is not called')
t.assert.ok(!storeInit, 'is always empty because fastify.addSchema is not called')
return {
getSchemas () {
return {
@@ -330,13 +339,14 @@ test("serializer read validator's schemas", t => {
})
fastify.inject('/', (err, res) => {
t.error(err)
t.equal(res.statusCode, 200)
t.same(res.json(), { hello: 'world' })
t.assert.ifError(err)
t.assert.strictEqual(res.statusCode, 200)
t.assert.deepStrictEqual(res.json(), { hello: 'world' })
testDone()
})
})
test('setSchemaController in a plugin', t => {
test('setSchemaController in a plugin', (t, testDone) => {
t.plan(5)
const baseSchema = {
$id: 'urn:schema:base',
@@ -376,15 +386,16 @@ test('setSchemaController in a plugin', t => {
})
fastify.inject('/', (err, res) => {
t.error(err)
t.equal(res.statusCode, 200)
t.same(res.json(), { hello: 'world' })
t.assert.ifError(err)
t.assert.strictEqual(res.statusCode, 200)
t.assert.deepStrictEqual(res.json(), { hello: 'world' })
testDone()
})
async function schemaPlugin (server) {
server.setSchemaController({
bucket () {
t.pass('the bucket is created')
t.assert.ok('the bucket is created')
return {
addSchema (source) {
ajvInstance.addSchema(source)
@@ -402,7 +413,7 @@ test('setSchemaController in a plugin', t => {
}
})
server.setValidatorCompiler(function ({ schema }) {
t.pass('the querystring schema is compiled')
t.assert.ok('the querystring schema is compiled')
return ajvInstance.compile(schema)
})
}
@@ -506,7 +517,7 @@ test('only response schema trigger AJV pollution #2', async t => {
await fastify.ready()
})
test('setSchemaController in a plugin with head routes', t => {
test('setSchemaController in a plugin with head routes', (t, testDone) => {
t.plan(6)
const baseSchema = {
$id: 'urn:schema:base',
@@ -546,15 +557,16 @@ test('setSchemaController in a plugin with head routes', t => {
})
fastify.inject('/', (err, res) => {
t.error(err)
t.equal(res.statusCode, 200)
t.same(res.json(), { hello: 'world' })
t.assert.ifError(err)
t.assert.strictEqual(res.statusCode, 200)
t.assert.deepStrictEqual(res.json(), { hello: 'world' })
testDone()
})
async function schemaPlugin (server) {
server.setSchemaController({
bucket () {
t.pass('the bucket is created')
t.assert.ok('the bucket is created')
return {
addSchema (source) {
ajvInstance.addSchema(source)
@@ -575,11 +587,11 @@ test('setSchemaController in a plugin with head routes', t => {
if (schema.$id) {
const stored = ajvInstance.getSchema(schema.$id)
if (stored) {
t.pass('the schema is reused')
t.assert.ok('the schema is reused')
return stored
}
}
t.pass('the schema is compiled')
t.assert.ok('the schema is compiled')
return ajvInstance.compile(schema)
})
@@ -587,7 +599,7 @@ test('setSchemaController in a plugin with head routes', t => {
schemaPlugin[Symbol.for('skip-override')] = true
})
test('multiple refs with the same ids', t => {
test('multiple refs with the same ids', (t, testDone) => {
t.plan(3)
const baseSchema = {
$id: 'urn:schema:base',
@@ -638,13 +650,14 @@ test('multiple refs with the same ids', t => {
})
fastify.inject('/', (err, res) => {
t.error(err)
t.equal(res.statusCode, 200)
t.same(res.json(), { hello: 'world' })
t.assert.ifError(err)
t.assert.strictEqual(res.statusCode, 200)
t.assert.deepStrictEqual(res.json(), { hello: 'world' })
testDone()
})
})
test('JOI validation overwrite request headers', t => {
test('JOI validation overwrite request headers', (t, testDone) => {
t.plan(3)
const schemaValidator = ({ schema }) => data => {
const validationResult = schema.validate(data)
@@ -666,12 +679,13 @@ test('JOI validation overwrite request headers', t => {
})
fastify.inject('/', (err, res) => {
t.error(err)
t.equal(res.statusCode, 200)
t.same(res.json(), {
t.assert.ifError(err)
t.assert.strictEqual(res.statusCode, 200)
t.assert.deepStrictEqual(res.json(), {
'user-agent': 'lightMyRequest',
host: 'localhost:80'
})
testDone()
})
})
@@ -700,16 +714,16 @@ test('Custom schema object should not trigger FST_ERR_SCH_DUPLICATE', async t =>
})
await fastify.ready()
t.pass('fastify is ready')
t.assert.ok('fastify is ready')
})
test('The default schema compilers should not be called when overwritten by the user', async t => {
const Fastify = t.mock('../', {
const Fastify = proxyquire('../', {
'@fastify/ajv-compiler': () => {
t.fail('The default validator compiler should not be called')
t.assert.fail('The default validator compiler should not be called')
},
'@fastify/fast-json-stringify-compiler': () => {
t.fail('The default serializer compiler should not be called')
t.assert.fail('The default serializer compiler should not be called')
}
})
@@ -717,13 +731,13 @@ test('The default schema compilers should not be called when overwritten by the
schemaController: {
compilersFactory: {
buildValidator: function factory () {
t.pass('The custom validator compiler should be called')
t.assert.ok('The custom validator compiler should be called')
return function validatorCompiler () {
return () => { return true }
}
},
buildSerializer: function factory () {
t.pass('The custom serializer compiler should be called')
t.assert.ok('The custom serializer compiler should be called')
return function serializerCompiler () {
return () => { return true }
}
@@ -745,7 +759,7 @@ test('The default schema compilers should not be called when overwritten by the
await fastify.ready()
})
test('Supports async JOI validation', t => {
test('Supports async JOI validation', (t, testDone) => {
t.plan(7)
const schemaValidator = ({ schema }) => async data => {
@@ -766,7 +780,7 @@ test('Supports async JOI validation', t => {
throw new Error('Invalid user-agent')
}
t.equal(val, 'lightMyRequest')
t.assert.strictEqual(val, 'lightMyRequest')
return val
}),
host: Joi.string().required()
@@ -776,33 +790,37 @@ test('Supports async JOI validation', t => {
reply.send(request.headers)
})
const completion = waitForCb({ steps: 2 })
fastify.inject('/', (err, res) => {
t.error(err)
t.equal(res.statusCode, 200)
t.same(res.json(), {
t.assert.ifError(err)
t.assert.strictEqual(res.statusCode, 200)
t.assert.deepStrictEqual(res.json(), {
'user-agent': 'lightMyRequest',
host: 'localhost:80'
})
completion.stepIn()
})
fastify.inject({
url: '/',
headers: {
'user-agent': 'invalid'
}
}, (err, res) => {
t.error(err)
t.equal(res.statusCode, 400)
t.same(res.json(), {
t.assert.ifError(err)
t.assert.strictEqual(res.statusCode, 400)
t.assert.deepStrictEqual(res.json(), {
statusCode: 400,
code: 'FST_ERR_VALIDATION',
error: 'Bad Request',
message: 'Invalid user-agent (user-agent)'
})
completion.stepIn()
})
completion.patience.then(testDone)
})
test('Supports async AJV validation', t => {
test('Supports async AJV validation', (t, testDone) => {
t.plan(12)
const fastify = Fastify({
@@ -861,63 +879,67 @@ test('Supports async AJV validation', t => {
handler (req, reply) { reply.send(req.body) }
})
const completion = waitForCb({ steps: 4 })
fastify.inject({
method: 'POST',
url: '/',
payload: { userId: 99 }
}, (err, res) => {
t.error(err)
t.equal(res.statusCode, 400)
t.same(res.json(), {
t.assert.ifError(err)
t.assert.strictEqual(res.statusCode, 400)
t.assert.deepStrictEqual(res.json(), {
statusCode: 400,
code: 'FST_ERR_VALIDATION',
error: 'Bad Request',
message: 'validation failed'
})
completion.stepIn()
})
fastify.inject({
method: 'POST',
url: '/',
payload: { userId: 500 }
}, (err, res) => {
t.error(err)
t.equal(res.statusCode, 400)
t.same(res.json(), {
t.assert.ifError(err)
t.assert.strictEqual(res.statusCode, 400)
t.assert.deepStrictEqual(res.json(), {
statusCode: 400,
code: 'FST_ERR_VALIDATION',
error: 'Bad Request',
message: 'custom error'
})
completion.stepIn()
})
fastify.inject({
method: 'POST',
url: '/',
payload: { userId: 42 }
}, (err, res) => {
t.error(err)
t.equal(res.statusCode, 200)
t.same(res.json(), { userId: 42 })
t.assert.ifError(err)
t.assert.strictEqual(res.statusCode, 200)
t.assert.deepStrictEqual(res.json(), { userId: 42 })
completion.stepIn()
})
fastify.inject({
method: 'POST',
url: '/',
payload: { userId: 42, postId: 19 }
}, (err, res) => {
t.error(err)
t.equal(res.statusCode, 400)
t.same(res.json(), {
t.assert.ifError(err)
t.assert.strictEqual(res.statusCode, 400)
t.assert.deepStrictEqual(res.json(), {
statusCode: 400,
code: 'FST_ERR_VALIDATION',
error: 'Bad Request',
message: 'validation failed'
})
completion.stepIn()
})
completion.patience.then(testDone)
})
test('Check all the async AJV validation paths', t => {
test('Check all the async AJV validation paths', async (t) => {
const fastify = Fastify({
exposeHeadRoutes: false,
ajv: {
@@ -1004,30 +1026,34 @@ test('Check all the async AJV validation paths', t => {
response: 200
}
]
t.plan(testCases.length * 2)
testCases.forEach(validate)
t.plan(testCases.length)
for (const testCase of testCases) {
await validate(testCase)
}
function validate ({
async function validate ({
params,
body,
querystring,
headers,
response
}) {
fastify.inject({
method: 'POST',
url: `/${params}`,
headers: { id: headers },
query: { id: querystring },
payload: { id: body }
}, (err, res) => {
t.error(err)
t.equal(res.statusCode, response)
})
try {
const res = await fastify.inject({
method: 'POST',
url: `/${params}`,
headers: { id: headers },
query: { id: querystring },
payload: { id: body }
})
t.assert.strictEqual(res.statusCode, response)
} catch (error) {
t.assert.fail('should not throw')
}
}
})
test('Check mixed sync and async AJV validations', t => {
test('Check mixed sync and async AJV validations', async (t) => {
const fastify = Fastify({
exposeHeadRoutes: false,
ajv: {
@@ -1179,10 +1205,12 @@ test('Check mixed sync and async AJV validations', t => {
response: 400
}
]
t.plan(testCases.length * 2)
testCases.forEach(validate)
t.plan(testCases.length)
for (const testCase of testCases) {
await validate(testCase)
}
function validate ({
async function validate ({
url,
params,
body,
@@ -1190,20 +1218,22 @@ test('Check mixed sync and async AJV validations', t => {
headers,
response
}) {
fastify.inject({
method: 'POST',
url: `${url}/${params || ''}`,
headers: { id: headers },
query: { id: querystring },
payload: { id: body }
}, (err, res) => {
t.error(err)
t.equal(res.statusCode, response)
})
try {
const res = await fastify.inject({
method: 'POST',
url: `${url}/${params || ''}`,
headers: { id: headers },
query: { id: querystring },
payload: { id: body }
})
t.assert.strictEqual(res.statusCode, response)
} catch (error) {
t.assert.fail('should not fail')
}
}
})
test('Check if hooks and attachValidation work with AJV validations', t => {
test('Check if hooks and attachValidation work with AJV validations', async (t) => {
const fastify = Fastify({
exposeHeadRoutes: false,
ajv: {
@@ -1245,8 +1275,8 @@ test('Check if hooks and attachValidation work with AJV validations', t => {
fastify.post('/:id', {
preHandler: function hook (request, reply, done) {
t.equal(request.validationError.message, 'validation failed')
t.pass('preHandler called')
t.assert.strictEqual(request.validationError.message, 'validation failed')
t.assert.ok('preHandler called')
reply.code(400).send(request.body)
},
@@ -1290,26 +1320,29 @@ test('Check if hooks and attachValidation work with AJV validations', t => {
response: 400
}
]
t.plan(testCases.length * 4)
testCases.forEach(validate)
t.plan(testCases.length * 3)
for (const testCase of testCases) {
await validate(testCase)
}
function validate ({
url,
async function validate ({
params,
body,
querystring,
headers,
response
}) {
fastify.inject({
method: 'POST',
url: `/${params}`,
headers: { id: headers },
query: { id: querystring },
payload: { id: body }
}, (err, res) => {
t.error(err)
t.equal(res.statusCode, response)
})
try {
const res = await fastify.inject({
method: 'POST',
url: `/${params}`,
headers: { id: headers },
query: { id: querystring },
payload: { id: body }
})
t.assert.strictEqual(res.statusCode, response)
} catch (error) {
t.assert.fail('should not fail')
}
}
})