fastify 5.8.4 → 5.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/SECURITY.md +1 -1
- package/SPONSORS.md +6 -4
- package/docs/Guides/Database.md +0 -28
- package/docs/Guides/Ecosystem.md +17 -2
- package/docs/Guides/Serverless.md +2 -2
- package/docs/Guides/Write-Plugin.md +1 -1
- package/docs/Reference/Encapsulation.md +27 -26
- package/docs/Reference/Errors.md +10 -4
- package/docs/Reference/HTTP2.md +10 -10
- package/docs/Reference/Hooks.md +4 -4
- package/docs/Reference/Index.md +14 -16
- package/docs/Reference/LTS.md +12 -13
- package/docs/Reference/Lifecycle.md +9 -8
- package/docs/Reference/Logging.md +44 -39
- package/docs/Reference/Middleware.md +21 -25
- package/docs/Reference/Principles.md +2 -2
- package/docs/Reference/Reply.md +6 -1
- package/docs/Reference/Request.md +27 -16
- package/docs/Reference/Routes.md +5 -2
- package/docs/Reference/Server.md +31 -3
- package/docs/Reference/Type-Providers.md +29 -5
- package/docs/Reference/Validation-and-Serialization.md +15 -2
- package/docs/Reference/Warnings.md +7 -6
- package/eslint.config.js +7 -2
- package/fastify.d.ts +8 -3
- package/fastify.js +43 -14
- package/lib/content-type-parser.js +13 -1
- package/lib/decorate.js +11 -3
- package/lib/error-handler.js +4 -3
- package/lib/error-serializer.js +59 -59
- package/lib/errors.js +16 -1
- package/lib/four-oh-four.js +14 -9
- package/lib/handle-request.js +11 -5
- package/lib/plugin-override.js +2 -1
- package/lib/plugin-utils.js +5 -5
- package/lib/reply.js +63 -8
- package/lib/request.js +23 -19
- package/lib/route.js +20 -6
- package/lib/schema-controller.js +1 -1
- package/lib/schemas.js +37 -30
- package/lib/symbols.js +3 -1
- package/lib/validation.js +1 -13
- package/lib/warnings.js +3 -3
- package/package.json +16 -18
- package/scripts/validate-ecosystem-links.js +1 -0
- package/test/bundler/esbuild/package.json +1 -1
- package/test/close-pipelining.test.js +1 -2
- package/test/custom-http-server.test.js +38 -0
- package/test/decorator-instance-properties.test.js +63 -0
- package/test/diagnostics-channel/async-error-handler.test.js +74 -0
- package/test/hooks.test.js +23 -0
- package/test/http-methods/get.test.js +1 -1
- package/test/http2/plain.test.js +135 -0
- package/test/http2/secure-with-fallback.test.js +1 -1
- package/test/https/https.test.js +1 -2
- package/test/internals/errors.test.js +31 -1
- package/test/internals/plugin.test.js +3 -1
- package/test/internals/request.test.js +27 -3
- package/test/internals/schema-controller-perf.test.js +33 -0
- package/test/logger/logging.test.js +18 -1
- package/test/logger/options.test.js +38 -1
- package/test/reply-error.test.js +1 -1
- package/test/reply-trailers.test.js +70 -0
- package/test/request-media-type.test.js +105 -0
- package/test/request-port.test.js +72 -0
- package/test/route-prefix.test.js +34 -0
- package/test/router-options.test.js +222 -11
- package/test/schema-serialization.test.js +108 -0
- package/test/schema-validation.test.js +45 -0
- package/test/scripts/validate-ecosystem-links.test.js +40 -57
- package/test/throw.test.js +14 -0
- package/test/trust-proxy.test.js +74 -0
- package/test/types/content-type-parser.tst.ts +70 -0
- package/test/types/{decorate-request-reply.test-d.ts → decorate-request-reply.tst.ts} +4 -4
- package/test/types/{dummy-plugin.ts → dummy-plugin.mts} +1 -1
- package/test/types/errors.tst.ts +91 -0
- package/test/types/fastify.tst.ts +351 -0
- package/test/types/hooks.tst.ts +578 -0
- package/test/types/instance.tst.ts +597 -0
- package/test/types/{logger.test-d.ts → logger.tst.ts} +61 -62
- package/test/types/plugin.tst.ts +96 -0
- package/test/types/register.tst.ts +245 -0
- package/test/types/reply.tst.ts +297 -0
- package/test/types/request.tst.ts +199 -0
- package/test/types/route.tst.ts +576 -0
- package/test/types/{schema.test-d.ts → schema.tst.ts} +22 -22
- package/test/types/{serverFactory.test-d.ts → serverFactory.tst.ts} +4 -4
- package/test/types/tsconfig.json +9 -0
- package/test/types/{type-provider.test-d.ts → type-provider.tst.ts} +256 -250
- package/test/types/using.tst.ts +14 -0
- package/types/errors.d.ts +3 -0
- package/types/request.d.ts +23 -2
- package/AGENTS.md +0 -290
- package/test/types/content-type-parser.test-d.ts +0 -72
- package/test/types/errors.test-d.ts +0 -90
- package/test/types/fastify.test-d.ts +0 -352
- package/test/types/hooks.test-d.ts +0 -550
- package/test/types/import.ts +0 -2
- package/test/types/instance.test-d.ts +0 -588
- package/test/types/plugin.test-d.ts +0 -97
- package/test/types/register.test-d.ts +0 -237
- package/test/types/reply.test-d.ts +0 -254
- package/test/types/request.test-d.ts +0 -188
- package/test/types/route.test-d.ts +0 -553
- package/test/types/using.test-d.ts +0 -17
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { test } = require('node:test')
|
|
4
|
+
const Fastify = require('..')
|
|
5
|
+
|
|
6
|
+
test('hasRequestDecorator returns true for built-in constructor-assigned request properties', t => {
|
|
7
|
+
t.plan(6)
|
|
8
|
+
const fastify = Fastify()
|
|
9
|
+
t.assert.equal(fastify.hasRequestDecorator('id'), true)
|
|
10
|
+
t.assert.equal(fastify.hasRequestDecorator('params'), true)
|
|
11
|
+
t.assert.equal(fastify.hasRequestDecorator('raw'), true)
|
|
12
|
+
t.assert.equal(fastify.hasRequestDecorator('query'), true)
|
|
13
|
+
t.assert.equal(fastify.hasRequestDecorator('log'), true)
|
|
14
|
+
t.assert.equal(fastify.hasRequestDecorator('body'), true)
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
test('hasReplyDecorator returns true for built-in constructor-assigned reply properties', t => {
|
|
18
|
+
t.plan(3)
|
|
19
|
+
const fastify = Fastify()
|
|
20
|
+
t.assert.equal(fastify.hasReplyDecorator('raw'), true)
|
|
21
|
+
t.assert.equal(fastify.hasReplyDecorator('request'), true)
|
|
22
|
+
t.assert.equal(fastify.hasReplyDecorator('log'), true)
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
test('decorateRequest throws FST_ERR_DEC_ALREADY_PRESENT for built-in request properties', t => {
|
|
26
|
+
t.plan(4)
|
|
27
|
+
const fastify = Fastify()
|
|
28
|
+
for (const name of ['id', 'params', 'raw', 'body']) {
|
|
29
|
+
t.assert.throws(
|
|
30
|
+
() => fastify.decorateRequest(name, null),
|
|
31
|
+
(err) => err.code === 'FST_ERR_DEC_ALREADY_PRESENT'
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
test('decorateReply throws FST_ERR_DEC_ALREADY_PRESENT for built-in reply properties', t => {
|
|
37
|
+
t.plan(2)
|
|
38
|
+
const fastify = Fastify()
|
|
39
|
+
for (const name of ['raw', 'request']) {
|
|
40
|
+
t.assert.throws(
|
|
41
|
+
() => fastify.decorateReply(name, null),
|
|
42
|
+
(err) => err.code === 'FST_ERR_DEC_ALREADY_PRESENT'
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
test('hasRequestDecorator returns false for unknown properties', t => {
|
|
48
|
+
t.plan(1)
|
|
49
|
+
const fastify = Fastify()
|
|
50
|
+
t.assert.equal(fastify.hasRequestDecorator('nonExistent'), false)
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
test('decorateRequest still works for non-built-in names', (t, done) => {
|
|
54
|
+
t.plan(2)
|
|
55
|
+
const fastify = Fastify()
|
|
56
|
+
fastify.decorateRequest('myExtra', null)
|
|
57
|
+
t.assert.equal(fastify.hasRequestDecorator('myExtra'), true)
|
|
58
|
+
fastify.get('/', async (req) => req.myExtra)
|
|
59
|
+
fastify.ready((err) => {
|
|
60
|
+
t.assert.ifError(err)
|
|
61
|
+
done()
|
|
62
|
+
})
|
|
63
|
+
})
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { test } = require('node:test')
|
|
4
|
+
const diagnostics = require('node:diagnostics_channel')
|
|
5
|
+
const Fastify = require('../..')
|
|
6
|
+
const Request = require('../../lib/request')
|
|
7
|
+
const Reply = require('../../lib/reply')
|
|
8
|
+
|
|
9
|
+
test('diagnostics channel tracks async operations in async error handlers', async t => {
|
|
10
|
+
t.plan(17)
|
|
11
|
+
let callOrder = 0
|
|
12
|
+
let firstEncounteredMessage
|
|
13
|
+
let asyncStartFired = false
|
|
14
|
+
let asyncEndFired = false
|
|
15
|
+
|
|
16
|
+
const fastify = Fastify()
|
|
17
|
+
|
|
18
|
+
function subscribe (name, handler) {
|
|
19
|
+
const wrapped = (msg) => {
|
|
20
|
+
if (msg.request.server === fastify) handler(msg)
|
|
21
|
+
}
|
|
22
|
+
diagnostics.subscribe(name, wrapped)
|
|
23
|
+
t.after(() => diagnostics.unsubscribe(name, wrapped))
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
subscribe('tracing:fastify.request.handler:start', (msg) => {
|
|
27
|
+
t.assert.strictEqual(callOrder++, 0)
|
|
28
|
+
firstEncounteredMessage = msg
|
|
29
|
+
t.assert.ok(msg.request instanceof Request)
|
|
30
|
+
t.assert.ok(msg.reply instanceof Reply)
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
subscribe('tracing:fastify.request.handler:error', (msg) => {
|
|
34
|
+
t.assert.strictEqual(callOrder++, 1)
|
|
35
|
+
t.assert.ok(msg.error instanceof Error)
|
|
36
|
+
t.assert.strictEqual(msg.error.message, 'handler error')
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
subscribe('tracing:fastify.request.handler:end', (msg) => {
|
|
40
|
+
t.assert.strictEqual(callOrder++, 2)
|
|
41
|
+
t.assert.strictEqual(msg, firstEncounteredMessage)
|
|
42
|
+
t.assert.strictEqual(msg.async, true)
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
subscribe('tracing:fastify.request.handler:asyncStart', (msg) => {
|
|
46
|
+
t.assert.strictEqual(callOrder++, 3)
|
|
47
|
+
t.assert.strictEqual(msg, firstEncounteredMessage)
|
|
48
|
+
asyncStartFired = true
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
subscribe('tracing:fastify.request.handler:asyncEnd', (msg) => {
|
|
52
|
+
t.assert.strictEqual(callOrder++, 4)
|
|
53
|
+
t.assert.strictEqual(msg, firstEncounteredMessage)
|
|
54
|
+
asyncEndFired = true
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
fastify.setErrorHandler(async (error, request, reply) => {
|
|
58
|
+
await new Promise(resolve => setImmediate(resolve))
|
|
59
|
+
reply.status(503).send({ error: error.message })
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
fastify.get('/', () => {
|
|
63
|
+
throw new Error('handler error')
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
const fastifyServer = await fastify.listen({ port: 0 })
|
|
67
|
+
t.after(() => fastify.close())
|
|
68
|
+
|
|
69
|
+
const response = await fetch(fastifyServer)
|
|
70
|
+
t.assert.ok(!response.ok)
|
|
71
|
+
t.assert.strictEqual(response.status, 503)
|
|
72
|
+
t.assert.ok(asyncStartFired, 'asyncStart should fire for async error handler')
|
|
73
|
+
t.assert.ok(asyncEndFired, 'asyncEnd should fire for async error handler')
|
|
74
|
+
})
|
package/test/hooks.test.js
CHANGED
|
@@ -12,6 +12,7 @@ const proxyquire = require('proxyquire')
|
|
|
12
12
|
const { connect } = require('node:net')
|
|
13
13
|
const { sleep } = require('./helper')
|
|
14
14
|
const { waitForCb } = require('./toolkit.js')
|
|
15
|
+
const { fetch } = require('undici')
|
|
15
16
|
|
|
16
17
|
process.removeAllListeners('warning')
|
|
17
18
|
|
|
@@ -3277,6 +3278,28 @@ test('onTimeout should be triggered and socket _meta is set', async t => {
|
|
|
3277
3278
|
}
|
|
3278
3279
|
})
|
|
3279
3280
|
|
|
3281
|
+
test('socket._meta is cleared after response to prevent keep-alive leaks', async t => {
|
|
3282
|
+
t.plan(3)
|
|
3283
|
+
const fastify = Fastify({ connectionTimeout: 500 })
|
|
3284
|
+
t.after(() => { fastify.close() })
|
|
3285
|
+
|
|
3286
|
+
fastify.addHook('onTimeout', function (req, res, done) { done() })
|
|
3287
|
+
|
|
3288
|
+
fastify.addHook('onResponse', function (req, reply, done) {
|
|
3289
|
+
t.assert.strictEqual(req.raw.socket._meta, null, 'socket._meta must be null after response')
|
|
3290
|
+
done()
|
|
3291
|
+
})
|
|
3292
|
+
|
|
3293
|
+
fastify.get('/', async (req, reply) => {
|
|
3294
|
+
return { hello: 'world' }
|
|
3295
|
+
})
|
|
3296
|
+
|
|
3297
|
+
const address = await fastify.listen({ port: 0 })
|
|
3298
|
+
const result = await fetch(address)
|
|
3299
|
+
t.assert.ok(result.ok)
|
|
3300
|
+
t.assert.strictEqual(result.status, 200)
|
|
3301
|
+
})
|
|
3302
|
+
|
|
3280
3303
|
test('registering invalid hooks should throw an error', async t => {
|
|
3281
3304
|
t.plan(3)
|
|
3282
3305
|
|
package/test/http2/plain.test.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const { test } = require('node:test')
|
|
4
|
+
const http2 = require('node:http2')
|
|
4
5
|
const Fastify = require('../..')
|
|
5
6
|
const h2url = require('h2url')
|
|
6
7
|
const msg = { hello: 'world' }
|
|
@@ -66,3 +67,137 @@ test('http2 plain test', async t => {
|
|
|
66
67
|
t.assert.strictEqual(JSON.parse(res.body).port, parseInt(host.split(':')[1]))
|
|
67
68
|
})
|
|
68
69
|
})
|
|
70
|
+
|
|
71
|
+
test('http2 large non-stream replies are sent completely', async t => {
|
|
72
|
+
const modes = ['buffer', 'string']
|
|
73
|
+
|
|
74
|
+
for (const mode of modes) {
|
|
75
|
+
await t.test(mode, async t => {
|
|
76
|
+
const fastify = Fastify({ http2: true })
|
|
77
|
+
const payload = mode === 'buffer'
|
|
78
|
+
? Buffer.alloc((64 * 1024) + 1, 'a')
|
|
79
|
+
: 'a'.repeat((64 * 1024) + 1)
|
|
80
|
+
const contentLength = Buffer.byteLength(payload)
|
|
81
|
+
|
|
82
|
+
fastify.get('/large', async (req, reply) => {
|
|
83
|
+
reply.header('content-type', 'application/octet-stream')
|
|
84
|
+
reply.header('content-length', contentLength)
|
|
85
|
+
|
|
86
|
+
return payload
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
await fastify.listen({ port: 0, host: '127.0.0.1' })
|
|
90
|
+
|
|
91
|
+
const client = http2.connect(`http://127.0.0.1:${fastify.server.address().port}`)
|
|
92
|
+
t.after(() => {
|
|
93
|
+
client.close()
|
|
94
|
+
return fastify.close()
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
await new Promise((resolve, reject) => {
|
|
98
|
+
const large = client.request({
|
|
99
|
+
[http2.constants.HTTP2_HEADER_METHOD]: http2.constants.HTTP2_METHOD_GET,
|
|
100
|
+
[http2.constants.HTTP2_HEADER_PATH]: '/large'
|
|
101
|
+
})
|
|
102
|
+
const chunks = []
|
|
103
|
+
|
|
104
|
+
large.on('error', reject)
|
|
105
|
+
large.on('data', chunk => {
|
|
106
|
+
chunks.push(chunk)
|
|
107
|
+
})
|
|
108
|
+
large.on('end', () => {
|
|
109
|
+
t.assert.strictEqual(Buffer.concat(chunks).length, contentLength)
|
|
110
|
+
resolve()
|
|
111
|
+
})
|
|
112
|
+
large.end()
|
|
113
|
+
})
|
|
114
|
+
})
|
|
115
|
+
}
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
test('http2 large buffer replies can be cancelled without rejecting the next stream', async t => {
|
|
119
|
+
const fastify = Fastify({ http2: true })
|
|
120
|
+
const payload = Buffer.alloc(32 * 1024 * 1024, 'a')
|
|
121
|
+
let smallHit = false
|
|
122
|
+
|
|
123
|
+
fastify.get('/large', async (req, reply) => {
|
|
124
|
+
reply.header('content-type', 'application/octet-stream')
|
|
125
|
+
reply.header('content-length', payload.length)
|
|
126
|
+
|
|
127
|
+
return payload
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
fastify.get('/small', async () => {
|
|
131
|
+
smallHit = true
|
|
132
|
+
return 'ok'
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
await fastify.listen({ port: 0, host: '127.0.0.1' })
|
|
136
|
+
|
|
137
|
+
const client = http2.connect(`http://127.0.0.1:${fastify.server.address().port}`)
|
|
138
|
+
t.after(() => {
|
|
139
|
+
client.close()
|
|
140
|
+
return fastify.close()
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
await new Promise((resolve, reject) => {
|
|
144
|
+
let cancelTimer
|
|
145
|
+
let requestTimer
|
|
146
|
+
let timeout
|
|
147
|
+
|
|
148
|
+
function cleanup () {
|
|
149
|
+
clearTimeout(cancelTimer)
|
|
150
|
+
clearTimeout(requestTimer)
|
|
151
|
+
clearTimeout(timeout)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const large = client.request({
|
|
155
|
+
[http2.constants.HTTP2_HEADER_METHOD]: http2.constants.HTTP2_METHOD_GET,
|
|
156
|
+
[http2.constants.HTTP2_HEADER_PATH]: '/large'
|
|
157
|
+
})
|
|
158
|
+
let largeResponded = false
|
|
159
|
+
|
|
160
|
+
large.on('error', err => {
|
|
161
|
+
if (!largeResponded) {
|
|
162
|
+
cleanup()
|
|
163
|
+
reject(err)
|
|
164
|
+
}
|
|
165
|
+
})
|
|
166
|
+
large.on('response', () => {
|
|
167
|
+
largeResponded = true
|
|
168
|
+
large.pause()
|
|
169
|
+
cancelTimer = setTimeout(() => {
|
|
170
|
+
large.close(http2.constants.NGHTTP2_CANCEL)
|
|
171
|
+
}, 100)
|
|
172
|
+
requestTimer = setTimeout(() => {
|
|
173
|
+
const small = client.request({
|
|
174
|
+
[http2.constants.HTTP2_HEADER_METHOD]: http2.constants.HTTP2_METHOD_GET,
|
|
175
|
+
[http2.constants.HTTP2_HEADER_PATH]: '/small'
|
|
176
|
+
})
|
|
177
|
+
let body = ''
|
|
178
|
+
|
|
179
|
+
timeout = setTimeout(() => {
|
|
180
|
+
cleanup()
|
|
181
|
+
reject(new Error('timed out waiting for /small response'))
|
|
182
|
+
}, 3000)
|
|
183
|
+
|
|
184
|
+
small.setEncoding('utf8')
|
|
185
|
+
small.on('error', err => {
|
|
186
|
+
cleanup()
|
|
187
|
+
reject(err)
|
|
188
|
+
})
|
|
189
|
+
small.on('data', chunk => {
|
|
190
|
+
body += chunk
|
|
191
|
+
})
|
|
192
|
+
small.on('end', () => {
|
|
193
|
+
cleanup()
|
|
194
|
+
t.assert.strictEqual(body, 'ok')
|
|
195
|
+
t.assert.strictEqual(smallHit, true)
|
|
196
|
+
resolve()
|
|
197
|
+
})
|
|
198
|
+
small.end()
|
|
199
|
+
}, 200)
|
|
200
|
+
})
|
|
201
|
+
large.end()
|
|
202
|
+
})
|
|
203
|
+
})
|
|
@@ -6,7 +6,7 @@ const h2url = require('h2url')
|
|
|
6
6
|
const msg = { hello: 'world' }
|
|
7
7
|
|
|
8
8
|
const { buildCertificate } = require('../build-certificate')
|
|
9
|
-
const { Agent } = require('undici')
|
|
9
|
+
const { Agent, fetch } = require('undici')
|
|
10
10
|
test.before(buildCertificate)
|
|
11
11
|
|
|
12
12
|
test('secure with fallback', async (t) => {
|
package/test/https/https.test.js
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const { test } = require('node:test')
|
|
4
|
-
const { request } = require('undici')
|
|
4
|
+
const { request, Agent, fetch } = require('undici')
|
|
5
5
|
const Fastify = require('../..')
|
|
6
6
|
|
|
7
7
|
const { buildCertificate } = require('../build-certificate')
|
|
8
|
-
const { Agent } = require('undici')
|
|
9
8
|
test.before(buildCertificate)
|
|
10
9
|
|
|
11
10
|
test('https', async (t) => {
|
|
@@ -5,7 +5,7 @@ const errors = require('../../lib/errors')
|
|
|
5
5
|
const { readFileSync } = require('node:fs')
|
|
6
6
|
const { resolve } = require('node:path')
|
|
7
7
|
|
|
8
|
-
const expectedErrors =
|
|
8
|
+
const expectedErrors = 91
|
|
9
9
|
|
|
10
10
|
test(`should expose ${expectedErrors} errors`, t => {
|
|
11
11
|
t.plan(1)
|
|
@@ -630,6 +630,16 @@ test('FST_ERR_BAD_URL', t => {
|
|
|
630
630
|
t.assert.ok(error instanceof Error)
|
|
631
631
|
})
|
|
632
632
|
|
|
633
|
+
test('FST_ERR_MAX_PARAM_LENGTH', t => {
|
|
634
|
+
t.plan(5)
|
|
635
|
+
const error = new errors.FST_ERR_MAX_PARAM_LENGTH()
|
|
636
|
+
t.assert.strictEqual(error.name, 'FastifyError')
|
|
637
|
+
t.assert.strictEqual(error.code, 'FST_ERR_MAX_PARAM_LENGTH')
|
|
638
|
+
t.assert.strictEqual(error.message, "'%s' is exceeding the max param length")
|
|
639
|
+
t.assert.strictEqual(error.statusCode, 414)
|
|
640
|
+
t.assert.ok(error instanceof Error)
|
|
641
|
+
})
|
|
642
|
+
|
|
633
643
|
test('FST_ERR_ASYNC_CONSTRAINT', t => {
|
|
634
644
|
t.plan(5)
|
|
635
645
|
const error = new errors.FST_ERR_ASYNC_CONSTRAINT()
|
|
@@ -710,6 +720,16 @@ test('FST_ERR_ROUTE_METHOD_NOT_SUPPORTED', t => {
|
|
|
710
720
|
t.assert.ok(error instanceof Error)
|
|
711
721
|
})
|
|
712
722
|
|
|
723
|
+
test('FST_ERR_ROUTE_LOG_LEVEL_INVALID', t => {
|
|
724
|
+
t.plan(5)
|
|
725
|
+
const error = new errors.FST_ERR_ROUTE_LOG_LEVEL_INVALID()
|
|
726
|
+
t.assert.strictEqual(error.name, 'FastifyError')
|
|
727
|
+
t.assert.strictEqual(error.code, 'FST_ERR_ROUTE_LOG_LEVEL_INVALID')
|
|
728
|
+
t.assert.strictEqual(error.message, "Log level for '%s:%s' route must be a valid logger level. Received: '%s'")
|
|
729
|
+
t.assert.strictEqual(error.statusCode, 500)
|
|
730
|
+
t.assert.ok(error instanceof TypeError)
|
|
731
|
+
})
|
|
732
|
+
|
|
713
733
|
test('FST_ERR_ROUTE_BODY_VALIDATION_SCHEMA_NOT_SUPPORTED', t => {
|
|
714
734
|
t.plan(5)
|
|
715
735
|
const error = new errors.FST_ERR_ROUTE_BODY_VALIDATION_SCHEMA_NOT_SUPPORTED()
|
|
@@ -810,6 +830,16 @@ test('FST_ERR_PLUGIN_INVALID_ASYNC_HANDLER', t => {
|
|
|
810
830
|
t.assert.ok(error instanceof TypeError)
|
|
811
831
|
})
|
|
812
832
|
|
|
833
|
+
test('FST_ERR_PLUGIN_DEPENDENCY_NOT_REGISTERED', t => {
|
|
834
|
+
t.plan(5)
|
|
835
|
+
const error = new errors.FST_ERR_PLUGIN_DEPENDENCY_NOT_REGISTERED('my-dep', 'my-plugin')
|
|
836
|
+
t.assert.strictEqual(error.name, 'FastifyError')
|
|
837
|
+
t.assert.strictEqual(error.code, 'FST_ERR_PLUGIN_DEPENDENCY_NOT_REGISTERED')
|
|
838
|
+
t.assert.strictEqual(error.message, "The dependency 'my-dep' of plugin 'my-plugin' is not registered")
|
|
839
|
+
t.assert.strictEqual(error.statusCode, 500)
|
|
840
|
+
t.assert.ok(error instanceof Error)
|
|
841
|
+
})
|
|
842
|
+
|
|
813
843
|
test('FST_ERR_PLUGIN_CALLBACK_NOT_FN', t => {
|
|
814
844
|
t.plan(5)
|
|
815
845
|
const error = new errors.FST_ERR_PLUGIN_CALLBACK_NOT_FN()
|
|
@@ -149,7 +149,7 @@ test('checkDependencies should check if the given dependency is present in the i
|
|
|
149
149
|
})
|
|
150
150
|
|
|
151
151
|
test('checkDependencies should check if the given dependency is present in the instance (errored)', t => {
|
|
152
|
-
t.plan(
|
|
152
|
+
t.plan(3)
|
|
153
153
|
|
|
154
154
|
fn[Symbol.for('plugin-meta')] = {
|
|
155
155
|
name: 'test-plugin',
|
|
@@ -164,6 +164,8 @@ test('checkDependencies should check if the given dependency is present in the i
|
|
|
164
164
|
t.assert.fail('should throw')
|
|
165
165
|
} catch (err) {
|
|
166
166
|
t.assert.strictEqual(err.message, "The dependency 'plugin' of plugin 'test-plugin' is not registered")
|
|
167
|
+
t.assert.strictEqual(err.code, 'FST_ERR_PLUGIN_DEPENDENCY_NOT_REGISTERED')
|
|
168
|
+
t.assert.strictEqual(err.name, 'FastifyError')
|
|
167
169
|
}
|
|
168
170
|
|
|
169
171
|
function fn () {}
|
|
@@ -487,11 +487,13 @@ test('Request with undefined socket', t => {
|
|
|
487
487
|
t.assert.ok(request.compileValidationSchema instanceof Function)
|
|
488
488
|
})
|
|
489
489
|
|
|
490
|
-
test('Request with trust proxy and undefined socket', t => {
|
|
491
|
-
t.plan(
|
|
490
|
+
test('Request with trust proxy and undefined socket does not trust x-forwarded-host/proto', t => {
|
|
491
|
+
t.plan(2)
|
|
492
492
|
const headers = {
|
|
493
|
+
host: 'hostname',
|
|
493
494
|
'x-forwarded-for': '2.2.2.2, 1.1.1.1',
|
|
494
|
-
'x-forwarded-host': 'fastify.test'
|
|
495
|
+
'x-forwarded-host': 'fastify.test',
|
|
496
|
+
'x-forwarded-proto': 'https'
|
|
495
497
|
}
|
|
496
498
|
const req = {
|
|
497
499
|
method: 'GET',
|
|
@@ -502,5 +504,27 @@ test('Request with trust proxy and undefined socket', t => {
|
|
|
502
504
|
|
|
503
505
|
const TpRequest = Request.buildRequest(Request, true)
|
|
504
506
|
const request = new TpRequest('id', 'params', req, 'query', 'log')
|
|
507
|
+
t.assert.strictEqual(request.host, 'hostname')
|
|
508
|
+
t.assert.deepStrictEqual(request.protocol, undefined)
|
|
509
|
+
})
|
|
510
|
+
|
|
511
|
+
test('Request with trust proxy and null socket does not trust x-forwarded-host/proto', t => {
|
|
512
|
+
t.plan(2)
|
|
513
|
+
const headers = {
|
|
514
|
+
host: 'hostname',
|
|
515
|
+
'x-forwarded-for': '2.2.2.2, 1.1.1.1',
|
|
516
|
+
'x-forwarded-host': 'fastify.test',
|
|
517
|
+
'x-forwarded-proto': 'https'
|
|
518
|
+
}
|
|
519
|
+
const req = {
|
|
520
|
+
method: 'GET',
|
|
521
|
+
url: '/',
|
|
522
|
+
socket: null,
|
|
523
|
+
headers
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
const TpRequest = Request.buildRequest(Request, true)
|
|
527
|
+
const request = new TpRequest('id', 'params', req, 'query', 'log')
|
|
528
|
+
t.assert.strictEqual(request.host, 'hostname')
|
|
505
529
|
t.assert.deepStrictEqual(request.protocol, undefined)
|
|
506
530
|
})
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const { sep } = require('node:path')
|
|
2
2
|
const { test } = require('node:test')
|
|
3
3
|
const Fastify = require('../../fastify')
|
|
4
|
+
const { kSchemaController } = require('../../lib/symbols')
|
|
4
5
|
|
|
5
6
|
test('SchemaController are NOT loaded when the controllers are custom', async t => {
|
|
6
7
|
const app = Fastify({
|
|
@@ -22,6 +23,38 @@ test('SchemaController are NOT loaded when the controllers are custom', async t
|
|
|
22
23
|
t.assert.equal(stringifyModule, undefined, 'Stringify compiler is loaded')
|
|
23
24
|
})
|
|
24
25
|
|
|
26
|
+
test('isCustomSerializerCompiler flag is set correctly when only buildSerializer is provided', async t => {
|
|
27
|
+
const app = Fastify({
|
|
28
|
+
schemaController: {
|
|
29
|
+
compilersFactory: {
|
|
30
|
+
buildSerializer: () => () => { }
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
await app.ready()
|
|
36
|
+
|
|
37
|
+
const schemaController = app[kSchemaController]
|
|
38
|
+
t.assert.equal(schemaController.isCustomValidatorCompiler, false, 'isCustomValidatorCompiler should be false')
|
|
39
|
+
t.assert.equal(schemaController.isCustomSerializerCompiler, true, 'isCustomSerializerCompiler should be true')
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
test('isCustomValidatorCompiler flag is set correctly when only buildValidator is provided', async t => {
|
|
43
|
+
const app = Fastify({
|
|
44
|
+
schemaController: {
|
|
45
|
+
compilersFactory: {
|
|
46
|
+
buildValidator: () => () => { }
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
await app.ready()
|
|
52
|
+
|
|
53
|
+
const schemaController = app[kSchemaController]
|
|
54
|
+
t.assert.equal(schemaController.isCustomValidatorCompiler, true, 'isCustomValidatorCompiler should be true')
|
|
55
|
+
t.assert.equal(schemaController.isCustomSerializerCompiler, false, 'isCustomSerializerCompiler should be false')
|
|
56
|
+
})
|
|
57
|
+
|
|
25
58
|
test('SchemaController are loaded when the controllers are not custom', async t => {
|
|
26
59
|
const app = Fastify()
|
|
27
60
|
await app.ready()
|
|
@@ -16,7 +16,7 @@ t.test('logging', { timeout: 60000 }, async (t) => {
|
|
|
16
16
|
let localhost
|
|
17
17
|
let localhostForURL
|
|
18
18
|
|
|
19
|
-
t.plan(
|
|
19
|
+
t.plan(15)
|
|
20
20
|
|
|
21
21
|
t.before(async function () {
|
|
22
22
|
[localhost, localhostForURL] = await helper.getLoopbackHost()
|
|
@@ -282,6 +282,23 @@ t.test('logging', { timeout: 60000 }, async (t) => {
|
|
|
282
282
|
t.assert.strictEqual(stream.readableLength, 0)
|
|
283
283
|
})
|
|
284
284
|
|
|
285
|
+
await t.test('should not log incoming request, outgoing response and route not found for 414 onMaxParamLength when disabled', async (t) => {
|
|
286
|
+
t.plan(1)
|
|
287
|
+
const stream = split(JSON.parse)
|
|
288
|
+
const fastify = Fastify({ disableRequestLogging: true, logger: { level: 'info', stream } })
|
|
289
|
+
t.after(() => fastify.close())
|
|
290
|
+
|
|
291
|
+
await fastify.ready()
|
|
292
|
+
|
|
293
|
+
await fastify.inject({
|
|
294
|
+
method: 'GET',
|
|
295
|
+
url: `/${'1234567890'.repeat(12)}`
|
|
296
|
+
})
|
|
297
|
+
|
|
298
|
+
// no more readable data
|
|
299
|
+
t.assert.strictEqual(stream.readableLength, 0)
|
|
300
|
+
})
|
|
301
|
+
|
|
285
302
|
await t.test('should log incoming request and outgoing response based on disableRequestLogging function', async (t) => {
|
|
286
303
|
const lines = [
|
|
287
304
|
'incoming request',
|
|
@@ -10,7 +10,7 @@ const Fastify = require('../../fastify')
|
|
|
10
10
|
const { on } = stream
|
|
11
11
|
|
|
12
12
|
t.test('logger options', { timeout: 60000 }, async (t) => {
|
|
13
|
-
t.plan(
|
|
13
|
+
t.plan(18)
|
|
14
14
|
|
|
15
15
|
await t.test('logger can be silenced', (t) => {
|
|
16
16
|
t.plan(17)
|
|
@@ -479,6 +479,43 @@ t.test('logger options', { timeout: 60000 }, async (t) => {
|
|
|
479
479
|
}
|
|
480
480
|
})
|
|
481
481
|
|
|
482
|
+
await t.test('Should throw when custom log level for a route is invalid', async (t) => {
|
|
483
|
+
t.plan(4)
|
|
484
|
+
|
|
485
|
+
const fastify = Fastify({
|
|
486
|
+
logger: true
|
|
487
|
+
})
|
|
488
|
+
t.after(() => fastify.close())
|
|
489
|
+
|
|
490
|
+
try {
|
|
491
|
+
fastify.get('/log', { logLevel: 'invalid' }, (req, reply) => {
|
|
492
|
+
reply.send({ hello: 'world' })
|
|
493
|
+
})
|
|
494
|
+
t.assert.fail('fastify.get should throw')
|
|
495
|
+
} catch (err) {
|
|
496
|
+
t.assert.ok(err)
|
|
497
|
+
t.assert.strictEqual(err.code, 'FST_ERR_ROUTE_LOG_LEVEL_INVALID')
|
|
498
|
+
t.assert.strictEqual(err.statusCode, 500)
|
|
499
|
+
t.assert.strictEqual(err.message, "Log level for 'GET:/log' route must be a valid logger level. Received: 'invalid'")
|
|
500
|
+
}
|
|
501
|
+
})
|
|
502
|
+
|
|
503
|
+
await t.test('Should allow null custom log level for a route', async (t) => {
|
|
504
|
+
t.plan(1)
|
|
505
|
+
|
|
506
|
+
const fastify = Fastify({
|
|
507
|
+
logger: true
|
|
508
|
+
})
|
|
509
|
+
t.after(() => fastify.close())
|
|
510
|
+
|
|
511
|
+
fastify.get('/log', { logLevel: null }, (req, reply) => {
|
|
512
|
+
reply.send({ hello: 'world' })
|
|
513
|
+
})
|
|
514
|
+
|
|
515
|
+
const response = await fastify.inject({ method: 'GET', url: '/log' })
|
|
516
|
+
t.assert.deepEqual(await response.json(), { hello: 'world' })
|
|
517
|
+
})
|
|
518
|
+
|
|
482
519
|
await t.test('should pass when using unWritable props in the logger option', (t) => {
|
|
483
520
|
t.plan(8)
|
|
484
521
|
const fastify = Fastify({
|
package/test/reply-error.test.js
CHANGED
|
@@ -575,7 +575,7 @@ test('allow re-thrown error to default error handler when route handler is async
|
|
|
575
575
|
})
|
|
576
576
|
|
|
577
577
|
// Issue 2078 https://github.com/fastify/fastify/issues/2078
|
|
578
|
-
// Supported error code list:
|
|
578
|
+
// Supported error code list: https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
|
|
579
579
|
const invalidErrorCodes = [
|
|
580
580
|
undefined,
|
|
581
581
|
null,
|