@pact-foundation/pact
Version:
Pact for all things Javascript
262 lines • 11.1 kB
JavaScript
"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