@fastify/static
Version:
Plugin for serving static files as fast as possible.
1,518 lines (1,201 loc) • 116 kB
JavaScript
'use strict'
/* eslint n/no-deprecated-api: "off" */
const path = require('node:path')
const fs = require('node:fs')
const url = require('node:url')
const http = require('node:http')
const { test } = require('node:test')
const Fastify = require('fastify')
const compress = require('@fastify/compress')
const concat = require('concat-stream')
const pino = require('pino')
const proxyquire = require('proxyquire')
const fastifyStatic = require('../')
const indexContent = fs
.readFileSync('./test/static/index.html')
.toString('utf8')
const index2Content = fs
.readFileSync('./test/static2/index.html')
.toString('utf8')
const foobarContent = fs
.readFileSync('./test/static/foobar.html')
.toString('utf8')
const deepContent = fs
.readFileSync('./test/static/deep/path/for/test/purpose/foo.html')
.toString('utf8')
const innerIndex = fs
.readFileSync('./test/static/deep/path/for/test/index.html')
.toString('utf8')
const allThreeBr = fs.readFileSync(
'./test/static-pre-compressed/all-three.html.br'
)
const allThreeGzip = fs.readFileSync(
'./test/static-pre-compressed/all-three.html.gz'
)
const gzipOnly = fs.readFileSync(
'./test/static-pre-compressed/gzip-only.html.gz'
)
const indexBr = fs.readFileSync(
'./test/static-pre-compressed/index.html.br'
)
const dirIndexBr = fs.readFileSync(
'./test/static-pre-compressed/dir/index.html.br'
)
const dirIndexGz = fs.readFileSync(
'./test/static-pre-compressed/dir-gz/index.html.gz'
)
const uncompressedStatic = fs
.readFileSync('./test/static-pre-compressed/uncompressed.html')
.toString('utf8')
const fooContent = fs.readFileSync('./test/static/foo.html').toString('utf8')
const barContent = fs.readFileSync('./test/static2/bar.html').toString('utf8')
const jsonHiddenContent = fs.readFileSync('./test/static-hidden/.hidden/sample.json').toString('utf8')
const GENERIC_RESPONSE_CHECK_COUNT = 5
function genericResponseChecks (t, response) {
t.assert.ok(/text\/(html|css)/.test(response.headers.get?.('content-type') ?? response.headers['content-type']))
t.assert.ok(response.headers.get?.('etag') ?? response.headers.etag)
t.assert.ok(response.headers.get?.('last-modified') ?? response.headers['last-modified'])
t.assert.ok(response.headers.get?.('date') ?? response.headers.date)
t.assert.ok(response.headers.get?.('cache-control') ?? response.headers['cache-control'])
}
const GENERIC_ERROR_RESPONSE_CHECK_COUNT = 2
function genericErrorResponseChecks (t, response) {
t.assert.deepStrictEqual(response.headers.get?.('content-type') ?? response.headers['content-type'], 'application/json; charset=utf-8')
t.assert.ok(response.headers.get?.('date') ?? response.headers.date)
}
if (typeof Promise.withResolvers === 'undefined') {
Promise.withResolvers = function () {
let promiseResolve, promiseReject
const promise = new Promise((resolve, reject) => {
promiseResolve = resolve
promiseReject = reject
})
return { promise, resolve: promiseResolve, reject: promiseReject }
}
}
test('register /static prefixAvoidTrailingSlash', async t => {
t.plan(11)
const pluginOptions = {
root: path.join(__dirname, '/static'),
prefix: '/static',
prefixAvoidTrailingSlash: true
}
const fastify = Fastify()
fastify.register(fastifyStatic, pluginOptions)
t.after(() => fastify.close())
await fastify.listen({ port: 0 })
fastify.server.unref()
await t.test('/static/index.html', async (t) => {
t.plan(3 + GENERIC_RESPONSE_CHECK_COUNT)
const response = await fetch('http://localhost:' + fastify.server.address().port + '/static/index.html')
t.assert.ok(response.ok)
t.assert.deepStrictEqual(response.status, 200)
t.assert.deepStrictEqual(await response.text(), indexContent)
genericResponseChecks(t, response)
})
await t.test('/static/index.css', async (t) => {
t.plan(2 + GENERIC_RESPONSE_CHECK_COUNT)
const response = await fetch('http://localhost:' + fastify.server.address().port + '/static/index.css')
t.assert.ok(response.ok)
t.assert.deepStrictEqual(response.status, 200)
genericResponseChecks(t, response)
})
await t.test('/static/', async (t) => {
t.plan(3 + GENERIC_RESPONSE_CHECK_COUNT)
const response = await fetch('http://localhost:' + fastify.server.address().port + '/static/')
t.assert.ok(response.ok)
t.assert.deepStrictEqual(response.status, 200)
t.assert.deepStrictEqual(await response.text(), indexContent)
genericResponseChecks(t, response)
})
await t.test('/static', async (t) => {
t.plan(3 + GENERIC_RESPONSE_CHECK_COUNT)
const response = await fetch('http://localhost:' + fastify.server.address().port + '/static')
t.assert.ok(response.ok)
t.assert.deepStrictEqual(response.status, 200)
t.assert.deepStrictEqual(await response.text(), indexContent)
genericResponseChecks(t, response)
})
await t.test('/static/deep/path/for/test/purpose/foo.html', async (t) => {
t.plan(3 + GENERIC_RESPONSE_CHECK_COUNT)
const response = await fetch('http://localhost:' + fastify.server.address().port + '/static/deep/path/for/test/purpose/foo.html')
t.assert.ok(response.ok)
t.assert.deepStrictEqual(response.status, 200)
t.assert.deepStrictEqual(await response.text(), deepContent)
genericResponseChecks(t, response)
})
await t.test('/static/deep/path/for/test/', async (t) => {
t.plan(3 + GENERIC_RESPONSE_CHECK_COUNT)
const response = await fetch('http://localhost:' + fastify.server.address().port + '/static/deep/path/for/test/')
t.assert.ok(response.ok)
t.assert.deepStrictEqual(response.status, 200)
t.assert.deepStrictEqual(await response.text(), innerIndex)
genericResponseChecks(t, response)
})
await t.test('/static/this/path/for/test', async (t) => {
t.plan(2 + GENERIC_ERROR_RESPONSE_CHECK_COUNT)
const response = await fetch('http://localhost:' + fastify.server.address().port + '/static/this/path/for/test')
t.assert.ok(!response.ok)
t.assert.deepStrictEqual(response.status, 404)
genericErrorResponseChecks(t, response)
})
await t.test('/static/this/path/doesnt/exist.html', async (t) => {
t.plan(2 + GENERIC_ERROR_RESPONSE_CHECK_COUNT)
const response = await fetch('http://localhost:' + fastify.server.address().port + '/static/this/path/doesnt/exist.html')
t.assert.ok(!response.ok)
t.assert.deepStrictEqual(response.status, 404)
genericErrorResponseChecks(t, response)
})
await t.test('/static/../index.js', async (t) => {
t.plan(2 + GENERIC_ERROR_RESPONSE_CHECK_COUNT)
const response = await fetch('http://localhost:' + fastify.server.address().port + '/static/../index.js')
t.assert.ok(!response.ok)
t.assert.deepStrictEqual(response.status, 404)
genericErrorResponseChecks(t, response)
})
await t.test('file not exposed outside of the plugin', async (t) => {
t.plan(2)
const response = await fetch('http://localhost:' + fastify.server.address().port + '/foobar.html')
t.assert.ok(!response.ok)
t.assert.deepStrictEqual(response.status, 404)
})
await t.test('file retrieve with HEAD method', async t => {
t.plan(3 + GENERIC_RESPONSE_CHECK_COUNT)
const response = await fetch('http://localhost:' + fastify.server.address().port + '/static/index.html', {
method: 'HEAD'
})
t.assert.ok(response.ok)
t.assert.deepStrictEqual(response.status, 200)
t.assert.deepStrictEqual(await response.text(), '')
genericResponseChecks(t, response)
})
})
test('register /static', async (t) => {
t.plan(10)
const pluginOptions = {
root: path.join(__dirname, '/static'),
prefix: '/static'
}
const fastify = Fastify()
fastify.register(fastifyStatic, pluginOptions)
t.after(() => fastify.close())
await fastify.listen({ port: 0 })
fastify.server.unref()
await t.test('/static/index.html', async (t) => {
t.plan(3 + GENERIC_RESPONSE_CHECK_COUNT)
const response = await fetch('http://localhost:' + fastify.server.address().port + '/static/index.html')
t.assert.ok(response.ok)
t.assert.deepStrictEqual(response.status, 200)
t.assert.deepStrictEqual(await response.text(), indexContent)
genericResponseChecks(t, response)
})
await t.test('/static/index.css', async (t) => {
t.plan(2 + GENERIC_RESPONSE_CHECK_COUNT)
const response = await fetch('http://localhost:' + fastify.server.address().port + '/static/index.css')
t.assert.ok(response.ok)
t.assert.deepStrictEqual(response.status, 200)
genericResponseChecks(t, response)
})
await t.test('/static/', async (t) => {
t.plan(3 + GENERIC_RESPONSE_CHECK_COUNT)
const response = await fetch('http://localhost:' + fastify.server.address().port + '/static/')
t.assert.ok(response.ok)
t.assert.deepStrictEqual(response.status, 200)
t.assert.deepStrictEqual(await response.text(), indexContent)
genericResponseChecks(t, response)
})
await t.test('/static', async (t) => {
t.plan(2)
const response = await fetch('http://localhost:' + fastify.server.address().port + '/static')
t.assert.ok(!response.ok)
t.assert.deepStrictEqual(response.status, 404)
})
await t.test('/static/deep/path/for/test/purpose/foo.html', async (t) => {
t.plan(3 + GENERIC_RESPONSE_CHECK_COUNT)
const response = await fetch('http://localhost:' + fastify.server.address().port + '/static/deep/path/for/test/purpose/foo.html')
t.assert.ok(response.ok)
t.assert.deepStrictEqual(response.status, 200)
t.assert.deepStrictEqual(await response.text(), deepContent)
genericResponseChecks(t, response)
})
await t.test('/static/deep/path/for/test/', async (t) => {
t.plan(3 + GENERIC_RESPONSE_CHECK_COUNT)
const response = await fetch('http://localhost:' + fastify.server.address().port + '/static/deep/path/for/test/')
t.assert.ok(response.ok)
t.assert.deepStrictEqual(response.status, 200)
t.assert.deepStrictEqual(await response.text(), innerIndex)
genericResponseChecks(t, response)
})
await t.test('/static/this/path/for/test', async (t) => {
t.plan(2 + GENERIC_ERROR_RESPONSE_CHECK_COUNT)
const response = await fetch('http://localhost:' + fastify.server.address().port + '/static/this/path/for/test')
t.assert.ok(!response.ok)
t.assert.deepStrictEqual(response.status, 404)
genericErrorResponseChecks(t, response)
})
await t.test('/static/this/path/doesnt/exist.html', async (t) => {
t.plan(2 + GENERIC_ERROR_RESPONSE_CHECK_COUNT)
const response = await fetch('http://localhost:' + fastify.server.address().port + '/static/this/path/doesnt/exist.html')
t.assert.ok(!response.ok)
t.assert.deepStrictEqual(response.status, 404)
genericErrorResponseChecks(t, response)
})
await t.test('/static/../index.js', async (t) => {
t.plan(2 + GENERIC_ERROR_RESPONSE_CHECK_COUNT)
const response = await fetch('http://localhost:' + fastify.server.address().port + '/static/../index.js', {
redirect: 'error'
})
t.assert.ok(!response.ok)
t.assert.deepStrictEqual(response.status, 404)
genericErrorResponseChecks(t, response)
})
await t.test('file not exposed outside of the plugin', async (t) => {
t.plan(2)
const response = await fetch('http://localhost:' + fastify.server.address().port + '/foobar.html')
t.assert.ok(!response.ok)
t.assert.deepStrictEqual(response.status, 404)
})
})
test('register /static/', async t => {
t.plan(11)
const pluginOptions = {
root: path.join(__dirname, '/static'),
prefix: '/static/'
}
const fastify = Fastify()
fastify.register(fastifyStatic, pluginOptions)
t.after(() => fastify.close())
await fastify.listen({ port: 0 })
fastify.server.unref()
await t.test('/static/index.html', async (t) => {
t.plan(3 + GENERIC_RESPONSE_CHECK_COUNT)
const response = await fetch('http://localhost:' + fastify.server.address().port + '/static/index.html')
t.assert.ok(response.ok)
t.assert.deepStrictEqual(response.status, 200)
t.assert.deepStrictEqual(await response.text(), indexContent)
genericResponseChecks(t, response)
})
await t.test('/static/index.html', async t => {
t.plan(3 + GENERIC_RESPONSE_CHECK_COUNT)
const response = await fetch('http://localhost:' + fastify.server.address().port + '/static/index.html', {
method: 'HEAD'
})
t.assert.ok(response.ok)
t.assert.deepStrictEqual(response.status, 200)
t.assert.deepStrictEqual(await response.text(), '')
genericResponseChecks(t, response)
})
await t.test('/static/index.css', async (t) => {
t.plan(2 + GENERIC_RESPONSE_CHECK_COUNT)
const response = await fetch('http://localhost:' + fastify.server.address().port + '/static/index.css')
t.assert.ok(response.ok)
t.assert.deepStrictEqual(response.status, 200)
genericResponseChecks(t, response)
})
await t.test('/static/', async (t) => {
t.plan(3 + GENERIC_RESPONSE_CHECK_COUNT)
const response = await fetch('http://localhost:' + fastify.server.address().port + '/static/')
t.assert.ok(response.ok)
t.assert.deepStrictEqual(response.status, 200)
t.assert.deepStrictEqual(await response.text(), indexContent)
genericResponseChecks(t, response)
})
await t.test('/static', async (t) => {
t.plan(2)
const response = await fetch('http://localhost:' + fastify.server.address().port + '/static')
t.assert.ok(!response.ok)
t.assert.deepStrictEqual(response.status, 404)
})
await t.test('/static/deep/path/for/test/purpose/foo.html', async (t) => {
t.plan(3 + GENERIC_RESPONSE_CHECK_COUNT)
const response = await fetch('http://localhost:' + fastify.server.address().port + '/static/deep/path/for/test/purpose/foo.html')
t.assert.ok(response.ok)
t.assert.deepStrictEqual(response.status, 200)
t.assert.deepStrictEqual(await response.text(), deepContent)
genericResponseChecks(t, response)
})
await t.test('/static/deep/path/for/test/', async (t) => {
t.plan(3 + GENERIC_RESPONSE_CHECK_COUNT)
const response = await fetch('http://localhost:' + fastify.server.address().port + '/static/deep/path/for/test/')
t.assert.ok(response.ok)
t.assert.deepStrictEqual(response.status, 200)
t.assert.deepStrictEqual(await response.text(), innerIndex)
genericResponseChecks(t, response)
})
await t.test('/static/this/path/for/test', async (t) => {
t.plan(2 + GENERIC_ERROR_RESPONSE_CHECK_COUNT)
const response = await fetch('http://localhost:' + fastify.server.address().port + '/static/this/path/for/test')
t.assert.ok(!response.ok)
t.assert.deepStrictEqual(response.status, 404)
genericErrorResponseChecks(t, response)
})
await t.test('/static/this/path/doesnt/exist.html', async (t) => {
t.plan(2 + GENERIC_ERROR_RESPONSE_CHECK_COUNT)
const response = await fetch('http://localhost:' + fastify.server.address().port + '/static/this/path/doesnt/exist.html')
t.assert.ok(!response.ok)
t.assert.deepStrictEqual(response.status, 404)
genericErrorResponseChecks(t, response)
})
await t.test('/static/../index.js', async (t) => {
t.plan(2 + GENERIC_ERROR_RESPONSE_CHECK_COUNT)
const response = await fetch('http://localhost:' + fastify.server.address().port + '/static/../index.js')
t.assert.ok(!response.ok)
t.assert.deepStrictEqual(response.status, 404)
genericErrorResponseChecks(t, response)
})
await t.test('304', async t => {
t.plan(5 + GENERIC_RESPONSE_CHECK_COUNT)
const response = await fetch('http://localhost:' + fastify.server.address().port + '/static/index.html')
t.assert.ok(response.ok)
t.assert.deepStrictEqual(response.status, 200)
t.assert.deepStrictEqual(await response.text(), indexContent)
genericResponseChecks(t, response)
const response2 = await fetch('http://localhost:' + fastify.server.address().port + '/static/index.html', {
headers: {
'if-none-match': response.headers.get('etag')
},
cache: 'no-cache'
})
t.assert.ok(!response2.ok)
t.assert.deepStrictEqual(response2.status, 304)
})
})
test('register /static and /static2', async (t) => {
t.plan(4)
const pluginOptions = {
root: [path.join(__dirname, '/static'), path.join(__dirname, '/static2')],
prefix: '/static'
}
const fastify = Fastify()
fastify.register(fastifyStatic, pluginOptions)
fastify.get('/foo', (_req, rep) => {
rep.sendFile('foo.html')
})
fastify.get('/bar', (_req, rep) => {
rep.sendFile('bar.html')
})
t.after(() => fastify.close())
await fastify.listen({ port: 0 })
fastify.server.unref()
await t.test('/static/index.html', async (t) => {
t.plan(4 + GENERIC_RESPONSE_CHECK_COUNT)
const response = await fetch('http://localhost:' + fastify.server.address().port + '/static/index.html')
t.assert.ok(response.ok)
t.assert.deepStrictEqual(response.status, 200)
const responseText = await response.text()
t.assert.deepStrictEqual(responseText, indexContent)
t.assert.notDeepStrictEqual(responseText, index2Content)
genericResponseChecks(t, response)
})
await t.test('/static/bar.html', async (t) => {
t.plan(3 + GENERIC_RESPONSE_CHECK_COUNT)
const response = await fetch('http://localhost:' + fastify.server.address().port + '/static/bar.html')
t.assert.ok(response.ok)
t.assert.deepStrictEqual(response.status, 200)
t.assert.deepStrictEqual(await response.text(), barContent)
genericResponseChecks(t, response)
})
await t.test('sendFile foo.html', async (t) => {
t.plan(3 + GENERIC_RESPONSE_CHECK_COUNT)
const response = await fetch('http://localhost:' + fastify.server.address().port + '/foo')
t.assert.ok(response.ok)
t.assert.deepStrictEqual(response.status, 200)
t.assert.deepStrictEqual(await response.text(), fooContent)
genericResponseChecks(t, response)
})
await t.test('sendFile bar.html', async (t) => {
t.plan(3 + GENERIC_RESPONSE_CHECK_COUNT)
const response = await fetch('http://localhost:' + fastify.server.address().port + '/bar')
t.assert.ok(response.ok)
t.assert.deepStrictEqual(response.status, 200)
t.assert.deepStrictEqual(await response.text(), barContent)
genericResponseChecks(t, response)
})
})
test('register /static with constraints', async (t) => {
t.plan(2)
const pluginOptions = {
root: path.join(__dirname, '/static'),
prefix: '/static',
constraints: {
version: '1.2.0'
}
}
const fastify = Fastify()
fastify.register(fastifyStatic, pluginOptions)
t.after(() => fastify.close())
await fastify.listen({ port: 0 })
fastify.server.unref()
await t.test('example.com/static/index.html', async (t) => {
t.plan(3 + GENERIC_RESPONSE_CHECK_COUNT)
const response = await fetch('http://localhost:' + fastify.server.address().port + '/static/index.html', {
headers: {
'accept-version': '1.x'
}
})
t.assert.ok(response.ok)
t.assert.deepStrictEqual(response.status, 200)
t.assert.deepStrictEqual(await response.text(), indexContent)
genericResponseChecks(t, response)
})
await t.test('not-example.com/static/index.html', async (t) => {
t.plan(2 + GENERIC_ERROR_RESPONSE_CHECK_COUNT)
const response = await fetch('http://localhost:' + fastify.server.address().port + '/static/index.html', {
headers: {
'accept-version': '2.x'
}
})
t.assert.ok(!response.ok)
t.assert.deepStrictEqual(response.status, 404)
genericErrorResponseChecks(t, response)
})
})
test('payload.path is set', async (t) => {
t.plan(2)
const pluginOptions = {
root: path.join(__dirname, '/static'),
prefix: '/static/'
}
const fastify = Fastify()
let gotFilename
fastify.register(fastifyStatic, pluginOptions)
fastify.addHook('onSend', function (_req, _reply, payload, next) {
gotFilename = payload.path
next()
})
t.after(() => fastify.close())
await fastify.listen({ port: 0 })
fastify.server.unref()
await t.test('/static/index.html', async (t) => {
t.plan(5 + GENERIC_RESPONSE_CHECK_COUNT)
const response = await fetch('http://localhost:' + fastify.server.address().port + '/static/index.html')
t.assert.ok(response.ok)
t.assert.deepStrictEqual(response.status, 200)
t.assert.deepStrictEqual(await response.text(), indexContent)
t.assert.deepStrictEqual(typeof gotFilename, 'string')
t.assert.deepStrictEqual(gotFilename, path.join(pluginOptions.root, 'index.html'))
genericResponseChecks(t, response)
})
await t.test('/static/this/path/doesnt/exist.html', async (t) => {
t.plan(3 + GENERIC_ERROR_RESPONSE_CHECK_COUNT)
const response = await fetch('http://localhost:' + fastify.server.address().port + '/static/this/path/doesnt/exist.html')
t.assert.ok(!response.ok)
t.assert.deepStrictEqual(response.status, 404)
t.assert.deepStrictEqual(typeof gotFilename, 'undefined')
genericErrorResponseChecks(t, response)
})
})
test('error responses can be customized with fastify.setErrorHandler()', async t => {
t.plan(1)
const pluginOptions = {
root: path.join(__dirname, '/static')
}
const fastify = Fastify()
fastify.setErrorHandler(function errorHandler (err, _request, reply) {
reply.code(403).type('text/plain').send(`${err.statusCode} Custom error message`)
})
fastify.get('/index.js', (_, reply) => {
return reply.type('text/html').sendFile('foo.js')
})
fastify.register(fastifyStatic, pluginOptions)
t.after(() => fastify.close())
await fastify.listen({ port: 0 })
fastify.server.unref()
await t.test('/../index.js', async t => {
t.plan(4)
const response = await fetch('http://localhost:' + fastify.server.address().port + '/index.js')
t.assert.ok(!response.ok)
t.assert.deepStrictEqual(response.status, 403)
t.assert.deepStrictEqual(response.headers.get('content-type'), 'text/plain')
t.assert.deepStrictEqual(await response.text(), '500 Custom error message')
})
})
test('not found responses can be customized with fastify.setNotFoundHandler()', async t => {
t.plan(1)
const pluginOptions = {
root: path.join(__dirname, '/static')
}
const fastify = Fastify()
fastify.setNotFoundHandler(function notFoundHandler (request, reply) {
reply.code(404).type('text/plain').send(request.raw.url + ' Not Found')
})
fastify.register(fastifyStatic, pluginOptions)
t.after(() => fastify.close())
await fastify.listen({ port: 0 })
fastify.server.unref()
await t.test('/path/does/not/exist.html', async t => {
t.plan(4)
const response = await fetch('http://localhost:' + fastify.server.address().port + '/path/does/not/exist.html')
t.assert.ok(!response.ok)
t.assert.deepStrictEqual(response.status, 404)
t.assert.deepStrictEqual(response.headers.get('content-type'), 'text/plain')
t.assert.deepStrictEqual(await response.text(), '/path/does/not/exist.html Not Found')
})
})
test('fastify.setNotFoundHandler() is called for dotfiles when send is configured to ignore dotfiles', async t => {
t.plan(1)
const pluginOptions = {
root: path.join(__dirname, '/static'),
send: {
dotfiles: 'ignore'
}
}
const fastify = Fastify()
fastify.setNotFoundHandler(function notFoundHandler (request, reply) {
reply.code(404).type('text/plain').send(request.raw.url + ' Not Found')
})
fastify.register(fastifyStatic, pluginOptions)
t.after(() => fastify.close())
await fastify.listen({ port: 0 })
fastify.server.unref()
// Requesting files with a leading dot doesn't follow the same code path as
// other 404 errors
await t.test('/path/does/not/.exist.html', async t => {
t.plan(4)
const response = await fetch('http://localhost:' + fastify.server.address().port + '/path/does/not/.exist.html')
t.assert.ok(!response.ok)
t.assert.deepStrictEqual(response.status, 404)
t.assert.deepStrictEqual(response.headers.get('content-type'), 'text/plain')
t.assert.deepStrictEqual(await response.text(), '/path/does/not/.exist.html Not Found')
})
})
test('serving disabled', async (t) => {
t.plan(2)
const pluginOptions = {
root: path.join(__dirname, '/static'),
prefix: '/static/',
serve: false
}
const fastify = Fastify()
fastify.register(fastifyStatic, pluginOptions)
t.after(() => fastify.close())
fastify.get('/foo/bar', (_request, reply) => {
reply.sendFile('index.html')
})
t.after(() => fastify.close())
await fastify.listen({ port: 0 })
fastify.server.unref()
await t.test('/static/index.html not found', async (t) => {
t.plan(2)
const response = await fetch('http://localhost:' + fastify.server.address().port + '/static/index.html')
t.assert.ok(!response.ok)
t.assert.deepStrictEqual(response.status, 404)
})
await t.test('/static/index.html via sendFile found', async (t) => {
t.plan(3 + GENERIC_RESPONSE_CHECK_COUNT)
const response = await fetch('http://localhost:' + fastify.server.address().port + '/foo/bar')
t.assert.ok(response.ok)
t.assert.deepStrictEqual(response.status, 200)
t.assert.deepStrictEqual(await response.text(), indexContent)
genericResponseChecks(t, response)
})
})
test('sendFile', async (t) => {
t.plan(4)
const pluginOptions = {
root: path.join(__dirname, '/static'),
prefix: '/static'
}
const fastify = Fastify()
const maxAge = Math.round(Math.random() * 10) * 10000
fastify.register(fastifyStatic, pluginOptions)
t.after(() => fastify.close())
fastify.get('/foo/bar', function (_req, reply) {
reply.sendFile('/index.html')
})
fastify.get('/root/path/override/test', (_request, reply) => {
reply.sendFile(
'/foo.html',
path.join(__dirname, 'static', 'deep', 'path', 'for', 'test', 'purpose')
)
})
fastify.get('/foo/bar/options/override/test', function (_req, reply) {
reply.sendFile('/index.html', { maxAge })
})
await fastify.listen({ port: 0 })
fastify.server.unref()
await t.test('reply.sendFile()', async (t) => {
t.plan(3 + GENERIC_RESPONSE_CHECK_COUNT)
const response = await fetch('http://localhost:' + fastify.server.address().port + '/foo/bar')
t.assert.ok(response.ok)
t.assert.deepStrictEqual(response.status, 200)
t.assert.deepStrictEqual(await response.text(), indexContent)
genericResponseChecks(t, response)
})
await t.test('reply.sendFile() with rootPath', async (t) => {
t.plan(3 + GENERIC_RESPONSE_CHECK_COUNT)
const response = await fetch('http://localhost:' + fastify.server.address().port + '/root/path/override/test')
t.assert.ok(response.ok)
t.assert.deepStrictEqual(response.status, 200)
t.assert.deepStrictEqual(await response.text(), deepContent)
genericResponseChecks(t, response)
})
await t.test('reply.sendFile() again without root path', async (t) => {
t.plan(3 + GENERIC_RESPONSE_CHECK_COUNT)
const response = await fetch('http://localhost:' + fastify.server.address().port + '/foo/bar')
t.assert.ok(response.ok)
t.assert.deepStrictEqual(response.status, 200)
t.assert.deepStrictEqual(await response.text(), indexContent)
genericResponseChecks(t, response)
})
await t.test('reply.sendFile() with options', async (t) => {
t.plan(4 + GENERIC_RESPONSE_CHECK_COUNT)
const response = await fetch('http://localhost:' + fastify.server.address().port + '/foo/bar/options/override/test')
t.assert.ok(response.ok)
t.assert.deepStrictEqual(response.status, 200)
t.assert.deepStrictEqual(await response.text(), indexContent)
t.assert.deepStrictEqual(response.headers.get('cache-control'), `public, max-age=${maxAge / 1000}`)
genericResponseChecks(t, response)
})
})
test('sendFile disabled', async (t) => {
t.plan(1)
const pluginOptions = {
root: path.join(__dirname, '/static'),
prefix: '/static',
decorateReply: false
}
const fastify = Fastify()
fastify.register(fastifyStatic, pluginOptions)
t.after(() => fastify.close())
fastify.get('/foo/bar', function (_req, reply) {
if (reply.sendFile === undefined) {
reply.send('pass')
} else {
reply.send('fail')
}
})
await fastify.listen({ port: 0 })
fastify.server.unref()
await t.test('reply.sendFile undefined', async (t) => {
t.plan(3)
const response = await fetch('http://localhost:' + fastify.server.address().port + '/foo/bar')
t.assert.ok(response.ok)
t.assert.deepStrictEqual(response.status, 200)
t.assert.deepStrictEqual(await response.text(), 'pass')
})
})
test('allowedPath option - pathname', async (t) => {
t.plan(2)
const pluginOptions = {
root: path.join(__dirname, '/static'),
allowedPath: (pathName) => pathName !== '/foobar.html'
}
const fastify = Fastify()
fastify.register(fastifyStatic, pluginOptions)
t.after(() => fastify.close())
await fastify.listen({ port: 0 })
fastify.server.unref()
await t.test('/foobar.html not found', async (t) => {
t.plan(2 + GENERIC_ERROR_RESPONSE_CHECK_COUNT)
const response = await fetch('http://localhost:' + fastify.server.address().port + '/foobar.html')
t.assert.ok(!response.ok)
t.assert.deepStrictEqual(response.status, 404)
genericErrorResponseChecks(t, response)
})
await t.test('/index.css found', async (t) => {
t.plan(2)
const response = await fetch('http://localhost:' + fastify.server.address().port + '/index.css')
t.assert.ok(response.ok)
t.assert.deepStrictEqual(response.status, 200)
})
})
test('allowedPath option - request', async (t) => {
t.plan(2)
const pluginOptions = {
root: path.join(__dirname, '/static'),
allowedPath: (_pathName, _root, request) => request.query.key === 'temporaryKey'
}
const fastify = Fastify()
fastify.register(fastifyStatic, pluginOptions)
t.after(() => fastify.close())
await fastify.listen({ port: 0 })
fastify.server.unref()
await t.test('/foobar.html not found', async (t) => {
t.plan(2 + GENERIC_ERROR_RESPONSE_CHECK_COUNT)
const response = await fetch('http://localhost:' + fastify.server.address().port + '/foobar.html')
t.assert.ok(!response.ok)
t.assert.deepStrictEqual(response.status, 404)
genericErrorResponseChecks(t, response)
})
await t.test('/index.css found', async (t) => {
t.plan(2)
const response = await fetch('http://localhost:' + fastify.server.address().port + '/index.css?key=temporaryKey')
t.assert.ok(response.ok)
t.assert.deepStrictEqual(response.status, 200)
})
})
test('download', async (t) => {
t.plan(6)
const pluginOptions = {
root: path.join(__dirname, '/static'),
prefix: '/static'
}
const fastify = Fastify()
fastify.register(fastifyStatic, pluginOptions)
t.after(() => fastify.close())
fastify.get('/foo/bar', function (_req, reply) {
reply.download('/index.html')
})
fastify.get('/foo/bar/change', function (_req, reply) {
reply.download('/index.html', 'hello-world.html')
})
fastify.get('/foo/bar/override', function (_req, reply) {
reply.download('/index.html', 'hello-world.html', {
maxAge: '2 hours',
immutable: true
})
})
fastify.get('/foo/bar/override/2', function (_req, reply) {
reply.download('/index.html', { acceptRanges: false })
})
fastify.get('/root/path/override/test', (_request, reply) => {
reply.download('/foo.html', {
root: path.join(
__dirname,
'static',
'deep',
'path',
'for',
'test',
'purpose'
)
})
})
fastify.get('/root/path/override/test/change', (_request, reply) => {
reply.download('/foo.html', 'hello-world.html', {
root: path.join(
__dirname,
'static',
'deep',
'path',
'for',
'test',
'purpose'
)
})
})
await fastify.listen({ port: 0 })
fastify.server.unref()
await t.test('reply.download()', async (t) => {
t.plan(4 + GENERIC_RESPONSE_CHECK_COUNT)
const response = await fetch('http://localhost:' + fastify.server.address().port + '/foo/bar')
t.assert.ok(response.ok)
t.assert.deepStrictEqual(response.headers.get('content-disposition'), 'attachment; filename="index.html"')
t.assert.deepStrictEqual(response.status, 200)
t.assert.deepStrictEqual(await response.text(), indexContent)
genericResponseChecks(t, response)
})
await t.test('reply.download() with fileName', async t => {
t.plan(4 + GENERIC_RESPONSE_CHECK_COUNT)
const response = await fetch('http://localhost:' + fastify.server.address().port + '/foo/bar/change')
t.assert.ok(response.ok)
t.assert.deepStrictEqual(response.status, 200)
t.assert.deepStrictEqual(response.headers.get('content-disposition'), 'attachment; filename="hello-world.html"')
t.assert.deepStrictEqual(await response.text(), indexContent)
genericResponseChecks(t, response)
})
await t.test('reply.download() with fileName - override', async (t) => {
t.plan(4 + GENERIC_RESPONSE_CHECK_COUNT)
const response = await fetch('http://localhost:' + fastify.server.address().port + '/root/path/override/test')
t.assert.ok(response.ok)
t.assert.deepStrictEqual(response.status, 200)
t.assert.deepStrictEqual(response.headers.get('content-disposition'), 'attachment; filename="foo.html"')
t.assert.deepStrictEqual(await response.text(), deepContent)
genericResponseChecks(t, response)
})
await t.test('reply.download() with custom opts', async (t) => {
t.plan(5 + GENERIC_RESPONSE_CHECK_COUNT)
const response = await fetch('http://localhost:' + fastify.server.address().port + '/foo/bar/override')
t.assert.ok(response.ok)
t.assert.deepStrictEqual(response.status, 200)
t.assert.deepStrictEqual(response.headers.get('content-disposition'), 'attachment; filename="hello-world.html"')
t.assert.deepStrictEqual(response.headers.get('cache-control'), 'public, max-age=7200, immutable')
t.assert.deepStrictEqual(await response.text(), indexContent)
genericResponseChecks(t, response)
})
await t.test('reply.download() with custom opts (2)', async (t) => {
t.plan(5 + GENERIC_RESPONSE_CHECK_COUNT)
const response = await fetch('http://localhost:' + fastify.server.address().port + '/foo/bar/override/2')
t.assert.ok(response.ok)
t.assert.deepStrictEqual(response.status, 200)
t.assert.deepStrictEqual(response.headers.get('content-disposition'), 'attachment; filename="index.html"')
t.assert.deepStrictEqual(response.headers.get('accept-ranges'), null)
t.assert.deepStrictEqual(await response.text(), indexContent)
genericResponseChecks(t, response)
})
await t.test('reply.download() with rootPath and fileName', async (t) => {
t.plan(4 + GENERIC_RESPONSE_CHECK_COUNT)
const response = await fetch('http://localhost:' + fastify.server.address().port + '/root/path/override/test/change')
t.assert.ok(response.ok)
t.assert.deepStrictEqual(response.status, 200)
t.assert.deepStrictEqual(response.headers.get('content-disposition'), 'attachment; filename="hello-world.html"')
t.assert.deepStrictEqual(await response.text(), deepContent)
genericResponseChecks(t, response)
})
})
test('download disabled', async (t) => {
t.plan(2)
const pluginOptions = {
root: path.join(__dirname, '/static'),
prefix: '/static',
decorateReply: false
}
const fastify = Fastify()
fastify.register(fastifyStatic, pluginOptions)
fastify.get('/foo/bar', function (_req, reply) {
if (reply.download === undefined) {
t.assert.deepStrictEqual(reply.download, undefined)
reply.send('pass')
} else {
reply.send('fail')
}
})
t.after(() => fastify.close())
await fastify.listen({ port: 0 })
fastify.server.unref()
await t.test('reply.sendFile undefined', async (t) => {
t.plan(3)
const response = await fetch('http://localhost:' + fastify.server.address().port + '/foo/bar')
t.assert.ok(response.ok)
t.assert.deepStrictEqual(response.status, 200)
t.assert.deepStrictEqual(await response.text(), 'pass')
})
})
test('prefix default', (t) => {
t.plan(1)
const pluginOptions = { root: path.join(__dirname, 'static') }
const fastify = Fastify({ logger: false })
t.assert.doesNotThrow(() => fastify.register(fastifyStatic, pluginOptions))
})
test('root not found warning', async (t) => {
t.plan(1)
const rootPath = path.join(__dirname, 'does-not-exist')
const pluginOptions = { root: rootPath }
const destination = concat((data) => {
t.assert.deepStrictEqual(JSON.parse(data).msg, `"root" path "${rootPath}" must exist`)
})
const loggerInstance = pino(
{
level: 'warn'
},
destination
)
const fastify = Fastify({ loggerInstance })
fastify.register(fastifyStatic, pluginOptions)
await fastify.listen({ port: 0 })
fastify.server.unref()
destination.end()
})
test('send options', (t) => {
t.plan(12)
const pluginOptions = {
root: path.join(__dirname, '/static'),
acceptRanges: 'acceptRanges',
contentType: 'contentType',
cacheControl: 'cacheControl',
dotfiles: 'dotfiles',
etag: 'etag',
extensions: 'extensions',
immutable: 'immutable',
index: 'index',
lastModified: 'lastModified',
maxAge: 'maxAge'
}
const fastify = Fastify({ logger: false })
const { resolve, promise } = Promise.withResolvers()
const fastifyStatic = require('proxyquire')('../', {
'@fastify/send': function sendStub (_req, pathName, options) {
t.assert.deepStrictEqual(pathName, '/index.html')
t.assert.deepStrictEqual(options.root, path.join(__dirname, '/static'))
t.assert.deepStrictEqual(options.acceptRanges, 'acceptRanges')
t.assert.deepStrictEqual(options.contentType, 'contentType')
t.assert.deepStrictEqual(options.cacheControl, 'cacheControl')
t.assert.deepStrictEqual(options.dotfiles, 'dotfiles')
t.assert.deepStrictEqual(options.etag, 'etag')
t.assert.deepStrictEqual(options.extensions, 'extensions')
t.assert.deepStrictEqual(options.immutable, 'immutable')
t.assert.deepStrictEqual(options.index, 'index')
t.assert.deepStrictEqual(options.lastModified, 'lastModified')
t.assert.deepStrictEqual(options.maxAge, 'maxAge')
resolve()
return { on: () => { }, pipe: () => { } }
}
})
fastify.register(fastifyStatic, pluginOptions)
fastify.inject({ url: '/index.html' })
return promise
})
test('setHeaders option', async (t) => {
t.plan(5 + GENERIC_RESPONSE_CHECK_COUNT)
const pluginOptions = {
root: path.join(__dirname, 'static'),
setHeaders: function (res, pathName) {
t.assert.deepStrictEqual(pathName, path.join(__dirname, 'static/index.html'))
res.setHeader('X-Test-Header', 'test')
}
}
const fastify = Fastify()
fastify.register(fastifyStatic, pluginOptions)
t.after(() => fastify.close())
await fastify.listen({ port: 0 })
fastify.server.unref()
const response = await fetch('http://localhost:' + fastify.server.address().port + '/index.html')
t.assert.ok(response.ok)
t.assert.deepStrictEqual(response.status, 200)
t.assert.deepStrictEqual(response.headers.get('x-test-header'), 'test')
t.assert.deepStrictEqual(await response.text(), indexContent)
genericResponseChecks(t, response)
})
test('maxAge option', async (t) => {
t.plan(4 + GENERIC_RESPONSE_CHECK_COUNT)
const pluginOptions = {
root: path.join(__dirname, 'static'),
maxAge: 3600000
}
const fastify = Fastify()
fastify.register(fastifyStatic, pluginOptions)
t.after(() => fastify.close())
await fastify.listen({ port: 0 })
fastify.server.unref()
const response = await fetch('http://localhost:' + fastify.server.address().port + '/index.html')
t.assert.ok(response.ok)
t.assert.deepStrictEqual(response.status, 200)
t.assert.deepStrictEqual(response.headers.get('cache-control'), 'public, max-age=3600')
t.assert.deepStrictEqual(await response.text(), indexContent)
genericResponseChecks(t, response)
})
test('errors', async (t) => {
t.plan(11)
await t.test('no root', async (t) => {
t.plan(1)
const pluginOptions = {}
const fastify = Fastify({ logger: false })
await t.assert.rejects(async () => await fastify.register(fastifyStatic, pluginOptions))
})
await t.test('root is not a string', async (t) => {
t.plan(1)
const pluginOptions = { root: 42 }
const fastify = Fastify({ logger: false })
await t.assert.rejects(async () => await fastify.register(fastifyStatic, pluginOptions))
})
await t.test('root is not an absolute path', async (t) => {
t.plan(1)
const pluginOptions = { root: './my/path' }
const fastify = Fastify({ logger: false })
await t.assert.rejects(async () => await fastify.register(fastifyStatic, pluginOptions))
})
await t.test('root is not a directory', async (t) => {
t.plan(1)
const pluginOptions = { root: __filename }
const fastify = Fastify({ logger: false })
await t.assert.rejects(async () => await fastify.register(fastifyStatic, pluginOptions))
})
await t.test('root is an empty array', async (t) => {
t.plan(1)
const pluginOptions = { root: [] }
const fastify = Fastify({ logger: false })
await t.assert.rejects(async () => await fastify.register(fastifyStatic, pluginOptions))
})
await t.test('root array does not contain strings', async (t) => {
t.plan(1)
const pluginOptions = { root: [1] }
const fastify = Fastify({ logger: false })
await t.assert.rejects(async () => await fastify.register(fastifyStatic, pluginOptions))
})
await t.test('root array does not contain an absolute path', async (t) => {
t.plan(1)
const pluginOptions = { root: ['./my/path'] }
const fastify = Fastify({ logger: false })
await t.assert.rejects(async () => await fastify.register(fastifyStatic, pluginOptions))
})
await t.test('root array path is not a directory', async (t) => {
t.plan(1)
const pluginOptions = { root: [__filename] }
const fastify = Fastify({ logger: false })
await t.assert.rejects(async () => await fastify.register(fastifyStatic, pluginOptions))
})
await t.test('all root array paths must be valid', async (t) => {
t.plan(1)
const pluginOptions = { root: [path.join(__dirname, '/static'), 1] }
const fastify = Fastify({ logger: false })
await t.assert.rejects(async () => await fastify.register(fastifyStatic, pluginOptions))
})
await t.test('duplicate root paths are not allowed', async (t) => {
t.plan(1)
const pluginOptions = {
root: [path.join(__dirname, '/static'), path.join(__dirname, '/static')]
}
const fastify = Fastify({ logger: false })
await t.assert.rejects(async () => await fastify.register(fastifyStatic, pluginOptions))
})
await t.test('setHeaders is not a function', async (t) => {
t.plan(1)
const pluginOptions = { root: __dirname, setHeaders: 'headers' }
const fastify = Fastify({ logger: false })
await t.assert.rejects(async () => await fastify.register(fastifyStatic, pluginOptions))
})
})
test('register no prefix', async (t) => {
t.plan(7)
const pluginOptions = {
root: path.join(__dirname, '/static')
}
const fastify = Fastify()
fastify.register(fastifyStatic, pluginOptions)
fastify.get('/', (_request, reply) => {
reply.send({ hello: 'world' })
})
t.after(() => fastify.close())
await fastify.listen({ port: 0 })
fastify.server.unref()
await t.test('/index.html', async (t) => {
t.plan(3 + GENERIC_RESPONSE_CHECK_COUNT)
const response = await fetch('http://localhost:' + fastify.server.address().port + '/index.html')
t.assert.ok(response.ok)
t.assert.deepStrictEqual(response.status, 200)
t.assert.deepStrictEqual(await response.text(), indexContent)
genericResponseChecks(t, response)
})
await t.test('/index.css', async (t) => {
t.plan(2 + GENERIC_RESPONSE_CHECK_COUNT)
const response = await fetch('http://localhost:' + fastify.server.address().port + '/index.css')
t.assert.ok(response.ok)
t.assert.deepStrictEqual(response.status, 200)
genericResponseChecks(t, response)
})
await t.test('/', async (t) => {
t.plan(3)
const response = await fetch('http://localhost:' + fastify.server.address().port)
t.assert.ok(response.ok)
t.assert.deepStrictEqual(response.status, 200)
t.assert.deepStrictEqual(await response.json(), { hello: 'world' })
})
await t.test('/deep/path/for/test/purpose/foo.html', async (t) => {
t.plan(3 + GENERIC_RESPONSE_CHECK_COUNT)
const response = await fetch('http://localhost:' + fastify.server.address().port + '/deep/path/for/test/purpose/foo.html')
t.assert.ok(response.ok)
t.assert.deepStrictEqual(response.status, 200)
t.assert.deepStrictEqual(await response.text(), deepContent)
genericResponseChecks(t, response)
})
await t.test('/deep/path/for/test/', async (t) => {
t.plan(3 + GENERIC_RESPONSE_CHECK_COUNT)
const response = await fetch('http://localhost:' + fastify.server.address().port + '/deep/path/for/test/')
t.assert.ok(response.ok)
t.assert.deepStrictEqual(response.status, 200)
t.assert.deepStrictEqual(await response.text(), innerIndex)
genericResponseChecks(t, response)
})
await t.test('/this/path/doesnt/exist.html', async (t) => {
t.plan(2 + GENERIC_ERROR_RESPONSE_CHECK_COUNT)
const response = await fetch('http://localhost:' + fastify.server.address().port + '/this/path/doesnt/exist.html')
t.assert.ok(!response.ok)
t.assert.deepStrictEqual(response.status, 404)
genericErrorResponseChecks(t, response)
})
await t.test('/../index.js', async (t) => {
t.plan(2 + GENERIC_ERROR_RESPONSE_CHECK_COUNT)
const response = await fetch('http://localhost:' + fastify.server.address().port + '/../index.js')
t.assert.ok(!response.ok)
t.assert.deepStrictEqual(response.status, 404)
genericErrorResponseChecks(t, response)
})
})
test('with fastify-compress', async t => {
t.plan(2)
const pluginOptions = {
root: path.join(__dirname, '/static')
}
const fastify = Fastify()
fastify.register(compress, { threshold: 0 })
fastify.register(fastifyStatic, pluginOptions)
t.after(() => fastify.close())
await fastify.listen({ port: 0 })
await t.test('deflate', async function (t) {
t.plan(3 + GENERIC_RESPONSE_CHECK_COUNT)
const response = await fetch('http://localhost:' + fastify.server.address().port + '/index.html', {
headers: {
'accept-encoding': ['deflate']
}
})
t.assert.ok(response.ok)
t.assert.deepStrictEqual(response.status, 200)
t.assert.deepStrictEqual(response.headers.get('content-encoding'), 'deflate')
genericResponseChecks(t, response)
})
await t.test('gzip', async function (t) {
t.plan(3 + GENERIC_RESPONSE_CHECK_COUNT)
const response = await fetch('http://localhost:' + fastify.server.address().port + '/index.html')
t.assert.ok(response.ok)
t.assert.deepStrictEqual(response.status, 200)
t.assert.deepStrictEqual(response.headers.get('content-encoding'), 'gzip')
genericResponseChecks(t, response)
})
})
test('register /static/ with schemaHide true', async t => {
t.plan(2)
const pluginOptions = {
root: path.join(__dirname, '/static'),
prefix: '/static/',
schemaHide: true
}
const fastify = Fastify()
fastify.addHook('onRoute', function (routeOptions) {
t.assert.deepStrictEqual(routeOptions.schema, { hide: true })
})
fastify.register(fastifyStatic, pluginOptions)
t.after(() => fastify.close())
await fastify.listen({ port: 0 })
fastify.server.unref()
await t.test('/static/index.html', async (t) => {
t.plan(3 + GENERIC_RESPONSE_CHECK_COUNT)
const response = await fetch('http://localhost:' + fastify.server.address().port + '/static/index.html')
t.assert.ok(response.ok)
t.assert.deepStrictEqual(response.status, 200)
t.assert.deepStrictEqual(response.headers.get('content-type'), 'text/html; charset=utf-8')
genericResponseChecks(t, response)
})
})
test('register /static/ with schemaHide false', async t => {
t.plan(2)
const pluginOptions = {
root: path.join(__dirname, '/static'),
prefix: '/static/',
schemaHide: false
}
const fastify = Fastify()
fastify.addHook('onRoute', function (routeOptions) {
t.assert.deepStrictEqual(routeOptions.schema, { hide: false })
})
fastify.register(fastifyStatic, pluginOptions)
t.after(() => fastify.close())
await fastify.listen({ port: 0 })
fastify.server.unref()
await t.test('/static/index.html', async (t) => {
t.plan(3 + GENERIC_RESPONSE_CHECK_COUNT)
const response = await fetch('http://localhost:' + fastify.server.address().port + '/static/index.html')
t.assert.ok(response.ok)
t.assert.deepStrictEqual(response.status, 200)
t.assert.deepStrictEqual(response.headers.get('content-type'), 'text/html; charset=utf-8')
genericResponseChecks(t, response)
})
})
test('register /static/ without schemaHide', async t => {
t.plan(2)
const pluginOptions = {
root: path.join(__dirname, '/static'),
prefix: '/static/'
}
const fastify = Fastify()
fastify.addHook('onRoute', function (routeOptions) {
t.assert.deepStrictEqual(routeOptions.schema, { hide: true })
})
fastify.register(fastifyStatic, pluginOptions)
t.after(() => fastify.close())
await fastify.listen({ port: 0 })
fastify.server.unref()
await t.test('/static/index.html', async (t) => {
t.plan(3 + GENERIC_RESPONSE_CHECK_COUNT)
const response = await fetch('http://localhost:' + fastify.server.address().port + '/static/index.html')
t.assert.ok(