UNPKG

@pact-foundation/pact

Version:
262 lines 11.1 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Pact = void 0; const node_path_1 = __importDefault(require("node:path")); const node_process_1 = __importDefault(require("node:process")); const pact_core_1 = __importStar(require("@pact-foundation/pact-core")); const chalk_1 = __importDefault(require("chalk")); const lodash_1 = require("lodash"); const package_json_1 = require("../../package.json"); const logger_1 = __importStar(require("../common/logger")); const net_1 = require("../common/net"); const spec_1 = require("../common/spec"); const interaction_1 = require("../dsl/interaction"); const configurationError_1 = __importDefault(require("../errors/configurationError")); const verificationError_1 = __importDefault(require("../errors/verificationError")); const v3_1 = require("../v3"); const display_1 = require("../v3/display"); const ffi_1 = require("./ffi"); const logErrorNoMockServer = () => { logger_1.default.error("The pact mock service doesn't appear to be running\n" + ' - Please check the logs above to ensure that there are no pact service startup failures\n' + ' - Please check that pact lifecycle methods are called in the correct order (setup() needs to be called before this method)\n' + ' - Please check that your test code waits for the promises returned from lifecycle methods to complete before calling the next one\n' + " - To learn more about what is happening during your pact run, try setting logLevel: 'DEBUG'"); }; /** * Creates a new {@link PactProvider}. * @memberof Pact * @name create * @param {PactV2Options} opts * @return {@link PactProvider} */ class Pact { static defaults = { consumer: '', cors: false, dir: node_path_1.default.resolve(node_process_1.default.cwd(), 'pacts'), host: '127.0.0.1', log: node_path_1.default.resolve(node_process_1.default.cwd(), 'logs', 'pact.log'), logLevel: 'info', pactfileWriteMode: 'merge', provider: '', spec: 2, ssl: false, port: 0, }; static createOptionsWithDefaults(opts) { return { ...Pact.defaults, ...opts }; } mockService; opts; mockServerStartedPort; pact; interaction; finalized = false; constructor(config) { this.opts = Pact.createOptionsWithDefaults(config); if (this.opts.pactfileWriteMode === 'overwrite') { logger_1.default.warn("WARNING: the behaviour of pactfileWriteMode 'overwrite' has changed since version 9.x.x. See the type definition or the MIGRATION.md guide for details."); } if ((0, lodash_1.isEmpty)(this.opts.consumer)) { throw new configurationError_1.default('You must specify a Consumer for this pact.'); } if ((0, lodash_1.isEmpty)(this.opts.provider)) { throw new configurationError_1.default('You must specify a Provider for this pact.'); } (0, logger_1.setLogLevel)(this.opts.logLevel); pact_core_1.default.logLevel(this.opts.logLevel); // TODO: this now hangs the process // if (this.opts.logLevel === 'trace') { // traceHttpInteractions(); // } this.reset(); } /** * Setup the pact framework, including allocating a port for the dynamic * mock server * * @returns {Promise} */ async setup() { if (this.opts.port > 0) { await (0, net_1.isPortAvailable)(this.opts.port, this.opts.host); } else { const port = await (0, net_1.freePort)(); logger_1.default.debug(`free port discovered: ${port}`); this.opts.port = port; } // create the mock service this.mockService = { baseUrl: `${this.opts.ssl ? 'https' : 'http'}://${this.opts.host}:${this.opts.port}`, pactDetails: { pactfile_write_mode: this.opts.pactfileWriteMode || 'merge', consumer: { name: this.opts.consumer, }, provider: { name: this.opts.provider }, }, }; return this.opts; } /** * Add an interaction to the {@link MockService}. * @memberof PactProvider * @instance * @param {Interaction} interactionObj * @returns {Promise} */ addInteraction(interactionObj) { if (!this.mockService) { logErrorNoMockServer(); return Promise.reject(new Error("The pact mock service wasn't configured when addInteraction was called")); } let interaction; if (interactionObj instanceof interaction_1.Interaction) { // This will throw if not valid const interactionState = interactionObj.json(); // Convert into the same type interaction = (0, interaction_1.interactionToInteractionObject)(interactionState); } else { interaction = interactionObj; } this.interaction.uponReceiving(interaction.uponReceiving); if (interaction.state) { this.interaction.given(interaction.state); } (0, ffi_1.setRequestDetails)(this.interaction, interaction.withRequest); (0, ffi_1.setResponseDetails)(this.interaction, interaction.willRespondWith); this.startMockServer(); return Promise.resolve(''); } /** * Checks with the Mock Service if the expected interactions have been exercised. * @memberof PactProvider * @instance * @returns {Promise} */ verify() { if (!this.mockService) { logErrorNoMockServer(); return Promise.reject(new Error("The pact mock service wasn't running when verify was called")); } const matchingResults = this.pact.mockServerMismatches(this.opts.port); const success = this.pact.mockServerMatchedSuccessfully(this.opts.port); // Feature flag: allow missing requests on the mock service if (!success) { let error = 'Test failed for the following reasons:'; error += `\n\n ${(0, display_1.generateMockServerError)(matchingResults, '\t')}`; console.error(''); console.error(chalk_1.default.red('Pact verification failed!')); console.error(chalk_1.default.red(error)); this.reset(); throw new verificationError_1.default('Pact verification failed - expected interactions did not match actual.'); } return this.writePact() .then(() => this.reset()) .then(() => ''); } /** * Writes the Pact and clears any interactions left behind and shutdown the * mock server * @memberof PactProvider * @instance * @returns {Promise} */ finalize() { if (this.finalized) { logger_1.default.warn('finalize() has already been called, this is probably a logic error in your test setup. ' + 'In the future this will be an error.'); } this.finalized = true; return Promise.resolve(); } /** * Writes the pact file out to file. Should be called when all tests have been performed for a * given Consumer <-> Provider pair. It will write out the Pact to the * configured file. * @memberof PactProvider * @instance * @returns {Promise} */ writePact() { if (!this.mockService) { logErrorNoMockServer(); return Promise.reject(new Error("The pact mock service wasn't running when writePact was called")); } this.pact.writePactFile(this.opts.dir || './pacts', this.opts.pactfileWriteMode !== 'overwrite'); return Promise.resolve(''); } /** * Clear up any interactions in the Provider Mock Server. * @memberof PactProvider * @instance * @returns {Promise} */ removeInteractions() { logger_1.default.info('removeInteractions() is no longer required to be called, but has been kept for compatibility with upgrade from 9.x.x. You should remove any use of this method.'); return Promise.resolve(''); } startMockServer() { logger_1.default.debug(`Setting up Pact mock server with Consumer "${this.opts.consumer}" and Provider "${this.opts.provider}" using mock service on Port: "${this.opts.port}"`); const port = this.pact.createMockServer(this.opts.host, this.opts.port, this.opts.ssl); this.mockServerStartedPort = port; logger_1.default.debug(`mock service started on port: ${port}`); } // reset the internal state // (this.pact cannot be re-used between tests) reset() { this.pact = (0, pact_core_1.makeConsumerPact)(this.opts.consumer, this.opts.provider, (0, spec_1.numberToSpec)(this.opts.spec, v3_1.SpecificationVersion.SPECIFICATION_VERSION_V2), this.opts.logLevel ?? 'info'); this.interaction = this.pact.newInteraction(''); if (this.mockServerStartedPort) { logger_1.default.debug(`cleaning up old mock server on port ${this.opts.port}`); const res = this.pact.cleanupMockServer(this.mockServerStartedPort); if (!res) { logger_1.default.warn('Unable to cleanup the Pact mock server. '); } this.mockServerStartedPort = undefined; } this.pact.addMetadata('pact-js', 'version', package_json_1.version); } } exports.Pact = Pact; //# sourceMappingURL=index.js.map