Skip to content

Commit

Permalink
refactor!: allow hooks to run only once per interaction
Browse files Browse the repository at this point in the history
Breaking change:
Current behaviour will run the beforeEach and afterEach hooks
multiple times if there are several provider states defined in
an interaction. This change will ensure each of those hooks is
run only once, regardless of how many provider states are
defined in an interaction.

Fixes pact-foundation#1068
  • Loading branch information
lhokktyn committed Oct 8, 2024
1 parent 7bf180f commit 0c87743
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 26 deletions.
62 changes: 62 additions & 0 deletions src/dsl/verifier/proxy/hooks.spec.ts
Original file line number Diff line number Diff line change
@@ -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);
}
});
});
});
});
});
});
60 changes: 36 additions & 24 deletions src/dsl/verifier/proxy/hooks.ts
Original file line number Diff line number Diff line change
@@ -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();
}
});
};
};
12 changes: 10 additions & 2 deletions src/dsl/verifier/proxy/proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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') {
Expand Down

0 comments on commit 0c87743

Please sign in to comment.