Giter VIP home page Giter VIP logo

pact-workshop-js-v1's Introduction

NOTE: This project is superceded by https://github.com/pact-foundation/pact-workshop-js

Example JS project for the Pact workshop

This project has 2 components, a consumer project and a service provider as an Express API.

NOTE: Each step is tied to, and must be run within, a git branch, allowing you to progress through each stage incrementally. For example, to move to step 2 run the following: git checkout step2

Step 1 - Simple Consumer calling Provider

Given we have a client that needs to make a HTTP GET request to a provider service, and requires a response in JSON format.

Simple Consumer

The consumer client is quite simple and looks like this

consumer/consumer.js:

request
  .get(`${API_ENDPOINT}/provider`)
  .query({ validDate: new Date().toISOString() })
  .then(res => {
    console.log(res.body)
  })

and the express provider resource

provider/provider.js:

server.get('/provider/:', (req, res) => {
  const date = req.query.validDate

  res.json({
    test: 'NO',
    validDate: new Date().toISOString(),
    count: 100,
  })
})

This providers expects a validDate parameter in HTTP date format, and then return some simple json back.

Sequence Diagram

Start the provider in a separate terminal:

$ node provider/provider.js
Provider Service listening on http://localhost:9123

Running the client works nicely.

$ node consumer/consumer.js
{ test: 'NO', validDate: '2017-06-12T06:25:42.392Z', count: 100 }

Step 2 - Client Tested but integration fails

Now lets separate the API client (collaborator) that uses the data it gets back from the provider into its own module. Here is the updated client method that uses the returned data:

consumer/client.js:

const fetchProviderData = () => {
  return request
    .get(`${API_ENDPOINT}/provider`)
    .query({validDate: new Date().toISOString()})
    .then((res) => {
      return {
        value: 100 / res.body.count,
        date: res.body.date
      }
    })
}

The consumer is now a lot simpler:

consumer/consumer.js:

const client = require('./client')

client.fetchProviderData().then(response => console.log(response))

Sequence 2

Let's now test our updated client.

consumer/test/consumer.spec.js:

describe('Consumer', () => {
  describe('when a call to the Provider is made', () => {
    const date = '2013-08-16T15:31:20+10:00'
    nock(API_HOST)
      .get('/provider')
      .query({validDate: /.*/})
      .reply(200, {
        test: 'NO',
        date: date,
        count: 1000
      })

    it('can process the JSON payload from the provider', done => {
      const {fetchProviderData} = require('../consumer')
      const response = fetchProviderData()

      expect(response).to.eventually.have.property('count', 1000)
      expect(response).to.eventually.have.property('date', date).notify(done)
    })
  })
})

Unit Test With Mocked Response

Let's run this spec and see it all pass:

$ npm run test:consumer

> [email protected] test:consumer /Users/mfellows/development/public/pact-workshop-js
> mocha consumer/test/consumer.spec.js



  Consumer
    when a call to the Provider is made
      ✓ can process the JSON payload from the provider


  1 passing (24ms)

However, there is a problem with this integration point. Running the actual client against any of the providers results in problem!

$ node consumer/consumer.js
{ count: 100, date: undefined }

The provider returns a validDate while the consumer is trying to use date, which will blow up when run for real even with the tests all passing. Here is where Pact comes in.

Step 3 - Pact to the rescue

Let us add Pact to the project and write a consumer pact test.

consumer/test/consumerPact.spec.js:

const provider = pact({
  consumer: 'Our Little Consumer',
  provider: 'Our Provider',
  port: API_PORT,
  log: path.resolve(process.cwd(), 'logs', 'pact.log'),
  dir: path.resolve(process.cwd(), 'pacts'),
  logLevel: LOG_LEVEL,
  spec: 2
})
const submissionDate = new Date().toISOString()
const date = '2013-08-16T15:31:20+10:00'
const expectedBody = {
  test: 'NO',
  date: date,
  count: 1000
}

describe('Pact with Our Provider', () => {
  describe('given data count > 0', () => {
    describe('when a call to the Provider is made', () => {
      before(() => {
        return provider.setup()
          .then(() => {
            provider.addInteraction({
              uponReceiving: 'a request for JSON data',
              withRequest: {
                method: 'GET',
                path: '/provider',
                query: {
                  validDate: submissionDate
                }
              },
              willRespondWith: {
                status: 200,
                headers: {
                  'Content-Type': 'application/json; charset=utf-8'
                },
                body: expectedBody
              }
            })
          })
      })

      it('can process the JSON payload from the provider', done => {
        const response = fetchProviderData(submissionDate)

        expect(response).to.eventually.have.property('count', 1000)
        expect(response).to.eventually.have.property('date', date).notify(done)
      })

      it('should validate the interactions and create a contract', () => {
        return provider.verify()
      })
    })

    // Write pact files to file
    after(() => {
      return provider.finalize()
    })
  })
})

Test using Pact

This test starts a mock server on port 1234 that pretends to be our provider. To get this to work we needed to update our consumer to pass in the URL of the provider. We also updated the fetchProviderData method to pass in the query parameter.

Running this spec still passes, but it creates a pact file which we can use to validate our assumptions on the provider side.

$ npm run "test:pact:consumer"

> [email protected] test:pact:consumer /Users/mfellows/development/public/pact-workshop-js
> mocha consumer/test/consumerPact.spec.js

  Pact with Our Provider
    when a call to the Provider is made
      when data count > 0
        ✓ can process the JSON payload from the provider
        ✓ should validate the interactions and create a contract


  2 passing (571ms)

Generated pact file (pacts/our_little_consumer-our_provider.json):

{
  "consumer": {
    "name": "Our Little Consumer"
  },
  "provider": {
    "name": "Our Provider"
  },
  "interactions": [
    {
      "description": "a request for JSON data",
      "providerState": "data count > 0",
      "request": {
        "method": "GET",
        "path": "/provider",
        "query": "validDate=2017-06-12T08%3A04%3A24.387Z"
      },
      "response": {
        "status": 200,
        "headers": {
          "Content-Type": "application/json; charset=utf-8"
        },
        "body": {
          "test": "NO",
          "date": "2013-08-16T15:31:20+10:00",
          "count": 1000
        }
      }
    }
  ],
  "metadata": {
    "pactSpecification": {
      "version": "2.0.0"
    }
  }
}

Step 4 - Verify the provider

Pact Verification

We now need to validate the pact generated by the consumer is valid, by executing it against the running service provider, which should fail:

npm run test:pact:provider

> [email protected] test:pact:provider /Users/mfellows/development/public/pact-workshop-js
> mocha provider/test/providerPact.spec.js



Provider Service listening on http://localhost:8081
  Pact Verification
    1) should validate the expectations of Our Little Consumer


  0 passing (538ms)
  1 failing

  1) Pact Verification should validate the expectations of Our Little Consumer:
     Error: Reading pact at pacts/our_little_consumer-our_provider.json

Verifying a pact between Our Little Consumer and Our Provider
  A request for json data
    with GET /provider?validDate=2017-06-12T08%3A29%3A09.261Z
      returns a response which
        has status code 200
        has a matching body (FAILED - 1)
        includes headers
          "Content-Type" with value "application/json; charset=utf-8"

Failures:

  1) Verifying a pact between Our Little Consumer and Our Provider A request for json data with GET /provider?validDate=2017-06-12T08%3A29%3A09.261Z returns a response which has a matching body
     Failure/Error: expect(response_body).to match_term expected_response_body, diff_options

       Actual: {"test":"NO","validDate":"2017-06-12T08:31:42.460Z","count":100}

       @@ -1,4 +1,3 @@
        {
       -  "date": "2013-08-16T15:31:20+10:00",
       -  "count": 1000
       +  "count": 100
        }

       Key: - means "expected, but was not found".
            + means "actual, should not be found".
            Values where the expected matches the actual are not shown.

1 interaction, 1 failure

The test has failed for 2 reasons. Firstly, the count field has a different value to what was expected by the consumer.

Secondly, and more importantly, the consumer was expecting a date field while the provider generates a validDate field. Also, the date formats are different.

NOTE: We have separated the API provider into two components: one that provides a testable API and the other to start the actual service for local testing. You should now start the provider as follows:

node provider/providerService.js

Step 5

Intentionally blank to align with the JVM workshop steps

Step 6 - Back to the client we go

Let's correct the consumer test to handle any integer for count and use the correct field for the date. Then we need to add a type matcher for count and change the field for the date to be validDate.

We can also add a date regular expression to make sure the validDate field is a valid date. This is important because we are parsing it.

The updated consumer test is now:

const { somethingLike: like, term } = pact.Matchers

describe('Pact with Our Provider', () => {
  describe('given data count > 0', () => {
    describe('when a call to the Provider is made', () => {
      before(() => {
        return provider.setup()
          .then(() => {
            provider.addInteraction({
              uponReceiving: 'a request for JSON data',
              withRequest: {
                method: 'GET',
                path: '/provider',
                query: {
                  validDate: submissionDate
                }
              },
              willRespondWith: {
                status: 200,
                headers: {
                  'Content-Type': 'application/json; charset=utf-8'
                },
                body: {
                  test: 'NO',
                  validDate: term({generate: date, matcher: '\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\+\\d{2}:\\d{2}'}),
                  count: like(100)
                }
              }
            })
          })
      })
...
})

Running this test will fail until we fix the client. Here is the correct client function, which parses the date and formats it correctly:

const fetchProviderData = (submissionDate) => {
  return request
    .get(`${API_ENDPOINT}/provider`)
    .query({validDate: submissionDate})
    .then((res) => {
      // Validate date
      if (res.body.validDate.match(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\+\d{2}:\d{2}/)) {
        return {
          count: res.body.count,
          date: moment(res.body.validDate, moment.ISO_8601).format('YYYY-MM-DDTHH:mm:ssZ')
        }
      } else {
        throw new Error('Invalid date format in response')
      }
    })
}

Now the test passes. But we still have a problem with the date format, which we must fix in the provider. Running the client now fails because of that.

$ node consumer/consumer.js
Error: Invalid date format in response
    at request.get.query.then (consumer/client.js:20:15)
    at process._tickCallback (internal/process/next_tick.js:103:7)

Step 7 - Verify the providers again

We need to run 'npm run test:pact:consumer' to publish the consumer pact file again. Then, running the provider verification tests we get the expected failure about the date format.

Failures:

...

@@ -1,4 +1,4 @@
{
-  "validDate": /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\+\d{2}:\d{2}/
+  "validDate": "2017-06-12T10:28:12.904Z"
}

Lets fix the provider and then re-run the verification tests. Here is the corrected /provider resource:

server.get('/provider', (req, res) => {
  const date = req.query.validDate

  res.json(
    {
      'test': 'NO',
      'validDate': moment(new Date(), moment.ISO_8601).format('YYYY-MM-DDTHH:mm:ssZ'),
      'count': 100
    }
  )
})

Verification Passes

Running the verification against the providers now pass. Yay!

$ npm run test:pact:provider

> [email protected] test:pact:provider /Users/mfellows/development/public/pact-workshop-js
> mocha provider/test/providerPact.spec.js



Provider Service listening on http://localhost:8081
  Pact Verification
Pact Verification Complete!
Reading pact at pacts/our_little_consumer-our_provider.json

Verifying a pact between Our Little Consumer and Our Provider
  A request for json data
    with GET /provider?validDate=2017-06-12T10%3A39%3A01.793Z
      returns a response which
        has status code 200
        has a matching body
        includes headers
          "Content-Type" with value "application/json; charset=utf-8"

1 interaction, 0 failures



    ✓ should validate the expectations of Our Little Consumer (498ms)


  1 passing (503ms)

Our consumer also now works:

$ node consumer/consumer.js
{ count: 100, date: '2017-06-12T20:40:51+10:00' }

Step 8 - Test for the missing query parameter

In this step we are going to add a test for the case where the query parameter is missing or invalid. We do this by adding additional tests and expectations to the consumer pact test. Our client code needs to be modified slightly to be able to pass invalid dates in, and if the date parameter is null, don't include it in the request.

Here are the two additional tests:

consumer/test/consumerPact.spec.js:

describe('and an invalid date is provided', () => {
  before(() => {
    return provider.addInteraction({
      uponReceiving: 'a request with an invalid date parameter',
      withRequest: {
        method: 'GET',
        path: '/provider',
        query: {
          validDate: 'This is not a date'
        }
      },
      willRespondWith: {
        status: 400,
        headers: {
          'Content-Type': 'application/json; charset=utf-8'
        },
        body: {'error': '"\'This is not a date\' is not a date"'}
      }
    })
  })

  it('can handle an invalid date parameter', (done) => {
    expect(fetchProviderData('This is not a date')).to.eventually.be.rejectedWith(Error).notify(done)
  })

  it('should validate the interactions and create a contract', () => {
    return provider.verify()
  })
})

describe('and no date is provided', () => {
  before(() => {
    return provider.addInteraction({
      uponReceiving: 'a request with a missing date parameter',
      withRequest: {
        method: 'GET',
        path: '/provider',
      },
      willRespondWith: {
        status: 400,
        headers: {
          'Content-Type': 'application/json; charset=utf-8'
        },
        body: {'error': '"validDate is required"'}
      }
    })
  })

  it('can handle missing date parameter', (done) => {
    expect(fetchProviderData(null)).to.eventually.be.rejectedWith(Error).notify(done)
  })

  it('should validate the interactions and create a contract', () => {
    return provider.verify()
  })
})

After running our specs, the pact file will have 2 new interactions.

pacts/our_little_consumer-our_provider.json:

[
  {
    "description": "a request with an invalid date parameter",
    "request": {
      "method": "GET",
      "path": "/provider",
      "query": "validDate=This+is+not+a+date"
    },
    "response": {
      "status": 400,
      "headers": {
        "Content-Type": "application/json; charset=utf-8"
      },
      "body": {
        "error": "'This is not a date' is not a date"
      }
    }
  },
  {
    "description": "a request with a missing date parameter",
    "request": {
      "method": "GET",
      "path": "/provider"
    },
    "response": {
      "status": 400,
      "headers": {
        "Content-Type": "application/json; charset=utf-8"
      },
      "body": {
        "error": "validDate is required"
      }
    }
  }
]

Step 9 - Verify the provider with the missing/invalid date query parameter

Let us run this updated pact file with Our Providers. We still get a 200 response as the provider doesn't yet do anything useful with the date.

Here is the provider test output:

$ npm run test:pact:provider

> [email protected] test:pact:provider /Users/mfellows/development/public/pact-workshop-js
> mocha provider/test/providerPact.spec.js



Provider Service listening on http://localhost:8081
  Pact Verification
    1) should validate the expectations of Our Little Consumer


  0 passing (590ms)
  1 failing

  1) Pact Verification should validate the expectations of Our Little Consumer:
     Error: Reading pact at pacts/our_little_consumer-our_provider.json

Verifying a pact between Our Little Consumer and Our Provider
  A request for json data
    with GET /provider?validDate=2017-06-12T12%3A47%3A18.793Z
      returns a response which
        has status code 200
        has a matching body
        includes headers
          "Content-Type" with value "application/json; charset=utf-8"
  A request with an invalid date parameter
    with GET /provider?validDate=This+is+not+a+date
      returns a response which
        has status code 400 (FAILED - 1)
        has a matching body (FAILED - 2)
        includes headers
          "Content-Type" with value "application/json; charset=utf-8"
  A request with a missing date parameter
    with GET /provider
      returns a response which
        has status code 400 (FAILED - 3)
        has a matching body (FAILED - 4)
        includes headers
          "Content-Type" with value "application/json; charset=utf-8"

Failures:

  1) Verifying a pact between Our Little Consumer and Our Provider A request with an invalid date parameter with GET /provider?validDate=This+is+not+a+date returns a response which has status code 400
     Failure/Error: expect(response_status).to eql expected_response_status

       expected: 400
            got: 200

       (compared using eql?)

  2) Verifying a pact between Our Little Consumer and Our Provider A request with an invalid date parameter with GET /provider?validDate=This+is+not+a+date returns a response which has a matching body
     Failure/Error: expect(response_body).to match_term expected_response_body, diff_options

       Actual: {"test":"NO","validDate":"2017-06-12T22:47:22+10:00","count":100}

       @@ -1,4 +1,3 @@
        {
       -  "error": "'This is not a date' is not a date"
        }

       Key: - means "expected, but was not found".
            + means "actual, should not be found".
            Values where the expected matches the actual are not shown.

  3) Verifying a pact between Our Little Consumer and Our Provider A request with a missing date parameter with GET /provider returns a response which has status code 400
     Failure/Error: expect(response_status).to eql expected_response_status

       expected: 400
            got: 200

       (compared using eql?)

  4) Verifying a pact between Our Little Consumer and Our Provider A request with a missing date parameter with GET /provider returns a response which has a matching body
     Failure/Error: expect(response_body).to match_term expected_response_body, diff_options

       Actual: {"test":"NO","validDate":"2017-06-12T22:47:22+10:00","count":100}

       @@ -1,4 +1,3 @@
        {
       -  "error": "validDate is required"
        }

       Key: - means "expected, but was not found".
            + means "actual, should not be found".
            Values where the expected matches the actual are not shown.

3 interactions, 2 failures

Time to update the providers to handle these cases.

Step 10 - Update the providers to handle the missing/invalid query parameters

Let's fix Our Provider so it generate the correct responses for the query parameters.

The API resource gets updated to check if the parameter has been passed, and handle a date parse Error if it is invalid. Two new Errors are thrown for these cases.

server.get('/provider', (req, res) => {
  const validDate = req.query.validDate
  const dateRegex = /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\+\d{2}:\d{2}/

  if (!validDate) {
    res.status(400)
    res.json({error: 'validDate is required'});
  } else if (!moment(validDate, moment.ISO_8601).isValid()) {
    res.status(400)
    res.json({error: `'${validDate}' is not a date`})
  }  else {
    res.json({
      'test': 'NO',
      'validDate': moment(new Date(), moment.ISO_8601).format('YYYY-MM-DDTHH:mm:ssZ'),
      'count': 100
    })
  }
})

Now running the npm run test:pact:provider will pass.

$ npm run test:pact:provider

> [email protected] test:pact:provider /Users/mfellows/development/public/pact-workshop-js
> mocha provider/test/providerPact.spec.js



Provider Service listening on http://localhost:8081
  Pact Verification
Pact Verification Complete!
Reading pact at pacts/our_little_consumer-our_provider.json

Verifying a pact between Our Little Consumer and Our Provider
  A request for json data
    with GET /provider?validDate=2017-06-12T12%3A35%3A19.522Z
      returns a response which
        has status code 200
        has a matching body
        includes headers
          "Content-Type" with value "application/json; charset=utf-8"
  A request with an invalid date parameter
    with GET /provider?validDate=This+is+not+a+date
      returns a response which
        has status code 400
        has a matching body
        includes headers
          "Content-Type" with value "application/json; charset=utf-8"
  A request with a missing date parameter
    with GET /provider
      returns a response which
        has status code 400
        has a matching body
        includes headers
          "Content-Type" with value "application/json; charset=utf-8"

3 interactions, 0 failures



    ✓ should validate the expectations of Our Little Consumer (560ms)


  1 passing (566ms)

Step 11 - Provider states

We have one final thing to test for. If the provider ever returns a count of zero, we will get a division by zero error in our client. This is an important bit of information to add to our contract. Let us start with a consumer test for this.

  describe('given data count == 0', () => {
    describe('when a call to the Provider is made', () => {
      describe('and a valid date is provided', () => {
        before(() => {
          return provider.addInteraction({
            state: 'date count == 0',
            uponReceiving: 'a request for JSON data',
            withRequest: {
              method: 'GET',
              path: '/provider',
              query: { validDate: submissionDate }
            },
            willRespondWith: {
              status: 404,
              headers: {
                'Content-Type': 'application/json; charset=utf-8'
              },
              body: {
                test: 'NO',
                validDate: term({generate: date, matcher: dateRegex}),
                count: like(100)
              }
            }
          })
        })

        it('can handle missing data', (done) => {
          expect(fetchProviderData(submissionDate)).to.eventually.be.rejectedWith(Error).notify(done)
        })

        it('should validate the interactions and create a contract', () => {
          return provider.verify()
        })
      })
    })
  })

It is important to take note of the state property of the Interaction. There are two states: "data count > 0" and "data count == 0". This is how we tell the provider during verification that it should prepare itself into a particular state, so as to illicit the response we are expecting.

This adds a new interaction to the pact file:

  {
      "description": "a request for JSON data",
      "request": {
          "method": "GET",
          "path": "/provider.json",
          "query": "validDate=2017-05-22T13%3A34%3A41.515"
      },
      "response": {
          "status": 404
      },
      "providerState": "data count == 0"
  }

YOur Provider side verification will fail as it is not yet aware of these new 'states'.

Step 12 - provider states for the providers

To be able to verify our providers, we need to be able to change the data that the provider returns. To do this, we need to instrument the running API with an extra diagnostic endpoint to modify the data available to the API at runtime.

For our case, we are just going to use an in memory object to act as our persistence layer, but in a real project you would probably use a database.

Here is our data store:

const dataStore = {
  count: 1000
}

Next, we update our API to use the value from the data store, and throw a 404 if there is no data.

server.get('/provider', (req, res) => {
  const validDate = req.query.validDate
  const dateRegex = /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\+\d{2}:\d{2}/

  if (!validDate) {
    res.status(400)
    res.json({error: 'validDate is required'});
  } else if (!moment(validDate, moment.ISO_8601).isValid()) {
    res.status(400)
    res.json({error: `'${validDate}' is not a date`})
  }  else {
    if (dataStore.count > 0) {
      res.json({
        'test': 'NO',
        'validDate': moment(new Date(), moment.ISO_8601).format('YYYY-MM-DDTHH:mm:ssZ'),
        'count': dataStore.count
      })
    } else {
      res.status(404)
      res.send()
    }
  }
})

Now we can change the data store value in our test based on the provider state to vary its behaviour.

Next we need to add a new endpoint to be able to manipulate this data store:

  1. /setup to allow the Pact verification process to notify the API to switch to the new state

We do this by instrumenting the API in the test code only:

provider/test/providerPact.spec.js

const { server, dataStore } = require('../provider.js')

// Set the current state
server.post('/setup', (req, res) => {
  switch (req.body.state) {
    case 'data count == 0':
      dataStore.count = 0
      break
    default:
      dataStore.count = 1000
  }

  res.end()
})

Lastly, we need to update our Pact configuration so that in knows how to find these new endpoits:

let opts = {
  provider: 'Our Provider',
  providerBaseUrl: 'http://localhost:8081',
  providerStatesUrl: 'http://localhost:8081/states',
  providerStatesSetupUrl: 'http://localhost:8081/setup',
  pactUrls: [path.resolve(process.cwd(), './pacts/our_little_consumer-our_provider.json')]
}

Running the pact verification now passes:

$ npm run test:pact:provider

> [email protected] test:pact:provider /Users/mfellows/development/public/pact-workshop-js
> mocha provider/test/providerPact.spec.js



Animal Profile Service listening on http://localhost:8081
  Pact Verification
Pact Verification Complete!
Reading pact at /Users/mfellows/development/public/pact-workshop-js/pacts/our_little_consumer-our_provider.json

Verifying a pact between Our Little Consumer and Our Provider
  Given date count > 0
    a request for JSON data
      with GET /provider?validDate=2017-06-12T13%3A14%3A49.745Z
        returns a response which
          has status code 200
          has a matching body
          includes headers
            "Content-Type" with value "application/json; charset=utf-8"
  Given date count > 0
    a request with an invalid date parameter
      with GET /provider?validDate=This+is+not+a+date
        returns a response which
          has status code 400
          has a matching body
          includes headers
            "Content-Type" with value "application/json; charset=utf-8"
  Given date count > 0
    a request with a missing date parameter
      with GET /provider
        returns a response which
          has status code 400
          has a matching body
          includes headers
            "Content-Type" with value "application/json; charset=utf-8"
  Given date count == 0
    a request for JSON data
      with GET /provider?validDate=2017-06-12T13%3A14%3A49.745Z
        returns a response which
          has status code 404
          includes headers
            "Content-Type" with value "application/json; charset=utf-8"

4 interactions, 0 failures



    ✓ should validate the expectations of Our Little Consumer (562ms)


  1 passing (567ms)

Step 13 - Using a Pact Broker

We've been publishing our pacts from the consumer project by essentially sharing the file system with the provider. But this is not very manageable when you have multiple teams contributing to the code base, and pushing to CI. We can use a Pact Broker to do this instead.

Using a broker simplies the management of pacts and adds a number of useful features, including some safety enhancements for continuous delivery which we'll see shortly.

Consumer

First, in the consumer project we need to tell Pact about our broker. We've created a small utility to push the pact files to the broker:

consumer/test/publish:

const opts = {
  pactUrls: [path.resolve(__dirname, '../../pacts/our_little_consumer-our_provider.json')],
  pactBroker: 'https://test.pact.dius.com.au',
  pactBrokerUsername: 'dXfltyFMgNOFZAxr8io9wJ37iUpY42M',
  pactBrokerPassword: 'O5AIZWxelWbLvqMd8PkAVycBJh2Psyg1',
  tags: ['prod', 'test'],
  consumerVersion: '1.0.0'
}

pact.publishPacts(opts)

You can run this with test:pact:publish:

$ npm run test:pact:publish

> [email protected] test:pact:publish /Users/mfellows/development/public/pact-workshop-js
> node consumer/test/publish.js

Pact contract publishing complete!

Head over to https://test.pact.dius.com.au/ and login with
=> Username: dXfltyFMgNOFZAxr8io9wJ37iUpY42M
=> Password: O5AIZWxelWbLvqMd8PkAVycBJh2Psyg1
to see your published contracts.

Have a browse around the broker and see your newly published contract!

Provider

All we need to do for the provider is update where it finds its pacts from local URLs, to one from a broker:

let opts = {
  provider: 'Our Provider',
  providerBaseUrl: 'http://localhost:8081',
  providerStatesUrl: 'http://localhost:8081/states',
  providerStatesSetupUrl: 'http://localhost:8081/setup',
  pactBrokerUrl: 'https://test.pact.dius.com.au/',
  tags: ['prod'],
  pactBrokerUsername: 'dXfltyFMgNOFZAxr8io9wJ37iUpY42M',
  pactBrokerPassword: 'O5AIZWxelWbLvqMd8PkAVycBJh2Psyg1',
  publishVerificationResult: true,
  providerVersion: '1.1.0'
}

One thing you'll note in the output, is a message like this:

Publishing verification result {"success":true,"providerApplicationVersion":"1.0.0"} to https://dXfltyFMgNOFZAxr8io9wJ37iUpY42M:*****@test.pact.dius.com.au/pacts/provider/Our%20Provider/consumer/Our%20Little%20Consumer/pact-version/1e37fb5393ea824ad898a5f12fbaa66af7ff3d3b/verification-results

This is a relatively new feature, but is very powerful. Called Verifications, it allows providers to report back the status of a verification to the broker. You'll get a quick view of the status of each consumer and provider on a nice dashboard. But, it is much more important than this!

With just a simple query to an API, we can quickly determine if a consumer is safe to release or not - the Broker will detect if any contracts for the consumer have changed and if so, have they been validated by each provider.

If something has changed, or it hasn't yet been validated by all downstream providers, then you should prevent any deployment going ahead. This is obviously really powerful for continuous delivery, which we alread had for providers.

Here is a simple cURL that will tell you if it's safe to release Our Little Consumer:

curl -s -u dXfltyFMgNOFZAxr8io9wJ37iUpY42M:O5AIZWxelWbLvqMd8PkAVycBJh2Psyg1 "https://test.pact.dius.com.au/verification-results/consumer/Our%20Little%20Consumer/version/1.0.${USER}/latest" | jq .success

Or better yet, you can use our CLI Tools to do the job, which are bundled as part of Pact JS:

npm run can-i-deploy:consumer
npm run can-i-deploy:provider

That's it - you're now a Pact pro!

pact-workshop-js-v1's People

Contributors

mefellows avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

pact-workshop-js-v1's Issues

Getting error on running npm run test:pact:provider

:\pact-workshop-js-master>npm run test:pact:provider

[email protected] test:pact:provider C:\pact-workshop-js-master
mocha --timeout 1000000 provider/test/providerPact.spec.js

                                                                                                                                                                    Provider service listening on http://localhost:8081

Pact Verification
[2018-11-21T17:30:52.918Z] INFO: [email protected]/24720 on APTSW57SLPC0201: Verifying Pacts. [2018-11-21T17:30:52.920Z] INFO: [email protected]/24720 on APTSW57SLPC0201: Verifying Pact Files [2018-11-21T17:30:55.928Z] INFO: [email protected]/24720 on APTSW57SLPC0201: Created 'standalone\win32-1.61.1\bin\pact-provider-verifier.bat 'https://test.pact.dius.com.au/pacts/provider/Our%20Provider/consumer/Our%20Little%20Consumer/version/1.0.1542820574' --provider-base-url 'http://localhost:8081' --provider-states-setup-url 'http://localhost:8081/setup' --broker-username 'dXfltyFMgNOFZAxr8io9wJ37iUpY42M' --broker-password 'O5AIZWxelWbLvqMd8PkAVycBJh2Psyg1' --publish-verification-results 'true' --provider-app-version '1.0.0'' process with PID: 27156
[2018-11-21T17:31:07.274Z] WARN: [email protected]/24720 on APTSW57SLPC0201: Pact exited with code 1.
1) should validate the expectations of Our Little Consumer

0 passing (14s)
1 failing
1) Pact Verification
should validate the expectations of Our Little Consumer:
Error: INFO: Reading pact at https://dXfltyFMgNOFZAxr8io9wJ37iUpY42M:*****@test.pact.dius.com.au/pacts/provider/Our%20Provider/consumer/Our%20Little%20Consumer/version/1.0.1542820574

Verifying a pact between Our Little Consumer and Our Provider
Given date count > 0
a request for JSON data
with GET /provider?validDate=2018-11-21T17%3A15%3A44.943Z
returns a response which

      has status code 200 (FAILED - 1)

      has a matching body (FAILED - 2)

      includes headers

        "Content-Type" which equals "application/json; charset=utf-8" (FAILED - 3)

Given date count > 0

a request with an invalid date parameter

  with GET /provider?validDate=This+is+not+a+date

    returns a response which

      has status code 400 (FAILED - 4)

      has a matching body (FAILED - 5)

      includes headers

        "Content-Type" which equals "application/json; charset=utf-8" (FAILED - 6)

Given date count > 0

a request with a missing date parameter

  with GET /provider

    returns a response which

      has status code 400 (FAILED - 7)

      has a matching body (FAILED - 8)

      includes headers

        "Content-Type" which equals "application/json; charset=utf-8" (FAILED - 9)

Given date count == 0

a request for JSON data

  with GET /provider?validDate=2018-11-21T17%3A15%3A44.943Z

    returns a response which

      has status code 404 (FAILED - 10)

      includes headers                                                                                                                                                                                                                                                                                                                                  "Content-Type" which equals "application/json; charset=utf-8" (FAILED - 11)

Failures:

  1. Verifying a pact between Our Little Consumer and Our Provider Given date count > 0 a request for JSON data with GET /provider?validDate=2018-11-21T17%3A15%3A44.943Z returns a response which has status code 200
    Failure/Error: set_up_provider_states interaction.provider_states, options[:consumer]
    Pact::ProviderVerifier::SetUpProviderStateError:
    Error setting up provider state 'date count > 0' for consumer 'Our Little Consumer' at http://localhost:8081/setup. response status=404 response body=404 Not Found

  2. Verifying a pact between Our Little Consumer and Our Provider Given date count > 0 a request for JSON data with GET /provider?validDate=2018-11-21T17%3A15%3A44.943Z returns a response which has a matching body
    Failure/Error: set_up_provider_states interaction.provider_states, options[:consumer]
    Pact::ProviderVerifier::SetUpProviderStateError: Error setting up provider state 'date count > 0' for consumer 'Our Little Consumer' at http://localhost:8081/setup. response status=404 response body=404 Not Found
    3) Verifying a pact between Our Little Consumer and Our Provider Given date count > 0 a request for JSON data with GET /provider?validDate=2018-11-21T17%3A15%3A44.943Z returns a response which includes headers "Content-Type" which equals "application/json; charset=utf-8"
    Failure/Error: set_up_provider_states interaction.provider_states, options[:consumer]
    Pact::ProviderVerifier::SetUpProviderStateError: Error setting up provider state 'date count > 0' for consumer 'Our Little Consumer' at http://localhost:8081/setup. response status=404 response body=404 Not Found
    4) Verifying a pact between Our Little Consumer and Our Provider Given date count > 0 a request with an invalid date parameter with GET /provider?validDate=This+is+not+a+date returns a response which has status code 400 Failure/Error: set_up_provider_states interaction.provider_states, options[:consumer] Pact::ProviderVerifier::SetUpProviderStateError:
    Error setting up provider state 'date count > 0' for consumer 'Our Little Consumer' at http://localhost:8081/setup. response status=404 response body=404 Not Found
    5) Verifying a pact between Our Little Consumer and Our Provider Given date count > 0 a request with an invalid date parameter with GET /provider?validDate=This+is+not+a+date returns a response which has a matching body Failure/Error: set_up_provider_states interaction.provider_states, options[:consumer]

    Pact::ProviderVerifier::SetUpProviderStateError: Error setting up provider state 'date count > 0' for consumer 'Our Little Consumer' at http://localhost:8081/setup. response status=404 response body=404 Not Found

  3. Verifying a pact between Our Little Consumer and Our Provider Given date count > 0 a request with an invalid date parameter with GET /provider?validDate=This+is+not+a+date returns a response which includes headers "Content-Type" which equals "application/json; charset=utf-8"
    Failure/Error: set_up_provider_states interaction.provider_states, options[:consumer]
    Pact::ProviderVerifier::SetUpProviderStateError:
    Error setting up provider state 'date count > 0' for consumer 'Our Little Consumer' at http://localhost:8081/setup. response status=404 response body=404 Not Found
    7) Verifying a pact between Our Little Consumer and Our Provider Given date count > 0 a request with a missing date parameter with GET /provider returns a response which has status code 400
    Failure/Error: set_up_provider_states interaction.provider_states, options[:consumer]
    Pact::ProviderVerifier::SetUpProviderStateError: Error setting up provider state 'date count > 0' for consumer 'Our Little Consumer' at http://localhost:8081/setup. response status=404 response body=404 Not Found 8) Verifying a pact between Our Little Consumer and Our Provider Given date count > 0 a request with a missing date parameter with GET /provider returns a response which has a matching body
    Failure/Error: set_up_provider_states interaction.provider_states, options[:consumer]
    Pact::ProviderVerifier::SetUpProviderStateError: Error setting up provider state 'date count > 0' for consumer 'Our Little Consumer' at http://localhost:8081/setup. response status=404 response body=404 Not Found

  4. Verifying a pact between Our Little Consumer and Our Provider Given date count > 0 a request with a missing date parameter with GET /provider returns a response which includes headers "Content-Type" which equals "application/json; charset=utf-8"
    Failure/Error: set_up_provider_states interaction.provider_states, options[:consumer]
    Pact::ProviderVerifier::SetUpProviderStateError:
    Error setting up provider state 'date count > 0' for consumer 'Our Little Consumer' at http://localhost:8081/setup. response status=404 response body=404 Not Found

  5. Verifying a pact between Our Little Consumer and Our Provider Given date count == 0 a request for JSON data with GET /provider?validDate=2018-11-21T17%3A15%3A44.943Z returns a response which has status code 404 Failure/Error: set_up_provider_states interaction.provider_states, options[:consumer]
    Pact::ProviderVerifier::SetUpProviderStateError:
    Error setting up provider state 'date count == 0' for consumer 'Our Little Consumer' at http://localhost:8081/setup. response status=404 response body=404 Not Found

  6. Verifying a pact between Our Little Consumer and Our Provider Given date count == 0 a request for JSON data with GET /provider?validDate=2018-11-21T17%3A15%3A44.943Z returns a response which includes headers "Content-Type" which equals "application/json; charset=utf-8"
    Failure/Error: set_up_provider_states interaction.provider_states, options[:consumer]

    Pact::ProviderVerifier::SetUpProviderStateError:
    Error setting up provider state 'date count == 0' for consumer 'Our Little Consumer' at http://localhost:8081/setup. response status=404 response body=404 Not Found

4 interactions, 4 failures

Failed interactions:

  • A request for json data given date count > 0

  • A request with an invalid date parameter given date count > 0

  • A request with a missing date parameter given date count > 0

  • A request for json data given date count == 0

INFO: Verification results published to https://test.pact.dius.com.au/pacts/provider/Our%20Provider/consumer/Our%20Little%20Consumer/pact-version/3dc154c18ef205be0003a2196515f34aedf39c95/verification-results/11957

  at ChildProcess.<anonymous> (node_modules\@pact-foundation\pact-node\src\verifier.js:130:68)
  at maybeClose (internal/child_process.js:925:16)
  at Process.ChildProcess._handle.onexit (internal/child_process.js:209:5)

Typos (minor) in the readme.

I fixed 2 typos in the readme.

If you want, I can submit a PR for them.

First was line 853 YOur -> Your
Second was line 1067 alread -> already

Thanks for this guide, it is great!

Cannot get this to work

Upon checking out the latest, there are still some minor typos, but I can only get as far as Step 3 and simply cannot get the pact to validate.

Can you please re-test this yourself and provide an updated starting point and step by step guide. Please, please, please.

Getting error when I run pact verification

Hi,
unfortunately I have an error when I run npm run "test:pact:provider":

[email protected] test:pact:provider C:\Users\tgdlima3\workspace0056\pact\pact-workshop-js-v1
mocha --timeout 10000 provider/test/providerPact.spec.js

Provider service listening on http://localhost:8081
Pact Verification
[2020-02-26T10:09:41.229Z] INFO: [email protected]/5376 on U318882: Verifying Pacts.
[2020-02-26T10:09:41.232Z] INFO: [email protected]/5376 on U318882: Verifying Pact Files
[2020-02-26T10:09:41.248Z] INFO: [email protected]/5376 on U318882: Created 'standalone\win32-1.61.1\bin\pact-provider-verifier.bat '/Users/tgdlima3/workspace0056/pact/pact-workshop-js-v1/pacts/our_little_consumer-our_provider.json'
--provider-base-url 'http://localhost:8081'' process with PID: 15640
[2020-02-26T10:09:41.973Z] WARN: [email protected]/5376 on U318882: Pact exited with code 1.
1) should validate the expectations of Our Little Consumer

0 passing (751ms)
1 failing

  1. Pact Verification should validate the expectations of Our Little Consumer:
    Error: C:/Users/tgdlima3/workspace0056/pact/pact-workshop-js-v1/node_modules/@pact-foundation/pact-node/standalone/win32-1.61.1/lib/vendor/ruby/2.2.0/gems/pact-provider-verifier-1.20.0/lib/pact/provider_verifier/app.rb:3:in re quire': cannot load such file -- pact/provider_verifier/provider_states/remove_provider_states_header_middleware (LoadError) from C:/Users/tgdlima3/workspace0056/pact/pact-workshop-js-v1/node_modules/@pact-foundation/pact-node/standalone/win32-1.61.1/lib/vendor/ruby/2.2.0/gems/pact-provider-verifier-1.20.0/lib/pact/provider_verifier/app.rb:3:in <
    top (required)>'
    from C:/Users/tgdlima3/workspace0056/pact/pact-workshop-js-v1/node_modules/@pact-foundation/pact-node/standalone/win32-1.61.1/lib/vendor/ruby/2.2.0/gems/pact-provider-verifier-1.20.0/lib/pact/provider_verifier/cli/verify.rb:
    3:in require' from C:/Users/tgdlima3/workspace0056/pact/pact-workshop-js-v1/node_modules/@pact-foundation/pact-node/standalone/win32-1.61.1/lib/vendor/ruby/2.2.0/gems/pact-provider-verifier-1.20.0/lib/pact/provider_verifier/cli/verify.rb: 3:in <top (required)>'
    from C:/Users/tgdlima3/workspace0056/pact/pact-workshop-js-v1/node_modules/@pact-foundation/pact-node/standalone/win32-1.61.1/lib/app/pact-provider-verifier.rb:18:in `require'

    from C:/Users/tgdlima3/workspace0056/pact/pact-workshop-js-v1/node_modules/@pact-foundation/pact-node/standalone/win32-1.61.1/lib/app/pact-provider-verifier.rb:18:in `

    '

    at ChildProcess. (node_modules@pact-foundation\pact-node\src\verifier.js:130:68)
    at maybeClose (internal/child_process.js:1021:16)
    at Process.ChildProcess._handle.onexit (internal/child_process.js:283:5)

npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! [email protected] test:pact:provider: mocha --timeout 10000 provider/test/providerPact.spec.js
npm ERR! Exit status 1
npm ERR!
npm ERR! Failed at the [email protected] test:pact:provider script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

npm ERR! A complete log of this run can be found in:
npm ERR! C:\Users\tgdlima3\AppData\Roaming\npm-cache_logs\2020-02-26T10_09_42_009Z-debug.log

Any idea what is wrong here?
Thanks for support.

Best Regards,
Marco

The actual code does not verify the full cases.

      it('can process the JSON payload from the provider', () => {
        const response = fetchProviderData(submissionDate)

        return expect(response).to.eventually.have.property('count', 0.1)

The code(in Consumer.spec.js and ConsumerPact.spec.js) does not verify the 'data' property.
Is it left out for simplicity? Or is it because we can only have one return expect...?

If I change the code to

   expect(response).to.eventually.have.property('date2', 'we')  // no return
  return expect(response).to.eventually.have.property('count', 0.1)

This code will always pass no matter what the 'date2' or 'date3'
If the code is

   return expect(response).to.eventually.have.property('date2', 'we')  // with return

The code will fail.

So al the expect without a return in the repo are invalid tests!
I run the test with npm run test:consumer and gets the behavior I see above.

The step 3 says that pact to rescue is not convinced

The consumer fail in step 2 because it does not follow the API that the provider gives instead consumer tests the API response based on its imagination.

Detail: The provider responses with "validate" but the consumer test "date". So how can the consumer would not fail when connect to the real provider?
So I'm not quite sold. Could anyone help to clear my doubts? Thank you very much!

Cannot find module @pact-foundation/pact-node

I thought this issue was fixed. As in another post I see its already added as a direct dependency:

When I fire npm run "test:pact:consumer" I am getting below error:

Error: Cannot find module '@pact-foundation/pact-node'
at Function.Module._resolveFilename (module.js:469:15)
at Function.Module._load (module.js:417:25)
at Module.require (module.js:497:17)
at require (internal/module.js:20:19)
at Object. (C:\Users\userid\pact-workshop-js\node_modules\pact\sr
c\dsl\verifier.js:8:24)
at Module._compile (module.js:570:32)
at Object.Module._extensions..js (module.js:579:10)
at Module.load (module.js:487:32)

regex test for validDate should not assume '+' in the timeZone

I am testing in North America and the timeZone is - instead of +

              validDate: term({ generate: date, matcher: '\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}-\\d{2}:\\d{2}' }),


   "body": {
      "test": "NO",
      "validDate": "2013-08-16T15:31:20-10:00",
      "count": 1000
    },

Error in the readme

Provider Service listening on http://localhost:9123

That is wrong.

The right call would be provider/providerService.js

Pact tests fail after clean clone

Hi there. I'm not sure if this is an issue with the code, the documentation, or me, but the pact tests are failing for me on the first run.

With setup

Ran nvm use 6
Ran npm install
Ran node provider/provider.js

test:consumer: passes
test:pact:consumer: fails
test:pact:provider: fails

test:pact:consumer

fails with

(node:56245) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): AssertionError: expected { Object (count, date) } to have a property 'count' of 100, but got 0.1

AssertionError: expected { Object (count, date) } to have a property 'date' of '2013-08-16T15:31:20+10:00', but got '2013-08-16T06:31:20+01:00'

passes if I change

          count: like(1000)

to

          count: like(1)

and

          expect(response).to.eventually.have.property('count', 100)
          expect(response).to.eventually.have.property('date', date).notify(done)

to

          expect(response).to.eventually.have.property('count', 100).notify(done)

test:pact:provider

fails with

  1) Uncaught error outside test suite
  Pact Verification
    2) should validate the expectations of Our Little Consumer

  0 passing (8s)
  2 failing

  1)  Uncaught error outside test suite:
     Uncaught Error: listen EADDRINUSE :::8081
      at Object.exports._errnoException (util.js:1018:11)
      at exports._exceptionWithHostPort (util.js:1041:20)
      at Server._listen2 (net.js:1258:14)
      at listen (net.js:1294:10)
      at Server.listen (net.js:1390:5)
      at EventEmitter.listen (node_modules/express/lib/application.js:618:24)
      at Object.<anonymous> (provider/test/providerPact.spec.js:23:8)
      at require (internal/module.js:20:19)
      at Array.forEach (native)
      at run (bootstrap_node.js:390:7)
      at startup (bootstrap_node.js:150:9)
      at bootstrap_node.js:505:3

If you could help with this, I'd really appreciate it.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.