@pact-foundation/pact
Version:
Pact for all things Javascript
319 lines • 15.2 kB
JavaScript
"use strict";
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
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 __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (g && (g = 0, op[0] && (_ = 0)), _) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Pact = void 0;
var pact_core_1 = __importStar(require("@pact-foundation/pact-core"));
var chalk_1 = __importDefault(require("chalk"));
var path_1 = __importDefault(require("path"));
var process_1 = __importDefault(require("process"));
var lodash_1 = require("lodash");
var interaction_1 = require("../dsl/interaction");
var net_1 = require("../common/net");
var logger_1 = __importStar(require("../common/logger"));
var verificationError_1 = __importDefault(require("../errors/verificationError"));
var configurationError_1 = __importDefault(require("../errors/configurationError"));
var v3_1 = require("../v3");
var package_json_1 = require("../../package.json");
var display_1 = require("../v3/display");
var spec_1 = require("../common/spec");
var ffi_1 = require("./ffi");
var logErrorNoMockServer = function () {
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 {PactOptions} opts
* @return {@link PactProvider}
*/
var Pact = /** @class */ (function () {
function Pact(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();
}
Pact.createOptionsWithDefaults = function (opts) {
return __assign(__assign({}, Pact.defaults), opts);
};
/**
* Setup the pact framework, including allocating a port for the dynamic
* mock server
*
* @returns {Promise}
*/
Pact.prototype.setup = function () {
return __awaiter(this, void 0, void 0, function () {
var port;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
if (!(this.opts.port > 0)) return [3 /*break*/, 2];
return [4 /*yield*/, (0, net_1.isPortAvailable)(this.opts.port, this.opts.host)];
case 1:
_a.sent();
return [3 /*break*/, 4];
case 2: return [4 /*yield*/, (0, net_1.freePort)()];
case 3:
port = _a.sent();
logger_1.default.debug("free port discovered: ".concat(port));
this.opts.port = port;
_a.label = 4;
case 4:
// create the mock service
this.mockService = {
baseUrl: "".concat(this.opts.ssl ? 'https' : 'http', "://").concat(this.opts.host, ":").concat(this.opts.port),
pactDetails: {
pactfile_write_mode: this.opts.pactfileWriteMode || 'merge',
consumer: {
name: this.opts.consumer,
},
provider: { name: this.opts.provider },
},
};
return [2 /*return*/, this.opts];
}
});
});
};
/**
* Add an interaction to the {@link MockService}.
* @memberof PactProvider
* @instance
* @param {Interaction} interactionObj
* @returns {Promise}
*/
Pact.prototype.addInteraction = function (interactionObj) {
if (!this.mockService) {
logErrorNoMockServer();
return Promise.reject(new Error("The pact mock service wasn't configured when addInteraction was called"));
}
var interaction;
if (interactionObj instanceof interaction_1.Interaction) {
// This will throw if not valid
var 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}
*/
Pact.prototype.verify = function () {
var _this = this;
if (!this.mockService) {
logErrorNoMockServer();
return Promise.reject(new Error("The pact mock service wasn't running when verify was called"));
}
var matchingResults = this.pact.mockServerMismatches(this.opts.port);
var success = this.pact.mockServerMatchedSuccessfully(this.opts.port);
// Feature flag: allow missing requests on the mock service
if (!success) {
var error = 'Test failed for the following reasons:';
error += "\n\n ".concat((0, display_1.generateMockServerError)(matchingResults, '\t'));
/* eslint-disable no-console */
console.error('');
console.error(chalk_1.default.red('Pact verification failed!'));
console.error(chalk_1.default.red(error));
/* eslint-enable */
this.reset();
throw new verificationError_1.default('Pact verification failed - expected interactions did not match actual.');
}
return this.writePact()
.then(function () { return _this.reset(); })
.then(function () { return ''; });
};
/**
* Writes the Pact and clears any interactions left behind and shutdown the
* mock server
* @memberof PactProvider
* @instance
* @returns {Promise}
*/
Pact.prototype.finalize = function () {
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}
*/
Pact.prototype.writePact = function () {
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}
*/
Pact.prototype.removeInteractions = function () {
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('');
};
Pact.prototype.startMockServer = function () {
logger_1.default.debug("Setting up Pact mock server with Consumer \"".concat(this.opts.consumer, "\" and Provider \"").concat(this.opts.provider, "\"\n using mock service on Port: \"").concat(this.opts.port, "\""));
var 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: ".concat(port));
};
// reset the internal state
// (this.pact cannot be re-used between tests)
Pact.prototype.reset = function () {
var _a;
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), (_a = this.opts.logLevel) !== null && _a !== void 0 ? _a : 'info');
this.interaction = this.pact.newInteraction('');
if (this.mockServerStartedPort) {
logger_1.default.debug("cleaning up old mock server on port ".concat(this.opts.port));
var 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);
};
Pact.defaults = {
consumer: '',
cors: false,
dir: path_1.default.resolve(process_1.default.cwd(), 'pacts'),
host: '127.0.0.1',
log: path_1.default.resolve(process_1.default.cwd(), 'logs', 'pact.log'),
logLevel: 'info',
pactfileWriteMode: 'merge',
provider: '',
spec: 2,
ssl: false,
port: 0,
};
return Pact;
}());
exports.Pact = Pact;
//# sourceMappingURL=index.js.map