Repository navigation
Add Synchronous Plugin Interaction Support - #551
max-lancaster wants to merge 18 commits into
Conversation
82da8f1 to
dedfecf
Compare
dedfecf to
12b9663
Compare
|
I've added a commit for some cleanups and things just to save on a bunch of review comments. I think the big design change that stands out is the two interfaces that have moved from the PactNet project to PactNet.Abstractions and made public instead of internal, and then making the port public also. Those are supposed to be implementation detail concerns so I'd prefer to keep them where they are. We should provide some kind of generic public API if needed for plugins if they need to start a mock server instead of PactNet starting it for them at the appropriate time (but I don't see at first hand why that should happen vs. PactNet taking care of it). |
These changes were introduced in order to support a public |
Ok, @adamrodger I took another look at this and I was able to revert the changes that made these details public and still keep my Grpc Extensions library working as intended, I think this should be ready to go now. Basically I didn't really need to inherit from |
|
Thanks for the great work here @max-lancaster - what's needed to move this one forward @adamrodger ? |
adamrodger
left a comment
There was a problem hiding this comment.
Just one API design choice to review, and then I think this is good to merge
| /// </summary> | ||
| /// <param name="contentType">Content type</param> | ||
| /// <param name="content">A dictionary containing the plugin content.</param> | ||
| ISynchronousPluginRequestBuilderV4 WithContent(string contentType, Dictionary<string, object> content); |
There was a problem hiding this comment.
question: Given this returns the same object again, what happens if this is called twice? Currently this allows:
pact.UponReceiving("a request").WithContent("foo", foo).WithContent("bar", bar);Is that intentional? Are we trying to allow setting different content types with different contents? What if we use the same content type but a different body? I think it's ambiguous whether those would combine or replace.
Other builders 'consume' the request builder when this part is called, so that you can't accidentally call it twice and thuse you avoid that ambiguity.
So I suggest maybe this returns some other type which then moves you onto the verification stage, then this has to be the last call and it can only happen once.
There was a problem hiding this comment.
On the request, this will replace the previous contents.
It is allowed on the response, and will append the contents (i.e. multiple responses).
So on this method, it should "consume" the builder as you describe.
There was a problem hiding this comment.
Thanks @adamrodger & @mefellows, this is now addressed.
WithContent returns void and is the last call on the request builder. It can only be called once per interaction, so the ambiguity @adamrodger raised is gone. Given is still chainable before it.
I chose void rather than a new response stage after reading how pactffi_interaction_contents treats synchronous message interactions. For those, the FFI ignores the InteractionPart argument. The plugin receives one contents document and splits it into the request and the responses itself. Calling it twice replaces the request and appends a second response, exactly as @mefellows described. That means there is no separately addressable "response side" to build a stage around, and verification happens on the pact builder rather than per interaction, so the chain just ends.
Also in this update:
- unit tests for the plugin request builder and pact builder
- docs/plugin-interactions.md, covering the raw plugin API and how a wrapper library layers on top of it (the guidance Adam asked for on Add PactNet.Extensions.Grpc package for grpc plugin support #548)
- rebased onto current master, and the PR base changed from dev/plugins to master so this can merge on its own
#548 remains stacked on this branch and will shrink to a single commit once this merges.
This enables creating a nicer API to expose publicly for use by plugin wrappers. See: pact-foundation#492
…ilder Setting the plugin contents completes a synchronous plugin interaction, so WithContent now returns void instead of the same builder. The plugin splits one contents document into the request and its responses itself: a second call would replace the request but append another response, which is why the builder no longer allows it. Provider states stay chainable via Given. Adds unit tests for the plugin request and pact builders and a docs page for the raw plugin API, linked from the README. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
de9f17d to
8b6d35e
Compare
pact-foundation#551 reorganises Drivers/ into Http/, Message/ and Plugins/ subfolders. The sync message driver belongs in Message/ alongside the async one, since it's the same message-queue-style request/response category, not a plugin. Namespace stays PactNet.Drivers for now since the rest of the tree hasn't moved yet; that follows once pact-foundation#551 lands.
There was a problem hiding this comment.
Hi @max-lancaster,
This looks really good to me,
Could you add two commits to help get this passing in CI, and an additional to test with the latest ffi and pact protobuf plugin version
- add protoc to linux arm64 ci (fails without it) YOU54F@07b0695
- update the grpc packages to 2.84.0 (there is a bug in 2.82.x and below that affects linux arm64) YOU54F@a82f8ce
- Regenerate the fixtured pact file with the latest pact protobuf version (0.8.0) YOU54F@9274843 additional bringing in the pact-ffi update.
Tested PR in CI here against my fork
I'll add some comments on the packaged grpc extension about where best to house it!
|
Thanks @YOU54F I will look into addressing your suggestions. |
…rotobuf plugin 0.8.0 Brings in YOU54F/pact-net@92748436 as requested in the PR pact-foundation#551 review. - pact-ffi 0.4.27 -> 0.5.10; a request header configured with multiple values is now matched as one joined value, so the tests and the upgrading-to-5 doc reflect that - pact fixtures regenerated (pactRust metadata, merged X-Request header) - grpc sample pact regenerated with pact-protobuf-plugin 0.8.0 - grpc sample and plugin docs now request plugin 0.8.0 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
@YOU54F thanks for the review and for testing this in CI on your fork. All three items are now on this branch:
Verified locally on Windows against ffi 0.5.10: PactNet.Tests on net8.0 and net462, plus the gRPC and OrdersApi consumer and provider samples, all pass. The sample pacts regenerated locally with plugin 0.8.0 are byte-identical to the fixtures in your commit. |
|
No worries dude, my pleasure. CI is failing as the protobuf plugin when not the disk, will auto download with the framework. I think it may either be a permissions issue, or a timing issue, as reproducing locally, with my local plugins removed. After a failing test run, the protobuf plugin is correctly installed to disk, and executable. We can circumvent by using the pact-cli to pre-install the plugin - uses: pact-foundation/pact-cli@main
- run: pact plugin install --yes protobuf --version 0.8.0I wonder if this is something we can cater for in the core, or if it needs handling in the client library. Haven't investigated too far. FYI, the grpc wrapper is over here now https://github.com/pact-foundation/pact-net-extensions Just trying to get CI all green over there, and then I will update to your latest commit. It is currently one behind |
|
So with your changes, I was able to build out
both tested with a newly created avro plugin, to test out the additional plugin contribution path
With these in place, users should be able to create new plugins for http / async / sync types, with their own transport (excl. http) or content matchers @adamrodger - I think this is in a good state to re-review.
happy to help with any merging, releasing. |
|
@YOU54F it's a timing issue. I had Claude dig through the failed run and the plugin driver source, and this is what it found: Running I pushed a fix in c7f45bb: a new Longer term this is probably worth fixing in the driver itself, e.g. write the manifest last or take a file lock on the plugin directory during install. |
Replace build/install-plugins.sh with the pact-foundation/pact-cli action and `pact plugin install`, as suggested in review. The plugin is still pre-installed before the tests run so parallel test projects do not race each other auto-downloading it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
@YOU54F follow-up: I've switched to your suggested approach in 6e28aef. The shell script is gone and CI now uses the |
|
Appreciate the investigation dude, I'll get an issue raised in the pact-plugins repo as the fix will need to go into the rust driver here https://github.com/pact-foundation/pact-plugins/tree/main/drivers/rust/driver |
pact-foundation#551 reorganises Drivers/ into Http/, Message/ and Plugins/ subfolders. The sync message driver belongs in Message/ alongside the async one, since it's the same message-queue-style request/response category, not a plugin. Namespace stays PactNet.Drivers for now since the rest of the tree hasn't moved yet; that follows once pact-foundation#551 lands.
pact-foundation#551 reorganises Drivers/ into Http/, Message/ and Plugins/ subfolders. The sync message driver belongs in Message/ alongside the async one, since it's the same message-queue-style request/response category, not a plugin. Namespace stays PactNet.Drivers for now since the rest of the tree hasn't moved yet; that follows once pact-foundation#551 lands.
MockServerLogs() drains the process-wide FFI log buffer, which other test classes running in parallel also drain on every Verify, so the assertion that it is non-empty fails intermittently. The logs are still written to the test output. Option 1 from the issue as a stop-gap; the per-server log fix stays open. Refs pact-foundation#568 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@YOU54F I went with option 1 from #568 and dropped the @adamrodger can you take another look when you have a minute? The 🤖 Claude written, Max approved ✅ |
I've hit approve on the CI run - thanks Max! |
fab thanks dude! I’ve just had the last blocker merged in pact-ref for the 0.5.x ffi upgrade today so will get that released tomorrow. Also adding compatibility suite plugin tests so we can cover these new changes too in pact-net. |
|
Please feel free to rebase on top of #569 @adamrodger This is good to go, would it be useful if I created an RFC for changes for pact-net 5.1.0? It would include
I would also like to follow up after that, to stop them waiting too long
These are currently, or all will be supported by examples Along with a few other QoL fixes provided by contributors, and. some maintainer aids such as coverage in the Pact compatibility suite for plugins pact-foundation/pact-compatibility-suite#18 I appreciate it it will be a-lot to review, but I am confident that everything is well tested, and I am happy to support ongoing concerns, issues, and maintenance if you are time poor at the moment. Will be happy holding the bar of quality high for pact-net. |
@adamrodger I took your commit in the dev/plugins branch that you referenced in Issue #492 and made a "raw" plugin implementation that should work with the
PactNet.Extensions.Grpcpackage I created in PR #548.There may still be some design issues to consider. One thing to note is that Plugins can have both synchronous and asynchronous interactions so I made my builder specific to the synchronous use case so we can add an asynchronous version at some point.