From 95b26c44bf3e26f1e5150c8e1fdab76da2465b8c Mon Sep 17 00:00:00 2001 From: Matt Fellows Date: Wed, 6 Dec 2017 08:43:02 +1100 Subject: [PATCH] feat(pact-web): refactor PactWeb module - Adds simple test suite - Shares Options module with Pact BREAKING CHANGE: Requires constructor to create now. --- README.md | 4 +- src/pact-web.spec.ts | 222 +++++++++++++++++++++++++++++++++++++++++++ src/pact-web.ts | 18 ++-- src/pact.spec.ts | 2 +- src/pact.ts | 1 - 5 files changed, 231 insertions(+), 16 deletions(-) create mode 100644 src/pact-web.spec.ts diff --git a/README.md b/README.md index 12493349b..26a16de4b 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ how to get going. It's easy, simply run the below: ``` -npm install --save-dev pact +npm install --save-dev @pact-foundation/pact ``` ## Using Pact JS @@ -511,7 +511,7 @@ package, so you will need to coordinate this yourself prior to and after executi To get started, install `pact-web` and [Pact Node](https://github.com/pact-foundation/pact-node): - npm install --save-dev pact-web pact-node + npm install --save-dev @pact-foundation/pact-web @pact-foundation/pact-node If you're not using Karma, you can start and stop the mock server using [Pact Node](https://github.com/pact-foundation/pact-node) or something like [Grunt Pact](https://github.com/pact-foundation/grunt-pact). diff --git a/src/pact-web.spec.ts b/src/pact-web.spec.ts new file mode 100644 index 000000000..d850c5fde --- /dev/null +++ b/src/pact-web.spec.ts @@ -0,0 +1,222 @@ +'use strict' +import { Interaction, InteractionObject } from './dsl/interaction'; +import { MockService } from './dsl/mockService'; +import { PactWeb } from './pact-web'; +import { PactOptions, PactOptionsComplete } from './dsl/options'; +import * as sinon from 'sinon'; +import * as chai from 'chai'; +import * as chaiAsPromised from 'chai-as-promised'; +import * as sinonChai from 'sinon-chai'; + +const expect = chai.expect; +const proxyquire = require('proxyquire').noCallThru(); +chai.use(sinonChai) +chai.use(chaiAsPromised) + +describe('PactWeb', () => { + const fullOpts = { + consumer: 'A', + provider: 'B', + port: 1234, + host: '127.0.0.1', + ssl: false, + logLevel: 'info', + spec: 2, + cors: false, + pactfileWriteMode: 'overwrite' + } as PactOptionsComplete; + + const sandbox = sinon.sandbox.create({ + injectInto: null, + properties: ['spy', 'stub', 'mock'], + useFakeTimers: false, + useFakeServer: false + }); + + afterEach(() => { + sandbox.restore(); + }); + + describe('#constructor', () => { + const defaults = { + consumer: 'A', + provider: 'B' + } as PactOptions; + + it('throws Error when consumer not provided', () => { + expect(() => { new PactWeb({ 'consumer': '', 'provider': 'provider' }) }). + not.to.throw(Error, 'You must specify a Consumer for this pact.'); + }); + + it('throws Error when provider not provided', () => { + expect(() => { new PactWeb({ 'consumer': 'someconsumer', 'provider': '' }) }). + not.to.throw(Error, 'You must specify a Provider for this pact.'); + }); + }); + + + describe('#addInteraction', () => { + const interaction: InteractionObject = { + state: 'i have a list of projects', + uponReceiving: 'a request for projects', + withRequest: { + method: 'GET', + path: '/projects', + headers: { 'Accept': 'application/json' } + }, + willRespondWith: { + status: 200, + headers: { 'Content-Type': 'application/json' }, + body: {} + } + }; + + describe('when given a provider state', () => { + it('creates interaction with state', (done) => { + const pact = Object.create(PactWeb.prototype); + pact.opts = fullOpts; + pact.mockService = { + addInteraction: (int: InteractionObject): Promise => Promise.resolve(int.state) + }; + expect(pact.addInteraction(interaction)).to.eventually.have.property('providerState').notify(done) + }); + }); + + describe('when not given a provider state', () => { + it('creates interaction with state', (done) => { + const pact = Object.create(PactWeb.prototype); + pact.opts = fullOpts; + pact.mockService = { + addInteraction: (int: InteractionObject): Promise => Promise.resolve(int.state) + }; + + const interactionWithNoState = interaction; + interactionWithNoState.state = undefined; + expect(pact.addInteraction(interaction)).to.eventually.not.have.property('providerState').notify(done) + }); + }); + }); + + describe('#verify', () => { + const Pact = PactWeb; + + describe('when pact verification is successful', () => { + it('should return a successful promise and remove interactions', (done) => { + const verifyStub = sandbox.stub(MockService.prototype, 'verify'); + verifyStub.resolves('verified!'); + const removeInteractionsStub = sandbox.stub(MockService.prototype, 'removeInteractions'); + removeInteractionsStub.resolves('removeInteractions'); + + const b = Object.create(PactWeb.prototype); + b.opts = fullOpts; + b.mockService = { verify: verifyStub, removeInteractions: removeInteractionsStub }; + + const verifyPromise = b.verify(); + expect(verifyPromise).to.eventually.eq('removeInteractions'); + expect(verifyPromise).to.eventually.be.fulfilled.notify(done); + }); + }); + + describe('when pact verification is unsuccessful', () => { + it('should throw an error', (done) => { + const verifyStub = sandbox.stub(MockService.prototype, 'verify'); + verifyStub.rejects('not verified!'); + const removeInteractionsStub = sandbox.stub(MockService.prototype, 'removeInteractions'); + removeInteractionsStub.resolves('removeInteractions'); + + const b = Object.create(PactWeb.prototype); + b.opts = fullOpts; + b.mockService = { verify: verifyStub, removeInteractions: removeInteractionsStub }; + + const verifyPromise = b.verify(); + expect(verifyPromise).to.eventually.be.rejectedWith(Error).notify(done) + verifyPromise.catch((e) => { + expect(removeInteractionsStub).to.callCount(0); + }); + }); + }); + + describe('when pact verification is successful', () => { + describe('and an error is thrown in the cleanup', () => { + it('should throw an error', (done) => { + const verifyStub = sandbox.stub(MockService.prototype, 'verify'); + verifyStub.resolves('verified!'); + const removeInteractionsStub = sandbox.stub(MockService.prototype, 'removeInteractions'); + removeInteractionsStub.throws(new Error('error removing interactions')); + + const b = Object.create(PactWeb.prototype); + b.opts = fullOpts; + b.mockService = { verify: verifyStub, removeInteractions: removeInteractionsStub }; + + expect(b.verify()).to.eventually.be.rejectedWith(Error).notify(done) + }); + }); + }); + }); + + describe('#finalize', () => { + const Pact = PactWeb; + + describe('when writing Pact is successful', () => { + it('should return a successful promise and shut down down the mock server', (done) => { + const writePactStub = sandbox.stub(MockService.prototype, 'writePact').resolves('pact file written!'); + + const p = Object.create(PactWeb.prototype); + p.opts = fullOpts; + p.mockService = { writePact: writePactStub, removeInteractions: sandbox.stub() }; + + const writePactPromise = p.finalize(); + expect(writePactPromise).to.eventually.be.fulfilled.notify(done); + }); + }); + + describe('when writing Pact is unsuccessful', () => { + it('should throw an error', (done) => { + const writePactStub = sandbox.stub(MockService.prototype, 'writePact').rejects('pact not file written!'); + + const p = Object.create(PactWeb.prototype); + p.opts = fullOpts; + p.mockService = { writePact: writePactStub, removeInteractions: sandbox.stub() }; + + const writePactPromise = p.finalize(); + expect(writePactPromise).to.eventually.be.rejectedWith(Error).notify(done); + }); + }); + }); + + describe('#writePact', () => { + const Pact = PactWeb; + + describe('when writing Pact is successful', () => { + it('should return a successful promise', (done) => { + const writePactStub = sandbox.stub(MockService.prototype, 'writePact').resolves('pact file written!'); + + const p = Object.create(PactWeb.prototype); + p.opts = fullOpts; + p.mockService = { writePact: writePactStub, removeInteractions: sandbox.stub() }; + + const writePactPromise = p.writePact(); + expect(writePactPromise).to.eventually.eq('pact file written!'); + expect(writePactPromise).to.eventually.be.fulfilled.notify(done); + }); + }); + }); + + describe('#removeInteractions', () => { + const Pact = PactWeb; + + describe('when removing interactions is successful', () => { + it('should return a successful promise', (done) => { + const removeInteractionsStub = sandbox.stub(MockService.prototype, 'removeInteractions').resolves('interactions removed!'); + + const p = Object.create(PactWeb.prototype); + p.opts = fullOpts; + p.mockService = { removeInteractions: removeInteractionsStub }; + + const removeInteractionsPromise = p.removeInteractions(); + expect(removeInteractionsPromise).to.eventually.eq('interactions removed!'); + expect(removeInteractionsPromise).to.eventually.be.fulfilled.notify(done); + }); + }); + }); +}); diff --git a/src/pact-web.ts b/src/pact-web.ts index 66e88ee2c..1410f862f 100644 --- a/src/pact-web.ts +++ b/src/pact-web.ts @@ -11,23 +11,17 @@ import { PactOptions, PactOptionsComplete } from './dsl/options'; polyfill(); /** - * Creates a new {@link PactProvider}. + * Creates a new {@link PactWeb}. * @memberof Pact * @name create - * @param {Object} opts - * @param {string} opts.consumer - the name of the consumer - * @param {string} opts.provider - the name of the provider - * @param {number} opts.port - port of the mock service, defaults to 1234 - * @param {string} opts.host - host address of the mock service, defaults to 127.0.0.1 - * @param {boolean} opts.ssl - SSL flag to identify the protocol to be used (default false, HTTP) - * @param {string} pactfileWriteMode - 'overwrite' | 'update' | 'smart' | 'none', defaults to 'overwrite' - * @return {@link PactProvider} + * @param {PactOptions} opts + * @return {@link PactWeb} * @static */ export class PactWeb { - private mockService: MockService; - private server: any; - private opts: PactOptionsComplete; + public mockService: MockService; + public server: any; + public opts: PactOptionsComplete; constructor(config: PactOptions) { const defaults = { diff --git a/src/pact.spec.ts b/src/pact.spec.ts index c81f3177c..459125444 100644 --- a/src/pact.spec.ts +++ b/src/pact.spec.ts @@ -310,7 +310,7 @@ describe('Pact', () => { describe('#removeInteractions', () => { const Pact = PactType; - describe('when writing Pact is successful', () => { + describe('when removing interactions is successful', () => { it('should return a successful promise', (done) => { const removeInteractionsStub = sandbox.stub(MockService.prototype, 'removeInteractions').resolves('interactions removed!'); diff --git a/src/pact.ts b/src/pact.ts index 6c3e39b45..19407a463 100644 --- a/src/pact.ts +++ b/src/pact.ts @@ -22,7 +22,6 @@ import serviceFactory from '@pact-foundation/pact-node'; * @name create * @param {PactOptions} opts * @return {@link PactProvider} - * @static */ export class Pact { public server: any;