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,352 @@
1
+ 'use strict'
2
+
3
+ const { test } = require('node:test')
4
+
5
+ const Ajv = require('ajv')
6
+ const ajv = new Ajv({ coerceTypes: true })
7
+
8
+ const validation = require('../../lib/validation')
9
+ const { normalizeSchema } = require('../../lib/schemas')
10
+ const symbols = require('../../lib/validation').symbols
11
+ const { kSchemaVisited } = require('../../lib/symbols')
12
+
13
+ test('Symbols', t => {
14
+ t.plan(5)
15
+ t.assert.strictEqual(typeof symbols.responseSchema, 'symbol')
16
+ t.assert.strictEqual(typeof symbols.bodySchema, 'symbol')
17
+ t.assert.strictEqual(typeof symbols.querystringSchema, 'symbol')
18
+ t.assert.strictEqual(typeof symbols.paramsSchema, 'symbol')
19
+ t.assert.strictEqual(typeof symbols.headersSchema, 'symbol')
20
+ })
21
+
22
+ ;['compileSchemasForValidation',
23
+ 'compileSchemasForSerialization'].forEach(func => {
24
+ test(`${func} schema - missing schema`, t => {
25
+ t.plan(2)
26
+ const context = {}
27
+ validation[func](context)
28
+ t.assert.strictEqual(typeof context[symbols.bodySchema], 'undefined')
29
+ t.assert.strictEqual(typeof context[symbols.responseSchema], 'undefined')
30
+ })
31
+
32
+ test(`${func} schema - missing output schema`, t => {
33
+ t.plan(1)
34
+ const context = { schema: {} }
35
+ validation[func](context, null)
36
+ t.assert.strictEqual(typeof context[symbols.responseSchema], 'undefined')
37
+ })
38
+ })
39
+
40
+ test('build schema - output schema', t => {
41
+ t.plan(2)
42
+ const opts = {
43
+ schema: {
44
+ response: {
45
+ '2xx': {
46
+ type: 'object',
47
+ properties: {
48
+ hello: { type: 'string' }
49
+ }
50
+ },
51
+ 201: {
52
+ type: 'object',
53
+ properties: {
54
+ hello: { type: 'number' }
55
+ }
56
+ }
57
+ }
58
+ }
59
+ }
60
+ validation.compileSchemasForSerialization(opts, ({ schema, method, url, httpPart }) => ajv.compile(schema))
61
+ t.assert.strictEqual(typeof opts[symbols.responseSchema]['2xx'], 'function')
62
+ t.assert.strictEqual(typeof opts[symbols.responseSchema]['201'], 'function')
63
+ })
64
+
65
+ test('build schema - body schema', t => {
66
+ t.plan(1)
67
+ const opts = {
68
+ schema: {
69
+ body: {
70
+ type: 'object',
71
+ properties: {
72
+ hello: { type: 'string' }
73
+ }
74
+ }
75
+ }
76
+ }
77
+ validation.compileSchemasForValidation(opts, ({ schema, method, url, httpPart }) => ajv.compile(schema))
78
+ t.assert.strictEqual(typeof opts[symbols.bodySchema], 'function')
79
+ })
80
+
81
+ test('build schema - body with multiple content type schemas', t => {
82
+ t.plan(2)
83
+ const opts = {
84
+ schema: {
85
+ body: {
86
+ content: {
87
+ 'application/json': {
88
+ schema: {
89
+ type: 'object',
90
+ properties: {
91
+ hello: { type: 'string' }
92
+ }
93
+ }
94
+ },
95
+ 'text/plain': {
96
+ schema: { type: 'string' }
97
+ }
98
+ }
99
+ }
100
+ }
101
+ }
102
+ validation.compileSchemasForValidation(opts, ({ schema, method, url, httpPart }) => ajv.compile(schema))
103
+ t.assert.ok(opts[symbols.bodySchema]['application/json'], 'function')
104
+ t.assert.ok(opts[symbols.bodySchema]['text/plain'], 'function')
105
+ })
106
+
107
+ test('build schema - avoid repeated normalize schema', t => {
108
+ t.plan(3)
109
+ const serverConfig = {}
110
+ const opts = {
111
+ schema: {
112
+ query: {
113
+ type: 'object',
114
+ properties: {
115
+ hello: { type: 'string' }
116
+ }
117
+ }
118
+ }
119
+ }
120
+ opts.schema = normalizeSchema(opts.schema, serverConfig)
121
+ t.assert.notStrictEqual(kSchemaVisited, undefined)
122
+ t.assert.strictEqual(opts.schema[kSchemaVisited], true)
123
+ t.assert.strictEqual(opts.schema, normalizeSchema(opts.schema, serverConfig))
124
+ })
125
+
126
+ test('build schema - query schema', t => {
127
+ t.plan(2)
128
+ const serverConfig = {}
129
+ const opts = {
130
+ schema: {
131
+ query: {
132
+ type: 'object',
133
+ properties: {
134
+ hello: { type: 'string' }
135
+ }
136
+ }
137
+ }
138
+ }
139
+ opts.schema = normalizeSchema(opts.schema, serverConfig)
140
+ validation.compileSchemasForValidation(opts, ({ schema, method, url, httpPart }) => ajv.compile(schema))
141
+ t.assert.ok(typeof opts[symbols.querystringSchema].schema.type === 'string')
142
+ t.assert.strictEqual(typeof opts[symbols.querystringSchema], 'function')
143
+ })
144
+
145
+ test('build schema - query schema abbreviated', t => {
146
+ t.plan(2)
147
+ const serverConfig = {}
148
+ const opts = {
149
+ schema: {
150
+ query: {
151
+ type: 'object',
152
+ properties: {
153
+ hello: { type: 'string' }
154
+ }
155
+ }
156
+ }
157
+ }
158
+ opts.schema = normalizeSchema(opts.schema, serverConfig)
159
+ validation.compileSchemasForValidation(opts, ({ schema, method, url, httpPart }) => ajv.compile(schema))
160
+ t.assert.ok(typeof opts[symbols.querystringSchema].schema.type === 'string')
161
+ t.assert.strictEqual(typeof opts[symbols.querystringSchema], 'function')
162
+ })
163
+
164
+ test('build schema - querystring schema', t => {
165
+ t.plan(2)
166
+ const opts = {
167
+ schema: {
168
+ querystring: {
169
+ type: 'object',
170
+ properties: {
171
+ hello: { type: 'string' }
172
+ }
173
+ }
174
+ }
175
+ }
176
+ validation.compileSchemasForValidation(opts, ({ schema, method, url, httpPart }) => ajv.compile(schema))
177
+ t.assert.ok(typeof opts[symbols.querystringSchema].schema.type === 'string')
178
+ t.assert.strictEqual(typeof opts[symbols.querystringSchema], 'function')
179
+ })
180
+
181
+ test('build schema - querystring schema abbreviated', t => {
182
+ t.plan(2)
183
+ const serverConfig = {}
184
+ const opts = {
185
+ schema: {
186
+ querystring: {
187
+ type: 'object',
188
+ properties: {
189
+ hello: { type: 'string' }
190
+ }
191
+ }
192
+ }
193
+ }
194
+ opts.schema = normalizeSchema(opts.schema, serverConfig)
195
+ validation.compileSchemasForValidation(opts, ({ schema, method, url, httpPart }) => ajv.compile(schema))
196
+ t.assert.ok(typeof opts[symbols.querystringSchema].schema.type === 'string')
197
+ t.assert.strictEqual(typeof opts[symbols.querystringSchema], 'function')
198
+ })
199
+
200
+ test('build schema - must throw if querystring and query schema exist', t => {
201
+ t.plan(2)
202
+ try {
203
+ const serverConfig = {}
204
+ const opts = {
205
+ schema: {
206
+ query: {
207
+ type: 'object',
208
+ properties: {
209
+ hello: { type: 'string' }
210
+ }
211
+ },
212
+ querystring: {
213
+ type: 'object',
214
+ properties: {
215
+ hello: { type: 'string' }
216
+ }
217
+ }
218
+ }
219
+ }
220
+ opts.schema = normalizeSchema(opts.schema, serverConfig)
221
+ } catch (err) {
222
+ t.assert.strictEqual(err.code, 'FST_ERR_SCH_DUPLICATE')
223
+ t.assert.strictEqual(err.message, 'Schema with \'querystring\' already present!')
224
+ }
225
+ })
226
+
227
+ test('build schema - params schema', t => {
228
+ t.plan(1)
229
+ const opts = {
230
+ schema: {
231
+ params: {
232
+ type: 'object',
233
+ properties: {
234
+ hello: { type: 'string' }
235
+ }
236
+ }
237
+ }
238
+ }
239
+ validation.compileSchemasForValidation(opts, ({ schema, method, url, httpPart }) => ajv.compile(schema))
240
+ t.assert.strictEqual(typeof opts[symbols.paramsSchema], 'function')
241
+ })
242
+
243
+ test('build schema - headers schema', t => {
244
+ t.plan(1)
245
+ const opts = {
246
+ schema: {
247
+ headers: {
248
+ type: 'object',
249
+ properties: {
250
+ 'content-type': { type: 'string' }
251
+ }
252
+ }
253
+ }
254
+ }
255
+ validation.compileSchemasForValidation(opts, ({ schema, method, url, httpPart }) => ajv.compile(schema))
256
+ t.assert.strictEqual(typeof opts[symbols.headersSchema], 'function')
257
+ })
258
+
259
+ test('build schema - headers are lowercase', t => {
260
+ t.plan(1)
261
+ const opts = {
262
+ schema: {
263
+ headers: {
264
+ type: 'object',
265
+ properties: {
266
+ 'Content-Type': { type: 'string' }
267
+ }
268
+ }
269
+ }
270
+ }
271
+ validation.compileSchemasForValidation(opts, ({ schema, method, url, httpPart }) => {
272
+ t.assert.ok(schema.properties['content-type'], 'lowercase content-type exists')
273
+ return () => { }
274
+ })
275
+ })
276
+
277
+ test('build schema - headers are not lowercased in case of custom object', t => {
278
+ t.plan(1)
279
+
280
+ class Headers { }
281
+ const opts = {
282
+ schema: {
283
+ headers: new Headers()
284
+ }
285
+ }
286
+ validation.compileSchemasForValidation(opts, ({ schema, method, url, httpPart }) => {
287
+ t.assert.ok(schema, Headers)
288
+ return () => { }
289
+ })
290
+ })
291
+
292
+ test('build schema - headers are not lowercased in case of custom validator provided', t => {
293
+ t.plan(1)
294
+
295
+ class Headers { }
296
+ const opts = {
297
+ schema: {
298
+ headers: new Headers()
299
+ }
300
+ }
301
+ validation.compileSchemasForValidation(opts, ({ schema, method, url, httpPart }) => {
302
+ t.assert.ok(schema, Headers)
303
+ return () => { }
304
+ }, true)
305
+ })
306
+
307
+ test('build schema - uppercased headers are not included', t => {
308
+ t.plan(1)
309
+ const opts = {
310
+ schema: {
311
+ headers: {
312
+ type: 'object',
313
+ properties: {
314
+ 'Content-Type': { type: 'string' }
315
+ }
316
+ }
317
+ }
318
+ }
319
+ validation.compileSchemasForValidation(opts, ({ schema, method, url, httpPart }) => {
320
+ t.assert.ok(!('Content-Type' in schema.properties), 'uppercase does not exist')
321
+ return () => { }
322
+ })
323
+ })
324
+
325
+ test('build schema - mixed schema types are individually skipped or normalized', t => {
326
+ t.plan(2)
327
+
328
+ class CustomSchemaClass { }
329
+
330
+ const testCases = [{
331
+ schema: {
332
+ body: new CustomSchemaClass()
333
+ },
334
+ assertions: (schema) => {
335
+ t.assert.ok(schema.body, CustomSchemaClass)
336
+ }
337
+ }, {
338
+ schema: {
339
+ response: {
340
+ 200: new CustomSchemaClass()
341
+ }
342
+ },
343
+ assertions: (schema) => {
344
+ t.assert.ok(schema.response[200], CustomSchemaClass)
345
+ }
346
+ }]
347
+
348
+ testCases.forEach((testCase) => {
349
+ const result = normalizeSchema(testCase.schema, {})
350
+ testCase.assertions(result)
351
+ })
352
+ })
@@ -0,0 +1,118 @@
1
+ 'use strict'
2
+
3
+ const { test } = require('node:test')
4
+ const http = require('node:http')
5
+ const Fastify = require('../fastify')
6
+ const { setTimeout } = require('node:timers')
7
+
8
+ /*
9
+ * Ensure that a socket error during the request does not cause the
10
+ * onSend hook to be called multiple times.
11
+ *
12
+ * @see https://github.com/fastify/fastify/issues/4959
13
+ */
14
+ function runBadClientCall (reqOptions, payload, waitBeforeDestroy) {
15
+ let innerResolve, innerReject
16
+ const promise = new Promise((resolve, reject) => {
17
+ innerResolve = resolve
18
+ innerReject = reject
19
+ })
20
+
21
+ const postData = JSON.stringify(payload)
22
+
23
+ const req = http.request({
24
+ ...reqOptions,
25
+ headers: {
26
+ 'Content-Type': 'application/json',
27
+ 'Content-Length': Buffer.byteLength(postData)
28
+ }
29
+ }, () => {
30
+ innerReject(new Error('Request should have failed'))
31
+ })
32
+
33
+ // Kill the socket after the request has been fully written.
34
+ // Destroying it on `connect` can race before any bytes are sent, making the
35
+ // server-side assertions (hooks/handler) non-deterministic.
36
+ //
37
+ // To keep the test deterministic, we optionally wait for a server-side signal
38
+ // (e.g. onSend entered) before aborting the client.
39
+ let socket
40
+ req.on('socket', (s) => { socket = s })
41
+ req.on('finish', () => {
42
+ if (waitBeforeDestroy && typeof waitBeforeDestroy.then === 'function') {
43
+ Promise.race([
44
+ waitBeforeDestroy,
45
+ new Promise(resolve => setTimeout(resolve, 200))
46
+ ]).then(() => {
47
+ if (socket) socket.destroy()
48
+ }, innerResolve)
49
+ return
50
+ }
51
+ setTimeout(() => { socket.destroy() }, 0)
52
+ })
53
+ req.on('error', innerResolve)
54
+ req.write(postData)
55
+ req.end()
56
+
57
+ return promise
58
+ }
59
+
60
+ test('should handle a socket error', async (t) => {
61
+ t.plan(4)
62
+ const fastify = Fastify()
63
+
64
+ let resolveOnSendEntered
65
+ const onSendEntered = new Promise((resolve) => {
66
+ resolveOnSendEntered = resolve
67
+ })
68
+
69
+ function shouldNotHappen () {
70
+ t.assert.fail('This should not happen')
71
+ }
72
+ process.on('unhandledRejection', shouldNotHappen)
73
+
74
+ t.after(() => {
75
+ fastify.close()
76
+ process.removeListener('unhandledRejection', shouldNotHappen)
77
+ })
78
+
79
+ fastify.addHook('onRequest', async (request, reply) => {
80
+ t.assert.ok('onRequest hook called')
81
+ })
82
+
83
+ fastify.addHook('onSend', async (request, reply, payload) => {
84
+ if (request.onSendCalled) {
85
+ t.assert.fail('onSend hook called more than once')
86
+ return
87
+ }
88
+
89
+ t.assert.ok('onSend hook called')
90
+ request.onSendCalled = true
91
+
92
+ if (resolveOnSendEntered) {
93
+ resolveOnSendEntered()
94
+ resolveOnSendEntered = null
95
+ }
96
+
97
+ // Introduce a delay (gives time for client-side abort to happen while the
98
+ // request has already been processed, exercising the original issue).
99
+ await new Promise(resolve => setTimeout(resolve, 50))
100
+ return payload
101
+ })
102
+
103
+ // The handler must be async to trigger the error
104
+ fastify.put('/', async (request, reply) => {
105
+ t.assert.ok('PUT handler called')
106
+ return reply.send({ hello: 'world' })
107
+ })
108
+
109
+ await fastify.listen({ port: 0 })
110
+
111
+ const err = await runBadClientCall({
112
+ hostname: 'localhost',
113
+ port: fastify.server.address().port,
114
+ path: '/',
115
+ method: 'PUT'
116
+ }, { test: 'me' }, onSendEntered)
117
+ t.assert.equal(err.code, 'ECONNRESET')
118
+ })
@@ -0,0 +1,42 @@
1
+ 'use strict'
2
+
3
+ const Fastify = require('..')
4
+ const http = require('node:http')
5
+ const { test } = require('node:test')
6
+
7
+ test('keepAliveTimeout', t => {
8
+ t.plan(6)
9
+
10
+ try {
11
+ Fastify({ keepAliveTimeout: 1.3 })
12
+ t.assert.fail('option must be an integer')
13
+ } catch (err) {
14
+ t.assert.ok(err)
15
+ }
16
+
17
+ try {
18
+ Fastify({ keepAliveTimeout: [] })
19
+ t.assert.fail('option must be an integer')
20
+ } catch (err) {
21
+ t.assert.ok(err)
22
+ }
23
+
24
+ const httpServer = Fastify({ keepAliveTimeout: 1 }).server
25
+ t.assert.strictEqual(httpServer.keepAliveTimeout, 1)
26
+
27
+ const httpsServer = Fastify({ keepAliveTimeout: 2, https: {} }).server
28
+ t.assert.strictEqual(httpsServer.keepAliveTimeout, 2)
29
+
30
+ const http2Server = Fastify({ keepAliveTimeout: 3, http2: true }).server
31
+ t.assert.notStrictEqual(http2Server.keepAliveTimeout, 3)
32
+
33
+ const serverFactory = (handler, _) => {
34
+ const server = http.createServer((req, res) => {
35
+ handler(req, res)
36
+ })
37
+ server.keepAliveTimeout = 5
38
+ return server
39
+ }
40
+ const customServer = Fastify({ keepAliveTimeout: 4, serverFactory }).server
41
+ t.assert.strictEqual(customServer.keepAliveTimeout, 5)
42
+ })
@@ -0,0 +1,154 @@
1
+ 'use strict'
2
+
3
+ const { networkInterfaces } = require('node:os')
4
+ const { test, before } = require('node:test')
5
+ const Fastify = require('..')
6
+ const helper = require('./helper')
7
+
8
+ let localhost
9
+ let localhostForURL
10
+
11
+ before(async function () {
12
+ [localhost, localhostForURL] = await helper.getLoopbackHost()
13
+ })
14
+
15
+ test('listen works without arguments', async t => {
16
+ const doNotWarn = () => {
17
+ t.assert.fail('should not be deprecated')
18
+ }
19
+ process.on('warning', doNotWarn)
20
+
21
+ const fastify = Fastify()
22
+ t.after(() => {
23
+ fastify.close()
24
+ process.removeListener('warning', doNotWarn)
25
+ })
26
+ await fastify.listen()
27
+ const address = fastify.server.address()
28
+ t.assert.strictEqual(address.address, localhost)
29
+ t.assert.ok(address.port > 0)
30
+ })
31
+
32
+ test('Async/await listen with arguments', async t => {
33
+ const doNotWarn = () => {
34
+ t.assert.fail('should not be deprecated')
35
+ }
36
+ process.on('warning', doNotWarn)
37
+
38
+ const fastify = Fastify()
39
+ t.after(() => {
40
+ fastify.close()
41
+ process.removeListener('warning', doNotWarn)
42
+ })
43
+ const addr = await fastify.listen({ port: 0, host: '0.0.0.0' })
44
+ const address = fastify.server.address()
45
+ const { protocol, hostname, port, pathname } = new URL(addr)
46
+ t.assert.strictEqual(protocol, 'http:')
47
+ t.assert.ok(Object.values(networkInterfaces())
48
+ .flat()
49
+ .filter(({ internal }) => internal)
50
+ .some(({ address }) => address === hostname))
51
+ t.assert.strictEqual(pathname, '/')
52
+ t.assert.strictEqual(Number(port), address.port)
53
+ t.assert.deepEqual(address, {
54
+ address: '0.0.0.0',
55
+ family: 'IPv4',
56
+ port: address.port
57
+ })
58
+ })
59
+
60
+ test('listen accepts a callback', (t, done) => {
61
+ t.plan(2)
62
+ const doNotWarn = () => {
63
+ t.assert.fail('should not be deprecated')
64
+ }
65
+ process.on('warning', doNotWarn)
66
+
67
+ const fastify = Fastify()
68
+ t.after(() => {
69
+ fastify.close()
70
+ process.removeListener('warning', doNotWarn)
71
+ })
72
+ fastify.listen({ port: 0 }, (err) => {
73
+ t.assert.ifError(err)
74
+ t.assert.strictEqual(fastify.server.address().address, localhost)
75
+ done()
76
+ })
77
+ })
78
+
79
+ test('listen accepts options and a callback', (t, done) => {
80
+ t.plan(1)
81
+ const doNotWarn = () => {
82
+ t.assert.fail('should not be deprecated')
83
+ }
84
+ process.on('warning', doNotWarn)
85
+
86
+ const fastify = Fastify()
87
+ t.after(() => {
88
+ fastify.close()
89
+ process.removeListener('warning', doNotWarn)
90
+ })
91
+ fastify.listen({
92
+ port: 0,
93
+ host: 'localhost',
94
+ backlog: 511,
95
+ exclusive: false,
96
+ readableAll: false,
97
+ writableAll: false,
98
+ ipv6Only: false
99
+ }, (err) => {
100
+ t.assert.ifError(err)
101
+ done()
102
+ })
103
+ })
104
+
105
+ test('listen after Promise.resolve()', (t, done) => {
106
+ t.plan(2)
107
+ const fastify = Fastify()
108
+ t.after(() => fastify.close())
109
+ Promise.resolve()
110
+ .then(() => {
111
+ fastify.listen({ port: 0 }, (err, address) => {
112
+ fastify.server.unref()
113
+ t.assert.strictEqual(address, `http://${localhostForURL}:${fastify.server.address().port}`)
114
+ t.assert.ifError(err)
115
+ done()
116
+ })
117
+ })
118
+ })
119
+
120
+ test('listen works with undefined host', async t => {
121
+ const doNotWarn = () => {
122
+ t.assert.fail('should not be deprecated')
123
+ }
124
+ process.on('warning', doNotWarn)
125
+
126
+ const fastify = Fastify()
127
+ t.after(() => fastify.close())
128
+ t.after(() => {
129
+ fastify.close()
130
+ process.removeListener('warning', doNotWarn)
131
+ })
132
+ await fastify.listen({ host: undefined, port: 0 })
133
+ const address = fastify.server.address()
134
+ t.assert.strictEqual(address.address, localhost)
135
+ t.assert.ok(address.port > 0)
136
+ })
137
+
138
+ test('listen works with null host', async t => {
139
+ const doNotWarn = () => {
140
+ t.fail('should not be deprecated')
141
+ }
142
+ process.on('warning', doNotWarn)
143
+
144
+ const fastify = Fastify()
145
+ t.after(() => fastify.close())
146
+ t.after(() => {
147
+ fastify.close()
148
+ process.removeListener('warning', doNotWarn)
149
+ })
150
+ await fastify.listen({ host: null, port: 0 })
151
+ const address = fastify.server.address()
152
+ t.assert.strictEqual(address.address, localhost)
153
+ t.assert.ok(address.port > 0)
154
+ })