UNPKG

nextjs-middleware-chain

Version:
377 lines (303 loc) 9.9 kB
import '@babel/polyfill' import { createMiddleware } from '.' import './uuid' import { Middleware, DEFAULT_OPTIONS } from './middleware' jest.mock('./uuid') /** * SET UP FUNCTION COLLECTIONS */ let goodMWFns let badMWFnsArrow let badMWFnsFunc let error let fnASpy let fnBSpy let fnCSpy let fnDSpy let fnSkipAllSpy let fnSkipMwSpy beforeEach(() => { fnASpy = jest.fn() fnBSpy = jest.fn() fnCSpy = jest.fn() fnDSpy = jest.fn() fnSkipAllSpy = jest.fn() fnSkipMwSpy = jest.fn() const fnA = (req, res, next) => { fnASpy() return next('Ran A') } const fnB = function fnB(req, res, next) { fnBSpy() return next('Ran B') } const fnC = function fnC(req, res, next) { fnCSpy() return next('Ran C') } const fnD = (req, res, next) => { fnDSpy() return next('Ran D') } const fnSkipRemainingAll = (req, res, next) => { fnSkipAllSpy() return next('EnD') } const fnSkipRemainingMiddleware = (req, res, next) => { fnSkipMwSpy() return next('rOutE') } badMWFnsArrow = [ fnA, () => { } ] badMWFnsFunc = [ fnB, function() { } // eslint-disable-line func-names ] goodMWFns = [ fnA, fnB, fnC, fnD, fnSkipRemainingAll, fnSkipRemainingMiddleware ] }) /** * SET UP MW FACTORY & INSTANCE */ let middlewareFactory let middlewareInstance beforeEach(() => { middlewareFactory = createMiddleware(goodMWFns) middlewareInstance = middlewareFactory() }) describe('The basics of `createmiddleware` function', () => { it('Should throw error if an array of functions is not provided as the first argument', () => { try { createMiddleware() expect(1).toBe(2) // never hit } catch (e) { error = e } expect(error).toBeDefined() error = undefined // reset error try { createMiddleware([]) expect(1).toBe(2) // never hit } catch (e) { error = e } expect(error).toBeDefined() }) it('Should throw an error if a non-function is provided in the functions array', () => { try { createMiddleware([...goodMWFns, 'hi there']) expect(1).toBe(2) // never hit } catch (e) { error = e } expect(error).toBeDefined() }) it('Should throw an error if an unnamed function is provided in the functions array', () => { try { createMiddleware(badMWFnsArrow) expect(1).toBe(2) // never hit } catch (e) { error = e } expect(error).toBeDefined() error = undefined // reset error try { createMiddleware(badMWFnsFunc) expect(1).toBe(2) // never hit } catch (e) { error = e } expect(error).toBeDefined() }) }) describe('The factory function factory function returned by `createMiddleware`', () => { it('Should return a function that creates a new instance of Middleware', () => { expect(typeof middlewareFactory).toBe('function') expect(middlewareInstance).toBeInstanceOf(Middleware) }) }) describe('Instances of `Middleware`', () => { it('Should have a property for each function in the functions array that is a function', () => { goodMWFns.forEach((fn) => { expect(middlewareInstance[fn.name]).toBeDefined() expect(typeof middlewareInstance[fn.name]).toBe('function') }) }) it('Should have global options defined', () => { Object.keys(DEFAULT_OPTIONS).forEach((key) => { expect(middlewareInstance.options[key]).toBeDefined() }) }) it('Should allow overwriting options at factory and instance levels', () => { const mwFactory = createMiddleware(goodMWFns, { useChainOrder: false }) const mwInstance = mwFactory() const mwInstance2 = mwFactory({ useChainOrder: true }) expect(mwInstance.options.useChainOrder).toBeFalsy() expect(mwInstance2.options.useChainOrder).toBeTruthy() }) }) describe('The `Middleware.finish` factory method', () => { describe('The `runnableMiddleware` returned by the `Middleware.finish` factory', () => { it('should run as API type when runnableMiddleware receives two arguments', async () => { const nmc = { id: undefined, name: 'Final Func', type: 'api', } const finalFunc = jest.fn() const req = jest.fn().mockReturnValue() const res = jest.fn().mockReturnValue() const mwInstance = middlewareFactory({ useAsyncMiddleware: false }).fnA() const runnableMiddleware = mwInstance.finish(finalFunc, nmc.name) // Expect that it will trim all null values from run array expect(mwInstance.run.length).toBe(1) expect(mwInstance.run[0].name).toBe('fnA') await runnableMiddleware(req, res) expect(finalFunc).toBeCalledTimes(1) expect(finalFunc).toHaveBeenLastCalledWith(req, res) }) it('should run as SSR type when runnableMiddleware receives a single argument', async () => { const nmc = { id: undefined, name: 'Final Func', type: 'ssr', } const finalFunc = jest.fn() const req = jest.fn().mockReturnValue({ req: 'req' }) const res = jest.fn().mockReturnValue({ res: 'res' }) const mwInstance = middlewareFactory({ useAsyncMiddleware: false }).fnA() const runnableMiddleware = mwInstance.finish(finalFunc, nmc.name) // Expect that it will trim all null values from run array expect(mwInstance.run.length).toBe(1) expect(mwInstance.run[0].name).toBe('fnA') await runnableMiddleware({ req, res }) expect(finalFunc).toBeCalledTimes(1) expect(finalFunc).toHaveBeenLastCalledWith({ req, res }) }) }) it('should run with the `req`, `res`, and `next` arguments', () => { }) }) describe('The `Middleware` class constructor', () => { let mwFactory beforeEach(() => { mwFactory = createMiddleware(goodMWFns) }) describe('The `fnsArray.forEach` loop behavior', () => { it(`Should add a function to the end of the 'run' array in chained order when 'useChainOrder' is true`, () => { const mwInstance = mwFactory().fnC().fnA() const firstNamedIndex = goodMWFns.length expect(mwInstance.run[0]).toBeNull() expect(mwInstance.run[1]).toBeNull() expect(mwInstance.run[2]).toBeNull() expect(mwInstance.run[3]).toBeNull() expect(mwInstance.run[firstNamedIndex].name).toBe('fnC') expect(mwInstance.run[firstNamedIndex + 1].name).toBe('fnA') }) it(`Should add a function to the 'run' array at the same order as the initial functions array when 'useChainOrder' is false`, () => { const mwInstance = mwFactory({ useChainOrder: false }).fnC().fnA() const firstUndefinedIndex = goodMWFns.length expect(mwInstance.run[0].name).toBe('fnA') expect(mwInstance.run[1]).toBeNull() expect(mwInstance.run[2].name).toBe('fnC') expect(mwInstance.run[3]).toBeNull() expect(mwInstance.run[firstUndefinedIndex]).toBeUndefined() }) }) }) describe('The `Middleware` runner short-circuit methods', () => { let mwFactory beforeEach(() => { jest.resetAllMocks() mwFactory = createMiddleware(goodMWFns) }) it('Should skip all remaining middleware AND Next.js route when any mw function returns `next("end")`', async () => { const routeFn = jest.fn() const mwChain = mwFactory() .fnA() .fnB() .fnSkipRemainingAll() .fnC() .fnD() const runnableMiddleware = mwChain.finish(routeFn, 'Route Function') await runnableMiddleware({}, {}) expect(fnASpy).toHaveBeenCalledTimes(1) expect(fnBSpy).toHaveBeenCalledTimes(1) expect(fnCSpy).toHaveBeenCalledTimes(0) expect(fnDSpy).toHaveBeenCalledTimes(0) expect(routeFn).toHaveBeenCalledTimes(0) }) it('Should skip any remaining middleware when any mw function returns `next("route")`', async () => { const routeFn = jest.fn() const mwChain = mwFactory() .fnA() .fnB() .fnSkipRemainingMiddleware() .fnC() .fnD() const runnableMiddleware = mwChain.finish(routeFn, 'Route Function') await runnableMiddleware({}, {}) expect(fnASpy).toHaveBeenCalledTimes(1) expect(fnBSpy).toHaveBeenCalledTimes(1) expect(fnCSpy).toHaveBeenCalledTimes(0) expect(fnDSpy).toHaveBeenCalledTimes(0) expect(routeFn).toHaveBeenCalledTimes(1) }) }) describe('Multi-method patterns', () => { describe('Multiple middlewares run in a single middleware', () => { it('should run without errors', () => { const mwFn1 = (req, res, next) => next() const mwFn2 = (req, res, next) => next() const mwFnCommon = (req, res, next) => { const return1 = mwFn1(req, res, next) const return2 = mwFn2(req, res, next) expect(return1).toBe(true) expect(return2).toBe(true) return next() } const routeFn = jest.fn() const mwFns = [mwFn1, mwFn2, mwFnCommon] const mwFactory = createMiddleware(mwFns, { useAsyncMiddleware: false }) const runnableMiddleware = mwFactory().mwFnCommon().finish(routeFn) runnableMiddleware({}, {}) }) }) describe('Multiple middlewares in a pre-built chain', () => { it('Should allow further chaining off of a pre-built chain', () => { const spyFn = jest.fn() const mwFn1 = (req, res, next) => { spyFn() return next() } const mwFn2 = (req, res, next) => { spyFn() return next() } const routeFn = jest.fn() const mwFns = [mwFn1, mwFn2] const mwFactory = createMiddleware(mwFns, { useAsyncMiddleware: false }) const preBuiltChain = mwFactory().mwFn1() const runnableMiddleware = preBuiltChain.mwFn2().finish(routeFn) runnableMiddleware({}, {}) expect(spyFn).toBeCalledTimes(2) }) }) }) describe('The sync vs async behavior', () => { })