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,2035 @@
1
+ 'use strict'
2
+
3
+ const { test } = require('node:test')
4
+ const fp = require('fastify-plugin')
5
+ const errors = require('http-errors')
6
+ const split = require('split2')
7
+ const Fastify = require('..')
8
+ const { getServerUrl } = require('./helper')
9
+
10
+ test('default 404', async t => {
11
+ t.plan(4)
12
+
13
+ const fastify = Fastify()
14
+
15
+ fastify.get('/', function (req, reply) {
16
+ reply.send({ hello: 'world' })
17
+ })
18
+
19
+ t.after(() => { fastify.close() })
20
+
21
+ await fastify.listen({ port: 0 })
22
+
23
+ await t.test('unsupported method', async (t) => {
24
+ t.plan(3)
25
+ const result = await fetch(getServerUrl(fastify), {
26
+ method: 'PUT'
27
+ })
28
+
29
+ t.assert.ok(!result.ok)
30
+ t.assert.strictEqual(result.status, 404)
31
+ t.assert.strictEqual(result.headers.get('content-type'), 'application/json; charset=utf-8')
32
+ })
33
+
34
+ // Return 404 instead of 405 see https://github.com/fastify/fastify/pull/862 for discussion
35
+ await t.test('framework-unsupported method', async (t) => {
36
+ t.plan(3)
37
+ const result = await fetch(getServerUrl(fastify), {
38
+ method: 'PROPFIND'
39
+ })
40
+
41
+ t.assert.ok(!result.ok)
42
+ t.assert.strictEqual(result.status, 404)
43
+ t.assert.strictEqual(result.headers.get('content-type'), 'application/json; charset=utf-8')
44
+ })
45
+
46
+ await t.test('unsupported route', async (t) => {
47
+ t.plan(3)
48
+ const response = await fetch(getServerUrl(fastify) + '/notSupported')
49
+
50
+ t.assert.ok(!response.ok)
51
+ t.assert.strictEqual(response.status, 404)
52
+ t.assert.strictEqual(response.headers.get('content-type'), 'application/json; charset=utf-8')
53
+ })
54
+
55
+ await t.test('using post method and multipart/formdata', async t => {
56
+ t.plan(3)
57
+ const form = new FormData()
58
+ form.set('test-field', 'just some field')
59
+
60
+ const response = await fetch(getServerUrl(fastify) + '/notSupported', {
61
+ method: 'POST',
62
+ body: form
63
+ })
64
+ t.assert.strictEqual(response.status, 404)
65
+ t.assert.strictEqual(response.statusText, 'Not Found')
66
+ t.assert.strictEqual(response.headers.get('content-type'), 'application/json; charset=utf-8')
67
+ })
68
+ })
69
+
70
+ test('customized 404', async t => {
71
+ t.plan(5)
72
+
73
+ const fastify = Fastify()
74
+
75
+ fastify.get('/', function (req, reply) {
76
+ reply.send({ hello: 'world' })
77
+ })
78
+
79
+ fastify.get('/with-error', function (req, reply) {
80
+ reply.send(new errors.NotFound())
81
+ })
82
+
83
+ fastify.get('/with-error-custom-header', function (req, reply) {
84
+ const err = new errors.NotFound()
85
+ err.headers = { 'x-foo': 'bar' }
86
+ reply.send(err)
87
+ })
88
+
89
+ fastify.setNotFoundHandler(function (req, reply) {
90
+ reply.code(404).send('this was not found')
91
+ })
92
+
93
+ t.after(() => { fastify.close() })
94
+
95
+ await fastify.listen({ port: 0 })
96
+
97
+ await t.test('unsupported method', async (t) => {
98
+ t.plan(3)
99
+ const response = await fetch(getServerUrl(fastify), {
100
+ method: 'PUT',
101
+ body: JSON.stringify({ hello: 'world' }),
102
+ headers: { 'Content-Type': 'application/json' }
103
+ })
104
+
105
+ t.assert.ok(!response.ok)
106
+ t.assert.strictEqual(response.status, 404)
107
+ t.assert.strictEqual(await response.text(), 'this was not found')
108
+ })
109
+
110
+ await t.test('framework-unsupported method', async (t) => {
111
+ t.plan(3)
112
+ const response = await fetch(getServerUrl(fastify), {
113
+ method: 'PROPFIND',
114
+ body: JSON.stringify({ hello: 'world' }),
115
+ headers: { 'Content-Type': 'application/json' }
116
+ })
117
+
118
+ t.assert.ok(!response.ok)
119
+ t.assert.strictEqual(response.status, 404)
120
+ t.assert.strictEqual(await response.text(), 'this was not found')
121
+ })
122
+
123
+ await t.test('unsupported route', async (t) => {
124
+ t.plan(3)
125
+ const response = await fetch(getServerUrl(fastify) + '/notSupported')
126
+
127
+ t.assert.ok(!response.ok)
128
+ t.assert.strictEqual(response.status, 404)
129
+ t.assert.strictEqual(await response.text(), 'this was not found')
130
+ })
131
+
132
+ await t.test('with error object', async (t) => {
133
+ t.plan(3)
134
+ const response = await fetch(getServerUrl(fastify) + '/with-error')
135
+
136
+ t.assert.ok(!response.ok)
137
+ t.assert.strictEqual(response.status, 404)
138
+ t.assert.deepStrictEqual(await response.json(), {
139
+ error: 'Not Found',
140
+ message: 'Not Found',
141
+ statusCode: 404
142
+ })
143
+ })
144
+
145
+ await t.test('error object with headers property', async (t) => {
146
+ t.plan(4)
147
+ const response = await fetch(getServerUrl(fastify) + '/with-error-custom-header')
148
+
149
+ t.assert.ok(!response.ok)
150
+ t.assert.strictEqual(response.status, 404)
151
+ t.assert.strictEqual(response.headers.get('x-foo'), 'bar')
152
+ t.assert.deepStrictEqual(await response.json(), {
153
+ error: 'Not Found',
154
+ message: 'Not Found',
155
+ statusCode: 404
156
+ })
157
+ })
158
+ })
159
+
160
+ test('custom header in notFound handler', async t => {
161
+ t.plan(1)
162
+
163
+ const fastify = Fastify()
164
+
165
+ fastify.setNotFoundHandler(function (req, reply) {
166
+ reply.code(404).header('x-foo', 'bar').send('this was not found')
167
+ })
168
+
169
+ t.after(() => { fastify.close() })
170
+
171
+ await fastify.listen({ port: 0 })
172
+
173
+ await t.test('not found with custom header', async (t) => {
174
+ t.plan(4)
175
+ const response = await fetch(getServerUrl(fastify) + '/notSupported')
176
+
177
+ t.assert.ok(!response.ok)
178
+ t.assert.strictEqual(response.status, 404)
179
+ t.assert.strictEqual(response.headers.get('x-foo'), 'bar')
180
+ t.assert.strictEqual(await response.text(), 'this was not found')
181
+ })
182
+ })
183
+
184
+ test('setting a custom 404 handler multiple times is an error', async t => {
185
+ t.plan(5)
186
+
187
+ await t.test('at the root level', t => {
188
+ t.plan(2)
189
+
190
+ const fastify = Fastify()
191
+
192
+ fastify.setNotFoundHandler(() => {})
193
+
194
+ try {
195
+ fastify.setNotFoundHandler(() => {})
196
+ t.assert.fail('setting multiple 404 handlers at the same prefix encapsulation level should throw')
197
+ } catch (err) {
198
+ t.assert.ok(err instanceof Error)
199
+ t.assert.strictEqual(err.message, 'Not found handler already set for Fastify instance with prefix: \'/\'')
200
+ }
201
+ })
202
+
203
+ await t.test('at the plugin level', (t, done) => {
204
+ t.plan(3)
205
+
206
+ const fastify = Fastify()
207
+
208
+ fastify.register((instance, options, done) => {
209
+ instance.setNotFoundHandler(() => {})
210
+
211
+ try {
212
+ instance.setNotFoundHandler(() => {})
213
+ t.assert.fail('setting multiple 404 handlers at the same prefix encapsulation level should throw')
214
+ } catch (err) {
215
+ t.assert.ok(err instanceof Error)
216
+ t.assert.strictEqual(err.message, 'Not found handler already set for Fastify instance with prefix: \'/prefix\'')
217
+ }
218
+
219
+ done()
220
+ }, { prefix: '/prefix' })
221
+
222
+ fastify.listen({ port: 0 }, err => {
223
+ t.assert.ifError(err)
224
+ fastify.close()
225
+ done()
226
+ })
227
+ })
228
+
229
+ await t.test('at multiple levels', (t, done) => {
230
+ t.plan(3)
231
+
232
+ const fastify = Fastify()
233
+
234
+ fastify.register((instance, options, done) => {
235
+ try {
236
+ instance.setNotFoundHandler(() => {})
237
+ t.assert.fail('setting multiple 404 handlers at the same prefix encapsulation level should throw')
238
+ } catch (err) {
239
+ t.assert.ok(err instanceof Error)
240
+ t.assert.strictEqual(err.message, 'Not found handler already set for Fastify instance with prefix: \'/\'')
241
+ }
242
+ done()
243
+ })
244
+
245
+ fastify.setNotFoundHandler(() => {})
246
+
247
+ fastify.listen({ port: 0 }, err => {
248
+ t.assert.ifError(err)
249
+ fastify.close()
250
+ done()
251
+ })
252
+ })
253
+
254
+ await t.test('at multiple levels / 2', (t, done) => {
255
+ t.plan(3)
256
+
257
+ const fastify = Fastify()
258
+
259
+ fastify.register((instance, options, done) => {
260
+ instance.setNotFoundHandler(() => {})
261
+
262
+ instance.register((instance2, options, done) => {
263
+ try {
264
+ instance2.setNotFoundHandler(() => {})
265
+ t.assert.fail('setting multiple 404 handlers at the same prefix encapsulation level should throw')
266
+ } catch (err) {
267
+ t.assert.ok(err instanceof Error)
268
+ t.assert.strictEqual(err.message, 'Not found handler already set for Fastify instance with prefix: \'/prefix\'')
269
+ }
270
+ done()
271
+ })
272
+
273
+ done()
274
+ }, { prefix: '/prefix' })
275
+
276
+ fastify.setNotFoundHandler(() => {})
277
+
278
+ fastify.listen({ port: 0 }, err => {
279
+ t.assert.ifError(err)
280
+ fastify.close()
281
+ done()
282
+ })
283
+ })
284
+
285
+ await t.test('in separate plugins at the same level', (t, done) => {
286
+ t.plan(3)
287
+
288
+ const fastify = Fastify()
289
+
290
+ fastify.register((instance, options, done) => {
291
+ instance.register((instance2A, options, done) => {
292
+ instance2A.setNotFoundHandler(() => {})
293
+ done()
294
+ })
295
+
296
+ instance.register((instance2B, options, done) => {
297
+ try {
298
+ instance2B.setNotFoundHandler(() => {})
299
+ t.assert.fail('setting multiple 404 handlers at the same prefix encapsulation level should throw')
300
+ } catch (err) {
301
+ t.assert.ok(err instanceof Error)
302
+ t.assert.strictEqual(err.message, 'Not found handler already set for Fastify instance with prefix: \'/prefix\'')
303
+ }
304
+ done()
305
+ })
306
+
307
+ done()
308
+ }, { prefix: '/prefix' })
309
+
310
+ fastify.setNotFoundHandler(() => {})
311
+
312
+ fastify.listen({ port: 0 }, err => {
313
+ t.assert.ifError(err)
314
+ fastify.close()
315
+ done()
316
+ })
317
+ })
318
+ })
319
+
320
+ test('encapsulated 404', async t => {
321
+ t.plan(12)
322
+
323
+ const fastify = Fastify()
324
+
325
+ fastify.get('/', function (req, reply) {
326
+ reply.send({ hello: 'world' })
327
+ })
328
+
329
+ fastify.setNotFoundHandler(function (req, reply) {
330
+ reply.code(404).send('this was not found')
331
+ })
332
+
333
+ fastify.register(function (f, opts, done) {
334
+ f.setNotFoundHandler(function (req, reply) {
335
+ reply.code(404).send('this was not found 2')
336
+ })
337
+ done()
338
+ }, { prefix: '/test' })
339
+
340
+ fastify.register(function (f, opts, done) {
341
+ f.setNotFoundHandler(function (req, reply) {
342
+ reply.code(404).send('this was not found 3')
343
+ })
344
+ done()
345
+ }, { prefix: '/test2' })
346
+
347
+ fastify.register(function (f, opts, done) {
348
+ f.setNotFoundHandler(function (request, reply) {
349
+ reply.code(404).send('this was not found 4')
350
+ })
351
+ done()
352
+ }, { prefix: '/test3/' })
353
+
354
+ t.after(() => { fastify.close() })
355
+
356
+ await fastify.listen({ port: 0 })
357
+
358
+ await t.test('root unsupported method', async (t) => {
359
+ t.plan(3)
360
+ const response = await fetch(getServerUrl(fastify), {
361
+ method: 'PUT',
362
+ body: JSON.stringify({ hello: 'world' }),
363
+ headers: { 'Content-Type': 'application/json' }
364
+ })
365
+
366
+ t.assert.ok(!response.ok)
367
+ t.assert.strictEqual(response.status, 404)
368
+ t.assert.strictEqual(await response.text(), 'this was not found')
369
+ })
370
+
371
+ await t.test('root framework-unsupported method', async (t) => {
372
+ t.plan(3)
373
+ const response = await fetch(getServerUrl(fastify), {
374
+ method: 'PROPFIND',
375
+ body: JSON.stringify({ hello: 'world' }),
376
+ headers: { 'Content-Type': 'application/json' }
377
+ })
378
+
379
+ t.assert.ok(!response.ok)
380
+ t.assert.strictEqual(response.status, 404)
381
+ t.assert.strictEqual(await response.text(), 'this was not found')
382
+ })
383
+
384
+ await t.test('root unsupported route', async (t) => {
385
+ t.plan(3)
386
+ const response = await fetch(getServerUrl(fastify) + '/notSupported')
387
+
388
+ t.assert.ok(!response.ok)
389
+ t.assert.strictEqual(response.status, 404)
390
+ t.assert.strictEqual(await response.text(), 'this was not found')
391
+ })
392
+
393
+ await t.test('unsupported method', async (t) => {
394
+ t.plan(3)
395
+ const response = await fetch(getServerUrl(fastify) + '/test', {
396
+ method: 'PUT',
397
+ body: JSON.stringify({ hello: 'world' }),
398
+ headers: { 'Content-Type': 'application/json' }
399
+ })
400
+
401
+ t.assert.ok(!response.ok)
402
+ t.assert.strictEqual(response.status, 404)
403
+ t.assert.strictEqual(await response.text(), 'this was not found 2')
404
+ })
405
+
406
+ await t.test('framework-unsupported method', async (t) => {
407
+ t.plan(3)
408
+ const response = await fetch(getServerUrl(fastify) + '/test', {
409
+ method: 'PROPFIND',
410
+ body: JSON.stringify({ hello: 'world' }),
411
+ headers: { 'Content-Type': 'application/json' }
412
+ })
413
+
414
+ t.assert.ok(!response.ok)
415
+ t.assert.strictEqual(response.status, 404)
416
+ t.assert.strictEqual(await response.text(), 'this was not found 2')
417
+ })
418
+
419
+ await t.test('unsupported route', async (t) => {
420
+ t.plan(3)
421
+ const response = await fetch(getServerUrl(fastify) + '/test/notSupported')
422
+
423
+ t.assert.ok(!response.ok)
424
+ t.assert.strictEqual(response.status, 404)
425
+ t.assert.strictEqual(await response.text(), 'this was not found 2')
426
+ })
427
+
428
+ await t.test('unsupported method 2', async (t) => {
429
+ t.plan(3)
430
+ const response = await fetch(getServerUrl(fastify) + '/test2', {
431
+ method: 'PUT',
432
+ body: JSON.stringify({ hello: 'world' }),
433
+ headers: { 'Content-Type': 'application/json' }
434
+ })
435
+
436
+ t.assert.ok(!response.ok)
437
+ t.assert.strictEqual(response.status, 404)
438
+ t.assert.strictEqual(await response.text(), 'this was not found 3')
439
+ })
440
+
441
+ await t.test('framework-unsupported method 2', async (t) => {
442
+ t.plan(3)
443
+ const response = await fetch(getServerUrl(fastify) + '/test2', {
444
+ method: 'PROPFIND',
445
+ body: JSON.stringify({ hello: 'world' }),
446
+ headers: { 'Content-Type': 'application/json' }
447
+ })
448
+
449
+ t.assert.ok(!response.ok)
450
+ t.assert.strictEqual(response.status, 404)
451
+ t.assert.strictEqual(await response.text(), 'this was not found 3')
452
+ })
453
+
454
+ await t.test('unsupported route 2', async (t) => {
455
+ t.plan(3)
456
+ const response = await fetch(getServerUrl(fastify) + '/test2/notSupported')
457
+
458
+ t.assert.ok(!response.ok)
459
+ t.assert.strictEqual(response.status, 404)
460
+ t.assert.strictEqual(await response.text(), 'this was not found 3')
461
+ })
462
+
463
+ await t.test('unsupported method 3', async (t) => {
464
+ t.plan(3)
465
+ const response = await fetch(getServerUrl(fastify) + '/test3/', {
466
+ method: 'PUT',
467
+ body: JSON.stringify({ hello: 'world' }),
468
+ headers: { 'Content-Type': 'application/json' }
469
+ })
470
+
471
+ t.assert.ok(!response.ok)
472
+ t.assert.strictEqual(response.status, 404)
473
+ t.assert.strictEqual(await response.text(), 'this was not found 4')
474
+ })
475
+
476
+ await t.test('framework-unsupported method 3', async (t) => {
477
+ t.plan(3)
478
+ const response = await fetch(getServerUrl(fastify) + '/test3/', {
479
+ method: 'PROPFIND',
480
+ body: JSON.stringify({ hello: 'world' }),
481
+ headers: { 'Content-Type': 'application/json' }
482
+ })
483
+
484
+ t.assert.ok(!response.ok)
485
+ t.assert.strictEqual(response.status, 404)
486
+ t.assert.strictEqual(await response.text(), 'this was not found 4')
487
+ })
488
+
489
+ await t.test('unsupported route 3', async (t) => {
490
+ t.plan(3)
491
+ const response = await fetch(getServerUrl(fastify) + '/test3/notSupported')
492
+
493
+ t.assert.ok(!response.ok)
494
+ t.assert.strictEqual(response.status, 404)
495
+ t.assert.strictEqual(await response.text(), 'this was not found 4')
496
+ })
497
+ })
498
+
499
+ test('custom 404 hook and handler context', async t => {
500
+ t.plan(19)
501
+
502
+ const fastify = Fastify()
503
+
504
+ fastify.decorate('foo', 42)
505
+
506
+ fastify.addHook('onRequest', function (req, res, done) {
507
+ t.assert.strictEqual(this.foo, 42)
508
+ done()
509
+ })
510
+ fastify.addHook('preHandler', function (request, reply, done) {
511
+ t.assert.strictEqual(this.foo, 42)
512
+ done()
513
+ })
514
+ fastify.addHook('onSend', function (request, reply, payload, done) {
515
+ t.assert.strictEqual(this.foo, 42)
516
+ done()
517
+ })
518
+ fastify.addHook('onResponse', function (request, reply, done) {
519
+ t.assert.strictEqual(this.foo, 42)
520
+ done()
521
+ })
522
+
523
+ fastify.setNotFoundHandler(function (req, reply) {
524
+ t.assert.strictEqual(this.foo, 42)
525
+ reply.code(404).send('this was not found')
526
+ })
527
+
528
+ fastify.register(function (instance, opts, done) {
529
+ instance.decorate('bar', 84)
530
+
531
+ instance.addHook('onRequest', function (req, res, done) {
532
+ t.assert.strictEqual(this.bar, 84)
533
+ done()
534
+ })
535
+ instance.addHook('preHandler', function (request, reply, done) {
536
+ t.assert.strictEqual(this.bar, 84)
537
+ done()
538
+ })
539
+ instance.addHook('onSend', function (request, reply, payload, done) {
540
+ t.assert.strictEqual(this.bar, 84)
541
+ done()
542
+ })
543
+ instance.addHook('onResponse', function (request, reply, done) {
544
+ t.assert.strictEqual(this.bar, 84)
545
+ done()
546
+ })
547
+
548
+ instance.setNotFoundHandler(function (req, reply) {
549
+ t.assert.strictEqual(this.foo, 42)
550
+ t.assert.strictEqual(this.bar, 84)
551
+ reply.code(404).send('encapsulated was not found')
552
+ })
553
+
554
+ done()
555
+ }, { prefix: '/encapsulated' })
556
+
557
+ {
558
+ const res = await fastify.inject('/not-found')
559
+ t.assert.strictEqual(res.statusCode, 404)
560
+ t.assert.strictEqual(res.payload, 'this was not found')
561
+ }
562
+
563
+ {
564
+ const res = await fastify.inject('/encapsulated/not-found')
565
+ t.assert.strictEqual(res.statusCode, 404)
566
+ t.assert.strictEqual(res.payload, 'encapsulated was not found')
567
+ }
568
+ })
569
+
570
+ test('encapsulated custom 404 without - prefix hook and handler context', (t, done) => {
571
+ t.plan(13)
572
+
573
+ const fastify = Fastify()
574
+
575
+ fastify.decorate('foo', 42)
576
+
577
+ fastify.register(function (instance, opts, done) {
578
+ instance.decorate('bar', 84)
579
+
580
+ instance.addHook('onRequest', function (req, res, done) {
581
+ t.assert.strictEqual(this.foo, 42)
582
+ t.assert.strictEqual(this.bar, 84)
583
+ done()
584
+ })
585
+ instance.addHook('preHandler', function (request, reply, done) {
586
+ t.assert.strictEqual(this.foo, 42)
587
+ t.assert.strictEqual(this.bar, 84)
588
+ done()
589
+ })
590
+ instance.addHook('onSend', function (request, reply, payload, done) {
591
+ t.assert.strictEqual(this.foo, 42)
592
+ t.assert.strictEqual(this.bar, 84)
593
+ done()
594
+ })
595
+ instance.addHook('onResponse', function (request, reply, done) {
596
+ t.assert.strictEqual(this.foo, 42)
597
+ t.assert.strictEqual(this.bar, 84)
598
+ done()
599
+ })
600
+
601
+ instance.setNotFoundHandler(function (request, reply) {
602
+ t.assert.strictEqual(this.foo, 42)
603
+ t.assert.strictEqual(this.bar, 84)
604
+ reply.code(404).send('custom not found')
605
+ })
606
+
607
+ done()
608
+ })
609
+
610
+ fastify.inject('/not-found', (err, res) => {
611
+ t.assert.ifError(err)
612
+ t.assert.strictEqual(res.statusCode, 404)
613
+ t.assert.strictEqual(res.payload, 'custom not found')
614
+ done()
615
+ })
616
+ })
617
+
618
+ test('run hooks on default 404', async t => {
619
+ t.plan(6)
620
+
621
+ const fastify = Fastify()
622
+
623
+ fastify.addHook('onRequest', function (req, res, done) {
624
+ t.assert.ok(true, 'onRequest called')
625
+ done()
626
+ })
627
+
628
+ fastify.addHook('preHandler', function (request, reply, done) {
629
+ t.assert.ok(true, 'preHandler called')
630
+ done()
631
+ })
632
+
633
+ fastify.addHook('onSend', function (request, reply, payload, done) {
634
+ t.assert.ok(true, 'onSend called')
635
+ done()
636
+ })
637
+
638
+ fastify.addHook('onResponse', function (request, reply, done) {
639
+ t.assert.ok(true, 'onResponse called')
640
+ done()
641
+ })
642
+
643
+ fastify.get('/', function (req, reply) {
644
+ reply.send({ hello: 'world' })
645
+ })
646
+
647
+ t.after(() => { fastify.close() })
648
+
649
+ await fastify.listen({ port: 0 })
650
+
651
+ const response = await fetch(getServerUrl(fastify), {
652
+ method: 'PUT',
653
+ body: JSON.stringify({ hello: 'world' }),
654
+ headers: { 'Content-Type': 'application/json' }
655
+ })
656
+
657
+ t.assert.ok(!response.ok)
658
+ t.assert.strictEqual(response.status, 404)
659
+ })
660
+
661
+ test('run non-encapsulated plugin hooks on default 404', (t, done) => {
662
+ t.plan(6)
663
+
664
+ const fastify = Fastify()
665
+
666
+ fastify.register(fp(function (instance, options, done) {
667
+ instance.addHook('onRequest', function (req, res, done) {
668
+ t.assert.ok(true, 'onRequest called')
669
+ done()
670
+ })
671
+
672
+ instance.addHook('preHandler', function (request, reply, done) {
673
+ t.assert.ok(true, 'preHandler called')
674
+ done()
675
+ })
676
+
677
+ instance.addHook('onSend', function (request, reply, payload, done) {
678
+ t.assert.ok(true, 'onSend called')
679
+ done()
680
+ })
681
+
682
+ instance.addHook('onResponse', function (request, reply, done) {
683
+ t.assert.ok(true, 'onResponse called')
684
+ done()
685
+ })
686
+
687
+ done()
688
+ }))
689
+
690
+ fastify.get('/', function (req, reply) {
691
+ reply.send({ hello: 'world' })
692
+ })
693
+
694
+ fastify.inject({
695
+ method: 'POST',
696
+ url: '/',
697
+ payload: { hello: 'world' }
698
+ }, (err, res) => {
699
+ t.assert.ifError(err)
700
+ t.assert.strictEqual(res.statusCode, 404)
701
+ done()
702
+ })
703
+ })
704
+
705
+ test('run non-encapsulated plugin hooks on custom 404', (t, done) => {
706
+ t.plan(11)
707
+
708
+ const fastify = Fastify()
709
+
710
+ const plugin = fp((instance, opts, done) => {
711
+ instance.addHook('onRequest', function (req, res, done) {
712
+ t.assert.ok(true, 'onRequest called')
713
+ done()
714
+ })
715
+
716
+ instance.addHook('preHandler', function (request, reply, done) {
717
+ t.assert.ok(true, 'preHandler called')
718
+ done()
719
+ })
720
+
721
+ instance.addHook('onSend', function (request, reply, payload, done) {
722
+ t.assert.ok(true, 'onSend called')
723
+ done()
724
+ })
725
+
726
+ instance.addHook('onResponse', function (request, reply, done) {
727
+ t.assert.ok(true, 'onResponse called')
728
+ done()
729
+ })
730
+
731
+ done()
732
+ })
733
+
734
+ fastify.register(plugin)
735
+
736
+ fastify.get('/', function (req, reply) {
737
+ reply.send({ hello: 'world' })
738
+ })
739
+
740
+ fastify.setNotFoundHandler(function (req, reply) {
741
+ reply.code(404).send('this was not found')
742
+ })
743
+
744
+ fastify.register(plugin) // Registering plugin after handler also works
745
+
746
+ fastify.inject({ url: '/not-found' }, (err, res) => {
747
+ t.assert.ifError(err)
748
+ t.assert.strictEqual(res.statusCode, 404)
749
+ t.assert.strictEqual(res.payload, 'this was not found')
750
+ done()
751
+ })
752
+ })
753
+
754
+ test('run hook with encapsulated 404', async t => {
755
+ t.plan(10)
756
+
757
+ const fastify = Fastify()
758
+
759
+ fastify.addHook('onRequest', function (req, res, done) {
760
+ t.assert.ok(true, 'onRequest called')
761
+ done()
762
+ })
763
+
764
+ fastify.addHook('preHandler', function (request, reply, done) {
765
+ t.assert.ok(true, 'preHandler called')
766
+ done()
767
+ })
768
+
769
+ fastify.addHook('onSend', function (request, reply, payload, done) {
770
+ t.assert.ok(true, 'onSend called')
771
+ done()
772
+ })
773
+
774
+ fastify.addHook('onResponse', function (request, reply, done) {
775
+ t.assert.ok(true, 'onResponse called')
776
+ done()
777
+ })
778
+
779
+ fastify.register(function (f, opts, done) {
780
+ f.setNotFoundHandler(function (req, reply) {
781
+ reply.code(404).send('this was not found 2')
782
+ })
783
+
784
+ f.addHook('onRequest', function (req, res, done) {
785
+ t.assert.ok(true, 'onRequest 2 called')
786
+ done()
787
+ })
788
+
789
+ f.addHook('preHandler', function (request, reply, done) {
790
+ t.assert.ok(true, 'preHandler 2 called')
791
+ done()
792
+ })
793
+
794
+ f.addHook('onSend', function (request, reply, payload, done) {
795
+ t.assert.ok(true, 'onSend 2 called')
796
+ done()
797
+ })
798
+
799
+ f.addHook('onResponse', function (request, reply, done) {
800
+ t.assert.ok(true, 'onResponse 2 called')
801
+ done()
802
+ })
803
+
804
+ done()
805
+ }, { prefix: '/test' })
806
+
807
+ t.after(() => { fastify.close() })
808
+
809
+ await fastify.listen({ port: 0 })
810
+
811
+ const response = await fetch(getServerUrl(fastify) + '/test', {
812
+ method: 'PUT',
813
+ body: JSON.stringify({ hello: 'world' }),
814
+ headers: { 'Content-Type': 'application/json' }
815
+ })
816
+
817
+ t.assert.ok(!response.ok)
818
+ t.assert.strictEqual(response.status, 404)
819
+ })
820
+
821
+ test('run hook with encapsulated 404 and framework-unsupported method', async t => {
822
+ t.plan(10)
823
+
824
+ const fastify = Fastify()
825
+
826
+ fastify.addHook('onRequest', function (req, res, done) {
827
+ t.assert.ok(true, 'onRequest called')
828
+ done()
829
+ })
830
+
831
+ fastify.addHook('preHandler', function (request, reply, done) {
832
+ t.assert.ok(true, 'preHandler called')
833
+ done()
834
+ })
835
+
836
+ fastify.addHook('onSend', function (request, reply, payload, done) {
837
+ t.assert.ok(true, 'onSend called')
838
+ done()
839
+ })
840
+
841
+ fastify.addHook('onResponse', function (request, reply, done) {
842
+ t.assert.ok(true, 'onResponse called')
843
+ done()
844
+ })
845
+
846
+ fastify.register(function (f, opts, done) {
847
+ f.setNotFoundHandler(function (req, reply) {
848
+ reply.code(404).send('this was not found 2')
849
+ })
850
+
851
+ f.addHook('onRequest', function (req, res, done) {
852
+ t.assert.ok(true, 'onRequest 2 called')
853
+ done()
854
+ })
855
+
856
+ f.addHook('preHandler', function (request, reply, done) {
857
+ t.assert.ok(true, 'preHandler 2 called')
858
+ done()
859
+ })
860
+
861
+ f.addHook('onSend', function (request, reply, payload, done) {
862
+ t.assert.ok(true, 'onSend 2 called')
863
+ done()
864
+ })
865
+
866
+ f.addHook('onResponse', function (request, reply, done) {
867
+ t.assert.ok(true, 'onResponse 2 called')
868
+ done()
869
+ })
870
+
871
+ done()
872
+ }, { prefix: '/test' })
873
+
874
+ t.after(() => { fastify.close() })
875
+
876
+ await fastify.listen({ port: 0 })
877
+
878
+ const response = await fetch(getServerUrl(fastify) + '/test', {
879
+ method: 'PROPFIND',
880
+ body: JSON.stringify({ hello: 'world' }),
881
+ headers: { 'Content-Type': 'application/json' }
882
+ })
883
+
884
+ t.assert.ok(!response.ok)
885
+ t.assert.strictEqual(response.status, 404)
886
+ })
887
+
888
+ test('hooks check 404', async t => {
889
+ t.plan(12)
890
+
891
+ const fastify = Fastify()
892
+
893
+ fastify.get('/', function (req, reply) {
894
+ reply.send({ hello: 'world' })
895
+ })
896
+
897
+ fastify.addHook('onSend', (req, reply, payload, done) => {
898
+ t.assert.deepStrictEqual(req.query, { foo: 'asd' })
899
+ t.assert.ok(true, 'called onSend')
900
+ done()
901
+ })
902
+ fastify.addHook('onRequest', (req, res, done) => {
903
+ t.assert.ok(true, 'called onRequest')
904
+ done()
905
+ })
906
+ fastify.addHook('onResponse', (request, reply, done) => {
907
+ t.assert.ok(true, 'calledonResponse')
908
+ done()
909
+ })
910
+
911
+ t.after(() => { fastify.close() })
912
+
913
+ await fastify.listen({ port: 0 })
914
+
915
+ const response1 = await fetch(getServerUrl(fastify) + '?foo=asd', {
916
+ method: 'PUT',
917
+ body: JSON.stringify({ hello: 'world' }),
918
+ headers: { 'Content-Type': 'application/json' }
919
+ })
920
+
921
+ t.assert.ok(!response1.ok)
922
+ t.assert.strictEqual(response1.status, 404)
923
+
924
+ const response2 = await fetch(getServerUrl(fastify) + '/notSupported?foo=asd')
925
+
926
+ t.assert.ok(!response2.ok)
927
+ t.assert.strictEqual(response2.status, 404)
928
+ })
929
+
930
+ test('setNotFoundHandler should not suppress duplicated routes checking', t => {
931
+ t.plan(1)
932
+
933
+ const fastify = Fastify()
934
+
935
+ try {
936
+ fastify.get('/', function (req, reply) {
937
+ reply.send({ hello: 'world' })
938
+ })
939
+ fastify.get('/', function (req, reply) {
940
+ reply.send({ hello: 'world' })
941
+ })
942
+ fastify.setNotFoundHandler(function (req, reply) {
943
+ reply.code(404).send('this was not found')
944
+ })
945
+
946
+ t.assert.fail('setNotFoundHandler should not interfere duplicated route error')
947
+ } catch (error) {
948
+ t.assert.ok(error)
949
+ }
950
+ })
951
+
952
+ test('log debug for 404', async t => {
953
+ t.plan(1)
954
+
955
+ const Writable = require('node:stream').Writable
956
+
957
+ const logStream = new Writable()
958
+ logStream.logs = []
959
+ logStream._write = function (chunk, encoding, callback) {
960
+ this.logs.push(chunk.toString())
961
+ callback()
962
+ }
963
+
964
+ const fastify = Fastify({
965
+ logger: {
966
+ level: 'trace',
967
+ stream: logStream
968
+ }
969
+ })
970
+
971
+ fastify.get('/', function (req, reply) {
972
+ reply.send({ hello: 'world' })
973
+ })
974
+
975
+ t.after(() => { fastify.close() })
976
+
977
+ await t.test('log debug', (t, done) => {
978
+ t.plan(7)
979
+ fastify.inject({
980
+ method: 'GET',
981
+ url: '/not-found'
982
+ }, (err, response) => {
983
+ t.assert.ifError(err)
984
+ t.assert.strictEqual(response.statusCode, 404)
985
+
986
+ const INFO_LEVEL = 30
987
+ t.assert.strictEqual(JSON.parse(logStream.logs[0]).msg, 'incoming request')
988
+ t.assert.strictEqual(JSON.parse(logStream.logs[1]).msg, 'Route GET:/not-found not found')
989
+ t.assert.strictEqual(JSON.parse(logStream.logs[1]).level, INFO_LEVEL)
990
+ t.assert.strictEqual(JSON.parse(logStream.logs[2]).msg, 'request completed')
991
+ t.assert.strictEqual(logStream.logs.length, 3)
992
+ done()
993
+ })
994
+ })
995
+ })
996
+
997
+ test('Unknown method', async t => {
998
+ t.plan(4)
999
+
1000
+ const fastify = Fastify()
1001
+
1002
+ fastify.get('/', function (req, reply) {
1003
+ reply.send({ hello: 'world' })
1004
+ })
1005
+
1006
+ t.after(() => { fastify.close() })
1007
+
1008
+ await fastify.listen({ port: 0 })
1009
+
1010
+ const handler = () => {}
1011
+ // See https://github.com/fastify/light-my-request/pull/20
1012
+ t.assert.throws(() => fastify.inject({
1013
+ method: 'UNKNOWN_METHOD',
1014
+ url: '/'
1015
+ }, handler), Error)
1016
+
1017
+ const response = await fetch(getServerUrl(fastify), {
1018
+ method: 'UNKNOWN_METHOD'
1019
+ })
1020
+
1021
+ t.assert.ok(!response.ok)
1022
+ t.assert.strictEqual(response.status, 400)
1023
+
1024
+ t.assert.deepStrictEqual(await response.json(), {
1025
+ error: 'Bad Request',
1026
+ message: 'Client Error',
1027
+ statusCode: 400
1028
+ })
1029
+ })
1030
+
1031
+ test('recognizes errors from the http-errors module', async t => {
1032
+ t.plan(4)
1033
+
1034
+ const fastify = Fastify()
1035
+
1036
+ fastify.get('/', function (req, reply) {
1037
+ reply.send(new errors.NotFound())
1038
+ })
1039
+
1040
+ t.after(() => { fastify.close() })
1041
+
1042
+ await fastify.listen({ port: 0 })
1043
+
1044
+ fastify.inject({
1045
+ method: 'GET',
1046
+ url: '/'
1047
+ }, (err, res) => {
1048
+ t.assert.ifError(err)
1049
+ t.assert.strictEqual(res.statusCode, 404)
1050
+ })
1051
+
1052
+ const response = await fetch(getServerUrl(fastify))
1053
+
1054
+ t.assert.ok(!response.ok)
1055
+ t.assert.deepStrictEqual(await response.json(), {
1056
+ error: 'Not Found',
1057
+ message: 'Not Found',
1058
+ statusCode: 404
1059
+ })
1060
+ })
1061
+
1062
+ test('the default 404 handler can be invoked inside a prefixed plugin', (t, done) => {
1063
+ t.plan(3)
1064
+
1065
+ const fastify = Fastify()
1066
+
1067
+ fastify.register(function (instance, opts, done) {
1068
+ instance.get('/path', function (request, reply) {
1069
+ reply.send(new errors.NotFound())
1070
+ })
1071
+
1072
+ done()
1073
+ }, { prefix: '/v1' })
1074
+
1075
+ fastify.inject('/v1/path', (err, res) => {
1076
+ t.assert.ifError(err)
1077
+ t.assert.strictEqual(res.statusCode, 404)
1078
+ t.assert.deepStrictEqual(JSON.parse(res.payload), {
1079
+ error: 'Not Found',
1080
+ message: 'Not Found',
1081
+ statusCode: 404
1082
+ })
1083
+ done()
1084
+ })
1085
+ })
1086
+
1087
+ test('an inherited custom 404 handler can be invoked inside a prefixed plugin', (t, done) => {
1088
+ t.plan(3)
1089
+
1090
+ const fastify = Fastify()
1091
+
1092
+ fastify.setNotFoundHandler(function (request, reply) {
1093
+ reply.code(404).send('custom handler')
1094
+ })
1095
+
1096
+ fastify.register(function (instance, opts, done) {
1097
+ instance.get('/path', function (request, reply) {
1098
+ reply.send(new errors.NotFound())
1099
+ })
1100
+
1101
+ done()
1102
+ }, { prefix: '/v1' })
1103
+
1104
+ fastify.inject('/v1/path', (err, res) => {
1105
+ t.assert.ifError(err)
1106
+ t.assert.strictEqual(res.statusCode, 404)
1107
+ t.assert.deepStrictEqual(JSON.parse(res.payload), {
1108
+ error: 'Not Found',
1109
+ message: 'Not Found',
1110
+ statusCode: 404
1111
+ })
1112
+ done()
1113
+ })
1114
+ })
1115
+
1116
+ test('encapsulated custom 404 handler without a prefix is the handler for the entire 404 level', async t => {
1117
+ t.plan(4)
1118
+
1119
+ const fastify = Fastify()
1120
+
1121
+ fastify.register(function (instance, opts, done) {
1122
+ instance.setNotFoundHandler(function (request, reply) {
1123
+ reply.code(404).send('custom handler')
1124
+ })
1125
+
1126
+ done()
1127
+ })
1128
+
1129
+ fastify.register(function (instance, opts, done) {
1130
+ instance.register(function (instance2, opts, done) {
1131
+ instance2.setNotFoundHandler(function (request, reply) {
1132
+ reply.code(404).send('custom handler 2')
1133
+ })
1134
+ done()
1135
+ })
1136
+
1137
+ done()
1138
+ }, { prefix: 'prefixed' })
1139
+
1140
+ {
1141
+ const res = await fastify.inject('/not-found')
1142
+ t.assert.strictEqual(res.statusCode, 404)
1143
+ t.assert.strictEqual(res.payload, 'custom handler')
1144
+ }
1145
+
1146
+ {
1147
+ const res = await fastify.inject('/prefixed/not-found')
1148
+ t.assert.strictEqual(res.statusCode, 404)
1149
+ t.assert.strictEqual(res.payload, 'custom handler 2')
1150
+ }
1151
+ })
1152
+
1153
+ test('cannot set notFoundHandler after binding', (t, done) => {
1154
+ t.plan(2)
1155
+
1156
+ const fastify = Fastify()
1157
+ t.after(() => { fastify.close() })
1158
+
1159
+ fastify.listen({ port: 0 }, err => {
1160
+ t.assert.ifError(err)
1161
+
1162
+ try {
1163
+ fastify.setNotFoundHandler(() => { })
1164
+ t.assert.fail()
1165
+ } catch (e) {
1166
+ t.assert.ok(true)
1167
+ done()
1168
+ }
1169
+ })
1170
+ })
1171
+
1172
+ test('404 inside onSend', async t => {
1173
+ t.plan(2)
1174
+
1175
+ const fastify = Fastify()
1176
+
1177
+ let called = false
1178
+
1179
+ fastify.get('/', function (req, reply) {
1180
+ reply.send({ hello: 'world' })
1181
+ })
1182
+
1183
+ fastify.addHook('onSend', function (request, reply, payload, done) {
1184
+ if (!called) {
1185
+ called = true
1186
+ done(new errors.NotFound())
1187
+ } else {
1188
+ done()
1189
+ }
1190
+ })
1191
+
1192
+ t.after(() => { fastify.close() })
1193
+
1194
+ await fastify.listen({ port: 0 })
1195
+
1196
+ const response = await fetch(getServerUrl(fastify))
1197
+
1198
+ t.assert.ok(!response.ok)
1199
+ t.assert.strictEqual(response.status, 404)
1200
+ })
1201
+
1202
+ // https://github.com/fastify/fastify/issues/868
1203
+ test('onSend hooks run when an encapsulated route invokes the notFound handler', (t, done) => {
1204
+ t.plan(3)
1205
+ const fastify = Fastify()
1206
+
1207
+ fastify.register((instance, options, done) => {
1208
+ instance.addHook('onSend', (request, reply, payload, done) => {
1209
+ t.assert.ok(true, 'onSend hook called')
1210
+ done()
1211
+ })
1212
+
1213
+ instance.get('/', (request, reply) => {
1214
+ reply.send(new errors.NotFound())
1215
+ })
1216
+
1217
+ done()
1218
+ })
1219
+
1220
+ fastify.inject('/', (err, res) => {
1221
+ t.assert.ifError(err)
1222
+ t.assert.strictEqual(res.statusCode, 404)
1223
+ done()
1224
+ })
1225
+ })
1226
+
1227
+ // https://github.com/fastify/fastify/issues/713
1228
+ test('preHandler option for setNotFoundHandler', async t => {
1229
+ t.plan(10)
1230
+
1231
+ await t.test('preHandler option', (t, done) => {
1232
+ t.plan(2)
1233
+ const fastify = Fastify()
1234
+
1235
+ fastify.setNotFoundHandler({
1236
+ preHandler: (req, reply, done) => {
1237
+ req.body.preHandler = true
1238
+ done()
1239
+ }
1240
+ }, function (req, reply) {
1241
+ reply.code(404).send(req.body)
1242
+ })
1243
+
1244
+ fastify.inject({
1245
+ method: 'POST',
1246
+ url: '/not-found',
1247
+ payload: { hello: 'world' }
1248
+ }, (err, res) => {
1249
+ t.assert.ifError(err)
1250
+ const payload = JSON.parse(res.payload)
1251
+ t.assert.deepStrictEqual(payload, { preHandler: true, hello: 'world' })
1252
+ done()
1253
+ })
1254
+ })
1255
+
1256
+ // https://github.com/fastify/fastify/issues/2229
1257
+ await t.test('preHandler hook in setNotFoundHandler should be called when callNotFound', { timeout: 40000 }, (t, done) => {
1258
+ t.plan(3)
1259
+ const fastify = Fastify()
1260
+
1261
+ fastify.setNotFoundHandler({
1262
+ preHandler: (req, reply, done) => {
1263
+ req.body.preHandler = true
1264
+ done()
1265
+ }
1266
+ }, function (req, reply) {
1267
+ reply.code(404).send(req.body)
1268
+ })
1269
+
1270
+ fastify.post('/', function (req, reply) {
1271
+ t.assert.strictEqual(reply.callNotFound(), reply)
1272
+ })
1273
+
1274
+ fastify.inject({
1275
+ method: 'POST',
1276
+ url: '/',
1277
+ payload: { hello: 'world' }
1278
+ }, (err, res) => {
1279
+ t.assert.ifError(err)
1280
+ const payload = JSON.parse(res.payload)
1281
+ t.assert.deepStrictEqual(payload, { preHandler: true, hello: 'world' })
1282
+ done()
1283
+ })
1284
+ })
1285
+
1286
+ await t.test('preHandler hook in setNotFoundHandler should accept an array of functions and be called when callNotFound', (t, done) => {
1287
+ t.plan(2)
1288
+ const fastify = Fastify()
1289
+
1290
+ fastify.setNotFoundHandler({
1291
+ preHandler: [
1292
+ (req, reply, done) => {
1293
+ req.body.preHandler1 = true
1294
+ done()
1295
+ },
1296
+ (req, reply, done) => {
1297
+ req.body.preHandler2 = true
1298
+ done()
1299
+ }
1300
+ ]
1301
+ }, function (req, reply) {
1302
+ reply.code(404).send(req.body)
1303
+ })
1304
+
1305
+ fastify.post('/', function (req, reply) {
1306
+ reply.callNotFound()
1307
+ })
1308
+
1309
+ fastify.inject({
1310
+ method: 'POST',
1311
+ url: '/',
1312
+ payload: { hello: 'world' }
1313
+ }, (err, res) => {
1314
+ t.assert.ifError(err)
1315
+ const payload = JSON.parse(res.payload)
1316
+ t.assert.deepStrictEqual(payload, { preHandler1: true, preHandler2: true, hello: 'world' })
1317
+ done()
1318
+ })
1319
+ })
1320
+
1321
+ await t.test('preHandler option should be called after preHandler hook', (t, done) => {
1322
+ t.plan(2)
1323
+ const fastify = Fastify()
1324
+
1325
+ fastify.addHook('preHandler', (req, reply, done) => {
1326
+ req.body.check = 'a'
1327
+ done()
1328
+ })
1329
+
1330
+ fastify.setNotFoundHandler({
1331
+ preHandler: (req, reply, done) => {
1332
+ req.body.check += 'b'
1333
+ done()
1334
+ }
1335
+ }, (req, reply) => {
1336
+ reply.send(req.body)
1337
+ })
1338
+
1339
+ fastify.inject({
1340
+ method: 'POST',
1341
+ url: '/',
1342
+ payload: { hello: 'world' }
1343
+ }, (err, res) => {
1344
+ t.assert.ifError(err)
1345
+ const payload = JSON.parse(res.payload)
1346
+ t.assert.deepStrictEqual(payload, { check: 'ab', hello: 'world' })
1347
+ done()
1348
+ })
1349
+ })
1350
+
1351
+ await t.test('preHandler option should be unique per prefix', async t => {
1352
+ t.plan(2)
1353
+ const fastify = Fastify()
1354
+
1355
+ fastify.setNotFoundHandler({
1356
+ preHandler: (req, reply, done) => {
1357
+ req.body.hello = 'earth'
1358
+ done()
1359
+ }
1360
+ }, (req, reply) => {
1361
+ reply.send(req.body)
1362
+ })
1363
+
1364
+ fastify.register(function (i, o, n) {
1365
+ i.setNotFoundHandler((req, reply) => {
1366
+ reply.send(req.body)
1367
+ })
1368
+
1369
+ n()
1370
+ }, { prefix: '/no' })
1371
+
1372
+ {
1373
+ const res = await fastify.inject({
1374
+ method: 'POST',
1375
+ url: '/not-found',
1376
+ payload: { hello: 'world' }
1377
+ })
1378
+
1379
+ const payload = JSON.parse(res.payload)
1380
+ t.assert.deepStrictEqual(payload, { hello: 'earth' })
1381
+ }
1382
+
1383
+ {
1384
+ const res = await fastify.inject({
1385
+ method: 'POST',
1386
+ url: '/no/not-found',
1387
+ payload: { hello: 'world' }
1388
+ })
1389
+
1390
+ const payload = JSON.parse(res.payload)
1391
+ t.assert.deepStrictEqual(payload, { hello: 'world' })
1392
+ }
1393
+ })
1394
+
1395
+ await t.test('preHandler option should handle errors', (t, done) => {
1396
+ t.plan(3)
1397
+ const fastify = Fastify()
1398
+
1399
+ fastify.setNotFoundHandler({
1400
+ preHandler: (req, reply, done) => {
1401
+ done(new Error('kaboom'))
1402
+ }
1403
+ }, (req, reply) => {
1404
+ reply.send(req.body)
1405
+ })
1406
+
1407
+ fastify.inject({
1408
+ method: 'POST',
1409
+ url: '/not-found',
1410
+ payload: { hello: 'world' }
1411
+ }, (err, res) => {
1412
+ t.assert.ifError(err)
1413
+ const payload = JSON.parse(res.payload)
1414
+ t.assert.strictEqual(res.statusCode, 500)
1415
+ t.assert.deepStrictEqual(payload, {
1416
+ message: 'kaboom',
1417
+ error: 'Internal Server Error',
1418
+ statusCode: 500
1419
+ })
1420
+ done()
1421
+ })
1422
+ })
1423
+
1424
+ await t.test('preHandler option should handle errors with custom status code', (t, done) => {
1425
+ t.plan(3)
1426
+ const fastify = Fastify()
1427
+
1428
+ fastify.setNotFoundHandler({
1429
+ preHandler: (req, reply, done) => {
1430
+ reply.code(401)
1431
+ done(new Error('go away'))
1432
+ }
1433
+ }, (req, reply) => {
1434
+ reply.send(req.body)
1435
+ })
1436
+
1437
+ fastify.inject({
1438
+ method: 'POST',
1439
+ url: '/not-found',
1440
+ payload: { hello: 'world' }
1441
+ }, (err, res) => {
1442
+ t.assert.ifError(err)
1443
+ const payload = JSON.parse(res.payload)
1444
+ t.assert.strictEqual(res.statusCode, 401)
1445
+ t.assert.deepStrictEqual(payload, {
1446
+ message: 'go away',
1447
+ error: 'Unauthorized',
1448
+ statusCode: 401
1449
+ })
1450
+ done()
1451
+ })
1452
+ })
1453
+
1454
+ await t.test('preHandler option could accept an array of functions', (t, done) => {
1455
+ t.plan(2)
1456
+ const fastify = Fastify()
1457
+
1458
+ fastify.setNotFoundHandler({
1459
+ preHandler: [
1460
+ (req, reply, done) => {
1461
+ req.body.preHandler = 'a'
1462
+ done()
1463
+ },
1464
+ (req, reply, done) => {
1465
+ req.body.preHandler += 'b'
1466
+ done()
1467
+ }
1468
+ ]
1469
+ }, (req, reply) => {
1470
+ reply.send(req.body)
1471
+ })
1472
+
1473
+ fastify.inject({
1474
+ method: 'POST',
1475
+ url: '/not-found',
1476
+ payload: { hello: 'world' }
1477
+ }, (err, res) => {
1478
+ t.assert.ifError(err)
1479
+ const payload = JSON.parse(res.payload)
1480
+ t.assert.deepStrictEqual(payload, { preHandler: 'ab', hello: 'world' })
1481
+ done()
1482
+ })
1483
+ })
1484
+
1485
+ await t.test('preHandler option does not interfere with preHandler', async t => {
1486
+ t.plan(2)
1487
+ const fastify = Fastify()
1488
+
1489
+ fastify.addHook('preHandler', (req, reply, done) => {
1490
+ req.body.check = 'a'
1491
+ done()
1492
+ })
1493
+
1494
+ fastify.setNotFoundHandler({
1495
+ preHandler: (req, reply, done) => {
1496
+ req.body.check += 'b'
1497
+ done()
1498
+ }
1499
+ }, (req, reply) => {
1500
+ reply.send(req.body)
1501
+ })
1502
+
1503
+ fastify.register(function (i, o, n) {
1504
+ i.setNotFoundHandler((req, reply) => {
1505
+ reply.send(req.body)
1506
+ })
1507
+
1508
+ n()
1509
+ }, { prefix: '/no' })
1510
+
1511
+ {
1512
+ const res = await fastify.inject({
1513
+ method: 'post',
1514
+ url: '/not-found',
1515
+ payload: { hello: 'world' }
1516
+ })
1517
+
1518
+ const payload = JSON.parse(res.payload)
1519
+ t.assert.deepStrictEqual(payload, { check: 'ab', hello: 'world' })
1520
+ }
1521
+
1522
+ {
1523
+ const res = await fastify.inject({
1524
+ method: 'post',
1525
+ url: '/no/not-found',
1526
+ payload: { hello: 'world' }
1527
+ })
1528
+
1529
+ const payload = JSON.parse(res.payload)
1530
+ t.assert.deepStrictEqual(payload, { check: 'a', hello: 'world' })
1531
+ }
1532
+ })
1533
+
1534
+ await t.test('preHandler option should keep the context', (t, done) => {
1535
+ t.plan(3)
1536
+ const fastify = Fastify()
1537
+
1538
+ fastify.decorate('foo', 42)
1539
+
1540
+ fastify.setNotFoundHandler({
1541
+ preHandler: function (req, reply, done) {
1542
+ t.assert.strictEqual(this.foo, 42)
1543
+ this.foo += 1
1544
+ req.body.foo = this.foo
1545
+ done()
1546
+ }
1547
+ }, (req, reply) => {
1548
+ reply.send(req.body)
1549
+ })
1550
+
1551
+ fastify.inject({
1552
+ method: 'POST',
1553
+ url: '/not-found',
1554
+ payload: { hello: 'world' }
1555
+ }, (err, res) => {
1556
+ t.assert.ifError(err)
1557
+ const payload = JSON.parse(res.payload)
1558
+ t.assert.deepStrictEqual(payload, { foo: 43, hello: 'world' })
1559
+ done()
1560
+ })
1561
+ })
1562
+ })
1563
+
1564
+ test('reply.notFound invoked the notFound handler', (t, done) => {
1565
+ t.plan(3)
1566
+
1567
+ const fastify = Fastify()
1568
+
1569
+ fastify.setNotFoundHandler((req, reply) => {
1570
+ reply.code(404).send(new Error('kaboom'))
1571
+ })
1572
+
1573
+ fastify.get('/', function (req, reply) {
1574
+ reply.callNotFound()
1575
+ })
1576
+
1577
+ fastify.inject({
1578
+ url: '/',
1579
+ method: 'GET'
1580
+ }, (err, res) => {
1581
+ t.assert.ifError(err)
1582
+ t.assert.strictEqual(res.statusCode, 404)
1583
+ t.assert.deepStrictEqual(JSON.parse(res.payload), {
1584
+ error: 'Not Found',
1585
+ message: 'kaboom',
1586
+ statusCode: 404
1587
+ })
1588
+ done()
1589
+ })
1590
+ })
1591
+
1592
+ test('The custom error handler should be invoked after the custom not found handler', (t, done) => {
1593
+ t.plan(6)
1594
+
1595
+ const fastify = Fastify()
1596
+ const order = [1, 2]
1597
+
1598
+ fastify.setErrorHandler((err, req, reply) => {
1599
+ t.assert.strictEqual(order.shift(), 2)
1600
+ t.assert.ok(err instanceof Error)
1601
+ reply.send(err)
1602
+ })
1603
+
1604
+ fastify.setNotFoundHandler((req, reply) => {
1605
+ t.assert.strictEqual(order.shift(), 1)
1606
+ reply.code(404).send(new Error('kaboom'))
1607
+ })
1608
+
1609
+ fastify.get('/', function (req, reply) {
1610
+ reply.callNotFound()
1611
+ })
1612
+
1613
+ fastify.inject({
1614
+ url: '/',
1615
+ method: 'GET'
1616
+ }, (err, res) => {
1617
+ t.assert.ifError(err)
1618
+ t.assert.strictEqual(res.statusCode, 404)
1619
+ t.assert.deepStrictEqual(JSON.parse(res.payload), {
1620
+ error: 'Not Found',
1621
+ message: 'kaboom',
1622
+ statusCode: 404
1623
+ })
1624
+ done()
1625
+ })
1626
+ })
1627
+
1628
+ test('If the custom not found handler does not use an Error, the custom error handler should not be called', (t, done) => {
1629
+ t.plan(3)
1630
+
1631
+ const fastify = Fastify()
1632
+
1633
+ fastify.setErrorHandler((_err, req, reply) => {
1634
+ t.assert.fail('Should not be called')
1635
+ })
1636
+
1637
+ fastify.setNotFoundHandler((req, reply) => {
1638
+ reply.code(404).send('kaboom')
1639
+ })
1640
+
1641
+ fastify.get('/', function (req, reply) {
1642
+ reply.callNotFound()
1643
+ })
1644
+
1645
+ fastify.inject({
1646
+ url: '/',
1647
+ method: 'GET'
1648
+ }, (err, res) => {
1649
+ t.assert.ifError(err)
1650
+ t.assert.strictEqual(res.statusCode, 404)
1651
+ t.assert.strictEqual(res.payload, 'kaboom')
1652
+ done()
1653
+ })
1654
+ })
1655
+
1656
+ test('preValidation option', (t, done) => {
1657
+ t.plan(3)
1658
+ const fastify = Fastify()
1659
+
1660
+ fastify.decorate('foo', true)
1661
+
1662
+ fastify.setNotFoundHandler({
1663
+ preValidation: function (req, reply, done) {
1664
+ t.assert.ok(this.foo)
1665
+ done()
1666
+ }
1667
+ }, function (req, reply) {
1668
+ reply.code(404).send(req.body)
1669
+ })
1670
+
1671
+ fastify.inject({
1672
+ method: 'POST',
1673
+ url: '/not-found',
1674
+ payload: { hello: 'world' }
1675
+ }, (err, res) => {
1676
+ t.assert.ifError(err)
1677
+ const payload = JSON.parse(res.payload)
1678
+ t.assert.deepStrictEqual(payload, { hello: 'world' })
1679
+ done()
1680
+ })
1681
+ })
1682
+
1683
+ test('preValidation option could accept an array of functions', (t, done) => {
1684
+ t.plan(4)
1685
+ const fastify = Fastify()
1686
+
1687
+ fastify.setNotFoundHandler({
1688
+ preValidation: [
1689
+ (req, reply, done) => {
1690
+ t.assert.ok('called')
1691
+ done()
1692
+ },
1693
+ (req, reply, done) => {
1694
+ t.assert.ok('called')
1695
+ done()
1696
+ }
1697
+ ]
1698
+ }, (req, reply) => {
1699
+ reply.send(req.body)
1700
+ })
1701
+
1702
+ fastify.inject({
1703
+ method: 'POST',
1704
+ url: '/not-found',
1705
+ payload: { hello: 'world' }
1706
+ }, (err, res) => {
1707
+ t.assert.ifError(err)
1708
+ const payload = JSON.parse(res.payload)
1709
+ t.assert.deepStrictEqual(payload, { hello: 'world' })
1710
+ done()
1711
+ })
1712
+ })
1713
+
1714
+ test('Should fail to invoke callNotFound inside a 404 handler', (t, done) => {
1715
+ t.plan(5)
1716
+
1717
+ let fastify = null
1718
+ const logStream = split(JSON.parse)
1719
+ try {
1720
+ fastify = Fastify({
1721
+ logger: {
1722
+ stream: logStream,
1723
+ level: 'warn'
1724
+ }
1725
+ })
1726
+ } catch (e) {
1727
+ t.assert.fail()
1728
+ }
1729
+
1730
+ fastify.setNotFoundHandler((req, reply) => {
1731
+ reply.callNotFound()
1732
+ })
1733
+
1734
+ fastify.get('/', function (req, reply) {
1735
+ reply.callNotFound()
1736
+ })
1737
+
1738
+ logStream.once('data', line => {
1739
+ t.assert.strictEqual(line.msg, 'Trying to send a NotFound error inside a 404 handler. Sending basic 404 response.')
1740
+ t.assert.strictEqual(line.level, 40)
1741
+ })
1742
+
1743
+ fastify.inject({
1744
+ url: '/',
1745
+ method: 'GET'
1746
+ }, (err, res) => {
1747
+ t.assert.ifError(err)
1748
+ t.assert.strictEqual(res.statusCode, 404)
1749
+ t.assert.strictEqual(res.payload, '404 Not Found')
1750
+ done()
1751
+ })
1752
+ })
1753
+
1754
+ test('400 in case of bad url (pre find-my-way v2.2.0 was a 404)', async t => {
1755
+ await t.test('Dynamic route', (t, done) => {
1756
+ t.plan(3)
1757
+ const fastify = Fastify()
1758
+ fastify.get('/hello/:id', () => t.assert.fail('we should not be here'))
1759
+ fastify.inject({
1760
+ url: '/hello/%world',
1761
+ method: 'GET'
1762
+ }, (err, response) => {
1763
+ t.assert.ifError(err)
1764
+ t.assert.strictEqual(response.statusCode, 400)
1765
+ t.assert.deepStrictEqual(JSON.parse(response.payload), {
1766
+ error: 'Bad Request',
1767
+ message: "'/hello/%world' is not a valid url component",
1768
+ statusCode: 400,
1769
+ code: 'FST_ERR_BAD_URL'
1770
+ })
1771
+ done()
1772
+ })
1773
+ })
1774
+
1775
+ await t.test('Wildcard', (t, done) => {
1776
+ t.plan(3)
1777
+ const fastify = Fastify()
1778
+ fastify.get('*', () => t.assert.fail('we should not be here'))
1779
+ fastify.inject({
1780
+ url: '/hello/%world',
1781
+ method: 'GET'
1782
+ }, (err, response) => {
1783
+ t.assert.ifError(err)
1784
+ t.assert.strictEqual(response.statusCode, 400)
1785
+ t.assert.deepStrictEqual(JSON.parse(response.payload), {
1786
+ error: 'Bad Request',
1787
+ message: "'/hello/%world' is not a valid url component",
1788
+ statusCode: 400,
1789
+ code: 'FST_ERR_BAD_URL'
1790
+ })
1791
+ done()
1792
+ })
1793
+ })
1794
+
1795
+ await t.test('No route registered', (t, done) => {
1796
+ t.plan(3)
1797
+ const fastify = Fastify()
1798
+ fastify.inject({
1799
+ url: '/%c0',
1800
+ method: 'GET'
1801
+ }, (err, response) => {
1802
+ t.assert.ifError(err)
1803
+ t.assert.strictEqual(response.statusCode, 404)
1804
+ t.assert.deepStrictEqual(JSON.parse(response.payload), {
1805
+ error: 'Not Found',
1806
+ message: 'Route GET:/%c0 not found',
1807
+ statusCode: 404
1808
+ })
1809
+ done()
1810
+ })
1811
+ })
1812
+
1813
+ await t.test('Only / is registered', (t, done) => {
1814
+ t.plan(3)
1815
+ const fastify = Fastify()
1816
+ fastify.get('/', () => t.assert.fail('we should not be here'))
1817
+ fastify.inject({
1818
+ url: '/non-existing',
1819
+ method: 'GET'
1820
+ }, (err, response) => {
1821
+ t.assert.ifError(err)
1822
+ t.assert.strictEqual(response.statusCode, 404)
1823
+ t.assert.deepStrictEqual(JSON.parse(response.payload), {
1824
+ error: 'Not Found',
1825
+ message: 'Route GET:/non-existing not found',
1826
+ statusCode: 404
1827
+ })
1828
+ done()
1829
+ })
1830
+ })
1831
+
1832
+ await t.test('customized 404', (t, done) => {
1833
+ t.plan(3)
1834
+ const fastify = Fastify({ logger: true })
1835
+ fastify.setNotFoundHandler(function (req, reply) {
1836
+ reply.code(404).send('this was not found')
1837
+ })
1838
+ fastify.inject({
1839
+ url: '/%c0',
1840
+ method: 'GET'
1841
+ }, (err, response) => {
1842
+ t.assert.ifError(err)
1843
+ t.assert.strictEqual(response.statusCode, 404)
1844
+ t.assert.deepStrictEqual(response.payload, 'this was not found')
1845
+ done()
1846
+ })
1847
+ })
1848
+
1849
+ await t.test('Bad URL with special characters should be properly JSON escaped', (t, done) => {
1850
+ t.plan(3)
1851
+ const fastify = Fastify()
1852
+ fastify.get('/hello/:id', () => t.assert.fail('we should not be here'))
1853
+ fastify.inject({
1854
+ url: '/hello/%world%22test',
1855
+ method: 'GET'
1856
+ }, (err, response) => {
1857
+ t.assert.ifError(err)
1858
+ t.assert.strictEqual(response.statusCode, 400)
1859
+ t.assert.deepStrictEqual(JSON.parse(response.payload), {
1860
+ error: 'Bad Request',
1861
+ message: '\'/hello/%world%22test\' is not a valid url component',
1862
+ statusCode: 400,
1863
+ code: 'FST_ERR_BAD_URL'
1864
+ })
1865
+ done()
1866
+ })
1867
+ })
1868
+ })
1869
+
1870
+ test('setNotFoundHandler should be chaining fastify instance', async t => {
1871
+ await t.test('Register route after setNotFoundHandler', async t => {
1872
+ t.plan(4)
1873
+ const fastify = Fastify()
1874
+ fastify.setNotFoundHandler(function (_req, reply) {
1875
+ reply.code(404).send('this was not found')
1876
+ }).get('/valid-route', function (_req, reply) {
1877
+ reply.send('valid route')
1878
+ })
1879
+
1880
+ {
1881
+ const response = await fastify.inject({
1882
+ url: '/invalid-route',
1883
+ method: 'GET'
1884
+ })
1885
+ t.assert.strictEqual(response.statusCode, 404)
1886
+ t.assert.strictEqual(response.payload, 'this was not found')
1887
+ }
1888
+
1889
+ {
1890
+ const response = await fastify.inject({
1891
+ url: '/valid-route',
1892
+ method: 'GET'
1893
+ })
1894
+
1895
+ t.assert.strictEqual(response.statusCode, 200)
1896
+ t.assert.strictEqual(response.payload, 'valid route')
1897
+ }
1898
+ })
1899
+ })
1900
+
1901
+ test('Send 404 when frameworkError calls reply.callNotFound', async t => {
1902
+ await t.test('Dynamic route', (t, done) => {
1903
+ t.plan(4)
1904
+ const fastify = Fastify({
1905
+ frameworkErrors: (error, req, reply) => {
1906
+ t.assert.strictEqual(error.message, "'/hello/%world' is not a valid url component")
1907
+ return reply.callNotFound()
1908
+ }
1909
+ })
1910
+ fastify.get('/hello/:id', () => t.assert.fail('we should not be here'))
1911
+ fastify.inject({
1912
+ url: '/hello/%world',
1913
+ method: 'GET'
1914
+ }, (err, response) => {
1915
+ t.assert.ifError(err)
1916
+ t.assert.strictEqual(response.statusCode, 404)
1917
+ t.assert.strictEqual(response.payload, '404 Not Found')
1918
+ done()
1919
+ })
1920
+ })
1921
+ })
1922
+
1923
+ test('hooks are applied to not found handlers /1', async t => {
1924
+ const fastify = Fastify()
1925
+
1926
+ // adding await here is fundamental for this test
1927
+ await fastify.register(async function (fastify) {
1928
+ })
1929
+
1930
+ fastify.setErrorHandler(function (_, request, reply) {
1931
+ return reply.code(401).send({ error: 'Unauthorized' })
1932
+ })
1933
+
1934
+ fastify.addHook('preValidation', async function (request, reply) {
1935
+ throw new Error('kaboom')
1936
+ })
1937
+
1938
+ const { statusCode } = await fastify.inject('/')
1939
+ t.assert.strictEqual(statusCode, 401)
1940
+ })
1941
+
1942
+ test('hooks are applied to not found handlers /2', async t => {
1943
+ const fastify = Fastify()
1944
+
1945
+ async function plugin (fastify) {
1946
+ fastify.setErrorHandler(function (_, request, reply) {
1947
+ return reply.code(401).send({ error: 'Unauthorized' })
1948
+ })
1949
+ }
1950
+
1951
+ plugin[Symbol.for('skip-override')] = true
1952
+
1953
+ fastify.register(plugin)
1954
+
1955
+ fastify.addHook('preValidation', async function (request, reply) {
1956
+ throw new Error('kaboom')
1957
+ })
1958
+
1959
+ const { statusCode } = await fastify.inject('/')
1960
+ t.assert.strictEqual(statusCode, 401)
1961
+ })
1962
+
1963
+ test('hooks are applied to not found handlers /3', async t => {
1964
+ const fastify = Fastify()
1965
+
1966
+ async function plugin (fastify) {
1967
+ fastify.setNotFoundHandler({ errorHandler }, async () => {
1968
+ t.assert.fail('this should never be called')
1969
+ })
1970
+
1971
+ function errorHandler (_, request, reply) {
1972
+ return reply.code(401).send({ error: 'Unauthorized' })
1973
+ }
1974
+ }
1975
+
1976
+ plugin[Symbol.for('skip-override')] = true
1977
+
1978
+ fastify.register(plugin)
1979
+
1980
+ fastify.addHook('preValidation', async function (request, reply) {
1981
+ throw new Error('kaboom')
1982
+ })
1983
+
1984
+ const { statusCode } = await fastify.inject('/')
1985
+ t.assert.strictEqual(statusCode, 401)
1986
+ })
1987
+
1988
+ test('should honor disableRequestLogging function for 404', async t => {
1989
+ t.plan(3)
1990
+
1991
+ const Writable = require('node:stream').Writable
1992
+
1993
+ const logStream = new Writable()
1994
+ logStream.logs = []
1995
+ logStream._write = function (chunk, encoding, callback) {
1996
+ this.logs.push(JSON.parse(chunk.toString()))
1997
+ callback()
1998
+ }
1999
+
2000
+ const fastify = Fastify({
2001
+ logger: {
2002
+ level: 'info',
2003
+ stream: logStream
2004
+ },
2005
+ disableRequestLogging: (req) => {
2006
+ // Disable logging for URLs containing 'silent'
2007
+ return req.url.includes('silent')
2008
+ }
2009
+ })
2010
+
2011
+ fastify.get('/', function (req, reply) {
2012
+ reply.send({ hello: 'world' })
2013
+ })
2014
+
2015
+ t.after(() => { fastify.close() })
2016
+
2017
+ // First request to a non-existent route (no 'silent' in URL) - should log
2018
+ const response1 = await fastify.inject({
2019
+ method: 'GET',
2020
+ url: '/not-found'
2021
+ })
2022
+ t.assert.strictEqual(response1.statusCode, 404)
2023
+
2024
+ // Second request to a non-existent route with 'silent' in URL - should not log
2025
+ const response2 = await fastify.inject({
2026
+ method: 'GET',
2027
+ url: '/silent-route'
2028
+ })
2029
+ t.assert.strictEqual(response2.statusCode, 404)
2030
+
2031
+ // Check logs: first request should have logged, second should not
2032
+ // We expect: incoming request, Route not found info, request completed (for first request only)
2033
+ const infoLogs = logStream.logs.filter(log => log.msg && log.msg.includes('Route GET:/not-found not found'))
2034
+ t.assert.strictEqual(infoLogs.length, 1, 'Should log 404 info only for non-silent route')
2035
+ })