New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal: Allow customProviderHeaders to be dynamically added to different interactions #209

Open
bernardodazn opened this Issue Aug 24, 2018 · 11 comments

Comments

Projects
None yet
4 participants
@bernardodazn

bernardodazn commented Aug 24, 2018

Issue

It appears customProviderHeaders currently adds the headers for all the interactions in a pact file. But in certain situations you may want the header to be relevant to a specific interaction (and, in fact, may need information about that interaction itself to create the header). For example, for authentication, it may change between requests.

I am writing this here as a request because Matt Fellows asked me on the pact-js slack channel to raise a request .

Thank you!

@bethesque

This comment has been minimized.

Contributor

bethesque commented Sep 16, 2018

I've implemented this. Just posting this to remind myself to post the docs here.

@bethesque

This comment has been minimized.

Contributor

bethesque commented Sep 16, 2018

Ping me if I forget.

@bethesque

This comment has been minimized.

Contributor

bethesque commented Sep 18, 2018

Ok, you can write your own middleware. Unfortunately, it will need to be in Ruby, not js (otherwise, you'll have to write your own independent proxy app in JS).

Here's an intro to Rack middleware. Rack is the bomb.

https://www.engineyard.com/blog/understanding-rack-apps-and-middleware

Here's an example spec using --custom-middleware

https://github.com/pact-foundation/pact-provider-verifier/blob/master/spec/integration_with_custom_middleware_spec.rb#L8

Here's the example custom middleware (note that it has to inherit from Pact::ProviderVerifier::CustomMiddleware)

https://github.com/pact-foundation/pact-provider-verifier/blob/master/spec/support/custom_middleware.rb

Matt, I've just released 1.60.0 of the standalone, can you update the js please?

@mefellows

This comment has been minimized.

Member

mefellows commented Sep 18, 2018

Thanks Beth, yes will upgrade soon.

That being said, a more ideal solution might be following a similar strategy to the message pact, whereby we spin up a server in JS land that intercepts requests from the verifier and can add the custom headers to the Provider. This way it will leverage native JS code, rather than having to create custom Ruby middleware.

At least now we have the infrastructure to do all of these things.

@bethesque

This comment has been minimized.

Contributor

bethesque commented Sep 18, 2018

Yes, I said that :P

(otherwise, you'll have to write your own independent proxy app in JS)

But we could make the experience nicer.

@mefellows

This comment has been minimized.

Member

mefellows commented Oct 18, 2018

I've spiked locally making the verification system more flexible, whilst I don't think the API is perfect yet, it already feels so much nicer now that the user can just pass around functions, and doesn't need to provide a /setup like endpoint:

const { Verifier } = require('@pact-foundation/pact')
const { server, importData, animalRepository } = require('../provider.js')

// Start the actual provider
server.listen(8081, () => {
  console.log('Animal Profile Service listening on http://localhost:8081')
})

// Verify that the provider meets all consumer expectations
describe('Pact Verification', () => {
  it('should validate the expectations of Matching Service', () => {
    // Authentication header we'll manipulate for different requests
    let token = "INVALID TOKEN"

    // Verification options
    let opts = {
      provider: 'Animal Profile Service',
      providerBaseUrl: 'http://localhost:8081',
      pactBrokerUrl: 'https://test.pact.dius.com.au/',
      pactBrokerUsername: 'dXfltyFMgNOFZAxr8io9wJ37iUpY42M',
      pactBrokerPassword: 'O5AIZWxelWbLvqMd8PkAVycBJh2Psyg1',
      publishVerificationResult: true,
      providerVersion: "1.0.0",

      // Optional middleware to intercept the request _before_ the provider is invoked
      // Implements the 'request filter' pattern as per JVM
      // e.g. ADD Bearer token
      beforeHook: (req, res, next) => {
        console.log('Middleware invoked before provider API - chance to modify request')
        // Set's the auth token for each request
        // State handlers will ensure it is correct/incorrect later on
        req.headers['Authorization'] = `Bearer: ${token}`
        next()
      },

      // Optional middleware to intercept the request _after_ the provider is invoked, but _before_
      // the response is passed to the verifier itself
      // Very advanced, with narrow real-world use cases, but can be useful if e.g. verifying a 3rd party service
      afterHook: (req, res, next) => {
        console.log(`Middleware invoked after provider API - chance to modify response... but probably don't`)
        token = "INVALID TOKEN AGAIN"
        next()
      },

      // Register handler functions for any known provider states
      // Could we use decorators as an alternative to this to keep things DRY
      stateHandlers: {
        "Has no animals": () => {
          animalRepository.clear()
          return Promise.resolve(`Animals added to the db`)
        },
        "is authenticated": () => {
          token = "generate a valid token for this state"
          return Promise.resolve(`Valid bearer token generated`)}
      },

    }

    return new Verifier(opts)
      .verifyProvider()
      .then(output => {
        console.log('Pact Verification Complete!')
        console.log(output)
      })
  })
})

cc: @lirantal @TimothyJones

Worth adding/thinking about in #215

@bethesque

This comment has been minimized.

Contributor

bethesque commented Oct 18, 2018

That looks amazing @mefellows! (Just like the Ruby one ;) )

@mefellows

This comment has been minimized.

Member

mefellows commented Oct 23, 2018

As an aside, I've published this to a tag proxy-spike if people would like to test, and all code is in the branch https://github.com/pact-foundation/pact-js/tree/spike/proxy-system.

Install with npm i @pact-foundation/pact@proxy-spike and use the API as per above.

@lirantal

This comment has been minimized.

Contributor

lirantal commented Oct 23, 2018

@mefellows this is really cool, both the hooks and the state handlers!

Some comments:

  1. the state handlers should always return a promise, right?
  2. if you refer to decorators in terms of also adding transpiling requirement then I'd choose not to opt in to that.
  3. when this lands can we make sure we also document it? :-)
@mefellows

This comment has been minimized.

Member

mefellows commented Oct 23, 2018

  1. Yes, at least that's the current thinking
  2. It would either be a) for versions of Node\TS that support it or b) fall back to other options. I've been thinking about it, and even did a bit of a spike. It's a bit weird, because the decorator proposal is really only for Classes, Class methods etc. Really, all we need is a wrapper function that can decorate an existing function. In pure JS, this is pretty easy. A DSL might look like this (simplified for readability):
import { registerStateHandler } from "pact"

const loadUsers = () => { ... }
const removeUsers = () => { ... }

registerStateHandler('User A exists', loadUsers)
registerStateHandler('User A does not exist', removeUsers)


// Do provider verification

Where registerStateHandler takes the function and stores it away in the subsystem somewhere for use during verification. I'm not convinced the interface is much easier to use as a consumer, but something like this might be useful if other UIs are built on top of the Pact library (e.g. a Mocha/Jest/Karma/Ava/... DSL)

  1. Yes 😂
@lirantal

This comment has been minimized.

Contributor

lirantal commented Oct 23, 2018

That HOF looks nice.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment