I have to drop off for a thing, but we should also...
# pact-js-development
m
I have to drop off for a thing, but we should also discuss V4. V3 was never an API I was very comfortable with but we had it in beta for so long it was all but impossible to change. V4 has some significant changes - plugin support, different interaction models etc. I’m currently experimenting with a Type State builder pattern for V4 and it feels a lot better and removes a class of problems (what state am I in? Can the user do that just yet? are all of the required fields set?). See example here: https://github.com/pact-foundation/pact-js/blob/master/examples/v4/plugins/test/matt.consumer.spec.ts (it’s currently dark launched as a beta and behind a feature toggle env var. Side bar: I think I like this way of doing it, because this way there isn’t a long lived branch off the side. Opinions thoughts welcome 🙂). I’d like to keep the “all in one” interface, but it is harder to do when there are more features (you need to guard against state a lot more). I don’t think we even need to keep the interface in V4, or if we do, we can just leave support for what it already does. The newer features are things like synchronous messages and plugins, so you wouldn’t be able to / need to “migrate” to it anyway
t
Side bar: I think I like this way of doing it, because this way there isn’t a long lived branch off the side. Opinions thoughts welcome 🙂).
YES
🙌 1
What a good idea
Copy code
.usingPlugin({
            plugin: 'matt',
            version: '0.0.5',
          })
I’m not a fan of this. Can’t it be done at the Pact level, when you initialise the contract?
In general, I think the whole plugin interface exposes too much complexity to the user
t
…..
From reading that test, I can’t tell what is part of pact, what is plugin boilerplate, and what is part of the interaction description
I think the “right” DSL would make those three things clear
👍 1
Also, I have said this before, but the plugin interface should be designed to make things simpler not harder
t
No one asked you, slackbot.
btw, I read that page, and I still don’t know what I can and can’t do with plugins
Does
startTransport
begin the listener? I think you can avoid the need for this call
Similarly, I don’t think you need to make a distinction here:
addSynchronousInteraction
- Just make PactJS think they’re both asynchronous
Then,
addInteraction
implies
startTransport
or the end of the builder pattern does
withPluginContents
is the interaction description? I would put the content in there
Copy code
pact
          .addInteraction('a MATT message')
          .interaction(mattMessage, 'application/matt')
          .config({ host: HOST })
          .executeTest(async (tc) => {
            const message = await sendMattMessageTCP('hellotcp', HOST, tc.port);
            expect(message).to.eq('tcpworld');
          });
This is very similar to the way I’m doing it:
Copy code
setup(
      willSendHttpInteraction({
        request: {
          method: 'GET',
          path: '/health',
        },
        response: { status: httpStatus(['4XX', '5XX']) },
      }),
     { port } // config is optional and inherited from root contract
)
In my (non-Pact) DSL, it would look like:
Copy code
setup(
   willSendMattMessage({
      message: mattMessage,
   }),
   { host: HOST }
)
👍 1
As long as earlier you did:
Copy code
startContract({
      consumerName: 'matt response consumer',
      providerName: 'matt response provider',
    }).withPlugin(matt)
👍 1
^ I haven’t built that part, though
You could even ask the plugin ecosystem for the matcher:
Copy code
setup(
      getMatcher('willSendMattMessage')({
   .....
)
m
I think it’s worth exploring those ideas!
Does
startTransport
begin the listener? I think you can avoid the need for this call
I’d like to. I think we could put it on the
PactV4
constructor. One challenge is that not all plugins provide transports and they aren’t always going to be used in all interactions.
Similarly, I don’t think you need to make a distinction here:
addSynchronousInteraction
- Just make PactJS think they’re both asynchronous
That would be very hard to retrofit into the model now I think
No one asked you, slackbot.
I’m going to delete that entry. Not sure who did it on such a wide string match (hopefully not me!)
withPluginContents
is the interaction description? I would put the content in there
No, that accepts the plugin configuration (which is the contents + whatever config the plugin itself takes)
so I think it mostly maps to your expectations, but probably the example is not aiding that
Copy code
return pact
          .addSynchronousInteraction('a MATT message') // <- I'm testing a req/response message with this description/scenario name
          .usingPlugin({ // <- I need a plugin for this interaction
            plugin: 'matt',
            version: '0.0.5',
          })
          .withPluginContents(mattMessage, 'application/matt') // <- interaction contents
          .startTransport('matt', HOST) // <- use a mock server  for this test please
          .executeTest(async (tc) => { // <- transport specific test execution. Will validate the mock server was used, and delegate to the plugin mock server
            const message = await sendMattMessageTCP('hellotcp', HOST, tc.port);
            expect(message).to.eq('tcpworld');
          });
(those comments might render a bit shit on mobile if you’re on it)
I know what you’re saying about moving the plugin guff up and out of the configuration. Any ideas on how we could do that, whilst preserving the dynamic nature of the
executeTest
function and also ensuring the right version of a plugin is used? Plugins unfortunately introduce another versioning problem, and we made the decision to be explicit about versioning, rather than implicit (i.e. discovering). We could change that, but we have already seen issues on the provider side where the driver loaded the most recent version of a plugin to verify and it was incompatible with what was used on the consumer side. We fixed that to ensure the same version is used, because you can imagine the classes of bugs we’d need to address if we didn’t.
t
That would be very hard to retrofit into the model now I think
Yes. I’m afraid this is my general position on the plugins
So, I mean
…..if everything was a matcher
then all plugins do is provide new matchers
it would be easy to register them
Plugins unfortunately introduce another versioning problem, and we made the decision to be explicit about versioning, rather than implicit (i.e. discovering).
I don’t think they do. I think this problem already exists inside Pact, with matchers.
So, you need some way of saying “hey, is this matcher understandable by this version of Pact”?
At the moment that’s handwaved away by “pact specification version” but really it’s the same problemn
I have this contract file, can it be verified by this older version of Pact?
So, already we need a mechanism for “do I understand this bit of the contract?”
why not just use the same mechanism?
I think it’s important for the plugin framework to reduce complexity, rather than increase it
I mean, all of this is the design for Case
I don’t know if it will support plugins (it might not need to) but it will be pretty easy to do that
it does need to support “do I know this matcher”
Any ideas on how we could do that, whilst preserving the dynamic nature of the executeTest function
So, what I’m doing, is there’s a test context that knows all the configuration. This is passed recursively down the matcher tree, and some matchers might write to it. This means that configuration at the matcher (ie, the interaction) level is the same as at the contract level.
also ensuring the right version of a plugin is used?
I think this is the responsibility of the package manager. When you register a plugin (ie, connect it to Pact) you can maybe set a semver string for the versions that you will accept.
Plugins unfortunately introduce another versioning problem, and we made the decision to be explicit about versioning, rather than implicit (i.e. discovering).
You need to record the version in the contract, of course, and you need to be able to interpret it at the other end to make sure that your version of the plugin is acceptable.
We could change that, but we have already seen issues on the provider side where the driver loaded the most recent version of a plugin to verify and it was incompatible with what was used on the consumer side.
This is the fault of the plugin author, yes? You’d need to say what “breaking change” means, but with semver you could just reject the plugin if you expected it to be incompatible. Yes, it’s a bug if a plugin author violates that, but I mean, this is a good reason not to.
We fixed that to ensure the same version is used, because you can imagine the classes of bugs we’d need to address if we didn’t.
This feels like it’s unnecessarily strict. I think it’s also not a good look for a contract testing framework to say “we couldn’t figure out how this contract compatibility should work, so we enforce that you use the same version”
There’s a subtlety in that in order for everything to be a matcher, all matches have to be asynchronous. Since for “did I receive this request?” - you don’t know yet.
This idea is of everything being a matcher is so powerful. For example, Matchers have
match
functions for testing whether their data matches, and
strip
functions for removing the matchers and returning the example. This means that even
fromProviderState
is a matcher - when asked to match it does an exact match against the data returned from provider state, and when asked to strip, it returns that data.
Fewer concepts === better
m
This feels like it’s unnecessarily strict. I think it’s also not a good look for a contract testing framework to say “we couldn’t figure out how this contract compatibility should work, so we enforce that you use the same version” (edited)
I think it did/does support something like that, so we could probably improve that configuration option.
This idea is of everything being a matcher is so powerful. For example, Matchers have
match
functions for testing whether their data matches, and
strip
functions for removing the matchers and returning the example. This means that even
fromProviderState
is a matcher - when asked to match it does an exact match against the data returned from provider state, and when asked to strip, it returns that data. (edited)
it definitely sounds elegant, but that ship has sailed (at least for this version of Pact). I’d be interested to see how we might be able to adopt/migrate to something like that and any consequences of doing so, but it’s definitely beyond my technical know how to suggest it. Also, it is a bit of a paradigm shift from Pact as it is today and (possibly) it is best to have this not be in Pact, and let the marketplace of ideas choose the winner. That is to say, it may well be the better paradigm and have it run alongside tools like Pact might be the best way to validate the idea cc @uglyog who might be keen to learn more about this line of thinking.
I think it’s important for the plugin framework to reduce complexity, rather than increase it
I’m not sure I agree with this statement, albeit of course in general, reducing complexity usually improves DX and that is a good thing. What I’m saying is that we don’t actually have a tabula rasa here - we are working with an existing toolchain, ecosystem and user base. These conditions mean we are necessarily constrained in certain choices
I’m not a fan of this. Can’t it be done at the Pact level, when you initialise the contract?
Coming back to this. I think we probably could and then just behind the scenes add them to the interaction, however as I said, it’s possible different interactions will need different transports (because you might have an HTTP and gRPC or whatever endpoint) so I’m not sure we can work around that
startTransport
fn.
t
Why not? You can tell from the interaction description. Source: that’s what I did in Case
an existing toolchain, ecosystem and user base.
That’s true, but the pact file is opaque, so you have a lot of flexibility - unless concepts get surfaced unnecessarily to the user
👍 1
m
At the type level?
That’s true, but the pact file is opaque, so you have a lot of flexibility - unless concepts get surfaced unnecessarily to the user
These concepts are already exposed to the user, that's exactly the point. A case could be made (see what I did there?) That Pact != Pact Specification. In fact that's really the case, we already know there are many behaviours of Pact that aren't modelled in the spec at all. Perhaps we should be taking a wider lens of the pact spec to support wider use cases (such as different contract testing solutions?)
t
I thought the plug-in DSL wasn’t finalised yet?
That’s what I’m talking about
m
Oh right, yes the DSL is still a WIP
t
From a user perspective, I think pact is the DSL and the concepts that the broker provides
👍 1
m
Could you maybe show what you think the Pact DSL could look like with regards to transport specific interactions?
t
Maybe not even that- maybe just the DSL’s concepts
🤔 1
I did, above
Like the broker supports arbitrary contracts, so that might not be Pact
m
Oh right the .config thing. I think that could work
I'll have a play next week
t
I have built that DSL, btw
👍 1
m
Sorry slack folds old messages and that one I missed (albeit I saw the case ones)
t
Not for pact
👍 1
m
(dropping off sorry, running to get a train)
👍 1