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!
I've implemented this. Just posting this to remind myself to post the docs here.
Ping me if I forget.
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
Here's the example custom middleware (note that it has to inherit from Pact::ProviderVerifier::CustomMiddleware)
Matt, I've just released 1.60.0 of the standalone, can you update the js please?
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.
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.
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
That looks amazing @mefellows! (Just like the Ruby one ;) )
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.
@mefellows this is really cool, both the hooks and the state handlers!
Some comments:
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)
That HOF looks nice.
This is now in the 8.x.x line 馃帀
Hey @mefellows
Great work on this proposal, it has come in useful as I need to sign requests for an AWS API Gateway endpoint as my providers are protected. I am using https://github.com/mhart/aws4
For a GET request, it needs the path, I hardcoded pathname in the state handler and it worked great for a single test.
For a POST request, it needs the path and body
Each pact test, may have a different path and body, and currently my stateHandler Is authenticated can't access the body, or pathname.
``
let signedHost: string;
let signedXAmzSecurityToken: string;
let signedXAmzDate: string;
let signedAuthorization: string;
opts = {
stateHandlers: {
"Is authenticated": () => {
const requestUrl = PACT_PROVIDER_URL;
const host = new url.URL(requestUrl).host;
const pathname = '/path/that/the/pact/test/is/calling';
const options = {
host,
path:pathname,
headers: {}
};
console.log('creating AWS signed headers')
aws4.sign(options);
console.log('created AWS signed headers')
authHeaders = options.headers
signedHost = authHeaders['Host']
signedXAmzSecurityToken = authHeaders['X-Amz-Security-Token']
signedXAmzDate = authHeaders['X-Amz-Date']
signedAuthorization = authHeaders['Authorization']
return Promise.resolve(AWS signed headers created`)
}
},
requestFilter: (req, res, next) => {
// over-riding request headers with AWS credentials
console.log('req.path',req.path)
console.log('req.body',req.body)
req.headers['Host'] = signedHost
req.headers['X-Amz-Security-Token'] = signedXAmzSecurityToken
req.headers['X-Amz-Date'] = signedXAmzDate
req.headers['Authorization'] = signedAuthorization
next()
},
provider: PACT_PROVIDER_NAME
providerBaseUrl: PACT_PROVIDER_URL,
pactUrls: [
path.resolve(
process.cwd(),
"pact",
"pacts",
"consumer-service-provider-service.json"
),
],
publishVerificationResult: publishResultsFlag || false, // ONLY SET THIS TRUE IN CI!
validateSSL: true,
changeOrigin: true,
providerVersion, // the application version of the provider
pactBrokerUsername: PACT_BROKER_BASIC_AUTH_USERNAME,
pactBrokerPassword: PACT_BROKER_BASIC_AUTH_PASSWORD,
tags: tagsArray
};
}
I can get access to the path and body under test in `requestFilter` with `req.path` & `req.body`, where I can see the state`Is authenticated`, and then the path and body under test.
Log output of `requestFilter`
req.path /_pactSetup
req.body { consumer: 'consumer-service',
state: 'Is authenticated',
states: [ 'Is authenticated' ],
params: {} }
creating AWS signed headers
created AWS signed headers
req.path /path/that/the/pact/test/is/calling
req.body undefined
```
So I suppose I could check if the state is authenticated in the requestFilter function, and then call a authentication function that has access to the path and body.
It would be nice if the stateHandler function can easily access the path and body for the current pact test being verified, it would make it cleaner. I might just be being dim as to how to achieve this!
I'll get a proper example up in a repo shortly
Most helpful comment
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
/setuplike endpoint:cc: @lirantal @TimothyJones
Worth adding/thinking about in #215