Skip to content

Commit

Permalink
fix(can-i-deploy): Fixing can-i-deploy to only have a single particip…
Browse files Browse the repository at this point in the history
…ant at a time, but breaking API. New major release. (#144)

* WIP, fixing can-i-deploy for latest arguments

* fixing can-i-deploy to work with 'latest' and 'to', but breaking the old API by not allowing multiple participants
  • Loading branch information
mboudreau authored Mar 4, 2019
1 parent de88d88 commit 2032ba2
Show file tree
Hide file tree
Showing 8 changed files with 150 additions and 140 deletions.
18 changes: 11 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -297,22 +297,26 @@ var opts = {
...
};

pact.canDeploy(opts).then(function () {
// do something
});
pact.canDeploy(opts)
.then(function () {
// Deployment worked
})
.catch(function() {
// Deployment failed
});
```

**Options**:

| Parameter | Required? | Type | Description |
| -------------------- | --------- | ----------- | ----------------------------------------------------------------------------------- |
| `pacticipant` | true | repeatable | Repeatable list of pacticipant names. Required. |
| `pacticipantVersion` | true | repeatable | Repeatable version of the pacticipant. Must follow after the pacticipant. Required. |
| `participant` | true | string | The participant name. Required. |
| `participantVersion` | true | string | Version of the participant. Must follow after the participant. Required. |
| `latest` | false | string | Use the latest participant version, Must follow after participant. Optional |
| `to` | false | string | Which tag are you deploying to, Must follow after participant. Optional |
| `pactBroker` | true | string | URL of the Pact Broker to publish pacts to. Required. |
| `latest` | false | repeatable | Use the latest pacticipant version, Must follow after pacticipant. Optional |
| `pactBrokerUsername` | false | string | Username for Pact Broker basic authentication. Optional |
| `pactBrokerPassword` | false | string | Password for Pact Broker basic authentication. Optional, |
| `tags` | false | array | An array of Strings to tag the Pacts being published. Optional |
| `output` | false | json,table | Specify output to show, json or table. Optional |
| `verbose` | false | flag | Set logging mode to verbose. Optional |
| `retryWhileUnknown` | false | number | The number of times to retry while there is an unknown verification result. Optional|
Expand Down
2 changes: 1 addition & 1 deletion bin/pact-cli.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ describe("Pact CLI Spec", () => {
after(() => server.close());

it("should work pointing to fake broker", () => {
const p = CLI.runSync(["can-i-deploy", "--pacticipant", "pacticipant1", "--version", "1.0.0", "--pact-broker", brokerBaseUrl])
const p = CLI.runSync(["can-i-deploy", "--participant", "participant1", "--version", "1.0.0", "--pact-broker", brokerBaseUrl])
.then((cp) => cp.stdout);
return expect(p).to.eventually.be.fulfilled;
});
Expand Down
40 changes: 14 additions & 26 deletions bin/pact-cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import pact from "../src/pact";
import * as cli from "caporal";
import * as _ from "underscore";
const pkg = require("../package.json");

cli
Expand All @@ -17,8 +16,7 @@ cli
.option("-h, --host <hostname>", "Host on which to bind the service. Default is localhost.")
.option("-l, --log <file>", "File to which to log output to.")
.option("-s, --ssl <boolean>", "Use a self-signed SSL cert to run the service over HTTPS. Default is false (HTTP).", cli.BOOL)
.option("-o, --cors <boolean>", "Support browser security in tests by responding to OPTIONS requests and adding CORS headers" +
" to mocked responses. Default is false.", cli.BOOL)
.option("-o, --cors <boolean>", "Support browser security in tests by responding to OPTIONS requests and adding CORS headers to mocked responses. Default is false.", cli.BOOL)
.option("-d, --pact-dir <directory>", "Directory to which the pacts will be written. Default is cwd.")
.option("-i, --pact-version <n>", "The Pact specification version to use when writing the Pact files. Default is 1.", cli.INT)
.option("-w, --pact-file-write-mode <m>", "Controls how pact files are written to disk. One of 'overwrite', 'update', 'merge'", /^overwrite|update|merge$/)
Expand All @@ -35,8 +33,7 @@ cli
.option("-d, --dir <directory>", "Directory to which the pacts will be written.")
.option("--consumer <consumerName>", "Specify consumer name for written Pact files.")
.option("--provider <providerName>", "Specify provider name for written Pact files.")
.option("-s, --spec <n>", "The Pact specification version to use when writing the" +
" Pact files. Default is 3.", cli.INT)
.option("-s, --spec <n>", "The Pact specification version to use when writing the Pact files. Default is 3.", cli.INT)
.action((args: any, options: any) => pact.createMessage(options));

cli
Expand All @@ -45,10 +42,8 @@ cli
.option("-h, --host <hostname>", "Host on which to bind the service. Default is localhost.")
.option("-l, --log <file>", "File to which to log output to.")
.option("-s, --ssl <boolean>", "Use a self-signed SSL cert to run the service over HTTPS. Default is false (HTTP).", cli.BOOL)
.option("-o, --cors <boolean>", `Support browser security in tests by responding to OPTIONS requests and adding CORS headers
to mocked responses. Default is false.`, cli.BOOL)
.option("-i, --pact-version <n>", "The Pact specification version to use when writing the" +
" Pact files. Default is 1.", cli.INT)
.option("-o, --cors <boolean>", "Support browser security in tests by responding to OPTIONS requests and adding CORS headers to mocked responses. Default is false.", cli.BOOL)
.option("-i, --pact-version <n>", "The Pact specification version to use when writing the Pact files. Default is 1.", cli.INT)
.option("-u, --pact-urls <URLs>", "Comma separated list of local Pact files", cli.LIST)
.action((args: any, options: any) => pact.createStub(options).start());

Expand All @@ -64,42 +59,35 @@ cli
.option("-v, --provider-version <version>", "Provider version, required to publish verification result to Broker.")
.option("-t, --timeout <milliseconds>", "The duration in ms we should wait to confirm verification process was successful. Defaults to 30000.", cli.INT)
.option("-pub, --publish-verification-result", "Publish verification result to Broker.")
.option("-c, --custom-provider-header <headers>", "Header to add to provider state set up and pact verification requests." +
" eg 'Authorization: Basic cGFjdDpwYWN0'.", cli.LIST)
.option("-c, --custom-provider-header <headers>", "Header to add to provider state set up and pact verification requests. eg 'Authorization: Basic cGFjdDpwYWN0'.", cli.LIST)
.option("--monkeypatch <file>", "Absolute path to a Ruby file that will monkeypatch the underlying Pact mock.")
.action((args: any, options: any) => {
return pact.verifyPacts(options);
});
.action((args: any, options: any) => pact.verifyPacts(options));

cli
.command("publish", "Publishes Pact Contracts to the broker")
.option("-p, --pact-files-or-dirs <paths>", "Comma separated list of Pact file or directory paths", cli.LIST, undefined, true)
.option("-c, --consumer-version <version>", "Version of the consumer, can be any string, but recommended to use a semantic" +
" version or git hash.", undefined, undefined, true)
.option("-c, --consumer-version <version>", "Version of the consumer, can be any string, but recommended to use a semantic version or git hash.", undefined, undefined, true)
.option("-b, --pact-broker <URL>", "URL of the Pact Broker to publish pacts to.", undefined, undefined, true)
.option("-username, --pact-broker-username <user>", "Pact Broker username.")
.option("-password, --pact-broker-password <password>", "Pact Broker password.")
.option("-t, --tags <tags>", "Comma separated list of tags to attach to the Pact Contracts being published", cli.LIST)
.action((args: any, options: any) => pact.publishPacts(options));

cli
.command("can-i-deploy", "Check if pacticipant are safe to deploy together")
.option("-p, --pacticipant <pacticipant>", "Repeatable list of pacticipant names", cli.REPEATABLE, undefined, true)
.option("-v, --pacticipant-version <version>", "Repeatable version of the pacticipant. Must follow after the pacticipant", cli.REPEATABLE, undefined, true)
.option("-l, --latest", "Use the latest pacticipant version, Must follow after pacticipant", cli.BOOL, undefined)
.option("-t, --to <tag>", "Pacticipant tags to check against", cli.LIST)
.command("can-i-deploy", "Check if participant are safe to deploy together")
.option("-p, --participant, --participant <participant>", "The participant name", undefined, undefined, true)
.option("-v, --participant-version, --participant-version <version>", "The version of the participant. Must follow after the participant", undefined, undefined, true)
.option("-l, --latest [TAG]", "Use the latest version of the participant, or the latest based on the tag. Must follow after participant.", undefined, undefined)
.option("-t, --to <tag>", "participant tags to check against", undefined, undefined)
.option("-b, --pact-broker <URL>", "URL of the Pact Broker to publish pacts to.", undefined, undefined, true)
.option("-username, --pact-broker-username <user>", "Pact Broker username.")
.option("-password, --pact-broker-password <password>", "Pact Broker password.")
.option("-o, --output <output>", "json or table.")
.option("--verbose", "Verbose output.")
.option("--retry-while-unknown <times>",
"The number of times to retry while there is an unknown verification result (ie. the provider verification is likely still running).")
.option("--retry-while-unknown <times>", "The number of times to retry while there is an unknown verification result (ie. the provider verification is likely still running).")
.option("--retry-interval <seconds>", "The time between retries in seconds. Use in conjuction with --retry-while-unknown.")
.action((args: any, options: any) => {
options.pacticipant = _.toArray(options.pacticipant);
options.pacticipantVersion = _.toArray(options.pacticipantVersion);
pact.canDeploy(options);
return pact.canDeploy(options);
});

cli.parse(process.argv);
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@pact-foundation/pact-node",
"version": "6.21.5",
"version": "7.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",
Expand Down Expand Up @@ -84,7 +84,6 @@
"nodemon": "1.17.1",
"rewire": "3.0.2",
"sinon": "4.4.2",
"standard-version": "4.3.0",
"ts-node": "5.0.0",
"tslint": "5.9.1",
"typescript": "2.7.2"
Expand Down
145 changes: 84 additions & 61 deletions src/can-deploy.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ import path = require("path");
import fs = require("fs");
import chai = require("chai");
import chaiAsPromised = require("chai-as-promised");
import canDeployFactory, {CanDeployOptions, CanDeploy} from "./can-deploy";
import {SpawnArguments} from "./pact-util";
import canDeployFactory, {CanDeploy, CanDeployOptions} from "./can-deploy";
import logger from "./logger";
import brokerMock from "../test/integration/broker-mock";
import * as http from "http";
Expand Down Expand Up @@ -41,96 +40,120 @@ describe("CanDeploy Spec", () => {
}
});

describe("convertForSpawnBinary helper function",() => {
let result : SpawnArguments[];
describe("convertForSpawnBinary helper function", () => {

beforeEach(() => {
result = CanDeploy.convertForSpawnBinary(
it("produces an array of SpawnArguments", () => {
const value = {pactBroker: "some broker"};
const result = CanDeploy.convertForSpawnBinary(value);
expect(result).to.be.an("array");
expect(result.length).to.be.equal(1);
expect(result[0]).to.be.deep.equal(value);
});

it("has version and participant in the right order", () => {
const result = CanDeploy.convertForSpawnBinary({
participantVersion: "v1",
participant: "one",
pactBroker: "some broker",
pactBrokerUsername: "username",
pactBrokerPassword: "password",
});

expect(result).to.eql([
{participant: "one"},
{participantVersion: "v1"},
{
pacticipant: ["one","two"],
pacticipantVersion: ["v1","v2"],
pactBroker: "some broker",
pactBrokerUsername: "username",
pactBrokerPassword: "password",
});
pactBrokerPassword: "password"
}
]);
});

it("produces an array of SpawnArguments", ()=> {
expect(result).to.be.an("array");
});
it("has latest tag and participant in the right order", () => {
const result = CanDeploy.convertForSpawnBinary({
latest: "v2",
participant: "two",
pactBroker: "some broker",
});

it("has version and pacticipant in the right order", ()=> {
expect(result).to.eql(
[
{pacticipant: "one"},
{pacticipantVersion: "v1"},
{pacticipant: "two"},
{pacticipantVersion:"v2"},
{
pactBroker: "some broker",
pactBrokerUsername: "username",
pactBrokerPassword: "password",
}
]);
expect(result).to.eql([
{participant: "two"},
{latest: "v2"},
{
pactBroker: "some broker"
}
]);
});
});

context("when invalid options are set", () => {
it("should fail with an Error when not given pactBroker", () => {
expect(() => {
canDeployFactory({
} as CanDeployOptions);
}).to.throw(Error);
expect(() => canDeployFactory({} as CanDeployOptions)).to.throw(Error);
});

it("should fail with an Error when not given pacticipant", () => {
expect(() => {
canDeployFactory({
pactBroker: "http://localhost",
pacticipantVersion: ["v1"],
} as CanDeployOptions);
}).to.throw(Error);
it("should fail with an Error when not given participant", () => {
expect(() => canDeployFactory({
pactBroker: "http://localhost",
participantVersion: "v1",
} as CanDeployOptions)).to.throw(Error);
});

it("should fail with an Error when not given version", () => {
expect(() => {
canDeployFactory({
pactBroker: "http://localhost",
pacticipant: ["p1","p2"]
} as CanDeployOptions);
}).to.throw(Error);
expect(() => canDeployFactory({
pactBroker: "http://localhost",
participant: "p1"
} as CanDeployOptions)).to.throw(Error);
});

it("should fail with an error when not given equal numbers of version and pacticipant", () => {
expect(() => {
canDeployFactory({
pactBroker: "http://localhost",
pacticipantVersion: ["v1"],
pacticipant: ["p1","p2"]
});
}).to.throw(Error);
it("should fail with an error when version and paticipants are empty", () => {
expect(() => canDeployFactory({
pactBroker: "http://localhost",
participantVersion: undefined,
participant: undefined
})).to.throw(Error);
});

it("should fail with an error when version and paticipants are empty", () => {
expect(() => {
canDeployFactory({
pactBroker: "http://localhost",
pacticipantVersion: [],
pacticipant: []
});
}).to.throw(Error);
it("should fail with an error when 'latest' is an empty string", () => {
expect(() => canDeployFactory({
pactBroker: "http://localhost",
participantVersion: "v1",
participant: "p1",
latest: ""
})).to.throw(Error);
});

it("should fail with an error when 'to' is an empty string", () => {
expect(() => canDeployFactory({
pactBroker: "http://localhost",
participantVersion: "v1",
participant: "p1",
to: ""
})).to.throw(Error);
});
});

context("when valid options are set", () => {
it("should return a CanDeploy object when given the correct arguments", () => {
const c = canDeployFactory({
pactBroker: "http://localhost",
pacticipantVersion: ["v1","v2"],
pacticipant: ["p1","p2"]
participantVersion: "v1",
participant: "p1"
});
expect(c).to.be.ok;
expect(c.canDeploy).to.be.a("function");
});

it("should work when using 'latest' with either a boolean or a string", () => {
const opts: CanDeployOptions = {
pactBroker: "http://localhost",
participantVersion: "v1",
participant: "p1"
};
opts.latest = true;
expect(canDeployFactory(opts)).to.be.ok;
opts.latest = "tag";
expect(canDeployFactory(opts)).to.be.ok;
});
});
});
Loading

0 comments on commit 2032ba2

Please sign in to comment.