GitHub
11/10/2025, 5:35 AMGitHub
11/10/2025, 5:55 AM<https://github.com/pact-foundation/pact-net/tree/master|master> by JP-Ellis
<https://github.com/pact-foundation/pact-net/commit/6c88659ee367f3fab7a269e876166d885cdc79d3|6c88659e> - chore: fix json schema url
pact-foundation/pact-netGitHub
12/28/2025, 10:49 PMprovidertests code to generate OrderCreatedEvent with any number other than 1
Expected behavior
Pact Verify test should fail with body content mismatch
Log Output
ated"}: pact_verifier::messages: actual message = AsynchronousMessage { id: None, key: None, description: "Asynchronous/Message Interaction", provider_states: [], contents: MessageContents { contents: Present(b"{\"id\":30}", Some(ContentType { main_type: "application", sub_type: "json", attributes: {}, suffix: None }), None), metadata: {"contentType": String("application/json")}, matching_rules: MatchingRules { rules: {} }, generators: Generators { categories: {} } }, comments: {}, pending: false, plugin_config: {}, interaction_markup: InteractionMarkup { markup: "", markup_type: "" }, transport: None }
2025-12-28T21:57:19.815008Z DEBUG ThreadId(01) verify_interaction{interaction="an event indicating that an order has been created"}: pact_matching: comparing to expected message: AsynchronousMessage { id: None, key: None, description: "an event indicating that an order has been created", provider_states: [], contents: MessageContents { contents: Present(b"{\"id\":35}", Some(ContentType { main_type: "application", sub_type: "json", attributes: {}, suffix: None }), None), metadata: {}, matching_rules: MatchingRules { rules: {BODY: MatchingRuleCategory { name: BODY, rules: {DocPath { path_tokens: [Root, Field("id")], expr: "$.id" }: RuleList { rules: [Integer], rule_logic: And, cascaded: false }} }} }, generators: Generators { categories: {} } }, comments: {}, pending: false, plugin_config: {}, interaction_markup: InteractionMarkup { markup: "", markup_type: "" }, transport: None }
2025-12-28T21:57:19.815059Z DEBUG ThreadId(01) verify_interaction{interaction="an event indicating that an order has been created"}: pact_matching: expected content type = 'application/json', actual content type = 'application/json'
2025-12-28T21:57:19.815069Z DEBUG ThreadId(01) verify_interaction{interaction="an event indicating that an order has been created"}: pact_plugin_driver::catalogue_manager: Looking for a content matcher for application/json
2025-12-28T21:57:19.815900Z DEBUG ThreadId(01) verify_interaction{interaction="an event indicating that an order has been created"}: pact_matching: No content matcher defined for content type 'application/json', using core matcher implementation
2025-12-28T21:57:19.815909Z DEBUG ThreadId(01) verify_interaction{interaction="an event indicating that an order has been created"}: pact_matching: Using body matcher for content type 'application/json'
2025-12-28T21:57:19.815917Z DEBUG ThreadId(01) verify_interaction{interaction="an event indicating that an order has been created"}: pact_matching::json: compare: Comparing path $
2025-12-28T21:57:19.815919Z DEBUG ThreadId(01) verify_interaction{interaction="an event indicating that an order has been created"}: pact_matching::json: compare_maps: Comparing maps at $: {"id": Number(35)} -> {"id": Number(30)}
2025-12-28T21:57:19.815944Z DEBUG ThreadId(01) verify_interaction{interaction="an event indicating that an order has been created"}: pact_matching::json: compare: Comparing path $.id
2025-12-28T21:57:19.815949Z DEBUG ThreadId(01) verify_interaction{interaction="an event indicating that an order has been created"}: pact_matching::json: compare_values: Calling match_values for path $.id
2025-12-28T21:57:19.815953Z DEBUG ThreadId(01) verify_interaction{interaction="an event indicating that an order has been created"}: pact_matching::json: JSON -> JSON: Comparing '35' (Integer) to '30' (Integer) using Integer -> Ok(())
2025-12-28T21:57:19.815957Z DEBUG ThreadId(01) verify_interaction{interaction="an event indicating that an order has been created"}: pact_matching::json: compare_values: Comparing 'Number(35)' to 'Number(30)' at path '$.id' -> Ok(())
2025-12-28T21:57:19.815975Z DEBUG ThreadId(01) verify_interaction{interaction="an event indicating that an order has been created"}: pact_matching: Matching message metadata
2025-12-28T21:57:19.815977Z DEBUG ThreadId(01) verify_interaction{interaction="an event indicating that an order has been created"}: pact_matching: Matching message metadata. Expected '{"contentType": String("application/json")}', Actual '{"contentType": String("application/json")}'
2025-12-28T21:57:19.815982Z DEBUG ThreadId(01) verify_interaction{interaction="an event indicating that an order has been created"}: pact_matching: Comparing metadata values for key 'contentType'
2025-12-28T21:57:19.815989Z DEBUG ThreadId(01) verify_interaction{interaction="an event indicating that an order has been created"}: pact_matching: Comparing message context type '"application/json"' => '"application/json"'
Additional context
Suspicious text
compare_values: Comparing 'Number(35)' to 'Number(30)' at path '$.id' -> Ok(())
Fulfilment API-Orders API.json excerpt of contents
{
"contents": {
"content": {
"id": 35
},
"contentType": "application/json",
"encoded": false
},
"description": "an event indicating that an order has been created",
"matchingRules": {
"body": {
"$.id": {
"combine": "AND",
"matchers": [
{
"match": "integer"
}
]
}
}
},
"pending": false,
"type": "Asynchronous/Messages"
}
pact-foundation/pact-netPaul Williams
03/02/2026, 8:38 AMNorbert Gulácsi
03/25/2026, 12:17 PMVaibhav Suryawanshi
04/14/2026, 5:18 AMVaibhav Suryawanshi
04/14/2026, 9:32 AMMatt (pactflow.io / pact-js / pact-go)
GitHub
06/08/2026, 2:23 PMGitHub
07/13/2026, 8:05 AM2025-07-10T21:23:11.928119Z ERROR ThreadId(01) pact_verifier::pact_broker: Failed to push branch refs/head/test for provider version test
2025-07-10T21:23:11.928223Z ERROR ThreadId(01) pact_verifier: Publishing of verification results failed with an error: Link/Resource was not found - Request to pact broker path '/pacticipants/myprovider/branches/refs%2Fhead%2Ftest/versions/test' failed: 404 Not Found. URL: '<https://pactbroker.com/>'
Steps To Reproduce
options.PublishResults(brokerConfig.ProviderVersion, publishOptions =>
{
publishOptions
.BuildUri("<http://test>")
.ProviderBranch("/refs/head/test");
});
Expected behavior
The provider verification result should be published to a branch with a slash. This is a supported value with the pact cli to publish consumer contract.
Log Output
See above
Additional context
Replacing / with - make it works.
pact-foundation/pact-netGitHub
07/21/2026, 12:37 AMGitHub
07/31/2026, 2:20 PMcomments.references.AsyncAPI structure.
According to PactFlow's AsyncAPI BDCT documentation, consumer pact files should include operation references like this:
{
"description": "Message interaction",
"comments": {
"references": {
"AsyncAPI": {
"operationId": "receiveUserEvents"
}
}
}
}
Current State in PactNet
PactNet 5.0.1 provides IMessageBuilderV4.WithMetadata() but does not expose a .reference() method to add AsyncAPI operation references.
Workaround (Verified Working)
I manually added the comments.references.AsyncAPI structure to the generated pact file:
{
"interactions": [
{
"description": "Some interaction",
"comments": {
"references": {
"AsyncAPI": {
"operationId": "someOperation"
}
}
},
"metadata": { ... },
"contents": { ... }
}
]
}
After adding this structure manually, PactFlow successfully validated the Message Pact against the AsyncAPI provider specification. This confirms that the feature works end-to-end — PactNet just needs a programmatic way to add these references.
Expected Behavior
PactNet should support something like this:
_pactBuilder
.ExpectsToReceive("Some message")
.Reference("AsyncAPI", "operationId", "someOperation") // ← Missing
.WithJsonContent(new { ... })
.Verify<Message>(...)
Or alternatively:
_pactBuilder
.ExpectsToReceive("Some message")
.WithAsyncApiReference("someOperation") // ← Missing
.WithJsonContent(new { ... })
.Verify<Message>(...)
Use Case
This feature is essential for teams using PactFlow's AsyncAPI BDCT approach with .NET Message Pact consumers. Without it, the generated pact files cannot be automatically validated against AsyncAPI provider specifications in Pactflow.
Environment
• PactNet Version: 5.0.1
• .NET Version: 10.0
• Target: PactFlow AsyncAPI BDCT
Thanks in advance!
pact-foundation/pact-netGitHub
08/04/2026, 6:24 PM5.0.0 and 5.0.1
• Pact Specification: V3 (messages) and V4 (asyncMessage)
• Framework: .NET 8.0 / .NET 10
• OS: Cross-platform (macOS, Windows, Linux)
---
Describe the bug
When performing provider verification for an asynchronous message contract using the new unified PactVerifier.WithMessages(...) builder introduced in PactNet 5.0+, the provider verification instantly fails before executing any message scenario or mapper.
The underlying Rust verification engine attempts to send HTTP requests to a URL with the scheme message:// instead of http://, which causes the internal Rust HTTP client (reqwest) to fail immediately with a URL builder error: builder error for url (<message://localhost:XXXX/pact-messages>).
---
Expected behavior
PactNet starts a tiny local HTTP server within the test process (`http://localhost:PORT/pact-messages/`) to host the messaging scenarios. When instructing the native Rust engine to verify the interactions, the verification engine should send POST requests via HTTP to that internal server (`http://localhost:PORT/pact-messages/`), receive the serialized message, and compare fields against the contract.
---
Actual behavior & Contradictory Logs
The verifier logs reveal the exact contradiction in two sequential lines:
Starting messaging provider at <http://localhost:49152/pact-messages/> <-- PactNet starts HTTP adapter correctly
Sending request to provider at <message://localhost:49152/pact-messages/> <-- Rust FFI engine requests wrong scheme
Request Failed - builder error for url (<message://localhost:49152/pact-messages>)
Because reqwest does not recognize message:// as a valid network transport scheme, the HTTP request never leaves the builder phase, failing with zero field comparisons performed.
Root Cause Analysis
In PactNet 5.0+, when registering the message transport provider via FFI (likely invoking pactffi_verifier_add_provider_transport), two distinct attributes are handled:
protocol: The interaction type (message).
scheme: The network scheme to physically reach the local verification adapter (http).
It appears PactNet correctly registers the protocol as "message", but either leaves the scheme undefined or erroneously passes "message" as the scheme as well. Consequently, the native engine defaults to duplicating the protocol name into the URL scheme, building message://localhost:49152/pact-messages/ instead of http://....
Steps to Reproduce
Create a consumer message test generating a valid message pact (tested with both V3 and V4 specifications).
Set up a provider test using PactVerifier:
csharp
using var verifier = new PactVerifier("MyProvider", config);
verifier
.WithMessages(scenarios => scenarios.Add("a sample message", () => MyMessageFactory.Create()), jsonOptions)
.WithFileSource(pactFilePath)
.Verify();
Run the provider test. Notice that none of the scenario factory methods are invoked, and verification terminates immediately with builder error for url (message://...).
Why this is blocking & tested combinations
We ran systematic testing across multiple matrix combinations to rule out local configuration issues:
PactNet Versions: Fails identically on 5.0.0 and 5.0.1.
Contract Formats: Fails identically on both V3 and V4 specs.
Data/Mapper execution: Confirmed via debugging that scenarios and mappers never get called because the internal POST request fails during URL construction.
Configuration: PactVerifierConfig does not expose any public knobs to manually override the scheme for messaging transports.
pact-foundation/pact-netGitHub
08/10/2026, 11:15 PM<https://github.com/pact-foundation/pact-net/tree/master|master> by JP-Ellis
<https://github.com/pact-foundation/pact-net/commit/519d86279daba984b78b2cea1d9da453a7d661b6|519d8627> - ci: remove unused triage board workflow
pact-foundation/pact-netGitHub
08/13/2026, 2:53 PMWithComment(key, value) - for custom key-value comments
• WithTextComment(comment) - for text comments array
• WithAsyncApiReference(operationId) - convenience method for AsyncAPI refs
Changes
• Core implementation in MessageBuilder and drivers
• FFI bindings for pactffi_set_comment and pactffi_add_text_comment
• Unit, integration, and E2E tests
• Updated sample code and documentation
• V4 only (V3 doesn't support comments per spec)
Testing
All 411 tests passing. Added comprehensive test coverage.
Breaking Changes
None - additive change to IMessageBuilderV4 only.
pact-foundation/pact-netGitHub
09/14/2026, 11:03 AMGitHub
09/16/2026, 6:57 AM<https://github.com/pact-foundation/pact-net/tree/master|master> by adamrodger
<https://github.com/pact-foundation/pact-net/commit/783124aa54f1d3f7b71234c1c759f6fef60ce8bb|783124aa> - deps(ci): actions updates + macos 15 gh runners
pact-foundation/pact-netGitHub
09/23/2026, 2:05 AMGitHub
09/23/2026, 3:08 AM<https://github.com/pact-foundation/pact-net/tree/master|master> by JP-Ellis
<https://github.com/pact-foundation/pact-net/commit/229378fd4d048d570161ac3445681868f1a5cbdd|229378fd> - chore(ci): remove stale internal workflow
pact-foundation/pact-netFábio Rodrigues
09/24/2026, 2:46 PMGitHub
09/24/2026, 2:54 PMIPactV4.WithMessageInteractions()). There is no way to write a V4 synchronous (request/response) message pact - the type PactFlow uses for AsyncAPI 3 operations that have a reply block.
The underlying pact_ffi native library this project wraps already has most of what's needed:
• pactffi_new_sync_message_interaction exists in the native lib, but wasn't yet declared in NativeInterop.cs
• The existing generic pactffi_with_body / pactffi_with_metadata FFI functions (already used for HTTP interactions in this codebase) work against a synchronous message interaction handle too, since they take an InteractionPart (Request/Response)
The one missing piece was a way to retrieve the generated (matchers stripped, generators applied) request/response contents of a synchronous message interaction for the consumer test - pactffi_message_reify only supports async messages. I've proposed pactffi_sync_message_generate_contents upstream in pact-reference: pact-foundation/pact-reference#555
Changes
• IPactV4.WithSynchronousMessageInteractions() - new entry point, parallel to WithMessageInteractions()
• ISyncMessagePactBuilderV4 / ISyncMessageBuilderV4 / IConfiguredSyncMessageVerifier - public API mirroring the existing async IMessagePactBuilderV4 / IMessageBuilderV4 / IConfiguredMessageVerifier, but with separate `WithRequest*`/`WithResponse*` methods for request and response content/metadata
• SyncMessageInteractionDriver / SyncMessageBuilder / SyncMessagePactBuilder / ConfiguredSyncMessageVerifier - internal implementation, following the existing async message classes as closely as possible
• NativeInterop.NewSyncMessageInteraction, NativeInterop.WithMetadata (the InteractionPart-aware variant used for both HTTP and sync messages), NativeInterop.SyncMessageGenerateContents
ConfiguredSyncMessageVerifier.Verify<TRequest, TResponse> reads the generated request via ISyncMessageInteractionDriver.GenerateContents() and passes it to the handler under test; the response content is stored in the pact but not currently asserted against the handler's return value, matching the existing async Verify<T>(Action<T>) semantics (which likewise doesn't assert anything beyond "the handler didn't throw"). Response-value assertion could be a good follow-up once there's agreement on the right semantics for it.
Note: the async message driver/verifier keeps its existing Reify() naming untouched - only the new synchronous-message members use GenerateContents(), since that's less reliant on Pact-specific "reify" terminology for callers who don't need to know about it.
⚠️ Depends on an unreleased pact_ffi function
This PR depends on pactffi_sync_message_generate_contents, proposed in pact-reference#555 and not yet in any pact_ffi release. Until that lands:
• Everything up to calling `.Verify`/`.VerifyAsync` works today, since it only relies on FFI functions that already exist in the currently pinned pact_ffi version.
• `ConfiguredSyncMessageVerifier.Verify`/`VerifyAsync` will throw EntryPointNotFoundException at runtime, since pactffi_sync_message_generate_contents isn't in the native library yet.
I've marked the one FFI-level integration test (FfiIntegrationTests.SyncMessageInteraction_v4_CreatesPactFile) as Skip for the same reason, to keep CI green. Opening this as a draft to get early feedback on the API shape while the pact-reference PR is reviewed; happy to update the pinned pact_ffi version and un-skip that test once a release is available.
Test plan
• dotnet build (all projects)
• dotnet test tests/PactNet.Tests - 166 passed, 1 skipped (the FFI-dependent integration test noted above)
• Added SyncMessageBuilderTests, SyncMessagePactBuilderTests, ConfiguredSyncMessageVerifierTests mirroring the existing async message test coverage
• Documented the new API in docs/messaging-pacts.md
pact-foundation/pact-netGitHub
09/29/2026, 1:50 PMpact_ffi (which are always UTF-8) using `Marshal.PtrToStringAnsi`:
• src/PactNet/Drivers/MessageInteractionDriver.cs:71 (Reify())
• src/PactNet/Drivers/MockServerDriver.cs:42 (MockServerMismatches()) and :55 (MockServerLogs())
• src/PactNet/Verifier/InteropVerifierProvider.cs:297 (verifier output) and :307 (verifier logs)
Per Microsoft's docs, PtrToStringAnsi decodes using the Windows ANSI code page on Windows, but UTF-8 on Unix. Since pact_ffi always returns UTF-8, any non-ASCII character (accents, £/€, CJK, emoji) in a message body, mismatch description, or log line gets corrupted (mojibake) on Windows.
The Reify() case is the most serious: its output is fed directly into JsonSerializer.Deserialize<NativeMessage> in ConfiguredMessageVerifier.MessageReified<T>(), so a message pact with non-ASCII content on Windows either throws a confusing PactMessageConsumerVerificationException or silently hands the consumer's test handler a corrupted body.
Repro: On Windows, define a message pact with WithJsonContent(new { name = "café" }), then verify it — the reified body comes back mangled.
Suggested fix: decode these pointers as UTF-8 explicitly (e.g. read to a null terminator and Encoding.UTF8.GetString, or a helper that doesn't depend on `Marshal`'s platform-dependent "ANSI" behavior) instead of PtrToStringAnsi.
Filed after a manual code review of the current master branch (HEAD around 5.0.1 + a handful of commits); happy to help verify a fix.
pact-foundation/pact-netGitHub
09/29/2026, 1:51 PMWithContents(string contentType, string body, uint size) in src/PactNet/Drivers/MessageInteractionDriver.cs:61-62 always calls:
public void WithContents(string contentType, string body, uint size)
=> NativeInterop.MessageWithContents(this.interaction, contentType, body, new UIntPtr(0));
It discards the size argument entirely instead of forwarding it, always passing UIntPtr(0) to the native pactffi_message_with_contents call.
Every current call site (MessageBuilder.WithJsonContent → this.driver.WithContents("application/json", serialised, 0)) happens to pass 0 already, which is why this hasn't surfaced as a visible bug — but the parameter is effectively dead code. Any future caller that computes and passes a real content length will silently have it discarded.
Suggested fix: pass new UIntPtr(size) instead of hardcoding zero.
Filed after a manual code review of the current master branch (HEAD around 5.0.1 + a handful of commits).
pact-foundation/pact-netGitHub
09/29/2026, 1:53 PMsrc/PactNet/Verifier/Messaging/MessageScenarios.cs:72 blocks on the async configure delegate with:
public IMessageScenarios Add(string description, Func<IMessageScenarioBuilder, Task> configure)
{
var builder = new MessageScenarioBuilder(description);
configure?.Invoke(builder).GetAwaiter().GetResult();
...
}
If this runs under a captured SynchronizationContext (e.g. a WPF/WinForms-hosted provider test process) and the user's async lambda awaits something that needs to resume on that context, this deadlocks the calling thread — the classic sync-over-async trap. The sibling synchronous overloads (Add(string, Func<dynamic>) and Add(string, Action<IMessageScenarioBuilder>)) don't have this risk.
Suggested fix: avoid GetAwaiter().GetResult() here — e.g. make the surrounding call path (`WithMessages`/`PactVerifier`) async, or use Task.Run(...).GetAwaiter().GetResult() to force the continuation off the captured context (with the usual caveats), or ensure the awaited chain uses ConfigureAwait(false) throughout.
Filed after a manual code review of the current master branch (HEAD around 5.0.1 + a handful of commits).
pact-foundation/pact-netGitHub
10/08/2026, 1:16 PMlibpact_ffi 0.5.x, a request header configured as separate values can mismatch
the single comma-separated value sent by an HTTP client. Clients will be expected to
configure the expected header as the joined value instead:
.WithHeader("X-Request", "request1, request2")
This replaces separate calls such as .WithHeader("X-Request", "request1") and
.WithHeader("X-Request", "request2") when those values are sent together in one
request header.
such as
.WithHeader("X-Request", "request1")
.WithHeader("X-Request", "request2")
This is going to change client behaviour so will be a breaking change.
According to RFC 7230,
A sender MUST NOT generate multiple header fields with the same field
name in a message unless either the entire field value for that
header field is defined as a comma-separated list [i.e., #(values)]
or the header field is a well-known exception (as noted below).The only valid exception is
Set-Cookie
So the client is expected to fold up multiple headers into a single comma separated string, so one could argue this change is correct, and correcting a previous bug. If the client was sending two headers of the same name, with a different value, they would not be honouring the above RFC
Not sure what this means for versioning here, minor or major.
pact-foundation/pact-netGitHub
10/12/2026, 7:08 PMGitHub
10/12/2026, 7:30 PMHttpInteraction_v3_CreatesPactFile, Did not expect logs to be empty and MockServerLogs. No prior reports.
Version information:
• OS: macOS Intel (macos-15-intel GitHub runner), but the root cause is platform-independent
• PactNet Version: master / PR #551 branch, pact-ffi 0.5.10
• .Net Version: .NET 8.0
• Pact Broker Version (if applicable): n/a
Describe the bug
PactNet.Tests.Drivers.FfiIntegrationTests.HttpInteraction_v3_CreatesPactFile fails intermittently in CI with:
Did not expect logs to be empty.
at PactNet.Tests.Drivers.FfiIntegrationTests.HttpInteraction_v3_CreatesPactFile() in tests/PactNet.Tests/Drivers/FfiIntegrationTests.cs:line 69
The underlying cause is that PactNet reads mock server logs and driver logs from a single process-wide FFI log buffer, and reading it is destructive:
• MockServerDriver.MockServerLogs() and PactDriver.DriverLogs() both call pactffi_fetch_log_buffer(null), which reads the "global" buffer.
• In pact_ffi, fetch_buffer_contents does buffer.split().freeze(), so every fetch empties the buffer.
• AbstractPactBuilder.Verify / VerifyAsync call PrintLogs → MockServerLogs() in a finally block on every run, pass or fail.
• xUnit runs test classes in parallel, and several classes in PactNet.Tests drive the real FFI (PactExtensionsTests, MessagingProviderTests, InteropVerifierProviderTests, FfiIntegrationTests).
So whenever another class's Verify completes while HttpInteraction_v3_CreatesPactFile is in the middle of its HTTP round trip, the global buffer is drained before line 68 reads it, and the assertion on line 69 fails. The driver logs printed in the test's finally block are empty for the same reason.
The failing run shows the overlap: in the passing ubuntu-latest job of the same run, FfiIntegrationTests.HttpInteraction_v3_CreatesPactFile and PactExtensionsTests.WithHttpInteractions_V3_CreatesExpectedPactFile finished within 1 ms of each other. The slower Intel macOS runner has the widest window for the race, which is where it was first caught.
A side effect of the same design: MockServerLogs() never actually returns the mock server's own request logs. The mock server threads log under their own LOG_ID, so those lines land in a per-server buffer that PactNet never reads (pactffi_mock_server_logs(port)). What MockServerLogs() returns today is the driver-call trace output from whichever threads happened to log to "global" since the last fetch.
Steps To Reproduce
1. Run dotnet test tests/PactNet.Tests repeatedly, ideally on a slow or heavily loaded machine so test classes overlap.
2. Occasionally HttpInteraction_v3_CreatesPactFile fails with Did not expect logs to be empty.
Failing CI run: https://github.com/pact-foundation/pact-net/actions/runs/38292879712/job/114967624969 (macos-15-intel). Every other job in the matrix passed on the same commit.
Expected behavior
Tests that read FFI logs should not be affected by other tests running concurrently in the same process.
Log Output
Failed PactNet.Tests.Drivers.FfiIntegrationTests.HttpInteraction_v3_CreatesPactFile [1 s]
Error Message:
Did not expect logs to be empty.
Stack Trace:
at FluentAssertions.Primitives.StringAssertions`1.NotBeEmpty(String because, Object[] becauseArgs)
at PactNet.Tests.Drivers.FfiIntegrationTests.HttpInteraction_v3_CreatesPactFile() in /Users/runner/work/pact-net/pact-net/tests/PactNet.Tests/Drivers/FfiIntegrationTests.cs:line 69
Standard Output Messages:
Driver Logs
-----------
Additional context
Possible fixes, in increasing order of scope:
1. Drop the logs.Should().NotBeEmpty() assertion in the integration test. The log content is diagnostic; the FFI calls and the pact file comparison are what the test actually verifies.
2. Put every test class that drives the real FFI into one xUnit collection so they never run concurrently.
3. Make MockServerLogs() call pactffi_mock_server_logs(port) so it returns the per-server buffer instead of the shared global one. This is the real fix but changes library behaviour. Note that upstream has LOG_ID marked #[deprecated(note = "This must be moved to the FFI crate")] in pact_mock_server, so the FFI side may change too.
Found while working on #551; the failure is unrelated to that PR, the same code paths exist on master.
pact-foundation/pact-netGitHub
10/16/2026, 5:00 PMGitHub
10/16/2026, 5:09 PMPactNet.Extensions.Grpc package, which is the reference example of a wrapper library built on the raw plugin API described in docs/plugin-interactions.md.
I have read through issue #492 thoroughly and examined the source code and feel that I have a decent understanding of what's involved in adding plugin support, specifically for Grpc. I was able to take @Yousaf Nabi (pactflow.io)'s example and create this PR to get some sample Grpc tests working.
I have extracted a PactNet.Interop package as @adamrodger suggested in Issue #492 . This was not a breaking change since all the classes in this namespace were internal, PactNet now references this new package and none of the pre-existing interface methods have changed. Extracting this package made it possible to create bare-bones Grpc plugin tests and is effectively a very "raw" way of using any plugin. Perhaps a next step would be to wrap the bare-bones methods in a more fluent "raw" interface in a plugin package similar to implementations in other languages as @Matt (pactflow.io / pact-js / pact-go) suggested.
Based on above "raw" tests I was able to refactor and extract shared functionality to create a new PactNet.Extensions.Grpc library that provides a fluent interface similar to the interfaces for http / rest and asynchronous messaging interactions.
An alternative approach here would be to just add <InternalsVisibleTo Include="PactNet.Extensions.Grpc" /> to the PactNet.csproj file but this allows integration of other plugins in repos outside this one so it depends on whether you want to explicitly allow that or not.
pact-foundation/pact-netYousaf Nabi (pactflow.io)