diff --git a/src/dsl/verifier/proxy/hooks.spec.ts b/src/dsl/verifier/proxy/hooks.spec.ts new file mode 100644 index 000000000..f41784dbe --- /dev/null +++ b/src/dsl/verifier/proxy/hooks.spec.ts @@ -0,0 +1,62 @@ +import { expect } from 'chai'; +import { stub } from 'sinon'; + +import { registerBeforeHook, registerAfterHook } from './hooks'; + +describe('Verifier', () => { + describe('#registerBeforeHook', () => { + describe('when the state setup routine is called multiple times before the next teardown', () => { + it('it executes the afterEach hook only once', (done) => { + const mockHook = stub().resolves(); + + const requestHandler = registerBeforeHook(mockHook); + + const mockRequest = { + app: {}, + body: { + action: 'setup', + }, + }; + + requestHandler(mockRequest as any, null as any, () => { + requestHandler(mockRequest as any, null as any, () => { + try { + expect(mockHook).to.be.calledOnce; + done(); + } catch (err) { + done(err); + } + }); + }); + }); + }); + }); + + describe('#registerAfterHook', () => { + describe('when the state teardown routine is called multiple times before the next setup', () => { + it('it executes the afterEach hook only once', (done) => { + const mockHook = stub().resolves(); + + const requestHandler = registerAfterHook(mockHook); + + const mockRequest = { + app: {}, + body: { + action: 'teardown', + }, + }; + + requestHandler(mockRequest as any, null as any, () => { + requestHandler(mockRequest as any, null as any, () => { + try { + expect(mockHook).to.be.calledOnce; + done(); + } catch (err) { + done(err); + } + }); + }); + }); + }); + }); +}); diff --git a/src/dsl/verifier/proxy/hooks.ts b/src/dsl/verifier/proxy/hooks.ts index 6a4d81c59..b5ccf41a0 100644 --- a/src/dsl/verifier/proxy/hooks.ts +++ b/src/dsl/verifier/proxy/hooks.ts @@ -1,50 +1,62 @@ -import express from 'express'; +/* eslint-disable no-param-reassign */ +import { RequestHandler, Application, Request } from 'express'; import logger from '../../../common/logger'; -import { ProxyOptions } from './types'; +import { Hook } from './types'; -export const registerBeforeHook = ( - app: express.Express, - config: ProxyOptions, - stateSetupPath: string -): void => { - if (config.beforeEach) logger.trace("registered 'beforeEach' hook"); - app.use(async (req, res, next) => { - if (req.path === stateSetupPath && config.beforeEach) { +type ApplicationWithHookMetadata = Application & { + beforeEachExecuted?: boolean; + afterEachExecuted?: boolean; +}; + +export const registerBeforeHook = + (beforeEach: Hook): RequestHandler => + async ( + { body, app }: Request & { app: ApplicationWithHookMetadata }, + res, + next + ) => { + if (body?.action === 'setup' && !app.beforeEachExecuted) { logger.debug("executing 'beforeEach' hook"); try { - await config.beforeEach(); + await beforeEach(); + app.beforeEachExecuted = true; next(); } catch (e) { logger.error(`error executing 'beforeEach' hook: ${e.message}`); logger.debug(`Stack trace was: ${e.stack}`); next(new Error(`error executing 'beforeEach' hook: ${e.message}`)); } + } else if (body?.action === 'teardown') { + app.beforeEachExecuted = false; + next(); } else { next(); } - }); -}; + }; -export const registerAfterHook = ( - app: express.Express, - config: ProxyOptions, - stateSetupPath: string -): void => { - if (config.afterEach) logger.trace("registered 'afterEach' hook"); - app.use(async (req, res, next) => { - if (req.path !== stateSetupPath && config.afterEach) { +export const registerAfterHook = + (afterEach: Hook): RequestHandler => + async ( + { body, app }: Request & { app: ApplicationWithHookMetadata }, + res, + next + ) => { + if (body?.action === 'teardown' && !app.afterEachExecuted) { logger.debug("executing 'afterEach' hook"); try { - await config.afterEach(); + await afterEach(); + app.afterEachExecuted = true; next(); } catch (e) { logger.error(`error executing 'afterEach' hook: ${e.message}`); logger.debug(`Stack trace was: ${e.stack}`); next(new Error(`error executing 'afterEach' hook: ${e.message}`)); } + } else if (body?.action === 'setup') { + app.afterEachExecuted = false; + next(); } else { next(); } - }); -}; + }; diff --git a/src/dsl/verifier/proxy/proxy.ts b/src/dsl/verifier/proxy/proxy.ts index a90c8933b..666cfd564 100644 --- a/src/dsl/verifier/proxy/proxy.ts +++ b/src/dsl/verifier/proxy/proxy.ts @@ -43,8 +43,16 @@ export const createProxy = ( ); app.use(bodyParser.urlencoded({ extended: true })); app.use('/*', bodyParser.raw({ type: '*/*' })); - registerBeforeHook(app, config, stateSetupPath); - registerAfterHook(app, config, stateSetupPath); + + // Hooks + if (config.beforeEach) { + logger.trace("registered 'beforeEach' hook"); + app.use(stateSetupPath, registerBeforeHook(config.beforeEach)); + } + if (config.afterEach) { + logger.trace("registered 'afterEach' hook"); + app.use(stateSetupPath, registerAfterHook(config.afterEach)); + } // Trace req/res logging if (config.logLevel === 'debug' || config.logLevel === 'trace') {