Eva-Maria Garcia
04/26/2023, 4:44 PMEdwin Raju
04/28/2023, 11:13 AMGitHub
04/28/2023, 12:29 PMGitHub
04/28/2023, 1:14 PMimage▾
GitHub
05/02/2023, 10:43 AMMegha Agarwal
05/02/2023, 12:47 PMpublish_provider_contract:
@echo "\n========== STAGE: publish-provider-contract (spec + results) ==========\n"
${PACTFLOW_CLI_COMMAND} publish-provider-contract \
${OAS_PATH} \
--provider ${PACTICIPANT} \
--provider-app-version ${VERSION} \
--branch ${BRANCH} \
--content-type application/xml \
--verification-exit-code=0 \
--verification-results './report.xml' \
--verification-results-content-type ${REPORT_FILE_CONTENT_TYPE}\
--verifier xunit
where OAS_PATH is the swagger path.
Can someone help me how to publish the verification result without Swagger fileMragni Majhwar
05/03/2023, 5:12 PMMegha Agarwal
05/05/2023, 6:03 AMpublic void EnsureEventApiHonoursPactWithConsumer()
{
string pactPath = Path.Combine("..",
"..",
"..",
"..",
// "Consumer.Tests",
"pacts",
"Poc.Pactflow.Kafka.Consumer1-Poc.Pactflow.Kafka.Producer1.json");
var defaultSettings = new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver(),
DefaultValueHandling = DefaultValueHandling.Ignore,
NullValueHandling = NullValueHandling.Ignore,
Formatting = Formatting.Indented
};
this.verifier
.MessagingProvider("Poc.Pactflow.Kafka.Producer1", defaultSettings)
.WithProviderMessages(scenarios =>
{
scenarios.Add("a single event", () => new NotificationSmsEvent()
{
NotificationId = 237,
PhoneNumber = "123456789",
Message = "event consumed"
})
.Add("some stock events", builder =>
{
builder.WithMetadata(new
{
ContentType = "application/json",
kafka_topic = "products"
})
.WithContent(() => new[] {
new NotificationSmsEvent() { NotificationId = 237, PhoneNumber = "123456789", Message = "event consumed"}
}
);
});
})
.WithFileSource(new FileInfo(pactPath))
.Verify();
}
here this.verifier is the PactVerifier object....
Below is the pactfile generated by Consumer test
{
"consumer": {
"name": "Poc.Pactflow.Kafka.Consumer1"
},
"messages": [
{
"contents": [
{
"message": "event consumed",
"notificationId": 123,
"phoneNumber": "123456789",
"something": "123"
}
],
"description": "some stock events",
"matchingRules": {
"body": {
"$": {
"combine": "AND",
"matchers": [
{
"match": "type",
"min": 1
}
]
},
"$[*].message": {
"combine": "AND",
"matchers": [
{
"match": "type"
}
]
},
"$[*].notificationId": {
"combine": "AND",
"matchers": [
{
"match": "type"
}
]
},
"$[*].phoneNumber": {
"combine": "AND",
"matchers": [
{
"match": "type"
}
]
},
"$[*].something": {
"combine": "AND",
"matchers": [
{
"match": "type"
}
]
}
}
},
"metadata": {
"contentType": "application/json",
"kafka_topic": "products"
},
"providerStates": [
{
"name": "A list of events is pushed to the queue"
}
]
}
],
"metadata": {
"pactRust": {
"ffi": "0.4.0",
"models": "1.0.4"
},
"pactSpecification": {
"version": "3.0.0"
}
},
"provider": {
"name": "Poc.Pactflow.Kafka.Producer1"
}
}
this test is passing.... but when I am changing the contract in pact json, the test is not failing .... but when I change the description it fails......
On debugging I found that thr r no events to verify..... Am I missing anything?Megha Agarwal
05/05/2023, 6:04 AMGitHub
05/05/2023, 10:45 AMPactVerifier
API enforces either verifying HTTP-style pacts or messaging-style pacts, but not both:
var verifier = new PactVerifier();
// HTTP style
verifier.ServiceProvider("My API", new Uri("<http://localhost:5000>"))
.WithFileSource(...)
.Verify();;
// Messaging style
verifier.MessagingProvider("My API")
.WithProviderMessages(scenarios => { ... })
.WithFileSource(...)
.Verify();;
This means that any v4 pacts containing both HTTP and messaging interactions will fail to verify, because you can't specify both a HTTP and messaging provider on the verifier. Even though the v4 pacts themselves support shared pacts, PactNet 4.x does not.
Proposal
A breaking change is made to PactNet 5.x such that you can specify both a HTTP and messaging 'transport' (which is what the Pact FFI calls them internally):
var verifier = new PactVerifier("My API");
verifier
.WithHttpEndpoint(new Uri("<http://localhost:5000>"))
.WithMessages(scenarios =>
{
scenarios.Add<MyEvent>("an event happens")
})
.WithFileSource(new FileInfo(@"..."))
.Verify();
The breaking changes are:
• PactVerifier
requires the provider name as a mandatory constructor parameter (otherwise it would have to be specified on both the HTTP and message transport, and they'd have to match)
• ServiceProvider
is renamed to WithHttpEndpoint
and the provider name parameter is removed
• MessagingProvider
is renamed to WithMessages
, the provider name parameter is removed and the messages are supplied directly instead of via an additional call to WithProviderMessages
• This means IPactVerifierMessagingProvider
is no longer needed and is deleted
• All the WithXSource
methods are moved to IPactVerifier
• This means IPactVerifierProvider
is no longer needed and is deleted
Caveats/Drawbacks
Both WithHttpEndpoint
and WithMessages
are optional in case a particular service doesn't have both types of interactions, but at least one must be provided and at most one of each type. I would ideally like these invariants to be enforced at compile time but that isn't really possible without introducing a very awkward type hierarchy. Consider:
• After you add a HTTP transport you can either add a message transport also or move directly to specifying a source
• If you add a message transport, you must move directly to adding a source
• After you add a message transport you can either add a HTTP transport also or move directly to specifying a source
• If you add a HTTP transport, you must move directly to adding a source
This means you have really awkward types like:
public interface IPactVerifier
{
public IHttpAddedButNotMessageYet WithHttpEndpoint();
public IMessageAddedButNotHttpYet WithMessages();
}
public interface IMessageAddedButNotHttpYet : IPactVerifierTransportsConfigured
{
// 👇 this is a duplicate of the method above in terms of signature, but with different return type
public IPactVerifierTransportsConfigured WithHttpEndpoint();
}
public interface IHttpAddedButNotMessageYet : IPactVerifierTransportsConfigured
{
// 👇 this is a duplicate of the method above in terms of signature, but with different return type
public IPactVerifierTransportsConfigured WithMessages();
}
public interface IPactVerifierTransportsConfigured
{
public IPactVerifierSource WithFileSource(...);
public IPactVerifierSource WithDirectorySource(...);
// etc
}
I don't think the added complexity to the types is worth the added runtime safety in this instance, particular given this is a testing library and we can simply throw InvalidOperationException
if you try to add the same transport twice, or no transports at all. The feedback loop is fast, the error is easy to communicate/fix, and the code isn't production code.
It also creates a combinatorial explosion if in future we support more transport types.
pact-foundation/pact-netGitHub
05/05/2023, 1:28 PMJsonSerializerSettings
to System.Text.Json.JsonSerializerOptions
.
This will create a minimal impact during migration whilst still allowing us to change serialiser.
Internally, PactNet will use System.Text.Json
to serialise matchers and dynamic request/response bodies supply by users,
and to deserialise message interaction requests from the FFI during message interaction verification. To the user these
are entirely transparent changes.
The library will continue to ship as netstandard2.0
instead of changing to a direct .Net version so that existing compatibility
is maintained. System.Text.Json
supports netstandard2.0
explicitly.
Caveats/Drawbacks
Version
There is an open question on exactly which version of STJ to reference. If we pick the current version then we may prevent people from upgrading if they're on older version. If we pick an older version then which one?
Breaking Serialisation Changes
Any custom serialisation options applied to objects previously which were not being used may now start to be used, and so
potential behavioural differences might happen post-upgrade. For example, given the class:
public class MyDto
{
public int Foo { get; init; }
[System.Text.Json.Serialization.JsonIgnore]
public string Bar { get; init; }
}
In PactNet 4.x the Bar
property would still be serialised, whereas following this change it would no longer be serialised.
This behaviour seems advantageous rather than problematic, as it also wouldn't be serialised by the real API under real usage
and thus the pacts were technically using the wrong format.
However, there are situations where the behaviour of serialisation itself may have been altered which could potentially cause
behavioural changes. It's not felt the risk is high or that the consequence is severe in these situations though. The correct
action to perform in that case would ensure that the pacts and API are as close as possible anyway.
pact-foundation/pact-netGitHub
05/05/2023, 1:29 PM.WithJsonBody
, would be nice to be able to pass in a System.Text.Json.JsonSerializerOptions
body.
Another solution would be not to serialize the when the body is already a string (maybe related to this issue #391 )
This following line is the one I want to avoid:
pact-net/src/PactNet/RequestBuilder.cs
Line 376 in </pact-foundation/pact-net/commit/701cb50e6a1e307ee6965079e4880d910572d7bb|701cb50>
pact-foundation/pact-netSlackbot
05/09/2023, 6:15 AMRavi L
05/09/2023, 2:10 PMGitHub
05/10/2023, 1:06 PM<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
add the following package refs:
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
<PackageReference Include="NUnit" Version="3.13.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.4.2" />
<PackageReference Include="PactNet" Version="4.5.0" />
</ItemGroup>
Edit program.cs:
using Newtonsoft.Json.Serialization;
using Newtonsoft.Json;
using NUnit.Framework;
using PactNet;
using PactNet.Matchers;
using PactNet.Verifier;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using <http://System.Net|System.Net>;
using Microsoft.AspNetCore.Http;
// create pact
var pactDir = Path.Join("..", "..", "..", "pacts");
var v3 = Pact.V3("Message Consumer", "Message Producer", new PactConfig
{
PactDir = pactDir,
DefaultJsonSettings = new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
}
});
var messagePact = v3.WithMessageInteractions();
messagePact.ExpectsToReceive("some events")
.Given("events exist")
.WithJsonContent(Match.MinType(new { MessageText = "Hello World"}, 1))
.Verify<ICollection<MyMessage>>(events => Assert.That(events, Is.Not.Empty));
// verify pact
// configure provider states handler
var isProviderStatesCalled = false;
const string pactProviderServiceUri = "<http://127.0.0.1:9001>";
var builder = WebApplication.CreateBuilder(new WebApplicationOptions());
builder.WebHost.UseUrls(pactProviderServiceUri);
await using var app = builder.Build();
app.MapPost("/provider-states", async context =>
{
isProviderStatesCalled = true;
context.Response.StatusCode = (int) HttpStatusCode.OK;
await context.Response.WriteAsync(string.Empty);
});
await app.StartAsync();
var verifier = new PactVerifier(new PactVerifierConfig
{
LogLevel = PactLogLevel.Debug
});
var defaultSettings = new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver(),
DefaultValueHandling = DefaultValueHandling.Ignore,
NullValueHandling = NullValueHandling.Ignore,
Formatting = Formatting.Indented
};
verifier.MessagingProvider("Message Producer", defaultSettings)
.WithProviderMessages(scenarios =>
{
scenarios.Add("some events", scenarioBuilder =>
{
return scenarioBuilder.WithContentAsync(async () =>
{
var events = new List<MyMessage>
{
new MyMessage
{
MessageText = "Hello World"
}
};
Assert.That(isProviderStatesCalled, Is.True);
await Task.CompletedTask;
return events;
});
});
}).WithFileSource(new FileInfo(Path.Join(pactDir, "Message Consumer-Message Producer.json")))
.WithProviderStateUrl(new Uri(new Uri(pactProviderServiceUri), "/provider-states"))
.Verify();
await app.StopAsync();
Console.WriteLine($"isProviderStatesCalled {isProviderStatesCalled}");
public class MyMessage
{
public string MessageText { get; set; }
}
On running the app, the assertion
Assert.That(isProviderStatesCalled, Is.True);
fails because the POST to provider-states has not been made.
Changing to WithContent works:
/* ... */
verifier.MessagingProvider("Message Producer", defaultSettings)
.WithProviderMessages(scenarios =>
{
scenarios.Add("some events", scenarioBuilder =>
{
scenarioBuilder.WithContent(() =>
{
var events = new List<MyMessage>
{
new MyMessage
{
MessageText = "Hello World"
}
};
Assert.That(isProviderStatesCalled, Is.True);
return events;
});
});
}).WithFileSource(new FileInfo(Path.Join(pactDir, "Message Consumer-Message Producer.json")))
.WithProviderStateUrl(new Uri(new Uri(pactProviderServiceUri), "/provider-states"))
.Verify();
pact-foundation/pact-netGitHub
05/10/2023, 2:41 PMIPactVerifier pactVerifier = new PactVerifier(config);
pactVerifier
.ServiceProvider("App.Api", new Uri(_baseUrl))
.WithPactBrokerSource(
new Uri(_providerUrl),
options => options
.TokenAuthentication(_pactflowToken)
.PublishResults(
_providerVersion,
options => options.ProviderTags(_providerTags)
.ProviderBranch(_providerBranch)))
.WithProviderStateUrl(new Uri(_fixture.ServerUri, "/provider-states"))
.WithCustomHeader("Authorization", $"Bearer {tokenValue}")
.Verify();
`
The "WithCustomHeader" function seems to have an issue, which I haven't been able to discern exactly but believe it is one of two things:
1. If a header already exists within the consumer pact json, it does not replace the value as it should.
2. It is only adding an additional header, and in this case, we end up with 2 "Authorization" headers
Either way, the end result is the same in that our API grabs the old token that exists within the pact file and attempts to use it, sees it is expired and throws a 401.
To get this working, we removed the Authorization header from the pact file and everything ran clean from the provider side, but this lead to a new issue within pactflow. Pactflow will check the request headers against our API's OAS, and sees that the authorization header is missing and fails for the consumer side check.
Is it possible to get new functionality to allow users to replace existing headers?
pact-foundation/pact-netGitHub
05/16/2023, 1:07 PMCalum Maciver-Whyte
05/19/2023, 4:34 PMRob Turner
05/22/2023, 3:52 PMmake
file that references pactfoundation/pact-cli
. But this does not exist in the repo so it fails. can't see any sign of this folder anyway. Anyone know what the deal is?Cyrus Devnomad
05/22/2023, 11:17 PMMatt (pactflow.io / pact-js / pact-go)
So, the question arises whether it would be possible to publish two files with two different API versions for the same provider version? Or is one file with two paths the only (and the proper) way to publish two versions for the same provider version?It’s the latter. we only support a single OAS document for a given provider
Matt (pactflow.io / pact-js / pact-go)
Matt (pactflow.io / pact-js / pact-go)
ProviderV1
and ProviderV2
GitHub
05/24/2023, 2:26 PM--fail-if-no-pacts-found
in verifier configuration
pact-foundation/pact-netGitHub
05/24/2023, 2:27 PMeachItem
matcher for collection attributes
pact-foundation/pact-netDavid Hvilava
05/25/2023, 4:31 PM.WithProviderStateUrl(
providerStateUrl,
(e) => {
e.WithTeardown();
Console.WriteLine("Provider state teardown executed.");
});
Tigran Davtyan
05/26/2023, 8:52 AMTigran Davtyan
05/26/2023, 8:54 AMGitHub
05/26/2023, 3:06 PMvar pact = Pact.V3("My API", "Consumer").WithHttpInteractions();
var body = new { value = 10m };
pact.UponReceiving("A request with decimals")
.WithRequest(<http://HttpMethod.Post|HttpMethod.Post>, "/test")
.WithJsonBody(body)
.WillRespond()
.WithStatus(HttpStatusCode.OK);
pact.Verify(context =>
{
var client = new HttpClient { BaseAddress = context.MockServerUri };
client.PostAsJsonAsync("/test", body).Wait();
});
Gives Verification mismatches:
It looks like the library expects the decimals to be serialized by Json.NET (which always serializes floats and decimals with a decimal point), unlike System.Text.Json or other frameworks. pact-foundation/pact-netCopy code{ "actual": "10", "expected": "10.0", "mismatch": "Expected '10.0' to be equal to '10'", "path": "$.value", "type": "BodyMismatch" }
Andrea Berman
06/01/2023, 11:30 PM