node-fastify 5.8.3

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 (354) hide show
  1. package/.borp.yaml +3 -0
  2. package/.markdownlint-cli2.yaml +22 -0
  3. package/.prettierignore +1 -0
  4. package/GOVERNANCE.md +4 -0
  5. package/LICENSE +21 -0
  6. package/PROJECT_CHARTER.md +126 -0
  7. package/README.md +423 -0
  8. package/SECURITY.md +220 -0
  9. package/SPONSORS.md +24 -0
  10. package/build/build-error-serializer.js +35 -0
  11. package/build/build-validation.js +169 -0
  12. package/build/sync-version.js +11 -0
  13. package/docs/Guides/Benchmarking.md +60 -0
  14. package/docs/Guides/Database.md +321 -0
  15. package/docs/Guides/Delay-Accepting-Requests.md +608 -0
  16. package/docs/Guides/Detecting-When-Clients-Abort.md +172 -0
  17. package/docs/Guides/Ecosystem.md +726 -0
  18. package/docs/Guides/Fluent-Schema.md +127 -0
  19. package/docs/Guides/Getting-Started.md +620 -0
  20. package/docs/Guides/Index.md +43 -0
  21. package/docs/Guides/Migration-Guide-V3.md +287 -0
  22. package/docs/Guides/Migration-Guide-V4.md +267 -0
  23. package/docs/Guides/Migration-Guide-V5.md +727 -0
  24. package/docs/Guides/Plugins-Guide.md +520 -0
  25. package/docs/Guides/Prototype-Poisoning.md +383 -0
  26. package/docs/Guides/Recommendations.md +378 -0
  27. package/docs/Guides/Serverless.md +604 -0
  28. package/docs/Guides/Style-Guide.md +246 -0
  29. package/docs/Guides/Testing.md +481 -0
  30. package/docs/Guides/Write-Plugin.md +103 -0
  31. package/docs/Guides/Write-Type-Provider.md +34 -0
  32. package/docs/Reference/ContentTypeParser.md +271 -0
  33. package/docs/Reference/Decorators.md +436 -0
  34. package/docs/Reference/Encapsulation.md +194 -0
  35. package/docs/Reference/Errors.md +377 -0
  36. package/docs/Reference/HTTP2.md +94 -0
  37. package/docs/Reference/Hooks.md +958 -0
  38. package/docs/Reference/Index.md +73 -0
  39. package/docs/Reference/LTS.md +86 -0
  40. package/docs/Reference/Lifecycle.md +99 -0
  41. package/docs/Reference/Logging.md +268 -0
  42. package/docs/Reference/Middleware.md +79 -0
  43. package/docs/Reference/Plugins.md +245 -0
  44. package/docs/Reference/Principles.md +73 -0
  45. package/docs/Reference/Reply.md +1001 -0
  46. package/docs/Reference/Request.md +295 -0
  47. package/docs/Reference/Routes.md +802 -0
  48. package/docs/Reference/Server.md +2389 -0
  49. package/docs/Reference/Type-Providers.md +256 -0
  50. package/docs/Reference/TypeScript.md +1729 -0
  51. package/docs/Reference/Validation-and-Serialization.md +1130 -0
  52. package/docs/Reference/Warnings.md +58 -0
  53. package/docs/index.md +24 -0
  54. package/docs/resources/encapsulation_context.drawio +1 -0
  55. package/docs/resources/encapsulation_context.svg +3 -0
  56. package/eslint.config.js +35 -0
  57. package/examples/asyncawait.js +38 -0
  58. package/examples/benchmark/body.json +3 -0
  59. package/examples/benchmark/hooks-benchmark-async-await.js +44 -0
  60. package/examples/benchmark/hooks-benchmark.js +52 -0
  61. package/examples/benchmark/parser.js +47 -0
  62. package/examples/benchmark/simple.js +30 -0
  63. package/examples/benchmark/webstream.js +27 -0
  64. package/examples/hooks.js +91 -0
  65. package/examples/http2.js +39 -0
  66. package/examples/https.js +38 -0
  67. package/examples/parser.js +53 -0
  68. package/examples/plugin.js +12 -0
  69. package/examples/route-prefix.js +38 -0
  70. package/examples/shared-schema.js +38 -0
  71. package/examples/simple-stream.js +20 -0
  72. package/examples/simple.js +32 -0
  73. package/examples/simple.mjs +27 -0
  74. package/examples/typescript-server.ts +79 -0
  75. package/examples/use-plugin.js +29 -0
  76. package/fastify.d.ts +253 -0
  77. package/fastify.js +985 -0
  78. package/integration/server.js +29 -0
  79. package/integration/test.sh +23 -0
  80. package/lib/config-validator.js +1266 -0
  81. package/lib/content-type-parser.js +413 -0
  82. package/lib/content-type.js +160 -0
  83. package/lib/context.js +98 -0
  84. package/lib/decorate.js +152 -0
  85. package/lib/error-handler.js +173 -0
  86. package/lib/error-serializer.js +134 -0
  87. package/lib/error-status.js +14 -0
  88. package/lib/errors.js +516 -0
  89. package/lib/four-oh-four.js +190 -0
  90. package/lib/handle-request.js +195 -0
  91. package/lib/head-route.js +45 -0
  92. package/lib/hooks.js +429 -0
  93. package/lib/initial-config-validation.js +37 -0
  94. package/lib/logger-factory.js +136 -0
  95. package/lib/logger-pino.js +68 -0
  96. package/lib/noop-set.js +10 -0
  97. package/lib/plugin-override.js +90 -0
  98. package/lib/plugin-utils.js +169 -0
  99. package/lib/promise.js +23 -0
  100. package/lib/reply.js +1030 -0
  101. package/lib/req-id-gen-factory.js +52 -0
  102. package/lib/request.js +391 -0
  103. package/lib/route.js +686 -0
  104. package/lib/schema-controller.js +164 -0
  105. package/lib/schemas.js +207 -0
  106. package/lib/server.js +441 -0
  107. package/lib/symbols.js +71 -0
  108. package/lib/validation.js +280 -0
  109. package/lib/warnings.js +57 -0
  110. package/lib/wrap-thenable.js +84 -0
  111. package/package.json +225 -0
  112. package/scripts/validate-ecosystem-links.js +179 -0
  113. package/test/404s.test.js +2035 -0
  114. package/test/500s.test.js +422 -0
  115. package/test/allow-unsafe-regex.test.js +92 -0
  116. package/test/als.test.js +65 -0
  117. package/test/async-await.test.js +705 -0
  118. package/test/async-dispose.test.js +20 -0
  119. package/test/async_hooks.test.js +52 -0
  120. package/test/body-limit.test.js +224 -0
  121. package/test/buffer.test.js +74 -0
  122. package/test/build/error-serializer.test.js +36 -0
  123. package/test/build/version.test.js +14 -0
  124. package/test/build-certificate.js +109 -0
  125. package/test/bundler/README.md +29 -0
  126. package/test/bundler/esbuild/bundler-test.js +32 -0
  127. package/test/bundler/esbuild/package.json +10 -0
  128. package/test/bundler/esbuild/src/fail-plugin-version.js +14 -0
  129. package/test/bundler/esbuild/src/index.js +9 -0
  130. package/test/bundler/webpack/bundler-test.js +32 -0
  131. package/test/bundler/webpack/package.json +11 -0
  132. package/test/bundler/webpack/src/fail-plugin-version.js +14 -0
  133. package/test/bundler/webpack/src/index.js +9 -0
  134. package/test/bundler/webpack/webpack.config.js +15 -0
  135. package/test/case-insensitive.test.js +102 -0
  136. package/test/chainable.test.js +40 -0
  137. package/test/child-logger-factory.test.js +128 -0
  138. package/test/client-timeout.test.js +38 -0
  139. package/test/close-pipelining.test.js +78 -0
  140. package/test/close.test.js +706 -0
  141. package/test/conditional-pino.test.js +47 -0
  142. package/test/connection-timeout.test.js +42 -0
  143. package/test/constrained-routes.test.js +1138 -0
  144. package/test/content-length.test.js +174 -0
  145. package/test/content-parser.test.js +739 -0
  146. package/test/content-type.test.js +181 -0
  147. package/test/context-config.test.js +164 -0
  148. package/test/custom-http-server.test.js +118 -0
  149. package/test/custom-parser-async.test.js +59 -0
  150. package/test/custom-parser.0.test.js +701 -0
  151. package/test/custom-parser.1.test.js +266 -0
  152. package/test/custom-parser.2.test.js +91 -0
  153. package/test/custom-parser.3.test.js +208 -0
  154. package/test/custom-parser.4.test.js +218 -0
  155. package/test/custom-parser.5.test.js +130 -0
  156. package/test/custom-querystring-parser.test.js +129 -0
  157. package/test/decorator.test.js +1330 -0
  158. package/test/delete.test.js +344 -0
  159. package/test/diagnostics-channel/404.test.js +49 -0
  160. package/test/diagnostics-channel/async-delay-request.test.js +65 -0
  161. package/test/diagnostics-channel/async-request.test.js +64 -0
  162. package/test/diagnostics-channel/error-before-handler.test.js +35 -0
  163. package/test/diagnostics-channel/error-request.test.js +53 -0
  164. package/test/diagnostics-channel/error-status.test.js +123 -0
  165. package/test/diagnostics-channel/init.test.js +50 -0
  166. package/test/diagnostics-channel/sync-delay-request.test.js +49 -0
  167. package/test/diagnostics-channel/sync-request-reply.test.js +51 -0
  168. package/test/diagnostics-channel/sync-request.test.js +54 -0
  169. package/test/encapsulated-child-logger-factory.test.js +69 -0
  170. package/test/encapsulated-error-handler.test.js +237 -0
  171. package/test/esm/errorCodes.test.mjs +10 -0
  172. package/test/esm/esm.test.mjs +13 -0
  173. package/test/esm/index.test.js +8 -0
  174. package/test/esm/named-exports.mjs +14 -0
  175. package/test/esm/other.mjs +8 -0
  176. package/test/esm/plugin.mjs +8 -0
  177. package/test/fastify-instance.test.js +300 -0
  178. package/test/find-route.test.js +152 -0
  179. package/test/fluent-schema.test.js +209 -0
  180. package/test/genReqId.test.js +426 -0
  181. package/test/handler-context.test.js +45 -0
  182. package/test/handler-timeout.test.js +367 -0
  183. package/test/has-route.test.js +88 -0
  184. package/test/header-overflow.test.js +55 -0
  185. package/test/helper.js +496 -0
  186. package/test/hooks-async.test.js +1099 -0
  187. package/test/hooks.on-listen.test.js +1162 -0
  188. package/test/hooks.on-ready.test.js +421 -0
  189. package/test/hooks.test.js +3578 -0
  190. package/test/http-methods/copy.test.js +35 -0
  191. package/test/http-methods/custom-http-methods.test.js +114 -0
  192. package/test/http-methods/get.test.js +412 -0
  193. package/test/http-methods/head.test.js +263 -0
  194. package/test/http-methods/lock.test.js +108 -0
  195. package/test/http-methods/mkcalendar.test.js +143 -0
  196. package/test/http-methods/mkcol.test.js +35 -0
  197. package/test/http-methods/move.test.js +42 -0
  198. package/test/http-methods/propfind.test.js +136 -0
  199. package/test/http-methods/proppatch.test.js +105 -0
  200. package/test/http-methods/report.test.js +142 -0
  201. package/test/http-methods/search.test.js +233 -0
  202. package/test/http-methods/trace.test.js +21 -0
  203. package/test/http-methods/unlock.test.js +38 -0
  204. package/test/http2/closing.test.js +270 -0
  205. package/test/http2/constraint.test.js +109 -0
  206. package/test/http2/head.test.js +34 -0
  207. package/test/http2/plain.test.js +68 -0
  208. package/test/http2/secure-with-fallback.test.js +113 -0
  209. package/test/http2/secure.test.js +67 -0
  210. package/test/http2/unknown-http-method.test.js +34 -0
  211. package/test/https/custom-https-server.test.js +58 -0
  212. package/test/https/https.test.js +136 -0
  213. package/test/imports.test.js +17 -0
  214. package/test/inject.test.js +502 -0
  215. package/test/input-validation.js +335 -0
  216. package/test/internals/all.test.js +38 -0
  217. package/test/internals/content-type-parser.test.js +111 -0
  218. package/test/internals/context.test.js +31 -0
  219. package/test/internals/decorator.test.js +156 -0
  220. package/test/internals/errors.test.js +982 -0
  221. package/test/internals/handle-request.test.js +270 -0
  222. package/test/internals/hook-runner.test.js +449 -0
  223. package/test/internals/hooks.test.js +96 -0
  224. package/test/internals/initial-config.test.js +383 -0
  225. package/test/internals/logger.test.js +163 -0
  226. package/test/internals/plugin.test.js +170 -0
  227. package/test/internals/promise.test.js +63 -0
  228. package/test/internals/reply-serialize.test.js +714 -0
  229. package/test/internals/reply.test.js +1920 -0
  230. package/test/internals/req-id-gen-factory.test.js +133 -0
  231. package/test/internals/request-validate.test.js +1402 -0
  232. package/test/internals/request.test.js +506 -0
  233. package/test/internals/schema-controller-perf.test.js +40 -0
  234. package/test/internals/server.test.js +91 -0
  235. package/test/internals/validation.test.js +352 -0
  236. package/test/issue-4959.test.js +118 -0
  237. package/test/keep-alive-timeout.test.js +42 -0
  238. package/test/listen.1.test.js +154 -0
  239. package/test/listen.2.test.js +113 -0
  240. package/test/listen.3.test.js +83 -0
  241. package/test/listen.4.test.js +168 -0
  242. package/test/listen.5.test.js +122 -0
  243. package/test/logger/instantiation.test.js +341 -0
  244. package/test/logger/logger-test-utils.js +47 -0
  245. package/test/logger/logging.test.js +460 -0
  246. package/test/logger/options.test.js +579 -0
  247. package/test/logger/request.test.js +292 -0
  248. package/test/logger/response.test.js +183 -0
  249. package/test/logger/tap-parallel-not-ok +0 -0
  250. package/test/max-requests-per-socket.test.js +113 -0
  251. package/test/middleware.test.js +37 -0
  252. package/test/noop-set.test.js +19 -0
  253. package/test/nullable-validation.test.js +187 -0
  254. package/test/options.error-handler.test.js +5 -0
  255. package/test/options.test.js +5 -0
  256. package/test/output-validation.test.js +140 -0
  257. package/test/patch.error-handler.test.js +5 -0
  258. package/test/patch.test.js +5 -0
  259. package/test/plugin.1.test.js +230 -0
  260. package/test/plugin.2.test.js +314 -0
  261. package/test/plugin.3.test.js +287 -0
  262. package/test/plugin.4.test.js +504 -0
  263. package/test/plugin.helper.js +8 -0
  264. package/test/plugin.name.display.js +10 -0
  265. package/test/post-empty-body.test.js +38 -0
  266. package/test/pretty-print.test.js +366 -0
  267. package/test/promises.test.js +125 -0
  268. package/test/proto-poisoning.test.js +145 -0
  269. package/test/put.error-handler.test.js +5 -0
  270. package/test/put.test.js +5 -0
  271. package/test/register.test.js +184 -0
  272. package/test/reply-code.test.js +148 -0
  273. package/test/reply-early-hints.test.js +100 -0
  274. package/test/reply-error.test.js +815 -0
  275. package/test/reply-trailers.test.js +445 -0
  276. package/test/reply-web-stream-locked.test.js +37 -0
  277. package/test/request-error.test.js +624 -0
  278. package/test/request-header-host.test.js +339 -0
  279. package/test/request-id.test.js +118 -0
  280. package/test/request-timeout.test.js +53 -0
  281. package/test/route-hooks.test.js +635 -0
  282. package/test/route-prefix.test.js +904 -0
  283. package/test/route-shorthand.test.js +48 -0
  284. package/test/route.1.test.js +259 -0
  285. package/test/route.2.test.js +100 -0
  286. package/test/route.3.test.js +213 -0
  287. package/test/route.4.test.js +127 -0
  288. package/test/route.5.test.js +211 -0
  289. package/test/route.6.test.js +306 -0
  290. package/test/route.7.test.js +406 -0
  291. package/test/route.8.test.js +225 -0
  292. package/test/router-options.test.js +1108 -0
  293. package/test/same-shape.test.js +124 -0
  294. package/test/schema-examples.test.js +661 -0
  295. package/test/schema-feature.test.js +2198 -0
  296. package/test/schema-serialization.test.js +1171 -0
  297. package/test/schema-special-usage.test.js +1348 -0
  298. package/test/schema-validation.test.js +1572 -0
  299. package/test/scripts/validate-ecosystem-links.test.js +339 -0
  300. package/test/serialize-response.test.js +186 -0
  301. package/test/server.test.js +347 -0
  302. package/test/set-error-handler.test.js +69 -0
  303. package/test/skip-reply-send.test.js +317 -0
  304. package/test/stream-serializers.test.js +40 -0
  305. package/test/stream.1.test.js +94 -0
  306. package/test/stream.2.test.js +129 -0
  307. package/test/stream.3.test.js +198 -0
  308. package/test/stream.4.test.js +176 -0
  309. package/test/stream.5.test.js +188 -0
  310. package/test/sync-routes.test.js +32 -0
  311. package/test/throw.test.js +359 -0
  312. package/test/toolkit.js +63 -0
  313. package/test/trust-proxy.test.js +162 -0
  314. package/test/type-provider.test.js +22 -0
  315. package/test/types/content-type-parser.test-d.ts +72 -0
  316. package/test/types/decorate-request-reply.test-d.ts +18 -0
  317. package/test/types/dummy-plugin.ts +9 -0
  318. package/test/types/errors.test-d.ts +90 -0
  319. package/test/types/fastify.test-d.ts +352 -0
  320. package/test/types/hooks.test-d.ts +550 -0
  321. package/test/types/import.ts +2 -0
  322. package/test/types/instance.test-d.ts +588 -0
  323. package/test/types/logger.test-d.ts +277 -0
  324. package/test/types/plugin.test-d.ts +97 -0
  325. package/test/types/register.test-d.ts +237 -0
  326. package/test/types/reply.test-d.ts +254 -0
  327. package/test/types/request.test-d.ts +188 -0
  328. package/test/types/route.test-d.ts +553 -0
  329. package/test/types/schema.test-d.ts +135 -0
  330. package/test/types/serverFactory.test-d.ts +37 -0
  331. package/test/types/type-provider.test-d.ts +1213 -0
  332. package/test/types/using.test-d.ts +17 -0
  333. package/test/upgrade.test.js +52 -0
  334. package/test/url-rewriting.test.js +122 -0
  335. package/test/use-semicolon-delimiter.test.js +168 -0
  336. package/test/validation-error-handling.test.js +900 -0
  337. package/test/versioned-routes.test.js +603 -0
  338. package/test/web-api.test.js +616 -0
  339. package/test/wrap-thenable.test.js +30 -0
  340. package/types/content-type-parser.d.ts +75 -0
  341. package/types/context.d.ts +22 -0
  342. package/types/errors.d.ts +92 -0
  343. package/types/hooks.d.ts +875 -0
  344. package/types/instance.d.ts +609 -0
  345. package/types/logger.d.ts +107 -0
  346. package/types/plugin.d.ts +44 -0
  347. package/types/register.d.ts +42 -0
  348. package/types/reply.d.ts +81 -0
  349. package/types/request.d.ts +95 -0
  350. package/types/route.d.ts +199 -0
  351. package/types/schema.d.ts +61 -0
  352. package/types/server-factory.d.ts +19 -0
  353. package/types/type-provider.d.ts +130 -0
  354. package/types/utils.d.ts +98 -0
@@ -0,0 +1,1108 @@
1
+ 'use strict'
2
+
3
+ const split = require('split2')
4
+ const { test } = require('node:test')
5
+ const querystring = require('node:querystring')
6
+ const Fastify = require('../')
7
+ const {
8
+ FST_ERR_BAD_URL,
9
+ FST_ERR_ASYNC_CONSTRAINT
10
+ } = require('../lib/errors')
11
+
12
+ test('Should honor ignoreTrailingSlash option', async t => {
13
+ t.plan(4)
14
+ const fastify = Fastify({
15
+ ignoreTrailingSlash: true
16
+ })
17
+
18
+ fastify.get('/test', (req, res) => {
19
+ res.send('test')
20
+ })
21
+
22
+ let res = await fastify.inject('/test')
23
+ t.assert.strictEqual(res.statusCode, 200)
24
+ t.assert.strictEqual(res.payload.toString(), 'test')
25
+
26
+ res = await fastify.inject('/test/')
27
+ t.assert.strictEqual(res.statusCode, 200)
28
+ t.assert.strictEqual(res.payload.toString(), 'test')
29
+ })
30
+
31
+ test('Should honor ignoreDuplicateSlashes option', async t => {
32
+ t.plan(4)
33
+ const fastify = Fastify({
34
+ ignoreDuplicateSlashes: true
35
+ })
36
+
37
+ fastify.get('/test//test///test', (req, res) => {
38
+ res.send('test')
39
+ })
40
+
41
+ let res = await fastify.inject('/test/test/test')
42
+ t.assert.strictEqual(res.statusCode, 200)
43
+ t.assert.strictEqual(res.payload.toString(), 'test')
44
+
45
+ res = await fastify.inject('/test//test///test')
46
+ t.assert.strictEqual(res.statusCode, 200)
47
+ t.assert.strictEqual(res.payload.toString(), 'test')
48
+ })
49
+
50
+ test('Should honor ignoreTrailingSlash and ignoreDuplicateSlashes options', async t => {
51
+ t.plan(4)
52
+ const fastify = Fastify({
53
+ ignoreTrailingSlash: true,
54
+ ignoreDuplicateSlashes: true
55
+ })
56
+
57
+ fastify.get('/test//test///test', (req, res) => {
58
+ res.send('test')
59
+ })
60
+
61
+ let res = await fastify.inject('/test/test/test/')
62
+ t.assert.strictEqual(res.statusCode, 200)
63
+ t.assert.strictEqual(res.payload.toString(), 'test')
64
+
65
+ res = await fastify.inject('/test//test///test//')
66
+ t.assert.strictEqual(res.statusCode, 200)
67
+ t.assert.strictEqual(res.payload.toString(), 'test')
68
+ })
69
+
70
+ test('Should honor maxParamLength option', async (t) => {
71
+ const fastify = Fastify({ maxParamLength: 10 })
72
+
73
+ fastify.get('/test/:id', (req, reply) => {
74
+ reply.send({ hello: 'world' })
75
+ })
76
+
77
+ const res = await fastify.inject({
78
+ method: 'GET',
79
+ url: '/test/123456789'
80
+ })
81
+ t.assert.strictEqual(res.statusCode, 200)
82
+
83
+ const resError = await fastify.inject({
84
+ method: 'GET',
85
+ url: '/test/123456789abcd'
86
+ })
87
+ t.assert.strictEqual(resError.statusCode, 404)
88
+ })
89
+
90
+ test('Should expose router options via getters on request and reply', (t, done) => {
91
+ t.plan(9)
92
+ const fastify = Fastify()
93
+ const expectedSchema = {
94
+ params: {
95
+ type: 'object',
96
+ properties: {
97
+ id: { type: 'integer' }
98
+ }
99
+ }
100
+ }
101
+
102
+ fastify.get('/test/:id', {
103
+ schema: expectedSchema
104
+ }, (req, reply) => {
105
+ t.assert.strictEqual(reply.routeOptions.config.url, '/test/:id')
106
+ t.assert.strictEqual(reply.routeOptions.config.method, 'GET')
107
+ t.assert.deepStrictEqual(req.routeOptions.schema, expectedSchema)
108
+ t.assert.strictEqual(typeof req.routeOptions.handler, 'function')
109
+ t.assert.strictEqual(req.routeOptions.config.url, '/test/:id')
110
+ t.assert.strictEqual(req.routeOptions.config.method, 'GET')
111
+ t.assert.strictEqual(req.is404, false)
112
+ reply.send({ hello: 'world' })
113
+ })
114
+
115
+ fastify.inject({
116
+ method: 'GET',
117
+ url: '/test/123456789'
118
+ }, (error, res) => {
119
+ t.assert.ifError(error)
120
+ t.assert.strictEqual(res.statusCode, 200)
121
+ done()
122
+ })
123
+ })
124
+
125
+ test('Should set is404 flag for unmatched paths', (t, done) => {
126
+ t.plan(3)
127
+ const fastify = Fastify()
128
+
129
+ fastify.setNotFoundHandler((req, reply) => {
130
+ t.assert.strictEqual(req.is404, true)
131
+ reply.code(404).send({ error: 'Not Found', message: 'Four oh for', statusCode: 404 })
132
+ })
133
+
134
+ fastify.inject({
135
+ method: 'GET',
136
+ url: '/nonexist/123456789'
137
+ }, (error, res) => {
138
+ t.assert.ifError(error)
139
+ t.assert.strictEqual(res.statusCode, 404)
140
+ done()
141
+ })
142
+ })
143
+
144
+ test('Should honor frameworkErrors option - FST_ERR_BAD_URL', (t, done) => {
145
+ t.plan(3)
146
+ const fastify = Fastify({
147
+ frameworkErrors: function (err, req, res) {
148
+ if (err instanceof FST_ERR_BAD_URL) {
149
+ t.assert.ok(true)
150
+ } else {
151
+ t.assert.fail()
152
+ }
153
+ res.send(`${err.message} - ${err.code}`)
154
+ }
155
+ })
156
+
157
+ fastify.get('/test/:id', (req, res) => {
158
+ res.send('{ hello: \'world\' }')
159
+ })
160
+
161
+ fastify.inject(
162
+ {
163
+ method: 'GET',
164
+ url: '/test/%world'
165
+ },
166
+ (err, res) => {
167
+ t.assert.ifError(err)
168
+ t.assert.strictEqual(res.body, '\'/test/%world\' is not a valid url component - FST_ERR_BAD_URL')
169
+ done()
170
+ }
171
+ )
172
+ })
173
+
174
+ test('Should supply Fastify request to the logger in frameworkErrors wrapper - FST_ERR_BAD_URL', (t, done) => {
175
+ t.plan(8)
176
+
177
+ const REQ_ID = 'REQ-1234'
178
+ const logStream = split(JSON.parse)
179
+
180
+ const fastify = Fastify({
181
+ frameworkErrors: function (err, req, res) {
182
+ t.assert.deepStrictEqual(req.id, REQ_ID)
183
+ t.assert.deepStrictEqual(req.raw.httpVersion, '1.1')
184
+ res.send(`${err.message} - ${err.code}`)
185
+ },
186
+ logger: {
187
+ stream: logStream,
188
+ serializers: {
189
+ req (request) {
190
+ t.assert.deepStrictEqual(request.id, REQ_ID)
191
+ return { httpVersion: request.raw.httpVersion }
192
+ }
193
+ }
194
+ },
195
+ genReqId: () => REQ_ID
196
+ })
197
+
198
+ fastify.get('/test/:id', (req, res) => {
199
+ res.send('{ hello: \'world\' }')
200
+ })
201
+
202
+ logStream.on('data', (json) => {
203
+ t.assert.deepStrictEqual(json.msg, 'incoming request')
204
+ t.assert.deepStrictEqual(json.reqId, REQ_ID)
205
+ t.assert.deepStrictEqual(json.req.httpVersion, '1.1')
206
+ })
207
+
208
+ fastify.inject(
209
+ {
210
+ method: 'GET',
211
+ url: '/test/%world'
212
+ },
213
+ (err, res) => {
214
+ t.assert.ifError(err)
215
+ t.assert.strictEqual(res.body, '\'/test/%world\' is not a valid url component - FST_ERR_BAD_URL')
216
+ done()
217
+ }
218
+ )
219
+ })
220
+
221
+ test('Should honor disableRequestLogging option in frameworkErrors wrapper - FST_ERR_BAD_URL', (t, done) => {
222
+ t.plan(2)
223
+
224
+ const logStream = split(JSON.parse)
225
+
226
+ const fastify = Fastify({
227
+ disableRequestLogging: true,
228
+ frameworkErrors: function (err, req, res) {
229
+ res.send(`${err.message} - ${err.code}`)
230
+ },
231
+ logger: {
232
+ stream: logStream,
233
+ serializers: {
234
+ req () {
235
+ t.assert.fail('should not be called')
236
+ },
237
+ res () {
238
+ t.assert.fail('should not be called')
239
+ }
240
+ }
241
+ }
242
+ })
243
+
244
+ fastify.get('/test/:id', (req, res) => {
245
+ res.send('{ hello: \'world\' }')
246
+ })
247
+
248
+ logStream.on('data', (json) => {
249
+ t.assert.fail('should not be called')
250
+ })
251
+
252
+ fastify.inject(
253
+ {
254
+ method: 'GET',
255
+ url: '/test/%world'
256
+ },
257
+ (err, res) => {
258
+ t.assert.ifError(err)
259
+ t.assert.strictEqual(res.body, '\'/test/%world\' is not a valid url component - FST_ERR_BAD_URL')
260
+ done()
261
+ }
262
+ )
263
+ })
264
+
265
+ test('Should honor frameworkErrors option - FST_ERR_ASYNC_CONSTRAINT', (t, done) => {
266
+ t.plan(3)
267
+
268
+ const constraint = {
269
+ name: 'secret',
270
+ storage: function () {
271
+ const secrets = {}
272
+ return {
273
+ get: (secret) => { return secrets[secret] || null },
274
+ set: (secret, store) => { secrets[secret] = store }
275
+ }
276
+ },
277
+ deriveConstraint: (req, ctx, done) => {
278
+ done(Error('kaboom'))
279
+ },
280
+ validate () { return true }
281
+ }
282
+
283
+ const fastify = Fastify({
284
+ frameworkErrors: function (err, req, res) {
285
+ if (err instanceof FST_ERR_ASYNC_CONSTRAINT) {
286
+ t.assert.ok(true)
287
+ } else {
288
+ t.assert.fail()
289
+ }
290
+ res.send(`${err.message} - ${err.code}`)
291
+ },
292
+ constraints: { secret: constraint }
293
+ })
294
+
295
+ fastify.route({
296
+ method: 'GET',
297
+ url: '/',
298
+ constraints: { secret: 'alpha' },
299
+ handler: (req, reply) => {
300
+ reply.send({ hello: 'from alpha' })
301
+ }
302
+ })
303
+
304
+ fastify.inject(
305
+ {
306
+ method: 'GET',
307
+ url: '/'
308
+ },
309
+ (err, res) => {
310
+ t.assert.ifError(err)
311
+ t.assert.strictEqual(res.body, 'Unexpected error from async constraint - FST_ERR_ASYNC_CONSTRAINT')
312
+ done()
313
+ }
314
+ )
315
+ })
316
+
317
+ test('Should supply Fastify request to the logger in frameworkErrors wrapper - FST_ERR_ASYNC_CONSTRAINT', (t, done) => {
318
+ t.plan(8)
319
+
320
+ const constraint = {
321
+ name: 'secret',
322
+ storage: function () {
323
+ const secrets = {}
324
+ return {
325
+ get: (secret) => { return secrets[secret] || null },
326
+ set: (secret, store) => { secrets[secret] = store }
327
+ }
328
+ },
329
+ deriveConstraint: (req, ctx, done) => {
330
+ done(Error('kaboom'))
331
+ },
332
+ validate () { return true }
333
+ }
334
+
335
+ const REQ_ID = 'REQ-1234'
336
+ const logStream = split(JSON.parse)
337
+
338
+ const fastify = Fastify({
339
+ constraints: { secret: constraint },
340
+ frameworkErrors: function (err, req, res) {
341
+ t.assert.deepStrictEqual(req.id, REQ_ID)
342
+ t.assert.deepStrictEqual(req.raw.httpVersion, '1.1')
343
+ res.send(`${err.message} - ${err.code}`)
344
+ },
345
+ logger: {
346
+ stream: logStream,
347
+ serializers: {
348
+ req (request) {
349
+ t.assert.deepStrictEqual(request.id, REQ_ID)
350
+ return { httpVersion: request.raw.httpVersion }
351
+ }
352
+ }
353
+ },
354
+ genReqId: () => REQ_ID
355
+ })
356
+
357
+ fastify.route({
358
+ method: 'GET',
359
+ url: '/',
360
+ constraints: { secret: 'alpha' },
361
+ handler: (req, reply) => {
362
+ reply.send({ hello: 'from alpha' })
363
+ }
364
+ })
365
+
366
+ logStream.on('data', (json) => {
367
+ t.assert.deepStrictEqual(json.msg, 'incoming request')
368
+ t.assert.deepStrictEqual(json.reqId, REQ_ID)
369
+ t.assert.deepStrictEqual(json.req.httpVersion, '1.1')
370
+ })
371
+
372
+ fastify.inject(
373
+ {
374
+ method: 'GET',
375
+ url: '/'
376
+ },
377
+ (err, res) => {
378
+ t.assert.ifError(err)
379
+ t.assert.strictEqual(res.body, 'Unexpected error from async constraint - FST_ERR_ASYNC_CONSTRAINT')
380
+ done()
381
+ }
382
+ )
383
+ })
384
+
385
+ test('Should honor disableRequestLogging option in frameworkErrors wrapper - FST_ERR_ASYNC_CONSTRAINT', (t, done) => {
386
+ t.plan(2)
387
+
388
+ const constraint = {
389
+ name: 'secret',
390
+ storage: function () {
391
+ const secrets = {}
392
+ return {
393
+ get: (secret) => { return secrets[secret] || null },
394
+ set: (secret, store) => { secrets[secret] = store }
395
+ }
396
+ },
397
+ deriveConstraint: (req, ctx, done) => {
398
+ done(Error('kaboom'))
399
+ },
400
+ validate () { return true }
401
+ }
402
+
403
+ const logStream = split(JSON.parse)
404
+
405
+ const fastify = Fastify({
406
+ constraints: { secret: constraint },
407
+ disableRequestLogging: true,
408
+ frameworkErrors: function (err, req, res) {
409
+ res.send(`${err.message} - ${err.code}`)
410
+ },
411
+ logger: {
412
+ stream: logStream,
413
+ serializers: {
414
+ req () {
415
+ t.assert.fail('should not be called')
416
+ },
417
+ res () {
418
+ t.assert.fail('should not be called')
419
+ }
420
+ }
421
+ }
422
+ })
423
+
424
+ fastify.route({
425
+ method: 'GET',
426
+ url: '/',
427
+ constraints: { secret: 'alpha' },
428
+ handler: (req, reply) => {
429
+ reply.send({ hello: 'from alpha' })
430
+ }
431
+ })
432
+
433
+ logStream.on('data', (json) => {
434
+ t.assert.fail('should not be called')
435
+ })
436
+
437
+ fastify.inject(
438
+ {
439
+ method: 'GET',
440
+ url: '/'
441
+ },
442
+ (err, res) => {
443
+ t.assert.ifError(err)
444
+ t.assert.strictEqual(res.body, 'Unexpected error from async constraint - FST_ERR_ASYNC_CONSTRAINT')
445
+ done()
446
+ }
447
+ )
448
+ })
449
+
450
+ test('Should honor disableRequestLogging function in frameworkErrors wrapper - FST_ERR_BAD_URL', (t, done) => {
451
+ t.plan(4)
452
+
453
+ let logCallCount = 0
454
+ const logStream = split(JSON.parse)
455
+
456
+ const fastify = Fastify({
457
+ disableRequestLogging: (req) => {
458
+ // Disable logging for URLs containing 'silent'
459
+ return req.url.includes('silent')
460
+ },
461
+ frameworkErrors: function (err, req, res) {
462
+ res.send(`${err.message} - ${err.code}`)
463
+ },
464
+ logger: {
465
+ stream: logStream,
466
+ level: 'info'
467
+ }
468
+ })
469
+
470
+ fastify.get('/test/:id', (req, res) => {
471
+ res.send('{ hello: \'world\' }')
472
+ })
473
+
474
+ logStream.on('data', (json) => {
475
+ if (json.msg === 'incoming request') {
476
+ logCallCount++
477
+ }
478
+ })
479
+
480
+ // First request: URL does not contain 'silent', so logging should happen
481
+ fastify.inject(
482
+ {
483
+ method: 'GET',
484
+ url: '/test/%world'
485
+ },
486
+ (err, res) => {
487
+ t.assert.ifError(err)
488
+ t.assert.strictEqual(res.body, '\'/test/%world\' is not a valid url component - FST_ERR_BAD_URL')
489
+
490
+ // Second request: URL contains 'silent', so logging should be disabled
491
+ fastify.inject(
492
+ {
493
+ method: 'GET',
494
+ url: '/silent/%world'
495
+ },
496
+ (err2, res2) => {
497
+ t.assert.ifError(err2)
498
+ // Give time for any potential log events
499
+ setImmediate(() => {
500
+ // Only the first request should have logged
501
+ t.assert.strictEqual(logCallCount, 1)
502
+ done()
503
+ })
504
+ }
505
+ )
506
+ }
507
+ )
508
+ })
509
+
510
+ test('Should honor disableRequestLogging function in frameworkErrors wrapper - FST_ERR_ASYNC_CONSTRAINT', (t, done) => {
511
+ t.plan(4)
512
+
513
+ let logCallCount = 0
514
+
515
+ const constraint = {
516
+ name: 'secret',
517
+ storage: function () {
518
+ const secrets = {}
519
+ return {
520
+ get: (secret) => { return secrets[secret] || null },
521
+ set: (secret, store) => { secrets[secret] = store }
522
+ }
523
+ },
524
+ deriveConstraint: (req, ctx, done) => {
525
+ done(Error('kaboom'))
526
+ },
527
+ validate () { return true }
528
+ }
529
+
530
+ const logStream = split(JSON.parse)
531
+
532
+ const fastify = Fastify({
533
+ constraints: { secret: constraint },
534
+ disableRequestLogging: (req) => {
535
+ // Disable logging for URLs containing 'silent'
536
+ return req.url.includes('silent')
537
+ },
538
+ frameworkErrors: function (err, req, res) {
539
+ res.send(`${err.message} - ${err.code}`)
540
+ },
541
+ logger: {
542
+ stream: logStream,
543
+ level: 'info'
544
+ }
545
+ })
546
+
547
+ fastify.route({
548
+ method: 'GET',
549
+ url: '/',
550
+ constraints: { secret: 'alpha' },
551
+ handler: (req, reply) => {
552
+ reply.send({ hello: 'from alpha' })
553
+ }
554
+ })
555
+
556
+ fastify.route({
557
+ method: 'GET',
558
+ url: '/silent',
559
+ constraints: { secret: 'alpha' },
560
+ handler: (req, reply) => {
561
+ reply.send({ hello: 'from alpha' })
562
+ }
563
+ })
564
+
565
+ logStream.on('data', (json) => {
566
+ if (json.msg === 'incoming request') {
567
+ logCallCount++
568
+ }
569
+ })
570
+
571
+ // First request: URL does not contain 'silent', so logging should happen
572
+ fastify.inject(
573
+ {
574
+ method: 'GET',
575
+ url: '/'
576
+ },
577
+ (err, res) => {
578
+ t.assert.ifError(err)
579
+ t.assert.strictEqual(res.body, 'Unexpected error from async constraint - FST_ERR_ASYNC_CONSTRAINT')
580
+
581
+ // Second request: URL contains 'silent', so logging should be disabled
582
+ fastify.inject(
583
+ {
584
+ method: 'GET',
585
+ url: '/silent'
586
+ },
587
+ (err2, res2) => {
588
+ t.assert.ifError(err2)
589
+ // Give time for any potential log events
590
+ setImmediate(() => {
591
+ // Only the first request should have logged
592
+ t.assert.strictEqual(logCallCount, 1)
593
+ done()
594
+ })
595
+ }
596
+ )
597
+ }
598
+ )
599
+ })
600
+
601
+ test('Should honor routerOptions.defaultRoute', async t => {
602
+ t.plan(3)
603
+ const fastify = Fastify({
604
+ routerOptions: {
605
+ defaultRoute: function (_, res) {
606
+ t.assert.ok('default route called')
607
+ res.statusCode = 404
608
+ res.end('default route')
609
+ }
610
+ }
611
+ })
612
+
613
+ const res = await fastify.inject('/')
614
+ t.assert.strictEqual(res.statusCode, 404)
615
+ t.assert.strictEqual(res.payload, 'default route')
616
+ })
617
+
618
+ test('Should honor routerOptions.badUrl', async t => {
619
+ t.plan(3)
620
+ const fastify = Fastify({
621
+ routerOptions: {
622
+ defaultRoute: function (_, res) {
623
+ t.asset.fail('default route should not be called')
624
+ },
625
+ onBadUrl: function (path, _, res) {
626
+ t.assert.ok('bad url called')
627
+ res.statusCode = 400
628
+ res.end(`Bath URL: ${path}`)
629
+ }
630
+ }
631
+ })
632
+
633
+ fastify.get('/hello/:id', (req, res) => {
634
+ res.send({ hello: 'world' })
635
+ })
636
+
637
+ const res = await fastify.inject('/hello/%world')
638
+ t.assert.strictEqual(res.statusCode, 400)
639
+ t.assert.strictEqual(res.payload, 'Bath URL: /hello/%world')
640
+ })
641
+
642
+ test('Should honor routerOptions.ignoreTrailingSlash', async t => {
643
+ t.plan(4)
644
+ const fastify = Fastify({
645
+ routerOptions: {
646
+ ignoreTrailingSlash: true
647
+ }
648
+ })
649
+
650
+ fastify.get('/test', (req, res) => {
651
+ res.send('test')
652
+ })
653
+
654
+ let res = await fastify.inject('/test')
655
+ t.assert.strictEqual(res.statusCode, 200)
656
+ t.assert.strictEqual(res.payload.toString(), 'test')
657
+
658
+ res = await fastify.inject('/test/')
659
+ t.assert.strictEqual(res.statusCode, 200)
660
+ t.assert.strictEqual(res.payload.toString(), 'test')
661
+ })
662
+
663
+ test('Should honor routerOptions.ignoreDuplicateSlashes', async t => {
664
+ t.plan(4)
665
+ const fastify = Fastify({
666
+ routerOptions: {
667
+ ignoreDuplicateSlashes: true
668
+ }
669
+ })
670
+
671
+ fastify.get('/test//test///test', (req, res) => {
672
+ res.send('test')
673
+ })
674
+
675
+ let res = await fastify.inject('/test/test/test')
676
+ t.assert.strictEqual(res.statusCode, 200)
677
+ t.assert.strictEqual(res.payload.toString(), 'test')
678
+
679
+ res = await fastify.inject('/test//test///test')
680
+ t.assert.strictEqual(res.statusCode, 200)
681
+ t.assert.strictEqual(res.payload.toString(), 'test')
682
+ })
683
+
684
+ test('Should honor routerOptions.ignoreTrailingSlash and routerOptions.ignoreDuplicateSlashes', async t => {
685
+ t.plan(4)
686
+ const fastify = Fastify({
687
+ routerOptions: {
688
+ ignoreTrailingSlash: true,
689
+ ignoreDuplicateSlashes: true
690
+ }
691
+ })
692
+
693
+ t.after(() => fastify.close())
694
+
695
+ fastify.get('/test//test///test', (req, res) => {
696
+ res.send('test')
697
+ })
698
+
699
+ let res = await fastify.inject('/test/test/test/')
700
+ t.assert.strictEqual(res.statusCode, 200)
701
+ t.assert.strictEqual(res.payload.toString(), 'test')
702
+
703
+ res = await fastify.inject('/test//test///test//')
704
+ t.assert.strictEqual(res.statusCode, 200)
705
+ t.assert.strictEqual(res.payload.toString(), 'test')
706
+ })
707
+
708
+ test('Should honor routerOptions.maxParamLength', async (t) => {
709
+ const fastify = Fastify({
710
+ routerOptions:
711
+ {
712
+ maxParamLength: 10
713
+ }
714
+ })
715
+
716
+ fastify.get('/test/:id', (req, reply) => {
717
+ reply.send({ hello: 'world' })
718
+ })
719
+
720
+ const res = await fastify.inject({
721
+ method: 'GET',
722
+ url: '/test/123456789'
723
+ })
724
+ t.assert.strictEqual(res.statusCode, 200)
725
+
726
+ const resError = await fastify.inject({
727
+ method: 'GET',
728
+ url: '/test/123456789abcd'
729
+ })
730
+ t.assert.strictEqual(resError.statusCode, 404)
731
+ })
732
+
733
+ test('Should honor routerOptions.allowUnsafeRegex', async (t) => {
734
+ const fastify = Fastify({
735
+ routerOptions:
736
+ {
737
+ allowUnsafeRegex: true
738
+ }
739
+ })
740
+
741
+ fastify.get('/test/:id(([a-f0-9]{3},?)+)', (req, reply) => {
742
+ reply.send({ hello: 'world' })
743
+ })
744
+
745
+ let res = await fastify.inject({
746
+ method: 'GET',
747
+ url: '/test/bac,1ea'
748
+ })
749
+ t.assert.strictEqual(res.statusCode, 200)
750
+
751
+ res = await fastify.inject({
752
+ method: 'GET',
753
+ url: '/test/qwerty'
754
+ })
755
+
756
+ t.assert.strictEqual(res.statusCode, 404)
757
+ })
758
+
759
+ test('Should honor routerOptions.caseSensitive', async (t) => {
760
+ const fastify = Fastify({
761
+ routerOptions:
762
+ {
763
+ caseSensitive: false
764
+ }
765
+ })
766
+
767
+ fastify.get('/TeSt', (req, reply) => {
768
+ reply.send('test')
769
+ })
770
+
771
+ let res = await fastify.inject({
772
+ method: 'GET',
773
+ url: '/test'
774
+ })
775
+ t.assert.strictEqual(res.statusCode, 200)
776
+
777
+ res = await fastify.inject({
778
+ method: 'GET',
779
+ url: '/tEsT'
780
+ })
781
+ t.assert.strictEqual(res.statusCode, 200)
782
+
783
+ res = await fastify.inject({
784
+ method: 'GET',
785
+ url: '/TEST'
786
+ })
787
+ t.assert.strictEqual(res.statusCode, 200)
788
+ })
789
+
790
+ test('Should honor routerOptions.queryStringParser', async (t) => {
791
+ t.plan(4)
792
+ const fastify = Fastify({
793
+ routerOptions:
794
+ {
795
+ querystringParser: function (str) {
796
+ t.assert.ok('custom query string parser called')
797
+ return querystring.parse(str)
798
+ }
799
+ }
800
+ })
801
+
802
+ fastify.get('/test', (req, reply) => {
803
+ t.assert.deepStrictEqual(req.query.foo, 'bar')
804
+ t.assert.deepStrictEqual(req.query.baz, 'faz')
805
+ reply.send('test')
806
+ })
807
+
808
+ const res = await fastify.inject({
809
+ method: 'GET',
810
+ url: '/test?foo=bar&baz=faz'
811
+ })
812
+ t.assert.strictEqual(res.statusCode, 200)
813
+ })
814
+
815
+ test('Should honor routerOptions.useSemicolonDelimiter', async (t) => {
816
+ t.plan(6)
817
+ const fastify = Fastify({
818
+ routerOptions:
819
+ {
820
+ useSemicolonDelimiter: true
821
+ }
822
+ })
823
+
824
+ fastify.get('/test', (req, reply) => {
825
+ t.assert.deepStrictEqual(req.query.foo, 'bar')
826
+ t.assert.deepStrictEqual(req.query.baz, 'faz')
827
+ reply.send('test')
828
+ })
829
+
830
+ // Support semicolon delimiter
831
+ let res = await fastify.inject({
832
+ method: 'GET',
833
+ url: '/test;foo=bar&baz=faz'
834
+ })
835
+ t.assert.strictEqual(res.statusCode, 200)
836
+
837
+ // Support query string `?` delimiter
838
+ res = await fastify.inject({
839
+ method: 'GET',
840
+ url: '/test?foo=bar&baz=faz'
841
+ })
842
+ t.assert.strictEqual(res.statusCode, 200)
843
+ })
844
+
845
+ test('Should honor routerOptions.buildPrettyMeta', async (t) => {
846
+ t.plan(10)
847
+ const fastify = Fastify({
848
+ routerOptions:
849
+ {
850
+ buildPrettyMeta: function (route) {
851
+ t.assert.ok('custom buildPrettyMeta called')
852
+ return { metaKey: route.path }
853
+ }
854
+ }
855
+ })
856
+
857
+ fastify.get('/test', () => {})
858
+ fastify.get('/test/hello', () => {})
859
+ fastify.get('/testing', () => {})
860
+ fastify.get('/testing/:param', () => {})
861
+ fastify.put('/update', () => {})
862
+
863
+ await fastify.ready()
864
+
865
+ const result = fastify.printRoutes({ includeMeta: true })
866
+ const expected = `\
867
+ └── /
868
+ ├── test (GET, HEAD)
869
+ │ • (metaKey) "/test"
870
+ │ ├── /hello (GET, HEAD)
871
+ │ │ • (metaKey) "/test/hello"
872
+ │ └── ing (GET, HEAD)
873
+ │ • (metaKey) "/testing"
874
+ │ └── /
875
+ │ └── :param (GET, HEAD)
876
+ │ • (metaKey) "/testing/:param"
877
+ └── update (PUT)
878
+ • (metaKey) "/update"
879
+ `
880
+
881
+ t.assert.strictEqual(result, expected)
882
+ })
883
+
884
+ test('Should honor routerOptions.ignoreTrailingSlash and routerOptions.ignoreDuplicateSlashes over top level options', async t => {
885
+ t.plan(4)
886
+ const fastify = Fastify({
887
+ ignoreTrailingSlash: false,
888
+ ignoreDuplicateSlashes: false,
889
+ routerOptions: {
890
+ ignoreTrailingSlash: true,
891
+ ignoreDuplicateSlashes: true
892
+ }
893
+ })
894
+
895
+ fastify.get('/test//test///test', (req, res) => {
896
+ res.send('test')
897
+ })
898
+
899
+ let res = await fastify.inject('/test/test/test/')
900
+ t.assert.strictEqual(res.statusCode, 200)
901
+ t.assert.strictEqual(res.payload.toString(), 'test')
902
+
903
+ res = await fastify.inject('/test//test///test//')
904
+ t.assert.strictEqual(res.statusCode, 200)
905
+ t.assert.strictEqual(res.payload.toString(), 'test')
906
+ })
907
+
908
+ test('Should honor routerOptions.maxParamLength over maxParamLength option', async (t) => {
909
+ const fastify = Fastify({
910
+ maxParamLength: 0,
911
+ routerOptions:
912
+ {
913
+ maxParamLength: 10
914
+ }
915
+ })
916
+
917
+ fastify.get('/test/:id', (req, reply) => {
918
+ reply.send({ hello: 'world' })
919
+ })
920
+
921
+ const res = await fastify.inject({
922
+ method: 'GET',
923
+ url: '/test/123456789'
924
+ })
925
+ t.assert.strictEqual(res.statusCode, 200)
926
+
927
+ const resError = await fastify.inject({
928
+ method: 'GET',
929
+ url: '/test/123456789abcd'
930
+ })
931
+ t.assert.strictEqual(resError.statusCode, 404)
932
+ })
933
+
934
+ test('Should honor routerOptions.allowUnsafeRegex over allowUnsafeRegex option', async (t) => {
935
+ const fastify = Fastify({
936
+ allowUnsafeRegex: false,
937
+ routerOptions:
938
+ {
939
+ allowUnsafeRegex: true
940
+ }
941
+ })
942
+
943
+ fastify.get('/test/:id(([a-f0-9]{3},?)+)', (req, reply) => {
944
+ reply.send({ hello: 'world' })
945
+ })
946
+
947
+ let res = await fastify.inject({
948
+ method: 'GET',
949
+ url: '/test/bac,1ea'
950
+ })
951
+ t.assert.strictEqual(res.statusCode, 200)
952
+
953
+ res = await fastify.inject({
954
+ method: 'GET',
955
+ url: '/test/qwerty'
956
+ })
957
+
958
+ t.assert.strictEqual(res.statusCode, 404)
959
+ })
960
+
961
+ test('Should honor routerOptions.caseSensitive over caseSensitive option', async (t) => {
962
+ const fastify = Fastify({
963
+ caseSensitive: true,
964
+ routerOptions:
965
+ {
966
+ caseSensitive: false
967
+ }
968
+ })
969
+
970
+ fastify.get('/TeSt', (req, reply) => {
971
+ reply.send('test')
972
+ })
973
+
974
+ let res = await fastify.inject({
975
+ method: 'GET',
976
+ url: '/test'
977
+ })
978
+ t.assert.strictEqual(res.statusCode, 200)
979
+
980
+ res = await fastify.inject({
981
+ method: 'GET',
982
+ url: '/tEsT'
983
+ })
984
+ t.assert.strictEqual(res.statusCode, 200)
985
+
986
+ res = await fastify.inject({
987
+ method: 'GET',
988
+ url: '/TEST'
989
+ })
990
+ t.assert.strictEqual(res.statusCode, 200)
991
+ })
992
+
993
+ test('Should honor routerOptions.queryStringParser over queryStringParser option', async (t) => {
994
+ t.plan(4)
995
+ const fastify = Fastify({
996
+ queryStringParser: undefined,
997
+ routerOptions:
998
+ {
999
+ querystringParser: function (str) {
1000
+ t.assert.ok('custom query string parser called')
1001
+ return querystring.parse(str)
1002
+ }
1003
+ }
1004
+ })
1005
+
1006
+ fastify.get('/test', (req, reply) => {
1007
+ t.assert.deepStrictEqual(req.query.foo, 'bar')
1008
+ t.assert.deepStrictEqual(req.query.baz, 'faz')
1009
+ reply.send('test')
1010
+ })
1011
+
1012
+ const res = await fastify.inject({
1013
+ method: 'GET',
1014
+ url: '/test?foo=bar&baz=faz'
1015
+ })
1016
+ t.assert.strictEqual(res.statusCode, 200)
1017
+ })
1018
+
1019
+ test('Should honor routerOptions.useSemicolonDelimiter over useSemicolonDelimiter option', async (t) => {
1020
+ t.plan(6)
1021
+ const fastify = Fastify({
1022
+ useSemicolonDelimiter: false,
1023
+ routerOptions:
1024
+ {
1025
+ useSemicolonDelimiter: true
1026
+ }
1027
+ })
1028
+
1029
+ fastify.get('/test', (req, reply) => {
1030
+ t.assert.deepStrictEqual(req.query.foo, 'bar')
1031
+ t.assert.deepStrictEqual(req.query.baz, 'faz')
1032
+ reply.send('test')
1033
+ })
1034
+
1035
+ // Support semicolon delimiter
1036
+ let res = await fastify.inject({
1037
+ method: 'GET',
1038
+ url: '/test;foo=bar&baz=faz'
1039
+ })
1040
+ t.assert.strictEqual(res.statusCode, 200)
1041
+
1042
+ // Support query string `?` delimiter
1043
+ res = await fastify.inject({
1044
+ method: 'GET',
1045
+ url: '/test?foo=bar&baz=faz'
1046
+ })
1047
+ t.assert.strictEqual(res.statusCode, 200)
1048
+ })
1049
+
1050
+ test('Should support extra find-my-way options', async t => {
1051
+ t.plan(1)
1052
+ // Use a real upstream option from find-my-way
1053
+ const fastify = Fastify({
1054
+ routerOptions: {
1055
+ buildPrettyMeta: (route) => {
1056
+ const cleanMeta = Object.assign({}, route.store)
1057
+ return cleanMeta
1058
+ }
1059
+ }
1060
+ })
1061
+
1062
+ t.after(() => fastify.close())
1063
+
1064
+ await fastify.ready()
1065
+
1066
+ // Ensure the option is preserved after validation
1067
+ t.assert.strictEqual(typeof fastify.initialConfig.routerOptions.buildPrettyMeta, 'function')
1068
+ })
1069
+
1070
+ test('Should allow reusing a routerOptions object across instances', async t => {
1071
+ t.plan(1)
1072
+
1073
+ const options = {
1074
+ routerOptions: {
1075
+ maxParamLength: 2048
1076
+ }
1077
+ }
1078
+
1079
+ const app1 = Fastify(options)
1080
+ const app2 = Fastify(options)
1081
+
1082
+ t.after(() => Promise.all([
1083
+ app1.close(),
1084
+ app2.close()
1085
+ ]))
1086
+
1087
+ const response = await app2.inject('/not-found')
1088
+ t.assert.strictEqual(response.statusCode, 404)
1089
+ })
1090
+
1091
+ test('Should not mutate user-provided routerOptions object', async t => {
1092
+ t.plan(4)
1093
+
1094
+ const routerOptions = {
1095
+ maxParamLength: 2048
1096
+ }
1097
+ const options = { routerOptions }
1098
+
1099
+ const app = Fastify(options)
1100
+ t.after(() => app.close())
1101
+
1102
+ await app.ready()
1103
+
1104
+ t.assert.deepStrictEqual(Object.keys(routerOptions), ['maxParamLength'])
1105
+ t.assert.strictEqual(routerOptions.maxParamLength, 2048)
1106
+ t.assert.strictEqual(routerOptions.defaultRoute, undefined)
1107
+ t.assert.strictEqual(routerOptions.onBadUrl, undefined)
1108
+ })