fastify 4.0.2 → 4.2.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.
Files changed (48) hide show
  1. package/.eslintrc +1 -0
  2. package/README.md +3 -3
  3. package/docs/Guides/Database.md +5 -5
  4. package/docs/Guides/Delay-Accepting-Requests.md +1 -1
  5. package/docs/Guides/Ecosystem.md +13 -0
  6. package/docs/Guides/Migration-Guide-V4.md +58 -2
  7. package/docs/Guides/Serverless.md +23 -10
  8. package/docs/Reference/Hooks.md +52 -0
  9. package/docs/Reference/LTS.md +2 -2
  10. package/docs/Reference/Plugins.md +1 -1
  11. package/docs/Reference/Server.md +1 -1
  12. package/docs/Reference/Type-Providers.md +4 -3
  13. package/docs/Reference/TypeScript.md +38 -25
  14. package/docs/Reference/Validation-and-Serialization.md +11 -0
  15. package/docs/index.md +1 -1
  16. package/fastify.d.ts +3 -3
  17. package/fastify.js +19 -19
  18. package/integration/server.js +27 -0
  19. package/integration/test.sh +23 -0
  20. package/lib/context.js +5 -2
  21. package/lib/error-serializer.js +173 -8
  22. package/lib/handleRequest.js +1 -1
  23. package/lib/reply.js +2 -0
  24. package/lib/route.js +39 -29
  25. package/lib/server.js +9 -1
  26. package/lib/symbols.js +2 -1
  27. package/lib/validation.js +2 -0
  28. package/lib/wrapThenable.js +8 -3
  29. package/package.json +6 -6
  30. package/test/404s.test.js +2 -2
  31. package/test/build/error-serializer.test.js +6 -1
  32. package/test/hooks.test.js +21 -0
  33. package/test/internals/reply.test.js +12 -0
  34. package/test/listen.test.js +16 -2
  35. package/test/pretty-print.test.js +3 -3
  36. package/test/reply-error.test.js +1 -1
  37. package/test/schema-feature.test.js +2 -2
  38. package/test/stream.test.js +73 -0
  39. package/test/types/fastify.test-d.ts +12 -1
  40. package/test/types/instance.test-d.ts +1 -1
  41. package/test/types/register.test-d.ts +77 -2
  42. package/test/types/request.test-d.ts +8 -4
  43. package/test/types/type-provider.test-d.ts +11 -2
  44. package/test/validation-error-handling.test.js +32 -0
  45. package/types/register.d.ts +9 -7
  46. package/types/route.d.ts +10 -12
  47. package/types/schema.d.ts +1 -1
  48. package/types/type-provider.d.ts +12 -5
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/bash
2
+
3
+ set -e
4
+
5
+ NUMBER=$RANDOM
6
+ curl -i -X GET -H 'Content-Type: application/json' localhost:3000/ > GET
7
+ if [[ ! $(cat GET | head -1| cut -f2 -d" ") == "200" || ! $(cat GET | tail -1| cut -f4 -d"\"") == "home page" ]] ; then
8
+ exit 1
9
+ fi;
10
+ curl -i -X POST -H 'Content-Type: application/json' localhost:3000/post/$NUMBER --data {} > POST
11
+ if [[ ! $(cat POST | head -1| cut -f2 -d" ") == "201" || ! $(cat POST | tail -1| cut -f4 -d"\"") == $(echo $NUMBER) ]]; then
12
+ exit 1
13
+ fi;
14
+ curl -i -X PUT -H 'Content-Type: application/json' localhost:3000/put/$NUMBER --data {} > PUT
15
+ if [[ ! $(cat PUT | head -1| cut -f2 -d" ") == "200" || ! $(cat PUT | tail -1| cut -f4 -d"\"") == $(echo $NUMBER) ]]; then
16
+ exit 1
17
+ fi;
18
+ curl -i -X DELETE -H 'Content-Type: application/json' localhost:3000/delete/$NUMBER --data {} > DELETE
19
+ if [[ ! $(cat DELETE | head -1| cut -f2 -d" ") == "204" ]]; then
20
+ exit 1
21
+ fi;
22
+
23
+ rm -f GET POST PUT DELETE
package/lib/context.js CHANGED
@@ -9,7 +9,8 @@ const {
9
9
  kRequest,
10
10
  kBodyLimit,
11
11
  kLogLevel,
12
- kContentTypeParser
12
+ kContentTypeParser,
13
+ kRouteByFastify
13
14
  } = require('./symbols.js')
14
15
 
15
16
  // Objects that holds the context of every request
@@ -25,7 +26,8 @@ function Context ({
25
26
  attachValidation,
26
27
  replySerializer,
27
28
  schemaErrorFormatter,
28
- server
29
+ server,
30
+ isFastify
29
31
  }) {
30
32
  this.schema = schema
31
33
  this.handler = handler
@@ -50,6 +52,7 @@ function Context ({
50
52
  this.attachValidation = attachValidation
51
53
  this[kReplySerializerDefault] = replySerializer
52
54
  this.schemaErrorFormatter = schemaErrorFormatter || server[kSchemaErrorFormatter] || defaultSchemaErrorFormatter
55
+ this[kRouteByFastify] = isFastify
53
56
 
54
57
  this.server = server
55
58
  }
@@ -1,15 +1,180 @@
1
1
  // This file is autogenerated by build/build-error-serializer.js, do not edit
2
2
  /* istanbul ignore file */
3
3
 
4
- 'use strict'
4
+ 'use strict'
5
5
 
6
- const Serializer = require('fast-json-stringify/serializer')
7
- const buildAjv = require('fast-json-stringify/ajv')
6
+
7
+
8
+ class Serializer {
9
+ constructor (options = {}) {
10
+ switch (options.rounding) {
11
+ case 'floor':
12
+ this.parseInteger = Math.floor
13
+ break
14
+ case 'ceil':
15
+ this.parseInteger = Math.ceil
16
+ break
17
+ case 'round':
18
+ this.parseInteger = Math.round
19
+ break
20
+ default:
21
+ this.parseInteger = Math.trunc
22
+ break
23
+ }
24
+ }
25
+
26
+ asAny (i) {
27
+ return JSON.stringify(i)
28
+ }
29
+
30
+ asNull () {
31
+ return 'null'
32
+ }
33
+
34
+ asInteger (i) {
35
+ if (typeof i === 'bigint') {
36
+ return i.toString()
37
+ } else if (Number.isInteger(i)) {
38
+ return '' + i
39
+ } else {
40
+ /* eslint no-undef: "off" */
41
+ const integer = this.parseInteger(i)
42
+ if (Number.isNaN(integer) || !Number.isFinite(integer)) {
43
+ throw new Error(`The value "${i}" cannot be converted to an integer.`)
44
+ } else {
45
+ return '' + integer
46
+ }
47
+ }
48
+ }
49
+
50
+ asIntegerNullable (i) {
51
+ return i === null ? 'null' : this.asInteger(i)
52
+ }
53
+
54
+ asNumber (i) {
55
+ const num = Number(i)
56
+ if (Number.isNaN(num)) {
57
+ throw new Error(`The value "${i}" cannot be converted to a number.`)
58
+ } else if (!Number.isFinite(num)) {
59
+ return null
60
+ } else {
61
+ return '' + num
62
+ }
63
+ }
64
+
65
+ asNumberNullable (i) {
66
+ return i === null ? 'null' : this.asNumber(i)
67
+ }
68
+
69
+ asBoolean (bool) {
70
+ return bool && 'true' || 'false' // eslint-disable-line
71
+ }
72
+
73
+ asBooleanNullable (bool) {
74
+ return bool === null ? 'null' : this.asBoolean(bool)
75
+ }
76
+
77
+ asDatetime (date) {
78
+ const quotes = '"'
79
+ if (date instanceof Date) {
80
+ return quotes + date.toISOString() + quotes
81
+ }
82
+ return this.asString(date)
83
+ }
84
+
85
+ asDatetimeNullable (date) {
86
+ return date === null ? 'null' : this.asDatetime(date)
87
+ }
8
88
 
9
- const serializer = new Serializer({"mode":"standalone"})
10
- const ajv = buildAjv({})
89
+ asDate (date) {
90
+ const quotes = '"'
91
+ if (date instanceof Date) {
92
+ return quotes + new Date(date.getTime() - (date.getTimezoneOffset() * 60000)).toISOString().slice(0, 10) + quotes
93
+ }
94
+ return this.asString(date)
95
+ }
96
+
97
+ asDateNullable (date) {
98
+ return date === null ? 'null' : this.asDate(date)
99
+ }
100
+
101
+ asTime (date) {
102
+ const quotes = '"'
103
+ if (date instanceof Date) {
104
+ return quotes + new Date(date.getTime() - (date.getTimezoneOffset() * 60000)).toISOString().slice(11, 19) + quotes
105
+ }
106
+ return this.asString(date)
107
+ }
108
+
109
+ asTimeNullable (date) {
110
+ return date === null ? 'null' : this.asTime(date)
111
+ }
112
+
113
+ asString (str) {
114
+ const quotes = '"'
115
+ if (str instanceof Date) {
116
+ return quotes + str.toISOString() + quotes
117
+ } else if (str === null) {
118
+ return quotes + quotes
119
+ } else if (str instanceof RegExp) {
120
+ str = str.source
121
+ } else if (typeof str !== 'string') {
122
+ str = str.toString()
123
+ }
124
+
125
+ if (str.length < 42) {
126
+ return this.asStringSmall(str)
127
+ } else {
128
+ return JSON.stringify(str)
129
+ }
130
+ }
11
131
 
132
+ asStringNullable (str) {
133
+ return str === null ? 'null' : this.asString(str)
134
+ }
135
+
136
+ // magically escape strings for json
137
+ // relying on their charCodeAt
138
+ // everything below 32 needs JSON.stringify()
139
+ // every string that contain surrogate needs JSON.stringify()
140
+ // 34 and 92 happens all the time, so we
141
+ // have a fast case for them
142
+ asStringSmall (str) {
143
+ const l = str.length
144
+ let result = ''
145
+ let last = 0
146
+ let found = false
147
+ let surrogateFound = false
148
+ let point = 255
149
+ // eslint-disable-next-line
150
+ for (var i = 0; i < l && point >= 32; i++) {
151
+ point = str.charCodeAt(i)
152
+ if (point >= 0xD800 && point <= 0xDFFF) {
153
+ // The current character is a surrogate.
154
+ surrogateFound = true
155
+ }
156
+ if (point === 34 || point === 92) {
157
+ result += str.slice(last, i) + '\\'
158
+ last = i
159
+ found = true
160
+ }
161
+ }
162
+
163
+ if (!found) {
164
+ result = str
165
+ } else {
166
+ result += str.slice(last)
167
+ }
168
+ return ((point < 32) || (surrogateFound === true)) ? JSON.stringify(str) : '"' + result + '"'
169
+ }
170
+ }
171
+
172
+
12
173
 
174
+ const serializer = new Serializer({"mode":"standalone"})
175
+
176
+
177
+
13
178
  function main (input) {
14
179
  let json = ''
15
180
  json += anonymous0(input)
@@ -17,7 +182,7 @@ const ajv = buildAjv({})
17
182
  }
18
183
 
19
184
  function anonymous0 (input) {
20
- // main
185
+ // #
21
186
 
22
187
  var obj = (input && typeof input.toJSON === 'function')
23
188
  ? input.toJSON()
@@ -81,5 +246,5 @@ const ajv = buildAjv({})
81
246
 
82
247
 
83
248
 
84
- module.exports = main
85
-
249
+ module.exports = main
250
+
@@ -90,7 +90,7 @@ function preValidationCallback (err, request, reply) {
90
90
  const result = validateSchema(reply.context, request)
91
91
  if (result) {
92
92
  if (reply.context.attachValidation === false) {
93
- reply.code(400).send(result)
93
+ reply.send(result)
94
94
  return
95
95
  }
96
96
 
package/lib/reply.js CHANGED
@@ -331,10 +331,12 @@ Reply.prototype.redirect = function (code, url) {
331
331
  }
332
332
 
333
333
  this.header('location', url).code(code).send()
334
+ return this
334
335
  }
335
336
 
336
337
  Reply.prototype.callNotFound = function () {
337
338
  notFound(this)
339
+ return this
338
340
  }
339
341
 
340
342
  Reply.prototype.getResponseTime = function () {
package/lib/route.js CHANGED
@@ -8,7 +8,7 @@ const supportedMethods = ['DELETE', 'GET', 'HEAD', 'PATCH', 'POST', 'PUT', 'OPTI
8
8
  const { normalizeSchema } = require('./schemas')
9
9
  const { parseHeadOnSendHandlers } = require('./headRoute')
10
10
  const warning = require('./warnings')
11
- const { kRequestAcceptVersion } = require('./symbols')
11
+ const { kRequestAcceptVersion, kRouteByFastify } = require('./symbols')
12
12
 
13
13
  const {
14
14
  compileSchemasForValidation,
@@ -113,7 +113,7 @@ function buildRouting (options) {
113
113
  }
114
114
 
115
115
  // Convert shorthand to extended route declaration
116
- function prepareRoute (method, url, options, handler) {
116
+ function prepareRoute ({ method, url, options, handler, isFastify }) {
117
117
  if (typeof url !== 'string') {
118
118
  throw new FST_ERR_INVALID_URL(typeof url)
119
119
  }
@@ -140,11 +140,11 @@ function buildRouting (options) {
140
140
  handler: handler || (options && options.handler)
141
141
  })
142
142
 
143
- return route.call(this, options)
143
+ return route.call(this, { options, isFastify })
144
144
  }
145
145
 
146
146
  // Route management
147
- function route (options) {
147
+ function route ({ options, isFastify }) {
148
148
  // Since we are mutating/assigning only top level props, it is fine to have a shallow copy using the spread operator
149
149
  const opts = { ...options }
150
150
 
@@ -176,30 +176,30 @@ function buildRouting (options) {
176
176
  if (path === '/' && prefix.length > 0 && opts.method !== 'HEAD') {
177
177
  switch (opts.prefixTrailingSlash) {
178
178
  case 'slash':
179
- addNewRoute.call(this, path)
179
+ addNewRoute.call(this, { path, isFastify })
180
180
  break
181
181
  case 'no-slash':
182
- addNewRoute.call(this, '')
182
+ addNewRoute.call(this, { path: '', isFastify })
183
183
  break
184
184
  case 'both':
185
185
  default:
186
- addNewRoute.call(this, '')
186
+ addNewRoute.call(this, { path: '', isFastify })
187
187
  // If ignoreTrailingSlash is set to true we need to add only the '' route to prevent adding an incomplete one.
188
188
  if (ignoreTrailingSlash !== true && (ignoreDuplicateSlashes !== true || !prefix.endsWith('/'))) {
189
- addNewRoute.call(this, path, true)
189
+ addNewRoute.call(this, { path, prefixing: true, isFastify })
190
190
  }
191
191
  }
192
192
  } else if (path[0] === '/' && prefix.endsWith('/')) {
193
193
  // Ensure that '/prefix/' + '/route' gets registered as '/prefix/route'
194
- addNewRoute.call(this, path.slice(1))
194
+ addNewRoute.call(this, { path: path.slice(1), isFastify })
195
195
  } else {
196
- addNewRoute.call(this, path)
196
+ addNewRoute.call(this, { path, isFastify })
197
197
  }
198
198
 
199
199
  // chainable api
200
200
  return this
201
201
 
202
- function addNewRoute (path, prefixing = false) {
202
+ function addNewRoute ({ path, prefixing = false, isFastify = false }) {
203
203
  const url = prefix + path
204
204
 
205
205
  opts.url = url
@@ -241,7 +241,8 @@ function buildRouting (options) {
241
241
  attachValidation: opts.attachValidation,
242
242
  schemaErrorFormatter: opts.schemaErrorFormatter,
243
243
  replySerializer: this[kReplySerializerDefault],
244
- server: this
244
+ server: this,
245
+ isFastify
245
246
  })
246
247
 
247
248
  if (opts.version) {
@@ -249,13 +250,20 @@ function buildRouting (options) {
249
250
  constraints.version = opts.version
250
251
  }
251
252
 
252
- const headRouteExists = opts.method === 'HEAD' && router.find(opts.method, opts.url, constraints) != null
253
+ const headHandler = router.find('HEAD', opts.url, constraints)
254
+ const hasHEADHandler = headHandler != null
253
255
 
254
- // Check if the current route is not for a sibling HEAD one
255
- if (!headRouteExists) {
256
- try {
257
- router.on(opts.method, opts.url, { constraints }, routeHandler, context)
258
- } catch (error) {
256
+ // remove the head route created by fastify
257
+ if (hasHEADHandler && !context[kRouteByFastify] && headHandler.store[kRouteByFastify]) {
258
+ router.off(opts.method, opts.url, { constraints })
259
+ }
260
+
261
+ try {
262
+ router.on(opts.method, opts.url, { constraints }, routeHandler, context)
263
+ } catch (error) {
264
+ // any route insertion error created by fastify can be safely ignore
265
+ // because it only duplicate route for head
266
+ if (!context[kRouteByFastify]) {
259
267
  const isDuplicatedRoute = error.message.includes(`Method '${opts.method}' already declared for route '${opts.url}'`)
260
268
  if (isDuplicatedRoute) {
261
269
  throw new FST_ERR_DUPLICATED_ROUTE(opts.method, opts.url)
@@ -320,19 +328,21 @@ function buildRouting (options) {
320
328
  }
321
329
  })
322
330
 
323
- const { exposeHeadRoute } = opts
324
- const hasRouteExposeHeadRouteFlag = exposeHeadRoute != null
325
- const shouldExposeHead = hasRouteExposeHeadRouteFlag ? exposeHeadRoute : globalExposeHeadRoutes
326
-
327
- if (shouldExposeHead && options.method === 'GET' && !headRouteExists) {
328
- const onSendHandlers = parseHeadOnSendHandlers(opts.onSend)
329
- prepareRoute.call(this, 'HEAD', path, { ...opts, onSend: onSendHandlers })
330
- } else if (headRouteExists && exposeHeadRoute) {
331
- warning.emit('FSTDEP007')
332
- }
333
-
334
331
  done(notHandledErr)
335
332
  })
333
+
334
+ // register head route in sync
335
+ // we must place it after the `this.after`
336
+ const { exposeHeadRoute } = opts
337
+ const hasRouteExposeHeadRouteFlag = exposeHeadRoute != null
338
+ const shouldExposeHead = hasRouteExposeHeadRouteFlag ? exposeHeadRoute : globalExposeHeadRoutes
339
+
340
+ if (shouldExposeHead && options.method === 'GET' && !hasHEADHandler) {
341
+ const onSendHandlers = parseHeadOnSendHandlers(opts.onSend)
342
+ prepareRoute.call(this, { method: 'HEAD', url: path, options: { ...opts, onSend: onSendHandlers }, isFastify: true })
343
+ } else if (hasHEADHandler && exposeHeadRoute) {
344
+ warning.emit('FSTDEP007')
345
+ }
336
346
  }
337
347
  }
338
348
 
package/lib/server.js CHANGED
@@ -41,7 +41,15 @@ function createServer (options, httpHandler) {
41
41
  listenOptions.cb = cb
42
42
  }
43
43
 
44
- const { host = 'localhost' } = listenOptions
44
+ // If we have a path specified, don't default host to 'localhost' so we don't end up listening
45
+ // on both path and host
46
+ // See https://github.com/fastify/fastify/issues/4007
47
+ let host
48
+ if (listenOptions.path == null) {
49
+ host = listenOptions.host ?? 'localhost'
50
+ } else {
51
+ host = listenOptions.host
52
+ }
45
53
  if (Object.prototype.hasOwnProperty.call(listenOptions, 'host') === false) {
46
54
  listenOptions.host = host
47
55
  }
package/lib/symbols.js CHANGED
@@ -47,7 +47,8 @@ const keys = {
47
47
  kTestInternals: Symbol('fastify.testInternals'),
48
48
  kErrorHandler: Symbol('fastify.errorHandler'),
49
49
  kHasBeenDecorated: Symbol('fastify.hasBeenDecorated'),
50
- kKeepAliveConnections: Symbol('fastify.keepAliveConnections')
50
+ kKeepAliveConnections: Symbol('fastify.keepAliveConnections'),
51
+ kRouteByFastify: Symbol('fastify.routeByFastify')
51
52
  }
52
53
 
53
54
  module.exports = keys
package/lib/validation.js CHANGED
@@ -106,11 +106,13 @@ function validate (context, request) {
106
106
 
107
107
  function wrapValidationError (result, dataVar, schemaErrorFormatter) {
108
108
  if (result instanceof Error) {
109
+ result.statusCode = result.statusCode || 400
109
110
  result.validationContext = result.validationContext || dataVar
110
111
  return result
111
112
  }
112
113
 
113
114
  const error = schemaErrorFormatter(result, dataVar)
115
+ error.statusCode = error.statusCode || 400
114
116
  error.validation = result
115
117
  error.validationContext = dataVar
116
118
  return error
@@ -11,9 +11,14 @@ function wrapThenable (thenable, reply) {
11
11
  return
12
12
  }
13
13
 
14
- // this is for async functions that
15
- // are using reply.send directly
16
- if (payload !== undefined || reply.sent === false) {
14
+ // this is for async functions that are using reply.send directly
15
+ //
16
+ // since wrap-thenable will be called when using reply.send directly
17
+ // without actual return. the response can be sent already or
18
+ // the request may be terminated during the reply. in this situation,
19
+ // it require an extra checking of request.aborted to see whether
20
+ // the request is killed by client.
21
+ if (payload !== undefined || (reply.sent === false && reply.raw.headersSent === false && reply.request.raw.aborted === false)) {
17
22
  // we use a try-catch internally to avoid adding a catch to another
18
23
  // promise, increase promise perf by 10%
19
24
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fastify",
3
- "version": "4.0.2",
3
+ "version": "4.2.0",
4
4
  "description": "Fast and low overhead web framework, for Node.js",
5
5
  "main": "fastify.js",
6
6
  "type": "commonjs",
@@ -128,7 +128,7 @@
128
128
  "@fastify/pre-commit": "^2.0.2",
129
129
  "@sinclair/typebox": "^0.23.5",
130
130
  "@sinonjs/fake-timers": "^9.1.2",
131
- "@types/node": "^17.0.38",
131
+ "@types/node": "^18.0.0",
132
132
  "@typescript-eslint/eslint-plugin": "^5.27.0",
133
133
  "@typescript-eslint/parser": "^5.27.0",
134
134
  "ajv": "^8.11.0",
@@ -146,6 +146,7 @@
146
146
  "eslint-plugin-n": "^15.2.0",
147
147
  "eslint-plugin-promise": "^6.0.0",
148
148
  "fast-json-body": "^1.1.0",
149
+ "fast-json-stringify": "^5.0.0",
149
150
  "fastify-plugin": "^3.0.1",
150
151
  "fluent-json-schema": "^3.1.0",
151
152
  "form-data": "^4.0.0",
@@ -169,7 +170,7 @@
169
170
  "split2": "^4.1.0",
170
171
  "standard": "^17.0.0-2",
171
172
  "tap": "^16.2.0",
172
- "tsd": "^0.20.0",
173
+ "tsd": "^0.21.0",
173
174
  "typescript": "^4.7.2",
174
175
  "undici": "^5.4.0",
175
176
  "x-xss-protection": "^2.0.0",
@@ -178,11 +179,10 @@
178
179
  "dependencies": {
179
180
  "@fastify/ajv-compiler": "^3.1.0",
180
181
  "@fastify/error": "^3.0.0",
181
- "@fastify/fast-json-stringify-compiler": "^3.0.0",
182
+ "@fastify/fast-json-stringify-compiler": "^4.0.0",
182
183
  "abstract-logging": "^2.0.1",
183
184
  "avvio": "^8.1.3",
184
- "fast-json-stringify": "^4.1.0",
185
- "find-my-way": "^6.3.0",
185
+ "find-my-way": "^7.0.0",
186
186
  "light-my-request": "^5.0.0",
187
187
  "pino": "^8.0.0",
188
188
  "process-warning": "^2.0.0",
package/test/404s.test.js CHANGED
@@ -1320,7 +1320,7 @@ test('preHandler option for setNotFoundHandler', t => {
1320
1320
 
1321
1321
  // https://github.com/fastify/fastify/issues/2229
1322
1322
  t.test('preHandler hook in setNotFoundHandler should be called when callNotFound', { timeout: 40000 }, t => {
1323
- t.plan(2)
1323
+ t.plan(3)
1324
1324
  const fastify = Fastify()
1325
1325
 
1326
1326
  fastify.setNotFoundHandler({
@@ -1333,7 +1333,7 @@ test('preHandler option for setNotFoundHandler', t => {
1333
1333
  })
1334
1334
 
1335
1335
  fastify.post('/', function (req, reply) {
1336
- reply.callNotFound()
1336
+ t.equal(reply.callNotFound(), reply)
1337
1337
  })
1338
1338
 
1339
1339
  fastify.inject({
@@ -7,6 +7,10 @@ const path = require('path')
7
7
 
8
8
  const { code } = require('../../build/build-error-serializer')
9
9
 
10
+ function unifyLineBreak (str) {
11
+ return str.toString().replace(/\r\n/g, '\n')
12
+ }
13
+
10
14
  test('check generated code syntax', async (t) => {
11
15
  t.plan(1)
12
16
 
@@ -24,5 +28,6 @@ test('ensure the current error serializer is latest', async (t) => {
24
28
 
25
29
  const current = await fs.promises.readFile(path.resolve('lib/error-serializer.js'))
26
30
 
27
- t.equal(current.toString(), code)
31
+ // line break should not be a problem depends on system
32
+ t.equal(unifyLineBreak(current), unifyLineBreak(code))
28
33
  })
@@ -799,6 +799,27 @@ test('onRoute hook with many prefix', t => {
799
799
  fastify.ready(err => { t.error(err) })
800
800
  })
801
801
 
802
+ test('onRoute hook should not be called when it registered after route', t => {
803
+ t.plan(3)
804
+ const fastify = Fastify()
805
+
806
+ fastify.addHook('onRoute', () => {
807
+ t.pass()
808
+ })
809
+
810
+ fastify.get('/', function (req, reply) {
811
+ reply.send()
812
+ })
813
+
814
+ fastify.addHook('onRoute', () => {
815
+ t.fail('should not be called')
816
+ })
817
+
818
+ fastify.ready(err => {
819
+ t.error(err)
820
+ })
821
+ })
822
+
802
823
  test('onResponse hook should log request error', t => {
803
824
  t.plan(4)
804
825
 
@@ -233,6 +233,10 @@ test('within an instance', t => {
233
233
  reply.redirect('/')
234
234
  })
235
235
 
236
+ fastify.get('/redirect-async', async function (req, reply) {
237
+ return reply.redirect('/')
238
+ })
239
+
236
240
  fastify.get('/redirect-code', function (req, reply) {
237
241
  reply.redirect(301, '/')
238
242
  })
@@ -412,6 +416,14 @@ test('within an instance', t => {
412
416
  })
413
417
  })
414
418
 
419
+ test('redirect with async function to `/` - 10', t => {
420
+ t.plan(1)
421
+
422
+ http.get('http://localhost:' + fastify.server.address().port + '/redirect-async', function (response) {
423
+ t.equal(response.statusCode, 302)
424
+ })
425
+ })
426
+
415
427
  t.end()
416
428
  })
417
429
  })
@@ -196,14 +196,28 @@ if (os.platform() !== 'win32') {
196
196
  const fastify = Fastify()
197
197
  t.teardown(fastify.close.bind(fastify))
198
198
 
199
- const sockFile = path.join(os.tmpdir(), `${(Math.random().toString(16) + '0000000').substr(2, 8)}-server.sock`)
199
+ const sockFile = path.join(os.tmpdir(), `${(Math.random().toString(16) + '0000000').slice(2, 10)}-server.sock`)
200
200
  try {
201
201
  fs.unlinkSync(sockFile)
202
202
  } catch (e) { }
203
203
 
204
204
  fastify.listen({ path: sockFile }, (err, address) => {
205
205
  t.error(err)
206
- t.equal(sockFile, fastify.server.address())
206
+ t.strictSame(fastify.addresses(), [sockFile])
207
+ t.equal(address, sockFile)
208
+ })
209
+ })
210
+ } else {
211
+ test('listen on socket', t => {
212
+ t.plan(3)
213
+ const fastify = Fastify()
214
+ t.teardown(fastify.close.bind(fastify))
215
+
216
+ const sockFile = `\\\\.\\pipe\\${(Math.random().toString(16) + '0000000').slice(2, 10)}-server-sock`
217
+
218
+ fastify.listen({ path: sockFile }, (err, address) => {
219
+ t.error(err)
220
+ t.strictSame(fastify.addresses(), [sockFile])
207
221
  t.equal(address, sockFile)
208
222
  })
209
223
  })
@@ -144,7 +144,7 @@ test('pretty print - commonPrefix', t => {
144
144
  `
145
145
  const flatExpected = `└── / (-)
146
146
  ├── helicopter (GET, HEAD)
147
- └── hello (GET, PUT, HEAD)
147
+ └── hello (GET, HEAD, PUT)
148
148
  `
149
149
  t.equal(typeof radixTree, 'string')
150
150
  t.equal(typeof flatTree, 'string')
@@ -200,7 +200,7 @@ test('pretty print - includeMeta, includeHooks', t => {
200
200
  │ • (onTimeout) ["onTimeout()"]
201
201
  │ • (onRequest) ["anonymous()"]
202
202
  │ • (errorHandler) "defaultErrorHandler()"
203
- └── hello (GET, PUT, HEAD)
203
+ └── hello (GET, HEAD, PUT)
204
204
  • (onTimeout) ["onTimeout()"]
205
205
  • (onRequest) ["anonymous()"]
206
206
  • (errorHandler) "defaultErrorHandler()"
@@ -210,7 +210,7 @@ test('pretty print - includeMeta, includeHooks', t => {
210
210
  ├── helicopter (GET, HEAD)
211
211
  │ • (onTimeout) ["onTimeout()"]
212
212
  │ • (onRequest) ["anonymous()"]
213
- └── hello (GET, PUT, HEAD)
213
+ └── hello (GET, HEAD, PUT)
214
214
  • (onTimeout) ["onTimeout()"]
215
215
  • (onRequest) ["anonymous()"]
216
216
  `