fastify 5.2.2 → 5.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/SPONSORS.md +0 -1
  2. package/docs/Guides/Ecosystem.md +2 -0
  3. package/docs/Reference/Decorators.md +199 -0
  4. package/docs/Reference/Errors.md +2 -0
  5. package/fastify.js +3 -2
  6. package/lib/decorate.js +18 -3
  7. package/lib/errors.js +4 -0
  8. package/lib/reply.js +17 -2
  9. package/lib/request.js +28 -2
  10. package/lib/validation.js +11 -1
  11. package/package.json +3 -3
  12. package/test/build/error-serializer.test.js +1 -2
  13. package/test/custom-parser.0.test.js +160 -129
  14. package/test/custom-parser.1.test.js +77 -63
  15. package/test/custom-parser.4.test.js +55 -38
  16. package/test/custom-querystring-parser.test.js +46 -28
  17. package/test/decorator.test.js +174 -4
  18. package/test/fastify-instance.test.js +12 -2
  19. package/test/hooks.on-listen.test.js +17 -14
  20. package/test/internals/errors.test.js +14 -1
  21. package/test/listen.2.test.js +4 -1
  22. package/test/logger/instantiation.test.js +89 -96
  23. package/test/logger/logging.test.js +116 -120
  24. package/test/logger/options.test.js +97 -99
  25. package/test/logger/request.test.js +66 -66
  26. package/test/schema-validation.test.js +120 -0
  27. package/test/server.test.js +175 -0
  28. package/test/stream.4.test.js +38 -33
  29. package/test/toolkit.js +31 -0
  30. package/test/types/instance.test-d.ts +3 -0
  31. package/test/types/reply.test-d.ts +1 -0
  32. package/test/types/request.test-d.ts +4 -0
  33. package/test/types/type-provider.test-d.ts +40 -0
  34. package/test/upgrade.test.js +3 -6
  35. package/types/instance.d.ts +2 -0
  36. package/types/reply.d.ts +1 -0
  37. package/types/request.d.ts +2 -0
  38. package/types/type-provider.d.ts +12 -3
@@ -2,17 +2,16 @@
2
2
 
3
3
  const stream = require('node:stream')
4
4
 
5
- const t = require('tap')
5
+ const t = require('node:test')
6
6
  const split = require('split2')
7
7
 
8
8
  const Fastify = require('../../fastify')
9
9
  const helper = require('../helper')
10
10
  const { on } = stream
11
11
  const { request } = require('./logger-test-utils')
12
+ const { partialDeepStrictEqual } = require('../toolkit')
12
13
 
13
- t.test('request', (t) => {
14
- t.setTimeout(60000)
15
-
14
+ t.test('request', { timeout: 60000 }, async (t) => {
16
15
  let localhost
17
16
 
18
17
  t.plan(7)
@@ -20,7 +19,7 @@ t.test('request', (t) => {
20
19
  [localhost] = await helper.getLoopbackHost()
21
20
  })
22
21
 
23
- t.test('The request id header key can be customized', async (t) => {
22
+ await t.test('The request id header key can be customized', async (t) => {
24
23
  const lines = ['incoming request', 'some log message', 'request completed']
25
24
  t.plan(lines.length * 2 + 2)
26
25
  const REQUEST_ID = '42'
@@ -30,26 +29,26 @@ t.test('request', (t) => {
30
29
  logger: { stream, level: 'info' },
31
30
  requestIdHeader: 'my-custom-request-id'
32
31
  })
33
- t.teardown(fastify.close.bind(fastify))
32
+ t.after(() => fastify.close())
34
33
 
35
34
  fastify.get('/', (req, reply) => {
36
- t.equal(req.id, REQUEST_ID)
35
+ t.assert.strictEqual(req.id, REQUEST_ID)
37
36
  req.log.info('some log message')
38
37
  reply.send({ id: req.id })
39
38
  })
40
39
 
41
40
  const response = await fastify.inject({ method: 'GET', url: '/', headers: { 'my-custom-request-id': REQUEST_ID } })
42
41
  const body = await response.json()
43
- t.equal(body.id, REQUEST_ID)
42
+ t.assert.strictEqual(body.id, REQUEST_ID)
44
43
 
45
44
  for await (const [line] of on(stream, 'data')) {
46
- t.equal(line.reqId, REQUEST_ID)
47
- t.equal(line.msg, lines.shift(), 'message is set')
45
+ t.assert.strictEqual(line.reqId, REQUEST_ID)
46
+ t.assert.strictEqual(line.msg, lines.shift(), 'message is set')
48
47
  if (lines.length === 0) break
49
48
  }
50
49
  })
51
50
 
52
- t.test('The request id header key can be ignored', async (t) => {
51
+ await t.test('The request id header key can be ignored', async (t) => {
53
52
  const lines = ['incoming request', 'some log message', 'request completed']
54
53
  t.plan(lines.length * 2 + 2)
55
54
  const REQUEST_ID = 'ignore-me'
@@ -59,33 +58,33 @@ t.test('request', (t) => {
59
58
  logger: { stream, level: 'info' },
60
59
  requestIdHeader: false
61
60
  })
62
- t.teardown(fastify.close.bind(fastify))
61
+ t.after(() => fastify.close())
63
62
 
64
63
  fastify.get('/', (req, reply) => {
65
- t.equal(req.id, 'req-1')
64
+ t.assert.strictEqual(req.id, 'req-1')
66
65
  req.log.info('some log message')
67
66
  reply.send({ id: req.id })
68
67
  })
69
68
  const response = await fastify.inject({ method: 'GET', url: '/', headers: { 'request-id': REQUEST_ID } })
70
69
  const body = await response.json()
71
- t.equal(body.id, 'req-1')
70
+ t.assert.strictEqual(body.id, 'req-1')
72
71
 
73
72
  for await (const [line] of on(stream, 'data')) {
74
- t.equal(line.reqId, 'req-1')
75
- t.equal(line.msg, lines.shift(), 'message is set')
73
+ t.assert.strictEqual(line.reqId, 'req-1')
74
+ t.assert.strictEqual(line.msg, lines.shift(), 'message is set')
76
75
  if (lines.length === 0) break
77
76
  }
78
77
  })
79
78
 
80
- t.test('The request id header key can be customized along with a custom id generator', async (t) => {
79
+ await t.test('The request id header key can be customized along with a custom id generator', async (t) => {
81
80
  const REQUEST_ID = '42'
82
81
  const matches = [
83
- { reqId: REQUEST_ID, msg: /incoming request/ },
84
- { reqId: REQUEST_ID, msg: /some log message/ },
85
- { reqId: REQUEST_ID, msg: /request completed/ },
86
- { reqId: 'foo', msg: /incoming request/ },
87
- { reqId: 'foo', msg: /some log message 2/ },
88
- { reqId: 'foo', msg: /request completed/ }
82
+ { reqId: REQUEST_ID, msg: 'incoming request' },
83
+ { reqId: REQUEST_ID, msg: 'some log message' },
84
+ { reqId: REQUEST_ID, msg: 'request completed' },
85
+ { reqId: 'foo', msg: 'incoming request' },
86
+ { reqId: 'foo', msg: 'some log message 2' },
87
+ { reqId: 'foo', msg: 'request completed' }
89
88
  ]
90
89
  t.plan(matches.length + 4)
91
90
 
@@ -97,16 +96,16 @@ t.test('request', (t) => {
97
96
  return 'foo'
98
97
  }
99
98
  })
100
- t.teardown(fastify.close.bind(fastify))
99
+ t.after(() => fastify.close())
101
100
 
102
101
  fastify.get('/one', (req, reply) => {
103
- t.equal(req.id, REQUEST_ID)
102
+ t.assert.strictEqual(req.id, REQUEST_ID)
104
103
  req.log.info('some log message')
105
104
  reply.send({ id: req.id })
106
105
  })
107
106
 
108
107
  fastify.get('/two', (req, reply) => {
109
- t.equal(req.id, 'foo')
108
+ t.assert.strictEqual(req.id, 'foo')
110
109
  req.log.info('some log message 2')
111
110
  reply.send({ id: req.id })
112
111
  })
@@ -114,30 +113,30 @@ t.test('request', (t) => {
114
113
  {
115
114
  const response = await fastify.inject({ method: 'GET', url: '/one', headers: { 'my-custom-request-id': REQUEST_ID } })
116
115
  const body = await response.json()
117
- t.equal(body.id, REQUEST_ID)
116
+ t.assert.strictEqual(body.id, REQUEST_ID)
118
117
  }
119
118
 
120
119
  {
121
120
  const response = await fastify.inject({ method: 'GET', url: '/two' })
122
121
  const body = await response.json()
123
- t.equal(body.id, 'foo')
122
+ t.assert.strictEqual(body.id, 'foo')
124
123
  }
125
124
 
126
125
  for await (const [line] of on(stream, 'data')) {
127
- t.match(line, matches.shift())
126
+ t.assert.ok(partialDeepStrictEqual(line, matches.shift()))
128
127
  if (matches.length === 0) break
129
128
  }
130
129
  })
131
130
 
132
- t.test('The request id header key can be ignored along with a custom id generator', async (t) => {
131
+ await t.test('The request id header key can be ignored along with a custom id generator', async (t) => {
133
132
  const REQUEST_ID = 'ignore-me'
134
133
  const matches = [
135
- { reqId: 'foo', msg: /incoming request/ },
136
- { reqId: 'foo', msg: /some log message/ },
137
- { reqId: 'foo', msg: /request completed/ },
138
- { reqId: 'foo', msg: /incoming request/ },
139
- { reqId: 'foo', msg: /some log message 2/ },
140
- { reqId: 'foo', msg: /request completed/ }
134
+ { reqId: 'foo', msg: 'incoming request' },
135
+ { reqId: 'foo', msg: 'some log message' },
136
+ { reqId: 'foo', msg: 'request completed' },
137
+ { reqId: 'foo', msg: 'incoming request' },
138
+ { reqId: 'foo', msg: 'some log message 2' },
139
+ { reqId: 'foo', msg: 'request completed' }
141
140
  ]
142
141
  t.plan(matches.length + 4)
143
142
 
@@ -149,16 +148,16 @@ t.test('request', (t) => {
149
148
  return 'foo'
150
149
  }
151
150
  })
152
- t.teardown(fastify.close.bind(fastify))
151
+ t.after(() => fastify.close())
153
152
 
154
153
  fastify.get('/one', (req, reply) => {
155
- t.equal(req.id, 'foo')
154
+ t.assert.strictEqual(req.id, 'foo')
156
155
  req.log.info('some log message')
157
156
  reply.send({ id: req.id })
158
157
  })
159
158
 
160
159
  fastify.get('/two', (req, reply) => {
161
- t.equal(req.id, 'foo')
160
+ t.assert.strictEqual(req.id, 'foo')
162
161
  req.log.info('some log message 2')
163
162
  reply.send({ id: req.id })
164
163
  })
@@ -166,27 +165,27 @@ t.test('request', (t) => {
166
165
  {
167
166
  const response = await fastify.inject({ method: 'GET', url: '/one', headers: { 'request-id': REQUEST_ID } })
168
167
  const body = await response.json()
169
- t.equal(body.id, 'foo')
168
+ t.assert.strictEqual(body.id, 'foo')
170
169
  }
171
170
 
172
171
  {
173
172
  const response = await fastify.inject({ method: 'GET', url: '/two' })
174
173
  const body = await response.json()
175
- t.equal(body.id, 'foo')
174
+ t.assert.strictEqual(body.id, 'foo')
176
175
  }
177
176
 
178
177
  for await (const [line] of on(stream, 'data')) {
179
- t.match(line, matches.shift())
178
+ t.assert.ok(partialDeepStrictEqual(line, matches.shift()))
180
179
  if (matches.length === 0) break
181
180
  }
182
181
  })
183
182
 
184
- t.test('The request id log label can be changed', async (t) => {
183
+ await t.test('The request id log label can be changed', async (t) => {
185
184
  const REQUEST_ID = '42'
186
185
  const matches = [
187
- { traceId: REQUEST_ID, msg: /incoming request/ },
188
- { traceId: REQUEST_ID, msg: /some log message/ },
189
- { traceId: REQUEST_ID, msg: /request completed/ }
186
+ { traceId: REQUEST_ID, msg: 'incoming request' },
187
+ { traceId: REQUEST_ID, msg: 'some log message' },
188
+ { traceId: REQUEST_ID, msg: 'request completed' }
190
189
  ]
191
190
  t.plan(matches.length + 2)
192
191
 
@@ -196,10 +195,10 @@ t.test('request', (t) => {
196
195
  requestIdHeader: 'my-custom-request-id',
197
196
  requestIdLogLabel: 'traceId'
198
197
  })
199
- t.teardown(fastify.close.bind(fastify))
198
+ t.after(() => fastify.close())
200
199
 
201
200
  fastify.get('/one', (req, reply) => {
202
- t.equal(req.id, REQUEST_ID)
201
+ t.assert.strictEqual(req.id, REQUEST_ID)
203
202
  req.log.info('some log message')
204
203
  reply.send({ id: req.id })
205
204
  })
@@ -207,22 +206,16 @@ t.test('request', (t) => {
207
206
  {
208
207
  const response = await fastify.inject({ method: 'GET', url: '/one', headers: { 'my-custom-request-id': REQUEST_ID } })
209
208
  const body = await response.json()
210
- t.equal(body.id, REQUEST_ID)
209
+ t.assert.strictEqual(body.id, REQUEST_ID)
211
210
  }
212
211
 
213
212
  for await (const [line] of on(stream, 'data')) {
214
- t.match(line, matches.shift())
213
+ t.assert.ok(partialDeepStrictEqual(line, matches.shift()))
215
214
  if (matches.length === 0) break
216
215
  }
217
216
  })
218
217
 
219
- t.test('should redact the authorization header if so specified', async (t) => {
220
- const lines = [
221
- { msg: /Server listening at/ },
222
- { req: { headers: { authorization: '[Redacted]' } }, msg: 'incoming request' },
223
- { res: { statusCode: 200 }, msg: 'request completed' }
224
- ]
225
- t.plan(lines.length + 3)
218
+ await t.test('should redact the authorization header if so specified', async (t) => {
226
219
  const stream = split(JSON.parse)
227
220
  const fastify = Fastify({
228
221
  logger: {
@@ -243,15 +236,22 @@ t.test('request', (t) => {
243
236
  }
244
237
  }
245
238
  })
246
- t.teardown(fastify.close.bind(fastify))
239
+ t.after(() => fastify.close())
247
240
 
248
241
  fastify.get('/', function (req, reply) {
249
- t.same(req.headers.authorization, 'Bearer abcde')
242
+ t.assert.deepStrictEqual(req.headers.authorization, 'Bearer abcde')
250
243
  reply.send({ hello: 'world' })
251
244
  })
252
245
 
253
246
  await fastify.ready()
254
- await fastify.listen({ port: 0, host: localhost })
247
+ const server = await fastify.listen({ port: 0, host: localhost })
248
+
249
+ const lines = [
250
+ { msg: `Server listening at ${server}` },
251
+ { req: { headers: { authorization: '[Redacted]' } }, msg: 'incoming request' },
252
+ { res: { statusCode: 200 }, msg: 'request completed' }
253
+ ]
254
+ t.plan(lines.length + 3)
255
255
 
256
256
  await request({
257
257
  method: 'GET',
@@ -262,17 +262,17 @@ t.test('request', (t) => {
262
262
  authorization: 'Bearer abcde'
263
263
  }
264
264
  }, function (response, body) {
265
- t.equal(response.statusCode, 200)
266
- t.same(body, JSON.stringify({ hello: 'world' }))
265
+ t.assert.strictEqual(response.statusCode, 200)
266
+ t.assert.deepStrictEqual(body, JSON.stringify({ hello: 'world' }))
267
267
  })
268
268
 
269
269
  for await (const [line] of on(stream, 'data')) {
270
- t.match(line, lines.shift())
270
+ t.assert.ok(partialDeepStrictEqual(line, lines.shift()))
271
271
  if (lines.length === 0) break
272
272
  }
273
273
  })
274
274
 
275
- t.test('should not throw error when serializing custom req', (t) => {
275
+ await t.test('should not throw error when serializing custom req', (t) => {
276
276
  t.plan(1)
277
277
 
278
278
  const lines = []
@@ -283,10 +283,10 @@ t.test('request', (t) => {
283
283
  }
284
284
  })
285
285
  const fastify = Fastify({ logger: { level: 'info', stream: dest } })
286
- t.teardown(fastify.close.bind(fastify))
286
+ t.after(() => fastify.close())
287
287
 
288
288
  fastify.log.info({ req: {} })
289
289
 
290
- t.same(lines[0].req, {})
290
+ t.assert.deepStrictEqual(lines[0].req, {})
291
291
  })
292
292
  })
@@ -1300,3 +1300,123 @@ test('Custom validator builder override by custom validator compiler in child in
1300
1300
  })
1301
1301
  t.equal(two.statusCode, 200)
1302
1302
  })
1303
+
1304
+ test('Schema validation when no content type is provided', async t => {
1305
+ // this case should not be happened in normal use-case,
1306
+ // it is added for the completeness of code branch
1307
+ const fastify = Fastify()
1308
+
1309
+ fastify.post('/', {
1310
+ schema: {
1311
+ body: {
1312
+ content: {
1313
+ 'application/json': {
1314
+ schema: {
1315
+ type: 'object',
1316
+ properties: {
1317
+ foo: { type: 'string' }
1318
+ },
1319
+ required: ['foo'],
1320
+ additionalProperties: false
1321
+ }
1322
+ }
1323
+ }
1324
+ }
1325
+ },
1326
+ preValidation: async (request) => {
1327
+ request.headers['content-type'] = undefined
1328
+ }
1329
+ }, async () => 'ok')
1330
+
1331
+ await fastify.ready()
1332
+
1333
+ const invalid = await fastify.inject({
1334
+ method: 'POST',
1335
+ url: '/',
1336
+ headers: {
1337
+ 'content-type': 'application/json'
1338
+ },
1339
+ body: { invalid: 'string' }
1340
+ })
1341
+ t.equal(invalid.statusCode, 200)
1342
+ })
1343
+
1344
+ test('Schema validation will not be bypass by different content type', async t => {
1345
+ t.plan(8)
1346
+
1347
+ const fastify = Fastify()
1348
+
1349
+ fastify.post('/', {
1350
+ schema: {
1351
+ body: {
1352
+ content: {
1353
+ 'application/json': {
1354
+ schema: {
1355
+ type: 'object',
1356
+ properties: {
1357
+ foo: { type: 'string' }
1358
+ },
1359
+ required: ['foo'],
1360
+ additionalProperties: false
1361
+ }
1362
+ }
1363
+ }
1364
+ }
1365
+ }
1366
+ }, async () => 'ok')
1367
+
1368
+ await fastify.ready()
1369
+
1370
+ const correct1 = await fastify.inject({
1371
+ method: 'POST',
1372
+ url: '/',
1373
+ headers: {
1374
+ 'content-type': 'application/json'
1375
+ },
1376
+ body: { foo: 'string' }
1377
+ })
1378
+ t.equal(correct1.statusCode, 200)
1379
+
1380
+ const correct2 = await fastify.inject({
1381
+ method: 'POST',
1382
+ url: '/',
1383
+ headers: {
1384
+ 'content-type': 'application/json; charset=utf-8'
1385
+ },
1386
+ body: { foo: 'string' }
1387
+ })
1388
+ t.equal(correct2.statusCode, 200)
1389
+
1390
+ const invalid1 = await fastify.inject({
1391
+ method: 'POST',
1392
+ url: '/',
1393
+ headers: {
1394
+ 'content-type': 'application/json ;'
1395
+ },
1396
+ body: { invalid: 'string' }
1397
+ })
1398
+ t.equal(invalid1.statusCode, 400)
1399
+ t.equal(invalid1.json().code, 'FST_ERR_VALIDATION')
1400
+
1401
+ const invalid2 = await fastify.inject({
1402
+ method: 'POST',
1403
+ url: '/',
1404
+ headers: {
1405
+ 'content-type': 'ApPlIcAtIoN/JsOn;'
1406
+ },
1407
+ body: { invalid: 'string' }
1408
+ })
1409
+ t.equal(invalid2.statusCode, 400)
1410
+ t.equal(invalid2.json().code, 'FST_ERR_VALIDATION')
1411
+
1412
+ const invalid3 = await fastify.inject({
1413
+ method: 'POST',
1414
+ url: '/',
1415
+ headers: {
1416
+ 'content-type': 'ApPlIcAtIoN/JsOn ;'
1417
+ },
1418
+ body: { invalid: 'string' }
1419
+ })
1420
+ t.equal(invalid3.statusCode, 400)
1421
+ t.equal(invalid3.json().code, 'FST_ERR_VALIDATION')
1422
+ })
@@ -2,6 +2,8 @@
2
2
 
3
3
  const { test } = require('node:test')
4
4
  const Fastify = require('..')
5
+ const sget = require('simple-get').concat
6
+ const undici = require('undici')
5
7
 
6
8
  test('listen should accept null port', async t => {
7
9
  const fastify = Fastify()
@@ -11,3 +13,176 @@ test('listen should accept null port', async t => {
11
13
  fastify.listen({ port: null })
12
14
  )
13
15
  })
16
+
17
+ test('listen should accept undefined port', async t => {
18
+ const fastify = Fastify()
19
+ t.after(() => fastify.close())
20
+
21
+ await t.assert.doesNotReject(
22
+ fastify.listen({ port: undefined })
23
+ )
24
+ })
25
+
26
+ test('listen should accept stringified number port', async t => {
27
+ const fastify = Fastify()
28
+ t.after(() => fastify.close())
29
+
30
+ await t.assert.doesNotReject(
31
+ fastify.listen({ port: '1234' })
32
+ )
33
+ })
34
+
35
+ test('listen should accept log text resolution function', async t => {
36
+ const fastify = Fastify()
37
+ t.after(() => fastify.close())
38
+
39
+ await t.assert.doesNotReject(
40
+ fastify.listen({
41
+ host: '127.0.0.1',
42
+ port: '1234',
43
+ listenTextResolver: (address) => {
44
+ t.assert.strictEqual(address, 'http://127.0.0.1:1234')
45
+ return 'hardcoded text'
46
+ }
47
+ })
48
+ )
49
+ })
50
+
51
+ test('listen should reject string port', async (t) => {
52
+ const fastify = Fastify()
53
+ t.after(() => fastify.close())
54
+
55
+ try {
56
+ await fastify.listen({ port: 'hello-world' })
57
+ } catch (error) {
58
+ t.assert.strictEqual(error.code, 'ERR_SOCKET_BAD_PORT')
59
+ }
60
+
61
+ try {
62
+ await fastify.listen({ port: '1234hello' })
63
+ } catch (error) {
64
+ t.assert.strictEqual(error.code, 'ERR_SOCKET_BAD_PORT')
65
+ }
66
+ })
67
+
68
+ test('Test for hostname and port', (t, end) => {
69
+ const app = Fastify()
70
+ t.after(() => app.close())
71
+ app.get('/host', (req, res) => {
72
+ const host = 'localhost:8000'
73
+ t.assert.strictEqual(req.host, host)
74
+ t.assert.strictEqual(req.hostname, req.host.split(':')[0])
75
+ t.assert.strictEqual(req.port, Number(req.host.split(':')[1]))
76
+ res.send('ok')
77
+ })
78
+
79
+ app.listen({ port: 8000 }, () => {
80
+ sget('http://localhost:8000/host', () => { end() })
81
+ })
82
+ })
83
+
84
+ test('abort signal', async t => {
85
+ await t.test('listen should not start server', (t, end) => {
86
+ t.plan(2)
87
+ function onClose (instance, done) {
88
+ t.assert.strictEqual(instance, fastify)
89
+ done()
90
+ end()
91
+ }
92
+ const controller = new AbortController()
93
+
94
+ const fastify = Fastify()
95
+ fastify.addHook('onClose', onClose)
96
+ fastify.listen({ port: 1234, signal: controller.signal }, (err) => {
97
+ t.assert.ifError(err)
98
+ })
99
+ controller.abort()
100
+ t.assert.strictEqual(fastify.server.listening, false)
101
+ })
102
+
103
+ await t.test('listen should not start server if already aborted', (t, end) => {
104
+ t.plan(2)
105
+ function onClose (instance, done) {
106
+ t.assert.strictEqual(instance, fastify)
107
+ done()
108
+ end()
109
+ }
110
+
111
+ const controller = new AbortController()
112
+ controller.abort()
113
+ const fastify = Fastify()
114
+ fastify.addHook('onClose', onClose)
115
+ fastify.listen({ port: 1234, signal: controller.signal }, (err) => {
116
+ t.assert.ifError(err)
117
+ })
118
+ t.assert.strictEqual(fastify.server.listening, false)
119
+ })
120
+
121
+ await t.test('listen should throw if received invalid signal', t => {
122
+ t.plan(2)
123
+ const fastify = Fastify()
124
+
125
+ try {
126
+ fastify.listen({ port: 1234, signal: {} }, (err) => {
127
+ t.assert.ifError(err)
128
+ })
129
+ t.assert.fail('should throw')
130
+ } catch (e) {
131
+ t.assert.strictEqual(e.code, 'FST_ERR_LISTEN_OPTIONS_INVALID')
132
+ t.assert.strictEqual(e.message, 'Invalid listen options: \'Invalid options.signal\'')
133
+ }
134
+ })
135
+ })
136
+
137
+ test('#5180 - preClose should be called before closing secondary server', async (t) => {
138
+ t.plan(2)
139
+ const fastify = Fastify({ forceCloseConnections: true })
140
+ let flag = false
141
+ t.after(() => fastify.close())
142
+
143
+ fastify.addHook('preClose', () => {
144
+ flag = true
145
+ })
146
+
147
+ fastify.get('/', async (req, reply) => {
148
+ // request will be pending for 1 second to simulate a slow request
149
+ await new Promise((resolve) => { setTimeout(resolve, 1000) })
150
+ return { hello: 'world' }
151
+ })
152
+
153
+ fastify.listen({ port: 0 }, (err) => {
154
+ t.assert.ifError(err)
155
+ const addresses = fastify.addresses()
156
+ const mainServerAddress = fastify.server.address()
157
+ let secondaryAddress
158
+ for (const addr of addresses) {
159
+ if (addr.family !== mainServerAddress.family) {
160
+ secondaryAddress = addr
161
+ secondaryAddress.address = secondaryAddress.family === 'IPv6'
162
+ ? `[${secondaryAddress.address}]`
163
+ : secondaryAddress.address
164
+ break
165
+ }
166
+ }
167
+
168
+ if (!secondaryAddress) {
169
+ t.assert.ok(true, 'Secondary address not found')
170
+ return
171
+ }
172
+
173
+ undici.request(`http://${secondaryAddress.address}:${secondaryAddress.port}/`)
174
+ .then(
175
+ () => { t.assert.fail('Request should not succeed') },
176
+ () => {
177
+ t.assert.ok(flag)
178
+ }
179
+ )
180
+
181
+ // Close the server while the slow request is pending
182
+ setTimeout(fastify.close, 250)
183
+ })
184
+
185
+ // Wait 1000ms to ensure that the test is finished and async operations are
186
+ // completed
187
+ await new Promise((resolve) => { setTimeout(resolve, 1000) })
188
+ })