pact-foundation / pact-net

.NET version of Pact. Enables consumer driven contract testing, providing a mock service and DSL for the consumer project, and interaction playback and verification for the service provider project.
https://pact.io
MIT License
832 stars 230 forks source link

Match.Regex returning 500 when used in WithHeader #420

Open facusantillo opened 1 year ago

facusantillo commented 1 year ago

Following the tutorial for implementing V3, I found that the client returns an internal server error when trying to use a regex matcher in the header. Seems like it's not working as expected (based in what I've seen from the guide)

Here is the code example

Test implementation:

public async void GetProduct()
        {
            // Arange
            pact.UponReceiving("A valid request for a product")
                    .Given("product with ID 10 exists")
                    .WithRequest(HttpMethod.Get, "/api/products/10")
                    .WithHeader("Authorization", Match.Regex("Bearer 2019-01-14T11:34:18.045Z", "Bearer \\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}Z"))
                .WillRespond()
                    .WithStatus(HttpStatusCode.OK)
                    .WithHeader("Content-Type", "application/json; charset=utf-8")
                    .WithJsonBody(new TypeMatcher(products[1]));

            await pact.VerifyAsync(async ctx =>
            {
                var response = await ApiClient.GetProduct(10);
                Assert.Equal(HttpStatusCode.OK, response.StatusCode);
            });
        }

Client:

 public async Task<HttpResponseMessage> GetProduct(int id)
        {
            using (var client = new HttpClient { BaseAddress = BaseUri })
            {
                try
                {
                    client.DefaultRequestHeaders.Add("Authorization", $"Bearer {DateTime.Now:yyyy-MM-ddTHH:mm:ss.fffZ}");
                    var response = await client.GetAsync($"/api/products/{id}");
                    return response;
                }
                catch (Exception ex)
                {
                    throw new Exception("There was a problem connecting to Provider API.", ex);
                }
            }
        }

Error:

Message: 
Assert.Equal() Failure
Expected: OK
Actual:   InternalServerError

  Stack Trace: 
ApiTest.<GetProduct>b__6_0(IConsumerContext ctx) line 80
PactBuilder.VerifyAsync(Func`2 interact)
ApiTest.GetProduct() line 77
<>c.<ThrowAsync>b__139_0(Object state)

  Standard Output: 
Mock driver logs:

2022-10-18T11:47:49.732152Z  INFO tokio-runtime-worker pact_mock_server::hyper_server: Received request HTTP Request ( method: GET, path: /api/products/10, query: None, headers: Some({"host": ["localhost:9000"], "authorization": ["Bearer 2022-10-18T13:47:47.628Z"]}), body: Empty )
2022-10-18T11:47:49.732290Z  INFO tokio-runtime-worker pact_matching: comparing to expected HTTP Request ( method: GET, path: /api/products/10, query: None, headers: Some({"Authorization": ["Bearer 2019-01-14T11:34:18.045Z"]}), body: Missing )
mefellows commented 1 year ago

Can you please share the full DEBUG level logs for this?

facusantillo commented 1 year ago
Standard Output: 
Mock driver logs:

2022-10-18T12:15:25.022211Z DEBUG tokio-runtime-worker pact_mock_server::hyper_server: Creating pact request from hyper request
2022-10-18T12:15:25.022217Z DEBUG tokio-runtime-worker pact_mock_server::hyper_server: Extracting query from uri /api/products/10
2022-10-18T12:15:25.022255Z  INFO tokio-runtime-worker pact_mock_server::hyper_server: Received request HTTP Request ( method: GET, path: /api/products/10, query: None, headers: Some({"host": ["localhost:9000"], "authorization": ["Bearer 2022-10-18T14:15:22.953Z"]}), body: Empty )
2022-10-18T12:15:25.022313Z  INFO tokio-runtime-worker pact_matching: comparing to expected HTTP Request ( method: GET, path: /api/products/10, query: None, headers: Some({"Authorization": ["Bearer 2019-01-14T11:34:18.045Z"]}), body: Missing )
2022-10-18T12:15:25.022316Z DEBUG tokio-runtime-worker pact_matching:      body: ''
2022-10-18T12:15:25.022318Z DEBUG tokio-runtime-worker pact_matching:      matching_rules: MatchingRules { rules: {PATH: MatchingRuleCategory { name: PATH, rules: {} }, HEADER: MatchingRuleCategory { name: HEADER, rules: {DocPath { path_tokens: [Root, Field("Authorization"), Index(0)], expr: "$.Authorization[0]" }: RuleList { rules: [Regex("Bearer \\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}Z")], rule_logic: And, cascaded: false }} }} }
2022-10-18T12:15:25.022330Z DEBUG tokio-runtime-worker pact_matching:      generators: Generators { categories: {} }
2022-10-18T12:15:25.022357Z DEBUG tokio-runtime-worker pact_matching::matchers: String -> String: comparing '/api/products/10' to '/api/products/10' ==> true cascaded=false matcher=Equality
2022-10-18T12:15:25.022371Z DEBUG tokio-runtime-worker pact_matching: expected content type = '*/*', actual content type = '*/*'
2022-10-18T12:15:25.022387Z DEBUG tokio-runtime-worker pact_matching: content type header matcher = 'RuleList { rules: [], rule_logic: And, cascaded: false }'
2022-10-18T12:15:25.022401Z DEBUG tokio-runtime-worker pact_matching::matchers: String -> String: comparing 'Bearer 2019-01-14T11:34:18.045Z' to 'Bearer 2022-10-18T14:15:22.953Z' ==> false cascaded=false matcher=Equality
2022-10-18T12:15:25.022417Z DEBUG tokio-runtime-worker pact_matching: --> Mismatches: [HeaderMismatch { key: "Authorization", expected: "Bearer 2019-01-14T11:34:18.045Z", actual: "Bearer 2022-10-18T14:15:22.953Z", mismatch: "Mismatch with header 'Authorization': Expected 'Bearer 2019-01-14T11:34:18.045Z' to be equal to 'Bearer 2022-10-18T14:15:22.953Z'" }]
2022-10-18T12:15:25.022452Z DEBUG tokio-runtime-worker pact_mock_server::hyper_server: Request did not match: Request did not match - HTTP Request ( method: GET, path: /api/products/10, query: None, headers: Some({"Authorization": ["Bearer 2019-01-14T11:34:18.045Z"]}), body: Missing )    0) Mismatch with header 'Authorization': Expected 'Bearer 2019-01-14T11:34:18.045Z' to be equal to 'Bearer 2022-10-18T14:15:22.953Z'
adamrodger commented 1 year ago

So the error is that the actual header doesn't match the expected one (it has a different date).

Since you've used a Matcher instead of a literal value then you'd expect that to work, but looks like it's matching literally.

adamrodger commented 1 year ago

PactNer serialises the Matcher properly and submits it to pactffi, so it looks like this is a bug in pactffi to me

https://github.com/pact-foundation/pact-net/blob/a16fac3588972006c0b1f35f934756229a3d3827/src/PactNet/RequestBuilder.cs#L352

adamrodger commented 1 year ago

I wonder if this is caused by https://github.com/pact-foundation/pact-reference/issues/214

The regex library on pactffi is known to be a bit quirky with inputs with partial overlaps, which all ISO dates are very likely to have since they start with the year.

Danae-Planit commented 1 year ago

I can confirm I have reproduced this exact same issue on pactnet 4.2.0+. Match.Type also fails for request headers, and for simple strings such as host - does not have to be using date time regex.

*This issue does not occur in pactnet 4.1.0

SUCCESSFUL: .WithRequest(HttpMethod.Get, "/api/products") .WithHeader("host", "localhost:9000")

FAILS: .WithHeader("host", Match.Type("localhost:8000"))

Response (Failure clue from debug log): 2022-10-27T00:36:38.640811Z DEBUG tokio-runtime-worker pact_matching::matchers: String -> String: comparing 'localhost:8000' to 'localhost:9000' ==> false cascaded=false matcher=Equality 2022-10-27T00:36:38.640828Z DEBUG tokio-runtime-worker pact_matching: --> Mismatches: [HeaderMismatch { key: "host", expected: "localhost:8000", actual: "localhost:9000", mismatch: "Mismatch with header 'host': Expected 'localhost:8000' to be equal to 'localhost:9000'" }]

lehmamic commented 1 year ago

I experienced the same issue. It worked for me with pactnet 4.1.0 as well.

mefellows commented 1 year ago

Raised upstream issue: https://github.com/pact-foundation/pact-reference/issues/238

vcalatayud commented 1 year ago

Could be that this is already fixed? At least my test is not failing anymore. https://github.com/pact-foundation/pact-net/pull/433

adamrodger commented 1 year ago

Yeah we've upgraded FFI since then so there's definitely a chance it was fixed. If you do any more testing and decide this works now then feel free to close.

On Tue, 29 Aug 2023, 06:53 vcalatayud, @.***> wrote:

Could be that this is already fixed? At least my test is not failing anymore. #433 https://github.com/pact-foundation/pact-net/pull/433

— Reply to this email directly, view it on GitHub https://github.com/pact-foundation/pact-net/issues/420#issuecomment-1696813663, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAD4FKU3K6E4BEXLUS2TRSTXXV7U3ANCNFSM6AAAAAARH76IXQ . You are receiving this because you commented.Message ID: @.***>