Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Specifying Content-Type serializes Matcher object #1107

Open
4 of 5 tasks
andreas-sundstrom opened this issue Jul 28, 2023 · 3 comments
Open
4 of 5 tasks

Specifying Content-Type serializes Matcher object #1107

andreas-sundstrom opened this issue Jul 28, 2023 · 3 comments
Labels
awaiting feedback Awaiting Feedback from OP documentation Indicates a need for improvements or additions to documentation

Comments

@andreas-sundstrom
Copy link

andreas-sundstrom commented Jul 28, 2023

Software versions

Please provide at least OS and version of pact-js

  • OS: e.g. Windows 10 Enterprise 22H2 19045.3208
  • Consumer Pact library: @pact-foundation/pact 12.1.0
  • Node Version: 18.16.1

Issue Checklist

Please confirm the following:

  • I have upgraded to the latest
  • I have the read the FAQs in the Readme
  • I have triple checked, that there are no unhandled promises in my code and have read the section on intermittent test failures
  • I have set my log level to debug and attached a log file showing the complete request/response cycle
  • For bonus points and virtual high fives, I have created a reproduceable git repository (see below) to illustrate the problem

Expected behaviour

Testing an api-client for an endpoint that returns a string I create a pact provider that responds with a body and an exact content-type. The matching should only ensure it's a string body, but the content-type header must be exact match:

.willRespondWith({
              status: 200,
              headers: {
                'Content-Type': 'text/plain',
              },
              body: MatchersV3.string('07/14/2023 23:47:00'),
            })

I expect something like:

      "response": {
        "body": "07/14/2023 23:47:00",
        "headers": {
          "Content-Type": "text/plain"
        },
        "matchingRules": {
          "body": {
            "$": {
              "combine": "AND",
              "matchers": [
                {
                  "match": "type"
                }
              ]
            }
          }
        },
        "status": 200

Actual behaviour

When providing a content-type header, the actual matcher is serialized to a json-string:

      "response": {
        "body": "{\"pact:matcher:type\":\"type\",\"value\":\"07/14/2023 23:47:00\"}",
        "headers": {
          "Content-Type": "text/plain"
        },
        "status": 200
      }

When not providing a content-type header the body is correct but the header is incorrect:

      "response": {
        "body": "07/14/2023 23:47:00",
        "headers": {
          "Content-Type": "application/json"
        },
        "matchingRules": {
          "body": {
            "$": {
              "combine": "AND",
              "matchers": [
                {
                  "match": "type"
                }
              ]
            }
          }
        },
        "status": 200

Steps to reproduce

// Set up
const pactFileDir = './pacts/';
const consumerName = 'patientoversikt-client';
const providerName = 'patientoversikt-api';
const pactFilePath = `${pactFileDir}${consumerName}-${providerName}.json`;

const mockProvider = new PactV3({
  consumer: consumerName,
  logLevel: 'error',
  dir: pactFileDir,
  provider: providerName,
});

test('latest timestamp', async () => {
          // Arrange
          mockProvider
            .given('latest timestamp')
            .uponReceiving(
              'a request to get data with the most recent time stamp',
            )
            .withRequest({
              method: 'GET',
              path: `/api/v1/vhs/unapproved-care-events/get-latest-time-stamp-from-takecare`,
            })
            .willRespondWith({
              status: 200,
              headers: {
                'Content-Type': 'text/plain',
              },
              body: MatchersV3.string('07/14/2023 23:47:00'),
            });

          return mockProvider.executeTest(async mockService => {
            // Act
            const mockedApi = api(mockService.url);
            const timestamp =
              await mockedApi.fetchLatestTimestampFromTakeCare();

            // Assert - did we get the expected response
            if (timestamp !== undefined) {
              expect(timestamp.data).toEqual('07/14/2023 23:47:00');
            }
          });
        });

Generated pact-file (stub):

    {
      "description": "a request to get data with the most recent time stamp",
      "providerStates": [
        {
          "name": "latest timestamp"
        }
      ],
      "request": {
        "method": "GET",
        "path": "/api/v1/vhs/unapproved-care-events/get-latest-time-stamp-from-takecare"
      },
      "response": {
        "body": "{\"pact:matcher:type\":\"type\",\"value\":\"07/14/2023 23:47:00\"}",
        "headers": {
          "Content-Type": "text/plain"
        },
        "status": 200
      }
    }

If removing the header in the response the body matching rule is correct but content-type is wrong:

            .willRespondWith({
              status: 200,
              body: MatchersV3.string('07/14/2023 23:47:00'),
            });
      "response": {
        "body": "07/14/2023 23:47:00",
        "headers": {
          "Content-Type": "application/json"
        },
        "matchingRules": {
          "body": {
            "$": {
              "combine": "AND",
              "matchers": [
                {
                  "match": "type"
                }
              ]
            }
          }
        },
        "status": 200
      }

Relevant log files

Please ensure you set logging to DEBUG and attach any relevant log files here (or link to a gist).

[10:24:42.638] INFO (12244): 0.4.6: pact native library successfully found, and the correct version
2023-07-28T08:24:42.645840Z DEBUG ThreadId(01) pact_ffi::mock_server::handles: parsed header value: Left("text/plain")
2023-07-28T08:24:42.646751Z DEBUG ThreadId(01) pact_plugin_driver::catalogue_manager: Updated catalogue entries:      
core/transport/http
core/transport/https
2023-07-28T08:24:42.647031Z DEBUG ThreadId(01) pact_plugin_driver::catalogue_manager: Updated catalogue entries:      
core/content-generator/binary
core/content-generator/json
core/content-matcher/json
core/content-matcher/multipart-form-data
core/content-matcher/text
core/content-matcher/xml
2023-07-28T08:24:42.647208Z DEBUG ThreadId(01) pact_plugin_driver::catalogue_manager: Updated catalogue entries:      
core/matcher/v1-equality
core/matcher/v2-max-type
core/matcher/v2-min-type
core/matcher/v2-minmax-type
core/matcher/v2-regex
core/matcher/v2-type
core/matcher/v3-content-type
core/matcher/v3-date
core/matcher/v3-datetime
core/matcher/v3-decimal-type
core/matcher/v3-includes
core/matcher/v3-integer-type
core/matcher/v3-null
core/matcher/v3-number-type
core/matcher/v3-time
core/matcher/v4-array-contains
core/matcher/v4-equals-ignore-order
core/matcher/v4-max-equals-ignore-order
core/matcher/v4-min-equals-ignore-order
core/matcher/v4-minmax-equals-ignore-order
core/matcher/v4-not-empty
core/matcher/v4-semver
 RUNS  src/api.test.ts
2023-07-28T08:24:42.667398Z DEBUG tokio-runtime-worker hyper::proto::h1::io: parsed 4 headers
2023-07-28T08:24:42.667906Z DEBUG tokio-runtime-worker hyper::proto::h1::conn: incoming body is empty
2023-07-28T08:24:42.668277Z DEBUG tokio-runtime-worker pact_mock_server::hyper_server: Creating pact request from hyper request
2023-07-28T08:24:42.668585Z DEBUG tokio-runtime-worker pact_mock_server::hyper_server: Extracting query from uri /api/v1/vhs/unapproved-care-events/get-latest-time-stamp-from-takecare
2023-07-28T08:24:42.668987Z  INFO tokio-runtime-worker pact_mock_server::hyper_server: Received request GET /api/v1/vhs/unapproved-care-events/get-latest-time-stamp-from-takecare
2023-07-28T08:24:42.669319Z DEBUG tokio-runtime-worker pact_mock_server::hyper_server:
      ----------------------------------------------------------------------------------------
       method: GET
       path: /api/v1/vhs/unapproved-care-events/get-latest-time-stamp-from-takecare
       query: None
       headers: Some({"user-agent": ["axios/0.26.1"], "accept": ["application/json", "text/plain", "*/*"], "connection": ["close"], "host": ["127.0.0.1:59990"]})
       body: Empty
      ----------------------------------------------------------------------------------------

2023-07-28T08:24:42.670477Z  INFO tokio-runtime-worker pact_matching: comparing to expected HTTP Request ( method: GET, path: /api/v1/vhs/unapproved-care-events/get-latest-time-stamp-from-takecare, query: None, headers: None, body: Missing )
2023-07-28T08:24:42.670719Z DEBUG tokio-runtime-worker pact_matching:      body: ''
2023-07-28T08:24:42.670921Z DEBUG tokio-runtime-worker pact_matching:      matching_rules: MatchingRules { rules: {PATH: MatchingRuleCategory { name: PATH, rules: {} }} }
2023-07-28T08:24:42.671196Z DEBUG tokio-runtime-worker pact_matching:      generators: Generators { categories: {} }
2023-07-28T08:24:42.671391Z DEBUG tokio-runtime-worker pact_matching::matchers: String -> String: comparing '/api/v1/vhs/unapproved-care-events/get-latest-time-stamp-from-takecare' to '/api/v1/vhs/unapproved-care-events/get-latest-time-stamp-from-takecare' ==> true cascaded=false matcher=Equality
2023-07-28T08:24:42.671672Z DEBUG tokio-runtime-worker pact_matching: expected content type = '*/*', actual content type = '*/*'
2023-07-28T08:24:42.671865Z DEBUG tokio-runtime-worker pact_matching: content type header matcher = 'RuleList { rules: [], rule_logic: And, cascaded: false }'
2023-07-28T08:24:42.672085Z DEBUG tokio-runtime-worker pact_matching: --> Mismatches: []
2023-07-28T08:24:42.672259Z DEBUG tokio-runtime-worker pact_mock_server::hyper_server: Test context = {"mockServer": Object {"port": Number(59990), "url": String("http://127.0.0.1:59990")}}
2023-07-28T08:24:42.672469Z  INFO tokio-runtime-worker pact_mock_server::hyper_server: Request matched, sending response
2023-07-28T08:24:42.672598Z DEBUG tokio-runtime-worker pact_mock_server::hyper_server:
          ----------------------------------------------------------------------------------------
           status: 200
           headers: Some({"Content-Type": ["text/plain"]})
           body: Present(58 bytes)
          ----------------------------------------------------------------------------------------

2023-07-28T08:24:42.673211Z DEBUG tokio-runtime-worker hyper::proto::h1::io: flushed 404 bytes
2023-07-28T08:24:42.675036Z DEBUG ThreadId(01) pact_ffi::mock_server::handles: pact_ffi::mock_server::handles::pactffi_pact_handle_write_file FFI function invoked
2023-07-28T08:24:42.675675Z DEBUG ThreadId(01) pact_models::pact: Writing new pact file to "./pacts/patientoversikt-client-patientoversikt-api.json"
2023-07-28T08:24:42.676660Z DEBUG ThreadId(01) pact_matching::metrics: Could not get the tokio runtime, will not send metrics - there is no reactor running, must be called from the context of a Tokio 1.x runtime
2023-07-28T08:24:42.676966Z DEBUG ThreadId(01) pact_mock_server::server_manager: Shutting down mock server with ID 90f83a6c-1d3a-44f2-993e-db806535673a - MockServerMetrics { requests: 1, requests_by_path: {"/api/v1/vhs/unapproved-care-events/get-latest-time-stamp-from-takecare": 1} }
2023-07-28T08:24:42.677202Z DEBUG ThreadId(01) pact_mock_server::mock_server: Mock server 90f83a6c-1d3a-44f2-993e-db806535673a shutdown - MockServe PASS  src/api.test.ts (9.337 s)_by_path: {"/api/v1/vhs/unapproved-care-events/get-latest-time-stamp-from-takecare": 1} }
@andreas-sundstrom andreas-sundstrom added bug Indicates an unexpected problem or unintended behavior triage This issue is yet to be triaged by a maintainer labels Jul 28, 2023
@mefellows
Copy link
Member

Thanks for this.

Unfortunately, matching rules only work on JSON (and XML) content types - not on plain text (or other content types).

So it's a bug of sorts that we allow you to put a matching rule in there or that we serialise the matching rule as plain text.

If you remove the matcher, I expect it to work (however the provider side would need to match on exact values).

@mefellows mefellows added documentation Indicates a need for improvements or additions to documentation awaiting feedback Awaiting Feedback from OP and removed bug Indicates an unexpected problem or unintended behavior triage This issue is yet to be triaged by a maintainer labels Jul 29, 2023
@andreas-sundstrom
Copy link
Author

Thanks for the answer @mefellows , that does indeed work and it's the way we solved it. Is this specific to the V3 matchers or has this always been the case?

@mefellows
Copy link
Member

Thanks for confirming. No, this has always been the case for Pact.

It could be easily supported now, by creating a plugin for your use case (or creating a general "plain text" one).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
awaiting feedback Awaiting Feedback from OP documentation Indicates a need for improvements or additions to documentation
Projects
None yet
Development

No branches or pull requests

2 participants