Socket
Socket
Sign inDemoInstall

@pact-foundation/pact-node

Package Overview
Dependencies
13
Maintainers
4
Versions
187
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 5.2.1 to 6.0.0

docker-compose.yml

11

bin/pact-cli.spec.ts

@@ -5,3 +5,2 @@ import chai = require("chai");

import q = require("q");
import request = require("request");
import path = require("path");

@@ -12,5 +11,5 @@ import _ = require("underscore");

import decamelize = require("decamelize");
import provider from "../test/integration/provider";
import providerMock from "../test/integration/provider-mock";
const http = q.denodeify(request);
const request = q.denodeify(require("request"));
const pkg = require("../package.json");

@@ -51,3 +50,3 @@ const isWindows = process.platform === "win32";

expect(p).to.eventually.be.fulfilled,
expect(p).to.eventually.contain("Creating Pact with PID"),
expect(p).to.eventually.match(/Created.*process with PID/),
]);

@@ -75,3 +74,3 @@ });

before((done) => server = provider.listen(PORT, () => done()));
before(() => providerMock(PORT).then((s) => server = s));
after(() => server.close());

@@ -188,3 +187,3 @@

return http(config)
return request(config)
.then((data) => data[0])

@@ -191,0 +190,0 @@ .then((response) => {

{
"name": "@pact-foundation/pact-node",
"version": "5.2.1",
"description": "A wrapper for the Ruby version of Pact to work within Node",
"main": "src/index.js",
"homepage": "https://github.com/pact-foundation/pact-node#readme",
"types": "src/index.d.ts",
"bin": {
"pact": "bin/pact-cli.js"
},
"os": [
"darwin",
"linux",
"win32"
],
"cpu": [
"x64",
"ia32"
],
"engine": {
"node": ">=4"
},
"repository": {
"type": "git",
"url": "git://github.com/pact-foundation/pact-node.git"
},
"keywords": [
"pact",
"node",
"wrapper",
"mock",
"service",
"provider",
"verifier"
],
"author": "Michel Boudreau <michelboudreau@gmail.com> (codinghitchhiker.com)",
"contributors": [
"Matt Fellows <m@onegeek.com.au> (http://www.onegeek.com.au)"
],
"license": "MIT",
"bugs": {
"url": "https://github.com/pact-foundation/pact-node/issues"
},
"publishConfig": {
"access": "public"
},
"dependencies": {
"@pact-foundation/pact-standalone": "~1.1.4",
"bunyan": "1.8.12",
"bunyan-prettystream": "0.1.3",
"check-types": "7.3.0",
"mkdirp": "0.5.1",
"q": "1.5.0",
"request": "2.81.0",
"traverson": "6.0.3",
"traverson-hal": "6.0.0",
"traverson-promise": "0.0.9",
"underscore": "1.8.3",
"unixify": "1.0.0",
"url-join": "2.0.2"
},
"devDependencies": {
"@types/bunyan": "1.8.2",
"@types/chai": "4.0.4",
"@types/chai-as-promised": "0.0.31",
"@types/express": "4.0.37",
"@types/mocha": "2.2.42",
"@types/node": "8.0.26",
"@types/q": "1.0.5",
"@types/request": "2.0.3",
"basic-auth": "1.1.0",
"body-parser": "1.17.2",
"caporal": "0.7.0",
"chai": "4.1.1",
"chai-as-promised": "7.1.1",
"cors": "2.8.4",
"cross-env": "5.0.5",
"decamelize": "^1.2.0",
"express": "4.15.4",
"jscs": "3.0.7",
"mocha": "3.5.0",
"nodemon": "1.11.0",
"rewire": "2.5.2",
"rimraf": "2.6.1",
"sinon": "3.2.1",
"ts-node": "3.3.0",
"tslint": "5.7.0",
"typescript": "2.4.2"
},
"scripts": {
"clean": "rimraf '{src,test,bin}/**/*.{js,map,d.ts}' 'package.zip'",
"lint": "tslint -p tsconfig.json",
"pretest": "npm run build",
"test": "mocha -r ts-node/register -R spec --timeout 10000 \"{src,test,bin}/**/*.spec.ts\"",
"dev": "npm run lint --force && npm test && node .",
"build": "npm run clean && tsc",
"start": "npm run watch"
}
"name": "@pact-foundation/pact-node",
"version": "6.0.0",
"description": "A wrapper for the Ruby version of Pact to work within Node",
"main": "src/index.js",
"homepage": "https://github.com/pact-foundation/pact-node#readme",
"types": "src/index.d.ts",
"bin": {
"pact": "bin/pact-cli.js"
},
"os": [
"darwin",
"linux",
"win32"
],
"cpu": [
"x64",
"ia32"
],
"engine": {
"node": ">=4"
},
"repository": {
"type": "git",
"url": "git://github.com/pact-foundation/pact-node.git"
},
"keywords": [
"pact",
"node",
"wrapper",
"mock",
"service",
"provider",
"verifier"
],
"author": "Michel Boudreau <michelboudreau@gmail.com> (codinghitchhiker.com)",
"contributors": [
"Matt Fellows <m@onegeek.com.au> (http://www.onegeek.com.au)"
],
"license": "MIT",
"bugs": {
"url": "https://github.com/pact-foundation/pact-node/issues"
},
"publishConfig": {
"access": "public"
},
"dependencies": {
"@pact-foundation/pact-standalone": "~3.3.0",
"bunyan": "1.8.12",
"bunyan-prettystream": "0.1.3",
"check-types": "7.3.0",
"mkdirp": "0.5.1",
"q": "1.5.0",
"request": "2.81.0",
"traverson": "6.0.3",
"traverson-hal": "6.0.0",
"traverson-promise": "0.0.9",
"underscore": "1.8.3",
"unixify": "1.0.0",
"url-join": "2.0.2"
},
"devDependencies": {
"@types/bunyan": "1.8.2",
"@types/chai": "4.0.4",
"@types/chai-as-promised": "0.0.31",
"@types/express": "4.0.37",
"@types/mocha": "2.2.42",
"@types/node": "8.0.26",
"@types/q": "1.0.5",
"@types/request": "2.0.3",
"basic-auth": "1.1.0",
"body-parser": "1.17.2",
"caporal": "0.7.0",
"chai": "4.1.1",
"chai-as-promised": "7.1.1",
"cors": "2.8.4",
"cross-env": "5.0.5",
"decamelize": "^1.2.0",
"express": "4.15.4",
"jscs": "3.0.7",
"mocha": "3.5.0",
"mocha-unfunk-reporter": "^0.4.0",
"nodemon": "1.11.0",
"rewire": "2.5.2",
"rimraf": "2.6.1",
"sinon": "3.2.1",
"ts-node": "3.3.0",
"tslint": "5.7.0",
"typescript": "2.4.2"
},
"scripts": {
"clean": "rimraf '{src,test,bin}/**/*.{js,map,d.ts}' 'package.zip' '.tmp'",
"lint": "tslint -p tsconfig.json",
"pretest": "npm run build",
"test": "mocha -r ts-node/register -R mocha-unfunk-reporter --timeout 10000 \"{src,test,bin}/**/*.spec.ts\"",
"dev": "npm run lint --force && npm test && node .",
"build": "npm run clean && tsc",
"start": "npm run watch"
}
}

@@ -84,4 +84,4 @@ <img src="https://raw.githubusercontent.com/pact-foundation/pact-logo/master/media/logo-black.png" width="200">

providerBaseUrl: <String>, // Running API provider host endpoint. Required.
pactBrokerUrl: <String> // URL of the Pact Broker to retrieve pacts from. Required if not using pactUrls.
provider: <String> // Name of the Provider. Required if not using pactUrls.
pactBrokerUrl: <String> // URL to fetch the pacts if pactUrls not supplied. Optional.
provider: <String> // Name of the provider if fetching from a Broker. Optional.
tags: <Array> // Array of tags, used to filter pacts from the Broker. Optional.

@@ -103,3 +103,3 @@ pactUrls: <Array>, // Array of local Pact file paths or HTTP-based URLs (e.g. from a broker). Required if not using a Broker.

var opts = {
pactUrls: <Array>, // Array of local Pact files or directories containing them. Required.
pactFilesOrDirs: <Array>, // Array of local Pact files or directories containing them. Required.
pactBroker: <String>, // URL to fetch the provider states for the given provider API. Optional.

@@ -106,0 +106,0 @@ pactBrokerUsername: <String>, // Username for Pact Broker basic authentication. Optional

@@ -6,6 +6,5 @@ /// <reference types="q" />

readonly options: BrokerOptions;
private __requestOptions;
constructor(options: BrokerOptions);
findPacts(tag?: string): any;
findConsumers(): q.Promise<{}>;
findPacts(tag?: string): q.Promise<any>;
findConsumers(): q.Promise<string[]>;
}

@@ -12,0 +11,0 @@ declare const _default: typeof Broker.create;

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var checkTypes = require("check-types");
var traverson = require("traverson-promise");
var JsonHalAdapter = require("traverson-hal");
var q = require("q");
var _ = require("underscore");
var logger_1 = require("./logger");
traverson.registerMediaType(JsonHalAdapter.mediaType, JsonHalAdapter);
var pactURLPattern = "/pacts/provider/%s/latest";
var pactURLPatternWithTag = "/pacts/provider/%s/latest/%s";
var request = q.denodeify(require("request"));
var Broker = (function () {
function Broker(options) {
this.options = options;
this.__requestOptions = this.options.username && this.options.password ? {
"auth": {
"user": this.options.username,
"password": this.options.password
}
} : {};
}
Broker.create = function (options) {
options.username = options.username;
options.password = options.password;
options.tags = options.tags || [];

@@ -39,12 +28,26 @@ checkTypes.assert.nonEmptyString(options.brokerUrl);

Broker.prototype.findPacts = function (tag) {
logger_1.default.debug("finding pacts for Provider:", this.options.provider, ", Tag:", tag);
var linkName = tag ? "pb:latest-provider-pacts-with-tag" : "pb:latest-provider-pacts";
return traverson
.from(this.options.brokerUrl)
.withTemplateParameters({ provider: this.options.provider, tag: tag })
.withRequestOptions(this.__requestOptions)
.jsonHal()
.follow(linkName)
.getResource()
.result;
var _this = this;
logger_1.default.debug("finding pacts for Provider: " + this.options.provider + " Tag: " + tag);
var requestOptions = {
uri: this.options.brokerUrl,
method: "GET",
headers: {
"Content-Type": "application/json"
},
"auth": this.options.username && this.options.password ? {
"user": this.options.username,
"password": this.options.password
} : null
};
return request(requestOptions)
.then(function (data) { return data[0]; })
.then(function (response) {
if (response.statusCode < 200 && response.statusCode >= 300) {
return q.reject(response);
}
var body = JSON.parse(response.body);
return request(_.extend({}, requestOptions, { uri: body._links["pb:latest-provider-pacts" + (tag ? "-with-tag" : "")].href.replace("{tag}", tag).replace("{provider}", _this.options.provider) }));
})
.then(function (data) { return data[0]; })
.then(function (response) { return response.statusCode < 200 && response.statusCode >= 300 ? q.reject(response) : JSON.parse(response.body); });
};

@@ -54,16 +57,10 @@ Broker.prototype.findConsumers = function () {

logger_1.default.debug("Finding consumers");
var promises = (this.options.tags.length > 0) ? this.options.tags.map(this.findPacts, this) : [this.findPacts()];
var promises = _.isEmpty(this.options.tags) ? [this.findPacts()] : _.map(this.options.tags, function (t) { return _this.findPacts(t); });
return q.all(promises)
.then(function (values) {
var pactUrls = {};
values.forEach(function (response) {
if (response && response._links && response._links.pacts) {
response._links.pacts.forEach(function (pact) { return pactUrls[pact.title] = pact.href; });
}
});
return Object.keys(pactUrls).reduce(function (pacts, key) {
pacts.push(pactUrls[key]);
return pacts;
}, []);
})
.then(function (values) { return _.reduce(values, function (array, v) {
if (v && v._links && v._links.pacts) {
array.push.apply(array, _.pluck(v._links.pacts, "href"));
}
return array;
}, []); })
.catch(function () { return q.reject("Unable to find pacts for given provider '" + _this.options.provider + "' and tags '" + _this.options.tags + "'"); });

@@ -70,0 +67,0 @@ };

import chai = require("chai");
import chaiAsPromised = require("chai-as-promised");
import logger from "./logger";
import brokerMock from "../test/integration/brokerMock.js";
import brokerMock from "../test/integration/broker-mock";
import brokerFactory from "./broker";
import * as http from "http";

@@ -11,10 +12,13 @@ const expect = chai.expect;

describe("Broker Spec", () => {
const PORT = 9124;
let server: http.Server;
const PORT = Math.floor(Math.random() * 999) + 9000;
const pactBrokerBaseUrl = `http://localhost:${PORT}`;
before((done) => brokerMock.listen(PORT, () => {
logger.debug(`Broker (Mock) running on port: ${PORT}`);
done();
before(() => brokerMock(PORT).then((s) => {
logger.debug(`Pact Broker Mock listening on port: ${PORT}`);
server = s;
}));
after(() => server.close());
describe("Broker", () => {

@@ -51,10 +55,6 @@ context("when not given a Pact Broker URL", () => {

it("should fail with an Error", () => {
const broker = brokerFactory({
return expect(brokerFactory({
brokerUrl: pactBrokerBaseUrl,
provider: "notfound",
username: "dXfltyFMgNOFZAxr8io9wJ37iUpY42M",
password: "O5AIZWxelWbLvqMd8PkAVycBJh2Psyg1"
});
const promise = broker.findConsumers();
return expect(promise).to.eventually.be.rejected;
provider: "notfound"
}).findConsumers()).to.eventually.be.rejected;
});

@@ -66,10 +66,6 @@ });

it("should return an empty array of pact links", () => {
const broker = brokerFactory({
return expect(brokerFactory({
brokerUrl: pactBrokerBaseUrl,
provider: "nolinks",
username: "dXfltyFMgNOFZAxr8io9wJ37iUpY42M",
password: "O5AIZWxelWbLvqMd8PkAVycBJh2Psyg1"
});
const promise = broker.findConsumers();
return expect(promise).to.eventually.eql([]);
provider: "nolinks"
}).findConsumers()).to.eventually.eql([]);
});

@@ -82,11 +78,7 @@ });

it("should find pacts from all known consumers of the provider given any of the tags", () => {
const broker = brokerFactory({
return expect(brokerFactory({
brokerUrl: pactBrokerBaseUrl,
provider: "they",
username: "dXfltyFMgNOFZAxr8io9wJ37iUpY42M",
password: "O5AIZWxelWbLvqMd8PkAVycBJh2Psyg1",
tags: ["prod"]
});
const promise = broker.findConsumers();
return expect(promise).to.eventually.have.lengthOf(2);
}).findConsumers()).to.eventually.have.lengthOf(2);
});

@@ -97,10 +89,6 @@ });

it("should find pacts from all known consumers of the provider", () => {
const broker = brokerFactory({
return expect(brokerFactory({
brokerUrl: pactBrokerBaseUrl,
provider: "they",
username: "dXfltyFMgNOFZAxr8io9wJ37iUpY42M",
password: "O5AIZWxelWbLvqMd8PkAVycBJh2Psyg1"
});
const promise = broker.findConsumers();
return expect(promise).to.eventually.have.lengthOf(2);
provider: "they"
}).findConsumers()).to.eventually.have.lengthOf(2);
});

@@ -107,0 +95,0 @@ });

import checkTypes = require("check-types");
import traverson = require("traverson-promise");
import JsonHalAdapter = require("traverson-hal");
import q = require("q");
import _ = require("underscore");
import logger from "./logger";
// register the traverson-hal plug-in for media type "application/hal+json"
traverson.registerMediaType(JsonHalAdapter.mediaType, JsonHalAdapter);
const request = q.denodeify(require("request"));
const pactURLPattern = "/pacts/provider/%s/latest";
const pactURLPatternWithTag = "/pacts/provider/%s/latest/%s";
export class Broker {
public static create(options: BrokerOptions) {
// defaults
options.username = options.username;
options.password = options.password;
options.tags = options.tags || [];

@@ -39,27 +32,32 @@

public readonly options: BrokerOptions;
private __requestOptions;
constructor(options: BrokerOptions) {
this.options = options;
this.__requestOptions = this.options.username && this.options.password ? {
"auth": {
"user": this.options.username,
"password": this.options.password
}
} : {};
}
// Find Pacts returns the raw response from the HAL resource
public findPacts(tag?: string) {
logger.debug("finding pacts for Provider:", this.options.provider, ", Tag:", tag);
const linkName = tag ? "pb:latest-provider-pacts-with-tag" : "pb:latest-provider-pacts";
return traverson
.from(this.options.brokerUrl)
.withTemplateParameters({provider: this.options.provider, tag: tag})
.withRequestOptions(this.__requestOptions)
.jsonHal()
.follow(linkName)
.getResource()
.result;
public findPacts(tag?: string): q.Promise<any> {
logger.debug(`finding pacts for Provider: ${this.options.provider} Tag: ${tag}`);
const requestOptions = {
uri: this.options.brokerUrl,
method: "GET",
headers: {
"Content-Type": "application/json"
},
"auth": this.options.username && this.options.password ? {
"user": this.options.username,
"password": this.options.password
} : null
};
return request(requestOptions)
.then((data) => data[0])
.then((response) => {
if (response.statusCode < 200 && response.statusCode >= 300) {
return q.reject(response);
}
const body = JSON.parse(response.body);
return request(_.extend({}, requestOptions, {uri: body._links[`pb:latest-provider-pacts${tag ? "-with-tag" : ""}`].href.replace("{tag}", tag).replace("{provider}", this.options.provider)}));
})
.then((data) => data[0])
.then((response) => response.statusCode < 200 && response.statusCode >= 300 ? q.reject(response) : JSON.parse(response.body));
}

@@ -69,19 +67,13 @@

// and removes duplicates (e.g. where multiple tags on the same pact)
public findConsumers() {
public findConsumers(): q.Promise<string[]> {
logger.debug("Finding consumers");
const promises = (this.options.tags.length > 0) ? this.options.tags.map(this.findPacts, this) : [this.findPacts()];
const promises = _.isEmpty(this.options.tags) ? [this.findPacts()] : _.map(this.options.tags, (t) => this.findPacts(t));
return q.all(promises)
.then((values) => {
const pactUrls = {};
values.forEach((response) => {
if (response && response._links && response._links.pacts) {
response._links.pacts.forEach((pact) => pactUrls[pact.title] = pact.href);
}
});
return Object.keys(pactUrls).reduce((pacts, key) => {
pacts.push(pactUrls[key]);
return pacts;
}, []);
})
.then((values) => _.reduce(values, (array, v) => {
if (v && v._links && v._links.pacts) {
array.push(..._.pluck(v._links.pacts, "href"));
}
return array;
}, []))
.catch(() => q.reject(`Unable to find pacts for given provider '${this.options.provider}' and tags '${this.options.tags}'`));

@@ -88,0 +80,0 @@ }

@@ -0,5 +1,17 @@

/// <reference types="node" />
import { ChildProcess } from "child_process";
export declare const DEFAULT_ARG = "DEFAULT";
export declare class PactUtil {
createArguments(args: any, mappings: any): any;
createArguments(args: SpawnArguments, mappings: {
[id: string]: string;
}): string[];
spawnBinary(command: string, args?: SpawnArguments, argMapping?: {
[id: string]: string;
}): ChildProcess;
killBinary(binary: ChildProcess): boolean;
}
export interface SpawnArguments {
[id: string]: string | string[] | boolean | number;
}
declare const _default: PactUtil;
export default _default;

@@ -5,2 +5,7 @@ "use strict";

var checkTypes = require("check-types");
var pactStandalone = require("@pact-foundation/pact-standalone");
var cp = require("child_process");
var logger_1 = require("./logger");
var isWindows = process.platform === "win32";
exports.DEFAULT_ARG = "DEFAULT";
var PactUtil = (function () {

@@ -11,7 +16,14 @@ function PactUtil() {

return _.chain(args)
.map(function (value, key) {
.reduce(function (acc, value, key) {
if (value && mappings[key]) {
return [mappings[key], "'" + (checkTypes.array(value) ? value.join(",") : value) + "'"];
var mapping_1 = mappings[key];
var f_1 = acc.push.bind(acc);
if (mapping_1 === exports.DEFAULT_ARG) {
mapping_1 = null;
f_1 = acc.unshift.bind(acc);
}
_.map(checkTypes.array(value) ? value : [value], function (v) { return f_1([mapping_1, "'" + v + "'"]); });
}
})
return acc;
}, [])
.flatten()

@@ -21,2 +33,54 @@ .compact()

};
PactUtil.prototype.spawnBinary = function (command, args, argMapping) {
if (args === void 0) { args = {}; }
if (argMapping === void 0) { argMapping = {}; }
var envVars = JSON.parse(JSON.stringify(process.env));
delete envVars["RUBYGEMS_GEMDEPS"];
var file;
var opts = {
cwd: pactStandalone.cwd,
detached: !isWindows,
env: envVars
};
var cmd = [command].concat(this.createArguments(args, argMapping)).join(" ");
var spawnArgs;
if (isWindows) {
file = "cmd.exe";
spawnArgs = ["/s", "/c", cmd];
opts.windowsVerbatimArguments = true;
}
else {
cmd = "./" + cmd;
file = "/bin/sh";
spawnArgs = ["-c", cmd];
}
logger_1.default.debug("Starting pact binary with '" + _.flatten([file, args, JSON.stringify(opts)]) + "'");
var instance = cp.spawn(file, spawnArgs, opts);
instance.stdout.setEncoding("utf8");
instance.stderr.setEncoding("utf8");
instance.stdout.on("data", logger_1.default.debug.bind(logger_1.default));
instance.stderr.on("data", logger_1.default.debug.bind(logger_1.default));
instance.on("error", logger_1.default.error.bind(logger_1.default));
instance.once("close", function (code) {
if (code !== 0) {
logger_1.default.warn("Pact exited with code " + code + ".");
}
});
logger_1.default.info("Created '" + cmd + "' process with PID: " + instance.pid);
return instance;
};
PactUtil.prototype.killBinary = function (binary) {
if (binary) {
var pid = binary.pid;
logger_1.default.info("Removing Pact with PID: " + pid);
binary.removeAllListeners();
try {
isWindows ? cp.execSync("taskkill /f /t /pid " + pid) : process.kill(-pid, "SIGINT");
}
catch (e) {
return false;
}
}
return true;
};
return PactUtil;

@@ -23,0 +87,0 @@ }());

import chai = require("chai");
import chaiAsPromised = require("chai-as-promised");
import pactUtil from "./pact-util";
import pactUtil, {DEFAULT_ARG} from "./pact-util";

@@ -11,18 +11,34 @@ const expect = chai.expect;

describe("createArguments", () => {
context("when provided any arguments", () => {
it("should wrap its arguments in quotes", () => {
const result = pactUtil.createArguments({
providerBaseUrl: "http://localhost",
pactUrls: ["http://idontexist"]
}, {
providerBaseUrl: "--provider-base-url",
pactUrls: "--pact-urls"
});
expect(result).to.include("--provider-base-url");
expect(result).to.include("'http://localhost'");
expect(result).to.include("--pact-urls");
expect(result).to.include("'http://idontexist'");
it("should return an array of all arguments", () => {
const result = pactUtil.createArguments({providerBaseUrl: "http://localhost",}, {providerBaseUrl: "--provider-base-url",});
expect(result).to.be.an("array").that.includes("--provider-base-url");
expect(result.length).to.be.equal(2);
});
it("should wrap its argument values in quotes", () => {
const result = pactUtil.createArguments({
providerBaseUrl: "http://localhost",
pactUrls: ["http://idontexist"]
}, {
providerBaseUrl: "--provider-base-url",
pactUrls: "--pact-urls"
});
expect(result).to.include("--provider-base-url");
expect(result).to.include("'http://localhost'");
expect(result).to.include("--pact-urls");
expect(result).to.include("'http://idontexist'");
});
it("should make DEFAULT values first, everything else after", () => {
const result = pactUtil.createArguments({
providerBaseUrl: "http://localhost",
pactUrls: ["http://idontexist"]
}, {
providerBaseUrl: "--provider-base-url",
pactUrls: DEFAULT_ARG
});
expect(result.length).to.be.equal(3);
expect(result[0]).to.be.equal("'http://idontexist'");
});
});
});

@@ -0,12 +1,28 @@

// tslint:disable:no-string-literal
import _ = require("underscore");
import checkTypes = require("check-types");
import pactStandalone = require("@pact-foundation/pact-standalone");
import cp = require("child_process");
import logger from "./logger";
import {ChildProcess, SpawnOptions} from "child_process";
const isWindows = process.platform === "win32";
export const DEFAULT_ARG = "DEFAULT";
export class PactUtil {
public createArguments(args, mappings) {
public createArguments(args: SpawnArguments, mappings: { [id: string]: string }): string[] {
return _.chain(args)
.map((value, key) => {
.reduce((acc, value, key) => {
if (value && mappings[key]) {
return [mappings[key], `'${checkTypes.array(value) ? value.join(",") : value}'`];
let mapping = mappings[key];
let f = acc.push.bind(acc);
if (mapping === DEFAULT_ARG) {
mapping = null;
f = acc.unshift.bind(acc);
}
_.map(checkTypes.array(value) ? value : [value], (v) => f([mapping, `'${v}'`]));
}
})
return acc;
}, [])
.flatten()

@@ -16,4 +32,68 @@ .compact()

}
public spawnBinary(command: string, args: SpawnArguments = {}, argMapping: { [id: string]: string } = {}): ChildProcess {
const envVars = JSON.parse(JSON.stringify(process.env)); // Create copy of environment variables
// Remove environment variable if there
// This is a hack to prevent some weird Travelling Ruby behaviour with Gems
// https://github.com/pact-foundation/pact-mock-service-npm/issues/16
delete envVars["RUBYGEMS_GEMDEPS"];
let file: string;
let opts: SpawnOptions = {
cwd: pactStandalone.cwd,
detached: !isWindows,
env: envVars
};
let cmd: string = [command].concat(this.createArguments(args, argMapping)).join(" ");
let spawnArgs: string[];
if (isWindows) {
file = "cmd.exe";
spawnArgs = ["/s", "/c", cmd];
(opts as any).windowsVerbatimArguments = true;
} else {
cmd = `./${cmd}`;
file = "/bin/sh";
spawnArgs = ["-c", cmd];
}
logger.debug(`Starting pact binary with '${_.flatten([file, args, JSON.stringify(opts)])}'`);
const instance = cp.spawn(file, spawnArgs, opts);
instance.stdout.setEncoding("utf8");
instance.stderr.setEncoding("utf8");
instance.stdout.on("data", logger.debug.bind(logger));
instance.stderr.on("data", logger.debug.bind(logger));
instance.on("error", logger.error.bind(logger));
instance.once("close", (code) => {
if (code !== 0) {
logger.warn(`Pact exited with code ${code}.`);
}
});
logger.info(`Created '${cmd}' process with PID: ${instance.pid}`);
return instance;
}
public killBinary(binary: ChildProcess): boolean {
if (binary) {
const pid = binary.pid;
logger.info(`Removing Pact with PID: ${pid}`);
binary.removeAllListeners();
// Killing instance, since windows can't send signals, must kill process forcefully
try {
isWindows ? cp.execSync(`taskkill /f /t /pid ${pid}`) : process.kill(-pid, "SIGINT");
} catch (e) {
return false;
}
}
return true;
}
}
export interface SpawnArguments {
[id: string]: string | string[] | boolean | number;
}
export default new PactUtil();

@@ -240,3 +240,3 @@ import chai = require("chai");

// These tests never worked because the expect was wrong. When fixed, massive issue ensues
xdescribe("Verify Pacts", () => {
describe.skip("Verify Pacts", () => {
context("With provider states", () => {

@@ -253,3 +253,3 @@ it("should start the pact-provider-verifier service and verify pacts", () => {

xdescribe("Publish Pacts", () => {
describe.skip("Publish Pacts", () => {
it("should start running the Pact publishing process", () => {

@@ -256,0 +256,0 @@ let opts = {

/// <reference types="q" />
import q = require("q");
import { SpawnArguments } from "./pact-util";
export declare class Publisher {
static create(options: PublisherOptions): Publisher;
readonly options: PublisherOptions;
private readonly __argMapping;
constructor(options?: PublisherOptions);
publish(): q.Promise<any[]>;
private __callPact(options, config);
private __getPactFile(options, uri);
private __constructPutUrl(options, data);
private __constructTagUrl(options, tag, data);
publish(): q.Promise<string[]>;
}
declare const _default: typeof Publisher.create;
export default _default;
export interface PublisherOptions {
export interface PublisherOptions extends SpawnArguments {
pactFilesOrDirs?: string[];
pactBroker?: string;
pactUrls?: string[];
consumerVersion?: string;

@@ -22,10 +20,4 @@ pactBrokerUsername?: string;

tags?: string[];
verbose?: boolean;
timeout?: number;
}
export interface PublishData {
consumer: {
name: string;
};
provider: {
name: string;
};
}
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var checkTypes = require("check-types");
var _ = require("underscore");
var path = require("path");
var fs = require("fs");
var q = require("q");
var request = require("request");
var urlJoin = require("url-join");
var logger_1 = require("./logger");
var http = q.denodeify(request);
var pactStandalone = require("@pact-foundation/pact-standalone");
var pact_util_1 = require("./pact-util");
var Publisher = (function () {
function Publisher(options) {
if (options === void 0) { options = {}; }
this.__argMapping = {
"pactFilesOrDirs": pact_util_1.DEFAULT_ARG,
"pactBroker": "--broker-base-url",
"pactBrokerUsername": "--broker-username",
"pactBrokerPassword": "--broker-password",
"tags": "--tag",
"consumerVersion": "--consumer-app-version",
"verbose": "--verbose"
};
this.options = options;

@@ -20,22 +25,11 @@ }

options.pactBroker = options.pactBroker || "";
options.pactUrls = options.pactUrls || [];
options.pactFilesOrDirs = options.pactFilesOrDirs || [];
options.tags = options.tags || [];
if (options.pactUrls) {
checkTypes.assert.array.of.string(options.pactUrls);
options.timeout = options.timeout || 60000;
if (options.pactFilesOrDirs) {
checkTypes.assert.array.of.string(options.pactFilesOrDirs);
}
var url = require("url");
_.each(options.pactUrls, function (uri) {
var proto = url.parse(uri).protocol;
if (proto === "file://" || proto === null) {
try {
fs.statSync(path.normalize(uri));
}
catch (e) {
throw new Error("Pact contract or directory: '" + uri + "' doesn't exist");
}
}
});
checkTypes.assert.nonEmptyString(options.pactBroker, "Must provide the pactBroker argument");
checkTypes.assert.nonEmptyString(options.consumerVersion, "Must provide the consumerVersion argument");
checkTypes.assert.not.emptyArray(options.pactUrls, "Must provide the pactUrls argument");
checkTypes.assert.not.emptyArray(options.pactFilesOrDirs, "Must provide the pactFilesOrDirs argument");
if (options.pactBrokerUsername) {

@@ -56,149 +50,21 @@ checkTypes.assert.string(options.pactBrokerUsername);

Publisher.prototype.publish = function () {
var _this = this;
logger_1.default.info("Publishing pacts to broker at: " + this.options.pactBroker);
return q(_.chain(this.options.pactUrls)
.map(function (uri) {
var localFileOrDir = path.normalize(uri);
if (!(/^http/.test(uri)) && fs.statSync(localFileOrDir) && fs.statSync(localFileOrDir).isDirectory()) {
uri = localFileOrDir;
return _.map(fs.readdirSync(uri, ""), function (file) {
if (/\.json$/.test(file)) {
return path.join(uri, file);
}
});
var deferred = q.defer();
var instance = pact_util_1.default.spawnBinary(pactStandalone.brokerPath + " publish", this.options, this.__argMapping);
var output = [];
instance.stdout.on("data", function (l) { return output.push(l); });
instance.stderr.on("data", function (l) { return output.push(l); });
instance.once("close", function (code) {
var o = output.join("\n");
var pactUrls = /^https?:\/\/.*\/pacts\/.*$/igm.exec(o);
if (code !== 0 || !pactUrls) {
logger_1.default.error("Could not publish pact:\n" + o);
return deferred.reject(new Error(o));
}
else {
return uri;
}
})
.flatten(true)
.compact()
.value())
.then(function (uris) { return q.allSettled(_.map(uris, function (uri) { return _this.__getPactFile(_this.options, uri); }))
.then(function (data) {
var rejects = [];
data = _.map(data, function (result) {
if (result.state === "fulfilled") {
return result.value;
}
rejects.push(result.reason);
});
return rejects.length ? q.reject(new Error("Could not retrieve all Pact contracts:\n - " + rejects.join("\n - "))) : data;
}); })
.tap(function (files) { return q.allSettled(_.map(files, function (data) {
return _this.__callPact(_this.options, {
uri: _this.__constructPutUrl(_this.options, data),
method: "PUT",
headers: {
"Content-Type": "application/json",
"Accept": "application/json"
},
json: true,
body: data
});
}))
.then(function (results) {
var rejects = _.where(results, { state: "rejected" });
if (rejects.length) {
return q.reject(new Error("Could not publish pacts to broker at '" + _this.options.pactBroker + "':\n - " + rejects.join("\n - ")));
}
}); })
.tap(function (files) {
if (!_this.options.tags || !_this.options.tags.length) {
return;
}
return q.allSettled(_.chain(files)
.map(function (data) {
return _.map(_this.options.tags, function (tag) {
return _this.__callPact(_this.options, {
uri: _this.__constructTagUrl(_this.options, tag, data),
method: "PUT",
headers: {
"Content-Type": "application/json"
}
}).fail(function (err) { return q.reject("Error with tag '" + tag + "' : " + err); });
});
})
.flatten(true)
.value()).then(function (results) {
var rejects = _.where(results, { state: "rejected" });
if (rejects.length) {
return q.reject(new Error("Could not tag Pact contract:\n - " + rejects.join("\n - ")));
}
});
})
.catch(function (err) {
logger_1.default.error(err);
return q.reject(err);
logger_1.default.info(o);
return deferred.resolve(pactUrls);
});
return deferred.promise
.timeout(this.options.timeout, "Timeout waiting for verification process to complete (PID: " + instance.pid + ")");
};
Publisher.prototype.__callPact = function (options, config) {
config = _.extend({
uri: options.pactBroker ? options.pactBroker : "http://localhost",
method: "GET",
headers: {
"Accept": "application/json"
}
}, config);
if (options.pactBrokerUsername && options.pactBrokerPassword) {
config.auth = {
user: options.pactBrokerUsername,
pass: options.pactBrokerPassword
};
}
return http(config)
.then(function (data) { return data[0]; })
.then(function (response) {
if (response.statusCode < 200 || response.statusCode >= 300) {
return q.reject("Failed http call to pact broker.\n\t\t\t\t\tURI: " + config.uri + "\n\t\t\t\t\tCode: " + response.statusCode + "\n\t\t\t\t\tBody: " + response.body);
}
return response.body;
});
};
Publisher.prototype.__getPactFile = function (options, uri) {
if (/\.json$/.test(uri)) {
try {
return q(require(uri));
}
catch (err) {
return q.reject("Invalid Pact contract '" + uri + ":\n" + err);
}
}
else {
return this.__callPact(options, {
uri: uri,
json: true
}).fail(function (err) { return q.reject("Failed to get Pact contract from broker:\n" + err); });
}
};
Publisher.prototype.__constructPutUrl = function (options, data) {
if (!_.has(options, "pactBroker")) {
throw new Error("Cannot construct Pact publish URL: 'pactBroker' not specified");
}
if (!_.has(options, "consumerVersion")) {
throw new Error("Cannot construct Pact publish URL: 'consumerVersion' not specified");
}
if (!_.isObject(options)
|| !_.has(data, "consumer")
|| !_.has(data, "provider")
|| !_.has(data.consumer, "name")
|| !_.has(data.provider, "name")) {
throw new Error("Invalid Pact contract given. Unable to parse consumer and provider name");
}
return urlJoin(options.pactBroker, "pacts/provider", data.provider.name, "consumer", data.consumer.name, "version", options.consumerVersion);
};
Publisher.prototype.__constructTagUrl = function (options, tag, data) {
if (!_.has(options, "pactBroker")) {
throw new Error("Cannot construct Pact Tag URL: 'pactBroker' not specified");
}
if (!_.has(options, "consumerVersion")) {
throw new Error("Cannot construct Pact Tag URL: 'consumerVersion' not specified");
}
if (!_.isObject(options)
|| !_.has(data, "consumer")
|| !_.has(data.consumer, "name")) {
throw new Error("Invalid Pact contract given. Unable to parse consumer name");
}
return urlJoin(options.pactBroker, "pacticipants", data.consumer.name, "versions", options.consumerVersion, "tags", tag);
};
return Publisher;

@@ -205,0 +71,0 @@ }());

@@ -7,3 +7,5 @@ // tslint:disable:no-string-literal

import publisherFactory from "./publisher";
import brokerMock from "../test/integration/brokerMock";
import logger from "./logger";
import brokerMock from "../test/integration/broker-mock";
import * as http from "http";

@@ -16,10 +18,13 @@ const expect = chai.expect;

const PORT = Math.floor(Math.random() * 999) + 9000;
let server: http.Server;
before((done) => brokerMock.listen(PORT, () => {
console.log(`Broker (Mock) running on port: ${PORT}`);
done();
before(() => brokerMock(PORT).then((s) => {
logger.debug(`Pact Broker Mock listening on port: ${PORT}`);
server = s;
}));
after(() => server.close());
describe("Publisher", () => {
context("when not given pactUrls", () => {
context("when not given pactFilesOrDirs", () => {
it("should fail with an error", () => {

@@ -39,3 +44,3 @@ expect(() => {

publisherFactory({
pactUrls: ["http://localhost"],
pactFilesOrDirs: [path.dirname(process.mainModule.filename)],
consumerVersion: "1.0.0"

@@ -52,3 +57,3 @@ });

pactBroker: "http://localhost",
pactUrls: [path.dirname(process.mainModule.filename)]
pactFilesOrDirs: [path.dirname(process.mainModule.filename)]
});

@@ -64,3 +69,3 @@ }).to.throw(Error);

pactBroker: "http://localhost",
pactUrls: ["./test.json"]
pactFilesOrDirs: ["./test.json"]
});

@@ -76,3 +81,3 @@ }).to.throw(Error);

pactBroker: "http://localhost",
pactUrls: [path.dirname(process.mainModule.filename)],
pactFilesOrDirs: [path.dirname(process.mainModule.filename)],
consumerVersion: "1.0.0"

@@ -88,3 +93,3 @@ });

pactBroker: "http://localhost",
pactUrls: ["http://idontexist"],
pactFilesOrDirs: ["http://idontexist"],
consumerVersion: "1.0.0"

@@ -97,113 +102,2 @@ });

});
context("constructPutUrl", () => {
let constructPutUrl: (options: any, data: any) => string;
beforeEach(() => {
const publisher = publisherFactory({
pactBroker: "http://localhost",
pactUrls: [path.dirname(process.mainModule.filename)],
consumerVersion: "1.0.0"
});
constructPutUrl = (publisher as any)["__constructPutUrl"].bind(publisher);
});
context("when given a valid config object and pact JSON", () => {
it("should return a PUT url", () => {
let options = {
"pactBroker": "http://foo",
"consumerVersion": "1"
};
let data = {"consumer": {"name": "consumerName"}, "provider": {"name": "providerName"}};
expect(constructPutUrl(options, data)).to.eq("http://foo/pacts/provider/providerName/consumer/consumerName/version/1");
});
});
context("when given an invalid config object", () => {
it("should throw Error when pactBroker is not specified", () => {
let options = {"someotherurl": "http://foo"};
let data = {"consumer": {"name": "consumerName"}, "provider": {"name": "providerName"}};
expect(() => constructPutUrl(options, data)).to.throw(Error, "Cannot construct Pact publish URL: 'pactBroker' not specified");
});
it("should throw Error when consumerVersion is not specified", () => {
let options = {"pactBroker": "http://foo"};
let data = {"consumer": {"name": "consumerName"}, "provider": {"name": "providerName"}};
expect(() => constructPutUrl(options, data)).to.throw(Error, "Cannot construct Pact publish URL: 'consumerVersion' not specified");
});
});
context("when given an invalid Pact contract (no consumer/provider keys)", () => {
it("should return a PUT url", () => {
let options = {"pactBroker": "http://foo", "consumerVersion": "1"};
let data = {};
expect(() => {
constructPutUrl(options, data);
}).to.throw(Error, "Invalid Pact contract given. Unable to parse consumer and provider name");
});
});
context("when given an invalid Pact contract (no name keys)", () => {
it("should return a PUT url", () => {
let options = {"pactBroker": "http://foo", "consumerVersion": "1"};
let data = {"consumer": {}, "provider": {}};
expect(() => {
constructPutUrl(options, data);
}).to.throw(Error, "Invalid Pact contract given. Unable to parse consumer and provider name");
});
});
});
context("constructTagUrl", () => {
let constructTagUrl: (options: any, tag: string, data: any) => string;
beforeEach(() => {
const publisher = publisherFactory({
pactBroker: "http://localhost",
pactUrls: [path.dirname(process.mainModule.filename)],
consumerVersion: "1.0.0"
});
constructTagUrl = (publisher as any)["__constructTagUrl"].bind(publisher);
});
context("when given a valid config object and pact JSON", () => {
it("should return a PUT url", () => {
let options = {"pactBroker": "http://foo", consumerVersion: "1.0"};
let data = {"consumer": {"name": "consumerName"}};
expect(constructTagUrl(options, "test", data)).to.eq("http://foo/pacticipants/consumerName/versions/1.0/tags/test");
});
});
context("when given an invalid config object", () => {
it("should throw Error when pactBroker is not specified", () => {
let options = {"someotherurl": "http://foo", consumerVersion: "1.0"};
let data = {"consumer": {"name": "consumerName"}};
expect(() => constructTagUrl(options, "", data)).to.throw(Error, "Cannot construct Pact Tag URL: 'pactBroker' not specified");
});
it("should throw Error when consumerVersion is not specified", () => {
let options = {"pactBroker": "http://foo"};
let data = {"consumer": {"name": "consumerName"}, "provider": {"name": "providerName"}};
expect(() => constructTagUrl(options, "", data)).to.throw(Error, "Cannot construct Pact Tag URL: 'consumerVersion' not specified");
});
});
context("when given an invalid Pact contract (no consumer key)", () => {
it("should return a PUT url", () => {
let options = {"pactBroker": "http://foo", consumerVersion: "1.0"};
let data = {};
expect(() => {
constructTagUrl(options, "", data);
}).to.throw(Error, "Invalid Pact contract given. Unable to parse consumer name");
});
});
context("when given an invalid Pact contract (no name keys)", () => {
it("should return a PUT url", () => {
let options = {"pactBroker": "http://foo", consumerVersion: "1.0"};
let data = {"consumer": {}};
expect(() => {
constructTagUrl(options, "", data);
}).to.throw(Error, "Invalid Pact contract given. Unable to parse consumer name");
});
});
});
})
;
});
import checkTypes = require("check-types");
import _ = require("underscore");
import path = require("path");
import fs = require("fs");
import q = require("q");
import request = require("request");
import urlJoin = require("url-join");
import logger from "./logger";
import pactStandalone = require("@pact-foundation/pact-standalone");
import pactUtil, {DEFAULT_ARG, SpawnArguments} from "./pact-util";
const http = q.denodeify(request);
export class Publisher {

@@ -18,26 +13,13 @@

options.pactBroker = options.pactBroker || "";
options.pactUrls = options.pactUrls || [];
options.pactFilesOrDirs = options.pactFilesOrDirs || [];
options.tags = options.tags || [];
options.timeout = options.timeout || 60000;
if (options.pactUrls) {
checkTypes.assert.array.of.string(options.pactUrls);
if (options.pactFilesOrDirs) {
checkTypes.assert.array.of.string(options.pactFilesOrDirs);
}
// Stat all paths in pactUrls to make sure they exist
let url = require("url");
_.each(options.pactUrls, (uri) => {
// only check local files
let proto = url.parse(uri).protocol;
if (proto === "file://" || proto === null) {
try {
fs.statSync(path.normalize(uri));
} catch (e) {
throw new Error(`Pact contract or directory: '${uri}' doesn't exist`);
}
}
});
checkTypes.assert.nonEmptyString(options.pactBroker, "Must provide the pactBroker argument");
checkTypes.assert.nonEmptyString(options.consumerVersion, "Must provide the consumerVersion argument");
checkTypes.assert.not.emptyArray(options.pactUrls, "Must provide the pactUrls argument");
checkTypes.assert.not.emptyArray(options.pactFilesOrDirs, "Must provide the pactFilesOrDirs argument");

@@ -64,2 +46,11 @@ if (options.pactBrokerUsername) {

public readonly options: PublisherOptions;
private readonly __argMapping = {
"pactFilesOrDirs": DEFAULT_ARG,
"pactBroker": "--broker-base-url",
"pactBrokerUsername": "--broker-username",
"pactBrokerPassword": "--broker-password",
"tags": "--tag",
"consumerVersion": "--consumer-app-version",
"verbose": "--verbose"
};

@@ -70,178 +61,24 @@ constructor(options: PublisherOptions = {}) {

public publish(): q.Promise<any[]> {
public publish(): q.Promise<string[]> {
logger.info(`Publishing pacts to broker at: ${this.options.pactBroker}`);
// Return a promise that does everything one after another
return q(_.chain(this.options.pactUrls)
.map((uri) => {
// Stat all paths in pactUrls to make sure they exist
// publish template $pactHost/pacts/provider/$provider/consumer/$client/$version
let localFileOrDir = path.normalize(uri);
if (!(/^http/.test(uri)) && fs.statSync(localFileOrDir) && fs.statSync(localFileOrDir).isDirectory()) {
uri = localFileOrDir;
return _.map(fs.readdirSync(uri, ""), (file) => {
if (/\.json$/.test(file)) {
return path.join(uri, file);
}
});
} else {
return uri;
}
})
.flatten(true)
.compact()
.value())
// Get the pact contract either from local file or broker
.then((uris) => q.allSettled(
_.map(uris, (uri) => this.__getPactFile(this.options, uri)))
.then((data) => { // Make sure all files have been retrieved
let rejects = [];
data = _.map(data, (result) => {
if (result.state === "fulfilled") {
return result.value;
}
rejects.push(result.reason);
});
return rejects.length ? q.reject(new Error(`Could not retrieve all Pact contracts:\n - ${rejects.join("\n - ")}`)) : data;
}))
// Publish the contracts to broker
.tap((files) => q.allSettled(
_.map(files, (data) =>
this.__callPact(this.options, {
uri: this.__constructPutUrl(this.options, data),
method: "PUT",
headers: {
"Content-Type": "application/json",
"Accept": "application/json"
},
json: true,
body: data
})
))
.then((results) => { // Make sure publishing promises came back fulfilled, or else error out
let rejects = _.where(results, {state: "rejected"});
if (rejects.length) {
return q.reject(new Error(`Could not publish pacts to broker at '${this.options.pactBroker}':\n - ${rejects.join("\n - ")}`));
}
})
)
// If publishing works, try to tag those contracts
.tap((files) => {
if (!this.options.tags || !this.options.tags.length) {
return;
}
return q.allSettled(
_.chain(files)
.map((data) =>
_.map(this.options.tags, (tag) =>
this.__callPact(this.options, {
uri: this.__constructTagUrl(this.options, tag, data),
method: "PUT",
headers: {
"Content-Type": "application/json"
}
}).fail((err) => q.reject(`Error with tag '${tag}' : ${err}`))
)
)
.flatten(true)
.value()
).then((results) => {
let rejects = _.where(results, {state: "rejected"});
if (rejects.length) {
return q.reject(new Error(`Could not tag Pact contract:\n - ${rejects.join("\n - ")}`));
}
});
})
.catch((err) => {
logger.error(err);
return q.reject(err);
});
}
private __callPact(options: PublisherOptions, config: request.OptionsWithUri): q.Promise<any> {
config = _.extend({
uri: options.pactBroker ? options.pactBroker : "http://localhost",
method: "GET",
headers: {
"Accept": "application/json"
const deferred = q.defer<string[]>();
const instance = pactUtil.spawnBinary(`${pactStandalone.brokerPath} publish`, this.options, this.__argMapping);
const output = [];
instance.stdout.on("data", (l) => output.push(l));
instance.stderr.on("data", (l) => output.push(l));
instance.once("close", (code) => {
const o = output.join("\n");
const pactUrls = /^https?:\/\/.*\/pacts\/.*$/igm.exec(o);
if (code !== 0 || !pactUrls) {
logger.error(`Could not publish pact:\n${o}`);
return deferred.reject(new Error(o));
}
}, config);
// Authentication
if (options.pactBrokerUsername && options.pactBrokerPassword) {
(config as any).auth = {
user: options.pactBrokerUsername,
pass: options.pactBrokerPassword
};
}
logger.info(o);
return deferred.resolve(pactUrls);
});
return http(config)
// return response only
.then((data) => data[0])
.then((response) => {
if (response.statusCode < 200 || response.statusCode >= 300) {
return q.reject(`Failed http call to pact broker.
URI: ${config.uri}
Code: ${response.statusCode}
Body: ${response.body}`);
}
return response.body;
});
return deferred.promise
.timeout(this.options.timeout, `Timeout waiting for verification process to complete (PID: ${instance.pid})`);
}
private __getPactFile(options: PublisherOptions, uri: string): q.Promise<any> {
// Parse the Pact file to extract consumer/provider names
if (/\.json$/.test(uri)) {
try {
return q(require(uri));
} catch (err) {
return q.reject(`Invalid Pact contract '${uri}:\n${err}`);
}
} else {
return this.__callPact(options, {
uri: uri,
json: true
}).fail((err) => q.reject(`Failed to get Pact contract from broker:\n${err}`));
}
}
// Given Pact Options and a Pact contract, construct a Pact URL used to
// PUT/POST to the Pact Broker.
private __constructPutUrl(options: PublisherOptions, data: PublishData): string {
if (!_.has(options, "pactBroker")) {
throw new Error("Cannot construct Pact publish URL: 'pactBroker' not specified");
}
if (!_.has(options, "consumerVersion")) {
throw new Error("Cannot construct Pact publish URL: 'consumerVersion' not specified");
}
if (!_.isObject(options)
|| !_.has(data, "consumer")
|| !_.has(data, "provider")
|| !_.has(data.consumer, "name")
|| !_.has(data.provider, "name")) {
throw new Error("Invalid Pact contract given. Unable to parse consumer and provider name");
}
return urlJoin(options.pactBroker, "pacts/provider", data.provider.name, "consumer", data.consumer.name, "version", options.consumerVersion);
}
private __constructTagUrl(options: PublisherOptions, tag: string, data: PublishData): string {
if (!_.has(options, "pactBroker")) {
throw new Error("Cannot construct Pact Tag URL: 'pactBroker' not specified");
}
if (!_.has(options, "consumerVersion")) {
throw new Error("Cannot construct Pact Tag URL: 'consumerVersion' not specified");
}
if (!_.isObject(options)
|| !_.has(data, "consumer")
|| !_.has(data.consumer, "name")) {
throw new Error("Invalid Pact contract given. Unable to parse consumer name");
}
return urlJoin(options.pactBroker, "pacticipants", data.consumer.name, "versions", options.consumerVersion, "tags", tag);
}
}

@@ -251,5 +88,5 @@

export interface PublisherOptions {
export interface PublisherOptions extends SpawnArguments {
pactFilesOrDirs?: string[];
pactBroker?: string;
pactUrls?: string[];
consumerVersion?: string;

@@ -259,7 +96,4 @@ pactBrokerUsername?: string;

tags?: string[];
verbose?: boolean;
timeout?: number;
}
export interface PublishData {
consumer: { name: string };
provider: { name: string };
}

@@ -5,2 +5,3 @@ /// <reference types="node" />

import q = require("q");
import { SpawnArguments } from "./pact-util";
export declare class Server extends events.EventEmitter {

@@ -16,2 +17,3 @@ static readonly Events: {

private __instance;
private readonly __argMapping;
constructor(options: ServerOptions);

@@ -27,3 +29,3 @@ start(): q.Promise<Server>;

export default _default;
export interface ServerOptions {
export interface ServerOptions extends SpawnArguments {
port?: number;

@@ -30,0 +32,0 @@ ssl?: boolean;

@@ -14,6 +14,4 @@ "use strict";

var checkTypes = require("check-types");
var _ = require("underscore");
var path = require("path");
var fs = require("fs");
var cp = require("child_process");
var events = require("events");

@@ -26,3 +24,2 @@ var http = require("request");

var pact_util_1 = require("./pact-util");
var isWindows = process.platform === "win32";
var CHECKTIME = 500;

@@ -35,2 +32,15 @@ var RETRY_AMOUNT = 60;

var _this = _super.call(this) || this;
_this.__argMapping = {
"port": "--port",
"host": "--host",
"log": "--log",
"ssl": "--ssl",
"sslcert": "--sslcert",
"sslkey": "--sslkey",
"cors": "--cors",
"dir": "--pact_dir",
"spec": "--pact_specification_version",
"consumer": "--consumer",
"provider": "--provider"
};
_this.options = options;

@@ -129,61 +139,17 @@ _this.__running = false;

}
var envVars = JSON.parse(JSON.stringify(process.env));
delete envVars["RUBYGEMS_GEMDEPS"];
var file;
var opts = {
cwd: pact.cwd,
detached: !isWindows,
env: envVars
};
var args = pact_util_1.default.createArguments(this.options, {
"port": "--port",
"host": "--host",
"log": "--log",
"ssl": "--ssl",
"sslcert": "--sslcert",
"sslkey": "--sslkey",
"cors": "--cors",
"dir": "--pact_dir",
"spec": "--pact_specification_version",
"consumer": "--consumer",
"provider": "--provider"
});
var cmd = (_a = [pact.mockServicePath]).concat.apply(_a, ["service"].concat(args)).join(" ");
if (isWindows) {
file = "cmd.exe";
args = ["/s", "/c", cmd];
opts.windowsVerbatimArguments = true;
}
else {
cmd = "./" + cmd;
file = "/bin/sh";
args = ["-c", cmd];
}
logger_1.default.debug("Starting binary with '" + _.flatten([file, args, JSON.stringify(opts)]) + "'");
this.__instance = cp.spawn(file, args, opts);
this.__instance.stdout.setEncoding("utf8");
this.__instance.stdout.on("data", logger_1.default.debug.bind(logger_1.default));
this.__instance.stderr.setEncoding("utf8");
this.__instance.stderr.on("data", logger_1.default.debug.bind(logger_1.default));
this.__instance.on("error", logger_1.default.error.bind(logger_1.default));
var catchPort = function (data) {
var match = data.match(/port=([0-9]+)/);
if (match && match[1]) {
_this.options.port = parseInt(match[1], 10);
_this.__instance.stdout.removeListener("data", catchPort.bind(_this));
_this.__instance.stderr.removeListener("data", catchPort.bind(_this));
logger_1.default.info("Pact running on port " + _this.options.port);
}
};
this.__instance = pact_util_1.default.spawnBinary(pact.mockServicePath + " service", this.options, this.__argMapping);
this.__instance.once("close", function () { return _this.stop(); });
if (!this.options.port) {
this.__instance.stdout.on("data", catchPort.bind(this));
this.__instance.stderr.on("data", catchPort.bind(this));
var catchPort_1 = function (data) {
var match = data.match(/port=([0-9]+)/);
if (match && match[1]) {
_this.options.port = parseInt(match[1], 10);
_this.__instance.stdout.removeListener("data", catchPort_1);
_this.__instance.stderr.removeListener("data", catchPort_1);
logger_1.default.info("Pact running on port " + _this.options.port);
}
};
this.__instance.stdout.on("data", catchPort_1);
this.__instance.stderr.on("data", catchPort_1);
}
logger_1.default.info("Creating Pact with PID: " + this.__instance.pid);
this.__instance.once("close", function (code) {
if (code !== 0) {
logger_1.default.warn("Pact exited with code " + code + ".");
}
return _this.stop();
});
return this.__waitForServerUp()

@@ -196,27 +162,12 @@ .timeout(PROCESS_TIMEOUT, "Couldn't start Pact with PID: " + this.__instance.pid)

});
var _a;
};
Server.prototype.stop = function () {
var _this = this;
var pid = -1;
if (this.__instance) {
pid = this.__instance.pid;
logger_1.default.info("Removing Pact with PID: " + pid);
this.__instance.removeAllListeners();
try {
if (isWindows) {
cp.execSync("taskkill /f /t /pid " + pid);
}
else {
process.kill(-pid, "SIGINT");
}
}
catch (e) {
}
this.__instance = undefined;
}
return this.__waitForServerDown()
var pid = this.__instance ? this.__instance.pid : -1;
return q(pact_util_1.default.killBinary(this.__instance))
.then(function () { return _this.__waitForServerDown(); })
.timeout(PROCESS_TIMEOUT, "Couldn't stop Pact with PID '" + pid + "'")
.then(function () {
_this.__running = false;
_this.__instance = undefined;
_this.emit(Server.Events.STOP_EVENT, _this);

@@ -291,3 +242,3 @@ return _this;

}
http(config, function (err, res) { return (!err && res.statusCode === 200) ? deferred.resolve() : deferred.reject("HTTP Error: '" + (err ? err.message : "") + "'"); });
http(config, function (err, res) { return (!err && res.statusCode === 200) ? deferred.resolve() : deferred.reject("HTTP Error: '" + JSON.stringify(err ? err : res) + "'"); });
return deferred.promise;

@@ -294,0 +245,0 @@ };

@@ -5,2 +5,3 @@ // tslint:disable:no-string-literal

import chai = require("chai");
import chaiAsPromised = require("chai-as-promised");
import fs = require("fs");

@@ -11,2 +12,3 @@ import path = require("path");

chai.use(chaiAsPromised);
const expect = chai.expect;

@@ -23,3 +25,3 @@

server = serverFactory();
return server.start();
return expect(server.start()).to.eventually.be.fulfilled;
});

@@ -87,4 +89,4 @@ });

server = serverFactory({ssl: true});
return server.start()
.then(() => expect(server.options.ssl).to.equal(true));
expect(server.options.ssl).to.equal(true);
return expect(server.start()).to.eventually.be.fulfilled;
});

@@ -98,4 +100,4 @@

});
return server.start()
.then(() => expect(server.options.ssl).to.equal(true));
expect(server.options.ssl).to.equal(true);
return expect(server.start()).to.eventually.be.fulfilled;
});

@@ -108,4 +110,5 @@

});
return server.start()
.then(() => expect(server.options.ssl).to.equal(true));
expect(server.options.ssl).to.equal(true);
return expect(server.start()).to.eventually.be.fulfilled;
});

@@ -115,16 +118,18 @@

server = serverFactory({cors: true});
return server.start()
.then(() => expect(server.options.cors).to.equal(true));
expect(server.options.cors).to.equal(true);
return expect(server.start()).to.eventually.be.fulfilled;
});
it("should start correctly with port", () => {
server = serverFactory({port: 9500});
return server.start()
.then(() => expect(server.options.port).to.equal(9500));
const port = Math.floor(Math.random() * 999) + 9000;
server = serverFactory({port: port});
expect(server.options.port).to.equal(port);
return expect(server.start()).to.eventually.be.fulfilled;
});
it("should start correctly with host", () => {
server = serverFactory({host: "localhost"});
return server.start()
.then(() => expect(server.options.host).to.equal("localhost"));
const host = "localhost";
server = serverFactory({host: host});
expect(server.options.host).to.equal(host);
return expect(server.start()).to.eventually.be.fulfilled;
});

@@ -134,3 +139,4 @@

server = serverFactory({spec: 1});
return server.start().then(() => expect(server.options.spec).to.equal(1));
expect(server.options.spec).to.equal(1);
return expect(server.start()).to.eventually.be.fulfilled;
});

@@ -140,4 +146,4 @@

server = serverFactory({spec: 2});
return server.start()
.then(() => expect(server.options.spec).to.equal(2));
expect(server.options.spec).to.equal(2);
return expect(server.start()).to.eventually.be.fulfilled;
});

@@ -147,4 +153,4 @@

server = serverFactory({dir: dirPath});
return server.start()
.then(() => expect(server.options.dir).to.equal(dirPath));
expect(server.options.dir).to.equal(dirPath);
return expect(server.start()).to.eventually.be.fulfilled;
});

@@ -155,16 +161,18 @@

server = serverFactory({log: logPath});
return server.start()
.then(() => expect(server.options.log).to.equal(logPath));
expect(server.options.log).to.equal(logPath);
return expect(server.start()).to.eventually.be.fulfilled;
});
it("should start correctly with consumer name", () => {
server = serverFactory({consumer: "cName"});
return server.start()
.then(() => expect(server.options.consumer).to.equal("cName"));
const consumerName = "cName";
server = serverFactory({consumer: consumerName});
expect(server.options.consumer).to.equal(consumerName);
return expect(server.start()).to.eventually.be.fulfilled;
});
it("should start correctly with provider name", () => {
server = serverFactory({provider: "pName"});
return server.start()
.then(() => expect(server.options.provider).to.equal("pName"));
const providerName = "pName";
server = serverFactory({provider: providerName});
expect(server.options.provider).to.equal(providerName);
return expect(server.start()).to.eventually.be.fulfilled;
});

@@ -171,0 +179,0 @@ });

// tslint:disable:no-string-literal
import checkTypes = require("check-types");
import _ = require("underscore");
import path = require("path");
import fs = require("fs");
import cp = require("child_process");
import events = require("events");

@@ -14,6 +12,5 @@ import http = require("request");

import logger from "./logger";
import pactUtil from "./pact-util";
import {ChildProcess, SpawnOptions} from "child_process";
import pactUtil, {SpawnArguments} from "./pact-util";
import {ChildProcess} from "child_process";
const isWindows = process.platform === "win32";
const CHECKTIME = 500;

@@ -132,2 +129,15 @@ const RETRY_AMOUNT = 60;

private __instance: ChildProcess;
private readonly __argMapping = {
"port": "--port",
"host": "--host",
"log": "--log",
"ssl": "--ssl",
"sslcert": "--sslcert",
"sslkey": "--sslkey",
"cors": "--cors",
"dir": "--pact_dir",
"spec": "--pact_specification_version",
"consumer": "--consumer",
"provider": "--provider"
};

@@ -146,73 +156,21 @@ constructor(options: ServerOptions) {

}
this.__instance = pactUtil.spawnBinary(`${pact.mockServicePath} service`, this.options, this.__argMapping);
this.__instance.once("close", () => this.stop());
const envVars = JSON.parse(JSON.stringify(process.env)); // Create copy of environment variables
// Remove environment variable if there
// This is a hack to prevent some weird Travelling Ruby behaviour with Gems
// https://github.com/pact-foundation/pact-mock-service-npm/issues/16
delete envVars["RUBYGEMS_GEMDEPS"];
let file: string;
let opts: SpawnOptions = {
cwd: pact.cwd,
detached: !isWindows,
env: envVars
};
let args: string[] = pactUtil.createArguments(this.options, {
"port": "--port",
"host": "--host",
"log": "--log",
"ssl": "--ssl",
"sslcert": "--sslcert",
"sslkey": "--sslkey",
"cors": "--cors",
"dir": "--pact_dir",
"spec": "--pact_specification_version",
"consumer": "--consumer",
"provider": "--provider"
});
if (!this.options.port) {
// if port isn't specified, listen for it when pact runs
const catchPort = (data) => {
const match = data.match(/port=([0-9]+)/);
if (match && match[1]) {
this.options.port = parseInt(match[1], 10);
this.__instance.stdout.removeListener("data", catchPort);
this.__instance.stderr.removeListener("data", catchPort);
logger.info(`Pact running on port ${this.options.port}`);
}
};
let cmd: string = [pact.mockServicePath].concat("service", ...args).join(" ");
if (isWindows) {
file = "cmd.exe";
args = ["/s", "/c", cmd];
(opts as any).windowsVerbatimArguments = true;
} else {
cmd = `./${cmd}`;
file = "/bin/sh";
args = ["-c", cmd];
this.__instance.stdout.on("data", catchPort);
this.__instance.stderr.on("data", catchPort);
}
logger.debug(`Starting binary with '${_.flatten([file, args, JSON.stringify(opts)])}'`);
this.__instance = cp.spawn(file, args, opts);
this.__instance.stdout.setEncoding("utf8");
this.__instance.stdout.on("data", logger.debug.bind(logger));
this.__instance.stderr.setEncoding("utf8");
this.__instance.stderr.on("data", logger.debug.bind(logger));
this.__instance.on("error", logger.error.bind(logger));
// if port isn't specified, listen for it when pact runs
const catchPort = (data) => {
const match = data.match(/port=([0-9]+)/);
if (match && match[1]) {
this.options.port = parseInt(match[1], 10);
this.__instance.stdout.removeListener("data", catchPort.bind(this));
this.__instance.stderr.removeListener("data", catchPort.bind(this));
logger.info(`Pact running on port ${this.options.port}`);
}
};
if (!this.options.port) {
this.__instance.stdout.on("data", catchPort.bind(this));
this.__instance.stderr.on("data", catchPort.bind(this));
}
logger.info(`Creating Pact with PID: ${this.__instance.pid}`);
this.__instance.once("close", (code) => {
if (code !== 0) {
logger.warn(`Pact exited with code ${code}.`);
}
return this.stop();
});
// check service is available

@@ -230,25 +188,9 @@ return this.__waitForServerUp()

public stop(): q.Promise<Server> {
let pid = -1;
if (this.__instance) {
pid = this.__instance.pid;
logger.info(`Removing Pact with PID: ${pid}`);
this.__instance.removeAllListeners();
// Killing instance, since windows can't send signals, must kill process forcefully
try {
if (isWindows) {
cp.execSync(`taskkill /f /t /pid ${pid}`);
} else {
process.kill(-pid, "SIGINT");
}
} catch (e) {
}
this.__instance = undefined;
}
return this.__waitForServerDown()
const pid = this.__instance ? this.__instance.pid : -1;
return q(pactUtil.killBinary(this.__instance))
.then(() => this.__waitForServerDown())
.timeout(PROCESS_TIMEOUT, `Couldn't stop Pact with PID '${pid}'`)
.then(() => {
this.__running = false;
this.__instance = undefined;
this.emit(Server.Events.STOP_EVENT, this);

@@ -331,3 +273,3 @@ return this;

http(config, (err, res) => (!err && res.statusCode === 200) ? deferred.resolve() : deferred.reject(`HTTP Error: '${err ? err.message : ""}'`));
http(config, (err, res) => (!err && res.statusCode === 200) ? deferred.resolve() : deferred.reject(`HTTP Error: '${JSON.stringify(err ? err : res)}'`));

@@ -341,3 +283,3 @@ return deferred.promise;

export interface ServerOptions {
export interface ServerOptions extends SpawnArguments {
port?: number;

@@ -344,0 +286,0 @@ ssl?: boolean;

/// <reference types="q" />
import q = require("q");
import { SpawnArguments } from "./pact-util";
export declare class Verifier {
static create(options: VerifierOptions): Verifier;
readonly options: VerifierOptions;
private __instance;
private readonly __argMapping;
constructor(options: VerifierOptions);

@@ -12,3 +13,3 @@ verify(): q.Promise<string>;

export default _default;
export interface VerifierOptions {
export interface VerifierOptions extends SpawnArguments {
providerBaseUrl: string;

@@ -15,0 +16,0 @@ provider?: string;

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var pactStandalone = require("@pact-foundation/pact-standalone");
var checkTypes = require("check-types");
var cp = require("child_process");
var path = require("path");

@@ -11,2 +9,3 @@ var q = require("q");

var url = require("url");
var pactStandalone = require("@pact-foundation/pact-standalone");
var broker_1 = require("./broker");

@@ -16,5 +15,13 @@ var logger_1 = require("./logger");

var fs = require("fs");
var isWindows = process.platform === "win32";
var Verifier = (function () {
function Verifier(options) {
this.__argMapping = {
"pactUrls": pact_util_1.DEFAULT_ARG,
"providerBaseUrl": "--provider-base-url",
"providerStatesSetupUrl": "--provider-states-setup-url",
"pactBrokerUsername": "--broker-username",
"pactBrokerPassword": "--broker-password",
"publishVerificationResult": "--publish-verification-results",
"providerVersion": "--provider-app-version"
};
this.options = options;

@@ -46,6 +53,6 @@ }

if (checkTypes.emptyArray(options.pactUrls) && !options.pactBrokerUrl) {
throw new Error("Must provide the pactUrls argument if no brokerUrl provided");
throw new Error("Must provide the pactFilesOrDirs argument if no brokerUrl provided");
}
if ((!options.pactBrokerUrl || _.isEmpty(options.provider)) && checkTypes.emptyArray(options.pactUrls)) {
throw new Error("Must provide both provider and brokerUrl or if pactUrls not provided.");
throw new Error("Must provide both provider and brokerUrl or if pactFilesOrDirs not provided.");
}

@@ -84,61 +91,29 @@ if (options.providerStatesSetupUrl) {

var _this = this;
logger_1.default.info("Verifier verify()");
var retrievePactsPromise;
if (this.options.pactUrls.length > 0) {
retrievePactsPromise = q(this.options.pactUrls);
}
else {
retrievePactsPromise = new broker_1.default({
brokerUrl: this.options.pactBrokerUrl,
provider: this.options.provider,
username: this.options.pactBrokerUsername,
password: this.options.pactBrokerPassword,
tags: this.options.tags
}).findConsumers();
}
return retrievePactsPromise.then(function (data) {
logger_1.default.info("Verifying Pact Files");
return q(this.options.pactUrls)
.then(function (uris) {
if (!uris || uris.length === 0) {
return new broker_1.default({
brokerUrl: _this.options.pactBrokerUrl,
provider: _this.options.provider,
username: _this.options.pactBrokerUsername,
password: _this.options.pactBrokerPassword,
tags: _this.options.tags
}).findConsumers();
}
return uris;
})
.then(function (data) {
var deferred = q.defer();
_this.options.pactUrls = data;
var deferred = q.defer();
var output = "";
function outputHandler(log) {
logger_1.default.info(log);
output += log;
}
var envVars = JSON.parse(JSON.stringify(process.env));
delete envVars["RUBYGEMS_GEMDEPS"];
var file;
var opts = {
cwd: pactStandalone.cwd,
detached: !isWindows,
env: envVars
};
var args = pact_util_1.default.createArguments(_this.options, {
"providerBaseUrl": "--provider-base-url",
"pactUrls": "--pact-urls",
"providerStatesSetupUrl": "--provider-states-setup-url",
"pactBrokerUsername": "--broker-username",
"pactBrokerPassword": "--broker-password",
"publishVerificationResult": "--publish-verification-results",
"providerVersion": "--provider-app-version"
var instance = pact_util_1.default.spawnBinary(pactStandalone.verifierPath, _this.options, _this.__argMapping);
var output = [];
instance.stdout.on("data", function (l) { return output.push(l); });
instance.stderr.on("data", function (l) { return output.push(l); });
instance.once("close", function (code) {
var o = output.join("\n");
code === 0 ? deferred.resolve(o) : deferred.reject(new Error(o));
});
var cmd = [pactStandalone.verifierPath].concat(args).join(" ");
if (isWindows) {
file = "cmd.exe";
args = ["/s", "/c", cmd];
opts.windowsVerbatimArguments = true;
}
else {
cmd = "./" + cmd;
file = "/bin/sh";
args = ["-c", cmd];
}
_this.__instance = cp.spawn(file, args, opts);
_this.__instance.stdout.setEncoding("utf8");
_this.__instance.stdout.on("data", outputHandler);
_this.__instance.stderr.setEncoding("utf8");
_this.__instance.stderr.on("data", outputHandler);
_this.__instance.on("error", logger_1.default.error.bind(logger_1.default));
_this.__instance.once("close", function (code) { return code === 0 ? deferred.resolve(output) : deferred.reject(new Error(output)); });
logger_1.default.info("Created Pact Verifier process with PID: " + _this.__instance.pid);
return deferred.promise.timeout(_this.options.timeout, "Timeout waiting for verification process to complete (PID: " + _this.__instance.pid + ")")
return deferred.promise
.timeout(_this.options.timeout, "Timeout waiting for verification process to complete (PID: " + instance.pid + ")")
.tap(function () { return logger_1.default.info("Pact Verification succeeded."); });

@@ -145,0 +120,0 @@ });

@@ -1,6 +0,2 @@

// tslint:disable:no-string-literal
import pactStandalone = require("@pact-foundation/pact-standalone");
import checkTypes = require("check-types");
import cp = require("child_process");
import { ChildProcess, SpawnOptions } from "child_process";
import path = require("path");

@@ -11,9 +7,8 @@ import q = require("q");

import url = require("url");
import pactStandalone = require("@pact-foundation/pact-standalone");
import Broker from "./broker";
import logger from "./logger";
import pactUtil from "./pact-util";
import pactUtil, {DEFAULT_ARG, SpawnArguments} from "./pact-util";
import fs = require("fs");
const isWindows = process.platform === "win32";

@@ -39,3 +34,3 @@ export class Verifier {

// might be a problem on non root-drives
// options.pactUrls.push(uri);
// options.pactFilesOrDirs.push(uri);
return unixify(uri);

@@ -55,7 +50,7 @@ } catch (e) {

if (checkTypes.emptyArray(options.pactUrls) && !options.pactBrokerUrl) {
throw new Error("Must provide the pactUrls argument if no brokerUrl provided");
throw new Error("Must provide the pactFilesOrDirs argument if no brokerUrl provided");
}
if ((!options.pactBrokerUrl || _.isEmpty(options.provider)) && checkTypes.emptyArray(options.pactUrls)) {
throw new Error("Must provide both provider and brokerUrl or if pactUrls not provided.");
throw new Error("Must provide both provider and brokerUrl or if pactFilesOrDirs not provided.");
}

@@ -105,3 +100,11 @@

public readonly options: VerifierOptions;
private __instance: ChildProcess;
private readonly __argMapping = {
"pactUrls": DEFAULT_ARG,
"providerBaseUrl": "--provider-base-url",
"providerStatesSetupUrl": "--provider-states-setup-url",
"pactBrokerUsername": "--broker-username",
"pactBrokerPassword": "--broker-password",
"publishVerificationResult": "--publish-verification-results",
"providerVersion": "--provider-app-version"
};

@@ -113,76 +116,32 @@ constructor(options: VerifierOptions) {

public verify(): q.Promise<string> {
logger.info("Verifier verify()");
let retrievePactsPromise;
logger.info("Verifying Pact Files");
return q(this.options.pactUrls)
.then((uris) => {
if (!uris || uris.length === 0) {
return new Broker({
brokerUrl: this.options.pactBrokerUrl,
provider: this.options.provider,
username: this.options.pactBrokerUsername,
password: this.options.pactBrokerPassword,
tags: this.options.tags
}).findConsumers();
}
return uris;
})
.then((data: string[]): q.Promise<string> => {
const deferred = q.defer<string>();
this.options.pactUrls = data;
const instance = pactUtil.spawnBinary(pactStandalone.verifierPath, this.options, this.__argMapping);
const output = [];
instance.stdout.on("data", (l) => output.push(l));
instance.stderr.on("data", (l) => output.push(l));
instance.once("close", (code) => {
const o = output.join("\n");
code === 0 ? deferred.resolve(o) : deferred.reject(new Error(o));
});
if (this.options.pactUrls.length > 0) {
retrievePactsPromise = q(this.options.pactUrls);
} else {
// If no pactUrls provided, we must fetch them from the broker!
retrievePactsPromise = new Broker({
brokerUrl: this.options.pactBrokerUrl,
provider: this.options.provider,
username: this.options.pactBrokerUsername,
password: this.options.pactBrokerPassword,
tags: this.options.tags
}).findConsumers();
}
return retrievePactsPromise.then((data) => {
this.options.pactUrls = data;
const deferred = q.defer();
let output = ""; // Store output here in case of error
function outputHandler(log) {
logger.info(log);
output += log;
}
const envVars = JSON.parse(JSON.stringify(process.env)); // Create copy of environment variables
// Remove environment variable if there
// This is a hack to prevent some weird Travelling Ruby behaviour with Gems
// https://github.com/pact-foundation/pact-mock-service-npm/issues/16
delete envVars["RUBYGEMS_GEMDEPS"];
let file: string;
let opts: SpawnOptions = {
cwd: pactStandalone.cwd,
detached: !isWindows,
env: envVars
};
let args: string[] = pactUtil.createArguments(this.options, {
"providerBaseUrl": "--provider-base-url",
"pactUrls": "--pact-urls",
"providerStatesSetupUrl": "--provider-states-setup-url",
"pactBrokerUsername": "--broker-username",
"pactBrokerPassword": "--broker-password",
"publishVerificationResult": "--publish-verification-results",
"providerVersion": "--provider-app-version"
return deferred.promise
.timeout(this.options.timeout, `Timeout waiting for verification process to complete (PID: ${instance.pid})`)
.tap(() => logger.info("Pact Verification succeeded."));
});
let cmd = [pactStandalone.verifierPath].concat(args).join(" ");
if (isWindows) {
file = "cmd.exe";
args = ["/s", "/c", cmd];
(opts as any).windowsVerbatimArguments = true;
} else {
cmd = `./${cmd}`;
file = "/bin/sh";
args = ["-c", cmd];
}
this.__instance = cp.spawn(file, args, opts);
this.__instance.stdout.setEncoding("utf8");
this.__instance.stdout.on("data", outputHandler);
this.__instance.stderr.setEncoding("utf8");
this.__instance.stderr.on("data", outputHandler);
this.__instance.on("error", logger.error.bind(logger));
this.__instance.once("close", (code) => code === 0 ? deferred.resolve(output) : deferred.reject(new Error(output)));
logger.info(`Created Pact Verifier process with PID: ${this.__instance.pid}`);
return deferred.promise.timeout(this.options.timeout, `Timeout waiting for verification process to complete (PID: ${this.__instance.pid})`)
.tap(() => logger.info("Pact Verification succeeded."));
});
}

@@ -194,3 +153,3 @@ }

export interface VerifierOptions {
export interface VerifierOptions extends SpawnArguments {
providerBaseUrl: string;

@@ -197,0 +156,0 @@ provider?: string;

import path = require("path");
import chai = require("chai");
import logger from "../src/logger";
import chaiAsPromised = require("chai-as-promised");
import publisherFactory from "../src/publisher";
import broker from "./integration/brokerMock";
import brokerMock from "./integration/broker-mock";
import * as http from "http";

@@ -11,10 +13,11 @@ const expect = chai.expect;

describe("Publish Spec", () => {
let server;
let server: http.Server;
const PORT = Math.floor(Math.random() * 999) + 9000;
const pactBrokerBaseUrl = `http://localhost:${PORT}`;
const authenticatedPactBrokerBaseUrl = `http://localhost:${PORT}/auth`;
// const pactBrokerBaseUrl = `http://172.17.0.2`;
const authenticatedPactBrokerBaseUrl = `${pactBrokerBaseUrl}/auth`;
before((done) => server = broker.listen(PORT, () => {
console.log(`Pact Broker Mock listening on port: ${PORT}`);
done();
before(() => brokerMock(PORT).then((s) => {
logger.debug(`Pact Broker Mock listening on port: ${PORT}`);
server = s;
}));

@@ -30,3 +33,3 @@

pactBroker: pactBrokerBaseUrl,
pactUrls: [path.resolve(__dirname, "integration/publish/publish-success.json")],
pactFilesOrDirs: [path.resolve(__dirname, "integration/publish/publish-success.json")],
consumerVersion: "1.0.0"

@@ -43,3 +46,3 @@ });

pactBroker: pactBrokerBaseUrl,
pactUrls: [path.resolve(__dirname, "integration/publish/publish-success.json")],
pactFilesOrDirs: [path.resolve(__dirname, "integration/publish/publish-success.json")],
consumerVersion: "1.0.0",

@@ -58,3 +61,3 @@ tags: ["test", "latest"]

pactBroker: pactBrokerBaseUrl,
pactUrls: [path.resolve(__dirname, "integration/publish/publish-fail.json")],
pactFilesOrDirs: [path.resolve(__dirname, "integration/publish/publish-fail.json")],
consumerVersion: "1.0.0"

@@ -75,3 +78,3 @@ });

pactBroker: authenticatedPactBrokerBaseUrl,
pactUrls: [path.resolve(__dirname, "integration/publish/publish-success.json")],
pactFilesOrDirs: [path.resolve(__dirname, "integration/publish/publish-success.json")],
consumerVersion: "1.0.0",

@@ -92,3 +95,3 @@ pactBrokerUsername: "foo",

pactBroker: authenticatedPactBrokerBaseUrl,
pactUrls: [path.resolve(__dirname, "integration/publish/publish-success.json")],
pactFilesOrDirs: [path.resolve(__dirname, "integration/publish/publish-success.json")],
consumerVersion: "1.0.0",

@@ -113,3 +116,3 @@ pactBrokerUsername: "not",

pactBroker: pactBrokerBaseUrl,
pactUrls: [path.resolve(__dirname, "integration/publish/pactDirTests")],
pactFilesOrDirs: [path.resolve(__dirname, "integration/publish/pactDirTests")],
consumerVersion: "1.0.0"

@@ -120,4 +123,3 @@ });

expect(publisher).to.respondTo("publish");
return expect(publisher.publish()
.then((res) => expect(res.length).to.eq(2))).to.eventually.be.fulfilled;
return expect(publisher.publish()).to.eventually.be.fulfilled;
});

@@ -128,3 +130,3 @@

pactBroker: pactBrokerBaseUrl,
pactUrls: [path.resolve(__dirname, "integration/publish/pactDirTests")],
pactFilesOrDirs: [path.resolve(__dirname, "integration/publish/pactDirTests")],
consumerVersion: "1.0.0",

@@ -136,4 +138,3 @@ tags: ["test", "latest"]

expect(publisher).to.respondTo("publish");
return expect(publisher.publish()
.then((res) => expect(res.length).to.eq(2))).to.eventually.be.fulfilled;
return expect(publisher.publish()).to.eventually.be.fulfilled;
});

@@ -146,3 +147,3 @@ });

pactBroker: pactBrokerBaseUrl,
pactUrls: [path.resolve(__dirname, "integration/publish/pactDirTestsWithOtherStuff")],
pactFilesOrDirs: [path.resolve(__dirname, "integration/publish/pactDirTestsWithOtherStuff")],
consumerVersion: "1.0.0"

@@ -153,59 +154,6 @@ });

expect(publisher).to.respondTo("publish");
return expect(publisher.publish()
.then((res) => expect(res.length).to.eq(2))).to.eventually.be.fulfilled;
});
});
});
context("when publishing Pacts from an http-based URL", () => {
context("and the Pact contracts are valid", () => {
it("should asynchronously send the Pact contracts to the broker", () => {
const publisher = publisherFactory({
pactBroker: pactBrokerBaseUrl,
pactUrls: [`${pactBrokerBaseUrl}/somepact`],
consumerVersion: "1.0.0"
});
return expect(publisher.publish()).to.eventually.be.fulfilled;
});
it("should successfully tag all Pacts sent with `test` and `latest`", () => {
const publisher = publisherFactory({
pactBroker: pactBrokerBaseUrl,
pactUrls: [`${pactBrokerBaseUrl}/somepact`],
consumerVersion: "1.0.0",
tags: ["test", "latest"]
});
return expect(publisher.publish()).to.eventually.be.fulfilled;
});
});
context("and the Pact contracts do not exist (404)", () => {
it("should return a rejected promise", () => {
const publisher = publisherFactory({
pactBroker: pactBrokerBaseUrl,
pactUrls: [`${pactBrokerBaseUrl}/somepacturlthatdoesntexist`],
consumerVersion: "1.0.0"
});
return publisher.publish()
.then(() => { throw new Error("Expected an error but got none"); })
.catch((err) => expect(err.message).to.contain("Cannot GET /somepacturlthatdoesntexist"));
});
});
context("and the Pact contracts are invalid (no consumer/provider)", () => {
it("should return a rejected promise", () => {
const publisher = publisherFactory({
pactBroker: pactBrokerBaseUrl,
pactUrls: [`${pactBrokerBaseUrl}/somebrokenpact`],
consumerVersion: "1.0.0"
});
return publisher.publish()
.then(() => { throw new Error("Expected an error but got none"); })
.catch((err) => expect(err.message).to.contain("Invalid Pact contract given. Unable to parse consumer and provider name"));
});
});
});
});

@@ -5,3 +5,4 @@ import verifierFactory from "../src/verifier";

import chaiAsPromised = require("chai-as-promised");
import provider from "./integration/provider";
import providerMock from "./integration/provider-mock";
import * as http from "http";

@@ -13,11 +14,11 @@ const expect = chai.expect;

let server;
let server:http.Server;
const PORT = 9123;
const providerBaseUrl = `http://localhost:${PORT}`;
const providerStatesSetupUrl = `${providerBaseUrl}/provider-state/`;
const providerStatesSetupUrl = `${providerBaseUrl}/provider-state`;
const pactBrokerBaseUrl = `http://localhost:${PORT}`;
before((done) => server = provider.listen(PORT, () => {
before(() => providerMock(PORT).then((s) => {
console.log(`Pact Broker Mock listening on port: ${PORT}`);
done();
server = s;
}));

@@ -24,0 +25,0 @@

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Packages

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc