Troubleshooting
error NU1102: Unable to find package PactNet with version
Problem
When the Consumer pipeline runs, we get the following error:
D:\a\1\s\tests\tests.csproj : error NU1102: Unable to find package PactNet with version (>= 4.0.0-4386091) [D:\a\1\s\Consumer.sln]
D:\a\1\s\tests\tests.csproj : error NU1102: - Found 99 version(s) in nuget.org [ Nearest version: 3.0.2 ] [D:\a\1\s\Consumer.sln]
D:\a\1\s\tests\tests.csproj : error NU1102: - Found 0 version(s) in Microsoft Visual Studio Offline Packages [D:\a\1\s\Consumer.sln]
D:\a\1\s\tests\tests.csproj : error NU1101: Unable to find package PactNet.Native. No packages exist with this id in source(s): Microsoft Visual Studio Offline Packages, nuget.org [D:\a\1\s\Consumer.sln]
Failed to restore D:\a\1\s\tests\tests.csproj (in 20.1 sec).
Resolution
The problem seems to be due to the fact, that locally we had to add a specific source for the pact’s nuget packages (you can see the NuGet package sources in VS [Options], under [NuGet-Package-Manager]), but on the DevOps Agent, this reference is of course missing.
Run the following command on Agent Pool to see whether the sources necessary for Pact are included or not:
dotnet nuget list source
In the pipeline right before running “dotnet test” or “dotnet run” add the missing package source as follows:
dotnet nuget add source https://pactfoundation.jfrog.io/artifactory/api/nuget/v3/default-nuget-local -n ArtifactoryNuGetV3
The local source … doesn’t exist
Problem
After running the pipeline, the command “dotnet restore” or “dotnet test” fails with the following error:
C:\Program Files\dotnet\sdk\5.0.404\NuGet.targets(131,5): error : The local source 'C:\Users\VssAdministrator\AppData\Roaming\NuGet\ArtifactoryNuGetV3' doesn't exist. [D:\a\1\s\Consumer.sln]
Resolution
Very likely you have used the command “dotnet nuget add source” in the wrong way. You could have confused the key and the value parameters:
Bad:
dotnet nuget add source ArtifactoryNuGetV3 -n https://pactfoundation.jfrog.io/artifactory/api/nuget/v3/default-nuget-local
Good:
dotnet nuget add source https://pactfoundation.jfrog.io/artifactory/api/nuget/v3/default-nuget-local -n ArtifactoryNuGetV3
Pact file and OpenAPI have different casing
Problem
We have the following error in Pactflow:
A request to post an email message
Incompatibility With Provider Contract
Request Body Is Incompatible
Request
Body:
{
"First Name": "Alan",
"Family Name": "Glenn"
}
Headers:
{
"Content-Type": "application/json"
}
Our pact file has properties beginning with uppercase letters while the properties in our OpenAPI (swagger.json) have properties beginning with lowercase letters.
Resolution
Pactflow is case-sensitive and there is no way to change that behavior. To avoid issues caused by incompatible casing you have to ensure that both the pact file and swagger.json file have the same casing.
On the provider side, one option to change the casing of properties would be to use the JsonProperty attribute like this:
[JsonProperty(PropertyName = "
firstName
")]
public string FirstName { get; set; }
System.InvalidOperationException : Unable to start mock server
Problem
You might experience this problem in a Pact.NET consumer test where you have unit tests that call an async method of the tested consumer.
Normally you spin up the mock provider in a consumer unit test once for every test case. But if the tests involve async calls you might run into issues and one such issue is the following exception being thrown:
System.InvalidOperationException : Unable to start mock server
Potential Resolution
You might try to make sure to spin the mock provider per each test case separately and also make sure that you use a free port.
The following code shows an example of how to do that:
public class MyConsumerMyProviderBiDirectPactTest
{
ITestOutputHelper output;
public MyConsumerMyProviderBiDirectPactTest(ITestOutputHelper output)
{
this.output = output;
}
private int _getFreePort()
{
var port = 1024;
for (int p = port; p < 49151; p++)
{
if (IsPortFree(p))
{
port = p;
Console.WriteLine($"Port {port} is currently available.");
return port;
}
else
{
Console.WriteLine($"Port {port} is not available.");
}
}
return port;
}
[Fact]
public async void TestCase1()
{
var config = new PactConfig
{
PactDir = @$"{Directory.GetCurrentDirectory()}/../../../pacts",
LogLevel = PactLogLevel.Trace,
Outputters = (new[]
{
new XUnitOutput(output)
}),
DefaultJsonSettings = new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
}
};
String provider = Environment.GetEnvironmentVariable("PACT_PROVIDER");
IPactV3 pactPrep = Pact.V3("MyConsumer-Bi-Directional", provider ?? "MyProvider-Bi-Directional", config);
var expectedResponse = new[] {
new {
title ="bla bla",
content = "bla bla bla"
},
new {
title ="xyzxyz",
content = "bla bla bla bla"
}
};
var param1 = "xyz";
var param2 = 2;
//Arrange
// the pact builder is created in the constructor so it's unique to each test
var port = _getFreePort();
IPactBuilderV3 pact = pactPrep.WithHttpInteractions(port);
pact.UponReceiving("a request to get users of a specific role and tenant with valid parameters")
.WithRequest(HttpMethod.Get, $"/v1/ParamX/ByParamX")
.WithQuery("param2", $"{param2}")
.WithQuery("param1", param1)
.WithHeader("authorization", "Bearer MyToken")
.WithHeader("host", $"127.0.0.1:{port}")
.WillRespond()
.WithStatus(HttpStatusCode.OK)
.WithHeader("Content-Type", "application/json; charset=utf-8")
.WithJsonBody(expectedResponse);
//Act
await pact.VerifyAsync(async ctx =>
{
var myConsumer = new MyConsumer();
var result = await myConsumer.GetByParamX(param1, param2);
//Assert
Assert.True(result.IsSuccessful);
Assert.Equal(HActionStatus.Ok, result.Status);
});
}
[Fact]
public async void TestCase2()
{
var config = new PactConfig
{
PactDir = @$"{Directory.GetCurrentDirectory()}/../../../pacts",
LogLevel = PactLogLevel.Trace,
Outputters = (new[]
{
new XUnitOutput(output)
}),
DefaultJsonSettings = new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
}
};
String provider = Environment.GetEnvironmentVariable("PACT_PROVIDER");
IPactV3 pactPrep = Pact.V3("MyConsumer-Bi-Directional", provider ?? "MyProvider-Bi-Directional", config);
var expectedResponse = new[] {
new {
title ="aaa bbb",
content = "asdf asdf asdf"
},
new {
title ="xyzxyz",
content = "bla bla bla bla"
}
};
var param1 = "abc";
var param2 = -2;
//Arrange
// the pact builder is created in the constructor so it's unique to each test
var port = _getFreePort();
IPactBuilderV3 pact = pactPrep.WithHttpInteractions(port);
pact.UponReceiving("a request to get users of a specific role and tenant with valid parameters")
.WithRequest(HttpMethod.Get, $"/v1/ParamX/ByParamX")
.WithQuery("param2", $"{param2}")
.WithQuery("param1", param1)
.WithHeader("authorization", "Bearer MyToken")
.WithHeader("host", $"127.0.0.1:{port}")
.WillRespond()
.WithStatus(HttpStatusCode.OK)
.WithHeader("Content-Type", "application/json; charset=utf-8")
.WithJsonBody(expectedResponse);
//Act
await pact.VerifyAsync(async ctx =>
{
var myConsumer = new MyConsumer();
var result = await myConsumer.GetByParamX(param1, param2);
//Assert
Assert.True(result.IsSuccessful);
Assert.Equal(HActionStatus.Ok, result.Status);
});
}
static bool IsPortFree(int port)
{
bool isAvailable = true;
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
try
{
socket.Bind(new IPEndPoint(IPAddress.Any, port));
}
catch (SocketException)
{
isAvailable = false;
}
socket.Close();
return isAvailable;
}
}
In the code shown here, we make sure that the mock provider is configured separately for each test case and we also make sure to avoid port “collisions” by making sure to select a new free port for each mock provider we spin up per test case.
Different port per test means that we spin up a mock server per test that is isolated, in this case, we need pact file write mode set to merge otherwise the last mock server to write to fill might run into issues.