From e9f569b5a72279b522a21ad18809279acfe54be1 Mon Sep 17 00:00:00 2001 From: Timothy Jones Date: Wed, 15 Mar 2023 16:25:48 +1100 Subject: [PATCH] feat!: Remove all classes that were backed by the Ruby implementation (Publisher; Message; Server; AbstractService; Stub; CanDeploy; CannotDeployError). Also remove the Pact() methods that called them (createServer; listServer; removeAllServers; createStub; listStub; createMessage; publishPacts). If you need these features, please use @pact-foundation/pact-cli --- package.json | 2 +- src/can-deploy/CannotDeployError.ts | 11 - src/can-deploy/can-deploy.spec.ts | 270 -------------------- src/can-deploy/can-deploy.ts | 169 ------------- src/can-deploy/index.ts | 6 - src/can-deploy/types.ts | 29 --- src/index.spec.ts | 2 - src/index.ts | 9 - src/message.spec.ts | 142 ----------- src/message.ts | 110 --------- src/pact-standalone.spec.ts | 234 ------------------ src/pact-standalone.ts | 70 ------ src/pact.spec.ts | 228 ----------------- src/pact.ts | 144 ----------- src/publisher.spec.ts | 134 ---------- src/publisher.ts | 151 ------------ src/server.spec.ts | 370 ---------------------------- src/server.ts | 111 --------- src/service.ts | 347 -------------------------- src/spawn/arguments.spec.ts | 168 ------------- src/spawn/arguments.ts | 77 ------ src/spawn/index.ts | 5 - src/spawn/spawn.ts | 87 ------- src/stub.spec.ts | 230 ----------------- src/stub.ts | 50 ---- src/types.ts | 37 --- test/publisher.integration.spec.ts | 179 -------------- 27 files changed, 1 insertion(+), 3371 deletions(-) delete mode 100644 src/can-deploy/CannotDeployError.ts delete mode 100644 src/can-deploy/can-deploy.spec.ts delete mode 100644 src/can-deploy/can-deploy.ts delete mode 100644 src/can-deploy/index.ts delete mode 100644 src/can-deploy/types.ts delete mode 100644 src/message.spec.ts delete mode 100644 src/message.ts delete mode 100644 src/pact-standalone.spec.ts delete mode 100644 src/pact-standalone.ts delete mode 100644 src/publisher.spec.ts delete mode 100644 src/publisher.ts delete mode 100644 src/server.spec.ts delete mode 100644 src/server.ts delete mode 100644 src/service.ts delete mode 100644 src/spawn/arguments.spec.ts delete mode 100644 src/spawn/arguments.ts delete mode 100644 src/spawn/index.ts delete mode 100644 src/spawn/spawn.ts delete mode 100644 src/stub.spec.ts delete mode 100644 src/stub.ts delete mode 100644 test/publisher.integration.spec.ts diff --git a/package.json b/package.json index b8e62007..93a6b95f 100644 --- a/package.json +++ b/package.json @@ -120,7 +120,7 @@ "build": "tsc --project tsconfig.build.json", "prerelease": "npm run snyk-protect", "release": "standard-version", - "test": "cross-env LOGLEVEL=debug PACT_DO_NOT_TRACK=true mocha \"{src,bin}/**/*.spec.ts\"", + "test": "cross-env LOGLEVEL=debug PACT_DO_NOT_TRACK=true mocha \"{src,test}/**/*.spec.ts\"", "snyk-protect": "snyk protect", "format:base": "prettier --parser typescript", "format:check": "npm run format:base -- --list-different \"{src,test}/**/*.{ts,tsx}\"", diff --git a/src/can-deploy/CannotDeployError.ts b/src/can-deploy/CannotDeployError.ts deleted file mode 100644 index 6d8ff64b..00000000 --- a/src/can-deploy/CannotDeployError.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { CanDeployResponse } from './types'; - -export class CannotDeployError extends Error { - output: CanDeployResponse | string; - - constructor(output: CanDeployResponse | string) { - super('can-i-deploy result: it is not safe to deploy'); - this.name = 'CannotDeployError'; - this.output = output; - } -} diff --git a/src/can-deploy/can-deploy.spec.ts b/src/can-deploy/can-deploy.spec.ts deleted file mode 100644 index 3659c961..00000000 --- a/src/can-deploy/can-deploy.spec.ts +++ /dev/null @@ -1,270 +0,0 @@ -import * as path from 'path'; -import * as fs from 'fs'; -import * as chai from 'chai'; -import chaiAsPromised from 'chai-as-promised'; -import * as http from 'http'; -import * as rimraf from 'rimraf'; -import * as mkdirp from 'mkdirp'; - -import canDeployFactory, { CanDeploy } from './can-deploy'; -import { CanDeployOptions } from './types'; -import logger from '../logger'; -import brokerMock from '../../test/integration/broker-mock'; - -const { expect } = chai; -chai.use(chaiAsPromised); - -describe('CanDeploy Spec', () => { - const PORT = Math.floor(Math.random() * 999) + 9000; - let server: http.Server; - let absolutePath: string; - let relativePath: string; - - before(() => - brokerMock(PORT).then((s) => { - logger.debug(`Pact Broker Mock listening on port: ${PORT}`); - server = s; - }) - ); - - after(() => server.close()); - - beforeEach(() => { - relativePath = `.tmp/${Math.floor(Math.random() * 1000)}`; - absolutePath = path.resolve(__dirname, '..', relativePath); - mkdirp.sync(absolutePath); - }); - - afterEach(() => { - if (fs.existsSync(absolutePath)) { - rimraf.sync(absolutePath); - } - }); - - describe('convertForSpawnBinary helper function', () => { - it('produces an array of SpawnArguments', () => { - 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).to.be.deep.equal([{ pactBroker: 'some broker' }]); - }); - - it('has version and participant in the right order', () => { - const result = CanDeploy.convertForSpawnBinary({ - pacticipants: [{ version: 'v2', name: 'one' }], - pactBroker: 'some broker', - pactBrokerUsername: 'username', - pactBrokerPassword: 'password', - }); - - expect(result).to.eql([ - { - 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({ - pacticipants: [{ name: 'two', latest: 'SOME_TAG' }], - pactBroker: 'some broker', - }); - - expect(result).to.eql([ - { - 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' }, - ]); - }); - }); - - context('when invalid options are set', () => { - it('should fail with an Error when not given pactBroker', () => { - expect(() => canDeployFactory({} as CanDeployOptions)).to.throw(Error); - }); - - it('should fail with an error when there are no paticipants', () => { - expect(() => - canDeployFactory({ - pactBroker: 'http://localhost', - pacticipants: [], - }) - ).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', - pacticipants: [{ name: 'two', version: '2' }], - }); - expect(c).to.be.ok; - expect(c.canDeploy).to.be.a('function'); - }); - }); - - context('candeploy function', () => { - it('should return success with a table result deployable true', (done) => { - const opts: CanDeployOptions = { - pactBroker: `http://localhost:${PORT}`, - pacticipants: [{ name: 'Foo', version: '4' }], - }; - const ding = canDeployFactory(opts); - - ding.canDeploy().then((results) => { - expect(results).not.to.be.null; - 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((results) => { - expect(results).not.to.be.null; - 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((results) => { - expect(results).not.to.be.null; - 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((results) => { - expect(results).not.to.be.null; - 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}`, - pacticipants: [{ name: 'FooFail', version: '4' }], - }; - const ding = canDeployFactory(opts); - - return ding - .canDeploy() - .then(() => expect.fail()) - .catch((message) => expect(message).not.be.null); - }); - - it('should return success with a json result deployable true', (done) => { - const opts: CanDeployOptions = { - pactBroker: `http://localhost:${PORT}`, - pacticipants: [{ name: 'Foo', version: '4' }], - output: 'json', - }; - const ding = canDeployFactory(opts); - - ding.canDeploy().then((results) => { - expect(results).not.to.be.null; - done(); - }); - }); - - it('should throw an error with a json result deployable false', () => { - const opts: CanDeployOptions = { - pactBroker: `http://localhost:${PORT}`, - pacticipants: [{ name: 'FooFail', version: '4' }], - output: 'json', - }; - const ding = canDeployFactory(opts); - - return ding - .canDeploy() - .then(() => expect.fail()) - .catch((message) => expect(message).not.be.null); - }); - }); -}); diff --git a/src/can-deploy/can-deploy.ts b/src/can-deploy/can-deploy.ts deleted file mode 100644 index 32643020..00000000 --- a/src/can-deploy/can-deploy.ts +++ /dev/null @@ -1,169 +0,0 @@ -import { timeout, TimeoutError } from 'promise-timeout'; -import * as _ from 'underscore'; -import checkTypes = require('check-types'); - -import { CanDeployOptions, CanDeployResponse } from './types'; -import logger, { verboseIsImplied } from '../logger'; -import pactStandalone from '../pact-standalone'; -import spawn, { PACT_NODE_NO_VALUE } from '../spawn'; -import { CannotDeployError } from './CannotDeployError'; - -export class CanDeploy { - public static convertForSpawnBinary( - options: CanDeployOptions - ): CanDeployOptions[] { - 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 = { - name: '--pacticipant', - version: '--version', - latest: '--latest', - to: '--to', - pactBroker: '--broker-base-url', - pactBrokerToken: '--broker-token', - pactBrokerUsername: '--broker-username', - pactBrokerPassword: '--broker-password', - output: '--output', - verbose: '--verbose', - retryWhileUnknown: '--retry-while-unknown', - retryInterval: '--retry-interval', - }; - - constructor(passedOptions: CanDeployOptions) { - const options = { ...passedOptions }; - // Setting defaults - options.timeout = options.timeout || 60000; - if (!options.output) { - options.output = 'json'; - } - - checkTypes.assert.nonEmptyArray( - options.pacticipants, - 'Must provide at least one pacticipant' - ); - - checkTypes.assert.nonEmptyString( - options.pactBroker, - 'Must provide the pactBroker argument' - ); - - if (options.pactBrokerToken !== undefined) { - checkTypes.assert.nonEmptyString(options.pactBrokerToken); - } - if (options.pactBrokerUsername !== undefined) { - checkTypes.assert.string(options.pactBrokerUsername); - } - if (options.pactBrokerPassword !== undefined) { - checkTypes.assert.string(options.pactBrokerPassword); - } - if (options.verbose === undefined && verboseIsImplied()) { - options.verbose = true; - } - - if ( - (options.pactBrokerUsername && !options.pactBrokerPassword) || - (options.pactBrokerPassword && !options.pactBrokerUsername) - ) { - throw new Error( - 'Must provide both Pact Broker username and password. None needed if authentication on Broker is disabled.' - ); - } - - this.options = options; - } - - public canDeploy(): Promise { - logger.info( - `Asking broker at ${this.options.pactBroker} if it is possible to deploy` - ); - const canDeployPromise = new Promise( - (resolve, reject) => { - const instance = spawn.spawnBinary( - pactStandalone.brokerFullPath, - [ - { cliVerb: 'can-i-deploy' }, - ...CanDeploy.convertForSpawnBinary(this.options), - ], - this.__argMapping - ); - const output: Array = []; - if (instance.stdout && instance.stderr) { - instance.stdout.on('data', (l) => output.push(l)); - instance.stderr.on('data', (l) => output.push(l)); - } - instance.once('close', (code) => { - const result: string = output.join('\n'); - - if (this.options.output === 'json') { - try { - const startIndex = output.findIndex((l: string | Buffer) => - l.toString().startsWith('{') - ); - if (startIndex === -1) { - logger.error( - `can-i-deploy produced no json output:\n${result}` - ); - return reject(new Error(result)); - } - if (startIndex !== 0) { - logger.warn( - `can-i-deploy produced additional output: \n${output.slice( - 0, - startIndex - )}` - ); - } - const jsonPart = output.slice(startIndex).join('\n'); - - const parsed = JSON.parse(jsonPart) as CanDeployResponse; - if (code === 0 && parsed.summary.deployable) { - return resolve(parsed); - } - return reject(new CannotDeployError(parsed)); - } catch (e) { - logger.error(`can-i-deploy produced non-json output:\n${result}`); - return reject(new Error(result)); - } - } - - if (code === 0) { - logger.info(result); - return resolve(result); - } - - logger.error( - `can-i-deploy did not return success message:\n${result}` - ); - return reject(new CannotDeployError(result)); - }); - } - ); - - return timeout(canDeployPromise, this.options.timeout as number).catch( - (err: Error) => { - if (err instanceof TimeoutError) { - throw new Error( - `Timeout waiting for publication process to complete` - ); - } - throw err; - } - ); - } -} - -export default (options: CanDeployOptions): CanDeploy => new CanDeploy(options); diff --git a/src/can-deploy/index.ts b/src/can-deploy/index.ts deleted file mode 100644 index 93cb37ab..00000000 --- a/src/can-deploy/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -import factory from './can-deploy'; - -export * from './can-deploy'; -export * from './CannotDeployError'; - -export default factory; diff --git a/src/can-deploy/types.ts b/src/can-deploy/types.ts deleted file mode 100644 index 6288594b..00000000 --- a/src/can-deploy/types.ts +++ /dev/null @@ -1,29 +0,0 @@ -export interface CanDeployPacticipant { - name: string; - version?: string; - latest?: string | boolean; -} - -export interface CanDeployOptions { - pacticipants: CanDeployPacticipant[]; - pactBroker: string; - pactBrokerToken?: string; - pactBrokerUsername?: string; - pactBrokerPassword?: string; - output?: 'json' | 'table'; - verbose?: boolean; - to?: string; - retryWhileUnknown?: number; - retryInterval?: number; - timeout?: number; -} - -export interface CanDeployResponse { - summary: { deployable: boolean; reason: string; unknown: number }; - matrix: Array<{ - consumer: CanDeployPacticipant; - provider: CanDeployPacticipant; - verificationResult: { verifiedAt: string; success: boolean }; - pact: { createdAt: string }; - }>; -} diff --git a/src/index.spec.ts b/src/index.spec.ts index 29d9a00b..30185d71 100644 --- a/src/index.spec.ts +++ b/src/index.spec.ts @@ -6,7 +6,5 @@ const { expect } = chai; describe('Index Spec', () => { it('Typescript import should work', () => { expect(index).to.be.ok; - expect(index.createServer).to.be.ok; - expect(index.createServer).to.be.a('function'); }); }); diff --git a/src/index.ts b/src/index.ts index 971334e9..748a2586 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,18 +8,9 @@ export default pact; export * from './verifier'; export * from './verifier/types'; -export * from './server'; - -export * from './publisher'; - export * from './logger'; export * from './logger/types'; -export * from './stub'; - -export * from './can-deploy'; -export * from './can-deploy/types'; - export * from './consumer'; export * from './consumer/types'; diff --git a/src/message.spec.ts b/src/message.spec.ts deleted file mode 100644 index 59b6d9e4..00000000 --- a/src/message.spec.ts +++ /dev/null @@ -1,142 +0,0 @@ -import chai = require('chai'); -import path = require('path'); -import chaiAsPromised = require('chai-as-promised'); -import fs = require('fs'); -import mkdirp = require('mkdirp'); -import rimraf = require('rimraf'); -import messageFactory from './message'; - -const { expect } = chai; -chai.use(chaiAsPromised); - -describe('Message Spec', () => { - const validJSON = `{ "description": "a test mesage", "content": { "name": "Mary" } }`; - - let absolutePath: string; - let relativePath: string; - beforeEach(() => { - relativePath = `.tmp/${Math.floor(Math.random() * 1000)}`; - absolutePath = path.resolve(__dirname, '..', relativePath); - mkdirp.sync(absolutePath); - }); - - afterEach(() => { - if (fs.existsSync(absolutePath)) { - rimraf.sync(absolutePath); - } - }); - - context('when invalid options are set', () => { - it('should throw an Error when not given any message content', () => { - expect(() => - messageFactory({ - consumer: 'a-consumer', - dir: absolutePath, - }) - ).to.throw(Error); - }); - - it('should throw an Error when not given a consumer', () => { - expect(() => - messageFactory({ - provider: 'a-provider', - dir: absolutePath, - content: validJSON, - }) - ).to.throw(Error); - }); - - it('should throw an Error when not given a provider', () => { - expect(() => - messageFactory({ - consumer: 'a-provider', - dir: absolutePath, - content: validJSON, - }) - ).to.throw(Error); - }); - - it('should throw an Error when given an invalid JSON document', () => { - expect(() => - messageFactory({ - consumer: 'some-consumer', - provider: 'a-provider', - dir: absolutePath, - content: `{ "unparseable" }`, - }) - ).to.throw(Error); - }); - - it('should throw an Error when not given a pact dir', () => { - expect(() => - messageFactory({ - consumer: 'a-consumer', - content: validJSON, - }) - ).to.throw(Error); - }); - }); - - context('when valid options are set', () => { - it('should return a message object when given the correct arguments', () => { - const message = messageFactory({ - consumer: 'some-consumer', - provider: 'a-provider', - dir: absolutePath, - content: validJSON, - }); - expect(message).to.be.a('object'); - expect(message).to.respondTo('createMessage'); - }); - - it('should return a promise when calling createMessage', () => { - const promise = messageFactory({ - consumer: 'some-consumer', - provider: 'a-provider', - dir: absolutePath, - content: validJSON, - }).createMessage(); - expect(promise).to.ok; - return expect(promise).to.eventually.be.fulfilled; - }); - - it("should create a new directory if the directory specified doesn't exist yet", () => { - const dir = path.resolve(absolutePath, 'create'); - return messageFactory({ - consumer: 'some-consumer', - provider: 'a-provider', - dir, - content: validJSON, - }) - .createMessage() - .then(() => expect(fs.existsSync(dir)).to.be.true); - }); - - it('should return an absolute path when a relative one is given', () => { - const dir = path.join(relativePath, 'create'); - expect( - messageFactory({ - consumer: 'some-consumer', - provider: 'a-provider', - dir, - content: validJSON, - }).options.dir - ).to.equal(path.resolve(__dirname, '..', dir)); - }); - - it('should create a new directory with a relative path', () => { - const dir = path.join(relativePath, 'create'); - return messageFactory({ - consumer: 'some-consumer', - provider: 'a-provider', - dir, - content: validJSON, - }) - .createMessage() - .then( - () => - expect(fs.existsSync(path.resolve(__dirname, '..', dir))).to.be.true - ); - }); - }); -}); diff --git a/src/message.ts b/src/message.ts deleted file mode 100644 index 6565d2ce..00000000 --- a/src/message.ts +++ /dev/null @@ -1,110 +0,0 @@ -import fs = require('fs'); -import path = require('path'); -import mkdirp = require('mkdirp'); -import checkTypes = require('check-types'); -import logger from './logger'; -import spawn, { DEFAULT_ARG } from './spawn'; -import pactStandalone from './pact-standalone'; -import { MessageOptions } from './types'; - -export class Message { - public readonly options: MessageOptions; - - private readonly __argMapping = { - pactFileWriteMode: DEFAULT_ARG, - dir: '--pact_dir', - consumer: '--consumer', - provider: '--provider', - spec: '--pact_specification_version', - }; - - constructor(passedOptions: MessageOptions = {}) { - const options = { ...passedOptions }; - options.pactFileWriteMode = options.pactFileWriteMode || 'update'; - options.spec = options.spec || 3; - - checkTypes.assert.nonEmptyString( - options.consumer || '', - 'Must provide the consumer name' - ); - checkTypes.assert.nonEmptyString( - options.provider || '', - 'Must provide the provider name' - ); - checkTypes.assert.nonEmptyString( - options.content || '', - 'Must provide message content' - ); - checkTypes.assert.nonEmptyString( - options.dir || '', - 'Must provide pact output dir' - ); - - if (options.spec) { - checkTypes.assert.number(options.spec); - checkTypes.assert.integer(options.spec); - checkTypes.assert.positive(options.spec); - } - - if (options.dir) { - options.dir = path.resolve(options.dir); - try { - fs.statSync(options.dir).isDirectory(); - } catch (e) { - mkdirp.sync(options.dir); - } - } - - if (options.content) { - try { - JSON.parse(options.content); - } catch (e) { - throw new Error( - 'Unable to parse message content to JSON, invalid json supplied' - ); - } - } - - if (options.consumer) { - checkTypes.assert.string(options.consumer); - } - - if (options.provider) { - checkTypes.assert.string(options.provider); - } - - this.options = options; - } - - public createMessage(): Promise { - logger.info(`Creating message pact`); - - return new Promise((resolve, reject) => { - const { pactFileWriteMode, content, ...restOptions } = this.options; - - const instance = spawn.spawnBinary( - pactStandalone.messageFullPath, - [{ pactFileWriteMode }, restOptions], - this.__argMapping - ); - const output: Array = []; - if (instance.stdout && instance.stderr && instance.stdin) { - instance.stdout.on('data', (l) => output.push(l)); - instance.stderr.on('data', (l) => output.push(l)); - instance.stdin.write(content); - instance.stdin.end(); - } - instance.once('close', (code) => { - const o = output.join('\n'); - logger.info(o); - - if (code === 0) { - return resolve(o); - } - return reject(o); - }); - }); - } -} - -export default (options: MessageOptions): Message => new Message(options); diff --git a/src/pact-standalone.spec.ts b/src/pact-standalone.spec.ts deleted file mode 100644 index 4d46437f..00000000 --- a/src/pact-standalone.spec.ts +++ /dev/null @@ -1,234 +0,0 @@ -import * as fs from 'fs'; -import * as path from 'path'; -import * as chai from 'chai'; -import pactEnvironment from './pact-environment'; -import { PactStandalone, standalone } from './pact-standalone'; - -const { expect } = chai; -const basePath = pactEnvironment.cwd; - -// Needs to stay a function and not an arrow function to access mocha 'this' context -describe('Pact Standalone', function forMocha() { - // Set timeout to 10 minutes because downloading binaries might take a while. - this.timeout(600000); - - let pact: PactStandalone; - - it('should return an object with cwd, file and fullPath properties that is platform specific', () => { - pact = standalone(); - expect(pact).to.be.an('object'); - expect(pact.cwd).to.be.ok; - expect(pact.brokerPath).to.contain('pact-broker'); - expect(pact.brokerFullPath).to.contain('pact-broker'); - expect(pact.mockServicePath).to.contain('pact-mock-service'); - expect(pact.mockServiceFullPath).to.contain('pact-mock-service'); - expect(pact.stubPath).to.contain('pact-stub-service'); - expect(pact.stubFullPath).to.contain('pact-stub-service'); - expect(pact.verifierPath).to.contain('pact-provider-verifier'); - expect(pact.verifierFullPath).to.contain('pact-provider-verifier'); - expect(pact.pactPath).to.contain('pact'); - expect(pact.pactFullPath).to.contain('pact'); - expect(pact.pactflowPath).to.contain('pactflow'); - expect(pact.pactflowFullPath).to.contain('pactflow'); - }); - - it("should return the base directory of the project with 'cwd' (where the package.json file is)", () => { - expect(fs.existsSync(path.resolve(pact.cwd, 'package.json'))).to.be.true; - }); - - describe('Check if OS specific files are there', () => { - if (!process.env['ONLY_DOWNLOAD_PACT_FOR_WINDOWS']) { - describe('OSX', () => { - beforeEach(() => { - pact = standalone('darwin'); - }); - - it('broker relative path', () => { - expect(fs.existsSync(path.resolve(basePath, pact.brokerPath))).to.be - .true; - }); - - it('broker full path', () => { - expect(fs.existsSync(pact.brokerFullPath)).to.be.true; - }); - - it('mock service relative path', () => { - expect(fs.existsSync(path.resolve(basePath, pact.mockServicePath))).to - .be.true; - }); - - it('mock service full path', () => { - expect(fs.existsSync(pact.mockServiceFullPath)).to.be.true; - }); - - it('stub relative path', () => { - expect(fs.existsSync(path.resolve(basePath, pact.stubPath))).to.be - .true; - }); - - it('stub full path', () => { - expect(fs.existsSync(pact.stubFullPath)).to.be.true; - }); - - it('provider verifier relative path', () => { - expect(fs.existsSync(path.resolve(basePath, pact.verifierPath))).to.be - .true; - }); - - it('provider verifier full path', () => { - expect(fs.existsSync(pact.verifierFullPath)).to.be.true; - }); - - it('pact relative path', () => { - expect(fs.existsSync(path.resolve(basePath, pact.pactPath))).to.be - .true; - }); - - it('pact full path', () => { - expect(fs.existsSync(pact.pactFullPath)).to.be.true; - }); - it('pactflow relative path', () => { - expect(fs.existsSync(path.resolve(basePath, pact.pactflowPath))).to.be - .true; - }); - - it('pactflow full path', () => { - expect(fs.existsSync(pact.pactflowFullPath)).to.be.true; - }); - }); - - describe('Linux X64', () => { - beforeEach(() => { - pact = standalone('linux', 'x64'); - }); - - it('broker relative path', () => { - expect(fs.existsSync(path.resolve(basePath, pact.brokerPath))).to.be - .true; - }); - - it('broker full path', () => { - expect(fs.existsSync(pact.brokerFullPath)).to.be.true; - }); - - it('mock service relative path', () => { - expect(fs.existsSync(path.resolve(basePath, pact.mockServicePath))).to - .be.true; - }); - - it('mock service full path', () => { - expect(fs.existsSync(pact.mockServiceFullPath)).to.be.true; - }); - - it('stub relative path', () => { - expect(fs.existsSync(path.resolve(basePath, pact.stubPath))).to.be - .true; - }); - - it('stub full path', () => { - expect(fs.existsSync(pact.stubFullPath)).to.be.true; - }); - - it('provider verifier relative path', () => { - expect(fs.existsSync(path.resolve(basePath, pact.verifierPath))).to.be - .true; - }); - - it('provider verifier full path', () => { - expect(fs.existsSync(pact.verifierFullPath)).to.be.true; - }); - - it('pact relative path', () => { - expect(fs.existsSync(path.resolve(basePath, pact.pactPath))).to.be - .true; - }); - - it('pact full path', () => { - expect(fs.existsSync(pact.pactFullPath)).to.be.true; - }); - - it('pactflow relative path', () => { - expect(fs.existsSync(path.resolve(basePath, pact.pactflowPath))).to.be - .true; - }); - - it('pactflow full path', () => { - expect(fs.existsSync(pact.pactflowFullPath)).to.be.true; - }); - }); - } - - describe('Windows', () => { - beforeEach(() => { - pact = standalone('win32'); - }); - - it("should add '.bat' to the end of the binary names", () => { - expect(pact.brokerPath).to.contain('pact-broker.bat'); - expect(pact.brokerFullPath).to.contain('pact-broker.bat'); - expect(pact.mockServicePath).to.contain('pact-mock-service.bat'); - expect(pact.mockServiceFullPath).to.contain('pact-mock-service.bat'); - expect(pact.stubPath).to.contain('pact-stub-service.bat'); - expect(pact.stubFullPath).to.contain('pact-stub-service.bat'); - expect(pact.verifierPath).to.contain('pact-provider-verifier.bat'); - expect(pact.verifierFullPath).to.contain('pact-provider-verifier.bat'); - expect(pact.pactPath).to.contain('pact.bat'); - expect(pact.pactFullPath).to.contain('pact.bat'); - expect(pact.pactflowPath).to.contain('pactflow.bat'); - expect(pact.pactflowFullPath).to.contain('pactflow.bat'); - }); - - it('broker relative path', () => { - expect(fs.existsSync(path.resolve(basePath, pact.brokerPath))).to.be - .true; - }); - - it('broker full path', () => { - expect(fs.existsSync(pact.brokerFullPath)).to.be.true; - }); - - it('mock service relative path', () => { - expect(fs.existsSync(path.resolve(basePath, pact.mockServicePath))).to - .be.true; - }); - - it('mock service full path', () => { - expect(fs.existsSync(pact.mockServiceFullPath)).to.be.true; - }); - - it('stub relative path', () => { - expect(fs.existsSync(path.resolve(basePath, pact.stubPath))).to.be.true; - }); - - it('stub full path', () => { - expect(fs.existsSync(pact.stubFullPath)).to.be.true; - }); - - it('provider verifier relative path', () => { - expect(fs.existsSync(path.resolve(basePath, pact.verifierPath))).to.be - .true; - }); - - it('provider verifier full path', () => { - expect(fs.existsSync(pact.verifierFullPath)).to.be.true; - }); - - it('pact relative path', () => { - expect(fs.existsSync(path.resolve(basePath, pact.pactPath))).to.be.true; - }); - - it('pact full path', () => { - expect(fs.existsSync(pact.pactFullPath)).to.be.true; - }); - - it('pactflow relative path', () => { - expect(fs.existsSync(path.resolve(basePath, pact.pactflowPath))).to.be - .true; - }); - - it('pactflow full path', () => { - expect(fs.existsSync(pact.pactflowPath)).to.be.true; - }); - }); - }); -}); diff --git a/src/pact-standalone.ts b/src/pact-standalone.ts deleted file mode 100644 index 94a0ccf8..00000000 --- a/src/pact-standalone.ts +++ /dev/null @@ -1,70 +0,0 @@ -import * as path from 'path'; -import { getBinaryEntry } from '../standalone/install'; -import pactEnvironment from './pact-environment'; - -export interface PactStandalone { - cwd: string; - brokerPath: string; - brokerFullPath: string; - mockServicePath: string; - mockServiceFullPath: string; - stubPath: string; - stubFullPath: string; - verifierPath: string; - verifierFullPath: string; - messagePath: string; - messageFullPath: string; - pactPath: string; - pactFullPath: string; - pactflowPath: string; - pactflowFullPath: string; -} - -export const standalone = ( - platform: string = process.platform, - arch: string = process.arch -): PactStandalone => { - const binName = (name: string): string => - `${name}${pactEnvironment.isWindows(platform) ? '.bat' : ''}`; - const mock = binName('pact-mock-service'); - const message = binName('pact-message'); - const verify = binName('pact-provider-verifier'); - const broker = binName('pact-broker'); - const stub = binName('pact-stub-service'); - const pact = binName('pact'); - const pactflow = binName('pactflow'); - const basePath = path.join( - 'standalone', - getBinaryEntry(platform, arch).folderName, - 'pact', - 'bin' - ); - - return { - cwd: pactEnvironment.cwd, - brokerPath: path.join(basePath, broker), - brokerFullPath: path.resolve(pactEnvironment.cwd, basePath, broker).trim(), - messagePath: path.join(basePath, message), - messageFullPath: path - .resolve(pactEnvironment.cwd, basePath, message) - .trim(), - mockServicePath: path.join(basePath, mock), - mockServiceFullPath: path - .resolve(pactEnvironment.cwd, basePath, mock) - .trim(), - stubPath: path.join(basePath, stub), - stubFullPath: path.resolve(pactEnvironment.cwd, basePath, stub).trim(), - pactPath: path.join(basePath, pact), - pactFullPath: path.resolve(pactEnvironment.cwd, basePath, pact).trim(), - pactflowPath: path.join(basePath, pactflow), - pactflowFullPath: path - .resolve(pactEnvironment.cwd, basePath, pactflow) - .trim(), - verifierPath: path.join(basePath, verify), - verifierFullPath: path - .resolve(pactEnvironment.cwd, basePath, verify) - .trim(), - }; -}; - -export default standalone(); diff --git a/src/pact.spec.ts b/src/pact.spec.ts index 9224bfd5..77f42cd2 100644 --- a/src/pact.spec.ts +++ b/src/pact.spec.ts @@ -1,16 +1,10 @@ import * as chai from 'chai'; -import * as path from 'path'; import chaiAsPromised from 'chai-as-promised'; -import * as fs from 'fs'; import pact from './pact'; -import { ServerOptions } from '.'; -const { expect } = chai; chai.use(chaiAsPromised); describe('Pact Spec', () => { - afterEach(() => pact.removeAllServers()); - describe('Set Log Level', () => { let originalLogLevel: any; // Reset log level after the tests @@ -46,226 +40,4 @@ describe('Pact Spec', () => { }); }); }); - - describe('Create serverFactory', () => { - let dirPath: string; - const monkeypatchFile: string = path.resolve( - __dirname, - '../test/monkeypatch.rb' - ); - - beforeEach(() => { - dirPath = path.resolve( - __dirname, - `../.tmp/${Math.floor(Math.random() * 1000)}` - ); - }); - - afterEach(() => { - try { - if (fs.statSync(dirPath).isDirectory()) { - fs.rmdirSync(dirPath); - } - } catch (e) { - /* any errors here are not a failed test */ - } - }); - - context('when no options are set', () => { - it('should use defaults and return serverFactory', () => { - const server = pact.createServer(); - expect(server).to.be.an('object'); - expect(server.options).to.be.an('object'); - expect(server.options).to.contain.all.keys([ - 'cors', - 'ssl', - 'host', - 'dir', - ]); - expect(server.start).to.be.a('function'); - expect(server.stop).to.be.a('function'); - expect(server.delete).to.be.a('function'); - }); - }); - - context('when user specifies valid options', () => { - it('should return serverFactory using specified options', () => { - const options = { - port: 9500, - host: 'localhost', - dir: dirPath, - ssl: true, - cors: true, - log: './log/log.txt', - spec: 1, - consumer: 'consumerName', - provider: 'providerName', - monkeypatch: monkeypatchFile, - }; - const server = pact.createServer(options); - expect(server).to.be.an('object'); - expect(server.options).to.be.an('object'); - expect(server.options.port).to.equal(options.port); - expect(server.options.host).to.equal(options.host); - expect(server.options.dir).to.equal(options.dir); - expect(server.options.ssl).to.equal(options.ssl); - expect(server.options.cors).to.equal(options.cors); - expect(server.options.log).to.equal(path.resolve(options.log)); - expect(server.options.spec).to.equal(options.spec); - expect(server.options.consumer).to.equal(options.consumer); - expect(server.options.provider).to.equal(options.provider); - expect(server.options.monkeypatch).to.equal(options.monkeypatch); - }); - }); - - context('when user specifies invalid port', () => { - it('should return an error on negative port number', () => { - expect(() => pact.createServer({ port: -42 })).to.throw(Error); - }); - - it('should return an error on non-integer', () => { - expect(() => { - pact.createServer({ port: 42.42 }); - }).to.throw(Error); - }); - - it('should return an error on non-number', () => { - expect(() => - pact.createServer({ port: '99' } as unknown as ServerOptions) - ).to.throw(Error); - }); - - it('should return an error on outside port range', () => { - expect(() => { - pact.createServer({ port: 99999 }); - }).to.throw(Error); - }); - }); - - context("when user specifies port that's currently in use", () => { - it('should return a port conflict error', () => { - pact.createServer({ port: 5100 }); - expect(() => pact.createServer({ port: 5100 })).to.throw(Error); - }); - }); - - context('when user specifies invalid host', () => { - it('should return an error on non-string', () => { - expect(() => - pact.createServer({ host: 12 } as unknown as ServerOptions) - ).to.throw(Error); - }); - }); - - context('when user specifies invalid pact directory', () => { - it('should create the directory for us', () => { - pact.createServer({ dir: dirPath }); - expect(fs.statSync(dirPath).isDirectory()).to.be.true; - }); - }); - - context('when user specifies invalid ssl', () => { - it('should return an error on non-boolean', () => { - expect(() => - pact.createServer({ ssl: 1 } as unknown as ServerOptions) - ).to.throw(Error); - }); - }); - - context('when user specifies invalid cors', () => { - it('should return an error on non-boolean', () => { - expect(() => - pact.createServer({ cors: 1 } as unknown as ServerOptions) - ).to.throw(Error); - }); - }); - - context('when user specifies invalid spec', () => { - it('should return an error on non-number', () => { - expect(() => - pact.createServer({ spec: '1' } as unknown as ServerOptions) - ).to.throw(Error); - }); - - it('should return an error on negative number', () => { - expect(() => { - pact.createServer({ spec: -12 }); - }).to.throw(Error); - }); - - it('should return an error on non-integer', () => { - expect(() => { - pact.createServer({ spec: 3.14 }); - }).to.throw(Error); - }); - }); - - context('when user specifies invalid consumer name', () => { - it('should return an error on non-string', () => { - expect(() => - pact.createServer({ consumer: 1234 } as unknown as ServerOptions) - ).to.throw(Error); - }); - }); - - context('when user specifies invalid provider name', () => { - it('should return an error on non-string', () => { - expect(() => - pact.createServer({ provider: 2341 } as unknown as ServerOptions) - ).to.throw(Error); - }); - }); - - context('when user specifies invalid monkeypatch', () => { - it('should return an error on invalid path', () => { - expect(() => { - pact.createServer({ monkeypatch: 'some-ruby-file.rb' }); - }).to.throw(Error); - }); - }); - }); - - describe('List servers', () => { - context('when called and there are no servers', () => { - it('should return an empty list', () => { - expect(pact.listServers()).to.be.empty; - }); - }); - - context('when called and there are servers in list', () => { - it('should return a list of all servers', () => { - pact.createServer({ port: 1234 }); - pact.createServer({ port: 1235 }); - pact.createServer({ port: 1236 }); - expect(pact.listServers()).to.have.length(3); - }); - }); - - context('when server is removed', () => { - it('should update the list', () => { - pact.createServer({ port: 1234 }); - pact.createServer({ port: 1235 }); - return pact - .createServer({ port: 1236 }) - .delete() - .then(() => expect(pact.listServers()).to.have.length(2)); - }); - }); - }); - - describe('Remove all servers', () => { - context( - 'when removeAll() is called and there are servers to remove', - () => { - it('should remove all servers', () => { - pact.createServer({ port: 1234 }); - pact.createServer({ port: 1235 }); - pact.createServer({ port: 1236 }); - return pact - .removeAllServers() - .then(() => expect(pact.listServers()).to.be.empty); - }); - } - ); - }); }); diff --git a/src/pact.ts b/src/pact.ts index b5e7a19b..6ffed0c6 100644 --- a/src/pact.ts +++ b/src/pact.ts @@ -1,162 +1,18 @@ -import * as _ from 'underscore'; -import serverFactory, { Server, ServerOptions } from './server'; -import stubFactory, { Stub, StubOptions } from './stub'; import verifierFactory from './verifier'; import { VerifierOptions } from './verifier/types'; -import messageFactory from './message'; -import publisherFactory from './publisher'; -import canDeployFactory from './can-deploy'; import logger, { setLogLevel } from './logger'; import { LogLevel } from './logger/types'; -import { MessageOptions, PublisherOptions } from './types'; -import { AbstractService } from './service'; -import { CanDeployOptions, CanDeployResponse } from './can-deploy/types'; export class Pact { - private __servers: Server[] = []; - - private __stubs: Stub[] = []; - - constructor() { - // Check to see if we hit into Windows Long Path issue - - // Listen for Node exiting or someone killing the process - // Must remove all the instances of Pact mock service - process.once('exit', () => this.removeAll()); - process.once('SIGINT', () => process.exit()); - } - public logLevel(level?: LogLevel): void { return setLogLevel(level); } - // Creates server with specified options - public createServer(options: ServerOptions = {}): Server { - if ( - options && - options.port && - _.some(this.__servers, (s: Server) => s.options.port === options.port) - ) { - const msg = `Port '${options.port}' is already in use by another process.`; - logger.error(msg); - throw new Error(msg); - } - - const server = serverFactory(options); - this.__servers.push(server); - logger.info( - `Creating Pact Server with options: \n${JSON.stringify(server.options)}` - ); - - // Listen to server delete events, to remove from server list - server.once(AbstractService.Events.DELETE_EVENT, (s: Server) => { - logger.info( - `Deleting Pact Server with options: \n${JSON.stringify(s.options)}` - ); - this.__servers = _.without(this.__servers, s); - }); - - return server; - } - - // Return arrays of all servers - public listServers(): Server[] { - return this.__servers; - } - - // Remove all the servers that have been created - // Return promise of all others - public removeAllServers(): Promise { - if (this.__servers.length === 0) { - return Promise.resolve(this.__servers); - } - - logger.info('Removing all Pact servers.'); - return Promise.all( - _.map(this.__servers, (server: Server) => server.delete()) - ); - } - - // Creates stub with specified options - public createStub(options: StubOptions = {}): Stub { - if ( - options && - options.port && - _.some(this.__stubs, (s: Stub) => s.options.port === options.port) - ) { - const msg = `Port '${options.port}' is already in use by another process.`; - logger.error(msg); - throw new Error(msg); - } - - const stub = stubFactory(options); - this.__stubs.push(stub); - logger.info( - `Creating Pact Stub with options: \n${JSON.stringify(stub.options)}` - ); - - // Listen to stub delete events, to remove from stub list - stub.once(AbstractService.Events.DELETE_EVENT, (s: Stub) => { - logger.info( - `Deleting Pact Stub with options: \n${JSON.stringify(stub.options)}` - ); - this.__stubs = _.without(this.__stubs, s); - }); - - return stub; - } - - // Return arrays of all stubs - public listStubs(): Stub[] { - return this.__stubs; - } - - // Remove all the stubs that have been created - // Return promise of all others - public removeAllStubs(): Promise { - if (this.__stubs.length === 0) { - return Promise.resolve(this.__stubs); - } - - logger.info('Removing all Pact stubs.'); - return Promise.all( - _.map(this.__stubs, (stub: Stub) => stub.delete()) - ); - } - - // Remove all the servers and stubs - public removeAll(): Promise { - return Promise.all( - _.flatten([this.removeAllStubs(), this.removeAllServers()]) - ); - // .tap(endDestination); - } - // Run the Pact Verification process public verifyPacts(options: VerifierOptions): Promise { logger.info('Verifying Pacts.'); return verifierFactory(options).verify(); } - - // Run the Message Pact creation process - public createMessage(options: MessageOptions): Promise { - logger.info('Creating Message'); - return messageFactory(options).createMessage(); - } - - // Publish Pacts to a Pact Broker - public publishPacts(options: PublisherOptions): Promise { - logger.info('Publishing Pacts to Broker'); - return publisherFactory(options).publish(); - } - - // Use can-i-deploy to determine if it is safe to deploy - public canDeploy( - options: CanDeployOptions - ): Promise { - logger.info('Checking if it it possible to deploy'); - return canDeployFactory(options).canDeploy(); - } } export default new Pact(); diff --git a/src/publisher.spec.ts b/src/publisher.spec.ts deleted file mode 100644 index dd1b78ec..00000000 --- a/src/publisher.spec.ts +++ /dev/null @@ -1,134 +0,0 @@ -import path = require('path'); -import fs = require('fs'); -import chai = require('chai'); -import chaiAsPromised = require('chai-as-promised'); -import * as http from 'http'; -import rimraf = require('rimraf'); -import mkdirp = require('mkdirp'); -import publisherFactory from './publisher'; -import logger from './logger'; -import brokerMock from '../test/integration/broker-mock'; -import { PublisherOptions } from './types'; - -const { expect } = chai; -chai.use(chaiAsPromised); - -describe('Publish Spec', () => { - const PORT = Math.floor(Math.random() * 999) + 9000; - const pactFile = path.resolve( - __dirname, - '../test/integration/me-they-success.json' - ); - let server: http.Server; - let absolutePath: string; - let relativePath: string; - - before(() => - brokerMock(PORT).then((s) => { - logger.debug(`Pact Broker Mock listening on port: ${PORT}`); - server = s; - }) - ); - - after(() => server.close()); - - beforeEach(() => { - relativePath = `.tmp/${Math.floor(Math.random() * 1000)}`; - absolutePath = path.resolve(__dirname, '..', relativePath); - mkdirp.sync(absolutePath); - }); - - afterEach(() => { - if (fs.existsSync(absolutePath)) { - rimraf.sync(absolutePath); - } - }); - - context('when invalid options are set', () => { - it('should fail with an Error when not given pactBroker', () => { - expect(() => { - publisherFactory({ - pactFilesOrDirs: [absolutePath], - consumerVersion: '1.0.0', - } as PublisherOptions); - }).to.throw(Error); - }); - - it('should fail with an Error when not given consumerVersion', () => { - expect(() => { - publisherFactory({ - pactBroker: 'http://localhost', - pactFilesOrDirs: [absolutePath], - } as PublisherOptions); - }).to.throw(Error); - }); - - it('should fail with an error when not given pactFilesOrDirs', () => { - expect(() => { - publisherFactory({ - pactBroker: 'http://localhost', - consumerVersion: '1.0.0', - } as PublisherOptions); - }).to.throw(Error); - }); - - it('should fail with an Error when given Pact paths that do not exist', () => { - expect(() => { - publisherFactory({ - pactBroker: 'http://localhost', - pactFilesOrDirs: ['test.json'], - consumerVersion: '1.0.0', - }); - }).to.throw(Error); - }); - }); - - context('when valid options are set', () => { - it('should return an absolute path when a relative one is given', () => { - expect( - publisherFactory({ - pactBroker: 'http://localhost', - pactFilesOrDirs: [relativePath], - consumerVersion: '1.0.0', - }).options.pactFilesOrDirs[0] - ).to.be.equal(path.resolve(__dirname, '..', relativePath)); - }); - - it('should return a Publisher object when given the correct arguments', () => { - const p = publisherFactory({ - pactBroker: 'http://localhost', - pactFilesOrDirs: [pactFile], - consumerVersion: '1.0.0', - }); - expect(p).to.be.ok; - expect(p.publish).to.be.a('function'); - }); - }); - - context('when a bearer token is provided', () => { - context('and specifies a username or password', () => { - it('should fail with an error', () => { - expect(() => - publisherFactory({ - pactBroker: 'http://localhost', - pactFilesOrDirs: [relativePath], - consumerVersion: '1.0.0', - pactBrokerToken: '1234', - pactBrokerUsername: 'username', - pactBrokerPassword: '5678', - }) - ).to.throw(Error); - }); - }); - it('should not fail', () => { - const p = publisherFactory({ - pactBroker: 'http://localhost', - pactFilesOrDirs: [pactFile], - consumerVersion: '1.0.0', - pactBrokerToken: '1234', - }); - expect(p).to.be.ok; - expect(p.publish).to.be.a('function'); - }); - }); -}); diff --git a/src/publisher.ts b/src/publisher.ts deleted file mode 100644 index 08824019..00000000 --- a/src/publisher.ts +++ /dev/null @@ -1,151 +0,0 @@ -import path = require('path'); -import fs = require('fs'); -import { timeout, TimeoutError } from 'promise-timeout'; -import checkTypes = require('check-types'); -import logger, { verboseIsImplied } from './logger'; -import spawn, { DEFAULT_ARG } from './spawn'; -import pactStandalone from './pact-standalone'; -import { PublisherOptions } from './types'; - -export class Publisher { - public readonly options: PublisherOptions; - - private readonly __argMapping = { - pactFilesOrDirs: DEFAULT_ARG, - pactBroker: '--broker-base-url', - pactBrokerUsername: '--broker-username', - pactBrokerPassword: '--broker-password', - pactBrokerToken: '--broker-token', - tags: '--tag', - consumerVersion: '--consumer-app-version', - verbose: '--verbose', - buildUrl: '--build-url', - branch: '--branch', - autoDetectVersionProperties: '--auto-detect-version-properties', - }; - - constructor(passedOptions: PublisherOptions) { - this.options = passedOptions || {}; - // Setting defaults - this.options.tags = this.options.tags || []; - this.options.timeout = this.options.timeout || 60000; - - checkTypes.assert.nonEmptyString( - this.options.pactBroker, - 'Must provide the pactBroker argument' - ); - checkTypes.assert.nonEmptyString( - this.options.consumerVersion, - 'Must provide the consumerVersion argument' - ); - checkTypes.assert.arrayLike( - this.options.pactFilesOrDirs, - 'Must provide the pactFilesOrDirs argument' - ); - checkTypes.assert.nonEmptyArray( - this.options.pactFilesOrDirs, - 'Must provide the pactFilesOrDirs argument with an array' - ); - - if (this.options.pactFilesOrDirs) { - checkTypes.assert.array.of.string(this.options.pactFilesOrDirs); - - // Resolve all paths as absolute paths - this.options.pactFilesOrDirs = this.options.pactFilesOrDirs.map((v) => { - const newPath = path.resolve(v); - if (!fs.existsSync(newPath)) { - throw new Error( - `Path '${v}' given in pactFilesOrDirs does not exists.` - ); - } - return newPath; - }); - } - - if (this.options.pactBroker) { - checkTypes.assert.string(this.options.pactBroker); - } - - if (this.options.pactBrokerUsername) { - checkTypes.assert.string(this.options.pactBrokerUsername); - } - - if (this.options.pactBrokerPassword) { - checkTypes.assert.string(this.options.pactBrokerPassword); - } - - if (this.options.verbose === undefined && verboseIsImplied()) { - this.options.verbose = true; - } - - if ( - (this.options.pactBrokerUsername && !this.options.pactBrokerPassword) || - (this.options.pactBrokerPassword && !this.options.pactBrokerUsername) - ) { - throw new Error( - 'Must provide both Pact Broker username and password. None needed if authentication on Broker is disabled.' - ); - } - - if ( - this.options.pactBrokerToken && - (this.options.pactBrokerUsername || this.options.pactBrokerPassword) - ) { - throw new Error( - 'Must provide pactBrokerToken or pactBrokerUsername/pactBrokerPassword but not both.' - ); - } - - if (this.options.branch) { - checkTypes.assert.string(this.options.branch); - } - - if (this.options.autoDetectVersionProperties) { - checkTypes.assert.boolean(this.options.autoDetectVersionProperties); - } - } - - public publish(): Promise { - logger.info(`Publishing pacts to broker at: ${this.options.pactBroker}`); - - return timeout( - new Promise((resolve, reject) => { - const instance = spawn.spawnBinary( - pactStandalone.brokerFullPath, - [{ cliVerb: 'publish' }, this.options], - this.__argMapping - ); - const output: Array = []; - if (instance.stderr && instance.stdout) { - 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\/.*$/gim.exec(o); - if (code !== 0) { - const message = `Pact publication failed with non-zero exit code. Full output was:\n${o}`; - logger.error(message); - return reject(new Error(message)); - } - if (!pactUrls) { - const message = `Publication appeared to fail, as we did not detect any pact URLs in the following output:\n${o}`; - logger.error(message); - return reject(new Error(message)); - } - - logger.info(o); - return resolve(pactUrls); - }); - }), - this.options.timeout as number - ).catch((err: Error) => { - if (err instanceof TimeoutError) { - throw new Error(`Timeout waiting for publication process to complete`); - } - throw err; - }); - } -} - -export default (options: PublisherOptions): Publisher => new Publisher(options); diff --git a/src/server.spec.ts b/src/server.spec.ts deleted file mode 100644 index 3a6a8d12..00000000 --- a/src/server.spec.ts +++ /dev/null @@ -1,370 +0,0 @@ -import chai = require('chai'); -import chaiAsPromised = require('chai-as-promised'); -import fs = require('fs'); -import util = require('util'); -import path = require('path'); -import mkdirp = require('mkdirp'); -import rimraf = require('rimraf'); -import serverFactory, { ServerOptions } from './server'; - -chai.use(chaiAsPromised); -const { expect } = chai; -const rm = util.promisify(rimraf); - -describe('Server Spec', () => { - let server: any; - const monkeypatchFile: string = path.resolve( - __dirname, - '../test/monkeypatch.rb' - ); - - let absolutePath: string; - let relativePath: string; - - beforeEach(() => { - relativePath = `.tmp/${Math.floor(Math.random() * 1000)}`; - absolutePath = path.resolve(__dirname, '..', relativePath); - mkdirp.sync(absolutePath); - }); - - afterEach(async () => { - if (server) { - await server.delete(); - } - - if (fs.existsSync(absolutePath)) { - await rm(absolutePath); - } - }); - - const relativeSSLCertPath = 'test/ssl/server.crt'; - const absoluteSSLCertPath = path.resolve( - __dirname, - '..', - relativeSSLCertPath - ); - const relativeSSLKeyPath = 'test/ssl/server.key'; - const absoluteSSLKeyPath = path.resolve(__dirname, '..', relativeSSLKeyPath); - - describe('Start server', () => { - context('when no options are set', () => { - it('should start correctly with defaults', () => { - server = serverFactory(); - return expect(server.start()).to.eventually.be.fulfilled; - }); - }); - - context('when invalid options are set', () => { - it('should fail if custom ssl certs do not exist', () => { - expect(() => - serverFactory({ - ssl: true, - sslcert: 'does/not/exist', - sslkey: absoluteSSLKeyPath, - }) - ).to.throw(Error); - }); - - it('should fail if custom ssl keys do not exist', () => { - expect(() => - serverFactory({ - ssl: true, - sslcert: absoluteSSLCertPath, - sslkey: 'does/not/exist', - }) - ).to.throw(Error); - }); - - it("should fail if custom ssl cert is set, but ssl key isn't", () => { - expect(() => - serverFactory({ - ssl: true, - sslcert: absoluteSSLCertPath, - }) - ).to.throw(Error); - }); - - it("should fail if custom ssl key is set, but ssl cert isn't", () => { - expect(() => - serverFactory({ - ssl: true, - sslkey: absoluteSSLKeyPath, - }) - ).to.throw(Error); - }); - }); - - context('when valid options are set', () => { - it('should start correctly when instance is delayed', () => { - server = serverFactory(); - - const waitForServerUp = server.__waitForServiceUp.bind(server); - return expect( - Promise.all([ - waitForServerUp(server.options), - new Promise((resolve) => { - setTimeout(resolve, 5000); - }).then(() => server.start()), - ]) - ).to.eventually.be.fulfilled; - }); - - it('should start correctly with ssl', () => { - server = serverFactory({ ssl: true }); - expect(server.options.ssl).to.equal(true); - return expect(server.start()).to.eventually.be.fulfilled; - }); - - it('should start correctly with custom ssl cert/key', () => { - server = serverFactory({ - ssl: true, - sslcert: absoluteSSLCertPath, - sslkey: absoluteSSLKeyPath, - }); - expect(server.options.ssl).to.equal(true); - return expect(server.start()).to.eventually.be.fulfilled; - }); - - it('should start correctly with custom ssl cert/key but without specifying ssl flag', () => { - server = serverFactory({ - sslcert: absoluteSSLCertPath, - sslkey: absoluteSSLKeyPath, - }); - - expect(server.options.ssl).to.equal(true); - return expect(server.start()).to.eventually.be.fulfilled; - }); - - it('should start correctly with cors', () => { - server = serverFactory({ cors: true }); - expect(server.options.cors).to.equal(true); - return expect(server.start()).to.eventually.be.fulfilled; - }); - - it('should start correctly with port', () => { - const port = Math.floor(Math.random() * 999) + 9000; - server = serverFactory({ port }); - expect(server.options.port).to.equal(port); - return expect(server.start()).to.eventually.be.fulfilled; - }); - - it('should start correctly with host', () => { - const host = 'localhost'; - server = serverFactory({ host }); - expect(server.options.host).to.equal(host); - return expect(server.start()).to.eventually.be.fulfilled; - }); - - it('should start correctly with spec version 1', () => { - server = serverFactory({ spec: 1 }); - expect(server.options.spec).to.equal(1); - return expect(server.start()).to.eventually.be.fulfilled; - }); - - it('should start correctly with spec version 2', () => { - server = serverFactory({ spec: 2 }); - expect(server.options.spec).to.equal(2); - return expect(server.start()).to.eventually.be.fulfilled; - }); - - it('should start correctly with log', () => { - const logPath = path.resolve(absolutePath, 'log.txt'); - server = serverFactory({ log: logPath }); - expect(server.options.log).to.equal(logPath); - - return expect(server.start()).to.eventually.be.fulfilled; - - // return expect(server.start()).to.eventually.be.fulfilled.then( - // () => void rm(logPath), - // ); - }); - - it('should start correctly with consumer name', () => { - 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', () => { - const providerName = 'pName'; - server = serverFactory({ provider: providerName }); - expect(server.options.provider).to.equal(providerName); - return expect(server.start()).to.eventually.be.fulfilled; - }); - - it('should start correctly with monkeypatch', () => { - const s = serverFactory({ monkeypatch: monkeypatchFile }); - expect(s.options.monkeypatch).to.equal(monkeypatchFile); - return expect(s.start()).to.eventually.be.fulfilled; - }); - - context('Paths', () => { - it('should start correctly with dir, absolute path', () => { - server = serverFactory({ dir: relativePath }); - expect(server.options.dir).to.equal(absolutePath); - return expect(server.start()).to.eventually.be.fulfilled; - }); - - it('should start correctly with log, relative paths', () => { - const logPath = path.join(relativePath, 'log.txt'); - server = serverFactory({ log: logPath }); - expect(server.options.log).to.equal(path.resolve(logPath)); - return expect(server.start()).to.eventually.be.fulfilled; - }); - - it('should start correctly with custom ssl cert/key, relative paths', () => { - server = serverFactory({ - sslcert: relativeSSLCertPath, - sslkey: relativeSSLKeyPath, - }); - expect(server.options.sslcert).to.equal(absoluteSSLCertPath); - expect(server.options.sslkey).to.equal(absoluteSSLKeyPath); - return expect(server.start()).to.eventually.be.fulfilled; - }); - }); - - context('File Write Mode', () => { - it("should start correctly with 'overwrite'", () => - expect( - serverFactory({ - pactFileWriteMode: 'overwrite', - }).start() - ).to.eventually.be.fulfilled); - - it("should start correctly with 'merge'", () => - expect( - serverFactory({ - pactFileWriteMode: 'merge', - }).start() - ).to.eventually.be.fulfilled); - - it("should start correctly with 'update'", () => - expect( - serverFactory({ - pactFileWriteMode: 'update', - }).start() - ).to.eventually.be.fulfilled); - }); - - context('Log Level', () => { - it("should start correctly with 'debug'", () => - Promise.all([ - expect( - serverFactory({ - logLevel: 'debug', - }).start() - ).to.eventually.be.fulfilled, - - expect( - serverFactory({ - logLevel: 'DEBUG', - } as unknown as ServerOptions).start() - ).to.eventually.be.fulfilled, - ])); - - it("should start correctly with 'info'", () => - Promise.all([ - expect( - serverFactory({ - logLevel: 'info', - }).start() - ).to.eventually.be.fulfilled, - - expect( - serverFactory({ - logLevel: 'INFO', - } as unknown as ServerOptions).start() - ).to.eventually.be.fulfilled, - ])); - - it("should start correctly with 'warn'", () => - Promise.all([ - expect( - serverFactory({ - logLevel: 'warn', - }).start() - ).to.eventually.be.fulfilled, - - expect( - serverFactory({ - logLevel: 'WARN', - } as unknown as ServerOptions).start() - ).to.eventually.be.fulfilled, - ])); - - it("should start correctly with 'error'", () => - Promise.all([ - expect( - serverFactory({ - logLevel: 'error', - }).start() - ).to.eventually.be.fulfilled, - - expect( - serverFactory({ - logLevel: 'ERROR', - } as unknown as ServerOptions).start() - ).to.eventually.be.fulfilled, - ])); - }); - }); - - it('should dispatch event when starting', (done) => { - server = serverFactory(); - server.once('start', () => done()); - server.start(); - }); - - it('should change running state to true', () => { - server = serverFactory(); - return server.start().then(() => expect(server.__running).to.be.true); - }); - }); - - describe('Stop server', () => { - context('when already started', () => { - it('should stop running', () => { - server = serverFactory(); - return server.start().then(() => server.stop()); - }); - - it('should dispatch event when stopping', (done) => { - server = serverFactory(); - server.once('stop', () => done()); - server.start().then(() => server.stop()); - }); - - it('should change running state to false', () => { - server = serverFactory(); - return server - .start() - .then(() => server.stop()) - .then(() => expect(server.__running).to.be.false); - }); - }); - }); - - describe('Delete server', () => { - context('when already running', () => { - it('should stop & delete server', () => { - server = serverFactory(); - return server.start().then(() => server.delete()); - }); - - it('should dispatch event when deleting', (done) => { - server = serverFactory(); - server.once('delete', () => done()); - server.start().then(() => server.delete()); - }); - - it('should change running state to false', () => { - server = serverFactory(); - return server - .start() - .then(() => server.delete()) - .then(() => expect(server.__running).to.be.false); - }); - }); - }); -}); diff --git a/src/server.ts b/src/server.ts deleted file mode 100644 index 0e450e91..00000000 --- a/src/server.ts +++ /dev/null @@ -1,111 +0,0 @@ -import path = require('path'); -import fs = require('fs'); -import mkdirp = require('mkdirp'); -import checkTypes = require('check-types'); -import pact from './pact-standalone'; -import { AbstractService } from './service'; -import { LogLevel } from './logger/types'; - -export class Server extends AbstractService { - public readonly options: ServerOptions; - - constructor(passedOptions: ServerOptions = {}) { - const options = { ...passedOptions }; - options.dir = options.dir ? path.resolve(options.dir) : process.cwd(); // Use directory relative to cwd - options.pactFileWriteMode = options.pactFileWriteMode || 'overwrite'; - - if (options.spec) { - checkTypes.assert.number(options.spec); - checkTypes.assert.integer(options.spec); - checkTypes.assert.positive(options.spec); - } - - if (options.dir) { - const dir = path.resolve(options.dir); - try { - fs.statSync(dir).isDirectory(); - } catch (e) { - mkdirp.sync(dir); - } - } - - if (options.log) { - options.log = path.resolve(options.log); - } - - if (options.sslcert) { - options.sslcert = path.resolve(options.sslcert); - } - - if (options.sslkey) { - options.sslkey = path.resolve(options.sslkey); - } - - if (options.consumer) { - checkTypes.assert.string(options.consumer); - } - - if (options.provider) { - checkTypes.assert.string(options.provider); - } - - if (options.logLevel) { - options.logLevel = options.logLevel.toLowerCase() as LogLevel; - } - - if (options.monkeypatch) { - checkTypes.assert.string(options.monkeypatch); - try { - fs.statSync(path.normalize(options.monkeypatch)).isFile(); - } catch (e) { - throw new Error( - `Monkeypatch ruby file not found at path: ${options.monkeypatch}` - ); - } - } - - super( - pact.mockServiceFullPath, - options, - { - port: '--port', - host: '--host', - log: '--log', - ssl: '--ssl', - sslcert: '--sslcert', - sslkey: '--sslkey', - cors: '--cors', - dir: '--pact_dir', - spec: '--pact_specification_version', - pactFileWriteMode: '--pact-file-write-mode', - consumer: '--consumer', - provider: '--provider', - monkeypatch: '--monkeypatch', - logLevel: '--log-level', - }, - { cliVerb: 'service' } - ); - this.options = options; - } -} - -// Creates a new instance of the pact server with the specified option -export default (options?: ServerOptions): Server => new Server(options); - -export interface ServerOptions { - port?: number; - ssl?: boolean; - cors?: boolean; - dir?: string; - host?: string; - sslcert?: string; - sslkey?: string; - log?: string; - spec?: number; - consumer?: string; - provider?: string; - monkeypatch?: string; - logLevel?: LogLevel; - timeout?: number; - pactFileWriteMode?: 'overwrite' | 'update' | 'merge'; -} diff --git a/src/service.ts b/src/service.ts deleted file mode 100644 index 0304acf1..00000000 --- a/src/service.ts +++ /dev/null @@ -1,347 +0,0 @@ -/* eslint-disable no-param-reassign */ -// TODO - the param reassign behaviour is relied on, and should be fixed -import path = require('path'); -import fs = require('fs'); -import events = require('events'); -import { ChildProcess } from 'child_process'; -import { timeout, TimeoutError } from 'promise-timeout'; -import mkdirp = require('mkdirp'); -import checkTypes = require('check-types'); -import needle = require('needle'); -import spawn, { CliVerbOptions } from './spawn'; -import { LogLevel } from './logger/types'; -import logger, { setLogLevel } from './logger'; -import { HTTPConfig, ServiceOptions } from './types'; - -// Get a reference to the global setTimeout object in case it is mocked by a testing library later -const { setTimeout } = global; - -const RETRY_AMOUNT = 60; - -const getTimeout = (options: ServiceOptions): number => - options.timeout || 30000; - -const getRetryTickTime = (options: ServiceOptions): number => - Math.round(getTimeout(options) / RETRY_AMOUNT); - -interface AbstractServiceEventInterface { - START_EVENT: string; - STOP_EVENT: string; - DELETE_EVENT: string; -} - -export abstract class AbstractService extends events.EventEmitter { - public static get Events(): AbstractServiceEventInterface { - return { - START_EVENT: 'start', - STOP_EVENT: 'stop', - DELETE_EVENT: 'delete', - }; - } - - public readonly options: ServiceOptions; - - protected __argMapping: Record; - - protected __running: boolean; - - protected __instance: ChildProcess | undefined; - - protected __cliVerb?: CliVerbOptions; - - protected __serviceCommand: string; - - protected constructor( - command: string, - options: ServiceOptions, - argMapping: Record, - cliVerb?: CliVerbOptions - ) { - super(); - // Set logger first - if (options.logLevel) { - setLogLevel(options.logLevel); - // Pact-js-core's logger supports fatal and trace, - // but the ruby binary doesn't. So we map those. - if ((options.logLevel as string) === 'fatal') { - options.logLevel = 'error'; - } else if ((options.logLevel as string) === 'trace') { - options.logLevel = 'debug'; - } - - // Then need to uppercase log level for ruby - options.logLevel = options.logLevel.toUpperCase() as LogLevel; - } - // defaults - options.ssl = options.ssl || false; - options.cors = options.cors || false; - options.host = options.host || 'localhost'; - - // port checking - if (options.port) { - checkTypes.assert.number(options.port); - checkTypes.assert.integer(options.port); - checkTypes.assert.positive(options.port); - if (!checkTypes.inRange(options.port, 0, 65535)) { - throw new Error( - `Port number ${options.port} is not in the range 0-65535` - ); - } - - if (checkTypes.not.inRange(options.port, 1024, 49151)) { - logger.warn( - 'Like a Boss, you used a port outside of the recommended range (1024 to 49151); I too like to live dangerously.' - ); - } - } - - // ssl check - checkTypes.assert.boolean(options.ssl); - - // Throw error if one ssl option is set, but not the other - if ( - (options.sslcert && !options.sslkey) || - (!options.sslcert && options.sslkey) - ) { - throw new Error( - 'Custom ssl certificate and key must be specified together.' - ); - } - - // check certs/keys exist for SSL - if (options.sslcert) { - try { - fs.statSync(path.normalize(options.sslcert)).isFile(); - } catch (e) { - throw new Error( - `Custom ssl certificate not found at path: ${options.sslcert}` - ); - } - } - - if (options.sslkey) { - try { - fs.statSync(path.normalize(options.sslkey)).isFile(); - } catch (e) { - throw new Error(`Custom ssl key not found at path: ${options.sslkey}`); - } - } - - // If both sslcert and sslkey option has been specified, let's assume the user wants to enable ssl - if (options.sslcert && options.sslkey) { - options.ssl = true; - } - - // cors check - checkTypes.assert.boolean(options.cors); - - // log check - if (options.log) { - const fileObj = path.parse(path.normalize(options.log)); - try { - fs.statSync(fileObj.dir).isDirectory(); - } catch (e) { - // If log path doesn't exist, create it - mkdirp.sync(fileObj.dir); - } - } - - // host check - if (options.host) { - checkTypes.assert.string(options.host); - } - - this.options = options; - this.__running = false; - this.__cliVerb = cliVerb; - this.__serviceCommand = command; - this.__argMapping = argMapping; - } - - public start(): Promise { - if (this.__instance && this.__instance.connected) { - logger.warn( - `You already have a process running with PID: ${this.__instance.pid}` - ); - return Promise.resolve(this); - } - - this.__instance = this.spawnBinary(); - this.__instance.once('close', () => this.stop()); - - if (!this.options.port) { - // if port isn't specified, listen for it when pact runs - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const catchPort = (data: any): void => { - const match = data.match(/port=([0-9]+)/); - if (match && match[1]) { - this.options.port = parseInt(match[1], 10); - if (this?.__instance?.stdout) { - // __instance will never be undefined here because we just - // read the port number from it - this.__instance.stdout.removeListener('data', catchPort); - } - logger.info(`Pact running on port ${this.options.port}`); - } - }; - - if (this?.__instance?.stdout) { - this.__instance.stdout.on('data', catchPort); - } - } - - if (this?.__instance?.stderr) { - this.__instance.stderr.on('data', (data) => - logger.error(`Pact Binary Error: ${data}`) - ); - } - - // check service is available - return timeout(this.__waitForServiceUp(), getTimeout(this.options)) - .then(() => { - this.__running = true; - this.emit(AbstractService.Events.START_EVENT, this); - return this; - }) - .catch((err: Error) => { - if (err instanceof TimeoutError) { - throw new Error( - `Timeout while waiting to start Pact with PID: ${ - this.__instance ? this.__instance.pid : 'No Instance' - }` - ); - } - throw err; - }); - } - - // Stop the instance - public stop(): Promise { - const pid = this.__instance ? this.__instance.pid : -1; - return timeout( - Promise.resolve(this.__instance) - .then(spawn.killBinary) - .then(() => this.__waitForServiceDown()), - getTimeout(this.options) - ) - .catch((err: Error) => { - if (err instanceof TimeoutError) { - throw new Error( - `Timeout while waiting to stop Pact with PID '${pid}'` - ); - } - throw err; - }) - .then(() => { - this.__running = false; - this.emit(AbstractService.Events.STOP_EVENT, this); - return this; - }); - } - - // Deletes this instance and emit an event - public delete(): Promise { - return this.stop().then(() => { - this.emit(AbstractService.Events.DELETE_EVENT, this); - - return this; - }); - } - - // Subclass responsible for spawning the process - protected spawnBinary(): ChildProcess { - return spawn.spawnBinary( - this.__serviceCommand, - this.__cliVerb ? [this.__cliVerb, this.options] : [this.options], - this.__argMapping - ); - } - - // Wait for the service to be initialized and ready - protected __waitForServiceUp(): Promise { - let amount = 0; - - const waitPromise = new Promise((resolve, reject) => { - const retry = (): void => { - if (amount >= RETRY_AMOUNT) { - reject( - new Error( - `Pact startup failed; tried calling service ${RETRY_AMOUNT} times with no result.` - ) - ); - } - // eslint-disable-next-line @typescript-eslint/no-use-before-define - setTimeout(check.bind(this), getRetryTickTime(this.options)); - }; - - const check = (): void => { - amount += 1; - if (this.options.port) { - this.__call(this.options).then(() => resolve(), retry.bind(this)); - } else { - retry(); - } - }; - - check(); // Check first time, start polling - }); - return waitPromise; - } - - protected __waitForServiceDown(): Promise { - let amount = 0; - - const checkPromise = new Promise((resolve, reject) => { - const check = (): void => { - amount += 1; - if (this.options.port) { - this.__call(this.options).then( - () => { - if (amount >= RETRY_AMOUNT) { - reject( - new Error( - `Pact stop failed; tried calling service ${RETRY_AMOUNT} times with no result.` - ) - ); - return; - } - setTimeout(check, getRetryTickTime(this.options)); - }, - () => resolve() - ); - } else { - resolve(); - } - }; - check(); // Check first time, start polling - }); - - return checkPromise; - } - - private __call(options: ServiceOptions): Promise { - const config: HTTPConfig = { - method: 'GET', - headers: { - 'X-Pact-Mock-Service': 'true', - 'Content-Type': 'application/json', - }, - }; - - if (options.ssl) { - process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = '0'; - config.rejectUnauthorized = false; - config.agent = false; - } - - return needle( - 'get', - `http${options.ssl ? 's' : ''}://${options.host}:${options.port}`, - config - ).then((res) => { - if (res.statusCode !== 200) { - throw new Error(`HTTP Error: '${JSON.stringify(res)}'`); - } - }); - } -} diff --git a/src/spawn/arguments.spec.ts b/src/spawn/arguments.spec.ts deleted file mode 100644 index fc606b0f..00000000 --- a/src/spawn/arguments.spec.ts +++ /dev/null @@ -1,168 +0,0 @@ -import chai = require('chai'); -import chaiAsPromised = require('chai-as-promised'); -import argsHelper, { DEFAULT_ARG } from './arguments'; - -const { expect } = chai; - -chai.use(chaiAsPromised); - -describe('Pact Util Spec', () => { - describe('toArgumentsArray', () => { - describe('when called with an object', () => { - it('should return an array of all arguments', () => { - const result = argsHelper.toArgumentsArray( - { 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 = argsHelper.toArgumentsArray( - { - 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'); - }); - describe("and the argument's value is also an object", () => { - it('should serialise the argument value to a JSON string', () => { - const result = argsHelper.toArgumentsArray( - { - consumerVersionSelectors: [ - { - latest: true, - tag: 'prod', - }, - { - tag: 'bar', - }, - ], - }, - { consumerVersionSelectors: '--consumer-version-selector' } - ); - - expect(result) - .to.be.an('array') - .that.includes('--consumer-version-selector') - .and.includes('{"latest":true,"tag":"prod"}') - .and.includes('{"tag":"bar"}'); - expect(result.length).to.be.equal(4); - }); - }); - }); - describe('when called with an array', () => { - describe('with one element', () => { - it('should return an array of all arguments', () => { - const result = argsHelper.toArgumentsArray( - [{ 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 produce correct arguments array', () => { - const result = argsHelper.toArgumentsArray( - [ - { - 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'); - }); - }); - describe('with multiple elements', () => { - it('should produce correct arguments array', () => { - const result = argsHelper.toArgumentsArray( - [ - { participant: 'one' }, - { version: 'v1' }, - { participant: 'two' }, - { version: 'v2' }, - ], - { version: '--version', participant: '--participant' } - ); - - expect(result).to.be.an('array'); - expect(result).to.eql([ - '--participant', - 'one', - '--version', - 'v1', - '--participant', - 'two', - '--version', - 'v2', - ]); - }); - }); - describe("and an argument's value is an object", () => { - it('should serialise the argument value to a JSON string', () => { - const result = argsHelper.toArgumentsArray( - [ - { - consumerVersionSelectors: [ - { - latest: true, - tag: 'prod', - }, - ], - }, - { - consumerVersionSelectors: [ - { - tag: 'foo', - }, - ], - }, - ], - { consumerVersionSelectors: '--consumer-version-selector' } - ); - - expect(result) - .to.be.an('array') - .that.includes('--consumer-version-selector') - .and.includes('{"latest":true,"tag":"prod"}') - .and.includes('{"tag":"foo"}'); - expect(result.length).to.be.equal(4); - }); - }); - }); - - it('should make DEFAULT values first, everything else after', () => { - const result = argsHelper.toArgumentsArray( - { - 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'); - }); - }); -}); diff --git a/src/spawn/arguments.ts b/src/spawn/arguments.ts deleted file mode 100644 index f27f134c..00000000 --- a/src/spawn/arguments.ts +++ /dev/null @@ -1,77 +0,0 @@ -import _ = require('underscore'); -import checkTypes = require('check-types'); -import { VerifierOptions } from '../verifier/types'; -import { MessageOptions, PublisherOptions, ServiceOptions } from '../types'; -import { CanDeployOptions } from '../can-deploy/types'; - -export type CliVerbOptions = { - cliVerb: string; -}; - -export type SpawnArgument = - | CanDeployOptions - | MessageOptions - | PublisherOptions - | ServiceOptions - | VerifierOptions - | CliVerbOptions - // eslint-disable-next-line @typescript-eslint/ban-types - | {}; // Empty object is allowed to make tests less noisy. We should change this in the future - -export type SpawnArguments = Array | SpawnArgument; - -export const DEFAULT_ARG = 'DEFAULT'; -export const PACT_NODE_NO_VALUE = 'PACT_NODE_NO_VALUE'; - -const valFor = (v: SpawnArgument): Array => { - if (typeof v === 'object') { - return [JSON.stringify(v)]; - } - return v !== PACT_NODE_NO_VALUE ? [`${v}`] : []; -}; - -const mapFor = (mapping: string, v: string): Array => - mapping === DEFAULT_ARG ? valFor(v) : [mapping].concat(valFor(v)); - -const convertValue = ( - mapping: string, - v: SpawnArgument | Array -): Array => { - if (v && mapping) { - return checkTypes.array(v) - ? _.flatten( - (v as Array).map((val: string) => mapFor(mapping, val)) - ) - : mapFor(mapping, v as string); - } - return []; -}; - -export class Arguments { - public toArgumentsArray( - args: SpawnArguments, - mappings: { [id: string]: string } - ): string[] { - return _.chain(args instanceof Array ? args : [args]) - .map((x: SpawnArgument) => this.createArgumentsFromObject(x, mappings)) - .flatten() - .value(); - } - - private createArgumentsFromObject( - args: SpawnArgument, - mappings: { [id: string]: string } - ): string[] { - return _.chain(Object.keys(args)) - .reduce( - (acc: Array, key: string): Array => - mappings[key] === DEFAULT_ARG - ? convertValue(mappings[key], args[key]).concat(acc) - : acc.concat(convertValue(mappings[key], args[key])), - [] - ) - .value(); - } -} - -export default new Arguments(); diff --git a/src/spawn/index.ts b/src/spawn/index.ts deleted file mode 100644 index dab54344..00000000 --- a/src/spawn/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -import spawn from './spawn'; - -export * from './arguments'; - -export default spawn; diff --git a/src/spawn/spawn.ts b/src/spawn/spawn.ts deleted file mode 100644 index 790fbace..00000000 --- a/src/spawn/spawn.ts +++ /dev/null @@ -1,87 +0,0 @@ -import spawn = require('cross-spawn'); -import cp = require('child_process'); -import { ChildProcess, SpawnOptions } from 'child_process'; -import * as path from 'path'; -import logger from '../logger'; -import pactEnvironment from '../pact-environment'; -import argsHelper, { SpawnArguments, DEFAULT_ARG } from './arguments'; - -export class Spawn { - public get cwd(): string { - return path.resolve(__dirname, '..'); - } - - public spawnBinary( - command: string, - args: SpawnArguments = {}, - argMapping: { [id: string]: string } = {} - ): ChildProcess { - const envVars = JSON.parse(JSON.stringify(process.env)); // Create copy of environment variables - - envVars.PACT_EXECUTING_LANGUAGE = 'node.js'; - envVars.PACT_EXECUTING_LANGUAGE_VERSION = process.versions.node; - - // 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; - - const opts: SpawnOptions = { - cwd: pactEnvironment.cwd, - detached: !pactEnvironment.isWindows(), - env: envVars, - }; - - const spawnArgs: string[] = argsHelper.toArgumentsArray(args, { - cliVerb: DEFAULT_ARG, - ...argMapping, - }); - - logger.debug( - `Starting pact binary '${command}', with arguments [${spawnArgs.join( - ' ' - )}]` - ); - logger.trace(`Environment: ${JSON.stringify(opts)}`); - const instance = spawn(command, spawnArgs, opts); - - if (instance.stderr && instance.stdout) { - instance.stdout.on('data', logger.debug.bind(logger)); - instance.stdout.setEncoding('utf8'); - instance.stderr.setEncoding('utf8'); - 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.debug(`Created '${command}' process with PID: ${instance.pid}`); - return instance; - } - - public killBinary(binary?: ChildProcess): boolean { - if (binary) { - const { pid } = binary; - logger.info(`Removing Pact process with PID: ${pid}`); - binary.removeAllListeners(); - // Killing instance, since windows can't send signals, must kill process forcefully - try { - if (pid) { - if (pactEnvironment.isWindows()) { - cp.execSync(`taskkill /f /t /pid ${pid}`); - } else { - process.kill(-pid, 'SIGINT'); - } - } - } catch (e) { - return false; - } - } - return true; - } -} - -export default new Spawn(); diff --git a/src/stub.spec.ts b/src/stub.spec.ts deleted file mode 100644 index dbb52b3e..00000000 --- a/src/stub.spec.ts +++ /dev/null @@ -1,230 +0,0 @@ -import * as chai from 'chai'; -import chaiAsPromised from 'chai-as-promised'; -import * as fs from 'fs'; -import * as path from 'path'; -import stubFactory from './stub'; - -chai.use(chaiAsPromised); -const { expect } = chai; - -describe('Stub Spec', () => { - let stub: any; - const validDefaults = { - pactUrls: [ - path.resolve(__dirname, '../test/integration/me-they-success.json'), - ], - }; - - afterEach(() => - stub - ? stub.delete().then(() => { - stub = null; - return stub; - }) - : null - ); - - describe('Start stub', () => { - context('when invalid options are set', () => { - it('should fail if custom ssl certs do not exist', () => { - expect(() => - stubFactory({ - ssl: true, - sslcert: 'does/not/exist', - sslkey: path.resolve(__dirname, '../test/ssl/server.key'), - }) - ).to.throw(Error); - }); - - it('should fail if custom ssl keys do not exist', () => { - expect(() => - stubFactory({ - ssl: true, - sslcert: path.resolve(__dirname, '../test/ssl/server.crt'), - sslkey: 'does/not/exist', - }) - ).to.throw(Error); - }); - - it("should fail if custom ssl cert is set, but ssl key isn't", () => { - expect(() => - stubFactory({ - ssl: true, - sslcert: path.resolve(__dirname, '../test/ssl/server.crt'), - }) - ).to.throw(Error); - }); - - it("should fail if custom ssl key is set, but ssl cert isn't", () => { - expect(() => - stubFactory({ - ssl: true, - sslkey: path.resolve(__dirname, '../test/ssl/server.key'), - }) - ).to.throw(Error); - }); - }); - - context('when valid options are set', () => { - let dirPath: string; - - beforeEach(() => { - dirPath = path.resolve( - __dirname, - `../.tmp/${Math.floor(Math.random() * 1000)}` - ); - }); - - afterEach(() => { - try { - if (fs.statSync(dirPath).isDirectory()) { - fs.rmdirSync(dirPath); - } - } catch (e) { - /* any errors here are not a failed test */ - } - }); - - it('should start correctly when instance is delayed', () => { - stub = stubFactory(validDefaults); - - const waitForStubUp = stub.__waitForServiceUp.bind(stub); - return Promise.all([ - waitForStubUp(stub.options), - new Promise((resolve) => { - setTimeout(resolve, 5000); - }).then(() => stub.start()), - ]); - }); - - it('should start correctly with valid pact URLs', () => { - stub = stubFactory(validDefaults); - return expect(stub.start()).to.eventually.be.fulfilled; - }); - - it('should start correctly with valid pact URLs with spaces in it', () => { - stub = stubFactory({ - pactUrls: [ - path.resolve( - __dirname, - '../test/integration/me-they-weird path-success.json' - ), - ], - }); - return expect(stub.start()).to.eventually.be.fulfilled; - }); - - it('should start correctly with ssl', () => { - stub = stubFactory({ ...validDefaults, ssl: true }); - expect(stub.options.ssl).to.equal(true); - return expect(stub.start()).to.eventually.be.fulfilled; - }); - - it('should start correctly with custom ssl cert/key', () => { - stub = stubFactory({ - ...validDefaults, - ssl: true, - sslcert: path.resolve(__dirname, '../test/ssl/server.crt'), - sslkey: path.resolve(__dirname, '../test/ssl/server.key'), - }); - expect(stub.options.ssl).to.equal(true); - return expect(stub.start()).to.eventually.be.fulfilled; - }); - - it('should start correctly with custom ssl cert/key but without specifying ssl flag', () => { - stub = stubFactory({ - ...validDefaults, - sslcert: path.resolve(__dirname, '../test/ssl/server.crt'), - sslkey: path.resolve(__dirname, '../test/ssl/server.key'), - }); - - expect(stub.options.ssl).to.equal(true); - return expect(stub.start()).to.eventually.be.fulfilled; - }); - - it('should start correctly with cors', () => { - stub = stubFactory({ ...validDefaults, cors: true }); - expect(stub.options.cors).to.equal(true); - return expect(stub.start()).to.eventually.be.fulfilled; - }); - - it('should start correctly with port', () => { - const port = Math.floor(Math.random() * 999) + 9000; - stub = stubFactory({ ...validDefaults, port }); - expect(stub.options.port).to.equal(port); - return expect(stub.start()).to.eventually.be.fulfilled; - }); - - it('should start correctly with host', () => { - const host = 'localhost'; - stub = stubFactory({ ...validDefaults, host }); - expect(stub.options.host).to.equal(host); - return expect(stub.start()).to.eventually.be.fulfilled; - }); - - it('should start correctly with log', () => { - const logPath = path.resolve(dirPath, 'log.txt'); - stub = stubFactory({ ...validDefaults, log: logPath }); - expect(stub.options.log).to.equal(logPath); - return expect(stub.start()).to.eventually.be.fulfilled; - }); - }); - - it('should dispatch event when starting', (done) => { - stub = stubFactory(validDefaults); - stub.once('start', () => done()); - stub.start(); - }); - - it('should change running state to true', () => { - stub = stubFactory(validDefaults); - return stub.start().then(() => expect(stub.__running).to.be.true); - }); - }); - - describe('Stop stub', () => { - context('when already started', () => { - it('should stop running', () => { - stub = stubFactory(validDefaults); - return stub.start().then(() => stub.stop()); - }); - - it('should dispatch event when stopping', (done) => { - stub = stubFactory(validDefaults); - stub.once('stop', () => done()); - stub.start().then(() => stub.stop()); - }); - - it('should change running state to false', () => { - stub = stubFactory(validDefaults); - return stub - .start() - .then(() => stub.stop()) - .then(() => expect(stub.__running).to.be.false); - }); - }); - }); - - describe('Delete stub', () => { - context('when already running', () => { - it('should stop & delete stub', () => { - stub = stubFactory(validDefaults); - return stub.start().then(() => stub.delete()); - }); - - it('should dispatch event when deleting', (done) => { - stub = stubFactory(validDefaults); - stub.once('delete', () => done()); - stub.start().then(() => stub.delete()); - }); - - it('should change running state to false', () => { - stub = stubFactory(validDefaults); - return stub - .start() - .then(() => stub.delete()) - .then(() => expect(stub.__running).to.be.false); - }); - }); - }); -}); diff --git a/src/stub.ts b/src/stub.ts deleted file mode 100644 index 833382dd..00000000 --- a/src/stub.ts +++ /dev/null @@ -1,50 +0,0 @@ -import checkTypes = require('check-types'); -import { DEFAULT_ARG } from './spawn'; -import { AbstractService } from './service'; - -import pact from './pact-standalone'; -import { LogLevel } from './logger/types'; - -export class Stub extends AbstractService { - public readonly options: StubOptions; - - constructor(passedOptions: StubOptions = {}) { - const options = { ...passedOptions }; - options.pactUrls = options.pactUrls || []; - - if (options.pactUrls) { - checkTypes.assert.array.of.string(options.pactUrls); - } - - checkTypes.assert.not.emptyArray(options.pactUrls); - - super(pact.stubFullPath, options, { - pactUrls: DEFAULT_ARG, - port: '--port', - host: '--host', - log: '--log', - logLevel: '--log-level', - ssl: '--ssl', - sslcert: '--sslcert', - sslkey: '--sslkey', - cors: '--cors', - }); - this.options = options; - } -} - -// Creates a new instance of the pact stub with the specified option -export default (options?: StubOptions): Stub => new Stub(options); - -export interface StubOptions { - pactUrls?: string[]; - port?: number; - ssl?: boolean; - cors?: boolean; - host?: string; - sslcert?: string; - sslkey?: string; - log?: string; - logLevel?: LogLevel; - timeout?: number; -} diff --git a/src/types.ts b/src/types.ts index db4b50af..80061004 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,41 +1,4 @@ import needle from 'needle'; -import { LogLevel } from './logger/types'; - -export interface MessageOptions { - content?: string; - dir?: string; - consumer?: string; - provider?: string; - pactFileWriteMode?: 'overwrite' | 'update' | 'merge'; - spec?: number; -} - -export interface PublisherOptions { - pactFilesOrDirs: string[]; - pactBroker: string; - consumerVersion: string; - pactBrokerUsername?: string; - pactBrokerPassword?: string; - pactBrokerToken?: string; - tags?: string[]; - verbose?: boolean; - timeout?: number; - buildUrl?: string; - branch?: string; - autoDetectVersionProperties?: boolean; -} - -export interface ServiceOptions { - port?: number; - ssl?: boolean; - cors?: boolean; - host?: string; - sslcert?: string; - sslkey?: string; - log?: string; - logLevel?: LogLevel; - timeout?: number; -} export interface HTTPConfig extends Omit { headers: { diff --git a/test/publisher.integration.spec.ts b/test/publisher.integration.spec.ts deleted file mode 100644 index c9efb13e..00000000 --- a/test/publisher.integration.spec.ts +++ /dev/null @@ -1,179 +0,0 @@ -import path = require('path'); -import chai = require('chai'); -import chaiAsPromised = require('chai-as-promised'); -import * as http from 'http'; -import logger from '../src/logger'; -import publisherFactory from '../src/publisher'; -import brokerMock from './integration/broker-mock'; - -const { expect } = chai; -chai.use(chaiAsPromised); - -describe('Publish Spec', () => { - let server: http.Server; - const PORT = Math.floor(Math.random() * 999) + 9000; - const pactBrokerBaseUrl = `http://localhost:${PORT}`; - const authenticatedPactBrokerBaseUrl = `${pactBrokerBaseUrl}/auth`; - - before(() => - brokerMock(PORT).then((s) => { - logger.debug(`Pact Broker Mock listening on port: ${PORT}`); - server = s; - }) - ); - - after(() => server.close()); - - context('when publishing a to a broker', () => { - context('without authentication', () => { - context('and the Pact contract is valid', () => { - it('should successfully publish all Pacts', () => { - const publisher = publisherFactory({ - pactBroker: pactBrokerBaseUrl, - pactFilesOrDirs: [ - path.resolve( - __dirname, - 'integration/publish/publish-success.json' - ), - ], - consumerVersion: '1.0.0', - buildUrl: 'http://ci/build/1', - }); - - expect(publisher).to.be.a('object'); - expect(publisher).to.respondTo('publish'); - return expect(publisher.publish()).to.eventually.be.fulfilled; - }); - - it('should successfully tag all Pacts with `test` and `latest`', () => { - const publisher = publisherFactory({ - pactBroker: pactBrokerBaseUrl, - pactFilesOrDirs: [ - path.resolve( - __dirname, - 'integration/publish/publish-success.json' - ), - ], - consumerVersion: '1.0.0', - tags: ['test', 'latest'], - }); - - expect(publisher).to.be.a('object'); - expect(publisher).to.respondTo('publish'); - return expect(publisher.publish()).to.eventually.be.fulfilled; - }); - }); - context('and the Pact contract is invalid', () => { - it('should report an unsuccessful push', () => { - const publisher = publisherFactory({ - pactBroker: pactBrokerBaseUrl, - pactFilesOrDirs: [ - path.resolve(__dirname, 'integration/publish/publish-fail.json'), - ], - consumerVersion: '1.0.0', - }); - - expect(publisher).to.be.a('object'); - expect(publisher).to.respondTo('publish'); - return expect(publisher.publish()).to.eventually.be.rejected; - }); - }); - }); - - context('with authentication', () => { - context('and valid credentials', () => { - it('should return a sucessful promise', () => { - const publisher = publisherFactory({ - pactBroker: authenticatedPactBrokerBaseUrl, - pactFilesOrDirs: [ - path.resolve( - __dirname, - 'integration/publish/publish-success.json' - ), - ], - consumerVersion: '1.0.0', - pactBrokerUsername: 'foo', - pactBrokerPassword: 'bar', - }); - - expect(publisher).to.be.a('object'); - expect(publisher).to.respondTo('publish'); - return expect(publisher.publish()).to.eventually.be.fulfilled; - }); - }); - - context('and invalid credentials', () => { - it('should return a rejected promise', () => { - const publisher = publisherFactory({ - pactBroker: authenticatedPactBrokerBaseUrl, - pactFilesOrDirs: [ - path.resolve( - __dirname, - 'integration/publish/publish-success.json' - ), - ], - consumerVersion: '1.0.0', - pactBrokerUsername: 'not', - pactBrokerPassword: 'right', - }); - - expect(publisher).to.be.a('object'); - expect(publisher).to.respondTo('publish'); - return expect(publisher.publish()).to.eventually.be.rejected; - }); - }); - }); - }); - - context('when publishing a directory of Pacts to a Broker', () => { - context('and the directory contains only valid Pact contracts', () => { - it('should asynchronously send all Pacts to the Broker', () => { - const publisher = publisherFactory({ - pactBroker: pactBrokerBaseUrl, - pactFilesOrDirs: [ - path.resolve(__dirname, 'integration/publish/pactDirTests'), - ], - consumerVersion: '1.0.0', - }); - - expect(publisher).to.be.a('object'); - expect(publisher).to.respondTo('publish'); - return expect(publisher.publish()).to.eventually.be.fulfilled; - }); - - it('should successfully tag all Pacts sent with `test` and `latest`', () => { - const publisher = publisherFactory({ - pactBroker: pactBrokerBaseUrl, - pactFilesOrDirs: [ - path.resolve(__dirname, 'integration/publish/pactDirTests'), - ], - consumerVersion: '1.0.0', - tags: ['test', 'latest'], - }); - - expect(publisher).to.be.a('object'); - expect(publisher).to.respondTo('publish'); - return expect(publisher.publish()).to.eventually.be.fulfilled; - }); - }); - - context('and the directory contains Pact and non-Pact contracts', () => { - it('should asynchronously send only the Pact contracts to the broker', () => { - const publisher = publisherFactory({ - pactBroker: pactBrokerBaseUrl, - pactFilesOrDirs: [ - path.resolve( - __dirname, - 'integration/publish/pactDirTestsWithOtherStuff' - ), - ], - consumerVersion: '1.0.0', - }); - - expect(publisher).to.be.a('object'); - expect(publisher).to.respondTo('publish'); - return expect(publisher.publish()).to.eventually.be.fulfilled; - }); - }); - }); -});