@pact-foundation/pact
Version:
Pact for all things Javascript
335 lines • 12.7 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SynchronousMessageWithResponse = exports.SynchronousMessageWithTransport = exports.SynchronousMessageWithPluginContents = exports.SynchronousMessageWithResponseBuilder = exports.SynchronousMessageWithRequest = exports.SynchronousMessageWithRequestBuilder = exports.SynchronousMessageWithPlugin = exports.UnconfiguredSynchronousMessage = void 0;
exports.v4SynchronousBodyHandler = v4SynchronousBodyHandler;
exports.v4AsynchronousBodyHandler = v4AsynchronousBodyHandler;
const ramda_1 = require("ramda");
const logger_1 = __importDefault(require("../../common/logger"));
const matchingRules_1 = require("../../common/matchingRules");
const configurationError_1 = __importDefault(require("../../errors/configurationError"));
const display_1 = require("../../v3/display");
const defaultPactDir = './pacts';
class UnconfiguredSynchronousMessage {
pact;
interaction;
opts;
cleanupFn;
constructor(pact, interaction, opts, cleanupFn) {
this.pact = pact;
this.interaction = interaction;
this.opts = opts;
this.cleanupFn = cleanupFn;
}
given(state, parameters) {
if (parameters) {
this.interaction.givenWithParams(state, JSON.stringify(parameters));
}
else {
this.interaction.given(state);
}
return this;
}
pending(pending = true) {
this.interaction.setPending(pending);
return this;
}
comment(comment) {
if (typeof comment === 'string') {
this.interaction.addTextComment(comment);
return this;
}
this.interaction.setComment(comment.key, comment.value);
return this;
}
testName(name) {
this.interaction.setInteractionTestName(name);
return this;
}
reference(group, name, value) {
this.interaction.addInteractionReference(group, name, value);
return this;
}
usingPlugin(config) {
this.pact.addPlugin(config.plugin, config.version);
return new SynchronousMessageWithPlugin(this.pact, this.interaction, this.opts, this.cleanupFn);
}
withRequest(builder) {
builder(new SynchronousMessageWithRequestBuilder(this.pact, this.interaction, this.opts));
return new SynchronousMessageWithRequest(this.pact, this.interaction, this.opts, this.cleanupFn);
}
}
exports.UnconfiguredSynchronousMessage = UnconfiguredSynchronousMessage;
class SynchronousMessageWithPlugin {
pact;
interaction;
opts;
cleanupFn;
constructor(pact, interaction, opts, cleanupFn) {
this.pact = pact;
this.interaction = interaction;
this.opts = opts;
this.cleanupFn = cleanupFn;
}
usingPlugin(config) {
this.pact.addPlugin(config.plugin, config.version);
return this;
}
withPluginContents(contents, contentType) {
this.interaction.withPluginRequestResponseInteractionContents(contentType, contents);
return new SynchronousMessageWithPluginContents(this.pact, this.interaction, this.opts, this.cleanupFn);
}
}
exports.SynchronousMessageWithPlugin = SynchronousMessageWithPlugin;
class SynchronousMessageWithRequestBuilder {
pact;
interaction;
opts;
constructor(pact, interaction, opts) {
this.pact = pact;
this.interaction = interaction;
this.opts = opts;
}
withContent(contentType, body) {
this.interaction.withRequestBinaryContents(body, contentType);
return this;
}
withJSONContent(content) {
if ((0, ramda_1.isEmpty)(content)) {
throw new configurationError_1.default('You must provide a valid JSON document or primitive for the Message.');
}
this.interaction.withRequestContents(JSON.stringify(content), 'application/json');
return this;
}
withMatchingRules(rules) {
(0, matchingRules_1.validateRules)(rules);
const ffiRules = (0, matchingRules_1.convertRulesToFFI)(rules);
this.interaction.withRequestMatchingRules(JSON.stringify(ffiRules));
return this;
}
}
exports.SynchronousMessageWithRequestBuilder = SynchronousMessageWithRequestBuilder;
class SynchronousMessageWithRequest {
pact;
interaction;
opts;
cleanupFn;
constructor(pact, interaction, opts, cleanupFn) {
this.pact = pact;
this.interaction = interaction;
this.opts = opts;
this.cleanupFn = cleanupFn;
}
withResponse(builder) {
builder(new SynchronousMessageWithResponseBuilder(this.pact, this.interaction, this.opts));
return new SynchronousMessageWithResponse(this.pact, this.interaction, this.opts, this.cleanupFn);
}
}
exports.SynchronousMessageWithRequest = SynchronousMessageWithRequest;
class SynchronousMessageWithResponseBuilder {
pact;
interaction;
opts;
constructor(pact, interaction, opts) {
this.pact = pact;
this.interaction = interaction;
this.opts = opts;
}
withMetadata(metadata) {
if ((0, ramda_1.isEmpty)(metadata)) {
throw new configurationError_1.default('You must provide valid metadata for the Message, or none at all');
}
(0, ramda_1.forEachObjIndexed)((v, k) => {
this.interaction.withMetadata(`${k}`, JSON.stringify(v));
}, metadata);
return this;
}
withContent(contentType, body) {
this.interaction.withResponseBinaryContents(body, contentType);
return this;
}
withJSONContent(content) {
if ((0, ramda_1.isEmpty)(content)) {
throw new configurationError_1.default('You must provide a valid JSON document or primitive for the Message.');
}
this.interaction.withResponseContents(JSON.stringify(content), 'application/json');
return this;
}
withMatchingRules(rules) {
(0, matchingRules_1.validateRules)(rules);
const ffiRules = (0, matchingRules_1.convertRulesToFFI)(rules);
this.interaction.withResponseMatchingRules(JSON.stringify(ffiRules));
return this;
}
}
exports.SynchronousMessageWithResponseBuilder = SynchronousMessageWithResponseBuilder;
class SynchronousMessageWithPluginContents {
pact;
interaction;
opts;
cleanupFn;
constructor(pact, interaction, opts, cleanupFn) {
this.pact = pact;
this.interaction = interaction;
this.opts = opts;
this.cleanupFn = cleanupFn;
}
executeTest(integrationTest) {
return executeNonTransportTest(this.pact, this.opts, this.interaction, integrationTest, this.cleanupFn);
}
startTransport(transport, address, // IP Address or hostname
config) {
const port = this.pact.pactffiCreateMockServerForTransport(address, transport, config ? JSON.stringify(config) : '');
return new SynchronousMessageWithTransport(this.pact, this.interaction, this.opts, port, address, this.cleanupFn);
}
}
exports.SynchronousMessageWithPluginContents = SynchronousMessageWithPluginContents;
class SynchronousMessageWithTransport {
pact;
interaction;
opts;
port;
address;
cleanupFn;
constructor(pact, interaction, opts, port, address, cleanupFn) {
this.pact = pact;
this.interaction = interaction;
this.opts = opts;
this.port = port;
this.address = address;
this.cleanupFn = cleanupFn;
}
// TODO: this is basically the same as the HTTP variant, except only with a different test function wrapper
// extract these into smaller, testable chunks and re-use them
async executeTest(integrationTest) {
let val;
let error;
try {
const request = this.interaction.getRequestContents();
const response = this.interaction.getResponseContents();
val = await integrationTest({ port: this.port, address: this.address }, {
Request: {
content: request,
},
Response: response.map((c) => ({
content: c,
})),
});
}
catch (e) {
error = e instanceof Error ? e : new Error(String(e));
}
const matchingResults = this.pact.mockServerMismatches(this.port);
const errors = (0, display_1.filterMissingFeatureFlag)(matchingResults);
const success = this.pact.mockServerMatchedSuccessfully(this.port);
// Scenario: Pact validation failed
if (!success && errors.length > 0) {
let errorMessage = 'Test failed for the following reasons:';
errorMessage += `\n\n ${(0, display_1.generateMockServerError)(matchingResults, '\t')}`;
cleanup(false, this.pact, this.opts, this.cleanupFn, this.port, true);
// 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
// (The proximate cause here is often the HTTP 500 from the mock server,
// where the HTTP client then throws)
if (error) {
logger_1.default.error(errorMessage);
throw error;
}
// Test didn't throw, so we need to ensure the test fails
return Promise.reject(new Error(errorMessage));
}
// Scenario: test threw an error, but Pact validation was OK (error in client or test)
if (error) {
cleanup(false, this.pact, this.opts, this.cleanupFn, this.port, true);
throw error;
}
// Scenario: Pact validation passed, test didn't throw - return the callback value
cleanup(true, this.pact, this.opts, this.cleanupFn, this.port, true);
return val;
}
}
exports.SynchronousMessageWithTransport = SynchronousMessageWithTransport;
class SynchronousMessageWithResponse {
pact;
interaction;
opts;
cleanupFn;
constructor(pact, interaction, opts, cleanupFn) {
this.pact = pact;
this.interaction = interaction;
this.opts = opts;
this.cleanupFn = cleanupFn;
}
executeTest(integrationTest) {
const res = executeNonTransportTest(this.pact, this.opts, this.interaction, integrationTest, this.cleanupFn);
this.cleanupFn();
return res;
}
}
exports.SynchronousMessageWithResponse = SynchronousMessageWithResponse;
const cleanup = (success, pact, opts, cleanupFn, port, transport = false) => {
if (success) {
if (transport && port) {
pact.writePactFileForPluginServer(port, opts.dir || defaultPactDir, true);
}
else {
pact.writePactFile(opts.dir || defaultPactDir);
}
}
if (port) {
pact.cleanupMockServer(port);
}
pact.cleanupPlugins();
cleanupFn();
};
const executeNonTransportTest = async (pact, opts, interaction, integrationTest, cleanupFn) => {
let val;
let error;
try {
const request = interaction.getRequestContents();
const response = interaction.getResponseContents();
val = await integrationTest({
Request: {
content: request,
},
Response: response.map((c) => ({
content: c,
})),
});
}
catch (e) {
error = e instanceof Error ? e : new Error(String(e));
}
// Scenario: test threw an error, but Pact validation was OK (error in client or test)
if (error) {
cleanup(false, pact, opts, cleanupFn);
throw error;
}
// Scenario: Pact validation passed, test didn't throw - return the callback value
cleanup(true, pact, opts, cleanupFn);
return val;
};
// bodyHandler takes a synchronous function and returns
// a wrapped function that accepts a Message and returns a Promise
function v4SynchronousBodyHandler(handler) {
return (m) => {
const body = m.contents.content;
return new Promise((resolve, reject) => {
try {
const res = handler(body);
resolve(res);
}
catch (e) {
reject(e);
}
});
};
}
// bodyHandler takes an asynchronous (promisified) function and returns
// a wrapped function that accepts a Message and returns a Promise
function v4AsynchronousBodyHandler(handler) {
return (m) => handler(m.contents.content);
}
//# sourceMappingURL=index.js.map