UNPKG

@pact-foundation/pact

Version:
335 lines 12.7 kB
"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