feathers/packages/express/test/error-handler.test.ts
David Luecke c606c56dc2
chore: Format code using Prettier and updated ESLint rules (#2647)
Co-authored-by: Marshall Thompson <marshall@creativeideal.net>
2022-05-27 15:21:13 -07:00

400 lines
11 KiB
TypeScript

/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function */
import { strict as assert } from 'assert'
import express, { Request, Response, NextFunction } from 'express'
import axios from 'axios'
import fs from 'fs'
import { join } from 'path'
import { BadRequest, NotAcceptable, NotAuthenticated, NotFound, PaymentError } from '@feathersjs/errors'
import { errorHandler } from '../src'
const content = '<html><head></head><body>Error</body></html>'
const htmlHandler = function (_error: Error, _req: Request, res: Response, _next: NextFunction) {
res.send(content)
}
const jsonHandler = function (error: Error, _req: Request, res: Response, _next: NextFunction) {
res.json(error)
}
describe('error-handler', () => {
describe('supports catch-all custom handlers', function () {
before(function () {
this.app = express()
.get('/error', function (_req: Request, _res: Response, next: NextFunction) {
next(new Error('Something went wrong'))
})
.use(
errorHandler({
html: htmlHandler,
json: jsonHandler
})
)
this.server = this.app.listen(5050)
})
after(function (done) {
this.server.close(done)
})
describe('JSON handler', () => {
const options = {
url: 'http://localhost:5050/error',
headers: {
'Content-Type': 'application/json',
Accept: 'application/json'
}
}
it('can send a custom response', async () => {
try {
await axios(options)
assert.fail('Should never get here')
} catch (error: any) {
assert.deepEqual(error.response.data, {
name: 'GeneralError',
message: 'Something went wrong',
code: 500,
className: 'general-error'
})
}
})
})
})
describe('supports error-code specific custom handlers', () => {
describe('HTML handler', () => {
const req = {
headers: { 'content-type': 'text/html' }
}
const makeRes = (errCode: number, props?: any) => {
return Object.assign(
{
set() {},
status(code: number) {
assert.equal(code, errCode)
}
},
props
)
}
it('if the value is a string, calls res.sendFile', (done) => {
const err = new NotAuthenticated()
const middleware = errorHandler({
logger: null,
html: { 401: 'path/to/401.html' }
})
const res = makeRes(401, {
sendFile(f: any) {
assert.equal(f, 'path/to/401.html')
done()
}
})
;(middleware as any)(err, req, res)
})
it('if the value is a function, calls as middleware ', (done) => {
const err = new PaymentError()
const res = makeRes(402)
const middleware = errorHandler({
logger: null,
html: {
402: (_err: any, _req: any, _res: any) => {
assert.equal(_err, err)
assert.equal(_req, req)
assert.equal(_res, res)
done()
}
}
})
;(middleware as any)(err, req, res)
})
it('falls back to default if error code config is available', (done) => {
const err = new NotAcceptable()
const res = makeRes(406)
const middleware = errorHandler({
logger: null,
html: {
default: (_err: any, _req: any, _res: any) => {
assert.equal(_err, err)
assert.equal(_req, req)
assert.equal(_res, res)
done()
}
}
})
;(middleware as any)(err, req, res)
})
})
describe('JSON handler', () => {
const req = {
headers: { 'content-type': 'application/json' }
}
const makeRes = (errCode: number, props?: any) => {
return Object.assign(
{
set() {},
status(code: number) {
assert.equal(code, errCode)
}
},
props
)
}
it('calls res.json by default', (done) => {
const err = new NotAuthenticated()
const middleware = errorHandler({
logger: null,
json: {}
})
const res = makeRes(401, {
json(obj: any) {
assert.deepEqual(obj, err.toJSON())
done()
}
})
;(middleware as any)(err, req, res)
})
it('if the value is a function, calls as middleware ', (done) => {
const err = new PaymentError()
const res = makeRes(402)
const middleware = errorHandler({
logger: null,
json: {
402: (_err: any, _req: any, _res: any) => {
assert.equal(_err, err)
assert.equal(_req, req)
assert.equal(_res, res)
done()
}
}
})
;(middleware as any)(err, req, res)
})
it('falls back to default if error code config is available', (done) => {
const err = new NotAcceptable()
const res = makeRes(406)
const middleware = errorHandler({
logger: null,
json: {
default: (_err: any, _req: any, _res: any) => {
assert.equal(_err, err)
assert.equal(_req, req)
assert.equal(_res, res)
done()
}
}
})
;(middleware as any)(err, req, res)
})
})
})
describe('use as app error handler', function () {
before(function () {
this.app = express()
.get('/error', function (_req: Request, _res: Response, next: NextFunction) {
next(new Error('Something went wrong'))
})
.get('/string-error', function (_req: Request, _res: Response, next: NextFunction) {
const e: any = new Error('Something was not found')
e.code = '404'
next(e)
})
.get('/bad-request', function (_req: Request, _res: Response, next: NextFunction) {
next(
new BadRequest({
message: 'Invalid Password',
errors: [
{
path: 'password',
value: null,
message: "'password' cannot be 'null'"
}
]
})
)
})
.use(function (_req: Request, _res: Response, next: NextFunction) {
next(new NotFound('File not found'))
})
.use(
errorHandler({
logger: null
})
)
this.server = this.app.listen(5050)
})
after(function (done) {
this.server.close(done)
})
describe('converts an non-feathers error', () => {
it('is an instance of GeneralError', async () => {
try {
await axios({
url: 'http://localhost:5050/error',
responseType: 'json'
})
assert.fail('Should never get here')
} catch (error: any) {
assert.equal(error.response.status, 500)
assert.deepEqual(error.response.data, {
name: 'GeneralError',
message: 'Something went wrong',
code: 500,
className: 'general-error'
})
}
})
})
describe('text/html format', () => {
it('serves a 404.html', (done) => {
fs.readFile(join(__dirname, '..', 'public', '404.html'), async function (_err, html) {
try {
await axios({
url: 'http://localhost:5050/path/to/nowhere',
headers: {
'Content-Type': 'text/html',
Accept: 'text/html'
}
})
assert.fail('Should never get here')
} catch (error: any) {
assert.equal(error.response.status, 404)
assert.equal(error.response.data, html.toString())
done()
}
})
})
it('serves a 500.html', (done) => {
fs.readFile(join(__dirname, '..', 'public', 'default.html'), async function (_err, html) {
try {
await axios({
url: 'http://localhost:5050/error',
headers: {
'Content-Type': 'text/html',
Accept: 'text/html'
}
})
assert.fail('Should never get here')
} catch (error: any) {
assert.equal(error.response.status, 500)
assert.equal(error.response.data, html.toString())
done()
}
})
})
})
describe('application/json format', () => {
it('500', async () => {
try {
await axios({
url: 'http://localhost:5050/error',
headers: {
'Content-Type': 'application/json',
Accept: 'application/json'
}
})
assert.fail('Should never get here')
} catch (error: any) {
assert.equal(error.response.status, 500)
assert.deepEqual(error.response.data, {
name: 'GeneralError',
message: 'Something went wrong',
code: 500,
className: 'general-error'
})
}
})
it('404', async () => {
try {
await axios({
url: 'http://localhost:5050/path/to/nowhere',
headers: {
'Content-Type': 'application/json',
Accept: 'application/json'
}
})
assert.fail('Should never get here')
} catch (error: any) {
assert.equal(error.response.status, 404)
assert.deepEqual(error.response.data, {
name: 'NotFound',
message: 'File not found',
code: 404,
className: 'not-found'
})
}
})
it('400', async () => {
try {
await axios({
url: 'http://localhost:5050/bad-request',
headers: {
'Content-Type': 'application/json',
Accept: 'application/json'
}
})
assert.fail('Should never get here')
} catch (error: any) {
assert.equal(error.response.status, 400)
assert.deepEqual(error.response.data, {
name: 'BadRequest',
message: 'Invalid Password',
code: 400,
className: 'bad-request',
data: {},
errors: [
{
path: 'password',
value: null,
message: "'password' cannot be 'null'"
}
]
})
}
})
})
it('returns JSON by default', async () => {
try {
await axios('http://localhost:5050/bad-request')
assert.fail('Should never get here')
} catch (error: any) {
assert.equal(error.response.status, 400)
assert.deepEqual(error.response.data, {
name: 'BadRequest',
message: 'Invalid Password',
code: 400,
className: 'bad-request',
data: {},
errors: [
{
path: 'password',
value: null,
message: "'password' cannot be 'null'"
}
]
})
}
})
})
})