Skip to content

Commit

Permalink
feat(can-i-deploy): allow multiple pacticipants to be specified to Ca…
Browse files Browse the repository at this point in the history
…nDeploy

BREAKING CHANGE: Options for CanDeploy have changed. Now, pacticipants are specified by an array of { name: <string>, latest?: <string | boolean>, version?: <string> }, allowing more than one pacticipant to be specified. You must specify one of latest
or version. If latest is `true`, the latest pact is used. If it is string, then the latest pact with that tag is used.
  • Loading branch information
TimothyJones committed Oct 15, 2019
1 parent 8e904a4 commit b4b3921
Show file tree
Hide file tree
Showing 4 changed files with 164 additions and 130 deletions.
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -314,18 +314,18 @@ pact.canDeploy(opts)

| Parameter | Required? | Type | Description |
| -------------------- | --------- | ----------- | ----------------------------------------------------------------------------------- |
| `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. |
| `pacticipants` | true | string | An array of { name: String, latest? string | boolean, version? string } objects. To |
| | | | specify a tag, use the tagname with latest. Specify one of these per pacticipant |
| | | | that you want to deploy |
| `pactBroker` | true | string | URL of the Pact Broker to query about deployment. Required. |
| `pactBrokerUsername` | false | string | Username for Pact Broker basic authentication. Optional |
| `pactBrokerPassword` | false | string | Password for Pact Broker basic authentication. Optional |
| `pactBrokerToken` | false | string | Bearer token for Pact Broker authentication. Optional |
| `pactBrokerToken` | false | string | Bearer token for Pact Broker authentication. 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|
| `retryInterval` | false | number | The time between retries in seconds, use with retryWhileUnknown. Optional |
| `to` | false | string | The tag that you want to deploy to (eg, 'prod') |

### Stub Servers

Expand Down
183 changes: 108 additions & 75 deletions src/can-deploy.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,46 +43,59 @@ describe('CanDeploy Spec', () => {

describe('convertForSpawnBinary helper function', () => {
it('produces an array of SpawnArguments', () => {
const value = { pactBroker: 'some broker' };
const value = { pactBroker: 'some broker', pacticipants: [] };
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);
expect(result).to.be.deep.equal([{ pactBroker: 'some broker' }]);
});

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

expect(result).to.eql([
{ participant: 'one' },
{ participantVersion: 'v1' },
{
pactBroker: 'some broker',
pactBrokerUsername: 'username',
pactBrokerPassword: 'password',
},
{ name: 'one' },
{ version: 'v2' },
]);
});

it('has latest tag and participant in the right order', () => {
const result = CanDeploy.convertForSpawnBinary({
latest: 'v2',
participant: 'two',
pacticipants: [{ name: 'two', latest: 'SOME_TAG' }],
pactBroker: 'some broker',
});

expect(result).to.eql([
{ participant: 'two' },
{ latest: 'v2' },
{
pactBroker: 'some broker',
},
{ name: 'two' },
{ latest: 'SOME_TAG' },
]);
});

it("understands 'true' for latest", () => {
const result = CanDeploy.convertForSpawnBinary({
pacticipants: [{ name: 'two', latest: true }],
pactBroker: 'some broker',
});

expect(result).to.eql([
{
pactBroker: 'some broker',
},
{ name: 'two' },
{ latest: 'PACT_NODE_NO_VALUE' },
]);
});
});
Expand All @@ -92,52 +105,11 @@ describe('CanDeploy Spec', () => {
expect(() => canDeployFactory({} as CanDeployOptions)).to.throw(Error);
});

it('should fail with an Error when not given participant', () => {
it('should fail with an error when there are no paticipants', () => {
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',
participant: 'p1',
} as CanDeployOptions),
).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 '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: '',
pacticipants: [],
}),
).to.throw(Error);
});
Expand All @@ -147,42 +119,105 @@ describe('CanDeploy Spec', () => {
it('should return a CanDeploy object when given the correct arguments', () => {
const c = canDeployFactory({
pactBroker: 'http://localhost',
participantVersion: 'v1',
participant: 'p1',
pacticipants: [{ name: 'two', version: '2' }],
});
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;
});
});

context('candeploy function', () => {
it('should return success with a table result deployable true', done => {
const opts: CanDeployOptions = {
pactBroker: `http://localhost:${PORT}`,
participantVersion: '4',
participant: 'Foo',
pacticipants: [{ name: 'Foo', version: '4' }],
};
const ding = canDeployFactory(opts);

ding.canDeploy().then(done);
});

context('with latest true', () => {
it('should return success with a table result deployable true', done => {
const opts: CanDeployOptions = {
pactBroker: `http://localhost:${PORT}`,
pacticipants: [{ name: 'Foo', latest: true }],
};
const ding = canDeployFactory(opts);

ding.canDeploy().then(done);
});

it('should throw an error with a table result deployable false', () => {
const opts: CanDeployOptions = {
pactBroker: `http://localhost:${PORT}`,
pacticipants: [{ name: 'FooFail', latest: true }],
};
const ding = canDeployFactory(opts);

return ding
.canDeploy()
.then(() => expect.fail())
.catch(message => expect(message).not.be.null);
});
});

context('with latest a string', () => {
it('should return success with a table result deployable true', done => {
const opts: CanDeployOptions = {
pactBroker: `http://localhost:${PORT}`,
pacticipants: [{ name: 'Foo', latest: 'tag' }],
};
const ding = canDeployFactory(opts);

ding.canDeploy().then(done);
});

it('should throw an error with a table result deployable false', () => {
const opts: CanDeployOptions = {
pactBroker: `http://localhost:${PORT}`,
pacticipants: [{ name: 'FooFail', latest: 'tag' }],
};
const ding = canDeployFactory(opts);

return ding
.canDeploy()
.then(() => expect.fail())
.catch(message => expect(message).not.be.null);
});
});

context('with latest a string, and a to', () => {
it('should return success with a table result deployable true', done => {
const opts: CanDeployOptions = {
pactBroker: `http://localhost:${PORT}`,
pacticipants: [{ name: 'Foo', latest: 'tag' }],
to: 'prod',
};
const ding = canDeployFactory(opts);

ding.canDeploy().then(done);
});

it('should throw an error with a table result deployable false', () => {
const opts: CanDeployOptions = {
pactBroker: `http://localhost:${PORT}`,
pacticipants: [{ name: 'FooFail', latest: 'tag' }],
to: 'prod',
};
const ding = canDeployFactory(opts);

return ding
.canDeploy()
.then(() => expect.fail())
.catch(message => expect(message).not.be.null);
});
});

it('should throw an error with a table result deployable false', () => {
const opts: CanDeployOptions = {
pactBroker: `http://localhost:${PORT}`,
participantVersion: '4',
participant: 'FooFail',
pacticipants: [{ name: 'FooFail', version: '4' }],
};
const ding = canDeployFactory(opts);

Expand All @@ -195,8 +230,7 @@ describe('CanDeploy Spec', () => {
it('should return success with a json result deployable true', done => {
const opts: CanDeployOptions = {
pactBroker: `http://localhost:${PORT}`,
participantVersion: '4',
participant: 'Foo',
pacticipants: [{ name: 'Foo', version: '4' }],
output: 'json',
};
const ding = canDeployFactory(opts);
Expand All @@ -207,8 +241,7 @@ describe('CanDeploy Spec', () => {
it('should throw an error with a json result deployable false', () => {
const opts: CanDeployOptions = {
pactBroker: `http://localhost:${PORT}`,
participantVersion: '4',
participant: 'FooFail',
pacticipants: [{ name: 'FooFail', version: '4' }],
output: 'json',
};
const ding = canDeployFactory(opts);
Expand Down
61 changes: 28 additions & 33 deletions src/can-deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import q = require('q');
import logger from './logger';
import spawn from './spawn';
import pactStandalone from './pact-standalone';
import { PACT_NODE_NO_VALUE } from './spawn';
import * as _ from 'underscore';

// eslint-disable-next-line @typescript-eslint/no-var-requires
Expand All @@ -11,29 +12,24 @@ export class CanDeploy {
public static convertForSpawnBinary(
options: CanDeployOptions,
): CanDeployOptions[] {
// This is the order that the arguments must be in, everything else is afterwards
const keys = ['participant', 'participantVersion', 'latest', 'to'];
// Create copy of options, while omitting the arguments specified above
const args: CanDeployOptions[] = [_.omit(options, keys)];

// Go backwards in the keys as we are going to unshift them into the array
keys.reverse().forEach(key => {
const val = options[key];
if (options[key] !== undefined) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const obj: any = {};
obj[key] = val;
args.unshift(obj);
}
});

return args;
return _.flatten(
[_.omit(options, 'pacticipants')].concat(
options.pacticipants.map(({ name, latest, version }) => [
{ name },
version
? { version }
: {
latest: latest === true ? PACT_NODE_NO_VALUE : latest,
},
]),
),
);
}

public readonly options: CanDeployOptions;
private readonly __argMapping = {
participant: '--pacticipant',
participantVersion: '--version',
name: '--pacticipant',
version: '--version',
latest: '--latest',
to: '--to',
pactBroker: '--broker-base-url',
Expand All @@ -51,21 +47,16 @@ export class CanDeploy {
// Setting defaults
options.timeout = options.timeout || 60000;

checkTypes.assert.nonEmptyString(
options.participant,
'Must provide the participant argument',
);
checkTypes.assert.nonEmptyString(
options.participantVersion,
'Must provide the participant version argument',
checkTypes.assert.nonEmptyArray(
options.pacticipants,
'Must provide at least one pacticipant',
);

checkTypes.assert.nonEmptyString(
options.pactBroker,
'Must provide the pactBroker argument',
);
options.latest !== undefined &&
checkTypes.assert.nonEmptyString(options.latest.toString());
options.to !== undefined && checkTypes.assert.nonEmptyString(options.to);

options.pactBrokerToken !== undefined &&
checkTypes.assert.nonEmptyString(options.pactBrokerToken);
options.pactBrokerUsername !== undefined &&
Expand Down Expand Up @@ -127,17 +118,21 @@ export class CanDeploy {

export default (options: CanDeployOptions): CanDeploy => new CanDeploy(options);

export interface CanDeployPacticipant {
name: string;
version?: string;
latest?: string | boolean;
}

export interface CanDeployOptions {
participant?: string;
participantVersion?: string;
to?: string;
latest?: boolean | string;
pacticipants: CanDeployPacticipant[];
pactBroker: string;
pactBrokerToken?: string;
pactBrokerUsername?: string;
pactBrokerPassword?: string;
output?: 'json' | 'table';
verbose?: boolean;
to?: string;
retryWhileUnknown?: number;
retryInterval?: number;
timeout?: number;
Expand Down
Loading

0 comments on commit b4b3921

Please sign in to comment.