fastify 3.27.2 → 4.0.0-alpha.1
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/README.md +5 -4
- package/build/build-error-serializer.js +27 -0
- package/build/build-validation.js +47 -35
- package/docs/Migration-Guide-V4.md +12 -0
- package/docs/Reference/ContentTypeParser.md +4 -0
- package/docs/Reference/Errors.md +51 -6
- package/docs/Reference/Hooks.md +4 -7
- package/docs/Reference/LTS.md +5 -4
- package/docs/Reference/Reply.md +23 -22
- package/docs/Reference/Request.md +1 -3
- package/docs/Reference/Routes.md +17 -10
- package/docs/Reference/Server.md +48 -63
- package/docs/Reference/TypeScript.md +11 -13
- package/docs/Reference/Validation-and-Serialization.md +28 -53
- package/docs/Type-Providers.md +257 -0
- package/examples/hooks.js +1 -1
- package/examples/simple-stream.js +18 -0
- package/fastify.d.ts +34 -22
- package/fastify.js +37 -35
- package/lib/configValidator.js +902 -1023
- package/lib/contentTypeParser.js +6 -16
- package/lib/context.js +36 -10
- package/lib/decorate.js +3 -1
- package/lib/error-handler.js +158 -0
- package/lib/error-serializer.js +257 -0
- package/lib/errors.js +43 -9
- package/lib/fourOhFour.js +31 -20
- package/lib/handleRequest.js +10 -13
- package/lib/hooks.js +14 -9
- package/lib/pluginOverride.js +0 -3
- package/lib/pluginUtils.js +3 -2
- package/lib/reply.js +28 -157
- package/lib/request.js +13 -10
- package/lib/route.js +131 -138
- package/lib/schema-controller.js +2 -2
- package/lib/schemas.js +27 -1
- package/lib/server.js +219 -116
- package/lib/symbols.js +4 -3
- package/lib/validation.js +2 -1
- package/lib/warnings.js +2 -12
- package/lib/wrapThenable.js +4 -11
- package/package.json +31 -35
- package/test/404s.test.js +243 -110
- package/test/500s.test.js +2 -2
- package/test/async-await.test.js +13 -69
- package/test/content-parser.test.js +32 -0
- package/test/context-config.test.js +52 -0
- package/test/custom-http-server.test.js +14 -7
- package/test/custom-parser-async.test.js +0 -65
- package/test/custom-parser.test.js +54 -121
- package/test/decorator.test.js +1 -3
- package/test/delete.test.js +5 -5
- package/test/encapsulated-error-handler.test.js +50 -0
- package/test/esm/index.test.js +0 -14
- package/test/fastify-instance.test.js +4 -4
- package/test/fluent-schema.test.js +4 -4
- package/test/get.test.js +3 -3
- package/test/helper.js +18 -3
- package/test/hooks-async.test.js +14 -47
- package/test/hooks.on-ready.test.js +9 -4
- package/test/hooks.test.js +58 -99
- package/test/http2/closing.test.js +5 -11
- package/test/http2/unknown-http-method.test.js +3 -9
- package/test/https/custom-https-server.test.js +12 -6
- package/test/input-validation.js +2 -2
- package/test/internals/handleRequest.test.js +3 -40
- package/test/internals/initialConfig.test.js +33 -12
- package/test/internals/reply.test.js +245 -3
- package/test/internals/request.test.js +13 -7
- package/test/internals/server.test.js +88 -0
- package/test/listen.test.js +84 -1
- package/test/logger.test.js +80 -40
- package/test/maxRequestsPerSocket.test.js +6 -4
- package/test/middleware.test.js +2 -25
- package/test/nullable-validation.test.js +51 -14
- package/test/plugin.test.js +31 -5
- package/test/pretty-print.test.js +22 -10
- package/test/reply-error.test.js +123 -12
- package/test/request-error.test.js +2 -5
- package/test/route-hooks.test.js +17 -17
- package/test/route-prefix.test.js +2 -1
- package/test/route.test.js +204 -20
- package/test/router-options.test.js +1 -1
- package/test/schema-examples.test.js +11 -5
- package/test/schema-feature.test.js +24 -19
- package/test/schema-serialization.test.js +9 -9
- package/test/schema-special-usage.test.js +14 -81
- package/test/schema-validation.test.js +9 -9
- package/test/skip-reply-send.test.js +1 -1
- package/test/stream.test.js +23 -12
- package/test/throw.test.js +8 -5
- package/test/type-provider.test.js +20 -0
- package/test/types/fastify.test-d.ts +10 -18
- package/test/types/import.js +2 -0
- package/test/types/import.ts +1 -0
- package/test/types/instance.test-d.ts +35 -14
- package/test/types/logger.test-d.ts +44 -15
- package/test/types/route.test-d.ts +8 -2
- package/test/types/schema.test-d.ts +2 -39
- package/test/types/type-provider.test-d.ts +417 -0
- package/test/validation-error-handling.test.js +8 -8
- package/test/versioned-routes.test.js +28 -16
- package/test/wrapThenable.test.js +7 -6
- package/types/content-type-parser.d.ts +17 -8
- package/types/hooks.d.ts +102 -59
- package/types/instance.d.ts +124 -104
- package/types/logger.d.ts +18 -104
- package/types/plugin.d.ts +10 -4
- package/types/reply.d.ts +16 -11
- package/types/request.d.ts +10 -5
- package/types/route.d.ts +42 -31
- package/types/schema.d.ts +1 -1
- package/types/type-provider.d.ts +99 -0
- package/types/utils.d.ts +1 -1
- package/lib/schema-compilers.js +0 -12
- package/test/emit-warning.test.js +0 -166
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
const t = require('tap')
|
|
4
4
|
const Fastify = require('../..')
|
|
5
5
|
const http2 = require('http2')
|
|
6
|
-
const semver = require('semver')
|
|
7
6
|
const { promisify } = require('util')
|
|
8
7
|
const connect = promisify(http2.connect)
|
|
9
8
|
const { once } = require('events')
|
|
@@ -37,8 +36,7 @@ t.test('http/2 request while fastify closing', t => {
|
|
|
37
36
|
t.error(err)
|
|
38
37
|
fastify.server.unref()
|
|
39
38
|
|
|
40
|
-
|
|
41
|
-
t.test('return 200', { skip: semver.lt(process.versions.node, '10.15.0') }, t => {
|
|
39
|
+
t.test('return 200', t => {
|
|
42
40
|
const url = getUrl(fastify)
|
|
43
41
|
const session = http2.connect(url, function () {
|
|
44
42
|
this.request({
|
|
@@ -85,8 +83,7 @@ t.test('http/2 request while fastify closing - return503OnClosing: false', t =>
|
|
|
85
83
|
t.error(err)
|
|
86
84
|
fastify.server.unref()
|
|
87
85
|
|
|
88
|
-
|
|
89
|
-
t.test('return 200', { skip: semver.lt(process.versions.node, '10.15.0') }, t => {
|
|
86
|
+
t.test('return 200', t => {
|
|
90
87
|
const url = getUrl(fastify)
|
|
91
88
|
const session = http2.connect(url, function () {
|
|
92
89
|
this.request({
|
|
@@ -115,8 +112,7 @@ t.test('http/2 request while fastify closing - return503OnClosing: false', t =>
|
|
|
115
112
|
})
|
|
116
113
|
})
|
|
117
114
|
|
|
118
|
-
|
|
119
|
-
t.test('http/2 closes successfully with async await', { skip: semver.lt(process.versions.node, '10.15.0') }, async t => {
|
|
115
|
+
t.test('http/2 closes successfully with async await', async t => {
|
|
120
116
|
const fastify = Fastify({
|
|
121
117
|
http2SessionTimeout: 100,
|
|
122
118
|
http2: true
|
|
@@ -131,8 +127,7 @@ t.test('http/2 closes successfully with async await', { skip: semver.lt(process.
|
|
|
131
127
|
await fastify.close()
|
|
132
128
|
})
|
|
133
129
|
|
|
134
|
-
|
|
135
|
-
t.test('https/2 closes successfully with async await', { skip: semver.lt(process.versions.node, '10.15.0') }, async t => {
|
|
130
|
+
t.test('https/2 closes successfully with async await', async t => {
|
|
136
131
|
const fastify = Fastify({
|
|
137
132
|
http2SessionTimeout: 100,
|
|
138
133
|
http2: true,
|
|
@@ -151,8 +146,7 @@ t.test('https/2 closes successfully with async await', { skip: semver.lt(process
|
|
|
151
146
|
await fastify.close()
|
|
152
147
|
})
|
|
153
148
|
|
|
154
|
-
|
|
155
|
-
t.test('http/2 server side session emits a timeout event', { skip: semver.lt(process.versions.node, '10.15.0') }, async t => {
|
|
149
|
+
t.test('http/2 server side session emits a timeout event', async t => {
|
|
156
150
|
let _resolve
|
|
157
151
|
const p = new Promise((resolve) => { _resolve = resolve })
|
|
158
152
|
|
|
@@ -6,15 +6,9 @@ const Fastify = require('../..')
|
|
|
6
6
|
const h2url = require('h2url')
|
|
7
7
|
const msg = { hello: 'world' }
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
http2: true
|
|
13
|
-
})
|
|
14
|
-
t.pass('http2 successfully loaded')
|
|
15
|
-
} catch (e) {
|
|
16
|
-
t.fail('http2 loading failed', e)
|
|
17
|
-
}
|
|
9
|
+
const fastify = Fastify({
|
|
10
|
+
http2: true
|
|
11
|
+
})
|
|
18
12
|
|
|
19
13
|
fastify.get('/', function (req, reply) {
|
|
20
14
|
reply.code(200).send(msg)
|
|
@@ -5,15 +5,18 @@ const test = t.test
|
|
|
5
5
|
const Fastify = require('../..')
|
|
6
6
|
const https = require('https')
|
|
7
7
|
const sget = require('simple-get').concat
|
|
8
|
+
const dns = require('dns').promises
|
|
8
9
|
|
|
9
10
|
const { buildCertificate } = require('../build-certificate')
|
|
10
11
|
t.before(buildCertificate)
|
|
11
12
|
|
|
12
|
-
test('Should support a custom https server', t => {
|
|
13
|
-
|
|
13
|
+
test('Should support a custom https server', async t => {
|
|
14
|
+
const localAddresses = await dns.lookup('localhost', { all: true })
|
|
15
|
+
|
|
16
|
+
t.plan(localAddresses.length + 3)
|
|
14
17
|
|
|
15
18
|
const serverFactory = (handler, opts) => {
|
|
16
|
-
t.ok(opts.serverFactory)
|
|
19
|
+
t.ok(opts.serverFactory, 'it is called twice for every HOST interface')
|
|
17
20
|
|
|
18
21
|
const options = {
|
|
19
22
|
key: global.context.key,
|
|
@@ -37,17 +40,20 @@ test('Should support a custom https server', t => {
|
|
|
37
40
|
reply.send({ hello: 'world' })
|
|
38
41
|
})
|
|
39
42
|
|
|
40
|
-
fastify.listen(0
|
|
41
|
-
t.error(err)
|
|
43
|
+
await fastify.listen(0)
|
|
42
44
|
|
|
45
|
+
await new Promise((resolve, reject) => {
|
|
43
46
|
sget({
|
|
44
47
|
method: 'GET',
|
|
45
48
|
url: 'https://localhost:' + fastify.server.address().port,
|
|
46
49
|
rejectUnauthorized: false
|
|
47
50
|
}, (err, response, body) => {
|
|
48
|
-
|
|
51
|
+
if (err) {
|
|
52
|
+
return reject(err)
|
|
53
|
+
}
|
|
49
54
|
t.equal(response.statusCode, 200)
|
|
50
55
|
t.same(JSON.parse(body), { hello: 'world' })
|
|
56
|
+
resolve()
|
|
51
57
|
})
|
|
52
58
|
})
|
|
53
59
|
})
|
package/test/input-validation.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
const sget = require('simple-get').concat
|
|
5
5
|
const Ajv = require('ajv')
|
|
6
|
-
const Joi = require('
|
|
6
|
+
const Joi = require('joi')
|
|
7
7
|
const yup = require('yup')
|
|
8
8
|
|
|
9
9
|
module.exports.payloadMethod = function (method, t) {
|
|
@@ -182,7 +182,7 @@ module.exports.payloadMethod = function (method, t) {
|
|
|
182
182
|
t.equal(response.statusCode, 400)
|
|
183
183
|
t.same(body, {
|
|
184
184
|
error: 'Bad Request',
|
|
185
|
-
message: 'body
|
|
185
|
+
message: 'body/hello must be integer',
|
|
186
186
|
statusCode: 400
|
|
187
187
|
})
|
|
188
188
|
})
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const { test } = require('tap')
|
|
4
|
-
const semver = require('semver')
|
|
5
4
|
const handleRequest = require('../../lib/handleRequest')
|
|
6
5
|
const internals = require('../../lib/handleRequest')[Symbol.for('internals')]
|
|
7
6
|
const Request = require('../../lib/request')
|
|
@@ -40,13 +39,8 @@ test('handleRequest function - invoke with error', t => {
|
|
|
40
39
|
})
|
|
41
40
|
|
|
42
41
|
test('handler function - invalid schema', t => {
|
|
43
|
-
t.plan(
|
|
42
|
+
t.plan(1)
|
|
44
43
|
const res = {}
|
|
45
|
-
res.end = () => {
|
|
46
|
-
t.equal(res.statusCode, 400)
|
|
47
|
-
t.pass()
|
|
48
|
-
}
|
|
49
|
-
res.writeHead = () => {}
|
|
50
44
|
res.log = { error: () => {}, info: () => {} }
|
|
51
45
|
const context = {
|
|
52
46
|
config: {
|
|
@@ -61,6 +55,7 @@ test('handler function - invalid schema', t => {
|
|
|
61
55
|
}
|
|
62
56
|
}
|
|
63
57
|
},
|
|
58
|
+
errorHandler: { func: () => { t.pass('errorHandler called') } },
|
|
64
59
|
handler: () => {},
|
|
65
60
|
Reply,
|
|
66
61
|
Request,
|
|
@@ -108,39 +103,7 @@ test('handler function - preValidationCallback with finished response', t => {
|
|
|
108
103
|
t.plan(0)
|
|
109
104
|
const res = {}
|
|
110
105
|
// Be sure to check only `writableEnded` where is available
|
|
111
|
-
|
|
112
|
-
res.writableEnded = true
|
|
113
|
-
} else {
|
|
114
|
-
res.writable = false
|
|
115
|
-
res.finished = true
|
|
116
|
-
}
|
|
117
|
-
res.end = () => {
|
|
118
|
-
t.fail()
|
|
119
|
-
}
|
|
120
|
-
res.writeHead = () => {}
|
|
121
|
-
const context = {
|
|
122
|
-
handler: (req, reply) => {
|
|
123
|
-
t.fail()
|
|
124
|
-
reply.send(undefined)
|
|
125
|
-
},
|
|
126
|
-
Reply,
|
|
127
|
-
Request,
|
|
128
|
-
preValidation: null,
|
|
129
|
-
preHandler: [],
|
|
130
|
-
onSend: [],
|
|
131
|
-
onError: []
|
|
132
|
-
}
|
|
133
|
-
buildSchema(context, schemaValidator)
|
|
134
|
-
internals.handler({}, new Reply(res, { context }))
|
|
135
|
-
})
|
|
136
|
-
|
|
137
|
-
test('handler function - preValidationCallback with finished response (< v12.9.0)', t => {
|
|
138
|
-
t.plan(0)
|
|
139
|
-
const res = {}
|
|
140
|
-
// Be sure to check only `writableEnded` where is available
|
|
141
|
-
res.writable = false
|
|
142
|
-
res.finished = true
|
|
143
|
-
|
|
106
|
+
res.writableEnded = true
|
|
144
107
|
res.end = () => {
|
|
145
108
|
t.fail()
|
|
146
109
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const { test, before } = require('tap')
|
|
4
4
|
const Fastify = require('../..')
|
|
5
|
+
const helper = require('../helper')
|
|
5
6
|
const http = require('http')
|
|
6
7
|
const pino = require('pino')
|
|
7
8
|
const split = require('split2')
|
|
@@ -9,10 +10,17 @@ const deepClone = require('rfdc')({ circles: true, proto: false })
|
|
|
9
10
|
const { deepFreezeObject } = require('../../lib/initialConfigValidation').utils
|
|
10
11
|
|
|
11
12
|
const { buildCertificate } = require('../build-certificate')
|
|
12
|
-
before(buildCertificate)
|
|
13
13
|
|
|
14
14
|
process.removeAllListeners('warning')
|
|
15
15
|
|
|
16
|
+
let localhost
|
|
17
|
+
let localhostForURL
|
|
18
|
+
|
|
19
|
+
before(async function () {
|
|
20
|
+
await buildCertificate();
|
|
21
|
+
[localhost, localhostForURL] = await helper.getLoopbackHost()
|
|
22
|
+
})
|
|
23
|
+
|
|
16
24
|
test('Fastify.initialConfig is an object', t => {
|
|
17
25
|
t.plan(1)
|
|
18
26
|
t.type(Fastify().initialConfig, 'object')
|
|
@@ -23,7 +31,8 @@ test('without options passed to Fastify, initialConfig should expose default val
|
|
|
23
31
|
|
|
24
32
|
const fastifyDefaultOptions = {
|
|
25
33
|
connectionTimeout: 0,
|
|
26
|
-
keepAliveTimeout:
|
|
34
|
+
keepAliveTimeout: 72000,
|
|
35
|
+
forceCloseConnections: false,
|
|
27
36
|
maxRequestsPerSocket: 0,
|
|
28
37
|
requestTimeout: 0,
|
|
29
38
|
bodyLimit: 1024 * 1024,
|
|
@@ -37,7 +46,8 @@ test('without options passed to Fastify, initialConfig should expose default val
|
|
|
37
46
|
pluginTimeout: 10000,
|
|
38
47
|
requestIdHeader: 'request-id',
|
|
39
48
|
requestIdLogLabel: 'reqId',
|
|
40
|
-
http2SessionTimeout:
|
|
49
|
+
http2SessionTimeout: 72000,
|
|
50
|
+
exposeHeadRoutes: true
|
|
41
51
|
}
|
|
42
52
|
|
|
43
53
|
t.same(Fastify().initialConfig, fastifyDefaultOptions)
|
|
@@ -81,7 +91,7 @@ test('Fastify.initialConfig should expose all options', t => {
|
|
|
81
91
|
ignoreTrailingSlash: true,
|
|
82
92
|
maxParamLength: 200,
|
|
83
93
|
connectionTimeout: 0,
|
|
84
|
-
keepAliveTimeout:
|
|
94
|
+
keepAliveTimeout: 72000,
|
|
85
95
|
bodyLimit: 1049600,
|
|
86
96
|
onProtoPoisoning: 'remove',
|
|
87
97
|
serverFactory,
|
|
@@ -103,11 +113,11 @@ test('Fastify.initialConfig should expose all options', t => {
|
|
|
103
113
|
|
|
104
114
|
const fastify = Fastify(options)
|
|
105
115
|
t.equal(fastify.initialConfig.http2, true)
|
|
106
|
-
t.equal(fastify.initialConfig.https, true)
|
|
116
|
+
t.equal(fastify.initialConfig.https, true, 'for security reason the key cert is hidden')
|
|
107
117
|
t.equal(fastify.initialConfig.ignoreTrailingSlash, true)
|
|
108
118
|
t.equal(fastify.initialConfig.maxParamLength, 200)
|
|
109
119
|
t.equal(fastify.initialConfig.connectionTimeout, 0)
|
|
110
|
-
t.equal(fastify.initialConfig.keepAliveTimeout,
|
|
120
|
+
t.equal(fastify.initialConfig.keepAliveTimeout, 72000)
|
|
111
121
|
t.equal(fastify.initialConfig.bodyLimit, 1049600)
|
|
112
122
|
t.equal(fastify.initialConfig.onProtoPoisoning, 'remove')
|
|
113
123
|
t.equal(fastify.initialConfig.caseSensitive, true)
|
|
@@ -157,10 +167,19 @@ test('We must avoid shallow freezing and ensure that the whole object is freezed
|
|
|
157
167
|
t.type(error, TypeError)
|
|
158
168
|
t.equal(error.message, "Cannot assign to read only property 'allowHTTP1' of object '#<Object>'")
|
|
159
169
|
t.ok(error.stack)
|
|
160
|
-
t.
|
|
170
|
+
t.same(fastify.initialConfig.https, {
|
|
171
|
+
allowHTTP1: true
|
|
172
|
+
}, 'key cert removed')
|
|
161
173
|
}
|
|
162
174
|
})
|
|
163
175
|
|
|
176
|
+
test('https value check', t => {
|
|
177
|
+
t.plan(1)
|
|
178
|
+
|
|
179
|
+
const fastify = Fastify({})
|
|
180
|
+
t.notOk(fastify.initialConfig.https)
|
|
181
|
+
})
|
|
182
|
+
|
|
164
183
|
test('Return an error if options do not match the validation schema', t => {
|
|
165
184
|
t.plan(6)
|
|
166
185
|
|
|
@@ -171,7 +190,7 @@ test('Return an error if options do not match the validation schema', t => {
|
|
|
171
190
|
} catch (error) {
|
|
172
191
|
t.type(error, Error)
|
|
173
192
|
t.equal(error.name, 'FastifyError')
|
|
174
|
-
t.equal(error.message, 'Invalid initialization options: \'["
|
|
193
|
+
t.equal(error.message, 'Invalid initialization options: \'["must be boolean"]\'')
|
|
175
194
|
t.equal(error.code, 'FST_ERR_INIT_OPTS_INVALID')
|
|
176
195
|
t.ok(error.stack)
|
|
177
196
|
t.pass()
|
|
@@ -241,7 +260,8 @@ test('Should not have issues when passing stream options to Pino.js', t => {
|
|
|
241
260
|
t.type(fastify, 'object')
|
|
242
261
|
t.same(fastify.initialConfig, {
|
|
243
262
|
connectionTimeout: 0,
|
|
244
|
-
keepAliveTimeout:
|
|
263
|
+
keepAliveTimeout: 72000,
|
|
264
|
+
forceCloseConnections: false,
|
|
245
265
|
maxRequestsPerSocket: 0,
|
|
246
266
|
requestTimeout: 0,
|
|
247
267
|
bodyLimit: 1024 * 1024,
|
|
@@ -255,7 +275,8 @@ test('Should not have issues when passing stream options to Pino.js', t => {
|
|
|
255
275
|
pluginTimeout: 10000,
|
|
256
276
|
requestIdHeader: 'request-id',
|
|
257
277
|
requestIdLogLabel: 'reqId',
|
|
258
|
-
http2SessionTimeout:
|
|
278
|
+
http2SessionTimeout: 72000,
|
|
279
|
+
exposeHeadRoutes: true
|
|
259
280
|
})
|
|
260
281
|
} catch (error) {
|
|
261
282
|
t.fail()
|
|
@@ -287,11 +308,11 @@ test('Should not have issues when passing stream options to Pino.js', t => {
|
|
|
287
308
|
})
|
|
288
309
|
})
|
|
289
310
|
|
|
290
|
-
fastify.listen(0, err => {
|
|
311
|
+
fastify.listen(0, localhost, err => {
|
|
291
312
|
t.error(err)
|
|
292
313
|
fastify.server.unref()
|
|
293
314
|
|
|
294
|
-
http.get(
|
|
315
|
+
http.get(`http://${localhostForURL}:${fastify.server.address().port}`)
|
|
295
316
|
})
|
|
296
317
|
})
|
|
297
318
|
|
|
@@ -15,6 +15,21 @@ const {
|
|
|
15
15
|
kReplyIsError,
|
|
16
16
|
kReplySerializerDefault
|
|
17
17
|
} = require('../../lib/symbols')
|
|
18
|
+
const fs = require('fs')
|
|
19
|
+
const path = require('path')
|
|
20
|
+
const warning = require('../../lib/warnings')
|
|
21
|
+
|
|
22
|
+
const doGet = function (url) {
|
|
23
|
+
return new Promise((resolve, reject) => {
|
|
24
|
+
sget({ method: 'GET', url, followRedirects: false }, (err, response, body) => {
|
|
25
|
+
if (err) {
|
|
26
|
+
reject(err)
|
|
27
|
+
} else {
|
|
28
|
+
resolve({ response, body })
|
|
29
|
+
}
|
|
30
|
+
})
|
|
31
|
+
})
|
|
32
|
+
}
|
|
18
33
|
|
|
19
34
|
test('Once called, Reply should return an object with methods', t => {
|
|
20
35
|
t.plan(13)
|
|
@@ -37,7 +52,7 @@ test('Once called, Reply should return an object with methods', t => {
|
|
|
37
52
|
t.equal(reply.request, request)
|
|
38
53
|
})
|
|
39
54
|
|
|
40
|
-
test('reply.send will logStream error and destroy the stream',
|
|
55
|
+
test('reply.send will logStream error and destroy the stream', t => {
|
|
41
56
|
t.plan(1)
|
|
42
57
|
let destroyCalled
|
|
43
58
|
const payload = new EventEmitter()
|
|
@@ -452,8 +467,6 @@ test('stream with content type should not send application/octet-stream', t => {
|
|
|
452
467
|
t.plan(4)
|
|
453
468
|
|
|
454
469
|
const fastify = require('../..')()
|
|
455
|
-
const fs = require('fs')
|
|
456
|
-
const path = require('path')
|
|
457
470
|
|
|
458
471
|
const streamPath = path.join(__dirname, '..', '..', 'package.json')
|
|
459
472
|
const stream = fs.createReadStream(streamPath)
|
|
@@ -477,6 +490,32 @@ test('stream with content type should not send application/octet-stream', t => {
|
|
|
477
490
|
})
|
|
478
491
|
})
|
|
479
492
|
|
|
493
|
+
test('stream without content type should not send application/octet-stream', t => {
|
|
494
|
+
t.plan(4)
|
|
495
|
+
|
|
496
|
+
const fastify = require('../..')()
|
|
497
|
+
|
|
498
|
+
const stream = fs.createReadStream(__filename)
|
|
499
|
+
const buf = fs.readFileSync(__filename)
|
|
500
|
+
|
|
501
|
+
fastify.get('/', function (req, reply) {
|
|
502
|
+
reply.send(stream)
|
|
503
|
+
})
|
|
504
|
+
|
|
505
|
+
fastify.listen(0, err => {
|
|
506
|
+
t.error(err)
|
|
507
|
+
fastify.server.unref()
|
|
508
|
+
sget({
|
|
509
|
+
method: 'GET',
|
|
510
|
+
url: 'http://localhost:' + fastify.server.address().port
|
|
511
|
+
}, (err, response, body) => {
|
|
512
|
+
t.error(err)
|
|
513
|
+
t.equal(response.headers['content-type'], undefined)
|
|
514
|
+
t.same(body, buf)
|
|
515
|
+
})
|
|
516
|
+
})
|
|
517
|
+
})
|
|
518
|
+
|
|
480
519
|
test('stream using reply.raw.writeHead should return customize headers', t => {
|
|
481
520
|
t.plan(6)
|
|
482
521
|
|
|
@@ -1301,6 +1340,34 @@ test('reply.header setting multiple cookies as multiple Set-Cookie headers', t =
|
|
|
1301
1340
|
})
|
|
1302
1341
|
})
|
|
1303
1342
|
|
|
1343
|
+
test('should emit deprecation warning when trying to modify the reply.sent property', t => {
|
|
1344
|
+
t.plan(4)
|
|
1345
|
+
const fastify = require('../..')()
|
|
1346
|
+
|
|
1347
|
+
const deprecationCode = 'FSTDEP010'
|
|
1348
|
+
warning.emitted.delete(deprecationCode)
|
|
1349
|
+
|
|
1350
|
+
process.removeAllListeners('warning')
|
|
1351
|
+
process.on('warning', onWarning)
|
|
1352
|
+
function onWarning (warning) {
|
|
1353
|
+
t.equal(warning.name, 'FastifyDeprecation')
|
|
1354
|
+
t.equal(warning.code, deprecationCode)
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
fastify.get('/', (req, reply) => {
|
|
1358
|
+
reply.sent = true
|
|
1359
|
+
|
|
1360
|
+
reply.raw.end()
|
|
1361
|
+
})
|
|
1362
|
+
|
|
1363
|
+
fastify.inject('/', (err, res) => {
|
|
1364
|
+
t.error(err)
|
|
1365
|
+
t.pass()
|
|
1366
|
+
|
|
1367
|
+
process.removeListener('warning', onWarning)
|
|
1368
|
+
})
|
|
1369
|
+
})
|
|
1370
|
+
|
|
1304
1371
|
test('should throw error when passing falsy value to reply.sent', t => {
|
|
1305
1372
|
t.plan(4)
|
|
1306
1373
|
const fastify = require('../..')()
|
|
@@ -1329,6 +1396,7 @@ test('should throw error when attempting to set reply.sent more than once', t =>
|
|
|
1329
1396
|
reply.sent = true
|
|
1330
1397
|
try {
|
|
1331
1398
|
reply.sent = true
|
|
1399
|
+
t.fail('must throw')
|
|
1332
1400
|
} catch (err) {
|
|
1333
1401
|
t.equal(err.code, 'FST_ERR_REP_ALREADY_SENT')
|
|
1334
1402
|
t.equal(err.message, 'Reply was already sent.')
|
|
@@ -1342,6 +1410,23 @@ test('should throw error when attempting to set reply.sent more than once', t =>
|
|
|
1342
1410
|
})
|
|
1343
1411
|
})
|
|
1344
1412
|
|
|
1413
|
+
test('should not throw error when attempting to set reply.sent if the underlining request was sent', t => {
|
|
1414
|
+
t.plan(3)
|
|
1415
|
+
const fastify = require('../..')()
|
|
1416
|
+
|
|
1417
|
+
fastify.get('/', function (req, reply) {
|
|
1418
|
+
reply.raw.end()
|
|
1419
|
+
t.doesNotThrow(() => {
|
|
1420
|
+
reply.sent = true
|
|
1421
|
+
})
|
|
1422
|
+
})
|
|
1423
|
+
|
|
1424
|
+
fastify.inject('/', (err, res) => {
|
|
1425
|
+
t.error(err)
|
|
1426
|
+
t.pass()
|
|
1427
|
+
})
|
|
1428
|
+
})
|
|
1429
|
+
|
|
1345
1430
|
test('reply.getResponseTime() should return 0 before the timer is initialised on the reply by setting up response listeners', t => {
|
|
1346
1431
|
t.plan(1)
|
|
1347
1432
|
const response = { statusCode: 200 }
|
|
@@ -1727,3 +1812,160 @@ test('reply.then', t => {
|
|
|
1727
1812
|
response.destroy(_err)
|
|
1728
1813
|
})
|
|
1729
1814
|
})
|
|
1815
|
+
|
|
1816
|
+
test('reply.sent should read from response.writableEnded if it is defined', t => {
|
|
1817
|
+
t.plan(1)
|
|
1818
|
+
|
|
1819
|
+
const reply = new Reply({ writableEnded: true }, {}, {})
|
|
1820
|
+
|
|
1821
|
+
t.equal(reply.sent, true)
|
|
1822
|
+
})
|
|
1823
|
+
|
|
1824
|
+
test('redirect to an invalid URL should not crash the server', async t => {
|
|
1825
|
+
const fastify = require('../..')()
|
|
1826
|
+
fastify.route({
|
|
1827
|
+
method: 'GET',
|
|
1828
|
+
url: '/redirect',
|
|
1829
|
+
handler: (req, reply) => {
|
|
1830
|
+
reply.log.warn = function mockWarn (obj, message) {
|
|
1831
|
+
t.equal(message, 'Invalid character in header content ["location"]')
|
|
1832
|
+
}
|
|
1833
|
+
|
|
1834
|
+
switch (req.query.useCase) {
|
|
1835
|
+
case '1':
|
|
1836
|
+
reply.redirect('/?key=a’b')
|
|
1837
|
+
break
|
|
1838
|
+
|
|
1839
|
+
case '2':
|
|
1840
|
+
reply.redirect(encodeURI('/?key=a’b'))
|
|
1841
|
+
break
|
|
1842
|
+
|
|
1843
|
+
default:
|
|
1844
|
+
reply.redirect('/?key=ab')
|
|
1845
|
+
break
|
|
1846
|
+
}
|
|
1847
|
+
}
|
|
1848
|
+
})
|
|
1849
|
+
|
|
1850
|
+
await fastify.listen(0)
|
|
1851
|
+
|
|
1852
|
+
{
|
|
1853
|
+
const { response, body } = await doGet(`http://localhost:${fastify.server.address().port}/redirect?useCase=1`)
|
|
1854
|
+
t.equal(response.statusCode, 500)
|
|
1855
|
+
t.same(JSON.parse(body), {
|
|
1856
|
+
statusCode: 500,
|
|
1857
|
+
code: 'ERR_INVALID_CHAR',
|
|
1858
|
+
error: 'Internal Server Error',
|
|
1859
|
+
message: 'Invalid character in header content ["location"]'
|
|
1860
|
+
})
|
|
1861
|
+
}
|
|
1862
|
+
{
|
|
1863
|
+
const { response } = await doGet(`http://localhost:${fastify.server.address().port}/redirect?useCase=2`)
|
|
1864
|
+
t.equal(response.statusCode, 302)
|
|
1865
|
+
t.equal(response.headers.location, '/?key=a%E2%80%99b')
|
|
1866
|
+
}
|
|
1867
|
+
|
|
1868
|
+
{
|
|
1869
|
+
const { response } = await doGet(`http://localhost:${fastify.server.address().port}/redirect?useCase=3`)
|
|
1870
|
+
t.equal(response.statusCode, 302)
|
|
1871
|
+
t.equal(response.headers.location, '/?key=ab')
|
|
1872
|
+
}
|
|
1873
|
+
|
|
1874
|
+
await fastify.close()
|
|
1875
|
+
})
|
|
1876
|
+
|
|
1877
|
+
test('invalid response headers should not crash the server', async t => {
|
|
1878
|
+
const fastify = require('../..')()
|
|
1879
|
+
fastify.route({
|
|
1880
|
+
method: 'GET',
|
|
1881
|
+
url: '/bad-headers',
|
|
1882
|
+
handler: (req, reply) => {
|
|
1883
|
+
reply.log.warn = function mockWarn (obj, message) {
|
|
1884
|
+
t.equal(message, 'Invalid character in header content ["smile-encoded"]', 'only the first invalid header is logged')
|
|
1885
|
+
}
|
|
1886
|
+
|
|
1887
|
+
reply.header('foo', '$')
|
|
1888
|
+
reply.header('smile-encoded', '\uD83D\uDE00')
|
|
1889
|
+
reply.header('smile', '😄')
|
|
1890
|
+
reply.header('bar', 'ƒ∂å')
|
|
1891
|
+
|
|
1892
|
+
reply.send({})
|
|
1893
|
+
}
|
|
1894
|
+
})
|
|
1895
|
+
|
|
1896
|
+
await fastify.listen(0)
|
|
1897
|
+
|
|
1898
|
+
const { response, body } = await doGet(`http://localhost:${fastify.server.address().port}/bad-headers`)
|
|
1899
|
+
t.equal(response.statusCode, 500)
|
|
1900
|
+
t.same(JSON.parse(body), {
|
|
1901
|
+
statusCode: 500,
|
|
1902
|
+
code: 'ERR_INVALID_CHAR',
|
|
1903
|
+
error: 'Internal Server Error',
|
|
1904
|
+
message: 'Invalid character in header content ["smile-encoded"]'
|
|
1905
|
+
})
|
|
1906
|
+
|
|
1907
|
+
await fastify.close()
|
|
1908
|
+
})
|
|
1909
|
+
|
|
1910
|
+
test('invalid response headers when sending back an error', async t => {
|
|
1911
|
+
const fastify = require('../..')()
|
|
1912
|
+
fastify.route({
|
|
1913
|
+
method: 'GET',
|
|
1914
|
+
url: '/bad-headers',
|
|
1915
|
+
handler: (req, reply) => {
|
|
1916
|
+
reply.log.warn = function mockWarn (obj, message) {
|
|
1917
|
+
t.equal(message, 'Invalid character in header content ["smile"]', 'only the first invalid header is logged')
|
|
1918
|
+
}
|
|
1919
|
+
|
|
1920
|
+
reply.header('smile', '😄')
|
|
1921
|
+
reply.send(new Error('user land error'))
|
|
1922
|
+
}
|
|
1923
|
+
})
|
|
1924
|
+
|
|
1925
|
+
await fastify.listen(0)
|
|
1926
|
+
|
|
1927
|
+
const { response, body } = await doGet(`http://localhost:${fastify.server.address().port}/bad-headers`)
|
|
1928
|
+
t.equal(response.statusCode, 500)
|
|
1929
|
+
t.same(JSON.parse(body), {
|
|
1930
|
+
statusCode: 500,
|
|
1931
|
+
code: 'ERR_INVALID_CHAR',
|
|
1932
|
+
error: 'Internal Server Error',
|
|
1933
|
+
message: 'Invalid character in header content ["smile"]'
|
|
1934
|
+
})
|
|
1935
|
+
|
|
1936
|
+
await fastify.close()
|
|
1937
|
+
})
|
|
1938
|
+
|
|
1939
|
+
test('invalid response headers and custom error handler', async t => {
|
|
1940
|
+
const fastify = require('../..')()
|
|
1941
|
+
fastify.route({
|
|
1942
|
+
method: 'GET',
|
|
1943
|
+
url: '/bad-headers',
|
|
1944
|
+
handler: (req, reply) => {
|
|
1945
|
+
reply.log.warn = function mockWarn (obj, message) {
|
|
1946
|
+
t.equal(message, 'Invalid character in header content ["smile"]', 'only the first invalid header is logged')
|
|
1947
|
+
}
|
|
1948
|
+
|
|
1949
|
+
reply.header('smile', '😄')
|
|
1950
|
+
reply.send(new Error('user land error'))
|
|
1951
|
+
}
|
|
1952
|
+
})
|
|
1953
|
+
|
|
1954
|
+
fastify.setErrorHandler(function (error, request, reply) {
|
|
1955
|
+
t.equal(error.message, 'user land error', 'custom error handler receives the error')
|
|
1956
|
+
reply.status(500).send({ ops: true })
|
|
1957
|
+
})
|
|
1958
|
+
|
|
1959
|
+
await fastify.listen(0)
|
|
1960
|
+
|
|
1961
|
+
const { response, body } = await doGet(`http://localhost:${fastify.server.address().port}/bad-headers`)
|
|
1962
|
+
t.equal(response.statusCode, 500)
|
|
1963
|
+
t.same(JSON.parse(body), {
|
|
1964
|
+
statusCode: 500,
|
|
1965
|
+
code: 'ERR_INVALID_CHAR',
|
|
1966
|
+
error: 'Internal Server Error',
|
|
1967
|
+
message: 'Invalid character in header content ["smile"]'
|
|
1968
|
+
})
|
|
1969
|
+
|
|
1970
|
+
await fastify.close()
|
|
1971
|
+
})
|