fastify 3.26.0 → 4.0.0-alpha.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 (129) hide show
  1. package/README.md +5 -4
  2. package/build/build-error-serializer.js +27 -0
  3. package/build/build-validation.js +49 -35
  4. package/docs/Guides/Ecosystem.md +2 -1
  5. package/docs/Guides/Prototype-Poisoning.md +3 -3
  6. package/docs/Migration-Guide-V4.md +12 -0
  7. package/docs/Reference/ContentTypeParser.md +8 -1
  8. package/docs/Reference/Errors.md +51 -6
  9. package/docs/Reference/Hooks.md +4 -7
  10. package/docs/Reference/LTS.md +5 -4
  11. package/docs/Reference/Reply.md +23 -22
  12. package/docs/Reference/Request.md +1 -3
  13. package/docs/Reference/Routes.md +17 -10
  14. package/docs/Reference/Server.md +98 -63
  15. package/docs/Reference/TypeScript.md +11 -13
  16. package/docs/Reference/Validation-and-Serialization.md +32 -54
  17. package/docs/Type-Providers.md +257 -0
  18. package/examples/hooks.js +1 -1
  19. package/examples/simple-stream.js +18 -0
  20. package/fastify.d.ts +36 -22
  21. package/fastify.js +72 -53
  22. package/lib/configValidator.js +902 -1023
  23. package/lib/contentTypeParser.js +6 -16
  24. package/lib/context.js +36 -10
  25. package/lib/decorate.js +5 -3
  26. package/lib/error-handler.js +158 -0
  27. package/lib/error-serializer.js +257 -0
  28. package/lib/errors.js +49 -10
  29. package/lib/fourOhFour.js +31 -20
  30. package/lib/handleRequest.js +10 -13
  31. package/lib/hooks.js +14 -9
  32. package/lib/noop-set.js +10 -0
  33. package/lib/pluginOverride.js +0 -3
  34. package/lib/pluginUtils.js +3 -2
  35. package/lib/reply.js +44 -163
  36. package/lib/request.js +13 -10
  37. package/lib/route.js +158 -139
  38. package/lib/schema-controller.js +3 -3
  39. package/lib/schemas.js +27 -1
  40. package/lib/server.js +219 -116
  41. package/lib/symbols.js +6 -4
  42. package/lib/validation.js +2 -1
  43. package/lib/warnings.js +2 -12
  44. package/lib/wrapThenable.js +4 -11
  45. package/package.json +40 -45
  46. package/test/404s.test.js +265 -108
  47. package/test/500s.test.js +2 -2
  48. package/test/async-await.test.js +15 -71
  49. package/test/close.test.js +39 -1
  50. package/test/content-parser.test.js +32 -0
  51. package/test/context-config.test.js +56 -4
  52. package/test/custom-http-server.test.js +14 -7
  53. package/test/custom-parser-async.test.js +0 -65
  54. package/test/custom-parser.test.js +54 -121
  55. package/test/decorator.test.js +1 -3
  56. package/test/delete.test.js +5 -5
  57. package/test/encapsulated-error-handler.test.js +50 -0
  58. package/test/esm/index.test.js +0 -14
  59. package/test/fastify-instance.test.js +4 -4
  60. package/test/fluent-schema.test.js +4 -4
  61. package/test/get.test.js +3 -3
  62. package/test/helper.js +18 -3
  63. package/test/hooks-async.test.js +14 -47
  64. package/test/hooks.on-ready.test.js +9 -4
  65. package/test/hooks.test.js +58 -99
  66. package/test/http2/closing.test.js +5 -11
  67. package/test/http2/unknown-http-method.test.js +3 -9
  68. package/test/https/custom-https-server.test.js +12 -6
  69. package/test/inject.test.js +1 -1
  70. package/test/input-validation.js +2 -2
  71. package/test/internals/all.test.js +2 -2
  72. package/test/internals/contentTypeParser.test.js +4 -4
  73. package/test/internals/handleRequest.test.js +9 -46
  74. package/test/internals/initialConfig.test.js +33 -12
  75. package/test/internals/logger.test.js +1 -1
  76. package/test/internals/reply.test.js +245 -3
  77. package/test/internals/request.test.js +13 -7
  78. package/test/internals/server.test.js +88 -0
  79. package/test/listen.test.js +84 -1
  80. package/test/logger.test.js +98 -58
  81. package/test/maxRequestsPerSocket.test.js +8 -6
  82. package/test/middleware.test.js +2 -25
  83. package/test/noop-set.test.js +19 -0
  84. package/test/nullable-validation.test.js +51 -14
  85. package/test/plugin.test.js +31 -5
  86. package/test/pretty-print.test.js +22 -10
  87. package/test/reply-error.test.js +123 -12
  88. package/test/request-error.test.js +2 -5
  89. package/test/route-hooks.test.js +17 -17
  90. package/test/route-prefix.test.js +2 -1
  91. package/test/route.test.js +216 -20
  92. package/test/router-options.test.js +1 -1
  93. package/test/schema-examples.test.js +11 -5
  94. package/test/schema-feature.test.js +24 -19
  95. package/test/schema-serialization.test.js +50 -9
  96. package/test/schema-special-usage.test.js +14 -81
  97. package/test/schema-validation.test.js +9 -9
  98. package/test/skip-reply-send.test.js +8 -8
  99. package/test/stream.test.js +23 -12
  100. package/test/throw.test.js +8 -5
  101. package/test/trust-proxy.test.js +1 -1
  102. package/test/type-provider.test.js +20 -0
  103. package/test/types/fastify.test-d.ts +12 -18
  104. package/test/types/hooks.test-d.ts +7 -3
  105. package/test/types/import.js +2 -0
  106. package/test/types/import.ts +1 -0
  107. package/test/types/instance.test-d.ts +61 -15
  108. package/test/types/logger.test-d.ts +44 -15
  109. package/test/types/route.test-d.ts +8 -2
  110. package/test/types/schema.test-d.ts +2 -39
  111. package/test/types/type-provider.test-d.ts +417 -0
  112. package/test/validation-error-handling.test.js +9 -9
  113. package/test/versioned-routes.test.js +29 -17
  114. package/test/wrapThenable.test.js +7 -6
  115. package/types/.eslintrc.json +1 -1
  116. package/types/content-type-parser.d.ts +17 -8
  117. package/types/hooks.d.ts +107 -60
  118. package/types/instance.d.ts +137 -105
  119. package/types/logger.d.ts +18 -104
  120. package/types/plugin.d.ts +10 -4
  121. package/types/register.d.ts +1 -1
  122. package/types/reply.d.ts +16 -11
  123. package/types/request.d.ts +10 -5
  124. package/types/route.d.ts +42 -31
  125. package/types/schema.d.ts +15 -1
  126. package/types/type-provider.d.ts +99 -0
  127. package/types/utils.d.ts +1 -1
  128. package/lib/schema-compilers.js +0 -12
  129. package/test/emit-warning.test.js +0 -166
@@ -15,6 +15,21 @@ const {
15
15
  kReplyIsError,
16
16
  kReplySerializerDefault
17
17
  } = require('../../lib/symbols')
18
+ const fs = require('fs')
19
+ const path = require('path')
20
+ const warning = require('../../lib/warnings')
21
+
22
+ const doGet = function (url) {
23
+ return new Promise((resolve, reject) => {
24
+ sget({ method: 'GET', url, followRedirects: false }, (err, response, body) => {
25
+ if (err) {
26
+ reject(err)
27
+ } else {
28
+ resolve({ response, body })
29
+ }
30
+ })
31
+ })
32
+ }
18
33
 
19
34
  test('Once called, Reply should return an object with methods', t => {
20
35
  t.plan(13)
@@ -37,7 +52,7 @@ test('Once called, Reply should return an object with methods', t => {
37
52
  t.equal(reply.request, request)
38
53
  })
39
54
 
40
- test('reply.send will logStream error and destroy the stream', { only: true }, t => {
55
+ test('reply.send will logStream error and destroy the stream', t => {
41
56
  t.plan(1)
42
57
  let destroyCalled
43
58
  const payload = new EventEmitter()
@@ -452,8 +467,6 @@ test('stream with content type should not send application/octet-stream', t => {
452
467
  t.plan(4)
453
468
 
454
469
  const fastify = require('../..')()
455
- const fs = require('fs')
456
- const path = require('path')
457
470
 
458
471
  const streamPath = path.join(__dirname, '..', '..', 'package.json')
459
472
  const stream = fs.createReadStream(streamPath)
@@ -477,6 +490,32 @@ test('stream with content type should not send application/octet-stream', t => {
477
490
  })
478
491
  })
479
492
 
493
+ test('stream without content type should not send application/octet-stream', t => {
494
+ t.plan(4)
495
+
496
+ const fastify = require('../..')()
497
+
498
+ const stream = fs.createReadStream(__filename)
499
+ const buf = fs.readFileSync(__filename)
500
+
501
+ fastify.get('/', function (req, reply) {
502
+ reply.send(stream)
503
+ })
504
+
505
+ fastify.listen(0, err => {
506
+ t.error(err)
507
+ fastify.server.unref()
508
+ sget({
509
+ method: 'GET',
510
+ url: 'http://localhost:' + fastify.server.address().port
511
+ }, (err, response, body) => {
512
+ t.error(err)
513
+ t.equal(response.headers['content-type'], undefined)
514
+ t.same(body, buf)
515
+ })
516
+ })
517
+ })
518
+
480
519
  test('stream using reply.raw.writeHead should return customize headers', t => {
481
520
  t.plan(6)
482
521
 
@@ -1301,6 +1340,34 @@ test('reply.header setting multiple cookies as multiple Set-Cookie headers', t =
1301
1340
  })
1302
1341
  })
1303
1342
 
1343
+ test('should emit deprecation warning when trying to modify the reply.sent property', t => {
1344
+ t.plan(4)
1345
+ const fastify = require('../..')()
1346
+
1347
+ const deprecationCode = 'FSTDEP010'
1348
+ warning.emitted.delete(deprecationCode)
1349
+
1350
+ process.removeAllListeners('warning')
1351
+ process.on('warning', onWarning)
1352
+ function onWarning (warning) {
1353
+ t.equal(warning.name, 'FastifyDeprecation')
1354
+ t.equal(warning.code, deprecationCode)
1355
+ }
1356
+
1357
+ fastify.get('/', (req, reply) => {
1358
+ reply.sent = true
1359
+
1360
+ reply.raw.end()
1361
+ })
1362
+
1363
+ fastify.inject('/', (err, res) => {
1364
+ t.error(err)
1365
+ t.pass()
1366
+
1367
+ process.removeListener('warning', onWarning)
1368
+ })
1369
+ })
1370
+
1304
1371
  test('should throw error when passing falsy value to reply.sent', t => {
1305
1372
  t.plan(4)
1306
1373
  const fastify = require('../..')()
@@ -1329,6 +1396,7 @@ test('should throw error when attempting to set reply.sent more than once', t =>
1329
1396
  reply.sent = true
1330
1397
  try {
1331
1398
  reply.sent = true
1399
+ t.fail('must throw')
1332
1400
  } catch (err) {
1333
1401
  t.equal(err.code, 'FST_ERR_REP_ALREADY_SENT')
1334
1402
  t.equal(err.message, 'Reply was already sent.')
@@ -1342,6 +1410,23 @@ test('should throw error when attempting to set reply.sent more than once', t =>
1342
1410
  })
1343
1411
  })
1344
1412
 
1413
+ test('should not throw error when attempting to set reply.sent if the underlining request was sent', t => {
1414
+ t.plan(3)
1415
+ const fastify = require('../..')()
1416
+
1417
+ fastify.get('/', function (req, reply) {
1418
+ reply.raw.end()
1419
+ t.doesNotThrow(() => {
1420
+ reply.sent = true
1421
+ })
1422
+ })
1423
+
1424
+ fastify.inject('/', (err, res) => {
1425
+ t.error(err)
1426
+ t.pass()
1427
+ })
1428
+ })
1429
+
1345
1430
  test('reply.getResponseTime() should return 0 before the timer is initialised on the reply by setting up response listeners', t => {
1346
1431
  t.plan(1)
1347
1432
  const response = { statusCode: 200 }
@@ -1727,3 +1812,160 @@ test('reply.then', t => {
1727
1812
  response.destroy(_err)
1728
1813
  })
1729
1814
  })
1815
+
1816
+ test('reply.sent should read from response.writableEnded if it is defined', t => {
1817
+ t.plan(1)
1818
+
1819
+ const reply = new Reply({ writableEnded: true }, {}, {})
1820
+
1821
+ t.equal(reply.sent, true)
1822
+ })
1823
+
1824
+ test('redirect to an invalid URL should not crash the server', async t => {
1825
+ const fastify = require('../..')()
1826
+ fastify.route({
1827
+ method: 'GET',
1828
+ url: '/redirect',
1829
+ handler: (req, reply) => {
1830
+ reply.log.warn = function mockWarn (obj, message) {
1831
+ t.equal(message, 'Invalid character in header content ["location"]')
1832
+ }
1833
+
1834
+ switch (req.query.useCase) {
1835
+ case '1':
1836
+ reply.redirect('/?key=a’b')
1837
+ break
1838
+
1839
+ case '2':
1840
+ reply.redirect(encodeURI('/?key=a’b'))
1841
+ break
1842
+
1843
+ default:
1844
+ reply.redirect('/?key=ab')
1845
+ break
1846
+ }
1847
+ }
1848
+ })
1849
+
1850
+ await fastify.listen(0)
1851
+
1852
+ {
1853
+ const { response, body } = await doGet(`http://localhost:${fastify.server.address().port}/redirect?useCase=1`)
1854
+ t.equal(response.statusCode, 500)
1855
+ t.same(JSON.parse(body), {
1856
+ statusCode: 500,
1857
+ code: 'ERR_INVALID_CHAR',
1858
+ error: 'Internal Server Error',
1859
+ message: 'Invalid character in header content ["location"]'
1860
+ })
1861
+ }
1862
+ {
1863
+ const { response } = await doGet(`http://localhost:${fastify.server.address().port}/redirect?useCase=2`)
1864
+ t.equal(response.statusCode, 302)
1865
+ t.equal(response.headers.location, '/?key=a%E2%80%99b')
1866
+ }
1867
+
1868
+ {
1869
+ const { response } = await doGet(`http://localhost:${fastify.server.address().port}/redirect?useCase=3`)
1870
+ t.equal(response.statusCode, 302)
1871
+ t.equal(response.headers.location, '/?key=ab')
1872
+ }
1873
+
1874
+ await fastify.close()
1875
+ })
1876
+
1877
+ test('invalid response headers should not crash the server', async t => {
1878
+ const fastify = require('../..')()
1879
+ fastify.route({
1880
+ method: 'GET',
1881
+ url: '/bad-headers',
1882
+ handler: (req, reply) => {
1883
+ reply.log.warn = function mockWarn (obj, message) {
1884
+ t.equal(message, 'Invalid character in header content ["smile-encoded"]', 'only the first invalid header is logged')
1885
+ }
1886
+
1887
+ reply.header('foo', '$')
1888
+ reply.header('smile-encoded', '\uD83D\uDE00')
1889
+ reply.header('smile', '😄')
1890
+ reply.header('bar', 'ƒ∂å')
1891
+
1892
+ reply.send({})
1893
+ }
1894
+ })
1895
+
1896
+ await fastify.listen(0)
1897
+
1898
+ const { response, body } = await doGet(`http://localhost:${fastify.server.address().port}/bad-headers`)
1899
+ t.equal(response.statusCode, 500)
1900
+ t.same(JSON.parse(body), {
1901
+ statusCode: 500,
1902
+ code: 'ERR_INVALID_CHAR',
1903
+ error: 'Internal Server Error',
1904
+ message: 'Invalid character in header content ["smile-encoded"]'
1905
+ })
1906
+
1907
+ await fastify.close()
1908
+ })
1909
+
1910
+ test('invalid response headers when sending back an error', async t => {
1911
+ const fastify = require('../..')()
1912
+ fastify.route({
1913
+ method: 'GET',
1914
+ url: '/bad-headers',
1915
+ handler: (req, reply) => {
1916
+ reply.log.warn = function mockWarn (obj, message) {
1917
+ t.equal(message, 'Invalid character in header content ["smile"]', 'only the first invalid header is logged')
1918
+ }
1919
+
1920
+ reply.header('smile', '😄')
1921
+ reply.send(new Error('user land error'))
1922
+ }
1923
+ })
1924
+
1925
+ await fastify.listen(0)
1926
+
1927
+ const { response, body } = await doGet(`http://localhost:${fastify.server.address().port}/bad-headers`)
1928
+ t.equal(response.statusCode, 500)
1929
+ t.same(JSON.parse(body), {
1930
+ statusCode: 500,
1931
+ code: 'ERR_INVALID_CHAR',
1932
+ error: 'Internal Server Error',
1933
+ message: 'Invalid character in header content ["smile"]'
1934
+ })
1935
+
1936
+ await fastify.close()
1937
+ })
1938
+
1939
+ test('invalid response headers and custom error handler', async t => {
1940
+ const fastify = require('../..')()
1941
+ fastify.route({
1942
+ method: 'GET',
1943
+ url: '/bad-headers',
1944
+ handler: (req, reply) => {
1945
+ reply.log.warn = function mockWarn (obj, message) {
1946
+ t.equal(message, 'Invalid character in header content ["smile"]', 'only the first invalid header is logged')
1947
+ }
1948
+
1949
+ reply.header('smile', '😄')
1950
+ reply.send(new Error('user land error'))
1951
+ }
1952
+ })
1953
+
1954
+ fastify.setErrorHandler(function (error, request, reply) {
1955
+ t.equal(error.message, 'user land error', 'custom error handler receives the error')
1956
+ reply.status(500).send({ ops: true })
1957
+ })
1958
+
1959
+ await fastify.listen(0)
1960
+
1961
+ const { response, body } = await doGet(`http://localhost:${fastify.server.address().port}/bad-headers`)
1962
+ t.equal(response.statusCode, 500)
1963
+ t.same(JSON.parse(body), {
1964
+ statusCode: 500,
1965
+ code: 'ERR_INVALID_CHAR',
1966
+ error: 'Internal Server Error',
1967
+ message: 'Invalid character in header content ["smile"]'
1968
+ })
1969
+
1970
+ await fastify.close()
1971
+ })
@@ -4,8 +4,9 @@ const { test } = require('tap')
4
4
 
5
5
  const Request = require('../../lib/request')
6
6
 
7
+ process.removeAllListeners('warning')
8
+
7
9
  test('Regular request', t => {
8
- t.plan(15)
9
10
  const headers = {
10
11
  host: 'hostname'
11
12
  }
@@ -15,22 +16,27 @@ test('Regular request', t => {
15
16
  socket: { remoteAddress: 'ip' },
16
17
  headers
17
18
  }
19
+ req.connection = req.socket
18
20
  const request = new Request('id', 'params', req, 'query', 'log')
19
21
  t.type(request, Request)
20
22
  t.equal(request.id, 'id')
21
23
  t.equal(request.params, 'params')
22
- t.same(request.raw, req)
24
+ t.equal(request.raw, req)
23
25
  t.equal(request.query, 'query')
24
26
  t.equal(request.headers, headers)
25
27
  t.equal(request.log, 'log')
26
28
  t.equal(request.ip, 'ip')
27
29
  t.equal(request.ips, undefined)
28
30
  t.equal(request.hostname, 'hostname')
29
- t.equal(request.body, null)
31
+ t.equal(request.body, undefined)
30
32
  t.equal(request.method, 'GET')
31
33
  t.equal(request.url, '/')
34
+ t.equal(request.socket, req.socket)
32
35
  t.equal(request.protocol, 'http')
33
- t.same(request.socket, req.socket)
36
+
37
+ // This will be removed, it's deprecated
38
+ t.equal(request.connection, req.connection)
39
+ t.end()
34
40
  })
35
41
 
36
42
  test('Regular request - hostname from authority', t => {
@@ -92,11 +98,11 @@ test('Request with trust proxy', t => {
92
98
  t.equal(request.ip, '2.2.2.2')
93
99
  t.same(request.ips, ['ip', '1.1.1.1', '2.2.2.2'])
94
100
  t.equal(request.hostname, 'example.com')
95
- t.equal(request.body, null)
101
+ t.equal(request.body, undefined)
96
102
  t.equal(request.method, 'GET')
97
103
  t.equal(request.url, '/')
104
+ t.equal(request.socket, req.socket)
98
105
  t.equal(request.protocol, 'http')
99
- t.same(request.socket, req.socket)
100
106
  })
101
107
 
102
108
  test('Request with trust proxy, encrypted', t => {
@@ -236,7 +242,7 @@ test('Request with undefined socket', t => {
236
242
  t.equal(request.ip, undefined)
237
243
  t.equal(request.ips, undefined)
238
244
  t.equal(request.hostname, 'hostname')
239
- t.equal(request.body, null)
245
+ t.same(request.body, null)
240
246
  t.equal(request.method, 'GET')
241
247
  t.equal(request.url, '/')
242
248
  t.equal(request.protocol, undefined)
@@ -0,0 +1,88 @@
1
+ 'use strict'
2
+
3
+ const { test } = require('tap')
4
+ const proxyquire = require('proxyquire')
5
+
6
+ const Fastify = require('../../fastify')
7
+ const createServer = require('../../lib/server')
8
+
9
+ const handler = (req, res) => {
10
+ res.writeHead(200, { 'Content-Type': 'application/json' })
11
+ res.end(JSON.stringify({ data: 'Hello World!' }))
12
+ }
13
+
14
+ test('start listening', async t => {
15
+ const { server, listen } = createServer({}, handler)
16
+ await listen.call(Fastify(), 0, 'localhost')
17
+ server.close()
18
+ t.pass('server started')
19
+ })
20
+
21
+ test('DNS errors does not stop the main server on localhost - promise interface', async t => {
22
+ const createServer = proxyquire('../../lib/server', {
23
+ dns: {
24
+ lookup: (hostname, options, cb) => {
25
+ cb(new Error('DNS error'))
26
+ }
27
+ }
28
+ })
29
+ const { server, listen } = createServer({}, handler)
30
+ await listen.call(Fastify(), 0, 'localhost')
31
+ server.close()
32
+ t.pass('server started')
33
+ })
34
+
35
+ test('DNS errors does not stop the main server on localhost - callback interface', t => {
36
+ t.plan(2)
37
+ const createServer = proxyquire('../../lib/server', {
38
+ dns: {
39
+ lookup: (hostname, options, cb) => {
40
+ cb(new Error('DNS error'))
41
+ }
42
+ }
43
+ })
44
+ const { server, listen } = createServer({}, handler)
45
+ listen.call(Fastify(), 0, 'localhost', (err) => {
46
+ t.error(err)
47
+ server.close()
48
+ t.pass('server started')
49
+ })
50
+ })
51
+
52
+ test('DNS returns empty binding', t => {
53
+ t.plan(2)
54
+ const createServer = proxyquire('../../lib/server', {
55
+ dns: {
56
+ lookup: (hostname, options, cb) => {
57
+ cb(null, [])
58
+ }
59
+ }
60
+ })
61
+ const { server, listen } = createServer({}, handler)
62
+ listen.call(Fastify(), 0, 'localhost', (err) => {
63
+ t.error(err)
64
+ server.close()
65
+ t.pass('server started')
66
+ })
67
+ })
68
+
69
+ test('DNS returns more than two binding', t => {
70
+ t.plan(2)
71
+ const createServer = proxyquire('../../lib/server', {
72
+ dns: {
73
+ lookup: (hostname, options, cb) => {
74
+ cb(null, [
75
+ { address: '::1', family: 6 },
76
+ { address: '127.0.0.1', family: 4 },
77
+ { address: '0.0.0.0', family: 4 }
78
+ ])
79
+ }
80
+ }
81
+ })
82
+ const { server, listen } = createServer({}, handler)
83
+ listen.call(Fastify(), 0, 'localhost', (err) => {
84
+ t.error(err)
85
+ server.close()
86
+ t.pass('server started')
87
+ })
88
+ })
@@ -4,8 +4,10 @@ const os = require('os')
4
4
  const path = require('path')
5
5
  const fs = require('fs')
6
6
  const { test, before } = require('tap')
7
- const Fastify = require('..')
8
7
  const dns = require('dns').promises
8
+ const dnsCb = require('dns')
9
+ const sget = require('simple-get').concat
10
+ const Fastify = require('..')
9
11
 
10
12
  let localhost
11
13
  let localhostForURL
@@ -393,3 +395,84 @@ test('listen when firstArg is string(pipe) and with backlog', async t => {
393
395
  const address = await fastify.listen('\\\\.\\pipe\\testPipe', 511)
394
396
  t.equal(address, '\\\\.\\pipe\\testPipe')
395
397
  })
398
+
399
+ test('listen on localhost binds IPv4 and IPv6 - promise interface', async t => {
400
+ const lookups = await dns.lookup('localhost', { all: true })
401
+ t.plan(2 * lookups.length)
402
+
403
+ const app = Fastify()
404
+ app.get('/', async () => 'hello localhost')
405
+ t.teardown(app.close.bind(app))
406
+ await app.listen(0, 'localhost')
407
+
408
+ for (const lookup of lookups) {
409
+ await new Promise((resolve, reject) => {
410
+ sget({
411
+ method: 'GET',
412
+ url: getUrl(app, lookup)
413
+ }, (err, response, body) => {
414
+ if (err) { return reject(err) }
415
+ t.equal(response.statusCode, 200)
416
+ t.same(body.toString(), 'hello localhost')
417
+ resolve()
418
+ })
419
+ })
420
+ }
421
+ })
422
+
423
+ test('listen on localhost binds to all interfaces (both IPv4 and IPv6 if present) - callback interface', t => {
424
+ dnsCb.lookup('localhost', { all: true }, (err, lookups) => {
425
+ t.plan(2 + (3 * lookups.length))
426
+ t.error(err)
427
+
428
+ const app = Fastify()
429
+ app.get('/', async () => 'hello localhost')
430
+ app.listen(0, 'localhost', (err) => {
431
+ t.error(err)
432
+ t.teardown(app.close.bind(app))
433
+
434
+ for (const lookup of lookups) {
435
+ sget({
436
+ method: 'GET',
437
+ url: getUrl(app, lookup)
438
+ }, (err, response, body) => {
439
+ t.error(err)
440
+ t.equal(response.statusCode, 200)
441
+ t.same(body.toString(), 'hello localhost')
442
+ })
443
+ }
444
+ })
445
+ })
446
+ })
447
+
448
+ test('addresses getter', async t => {
449
+ t.plan(4)
450
+ const app = Fastify()
451
+ app.get('/', async () => 'hello localhost')
452
+
453
+ t.same(app.addresses(), [], 'before ready')
454
+ await app.ready()
455
+
456
+ t.same(app.addresses(), [], 'after ready')
457
+ await app.listen(0, 'localhost')
458
+ const { port } = app.server.address()
459
+ const localAddresses = await dns.lookup('localhost', { all: true })
460
+ for (const address of localAddresses) {
461
+ address.port = port
462
+ address.family = 'IPv' + address.family
463
+ }
464
+ localAddresses.sort((a, b) => a.address.localeCompare(b.address))
465
+ t.same(app.addresses().sort((a, b) => a.address.localeCompare(b.address)), localAddresses, 'after listen')
466
+
467
+ await app.close()
468
+ t.same(app.addresses(), [], 'after close')
469
+ })
470
+
471
+ function getUrl (fastify, lookup) {
472
+ const { port } = fastify.server.address()
473
+ if (lookup.family === 6) {
474
+ return `http://[${lookup.address}]:${port}/`
475
+ } else {
476
+ return `http://${lookup.address}:${port}/`
477
+ }
478
+ }