@fastify/middie
Version:
Middleware engine for Fastify
779 lines (613 loc) • 20.1 kB
JavaScript
// Original Fastify test/middlewares.test.js file
const test = require('node:test')
const fastify = require('fastify')
const fp = require('fastify-plugin')
const cors = require('cors')
const helmet = require('helmet')
const fs = require('node:fs')
const middiePlugin = require('../index')
test('use a middleware', async (t) => {
t.plan(6)
const instance = fastify()
instance.register(middiePlugin)
.after(() => {
const useRes = instance.use(function (_req, _res, next) {
t.assert.ok('middleware called')
next()
})
t.assert.strictEqual(useRes, instance)
})
instance.get('/', function (_request, reply) {
reply.send({ hello: 'world' })
})
const fastifyServerAddress = await instance.listen({ port: 0 })
t.after(() => instance.server.close())
const response = await fetch(fastifyServerAddress)
t.assert.strictEqual(response.status, 200)
t.assert.strictEqual(response.headers.get('content-length'), '' + (await response.text()).length)
const secondResponse = await fetch(fastifyServerAddress, {
method: 'GET'
})
const body = await secondResponse.json()
t.assert.deepStrictEqual(body, { hello: 'world' })
})
test('use cors', async (t) => {
t.plan(2)
const instance = fastify()
instance.register(middiePlugin)
.after(() => {
instance.use(cors())
})
instance.get('/', function (_request, reply) {
reply.send({ hello: 'world' })
})
const fastifyServerAddress = await instance.listen({ port: 0 })
t.after(() => instance.server.close())
const response = await fetch(fastifyServerAddress)
t.assert.ok(response.ok)
t.assert.strictEqual(response.headers.get('access-control-allow-origin'), '*')
})
test('use helmet', async (t) => {
t.plan(2)
const instance = fastify()
instance.register(middiePlugin)
.after(() => {
instance.use(helmet())
})
instance.get('/', function (_request, reply) {
reply.send({ hello: 'world' })
})
const fastifyServerAddress = await instance.listen({ port: 0 })
t.after(() => instance.server.close())
const response = await fetch(fastifyServerAddress)
t.assert.ok(response.ok)
t.assert.ok(response.headers.get('x-xss-protection'))
})
test('use helmet and cors', async (t) => {
t.plan(3)
const instance = fastify()
instance.register(middiePlugin)
.after(() => {
instance.use(cors())
instance.use(helmet())
})
instance.get('/', function (_request, reply) {
reply.send({ hello: 'world' })
})
const fastifyServerAddress = await instance.listen({ port: 0 })
t.after(() => instance.server.close())
const response = await fetch(fastifyServerAddress)
t.assert.ok(response.ok)
t.assert.ok(response.headers.get('x-xss-protection'))
t.assert.strictEqual(response.headers.get('access-control-allow-origin'), '*')
})
test('middlewares with prefix', async t => {
t.plan(4)
const instance = fastify()
instance.register(middiePlugin)
.after(() => {
instance.use(function (req, _res, next) {
req.global = true
next()
})
instance.use('', function (req, _res, next) {
req.global2 = true
next()
})
instance.use('/', function (req, _res, next) {
req.root = true
next()
})
instance.use('/prefix', function (req, _res, next) {
req.prefixed = true
next()
})
instance.use('/prefix/', function (req, _res, next) {
req.slashed = true
next()
})
})
function handler (request, reply) {
reply.send({
prefixed: request.raw.prefixed,
slashed: request.raw.slashed,
global: request.raw.global,
global2: request.raw.global2,
root: request.raw.root
})
}
instance.get('/', handler)
instance.get('/prefix', handler)
instance.get('/prefix/', handler)
instance.get('/prefix/inner', handler)
const fastifyServerAddress = await instance.listen({ port: 0 })
t.after(() => instance.server.close())
await t.test('/', async (t) => {
t.plan(2)
const response = await fetch(fastifyServerAddress)
t.assert.ok(response.ok)
const body = await response.json()
t.assert.deepStrictEqual(body, {
global: true,
global2: true,
root: true
})
})
await t.test('/prefix', async (t) => {
t.plan(2)
const response = await fetch(fastifyServerAddress + '/prefix')
t.assert.ok(response.ok)
const body = await response.json()
t.assert.deepStrictEqual(body, {
prefixed: true,
global: true,
global2: true,
root: true,
slashed: true
})
})
await t.test('/prefix/', async (t) => {
t.plan(2)
const response = await fetch(fastifyServerAddress + '/prefix/')
t.assert.ok(response.ok)
const body = await response.json()
t.assert.deepStrictEqual(body, {
prefixed: true,
slashed: true,
global: true,
global2: true,
root: true
})
})
await t.test('/prefix/inner', async (t) => {
t.plan(2)
const response = await fetch(fastifyServerAddress + '/prefix/inner')
t.assert.ok(response.ok)
const body = await response.json()
t.assert.deepStrictEqual(body, {
prefixed: true,
slashed: true,
global: true,
global2: true,
root: true
})
})
})
test('middlewares for encoded paths', async t => {
t.plan(2)
const instance = fastify()
instance.register(middiePlugin)
.after(() => {
instance.use('/encoded', function (req, _res, next) {
req.slashed = true
next()
})
instance.use('/%65ncoded', function (req, _res, next) {
req.slashedSpecial = true
next()
})
})
function handler (request, reply) {
reply.send({
slashed: request.raw.slashed,
slashedSpecial: request.raw.slashedSpecial
})
}
instance.get('/encoded', handler)
instance.get('/%65ncoded', handler)
const fastifyServerAddress = await instance.listen({ port: 0 })
t.after(() => instance.server.close())
await t.test('decode the request url and run the middleware', async (t) => {
t.plan(2)
const response = await fetch(fastifyServerAddress + '/%65ncod%65d') // '/encoded'
t.assert.ok(response.ok)
const body = await response.json()
t.assert.deepStrictEqual(body, { slashed: true })
})
await t.test('does not double decode the url', async (t) => {
t.plan(2)
const response = await fetch(fastifyServerAddress + '/%2565ncoded')
const body = await response.json()
t.assert.ok(response.ok)
t.assert.deepStrictEqual(body, { slashedSpecial: true })
})
})
test('res.end should block middleware execution', (t, done) => {
t.plan(4)
const instance = fastify()
instance.register(middiePlugin)
.after(() => {
instance.use(function (_req, res, _next) {
res.end('hello')
})
instance.use(function () {
t.assert.fail('we should not be here')
})
})
instance.addHook('onRequest', (_req, _res, next) => {
t.assert.ok('called')
next()
})
instance.addHook('preHandler', (_req, _reply, _next) => {
t.assert.fail('this should not be called')
})
instance.addHook('onSend', (_req, _reply, _payload, _next) => {
t.assert.fail('this should not be called')
})
instance.addHook('onResponse', (_request, _reply, next) => {
t.assert.ok('called')
next()
})
instance.get('/', function () {
t.assert.fail('we should no be here')
})
instance.inject({
url: '/',
method: 'GET'
}, (err, res) => {
t.assert.ifError(err)
t.assert.strictEqual(res.statusCode, 200)
t.assert.strictEqual(res.payload, 'hello')
done()
})
})
test('middlewares should be able to respond with a stream', (t, done) => {
t.plan(4)
const instance = fastify()
instance.addHook('onRequest', (_req, _res, next) => {
t.assert.ok('called')
next()
})
instance.register(middiePlugin)
.after(() => {
instance.use(function (_req, res, next) {
const stream = fs.createReadStream(process.cwd() + '/test/middleware.test.js', 'utf8')
stream.pipe(res)
res.once('finish', next)
})
instance.use(function () {
t.assert.fail('we should not be here')
})
})
instance.addHook('preHandler', () => {
t.assert.fail('this should not be called')
})
instance.addHook('onSend', () => {
t.assert.fail('this should not be called')
})
instance.addHook('onResponse', (_request, _reply, next) => {
t.assert.ok('called')
next()
})
instance.get('/', function () {
t.assert.fail('we should no be here')
})
instance.inject({
url: '/',
method: 'GET'
}, (err, res) => {
t.assert.ifError(err)
t.assert.strictEqual(res.statusCode, 200)
done()
})
})
test('Use a middleware inside a plugin after an encapsulated plugin', (t, done) => {
t.plan(4)
const f = fastify()
f.register(middiePlugin)
f.register(function (instance, _opts, next) {
instance.use(function (_req, _res, next) {
t.assert.ok('first middleware called')
next()
})
instance.get('/', function (_request, reply) {
reply.send({ hello: 'world' })
})
next()
})
f.register(fp(function (instance, _opts, next) {
instance.use(function (_req, _res, next) {
t.ok('second middleware called')
next()
})
next()
}))
f.inject('/', (err, res) => {
t.assert.ifError(err)
t.assert.strictEqual(res.statusCode, 200)
t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' })
done()
})
})
test('middlewares should run in the order in which they are defined', (t, done) => {
t.plan(9)
const f = fastify()
f.register(middiePlugin)
f.register(fp(function (instance, _opts, next) {
instance.use(function (req, _res, next) {
t.assert.strictEqual(req.previous, undefined)
req.previous = 1
next()
})
instance.register(fp(function (i, _opts, next) {
i.use(function (req, _res, next) {
t.assert.strictEqual(req.previous, 2)
req.previous = 3
next()
})
next()
}))
instance.use(function (req, _res, next) {
t.assert.strictEqual(req.previous, 1)
req.previous = 2
next()
})
next()
}))
f.register(function (instance, _opts, next) {
instance.use(function (req, _res, next) {
t.assert.strictEqual(req.previous, 3)
req.previous = 4
next()
})
instance.get('/', function (request, reply) {
t.assert.strictEqual(request.raw.previous, 5)
reply.send({ hello: 'world' })
})
instance.register(fp(function (i, _opts, next) {
i.use(function (req, _res, next) {
t.assert.strictEqual(req.previous, 4)
req.previous = 5
next()
})
next()
}))
next()
})
f.inject('/', (err, res) => {
t.assert.ifError(err)
t.assert.strictEqual(res.statusCode, 200)
t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' })
done()
})
})
test('should not double-prefix inherited middleware paths in child scopes', async t => {
t.plan(4)
const instance = fastify()
t.after(() => instance.close())
await instance.register(middiePlugin)
instance.use('/admin', function (req, res, next) {
if (req.headers.authorization == null) {
res.statusCode = 403
res.end('forbidden')
return
}
next()
})
instance.get('/admin/root-data', async function () {
return { data: 'root-secret' }
})
await instance.register(async function (child) {
child.get('/secret', async function (req) {
return { data: 'child-secret' }
})
}, { prefix: '/admin' })
const address = await instance.listen({ port: 0 })
const rootNoAuth = await fetch(address + '/admin/root-data')
t.assert.deepStrictEqual(rootNoAuth.status, 403)
const childNoAuth = await fetch(address + '/admin/secret')
t.assert.deepStrictEqual(childNoAuth.status, 403)
const childWithAuth = await fetch(address + '/admin/secret', {
headers: {
authorization: 'Bearer test'
}
})
t.assert.deepStrictEqual(childWithAuth.status, 200)
t.assert.deepStrictEqual(await childWithAuth.json(), { data: 'child-secret' })
})
test('should allow child scopes register middleware with same prefix', async t => {
t.plan(7)
const instance = fastify()
t.after(() => instance.close())
await instance.register(middiePlugin)
const count = { admin: 0, child: 0 }
instance.use('/admin', function (req, res, next) {
count.admin++
next()
})
instance.get('/admin/root-data', async function () {
return { data: 'admin' }
})
await instance.register(async function (child) {
child.use('/admin', function (req, res, next) {
count.child++
next()
})
child.get('/secret', async function (req) {
return { data: 'child' }
})
child.get('/admin', async function (req) {
return { data: 'child-admin' }
})
}, { prefix: '/admin' })
const address = await instance.listen({ port: 0 })
const root = await fetch(address + '/admin/root-data')
t.assert.deepStrictEqual(root.status, 200)
t.assert.deepStrictEqual(await root.json(), { data: 'admin' })
const child = await fetch(address + '/admin/secret')
t.assert.deepStrictEqual(child.status, 200)
t.assert.deepStrictEqual(await child.json(), { data: 'child' })
const childAdmin = await fetch(address + '/admin/admin')
t.assert.deepStrictEqual(childAdmin.status, 200)
t.assert.deepStrictEqual(await childAdmin.json(), { data: 'child-admin' })
t.assert.deepStrictEqual(count, { admin: 3, child: 1 })
})
test('should enforce inherited middleware in nested grandchild scopes', async t => {
t.plan(6)
const instance = fastify()
t.after(() => instance.close())
await instance.register(middiePlugin)
instance.use('/admin', function (req, res, next) {
if (req.headers.authorization == null) {
res.statusCode = 403
res.end('forbidden')
return
}
next()
})
instance.get('/admin/root-data', async function () {
return { data: 'root-secret' }
})
await instance.register(async function (parent) {
parent.get('/info', async function () {
return { data: 'parent-info' }
})
await parent.register(async function (grandchild) {
grandchild.get('/deep', async function () {
return { data: 'nested-secret' }
})
}, { prefix: '/sub' })
}, { prefix: '/admin' })
const address = await instance.listen({ port: 0 })
const rootNoAuth = await fetch(address + '/admin/root-data')
t.assert.deepStrictEqual(rootNoAuth.status, 403)
const parentNoAuth = await fetch(address + '/admin/info')
t.assert.deepStrictEqual(parentNoAuth.status, 403)
const grandchildNoAuth = await fetch(address + '/admin/sub/deep')
t.assert.deepStrictEqual(grandchildNoAuth.status, 403)
const grandchildWithAuth = await fetch(address + '/admin/sub/deep', {
headers: { authorization: 'Bearer test' }
})
t.assert.deepStrictEqual(grandchildWithAuth.status, 200)
t.assert.deepStrictEqual(await grandchildWithAuth.json(), { data: 'nested-secret' })
const parentWithAuth = await fetch(address + '/admin/info', {
headers: { authorization: 'Bearer test' }
})
t.assert.deepStrictEqual(parentWithAuth.status, 200)
})
test('should enforce inherited middleware across three nesting levels', async t => {
t.plan(3)
const instance = fastify()
t.after(() => instance.close())
await instance.register(middiePlugin)
instance.use('/api', function (req, res, next) {
if (req.headers.authorization == null) {
res.statusCode = 403
res.end('forbidden')
return
}
next()
})
await instance.register(async function (l1) {
await l1.register(async function (l2) {
await l2.register(async function (l3) {
l3.get('/resource', async function () {
return { data: 'deep-resource' }
})
}, { prefix: '/c' })
}, { prefix: '/b' })
}, { prefix: '/api/a' })
const address = await instance.listen({ port: 0 })
const noAuth = await fetch(address + '/api/a/b/c/resource')
t.assert.deepStrictEqual(noAuth.status, 403)
const withAuth = await fetch(address + '/api/a/b/c/resource', {
headers: { authorization: 'Bearer test' }
})
t.assert.deepStrictEqual(withAuth.status, 200)
t.assert.deepStrictEqual(await withAuth.json(), { data: 'deep-resource' })
})
test('should not apply middleware to unrelated nested prefixes', async t => {
t.plan(4)
const instance = fastify()
t.after(() => instance.close())
await instance.register(middiePlugin)
instance.use('/admin', function (req, res, next) {
if (req.headers.authorization == null) {
res.statusCode = 403
res.end('forbidden')
return
}
next()
})
await instance.register(async function (child) {
child.get('/data', async function () {
return { data: 'public' }
})
await child.register(async function (grandchild) {
grandchild.get('/info', async function () {
return { data: 'public-nested' }
})
}, { prefix: '/nested' })
}, { prefix: '/public' })
const address = await instance.listen({ port: 0 })
const publicData = await fetch(address + '/public/data')
t.assert.deepStrictEqual(publicData.status, 200)
t.assert.deepStrictEqual(await publicData.json(), { data: 'public' })
const publicNested = await fetch(address + '/public/nested/info')
t.assert.deepStrictEqual(publicNested.status, 200)
t.assert.deepStrictEqual(await publicNested.json(), { data: 'public-nested' })
})
test('should not apply middleware when prefix shares string prefix but not path segment', async t => {
t.plan(4)
const instance = fastify()
t.after(() => instance.close())
await instance.register(middiePlugin)
instance.use('/admin', function (req, res, next) {
if (req.headers.authorization == null) {
res.statusCode = 403
res.end('forbidden')
return
}
next()
})
await instance.register(async function (child) {
child.get('/settings', async function () {
return { data: 'panel-settings' }
})
}, { prefix: '/admin-panel' })
await instance.register(async function (child) {
child.get('/settings', async function () {
return { data: 'admin-settings' }
})
}, { prefix: '/admin/real' })
const address = await instance.listen({ port: 0 })
const panelNoAuth = await fetch(address + '/admin-panel/settings')
t.assert.deepStrictEqual(panelNoAuth.status, 200)
t.assert.deepStrictEqual(await panelNoAuth.json(), { data: 'panel-settings' })
const realNoAuth = await fetch(address + '/admin/real/settings')
t.assert.deepStrictEqual(realNoAuth.status, 403)
const realWithAuth = await fetch(address + '/admin/real/settings', {
headers: { authorization: 'Bearer test' }
})
t.assert.deepStrictEqual(realWithAuth.status, 200)
})
test('should enforce middleware with partial prefix overlap in nested scopes', async t => {
t.plan(3)
const instance = fastify()
t.after(() => instance.close())
await instance.register(middiePlugin)
instance.use('/admin', function (req, res, next) {
if (req.headers.authorization == null) {
res.statusCode = 403
res.end('forbidden')
return
}
next()
})
await instance.register(async function (child) {
await child.register(async function (grandchild) {
grandchild.get('/settings', async function () {
return { data: 'admin-settings' }
})
}, { prefix: '/panel' })
}, { prefix: '/admin' })
const address = await instance.listen({ port: 0 })
const noAuth = await fetch(address + '/admin/panel/settings')
t.assert.deepStrictEqual(noAuth.status, 403)
const withAuth = await fetch(address + '/admin/panel/settings', {
headers: { authorization: 'Bearer test' }
})
t.assert.deepStrictEqual(withAuth.status, 200)
t.assert.deepStrictEqual(await withAuth.json(), { data: 'admin-settings' })
})