Pact troubleshooting


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.