Skip to content

Commit

Permalink
fix: allow re-use of PactV4 object
Browse files Browse the repository at this point in the history
  • Loading branch information
mefellows committed Aug 1, 2023
1 parent d89ea9a commit 38a68fb
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 63 deletions.
18 changes: 6 additions & 12 deletions examples/v4/matchers/consumer.spec.ts
Expand Up @@ -16,14 +16,14 @@ const { expect } = chai;
process.env.ENABLE_FEATURE_V4 = 'true';

describe('V4 Matchers', () => {
const pact = new PactV4({
consumer: 'myconsumer',
provider: 'myprovider',
spec: SpecificationVersion.SPECIFICATION_VERSION_V4,
logLevel: (process.env.LOG_LEVEL as LogLevel) || 'trace',
});
describe('eachKeyMatches', () => {
it('returns the shape of object given to the matcher', async () => {
const pact = new PactV4({
consumer: 'myconsumer',
provider: 'myprovider',
spec: SpecificationVersion.SPECIFICATION_VERSION_V4,
logLevel: (process.env.LOG_LEVEL as LogLevel) || 'error',
});
await pact
.addInteraction()
.uponReceiving('a request only checks the keys and ignores the values')
Expand Down Expand Up @@ -56,12 +56,6 @@ describe('V4 Matchers', () => {

describe('eachValueMatches', () => {
it('returns the shape of object given to the matcher', async () => {
const pact = new PactV4({
consumer: 'myconsumer',
provider: 'myprovider',
spec: SpecificationVersion.SPECIFICATION_VERSION_V4,
logLevel: (process.env.LOG_LEVEL as LogLevel) || 'error',
});
await pact
.addInteraction()
.uponReceiving(
Expand Down
66 changes: 42 additions & 24 deletions src/v4/http/index.ts
Expand Up @@ -41,7 +41,8 @@ export class UnconfiguredInteraction implements V4UnconfiguredInteraction {
constructor(
protected pact: ConsumerPact,
protected interaction: ConsumerInteraction,
protected opts: PactV4Options
protected opts: PactV4Options,
protected cleanupFn: () => void
) {}

uponReceiving(description: string): V4UnconfiguredInteraction {
Expand All @@ -67,7 +68,7 @@ export class UnconfiguredInteraction implements V4UnconfiguredInteraction {
this.pact,
this.interaction,
this.opts,
request
this.cleanupFn
);
}

Expand All @@ -81,13 +82,23 @@ export class UnconfiguredInteraction implements V4UnconfiguredInteraction {
if (builder) {
builder(new RequestBuilder(this.interaction));
}
return new InteractionwithRequest(this.pact, this.interaction, this.opts);
return new InteractionwithRequest(
this.pact,
this.interaction,
this.opts,
this.cleanupFn
);
}

usingPlugin(config: PluginConfig): V4InteractionWithPlugin {
this.pact.addPlugin(config.plugin, config.version);

return new InteractionWithPlugin(this.pact, this.interaction, this.opts);
return new InteractionWithPlugin(
this.pact,
this.interaction,
this.opts,
this.cleanupFn
);
}
}

Expand All @@ -98,13 +109,13 @@ export class InteractionWithCompleteRequest
private pact: ConsumerPact,
private interaction: ConsumerInteraction,
private opts: PactV4Options,
private request: V4Request
protected cleanupFn: () => void
) {
throw Error('V4InteractionWithCompleteRequest is unimplemented');
}

withCompleteResponse(response: V4Response): V4InteractionWithResponse {
return new InteractionWithResponse(this.pact, this.interaction, this.opts);
return new InteractionWithResponse(this.pact, this.opts, this.cleanupFn);
}
}

Expand All @@ -113,7 +124,8 @@ export class InteractionwithRequest implements V4InteractionwithRequest {
constructor(
private pact: ConsumerPact,
private interaction: ConsumerInteraction,
private opts: PactV4Options
private opts: PactV4Options,
protected cleanupFn: () => void
) {}

willRespondWith(status: number, builder?: V4ResponseBuilderFunc) {
Expand All @@ -123,7 +135,7 @@ export class InteractionwithRequest implements V4InteractionwithRequest {
builder(new ResponseBuilder(this.interaction));
}

return new InteractionWithResponse(this.pact, this.interaction, this.opts);
return new InteractionWithResponse(this.pact, this.opts, this.cleanupFn);
}
}

Expand Down Expand Up @@ -240,12 +252,12 @@ export class InteractionWithResponse implements V4InteractionWithResponse {
// tslint:disable:no-empty-function
constructor(
private pact: ConsumerPact,
private interaction: ConsumerInteraction,
private opts: PactV4Options
private opts: PactV4Options,
protected cleanupFn: () => void
) {}

async executeTest<T>(testFn: TestFunction<T>) {
return executeTest(this.pact, this.opts, testFn);
return executeTest(this.pact, this.opts, testFn, this.cleanupFn);
}
}

Expand All @@ -254,7 +266,8 @@ export class InteractionWithPlugin implements V4InteractionWithPlugin {
constructor(
private pact: ConsumerPact,
private interaction: ConsumerInteraction,
private opts: PactV4Options
private opts: PactV4Options,
protected cleanupFn: () => void
) {}

// Multiple plugins are allowed
Expand All @@ -277,7 +290,8 @@ export class InteractionWithPlugin implements V4InteractionWithPlugin {
return new InteractionWithPluginRequest(
this.pact,
this.interaction,
this.opts
this.opts,
this.cleanupFn
);
}
}
Expand All @@ -289,7 +303,8 @@ export class InteractionWithPluginRequest
constructor(
private pact: ConsumerPact,
private interaction: ConsumerInteraction,
private opts: PactV4Options
private opts: PactV4Options,
protected cleanupFn: () => void
) {}

willRespondWith(
Expand All @@ -304,8 +319,8 @@ export class InteractionWithPluginRequest

return new InteractionWithPluginResponse(
this.pact,
this.interaction,
this.opts
this.opts,
this.cleanupFn
);
}
}
Expand Down Expand Up @@ -346,12 +361,12 @@ export class InteractionWithPluginResponse
// tslint:disable:no-empty-function
constructor(
private pact: ConsumerPact,
private interaction: ConsumerInteraction,
private opts: PactV4Options
private opts: PactV4Options,
protected cleanupFn: () => void
) {}

async executeTest<T>(testFn: (mockServer: V4MockServer) => Promise<T>) {
return executeTest(this.pact, this.opts, testFn);
return executeTest(this.pact, this.opts, testFn, this.cleanupFn);
}
}
const readBinaryData = (file: string): Buffer => {
Expand All @@ -368,19 +383,22 @@ const cleanup = (
success: boolean,
pact: ConsumerPact,
opts: PactV4Options,
server: V3MockServer
server: V3MockServer,
cleanupFn: () => void
) => {
if (success) {
pact.writePactFile(opts.dir || './pacts');
}
pact.cleanupMockServer(server.port);
pact.cleanupPlugins();
cleanupFn();
};

const executeTest = async <T>(
pact: ConsumerPact,
opts: PactV4Options,
testFn: TestFunction<T>
testFn: TestFunction<T>,
cleanupFn: () => void
) => {
const scheme = opts.tls ? 'https' : 'http';
const host = opts.host || '127.0.0.1';
Expand All @@ -405,7 +423,7 @@ const executeTest = async <T>(
let errorMessage = 'Test failed for the following reasons:';
errorMessage += `\n\n ${generateMockServerError(matchingResults, '\t')}`;

cleanup(false, pact, opts, server);
cleanup(false, pact, opts, server, cleanupFn);

// If the tests throws an error, we need to rethrow the error, but print out
// any additional mock server errors to help the user understand what happened
Expand All @@ -422,12 +440,12 @@ const executeTest = async <T>(

// Scenario: test threw an error, but Pact validation was OK (error in client or test)
if (error) {
cleanup(false, pact, opts, server);
cleanup(false, pact, opts, server, cleanupFn);
throw error;
}

// Scenario: Pact validation passed, test didn't throw - return the callback value
cleanup(true, pact, opts, server);
cleanup(true, pact, opts, server, cleanupFn);

return val;
};
31 changes: 23 additions & 8 deletions src/v4/index.ts
Expand Up @@ -17,21 +17,30 @@ export class PactV4 implements V4ConsumerPact {
);
}

this.setup();
this.pact.addMetadata('pact-js', 'version', pactPackageVersion);
}

setup(): void {
this.pact = makeConsumerPact(
opts.consumer,
opts.provider,
opts.spec ?? SpecificationVersion.SPECIFICATION_VERSION_V4,
opts.logLevel
this.opts.consumer,
this.opts.provider,
this.opts.spec ?? SpecificationVersion.SPECIFICATION_VERSION_V3,
this.opts.logLevel ?? 'info'
);

this.pact.addMetadata('pact-js', 'version', pactPackageVersion);
}

addInteraction(): V4UnconfiguredInteraction {
return new UnconfiguredInteraction(
this.pact,
this.pact.newInteraction(''),
this.opts
this.opts,
() => {
// This function needs to be called if the PactV4 object is to be re-used (commonly expected by users)
// Because of the type-state model used here, it's a bit awkward as we need to thread this through
// to children, ultimately to be called on the "executeTest" stage.
this.setup();
}
);
}

Expand All @@ -41,7 +50,13 @@ export class PactV4 implements V4ConsumerPact {
return new UnconfiguredSynchronousMessage(
this.pact,
this.pact.newSynchronousMessage(description),
this.opts
this.opts,
() => {
// This function needs to be called if the PactV4 object is to be re-used (commonly expected by users)
// Because of the type-state model used here, it's a bit awkward as we need to thread this through
// to children, ultimately to be called on the "executeTest" stage.
this.setup();
}
);
}
}

0 comments on commit 38a68fb

Please sign in to comment.